diff --git a/src/components/Button.tsx b/src/components/Button.tsx index e6d012c0..b21176cf 100644 --- a/src/components/Button.tsx +++ b/src/components/Button.tsx @@ -67,3 +67,33 @@ export function Button(props: Props) { ); } + +interface ButtonPlainProps { + onClick?: () => void; + children?: ReactNode; + theme?: "white" | "purple" | "secondary"; + className?: string; +} + +export function ButtonPlain(props: ButtonPlainProps) { + let colorClasses = "bg-white hover:bg-gray-200 text-black"; + if (props.theme === "purple") + colorClasses = + "bg-video-buttons-purple hover:bg-video-buttons-purpleHover text-white"; + if (props.theme === "secondary") + colorClasses = + "bg-video-buttons-cancel hover:bg-video-buttons-cancelHover transition-colors duration-100 text-white"; + + const 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", + "px-4 py-3", + props.className, + colorClasses + ); + + return ( + + ); +} diff --git a/src/components/layout/ErrorBoundary.tsx b/src/components/layout/ErrorBoundary.tsx deleted file mode 100644 index 645a4a9e..00000000 --- a/src/components/layout/ErrorBoundary.tsx +++ /dev/null @@ -1,114 +0,0 @@ -import { Component } from "react"; -import { Trans, useTranslation } from "react-i18next"; - -import { IconPatch } from "@/components/buttons/IconPatch"; -import { Icons } from "@/components/Icon"; -import { Link } from "@/components/text/Link"; -import { Title } from "@/components/text/Title"; -import { conf } from "@/setup/config"; - -interface ErrorShowcaseProps { - error: { - name: string; - description: string; - path: string; - }; -} - -export function ErrorShowcase(props: ErrorShowcaseProps) { - return ( -
-

- {props.error.name} - {props.error.description} -

-

{props.error.path}

-
- ); -} - -interface ErrorMessageProps { - error?: { - name: string; - description: string; - path: string; - }; - localSize?: boolean; - children?: React.ReactNode; -} - -export function ErrorMessage(props: ErrorMessageProps) { - const { t } = useTranslation(); - - return ( -
-
- - {t("media.errors.genericTitle")} - {props.children ? ( -

{props.children}

- ) : ( -

- - - - -

- )} -
- {props.error ? : null} -
- ); -} - -interface ErrorBoundaryState { - hasError: boolean; - error?: { - name: string; - description: string; - path: string; - }; -} - -export class ErrorBoundary extends Component< - Record, - ErrorBoundaryState -> { - constructor(props: { children: any }) { - super(props); - this.state = { - hasError: false, - }; - } - - static getDerivedStateFromError() { - return { - hasError: true, - }; - } - - componentDidCatch(error: any, errorInfo: any) { - console.error("Render error caught", error, errorInfo); - if (error instanceof Error) { - const realError: Error = error as Error; - this.setState((s) => ({ - ...s, - hasError: true, - error: { - name: realError.name, - description: realError.message, - path: errorInfo.componentStack.split("\n")[1], - }, - })); - } - } - - render() { - if (!this.state.hasError) return this.props.children as any; - - return ; - } -} diff --git a/src/components/player/atoms/ProgressBar.tsx b/src/components/player/atoms/ProgressBar.tsx index 6b421a3d..c5deb7ca 100644 --- a/src/components/player/atoms/ProgressBar.tsx +++ b/src/components/player/atoms/ProgressBar.tsx @@ -144,13 +144,13 @@ export function ProgressBar() { >
{/* Pre-loaded content bar */}
s.setSource); const setSourceId = usePlayerStore((s) => s.setSourceId); const progress = usePlayerStore((s) => s.progress.time); + + const unknownEmbedName = "Unknown"; + const embedName = useMemo(() => { - if (!props.embedId) return "..."; + if (!props.embedId) return unknownEmbedName; const sourceMeta = providers.getMetadata(props.embedId); - return sourceMeta?.name ?? "..."; + return sourceMeta?.name ?? unknownEmbedName; }, [props.embedId]); + const [request, run] = useAsyncFn(async () => { const result = await providers.runEmbedScraper({ id: props.embedId, @@ -46,26 +50,14 @@ export function EmbedOption(props: { router.close(); }, [props.embedId, props.sourceId, meta, router]); - let content: ReactNode = null; - if (request.loading) - content = ( - - - - ); - else if (request.error) - content = ( - - We were unable to find any videos for this source. Don't come - bitchin' to us about it, just try another source. - - ); - return ( - + {embedName} - {content} ); diff --git a/src/index.tsx b/src/index.tsx index 84d3a93e..b30b7595 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -6,7 +6,7 @@ import { HelmetProvider } from "react-helmet-async"; import { BrowserRouter, HashRouter } from "react-router-dom"; import { registerSW } from "virtual:pwa-register"; -import { ErrorBoundary } from "@/components/layout/ErrorBoundary"; +import { ErrorBoundary } from "@/pages/errors/ErrorBoundary"; import App from "@/setup/App"; import { conf } from "@/setup/config"; import i18n from "@/setup/i18n"; diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx index c51cbf32..7cc49fba 100644 --- a/src/pages/HomePage.tsx +++ b/src/pages/HomePage.tsx @@ -9,6 +9,7 @@ import { HomeLayout } from "@/pages/layouts/HomeLayout"; import { BookmarksPart } from "@/pages/parts/home/BookmarksPart"; import { HeroPart } from "@/pages/parts/home/HeroPart"; import { WatchingPart } from "@/pages/parts/home/WatchingPart"; +import { MigrationPart } from "@/pages/parts/migrations/MigrationPart"; import { SearchListPart } from "@/pages/parts/search/SearchListPart"; import { SearchLoadingPart } from "@/pages/parts/search/SearchLoadingPart"; @@ -38,6 +39,8 @@ export function HomePage() { const [search] = searchParams; const s = useSearch(search); + return ; + return (
diff --git a/src/pages/developer/TestView.tsx b/src/pages/developer/TestView.tsx index 6c175eb2..96fd2da4 100644 --- a/src/pages/developer/TestView.tsx +++ b/src/pages/developer/TestView.tsx @@ -1,80 +1,12 @@ -import { OverlayAnchor } from "@/components/overlays/OverlayAnchor"; -import { Overlay, OverlayDisplay } from "@/components/overlays/OverlayDisplay"; -import { OverlayPage } from "@/components/overlays/OverlayPage"; -import { OverlayRouter } from "@/components/overlays/OverlayRouter"; -import { useOverlayRouter } from "@/hooks/useOverlayRouter"; +import { useState } from "react"; -// simple empty view, perfect for putting in tests +import { Button } from "@/components/Button"; + +// mostly empty view, add whatever you need export default function TestView() { - const router = useOverlayRouter("test"); + const [val, setVal] = useState(false); + + if (val) throw new Error("I crashed"); - return ( - -
- - - - - -
-

HOME

- - -
-
- -
-

ONE

- -
-
- -
-

TWO

- -
-
-
-
-
-
- ); + return ; } diff --git a/src/pages/errors/ErrorBoundary.tsx b/src/pages/errors/ErrorBoundary.tsx new file mode 100644 index 00000000..a6c7ac98 --- /dev/null +++ b/src/pages/errors/ErrorBoundary.tsx @@ -0,0 +1,44 @@ +import { Component } from "react"; + +import { ErrorPart } from "@/pages/parts/errors/ErrorPart"; + +interface ErrorBoundaryState { + error?: { + error: any; + errorInfo: any; + }; +} + +export class ErrorBoundary extends Component< + Record, + ErrorBoundaryState +> { + constructor(props: { children: any }) { + super(props); + this.state = { + error: undefined, + }; + } + + componentDidCatch(error: any, errorInfo: any) { + console.error("Render error caught", error, errorInfo); + this.setState((s) => ({ + ...s, + error: { + error, + errorInfo, + }, + })); + } + + render() { + if (!this.state.error) return this.props.children as any; + + return ( + + ); + } +} diff --git a/src/pages/errors/NotFoundPage.tsx b/src/pages/errors/NotFoundPage.tsx index a944e05f..cf9cfa7e 100644 --- a/src/pages/errors/NotFoundPage.tsx +++ b/src/pages/errors/NotFoundPage.tsx @@ -1,23 +1,5 @@ -import { useTranslation } from "react-i18next"; - -import { IconPatch } from "@/components/buttons/IconPatch"; -import { Icons } from "@/components/Icon"; -import { ArrowLink } from "@/components/text/ArrowLink"; -import { Title } from "@/components/text/Title"; -import { ErrorWrapperPart } from "@/pages/parts/errors/ErrorWrapperPart"; +import { NotFoundPart } from "@/pages/parts/errors/ErrorWrapperPart"; export function NotFoundPage() { - const { t } = useTranslation(); - - return ( - - - {t("notFound.page.title")} -

{t("notFound.page.description")}

- -
- ); + return ; } diff --git a/src/pages/layouts/SubPageLayout.tsx b/src/pages/layouts/SubPageLayout.tsx index f5c7ca50..0b12de6a 100644 --- a/src/pages/layouts/SubPageLayout.tsx +++ b/src/pages/layouts/SubPageLayout.tsx @@ -1,6 +1,16 @@ import { FooterView } from "@/components/layout/Footer"; import { Navigation } from "@/components/layout/Navigation"; +export function BlurEllipsis() { + return ( + <> + {/* Blur elipsis */} +
+
+ + ); +} + export function SubPageLayout(props: { children: React.ReactNode }) { return (
- {/* Blur elipsis */} -
-
- + {/* Main page */} diff --git a/src/pages/parts/errors/ErrorPart.tsx b/src/pages/parts/errors/ErrorPart.tsx new file mode 100644 index 00000000..a547f649 --- /dev/null +++ b/src/pages/parts/errors/ErrorPart.tsx @@ -0,0 +1,33 @@ +import { ButtonPlain } from "@/components/Button"; +import { Icons } from "@/components/Icon"; +import { IconPill } from "@/components/layout/IconPill"; +import { Title } from "@/components/text/Title"; +import { Paragraph } from "@/components/utils/Text"; +import { ErrorContainer, ErrorLayout } from "@/pages/layouts/ErrorLayout"; + +export function ErrorPart(props: { error: any; errorInfo: any }) { + const data = JSON.stringify({ + error: props.error, + errorInfo: props.errorInfo, + }); + return ( +
+
+ + + It broke + Failed to load meta data + {data} + window.location.reload()} + > + Reload the page + + + +
+
+ ); +} diff --git a/src/pages/parts/errors/ErrorWrapperPart.tsx b/src/pages/parts/errors/ErrorWrapperPart.tsx index 37410c49..64d36459 100644 --- a/src/pages/parts/errors/ErrorWrapperPart.tsx +++ b/src/pages/parts/errors/ErrorWrapperPart.tsx @@ -1,10 +1,15 @@ -import { ReactNode } from "react"; import { Helmet } from "react-helmet-async"; import { useTranslation } from "react-i18next"; +import { Button } from "@/components/Button"; +import { Icons } from "@/components/Icon"; +import { IconPill } from "@/components/layout/IconPill"; import { Navigation } from "@/components/layout/Navigation"; +import { Title } from "@/components/text/Title"; +import { Paragraph } from "@/components/utils/Text"; +import { ErrorContainer, ErrorLayout } from "@/pages/layouts/ErrorLayout"; -export function ErrorWrapperPart(props: { children?: ReactNode }) { +export function NotFoundPart() { const { t } = useTranslation(); return ( @@ -14,7 +19,28 @@ export function ErrorWrapperPart(props: { children?: ReactNode }) {
- {props.children} + + + + {t("notFound.genericTitle")} + + Failed to load meta data + + Oh, my apowogies, sweetie! The itty-bitty movie-web did its utmost + bestest, but alas, no wucky videos to be spotted anywhere (ยดโŠ™ฯ‰โŠ™`) + Please don't be angwy, wittle movie-web ish twying so hard. + Can you find it in your heart to forgive? UwU ๐Ÿ’– + + + +
); diff --git a/src/pages/parts/migrations/MigrationPart.tsx b/src/pages/parts/migrations/MigrationPart.tsx new file mode 100644 index 00000000..da8c027e --- /dev/null +++ b/src/pages/parts/migrations/MigrationPart.tsx @@ -0,0 +1,26 @@ +import { BrandPill } from "@/components/layout/BrandPill"; +import { Loading } from "@/components/layout/Loading"; +import { BlurEllipsis } from "@/pages/layouts/SubPageLayout"; + +export function MigrationPart() { + return ( +
+ {/* Overlaid elements */} + +
+ +
+ + {/* Content */} + +

+ Please hold, we are migrating your data. This shouldn't take long. + Also, fuck you. +

+
+
+
+

25%

+
+ ); +} diff --git a/tailwind.config.js b/tailwind.config.js index 76b647bd..3783226d 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -26,23 +26,23 @@ module.exports = { "ash-400": "#3D394D", "ash-300": "#2C293A", "ash-200": "#2B2836", - "ash-100": "#1E1C26", + "ash-100": "#1E1C26" }, /* fonts */ fontFamily: { - "open-sans": "'Open Sans'", + "open-sans": "'Open Sans'" }, /* animations */ keyframes: { "loading-pin": { "0%, 40%, 100%": { height: "0.5em", "background-color": "#282336" }, - "20%": { height: "1em", "background-color": "white" }, - }, + "20%": { height: "1em", "background-color": "white" } + } }, - animation: { "loading-pin": "loading-pin 1.8s ease-in-out infinite" }, - }, + animation: { "loading-pin": "loading-pin 1.8s ease-in-out infinite" } + } }, plugins: [ require("tailwind-scrollbar"), @@ -52,31 +52,31 @@ module.exports = { colors: { // Branding pill: { - background: "#1C1C36", + background: "#1C1C36" }, // meta data for the theme itself global: { accentA: "#505DBD", - accentB: "#3440A1", + accentB: "#3440A1" }, // light bar lightBar: { - light: "#2A2A71", + light: "#2A2A71" }, // Buttons buttons: { toggle: "#8D44D6", - toggleDisabled: "#202836", + toggleDisabled: "#202836" }, // only used for body colors/textures background: { main: "#0A0A10", accentA: "#6E3B80", - accentB: "#1F1F50", + accentB: "#1F1F50" }, // typography @@ -85,7 +85,7 @@ module.exports = { text: "#73739D", dimmed: "#926CAD", divider: "#262632", - secondary: "#64647B", + secondary: "#64647B" }, // search bar @@ -94,7 +94,7 @@ module.exports = { focused: "#24243C", placeholder: "#4A4A71", icon: "#545476", - text: "#FFFFFF", + text: "#FFFFFF" }, // media cards @@ -106,7 +106,7 @@ module.exports = { barColor: "#4B4B63", barFillColor: "#BA7FD6", badge: "#151522", - badgeText: "#5F5F7A", + badgeText: "#5F5F7A" }, // Error page @@ -115,14 +115,20 @@ module.exports = { border: "#252534", type: { - secondary: "#62627D", - }, + secondary: "#62627D" + } }, // About page about: { circle: "#262632", - circleText: "#9A9AC3", + circleText: "#9A9AC3" + }, + + progress: { + background: "#8787A8", + preloaded: "#8787A8", + filled: "#A75FC9" }, // video player @@ -134,17 +140,11 @@ module.exports = { error: "#E44F4F", success: "#40B44B", loading: "#B759D8", - noresult: "#64647B", - }, - - progress: { - background: "#8787A8", - preloaded: "#8787A8", - watched: "#A75FC9", + noresult: "#64647B" }, audio: { - set: "#A75FC9", + set: "#A75FC9" }, buttons: { @@ -157,7 +157,7 @@ module.exports = { purple: "#6b298a", purpleHover: "#7f35a1", cancel: "#252533", - cancelHover: "#3C3C4A", + cancelHover: "#3C3C4A" }, context: { @@ -177,19 +177,19 @@ module.exports = { buttons: { list: "#161C26", - active: "#0D1317", + active: "#0D1317" }, type: { main: "#617A8A", secondary: "#374A56", - accent: "#A570FA", - }, - }, - }, - }, - }, - }, - }), - ], + accent: "#A570FA" + } + } + } + } + } + } + }) + ] };