mirror of
https://github.com/sussy-code/smov.git
synced 2024-12-20 14:37:43 +01:00
settings menu styling + fix shows + fix back link and double redirects
Co-authored-by: Jip Frijlink <JipFr@users.noreply.github.com>
This commit is contained in:
parent
562ab8fa49
commit
5c1807c8f4
14 changed files with 200 additions and 42 deletions
|
@ -19,7 +19,7 @@
|
|||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600;700&display=swap"
|
||||
href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;500;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
|
|
|
@ -44,10 +44,7 @@ export function OverlayPage(props: Props) {
|
|||
show={show}
|
||||
>
|
||||
<div
|
||||
className={classNames([
|
||||
props.className,
|
||||
"grid grid-rows-[auto,minmax(0,1fr)]",
|
||||
])}
|
||||
className={classNames([props.className, ""])}
|
||||
style={{
|
||||
height: props.height ? `${props.height}px` : undefined,
|
||||
maxHeight: "70vh",
|
||||
|
|
|
@ -3,6 +3,7 @@ import { ReactNode, useEffect, useMemo, useRef } from "react";
|
|||
|
||||
import { OverlayAnchorPosition } from "@/components/overlays/positions/OverlayAnchorPosition";
|
||||
import { OverlayMobilePosition } from "@/components/overlays/positions/OverlayMobilePosition";
|
||||
import { Flare } from "@/components/utils/Flare";
|
||||
import { useIsMobile } from "@/hooks/useIsMobile";
|
||||
import { useInternalOverlayRouter } from "@/hooks/useOverlayRouter";
|
||||
import { useOverlayStore } from "@/stores/overlay/store";
|
||||
|
@ -71,12 +72,18 @@ function RouterBase(props: { id: string; children: ReactNode }) {
|
|||
}, [routeMeta?.height, routeMeta?.width, isMobile, api]);
|
||||
|
||||
return (
|
||||
<a.div
|
||||
ref={ref}
|
||||
style={dimensions}
|
||||
className="relative flex items-center justify-center overflow-hidden bg-red-500"
|
||||
>
|
||||
{props.children}
|
||||
<a.div ref={ref} style={dimensions} className="overflow-hidden">
|
||||
<Flare.Base className="group w-full h-full rounded-xl transition-colors duration-100 text-video-context-type-main">
|
||||
<Flare.Light
|
||||
flareSize={400}
|
||||
cssColorVar="--colors-video-context-light"
|
||||
backgroundClass="bg-video-context-background duration-100"
|
||||
className="rounded-xl opacity-100"
|
||||
/>
|
||||
<Flare.Child className="pointer-events-auto relative transition-transform duration-100">
|
||||
{props.children}
|
||||
</Flare.Child>
|
||||
</Flare.Base>
|
||||
</a.div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -6,15 +6,41 @@ import { Overlay } from "@/components/overlays/OverlayDisplay";
|
|||
import { OverlayPage } from "@/components/overlays/OverlayPage";
|
||||
import { OverlayRouter } from "@/components/overlays/OverlayRouter";
|
||||
import { VideoPlayerButton } from "@/components/player/internals/Button";
|
||||
import { Context } from "@/components/player/internals/ContextUtils";
|
||||
import { useOverlayRouter } from "@/hooks/useOverlayRouter";
|
||||
import { usePlayerStore } from "@/stores/player/store";
|
||||
|
||||
function SettingsOverlay({ id }: { id: string }) {
|
||||
const router = useOverlayRouter("settings");
|
||||
|
||||
return (
|
||||
<Overlay id={id}>
|
||||
<OverlayRouter id={id}>
|
||||
<OverlayPage id={id} path="/" width={400} height={400}>
|
||||
<p>This is settings menu, welcome!</p>
|
||||
<OverlayPage id={id} path="/" width={343} height={431}>
|
||||
<Context.Card>
|
||||
<Context.Title>Ba ba ba ba my title</Context.Title>
|
||||
<Context.Section>
|
||||
Hi
|
||||
<Context.Link onClick={() => router.navigate("/other")}>
|
||||
<Context.LinkTitle>Go to page 2</Context.LinkTitle>
|
||||
<Context.LinkChevron />
|
||||
</Context.Link>
|
||||
<Context.Link>
|
||||
<Context.LinkTitle>Video source</Context.LinkTitle>
|
||||
<Context.LinkChevron>SuperStream</Context.LinkChevron>
|
||||
</Context.Link>
|
||||
</Context.Section>
|
||||
</Context.Card>
|
||||
</OverlayPage>
|
||||
<OverlayPage id={id} path="/other" width={343} height={431}>
|
||||
<Context.Card>
|
||||
<Context.Title>Some other bit</Context.Title>
|
||||
<Context.Section>
|
||||
<button type="button" onClick={() => router.navigate("/")}>
|
||||
Go BACK PLS
|
||||
</button>
|
||||
</Context.Section>
|
||||
</Context.Card>
|
||||
</OverlayPage>
|
||||
</OverlayRouter>
|
||||
</Overlay>
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
import { useTranslation } from "react-i18next";
|
||||
import { useHistory } from "react-router-dom";
|
||||
|
||||
import { Icon, Icons } from "@/components/Icon";
|
||||
import { useGoBack } from "@/hooks/useGoBack";
|
||||
|
||||
export function BackLink() {
|
||||
export function BackLink(props: { url: string }) {
|
||||
const { t } = useTranslation();
|
||||
const goBack = useGoBack();
|
||||
const history = useHistory();
|
||||
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
<span
|
||||
onClick={() => goBack()}
|
||||
onClick={() => history.push(props.url)}
|
||||
className="flex items-center cursor-pointer text-type-secondary hover:text-white transition-colors duration-200 font-medium"
|
||||
>
|
||||
<Icon className="mr-2" icon={Icons.ARROW_LEFT} />
|
||||
|
|
|
@ -13,9 +13,11 @@ export function usePlayer() {
|
|||
const setMeta = usePlayerStore((s) => s.setMeta);
|
||||
const status = usePlayerStore((s) => s.status);
|
||||
const display = usePlayerStore((s) => s.display);
|
||||
const reset = usePlayerStore((s) => s.reset);
|
||||
const { init } = useInitializePlayer();
|
||||
|
||||
return {
|
||||
reset,
|
||||
status,
|
||||
setMeta(meta: PlayerMeta) {
|
||||
setMeta(meta);
|
||||
|
|
56
src/components/player/internals/ContextUtils.tsx
Normal file
56
src/components/player/internals/ContextUtils.tsx
Normal file
|
@ -0,0 +1,56 @@
|
|||
import { Icon, Icons } from "@/components/Icon";
|
||||
|
||||
function Card(props: { children: React.ReactNode }) {
|
||||
return <div className="px-6 py-8">{props.children}</div>;
|
||||
}
|
||||
|
||||
function Title(props: { children: React.ReactNode }) {
|
||||
return (
|
||||
<h3 className="uppercase font-bold text-video-context-type-secondary text-sm pl-1 pb-2 border-b border-opacity-25 border-video-context-border mb-6">
|
||||
{props.children}
|
||||
</h3>
|
||||
);
|
||||
}
|
||||
|
||||
function Section(props: { children: React.ReactNode }) {
|
||||
return <div className="my-5">{props.children}</div>;
|
||||
}
|
||||
|
||||
function Link(props: { onClick?: () => void; children: React.ReactNode }) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className="flex justify-between items-center py-2 pl-3 pr-3 -ml-3 rounded hover:bg-video-context-border hover:bg-opacity-10 w-full"
|
||||
style={{ width: "calc(100% + 1.5rem)" }}
|
||||
onClick={props.onClick}
|
||||
>
|
||||
{props.children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
function LinkTitle(props: { children: React.ReactNode }) {
|
||||
return (
|
||||
<span className="text-video-context-type-main font-medium">
|
||||
{props.children}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
function LinkChevron(props: { children?: React.ReactNode }) {
|
||||
return (
|
||||
<span className="text-white flex items-center font-medium">
|
||||
{props.children}
|
||||
<Icon className="text-xl ml-1" icon={Icons.CHEVRON_RIGHT} />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export const Context = {
|
||||
Card,
|
||||
Title,
|
||||
Section,
|
||||
Link,
|
||||
LinkTitle,
|
||||
LinkChevron,
|
||||
};
|
|
@ -56,9 +56,9 @@ export function useInternalOverlayRouter(id: string) {
|
|||
}
|
||||
|
||||
const close = useCallback(() => {
|
||||
if (route) setRoute(null);
|
||||
setTransition(null);
|
||||
setRoute(null);
|
||||
}, [setRoute, setTransition]);
|
||||
}, [setRoute, route, setTransition]);
|
||||
|
||||
const open = useCallback(() => {
|
||||
const anchor = document.getElementById(`__overlayRouter::${id}`);
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
import { RunOutput } from "@movie-web/providers";
|
||||
import { useCallback } from "react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useAsync } from "react-use";
|
||||
|
||||
import { MWStreamType } from "@/backend/helpers/streams";
|
||||
import { getMetaFromId } from "@/backend/metadata/getmeta";
|
||||
import { decodeTMDBId } from "@/backend/metadata/tmdb";
|
||||
import { usePlayer } from "@/components/player/hooks/usePlayer";
|
||||
import { usePlayerMeta } from "@/components/player/hooks/usePlayerMeta";
|
||||
import { MetaPart } from "@/pages/parts/player/MetaPart";
|
||||
import { PlayerPart } from "@/pages/parts/player/PlayerPart";
|
||||
import { ScrapingPart } from "@/pages/parts/player/ScrapingPart";
|
||||
import { playerStatus } from "@/stores/player/slices/source";
|
||||
|
@ -18,18 +16,13 @@ export function PlayerView() {
|
|||
episode?: string;
|
||||
season?: string;
|
||||
}>();
|
||||
const { status, playMedia } = usePlayer();
|
||||
const { status, playMedia, reset } = usePlayer();
|
||||
const { setPlayerMeta, scrapeMedia } = usePlayerMeta();
|
||||
const [backUrl] = useState("/");
|
||||
|
||||
const { loading, error } = useAsync(async () => {
|
||||
const data = decodeTMDBId(params.media);
|
||||
if (!data) return;
|
||||
|
||||
const meta = await getMetaFromId(data.type, data.id, params.season);
|
||||
if (!meta) return;
|
||||
|
||||
setPlayerMeta(meta);
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
reset();
|
||||
}, [params.media, reset]);
|
||||
|
||||
const playAfterScrape = useCallback(
|
||||
(out: RunOutput | null) => {
|
||||
|
@ -57,12 +50,9 @@ export function PlayerView() {
|
|||
);
|
||||
|
||||
return (
|
||||
<PlayerPart>
|
||||
<PlayerPart backUrl={backUrl}>
|
||||
{status === playerStatus.IDLE ? (
|
||||
<div className="flex items-center justify-center">
|
||||
{loading ? <p>loading meta...</p> : null}
|
||||
{error ? <p>failed to load meta!</p> : null}
|
||||
</div>
|
||||
<MetaPart onGetMeta={setPlayerMeta} />
|
||||
) : null}
|
||||
{status === playerStatus.SCRAPING && scrapeMedia ? (
|
||||
<ScrapingPart media={scrapeMedia} onGetStream={playAfterScrape} />
|
||||
|
|
54
src/pages/parts/player/MetaPart.tsx
Normal file
54
src/pages/parts/player/MetaPart.tsx
Normal file
|
@ -0,0 +1,54 @@
|
|||
import { useHistory, useParams } from "react-router-dom";
|
||||
import { useAsync } from "react-use";
|
||||
|
||||
import { DetailedMeta, getMetaFromId } from "@/backend/metadata/getmeta";
|
||||
import { decodeTMDBId } from "@/backend/metadata/tmdb";
|
||||
import { MWMediaType } from "@/backend/metadata/types/mw";
|
||||
|
||||
export interface MetaPartProps {
|
||||
onGetMeta?: (meta: DetailedMeta, episodeId?: string) => void;
|
||||
}
|
||||
|
||||
export function MetaPart(props: MetaPartProps) {
|
||||
const params = useParams<{
|
||||
media: string;
|
||||
episode?: string;
|
||||
season?: string;
|
||||
}>();
|
||||
const history = useHistory();
|
||||
|
||||
const { loading, error } = useAsync(async () => {
|
||||
const data = decodeTMDBId(params.media);
|
||||
if (!data) return;
|
||||
|
||||
const meta = await getMetaFromId(data.type, data.id, params.season);
|
||||
if (!meta) return;
|
||||
|
||||
// replace link with new link if youre not already on the right link
|
||||
let epId = params.episode;
|
||||
if (meta.meta.type === MWMediaType.SERIES) {
|
||||
let ep = meta.meta.seasonData.episodes.find(
|
||||
(v) => v.id === params.episode
|
||||
);
|
||||
if (!ep) ep = meta.meta.seasonData.episodes[0];
|
||||
epId = ep.id;
|
||||
if (
|
||||
params.season !== meta.meta.seasonData.id ||
|
||||
params.episode !== ep.id
|
||||
) {
|
||||
history.replace(
|
||||
`/media/${params.media}/${meta.meta.seasonData.id}/${ep.id}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
props.onGetMeta?.(meta, epId);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-center">
|
||||
{loading ? <p>loading meta...</p> : null}
|
||||
{error ? <p>failed to load meta!</p> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -6,6 +6,7 @@ import { useShouldShowControls } from "@/components/player/hooks/useShouldShowCo
|
|||
|
||||
export interface PlayerPartProps {
|
||||
children?: ReactNode;
|
||||
backUrl: string;
|
||||
onLoad?: () => void;
|
||||
}
|
||||
|
||||
|
@ -34,7 +35,7 @@ export function PlayerPart(props: PlayerPartProps) {
|
|||
<Player.TopControls show={showTargets}>
|
||||
<div className="grid grid-cols-[1fr,auto] xl:grid-cols-3 items-center">
|
||||
<div className="flex space-x-3 items-center">
|
||||
<Player.BackLink />
|
||||
<Player.BackLink url={props.backUrl} />
|
||||
<span className="text mx-3 text-type-secondary">/</span>
|
||||
<Player.Title />
|
||||
<Player.BookmarkButton />
|
||||
|
|
|
@ -67,13 +67,18 @@ function App() {
|
|||
<QuickSearch />
|
||||
</Route>
|
||||
<Route exact path="/search/:type">
|
||||
<Redirect to="/browse" />
|
||||
<Redirect to="/browse" push={false} />
|
||||
</Route>
|
||||
<Route exact path="/search/:type/:query?">
|
||||
{({ match }) => {
|
||||
if (match?.params.query)
|
||||
return <Redirect to={`/browse/${match?.params.query}`} />;
|
||||
return <Redirect to="/browse" />;
|
||||
return (
|
||||
<Redirect
|
||||
to={`/browse/${match?.params.query}`}
|
||||
push={false}
|
||||
/>
|
||||
);
|
||||
return <Redirect to="/browse" push={false} />;
|
||||
}}
|
||||
</Route>
|
||||
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import { DisplayInterface } from "@/components/player/display/displayInterface";
|
||||
import { playerStatus } from "@/stores/player/slices/source";
|
||||
import { MakeSlice } from "@/stores/player/slices/types";
|
||||
|
||||
export interface DisplaySlice {
|
||||
display: DisplayInterface | null;
|
||||
setDisplay(display: DisplayInterface | null): void;
|
||||
reset(): void;
|
||||
}
|
||||
|
||||
export const createDisplaySlice: MakeSlice<DisplaySlice> = (set, get) => ({
|
||||
|
@ -68,4 +70,11 @@ export const createDisplaySlice: MakeSlice<DisplaySlice> = (set, get) => ({
|
|||
s.display = newDisplay;
|
||||
});
|
||||
},
|
||||
reset() {
|
||||
get().display?.destroy();
|
||||
set((s) => {
|
||||
s.status = playerStatus.IDLE;
|
||||
s.meta = null;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
|
@ -123,6 +123,17 @@ module.exports = {
|
|||
|
||||
audio: {
|
||||
set: "#A75FC9"
|
||||
},
|
||||
|
||||
context: {
|
||||
background: "#0C1216",
|
||||
light: "#4D79A8",
|
||||
border: "#4F5C66",
|
||||
|
||||
type: {
|
||||
main: "#617A8A",
|
||||
secondary: "#374A56"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue