Rework router

This commit is contained in:
Zoe Roux 2024-05-11 11:27:53 +02:00
parent cb65325c56
commit d2a6ffc1c7
No known key found for this signature in database
19 changed files with 139 additions and 131 deletions

View File

@ -21,12 +21,9 @@
import { SearchPage } from "@kyoo/ui";
import { Stack, useLocalSearchParams } from "expo-router";
import { useTranslation } from "react-i18next";
import { createParam } from "solito";
import { useRouter } from "@kyoo/primitives";
import { useRouter, useParam } from "@kyoo/primitives";
import { useTheme } from "yoshiki/native";
const { useParam } = createParam<{ q?: string }>();
const Search = () => {
const theme = useTheme();
const { back } = useRouter();
@ -52,7 +49,7 @@ const Search = () => {
},
}}
/>
<SearchPage {...routeParams} />
<SearchPage q={query} />
</>
);
};

View File

@ -22,6 +22,7 @@ import "~/polyfill";
// typeof layoutInfo === "function" ? { Layout: layoutInfo, props: {} } : layoutInfo;
// return <Layout page={<Component {...props} />} randomItems={[]} {...layoutProps} />;
// };
const GlobalCssTheme = () => {
const theme = useTheme();
return (
@ -76,35 +77,36 @@ const GlobalCssTheme = () => {
};
export default function Layout({ children }: { children: ReactNode }) {
// TODO: theme ssr
const userTheme = useUserTheme(undefined);
// const userTheme = useUserTheme(undefined);
useMobileHover();
// TODO: ssr account/error
return (
<>
<AccountProvider ssrAccount={undefined} ssrError={undefined}>
<ThemeSelector theme={userTheme} font={{ normal: "inherit" }}>
<PortalProvider>
<SnackbarProvider>
{/* <AccountProvider ssrAccount={undefined} ssrError={undefined}> */}
{/* <ThemeSelector theme={userTheme} font={{ normal: "inherit" }}> */}
{/* <PortalProvider> */}
{/* <SnackbarProvider> */}
<GlobalCssTheme />
{children}
{/* <ConnectionErrorVerifier skipErrors={(Component as QueryPage).isPublic}>
<WithLayout
Component={children}
randomItems={
randomItems[Component.displayName!] ??
arrayShuffle((Component as QueryPage).randomItems ?? [])
}
{...props}
/>
</ConnectionErrorVerifier> */}
<Tooltip id="tooltip" positionStrategy={"fixed"} />
</SnackbarProvider>
</PortalProvider>
</ThemeSelector>
</AccountProvider>
<ReactQueryDevtools initialIsOpen={false} />
{/* {/* <ConnectionErrorVerifier skipErrors={(Component as QueryPage).isPublic}> */}
{/* <WithLayout */}
{/* Component={children} */}
{/* randomItems={ */}
{/* randomItems[Component.displayName!] ?? */}
{/* arrayShuffle((Component as QueryPage).randomItems ?? []) */}
{/* } */}
{/* {...props} */}
{/* /> */}
{/* </ConnectionErrorVerifier> */}
{/* <Tooltip id="tooltip" positionStrategy={"fixed"} /> */}
{/* </SnackbarProvider> */}
{/* </PortalProvider> */}
{/* </ThemeSelector> */}
{/* </AccountProvider> */}
{/* <ReactQueryDevtools initialIsOpen={false} /> */}
</>
);
}

View File

@ -1,11 +1,10 @@
import type { Config } from "vike/types";
import logoUrl from "../../public/icon.svg";
import vikeReact from "vike-react/config";
import vikeReactQuery from "vike-react-query/config";
export default {
ssr: true,
title: "Kyoo",
favicon: logoUrl,
extends: [vikeReact, vikeReactQuery],
} satisfies Config;

View File

@ -1,3 +1,9 @@
import { HomePage } from "@kyoo/ui";
// import { HomePage } from "@kyoo/ui";
export default HomePage;
// export default HomePage;
export default function Test() {
return <div>
<p>tosej</p>
</div>
}

View File

@ -13,5 +13,15 @@ export default {
"~": path.resolve(__dirname, "./src"),
},
},
build: {
commonjsOptions: {
transformMixedEsModules: true,
},
},
optimizeDeps: {
esbuildOptions: {
mainFields: ["module", "main"],
},
},
plugins: [react(), vike(), reactNativeWeb()],
} satisfies UserConfig;

