Skip to content

Commit b30e971

Browse files
committed
feat(images): add setting to enforces show image or poster UI in tabs
1 parent 74b4898 commit b30e971

File tree

8 files changed

+190
-20
lines changed

8 files changed

+190
-20
lines changed

src/components/common/poster/PosterComponent.vue

+5-2
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,11 @@ const getPosters = async (
9999
100100
const query = _item.getPosterQuery?.();
101101
if (!query) return;
102-
if (_type === 'show' && (_item.type === 'season' || _item.type === 'episode')) {
103-
query.type = _type;
102+
if (
103+
(_type === 'show' || !backdrop.value) &&
104+
(_item.type === 'season' || _item.type === 'episode')
105+
) {
106+
query.type = 'show';
104107
if (_item.type === 'episode') delete query.episode;
105108
if (_item.type === 'season') delete query.season;
106109
}

src/components/views/settings/SettingsComponent.vue

+4
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ const SettingsTabs = lazyComponent(
4646
const SettingsWatching = lazyComponent(
4747
() => import('~/components/views/settings/SettingsWatching.vue'),
4848
);
49+
const SettingsImage = lazyComponent(
50+
() => import('~/components/views/settings/SettingsImage.vue'),
51+
);
4952
5053
const i18n = useI18n('settings');
5154
@@ -68,6 +71,7 @@ const sections = computed<Section[]>(() =>
6871
},
6972
{ title: 'menu__tabs', reference: ref(), component: SettingsTabs },
7073
{ title: 'menu__links', reference: ref(), component: SettingsLinks },
74+
{ title: 'menu__images', reference: ref(), component: SettingsImage },
7175
{ title: 'menu__menus', reference: ref(), component: SettingsMenus },
7276
{ title: 'menu__watching', reference: ref(), component: SettingsWatching },
7377
{ title: 'menu__activity', reference: ref(), component: SettingsActivity },

src/components/views/settings/SettingsFormItem.vue

+14-2
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,24 @@ defineProps({
1616
type: Object as PropType<FormItemGiProps>,
1717
required: false,
1818
},
19+
vertical: {
20+
type: Boolean,
21+
required: false,
22+
default: false,
23+
},
1924
});
2025
</script>
2126

2227
<template>
23-
<NFlex class="flex-auto" wrap justify="space-between" align="center">
28+
<NFlex class="flex-auto" justify="space-between" align="center" wrap>
2429
<NFormItem
2530
class="form-row"
31+
:class="{ vertical }"
2632
label-placement="left"
2733
:show-feedback="false"
2834
v-bind="form"
2935
>
30-
<template #label>
36+
<template v-if="label" #label>
3137
<span class="from-label">{{ label }}</span>
3238
</template>
3339
<slot />
@@ -81,5 +87,11 @@ defineProps({
8187
color: var(--white-mute);
8288
}
8389
}
90+
91+
&.vertical {
92+
flex-direction: column;
93+
align-items: flex-start;
94+
margin-top: 8px;
95+
}
8496
}
8597
</style>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
<script lang="ts" setup>
2+
import { NFlex, NSelect, NText } from 'naive-ui';
3+
4+
import { computed, ref } from 'vue';
5+
6+
import type { Route } from '~/models/router.model';
7+
8+
import SettingsFormItem from '~/components/views/settings/SettingsFormItem.vue';
9+
import {
10+
type ImageFormat,
11+
type ImageType,
12+
useExtensionSettingsStore,
13+
useExtensionSettingsStoreRefs,
14+
} from '~/stores/settings/extension.store';
15+
import { useI18n } from '~/utils/i18n.utils';
16+
17+
const i18n = useI18n('settings', 'images');
18+
19+
const { enabledTabs, imageType, imageFormat } = useExtensionSettingsStoreRefs();
20+
const { setImageType, setImageFormat } = useExtensionSettingsStore();
21+
22+
const typeOptions = computed<{ label: string; value: ImageType }[]>(() => [
23+
{ label: i18n('default', 'common', 'image', 'type'), value: 'default' },
24+
{ label: i18n('show', 'common', 'image', 'type'), value: 'show' },
25+
]);
26+
27+
const formatOptions = computed<{ label: string; value: ImageFormat }[]>(() => [
28+
{ label: i18n('poster', 'common', 'image', 'format'), value: 'poster' },
29+
{ label: i18n('backdrop', 'common', 'image', 'format'), value: 'backdrop' },
30+
]);
31+
32+
const toggleImageType = (tab: Route, current?: ImageType) => {
33+
setImageType(tab, current === 'default' ? 'show' : 'default');
34+
};
35+
36+
const toggleImageFormat = (tab: Route, current?: ImageFormat) => {
37+
setImageFormat(tab, current === 'poster' ? 'backdrop' : 'poster');
38+
};
39+
40+
const container = ref();
41+
</script>
42+
43+
<template>
44+
<div ref="container" class="image-container">
45+
<NText class="description" tag="p">{{ i18n('image_format_type') }}</NText>
46+
<NFlex
47+
v-for="[tab] in enabledTabs"
48+
:key="tab"
49+
class="form-row"
50+
align="center"
51+
justify="space-between"
52+
>
53+
<NText class="form-header" tag="h3">{{ i18n(tab, 'route') }}</NText>
54+
<NFlex class="form-selects" align="center" justify="flex-end">
55+
<!-- Image type -->
56+
<SettingsFormItem class="form-item" vertical>
57+
<NSelect
58+
class="form-select"
59+
:value="imageType[tab]"
60+
:to="container"
61+
:options="typeOptions"
62+
@update:value="toggleImageType(tab, imageType[tab])"
63+
/>
64+
</SettingsFormItem>
65+
66+
<!-- Image format -->
67+
<SettingsFormItem class="form-item" vertical>
68+
<NSelect
69+
class="form-select"
70+
:value="imageFormat[tab]"
71+
:to="container"
72+
:options="formatOptions"
73+
@update:value="toggleImageFormat(tab, imageFormat[tab])"
74+
/>
75+
</SettingsFormItem>
76+
</NFlex>
77+
</NFlex>
78+
</div>
79+
</template>
80+
81+
<style lang="scss" scoped>
82+
.image-container {
83+
display: flex;
84+
flex-direction: column;
85+
gap: 1.5rem;
86+
87+
.form-row {
88+
padding: 0.25rem 1rem;
89+
background: var(--bg-black-soft);
90+
border: 1px solid var(--white-10);
91+
border-radius: 0.5rem;
92+
transition:
93+
background 0.3s var(--n-bezier),
94+
border 0.3s var(--n-bezier);
95+
96+
&:active,
97+
&:focus-within,
98+
&:hover {
99+
border-color: var(--white-15);
100+
}
101+
}
102+
103+
.form-selects {
104+
flex: 1 1 70%;
105+
}
106+
107+
.form-select,
108+
.form-item {
109+
flex: 0 0 7rem;
110+
padding: 0 0.25rem;
111+
}
112+
113+
.form-header {
114+
flex: 0 1 20%;
115+
}
116+
117+
.description {
118+
white-space: pre-line;
119+
}
120+
}
121+
</style>

src/i18n/en/common/image.json

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"common__image__type__default": {
3+
"message": "Default",
4+
"description": "Default image type"
5+
},
6+
"common__image__type__show": {
7+
"message": "Show",
8+
"description": "Show image type"
9+
},
10+
"common__image__format__poster": {
11+
"message": "Poster",
12+
"description": "Poster image format"
13+
},
14+
"common__image__format__backdrop": {
15+
"message": "Backdrop",
16+
"description": "Backdrop image format"
17+
}
18+
}
+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"settings__images__image_format_type": {
3+
"message": "Enforce show image for season or episodes when applicable, display backdrop or poster image when available.",
4+
"description": "Image format type"
5+
}
6+
}

