mirror of
https://github.com/gotson/komga.git
synced 2026-05-08 12:35:30 +02:00
better mobile UI
This commit is contained in:
parent
f741de8dbc
commit
7f622accf2
14 changed files with 749 additions and 632 deletions
|
|
@ -1,4 +1,4 @@
|
|||
import { defineMutation, useMutation, useQueryCache } from '@pinia/colada'
|
||||
import { defineMutation, useMutation } from '@pinia/colada'
|
||||
import { komgaClient } from '@/api/komga-client'
|
||||
|
||||
export const useDeleteSyncPoints = defineMutation(() => {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
:activator="activator"
|
||||
:max-width="maxWidth"
|
||||
:fullscreen="fullscreen"
|
||||
:transition="fullscreen ? 'dialog-bottom-transition' : undefined"
|
||||
>
|
||||
<template #default="{ isActive }">
|
||||
<v-form
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@
|
|||
:activator="activator"
|
||||
:max-width="maxWidth"
|
||||
:fullscreen="fullscreen"
|
||||
:transition="fullscreen ? 'dialog-bottom-transition' : undefined"
|
||||
:scrollable="scrollable"
|
||||
>
|
||||
<template #default="{ isActive }">
|
||||
<v-confirm-edit
|
||||
|
|
@ -87,6 +89,7 @@ export interface DialogConfirmEditProps {
|
|||
loading?: boolean
|
||||
closeOnSave?: boolean
|
||||
fullscreen?: boolean
|
||||
scrollable?: boolean
|
||||
}
|
||||
|
||||
const {
|
||||
|
|
@ -97,5 +100,6 @@ const {
|
|||
loading = false,
|
||||
closeOnSave = true,
|
||||
fullscreen = undefined,
|
||||
scrollable = undefined,
|
||||
} = defineProps<DialogConfirmEditProps>()
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -8,36 +8,36 @@
|
|||
class="text-h4 font-weight-medium link-underline me-2"
|
||||
>{{ release.version }}</a
|
||||
>
|
||||
<v-chip
|
||||
v-if="current"
|
||||
class="mx-2 mt-n3"
|
||||
size="small"
|
||||
rounded
|
||||
color="info"
|
||||
>
|
||||
{{
|
||||
$formatMessage({
|
||||
description:
|
||||
'Updates view: badge showing next to the currently installed release number',
|
||||
defaultMessage: 'Currently installed',
|
||||
id: '3jrAF6',
|
||||
})
|
||||
}}
|
||||
</v-chip>
|
||||
<v-chip
|
||||
v-if="latest"
|
||||
class="mx-2 mt-n3"
|
||||
size="small"
|
||||
rounded
|
||||
>
|
||||
{{
|
||||
$formatMessage({
|
||||
description: 'Updates view: badge showing next to the latest release number',
|
||||
defaultMessage: 'Latest',
|
||||
id: '2Bh8F2',
|
||||
})
|
||||
}}
|
||||
</v-chip>
|
||||
<span class="d-inline-flex mt-n3 ga-2 ms-2">
|
||||
<v-chip
|
||||
v-if="current"
|
||||
size="small"
|
||||
rounded
|
||||
color="info"
|
||||
>
|
||||
{{
|
||||
$formatMessage({
|
||||
description:
|
||||
'Updates view: badge showing next to the currently installed release number',
|
||||
defaultMessage: 'Installed',
|
||||
id: 'WADecv',
|
||||
})
|
||||
}}
|
||||
</v-chip>
|
||||
<v-chip
|
||||
v-if="latest"
|
||||
size="small"
|
||||
rounded
|
||||
>
|
||||
{{
|
||||
$formatMessage({
|
||||
description: 'Updates view: badge showing next to the latest release number',
|
||||
defaultMessage: 'Latest',
|
||||
id: '2Bh8F2',
|
||||
})
|
||||
}}
|
||||
</v-chip>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -9,101 +9,110 @@
|
|||
@submit.prevent="submitForm(save)"
|
||||
:disabled="loading"
|
||||
>
|
||||
<v-list>
|
||||
<v-list-subheader
|
||||
:class="headerClass"
|
||||
class="mb-2"
|
||||
>{{
|
||||
$formatMessage({
|
||||
description: 'Server settings: section header for posters',
|
||||
defaultMessage: 'Posters',
|
||||
id: 'a5MYiP',
|
||||
})
|
||||
}}
|
||||
</v-list-subheader>
|
||||
<v-select
|
||||
v-model="proxyModel.value.thumbnailSize"
|
||||
:label="
|
||||
$formatMessage({
|
||||
description: 'Server settings: selection of poster size',
|
||||
defaultMessage: 'Generated poster size',
|
||||
id: 'eDA9Gm',
|
||||
})
|
||||
"
|
||||
:items="thumbnailSizes"
|
||||
/>
|
||||
</v-list>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<div class="text-subtitle-2">
|
||||
{{
|
||||
$formatMessage({
|
||||
description: 'Server settings: section header for posters',
|
||||
defaultMessage: 'Posters',
|
||||
id: 'a5MYiP',
|
||||
})
|
||||
}}
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-select
|
||||
v-model="proxyModel.value.thumbnailSize"
|
||||
:label="
|
||||
$formatMessage({
|
||||
description: 'Server settings: selection of poster size',
|
||||
defaultMessage: 'Generated poster size',
|
||||
id: 'eDA9Gm',
|
||||
})
|
||||
"
|
||||
:items="thumbnailSizes"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-divider />
|
||||
<v-divider class="mb-4" />
|
||||
|
||||
<v-list>
|
||||
<v-list-subheader :class="headerClass"
|
||||
>{{
|
||||
$formatMessage({
|
||||
description: 'Server settings: section header for scan behaviour',
|
||||
defaultMessage: 'Overall scan behaviour',
|
||||
id: 'dSlkbn',
|
||||
})
|
||||
}}
|
||||
</v-list-subheader>
|
||||
<v-checkbox
|
||||
v-model="proxyModel.value.deleteEmptyCollections"
|
||||
:label="
|
||||
$formatMessage({
|
||||
description: 'Server settings: checkbox to delete empty collections after scan',
|
||||
defaultMessage: 'Delete empty collections after scan',
|
||||
id: 'pHdVzh',
|
||||
})
|
||||
"
|
||||
hide-details
|
||||
/>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<div class="text-subtitle-2">
|
||||
{{
|
||||
$formatMessage({
|
||||
description: 'Server settings: section header for scan behaviour',
|
||||
defaultMessage: 'Overall scan behaviour',
|
||||
id: 'dSlkbn',
|
||||
})
|
||||
}}
|
||||
</div>
|
||||
<v-checkbox
|
||||
v-model="proxyModel.value.deleteEmptyCollections"
|
||||
:label="
|
||||
$formatMessage({
|
||||
description: 'Server settings: checkbox to delete empty collections after scan',
|
||||
defaultMessage: 'Delete empty collections after scan',
|
||||
id: 'pHdVzh',
|
||||
})
|
||||
"
|
||||
hide-details
|
||||
/>
|
||||
<v-checkbox
|
||||
v-model="proxyModel.value.deleteEmptyReadLists"
|
||||
:label="
|
||||
$formatMessage({
|
||||
description: 'Server settings: checkbox to delete empty readlists after scan',
|
||||
defaultMessage: 'Delete empty read lists after scan',
|
||||
id: 'kqV7EJ',
|
||||
})
|
||||
"
|
||||
hide-details
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-checkbox
|
||||
v-model="proxyModel.value.deleteEmptyReadLists"
|
||||
:label="
|
||||
$formatMessage({
|
||||
description: 'Server settings: checkbox to delete empty readlists after scan',
|
||||
defaultMessage: 'Delete empty read lists after scan',
|
||||
id: 'kqV7EJ',
|
||||
})
|
||||
"
|
||||
hide-details
|
||||
/>
|
||||
</v-list>
|
||||
<v-divider class="mb-4" />
|
||||
|
||||
<v-divider />
|
||||
<v-row>
|
||||
<v-col>
|
||||
<div class="text-subtitle-2">
|
||||
{{
|
||||
$formatMessage({
|
||||
description: 'Server settings: section header for tasks',
|
||||
defaultMessage: 'Tasks',
|
||||
id: '8hC76W',
|
||||
})
|
||||
}}
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-number-input
|
||||
v-model="proxyModel.value.taskPoolSize"
|
||||
:label="
|
||||
$formatMessage({
|
||||
description: 'Server settings: input field for task threads',
|
||||
defaultMessage: 'Task threads',
|
||||
id: 'rHwSrF',
|
||||
})
|
||||
"
|
||||
:min="1"
|
||||
:rules="['required']"
|
||||
></v-number-input>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-list>
|
||||
<v-list-subheader
|
||||
:class="headerClass"
|
||||
class="mb-2"
|
||||
>{{
|
||||
$formatMessage({
|
||||
description: 'Server settings: section header for tasks',
|
||||
defaultMessage: 'Tasks',
|
||||
id: '8hC76W',
|
||||
})
|
||||
}}
|
||||
</v-list-subheader>
|
||||
<v-number-input
|
||||
v-model="proxyModel.value.taskPoolSize"
|
||||
:label="
|
||||
$formatMessage({
|
||||
description: 'Server settings: input field for task threads',
|
||||
defaultMessage: 'Task threads',
|
||||
id: 'rHwSrF',
|
||||
})
|
||||
"
|
||||
:min="1"
|
||||
:rules="['required']"
|
||||
></v-number-input>
|
||||
</v-list>
|
||||
<v-divider class="mb-4" />
|
||||
|
||||
<v-divider />
|
||||
|
||||
<v-list>
|
||||
<v-list-subheader class="mb-4">
|
||||
<div :class="headerClass">
|
||||
<v-row>
|
||||
<v-col>
|
||||
<div class="text-subtitle-2">
|
||||
{{
|
||||
$formatMessage({
|
||||
description: 'Server settings: section header for remember me',
|
||||
|
|
@ -113,38 +122,41 @@
|
|||
}}
|
||||
</div>
|
||||
<div class="text-caption">{{ messageRequiresRestart }}</div>
|
||||
</v-list-subheader>
|
||||
<v-number-input
|
||||
v-model="proxyModel.value.rememberMeDurationDays"
|
||||
:label="
|
||||
$formatMessage({
|
||||
description: 'Server settings: input field for remember me duration',
|
||||
defaultMessage: 'Remember me duration (in days)',
|
||||
id: 'iDU5FS',
|
||||
})
|
||||
"
|
||||
:min="1"
|
||||
:rules="['required']"
|
||||
></v-number-input>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-number-input
|
||||
v-model="proxyModel.value.rememberMeDurationDays"
|
||||
:label="
|
||||
$formatMessage({
|
||||
description: 'Server settings: input field for remember me duration',
|
||||
defaultMessage: 'Remember me duration (in days)',
|
||||
id: 'iDU5FS',
|
||||
})
|
||||
"
|
||||
:min="1"
|
||||
:rules="['required']"
|
||||
></v-number-input>
|
||||
<v-checkbox
|
||||
v-model="proxyModel.value.renewRememberMeKey"
|
||||
:label="
|
||||
$formatMessage({
|
||||
description: 'Server settings: checkbox to regenerate the remember me key',
|
||||
defaultMessage: 'Regenerate the \'remember me\' key',
|
||||
id: 'UaD47n',
|
||||
})
|
||||
"
|
||||
hide-details
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-checkbox
|
||||
v-model="proxyModel.value.renewRememberMeKey"
|
||||
:label="
|
||||
$formatMessage({
|
||||
description: 'Server settings: checkbox to regenerate the remember me key',
|
||||
defaultMessage: 'Regenerate the \'remember me\' key',
|
||||
id: 'UaD47n',
|
||||
})
|
||||
"
|
||||
hide-details
|
||||
/>
|
||||
</v-list>
|
||||
<v-divider class="mb-4" />
|
||||
|
||||
<v-divider />
|
||||
|
||||
<v-list>
|
||||
<v-list-subheader class="mb-4">
|
||||
<div :class="headerClass">
|
||||
<v-row>
|
||||
<v-col>
|
||||
<div class="text-subtitle-2">
|
||||
{{
|
||||
$formatMessage({
|
||||
description: 'Server settings: section header for HTTP server',
|
||||
|
|
@ -154,144 +166,159 @@
|
|||
}}
|
||||
</div>
|
||||
<div class="text-caption">{{ messageRequiresRestart }}</div>
|
||||
</v-list-subheader>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-number-input
|
||||
v-model="proxyModel.value.serverPort"
|
||||
:label="
|
||||
$formatMessage({
|
||||
description: 'Server settings: input field for server port',
|
||||
defaultMessage: 'Server listening port',
|
||||
id: '+r8FCS',
|
||||
})
|
||||
"
|
||||
:min="1"
|
||||
:max="65535"
|
||||
:placeholder="settings?.serverPort.configurationSource?.toString()"
|
||||
:persistent-placeholder="!!settings?.serverPort.configurationSource"
|
||||
clearable
|
||||
>
|
||||
<template
|
||||
v-slot:append-inner
|
||||
v-if="!!settings?.serverPort.configurationSource"
|
||||
>
|
||||
<v-icon
|
||||
icon="i-mdi:information-outline"
|
||||
v-tooltip:bottom="messagePrecedence"
|
||||
></v-icon>
|
||||
</template>
|
||||
</v-number-input>
|
||||
|
||||
<v-text-field
|
||||
v-model="proxyModel.value.serverContextPath"
|
||||
:label="
|
||||
$formatMessage({
|
||||
description: 'Server settings: input field for server base URL',
|
||||
defaultMessage: 'Server base URL',
|
||||
id: 'eRJOa6',
|
||||
})
|
||||
"
|
||||
:placeholder="settings?.serverContextPath.configurationSource?.toString()"
|
||||
:persistent-placeholder="!!settings?.serverContextPath.configurationSource"
|
||||
clearable
|
||||
:rules="[
|
||||
[
|
||||
'pattern',
|
||||
/^\/[-a-zA-Z0-9_\/]*[a-zA-Z0-9]$/,
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-number-input
|
||||
v-model="proxyModel.value.serverPort"
|
||||
:label="
|
||||
$formatMessage({
|
||||
description: 'Server settings: error message when server context path is invalid',
|
||||
defaultMessage:
|
||||
'Must start with \'/\', not end with \'/-_\', and contain only \'/-_a-z0-9\'',
|
||||
id: 'Lto2Lg',
|
||||
}),
|
||||
],
|
||||
]"
|
||||
><template
|
||||
v-slot:append-inner
|
||||
v-if="!!settings?.serverContextPath.configurationSource"
|
||||
description: 'Server settings: input field for server port',
|
||||
defaultMessage: 'Server listening port',
|
||||
id: '+r8FCS',
|
||||
})
|
||||
"
|
||||
:min="1"
|
||||
:max="65535"
|
||||
:placeholder="settings?.serverPort.configurationSource?.toString()"
|
||||
:persistent-placeholder="!!settings?.serverPort.configurationSource"
|
||||
clearable
|
||||
hide-details
|
||||
>
|
||||
<v-icon
|
||||
icon="i-mdi:information-outline"
|
||||
v-tooltip:bottom="messagePrecedence"
|
||||
></v-icon> </template
|
||||
></v-text-field>
|
||||
</v-list>
|
||||
<template
|
||||
v-slot:append-inner
|
||||
v-if="!!settings?.serverPort.configurationSource"
|
||||
>
|
||||
<v-icon
|
||||
icon="i-mdi:information-outline"
|
||||
v-tooltip:bottom="messagePrecedence"
|
||||
></v-icon>
|
||||
</template>
|
||||
</v-number-input>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-divider />
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-text-field
|
||||
v-model="proxyModel.value.serverContextPath"
|
||||
:label="
|
||||
$formatMessage({
|
||||
description: 'Server settings: input field for server base URL',
|
||||
defaultMessage: 'Server base URL',
|
||||
id: 'eRJOa6',
|
||||
})
|
||||
"
|
||||
:placeholder="settings?.serverContextPath.configurationSource?.toString()"
|
||||
:persistent-placeholder="!!settings?.serverContextPath.configurationSource"
|
||||
clearable
|
||||
:rules="[
|
||||
[
|
||||
'pattern',
|
||||
/^\/[-a-zA-Z0-9_\/]*[a-zA-Z0-9]$/,
|
||||
$formatMessage({
|
||||
description:
|
||||
'Server settings: error message when server context path is invalid',
|
||||
defaultMessage:
|
||||
'Must start with \'/\', not end with \'/-_\', and contain only \'/-_a-z0-9\'',
|
||||
id: 'Lto2Lg',
|
||||
}),
|
||||
],
|
||||
]"
|
||||
><template
|
||||
v-slot:append-inner
|
||||
v-if="!!settings?.serverContextPath.configurationSource"
|
||||
>
|
||||
<v-icon
|
||||
icon="i-mdi:information-outline"
|
||||
v-tooltip:bottom="messagePrecedence"
|
||||
></v-icon> </template
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-list>
|
||||
<v-list-subheader :class="headerClass"
|
||||
>{{
|
||||
$formatMessage({
|
||||
description: 'Server settings: section header for Kobo Sync',
|
||||
defaultMessage: 'Kobo Sync',
|
||||
id: 'rRFQKU',
|
||||
})
|
||||
}}
|
||||
</v-list-subheader>
|
||||
<v-checkbox
|
||||
v-model="proxyModel.value.koboProxy"
|
||||
:label="
|
||||
$formatMessage({
|
||||
description:
|
||||
'Server settings: checkbox to enable Kobo Store proxying for Kobo Sync ',
|
||||
defaultMessage: 'Proxy unhandled requests to Kobo Store',
|
||||
id: 'iNBto3',
|
||||
})
|
||||
"
|
||||
hide-details
|
||||
/>
|
||||
<v-divider class="mb-4" />
|
||||
|
||||
<v-number-input
|
||||
v-model="proxyModel.value.koboPort"
|
||||
:label="
|
||||
$formatMessage({
|
||||
description: 'Server settings: input field for kobo sync port',
|
||||
defaultMessage: 'Kobo Sync external port',
|
||||
id: '4AKIbg',
|
||||
})
|
||||
"
|
||||
:min="1"
|
||||
:max="65535"
|
||||
:hint="
|
||||
$formatMessage({
|
||||
description: 'Server settings: input field hint for kobo sync port',
|
||||
defaultMessage: 'Set only in case of sync issues with covers and downloads',
|
||||
id: 'TwB29u',
|
||||
})
|
||||
"
|
||||
persistent-hint
|
||||
clearable
|
||||
></v-number-input>
|
||||
</v-list>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<div class="text-subtitle-2">
|
||||
{{
|
||||
$formatMessage({
|
||||
description: 'Server settings: section header for Kobo Sync',
|
||||
defaultMessage: 'Kobo Sync',
|
||||
id: 'rRFQKU',
|
||||
})
|
||||
}}
|
||||
</div>
|
||||
<v-checkbox
|
||||
v-model="proxyModel.value.koboProxy"
|
||||
:label="
|
||||
$formatMessage({
|
||||
description:
|
||||
'Server settings: checkbox to enable Kobo Store proxying for Kobo Sync ',
|
||||
defaultMessage: 'Proxy unhandled requests to Kobo Store',
|
||||
id: 'iNBto3',
|
||||
})
|
||||
"
|
||||
hide-details
|
||||
/>
|
||||
<v-number-input
|
||||
v-model="proxyModel.value.koboPort"
|
||||
:label="
|
||||
$formatMessage({
|
||||
description: 'Server settings: input field for kobo sync port',
|
||||
defaultMessage: 'Kobo Sync external port',
|
||||
id: '4AKIbg',
|
||||
})
|
||||
"
|
||||
:min="1"
|
||||
:max="65535"
|
||||
:hint="
|
||||
$formatMessage({
|
||||
description: 'Server settings: input field hint for kobo sync port',
|
||||
defaultMessage: 'Set only in case of sync issues with covers and downloads',
|
||||
id: 'TwB29u',
|
||||
})
|
||||
"
|
||||
persistent-hint
|
||||
clearable
|
||||
></v-number-input>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-list>
|
||||
<v-btn
|
||||
:text="
|
||||
$formatMessage({
|
||||
description: 'Server settings: button to discard any changes made',
|
||||
defaultMessage: 'Discard',
|
||||
id: 'kh49ZJ',
|
||||
})
|
||||
"
|
||||
@click="cancel"
|
||||
:disabled="isPristine"
|
||||
variant="text"
|
||||
></v-btn>
|
||||
<v-btn
|
||||
:text="
|
||||
$formatMessage({
|
||||
description: 'Server settings: button to save any changes made',
|
||||
defaultMessage: 'Save changes',
|
||||
id: 'FpwJlU',
|
||||
})
|
||||
"
|
||||
type="submit"
|
||||
:disabled="isPristine"
|
||||
variant="text"
|
||||
:loading="loading"
|
||||
></v-btn>
|
||||
</v-list>
|
||||
<v-row>
|
||||
<v-col cols="auto">
|
||||
<v-btn
|
||||
:text="
|
||||
$formatMessage({
|
||||
description: 'Server settings: button to discard any changes made',
|
||||
defaultMessage: 'Discard',
|
||||
id: 'kh49ZJ',
|
||||
})
|
||||
"
|
||||
@click="cancel"
|
||||
:disabled="isPristine"
|
||||
variant="text"
|
||||
></v-btn>
|
||||
</v-col>
|
||||
<v-col cols="auto">
|
||||
<v-btn
|
||||
:text="
|
||||
$formatMessage({
|
||||
description: 'Server settings: button to save any changes made',
|
||||
defaultMessage: 'Save changes',
|
||||
id: 'FpwJlU',
|
||||
})
|
||||
"
|
||||
type="submit"
|
||||
:disabled="isPristine"
|
||||
variant="text"
|
||||
:loading="loading"
|
||||
></v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-form>
|
||||
</template>
|
||||
</v-confirm-edit>
|
||||
|
|
@ -303,7 +330,6 @@ import { useIntl } from 'vue-intl'
|
|||
import type { components } from '@/generated/openapi/komga'
|
||||
|
||||
const intl = useIntl()
|
||||
const headerClass = 'text-subtitle1 font-weight-bold'
|
||||
|
||||
const { settings, loading = false } = defineProps<{
|
||||
settings?: components['schemas']['SettingsDto']
|
||||
|
|
|
|||
|
|
@ -1,13 +1,17 @@
|
|||
<template>
|
||||
<v-dialog
|
||||
ref="dialogRef"
|
||||
v-model="showDialog"
|
||||
:activator="activator"
|
||||
:fullscreen="fullscreen"
|
||||
:transition="fullscreen ? 'dialog-bottom-transition' : undefined"
|
||||
max-width="600px"
|
||||
@after-leave="reset()"
|
||||
>
|
||||
<template #default="{ isActive }">
|
||||
<v-form @submit.prevent="generateApiKey()">
|
||||
<v-form
|
||||
@submit.prevent="generateApiKey()"
|
||||
class="fill-height"
|
||||
>
|
||||
<v-card
|
||||
:title="
|
||||
$formatMessage({
|
||||
|
|
@ -150,7 +154,11 @@ const messagesStore = useMessagesStore()
|
|||
const { isSupported: clipboardSupported, copy, copied } = useClipboard({ copiedDuring: 3000 })
|
||||
|
||||
const showDialog = defineModel<boolean>('dialog', { required: false })
|
||||
const activator = defineModel<Element | string>('activator', { required: false })
|
||||
|
||||
const { fullscreen = undefined, activator = undefined } = defineProps<{
|
||||
fullscreen?: boolean
|
||||
activator?: Element | string
|
||||
}>()
|
||||
|
||||
const comment = ref<string>('')
|
||||
const creationError = ref<string>('')
|
||||
|
|
|
|||
|
|
@ -1,129 +1,160 @@
|
|||
<template>
|
||||
<template v-if="!user.id">
|
||||
<v-text-field
|
||||
v-model="user!.email"
|
||||
autofocus
|
||||
:rules="['required', 'email']"
|
||||
:label="
|
||||
$formatMessage({
|
||||
description: 'User creation dialog: Email field',
|
||||
defaultMessage: 'Email',
|
||||
id: 'ToD0+o',
|
||||
})
|
||||
"
|
||||
prepend-icon="i-mdi:account"
|
||||
/>
|
||||
<v-text-field
|
||||
v-model="user.password"
|
||||
class="mt-1 mb-2"
|
||||
:rules="['required']"
|
||||
:label="
|
||||
$formatMessage({
|
||||
description: 'User creation dialog: Password field',
|
||||
defaultMessage: 'Password',
|
||||
id: 'o+A10T',
|
||||
})
|
||||
"
|
||||
autocomplete="off"
|
||||
:type="showPassword ? 'text' : 'password'"
|
||||
:append-inner-icon="showPassword ? 'i-mdi:eye' : 'i-mdi:eye-off'"
|
||||
prepend-icon="mdi-none"
|
||||
@click:append-inner="showPassword = !showPassword"
|
||||
/>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-text-field
|
||||
v-model="user!.email"
|
||||
autofocus
|
||||
:rules="['required', 'email']"
|
||||
:label="
|
||||
$formatMessage({
|
||||
description: 'User creation dialog: Email field',
|
||||
defaultMessage: 'Email',
|
||||
id: 'ToD0+o',
|
||||
})
|
||||
"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-text-field
|
||||
v-model="user.password"
|
||||
:rules="['required']"
|
||||
:label="
|
||||
$formatMessage({
|
||||
description: 'User creation dialog: Password field',
|
||||
defaultMessage: 'Password',
|
||||
id: 'o+A10T',
|
||||
})
|
||||
"
|
||||
autocomplete="off"
|
||||
:type="showPassword ? 'text' : 'password'"
|
||||
:append-inner-icon="showPassword ? 'i-mdi:eye' : 'i-mdi:eye-off'"
|
||||
@click:append-inner="showPassword = !showPassword"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-divider
|
||||
class="mb-4"
|
||||
v-if="!user.id"
|
||||
/>
|
||||
<div class="text-subtitle-2">Permissions</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<!-- Roles -->
|
||||
<v-select
|
||||
v-model="user.roles"
|
||||
chips
|
||||
closable-chips
|
||||
multiple
|
||||
:label="
|
||||
$formatMessage({
|
||||
description: 'User creation/edit dialog: Roles field',
|
||||
defaultMessage: 'Roles',
|
||||
id: 'CUxhzL',
|
||||
})
|
||||
"
|
||||
prepend-icon="i-mdi:key-chain"
|
||||
:items="userRoles"
|
||||
/>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-select
|
||||
v-model="user.roles"
|
||||
chips
|
||||
closable-chips
|
||||
multiple
|
||||
hide-details
|
||||
:label="
|
||||
$formatMessage({
|
||||
description: 'User creation/edit dialog: Roles field',
|
||||
defaultMessage: 'Roles',
|
||||
id: 'CUxhzL',
|
||||
})
|
||||
"
|
||||
:items="userRoles"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<!-- Shared libraries -->
|
||||
<v-select
|
||||
v-model="user.sharedLibraries!.libraryIds"
|
||||
multiple
|
||||
:label="
|
||||
$formatMessage({
|
||||
description: 'User creation/edit dialog: Shared Libraries field',
|
||||
defaultMessage: 'Shared Libraries',
|
||||
id: 'UvhIIT',
|
||||
})
|
||||
"
|
||||
:items="libraries"
|
||||
item-title="name"
|
||||
item-value="id"
|
||||
prepend-icon="i-mdi:book-multiple"
|
||||
>
|
||||
<!-- Workaround for the lack of a slot to override the whole selection -->
|
||||
<template #prepend-inner>
|
||||
<!-- Show an All Libraries chip instead of the selection -->
|
||||
<v-chip
|
||||
v-if="user.sharedLibraries?.all"
|
||||
:text="
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-select
|
||||
v-model="user.sharedLibraries!.libraryIds"
|
||||
multiple
|
||||
hide-details
|
||||
:label="
|
||||
$formatMessage({
|
||||
description:
|
||||
'User creation/edit dialog: Shared Libraries field, value shown when user has access to all libraries',
|
||||
defaultMessage: 'All libraries',
|
||||
id: 'app.user-create-dialog.all_libraries',
|
||||
description: 'User creation/edit dialog: Shared Libraries field',
|
||||
defaultMessage: 'Shared Libraries',
|
||||
id: 'UvhIIT',
|
||||
})
|
||||
"
|
||||
size="small"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #selection="{ item }">
|
||||
<!-- Show the selection only if 'all' is false -->
|
||||
<v-chip
|
||||
v-if="!user.sharedLibraries?.all"
|
||||
size="small"
|
||||
:text="item.title"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #prepend-item>
|
||||
<v-list-item
|
||||
:title="
|
||||
$formatMessage({
|
||||
description:
|
||||
'User creation/edit dialog: Shared Libraries field, value shown when user has access to all libraries',
|
||||
defaultMessage: 'All libraries',
|
||||
id: 'app.user-create-dialog.all_libraries',
|
||||
})
|
||||
"
|
||||
@click="selectAllLibraries"
|
||||
:items="libraries"
|
||||
item-title="name"
|
||||
item-value="id"
|
||||
>
|
||||
<template #prepend>
|
||||
<v-checkbox-btn :model-value="user.sharedLibraries?.all" />
|
||||
<!-- Workaround for the lack of a slot to override the whole selection -->
|
||||
<template #prepend-inner>
|
||||
<!-- Show an All Libraries chip instead of the selection -->
|
||||
<v-chip
|
||||
v-if="user.sharedLibraries?.all"
|
||||
:text="
|
||||
$formatMessage({
|
||||
description:
|
||||
'User creation/edit dialog: Shared Libraries field, value shown when user has access to all libraries',
|
||||
defaultMessage: 'All libraries',
|
||||
id: 'app.user-create-dialog.all_libraries',
|
||||
})
|
||||
"
|
||||
size="small"
|
||||
/>
|
||||
</template>
|
||||
</v-list-item>
|
||||
</template>
|
||||
|
||||
<template #item="{ props: itemProps }">
|
||||
<v-list-item
|
||||
:disabled="user.sharedLibraries?.all"
|
||||
v-bind="itemProps"
|
||||
>
|
||||
<template #prepend="{ isSelected }">
|
||||
<v-checkbox-btn :model-value="isSelected" />
|
||||
<template #selection="{ item }">
|
||||
<!-- Show the selection only if 'all' is false -->
|
||||
<v-chip
|
||||
v-if="!user.sharedLibraries?.all"
|
||||
size="small"
|
||||
:text="item.title"
|
||||
/>
|
||||
</template>
|
||||
</v-list-item>
|
||||
</template>
|
||||
</v-select>
|
||||
|
||||
<template #prepend-item>
|
||||
<v-list-item
|
||||
:title="
|
||||
$formatMessage({
|
||||
description:
|
||||
'User creation/edit dialog: Shared Libraries field, value shown when user has access to all libraries',
|
||||
defaultMessage: 'All libraries',
|
||||
id: 'app.user-create-dialog.all_libraries',
|
||||
})
|
||||
"
|
||||
@click="selectAllLibraries"
|
||||
>
|
||||
<template #prepend>
|
||||
<v-checkbox-btn :model-value="user.sharedLibraries?.all" />
|
||||
</template>
|
||||
</v-list-item>
|
||||
</template>
|
||||
|
||||
<template #item="{ props: itemProps }">
|
||||
<v-list-item
|
||||
:disabled="user.sharedLibraries?.all"
|
||||
v-bind="itemProps"
|
||||
>
|
||||
<template #prepend="{ isSelected }">
|
||||
<v-checkbox-btn :model-value="isSelected" />
|
||||
</template>
|
||||
</v-list-item>
|
||||
</template>
|
||||
</v-select>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<!-- Age restriction -->
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-divider class="mb-4" />
|
||||
<div class="text-subtitle-2">Age restriction</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<v-select
|
||||
v-model="user.ageRestriction!.restriction"
|
||||
:label="
|
||||
|
|
@ -134,10 +165,13 @@
|
|||
})
|
||||
"
|
||||
:items="ageRestrictions"
|
||||
prepend-icon="i-mdi:folder-lock"
|
||||
hide-details
|
||||
/>
|
||||
</v-col>
|
||||
<v-col>
|
||||
<v-col
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<v-number-input
|
||||
v-model="user.ageRestriction!.age"
|
||||
:disabled="user.ageRestriction?.restriction?.toString() === 'NONE'"
|
||||
|
|
@ -154,59 +188,73 @@
|
|||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-divider class="mb-4" />
|
||||
<div class="text-subtitle-2">Label restrictions</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<!-- Allow labels -->
|
||||
<v-combobox
|
||||
v-model="user.labelsAllow"
|
||||
:label="
|
||||
$formatMessage({
|
||||
description: 'User creation/edit dialog: Allow only labels field label',
|
||||
defaultMessage: 'Allow only labels',
|
||||
id: 'Sj0HXz',
|
||||
})
|
||||
"
|
||||
chips
|
||||
closable-chips
|
||||
multiple
|
||||
:items="sharingLabels"
|
||||
prepend-icon="mdi-none"
|
||||
>
|
||||
<template #prepend-item>
|
||||
<v-list-item>
|
||||
<span class="font-weight-medium">
|
||||
{{ $formatMessage(commonMessages.selectItemOrCreateOne) }}
|
||||
</span>
|
||||
</v-list-item>
|
||||
</template>
|
||||
</v-combobox>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-combobox
|
||||
v-model="user.labelsAllow"
|
||||
:label="
|
||||
$formatMessage({
|
||||
description: 'User creation/edit dialog: Allow only labels field label',
|
||||
defaultMessage: 'Allow only labels',
|
||||
id: 'Sj0HXz',
|
||||
})
|
||||
"
|
||||
chips
|
||||
closable-chips
|
||||
multiple
|
||||
hide-details
|
||||
:items="sharingLabels"
|
||||
>
|
||||
<template #prepend-item>
|
||||
<v-list-item>
|
||||
<span class="font-weight-medium">
|
||||
{{ $formatMessage(commonMessages.selectItemOrCreateOne) }}
|
||||
</span>
|
||||
</v-list-item>
|
||||
</template>
|
||||
</v-combobox>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<!-- Exclude labels -->
|
||||
<v-combobox
|
||||
v-model="user.labelsExclude"
|
||||
:label="
|
||||
$formatMessage({
|
||||
description: 'User creation/edit dialog: Exclude labels field label',
|
||||
defaultMessage: 'Exclude labels',
|
||||
id: '3W0jUi',
|
||||
})
|
||||
"
|
||||
chips
|
||||
closable-chips
|
||||
multiple
|
||||
:items="sharingLabels"
|
||||
prepend-icon="mdi-none"
|
||||
>
|
||||
<template #prepend-item>
|
||||
<v-list-item>
|
||||
<span class="font-weight-medium">
|
||||
{{ $formatMessage(commonMessages.selectItemOrCreateOne) }}
|
||||
</span>
|
||||
</v-list-item>
|
||||
</template>
|
||||
</v-combobox>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-combobox
|
||||
v-model="user.labelsExclude"
|
||||
:label="
|
||||
$formatMessage({
|
||||
description: 'User creation/edit dialog: Exclude labels field label',
|
||||
defaultMessage: 'Exclude labels',
|
||||
id: '3W0jUi',
|
||||
})
|
||||
"
|
||||
chips
|
||||
closable-chips
|
||||
multiple
|
||||
hide-details
|
||||
:items="sharingLabels"
|
||||
>
|
||||
<template #prepend-item>
|
||||
<v-list-item>
|
||||
<span class="font-weight-medium">
|
||||
{{ $formatMessage(commonMessages.selectItemOrCreateOne) }}
|
||||
</span>
|
||||
</v-list-item>
|
||||
</template>
|
||||
</v-combobox>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { UserRoles } from '@/types/UserRoles'
|
||||
import { UserRoles, userRolesMessages } from '@/types/UserRoles'
|
||||
import type { components } from '@/generated/openapi/komga'
|
||||
import { useLibraries } from '@/colada/libraries'
|
||||
import { useSharingLabels } from '@/colada/referential'
|
||||
|
|
@ -237,7 +285,7 @@ function selectAllLibraries() {
|
|||
|
||||
const userRoles = computed(() =>
|
||||
Object.keys(UserRoles).map((x) => ({
|
||||
title: x,
|
||||
title: intl.formatMessage(userRolesMessages[x as UserRoles]),
|
||||
value: x,
|
||||
})),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -4,12 +4,7 @@
|
|||
<LayoutAppDrawer />
|
||||
|
||||
<v-main scrollable>
|
||||
<v-container
|
||||
fluid
|
||||
class="pa-6"
|
||||
>
|
||||
<router-view />
|
||||
</v-container>
|
||||
<router-view />
|
||||
</v-main>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,24 +1,32 @@
|
|||
<template>
|
||||
<v-empty-state
|
||||
v-if="error"
|
||||
icon="i-mdi:connection"
|
||||
:title="$formatMessage(commonMessages.somethingWentWrongTitle)"
|
||||
:text="$formatMessage(commonMessages.somethingWentWrongSubTitle)"
|
||||
/>
|
||||
|
||||
<template v-else>
|
||||
<FragmentApikeyTable
|
||||
:api-keys="apiKeys"
|
||||
:loading="isLoading"
|
||||
@enter-add-api-key="(target) => (dialogGenerateActivator = target)"
|
||||
@enter-force-sync-api-key="(target) => (dialogConfirm.activator = target)"
|
||||
@force-sync-api-key="(apiKey) => showDialog(ACTION.FORCE_SYNC, apiKey)"
|
||||
@enter-delete-api-key="(target) => (dialogConfirm.activator = target)"
|
||||
@delete-api-key="(apiKey) => showDialog(ACTION.DELETE, apiKey)"
|
||||
<v-container
|
||||
fluid
|
||||
class="pa-0 pa-sm-4"
|
||||
>
|
||||
<v-empty-state
|
||||
v-if="error"
|
||||
icon="i-mdi:connection"
|
||||
:title="$formatMessage(commonMessages.somethingWentWrongTitle)"
|
||||
:text="$formatMessage(commonMessages.somethingWentWrongSubTitle)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<FragmentApikeyGenerateDialog :activator="dialogGenerateActivator" />
|
||||
<template v-else>
|
||||
<FragmentApikeyTable
|
||||
:api-keys="apiKeys"
|
||||
:loading="isLoading"
|
||||
@enter-add-api-key="(target) => (dialogGenerateActivator = target)"
|
||||
@enter-force-sync-api-key="(target) => (dialogConfirm.activator = target)"
|
||||
@force-sync-api-key="(apiKey) => showDialog(ACTION.FORCE_SYNC, apiKey)"
|
||||
@enter-delete-api-key="(target) => (dialogConfirm.activator = target)"
|
||||
@delete-api-key="(apiKey) => showDialog(ACTION.DELETE, apiKey)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<FragmentApikeyGenerateDialog
|
||||
:activator="dialogGenerateActivator"
|
||||
:fullscreen="display.xs.value"
|
||||
/>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
@ -33,8 +41,10 @@ import ApikeyDeletionWarning from '@/components/apikey/DeletionWarning.vue'
|
|||
import ForceSyncWarning from '@/components/apikey/ForceSyncWarning.vue'
|
||||
import { useApiKeys, useDeleteApiKey } from '@/colada/users'
|
||||
import { useDeleteSyncPoints } from '@/colada/syncpoints'
|
||||
import { useDisplay } from 'vuetify'
|
||||
|
||||
const intl = useIntl()
|
||||
const display = useDisplay()
|
||||
|
||||
// API data
|
||||
const { data: apiKeys, error, isLoading, refetch: refetchApiKeys } = useApiKeys()
|
||||
|
|
@ -79,6 +89,7 @@ function showDialog(action: ACTION, apiKey?: components['schemas']['ApiKeyDto'])
|
|||
id: 'IE0XzE',
|
||||
}),
|
||||
closeOnSave: false,
|
||||
fullscreen: display.xs.value,
|
||||
}
|
||||
dialogConfirm.value.slotWarning = {
|
||||
component: markRaw(ApikeyDeletionWarning),
|
||||
|
|
@ -102,6 +113,7 @@ function showDialog(action: ACTION, apiKey?: components['schemas']['ApiKeyDto'])
|
|||
id: 'W3BUf7',
|
||||
}),
|
||||
closeOnSave: false,
|
||||
fullscreen: display.xs.value,
|
||||
}
|
||||
dialogConfirm.value.slotWarning = {
|
||||
component: markRaw(ForceSyncWarning),
|
||||
|
|
|
|||
|
|
@ -1,26 +1,31 @@
|
|||
<template>
|
||||
<v-empty-state
|
||||
v-if="error"
|
||||
icon="i-mdi:connection"
|
||||
:title="$formatMessage(commonMessages.somethingWentWrongTitle)"
|
||||
:text="$formatMessage(commonMessages.somethingWentWrongSubTitle)"
|
||||
/>
|
||||
|
||||
<template v-else-if="currentUser">
|
||||
<UserDetails :user="currentUser" />
|
||||
<v-btn
|
||||
:text="
|
||||
$formatMessage({
|
||||
description: 'User details screen: change password button',
|
||||
defaultMessage: 'Change password',
|
||||
id: 'sGsWvI',
|
||||
})
|
||||
"
|
||||
class="mt-8"
|
||||
@click="changePassword()"
|
||||
@mouseenter="dialogConfirmEdit.activator = $event.currentTarget"
|
||||
<v-container
|
||||
fluid
|
||||
class="pa-4"
|
||||
>
|
||||
<v-empty-state
|
||||
v-if="error"
|
||||
icon="i-mdi:connection"
|
||||
:title="$formatMessage(commonMessages.somethingWentWrongTitle)"
|
||||
:text="$formatMessage(commonMessages.somethingWentWrongSubTitle)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template v-else-if="currentUser">
|
||||
<UserDetails :user="currentUser" />
|
||||
<v-btn
|
||||
:text="
|
||||
$formatMessage({
|
||||
description: 'User details screen: change password button',
|
||||
defaultMessage: 'Change password',
|
||||
id: 'sGsWvI',
|
||||
})
|
||||
"
|
||||
class="mt-8"
|
||||
@click="changePassword()"
|
||||
@mouseenter="dialogConfirmEdit.activator = $event.currentTarget"
|
||||
/>
|
||||
</template>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
|
|||
|
|
@ -1,59 +1,61 @@
|
|||
<template>
|
||||
<v-skeleton-loader
|
||||
v-if="isPending"
|
||||
type="heading, text, paragraph@3"
|
||||
/>
|
||||
<v-container fluid>
|
||||
<v-skeleton-loader
|
||||
v-if="isPending"
|
||||
type="heading, text, paragraph@3"
|
||||
/>
|
||||
|
||||
<v-empty-state
|
||||
v-else-if="error"
|
||||
icon="i-mdi:connection"
|
||||
:title="$formatMessage(commonMessages.somethingWentWrongTitle)"
|
||||
:text="$formatMessage(commonMessages.somethingWentWrongSubTitle)"
|
||||
/>
|
||||
<v-empty-state
|
||||
v-else-if="error"
|
||||
icon="i-mdi:connection"
|
||||
:title="$formatMessage(commonMessages.somethingWentWrongTitle)"
|
||||
:text="$formatMessage(commonMessages.somethingWentWrongSubTitle)"
|
||||
/>
|
||||
|
||||
<template v-else-if="announcements">
|
||||
<div
|
||||
v-for="(item, index) in announcements.items"
|
||||
:key="index"
|
||||
>
|
||||
<AnnouncementCard
|
||||
class="mb-4"
|
||||
:item="item"
|
||||
@mark-read="markRead"
|
||||
/>
|
||||
|
||||
<!-- Bottom spacing for the FAB -->
|
||||
<template v-else-if="announcements">
|
||||
<div
|
||||
v-if="index == announcements.items.length - 1"
|
||||
class="mb-16"
|
||||
/>
|
||||
</div>
|
||||
v-for="(item, index) in announcements.items"
|
||||
:key="index"
|
||||
>
|
||||
<AnnouncementCard
|
||||
class="mb-4"
|
||||
:item="item"
|
||||
@mark-read="markRead"
|
||||
/>
|
||||
|
||||
<v-fab
|
||||
:active="unreadCount > 0"
|
||||
location="bottom right"
|
||||
app
|
||||
icon
|
||||
size="x-large"
|
||||
class="ms-n5"
|
||||
@click="markAllRead()"
|
||||
>
|
||||
<!-- Workaround for https://github.com/vuetifyjs/vuetify/issues/21439 -->
|
||||
<v-btn
|
||||
v-tooltip:start="
|
||||
$formatMessage({
|
||||
description: 'Announcements view: mark all as read button tooltip',
|
||||
defaultMessage: 'Mark all as read',
|
||||
id: 'da/wb0',
|
||||
})
|
||||
"
|
||||
color="success"
|
||||
<!-- Bottom spacing for the FAB -->
|
||||
<div
|
||||
v-if="index == announcements.items.length - 1"
|
||||
class="mb-16"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<v-fab
|
||||
:active="unreadCount > 0"
|
||||
location="bottom right"
|
||||
app
|
||||
icon
|
||||
size="x-large"
|
||||
icon="i-mdi:check-all"
|
||||
aria-label="mark all read"
|
||||
/>
|
||||
</v-fab>
|
||||
</template>
|
||||
class="ms-n5"
|
||||
@click="markAllRead()"
|
||||
>
|
||||
<!-- Workaround for https://github.com/vuetifyjs/vuetify/issues/21439 -->
|
||||
<v-btn
|
||||
v-tooltip:start="
|
||||
$formatMessage({
|
||||
description: 'Announcements view: mark all as read button tooltip',
|
||||
defaultMessage: 'Mark all as read',
|
||||
id: 'da/wb0',
|
||||
})
|
||||
"
|
||||
color="success"
|
||||
size="x-large"
|
||||
icon="i-mdi:check-all"
|
||||
aria-label="mark all read"
|
||||
/>
|
||||
</v-fab>
|
||||
</template>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
|
|||
|
|
@ -1,24 +1,29 @@
|
|||
<template>
|
||||
<v-skeleton-loader
|
||||
type="article@6, button@2"
|
||||
v-if="isPending"
|
||||
/>
|
||||
<v-container
|
||||
fluid
|
||||
class="pa-4"
|
||||
>
|
||||
<v-skeleton-loader
|
||||
type="article@6, button@2"
|
||||
v-if="isPending"
|
||||
/>
|
||||
|
||||
<v-empty-state
|
||||
v-else-if="error"
|
||||
icon="i-mdi:connection"
|
||||
:title="$formatMessage(commonMessages.somethingWentWrongTitle)"
|
||||
:text="$formatMessage(commonMessages.somethingWentWrongSubTitle)"
|
||||
/>
|
||||
<template v-else-if="settings">
|
||||
<div class="d-flex">
|
||||
<ServerSettings
|
||||
:settings="settings"
|
||||
:loading="loading"
|
||||
@update-settings="(s) => saveSettings(s)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<v-empty-state
|
||||
v-else-if="error"
|
||||
icon="i-mdi:connection"
|
||||
:title="$formatMessage(commonMessages.somethingWentWrongTitle)"
|
||||
:text="$formatMessage(commonMessages.somethingWentWrongSubTitle)"
|
||||
/>
|
||||
<template v-else-if="settings">
|
||||
<div class="d-flex">
|
||||
<ServerSettings
|
||||
:settings="settings"
|
||||
:loading="loading"
|
||||
@update-settings="(s) => saveSettings(s)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
|
|||
|
|
@ -1,62 +1,64 @@
|
|||
<template>
|
||||
<v-skeleton-loader
|
||||
v-if="isLoading"
|
||||
type="heading, text, paragraph@3"
|
||||
/>
|
||||
<v-container fluid>
|
||||
<v-skeleton-loader
|
||||
v-if="isLoading"
|
||||
type="heading, text, paragraph@3"
|
||||
/>
|
||||
|
||||
<v-empty-state
|
||||
v-else-if="error"
|
||||
icon="i-mdi:connection"
|
||||
:title="$formatMessage(commonMessages.somethingWentWrongTitle)"
|
||||
:text="$formatMessage(commonMessages.somethingWentWrongSubTitle)"
|
||||
/>
|
||||
<v-empty-state
|
||||
v-else-if="error"
|
||||
icon="i-mdi:connection"
|
||||
:title="$formatMessage(commonMessages.somethingWentWrongTitle)"
|
||||
:text="$formatMessage(commonMessages.somethingWentWrongSubTitle)"
|
||||
/>
|
||||
|
||||
<template v-else-if="releases">
|
||||
<v-row>
|
||||
<v-col>
|
||||
<div v-if="isLatestVersion == true">
|
||||
<v-alert
|
||||
type="success"
|
||||
variant="tonal"
|
||||
>
|
||||
{{
|
||||
$formatMessage({
|
||||
description: 'Updates view: banner shown at the top',
|
||||
defaultMessage: 'The latest version of Komga is already installed',
|
||||
id: 'WNY0pu',
|
||||
})
|
||||
}}
|
||||
</v-alert>
|
||||
</div>
|
||||
<div v-if="isLatestVersion == false">
|
||||
<v-alert
|
||||
type="warning"
|
||||
variant="tonal"
|
||||
>
|
||||
{{
|
||||
$formatMessage({
|
||||
description: 'Updates view: banner shown at the top',
|
||||
defaultMessage: 'Updates are available',
|
||||
id: 'n1Ik+L',
|
||||
})
|
||||
}}
|
||||
</v-alert>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<template v-else-if="releases">
|
||||
<v-row>
|
||||
<v-col>
|
||||
<div v-if="isLatestVersion == true">
|
||||
<v-alert
|
||||
type="success"
|
||||
variant="tonal"
|
||||
>
|
||||
{{
|
||||
$formatMessage({
|
||||
description: 'Updates view: banner shown at the top',
|
||||
defaultMessage: 'The latest version of Komga is already installed',
|
||||
id: 'WNY0pu',
|
||||
})
|
||||
}}
|
||||
</v-alert>
|
||||
</div>
|
||||
<div v-if="isLatestVersion == false">
|
||||
<v-alert
|
||||
type="warning"
|
||||
variant="tonal"
|
||||
>
|
||||
{{
|
||||
$formatMessage({
|
||||
description: 'Updates view: banner shown at the top',
|
||||
defaultMessage: 'Updates are available',
|
||||
id: 'n1Ik+L',
|
||||
})
|
||||
}}
|
||||
</v-alert>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<div
|
||||
v-for="(release, index) in releases"
|
||||
:key="index"
|
||||
>
|
||||
<ReleaseCard
|
||||
:release="release"
|
||||
:current="release.version == currentVersion"
|
||||
:latest="release.version == latest?.version"
|
||||
class="my-4"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<div
|
||||
v-for="(release, index) in releases"
|
||||
:key="index"
|
||||
>
|
||||
<ReleaseCard
|
||||
:release="release"
|
||||
:current="release.version == currentVersion"
|
||||
:latest="release.version == latest?.version"
|
||||
class="my-4"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
|
|||
|
|
@ -1,25 +1,30 @@
|
|||
<template>
|
||||
<v-empty-state
|
||||
v-if="error"
|
||||
icon="i-mdi:connection"
|
||||
:title="$formatMessage(commonMessages.somethingWentWrongTitle)"
|
||||
:text="$formatMessage(commonMessages.somethingWentWrongSubTitle)"
|
||||
/>
|
||||
|
||||
<template v-else>
|
||||
<FragmentUserTable
|
||||
:users="users"
|
||||
:loading="isLoading"
|
||||
@add-user="showDialog(ACTION.ADD)"
|
||||
@change-password="(user) => showDialog(ACTION.PASSWORD, user)"
|
||||
@edit-user="(user) => showDialog(ACTION.EDIT, user)"
|
||||
@delete-user="(user) => showDialog(ACTION.DELETE, user)"
|
||||
@enter-add-user="(target) => (dialogConfirmEdit.activator = target)"
|
||||
@enter-edit-user="(target) => (dialogConfirmEdit.activator = target)"
|
||||
@enter-change-password="(target) => (dialogConfirmEdit.activator = target)"
|
||||
@enter-delete-user="(target) => (dialogConfirm.activator = target)"
|
||||
<v-container
|
||||
fluid
|
||||
class="pa-0 pa-sm-4"
|
||||
>
|
||||
<v-empty-state
|
||||
v-if="error"
|
||||
icon="i-mdi:connection"
|
||||
:title="$formatMessage(commonMessages.somethingWentWrongTitle)"
|
||||
:text="$formatMessage(commonMessages.somethingWentWrongSubTitle)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<FragmentUserTable
|
||||
:users="users"
|
||||
:loading="isLoading"
|
||||
@add-user="showDialog(ACTION.ADD)"
|
||||
@change-password="(user) => showDialog(ACTION.PASSWORD, user)"
|
||||
@edit-user="(user) => showDialog(ACTION.EDIT, user)"
|
||||
@delete-user="(user) => showDialog(ACTION.DELETE, user)"
|
||||
@enter-add-user="(target) => (dialogConfirmEdit.activator = target)"
|
||||
@enter-edit-user="(target) => (dialogConfirmEdit.activator = target)"
|
||||
@enter-change-password="(target) => (dialogConfirmEdit.activator = target)"
|
||||
@enter-delete-user="(target) => (dialogConfirm.activator = target)"
|
||||
/>
|
||||
</template>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
@ -85,6 +90,7 @@ function showDialog(action: ACTION, user?: components['schemas']['UserDto']) {
|
|||
defaultMessage: 'Add User',
|
||||
id: 'Bl30xt',
|
||||
}),
|
||||
scrollable: true,
|
||||
maxWidth: 600,
|
||||
closeOnSave: false,
|
||||
fullscreen: display.xs.value,
|
||||
|
|
@ -117,6 +123,7 @@ function showDialog(action: ACTION, user?: components['schemas']['UserDto']) {
|
|||
id: 'Zh8AOV',
|
||||
}),
|
||||
subtitle: user?.email,
|
||||
scrollable: true,
|
||||
maxWidth: 600,
|
||||
closeOnSave: false,
|
||||
fullscreen: display.xs.value,
|
||||
|
|
@ -158,6 +165,7 @@ function showDialog(action: ACTION, user?: components['schemas']['UserDto']) {
|
|||
id: 'o8WeX3',
|
||||
}),
|
||||
closeOnSave: false,
|
||||
fullscreen: display.xs.value,
|
||||
}
|
||||
dialogConfirm.value.slotWarning = {
|
||||
component: markRaw(UserDeletionWarning),
|
||||
|
|
@ -171,6 +179,7 @@ function showDialog(action: ACTION, user?: components['schemas']['UserDto']) {
|
|||
subtitle: user?.email,
|
||||
maxWidth: 400,
|
||||
closeOnSave: false,
|
||||
fullscreen: display.xs.value,
|
||||
}
|
||||
dialogConfirmEdit.value.slot = {
|
||||
component: markRaw(UserFormChangePassword),
|
||||
|
|
|
|||
Loading…
Reference in a new issue