From ad421020fb2c130a772358608f2df68e36920329 Mon Sep 17 00:00:00 2001 From: Gauthier Roebroeck Date: Wed, 1 Apr 2026 14:34:56 +0800 Subject: [PATCH] browse books --- next-ui/src/components.d.ts | 5 +- next-ui/src/components/ChipCount.vue | 18 ++ next-ui/src/components/filter/ChipCount.vue | 27 ++ .../components/{ => filter}/FilterButton.vue | 0 next-ui/src/components/item/Browser.vue | 66 ++++ next-ui/src/composables/filters.ts | 37 +++ next-ui/src/functions/filter.ts | 11 + next-ui/src/pages/libraries/[id]/books.vue | 289 +++++++++++++++++- next-ui/src/pages/libraries/[id]/series.vue | 207 ++++--------- next-ui/src/stores/selection.ts | 3 +- next-ui/src/types/sort.ts | 127 +++++++- 11 files changed, 629 insertions(+), 161 deletions(-) create mode 100644 next-ui/src/components/ChipCount.vue create mode 100644 next-ui/src/components/filter/ChipCount.vue rename next-ui/src/components/{ => filter}/FilterButton.vue (100%) create mode 100644 next-ui/src/components/item/Browser.vue create mode 100644 next-ui/src/composables/filters.ts diff --git a/next-ui/src/components.d.ts b/next-ui/src/components.d.ts index 84380674..de0c575d 100644 --- a/next-ui/src/components.d.ts +++ b/next-ui/src/components.d.ts @@ -23,6 +23,7 @@ declare module 'vue' { BookMenuBottomSheet: typeof import('./components/book/menu/BookMenuBottomSheet.vue')['default'] BuildCommit: typeof import('./components/BuildCommit.vue')['default'] BuildVersion: typeof import('./components/BuildVersion.vue')['default'] + ChipCount: typeof import('./components/ChipCount.vue')['default'] DialogBookPicker: typeof import('./components/dialog/BookPicker.vue')['default'] DialogConfirm: typeof import('./components/dialog/Confirm.vue')['default'] DialogConfirmEdit: typeof import('./components/dialog/ConfirmEdit.vue')['default'] @@ -35,7 +36,7 @@ declare module 'vue' { EmptyStateConstruction: typeof import('./components/EmptyStateConstruction.vue')['default'] EmptyStateNetworkError: typeof import('./components/EmptyStateNetworkError.vue')['default'] FilterAnyAll: typeof import('./components/filter/AnyAll.vue')['default'] - FilterButton: typeof import('./components/FilterButton.vue')['default'] + FilterButton: typeof import('./components/filter/FilterButton.vue')['default'] FilterByAgeRating: typeof import('./components/filter/by/AgeRating.vue')['default'] FilterByAuthor: typeof import('./components/filter/by/Author.vue')['default'] FilterByComplete: typeof import('./components/filter/by/Complete.vue')['default'] @@ -50,6 +51,7 @@ declare module 'vue' { FilterBySharingLabel: typeof import('./components/filter/by/SharingLabel.vue')['default'] FilterByTag: typeof import('./components/filter/by/Tag.vue')['default'] FilterByUnavailable: typeof import('./components/filter/by/Unavailable.vue')['default'] + FilterChipCount: typeof import('./components/filter/ChipCount.vue')['default'] FilterExpansionPanel: typeof import('./components/filter/ExpansionPanel.vue')['default'] FilterIncludeExclude: typeof import('./components/filter/IncludeExclude.vue')['default'] FilterList: typeof import('./components/filter/List.vue')['default'] @@ -68,6 +70,7 @@ declare module 'vue' { ImportBooksDirectorySelection: typeof import('./components/import/books/DirectorySelection.vue')['default'] ImportBooksTransientBooksTable: typeof import('./components/import/books/TransientBooksTable.vue')['default'] ImportReadlistTable: typeof import('./components/import/readlist/Table.vue')['default'] + ItemBrowser: typeof import('./components/item/Browser.vue')['default'] ItemCard: typeof import('./components/item/card/ItemCard.vue')['default'] ItemCardWide: typeof import('./components/item/CardWide/ItemCardWide.vue')['default'] LayoutAppBar: typeof import('./components/layout/app/Bar.vue')['default'] diff --git a/next-ui/src/components/ChipCount.vue b/next-ui/src/components/ChipCount.vue new file mode 100644 index 00000000..cf00d3d4 --- /dev/null +++ b/next-ui/src/components/ChipCount.vue @@ -0,0 +1,18 @@ + + + + + + + diff --git a/next-ui/src/components/filter/ChipCount.vue b/next-ui/src/components/filter/ChipCount.vue new file mode 100644 index 00000000..e8bc0ade --- /dev/null +++ b/next-ui/src/components/filter/ChipCount.vue @@ -0,0 +1,27 @@ + + + + + + + diff --git a/next-ui/src/components/FilterButton.vue b/next-ui/src/components/filter/FilterButton.vue similarity index 100% rename from next-ui/src/components/FilterButton.vue rename to next-ui/src/components/filter/FilterButton.vue diff --git a/next-ui/src/components/item/Browser.vue b/next-ui/src/components/item/Browser.vue new file mode 100644 index 00000000..44bf31f4 --- /dev/null +++ b/next-ui/src/components/item/Browser.vue @@ -0,0 +1,66 @@ + + + diff --git a/next-ui/src/composables/filters.ts b/next-ui/src/composables/filters.ts new file mode 100644 index 00000000..5863d924 --- /dev/null +++ b/next-ui/src/composables/filters.ts @@ -0,0 +1,37 @@ +import * as v from 'valibot' +import { SchemaFilterAuthors } from '@/types/filter' +import { useRouteQuerySchema } from '@/composables/useRouteQuerySchema' +import { authorRoles } from '@/types/referential' +import { useIntl } from 'vue-intl' + +export function useFilterAuthors() { + const intl = useIntl() + + const filterAuthors = reactive< + Record< + string, + { filter: v.InferOutput; text: string; role?: string } + > + >({}) + + filterAuthors['anyrole'] = { + filter: useRouteQuerySchema('anyrole', SchemaFilterAuthors).data.value, + text: intl.formatMessage({ + description: 'Author filter: any role', + defaultMessage: 'All creators', + id: 'RmNasP', + }), + } + // TODO: get roles dynamically + Object.entries(authorRoles).forEach(([role, value]) => { + filterAuthors[role] = { + filter: useRouteQuerySchema(role, SchemaFilterAuthors).data.value, + text: intl.formatMessage(value), + role: role, + } + }) + + return { + filterAuthors: filterAuthors, + } +} diff --git a/next-ui/src/functions/filter.ts b/next-ui/src/functions/filter.ts index 1ceaa142..e4316f75 100644 --- a/next-ui/src/functions/filter.ts +++ b/next-ui/src/functions/filter.ts @@ -1,5 +1,7 @@ import { type FilterIncludeExclude, + type FilterType, + type FilterTypeSelectRange, SchemaAnyNone, SchemaFilterAuthors, SchemaFilterReadStatus, @@ -11,6 +13,15 @@ import { import type { InferOutput } from 'valibot' import * as v from 'valibot' +export function clearFilter(filter: FilterType | FilterTypeSelectRange | FilterIncludeExclude) { + if ('v' in filter) filter.v = [] + if ('m' in filter) filter.m = 'anyOf' + if ('is' in filter) filter.is = undefined + if ('min' in filter) filter.min = undefined + if ('max' in filter) filter.max = undefined + if ('i' in filter) filter.i = undefined +} + export function schemaFilterSeriesStatusToConditions( filter: InferOutput, ) { diff --git a/next-ui/src/pages/libraries/[id]/books.vue b/next-ui/src/pages/libraries/[id]/books.vue index 05191054..7b982001 100644 --- a/next-ui/src/pages/libraries/[id]/books.vue +++ b/next-ui/src/pages/libraries/[id]/books.vue @@ -1,10 +1,291 @@ -series.vue - + meta: diff --git a/next-ui/src/pages/libraries/[id]/series.vue b/next-ui/src/pages/libraries/[id]/series.vue index f6666002..a454f3da 100644 --- a/next-ui/src/pages/libraries/[id]/series.vue +++ b/next-ui/src/pages/libraries/[id]/series.vue @@ -1,5 +1,7 @@