Skip to content

Commit

Permalink
add ProgressGranularity
Browse files Browse the repository at this point in the history
  • Loading branch information
juliusmarminge committed Feb 17, 2025
1 parent 5e3f815 commit 8e663b9
Show file tree
Hide file tree
Showing 8 changed files with 55 additions and 31 deletions.
17 changes: 13 additions & 4 deletions packages/react/src/components/button.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"use client";

import type { CSSProperties } from "react";
import { useCallback, useMemo, useRef, useState } from "react";

import {
Expand All @@ -24,7 +25,7 @@ import type { FileRouter } from "uploadthing/types";
import type { UploadthingComponentProps } from "../types";
import { __useUploadThingInternal } from "../use-uploadthing";
import { usePaste } from "../utils/usePaste";
import { Cancel, progressWidths, Spinner } from "./shared";
import { Cancel, Spinner } from "./shared";

type ButtonStyleFieldCallbackArgs = {
__runtime: "react";
Expand Down Expand Up @@ -123,6 +124,7 @@ export function UploadButton<
void $props.onClientUploadComplete?.(res);
setUploadProgress(0);
},
uploadProgressGranularity: $props.uploadProgressGranularity,
onUploadProgress: (p) => {
setUploadProgress(p);
$props.onUploadProgress?.(p);
Expand Down Expand Up @@ -247,7 +249,9 @@ export function UploadButton<
if (uploadProgress >= 100) return <Spinner />;
return (
<span className="z-50">
<span className="block group-hover:hidden">{uploadProgress}%</span>
<span className="block group-hover:hidden">
{Math.round(uploadProgress)}%
</span>
<Cancel cn={cn} className="hidden size-4 group-hover:block" />
</span>
);
Expand Down Expand Up @@ -312,7 +316,12 @@ export function UploadButton<
$props.className,
styleFieldToClassName($props.appearance?.container, styleFieldArg),
)}
style={styleFieldToCssObject($props.appearance?.container, styleFieldArg)}
style={
{
"--progress-width": `${uploadProgress}%`,
...styleFieldToCssObject($props.appearance?.container, styleFieldArg),
} as CSSProperties
}
data-state={state}
>
<label
Expand All @@ -321,7 +330,7 @@ export function UploadButton<
state === "disabled" && "cursor-not-allowed bg-blue-400",
state === "readying" && "cursor-not-allowed bg-blue-400",
state === "uploading" &&
`bg-blue-400 after:absolute after:left-0 after:h-full after:bg-blue-600 after:content-[''] ${progressWidths[uploadProgress]}`,
`bg-blue-400 after:absolute after:left-0 after:h-full after:w-[var(--progress-width)] after:bg-blue-600 after:content-['']`,
state === "ready" && "bg-blue-600",
styleFieldToClassName($props.appearance?.button, styleFieldArg),
)}
Expand Down
17 changes: 13 additions & 4 deletions packages/react/src/components/dropzone.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
} from "react";
import type {
ChangeEvent,
CSSProperties,
DragEvent,
HTMLProps,
KeyboardEvent,
Expand Down Expand Up @@ -51,7 +52,7 @@ import type { FileRouter } from "uploadthing/types";
import type { UploadthingComponentProps } from "../types";
import { __useUploadThingInternal } from "../use-uploadthing";
import { usePaste } from "../utils/usePaste";
import { Cancel, progressWidths, Spinner } from "./shared";
import { Cancel, Spinner } from "./shared";

type DropzoneStyleFieldCallbackArgs = {
__runtime: "react";
Expand Down Expand Up @@ -155,6 +156,7 @@ export function UploadDropzone<
void $props.onClientUploadComplete?.(res);
setUploadProgress(0);
},
uploadProgressGranularity: $props.uploadProgressGranularity,
onUploadProgress: (p) => {
setUploadProgress(p);
$props.onUploadProgress?.(p);
Expand Down Expand Up @@ -279,7 +281,9 @@ export function UploadDropzone<
if (uploadProgress >= 100) return <Spinner />;
return (
<span className="z-50">
<span className="block group-hover:hidden">{uploadProgress}%</span>
<span className="block group-hover:hidden">
{Math.round(uploadProgress)}%
</span>
<Cancel cn={cn} className="hidden size-4 group-hover:block" />
</span>
);
Expand All @@ -304,7 +308,12 @@ export function UploadDropzone<
styleFieldToClassName($props.appearance?.container, styleFieldArg),
)}
{...getRootProps()}
style={styleFieldToCssObject($props.appearance?.container, styleFieldArg)}
style={
{
"--progress-width": `${uploadProgress}%`,
...styleFieldToCssObject($props.appearance?.container, styleFieldArg),
} as CSSProperties
}
data-state={state}
>
{contentFieldToContent($props.content?.uploadIcon, styleFieldArg) ?? (
Expand Down Expand Up @@ -371,7 +380,7 @@ export function UploadDropzone<
state === "disabled" && "cursor-not-allowed bg-blue-400",
state === "readying" && "cursor-not-allowed bg-blue-400",
state === "uploading" &&
`bg-blue-400 after:absolute after:left-0 after:h-full after:bg-blue-600 after:content-[''] ${progressWidths[uploadProgress]}`,
`bg-blue-400 after:absolute after:left-0 after:h-full after:w-[var(--progress-width)] after:bg-blue-600 after:content-['']`,
state === "ready" && "bg-blue-600",
"disabled:pointer-events-none",
styleFieldToClassName($props.appearance?.button, styleFieldArg),
Expand Down
14 changes: 0 additions & 14 deletions packages/react/src/components/shared.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,3 @@ export function Cancel({
</svg>
);
}

export const progressWidths: Record<number, string> = {
0: "after:w-0",
10: "after:w-[10%]",
20: "after:w-[20%]",
30: "after:w-[30%]",
40: "after:w-[40%]",
50: "after:w-[50%]",
60: "after:w-[60%]",
70: "after:w-[70%]",
80: "after:w-[80%]",
90: "after:w-[90%]",
100: "after:w-[100%]",
};
9 changes: 9 additions & 0 deletions packages/react/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type {
ExtendObjectIf,
FetchEsque,
MaybePromise,
ProgressGranularity,
UploadThingError,
} from "@uploadthing/shared";
import type {
Expand Down Expand Up @@ -63,6 +64,14 @@ export type UseUploadthingProps<
* Called when presigned URLs have been retrieved and the file upload is about to begin
*/
onUploadBegin?: ((fileName: string) => void) | undefined;
/**
* Control how granular the upload progress is reported
* - "all" - No filtering is applied, all progress events are reported
* - "fine" - Progress is reported in increments of 1%
* - "coarse" - Progress is reported in increments of 10%
* @default "coarse"
*/
uploadProgressGranularity?: ProgressGranularity | undefined;
/**
* Called continuously as the file is uploaded to the storage provider
*/
Expand Down
10 changes: 7 additions & 3 deletions packages/react/src/use-uploadthing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type {
import {
INTERNAL_DO_NOT_USE__fatalClientError,
resolveMaybeUrlArg,
roundProgress,
unwrap,
UploadAbortedError,
UploadThingError,
Expand Down Expand Up @@ -62,6 +63,7 @@ function useUploadThingInternal<
fetch: FetchEsque,
opts?: UseUploadthingProps<TRouter[TEndpoint]>,
) {
const progressGranularity = opts?.uploadProgressGranularity ?? "coarse";
const { uploadFiles, routeRegistry } = genUploader<TRouter>({
fetch,
url,
Expand Down Expand Up @@ -94,10 +96,12 @@ function useUploadThingInternal<
fileProgress.current.set(progress.file, progress.progress);
let sum = 0;
fileProgress.current.forEach((p) => {
sum += p;
sum = Math.min(100, sum + p);
});
const averageProgress =
Math.floor(sum / fileProgress.current.size / 10) * 10;
const averageProgress = roundProgress(
sum / fileProgress.current.size,
progressGranularity,
);
if (averageProgress !== uploadProgress.current) {
opts.onUploadProgress(averageProgress);
uploadProgress.current = averageProgress;
Expand Down
10 changes: 10 additions & 0 deletions packages/shared/src/component-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@ import { video } from "@uploadthing/mime-types/video";
import type { ExpandedRouteConfig } from "./types";
import { objectKeys } from "./utils";

export type ProgressGranularity = "all" | "fine" | "coarse";
export const roundProgress = (
progress: number,
granularity: ProgressGranularity,
) => {
if (granularity === "all") return progress;
if (granularity === "fine") return Math.round(progress);
return Math.floor(progress / 10) * 10;
};

export const generateMimeTypes = (
typesOrRouteConfig: string[] | ExpandedRouteConfig,
) => {
Expand Down
8 changes: 2 additions & 6 deletions packages/uploadthing/src/_internal/upload-browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,15 +208,11 @@ export const uploadFilesInternal = <
totalLoaded += ev.delta;
opts.onUploadProgress?.({
file: opts.files[i]!,
progress: Math.round(
(ev.loaded / opts.files[i]!.size) * 100,
),
progress: (ev.loaded / opts.files[i]!.size) * 100,
loaded: ev.loaded,
delta: ev.delta,
totalLoaded,
totalProgress: Math.round(
(totalLoaded / totalSize) * 100,
),
totalProgress: totalLoaded / totalSize,
});
},
},
Expand Down
1 change: 1 addition & 0 deletions playground/components/uploader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export function Uploader() {
button: "!text-sm/6",
allowedContent: "!h-0",
}}
uploadProgressGranularity="fine"
className="ut-button:bg-red-600"
/>
</div>
Expand Down

0 comments on commit 8e663b9

Please sign in to comment.