enketo-core
Version:
Extensible Enketo form engine
198 lines (178 loc) • 5.78 kB
JavaScript
/**
* @module fileManager
*
* @description Simple file manager with cross-browser support. That uses the FileReader
* to create previews. Can be replaced with a more advanced version that
* obtains files from storage.
*
* The replacement should support the same public methods and return the same
* types.
*/
import $ from 'jquery';
import { t } from 'enketo/translator';
import { getFilename, dataUriToBlobSync } from './utils';
const fileManager = {};
const URL_RE = /[a-zA-Z0-9+-.]+?:\/\//;
/**
* @static
* @function init
*
* @description Initialize the file manager.
*
* @return {Promise|boolean|Error} promise boolean or rejection with Error
*/
fileManager.init = () => Promise.resolve(true);
/**
* @static
* @function isWaitingForPermissions
*
* @description Whether the filemanager is waiting for user permissions
*
* @return {boolean} [description]
*/
fileManager.isWaitingForPermissions = () => false;
/**
* @static
* @function getFileUrl
*
* @description Obtains a URL that can be used to show a preview of the file when used
* as a src attribute.
*
* It is meant for media previews and media downloads.
*
* @param {?string|object} subject - File or filename in local storage
* @return {Promise|string|Error} promise url string or rejection with Error
*/
fileManager.getFileUrl = (subject) =>
new Promise((resolve, reject) => {
let error;
if (!subject) {
resolve(null);
} else if (typeof subject === 'string') {
// TODO obtain from storage as http URL or objectURL
// or from model for default binary files
// Very crude URL checker which is fine for now,
// because at this point we don't expect anything other than jr://
if (URL_RE.test(subject)) {
resolve(subject);
} else {
reject('no!');
}
} else if (typeof subject === 'object') {
if (fileManager.isTooLarge(subject)) {
error = new Error(
t('filepicker.toolargeerror', {
maxSize: fileManager.getMaxSizeReadable(),
})
);
reject(error);
} else {
resolve(URL.createObjectURL(subject));
}
} else {
reject(new Error('Unknown error occurred'));
}
});
/**
* @static
* @function getObjectUrl
*
* @description Similar to getFileURL, except that this one is guaranteed to return an objectURL
*
* It is meant for loading images into a canvas.
*
* @param {?string|object} subject - File or filename in local storage
* @return {Promise|string|Error} promise url string or rejection with Error
*/
fileManager.getObjectUrl = (subject) =>
fileManager.getFileUrl(subject).then((url) => {
if (/https?:\/\//.test(url)) {
return fileManager.urlToBlob(url).then(URL.createObjectURL);
}
return url;
});
/**
* @static
* @function urlToBlob
*
* @param {string} url - url to get
* @return {Promise} promise of XMLHttpRequesting given url
*/
fileManager.urlToBlob = (url) => {
const xhr = new XMLHttpRequest();
return new Promise((resolve) => {
xhr.open('GET', url);
xhr.responseType = 'blob';
xhr.onload = () => {
resolve(xhr.response);
};
xhr.send();
});
};
/**
* @static
* @function getCurrentFiles
*
* @description Obtain files currently stored in file input elements of open record
*
* @return {Array.File} array of files
*/
fileManager.getCurrentFiles = () => {
const files = [];
// Get any files inside file input elements or text input elements for drawings.
$('form.or')
.find(
'input[type="file"]:not(.ignore), input[type="text"][data-drawing="true"]'
)
.each(function () {
let newFilename;
let file = null;
if (this.type === 'file') {
file = this.files[0]; // Why doesn't this fail for empty file inputs?
} else if (
this.value &&
!URL_RE.test(this.value) &&
this.dataset?.cache
) {
// Load drawing from cache
file = dataUriToBlobSync(this.dataset.cache);
file.name = this.value;
}
if (file && file.name) {
// Correct file names by adding a unique-ish postfix
// First create a clone, because the name property is immutable
// TODO: in the future, when browser support increase we can invoke
// the File constructor to do this.
newFilename = getFilename(file, this.dataset.filenamePostfix);
// If file is resized, get Blob representation of data URI
if (this.dataset.resized && this.dataset.resizedDataURI) {
file = dataUriToBlobSync(this.dataset.resizedDataURI);
}
file = new Blob([file], {
type: file.type,
});
file.name = newFilename;
files.push(file);
}
});
return files;
};
/**
* @static
* @function isTooLarge
*
* @description Placeholder function to check if file size is acceptable.
*
* @return {boolean} whether file is too large
*/
fileManager.isTooLarge = () => false;
/**
* @static
* @function getMaxSizeReadable
*
* @description Replace with function that determines max size published in OpenRosa server response header.
*
* @return {string} human radable maximiym size
*/
fileManager.getMaxSizeReadable = () => `${5}MB`;
export default fileManager;