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.
154 lines
4.4 KiB
TypeScript
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);
|
|
}
|