First working POC
This commit is contained in:
@@ -0,0 +1,279 @@
|
||||
// Library state store
|
||||
|
||||
import { writable, derived } from "svelte/store";
|
||||
import type { Library, MediaItem, SearchResult, Genre } from "$lib/api/types";
|
||||
import { auth } from "./auth";
|
||||
|
||||
export type ViewMode = "grid" | "list";
|
||||
|
||||
interface LibraryState {
|
||||
libraries: Library[];
|
||||
currentLibrary: Library | null;
|
||||
items: MediaItem[];
|
||||
currentItem: MediaItem | null;
|
||||
isLoading: boolean;
|
||||
error: string | null;
|
||||
totalItems: number;
|
||||
searchQuery: string;
|
||||
searchResults: MediaItem[];
|
||||
viewMode: ViewMode;
|
||||
genres: Genre[];
|
||||
selectedGenres: string[];
|
||||
}
|
||||
|
||||
function getStoredViewMode(): ViewMode {
|
||||
if (typeof localStorage === "undefined") return "grid";
|
||||
const stored = localStorage.getItem("jellytau-view-mode");
|
||||
return stored === "list" ? "list" : "grid";
|
||||
}
|
||||
|
||||
function createLibraryStore() {
|
||||
const initialState: LibraryState = {
|
||||
libraries: [],
|
||||
currentLibrary: null,
|
||||
items: [],
|
||||
currentItem: null,
|
||||
isLoading: false,
|
||||
error: null,
|
||||
totalItems: 0,
|
||||
searchQuery: "",
|
||||
searchResults: [],
|
||||
viewMode: getStoredViewMode(),
|
||||
genres: [],
|
||||
selectedGenres: [],
|
||||
};
|
||||
|
||||
const { subscribe, set, update } = writable<LibraryState>(initialState);
|
||||
|
||||
// Test log to confirm cache logging is active
|
||||
console.log("✅ [LibraryStore] Cache logging enabled - you should see cache hit/miss logs below");
|
||||
|
||||
async function loadLibraries() {
|
||||
update((s) => ({ ...s, isLoading: true, error: null }));
|
||||
|
||||
try {
|
||||
const startTime = performance.now();
|
||||
const repo = auth.getRepository();
|
||||
|
||||
console.log("📚 [LibraryStore] Loading libraries...");
|
||||
|
||||
const libraries = await repo.getLibraries();
|
||||
|
||||
const loadTime = Math.round(performance.now() - startTime);
|
||||
|
||||
if (loadTime < 100) {
|
||||
console.log(`🚀 [LibraryStore] CACHE HIT! Loaded ${libraries.length} libraries in ${loadTime}ms (instant)`);
|
||||
} else {
|
||||
console.log(`⏳ [LibraryStore] Loaded ${libraries.length} libraries in ${loadTime}ms (from server)`);
|
||||
}
|
||||
|
||||
update((s) => ({
|
||||
...s,
|
||||
libraries,
|
||||
isLoading: false,
|
||||
}));
|
||||
|
||||
return libraries;
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "Failed to load libraries";
|
||||
update((s) => ({ ...s, isLoading: false, error: message }));
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadItems(
|
||||
parentId: string,
|
||||
options: { startIndex?: number; limit?: number; genres?: string[] } = {}
|
||||
) {
|
||||
update((s) => ({ ...s, isLoading: true, error: null }));
|
||||
|
||||
try {
|
||||
const startTime = performance.now();
|
||||
const repo = auth.getRepository();
|
||||
|
||||
console.log(`📚 [LibraryStore] Loading items for parent: ${parentId.substring(0, 8)}...`);
|
||||
|
||||
const result = await repo.getItems(parentId, {
|
||||
startIndex: options.startIndex ?? 0,
|
||||
limit: options.limit ?? 10000,
|
||||
fields: ["PrimaryImageAspectRatio", "Overview", "MediaStreams"],
|
||||
sortBy: "SortName",
|
||||
sortOrder: "Ascending",
|
||||
genres: options.genres,
|
||||
});
|
||||
|
||||
const loadTime = Math.round(performance.now() - startTime);
|
||||
|
||||
if (loadTime < 100) {
|
||||
console.log(`🚀 [LibraryStore] CACHE HIT! Loaded ${result.items.length} items in ${loadTime}ms (instant)`);
|
||||
} else {
|
||||
console.log(`⏳ [LibraryStore] Loaded ${result.items.length} items in ${loadTime}ms (from server)`);
|
||||
}
|
||||
|
||||
update((s) => ({
|
||||
...s,
|
||||
items: result.items,
|
||||
totalItems: result.totalRecordCount,
|
||||
isLoading: false,
|
||||
}));
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "Failed to load items";
|
||||
update((s) => ({ ...s, isLoading: false, error: message }));
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadItem(itemId: string) {
|
||||
update((s) => ({ ...s, isLoading: true, error: null }));
|
||||
|
||||
try {
|
||||
const repo = auth.getRepository();
|
||||
const item = await repo.getItem(itemId);
|
||||
|
||||
console.log(`[LibraryStore] loadItem(${itemId}): ${item.name} (${item.type})`);
|
||||
console.log(`[LibraryStore] - Has people? ${item.people ? `YES (${item.people.length})` : 'NO'}`);
|
||||
if (item.people && item.people.length > 0) {
|
||||
item.people.forEach((p, i) => {
|
||||
console.log(`[LibraryStore] [${i}] ${p.name} (type: "${p.type}", id: "${p.id}")`);
|
||||
});
|
||||
}
|
||||
|
||||
update((s) => ({
|
||||
...s,
|
||||
currentItem: item,
|
||||
isLoading: false,
|
||||
}));
|
||||
|
||||
return item;
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "Failed to load item";
|
||||
update((s) => ({ ...s, isLoading: false, error: message }));
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function search(query: string) {
|
||||
if (!query.trim()) {
|
||||
update((s) => ({ ...s, searchQuery: "", searchResults: [] }));
|
||||
return;
|
||||
}
|
||||
|
||||
update((s) => ({ ...s, isLoading: true, error: null, searchQuery: query }));
|
||||
|
||||
try {
|
||||
const repo = auth.getRepository();
|
||||
|
||||
// Add 10-second timeout to prevent indefinite hanging
|
||||
const timeoutPromise = new Promise<never>((_, reject) =>
|
||||
setTimeout(() => reject(new Error("Search timeout - please try again")), 10000)
|
||||
);
|
||||
|
||||
const result = await Promise.race([
|
||||
repo.search(query, { limit: 10000 }),
|
||||
timeoutPromise
|
||||
]);
|
||||
|
||||
update((s) => ({
|
||||
...s,
|
||||
searchResults: result.items,
|
||||
isLoading: false,
|
||||
}));
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "Search failed";
|
||||
update((s) => ({ ...s, isLoading: false, error: message }));
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
function setCurrentLibrary(library: Library | null) {
|
||||
update((s) => ({ ...s, currentLibrary: library, items: [], currentItem: null }));
|
||||
}
|
||||
|
||||
function clearSearch() {
|
||||
update((s) => ({ ...s, searchQuery: "", searchResults: [] }));
|
||||
}
|
||||
|
||||
function setViewMode(mode: ViewMode) {
|
||||
if (typeof localStorage !== "undefined") {
|
||||
localStorage.setItem("jellytau-view-mode", mode);
|
||||
}
|
||||
update((s) => ({ ...s, viewMode: mode }));
|
||||
}
|
||||
|
||||
function toggleViewMode() {
|
||||
update((s) => {
|
||||
const newMode = s.viewMode === "grid" ? "list" : "grid";
|
||||
if (typeof localStorage !== "undefined") {
|
||||
localStorage.setItem("jellytau-view-mode", newMode);
|
||||
}
|
||||
return { ...s, viewMode: newMode };
|
||||
});
|
||||
}
|
||||
|
||||
async function loadGenres(parentId?: string) {
|
||||
try {
|
||||
const repo = auth.getRepository();
|
||||
const genres = await repo.getGenres(parentId);
|
||||
update((s) => ({ ...s, genres }));
|
||||
return genres;
|
||||
} catch (error) {
|
||||
console.error("Failed to load genres:", error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function setSelectedGenres(genres: string[]) {
|
||||
update((s) => ({ ...s, selectedGenres: genres }));
|
||||
}
|
||||
|
||||
function toggleGenre(genreName: string) {
|
||||
update((s) => {
|
||||
const current = s.selectedGenres;
|
||||
const newGenres = current.includes(genreName)
|
||||
? current.filter((g) => g !== genreName)
|
||||
: [...current, genreName];
|
||||
return { ...s, selectedGenres: newGenres };
|
||||
});
|
||||
}
|
||||
|
||||
function clearGenres() {
|
||||
update((s) => ({ ...s, selectedGenres: [] }));
|
||||
}
|
||||
|
||||
function reset() {
|
||||
set(initialState);
|
||||
}
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
loadLibraries,
|
||||
loadItems,
|
||||
loadItem,
|
||||
search,
|
||||
setCurrentLibrary,
|
||||
clearSearch,
|
||||
setViewMode,
|
||||
toggleViewMode,
|
||||
loadGenres,
|
||||
setSelectedGenres,
|
||||
toggleGenre,
|
||||
clearGenres,
|
||||
reset,
|
||||
};
|
||||
}
|
||||
|
||||
export const library = createLibraryStore();
|
||||
|
||||
// Derived stores
|
||||
export const libraries = derived(library, ($lib) => $lib.libraries);
|
||||
export const currentLibrary = derived(library, ($lib) => $lib.currentLibrary);
|
||||
export const libraryItems = derived(library, ($lib) => $lib.items);
|
||||
export const isLibraryLoading = derived(library, ($lib) => $lib.isLoading);
|
||||
export const libraryError = derived(library, ($lib) => $lib.error);
|
||||
export const viewMode = derived(library, ($lib) => $lib.viewMode);
|
||||
export const genres = derived(library, ($lib) => $lib.genres);
|
||||
export const selectedGenres = derived(library, ($lib) => $lib.selectedGenres);
|
||||
Reference in New Issue
Block a user