1
1
<script lang="ts" setup>
2
- import { NTimeline , NTimelineItem , NVirtualList } from ' naive-ui' ;
2
+ import {
3
+ NEmpty ,
4
+ NFlex ,
5
+ NSkeleton ,
6
+ NTimeline ,
7
+ NTimelineItem ,
8
+ NVirtualList ,
9
+ } from ' naive-ui' ;
3
10
4
- import { onActivated , onMounted , ref , watch } from ' vue' ;
11
+ import { onActivated , onMounted , ref , Transition , watch } from ' vue' ;
5
12
6
13
import type { VirtualListInst } from ' naive-ui' ;
7
14
@@ -10,21 +17,30 @@ import type { TraktHistory } from '~/models/trakt/trakt-history.model';
10
17
import { useHistoryStore , useHistoryStoreRefs } from ' ~/stores/data/history.store' ;
11
18
import { useUserSettingsStoreRefs } from ' ~/stores/settings/user.store' ;
12
19
13
- const { filteredHistory : history, pagination } = useHistoryStoreRefs ();
20
+ const {
21
+ filteredHistory : history,
22
+ pagination,
23
+ loading,
24
+ pageSize,
25
+ belowThreshold,
26
+ } = useHistoryStoreRefs ();
14
27
const { fetchHistory } = useHistoryStore ();
15
28
16
29
const { user } = useUserSettingsStoreRefs ();
17
30
18
- const virtualList = ref <VirtualListInst >();
31
+ const virtualList = ref <VirtualListInst & typeof NVirtualList >();
19
32
20
33
const onScroll = async (e : Event ) => {
34
+ if (loading .value ) return ;
21
35
if (! e ?.target ) return ;
22
36
const { scrollTop, scrollHeight, clientHeight } = e .target as HTMLDivElement ;
23
37
if (! scrollTop || scrollHeight !== scrollTop + clientHeight ) return ;
24
38
if (pagination .value ?.page === pagination .value ?.pageCount ) return ;
25
39
26
40
const key = history .value [history .value .length - 1 ].id ;
27
- await fetchHistory ({ page: pagination .value ?.page ? pagination .value .page + 1 : 0 });
41
+ await fetchHistory ({
42
+ page: pagination .value ?.page ? pagination .value .page + 1 : 0 ,
43
+ });
28
44
virtualList .value ?.scrollTo ({ key , debounce: true });
29
45
};
30
46
@@ -42,50 +58,118 @@ onActivated(() => {
42
58
console .info (' History activated' );
43
59
});
44
60
61
+ /**
62
+ * This is a workaround for the onUpdated lifecycle hook not triggering when wrapped in transition.
63
+ */
64
+ const onUpdated = () => {
65
+ const { scrollHeight, clientHeight } = virtualList .value ?.$el ?.firstElementChild ?? {};
66
+ if (scrollHeight !== clientHeight || ! belowThreshold .value || loading .value ) return ;
67
+
68
+ return fetchHistory ({
69
+ page: pagination .value ?.page ? pagination .value .page + 1 : 0 ,
70
+ });
71
+ };
72
+
45
73
const getTitle = (media : TraktHistory ) => {
46
74
if (' movie' in media ) return media .movie .title ;
47
- return ` ${media .episode .season }x${media .episode .number .toString ().padStart (2 , ' 0' )} - ${
48
- media .episode .title
49
- } ` ;
75
+ const number = media .episode ?.number ?.toString ().padStart (2 , ' 0' );
76
+ return ` ${media .episode ?.season }x${number } - ${media ?.episode ?.title } ` ;
50
77
};
51
78
</script >
52
79
53
80
<template >
54
- <NVirtualList
55
- ref =" virtualList"
56
- class =" history-list"
57
- :item-size =" 80"
58
- :data-length =" history.length"
59
- :items =" history"
60
- :visible-items-tag =" NTimeline"
61
- :visible-items-tag-props =" { size: 'large' }"
62
- :padding-top =" 56"
63
- :padding-bottom =" 16"
64
- @scroll =" onScroll"
65
- >
66
- <template #default =" { item } " >
67
- <NTimelineItem
68
- :key =" item.id"
69
- :data-key =" item.id"
70
- :style =" {
71
- fontVariantNumeric: 'tabular-nums',
72
- margin: '0 1rem',
73
- }"
74
- type =" success"
75
- :title =" getTitle(item)"
76
- :content =" item.show?.title"
77
- :time =" new Date(item.watched_at).toLocaleString()"
78
- />
79
- </template >
80
- </NVirtualList >
81
+ <Transition name =" fade" mode =" out-in" >
82
+ <NVirtualList
83
+ v-if =" history.length || loading"
84
+ ref =" virtualList"
85
+ class =" history-list"
86
+ :item-size =" 80"
87
+ :data-length =" history.length"
88
+ :data-page-size =" pageSize"
89
+ :items =" history"
90
+ :visible-items-tag =" NTimeline"
91
+ :visible-items-tag-props =" { size: 'large' }"
92
+ :padding-top =" 56"
93
+ :padding-bottom =" 16"
94
+ @scroll =" onScroll"
95
+ @vue:updated =" onUpdated"
96
+ >
97
+ <template #default =" { item , index } " >
98
+ <template v-if =" item .id >= 0 " >
99
+ <NTimelineItem
100
+ :key =" item.id"
101
+ class =" timeline-item"
102
+ :data-key =" item.id"
103
+ :data-index =" index"
104
+ type =" success"
105
+ :title =" getTitle(item)"
106
+ :content =" item.show?.title"
107
+ :time =" new Date(item.watched_at).toLocaleString()"
108
+ />
109
+ </template >
110
+ <template v-else >
111
+ <NTimelineItem
112
+ :key =" item.id"
113
+ class =" timeline-item"
114
+ :data-key =" item.id"
115
+ :data-index =" index"
116
+ line-type =" dashed"
117
+ >
118
+ <template #default >
119
+ <NFlex vertical >
120
+ <NSkeleton text style =" width : 70% " />
121
+ <NSkeleton text style =" width : 60% " />
122
+ <NSkeleton text style =" width : 20% " />
123
+ </NFlex >
124
+ </template >
125
+ </NTimelineItem >
126
+ </template >
127
+ </template >
128
+ </NVirtualList >
129
+ <NEmpty v-else size =" large" :show-description =" false" >
130
+ <template #extra >
131
+ <span class =" empty" >No data found.</span >
132
+ <div v-if =" pagination?.page && pagination?.pageCount" >
133
+ <div class =" empty" >
134
+ Pages searched <span class =" page" > {{ pagination?.page }} </span > out of
135
+ <span class =" page" > {{ pagination?.pageCount }} </span >.
136
+ </div >
137
+ <template v-if =" pagination .page < pagination .pageCount " >
138
+ <div class =" empty" >Increase the page size to search more.</div >
139
+ <div class =" empty" >
140
+ Current page size is <span class =" page" > {{ pageSize }} </span >.
141
+ </div >
142
+ </template >
143
+ </div >
144
+ </template >
145
+ </NEmpty >
146
+ </Transition >
81
147
</template >
82
148
83
149
<style lang="scss" scoped>
84
150
@use ' ~/styles/layout' as layout ;
151
+ @use ' ~/styles/transition' as transition ;
152
+ @include transition .fade ;
85
153
86
154
.history-list {
87
- max- height : calc (100 dvh - 8px );
155
+ height : calc (100 dvh - 8px );
88
156
margin-top : - #{layout .$header-navbar-height } ;
89
157
margin-bottom : 8px ;
158
+
159
+ .timeline-item {
160
+ font-variant-numeric : tabular-nums ;
161
+ margin : 0 1rem ;
162
+ }
163
+ }
164
+
165
+ .empty {
166
+ margin-top : 0.5rem ;
167
+ color : var (--n-text-color );
168
+ transition : color 0.3s var (--n-bezier );
169
+
170
+ .page {
171
+ color : var (--primary-color-disabled );
172
+ font-weight : bold ;
173
+ }
90
174
}
91
175
</style >
0 commit comments