mirror of
https://github.com/gotson/komga.git
synced 2025-12-06 08:32:25 +01:00
unknown page hashes
This commit is contained in:
parent
78428f137a
commit
376bce3324
7 changed files with 540 additions and 2 deletions
|
|
@ -19,3 +19,8 @@ export function pageHashKnownThumbnailUrl(hash?: string): string | undefined {
|
||||||
if (hash) return `${API_BASE_URL}/api/v1/page-hashes/${hash}/thumbnail`
|
if (hash) return `${API_BASE_URL}/api/v1/page-hashes/${hash}/thumbnail`
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function pageHashUnknownThumbnailUrl(hash?: string): string | undefined {
|
||||||
|
if (hash) return `${API_BASE_URL}/api/v1/page-hashes/unknown/${hash}/thumbnail`
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,10 @@ import { defineQueryOptions } from '@pinia/colada'
|
||||||
import { komgaClient } from '@/api/komga-client'
|
import { komgaClient } from '@/api/komga-client'
|
||||||
import type { PageHashAction } from '@/types/PageHashAction'
|
import type { PageHashAction } from '@/types/PageHashAction'
|
||||||
|
|
||||||
|
export const QUERY_KEYS_PAGE_HASHES = {
|
||||||
|
unknown: ['page-hashes-unknown'] as const,
|
||||||
|
}
|
||||||
|
|
||||||
export const pageHashesKnownQuery = defineQueryOptions(
|
export const pageHashesKnownQuery = defineQueryOptions(
|
||||||
({
|
({
|
||||||
actions,
|
actions,
|
||||||
|
|
@ -33,6 +37,26 @@ export const pageHashesKnownQuery = defineQueryOptions(
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
export const pageHashesUnknownQuery = defineQueryOptions(
|
||||||
|
({ page, size, sort }: { page?: number; size?: number; sort?: string[] }) => ({
|
||||||
|
key: [QUERY_KEYS_PAGE_HASHES.unknown, { page: page, size: size, sort: sort }],
|
||||||
|
query: () =>
|
||||||
|
komgaClient
|
||||||
|
.GET('/api/v1/page-hashes/unknown', {
|
||||||
|
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
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
export const pageHashMatchesQuery = defineQueryOptions(
|
export const pageHashMatchesQuery = defineQueryOptions(
|
||||||
({
|
({
|
||||||
pageHash,
|
pageHash,
|
||||||
|
|
|
||||||
1
next-ui/src/components.d.ts
vendored
1
next-ui/src/components.d.ts
vendored
|
|
@ -54,6 +54,7 @@ declare module 'vue' {
|
||||||
LocaleSelector: typeof import('./components/LocaleSelector.vue')['default']
|
LocaleSelector: typeof import('./components/LocaleSelector.vue')['default']
|
||||||
PageHashKnownTable: typeof import('./components/pageHash/KnownTable.vue')['default']
|
PageHashKnownTable: typeof import('./components/pageHash/KnownTable.vue')['default']
|
||||||
PageHashMatchTable: typeof import('./components/pageHash/MatchTable.vue')['default']
|
PageHashMatchTable: typeof import('./components/pageHash/MatchTable.vue')['default']
|
||||||
|
PageHashUnknownTable: typeof import('./components/pageHash/UnknownTable.vue')['default']
|
||||||
ReleaseCard: typeof import('./components/release/Card.vue')['default']
|
ReleaseCard: typeof import('./components/release/Card.vue')['default']
|
||||||
RemoteFileList: typeof import('./components/RemoteFileList.vue')['default']
|
RemoteFileList: typeof import('./components/RemoteFileList.vue')['default']
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
|
|
|
||||||
61
next-ui/src/components/pageHash/UnknownTable.stories.ts
Normal file
61
next-ui/src/components/pageHash/UnknownTable.stories.ts
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
import type { Meta, StoryObj } from '@storybook/vue3-vite'
|
||||||
|
|
||||||
|
import UnknownTable from './UnknownTable.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'
|
||||||
|
import DialogSimpleInstance from '@/components/dialog/DialogSimpleInstance.vue'
|
||||||
|
import SnackQueue from '@/components/SnackQueue.vue'
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
component: UnknownTable,
|
||||||
|
subcomponents: { SnackQueue },
|
||||||
|
render: (args: object) => ({
|
||||||
|
components: { UnknownTable, DialogSimpleInstance, SnackQueue },
|
||||||
|
setup() {
|
||||||
|
return { args }
|
||||||
|
},
|
||||||
|
template: '<UnknownTable v-bind="args" /><DialogSimpleInstance/><SnackQueue/>',
|
||||||
|
}),
|
||||||
|
parameters: {
|
||||||
|
// More on how to position stories at: https://storybook.js.org/docs/configure/story-layout
|
||||||
|
},
|
||||||
|
args: {},
|
||||||
|
} satisfies Meta<typeof UnknownTable>
|
||||||
|
|
||||||
|
export default meta
|
||||||
|
type Story = StoryObj<typeof meta>
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
args: {},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const NoData: Story = {
|
||||||
|
parameters: {
|
||||||
|
msw: {
|
||||||
|
handlers: [
|
||||||
|
httpTyped.get('/api/v1/page-hashes/unknown', ({ response }) =>
|
||||||
|
response(200).json(mockPage([], new PageRequest())),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Loading: Story = {
|
||||||
|
parameters: {
|
||||||
|
msw: {
|
||||||
|
handlers: [http.all('*/api/*', async () => await delay(2_000))],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Error: Story = {
|
||||||
|
parameters: {
|
||||||
|
msw: {
|
||||||
|
handlers: [http.all('*/api/v1/page-hashes/unknown', response401Unauthorized)],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
393
next-ui/src/components/pageHash/UnknownTable.vue
Normal file
393
next-ui/src/components/pageHash/UnknownTable.vue
Normal file
|
|
@ -0,0 +1,393 @@
|
||||||
|
<template>
|
||||||
|
<v-data-table-server
|
||||||
|
v-model="selectedHashes"
|
||||||
|
v-model:sort-by="sortBy"
|
||||||
|
:loading="isLoading"
|
||||||
|
:items="data?.content"
|
||||||
|
:items-length="data?.totalElements || 0"
|
||||||
|
:items-per-page-options="[
|
||||||
|
{ value: 10, title: '10' },
|
||||||
|
{ value: 25, title: '25' },
|
||||||
|
{ value: 50, title: '50' },
|
||||||
|
{ value: 100, title: '100' },
|
||||||
|
]"
|
||||||
|
:headers="headers"
|
||||||
|
fixed-header
|
||||||
|
fixed-footer
|
||||||
|
select-strategy="page"
|
||||||
|
show-select
|
||||||
|
return-object
|
||||||
|
item-value="hash"
|
||||||
|
multi-sort
|
||||||
|
mobile-breakpoint="md"
|
||||||
|
@update:options="updateOptions"
|
||||||
|
>
|
||||||
|
<template #top>
|
||||||
|
<v-toolbar flat>
|
||||||
|
<v-toolbar-title
|
||||||
|
v-if="display.smAndUp.value || (display.xs.value && selectedHashes.length == 0)"
|
||||||
|
>
|
||||||
|
<v-icon
|
||||||
|
color="medium-emphasis"
|
||||||
|
icon="i-mdi:content-copy"
|
||||||
|
size="x-small"
|
||||||
|
start
|
||||||
|
/>
|
||||||
|
{{
|
||||||
|
$formatMessage({
|
||||||
|
description: 'Unknown Duplicate Page Table global header',
|
||||||
|
defaultMessage: 'Unknown Duplicates',
|
||||||
|
id: 'XuqK4C',
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
</v-toolbar-title>
|
||||||
|
|
||||||
|
<v-spacer v-if="display.smAndUp.value || (display.xs.value && selectedHashes.length > 0)" />
|
||||||
|
<v-btn
|
||||||
|
v-if="selectedHashes.length > 0"
|
||||||
|
variant="elevated"
|
||||||
|
class="mx-2"
|
||||||
|
append-icon="i-mdi:menu-down"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
$formatMessage({
|
||||||
|
description: 'Unknown Duplicate Page: selection action button',
|
||||||
|
defaultMessage: 'Mark as',
|
||||||
|
id: 'lFTdQ+',
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
|
||||||
|
<v-menu activator="parent">
|
||||||
|
<v-list
|
||||||
|
density="compact"
|
||||||
|
slim
|
||||||
|
>
|
||||||
|
<v-list-item
|
||||||
|
v-for="(item, index) in actionOptions"
|
||||||
|
:key="index"
|
||||||
|
:title="item.title"
|
||||||
|
:prepend-icon="item.icon"
|
||||||
|
@click="updateHashActions(selectedHashes, item.value)"
|
||||||
|
>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-menu>
|
||||||
|
</v-btn>
|
||||||
|
</v-toolbar>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #no-data>
|
||||||
|
<EmptyStateNetworkError v-if="error" />
|
||||||
|
<template v-else>
|
||||||
|
{{
|
||||||
|
$formatMessage({
|
||||||
|
description: 'Unknown Duplicate Page Table: shown when table has no data',
|
||||||
|
defaultMessage: 'No data found',
|
||||||
|
id: 'hPo41m',
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #[`item.hash`]="{ value }">
|
||||||
|
<div>
|
||||||
|
<v-img
|
||||||
|
width="200"
|
||||||
|
height="200"
|
||||||
|
contain
|
||||||
|
style="cursor: zoom-in"
|
||||||
|
:src="pageHashUnknownThumbnailUrl(value)"
|
||||||
|
lazy-src="@/assets/cover.svg"
|
||||||
|
class="my-1"
|
||||||
|
:alt="
|
||||||
|
$formatMessage({
|
||||||
|
description: 'Unknown Duplicate Page Table: alt description for thumbnail',
|
||||||
|
defaultMessage: 'Duplicate page',
|
||||||
|
id: 'IXhDH6',
|
||||||
|
})
|
||||||
|
"
|
||||||
|
@mouseenter="dialogSimple.activator = $event.currentTarget"
|
||||||
|
@click="showDialogImage(value)"
|
||||||
|
>
|
||||||
|
<template #placeholder>
|
||||||
|
<div class="d-flex align-center justify-center fill-height">
|
||||||
|
<v-progress-circular
|
||||||
|
color="grey"
|
||||||
|
indeterminate
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</v-img>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #[`item.action`]="{ item, value }">
|
||||||
|
<v-btn-toggle
|
||||||
|
:model-value="value"
|
||||||
|
variant="outlined"
|
||||||
|
divided
|
||||||
|
rounded="lg"
|
||||||
|
:disabled="value"
|
||||||
|
@update:model-value="(v) => updateHashAction(item, v)"
|
||||||
|
>
|
||||||
|
<v-btn
|
||||||
|
v-tooltip:bottom="
|
||||||
|
intl.formatMessage(pageHashActionMessages[PageHashActionEnum.DELETE_AUTO])
|
||||||
|
"
|
||||||
|
size="small"
|
||||||
|
icon="i-mdi:robot"
|
||||||
|
:value="PageHashActionEnum.DELETE_AUTO"
|
||||||
|
:loading="value === PageHashActionEnum.DELETE_AUTO"
|
||||||
|
color="success"
|
||||||
|
/>
|
||||||
|
<v-btn
|
||||||
|
v-tooltip:bottom="
|
||||||
|
intl.formatMessage(pageHashActionMessages[PageHashActionEnum.DELETE_MANUAL])
|
||||||
|
"
|
||||||
|
size="small"
|
||||||
|
icon="i-mdi:hand-back-right"
|
||||||
|
:value="PageHashActionEnum.DELETE_MANUAL"
|
||||||
|
:loading="value === PageHashActionEnum.DELETE_MANUAL"
|
||||||
|
color="warning"
|
||||||
|
/>
|
||||||
|
<v-btn
|
||||||
|
v-tooltip:bottom="intl.formatMessage(pageHashActionMessages[PageHashActionEnum.IGNORE])"
|
||||||
|
size="small"
|
||||||
|
icon="i-mdi:cancel"
|
||||||
|
:value="PageHashActionEnum.IGNORE"
|
||||||
|
:loading="value === PageHashActionEnum.IGNORE"
|
||||||
|
color=""
|
||||||
|
/>
|
||||||
|
</v-btn-toggle>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #[`item.size`]="{ value }">
|
||||||
|
{{ getFileSize(value) }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #[`item.deleteSize`]="{ value }">
|
||||||
|
{{ getFileSize(value) }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #[`item.matchCount`]="{ item, value }">
|
||||||
|
<v-btn-group
|
||||||
|
rounded="lg"
|
||||||
|
divided
|
||||||
|
variant="outlined"
|
||||||
|
>
|
||||||
|
<v-btn
|
||||||
|
:text="value"
|
||||||
|
append-icon="i-mdi:image-multiple-outline"
|
||||||
|
color=""
|
||||||
|
size="small"
|
||||||
|
:disabled="value == 0"
|
||||||
|
@mouseenter="dialogSimple.activator = $event.currentTarget"
|
||||||
|
@click="showDialogMatches(item.hash)"
|
||||||
|
/>
|
||||||
|
</v-btn-group>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #[`item.created`]="{ value }">
|
||||||
|
<div>{{ $formatDate(value, { dateStyle: 'short' }) }}</div>
|
||||||
|
<div>{{ $formatDate(value, { timeStyle: 'short' }) }}</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #[`item.lastModified`]="{ value }">
|
||||||
|
<div>{{ $formatDate(value, { dateStyle: 'short' }) }}</div>
|
||||||
|
<div>{{ $formatDate(value, { timeStyle: 'short' }) }}</div>
|
||||||
|
</template>
|
||||||
|
</v-data-table-server>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useIntl } from 'vue-intl'
|
||||||
|
import { PageRequest, type SortItem } from '@/types/PageRequest'
|
||||||
|
import { useMutation, useQuery, useQueryCache } from '@pinia/colada'
|
||||||
|
import { pageHashesUnknownQuery, QUERY_KEYS_PAGE_HASHES } from '@/colada/page-hashes'
|
||||||
|
import type { components } from '@/generated/openapi/komga'
|
||||||
|
import { getFileSize } from '@/utils/utils'
|
||||||
|
import { pageHashUnknownThumbnailUrl } from '@/api/images'
|
||||||
|
import { storeToRefs } from 'pinia'
|
||||||
|
import { useDialogsStore } from '@/stores/dialogs'
|
||||||
|
import { useDisplay } from 'vuetify'
|
||||||
|
import { VImg } from 'vuetify/components'
|
||||||
|
import MatchTable from '@/components/pageHash/MatchTable.vue'
|
||||||
|
import { type ErrorCause, komgaClient } from '@/api/komga-client'
|
||||||
|
import {
|
||||||
|
type PageHashAction,
|
||||||
|
PageHashActionEnum,
|
||||||
|
pageHashActionMessages,
|
||||||
|
} from '@/types/PageHashAction'
|
||||||
|
import { useMessagesStore } from '@/stores/messages'
|
||||||
|
import { commonMessages } from '@/utils/i18n/common-messages'
|
||||||
|
|
||||||
|
const intl = useIntl()
|
||||||
|
const display = useDisplay()
|
||||||
|
const messagesStore = useMessagesStore()
|
||||||
|
const { simple: dialogSimple } = storeToRefs(useDialogsStore())
|
||||||
|
|
||||||
|
const selectedHashes = ref<components['schemas']['PageHashUnknownDto'][]>([])
|
||||||
|
const sortBy = ref<SortItem[]>([{ key: 'deleteSize', order: 'desc' }])
|
||||||
|
|
||||||
|
//region headers
|
||||||
|
const headers = [
|
||||||
|
{
|
||||||
|
title: intl.formatMessage({
|
||||||
|
description: 'Unknown Duplicate Page Table header: thumbnail',
|
||||||
|
defaultMessage: 'Thumbnail',
|
||||||
|
id: 'oyeyK/',
|
||||||
|
}),
|
||||||
|
key: 'hash',
|
||||||
|
sortable: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: intl.formatMessage({
|
||||||
|
description: 'Unknown Duplicate Page Table header: action',
|
||||||
|
defaultMessage: 'Action',
|
||||||
|
id: 'gxZjIe',
|
||||||
|
}),
|
||||||
|
key: 'action',
|
||||||
|
value: (item: components['schemas']['PageHashUnknownDto']) => {
|
||||||
|
return getPageHashAction(item)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: intl.formatMessage({
|
||||||
|
description: 'Unknown Duplicate Page Table header: match count',
|
||||||
|
defaultMessage: 'Matches',
|
||||||
|
id: 'hdoWGT',
|
||||||
|
}),
|
||||||
|
key: 'matchCount',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: intl.formatMessage({
|
||||||
|
description: 'Unknown Duplicate Page Table header: size',
|
||||||
|
defaultMessage: 'Size',
|
||||||
|
id: 'zRDVnR',
|
||||||
|
}),
|
||||||
|
key: 'size',
|
||||||
|
},
|
||||||
|
] as const
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
const pageRequest = ref<PageRequest>(new PageRequest())
|
||||||
|
|
||||||
|
const { data, isLoading, error } = useQuery(pageHashesUnknownQuery, () => ({
|
||||||
|
...pageRequest.value,
|
||||||
|
}))
|
||||||
|
|
||||||
|
function updateOptions({
|
||||||
|
page,
|
||||||
|
itemsPerPage,
|
||||||
|
sortBy,
|
||||||
|
}: {
|
||||||
|
page: number
|
||||||
|
itemsPerPage: number
|
||||||
|
sortBy: SortItem[]
|
||||||
|
}) {
|
||||||
|
pageRequest.value = PageRequest.FromVuetify(page - 1, itemsPerPage, sortBy)
|
||||||
|
}
|
||||||
|
|
||||||
|
function showDialogImage(hash: string) {
|
||||||
|
dialogSimple.value.dialogProps = {
|
||||||
|
fullscreen: display.xs.value,
|
||||||
|
scrollable: true,
|
||||||
|
}
|
||||||
|
dialogSimple.value.slot = {
|
||||||
|
component: markRaw(VImg),
|
||||||
|
props: {
|
||||||
|
src: pageHashUnknownThumbnailUrl(hash),
|
||||||
|
contain: true,
|
||||||
|
style: 'cursor: zoom-out;',
|
||||||
|
},
|
||||||
|
handlers: {
|
||||||
|
click: () => {
|
||||||
|
dialogSimple.value.dialogProps.shown = false
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showDialogMatches(hash: string) {
|
||||||
|
dialogSimple.value.dialogProps = {
|
||||||
|
fullscreen: display.xs.value,
|
||||||
|
scrollable: true,
|
||||||
|
}
|
||||||
|
dialogSimple.value.slot = {
|
||||||
|
component: markRaw(MatchTable),
|
||||||
|
props: {
|
||||||
|
modelValue: hash,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//region Update action
|
||||||
|
const updateRequests = ref<Record<string, PageHashAction>>({})
|
||||||
|
|
||||||
|
function getPageHashAction(
|
||||||
|
pageHash: components['schemas']['PageHashUnknownDto'],
|
||||||
|
): PageHashAction | undefined {
|
||||||
|
return updateRequests.value[pageHash.hash]
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateHashAction(
|
||||||
|
pageHash: components['schemas']['PageHashUnknownDto'],
|
||||||
|
newAction: PageHashAction,
|
||||||
|
invalidateCache: boolean = true,
|
||||||
|
) {
|
||||||
|
updateRequests.value[pageHash.hash] = newAction
|
||||||
|
|
||||||
|
return useMutation({
|
||||||
|
mutation: () =>
|
||||||
|
komgaClient.PUT('/api/v1/page-hashes', {
|
||||||
|
body: {
|
||||||
|
...pageHash,
|
||||||
|
action: newAction,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.mutateAsync()
|
||||||
|
.then(() => {
|
||||||
|
if (selectedHashes.value.includes(pageHash))
|
||||||
|
selectedHashes.value.splice(selectedHashes.value.indexOf(pageHash), 1)
|
||||||
|
if (invalidateCache)
|
||||||
|
void useQueryCache().invalidateQueries({ key: [QUERY_KEYS_PAGE_HASHES.unknown] })
|
||||||
|
})
|
||||||
|
.catch((error) =>
|
||||||
|
messagesStore.messages.push({
|
||||||
|
text:
|
||||||
|
(error?.cause as ErrorCause)?.message || intl.formatMessage(commonMessages.networkError),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateHashActions(
|
||||||
|
pageHashes: components['schemas']['PageHashUnknownDto'][],
|
||||||
|
newAction: PageHashAction,
|
||||||
|
) {
|
||||||
|
const updates = pageHashes.map((it) => updateHashAction(it, newAction, false))
|
||||||
|
await Promise.allSettled(updates)
|
||||||
|
|
||||||
|
void useQueryCache().invalidateQueries({ key: [QUERY_KEYS_PAGE_HASHES.unknown] })
|
||||||
|
}
|
||||||
|
|
||||||
|
const actionOptions = [
|
||||||
|
{
|
||||||
|
title: intl.formatMessage(pageHashActionMessages[PageHashActionEnum.DELETE_AUTO]),
|
||||||
|
value: PageHashActionEnum.DELETE_AUTO,
|
||||||
|
icon: 'i-mdi:robot',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: intl.formatMessage(pageHashActionMessages[PageHashActionEnum.DELETE_MANUAL]),
|
||||||
|
value: PageHashActionEnum.DELETE_MANUAL,
|
||||||
|
icon: 'i-mdi:hand-back-right',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: intl.formatMessage(pageHashActionMessages[PageHashActionEnum.IGNORE]),
|
||||||
|
value: PageHashActionEnum.IGNORE,
|
||||||
|
icon: 'i-mdi:cancel',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
//endregion
|
||||||
|
</script>
|
||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
@ -21,6 +21,18 @@ export function mockPageHashesKnown(count: number): components['schemas']['PageH
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function mockPageHashesUnknown(
|
||||||
|
count: number,
|
||||||
|
): components['schemas']['PageHashUnknownDto'][] {
|
||||||
|
return [...Array(count).keys()].map((index) => {
|
||||||
|
return {
|
||||||
|
hash: `UNKN${index}`,
|
||||||
|
size: 1234 * (index + 1),
|
||||||
|
matchCount: index * 2,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export function mockPageHashMatches(count: number): components['schemas']['PageHashMatchDto'][] {
|
export function mockPageHashMatches(count: number): components['schemas']['PageHashMatchDto'][] {
|
||||||
return [...Array(count).keys()].map((index) => {
|
return [...Array(count).keys()].map((index) => {
|
||||||
return {
|
return {
|
||||||
|
|
@ -34,6 +46,8 @@ export function mockPageHashMatches(count: number): components['schemas']['PageH
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const knownHashes: string[] = []
|
||||||
|
|
||||||
export const pageHashesHandlers = [
|
export const pageHashesHandlers = [
|
||||||
httpTyped.get('/api/v1/page-hashes', ({ query, response }) => {
|
httpTyped.get('/api/v1/page-hashes', ({ query, response }) => {
|
||||||
let data = mockPageHashesKnown(50)
|
let data = mockPageHashesKnown(50)
|
||||||
|
|
@ -46,6 +60,15 @@ export const pageHashesHandlers = [
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
|
httpTyped.get('/api/v1/page-hashes/unknown', ({ query, response }) => {
|
||||||
|
const data = mockPageHashesUnknown(50).filter((it) => !knownHashes.includes(it.hash))
|
||||||
|
return response(200).json(
|
||||||
|
mockPage(
|
||||||
|
data,
|
||||||
|
new PageRequest(Number(query.get('page')), Number(query.get('size')), query.getAll('sort')),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}),
|
||||||
httpTyped.get('/api/v1/page-hashes/{pageHash}', ({ params, query, response }) => {
|
httpTyped.get('/api/v1/page-hashes/{pageHash}', ({ params, query, response }) => {
|
||||||
const hash = params.pageHash
|
const hash = params.pageHash
|
||||||
const data = mockPageHashMatches(Number(hash.substring(4)) * 2)
|
const data = mockPageHashMatches(Number(hash.substring(4)) * 2)
|
||||||
|
|
@ -56,7 +79,10 @@ export const pageHashesHandlers = [
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
httpTyped.put('/api/v1/page-hashes', ({ response }) => {
|
httpTyped.put('/api/v1/page-hashes', async ({ request, response }) => {
|
||||||
|
const body = await request.json()
|
||||||
|
knownHashes.push(body.hash)
|
||||||
|
|
||||||
return response(202).empty()
|
return response(202).empty()
|
||||||
}),
|
}),
|
||||||
httpTyped.post('/api/v1/page-hashes/{pageHash}/delete-all', ({ response }) => {
|
httpTyped.post('/api/v1/page-hashes/{pageHash}/delete-all', ({ response }) => {
|
||||||
|
|
@ -82,4 +108,27 @@ export const pageHashesHandlers = [
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
|
httpTyped.get(
|
||||||
|
'/api/v1/page-hashes/unknown/{pageHash}/thumbnail',
|
||||||
|
async ({ params, response }) => {
|
||||||
|
const hash = params.pageHash
|
||||||
|
|
||||||
|
// use landscape image for some images
|
||||||
|
const landscape = Number(hash.slice(-1)) % 2 === 0
|
||||||
|
|
||||||
|
// Get an ArrayBuffer from reading the file from disk or fetching it.
|
||||||
|
const buffer = await fetch(landscape ? mockThumbnailLandscapeUrl : mockThumbnailUrl).then(
|
||||||
|
(response) => response.arrayBuffer(),
|
||||||
|
)
|
||||||
|
|
||||||
|
return response.untyped(
|
||||||
|
HttpResponse.arrayBuffer(buffer, {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'content-type': 'image/jpg',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
<h1>Unknown</h1>
|
<v-container
|
||||||
|
fluid
|
||||||
|
class="pa-0 pa-sm-4 h-100 h-sm-auto"
|
||||||
|
>
|
||||||
|
<PageHashUnknownTable />
|
||||||
|
</v-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue