Skip to content

Commit 1ed14d3

Browse files
committed
feat(history): adds search and page size support to navbar
1 parent 51ef1e2 commit 1ed14d3

File tree

7 files changed

+266
-86
lines changed

7 files changed

+266
-86
lines changed

src/components/common/navbar/NavbarComponent.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ nav {
106106
}
107107
</style>
108108

109-
<style lang="scss">
109+
<style lang="scss" scoped>
110110
nav {
111111
.tabs {
112112
--n-bar-color: var(--trakt-red-dark) !important;

src/components/common/navbar/NavbarSettingsDopdown.vue

+6-18
Original file line numberDiff line numberDiff line change
@@ -140,12 +140,6 @@ const onSelect: DropdownProps['onSelect'] = async (key: string, { label }) => {
140140
placement="bottom"
141141
size="small"
142142
class="dropdown"
143-
:style="{
144-
'margin-top': '0.75rem',
145-
'text-align': 'left',
146-
'min-width': 'max(calc(100vw / 6), 8rem)',
147-
'max-width': '20rem',
148-
}"
149143
@select="onSelect"
150144
>
151145
<NFlex justify="space-around" align="center" :wrap="false">
@@ -181,17 +175,11 @@ const onSelect: DropdownProps['onSelect'] = async (key: string, { label }) => {
181175
</template>
182176

183177
<style lang="scss">
184-
@use '~/styles/mixin' as mixin;
185-
186-
.n-dropdown-menu {
187-
@include mixin.hover-background;
188-
189-
@media (prefers-color-scheme: light) {
190-
@include mixin.hover-background;
191-
}
192-
193-
@media (prefers-color-scheme: dark) {
194-
@include mixin.hover-background(rgb(0 0 0 / 80%), rgb(0 0 0 / 90%));
195-
}
178+
.dropdown {
179+
min-width: max(calc(100vw / 6), 8rem);
180+
max-width: 20rem;
181+
margin-top: 0.75rem;
182+
margin-right: -0.25rem;
183+
text-align: left;
196184
}
197185
</style>

src/components/container/ContainerComponent.ce.vue

+28
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ const root = ref<HTMLElement>();
5454

5555
<style lang="scss">
5656
@use '~/styles/base.scss';
57+
@use '~/styles/mixin' as mixin;
5758
5859
:host {
5960
display: flex;
@@ -87,4 +88,31 @@ const root = ref<HTMLElement>();
8788
#trakt-extension-root {
8889
height: 100%;
8990
}
91+
92+
.n-dropdown-menu,
93+
.n-date-panel,
94+
.n-virtual-list {
95+
@include mixin.hover-background;
96+
97+
@media (prefers-color-scheme: light) {
98+
@include mixin.hover-background;
99+
}
100+
101+
@media (prefers-color-scheme: dark) {
102+
@include mixin.hover-background(rgb(0 0 0 / 80%), rgb(0 0 0 / 90%));
103+
}
104+
}
105+
106+
.n-tooltip.n-tooltip {
107+
background: var(--bg-blur-bg);
108+
backdrop-filter: blur(var(--bg-blur));
109+
110+
&:hover {
111+
background: var(--bg-blur-bg-hover);
112+
}
113+
114+
@media (prefers-color-scheme: light) {
115+
color: var(--vt-c-black);
116+
}
117+
}
90118
</style>
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
<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';
310
4-
import { onActivated, onMounted, ref, watch } from 'vue';
11+
import { onActivated, onMounted, ref, Transition, watch } from 'vue';
512
613
import type { VirtualListInst } from 'naive-ui';
714
@@ -10,21 +17,30 @@ import type { TraktHistory } from '~/models/trakt/trakt-history.model';
1017
import { useHistoryStore, useHistoryStoreRefs } from '~/stores/data/history.store';
1118
import { useUserSettingsStoreRefs } from '~/stores/settings/user.store';
1219
13-
const { filteredHistory: history, pagination } = useHistoryStoreRefs();
20+
const {
21+
filteredHistory: history,
22+
pagination,
23+
loading,
24+
pageSize,
25+
belowThreshold,
26+
} = useHistoryStoreRefs();
1427
const { fetchHistory } = useHistoryStore();
1528
1629
const { user } = useUserSettingsStoreRefs();
1730
18-
const virtualList = ref<VirtualListInst>();
31+
const virtualList = ref<VirtualListInst & typeof NVirtualList>();
1932
2033
const onScroll = async (e: Event) => {
34+
if (loading.value) return;
2135
if (!e?.target) return;
2236
const { scrollTop, scrollHeight, clientHeight } = e.target as HTMLDivElement;
2337
if (!scrollTop || scrollHeight !== scrollTop + clientHeight) return;
2438
if (pagination.value?.page === pagination.value?.pageCount) return;
2539
2640
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+
});
2844
virtualList.value?.scrollTo({ key, debounce: true });
2945
};
3046
@@ -42,50 +58,118 @@ onActivated(() => {
4258
console.info('History activated');
4359
});
4460
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+
4573
const getTitle = (media: TraktHistory) => {
4674
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}`;
5077
};
5178
</script>
5279

5380
<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>
81147
</template>
82148

83149
<style lang="scss" scoped>
84150
@use '~/styles/layout' as layout;
151+
@use '~/styles/transition' as transition;
152+
@include transition.fade;
85153
86154
.history-list {
87-
max-height: calc(100dvh - 8px);
155+
height: calc(100dvh - 8px);
88156
margin-top: -#{layout.$header-navbar-height};
89157
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+
}
90174
}
91175
</style>

src/components/views/history/HistoryNavbar.vue

+35-16
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,26 @@
11
<script setup lang="ts">
2-
import { NDatePicker, NFlex, NIcon, NInput } from 'naive-ui';
2+
import { NDatePicker, NFlex, NIcon, NInput, NSelect, NTooltip } from 'naive-ui';
33
import { computed, defineProps, ref, watch } from 'vue';
44
5+
import type { SelectOption } from 'naive-ui';
6+
57
import IconLoop from '~/components/icons/IconLoop.vue';
68
import { useHistoryStore, useHistoryStoreRefs } from '~/stores/data/history.store';
79
import { debounce } from '~/utils/debounce.utils';
810
9-
const { searchHistory, historyEnd, historyStart } = useHistoryStoreRefs();
11+
const { searchHistory, historyEnd, historyStart, pageSize } = useHistoryStoreRefs();
1012
const { setHistoryRange } = useHistoryStore();
1113
1214
const debouncedSearch = ref(searchHistory.value);
1315
16+
const pageSizeOptions: SelectOption[] = [
17+
{ label: '50', value: 50 },
18+
{ label: '100', value: 100 },
19+
{ label: '200', value: 200 },
20+
{ label: '500', value: 500 },
21+
{ label: '1000', value: 1000 },
22+
];
23+
1424
defineProps({
1525
parentElement: HTMLElement,
1626
});
@@ -66,6 +76,23 @@ const onDateChange = debounce((values?: [number, number]) => {
6676
<NIcon :component="IconLoop" />
6777
</template>
6878
</NInput>
79+
<NTooltip
80+
:arrow="false"
81+
placement="bottom"
82+
:delay="500"
83+
trigger="hover"
84+
:to="parentElement"
85+
>
86+
<span> Page size </span>
87+
<template #trigger>
88+
<NSelect
89+
v-model:value="pageSize"
90+
class="empty select"
91+
:options="pageSizeOptions"
92+
:to="parentElement"
93+
/>
94+
</template>
95+
</NTooltip>
6996
</NFlex>
7097
</template>
7198

@@ -76,29 +103,21 @@ const onDateChange = debounce((values?: [number, number]) => {
76103
width: 100%;
77104
78105
.picker {
79-
flex: 0 1 48%;
106+
flex: 0 1 47%;
80107
}
81108
82109
.input {
83-
flex: 0 1 48%;
110+
flex: 0 1 calc(47% - 5rem);
111+
}
112+
113+
.select {
114+
flex: 0 1 5rem;
84115
}
85116
}
86117
</style>
87118

88119
<style lang="scss">
89-
@use '~/styles/mixin' as mixin;
90-
91120
.n-date-panel {
92-
@include mixin.hover-background;
93-
94-
@media (prefers-color-scheme: light) {
95-
@include mixin.hover-background;
96-
}
97-
98-
@media (prefers-color-scheme: dark) {
99-
@include mixin.hover-background(rgb(0 0 0 / 80%), rgb(0 0 0 / 90%));
100-
}
101-
102121
margin-top: 12px;
103122
margin-left: -16px;
104123
}

0 commit comments

Comments
 (0)