caption settings + working settings sidebar

This commit is contained in:
mrjvs 2023-11-18 19:27:19 +01:00
parent d8913bb2b7
commit 54cd1d52ca
8 changed files with 186 additions and 29 deletions

View File

@ -5,9 +5,10 @@ import { Icon, Icons } from "@/components/Icon";
export function SidebarSection(props: { export function SidebarSection(props: {
title: string; title: string;
children: React.ReactNode; children: React.ReactNode;
className?: string;
}) { }) {
return ( return (
<section> <section className={props.className ?? ""}>
<p className="text-sm font-bold uppercase text-settings-sidebar-type-secondary mb-2"> <p className="text-sm font-bold uppercase text-settings-sidebar-type-secondary mb-2">
{props.title} {props.title}
</p> </p>

View File

@ -33,7 +33,7 @@ export function ColorOption(props: {
); );
} }
function CaptionSetting(props: { export function CaptionSetting(props: {
textTransformer?: (s: string) => string; textTransformer?: (s: string) => string;
value: number; value: number;
onChange?: (val: number) => void; onChange?: (val: number) => void;
@ -209,7 +209,7 @@ function CaptionSetting(props: {
); );
} }
const colors = ["#ffffff", "#80b1fa", "#e2e535"]; export const colors = ["#ffffff", "#80b1fa", "#e2e535"];
export function CaptionSettingsView({ id }: { id: string }) { export function CaptionSettingsView({ id }: { id: string }) {
const router = useOverlayRouter(id); const router = useOverlayRouter(id);

View File

@ -46,10 +46,10 @@ export function CaptionCue({
return ( return (
<p <p
className="pointer-events-none mb-1 select-none rounded px-4 py-1 text-center [text-shadow:0_2px_4px_rgba(0,0,0,0.5)]" className="pointer-events-none mb-1 select-none rounded px-4 py-1 text-center leading-normal [text-shadow:0_2px_4px_rgba(0,0,0,0.5)]"
style={{ style={{
color: styling.color, color: styling.color,
fontSize: `${(1.5 * styling.size).toFixed(2)}rem`, fontSize: `${(1.5 * styling.size).toFixed(2)}em`,
backgroundColor: `rgba(0,0,0,${styling.backgroundOpacity.toFixed(2)})`, backgroundColor: `rgba(0,0,0,${styling.backgroundOpacity.toFixed(2)})`,
}} }}
> >

View File

@ -1,3 +1,4 @@
import classNames from "classnames";
import { useEffect } from "react"; import { useEffect } from "react";
import { useAsyncFn } from "react-use"; import { useAsyncFn } from "react-use";
@ -5,8 +6,10 @@ import { getSessions } from "@/backend/accounts/sessions";
import { WideContainer } from "@/components/layout/WideContainer"; import { WideContainer } from "@/components/layout/WideContainer";
import { Heading1 } from "@/components/utils/Text"; import { Heading1 } from "@/components/utils/Text";
import { useBackendUrl } from "@/hooks/auth/useBackendUrl"; import { useBackendUrl } from "@/hooks/auth/useBackendUrl";
import { useIsMobile } from "@/hooks/useIsMobile";
import { AccountActionsPart } from "@/pages/settings/AccountActionsPart"; import { AccountActionsPart } from "@/pages/settings/AccountActionsPart";
import { AccountEditPart } from "@/pages/settings/AccountEditPart"; import { AccountEditPart } from "@/pages/settings/AccountEditPart";
import { CaptionsPart } from "@/pages/settings/CaptionsPart";
import { DeviceListPart } from "@/pages/settings/DeviceListPart"; import { DeviceListPart } from "@/pages/settings/DeviceListPart";
import { RegisterCalloutPart } from "@/pages/settings/RegisterCalloutPart"; import { RegisterCalloutPart } from "@/pages/settings/RegisterCalloutPart";
import { SidebarPart } from "@/pages/settings/SidebarPart"; import { SidebarPart } from "@/pages/settings/SidebarPart";
@ -17,9 +20,16 @@ import { useThemeStore } from "@/stores/theme";
import { SubPageLayout } from "./layouts/SubPageLayout"; import { SubPageLayout } from "./layouts/SubPageLayout";
function SettingsLayout(props: { children: React.ReactNode }) { function SettingsLayout(props: { children: React.ReactNode }) {
const { isMobile } = useIsMobile();
return ( return (
<WideContainer ultraWide> <WideContainer ultraWide>
<div className="grid grid-cols-[260px,1fr] gap-12"> <div
className={classNames(
"grid gap-12",
isMobile ? "grid-cols-1" : "lg:grid-cols-[260px,1fr]"
)}
>
<SidebarPart /> <SidebarPart />
<div className="space-y-16">{props.children}</div> <div className="space-y-16">{props.children}</div>
</div> </div>
@ -59,6 +69,7 @@ export function SettingsPage() {
return ( return (
<SubPageLayout> <SubPageLayout>
<SettingsLayout> <SettingsLayout>
<div id="settings-account">
<Heading1 border className="!mb-0"> <Heading1 border className="!mb-0">
Account Account
</Heading1> </Heading1>
@ -67,7 +78,13 @@ export function SettingsPage() {
) : ( ) : (
<RegisterCalloutPart /> <RegisterCalloutPart />
)} )}
</div>
<div id="settings-appearance">
<ThemePart active={activeTheme} setTheme={setTheme} /> <ThemePart active={activeTheme} setTheme={setTheme} />
</div>
<div id="settings-captions">
<CaptionsPart />
</div>
</SettingsLayout> </SettingsLayout>
</SubPageLayout> </SubPageLayout>
); );

View File

@ -25,7 +25,7 @@ export function AccountActionsPart() {
<Heading2 border>Actions</Heading2> <Heading2 border>Actions</Heading2>
<SolidSettingsCard <SolidSettingsCard
paddingClass="px-6 py-12" paddingClass="px-6 py-12"
className="grid grid-cols-2 gap-12" className="grid grid-cols-1 lg:grid-cols-2 gap-6 lg:gap-12"
> >
<div> <div>
<Heading3>Delete account</Heading3> <Heading3>Delete account</Heading3>
@ -34,7 +34,7 @@ export function AccountActionsPart() {
can be recovered. can be recovered.
</p> </p>
</div> </div>
<div className="flex justify-end items-center"> <div className="flex justify-start lg:justify-end items-center">
<Button <Button
theme="danger" theme="danger"
onClick={deleteExec} onClick={deleteExec}

View File

@ -0,0 +1,78 @@
import {
CaptionSetting,
ColorOption,
colors,
} from "@/components/player/atoms/settings/CaptionSettingsView";
import { Menu } from "@/components/player/internals/ContextMenu";
import { CaptionCue } from "@/components/player/Player";
import { Heading1 } from "@/components/utils/Text";
import { useSubtitleStore } from "@/stores/subtitles";
export function CaptionsPart() {
const styling = useSubtitleStore((s) => s.styling);
const isFullscreenPreview = false;
const updateStyling = useSubtitleStore((s) => s.updateStyling);
return (
<div>
<Heading1 border>Captions</Heading1>
<div className="grid grid-cols-[1fr,356px] gap-8">
<div className="space-y-6">
<CaptionSetting
label="Background opacity"
max={100}
min={0}
onChange={(v) => updateStyling({ backgroundOpacity: v / 100 })}
value={styling.backgroundOpacity * 100}
textTransformer={(s) => `${s}%`}
/>
<CaptionSetting
label="Text size"
max={200}
min={1}
textTransformer={(s) => `${s}%`}
onChange={(v) => updateStyling({ size: v / 100 })}
value={styling.size * 100}
/>
<div className="flex justify-between items-center">
<Menu.FieldTitle>Color</Menu.FieldTitle>
<div className="flex justify-center items-center">
{colors.map((v) => (
<ColorOption
onClick={() => updateStyling({ color: v })}
color={v}
active={styling.color === v}
key={v}
/>
))}
</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>
</div>
</div>
);
}

View File

@ -1,31 +1,92 @@
import { useCallback, useEffect, useState } from "react";
import Sticky from "react-stickynode"; import Sticky from "react-stickynode";
import { Icons } from "@/components/Icon"; import { Icons } from "@/components/Icon";
import { SidebarLink, SidebarSection } from "@/components/layout/Sidebar"; import { SidebarLink, SidebarSection } from "@/components/layout/Sidebar";
import { Divider } from "@/components/utils/Divider"; import { Divider } from "@/components/utils/Divider";
import { useIsMobile } from "@/hooks/useIsMobile";
import { conf } from "@/setup/config"; import { conf } from "@/setup/config";
const percentageVisible = 10;
export function SidebarPart() { export function SidebarPart() {
const { isMobile } = useIsMobile();
// eslint-disable-next-line no-restricted-globals // eslint-disable-next-line no-restricted-globals
const hostname = location.hostname; const hostname = location.hostname;
const rem = 16; const rem = 16;
const [activeLink, setActiveLink] = useState("");
const settingLinks = [
{ text: "Account", id: "settings-account", icon: Icons.USER },
{ text: "Appearance", id: "settings-appearance", icon: Icons.GITHUB },
{ text: "Captions", id: "settings-captions", icon: Icons.CAPTIONS },
];
useEffect(() => {
function recheck() {
const windowHeight =
window.innerHeight || document.documentElement.clientHeight;
const viewList = settingLinks
.map((link) => {
const el = document.getElementById(link.id);
if (!el) return { visible: false, link: link.id };
const rect = el.getBoundingClientRect();
const visible = !(
Math.floor(
100 - ((rect.top >= 0 ? 0 : rect.top) / +-rect.height) * 100
) < percentageVisible ||
Math.floor(
100 - ((rect.bottom - windowHeight) / rect.height) * 100
) < percentageVisible
);
return { visible, link: link.id };
})
.filter((v) => v.visible);
setActiveLink(viewList[0]?.link ?? "");
}
document.addEventListener("scroll", recheck);
recheck();
return () => {
document.removeEventListener("scroll", recheck);
};
});
const scrollTo = useCallback((id: string) => {
const el = document.getElementById(id);
if (!el) return null;
const y = el.getBoundingClientRect().top + window.scrollY;
window.scrollTo({
top: y - 120,
behavior: "smooth",
});
}, []);
return ( return (
<div> <div>
<Sticky <Sticky
enabled enabled={!isMobile}
top={10 * rem} // 10rem top={10 * rem} // 10rem
className="text-settings-sidebar-type-inactive" className="text-settings-sidebar-type-inactive"
> >
<div className="hidden lg:block">
<SidebarSection title="Settings"> <SidebarSection title="Settings">
<SidebarLink icon={Icons.WAND}>A war in my name!</SidebarLink> {settingLinks.map((v) => (
<SidebarLink active icon={Icons.COMPRESS}> <SidebarLink
TANSTAAFL icon={v.icon}
active={v.id === activeLink}
onClick={() => scrollTo(v.id)}
>
{v.text}
</SidebarLink> </SidebarLink>
<SidebarLink icon={Icons.AIRPLAY}>We all float down here</SidebarLink> ))}
<SidebarLink icon={Icons.BOOKMARK}>My skin is not my own</SidebarLink>
</SidebarSection> </SidebarSection>
<Divider /> <Divider />
</div>
<SidebarSection title="App information"> <SidebarSection title="App information">
<div className="flex justify-between items-center space-x-3"> <div className="flex justify-between items-center space-x-3">
<span>Version</span> <span>Version</span>

View File

@ -1,7 +1,7 @@
import classNames from "classnames"; import classNames from "classnames";
import { Icon, Icons } from "@/components/Icon"; import { Icon, Icons } from "@/components/Icon";
import { Heading2 } from "@/components/utils/Text"; import { Heading1 } from "@/components/utils/Text";
const availableThemes = [ const availableThemes = [
{ {
@ -117,7 +117,7 @@ export function ThemePart(props: {
}) { }) {
return ( return (
<div> <div>
<Heading2 border>Themes</Heading2> <Heading1 border>Appearence</Heading1>
<div className="grid grid-cols-[repeat(auto-fill,minmax(200px,1fr))] gap-6 max-w-[700px]"> <div className="grid grid-cols-[repeat(auto-fill,minmax(200px,1fr))] gap-6 max-w-[700px]">
{/* default theme */} {/* default theme */}
<ThemePreview <ThemePreview