index.js 21 KB

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