mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-31 20:24:27 -04:00
Make the main scroll instead of the body. Support other layouts
This commit is contained in:
parent
bb63340555
commit
3b29e1a87a
@ -66,6 +66,10 @@ const GlobalCssTheme = () => {
|
|||||||
*:hover::-webkit-scrollbar-thumb {
|
*:hover::-webkit-scrollbar-thumb {
|
||||||
background-color: rgb(134, 127, 127);
|
background-color: rgb(134, 127, 127);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#__next {
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
`}</style>
|
`}</style>
|
||||||
<WebTooltip theme={theme} />
|
<WebTooltip theme={theme} />
|
||||||
<SkeletonCss />
|
<SkeletonCss />
|
||||||
|
@ -28,6 +28,7 @@ export * from "./image";
|
|||||||
export * from "./skeleton";
|
export * from "./skeleton";
|
||||||
export * from "./tooltip";
|
export * from "./tooltip";
|
||||||
|
|
||||||
|
export * from "./utils/breakpoints";
|
||||||
export * from "./utils/nojs";
|
export * from "./utils/nojs";
|
||||||
|
|
||||||
import { Dimensions } from "react-native";
|
import { Dimensions } from "react-native";
|
||||||
|
62
front/packages/primitives/src/utils/breakpoints.ts
Normal file
62
front/packages/primitives/src/utils/breakpoints.ts
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* Kyoo - A portable and vast media library solution.
|
||||||
|
* Copyright (c) Kyoo.
|
||||||
|
*
|
||||||
|
* See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||||
|
*
|
||||||
|
* Kyoo is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* any later version.
|
||||||
|
*
|
||||||
|
* Kyoo is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useWindowDimensions } from "react-native";
|
||||||
|
import { AtLeastOne, Breakpoints as YoshikiBreakpoint } from "yoshiki/dist/type";
|
||||||
|
import { isBreakpoints } from "yoshiki/dist/utils";
|
||||||
|
import { breakpoints } from "yoshiki/native";
|
||||||
|
|
||||||
|
export type Breakpoint<T> = T | AtLeastOne<YoshikiBreakpoint<T>>;
|
||||||
|
|
||||||
|
// copied from yoshiki.
|
||||||
|
const useBreakpoint = () => {
|
||||||
|
const { width } = useWindowDimensions();
|
||||||
|
const idx = Object.values(breakpoints).findIndex((x) => width <= x);
|
||||||
|
if (idx === -1) return 0;
|
||||||
|
return idx - 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getBreakpointValue = <T>(value: Breakpoint<T>, breakpoint: number): T => {
|
||||||
|
if (!isBreakpoints(value)) return value;
|
||||||
|
const bpKeys = Object.keys(breakpoints) as Array<keyof YoshikiBreakpoint<T>>;
|
||||||
|
for (let i = breakpoint; i >= 0; i--) {
|
||||||
|
if (bpKeys[i] in value) {
|
||||||
|
const val = value[bpKeys[i]];
|
||||||
|
if (val) return val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// This should never be reached.
|
||||||
|
return undefined!;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useBreakpointValue = <T>(value: Breakpoint<T>): T => {
|
||||||
|
const breakpoint = useBreakpoint();
|
||||||
|
return getBreakpointValue(value, breakpoint);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useBreakpointMap = <T extends Record<string, unknown>>(
|
||||||
|
value: T,
|
||||||
|
): { [key in keyof T]: T[key] extends Breakpoint<infer V> ? V : T } => {
|
||||||
|
const breakpoint = useBreakpoint();
|
||||||
|
// @ts-ignore
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.entries(value).map(([key, val]) => [key, getBreakpointValue(val, breakpoint)]),
|
||||||
|
);
|
||||||
|
};
|
@ -21,7 +21,7 @@
|
|||||||
import { Link, Skeleton, Poster, ts, P, SubP } from "@kyoo/primitives";
|
import { Link, Skeleton, Poster, ts, P, SubP } from "@kyoo/primitives";
|
||||||
import { Platform } from "react-native";
|
import { Platform } from "react-native";
|
||||||
import { percent, px, Stylable, useYoshiki } from "yoshiki/native";
|
import { percent, px, Stylable, useYoshiki } from "yoshiki/native";
|
||||||
import { WithLoading } from "../fetch";
|
import { Layout, WithLoading } from "../fetch";
|
||||||
|
|
||||||
export const ItemGrid = ({
|
export const ItemGrid = ({
|
||||||
href,
|
href,
|
||||||
@ -85,4 +85,7 @@ export const ItemGrid = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
ItemGrid.height = px(250);
|
ItemGrid.layout = {
|
||||||
|
size: px(150),
|
||||||
|
numColumns: { xs: 3, md: 5, xl: 7 },
|
||||||
|
} satisfies Layout;
|
||||||
|
@ -31,6 +31,7 @@ import { DefaultLayout } from "../layout";
|
|||||||
import { WithLoading } from "../fetch";
|
import { WithLoading } from "../fetch";
|
||||||
import { InfiniteFetch } from "../fetch-infinite";
|
import { InfiniteFetch } from "../fetch-infinite";
|
||||||
import { ItemGrid } from "./grid";
|
import { ItemGrid } from "./grid";
|
||||||
|
import { ItemList } from "./list";
|
||||||
import { SortBy, SortOrd, Layout } from "./types";
|
import { SortBy, SortOrd, Layout } from "./types";
|
||||||
|
|
||||||
const itemMap = (item: WithLoading<LibraryItem>): WithLoading<ComponentProps<typeof ItemGrid>> => {
|
const itemMap = (item: WithLoading<LibraryItem>): WithLoading<ComponentProps<typeof ItemGrid>> => {
|
||||||
@ -71,6 +72,8 @@ export const BrowsePage: QueryPage<{ slug?: string }> = ({ slug }) => {
|
|||||||
const [sortOrd, setSortOrd] = useState(SortOrd.Asc);
|
const [sortOrd, setSortOrd] = useState(SortOrd.Asc);
|
||||||
const [layout, setLayout] = useState(Layout.Grid);
|
const [layout, setLayout] = useState(Layout.Grid);
|
||||||
|
|
||||||
|
const LayoutComponent = layout === Layout.Grid ? ItemGrid : ItemList;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* <BrowseSettings */}
|
{/* <BrowseSettings */}
|
||||||
@ -84,16 +87,9 @@ export const BrowsePage: QueryPage<{ slug?: string }> = ({ slug }) => {
|
|||||||
<InfiniteFetch
|
<InfiniteFetch
|
||||||
query={query(slug, sortKey, sortOrd)}
|
query={query(slug, sortKey, sortOrd)}
|
||||||
placeholderCount={15}
|
placeholderCount={15}
|
||||||
size={ItemGrid.height}
|
layout={LayoutComponent.layout}
|
||||||
numColumns={3}
|
|
||||||
/* sx={{ */
|
|
||||||
/* display: "flex", */
|
|
||||||
/* flexWrap: "wrap", */
|
|
||||||
/* alignItems: "flex-start", */
|
|
||||||
/* justifyContent: "center", */
|
|
||||||
/* }} */
|
|
||||||
>
|
>
|
||||||
{(item, key) => <ItemGrid key={key} {...itemMap(item)} />}
|
{(item, key) => <LayoutComponent key={key} {...itemMap(item)} />}
|
||||||
</InfiniteFetch>
|
</InfiniteFetch>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -19,30 +19,30 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Page, QueryIdentifier, useInfiniteFetch } from "@kyoo/models";
|
import { Page, QueryIdentifier, useInfiniteFetch } from "@kyoo/models";
|
||||||
|
import { useBreakpointMap } from "@kyoo/primitives";
|
||||||
import { FlashList } from "@shopify/flash-list";
|
import { FlashList } from "@shopify/flash-list";
|
||||||
import { ReactElement } from "react";
|
import { ReactElement } from "react";
|
||||||
import { ErrorView, WithLoading } from "./fetch";
|
import { ErrorView, Layout, WithLoading } from "./fetch";
|
||||||
|
|
||||||
export const InfiniteFetch = <Data,>({
|
export const InfiniteFetch = <Data,>({
|
||||||
query,
|
query,
|
||||||
placeholderCount = 15,
|
placeholderCount = 15,
|
||||||
children,
|
children,
|
||||||
size,
|
layout,
|
||||||
numColumns,
|
|
||||||
...props
|
...props
|
||||||
}: {
|
}: {
|
||||||
query: QueryIdentifier<Data>;
|
query: QueryIdentifier<Data>;
|
||||||
placeholderCount?: number;
|
placeholderCount?: number;
|
||||||
numColumns: number;
|
layout: Layout;
|
||||||
children: (
|
children: (
|
||||||
item: Data extends Page<infer Item> ? WithLoading<Item> : WithLoading<Data>,
|
item: Data extends Page<infer Item> ? WithLoading<Item> : WithLoading<Data>,
|
||||||
key: string | undefined,
|
key: string | undefined,
|
||||||
i: number,
|
i: number,
|
||||||
) => ReactElement | null;
|
) => ReactElement | null;
|
||||||
size: number;
|
|
||||||
}): JSX.Element | null => {
|
}): JSX.Element | null => {
|
||||||
if (!query.infinite) console.warn("A non infinite query was passed to an InfiniteFetch.");
|
if (!query.infinite) console.warn("A non infinite query was passed to an InfiniteFetch.");
|
||||||
|
|
||||||
|
const { numColumns, size } = useBreakpointMap(layout);
|
||||||
const { items, error, fetchNextPage, hasNextPage, refetch, isRefetching } =
|
const { items, error, fetchNextPage, hasNextPage, refetch, isRefetching } =
|
||||||
useInfiniteFetch(query);
|
useInfiniteFetch(query);
|
||||||
|
|
||||||
|
@ -19,19 +19,22 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Page, QueryIdentifier, useInfiniteFetch } from "@kyoo/models";
|
import { Page, QueryIdentifier, useInfiniteFetch } from "@kyoo/models";
|
||||||
|
import { useBreakpointValue } from "@kyoo/primitives";
|
||||||
import { ReactElement } from "react";
|
import { ReactElement } from "react";
|
||||||
import InfiniteScroll from "react-infinite-scroll-component";
|
import InfiniteScroll from "react-infinite-scroll-component";
|
||||||
import { useYoshiki } from "yoshiki";
|
import { useYoshiki } from "yoshiki";
|
||||||
import { ErrorView, WithLoading } from "./fetch";
|
import { ErrorView, Layout, WithLoading } from "./fetch";
|
||||||
|
|
||||||
export const InfiniteFetch = <Data,>({
|
export const InfiniteFetch = <Data,>({
|
||||||
query,
|
query,
|
||||||
placeholderCount = 15,
|
placeholderCount = 15,
|
||||||
children,
|
children,
|
||||||
|
layout,
|
||||||
...props
|
...props
|
||||||
}: {
|
}: {
|
||||||
query: QueryIdentifier<Data>;
|
query: QueryIdentifier<Data>;
|
||||||
placeholderCount?: number;
|
placeholderCount?: number;
|
||||||
|
layout: Layout;
|
||||||
children: (
|
children: (
|
||||||
item: Data extends Page<infer Item> ? WithLoading<Item> : WithLoading<Data>,
|
item: Data extends Page<infer Item> ? WithLoading<Item> : WithLoading<Data>,
|
||||||
key: string | undefined,
|
key: string | undefined,
|
||||||
@ -41,23 +44,33 @@ export const InfiniteFetch = <Data,>({
|
|||||||
if (!query.infinite) console.warn("A non infinite query was passed to an InfiniteFetch.");
|
if (!query.infinite) console.warn("A non infinite query was passed to an InfiniteFetch.");
|
||||||
|
|
||||||
const { items, error, fetchNextPage, hasNextPage } = useInfiniteFetch(query);
|
const { items, error, fetchNextPage, hasNextPage } = useInfiniteFetch(query);
|
||||||
|
const { numColumns } = useBreakpointValue(layout);
|
||||||
const { css } = useYoshiki();
|
const { css } = useYoshiki();
|
||||||
|
|
||||||
if (error) return <ErrorView error={error} />;
|
if (error) return <ErrorView error={error} />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<InfiniteScroll
|
<InfiniteScroll
|
||||||
|
scrollableTarget="main" // Default to the main element for the scroll.
|
||||||
dataLength={items?.length ?? 0}
|
dataLength={items?.length ?? 0}
|
||||||
next={fetchNextPage}
|
next={fetchNextPage}
|
||||||
hasMore={hasNextPage!}
|
hasMore={hasNextPage!}
|
||||||
loader={[...Array(12)].map((_, i) => children({ isLoading: true } as any, i.toString(), i))}
|
loader={[...Array(12)].map((_, i) => children({ isLoading: true } as any, i.toString(), i))}
|
||||||
{...css(
|
{...css(
|
||||||
{
|
[
|
||||||
display: "flex",
|
{
|
||||||
flexWrap: "wrap",
|
display: "flex",
|
||||||
alignItems: "flex-start",
|
alignItems: "flex-start",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
},
|
},
|
||||||
|
numColumns === 1 && {
|
||||||
|
flexDirection: "column",
|
||||||
|
},
|
||||||
|
numColumns !== 1 && {
|
||||||
|
flexWrap: "wrap",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
props,
|
props,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
@ -19,10 +19,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Page, QueryIdentifier, useFetch, KyooErrors } from "@kyoo/models";
|
import { Page, QueryIdentifier, useFetch, KyooErrors } from "@kyoo/models";
|
||||||
import { P } from "@kyoo/primitives";
|
import { Breakpoint, P } from "@kyoo/primitives";
|
||||||
import { View } from "react-native";
|
import { View } from "react-native";
|
||||||
import { useYoshiki } from "yoshiki/native";
|
import { useYoshiki } from "yoshiki/native";
|
||||||
|
|
||||||
|
export type Layout = { numColumns: Breakpoint<number>; size: Breakpoint<number> };
|
||||||
|
|
||||||
export type WithLoading<Item> =
|
export type WithLoading<Item> =
|
||||||
| (Item & { isLoading: false })
|
| (Item & { isLoading: false })
|
||||||
| (Partial<Item> & { isLoading: true });
|
| (Partial<Item> & { isLoading: true });
|
||||||
|
@ -28,7 +28,9 @@ export const DefaultLayout = (page: ReactElement) => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Navbar />
|
<Navbar />
|
||||||
<main {...css({ flex: 1, display: "flex" })}>{page}</main>
|
<main id="main" {...css({ display: "flex", flexGrow: 1, flexShrink: 1, overflow: "auto" })}>
|
||||||
|
{page}
|
||||||
|
</main>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user