瀏覽代碼

Copy all utils to individual files.

Renée Kooi 7 年之前
父節點
當前提交
bafa247867

+ 18 - 0
src/utils/canvasToBlob.js

@@ -0,0 +1,18 @@
+const dataURItoBlob = require('./dataURItoBlob')
+
+/**
+ * Save a <canvas> element's content to a Blob object.
+ *
+ * @param {HTMLCanvasElement} canvas
+ * @return {Promise}
+ */
+module.exports = function canvasToBlob (canvas, type, quality) {
+  if (canvas.toBlob) {
+    return new Promise((resolve) => {
+      canvas.toBlob(resolve, type, quality)
+    })
+  }
+  return Promise.resolve().then(() => {
+    return dataURItoBlob(canvas.toDataURL(type, quality), {})
+  })
+}

+ 51 - 0
src/utils/copyToClipboard.js

@@ -0,0 +1,51 @@
+/**
+ * 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}
+ */
+module.exports = function copyToClipboard (textToCopy, fallbackString) {
+  fallbackString = fallbackString || 'Copy the URL below'
+
+  return new Promise((resolve) => {
+    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 = () => {
+      document.body.removeChild(textArea)
+      window.prompt(fallbackString, textToCopy)
+      resolve()
+    }
+
+    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)
+    }
+  })
+}

+ 25 - 0
src/utils/dataURItoBlob.js

@@ -0,0 +1,25 @@
+module.exports = 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})
+}

+ 5 - 0
src/utils/dataURItoFile.js

@@ -0,0 +1,5 @@
+const dataURItoBlob = require('./dataURItoBlob')
+
+module.exports = function dataURItoFile (dataURI, opts) {
+  return dataURItoBlob(dataURI, opts, true)
+}

+ 15 - 0
src/utils/emitSocketProgress.js

@@ -0,0 +1,15 @@
+const throttle = require('lodash.throttle')
+
+function _emitSocketProgress (uploader, progressData, file) {
+  const { progress, bytesUploaded, bytesTotal } = progressData
+  if (progress) {
+    uploader.uppy.log(`Upload progress: ${progress}`)
+    uploader.uppy.emit('upload-progress', file, {
+      uploader,
+      bytesUploaded: bytesUploaded,
+      bytesTotal: bytesTotal
+    })
+  }
+}
+
+module.exports = throttle(_emitSocketProgress, 300, {leading: true, trailing: true})

+ 18 - 0
src/utils/findAllDOMElements.js

@@ -0,0 +1,18 @@
+const isDOMElement = require('./isDOMElement')
+
+/**
+ * Find one or more DOM elements.
+ *
+ * @param {string} element
+ * @return {Array|null}
+ */
+module.exports = function findAllDOMElements (element) {
+  if (typeof element === 'string') {
+    const elements = [].slice.call(document.querySelectorAll(element))
+    return elements.length > 0 ? elements : null
+  }
+
+  if (typeof element === 'object' && isDOMElement(element)) {
+    return [element]
+  }
+}

+ 17 - 0
src/utils/findDOMElement.js

@@ -0,0 +1,17 @@
+const isDOMElement = require('./isDOMElement')
+
+/**
+ * Find a DOM element.
+ *
+ * @param {Node|string} element
+ * @return {Node|null}
+ */
+module.exports = function findDOMElement (element) {
+  if (typeof element === 'string') {
+    return document.querySelector(element)
+  }
+
+  if (typeof element === 'object' && isDOMElement(element)) {
+    return element
+  }
+}

+ 18 - 0
src/utils/generateFileID.js

@@ -0,0 +1,18 @@
+/**
+ * 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
+ *
+ */
+module.exports = 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(val => val).join('-')
+}

+ 15 - 0
src/utils/getArrayBuffer.js

@@ -0,0 +1,15 @@
+module.exports = 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)
+  })
+}

+ 3 - 0
src/utils/getBytesRemaining.js

@@ -0,0 +1,3 @@
+module.exports = function getBytesRemaining (fileProgress) {
+  return fileProgress.bytesTotal - fileProgress.bytesUploaded
+}

+ 12 - 0
src/utils/getETA.js

@@ -0,0 +1,12 @@
+const getSpeed = require('./getSpeed')
+const getBytesRemaining = require('./getBytesRemaining')
+
+module.exports = function getETA (fileProgress) {
+  if (!fileProgress.bytesUploaded) return 0
+
+  const uploadSpeed = getSpeed(fileProgress)
+  const bytesRemaining = getBytesRemaining(fileProgress)
+  const secondsRemaining = Math.round(bytesRemaining / uploadSpeed * 10) / 10
+
+  return secondsRemaining
+}

