uppy/src/core/Utils.js
2016-12-07 11:50:23 +01:00

425 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// import mime from 'mime-types'
// import pica from 'pica'
/**
* A collection of small utility functions that help with dom manipulation, adding listeners,
* promises and other good things.
*
* @module Utils
*/
/**
* Shallow flatten nested arrays.
*/
export function flatten (arr) {
return [].concat.apply([], arr)
}
export 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
*/
export 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
*/
export function $$ (selector, ctx) {
var els
if (typeof selector === 'string') {
els = (ctx || document).querySelectorAll(selector)
} else {
els = selector
return Array.prototype.slice.call(els)
}
}
export 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
}
export function secondsToTime (rawSeconds) {
const hours = Math.floor(rawSeconds / 3600) % 24
const minutes = Math.floor(rawSeconds / 60) % 60
const seconds = Math.floor(rawSeconds % 60)
return { hours, minutes, seconds }
}
/**
* Partition array by a grouping function.
* @param {[type]} array Input array
* @param {[type]} groupingFn Grouping function
* @return {[type]} Array of arrays
*/
export function groupBy (array, groupingFn) {
return array.reduce((result, item) => {
let key = groupingFn(item)
let 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
*/
export function every (array, predicateFn) {
return array.reduce((result, item) => {
if (!result) {
return false
}
return predicateFn(item)
}, true)
}
/**
* Converts list into array
*/
export function toArray (list) {
return Array.prototype.slice.call(list || [], 0)
}
/**
* Takes a fileName and turns it into fileID, by converting to lowercase,
* removing extra characters and adding unix timestamp
*
* @param {String} fileName
*
*/
export function generateFileID (fileName) {
let fileID = fileName.toLowerCase()
fileID = fileID.replace(/[^A-Z0-9]/ig, '')
fileID = fileID + Date.now()
return fileID
}
export function extend (...objs) {
return Object.assign.apply(this, [{}].concat(objs))
}
/**
* Takes function or class, returns its name.
* Because IE doesnt 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')
// }
export function getProportionalImageHeight (img, newWidth) {
var aspect = img.width / img.height
var newHeight = Math.round(newWidth / aspect)
return newHeight
}
export function getFileType (file) {
if (file.type) {
return file.type
}
return ''
// return mime.lookup(file.name)
}
// returns [fileName, fileExt]
export function getFileNameAndExtension (fullFileName) {
var re = /(?:\.([^.]+))?$/
var fileExt = re.exec(fullFileName)[1]
var fileName = fullFileName.replace('.' + fileExt, '')
return [fileName, fileExt]
}
/**
* Reads file as data URI from file object,
* the one you get from input[type=file] or drag & drop.
*
* @param {Object} file object
* @return {Promise} dataURL of the file
*
*/
export function readFile (fileObj) {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.addEventListener('load', function (ev) {
return resolve(ev.target.result)
})
reader.readAsDataURL(fileObj)
// function workerScript () {
// self.addEventListener('message', (e) => {
// const file = e.data.file
// try {
// const reader = new FileReaderSync()
// postMessage({
// file: reader.readAsDataURL(file)
// })
// } catch (err) {
// console.log(err)
// }
// })
// }
//
// const worker = makeWorker(workerScript)
// worker.postMessage({file: fileObj})
// worker.addEventListener('message', (e) => {
// const fileDataURL = e.data.file
// console.log('FILE _ DATA _ URL')
// return resolve(fileDataURL)
// })
})
}
/**
* Resizes an image to specified width and proportional height, using canvas
* See https://davidwalsh.name/resize-image-canvas,
* http://babalan.com/resizing-images-with-javascript/
* @TODO see if we need https://github.com/stomita/ios-imagefile-megapixel for iOS
*
* @param {String} Data URI of the original image
* @param {String} width of the resulting image
* @return {String} Data URI of the resized image
*/
export function createImageThumbnail (imgDataURI, newWidth) {
return new Promise((resolve, reject) => {
const img = new Image()
img.addEventListener('load', () => {
const newImageWidth = newWidth
const newImageHeight = getProportionalImageHeight(img, newImageWidth)
// create an off-screen canvas
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
// set its dimension to target size
canvas.width = newImageWidth
canvas.height = newImageHeight
// draw source image into the off-screen canvas:
// ctx.clearRect(0, 0, width, height)
ctx.drawImage(img, 0, 0, newImageWidth, newImageHeight)
// pica.resizeCanvas(img, canvas, (err) => {
// if (err) console.log(err)
// const thumbnail = canvas.toDataURL('image/png')
// return resolve(thumbnail)
// })
// encode image to data-uri with base64 version of compressed image
// canvas.toDataURL('image/jpeg', quality); // quality = [0.0, 1.0]
const thumbnail = canvas.toDataURL('image/png')
return resolve(thumbnail)
})
img.src = imgDataURI
})
}
export 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})
}
export 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}
*/
export function copyToClipboard (textToCopy, fallbackString) {
fallbackString = fallbackString || 'Copy the URL below'
return new Promise((resolve, reject) => {
const 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()
const magicCopyFailed = (err) => {
document.body.removeChild(textArea)
window.prompt(fallbackString, textToCopy)
return reject('Oops, unable to copy displayed fallback prompt: ' + err)
}
try {
const 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)
}
})
}
// export function createInlineWorker (workerFunction) {
// let code = workerFunction.toString()
// code = code.substring(code.indexOf('{') + 1, code.lastIndexOf('}'))
//
// const blob = new Blob([code], {type: 'application/javascript'})
// const worker = new Worker(URL.createObjectURL(blob))
//
// return worker
// }
export function makeWorker (script) {
var URL = window.URL || window.webkitURL
var Blob = window.Blob
var Worker = window.Worker
if (!URL || !Blob || !Worker || !script) {
return null
}
let code = script.toString()
code = code.substring(code.indexOf('{') + 1, code.lastIndexOf('}'))
var blob = new Blob([code])
var worker = new Worker(URL.createObjectURL(blob))
return worker
}
export function getSpeed (fileProgress) {
if (!fileProgress.bytesUploaded) return 0
const timeElapsed = (new Date()) - fileProgress.uploadStarted
const uploadSpeed = fileProgress.bytesUploaded / (timeElapsed / 1000)
return uploadSpeed
}
export function getETA (fileProgress) {
if (!fileProgress.bytesUploaded) return 0
const uploadSpeed = getSpeed(fileProgress)
const bytesRemaining = fileProgress.bytesTotal - fileProgress.bytesUploaded
const secondsRemaining = Math.round(bytesRemaining / uploadSpeed * 10) / 10
return secondsRemaining
}
export function prettyETA (seconds) {
const 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
const hoursStr = time.hours ? time.hours + 'h ' : ''
const minutesVal = time.hours ? ('0' + time.minutes).substr(-2) : time.minutes
const minutesStr = minutesVal ? minutesVal + 'm ' : ''
const secondsVal = minutesVal ? ('0' + time.seconds).substr(-2) : time.seconds
const secondsStr = secondsVal + 's'
return `${hoursStr}${minutesStr}${secondsStr}`
}
export function makeCachingFunction () {
let cachedEl = null
let lastUpdate = Date.now()
return function cacheElement (el, time) {
if (Date.now() - lastUpdate < time) {
return cachedEl
}
cachedEl = el
lastUpdate = Date.now()
return el
}
}
export default {
generateFileID,
toArray,
every,
flatten,
groupBy,
$,
$$,
extend,
readFile,
createImageThumbnail,
getProportionalImageHeight,
isTouchDevice,
getFileNameAndExtension,
truncateString,
getFileType,
secondsToTime,
dataURItoBlob,
dataURItoFile,
getSpeed,
getETA,
makeWorker,
makeCachingFunction
}