mirror of
https://github.com/sussy-code/smov.git
synced 2024-12-29 16:07:40 +01:00
Some shoepolish for movie-web
Co-authored-by: Jip Frijlink <JipFr@users.noreply.github.com>
This commit is contained in:
parent
b1b604d322
commit
4dc3a3216a
18 changed files with 186 additions and 83 deletions
|
@ -46,6 +46,7 @@
|
||||||
"errors": {
|
"errors": {
|
||||||
"details": "Error details",
|
"details": "Error details",
|
||||||
"reloadPage": "Reload the page",
|
"reloadPage": "Reload the page",
|
||||||
|
"showError": "Show error details",
|
||||||
"badge": "It broke",
|
"badge": "It broke",
|
||||||
"title": "That's an error boss"
|
"title": "That's an error boss"
|
||||||
},
|
},
|
||||||
|
|
|
@ -81,20 +81,10 @@ export function UserAvatar(props: {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function NoUserAvatar(props: {
|
export function NoUserAvatar(props: { iconClass?: string }) {
|
||||||
sizeClass?: string;
|
|
||||||
iconClass?: string;
|
|
||||||
}) {
|
|
||||||
return (
|
return (
|
||||||
<div className="relative inline-block">
|
<div className="relative inline-block p-1 text-type-dimmed">
|
||||||
<div
|
|
||||||
className={classNames(
|
|
||||||
props.sizeClass ?? "w-[2rem] h-[2rem]",
|
|
||||||
"rounded-full overflow-hidden flex items-center justify-center text-type-dimmed hover:text-type-secondary bg-pill-background bg-opacity-50 hover:bg-opacity-100 transition-colors duration-100"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Icon className={props.iconClass ?? "text-xl"} icon={Icons.MENU} />
|
<Icon className={props.iconClass ?? "text-xl"} icon={Icons.MENU} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -109,7 +109,7 @@ export function LinksDropdown(props: { children: React.ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<div className="relative is-dropdown">
|
<div className="relative is-dropdown">
|
||||||
<div
|
<div
|
||||||
className="cursor-pointer tabbable rounded-full flex gap-2 text-white items-center py-2 px-3 bg-pill-background bg-opacity-50"
|
className="cursor-pointer tabbable rounded-full flex gap-2 text-white items-center py-2 px-3 bg-pill-background bg-opacity-50 hover:bg-pill-backgroundHover transition-[background,transform] duration-100 hover:scale-105"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onClick={toggleOpen}
|
onClick={toggleOpen}
|
||||||
onKeyUp={(evt) => evt.key === "Enter" && toggleOpen()}
|
onKeyUp={(evt) => evt.key === "Enter" && toggleOpen()}
|
||||||
|
|
|
@ -99,6 +99,8 @@ export function Button(props: Props) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sometimes you can't use normal button, due to not having access to a useHistory context
|
||||||
|
// When that happens, use this!
|
||||||
interface ButtonPlainProps {
|
interface ButtonPlainProps {
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
|
|
|
@ -21,7 +21,7 @@ export function Dropdown(props: DropdownProps) {
|
||||||
<Listbox value={props.selectedItem} onChange={props.setSelectedItem}>
|
<Listbox value={props.selectedItem} onChange={props.setSelectedItem}>
|
||||||
{() => (
|
{() => (
|
||||||
<>
|
<>
|
||||||
<Listbox.Button className="relative w-full cursor-default rounded-lg bg-dropdown-background py-3 pl-3 pr-10 text-left text-white shadow-md focus:outline-none tabbable">
|
<Listbox.Button className="relative w-full rounded-lg bg-dropdown-background py-3 pl-3 pr-10 text-left text-white shadow-md focus:outline-none tabbable cursor-pointer">
|
||||||
<span className="flex gap-4 items-center truncate">
|
<span className="flex gap-4 items-center truncate">
|
||||||
{props.selectedItem.leftIcon
|
{props.selectedItem.leftIcon
|
||||||
? props.selectedItem.leftIcon
|
? props.selectedItem.leftIcon
|
||||||
|
@ -45,7 +45,7 @@ export function Dropdown(props: DropdownProps) {
|
||||||
{props.options.map((opt) => (
|
{props.options.map((opt) => (
|
||||||
<Listbox.Option
|
<Listbox.Option
|
||||||
className={({ active }) =>
|
className={({ active }) =>
|
||||||
`flex gap-4 items-center relative cursor-default select-none py-3 pl-4 pr-4 ${
|
`cursor-pointer flex gap-4 items-center relative select-none py-3 pl-4 pr-4 ${
|
||||||
active
|
active
|
||||||
? "bg-background-secondaryHover text-type-link"
|
? "bg-background-secondaryHover text-type-link"
|
||||||
: "text-white"
|
: "text-white"
|
||||||
|
|
|
@ -24,7 +24,7 @@ export const SearchBarInput = forwardRef<HTMLInputElement, SearchBarProps>(
|
||||||
return (
|
return (
|
||||||
<Flare.Base
|
<Flare.Base
|
||||||
className={c({
|
className={c({
|
||||||
"hover:flare-enabled group relative flex flex-col rounded-[28px] transition-colors sm:flex-row sm:items-center":
|
"hover:flare-enabled group flex flex-col rounded-[28px] transition-colors sm:flex-row sm:items-center relative":
|
||||||
true,
|
true,
|
||||||
"bg-search-background": !focused,
|
"bg-search-background": !focused,
|
||||||
"bg-search-focused": focused,
|
"bg-search-focused": focused,
|
||||||
|
@ -40,7 +40,6 @@ export const SearchBarInput = forwardRef<HTMLInputElement, SearchBarProps>(
|
||||||
"bg-search-focused": focused,
|
"bg-search-focused": focused,
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Flare.Child className="flex flex-1 flex-col">
|
<Flare.Child className="flex flex-1 flex-col">
|
||||||
<div className="pointer-events-none absolute bottom-0 left-5 top-0 flex max-h-14 items-center text-search-icon">
|
<div className="pointer-events-none absolute bottom-0 left-5 top-0 flex max-h-14 items-center text-search-icon">
|
||||||
<Icon icon={Icons.SEARCH} />
|
<Icon icon={Icons.SEARCH} />
|
||||||
|
|
|
@ -55,7 +55,7 @@ export function Footer() {
|
||||||
</h3>
|
</h3>
|
||||||
<p className="mt-3">{t("footer.legal.disclaimerText")}</p>
|
<p className="mt-3">{t("footer.legal.disclaimerText")}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-x-[0.5rem] -ml-3">
|
<div className="flex flex-wrap gap-[0.5rem] -ml-3">
|
||||||
<FooterLink icon={Icons.GITHUB} href={conf().GITHUB_LINK}>
|
<FooterLink icon={Icons.GITHUB} href={conf().GITHUB_LINK}>
|
||||||
{t("footer.links.github")}
|
{t("footer.links.github")}
|
||||||
</FooterLink>
|
</FooterLink>
|
||||||
|
|
|
@ -59,6 +59,7 @@ export function Navigation(props: NavigationProps) {
|
||||||
<BlurEllipsis positionClass="absolute" />
|
<BlurEllipsis positionClass="absolute" />
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
<div className="opacity-0 absolute inset-0 block h-20 pointer-events-auto" />
|
||||||
<div
|
<div
|
||||||
className={`${
|
className={`${
|
||||||
props.bg ? "opacity-100" : "opacity-0"
|
props.bg ? "opacity-100" : "opacity-0"
|
||||||
|
@ -77,8 +78,8 @@ export function Navigation(props: NavigationProps) {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className={classNames("fixed left-0 right-0 flex items-center")}>
|
<div className={classNames("fixed left-0 right-0 flex items-center")}>
|
||||||
<div className="pointer-events-auto px-7 py-5 relative z-[60] flex flex-1 items-center">
|
<div className="px-7 py-5 relative z-[60] flex flex-1 items-center justify-between">
|
||||||
<div className="flex items-center flex-1 space-x-3">
|
<div className="flex items-center space-x-3 pointer-events-auto">
|
||||||
<Link className="block tabbable rounded-full" to="/">
|
<Link className="block tabbable rounded-full" to="/">
|
||||||
<BrandPill clickable />
|
<BrandPill clickable />
|
||||||
</Link>
|
</Link>
|
||||||
|
@ -99,7 +100,7 @@ export function Navigation(props: NavigationProps) {
|
||||||
<IconPatch icon={Icons.GITHUB} clickable downsized />
|
<IconPatch icon={Icons.GITHUB} clickable downsized />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div className="relative">
|
<div className="relative pointer-events-auto">
|
||||||
<LinksDropdown>
|
<LinksDropdown>
|
||||||
{loggedIn ? <UserAvatar withName /> : <NoUserAvatar />}
|
{loggedIn ? <UserAvatar withName /> : <NoUserAvatar />}
|
||||||
</LinksDropdown>
|
</LinksDropdown>
|
||||||
|
|
|
@ -23,7 +23,7 @@ export function AutoPlayStart() {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
className="group pointer-events-auto flex h-16 w-16 cursor-pointer items-center justify-center rounded-full text-white transition-[background-color,transform] hover:scale-125 active:scale-100"
|
className="group pointer-events-auto flex h-16 w-16 cursor-pointer items-center justify-center bg-video-autoPlay-background hover:bg-video-autoPlay-hover rounded-full text-white transition-[background-color,transform] hover:scale-125 active:scale-100"
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
icon={Icons.PLAY}
|
icon={Icons.PLAY}
|
||||||
|
|
|
@ -230,7 +230,7 @@ export function CaptionSettingsView({ id }: { id: string }) {
|
||||||
</Menu.BackLink>
|
</Menu.BackLink>
|
||||||
<Menu.Section className="space-y-6">
|
<Menu.Section className="space-y-6">
|
||||||
<CaptionSetting
|
<CaptionSetting
|
||||||
label={t("player.menus.captions.settings.fixCapitals")}
|
label={t("player.menus.captions.settings.delay")}
|
||||||
max={10}
|
max={10}
|
||||||
min={-10}
|
min={-10}
|
||||||
onChange={(v) => setDelay(v)}
|
onChange={(v) => setDelay(v)}
|
||||||
|
@ -241,7 +241,7 @@ export function CaptionSettingsView({ id }: { id: string }) {
|
||||||
/>
|
/>
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<Menu.FieldTitle>
|
<Menu.FieldTitle>
|
||||||
{t("player.menus.captions.settings.delay")}
|
{t("player.menus.captions.settings.fixCapitals")}
|
||||||
</Menu.FieldTitle>
|
</Menu.FieldTitle>
|
||||||
<div className="flex justify-center items-center">
|
<div className="flex justify-center items-center">
|
||||||
<Toggle
|
<Toggle
|
||||||
|
|
|
@ -3,10 +3,13 @@ import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { Button } from "@/components/buttons/Button";
|
import { Button } from "@/components/buttons/Button";
|
||||||
import { Icon, Icons } from "@/components/Icon";
|
import { Icon, Icons } from "@/components/Icon";
|
||||||
|
import { Modal } from "@/components/overlays/Modal";
|
||||||
import { DisplayError } from "@/components/player/display/displayInterface";
|
import { DisplayError } from "@/components/player/display/displayInterface";
|
||||||
|
|
||||||
export function ErrorCard(props: { error: DisplayError | string }) {
|
export function ErrorCard(props: {
|
||||||
const [showErrorCard, setShowErrorCard] = useState(true);
|
error: DisplayError | string;
|
||||||
|
onClose: () => void;
|
||||||
|
}) {
|
||||||
const [hasCopied, setHasCopied] = useState(false);
|
const [hasCopied, setHasCopied] = useState(false);
|
||||||
const hasCopiedUnsetDebounce = useRef<ReturnType<typeof setTimeout> | null>(
|
const hasCopiedUnsetDebounce = useRef<ReturnType<typeof setTimeout> | null>(
|
||||||
null
|
null
|
||||||
|
@ -32,8 +35,6 @@ export function ErrorCard(props: { error: DisplayError | string }) {
|
||||||
hasCopiedUnsetDebounce.current = setTimeout(() => setHasCopied(false), 2e3);
|
hasCopiedUnsetDebounce.current = setTimeout(() => setHasCopied(false), 2e3);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!showErrorCard) return null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// I didn't put a <Transition> here because it'd fade out, then jump height weirdly
|
// I didn't put a <Transition> here because it'd fade out, then jump height weirdly
|
||||||
<div className="w-full bg-errors-card p-6 rounded-lg">
|
<div className="w-full bg-errors-card p-6 rounded-lg">
|
||||||
|
@ -60,7 +61,7 @@ export function ErrorCard(props: { error: DisplayError | string }) {
|
||||||
<Button
|
<Button
|
||||||
theme="secondary"
|
theme="secondary"
|
||||||
padding="p-2 md:px-2"
|
padding="p-2 md:px-2"
|
||||||
onClick={() => setShowErrorCard(false)}
|
onClick={props.onClose}
|
||||||
>
|
>
|
||||||
<Icon icon={Icons.X} className="text-2xl" />
|
<Icon icon={Icons.X} className="text-2xl" />
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -72,3 +73,35 @@ export function ErrorCard(props: { error: DisplayError | string }) {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// use plain modal version if there is no access to history api (like in error boundary)
|
||||||
|
export function ErrorCardInPlainModal(props: {
|
||||||
|
error?: DisplayError | string;
|
||||||
|
onClose: () => void;
|
||||||
|
show?: boolean;
|
||||||
|
}) {
|
||||||
|
if (!props.show || !props.error) return null;
|
||||||
|
return (
|
||||||
|
<div className="fixed inset-0 w-full h-full bg-black bg-opacity-30 flex justify-center items-center p-12">
|
||||||
|
<div className="max-w-2xl">
|
||||||
|
<ErrorCard error={props.error} onClose={props.onClose} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ErrorCardInModal(props: {
|
||||||
|
error?: DisplayError | string;
|
||||||
|
id: string;
|
||||||
|
onClose: () => void;
|
||||||
|
}) {
|
||||||
|
if (!props.error) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal id={props.id}>
|
||||||
|
<div className="max-w-2xl pointer-events-auto">
|
||||||
|
<ErrorCard error={props.error} onClose={props.onClose} />
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { ButtonPlain } from "@/components/buttons/Button";
|
import { ButtonPlain } from "@/components/buttons/Button";
|
||||||
|
@ -7,10 +8,11 @@ import { DisplayError } from "@/components/player/display/displayInterface";
|
||||||
import { Title } from "@/components/text/Title";
|
import { Title } from "@/components/text/Title";
|
||||||
import { Paragraph } from "@/components/utils/Text";
|
import { Paragraph } from "@/components/utils/Text";
|
||||||
import { ErrorContainer, ErrorLayout } from "@/pages/layouts/ErrorLayout";
|
import { ErrorContainer, ErrorLayout } from "@/pages/layouts/ErrorLayout";
|
||||||
import { ErrorCard } from "@/pages/parts/errors/ErrorCard";
|
import { ErrorCardInPlainModal } from "@/pages/parts/errors/ErrorCard";
|
||||||
|
|
||||||
export function ErrorPart(props: { error: any; errorInfo: any }) {
|
export function ErrorPart(props: { error: any; errorInfo: any }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const [showErrorCard, setShowErrorCard] = useState(false);
|
||||||
|
|
||||||
const maxLineCount = 5;
|
const maxLineCount = 5;
|
||||||
const errorLines = (props.errorInfo.componentStack || "")
|
const errorLines = (props.errorInfo.componentStack || "")
|
||||||
|
@ -30,15 +32,30 @@ export function ErrorPart(props: { error: any; errorInfo: any }) {
|
||||||
<ErrorContainer maxWidth="max-w-2xl">
|
<ErrorContainer maxWidth="max-w-2xl">
|
||||||
<IconPill icon={Icons.EYE_SLASH}>{t("errors.badge")}</IconPill>
|
<IconPill icon={Icons.EYE_SLASH}>{t("errors.badge")}</IconPill>
|
||||||
<Title>{t("errors.title")}</Title>
|
<Title>{t("errors.title")}</Title>
|
||||||
|
|
||||||
<Paragraph>{props.error.toString()}</Paragraph>
|
<Paragraph>{props.error.toString()}</Paragraph>
|
||||||
<ErrorCard error={error} />
|
<ErrorCardInPlainModal
|
||||||
|
show={showErrorCard}
|
||||||
|
onClose={() => setShowErrorCard(false)}
|
||||||
|
error={error}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="flex gap-3">
|
||||||
<ButtonPlain
|
<ButtonPlain
|
||||||
theme="purple"
|
theme="secondary"
|
||||||
className="mt-6 md:px-12 p-2.5"
|
className="mt-6 md:px-12 p-2.5"
|
||||||
onClick={() => window.location.reload()}
|
onClick={() => window.location.reload()}
|
||||||
>
|
>
|
||||||
{t("errors.reloadPage")}
|
{t("errors.reloadPage")}
|
||||||
</ButtonPlain>
|
</ButtonPlain>
|
||||||
|
<ButtonPlain
|
||||||
|
theme="purple"
|
||||||
|
className="mt-6 md:px-12 p-2.5"
|
||||||
|
onClick={() => setShowErrorCard(true)}
|
||||||
|
>
|
||||||
|
{t("errors.showError")}
|
||||||
|
</ButtonPlain>
|
||||||
|
</div>
|
||||||
</ErrorContainer>
|
</ErrorContainer>
|
||||||
</ErrorLayout>
|
</ErrorLayout>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -3,16 +3,18 @@ import { useTranslation } from "react-i18next";
|
||||||
import { Button } from "@/components/buttons/Button";
|
import { Button } from "@/components/buttons/Button";
|
||||||
import { Icons } from "@/components/Icon";
|
import { Icons } from "@/components/Icon";
|
||||||
import { IconPill } from "@/components/layout/IconPill";
|
import { IconPill } from "@/components/layout/IconPill";
|
||||||
|
import { useModal } from "@/components/overlays/Modal";
|
||||||
import { Paragraph } from "@/components/text/Paragraph";
|
import { Paragraph } from "@/components/text/Paragraph";
|
||||||
import { Title } from "@/components/text/Title";
|
import { Title } from "@/components/text/Title";
|
||||||
import { ErrorContainer, ErrorLayout } from "@/pages/layouts/ErrorLayout";
|
import { ErrorContainer, ErrorLayout } from "@/pages/layouts/ErrorLayout";
|
||||||
import { usePlayerStore } from "@/stores/player/store";
|
import { usePlayerStore } from "@/stores/player/store";
|
||||||
|
|
||||||
import { ErrorCard } from "../errors/ErrorCard";
|
import { ErrorCardInModal } from "../errors/ErrorCard";
|
||||||
|
|
||||||
export function PlaybackErrorPart() {
|
export function PlaybackErrorPart() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const playbackError = usePlayerStore((s) => s.interface.error);
|
const playbackError = usePlayerStore((s) => s.interface.error);
|
||||||
|
const modal = useModal("error");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ErrorLayout>
|
<ErrorLayout>
|
||||||
|
@ -20,19 +22,31 @@ export function PlaybackErrorPart() {
|
||||||
<IconPill icon={Icons.WAND}>{t("player.playbackError.badge")}</IconPill>
|
<IconPill icon={Icons.WAND}>{t("player.playbackError.badge")}</IconPill>
|
||||||
<Title>{t("player.playbackError.title")}</Title>
|
<Title>{t("player.playbackError.title")}</Title>
|
||||||
<Paragraph>{t("player.playbackError.text")}</Paragraph>
|
<Paragraph>{t("player.playbackError.text")}</Paragraph>
|
||||||
|
<div className="flex gap-3">
|
||||||
<Button
|
<Button
|
||||||
href="/"
|
href="/"
|
||||||
theme="purple"
|
theme="secondary"
|
||||||
padding="md:px-12 p-2.5"
|
padding="md:px-12 p-2.5"
|
||||||
className="mt-6"
|
className="mt-6"
|
||||||
>
|
>
|
||||||
{t("player.playbackError.homeButton")}
|
{t("player.playbackError.homeButton")}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => modal.show()}
|
||||||
|
theme="purple"
|
||||||
|
padding="md:px-12 p-2.5"
|
||||||
|
className="mt-6"
|
||||||
|
>
|
||||||
|
{t("errors.showError")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</ErrorContainer>
|
</ErrorContainer>
|
||||||
<ErrorContainer maxWidth="max-w-[45rem]">
|
|
||||||
{/* Error */}
|
{/* Error */}
|
||||||
{playbackError ? <ErrorCard error={playbackError} /> : null}
|
<ErrorCardInModal
|
||||||
</ErrorContainer>
|
onClose={() => modal.hide()}
|
||||||
|
error={playbackError}
|
||||||
|
id={modal.id}
|
||||||
|
/>
|
||||||
</ErrorLayout>
|
</ErrorLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,38 +64,54 @@ export function PlayerPart(props: PlayerPartProps) {
|
||||||
<BrandPill />
|
<BrandPill />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex sm:hidden items-center justify-end">
|
<div className="flex sm:hidden items-center justify-end">
|
||||||
|
{status === playerStatus.PLAYING ? (
|
||||||
|
<>
|
||||||
<Player.Airplay />
|
<Player.Airplay />
|
||||||
<Player.Chromecast />
|
<Player.Chromecast />
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Player.TopControls>
|
</Player.TopControls>
|
||||||
|
|
||||||
<Player.BottomControls show={showTargets}>
|
<Player.BottomControls show={showTargets}>
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3">
|
||||||
|
{status === playerStatus.PLAYING ? (
|
||||||
|
<>
|
||||||
{isMobile ? <Player.Time short /> : null}
|
{isMobile ? <Player.Time short /> : null}
|
||||||
<Player.ProgressBar />
|
<Player.ProgressBar />
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<div className="hidden lg:flex justify-between">
|
<div className="hidden lg:flex justify-between">
|
||||||
<Player.LeftSideControls>
|
<Player.LeftSideControls>
|
||||||
|
{status === playerStatus.PLAYING ? (
|
||||||
|
<>
|
||||||
<Player.Pause />
|
<Player.Pause />
|
||||||
<Player.SkipBackward />
|
<Player.SkipBackward />
|
||||||
<Player.SkipForward />
|
<Player.SkipForward />
|
||||||
<Player.Volume />
|
<Player.Volume />
|
||||||
<Player.Time />
|
<Player.Time />
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
</Player.LeftSideControls>
|
</Player.LeftSideControls>
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3">
|
||||||
<Player.Episodes />
|
<Player.Episodes />
|
||||||
|
{status === playerStatus.PLAYING ? (
|
||||||
|
<>
|
||||||
<Player.Pip />
|
<Player.Pip />
|
||||||
<Player.Airplay />
|
<Player.Airplay />
|
||||||
<Player.Chromecast />
|
<Player.Chromecast />
|
||||||
<Player.Settings />
|
<Player.Settings />
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
<Player.Fullscreen />
|
<Player.Fullscreen />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-[2.5rem,1fr,2.5rem] gap-3 lg:hidden">
|
<div className="grid grid-cols-[2.5rem,1fr,2.5rem] gap-3 lg:hidden">
|
||||||
<div />
|
<div />
|
||||||
<div className="flex justify-center space-x-3">
|
<div className="flex justify-center space-x-3">
|
||||||
<Player.Pip />
|
{status === playerStatus.PLAYING ? <Player.Pip /> : null}
|
||||||
<Player.Episodes />
|
<Player.Episodes />
|
||||||
<Player.Settings />
|
<Player.Settings />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -4,12 +4,13 @@ import { useTranslation } from "react-i18next";
|
||||||
import { Button } from "@/components/buttons/Button";
|
import { Button } from "@/components/buttons/Button";
|
||||||
import { Icons } from "@/components/Icon";
|
import { Icons } from "@/components/Icon";
|
||||||
import { IconPill } from "@/components/layout/IconPill";
|
import { IconPill } from "@/components/layout/IconPill";
|
||||||
|
import { useModal } from "@/components/overlays/Modal";
|
||||||
import { Paragraph } from "@/components/text/Paragraph";
|
import { Paragraph } from "@/components/text/Paragraph";
|
||||||
import { Title } from "@/components/text/Title";
|
import { Title } from "@/components/text/Title";
|
||||||
import { ScrapingItems, ScrapingSegment } from "@/hooks/useProviderScrape";
|
import { ScrapingItems, ScrapingSegment } from "@/hooks/useProviderScrape";
|
||||||
import { ErrorContainer, ErrorLayout } from "@/pages/layouts/ErrorLayout";
|
import { ErrorContainer, ErrorLayout } from "@/pages/layouts/ErrorLayout";
|
||||||
|
|
||||||
import { ErrorCard } from "../errors/ErrorCard";
|
import { ErrorCardInModal } from "../errors/ErrorCard";
|
||||||
|
|
||||||
export interface ScrapeErrorPartProps {
|
export interface ScrapeErrorPartProps {
|
||||||
data: {
|
data: {
|
||||||
|
@ -20,6 +21,8 @@ export interface ScrapeErrorPartProps {
|
||||||
|
|
||||||
export function ScrapeErrorPart(props: ScrapeErrorPartProps) {
|
export function ScrapeErrorPart(props: ScrapeErrorPartProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const modal = useModal("error");
|
||||||
|
|
||||||
const error = useMemo(() => {
|
const error = useMemo(() => {
|
||||||
const data = props.data;
|
const data = props.data;
|
||||||
const amountError = Object.values(data.sources).filter(
|
const amountError = Object.values(data.sources).filter(
|
||||||
|
@ -43,18 +46,33 @@ export function ScrapeErrorPart(props: ScrapeErrorPartProps) {
|
||||||
</IconPill>
|
</IconPill>
|
||||||
<Title>{t("player.scraping.notFound.title")}</Title>
|
<Title>{t("player.scraping.notFound.title")}</Title>
|
||||||
<Paragraph>{t("player.scraping.notFound.text")}</Paragraph>
|
<Paragraph>{t("player.scraping.notFound.text")}</Paragraph>
|
||||||
|
<div className="flex gap-3">
|
||||||
<Button
|
<Button
|
||||||
href="/"
|
href="/"
|
||||||
theme="purple"
|
theme="secondary"
|
||||||
padding="md:px-12 p-2.5"
|
padding="md:px-12 p-2.5"
|
||||||
className="mt-6"
|
className="mt-6"
|
||||||
>
|
>
|
||||||
{t("player.scraping.notFound.homeButton")}
|
{t("player.scraping.notFound.homeButton")}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => modal.show()}
|
||||||
|
theme="purple"
|
||||||
|
padding="md:px-12 p-2.5"
|
||||||
|
className="mt-6"
|
||||||
|
>
|
||||||
|
{t("errors.showError")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</ErrorContainer>
|
</ErrorContainer>
|
||||||
<ErrorContainer maxWidth="max-w-[45rem]">
|
<ErrorContainer maxWidth="max-w-[45rem]">
|
||||||
{/* Error */}
|
{error ? (
|
||||||
{error ? <ErrorCard error={error} /> : null}
|
<ErrorCardInModal
|
||||||
|
id={modal.id}
|
||||||
|
onClose={() => modal.hide()}
|
||||||
|
error={error}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
</ErrorContainer>
|
</ErrorContainer>
|
||||||
</ErrorLayout>
|
</ErrorLayout>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import { Helmet } from "react-helmet-async";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { Icon, Icons } from "@/components/Icon";
|
import { Icon, Icons } from "@/components/Icon";
|
||||||
|
@ -29,6 +30,11 @@ export function CaptionPreview(props: {
|
||||||
"fixed inset-0 z-[60]": props.fullscreen,
|
"fixed inset-0 z-[60]": props.fullscreen,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
|
{props.fullscreen && props.show ? (
|
||||||
|
<Helmet>
|
||||||
|
<html data-no-scroll />
|
||||||
|
</Helmet>
|
||||||
|
) : null}
|
||||||
<Transition animation="fade" show={props.show}>
|
<Transition animation="fade" show={props.show}>
|
||||||
<div
|
<div
|
||||||
className="absolute inset-0 pointer-events-auto"
|
className="absolute inset-0 pointer-events-auto"
|
||||||
|
|
|
@ -115,10 +115,10 @@ function BackendEdit({ backendUrl, setBackendUrl }: BackendEditProps) {
|
||||||
<div className="flex justify-between items-center gap-4">
|
<div className="flex justify-between items-center gap-4">
|
||||||
<div className="my-3">
|
<div className="my-3">
|
||||||
<p className="text-white font-bold mb-3">
|
<p className="text-white font-bold mb-3">
|
||||||
{t("settings.connections.workers.label")}
|
{t("settings.connections.server.label")}
|
||||||
</p>
|
</p>
|
||||||
<p className="max-w-[20rem] font-medium">
|
<p className="max-w-[20rem] font-medium">
|
||||||
{t("settings.connections.workers.description")}
|
{t("settings.connections.server.description")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
@ -132,7 +132,7 @@ function BackendEdit({ backendUrl, setBackendUrl }: BackendEditProps) {
|
||||||
<>
|
<>
|
||||||
<Divider marginClass="my-6 px-8 box-content -mx-8" />
|
<Divider marginClass="my-6 px-8 box-content -mx-8" />
|
||||||
<p className="text-white font-bold mb-3">
|
<p className="text-white font-bold mb-3">
|
||||||
{t("settings.connections.workers.urlLabel")}
|
{t("settings.connections.server.urlLabel")}
|
||||||
</p>
|
</p>
|
||||||
<AuthInputBox onChange={setBackendUrl} value={backendUrl ?? ""} />
|
<AuthInputBox onChange={setBackendUrl} value={backendUrl ?? ""} />
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -9,8 +9,8 @@ export const defaultTheme = {
|
||||||
|
|
||||||
// Branding
|
// Branding
|
||||||
pill: {
|
pill: {
|
||||||
background: "#1C1C36",
|
background: "#2e2e4d",
|
||||||
backgroundHover: "#1C1C36",
|
backgroundHover: "#3d3d61",
|
||||||
highlight: "#714C97",
|
highlight: "#714C97",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -97,7 +97,7 @@ export const defaultTheme = {
|
||||||
dropdown: {
|
dropdown: {
|
||||||
background: "#171728",
|
background: "#171728",
|
||||||
altBackground: "#151525",
|
altBackground: "#151525",
|
||||||
highlight: "#FCEC61",
|
highlight: "#afa349",
|
||||||
highlightHover: "#FCEC61",
|
highlightHover: "#FCEC61",
|
||||||
text: "#846D95",
|
text: "#846D95",
|
||||||
secondary: "#73739D",
|
secondary: "#73739D",
|
||||||
|
@ -179,6 +179,11 @@ export const defaultTheme = {
|
||||||
video: {
|
video: {
|
||||||
buttonBackground: "#444B5C",
|
buttonBackground: "#444B5C",
|
||||||
|
|
||||||
|
autoPlay: {
|
||||||
|
background: "#161C26",
|
||||||
|
hover: "#252533"
|
||||||
|
},
|
||||||
|
|
||||||
scraping: {
|
scraping: {
|
||||||
card: "#161620",
|
card: "#161620",
|
||||||
error: "#E44F4F",
|
error: "#E44F4F",
|
||||||
|
@ -211,6 +216,7 @@ export const defaultTheme = {
|
||||||
active: "#0D1317",
|
active: "#0D1317",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
type: {
|
type: {
|
||||||
main: "#617A8A",
|
main: "#617A8A",
|
||||||
secondary: "#374A56",
|
secondary: "#374A56",
|
||||||
|
|
Loading…
Reference in a new issue