diff --git a/next-ui/eslint.config.ts b/next-ui/eslint.config.ts index c2101a33..64ca56d8 100644 --- a/next-ui/eslint.config.ts +++ b/next-ui/eslint.config.ts @@ -64,7 +64,7 @@ export default defineConfigWithVueTs( 'error', { idInterpolationPattern: '[sha512:contenthash:base64:6]', - idWhitelist: ['app.*'], + idWhitelist: ['app.*', 'enum.*'], }, ], }, diff --git a/next-ui/src/api/images.ts b/next-ui/src/api/images.ts new file mode 100644 index 00000000..d468b0bf --- /dev/null +++ b/next-ui/src/api/images.ts @@ -0,0 +1,4 @@ +export function pageHashKnownThumbnailUrl(hash?: string): string | undefined { + if (hash) return `${import.meta.env.VITE_KOMGA_API_URL}/api/v1/page-hashes/${hash}/thumbnail` + return undefined +} diff --git a/next-ui/src/assets/cover.svg b/next-ui/src/assets/cover.svg new file mode 100644 index 00000000..893a6ed5 --- /dev/null +++ b/next-ui/src/assets/cover.svg @@ -0,0 +1,144 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/next-ui/src/colada/books.ts b/next-ui/src/colada/books.ts new file mode 100644 index 00000000..60fe3691 --- /dev/null +++ b/next-ui/src/colada/books.ts @@ -0,0 +1,22 @@ +import { defineQueryOptions } from '@pinia/colada' +import { komgaClient } from '@/api/komga-client' + +export const QUERY_KEYS_BOOKS = { + root: ['books'] as const, + byId: (bookId: string) => [...QUERY_KEYS_BOOKS.root, bookId] as const, +} + +export const bookDetailQuery = defineQueryOptions(({ bookId }: { bookId: string }) => ({ + key: QUERY_KEYS_BOOKS.byId(bookId), + query: () => + komgaClient + .GET('/api/v1/books/{bookId}', { + params: { + path: { + bookId: bookId, + }, + }, + }) + // unwrap the openapi-fetch structure on success + .then((res) => res.data), +})) diff --git a/next-ui/src/colada/history.ts b/next-ui/src/colada/history.ts new file mode 100644 index 00000000..ed47502d --- /dev/null +++ b/next-ui/src/colada/history.ts @@ -0,0 +1,18 @@ +import { defineQueryOptions } from '@pinia/colada' +import { komgaClient } from '@/api/komga-client' + +export const historyQuery = defineQueryOptions( + ({ page, size, sort }: { page?: number; size?: number; sort?: string[] }) => ({ + key: ['history', { page: page, size: size, sort: sort }], + query: () => + komgaClient + .GET('/api/v1/history', { + params: { + query: { page: page, size: size, sort: sort }, + }, + }) + // unwrap the openapi-fetch structure on success + .then((res) => res.data), + placeholderData: (previousData: any) => previousData, // eslint-disable-line @typescript-eslint/no-explicit-any + }), +) diff --git a/next-ui/src/colada/series.ts b/next-ui/src/colada/series.ts new file mode 100644 index 00000000..548cb1dd --- /dev/null +++ b/next-ui/src/colada/series.ts @@ -0,0 +1,22 @@ +import { defineQueryOptions } from '@pinia/colada' +import { komgaClient } from '@/api/komga-client' + +export const QUERY_KEYS_SERIES = { + root: ['series'] as const, + byId: (seriesId: string) => [...QUERY_KEYS_SERIES.root, seriesId] as const, +} + +export const seriesDetailQuery = defineQueryOptions(({ seriesId }: { seriesId: string }) => ({ + key: QUERY_KEYS_SERIES.byId(seriesId), + query: () => + komgaClient + .GET('/api/v1/series/{seriesId}', { + params: { + path: { + seriesId: seriesId, + }, + }, + }) + // unwrap the openapi-fetch structure on success + .then((res) => res.data), +})) diff --git a/next-ui/src/components.d.ts b/next-ui/src/components.d.ts index bb6b997f..46090f6f 100644 --- a/next-ui/src/components.d.ts +++ b/next-ui/src/components.d.ts @@ -22,6 +22,15 @@ declare module 'vue' { FragmentBuildVersion: typeof import('./fragments/fragment/BuildVersion.vue')['default'] FragmentDialogConfirm: typeof import('./fragments/fragment/dialog/Confirm.vue')['default'] FragmentDialogConfirmEdit: typeof import('./fragments/fragment/dialog/ConfirmEdit.vue')['default'] + FragmentHistoryBookFileDeleted: typeof import('./fragments/fragment/history/BookFileDeleted.vue')['default'] + FragmentHistoryExpandBookConverted: typeof import('./fragments/fragment/history/expand/BookConverted.vue')['default'] + FragmentHistoryExpandBookFileDeleted: typeof import('./fragments/fragment/history/expand/BookFileDeleted.vue')['default'] + FragmentHistoryExpandBookImported: typeof import('./fragments/fragment/history/expand/BookImported.vue')['default'] + FragmentHistoryExpandDuplicatePageDeleted: typeof import('./fragments/fragment/history/expand/DuplicatePageDeleted.vue')['default'] + FragmentHistoryExpandFileDeleted: typeof import('./fragments/fragment/history/ExpandFileDeleted.vue')['default'] + FragmentHistoryExpandSeriesFolderDeleted: typeof import('./fragments/fragment/history/expand/SeriesFolderDeleted.vue')['default'] + FragmentHistoryExpandTable: typeof import('./fragments/fragment/history/expand/Table.vue')['default'] + FragmentHistoryTable: typeof import('./fragments/fragment/history/Table.vue')['default'] FragmentLocaleSelector: typeof import('./fragments/fragment/LocaleSelector.vue')['default'] FragmentSnackQueue: typeof import('./fragments/fragment/SnackQueue.vue')['default'] FragmentThemeSelector: typeof import('./fragments/fragment/ThemeSelector.vue')['default'] diff --git a/next-ui/src/fragments/fragment/apikey/Table.mdx b/next-ui/src/fragments/fragment/apikey/Table.mdx index 2a2d1cd7..f83a1847 100644 --- a/next-ui/src/fragments/fragment/apikey/Table.mdx +++ b/next-ui/src/fragments/fragment/apikey/Table.mdx @@ -4,8 +4,8 @@ import * as Stories from './Table.stories'; -# ApikeyTable +# FragmentApikeyTable -Table showing API keys. +[Server DataTable](https://vuetifyjs.com/en/components/data-tables/server-side-tables/) showing API keys. diff --git a/next-ui/src/fragments/fragment/history/Table.mdx b/next-ui/src/fragments/fragment/history/Table.mdx new file mode 100644 index 00000000..7f693e82 --- /dev/null +++ b/next-ui/src/fragments/fragment/history/Table.mdx @@ -0,0 +1,13 @@ +import { Canvas, Meta } from '@storybook/addon-docs/blocks'; + +import * as Stories from './Table.stories'; + + + +# FragmentHistoryTable + +[Server DataTable](https://vuetifyjs.com/en/components/data-tables/server-side-tables/) showing historical events: +- rows can be expanded to display event details +- Series and Book column will show the actual series/book name if it exists + + diff --git a/next-ui/src/fragments/fragment/history/Table.stories.ts b/next-ui/src/fragments/fragment/history/Table.stories.ts new file mode 100644 index 00000000..44406d85 --- /dev/null +++ b/next-ui/src/fragments/fragment/history/Table.stories.ts @@ -0,0 +1,58 @@ +import type { Meta, StoryObj } from '@storybook/vue3-vite' + +import Table from './Table.vue' +import { delay, http } from 'msw' +import { response401Unauthorized } from '@/mocks/api/handlers' +import { httpTyped } from '@/mocks/api/httpTyped' +import { mockPage } from '@/mocks/api/pageable' +import { PageRequest } from '@/types/PageRequest' + +const meta = { + component: Table, + render: (args: object) => ({ + components: { Table }, + setup() { + return { args } + }, + template: '', + }), + parameters: { + // More on how to position stories at: https://storybook.js.org/docs/configure/story-layout + }, + args: {}, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Default: Story = { + args: {}, +} + +export const Loading: Story = { + parameters: { + msw: { + handlers: [http.all('*', async () => await delay(5_000))], + }, + }, +} + +export const NoData: Story = { + parameters: { + msw: { + handlers: [ + httpTyped.get('/api/v1/history', ({ response }) => + response(200).json(mockPage([], new PageRequest())), + ), + ], + }, + }, +} + +export const Error: Story = { + parameters: { + msw: { + handlers: [http.all('*', response401Unauthorized)], + }, + }, +} diff --git a/next-ui/src/fragments/fragment/history/Table.vue b/next-ui/src/fragments/fragment/history/Table.vue new file mode 100644 index 00000000..9700201a --- /dev/null +++ b/next-ui/src/fragments/fragment/history/Table.vue @@ -0,0 +1,232 @@ + + + diff --git a/next-ui/src/fragments/fragment/history/expand/BookConverted.stories.ts b/next-ui/src/fragments/fragment/history/expand/BookConverted.stories.ts new file mode 100644 index 00000000..f57cec86 --- /dev/null +++ b/next-ui/src/fragments/fragment/history/expand/BookConverted.stories.ts @@ -0,0 +1,28 @@ +import type { Meta, StoryObj } from '@storybook/vue3-vite' + +import BookConverted from './BookConverted.vue' +import { historyBookConverted } from '@/mocks/api/handlers/history' + +const meta = { + component: BookConverted, + render: (args: object) => ({ + components: { BookConverted }, + setup() { + return { args } + }, + template: '', + }), + parameters: { + // More on how to position stories at: https://storybook.js.org/docs/configure/story-layout + }, + args: {}, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Default: Story = { + args: { + event: historyBookConverted, + }, +} diff --git a/next-ui/src/fragments/fragment/history/expand/BookConverted.vue b/next-ui/src/fragments/fragment/history/expand/BookConverted.vue new file mode 100644 index 00000000..f754c9ad --- /dev/null +++ b/next-ui/src/fragments/fragment/history/expand/BookConverted.vue @@ -0,0 +1,16 @@ + + + diff --git a/next-ui/src/fragments/fragment/history/expand/BookFileDeleted.stories.ts b/next-ui/src/fragments/fragment/history/expand/BookFileDeleted.stories.ts new file mode 100644 index 00000000..58cb5b54 --- /dev/null +++ b/next-ui/src/fragments/fragment/history/expand/BookFileDeleted.stories.ts @@ -0,0 +1,28 @@ +import type { Meta, StoryObj } from '@storybook/vue3-vite' + +import BookFileDeleted from './BookFileDeleted.vue' +import { historyBookFileDeleted } from '@/mocks/api/handlers/history' + +const meta = { + component: BookFileDeleted, + render: (args: object) => ({ + components: { BookFileDeleted }, + setup() { + return { args } + }, + template: '', + }), + parameters: { + // More on how to position stories at: https://storybook.js.org/docs/configure/story-layout + }, + args: {}, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Default: Story = { + args: { + event: historyBookFileDeleted, + }, +} diff --git a/next-ui/src/fragments/fragment/history/expand/BookFileDeleted.vue b/next-ui/src/fragments/fragment/history/expand/BookFileDeleted.vue new file mode 100644 index 00000000..cecd3a1f --- /dev/null +++ b/next-ui/src/fragments/fragment/history/expand/BookFileDeleted.vue @@ -0,0 +1,16 @@ + + + diff --git a/next-ui/src/fragments/fragment/history/expand/BookImported.stories.ts b/next-ui/src/fragments/fragment/history/expand/BookImported.stories.ts new file mode 100644 index 00000000..00acc49e --- /dev/null +++ b/next-ui/src/fragments/fragment/history/expand/BookImported.stories.ts @@ -0,0 +1,28 @@ +import type { Meta, StoryObj } from '@storybook/vue3-vite' + +import BookImported from './BookImported.vue' +import { historyBookImported } from '@/mocks/api/handlers/history' + +const meta = { + component: BookImported, + render: (args: object) => ({ + components: { BookImported }, + setup() { + return { args } + }, + template: '', + }), + parameters: { + // More on how to position stories at: https://storybook.js.org/docs/configure/story-layout + }, + args: {}, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Default: Story = { + args: { + event: historyBookImported, + }, +} diff --git a/next-ui/src/fragments/fragment/history/expand/BookImported.vue b/next-ui/src/fragments/fragment/history/expand/BookImported.vue new file mode 100644 index 00000000..d1295540 --- /dev/null +++ b/next-ui/src/fragments/fragment/history/expand/BookImported.vue @@ -0,0 +1,17 @@ + + + diff --git a/next-ui/src/fragments/fragment/history/expand/DuplicatePageDeleted.stories.ts b/next-ui/src/fragments/fragment/history/expand/DuplicatePageDeleted.stories.ts new file mode 100644 index 00000000..c805845b --- /dev/null +++ b/next-ui/src/fragments/fragment/history/expand/DuplicatePageDeleted.stories.ts @@ -0,0 +1,28 @@ +import type { Meta, StoryObj } from '@storybook/vue3-vite' + +import DuplicatePageDeleted from './DuplicatePageDeleted.vue' +import { historyDuplicatePageDeleted } from '@/mocks/api/handlers/history' + +const meta = { + component: DuplicatePageDeleted, + render: (args: object) => ({ + components: { DuplicatePageDeleted }, + setup() { + return { args } + }, + template: '', + }), + parameters: { + // More on how to position stories at: https://storybook.js.org/docs/configure/story-layout + }, + args: {}, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Default: Story = { + args: { + event: historyDuplicatePageDeleted, + }, +} diff --git a/next-ui/src/fragments/fragment/history/expand/DuplicatePageDeleted.vue b/next-ui/src/fragments/fragment/history/expand/DuplicatePageDeleted.vue new file mode 100644 index 00000000..1abbe082 --- /dev/null +++ b/next-ui/src/fragments/fragment/history/expand/DuplicatePageDeleted.vue @@ -0,0 +1,72 @@ + + + diff --git a/next-ui/src/fragments/fragment/history/expand/SeriesFolderDeleted.stories.ts b/next-ui/src/fragments/fragment/history/expand/SeriesFolderDeleted.stories.ts new file mode 100644 index 00000000..caccb46a --- /dev/null +++ b/next-ui/src/fragments/fragment/history/expand/SeriesFolderDeleted.stories.ts @@ -0,0 +1,28 @@ +import type { Meta, StoryObj } from '@storybook/vue3-vite' + +import SeriesFolderDeleted from './SeriesFolderDeleted.vue' +import { historySeriesFolderDeleted } from '@/mocks/api/handlers/history' + +const meta = { + component: SeriesFolderDeleted, + render: (args: object) => ({ + components: { SeriesFolderDeleted }, + setup() { + return { args } + }, + template: '', + }), + parameters: { + // More on how to position stories at: https://storybook.js.org/docs/configure/story-layout + }, + args: {}, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Default: Story = { + args: { + event: historySeriesFolderDeleted, + }, +} diff --git a/next-ui/src/fragments/fragment/history/expand/SeriesFolderDeleted.vue b/next-ui/src/fragments/fragment/history/expand/SeriesFolderDeleted.vue new file mode 100644 index 00000000..088600d1 --- /dev/null +++ b/next-ui/src/fragments/fragment/history/expand/SeriesFolderDeleted.vue @@ -0,0 +1,16 @@ + + + diff --git a/next-ui/src/fragments/fragment/history/expand/Table.vue b/next-ui/src/fragments/fragment/history/expand/Table.vue new file mode 100644 index 00000000..f97970b5 --- /dev/null +++ b/next-ui/src/fragments/fragment/history/expand/Table.vue @@ -0,0 +1,24 @@ + + + diff --git a/next-ui/src/generated/openapi/komga.d.ts b/next-ui/src/generated/openapi/komga.d.ts index b853c692..73fd061b 100644 --- a/next-ui/src/generated/openapi/komga.d.ts +++ b/next-ui/src/generated/openapi/komga.d.ts @@ -3814,6 +3814,10 @@ export interface components { SettingsUpdateDto: { deleteEmptyCollections?: boolean; deleteEmptyReadLists?: boolean; + /** + * @deprecated + * @description Will be removed in a future version + */ kepubifyPath?: string; /** Format: int32 */ koboPort?: number; @@ -4641,6 +4645,13 @@ export interface operations { "*/*": components["schemas"]["ValidationErrorResponse"]; }; }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; }; }; bookAnalyze: { @@ -8133,6 +8144,13 @@ export interface operations { "*/*": components["schemas"]["ValidationErrorResponse"]; }; }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; }; }; seriesAnalyze: { diff --git a/next-ui/src/mocks/api/handlers.ts b/next-ui/src/mocks/api/handlers.ts index 836750ad..62c07dcf 100644 --- a/next-ui/src/mocks/api/handlers.ts +++ b/next-ui/src/mocks/api/handlers.ts @@ -7,18 +7,26 @@ import { referentialHandlers } from '@/mocks/api/handlers/referential' import { usersHandlers } from '@/mocks/api/handlers/users' import { settingsHandlers } from '@/mocks/api/handlers/settings' import { claimHandlers } from '@/mocks/api/handlers/claim' +import { historyHandlers } from '@/mocks/api/handlers/history' +import { seriesHandlers } from '@/mocks/api/handlers/series' +import { booksHandlers } from '@/mocks/api/handlers/books' export const handlers = [ ...actuatorHandlers, ...announcementHandlers, + ...booksHandlers, ...claimHandlers, + ...historyHandlers, ...librariesHandlers, ...referentialHandlers, ...releasesHandlers, + ...seriesHandlers, ...settingsHandlers, ...usersHandlers, ] +export const response404NotFound = () => HttpResponse.json({ error: 'NotFound' }, { status: 404 }) + export const response401Unauthorized = () => HttpResponse.json({ error: 'Unauthorized' }, { status: 401 }) diff --git a/next-ui/src/mocks/api/handlers/books.ts b/next-ui/src/mocks/api/handlers/books.ts new file mode 100644 index 00000000..7f4a2f3e --- /dev/null +++ b/next-ui/src/mocks/api/handlers/books.ts @@ -0,0 +1,67 @@ +import { httpTyped } from '@/mocks/api/httpTyped' + +const book = { + id: '05RKH8CC8B4RW', + seriesId: '57', + seriesTitle: 'Super Duck', + libraryId: '56', + name: 'Super_Duck_001__MLJ___Fall_1944___c2c___titansfan_editor_', + url: '/books/Super Duck/Super_Duck_001__MLJ___Fall_1944___c2c___titansfan_editor_.cbz', + number: 1, + created: new Date('2021-07-27T13:33:52Z'), + lastModified: new Date('2023-07-22T11:14:47Z'), + fileLastModified: new Date('2020-03-05T03:20:12Z'), + sizeBytes: 82920938, + size: '79.1 MiB', + media: { + status: 'READY', + mediaType: 'application/zip', + pagesCount: 53, + comment: '', + epubDivinaCompatible: false, + epubIsKepub: false, + mediaProfile: 'DIVINA', + }, + metadata: { + title: 'Super Duck 001', + titleLock: true, + summary: '', + summaryLock: false, + number: '1', + numberLock: false, + numberSort: 1.0, + numberSortLock: false, + releaseDateLock: false, + authors: [], + authorsLock: true, + tags: [], + tagsLock: false, + isbn: '', + isbnLock: false, + links: [], + linksLock: false, + created: new Date('2021-07-27T13:33:52Z'), + lastModified: new Date('2023-03-15T09:50:54Z'), + }, + readProgress: { + page: 53, + completed: true, + readDate: new Date('2025-02-19T11:29:25Z'), + created: new Date('2025-02-19T11:29:25Z'), + lastModified: new Date('2025-02-19T11:29:25Z'), + deviceId: '', + deviceName: '', + }, + deleted: false, + fileHash: '7dc12ae431a8847b7f49918745254b0b', + oneshot: false, +} + +export const booksHandlers = [ + httpTyped.get('/api/v1/books/{bookId}', ({ params, response }) => { + if (params.bookId === '404') return response(404).empty() + return response(200).json( + Object.assign({}, book, { metadata: { title: `Book ${params.bookId}` } }), + ) + }), +] diff --git a/next-ui/src/mocks/api/handlers/history.ts b/next-ui/src/mocks/api/handlers/history.ts new file mode 100644 index 00000000..b817c154 --- /dev/null +++ b/next-ui/src/mocks/api/handlers/history.ts @@ -0,0 +1,118 @@ +import { httpTyped } from '@/mocks/api/httpTyped' +import { mockPage } from '@/mocks/api/pageable' +import { PageRequest } from '@/types/PageRequest' +import { http, HttpResponse } from 'msw' +import logoUrl from '@/assets/logo.svg' + +export const historyBookImported = { + type: 'BookImported', + timestamp: new Date('2025-08-07T09:36:22.596'), + bookId: 'B5', + seriesId: 'S1', + properties: { + name: '/data/Books/Donjon Parade/Donjon Parade - 09 - Nécomancien pour de faux.cbz', + source: '/data/Import/Donjon Parade - 9 - Nécomancien pour de faux (Sfar-Trandheim-Delaf).cbz', + upgrade: 'No', + }, +} + +export const historySeriesFolderDeleted = { + type: 'SeriesFolderDeleted', + timestamp: new Date('2025-08-05T21:33:56.452'), + seriesId: '404', + properties: { + name: '/data/Books/One Piece', + reason: 'Folder was deleted because it was empty', + }, +} + +export const historyBookFileDeleted = { + type: 'BookFileDeleted', + timestamp: new Date('2025-08-05T21:33:56.445'), + bookId: '404', + seriesId: 'S2', + properties: { + name: '/data/Books/One Piece/Volume 1.cbz', + reason: 'File was deleted by user request', + }, +} + +export const historyBookImportedForUpgrade = { + type: 'BookImported', + timestamp: new Date('2025-07-28T17:52:14.126'), + bookId: 'B15', + seriesId: 'S3', + properties: { + name: '/data/Lastman/Lastman - T12.cbz', + source: '/data/Import/Lastman/Lastman_-_Nouvelle_Édition_T12.cbz', + upgrade: 'Yes', + }, +} + +export const historyBookFileDeletedForUpgrade = { + type: 'BookFileDeleted', + timestamp: new Date('2025-07-28T17:52:14.016'), + bookId: 'b14', + seriesId: 'S3', + properties: { + name: '/data/Lastman/Lastman - 12.cbz', + reason: 'File was deleted to import an upgrade', + }, +} + +export const historyDuplicatePageDeleted = { + type: 'DuplicatePageDeleted', + timestamp: new Date('2025-07-26T21:33:18.809'), + bookId: 'B5', + seriesId: 'S2', + properties: { + name: '/data/Lastman/Volume 6.cbz', + 'page file hash': 'e3f8f3814609645d6ffc6f6f2c4c65aa', + 'page file name': 'credits.jpg', + 'page file size': '1204289', + 'page media type': 'image/jpeg', + 'page number': '34', + }, +} + +export const historyBookConverted = { + type: 'BookConverted', + timestamp: new Date('2025-07-07T17:33:39.545'), + bookId: 'B25', + seriesId: 'S21', + properties: { + 'former file': '/data/Usagi Yojimbo/Usagi Yojimbo (Book 40) - The Crow (2025).cbr', + name: '/data/Usagi Yojimbo/Usagi Yojimbo (Book 40) - The Crow (2025).cbz', + }, +} + +const history = [ + historyBookImported, + historySeriesFolderDeleted, + historyBookFileDeleted, + historyBookFileDeletedForUpgrade, + historyBookImportedForUpgrade, + historyDuplicatePageDeleted, + historyBookConverted, +] + +export const historyHandlers = [ + httpTyped.get('/api/v1/history', ({ query, response }) => + response(200).json( + mockPage( + history, + new PageRequest(Number(query.get('page')), Number(query.get('size')), query.getAll('sort')), + ), + ), + ), + http.get('*/api/v1/page-hashes/*/thumbnail', async () => { + // Get an ArrayBuffer from reading the file from disk or fetching it. + const buffer = await fetch(logoUrl).then((response) => response.arrayBuffer()) + + return HttpResponse.arrayBuffer(buffer, { + headers: { + 'content-type': 'image/svg+xml', + }, + }) + }), +] diff --git a/next-ui/src/mocks/api/handlers/series.ts b/next-ui/src/mocks/api/handlers/series.ts new file mode 100644 index 00000000..99881722 --- /dev/null +++ b/next-ui/src/mocks/api/handlers/series.ts @@ -0,0 +1,66 @@ +import { httpTyped } from '@/mocks/api/httpTyped' + +const series = { + id: '57', + libraryId: '56', + name: 'Super Duck', + url: '/books/Super Duck', + created: new Date('2020-07-05T12:11:50Z'), + lastModified: new Date('2021-07-27T13:33:54Z'), + fileLastModified: new Date('2020-03-05T15:24:59Z'), + booksCount: 5, + booksReadCount: 1, + booksUnreadCount: 3, + booksInProgressCount: 1, + metadata: { + status: 'ENDED', + statusLock: true, + title: 'Super Duck', + titleLock: false, + titleSort: 'Super Duck', + titleSortLock: false, + summary: + 'Super Duck is the greatest hero of New Duck City. Brash, arrogant and virtually unbeatable, he’s defeated all threats to the city and routinely foils the schemes of his greatest rival, criminal genius and corporate billionaire Dapper Duck.\n\nHowever, when Dapper takes to the streets with a giant mechanical monster, will Super Duck prove once more to be the heroic champion everyone knows and loves or is his goose finally cooked?', + summaryLock: true, + readingDirection: 'LEFT_TO_RIGHT', + readingDirectionLock: true, + publisher: 'Archie', + publisherLock: true, + ageRatingLock: false, + language: 'en', + languageLock: true, + genres: ['humor'], + genresLock: true, + tags: [], + tagsLock: false, + totalBookCount: 94, + totalBookCountLock: true, + sharingLabels: [], + sharingLabelsLock: false, + links: [], + linksLock: false, + alternateTitles: [], + alternateTitlesLock: false, + created: new Date('2020-07-05T12:11:50Z'), + lastModified: new Date('2023-07-22T11:14:45Z'), + }, + booksMetadata: { + authors: [], + tags: [], + summary: '', + summaryNumber: '', + created: new Date('2021-01-11T09:59:23Z'), + lastModified: new Date('2023-07-22T11:14:45Z'), + }, + deleted: false, + oneshot: false, +} + +export const seriesHandlers = [ + httpTyped.get('/api/v1/series/{seriesId}', ({ params, response }) => { + if (params.seriesId === '404') return response(404).empty() + return response(200).json( + Object.assign({}, series, { metadata: { title: `Series ${params.seriesId}` } }), + ) + }), +] diff --git a/next-ui/src/pages/history.vue b/next-ui/src/pages/history.vue index 256e54e5..755202f1 100644 --- a/next-ui/src/pages/history.vue +++ b/next-ui/src/pages/history.vue @@ -1,10 +1,13 @@ - + meta: diff --git a/next-ui/src/utils/i18n/enum/historical-event.ts b/next-ui/src/utils/i18n/enum/historical-event.ts new file mode 100644 index 00000000..58ad873f --- /dev/null +++ b/next-ui/src/utils/i18n/enum/historical-event.ts @@ -0,0 +1,30 @@ +import { defineMessage, type MessageDescriptor } from 'vue-intl' + +// the keys are the event names for easier lookup +export const historicalEventMessages: Record = { + BookFileDeleted: defineMessage({ + description: 'Historical event: BookFileDeleted', + defaultMessage: 'Book File Deleted', + id: 'enum.historicalEvent.BookFileDeleted', + }), + SeriesFolderDeleted: defineMessage({ + description: 'Historical event: SeriesFolderDeleted', + defaultMessage: 'Series Folder Deleted', + id: 'enum.historicalEvent.SeriesFolderDeleted', + }), + DuplicatePageDeleted: defineMessage({ + description: 'Historical event: DuplicatePageDeleted', + defaultMessage: 'Duplicate Page Deleted', + id: 'enum.historicalEvent.DuplicatePageDeleted', + }), + BookConverted: defineMessage({ + description: 'Historical event: ', + defaultMessage: 'Book Converted', + id: 'enum.historicalEvent.BookConverted', + }), + BookImported: defineMessage({ + description: 'Historical event: BookImported', + defaultMessage: 'Book Imported', + id: 'enum.historicalEvent.BookImported', + }), +}