+ 15 - 0
src/utils/getFileNameAndExtension.js

@@ -0,0 +1,15 @@
+/**
+* Takes a full filename string and returns an object {name, extension}
+*
+* @param {string} fullFileName
+* @return {object} {name, extension}
+*/
+module.exports = function getFileNameAndExtension (fullFileName) {
+  var re = /(?:\.([^.]+))?$/
+  var fileExt = re.exec(fullFileName)[1]
+  var fileName = fullFileName.replace('.' + fileExt, '')
+  return {
+    name: fileName,
+    extension: fileExt
+  }
+}

+ 24 - 0
src/utils/getFileType.js

@@ -0,0 +1,24 @@
+const getFileNameAndExtension = require('./getFileNameAndExtension')
+const mimeTypes = require('./mimeTypes')
+
+module.exports = function getFileType (file) {
+  const fileExtension = file.name ? getFileNameAndExtension(file.name).extension : null
+
+  if (file.isRemote) {
+    // some remote providers do not support file types
+    return file.type ? file.type : mimeTypes[fileExtension]
+  }
+
+  // check if mime type is set in the file object
+  if (file.type) {
+    return file.type
+  }
+
+  // see if we can map extension to a mime type
+  if (fileExtension && mimeTypes[fileExtension]) {
+    return mimeTypes[fileExtension]
+  }
+
+  // if all fails, well, return empty
+  return null
+}

+ 16 - 0
src/utils/getFileTypeExtension.js

@@ -0,0 +1,16 @@
+// 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.
+const mimeToExtensions = {
+  'video/ogg': 'ogv',
+  'audio/ogg': 'ogg',
+  'video/webm': 'webm',
+  'audio/webm': 'webm',
+  'video/mp4': 'mp4',
+  'audio/mp3': 'mp3'
+}
+
+module.exports = function getFileTypeExtension (mimeType) {
+  return mimeToExtensions[mimeType] || null
+}

+ 8 - 0
src/utils/getSocketHost.js

