浏览代码

Add @uppy/drop-target — drag and drop files on any existing DOM element (#2836)

* Add dom-target plugin

* add dom-target to bundle and website — had trouble with css otherwise

* cleanup and dev setup

* newline at the end, restore inline: false by default

* dom-target --> drop-target, remove paste

* add docs, bundle, website example

* add types

@goto-bus-stop this should probably be something other than Uppy.PluginTarget since it won’t accept an uppy plugin?

* fix types

* Update examples/dev/Dashboard.js

Co-authored-by: Renée Kooi <renee@kooi.me>

* type string | Element

* Update packages/@uppy/drop-target/package.json

Co-authored-by: Renée Kooi <renee@kooi.me>

* Update packages/@uppy/drop-target/package.json

Co-authored-by: Renée Kooi <renee@kooi.me>

* Update examples/dev/Dashboard.js

Co-authored-by: Renée Kooi <renee@kooi.me>

* Create package-lock.json

* Update package-lock.json

* Update package-lock.json

Co-authored-by: Renée Kooi <renee@kooi.me>
Artur Paikin 4 年之前
父节点
当前提交
1fcb99540c

+ 0 - 1
bin/build-css.js

@@ -4,7 +4,6 @@ const autoprefixer = require('autoprefixer')
 const postcssLogical = require('postcss-logical')
 const postcssDirPseudoClass = require('postcss-dir-pseudo-class')
 const cssnano = require('cssnano')
-// const safeImportant = require('postcss-safe-important')
 const chalk = require('chalk')
 const { promisify } = require('util')
 const fs = require('fs')

+ 4 - 0
examples/dev/Dashboard.js

@@ -21,6 +21,7 @@ const XHRUpload = require('@uppy/xhr-upload/src')
 const Transloadit = require('@uppy/transloadit/src')
 const Form = require('@uppy/form/src')
 const ImageEditor = require('@uppy/image-editor/src')
+const DropTarget = require('@uppy/drop-target/src')
 /* eslint-enable import/no-extraneous-dependencies */
 
 // DEV CONFIG: pick an uploader
@@ -87,6 +88,9 @@ module.exports = () => {
     .use(ScreenCapture, { target: Dashboard })
     .use(Form, { target: '#upload-form' })
     .use(ImageEditor, { target: Dashboard })
+    .use(DropTarget, {
+      target: document.body,
+    })
 
   switch (UPLOADER) {
     case 'tus':

文件差异内容过多而无法显示
+ 95 - 707
package-lock.json


+ 0 - 9
packages/@uppy/dashboard/src/style.scss

@@ -687,15 +687,6 @@
   }
 }
 
-.uppy-Dashboard-dropFilesIcon {
-  display: none;
-  margin-bottom: 15px;
-
-  .uppy-size--md.uppy-size--height-md & {
-    display: block;
-  }
-}
-
 .uppy-Dashboard-AddFiles-title {
   font-size: 17px;
   line-height: 1.35;

+ 32 - 0
packages/@uppy/drop-target/package.json

@@ -0,0 +1,32 @@
+{
+  "name": "@uppy/drop-target",
+  "description": "Lets your users drag and drop files on a DOM element",
+  "version": "0.1.0",
+  "license": "MIT",
+  "main": "lib/index.js",
+  "types": "types/index.d.ts",
+  "keywords": [
+    "file uploader",
+    "uppy",
+    "uppy-plugin",
+    "drag-drop",
+    "drag",
+    "drop",
+    "dropzone",
+    "upload"
+  ],
+  "homepage": "https://uppy.io",
+  "bugs": {
+    "url": "https://github.com/transloadit/uppy/issues"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/transloadit/uppy.git"
+  },
+  "dependencies": {
+    "@uppy/utils": "file:../utils"
+  },
+  "peerDependencies": {
+    "@uppy/core": "^1.0.0"
+  }
+}

+ 133 - 0
packages/@uppy/drop-target/src/index.js

@@ -0,0 +1,133 @@
+const { Plugin } = require('@uppy/core')
+const getDroppedFiles = require('@uppy/utils/lib/getDroppedFiles')
+const toArray = require('@uppy/utils/lib/toArray')
+
+/**
+ * Drop Target plugin
+ *
+ */
+module.exports = class DropTarget extends Plugin {
+  static VERSION = require('../package.json').version
+
+  constructor (uppy, opts) {
+    super(uppy, opts)
+    this.type = 'acquirer'
+    this.id = this.opts.id || 'DropTarget'
+    this.title = 'Drop Target'
+
+    // Default options
+    const defaultOpts = {
+      target: null,
+    }
+
+    // Merge default options with the ones set by user
+    this.opts = { ...defaultOpts, ...opts }
+    this.removeDragOverClassTimeout = null
+  }
+
+  addFiles = (files) => {
+    const descriptors = files.map((file) => ({
+      source: this.id,
+      name: file.name,
+      type: file.type,
+      data: file,
+      meta: {
+        // path of the file relative to the ancestor directory the user selected.
+        // e.g. 'docs/Old Prague/airbnb.pdf'
+        relativePath: file.relativePath || null,
+      },
+    }))
+
+    try {
+      this.uppy.addFiles(descriptors)
+    } catch (err) {
+      this.uppy.log(err)
+    }
+  }
+
+  handleDrop = (event) => {
+    event.preventDefault()
+    event.stopPropagation()
+    clearTimeout(this.removeDragOverClassTimeout)
+
+    // 2. Remove dragover class
+    event.currentTarget.classList.remove('uppy-is-drag-over')
+    this.setPluginState({ isDraggingOver: false })
+
+    // 3. Add all dropped files
+    this.uppy.log('[DropTarget] Files were dropped')
+    const logDropError = (error) => {
+      this.uppy.log(error, 'error')
+    }
+    getDroppedFiles(event.dataTransfer, { logDropError })
+      .then((files) => this.addFiles(files))
+  }
+
+  handleDragOver = (event) => {
+    event.preventDefault()
+    event.stopPropagation()
+
+    // 1. Add a small (+) icon on drop
+    // (and prevent browsers from interpreting this as files being _moved_ into the browser,
+    // https://github.com/transloadit/uppy/issues/1978)
+    event.dataTransfer.dropEffect = 'copy'
+
+    clearTimeout(this.removeDragOverClassTimeout)
+    event.currentTarget.classList.add('uppy-is-drag-over')
+    this.setPluginState({ isDraggingOver: true })
+  }
+
+  handleDragLeave = (event) => {
+    event.preventDefault()
+    event.stopPropagation()
+
+    const { currentTarget } = event
+
+    clearTimeout(this.removeDragOverClassTimeout)
+    // Timeout against flickering, this solution is taken from drag-drop library.
+    // Solution with 'pointer-events: none' didn't work across browsers.
+    this.removeDragOverClassTimeout = setTimeout(() => {
+      currentTarget.classList.remove('uppy-is-drag-over')
+      this.setPluginState({ isDraggingOver: false })
+    }, 50)
+  }
+
+  addListeners = () => {
+    const { target } = this.opts
+
+    if (target instanceof Element) {
+      this.nodes = [target]
+    } else if (typeof target === 'string') {
+      this.nodes = toArray(document.querySelectorAll(target))
+    }
+
+    if (!this.nodes && !this.nodes.length > 0) {
+      throw new Error(`"${target}" does not match any HTML elements`)
+    }
+
+    this.nodes.forEach((node) => {
+      node.addEventListener('dragover', this.handleDragOver, false)
+      node.addEventListener('dragleave', this.handleDragLeave, false)
+      node.addEventListener('drop', this.handleDrop, false)
+    })
+  }
+
+  removeListeners = () => {
+    if (this.nodes) {
+      this.nodes.forEach((node) => {
+        node.removeEventListener('dragover', this.handleDragOver, false)
+        node.removeEventListener('dragleave', this.handleDragLeave, false)
+        node.removeEventListener('drop', this.handleDrop, false)
+      })
+    }
+  }
+
+  install () {
+    this.setPluginState({ isDraggingOver: false })
+    this.addListeners()
+  }
+
+  uninstall () {
+    this.removeListeners()
+  }
+}

+ 14 - 0
packages/@uppy/drop-target/src/style.scss

@@ -0,0 +1,14 @@
+@import '@uppy/core/src/_variables.scss';
+
+.uppy-is-drag-over:after {
+  content: '';
+  position: fixed;
+  top: 7px;
+  right: 7px;
+  bottom: 7px;
+  left: 7px;
+  border: 5px dashed $gray-400;
+  // border-radius: 5px;
+  z-index: 10000;
+  background-color: rgba($gray-200, 0.5);
+}

+ 11 - 0
packages/@uppy/drop-target/types/index.d.ts

@@ -0,0 +1,11 @@
+import Uppy = require('@uppy/core')
+
+declare module DropTarget {
+  interface DropTargetOptions extends Uppy.PluginOptions {
+    target: string | Element
+  }
+}
+
+declare class DropTarget extends Uppy.Plugin<DropTarget.DropTargetOptions> {}
+
+export = DropTarget

+ 2 - 0
packages/@uppy/drop-target/types/index.test-d.ts

@@ -0,0 +1,2 @@
+import DropTarget = require('../')
+// TODO implement

+ 1 - 0
packages/uppy/index.js

@@ -17,6 +17,7 @@ exports.ReduxStore = require('@uppy/store-redux')
 // UI plugins
 exports.Dashboard = require('@uppy/dashboard')
 exports.DragDrop = require('@uppy/drag-drop')
+exports.DropTarget = require('@uppy/drop-target')
 exports.FileInput = require('@uppy/file-input')
 exports.Informer = require('@uppy/informer')
 exports.ProgressBar = require('@uppy/progress-bar')

+ 1 - 0
packages/uppy/index.mjs

@@ -13,6 +13,7 @@ export { default as ReduxStore } from '@uppy/store-redux'
 // UI plugins
 export { default as Dashboard } from '@uppy/dashboard'
 export { default as DragDrop } from '@uppy/drag-drop'
+export { default as DropTarget } from '@uppy/drop-target'
 export { default as FileInput } from '@uppy/file-input'
 export { default as Informer } from '@uppy/informer'
 export { default as ProgressBar } from '@uppy/progress-bar'

+ 2 - 1
packages/uppy/package.json

@@ -61,6 +61,7 @@
     "@uppy/unsplash": "file:../@uppy/unsplash",
     "@uppy/url": "file:../@uppy/url",
     "@uppy/webcam": "file:../@uppy/webcam",
-    "@uppy/xhr-upload": "file:../@uppy/xhr-upload"
+    "@uppy/xhr-upload": "file:../@uppy/xhr-upload",
+    "@uppy/drop-target": "file:../@uppy/drop-target"
   }
 }

