Skip to content

Commit 3dd58e8

Browse files
committed
feat(activity): adds activity polling and fix watching polling
1 parent 61ff5c0 commit 3dd58e8

10 files changed

+193
-69
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<script lang="ts" setup>
2+
import { NSelect } from 'naive-ui';
3+
4+
import { ref } from 'vue';
5+
6+
import SettingsFormItem from '~/components/views/settings/SettingsFormItem.vue';
7+
import { PollingIntervals } from '~/models/polling.model';
8+
import { useActivityStoreRefs } from '~/stores/data/activity.store';
9+
import { useI18n } from '~/utils/i18n.utils';
10+
11+
const i18n = useI18n('settings', 'activity');
12+
13+
const { polling } = useActivityStoreRefs();
14+
15+
const toSnakeCase = (str: string) =>
16+
str
17+
.replace(/([A-Z])/g, '_$1')
18+
.toLowerCase()
19+
.slice(1);
20+
21+
const options = Object.entries(PollingIntervals).map(([key, value]) => ({
22+
label: i18n(toSnakeCase(key), 'common', 'polling', 'interval'),
23+
value,
24+
}));
25+
26+
const container = ref();
27+
</script>
28+
29+
<template>
30+
<div ref="container" class="activity-container">
31+
<!-- Polling -->
32+
<SettingsFormItem :label="i18n('label_polling_interval')">
33+
<NSelect
34+
v-model:value="polling"
35+
class="form-select"
36+
:to="container"
37+
:options="options"
38+
/>
39+
</SettingsFormItem>
40+
</div>
41+
</template>
42+
43+
<style lang="scss" scoped>
44+
.activity-container {
45+
display: flex;
46+
flex-direction: column;
47+
gap: 1.5rem;
48+
}
49+
50+
.form-select {
51+
min-width: 10rem;
52+
}
53+
</style>

src/components/views/settings/SettingsComponent.vue

+2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { NAnchor, NAnchorLink, NCard, NLayout, NLayoutSider } from 'naive-ui';
44
import { type Component, onDeactivated, type Ref, ref } from 'vue';
55
66
import SettingsAccount from '~/components/views/settings/SettingsAccount.vue';
7+
import SettingsActivity from '~/components/views/settings/SettingsActivity.vue';
78
import SettingsCache from '~/components/views/settings/SettingsCache.vue';
89
import SettingsExport from '~/components/views/settings/SettingsExport.vue';
910
import SettingsLinks from '~/components/views/settings/SettingsLinks.vue';
@@ -27,6 +28,7 @@ const sections: Section[] = [
2728
{ title: 'menu__links', reference: ref(), component: SettingsLinks },
2829
{ title: 'menu__menus', reference: ref(), component: SettingsMenus },
2930
{ title: 'menu__watching', reference: ref(), component: SettingsWatching },
31+
{ title: 'menu__activity', reference: ref(), component: SettingsActivity },
3032
{ title: 'menu__cache', reference: ref(), component: SettingsCache },
3133
{ title: 'menu__export', reference: ref(), component: SettingsExport },
3234
{ title: 'menu__logs', reference: ref(), component: SettingsLogs },

src/components/views/settings/SettingsWatching.vue

+3-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import { NSelect, NSwitch } from 'naive-ui';
44
import { ref } from 'vue';
55
66
import SettingsFormItem from '~/components/views/settings/SettingsFormItem.vue';
7-
import { PollingIntervals, useWatchingStoreRefs } from '~/stores/data/watching.store';
7+
import { PollingIntervals } from '~/models/polling.model';
8+
import { useWatchingStoreRefs } from '~/stores/data/watching.store';
89
import { useI18n } from '~/utils/i18n.utils';
910
1011
const i18n = useI18n('settings', 'watching');
@@ -18,7 +19,7 @@ const toSnakeCase = (str: string) =>
1819
.slice(1);
1920
2021
const options = Object.entries(PollingIntervals).map(([key, value]) => ({
21-
label: i18n(`label_interval_${toSnakeCase(key)}`),
22+
label: i18n(toSnakeCase(key), 'common', 'polling', 'interval'),
2223
value,
2324
}));
2425

src/i18n/en/common/polling.json

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"common__polling__interval__disabled": {
3+
"message": "Disabled",
4+
"description": "Label for the disabled interval."
5+
},
6+
"common__polling__interval__second": {
7+
"message": "1 second",
8+
"description": "Label for the 1 second interval."
9+
},
10+
"common__polling__interval__five_seconds": {
11+
"message": "5 seconds",
12+
"description": "Label for the 5 seconds interval."
13+
},
14+
"common__polling__interval__ten_seconds": {
15+
"message": "10 seconds",
16+
"description": "Label for the 10 seconds interval."
17+
},
18+
"common__polling__interval__thirty_seconds": {
19+
"message": "30 seconds",
20+
"description": "Label for the 30 seconds interval."
21+
},
22+
"common__polling__interval__one_minute": {
23+
"message": "1 minute",
24+
"description": "Label for the 1 minute interval."
25+
},
26+
"common__polling__interval__five_minutes": {
27+
"message": "5 minutes",
28+
"description": "Label for the 5 minutes interval."
29+
},
30+
"common__polling__interval__ten_minutes": {
31+
"message": "10 minutes",
32+
"description": "Label for the 10 minutes interval."
33+
},
34+
"common__polling__interval__thirty_minutes": {
35+
"message": "30 minutes",
36+
"description": "Label for the 30 minutes interval."
37+
}
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"settings__activity__label_polling_interval": {
3+
"message": "Configure polling interval for activity state.",
4+
"description": "Label for the polling interval setting."
5+
}
6+
}

