diff --git a/komga-webui/src/components/PageHashMatchesTable.vue b/komga-webui/src/components/PageHashMatchesTable.vue new file mode 100644 index 000000000..53dfc4708 --- /dev/null +++ b/komga-webui/src/components/PageHashMatchesTable.vue @@ -0,0 +1,99 @@ + + + + {{ item.url }} + + + + + + + + + mdi-refresh + + + + + + diff --git a/komga-webui/src/functions/urls.ts b/komga-webui/src/functions/urls.ts index a54774c1e..51f0ea720 100644 --- a/komga-webui/src/functions/urls.ts +++ b/komga-webui/src/functions/urls.ts @@ -71,3 +71,11 @@ export function readListThumbnailUrlByThumbnailId(readListId: string, thumbnailI export function transientBookPageUrl(transientBookId: string, page: number): string { return `${urls.originNoSlash}/api/v1/transient-books/${transientBookId}/pages/${page}` } + +export function pageHashUnknownThumbnailUrl(hash: string, resize?: number): string { + let url = `${urls.originNoSlash}/api/v1/page-hashes/unknown/${hash}/thumbnail` + if(resize) { + url += `?resize=${resize}` + } + return url +} diff --git a/komga-webui/src/locales/en.json b/komga-webui/src/locales/en.json index 3e63dc2f1..d61549efa 100644 --- a/komga-webui/src/locales/en.json +++ b/komga-webui/src/locales/en.json @@ -202,6 +202,8 @@ "n_selected": "{count} selected", "nothing_to_show": "Nothing to show", "outdated": "Outdated", + "page": "Page", + "page_number": "Page number", "pages": "pages", "pages_n": "No pages | 1 page | {count} pages", "password": "Password", @@ -216,6 +218,7 @@ "tags": "Tags", "unavailable": "Unavailable", "unlock_all": "Unlock all", + "url": "URL", "use_filter_panel_to_change_filter": "Use the filter panel to change the active filter", "year": "year" }, @@ -524,6 +527,14 @@ "title_comparison": "Book Comparison" } }, + "duplicate_pages": { + "action_delete_auto": "Auto delete", + "action_delete_manual": "Manual delete", + "action_ignore": "Ignore", + "matches_n": "No matches | 1 match | {count} matches", + "title": "Duplicate pages", + "unknown_size": "Unknown size" + }, "duplicates": { "file_hash": "File hash", "size": "Size", diff --git a/komga-webui/src/main.ts b/komga-webui/src/main.ts index 64080bace..989028e4c 100644 --- a/komga-webui/src/main.ts +++ b/komga-webui/src/main.ts @@ -21,6 +21,7 @@ import komgaSse from './plugins/komga-sse.plugin' import komgaTasks from './plugins/komga-tasks.plugin' import komgaOauth2 from './plugins/komga-oauth2.plugin' import komgaLogin from './plugins/komga-login.plugin' +import komgaPageHashes from './plugins/komga-pagehashes.plugin' import vuetify from './plugins/vuetify' import logger from './plugins/logger.plugin' import './public-path' @@ -51,6 +52,7 @@ Vue.use(actuator, {http: Vue.prototype.$http}) Vue.use(komgaTasks, {http: Vue.prototype.$http}) Vue.use(komgaOauth2, {http: Vue.prototype.$http}) Vue.use(komgaLogin, {http: Vue.prototype.$http}) +Vue.use(komgaPageHashes, {http: Vue.prototype.$http}) Vue.config.productionTip = false diff --git a/komga-webui/src/plugins/komga-pagehashes.plugin.ts b/komga-webui/src/plugins/komga-pagehashes.plugin.ts new file mode 100644 index 000000000..5a17ea688 --- /dev/null +++ b/komga-webui/src/plugins/komga-pagehashes.plugin.ts @@ -0,0 +1,17 @@ +import {AxiosInstance} from 'axios' +import _Vue from 'vue' +import KomgaPageHashesService from '@/services/komga-pagehashes.service' + +export default { + install( + Vue: typeof _Vue, + {http}: { http: AxiosInstance }) { + Vue.prototype.$komgaPageHashes = new KomgaPageHashesService(http) + }, +} + +declare module 'vue/types/vue' { + interface Vue { + $komgaPageHashes: KomgaPageHashesService; + } +} diff --git a/komga-webui/src/router.ts b/komga-webui/src/router.ts index fe0089299..924d5cae0 100644 --- a/komga-webui/src/router.ts +++ b/komga-webui/src/router.ts @@ -94,6 +94,12 @@ const router = new Router({ beforeEnter: adminGuard, component: () => import(/* webpackChunkName: "settings-duplicates" */ './views/SettingsDuplicates.vue'), }, + { + path: '/settings/duplicate-pages', + name: 'settings-duplicate-pages', + beforeEnter: adminGuard, + component: () => import(/* webpackChunkName: "settings-duplicate-pages" */ './views/SettingsDuplicatePages.vue'), + }, { path: '/settings/server', name: 'settings-server', diff --git a/komga-webui/src/services/komga-pagehashes.service.ts b/komga-webui/src/services/komga-pagehashes.service.ts new file mode 100644 index 000000000..606368986 --- /dev/null +++ b/komga-webui/src/services/komga-pagehashes.service.ts @@ -0,0 +1,44 @@ +import {AxiosInstance} from 'axios' +import {PageHashMatchDto, PageHashUnknownDto} from '@/types/komga-pagehashes' + +const qs = require('qs') + +const API_PAGE_HASH = '/api/v1/page-hashes' + +export default class KomgaPageHashesService { + private http: AxiosInstance + + constructor(http: AxiosInstance) { + this.http = http + } + + async getUnknownHashes(pageRequest?: PageRequest): Promise> { + try { + return (await this.http.get(`${API_PAGE_HASH}/unknown`, { + params: pageRequest, + paramsSerializer: params => qs.stringify(params, {indices: false}), + })).data + } catch (e) { + let msg = 'An error occurred while trying to retrieve unknown page hashes' + if (e.response.data.message) { + msg += `: ${e.response.data.message}` + } + throw new Error(msg) + } + } + + async getUnknownPageHashMatches(hash: string, pageRequest?: PageRequest): Promise> { + try { + return (await this.http.get(`${API_PAGE_HASH}/unknown/${hash}`, { + params: pageRequest, + paramsSerializer: params => qs.stringify(params, {indices: false}), + })).data + } catch (e) { + let msg = `An error occurred while trying to retrieve matches for page hash: ${hash}` + if (e.response.data.message) { + msg += `: ${e.response.data.message}` + } + throw new Error(msg) + } + } +} diff --git a/komga-webui/src/types/komga-pagehashes.ts b/komga-webui/src/types/komga-pagehashes.ts new file mode 100644 index 000000000..51c30f6f8 --- /dev/null +++ b/komga-webui/src/types/komga-pagehashes.ts @@ -0,0 +1,13 @@ +export interface PageHashUnknownDto { + hash: string, + mediaType: string, + sizeBytes?: number, + size?: string, + matchCount: number, +} + +export interface PageHashMatchDto{ + bookId: string, + url: string, + pageNumber: number, +} diff --git a/komga-webui/src/views/SettingsDuplicatePages.vue b/komga-webui/src/views/SettingsDuplicatePages.vue new file mode 100644 index 000000000..ba4118057 --- /dev/null +++ b/komga-webui/src/views/SettingsDuplicatePages.vue @@ -0,0 +1,158 @@ + + + + + + + + + + + + + + + {{ element.mediaType }} + {{ element.size || $t('duplicate_pages.unknown_size') }} + + {{ $tc('duplicate_pages.matches_n', element.matchCount) }} + + + + + + + {{ $t('duplicate_pages.action_ignore') }} + {{ $t('duplicate_pages.action_delete_manual') }} + {{ $t('duplicate_pages.action_delete_auto') }} + + + + + + + + + + + + + + + + + + + + + {{ $t('common.close') }} + + + + + + + + + diff --git a/komga-webui/src/views/SettingsHolder.vue b/komga-webui/src/views/SettingsHolder.vue index 329603f44..23ee205af 100644 --- a/komga-webui/src/views/SettingsHolder.vue +++ b/komga-webui/src/views/SettingsHolder.vue @@ -11,6 +11,7 @@ {{ $t('duplicates.title') }} + {{ $t('duplicate_pages.title') }} {{ $t('users.users') }} {{ $t('server.tab_title') }}