UNPKG

uppy

Version:

Almost as cute as a Puppy :dog:

614 lines (531 loc) 17.8 kB
'use strict'; var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; var _Promise = typeof Promise === 'undefined' ? require('es6-promise').Promise : Promise; var throttle = require('lodash.throttle' // we inline file-type module, as opposed to using the NPM version, // because of this https://github.com/sindresorhus/file-type/issues/78 // and https://github.com/sindresorhus/copy-text-to-clipboard/issues/5 );var fileType = require('../vendor/file-type' /** * A collection of small utility functions that help with dom manipulation, adding listeners, * promises and other good things. * * @module Utils */ /** * Shallow flatten nested arrays. */ );function flatten(arr) { return [].concat.apply([], arr); } function isTouchDevice() { return 'ontouchstart' in window || // works on most browsers navigator.maxTouchPoints; // works on IE10/11 and Surface } // /** // * Shorter and fast way to select a single node in the DOM // * @param { String } selector - unique dom selector // * @param { Object } ctx - DOM node where the target of our search will is located // * @returns { Object } dom node found // */ // function $ (selector, ctx) { // return (ctx || document).querySelector(selector) // } // /** // * Shorter and fast way to select multiple nodes in the DOM // * @param { String|Array } selector - DOM selector or nodes list // * @param { Object } ctx - DOM node where the targets of our search will is located // * @returns { Object } dom nodes found // */ // function $$ (selector, ctx) { // var els // if (typeof selector === 'string') { // els = (ctx || document).querySelectorAll(selector) // } else { // els = selector // return Array.prototype.slice.call(els) // } // } function truncateString(str, length) { if (str.length > length) { return str.substr(0, length / 2) + '...' + str.substr(str.length - length / 4, str.length); } return str; // more precise version if needed // http://stackoverflow.com/a/831583 } function secondsToTime(rawSeconds) { var hours = Math.floor(rawSeconds / 3600) % 24; var minutes = Math.floor(rawSeconds / 60) % 60; var seconds = Math.floor(rawSeconds % 60); return { hours: hours, minutes: minutes, seconds: seconds }; } /** * Partition array by a grouping function. * @param {[type]} array Input array * @param {[type]} groupingFn Grouping function * @return {[type]} Array of arrays */ function groupBy(array, groupingFn) { return array.reduce(function (result, item) { var key = groupingFn(item); var xs = result.get(key) || []; xs.push(item); result.set(key, xs); return result; }, new Map()); } /** * Tests if every array element passes predicate * @param {Array} array Input array * @param {Object} predicateFn Predicate * @return {bool} Every element pass */ function every(array, predicateFn) { return array.reduce(function (result, item) { if (!result) { return false; } return predicateFn(item); }, true); } /** * Converts list into array */ function toArray(list) { return Array.prototype.slice.call(list || [], 0); } /** * Takes a file object and turns it into fileID, by converting file.name to lowercase, * removing extra characters and adding type, size and lastModified * * @param {Object} file * @return {String} the fileID * */ function generateFileID(file) { // filter is needed to not join empty values with `-` return ['uppy', file.name ? file.name.toLowerCase().replace(/[^A-Z0-9]/ig, '') : '', file.type, file.data.size, file.data.lastModified].filter(function (val) { return val; }).join('-'); } function extend() { for (var _len = arguments.length, objs = Array(_len), _key = 0; _key < _len; _key++) { objs[_key] = arguments[_key]; } return Object.assign.apply(this, [{}].concat(objs)); } /** * Runs an array of promise-returning functions in sequence. */ function runPromiseSequence(functions) { for (var _len2 = arguments.length, args = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { args[_key2 - 1] = arguments[_key2]; } var promise = Promise.resolve(); functions.forEach(function (func) { promise = promise.then(function () { return func.apply(undefined, args); }); }); return promise; } /** * Takes function or class, returns its name. * Because IE doesn’t support `constructor.name`. * https://gist.github.com/dfkaye/6384439, http://stackoverflow.com/a/15714445 * * @param {Object} fn — function * */ // function getFnName (fn) { // var f = typeof fn === 'function' // var s = f && ((fn.name && ['', fn.name]) || fn.toString().match(/function ([^\(]+)/)) // return (!f && 'not a function') || (s && s[1] || 'anonymous') // } function isPreviewSupported(fileTypeSpecific) { // list of images that browsers can preview if (/^(jpeg|gif|png|svg|svg\+xml|bmp)$/.test(fileTypeSpecific)) { return true; } return false; } function getArrayBuffer(chunk) { return new _Promise(function (resolve, reject) { var reader = new FileReader(); reader.addEventListener('load', function (e) { // e.target.result is an ArrayBuffer resolve(e.target.result); }); reader.addEventListener('error', function (err) { console.error('FileReader error' + err); reject(err); } // file-type only needs the first 4100 bytes );reader.readAsArrayBuffer(chunk); }); } function getFileType(file) { var emptyFileType = ['', '']; var extensionsToMime = { 'md': 'text/markdown', 'markdown': 'text/markdown', 'mp4': 'video/mp4', 'mp3': 'audio/mp3' // no smart detection for remote files, just trust the provider };if (file.isRemote) { return Promise.resolve(file.type.split('/')); } var fileExtension = getFileNameAndExtension(file.name)[1]; // 1. try to determine file type from magic bytes with file-type module // this should be the most trustworthy way var chunk = file.data.slice(0, 4100); return getArrayBuffer(chunk).then(function (buffer) { var type = fileType(buffer); if (type && type.mime) { return type.mime.split('/'); } // 2. if that’s no good, check if mime type is set in the file object if (file.type) { return file.type.split('/'); } // 3. if that’s no good, see if we can map extension to a mime type if (extensionsToMime[fileExtension]) { return extensionsToMime[fileExtension].split('/'); } // if all fails, well, return empty return emptyFileType; }).catch(function () { return emptyFileType; } // if (file.type) { // return Promise.resolve(file.type.split('/')) // } // return mime.lookup(file.name) // return file.type ? file.type.split('/') : ['', ''] ); } // TODO Check which types are actually supported in browsers. Chrome likes webm // from my testing, but we may need more. // We could use a library but they tend to contain dozens of KBs of mappings, // most of which will go unused, so not sure if that's worth it. var mimeToExtensions = { 'video/ogg': 'ogv', 'audio/ogg': 'ogg', 'video/webm': 'webm', 'audio/webm': 'webm', 'video/mp4': 'mp4', 'audio/mp3': 'mp3' }; function getFileTypeExtension(mimeType) { return mimeToExtensions[mimeType] || null; } // returns [fileName, fileExt] function getFileNameAndExtension(fullFileName) { var re = /(?:\.([^.]+))?$/; var fileExt = re.exec(fullFileName)[1]; var fileName = fullFileName.replace('.' + fileExt, ''); return [fileName, fileExt]; } function supportsMediaRecorder() { return typeof MediaRecorder === 'function' && !!MediaRecorder.prototype && typeof MediaRecorder.prototype.start === 'function'; } /** * Check if a URL string is an object URL from `URL.createObjectURL`. * * @param {string} url * @return {boolean} */ function isObjectURL(url) { return url.indexOf('blob:') === 0; } function getProportionalHeight(img, width) { var aspect = img.width / img.height; return Math.round(width / aspect); } /** * Create a thumbnail for the given Uppy file object. * * @param {{data: Blob}} file * @param {number} width * @return {Promise} */ function createThumbnail(file, targetWidth) { var originalUrl = URL.createObjectURL(file.data); var onload = new _Promise(function (resolve, reject) { var image = new Image(); image.src = originalUrl; image.onload = function () { URL.revokeObjectURL(originalUrl); resolve(image); }; image.onerror = function () { // The onerror event is totally useless unfortunately, as far as I know URL.revokeObjectURL(originalUrl); reject(new Error('Could not create thumbnail')); }; }); return onload.then(function (image) { var targetHeight = getProportionalHeight(image, targetWidth); var canvas = resizeImage(image, targetWidth, targetHeight); return canvasToBlob(canvas, 'image/jpeg'); }).then(function (blob) { return URL.createObjectURL(blob); }); } /** * Resize an image to the target `width` and `height`. * * Returns a Canvas with the resized image on it. */ function resizeImage(image, targetWidth, targetHeight) { var sourceWidth = image.width; var sourceHeight = image.height; if (targetHeight < image.height / 2) { var steps = Math.floor(Math.log(image.width / targetWidth) / Math.log(2)); var stepScaled = downScaleInSteps(image, steps); image = stepScaled.image; sourceWidth = stepScaled.sourceWidth; sourceHeight = stepScaled.sourceHeight; } var canvas = document.createElement('canvas'); canvas.width = targetWidth; canvas.height = targetHeight; var context = canvas.getContext('2d'); context.drawImage(image, 0, 0, sourceWidth, sourceHeight, 0, 0, targetWidth, targetHeight); return canvas; } /** * Downscale an image by 50% `steps` times. */ function downScaleInSteps(image, steps) { var source = image; var currentWidth = source.width; var currentHeight = source.height; var canvas = document.createElement('canvas'); var context = canvas.getContext('2d'); canvas.width = currentWidth / 2; canvas.height = currentHeight / 2; for (var i = 0; i < steps; i += 1) { context.drawImage(source, // The entire source image. We pass width and height here, // because we reuse this canvas, and should only scale down // the part of the canvas that contains the previous scale step. 0, 0, currentWidth, currentHeight, // Draw to 50% size 0, 0, currentWidth / 2, currentHeight / 2); currentWidth /= 2; currentHeight /= 2; source = canvas; } return { image: canvas, sourceWidth: currentWidth, sourceHeight: currentHeight }; } /** * Save a <canvas> element's content to a Blob object. * * @param {HTMLCanvasElement} canvas * @return {Promise} */ function canvasToBlob(canvas, type, quality) { if (canvas.toBlob) { return new _Promise(function (resolve) { canvas.toBlob(resolve, type, quality); }); } return Promise.resolve().then(function () { return dataURItoBlob(canvas.toDataURL(type, quality), {}); }); } function dataURItoBlob(dataURI, opts, toFile) { // get the base64 data var data = dataURI.split(',')[1]; // user may provide mime type, if not get it from data URI var mimeType = opts.mimeType || dataURI.split(',')[0].split(':')[1].split(';')[0]; // default to plain/text if data URI has no mimeType if (mimeType == null) { mimeType = 'plain/text'; } var binary = atob(data); var array = []; for (var i = 0; i < binary.length; i++) { array.push(binary.charCodeAt(i)); } // Convert to a File? if (toFile) { return new File([new Uint8Array(array)], opts.name || '', { type: mimeType }); } return new Blob([new Uint8Array(array)], { type: mimeType }); } function dataURItoFile(dataURI, opts) { return dataURItoBlob(dataURI, opts, true); } /** * Copies text to clipboard by creating an almost invisible textarea, * adding text there, then running execCommand('copy'). * Falls back to prompt() when the easy way fails (hello, Safari!) * From http://stackoverflow.com/a/30810322 * * @param {String} textToCopy * @param {String} fallbackString * @return {Promise} */ function copyToClipboard(textToCopy, fallbackString) { fallbackString = fallbackString || 'Copy the URL below'; return new _Promise(function (resolve, reject) { var textArea = document.createElement('textarea'); textArea.setAttribute('style', { position: 'fixed', top: 0, left: 0, width: '2em', height: '2em', padding: 0, border: 'none', outline: 'none', boxShadow: 'none', background: 'transparent' }); textArea.value = textToCopy; document.body.appendChild(textArea); textArea.select(); var magicCopyFailed = function magicCopyFailed(err) { document.body.removeChild(textArea); window.prompt(fallbackString, textToCopy); return reject('Oops, unable to copy displayed fallback prompt: ' + err); }; try { var successful = document.execCommand('copy'); if (!successful) { return magicCopyFailed('copy command unavailable'); } document.body.removeChild(textArea); return resolve(); } catch (err) { document.body.removeChild(textArea); return magicCopyFailed(err); } }); } function getSpeed(fileProgress) { if (!fileProgress.bytesUploaded) return 0; var timeElapsed = new Date() - fileProgress.uploadStarted; var uploadSpeed = fileProgress.bytesUploaded / (timeElapsed / 1000); return uploadSpeed; } function getBytesRemaining(fileProgress) { return fileProgress.bytesTotal - fileProgress.bytesUploaded; } function getETA(fileProgress) { if (!fileProgress.bytesUploaded) return 0; var uploadSpeed = getSpeed(fileProgress); var bytesRemaining = getBytesRemaining(fileProgress); var secondsRemaining = Math.round(bytesRemaining / uploadSpeed * 10) / 10; return secondsRemaining; } function prettyETA(seconds) { var time = secondsToTime(seconds // Only display hours and minutes if they are greater than 0 but always // display minutes if hours is being displayed // Display a leading zero if the there is a preceding unit: 1m 05s, but 5s );var hoursStr = time.hours ? time.hours + 'h ' : ''; var minutesVal = time.hours ? ('0' + time.minutes).substr(-2) : time.minutes; var minutesStr = minutesVal ? minutesVal + 'm ' : ''; var secondsVal = minutesVal ? ('0' + time.seconds).substr(-2) : time.seconds; var secondsStr = secondsVal + 's'; return '' + hoursStr + minutesStr + secondsStr; } /** * Check if an object is a DOM element. Duck-typing based on `nodeType`. * * @param {*} obj */ function isDOMElement(obj) { return obj && (typeof obj === 'undefined' ? 'undefined' : _typeof(obj)) === 'object' && obj.nodeType === Node.ELEMENT_NODE; } /** * Find a DOM element. * * @param {Node|string} element * @return {Node|null} */ function findDOMElement(element) { if (typeof element === 'string') { return document.querySelector(element); } if ((typeof element === 'undefined' ? 'undefined' : _typeof(element)) === 'object' && isDOMElement(element)) { return element; } } /** * Find one or more DOM elements. * * @param {string} element * @return {Array|null} */ function findAllDOMElements(element) { if (typeof element === 'string') { var elements = [].slice.call(document.querySelectorAll(element)); return elements.length > 0 ? elements : null; } if ((typeof element === 'undefined' ? 'undefined' : _typeof(element)) === 'object' && isDOMElement(element)) { return [element]; } } function getSocketHost(url) { // get the host domain var regex = /^(?:https?:\/\/|\/\/)?(?:[^@\n]+@)?(?:www\.)?([^\n]+)/; var host = regex.exec(url)[1]; var socketProtocol = location.protocol === 'https:' ? 'wss' : 'ws'; return socketProtocol + '://' + host; } function _emitSocketProgress(uploader, progressData, file) { var progress = progressData.progress, bytesUploaded = progressData.bytesUploaded, bytesTotal = progressData.bytesTotal; if (progress) { uploader.core.log('Upload progress: ' + progress); uploader.core.emitter.emit('core:upload-progress', { uploader: uploader, id: file.id, bytesUploaded: bytesUploaded, bytesTotal: bytesTotal }); } } var emitSocketProgress = throttle(_emitSocketProgress, 300, { leading: true, trailing: true }); module.exports = { generateFileID: generateFileID, toArray: toArray, every: every, flatten: flatten, groupBy: groupBy, extend: extend, runPromiseSequence: runPromiseSequence, supportsMediaRecorder: supportsMediaRecorder, isTouchDevice: isTouchDevice, getFileNameAndExtension: getFileNameAndExtension, truncateString: truncateString, getFileTypeExtension: getFileTypeExtension, getFileType: getFileType, getArrayBuffer: getArrayBuffer, isPreviewSupported: isPreviewSupported, isObjectURL: isObjectURL, createThumbnail: createThumbnail, secondsToTime: secondsToTime, dataURItoBlob: dataURItoBlob, dataURItoFile: dataURItoFile, getSpeed: getSpeed, getBytesRemaining: getBytesRemaining, getETA: getETA, copyToClipboard: copyToClipboard, prettyETA: prettyETA, findDOMElement: findDOMElement, findAllDOMElements: findAllDOMElements, getSocketHost: getSocketHost, emitSocketProgress: emitSocketProgress }; //# sourceMappingURL=Utils.js.map