diff --git a/src/assets/locales/en.json b/src/assets/locales/en.json index dc4d6ed1..59bc992d 100644 --- a/src/assets/locales/en.json +++ b/src/assets/locales/en.json @@ -316,7 +316,7 @@ "unknownOption": "Unknown" }, "subtitles": { - "customChoice": "Select subtitle from file", + "customChoice": "Drop or upload file", "customizeLabel": "Customize", "offChoice": "Off", "settings": { diff --git a/src/components/DropFile.tsx b/src/components/DropFile.tsx new file mode 100644 index 00000000..b432c2ce --- /dev/null +++ b/src/components/DropFile.tsx @@ -0,0 +1,51 @@ +import { useEffect, useState } from "react"; +import type { DragEvent, ReactNode } from "react"; + +interface FileDropHandlerProps { + children: ReactNode; + className: string; + onDrop: (event: DragEvent) => void; + onDraggingChange: (isDragging: boolean) => void; +} + +export function FileDropHandler(props: FileDropHandlerProps) { + const [dragging, setDragging] = useState(false); + + const handleDragEnter = (event: DragEvent) => { + event.preventDefault(); + setDragging(true); + }; + + const handleDragLeave = (event: DragEvent) => { + if (!event.currentTarget.contains(event.relatedTarget as Node)) { + setDragging(false); + } + }; + + const handleDragOver = (event: DragEvent) => { + event.preventDefault(); + }; + + const handleDrop = (event: DragEvent) => { + event.preventDefault(); + setDragging(false); + + props.onDrop(event); + }; + + useEffect(() => { + props.onDraggingChange(dragging); + }, [dragging, props]); + + return ( +
+ {props.children} +
+ ); +} diff --git a/src/components/player/atoms/settings/CaptionsView.tsx b/src/components/player/atoms/settings/CaptionsView.tsx index 8524ecc8..627787ba 100644 --- a/src/components/player/atoms/settings/CaptionsView.tsx +++ b/src/components/player/atoms/settings/CaptionsView.tsx @@ -5,6 +5,7 @@ import { useAsyncFn } from "react-use"; import { convert } from "subsrt-ts"; import { subtitleTypeList } from "@/backend/helpers/subs"; +import { FileDropHandler } from "@/components/DropFile"; import { FlagIcon } from "@/components/FlagIcon"; import { useCaptions } from "@/components/player/hooks/useCaptions"; import { Menu } from "@/components/player/internals/ContextMenu"; @@ -123,6 +124,8 @@ export function CaptionsView({ id }: { id: string }) { const { selectCaptionById, disable } = useCaptions(); const captionList = usePlayerStore((s) => s.captionList); const getHlsCaptionList = usePlayerStore((s) => s.display?.getCaptionList); + const [dragging, setDragging] = useState(false); + const setCaption = usePlayerStore((s) => s.setCaption); const captions = useMemo( () => @@ -162,7 +165,31 @@ export function CaptionsView({ id }: { id: string }) { }); return ( - <> + { + setDragging(isDragging); + }} + onDrop={(event) => { + const files = event.dataTransfer.files; + if (!files || files.length === 0) return; + + const reader = new FileReader(); + reader.addEventListener("load", (e) => { + if (!e.target || typeof e.target.result !== "string") return; + + const converted = convert(e.target.result, "srt"); + + setCaption({ + language: "custom", + srtData: converted, + id: "custom-caption", + }); + }); + + reader.readAsText(files[0]); + }} + >
router.navigate("/")} @@ -182,6 +209,7 @@ export function CaptionsView({ id }: { id: string }) {
+ disable()} selected={!selectedCaptionId}> {t("player.menus.subtitles.offChoice")} @@ -189,7 +217,7 @@ export function CaptionsView({ id }: { id: string }) { {content} - +
); }