diff --git a/.eslintrc.js b/.eslintrc.js index 950799469f..27f2f42a47 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -85,7 +85,7 @@ module.exports = { 'no-param-reassign': ['warn'], 'no-redeclare': ['warn'], 'no-shadow': ['warn'], - 'no-use-before-define': ['warn'], + 'no-use-before-define': ['warn', { 'functions': false }], 'radix': ['warn'], 'react/button-has-type': 'error', 'react/destructuring-assignment': ['warn'], diff --git a/BACKLOG.md b/BACKLOG.md index e73cf9a67c..7387eba168 100644 --- a/BACKLOG.md +++ b/BACKLOG.md @@ -50,7 +50,6 @@ PRs are welcome! Please do open an issue to discuss first if it's a big feature, - [ ] Display data like image resolution on file cards. should be done by thumbnail generator maybe #783 - [ ] Possibility to edit/delete more than one file at once. example: add copyrigh info to 1000 files #118, #97 - [ ] Possibility to work on already uploaded / in progress files. We'll just provide the `fileId` to the `file-edit-complete` event so that folks can more easily roll out custom code for this themselves #112, #113, #2063 -- [ ] Show upload speed too if `showProgressDetails: true`. Maybe have separate options for which things are displayed, or at least have css-classes that can be hidden with `display: none` #766 - [ ] Focus jumps weirdly if you remove a file https://github.com/transloadit/uppy/pull/2161#issuecomment-613565486 - [ ] A mini UI that features drop & progress (may involve a `mini: true` options for dashboard, may involve drop+progress or new plugin) (@arturi) - [ ] Add a Load More button so you don't have to TAB endlessly to get to the upload button (https://github.com/transloadit/uppy/issues/1419) diff --git a/packages/@uppy/dashboard/src/style.scss b/packages/@uppy/dashboard/src/style.scss index c2d62b197a..c6466a36e8 100644 --- a/packages/@uppy/dashboard/src/style.scss +++ b/packages/@uppy/dashboard/src/style.scss @@ -705,11 +705,6 @@ height: 100%; } -// Do not show progress details in the StatusBar if we do not have space. -.uppy-Dashboard:not(.uppy-size--md) .uppy-StatusBar-additionalInfo { - display: none; -} - .uppy-Dashboard-filesContainer { @include clearfix; diff --git a/packages/@uppy/status-bar/src/Components.js b/packages/@uppy/status-bar/src/Components.js new file mode 100644 index 0000000000..24b8902be5 --- /dev/null +++ b/packages/@uppy/status-bar/src/Components.js @@ -0,0 +1,446 @@ +const classNames = require('classnames') +const throttle = require('lodash.throttle') +const prettierBytes = require('@transloadit/prettier-bytes') +const prettyETA = require('@uppy/utils/lib/prettyETA') +const { h } = require('preact') + +const statusBarStates = require('./StatusBarStates') + +const DOT = `\u00B7` +const renderDot = () => ` ${DOT} ` + +function UploadBtn (props) { + const { + newFiles, + isUploadStarted, + recoveredState, + i18n, + uploadState, + isSomeGhost, + startUpload, + } = props + + const uploadBtnClassNames = classNames( + 'uppy-u-reset', + 'uppy-c-btn', + 'uppy-StatusBar-actionBtn', + 'uppy-StatusBar-actionBtn--upload', + { + 'uppy-c-btn-primary': uploadState === statusBarStates.STATE_WAITING, + }, + { 'uppy-StatusBar-actionBtn--disabled': isSomeGhost } + ) + + const uploadBtnText + = newFiles && isUploadStarted && !recoveredState + ? i18n('uploadXNewFiles', { smart_count: newFiles }) + : i18n('uploadXFiles', { smart_count: newFiles }) + + return ( + + ) +} + +function RetryBtn (props) { + const { i18n, uppy } = props + + return ( + + ) +} + +function CancelBtn (props) { + const { i18n, uppy } = props + + return ( + + ) +} + +function PauseResumeButton (props) { + const { isAllPaused, i18n, isAllComplete, resumableUploads, uppy } = props + const title = isAllPaused ? i18n('resume') : i18n('pause') + + function togglePauseResume () { + if (isAllComplete) return null + + if (!resumableUploads) { + return uppy.cancelAll() + } + + if (isAllPaused) { + return uppy.resumeAll() + } + + return uppy.pauseAll() + } + + return ( + + ) +} + +function DoneBtn (props) { + const { i18n, doneButtonHandler } = props + + return ( + + ) +} + +function LoadingSpinner () { + return ( + + ) +} + +function ProgressBarProcessing (props) { + const { value, mode, message } = props + const roundedValue = Math.round(value * 100) + const dot = `\u00B7` + + return ( +
+ + {mode === 'determinate' ? `${roundedValue}% ${dot} ` : ''} + {message} +
+ ) +} + +function ProgressDetails (props) { + const { + numUploads, + complete, + totalUploadedSize, + totalSize, + totalETA, + i18n, + } = props + + const ifShowFilesUploadedOfTotal = numUploads > 1 + + return ( +
+ {ifShowFilesUploadedOfTotal + && i18n('filesUploadedOfTotal', { + complete, + smart_count: numUploads, + })} + + {/* When should we render this dot? + 1. .-additionalInfo is shown (happens only on desktops) + 2. AND 'filesUploadedOfTotal' was shown + */} + {ifShowFilesUploadedOfTotal && renderDot()} + + {i18n('dataUploadedOfTotal', { + complete: prettierBytes(totalUploadedSize), + total: prettierBytes(totalSize), + })} + + {renderDot()} + + {i18n('xTimeLeft', { + time: prettyETA(totalETA), + })} + +
+ ) +} + +function UnknownProgressDetails (props) { + const { i18n, complete, numUploads } = props + + return ( +
+ {i18n('filesUploadedOfTotal', { complete, smart_count: numUploads })} +
+ ) +} + +function UploadNewlyAddedFiles (props) { + const { i18n, newFiles, startUpload } = props + const uploadBtnClassNames = classNames( + 'uppy-u-reset', + 'uppy-c-btn', + 'uppy-StatusBar-actionBtn', + 'uppy-StatusBar-actionBtn--uploadNewlyAdded' + ) + + return ( +
+
+ {i18n('xMoreFilesAdded', { smart_count: newFiles })} +
+ +
+ ) +} + +const ThrottledProgressDetails = throttle(ProgressDetails, 500, { + leading: true, + trailing: true, +}) + +function ProgressBarUploading (props) { + const { + i18n, + supportsUploadProgress, + totalProgress, + showProgressDetails, + isUploadStarted, + isAllComplete, + isAllPaused, + newFiles, + numUploads, + complete, + totalUploadedSize, + totalSize, + totalETA, + startUpload, + } = props + const showUploadNewlyAddedFiles = newFiles && isUploadStarted + + if (!isUploadStarted || isAllComplete) { + return null + } + + const title = isAllPaused ? i18n('paused') : i18n('uploading') + + function renderProgressDetails () { + if (!isAllPaused && !showUploadNewlyAddedFiles && showProgressDetails) { + if (supportsUploadProgress) { + return ( + + ) + } + return ( + + ) + } + return null + } + + return ( +
+ {!isAllPaused ? : null} +
+
+ {supportsUploadProgress ? `${title}: ${totalProgress}%` : title} +
+ + {renderProgressDetails()} + + {showUploadNewlyAddedFiles ? ( + + ) : null} +
+
+ ) +} + +function ProgressBarComplete (props) { + const { i18n } = props + + return ( +
+
+
+ + {i18n('complete')} +
+
+
+ ) +} + +function ProgressBarError (props) { + const { error, i18n } = props + + function displayErrorAlert () { + const errorMessage = `${i18n('uploadFailed')} \n\n ${error}` + // eslint-disable-next-line no-alert + alert(errorMessage) // TODO: move to custom alert implementation + } + + return ( +
+
+
+ + {i18n('uploadFailed')} +
+
+ +
+ ) +} + +module.exports = { + UploadBtn, + RetryBtn, + CancelBtn, + PauseResumeButton, + DoneBtn, + LoadingSpinner, + ProgressDetails, + ProgressBarProcessing, + ProgressBarError, + ProgressBarUploading, + ProgressBarComplete, +} diff --git a/packages/@uppy/status-bar/src/StatusBar.js b/packages/@uppy/status-bar/src/StatusBar.js index d87ae6d3d5..bbd412d4bc 100644 --- a/packages/@uppy/status-bar/src/StatusBar.js +++ b/packages/@uppy/status-bar/src/StatusBar.js @@ -1,57 +1,32 @@ -const throttle = require('lodash.throttle') -const classNames = require('classnames') -const prettierBytes = require('@transloadit/prettier-bytes') -const prettyETA = require('@uppy/utils/lib/prettyETA') const { h } = require('preact') +const classNames = require('classnames') const statusBarStates = require('./StatusBarStates') - -function calculateProcessingProgress (files) { - // Collect pre or postprocessing progress states. - const progresses = [] - Object.keys(files).forEach((fileID) => { - const { progress } = files[fileID] - if (progress.preprocess) { - progresses.push(progress.preprocess) - } - if (progress.postprocess) { - progresses.push(progress.postprocess) - } - }) - - // In the future we should probably do this differently. For now we'll take the - // mode and message from the first file… - const { mode, message } = progresses[0] - const value = progresses.filter(isDeterminate).reduce((total, progress, index, all) => { - return total + progress.value / all.length - }, 0) - function isDeterminate (progress) { - return progress.mode === 'determinate' - } - - return { - mode, - message, - value, - } -} - -function togglePauseResume (props) { - if (props.isAllComplete) return - - if (!props.resumableUploads) { - return props.uppy.cancelAll() - } - - if (props.isAllPaused) { - return props.uppy.resumeAll() - } - - return props.uppy.pauseAll() -} - -module.exports = (props) => { - props = props || {} - +const calculateProcessingProgress = require('./calculateProcessingProgress') + +const { + UploadBtn, + RetryBtn, + CancelBtn, + PauseResumeButton, + DoneBtn, + ProgressBarProcessing, + ProgressBarError, + ProgressBarUploading, + ProgressBarComplete, +} = require('./Components') + +const { + STATE_ERROR, + STATE_WAITING, + STATE_PREPROCESSING, + STATE_UPLOADING, + STATE_POSTPROCESSING, + STATE_COMPLETE, +} = statusBarStates + +module.exports = StatusBar + +function StatusBar (props) { const { newFiles, allowNewUpload, @@ -64,68 +39,122 @@ module.exports = (props) => { hideCancelButton, hideRetryButton, recoveredState, + uploadState, + totalProgress, + files, + supportsUploadProgress, + hideAfterFinish, + isSomeGhost, + isTargetDOMEl, + doneButtonHandler, + isUploadStarted, + i18n, + startUpload, + uppy, + isAllComplete, + showProgressDetails, + numUploads, + complete, + totalSize, + totalETA, + totalUploadedSize, } = props - const { uploadState } = props + function getProgressValue () { + switch (uploadState) { + case STATE_POSTPROCESSING: + case STATE_PREPROCESSING: { + const progress = calculateProcessingProgress(files) - let progressValue = props.totalProgress - let progressMode - let progressBarContent + if (progress.mode === 'determinate') { + return progress.value * 100 + } + return totalProgress + } + case STATE_ERROR: { + return null + } + case STATE_UPLOADING: { + if (!supportsUploadProgress) { + return null + } + return totalProgress + } + default: + return totalProgress + } + } - if (uploadState === statusBarStates.STATE_PREPROCESSING || uploadState === statusBarStates.STATE_POSTPROCESSING) { - const progress = calculateProcessingProgress(props.files) - progressMode = progress.mode - if (progressMode === 'determinate') { - progressValue = progress.value * 100 + function getIsIndeterminate () { + switch (uploadState) { + case STATE_POSTPROCESSING: + case STATE_PREPROCESSING: { + const { mode } = calculateProcessingProgress(files) + return mode === 'indeterminate' + } + case STATE_UPLOADING: { + if (!supportsUploadProgress) { + return true + } + return false + } + default: + return false } + } - progressBarContent = ProgressBarProcessing(progress) - } else if (uploadState === statusBarStates.STATE_COMPLETE) { - progressBarContent = ProgressBarComplete(props) - } else if (uploadState === statusBarStates.STATE_UPLOADING) { - if (!props.supportsUploadProgress) { - progressMode = 'indeterminate' - progressValue = null + function getIsHidden () { + if (recoveredState) { + return false } - progressBarContent = ProgressBarUploading(props) - } else if (uploadState === statusBarStates.STATE_ERROR) { - progressValue = undefined - progressBarContent = ProgressBarError(props) + switch (uploadState) { + case STATE_WAITING: + return hideUploadButton || newFiles === 0 + case STATE_COMPLETE: + return hideAfterFinish + default: + return false + } } - const width = typeof progressValue === 'number' ? progressValue : 100 - let isHidden = (uploadState === statusBarStates.STATE_WAITING && props.hideUploadButton) - || (uploadState === statusBarStates.STATE_WAITING && !props.newFiles > 0) - || (uploadState === statusBarStates.STATE_COMPLETE && props.hideAfterFinish) + const progressValue = getProgressValue() - let showUploadBtn = !error && newFiles - && !isUploadInProgress && !isAllPaused - && allowNewUpload && !hideUploadButton + const isHidden = getIsHidden() - if (recoveredState) { - isHidden = false - showUploadBtn = true - } + const width = progressValue ?? 100 + + const showUploadBtn + = !error + && newFiles + && !isUploadInProgress + && !isAllPaused + && allowNewUpload + && !hideUploadButton - const showCancelBtn = !hideCancelButton - && uploadState !== statusBarStates.STATE_WAITING - && uploadState !== statusBarStates.STATE_COMPLETE - const showPauseResumeBtn = resumableUploads && !hidePauseResumeButton - && uploadState === statusBarStates.STATE_UPLOADING + const showCancelBtn + = !hideCancelButton + && uploadState !== STATE_WAITING + && uploadState !== STATE_COMPLETE + + const showPauseResumeBtn + = resumableUploads + && !hidePauseResumeButton + && uploadState === STATE_UPLOADING const showRetryBtn = error && !hideRetryButton - const showDoneBtn = props.doneButtonHandler && uploadState === statusBarStates.STATE_COMPLETE + const showDoneBtn = doneButtonHandler && uploadState === STATE_COMPLETE - const progressClassNames = `uppy-StatusBar-progress - ${progressMode ? `is-${progressMode}` : ''}` + const progressClassNames = classNames('uppy-StatusBar-progress', { + 'is-indeterminate': getIsIndeterminate(), + }) const statusBarClassNames = classNames( - { 'uppy-Root': props.isTargetDOMEl }, + { 'uppy-Root': isTargetDOMEl }, 'uppy-StatusBar', `is-${uploadState}`, - { 'has-ghosts': props.isSomeGhost } + { 'has-ghosts': isSomeGhost } ) return ( @@ -140,291 +169,75 @@ module.exports = (props) => { aria-valuemax="100" aria-valuenow={progressValue} /> - {progressBarContent} -
- {showUploadBtn ? : null} - {showRetryBtn ? : null} - {showPauseResumeBtn ? : null} - {showCancelBtn ? : null} - {showDoneBtn ? : null} -
- - ) -} - -const UploadBtn = (props) => { - const uploadBtnClassNames = classNames( - 'uppy-u-reset', - 'uppy-c-btn', - 'uppy-StatusBar-actionBtn', - 'uppy-StatusBar-actionBtn--upload', - { 'uppy-c-btn-primary': props.uploadState === statusBarStates.STATE_WAITING }, - { 'uppy-StatusBar-actionBtn--disabled': props.isSomeGhost } - ) - - const uploadBtnText = props.newFiles && props.isUploadStarted && !props.recoveredState - ? props.i18n('uploadXNewFiles', { smart_count: props.newFiles }) - : props.i18n('uploadXFiles', { smart_count: props.newFiles }) - - return ( - - ) -} - -const RetryBtn = (props) => { - return ( - - ) -} -const CancelBtn = (props) => { - return ( - - ) -} - -const PauseResumeButton = (props) => { - const { isAllPaused, i18n } = props - const title = isAllPaused ? i18n('resume') : i18n('pause') - - return ( - - ) -} - -const DoneBtn = (props) => { - const { i18n } = props - return ( - - ) -} - -const LoadingSpinner = () => { - return ( - - ) -} - -const ProgressBarProcessing = (props) => { - const value = Math.round(props.value * 100) - - return ( -
- - {props.mode === 'determinate' ? `${value}% \u00B7 ` : ''} - {props.message} -
- ) -} - -const renderDot = () => ' \u00B7 ' - -const ProgressDetails = (props) => { - const ifShowFilesUploadedOfTotal = props.numUploads > 1 - - return ( -
- { - ifShowFilesUploadedOfTotal - && props.i18n('filesUploadedOfTotal', { - complete: props.complete, - smart_count: props.numUploads, - }) - } - - {/* When should we render this dot? - 1. .-additionalInfo is shown (happens only on desktops) - 2. AND 'filesUploadedOfTotal' was shown - */} - {ifShowFilesUploadedOfTotal && renderDot()} - - { - props.i18n('dataUploadedOfTotal', { - complete: prettierBytes(props.totalUploadedSize), - total: prettierBytes(props.totalSize), - }) + {(() => { + switch (uploadState) { + case STATE_PREPROCESSING: + case STATE_POSTPROCESSING: + return ( + + ) + case STATE_COMPLETE: + return + case STATE_ERROR: + return + case STATE_UPLOADING: + return ( + + ) + default: + return null } + })()} - {renderDot()} - - { - props.i18n('xTimeLeft', { - time: prettyETA(props.totalETA), - }) - } - -
- ) -} - -const UnknownProgressDetails = (props) => { - return ( -
- {props.i18n('filesUploadedOfTotal', { complete: props.complete, smart_count: props.numUploads })} -
- ) -} - -const UploadNewlyAddedFiles = (props) => { - const uploadBtnClassNames = classNames( - 'uppy-u-reset', - 'uppy-c-btn', - 'uppy-StatusBar-actionBtn', - 'uppy-StatusBar-actionBtn--uploadNewlyAdded' - ) - - return ( -
-
- {props.i18n('xMoreFilesAdded', { smart_count: props.newFiles })} -
- -
- ) -} - -const ThrottledProgressDetails = throttle(ProgressDetails, 500, { leading: true, trailing: true }) - -const ProgressBarUploading = (props) => { - if (!props.isUploadStarted || props.isAllComplete) { - return null - } - - const title = props.isAllPaused ? props.i18n('paused') : props.i18n('uploading') - const showUploadNewlyAddedFiles = props.newFiles && props.isUploadStarted - - return ( -
- {!props.isAllPaused ? : null} -
-
- {props.supportsUploadProgress ? `${title}: ${props.totalProgress}%` : title} -
- {/* eslint-disable-next-line no-nested-ternary */} - {!props.isAllPaused && !showUploadNewlyAddedFiles && props.showProgressDetails - ? (props.supportsUploadProgress ? : ) - : null} - {showUploadNewlyAddedFiles ? : null} -
-
- ) -} - -const ProgressBarComplete = ({ i18n }) => { - return ( -
-
-
- - {i18n('complete')} -
-
-
- ) -} - -const ProgressBarError = ({ error, i18n }) => { - function displayErrorAlert () { - const errorMessage = `${i18n('uploadFailed')} \n\n ${error}` - // eslint-disable-next-line no-alert - alert(errorMessage) // TODO: move to custom alert implementation - } - - return ( -
-
-
- - {i18n('uploadFailed')} -
+
+ {(recoveredState || showUploadBtn) ? ( + + ) : null} + + {showRetryBtn ? : null} + + {showPauseResumeBtn ? ( + + ) : null} + + {showCancelBtn ? : null} + + {showDoneBtn ? ( + + ) : null}
-
) } diff --git a/packages/@uppy/status-bar/src/calculateProcessingProgress.js b/packages/@uppy/status-bar/src/calculateProcessingProgress.js new file mode 100644 index 0000000000..2864022fbf --- /dev/null +++ b/packages/@uppy/status-bar/src/calculateProcessingProgress.js @@ -0,0 +1,26 @@ +module.export = function calculateProcessingProgress (files) { + const values = [] + let mode + let message + + for (const { progress } of Object.values(files)) { + const { preprocess, postprocess } = progress + // In the future we should probably do this differently. For now we'll take the + // mode and message from the first file… + if (message == null && (preprocess || postprocess)) { + ({ mode, message } = preprocess || postprocess) + } + if (preprocess?.mode === 'determinate') values.push(preprocess.value) + if (postprocess?.mode === 'determinate') values.push(postprocess.value) + } + + const value = values.reduce((total, progressValue) => { + return total + progressValue / values.length + }, 0) + + return { + mode, + message, + value, + } +} diff --git a/packages/@uppy/status-bar/src/index.js b/packages/@uppy/status-bar/src/index.js index a52b47a66e..30ca006ec5 100644 --- a/packages/@uppy/status-bar/src/index.js +++ b/packages/@uppy/status-bar/src/index.js @@ -10,7 +10,8 @@ const StatusBarUI = require('./StatusBar') * progress percentage and time remaining. */ module.exports = class StatusBar extends UIPlugin { - static VERSION = require('../package.json').version + // eslint-disable-next-line global-require + static VERSION = require('../package.json').version; constructor (uppy, opts) { super(uppy, opts) @@ -72,74 +73,18 @@ module.exports = class StatusBar extends UIPlugin { this.install = this.install.bind(this) } - getTotalSpeed (files) { - let totalSpeed = 0 - files.forEach((file) => { - totalSpeed += getSpeed(file.progress) - }) - return totalSpeed - } - - getTotalETA (files) { - const totalSpeed = this.getTotalSpeed(files) - if (totalSpeed === 0) { - return 0 - } - - const totalBytesRemaining = files.reduce((total, file) => { - return total + getBytesRemaining(file.progress) - }, 0) - - return Math.round(totalBytesRemaining / totalSpeed * 10) / 10 - } - startUpload = () => { const { recoveredState } = this.uppy.getState() + if (recoveredState) { this.uppy.emit('restore-confirmed') - return + return undefined } + return this.uppy.upload().catch(() => { // Error logged in Core }) - } - - getUploadingState (isAllErrored, isAllComplete, recoveredState, files) { - if (isAllErrored) { - return statusBarStates.STATE_ERROR - } - - if (isAllComplete) { - return statusBarStates.STATE_COMPLETE - } - - if (recoveredState) { - return statusBarStates.STATE_WAITING - } - - let state = statusBarStates.STATE_WAITING - const fileIDs = Object.keys(files) - for (let i = 0; i < fileIDs.length; i++) { - const { progress } = files[fileIDs[i]] - // If ANY files are being uploaded right now, show the uploading state. - if (progress.uploadStarted && !progress.uploadComplete) { - return statusBarStates.STATE_UPLOADING - } - // If files are being preprocessed AND postprocessed at this time, we show the - // preprocess state. If any files are being uploaded we show uploading. - if (progress.preprocess && state !== statusBarStates.STATE_UPLOADING) { - state = statusBarStates.STATE_PREPROCESSING - } - // If NO files are being preprocessed or uploaded right now, but some files are - // being postprocessed, show the postprocess state. - if (progress.postprocess - && state !== statusBarStates.STATE_UPLOADING - && state !== statusBarStates.STATE_PREPROCESSING) { - state = statusBarStates.STATE_POSTPROCESSING - } - } - return state - } + }; render (state) { const { @@ -168,8 +113,10 @@ module.exports = class StatusBar extends UIPlugin { // If some state was recovered, we want to show Upload button/counter // for all the files, because in this case it’s not an Upload button, // but “Confirm Restore Button” - const newFilesOrRecovered = recoveredState ? Object.values(files) : newFiles - const totalETA = this.getTotalETA(inProgressNotPausedFiles) + const newFilesOrRecovered = recoveredState + ? Object.values(files) + : newFiles + const totalETA = getTotalETA(inProgressNotPausedFiles) const resumableUploads = !!capabilities.resumableUploads const supportsUploadProgress = capabilities.uploadProgress !== false @@ -177,18 +124,23 @@ module.exports = class StatusBar extends UIPlugin { let totalUploadedSize = 0 startedFiles.forEach((file) => { - totalSize += (file.progress.bytesTotal || 0) - totalUploadedSize += (file.progress.bytesUploaded || 0) + totalSize += file.progress.bytesTotal || 0 + totalUploadedSize += file.progress.bytesUploaded || 0 }) return StatusBarUI({ error, - uploadState: this.getUploadingState(isAllErrored, isAllComplete, recoveredState, state.files || {}), + uploadState: getUploadingState( + isAllErrored, + isAllComplete, + recoveredState, + state.files || {} + ), allowNewUpload, totalProgress, totalSize, totalUploadedSize, - isAllComplete, + isAllComplete: false, isAllPaused, isAllErrored, isUploadStarted, @@ -236,3 +188,63 @@ module.exports = class StatusBar extends UIPlugin { this.unmount() } } + +function getTotalSpeed (files) { + let totalSpeed = 0 + files.forEach((file) => { + totalSpeed += getSpeed(file.progress) + }) + return totalSpeed +} + +function getTotalETA (files) { + const totalSpeed = getTotalSpeed(files) + if (totalSpeed === 0) { + return 0 + } + + const totalBytesRemaining = files.reduce((total, file) => { + return total + getBytesRemaining(file.progress) + }, 0) + + return Math.round((totalBytesRemaining / totalSpeed) * 10) / 10 +} + +function getUploadingState (isAllErrored, isAllComplete, recoveredState, files) { + if (isAllErrored) { + return statusBarStates.STATE_ERROR + } + + if (isAllComplete) { + return statusBarStates.STATE_COMPLETE + } + + if (recoveredState) { + return statusBarStates.STATE_WAITING + } + + let state = statusBarStates.STATE_WAITING + const fileIDs = Object.keys(files) + for (let i = 0; i < fileIDs.length; i++) { + const { progress } = files[fileIDs[i]] + // If ANY files are being uploaded right now, show the uploading state. + if (progress.uploadStarted && !progress.uploadComplete) { + return statusBarStates.STATE_UPLOADING + } + // If files are being preprocessed AND postprocessed at this time, we show the + // preprocess state. If any files are being uploaded we show uploading. + if (progress.preprocess && state !== statusBarStates.STATE_UPLOADING) { + state = statusBarStates.STATE_PREPROCESSING + } + // If NO files are being preprocessed or uploaded right now, but some files are + // being postprocessed, show the postprocess state. + if ( + progress.postprocess + && state !== statusBarStates.STATE_UPLOADING + && state !== statusBarStates.STATE_PREPROCESSING + ) { + state = statusBarStates.STATE_POSTPROCESSING + } + } + return state +} diff --git a/packages/@uppy/status-bar/src/style.scss b/packages/@uppy/status-bar/src/style.scss index ece3152fb2..eea2c756e8 100644 --- a/packages/@uppy/status-bar/src/style.scss +++ b/packages/@uppy/status-bar/src/style.scss @@ -6,7 +6,7 @@ position: relative; z-index: $zIndex-2; display: flex; - height: 40px; + height: 46px; color: $white; font-weight: 400; font-size: 12px; @@ -14,10 +14,6 @@ background-color: $white; transition: height 0.2s; - .uppy-size--md & { - height: 46px; - } - [data-uppy-theme="dark"] & { background-color: $gray-900; }