Skip to content

Commit 09a0903

Browse files
committed
feat(history): adds history store and enable nabar filtering
1 parent 4d862b4 commit 09a0903

File tree

6 files changed

+170
-28
lines changed

6 files changed

+170
-28
lines changed

src/components/views/history/HistoryComponent.vue

+15-21
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,21 @@
11
<script lang="ts" setup>
22
import { NTimeline, NTimelineItem, NVirtualList } from 'naive-ui';
3-
import { onActivated, onMounted, ref } from 'vue';
3+
4+
import { onActivated, onMounted, ref, watch } from 'vue';
45
56
import type { VirtualListInst } from 'naive-ui';
67
7-
import type { TraktClientPagination } from '~/models/trakt/trakt-client.model';
88
import type { TraktHistory } from '~/models/trakt/trakt-history.model';
99
10-
import { TraktService } from '~/services/trakt.service';
10+
import { useHistoryStore, useHistoryStoreRefs } from '~/stores/data/history.store';
11+
import { useUserSettingsStoreRefs } from '~/stores/settings/user.store';
1112
12-
const history = ref<TraktHistory[]>([]);
13-
const pagination = ref<TraktClientPagination>();
14-
const virtualList = ref<VirtualListInst>();
13+
const { filteredHistory: history, pagination } = useHistoryStoreRefs();
14+
const { fetchHistory } = useHistoryStore();
1515
16-
const fetchData = async (
17-
page = pagination.value?.page ? pagination.value.page + 1 : 0,
18-
) => {
19-
const response = await TraktService.traktClient.sync.history.get.cached({
20-
pagination: {
21-
page,
22-
limit: 30,
23-
},
24-
});
16+
const { user } = useUserSettingsStoreRefs();
2517
26-
const data = await response.json();
27-
pagination.value = response.pagination;
28-
history.value = [...history.value, ...data];
29-
};
18+
const virtualList = ref<VirtualListInst>();
3019
3120
const onScroll = async (e: Event) => {
3221
if (!e?.target) return;
@@ -35,13 +24,18 @@ const onScroll = async (e: Event) => {
3524
if (pagination.value?.page === pagination.value?.pageCount) return;
3625
3726
const key = history.value[history.value.length - 1].id;
38-
await fetchData();
27+
await fetchHistory({ page: pagination.value?.page ? pagination.value.page + 1 : 0 });
3928
virtualList.value?.scrollTo({ key, debounce: true });
4029
};
4130
4231
onMounted(() => {
4332
console.info('History mounted');
44-
fetchData();
33+
fetchHistory();
34+
35+
watch(user, () => {
36+
console.info('User Change - re fetching');
37+
fetchHistory();
38+
});
4539
});
4640
4741
onActivated(() => {

src/components/views/history/HistoryNavbar.vue

+42-4
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,43 @@
11
<script setup lang="ts">
22
import { NDatePicker, NFlex, NIcon, NInput } from 'naive-ui';
3-
import { defineProps } from 'vue';
3+
import { computed, defineProps, ref, watch } from 'vue';
44
55
import IconLoop from '~/components/icons/IconLoop.vue';
6+
import { useHistoryStore, useHistoryStoreRefs } from '~/stores/data/history.store';
7+
import { debounce } from '~/utils/debounce.utils';
8+
9+
const { searchHistory, historyEnd, historyStart } = useHistoryStoreRefs();
10+
const { setHistoryRange } = useHistoryStore();
11+
12+
const debouncedSearch = ref(searchHistory.value);
613
714
defineProps({
815
parentElement: HTMLElement,
916
});
17+
18+
watch(searchHistory, () => {
19+
if (searchHistory.value !== debouncedSearch.value) {
20+
debouncedSearch.value = searchHistory.value;
21+
}
22+
});
23+
24+
watch(
25+
debouncedSearch,
26+
debounce(() => {
27+
searchHistory.value = debouncedSearch.value;
28+
}, 350),
29+
);
30+
31+
const pickerValues = computed<[number, number] | null>(() => {
32+
if (!historyStart.value || !historyEnd.value) return null;
33+
return [historyStart.value.getTime(), historyEnd.value.getTime()];
34+
});
35+
36+
const onDateChange = debounce((values?: [number, number]) => {
37+
if (!values?.length) return setHistoryRange();
38+
const [start, end] = values;
39+
setHistoryRange({ start: new Date(start), end: new Date(end) });
40+
}, 350);
1041
</script>
1142

1243
<template>
@@ -20,10 +51,17 @@ defineProps({
2051
update-value-on-close
2152
close-on-select
2253
clearable
23-
:on-clear="() => console.log('clear')"
24-
:on-confirm="value => console.log('confirm', value)"
54+
:value="pickerValues"
55+
:on-clear="onDateChange"
56+
:on-confirm="onDateChange"
2557
/>
26-
<NInput class="input" placeholder="Search" autosize clearable>
58+
<NInput
59+
v-model:value="debouncedSearch"
60+
class="input"
61+
placeholder="Search"
62+
autosize
63+
clearable
64+
>
2765
<template #prefix>
2866
<NIcon :component="IconLoop" />
2967
</template>

src/services/trakt.service.ts

+7-2
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,13 @@ export class TraktService {
4949
};
5050

5151
this.traktClient = new TraktClient({ ...traktClientSettings, cacheStore: this.caches.trakt }, {}, traktApi);
52-
this.tvdbClient = new TvdbClient({ ...tvdbClientSettings, cacheStore: this.caches.tvdb });
53-
this.tmdbClient = new TmdbClient({ ...tmdbClientSettings, cacheStore: this.caches.tmdb });
52+
this.tvdbClient = new TvdbClient({ ...tvdbClientSettings, cacheStore: this.caches.tvdb }, {}, tvdbApi);
53+
this.tmdbClient = new TmdbClient({ ...tmdbClientSettings, cacheStore: this.caches.tmdb }, {}, tmdbApi);
54+
}
55+
56+
static changeUser(user: string) {
57+
this.caches.trakt.prefix = `trakt-cache-${user}`;
58+
this.caches.tvdb.prefix = `tvdb-cache-${user}`;
5459
}
5560

5661
private static async saveAuth(

src/stores/data/history.store.ts

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { defineStore, storeToRefs } from 'pinia';
2+
import { computed, ref } from 'vue';
3+
4+
import type { TraktClientPagination } from '~/models/trakt/trakt-client.model';
5+
import type { TraktHistory } from '~/models/trakt/trakt-history.model';
6+
7+
import { TraktService } from '~/services/trakt.service';
8+
9+
export const useHistoryStore = defineStore('data.history', () => {
10+
const history = ref<TraktHistory[]>([]);
11+
const pagination = ref<TraktClientPagination>();
12+
13+
const searchHistory = ref('');
14+
const filteredHistory = computed(() => {
15+
if (!searchHistory.value) return history.value;
16+
return history.value.filter((item: TraktHistory) => {
17+
if ('movie' in item) return item.movie?.title?.toLowerCase().includes(searchHistory.value.toLowerCase());
18+
if ('show' in item) {
19+
if (item.show?.title?.toLowerCase().includes(searchHistory.value.toLowerCase())) return true;
20+
if (item.episode?.title?.toLowerCase().includes(searchHistory.value.toLowerCase())) return true;
21+
}
22+
return false;
23+
});
24+
});
25+
26+
const historyStart = ref<Date | undefined>(undefined);
27+
const historyEnd = ref<Date | undefined>(undefined);
28+
29+
const fetchHistory = async ({ page, start = historyStart.value, end = historyEnd.value }: { page?: number; start?: Date; end?: Date } = {}) => {
30+
const response = await TraktService.traktClient.sync.history.get.cached({
31+
pagination: {
32+
page,
33+
limit: 30,
34+
},
35+
start_at: start?.toISOString(),
36+
end_at: end?.toISOString(),
37+
});
38+
39+
const data = await response.json();
40+
pagination.value = response.pagination;
41+
history.value = page ? [...history.value, ...data] : data;
42+
};
43+
44+
const setHistoryRange = ({ start, end }: { start?: Date; end?: Date } = {}) => {
45+
historyStart.value = start;
46+
historyEnd.value = end;
47+
return fetchHistory({ start, end });
48+
};
49+
50+
return { history, pagination, fetchHistory, searchHistory, filteredHistory, historyStart, historyEnd, setHistoryRange };
51+
});
52+
53+
export const useHistoryStoreRefs = () => storeToRefs(useHistoryStore());

src/stores/settings/user.store.ts

+36-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { defineStore, storeToRefs } from 'pinia';
22

3-
import { computed, reactive, ref } from 'vue';
3+
import { computed, reactive, ref, watch } from 'vue';
44

55
import type { UserSetting } from '~/models/trakt-service.model';
66

7+
import { TraktService } from '~/services/trakt.service';
78
import { storage } from '~/utils/browser/browser-storage.utils';
89

910
type UserSettings = Record<string, UserSetting>;
@@ -16,20 +17,36 @@ export const useUserSettingsStore = defineStore('settings.user', () => {
1617

1718
const userSetting = computed(() => userSettings[user.value]);
1819

20+
/**
21+
* Save the current user settings to chrome storage
22+
* @param _settings
23+
* @param account
24+
*/
1925
const syncSetUser = (_settings: UserSetting = userSetting.value, account: string = _settings?.user?.username ?? user.value) => {
2026
const _lastUser = storage.sync.set(`settings.last-user`, account);
2127
const _setting = storage.sync.set(`settings.user.${encodeURIComponent(account)}`, _settings);
2228
console.info('user-store', 'Saving user', account, JSON.parse(JSON.stringify(_settings)));
2329
return Promise.all([_lastUser, _setting]);
2430
};
2531

32+
/**
33+
* Clear the last user from chrome storage
34+
*/
2635
const syncClearLastUser = () => storage.sync.remove(`settings.last-user`);
2736

37+
/**
38+
* Clear a specific user from chrome storage
39+
* @param account
40+
*/
2841
const syncClearUser = (account?: string) => {
2942
console.info('user-store', 'Clearing user', account);
3043
return storage.sync.remove(`settings.user${account ? `.${encodeURIComponent(account)}` : ''}`);
3144
};
3245

46+
/**
47+
* Restore a specific user from chrome storage
48+
* @param account
49+
*/
3350
const syncRestoreUser = async (account: string = user.value) => {
3451
if (account === defaultUser) account = await storage.sync.get<string>(`settings.last-user`);
3552
if (!account) account = Object.keys(userSettings).find(_account => _account !== defaultUser) ?? defaultUser;
@@ -41,6 +58,11 @@ export const useUserSettingsStore = defineStore('settings.user', () => {
4158
return _setting;
4259
};
4360

61+
/**
62+
* Change the current user settings for a specific account
63+
* @param _settings
64+
* @param account
65+
*/
4466
const setUserSetting = async (_settings: UserSetting = {}, account: string = _settings?.user?.username ?? user.value) => {
4567
if (Object.keys(_settings).length < 1) {
4668
delete userSettings[account];
@@ -55,6 +77,9 @@ export const useUserSettingsStore = defineStore('settings.user', () => {
5577
return syncSetUser(userSettings[account], account);
5678
};
5779

80+
/**
81+
* Restore all users from chrome storage
82+
*/
5883
const syncRestoreAllUsers = async () => {
5984
const restored = await storage.sync.getAll<UserSettings>('settings.user.');
6085

@@ -68,6 +93,10 @@ export const useUserSettingsStore = defineStore('settings.user', () => {
6893
});
6994
};
7095

96+
/**
97+
* Change the current user
98+
* @param account
99+
*/
71100
const setCurrentUser = (account?: string) => {
72101
console.info('user-store', 'Setting current user', JSON.parse(JSON.stringify(userSettings)));
73102

@@ -78,6 +107,12 @@ export const useUserSettingsStore = defineStore('settings.user', () => {
78107
return setUserSetting(userSettings[account], account);
79108
};
80109

110+
// Propagate user change to http service
111+
watch(user, _user => {
112+
console.info('user changed', _user);
113+
TraktService.changeUser(_user);
114+
});
115+
81116
return { user, userSettings, userSetting, setUserSetting, syncSetUser, syncRestoreUser, syncRestoreAllUsers, setCurrentUser };
82117
});
83118

src/utils/debounce.utils.ts

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- generic type
2+
type AnyFunction<T> = (...args: any[]) => T;
3+
4+
export function debounce<T>(func: AnyFunction<T>, delay: number = 250): AnyFunction<Promise<T>> {
5+
let timeoutId: ReturnType<typeof setTimeout> | undefined;
6+
7+
return async (...args: Parameters<typeof func>[]): Promise<T> => {
8+
return new Promise(resolve => {
9+
if (timeoutId) clearTimeout(timeoutId);
10+
11+
timeoutId = setTimeout(async () => {
12+
const result = await func(...args);
13+
resolve(result);
14+
}, delay);
15+
});
16+
};
17+
}

0 commit comments

Comments
 (0)