Skip to content

Commit 9a788b0

Browse files
committed
feat(list): implement list buttons in all lists
1 parent 644dc61 commit 9a788b0

21 files changed

+472
-147
lines changed

src/components/common/list/ListButtonCheckin.vue

+12-5
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import IconConfirmCircle from '~/components/icons/IconConfirmCircle.vue';
99
import { isEpisodeOrMovie, type ListScrollItem } from '~/models/list-scroll.model';
1010
import { Logger } from '~/services/logger.service';
1111
import { NotificationService } from '~/services/notification.service';
12-
import { useCancelWatching } from '~/stores/composable/use-watching';
12+
import { type CheckinQuery, useCancelWatching } from '~/stores/composable/use-watching';
1313
import { useWatchingStore, useWatchingStoreRefs } from '~/stores/data/watching.store';
1414
import { useI18n } from '~/utils/i18n.utils';
1515
@@ -20,7 +20,11 @@ const { disabled, item } = defineProps<{
2020
iconProps?: IconProps;
2121
}>();
2222
23-
const i18n = useI18n('list', 'checkin');
23+
const emit = defineEmits<{
24+
(e: 'onClick', payload: { query: CheckinQuery; request: Promise<unknown> }): void;
25+
}>();
26+
27+
const i18n = useI18n('list', 'button', 'checkin');
2428
2529
const { isWatchingListItem } = useWatchingStore();
2630
const watching = computed(() => {
@@ -41,8 +45,11 @@ const onClick = async () => {
4145
};
4246
4347
try {
44-
if (watching.value) await cancel(query);
45-
else await checkin(query);
48+
let request: Promise<unknown>;
49+
if (watching.value) request = cancel(query);
50+
else request = checkin(query);
51+
emit('onClick', { query, request });
52+
await request;
4653
} catch (error) {
4754
Logger.error('Failed to checkin', { query, error });
4855
NotificationService.error(i18n('checkin_failed', 'watching'), error);
@@ -54,7 +61,7 @@ const { loading } = useWatchingStoreRefs();
5461

5562
<template>
5663
<ListButton
57-
:disabled="loading || disabled"
64+
:disabled="disabled"
5865
:loading="loading"
5966
:colored="watching"
6067
:button-props="{ type: 'error', ...buttonProps }"
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script setup lang="ts">
2-
import { computed, type PropType, toRefs } from 'vue';
2+
import { computed, type PropType, toRefs, watch } from 'vue';
33
44
import type { ButtonProps, IconProps } from 'naive-ui';
55
@@ -9,15 +9,20 @@ import IconRestore from '~/components/icons/IconRestore.vue';
99
import { type ListScrollItem } from '~/models/list-scroll.model';
1010
import { isListItemType, ListType } from '~/models/list.model';
1111
import { useItemCollected } from '~/stores/composable/use-item-played';
12-
import { useWatchedUpdates } from '~/stores/composable/use-list-update';
12+
import {
13+
type AddOrRemoveQuery,
14+
useWatchedUpdates,
15+
} from '~/stores/composable/use-list-update';
1316
import { useListStore } from '~/stores/data/list.store';
17+
import { useMovieStoreRefs } from '~/stores/data/movie.store';
18+
import { useShowStore } from '~/stores/data/show.store';
1419
import {
1520
QuickActionDate,
1621
type QuickActionDates,
1722
} from '~/stores/settings/extension.store';
1823
import { useI18n } from '~/utils/i18n.utils';
1924
20-
const i18n = useI18n('list', 'watched');
25+
const i18n = useI18n('list', 'button', 'collected');
2126
2227
const props = defineProps({
2328
item: {
@@ -32,6 +37,10 @@ const props = defineProps({
3237
type: Boolean,
3338
required: false,
3439
},
40+
disableFetch: {
41+
type: Boolean,
42+
required: false,
43+
},
3544
buttonProps: {
3645
type: Object as PropType<ButtonProps>,
3746
required: false,
@@ -42,47 +51,76 @@ const props = defineProps({
4251
},
4352
});
4453
45-
const { disabled, item, dateType } = toRefs(props);
54+
const emit = defineEmits<{
55+
(e: 'onClick', payload: { query: AddOrRemoveQuery; request: Promise<unknown> }): void;
56+
}>();
57+
58+
const { disabled, disableFetch, item, dateType } = toRefs(props);
4659
4760
const { collected, date: dateCollected } = useItemCollected(item);
4861
4962
const { isItemListLoading } = useListStore();
5063
51-
const isLoading = computed(() => {
52-
if (!item.value?.id) return;
64+
const isListLoading = computed(() => {
5365
if (!isListItemType(item.value.type)) return;
66+
const itemId = item.value.meta?.ids?.[item.value.type]?.trakt;
67+
if (!itemId) return false;
5468
return isItemListLoading({
5569
listType: ListType.Collection,
5670
itemType: item.value.type,
57-
itemId: item.value?.id,
58-
}).value;
71+
itemId,
72+
});
5973
});
6074
61-
const { addOrRemoveCollected } = useWatchedUpdates();
75+
const { loadingCollected } = useMovieStoreRefs();
76+
const { getShowCollectionLoading } = useShowStore();
6277
63-
const onClick = () => {
78+
const isLoading = computed(() => {
79+
if (isListLoading.value) return true;
80+
if (item.value.type === 'movie') return loadingCollected.value;
81+
if (!item.value?.meta?.ids?.show?.trakt) return false;
82+
return getShowCollectionLoading(item.value?.meta?.ids?.show?.trakt);
83+
});
84+
85+
const { addOrRemoveCollected, fetchCollection } = useWatchedUpdates();
86+
87+
const getQuery = (): AddOrRemoveQuery | undefined => {
6488
if (!isListItemType(item.value.type)) return;
6589
const trakt = item.value?.meta?.ids?.[item.value.type]?.trakt;
6690
if (!trakt) return;
6791
68-
let date: Date | string | undefined;
69-
if (dateType?.value === QuickActionDate.Release) {
70-
date = item.value?.meta?.released[item.value.type];
71-
}
72-
73-
return addOrRemoveCollected({
92+
return {
7493
itemIds: { trakt },
7594
itemType: item.value.type,
7695
remove: !!collected.value,
7796
showId: item.value?.meta?.ids?.show?.trakt,
78-
date,
79-
});
97+
};
8098
};
99+
100+
const onClick = () => {
101+
if (!isListItemType(item.value.type)) return;
102+
const query = getQuery();
103+
if (!query) return;
104+
105+
if (dateType?.value === QuickActionDate.Release) {
106+
query.date = item.value?.meta?.released?.[item.value.type];
107+
}
108+
109+
const request = addOrRemoveCollected(query);
110+
emit('onClick', { query, request });
111+
};
112+
113+
watch(disabled, () => {
114+
if (disabled.value || disableFetch.value) return;
115+
const query = getQuery();
116+
if (!query) return;
117+
return fetchCollection(query);
118+
});
81119
</script>
82120

83121
<template>
84122
<ListButton
85-
:disabled="isLoading || disabled"
123+
:disabled="disabled"
86124
:loading="isLoading"
87125
:colored="!!collected"
88126
:button-props="{ type: 'info', ...buttonProps }"
@@ -93,6 +131,6 @@ const onClick = () => {
93131
"
94132
@on-click="onClick"
95133
>
96-
<span>{{ i18n(collected ? 'remove' : 'collected', 'common', 'button') }}</span>
134+
<span>{{ i18n(collected ? 'remove' : 'collect', 'common', 'button') }}</span>
97135
</ListButton>
98136
</template>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
<script setup lang="ts">
2+
import { computed, type PropType, toRefs, watch } from 'vue';
3+
4+
import type { ButtonProps, IconProps } from 'naive-ui';
5+
6+
import ListButton from '~/components/common/list/ListButton.vue';
7+
import IconList from '~/components/icons/IconList.vue';
8+
import IconRestore from '~/components/icons/IconRestore.vue';
9+
import { type ListScrollItem } from '~/models/list-scroll.model';
10+
import {
11+
type AddOrRemoveListQuery,
12+
isListItemType,
13+
type ListEntity,
14+
ListType,
15+
} from '~/models/list.model';
16+
import { useListStore } from '~/stores/data/list.store';
17+
import { useI18n } from '~/utils/i18n.utils';
18+
19+
const i18n = useI18n('list', 'button', 'list');
20+
21+
const props = defineProps({
22+
item: {
23+
type: Object as PropType<ListScrollItem>,
24+
required: true,
25+
},
26+
list: {
27+
type: Object as PropType<ListEntity>,
28+
required: true,
29+
},
30+
disabled: {
31+
type: Boolean,
32+
required: false,
33+
},
34+
disableFetch: {
35+
type: Boolean,
36+
required: false,
37+
},
38+
buttonProps: {
39+
type: Object as PropType<ButtonProps>,
40+
required: false,
41+
},
42+
iconProps: {
43+
type: Object as PropType<IconProps>,
44+
required: false,
45+
},
46+
});
47+
48+
const emit = defineEmits<{
49+
(
50+
e: 'onClick',
51+
payload: { query: AddOrRemoveListQuery; request: Promise<unknown> },
52+
): void;
53+
}>();
54+
55+
const { disabled, disableFetch, item, list } = toRefs(props);
56+
57+
const { isItemListLoading, addToOrRemoveFromList, isItemInList, fetchListItems } =
58+
useListStore();
59+
60+
const isLoading = computed(() => {
61+
if (!item.value?.id) return;
62+
if (!isListItemType(item.value.type)) return;
63+
return isItemListLoading({
64+
listType: list.value.type,
65+
itemType: item.value.type,
66+
itemId: item.value?.id,
67+
});
68+
});
69+
70+
const checked = computed(() => {
71+
if (!isListItemType(item.value.type)) return false;
72+
const id = item.value.meta?.ids?.[item.value.type]?.trakt;
73+
if (!id) return false;
74+
return isItemInList(list.value.id, item.value.type, id);
75+
});
76+
77+
const label = computed(() => {
78+
if (checked.value) return i18n('remove', 'common', 'button');
79+
if (list.value.type === ListType.List) return list.value.name;
80+
return i18n(list.value.name, 'list');
81+
});
82+
83+
const onClick = () => {
84+
if (!isListItemType(item.value.type)) return;
85+
const trakt = item.value?.meta?.ids?.[item.value.type]?.trakt;
86+
if (!trakt) return;
87+
88+
const query = {
89+
list: list.value,
90+
itemIds: { trakt },
91+
itemType: item.value.type,
92+
remove: checked.value,
93+
};
94+
const request = addToOrRemoveFromList(query);
95+
emit('onClick', { query, request });
96+
};
97+
98+
watch(disabled, () => {
99+
if (disabled.value || disableFetch.value) return;
100+
if (!list.value) return;
101+
return fetchListItems({ list: list.value }, { parallel: false, updateState: false });
102+
});
103+
</script>
104+
105+
<template>
106+
<ListButton
107+
:disabled="disabled"
108+
:loading="isLoading"
109+
:colored="checked"
110+
:button-props="{ type: 'warning', ...buttonProps }"
111+
:icon-props="{ component: checked ? IconRestore : IconList, ...iconProps }"
112+
@on-click="onClick"
113+
>
114+
<span>{{ label }}</span>
115+
</ListButton>
116+
</template>

0 commit comments

Comments
 (0)