Cache reimplementation

This commit is contained in:
2025-10-10 13:09:11 +02:00
parent 6717e6b0de
commit a199b104ad
8 changed files with 356 additions and 155 deletions

View File

@@ -1,10 +1,11 @@
import { pieceCache } from "@/cache";
import { denormalizePiece, pieceCache } from "@/cache";
import { client } from "@/client";
import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { useCache } from "@/hooks/useCache";
import { useLoading } from "@/hooks/useLoading";
import { Updater } from "@/hooks/useStore";
import { authors, created, DEBOUNCE, modified, SAVE_DELAY } from "@/snippets";
@@ -12,8 +13,8 @@ import clsx from "clsx";
import { PieceId } from "common";
import * as Body from "common/Body";
import { getMediaTypeForFilename } from "common/MediaType";
import { Array, Cause, Effect, Fiber, Iterable, Match, Option, Order, pipe, Scope, SortedMap } from "effect";
import { Import, Loader2, Plus, UploadCloud } from "lucide-react";
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 { Link, useNavigate } from "react-router-dom";
@@ -119,7 +120,7 @@ namespace PieceRow {
function PieceRow(props: PieceRow.Props) {
const { isLoading, error, data: piece } = useLoading(Effect.uninterruptible(pieceCache.get(props.pieceId)), [props.pieceId]);
const { isLoading, error, data: piece } = useCache(pieceCache, props.pieceId);
if (isLoading) {
return (
@@ -188,12 +189,16 @@ function AddPieceDialogContent() {
setIsLoading(true);
const { pieceId } = yield* client.createPiece({
name,
composer: composer.length > 0 ? Option.some(composer) : Option.none(),
lyricist: lyricist.length > 0 ? Option.some(lyricist) : Option.none(),
arranger: arranger.length > 0 ? Option.some(arranger) : Option.none(),
});
const { pieceId } = yield* pipe(
client.createPiece({
name,
composer: composer.length > 0 ? Option.some(composer) : Option.none(),
lyricist: lyricist.length > 0 ? Option.some(lyricist) : Option.none(),
arranger: arranger.length > 0 ? Option.some(arranger) : Option.none(),
}),
Effect.flatMap(denormalizePiece),
Effect.tap((piece) => Effect.sync(() => pieceCache.setFulfilledSucceed(piece.pieceId, piece))),
);
navigate(pieceId);
}));
@@ -262,7 +267,7 @@ function ImportPiecesDialogContent(props: ImportPiecesDialogContent.Props) {
const [isLoading, setIsLoading] = useState(false);
const [attachments, setAttachments] = useState<SortedMap.SortedMap<string, readonly File[]>>(() => SortedMap.empty(Order.string));
const [files, setFiles] = useState<SortedMap.SortedMap<string, readonly File[]>>(() => SortedMap.empty(Order.string));
const onDragOver: DragEventHandler<HTMLElement> = (e) => {
e.preventDefault();
@@ -272,7 +277,7 @@ function ImportPiecesDialogContent(props: ImportPiecesDialogContent.Props) {
const onDrop: DragEventHandler<HTMLElement> = (e) => Effect.gen(function* () {
e.preventDefault();
setAttachments((value) => {
setFiles((value) => {
for (const file of e.dataTransfer.files) {
const mediaType = getMediaTypeForFilename(file.name);
if (mediaType === undefined) {
@@ -314,18 +319,20 @@ function ImportPiecesDialogContent(props: ImportPiecesDialogContent.Props) {
setIsLoading(true);
yield* pipe(
attachments,
Iterable.map(([name, attachments]) => Effect.gen(function* () {
files,
Iterable.map(([name, files]) => Effect.gen(function* () {
const { pieceId } = yield* client.createPiece({
name,
composer: Option.none(),
arranger: Option.none(),
lyricist: Option.none(),
});
const piece = yield* pipe(
client.createPiece({
name,
composer: Option.none(),
arranger: Option.none(),
lyricist: Option.none(),
}),
);
yield* pipe(
attachments,
const attachments = yield* pipe(
files,
Array.map((file) => Effect.gen(function* () {
const mediaType = getMediaTypeForFilename(file.name);
if (mediaType === undefined) {
@@ -335,14 +342,23 @@ function ImportPiecesDialogContent(props: ImportPiecesDialogContent.Props) {
// NOTE Apparently, file.bytes is not a thing in this context
const data = new Uint8Array(yield* Body.arrayBuffer(file));
yield* client.createAttachment({
pieceId,
const attachment = yield* client.createAttachment({
pieceId: piece.pieceId,
data,
filename: file.name,
mediaType,
});
return attachment;
})),
Effect.allWith({ concurrency: "unbounded" }),
Effect.map(Array.filter(Predicate.isNotUndefined)),
);
yield* pipe(
{ ...piece, attachments },
denormalizePiece,
Effect.tap((piece) => Effect.sync(() => pieceCache.setFulfilledSucceed(piece.pieceId, piece))),
);
})),
Effect.allWith({ concurrency: "unbounded" }),
@@ -359,7 +375,7 @@ function ImportPiecesDialogContent(props: ImportPiecesDialogContent.Props) {
<DialogTitle>Importuj utwory</DialogTitle>
</DialogHeader>
<div className="flex flex-col gap-4 py-4">
{SortedMap.size(attachments) > 0 && (
{SortedMap.size(files) > 0 && (
<div className="max-h-[50vh] overflow-y-auto">
<Table>
<TableHeader>
@@ -370,7 +386,7 @@ function ImportPiecesDialogContent(props: ImportPiecesDialogContent.Props) {
</TableHeader>
<TableBody>
{pipe(
attachments,
files,
SortedMap.entries,
Iterable.map(([name, files]) => (
<TableRow key={name}>