1.0.9 improves library loading performance
This commit is contained in:
@@ -5,9 +5,9 @@
|
||||
import * as Sidebar from "./lib/components/ui/sidebar";
|
||||
import { Button } from "./lib/components/ui/button";
|
||||
import SettingsPanel from "./components/SettingsPanel.svelte";
|
||||
import ScanPanel from "./components/ScanPanel.svelte";
|
||||
import ScanPanel from "./components/scan/ScanPanel.svelte";
|
||||
import HistoryPanel from "./components/HistoryPanel.svelte";
|
||||
import LibraryPanel from "./components/LibraryPanel.svelte";
|
||||
import LibraryPanel from "./components/library/LibraryPanel.svelte";
|
||||
import { Menu } from "lucide-svelte";
|
||||
import ToastHost from "./components/ToastHost.svelte";
|
||||
import { healthCheck } from "./lib/api.js";
|
||||
|
||||
@@ -154,7 +154,7 @@
|
||||
>
|
||||
{#if !collapsed}
|
||||
<Badge className="bg-white/10 text-text-secondary"
|
||||
>v1.0.8 Release Candiate</Badge
|
||||
>v1.0.9 Release Candiate</Badge
|
||||
>
|
||||
{:else}
|
||||
<Badge className="bg-white/10 text-text-secondary">v</Badge>
|
||||
|
||||
+62
-30
@@ -1,30 +1,31 @@
|
||||
<script>
|
||||
import { onMount } from "svelte";
|
||||
import { getLibraryReport } from "../lib/api.js";
|
||||
import { Button } from "../lib/components/ui/button";
|
||||
import { getLibraryReport } from "../../lib/api.js";
|
||||
import { Button } from "../../lib/components/ui/button";
|
||||
import { Skeleton } from "../../lib/components/ui/skeleton";
|
||||
import { ChevronDown, ChevronUp, RefreshCcw, FileText } from "lucide-svelte";
|
||||
|
||||
let items = [];
|
||||
let loading = false;
|
||||
let error = null;
|
||||
let expanded = {};
|
||||
let page = 0;
|
||||
let page = 1;
|
||||
const pageSize = 200;
|
||||
let totalItems = 0;
|
||||
let totalPages = 1;
|
||||
let hasMore = false;
|
||||
|
||||
async function loadLibrary(reset = true) {
|
||||
async function loadLibrary(nextPage = 1) {
|
||||
loading = true;
|
||||
error = null;
|
||||
try {
|
||||
if (reset) {
|
||||
page = 0;
|
||||
items = [];
|
||||
}
|
||||
const response = await getLibraryReport(pageSize, page * pageSize);
|
||||
const nextItems = response.items || [];
|
||||
items = reset ? nextItems : [...items, ...nextItems];
|
||||
if (nextItems.length > 0) {
|
||||
page += 1;
|
||||
}
|
||||
page = nextPage;
|
||||
expanded = {};
|
||||
const response = await getLibraryReport(page, pageSize);
|
||||
items = response.items || [];
|
||||
totalItems = response.total_items ?? items.length;
|
||||
totalPages = Math.max(1, Math.ceil(totalItems / pageSize));
|
||||
hasMore = response.has_more ?? page < totalPages;
|
||||
} catch (err) {
|
||||
error = `Failed to load library report: ${err.message}`;
|
||||
} finally {
|
||||
@@ -36,7 +37,7 @@
|
||||
expanded = { ...expanded, [key]: !expanded[key] };
|
||||
}
|
||||
|
||||
onMount(() => loadLibrary(true));
|
||||
onMount(() => loadLibrary(1));
|
||||
</script>
|
||||
|
||||
<div class="space-y-6">
|
||||
@@ -51,7 +52,7 @@
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="border-white/15 text-text-secondary hover:bg-white/10"
|
||||
on:click={() => loadLibrary(true)}
|
||||
on:click={() => loadLibrary(1)}
|
||||
disabled={loading}
|
||||
>
|
||||
<RefreshCcw class="h-4 w-4" />
|
||||
@@ -66,7 +67,24 @@
|
||||
{/if}
|
||||
|
||||
{#if loading}
|
||||
<div class="text-[13px] text-text-secondary">Loading library report...</div>
|
||||
<div class="space-y-4">
|
||||
{#each Array(4) as _, index}
|
||||
<div class="rounded-2xl border border-border bg-card/60 overflow-hidden">
|
||||
<div class="px-6 py-4 flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
|
||||
<div class="space-y-2">
|
||||
<Skeleton className="h-4 w-40" />
|
||||
<Skeleton className="h-3 w-24" />
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<Skeleton className="h-6 w-20 rounded-full" />
|
||||
<Skeleton className="h-6 w-20 rounded-full" />
|
||||
<Skeleton className="h-6 w-20 rounded-full" />
|
||||
<Skeleton className="h-6 w-6 rounded-full" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{:else if items.length === 0}
|
||||
<div class="border border-border rounded-2xl p-12 text-center">
|
||||
<div class="flex flex-col items-center gap-4">
|
||||
@@ -102,10 +120,10 @@
|
||||
</span>
|
||||
<button
|
||||
class="ml-2 text-text-secondary hover:text-white transition-colors"
|
||||
on:click={() => toggleScan(item.title)}
|
||||
on:click={() => toggleScan(item.year ? `${item.title} (${item.year})` : item.title)}
|
||||
aria-label="Toggle scan details"
|
||||
>
|
||||
{#if expanded[item.title]}
|
||||
{#if expanded[item.year ? `${item.title} (${item.year})` : item.title]}
|
||||
<ChevronUp class="h-4 w-4" />
|
||||
{:else}
|
||||
<ChevronDown class="h-4 w-4" />
|
||||
@@ -114,7 +132,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if expanded[item.title]}
|
||||
{#if expanded[item.year ? `${item.title} (${item.year})` : item.title]}
|
||||
<div class="border-t border-border bg-bg-secondary/40">
|
||||
<div class="px-6 py-4 overflow-x-auto">
|
||||
<table class="min-w-full text-[12px] text-text-secondary">
|
||||
@@ -158,16 +176,30 @@
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
<div class="flex justify-center">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="border-white/15 text-text-secondary hover:bg-white/10"
|
||||
on:click={() => loadLibrary(false)}
|
||||
disabled={loading}
|
||||
>
|
||||
Load more
|
||||
</Button>
|
||||
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3 pt-2">
|
||||
<div class="text-[11px] text-text-tertiary">
|
||||
Page {page} of {totalPages}
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="border-white/15 text-text-secondary hover:bg-white/10"
|
||||
on:click={() => loadLibrary(page - 1)}
|
||||
disabled={loading || page <= 1}
|
||||
>
|
||||
Previous
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="border-white/15 text-text-secondary hover:bg-white/10"
|
||||
on:click={() => loadLibrary(page + 1)}
|
||||
disabled={loading || !hasMore}
|
||||
>
|
||||
Next
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
+5
-5
@@ -1,8 +1,8 @@
|
||||
<script>
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import StatusBadge from "./StatusBadge.svelte";
|
||||
import { Button } from "../lib/components/ui/button";
|
||||
import { Skeleton } from "../lib/components/ui/skeleton";
|
||||
import StatusBadge from "../StatusBadge.svelte";
|
||||
import { Button } from "../../lib/components/ui/button";
|
||||
import { Skeleton } from "../../lib/components/ui/skeleton";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
@@ -10,12 +10,12 @@
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "../lib/components/ui/table";
|
||||
} from "../../lib/components/ui/table";
|
||||
import {
|
||||
searchTitle,
|
||||
saveSuggestedMatches,
|
||||
processBatch,
|
||||
} from "../lib/api.js";
|
||||
} from "../../lib/api.js";
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
+9
-9
@@ -8,22 +8,22 @@
|
||||
clearAllSuggestedMatches,
|
||||
getFolderRules,
|
||||
getScanHistory,
|
||||
} from "../lib/api.js";
|
||||
} from "../../lib/api.js";
|
||||
import ResultsList from "./ResultsList.svelte";
|
||||
import TypewriterQuote from "./TypewriterQuote.svelte";
|
||||
import { scanResults } from "../lib/scanStore.js";
|
||||
import { Button } from "../lib/components/ui/button";
|
||||
import { addToast } from "../lib/toastStore.js";
|
||||
import TypewriterQuote from "../TypewriterQuote.svelte";
|
||||
import { scanResults } from "../../lib/scanStore.js";
|
||||
import { Button } from "../../lib/components/ui/button";
|
||||
import { addToast } from "../../lib/toastStore.js";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "../lib/components/ui/card";
|
||||
import { Input } from "../lib/components/ui/input";
|
||||
import { Skeleton } from "../lib/components/ui/skeleton";
|
||||
import { Combobox } from "../lib/components/ui/combobox";
|
||||
} from "../../lib/components/ui/card";
|
||||
import { Input } from "../../lib/components/ui/input";
|
||||
import { Skeleton } from "../../lib/components/ui/skeleton";
|
||||
import { Combobox } from "../../lib/components/ui/combobox";
|
||||
import { FileText, Folder, Info, Plug, Scan } from "lucide-svelte";
|
||||
|
||||
// ------------------------------------------------------------
|
||||
@@ -336,10 +336,14 @@ export async function getStatistics() {
|
||||
|
||||
/**
|
||||
* GET /api/library - Get library health report
|
||||
* Returns: { success, scans: [...] }
|
||||
* Returns: { success, items: [...], total_items, page, page_size, has_more }
|
||||
*/
|
||||
export async function getLibraryReport(limit = 200, offset = 0) {
|
||||
return apiFetch(`/library?limit=${limit}&offset=${offset}`)
|
||||
export async function getLibraryReport(page = 1, pageSize = 200) {
|
||||
const params = new URLSearchParams({
|
||||
page: String(page),
|
||||
page_size: String(pageSize)
|
||||
})
|
||||
return apiFetch(`/library?${params.toString()}`)
|
||||
}
|
||||
|
||||
// ============ SCHEDULED SCANS API ============
|
||||
|
||||
Reference in New Issue
Block a user