src/i18n/en/settings/settings-menu.json

+4
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@
2323
"message": "Links",
2424
"description": "Links menu item"
2525
},
26+
"settings__menu__images": {
27+
"message": "Images",
28+
"description": "Images menu item"
29+
},
2630
"settings__menu__menus": {
2731
"message": "Menus",
2832
"description": "Context menus menu item"

src/stores/settings/extension.store.ts

+18-16
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ const DefaultRoutes: RouteDictionary = {
4242
[Route.Search]: true,
4343
};
4444

45-
type ImageTypeDictionary = Partial<Record<Route, PosterItem['type'] | 'default'>>;
45+
export type ImageType = PosterItem['type'] | 'default';
46+
type ImageTypeDictionary = Partial<Record<Route, ImageType>>;
4647

4748
const DefaultImageTypes: ImageTypeDictionary = {
4849
[Route.Progress]: 'default',
@@ -53,7 +54,8 @@ const DefaultImageTypes: ImageTypeDictionary = {
5354
[Route.Search]: 'default',
5455
};
5556

56-
type ImageFormatDictionary = Partial<Record<Route, 'backdrop' | 'poster'>>;
57+
export type ImageFormat = 'backdrop' | 'poster';
58+
type ImageFormatDictionary = Partial<Record<Route, ImageFormat>>;
5759
const DefaultImageFormats: ImageFormatDictionary = {
5860
[Route.Progress]: 'backdrop',
5961
[Route.Calendar]: 'backdrop',
@@ -205,6 +207,16 @@ export const useExtensionSettingsStore = defineStore(ExtensionSettingsConstants.
205207
backdrop: imageFormat[route] === 'backdrop',
206208
}));
207209

210+
const setImageType = (route: Route, type: PosterItem['type'] | 'default') => {
211+
imageType[route] = type;
212+
saveState().catch(err => Logger.error('Failed to save image type extension settings', { route, type, err }));
213+
};
214+
215+
const setImageFormat = (route: Route, format: 'backdrop' | 'poster') => {
216+
imageFormat[route] = format;
217+
saveState().catch(err => Logger.error('Failed to save image format extension settings', { route, format, err }));
218+
};
219+
208220
return {
209221
initExtensionSettingsStore,
210222
restoreDefaultTab,
@@ -306,20 +318,10 @@ export const useExtensionSettingsStore = defineStore(ExtensionSettingsConstants.
306318
},
307319
}),
308320
getImageSettings,
309-
imageType: computed({
310-
get: () => imageType,
311-
set: (value: ImageTypeDictionary) => {
312-
Object.assign(imageType, value);
313-
saveState().catch(err => Logger.error('Failed to save image type extension settings', { value, err }));
314-
},
315-
}),
316-
imageFormat: computed({
317-
get: () => imageFormat,
318-
set: (value: ImageFormatDictionary) => {
319-
Object.assign(imageFormat, value);
320-
saveState().catch(err => Logger.error('Failed to save image format extension settings', { value, err }));
321-
},
322-
}),
321+
setImageType,
322+
setImageFormat,
323+
imageType,
324+
imageFormat,
323325
};
324326
});
325327

0 commit comments

Comments
 (0)