Add fullscreen preview for caption settings + optimize subtitle rendering

This commit is contained in:
mrjvs 2023-11-20 19:36:35 +01:00
parent 2ce42fdb85
commit 340673237b
3 changed files with 95 additions and 51 deletions

View file

@ -11,6 +11,10 @@ import { Transition } from "@/components/Transition";
import { usePlayerStore } from "@/stores/player/store";
import { SubtitleStyling, useSubtitleStore } from "@/stores/subtitles";
const wordOverrides: Record<string, string> = {
i: "I",
};
export function CaptionCue({
text,
styling,
@ -20,29 +24,29 @@ export function CaptionCue({
styling: SubtitleStyling;
overrideCasing: boolean;
}) {
const wordOverrides: Record<string, string> = {
i: "I",
};
const parsedHtml = useMemo(() => {
let textToUse = text;
if (overrideCasing && text) {
textToUse = text.slice(0, 1) + text.slice(1).toLowerCase();
}
let textToUse = text;
if (overrideCasing && text) {
textToUse = text.slice(0, 1) + text.slice(1).toLowerCase();
}
const textWithNewlines = (textToUse || "")
.split(" ")
.map((word) => wordOverrides[word] ?? word)
.join(" ")
.replaceAll(/ i'/g, " I'")
.replaceAll(/\r?\n/g, "<br />");
const textWithNewlines = (textToUse || "")
.split(" ")
.map((word) => wordOverrides[word] ?? word)
.join(" ")
.replaceAll(/ i'/g, " I'")
.replaceAll(/\r?\n/g, "<br />");
// https://www.w3.org/TR/webvtt1/#dom-construction-rules
// added a <br /> for newlines
const html = sanitize(textWithNewlines, {
ALLOWED_TAGS: ["c", "b", "i", "u", "span", "ruby", "rt", "br"],
ADD_TAGS: ["v", "lang"],
ALLOWED_ATTR: ["title", "lang"],
});
// https://www.w3.org/TR/webvtt1/#dom-construction-rules
// added a <br /> for newlines
const html = sanitize(textWithNewlines, {
ALLOWED_TAGS: ["c", "b", "i", "u", "span", "ruby", "rt", "br"],
ADD_TAGS: ["v", "lang"],
ALLOWED_ATTR: ["title", "lang"],
});
return html;
}, [text, overrideCasing]);
return (
<p
@ -57,7 +61,7 @@ export function CaptionCue({
// its sanitised a few lines up
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{
__html: html,
__html: parsedHtml,
}}
dir="auto"
/>

View file

@ -32,7 +32,7 @@ function SettingsLayout(props: { children: React.ReactNode }) {
)}
>
<SidebarPart />
<div className="space-y-16">{props.children}</div>
<div>{props.children}</div>
</div>
</WideContainer>
);
@ -80,13 +80,13 @@ export function SettingsPage() {
<RegisterCalloutPart />
)}
</div>
<div id="settings-locale">
<div id="settings-locale" className="mt-48">
<LocalePart />
</div>
<div id="settings-appearance">
<div id="settings-appearance" className="mt-48">
<ThemePart active={activeTheme} setTheme={setTheme} />
</div>
<div id="settings-captions">
<div id="settings-captions" className="mt-48">
<CaptionsPart />
</div>
</SettingsLayout>

View file

@ -1,3 +1,7 @@
import classNames from "classnames";
import { useState } from "react";
import { Icon, Icons } from "@/components/Icon";
import {
CaptionSetting,
ColorOption,
@ -5,12 +9,61 @@ import {
} from "@/components/player/atoms/settings/CaptionSettingsView";
import { Menu } from "@/components/player/internals/ContextMenu";
import { CaptionCue } from "@/components/player/Player";
import { Transition } from "@/components/Transition";
import { Heading1 } from "@/components/utils/Text";
import { useSubtitleStore } from "@/stores/subtitles";
import { SubtitleStyling, useSubtitleStore } from "@/stores/subtitles";
export function CaptionPreview(props: {
fullscreen?: boolean;
show?: boolean;
styling: SubtitleStyling;
onToggle: () => void;
}) {
return (
<div
className={classNames({
"pointer-events-none overflow-hidden w-full rounded": true,
"aspect-video relative": !props.fullscreen,
"fixed inset-0 z-50": props.fullscreen,
})}
>
<Transition animation="fade" show={props.show}>
<div
className="absolute inset-0 pointer-events-auto"
style={{
backgroundImage:
"radial-gradient(102.95% 87.07% at 100% 100%, #EEAA45 0%, rgba(165, 186, 151, 0.56) 54.69%, rgba(74, 207, 254, 0.00) 100%), linear-gradient(180deg, #48D3FF 0%, #3B27B2 100%)",
}}
>
<div
className="bg-black absolute right-3 top-3 text-white bg-opacity-25 duration-100 transition-[background-color,transform] active:scale-110 hover:bg-opacity-50 p-2 rounded-md cursor-pointer"
onClick={props.onToggle}
>
<Icon icon={props.fullscreen ? Icons.X : Icons.EXPAND} />
</div>
<div className="text-white pointer-events-none absolute flex w-full flex-col items-center transition-[bottom] bottom-0 p-4">
<div
className={
props.fullscreen ? "" : "transform origin-bottom text-[0.5rem]"
}
>
<CaptionCue
text="I must not fear. Fear is the mind-killer."
styling={props.styling}
overrideCasing={false}
/>
</div>
</div>
</div>
</Transition>
</div>
);
}
export function CaptionsPart() {
const styling = useSubtitleStore((s) => s.styling);
const isFullscreenPreview = false;
const [fullscreenPreview, setFullscreenPreview] = useState(false);
const updateStyling = useSubtitleStore((s) => s.updateStyling);
return (
@ -48,30 +101,17 @@ export function CaptionsPart() {
</div>
</div>
</div>
<div
className="w-full aspect-video rounded relative overflow-hidden"
style={{
backgroundImage:
"radial-gradient(102.95% 87.07% at 100% 100%, #EEAA45 0%, rgba(165, 186, 151, 0.56) 54.69%, rgba(74, 207, 254, 0.00) 100%), linear-gradient(180deg, #48D3FF 0%, #3B27B2 100%)",
}}
>
<div className="text-white pointer-events-none absolute flex w-full flex-col items-center transition-[bottom] bottom-0 p-4">
<div
className={
isFullscreenPreview
? ""
: "transform origin-bottom text-[0.5rem]"
}
>
<CaptionCue
// Can we keep this Dune quote 🥺
text="I must not fear. Fear is the mind-killer."
styling={styling}
overrideCasing={false}
/>
</div>
</div>
</div>
<CaptionPreview
show
styling={styling}
onToggle={() => setFullscreenPreview((s) => !s)}
/>
<CaptionPreview
show={fullscreenPreview}
fullscreen
styling={styling}
onToggle={() => setFullscreenPreview((s) => !s)}
/>
</div>
</div>
);