Sfoglia il codice sorgente

@uppy/dashboard: auto discover and install plugins without target (#4343)

* Dashboard: auto discover plugins no matter when they were installed

* Remove target from RemoteSoruces, making it compatible with @uppy/react

* Update packages/@uppy/dashboard/src/Dashboard.jsx

Co-authored-by: Mikael Finstad <finstaden@gmail.com>

* Removed comments, added comments

* better comment

* Change type — otherwise gets listed on Dashboard sources

* Add RemoteSources to React test

* Add RemoteSources to React example

* Add tests

* Refactor for less iteration for each plugin, rename functions

* Prevent error when opts are undefined

* remove console.logs

* prettier

---------

Co-authored-by: Mikael Finstad <finstaden@gmail.com>
Artur Paikin 1 anno fa
parent
commit
8b252086f5

+ 6 - 1
e2e/clients/react/App.jsx

@@ -4,13 +4,18 @@ import Uppy from '@uppy/core'
 import React, { useState } from 'react'
 import { Dashboard, DashboardModal, DragDrop } from '@uppy/react'
 import ThumbnailGenerator from '@uppy/thumbnail-generator'
+import RemoteSources from '@uppy/remote-sources'
 
 import '@uppy/core/dist/style.css'
 import '@uppy/dashboard/dist/style.css'
 import '@uppy/drag-drop/dist/style.css'
 
 export default function App () {
-  const uppyDashboard = new Uppy({ id: 'dashboard' })
+  const RemoteSourcesOptions = {
+    companionUrl: 'http://companion.uppy.io',
+    sources: ['GoogleDrive', 'OneDrive', 'Unsplash', 'Zoom', 'Url'],
+  }
+  const uppyDashboard = new Uppy({ id: 'dashboard' }).use(RemoteSources, { ...RemoteSourcesOptions })
   const uppyModal = new Uppy({ id: 'modal' })
   const uppyDragDrop = new Uppy({ id: 'drag-drop' }).use(ThumbnailGenerator)
   const [open, setOpen] = useState(false)

+ 16 - 0
e2e/cypress/integration/react.spec.ts

@@ -19,6 +19,22 @@ describe('@uppy/react', () => {
       .each((element) => expect(element).attr('src').to.include('blob:'))
   })
 
+  it('should render Dashboard with Remote Sources plugin pack', () => {
+    const sources = [
+      'My Device',
+      'Google Drive',
+      'OneDrive',
+      'Unsplash',
+      'Zoom',
+      'Link',
+    ]
+    cy.get('#dashboard .uppy-DashboardTab-name').each((item, index, list) => {
+      expect(list).to.have.length(6)
+      // Returns the current element from the loop
+      expect(Cypress.$(item).text()).to.eq(sources[index])
+    })
+  })
+
   it('should render Modal in React and show thumbnails', () => {
     cy.get('#open').click()
     cy.get('@modal-input').selectFile(

+ 6 - 2
examples/react-example/App.jsx

@@ -2,7 +2,9 @@
 import React from'react'
 import Uppy from'@uppy/core'
 import Tus from'@uppy/tus'
-import GoogleDrive from'@uppy/google-drive'
+import GoogleDrive from '@uppy/google-drive'
+import Webcam from '@uppy/webcam'
+import RemoteSources from '@uppy/remote-sources' 
 import { Dashboard, DashboardModal, DragDrop, ProgressBar, FileInput } from'@uppy/react'
 
 import '@uppy/core/dist/style.css'
@@ -22,7 +24,9 @@ export default class App extends React.Component {
 
     this.uppy = new Uppy({ id: 'uppy1', autoProceed: true, debug: true })
       .use(Tus, { endpoint: 'https://tusd.tusdemo.net/files/' })
-      .use(GoogleDrive, { companionUrl: 'https://companion.uppy.io' })
+      .use(Webcam)
+      .use(RemoteSources, { companionUrl: 'https://companion.uppy.io', sources: ['GoogleDrive', 'Box', 'Dropbox', 'Facebook', 'Instagram', 'OneDrive', 'Unsplash', 'Zoom', 'Url'],
+      })
 
     this.uppy2 = new Uppy({ id: 'uppy2', autoProceed: false, debug: true })
       .use(Tus, { endpoint: 'https://tusd.tusdemo.net/files/' })

+ 2 - 0
packages/@uppy/core/src/Uppy.js

@@ -1245,6 +1245,8 @@ class Uppy {
     }
     plugin.install()
 
+    this.emit('plugin-added', plugin)
+
     return this
   }
 

+ 2 - 0
packages/@uppy/dashboard/package.json

@@ -39,6 +39,8 @@
   "devDependencies": {
     "@uppy/google-drive": "workspace:^",
     "@uppy/status-bar": "workspace:^",
+    "@uppy/url": "workspace:^",
+    "@uppy/webcam": "workspace:^",
     "resize-observer-polyfill": "^1.5.0",
     "vitest": "^0.34.5"
   },

+ 32 - 15
packages/@uppy/dashboard/src/Dashboard.jsx

@@ -749,6 +749,7 @@ export default class Dashboard extends UIPlugin {
     this.startListeningToResize()
     document.addEventListener('paste', this.handlePasteOnBody)
 
+    this.uppy.on('plugin-added', this.#addSupportedPluginIfNoTarget)
     this.uppy.on('plugin-remove', this.removeTarget)
     this.uppy.on('file-added', this.hideAllPanels)
     this.uppy.on('dashboard:modal-closed', this.hideAllPanels)
@@ -780,8 +781,9 @@ export default class Dashboard extends UIPlugin {
 
     this.stopListeningToResize()
     document.removeEventListener('paste', this.handlePasteOnBody)
-
     window.removeEventListener('popstate', this.handlePopState, false)
+
+    this.uppy.off('plugin-added', this.#addSupportedPluginIfNoTarget)
     this.uppy.off('plugin-remove', this.removeTarget)
     this.uppy.off('file-added', this.hideAllPanels)
     this.uppy.off('dashboard:modal-closed', this.hideAllPanels)
@@ -1015,14 +1017,37 @@ export default class Dashboard extends UIPlugin {
     })
   }
 
-  discoverProviderPlugins = () => {
-    this.uppy.iteratePlugins((plugin) => {
-      if (plugin && !plugin.target && plugin.opts && plugin.opts.target === this.constructor) {
-        this.addTarget(plugin)
+  #addSpecifiedPluginsFromOptions = () => {
+    const plugins = this.opts.plugins || []
+
+    plugins.forEach((pluginID) => {
+      const plugin = this.uppy.getPlugin(pluginID)
+      if (plugin) {
+        plugin.mount(this, plugin)
+      } else {
+        this.uppy.log(`[Uppy] Dashboard could not find plugin '${pluginID}', make sure to uppy.use() the plugins you are specifying`, 'warning')
       }
     })
   }
 
+  #autoDiscoverPlugins = () => {
+    this.uppy.iteratePlugins(this.#addSupportedPluginIfNoTarget)
+  }
+
+  #addSupportedPluginIfNoTarget = (plugin) => {
+    // Only these types belong on the Dashboard,
+    // we wouldn’t want to try and mount Compressor or Tus, for example.
+    const typesAllowed = ['acquirer', 'editor']
+    if (plugin && !plugin.opts?.target && typesAllowed.includes(plugin.type)) {
+      const pluginAlreadyAdded = this.getPluginState().targets.some(
+        installedPlugin => plugin.id === installedPlugin.id,
+      )
+      if (!pluginAlreadyAdded) {
+        plugin.mount(this, plugin)
+      }
+    }
+  }
+
   install = () => {
     // Set default state for Dashboard
     this.setPluginState({
@@ -1055,15 +1080,6 @@ export default class Dashboard extends UIPlugin {
       this.mount(target, this)
     }
 
-    const plugins = this.opts.plugins || []
-
-    plugins.forEach((pluginID) => {
-      const plugin = this.uppy.getPlugin(pluginID)
-      if (plugin) {
-        plugin.mount(this, plugin)
-      }
-    })
-
     if (!this.opts.disableStatusBar) {
       this.uppy.use(StatusBar, {
         id: `${this.id}:StatusBar`,
@@ -1111,7 +1127,8 @@ export default class Dashboard extends UIPlugin {
       this.darkModeMediaQuery.addListener(this.handleSystemDarkModeChange)
     }
 
-    this.discoverProviderPlugins()
+    this.#addSpecifiedPluginsFromOptions()
+    this.#autoDiscoverPlugins()
     this.initEvents()
   }
 

+ 36 - 0
packages/@uppy/dashboard/src/index.test.js

@@ -3,6 +3,9 @@ import { afterAll, beforeAll, describe, it, expect } from 'vitest'
 import Core from '@uppy/core'
 import StatusBarPlugin from '@uppy/status-bar'
 import GoogleDrivePlugin from '@uppy/google-drive'
+import WebcamPlugin from '@uppy/webcam'
+import Url from '@uppy/url'
+
 import resizeObserverPolyfill from 'resize-observer-polyfill'
 import DashboardPlugin from '../lib/index.js'
 
@@ -66,6 +69,39 @@ describe('Dashboard', () => {
     core.close()
   })
 
+  it('should automatically add plugins which have no target', () => {
+    const core = new Core()
+    core.use(Url, { companionUrl: 'https://companion.uppy.io' })
+    core.use(DashboardPlugin, { inline: false })
+    core.use(WebcamPlugin)
+
+    const dashboardPlugins = core.getState().plugins['Dashboard'].targets
+
+    // two built-in plugins + these ones below
+    expect(dashboardPlugins.length).toEqual(4)
+    expect(dashboardPlugins.some((plugin) => plugin.id === 'Url')).toEqual(true)
+    expect(dashboardPlugins.some((plugin) => plugin.id === 'Webcam')).toEqual(true)
+
+    core.close()
+  })
+
+  it('should not automatically add plugins which have a non-Dashboard target', () => {
+    const core = new Core()
+    WebcamPlugin.prototype.start = () => {}
+    core.use(Url, { companionUrl: 'https://companion.uppy.io' })
+    core.use(DashboardPlugin, { inline: false })
+    core.use(WebcamPlugin, { target: 'body' })
+
+    const dashboardPlugins = core.getState().plugins['Dashboard'].targets
+
+    // two built-in plugins + these ones below
+    expect(dashboardPlugins.length).toEqual(3)
+    expect(dashboardPlugins.some((plugin) => plugin.id === 'Url')).toEqual(true)
+    expect(dashboardPlugins.some((plugin) => plugin.id === 'Webcam')).toEqual(false)
+
+    core.close()
+  })
+
   it('should change options on the fly', () => {
     const core = new Core()
     core.use(DashboardPlugin, {

+ 2 - 2
packages/@uppy/image-editor/src/ImageEditor.jsx

@@ -45,11 +45,11 @@ export default class ImageEditor extends UIPlugin {
       ...opts,
       actions: {
         ...defaultActions,
-        ...opts.actions,
+        ...opts?.actions,
       },
       cropperOptions: {
         ...defaultCropperOptions,
-        ...opts.cropperOptions,
+        ...opts?.cropperOptions,
       },
     }
 

+ 1 - 1
packages/@uppy/provider-views/src/ProviderView/ProviderView.jsx

@@ -384,7 +384,7 @@ export default class ProviderView extends View {
         // and that will allow the user to start the upload, so we need to make sure we have
         // finished all async operations before we add any file
         // see https://github.com/transloadit/uppy/pull/4384
-        this.plugin.uppy.log('Adding remote provider files')
+        this.plugin.uppy.log('Adding files from a remote provider')
         this.plugin.uppy.addFiles(newFiles.map((file) => this.getTagFile(file)))
 
         this.plugin.setPluginState({ filterInput: '' })

+ 1 - 3
packages/@uppy/remote-sources/src/index.js

@@ -1,5 +1,4 @@
 import { BasePlugin } from '@uppy/core'
-import Dashboard from '@uppy/dashboard'
 import Dropbox from '@uppy/dropbox'
 import GoogleDrive from '@uppy/google-drive'
 import Instagram from '@uppy/instagram'
@@ -34,11 +33,10 @@ export default class RemoteSources extends BasePlugin {
   constructor (uppy, opts) {
     super(uppy, opts)
     this.id = this.opts.id || 'RemoteSources'
-    this.type = 'acquirer'
+    this.type = 'preset'
 
     const defaultOptions = {
       sources: Object.keys(availablePlugins),
-      target: Dashboard,
     }
     this.opts = { ...defaultOptions, ...opts }
 

+ 2 - 0
yarn.lock

@@ -9867,7 +9867,9 @@ __metadata:
     "@uppy/provider-views": "workspace:^"
     "@uppy/status-bar": "workspace:^"
     "@uppy/thumbnail-generator": "workspace:^"
+    "@uppy/url": "workspace:^"
     "@uppy/utils": "workspace:^"
+    "@uppy/webcam": "workspace:^"
     classnames: ^2.2.6
     is-shallow-equal: ^1.0.1
     lodash: ^4.17.21