StatusBar.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. const html = require('yo-yo')
  2. const throttle = require('lodash.throttle')
  3. function progressDetails (props) {
  4. return html`<span>${props.totalProgress || 0}%・${props.complete} / ${props.inProgress}・${props.totalUploadedSize} / ${props.totalSize}・↑ ${props.totalSpeed}/s・${props.totalETA}</span>`
  5. }
  6. const throttledProgressDetails = throttle(progressDetails, 1000, {leading: true, trailing: true})
  7. const STATE_ERROR = 'error'
  8. const STATE_WAITING = 'waiting'
  9. const STATE_PREPROCESSING = 'preprocessing'
  10. const STATE_UPLOADING = 'uploading'
  11. const STATE_POSTPROCESSING = 'postprocessing'
  12. const STATE_COMPLETE = 'complete'
  13. function getUploadingState (props, files) {
  14. if (props.error) {
  15. return STATE_ERROR
  16. }
  17. // If ALL files have been completed, show the completed state.
  18. if (props.isAllComplete) {
  19. return STATE_COMPLETE
  20. }
  21. let state = STATE_WAITING
  22. const fileIDs = Object.keys(files)
  23. for (let i = 0; i < fileIDs.length; i++) {
  24. const progress = files[fileIDs[i]].progress
  25. // If ANY files are being uploaded right now, show the uploading state.
  26. if (progress.uploadStarted && !progress.uploadComplete) {
  27. return STATE_UPLOADING
  28. }
  29. // If files are being preprocessed AND postprocessed at this time, we show the
  30. // preprocess state. If any files are being uploaded we show uploading.
  31. if (progress.preprocess && state !== STATE_UPLOADING) {
  32. state = STATE_PREPROCESSING
  33. }
  34. // If NO files are being preprocessed or uploaded right now, but some files are
  35. // being postprocessed, show the postprocess state.
  36. if (progress.postprocess && state !== STATE_UPLOADING && state !== STATE_PREPROCESSING) {
  37. state = STATE_POSTPROCESSING
  38. }
  39. }
  40. return state
  41. }
  42. module.exports = (props) => {
  43. props = props || {}
  44. const uploadState = getUploadingState(props, props.files || {})
  45. let progressValue = props.totalProgress
  46. let progressMode
  47. let progressBarContent
  48. if (uploadState === STATE_PREPROCESSING || uploadState === STATE_POSTPROCESSING) {
  49. // TODO set progressValue and progressMode depending on the actual pre/postprocess
  50. // progress state
  51. progressMode = 'indeterminate'
  52. progressValue = undefined
  53. progressBarContent = ProgressBarProcessing(props)
  54. } else if (uploadState === STATE_COMPLETE) {
  55. progressBarContent = ProgressBarComplete(props)
  56. } else if (uploadState === STATE_UPLOADING) {
  57. progressBarContent = ProgressBarUploading(props)
  58. } else if (uploadState === STATE_ERROR) {
  59. progressValue = undefined
  60. progressBarContent = ProgressBarError(props)
  61. }
  62. const width = typeof progressValue === 'number' ? progressValue : 100
  63. return html`
  64. <div class="UppyStatusBar is-${uploadState}"
  65. aria-hidden="${uploadState === STATE_WAITING}"
  66. title="">
  67. <progress style="display: none;" min="0" max="100" value=${progressValue}></progress>
  68. <div class="UppyStatusBar-progress ${progressMode ? `is-${progressMode}` : ''}"
  69. style="width: ${width}%"></div>
  70. ${progressBarContent}
  71. </div>
  72. `
  73. }
  74. const ProgressBarProcessing = (props) => {
  75. // Collect pre or postprocessing progress states.
  76. const progresses = []
  77. Object.keys(props.files).forEach((fileID) => {
  78. const { progress } = props.files[fileID]
  79. if (progress.preprocess) {
  80. progresses.push(progress.preprocess)
  81. }
  82. if (progress.postprocess) {
  83. progresses.push(progress.postprocess)
  84. }
  85. })
  86. // In the future we should probably do this differently. For now we'll take the
  87. // mode and message from the first file…
  88. const { mode, message } = progresses[0]
  89. const value = progresses.filter(isDeterminate).reduce((total, progress, all) => {
  90. return total + progress.value / all.length
  91. }, 0)
  92. function isDeterminate (progress) {
  93. return progress.mode === 'determinate'
  94. }
  95. return html`
  96. <div class="UppyStatusBar-content">
  97. ${mode === 'determinate' ? `${value * 100}%・` : ''}
  98. ${message}
  99. </div>
  100. `
  101. }
  102. const ProgressBarUploading = (props) => {
  103. return html`
  104. <div class="UppyStatusBar-content">
  105. ${props.isUploadStarted && !props.isAllComplete
  106. ? !props.isAllPaused
  107. ? html`<span title="Uploading">${pauseResumeButtons(props)} Uploading... ${throttledProgressDetails(props)}</span>`
  108. : html`<span title="Paused">${pauseResumeButtons(props)} Paused・${props.totalProgress}%</span>`
  109. : null
  110. }
  111. </div>
  112. `
  113. }
  114. const ProgressBarComplete = ({ totalProgress }) => {
  115. return html`
  116. <div class="UppyStatusBar-content">
  117. <span title="Complete">
  118. <svg class="UppyStatusBar-action UppyIcon" width="18" height="17" viewBox="0 0 23 17">
  119. <path d="M8.944 17L0 7.865l2.555-2.61 6.39 6.525L20.41 0 23 2.645z" />
  120. </svg>
  121. Upload complete・${totalProgress}%
  122. </span>
  123. </div>
  124. `
  125. }
  126. const ProgressBarError = ({ error }) => {
  127. return html`
  128. <div class="UppyStatusBar-content">
  129. <span>
  130. ${error.message}
  131. </span>
  132. </div>
  133. `
  134. }
  135. const pauseResumeButtons = (props) => {
  136. const title = props.resumableUploads
  137. ? props.isAllPaused
  138. ? 'resume upload'
  139. : 'pause upload'
  140. : 'cancel upload'
  141. return html`<button title="${title}" class="UppyStatusBar-action" type="button" onclick=${() => togglePauseResume(props)}>
  142. ${props.resumableUploads
  143. ? props.isAllPaused
  144. ? html`<svg class="UppyIcon" width="15" height="17" viewBox="0 0 11 13">
  145. <path d="M1.26 12.534a.67.67 0 0 1-.674.012.67.67 0 0 1-.336-.583v-11C.25.724.38.5.586.382a.658.658 0 0 1 .673.012l9.165 5.5a.66.66 0 0 1 .325.57.66.66 0 0 1-.325.573l-9.166 5.5z" />
  146. </svg>`
  147. : html`<svg class="UppyIcon" width="16" height="17" viewBox="0 0 12 13">
  148. <path d="M4.888.81v11.38c0 .446-.324.81-.722.81H2.722C2.324 13 2 12.636 2 12.19V.81c0-.446.324-.81.722-.81h1.444c.398 0 .722.364.722.81zM9.888.81v11.38c0 .446-.324.81-.722.81H7.722C7.324 13 7 12.636 7 12.19V.81c0-.446.324-.81.722-.81h1.444c.398 0 .722.364.722.81z"/>
  149. </svg>`
  150. : html`<svg class="UppyIcon" width="16px" height="16px" viewBox="0 0 19 19">
  151. <path d="M17.318 17.232L9.94 9.854 9.586 9.5l-.354.354-7.378 7.378h.707l-.62-.62v.706L9.318 9.94l.354-.354-.354-.354L1.94 1.854v.707l.62-.62h-.706l7.378 7.378.354.354.354-.354 7.378-7.378h-.707l.622.62v-.706L9.854 9.232l-.354.354.354.354 7.378 7.378.708-.707-7.38-7.378v.708l7.38-7.38.353-.353-.353-.353-.622-.622-.353-.353-.354.352-7.378 7.38h.708L2.56 1.23 2.208.88l-.353.353-.622.62-.353.355.352.353 7.38 7.38v-.708l-7.38 7.38-.353.353.352.353.622.622.353.353.354-.353 7.38-7.38h-.708l7.38 7.38z"/>
  152. </svg>`
  153. }
  154. </button>`
  155. }
  156. const togglePauseResume = (props) => {
  157. if (props.isAllComplete) return
  158. if (!props.resumableUploads) {
  159. return props.cancelAll()
  160. }
  161. if (props.isAllPaused) {
  162. return props.resumeAll()
  163. }
  164. return props.pauseAll()
  165. }