Skip to content

Commit b7da524

Browse files
committed
feat(rating): refactor and add loading & link handling
1 parent c296dca commit b7da524

11 files changed

+577
-254
lines changed

src/components/common/buttons/ButtonLinkExternal.vue

+7-2
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ defineProps({
2222
required: false,
2323
},
2424
icon: {
25-
type: Object as PropType<Component>,
25+
type: Object as PropType<Component | null>,
2626
required: false,
2727
default: IconExternalLinkRounded,
2828
},
@@ -51,7 +51,7 @@ const anchor = ref();
5151
v-bind="$attrs"
5252
>
5353
<slot />
54-
<template #icon>
54+
<template v-if="icon" #icon>
5555
<NIcon :component="icon" />
5656
</template>
5757
</NButton>
@@ -68,6 +68,11 @@ const anchor = ref();
6868
color: inherit;
6969
text-decoration: none;
7070
71+
.external-link {
72+
height: unset;
73+
min-height: var(--n-height);
74+
}
75+
7176
.external-link:not(.slotted) {
7277
width: 2.25rem;
7378
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<script lang="ts" setup>
2+
import { NNumberAnimation, NStatistic } from 'naive-ui';
3+
4+
defineProps({
5+
from: {
6+
type: Number,
7+
required: false,
8+
default: 0,
9+
},
10+
to: {
11+
type: Number,
12+
required: false,
13+
},
14+
duration: {
15+
type: Number,
16+
required: false,
17+
default: 1000,
18+
},
19+
precision: {
20+
type: Number,
21+
required: false,
22+
default: 0,
23+
},
24+
unit: {
25+
type: String,
26+
required: false,
27+
},
28+
disabled: {
29+
type: Boolean,
30+
required: false,
31+
default: false,
32+
},
33+
});
34+
</script>
35+
36+
<template>
37+
<NStatistic class="statistics" :class="{ disabled }" tabular-nums>
38+
<NNumberAnimation :from="from" :to="to" :duration="duration" :precision="precision" />
39+
<span v-if="unit" class="unit">{{ unit }}</span>
40+
</NStatistic>
41+
</template>
42+
43+
<style lang="scss" scoped>
44+
.statistics {
45+
--n-value-font-size: 1rem !important;
46+
47+
font-variant-numeric: tabular-nums;
48+
align-self: center;
49+
50+
.unit {
51+
padding-left: 0.125rem;
52+
color: var(--white-50);
53+
}
54+
55+
&.disabled {
56+
--n-value-text-color: var(--text-color-disabled) !important;
57+
}
58+
}
59+
</style>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
<script lang="ts" setup>
2+
import { NProgress, NSkeleton, NSpin } from 'naive-ui';
3+
4+
import { onDeactivated, onMounted, onUnmounted, ref, toRefs, watch } from 'vue';
5+
6+
import AnimatedNumber from './AnimatedNumber.vue';
7+
8+
import { wait } from '~/utils/promise.utils';
9+
10+
const props = defineProps({
11+
from: {
12+
type: Number,
13+
required: false,
14+
default: 0,
15+
},
16+
progress: {
17+
type: Number,
18+
required: true,
19+
},
20+
duration: {
21+
type: Number,
22+
required: false,
23+
default: 1000,
24+
},
25+
delay: {
26+
type: Number,
27+
required: false,
28+
default: 50,
29+
},
30+
precision: {
31+
type: Number,
32+
required: false,
33+
default: 0,
34+
},
35+
unit: {
36+
type: String,
37+
required: false,
38+
},
39+
loading: {
40+
type: Boolean,
41+
required: false,
42+
default: false,
43+
},
44+
});
45+
46+
const { progress, delay } = toRefs(props);
47+
48+
const _progress = ref(0);
49+
50+
onMounted(async () => {
51+
watch(
52+
progress,
53+
async val => {
54+
await wait(delay.value);
55+
_progress.value = val;
56+
},
57+
{ immediate: true },
58+
);
59+
});
60+
61+
onUnmounted(() => {
62+
_progress.value = 0;
63+
});
64+
</script>
65+
66+
<template>
67+
<NSpin v-if="loading" class="spin" size="large">
68+
<NProgress class="progress" type="circle">
69+
<NSkeleton class="skeleton" text round />
70+
</NProgress>
71+
</NSpin>
72+
<NProgress
73+
v-else
74+
class="progress custom-color"
75+
type="circle"
76+
:percentage="_progress"
77+
:style="{ '--duration': `${duration - delay}ms` }"
78+
>
79+
<AnimatedNumber
80+
:from="from"
81+
:to="_progress"
82+
:duration="duration"
83+
:precision="precision"
84+
:disabled="!_progress"
85+
:unit="unit"
86+
/>
87+
</NProgress>
88+
</template>
89+
90+
<style lang="scss" scoped>
91+
.spin,
92+
.progress {
93+
--progress-size: 3.7rem !important;
94+
}
95+
96+
.custom-color {
97+
--custom-color: var(--info-color) --n-fill-color: var(--custom-color) !important;
98+
}
99+
100+
.spin {
101+
--n-opacity-spinning: 1 !important;
102+
--n-size: var(--progress-size) !important;
103+
--n-color: var(--text-color-disabled) !important;
104+
105+
.skeleton {
106+
width: 2ch;
107+
}
108+
}
109+
110+
.progress {
111+
width: var(--progress-size);
112+
113+
:deep(path.n-progress-graph-circle-fill) {
114+
transition:
115+
opacity var(--duration) var(--n-bezier),
116+
stroke var(--duration) var(--n-bezier),
117+
stroke-dasharray var(--duration) var(--n-bezier) !important;
118+
}
119+
120+
:deep(.n-statistic .n-statistic-value) {
121+
margin: 0;
122+
}
123+
}
124+
125+
.statistics {
126+
--n-value-font-size: 1rem !important;
127+
128+
font-variant-numeric: tabular-nums;
129+
align-self: center;
130+
131+
.unit {
132+
padding-left: 0.125rem;
133+
color: var(--white-50);
134+
}
135+
136+
&.disabled {
137+
--n-value-text-color: var(--text-color-disabled) !important;
138+
}
139+
}
140+
</style>

src/components/views/panel/MoviePanel.vue

+28-11
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ import MoviePanelButtons from '~/components/views/panel/MoviePanelButtons.vue';
1010
import MoviePanelDetails from '~/components/views/panel/MoviePanelDetails.vue';
1111
import MoviePanelOverview from '~/components/views/panel/MoviePanelOverview.vue';
1212
import PanelPoster from '~/components/views/panel/PanelPoster.vue';
13-
14-
import PanelRatings from '~/components/views/panel/PanelRatings.vue';
13+
import PanelStatistics from '~/components/views/panel/PanelStatistics.vue';
1514
import {
1615
PanelButtonsOption,
1716
type PanelButtonsOptions,
@@ -50,6 +49,7 @@ const { movieId } = toRefs(props);
5049
5150
const {
5251
getMovie,
52+
getMovieLoading,
5353
fetchMovie,
5454
getMovieWatched,
5555
getMovieWatchedDate,
@@ -63,6 +63,11 @@ const {
6363
6464
const { loadingCollected, loadingWatched } = useMovieStoreRefs();
6565
66+
const movieLoading = computed(() => {
67+
if (!movieId?.value) return false;
68+
return getMovieLoading(movieId.value).value;
69+
});
70+
6671
const movie = computed(() => {
6772
if (!movieId?.value) return;
6873
return getMovie(movieId.value).value;
@@ -251,13 +256,24 @@ const onCheckin = async (cancel: boolean) => {
251256
252257
const { openTab } = useLinksStore();
253258
254-
const { loadRatings, getRatings } = useRatingsStore();
259+
const { loadRatings, getRatings, getLoading } = useRatingsStore();
260+
261+
const scoreLoading = computed(() => getLoading(TraktRatingType.Movies));
255262
256263
const score = computed(() => {
257264
if (!movieId.value) return;
258265
return getRatings(TraktRatingType.Movies, movieId.value);
259266
});
260267
268+
const ratingUrl = computed(() => {
269+
if (!movie.value?.ids?.slug) return;
270+
return ResolveExternalLinks.trakt.item({
271+
type: 'movies',
272+
slug: movie.value.ids.slug,
273+
suffix: '/stats',
274+
});
275+
});
276+
261277
onMounted(() => {
262278
watch(
263279
movieId,
@@ -301,11 +317,16 @@ onMounted(() => {
301317
round
302318
/>
303319

304-
<NFlex class="rating-row" justify="space-around">
305-
<PanelRatings v-if="enableRatings" :votes="movie?.votes" :rating="movie?.rating" />
320+
<PanelStatistics
321+
:rating="movie?.rating"
322+
:votes="movie?.votes"
323+
:score="score?.rating"
324+
:loading-score="scoreLoading"
325+
:loading-rating="movieLoading"
326+
:url="ratingUrl"
327+
>
306328
<PanelPoster :tmdb="movie?.ids.tmdb" mode="movie" />
307-
<PanelRatings v-if="enableRatings" :score="score?.rating" mode="score" />
308-
</NFlex>
329+
</PanelStatistics>
309330

310331
<MoviePanelDetails
311332
:movie="movie"
@@ -335,10 +356,6 @@ onMounted(() => {
335356
</template>
336357

337358
<style lang="scss" scoped>
338-
.rating-row {
339-
width: 100%;
340-
}
341-
342359
.show-title-skeleton {
343360
height: 1.5rem;
344361
margin-top: 0.625rem;

0 commit comments

Comments
 (0)