From 57d3f69efad74ebcf6d7adeed7a57811cb67a9fa Mon Sep 17 00:00:00 2001
From: Isra <byzkk@protonmail.com>
Date: Sat, 16 Mar 2024 14:55:29 -0500
Subject: [PATCH] Drop subtitles

---
 src/assets/locales/en.json                    |  2 +-
 src/components/DropFile.tsx                   | 51 +++++++++++++++++++
 .../player/atoms/settings/CaptionsView.tsx    | 32 +++++++++++-
 3 files changed, 82 insertions(+), 3 deletions(-)
 create mode 100644 src/components/DropFile.tsx

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<HTMLDivElement>) => void;
+  onDraggingChange: (isDragging: boolean) => void;
+}
+
+export function FileDropHandler(props: FileDropHandlerProps) {
+  const [dragging, setDragging] = useState(false);
+
+  const handleDragEnter = (event: DragEvent<HTMLDivElement>) => {
+    event.preventDefault();
+    setDragging(true);
+  };
+
+  const handleDragLeave = (event: DragEvent<HTMLDivElement>) => {
+    if (!event.currentTarget.contains(event.relatedTarget as Node)) {
+      setDragging(false);
+    }
+  };
+
+  const handleDragOver = (event: DragEvent<HTMLDivElement>) => {
+    event.preventDefault();
+  };
+
+  const handleDrop = (event: DragEvent<HTMLDivElement>) => {
+    event.preventDefault();
+    setDragging(false);
+
+    props.onDrop(event);
+  };
+
+  useEffect(() => {
+    props.onDraggingChange(dragging);
+  }, [dragging, props]);
+
+  return (
+    <section
+      onDragEnter={handleDragEnter}
+      onDragLeave={handleDragLeave}
+      onDragOver={handleDragOver}
+      onDrop={handleDrop}
+      className={props.className}
+    >
+      {props.children}
+    </section>
+  );
+}
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 (
-    <>
+    <FileDropHandler
+      className={`transition duration-300 ${dragging ? "brightness-50" : ""}`}
+      onDraggingChange={(isDragging) => {
+        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]);
+      }}
+    >
       <div>
         <Menu.BackLink
           onClick={() => router.navigate("/")}
@@ -182,6 +209,7 @@ export function CaptionsView({ id }: { id: string }) {
           <Input value={searchQuery} onInput={setSearchQuery} />
         </div>
       </div>
+
       <Menu.ScrollToActiveSection className="!pt-1 mt-2 pb-3">
         <CaptionOption onClick={() => disable()} selected={!selectedCaptionId}>
           {t("player.menus.subtitles.offChoice")}
@@ -189,7 +217,7 @@ export function CaptionsView({ id }: { id: string }) {
         <CustomCaptionOption />
         {content}
       </Menu.ScrollToActiveSection>
-    </>
+    </FileDropHandler>
   );
 }