index.js 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. const { Plugin } = require('@uppy/core')
  2. const Translator = require('@uppy/utils/lib/Translator')
  3. const StatusBarUI = require('./StatusBar')
  4. const statusBarStates = require('./StatusBarStates')
  5. const getSpeed = require('@uppy/utils/lib/getSpeed')
  6. const getBytesRemaining = require('@uppy/utils/lib/getBytesRemaining')
  7. const getTextDirection = require('@uppy/utils/lib/getTextDirection')
  8. /**
  9. * StatusBar: renders a status bar with upload/pause/resume/cancel/retry buttons,
  10. * progress percentage and time remaining.
  11. */
  12. module.exports = class StatusBar extends Plugin {
  13. static VERSION = require('../package.json').version
  14. constructor (uppy, opts) {
  15. super(uppy, opts)
  16. this.id = this.opts.id || 'StatusBar'
  17. this.title = 'StatusBar'
  18. this.type = 'progressindicator'
  19. this.defaultLocale = {
  20. strings: {
  21. uploading: 'Uploading',
  22. upload: 'Upload',
  23. complete: 'Complete',
  24. uploadFailed: 'Upload failed',
  25. paused: 'Paused',
  26. retry: 'Retry',
  27. retryUpload: 'Retry upload',
  28. cancel: 'Cancel',
  29. pause: 'Pause',
  30. resume: 'Resume',
  31. done: 'Done',
  32. filesUploadedOfTotal: {
  33. 0: '%{complete} of %{smart_count} file uploaded',
  34. 1: '%{complete} of %{smart_count} files uploaded',
  35. },
  36. dataUploadedOfTotal: '%{complete} of %{total}',
  37. xTimeLeft: '%{time} left',
  38. uploadXFiles: {
  39. 0: 'Upload %{smart_count} file',
  40. 1: 'Upload %{smart_count} files',
  41. },
  42. uploadXNewFiles: {
  43. 0: 'Upload +%{smart_count} file',
  44. 1: 'Upload +%{smart_count} files',
  45. },
  46. xMoreFilesAdded: {
  47. 0: '%{smart_count} more file added',
  48. 1: '%{smart_count} more files added',
  49. },
  50. reSelectGhosts: 'Please re-select (or remove) files marked with ghosts',
  51. },
  52. }
  53. // set default options
  54. const defaultOptions = {
  55. target: 'body',
  56. hideUploadButton: false,
  57. hideRetryButton: false,
  58. hidePauseResumeButton: false,
  59. hideCancelButton: false,
  60. showProgressDetails: false,
  61. hideAfterFinish: true,
  62. doneButtonHandler: null,
  63. }
  64. this.opts = { ...defaultOptions, ...opts }
  65. this.i18nInit()
  66. this.render = this.render.bind(this)
  67. this.install = this.install.bind(this)
  68. }
  69. setOptions (newOpts) {
  70. super.setOptions(newOpts)
  71. this.i18nInit()
  72. }
  73. i18nInit () {
  74. this.translator = new Translator([this.defaultLocale, this.uppy.locale, this.opts.locale])
  75. this.i18n = this.translator.translate.bind(this.translator)
  76. this.setPluginState() // so that UI re-renders and we see the updated locale
  77. }
  78. getTotalSpeed (files) {
  79. let totalSpeed = 0
  80. files.forEach((file) => {
  81. totalSpeed += getSpeed(file.progress)
  82. })
  83. return totalSpeed
  84. }
  85. getTotalETA (files) {
  86. const totalSpeed = this.getTotalSpeed(files)
  87. if (totalSpeed === 0) {
  88. return 0
  89. }
  90. const totalBytesRemaining = files.reduce((total, file) => {
  91. return total + getBytesRemaining(file.progress)
  92. }, 0)
  93. return Math.round(totalBytesRemaining / totalSpeed * 10) / 10
  94. }
  95. startUpload = () => {
  96. const { recoveredState } = this.uppy.getState()
  97. if (recoveredState) {
  98. this.uppy.emit('restore-confirmed')
  99. return
  100. }
  101. return this.uppy.upload().catch(() => {
  102. // Error logged in Core
  103. })
  104. }
  105. getUploadingState (isAllErrored, isAllComplete, recoveredState, files) {
  106. if (isAllErrored) {
  107. return statusBarStates.STATE_ERROR
  108. }
  109. if (isAllComplete) {
  110. return statusBarStates.STATE_COMPLETE
  111. }
  112. if (recoveredState) {
  113. return statusBarStates.STATE_WAITING
  114. }
  115. let state = statusBarStates.STATE_WAITING
  116. const fileIDs = Object.keys(files)
  117. for (let i = 0; i < fileIDs.length; i++) {
  118. const progress = files[fileIDs[i]].progress
  119. // If ANY files are being uploaded right now, show the uploading state.
  120. if (progress.uploadStarted && !progress.uploadComplete) {
  121. return statusBarStates.STATE_UPLOADING
  122. }
  123. // If files are being preprocessed AND postprocessed at this time, we show the
  124. // preprocess state. If any files are being uploaded we show uploading.
  125. if (progress.preprocess && state !== statusBarStates.STATE_UPLOADING) {
  126. state = statusBarStates.STATE_PREPROCESSING
  127. }
  128. // If NO files are being preprocessed or uploaded right now, but some files are
  129. // being postprocessed, show the postprocess state.
  130. if (progress.postprocess && state !== statusBarStates.STATE_UPLOADING && state !== statusBarStates.STATE_PREPROCESSING) {
  131. state = statusBarStates.STATE_POSTPROCESSING
  132. }
  133. }
  134. return state
  135. }
  136. render (state) {
  137. const {
  138. capabilities,
  139. files,
  140. allowNewUpload,
  141. totalProgress,
  142. error,
  143. recoveredState,
  144. } = state
  145. // TODO: move this to Core, to share between Status Bar and Dashboard
  146. // (and any other plugin that might need it, too)
  147. const filesArray = Object.keys(files).map(file => files[file])
  148. let newFiles = filesArray.filter((file) => {
  149. return !file.progress.uploadStarted
  150. && !file.progress.preprocess
  151. && !file.progress.postprocess
  152. })
  153. // If some state was recovered, we want to show Upload button/counter
  154. // for all the files, because in this case it’s not an Upload button,
  155. // but “Confirm Restore Button”
  156. if (recoveredState) {
  157. newFiles = filesArray
  158. }
  159. const uploadStartedFiles = filesArray.filter(file => file.progress.uploadStarted)
  160. const pausedFiles = uploadStartedFiles.filter(file => file.isPaused)
  161. const completeFiles = filesArray.filter(file => file.progress.uploadComplete)
  162. const erroredFiles = filesArray.filter(file => file.error)
  163. const inProgressFiles = filesArray.filter((file) => {
  164. return !file.progress.uploadComplete
  165. && file.progress.uploadStarted
  166. })
  167. const inProgressNotPausedFiles = inProgressFiles.filter(file => !file.isPaused)
  168. const startedFiles = filesArray.filter((file) => {
  169. return file.progress.uploadStarted
  170. || file.progress.preprocess
  171. || file.progress.postprocess
  172. })
  173. const processingFiles = filesArray.filter(file => file.progress.preprocess || file.progress.postprocess)
  174. const totalETA = this.getTotalETA(inProgressNotPausedFiles)
  175. let totalSize = 0
  176. let totalUploadedSize = 0
  177. startedFiles.forEach((file) => {
  178. totalSize += (file.progress.bytesTotal || 0)
  179. totalUploadedSize += (file.progress.bytesUploaded || 0)
  180. })
  181. const isUploadStarted = startedFiles.length > 0
  182. const isAllComplete = totalProgress === 100
  183. && completeFiles.length === Object.keys(files).length
  184. && processingFiles.length === 0
  185. const isAllErrored = error && erroredFiles.length === filesArray.length
  186. const isAllPaused = inProgressFiles.length !== 0
  187. && pausedFiles.length === inProgressFiles.length
  188. const isUploadInProgress = inProgressFiles.length > 0
  189. const resumableUploads = capabilities.resumableUploads || false
  190. const supportsUploadProgress = capabilities.uploadProgress !== false
  191. const isSomeGhost = filesArray.some((file) => file.isGhost)
  192. return StatusBarUI({
  193. error,
  194. uploadState: this.getUploadingState(isAllErrored, isAllComplete, recoveredState, state.files || {}),
  195. allowNewUpload,
  196. totalProgress,
  197. totalSize,
  198. totalUploadedSize,
  199. isAllComplete,
  200. isAllPaused,
  201. isAllErrored,
  202. isUploadStarted,
  203. isUploadInProgress,
  204. isSomeGhost,
  205. recoveredState,
  206. complete: completeFiles.length,
  207. newFiles: newFiles.length,
  208. numUploads: startedFiles.length,
  209. totalETA,
  210. files,
  211. i18n: this.i18n,
  212. pauseAll: this.uppy.pauseAll,
  213. resumeAll: this.uppy.resumeAll,
  214. retryAll: this.uppy.retryAll,
  215. cancelAll: this.uppy.cancelAll,
  216. startUpload: this.startUpload,
  217. doneButtonHandler: this.opts.doneButtonHandler,
  218. resumableUploads,
  219. supportsUploadProgress,
  220. showProgressDetails: this.opts.showProgressDetails,
  221. hideUploadButton: this.opts.hideUploadButton,
  222. hideRetryButton: this.opts.hideRetryButton,
  223. hidePauseResumeButton: this.opts.hidePauseResumeButton,
  224. hideCancelButton: this.opts.hideCancelButton,
  225. hideAfterFinish: this.opts.hideAfterFinish,
  226. isTargetDOMEl: this.isTargetDOMEl,
  227. })
  228. }
  229. onMount () {
  230. // Set the text direction if the page has not defined one.
  231. const element = this.el
  232. const direction = getTextDirection(element)
  233. if (!direction) {
  234. element.dir = 'ltr'
  235. }
  236. }
  237. install () {
  238. const target = this.opts.target
  239. if (target) {
  240. this.mount(target, this)
  241. }
  242. }
  243. uninstall () {
  244. this.unmount()
  245. }
  246. }