Skip to content

Commit 08f2705

Browse files
committed
feat(checkin): implement checkin button & progress state evict
1 parent d370168 commit 08f2705

14 files changed

+321
-134
lines changed

src/components/views/checkin/CheckinComponent.vue

+24-74
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
11
<script lang="ts" setup>
2-
import { elapsedTime } from '@dvcol/common-utils/common/date';
32
import { formatTime } from '@dvcol/common-utils/common/format';
4-
import { NButton, NIcon, NTooltip, useDialog } from 'naive-ui';
5-
import { computed, defineProps, onBeforeMount, type Ref, ref, watch } from 'vue';
3+
import { NButton, NIcon, NTooltip } from 'naive-ui';
4+
import { computed, defineProps } from 'vue';
65
76
import IconCancel from '~/components/icons/IconCancel.vue';
87
import IconMovie from '~/components/icons/IconMovie.vue';
98
import IconScreen from '~/components/icons/IconScreen.vue';
9+
import { useMovieStore } from '~/stores/data/movie.store';
10+
import { useShowStore } from '~/stores/data/show.store';
11+
import { useWatchingStore, useWatchingStoreRefs } from '~/stores/data/watching.store';
12+
import { useI18n } from '~/utils/i18n.utils';
1013
import {
1114
isWatchingMovie,
1215
isWatchingShow,
13-
useWatchingStore,
14-
useWatchingStoreRefs,
15-
} from '~/stores/data/watching.store';
16-
import { useI18n } from '~/utils/i18n.utils';
16+
useCancelWatching,
17+
useWatchingProgress,
18+
} from '~/utils/watching.utils';
1719
1820
defineProps({
1921
parentElement: {
@@ -51,38 +53,22 @@ const episode = computed(() => {
5153
)} ${watching.value.episode.number}`;
5254
});
5355
54-
const now = ref(new Date());
56+
const {
57+
elapsed: elapsedSeconds,
58+
duration: durationSeconds,
59+
progress,
60+
} = useWatchingProgress(watching);
5561
56-
const elapsedSeconds = computed(() => {
57-
if (!watching.value) return 0;
58-
if (!watching.value.started_at) return 0;
59-
return elapsedTime(now.value, new Date(watching.value.started_at));
60-
});
6162
const elapsed = computed(() => {
6263
if (!elapsedSeconds.value) return '';
6364
return formatTime(elapsedSeconds.value);
6465
});
6566
66-
const durationSeconds = computed(() => {
67-
if (!watching.value) return 0;
68-
if (!watching.value.started_at) return 0;
69-
if (!watching.value.expires_at) return 0;
70-
return elapsedTime(
71-
new Date(watching.value.expires_at),
72-
new Date(watching.value.started_at),
73-
);
74-
});
7567
const duration = computed(() => {
7668
if (!durationSeconds.value) return '';
7769
return formatTime(durationSeconds.value);
7870
});
7971
80-
const progress = computed(() => {
81-
if (!durationSeconds.value) return 0;
82-
if (!elapsedSeconds.value) return 0;
83-
return `${(elapsedSeconds.value / durationSeconds.value) * 100}%`;
84-
});
85-
8672
const type = computed(() => {
8773
if (!watching.value) return '';
8874
return i18n(watching.value.type);
@@ -99,52 +85,16 @@ const started = computed(() => {
9985
return new Date(watching.value.started_at).toLocaleString();
10086
});
10187
102-
const dialog = useDialog();
103-
const onCancel = () => {
104-
dialog.error({
105-
title: i18n(`dialog_cancel_${watching.value?.action ?? 'checkin'}`),
106-
content: i18n('dialog_cancel_content'),
107-
positiveText: i18n('yes', 'common', 'button'),
108-
negativeText: i18n('no', 'common', 'button'),
109-
bordered: false,
110-
style: {
111-
width: '20rem',
112-
background: 'var(--bg-black-80)',
113-
backdropFilter: 'var(--bg-blur-10)',
114-
whiteSpace: 'pre-line',
115-
},
116-
positiveButtonProps: {
117-
quaternary: true,
118-
type: 'success',
119-
ghost: false,
120-
bordered: false,
121-
},
122-
negativeButtonProps: {
123-
quaternary: true,
124-
type: 'error',
125-
ghost: false,
126-
bordered: false,
127-
},
128-
onPositiveClick: () => cancel(),
129-
});
130-
};
88+
const { onCancel: onCancelWatching } = useCancelWatching(cancel, watching.value?.action);
89+
const { clearShowWatchedProgress } = useShowStore();
90+
const { clearMovieWatchedProgress } = useMovieStore();
13191
132-
const interval: Ref<ReturnType<typeof setInterval> | undefined> = ref();
133-
onBeforeMount(() => {
134-
watch(
135-
isWatching,
136-
value => {
137-
if (value) {
138-
interval.value = setInterval(() => {
139-
now.value = new Date();
140-
}, 1000);
141-
} else if (interval.value) {
142-
clearInterval(interval.value);
143-
}
144-
},
145-
{ immediate: true },
146-
);
147-
});
92+
const onCancel = async () => {
93+
const cancelled = await onCancelWatching();
94+
if (!cancelled) return;
95+
clearShowWatchedProgress();
96+
clearMovieWatchedProgress();
97+
};
14898
</script>
14999

150100
<template>
@@ -191,7 +141,7 @@ onBeforeMount(() => {
191141
</NTooltip>
192142
</span>
193143
</div>
194-
<div class="background" :style="{ '--progress': progress }" />
144+
<div class="background" :style="{ '--progress': `${progress}%` }" />
195145
</div>
196146
</template>
197147

src/components/views/panel/MoviePanel.vue

+39-2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
PanelButtonsOption,
1414
type PanelButtonsOptions,
1515
} from '~/components/views/panel/use-panel-buttons';
16+
import { NotificationService } from '~/services/notification.service';
1617
import { ResolveExternalLinks } from '~/settings/external.links';
1718
import {
1819
DefaultListId,
@@ -23,9 +24,15 @@ import {
2324
useListStore,
2425
} from '~/stores/data/list.store';
2526
import { useMovieStore, useMovieStoreRefs } from '~/stores/data/movie.store';
27+
import { useWatchingStore, useWatchingStoreRefs } from '~/stores/data/watching.store';
2628
import { useExtensionSettingsStoreRefs } from '~/stores/settings/extension.store';
2729
import { useLinksStore } from '~/stores/settings/links.store';
2830
import { useI18n } from '~/utils/i18n.utils';
31+
import {
32+
isWatchingMovie,
33+
useCancelWatching,
34+
useWatchingProgress,
35+
} from '~/utils/watching.utils';
2936
3037
const props = defineProps({
3138
movieId: {
@@ -159,7 +166,7 @@ const onCollectionUpdate = async (
159166
160167
const _id = movie.value?.ids?.trakt;
161168
if (_id === undefined) return;
162-
changeMovieCollected(_id, value === PanelButtonsOption.Remove);
169+
return changeMovieCollected(_id, value === PanelButtonsOption.Remove);
163170
};
164171
165172
const onWatchedUpdate = async (
@@ -185,7 +192,7 @@ const onWatchedUpdate = async (
185192
186193
const _id = movie.value?.ids?.trakt;
187194
if (_id === undefined) return;
188-
changeMovieWatched(_id, value === PanelButtonsOption.Remove);
195+
return changeMovieWatched(_id, value === PanelButtonsOption.Remove);
189196
};
190197
191198
const i18n = useI18n('movie', 'panel');
@@ -204,6 +211,32 @@ const titleUrl = computed(() => {
204211
});
205212
});
206213
214+
const { watching, loading: checkinLoading } = useWatchingStoreRefs();
215+
const { progress } = useWatchingProgress(watching);
216+
const isWatching = computed(() => {
217+
if (!watching.value) return false;
218+
if (isWatchingMovie(watching.value))
219+
return watching.value.movie?.ids?.trakt.toString() === movieId.value;
220+
return false;
221+
});
222+
223+
const { cancel: cancelCheckin, checkin } = useWatchingStore();
224+
225+
const { onCancel } = useCancelWatching(cancelCheckin);
226+
const onCheckin = async (cancel: boolean) => {
227+
if (cancel) {
228+
const cancelled = await onCancel();
229+
if (!cancelled) return;
230+
} else if (!movie.value?.ids?.trakt) {
231+
return NotificationService.error(
232+
i18n('checkin_failed', 'watching'),
233+
new Error('No movie id'),
234+
);
235+
} else await checkin({ movie: { ids: movie.value.ids } });
236+
237+
await fetchMovieWatched(true);
238+
};
239+
207240
const { openTab } = useLinksStore();
208241
209242
onMounted(() => {
@@ -259,9 +292,13 @@ onMounted(() => {
259292
:active-loading="listLoading"
260293
:active-lists="activeLists"
261294
:has-release="!!releaseDate"
295+
:watching="isWatching"
296+
:watch-progress="progress"
297+
:watch-loading="checkinLoading"
262298
@on-list-update="onListUpdate"
263299
@on-collection-update="onCollectionUpdate"
264300
@on-watched-update="onWatchedUpdate"
301+
@on-checkin="onCheckin"
265302
/>
266303

267304
<MoviePanelOverview :movie="movie" />

src/components/views/panel/MoviePanelButtons.vue

+5-6
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ const props = defineProps({
6060
type: Number,
6161
required: false,
6262
},
63-
checkinLoading: {
63+
watchLoading: {
6464
type: Boolean,
6565
required: false,
6666
},
@@ -209,12 +209,11 @@ onMounted(() => fetchLists());
209209
<!-- Check-in -->
210210
<NFlex class="button-container checkin" justify="center" align="center">
211211
<PanelButtonProgress
212-
:filled="watching"
213-
:percentage="watchProgress"
214-
:loading="checkinLoading"
212+
:percentage="watching ? watchProgress : 0"
213+
:loading="watchLoading"
215214
@click="onCheckin"
216215
>
217-
{{ i18n('checkin', 'common', 'button') }}
216+
{{ i18n(watching ? 'cancel' : 'checkin', 'common', 'button') }}
218217
</PanelButtonProgress>
219218
</NFlex>
220219
</div>
@@ -224,7 +223,7 @@ onMounted(() => fetchLists());
224223
.panel-buttons {
225224
display: flex;
226225
flex-wrap: wrap;
227-
gap: 1.25rem 1.5rem;
226+
gap: 1rem;
228227
align-items: center;
229228
justify-content: center;
230229
width: 100%;

src/components/views/panel/PanelButtonProgress.vue

+40-14
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
<script lang="ts" setup>
2-
import { type ButtonProps, NButton, NIcon } from 'naive-ui';
2+
import { type ButtonProps, NButton, NIcon, NTooltip } from 'naive-ui';
33
44
import { computed, type PropType, toRefs } from 'vue';
55
66
import IconConfirmCircle from '~/components/icons/IconConfirmCircle.vue';
7+
import { useI18n } from '~/utils/i18n.utils';
8+
9+
const i18n = useI18n('common');
710
811
const props = defineProps({
912
filled: {
@@ -31,6 +34,11 @@ const progress = computed(() => {
3134
if (percentage?.value === undefined) return false;
3235
return percentage.value > 0 && percentage.value < 100;
3336
});
37+
38+
const percentageLabel = computed(() => {
39+
if (percentage?.value) return Math.round(percentage.value);
40+
return 0;
41+
});
3442
</script>
3543

3644
<template>
@@ -43,20 +51,32 @@ const progress = computed(() => {
4351
'--progress-color': `var(--color-${type}-dark)`,
4452
}"
4553
>
46-
<NButton
47-
class="button"
48-
:class="{ filled, progress }"
49-
round
50-
:secondary="!filled"
51-
:disabled="loading"
52-
:loading="loading"
53-
:type="type"
54-
>
55-
<template #icon>
56-
<NIcon :component="IconConfirmCircle" />
54+
<NTooltip class="progress-tooltip" :disabled="!progress" :delay="100">
55+
<slot name="tooltip">
56+
<div>
57+
<span class="metric">{{ percentageLabel }}</span>
58+
<span>%</span>
59+
<span>&nbsp;</span>
60+
<span>{{ i18n('watched', 'common', 'button') }}</span>
61+
</div>
62+
</slot>
63+
<template #trigger>
64+
<NButton
65+
class="button"
66+
:class="{ filled, progress }"
67+
round
68+
:secondary="!filled"
69+
:disabled="loading"
70+
:loading="loading"
71+
:type="type"
72+
>
73+
<template #icon>
74+
<NIcon :component="IconConfirmCircle" />
75+
</template>
76+
<span><slot /></span>
77+
</NButton>
5778
</template>
58-
<span><slot /></span>
59-
</NButton>
79+
</NTooltip>
6080
</div>
6181
</template>
6282

@@ -93,4 +113,10 @@ const progress = computed(() => {
93113
var(--n-color) 100%
94114
);
95115
}
116+
117+
.metric {
118+
color: var(--white);
119+
font-weight: bolder;
120+
font-variant-numeric: tabular-nums;
121+
}
96122
</style>

0 commit comments

Comments
 (0)