+ 1 - 0
packages/uppy/src/style.scss

@@ -10,3 +10,4 @@
 @import '@uppy/webcam/src/style.scss';
 @import '@uppy/screen-capture/src/style.scss';
 @import '@uppy/image-editor/src/style.scss';
+@import '@uppy/drop-target/src/style.scss';

+ 2 - 0
packages/uppy/types/index.d.ts

@@ -17,6 +17,8 @@ import Dashboard = require('@uppy/dashboard');
 export { Dashboard };
 import DragDrop = require('@uppy/drag-drop');
 export { DragDrop };
+import DropTarget = require('@uppy/drop-target');
+export { DropTarget };
 import FileInput = require('@uppy/file-input');
 export { FileInput };
 import Informer = require('@uppy/informer');

+ 1 - 0
website/inject.js

@@ -61,6 +61,7 @@ const packages = [
   '@uppy/url',
   '@uppy/webcam',
   '@uppy/xhr-upload',
+  '@uppy/drop-target',
   // Stores
   '@uppy/store-default',
   '@uppy/store-redux',

+ 65 - 0
website/src/docs/drop-target.md

@@ -0,0 +1,65 @@
+---
+type: docs
+order: 1
+title: "Drop Target"
+module: "@uppy/drop-target"
+permalink: docs/drop-target/
+alias: docs/drop-target/
+category: "Sources"
+tagline: "drag-and-drop area on any element on the page"
+---
+
+The `@uppy/drop-target` plugin lets your users drag-and-drop files on any element on the page, for example the whole page, `document.body`.
+
+Can be used together with Uppy Dashboard or Drag & Drop plugins, or your custom solution, including plain text “please drop files here”.
+
+```js
+const DropTarget = require('@uppy/drop-target')
+
+uppy.use(DragDrop, {
+  target: document.body,
+})
+```
+
+<a class="TryButton" href="/examples/dashboard/">Try it live</a>
+
+## Installation
+
+This plugin is published as the `@uppy/drop-target` package.
+
+Install from NPM:
+
+```shell
+npm install @uppy/drag-drop
+```
+
+In the [CDN package](/docs/#With-a-script-tag), it is available on the `Uppy` global object:
+
+```js
+const DragDrop = Uppy.DropTarget
+```
+
+## CSS
+
+The `@uppy/drop-target` plugin includes some simple styles for `uppy-is-drag-over` CSS class name. You can also choose not to use it and provide your own styles instead.
+
+```js
+import '@uppy/core/dist/style.css'
+import '@uppy/drop-target/dist/style.css'
+```
+
+Import general Core styles from `@uppy/core/dist/style.css` first, then add the Drag & Drop styles from `@uppy/drop-target/dist/style.css`. A minified version is also available as `style.min.css` at the same path. The way to do import depends on your build system.
+
+## Options
+
+The `@uppy/drop-target` plugin has the following configurable options:
+
+```js
+uppy.use(DropTarget, {
+  target: null,
+})
+```
+
+### `target: null`
+
+DOM element or CSS selector to attach the drag and drop listeners to.

+ 24 - 15
website/src/examples/dashboard/app.es6

@@ -13,6 +13,7 @@ const Url = require('@uppy/url')
 const Webcam = require('@uppy/webcam')
 const ScreenCapture = require('@uppy/screen-capture')
 const Tus = require('@uppy/tus')
+const DropTarget = require('@uppy/drop-target')
 const localeList = require('../locale_list.json')
 
 const COMPANION = require('../env')
@@ -21,7 +22,7 @@ const RTL_LOCALES = ['ar_SA', 'fa_IR', 'he_IL']
 
 if (typeof window !== 'undefined' && typeof window.Uppy === 'undefined') {
   window.Uppy = {
-    locales: {}
+    locales: {},
   }
 }
 
@@ -33,7 +34,7 @@ function uppyInit () {
   const opts = window.uppyOptions
 
   const uppy = new Uppy({
-    logger: Uppy.debugLogger
+    logger: Uppy.debugLogger,
   })
 
   uppy.use(Tus, { endpoint: 'https://tusd.tusdemo.net/files/', resume: true })
@@ -54,8 +55,8 @@ function uppyInit () {
     showProgressDetails: true,
     metaFields: [
       { id: 'name', name: 'Name', placeholder: 'file name' },
-      { id: 'caption', name: 'Caption', placeholder: 'add description' }
-    ]
+      { id: 'caption', name: 'Caption', placeholder: 'add description' },
+    ],
   })
 
   window.uppy = uppy
@@ -69,25 +70,25 @@ function uppySetOptions () {
     minFileSize: null,
     maxNumberOfFiles: null,
     minNumberOfFiles: null,
-    allowedFileTypes: null
+    allowedFileTypes: null,
   }
 
   const restrictions = {
     maxFileSize: 1000000,
     maxNumberOfFiles: 3,
     minNumberOfFiles: 2,
-    allowedFileTypes: ['image/*', 'video/*']
+    allowedFileTypes: ['image/*', 'video/*'],
   }
 
   window.uppy.setOptions({
     autoProceed: opts.autoProceed,
-    restrictions: opts.restrictions ? restrictions : defaultNullRestrictions
+    restrictions: opts.restrictions ? restrictions : defaultNullRestrictions,
   })
 
   window.uppy.getPlugin('Dashboard').setOptions({
     note: opts.restrictions ? 'Images and video only, 2–3 files, up to 1 MB' : '',
     theme: opts.darkMode ? 'dark' : 'light',
-    disabled: opts.disabled
+    disabled: opts.disabled,
   })
 
   const googleDriveInstance = window.uppy.getPlugin('GoogleDrive')
@@ -150,7 +151,7 @@ function uppySetOptions () {
   if (opts.Webcam && !webcamInstance) {
     window.uppy.use(Webcam, {
       target: Dashboard,
-      showVideoSourceDropdown: true
+      showVideoSourceDropdown: true,
     })
   }
   if (!opts.Webcam && webcamInstance) {
@@ -172,11 +173,19 @@ function uppySetOptions () {
   if (!opts.imageEditor && imageEditorInstance) {
     window.uppy.removePlugin(imageEditorInstance)
   }
+
+  const dropTargetInstance = window.uppy.getPlugin('DropTarget')
+  if (opts.DropTarget && !dropTargetInstance) {
+    window.uppy.use(DropTarget, { target: document.body })
+  }
+  if (!opts.DropTarget && dropTargetInstance) {
+    window.uppy.removePlugin(dropTargetInstance)
+  }
 }
 
 function whenLocaleAvailable (localeName, callback) {
-  var interval = 100 // ms
-  var loop = setInterval(function () {
+  const interval = 100 // ms
+  const loop = setInterval(() => {
     if (window.Uppy && window.Uppy.locales && window.Uppy.locales[localeName]) {
       clearInterval(loop)
       callback(window.Uppy.locales[localeName])
@@ -185,8 +194,8 @@ function whenLocaleAvailable (localeName, callback) {
 }
 
 function loadLocaleFromCDN (localeName) {
-  var head = document.getElementsByTagName('head')[0]
-  var js = document.createElement('script')
+  const head = document.getElementsByTagName('head')[0]
+  const js = document.createElement('script')
   js.type = 'text/javascript'
   js.src = `https://releases.transloadit.com/uppy/locales/v1.17.2/${localeName}.min.js`
 
@@ -203,11 +212,11 @@ function setLocale (localeName) {
       : 'ltr'
 
     window.uppy.setOptions({
-      locale: localeObj
+      locale: localeObj,
     })
 
     window.uppy.getPlugin('Dashboard').setOptions({
-      direction
+      direction,
     })
   })
 }

+ 5 - 2
website/src/examples/dashboard/app.html

@@ -20,6 +20,7 @@
     <li><label for="opts-OneDrive"><input type="checkbox" id="opts-OneDrive" checked/> OneDrive</label></li>
     <li id="zoom-checkbox"><label for="opts-Zoom"><input type="checkbox" id="opts-Zoom" checked/> Zoom</label></li>
     <li><label for="opts-Url"><input type="checkbox" id="opts-Url" checked/> Url</label></li>
+    <li><label for="opts-DropTarget"><input type="checkbox" id="opts-DropTarget" checked/> Drag & Drop anywhere</label></li>
   </ul>
 
   <label for="localeList">Change locale:</label>
@@ -53,7 +54,8 @@
     restrictions: document.querySelector('#opts-restrictions'),
     darkMode: document.querySelector('#opts-darkMode'),
     imageEditor: document.querySelector('#opts-imageEditor'),
-    disabled: document.querySelector('#opts-disabled')
+    disabled: document.querySelector('#opts-disabled'),
+    DropTarget: document.querySelector('#opts-DropTarget')
   }
 
   var defaultOpts = {
@@ -70,7 +72,8 @@
     restrictions: false,
     darkMode: false,
     imageEditor: true,
-    disabled: false
+    disabled: false,
+    DropTarget: true,
   }
 
   // try to get options from localStorage, if its empty, set to defaultOpts

+ 1 - 0
website/src/examples/dashboard/index.ejs

@@ -67,6 +67,7 @@ const uppy = new Uppy({
 .use(ScreenCapture, { target: Dashboard })
 .use(ImageEditor, { target: Dashboard })
 .use(Tus, { endpoint: 'https://tusd.tusdemo.net/files/' })
+.use(DropTarget, {target: document.body })
 
 uppy.on('complete', result => {
   console.log('successful files:', result.successful)

部分文件因为文件数量过多而无法显示