diff --git a/e2e/clients/react/App.jsx b/e2e/clients/react/App.jsx index ba0aa45846..cfd68bcc5c 100644 --- a/e2e/clients/react/App.jsx +++ b/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) diff --git a/e2e/cypress/integration/react.spec.ts b/e2e/cypress/integration/react.spec.ts index c937e35070..e2fde4d0d6 100644 --- a/e2e/cypress/integration/react.spec.ts +++ b/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( diff --git a/examples/react-example/App.jsx b/examples/react-example/App.jsx index 064d07422f..8f1460fcb1 100644 --- a/examples/react-example/App.jsx +++ b/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/' }) diff --git a/packages/@uppy/core/src/Uppy.js b/packages/@uppy/core/src/Uppy.js index 3ed44108a1..fcde7efdfc 100644 --- a/packages/@uppy/core/src/Uppy.js +++ b/packages/@uppy/core/src/Uppy.js @@ -1245,6 +1245,8 @@ class Uppy { } plugin.install() + this.emit('plugin-added', plugin) + return this } diff --git a/packages/@uppy/dashboard/package.json b/packages/@uppy/dashboard/package.json index 954edc52d5..a755fb599e 100644 --- a/packages/@uppy/dashboard/package.json +++ b/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" }, diff --git a/packages/@uppy/dashboard/src/Dashboard.jsx b/packages/@uppy/dashboard/src/Dashboard.jsx index 4a4353621a..61cf6e2ff0 100644 --- a/packages/@uppy/dashboard/src/Dashboard.jsx +++ b/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() } diff --git a/packages/@uppy/dashboard/src/index.test.js b/packages/@uppy/dashboard/src/index.test.js index 155742d990..862c3ab7db 100644 --- a/packages/@uppy/dashboard/src/index.test.js +++ b/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, { diff --git a/packages/@uppy/image-editor/src/ImageEditor.jsx b/packages/@uppy/image-editor/src/ImageEditor.jsx index 8a47684299..3d65934cd3 100644 --- a/packages/@uppy/image-editor/src/ImageEditor.jsx +++ b/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, }, } diff --git a/packages/@uppy/provider-views/src/ProviderView/ProviderView.jsx b/packages/@uppy/provider-views/src/ProviderView/ProviderView.jsx index e0991350e2..8d55732bf2 100644 --- a/packages/@uppy/provider-views/src/ProviderView/ProviderView.jsx +++ b/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: '' }) diff --git a/packages/@uppy/remote-sources/src/index.js b/packages/@uppy/remote-sources/src/index.js index 8c16ce9724..1a18dfea8d 100644 --- a/packages/@uppy/remote-sources/src/index.js +++ b/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 } diff --git a/yarn.lock b/yarn.lock index de61bb3637..bda7de9512 100644 --- a/yarn.lock +++ b/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