From 2f5423073e01f5d201a8a8c7d706b85ab3d01389 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Mon, 16 Feb 2026 11:03:32 +0100 Subject: [PATCH 1/4] Small style fixes --- front/bun.lock | 2 +- front/src/app/_layout.tsx | 4 +- front/src/primitives/container.tsx | 2 +- front/src/primitives/select.web.tsx | 4 +- front/src/primitives/utils/hover.ts | 42 +++++++++++++++++++ front/src/primitives/utils/index.tsx | 3 +- front/src/primitives/utils/nojs.tsx | 17 -------- front/src/primitives/utils/spacing.tsx | 14 ------- front/src/primitives/utils/touchonly.tsx | 34 --------------- front/src/ui/player/controls/index.tsx | 2 - .../ui/player/controls/middle-controls.tsx | 6 +-- 11 files changed, 53 insertions(+), 77 deletions(-) create mode 100644 front/src/primitives/utils/hover.ts delete mode 100644 front/src/primitives/utils/nojs.tsx delete mode 100644 front/src/primitives/utils/touchonly.tsx diff --git a/front/bun.lock b/front/bun.lock index 3b81d2a7..60003eb3 100644 --- a/front/bun.lock +++ b/front/bun.lock @@ -442,7 +442,7 @@ "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], - "@legendapp/list": ["@legendapp/list@github:zoriya/legend-list#c36ff94", { "dependencies": { "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "react": "*" } }, "zoriya-legend-list-c36ff94"], + "@legendapp/list": ["@legendapp/list@github:zoriya/legend-list#a7465a6", { "dependencies": { "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "react": "*" } }, "zoriya-legend-list-a7465a6"], "@material-symbols/svg-400": ["@material-symbols/svg-400@0.40.2", "", {}, "sha512-e2yEgZW/OveVT1sGaZW1kkRWTPVghjsJYWy+vIea3q08Fv2o7FCYv23PESMyr5D4AaAXdM5dKWkF1e6yIm4swA=="], diff --git a/front/src/app/_layout.tsx b/front/src/app/_layout.tsx index fb5a9652..56447b66 100644 --- a/front/src/app/_layout.tsx +++ b/front/src/app/_layout.tsx @@ -3,10 +3,12 @@ import { Slot } from "expo-router"; import { Platform } from "react-native"; import { Providers } from "~/providers"; import "../global.css"; -import { Tooltip } from "~/primitives"; +import { Tooltip, useMobileHover } from "~/primitives"; import "~/fonts.web.css"; export default function Layout() { + useMobileHover(); + return ( diff --git a/front/src/primitives/container.tsx b/front/src/primitives/container.tsx index 8fd9fd00..e74e0f7d 100644 --- a/front/src/primitives/container.tsx +++ b/front/src/primitives/container.tsx @@ -14,7 +14,7 @@ export const Container = ({ return ( { + if (Platform.OS !== "web") return; + + // biome-ignore lint/correctness/useHookAtTopLevel: const condition + useEffect(() => { + const enableHover = () => { + console.log("pc"); + if (preventHover) return; + document.body.classList.remove("noHover"); + }; + + const disableHover = () => { + console.log("mobile"); + if (hoverTimeout) clearTimeout(hoverTimeout); + preventHover = true; + hoverTimeout = setTimeout(() => { + preventHover = false; + }, 1000); + document.body.classList.add("noHover"); + }; + + document.addEventListener("touchstart", disableHover, true); + document.addEventListener("mousemove", enableHover, true); + return () => { + document.removeEventListener("touchstart", disableHover); + document.removeEventListener("mousemove", enableHover); + }; + }, []); +}; + +export const useIsTouch = () => { + if (Platform.OS !== "web") return true; + if (typeof window === "undefined") return false; + // TODO: Subscribe to the change. + return document.body.classList.contains("noHover"); +}; diff --git a/front/src/primitives/utils/index.tsx b/front/src/primitives/utils/index.tsx index 9dece0d5..1b0d2bd0 100644 --- a/front/src/primitives/utils/index.tsx +++ b/front/src/primitives/utils/index.tsx @@ -1,6 +1,5 @@ export * from "./breakpoint"; export * from "./capitalize"; export * from "./head"; -export * from "./nojs"; +export * from "./hover"; export * from "./spacing"; -export * from "./touchonly"; diff --git a/front/src/primitives/utils/nojs.tsx b/front/src/primitives/utils/nojs.tsx deleted file mode 100644 index 8fd4796d..00000000 --- a/front/src/primitives/utils/nojs.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import type { ViewProps } from "react-native"; - -export const hiddenIfNoJs: ViewProps = { - style: { $$css: true, noJs: "noJsHidden" } as any, -}; - -export const HiddenIfNoJs = () => ( - -); diff --git a/front/src/primitives/utils/spacing.tsx b/front/src/primitives/utils/spacing.tsx index e30d1356..4b92e287 100644 --- a/front/src/primitives/utils/spacing.tsx +++ b/front/src/primitives/utils/spacing.tsx @@ -1,17 +1,3 @@ -import { Platform } from "react-native"; - -export const important = (value: T): T => { - return `${value} !important` as T; -}; - export const ts = (spacing: number) => { return spacing * 8; }; - -export const focusReset: object = - Platform.OS === "web" - ? { - boxShadow: "unset", - outline: "none", - } - : {}; diff --git a/front/src/primitives/utils/touchonly.tsx b/front/src/primitives/utils/touchonly.tsx deleted file mode 100644 index df6d2300..00000000 --- a/front/src/primitives/utils/touchonly.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { Platform, type ViewProps } from "react-native"; - -export const TouchOnlyCss = () => { - return ( - - ); -}; - -export const touchOnly: ViewProps = { - style: - Platform.OS === "web" - ? ({ $$css: true, touchOnly: "touchOnly" } as any) - : {}, -}; -export const noTouch: ViewProps = { - style: - Platform.OS === "web" - ? ({ $$css: true, noTouch: "noTouch" } as any) - : { display: "none" }, -}; - -export const useIsTouch = () => { - if (Platform.OS !== "web") return true; - if (typeof window === "undefined") return false; - // TODO: Subscribe to the change. - return document.body.classList.contains("noHover"); -}; diff --git a/front/src/ui/player/controls/index.tsx b/front/src/ui/player/controls/index.tsx index 290a99b3..ee07fc06 100644 --- a/front/src/ui/player/controls/index.tsx +++ b/front/src/ui/player/controls/index.tsx @@ -72,8 +72,6 @@ export const Controls = ({ previous={previous} next={next} setMenu={setMenu} - // Fixed is used because firefox android make the hover disappear under the navigation bar in absolute - // position: Platform.OS === "web" ? ("fixed" as any) : "absolute", className="absolute bottom-0 w-full bg-slate-900/50 px-safe pt-safe" {...hoverControls} /> diff --git a/front/src/ui/player/controls/middle-controls.tsx b/front/src/ui/player/controls/middle-controls.tsx index 9039d0a3..a56e71ec 100644 --- a/front/src/ui/player/controls/middle-controls.tsx +++ b/front/src/ui/player/controls/middle-controls.tsx @@ -35,12 +35,12 @@ export const MiddleControls = ({ "mx-6 bg-gray-800/70", !previous && "pointer-events-none opacity-0", )} - iconClassName="h-16 w-16" + iconClassName="h-16 w-16 fill-slate-200 dark:fill-slate-200" /> ); From 637505dde92fbd38541eb1b4e550821c4c8f2d8b Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Mon, 16 Feb 2026 12:01:59 +0100 Subject: [PATCH 2/4] Make native web tabbar --- front/src/app/(app)/(tabs)/_layout.tsx | 9 +- front/src/app/(app)/_layout.tsx | 8 +- front/src/app/_layout.tsx | 4 + front/src/primitives/links.tsx | 6 +- front/src/ui/navbar.tsx | 158 ++++++++++++++----------- 5 files changed, 109 insertions(+), 76 deletions(-) diff --git a/front/src/app/(app)/(tabs)/_layout.tsx b/front/src/app/(app)/(tabs)/_layout.tsx index e07da8b0..05977132 100644 --- a/front/src/app/(app)/(tabs)/_layout.tsx +++ b/front/src/app/(app)/(tabs)/_layout.tsx @@ -1,14 +1,21 @@ import Browse from "@material-symbols/svg-400/rounded/browse-fill.svg"; // import Downloading from "@material-symbols/svg-400/rounded/downloading-fill.svg"; import Home from "@material-symbols/svg-400/rounded/home-fill.svg"; -import { Tabs } from "expo-router"; +import { Slot, Tabs } from "expo-router"; import { useTranslation } from "react-i18next"; +import { Platform } from "react-native"; import { Icon } from "~/primitives"; import { cn } from "~/utils"; +export const unstable_settings = { + initialRouteName: "index", +}; + export default function TabsLayout() { const { t } = useTranslation(); + if (Platform.OS === "web") return ; + return ( , + headerTitle: () => , headerRight: () => , contentStyle: { paddingLeft: insets.left, diff --git a/front/src/app/_layout.tsx b/front/src/app/_layout.tsx index 56447b66..8d76b17e 100644 --- a/front/src/app/_layout.tsx +++ b/front/src/app/_layout.tsx @@ -6,6 +6,10 @@ import "../global.css"; import { Tooltip, useMobileHover } from "~/primitives"; import "~/fonts.web.css"; +export const unstable_settings = { + initialRouteName: "(app)", +}; + export default function Layout() { useMobileHover(); diff --git a/front/src/primitives/links.tsx b/front/src/primitives/links.tsx index 8d2a2ff1..0bf85a95 100644 --- a/front/src/primitives/links.tsx +++ b/front/src/primitives/links.tsx @@ -5,11 +5,11 @@ import { Platform, Pressable, type PressableProps, - Text, type TextProps, } from "react-native"; import { useResolveClassNames } from "uniwind"; import { cn } from "~/utils"; +import { P } from "./text"; export function useLinkTo({ href, @@ -57,7 +57,7 @@ export const A = ({ const linkProps = useLinkTo({ href, replace }); return ( - {children} - +

); }; diff --git a/front/src/ui/navbar.tsx b/front/src/ui/navbar.tsx index d48d2bdd..db3eeab6 100644 --- a/front/src/ui/navbar.tsx +++ b/front/src/ui/navbar.tsx @@ -45,6 +45,24 @@ import { useAccount, useAccounts } from "~/providers/account-context"; import { logout } from "~/ui/login/logic"; import { cn } from "~/utils"; +export const NavbarLeft = () => { + const { t } = useTranslation(); + + if (Platform.OS !== "web") return ; + + return ( + + + + {t("navbar.browse")} + + + ); +}; + export const NavbarTitle = ({ className, ...props @@ -64,68 +82,24 @@ export const NavbarTitle = ({ ); }; -const getDisplayUrl = (url: string) => { - url = url.replace(/\/api$/, ""); - url = url.replace(/https?:\/\//, ""); - return url; -}; - -export const NavbarProfile = () => { +export const NavbarRight = () => { const { t } = useTranslation(); - const account = useAccount(); - const accounts = useAccounts(); + const isAdmin = false; //useHasPermission(AdminPage.requiredPermissions); return ( - } - as={PressableFeedback} - src={account?.logo} - placeholder={account?.username} - alt={t("navbar.login")} - className="m-2" - {...tooltip(account?.username ?? t("navbar.login"))} - > - {accounts?.map((x) => ( - - } - selected={x.selected} - onSelect={() => x.select()} + + + {isAdmin && ( + - ))} - {accounts.length > 0 &&
} - - {!account ? ( - <> - - - - ) : ( - <> - - - )} -
+ + ); }; @@ -217,24 +191,68 @@ const SearchBar = () => { ); }; -export const NavbarRight = () => { +const getDisplayUrl = (url: string) => { + url = url.replace(/\/api$/, ""); + url = url.replace(/https?:\/\//, ""); + return url; +}; + +export const NavbarProfile = () => { const { t } = useTranslation(); - const isAdmin = false; //useHasPermission(AdminPage.requiredPermissions); + const account = useAccount(); + const accounts = useAccounts(); return ( - - - {isAdmin && ( - } + as={PressableFeedback} + src={account?.logo} + placeholder={account?.username} + alt={t("navbar.login")} + className="m-2" + {...tooltip(account?.username ?? t("navbar.login"))} + > + {accounts?.map((x) => ( + + } + selected={x.selected} + onSelect={() => x.select()} /> + ))} + {accounts.length > 0 &&
} + + {!account ? ( + <> + + + + ) : ( + <> + + + )} - -
+ ); }; From cef7a64017fb0af13aa443b0220ef208a0283426 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Mon, 16 Feb 2026 12:29:11 +0100 Subject: [PATCH 3/4] Properly use nextup in the home --- api/src/controllers/entries.ts | 2 +- api/src/controllers/profiles/nextup.ts | 11 +++- front/src/ui/home/index.tsx | 15 ++--- front/src/ui/home/news.tsx | 26 ++++----- front/src/ui/home/nextup.tsx | 66 +++++++++++++++++++++ front/src/ui/home/watchlist.tsx | 80 -------------------------- 6 files changed, 96 insertions(+), 104 deletions(-) create mode 100644 front/src/ui/home/nextup.tsx delete mode 100644 front/src/ui/home/watchlist.tsx diff --git a/api/src/controllers/entries.ts b/api/src/controllers/entries.ts index 6b1d0ffc..5c1c0d80 100644 --- a/api/src/controllers/entries.ts +++ b/api/src/controllers/entries.ts @@ -135,7 +135,7 @@ const newsSort: Sort = { ], }; -const entryRelations = { +export const entryRelations = { translations: () => { const { pk, language, ...trans } = getColumns(entryTranslations); return db diff --git a/api/src/controllers/profiles/nextup.ts b/api/src/controllers/profiles/nextup.ts index c2ffffbe..d27b6122 100644 --- a/api/src/controllers/profiles/nextup.ts +++ b/api/src/controllers/profiles/nextup.ts @@ -6,6 +6,7 @@ import { entries } from "~/db/schema"; import { watchlist } from "~/db/schema/watchlist"; import { getColumns } from "~/db/utils"; import { Entry } from "~/models/entry"; +import { Show } from "~/models/show"; import { AcceptLanguage, createPage, @@ -21,6 +22,7 @@ import { desc } from "~/models/utils/descriptions"; import { entryFilters, entryProgressQ, + entryRelations, entryVideosQ, getEntryTransQ, mapProgress, @@ -71,7 +73,7 @@ export const nextup = new Elysia({ tags: ["profiles"] }) query: { sort, filter, query, limit, after }, headers: { "accept-language": languages, ...headers }, request: { url }, - jwt: { sub }, + jwt: { sub, settings }, }) => { const langs = processLanguages(languages); const transQ = getEntryTransQ(langs); @@ -102,6 +104,11 @@ export const nextup = new Elysia({ tags: ["profiles"] }) seasonNumber: sql`${seasonNumber}`, episodeNumber: sql`${episodeNumber}`, name: sql`${transQ.name}`, + + show: sql`${entryRelations.show({ + languages: langs, + preferOriginal: settings.preferOriginal, + })}`, }) .from(entries) .innerJoin(watchlist, eq(watchlist.nextEntry, entries.pk)) @@ -134,7 +141,7 @@ export const nextup = new Elysia({ tags: ["profiles"] }) "accept-language": AcceptLanguage({ autoFallback: true }), }), response: { - 200: Page(Entry), + 200: Page(t.Intersect([Entry, t.Object({ show: Show })])), }, }, ); diff --git a/front/src/ui/home/index.tsx b/front/src/ui/home/index.tsx index 72a6cc78..ebeade48 100644 --- a/front/src/ui/home/index.tsx +++ b/front/src/ui/home/index.tsx @@ -8,9 +8,9 @@ import { HeaderBackground, useScrollNavbar } from "../navbar"; import { GenreGrid } from "./genre"; import { Header } from "./header"; import { NewsList } from "./news"; +import { NextupList } from "./nextup"; import { Recommended } from "./recommended"; import { VerticalRecommended } from "./vertical"; -import { WatchlistList } from "./watchlist"; export const HomePage = () => { const genres = shuffle(Object.values(Genre.enum)); @@ -50,7 +50,7 @@ export const HomePage = () => { )} Loader={Header.Loader} /> - + {genres .filter((_, i) => i < 2) @@ -64,10 +64,11 @@ export const HomePage = () => { ))} - {/* - TODO: Lazy load those items - {randomItems.filter((_, i) => i >= 6).map((x) => )} - */} + {genres + .filter((_, i) => i >= 6) + .map((x) => ( + + ))} ); @@ -75,7 +76,7 @@ export const HomePage = () => { HomePage.queries = (randomItems: Genre[]) => [ Header.query(), - WatchlistList.query(), + NextupList.query(), NewsList.query(), ...randomItems.filter((_, i) => i < 6).map((x) => GenreGrid.query(x)), Recommended.query(), diff --git a/front/src/ui/home/news.tsx b/front/src/ui/home/news.tsx index f8241bc0..b504f5b2 100644 --- a/front/src/ui/home/news.tsx +++ b/front/src/ui/home/news.tsx @@ -15,20 +15,18 @@ export const NewsList = () => { query={NewsList.query()} layout={{ ...EntryBox.layout, layout: "horizontal" }} Empty={} - Render={({ item }) => { - return ( - - ); - }} + Render={({ item }) => ( + + )} Loader={EntryBox.Loader} /> diff --git a/front/src/ui/home/nextup.tsx b/front/src/ui/home/nextup.tsx new file mode 100644 index 00000000..fede9cc2 --- /dev/null +++ b/front/src/ui/home/nextup.tsx @@ -0,0 +1,66 @@ +import { useTranslation } from "react-i18next"; +import { View } from "react-native"; +import { EntryBox, entryDisplayNumber } from "~/components/entries"; +import { ItemGrid } from "~/components/items"; +import { Entry } from "~/models"; +import { Button, Link, P } from "~/primitives"; +import { useAccount } from "~/providers/account-context"; +import { InfiniteFetch, type QueryIdentifier } from "~/query"; +import { EmptyView } from "~/ui/empty-view"; +import { Header } from "./genre"; + +export const NextupList = () => { + const { t } = useTranslation(); + const account = useAccount(); + + if (!account) { + return ( + <> +
+ +

{t("home.watchlistLogin")}

+