pagepiper/web/src/views/LibraryView.vue

108 lines
3.4 KiB
Vue

<template>
<main class="library">
<header class="library-header">
<h1>Library</h1>
<button class="btn-primary" @click="scan" :disabled="scanning">
{{ scanning ? "Scanning..." : "Scan for PDFs" }}
</button>
</header>
<p class="error-msg" v-if="error">{{ error }}</p>
<p class="empty-state" v-if="!loading && docs.length === 0">
No books indexed yet. Click "Scan for PDFs" to discover PDFs in your books directory.<br>
Make sure your PDF directory is mounted at <code>/books</code> inside the container.
</p>
<div class="doc-grid" v-else>
<DocumentCard
v-for="doc in docs"
:key="doc.id"
:doc="doc"
@reingest="reingest"
@delete="remove"
@refresh="load"
/>
</div>
<p class="scan-result" v-if="scanResult">
Found {{ scanResult.discovered }} PDFs, queued {{ scanResult.queued }} for indexing.
</p>
</main>
</template>
<script setup lang="ts">
import { onMounted, ref } from "vue"
import { api, type Document } from "@/api"
import DocumentCard from "@/components/DocumentCard.vue"
const docs = ref<Document[]>([])
const loading = ref(true)
const scanning = ref(false)
const error = ref<string | null>(null)
const scanResult = ref<{ discovered: number; queued: number } | null>(null)
async function load() {
loading.value = true
error.value = null
try {
docs.value = await api.getLibrary()
} catch (e) {
error.value = e instanceof Error ? e.message : "Failed to load library"
} finally {
loading.value = false
}
}
async function scan() {
scanning.value = true
error.value = null
try {
scanResult.value = await api.scanLibrary()
await load()
} catch (e) {
error.value = e instanceof Error ? e.message : "Scan failed"
} finally {
scanning.value = false
}
}
async function reingest(id: string) {
error.value = null
try {
await api.reingestDocument(id)
await load()
} catch (e) {
error.value = e instanceof Error ? e.message : "Re-index failed"
}
}
async function remove(id: string) {
if (!confirm("Remove this book from the library? The PDF file is not deleted.")) return
error.value = null
try {
await api.deleteDocument(id)
await load()
} catch (e) {
error.value = e instanceof Error ? e.message : "Remove failed"
}
}
onMounted(load)
</script>
<style scoped>
.library { padding: 1.5rem; max-width: 1200px; margin: 0 auto; }
.library-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 1.5rem; flex-wrap: wrap; gap: 1rem; }
h1 { font-size: 1.5rem; }
.btn-primary {
background: var(--color-accent); color: #fff; border: none; padding: 0.6rem 1.2rem;
border-radius: var(--radius-sm); cursor: pointer; font-size: 0.95rem;
}
.btn-primary:disabled { opacity: 0.5; cursor: default; }
.doc-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 1rem; }
.empty-state { color: var(--color-text-muted); line-height: 1.8; }
.empty-state code { font-family: var(--font-mono); background: var(--color-surface-alt); padding: 2px 6px; border-radius: 3px; }
.scan-result { margin-top: 1rem; color: var(--color-text-muted); font-size: 0.9rem; }
.error-msg { color: var(--color-error); margin-bottom: 1rem; font-size: 0.9rem; }
</style>