correct languages + settings page styling

Co-authored-by: Jip Frijlink <JipFr@users.noreply.github.com>
This commit is contained in:
mrjvs 2023-10-27 21:51:14 +02:00
parent 1cbf9f3c45
commit 9ff603f87c
18 changed files with 301 additions and 62 deletions

View File

@ -8,6 +8,7 @@
"@headlessui/react": "^1.5.0", "@headlessui/react": "^1.5.0",
"@movie-web/providers": "^1.0.2", "@movie-web/providers": "^1.0.2",
"@react-spring/web": "^9.7.1", "@react-spring/web": "^9.7.1",
"@sozialhelden/ietf-language-tags": "^5.4.2",
"classnames": "^2.3.2", "classnames": "^2.3.2",
"core-js": "^3.29.1", "core-js": "^3.29.1",
"dompurify": "^3.0.1", "dompurify": "^3.0.1",

15
pnpm-lock.yaml generated
View File

@ -17,6 +17,9 @@ dependencies:
'@react-spring/web': '@react-spring/web':
specifier: ^9.7.1 specifier: ^9.7.1
version: 9.7.3(react-dom@17.0.2)(react@17.0.2) version: 9.7.3(react-dom@17.0.2)(react@17.0.2)
'@sozialhelden/ietf-language-tags':
specifier: ^5.4.2
version: 5.4.2
classnames: classnames:
specifier: ^2.3.2 specifier: ^2.3.2
version: 2.3.2 version: 2.3.2
@ -1956,6 +1959,13 @@ packages:
rollup: 2.79.1 rollup: 2.79.1
dev: true dev: true
/@sozialhelden/ietf-language-tags@5.4.2:
resolution: {integrity: sha512-aCN7bVOfX9sBN0EHyWJT14H8bx+VYBo8tdcynai35wgoxKMfVtgEECkQ1gs8nEL6GHGes8lPIfo6AjIch44N3w==}
dependencies:
lodash.compact: 3.0.1
typescript: 4.9.5
dev: false
/@surma/rollup-plugin-off-main-thread@2.2.3: /@surma/rollup-plugin-off-main-thread@2.2.3:
resolution: {integrity: sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==} resolution: {integrity: sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==}
dependencies: dependencies:
@ -4456,6 +4466,10 @@ packages:
p-locate: 5.0.0 p-locate: 5.0.0
dev: true dev: true
/lodash.compact@3.0.1:
resolution: {integrity: sha512-2ozeiPi+5eBXW1CLtzjk8XQFhQOEMwwfxblqeq6EGyTxZJ1bPATqilY0e6g2SLQpP4KuMeuioBhEnWz5Pr7ICQ==}
dev: false
/lodash.debounce@4.0.8: /lodash.debounce@4.0.8:
resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==}
dev: true dev: true
@ -5986,7 +6000,6 @@ packages:
resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==} resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==}
engines: {node: '>=4.2.0'} engines: {node: '>=4.2.0'}
hasBin: true hasBin: true
dev: true
/ufo@1.3.0: /ufo@1.3.0:
resolution: {integrity: sha512-bRn3CsoojyNStCZe0BG0Mt4Nr/4KF+rhFlnNXybgqt5pXHNFRlqinSoQaTrGyzE4X8aHplSb+TorH+COin9Yxw==} resolution: {integrity: sha512-bRn3CsoojyNStCZe0BG0Mt4Nr/4KF+rhFlnNXybgqt5pXHNFRlqinSoQaTrGyzE4X8aHplSb+TorH+COin9Yxw==}

View File

