Skip to content

Commit bb892be

Browse files
authored
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
1 parent d38f469 commit bb892be

File tree

2 files changed

+53
-6
lines changed

2 files changed

+53
-6
lines changed

web/src/components/MemoEditor/ActionButton/UploadResourceButton.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,15 @@ import { useResourceStore } from "@/store/v1";
66
import { Resource } from "@/types/proto/api/v1/resource_service";
77
import { MemoEditorContext } from "../types";
88

9+
interface Props {
10+
isUploadingResource?: boolean;
11+
}
12+
913
interface State {
1014
uploadingFlag: boolean;
1115
}
1216

13-
const UploadResourceButton = () => {
17+
const UploadResourceButton = (props: Props) => {
1418
const context = useContext(MemoEditorContext);
1519
const resourceStore = useResourceStore();
1620
const [state, setState] = useState<State>({
@@ -65,13 +69,15 @@ const UploadResourceButton = () => {
6569
});
6670
};
6771

72+
const isUploading = state.uploadingFlag || props.isUploadingResource;
73+
6874
return (
69-
<Button className="relative" size="sm" variant="plain" disabled={state.uploadingFlag}>
70-
{state.uploadingFlag ? <LoaderIcon className="w-5 h-5 mx-auto animate-spin" /> : <PaperclipIcon className="w-5 h-5 mx-auto" />}
75+
<Button className="relative" size="sm" variant="plain" disabled={isUploading}>
76+
{isUploading ? <LoaderIcon className="w-5 h-5 mx-auto animate-spin" /> : <PaperclipIcon className="w-5 h-5 mx-auto" />}
7177
<input
7278
className="absolute inset-0 w-full h-full opacity-0 cursor-pointer"
7379
ref={fileInputRef}
74-
disabled={state.uploadingFlag}
80+
disabled={isUploading}
7581
onChange={handleFileInputChange}
7682
type="file"
7783
id="files"

web/src/components/MemoEditor/index.tsx

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ interface State {
5454
isUploadingResource: boolean;
5555
isRequesting: boolean;
5656
isComposing: boolean;
57+
isDraggingFile: boolean;
5758
}
5859

5960
const MemoEditor = observer((props: Props) => {
@@ -71,6 +72,7 @@ const MemoEditor = observer((props: Props) => {
7172
isUploadingResource: false,
7273
isRequesting: false,
7374
isComposing: false,
75+
isDraggingFile: false,
7476
});
7577
const [displayTime, setDisplayTime] = useState<Date | undefined>();
7678
const [hasContent, setHasContent] = useState<boolean>(false);
@@ -222,6 +224,12 @@ const MemoEditor = observer((props: Props) => {
222224
} catch (error: any) {
223225
console.error(error);
224226
toast.error(error.details);
227+
setState((state) => {
228+
return {
229+
...state,
230+
isUploadingResource: false,
231+
};
232+
});
225233
}
226234
};
227235

@@ -253,10 +261,36 @@ const MemoEditor = observer((props: Props) => {
253261
const handleDropEvent = async (event: React.DragEvent) => {
254262
if (event.dataTransfer && event.dataTransfer.files.length > 0) {
255263
event.preventDefault();
264+
setState((prevState) => ({
265+
...prevState,
266+
isDraggingFile: false,
267+
}));
268+
256269
await uploadMultiFiles(event.dataTransfer.files);
257270
}
258271
};
259272

273+
const handleDragOver = (event: React.DragEvent) => {
274+
if (event.dataTransfer && event.dataTransfer.types.includes("Files")) {
275+
event.preventDefault();
276+
event.dataTransfer.dropEffect = "copy";
277+
if (!state.isDraggingFile) {
278+
setState((prevState) => ({
279+
...prevState,
280+
isDraggingFile: true,
281+
}));
282+
}
283+
}
284+
};
285+
286+
const handleDragLeave = (event: React.DragEvent) => {
287+
event.preventDefault();
288+
setState((prevState) => ({
289+
...prevState,
290+
isDraggingFile: false,
291+
}));
292+
};
293+
260294
const handlePasteEvent = async (event: React.ClipboardEvent) => {
261295
if (event.clipboardData && event.clipboardData.files.length > 0) {
262296
event.preventDefault();
@@ -384,6 +418,7 @@ const MemoEditor = observer((props: Props) => {
384418
resourceList: [],
385419
relationList: [],
386420
location: undefined,
421+
isDraggingFile: false,
387422
};
388423
});
389424
};
@@ -436,10 +471,16 @@ const MemoEditor = observer((props: Props) => {
436471
<div
437472
className={`${
438473
className ?? ""
439-
} 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`}
474+
} relative w-full flex flex-col justify-start items-start bg-white dark:bg-zinc-800 px-4 pt-4 rounded-lg border ${
475+
state.isDraggingFile
476+
? "border-dashed border-gray-400 dark:border-primary-400 cursor-copy"
477+
: "border-gray-200 dark:border-zinc-700 cursor-auto"
478+
}`}
440479
tabIndex={0}
441480
onKeyDown={handleKeyDown}
442481
onDrop={handleDropEvent}
482+
onDragOver={handleDragOver}
483+
onDragLeave={handleDragLeave}
443484
onFocus={handleEditorFocus}
444485
onCompositionStart={handleCompositionStart}
445486
onCompositionEnd={handleCompositionEnd}
@@ -464,7 +505,7 @@ const MemoEditor = observer((props: Props) => {
464505
<div className="flex flex-row justify-start items-center opacity-80 dark:opacity-60 -space-x-1">
465506
<TagSelector editorRef={editorRef} />
466507
<MarkdownMenu editorRef={editorRef} />
467-
<UploadResourceButton />
508+
<UploadResourceButton isUploadingResource={state.isUploadingResource} />
468509
<AddMemoRelationPopover editorRef={editorRef} />
469510
{workspaceMemoRelatedSetting.enableLocation && (
470511
<LocationSelector

0 commit comments

Comments
 (0)