) {
const files = event.dataTransfer.files;
@@ -159,42 +125,10 @@ export function CaptionsView({ id }: { id: string }) {
reader.readAsText(firstFile);
}
- const captions = useMemo(
- () =>
- captionList.length !== 0 ? captionList : getHlsCaptionList?.() ?? [],
- [captionList, getHlsCaptionList],
- );
-
- const [searchQuery, setSearchQuery] = useState("");
- const subtitleList = useSubtitleList(captions, searchQuery);
-
- const [downloadReq, startDownload] = useAsyncFn(
- async (captionId: string) => {
- setCurrentlyDownloading(captionId);
- return selectCaptionById(captionId);
- },
- [selectCaptionById, setCurrentlyDownloading],
- );
-
- const content = subtitleList.map((v) => {
- return (
- startDownload(v.id)}
- >
- {v.languageName}
-
- );
- });
+ const selectedLanguagePretty = selectedCaptionLanguage
+ ? getPrettyLanguageNameFromLocale(selectedCaptionLanguage) ??
+ t("player.menus.subtitles.unknownLanguage")
+ : undefined;
return (
<>
@@ -213,20 +147,36 @@ export function CaptionsView({ id }: { id: string }) {
- router.navigate("/")}
- rightSide={
-
- }
- >
- {t("player.menus.subtitles.title")}
-
+ {backLink ? (
+ router.navigate("/")}
+ rightSide={
+
+ }
+ >
+ {t("player.menus.subtitles.title")}
+
+ ) : (
+ router.navigate("/captions/settingsOverlay")}
+ className="-mr-2 -my-1 px-2 p-[0.4em] rounded tabbable hover:bg-video-context-light hover:bg-opacity-10"
+ >
+ {t("player.menus.subtitles.customizeLabel")}
+
+ }
+ >
+ {t("player.menus.subtitles.title")}
+
+ )}
onDrop(event)}
>
-
-
-
-
disable()}
@@ -253,22 +193,36 @@ export function CaptionsView({ id }: { id: string }) {
{t("player.menus.subtitles.offChoice")}
- {content.length === 0 ? (
-
-
- {t("player.menus.subtitles.empty")}
-
-
-
- ) : (
- content
- )}
+
+ router.navigate(
+ backLink ? "/captions/source" : "/captions/sourceOverlay",
+ )
+ }
+ rightText={
+ useSubtitleStore((s) => s.isOpenSubtitles)
+ ? ""
+ : selectedLanguagePretty
+ }
+ >
+ {t("player.menus.subtitles.SourceChoice")}
+
+
+ router.navigate(
+ backLink
+ ? "/captions/opensubtitles"
+ : "/captions/opensubtitlesOverlay",
+ )
+ }
+ rightText={
+ useSubtitleStore((s) => s.isOpenSubtitles)
+ ? selectedLanguagePretty
+ : ""
+ }
+ >
+ {t("player.menus.subtitles.OpenSubtitlesChoice")}
+
>
diff --git a/src/components/player/atoms/settings/Opensubtitles.tsx b/src/components/player/atoms/settings/Opensubtitles.tsx
deleted file mode 100644
index b4dd2b1b..00000000
--- a/src/components/player/atoms/settings/Opensubtitles.tsx
+++ /dev/null
@@ -1,141 +0,0 @@
-import Fuse from "fuse.js";
-import { useMemo, useState } from "react";
-import { useTranslation } from "react-i18next";
-import { useAsyncFn } from "react-use";
-
-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 { useOverlayRouter } from "@/hooks/useOverlayRouter";
-import { CaptionListItem } from "@/stores/player/slices/source";
-import { usePlayerStore } from "@/stores/player/store";
-import {
- getPrettyLanguageNameFromLocale,
- sortLangCodes,
-} from "@/utils/language";
-
-export function CaptionOption(props: {
- countryCode?: string;
- children: React.ReactNode;
- selected?: boolean;
- loading?: boolean;
- onClick?: () => void;
- error?: React.ReactNode;
-}) {
- return (
-
-
-
-
-
- {props.children}
-
-
- );
-}
-
-function useSubtitleList(subs: CaptionListItem[], searchQuery: string) {
- const { t: translate } = useTranslation();
- const unknownChoice = translate("player.menus.subtitles.unknownLanguage");
- return useMemo(() => {
- const input = subs
- .map((t) => ({
- ...t,
- languageName:
- getPrettyLanguageNameFromLocale(t.language) ?? unknownChoice,
- }))
- .filter((x) => x.opensubtitles);
- 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 OpenSubtitlesCaptionView({ id }: { id: string }) {
- const { t } = useTranslation();
- const router = useOverlayRouter(id);
- const selectedCaptionId = usePlayerStore((s) => s.caption.selected?.id);
- const [currentlyDownloading, setCurrentlyDownloading] = useState<
- string | null
- >(null);
- const { selectCaptionById } = useCaptions();
- const captionList = usePlayerStore((s) => s.captionList);
- const getHlsCaptionList = usePlayerStore((s) => s.display?.getCaptionList);
-
- const captions = useMemo(
- () =>
- captionList.length !== 0 ? captionList : getHlsCaptionList?.() ?? [],
- [captionList, getHlsCaptionList],
- );
-
- const [searchQuery, setSearchQuery] = useState("");
- const subtitleList = useSubtitleList(captions, searchQuery);
-
- const [downloadReq, startDownload] = useAsyncFn(
- async (captionId: string) => {
- setCurrentlyDownloading(captionId);
- return selectCaptionById(captionId);
- },
- [selectCaptionById, setCurrentlyDownloading],
- );
-
- const content = subtitleList.map((v) => {
- return (
- startDownload(v.id)}
- >
- {v.languageName}
-
- );
- });
-
- return (
- <>
-
-
router.navigate("/captions")}>
- {t("player.menus.subtitles.OpenSubtitlesChoice")}
-
-
-
-
-
-
- {content}
-
- >
- );
-}
-
-export default OpenSubtitlesCaptionView;
diff --git a/src/components/player/atoms/settings/OpensubtitlesCaptionsView.tsx b/src/components/player/atoms/settings/OpensubtitlesCaptionsView.tsx
new file mode 100644
index 00000000..160d9009
--- /dev/null
+++ b/src/components/player/atoms/settings/OpensubtitlesCaptionsView.tsx
@@ -0,0 +1,104 @@
+import { useMemo, useState } from "react";
+import { useTranslation } from "react-i18next";
+import { useAsyncFn } from "react-use";
+
+import { useCaptions } from "@/components/player/hooks/useCaptions";
+import { Menu } from "@/components/player/internals/ContextMenu";
+import { Input } from "@/components/player/internals/ContextMenu/Input";
+import { useOverlayRouter } from "@/hooks/useOverlayRouter";
+import { usePlayerStore } from "@/stores/player/store";
+
+import { CaptionOption } from "./CaptionsView";
+import { useSubtitleList } from "./SourceCaptionsView";
+
+export function OpenSubtitlesCaptionView({
+ id,
+ overlayBackLink,
+}: {
+ id: string;
+ overlayBackLink?: true;
+}) {
+ const { t } = useTranslation();
+ const router = useOverlayRouter(id);
+ const selectedCaptionId = usePlayerStore((s) => s.caption.selected?.id);
+ const [currentlyDownloading, setCurrentlyDownloading] = useState<
+ string | null
+ >(null);
+ const { selectCaptionById } = useCaptions();
+ const captionList = usePlayerStore((s) => s.captionList);
+ const getHlsCaptionList = usePlayerStore((s) => s.display?.getCaptionList);
+
+ const captions = useMemo(
+ () =>
+ captionList.length !== 0 ? captionList : getHlsCaptionList?.() ?? [],
+ [captionList, getHlsCaptionList],
+ );
+
+ const [searchQuery, setSearchQuery] = useState("");
+ const subtitleList = useSubtitleList(
+ captions.filter((x) => x.opensubtitles),
+ searchQuery,
+ );
+
+ const [downloadReq, startDownload] = useAsyncFn(
+ async (captionId: string) => {
+ setCurrentlyDownloading(captionId);
+ return selectCaptionById(captionId);
+ },
+ [selectCaptionById, setCurrentlyDownloading],
+ );
+
+ const content = subtitleList.length
+ ? subtitleList.map((v) => {
+ return (
+ startDownload(v.id)}
+ >
+ {v.languageName}
+
+ );
+ })
+ : t("player.menus.subtitles.notFound");
+
+ return (
+ <>
+
+
+ router.navigate(overlayBackLink ? "/captionsOverlay" : "/captions")
+ }
+ >
+ {t("player.menus.subtitles.OpenSubtitlesChoice")}
+
+
+ {captionList.filter((x) => x.opensubtitles).length ? (
+
+
+
+ ) : null}
+
+ {!captionList.filter((x) => x.opensubtitles).length ? (
+
+
+ {t("player.menus.subtitles.empty")}
+
+
+ ) : (
+ {content}
+ )}
+
+ >
+ );
+}
+
+export default OpenSubtitlesCaptionView;
diff --git a/src/components/player/atoms/settings/SourceCaptionsView.tsx b/src/components/player/atoms/settings/SourceCaptionsView.tsx
new file mode 100644
index 00000000..3d24a366
--- /dev/null
+++ b/src/components/player/atoms/settings/SourceCaptionsView.tsx
@@ -0,0 +1,149 @@
+import Fuse from "fuse.js";
+import { useMemo, useState } from "react";
+import { useTranslation } from "react-i18next";
+import { useAsyncFn } from "react-use";
+
+import { useCaptions } from "@/components/player/hooks/useCaptions";
+import { Menu } from "@/components/player/internals/ContextMenu";
+import { Input } from "@/components/player/internals/ContextMenu/Input";
+import { useOverlayRouter } from "@/hooks/useOverlayRouter";
+import { CaptionListItem } from "@/stores/player/slices/source";
+import { usePlayerStore } from "@/stores/player/store";
+import {
+ getPrettyLanguageNameFromLocale,
+ sortLangCodes,
+} from "@/utils/language";
+
+import { CaptionOption } from "./CaptionsView";
+
+export function useSubtitleList(subs: CaptionListItem[], searchQuery: string) {
+ const { t: translate } = useTranslation();
+ const unknownChoice = translate("player.menus.subtitles.unknownLanguage");
+ return useMemo(() => {
+ const input = subs.map((t) => ({
+ ...t,
+ languageName:
+ getPrettyLanguageNameFromLocale(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 SourceCaptionsView({
+ id,
+ overlayBackLink,
+}: {
+ id: string;
+ overlayBackLink?: true;
+}) {
+ const { t } = useTranslation();
+ const router = useOverlayRouter(id);
+ const selectedCaptionId = usePlayerStore((s) => s.caption.selected?.id);
+ const [currentlyDownloading, setCurrentlyDownloading] = useState<
+ string | null
+ >(null);
+ const { selectCaptionById } = useCaptions();
+ const captionList = usePlayerStore((s) => s.captionList);
+ const getHlsCaptionList = usePlayerStore((s) => s.display?.getCaptionList);
+
+ const captions = useMemo(
+ () =>
+ captionList.length !== 0 ? captionList : getHlsCaptionList?.() ?? [],
+ [captionList, getHlsCaptionList],
+ );
+
+ const [searchQuery, setSearchQuery] = useState("");
+ const subtitleList = useSubtitleList(
+ captions.filter((x) => !x.opensubtitles),
+ searchQuery,
+ );
+
+ const [downloadReq, startDownload] = useAsyncFn(
+ async (captionId: string) => {
+ setCurrentlyDownloading(captionId);
+ return selectCaptionById(captionId);
+ },
+ [selectCaptionById, setCurrentlyDownloading],
+ );
+
+ const content = subtitleList.length
+ ? subtitleList.map((v) => {
+ return (
+ startDownload(v.id)}
+ >
+ {v.languageName}
+
+ );
+ })
+ : t("player.menus.subtitles.notFound");
+
+ return (
+ <>
+
+
+ router.navigate(overlayBackLink ? "/captionsOverlay" : "/captions")
+ }
+ >
+ {t("player.menus.subtitles.SourceChoice")}
+
+
+ {captionList.filter((x) => !x.opensubtitles).length ? (
+
+
+
+ ) : null}
+
+ {!captionList.filter((x) => !x.opensubtitles).length ? (
+
+
+ {t("player.menus.subtitles.empty")}
+
+
+
+ ) : (
+ {content}
+ )}
+
+ >
+ );
+}
+
+export default SourceCaptionsView;
diff --git a/src/components/player/internals/ContextMenu/Links.tsx b/src/components/player/internals/ContextMenu/Links.tsx
index 9b49db88..616647a9 100644
--- a/src/components/player/internals/ContextMenu/Links.tsx
+++ b/src/components/player/internals/ContextMenu/Links.tsx
@@ -123,34 +123,14 @@ export function SelectableLink(props: {
children?: ReactNode;
disabled?: boolean;
error?: ReactNode;
- chevron?: boolean;
}) {
let rightContent;
if (props.selected) {
- if (props.chevron) {
- rightContent = (
-
-
-
-
- );
- } else {
- rightContent = (
-
- );
- }
- } else if (props.chevron) {
rightContent = (
-
+
);
}
if (props.error)
diff --git a/src/pages/parts/player/PlayerPart.tsx b/src/pages/parts/player/PlayerPart.tsx
index 1f55013b..f012f8ea 100644
--- a/src/pages/parts/player/PlayerPart.tsx
+++ b/src/pages/parts/player/PlayerPart.tsx
@@ -111,7 +111,10 @@ export function PlayerPart(props: PlayerPartProps) {
) : null}
{status === playerStatus.PLAYBACK_ERROR ||
status === playerStatus.PLAYING ? (
-
+ <>
+
+
+ >
) : null}
@@ -121,7 +124,12 @@ export function PlayerPart(props: PlayerPartProps) {
{status === playerStatus.PLAYING ?
: null}
- {status === playerStatus.PLAYING ?
: null}
+ {status === playerStatus.PLAYING ? (
+ <>
+
+
+ >
+ ) : null}