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 { getSpeed } = require('../../core/Utils')
  6. const { getETA } = require('../../core/Utils')
  7. const { prettyETA } = require('../../core/Utils')
  8. const prettyBytes = require('pretty-bytes')
  9. const { defaultTabIcon } = require('./icons')
  10. /**
  11. * Modal Dialog & Dashboard
  12. */
  13. module.exports = class DashboardUI extends Plugin {
  14. constructor (core, opts) {
  15. super(core, opts)
  16. this.id = 'DashboardUI'
  17. this.title = 'Dashboard UI'
  18. this.type = 'orchestrator'
  19. const defaultLocale = {
  20. strings: {
  21. selectToUpload: 'Select files to upload',
  22. closeModal: 'Close Modal',
  23. upload: 'Upload',
  24. importFrom: 'Import files from',
  25. dashboardWindowTitle: 'Uppy Dashboard Window (Press escape to close)',
  26. dashboardTitle: 'Uppy Dashboard',
  27. copyLinkToClipboardSuccess: 'Link copied to clipboard.',
  28. copyLinkToClipboardFallback: 'Copy the URL below',
  29. done: 'Done',
  30. localDisk: 'Local Disk',
  31. dropPasteImport: 'Drop files here, paste, import from one of the locations above or',
  32. dropPaste: 'Drop files here, paste or',
  33. browse: 'browse',
  34. fileProgress: 'File progress: upload speed and ETA',
  35. numberOfSelectedFiles: 'Number of selected files',
  36. uploadAllNewFiles: 'Upload all new files'
  37. }
  38. }
  39. // set default options
  40. const defaultOptions = {
  41. target: 'body',
  42. inline: false,
  43. width: 750,
  44. height: 550,
  45. semiTransparent: false,
  46. defaultTabIcon: defaultTabIcon(),
  47. showProgressDetails: true,
  48. locale: defaultLocale
  49. }
  50. // merge default options with the ones set by user
  51. this.opts = Object.assign({}, defaultOptions, opts)
  52. this.locale = Object.assign({}, defaultLocale, this.opts.locale)
  53. this.locale.strings = Object.assign({}, defaultLocale.strings, this.opts.locale.strings)
  54. this.translator = new Translator({locale: this.locale})
  55. this.containerWidth = this.translator.translate.bind(this.translator)
  56. this.hideModal = this.hideModal.bind(this)
  57. this.showModal = this.showModal.bind(this)
  58. this.addTarget = this.addTarget.bind(this)
  59. this.actions = this.actions.bind(this)
  60. this.hideAllPanels = this.hideAllPanels.bind(this)
  61. this.showPanel = this.showPanel.bind(this)
  62. this.initEvents = this.initEvents.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.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.opts.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. document.querySelector('.UppyDashboard-inner').focus()
  137. this.updateDashboardElWidth()
  138. }
  139. initEvents () {
  140. // const dashboardEl = document.querySelector(`${this.opts.target} .UppyDashboard`)
  141. // Modal open button
  142. const showModalTrigger = document.querySelector(this.opts.trigger)
  143. if (!this.opts.inline && showModalTrigger) {
  144. showModalTrigger.addEventListener('click', this.showModal)
  145. } else {
  146. this.core.log('Modal trigger wasn’t found')
  147. }
  148. // Close the Modal on esc key press
  149. document.body.addEventListener('keyup', (event) => {
  150. if (event.keyCode === 27) {
  151. this.hideModal()
  152. }
  153. })
  154. // Drag Drop
  155. dragDrop(this.el, (files) => {
  156. this.handleDrop(files)
  157. })
  158. }
  159. actions () {
  160. const bus = this.core.bus
  161. bus.on('core:file-add', () => {
  162. this.hideAllPanels()
  163. })
  164. bus.on('dashboard:file-card', (fileId) => {
  165. const modal = this.core.getState().modal
  166. this.core.setState({
  167. modal: Object.assign({}, modal, {
  168. fileCardFor: fileId || false
  169. })
  170. })
  171. })
  172. window.addEventListener('resize', (ev) => this.updateDashboardElWidth())
  173. // bus.on('core:success', (uploadedCount) => {
  174. // bus.emit(
  175. // 'informer',
  176. // `${this.core.i18n('files', {'smart_count': uploadedCount})} successfully uploaded, Sir!`,
  177. // 'info',
  178. // 6000
  179. // )
  180. // })
  181. }
  182. updateDashboardElWidth () {
  183. const dashboardEl = document.querySelector('.UppyDashboard-inner')
  184. const modal = this.core.getState().modal
  185. this.core.setState({
  186. modal: Object.assign({}, modal, {
  187. containerWidth: dashboardEl.offsetWidth
  188. })
  189. })
  190. }
  191. handleDrop (files) {
  192. this.core.log('All right, someone dropped something...')
  193. files.forEach((file) => {
  194. this.core.bus.emit('core:file-add', {
  195. source: this.id,
  196. name: file.name,
  197. type: file.type,
  198. data: file
  199. })
  200. })
  201. }
  202. cancelAll () {
  203. this.core.bus.emit('core:cancel-all')
  204. }
  205. pauseAll () {
  206. this.core.bus.emit('core:pause-all')
  207. }
  208. resumeAll () {
  209. this.core.bus.emit('core:resume-all')
  210. }
  211. getTotalSpeed (files) {
  212. let totalSpeed = 0
  213. files.forEach((file) => {
  214. totalSpeed = totalSpeed + getSpeed(file.progress)
  215. })
  216. return totalSpeed
  217. }
  218. getTotalETA (files) {
  219. let totalSeconds = 0
  220. files.forEach((file) => {
  221. totalSeconds = totalSeconds + getETA(file.progress)
  222. })
  223. return totalSeconds
  224. }
  225. render (state) {
  226. const files = state.files
  227. const newFiles = Object.keys(files).filter((file) => {
  228. return !files[file].progress.uploadStarted
  229. })
  230. const uploadStartedFiles = Object.keys(files).filter((file) => {
  231. return files[file].progress.uploadStarted
  232. })
  233. const completeFiles = Object.keys(files).filter((file) => {
  234. return files[file].progress.uploadComplete
  235. })
  236. const inProgressFiles = Object.keys(files).filter((file) => {
  237. return !files[file].progress.uploadComplete &&
  238. files[file].progress.uploadStarted &&
  239. !files[file].isPaused
  240. })
  241. let inProgressFilesArray = []
  242. inProgressFiles.forEach((file) => {
  243. inProgressFilesArray.push(files[file])
  244. })
  245. const totalSpeed = prettyBytes(this.getTotalSpeed(inProgressFilesArray))
  246. const totalETA = prettyETA(this.getTotalETA(inProgressFilesArray))
  247. const isAllComplete = state.totalProgress === 100
  248. const isAllPaused = inProgressFiles.length === 0 && !isAllComplete && uploadStartedFiles.length > 0
  249. const isUploadStarted = uploadStartedFiles.length > 0
  250. const acquirers = state.modal.targets.filter((target) => {
  251. return target.type === 'acquirer'
  252. })
  253. const progressindicators = state.modal.targets.filter((target) => {
  254. return target.type === 'progressindicator'
  255. })
  256. const addFile = (file) => {
  257. this.core.emitter.emit('core:file-add', file)
  258. }
  259. const removeFile = (fileID) => {
  260. this.core.emitter.emit('core:file-remove', fileID)
  261. }
  262. const startUpload = (ev) => {
  263. this.core.emitter.emit('core:upload')
  264. }
  265. const pauseUpload = (fileID) => {
  266. this.core.emitter.emit('core:upload-pause', fileID)
  267. }
  268. const cancelUpload = (fileID) => {
  269. this.core.emitter.emit('core:upload-cancel', fileID)
  270. this.core.emitter.emit('core:file-remove', fileID)
  271. }
  272. const showFileCard = (fileID) => {
  273. this.core.emitter.emit('dashboard:file-card', fileID)
  274. }
  275. const fileCardDone = (meta, fileID) => {
  276. this.core.emitter.emit('core:update-meta', meta, fileID)
  277. this.core.emitter.emit('dashboard:file-card')
  278. }
  279. const info = (text, type, duration) => {
  280. this.core.emitter.emit('informer', text, type, duration)
  281. }
  282. const resumableUploads = this.core.getState().capabilities.resumableUploads || false
  283. return Dashboard({
  284. state: state,
  285. modal: state.modal,
  286. newFiles: newFiles,
  287. files: files,
  288. totalFileCount: Object.keys(files).length,
  289. isUploadStarted: isUploadStarted,
  290. inProgress: uploadStartedFiles.length,
  291. completeFiles: completeFiles,
  292. inProgressFiles: inProgressFiles,
  293. totalSpeed: totalSpeed,
  294. totalETA: totalETA,
  295. totalProgress: state.totalProgress,
  296. isAllComplete: isAllComplete,
  297. isAllPaused: isAllPaused,
  298. acquirers: acquirers,
  299. activePanel: state.modal.activePanel,
  300. progressindicators: progressindicators,
  301. autoProceed: this.core.opts.autoProceed,
  302. id: this.id,
  303. container: this.opts.target,
  304. hideModal: this.hideModal,
  305. showProgressDetails: this.opts.showProgressDetails,
  306. inline: this.opts.inline,
  307. semiTransparent: this.opts.semiTransparent,
  308. onPaste: this.handlePaste,
  309. showPanel: this.showPanel,
  310. hideAllPanels: this.hideAllPanels,
  311. log: this.core.log,
  312. bus: this.core.emitter,
  313. i18n: this.containerWidth,
  314. pauseAll: this.pauseAll,
  315. resumeAll: this.resumeAll,
  316. cancelAll: this.cancelAll,
  317. addFile: addFile,
  318. removeFile: removeFile,
  319. info: info,
  320. metaFields: state.metaFields,
  321. resumableUploads: resumableUploads,
  322. startUpload: startUpload,
  323. pauseUpload: pauseUpload,
  324. cancelUpload: cancelUpload,
  325. fileCardFor: state.modal.fileCardFor,
  326. showFileCard: showFileCard,
  327. fileCardDone: fileCardDone,
  328. updateDashboardElWidth: this.updateDashboardElWidth,
  329. maxWidth: this.opts.maxWidth,
  330. maxHeight: this.opts.maxHeight,
  331. currentWidth: state.modal.containerWidth,
  332. isWide: state.modal.containerWidth > 400
  333. })
  334. }
  335. install () {
  336. // Set default state for Modal
  337. this.core.setState({modal: {
  338. isHidden: true,
  339. showFileCard: false,
  340. activePanel: false,
  341. targets: []
  342. }})
  343. const target = this.opts.target
  344. const plugin = this
  345. this.target = this.mount(target, plugin)
  346. this.initEvents()
  347. this.actions()
  348. }
  349. }