index.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  1. const Plugin = require('../Plugin')
  2. const Translator = require('../../core/Translator')
  3. const dragDrop = require('drag-drop')
  4. const Dashboard = require('./Dashboard')
  5. const { findDOMElement } = require('../../core/Utils')
  6. const prettyBytes = require('prettier-bytes')
  7. const { defaultTabIcon } = require('./icons')
  8. /**
  9. * Modal Dialog & Dashboard
  10. */
  11. module.exports = class DashboardUI extends Plugin {
  12. constructor (core, opts) {
  13. super(core, opts)
  14. this.id = 'DashboardUI'
  15. this.title = 'Dashboard UI'
  16. this.type = 'orchestrator'
  17. const defaultLocale = {
  18. strings: {
  19. selectToUpload: 'Select files to upload',
  20. closeModal: 'Close Modal',
  21. upload: 'Upload',
  22. importFrom: 'Import files from',
  23. dashboardWindowTitle: 'Uppy Dashboard Window (Press escape to close)',
  24. dashboardTitle: 'Uppy Dashboard',
  25. copyLinkToClipboardSuccess: 'Link copied to clipboard.',
  26. copyLinkToClipboardFallback: 'Copy the URL below',
  27. done: 'Done',
  28. localDisk: 'Local Disk',
  29. dropPasteImport: 'Drop files here, paste, import from one of the locations above or',
  30. dropPaste: 'Drop files here, paste or',
  31. browse: 'browse',
  32. fileProgress: 'File progress: upload speed and ETA',
  33. numberOfSelectedFiles: 'Number of selected files',
  34. uploadAllNewFiles: 'Upload all new files'
  35. }
  36. }
  37. // set default options
  38. const defaultOptions = {
  39. target: 'body',
  40. inline: false,
  41. width: 750,
  42. height: 550,
  43. semiTransparent: false,
  44. defaultTabIcon: defaultTabIcon(),
  45. showProgressDetails: false,
  46. locale: defaultLocale
  47. }
  48. // merge default options with the ones set by user
  49. this.opts = Object.assign({}, defaultOptions, opts)
  50. this.locale = Object.assign({}, defaultLocale, this.opts.locale)
  51. this.locale.strings = Object.assign({}, defaultLocale.strings, this.opts.locale.strings)
  52. this.translator = new Translator({locale: this.locale})
  53. this.containerWidth = this.translator.translate.bind(this.translator)
  54. this.hideModal = this.hideModal.bind(this)
  55. this.showModal = this.showModal.bind(this)
  56. this.addTarget = this.addTarget.bind(this)
  57. this.actions = this.actions.bind(this)
  58. this.hideAllPanels = this.hideAllPanels.bind(this)
  59. this.showPanel = this.showPanel.bind(this)
  60. this.initEvents = this.initEvents.bind(this)
  61. this.handleEscapeKeyPress = this.handleEscapeKeyPress.bind(this)
  62. this.handleFileCard = this.handleFileCard.bind(this)
  63. this.handleDrop = this.handleDrop.bind(this)
  64. this.pauseAll = this.pauseAll.bind(this)
  65. this.resumeAll = this.resumeAll.bind(this)
  66. this.cancelAll = this.cancelAll.bind(this)
  67. this.updateDashboardElWidth = this.updateDashboardElWidth.bind(this)
  68. this.render = this.render.bind(this)
  69. this.install = this.install.bind(this)
  70. }
  71. addTarget (plugin) {
  72. const callerPluginId = plugin.id || plugin.constructor.name
  73. const callerPluginName = plugin.title || callerPluginId
  74. const callerPluginIcon = plugin.icon || this.opts.defaultTabIcon
  75. const callerPluginType = plugin.type
  76. if (callerPluginType !== 'acquirer' &&
  77. callerPluginType !== 'progressindicator' &&
  78. callerPluginType !== 'presenter') {
  79. let msg = 'Error: Modal can only be used by plugins of types: acquirer, progressindicator, presenter'
  80. this.core.log(msg)
  81. return
  82. }
  83. const target = {
  84. id: callerPluginId,
  85. name: callerPluginName,
  86. icon: callerPluginIcon,
  87. type: callerPluginType,
  88. focus: plugin.focus,
  89. render: plugin.render,
  90. isHidden: true
  91. }
  92. const modal = this.core.getState().modal
  93. const newTargets = modal.targets.slice()
  94. newTargets.push(target)
  95. this.core.setState({
  96. modal: Object.assign({}, modal, {
  97. targets: newTargets
  98. })
  99. })
  100. return this.target
  101. }
  102. hideAllPanels () {
  103. const modal = this.core.getState().modal
  104. this.core.setState({modal: Object.assign({}, modal, {
  105. activePanel: false
  106. })})
  107. }
  108. showPanel (id) {
  109. const modal = this.core.getState().modal
  110. const activePanel = modal.targets.filter((target) => {
  111. return target.type === 'acquirer' && target.id === id
  112. })[0]
  113. this.core.setState({modal: Object.assign({}, modal, {
  114. activePanel: activePanel
  115. })})
  116. }
  117. hideModal () {
  118. const modal = this.core.getState().modal
  119. this.core.setState({
  120. modal: Object.assign({}, modal, {
  121. isHidden: true
  122. })
  123. })
  124. document.body.classList.remove('is-UppyDashboard-open')
  125. }
  126. showModal () {
  127. const modal = this.core.getState().modal
  128. this.core.setState({
  129. modal: Object.assign({}, modal, {
  130. isHidden: false
  131. })
  132. })
  133. // add class to body that sets position fixed
  134. document.body.classList.add('is-UppyDashboard-open')
  135. // focus on modal inner block
  136. this.target.querySelector('.UppyDashboard-inner').focus()
  137. this.updateDashboardElWidth()
  138. // to be sure, sometimes when the function runs, container size is still 0
  139. setTimeout(this.updateDashboardElWidth, 300)
  140. }
  141. // Close the Modal on esc key press
  142. handleEscapeKeyPress (event) {
  143. if (event.keyCode === 27) {
  144. this.hideModal()
  145. }
  146. }
  147. initEvents () {
  148. // const dashboardEl = this.target.querySelector(`${this.opts.target} .UppyDashboard`)
  149. // Modal open button
  150. const showModalTrigger = findDOMElement(this.opts.trigger)
  151. if (!this.opts.inline && showModalTrigger) {
  152. showModalTrigger.addEventListener('click', this.showModal)
  153. } else {
  154. this.core.log('Modal trigger wasn’t found')
  155. }
  156. document.body.addEventListener('keyup', this.handleEscapeKeyPress)
  157. // Drag Drop
  158. this.removeDragDropListener = dragDrop(this.el, (files) => {
  159. this.handleDrop(files)
  160. })
  161. }
  162. removeEvents () {
  163. const showModalTrigger = findDOMElement(this.opts.trigger)
  164. if (!this.opts.inline && showModalTrigger) {
  165. showModalTrigger.removeEventListener('click', this.showModal)
  166. }
  167. this.removeDragDropListener()
  168. document.body.removeEventListener('keyup', this.handleEscapeKeyPress)
  169. }
  170. actions () {
  171. const bus = this.core.bus
  172. bus.on('core:file-add', this.hideAllPanels)
  173. bus.on('dashboard:file-card', this.handleFileCard)
  174. window.addEventListener('resize', this.updateDashboardElWidth)
  175. // bus.on('core:success', (uploadedCount) => {
  176. // bus.emit(
  177. // 'informer',
  178. // `${this.core.i18n('files', {'smart_count': uploadedCount})} successfully uploaded, Sir!`,
  179. // 'info',
  180. // 6000
  181. // )
  182. // })
  183. }
  184. removeActions () {
  185. const bus = this.core.bus
  186. window.removeEventListener('resize', this.updateDashboardElWidth)
  187. bus.off('core:file-add', this.hideAllPanels)
  188. bus.off('dashboard:file-card', this.handleFileCard)
  189. }
  190. updateDashboardElWidth () {
  191. const dashboardEl = this.target.querySelector('.UppyDashboard-inner')
  192. // const containerWidth = dashboardEl.offsetWidth
  193. // console.log(containerWidth)
  194. const modal = this.core.getState().modal
  195. this.core.setState({
  196. modal: Object.assign({}, modal, {
  197. containerWidth: dashboardEl.offsetWidth
  198. })
  199. })
  200. }
  201. handleFileCard (fileId) {
  202. const modal = this.core.getState().modal
  203. this.core.setState({
  204. modal: Object.assign({}, modal, {
  205. fileCardFor: fileId || false
  206. })
  207. })
  208. }
  209. handleDrop (files) {
  210. this.core.log('All right, someone dropped something...')
  211. files.forEach((file) => {
  212. this.core.bus.emit('core:file-add', {
  213. source: this.id,
  214. name: file.name,
  215. type: file.type,
  216. data: file
  217. })
  218. })
  219. }
  220. cancelAll () {
  221. this.core.bus.emit('core:cancel-all')
  222. }
  223. pauseAll () {
  224. this.core.bus.emit('core:pause-all')
  225. }
  226. resumeAll () {
  227. this.core.bus.emit('core:resume-all')
  228. }
  229. render (state) {
  230. const files = state.files
  231. const newFiles = Object.keys(files).filter((file) => {
  232. return !files[file].progress.uploadStarted
  233. })
  234. const inProgressFiles = Object.keys(files).filter((file) => {
  235. return !files[file].progress.uploadComplete &&
  236. files[file].progress.uploadStarted &&
  237. !files[file].isPaused
  238. })
  239. let inProgressFilesArray = []
  240. inProgressFiles.forEach((file) => {
  241. inProgressFilesArray.push(files[file])
  242. })
  243. // total size and uploaded size
  244. let totalSize = 0
  245. let totalUploadedSize = 0
  246. inProgressFilesArray.forEach((file) => {
  247. totalSize = totalSize + (file.progress.bytesTotal || 0)
  248. totalUploadedSize = totalUploadedSize + (file.progress.bytesUploaded || 0)
  249. })
  250. totalSize = prettyBytes(totalSize)
  251. totalUploadedSize = prettyBytes(totalUploadedSize)
  252. const acquirers = state.modal.targets.filter((target) => {
  253. return target.type === 'acquirer'
  254. })
  255. const progressindicators = state.modal.targets.filter((target) => {
  256. return target.type === 'progressindicator'
  257. })
  258. const addFile = (file) => {
  259. this.core.emitter.emit('core:file-add', file)
  260. }
  261. const removeFile = (fileID) => {
  262. this.core.emitter.emit('core:file-remove', fileID)
  263. }
  264. const startUpload = (ev) => {
  265. this.core.upload().catch((err) => {
  266. // Log error.
  267. console.error(err.stack || err.message)
  268. })
  269. }
  270. const pauseUpload = (fileID) => {
  271. this.core.emitter.emit('core:upload-pause', fileID)
  272. }
  273. const cancelUpload = (fileID) => {
  274. this.core.emitter.emit('core:upload-cancel', fileID)
  275. this.core.emitter.emit('core:file-remove', fileID)
  276. }
  277. const showFileCard = (fileID) => {
  278. this.core.emitter.emit('dashboard:file-card', fileID)
  279. }
  280. const fileCardDone = (meta, fileID) => {
  281. this.core.emitter.emit('core:update-meta', meta, fileID)
  282. this.core.emitter.emit('dashboard:file-card')
  283. }
  284. const info = (text, type, duration) => {
  285. this.core.emitter.emit('informer', text, type, duration)
  286. }
  287. const resumableUploads = this.core.getState().capabilities.resumableUploads || false
  288. return Dashboard({
  289. state: state,
  290. modal: state.modal,
  291. newFiles: newFiles,
  292. files: files,
  293. totalFileCount: Object.keys(files).length,
  294. totalProgress: state.totalProgress,
  295. acquirers: acquirers,
  296. activePanel: state.modal.activePanel,
  297. progressindicators: progressindicators,
  298. autoProceed: this.core.opts.autoProceed,
  299. id: this.id,
  300. hideModal: this.hideModal,
  301. showProgressDetails: this.opts.showProgressDetails,
  302. inline: this.opts.inline,
  303. semiTransparent: this.opts.semiTransparent,
  304. showPanel: this.showPanel,
  305. hideAllPanels: this.hideAllPanels,
  306. log: this.core.log,
  307. i18n: this.containerWidth,
  308. pauseAll: this.pauseAll,
  309. resumeAll: this.resumeAll,
  310. addFile: addFile,
  311. removeFile: removeFile,
  312. info: info,
  313. metaFields: state.metaFields,
  314. resumableUploads: resumableUploads,
  315. startUpload: startUpload,
  316. pauseUpload: pauseUpload,
  317. cancelUpload: cancelUpload,
  318. fileCardFor: state.modal.fileCardFor,
  319. showFileCard: showFileCard,
  320. fileCardDone: fileCardDone,
  321. updateDashboardElWidth: this.updateDashboardElWidth,
  322. maxWidth: this.opts.maxWidth,
  323. maxHeight: this.opts.maxHeight,
  324. currentWidth: state.modal.containerWidth,
  325. isWide: state.modal.containerWidth > 400
  326. })
  327. }
  328. install () {
  329. // Set default state for Modal
  330. this.core.setState({modal: {
  331. isHidden: true,
  332. showFileCard: false,
  333. activePanel: false,
  334. targets: []
  335. }})
  336. const target = this.opts.target
  337. const plugin = this
  338. this.target = this.mount(target, plugin)
  339. this.initEvents()
  340. this.actions()
  341. }
  342. uninstall () {
  343. this.unmount()
  344. this.removeActions()
  345. this.removeEvents()
  346. }
  347. }