From 6de8e8bc603e879bcad902a2b190d5600506a007 Mon Sep 17 00:00:00 2001 From: neon <18559658+neonsolstice@users.noreply.github.com> Date: Wed, 8 Apr 2026 05:02:57 -0600 Subject: [PATCH] feat(scanner): add live book grid during active scan --- client/src/components/AppSidebar.vue | 28 ++++---- client/src/composables/useDisplaySettings.ts | 2 +- .../src/composables/useViewDisplaySettings.ts | 2 +- .../book/components/VirtualBookGrid.vue | 19 ++++- .../components/LibraryCreatorModal.vue | 2 +- .../scanner/components/ScanProgressBar.vue | 31 ++++++++ .../scanner/composables/useLiveScanBooks.ts | 71 +++++++++++++++++++ .../features/settings/LibrariesSettings.vue | 9 +-- client/src/views/HomeView.vue | 23 +++++- packages/types/src/scanner.ts | 5 ++ server/src/modules/scanner/scan.gateway.ts | 5 ++ .../src/modules/scanner/scanner.repository.ts | 43 ++++++++++- server/src/modules/scanner/scanner.service.ts | 61 +++++++++++++++- 13 files changed, 272 insertions(+), 29 deletions(-) create mode 100644 client/src/features/scanner/components/ScanProgressBar.vue create mode 100644 client/src/features/scanner/composables/useLiveScanBooks.ts diff --git a/client/src/components/AppSidebar.vue b/client/src/components/AppSidebar.vue index 94afc6a18..dd227940e 100644 --- a/client/src/components/AppSidebar.vue +++ b/client/src/components/AppSidebar.vue @@ -48,12 +48,22 @@ const { lenses, fetchLenses, reorderLenses } = useLenses() const { collections, fetchCollections, reorderCollections } = useCollections() const { hasPermission } = usePermissions() const { subscribeLibrary, getProgress, progressMap } = useScanProgress() + +const refreshedFor = new Set() +watch(progressMap, (map) => { + for (const [libraryId, event] of map.entries()) { + if (event.status === 'completed' && !refreshedFor.has(libraryId)) { + refreshedFor.add(libraryId) + refreshLibraries() + setTimeout(() => refreshedFor.delete(libraryId), 5000) + } + } +}) const { onLibraryUploadCompleted } = useLibraryUploadEvents() const createLensOpen = ref(false) const createCollectionOpen = ref(false) const createLibraryOpen = ref(false) -const scanningLibraryId = ref(null) const { isOpen: librariesOpen, toggle: toggleLibraries } = useSidebarSection('libraries') const { isOpen: lensesOpen, toggle: toggleLenses } = useSidebarSection('lenses') @@ -108,19 +118,13 @@ function scanPct(libraryId: number): number { function onLibrarySaved(library: Library) { createLibraryOpen.value = false subscribeLibrary(library.id) - scanningLibraryId.value = library.id refreshLibraries() -} -watch(progressMap, (map) => { - if (scanningLibraryId.value === null) return - const event = map.get(scanningLibraryId.value) - if (event?.status === 'completed') { - const id = scanningLibraryId.value - scanningLibraryId.value = null - router.push({ name: 'library', params: { id } }) - } -}) + const sortKey = `projectx:sort:library:${library.id}` + localStorage.setItem(sortKey, JSON.stringify([{ field: 'addedAt', dir: 'desc' }])) + + router.push({ name: 'library', params: { id: library.id } }) +} onMounted(async () => { getSocket() diff --git a/client/src/composables/useDisplaySettings.ts b/client/src/composables/useDisplaySettings.ts index aa4e9cdf6..eee97b7d0 100644 --- a/client/src/composables/useDisplaySettings.ts +++ b/client/src/composables/useDisplaySettings.ts @@ -4,7 +4,7 @@ import { storage } from '@/services/storage' export type CardOverlayKey = 'series' | 'progress-bar' | 'progress-pill' | 'format' | 'rating' | 'new' | 'read-status' export type AuthorCoverShape = 'square' | 'circle' -const coverSize = ref(storage.get('coverSize', 140)) +const coverSize = ref(storage.get('coverSize', 130)) const gridGap = ref(storage.get('gridGap', 20)) const viewMode = ref<'grid' | 'list'>(storage.get('viewMode', 'grid')) const cardOverlays = ref(storage.get('cardOverlays', ['progress-bar', 'format', 'rating', 'read-status'])) diff --git a/client/src/composables/useViewDisplaySettings.ts b/client/src/composables/useViewDisplaySettings.ts index 7dd0fda31..346d29efc 100644 --- a/client/src/composables/useViewDisplaySettings.ts +++ b/client/src/composables/useViewDisplaySettings.ts @@ -12,7 +12,7 @@ function gridGapKey(type: ViewType, id: number) { } export function useViewDisplaySettings(viewType: ViewType, viewId: Readonly>) { - const globalCoverSize = storage.get('coverSize', 140) + const globalCoverSize = storage.get('coverSize', 130) const globalGridGap = storage.get('gridGap', 20) const coverSize = ref(globalCoverSize) diff --git a/client/src/features/book/components/VirtualBookGrid.vue b/client/src/features/book/components/VirtualBookGrid.vue index 802cadabf..9d25f78ba 100644 --- a/client/src/features/book/components/VirtualBookGrid.vue +++ b/client/src/features/book/components/VirtualBookGrid.vue @@ -15,10 +15,12 @@ const props = withDefaults( gridGap: number selectionMode?: boolean isSelected?: (bookId: number) => boolean + newBookIds?: Set }>(), { selectionMode: false, isSelected: undefined, + newBookIds: () => new Set(), }, ) @@ -92,7 +94,7 @@ const scrollerStyle = computed(() => ({ class="book-grid-scroller" >