Files
chitai/frontend/src/lib/state/bookshelf.svelte.ts
T
patrick 3a29294f96 chore: clear the mechanical lint and type findings
Dead imports and locals removed, each blocks keyed, `any` narrowed to unknown.
Two context setters kept their calls and lost only the unused binding; the
settings redirect no longer awaits a parent whose data it discards. A leading
underscore now marks a binding that only holds a position.
2026-08-17 17:55:21 -04:00

154 lines
4.4 KiB
TypeScript

import { invalidate } from '$app/navigation';
import { addBooksToShelf, createBookshelf, listBookshelves, removeBooksFromShelf } from '$lib/api';
import type { Book, Bookshelf } from '$lib/schema';
import { getContext, setContext } from 'svelte';
import { toast } from 'svelte-sonner';
import { SvelteMap } from 'svelte/reactivity';
export class BookshelfState {
readonly libraryBookshelves = new SvelteMap<string, Bookshelf[]>();
getBookshelves(libraryId: string | number): Bookshelf[] | undefined {
const id = libraryId.toString();
if (!this.libraryBookshelves.has(id)) {
this.fetchBookshelves(id).then((shelves) => {
this.libraryBookshelves.set(id, shelves);
});
}
return this.libraryBookshelves.get(id);
}
async fetchBookshelves(libraryId: string) {
try {
const paginatedBookshelves = await listBookshelves({
libraries: [libraryId]
});
return paginatedBookshelves.items;
} catch (error) {
console.error('Failed to retrieve bookshelves: ', error);
}
}
async addBookshelf(name: string, libraryId?: string | number, booksToAdd?: string[] | number[]) {
try {
const bookshelf = await createBookshelf({
title: name,
library_id: libraryId,
book_ids: booksToAdd
});
if (bookshelf.library_id) {
const currentShelves = this.libraryBookshelves.get(bookshelf.library_id.toString()) || [];
this.libraryBookshelves.set(bookshelf.library_id.toString(), [
...currentShelves,
bookshelf
]);
}
invalidate('app:books');
if (booksToAdd?.length) toast.success(`Added ${booksToAdd.length} books to '${name}'`);
else toast.success(`Created shelf '${name}'`);
return bookshelf;
} catch (error) {
toast.error(`Failed to create bookshelf '${name}'`);
console.error(`Failed to create bookshelf: `, error);
}
}
async addBooksToShelf(shelfId: number | string, bookIds: number[]) {
try {
const shelf = await addBooksToShelf({
shelf_id: shelfId,
book_ids: bookIds
});
this.updateShelf(shelf);
if (bookIds.length === 1) toast.success(`Added book to shelf!`);
else toast.success(`Added ${bookIds.length} books to shelf!`);
} catch (error) {
console.error('Failed to add book(s) to shelf: ', error);
toast.error('Failed to add book(s) to shelf.');
}
}
async removeBooksFromShelf(shelfId: number | string, bookIds: number[]) {
try {
const shelf = await removeBooksFromShelf({
shelf_id: shelfId,
book_ids: bookIds
});
this.updateShelf(shelf);
if (bookIds.length === 1) toast.success(`Removed book from shelf.`);
else toast.success(`Removed ${bookIds.length} books from shelf!`);
} catch (error) {
console.error('Failed to remove book(s) from shelf: ', error);
toast.error('Failed to remove book(s) from shelf.');
}
}
async updateShelf(shelf: Bookshelf) {
if (!shelf.library_id) return;
const bookshelves = this.libraryBookshelves.get(shelf.library_id.toString());
if (!bookshelves) return;
const index = bookshelves.findIndex((bookshelf) => bookshelf.id === shelf.id);
if (index !== -1) {
this.libraryBookshelves.set(shelf.library_id.toString(), [
...bookshelves.slice(0, index),
shelf,
...bookshelves.slice(index + 1)
]);
}
}
deletedBooks(books: Book[]) {
// Assume all books are in the same library
const libraryId = books[0].library_id.toString();
const bookshelves = this.libraryBookshelves.get(libraryId);
if (!bookshelves) return;
// Create a Set of shelf IDs that need updating for efficient lookup
// eslint-disable-next-line svelte/prefer-svelte-reactivity -- local temporary, not reactive state
const shelfIdsToUpdate = new Set<number>();
books.forEach((book) => {
book.lists.forEach((shelf) => {
shelfIdsToUpdate.add(shelf.id);
});
});
const updatedBookshelves = bookshelves.map((shelf) => {
if (shelfIdsToUpdate.has(shelf.id)) {
// Count how many times this shelf appears across all deleted books
let decrementBy = 0;
books.forEach((book) => {
if (book.lists.some((s) => s.id === shelf.id)) {
decrementBy++;
}
});
return { ...shelf, total: (shelf.total ?? 0) - decrementBy };
}
return shelf;
});
this.libraryBookshelves.set(libraryId, updatedBookshelves);
}
}
const BOOKSHELF_KEY = Symbol('BOOKSHELF');
export function setBookshelfState() {
return setContext(BOOKSHELF_KEY, new BookshelfState());
}
export function getBookshelfState() {
return getContext<ReturnType<typeof setBookshelfState>>(BOOKSHELF_KEY);
}