import {MultiProgressHandler} from '../utils/progress'; import {loaderList} from './loaderList'; // file content types export const fileContentTypes = { Text: 0, ArrayBuffer: 1, DataURL: 2 }; /** * Files loader. */ export class FilesLoader { /** * Input data. * * @type {File[]} */ #inputData = null; /** * Array of launched file readers. * * @type {FileReader[]} */ #readers = []; /** * Data loader. * * @type {object} */ #runningLoader = null; /** * Number of loaded data. * * @type {number} */ #nLoad = 0; /** * Number of load end events. * * @type {number} */ #nLoadend = 0; /** * The default character set (optional). * * @type {string} */ #defaultCharacterSet; /** * Get the default character set. * * @returns {string} The default character set. */ getDefaultCharacterSet() { return this.#defaultCharacterSet; } /** * Set the default character set. * * @param {string} characterSet The character set. */ setDefaultCharacterSet(characterSet) { this.#defaultCharacterSet = characterSet; } /** * Store the current input. * * @param {File[]} data The input data. */ #storeInputData(data) { this.#inputData = data; // reset counters this.#nLoad = 0; this.#nLoadend = 0; // clear storage this.#clearStoredReaders(); this.#clearStoredLoader(); } /** * Store a launched reader. * * @param {FileReader} reader The launched reader. */ #storeReader(reader) { this.#readers.push(reader); } /** * Clear the stored readers. * */ #clearStoredReaders() { this.#readers = []; } /** * Store the launched loader. * * @param {object} loader The launched loader. */ #storeLoader(loader) { this.#runningLoader = loader; } /** * Clear the stored loader. * */ #clearStoredLoader() { this.#runningLoader = null; } /** * Increment the number of loaded data * and call onload if loaded all data. * * @param {object} _event The load data event. */ #addLoad = (_event) => { this.#nLoad++; // call onload when all is loaded // (not using the input event since it is // an individual load) if (this.#nLoad === this.#inputData.length) { this.onload({ source: this.#inputData }); } }; /** * Increment the counter of load end events * and run callbacks when all done, erroneus or not. * * @param {object} _event The load end event. */ #addLoadend = (_event) => { this.#nLoadend++; // call onloadend when all is run // (not using the input event since it is // an individual load end) if (this.#nLoadend === this.#inputData.length) { this.onloadend({ source: this.#inputData }); } }; /** * @callback eventFn * @param {object} event The event. */ /** * Augment a callback event with a srouce. * * @param {object} callback The callback to augment its event. * @param {object} source The source to add to the event. * @returns {eventFn} The augmented callback. */ #augmentCallbackEvent(callback, source) { return (event) => { event.source = source; callback(event); }; } /** * Get a load handler for a data element. * * @param {object} loader The associated loader. * @param {File} dataElement The data element. * @param {number} i The index of the element. * @returns {eventFn} A load handler. */ #getLoadHandler(loader, dataElement, i) { return (event) => { loader.load(event.target.result, dataElement, i); }; } /** * Load a list of files. * * @param {File[]} data The list of files to load. */ load(data) { // check input if (typeof data === 'undefined' || data.length === 0) { return; } this.#storeInputData(data); // send start event this.onloadstart({ source: data }); // create prgress handler const mproghandler = new MultiProgressHandler(this.onprogress); mproghandler.setNToLoad(data.length); // create loaders const loaders = []; for (let m = 0; m < loaderList.length; ++m) { loaders.push(new loaderList[m]()); } // find an appropriate loader let dataElement = data[0]; let loader = null; let foundLoader = false; for (let l = 0; l < loaders.length; ++l) { loader = loaders[l]; if (loader.canLoadFile(dataElement)) { foundLoader = true; // load options loader.setOptions({ numberOfFiles: data.length, defaultCharacterSet: this.getDefaultCharacterSet() }); // set loader callbacks // loader.onloadstart: nothing to do loader.onprogress = mproghandler.getUndefinedMonoProgressHandler(1); loader.onloaditem = this.onloaditem; loader.onload = this.#addLoad; loader.onloadend = this.#addLoadend; loader.onerror = this.onerror; loader.onabort = this.onabort; // store loader this.#storeLoader(loader); // exit break; } } if (!foundLoader) { throw new Error('No loader found for file: ' + dataElement.name); } // loop on I/O elements for (let i = 0; i < data.length; ++i) { dataElement = data[i]; // check loader if (!loader.canLoadFile(dataElement)) { throw new Error('Input file of different type: ' + dataElement); } /** * The file reader. * * Ref: {@link https://developer.mozilla.org/en-US/docs/Web/API/FileReader}. * * @external FileReader */ const reader = new FileReader(); // store reader this.#storeReader(reader); // set reader callbacks // reader.onloadstart: nothing to do reader.onprogress = this.#augmentCallbackEvent( mproghandler.getMonoProgressHandler(i, 0), dataElement); reader.onload = this.#getLoadHandler(loader, dataElement, i); // reader.onloadend: nothing to do const errorCallback = this.#augmentCallbackEvent(this.onerror, dataElement); reader.onerror = (event) => { this.#addLoadend(); errorCallback(event); }; const abortCallback = this.#augmentCallbackEvent(this.onabort, dataElement); reader.onabort = (event) => { this.#addLoadend(); abortCallback(event); }; // read if (loader.loadFileAs() === fileContentTypes.Text) { reader.readAsText(dataElement); } else if (loader.loadFileAs() === fileContentTypes.DataURL) { reader.readAsDataURL(dataElement); } else if (loader.loadFileAs() === fileContentTypes.ArrayBuffer) { reader.readAsArrayBuffer(dataElement); } } } /** * Abort a load. */ abort() { // abort readers for (let i = 0; i < this.#readers.length; ++i) { // 0: EMPTY, 1: LOADING, 2: DONE if (this.#readers[i].readyState === 1) { this.#readers[i].abort(); } } // abort loader if (this.#runningLoader && this.#runningLoader.isLoading()) { this.#runningLoader.abort(); } } /** * Handle a load start event. * Default does nothing. * * @param {object} _event The load start event. */ onloadstart(_event) {} /** * Handle a load progress event. * Default does nothing. * * @param {object} _event The progress event. */ onprogress(_event) {} /** * Handle a load item event. * Default does nothing. * * @param {object} _event The load item event fired * when a file item has been loaded successfully. */ onloaditem(_event) {} /** * Handle a load event. * Default does nothing. * * @param {object} _event The load event fired * when a file has been loaded successfully. */ onload(_event) {} /** * Handle a load end event. * Default does nothing. * * @param {object} _event The load end event fired * when a file load has completed, successfully or not. */ onloadend(_event) {} /** * Handle an error event. * Default does nothing. * * @param {object} _event The error event. */ onerror(_event) {} /** * Handle an abort event. * Default does nothing. * * @param {object} _event The abort event. */ onabort(_event) {} } // class FilesLoader