View File

@ -43,3 +43,4 @@ export * from "./chip";
export * from "./utils";
export * from "./constants";
export * from "./navigation/router";
export * from "./navigation/params";

View File

@ -23,6 +23,8 @@ import { Platform, Pressable, type TextProps, type View, type PressableProps } f
import { useTheme, useYoshiki } from "yoshiki/native";
import type { UrlObject } from "node:url";
import { alpha } from "./themes";
import { P } from "./text";
import { useLink } from "./navigation/link";
export const A = ({
href,
@ -32,43 +34,16 @@ export const A = ({
...props
}: TextProps & {
href?: string | UrlObject | null;
target?: string;
target?: "_blank";
replace?: boolean;
children: ReactNode;
}) => {
const { css, theme } = useYoshiki();
const link = useLink(href ?? "#", { target, replace });
return (
<TextLink
href={href ?? ""}
target={target}
replace={replace as any}
experimental={
replace
? {
nativeBehavior: "stack-replace",
isNestedNavigator: true,
}
: undefined
}
textProps={css(
[
{
fontFamily: theme.font.normal,
color: theme.link,
},
{
userSelect: "text",
} as any,
],
{
hrefAttrs: { target },
...props,
},
)}
>
<P {...link} {...props}>
{children}
</TextLink>
</P>
);
};
@ -93,20 +68,17 @@ export const PressableFeedback = forwardRef<View, PressableProps>(function Feedb
});
export const Link = ({
href: link,
href,
replace,
target,
children,
...props
}: { href?: string | UrlObject | null; target?: string; replace?: boolean } & PressableProps) => {
const href = link && typeof link === "object" ? parseNextPath(link) : link;
const linkProps = useLink({
href: href ?? "#",
}: { href?: string | UrlObject | null; target?: "_blank"; replace?: boolean } & PressableProps) => {
const linkProps = useLink(href ?? "#", {
target,
replace,
experimental: { nativeBehavior: "stack-replace", isNestedNavigator: true },
isNested: true,
});
// @ts-ignore Missing hrefAttrs type definition.
linkProps.hrefAttrs = { ...linkProps.hrefAttrs, target };
return (
<PressableFeedback
{...linkProps}

View File

@ -15,8 +15,9 @@ export const useLink = (
href,
onPress: (e) => {
if (e?.defaultPrevented) return;
if (Platform.OS !== "web" && href.includes("://")) {
Linking.openURL(href);
const abs = href.includes("://");
if (Platform.OS !== "web" && (abs || opts.target)) {
Linking.openURL(abs ? href : `https://${href}`);
return;
}
@ -31,7 +32,9 @@ export const useLink = (
we.shiftKey ||
// ignore everything but left clicks
we.button !== null ||
we.button !== 0
we.button !== 0 ||
// let the browser handle target blank
opts.target
)
return;
}

View File

@ -0,0 +1,23 @@
import { useCallback } from "react";
import { usePageContext } from "vike-react/usePageContext";
import { useRouter } from "./router";
export const useParam = (name: string) => {
const { urlParsed } = usePageContext();
const router = useRouter();
const val = urlParsed.search[name];
const setState = useCallback(
(newVal: string | null) => {
if (newVal) urlParsed.search[name] = newVal;
else delete urlParsed.search[name];
router.replace({
...urlParsed,
search: Object.entries(urlParsed.search).join("&"),
});
},
[router],
);
return [val, setState] as const;
};

View File

@ -1,18 +1,23 @@
import { navigate } from "vike/client/router";
import { type UrlObject, format } from "node:url";
import { useMemo } from "react";
export const useRouter = () => {
return {
push: (route: string | UrlObject) => {
if (typeof route === "object") route = format(route);
navigate(route);
},
replace: (route: string | UrlObject, opts: {isNested?: boolean} = {}) => {
if (typeof route === "object") route = format(route);
navigate(route, { overwriteLastHistoryEntry: opts.isNested });
},
back: () => {
window.history.back();
},
};
const ret = useMemo(
() => ({
push: (route: string | UrlObject) => {
if (typeof route === "object") route = format(route);
navigate(route);
},
replace: (route: string | UrlObject, opts?: { isNested?: boolean }) => {
if (typeof route === "object") route = format(route);
navigate(route, { overwriteLastHistoryEntry: true });
},
back: () => {
window.history.back();
},
}),
[],
);
return ret;
};

View File

@ -1,18 +1,23 @@
import { navigate } from "vike/client/router";
import { type UrlObject, format } from "node:url";
import { useMemo } from "react";
export const useRouter = () => {
return {
push: (route: string | UrlObject) => {
if (typeof route === "object") route = format(route);
navigate(route);
},
replace: (route: string | UrlObject, isNested = true) => {
if (typeof route === "object") route = format(route);
navigate(route, { overwriteLastHistoryEntry: true });
},
back: () => {
window.history.back();
},
};
const ret = useMemo(
() => ({
push: (route: string | UrlObject) => {
if (typeof route === "object") route = format(route);
navigate(route);
},
replace: (route: string | UrlObject, opts?: { isNested?: boolean }) => {
if (typeof route === "object") route = format(route);
navigate(route, { overwriteLastHistoryEntry: true });
},
back: () => {
window.history.back();
},
}),
[],
);
return ret;
};

View File

@ -25,18 +25,16 @@ import {
type QueryPage,
getDisplayDate,
} from "@kyoo/models";
import { useParam } from "@kyoo/primitives";
import { type ComponentProps, useState } from "react";
import { createParam } from "solito";
import { DefaultLayout } from "../layout";
import type { WithLoading } from "../fetch";
import { InfiniteFetch } from "../fetch-infinite";
import { DefaultLayout } from "../layout";
import { ItemGrid } from "./grid";
import { BrowseSettings } from "./header";
import { ItemList } from "./list";
import { Layout, SortBy, SortOrd } from "./types";
const { useParam } = createParam<{ sortBy?: string }>();
export const itemMap = (
item: WithLoading<LibraryItem>,
): WithLoading<ComponentProps<typeof ItemGrid> & ComponentProps<typeof ItemList>> => {

View File

@ -44,7 +44,7 @@ export const LoginPage: QueryPage<{ apiUrl?: string; error?: string }> = ({
const { css } = useYoshiki();
useEffect(() => {
if (!apiUrl && Platform.OS !== "web") router.replace("/server-url", false);
if (!apiUrl && Platform.OS !== "web") router.replace("/server-url", { isNested: false });
}, [apiUrl, router]);
return (
@ -70,7 +70,7 @@ export const LoginPage: QueryPage<{ apiUrl?: string; error?: string }> = ({
});
setError(error);
if (error) return;
router.replace("/", false);
router.replace("/", { isNested: false });
}}
{...css({
m: ts(1),

View File

@ -104,12 +104,12 @@ export const OidcCallbackPage: QueryPage<{
hasRun.current = true;
function onError(error: string) {
router.replace({ pathname: "/login", query: { error, apiUrl } }, false);
router.replace({ pathname: "/login", query: { error, apiUrl } }, { isNested: false });
}
async function run() {
const { error: loginError } = await oidcLogin(provider, code, apiUrl);
if (loginError) onError(loginError);
else router.replace("/", false);
else router.replace("/", { isNested: false });
}
if (error) onError(error);

View File

@ -41,7 +41,7 @@ export const RegisterPage: QueryPage<{ apiUrl?: string }> = ({ apiUrl }) => {
const { css } = useYoshiki();
useEffect(() => {
if (!apiUrl && Platform.OS !== "web") router.replace("/server-url", false);
if (!apiUrl && Platform.OS !== "web") router.replace("/server-url", { isNested: false });
}, [apiUrl, router]);
return (
@ -79,7 +79,7 @@ export const RegisterPage: QueryPage<{ apiUrl?: string }> = ({ apiUrl }) => {
const { error } = await login("register", { email, username, password, apiUrl });
setError(error);
if (error) return;
router.replace("/", false);
router.replace("/", { isNested: false });
}}
{...css({
m: ts(1),

View File

@ -29,6 +29,7 @@ import {
Link,
Menu,
PressableFeedback,
useRouter,
tooltip,
ts,
} from "@kyoo/primitives";
@ -40,11 +41,10 @@ import Search from "@material-symbols/svg-400/rounded/search-fill.svg";
import Settings from "@material-symbols/svg-400/rounded/settings.svg";
import { forwardRef, useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { Platform, type TextInput, View, type ViewProps } from "react-native";
import { useRouter } from "solito/router";
import { type Stylable, useYoshiki } from "yoshiki/native";
import { AdminPage } from "../admin";
import { KyooLongLogo } from "./icon";
import { Platform, TextInput, View, ViewProps } from "react-native";
export const NavbarTitle = (props: Stylable & { onLayout?: ViewProps["onLayout"] }) => {
const { t } = useTranslation();
@ -66,7 +66,7 @@ const SearchBar = forwardRef<TextInput, Stylable>(function SearchBar(props, ref)
useEffect(() => {
if (Platform.OS !== "web" || !hasChanged.current) return;
const action = window.location.pathname.startsWith("/search") ? replace : push;
if (query) action(`/search?q=${encodeURI(query)}`, undefined, { shallow: true });
if (query) action(`/search?q=${encodeURI(query)}`);
else back();
}, [query, push, replace, back]);

View File

@ -158,8 +158,8 @@ export const Player = ({
startTime={startTime}
onEnd={() => {
if (!data) return;
if (data.type === "movie") router.replace(`/movie/${data.slug}`, true);
else router.replace(next ?? `/show/${data.show!.slug}`, true);
if (data.type === "movie") router.replace(`/movie/${data.slug}`, { isNested: true });
else router.replace(next ?? `/show/${data.show!.slug}`, { isNested: true });
}}
{...css(StyleSheet.absoluteFillObject)}
/>

View File

@ -19,19 +19,16 @@
*/
import { type LibraryItem, LibraryItemP, type QueryIdentifier, type QueryPage } from "@kyoo/models";
import { usePageStyle } from "@kyoo/primitives";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { createParam } from "solito";
import { DefaultLayout } from "../layout";
import { InfiniteFetch } from "../fetch-infinite";
import { itemMap } from "../browse";
import { ItemGrid } from "../browse/grid";
import { BrowseSettings } from "../browse/header";
import { ItemList } from "../browse/list";
import { useParam, usePageStyle } from "@kyoo/primitives";
import { Layout, SearchSort, SortOrd } from "../browse/types";
import { InfiniteFetch } from "../fetch-infinite";
import { DefaultLayout } from "../layout";
const { useParam } = createParam<{ sortBy?: string }>();
const query = (
query?: string,

View File

@ -3144,7 +3144,6 @@ __metadata:
react-native-blurhash: ^1.1.11
react-native-fast-image: ^8.6.3
react-native-safe-area-context: 4.8.2
solito: ^4.2.0
typescript: ^5.3.3
peerDependencies:
"@gorhom/portal": "*"
@ -12079,15 +12078,6 @@ __metadata:
languageName: node
linkType: hard
"solito@npm:^4.2.0":
version: 4.2.0
resolution: "solito@npm:4.2.0"
dependencies:
typescript: ^5.0.4
checksum: 58ea67fce743cc864e7cb9065d06fa287fd99bab1b0a96c5d9bd1e974740bb4a308620622c9b46975d58ba084127a8388dc7696cc6e171ed6b501843093ab64f
languageName: node
linkType: hard
"source-map-js@npm:^1.0.1, source-map-js@npm:^1.0.2":
version: 1.0.2
resolution: "source-map-js@npm:1.0.2"
@ -12740,7 +12730,7 @@ __metadata:
languageName: node
linkType: hard
"typescript@npm:5.3.3, typescript@npm:^5.0.4, typescript@npm:^5.3.3":
"typescript@npm:5.3.3, typescript@npm:^5.3.3":
version: 5.3.3
resolution: "typescript@npm:5.3.3"
bin:
@ -12750,7 +12740,7 @@ __metadata:
languageName: node
linkType: hard
"typescript@patch:typescript@5.3.3#~builtin<compat/typescript>, typescript@patch:typescript@^5.0.4#~builtin<compat/typescript>, typescript@patch:typescript@^5.3.3#~builtin<compat/typescript>":
"typescript@patch:typescript@5.3.3#~builtin<compat/typescript>, typescript@patch:typescript@^5.3.3#~builtin<compat/typescript>":
version: 5.3.3
resolution: "typescript@patch:typescript@npm%3A5.3.3#~builtin<compat/typescript>::version=5.3.3&hash=701156"
bin: