feat: improve visual feedback in MemoEditor for drag/drop file uploads (#4634)

* improve drag/drop file upload UI

* fix eslint errors

* use tailwind for cursor styles

* fix eslint issues
pull/4533/merge
Simon 6 days ago committed by GitHub
parent d38f4699c2
commit bb892be5b3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -6,11 +6,15 @@ import { useResourceStore } from "@/store/v1";
import { Resource } from "@/types/proto/api/v1/resource_service"; import { Resource } from "@/types/proto/api/v1/resource_service";
import { MemoEditorContext } from "../types"; import { MemoEditorContext } from "../types";
interface Props {
isUploadingResource?: boolean;
}
interface State { interface State {
uploadingFlag: boolean; uploadingFlag: boolean;
} }
const UploadResourceButton = () => { const UploadResourceButton = (props: Props) => {
const context = useContext(MemoEditorContext); const context = useContext(MemoEditorContext);
const resourceStore = useResourceStore(); const resourceStore = useResourceStore();
const [state, setState] = useState<State>({ const [state, setState] = useState<State>({
@ -65,13 +69,15 @@ const UploadResourceButton = () => {
}); });
}; };
const isUploading = state.uploadingFlag || props.isUploadingResource;
return ( return (
<Button className="relative" size="sm" variant="plain" disabled={state.uploadingFlag}> <Button className="relative" size="sm" variant="plain" disabled={isUploading}>
{state.uploadingFlag ? <LoaderIcon className="w-5 h-5 mx-auto animate-spin" /> : <PaperclipIcon className="w-5 h-5 mx-auto" />} {isUploading ? <LoaderIcon className="w-5 h-5 mx-auto animate-spin" /> : <PaperclipIcon className="w-5 h-5 mx-auto" />}
<input <input
className="absolute inset-0 w-full h-full opacity-0 cursor-pointer" className="absolute inset-0 w-full h-full opacity-0 cursor-pointer"
ref={fileInputRef} ref={fileInputRef}
disabled={state.uploadingFlag} disabled={isUploading}
onChange={handleFileInputChange} onChange={handleFileInputChange}
type="file" type="file"
id="files" id="files"

@ -54,6 +54,7 @@ interface State {
isUploadingResource: boolean; isUploadingResource: boolean;
isRequesting: boolean; isRequesting: boolean;
isComposing: boolean; isComposing: boolean;
isDraggingFile: boolean;
} }
const MemoEditor = observer((props: Props) => { const MemoEditor = observer((props: Props) => {
@ -71,6 +72,7 @@ const MemoEditor = observer((props: Props) => {
isUploadingResource: false, isUploadingResource: false,
isRequesting: false, isRequesting: false,
isComposing: false, isComposing: false,
isDraggingFile: false,
}); });
const [displayTime, setDisplayTime] = useState<Date | undefined>(); const [displayTime, setDisplayTime] = useState<Date | undefined>();
const [hasContent, setHasContent] = useState<boolean>(false); const [hasContent, setHasContent] = useState<boolean>(false);
@ -222,6 +224,12 @@ const MemoEditor = observer((props: Props) => {
} catch (error: any) { } catch (error: any) {
console.error(error); console.error(error);
toast.error(error.details); toast.error(error.details);
setState((state) => {
return {
...state,
isUploadingResource: false,
};
});
} }
}; };
@ -253,10 +261,36 @@ const MemoEditor = observer((props: Props) => {
const handleDropEvent = async (event: React.DragEvent) => { const handleDropEvent = async (event: React.DragEvent) => {
if (event.dataTransfer && event.dataTransfer.files.length > 0) { if (event.dataTransfer && event.dataTransfer.files.length > 0) {
event.preventDefault(); event.preventDefault();
setState((prevState) => ({
...prevState,
isDraggingFile: false,
}));
await uploadMultiFiles(event.dataTransfer.files); await uploadMultiFiles(event.dataTransfer.files);
} }
}; };
const handleDragOver = (event: React.DragEvent) => {
if (event.dataTransfer && event.dataTransfer.types.includes("Files")) {
event.preventDefault();
event.dataTransfer.dropEffect = "copy";
if (!state.isDraggingFile) {
setState((prevState) => ({
...prevState,
isDraggingFile: true,
}));
}
}
};
const handleDragLeave = (event: React.DragEvent) => {
event.preventDefault();
setState((prevState) => ({
...prevState,
isDraggingFile: false,
}));
};
const handlePasteEvent = async (event: React.ClipboardEvent) => { const handlePasteEvent = async (event: React.ClipboardEvent) => {
if (event.clipboardData && event.clipboardData.files.length > 0) { if (event.clipboardData && event.clipboardData.files.length > 0) {
event.preventDefault(); event.preventDefault();
@ -384,6 +418,7 @@ const MemoEditor = observer((props: Props) => {
resourceList: [], resourceList: [],
relationList: [], relationList: [],
location: undefined, location: undefined,
isDraggingFile: false,
}; };
}); });
}; };
@ -436,10 +471,16 @@ const MemoEditor = observer((props: Props) => {
<div <div
className={`${ className={`${
className ?? "" className ?? ""
} relative w-full flex flex-col justify-start items-start bg-white dark:bg-zinc-800 px-4 pt-4 rounded-lg border border-gray-200 dark:border-zinc-700`} } relative w-full flex flex-col justify-start items-start bg-white dark:bg-zinc-800 px-4 pt-4 rounded-lg border ${
state.isDraggingFile
? "border-dashed border-gray-400 dark:border-primary-400 cursor-copy"
: "border-gray-200 dark:border-zinc-700 cursor-auto"
}`}
tabIndex={0} tabIndex={0}
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
onDrop={handleDropEvent} onDrop={handleDropEvent}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onFocus={handleEditorFocus} onFocus={handleEditorFocus}
onCompositionStart={handleCompositionStart} onCompositionStart={handleCompositionStart}
onCompositionEnd={handleCompositionEnd} onCompositionEnd={handleCompositionEnd}
@ -464,7 +505,7 @@ const MemoEditor = observer((props: Props) => {
<div className="flex flex-row justify-start items-center opacity-80 dark:opacity-60 -space-x-1"> <div className="flex flex-row justify-start items-center opacity-80 dark:opacity-60 -space-x-1">
<TagSelector editorRef={editorRef} /> <TagSelector editorRef={editorRef} />
<MarkdownMenu editorRef={editorRef} /> <MarkdownMenu editorRef={editorRef} />
<UploadResourceButton /> <UploadResourceButton isUploadingResource={state.isUploadingResource} />
<AddMemoRelationPopover editorRef={editorRef} /> <AddMemoRelationPopover editorRef={editorRef} />
{workspaceMemoRelatedSetting.enableLocation && ( {workspaceMemoRelatedSetting.enableLocation && (
<LocationSelector <LocationSelector

Loading…
Cancel
Save