Skip to content

Commit 13e97d1

Browse files
committed
feat(refactor): makes list and empty generic re-usable components
1 parent 47ee7a5 commit 13e97d1

File tree

5 files changed

+145
-83
lines changed

5 files changed

+145
-83
lines changed

src/components/views/history/HistoryEmpty.vue src/components/common/list/ListEmpty.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { NEmpty } from 'naive-ui';
33
44
import { useI18n } from '~/utils';
55
6-
const i18n = useI18n('history', 'empty');
6+
const i18n = useI18n('list', 'empty');
77
88
defineProps({
99
page: {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import type { NVirtualList, VirtualListInst } from 'naive-ui';
2+
3+
import type { Ref } from 'vue';
4+
5+
export type VirtualListRef = VirtualListInst & typeof NVirtualList;
6+
7+
export type OnScroll = (listRef: Ref<VirtualListRef | undefined>) => void;
8+
export type OnUpdated = (listRef: Ref<VirtualListRef | undefined>) => void;
+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<script lang="ts" setup>
2+
import { NTimeline, NVirtualList } from 'naive-ui';
3+
4+
import { ref, toRefs } from 'vue';
5+
6+
import type { PropType, Ref, Transition } from 'vue';
7+
8+
import type { VirtualListRef } from '~/components/common/list/ListScroll.model';
9+
import type { TraktClientPagination } from '~/models/trakt/trakt-client.model';
10+
11+
import ListEmpty from '~/components/common/list/ListEmpty.vue';
12+
13+
const virtualList = ref<VirtualListRef>();
14+
15+
const props = defineProps({
16+
items: {
17+
type: Array as PropType<Record<string, unknown>[]>,
18+
required: true,
19+
},
20+
loading: {
21+
type: Boolean,
22+
required: true,
23+
},
24+
pagination: {
25+
type: Object as PropType<TraktClientPagination>,
26+
required: false,
27+
},
28+
pageSize: {
29+
type: Number,
30+
required: false,
31+
},
32+
});
33+
34+
const emits = defineEmits<{
35+
(e: 'onScroll', listRef: Ref<VirtualListRef | undefined>): void;
36+
(e: 'onUpdated', listRef: Ref<VirtualListRef | undefined>): void;
37+
}>();
38+
39+
const { items, loading, pagination } = toRefs(props);
40+
41+
const onScrollHandler = async (e: Event) => {
42+
if (loading.value) return;
43+
if (!e?.target) return;
44+
const { scrollTop, scrollHeight, clientHeight } = e.target as HTMLDivElement;
45+
if (!scrollTop || scrollHeight !== scrollTop + clientHeight) return;
46+
if (pagination?.value?.page === pagination?.value?.pageCount) return;
47+
48+
return emits('onScroll', virtualList);
49+
};
50+
51+
const onUpdatedHandler = () => {
52+
return emits('onUpdated', virtualList);
53+
};
54+
</script>
55+
56+
<template>
57+
<Transition name="fade" mode="out-in">
58+
<NVirtualList
59+
v-if="items.length || loading"
60+
ref="virtualList"
61+
class="list-scroll"
62+
:item-size="80"
63+
:data-length="items.length"
64+
:data-page-size="pageSize"
65+
:items="items"
66+
:visible-items-tag="NTimeline"
67+
:visible-items-tag-props="{ size: 'large' }"
68+
:padding-top="56"
69+
:padding-bottom="16"
70+
@scroll="onScrollHandler"
71+
@vue:updated="onUpdatedHandler"
72+
>
73+
<template #default="{ item, index }">
74+
<slot :item="item" :index="index" />
75+
</template>
76+
</NVirtualList>
77+
<ListEmpty
78+
v-else
79+
:page="pagination?.page"
80+
:page-count="pagination?.pageCount"
81+
:page-size="pageSize"
82+
/>
83+
</Transition>
84+
</template>
85+
86+
<style lang="scss" scoped>
87+
@use '~/styles/layout' as layout;
88+
@use '~/styles/transition' as transition;
89+
@include transition.fade;
90+
91+
.list-scroll {
92+
height: calc(100dvh - 8px);
93+
margin-top: -#{layout.$header-navbar-height};
94+
margin-bottom: 8px;
95+
}
96+
</style>
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
<script lang="ts" setup>
2-
import { NTimeline, NVirtualList } from 'naive-ui';
2+
import { onActivated, onDeactivated, onMounted, ref, watch } from 'vue';
33
4-
import { onActivated, onMounted, ref, Transition, watch } from 'vue';
4+
import type { WatchStopHandle } from 'vue';
55
6-
import type { VirtualListInst } from 'naive-ui';
7-
import type { TraktHistory } from '~/models/trakt/trakt-history.model';
6+
import type { OnScroll, OnUpdated } from '~/components/common/list/ListScroll.model';
87
9-
import HistoryEmpty from '~/components/views/history/HistoryEmpty.vue';
8+
import ListScroll from '~/components/common/list/ListScroll.vue';
109
import HistoryItem from '~/components/views/history/HistoryItem.vue';
1110
1211
import { useHistoryStore, useHistoryStoreRefs } from '~/stores/data/history.store';
@@ -23,93 +22,52 @@ const { fetchHistory } = useHistoryStore();
2322
2423
const { user } = useUserSettingsStoreRefs();
2524
26-
const virtualList = ref<VirtualListInst & typeof NVirtualList>();
25+
onMounted(() => {
26+
fetchHistory();
27+
});
2728
28-
const onScroll = async (e: Event) => {
29-
if (loading.value) return;
30-
if (!e?.target) return;
31-
const { scrollTop, scrollHeight, clientHeight } = e.target as HTMLDivElement;
32-
if (!scrollTop || scrollHeight !== scrollTop + clientHeight) return;
33-
if (pagination.value?.page === pagination.value?.pageCount) return;
29+
const watcher = ref<WatchStopHandle>();
30+
31+
onActivated(() => {
32+
watcher.value = watch(user, () => fetchHistory());
33+
});
3434
35+
onDeactivated(() => {
36+
watcher.value?.();
37+
});
38+
39+
const onScroll: OnScroll = async listRef => {
3540
const key = history.value[history.value.length - 1].id;
3641
await fetchHistory({
3742
page: pagination.value?.page ? pagination.value.page + 1 : 0,
3843
});
39-
virtualList.value?.scrollTo({ key, debounce: true });
44+
listRef.value?.scrollTo({ key, debounce: true });
4045
};
4146
42-
onMounted(() => {
43-
console.info('History mounted');
44-
fetchHistory();
45-
46-
watch(user, () => {
47-
console.info('User Change - re fetching');
48-
fetchHistory();
49-
});
50-
});
51-
52-
onActivated(() => {
53-
console.info('History activated');
54-
});
55-
5647
/**
5748
* This is a workaround for the onUpdated lifecycle hook not triggering when wrapped in transition.
5849
*/
59-
const onUpdated = () => {
60-
const { scrollHeight, clientHeight } = virtualList.value?.$el?.firstElementChild ?? {};
50+
const onUpdated: OnUpdated = listRef => {
51+
const { scrollHeight, clientHeight } = listRef.value?.$el?.firstElementChild ?? {};
6152
if (scrollHeight !== clientHeight || !belowThreshold.value || loading.value) return;
6253
6354
return fetchHistory({
6455
page: pagination.value?.page ? pagination.value.page + 1 : 0,
6556
});
6657
};
67-
68-
const getTitle = (media: TraktHistory) => {
69-
if ('movie' in media) return media.movie.title;
70-
const number = media.episode?.number?.toString().padStart(2, '0');
71-
return `${media.episode?.season}x${number} - ${media?.episode?.title}`;
72-
};
7358
</script>
7459

7560
<template>
76-
<Transition name="fade" mode="out-in">
77-
<NVirtualList
78-
v-if="history.length || loading"
79-
ref="virtualList"
80-
class="history-list"
81-
:item-size="80"
82-
:data-length="history.length"
83-
:data-page-size="pageSize"
84-
:items="history"
85-
:visible-items-tag="NTimeline"
86-
:visible-items-tag-props="{ size: 'large' }"
87-
:padding-top="56"
88-
:padding-bottom="16"
89-
@scroll="onScroll"
90-
@vue:updated="onUpdated"
91-
>
92-
<template #default="{ item, index }">
93-
<HistoryItem :item="item" :index="index" />
94-
</template>
95-
</NVirtualList>
96-
<HistoryEmpty
97-
v-else
98-
:page="pagination?.page"
99-
:page-count="pagination?.pageCount"
100-
:page-size="pageSize"
101-
/>
102-
</Transition>
61+
<ListScroll
62+
:items="history"
63+
:loading="loading"
64+
:pagination="pagination"
65+
:page-size="pageSize"
66+
@on-scroll="onScroll"
67+
@on-updated="onUpdated"
68+
>
69+
<template #default="{ item, index }">
70+
<HistoryItem :item="item" :index="index" />
71+
</template>
72+
</ListScroll>
10373
</template>
104-
105-
<style lang="scss" scoped>
106-
@use '~/styles/layout' as layout;
107-
@use '~/styles/transition' as transition;
108-
@include transition.fade;
109-
110-
.history-list {
111-
height: calc(100dvh - 8px);
112-
margin-top: -#{layout.$header-navbar-height};
113-
margin-bottom: 8px;
114-
}
115-
</style>

src/i18n/en/history/history.json src/i18n/en/list/list.json

+8-8
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
{
2-
"history__empty__no_data": {
2+
"list__empty__no_data": {
33
"message": "No data found.",
4-
"description": "Empty message when there is no data to display in the history view."
4+
"description": "Empty message when there is no data to display in the list."
55
},
6-
"history__empty__pages_line_1": {
6+
"list__empty__pages_line_1": {
77
"message": "Pages searched",
8-
"description": "First line of the history empty pagination message."
8+
"description": "First line of the list empty pagination message."
99
},
10-
"history__empty__pages_line_2": {
10+
"list__empty__pages_line_2": {
1111
"message": "out of",
12-
"description": "Second line of the history empty pagination message."
12+
"description": "Second line of the list empty pagination message."
1313
},
14-
"history__empty__page_size": {
14+
"list__empty__page_size": {
1515
"message": "Increase the page size to widen the search.",
1616
"description": "Prompt user to increase the page size to widen the search."
1717
},
18-
"history__empty__current_page_size": {
18+
"list__empty__current_page_size": {
1919
"message": "Current page size is",
2020
"description": "Current page size label."
2121
}

0 commit comments

Comments
 (0)