Infinite piece query
This commit is contained in:
@@ -14,32 +14,27 @@ import clsx from "clsx";
|
|||||||
import { PieceId, Updater } from "common";
|
import { PieceId, Updater } from "common";
|
||||||
import * as Body from "common/Body";
|
import * as Body from "common/Body";
|
||||||
import { getMediaTypeForFilename } from "common/MediaType";
|
import { getMediaTypeForFilename } from "common/MediaType";
|
||||||
import { Array, Cause, Effect, Fiber, Iterable, Match, Option, Order, pipe, Predicate, Scope, SortedMap } from "effect";
|
import { Array, Effect, Fiber, Iterable, Match, Option, Order, pipe, Predicate, Scope, SortedMap } from "effect";
|
||||||
import { Import, Loader2, Plus } from "lucide-react";
|
import { Import, Loader2, Plus, RefreshCwIcon } from "lucide-react";
|
||||||
import { DragEventHandler, FormEventHandler, useId, useRef, useState } from "react";
|
import { DragEventHandler, FormEventHandler, RefObject, useCallback, useId, useImperativeHandle, useRef, useState } from "react";
|
||||||
import { Link, useNavigate } from "react-router-dom";
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
|
const LIMIT = 100;
|
||||||
|
|
||||||
export function Pieces() {
|
export function Pieces() {
|
||||||
|
|
||||||
const name = useStore(state => state.pieceQueryName);
|
const name = useStore(state => state.pieceQueryName);
|
||||||
const author = useStore(state => state.pieceQueryAuthor);
|
const author = useStore(state => state.pieceQueryAuthor);
|
||||||
|
|
||||||
const [importDialogOpen, setImportDialogOpen] = useState(false);
|
const [importDialogOpen, setImportDialogOpen] = useState(false);
|
||||||
|
const [refresh, setRefresh] = useState(Effect.void);
|
||||||
|
|
||||||
const debounce = useRef(Effect.void);
|
const debounce = useRef(Effect.void);
|
||||||
const breakpoint = useBreakpoint();
|
const breakpoint = useBreakpoint();
|
||||||
const columns = breakpoint ? 4 : 1;
|
|
||||||
|
|
||||||
const { isLoading, error, data: pieceIds, refresh } = useLoading(Effect.gen(function* () {
|
const queryRef = useCallback((ref: Query.Ref | null) => {
|
||||||
yield* debounce.current;
|
setRefresh(ref !== null ? ref.refresh : Effect.void);
|
||||||
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]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-4 overflow-y-auto flex flex-col gap-4">
|
<div className="p-4 overflow-y-auto flex flex-col gap-4">
|
||||||
@@ -93,30 +88,98 @@ export function Pieces() {
|
|||||||
</TableHeader>
|
</TableHeader>
|
||||||
)}
|
)}
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{isLoading ? (
|
<Query
|
||||||
<TableRow>
|
ref={queryRef}
|
||||||
<TableCell colSpan={columns} >
|
offset={0}
|
||||||
<div className="flex items-center justify-center gap-2">
|
debounce={debounce}
|
||||||
<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} />)
|
|
||||||
)}
|
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
</div>
|
</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 {
|
namespace PieceRow {
|
||||||
export interface Props {
|
export interface Props {
|
||||||
readonly pieceId: PieceId;
|
readonly pieceId: PieceId;
|
||||||
|
|||||||
Reference in New Issue
Block a user