@uppy/status-bar
Version:
A progress bar for Uppy, with many bells and whistles.
275 lines (267 loc) • 10.9 kB
JavaScript
function _classPrivateFieldLooseBase(e, t) { if (!{}.hasOwnProperty.call(e, t)) throw new TypeError("attempted to use private field on non-instance"); return e; }
var id = 0;
function _classPrivateFieldLooseKey(e) { return "__private_" + id++ + "_" + e; }
import { UIPlugin } from '@uppy/core';
import emaFilter from '@uppy/utils/lib/emaFilter';
import getTextDirection from '@uppy/utils/lib/getTextDirection';
import statusBarStates from './StatusBarStates.js';
import StatusBarUI from "./StatusBarUI.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore We don't want TS to generate types for the package.json
const packageJson = {
"version": "4.1.2"
};
import locale from './locale.js';
const speedFilterHalfLife = 2000;
const ETAFilterHalfLife = 2000;
function getUploadingState(error, isAllComplete, recoveredState, files) {
if (error) {
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_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_PREPROCESSING) {
state = statusBarStates.STATE_POSTPROCESSING;
}
}
return state;
}
const defaultOptions = {
hideUploadButton: false,
hideRetryButton: false,
hidePauseResumeButton: false,
hideCancelButton: false,
showProgressDetails: false,
hideAfterFinish: true,
doneButtonHandler: null
};
/**
* StatusBar: renders a status bar with upload/pause/resume/cancel/retry buttons,
* progress percentage and time remaining.
*/
var _lastUpdateTime = /*#__PURE__*/_classPrivateFieldLooseKey("lastUpdateTime");
var _previousUploadedBytes = /*#__PURE__*/_classPrivateFieldLooseKey("previousUploadedBytes");
var _previousSpeed = /*#__PURE__*/_classPrivateFieldLooseKey("previousSpeed");
var _previousETA = /*#__PURE__*/_classPrivateFieldLooseKey("previousETA");
var _computeSmoothETA = /*#__PURE__*/_classPrivateFieldLooseKey("computeSmoothETA");
var _onUploadStart = /*#__PURE__*/_classPrivateFieldLooseKey("onUploadStart");
export default class StatusBar extends UIPlugin {
constructor(uppy, opts) {
super(uppy, {
...defaultOptions,
...opts
});
Object.defineProperty(this, _computeSmoothETA, {
value: _computeSmoothETA2
});
Object.defineProperty(this, _lastUpdateTime, {
writable: true,
value: void 0
});
Object.defineProperty(this, _previousUploadedBytes, {
writable: true,
value: void 0
});
Object.defineProperty(this, _previousSpeed, {
writable: true,
value: void 0
});
Object.defineProperty(this, _previousETA, {
writable: true,
value: void 0
});
this.startUpload = () => {
return this.uppy.upload().catch(() => {
// Error logged in Core
});
};
Object.defineProperty(this, _onUploadStart, {
writable: true,
value: () => {
const {
recoveredState
} = this.uppy.getState();
_classPrivateFieldLooseBase(this, _previousSpeed)[_previousSpeed] = null;
_classPrivateFieldLooseBase(this, _previousETA)[_previousETA] = null;
if (recoveredState) {
_classPrivateFieldLooseBase(this, _previousUploadedBytes)[_previousUploadedBytes] = Object.values(recoveredState.files).reduce((pv, _ref) => {
let {
progress
} = _ref;
return pv + progress.bytesUploaded;
}, 0);
// We don't set `#lastUpdateTime` at this point because the upload won't
// actually resume until the user asks for it.
this.uppy.emit('restore-confirmed');
return;
}
_classPrivateFieldLooseBase(this, _lastUpdateTime)[_lastUpdateTime] = performance.now();
_classPrivateFieldLooseBase(this, _previousUploadedBytes)[_previousUploadedBytes] = 0;
}
});
this.id = this.opts.id || 'StatusBar';
this.title = 'StatusBar';
this.type = 'progressindicator';
this.defaultLocale = locale;
this.i18nInit();
this.render = this.render.bind(this);
this.install = this.install.bind(this);
}
render(state) {
const {
capabilities,
files,
allowNewUpload,
totalProgress,
error,
recoveredState
} = state;
const {
newFiles,
startedFiles,
completeFiles,
isUploadStarted,
isAllComplete,
isAllPaused,
isUploadInProgress,
isSomeGhost
} = this.uppy.getObjectOfFilesPerState();
// 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 resumableUploads = !!capabilities.resumableUploads;
const supportsUploadProgress = capabilities.uploadProgress !== false;
let totalSize = null;
let totalUploadedSize = 0;
// Only if all files have a known size, does it make sense to display a total size
if (startedFiles.every(f => f.progress.bytesTotal != null && f.progress.bytesTotal !== 0)) {
totalSize = 0;
startedFiles.forEach(file => {
totalSize += file.progress.bytesTotal || 0;
totalUploadedSize += file.progress.bytesUploaded || 0;
});
} else {
// however uploaded size we will always have
startedFiles.forEach(file => {
totalUploadedSize += file.progress.bytesUploaded || 0;
});
}
const totalETA = _classPrivateFieldLooseBase(this, _computeSmoothETA)[_computeSmoothETA]({
uploaded: totalUploadedSize,
total: totalSize
});
return StatusBarUI({
error,
uploadState: getUploadingState(error, isAllComplete, recoveredState, state.files || {}),
allowNewUpload,
totalProgress,
totalSize,
totalUploadedSize,
isAllComplete: false,
isAllPaused,
isUploadStarted,
isUploadInProgress,
isSomeGhost,
recoveredState,
complete: completeFiles.length,
newFiles: newFilesOrRecovered.length,
numUploads: startedFiles.length,
totalETA,
files,
i18n: this.i18n,
uppy: this.uppy,
startUpload: this.startUpload,
doneButtonHandler: this.opts.doneButtonHandler,
resumableUploads,
supportsUploadProgress,
showProgressDetails: this.opts.showProgressDetails,
hideUploadButton: this.opts.hideUploadButton,
hideRetryButton: this.opts.hideRetryButton,
hidePauseResumeButton: this.opts.hidePauseResumeButton,
hideCancelButton: this.opts.hideCancelButton,
hideAfterFinish: this.opts.hideAfterFinish
});
}
onMount() {
// Set the text direction if the page has not defined one.
const element = this.el;
const direction = getTextDirection(element);
if (!direction) {
element.dir = 'ltr';
}
}
install() {
const {
target
} = this.opts;
if (target) {
this.mount(target, this);
}
this.uppy.on('upload', _classPrivateFieldLooseBase(this, _onUploadStart)[_onUploadStart]);
// To cover the use case where the status bar is installed while the upload
// has started, we set `lastUpdateTime` right away.
_classPrivateFieldLooseBase(this, _lastUpdateTime)[_lastUpdateTime] = performance.now();
_classPrivateFieldLooseBase(this, _previousUploadedBytes)[_previousUploadedBytes] = this.uppy.getFiles().reduce((pv, file) => pv + file.progress.bytesUploaded, 0);
}
uninstall() {
this.unmount();
this.uppy.off('upload', _classPrivateFieldLooseBase(this, _onUploadStart)[_onUploadStart]);
}
}
function _computeSmoothETA2(totalBytes) {
var _classPrivateFieldLoo, _classPrivateFieldLoo2;
if (totalBytes.total == null || totalBytes.total === 0) {
return null;
}
const remaining = totalBytes.total - totalBytes.uploaded;
if (remaining <= 0) {
return null;
}
// When state is restored, lastUpdateTime is still nullish at this point.
(_classPrivateFieldLoo2 = (_classPrivateFieldLoo = _classPrivateFieldLooseBase(this, _lastUpdateTime))[_lastUpdateTime]) != null ? _classPrivateFieldLoo2 : _classPrivateFieldLoo[_lastUpdateTime] = performance.now();
const dt = performance.now() - _classPrivateFieldLooseBase(this, _lastUpdateTime)[_lastUpdateTime];
if (dt === 0) {
var _classPrivateFieldLoo3;
return Math.round(((_classPrivateFieldLoo3 = _classPrivateFieldLooseBase(this, _previousETA)[_previousETA]) != null ? _classPrivateFieldLoo3 : 0) / 100) / 10;
}
const uploadedBytesSinceLastTick = totalBytes.uploaded - _classPrivateFieldLooseBase(this, _previousUploadedBytes)[_previousUploadedBytes];
_classPrivateFieldLooseBase(this, _previousUploadedBytes)[_previousUploadedBytes] = totalBytes.uploaded;
// uploadedBytesSinceLastTick can be negative in some cases (packet loss?)
// in which case, we wait for next tick to update ETA.
if (uploadedBytesSinceLastTick <= 0) {
var _classPrivateFieldLoo4;
return Math.round(((_classPrivateFieldLoo4 = _classPrivateFieldLooseBase(this, _previousETA)[_previousETA]) != null ? _classPrivateFieldLoo4 : 0) / 100) / 10;
}
const currentSpeed = uploadedBytesSinceLastTick / dt;
const filteredSpeed = _classPrivateFieldLooseBase(this, _previousSpeed)[_previousSpeed] == null ? currentSpeed : emaFilter(currentSpeed, _classPrivateFieldLooseBase(this, _previousSpeed)[_previousSpeed], speedFilterHalfLife, dt);
_classPrivateFieldLooseBase(this, _previousSpeed)[_previousSpeed] = filteredSpeed;
const instantETA = remaining / filteredSpeed;
const updatedPreviousETA = Math.max(_classPrivateFieldLooseBase(this, _previousETA)[_previousETA] - dt, 0);
const filteredETA = _classPrivateFieldLooseBase(this, _previousETA)[_previousETA] == null ? instantETA : emaFilter(instantETA, updatedPreviousETA, ETAFilterHalfLife, dt);
_classPrivateFieldLooseBase(this, _previousETA)[_previousETA] = filteredETA;
_classPrivateFieldLooseBase(this, _lastUpdateTime)[_lastUpdateTime] = performance.now();
return Math.round(filteredETA / 100) / 10;
}
StatusBar.VERSION = packageJson.version;