import Fuse from "fuse.js"; import { useMemo, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { useAsyncFn } from "react-use"; import { convert } from "subsrt-ts"; import { subtitleTypeList } from "@/backend/helpers/subs"; import { FlagIcon } from "@/components/FlagIcon"; import { useCaptions } from "@/components/player/hooks/useCaptions"; import { Menu } from "@/components/player/internals/ContextMenu"; import { Input } from "@/components/player/internals/ContextMenu/Input"; import { SelectableLink } from "@/components/player/internals/ContextMenu/Links"; import { getLanguageFromIETF } from "@/components/player/utils/language"; import { useOverlayRouter } from "@/hooks/useOverlayRouter"; import { CaptionListItem } from "@/stores/player/slices/source"; import { usePlayerStore } from "@/stores/player/store"; import { useSubtitleStore } from "@/stores/subtitles"; import { sortLangCodes } from "@/utils/sortLangCodes"; export function CaptionOption(props: { countryCode?: string; children: React.ReactNode; selected?: boolean; loading?: boolean; onClick?: () => void; error?: React.ReactNode; }) { return ( {props.children} ); } function CustomCaptionOption() { const { t } = useTranslation(); const lang = usePlayerStore((s) => s.caption.selected?.language); const setCaption = usePlayerStore((s) => s.setCaption); const setCustomSubs = useSubtitleStore((s) => s.setCustomSubs); const fileInput = useRef(null); return ( fileInput.current?.click()} > {t("player.menus.captions.customChoice")} { if (!e.target.files) return; const reader = new FileReader(); reader.addEventListener("load", (event) => { if (!event.target || typeof event.target.result !== "string") return; const converted = convert(event.target.result, "srt"); setCaption({ language: "custom", srtData: converted, }); setCustomSubs(); }); reader.readAsText(e.target.files[0], "utf-8"); }} /> ); } function useSubtitleList(subs: CaptionListItem[], searchQuery: string) { const { t: translate } = useTranslation(); const unknownChoice = translate("player.menus.captions.unknownLanguage"); return useMemo(() => { const input = subs.map((t) => ({ ...t, languageName: getLanguageFromIETF(t.language) ?? unknownChoice, })); const sorted = sortLangCodes(input.map((t) => t.language)); let results = input.sort((a, b) => { return sorted.indexOf(a.language) - sorted.indexOf(b.language); }); if (searchQuery.trim().length > 0) { const fuse = new Fuse(input, { includeScore: true, keys: ["languageName"], }); results = fuse.search(searchQuery).map((res) => res.item); } return results; }, [subs, searchQuery, unknownChoice]); } export function CaptionsView({ id }: { id: string }) { const { t } = useTranslation(); const router = useOverlayRouter(id); const lang = usePlayerStore((s) => s.caption.selected?.language); const [currentlyDownloading, setCurrentlyDownloading] = useState< string | null >(null); const { selectLanguage, disable } = useCaptions(); const captionList = usePlayerStore((s) => s.captionList); const [searchQuery, setSearchQuery] = useState(""); const subtitleList = useSubtitleList(captionList, searchQuery); const [downloadReq, startDownload] = useAsyncFn( async (language: string) => { setCurrentlyDownloading(language); return selectLanguage(language); }, [selectLanguage, setCurrentlyDownloading] ); const content = subtitleList.map((v) => { return ( startDownload(v.language)} > {v.languageName} ); }); return ( <>
router.navigate("/")} rightSide={ } > {t("player.menus.captions.title")}
disable()} selected={!lang}> {t("player.menus.captions.offChoice")} {content} ); }