mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-24 03:59:50 +02:00
feat: incremtal loading
This commit is contained in:
1 parent
11e87bd519
commit
de6137d1a5
5 files changed
+98
-10
No files matched your search
@@ -1,23 +1,26 @@
|
|||||||
import { useGameStrings } from "../../hooks/useGameStrings";
|
import { useGameStrings } from "../../hooks/useGameStrings";
|
||||||
import type { Avatar } from "../../protos/avatar";
|
import type { Avatar } from "../../protos/avatar";
|
||||||
|
import type { SkeletonProps } from "../../types/SkeletonProps";
|
||||||
import { Text } from "../Text";
|
import { Text } from "../Text";
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
|
|
||||||
interface Props {
|
export function AvatarCard(props: SkeletonProps<Avatar>) {
|
||||||
avatar: Avatar;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function AvatarCard({ avatar }: Props) {
|
|
||||||
const profileAvatarEntityStrings = useGameStrings("ProfileAvatarEntity");
|
const profileAvatarEntityStrings = useGameStrings("ProfileAvatarEntity");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="avatar-card">
|
<div className="avatar-card">
|
||||||
<div
|
<div
|
||||||
className="avatar-image"
|
className="avatar-image"
|
||||||
style={{ backgroundImage: `url(/api/avatars/${avatar.name}.webp)` }}
|
style={{
|
||||||
|
backgroundImage: props.skeleton
|
||||||
|
? undefined
|
||||||
|
: `url(/api/avatars/${props.name}.webp)`,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<Text align="center" className="avatar-label">
|
<Text align="center" className="avatar-label">
|
||||||
{profileAvatarEntityStrings[avatar.stuff_ui!.display_name]}
|
{props.skeleton
|
||||||
|
? "LOADING"
|
||||||
|
: profileAvatarEntityStrings[props.stuff_ui!.display_name]}
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
.incremental-loader {
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
import { times } from "lodash-es";
|
||||||
|
import { useEffect, useRef, useState, type ReactNode } from "react";
|
||||||
|
import type { SkeletonProps } from "../../types/SkeletonProps";
|
||||||
|
import "./index.css";
|
||||||
|
|
||||||
|
export interface IncrementalLoaderProps<Props> {
|
||||||
|
initial: number;
|
||||||
|
skeleton: number;
|
||||||
|
rate?: number;
|
||||||
|
|
||||||
|
data: Props[];
|
||||||
|
Component: (props: SkeletonProps<Props>) => ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function IncrementalLoader<Props>({
|
||||||
|
initial,
|
||||||
|
rate = 2,
|
||||||
|
data,
|
||||||
|
skeleton,
|
||||||
|
Component,
|
||||||
|
}: IncrementalLoaderProps<Props>) {
|
||||||
|
const [count, setCount] = useState(initial);
|
||||||
|
console.log(count);
|
||||||
|
const sliced = data.slice(0, count);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{sliced.map((item, index) => (
|
||||||
|
<Component key={index} {...item} />
|
||||||
|
))}
|
||||||
|
|
||||||
|
{times(Math.min(skeleton, data.length - count), (index) => (
|
||||||
|
<Item key={index} rate={rate} setCount={setCount}>
|
||||||
|
<Component skeleton />
|
||||||
|
</Item>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ItemProps {
|
||||||
|
children: ReactNode;
|
||||||
|
rate: number;
|
||||||
|
setCount: (setter: (state: number) => number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Item({ setCount, rate, children }: ItemProps) {
|
||||||
|
const wrapper = useRef<HTMLDivElement>(null);
|
||||||
|
const observer = useRef<IntersectionObserver | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
function dispose() {
|
||||||
|
observer.current?.disconnect();
|
||||||
|
observer.current = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!wrapper.current) return;
|
||||||
|
|
||||||
|
if (observer.current === null) {
|
||||||
|
observer.current = new IntersectionObserver(([entry]) => {
|
||||||
|
if (!entry.isIntersecting) return;
|
||||||
|
setCount((state) => state + rate);
|
||||||
|
});
|
||||||
|
|
||||||
|
observer.current.observe(wrapper.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
return dispose;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={wrapper} className="incremental-loader">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { AvatarCard } from "../../../components/AvatarCard";
|
import { AvatarCard } from "../../../components/AvatarCard";
|
||||||
|
import { IncrementalLoader } from "../../../components/IncrementalLoader";
|
||||||
import { api } from "../../../core/api/dynamic";
|
import { api } from "../../../core/api/dynamic";
|
||||||
import { useAwait } from "../../../hooks/useAwait";
|
import { useAwait } from "../../../hooks/useAwait";
|
||||||
import { useGameStrings } from "../../../hooks/useGameStrings";
|
import { useGameStrings } from "../../../hooks/useGameStrings";
|
||||||
@@ -41,9 +42,12 @@ function Content({ skeleton }: ContentProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="avatars">
|
<div className="avatars">
|
||||||
{ordered.slice(0, 12).map((avatar) => (
|
<IncrementalLoader
|
||||||
<AvatarCard key={avatar.name} avatar={avatar} />
|
initial={20}
|
||||||
))}
|
skeleton={6}
|
||||||
|
data={ordered}
|
||||||
|
Component={AvatarCard}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export type SkeletonProps<Props> =
|
||||||
|
| { skeleton: true }
|
||||||
|
| (Props & { skeleton?: false });
|
||||||
Reference in new issue
Block a user