@ -95,10 +95,6 @@ export async function searchSubtitles(
}); });
} }
export function languageIdToName(langId: string): string | null {
return languageMap[langId]?.nativeName ?? null;
}
export async function downloadSrt(legacySubId: string): Promise<string> { export async function downloadSrt(legacySubId: string): Promise<string> {
// TODO there is cloudflare protection so this may not always work. what to do about that? // TODO there is cloudflare protection so this may not always work. what to do about that?
// TODO also there is ratelimit on the page itself // TODO also there is ratelimit on the page itself

View File

@ -8,7 +8,7 @@ interface Props {
icon?: Icons; icon?: Icons;
onClick?: () => void; onClick?: () => void;
children?: ReactNode; children?: ReactNode;
theme?: "white" | "purple" | "secondary"; theme?: "white" | "purple" | "secondary" | "danger";
padding?: string; padding?: string;
className?: string; className?: string;
href?: string; href?: string;
@ -26,6 +26,8 @@ export function Button(props: Props) {
if (props.theme === "secondary") if (props.theme === "secondary")
colorClasses = colorClasses =
"bg-video-buttons-cancel hover:bg-video-buttons-cancelHover transition-colors duration-100 text-white"; "bg-video-buttons-cancel hover:bg-video-buttons-cancelHover transition-colors duration-100 text-white";
if (props.theme === "danger")
colorClasses = "bg-buttons-danger hover:bg-buttons-dangerHover text-white";
let classes = classNames( let classes = classNames(
"cursor-pointer inline-flex items-center justify-center rounded-lg font-medium transition-[transform,background-color] duration-100 active:scale-105 md:px-8", "cursor-pointer inline-flex items-center justify-center rounded-lg font-medium transition-[transform,background-color] duration-100 active:scale-105 md:px-8",

View File

@ -1,9 +1,11 @@
import classNames from "classnames";
import { ReactNode } from "react"; import { ReactNode } from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { IconPatch } from "@/components/buttons/IconPatch"; import { IconPatch } from "@/components/buttons/IconPatch";
import { Icons } from "@/components/Icon"; import { Icons } from "@/components/Icon";
import { Lightbar } from "@/components/utils/Lightbar"; import { Lightbar } from "@/components/utils/Lightbar";
import { BlurEllipsis } from "@/pages/layouts/SubPageLayout";
import { conf } from "@/setup/config"; import { conf } from "@/setup/config";
import { useBannerSize } from "@/stores/banner"; import { useBannerSize } from "@/stores/banner";
@ -13,10 +15,12 @@ export interface NavigationProps {
children?: ReactNode; children?: ReactNode;
bg?: boolean; bg?: boolean;
noLightbar?: boolean; noLightbar?: boolean;
doBackground?: boolean;
} }
export function Navigation(props: NavigationProps) { export function Navigation(props: NavigationProps) {
const bannerHeight = useBannerSize(); const bannerHeight = useBannerSize();
return ( return (
<> <>
{!props.noLightbar ? ( {!props.noLightbar ? (
@ -37,7 +41,17 @@ export function Navigation(props: NavigationProps) {
top: `${bannerHeight}px`, top: `${bannerHeight}px`,
}} }}
> >
<div className="fixed left-0 right-0 flex items-center"> <div
className={classNames(
"fixed left-0 right-0 flex items-center",
props.doBackground
? "bg-background-main border-b border-utils-divider border-opacity-50 overflow-hidden"
: null
)}
>
{props.doBackground ? (
<BlurEllipsis positionClass="absolute" />
) : null}
<div <div
className={`${ className={`${
props.bg ? "opacity-100" : "opacity-0" props.bg ? "opacity-100" : "opacity-0"

View File

@ -3,16 +3,13 @@ import { ReactNode, useRef, useState } from "react";
import { useAsync, useAsyncFn } from "react-use"; import { useAsync, useAsyncFn } from "react-use";
import { convert } from "subsrt-ts"; import { convert } from "subsrt-ts";
import { import { SubtitleSearchItem, subtitleTypeList } from "@/backend/helpers/subs";
SubtitleSearchItem,
languageIdToName,
subtitleTypeList,
} from "@/backend/helpers/subs";
import { FlagIcon } from "@/components/FlagIcon"; import { FlagIcon } from "@/components/FlagIcon";
import { useCaptions } from "@/components/player/hooks/useCaptions"; import { useCaptions } from "@/components/player/hooks/useCaptions";
import { Menu } from "@/components/player/internals/ContextMenu"; import { Menu } from "@/components/player/internals/ContextMenu";
import { Input } from "@/components/player/internals/ContextMenu/Input"; import { Input } from "@/components/player/internals/ContextMenu/Input";
import { SelectableLink } from "@/components/player/internals/ContextMenu/Links"; import { SelectableLink } from "@/components/player/internals/ContextMenu/Links";
import { getLanguageFromIETF } from "@/components/player/utils/language";
import { useOverlayRouter } from "@/hooks/useOverlayRouter"; import { useOverlayRouter } from "@/hooks/useOverlayRouter";
import { usePlayerStore } from "@/stores/player/store"; import { usePlayerStore } from "@/stores/player/store";
import { useSubtitleStore } from "@/stores/subtitles"; import { useSubtitleStore } from "@/stores/subtitles";
@ -35,6 +32,7 @@ export function CaptionOption(props: {
he: "il", he: "il",
ze: "cn", ze: "cn",
ar: "sa", ar: "sa",
ja: "jp",
}; };
let countryCode = let countryCode =
(props.countryCode || "")?.split("-").pop()?.toLowerCase() || ""; (props.countryCode || "")?.split("-").pop()?.toLowerCase() || "";
@ -155,7 +153,8 @@ export function CaptionsView({ id }: { id: string }) {
else if (req.error) content = <p>errored!</p>; else if (req.error) content = <p>errored!</p>;
else if (req.value) { else if (req.value) {
const subs = req.value.map((v) => { const subs = req.value.map((v) => {
const languageName = languageIdToName(v.attributes.language) ?? "unknown"; const languageName =
getLanguageFromIETF(v.attributes.language) ?? "unknown";
return { return {
...v, ...v,
languageName, languageName,

View File

@ -1,10 +1,10 @@
import { useMemo } from "react"; import { useMemo } from "react";
import { languageIdToName } from "@/backend/helpers/subs";
import { Toggle } from "@/components/buttons/Toggle"; import { Toggle } from "@/components/buttons/Toggle";
import { Icon, Icons } from "@/components/Icon"; import { Icon, Icons } from "@/components/Icon";
import { useCaptions } from "@/components/player/hooks/useCaptions"; import { useCaptions } from "@/components/player/hooks/useCaptions";
import { Menu } from "@/components/player/internals/ContextMenu"; import { Menu } from "@/components/player/internals/ContextMenu";
import { getLanguageFromIETF } from "@/components/player/utils/language";
import { useOverlayRouter } from "@/hooks/useOverlayRouter"; import { useOverlayRouter } from "@/hooks/useOverlayRouter";
import { usePlayerStore } from "@/stores/player/store"; import { usePlayerStore } from "@/stores/player/store";
import { qualityToString } from "@/stores/player/utils/qualities"; import { qualityToString } from "@/stores/player/utils/qualities";
@ -26,7 +26,7 @@ export function SettingsMenu({ id }: { id: string }) {
const { toggleLastUsed } = useCaptions(); const { toggleLastUsed } = useCaptions();
const selectedLanguagePretty = selectedCaptionLanguage const selectedLanguagePretty = selectedCaptionLanguage
? languageIdToName(selectedCaptionLanguage) ?? "unknown" ? getLanguageFromIETF(selectedCaptionLanguage) ?? "unknown"
: undefined; : undefined;
const source = usePlayerStore((s) => s.source); const source = usePlayerStore((s) => s.source);

View File

@ -68,13 +68,14 @@ export function CaptionCue({
export function SubtitleRenderer() { export function SubtitleRenderer() {
const videoTime = usePlayerStore((s) => s.progress.time); const videoTime = usePlayerStore((s) => s.progress.time);
const srtData = usePlayerStore((s) => s.caption.selected?.srtData); const srtData = usePlayerStore((s) => s.caption.selected?.srtData);
const language = usePlayerStore((s) => s.caption.selected?.language);
const styling = useSubtitleStore((s) => s.styling); const styling = useSubtitleStore((s) => s.styling);
const overrideCasing = useSubtitleStore((s) => s.overrideCasing); const overrideCasing = useSubtitleStore((s) => s.overrideCasing);
const delay = useSubtitleStore((s) => s.delay); const delay = useSubtitleStore((s) => s.delay);
const parsedCaptions = useMemo( const parsedCaptions = useMemo(
() => (srtData ? parseSubtitles(srtData) : []), () => (srtData ? parseSubtitles(srtData, language) : []),
[srtData] [srtData, language]
); );
const visibileCaptions = useMemo( const visibileCaptions = useMemo(

View File

@ -47,7 +47,10 @@ export function convertSubtitlesToSrt(text: string): string {
return srt; return srt;
} }
export function parseSubtitles(text: string): CaptionCueType[] { export function parseSubtitles(
text: string,
_language?: string
): CaptionCueType[] {
const vtt = convertSubtitlesToVtt(text); const vtt = convertSubtitlesToVtt(text);
return parse(vtt).filter((cue) => cue.type === "caption") as CaptionCueType[]; return parse(vtt).filter((cue) => cue.type === "caption") as CaptionCueType[];
} }

View File

@ -0,0 +1,14 @@
import { getTag } from "@sozialhelden/ietf-language-tags";
export function getLanguageFromIETF(ietf: string): string | null {
const tag = getTag(ietf, true);
const lang = tag?.language?.Description?.[0] ?? null;
if (!lang) return null;
const region = tag?.region?.Description?.[0] ?? null;
let regionText = "";
if (region) regionText = ` (${region})`;
return `${lang}${regionText}`;
}

View File

@ -75,4 +75,4 @@
100% { 100% {
transform: rotate(180deg) translateZ(0px) translateY(400px) scaleX(1); transform: rotate(180deg) translateZ(0px) translateY(400px) scaleX(1);
} }
} }

View File

@ -1,3 +1,4 @@
import classNames from "classnames";
import { useEffect, useRef } from "react"; import { useEffect, useRef } from "react";
import "./Lightbar.css"; import "./Lightbar.css";
@ -161,7 +162,12 @@ function ParticlesCanvas() {
export function Lightbar(props: { className?: string }) { export function Lightbar(props: { className?: string }) {
return ( return (
<div className={props.className}> <div
className={classNames(
"grid grid-cols-[100%] w-full overflow-x-hidden",
props.className
)}
>
<div className="lightbar"> <div className="lightbar">
<ParticlesCanvas /> <ParticlesCanvas />
<div className="lightbar-visual" /> <div className="lightbar-visual" />

View File

@ -1,13 +1,17 @@
interface TextProps { interface TextProps {
className?: string; className?: string;
children: React.ReactNode; children: React.ReactNode;
border?: boolean;
} }
const borderClass = "pb-4 border-b border-utils-divider border-opacity-50";
export function Heading1(props: TextProps) { export function Heading1(props: TextProps) {
return ( return (
<h1 <h1
className={[ className={[
"text-5xl font-bold text-white mb-9", "text-5xl font-bold text-white mb-9",
props.border ? borderClass : null,
props.className ?? "", props.className ?? "",
].join(" ")} ].join(" ")}
> >
@ -21,6 +25,21 @@ export function Heading2(props: TextProps) {
<h2 <h2
className={[ className={[
"text-3xl font-bold text-white mt-20 mb-9", "text-3xl font-bold text-white mt-20 mb-9",
props.border ? borderClass : null,
props.className ?? "",
].join(" ")}
>
{props.children}
</h2>
);
}
export function Heading3(props: TextProps) {
return (
<h2
className={[
"text-xl font-bold text-white mb-3",
props.border ? borderClass : null,
props.className ?? "", props.className ?? "",
].join(" ")} ].join(" ")}
> >
@ -34,6 +53,7 @@ export function Paragraph(props: TextProps) {
<p <p
className={[ className={[
"text-type-text my-9 font-medium", "text-type-text my-9 font-medium",
props.border ? borderClass : null,
props.className ?? "", props.className ?? "",
].join(" ")} ].join(" ")}
> >

View File

@ -1,7 +1,12 @@
import classNames from "classnames";
import { useHistory } from "react-router-dom";
import Sticky from "react-stickynode";
import { Button } from "@/components/Button";
import { Icon, Icons } from "@/components/Icon"; import { Icon, Icons } from "@/components/Icon";
import { WideContainer } from "@/components/layout/WideContainer"; import { WideContainer } from "@/components/layout/WideContainer";
import { Divider } from "@/components/utils/Divider"; import { Divider } from "@/components/utils/Divider";
import { Heading1 } from "@/components/utils/Text"; import { Heading1, Heading2, Heading3 } from "@/components/utils/Text";
import { conf } from "@/setup/config"; import { conf } from "@/setup/config";
import { SubPageLayout } from "./layouts/SubPageLayout"; import { SubPageLayout } from "./layouts/SubPageLayout";
@ -19,27 +24,59 @@ function SidebarSection(props: { title: string; children: React.ReactNode }) {
); );
} }
function SidebarLink(props: { children: React.ReactNode; icon: Icons }) { function SidebarLink(props: {
children: React.ReactNode;
icon: Icons;
active?: boolean;
}) {
const history = useHistory();
const goToPage = (link: string) => {
history.push(link);
};
return ( return (
<div className="w-full px-2 py-1 flex items-center space-x-3"> <a
onClick={() => goToPage("/settings")}
className={classNames(
"w-full px-3 py-2 flex items-center space-x-3 cursor-pointer rounded my-2",
props.active
? "bg-settings-sidebar-activeLink text-settings-sidebar-type-activated"
: null
)}
>
<Icon <Icon
className="text-2xl text-settings-sidebar-type-icon" className={classNames(
"text-2xl text-settings-sidebar-type-icon",
props.active ? "text-settings-sidebar-type-iconActivated" : null
)}
icon={props.icon} icon={props.icon}
/> />
<span>{props.children}</span> <span>{props.children}</span>
</div> </a>
); );
} }
function SettingsSidebar() { function SettingsSidebar() {
// 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;
return ( return (
<div> <div>
<div className="sticky top-24 text-settings-sidebar-type-inactive"> <Sticky
enabled
top={10 * rem} // 10rem
className="text-settings-sidebar-type-inactive"
>
<SidebarSection title="Settings"> <SidebarSection title="Settings">
<SidebarLink icon={Icons.WAND}>Account</SidebarLink> {/* I looked over at my bookshelf to come up with these links */}
<SidebarLink icon={Icons.WAND}>A war in my name!</SidebarLink>
<SidebarLink active icon={Icons.COMPRESS}>
TANSTAAFL
</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 />
<SidebarSection title="App information"> <SidebarSection title="App information">
@ -52,7 +89,7 @@ function SettingsSidebar() {
<span className="text-right">{hostname}</span> <span className="text-right">{hostname}</span>
</div> </div>
</SidebarSection> </SidebarSection>
</div> </Sticky>
</div> </div>
); );
} }
@ -62,17 +99,118 @@ function SettingsLayout(props: { children: React.ReactNode }) {
<WideContainer ultraWide> <WideContainer ultraWide>
<div className="grid grid-cols-[260px,1fr] gap-12"> <div className="grid grid-cols-[260px,1fr] gap-12">
<SettingsSidebar /> <SettingsSidebar />
{props.children} <div className="space-y-16">{props.children}</div>
</div> </div>
</WideContainer> </WideContainer>
); );
} }
function SecondaryLabel(props: { children: React.ReactNode }) {
return <p className="text-type-text">{props.children}</p>;
}
function Card(props: {
children: React.ReactNode;
className?: string;
paddingClass?: string;
}) {
return (
<div
className={classNames(
"w-full rounded-lg bg-settings-card-background bg-opacity-[0.15] border border-settings-card-border",
props.paddingClass ?? "px-8 py-6",
props.className
)}
>
{props.children}
</div>
);
}
function AltCard(props: {
children: React.ReactNode;
className?: string;
paddingClass?: string;
}) {
return (
<div
className={classNames(
"w-full rounded-lg bg-settings-card-altBackground bg-opacity-50",
props.paddingClass ?? "px-8 py-6",
props.className
)}
>
{props.children}
</div>
);
}
function AccountSection() {
return (
<div>
<Heading1 border>Account</Heading1>
<Card>Beep beep</Card>
</div>
);
}
function DevicesSection() {
const devices = [
"Jip's iPhone",
"Muad'Dib's Nintendo Switch",
"Oppenheimer's old-ass phone",
];
return (
<div>
<Heading2 border className="mt-0 mb-9">
Devices
</Heading2>
<div className="space-y-5">
{devices.map((deviceName) => (
<Card
className="flex justify-between items-center"
paddingClass="px-6 py-4"
key={deviceName}
>
<div className="font-medium">
<SecondaryLabel>Device name</SecondaryLabel>
<p className="text-white">{deviceName}</p>
</div>
<Button theme="danger">Remove</Button>
</Card>
))}
</div>
</div>
);
}
function ActionsSection() {
return (
<div>
<Heading2 border>Actions</Heading2>
<AltCard paddingClass="px-6 py-12" className="grid grid-cols-2 gap-12">
<div>
<Heading3>Delete account</Heading3>
<p className="text-type-text">
This action is irreversible. All data will be deleted and nothing
can be recovered.
</p>
</div>
<div className="flex justify-end items-center">
<Button theme="danger">Delete account</Button>
</div>
</AltCard>
</div>
);
}
export function SettingsPage() { export function SettingsPage() {
return ( return (
<SubPageLayout> <SubPageLayout>
<SettingsLayout> <SettingsLayout>
<Heading1>Setting</Heading1> <AccountSection />
<DevicesSection />
<ActionsSection />
</SettingsLayout> </SettingsLayout>
</SubPageLayout> </SubPageLayout>
); );

View File

@ -1,12 +1,24 @@
import classNames from "classnames";
import { FooterView } from "@/components/layout/Footer"; import { FooterView } from "@/components/layout/Footer";
import { Navigation } from "@/components/layout/Navigation"; import { Navigation } from "@/components/layout/Navigation";
export function BlurEllipsis() { export function BlurEllipsis(props: { positionClass?: string }) {
return ( return (
<> <>
{/* Blur elipsis */} {/* Blur elipsis */}
<div className="absolute top-0 -right-48 rotate-[32deg] w-[50rem] h-[15rem] rounded-[70rem] bg-background-accentA blur-[100px] pointer-events-none opacity-25" /> <div
<div className="absolute top-0 right-48 rotate-[32deg] w-[50rem] h-[15rem] rounded-[70rem] bg-background-accentB blur-[100px] pointer-events-none opacity-25" /> className={classNames(
props.positionClass ?? "fixed",
"top-0 -right-48 rotate-[32deg] w-[50rem] h-[15rem] rounded-[70rem] bg-background-accentA blur-[100px] pointer-events-none opacity-25"
)}
/>
<div
className={classNames(
props.positionClass ?? "fixed",
"top-0 right-48 rotate-[32deg] w-[50rem] h-[15rem] rounded-[70rem] bg-background-accentB blur-[100px] pointer-events-none opacity-25"
)}
/>
</> </>
); );
} }
@ -23,7 +35,7 @@ export function SubPageLayout(props: { children: React.ReactNode }) {
<BlurEllipsis /> <BlurEllipsis />
{/* Main page */} {/* Main page */}
<FooterView> <FooterView>
<Navigation noLightbar /> <Navigation doBackground noLightbar />
<div className="mt-40">{props.children}</div> <div className="mt-40">{props.children}</div>
</FooterView> </FooterView>
</div> </div>

View File

@ -69,7 +69,9 @@ module.exports = {
// Buttons // Buttons
buttons: { buttons: {
toggle: "#8D44D6", toggle: "#8D44D6",
toggleDisabled: "#202836" toggleDisabled: "#202836",
danger: "#792131",
dangerHover: "#8a293b"
}, },
// only used for body colors/textures // only used for body colors/textures
@ -111,12 +113,21 @@ module.exports = {
settings: { settings: {
sidebar: { sidebar: {
activeLink: "#171728",
type: { type: {
secondary: "#4B395F", secondary: "#4B395F",
inactive: "#8D68A9", inactive: "#8D68A9",
icon: "#926CAD", icon: "#926CAD",
iconActivated: "#6942A8",
activated: "#CBA1E8" activated: "#CBA1E8"
} }
},
card: {
border: "#2A243E",
background: "#29243D",
altBackground: "#29243D"
} }
}, },

View File

@ -17,7 +17,10 @@
"jsx": "react-jsx", "jsx": "react-jsx",
"baseUrl": "./src", "baseUrl": "./src",
"paths": { "paths": {
"@/*": ["./*"] "@/*": ["./*"],
"@sozialhelden/ietf-language-tags": [
"../node_modules/@sozialhelden/ietf-language-tags/dist/cjs"
]
}, },
"types": ["vite/client", "vite-plugin-pwa/vanillajs"] "types": ["vite/client", "vite-plugin-pwa/vanillajs"]
}, },

View File

@ -14,10 +14,12 @@ export default defineConfig(({ mode }) => {
handlebars({ handlebars({
vars: { vars: {
opensearchEnabled: env.VITE_OPENSEARCH_ENABLED === "true", opensearchEnabled: env.VITE_OPENSEARCH_ENABLED === "true",
routeDomain: env.VITE_APP_DOMAIN + (env.VITE_NORMAL_ROUTER !== 'true' ? "/#" : ""), routeDomain:
env.VITE_APP_DOMAIN +
(env.VITE_NORMAL_ROUTER !== "true" ? "/#" : ""),
domain: env.VITE_APP_DOMAIN, domain: env.VITE_APP_DOMAIN,
env, env
}, }
}), }),
react({ react({
babel: { babel: {
@ -29,23 +31,23 @@ export default defineConfig(({ mode }) => {
modules: false, modules: false,
useBuiltIns: "entry", useBuiltIns: "entry",
corejs: { corejs: {
version: "3.29", version: "3.29"
}, }
}, }
], ]
], ]
}, }
}), }),
VitePWA({ VitePWA({
disable: process.env.VITE_PWA_ENABLED !== "yes", disable: process.env.VITE_PWA_ENABLED !== "yes",
registerType: "autoUpdate", registerType: "autoUpdate",
workbox: { workbox: {
globIgnores: ["**ping.txt**"], globIgnores: ["**ping.txt**"]
}, },
includeAssets: [ includeAssets: [
"favicon.ico", "favicon.ico",
"apple-touch-icon.png", "apple-touch-icon.png",
"safari-pinned-tab.svg", "safari-pinned-tab.svg"
], ],
manifest: { manifest: {
name: "movie-web", name: "movie-web",
@ -61,53 +63,57 @@ export default defineConfig(({ mode }) => {
src: "android-chrome-192x192.png", src: "android-chrome-192x192.png",
sizes: "192x192", sizes: "192x192",
type: "image/png", type: "image/png",
purpose: "any", purpose: "any"
}, },
{ {
src: "android-chrome-512x512.png", src: "android-chrome-512x512.png",
sizes: "512x512", sizes: "512x512",
type: "image/png", type: "image/png",
purpose: "any", purpose: "any"
}, },
{ {
src: "android-chrome-192x192.png", src: "android-chrome-192x192.png",
sizes: "192x192", sizes: "192x192",
type: "image/png", type: "image/png",
purpose: "maskable", purpose: "maskable"
}, },
{ {
src: "android-chrome-512x512.png", src: "android-chrome-512x512.png",
sizes: "512x512", sizes: "512x512",
type: "image/png", type: "image/png",
purpose: "maskable", purpose: "maskable"
}, }
], ]
}, }
}), }),
loadVersion(), loadVersion(),
checker({ checker({
overlay: { overlay: {
position: "tr", position: "tr"
}, },
typescript: true, // check typescript build errors in dev server typescript: true, // check typescript build errors in dev server
eslint: { eslint: {
// check lint errors in dev server // check lint errors in dev server
lintCommand: "eslint --ext .tsx,.ts src", lintCommand: "eslint --ext .tsx,.ts src",
dev: { dev: {
logLevel: ["error"], logLevel: ["error"]
}, }
}, }
}), })
], ],
resolve: { resolve: {
alias: { alias: {
"@": path.resolve(__dirname, "./src"), "@": path.resolve(__dirname, "./src"),
}, "@sozialhelden/ietf-language-tags": path.resolve(
__dirname,
"./node_modules/@sozialhelden/ietf-language-tags/dist/cjs"
)
}
}, },
test: { test: {
environment: "jsdom", environment: "jsdom"
}, }
}; };
}); });