mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-07-09 03:04:20 -04:00
Split loaders for user list
This commit is contained in:
parent
2a1b805a7f
commit
f3fb96504a
@ -24,6 +24,7 @@ import { Image, type ImageProps, View, type ViewStyle } from "react-native";
|
|||||||
import { type Stylable, px, useYoshiki } from "yoshiki/native";
|
import { type Stylable, px, useYoshiki } from "yoshiki/native";
|
||||||
import { Icon } from "./icons";
|
import { Icon } from "./icons";
|
||||||
import { P } from "./text";
|
import { P } from "./text";
|
||||||
|
import { Skeleton } from "./skeleton";
|
||||||
|
|
||||||
const stringToColor = (string: string) => {
|
const stringToColor = (string: string) => {
|
||||||
let hash = 0;
|
let hash = 0;
|
||||||
@ -40,7 +41,7 @@ const stringToColor = (string: string) => {
|
|||||||
return color;
|
return color;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Avatar = forwardRef<
|
const AvatarC = forwardRef<
|
||||||
View,
|
View,
|
||||||
{
|
{
|
||||||
src?: string;
|
src?: string;
|
||||||
@ -48,12 +49,11 @@ export const Avatar = forwardRef<
|
|||||||
size?: number;
|
size?: number;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
color?: string;
|
color?: string;
|
||||||
isLoading?: boolean;
|
|
||||||
fill?: boolean;
|
fill?: boolean;
|
||||||
as?: ComponentType<{ style?: ViewStyle } & RefAttributes<View>>;
|
as?: ComponentType<{ style?: ViewStyle } & RefAttributes<View>>;
|
||||||
} & Stylable
|
} & Stylable
|
||||||
>(function Avatar(
|
>(function AvatarI(
|
||||||
{ src, alt, size = px(24), color, placeholder, isLoading = false, fill = false, as, ...props },
|
{ src, alt, size = px(24), color, placeholder, fill = false, as, ...props },
|
||||||
ref,
|
ref,
|
||||||
) {
|
) {
|
||||||
const { css, theme } = useYoshiki();
|
const { css, theme } = useYoshiki();
|
||||||
@ -106,3 +106,22 @@ export const Avatar = forwardRef<
|
|||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const AvatarLoader = ({ size = px(24), ...props }: { size?: number }) => {
|
||||||
|
const { css } = useYoshiki();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Skeleton
|
||||||
|
variant="round"
|
||||||
|
{...css(
|
||||||
|
{
|
||||||
|
height: size,
|
||||||
|
width: size,
|
||||||
|
},
|
||||||
|
props,
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Avatar = { ...AvatarC, Loader: AvatarLoader };
|
||||||
|
@ -45,7 +45,7 @@ type IconProps = {
|
|||||||
export const Icon = ({ icon: Icon, color, size = 24, ...props }: IconProps) => {
|
export const Icon = ({ icon: Icon, color, size = 24, ...props }: IconProps) => {
|
||||||
const { css, theme } = useYoshiki();
|
const { css, theme } = useYoshiki();
|
||||||
const computed = css(
|
const computed = css(
|
||||||
{ width: size, height: size, fill: color ?? theme.contrast } as any,
|
{ width: size, height: size, fill: color ?? theme.contrast, flexShrink: 0 } as any,
|
||||||
props,
|
props,
|
||||||
) as any;
|
) as any;
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ import { Alert, Avatar, Icon, IconButton, Menu, P, Skeleton, tooltip, ts } from
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { View } from "react-native";
|
import { View } from "react-native";
|
||||||
import { px, useYoshiki } from "yoshiki/native";
|
import { px, useYoshiki } from "yoshiki/native";
|
||||||
import type { Layout, WithLoading } from "../fetch";
|
import type { Layout } from "../fetch";
|
||||||
import { InfiniteFetch } from "../fetch-infinite";
|
import { InfiniteFetch } from "../fetch-infinite";
|
||||||
import { SettingsContainer } from "../settings/base";
|
import { SettingsContainer } from "../settings/base";
|
||||||
|
|
||||||
@ -36,20 +36,19 @@ import Verifed from "@material-symbols/svg-400/rounded/verified_user.svg";
|
|||||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
|
|
||||||
export const UserGrid = ({
|
export const UserGrid = ({
|
||||||
isLoading,
|
|
||||||
id,
|
id,
|
||||||
username,
|
username,
|
||||||
avatar,
|
avatar,
|
||||||
isAdmin,
|
isAdmin,
|
||||||
isVerified,
|
isVerified,
|
||||||
...props
|
...props
|
||||||
}: WithLoading<{
|
}: {
|
||||||
id: string;
|
id: string;
|
||||||
username: string;
|
username: string;
|
||||||
avatar: string;
|
avatar: string;
|
||||||
isAdmin: boolean;
|
isAdmin: boolean;
|
||||||
isVerified: boolean;
|
isVerified: boolean;
|
||||||
}>) => {
|
}) => {
|
||||||
const { css } = useYoshiki();
|
const { css } = useYoshiki();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
@ -66,11 +65,10 @@ export const UserGrid = ({
|
|||||||
return (
|
return (
|
||||||
<View {...css({ alignItems: "center" }, props)}>
|
<View {...css({ alignItems: "center" }, props)}>
|
||||||
<Avatar src={avatar} alt={username} placeholder={username} size={UserGrid.layout.size} fill />
|
<Avatar src={avatar} alt={username} placeholder={username} size={UserGrid.layout.size} fill />
|
||||||
<View {...css({ flexDirection: "row" })}>
|
<View {...css({ flexDirection: "row", alignItems: "center" })}>
|
||||||
<Icon
|
<Icon
|
||||||
icon={!isVerified ? Unverifed : isAdmin ? Admin : UserI}
|
icon={!isVerified ? Unverifed : isAdmin ? Admin : UserI}
|
||||||
{...css({
|
{...css({
|
||||||
alignSelf: "center",
|
|
||||||
m: ts(1),
|
m: ts(1),
|
||||||
})}
|
})}
|
||||||
{...tooltip(
|
{...tooltip(
|
||||||
@ -83,9 +81,7 @@ export const UserGrid = ({
|
|||||||
),
|
),
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<Skeleton>
|
<P>{username}</P>
|
||||||
<P>{username}</P>
|
|
||||||
</Skeleton>
|
|
||||||
<Menu Trigger={IconButton} icon={MoreVert} {...tooltip(t("misc.more"))}>
|
<Menu Trigger={IconButton} icon={MoreVert} {...tooltip(t("misc.more"))}>
|
||||||
{!isVerified && (
|
{!isVerified && (
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
@ -159,6 +155,21 @@ export const UserGrid = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
UserGrid.Loader = (props: object) => {
|
||||||
|
const { css } = useYoshiki();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View {...css({ alignItems: "center" }, props)}>
|
||||||
|
<Avatar.Loader size={UserGrid.layout.size} />
|
||||||
|
<View {...css({ flexDirection: "row", alignItems: "center", flexShrink: 1, flexGrow: 1 })}>
|
||||||
|
<Icon icon={UserI} {...css({ m: ts(1) })} />
|
||||||
|
<Skeleton {...css({ flexGrow: 1, width: ts(8) })} />
|
||||||
|
<IconButton icon={MoreVert} disabled />
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
UserGrid.layout = {
|
UserGrid.layout = {
|
||||||
size: px(150),
|
size: px(150),
|
||||||
numColumns: { xs: 2, sm: 3, md: 5, lg: 6, xl: 7 },
|
numColumns: { xs: 2, sm: 3, md: 5, lg: 6, xl: 7 },
|
||||||
@ -171,18 +182,20 @@ export const UserList = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingsContainer title={t("admin.users.label")}>
|
<SettingsContainer title={t("admin.users.label")}>
|
||||||
<InfiniteFetch query={UserList.query()} layout={UserGrid.layout}>
|
<InfiniteFetch
|
||||||
{(user) => (
|
query={UserList.query()}
|
||||||
|
layout={UserGrid.layout}
|
||||||
|
Render={({ item }) => (
|
||||||
<UserGrid
|
<UserGrid
|
||||||
isLoading={user.isLoading as any}
|
id={item.id}
|
||||||
id={user.id}
|
username={item.username}
|
||||||
username={user.username}
|
avatar={item.logo}
|
||||||
avatar={user.logo}
|
isAdmin={item.isAdmin}
|
||||||
isAdmin={user.isAdmin}
|
isVerified={item.isVerified}
|
||||||
isVerified={user.isVerified}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</InfiniteFetch>
|
Loader={UserGrid.Loader}
|
||||||
|
/>
|
||||||
</SettingsContainer>
|
</SettingsContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -155,30 +155,29 @@ export const CollectionPage: QueryPage<{ slug: string }> = ({ slug }) => {
|
|||||||
Header={CollectionHeader}
|
Header={CollectionHeader}
|
||||||
headerProps={{ slug }}
|
headerProps={{ slug }}
|
||||||
contentContainerStyle={{ padding: 0, paddingHorizontal: 0, ...pageStyle }}
|
contentContainerStyle={{ padding: 0, paddingHorizontal: 0, ...pageStyle }}
|
||||||
>
|
Render={({ item }) => (
|
||||||
{(x) => (
|
|
||||||
<ItemDetails
|
<ItemDetails
|
||||||
isLoading={x.isLoading as any}
|
slug={item.slug}
|
||||||
slug={x.slug}
|
type={item.kind}
|
||||||
type={x.kind}
|
name={item.name}
|
||||||
name={x.name}
|
tagline={"tagline" in item ? item.tagline : null}
|
||||||
tagline={"tagline" in x ? x.tagline : null}
|
overview={item.overview}
|
||||||
overview={x.overview}
|
poster={item.poster}
|
||||||
poster={x.poster}
|
subtitle={item.kind !== "collection" ? getDisplayDate(item) : null}
|
||||||
subtitle={x.kind !== "collection" && !x.isLoading ? getDisplayDate(x) : undefined}
|
genres={"genres" in item ? item.genres : null}
|
||||||
genres={"genres" in x ? x.genres : null}
|
href={item.href}
|
||||||
href={x.href}
|
playHref={item.kind !== "collection" ? item.playHref : null}
|
||||||
playHref={x.kind !== "collection" && !x.isLoading ? x.playHref : undefined}
|
watchStatus={(item.kind !== "collection" && item.watchStatus?.status) || null}
|
||||||
watchStatus={
|
|
||||||
!x.isLoading && x.kind !== "collection" ? x.watchStatus?.status ?? null : null
|
|
||||||
}
|
|
||||||
unseenEpisodesCount={
|
unseenEpisodesCount={
|
||||||
x.kind === "show" ? x.watchStatus?.unseenEpisodesCount ?? x.episodesCount! : null
|
item.kind === "show"
|
||||||
|
? item.watchStatus?.unseenEpisodesCount ?? item.episodesCount!
|
||||||
|
: null
|
||||||
}
|
}
|
||||||
{...css({ marginX: ItemGrid.layout.gap })}
|
{...css({ marginX: ItemGrid.layout.gap })}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</InfiniteFetch>
|
Loader={ItemDetails.Loader}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -77,9 +77,9 @@ export const SearchPage: QueryPage<{ q?: string }> = ({ q }) => {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
contentContainerStyle={pageStyle}
|
contentContainerStyle={pageStyle}
|
||||||
>
|
Render={({ item }) => <LayoutComponent {...itemMap(item)} />}
|
||||||
{(item) => <LayoutComponent {...itemMap(item)} />}
|
Loader={LayoutComponent.Loader}
|
||||||
</InfiniteFetch>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user