Some shoepolish for movie-web

Co-authored-by: Jip Frijlink <JipFr@users.noreply.github.com>
This commit is contained in:
mrjvs 2023-11-30 21:25:28 +01:00
parent b1b604d322
commit 4dc3a3216a
18 changed files with 186 additions and 83 deletions

View File

@ -46,6 +46,7 @@
"errors": {
"details": "Error details",
"reloadPage": "Reload the page",
"showError": "Show error details",
"badge": "It broke",
"title": "That's an error boss"
},

View File

@ -81,20 +81,10 @@ export function UserAvatar(props: {
);
}
export function NoUserAvatar(props: {
sizeClass?: string;
iconClass?: string;
}) {
export function NoUserAvatar(props: { iconClass?: string }) {
return (
<div className="relative inline-block">
<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} />
</div>
<div className="relative inline-block p-1 text-type-dimmed">
<Icon className={props.iconClass ?? "text-xl"} icon={Icons.MENU} />
</div>
);
}

View File

@ -109,7 +109,7 @@ export function LinksDropdown(props: { children: React.ReactNode }) {
return (
<div className="relative is-dropdown">
<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}
onClick={toggleOpen}
onKeyUp={(evt) => evt.key === "Enter" && toggleOpen()}

View File

@ -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 {
onClick?: () => void;
children?: ReactNode;

View File

@ -21,7 +21,7 @@ export function Dropdown(props: DropdownProps) {
<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">
{props.selectedItem.leftIcon
? props.selectedItem.leftIcon
@ -45,7 +45,7 @@ export function Dropdown(props: DropdownProps) {
{props.options.map((opt) => (
<Listbox.Option
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
? "bg-background-secondaryHover text-type-link"
: "text-white"

View File

@ -24,7 +24,7 @@ export const SearchBarInput = forwardRef<HTMLInputElement, SearchBarProps>(
return (
<Flare.Base
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,
"bg-search-background": !focused,
"bg-search-focused": focused,
@ -40,7 +40,6 @@ export const SearchBarInput = forwardRef<HTMLInputElement, SearchBarProps>(
"bg-search-focused": focused,
})}
/>
<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">
<Icon icon={Icons.SEARCH} />

View File

@ -55,7 +55,7 @@ export function Footer() {
</h3>
<p className="mt-3">{t("footer.legal.disclaimerText")}</p>
</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}>
{t("footer.links.github")}
</FooterLink>

View File

@ -59,6 +59,7 @@ export function Navigation(props: NavigationProps) {
<BlurEllipsis positionClass="absolute" />
</div>
) : null}
<div className="opacity-0 absolute inset-0 block h-20 pointer-events-auto" />
<div
className={`${
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="pointer-events-auto px-7 py-5 relative z-[60] flex flex-1 items-center">
<div className="flex items-center flex-1 space-x-3">
<div className="px-7 py-5 relative z-[60] flex flex-1 items-center justify-between">
<div className="flex items-center space-x-3 pointer-events-auto">
<Link className="block tabbable rounded-full" to="/">
<BrandPill clickable />
</Link>
@ -99,7 +100,7 @@ export function Navigation(props: NavigationProps) {
<IconPatch icon={Icons.GITHUB} clickable downsized />
</a>
</div>
<div className="relative">
<div className="relative pointer-events-auto">
<LinksDropdown>
{loggedIn ? <UserAvatar withName /> : <NoUserAvatar />}
</LinksDropdown>

View File

@ -23,7 +23,7 @@ export function AutoPlayStart() {
return (
<div
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={Icons.PLAY}

View File

@ -230,7 +230,7 @@ export function CaptionSettingsView({ id }: { id: string }) {
</Menu.BackLink>
<Menu.Section className="space-y-6">
<CaptionSetting
label={t("player.menus.captions.settings.fixCapitals")}
label={t("player.menus.captions.settings.delay")}
max={10}
min={-10}
onChange={(v) => setDelay(v)}
@ -241,7 +241,7 @@ export function CaptionSettingsView({ id }: { id: string }) {
/>
<div className="flex justify-between items-center">
<Menu.FieldTitle>
{t("player.menus.captions.settings.delay")}
{t("player.menus.captions.settings.fixCapitals")}
</Menu.FieldTitle>
<div className="flex justify-center items-center">
<Toggle

View File

@ -3,10 +3,13 @@ import { useTranslation } from "react-i18next";
import { Button } from "@/components/buttons/Button";
import { Icon, Icons } from "@/components/Icon";
import { Modal } from "@/components/overlays/Modal";
import { DisplayError } from "@/components/player/display/displayInterface";
export function ErrorCard(props: { error: DisplayError | string }) {
const [showErrorCard, setShowErrorCard] = useState(true);
export function ErrorCard(props: {
error: DisplayError | string;
onClose: () => void;
}) {
const [hasCopied, setHasCopied] = useState(false);
const hasCopiedUnsetDebounce = useRef<ReturnType<typeof setTimeout> | null>(
null
@ -32,8 +35,6 @@ export function ErrorCard(props: { error: DisplayError | string }) {
hasCopiedUnsetDebounce.current = setTimeout(() => setHasCopied(false), 2e3);
}
if (!showErrorCard) return null;
return (
// 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">
@ -60,7 +61,7 @@ export function ErrorCard(props: { error: DisplayError | string }) {
<Button
theme="secondary"
padding="p-2 md:px-2"
onClick={() => setShowErrorCard(false)}
onClick={props.onClose}
>
<Icon icon={Icons.X} className="text-2xl" />
</Button>
@ -72,3 +73,35 @@ export function ErrorCard(props: { error: DisplayError | string }) {
</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>
);
}

View File

@ -1,3 +1,4 @@
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { ButtonPlain } from "@/components/buttons/Button";
@ -7,10 +8,11 @@ import { DisplayError } from "@/components/player/display/displayInterface";
import { Title } from "@/components/text/Title";
import { Paragraph } from "@/components/utils/Text";
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 }) {
const { t } = useTranslation();
const [showErrorCard, setShowErrorCard] = useState(false);
const maxLineCount = 5;
const errorLines = (props.errorInfo.componentStack || "")
@ -30,15 +32,30 @@ export function ErrorPart(props: { error: any; errorInfo: any }) {
<ErrorContainer maxWidth="max-w-2xl">
<IconPill icon={Icons.EYE_SLASH}>{t("errors.badge")}</IconPill>
<Title>{t("errors.title")}</Title>
<Paragraph>{props.error.toString()}</Paragraph>
<ErrorCard error={error} />
<ButtonPlain
theme="purple"
className="mt-6 md:px-12 p-2.5"
onClick={() => window.location.reload()}
>
{t("errors.reloadPage")}
</ButtonPlain>
<ErrorCardInPlainModal
show={showErrorCard}
onClose={() => setShowErrorCard(false)}
error={error}
/>
<div className="flex gap-3">
<ButtonPlain
theme="secondary"
className="mt-6 md:px-12 p-2.5"
onClick={() => window.location.reload()}
>
{t("errors.reloadPage")}
</ButtonPlain>
<ButtonPlain
theme="purple"
className="mt-6 md:px-12 p-2.5"
onClick={() => setShowErrorCard(true)}
>
{t("errors.showError")}
</ButtonPlain>
</div>
</ErrorContainer>
</ErrorLayout>
</div>

View File

@ -3,16 +3,18 @@ import { useTranslation } from "react-i18next";
import { Button } from "@/components/buttons/Button";
import { Icons } from "@/components/Icon";
import { IconPill } from "@/components/layout/IconPill";
import { useModal } from "@/components/overlays/Modal";
import { Paragraph } from "@/components/text/Paragraph";
import { Title } from "@/components/text/Title";
import { ErrorContainer, ErrorLayout } from "@/pages/layouts/ErrorLayout";
import { usePlayerStore } from "@/stores/player/store";
import { ErrorCard } from "../errors/ErrorCard";
import { ErrorCardInModal } from "../errors/ErrorCard";
export function PlaybackErrorPart() {
const { t } = useTranslation();
const playbackError = usePlayerStore((s) => s.interface.error);
const modal = useModal("error");
return (
<ErrorLayout>
@ -20,19 +22,31 @@ export function PlaybackErrorPart() {
<IconPill icon={Icons.WAND}>{t("player.playbackError.badge")}</IconPill>
<Title>{t("player.playbackError.title")}</Title>
<Paragraph>{t("player.playbackError.text")}</Paragraph>
<Button
href="/"
theme="purple"
padding="md:px-12 p-2.5"
className="mt-6"
>
{t("player.playbackError.homeButton")}
</Button>
</ErrorContainer>
<ErrorContainer maxWidth="max-w-[45rem]">
{/* Error */}
{playbackError ? <ErrorCard error={playbackError} /> : null}
<div className="flex gap-3">
<Button
href="/"
theme="secondary"
padding="md:px-12 p-2.5"
className="mt-6"
>
{t("player.playbackError.homeButton")}
</Button>
<Button
onClick={() => modal.show()}
theme="purple"
padding="md:px-12 p-2.5"
className="mt-6"
>
{t("errors.showError")}
</Button>
</div>
</ErrorContainer>
{/* Error */}
<ErrorCardInModal
onClose={() => modal.hide()}
error={playbackError}
id={modal.id}
/>
</ErrorLayout>
);
}

View File

@ -64,38 +64,54 @@ export function PlayerPart(props: PlayerPartProps) {
<BrandPill />
</div>
<div className="flex sm:hidden items-center justify-end">
<Player.Airplay />
<Player.Chromecast />
{status === playerStatus.PLAYING ? (
<>
<Player.Airplay />
<Player.Chromecast />
</>
) : null}
</div>
</div>
</Player.TopControls>
<Player.BottomControls show={showTargets}>
<div className="flex items-center space-x-3">
{isMobile ? <Player.Time short /> : null}
<Player.ProgressBar />
{status === playerStatus.PLAYING ? (
<>
{isMobile ? <Player.Time short /> : null}
<Player.ProgressBar />
</>
) : null}
</div>
<div className="hidden lg:flex justify-between">
<Player.LeftSideControls>
<Player.Pause />
<Player.SkipBackward />
<Player.SkipForward />
<Player.Volume />
<Player.Time />
{status === playerStatus.PLAYING ? (
<>
<Player.Pause />
<Player.SkipBackward />
<Player.SkipForward />
<Player.Volume />
<Player.Time />
</>
) : null}
</Player.LeftSideControls>
<div className="flex items-center space-x-3">
<Player.Episodes />
<Player.Pip />
<Player.Airplay />
<Player.Chromecast />
<Player.Settings />
{status === playerStatus.PLAYING ? (
<>
<Player.Pip />
<Player.Airplay />
<Player.Chromecast />
<Player.Settings />
</>
) : null}
<Player.Fullscreen />
</div>
</div>
<div className="grid grid-cols-[2.5rem,1fr,2.5rem] gap-3 lg:hidden">
<div />
<div className="flex justify-center space-x-3">
<Player.Pip />
{status === playerStatus.PLAYING ? <Player.Pip /> : null}
<Player.Episodes />
<Player.Settings />
</div>

View File

@ -4,12 +4,13 @@ import { useTranslation } from "react-i18next";
import { Button } from "@/components/buttons/Button";
import { Icons } from "@/components/Icon";
import { IconPill } from "@/components/layout/IconPill";
import { useModal } from "@/components/overlays/Modal";
import { Paragraph } from "@/components/text/Paragraph";
import { Title } from "@/components/text/Title";
import { ScrapingItems, ScrapingSegment } from "@/hooks/useProviderScrape";
import { ErrorContainer, ErrorLayout } from "@/pages/layouts/ErrorLayout";
import { ErrorCard } from "../errors/ErrorCard";
import { ErrorCardInModal } from "../errors/ErrorCard";
export interface ScrapeErrorPartProps {
data: {
@ -20,6 +21,8 @@ export interface ScrapeErrorPartProps {
export function ScrapeErrorPart(props: ScrapeErrorPartProps) {
const { t } = useTranslation();
const modal = useModal("error");
const error = useMemo(() => {
const data = props.data;
const amountError = Object.values(data.sources).filter(
@ -43,18 +46,33 @@ export function ScrapeErrorPart(props: ScrapeErrorPartProps) {
</IconPill>
<Title>{t("player.scraping.notFound.title")}</Title>
<Paragraph>{t("player.scraping.notFound.text")}</Paragraph>
<Button
href="/"
theme="purple"
padding="md:px-12 p-2.5"
className="mt-6"
>
{t("player.scraping.notFound.homeButton")}
</Button>
<div className="flex gap-3">
<Button
href="/"
theme="secondary"
padding="md:px-12 p-2.5"
className="mt-6"
>
{t("player.scraping.notFound.homeButton")}
</Button>
<Button
onClick={() => modal.show()}
theme="purple"
padding="md:px-12 p-2.5"
className="mt-6"
>
{t("errors.showError")}
</Button>
</div>
</ErrorContainer>
<ErrorContainer maxWidth="max-w-[45rem]">
{/* Error */}
{error ? <ErrorCard error={error} /> : null}
{error ? (
<ErrorCardInModal
id={modal.id}
onClose={() => modal.hide()}
error={error}
/>
) : null}
</ErrorContainer>
</ErrorLayout>
);

View File

@ -1,5 +1,6 @@
import classNames from "classnames";
import { useState } from "react";
import { Helmet } from "react-helmet-async";
import { useTranslation } from "react-i18next";
import { Icon, Icons } from "@/components/Icon";
@ -29,6 +30,11 @@ export function CaptionPreview(props: {
"fixed inset-0 z-[60]": props.fullscreen,
})}
>
{props.fullscreen && props.show ? (
<Helmet>
<html data-no-scroll />
</Helmet>
) : null}
<Transition animation="fade" show={props.show}>
<div
className="absolute inset-0 pointer-events-auto"

View File

@ -115,10 +115,10 @@ function BackendEdit({ backendUrl, setBackendUrl }: BackendEditProps) {
<div className="flex justify-between items-center gap-4">
<div className="my-3">
<p className="text-white font-bold mb-3">
{t("settings.connections.workers.label")}
{t("settings.connections.server.label")}
</p>
<p className="max-w-[20rem] font-medium">
{t("settings.connections.workers.description")}
{t("settings.connections.server.description")}
</p>
</div>
<div>
@ -132,7 +132,7 @@ function BackendEdit({ backendUrl, setBackendUrl }: BackendEditProps) {
<>
<Divider marginClass="my-6 px-8 box-content -mx-8" />
<p className="text-white font-bold mb-3">
{t("settings.connections.workers.urlLabel")}
{t("settings.connections.server.urlLabel")}
</p>
<AuthInputBox onChange={setBackendUrl} value={backendUrl ?? ""} />
</>

View File

@ -9,8 +9,8 @@ export const defaultTheme = {
// Branding
pill: {
background: "#1C1C36",
backgroundHover: "#1C1C36",
background: "#2e2e4d",
backgroundHover: "#3d3d61",
highlight: "#714C97",
},
@ -97,7 +97,7 @@ export const defaultTheme = {
dropdown: {
background: "#171728",
altBackground: "#151525",
highlight: "#FCEC61",
highlight: "#afa349",
highlightHover: "#FCEC61",
text: "#846D95",
secondary: "#73739D",
@ -179,6 +179,11 @@ export const defaultTheme = {
video: {
buttonBackground: "#444B5C",
autoPlay: {
background: "#161C26",
hover: "#252533"
},
scraping: {
card: "#161620",
error: "#E44F4F",
@ -211,6 +216,7 @@ export const defaultTheme = {
active: "#0D1317",
},
type: {
main: "#617A8A",
secondary: "#374A56",