Infinite piece query

This commit is contained in:
2025-10-16 01:32:48 +02:00
parent 82c83c4554
commit a6ad34c721

View File

@@ -14,32 +14,27 @@ import clsx from "clsx";
import { PieceId, Updater } from "common";
import * as Body from "common/Body";
import { getMediaTypeForFilename } from "common/MediaType";
import { Array, Cause, Effect, Fiber, Iterable, Match, Option, Order, pipe, Predicate, Scope, SortedMap } from "effect";
import { Import, Loader2, Plus } from "lucide-react";
import { DragEventHandler, FormEventHandler, useId, useRef, useState } from "react";
import { Array, Effect, Fiber, Iterable, Match, Option, Order, pipe, Predicate, Scope, SortedMap } from "effect";
import { Import, Loader2, Plus, RefreshCwIcon } from "lucide-react";
import { DragEventHandler, FormEventHandler, RefObject, useCallback, useId, useImperativeHandle, useRef, useState } from "react";
import { Link, useNavigate } from "react-router-dom";
const LIMIT = 100;
export function Pieces() {
const name = useStore(state => state.pieceQueryName);
const author = useStore(state => state.pieceQueryAuthor);
const [importDialogOpen, setImportDialogOpen] = useState(false);
const [refresh, setRefresh] = useState(Effect.void);
const debounce = useRef(Effect.void);
const breakpoint = useBreakpoint();
const columns = breakpoint ? 4 : 1;
const { isLoading, error, data: pieceIds, refresh } = useLoading(Effect.gen(function* () {
yield* debounce.current;
const data = yield* client.queryPieces({
name: name !== "" ? Option.some(name) : Option.none(),
author: author !== "" ? Option.some(author) : Option.none(),
offset: 0,
limit: 100,
});
return data;
}), [name, author]);
const queryRef = useCallback((ref: Query.Ref | null) => {
setRefresh(ref !== null ? ref.refresh : Effect.void);
}, []);
return (
<div className="p-4 overflow-y-auto flex flex-col gap-4">
@@ -93,30 +88,98 @@ export function Pieces() {
</TableHeader>
)}
<TableBody>
{isLoading ? (
<TableRow>
<TableCell colSpan={columns} >
<div className="flex items-center justify-center gap-2">
<Loader2 className="animate-spin" />
Ładowanie
</div>
</TableCell>
</TableRow>
) : error !== null ? (
<TableRow>
<TableCell colSpan={columns} className="text-center">
{Cause.isUnknownException(error) ? "Wystąpił nieznany błąd" : `Wystąpił błąd: ${JSON.stringify(error)}`}
</TableCell>
</TableRow>
) : (
pieceIds.map((pieceId) => <PieceRow key={pieceId} pieceId={pieceId} />)
)}
<Query
ref={queryRef}
offset={0}
debounce={debounce}
/>
</TableBody>
</Table>
</div>
);
}
namespace Query {
export interface Props {
readonly offset: number;
readonly debounce?: RefObject<Effect.Effect<void>> | undefined;
readonly ref?: React.Ref<Ref> | undefined;
}
export interface Ref {
readonly refresh: Effect.Effect<void>;
}
}
function Query({
offset,
debounce,
ref,
}: Query.Props) {
const name = useStore(state => state.pieceQueryName);
const author = useStore(state => state.pieceQueryAuthor);
const breakpoint = useBreakpoint();
const columns = breakpoint ? 4 : 1;
const [continuationOpen, setContinuationOpen] = useState(false);
const { isLoading, error, data: pieceIds, refresh } = useLoading(Effect.gen(function* () {
setContinuationOpen(false);
if (debounce !== undefined) {
yield* debounce.current;
}
const data = yield* client.queryPieces({
name: name !== "" ? Option.some(name) : Option.none(),
author: author !== "" ? Option.some(author) : Option.none(),
offset,
limit: LIMIT,
});
return data;
}), [name, author, offset]);
useImperativeHandle(ref, () => Object.freeze<Query.Ref>({
refresh,
}), [refresh]);
return isLoading ? (
<TableRow>
<TableCell colSpan={columns} >
<div className="flex items-center justify-center gap-2">
<Loader2 className="animate-spin" />
Ładowanie
</div>
</TableCell>
</TableRow>
) : error !== null ? (
<TableRow>
<TableCell colSpan={columns} className="text-center">
Wystąpił błąd: {Match.value(error).pipe(
Match.tag("FetchError", () => "Nie można połączyć się z serwerem"),
Match.tag("Unauthenticated", () => "Zaloguj się, aby kontynuować"),
Match.tag("Unauthorized", () => "Nie posiadasz uprawnień"),
Match.exhaustive,
)}
</TableCell>
</TableRow>
) : (<>
{pieceIds.map((pieceId) => <PieceRow key={pieceId} pieceId={pieceId} />)}
{continuationOpen ? (
<Query offset={offset + LIMIT} />
) : pieceIds.length >= LIMIT ? (
<TableRow>
<TableCell colSpan={columns} className="text-center">
<Button variant="outline" onClick={() => setContinuationOpen(true)}>
<RefreshCwIcon/>Załaduj więcej
</Button>
</TableCell>
</TableRow>
) : null}
</>);
}
namespace PieceRow {
export interface Props {
readonly pieceId: PieceId;