diff --git a/front/apps/mobile/app/(app)/search/index.tsx b/front/apps/mobile/app/(app)/search/index.tsx index ad3064f3..2aa90ae2 100644 --- a/front/apps/mobile/app/(app)/search/index.tsx +++ b/front/apps/mobile/app/(app)/search/index.tsx @@ -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 = () => { }, }} /> - + ); }; diff --git a/front/apps/web/src/pages/+Layout.tsx b/front/apps/web/src/pages/+Layout.tsx index 21a864d7..da843cbf 100644 --- a/front/apps/web/src/pages/+Layout.tsx +++ b/front/apps/web/src/pages/+Layout.tsx @@ -22,6 +22,7 @@ import "~/polyfill"; // typeof layoutInfo === "function" ? { Layout: layoutInfo, props: {} } : layoutInfo; // return } 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 ( <> - - - - + {/* */} + {/* */} + {/* */} + {/* */} {children} - {/* - - */} - - - - - - + {/* {/* */} + {/* */} + {/* */} + {/* */} + {/* */} + {/* */} + {/* */} + {/* */} + {/* */} ); } + diff --git a/front/apps/web/src/pages/+config.ts b/front/apps/web/src/pages/+config.ts index 37249200..9c59c79b 100644 --- a/front/apps/web/src/pages/+config.ts +++ b/front/apps/web/src/pages/+config.ts @@ -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; diff --git a/front/apps/web/src/pages/index/+Page.tsx b/front/apps/web/src/pages/index/+Page.tsx index a423e40c..42416ea1 100644 --- a/front/apps/web/src/pages/index/+Page.tsx +++ b/front/apps/web/src/pages/index/+Page.tsx @@ -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
+

tosej

+
+} diff --git a/front/apps/web/vite.config.ts b/front/apps/web/vite.config.ts index a051333e..5505458d 100644 --- a/front/apps/web/vite.config.ts +++ b/front/apps/web/vite.config.ts @@ -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; diff --git a/front/packages/primitives/src/index.ts b/front/packages/primitives/src/index.ts index c163e34f..583fc2a4 100644 --- a/front/packages/primitives/src/index.ts +++ b/front/packages/primitives/src/index.ts @@ -43,3 +43,4 @@ export * from "./chip"; export * from "./utils"; export * from "./constants"; export * from "./navigation/router"; +export * from "./navigation/params"; diff --git a/front/packages/primitives/src/links.tsx b/front/packages/primitives/src/links.tsx index b20c6f73..6c8918be 100644 --- a/front/packages/primitives/src/links.tsx +++ b/front/packages/primitives/src/links.tsx @@ -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 ( - +

{children} - +

); }; @@ -93,20 +68,17 @@ export const PressableFeedback = forwardRef(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 ( { 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; } diff --git a/front/packages/primitives/src/navigation/params.ts b/front/packages/primitives/src/navigation/params.ts new file mode 100644 index 00000000..7b82bc93 --- /dev/null +++ b/front/packages/primitives/src/navigation/params.ts @@ -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; +}; diff --git a/front/packages/primitives/src/navigation/router.ts b/front/packages/primitives/src/navigation/router.ts index b70e4c69..0eea7890 100644 --- a/front/packages/primitives/src/navigation/router.ts +++ b/front/packages/primitives/src/navigation/router.ts @@ -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; }; diff --git a/front/packages/primitives/src/navigation/router.web.ts b/front/packages/primitives/src/navigation/router.web.ts index b683eae0..0eea7890 100644 --- a/front/packages/primitives/src/navigation/router.web.ts +++ b/front/packages/primitives/src/navigation/router.web.ts @@ -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; }; diff --git a/front/packages/ui/src/browse/index.tsx b/front/packages/ui/src/browse/index.tsx index 9b038f69..20a22c7b 100644 --- a/front/packages/ui/src/browse/index.tsx +++ b/front/packages/ui/src/browse/index.tsx @@ -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, ): WithLoading & ComponentProps> => { diff --git a/front/packages/ui/src/login/login.tsx b/front/packages/ui/src/login/login.tsx index cfdbea09..25a3e0b9 100644 --- a/front/packages/ui/src/login/login.tsx +++ b/front/packages/ui/src/login/login.tsx @@ -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), diff --git a/front/packages/ui/src/login/oidc.tsx b/front/packages/ui/src/login/oidc.tsx index 44b31e3e..a935c79b 100644 --- a/front/packages/ui/src/login/oidc.tsx +++ b/front/packages/ui/src/login/oidc.tsx @@ -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); diff --git a/front/packages/ui/src/login/register.tsx b/front/packages/ui/src/login/register.tsx index efca0d1e..877d70ee 100644 --- a/front/packages/ui/src/login/register.tsx +++ b/front/packages/ui/src/login/register.tsx @@ -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), diff --git a/front/packages/ui/src/navbar/index.tsx b/front/packages/ui/src/navbar/index.tsx index ec001dc4..ee210ec7 100644 --- a/front/packages/ui/src/navbar/index.tsx +++ b/front/packages/ui/src/navbar/index.tsx @@ -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(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]); diff --git a/front/packages/ui/src/player/index.tsx b/front/packages/ui/src/player/index.tsx index 01a1fdad..1f30218d 100644 --- a/front/packages/ui/src/player/index.tsx +++ b/front/packages/ui/src/player/index.tsx @@ -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)} /> diff --git a/front/packages/ui/src/search/index.tsx b/front/packages/ui/src/search/index.tsx index 8d88b3bb..4939bef8 100644 --- a/front/packages/ui/src/search/index.tsx +++ b/front/packages/ui/src/search/index.tsx @@ -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, diff --git a/front/yarn.lock b/front/yarn.lock index e89ceb05..6695fea2 100644 --- a/front/yarn.lock +++ b/front/yarn.lock @@ -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, typescript@patch:typescript@^5.0.4#~builtin, typescript@patch:typescript@^5.3.3#~builtin": +"typescript@patch:typescript@5.3.3#~builtin, typescript@patch:typescript@^5.3.3#~builtin": version: 5.3.3 resolution: "typescript@patch:typescript@npm%3A5.3.3#~builtin::version=5.3.3&hash=701156" bin: