index.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722
  1. const { Plugin } = require('@uppy/core')
  2. const Translator = require('@uppy/utils/lib/Translator')
  3. const dragDrop = require('drag-drop')
  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 prettyBytes = require('prettier-bytes')
  11. const ResizeObserver = require('resize-observer-polyfill')
  12. const { defaultTabIcon } = require('./components/icons')
  13. // Some code for managing focus was adopted from https://github.com/ghosh/micromodal
  14. // MIT licence, https://github.com/ghosh/micromodal/blob/master/LICENSE.md
  15. // Copyright (c) 2017 Indrashish Ghosh
  16. const FOCUSABLE_ELEMENTS = [
  17. 'a[href]:not([tabindex^="-"]):not([inert]):not([aria-hidden])',
  18. 'area[href]:not([tabindex^="-"]):not([inert]):not([aria-hidden])',
  19. 'input:not([disabled]):not([inert]):not([aria-hidden])',
  20. 'select:not([disabled]):not([inert]):not([aria-hidden])',
  21. 'textarea:not([disabled]):not([inert]):not([aria-hidden])',
  22. 'button:not([disabled]):not([inert]):not([aria-hidden])',
  23. 'iframe:not([tabindex^="-"]):not([inert]):not([aria-hidden])',
  24. 'object:not([tabindex^="-"]):not([inert]):not([aria-hidden])',
  25. 'embed:not([tabindex^="-"]):not([inert]):not([aria-hidden])',
  26. '[contenteditable]:not([tabindex^="-"]):not([inert]):not([aria-hidden])',
  27. '[tabindex]:not([tabindex^="-"]):not([inert]):not([aria-hidden])'
  28. ]
  29. const TAB_KEY = 9
  30. const ESC_KEY = 27
  31. /**
  32. * Dashboard UI with previews, metadata editing, tabs for various services and more
  33. */
  34. module.exports = class Dashboard extends Plugin {
  35. constructor (uppy, opts) {
  36. super(uppy, opts)
  37. this.id = this.opts.id || 'Dashboard'
  38. this.title = 'Dashboard'
  39. this.type = 'orchestrator'
  40. this.modalName = 'uppy-Dashboard'
  41. const defaultLocale = {
  42. strings: {
  43. selectToUpload: 'Select files to upload',
  44. closeModal: 'Close Modal',
  45. upload: 'Upload',
  46. importFrom: 'Import from %{name}',
  47. addingMoreFiles: 'Adding more files',
  48. addMoreFiles: 'Add more files',
  49. dashboardWindowTitle: 'Uppy Dashboard Window (Press escape to close)',
  50. dashboardTitle: 'Uppy Dashboard',
  51. copyLinkToClipboardSuccess: 'Link copied to clipboard',
  52. copyLinkToClipboardFallback: 'Copy the URL below',
  53. copyLink: 'Copy link',
  54. fileSource: 'File source: %{name}',
  55. done: 'Done',
  56. back: 'Back',
  57. name: 'Name',
  58. removeFile: 'Remove file',
  59. editFile: 'Edit file',
  60. editing: 'Editing %{file}',
  61. edit: 'Edit',
  62. finishEditingFile: 'Finish editing file',
  63. saveChanges: 'Save changes',
  64. cancel: 'Cancel',
  65. localDisk: 'Local Disk',
  66. myDevice: 'My Device',
  67. dropPasteImport: 'Drop files here, paste, %{browse} or import from',
  68. dropPaste: 'Drop files here, paste or %{browse}',
  69. browse: 'browse',
  70. fileProgress: 'File progress: upload speed and ETA',
  71. numberOfSelectedFiles: 'Number of selected files',
  72. uploadAllNewFiles: 'Upload all new files',
  73. emptyFolderAdded: 'No files were added from empty folder',
  74. uploadComplete: 'Upload complete',
  75. resumeUpload: 'Resume upload',
  76. pauseUpload: 'Pause upload',
  77. retryUpload: 'Retry upload',
  78. cancelUpload: 'Cancel upload',
  79. xFilesSelected: {
  80. 0: '%{smart_count} file selected',
  81. 1: '%{smart_count} files selected'
  82. },
  83. uploadXFiles: {
  84. 0: 'Upload %{smart_count} file',
  85. 1: 'Upload %{smart_count} files'
  86. },
  87. uploadXNewFiles: {
  88. 0: 'Upload +%{smart_count} file',
  89. 1: 'Upload +%{smart_count} files'
  90. },
  91. folderAdded: {
  92. 0: 'Added %{smart_count} file from %{folder}',
  93. 1: 'Added %{smart_count} files from %{folder}'
  94. }
  95. }
  96. }
  97. // set default options
  98. const defaultOptions = {
  99. target: 'body',
  100. metaFields: [],
  101. trigger: '#uppy-select-files',
  102. inline: false,
  103. width: 750,
  104. height: 550,
  105. thumbnailWidth: 280,
  106. defaultTabIcon: defaultTabIcon,
  107. showLinkToFileUploadResult: true,
  108. showProgressDetails: false,
  109. hideUploadButton: false,
  110. hideRetryButton: false,
  111. hidePauseResumeCancelButtons: false,
  112. hideProgressAfterFinish: false,
  113. note: null,
  114. closeModalOnClickOutside: false,
  115. disableStatusBar: false,
  116. disableInformer: false,
  117. disableThumbnailGenerator: false,
  118. disablePageScrollWhenModalOpen: true,
  119. animateOpenClose: true,
  120. proudlyDisplayPoweredByUppy: true,
  121. onRequestCloseModal: () => this.closeModal(),
  122. locale: defaultLocale,
  123. browserBackButtonClose: false
  124. }
  125. // merge default options with the ones set by user
  126. this.opts = Object.assign({}, defaultOptions, opts)
  127. this.locale = Object.assign({}, defaultLocale, this.opts.locale)
  128. this.locale.strings = Object.assign({}, defaultLocale.strings, this.opts.locale.strings)
  129. this.translator = new Translator({locale: this.locale})
  130. this.i18n = this.translator.translate.bind(this.translator)
  131. this.i18nArray = this.translator.translateArray.bind(this.translator)
  132. this.openModal = this.openModal.bind(this)
  133. this.closeModal = this.closeModal.bind(this)
  134. this.requestCloseModal = this.requestCloseModal.bind(this)
  135. this.isModalOpen = this.isModalOpen.bind(this)
  136. this.addTarget = this.addTarget.bind(this)
  137. this.removeTarget = this.removeTarget.bind(this)
  138. this.hideAllPanels = this.hideAllPanels.bind(this)
  139. this.showPanel = this.showPanel.bind(this)
  140. this.getFocusableNodes = this.getFocusableNodes.bind(this)
  141. this.setFocusToFirstNode = this.setFocusToFirstNode.bind(this)
  142. this.handlePopState = this.handlePopState.bind(this)
  143. this.maintainFocus = this.maintainFocus.bind(this)
  144. this.initEvents = this.initEvents.bind(this)
  145. this.onKeydown = this.onKeydown.bind(this)
  146. this.handleClickOutside = this.handleClickOutside.bind(this)
  147. this.toggleFileCard = this.toggleFileCard.bind(this)
  148. this.toggleAddFilesPanel = this.toggleAddFilesPanel.bind(this)
  149. this.handleDrop = this.handleDrop.bind(this)
  150. this.handlePaste = this.handlePaste.bind(this)
  151. this.handleInputChange = this.handleInputChange.bind(this)
  152. this.render = this.render.bind(this)
  153. this.install = this.install.bind(this)
  154. }
  155. removeTarget (plugin) {
  156. const pluginState = this.getPluginState()
  157. // filter out the one we want to remove
  158. const newTargets = pluginState.targets.filter(target => target.id !== plugin.id)
  159. this.setPluginState({
  160. targets: newTargets
  161. })
  162. }
  163. addTarget (plugin) {
  164. const callerPluginId = plugin.id || plugin.constructor.name
  165. const callerPluginName = plugin.title || callerPluginId
  166. const callerPluginType = plugin.type
  167. if (callerPluginType !== 'acquirer' &&
  168. callerPluginType !== 'progressindicator' &&
  169. callerPluginType !== 'presenter') {
  170. let msg = 'Dashboard: Modal can only be used by plugins of types: acquirer, progressindicator, presenter'
  171. this.uppy.log(msg)
  172. return
  173. }
  174. const target = {
  175. id: callerPluginId,
  176. name: callerPluginName,
  177. type: callerPluginType
  178. }
  179. const state = this.getPluginState()
  180. const newTargets = state.targets.slice()
  181. newTargets.push(target)
  182. this.setPluginState({
  183. targets: newTargets
  184. })
  185. return this.el
  186. }
  187. hideAllPanels () {
  188. this.setPluginState({
  189. activePanel: false,
  190. showAddFilesPanel: false
  191. })
  192. }
  193. showPanel (id) {
  194. const { targets } = this.getPluginState()
  195. const activePanel = targets.filter((target) => {
  196. return target.type === 'acquirer' && target.id === id
  197. })[0]
  198. this.setPluginState({
  199. activePanel: activePanel
  200. })
  201. }
  202. requestCloseModal () {
  203. if (this.opts.onRequestCloseModal) {
  204. return this.opts.onRequestCloseModal()
  205. } else {
  206. this.closeModal()
  207. }
  208. }
  209. getFocusableNodes () {
  210. const nodes = this.el.querySelectorAll(FOCUSABLE_ELEMENTS)
  211. console.log(Object.keys(nodes).map((key) => nodes[key]))
  212. return Object.keys(nodes).map((key) => nodes[key])
  213. }
  214. setFocusToFirstNode () {
  215. const focusableNodes = this.getFocusableNodes()
  216. if (focusableNodes.length) focusableNodes[0].focus()
  217. }
  218. updateBrowserHistory () {
  219. // Ensure history state does not already contain our modal name to avoid double-pushing
  220. if (!history.state || !history.state[this.modalName]) {
  221. // Push to history so that the page is not lost on browser back button press
  222. history.pushState({ [this.modalName]: true }, '')
  223. }
  224. // Listen for back button presses
  225. window.addEventListener('popstate', this.handlePopState, false)
  226. }
  227. handlePopState (event) {
  228. // Close the modal if the history state no longer contains our modal name
  229. if (!event.state || !event.state[this.modalName]) {
  230. this.closeModal({ manualClose: false })
  231. }
  232. // When the browser back button is pressed and uppy is now the latest entry in the history but the modal is closed, fix the history by removing the uppy history entry
  233. // This occurs when another entry is added into the history state while the modal is open, and then the modal gets manually closed
  234. // Solves PR #575 (https://github.com/transloadit/uppy/pull/575)
  235. if (!this.isModalOpen() && event.state && event.state[this.modalName]) {
  236. history.go(-1)
  237. }
  238. }
  239. setFocusToBrowse () {
  240. const browseBtn = this.el.querySelector('.uppy-Dashboard-browse')
  241. if (browseBtn) browseBtn.focus()
  242. }
  243. maintainFocus (event) {
  244. var focusableNodes = this.getFocusableNodes()
  245. var focusedItemIndex = focusableNodes.indexOf(document.activeElement)
  246. if (event.shiftKey && focusedItemIndex === 0) {
  247. focusableNodes[focusableNodes.length - 1].focus()
  248. event.preventDefault()
  249. }
  250. if (!event.shiftKey && focusedItemIndex === focusableNodes.length - 1) {
  251. focusableNodes[0].focus()
  252. event.preventDefault()
  253. }
  254. }
  255. openModal () {
  256. // save scroll position
  257. this.savedScrollPosition = window.scrollY
  258. // save active element, so we can restore focus when modal is closed
  259. this.savedActiveElement = document.activeElement
  260. if (this.opts.disablePageScrollWhenModalOpen) {
  261. document.body.classList.add('uppy-Dashboard-isFixed')
  262. }
  263. if (this.opts.animateOpenClose && this.getPluginState().isClosing) {
  264. const handler = () => {
  265. this.setPluginState({
  266. isHidden: false
  267. })
  268. this.el.removeEventListener('animationend', handler, false)
  269. }
  270. this.el.addEventListener('animationend', handler, false)
  271. } else {
  272. this.setPluginState({
  273. isHidden: false
  274. })
  275. }
  276. if (this.opts.browserBackButtonClose) {
  277. this.updateBrowserHistory()
  278. }
  279. // handle ESC and TAB keys in modal dialog
  280. document.addEventListener('keydown', this.onKeydown)
  281. // this.rerender(this.uppy.getState())
  282. this.setFocusToBrowse()
  283. }
  284. closeModal (opts = {}) {
  285. const {
  286. manualClose = true // Whether the modal is being closed by the user (`true`) or by other means (e.g. browser back button)
  287. } = opts
  288. if (this.opts.disablePageScrollWhenModalOpen) {
  289. document.body.classList.remove('uppy-Dashboard-isFixed')
  290. }
  291. if (this.opts.animateOpenClose) {
  292. this.setPluginState({
  293. isClosing: true
  294. })
  295. const handler = () => {
  296. this.setPluginState({
  297. isHidden: true,
  298. isClosing: false
  299. })
  300. this.el.removeEventListener('animationend', handler, false)
  301. }
  302. this.el.addEventListener('animationend', handler, false)
  303. } else {
  304. this.setPluginState({
  305. isHidden: true
  306. })
  307. }
  308. // handle ESC and TAB keys in modal dialog
  309. document.removeEventListener('keydown', this.onKeydown)
  310. this.savedActiveElement.focus()
  311. if (manualClose) {
  312. if (this.opts.browserBackButtonClose) {
  313. // Make sure that the latest entry in the history state is our modal name
  314. if (history.state && history.state[this.modalName]) {
  315. // Go back in history to clear out the entry we created (ultimately closing the modal)
  316. history.go(-1)
  317. }
  318. }
  319. }
  320. }
  321. isModalOpen () {
  322. return !this.getPluginState().isHidden || false
  323. }
  324. onKeydown (event) {
  325. // close modal on esc key press
  326. if (event.keyCode === ESC_KEY) this.requestCloseModal(event)
  327. // maintainFocus on tab key press
  328. if (event.keyCode === TAB_KEY) this.maintainFocus(event)
  329. }
  330. handleClickOutside () {
  331. if (this.opts.closeModalOnClickOutside) this.requestCloseModal()
  332. }
  333. handlePaste (ev) {
  334. const files = toArray(ev.clipboardData.items)
  335. files.forEach((file) => {
  336. if (file.kind !== 'file') return
  337. const blob = file.getAsFile()
  338. if (!blob) {
  339. this.uppy.log('[Dashboard] File pasted, but the file blob is empty')
  340. this.uppy.info('Error pasting file', 'error')
  341. return
  342. }
  343. this.uppy.log('[Dashboard] File pasted')
  344. try {
  345. this.uppy.addFile({
  346. source: this.id,
  347. name: file.name,
  348. type: file.type,
  349. data: blob
  350. })
  351. } catch (err) {
  352. // Nothing, restriction errors handled in Core
  353. }
  354. })
  355. }
  356. handleInputChange (ev) {
  357. ev.preventDefault()
  358. const files = toArray(ev.target.files)
  359. files.forEach((file) => {
  360. try {
  361. this.uppy.addFile({
  362. source: this.id,
  363. name: file.name,
  364. type: file.type,
  365. data: file
  366. })
  367. } catch (err) {
  368. // Nothing, restriction errors handled in Core
  369. }
  370. })
  371. }
  372. initEvents () {
  373. // Modal open button
  374. const showModalTrigger = findAllDOMElements(this.opts.trigger)
  375. if (!this.opts.inline && showModalTrigger) {
  376. showModalTrigger.forEach(trigger => trigger.addEventListener('click', this.openModal))
  377. }
  378. if (!this.opts.inline && !showModalTrigger) {
  379. this.uppy.log('Dashboard modal trigger not found. Make sure `trigger` is set in Dashboard options unless you are planning to call openModal() method yourself')
  380. }
  381. // Drag Drop
  382. this.removeDragDropListener = dragDrop(this.el, (files) => {
  383. this.handleDrop(files)
  384. })
  385. // Watch for Dashboard container (`.uppy-Dashboard-inner`) resize
  386. // and update containerWidth/containerHeight in plugin state accordingly
  387. this.ro = new ResizeObserver((entries, observer) => {
  388. for (const entry of entries) {
  389. const { width, height } = entry.contentRect
  390. this.uppy.log(`[Dashboard] resized: ${width} / ${height}`)
  391. this.setPluginState({
  392. containerWidth: width,
  393. containerHeight: height
  394. })
  395. }
  396. })
  397. this.ro.observe(this.el.querySelector('.uppy-Dashboard-inner'))
  398. this.uppy.on('plugin-remove', this.removeTarget)
  399. this.uppy.on('file-added', (ev) => this.toggleAddFilesPanel(false))
  400. }
  401. removeEvents () {
  402. const showModalTrigger = findAllDOMElements(this.opts.trigger)
  403. if (!this.opts.inline && showModalTrigger) {
  404. showModalTrigger.forEach(trigger => trigger.removeEventListener('click', this.openModal))
  405. }
  406. this.ro.unobserve(this.el.querySelector('.uppy-Dashboard-inner'))
  407. this.removeDragDropListener()
  408. // window.removeEventListener('resize', this.throttledUpdateDashboardElWidth)
  409. window.removeEventListener('popstate', this.handlePopState, false)
  410. this.uppy.off('plugin-remove', this.removeTarget)
  411. this.uppy.off('file-added', (ev) => this.toggleAddFilesPanel(false))
  412. }
  413. toggleFileCard (fileId) {
  414. this.setPluginState({
  415. fileCardFor: fileId || false
  416. })
  417. }
  418. toggleAddFilesPanel (show) {
  419. this.setPluginState({
  420. showAddFilesPanel: show
  421. })
  422. }
  423. handleDrop (files) {
  424. this.uppy.log('[Dashboard] Files were dropped')
  425. files.forEach((file) => {
  426. try {
  427. this.uppy.addFile({
  428. source: this.id,
  429. name: file.name,
  430. type: file.type,
  431. data: file
  432. })
  433. } catch (err) {
  434. // Nothing, restriction errors handled in Core
  435. }
  436. })
  437. }
  438. render (state) {
  439. const pluginState = this.getPluginState()
  440. const { files, capabilities } = state
  441. const newFiles = Object.keys(files).filter((file) => {
  442. return !files[file].progress.uploadStarted
  443. })
  444. const inProgressFiles = Object.keys(files).filter((file) => {
  445. return !files[file].progress.uploadComplete &&
  446. files[file].progress.uploadStarted &&
  447. !files[file].isPaused
  448. })
  449. let inProgressFilesArray = []
  450. inProgressFiles.forEach((file) => {
  451. inProgressFilesArray.push(files[file])
  452. })
  453. let totalSize = 0
  454. let totalUploadedSize = 0
  455. inProgressFilesArray.forEach((file) => {
  456. totalSize = totalSize + (file.progress.bytesTotal || 0)
  457. totalUploadedSize = totalUploadedSize + (file.progress.bytesUploaded || 0)
  458. })
  459. totalSize = prettyBytes(totalSize)
  460. totalUploadedSize = prettyBytes(totalUploadedSize)
  461. const attachRenderFunctionToTarget = (target) => {
  462. const plugin = this.uppy.getPlugin(target.id)
  463. return Object.assign({}, target, {
  464. icon: plugin.icon || this.opts.defaultTabIcon,
  465. render: plugin.render
  466. })
  467. }
  468. const isSupported = (target) => {
  469. const plugin = this.uppy.getPlugin(target.id)
  470. // If the plugin does not provide a `supported` check, assume the plugin works everywhere.
  471. if (typeof plugin.isSupported !== 'function') {
  472. return true
  473. }
  474. return plugin.isSupported()
  475. }
  476. const acquirers = pluginState.targets
  477. .filter(target => target.type === 'acquirer' && isSupported(target))
  478. .map(attachRenderFunctionToTarget)
  479. const progressindicators = pluginState.targets
  480. .filter(target => target.type === 'progressindicator')
  481. .map(attachRenderFunctionToTarget)
  482. const startUpload = (ev) => {
  483. this.uppy.upload().catch((err) => {
  484. // Log error.
  485. this.uppy.log(err.stack || err.message || err)
  486. })
  487. }
  488. const cancelUpload = (fileID) => {
  489. this.uppy.emit('upload-cancel', fileID)
  490. this.uppy.removeFile(fileID)
  491. }
  492. const saveFileCard = (meta, fileID) => {
  493. this.uppy.setFileMeta(fileID, meta)
  494. this.toggleFileCard()
  495. }
  496. return DashboardUI({
  497. state: state,
  498. modal: pluginState,
  499. newFiles: newFiles,
  500. files: files,
  501. totalFileCount: Object.keys(files).length,
  502. totalProgress: state.totalProgress,
  503. acquirers: acquirers,
  504. activePanel: pluginState.activePanel,
  505. animateOpenClose: this.opts.animateOpenClose,
  506. isClosing: pluginState.isClosing,
  507. getPlugin: this.uppy.getPlugin,
  508. progressindicators: progressindicators,
  509. autoProceed: this.uppy.opts.autoProceed,
  510. hideUploadButton: this.opts.hideUploadButton,
  511. hideRetryButton: this.opts.hideRetryButton,
  512. hidePauseResumeCancelButtons: this.opts.hidePauseResumeCancelButtons,
  513. id: this.id,
  514. closeModal: this.requestCloseModal,
  515. handleClickOutside: this.handleClickOutside,
  516. handleInputChange: this.handleInputChange,
  517. handlePaste: this.handlePaste,
  518. inline: this.opts.inline,
  519. showPanel: this.showPanel,
  520. hideAllPanels: this.hideAllPanels,
  521. log: this.uppy.log,
  522. i18n: this.i18n,
  523. i18nArray: this.i18nArray,
  524. addFile: this.uppy.addFile,
  525. removeFile: this.uppy.removeFile,
  526. info: this.uppy.info,
  527. note: this.opts.note,
  528. metaFields: pluginState.metaFields,
  529. resumableUploads: capabilities.resumableUploads || false,
  530. bundled: capabilities.bundled || false,
  531. startUpload: startUpload,
  532. pauseUpload: this.uppy.pauseResume,
  533. retryUpload: this.uppy.retryUpload,
  534. cancelUpload: cancelUpload,
  535. cancelAll: this.uppy.cancelAll,
  536. fileCardFor: pluginState.fileCardFor,
  537. toggleFileCard: this.toggleFileCard,
  538. toggleAddFilesPanel: this.toggleAddFilesPanel,
  539. showAddFilesPanel: pluginState.showAddFilesPanel,
  540. saveFileCard: saveFileCard,
  541. width: this.opts.width,
  542. height: this.opts.height,
  543. showLinkToFileUploadResult: this.opts.showLinkToFileUploadResult,
  544. proudlyDisplayPoweredByUppy: this.opts.proudlyDisplayPoweredByUppy,
  545. currentWidth: pluginState.containerWidth,
  546. isWide: pluginState.containerWidth > 400,
  547. containerWidth: pluginState.containerWidth,
  548. isTargetDOMEl: this.isTargetDOMEl,
  549. allowedFileTypes: this.uppy.opts.restrictions.allowedFileTypes,
  550. maxNumberOfFiles: this.uppy.opts.restrictions.maxNumberOfFiles
  551. })
  552. }
  553. discoverProviderPlugins () {
  554. this.uppy.iteratePlugins((plugin) => {
  555. if (plugin && !plugin.target && plugin.opts && plugin.opts.target === this.constructor) {
  556. this.addTarget(plugin)
  557. }
  558. })
  559. }
  560. install () {
  561. // Set default state for Dashboard
  562. this.setPluginState({
  563. isHidden: true,
  564. showFileCard: false,
  565. showAddFilesPanel: false,
  566. activePanel: false,
  567. metaFields: this.opts.metaFields,
  568. targets: []
  569. })
  570. const target = this.opts.target
  571. if (target) {
  572. this.mount(target, this)
  573. }
  574. const plugins = this.opts.plugins || []
  575. plugins.forEach((pluginID) => {
  576. const plugin = this.uppy.getPlugin(pluginID)
  577. if (plugin) plugin.mount(this, plugin)
  578. })
  579. if (!this.opts.disableStatusBar) {
  580. this.uppy.use(StatusBar, {
  581. id: `${this.id}:StatusBar`,
  582. target: this,
  583. hideUploadButton: this.opts.hideUploadButton,
  584. hideRetryButton: this.opts.hideRetryButton,
  585. hidePauseResumeCancelButtons: this.opts.hidePauseResumeCancelButtons,
  586. showProgressDetails: this.opts.showProgressDetails,
  587. hideAfterFinish: this.opts.hideProgressAfterFinish,
  588. locale: this.opts.locale
  589. })
  590. }
  591. if (!this.opts.disableInformer) {
  592. this.uppy.use(Informer, {
  593. id: `${this.id}:Informer`,
  594. target: this
  595. })
  596. }
  597. if (!this.opts.disableThumbnailGenerator) {
  598. this.uppy.use(ThumbnailGenerator, {
  599. id: `${this.id}:ThumbnailGenerator`,
  600. thumbnailWidth: this.opts.thumbnailWidth
  601. })
  602. }
  603. this.discoverProviderPlugins()
  604. this.initEvents()
  605. }
  606. uninstall () {
  607. if (!this.opts.disableInformer) {
  608. const informer = this.uppy.getPlugin(`${this.id}:Informer`)
  609. // Checking if this plugin exists, in case it was removed by uppy-core
  610. // before the Dashboard was.
  611. if (informer) this.uppy.removePlugin(informer)
  612. }
  613. if (!this.opts.disableStatusBar) {
  614. const statusBar = this.uppy.getPlugin(`${this.id}:StatusBar`)
  615. if (statusBar) this.uppy.removePlugin(statusBar)
  616. }
  617. if (!this.opts.disableThumbnailGenerator) {
  618. const thumbnail = this.uppy.getPlugin(`${this.id}:ThumbnailGenerator`)
  619. if (thumbnail) this.uppy.removePlugin(thumbnail)
  620. }
  621. const plugins = this.opts.plugins || []
  622. plugins.forEach((pluginID) => {
  623. const plugin = this.uppy.getPlugin(pluginID)
  624. if (plugin) plugin.unmount()
  625. })
  626. this.unmount()
  627. this.removeEvents()
  628. }
  629. }