diff --git a/src/components/Dropdown.tsx b/src/components/Dropdown.tsx index 84ab2957..9321e1fe 100644 --- a/src/components/Dropdown.tsx +++ b/src/components/Dropdown.tsx @@ -37,7 +37,7 @@ export function Dropdown(props: DropdownProps) { leaveFrom="opacity-100" leaveTo="opacity-0" > - + {props.options.map((opt) => ( diff --git a/src/components/layout/Modal.tsx b/src/components/layout/Modal.tsx index 7a1c3b64..8ae2f58f 100644 --- a/src/components/layout/Modal.tsx +++ b/src/components/layout/Modal.tsx @@ -35,9 +35,14 @@ export function Modal(props: Props) { ); } -export function ModalCard(props: { children?: ReactNode }) { +export function ModalCard(props: { className?: string; children?: ReactNode }) { return ( -
+
{props.children}
); diff --git a/src/components/layout/Navigation.tsx b/src/components/layout/Navigation.tsx index 4fe1864f..836c6206 100644 --- a/src/components/layout/Navigation.tsx +++ b/src/components/layout/Navigation.tsx @@ -1,9 +1,10 @@ -import { ReactNode } from "react"; +import { ReactNode, useState } from "react"; import { Link } from "react-router-dom"; import { IconPatch } from "@/components/buttons/IconPatch"; import { Icons } from "@/components/Icon"; import { conf } from "@/setup/config"; import { useBannerSize } from "@/hooks/useBanner"; +import SettingsModal from "@/views/SettingsModal"; import { BrandPill } from "./BrandPill"; export interface NavigationProps { @@ -13,7 +14,7 @@ export interface NavigationProps { export function Navigation(props: NavigationProps) { const bannerHeight = useBannerSize(); - + const [showModal, setShowModal] = useState(false); return (
+ { + setShowModal(true); + }} + />
+ setShowModal(false)} /> ); } diff --git a/src/setup/locales/en/translation.json b/src/setup/locales/en/translation.json index 1838b63c..71ee7064 100644 --- a/src/setup/locales/en/translation.json +++ b/src/setup/locales/en/translation.json @@ -99,6 +99,11 @@ "fatalError": "The video player encounted a fatal error, please report it to the <0>Discord server or on <1>GitHub." } }, + "settings": { + "title": "Settings", + "language":"Language", + "captionLanguage": "Caption Language" + }, "v3": { "newSiteTitle": "New version now released!", "newDomain": "https://movie-web.app", diff --git a/src/state/settings/context.tsx b/src/state/settings/context.tsx index 030833cc..be7a92e0 100644 --- a/src/state/settings/context.tsx +++ b/src/state/settings/context.tsx @@ -1,14 +1,15 @@ import { useStore } from "@/utils/storage"; import { createContext, ReactNode, useContext, useMemo } from "react"; +import { LangCode } from "@/setup/iso6391"; import { SettingsStore } from "./store"; import { MWSettingsData } from "./types"; interface MWSettingsDataSetters { - setLanguage(language: string): void; + setCaptionLanguage(language: LangCode): void; setCaptionDelay(delay: number): void; setCaptionColor(color: string): void; setCaptionFontSize(size: number): void; - setCaptionBackgroundColor(backgroundColor: string): void; + setCaptionBackgroundColor(backgroundColor: number): void; } type MWSettingsDataWrapper = MWSettingsData & MWSettingsDataSetters; const SettingsContext = createContext(null as any); @@ -17,16 +18,15 @@ export function SettingsProvider(props: { children: ReactNode }) { return Math.max(min, Math.min(value, max)); } const [settings, setSettings] = useStore(SettingsStore); - const context: MWSettingsDataWrapper = useMemo(() => { const settingsContext: MWSettingsDataWrapper = { ...settings, - setLanguage(language) { + setCaptionLanguage(language) { setSettings((oldSettings) => { - return { - ...oldSettings, - language, - }; + const captionSettings = oldSettings.captionSettings; + captionSettings.language = language; + const newSettings = oldSettings; + return newSettings; }); }, setCaptionDelay(delay: number) { @@ -56,7 +56,10 @@ export function SettingsProvider(props: { children: ReactNode }) { setCaptionBackgroundColor(backgroundColor) { setSettings((oldSettings) => { const style = oldSettings.captionSettings.style; - style.backgroundColor = backgroundColor; + style.backgroundColor = `${style.backgroundColor.substring( + 0, + 7 + )}${backgroundColor.toString(16).padStart(2, "0")}`; const newSettings = oldSettings; return newSettings; }); diff --git a/src/state/settings/store.ts b/src/state/settings/store.ts index c854dc04..e8c629c1 100644 --- a/src/state/settings/store.ts +++ b/src/state/settings/store.ts @@ -1,11 +1,11 @@ import { createVersionedStore } from "@/utils/storage"; -import { MWSettingsData } from "./types"; +import { MWSettingsData, MWSettingsDataV1 } from "./types"; export const SettingsStore = createVersionedStore() .setKey("mw-settings") .addVersion({ version: 0, - create(): MWSettingsData { + create(): MWSettingsDataV1 { return { language: "en", captionSettings: { @@ -18,5 +18,29 @@ export const SettingsStore = createVersionedStore() }, }; }, + migrate(data: MWSettingsDataV1): MWSettingsData { + return { + captionSettings: { + language: "none", + ...data.captionSettings, + }, + }; + }, + }) + .addVersion({ + version: 1, + create(): MWSettingsData { + return { + captionSettings: { + delay: 0, + language: "none", + style: { + color: "#ffffff", + fontSize: 25, + backgroundColor: "#00000096", + }, + }, + }; + }, }) .build(); diff --git a/src/state/settings/types.ts b/src/state/settings/types.ts index b793308d..e32bd685 100644 --- a/src/state/settings/types.ts +++ b/src/state/settings/types.ts @@ -1,3 +1,5 @@ +import { LangCode } from "@/setup/iso6391"; + export interface CaptionStyleSettings { color: string; /** @@ -7,7 +9,7 @@ export interface CaptionStyleSettings { backgroundColor: string; } -export interface CaptionSettings { +export interface CaptionSettingsV1 { /** * Range is [-10, 10]s */ @@ -15,7 +17,19 @@ export interface CaptionSettings { style: CaptionStyleSettings; } +export interface CaptionSettings { + language: LangCode; + /** + * Range is [-10, 10]s + */ + delay: number; + style: CaptionStyleSettings; +} +export interface MWSettingsDataV1 { + language: LangCode; + captionSettings: CaptionSettingsV1; +} + export interface MWSettingsData { - language: string; captionSettings: CaptionSettings; } diff --git a/src/video/components/actions/CaptionRendererAction.tsx b/src/video/components/actions/CaptionRendererAction.tsx index ab7edba0..f3a65f52 100644 --- a/src/video/components/actions/CaptionRendererAction.tsx +++ b/src/video/components/actions/CaptionRendererAction.tsx @@ -8,7 +8,7 @@ import { useVideoPlayerDescriptor } from "../../state/hooks"; import { useProgress } from "../../state/logic/progress"; import { useSource } from "../../state/logic/source"; -function CaptionCue({ text }: { text?: string }) { +export function CaptionCue({ text, scale }: { text?: string; scale?: number }) { const { captionSettings } = useSettings(); const textWithNewlines = (text || "").replaceAll(/\r?\n/g, "
"); @@ -22,9 +22,14 @@ function CaptionCue({ text }: { text?: string }) { return (

void; + show: boolean; +}) { + const { + captionSettings, + setCaptionLanguage, + setCaptionBackgroundColor, + setCaptionColor, + setCaptionFontSize, + } = useSettings(); + const { t, i18n } = useTranslation(); + + const colors = ["#ffffff", "#00ffff", "#ffff00"]; + const selectedCaptionLanguage = useMemo( + () => captionLanguages.find((l) => l.id === captionSettings.language)!, + [captionSettings.language] + ); + const captionBackgroundOpacity = ( + (parseInt(captionSettings.style.backgroundColor.substring(7, 9), 16) / + 255) * + 100 + ).toFixed(0); + return ( + + +

+ {t("settings.title")} +
props.onClose()} className="hover:cursor-pointer"> + +
+
+
+
+ + l.id === i18n.language)! + } + setSelectedItem={(val) => { + i18n.changeLanguage(val.id); + }} + options={appLanguageOptions} + /> +
+
+ + { + setCaptionLanguage(val.id as LangCode); + }} + options={captionLanguages} + /> +
+
+
+
+ setCaptionFontSize(e.target.valueAsNumber)} + /> + + setCaptionBackgroundColor(e.target.valueAsNumber) + } + /> +
+ +
+ {colors.map((color) => ( +
setCaptionColor(color)} + > +
+ +
+ ))} +
+
+
+
+ {selectedCaptionLanguage.id !== "none" ? ( +
+ +
+ ) : null} +
+
+ + + ); +}