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

@ -54,6 +54,7 @@ interface State {
isUploadingResource: boolean;
isRequesting: boolean;
isComposing: boolean;
isDraggingFile: boolean;
}
const MemoEditor = observer((props: Props) => {
@ -71,6 +72,7 @@ const MemoEditor = observer((props: Props) => {
isUploadingResource: false,
isRequesting: false,
isComposing: false,
isDraggingFile: false,
});
const [displayTime, setDisplayTime] = useState<Date | undefined>();
const [hasContent, setHasContent] = useState<boolean>(false);
@ -222,6 +224,12 @@ const MemoEditor = observer((props: Props) => {
} catch (error: any) {
console.error(error);
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) => {
if (event.dataTransfer && event.dataTransfer.files.length > 0) {
event.preventDefault();
setState((prevState) => ({
...prevState,
isDraggingFile: false,
}));
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) => {
if (event.clipboardData && event.clipboardData.files.length > 0) {
event.preventDefault();
@ -384,6 +418,7 @@ const MemoEditor = observer((props: Props) => {
resourceList: [],
relationList: [],
location: undefined,
isDraggingFile: false,
};
});
};
@ -436,10 +471,16 @@ const MemoEditor = observer((props: Props) => {
<div
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}
onKeyDown={handleKeyDown}
onDrop={handleDropEvent}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onFocus={handleEditorFocus}
onCompositionStart={handleCompositionStart}
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">
<TagSelector editorRef={editorRef} />
<MarkdownMenu editorRef={editorRef} />
<UploadResourceButton />
<UploadResourceButton isUploadingResource={state.isUploadingResource} />
<AddMemoRelationPopover editorRef={editorRef} />
{workspaceMemoRelatedSetting.enableLocation && (
<LocationSelector

Loading…
Cancel
Save