@@ -0,0 +1,8 @@
+module.exports = 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}`
+}

+ 7 - 0
src/utils/getSpeed.js

@@ -0,0 +1,7 @@
+module.exports = function getSpeed (fileProgress) {
+  if (!fileProgress.bytesUploaded) return 0
+
+  const timeElapsed = (new Date()) - fileProgress.uploadStarted
+  const uploadSpeed = fileProgress.bytesUploaded / (timeElapsed / 1000)
+  return uploadSpeed
+}

+ 17 - 0
src/utils/getTimeStamp.js

@@ -0,0 +1,17 @@
+/**
+ * Returns a timestamp in the format of `hours:minutes:seconds`
+*/
+module.exports = function getTimeStamp () {
+  var date = new Date()
+  var hours = pad(date.getHours().toString())
+  var minutes = pad(date.getMinutes().toString())
+  var seconds = pad(date.getSeconds().toString())
+  return hours + ':' + minutes + ':' + seconds
+}
+
+/**
+ * Adds zero to strings shorter than two characters
+*/
+function pad (str) {
+  return str.length !== 2 ? 0 + str : str
+}

+ 8 - 0
src/utils/isDOMElement.js

@@ -0,0 +1,8 @@
+/**
+ * Check if an object is a DOM element. Duck-typing based on `nodeType`.
+ *
+ * @param {*} obj
+ */
+module.exports = function isDOMElement (obj) {
+  return obj && typeof obj === 'object' && obj.nodeType === Node.ELEMENT_NODE
+}

+ 9 - 0
src/utils/isObjectURL.js

@@ -0,0 +1,9 @@
+/**
+ * Check if a URL string is an object URL from `URL.createObjectURL`.
+ *
+ * @param {string} url
+ * @return {boolean}
+ */
+module.exports = function isObjectURL (url) {
+  return url.indexOf('blob:') === 0
+}

+ 9 - 0
src/utils/isPreviewSupported.js

@@ -0,0 +1,9 @@
+module.exports = function isPreviewSupported (fileType) {
+  if (!fileType) return false
+  const fileTypeSpecific = fileType.split('/')[1]
+  // list of images that browsers can preview
+  if (/^(jpeg|gif|png|svg|svg\+xml|bmp)$/.test(fileTypeSpecific)) {
+    return true
+  }
+  return false
+}

+ 4 - 0
src/utils/isTouchDevice.js

@@ -0,0 +1,4 @@
+module.exports = function isTouchDevice () {
+  return 'ontouchstart' in window || // works on most browsers
+          navigator.maxTouchPoints   // works on IE10/11 and Surface
+}

+ 36 - 0
src/utils/limitPromises.js

@@ -0,0 +1,36 @@
+/**
+ * Limit the amount of simultaneously pending Promises.
+ * Returns a function that, when passed a function `fn`,
+ * will make sure that at most `limit` calls to `fn` are pending.
+ *
+ * @param {number} limit
+ * @return {function()}
+ */
+module.exports = function limitPromises (limit) {
+  let pending = 0
+  const queue = []
+  return (fn) => {
+    return (...args) => {
+      const call = () => {
+        pending++
+        const promise = fn(...args)
+        promise.then(onfinish, onfinish)
+        return promise
+      }
+
+      if (pending >= limit) {
+        return new Promise((resolve, reject) => {
+          queue.push(() => {
+            call().then(resolve, reject)
+          })
+        })
+      }
+      return call()
+    }
+  }
+  function onfinish () {
+    pending--
+    const next = queue.shift()
+    if (next) next()
+  }
+}

+ 36 - 0
src/utils/mimeTypes.js

@@ -0,0 +1,36 @@
+module.exports = {
+  'md': 'text/markdown',
+  'markdown': 'text/markdown',
+  'mp4': 'video/mp4',
+  'mp3': 'audio/mp3',
+  'svg': 'image/svg+xml',
+  'jpg': 'image/jpeg',
+  'png': 'image/png',
+  'gif': 'image/gif',
+  'yaml': 'text/yaml',
+  'yml': 'text/yaml',
+  'csv': 'text/csv',
+  'avi': 'video/x-msvideo',
+  'mks': 'video/x-matroska',
+  'mkv': 'video/x-matroska',
+  'mov': 'video/quicktime',
+  'doc': 'application/msword',
+  'docm': 'application/vnd.ms-word.document.macroenabled.12',
+  'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+  'dot': 'application/msword',
+  'dotm': 'application/vnd.ms-word.template.macroenabled.12',
+  'dotx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
+  'xla': 'application/vnd.ms-excel',
+  'xlam': 'application/vnd.ms-excel.addin.macroenabled.12',
+  'xlc': 'application/vnd.ms-excel',
+  'xlf': 'application/x-xliff+xml',
+  'xlm': 'application/vnd.ms-excel',
+  'xls': 'application/vnd.ms-excel',
+  'xlsb': 'application/vnd.ms-excel.sheet.binary.macroenabled.12',
+  'xlsm': 'application/vnd.ms-excel.sheet.macroenabled.12',
+  'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+  'xlt': 'application/vnd.ms-excel',
+  'xltm': 'application/vnd.ms-excel.template.macroenabled.12',
+  'xltx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
+  'xlw': 'application/vnd.ms-excel'
+}

+ 16 - 0
src/utils/prettyETA.js

@@ -0,0 +1,16 @@
+const secondsToTime = require('./secondsToTime')
+
+module.exports = 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}`
+}

+ 10 - 0
src/utils/runPromiseSequence.js

@@ -0,0 +1,10 @@
+/**
+ * Runs an array of promise-returning functions in sequence.
+ */
+module.exports = function runPromiseSequence (functions, ...args) {
+  let promise = Promise.resolve()
+  functions.forEach((func) => {
+    promise = promise.then(() => func(...args))
+  })
+  return promise
+}

+ 7 - 0
src/utils/secondsToTime.js

@@ -0,0 +1,7 @@
+module.exports = 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 }
+}

+ 21 - 0
src/utils/settle.js

@@ -0,0 +1,21 @@
+module.exports = function settle (promises) {
+  const resolutions = []
+  const rejections = []
+  function resolved (value) {
+    resolutions.push(value)
+  }
+  function rejected (error) {
+    rejections.push(error)
+  }
+
+  const wait = Promise.all(
+    promises.map((promise) => promise.then(resolved, rejected))
+  )
+
+  return wait.then(() => {
+    return {
+      successful: resolutions,
+      failed: rejections
+    }
+  })
+}

+ 6 - 0
src/utils/toArray.js

@@ -0,0 +1,6 @@
+/**
+ * Converts list into array
+*/
+module.exports = function toArray (list) {
+  return Array.prototype.slice.call(list || [], 0)
+}

+ 9 - 0
src/utils/truncateString.js

@@ -0,0 +1,9 @@
+module.exports = 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
+}