Skip to content

Commit ee3db29

Browse files
committed
feat(ratings): adds support for external ratings
1 parent 75f02d4 commit ee3db29

12 files changed

+368
-82
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
"@dvcol/common-utils": "^1.18.0",
5656
"@dvcol/simkl-http-client": "^1.1.8",
5757
"@dvcol/tmdb-http-client": "^1.3.10",
58-
"@dvcol/trakt-http-client": "^1.4.16",
58+
"@dvcol/trakt-http-client": "^1.5.2",
5959
"@dvcol/web-extension-utils": "^3.4.5",
6060
"@leeoniya/ufuzzy": "^1.0.14",
6161
"@vue/devtools": "^7.4.6",

pnpm-lock.yaml

+5-5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/components/common/panel/PanelMovieStatistics.vue

+69-24
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<script setup lang="ts">
2+
import { sentenceCase } from '@dvcol/common-utils/common/string';
23
import {
34
type TraktMovieExtended,
45
TraktRatingType,
@@ -11,7 +12,13 @@ import type { RatingItem } from '~/models/rating.model';
1112
1213
import PanelStatistics from '~/components/common/panel/PanelStatistics.vue';
1314
import PanelTrailers from '~/components/common/panel/PanelTrailers.vue';
14-
import { DataSource, getUrlFromSource } from '~/models/source.model';
15+
import {
16+
DataSource,
17+
getUrlFromSource,
18+
isKnownSource,
19+
normalizeSource,
20+
sortSources,
21+
} from '~/models/source.model';
1522
import { ResolveExternalLinks } from '~/settings/external.links';
1623
import { useRatingsStore } from '~/stores/data/ratings.store';
1724
import { useSimklStore } from '~/stores/data/simkl.store';
@@ -36,8 +43,15 @@ const props = defineProps({
3643
const { movie, movieLoading } = toRefs(props);
3744
3845
const { enableRatings } = useExtensionSettingsStoreRefs();
39-
const { loadRatings, getRatings, getLoading, addRating, removeRating } =
40-
useRatingsStore();
46+
const {
47+
loadRatings,
48+
getRatings,
49+
fetchRating,
50+
getRating,
51+
getLoading,
52+
addRating,
53+
removeRating,
54+
} = useRatingsStore();
4155
4256
const movieId = computed(() => movie?.value?.ids.trakt);
4357
const scoreLoading = computed(() => getLoading(TraktRatingType.Movies));
@@ -77,33 +91,63 @@ const trailers = computed(() => {
7791
}));
7892
});
7993
80-
const ratings = computed<RatingItem[]>(() => {
81-
const _ratings: RatingItem[] = [];
82-
_ratings.push({
83-
name: i18n('trakt', 'common', 'source', 'name'),
84-
icon: DataSource.Trakt,
85-
rating: {
86-
votes: movie?.value?.votes,
87-
rating: movie?.value?.rating,
88-
url: ratingUrl.value,
89-
loading: movieLoading.value,
90-
},
91-
});
92-
if (!simklMovie.value?.ratings) return _ratings;
93-
Object.entries(simklMovie.value.ratings).forEach(([key, value]) => {
94-
_ratings.push({
95-
name: i18n(key, 'common', 'source', 'name'),
94+
const extended = computed<[string, RatingItem][]>(() => {
95+
if (!movieId.value) return [];
96+
if (!enableRatings.value) return [];
97+
const _query = { id: movieId.value };
98+
const _ratings = getRating(TraktRatingType.Movies, _query);
99+
if (!_ratings) return [];
100+
return Object.entries(_ratings).map(([key, value]) => [
101+
key,
102+
{
103+
name: isKnownSource(key)
104+
? i18n(key, 'common', 'source', 'name')
105+
: sentenceCase(key.replaceAll('_', ' ')),
96106
icon: key,
97107
rating: {
98108
votes: value.votes,
99-
rating: value.rating,
100-
loading: simklMovieLoading.value,
101-
url: getUrlFromSource(key, simklMovie?.value?.ids, { type: 'movie' }),
109+
rating: normalizeSource(key, value.rating),
110+
loading: getLoading(TraktRatingType.Movies, _query),
111+
url: key === 'trakt' ? ratingUrl.value : undefined,
112+
},
113+
},
114+
]);
115+
});
116+
117+
const ratings = computed<RatingItem[]>(() => {
118+
const _ratings: Map<string, RatingItem> = new Map(extended.value);
119+
if (!_ratings.has(DataSource.Trakt)) {
120+
_ratings.set(DataSource.Trakt, {
121+
name: i18n('trakt', 'common', 'source', 'name'),
122+
icon: DataSource.Trakt,
123+
rating: {
124+
votes: movie?.value?.votes,
125+
rating: movie?.value?.rating,
126+
url: ratingUrl.value,
127+
loading: movieLoading.value,
102128
},
103129
});
104-
});
130+
}
131+
if (simklMovie.value?.ratings) {
132+
Object.entries(simklMovie.value.ratings).forEach(([key, value]) => {
133+
_ratings.set(key, {
134+
name: isKnownSource(key)
135+
? i18n(key, 'common', 'source', 'name')
136+
: sentenceCase(key.replaceAll('_', ' ')),
137+
icon: key,
138+
rating: {
139+
votes: value.votes,
140+
rating: normalizeSource(key, value.rating),
141+
loading: simklMovieLoading.value,
142+
url: getUrlFromSource(key, simklMovie?.value?.ids, { type: 'movie' }),
143+
},
144+
});
145+
});
146+
}
105147
106-
return _ratings;
148+
return Array.from(_ratings.values())
149+
.filter(r => r.name === DataSource.Trakt || r.rating.rating)
150+
.sort((a, b) => sortSources(a.name, b.name));
107151
});
108152
109153
const onScoreEdit = async (_score: TraktSyncRatingValue) => {
@@ -123,6 +167,7 @@ onMounted(() => {
123167
if (!movieId.value) return;
124168
if (!enableRatings.value) return;
125169
loadRatings(TraktRatingType.Movies, movieId.value.toString());
170+
fetchRating(TraktRatingType.Movies, { id: movieId.value });
126171
});
127172
});
128173
</script>

src/components/common/panel/PanelRatings.vue

+5-5
Original file line numberDiff line numberDiff line change
@@ -36,21 +36,21 @@ const getIcon = (icon: RatingItem['icon']): DropdownOption['icon'] => {
3636
3737
const options = computed<DropdownOption[]>(() => {
3838
if (!ratings?.value) return [];
39-
return ratings.value.map((rating, index) => ({
39+
return ratings.value.map(rating => ({
4040
label: rating.name,
41-
key: index,
41+
key: rating.name,
4242
icon: getIcon(rating.icon),
4343
}));
4444
});
4545
46-
const activeIndex = ref(0);
46+
const activeKey = ref();
4747
const onSelect = (key: number, option: DropdownOption) => {
48-
activeIndex.value = key;
48+
activeKey.value = key;
4949
};
5050
5151
const activeRating = computed(
5252
() =>
53-
ratings?.value?.at(activeIndex.value) ??
53+
ratings?.value?.find(r => r.name === activeKey.value) ??
5454
ratings?.value?.at(0) ?? { name: undefined, rating: {} },
5555
);
5656

src/components/common/panel/PanelShowStatistics.vue

+89-32
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<script setup lang="ts">
2+
import { sentenceCase } from '@dvcol/common-utils/common/string';
23
import {
34
type TraktEpisodeExtended,
45
TraktRatingType,
@@ -13,7 +14,13 @@ import type { RatingItem } from '~/models/rating.model';
1314
1415
import PanelStatistics from '~/components/common/panel/PanelStatistics.vue';
1516
import PanelTrailers from '~/components/common/panel/PanelTrailers.vue';
16-
import { DataSource } from '~/models/source.model';
17+
import {
18+
DataSource,
19+
getUrlFromSource,
20+
isKnownSource,
21+
normalizeSource,
22+
sortSources,
23+
} from '~/models/source.model';
1724
import { ResolveExternalLinks } from '~/settings/external.links';
1825
import { useRatingsStore } from '~/stores/data/ratings.store';
1926
@@ -49,8 +56,15 @@ const { mode, episode, season, show } = toRefs(props);
4956
5057
const { getShowLoading, getSeasonsLoading, getEpisodesLoading } = useShowStore();
5158
52-
const { loadRatings, getRatings, getLoading, addRating, removeRating } =
53-
useRatingsStore();
59+
const {
60+
loadRatings,
61+
getRatings,
62+
fetchRating,
63+
getRating,
64+
getLoading,
65+
addRating,
66+
removeRating,
67+
} = useRatingsStore();
5468
5569
const { enableRatings } = useExtensionSettingsStoreRefs();
5670
@@ -82,25 +96,28 @@ const scoreLoading = computed(() => {
8296
return getLoading(TraktRatingType.Shows);
8397
});
8498
99+
const rType = computed<TraktRatingTypes>(() => {
100+
if (mode.value === 'episode') return TraktRatingType.Episodes;
101+
if (mode.value === 'season') return TraktRatingType.Seasons;
102+
return TraktRatingType.Shows;
103+
});
104+
85105
const scoreIds = computed(() => {
86106
if (!enableRatings.value) return {};
87107
88108
let id: string | undefined;
89-
let type: TraktRatingTypes | undefined;
109+
const type: TraktRatingTypes | undefined = rType.value;
90110
91-
if (!showId.value) return { id, type };
111+
if (!showId.value) return { id };
92112
if (mode.value === 'show') {
93113
id = showId.value.toString();
94-
type = TraktRatingType.Shows;
95114
return { id, type };
96115
}
97116
if (mode.value === 'season') {
98-
type = TraktRatingType.Seasons;
99117
if (seasonId.value) id = `${showId.value}-${seasonId.value}`;
100118
return { id, type };
101119
}
102120
if (mode.value === 'episode') {
103-
type = TraktRatingType.Episodes;
104121
if (episodeId.value) id = `${showId.value}-${episodeId.value}`;
105122
return { id, type };
106123
}
@@ -176,35 +193,66 @@ const trailers = computed(() => {
176193
}));
177194
});
178195
179-
const ratings = computed<RatingItem[]>(() => {
180-
const _ratings: RatingItem[] = [];
181-
_ratings.push({
182-
name: i18n('trakt', 'common', 'source', 'name'),
183-
icon: DataSource.Trakt,
184-
rating: {
185-
votes: votes.value,
186-
rating: rating.value,
187-
url: ratingUrl.value,
188-
loading: ratingLoading.value,
189-
},
190-
});
191-
if (!simklShow.value?.ratings) return _ratings;
192-
if (mode.value !== 'show') return _ratings;
193-
Object.entries(simklShow.value.ratings).forEach(([key, value]) => {
194-
_ratings.push({
195-
name: i18n(key, 'common', 'source', 'name'),
196+
const extended = computed<[string, RatingItem][]>(() => {
197+
if (!showId.value) return [];
198+
if (!enableRatings.value) return [];
199+
const _query = { id: showId.value, season: seasonNb.value, episode: episodeNb.value };
200+
const _ratings = getRating(rType.value, _query);
201+
if (!_ratings) return [];
202+
return Object.entries(_ratings).map(([key, value]) => [
203+
key,
204+
{
205+
name: isKnownSource(key)
206+
? i18n(key, 'common', 'source', 'name')
207+
: sentenceCase(key.replaceAll('_', ' ')),
196208
icon: key,
197209
rating: {
198210
votes: value.votes,
199-
rating: value.rating,
200-
loading: simklShowLoading.value,
201-
url:
202-
key === 'mal' ? `https://myanimelist.net/anime/${simklShow.value}` : undefined,
211+
rating: normalizeSource(key, value.rating),
212+
loading: getLoading(rType.value, _query),
213+
url: key === 'trakt' ? ratingUrl.value : undefined,
203214
},
204-
});
205-
});
215+
},
216+
]);
217+
});
206218
207-
return _ratings;
219+
const ratings = computed<RatingItem[]>(() => {
220+
const _ratings: Map<string, RatingItem> = new Map(extended.value);
221+
if (!_ratings.has(DataSource.Trakt)) {
222+
_ratings.set(DataSource.Trakt, {
223+
name: i18n('trakt', 'common', 'source', 'name'),
224+
icon: DataSource.Trakt,
225+
rating: {
226+
votes: votes.value,
227+
rating: rating.value,
228+
url: ratingUrl.value,
229+
loading: ratingLoading.value,
230+
},
231+
});
232+
}
233+
if (mode.value === 'show' && simklShow.value?.ratings) {
234+
Object.entries(simklShow.value.ratings).forEach(([key, value]) => {
235+
_ratings.set(key, {
236+
name: isKnownSource(key)
237+
? i18n(key, 'common', 'source', 'name')
238+
: sentenceCase(key.replaceAll('_', ' ')),
239+
icon: key,
240+
rating: {
241+
votes: value.votes,
242+
rating: normalizeSource(key, value.rating),
243+
loading: simklShowLoading.value,
244+
url: getUrlFromSource(key, simklShow.value?.ids, {
245+
season: seasonNb.value,
246+
episode: episodeNb.value,
247+
type: mode.value,
248+
}),
249+
},
250+
});
251+
});
252+
}
253+
return Array.from(_ratings.values())
254+
.filter(r => r.name === DataSource.Trakt || r.rating.rating)
255+
.sort((a, b) => sortSources(a.name, b.name));
208256
});
209257
210258
const onScoreEdit = async (_score: TraktSyncRatingValue) => {
@@ -255,6 +303,15 @@ onMounted(() => {
255303
if (!id || !type) return;
256304
loadRatings(type, id);
257305
});
306+
watch([showId, seasonNb, episodeNb], () => {
307+
if (!showId.value) return;
308+
if (!enableRatings.value) return;
309+
fetchRating(rType.value, {
310+
id: showId.value,
311+
season: seasonNb.value,
312+
episode: episodeNb.value,
313+
});
314+
});
258315
});
259316
</script>
260317

src/components/icons/IconMeta.vue

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<template>
2+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 88 88">
3+
<circle
4+
fill="#001B36"
5+
stroke="#FC0"
6+
stroke-width="4.6"
7+
cx="44"
8+
cy="44"
9+
r="41.6"
10+
></circle>
11+
<path
12+
transform="translate(-10-961) matrix(1.2756629,-1.3487733,1.3685717,1.2634987,-267.04706,1066.0743)"
13+
fill="#FFF"
14+
d="m126.73438,92.087002 5.05859,0 0,2.832031 c 1.80989-2.200501 3.96483-3.30076 6.46484-3.300781 1.32811,2.1e-5 2.48045,.273458 3.45703,.820312 .97655,.546895 1.77733,1.373717 2.40235,2.480469 .91144-1.106752 1.89451-1.933574 2.94922-2.480469 1.05466-0.546854 2.18096-0.820291 3.3789-0.820312 1.52341,2.1e-5 2.81247,.309265 3.86719,.927734 1.05466,.618509 1.84242,1.526711 2.36328,2.724609 .37757,.885434 .56637,2.317724 .56641,4.296875 l 0,13.26172-5.48828,0 0-11.85547 c-3e-5-2.057277-0.18883-3.385401-0.56641-3.984375-0.50784-0.781233-1.28909-1.171858-2.34375-1.171875-0.76825,1.7e-5-1.49091,.234392-2.16797,.703125-0.6771,.468766-1.16538,1.155614-1.46484,2.060547-0.2995,.904961-0.44924,2.333998-0.44922,4.287108 l 0,9.96094-5.48828,0 0-11.36719 c-2e-5-2.018214-0.0977-3.320296-0.29297-3.906248-0.19533-0.585922-0.49806-1.02212-0.9082-1.308594-0.41017-0.286442-0.96681-0.429671-1.66993-0.429688-0.84636,1.7e-5-1.60808,.227882-2.28515,.683594-0.6771,.455745-1.16212,1.113297-1.45508,1.972656-0.29298,.859389-0.43946,2.28517-0.43945,4.27734 l 0,10.07813-5.48828,0z"
15+
/>
16+
</svg>
17+
</template>

0 commit comments

Comments
 (0)