1 | const { h } = require('preact')
|
2 | const { Plugin } = require('@uppy/core')
|
3 | const Translator = require('@uppy/utils/lib/Translator')
|
4 | const DashboardUI = require('./components/Dashboard')
|
5 | const StatusBar = require('@uppy/status-bar')
|
6 | const Informer = require('@uppy/informer')
|
7 | const ThumbnailGenerator = require('@uppy/thumbnail-generator')
|
8 | const findAllDOMElements = require('@uppy/utils/lib/findAllDOMElements')
|
9 | const toArray = require('@uppy/utils/lib/toArray')
|
10 | const getDroppedFiles = require('@uppy/utils/lib/getDroppedFiles')
|
11 | const getTextDirection = require('@uppy/utils/lib/getTextDirection')
|
12 | const trapFocus = require('./utils/trapFocus')
|
13 | const cuid = require('cuid')
|
14 | const ResizeObserver = require('resize-observer-polyfill').default || require('resize-observer-polyfill')
|
15 | const createSuperFocus = require('./utils/createSuperFocus')
|
16 | const memoize = require('memoize-one').default || require('memoize-one')
|
17 | const FOCUSABLE_ELEMENTS = require('@uppy/utils/lib/FOCUSABLE_ELEMENTS')
|
18 |
|
19 | const TAB_KEY = 9
|
20 | const ESC_KEY = 27
|
21 |
|
22 | function createPromise () {
|
23 | const o = {}
|
24 | o.promise = new Promise((resolve, reject) => {
|
25 | o.resolve = resolve
|
26 | o.reject = reject
|
27 | })
|
28 | return o
|
29 | }
|
30 |
|
31 | function defaultPickerIcon () {
|
32 | return (
|
33 | <svg aria-hidden="true" focusable="false" width="30" height="30" viewBox="0 0 30 30">
|
34 | <path d="M15 30c8.284 0 15-6.716 15-15 0-8.284-6.716-15-15-15C6.716 0 0 6.716 0 15c0 8.284 6.716 15 15 15zm4.258-12.676v6.846h-8.426v-6.846H5.204l9.82-12.364 9.82 12.364H19.26z" />
|
35 | </svg>
|
36 | )
|
37 | }
|
38 |
|
39 |
|
40 |
|
41 |
|
42 | module.exports = class Dashboard extends Plugin {
|
43 | static VERSION = require('../package.json').version
|
44 |
|
45 | constructor (uppy, opts) {
|
46 | super(uppy, opts)
|
47 | this.id = this.opts.id || 'Dashboard'
|
48 | this.title = 'Dashboard'
|
49 | this.type = 'orchestrator'
|
50 | this.modalName = `uppy-Dashboard-${cuid()}`
|
51 |
|
52 | this.defaultLocale = {
|
53 | strings: {
|
54 | closeModal: 'Close Modal',
|
55 | importFrom: 'Import from %{name}',
|
56 | addingMoreFiles: 'Adding more files',
|
57 | addMoreFiles: 'Add more files',
|
58 | dashboardWindowTitle: 'File Uploader Window (Press escape to close)',
|
59 | dashboardTitle: 'File Uploader',
|
60 | copyLinkToClipboardSuccess: 'Link copied to clipboard',
|
61 | copyLinkToClipboardFallback: 'Copy the URL below',
|
62 | copyLink: 'Copy link',
|
63 | fileSource: 'File source: %{name}',
|
64 | done: 'Done',
|
65 | back: 'Back',
|
66 | addMore: 'Add more',
|
67 | removeFile: 'Remove file',
|
68 | editFile: 'Edit file',
|
69 | editing: 'Editing %{file}',
|
70 | finishEditingFile: 'Finish editing file',
|
71 | saveChanges: 'Save changes',
|
72 | cancel: 'Cancel',
|
73 | myDevice: 'My Device',
|
74 | dropPasteFiles: 'Drop files here, paste or %{browseFiles}',
|
75 | dropPasteFolders: 'Drop files here, paste or %{browseFolders}',
|
76 | dropPasteBoth: 'Drop files here, paste, %{browseFiles} or %{browseFolders}',
|
77 | dropPasteImportFiles: 'Drop files here, paste, %{browseFiles} or import from:',
|
78 | dropPasteImportFolders: 'Drop files here, paste, %{browseFolders} or import from:',
|
79 | dropPasteImportBoth: 'Drop files here, paste, %{browseFiles}, %{browseFolders} or import from:',
|
80 | dropHint: 'Drop your files here',
|
81 | browseFiles: 'browse files',
|
82 | browseFolders: 'browse folders',
|
83 | uploadComplete: 'Upload complete',
|
84 | uploadPaused: 'Upload paused',
|
85 | resumeUpload: 'Resume upload',
|
86 | pauseUpload: 'Pause upload',
|
87 | retryUpload: 'Retry upload',
|
88 | cancelUpload: 'Cancel upload',
|
89 | xFilesSelected: {
|
90 | 0: '%{smart_count} file selected',
|
91 | 1: '%{smart_count} files selected'
|
92 | },
|
93 | uploadingXFiles: {
|
94 | 0: 'Uploading %{smart_count} file',
|
95 | 1: 'Uploading %{smart_count} files'
|
96 | },
|
97 | processingXFiles: {
|
98 | 0: 'Processing %{smart_count} file',
|
99 | 1: 'Processing %{smart_count} files'
|
100 | },
|
101 |
|
102 |
|
103 |
|
104 |
|
105 |
|
106 | poweredBy2: '%{backwardsCompat} %{uppy}',
|
107 | poweredBy: 'Powered by'
|
108 | }
|
109 | }
|
110 |
|
111 |
|
112 | const defaultOptions = {
|
113 | target: 'body',
|
114 | metaFields: [],
|
115 | trigger: '#uppy-select-files',
|
116 | inline: false,
|
117 | width: 750,
|
118 | height: 550,
|
119 | thumbnailWidth: 280,
|
120 | thumbnailType: 'image/jpeg',
|
121 | waitForThumbnailsBeforeUpload: false,
|
122 | defaultPickerIcon,
|
123 | showLinkToFileUploadResult: true,
|
124 | showProgressDetails: false,
|
125 | hideUploadButton: false,
|
126 | hideCancelButton: false,
|
127 | hideRetryButton: false,
|
128 | hidePauseResumeButton: false,
|
129 | hideProgressAfterFinish: false,
|
130 | doneButtonHandler: () => {
|
131 | this.uppy.reset()
|
132 | this.requestCloseModal()
|
133 | },
|
134 | note: null,
|
135 | closeModalOnClickOutside: false,
|
136 | closeAfterFinish: false,
|
137 | disableStatusBar: false,
|
138 | disableInformer: false,
|
139 | disableThumbnailGenerator: false,
|
140 | disablePageScrollWhenModalOpen: true,
|
141 | animateOpenClose: true,
|
142 | fileManagerSelectionType: 'files',
|
143 | proudlyDisplayPoweredByUppy: true,
|
144 | onRequestCloseModal: () => this.closeModal(),
|
145 | showSelectedFiles: true,
|
146 | showRemoveButtonAfterComplete: false,
|
147 | browserBackButtonClose: false,
|
148 | theme: 'light',
|
149 | autoOpenFileEditor: false,
|
150 | disabled: false
|
151 | }
|
152 |
|
153 |
|
154 | this.opts = { ...defaultOptions, ...opts }
|
155 |
|
156 | this.i18nInit()
|
157 |
|
158 | this.superFocus = createSuperFocus()
|
159 | this.ifFocusedOnUppyRecently = false
|
160 |
|
161 |
|
162 | this.makeDashboardInsidesVisibleAnywayTimeout = null
|
163 | this.removeDragOverClassTimeout = null
|
164 | }
|
165 |
|
166 | setOptions = (newOpts) => {
|
167 | super.setOptions(newOpts)
|
168 | this.i18nInit()
|
169 | }
|
170 |
|
171 | i18nInit = () => {
|
172 | this.translator = new Translator([this.defaultLocale, this.uppy.locale, this.opts.locale])
|
173 | this.i18n = this.translator.translate.bind(this.translator)
|
174 | this.i18nArray = this.translator.translateArray.bind(this.translator)
|
175 | this.setPluginState()
|
176 | }
|
177 |
|
178 | removeTarget = (plugin) => {
|
179 | const pluginState = this.getPluginState()
|
180 |
|
181 | const newTargets = pluginState.targets.filter(target => target.id !== plugin.id)
|
182 |
|
183 | this.setPluginState({
|
184 | targets: newTargets
|
185 | })
|
186 | }
|
187 |
|
188 | addTarget = (plugin) => {
|
189 | const callerPluginId = plugin.id || plugin.constructor.name
|
190 | const callerPluginName = plugin.title || callerPluginId
|
191 | const callerPluginType = plugin.type
|
192 |
|
193 | if (callerPluginType !== 'acquirer' &&
|
194 | callerPluginType !== 'progressindicator' &&
|
195 | callerPluginType !== 'editor') {
|
196 | const msg = 'Dashboard: can only be targeted by plugins of types: acquirer, progressindicator, editor'
|
197 | this.uppy.log(msg, 'error')
|
198 | return
|
199 | }
|
200 |
|
201 | const target = {
|
202 | id: callerPluginId,
|
203 | name: callerPluginName,
|
204 | type: callerPluginType
|
205 | }
|
206 |
|
207 | const state = this.getPluginState()
|
208 | const newTargets = state.targets.slice()
|
209 | newTargets.push(target)
|
210 |
|
211 | this.setPluginState({
|
212 | targets: newTargets
|
213 | })
|
214 |
|
215 | return this.el
|
216 | }
|
217 |
|
218 | hideAllPanels = () => {
|
219 | const update = {
|
220 | activePickerPanel: false,
|
221 | showAddFilesPanel: false,
|
222 | activeOverlayType: null,
|
223 | fileCardFor: null,
|
224 | showFileEditor: false
|
225 | }
|
226 |
|
227 | const current = this.getPluginState()
|
228 | if (current.activePickerPanel === update.activePickerPanel &&
|
229 | current.showAddFilesPanel === update.showAddFilesPanel &&
|
230 | current.showFileEditor === update.showFileEditor &&
|
231 | current.activeOverlayType === update.activeOverlayType) {
|
232 |
|
233 | return
|
234 | }
|
235 |
|
236 | this.setPluginState(update)
|
237 | }
|
238 |
|
239 | showPanel = (id) => {
|
240 | const { targets } = this.getPluginState()
|
241 |
|
242 | const activePickerPanel = targets.filter((target) => {
|
243 | return target.type === 'acquirer' && target.id === id
|
244 | })[0]
|
245 |
|
246 | this.setPluginState({
|
247 | activePickerPanel: activePickerPanel,
|
248 | activeOverlayType: 'PickerPanel'
|
249 | })
|
250 | }
|
251 |
|
252 | canEditFile = (file) => {
|
253 | const { targets } = this.getPluginState()
|
254 | const editors = this._getEditors(targets)
|
255 |
|
256 | return editors.some((target) => (
|
257 | this.uppy.getPlugin(target.id).canEditFile(file)
|
258 | ))
|
259 | }
|
260 |
|
261 | openFileEditor = (file) => {
|
262 | const { targets } = this.getPluginState()
|
263 | const editors = this._getEditors(targets)
|
264 |
|
265 | this.setPluginState({
|
266 | showFileEditor: true,
|
267 | fileCardFor: file.id || null,
|
268 | activeOverlayType: 'FileEditor'
|
269 | })
|
270 |
|
271 | editors.forEach((editor) => {
|
272 | this.uppy.getPlugin(editor.id).selectFile(file)
|
273 | })
|
274 | }
|
275 |
|
276 | openModal = () => {
|
277 | const { promise, resolve } = createPromise()
|
278 |
|
279 | this.savedScrollPosition = window.pageYOffset
|
280 |
|
281 | this.savedActiveElement = document.activeElement
|
282 |
|
283 | if (this.opts.disablePageScrollWhenModalOpen) {
|
284 | document.body.classList.add('uppy-Dashboard-isFixed')
|
285 | }
|
286 |
|
287 | if (this.opts.animateOpenClose && this.getPluginState().isClosing) {
|
288 | const handler = () => {
|
289 | this.setPluginState({
|
290 | isHidden: false
|
291 | })
|
292 | this.el.removeEventListener('animationend', handler, false)
|
293 | resolve()
|
294 | }
|
295 | this.el.addEventListener('animationend', handler, false)
|
296 | } else {
|
297 | this.setPluginState({
|
298 | isHidden: false
|
299 | })
|
300 | resolve()
|
301 | }
|
302 |
|
303 | if (this.opts.browserBackButtonClose) {
|
304 | this.updateBrowserHistory()
|
305 | }
|
306 |
|
307 |
|
308 | document.addEventListener('keydown', this.handleKeyDownInModal)
|
309 |
|
310 | this.uppy.emit('dashboard:modal-open')
|
311 |
|
312 | return promise
|
313 | }
|
314 |
|
315 | closeModal = (opts = {}) => {
|
316 | const {
|
317 | manualClose = true
|
318 | } = opts
|
319 |
|
320 | const { isHidden, isClosing } = this.getPluginState()
|
321 | if (isHidden || isClosing) {
|
322 |
|
323 | return
|
324 | }
|
325 |
|
326 | const { promise, resolve } = createPromise()
|
327 |
|
328 | if (this.opts.disablePageScrollWhenModalOpen) {
|
329 | document.body.classList.remove('uppy-Dashboard-isFixed')
|
330 | }
|
331 |
|
332 | if (this.opts.animateOpenClose) {
|
333 | this.setPluginState({
|
334 | isClosing: true
|
335 | })
|
336 | const handler = () => {
|
337 | this.setPluginState({
|
338 | isHidden: true,
|
339 | isClosing: false
|
340 | })
|
341 |
|
342 | this.superFocus.cancel()
|
343 | this.savedActiveElement.focus()
|
344 |
|
345 | this.el.removeEventListener('animationend', handler, false)
|
346 | resolve()
|
347 | }
|
348 | this.el.addEventListener('animationend', handler, false)
|
349 | } else {
|
350 | this.setPluginState({
|
351 | isHidden: true
|
352 | })
|
353 |
|
354 | this.superFocus.cancel()
|
355 | this.savedActiveElement.focus()
|
356 |
|
357 | resolve()
|
358 | }
|
359 |
|
360 |
|
361 | document.removeEventListener('keydown', this.handleKeyDownInModal)
|
362 |
|
363 | if (manualClose) {
|
364 | if (this.opts.browserBackButtonClose) {
|
365 |
|
366 | if (history.state && history.state[this.modalName]) {
|
367 |
|
368 | history.go(-1)
|
369 | }
|
370 | }
|
371 | }
|
372 |
|
373 | this.uppy.emit('dashboard:modal-closed')
|
374 |
|
375 | return promise
|
376 | }
|
377 |
|
378 | isModalOpen = () => {
|
379 | return !this.getPluginState().isHidden || false
|
380 | }
|
381 |
|
382 | requestCloseModal = () => {
|
383 | if (this.opts.onRequestCloseModal) {
|
384 | return this.opts.onRequestCloseModal()
|
385 | }
|
386 | return this.closeModal()
|
387 | }
|
388 |
|
389 | setDarkModeCapability = (isDarkModeOn) => {
|
390 | const { capabilities } = this.uppy.getState()
|
391 | this.uppy.setState({
|
392 | capabilities: {
|
393 | ...capabilities,
|
394 | darkMode: isDarkModeOn
|
395 | }
|
396 | })
|
397 | }
|
398 |
|
399 | handleSystemDarkModeChange = (event) => {
|
400 | const isDarkModeOnNow = event.matches
|
401 | this.uppy.log(`[Dashboard] Dark mode is ${isDarkModeOnNow ? 'on' : 'off'}`)
|
402 | this.setDarkModeCapability(isDarkModeOnNow)
|
403 | }
|
404 |
|
405 | toggleFileCard = (show, fileID) => {
|
406 | const file = this.uppy.getFile(fileID)
|
407 | if (show) {
|
408 | this.uppy.emit('dashboard:file-edit-start', file)
|
409 | } else {
|
410 | this.uppy.emit('dashboard:file-edit-complete', file)
|
411 | }
|
412 |
|
413 | this.setPluginState({
|
414 | fileCardFor: show ? fileID : null,
|
415 | activeOverlayType: show ? 'FileCard' : null
|
416 | })
|
417 | }
|
418 |
|
419 | toggleAddFilesPanel = (show) => {
|
420 | this.setPluginState({
|
421 | showAddFilesPanel: show,
|
422 | activeOverlayType: show ? 'AddFiles' : null
|
423 | })
|
424 | }
|
425 |
|
426 | addFiles = (files) => {
|
427 | const descriptors = files.map((file) => ({
|
428 | source: this.id,
|
429 | name: file.name,
|
430 | type: file.type,
|
431 | data: file,
|
432 | meta: {
|
433 |
|
434 |
|
435 | relativePath: file.relativePath || null
|
436 | }
|
437 | }))
|
438 |
|
439 | try {
|
440 | this.uppy.addFiles(descriptors)
|
441 | } catch (err) {
|
442 | this.uppy.log(err)
|
443 | }
|
444 | }
|
445 |
|
446 |
|
447 |
|
448 |
|
449 |
|
450 | startListeningToResize = () => {
|
451 |
|
452 |
|
453 |
|
454 | this.resizeObserver = new ResizeObserver((entries, observer) => {
|
455 | const uppyDashboardInnerEl = entries[0]
|
456 |
|
457 | const { width, height } = uppyDashboardInnerEl.contentRect
|
458 |
|
459 | this.uppy.log(`[Dashboard] resized: ${width} / ${height}`, 'debug')
|
460 |
|
461 | this.setPluginState({
|
462 | containerWidth: width,
|
463 | containerHeight: height,
|
464 | areInsidesReadyToBeVisible: true
|
465 | })
|
466 | })
|
467 | this.resizeObserver.observe(this.el.querySelector('.uppy-Dashboard-inner'))
|
468 |
|
469 |
|
470 | this.makeDashboardInsidesVisibleAnywayTimeout = setTimeout(() => {
|
471 | const pluginState = this.getPluginState()
|
472 | const isModalAndClosed = !this.opts.inline && pluginState.isHidden
|
473 | if (
|
474 |
|
475 | !pluginState.areInsidesReadyToBeVisible &&
|
476 |
|
477 | !isModalAndClosed
|
478 | ) {
|
479 | this.uppy.log("[Dashboard] resize event didn't fire on time: defaulted to mobile layout", 'debug')
|
480 |
|
481 | this.setPluginState({
|
482 | areInsidesReadyToBeVisible: true
|
483 | })
|
484 | }
|
485 | }, 1000)
|
486 | }
|
487 |
|
488 | stopListeningToResize = () => {
|
489 | this.resizeObserver.disconnect()
|
490 |
|
491 | clearTimeout(this.makeDashboardInsidesVisibleAnywayTimeout)
|
492 | }
|
493 |
|
494 |
|
495 | recordIfFocusedOnUppyRecently = (event) => {
|
496 | if (this.el.contains(event.target)) {
|
497 | this.ifFocusedOnUppyRecently = true
|
498 | } else {
|
499 | this.ifFocusedOnUppyRecently = false
|
500 |
|
501 |
|
502 |
|
503 | this.superFocus.cancel()
|
504 | }
|
505 | }
|
506 |
|
507 | disableAllFocusableElements = (disable) => {
|
508 | const focusableNodes = toArray(this.el.querySelectorAll(FOCUSABLE_ELEMENTS))
|
509 | if (disable) {
|
510 | focusableNodes.forEach((node) => {
|
511 |
|
512 | const currentTabIndex = node.getAttribute('tabindex')
|
513 | if (currentTabIndex) {
|
514 | node.dataset.inertTabindex = currentTabIndex
|
515 | }
|
516 | node.setAttribute('tabindex', '-1')
|
517 | })
|
518 | } else {
|
519 | focusableNodes.forEach((node) => {
|
520 | if ('inertTabindex' in node.dataset) {
|
521 | node.setAttribute('tabindex', node.dataset.inertTabindex)
|
522 | } else {
|
523 | node.removeAttribute('tabindex')
|
524 | }
|
525 | })
|
526 | }
|
527 | this.dashboardIsDisabled = disable
|
528 | }
|
529 |
|
530 | updateBrowserHistory = () => {
|
531 |
|
532 | if (!history.state || !history.state[this.modalName]) {
|
533 |
|
534 | history.pushState({
|
535 | ...history.state,
|
536 | [this.modalName]: true
|
537 | }, '')
|
538 | }
|
539 |
|
540 |
|
541 | window.addEventListener('popstate', this.handlePopState, false)
|
542 | }
|
543 |
|
544 | handlePopState = (event) => {
|
545 |
|
546 | if (this.isModalOpen() && (!event.state || !event.state[this.modalName])) {
|
547 | this.closeModal({ manualClose: false })
|
548 | }
|
549 |
|
550 |
|
551 |
|
552 |
|
553 | if (!this.isModalOpen() && event.state && event.state[this.modalName]) {
|
554 | history.go(-1)
|
555 | }
|
556 | }
|
557 |
|
558 | handleKeyDownInModal = (event) => {
|
559 |
|
560 | if (event.keyCode === ESC_KEY) this.requestCloseModal(event)
|
561 |
|
562 | if (event.keyCode === TAB_KEY) trapFocus.forModal(event, this.getPluginState().activeOverlayType, this.el)
|
563 | }
|
564 |
|
565 | handleClickOutside = () => {
|
566 | if (this.opts.closeModalOnClickOutside) this.requestCloseModal()
|
567 | }
|
568 |
|
569 | handlePaste = (event) => {
|
570 |
|
571 | this.uppy.iteratePlugins((plugin) => {
|
572 | if (plugin.type === 'acquirer') {
|
573 |
|
574 | plugin.handleRootPaste && plugin.handleRootPaste(event)
|
575 | }
|
576 | })
|
577 |
|
578 |
|
579 | const files = toArray(event.clipboardData.files)
|
580 | this.addFiles(files)
|
581 | }
|
582 |
|
583 | handleInputChange = (event) => {
|
584 | event.preventDefault()
|
585 | const files = toArray(event.target.files)
|
586 | this.addFiles(files)
|
587 | }
|
588 |
|
589 | handleDragOver = (event) => {
|
590 | event.preventDefault()
|
591 | event.stopPropagation()
|
592 |
|
593 | if (this.opts.disabled) {
|
594 | return
|
595 | }
|
596 |
|
597 |
|
598 |
|
599 | event.dataTransfer.dropEffect = 'copy'
|
600 |
|
601 | clearTimeout(this.removeDragOverClassTimeout)
|
602 | this.setPluginState({ isDraggingOver: true })
|
603 | }
|
604 |
|
605 | handleDragLeave = (event) => {
|
606 | event.preventDefault()
|
607 | event.stopPropagation()
|
608 |
|
609 | if (this.opts.disabled) {
|
610 | return
|
611 | }
|
612 |
|
613 | clearTimeout(this.removeDragOverClassTimeout)
|
614 |
|
615 | this.removeDragOverClassTimeout = setTimeout(() => {
|
616 | this.setPluginState({ isDraggingOver: false })
|
617 | }, 50)
|
618 | }
|
619 |
|
620 | handleDrop = (event, dropCategory) => {
|
621 | event.preventDefault()
|
622 | event.stopPropagation()
|
623 |
|
624 | if (this.opts.disabled) {
|
625 | return
|
626 | }
|
627 |
|
628 | clearTimeout(this.removeDragOverClassTimeout)
|
629 |
|
630 |
|
631 | this.setPluginState({ isDraggingOver: false })
|
632 |
|
633 |
|
634 | this.uppy.iteratePlugins((plugin) => {
|
635 | if (plugin.type === 'acquirer') {
|
636 |
|
637 | plugin.handleRootDrop && plugin.handleRootDrop(event)
|
638 | }
|
639 | })
|
640 |
|
641 |
|
642 | let executedDropErrorOnce = false
|
643 | const logDropError = (error) => {
|
644 | this.uppy.log(error, 'error')
|
645 |
|
646 |
|
647 | if (!executedDropErrorOnce) {
|
648 | this.uppy.info(error.message, 'error')
|
649 | executedDropErrorOnce = true
|
650 | }
|
651 | }
|
652 |
|
653 | getDroppedFiles(event.dataTransfer, { logDropError })
|
654 | .then((files) => {
|
655 | if (files.length > 0) {
|
656 | this.uppy.log('[Dashboard] Files were dropped')
|
657 | this.addFiles(files)
|
658 | }
|
659 | })
|
660 | }
|
661 |
|
662 | handleRequestThumbnail = (file) => {
|
663 | if (!this.opts.waitForThumbnailsBeforeUpload) {
|
664 | this.uppy.emit('thumbnail:request', file)
|
665 | }
|
666 | }
|
667 |
|
668 | |
669 |
|
670 |
|
671 | handleCancelThumbnail = (file) => {
|
672 | if (!this.opts.waitForThumbnailsBeforeUpload) {
|
673 | this.uppy.emit('thumbnail:cancel', file)
|
674 | }
|
675 | }
|
676 |
|
677 | handleKeyDownInInline = (event) => {
|
678 |
|
679 | if (event.keyCode === TAB_KEY) trapFocus.forInline(event, this.getPluginState().activeOverlayType, this.el)
|
680 | }
|
681 |
|
682 |
|
683 |
|
684 |
|
685 |
|
686 |
|
687 |
|
688 | handlePasteOnBody = (event) => {
|
689 | const isFocusInOverlay = this.el.contains(document.activeElement)
|
690 | if (isFocusInOverlay) {
|
691 | this.handlePaste(event)
|
692 | }
|
693 | }
|
694 |
|
695 | handleComplete = ({ failed }) => {
|
696 | if (this.opts.closeAfterFinish && failed.length === 0) {
|
697 |
|
698 | this.requestCloseModal()
|
699 | }
|
700 | }
|
701 |
|
702 | _openFileEditorWhenFilesAdded = (files) => {
|
703 | const firstFile = files[0]
|
704 | if (this.canEditFile(firstFile)) {
|
705 | this.openFileEditor(firstFile)
|
706 | }
|
707 | }
|
708 |
|
709 | initEvents = () => {
|
710 |
|
711 | if (this.opts.trigger && !this.opts.inline) {
|
712 | const showModalTrigger = findAllDOMElements(this.opts.trigger)
|
713 | if (showModalTrigger) {
|
714 | showModalTrigger.forEach(trigger => trigger.addEventListener('click', this.openModal))
|
715 | } else {
|
716 | this.uppy.log('Dashboard modal trigger not found. Make sure `trigger` is set in Dashboard options, unless you are planning to call `dashboard.openModal()` method yourself', 'warning')
|
717 | }
|
718 | }
|
719 |
|
720 | this.startListeningToResize()
|
721 | document.addEventListener('paste', this.handlePasteOnBody)
|
722 |
|
723 | this.uppy.on('plugin-remove', this.removeTarget)
|
724 | this.uppy.on('file-added', this.hideAllPanels)
|
725 | this.uppy.on('dashboard:modal-closed', this.hideAllPanels)
|
726 | this.uppy.on('file-editor:complete', this.hideAllPanels)
|
727 | this.uppy.on('complete', this.handleComplete)
|
728 |
|
729 |
|
730 |
|
731 | document.addEventListener('focus', this.recordIfFocusedOnUppyRecently, true)
|
732 | document.addEventListener('click', this.recordIfFocusedOnUppyRecently, true)
|
733 |
|
734 | if (this.opts.inline) {
|
735 | this.el.addEventListener('keydown', this.handleKeyDownInInline)
|
736 | }
|
737 |
|
738 | if (this.opts.autoOpenFileEditor) {
|
739 | this.uppy.on('files-added', this._openFileEditorWhenFilesAdded)
|
740 | }
|
741 | }
|
742 |
|
743 | removeEvents = () => {
|
744 | const showModalTrigger = findAllDOMElements(this.opts.trigger)
|
745 | if (!this.opts.inline && showModalTrigger) {
|
746 | showModalTrigger.forEach(trigger => trigger.removeEventListener('click', this.openModal))
|
747 | }
|
748 |
|
749 | this.stopListeningToResize()
|
750 | document.removeEventListener('paste', this.handlePasteOnBody)
|
751 |
|
752 | window.removeEventListener('popstate', this.handlePopState, false)
|
753 | this.uppy.off('plugin-remove', this.removeTarget)
|
754 | this.uppy.off('file-added', this.hideAllPanels)
|
755 | this.uppy.off('dashboard:modal-closed', this.hideAllPanels)
|
756 | this.uppy.off('complete', this.handleComplete)
|
757 |
|
758 | document.removeEventListener('focus', this.recordIfFocusedOnUppyRecently)
|
759 | document.removeEventListener('click', this.recordIfFocusedOnUppyRecently)
|
760 |
|
761 | if (this.opts.inline) {
|
762 | this.el.removeEventListener('keydown', this.handleKeyDownInInline)
|
763 | }
|
764 |
|
765 | if (this.opts.autoOpenFileEditor) {
|
766 | this.uppy.off('files-added', this._openFileEditorWhenFilesAdded)
|
767 | }
|
768 | }
|
769 |
|
770 | superFocusOnEachUpdate = () => {
|
771 | const isFocusInUppy = this.el.contains(document.activeElement)
|
772 |
|
773 | const isFocusNowhere = document.activeElement === document.body || document.activeElement === null
|
774 | const isInformerHidden = this.uppy.getState().info.isHidden
|
775 | const isModal = !this.opts.inline
|
776 |
|
777 | if (
|
778 |
|
779 | isInformerHidden &&
|
780 | (
|
781 |
|
782 | isModal ||
|
783 |
|
784 | isFocusInUppy ||
|
785 |
|
786 |
|
787 |
|
788 |
|
789 | (isFocusNowhere && this.ifFocusedOnUppyRecently)
|
790 | )
|
791 | ) {
|
792 | this.superFocus(this.el, this.getPluginState().activeOverlayType)
|
793 | } else {
|
794 | this.superFocus.cancel()
|
795 | }
|
796 | }
|
797 |
|
798 | afterUpdate = () => {
|
799 | if (this.opts.disabled && !this.dashboardIsDisabled) {
|
800 | this.disableAllFocusableElements(true)
|
801 | return
|
802 | }
|
803 |
|
804 | if (!this.opts.disabled && this.dashboardIsDisabled) {
|
805 | this.disableAllFocusableElements(false)
|
806 | }
|
807 |
|
808 | this.superFocusOnEachUpdate()
|
809 | }
|
810 |
|
811 | cancelUpload = (fileID) => {
|
812 | this.uppy.removeFile(fileID)
|
813 | }
|
814 |
|
815 | saveFileCard = (meta, fileID) => {
|
816 | this.uppy.setFileMeta(fileID, meta)
|
817 | this.toggleFileCard(false, fileID)
|
818 | }
|
819 |
|
820 | _attachRenderFunctionToTarget = (target) => {
|
821 | const plugin = this.uppy.getPlugin(target.id)
|
822 | return {
|
823 | ...target,
|
824 | icon: plugin.icon || this.opts.defaultPickerIcon,
|
825 | render: plugin.render
|
826 | }
|
827 | }
|
828 |
|
829 | _isTargetSupported = (target) => {
|
830 | const plugin = this.uppy.getPlugin(target.id)
|
831 |
|
832 | if (typeof plugin.isSupported !== 'function') {
|
833 | return true
|
834 | }
|
835 | return plugin.isSupported()
|
836 | }
|
837 |
|
838 | _getAcquirers = memoize((targets) => {
|
839 | return targets
|
840 | .filter(target => target.type === 'acquirer' && this._isTargetSupported(target))
|
841 | .map(this._attachRenderFunctionToTarget)
|
842 | })
|
843 |
|
844 | _getProgressIndicators = memoize((targets) => {
|
845 | return targets
|
846 | .filter(target => target.type === 'progressindicator')
|
847 | .map(this._attachRenderFunctionToTarget)
|
848 | })
|
849 |
|
850 | _getEditors = memoize((targets) => {
|
851 | return targets
|
852 | .filter(target => target.type === 'editor')
|
853 | .map(this._attachRenderFunctionToTarget)
|
854 | })
|
855 |
|
856 | render = (state) => {
|
857 | const pluginState = this.getPluginState()
|
858 | const { files, capabilities, allowNewUpload } = state
|
859 |
|
860 |
|
861 |
|
862 | const newFiles = Object.keys(files).filter((file) => {
|
863 | return !files[file].progress.uploadStarted
|
864 | })
|
865 |
|
866 | const uploadStartedFiles = Object.keys(files).filter((file) => {
|
867 | return files[file].progress.uploadStarted
|
868 | })
|
869 |
|
870 | const pausedFiles = Object.keys(files).filter((file) => {
|
871 | return files[file].isPaused
|
872 | })
|
873 |
|
874 | const completeFiles = Object.keys(files).filter((file) => {
|
875 | return files[file].progress.uploadComplete
|
876 | })
|
877 |
|
878 | const erroredFiles = Object.keys(files).filter((file) => {
|
879 | return files[file].error
|
880 | })
|
881 |
|
882 | const inProgressFiles = Object.keys(files).filter((file) => {
|
883 | return !files[file].progress.uploadComplete &&
|
884 | files[file].progress.uploadStarted
|
885 | })
|
886 |
|
887 | const inProgressNotPausedFiles = inProgressFiles.filter((file) => {
|
888 | return !files[file].isPaused
|
889 | })
|
890 |
|
891 | const processingFiles = Object.keys(files).filter((file) => {
|
892 | return files[file].progress.preprocess || files[file].progress.postprocess
|
893 | })
|
894 |
|
895 | const isUploadStarted = uploadStartedFiles.length > 0
|
896 |
|
897 | const isAllComplete = state.totalProgress === 100 &&
|
898 | completeFiles.length === Object.keys(files).length &&
|
899 | processingFiles.length === 0
|
900 |
|
901 | const isAllErrored = isUploadStarted &&
|
902 | erroredFiles.length === uploadStartedFiles.length
|
903 |
|
904 | const isAllPaused = inProgressFiles.length !== 0 &&
|
905 | pausedFiles.length === inProgressFiles.length
|
906 |
|
907 | const acquirers = this._getAcquirers(pluginState.targets)
|
908 | const progressindicators = this._getProgressIndicators(pluginState.targets)
|
909 | const editors = this._getEditors(pluginState.targets)
|
910 |
|
911 | let theme
|
912 | if (this.opts.theme === 'auto') {
|
913 | theme = capabilities.darkMode ? 'dark' : 'light'
|
914 | } else {
|
915 | theme = this.opts.theme
|
916 | }
|
917 |
|
918 | if (['files', 'folders', 'both'].indexOf(this.opts.fileManagerSelectionType) < 0) {
|
919 | this.opts.fileManagerSelectionType = 'files'
|
920 | console.error(`Unsupported option for "fileManagerSelectionType". Using default of "${this.opts.fileManagerSelectionType}".`)
|
921 | }
|
922 |
|
923 | return DashboardUI({
|
924 | state,
|
925 | isHidden: pluginState.isHidden,
|
926 | files,
|
927 | newFiles,
|
928 | uploadStartedFiles,
|
929 | completeFiles,
|
930 | erroredFiles,
|
931 | inProgressFiles,
|
932 | inProgressNotPausedFiles,
|
933 | processingFiles,
|
934 | isUploadStarted,
|
935 | isAllComplete,
|
936 | isAllErrored,
|
937 | isAllPaused,
|
938 | totalFileCount: Object.keys(files).length,
|
939 | totalProgress: state.totalProgress,
|
940 | allowNewUpload,
|
941 | acquirers,
|
942 | theme,
|
943 | disabled: this.opts.disabled,
|
944 | direction: this.opts.direction,
|
945 | activePickerPanel: pluginState.activePickerPanel,
|
946 | showFileEditor: pluginState.showFileEditor,
|
947 | disableAllFocusableElements: this.disableAllFocusableElements,
|
948 | animateOpenClose: this.opts.animateOpenClose,
|
949 | isClosing: pluginState.isClosing,
|
950 | getPlugin: this.uppy.getPlugin,
|
951 | progressindicators: progressindicators,
|
952 | editors: editors,
|
953 | autoProceed: this.uppy.opts.autoProceed,
|
954 | id: this.id,
|
955 | closeModal: this.requestCloseModal,
|
956 | handleClickOutside: this.handleClickOutside,
|
957 | handleInputChange: this.handleInputChange,
|
958 | handlePaste: this.handlePaste,
|
959 | inline: this.opts.inline,
|
960 | showPanel: this.showPanel,
|
961 | hideAllPanels: this.hideAllPanels,
|
962 | log: this.uppy.log,
|
963 | i18n: this.i18n,
|
964 | i18nArray: this.i18nArray,
|
965 | removeFile: this.uppy.removeFile,
|
966 | uppy: this.uppy,
|
967 | info: this.uppy.info,
|
968 | note: this.opts.note,
|
969 | metaFields: pluginState.metaFields,
|
970 | resumableUploads: capabilities.resumableUploads || false,
|
971 | individualCancellation: capabilities.individualCancellation,
|
972 | isMobileDevice: capabilities.isMobileDevice,
|
973 | pauseUpload: this.uppy.pauseResume,
|
974 | retryUpload: this.uppy.retryUpload,
|
975 | cancelUpload: this.cancelUpload,
|
976 | cancelAll: this.uppy.cancelAll,
|
977 | fileCardFor: pluginState.fileCardFor,
|
978 | toggleFileCard: this.toggleFileCard,
|
979 | toggleAddFilesPanel: this.toggleAddFilesPanel,
|
980 | showAddFilesPanel: pluginState.showAddFilesPanel,
|
981 | saveFileCard: this.saveFileCard,
|
982 | openFileEditor: this.openFileEditor,
|
983 | canEditFile: this.canEditFile,
|
984 | width: this.opts.width,
|
985 | height: this.opts.height,
|
986 | showLinkToFileUploadResult: this.opts.showLinkToFileUploadResult,
|
987 | fileManagerSelectionType: this.opts.fileManagerSelectionType,
|
988 | proudlyDisplayPoweredByUppy: this.opts.proudlyDisplayPoweredByUppy,
|
989 | hideCancelButton: this.opts.hideCancelButton,
|
990 | hideRetryButton: this.opts.hideRetryButton,
|
991 | hidePauseResumeButton: this.opts.hidePauseResumeButton,
|
992 | showRemoveButtonAfterComplete: this.opts.showRemoveButtonAfterComplete,
|
993 | containerWidth: pluginState.containerWidth,
|
994 | containerHeight: pluginState.containerHeight,
|
995 | areInsidesReadyToBeVisible: pluginState.areInsidesReadyToBeVisible,
|
996 | isTargetDOMEl: this.isTargetDOMEl,
|
997 | parentElement: this.el,
|
998 | allowedFileTypes: this.uppy.opts.restrictions.allowedFileTypes,
|
999 | maxNumberOfFiles: this.uppy.opts.restrictions.maxNumberOfFiles,
|
1000 | showSelectedFiles: this.opts.showSelectedFiles,
|
1001 | handleRequestThumbnail: this.handleRequestThumbnail,
|
1002 | handleCancelThumbnail: this.handleCancelThumbnail,
|
1003 |
|
1004 | isDraggingOver: pluginState.isDraggingOver,
|
1005 | handleDragOver: this.handleDragOver,
|
1006 | handleDragLeave: this.handleDragLeave,
|
1007 | handleDrop: this.handleDrop
|
1008 | })
|
1009 | }
|
1010 |
|
1011 | discoverProviderPlugins = () => {
|
1012 | this.uppy.iteratePlugins((plugin) => {
|
1013 | if (plugin && !plugin.target && plugin.opts && plugin.opts.target === this.constructor) {
|
1014 | this.addTarget(plugin)
|
1015 | }
|
1016 | })
|
1017 | }
|
1018 |
|
1019 | onMount () {
|
1020 |
|
1021 | const element = this.el
|
1022 | const direction = getTextDirection(element)
|
1023 | if (!direction) {
|
1024 | element.dir = 'ltr'
|
1025 | }
|
1026 | }
|
1027 |
|
1028 | install = () => {
|
1029 |
|
1030 | this.setPluginState({
|
1031 | isHidden: true,
|
1032 | fileCardFor: null,
|
1033 | activeOverlayType: null,
|
1034 | showAddFilesPanel: false,
|
1035 | activePickerPanel: false,
|
1036 | showFileEditor: false,
|
1037 | metaFields: this.opts.metaFields,
|
1038 | targets: [],
|
1039 |
|
1040 | areInsidesReadyToBeVisible: false,
|
1041 | isDraggingOver: false
|
1042 | })
|
1043 |
|
1044 | const { inline, closeAfterFinish } = this.opts
|
1045 | if (inline && closeAfterFinish) {
|
1046 | throw new Error('[Dashboard] `closeAfterFinish: true` cannot be used on an inline Dashboard, because an inline Dashboard cannot be closed at all. Either set `inline: false`, or disable the `closeAfterFinish` option.')
|
1047 | }
|
1048 |
|
1049 | const { allowMultipleUploads } = this.uppy.opts
|
1050 | if (allowMultipleUploads && closeAfterFinish) {
|
1051 | this.uppy.log('[Dashboard] When using `closeAfterFinish`, we recommended setting the `allowMultipleUploads` option to `false` in the Uppy constructor. See https://uppy.io/docs/uppy/#allowMultipleUploads-true', 'warning')
|
1052 | }
|
1053 |
|
1054 | const { target } = this.opts
|
1055 | if (target) {
|
1056 | this.mount(target, this)
|
1057 | }
|
1058 |
|
1059 | const plugins = this.opts.plugins || []
|
1060 | plugins.forEach((pluginID) => {
|
1061 | const plugin = this.uppy.getPlugin(pluginID)
|
1062 | if (plugin) {
|
1063 | plugin.mount(this, plugin)
|
1064 | }
|
1065 | })
|
1066 |
|
1067 | if (!this.opts.disableStatusBar) {
|
1068 | this.uppy.use(StatusBar, {
|
1069 | id: `${this.id}:StatusBar`,
|
1070 | target: this,
|
1071 | hideUploadButton: this.opts.hideUploadButton,
|
1072 | hideRetryButton: this.opts.hideRetryButton,
|
1073 | hidePauseResumeButton: this.opts.hidePauseResumeButton,
|
1074 | hideCancelButton: this.opts.hideCancelButton,
|
1075 | showProgressDetails: this.opts.showProgressDetails,
|
1076 | hideAfterFinish: this.opts.hideProgressAfterFinish,
|
1077 | locale: this.opts.locale,
|
1078 | doneButtonHandler: this.opts.doneButtonHandler
|
1079 | })
|
1080 | }
|
1081 |
|
1082 | if (!this.opts.disableInformer) {
|
1083 | this.uppy.use(Informer, {
|
1084 | id: `${this.id}:Informer`,
|
1085 | target: this
|
1086 | })
|
1087 | }
|
1088 |
|
1089 | if (!this.opts.disableThumbnailGenerator) {
|
1090 | this.uppy.use(ThumbnailGenerator, {
|
1091 | id: `${this.id}:ThumbnailGenerator`,
|
1092 | thumbnailWidth: this.opts.thumbnailWidth,
|
1093 | thumbnailType: this.opts.thumbnailType,
|
1094 | waitForThumbnailsBeforeUpload: this.opts.waitForThumbnailsBeforeUpload,
|
1095 |
|
1096 | lazy: !this.opts.waitForThumbnailsBeforeUpload
|
1097 | })
|
1098 | }
|
1099 |
|
1100 |
|
1101 | this.darkModeMediaQuery = (typeof window !== 'undefined' && window.matchMedia)
|
1102 | ? window.matchMedia('(prefers-color-scheme: dark)')
|
1103 | : null
|
1104 |
|
1105 | const isDarkModeOnFromTheStart = this.darkModeMediaQuery ? this.darkModeMediaQuery.matches : false
|
1106 | this.uppy.log(`[Dashboard] Dark mode is ${isDarkModeOnFromTheStart ? 'on' : 'off'}`)
|
1107 | this.setDarkModeCapability(isDarkModeOnFromTheStart)
|
1108 |
|
1109 | if (this.opts.theme === 'auto') {
|
1110 | this.darkModeMediaQuery.addListener(this.handleSystemDarkModeChange)
|
1111 | }
|
1112 |
|
1113 | this.discoverProviderPlugins()
|
1114 | this.initEvents()
|
1115 | }
|
1116 |
|
1117 | uninstall = () => {
|
1118 | if (!this.opts.disableInformer) {
|
1119 | const informer = this.uppy.getPlugin(`${this.id}:Informer`)
|
1120 |
|
1121 |
|
1122 | if (informer) this.uppy.removePlugin(informer)
|
1123 | }
|
1124 |
|
1125 | if (!this.opts.disableStatusBar) {
|
1126 | const statusBar = this.uppy.getPlugin(`${this.id}:StatusBar`)
|
1127 | if (statusBar) this.uppy.removePlugin(statusBar)
|
1128 | }
|
1129 |
|
1130 | if (!this.opts.disableThumbnailGenerator) {
|
1131 | const thumbnail = this.uppy.getPlugin(`${this.id}:ThumbnailGenerator`)
|
1132 | if (thumbnail) this.uppy.removePlugin(thumbnail)
|
1133 | }
|
1134 |
|
1135 | const plugins = this.opts.plugins || []
|
1136 | plugins.forEach((pluginID) => {
|
1137 | const plugin = this.uppy.getPlugin(pluginID)
|
1138 | if (plugin) plugin.unmount()
|
1139 | })
|
1140 |
|
1141 | if (this.opts.theme === 'auto') {
|
1142 | this.darkModeMediaQuery.removeListener(this.handleSystemDarkModeChange)
|
1143 | }
|
1144 |
|
1145 | this.unmount()
|
1146 | this.removeEvents()
|
1147 | }
|
1148 | }
|