mirror of
https://github.com/gotson/komga.git
synced 2026-01-02 22:08:58 +01:00
vuetify stuff
This commit is contained in:
parent
87102c9917
commit
49f924c8d5
84 changed files with 10846 additions and 10351 deletions
8
next-ui/.prettierrc.json
Normal file
8
next-ui/.prettierrc.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"$schema": "https://json.schemastore.org/prettierrc",
|
||||
"singleQuote": true,
|
||||
"printWidth": 100,
|
||||
"trailingComma": "all",
|
||||
"semi": false,
|
||||
"singleAttributePerLine": true
|
||||
}
|
||||
8
next-ui/dir2json.d.ts
vendored
8
next-ui/dir2json.d.ts
vendored
|
|
@ -8,7 +8,7 @@ declare module "*i18n?dir2json&ext=.json&1" {
|
|||
export default json;
|
||||
}
|
||||
|
||||
declare module "*dir2json" {
|
||||
const json: any;
|
||||
export default json;
|
||||
}
|
||||
declare module '*dir2json' {
|
||||
const json: any
|
||||
export default json
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,9 @@
|
|||
*/
|
||||
|
||||
import pluginVue from 'eslint-plugin-vue'
|
||||
import {defineConfigWithVueTs, vueTsConfigs} from '@vue/eslint-config-typescript'
|
||||
import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript'
|
||||
import formatjs from 'eslint-plugin-formatjs'
|
||||
import eslintConfigPrettier from 'eslint-config-prettier'
|
||||
|
||||
export default defineConfigWithVueTs(
|
||||
{
|
||||
|
|
@ -16,14 +17,21 @@ export default defineConfigWithVueTs(
|
|||
|
||||
{
|
||||
name: 'app/files-to-ignore',
|
||||
ignores: ['**/dist/**', '**/dist-ssr/**', '**/coverage/**', 'openapi-generator.mts'],
|
||||
ignores: [
|
||||
'**/dist/**',
|
||||
'**/dist-ssr/**',
|
||||
'**/coverage/**',
|
||||
'openapi-generator.mts',
|
||||
'**/generated/openapi/komga.d.ts',
|
||||
],
|
||||
},
|
||||
|
||||
...pluginVue.configs['flat/recommended'],
|
||||
...pluginVue.configs['flat/essential'],
|
||||
vueTsConfigs.recommendedTypeChecked,
|
||||
|
||||
{
|
||||
rules: {
|
||||
'prefer-promise-reject-errors': 'off',
|
||||
'@typescript-eslint/no-unused-expressions': [
|
||||
'error',
|
||||
{
|
||||
|
|
@ -31,8 +39,13 @@ export default defineConfigWithVueTs(
|
|||
allowTernary: true,
|
||||
},
|
||||
],
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'error',
|
||||
{ caughtErrors: 'all', caughtErrorsIgnorePattern: '^ignore' },
|
||||
],
|
||||
'no-empty': ['error', { allowEmptyCatch: true }],
|
||||
'vue/multi-word-component-names': 'off',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
formatjs.configs.recommended,
|
||||
|
|
@ -46,9 +59,11 @@ export default defineConfigWithVueTs(
|
|||
'error',
|
||||
{
|
||||
idInterpolationPattern: '[sha512:contenthash:base64:6]',
|
||||
idWhitelist: ['app.*']
|
||||
idWhitelist: ['app.*'],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
eslintConfigPrettier,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,13 +1,22 @@
|
|||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Welcome to Vuetify 3</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link
|
||||
rel="icon"
|
||||
href="/favicon.ico"
|
||||
/>
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0"
|
||||
/>
|
||||
<title>Welcome to Vuetify 3</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script
|
||||
type="module"
|
||||
src="/src/main.ts"
|
||||
></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
132
next-ui/package-lock.json
generated
132
next-ui/package-lock.json
generated
|
|
@ -14,6 +14,7 @@
|
|||
"core-js": "^3.37.1",
|
||||
"marked": "^15.0.12",
|
||||
"openapi-fetch": "^0.14.0",
|
||||
"pinia": "^3.0.2",
|
||||
"pinia-plugin-persistedstate": "^4.3.0",
|
||||
"vue": "^3.5.14",
|
||||
"vue-intl": "^6.5.25",
|
||||
|
|
@ -26,14 +27,16 @@
|
|||
"@tsconfig/node22": "^22.0.2",
|
||||
"@types/node": "^22.15.21",
|
||||
"@vitejs/plugin-vue": "^5.1.4",
|
||||
"@vue/eslint-config-prettier": "^10.2.0",
|
||||
"@vue/eslint-config-typescript": "^14.1.3",
|
||||
"@vue/tsconfig": "^0.7.0",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint-config-prettier": "^10.1.5",
|
||||
"eslint-plugin-formatjs": "^5.3.1",
|
||||
"eslint-plugin-vue": "^10.1.0",
|
||||
"npm-run-all2": "^8.0.3",
|
||||
"openapi-typescript": "^7.8.0",
|
||||
"pinia": "^3.0.2",
|
||||
"prettier": "^3.5.3",
|
||||
"sass": "^1.89.0",
|
||||
"sass-embedded": "^1.89.0",
|
||||
"typescript": "^5.8.3",
|
||||
|
|
@ -1384,6 +1387,19 @@
|
|||
"@pinia/colada": ">=0.16.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@pkgr/core": {
|
||||
"version": "0.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.7.tgz",
|
||||
"integrity": "sha512-YLT9Zo3oNPJoBjBc4q8G2mjU4tqIbf5CEOORbUUr48dCD9q3umJ3IPlVqOqDakPfd2HuwccBaqlGhN4Gmr5OWg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.18.0 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/pkgr"
|
||||
}
|
||||
},
|
||||
"node_modules/@redocly/ajv": {
|
||||
"version": "8.11.2",
|
||||
"resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.11.2.tgz",
|
||||
|
|
@ -2140,6 +2156,21 @@
|
|||
"rfdc": "^1.4.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/eslint-config-prettier": {
|
||||
"version": "10.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@vue/eslint-config-prettier/-/eslint-config-prettier-10.2.0.tgz",
|
||||
"integrity": "sha512-GL3YBLwv/+b86yHcNNfPJxOTtVFJ4Mbc9UU3zR+KVoG7SwGTjPT+32fXamscNumElhcpXW3mT0DgzS9w32S7Bw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"eslint-config-prettier": "^10.0.1",
|
||||
"eslint-plugin-prettier": "^5.2.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": ">= 8.21.0",
|
||||
"prettier": ">= 3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/eslint-config-typescript": {
|
||||
"version": "14.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@vue/eslint-config-typescript/-/eslint-config-typescript-14.5.0.tgz",
|
||||
|
|
@ -3110,6 +3141,22 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-config-prettier": {
|
||||
"version": "10.1.5",
|
||||
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.5.tgz",
|
||||
"integrity": "sha512-zc1UmCpNltmVY34vuLRV61r1K27sWuX39E+uyUnY8xS2Bex88VV9cugG+UZbRSRGtGyFboj+D8JODyme1plMpw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"eslint-config-prettier": "bin/cli.js"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/eslint-config-prettier"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-formatjs": {
|
||||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-formatjs/-/eslint-plugin-formatjs-5.3.1.tgz",
|
||||
|
|
@ -3131,6 +3178,37 @@
|
|||
"eslint": "^9.23.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-prettier": {
|
||||
"version": "5.4.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.4.1.tgz",
|
||||
"integrity": "sha512-9dF+KuU/Ilkq27A8idRP7N2DH8iUR6qXcjF3FR2wETY21PZdBrIjwCau8oboyGj9b7etWmTGEeM8e7oOed6ZWg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"prettier-linter-helpers": "^1.0.0",
|
||||
"synckit": "^0.11.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.18.0 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/eslint-plugin-prettier"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/eslint": ">=8.0.0",
|
||||
"eslint": ">=8.0.0",
|
||||
"eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0",
|
||||
"prettier": ">=3.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/eslint": {
|
||||
"optional": true
|
||||
},
|
||||
"eslint-config-prettier": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-vue": {
|
||||
"version": "10.1.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-10.1.0.tgz",
|
||||
|
|
@ -3316,6 +3394,13 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-diff": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz",
|
||||
"integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/fast-glob": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
|
||||
|
|
@ -4639,6 +4724,35 @@
|
|||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "3.5.3",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz",
|
||||
"integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/prettier-linter-helpers": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
|
||||
"integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-diff": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||
|
|
@ -5439,6 +5553,22 @@
|
|||
"node": ">=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/synckit": {
|
||||
"version": "0.11.8",
|
||||
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.8.tgz",
|
||||
"integrity": "sha512-+XZ+r1XGIJGeQk3VvXhT6xx/VpbHsRzsTkGgF6E5RX9TTXD0118l87puaEBZ566FhqblC6U0d4XnubznJDm30A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@pkgr/core": "^0.2.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.18.0 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/synckit"
|
||||
}
|
||||
},
|
||||
"node_modules/tinyexec": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz",
|
||||
|
|
|
|||
|
|
@ -9,7 +9,10 @@
|
|||
"preview": "vite preview",
|
||||
"build-only": "vite build",
|
||||
"type-check": "vue-tsc --build --force",
|
||||
"lint": "eslint . --fix",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "npm run lint -- --fix",
|
||||
"prettier": "prettier --check \"**/*.{js,ts,vue,scss,html,md,json}\" --ignore-path .gitignore",
|
||||
"prettier:fix": "npm run prettier -- --write",
|
||||
"openapi-generate": "npx tsx ./openapi-generator.mts",
|
||||
"i18n-extract": "formatjs extract \"src/**/*.{ts,tsx,vue}\" --ignore=\"**/*.d.ts\" --out-file i18n/en.json",
|
||||
"i18n-compile": "formatjs compile-folder i18n src/i18n",
|
||||
|
|
@ -22,6 +25,7 @@
|
|||
"core-js": "^3.37.1",
|
||||
"marked": "^15.0.12",
|
||||
"openapi-fetch": "^0.14.0",
|
||||
"pinia": "^3.0.2",
|
||||
"pinia-plugin-persistedstate": "^4.3.0",
|
||||
"vue": "^3.5.14",
|
||||
"vue-intl": "^6.5.25",
|
||||
|
|
@ -34,14 +38,16 @@
|
|||
"@tsconfig/node22": "^22.0.2",
|
||||
"@types/node": "^22.15.21",
|
||||
"@vitejs/plugin-vue": "^5.1.4",
|
||||
"@vue/eslint-config-prettier": "^10.2.0",
|
||||
"@vue/eslint-config-typescript": "^14.1.3",
|
||||
"@vue/tsconfig": "^0.7.0",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint-config-prettier": "^10.1.5",
|
||||
"eslint-plugin-formatjs": "^5.3.1",
|
||||
"eslint-plugin-vue": "^10.1.0",
|
||||
"npm-run-all2": "^8.0.3",
|
||||
"openapi-typescript": "^7.8.0",
|
||||
"pinia": "^3.0.2",
|
||||
"prettier": "^3.5.3",
|
||||
"sass": "^1.89.0",
|
||||
"sass-embedded": "^1.89.0",
|
||||
"typescript": "^5.8.3",
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
<script lang="ts" setup>
|
||||
import { useTheme } from 'vuetify'
|
||||
import {useAppStore} from '@/stores/app'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import { usePreferredDark } from '@vueuse/core'
|
||||
|
||||
const appStore = useAppStore()
|
||||
|
|
@ -14,19 +14,21 @@ const theme = useTheme()
|
|||
const prefersDark = usePreferredDark()
|
||||
|
||||
function updateTheme(selectedTheme: string, prefersDark: boolean) {
|
||||
if(selectedTheme === 'system') {
|
||||
if (selectedTheme === 'system') {
|
||||
theme.global.name.value = prefersDark ? 'dark' : 'light'
|
||||
} else {
|
||||
theme.global.name.value = selectedTheme
|
||||
}
|
||||
}
|
||||
|
||||
watch([() => appStore.theme, prefersDark], ([selectedTheme, prefersDark]) => updateTheme(selectedTheme, prefersDark))
|
||||
watch([() => appStore.theme, prefersDark], ([selectedTheme, prefersDark]) =>
|
||||
updateTheme(selectedTheme, prefersDark),
|
||||
)
|
||||
|
||||
// trigger an update on startup to get the proper theme loaded
|
||||
updateTheme(appStore.theme, prefersDark.value)
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@import "styles/global.scss";
|
||||
@import 'styles/global.scss';
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,17 +1,32 @@
|
|||
import type {Middleware} from 'openapi-fetch'
|
||||
import type { Middleware } from 'openapi-fetch'
|
||||
import createClient from 'openapi-fetch'
|
||||
import type {paths} from '@/generated/openapi/komga'
|
||||
import type { paths } from '@/generated/openapi/komga'
|
||||
|
||||
// Middleware that throws on error, so it works with Pinia Colada
|
||||
const coladaMiddleware: Middleware = {
|
||||
async onResponse({response}: { response: Response }) {
|
||||
async onResponse({ response }: { response: Response }) {
|
||||
if (!response.ok) {
|
||||
const body = await response.json()
|
||||
throw new Error(`${response.url}: ${response.status} ${response.statusText}`, {cause: body})
|
||||
let body: unknown
|
||||
try {
|
||||
body = await response.json()
|
||||
} catch (ignoreErr) {}
|
||||
throw new Error(`${response.url}: ${response.status} ${response.statusText}`, {
|
||||
cause: {
|
||||
body: body,
|
||||
status: response.status,
|
||||
},
|
||||
})
|
||||
}
|
||||
// return response untouched
|
||||
return undefined
|
||||
},
|
||||
onError() {
|
||||
throw new Error('error', {
|
||||
cause: {
|
||||
message: 'Server is unreachable',
|
||||
},
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
const client = createClient<paths>({
|
||||
|
|
@ -19,8 +34,14 @@ const client = createClient<paths>({
|
|||
// required to pass the session cookie on all requests
|
||||
credentials: 'include',
|
||||
// required to avoid browser basic-auth popups
|
||||
headers: {'X-Requested-With': 'XMLHttpRequest'},
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' },
|
||||
})
|
||||
client.use(coladaMiddleware)
|
||||
|
||||
export interface ErrorCause {
|
||||
body?: unknown
|
||||
status?: number
|
||||
message?: string
|
||||
}
|
||||
|
||||
export const komgaClient = client
|
||||
|
|
|
|||
261
next-ui/src/auto-imports.d.ts
vendored
261
next-ui/src/auto-imports.d.ts
vendored
|
|
@ -1,77 +1,94 @@
|
|||
/* eslint-disable */
|
||||
/* prettier-ignore */
|
||||
// prettier-ignore
|
||||
// @ts-nocheck
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
// Generated by unplugin-auto-import
|
||||
// biome-ignore lint: disable
|
||||
export {}
|
||||
declare global {
|
||||
const EffectScope: typeof import('vue')['EffectScope']
|
||||
const computed: typeof import('vue')['computed']
|
||||
const createApp: typeof import('vue')['createApp']
|
||||
const customRef: typeof import('vue')['customRef']
|
||||
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
|
||||
const defineComponent: typeof import('vue')['defineComponent']
|
||||
const effectScope: typeof import('vue')['effectScope']
|
||||
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
|
||||
const getCurrentScope: typeof import('vue')['getCurrentScope']
|
||||
const h: typeof import('vue')['h']
|
||||
const inject: typeof import('vue')['inject']
|
||||
const isProxy: typeof import('vue')['isProxy']
|
||||
const isReactive: typeof import('vue')['isReactive']
|
||||
const isReadonly: typeof import('vue')['isReadonly']
|
||||
const isRef: typeof import('vue')['isRef']
|
||||
const markRaw: typeof import('vue')['markRaw']
|
||||
const nextTick: typeof import('vue')['nextTick']
|
||||
const onActivated: typeof import('vue')['onActivated']
|
||||
const onBeforeMount: typeof import('vue')['onBeforeMount']
|
||||
const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave']
|
||||
const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate']
|
||||
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
|
||||
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
|
||||
const onDeactivated: typeof import('vue')['onDeactivated']
|
||||
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
|
||||
const onMounted: typeof import('vue')['onMounted']
|
||||
const onRenderTracked: typeof import('vue')['onRenderTracked']
|
||||
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
|
||||
const onScopeDispose: typeof import('vue')['onScopeDispose']
|
||||
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
|
||||
const onUnmounted: typeof import('vue')['onUnmounted']
|
||||
const onUpdated: typeof import('vue')['onUpdated']
|
||||
const onWatcherCleanup: typeof import('vue')['onWatcherCleanup']
|
||||
const provide: typeof import('vue')['provide']
|
||||
const reactive: typeof import('vue')['reactive']
|
||||
const readonly: typeof import('vue')['readonly']
|
||||
const ref: typeof import('vue')['ref']
|
||||
const resolveComponent: typeof import('vue')['resolveComponent']
|
||||
const shallowReactive: typeof import('vue')['shallowReactive']
|
||||
const shallowReadonly: typeof import('vue')['shallowReadonly']
|
||||
const shallowRef: typeof import('vue')['shallowRef']
|
||||
const toRaw: typeof import('vue')['toRaw']
|
||||
const toRef: typeof import('vue')['toRef']
|
||||
const toRefs: typeof import('vue')['toRefs']
|
||||
const toValue: typeof import('vue')['toValue']
|
||||
const triggerRef: typeof import('vue')['triggerRef']
|
||||
const unref: typeof import('vue')['unref']
|
||||
const useAttrs: typeof import('vue')['useAttrs']
|
||||
const useCssModule: typeof import('vue')['useCssModule']
|
||||
const useCssVars: typeof import('vue')['useCssVars']
|
||||
const useId: typeof import('vue')['useId']
|
||||
const useLink: typeof import('vue-router')['useLink']
|
||||
const useModel: typeof import('vue')['useModel']
|
||||
const useRoute: typeof import('vue-router/auto')['useRoute']
|
||||
const useRouter: typeof import('vue-router/auto')['useRouter']
|
||||
const useSlots: typeof import('vue')['useSlots']
|
||||
const useTemplateRef: typeof import('vue')['useTemplateRef']
|
||||
const watch: typeof import('vue')['watch']
|
||||
const watchEffect: typeof import('vue')['watchEffect']
|
||||
const watchPostEffect: typeof import('vue')['watchPostEffect']
|
||||
const watchSyncEffect: typeof import('vue')['watchSyncEffect']
|
||||
const EffectScope: (typeof import('vue'))['EffectScope']
|
||||
const computed: (typeof import('vue'))['computed']
|
||||
const createApp: (typeof import('vue'))['createApp']
|
||||
const customRef: (typeof import('vue'))['customRef']
|
||||
const defineAsyncComponent: (typeof import('vue'))['defineAsyncComponent']
|
||||
const defineComponent: (typeof import('vue'))['defineComponent']
|
||||
const effectScope: (typeof import('vue'))['effectScope']
|
||||
const getCurrentInstance: (typeof import('vue'))['getCurrentInstance']
|
||||
const getCurrentScope: (typeof import('vue'))['getCurrentScope']
|
||||
const h: (typeof import('vue'))['h']
|
||||
const inject: (typeof import('vue'))['inject']
|
||||
const isProxy: (typeof import('vue'))['isProxy']
|
||||
const isReactive: (typeof import('vue'))['isReactive']
|
||||
const isReadonly: (typeof import('vue'))['isReadonly']
|
||||
const isRef: (typeof import('vue'))['isRef']
|
||||
const markRaw: (typeof import('vue'))['markRaw']
|
||||
const nextTick: (typeof import('vue'))['nextTick']
|
||||
const onActivated: (typeof import('vue'))['onActivated']
|
||||
const onBeforeMount: (typeof import('vue'))['onBeforeMount']
|
||||
const onBeforeRouteLeave: (typeof import('vue-router'))['onBeforeRouteLeave']
|
||||
const onBeforeRouteUpdate: (typeof import('vue-router'))['onBeforeRouteUpdate']
|
||||
const onBeforeUnmount: (typeof import('vue'))['onBeforeUnmount']
|
||||
const onBeforeUpdate: (typeof import('vue'))['onBeforeUpdate']
|
||||
const onDeactivated: (typeof import('vue'))['onDeactivated']
|
||||
const onErrorCaptured: (typeof import('vue'))['onErrorCaptured']
|
||||
const onMounted: (typeof import('vue'))['onMounted']
|
||||
const onRenderTracked: (typeof import('vue'))['onRenderTracked']
|
||||
const onRenderTriggered: (typeof import('vue'))['onRenderTriggered']
|
||||
const onScopeDispose: (typeof import('vue'))['onScopeDispose']
|
||||
const onServerPrefetch: (typeof import('vue'))['onServerPrefetch']
|
||||
const onUnmounted: (typeof import('vue'))['onUnmounted']
|
||||
const onUpdated: (typeof import('vue'))['onUpdated']
|
||||
const onWatcherCleanup: (typeof import('vue'))['onWatcherCleanup']
|
||||
const provide: (typeof import('vue'))['provide']
|
||||
const reactive: (typeof import('vue'))['reactive']
|
||||
const readonly: (typeof import('vue'))['readonly']
|
||||
const ref: (typeof import('vue'))['ref']
|
||||
const resolveComponent: (typeof import('vue'))['resolveComponent']
|
||||
const shallowReactive: (typeof import('vue'))['shallowReactive']
|
||||
const shallowReadonly: (typeof import('vue'))['shallowReadonly']
|
||||
const shallowRef: (typeof import('vue'))['shallowRef']
|
||||
const toRaw: (typeof import('vue'))['toRaw']
|
||||
const toRef: (typeof import('vue'))['toRef']
|
||||
const toRefs: (typeof import('vue'))['toRefs']
|
||||
const toValue: (typeof import('vue'))['toValue']
|
||||
const triggerRef: (typeof import('vue'))['triggerRef']
|
||||
const unref: (typeof import('vue'))['unref']
|
||||
const useAttrs: (typeof import('vue'))['useAttrs']
|
||||
const useCssModule: (typeof import('vue'))['useCssModule']
|
||||
const useCssVars: (typeof import('vue'))['useCssVars']
|
||||
const useId: (typeof import('vue'))['useId']
|
||||
const useLink: (typeof import('vue-router'))['useLink']
|
||||
const useModel: (typeof import('vue'))['useModel']
|
||||
const useRoute: (typeof import('vue-router/auto'))['useRoute']
|
||||
const useRouter: (typeof import('vue-router/auto'))['useRouter']
|
||||
const useSlots: (typeof import('vue'))['useSlots']
|
||||
const useTemplateRef: (typeof import('vue'))['useTemplateRef']
|
||||
const watch: (typeof import('vue'))['watch']
|
||||
const watchEffect: (typeof import('vue'))['watchEffect']
|
||||
const watchPostEffect: (typeof import('vue'))['watchPostEffect']
|
||||
const watchSyncEffect: (typeof import('vue'))['watchSyncEffect']
|
||||
}
|
||||
// for type re-export
|
||||
declare global {
|
||||
// @ts-ignore
|
||||
export type { Component, Slot, Slots, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
|
||||
export type {
|
||||
Component,
|
||||
Slot,
|
||||
Slots,
|
||||
ComponentPublicInstance,
|
||||
ComputedRef,
|
||||
DirectiveBinding,
|
||||
ExtractDefaultPropTypes,
|
||||
ExtractPropTypes,
|
||||
ExtractPublicPropTypes,
|
||||
InjectionKey,
|
||||
PropType,
|
||||
Ref,
|
||||
MaybeRef,
|
||||
MaybeRefOrGetter,
|
||||
VNode,
|
||||
WritableComputedRef,
|
||||
} from 'vue'
|
||||
import('vue')
|
||||
}
|
||||
|
||||
|
|
@ -80,63 +97,63 @@ import { UnwrapRef } from 'vue'
|
|||
declare module 'vue' {
|
||||
interface GlobalComponents {}
|
||||
interface ComponentCustomProperties {
|
||||
readonly EffectScope: UnwrapRef<typeof import('vue')['EffectScope']>
|
||||
readonly computed: UnwrapRef<typeof import('vue')['computed']>
|
||||
readonly createApp: UnwrapRef<typeof import('vue')['createApp']>
|
||||
readonly customRef: UnwrapRef<typeof import('vue')['customRef']>
|
||||
readonly defineAsyncComponent: UnwrapRef<typeof import('vue')['defineAsyncComponent']>
|
||||
readonly defineComponent: UnwrapRef<typeof import('vue')['defineComponent']>
|
||||
readonly effectScope: UnwrapRef<typeof import('vue')['effectScope']>
|
||||
readonly getCurrentInstance: UnwrapRef<typeof import('vue')['getCurrentInstance']>
|
||||
readonly getCurrentScope: UnwrapRef<typeof import('vue')['getCurrentScope']>
|
||||
readonly h: UnwrapRef<typeof import('vue')['h']>
|
||||
readonly inject: UnwrapRef<typeof import('vue')['inject']>
|
||||
readonly isProxy: UnwrapRef<typeof import('vue')['isProxy']>
|
||||
readonly isReactive: UnwrapRef<typeof import('vue')['isReactive']>
|
||||
readonly isReadonly: UnwrapRef<typeof import('vue')['isReadonly']>
|
||||
readonly isRef: UnwrapRef<typeof import('vue')['isRef']>
|
||||
readonly markRaw: UnwrapRef<typeof import('vue')['markRaw']>
|
||||
readonly nextTick: UnwrapRef<typeof import('vue')['nextTick']>
|
||||
readonly onActivated: UnwrapRef<typeof import('vue')['onActivated']>
|
||||
readonly onBeforeMount: UnwrapRef<typeof import('vue')['onBeforeMount']>
|
||||
readonly onBeforeUnmount: UnwrapRef<typeof import('vue')['onBeforeUnmount']>
|
||||
readonly onBeforeUpdate: UnwrapRef<typeof import('vue')['onBeforeUpdate']>
|
||||
readonly onDeactivated: UnwrapRef<typeof import('vue')['onDeactivated']>
|
||||
readonly onErrorCaptured: UnwrapRef<typeof import('vue')['onErrorCaptured']>
|
||||
readonly onMounted: UnwrapRef<typeof import('vue')['onMounted']>
|
||||
readonly onRenderTracked: UnwrapRef<typeof import('vue')['onRenderTracked']>
|
||||
readonly onRenderTriggered: UnwrapRef<typeof import('vue')['onRenderTriggered']>
|
||||
readonly onScopeDispose: UnwrapRef<typeof import('vue')['onScopeDispose']>
|
||||
readonly onServerPrefetch: UnwrapRef<typeof import('vue')['onServerPrefetch']>
|
||||
readonly onUnmounted: UnwrapRef<typeof import('vue')['onUnmounted']>
|
||||
readonly onUpdated: UnwrapRef<typeof import('vue')['onUpdated']>
|
||||
readonly onWatcherCleanup: UnwrapRef<typeof import('vue')['onWatcherCleanup']>
|
||||
readonly provide: UnwrapRef<typeof import('vue')['provide']>
|
||||
readonly reactive: UnwrapRef<typeof import('vue')['reactive']>
|
||||
readonly readonly: UnwrapRef<typeof import('vue')['readonly']>
|
||||
readonly ref: UnwrapRef<typeof import('vue')['ref']>
|
||||
readonly resolveComponent: UnwrapRef<typeof import('vue')['resolveComponent']>
|
||||
readonly shallowReactive: UnwrapRef<typeof import('vue')['shallowReactive']>
|
||||
readonly shallowReadonly: UnwrapRef<typeof import('vue')['shallowReadonly']>
|
||||
readonly shallowRef: UnwrapRef<typeof import('vue')['shallowRef']>
|
||||
readonly toRaw: UnwrapRef<typeof import('vue')['toRaw']>
|
||||
readonly toRef: UnwrapRef<typeof import('vue')['toRef']>
|
||||
readonly toRefs: UnwrapRef<typeof import('vue')['toRefs']>
|
||||
readonly toValue: UnwrapRef<typeof import('vue')['toValue']>
|
||||
readonly triggerRef: UnwrapRef<typeof import('vue')['triggerRef']>
|
||||
readonly unref: UnwrapRef<typeof import('vue')['unref']>
|
||||
readonly useAttrs: UnwrapRef<typeof import('vue')['useAttrs']>
|
||||
readonly useCssModule: UnwrapRef<typeof import('vue')['useCssModule']>
|
||||
readonly useCssVars: UnwrapRef<typeof import('vue')['useCssVars']>
|
||||
readonly useId: UnwrapRef<typeof import('vue')['useId']>
|
||||
readonly useModel: UnwrapRef<typeof import('vue')['useModel']>
|
||||
readonly useRoute: UnwrapRef<typeof import('vue-router/auto')['useRoute']>
|
||||
readonly useRouter: UnwrapRef<typeof import('vue-router/auto')['useRouter']>
|
||||
readonly useSlots: UnwrapRef<typeof import('vue')['useSlots']>
|
||||
readonly useTemplateRef: UnwrapRef<typeof import('vue')['useTemplateRef']>
|
||||
readonly watch: UnwrapRef<typeof import('vue')['watch']>
|
||||
readonly watchEffect: UnwrapRef<typeof import('vue')['watchEffect']>
|
||||
readonly watchPostEffect: UnwrapRef<typeof import('vue')['watchPostEffect']>
|
||||
readonly watchSyncEffect: UnwrapRef<typeof import('vue')['watchSyncEffect']>
|
||||
readonly EffectScope: UnwrapRef<(typeof import('vue'))['EffectScope']>
|
||||
readonly computed: UnwrapRef<(typeof import('vue'))['computed']>
|
||||
readonly createApp: UnwrapRef<(typeof import('vue'))['createApp']>
|
||||
readonly customRef: UnwrapRef<(typeof import('vue'))['customRef']>
|
||||
readonly defineAsyncComponent: UnwrapRef<(typeof import('vue'))['defineAsyncComponent']>
|
||||
readonly defineComponent: UnwrapRef<(typeof import('vue'))['defineComponent']>
|
||||
readonly effectScope: UnwrapRef<(typeof import('vue'))['effectScope']>
|
||||
readonly getCurrentInstance: UnwrapRef<(typeof import('vue'))['getCurrentInstance']>
|
||||
readonly getCurrentScope: UnwrapRef<(typeof import('vue'))['getCurrentScope']>
|
||||
readonly h: UnwrapRef<(typeof import('vue'))['h']>
|
||||
readonly inject: UnwrapRef<(typeof import('vue'))['inject']>
|
||||
readonly isProxy: UnwrapRef<(typeof import('vue'))['isProxy']>
|
||||
readonly isReactive: UnwrapRef<(typeof import('vue'))['isReactive']>
|
||||
readonly isReadonly: UnwrapRef<(typeof import('vue'))['isReadonly']>
|
||||
readonly isRef: UnwrapRef<(typeof import('vue'))['isRef']>
|
||||
readonly markRaw: UnwrapRef<(typeof import('vue'))['markRaw']>
|
||||
readonly nextTick: UnwrapRef<(typeof import('vue'))['nextTick']>
|
||||
readonly onActivated: UnwrapRef<(typeof import('vue'))['onActivated']>
|
||||
readonly onBeforeMount: UnwrapRef<(typeof import('vue'))['onBeforeMount']>
|
||||
readonly onBeforeUnmount: UnwrapRef<(typeof import('vue'))['onBeforeUnmount']>
|
||||
readonly onBeforeUpdate: UnwrapRef<(typeof import('vue'))['onBeforeUpdate']>
|
||||
readonly onDeactivated: UnwrapRef<(typeof import('vue'))['onDeactivated']>
|
||||
readonly onErrorCaptured: UnwrapRef<(typeof import('vue'))['onErrorCaptured']>
|
||||
readonly onMounted: UnwrapRef<(typeof import('vue'))['onMounted']>
|
||||
readonly onRenderTracked: UnwrapRef<(typeof import('vue'))['onRenderTracked']>
|
||||
readonly onRenderTriggered: UnwrapRef<(typeof import('vue'))['onRenderTriggered']>
|
||||
readonly onScopeDispose: UnwrapRef<(typeof import('vue'))['onScopeDispose']>
|
||||
readonly onServerPrefetch: UnwrapRef<(typeof import('vue'))['onServerPrefetch']>
|
||||
readonly onUnmounted: UnwrapRef<(typeof import('vue'))['onUnmounted']>
|
||||
readonly onUpdated: UnwrapRef<(typeof import('vue'))['onUpdated']>
|
||||
readonly onWatcherCleanup: UnwrapRef<(typeof import('vue'))['onWatcherCleanup']>
|
||||
readonly provide: UnwrapRef<(typeof import('vue'))['provide']>
|
||||
readonly reactive: UnwrapRef<(typeof import('vue'))['reactive']>
|
||||
readonly readonly: UnwrapRef<(typeof import('vue'))['readonly']>
|
||||
readonly ref: UnwrapRef<(typeof import('vue'))['ref']>
|
||||
readonly resolveComponent: UnwrapRef<(typeof import('vue'))['resolveComponent']>
|
||||
readonly shallowReactive: UnwrapRef<(typeof import('vue'))['shallowReactive']>
|
||||
readonly shallowReadonly: UnwrapRef<(typeof import('vue'))['shallowReadonly']>
|
||||
readonly shallowRef: UnwrapRef<(typeof import('vue'))['shallowRef']>
|
||||
readonly toRaw: UnwrapRef<(typeof import('vue'))['toRaw']>
|
||||
readonly toRef: UnwrapRef<(typeof import('vue'))['toRef']>
|
||||
readonly toRefs: UnwrapRef<(typeof import('vue'))['toRefs']>
|
||||
readonly toValue: UnwrapRef<(typeof import('vue'))['toValue']>
|
||||
readonly triggerRef: UnwrapRef<(typeof import('vue'))['triggerRef']>
|
||||
readonly unref: UnwrapRef<(typeof import('vue'))['unref']>
|
||||
readonly useAttrs: UnwrapRef<(typeof import('vue'))['useAttrs']>
|
||||
readonly useCssModule: UnwrapRef<(typeof import('vue'))['useCssModule']>
|
||||
readonly useCssVars: UnwrapRef<(typeof import('vue'))['useCssVars']>
|
||||
readonly useId: UnwrapRef<(typeof import('vue'))['useId']>
|
||||
readonly useModel: UnwrapRef<(typeof import('vue'))['useModel']>
|
||||
readonly useRoute: UnwrapRef<(typeof import('vue-router/auto'))['useRoute']>
|
||||
readonly useRouter: UnwrapRef<(typeof import('vue-router/auto'))['useRouter']>
|
||||
readonly useSlots: UnwrapRef<(typeof import('vue'))['useSlots']>
|
||||
readonly useTemplateRef: UnwrapRef<(typeof import('vue'))['useTemplateRef']>
|
||||
readonly watch: UnwrapRef<(typeof import('vue'))['watch']>
|
||||
readonly watchEffect: UnwrapRef<(typeof import('vue'))['watchEffect']>
|
||||
readonly watchPostEffect: UnwrapRef<(typeof import('vue'))['watchPostEffect']>
|
||||
readonly watchSyncEffect: UnwrapRef<(typeof import('vue'))['watchSyncEffect']>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
import {defineMutation, useMutation, useQueryCache} from '@pinia/colada'
|
||||
import {komgaClient} from '@/api/komga-client'
|
||||
import { defineMutation, useMutation, useQueryCache } from '@pinia/colada'
|
||||
import { komgaClient } from '@/api/komga-client'
|
||||
|
||||
export const useLogout = defineMutation(() => {
|
||||
const queryCache = useQueryCache()
|
||||
return useMutation({
|
||||
mutation: () =>
|
||||
komgaClient.POST('/api/logout'),
|
||||
mutation: () => komgaClient.POST('/api/logout'),
|
||||
onSuccess: () => {
|
||||
void queryCache.invalidateQueries({key: ['current-user']})
|
||||
void queryCache.invalidateQueries({ key: ['current-user'] })
|
||||
},
|
||||
onError: (error) => {
|
||||
console.log('logout error', error)
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
import {defineMutation, useMutation, useQueryCache} from '@pinia/colada'
|
||||
import {komgaClient} from '@/api/komga-client'
|
||||
import { defineMutation, useMutation, useQueryCache } from '@pinia/colada'
|
||||
import { komgaClient } from '@/api/komga-client'
|
||||
|
||||
export const useMarkAnnouncementsRead = defineMutation(() => {
|
||||
const queryCache = useQueryCache()
|
||||
return useMutation({
|
||||
mutation: (announcementIds: string[]) =>
|
||||
komgaClient.PUT('/api/v1/announcements', {body: announcementIds}),
|
||||
komgaClient.PUT('/api/v1/announcements', { body: announcementIds }),
|
||||
onSuccess: () => {
|
||||
void queryCache.invalidateQueries({key: ['announcements']})
|
||||
void queryCache.invalidateQueries({ key: ['announcements'] })
|
||||
},
|
||||
onError: (error) => {
|
||||
console.log('announcements mark read error', error)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import {defineMutation, useMutation, useQueryCache} from '@pinia/colada'
|
||||
import {komgaClient} from '@/api/komga-client'
|
||||
import type {components} from '@/generated/openapi/komga'
|
||||
import { defineMutation, useMutation, useQueryCache } from '@pinia/colada'
|
||||
import { komgaClient } from '@/api/komga-client'
|
||||
import type { components } from '@/generated/openapi/komga'
|
||||
|
||||
export const useCreateUser = defineMutation(() => {
|
||||
const queryCache = useQueryCache()
|
||||
|
|
@ -10,7 +10,7 @@ export const useCreateUser = defineMutation(() => {
|
|||
body: user,
|
||||
}),
|
||||
onSuccess: () => {
|
||||
void queryCache.invalidateQueries({key: ['users']})
|
||||
void queryCache.invalidateQueries({ key: ['users'] })
|
||||
},
|
||||
onError: (error) => {
|
||||
console.log('create user error', error)
|
||||
|
|
@ -22,11 +22,11 @@ export const useUpdateUser = defineMutation(() => {
|
|||
return useMutation({
|
||||
mutation: (user: components['schemas']['UserDto']) =>
|
||||
komgaClient.PATCH('/api/v2/users/{id}', {
|
||||
params: {path: {id: user.id}},
|
||||
params: { path: { id: user.id } },
|
||||
body: user,
|
||||
}),
|
||||
onSuccess: () => {
|
||||
void queryCache.invalidateQueries({key: ['users']})
|
||||
void queryCache.invalidateQueries({ key: ['users'] })
|
||||
},
|
||||
onError: (error) => {
|
||||
console.log('update user error', error)
|
||||
|
|
@ -36,9 +36,9 @@ export const useUpdateUser = defineMutation(() => {
|
|||
|
||||
export const useUpdateUserPassword = defineMutation(() => {
|
||||
return useMutation({
|
||||
mutation: ({userId, newPassword}: { userId: string, newPassword: string }) =>
|
||||
mutation: ({ userId, newPassword }: { userId: string; newPassword: string }) =>
|
||||
komgaClient.PATCH('/api/v2/users/{id}/password', {
|
||||
params: {path: {id: userId}},
|
||||
params: { path: { id: userId } },
|
||||
body: {
|
||||
password: newPassword,
|
||||
},
|
||||
|
|
@ -54,10 +54,10 @@ export const useDeleteUser = defineMutation(() => {
|
|||
return useMutation({
|
||||
mutation: (userId: string) =>
|
||||
komgaClient.DELETE('/api/v2/users/{id}', {
|
||||
params: {path: {id: userId}},
|
||||
params: { path: { id: userId } },
|
||||
}),
|
||||
onSuccess: () => {
|
||||
void queryCache.invalidateQueries({key: ['users']})
|
||||
void queryCache.invalidateQueries({ key: ['users'] })
|
||||
},
|
||||
onError: (error) => {
|
||||
console.log('delete user error', error)
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
import {defineQuery, useQuery} from '@pinia/colada'
|
||||
import {komgaClient} from '@/api/komga-client'
|
||||
import type {ActuatorInfo} from '@/types/Actuator'
|
||||
import { defineQuery, useQuery } from '@pinia/colada'
|
||||
import { komgaClient } from '@/api/komga-client'
|
||||
import type { ActuatorInfo } from '@/types/Actuator'
|
||||
|
||||
export const useActuatorInfo = defineQuery(() => {
|
||||
const {data, ...rest} = useQuery({
|
||||
const { data, ...rest } = useQuery({
|
||||
key: () => ['actuator-info'],
|
||||
query: () => komgaClient.GET('/actuator/info')
|
||||
// unwrap the openapi-fetch structure on success
|
||||
.then((res) => res.data as ActuatorInfo),
|
||||
query: () =>
|
||||
komgaClient
|
||||
.GET('/actuator/info')
|
||||
// unwrap the openapi-fetch structure on success
|
||||
.then((res) => res.data as ActuatorInfo),
|
||||
// 1 hour
|
||||
staleTime: 60 * 60 * 1000,
|
||||
gcTime: false,
|
||||
|
|
|
|||
|
|
@ -1,21 +1,22 @@
|
|||
import {defineQuery, useQuery} from '@pinia/colada'
|
||||
import {komgaClient} from '@/api/komga-client'
|
||||
import { defineQuery, useQuery } from '@pinia/colada'
|
||||
import { komgaClient } from '@/api/komga-client'
|
||||
|
||||
export const useAnnouncements = defineQuery(() => {
|
||||
const {data, ...rest} = useQuery({
|
||||
const { data, ...rest } = useQuery({
|
||||
key: () => ['announcements'],
|
||||
query: () => komgaClient.GET('/api/v1/announcements')
|
||||
// unwrap the openapi-fetch structure on success
|
||||
.then((res) => res.data),
|
||||
query: () =>
|
||||
komgaClient
|
||||
.GET('/api/v1/announcements')
|
||||
// unwrap the openapi-fetch structure on success
|
||||
.then((res) => res.data),
|
||||
// 1 hour
|
||||
staleTime: 60 * 60 * 1000,
|
||||
gcTime: false,
|
||||
})
|
||||
|
||||
const unreadCount = computed(() => data.value?.items
|
||||
?.filter((x) => false == x._komga?.read)
|
||||
?.length || 0
|
||||
const unreadCount = computed(
|
||||
() => data.value?.items?.filter((x) => false == x._komga?.read)?.length || 0,
|
||||
)
|
||||
|
||||
return {...rest, data, unreadCount}
|
||||
return { ...rest, data, unreadCount }
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,23 +1,25 @@
|
|||
import {defineQuery, useQuery} from '@pinia/colada'
|
||||
import {komgaClient} from '@/api/komga-client'
|
||||
import {useActuatorInfo} from '@/colada/queries/actuator-info.ts'
|
||||
import { defineQuery, useQuery } from '@pinia/colada'
|
||||
import { komgaClient } from '@/api/komga-client'
|
||||
import { useActuatorInfo } from '@/colada/queries/actuator-info'
|
||||
|
||||
export const useAppReleases = defineQuery(() => {
|
||||
const {data, ...rest} = useQuery({
|
||||
const { data, ...rest } = useQuery({
|
||||
key: () => ['app-releases'],
|
||||
query: () => komgaClient.GET('/api/v1/releases')
|
||||
// unwrap the openapi-fetch structure on success
|
||||
.then((res) => res.data),
|
||||
query: () =>
|
||||
komgaClient
|
||||
.GET('/api/v1/releases')
|
||||
// unwrap the openapi-fetch structure on success
|
||||
.then((res) => res.data),
|
||||
// 1 hour
|
||||
staleTime: 60 * 60 * 1000,
|
||||
gcTime: false,
|
||||
})
|
||||
|
||||
const {buildVersion} = useActuatorInfo()
|
||||
const latestRelease = computed(() => data.value?.find(x => x.latest))
|
||||
const { buildVersion } = useActuatorInfo()
|
||||
const latestRelease = computed(() => data.value?.find((x) => x.latest))
|
||||
|
||||
const isLatestVersion = computed(() => {
|
||||
if(buildVersion.value && latestRelease.value)
|
||||
if (buildVersion.value && latestRelease.value)
|
||||
return buildVersion.value == latestRelease.value?.version
|
||||
else return undefined
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,20 +1,22 @@
|
|||
import {defineQuery, useQuery} from '@pinia/colada'
|
||||
import {komgaClient} from '@/api/komga-client'
|
||||
import {UserRoles} from '@/types/UserRoles.ts'
|
||||
import { defineQuery, useQuery } from '@pinia/colada'
|
||||
import { komgaClient } from '@/api/komga-client'
|
||||
import { UserRoles } from '@/types/UserRoles'
|
||||
|
||||
export const useCurrentUser = defineQuery(() => {
|
||||
const {data, ...rest} = useQuery({
|
||||
const { data, ...rest } = useQuery({
|
||||
key: () => ['current-user'],
|
||||
query: () => komgaClient.GET('/api/v2/users/me')
|
||||
// unwrap the openapi-fetch structure on success
|
||||
.then((res) => res.data),
|
||||
query: () =>
|
||||
komgaClient
|
||||
.GET('/api/v2/users/me')
|
||||
// unwrap the openapi-fetch structure on success
|
||||
.then((res) => res.data),
|
||||
// 10 minutes
|
||||
staleTime: 10 * 60 * 1000,
|
||||
gcTime: false,
|
||||
autoRefetch: true,
|
||||
})
|
||||
|
||||
const hasRole =(role: UserRoles) => data.value?.roles.includes(role)
|
||||
const hasRole = (role: UserRoles) => data.value?.roles.includes(role)
|
||||
const isAdmin = computed(() => hasRole(UserRoles.ADMIN))
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
import {defineQuery, useQuery} from '@pinia/colada'
|
||||
import {komgaClient} from '@/api/komga-client'
|
||||
import { defineQuery, useQuery } from '@pinia/colada'
|
||||
import { komgaClient } from '@/api/komga-client'
|
||||
|
||||
export const useLibraries = defineQuery(() => {
|
||||
return useQuery({
|
||||
key: () => ['libraries'],
|
||||
query: () => komgaClient.GET('/api/v1/libraries')
|
||||
// unwrap the openapi-fetch structure on success
|
||||
.then((res) => res.data),
|
||||
query: () =>
|
||||
komgaClient
|
||||
.GET('/api/v1/libraries')
|
||||
// unwrap the openapi-fetch structure on success
|
||||
.then((res) => res.data),
|
||||
// 1 hour
|
||||
staleTime: 60 * 60 * 1000,
|
||||
gcTime: false,
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
import {defineQuery, useQuery} from '@pinia/colada'
|
||||
import {komgaClient} from '@/api/komga-client'
|
||||
import { defineQuery, useQuery } from '@pinia/colada'
|
||||
import { komgaClient } from '@/api/komga-client'
|
||||
|
||||
export const useSharingLabels = defineQuery(() => {
|
||||
return useQuery({
|
||||
key: () => ['sharing-labels'],
|
||||
query: () => komgaClient.GET('/api/v1/sharing-labels')
|
||||
// unwrap the openapi-fetch structure on success
|
||||
.then((res) => res.data),
|
||||
query: () =>
|
||||
komgaClient
|
||||
.GET('/api/v1/sharing-labels')
|
||||
// unwrap the openapi-fetch structure on success
|
||||
.then((res) => res.data),
|
||||
// 1 hour
|
||||
staleTime: 60 * 60 * 1000,
|
||||
gcTime: false,
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
import {defineQuery, useQuery} from '@pinia/colada'
|
||||
import {komgaClient} from '@/api/komga-client'
|
||||
import { defineQuery, useQuery } from '@pinia/colada'
|
||||
import { komgaClient } from '@/api/komga-client'
|
||||
|
||||
export const useUsers = defineQuery(() => {
|
||||
return useQuery({
|
||||
key: () => ['users'],
|
||||
query: () => komgaClient.GET('/api/v2/users')
|
||||
// unwrap the openapi-fetch structure on success
|
||||
.then((res) => res.data),
|
||||
query: () =>
|
||||
komgaClient
|
||||
.GET('/api/v2/users')
|
||||
// unwrap the openapi-fetch structure on success
|
||||
.then((res) => res.data),
|
||||
})
|
||||
})
|
||||
|
|
|
|||
2
next-ui/src/components.d.ts
vendored
2
next-ui/src/components.d.ts
vendored
|
|
@ -23,12 +23,10 @@ declare module 'vue' {
|
|||
BuildVersion: typeof import('./components/BuildVersion.vue')['default']
|
||||
DialogConfirm: typeof import('./components/dialogs/DialogConfirm.vue')['default']
|
||||
DialogConfirmEdit: typeof import('./components/dialogs/DialogConfirmEdit.vue')['default']
|
||||
Discord: typeof import('./components/icons/discord.vue')['default']
|
||||
FormUserChangePassword: typeof import('./components/forms/user/FormUserChangePassword.vue')['default']
|
||||
FormUserEdit: typeof import('./components/forms/user/FormUserEdit.vue')['default']
|
||||
HelloWorld: typeof import('./components/HelloWorld.vue')['default']
|
||||
LocaleSelector: typeof import('./components/LocaleSelector.vue')['default']
|
||||
LoginForm: typeof import('./components/LoginForm.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
ThemeSelector: typeof import('./components/ThemeSelector.vue')['default']
|
||||
|
|
|
|||
|
|
@ -24,37 +24,41 @@
|
|||
class="text-caption"
|
||||
href="https://komga.org"
|
||||
target="_blank"
|
||||
:text="$formatMessage({
|
||||
description:'Drawer menu footer: documentation link',
|
||||
defaultMessage:'Documentation',
|
||||
id: 'ccAMWS',
|
||||
})"
|
||||
:text="
|
||||
$formatMessage({
|
||||
description: 'Drawer menu footer: documentation link',
|
||||
defaultMessage: 'Documentation',
|
||||
id: 'ccAMWS',
|
||||
})
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</v-footer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const items = [
|
||||
{
|
||||
title: 'Komga GitHub',
|
||||
icon: `mdi-github`,
|
||||
href: 'https://github.com/gotson/komga',
|
||||
},
|
||||
{
|
||||
title: 'Komga Discord',
|
||||
icon: ['M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z'],
|
||||
href: 'https://discord.gg/TdRpkDu',
|
||||
},
|
||||
]
|
||||
const items = [
|
||||
{
|
||||
title: 'Komga GitHub',
|
||||
icon: `mdi-github`,
|
||||
href: 'https://github.com/gotson/komga',
|
||||
},
|
||||
{
|
||||
title: 'Komga Discord',
|
||||
icon: [
|
||||
'M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z',
|
||||
],
|
||||
href: 'https://discord.gg/TdRpkDu',
|
||||
},
|
||||
]
|
||||
</script>
|
||||
|
||||
<style scoped lang="sass">
|
||||
.social-link :deep(.v-icon)
|
||||
color: rgba(var(--v-theme-on-background), var(--v-disabled-opacity))
|
||||
text-decoration: none
|
||||
transition: .2s ease-in-out
|
||||
.social-link :deep(.v-icon)
|
||||
color: rgba(var(--v-theme-on-background), var(--v-disabled-opacity))
|
||||
text-decoration: none
|
||||
transition: .2s ease-in-out
|
||||
|
||||
&:hover
|
||||
color: rgba(25, 118, 210, 1)
|
||||
&:hover
|
||||
color: rgba(25, 118, 210, 1)
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {useActuatorInfo} from '@/colada/queries/actuator-info'
|
||||
import { useActuatorInfo } from '@/colada/queries/actuator-info'
|
||||
|
||||
const {commitId} = useActuatorInfo()
|
||||
const { commitId } = useActuatorInfo()
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {useAppReleases} from '@/colada/queries/app-releases.ts'
|
||||
import { useAppReleases } from '@/colada/queries/app-releases'
|
||||
|
||||
const {buildVersion, isLatestVersion} = useAppReleases()
|
||||
const { buildVersion, isLatestVersion } = useAppReleases()
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -27,22 +27,16 @@
|
|||
</template>
|
||||
|
||||
<template #title>
|
||||
<h2 class="text-h5 font-weight-bold">
|
||||
Get started
|
||||
</h2>
|
||||
<h2 class="text-h5 font-weight-bold">Get started</h2>
|
||||
</template>
|
||||
|
||||
<template #subtitle>
|
||||
<div class="text-subtitle-1">
|
||||
Replace this page by removing
|
||||
<v-kbd>
|
||||
{{
|
||||
`
|
||||
{{ `
|
||||
|
||||
|
||||
|
||||
|
||||
<HelloWorld/>
|
||||
<HelloWorld />
|
||||
` }}
|
||||
</v-kbd>
|
||||
in
|
||||
|
|
@ -161,8 +155,6 @@
|
|||
</v-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
</script>
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<script lang="ts">
|
||||
</script>
|
||||
<script lang="ts"></script>
|
||||
|
|
|
|||
|
|
@ -12,11 +12,13 @@
|
|||
color="primary"
|
||||
>
|
||||
<v-list-subheader
|
||||
:title="$formatMessage({
|
||||
description: 'Translations pop-up menu header',
|
||||
defaultMessage: 'Translations',
|
||||
id: 'InW6ko'
|
||||
})"
|
||||
:title="
|
||||
$formatMessage({
|
||||
description: 'Translations pop-up menu header',
|
||||
defaultMessage: 'Translations',
|
||||
id: 'InW6ko',
|
||||
})
|
||||
"
|
||||
class="text-high-emphasis text-uppercase font-weight-black"
|
||||
/>
|
||||
|
||||
|
|
@ -37,7 +39,7 @@
|
|||
$formatMessage({
|
||||
description: 'Translations pop-up menu footer',
|
||||
defaultMessage: 'Help us translate',
|
||||
id: 'FLqm9f'
|
||||
id: 'FLqm9f',
|
||||
})
|
||||
}}
|
||||
</v-list-item-title>
|
||||
|
|
@ -47,7 +49,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {availableLocales, currentLocale, setLocale} from '@/utils/locale-helper.ts'
|
||||
import { availableLocales, currentLocale, setLocale } from '@/utils/i18n/locale-helper'
|
||||
|
||||
const locales = Object.entries(availableLocales).map(([k, v]) => ({
|
||||
title: v,
|
||||
|
|
@ -55,9 +57,6 @@ const locales = Object.entries(availableLocales).map(([k, v]) => ({
|
|||
}))
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
</script>
|
||||
<script lang="ts"></script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
<style scoped></style>
|
||||
|
|
|
|||
|
|
@ -1,113 +0,0 @@
|
|||
<template>
|
||||
<v-container max-width="550px">
|
||||
<v-row justify="center">
|
||||
<v-col>
|
||||
<v-img src="@/assets/logo.svg" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-text-field
|
||||
v-model="username"
|
||||
:label="$formatMessage({
|
||||
description: 'Login screen: email field label',
|
||||
defaultMessage: 'Email',
|
||||
id: 'QIr0z7'
|
||||
})"
|
||||
autofocus
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-text-field
|
||||
v-model="password"
|
||||
:label="$formatMessage({
|
||||
description: 'Login screen: password field label',
|
||||
defaultMessage: 'Password',
|
||||
id: '5AAGkA'
|
||||
})"
|
||||
type="password"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-checkbox
|
||||
v-model="rememberMe"
|
||||
:label="$formatMessage({
|
||||
description: 'Login screen: Remember Me checkbox',
|
||||
defaultMessage: 'Remember Me',
|
||||
id: '0YG9GQ'
|
||||
})"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-btn
|
||||
:text="$formatMessage({
|
||||
description: 'Login screen: Sign In button',
|
||||
defaultMessage: 'Sign in',
|
||||
id: '02SRax'
|
||||
})"
|
||||
@click="performLogin"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {komgaClient} from '@/api/komga-client'
|
||||
import {useMutation, useQueryCache} from '@pinia/colada'
|
||||
|
||||
const username = ref('')
|
||||
const password = ref('')
|
||||
const rememberMe = ref(false)
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
function performLogin() {
|
||||
const queryCache = useQueryCache()
|
||||
const {mutate} = useMutation({
|
||||
mutation: () =>
|
||||
komgaClient.GET('/api/v2/users/me', {
|
||||
headers: {
|
||||
authorization: 'Basic ' + btoa(username.value + ':' + password.value),
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
},
|
||||
params: {
|
||||
query: {
|
||||
'remember-me': rememberMe.value,
|
||||
}
|
||||
}
|
||||
}),
|
||||
onSuccess: ({data}) => {
|
||||
queryCache.setQueryData(['current-user'], data)
|
||||
queryCache.cancelQueries({key: ['current-user']})
|
||||
if(route.query.redirect)
|
||||
void router.push({path: route.query.redirect.toString()})
|
||||
else
|
||||
void router.push('/')
|
||||
},
|
||||
onError: (error) => {
|
||||
//TODO: handle error
|
||||
},
|
||||
})
|
||||
|
||||
mutate()
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
|
|
@ -10,13 +10,13 @@ The following example assumes a component located at `src/components/MyComponent
|
|||
|
||||
```vue
|
||||
<template>
|
||||
<div>
|
||||
<MyComponent />
|
||||
</div>
|
||||
<div>
|
||||
<MyComponent />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
//
|
||||
//
|
||||
</script>
|
||||
```
|
||||
|
||||
|
|
@ -24,12 +24,12 @@ When your template is rendered, the component's import will automatically be inl
|
|||
|
||||
```vue
|
||||
<template>
|
||||
<div>
|
||||
<MyComponent />
|
||||
</div>
|
||||
<div>
|
||||
<MyComponent />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import MyComponent from '@/components/MyComponent.vue'
|
||||
import MyComponent from '@/components/MyComponent.vue'
|
||||
</script>
|
||||
```
|
||||
|
|
|
|||
|
|
@ -6,39 +6,36 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {useAppStore} from '@/stores/app'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
|
||||
const appStore = useAppStore()
|
||||
|
||||
const themes= [
|
||||
const themes = [
|
||||
{
|
||||
value: 'light',
|
||||
icon: 'mdi-weather-sunny'
|
||||
icon: 'mdi-weather-sunny',
|
||||
},
|
||||
{
|
||||
value: 'dark',
|
||||
icon: 'mdi-weather-night'
|
||||
icon: 'mdi-weather-night',
|
||||
},
|
||||
{
|
||||
value: 'system',
|
||||
icon: 'mdi-theme-light-dark'
|
||||
icon: 'mdi-theme-light-dark',
|
||||
},
|
||||
]
|
||||
|
||||
const themeIcon = computed(
|
||||
() => themes.find(x => x.value === appStore.theme)?.icon || 'mdi-theme-light-dark'
|
||||
() => themes.find((x) => x.value === appStore.theme)?.icon || 'mdi-theme-light-dark',
|
||||
)
|
||||
|
||||
function cycleTheme() {
|
||||
const index = themes.findIndex(x => x.value === appStore.theme)
|
||||
const index = themes.findIndex((x) => x.value === appStore.theme)
|
||||
const newIndex = (index + 1) % themes.length
|
||||
appStore.theme = themes[newIndex]!.value
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
</script>
|
||||
<script lang="ts"></script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
<style scoped></style>
|
||||
|
|
|
|||
|
|
@ -17,14 +17,11 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {useAppStore} from '@/stores/app'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
|
||||
const appStore = useAppStore()
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
</script>
|
||||
<script lang="ts"></script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
<style scoped></style>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
<template>
|
||||
<v-navigation-drawer
|
||||
v-model="appStore.drawer"
|
||||
>
|
||||
<v-navigation-drawer v-model="appStore.drawer">
|
||||
<AppDrawerMenu />
|
||||
|
||||
<template #append>
|
||||
|
|
@ -11,7 +9,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {useAppStore} from '@/stores/app'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
|
||||
const appStore = useAppStore()
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -10,11 +10,13 @@
|
|||
class="text-caption"
|
||||
href="https://komga.org"
|
||||
target="_blank"
|
||||
:text="$formatMessage({
|
||||
description:'Drawer menu footer: documentation link',
|
||||
defaultMessage:'Documentation',
|
||||
id: 'ccAMWS',
|
||||
})"
|
||||
:text="
|
||||
$formatMessage({
|
||||
description: 'Drawer menu footer: documentation link',
|
||||
defaultMessage: 'Documentation',
|
||||
id: 'ccAMWS',
|
||||
})
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -28,11 +30,11 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <AppFooter />-->
|
||||
<!-- <AppFooter />-->
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {useCurrentUser} from '@/colada/queries/current-user.ts'
|
||||
import { useCurrentUser } from '@/colada/queries/current-user'
|
||||
|
||||
const {isAdmin} = useCurrentUser()
|
||||
const { isAdmin } = useCurrentUser()
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {useCurrentUser} from '@/colada/queries/current-user.ts'
|
||||
import { useCurrentUser } from '@/colada/queries/current-user'
|
||||
|
||||
const {isAdmin} = useCurrentUser()
|
||||
const { isAdmin } = useCurrentUser()
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -3,56 +3,62 @@
|
|||
<template #activator="{ props }">
|
||||
<v-list-item
|
||||
v-bind="props"
|
||||
:title="$formatMessage({
|
||||
description: 'Drawer menu for My Account',
|
||||
defaultMessage: 'My Account',
|
||||
id: 'od545m'
|
||||
})"
|
||||
:title="
|
||||
$formatMessage({
|
||||
description: 'Drawer menu for My Account',
|
||||
defaultMessage: 'My Account',
|
||||
id: 'od545m',
|
||||
})
|
||||
"
|
||||
prepend-icon="mdi-account"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<v-list-item
|
||||
to="/account/details"
|
||||
:title="$formatMessage({
|
||||
description: 'Drawer menu for My Account > Details',
|
||||
defaultMessage: 'Details',
|
||||
id: 'xYGXuU'
|
||||
})"
|
||||
:title="
|
||||
$formatMessage({
|
||||
description: 'Drawer menu for My Account > Details',
|
||||
defaultMessage: 'Details',
|
||||
id: 'xYGXuU',
|
||||
})
|
||||
"
|
||||
/>
|
||||
<v-list-item
|
||||
to="/account/api-keys"
|
||||
:title="$formatMessage({
|
||||
description: 'Drawer menu for My Account > API Keys',
|
||||
defaultMessage: 'API Keys',
|
||||
id: 'oFOkWZ'
|
||||
})"
|
||||
:title="
|
||||
$formatMessage({
|
||||
description: 'Drawer menu for My Account > API Keys',
|
||||
defaultMessage: 'API Keys',
|
||||
id: 'oFOkWZ',
|
||||
})
|
||||
"
|
||||
/>
|
||||
<v-list-item
|
||||
to="/account/ui"
|
||||
:title="$formatMessage({
|
||||
description: 'Drawer menu for My Account > User Interface',
|
||||
defaultMessage: 'User Interface',
|
||||
id: 'rw/Dkw'
|
||||
})"
|
||||
:title="
|
||||
$formatMessage({
|
||||
description: 'Drawer menu for My Account > User Interface',
|
||||
defaultMessage: 'User Interface',
|
||||
id: 'rw/Dkw',
|
||||
})
|
||||
"
|
||||
/>
|
||||
<v-list-item
|
||||
to="/account/activity"
|
||||
:title="$formatMessage({
|
||||
description: 'Drawer menu for My Account > Activity',
|
||||
defaultMessage: 'Activity',
|
||||
id: 'cGFtPg'
|
||||
})"
|
||||
:title="
|
||||
$formatMessage({
|
||||
description: 'Drawer menu for My Account > Activity',
|
||||
defaultMessage: 'Activity',
|
||||
id: 'cGFtPg',
|
||||
})
|
||||
"
|
||||
/>
|
||||
</v-list-group>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
</script>
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<script lang="ts">
|
||||
</script>
|
||||
<script lang="ts"></script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
<style scoped></style>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
<template>
|
||||
<v-list-item
|
||||
to="/history"
|
||||
:title="$formatMessage({
|
||||
description: 'Drawer menu for History',
|
||||
defaultMessage: 'History',
|
||||
id: 'l/To3S'
|
||||
})"
|
||||
:title="
|
||||
$formatMessage({
|
||||
description: 'Drawer menu for History',
|
||||
defaultMessage: 'History',
|
||||
id: 'l/To3S',
|
||||
})
|
||||
"
|
||||
prepend-icon="mdi-clock-time-four-outline"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -3,31 +3,37 @@
|
|||
<template #activator="{ props }">
|
||||
<v-list-item
|
||||
v-bind="props"
|
||||
:title="$formatMessage({
|
||||
description: 'Drawer menu for Import',
|
||||
defaultMessage: 'Import',
|
||||
id: 'N7+QXi'
|
||||
})"
|
||||
:title="
|
||||
$formatMessage({
|
||||
description: 'Drawer menu for Import',
|
||||
defaultMessage: 'Import',
|
||||
id: 'N7+QXi',
|
||||
})
|
||||
"
|
||||
prepend-icon="mdi-import"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<v-list-item
|
||||
to="/import/books"
|
||||
:title="$formatMessage({
|
||||
description: 'Drawer menu for Import > Books',
|
||||
defaultMessage: 'Books',
|
||||
id: 'fQIepD'
|
||||
})"
|
||||
:title="
|
||||
$formatMessage({
|
||||
description: 'Drawer menu for Import > Books',
|
||||
defaultMessage: 'Books',
|
||||
id: 'fQIepD',
|
||||
})
|
||||
"
|
||||
/>
|
||||
|
||||
<v-list-item
|
||||
to="/import/readlist"
|
||||
:title="$formatMessage({
|
||||
description: 'Drawer menu for Import > Read List',
|
||||
defaultMessage: 'Read List',
|
||||
id: 'Y6VlM9'
|
||||
})"
|
||||
:title="
|
||||
$formatMessage({
|
||||
description: 'Drawer menu for Import > Read List',
|
||||
defaultMessage: 'Read List',
|
||||
id: 'Y6VlM9',
|
||||
})
|
||||
"
|
||||
/>
|
||||
</v-list-group>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,20 +1,22 @@
|
|||
<template>
|
||||
<v-list-item
|
||||
:title="$formatMessage({
|
||||
description: 'Drawer menu for Logout',
|
||||
defaultMessage: 'Logout',
|
||||
id: 'ti4Pzo'
|
||||
})"
|
||||
:title="
|
||||
$formatMessage({
|
||||
description: 'Drawer menu for Logout',
|
||||
defaultMessage: 'Logout',
|
||||
id: 'ti4Pzo',
|
||||
})
|
||||
"
|
||||
prepend-icon="mdi-power"
|
||||
@click="performLogout"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {useLogout} from '@/colada/mutations/logout'
|
||||
import { useLogout } from '@/colada/mutations/logout'
|
||||
|
||||
const router = useRouter()
|
||||
const {mutateAsync: logoutAsync} = useLogout()
|
||||
const { mutateAsync: logoutAsync } = useLogout()
|
||||
|
||||
function performLogout() {
|
||||
void logoutAsync().then(() => router.push('/login'))
|
||||
|
|
|
|||
|
|
@ -3,71 +3,83 @@
|
|||
<template #activator="{ props }">
|
||||
<v-list-item
|
||||
v-bind="props"
|
||||
:title="$formatMessage({
|
||||
description: 'Drawer menu for Media',
|
||||
defaultMessage: 'Media',
|
||||
id: 'Hl9H/B'
|
||||
})"
|
||||
:title="
|
||||
$formatMessage({
|
||||
description: 'Drawer menu for Media',
|
||||
defaultMessage: 'Media',
|
||||
id: 'Hl9H/B',
|
||||
})
|
||||
"
|
||||
prepend-icon="mdi-book-cog"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<v-list-item
|
||||
to="/media/analysis"
|
||||
|
||||
:title="$formatMessage({
|
||||
description: 'Drawer menu for Media > Media Analysis',
|
||||
defaultMessage: 'Media Analysis',
|
||||
id: 'DxtDpt'
|
||||
})"
|
||||
:title="
|
||||
$formatMessage({
|
||||
description: 'Drawer menu for Media > Media Analysis',
|
||||
defaultMessage: 'Media Analysis',
|
||||
id: 'DxtDpt',
|
||||
})
|
||||
"
|
||||
/>
|
||||
<v-list-item
|
||||
to="/media/missing-posters"
|
||||
:title="$formatMessage({
|
||||
description: 'Drawer menu for Media > Missing Posters',
|
||||
defaultMessage: 'Missing Posters',
|
||||
id: 'Nb0V0p'
|
||||
})"
|
||||
:title="
|
||||
$formatMessage({
|
||||
description: 'Drawer menu for Media > Missing Posters',
|
||||
defaultMessage: 'Missing Posters',
|
||||
id: 'Nb0V0p',
|
||||
})
|
||||
"
|
||||
/>
|
||||
<v-list-item
|
||||
to="/media/duplicate-files"
|
||||
:title="$formatMessage({
|
||||
description: 'Drawer menu for Media > Duplicate Files',
|
||||
defaultMessage: 'Duplicate Files',
|
||||
id: 'eW3fXu'
|
||||
})"
|
||||
:title="
|
||||
$formatMessage({
|
||||
description: 'Drawer menu for Media > Duplicate Files',
|
||||
defaultMessage: 'Duplicate Files',
|
||||
id: 'eW3fXu',
|
||||
})
|
||||
"
|
||||
/>
|
||||
|
||||
<v-list-group value="Duplicate Pages">
|
||||
<template #activator="{ props }">
|
||||
<v-list-item
|
||||
v-bind="props"
|
||||
:title="$formatMessage({
|
||||
description: 'Drawer menu for Media > Duplicate Pages',
|
||||
defaultMessage: 'Duplicate Pages',
|
||||
id: 'cAu/I6'
|
||||
})"
|
||||
:title="
|
||||
$formatMessage({
|
||||
description: 'Drawer menu for Media > Duplicate Pages',
|
||||
defaultMessage: 'Duplicate Pages',
|
||||
id: 'cAu/I6',
|
||||
})
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<v-list-item
|
||||
to="/media/duplicate-pages/known"
|
||||
:title="$formatMessage({
|
||||
description: 'Drawer menu for Media > Duplicate Pages > Known',
|
||||
defaultMessage: 'Known',
|
||||
id: 'MvwDsn'
|
||||
})"
|
||||
:title="
|
||||
$formatMessage({
|
||||
description: 'Drawer menu for Media > Duplicate Pages > Known',
|
||||
defaultMessage: 'Known',
|
||||
id: 'MvwDsn',
|
||||
})
|
||||
"
|
||||
/>
|
||||
<v-list-item
|
||||
to="/media/duplicate-pages/unknown"
|
||||
:title="$formatMessage({
|
||||
description: 'Drawer menu for Media > Duplicate Pages > Unknown',
|
||||
defaultMessage: 'Unknown',
|
||||
id: 'qiZm6U'
|
||||
})"
|
||||
:title="
|
||||
$formatMessage({
|
||||
description: 'Drawer menu for Media > Duplicate Pages > Unknown',
|
||||
defaultMessage: 'Unknown',
|
||||
id: 'qiZm6U',
|
||||
})
|
||||
"
|
||||
/>
|
||||
</v-list-group>
|
||||
</v-list-group>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
</script>
|
||||
<script setup lang="ts"></script>
|
||||
|
|
|
|||
|
|
@ -6,11 +6,13 @@
|
|||
<template #activator="{ props }">
|
||||
<v-list-item
|
||||
v-bind="props"
|
||||
:title="$formatMessage({
|
||||
description: 'Drawer menu for Server',
|
||||
defaultMessage: 'Server',
|
||||
id: 'IpvWiZ'
|
||||
})"
|
||||
:title="
|
||||
$formatMessage({
|
||||
description: 'Drawer menu for Server',
|
||||
defaultMessage: 'Server',
|
||||
id: 'IpvWiZ',
|
||||
})
|
||||
"
|
||||
prepend-icon="mdi-cog"
|
||||
>
|
||||
<template #prepend>
|
||||
|
|
@ -28,44 +30,54 @@
|
|||
|
||||
<v-list-item
|
||||
to="/server/users"
|
||||
:title="$formatMessage({
|
||||
description: 'Drawer menu for Server > Users',
|
||||
defaultMessage: 'Users',
|
||||
id: 'JGOfZq'
|
||||
})"
|
||||
:title="
|
||||
$formatMessage({
|
||||
description: 'Drawer menu for Server > Users',
|
||||
defaultMessage: 'Users',
|
||||
id: 'JGOfZq',
|
||||
})
|
||||
"
|
||||
/>
|
||||
<v-list-item
|
||||
to="/server/settings"
|
||||
:title="$formatMessage({
|
||||
description: 'Drawer menu for Server > Settings',
|
||||
defaultMessage: 'Settings',
|
||||
id: 'HaWCi3'
|
||||
})"
|
||||
:title="
|
||||
$formatMessage({
|
||||
description: 'Drawer menu for Server > Settings',
|
||||
defaultMessage: 'Settings',
|
||||
id: 'HaWCi3',
|
||||
})
|
||||
"
|
||||
/>
|
||||
<v-list-item
|
||||
to="/server/ui"
|
||||
:title="$formatMessage({
|
||||
description: 'Drawer menu for Server > User Interface',
|
||||
defaultMessage: 'User Interface',
|
||||
id: 'Yf4DJ2'
|
||||
})"
|
||||
:title="
|
||||
$formatMessage({
|
||||
description: 'Drawer menu for Server > User Interface',
|
||||
defaultMessage: 'User Interface',
|
||||
id: 'Yf4DJ2',
|
||||
})
|
||||
"
|
||||
/>
|
||||
<v-list-item
|
||||
to="/server/metrics"
|
||||
:title="$formatMessage({
|
||||
description: 'Drawer menu for Server > Metrics',
|
||||
defaultMessage: 'Metrics',
|
||||
id: '2g7iOx'
|
||||
})"
|
||||
:title="
|
||||
$formatMessage({
|
||||
description: 'Drawer menu for Server > Metrics',
|
||||
defaultMessage: 'Metrics',
|
||||
id: '2g7iOx',
|
||||
})
|
||||
"
|
||||
/>
|
||||
|
||||
<v-list-item
|
||||
to="/server/announcements"
|
||||
:title="$formatMessage({
|
||||
description: 'Drawer menu for Server > Announcements',
|
||||
defaultMessage: 'Announcements',
|
||||
id: 'G7quju'
|
||||
})"
|
||||
:title="
|
||||
$formatMessage({
|
||||
description: 'Drawer menu for Server > Announcements',
|
||||
defaultMessage: 'Announcements',
|
||||
id: 'G7quju',
|
||||
})
|
||||
"
|
||||
>
|
||||
<template #append>
|
||||
<v-badge
|
||||
|
|
@ -79,17 +91,19 @@
|
|||
|
||||
<v-list-item
|
||||
to="/server/updates"
|
||||
:title="$formatMessage({
|
||||
description: 'Drawer menu for Server > Updates',
|
||||
defaultMessage: 'Updates',
|
||||
id: 'lDnmZD'
|
||||
})"
|
||||
:title="
|
||||
$formatMessage({
|
||||
description: 'Drawer menu for Server > Updates',
|
||||
defaultMessage: 'Updates',
|
||||
id: 'lDnmZD',
|
||||
})
|
||||
"
|
||||
/>
|
||||
</v-list-group>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {useAnnouncements} from '@/colada/queries/announcements.ts'
|
||||
import { useAnnouncements } from '@/colada/queries/announcements'
|
||||
|
||||
const {unreadCount} = useAnnouncements()
|
||||
const { unreadCount } = useAnnouncements()
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -20,11 +20,12 @@
|
|||
{
|
||||
description: 'Confirmation dialog: default hint to retype validation text',
|
||||
defaultMessage: 'Please type {validateText} to confirm.',
|
||||
id: 'eVoe+D'
|
||||
id: 'eVoe+D',
|
||||
},
|
||||
{
|
||||
validateText: validateText,
|
||||
})
|
||||
},
|
||||
)
|
||||
}}
|
||||
</slot>
|
||||
|
||||
|
|
@ -38,11 +39,13 @@
|
|||
<template #actions>
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
:text="$formatMessage({
|
||||
description: 'Confirmation dialog: Cancel button',
|
||||
defaultMessage: 'Cancel',
|
||||
id: 'pENCUD'
|
||||
})"
|
||||
:text="
|
||||
$formatMessage({
|
||||
description: 'Confirmation dialog: Cancel button',
|
||||
defaultMessage: 'Cancel',
|
||||
id: 'pENCUD',
|
||||
})
|
||||
"
|
||||
@click="close()"
|
||||
/>
|
||||
<v-btn
|
||||
|
|
@ -60,9 +63,9 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {useRules} from 'vuetify/labs/rules'
|
||||
import { useRules } from 'vuetify/labs/rules'
|
||||
|
||||
const showDialog = defineModel<boolean>('dialog', {required: false})
|
||||
const showDialog = defineModel<boolean>('dialog', { required: false })
|
||||
const emit = defineEmits<{
|
||||
confirm: []
|
||||
}>()
|
||||
|
|
@ -72,19 +75,19 @@ const formValid = ref<boolean>(false)
|
|||
const rules = useRules()
|
||||
|
||||
function submitForm() {
|
||||
if(formValid.value) {
|
||||
if (formValid.value) {
|
||||
emit('confirm')
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
||||
export interface Props {
|
||||
title?: string,
|
||||
subtitle?: string,
|
||||
okText?: string,
|
||||
validateText?: string,
|
||||
maxWidth?: string | number,
|
||||
activator?: Element | string,
|
||||
title?: string
|
||||
subtitle?: string
|
||||
okText?: string
|
||||
validateText?: string
|
||||
maxWidth?: string | number
|
||||
activator?: Element | string
|
||||
}
|
||||
|
||||
const {
|
||||
|
|
@ -100,4 +103,3 @@ function close() {
|
|||
showDialog.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -31,19 +31,23 @@
|
|||
<template #actions>
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
:text="$formatMessage({
|
||||
description: 'ConfirmEdit dialog: Cancel button',
|
||||
defaultMessage: 'Cancel',
|
||||
id: 'G/T8/2'
|
||||
})"
|
||||
:text="
|
||||
$formatMessage({
|
||||
description: 'ConfirmEdit dialog: Cancel button',
|
||||
defaultMessage: 'Cancel',
|
||||
id: 'G/T8/2',
|
||||
})
|
||||
"
|
||||
@click="close()"
|
||||
/>
|
||||
<v-btn
|
||||
:text="$formatMessage({
|
||||
description: 'ConfirmEdit dialog: Save button',
|
||||
defaultMessage: 'Save',
|
||||
id: 'N9WFH4'
|
||||
})"
|
||||
:text="
|
||||
$formatMessage({
|
||||
description: 'ConfirmEdit dialog: Save button',
|
||||
defaultMessage: 'Save',
|
||||
id: 'N9WFH4',
|
||||
})
|
||||
"
|
||||
type="submit"
|
||||
/>
|
||||
</template>
|
||||
|
|
@ -55,13 +59,13 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const showDialog = defineModel<boolean>('dialog', {required: false})
|
||||
const record = defineModel<unknown>('record', {required: true})
|
||||
const showDialog = defineModel<boolean>('dialog', { required: false })
|
||||
const record = defineModel<unknown>('record', { required: true })
|
||||
|
||||
const formValid = ref<boolean>(false)
|
||||
|
||||
function submitForm(callback: () => void) {
|
||||
if(formValid.value) callback()
|
||||
if (formValid.value) callback()
|
||||
}
|
||||
|
||||
interface Props {
|
||||
|
|
@ -69,10 +73,10 @@ interface Props {
|
|||
* Dialog title
|
||||
* @type string
|
||||
*/
|
||||
title?: string,
|
||||
subtitle?: string,
|
||||
maxWidth?: string | number,
|
||||
activator?: Element | string,
|
||||
title?: string
|
||||
subtitle?: string
|
||||
maxWidth?: string | number
|
||||
activator?: Element | string
|
||||
}
|
||||
|
||||
const {
|
||||
|
|
|
|||
|
|
@ -2,11 +2,13 @@
|
|||
<v-text-field
|
||||
v-model="newPassword"
|
||||
:rules="[rules.required()]"
|
||||
:label="$formatMessage({
|
||||
description: 'User password change dialog: New Password field label',
|
||||
defaultMessage: 'New password',
|
||||
id: 'WhasCZ'
|
||||
})"
|
||||
:label="
|
||||
$formatMessage({
|
||||
description: 'User password change dialog: New Password field label',
|
||||
defaultMessage: 'New password',
|
||||
id: 'WhasCZ',
|
||||
})
|
||||
"
|
||||
autocomplete="off"
|
||||
autofocus
|
||||
:type="showPassword ? 'text' : 'password'"
|
||||
|
|
@ -16,16 +18,23 @@
|
|||
<v-text-field
|
||||
v-model="confirmPassword"
|
||||
class="mt-2"
|
||||
:rules="[rules.sameAs(newPassword, $formatMessage({
|
||||
description: 'User password change dialog: Error message if passwords differ',
|
||||
defaultMessage: 'Passwords must be identical',
|
||||
id: 'LaxrEO'
|
||||
}))]"
|
||||
:label="$formatMessage({
|
||||
description: 'User password change dialog: Confirm Password field label',
|
||||
defaultMessage: 'Confirm password',
|
||||
id: 'nJiYF7'
|
||||
})"
|
||||
:rules="[
|
||||
rules.sameAs(
|
||||
newPassword,
|
||||
$formatMessage({
|
||||
description: 'User password change dialog: Error message if passwords differ',
|
||||
defaultMessage: 'Passwords must be identical',
|
||||
id: 'LaxrEO',
|
||||
}),
|
||||
),
|
||||
]"
|
||||
:label="
|
||||
$formatMessage({
|
||||
description: 'User password change dialog: Confirm Password field label',
|
||||
defaultMessage: 'Confirm password',
|
||||
id: 'nJiYF7',
|
||||
})
|
||||
"
|
||||
autocomplete="off"
|
||||
:type="showPassword ? 'text' : 'password'"
|
||||
:append-inner-icon="showPassword ? 'mdi-eye' : 'mdi-eye-off'"
|
||||
|
|
@ -38,7 +47,7 @@ import { useRules } from 'vuetify/labs/rules'
|
|||
|
||||
const rules = useRules()
|
||||
|
||||
const newPassword = defineModel<string>({required: true})
|
||||
const newPassword = defineModel<string>({ required: true })
|
||||
|
||||
const confirmPassword = ref<string>()
|
||||
const showPassword = ref<boolean>(false)
|
||||
|
|
|
|||
|
|
@ -4,22 +4,26 @@
|
|||
v-model="user!.email"
|
||||
autofocus
|
||||
:rules="[rules.required(), rules.email()]"
|
||||
:label="$formatMessage({
|
||||
description: 'User creation dialog: Email field',
|
||||
defaultMessage: 'Email',
|
||||
id: 'ToD0+o'
|
||||
})"
|
||||
:label="
|
||||
$formatMessage({
|
||||
description: 'User creation dialog: Email field',
|
||||
defaultMessage: 'Email',
|
||||
id: 'ToD0+o',
|
||||
})
|
||||
"
|
||||
prepend-icon="mdi-account"
|
||||
/>
|
||||
<v-text-field
|
||||
v-model="user.password"
|
||||
class="mt-1 mb-2"
|
||||
:rules="[rules.required()]"
|
||||
:label="$formatMessage({
|
||||
description: 'User creation dialog: Password field',
|
||||
defaultMessage: 'Password',
|
||||
id: 'o+A10T'
|
||||
})"
|
||||
:label="
|
||||
$formatMessage({
|
||||
description: 'User creation dialog: Password field',
|
||||
defaultMessage: 'Password',
|
||||
id: 'o+A10T',
|
||||
})
|
||||
"
|
||||
autocomplete="off"
|
||||
:type="showPassword ? 'text' : 'password'"
|
||||
:append-inner-icon="showPassword ? 'mdi-eye' : 'mdi-eye-off'"
|
||||
|
|
@ -34,11 +38,13 @@
|
|||
chips
|
||||
closable-chips
|
||||
multiple
|
||||
:label="$formatMessage({
|
||||
description: 'User creation/edit dialog: Roles field',
|
||||
defaultMessage: 'Roles',
|
||||
id: 'CUxhzL'
|
||||
})"
|
||||
:label="
|
||||
$formatMessage({
|
||||
description: 'User creation/edit dialog: Roles field',
|
||||
defaultMessage: 'Roles',
|
||||
id: 'CUxhzL',
|
||||
})
|
||||
"
|
||||
prepend-icon="mdi-key-chain"
|
||||
:items="userRoles"
|
||||
/>
|
||||
|
|
@ -47,11 +53,13 @@
|
|||
<v-select
|
||||
v-model="user.sharedLibraries!.libraryIds"
|
||||
multiple
|
||||
:label="$formatMessage({
|
||||
description: 'User creation/edit dialog: Shared Libraries field',
|
||||
defaultMessage: 'Shared Libraries',
|
||||
id: 'UvhIIT'
|
||||
})"
|
||||
:label="
|
||||
$formatMessage({
|
||||
description: 'User creation/edit dialog: Shared Libraries field',
|
||||
defaultMessage: 'Shared Libraries',
|
||||
id: 'UvhIIT',
|
||||
})
|
||||
"
|
||||
:items="libraries"
|
||||
item-title="name"
|
||||
item-value="id"
|
||||
|
|
@ -62,11 +70,14 @@
|
|||
<!-- 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'
|
||||
})"
|
||||
: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>
|
||||
|
|
@ -82,11 +93,14 @@
|
|||
|
||||
<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'
|
||||
})"
|
||||
: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>
|
||||
|
|
@ -100,7 +114,7 @@
|
|||
:disabled="user.sharedLibraries?.all"
|
||||
v-bind="itemProps"
|
||||
>
|
||||
<template #prepend="{isSelected}">
|
||||
<template #prepend="{ isSelected }">
|
||||
<v-checkbox-btn :model-value="isSelected" />
|
||||
</template>
|
||||
</v-list-item>
|
||||
|
|
@ -112,11 +126,13 @@
|
|||
<v-col>
|
||||
<v-select
|
||||
v-model="user.ageRestriction!.restriction"
|
||||
:label="$formatMessage({
|
||||
description: 'User creation/edit dialog: Age restriction field label',
|
||||
defaultMessage: 'Age restriction',
|
||||
id: 'hEOGa9'
|
||||
})"
|
||||
:label="
|
||||
$formatMessage({
|
||||
description: 'User creation/edit dialog: Age restriction field label',
|
||||
defaultMessage: 'Age restriction',
|
||||
id: 'hEOGa9',
|
||||
})
|
||||
"
|
||||
:items="ageRestrictions"
|
||||
prepend-icon="mdi-folder-lock"
|
||||
/>
|
||||
|
|
@ -125,11 +141,13 @@
|
|||
<v-number-input
|
||||
v-model="user.ageRestriction!.age"
|
||||
:disabled="user.ageRestriction?.restriction?.toString() === 'NONE'"
|
||||
:label="$formatMessage({
|
||||
description: 'User creation/edit dialog: Age Restriction > Age field label',
|
||||
defaultMessage: 'Age',
|
||||
id: 'jywpqq'
|
||||
})"
|
||||
:label="
|
||||
$formatMessage({
|
||||
description: 'User creation/edit dialog: Age Restriction > Age field label',
|
||||
defaultMessage: 'Age',
|
||||
id: 'jywpqq',
|
||||
})
|
||||
"
|
||||
:min="0"
|
||||
:rules="[rules.required()]"
|
||||
/>
|
||||
|
|
@ -139,11 +157,13 @@
|
|||
<!-- 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'
|
||||
})"
|
||||
:label="
|
||||
$formatMessage({
|
||||
description: 'User creation/edit dialog: Allow only labels field label',
|
||||
defaultMessage: 'Allow only labels',
|
||||
id: 'Sj0HXz',
|
||||
})
|
||||
"
|
||||
chips
|
||||
closable-chips
|
||||
multiple
|
||||
|
|
@ -157,7 +177,7 @@
|
|||
$formatMessage({
|
||||
description: 'User creation/edit dialog: Allow only labels field selection',
|
||||
defaultMessage: 'Select an item or create one',
|
||||
id: 'app.user-create-dialog.select_create_one'
|
||||
id: 'app.user-create-dialog.select_create_one',
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
|
|
@ -168,11 +188,13 @@
|
|||
<!-- Exclude labels -->
|
||||
<v-combobox
|
||||
v-model="user.labelsExclude"
|
||||
:label="$formatMessage({
|
||||
description: 'User creation/edit dialog: Exclude labels field label',
|
||||
defaultMessage: 'Exclude labels',
|
||||
id: '3W0jUi'
|
||||
})"
|
||||
:label="
|
||||
$formatMessage({
|
||||
description: 'User creation/edit dialog: Exclude labels field label',
|
||||
defaultMessage: 'Exclude labels',
|
||||
id: '3W0jUi',
|
||||
})
|
||||
"
|
||||
chips
|
||||
closable-chips
|
||||
multiple
|
||||
|
|
@ -186,7 +208,7 @@
|
|||
$formatMessage({
|
||||
description: 'User creation/edit dialog: Exclude labels field selection',
|
||||
defaultMessage: 'Select an item or create one',
|
||||
id: 'app.user-create-dialog.select_create_one'
|
||||
id: 'app.user-create-dialog.select_create_one',
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
|
|
@ -196,65 +218,67 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {UserRoles} from '@/types/UserRoles.ts'
|
||||
import type {components} from '@/generated/openapi/komga'
|
||||
import {useRules} from 'vuetify/labs/rules'
|
||||
import {useLibraries} from '@/colada/queries/libraries.ts'
|
||||
import {useSharingLabels} from '@/colada/queries/referential.ts'
|
||||
import {useIntl} from 'vue-intl'
|
||||
import { UserRoles } from '@/types/UserRoles'
|
||||
import type { components } from '@/generated/openapi/komga'
|
||||
import { useRules } from 'vuetify/labs/rules'
|
||||
import { useLibraries } from '@/colada/queries/libraries'
|
||||
import { useSharingLabels } from '@/colada/queries/referential'
|
||||
import { useIntl } from 'vue-intl'
|
||||
|
||||
const rules = useRules()
|
||||
const intl = useIntl()
|
||||
|
||||
interface UserExtend {
|
||||
id?: string,
|
||||
email: string,
|
||||
password?: string,
|
||||
id?: string
|
||||
email: string
|
||||
password?: string
|
||||
}
|
||||
|
||||
type UserCreation = components['schemas']['UserCreationDto'] & UserExtend
|
||||
type UserUpdate = components['schemas']['UserUpdateDto'] & UserExtend
|
||||
const user = defineModel<UserCreation | UserUpdate>({required: true})
|
||||
const user = defineModel<UserCreation | UserUpdate>({ required: true })
|
||||
|
||||
const showPassword = ref<boolean>(false)
|
||||
|
||||
const {data: libraries} = useLibraries()
|
||||
const {data: sharingLabels} = useSharingLabels()
|
||||
const { data: libraries } = useLibraries()
|
||||
const { data: sharingLabels } = useSharingLabels()
|
||||
|
||||
function selectAllLibraries() {
|
||||
user.value.sharedLibraries!.all = !user.value.sharedLibraries?.all
|
||||
user.value.sharedLibraries!.libraryIds = libraries.value?.map(x => x.id) || []
|
||||
user.value.sharedLibraries!.libraryIds = libraries.value?.map((x) => x.id) || []
|
||||
}
|
||||
|
||||
const userRoles = computed(() => Object.keys(UserRoles).map(x => ({
|
||||
title: x,
|
||||
value: x,
|
||||
})))
|
||||
const userRoles = computed(() =>
|
||||
Object.keys(UserRoles).map((x) => ({
|
||||
title: x,
|
||||
value: x,
|
||||
})),
|
||||
)
|
||||
|
||||
const ageRestrictions = [
|
||||
{
|
||||
title: intl.formatMessage({
|
||||
description: 'User creation/edit dialog: Age restriction field possible option',
|
||||
defaultMessage: 'No restriction',
|
||||
id: 'AeA9Ka'
|
||||
id: 'AeA9Ka',
|
||||
}),
|
||||
value: 'NONE'
|
||||
value: 'NONE',
|
||||
},
|
||||
{
|
||||
title: intl.formatMessage({
|
||||
description: 'User creation/edit dialog: Age restriction field possible option',
|
||||
defaultMessage: 'Allow only under',
|
||||
id: '/bathK'
|
||||
id: '/bathK',
|
||||
}),
|
||||
value: 'ALLOW_ONLY'
|
||||
value: 'ALLOW_ONLY',
|
||||
},
|
||||
{
|
||||
title: intl.formatMessage({
|
||||
description: 'User creation/edit dialog: Age restriction field possible option',
|
||||
defaultMessage: 'Exclude over',
|
||||
id: 'wmGcF+'
|
||||
id: 'wmGcF+',
|
||||
}),
|
||||
value: 'EXCLUDE'
|
||||
value: 'EXCLUDE',
|
||||
},
|
||||
]
|
||||
</script>
|
||||
|
|
|
|||
18969
next-ui/src/generated/openapi/komga.d.ts
vendored
18969
next-ui/src/generated/openapi/komga.d.ts
vendored
File diff suppressed because it is too large
Load diff
|
|
@ -13,6 +13,4 @@
|
|||
</v-main>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
||||
</script>
|
||||
<script lang="ts" setup></script>
|
||||
|
|
|
|||
|
|
@ -5,5 +5,5 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
//
|
||||
//
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -5,13 +5,13 @@
|
|||
*/
|
||||
|
||||
// Plugins
|
||||
import {registerPlugins} from '@/plugins'
|
||||
import { registerPlugins } from '@/plugins'
|
||||
|
||||
// Components
|
||||
import App from './App.vue'
|
||||
|
||||
// Composables
|
||||
import {createApp} from 'vue'
|
||||
import { createApp } from 'vue'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
|
|
|
|||
|
|
@ -3,5 +3,5 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
//
|
||||
//
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -3,5 +3,5 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
//
|
||||
//
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -3,5 +3,5 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
//
|
||||
//
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -3,5 +3,5 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
//
|
||||
//
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
//
|
||||
//
|
||||
</script>
|
||||
|
||||
<route lang="yaml">
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
//
|
||||
//
|
||||
</script>
|
||||
|
||||
<route lang="yaml">
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
//
|
||||
//
|
||||
</script>
|
||||
|
||||
<route lang="yaml">
|
||||
|
|
|
|||
|
|
@ -3,5 +3,5 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
//
|
||||
//
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,150 @@
|
|||
<template>
|
||||
<LoginForm />
|
||||
<v-form
|
||||
v-model="formValid"
|
||||
:disabled="isLoading"
|
||||
@submit.prevent="submitForm()"
|
||||
>
|
||||
<v-container max-width="550px">
|
||||
<v-row justify="center">
|
||||
<v-col>
|
||||
<v-img src="@/assets/logo.svg" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-text-field
|
||||
v-model="username"
|
||||
:label="
|
||||
$formatMessage({
|
||||
description: 'Login screen: email field label',
|
||||
defaultMessage: 'Email',
|
||||
id: 'QIr0z7',
|
||||
})
|
||||
"
|
||||
autofocus
|
||||
:rules="[rules.required(), rules.email()]"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-text-field
|
||||
v-model="password"
|
||||
:label="
|
||||
$formatMessage({
|
||||
description: 'Login screen: password field label',
|
||||
defaultMessage: 'Password',
|
||||
id: '5AAGkA',
|
||||
})
|
||||
"
|
||||
type="password"
|
||||
:rules="[rules.required()]"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-checkbox
|
||||
v-model="rememberMe"
|
||||
:label="
|
||||
$formatMessage({
|
||||
description: 'Login screen: Remember Me checkbox',
|
||||
defaultMessage: 'Remember Me',
|
||||
id: '0YG9GQ',
|
||||
})
|
||||
"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-btn
|
||||
:text="
|
||||
$formatMessage({
|
||||
description: 'Login screen: Sign In button',
|
||||
defaultMessage: 'Sign in',
|
||||
id: '02SRax',
|
||||
})
|
||||
"
|
||||
:loading="isLoading"
|
||||
type="submit"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-snackbar
|
||||
v-model="showError"
|
||||
color="error"
|
||||
:text="showErrorText"
|
||||
>
|
||||
<template v-slot:actions>
|
||||
<v-btn
|
||||
color="white"
|
||||
variant="text"
|
||||
@click="showError = false"
|
||||
>
|
||||
Dismiss
|
||||
</v-btn>
|
||||
</template></v-snackbar
|
||||
>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</v-form>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
//
|
||||
import { type ErrorCause, komgaClient } from '@/api/komga-client'
|
||||
import { useMutation, useQueryCache } from '@pinia/colada'
|
||||
import { useRules } from 'vuetify/labs/rules'
|
||||
|
||||
const rules = useRules()
|
||||
|
||||
const formValid = ref<boolean>(false)
|
||||
const username = ref('')
|
||||
const password = ref('')
|
||||
const rememberMe = ref(false)
|
||||
const showError = ref<boolean>(false)
|
||||
const showErrorText = ref<string>('')
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
const queryCache = useQueryCache()
|
||||
const { mutate: performLogin, isLoading } = useMutation({
|
||||
mutation: () =>
|
||||
komgaClient.GET('/api/v2/users/me', {
|
||||
headers: {
|
||||
authorization: 'Basic ' + btoa(username.value + ':' + password.value),
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
},
|
||||
params: {
|
||||
query: {
|
||||
'remember-me': rememberMe.value,
|
||||
},
|
||||
},
|
||||
}),
|
||||
onSuccess: ({ data }) => {
|
||||
queryCache.setQueryData(['current-user'], data)
|
||||
queryCache.cancelQueries({ key: ['current-user'] })
|
||||
if (route.query.redirect) void router.push({ path: route.query.redirect.toString() })
|
||||
else void router.push('/')
|
||||
},
|
||||
onError: (error) => {
|
||||
showErrorText.value = (error.cause as ErrorCause).message || 'Invalid authentication'
|
||||
showError.value = true
|
||||
},
|
||||
})
|
||||
|
||||
function submitForm() {
|
||||
if (formValid.value) performLogin()
|
||||
}
|
||||
</script>
|
||||
|
||||
<route lang="yaml">
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
//
|
||||
//
|
||||
</script>
|
||||
|
||||
<route lang="yaml">
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
//
|
||||
//
|
||||
</script>
|
||||
|
||||
<route lang="yaml">
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
//
|
||||
//
|
||||
</script>
|
||||
|
||||
<route lang="yaml">
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
//
|
||||
//
|
||||
</script>
|
||||
|
||||
<route lang="yaml">
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
//
|
||||
//
|
||||
</script>
|
||||
|
||||
<route lang="yaml">
|
||||
|
|
|
|||
|
|
@ -16,60 +16,47 @@
|
|||
v-for="(item, index) in announcements.items"
|
||||
:key="index"
|
||||
>
|
||||
<v-row
|
||||
justify="space-between"
|
||||
align="center"
|
||||
>
|
||||
<v-col cols="auto">
|
||||
<div class="ml-n2">
|
||||
<a
|
||||
:href="item.url"
|
||||
target="_blank"
|
||||
class="text-h3 font-weight-medium link-underline"
|
||||
>{{ item.title }}</a>
|
||||
</div>
|
||||
<div class="mt-2 subtitle-1">
|
||||
{{ $formatDate(item.date_modified, {dateStyle: 'long'}) }}
|
||||
</div>
|
||||
</v-col>
|
||||
<v-col cols="auto">
|
||||
<v-tooltip
|
||||
:text="$formatMessage({
|
||||
description: 'Announcements view: mark as read button tooltip',
|
||||
defaultMessage: 'Mark as read',
|
||||
id: 'sUSVQS'
|
||||
})"
|
||||
:disabled="item._komga?.read"
|
||||
<v-card class="mb-4">
|
||||
<template #title>
|
||||
<a
|
||||
:href="item.url"
|
||||
target="_blank"
|
||||
class="text-h3 font-weight-medium link-underline"
|
||||
>{{ item.title }}</a
|
||||
>
|
||||
<template #activator="{ props }">
|
||||
<v-fab
|
||||
v-bind="props"
|
||||
icon="mdi-check"
|
||||
elevation="3"
|
||||
color="success"
|
||||
variant="outlined"
|
||||
size="small"
|
||||
:disabled="item._komga?.read"
|
||||
@click="markRead(item.id)"
|
||||
/>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
<template #subtitle>
|
||||
{{ $formatDate(item.date_modified, { dateStyle: 'long' }) }}
|
||||
</template>
|
||||
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<template #text>
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<div
|
||||
class="announcement"
|
||||
v-html="item.content_html"
|
||||
/>
|
||||
<!-- eslint-enable vue/no-v-html -->
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-divider
|
||||
v-if="index != announcements.items.length - 1"
|
||||
class="my-8"
|
||||
</template>
|
||||
|
||||
<template #actions>
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
:text="
|
||||
$formatMessage({
|
||||
description: 'Announcements view: mark as read button tooltip',
|
||||
defaultMessage: 'Mark as read',
|
||||
id: 'sUSVQS',
|
||||
})
|
||||
"
|
||||
:disabled="item._komga?.read"
|
||||
@click="markRead(item.id)"
|
||||
/>
|
||||
</template>
|
||||
</v-card>
|
||||
|
||||
<div
|
||||
v-if="index == announcements.items.length - 1"
|
||||
class="mb-16"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -84,11 +71,13 @@
|
|||
>
|
||||
<!-- 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'
|
||||
})"
|
||||
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="mdi-check-all"
|
||||
|
|
@ -98,17 +87,17 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {useAnnouncements} from '@/colada/queries/announcements.ts'
|
||||
import {useMarkAnnouncementsRead} from '@/colada/mutations/mark-announcements-read.ts'
|
||||
import {commonMessages} from '@/utils/common-messages.ts'
|
||||
import { useAnnouncements } from '@/colada/queries/announcements'
|
||||
import { useMarkAnnouncementsRead } from '@/colada/mutations/mark-announcements-read'
|
||||
import { commonMessages } from '@/utils/i18n/common-messages'
|
||||
|
||||
const {data: announcements, error, unreadCount, isLoading} = useAnnouncements()
|
||||
const { data: announcements, error, unreadCount, isLoading } = useAnnouncements()
|
||||
|
||||
const {mutate: markAnnouncementsRead} = useMarkAnnouncementsRead()
|
||||
const { mutate: markAnnouncementsRead } = useMarkAnnouncementsRead()
|
||||
|
||||
function markAllRead() {
|
||||
const ids = announcements.value?.items.map(x => x.id)
|
||||
if(ids) markAnnouncementsRead(ids)
|
||||
const ids = announcements.value?.items.map((x) => x.id)
|
||||
if (ids) markAnnouncementsRead(ids)
|
||||
}
|
||||
|
||||
function markRead(id: string) {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
//
|
||||
//
|
||||
</script>
|
||||
|
||||
<route lang="yaml">
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
//
|
||||
//
|
||||
</script>
|
||||
|
||||
<route lang="yaml">
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
//
|
||||
//
|
||||
</script>
|
||||
|
||||
<route lang="yaml">
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
$formatMessage({
|
||||
description: 'Updates view: banner shown at the top',
|
||||
defaultMessage: 'The latest version of Komga is already installed',
|
||||
id: 'WNY0pu'
|
||||
id: 'WNY0pu',
|
||||
})
|
||||
}}
|
||||
</v-alert>
|
||||
|
|
@ -37,7 +37,7 @@
|
|||
$formatMessage({
|
||||
description: 'Updates view: banner shown at the top',
|
||||
defaultMessage: 'Updates are available',
|
||||
id: 'n1Ik+L'
|
||||
id: 'n1Ik+L',
|
||||
})
|
||||
}}
|
||||
</v-alert>
|
||||
|
|
@ -49,19 +49,15 @@
|
|||
v-for="(release, index) in releases"
|
||||
:key="index"
|
||||
>
|
||||
<v-row
|
||||
justify="space-between"
|
||||
align="center"
|
||||
>
|
||||
<v-col cols="auto">
|
||||
<v-card class="my-4">
|
||||
<template #title>
|
||||
<div>
|
||||
<a
|
||||
:href="release.url"
|
||||
target="_blank"
|
||||
class="text-h4 font-weight-medium link-underline me-2"
|
||||
>{{
|
||||
release.version
|
||||
}}</a>
|
||||
>{{ release.version }}</a
|
||||
>
|
||||
<v-chip
|
||||
v-if="release.version == currentVersion"
|
||||
class="mx-2 mt-n3"
|
||||
|
|
@ -71,9 +67,10 @@
|
|||
>
|
||||
{{
|
||||
$formatMessage({
|
||||
description: 'Updates view: badge showing next to the currently installed release number',
|
||||
description:
|
||||
'Updates view: badge showing next to the currently installed release number',
|
||||
defaultMessage: 'Currently installed',
|
||||
id: '3jrAF6'
|
||||
id: '3jrAF6',
|
||||
})
|
||||
}}
|
||||
</v-chip>
|
||||
|
|
@ -87,42 +84,43 @@
|
|||
$formatMessage({
|
||||
description: 'Updates view: badge showing next to the latest release number',
|
||||
defaultMessage: 'Latest',
|
||||
id: '2Bh8F2'
|
||||
id: '2Bh8F2',
|
||||
})
|
||||
}}
|
||||
</v-chip>
|
||||
</div>
|
||||
<div class="mt-2 subtitle-1">
|
||||
{{ $formatDate(release.releaseDate, {dateStyle: 'long'}) }}
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<template #subtitle>
|
||||
{{ $formatDate(release.releaseDate, { dateStyle: 'long' }) }}
|
||||
</template>
|
||||
|
||||
<template #text>
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<div
|
||||
class="release"
|
||||
v-html="marked(release.description)"
|
||||
/>
|
||||
<!-- eslint-enable vue/no-v-html -->
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-divider
|
||||
v-if="index != releases.length - 1"
|
||||
class="my-8"
|
||||
/>
|
||||
</template>
|
||||
</v-card>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {useAppReleases} from '@/colada/queries/app-releases.ts'
|
||||
import {marked} from 'marked'
|
||||
import {commonMessages} from '@/utils/common-messages.ts'
|
||||
import { useAppReleases } from '@/colada/queries/app-releases'
|
||||
import { marked } from 'marked'
|
||||
import { commonMessages } from '@/utils/i18n/common-messages'
|
||||
|
||||
const {data: releases, error, buildVersion: currentVersion, isLatestVersion, latestRelease: latest, isLoading} = useAppReleases()
|
||||
const {
|
||||
data: releases,
|
||||
error,
|
||||
buildVersion: currentVersion,
|
||||
isLatestVersion,
|
||||
latestRelease: latest,
|
||||
isLoading,
|
||||
} = useAppReleases()
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<template #[`item.actions`]="{ item : user }">
|
||||
<template #[`item.actions`]="{ item: user }">
|
||||
<div class="d-flex ga-1 justify-end">
|
||||
<v-icon-btn
|
||||
v-tooltip:bottom="'Change password'"
|
||||
|
|
@ -84,7 +84,7 @@
|
|||
:max-width="currentAction === ACTION.PASSWORD ? 400 : 600"
|
||||
@update:record="handleDialogConfirmation()"
|
||||
>
|
||||
<template #text="{proxyModel}">
|
||||
<template #text="{ proxyModel }">
|
||||
<component
|
||||
:is="dialogComponent"
|
||||
v-model="proxyModel.value"
|
||||
|
|
@ -112,9 +112,7 @@
|
|||
<li>The read progress for this user account will be permanently deleted.</li>
|
||||
<li>Authentication activity for this user will be permanently deleted.</li>
|
||||
</ul>
|
||||
<div class="font-weight-bold mt-4">
|
||||
This action cannot be undone.
|
||||
</div>
|
||||
<div class="font-weight-bold mt-4">This action cannot be undone.</div>
|
||||
</v-alert>
|
||||
</template>
|
||||
</DialogConfirm>
|
||||
|
|
@ -122,64 +120,72 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {useUsers} from '@/colada/queries/users.ts'
|
||||
import {komgaClient} from '@/api/komga-client.ts'
|
||||
import type {components} from '@/generated/openapi/komga'
|
||||
import {useCurrentUser} from '@/colada/queries/current-user.ts'
|
||||
import {UserRoles} from '@/types/UserRoles.ts'
|
||||
import {useCreateUser, useDeleteUser, useUpdateUser, useUpdateUserPassword} from '@/colada/mutations/update-user.ts'
|
||||
import { useUsers } from '@/colada/queries/users'
|
||||
import { komgaClient } from '@/api/komga-client'
|
||||
import type { components } from '@/generated/openapi/komga'
|
||||
import { useCurrentUser } from '@/colada/queries/current-user'
|
||||
import { UserRoles } from '@/types/UserRoles'
|
||||
import {
|
||||
useCreateUser,
|
||||
useDeleteUser,
|
||||
useUpdateUser,
|
||||
useUpdateUserPassword,
|
||||
} from '@/colada/mutations/update-user'
|
||||
import FormUserChangePassword from '@/components/forms/user/FormUserChangePassword.vue'
|
||||
import FormUserEdit from '@/components/forms/user/FormUserEdit.vue'
|
||||
import type {Component} from 'vue'
|
||||
import {useLibraries} from '@/colada/queries/libraries.ts'
|
||||
import {commonMessages} from '@/utils/common-messages.ts'
|
||||
import type { Component } from 'vue'
|
||||
import { useLibraries } from '@/colada/queries/libraries'
|
||||
import { commonMessages } from '@/utils/i18n/common-messages'
|
||||
|
||||
// API data
|
||||
const {data: users, error, isLoading, refetch: refetchUsers} = useUsers()
|
||||
const {data: me} = useCurrentUser()
|
||||
|
||||
const { data: users, error, isLoading, refetch: refetchUsers } = useUsers()
|
||||
const { data: me } = useCurrentUser()
|
||||
|
||||
// Table
|
||||
const hideFooter = computed(() => users.value && users.value.length < 11)
|
||||
const headers = [
|
||||
{title: 'Email', key: 'email'},
|
||||
{title: 'Latest Activity', key: 'activity', value: (item: components["schemas"]["UserDto"]) => latestActivity[item.id]},
|
||||
{title: 'Roles', value: 'roles', sortable: false},
|
||||
{title: 'Actions', key: 'actions', align: 'end', sortable: false},
|
||||
{ title: 'Email', key: 'email' },
|
||||
{
|
||||
title: 'Latest Activity',
|
||||
key: 'activity',
|
||||
value: (item: components['schemas']['UserDto']) => latestActivity[item.id],
|
||||
},
|
||||
{ title: 'Roles', value: 'roles', sortable: false },
|
||||
{ title: 'Actions', key: 'actions', align: 'end', sortable: false },
|
||||
] as const // workaround for https://github.com/vuetifyjs/vuetify/issues/18901
|
||||
|
||||
function getRoleColor(role: UserRoles) {
|
||||
if(role === UserRoles.ADMIN) return 'error'
|
||||
if (role === UserRoles.ADMIN) return 'error'
|
||||
}
|
||||
|
||||
|
||||
// store each user's latest activity in a map
|
||||
// when the 'users' change, we call the API for each user
|
||||
const latestActivity: Record<string, Date | undefined> = reactive({})
|
||||
|
||||
function getLatestActivity(userId: string) {
|
||||
komgaClient.GET('/api/v2/users/{id}/authentication-activity/latest', {
|
||||
params: {
|
||||
path: { id: userId }
|
||||
}
|
||||
})
|
||||
komgaClient
|
||||
.GET('/api/v2/users/{id}/authentication-activity/latest', {
|
||||
params: {
|
||||
path: { id: userId },
|
||||
},
|
||||
})
|
||||
// unwrap the openapi-fetch structure on success
|
||||
.then((res) => latestActivity[userId] = res.data?.dateTime)
|
||||
.then((res) => (latestActivity[userId] = res.data?.dateTime))
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
watch(users, (users) => {
|
||||
if(users) for (const user of users) {
|
||||
getLatestActivity(user.id)
|
||||
}
|
||||
if (users)
|
||||
for (const user of users) {
|
||||
getLatestActivity(user.id)
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => refetchUsers())
|
||||
|
||||
|
||||
// Dialogs handling
|
||||
// stores the user being actioned upon
|
||||
const userRecord = ref<components["schemas"]["UserDto"]>()
|
||||
const userRecord = ref<components['schemas']['UserDto']>()
|
||||
// stores the ongoing action, so we can handle the action when the dialog is closed with changes
|
||||
const currentAction = ref<ACTION>()
|
||||
// the record passed to the dialog's form's model
|
||||
|
|
@ -190,17 +196,20 @@ const dialogTitle = ref<string>()
|
|||
// dynamic component for the dialog's inner form
|
||||
const dialogComponent = shallowRef<Component>()
|
||||
|
||||
const {mutate: mutateCreateUser} = useCreateUser()
|
||||
const {mutate: mutateUser} = useUpdateUser()
|
||||
const {mutate: mutateUserPassword} = useUpdateUserPassword()
|
||||
const {mutate: mutateDeleteUser} = useDeleteUser()
|
||||
const {data: libraries} = useLibraries()
|
||||
const { mutate: mutateCreateUser } = useCreateUser()
|
||||
const { mutate: mutateUser } = useUpdateUser()
|
||||
const { mutate: mutateUserPassword } = useUpdateUserPassword()
|
||||
const { mutate: mutateDeleteUser } = useDeleteUser()
|
||||
const { data: libraries } = useLibraries()
|
||||
|
||||
enum ACTION {
|
||||
ADD, EDIT, DELETE, PASSWORD
|
||||
ADD,
|
||||
EDIT,
|
||||
DELETE,
|
||||
PASSWORD,
|
||||
}
|
||||
|
||||
function showDialog(action: ACTION, user?: components["schemas"]["UserDto"]) {
|
||||
function showDialog(action: ACTION, user?: components['schemas']['UserDto']) {
|
||||
currentAction.value = action
|
||||
switch (action) {
|
||||
case ACTION.ADD:
|
||||
|
|
@ -213,36 +222,38 @@ function showDialog(action: ACTION, user?: components["schemas"]["UserDto"]) {
|
|||
sharedLibraries: {
|
||||
all: true,
|
||||
// we fill the array with all libraries for a nicer display in the edit dialog
|
||||
libraryIds: libraries.value?.map(x => x.id) || [],
|
||||
libraryIds: libraries.value?.map((x) => x.id) || [],
|
||||
},
|
||||
ageRestriction: {
|
||||
age: 0,
|
||||
restriction: 'NONE',
|
||||
}
|
||||
} as components["schemas"]["UserCreationDto"]
|
||||
break;
|
||||
},
|
||||
} as components['schemas']['UserCreationDto']
|
||||
break
|
||||
case ACTION.EDIT:
|
||||
dialogTitle.value = 'Edit User'
|
||||
dialogComponent.value = FormUserEdit
|
||||
dialogRecord.value = {
|
||||
...user,
|
||||
roles: user?.roles.filter(x => x !== 'USER'),
|
||||
roles: user?.roles.filter((x) => x !== 'USER'),
|
||||
sharedLibraries: {
|
||||
all: user?.sharedAllLibraries,
|
||||
// we fill the array with all libraries for a nicer display in the edit dialog
|
||||
libraryIds: user?.sharedAllLibraries ? libraries.value?.map(x => x.id) || [] : user?.sharedLibrariesIds,
|
||||
libraryIds: user?.sharedAllLibraries
|
||||
? libraries.value?.map((x) => x.id) || []
|
||||
: user?.sharedLibrariesIds,
|
||||
},
|
||||
ageRestriction: user?.ageRestriction || {
|
||||
age: 0,
|
||||
restriction: 'NONE',
|
||||
}
|
||||
} as components["schemas"]["UserUpdateDto"]
|
||||
break;
|
||||
},
|
||||
} as components['schemas']['UserUpdateDto']
|
||||
break
|
||||
case ACTION.DELETE:
|
||||
dialogTitle.value = 'Delete User'
|
||||
dialogComponent.value = FormUserEdit
|
||||
dialogRecord.value = user
|
||||
break;
|
||||
break
|
||||
case ACTION.PASSWORD:
|
||||
dialogTitle.value = 'Change Password'
|
||||
dialogComponent.value = FormUserChangePassword
|
||||
|
|
@ -255,20 +266,20 @@ function showDialog(action: ACTION, user?: components["schemas"]["UserDto"]) {
|
|||
function handleDialogConfirmation() {
|
||||
switch (currentAction.value) {
|
||||
case ACTION.ADD:
|
||||
mutateCreateUser(dialogRecord.value as components["schemas"]["UserCreationDto"])
|
||||
break;
|
||||
mutateCreateUser(dialogRecord.value as components['schemas']['UserCreationDto'])
|
||||
break
|
||||
case ACTION.EDIT:
|
||||
mutateUser(dialogRecord.value as components["schemas"]["UserDto"])
|
||||
break;
|
||||
mutateUser(dialogRecord.value as components['schemas']['UserDto'])
|
||||
break
|
||||
case ACTION.DELETE:
|
||||
mutateDeleteUser(userRecord.value!.id)
|
||||
break;
|
||||
break
|
||||
case ACTION.PASSWORD:
|
||||
mutateUserPassword({
|
||||
userId: userRecord.value!.id,
|
||||
newPassword: dialogRecord.value as string,
|
||||
})
|
||||
break;
|
||||
break
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -9,22 +9,20 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {useCurrentUser} from '@/colada/queries/current-user'
|
||||
import { useCurrentUser } from '@/colada/queries/current-user'
|
||||
|
||||
async function checkAuthenticated() {
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const {data, error, refresh} = useCurrentUser()
|
||||
const { data, error, refresh } = useCurrentUser()
|
||||
|
||||
await refresh()
|
||||
if (data.value) {
|
||||
if(route.query.redirect)
|
||||
await router.push({path: route.query.redirect.toString()})
|
||||
else
|
||||
await router.push('/')
|
||||
if (route.query.redirect) await router.push({ path: route.query.redirect.toString() })
|
||||
else await router.push('/')
|
||||
}
|
||||
if (error.value) {
|
||||
await router.push({name: '/login', query: {redirect: route.query.redirect}})
|
||||
await router.push({ name: '/login', query: { redirect: route.query.redirect } })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,18 +5,18 @@
|
|||
*/
|
||||
|
||||
// Plugins
|
||||
import {vuetify, vuetifyRulesPlugin} from './vuetify'
|
||||
import { vuetify, vuetifyRulesPlugin } from './vuetify'
|
||||
import pinia from '../stores'
|
||||
import router from '../router'
|
||||
import {PiniaColada} from '@pinia/colada'
|
||||
import { PiniaColada } from '@pinia/colada'
|
||||
import { PiniaColadaAutoRefetch } from '@pinia/colada-plugin-auto-refetch'
|
||||
import {vueIntl} from '@/plugins/vue-intl.ts'
|
||||
import { vueIntl } from '@/plugins/vue-intl'
|
||||
|
||||
// Types
|
||||
import type {App} from 'vue'
|
||||
import type { App } from 'vue'
|
||||
// Navigation guards
|
||||
import {useLoginGuard} from '@/router/login-guard'
|
||||
import {useRoleGuard} from '@/router/role-guard.ts'
|
||||
import { useLoginGuard } from '@/router/login-guard'
|
||||
import { useRoleGuard } from '@/router/role-guard'
|
||||
|
||||
export function registerPlugins(app: App) {
|
||||
app
|
||||
|
|
@ -27,9 +27,7 @@ export function registerPlugins(app: App) {
|
|||
.use(router)
|
||||
.use(pinia)
|
||||
.use(PiniaColada, {
|
||||
plugins: [
|
||||
PiniaColadaAutoRefetch()
|
||||
]
|
||||
plugins: [PiniaColadaAutoRefetch()],
|
||||
})
|
||||
|
||||
// register navigation guards
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import {createIntl} from 'vue-intl'
|
||||
import {currentLocale, defaultLocale, loadLocale} from '@/utils/locale-helper'
|
||||
import { createIntl } from 'vue-intl'
|
||||
import { currentLocale, defaultLocale, loadLocale } from '@/utils/i18n/locale-helper'
|
||||
|
||||
const messages = loadLocale(currentLocale)
|
||||
|
||||
export const vueIntl = createIntl({
|
||||
locale: currentLocale,
|
||||
defaultLocale: defaultLocale,
|
||||
messages
|
||||
messages,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -9,27 +9,26 @@ import '@mdi/font/css/materialdesignicons.css'
|
|||
import 'vuetify/styles'
|
||||
|
||||
// Composables
|
||||
import {createVuetify} from 'vuetify'
|
||||
import {md3} from 'vuetify/blueprints'
|
||||
import { createVuetify } from 'vuetify'
|
||||
import { md3 } from 'vuetify/blueprints'
|
||||
|
||||
// Labs
|
||||
import {VIconBtn} from 'vuetify/labs/components'
|
||||
import {createRulesPlugin} from 'vuetify/labs/rules'
|
||||
import { VIconBtn } from 'vuetify/labs/components'
|
||||
import { createRulesPlugin } from 'vuetify/labs/rules'
|
||||
|
||||
|
||||
import {availableLocales, currentLocale, defaultLocale} from '@/utils/locale-helper'
|
||||
import { availableLocales, currentLocale, defaultLocale } from '@/utils/i18n/locale-helper'
|
||||
|
||||
// load vuetify locales only for the available locales in i18n
|
||||
async function loadVuetifyLocale(locale: string) {
|
||||
return await import(`../../node_modules/vuetify/lib/locale/${locale}.js`)
|
||||
}
|
||||
|
||||
const messages: Record<string, string> = {};
|
||||
void (async()=>{
|
||||
for (const locale of Object.keys(availableLocales)) {
|
||||
messages[locale] = (await loadVuetifyLocale(locale)).default
|
||||
}
|
||||
})();
|
||||
const messages: Record<string, string> = {}
|
||||
void (async () => {
|
||||
for (const locale of Object.keys(availableLocales)) {
|
||||
messages[locale] = (await loadVuetifyLocale(locale)).default
|
||||
}
|
||||
})()
|
||||
|
||||
// https://vuetifyjs.com/en/introduction/why-vuetify/#feature-guides
|
||||
export const vuetify = createVuetify({
|
||||
|
|
@ -65,10 +64,13 @@ export const vuetify = createVuetify({
|
|||
},
|
||||
})
|
||||
|
||||
export const vuetifyRulesPlugin = createRulesPlugin({
|
||||
aliases: {
|
||||
sameAs: (other?: string, err?: string) => {
|
||||
return (v: unknown) => other === v || err || 'Field must have the same value'
|
||||
export const vuetifyRulesPlugin = createRulesPlugin(
|
||||
{
|
||||
aliases: {
|
||||
sameAs: (other?: string, err?: string) => {
|
||||
return (v: unknown) => other === v || err || 'Field must have the same value'
|
||||
},
|
||||
},
|
||||
},
|
||||
}, vuetify.locale)
|
||||
vuetify.locale,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@
|
|||
*/
|
||||
|
||||
// Composables
|
||||
import {createRouter, createWebHistory} from 'vue-router/auto'
|
||||
import {setupLayouts} from 'virtual:generated-layouts'
|
||||
import {routes} from 'vue-router/auto-routes'
|
||||
import { createRouter, createWebHistory } from 'vue-router/auto'
|
||||
import { setupLayouts } from 'virtual:generated-layouts'
|
||||
import { routes } from 'vue-router/auto-routes'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type {Router} from 'vue-router'
|
||||
import {useCurrentUser} from '@/colada/queries/current-user'
|
||||
import type { Router } from 'vue-router'
|
||||
import { useCurrentUser } from '@/colada/queries/current-user'
|
||||
|
||||
// check if the user is authenticated before navigating to any page
|
||||
// the authentication is cached by Pinia Colada
|
||||
|
|
@ -7,11 +7,11 @@ import {useCurrentUser} from '@/colada/queries/current-user'
|
|||
export function useLoginGuard(router: Router) {
|
||||
router.beforeEach((to) => {
|
||||
if (!to.meta.noAuth) {
|
||||
const {data} = useCurrentUser()
|
||||
const { data } = useCurrentUser()
|
||||
const authenticated = data.value
|
||||
if(!authenticated) {
|
||||
const query = Object.assign({}, to.query, {redirect: to.fullPath})
|
||||
return {name: '/startup', query: query}
|
||||
if (!authenticated) {
|
||||
const query = Object.assign({}, to.query, { redirect: to.fullPath })
|
||||
return { name: '/startup', query: query }
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type {Router} from 'vue-router'
|
||||
import {useCurrentUser} from '@/colada/queries/current-user'
|
||||
import type { Router } from 'vue-router'
|
||||
import { useCurrentUser } from '@/colada/queries/current-user'
|
||||
|
||||
// check if the user has the necessary role before navigating to restricted pages
|
||||
// the authentication is cached by Pinia Colada
|
||||
|
|
@ -7,9 +7,9 @@ import {useCurrentUser} from '@/colada/queries/current-user'
|
|||
export function useRoleGuard(router: Router) {
|
||||
router.beforeEach((to) => {
|
||||
if (to.meta.requiresRole) {
|
||||
const {data} = useCurrentUser()
|
||||
if(!data.value?.roles?.includes(to.meta.requiresRole)) {
|
||||
return {name: '/'}
|
||||
const { data } = useCurrentUser()
|
||||
if (!data.value?.roles?.includes(to.meta.requiresRole)) {
|
||||
return { name: '/' }
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Utilities
|
||||
import {defineStore} from 'pinia'
|
||||
import {useDisplay} from 'vuetify'
|
||||
import { defineStore } from 'pinia'
|
||||
import { useDisplay } from 'vuetify'
|
||||
|
||||
export const useAppStore = defineStore('app', {
|
||||
state: () => ({
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ html {
|
|||
|
||||
.link-underline {
|
||||
text-decoration: none;
|
||||
color: var(--v-anchor-base)
|
||||
color: var(--v-anchor-base);
|
||||
}
|
||||
|
||||
.link-underline:hover {
|
||||
|
|
|
|||
|
|
@ -8,4 +8,3 @@
|
|||
// @use 'vuetify/settings' with (
|
||||
// $reset: true
|
||||
// );
|
||||
|
||||
|
|
|
|||
|
|
@ -1,22 +1,22 @@
|
|||
export interface ActuatorInfo {
|
||||
git: ActuatorGit,
|
||||
git: ActuatorGit
|
||||
build: ActuatorBuild
|
||||
}
|
||||
|
||||
export interface ActuatorGit {
|
||||
commit: ActuatorGitCommit,
|
||||
commit: ActuatorGitCommit
|
||||
branch: string
|
||||
}
|
||||
|
||||
export interface ActuatorGitCommit {
|
||||
time: Date,
|
||||
time: Date
|
||||
id: string
|
||||
}
|
||||
|
||||
export interface ActuatorBuild {
|
||||
version: string,
|
||||
artifact: string,
|
||||
name: string,
|
||||
group: string,
|
||||
version: string
|
||||
artifact: string
|
||||
name: string
|
||||
group: string
|
||||
time: Date
|
||||
}
|
||||
|
|
|
|||
2
next-ui/src/types/RouterMeta.d.ts
vendored
2
next-ui/src/types/RouterMeta.d.ts
vendored
|
|
@ -2,7 +2,7 @@
|
|||
// It can also be added to a `.d.ts` file. Make sure it's included in
|
||||
// project's tsconfig.json "files"
|
||||
import 'vue-router'
|
||||
import type {UserRoles} from '@/types/UserRoles.ts'
|
||||
import type { UserRoles } from '@/types/UserRoles'
|
||||
|
||||
// To ensure it is treated as a module, add at least one `export` statement
|
||||
export {}
|
||||
|
|
|
|||
|
|
@ -3,5 +3,5 @@ export enum UserRoles {
|
|||
FILE_DOWNLOAD = 'FILE_DOWNLOAD',
|
||||
PAGE_STREAMING = 'PAGE_STREAMING',
|
||||
KOBO_SYNC = 'KOBO_SYNC',
|
||||
KOREADER_SYNC = 'KOREADER_SYNC'
|
||||
KOREADER_SYNC = 'KOREADER_SYNC',
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +0,0 @@
|
|||
import {defineMessage} from 'vue-intl'
|
||||
|
||||
export const commonMessages = {
|
||||
somethingWentWrongTitle: defineMessage({
|
||||
description: 'Common message: an error happened while loading data',
|
||||
defaultMessage: 'Something went wrong',
|
||||
id: 'ixQlWv',
|
||||
}),
|
||||
somethingWentWrongSubTitle: defineMessage({
|
||||
description: 'Common message: an error happened while loading data, explanation',
|
||||
defaultMessage: 'There might be a problem with your connection or your server.',
|
||||
id: 'hYO2n6',
|
||||
}),
|
||||
}
|
||||
14
next-ui/src/utils/i18n/common-messages.ts
Normal file
14
next-ui/src/utils/i18n/common-messages.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { defineMessage } from 'vue-intl'
|
||||
|
||||
export const commonMessages = {
|
||||
somethingWentWrongTitle: defineMessage({
|
||||
description: 'Common message: an error happened while loading data',
|
||||
defaultMessage: 'Something went wrong',
|
||||
id: 'ixQlWv',
|
||||
}),
|
||||
somethingWentWrongSubTitle: defineMessage({
|
||||
description: 'Common message: an error happened while loading data, explanation',
|
||||
defaultMessage: 'There might be a problem with your connection or your server.',
|
||||
id: 'hYO2n6',
|
||||
}),
|
||||
}
|
||||
|
|
@ -1,12 +1,13 @@
|
|||
import {defineMessage} from 'vue-intl'
|
||||
import { defineMessage } from 'vue-intl'
|
||||
import localeMessages from '../i18n?dir2json&ext=.json&1'
|
||||
|
||||
export const defaultLocale = 'en'
|
||||
|
||||
const localeName = defineMessage({
|
||||
description: 'The name of the locale, shown in the language selection menu. Must be translated to the language\'s name',
|
||||
description:
|
||||
"The name of the locale, shown in the language selection menu. Must be translated to the language's name",
|
||||
defaultMessage: 'English',
|
||||
id: 'app.locale-name'
|
||||
id: 'app.locale-name',
|
||||
})
|
||||
|
||||
/**
|
||||
|
|
@ -16,14 +17,16 @@ const localeName = defineMessage({
|
|||
*/
|
||||
export function loadLocale(locale: string): Record<string, string> {
|
||||
const localeToLoad = locale in availableLocales ? locale : defaultLocale
|
||||
return (localeMessages as unknown as Record<string, Record<string, string>>)[localeToLoad]!;
|
||||
return (localeMessages as unknown as Record<string, Record<string, string>>)[localeToLoad]!
|
||||
}
|
||||
|
||||
|
||||
function loadAvailableLocales(): Record<string, string> {
|
||||
const localesInfo: Record<string, string> = {}
|
||||
Object.keys(localeMessages).forEach(x =>
|
||||
localesInfo[x] = (localeMessages as unknown as Record<string, Record<string, string>>)[x]![localeName.id]!
|
||||
Object.keys(localeMessages).forEach(
|
||||
(x) =>
|
||||
(localesInfo[x] = (localeMessages as unknown as Record<string, Record<string, string>>)[x]![
|
||||
localeName.id
|
||||
]!),
|
||||
)
|
||||
return localesInfo
|
||||
}
|
||||
|
|
@ -51,7 +54,7 @@ export const currentLocale = getLocale()
|
|||
* @param locale the new locale
|
||||
*/
|
||||
export function setLocale(locale: string) {
|
||||
if(locale !== currentLocale) {
|
||||
if (locale !== currentLocale) {
|
||||
localStorage.setItem('userLocale', locale)
|
||||
window.location.reload()
|
||||
}
|
||||
1
next-ui/src/utils/utils.ts
Normal file
1
next-ui/src/utils/utils.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export const delay = (ms: number) => new Promise((res) => setTimeout(res, ms))
|
||||
|
|
@ -3,17 +3,28 @@
|
|||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue", "./dir2json.d.ts"],
|
||||
"exclude": ["src/**/__tests__/*"],
|
||||
"compilerOptions": {
|
||||
"composite": false,
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"noUncheckedIndexedAccess": true, // openapi-ts
|
||||
"allowImportingTsExtensions":true,
|
||||
"target": "ESNext",
|
||||
"moduleResolution": "bundler", // Komga: https://uvr.esm.is/introduction.html#setup
|
||||
"noUncheckedIndexedAccess": true, // Komga: https://openapi-ts.dev/introduction
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
},
|
||||
"types": ["vite-plugin-vue-layouts-next/client"],
|
||||
"types": [
|
||||
"unplugin-vue-router/client", // Komga: https://uvr.esm.is/introduction.html#setup
|
||||
"vite-plugin-vue-layouts-next/client" // Komga: https://github.com/loicduong/vite-plugin-vue-layouts-next#client-types
|
||||
],
|
||||
"skipLibCheck": true,
|
||||
"isolatedModules": true,
|
||||
"module": "preserve",
|
||||
"noEmit": true,
|
||||
"strict": true,
|
||||
"allowUnreachableCode": false,
|
||||
"allowUnusedLabels": false,
|
||||
"noImplicitOverride": true,
|
||||
"allowImportingTsExtensions": false,
|
||||
"exactOptionalPropertyTypes": true,
|
||||
"lib": ["esnext", "dom", "dom.iterable"]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue