Skip to content

Commit

Permalink
unifiy even more
Browse files Browse the repository at this point in the history
  • Loading branch information
juliusmarminge committed Sep 25, 2024
1 parent dff4ec4 commit f91e6ac
Show file tree
Hide file tree
Showing 6 changed files with 197 additions and 215 deletions.
43 changes: 21 additions & 22 deletions packages/react/src/components/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,9 @@ export function UploadButton<
// since the ErrorMessage messes it up otherwise
const $props = props as unknown as UploadButtonProps<TRouter, TEndpoint> &
UploadThingInternalProps;
const fileRouteInput = "input" in $props ? $props.input : undefined;
const useUploadThing = INTERNAL_uploadthingHookGen<TRouter>({
url: resolveMaybeUrlArg($props.url),
});

const {
mode = "auto",
Expand All @@ -102,10 +104,6 @@ export function UploadButton<
} = $props.config ?? {};
const acRef = useRef(new AbortController());

const useUploadThing = INTERNAL_uploadthingHookGen<TRouter>({
url: resolveMaybeUrlArg($props.url),
});

const fileInputRef = useRef<HTMLInputElement>(null);
const [uploadProgress, setUploadProgress] = useState(
$props.__internal_upload_progress ?? 0,
Expand Down Expand Up @@ -134,18 +132,31 @@ export function UploadButton<
onBeforeUploadBegin: $props.onBeforeUploadBegin,
},
);
const { fileTypes, multiple } = generatePermittedFileTypes(routeConfig);

const disabled = !!($props.__internal_button_disabled ?? $props.disabled);
const state = (() => {
const ready = $props.__internal_state === "ready" || fileTypes.length > 0;

if ($props.__internal_state) return $props.__internal_state;
if (disabled) return "disabled";
if (!ready) return "readying";
if (ready && !isUploading) return "ready";
return "uploading";
})();

const uploadFiles = useCallback(
(files: File[]) => {
startUpload(files, fileRouteInput).catch((e) => {
const input = "input" in $props ? $props.input : undefined;
startUpload(files, input).catch((e) => {
if (e instanceof UploadAbortedError) {
void $props.onUploadAborted?.();
} else {
throw e;
}
});
},
[$props, startUpload, fileRouteInput],
[$props, startUpload],
);

const onUploadClick = (e: React.MouseEvent) => {
Expand All @@ -165,8 +176,6 @@ export function UploadButton<
}
};

const { fileTypes, multiple } = generatePermittedFileTypes(routeConfig);

const inputProps = useMemo(
() => ({
type: "file",
Expand All @@ -186,22 +195,12 @@ export function UploadButton<

uploadFiles(selectedFiles);
},
disabled: fileTypes.length === 0,
tabIndex: fileTypes.length === 0 ? -1 : 0,
disabled,
tabIndex: disabled ? -1 : 0,
}),
[$props, fileTypes, mode, multiple, uploadFiles],
[$props, disabled, fileTypes, mode, multiple, uploadFiles],
);

if ($props.__internal_button_disabled) inputProps.disabled = true;
if ($props.disabled) inputProps.disabled = true;

const state = (() => {
if ($props.__internal_state) return $props.__internal_state;
if (inputProps.disabled) return "disabled";
if (!inputProps.disabled && !isUploading) return "ready";
return "uploading";
})();

