feat(webui): add user setting to blur unread posters

Closes: #1549
This commit is contained in:
Gauthier Roebroeck 2025-02-06 11:39:01 +08:00
parent 05f73f0d1f
commit 4892945ddf
10 changed files with 136 additions and 24 deletions

View file

@ -14,6 +14,7 @@
aspect-ratio="0.7071"
:contain="!isStretch"
:position="isStretch ? 'top' : undefined"
:class="shouldBlurPoster ? 'blur' : undefined"
@error="thumbnailError = true"
@load="thumbnailError = false"
>
@ -277,6 +278,12 @@ export default Vue.extend({
isStretch(): boolean {
return this.$store.getters.getClientSettings[CLIENT_SETTING.WEBUI_POSTER_STRETCH]?.value === 'true'
},
isBlurUnread(): boolean {
return this.$store.getters.getClientSettings[CLIENT_SETTING.WEBUI_POSTER_BLUR_UNREAD]?.value === 'true'
},
shouldBlurPoster(): boolean | undefined {
return (this.isUnread || this.allUnread) && this.isBlurUnread
},
canReadPages(): boolean {
return this.$store.getters.mePageStreaming && this.computedItem.type() === ItemTypes.BOOK
},
@ -317,6 +324,10 @@ export default Vue.extend({
if (this.computedItem.type() === ItemTypes.SERIES) return (this.item as SeriesDto).booksUnreadCount + (this.item as SeriesDto).booksInProgressCount
return undefined
},
allUnread(): boolean | undefined {
if (this.computedItem.type() === ItemTypes.SERIES) return (this.item as SeriesDto).booksCount == (this.item as SeriesDto).booksUnreadCount
return undefined
},
readProgressPercentage(): number {
if (this.computedItem.type() === ItemTypes.BOOK) return getReadProgressPercentage(this.item as BookDto)
return 0
@ -385,6 +396,10 @@ export default Vue.extend({
</script>
<style>
.blur > .v-image__image {
filter: blur(5px);
}
.no-link {
cursor: default;
}

View file

@ -1027,6 +1027,7 @@
"ui_settings": {
"label_oauth2_auto_login": "Automatic OAuth2 login",
"label_oauth2_hide_login": "Hide login fields if OAuth2 is enabled",
"label_poster_blur_unread": "Blur poster for unread books and series",
"label_poster_stretch": "Stretch poster to fit card",
"tooltip_oauth2_auto_login": "Requires a single OAuth2 provider, and 'hide login fields' enabled"
},

View file

@ -25,8 +25,10 @@ const vuexModule: Module<any, any> = {
},
},
actions: {
async getClientSettings({commit}) {
async getClientSettingsGlobal({commit}) {
commit('setClientSettingsGlobal', await service.getClientSettingsGlobal())
},
async getClientSettingsUser({commit}) {
commit('setClientSettingsUser', await service.getClientSettingsUser())
},
},

View file

@ -151,6 +151,11 @@ const router = new Router({
name: 'account-api-keys',
component: () => import(/* webpackChunkName: "account-api-keys" */ './views/ApiKeys.vue'),
},
{
path: '/account/settings-ui',
name: 'account-settings-ui',
component: () => import(/* webpackChunkName: "account-settings-ui" */ './views/UIUserSettings.vue'),
},
{
path: '/account/authentication-activity',
name: 'account-activity',

View file

@ -15,5 +15,6 @@ export interface ClientSettingUserUpdateDto {
export enum CLIENT_SETTING {
WEBUI_OAUTH2_HIDE_LOGIN = 'webui.oauth2.hide_login',
WEBUI_OAUTH2_AUTO_LOGIN = 'webui.oauth2.auto_login',
WEBUI_POSTER_STRETCH = 'webui.poster.stretch'
WEBUI_POSTER_STRETCH = 'webui.poster.stretch',
WEBUI_POSTER_BLUR_UNREAD = 'webui.poster.blur_unread',
}

View file

@ -258,6 +258,10 @@
<v-list-item-title>{{ $t('users.api_keys') }}</v-list-item-title>
</v-list-item>
<v-list-item :to="{name: 'account-settings-ui'}">
<v-list-item-title>{{ $t('common.ui') }}</v-list-item-title>
</v-list-item>
<v-list-item :to="{name: 'account-activity'}">
<v-list-item-title>{{ $t('users.authentication_activity') }}</v-list-item-title>
</v-list-item>

View file

@ -300,7 +300,8 @@ export default Vue.extend({
})
await this.$store.dispatch('getLibraries')
await this.$store.dispatch('getClientSettings')
await this.$store.dispatch('getClientSettingsGlobal')
await this.$store.dispatch('getClientSettingsUser')
if (this.$route.query.redirect) {
await this.$router.push({path: this.$route.query.redirect.toString()})

View file

@ -42,7 +42,8 @@ export default Vue.extend({
await this.$store.dispatch('getMe')
await this.$store.dispatch('getLibraries')
await this.$store.dispatch('getClientSettings')
await this.$store.dispatch('getClientSettingsGlobal')
await this.$store.dispatch('getClientSettingsUser')
this.$router.back()
} catch (e) {
this.$router.push({name: 'login', query: {redirect: this.$route.query.redirect}})

View file

@ -8,6 +8,7 @@
:label="$t('ui_settings.label_oauth2_hide_login')"
hide-details
/>
<v-checkbox
v-model="form.oauth2AutoLogin"
@change="$v.form.oauth2AutoLogin.$touch()"
@ -24,15 +25,7 @@
{{ $t('ui_settings.tooltip_oauth2_auto_login') }}
</v-tooltip>
</template>
</v-checkbox>
<v-checkbox
v-model="form.posterStretch"
@change="$v.form.posterStretch.$touch()"
:label="$t('ui_settings.label_poster_stretch')"
hide-details
/>
</v-col>
</v-row>
<v-row>
@ -57,7 +50,7 @@
import Vue from 'vue'
import ConfirmationDialog from '@/components/dialogs/ConfirmationDialog.vue'
import FileBrowserDialog from '@/components/dialogs/FileBrowserDialog.vue'
import {CLIENT_SETTING, ClientSettingDto, ClientSettingGlobalUpdateDto} from '@/types/komga-clientsettings'
import {CLIENT_SETTING, ClientSettingGlobalUpdateDto} from '@/types/komga-clientsettings'
export default Vue.extend({
name: 'UISettings',
@ -66,15 +59,12 @@ export default Vue.extend({
form: {
oauth2HideLogin: false,
oauth2AutoLogin: false,
posterStretch: false,
},
existingSettings: [] as ClientSettingDto[],
}),
validations: {
form: {
oauth2HideLogin: {},
oauth2AutoLogin: {},
posterStretch: {},
},
},
mounted() {
@ -90,10 +80,9 @@ export default Vue.extend({
},
methods: {
async refreshSettings() {
await this.$store.dispatch('getClientSettings')
await this.$store.dispatch('getClientSettingsGlobal')
this.form.oauth2HideLogin = this.$store.state.komgaSettings.clientSettingsGlobal[CLIENT_SETTING.WEBUI_OAUTH2_HIDE_LOGIN]?.value === 'true'
this.form.oauth2AutoLogin = this.$store.state.komgaSettings.clientSettingsGlobal[CLIENT_SETTING.WEBUI_OAUTH2_AUTO_LOGIN]?.value === 'true'
this.form.posterStretch = this.$store.state.komgaSettings.clientSettingsGlobal[CLIENT_SETTING.WEBUI_POSTER_STRETCH]?.value === 'true'
this.$v.form.$reset()
},
async saveSettings() {
@ -110,12 +99,6 @@ export default Vue.extend({
allowUnauthorized: true,
}
if (this.$v.form?.posterStretch?.$dirty)
newSettings[CLIENT_SETTING.WEBUI_POSTER_STRETCH] = {
value: this.form.posterStretch ? 'true' : 'false',
allowUnauthorized: false,
}
await this.$komgaSettings.updateClientSettingGlobal(newSettings)
await this.refreshSettings()

View file

@ -0,0 +1,99 @@
<template>
<v-container fluid class="pa-6">
<v-row>
<v-col cols="auto">
<v-checkbox
v-model="form.posterStretch"
@change="$v.form.posterStretch.$touch()"
:label="$t('ui_settings.label_poster_stretch')"
hide-details
/>
<v-checkbox
v-model="form.posterBlurUnread"
@change="$v.form.posterBlurUnread.$touch()"
:label="$t('ui_settings.label_poster_blur_unread')"
hide-details
/>
</v-col>
</v-row>
<v-row>
<v-col cols="auto">
<v-btn @click="refreshSettings"
:disabled="discardDisabled"
>{{ $t('common.discard') }}
</v-btn>
</v-col>
<v-col cols="auto">
<v-btn color="primary"
:disabled="saveDisabled"
@click="saveSettings"
>{{ $t('common.save_changes') }}
</v-btn>
</v-col>
</v-row>
</v-container>
</template>
<script lang="ts">
import Vue from 'vue'
import ConfirmationDialog from '@/components/dialogs/ConfirmationDialog.vue'
import FileBrowserDialog from '@/components/dialogs/FileBrowserDialog.vue'
import {CLIENT_SETTING, ClientSettingUserUpdateDto} from '@/types/komga-clientsettings'
export default Vue.extend({
name: 'UIUserSettings',
components: {FileBrowserDialog, ConfirmationDialog},
data: () => ({
form: {
posterStretch: false,
posterBlurUnread: false,
},
}),
validations: {
form: {
posterStretch: {},
posterBlurUnread: {},
},
},
mounted() {
this.refreshSettings()
},
computed: {
saveDisabled(): boolean {
return this.$v.form.$invalid || !this.$v.form.$anyDirty
},
discardDisabled(): boolean {
return !this.$v.form.$anyDirty
},
},
methods: {
async refreshSettings() {
await this.$store.dispatch('getClientSettingsUser')
this.form.posterStretch = this.$store.state.komgaSettings.clientSettingsUser[CLIENT_SETTING.WEBUI_POSTER_STRETCH]?.value === 'true'
this.form.posterBlurUnread = this.$store.state.komgaSettings.clientSettingsUser[CLIENT_SETTING.WEBUI_POSTER_BLUR_UNREAD]?.value === 'true'
this.$v.form.$reset()
},
async saveSettings() {
let newSettings = {} as Record<string, ClientSettingUserUpdateDto>
if (this.$v.form?.posterStretch?.$dirty)
newSettings[CLIENT_SETTING.WEBUI_POSTER_STRETCH] = {
value: this.form.posterStretch ? 'true' : 'false',
}
if (this.$v.form?.posterBlurUnread?.$dirty)
newSettings[CLIENT_SETTING.WEBUI_POSTER_BLUR_UNREAD] = {
value: this.form.posterBlurUnread ? 'true' : 'false',
}
await this.$komgaSettings.updateClientSettingUser(newSettings)
await this.refreshSettings()
},
},
})
</script>
<style scoped>
</style>