From 4e93e20bbb6eef87da986a214a090789916e19f0 Mon Sep 17 00:00:00 2001 From: neon <18559658+neonsolstice@users.noreply.github.com> Date: Fri, 1 May 2026 23:59:37 -0600 Subject: [PATCH] feat(books): add table view, metadata export, and filter builder --- client/package.json | 20 +- client/src/components/AppLayout.vue | 9 +- client/src/components/SelectionActionBar.vue | 268 +++-- client/src/components/ViewHeader.vue | 162 +-- .../__tests__/SelectionActionBar.spec.ts | 104 +- .../components/ui/InputWithSuggestions.vue | 157 +++ .../ui/__tests__/InputWithSuggestions.spec.ts | 74 ++ .../ui/dropdown-menu/DropdownMenuItem.vue | 2 +- .../__tests__/useEffectiveViewMode.spec.ts | 55 + client/src/composables/useDisplaySettings.ts | 18 +- .../src/composables/useEffectiveViewMode.ts | 15 + .../src/composables/useViewDisplaySettings.ts | 2 +- .../src/features/author/views/AuthorsView.vue | 14 +- .../book/components/BookCoverCard.vue | 2 +- .../book/components/BookFilterBuilder.vue | 57 + .../features/book/components/BookListRow.vue | 2 +- .../book/components/BookQuickView.vue | 4 +- .../components/KeyboardShortcutOverlay.vue | 97 ++ .../book/components/MetadataExportDialog.vue | 356 +++++++ .../book/components/TableColumnPanel.vue | 266 +++++ .../book/components/VirtualBookGrid.vue | 2 +- .../book/components/VirtualBookTable.vue | 992 ++++++++++++++++++ .../__tests__/BookCoverCard.spec.ts | 20 +- .../components/__tests__/BookListRow.spec.ts | 20 +- .../__tests__/BookTableCells.spec.ts | 767 ++++++++++++++ .../__tests__/CollapsedSeriesCard.spec.ts | 9 + .../__tests__/KeyboardShortcutOverlay.spec.ts | 100 ++ .../__tests__/MetadataExportDialog.spec.ts | 211 ++++ .../VirtualBookTable.loadmore.spec.ts | 81 ++ .../detail/tabs/CoverEditorPanel.vue | 18 +- .../detail/tabs/CoverSearchDrawer.vue | 87 +- .../detail/tabs/EditMetadataTab.vue | 22 +- .../book/components/table/BookCoverDialog.vue | 154 +++ .../components/table/BookTableActionsCell.vue | 102 ++ .../table/BookTableCellDispatcher.vue | 165 +++ .../components/table/BookTableChipsCell.vue | 120 +++ .../table/BookTableCollapsedSeriesRow.vue | 55 + .../components/table/BookTableContextMenu.vue | 200 ++++ .../components/table/BookTableCoverCell.vue | 92 ++ .../components/table/BookTableDateCell.vue | 62 ++ .../components/table/BookTableFormatCell.vue | 51 + .../book/components/table/BookTableHeader.vue | 159 +++ .../table/BookTableHeaderContextMenu.vue | 190 ++++ .../table/BookTableLockableCell.vue | 44 + .../components/table/BookTableNumberCell.vue | 116 ++ .../table/BookTableProgressCell.vue | 30 + .../components/table/BookTableRatingCell.vue | 86 ++ .../table/BookTableReadStatusCell.vue | 88 ++ .../components/table/BookTableTextCell.vue | 255 +++++ .../__tests__/BookTableCellDispatcher.spec.ts | 100 ++ .../BookTableCollapsedSeriesRow.spec.ts | 156 +++ .../__tests__/tableColumnSchema.spec.ts | 88 ++ .../__tests__/useBookBulkActions.spec.ts | 237 ++++- .../__tests__/useBookDataSource.spec.ts | 235 +++++ .../__tests__/useBookMetadataExport.spec.ts | 221 ++++ .../__tests__/useBookQuery.spec.ts | 440 ++++++++ .../__tests__/useLibraryBooks.spec.ts | 546 ++++++++++ .../__tests__/useSavedViews.spec.ts | 99 ++ .../__tests__/useTableCellEditor.spec.ts | 343 ++++++ .../__tests__/useTableCellHelpers.spec.ts | 208 ++++ .../__tests__/useTableColumns.spec.ts | 366 +++++++ .../__tests__/useTableCoverDialog.spec.ts | 94 ++ .../__tests__/useTableDragReorder.spec.ts | 228 ++++ .../__tests__/useTableKeyboard.spec.ts | 281 +++++ .../__tests__/useTableLocks.spec.ts | 298 ++++++ .../__tests__/useTablePresets.spec.ts | 197 ++++ .../__tests__/useTableQuickFilters.spec.ts | 126 +++ .../__tests__/useTableResize.spec.ts | 122 +++ .../__tests__/useTableScrollBehavior.spec.ts | 135 +++ .../__tests__/useTableSorting.spec.ts | 188 ++++ .../composables/__tests__/useViewSort.spec.ts | 137 +++ .../book/composables/tableColumnSchema.ts | 355 +++++++ .../book/composables/useBookBulkActions.ts | 253 ++++- .../book/composables/useBookDataSource.ts | 111 ++ .../book/composables/useBookMetadataExport.ts | 169 +++ .../features/book/composables/useBookQuery.ts | 80 +- .../composables/useBookRefreshFeedback.ts | 147 +++ .../book/composables/useBookSelection.ts | 11 + .../book/composables/useBookTableShell.ts | 96 ++ .../book/composables/useBookViewContext.ts | 28 + .../book/composables/useBookViewSelection.ts | 28 + .../composables/useInfiniteScrollSentinel.ts | 52 + .../book/composables/useLibraryBooks.ts | 157 +++ .../composables/useMetadataFieldSearch.ts | 23 + .../book/composables/useRefreshMetadata.ts | 8 +- .../book/composables/useRefreshingBooks.ts | 23 +- .../book/composables/useSavedViews.ts | 180 ++++ .../book/composables/useScopedBookQuery.ts | 3 + .../book/composables/useTableCellEditor.ts | 162 +++ .../book/composables/useTableCellHelpers.ts | 50 + .../book/composables/useTableColumns.ts | 173 +++ .../book/composables/useTableContextMenu.ts | 20 + .../book/composables/useTableCoverDialog.ts | 23 + .../book/composables/useTableDragReorder.ts | 83 ++ .../book/composables/useTableKeyboard.ts | 222 ++++ .../book/composables/useTableLocks.ts | 95 ++ .../book/composables/useTablePresets.ts | 173 +++ .../book/composables/useTableQuickFilters.ts | 81 ++ .../book/composables/useTableResize.ts | 83 ++ .../composables/useTableScrollBehavior.ts | 31 + .../book/composables/useTableSorting.ts | 54 + .../book/composables/useTableViewControls.ts | 86 ++ .../features/book/composables/useViewSort.ts | 51 + .../src/features/book/lib/book-card-mapper.ts | 89 ++ client/src/features/book/lib/filter-labels.ts | 5 + .../book/lib/table-row-state-sync.spec.ts | 86 ++ .../features/book/lib/table-row-state-sync.ts | 32 + .../__tests__/useCollectionBooks.spec.ts | 154 +++ .../composables/useCollectionBooks.ts | 51 +- .../collection/composables/useCollections.ts | 21 +- .../series/views/SeriesDetailView.vue | 74 +- .../features/settings/AppearanceSettings.vue | 17 + .../__tests__/useSmartScope.spec.ts | 125 +++ .../smart-scope/composables/useSmartScope.ts | 49 +- .../smart-scope/composables/useSmartScopes.ts | 57 +- .../library/LanguageDistributionChart.vue | 16 +- client/src/views/CollectionView.vue | 380 ++++--- client/src/views/HomeView.vue | 498 ++++++--- client/src/views/SmartScopeView.vue | 393 ++++--- client/vite.config.ts | 4 + .../types/src/__tests__/table-layout.spec.ts | 146 +++ packages/types/src/audit.ts | 1 + packages/types/src/book.ts | 13 +- packages/types/src/font.ts | 36 +- packages/types/src/index.ts | 1 + packages/types/src/query.ts | 44 +- packages/types/src/table-layout.ts | 45 + packages/types/tsconfig.json | 2 +- pnpm-lock.yaml | 29 + server/src/db/migrations/0000_baseline.sql | 3 + .../src/db/migrations/meta/0000_snapshot.json | 65 +- server/src/db/migrations/meta/_journal.json | 2 +- server/src/db/schema/metadata.ts | 6 +- server/src/db/schema/reader.ts | 2 + .../book-metadata-lock.service.ts | 9 + .../book/book-query-builder.service.test.ts | 148 ++- .../book/book-query-builder.service.ts | 125 +-- .../book/book-sort-builder.service.test.ts | 182 ++++ .../modules/book/book-sort-builder.service.ts | 100 ++ .../src/modules/book/book.controller.test.ts | 186 +++- server/src/modules/book/book.controller.ts | 109 +- server/src/modules/book/book.module.test.ts | 3 +- server/src/modules/book/book.module.ts | 3 +- .../src/modules/book/book.repository.test.ts | 16 +- server/src/modules/book/book.repository.ts | 255 +++-- server/src/modules/book/book.service.test.ts | 620 ++++++++++- server/src/modules/book/book.service.ts | 661 +++++++++++- .../book/dto/book-dto-validation.test.ts | 96 +- .../src/modules/book/dto/bulk-book-ids.dto.ts | 9 +- .../book/dto/bulk-query-selection.dto.ts | 22 + .../modules/book/dto/bulk-selection.dto.ts | 24 + .../book/dto/bulk-set-metadata-lock.dto.ts | 10 +- .../modules/book/dto/bulk-set-metadata.dto.ts | 13 + .../modules/book/dto/bulk-set-rating.dto.ts | 10 +- .../modules/book/dto/bulk-set-status.dto.ts | 10 +- .../modules/book/dto/bulk-update-tags.dto.ts | 10 +- .../src/modules/book/dto/delete-books.dto.ts | 9 +- .../book/dto/metadata-export-options.dto.ts | 27 + .../modules/book/dto/metadata-export.dto.ts | 30 + .../book/pipes/book-query.pipe.test.ts | 38 +- .../src/modules/book/pipes/book-query.pipe.ts | 22 +- .../book/utils/assemble-book-cards.test.ts | 130 ++- .../modules/book/utils/assemble-book-cards.ts | 59 +- .../book/utils/group-rule.validator.ts | 84 +- .../collection/collection.controller.test.ts | 17 + .../collection/collection.controller.ts | 7 + .../collection/collection.repository.ts | 9 + .../collection/collection.service.test.ts | 125 +-- .../modules/collection/collection.service.ts | 96 +- .../src/modules/scanner/scanner.repository.ts | 6 +- .../smart-scope.controller.test.ts | 32 + .../smart-scope/smart-scope.controller.ts | 47 +- .../smart-scope/smart-scope.service.test.ts | 232 +++- .../smart-scope/smart-scope.service.ts | 91 +- 174 files changed, 18545 insertions(+), 1521 deletions(-) create mode 100644 client/src/components/ui/InputWithSuggestions.vue create mode 100644 client/src/components/ui/__tests__/InputWithSuggestions.spec.ts create mode 100644 client/src/composables/__tests__/useEffectiveViewMode.spec.ts create mode 100644 client/src/composables/useEffectiveViewMode.ts create mode 100644 client/src/features/book/components/KeyboardShortcutOverlay.vue create mode 100644 client/src/features/book/components/MetadataExportDialog.vue create mode 100644 client/src/features/book/components/TableColumnPanel.vue create mode 100644 client/src/features/book/components/VirtualBookTable.vue create mode 100644 client/src/features/book/components/__tests__/BookTableCells.spec.ts create mode 100644 client/src/features/book/components/__tests__/KeyboardShortcutOverlay.spec.ts create mode 100644 client/src/features/book/components/__tests__/MetadataExportDialog.spec.ts create mode 100644 client/src/features/book/components/__tests__/VirtualBookTable.loadmore.spec.ts create mode 100644 client/src/features/book/components/table/BookCoverDialog.vue create mode 100644 client/src/features/book/components/table/BookTableActionsCell.vue create mode 100644 client/src/features/book/components/table/BookTableCellDispatcher.vue create mode 100644 client/src/features/book/components/table/BookTableChipsCell.vue create mode 100644 client/src/features/book/components/table/BookTableCollapsedSeriesRow.vue create mode 100644 client/src/features/book/components/table/BookTableContextMenu.vue create mode 100644 client/src/features/book/components/table/BookTableCoverCell.vue create mode 100644 client/src/features/book/components/table/BookTableDateCell.vue create mode 100644 client/src/features/book/components/table/BookTableFormatCell.vue create mode 100644 client/src/features/book/components/table/BookTableHeader.vue create mode 100644 client/src/features/book/components/table/BookTableHeaderContextMenu.vue create mode 100644 client/src/features/book/components/table/BookTableLockableCell.vue create mode 100644 client/src/features/book/components/table/BookTableNumberCell.vue create mode 100644 client/src/features/book/components/table/BookTableProgressCell.vue create mode 100644 client/src/features/book/components/table/BookTableRatingCell.vue create mode 100644 client/src/features/book/components/table/BookTableReadStatusCell.vue create mode 100644 client/src/features/book/components/table/BookTableTextCell.vue create mode 100644 client/src/features/book/components/table/__tests__/BookTableCellDispatcher.spec.ts create mode 100644 client/src/features/book/components/table/__tests__/BookTableCollapsedSeriesRow.spec.ts create mode 100644 client/src/features/book/composables/__tests__/tableColumnSchema.spec.ts create mode 100644 client/src/features/book/composables/__tests__/useBookDataSource.spec.ts create mode 100644 client/src/features/book/composables/__tests__/useBookMetadataExport.spec.ts create mode 100644 client/src/features/book/composables/__tests__/useBookQuery.spec.ts create mode 100644 client/src/features/book/composables/__tests__/useLibraryBooks.spec.ts create mode 100644 client/src/features/book/composables/__tests__/useSavedViews.spec.ts create mode 100644 client/src/features/book/composables/__tests__/useTableCellEditor.spec.ts create mode 100644 client/src/features/book/composables/__tests__/useTableCellHelpers.spec.ts create mode 100644 client/src/features/book/composables/__tests__/useTableColumns.spec.ts create mode 100644 client/src/features/book/composables/__tests__/useTableCoverDialog.spec.ts create mode 100644 client/src/features/book/composables/__tests__/useTableDragReorder.spec.ts create mode 100644 client/src/features/book/composables/__tests__/useTableKeyboard.spec.ts create mode 100644 client/src/features/book/composables/__tests__/useTableLocks.spec.ts create mode 100644 client/src/features/book/composables/__tests__/useTablePresets.spec.ts create mode 100644 client/src/features/book/composables/__tests__/useTableQuickFilters.spec.ts create mode 100644 client/src/features/book/composables/__tests__/useTableResize.spec.ts create mode 100644 client/src/features/book/composables/__tests__/useTableScrollBehavior.spec.ts create mode 100644 client/src/features/book/composables/__tests__/useTableSorting.spec.ts create mode 100644 client/src/features/book/composables/__tests__/useViewSort.spec.ts create mode 100644 client/src/features/book/composables/tableColumnSchema.ts create mode 100644 client/src/features/book/composables/useBookDataSource.ts create mode 100644 client/src/features/book/composables/useBookMetadataExport.ts create mode 100644 client/src/features/book/composables/useBookRefreshFeedback.ts create mode 100644 client/src/features/book/composables/useBookTableShell.ts create mode 100644 client/src/features/book/composables/useBookViewContext.ts create mode 100644 client/src/features/book/composables/useBookViewSelection.ts create mode 100644 client/src/features/book/composables/useInfiniteScrollSentinel.ts create mode 100644 client/src/features/book/composables/useLibraryBooks.ts create mode 100644 client/src/features/book/composables/useMetadataFieldSearch.ts create mode 100644 client/src/features/book/composables/useSavedViews.ts create mode 100644 client/src/features/book/composables/useScopedBookQuery.ts create mode 100644 client/src/features/book/composables/useTableCellEditor.ts create mode 100644 client/src/features/book/composables/useTableCellHelpers.ts create mode 100644 client/src/features/book/composables/useTableColumns.ts create mode 100644 client/src/features/book/composables/useTableContextMenu.ts create mode 100644 client/src/features/book/composables/useTableCoverDialog.ts create mode 100644 client/src/features/book/composables/useTableDragReorder.ts create mode 100644 client/src/features/book/composables/useTableKeyboard.ts create mode 100644 client/src/features/book/composables/useTableLocks.ts create mode 100644 client/src/features/book/composables/useTablePresets.ts create mode 100644 client/src/features/book/composables/useTableQuickFilters.ts create mode 100644 client/src/features/book/composables/useTableResize.ts create mode 100644 client/src/features/book/composables/useTableScrollBehavior.ts create mode 100644 client/src/features/book/composables/useTableSorting.ts create mode 100644 client/src/features/book/composables/useTableViewControls.ts create mode 100644 client/src/features/book/composables/useViewSort.ts create mode 100644 client/src/features/book/lib/book-card-mapper.ts create mode 100644 client/src/features/book/lib/table-row-state-sync.spec.ts create mode 100644 client/src/features/book/lib/table-row-state-sync.ts create mode 100644 client/src/features/collection/composables/__tests__/useCollectionBooks.spec.ts create mode 100644 client/src/features/smart-scope/composables/__tests__/useSmartScope.spec.ts create mode 100644 packages/types/src/__tests__/table-layout.spec.ts create mode 100644 packages/types/src/table-layout.ts create mode 100644 server/src/modules/book/book-sort-builder.service.test.ts create mode 100644 server/src/modules/book/book-sort-builder.service.ts create mode 100644 server/src/modules/book/dto/bulk-query-selection.dto.ts create mode 100644 server/src/modules/book/dto/bulk-selection.dto.ts create mode 100644 server/src/modules/book/dto/bulk-set-metadata.dto.ts create mode 100644 server/src/modules/book/dto/metadata-export-options.dto.ts create mode 100644 server/src/modules/book/dto/metadata-export.dto.ts diff --git a/client/package.json b/client/package.json index f2811014c..3f54d1e17 100644 --- a/client/package.json +++ b/client/package.json @@ -23,20 +23,22 @@ "format:check": "prettier --check src/" }, "dependencies": { + "@bookorbit/types": "workspace:*", "@fontsource-variable/fraunces": "^5.2.9", "@fontsource-variable/inter": "^5.2.8", - "@bookorbit/types": "workspace:*", "@tailwindcss/vite": "^4.2.4", + "@tanstack/vue-table": "^8.21.3", + "@tanstack/vue-virtual": "^3.13.24", "@types/howler": "^2.2.12", - "@vueuse/core": "^14.2.1", + "@vueuse/core": "^14.3.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", - "dompurify": "^3.4.1", + "dompurify": "^3.4.2", "driver.js": "^1.4.0", "echarts": "^6.0.0", "howler": "^2.2.4", "lucide-vue-next": "^1.0.0", - "pdfjs-dist": "^5.6.205", + "pdfjs-dist": "^5.7.284", "pinia": "^3.0.4", "reka-ui": "^2.9.6", "socket.io-client": "^4.8.3", @@ -47,7 +49,7 @@ "vue-echarts": "8.1.0-beta.2", "vue-router": "^5.0.6", "vue-sonner": "^2.0.9", - "vue-virtual-scroller": "^3.0.0" + "vue-virtual-scroller": "^3.0.2" }, "devDependencies": { "@tsconfig/node24": "^24.0.4", @@ -58,16 +60,16 @@ "@vitest/coverage-v8": "^4.1.5", "@vitest/eslint-plugin": "^1.6.16", "@vue/eslint-config-typescript": "^14.7.0", - "@vue/test-utils": "^2.4.8", + "@vue/test-utils": "^2.4.10", "@vue/tsconfig": "^0.9.1", "eslint": "^10.2.1", "eslint-config-prettier": "^10.1.8", - "eslint-plugin-oxlint": "~1.61.0", + "eslint-plugin-oxlint": "~1.62.0", "eslint-plugin-vue": "~10.9.0", "jiti": "^2.6.1", - "jsdom": "^29.0.2", + "jsdom": "^29.1.1", "npm-run-all2": "^8.0.4", - "oxlint": "~1.61.0", + "oxlint": "~1.62.0", "prettier": "3.8.3", "tw-animate-css": "^1.4.0", "typescript": "~5.9.3", diff --git a/client/src/components/AppLayout.vue b/client/src/components/AppLayout.vue index f41277c46..dfbed603b 100644 --- a/client/src/components/AppLayout.vue +++ b/client/src/components/AppLayout.vue @@ -13,6 +13,7 @@ const themeStore = useThemeStore() const backgroundClass = computed(() => BACKGROUND_OPTIONS.find((b) => b.id === themeStore.background)?.cssClass ?? '') const BOOK_ROUTE_NAMES = new Set(['book-detail']) +const TABLE_VIEW_ROUTE_NAMES = new Set(['library', 'smartScope', 'collection', 'series-detail']) const viewKey = computed(() => { const name = String(route.name) if (BOOK_ROUTE_NAMES.has(name)) return name @@ -33,7 +34,13 @@ const viewKey = computed(() => {
Scope
+Format
+Columns
+Options
+Preflight
+Scope: {{ scopeSummary }}
+
+
+ Estimated size: + {{ (preflight.estimatedBytes / 1024 / 1024).toFixed(2) }} MB + ({{ preflight.sizeCategory }}) +
+File: {{ preflight.fileName }}
+ +{{ preflightError }}
+Save sort, layout, and view-specific filters for quick reuse.
+| + + | +
+
+
+
+
+
+
+
+ |
+ |
| + | ||
|
+
+
+
+ |
+
+
+
+
+ |
+
+
+
+
+
+ |
+
+
| + |
No books to display
+{{ error }}
diff --git a/client/src/features/book/components/detail/tabs/CoverSearchDrawer.vue b/client/src/features/book/components/detail/tabs/CoverSearchDrawer.vue index 1fef4a80c..2679f1eb3 100644 --- a/client/src/features/book/components/detail/tabs/CoverSearchDrawer.vue +++ b/client/src/features/book/components/detail/tabs/CoverSearchDrawer.vue @@ -1,16 +1,18 @@ -
Selection |
+ Title |
+ Authors |
+
|---|---|---|
| checkbox | +A very long book title | +Short | +