From 608f8dcc0fed91bc3a4c8f44f4bddf49b440e53e Mon Sep 17 00:00:00 2001 From: lawvs <18554747+lawvs@users.noreply.github.com> Date: Wed, 4 Dec 2024 18:29:06 +0800 Subject: [PATCH] feat: support drag-and-drop when importing opml --- apps/renderer/src/components/ui/drop-zone.tsx | 71 +++++++++++++++++++ apps/renderer/src/modules/discover/import.tsx | 10 +-- 2 files changed, 74 insertions(+), 7 deletions(-) create mode 100644 apps/renderer/src/components/ui/drop-zone.tsx diff --git a/apps/renderer/src/components/ui/drop-zone.tsx b/apps/renderer/src/components/ui/drop-zone.tsx new file mode 100644 index 0000000000..e0d8a46028 --- /dev/null +++ b/apps/renderer/src/components/ui/drop-zone.tsx @@ -0,0 +1,71 @@ +import type { DragEvent, ReactNode } from "react" +import { useCallback, useRef, useState } from "react" + +// Ported from https://github.com/react-dropzone/react-dropzone/issues/753#issuecomment-774782919 +const useDragAndDrop = ({ callback }: { callback: (file: FileList) => void | Promise }) => { + const [isDragging, setIsDragging] = useState(false) + const dragCounter = useRef(0) + + const onDrop = useCallback( + async (event: DragEvent) => { + event.preventDefault() + setIsDragging(false) + if (event.dataTransfer && event.dataTransfer.files && event.dataTransfer.files.length > 0) { + dragCounter.current = 0 + await callback(event.dataTransfer.files) + event.dataTransfer.clearData() + } + }, + [callback], + ) + + const onDragEnter = useCallback((event: DragEvent) => { + event.preventDefault() + dragCounter.current++ + setIsDragging(true) + }, []) + + const onDragOver = useCallback((event: DragEvent) => { + event.preventDefault() + }, []) + + const onDragLeave = useCallback((event: DragEvent) => { + event.preventDefault() + dragCounter.current-- + if (dragCounter.current > 0) return + setIsDragging(false) + }, []) + + return { + isDragging, + + dragHandlers: { + onDrop, + onDragOver, + onDragEnter, + onDragLeave, + }, + } +} + +export const DropZone = ({ + onDrop, + children, +}: { + onDrop: (file: FileList) => void | Promise + children?: ReactNode +}) => { + const { isDragging, dragHandlers } = useDragAndDrop({ callback: onDrop }) + + return ( + + ) +} diff --git a/apps/renderer/src/modules/discover/import.tsx b/apps/renderer/src/modules/discover/import.tsx index 17c757f6e6..7a9d1788f3 100644 --- a/apps/renderer/src/modules/discover/import.tsx +++ b/apps/renderer/src/modules/discover/import.tsx @@ -17,6 +17,7 @@ import { useForm } from "react-hook-form" import { Trans, useTranslation } from "react-i18next" import { z } from "zod" +import { DropZone } from "~/components/ui/drop-zone" import { apiFetch } from "~/lib/api-fetch" import { toastFetchError } from "~/lib/error-parser" import { Queries } from "~/queries" @@ -110,26 +111,21 @@ export function DiscoverImport({ isInit = false }: { isInit?: boolean }) { {isInit ? t("discover.import.new_import_opml") : t("discover.import.opml")} - +