usePaste((event) => {
if (!appendOnPaste) return;
if (document.activeElement !== fileInputRef.current) return;
Expand Down
136 changes: 62 additions & 74 deletions packages/react/src/components/dropzone.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import type { FileRouter } from "uploadthing/types";

import type { UploadthingComponentProps } from "../types";
import { INTERNAL_uploadthingHookGen } from "../useUploadThing";
import { usePaste } from "../utils/usePaste";
import { Cancel, progressWidths, Spinner } from "./shared";

type DropzoneStyleFieldCallbackArgs = {
Expand Down Expand Up @@ -128,7 +129,9 @@ export function UploadDropzone<
// since the ErrorMessage messes it up otherwise
const $props = props as unknown as UploadDropzoneProps<TRouter, TEndpoint> &
UploadThingInternalProps;
const fileRouteInput = "input" in $props ? $props.input : undefined;
const useUploadThing = INTERNAL_uploadthingHookGen<TRouter>({
url: resolveMaybeUrlArg($props.url),
});

const {
mode = "manual",
Expand All @@ -137,10 +140,6 @@ export function UploadDropzone<
} = $props.config ?? {};
const acRef = useRef(new AbortController());

const useUploadThing = INTERNAL_uploadthingHookGen<TRouter>({
url: resolveMaybeUrlArg($props.url),
});

const [files, setFiles] = useState<File[]>([]);
const [uploadProgress, setUploadProgress] = useState(
$props.__internal_upload_progress ?? 0,
Expand All @@ -165,20 +164,52 @@ export function UploadDropzone<
onBeforeUploadBegin: $props.onBeforeUploadBegin,
},
);
const { fileTypes, multiple } = generatePermittedFileTypes(routeConfig);

const disabled = !!($props.__internal_dropzone_disabled ?? $props.disabled);
const state = (() => {
const ready =
$props.__internal_ready ??
($props.__internal_state === "ready" || fileTypes.length > 0);

if ($props.__internal_state) return $props.__internal_state;
if (disabled) return "disabled";
if (!ready) return "readying";
if (ready && !isUploading) return "ready";
return "uploading";
})();

const uploadFiles = useCallback(
(files: File[]) => {
startUpload(files, fileRouteInput).catch((e) => {
const input = "input" in $props ? $props.input : undefined;
startUpload(files, input).catch((e) => {
if (e instanceof UploadAbortedError) {
void $props.onUploadAborted?.();
} else {
throw e;
}
});
},
[$props, startUpload, fileRouteInput],
[$props, startUpload],
);

const onUploadClick = (e: React.MouseEvent) => {
if (state === "uploading") {
e.preventDefault();
e.stopPropagation();

acRef.current.abort();
acRef.current = new AbortController();
return;
}
if (mode === "manual" && files.length > 0) {
e.preventDefault();
e.stopPropagation();

uploadFiles(files);
}
};

const onDrop = useCallback(
(acceptedFiles: File[]) => {
$props.onDrop?.(acceptedFiles);
Expand All @@ -192,68 +223,41 @@ export function UploadDropzone<
[$props, mode, uploadFiles],
);

const { fileTypes, multiple } = generatePermittedFileTypes(routeConfig);

const isDisabled = (() => {
if ($props.__internal_dropzone_disabled) return true;
return !!$props.disabled;
})();

const { getRootProps, getInputProps, isDragActive, rootRef } = useDropzone({
onDrop,
multiple,
accept: fileTypes ? generateClientDropzoneAccept(fileTypes) : undefined,
disabled: isDisabled,
disabled,
});

const ready =
$props.__internal_ready ??
($props.__internal_state === "ready" || fileTypes.length > 0);

const onUploadClick = (e: React.MouseEvent) => {
if (state === "uploading") {
e.preventDefault();
e.stopPropagation();

acRef.current.abort();
acRef.current = new AbortController();
return;
}
if (mode === "manual" && files.length > 0) {
e.preventDefault();
e.stopPropagation();
usePaste((event: ClipboardEvent) => {
if (!appendOnPaste) return;
if (document.activeElement !== rootRef.current) return;

uploadFiles(files);
}
};

useEffect(() => {
const handlePaste = (event: ClipboardEvent) => {
if (!appendOnPaste) return;
if (document.activeElement !== rootRef.current) return;
const pastedFiles = getFilesFromClipboardEvent(event);
if (!pastedFiles?.length) return;

const pastedFiles = getFilesFromClipboardEvent(event);
if (!pastedFiles?.length) return;
let filesToUpload = pastedFiles;
setFiles((prev) => {
filesToUpload = [...prev, ...pastedFiles];

let filesToUpload = pastedFiles;
setFiles((prev) => {
filesToUpload = [...prev, ...pastedFiles];

$props.onChange?.(filesToUpload);
$props.onChange?.(filesToUpload);

return filesToUpload;
});
return filesToUpload;
});

$props.onChange?.(filesToUpload);
$props.onChange?.(filesToUpload);

if (mode === "auto") uploadFiles(filesToUpload);
};
if (mode === "auto") uploadFiles(filesToUpload);
});

window.addEventListener("paste", handlePaste);
return () => {
window.removeEventListener("paste", handlePaste);
};
}, [uploadFiles, $props, appendOnPaste, mode, fileTypes, rootRef, files]);
const styleFieldArg = {
ready: state !== "readying",
isUploading: state === "uploading",
uploadProgress,
fileTypes,
isDragActive,
} as DropzoneStyleFieldCallbackArgs;

const getUploadButtonContents = () => {
const customContent = contentFieldToContent(
Expand Down Expand Up @@ -286,22 +290,6 @@ export function UploadDropzone<
}
};

const styleFieldArg = {
fileTypes,
isDragActive,
isUploading,
ready,
uploadProgress,
} as DropzoneStyleFieldCallbackArgs;

const state = (() => {
if ($props.__internal_state) return $props.__internal_state;
if (isDisabled) return "disabled";
if (!ready) return "readying";
if (ready && !isUploading) return "ready";
return "uploading";
})();

return (
<div
className={cn(
Expand Down Expand Up @@ -340,7 +328,7 @@ export function UploadDropzone<
<label
className={cn(
"relative mt-4 flex w-64 cursor-pointer items-center justify-center text-sm font-semibold leading-6 text-gray-600 focus-within:outline-none focus-within:ring-2 focus-within:ring-blue-600 focus-within:ring-offset-2 hover:text-blue-500",
ready ? "text-blue-600" : "text-gray-500",
state === "ready" ? "text-blue-600" : "text-gray-500",
styleFieldToClassName($props.appearance?.label, styleFieldArg),
)}
style={styleFieldToCssObject($props.appearance?.label, styleFieldArg)}
Expand All @@ -349,7 +337,7 @@ export function UploadDropzone<
>
<input className="sr-only" {...getInputProps()} />
{contentFieldToContent($props.content?.label, styleFieldArg) ??
(ready
(state === "ready"
? `Choose ${multiple ? "file(s)" : "a file"} or drag and drop`
: `Loading...`)}
</label>
Expand Down
52 changes: 26 additions & 26 deletions packages/solid/src/components/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,24 +77,22 @@ export function UploadButton<
? ErrorMessage<"You forgot to pass the generic">
: UploadButtonProps<TRouter, TEndpoint>,
) {
const [uploadProgress, setUploadProgress] = createSignal(0);
const [files, setFiles] = createSignal<File[]>([]);

let inputRef: HTMLInputElement;
const $props = props as UploadButtonProps<TRouter, TEndpoint>;

const { cn = defaultClassListMerger } = $props.config ?? {};

let acRef = new AbortController();

const fileRouteInput = "input" in $props ? $props.input : undefined;

const { mode = "auto", appendOnPaste = false } = $props.config ?? {};

const createUploadThing = INTERNAL_createUploadThingGen<TRouter>({
url: resolveMaybeUrlArg($props.url),
});

const {
mode = "auto",
appendOnPaste = false,
cn = defaultClassListMerger,
} = $props.config ?? {};
let acRef = new AbortController();

let inputRef: HTMLInputElement;
const [uploadProgress, setUploadProgress] = createSignal(0);
const [files, setFiles] = createSignal<File[]>([]);

const uploadThing = createUploadThing($props.endpoint, {
signal: acRef.signal,
headers: $props.headers,
Expand All @@ -115,24 +113,18 @@ export function UploadButton<

const fileInfo = () => generatePermittedFileTypes(uploadThing.routeConfig());

const ready = () => fileInfo().fileTypes.length > 0;

const styleFieldArg = {
ready: ready,
isUploading: uploadThing.isUploading,
uploadProgress: uploadProgress,
fileTypes: () => fileInfo().fileTypes,
} as ButtonStyleFieldCallbackArgs;

const state = () => {
if (!ready()) return "readying";
if (ready() && !uploadThing.isUploading()) return "ready";
const ready = fileInfo().fileTypes.length > 0;

if ($props.disabled) return "disabled";
if (!ready) return "readying";
if (ready && !uploadThing.isUploading()) return "ready";
return "uploading";
};

const uploadFiles = (files: File[]) => {
uploadThing.startUpload(files, fileRouteInput).catch((e) => {
const input = "input" in $props ? $props.input : undefined;
uploadThing.startUpload(files, input).catch((e) => {
if (e instanceof UploadAbortedError) {
void $props.onUploadAborted?.();
} else {
Expand All @@ -159,8 +151,9 @@ export function UploadButton<
};

createEffect(() => {
if (!appendOnPaste) return;

const pasteHandler = (e: ClipboardEvent) => {
if (!appendOnPaste) return;
if (document?.activeElement !== inputRef) return;

const pastedFiles = getFilesFromClipboardEvent(e);
Expand All @@ -177,6 +170,13 @@ export function UploadButton<
onCleanup(() => document?.removeEventListener("paste", pasteHandler));
});

const styleFieldArg = {
ready: () => state() !== "readying",
isUploading: uploadThing.isUploading,
uploadProgress: uploadProgress,
fileTypes: () => fileInfo().fileTypes,
} as ButtonStyleFieldCallbackArgs;

const getButtonContent = () => {
const customContent = contentFieldToContent(
$props.content?.button,
Expand Down
Loading

0 comments on commit f91e6ac

Please sign in to comment.