src/i18n/en/settings/settings-menu.json

+4
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,9 @@
3030
"settings__menu__watching": {
3131
"message": "Watching",
3232
"description": "Watching ui menu item"
33+
},
34+
"settings__menu__activity": {
35+
"message": "Activity",
36+
"description": "Activity ui menu item"
3337
}
3438
}

src/i18n/en/settings/settings-polling.json

-36
Original file line numberDiff line numberDiff line change
@@ -6,41 +6,5 @@
66
"settings__watching__label_override": {
77
"message": "Automatically cancel pending checkin request.",
88
"description": "Label for the override setting."
9-
},
10-
"settings__watching__label_interval_disabled": {
11-
"message": "Disabled",
12-
"description": "Label for the disabled interval."
13-
},
14-
"settings__watching__label_interval_second": {
15-
"message": "1 second",
16-
"description": "Label for the 1 second interval."
17-
},
18-
"settings__watching__label_interval_five_seconds": {
19-
"message": "5 seconds",
20-
"description": "Label for the 5 seconds interval."
21-
},
22-
"settings__watching__label_interval_ten_seconds": {
23-
"message": "10 seconds",
24-
"description": "Label for the 10 seconds interval."
25-
},
26-
"settings__watching__label_interval_thirty_seconds": {
27-
"message": "30 seconds",
28-
"description": "Label for the 30 seconds interval."
29-
},
30-
"settings__watching__label_interval_one_minute": {
31-
"message": "1 minute",
32-
"description": "Label for the 1 minute interval."
33-
},
34-
"settings__watching__label_interval_five_minutes": {
35-
"message": "5 minutes",
36-
"description": "Label for the 5 minutes interval."
37-
},
38-
"settings__watching__label_interval_ten_minutes": {
39-
"message": "10 minutes",
40-
"description": "Label for the 10 minutes interval."
41-
},
42-
"settings__watching__label_interval_thirty_minutes": {
43-
"message": "30 minutes",
44-
"description": "Label for the 30 minutes interval."
459
}
4610
}

src/models/polling.model.ts

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export const PollingIntervals = {
2+
Disabled: 0,
3+
Second: 1000,
4+
FiveSeconds: 5 * 1000,
5+
TenSeconds: 10 * 1000,
6+
ThirtySeconds: 30 * 1000,
7+
OneMinute: 60 * 1000,
8+
FiveMinutes: 5 * 60 * 1000,
9+
TenMinutes: 10 * 60 * 1000,
10+
ThirtyMinutes: 30 * 60 * 1000,
11+
} as const;

src/stores/data/activity.store.ts

+64-8
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,48 @@
11
import { compareDateObject, toDateObject } from '@dvcol/common-utils/common/date';
22
import { defineStore, storeToRefs } from 'pinia';
33

4-
import { ref, watch } from 'vue';
4+
import { computed, ref, watch } from 'vue';
55

66
import type { RecursiveType } from '@dvcol/common-utils/common';
77
import type { TraktSyncActivities } from '@dvcol/trakt-http-client/models';
88

