Merge @uppy/status-bar into @uppy/dashboard (#5825)

This pull request removes the `@uppy/status-bar` plugin and integrates
it directly into the `@uppy/dashboard` plugin.

### Breakdown of the merge

- The `StatusBar` class was refactored from a `UIPlugin` into a Preact
Class component.
- The `locale` strings from status-bar were merged into dashboard's
locale file.
- The Dashboard plugin now integrates the `StatusBar` component
directly, controlling its visibility and passing down all props ( i.e.
options that were specific to StatusBar (like showProgressDetails).
- The standalone StatusBar wrappers for React , Vue , svelte , Angular
were removed.
- every reference to the @uppy/status-bar package from the monorepo
(including in package.json and tsconfig.json files).
- fixed failing tests and removed redundant tests.

---------

Co-authored-by: Mikael Finstad <finstaden@gmail.com>
Co-authored-by: Merlijn Vos <merlijn@soverin.net>
This commit is contained in:
Prakash 2025-08-05 16:47:29 +05:30 committed by GitHub
parent c5b51f6158
commit e8692434d6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
58 changed files with 483 additions and 982 deletions

View file

@ -0,0 +1,48 @@
---
"@uppy/dashboard": major
---
### Merge @uppy/status-bar into @uppy/dashboard
The `@uppy/status-bar` package has been merged into `@uppy/dashboard`. The plugin gave a false promise of flexibility as a standalone plugin but was always built tightly coupled for `@uppy/dashboard`. With the new headless components and hooks, we want go all in those components and remove the confusing, inflexible ones.
StatusBar is now rendered as an integrated component within Dashboard rather than as a separate plugin. The standalone `@uppy/status-bar` package is no longer maintained and should be removed from your dependencies.
#### Migration from standalone StatusBar to Dashboard
If you were using StatusBar as a separate plugin, you'll need to migrate to using Dashboard with the equivalent options.
**Before:**
```javascript
import StatusBar from '@uppy/status-bar'
uppy.use(StatusBar, {
target: '#status-bar',
showProgressDetails: true,
hideUploadButton: false,
hideAfterFinish: true
})
```
**Now:**
```javascript
import Dashboard from '@uppy/dashboard'
uppy.use(Dashboard, {
target: '#dashboard',
hideProgressDetails: false,
hideUploadButton: false,
hideAfterFinish: true
})
```
All StatusBar configuration options are now available directly as Dashboard options:
- `hideProgressDetails` - Hide detailed progress information (previously `showProgressDetails` with inverted logic)
- `hideUploadButton` - Hide the upload button
- `hideAfterFinish` - Hide status bar after upload completion
- `hideRetryButton` - Hide the retry button
- `hidePauseResumeButton` - Hide pause/resume controls
- `hideCancelButton` - Hide the cancel button
- `doneButtonHandler` - Custom handler for the done button

View file

@ -440,7 +440,6 @@ Your `package.json` should resemble something like this:
"@uppy/dashboard": "workspace:^",
"@uppy/drag-drop": "workspace:^",
"@uppy/progress-bar": "workspace:^",
"@uppy/status-bar": "workspace:^",
"@uppy/utils": "workspace:^",
"prop-types": "^15.6.1"
},

43
.github/MIGRATION.md vendored
View file

@ -18,6 +18,7 @@ This is a temporary file that can be updated with any pending migration changes,
`logout()`, `thumbnail()`. Please use: `providerUserSession`.`accessToken`
instead.
### @uppy/informer merged into @uppy/dashboard
The `@uppy/informer` plugin has been merged into `@uppy/dashboard` to reduce bundle size and improve maintainability. The `@uppy/informer` package is no longer maintained as a standalone package and should be removed from your dependencies.
@ -52,7 +53,7 @@ uppy.on('progress', (progress) => {
**Migration steps:**
1. Remove `@uppy/progress-bar` from your dependencies
2. Create a custom progress indicator using Uppy's `progress` or `upload-progress` events
2. Create a custom progress indicator using Uppy's `progress` or `upload-progress` events.
3. Style your progress bar according to your design system.
### @uppy/drag-drop and @uppy/file-input removed
@ -134,4 +135,44 @@ import { Dashboard, StatusBar } from '@uppy/react'
```javascript
import Dashboard from '@uppy/react/dashboard'
import StatusBar from '@uppy/react/status-bar'
```
### @uppy/status-bar merged into @uppy/dashboard
The `@uppy/status-bar` package has been merged into `@uppy/dashboard`. The plugin gave a false promise of flexibility as a standalone plugin but was always built tightly coupled for `@uppy/dashboard`. With the new headless components and hooks, we want go all in those components and remove the confusing, inflexible ones.
**Migration steps:**
1. Remove `@uppy/status-bar` from your dependencies
2. Replace StatusBar usage with Dashboard
3. Move all StatusBar options directly to Dashboard options
All StatusBar configuration options are now available as Dashboard options:
- `hideProgressDetails` - Hide detailed progress information
- `hideUploadButton` - Hide the upload button
- `hideAfterFinish` - Hide status bar after upload completion
- `hideRetryButton` - Hide the retry button
- `hidePauseResumeButton` - Hide pause/resume controls
- `hideCancelButton` - Hide the cancel button
- `doneButtonHandler` - Custom handler for the done button
```js
// Before - separate StatusBar plugin
import StatusBar from '@uppy/status-bar'
uppy.use(StatusBar, {
target: '#status-bar',
hideProgressDetails: true,
hideUploadButton: false,
hideAfterFinish: true
})
// After - use Dashboard with StatusBar options
import Dashboard from '@uppy/dashboard'
uppy.use(Dashboard, {
target: '#dashboard',
hideProgressDetails: false,
hideUploadButton: false,
hideAfterFinish: true
})
```

View file

@ -29,7 +29,6 @@
"@angular/core": "^17.0.0 || ^18.0.0 || ^19.0.0",
"@uppy/core": "workspace:^",
"@uppy/dashboard": "workspace:^",
"@uppy/status-bar": "workspace:^",
"@uppy/utils": "workspace:^"
},
"sideEffects": false

View file

@ -1,30 +0,0 @@
import { ChangeDetectionStrategy, Component, type OnInit } from "@angular/core";
import { Uppy } from "@uppy/core";
import FileInput from "@uppy/file-input";
import type * as StatusBar from "@uppy/status-bar";
import Tus from "@uppy/tus";
import type { Body, Meta } from "@uppy/utils/lib/UppyFile";
@Component({
selector: "uppy-status-bar-demo",
template: `
<div class="UppyInput"></div>
<uppy-status-bar [uppy]="uppy" [props]="props"></uppy-status-bar>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class StatusBarDemoComponent<M extends Meta, B extends Body>
implements OnInit
{
uppy: Uppy<M, B> = new Uppy({ debug: true, autoProceed: true });
props: StatusBar.StatusBarOptions = {
hideUploadButton: true,
hideAfterFinish: false,
};
ngOnInit(): void {
this.uppy
.use(FileInput, { target: ".UppyInput", pretty: false })
.use(Tus, { endpoint: "https://master.tus.io/files/" });
}
}

View file

@ -1,24 +0,0 @@
import { async, type ComponentFixture, TestBed } from "@angular/core/testing";
import { StatusBarComponent } from "./status-bar.component";
describe("StatusBarComponent", () => {
let component: StatusBarComponent;
let fixture: ComponentFixture<StatusBarComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [StatusBarComponent],
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(StatusBarComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it("should create", () => {
expect(component).toBeTruthy();
});
});

View file

@ -1,52 +0,0 @@
import {
ChangeDetectionStrategy,
Component,
ElementRef,
Input,
inject,
type OnChanges,
type OnDestroy,
type SimpleChanges,
} from "@angular/core";
import { Uppy } from "@uppy/core";
import type { StatusBarOptions } from "@uppy/status-bar";
import StatusBar from "@uppy/status-bar";
import type { Body, Meta } from "@uppy/utils/lib/UppyFile";
import { UppyAngularWrapper } from "../../utils/wrapper";
@Component({
selector: "uppy-status-bar",
template: "",
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class StatusBarComponent<M extends Meta, B extends Body>
extends UppyAngularWrapper<M, B, StatusBarOptions>
implements OnDestroy, OnChanges
{
el = inject(ElementRef);
@Input() uppy: Uppy<M, B> = new Uppy();
@Input() props: StatusBarOptions = {};
/** Inserted by Angular inject() migration for backwards compatibility */
constructor(...args: unknown[]);
constructor() {
super();
}
ngOnInit() {
this.onMount(
{ id: "angular:StatusBar", target: this.el.nativeElement },
StatusBar,
);
}
ngOnChanges(changes: SimpleChanges): void {
this.handleChanges(changes, StatusBar);
}
ngOnDestroy(): void {
this.uninstall();
}
}

View file

@ -1,15 +0,0 @@
import { moduleMetadata } from "@storybook/angular";
import { StatusBarDemoComponent } from "./status-bar-demo.component";
export default {
title: "Status Bar",
decorators: [
moduleMetadata({
declarations: [StatusBarDemoComponent],
}),
],
};
export const Default = () => ({
component: StatusBarDemoComponent,
});

View file

@ -1,6 +1,6 @@
import type { ElementRef, SimpleChanges } from "@angular/core";
import type { UIPlugin, UIPluginOptions, Uppy } from "@uppy/core";
import type { StatusBarOptions } from "@uppy/status-bar";
import type { DashboardOptions } from "@uppy/dashboard";
import type { Body, Meta } from "@uppy/utils/lib/UppyFile";
export abstract class UppyAngularWrapper<
@ -9,7 +9,7 @@ export abstract class UppyAngularWrapper<
Opts extends UIPluginOptions,
PluginType extends UIPlugin<Opts, M, B> = UIPlugin<Opts, M, B>,
> {
abstract props: StatusBarOptions;
abstract props: DashboardOptions<M, B>;
abstract el: ElementRef;
abstract uppy: Uppy<M, B>;
private options: any;

View file

@ -4,4 +4,3 @@
export { DashboardComponent } from './lib/components/dashboard/dashboard.component'
export { DashboardModalComponent } from './lib/components/dashboard-modal/dashboard-modal.component'
export { StatusBarComponent } from './lib/components/status-bar/status-bar.component'

View file

@ -5,7 +5,6 @@
"dependsOn": [
"@uppy/core#build",
"@uppy/dashboard#build",
"@uppy/status-bar#build",
"@uppy/utils#build"
],
"inputs": [

View file

@ -40,7 +40,6 @@
"dependencies": {
"@transloadit/prettier-bytes": "^0.3.4",
"@uppy/provider-views": "workspace:^",
"@uppy/status-bar": "workspace:^",
"@uppy/thumbnail-generator": "workspace:^",
"@uppy/utils": "workspace:^",
"classnames": "^2.2.6",
@ -52,7 +51,6 @@
"devDependencies": {
"@uppy/core": "workspace:^",
"@uppy/google-drive": "workspace:^",
"@uppy/status-bar": "workspace:^",
"@uppy/url": "workspace:^",
"@uppy/webcam": "workspace:^",
"@vitest/browser": "^3.2.4",

View file

@ -11,8 +11,6 @@ import type {
} from '@uppy/core'
import { UIPlugin } from '@uppy/core'
import { defaultPickerIcon } from '@uppy/provider-views'
import StatusBar from '@uppy/status-bar'
import type StatusBarLocale from '@uppy/status-bar/lib/locale.js'
import ThumbnailGenerator from '@uppy/thumbnail-generator'
import findAllDOMElements from '@uppy/utils/lib/findAllDOMElements'
import getDroppedFiles from '@uppy/utils/lib/getDroppedFiles'
@ -160,7 +158,7 @@ interface DashboardMiscOptions<M extends Meta, B extends Body>
showLinkToFileUploadResult?: boolean
showNativePhotoCameraButton?: boolean
showNativeVideoCameraButton?: boolean
showProgressDetails?: boolean
hideProgressDetails?: boolean
showRemoveButtonAfterComplete?: boolean
showSelectedFiles?: boolean
singleFileFullScreen?: boolean
@ -170,7 +168,7 @@ interface DashboardMiscOptions<M extends Meta, B extends Body>
thumbnailWidth?: number
trigger?: string | Element | null
waitForThumbnailsBeforeUpload?: boolean
locale?: LocaleStrings<typeof locale> & typeof StatusBarLocale
locale?: LocaleStrings<typeof locale>
}
export type DashboardOptions<
@ -187,7 +185,7 @@ const defaultOptions = {
waitForThumbnailsBeforeUpload: false,
defaultPickerIcon,
showLinkToFileUploadResult: false,
showProgressDetails: false,
hideProgressDetails: false,
hideUploadButton: false,
hideCancelButton: false,
hideRetryButton: false,
@ -1239,9 +1237,6 @@ export default class Dashboard<M extends Meta, B extends Body> extends UIPlugin<
showLinkToFileUploadResult: this.opts.showLinkToFileUploadResult,
fileManagerSelectionType: this.opts.fileManagerSelectionType,
proudlyDisplayPoweredByUppy: this.opts.proudlyDisplayPoweredByUppy,
hideCancelButton: this.opts.hideCancelButton,
hideRetryButton: this.opts.hideRetryButton,
hidePauseResumeButton: this.opts.hidePauseResumeButton,
showRemoveButtonAfterComplete: this.opts.showRemoveButtonAfterComplete,
containerWidth: pluginState.containerWidth,
containerHeight: pluginState.containerHeight,
@ -1265,6 +1260,15 @@ export default class Dashboard<M extends Meta, B extends Body> extends UIPlugin<
handleDrop: this.handleDrop,
// informer props
disableInformer: this.opts.disableInformer,
// status-bar props
disableStatusBar: this.opts.disableStatusBar,
hideProgressDetails: this.opts.hideProgressDetails,
hideUploadButton: this.opts.hideUploadButton,
hideRetryButton: this.opts.hideRetryButton,
hidePauseResumeButton: this.opts.hidePauseResumeButton,
hideCancelButton: this.opts.hideCancelButton,
hideProgressAfterFinish: this.opts.hideProgressAfterFinish,
doneButtonHandler: this.opts.doneButtonHandler,
})
}
@ -1302,29 +1306,6 @@ export default class Dashboard<M extends Meta, B extends Body> extends UIPlugin<
}
}
#getStatusBarOpts() {
const {
hideUploadButton,
hideRetryButton,
hidePauseResumeButton,
hideCancelButton,
showProgressDetails,
hideProgressAfterFinish,
locale: l,
doneButtonHandler,
} = this.opts
return {
hideUploadButton,
hideRetryButton,
hidePauseResumeButton,
hideCancelButton,
showProgressDetails,
hideAfterFinish: hideProgressAfterFinish,
locale: l,
doneButtonHandler,
}
}
#getThumbnailGeneratorOpts() {
const {
thumbnailWidth,
@ -1344,19 +1325,11 @@ export default class Dashboard<M extends Meta, B extends Body> extends UIPlugin<
setOptions(opts: Partial<DashboardOptions<M, B>>) {
super.setOptions(opts)
this.uppy
.getPlugin(this.#getStatusBarId())
?.setOptions(this.#getStatusBarOpts())
this.uppy
.getPlugin(this.#getThumbnailGeneratorId())
?.setOptions(this.#getThumbnailGeneratorOpts())
}
#getStatusBarId() {
return `${this.id}:StatusBar`
}
#getThumbnailGeneratorId() {
return `${this.id}:ThumbnailGenerator`
}
@ -1401,14 +1374,6 @@ export default class Dashboard<M extends Meta, B extends Body> extends UIPlugin<
this.mount(target, this)
}
if (!this.opts.disableStatusBar) {
this.uppy.use(StatusBar, {
id: this.#getStatusBarId(),
target: this,
...this.#getStatusBarOpts(),
})
}
if (!this.opts.disableThumbnailGenerator) {
this.uppy.use(ThumbnailGenerator, {
id: this.#getThumbnailGeneratorId(),
@ -1440,11 +1405,6 @@ export default class Dashboard<M extends Meta, B extends Body> extends UIPlugin<
}
uninstall = (): void => {
if (!this.opts.disableStatusBar) {
const statusBar = this.uppy.getPlugin(`${this.id}:StatusBar`)
if (statusBar) this.uppy.removePlugin(statusBar)
}
if (!this.opts.disableThumbnailGenerator) {
const thumbnail = this.uppy.getPlugin(`${this.id}:ThumbnailGenerator`)
if (thumbnail) this.uppy.removePlugin(thumbnail)

View file

@ -23,6 +23,7 @@ import Informer from './Informer/Informer.js'
import PickerPanelContent from './PickerPanelContent.js'
import PanelTopBar from './PickerPanelTopBar.js'
import Slide from './Slide.js'
import StatusBar from './StatusBar/StatusBar.js'
// http://dev.edenspiekermann.com/2016/02/11/introducing-accessible-modal-dialog
// https://github.com/ghosh/micromodal
@ -121,6 +122,11 @@ type DashboardUIProps<M extends Meta, B extends Body> = {
handleDragLeave: (event: DragEvent) => void
handleDrop: (event: DragEvent) => void
disableInformer: boolean
disableStatusBar: boolean
hideProgressDetails: boolean
hideUploadButton: boolean
hideProgressAfterFinish: boolean
doneButtonHandler: (() => void) | null
}
export default function Dashboard<M extends Meta, B extends Body>(
@ -336,6 +342,20 @@ export default function Dashboard<M extends Meta, B extends Body>(
</Slide>
<div className="uppy-Dashboard-progressindicators">
{!props.disableInformer && <Informer uppy={props.uppy} />}
{!props.disableStatusBar && (
<StatusBar
uppy={props.uppy}
i18n={props.i18n}
hideProgressDetails={props.hideProgressDetails}
hideUploadButton={props.hideUploadButton}
hideRetryButton={props.hideRetryButton}
hidePauseResumeButton={props.hidePauseResumeButton}
hideCancelButton={props.hideCancelButton}
hideAfterFinish={props.hideProgressAfterFinish}
doneButtonHandler={props.doneButtonHandler}
/>
)}
{!props.disableInformer && <Informer uppy={props.uppy} />}
{props.progressindicators.map((target: TargetWithRender) => {
// TODO

View file

@ -72,7 +72,10 @@ export default function FileCard(props: $TSFixMe) {
form.addEventListener('submit', handleSave)
return () => {
form.removeEventListener('submit', handleSave)
document.body.removeChild(form)
// check if form is still in the DOM before removing
if (form.parentNode) {
document.body.removeChild(form)
}
}
}, [form, handleSave])

View file

@ -360,7 +360,7 @@ interface ProgressBarUploadingProps {
i18n: I18n
supportsUploadProgress: boolean
totalProgress: number
showProgressDetails: boolean | undefined
hideProgressDetails: boolean | undefined
isUploadStarted: boolean
isAllComplete: boolean
isAllPaused: boolean
@ -378,7 +378,7 @@ function ProgressBarUploading(props: ProgressBarUploadingProps) {
i18n,
supportsUploadProgress,
totalProgress,
showProgressDetails,
hideProgressDetails,
isUploadStarted,
isAllComplete,
isAllPaused,
@ -400,7 +400,7 @@ function ProgressBarUploading(props: ProgressBarUploadingProps) {
const title = isAllPaused ? i18n('paused') : i18n('uploading')
function renderProgressDetails() {
if (!isAllPaused && !showUploadNewlyAddedFiles && showProgressDetails) {
if (!isAllPaused && !showUploadNewlyAddedFiles && !hideProgressDetails) {
if (supportsUploadProgress) {
return (
<ProgressDetails

View file

@ -1,18 +1,8 @@
import type {
Body,
DefinePluginOpts,
Meta,
State,
Uppy,
UppyFile,
} from '@uppy/core'
import { UIPlugin } from '@uppy/core'
import type { Body, Meta, Uppy, UppyFile } from '@uppy/core'
import emaFilter from '@uppy/utils/lib/emaFilter'
import getTextDirection from '@uppy/utils/lib/getTextDirection'
import type { I18n } from '@uppy/utils/lib/Translator'
import type { ComponentChild } from 'preact'
import { h } from 'preact'
import packageJson from '../package.json' with { type: 'json' }
import locale from './locale.js'
import { Component, h } from 'preact'
import type { StatusBarOptions } from './StatusBarOptions.js'
import statusBarStates from './StatusBarStates.js'
import StatusBarUI, { type StatusBarUIProps } from './StatusBarUI.js'
@ -20,6 +10,18 @@ import StatusBarUI, { type StatusBarUIProps } from './StatusBarUI.js'
const speedFilterHalfLife = 2000
const ETAFilterHalfLife = 2000
type StatusBarProps<M extends Meta, B extends Body> = {
uppy: Uppy<M, B>
hideProgressDetails: boolean
hideUploadButton: boolean
hideRetryButton: boolean
hidePauseResumeButton: boolean
hideCancelButton: boolean
hideAfterFinish: boolean
doneButtonHandler: (() => void) | null
i18n: I18n
}
function getUploadingState(
error: unknown,
isAllComplete: boolean,
@ -66,22 +68,15 @@ const defaultOptions = {
hideRetryButton: false,
hidePauseResumeButton: false,
hideCancelButton: false,
showProgressDetails: false,
hideProgressDetails: false,
hideAfterFinish: true,
doneButtonHandler: null,
} satisfies StatusBarOptions
/**
* StatusBar: renders a status bar with upload/pause/resume/cancel/retry buttons,
* progress percentage and time remaining.
*/
export default class StatusBar<M extends Meta, B extends Body> extends UIPlugin<
DefinePluginOpts<StatusBarOptions, keyof typeof defaultOptions>,
M,
B
> {
static VERSION = packageJson.version
export default class StatusBar<
M extends Meta,
B extends Body,
> extends Component<StatusBarProps<M, B>> {
#lastUpdateTime!: ReturnType<typeof performance.now>
#previousUploadedBytes!: number | null
@ -90,24 +85,46 @@ export default class StatusBar<M extends Meta, B extends Body> extends UIPlugin<
#previousETA!: number | null
constructor(uppy: Uppy<M, B>, opts?: StatusBarOptions) {
super(uppy, { ...defaultOptions, ...opts })
this.id = this.opts.id || 'StatusBar'
this.title = 'StatusBar'
this.type = 'progressindicator'
componentDidMount(): void {
// Initialize ETA calculation variables
this.#lastUpdateTime = performance.now()
this.#previousUploadedBytes = this.props.uppy
.getFiles()
.reduce((pv, file) => pv + (file.progress.bytesUploaded || 0), 0)
this.defaultLocale = locale
// Listen for upload start to reset ETA calculation
this.props.uppy.on('upload', this.#onUploadStart)
}
this.i18nInit()
componentWillUnmount(): void {
this.props.uppy.off('upload', this.#onUploadStart)
}
this.render = this.render.bind(this)
this.install = this.install.bind(this)
#onUploadStart = (): void => {
const { recoveredState } = this.props.uppy.getState()
this.#previousSpeed = null
this.#previousETA = null
if (recoveredState) {
this.#previousUploadedBytes = Object.values(recoveredState.files).reduce(
(pv, { progress }) => pv + (progress.bytesUploaded || 0),
0,
)
// We don't set `#lastUpdateTime` at this point because the upload won't
// actually resume until the user asks for it.
this.props.uppy.emit('restore-confirmed')
return
}
this.#lastUpdateTime = performance.now()
this.#previousUploadedBytes = 0
}
#computeSmoothETA(totalBytes: {
uploaded: number
total: number | null // null means indeterminate
}) {
}): number | null {
if (totalBytes.total == null || totalBytes.total === 0) {
return null
}
@ -124,8 +141,14 @@ export default class StatusBar<M extends Meta, B extends Body> extends UIPlugin<
return Math.round((this.#previousETA ?? 0) / 100) / 10
}
// Initialize previousUploadedBytes if it's null
if (this.#previousUploadedBytes == null) {
this.#previousUploadedBytes = totalBytes.uploaded
return null // Can't calculate speed on first call
}
const uploadedBytesSinceLastTick =
totalBytes.uploaded - this.#previousUploadedBytes!
totalBytes.uploaded - this.#previousUploadedBytes
this.#previousUploadedBytes = totalBytes.uploaded
// uploadedBytesSinceLastTick can be negative in some cases (packet loss?)
@ -134,18 +157,41 @@ export default class StatusBar<M extends Meta, B extends Body> extends UIPlugin<
return Math.round((this.#previousETA ?? 0) / 100) / 10
}
const currentSpeed = uploadedBytesSinceLastTick / dt
// Guard against invalid speed values
if (!Number.isFinite(currentSpeed) || currentSpeed <= 0) {
return null
}
const filteredSpeed =
this.#previousSpeed == null
? currentSpeed
: emaFilter(currentSpeed, this.#previousSpeed, speedFilterHalfLife, dt)
// Guard against invalid filtered speed
if (!Number.isFinite(filteredSpeed) || filteredSpeed <= 0) {
return null
}
this.#previousSpeed = filteredSpeed
const instantETA = remaining / filteredSpeed
const updatedPreviousETA = Math.max(this.#previousETA! - dt, 0)
// Guard against invalid instantETA
if (!Number.isFinite(instantETA) || instantETA < 0) {
return null
}
const updatedPreviousETA = Math.max((this.#previousETA ?? 0) - dt, 0)
const filteredETA =
this.#previousETA == null
? instantETA
: emaFilter(instantETA, updatedPreviousETA, ETAFilterHalfLife, dt)
// Guard against invalid filteredETA
if (!Number.isFinite(filteredETA) || filteredETA < 0) {
return null
}
this.#previousETA = filteredETA
this.#lastUpdateTime = performance.now()
@ -153,12 +199,12 @@ export default class StatusBar<M extends Meta, B extends Body> extends UIPlugin<
}
startUpload = (): ReturnType<Uppy<M, B>['upload']> => {
return this.uppy.upload().catch((() => {
return this.props.uppy.upload().catch((() => {
// Error logged in Core
}) as () => undefined)
}
render(state: State<M, B>): ComponentChild {
render(): ComponentChild {
const {
capabilities,
files,
@ -166,23 +212,19 @@ export default class StatusBar<M extends Meta, B extends Body> extends UIPlugin<
totalProgress,
error,
recoveredState,
} = state
} = this.props.uppy.getState()
const {
newFiles,
startedFiles,
completeFiles,
isUploadStarted,
isAllComplete,
isAllPaused,
isUploadInProgress,
isSomeGhost,
} = this.uppy.getObjectOfFilesPerState()
} = this.props.uppy.getObjectOfFilesPerState()
// If some state was recovered, we want to show Upload button/counter
// for all the files, because in this case its not an Upload button,
// but “Confirm Restore Button”
const newFilesOrRecovered = recoveredState ? Object.values(files) : newFiles
const resumableUploads = !!capabilities.resumableUploads
const supportsUploadProgress = capabilities.uploadProgress !== false
@ -213,91 +255,43 @@ export default class StatusBar<M extends Meta, B extends Body> extends UIPlugin<
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(): void {
// Set the text direction if the page has not defined one.
const element = this.el!
const direction = getTextDirection(element)
if (!direction) {
element.dir = 'ltr'
}
}
#onUploadStart = (): void => {
const { recoveredState } = this.uppy.getState()
this.#previousSpeed = null
this.#previousETA = null
if (recoveredState) {
this.#previousUploadedBytes = Object.values(recoveredState.files).reduce(
(pv, { progress }) => pv + (progress.bytesUploaded as number),
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
}
this.#lastUpdateTime = performance.now()
this.#previousUploadedBytes = 0
}
install(): void {
const { target } = this.opts
if (target) {
this.mount(target, this)
}
this.uppy.on('upload', this.#onUploadStart)
// To cover the use case where the status bar is installed while the upload
// has started, we set `lastUpdateTime` right away.
this.#lastUpdateTime = performance.now()
this.#previousUploadedBytes = this.uppy
.getFiles()
.reduce((pv, file) => pv + (file.progress.bytesUploaded as number), 0)
}
uninstall(): void {
this.unmount()
this.uppy.off('upload', this.#onUploadStart)
return (
<StatusBarUI
error={error}
uploadState={getUploadingState(
error,
isAllComplete,
recoveredState,
files || {},
)}
allowNewUpload={allowNewUpload}
totalProgress={totalProgress}
totalSize={totalSize}
totalUploadedSize={totalUploadedSize}
isAllComplete={isAllComplete}
isAllPaused={isAllPaused}
isUploadStarted={isUploadStarted}
isUploadInProgress={isUploadInProgress}
isSomeGhost={isSomeGhost}
recoveredState={recoveredState}
complete={completeFiles.length}
newFiles={newFilesOrRecovered.length}
numUploads={startedFiles.length}
totalETA={totalETA}
files={files}
i18n={this.props.i18n}
uppy={this.props.uppy}
startUpload={this.startUpload}
doneButtonHandler={this.props.doneButtonHandler}
resumableUploads={resumableUploads}
supportsUploadProgress={supportsUploadProgress}
hideProgressDetails={this.props.hideProgressDetails}
hideUploadButton={this.props.hideUploadButton}
hideRetryButton={this.props.hideRetryButton}
hidePauseResumeButton={this.props.hidePauseResumeButton}
hideCancelButton={this.props.hideCancelButton}
hideAfterFinish={this.props.hideAfterFinish}
/>
)
}
}

View file

@ -1,13 +1,11 @@
import type { UIPluginOptions } from '@uppy/core'
import type StatusBarLocale from './locale.js'
export interface StatusBarOptions extends UIPluginOptions {
showProgressDetails?: boolean
hideProgressDetails?: boolean
hideUploadButton?: boolean
hideAfterFinish?: boolean
hideRetryButton?: boolean
hidePauseResumeButton?: boolean
hideCancelButton?: boolean
doneButtonHandler?: (() => void) | null
locale?: typeof StatusBarLocale
}

View file

@ -49,7 +49,7 @@ export interface StatusBarUIProps<M extends Meta, B extends Body> {
startUpload: () => void
uppy: Uppy<M, B>
isAllComplete: boolean
showProgressDetails?: boolean
hideProgressDetails?: boolean
numUploads: number
complete: number
totalSize: number | null
@ -81,7 +81,7 @@ export default function StatusBarUI<M extends Meta, B extends Body>({
startUpload,
uppy,
isAllComplete,
showProgressDetails = undefined,
hideProgressDetails = undefined,
numUploads,
complete,
totalSize,
@ -192,7 +192,7 @@ export default function StatusBarUI<M extends Meta, B extends Body>({
i18n={i18n}
supportsUploadProgress={supportsUploadProgress}
totalProgress={totalProgress}
showProgressDetails={showProgressDetails}
hideProgressDetails={hideProgressDetails}
isUploadStarted={isUploadStarted}
isAllComplete={isAllComplete}
isAllPaused={isAllPaused}

View file

@ -2,6 +2,8 @@ import Uppy from '@uppy/core'
import { page, userEvent } from '@vitest/browser/context'
import { expect, test } from 'vitest'
import Dashboard from './Dashboard.js'
import '@uppy/core/css/style.css'
import '@uppy/dashboard/css/style.css'
// Normally you would use one of vitest's framework renderers, such as vitest-browser-react,
// but that's overkill for us so we write our own plain HTML renderer.
@ -22,7 +24,9 @@ test('Basic Dashboard functionality works in the browser', async () => {
})
await expect.element(page.getByText('Drop files here')).toBeVisible()
const fileInput = document.getElementsByClassName('uppy-Dashboard-input')[0]
const fileInput = document.querySelector(
'.uppy-Dashboard-input',
) as HTMLInputElement
await userEvent.upload(fileInput, new File(['Hello, World!'], 'test.txt'))
await expect.element(page.getByText('test.txt')).toBeVisible()
await page.getByTitle('Edit file test.txt').click()
@ -31,3 +35,172 @@ test('Basic Dashboard functionality works in the browser', async () => {
await userEvent.fill(licenseInput.element(), 'MIT')
await page.getByText('Save changes').click()
})
test('Upload, pause, and resume functionality', async () => {
render('<div id="uppy"></div>')
let uploadResolve: (() => void) | null = null
let uploadPromise = new Promise<void>((resolve) => {
uploadResolve = resolve
})
let isPaused = false
let progress = 0
const uppy = new Uppy({
// Enable resumable uploads capability for pause/resume functionality
restrictions: { maxNumberOfFiles: 1 },
}).use(Dashboard, {
target: '#uppy',
inline: true,
hideProgressAfterFinish: false, // Keep StatusBar visible after completion
})
uppy.addUploader(async (fileIDs) => {
const files = fileIDs.map((id) => uppy.getFile(id))
// Emit upload-start event
uppy.emit('upload-start', files)
// Set initial progress state for all files
fileIDs.forEach((fileID) => {
const file = uppy.getFile(fileID)
if (file) {
uppy.setFileState(fileID, {
progress: {
...file.progress,
uploadStarted: null,
uploadComplete: false,
percentage: 0,
bytesUploaded: false,
bytesTotal: file.size,
},
})
}
})
// Simulate upload progress with pause/resume support
const progressInterval = setInterval(() => {
fileIDs.forEach((fileID) => {
const file = uppy.getFile(fileID)
if (!file) {
clearInterval(progressInterval)
return
}
// Check if file is paused
if (file.isPaused) {
isPaused = true
return
} else if (isPaused) {
// Resuming from pause
isPaused = false
}
// Only progress if not paused
if (!isPaused && progress < 100) {
progress += 10
// Set uploadStarted on first progress update
const uploadStarted = file.progress.uploadStarted || Date.now()
uppy.setFileState(fileID, {
progress: {
...file.progress,
uploadStarted,
uploadComplete: false,
percentage: progress,
bytesUploaded: (progress / 100) * (file.size || 0),
bytesTotal: file.size || 0,
},
})
// Emit upload-progress event
uppy.emit('upload-progress', file, {
uploadStarted,
bytesUploaded: (progress / 100) * (file.size || 0),
bytesTotal: file.size || 0,
})
if (progress >= 100) {
clearInterval(progressInterval)
// Set final progress state
uppy.setFileState(fileID, {
progress: {
...file.progress,
uploadComplete: true,
percentage: 100,
bytesUploaded: (progress / 100) * (file.size || 0),
bytesTotal: file.size || 0,
} as any, // Type assertion to bypass strict union type checking
})
// Emit upload-success event
uppy.emit('upload-success', file, {
status: 200,
uploadURL: `https://example.com/upload/${file.name}`,
})
uploadResolve?.()
}
}
})
}, 200) // 200ms intervals for controlled testing
return uploadPromise
})
// Set resumable uploads capability
uppy.setState({
capabilities: {
...uppy.getState().capabilities,
resumableUploads: true,
},
})
const fileInput = document.querySelector(
'.uppy-Dashboard-input',
) as HTMLInputElement
await userEvent.upload(
fileInput,
new File(['a'.repeat(50000)], 'test.txt'), // 50KB file
)
// Wait for file to be added and upload button to appear
await expect.element(page.getByText('test.txt')).toBeVisible()
await expect(
page.getByRole('button', { name: 'Upload 1 file' }),
).toBeInTheDocument()
// Start the upload
await page.getByRole('button', { name: 'Upload 1 file' }).click()
// Wait for upload to start
await new Promise((resolve) => setTimeout(resolve, 300))
// Verify upload has started by checking StatusBar state
await expect(page.getByText(/Uploading: \d+%/)).toBeVisible()
// Find and click pause button
const pauseButton = page.getByTitle('Pause', { exact: true })
await expect(pauseButton).toBeVisible()
await pauseButton.click()
// Find and click resume button
const resumeButton = page.getByTitle('Resume', { exact: true })
await expect(resumeButton).toBeVisible()
await resumeButton.click()
// Verify upload has resumed and is progressing
await expect(page.getByText(/Uploading: \d+%/)).toBeVisible()
// Wait for upload to complete - increase timeout
await uploadPromise
// Add a longer delay to allow StatusBar to render final state
await new Promise((resolve) => setTimeout(resolve, 500))
// Verify upload completion state using Playwright selector
await expect(page.getByText('Complete', { exact: true })).toBeVisible()
})

View file

@ -1,6 +1,5 @@
import Core, { type UIPlugin } from '@uppy/core'
import GoogleDrivePlugin from '@uppy/google-drive'
import StatusBarPlugin from '@uppy/status-bar'
// @ts-ignore untyped
import Url from '@uppy/url'
// @ts-ignore untyped
@ -22,17 +21,6 @@ describe('Dashboard', () => {
delete globalThis.ResizeObserver
})
it('can safely be added together with the StatusBar without id conflicts', () => {
const core = new Core()
core.use(StatusBarPlugin)
expect(() => {
core.use(DashboardPlugin, { inline: false })
}).not.toThrow()
core.destroy()
})
it('works without any remote provider plugins', () => {
const core = new Core()
@ -87,7 +75,7 @@ describe('Dashboard', () => {
.targets as UIPlugin<any, any, any>[]
// two built-in plugins + these ones below
expect(dashboardPlugins.length).toEqual(3)
expect(dashboardPlugins.length).toEqual(2)
expect(dashboardPlugins.some((plugin) => plugin.id === 'Url')).toEqual(true)
expect(dashboardPlugins.some((plugin) => plugin.id === 'Webcam')).toEqual(
true,
@ -107,7 +95,7 @@ describe('Dashboard', () => {
.targets as UIPlugin<any, any, any>[]
// two built-in plugins + these ones below
expect(dashboardPlugins.length).toEqual(2)
expect(dashboardPlugins.length).toEqual(1)
expect(dashboardPlugins.some((plugin) => plugin.id === 'Url')).toEqual(true)
expect(dashboardPlugins.some((plugin) => plugin.id === 'Webcam')).toEqual(
false,

View file

@ -90,5 +90,50 @@ export default {
// Used for native device camera buttons on mobile
takePictureBtn: 'Take Picture',
recordVideoBtn: 'Record Video',
// Strings for StatusBar
// Shown in the status bar while files are being uploaded.
uploading: 'Uploading',
// Shown in the status bar once all files have been uploaded.
complete: 'Complete',
// Shown in the status bar if an upload failed.
uploadFailed: 'Upload failed',
// Shown in the status bar while the upload is paused.
paused: 'Paused',
// Used as the label for the button that retries an upload.
retry: 'Retry',
// Used as the label for the button that pauses an upload.
pause: 'Pause',
// Used as the label for the button that resumes an upload.
resume: 'Resume',
// Used as the label for the button that resets the upload state after an upload
done: 'Done',
// When `hideProgressDetails` is set to false, shows the number of files that have been fully uploaded so far.
filesUploadedOfTotal: {
0: '%{complete} of %{smart_count} file uploaded',
1: '%{complete} of %{smart_count} files uploaded',
},
// When `hideProgressDetails` is set to false, shows the amount of bytes that have been uploaded so far.
dataUploadedOfTotal: '%{complete} of %{total}',
dataUploadedOfUnknown: '%{complete} of unknown',
// When `hideProgressDetails` is set to false, shows an estimation of how long the upload will take to complete.
xTimeLeft: '%{time} left',
// Used as the label for the button that starts an upload.
uploadXFiles: {
0: 'Upload %{smart_count} file',
1: 'Upload %{smart_count} files',
},
// Used as the label for the button that starts an upload, if another upload has been started in the past
// and new files were added later.
uploadXNewFiles: {
0: 'Upload +%{smart_count} file',
1: 'Upload +%{smart_count} files',
},
upload: 'Upload',
xMoreFilesAdded: {
0: '%{smart_count} more file added',
1: '%{smart_count} more files added',
},
showErrorDetails: 'Show error details',
},
}

View file

@ -1,13 +1,13 @@
@use "sass:color";
@use '@uppy/core/src/_utils.scss';
@use '@uppy/core/src/_variables.scss';
@use '@uppy/status-bar/src/style.scss' as style2;
@use '@uppy/provider-views/src/style.scss' as style3;
// Component-specific css imports
@use 'components/FileItem/index.scss';
@use 'components/FileCard/index.scss' as index2;
@use 'components/Informer/style.scss';
@use 'components/StatusBar/style.scss' as style2;
// Transitions //

View file

@ -10,9 +10,6 @@
{
"path": "../provider-views/tsconfig.build.json"
},
{
"path": "../status-bar/tsconfig.build.json"
},
{
"path": "../thumbnail-generator/tsconfig.build.json"
},

View file

@ -9,9 +9,6 @@
{
"path": "../provider-views/tsconfig.build.json"
},
{
"path": "../status-bar/tsconfig.build.json"
},
{
"path": "../thumbnail-generator/tsconfig.build.json"
},

View file

@ -23,7 +23,6 @@
"@uppy/dropbox#build",
"@uppy/webdav#build",
"@uppy/unsplash#build",
"@uppy/status-bar#build",
"@uppy/webcam#build",
"@uppy/instagram#build"
],

View file

@ -1,88 +0,0 @@
import type { Body, Meta, UnknownPlugin, Uppy } from '@uppy/core'
import StatusBarPlugin, { type StatusBarOptions } from '@uppy/status-bar'
import { Component, createElement as h } from 'react'
import getHTMLProps from './getHTMLProps.js'
import nonHtmlPropsHaveChanged from './nonHtmlPropsHaveChanged.js'
interface StatusBarProps<M extends Meta, B extends Body>
extends StatusBarOptions {
uppy: Uppy<M, B>
}
/**
* React component that renders a status bar containing upload progress and speed,
* processing progress and pause/resume/cancel controls.
*/
class StatusBar<M extends Meta, B extends Body> extends Component<
StatusBarProps<M, B>
> {
private container!: HTMLElement
private plugin!: UnknownPlugin<M, B>
componentDidMount(): void {
this.installPlugin()
}
componentDidUpdate(prevProps: StatusBar<M, B>['props']): void {
if (prevProps.uppy !== this.props.uppy) {
this.uninstallPlugin(prevProps)
this.installPlugin()
} else if (nonHtmlPropsHaveChanged(this.props, prevProps)) {
const { uppy, ...options } = { ...this.props, target: this.container }
this.plugin.setOptions(options)
}
}
componentWillUnmount(): void {
this.uninstallPlugin()
}
installPlugin(): void {
const {
uppy,
hideUploadButton,
hideRetryButton,
hidePauseResumeButton,
hideCancelButton,
showProgressDetails,
hideAfterFinish,
doneButtonHandler,
id,
} = this.props
const options = {
id: id || 'StatusBar',
hideUploadButton,
hideRetryButton,
hidePauseResumeButton,
hideCancelButton,
showProgressDetails,
hideAfterFinish,
doneButtonHandler,
target: this.container,
}
uppy.use(StatusBarPlugin, options)
this.plugin = uppy.getPlugin(options.id)!
}
uninstallPlugin(props = this.props): void {
const { uppy } = props
uppy.removePlugin(this.plugin)
}
render() {
return h('div', {
className: 'uppy-Container',
ref: (container: HTMLElement) => {
this.container = container
},
...getHTMLProps(this.props),
})
}
}
export default StatusBar

View file

@ -6,7 +6,6 @@ export {
UppyContext,
UppyContextProvider,
} from './headless/UppyContextProvider.js'
export { default as StatusBar } from './StatusBar.js'
export { useDropzone } from './useDropzone.js'
export { useFileInput } from './useFileInput.js'
export { useRemoteSource } from './useRemoteSource.js'

View file

@ -21,9 +21,6 @@
{
"path": "../dashboard/tsconfig.build.json"
},
{
"path": "../status-bar/tsconfig.build.json"
},
{
"path": "../screen-capture/tsconfig.build.json"
},

View file

@ -20,9 +20,6 @@
{
"path": "../dashboard/tsconfig.build.json"
},
{
"path": "../status-bar/tsconfig.build.json"
},
{
"path": "../screen-capture/tsconfig.build.json"
},

View file

@ -1 +0,0 @@
tsconfig.*

View file

@ -1,187 +0,0 @@
# @uppy/status-bar
## 4.1.2
Released: 2025-02-25
Included in: Uppy v4.13.3
- @uppy/status-bar: fix aria-hidden warning (Merlijn Vos / #5663)
## 4.1.1
Released: 2025-01-09
Included in: Uppy v4.12.2
- @uppy/status-bar: fix double upload progress (Mikael Finstad / #5587)
## 4.1.0
Released: 2025-01-06
Included in: Uppy v4.11.0
- @uppy/angular,@uppy/audio,@uppy/aws-s3,@uppy/box,@uppy/companion-client,@uppy/compressor,@uppy/core,@uppy/dashboard,@uppy/drag-drop,@uppy/drop-target,@uppy/dropbox,@uppy/facebook,@uppy/file-input,@uppy/form,@uppy/golden-retriever,@uppy/google-drive-picker,@uppy/google-drive,@uppy/google-photos-picker,@uppy/google-photos,@uppy/image-editor,@uppy/informer,@uppy/instagram,@uppy/locales,@uppy/onedrive,@uppy/progress-bar,@uppy/provider-views,@uppy/react,@uppy/remote-sources,@uppy/screen-capture,@uppy/status-bar,@uppy/thumbnail-generator,@uppy/transloadit,@uppy/tus,@uppy/unsplash,@uppy/url,@uppy/vue,@uppy/webcam,@uppy/webdav,@uppy/xhr-upload,@uppy/zoom: Remove "paths" from all tsconfig's (Merlijn Vos / #5572)
## 4.0.6
Released: 2024-12-17
Included in: Uppy v4.9.0
- e2e,@uppy/status-bar,@uppy/utils: Companion stream upload unknown size files (Mikael Finstad / #5489)
## 4.0.5
Released: 2024-12-05
Included in: Uppy v4.8.0
- @uppy/audio,@uppy/aws-s3,@uppy/box,@uppy/companion-client,@uppy/compressor,@uppy/core,@uppy/dashboard,@uppy/drag-drop,@uppy/drop-target,@uppy/dropbox,@uppy/facebook,@uppy/file-input,@uppy/form,@uppy/golden-retriever,@uppy/google-drive,@uppy/google-photos,@uppy/image-editor,@uppy/informer,@uppy/instagram,@uppy/locales,@uppy/onedrive,@uppy/progress-bar,@uppy/provider-views,@uppy/react,@uppy/remote-sources,@uppy/screen-capture,@uppy/status-bar,@uppy/store-default,@uppy/thumbnail-generator,@uppy/transloadit,@uppy/tus,@uppy/unsplash,@uppy/url,@uppy/utils,@uppy/vue,@uppy/webcam,@uppy/xhr-upload,@uppy/zoom: cleanup tsconfig (Mikael Finstad / #5520)
## 4.0.4
Released: 2024-10-31
Included in: Uppy v4.6.0
- @uppy/aws-s3,@uppy/box,@uppy/companion-client,@uppy/core,@uppy/dashboard,@uppy/drag-drop,@uppy/dropbox,@uppy/facebook,@uppy/file-input,@uppy/form,@uppy/golden-retriever,@uppy/google-drive,@uppy/google-photos,@uppy/image-editor,@uppy/informer,@uppy/instagram,@uppy/locales,@uppy/onedrive,@uppy/progress-bar,@uppy/provider-views,@uppy/react-native,@uppy/react,@uppy/redux-dev-tools,@uppy/screen-capture,@uppy/status-bar,@uppy/store-default,@uppy/store-redux,@uppy/svelte,@uppy/thumbnail-generator,@uppy/transloadit,@uppy/tus,@uppy/unsplash,@uppy/url,@uppy/utils,@uppy/vue,@uppy/webcam,@uppy/xhr-upload,@uppy/zoom: Fix links (Anthony Veaudry / #5492)
## 4.0.3
Released: 2024-08-20
Included in: Uppy v4.2.0
- @uppy/status-bar: show upload button when files are recovered (Merlijn Vos / #5418)
## 4.0.1
Released: 2024-07-30
Included in: Uppy v4.1.0
- @uppy/status-bar: GoldenRetriever + `hideUploadButton=true` (Evgenia Karunus / #5350)
## 4.0.0-beta.9
Released: 2024-06-04
Included in: Uppy v4.0.0-beta.10
- @uppy/status-bar: remove unused component props (Antoine du Hamel / #5211)
- @uppy/status-bar: rename `StatusBar` to `StatusBarUI` (Mikael Finstad / #5200)
## 4.0.0-beta.1
Released: 2024-03-28
Included in: Uppy v4.0.0-beta.1
- @uppy/companion-client,@uppy/provider-views,@uppy/status-bar: fix type imports (Antoine du Hamel / #5038)
- @uppy/status-bar: remove default target (Antoine du Hamel / #4970)
- @uppy/status-bar: refine type of private variables (Antoine du Hamel / #5025)
- @uppy/status-bar: fix `recoveredState` type (Antoine du Hamel / #4996)
## 3.3.3
Released: 2024-05-07
Included in: Uppy v3.25.2
- @uppy/compressor,@uppy/core,@uppy/dashboard,@uppy/status-bar: Upgrade @transloadit/prettier-bytes (Merlijn Vos / #5150)
## 3.3.1
Released: 2024-03-27
Included in: Uppy v3.24.0
- @uppy/box,@uppy/companion-client,@uppy/provider-views,@uppy/status-bar: fix type imports (Antoine du Hamel / #5038)
- @uppy/status-bar: refine type of private variables (Antoine du Hamel / #5025)
- @uppy/status-bar: fix `recoveredState` type (Antoine du Hamel / #4996)
## 3.2.7
Released: 2024-02-20
Included in: Uppy v3.22.1
- @uppy/compressor,@uppy/core,@uppy/dashboard,@uppy/status-bar: bump `@transloadit/prettier-bytes` (Antoine du Hamel / #4933)
## 3.2.6
Released: 2024-02-19
Included in: Uppy v3.22.0
- @uppy/status-bar: fix `statusbaroptions` type (antoine du hamel / #4883)
- @uppy/status-bar: refactor to typescript (antoine du hamel / #4839)
## 3.2.4
Released: 2023-08-15
Included in: Uppy v3.14.0
- @uppy/status-bar: e2e: add test for retrying and pausing uploads (Antoine du Hamel / #3599)
## 3.2.3
Released: 2023-07-20
Included in: Uppy v3.13.0
- @uppy/status-bar: fix ETA when status bar is installed during upload (Antoine du Hamel / #4588)
## 3.2.2
Released: 2023-07-13
Included in: Uppy v3.12.0
- @uppy/status-bar: listen to `upload` event instead of button click (Antoine du Hamel / #4563)
## 3.2.1
Released: 2023-07-06
Included in: Uppy v3.11.0
- @uppy/status-bar: remove throttled component (Artur Paikin / #4396)
- @uppy/status-bar: fix ETA when Uppy recovers its state (Antoine du Hamel / #4525)
## 3.2.0
Released: 2023-06-19
Included in: Uppy v3.10.0
- @uppy/companion,@uppy/core,@uppy/dashboard,@uppy/golden-retriever,@uppy/status-bar,@uppy/utils: Migrate all lodash' per-method-packages usage to lodash. (LinusMain / #4274)
- @uppy/status-bar: Filtered ETA (stduhpf / #4458)
## 3.0.1
Released: 2022-09-25
Included in: Uppy v3.1.0
- @uppy/audio,@uppy/aws-s3-multipart,@uppy/aws-s3,@uppy/box,@uppy/companion-client,@uppy/companion,@uppy/compressor,@uppy/core,@uppy/dashboard,@uppy/drag-drop,@uppy/drop-target,@uppy/dropbox,@uppy/facebook,@uppy/file-input,@uppy/form,@uppy/golden-retriever,@uppy/google-drive,@uppy/image-editor,@uppy/informer,@uppy/instagram,@uppy/locales,@uppy/onedrive,@uppy/progress-bar,@uppy/provider-views,@uppy/react,@uppy/redux-dev-tools,@uppy/remote-sources,@uppy/screen-capture,@uppy/status-bar,@uppy/store-default,@uppy/store-redux,@uppy/svelte,@uppy/thumbnail-generator,@uppy/transloadit,@uppy/tus,@uppy/unsplash,@uppy/url,@uppy/utils,@uppy/vue,@uppy/webcam,@uppy/xhr-upload,@uppy/zoom: add missing entries to changelog for individual packages (Antoine du Hamel / #4092)
## 3.0.0
Released: 2022-08-22
Included in: Uppy v3.0.0
- @uppy/core,@uppy/dashboard,@uppy/status-bar: Style tweaks: use all: initial + other resets (Artur Paikin / #3983)
- Switch to ESM
## 3.0.0-beta.2
Released: 2022-08-03
Included in: Uppy v3.0.0-beta.4
- @uppy/status-bar: rename internal modules (Antoine du Hamel / #3929)
## 2.2.1
Released: 2022-05-30
Included in: Uppy v2.11.0
- @uppy/angular,@uppy/audio,@uppy/aws-s3-multipart,@uppy/aws-s3,@uppy/box,@uppy/core,@uppy/dashboard,@uppy/drag-drop,@uppy/dropbox,@uppy/facebook,@uppy/file-input,@uppy/form,@uppy/golden-retriever,@uppy/google-drive,@uppy/image-editor,@uppy/informer,@uppy/instagram,@uppy/onedrive,@uppy/progress-bar,@uppy/react,@uppy/redux-dev-tools,@uppy/robodog,@uppy/screen-capture,@uppy/status-bar,@uppy/store-default,@uppy/store-redux,@uppy/thumbnail-generator,@uppy/transloadit,@uppy/tus,@uppy/unsplash,@uppy/url,@uppy/vue,@uppy/webcam,@uppy/xhr-upload,@uppy/zoom: doc: update bundler recommendation (Antoine du Hamel / #3763)
## 2.2.0
Released: 2022-05-14
Included in: Uppy v2.10.0
- @uppy/status-bar: refactor to ESM (Antoine du Hamel / #3697)
## 2.1.2
Released: 2021-12-07
Included in: Uppy v2.3.0
- @uppy/status-bar: Status bar error state improvements (Merlijn Vos / #3299)
- @uppy/aws-s3,@uppy/box,@uppy/core,@uppy/dashboard,@uppy/drag-drop,@uppy/dropbox,@uppy/facebook,@uppy/file-input,@uppy/google-drive,@uppy/image-editor,@uppy/instagram,@uppy/locales,@uppy/onedrive,@uppy/screen-capture,@uppy/status-bar,@uppy/thumbnail-generator,@uppy/transloadit,@uppy/url,@uppy/webcam,@uppy/xhr-upload,@uppy/zoom: Refactor locale scripts & generate types and docs (Merlijn Vos / #3276)

View file

@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2018 Transloadit
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -1,54 +0,0 @@
# @uppy/status-bar
<img src="https://uppy.io/img/logo.svg" width="120" alt="Uppy logo: a smiling puppy above a pink upwards arrow" align="right">
[![npm version](https://img.shields.io/npm/v/@uppy/status-bar.svg?style=flat-square)](https://www.npmjs.com/package/@uppy/status-bar)
![CI status for Uppy tests](https://github.com/transloadit/uppy/workflows/CI/badge.svg)
![CI status for Companion tests](https://github.com/transloadit/uppy/workflows/Companion/badge.svg)
![CI status for browser tests](https://github.com/transloadit/uppy/workflows/End-to-end%20tests/badge.svg)
The status-bar shows upload progress and speed, ETAs, pre- and post-processing
information, and allows users to control (pause/resume/cancel) the upload. Best
used together with a basic file source plugin, such as
[@uppy/file-input](https://uppy.io/docs/file-input) or
[@uppy/drag-drop](https://uppy.io/docs/drag-drop), or a custom implementation.
Its also included in the [@uppy/dashboard](https://uppy.io/docs/dashboard)
plugin.
Uppy is being developed by the folks at [Transloadit](https://transloadit.com),
a versatile file encoding service.
## Example
```js
import Uppy from '@uppy/core'
import StatusBar from '@uppy/status-bar'
const uppy = new Uppy()
uppy.use(StatusBar, {
target: 'body',
hideUploadButton: false,
showProgressDetails: false,
hideAfterFinish: true,
})
```
## Installation
```bash
$ npm install @uppy/status-bar
```
Alternatively, you can also use this plugin in a pre-built bundle from
Transloadits CDN: Smart CDN. In that case `Uppy` will attach itself to the
global `window.Uppy` object. See the
[main Uppy documentation](https://uppy.io/docs/#Installation) for instructions.
## Documentation
Documentation for this plugin can be found on the
[Uppy website](https://uppy.io/docs/status-bar).
## License
[The MIT License](./LICENSE).

View file

@ -1,52 +0,0 @@
{
"name": "@uppy/status-bar",
"description": "A progress bar for Uppy, with many bells and whistles.",
"version": "4.1.3",
"license": "MIT",
"main": "lib/index.js",
"style": "dist/style.min.css",
"type": "module",
"sideEffects": [
"*.css"
],
"scripts": {
"build": "tsc --build tsconfig.build.json",
"build:css": "sass --load-path=../../ src/style.scss dist/style.css && postcss dist/style.css -u cssnano -o dist/style.min.css",
"typecheck": "tsc --build"
},
"keywords": [
"file uploader",
"uppy",
"uppy-plugin",
"progress bar",
"status bar",
"progress",
"upload",
"eta",
"speed"
],
"homepage": "https://uppy.io",
"bugs": {
"url": "https://github.com/transloadit/uppy/issues"
},
"repository": {
"type": "git",
"url": "git+https://github.com/transloadit/uppy.git"
},
"dependencies": {
"@transloadit/prettier-bytes": "^0.3.4",
"@uppy/utils": "workspace:^",
"classnames": "^2.2.6",
"preact": "^10.5.13"
},
"peerDependencies": {
"@uppy/core": "workspace:^"
},
"devDependencies": {
"cssnano": "^7.0.7",
"postcss": "^8.5.6",
"postcss-cli": "^11.0.1",
"sass": "^1.89.2",
"typescript": "^5.8.3"
}
}

View file

@ -1,50 +0,0 @@
export default {
strings: {
// Shown in the status bar while files are being uploaded.
uploading: 'Uploading',
// Shown in the status bar once all files have been uploaded.
complete: 'Complete',
// Shown in the status bar if an upload failed.
uploadFailed: 'Upload failed',
// Shown in the status bar while the upload is paused.
paused: 'Paused',
// Used as the label for the button that retries an upload.
retry: 'Retry',
// Used as the label for the button that cancels an upload.
cancel: 'Cancel',
// Used as the label for the button that pauses an upload.
pause: 'Pause',
// Used as the label for the button that resumes an upload.
resume: 'Resume',
// Used as the label for the button that resets the upload state after an upload
done: 'Done',
// When `showProgressDetails` is set, shows the number of files that have been fully uploaded so far.
filesUploadedOfTotal: {
0: '%{complete} of %{smart_count} file uploaded',
1: '%{complete} of %{smart_count} files uploaded',
},
// When `showProgressDetails` is set, shows the amount of bytes that have been uploaded so far.
dataUploadedOfTotal: '%{complete} of %{total}',
dataUploadedOfUnknown: '%{complete} of unknown',
// When `showProgressDetails` is set, shows an estimation of how long the upload will take to complete.
xTimeLeft: '%{time} left',
// Used as the label for the button that starts an upload.
uploadXFiles: {
0: 'Upload %{smart_count} file',
1: 'Upload %{smart_count} files',
},
// Used as the label for the button that starts an upload, if another upload has been started in the past
// and new files were added later.
uploadXNewFiles: {
0: 'Upload +%{smart_count} file',
1: 'Upload +%{smart_count} files',
},
upload: 'Upload',
retryUpload: 'Retry upload',
xMoreFilesAdded: {
0: '%{smart_count} more file added',
1: '%{smart_count} more files added',
},
showErrorDetails: 'Show error details',
},
}

View file

@ -1,17 +0,0 @@
{
"extends": "../../../tsconfig.shared",
"compilerOptions": {
"outDir": "./lib",
"rootDir": "./src"
},
"include": ["./src/**/*.*"],
"exclude": ["./src/**/*.test.ts"],
"references": [
{
"path": "../utils/tsconfig.build.json"
},
{
"path": "../core/tsconfig.build.json"
}
]
}

View file

@ -1,16 +0,0 @@
{
"extends": "../../../tsconfig.shared",
"compilerOptions": {
"emitDeclarationOnly": false,
"noEmit": true
},
"include": ["./package.json", "./src/**/*.*"],
"references": [
{
"path": "../utils/tsconfig.build.json"
},
{
"path": "../core/tsconfig.build.json"
}
]
}

View file

@ -1,8 +0,0 @@
{
"extends": ["//"],
"tasks": {
"build": {
"dependsOn": ["^build", "@uppy/core#build"]
}
}
}

View file

@ -25,10 +25,6 @@
"types": "./dist/components/DashboardModal.svelte.d.ts",
"svelte": "./dist/components/DashboardModal.svelte"
},
"./status-bar": {
"types": "./dist/components/StatusBar.svelte.d.ts",
"svelte": "./dist/components/StatusBar.svelte"
},
"./package.json": "./package.json"
},
"homepage": "https://uppy.io",
@ -75,9 +71,6 @@
"peerDependenciesMeta": {
"@uppy/dashboard": {
"optional": true
},
"@uppy/status-bar": {
"optional": true
}
},
"publishConfig": {

View file

@ -1,43 +0,0 @@
<script
lang="ts"
generics="M extends import('@uppy/utils/lib/UppyFile').Meta, B extends import('@uppy/utils/lib/UppyFile').Body"
>
import type { Uppy } from "@uppy/core";
import StatusBarPlugin from "@uppy/status-bar";
import { onDestroy, onMount } from "svelte";
let container: HTMLElement;
let plugin: StatusBarPlugin<M, B>;
export let uppy: Uppy<M, B>;
export const props: Object | undefined = {};
const installPlugin = () => {
const options = {
id: "svelte:StatusBar",
inline: true,
...props,
target: container,
};
uppy.use(StatusBarPlugin, options);
plugin = uppy.getPlugin(options.id) as StatusBarPlugin<M, B>;
};
const uninstallPlugin = (uppyInstance: Uppy<M, B> = uppy) => {
if (plugin != null) uppyInstance.removePlugin(plugin);
};
onMount(() => installPlugin());
onDestroy(() => uninstallPlugin());
$: {
const options = {
id: "svelte:StatusBar",
...props,
target: container,
};
uppy.setOptions(options);
}
</script>
<div class="uppy-Container" bind:this={container}></div>

View file

@ -2,7 +2,6 @@ export * from "./components/headless/generated/index.js";
// Headless components
export { default as UppyContextProvider } from "./components/headless/UppyContextProvider.svelte";
// Hooks
export * from "./useDropzone.js";
export * from "./useFileInput.js";

View file

@ -38,9 +38,6 @@
"peerDependenciesMeta": {
"@uppy/dashboard": {
"optional": true
},
"@uppy/status-bar": {
"optional": true
}
},
"publishConfig": {

View file

@ -1,39 +0,0 @@
import { Uppy } from '@uppy/core'
import StatusBarPlugin, { type StatusBarOptions } from '@uppy/status-bar'
import { defineComponent, h, type PropType, ref } from 'vue'
import useUppy from './useUppy.js'
export default defineComponent({
name: 'StatusBar',
props: {
uppy: {
type: Object as PropType<Uppy<any, any>>,
required: true,
},
props: {
type: Object as PropType<StatusBarOptions>,
},
},
setup(props) {
const containerRef = ref<string>()
const pluginRef = ref<StatusBarPlugin<any, any>>()
const propsRef = ref(props.props)
const onMount = () => {
const { uppy } = props
const options = {
id: 'StatusBar',
...props.props,
target: containerRef.value,
}
uppy.use(StatusBarPlugin, options)
pluginRef.value = uppy.getPlugin(options.id) as StatusBarPlugin<any, any>
}
useUppy(onMount, pluginRef, props.uppy, propsRef)
return () =>
h('div', {
ref: containerRef,
})
},
})

View file

@ -15,9 +15,6 @@
},
{
"path": "../dashboard/tsconfig.build.json"
},
{
"path": "../status-bar/tsconfig.build.json"
}
]
}

View file

@ -14,9 +14,6 @@
},
{
"path": "../dashboard/tsconfig.build.json"
},
{
"path": "../status-bar/tsconfig.build.json"
}
]
}

View file

@ -52,7 +52,6 @@
"@uppy/provider-views": "workspace:^",
"@uppy/remote-sources": "workspace:^",
"@uppy/screen-capture": "workspace:^",
"@uppy/status-bar": "workspace:^",
"@uppy/store-default": "workspace:^",
"@uppy/thumbnail-generator": "workspace:^",
"@uppy/transloadit": "workspace:^",

View file

@ -38,7 +38,6 @@ export { default as Instagram } from '@uppy/instagram'
export { default as OneDrive } from '@uppy/onedrive'
export { default as RemoteSources } from '@uppy/remote-sources'
export { default as ScreenCapture } from '@uppy/screen-capture'
export { default as StatusBar } from '@uppy/status-bar'
// Stores
export { default as DefaultStore } from '@uppy/store-default'
export { default as ThumbnailGenerator } from '@uppy/thumbnail-generator'

View file

@ -20,7 +20,6 @@ export type { InstagramOptions } from '@uppy/instagram'
export type { OneDriveOptions } from '@uppy/onedrive'
export type { RemoteSourcesOptions } from '@uppy/remote-sources'
export type { ScreenCaptureOptions } from '@uppy/screen-capture'
export type { StatusBarOptions } from '@uppy/status-bar'
export type { ThumbnailGeneratorOptions } from '@uppy/thumbnail-generator'
export type { TransloaditOptions } from '@uppy/transloadit'
export type { TusOptions } from '@uppy/tus'

View file

@ -1,7 +1,6 @@
@use '@uppy/core/src/style.scss' as core;
@use '@uppy/dashboard/src/style.scss' as dashboard;
@use '@uppy/provider-views/src/style.scss' as provider-views;
@use '@uppy/status-bar/src/style.scss' as status-bar;
@use '@uppy/url/src/style.scss' as url;
@use '@uppy/webcam/src/style.scss' as webcam;
@use '@uppy/audio/src/style.scss' as audio;

View file

@ -70,9 +70,6 @@
{
"path": "../@uppy/screen-capture/tsconfig.build.json"
},
{
"path": "../@uppy/status-bar/tsconfig.build.json"
},
{
"path": "../@uppy/store-default/tsconfig.build.json"
},

View file

@ -69,9 +69,6 @@
{
"path": "../@uppy/screen-capture/tsconfig.build.json"
},
{
"path": "../@uppy/status-bar/tsconfig.build.json"
},
{
"path": "../@uppy/store-default/tsconfig.build.json"
},

View file

@ -111,7 +111,7 @@ export default () => {
{ id: 'license', name: 'License', placeholder: 'specify license' },
{ id: 'caption', name: 'Caption', placeholder: 'add caption' },
],
showProgressDetails: true,
hideProgressDetails: true,
proudlyDisplayPoweredByUppy: true,
note: `${JSON.stringify(restrictions)}`,
})

View file

@ -11477,7 +11477,6 @@ __metadata:
"@angular/core": ^17.0.0 || ^18.0.0 || ^19.0.0
"@uppy/core": "workspace:^"
"@uppy/dashboard": "workspace:^"
"@uppy/status-bar": "workspace:^"
"@uppy/utils": "workspace:^"
languageName: unknown
linkType: soft
@ -11689,7 +11688,6 @@ __metadata:
"@uppy/core": "workspace:^"
"@uppy/google-drive": "workspace:^"
"@uppy/provider-views": "workspace:^"
"@uppy/status-bar": "workspace:^"
"@uppy/thumbnail-generator": "workspace:^"
"@uppy/url": "workspace:^"
"@uppy/utils": "workspace:^"
@ -12005,24 +12003,6 @@ __metadata:
languageName: unknown
linkType: soft
"@uppy/status-bar@workspace:^, @uppy/status-bar@workspace:packages/@uppy/status-bar":
version: 0.0.0-use.local
resolution: "@uppy/status-bar@workspace:packages/@uppy/status-bar"
dependencies:
"@transloadit/prettier-bytes": "npm:^0.3.4"
"@uppy/utils": "workspace:^"
classnames: "npm:^2.2.6"
cssnano: "npm:^7.0.7"
postcss: "npm:^8.5.6"
postcss-cli: "npm:^11.0.1"
preact: "npm:^10.5.13"
sass: "npm:^1.89.2"
typescript: "npm:^5.8.3"
peerDependencies:
"@uppy/core": "workspace:^"
languageName: unknown
linkType: soft
"@uppy/store-default@workspace:^, @uppy/store-default@workspace:packages/@uppy/store-default":
version: 0.0.0-use.local
resolution: "@uppy/store-default@workspace:packages/@uppy/store-default"
@ -12056,8 +12036,6 @@ __metadata:
peerDependenciesMeta:
"@uppy/dashboard":
optional: true
"@uppy/status-bar":
optional: true
languageName: unknown
linkType: soft
@ -12178,8 +12156,6 @@ __metadata:
peerDependenciesMeta:
"@uppy/dashboard":
optional: true
"@uppy/status-bar":
optional: true
languageName: unknown
linkType: soft
@ -31074,7 +31050,6 @@ __metadata:
"@uppy/provider-views": "workspace:^"
"@uppy/remote-sources": "workspace:^"
"@uppy/screen-capture": "workspace:^"
"@uppy/status-bar": "workspace:^"
"@uppy/store-default": "workspace:^"
"@uppy/thumbnail-generator": "workspace:^"
"@uppy/transloadit": "workspace:^"