Skip to content

Commit d109336

Browse files
committed
feat(panel): adds external links & studio support
1 parent fd8e3e3 commit d109336

12 files changed

+157
-98
lines changed

src/components/views/panel/MoviePanelDetails.vue

+12-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import TextField from '~/components/common/typography/TextField.vue';
1010
import PanelAlias from '~/components/views/panel/PanelAlias.vue';
1111
1212
import PanelLinks from '~/components/views/panel/PanelLinks.vue';
13+
import { useSimklStore } from '~/stores/data/simkl.store';
1314
import { useLinksStore } from '~/stores/settings/links.store';
1415
import { useI18n } from '~/utils/i18n.utils';
1516
@@ -66,9 +67,19 @@ const runtime = computed(() => {
6667
return `${movie.value.runtime} min`;
6768
});
6869
70+
const { getMovie } = useSimklStore();
71+
72+
const simklMovie = computed(() => {
73+
if (!movie?.value?.ids?.imdb) return;
74+
return getMovie(movie.value.ids.imdb).value;
75+
});
76+
6977
const genres = computed(() => {
7078
if (!movie?.value) return;
71-
return movie.value?.genres?.map(g => ({ label: capitalizeEachWord(g) })) ?? [];
79+
const _genres = new Set<string>();
80+
movie.value?.genres?.forEach(g => _genres.add(g.trim().toLowerCase()));
81+
simklMovie.value?.genres?.forEach(g => _genres.add(g.trim().toLowerCase()));
82+
return [..._genres.values()]?.map(g => ({ label: capitalizeEachWord(g) }));
7283
});
7384
7485
const year = computed(() => {

src/components/views/panel/PanelLinks.vue

+23-77
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,27 @@
11
<script lang="ts" setup>
22
import { computed, type PropType, toRefs } from 'vue';
33
4+
import type { SimklIdsExtended } from '@dvcol/simkl-http-client/models';
45
import type { TraktApiIds } from '@dvcol/trakt-http-client/models';
56
import type { TagLink } from '~/models/tag.model';
67
78
import TextField from '~/components/common/typography/TextField.vue';
89
import IconExternalLinkRounded from '~/components/icons/IconExternalLinkRounded.vue';
9-
import IconIMDb from '~/components/icons/IconIMDb.vue';
10-
import IconTMDb from '~/components/icons/IconTMDb.vue';
11-
import IconTVDb from '~/components/icons/IconTVDb.vue';
12-
import IconTrakt from '~/components/icons/IconTrakt.vue';
1310
14-
import { ResolveExternalLinks } from '~/settings/external.links';
11+
import {
12+
DataSource,
13+
getIconFromSource,
14+
getLabelKeyFromSource,
15+
getSortedDataSources,
16+
getUrlFromSource,
17+
isKnownSource,
18+
} from '~/models/source.model';
1519
import { resolveLinkUrl, useLinksStore } from '~/stores/settings/links.store';
1620
import { useI18n } from '~/utils/i18n.utils';
1721
1822
const props = defineProps({
1923
ids: {
20-
type: Object as PropType<Partial<TraktApiIds>>,
24+
type: Object as PropType<Partial<TraktApiIds & SimklIdsExtended>>,
2125
required: false,
2226
},
2327
mode: {
@@ -47,15 +51,6 @@ const { ids, mode, season, episode, alias, title } = toRefs(props);
4751
4852
const i18n = useI18n('panel', 'detail');
4953
50-
const labelKey = computed(() => {
51-
const label = ['show', 'episode', 'season'].includes(mode.value)
52-
? `open_${mode.value}_in`
53-
: 'open_in';
54-
return (source: string) => {
55-
return i18n({ key: label, substitutions: [source] }, 'common', 'tooltip');
56-
};
57-
});
58-
5954
const { getLinks } = useLinksStore();
6055
6156
const customLinksTemplate = getLinks(mode);
@@ -77,69 +72,20 @@ const customLinks = computed(() => {
7772
const links = computed(() => {
7873
if (!ids?.value) return;
7974
const _links: TagLink[] = [];
80-
if (ids.value.trakt) {
81-
_links.push({
82-
label: 'Trakt',
83-
title: labelKey.value('Trakt.tv'),
84-
url: ResolveExternalLinks.search({
85-
type: mode.value,
86-
source: 'trakt',
87-
id: ids.value.trakt,
88-
}),
89-
icon: IconTrakt,
90-
iconProps: {
91-
style: {
92-
'--trakt-icon-path': 'white',
93-
'--trakt-icon-circle': 'transparent',
94-
},
95-
},
96-
});
97-
}
98-
if (ids.value.imdb) {
99-
_links.push({
100-
label: 'IMDb',
101-
title: labelKey.value('IMDb.com'),
102-
url: ResolveExternalLinks.imdb(ids.value.imdb),
103-
icon: IconIMDb,
104-
iconProps: {
105-
style: {
106-
'--imdb-icon-background': 'white',
107-
'--imdb-icon-height': '575',
108-
},
109-
},
110-
});
111-
}
112-
if (ids.value.tmdb) {
113-
_links.push({
114-
label: 'TMDb',
115-
title: labelKey.value('TheMovieDb.org'),
116-
url: ResolveExternalLinks.tmdb({
117-
id: ids.value.tmdb,
118-
type: mode.value,
119-
season: season?.value,
120-
episode: episode?.value,
121-
}),
122-
icon: IconTMDb,
123-
iconProps: {
124-
style: {
125-
'--tmdb-icon-background': 'white',
126-
},
127-
},
128-
});
129-
}
130-
if (ids.value.tvdb) {
131-
_links.push({
132-
label: 'TVDb',
133-
title: labelKey.value('TheTVDB.com'),
134-
url: ResolveExternalLinks.tvdb(ids.value.tvdb, mode.value),
135-
icon: IconTVDb,
136-
iconProps: {
137-
style: {
138-
'--tvdb-icon-background': 'white',
139-
},
140-
},
75+
getSortedDataSources(ids.value)
76+
.filter(isKnownSource)
77+
.forEach(key => {
78+
_links[key === DataSource.Trakt ? 'unshift' : 'push']({
79+
label: i18n(key, 'common', 'source', 'name'),
80+
title: getLabelKeyFromSource(i18n, key, mode.value),
81+
url: getUrlFromSource(key, ids.value, {
82+
type: mode.value,
83+
season: season?.value,
84+
episode: episode?.value,
85+
}),
86+
icon: getIconFromSource(key),
87+
});
14188
});
142-
}
14389
if (customLinks.value) _links.push(...customLinks.value);
14490
return _links;
14591
});

src/components/views/panel/PanelRatings.vue

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
<script setup lang="ts">
22
import { NDropdown, NFlex, NIcon } from 'naive-ui';
3+
34
import { computed, h, type PropType, ref, toRefs } from 'vue';
45
56
import type { DropdownOption } from 'naive-ui';
67
78
import type { RatingItem } from '~/models/rating.model';
89
10+
import IconStarFilledHalf from '~/components/icons/IconStarFilledHalf.vue';
911
import PanelRating from '~/components/views/panel/PanelRating.vue';
1012
import { getIconFromSource } from '~/models/source.model';
1113
@@ -23,7 +25,8 @@ const getIcon = (icon: RatingItem['icon']): DropdownOption['icon'] => {
2325
return () =>
2426
h(NIcon, {
2527
style: { marginRight: '-0.25rem' },
26-
component: typeof icon === 'string' ? getIconFromSource(icon) : icon,
28+
component:
29+
typeof icon === 'string' ? getIconFromSource(icon, IconStarFilledHalf) : icon,
2730
});
2831
};
2932

src/components/views/panel/ShowPanelDetails.vue

+29-3
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import TextField from '~/components/common/typography/TextField.vue';
2020
import PanelAlias from '~/components/views/panel/PanelAlias.vue';
2121
2222
import PanelLinks from '~/components/views/panel/PanelLinks.vue';
23+
import { useSimklStore } from '~/stores/data/simkl.store';
2324
import { useLinksStore } from '~/stores/settings/links.store';
2425
import { useI18n } from '~/utils/i18n.utils';
2526
@@ -115,9 +116,25 @@ const collectionTime = computed(() => {
115116
return shortTime(collectionProgress.value.date);
116117
});
117118
119+
const { getShowOrAnime } = useSimklStore();
120+
121+
const simklShow = computed(() => {
122+
if (!show?.value?.ids?.imdb) return;
123+
return getShowOrAnime(show.value.ids.imdb).value;
124+
});
125+
118126
const genres = computed(() => {
119127
if (!show?.value) return;
120-
return show.value?.genres?.map(g => ({ label: capitalizeEachWord(g) }));
128+
const _genres = new Set<string>();
129+
show.value?.genres?.forEach(g => _genres.add(g.trim().toLowerCase()));
130+
simklShow?.value?.genres?.forEach(g => _genres.add(g.trim().toLowerCase()));
131+
return [..._genres.values()]?.map(g => ({ label: capitalizeEachWord(g) }));
132+
});
133+
134+
const studio = computed(() => {
135+
if (!simklShow?.value) return;
136+
if (!('studios' in simklShow.value)) return;
137+
return simklShow.value.studios?.map(s => s.name).join(', ') || '-';
121138
});
122139
123140
const year = computed(() => {
@@ -168,7 +185,7 @@ const ids = computed(() => {
168185
};
169186
}
170187
if (!show?.value) return;
171-
return show.value?.ids;
188+
return { ...simklShow.value?.ids, ...show.value?.ids };
172189
});
173190
174191
const { getAlias } = useLinksStore();
@@ -242,9 +259,18 @@ const title = computed(() =>
242259
<TextField
243260
:label="i18n('status')"
244261
:value="status"
245-
grow
262+
:grow="mode !== 'show'"
246263
:skeleton="{ width: '7.5rem' }"
247264
/>
265+
266+
<!-- Studio -->
267+
<TextField
268+
v-if="mode === 'show' && studio"
269+
:label="i18n('studio')"
270+
:value="studio"
271+
:skeleton="{ width: '3rem' }"
272+
grow
273+
/>
248274
</NFlex>
249275

250276
<NFlex class="row" size="large">

src/components/views/settings/SettingsLinks.vue

+14-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import IconConfirm from '~/components/icons/IconConfirm.vue';
2424
import IconPlus from '~/components/icons/IconPlus.vue';
2525
import IconRestore from '~/components/icons/IconRestore.vue';
2626
import SettingsFormItem from '~/components/views/settings/SettingsFormItem.vue';
27+
import { AllDataSources, DataSource } from '~/models/source.model';
28+
import { useSimklStoreRefs } from '~/stores/data/simkl.store';
2729
import {
2830
AllCustomLinkScopes,
2931
type CustomLink,
@@ -102,7 +104,18 @@ const getType = (link: CustomLink) => {
102104
return 'success';
103105
};
104106
105-
const keywords = ['trakt', 'imdb', 'tmdb', 'tvdb', 'season', 'episode', 'title', 'alias'];
107+
const { simklEnabled } = useSimklStoreRefs();
108+
const defaultKeywords = ['season', 'episode', 'title', 'alias'];
109+
const keywords = computed(() => {
110+
if (simklEnabled.value) return [...AllDataSources, ...defaultKeywords];
111+
return [
112+
DataSource.Trakt,
113+
DataSource.Imdb,
114+
DataSource.Imdb,
115+
DataSource.Tvdb,
116+
...defaultKeywords,
117+
];
118+
});
106119
107120
const formRef = ref<FormInst>();
108121
const containerRef = ref<HTMLDivElement>();

src/i18n/en/common/tooltip.json

+11-7
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,30 @@
77
"message": "Open in $1",
88
"description": "Label for a link that opens a page in a new tab"
99
},
10+
"common__tooltip__open_movie_in": {
11+
"message": "Open movie in $1",
12+
"description": "Label for a movie link that opens a page in a new tab"
13+
},
1014
"common__tooltip__open_show_in": {
1115
"message": "Open show in $1",
1216
"description": "Label for a show link that opens a page in a new tab"
1317
},
14-
"common__tooltip__open_episode_in": {
15-
"message": "Open episode in $1",
16-
"description": "Label for a episode link that opens a page in a new tab"
17-
},
1818
"common__tooltip__open_season_in": {
1919
"message": "Open season in $1",
2020
"description": "Label for a season link that opens a page in a new tab"
2121
},
22-
"common__tooltip__open_movie_in": {
23-
"message": "Open movie in $1",
24-
"description": "Label for a movie link that opens a page in a new tab"
22+
"common__tooltip__open_episode_in": {
23+
"message": "Open episode in $1",
24+
"description": "Label for a episode link that opens a page in a new tab"
2525
},
2626
"common__tooltip__open_person_in": {
2727
"message": "Open person in $1",
2828
"description": "Label for a person link that opens a page in a new tab"
2929
},
30+
"common__tooltip__open_anime_in": {
31+
"message": "Open anime in $1",
32+
"description": "Label for a anime link that opens a page in a new tab"
33+
},
3034
"common__tooltip__open_in_trakt": {
3135
"message": "Open in Trakt.tv",
3236
"description": "Label for opening a trakt.tv link in a new tab."

src/i18n/en/panel/panel-detail.json

+4
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@
5151
"message": "Status",
5252
"description": "Status label in the panel detail"
5353
},
54+
"panel__detail__Studio": {
55+
"message": "Studio",
56+
"description": "Studio label in the panel detail"
57+
},
5458
"panel__detail__runtime": {
5559
"message": "Runtime",
5660
"description": "Runtime label in the panel detail"

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

+16
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,22 @@
7171
"message": "The tvdb ID",
7272
"description": "Description for the tvdb keyword"
7373
},
74+
"settings__links__template_keywords_simkl": {
75+
"message": "simkl (string)",
76+
"description": "Label for the simkl keyword"
77+
},
78+
"settings__links__template_keywords_simkl_description": {
79+
"message": "The simkl ID",
80+
"description": "Description for the simkl keyword"
81+
},
82+
"settings__links__template_keywords_mal": {
83+
"message": "mal (string)",
84+
"description": "Label for the mal keyword"
85+
},
86+
"settings__links__template_keywords_mal_description": {
87+
"message": "The mal ID",
88+
"description": "Description for the mal keyword"
89+
},
7490
"settings__links__template_keywords_season": {
7591
"message": "season (number)",
7692
"description": "Label for the season keyword"

src/models/i18n.model.ts

+3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
import type { useI18nTranslate } from '~/utils/browser/browser-i18n.utils';
2+
13
export type Locale = Record<string, { message: string; descriptions?: string }>;
24
export type Locales = Record<string, Locale>;
35
export type LocalesFetch = Record<string, Promise<Locale>>;
46
export type I18nParameters = { key: string; substitutions: string[] };
7+
export type I18nFunction = ReturnType<typeof useI18nTranslate>;

0 commit comments

Comments
 (0)