9+
import { PollingIntervals } from '~/models/polling.model';
910
import { Logger } from '~/services/logger.service';
1011
import { NotificationService } from '~/services/notification.service';
1112
import { TraktService } from '~/services/trakt.service';
12-
import { useUserSettingsStore } from '~/stores/settings/user.store';
13+
import { useAuthSettingsStoreRefs } from '~/stores/settings/auth.store';
14+
import { useUserSettingsStore, useUserSettingsStoreRefs } from '~/stores/settings/user.store';
1315
import { storage } from '~/utils/browser/browser-storage.utils';
1416

17+
type ActivityStoreState = {
18+
activity?: TraktSyncActivities;
19+
polling?: number;
20+
};
21+
1522
const ActivityStoreConstants = {
1623
Store: 'data.activity',
24+
/** 30 seconds */
25+
DefaultPolling: PollingIntervals.ThirtySeconds,
1726
} as const;
1827

1928
export const useActivityStore = defineStore(ActivityStoreConstants.Store, () => {
2029
const activity = ref<TraktSyncActivities>();
2130
const loading = ref(false);
31+
const polling = ref(ActivityStoreConstants.DefaultPolling);
2232

2333
const clearState = () => {
2434
activity.value = undefined;
2535
};
2636

27-
const saveState = async () => storage.local.set(ActivityStoreConstants.Store, activity.value);
37+
const saveState = async () =>
38+
storage.local.set<ActivityStoreState>(ActivityStoreConstants.Store, {
39+
activity: activity.value,
40+
polling: polling.value,
41+
});
2842
const restoreState = async () => {
29-
const state = await storage.local.get<TraktSyncActivities>(ActivityStoreConstants.Store);
30-
if (state) activity.value = state;
43+
const state = await storage.local.get<ActivityStoreState>(ActivityStoreConstants.Store);
44+
if (state?.activity) activity.value = state.activity;
45+
if (state?.polling !== undefined) polling.value = state.polling;
3146
};
3247

3348
const fetchActivity = async () => {
@@ -47,8 +62,11 @@ export const useActivityStore = defineStore(ActivityStoreConstants.Store, () =>
4762
};
4863

4964
const { refreshUserSettings } = useUserSettingsStore();
65+
const { isAuthenticated } = useAuthSettingsStoreRefs();
66+
const { user } = useUserSettingsStoreRefs();
67+
const interval = ref<ReturnType<typeof setInterval>>();
5068

51-
const initActivityStore = async (fetch?: boolean) => {
69+
const initActivityStore = async () => {
5270
await restoreState();
5371

5472
watch(activity, (next, prev) => {
@@ -108,10 +126,48 @@ export const useActivityStore = defineStore(ActivityStoreConstants.Store, () =>
108126
}
109127
});
110128

111-
if (fetch) await fetchActivity();
129+
watch(
130+
polling,
131+
async () => {
132+
if (interval.value) clearInterval(interval.value);
133+
if (!polling.value) return;
134+
if (isAuthenticated.value) await fetchActivity();
135+
interval.value = setInterval(() => {
136+
if (!isAuthenticated.value) return;
137+
return fetchActivity();
138+
}, polling.value);
139+
Logger.debug('Activity polling interval set to', polling.value);
140+
},
141+
{
142+
immediate: true,
143+
},
144+
);
145+
146+
watch(user, async () => {
147+
if (!isAuthenticated.value) return;
148+
await fetchActivity();
149+
});
150+
151+
if (polling.value || !isAuthenticated.value) return;
152+
return fetchActivity();
112153
};
113154

114-
return { activity, loading, fetchActivity, clearState, saveState, restoreState, initActivityStore };
155+
return {
156+
activity,
157+
polling: computed({
158+
get: () => polling.value,
159+
set: (value: number = ActivityStoreConstants.DefaultPolling) => {
160+
polling.value = value;
161+
saveState().catch(e => Logger.error('Failed to save watching state', e));
162+
},
163+
}),
164+
loading,
165+
fetchActivity,
166+
clearState,
167+
saveState,
168+
restoreState,
169+
initActivityStore,
170+
};
115171
});
116172

117173
export const useActivityStoreRefs = () => storeToRefs(useActivityStore());

0 commit comments

Comments
 (0)