mirror of
https://github.com/usememos/memos.git
synced 2025-09-13 17:24:27 +08:00
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
This commit is contained in:
parent
d38f4699c2
commit
bb892be5b3
2 changed files with 53 additions and 6 deletions
|
@ -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…
Add table
Reference in a new issue