Skip to content

Commit 1131f9b

Browse files
authored
feat: server-side settings (#493)
1 parent 608384c commit 1131f9b

File tree

8 files changed

+90
-58
lines changed

8 files changed

+90
-58
lines changed

src/App.vue

+7-9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<template lang="pug">
2-
div#wrapper
2+
div#wrapper(v-if="loaded")
33
aw-header
44

55
div(:class="{'container': !fullContainer, 'container-fluid': fullContainer}").px-0.px-md-2
@@ -21,6 +21,7 @@ export default {
2121
return {
2222
activityViews: [],
2323
isNewReleaseCheckEnabled: !process.env.VUE_APP_ON_ANDROID,
24+
loaded: false,
2425
};
2526
},
2627
@@ -30,9 +31,11 @@ export default {
3031
},
3132
},
3233
33-
beforeCreate() {
34+
async beforeCreate() {
3435
// Get Theme From LocalStorage
35-
const theme = localStorage.getItem('theme');
36+
const settingsStore = useSettingsStore();
37+
await settingsStore.ensureLoaded();
38+
const theme = settingsStore.theme;
3639
// Check Application Mode (Light | Dark)
3740
if (theme !== null && theme === 'dark') {
3841
// Create Dark Theme Element
@@ -42,15 +45,10 @@ export default {
4245
// Append Dark Theme Element If Selected Mode Is Dark
4346
theme === 'dark' ? document.querySelector('head').appendChild(themeLink) : '';
4447
}
48+
this.loaded = true;
4549
},
4650
4751
mounted: async function () {
48-
// Load settings
49-
// TODO: Move fetch of server-side settings to after getInfo
50-
51-
const settingsStore = useSettingsStore();
52-
await settingsStore.ensureLoaded();
53-
5452
const serverStore = useServerStore();
5553
await serverStore.getInfo();
5654
},

src/components/SelectableVisualization.vue

+2
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,8 @@ export default {
278278
return date;
279279
},
280280
timeline_daterange: function () {
281+
if (this.activityStore.query_options === null) return null;
282+
281283
let date = this.activityStore.query_options.date;
282284
if (!date) {
283285
date = this.activityStore.query_options.timeperiod.start;

src/main.js

+10-4
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,12 @@ Vue.prototype.COMMIT_HASH = COMMIT_HASH;
7272
// Set the $isAndroid constant
7373
Vue.prototype.$isAndroid = process.env.VUE_APP_ON_ANDROID;
7474

75+
// Create an instance of AWClient as this.$aw
76+
// NOTE: needs to be created before the Vue app is created,
77+
// since stores rely on it having been run.
78+
import { createClient, getClient, configureClient } from './util/awclient';
79+
createClient();
80+
7581
// Setup Vue app
7682
import App from './App';
7783
new Vue({
@@ -81,8 +87,8 @@ new Vue({
8187
pinia,
8288
});
8389

84-
// Create an instance of AWClient as this.$aw
85-
import { createClient, getClient } from './util/awclient';
86-
87-
createClient();
90+
// Set the $aw global
8891
Vue.prototype.$aw = getClient();
92+
93+
// Must be run after vue init since it relies on the settings store
94+
configureClient();

src/queries.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ const browser_appnames = {
273273
'Microsoft-edge',
274274
],
275275
arc: [
276-
"Arc" // macOS
276+
'Arc', // macOS
277277
],
278278
vivaldi: ['Vivaldi-stable', 'Vivaldi-snapshot', 'vivaldi.exe'],
279279
orion: ['Orion'],
@@ -411,6 +411,8 @@ export function editorActivityQuery(editorbuckets: string[]): string[] {
411411
// Returns a query that yields a single event with the duration set to
412412
// the sum of all non-afk time in the queried period
413413
// TODO: Would ideally account for `filter_afk` and `always_active_pattern`
414+
// TODO: rename to something like `activeDurationQuery`
415+
// FIXME: Doesn't respect audible-as-active and always-active-pattern
414416
export function activityQuery(afkbuckets: string[]): string[] {
415417
let q = ['not_afk = [];'];
416418
for (const afkbucket of afkbuckets) {

src/stores/activity.ts

-1
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,6 @@ export const useActivityStore = defineStore('activity', {
214214
if (!this.category.top) {
215215
return null;
216216
}
217-
console.log(this.category.top);
218217
const uncategorized = this.category.top.filter(e => {
219218
return _.isEqual(e.data['$category'], ['Uncategorized']);
220219
});

src/stores/settings.ts

+63-40
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { defineStore } from 'pinia';
22
import moment, { Moment } from 'moment';
3+
import { getClient } from '~/util/awclient';
34

45
// Backoffs for NewReleaseNotification
56
export const SHORT_BACKOFF_PERIOD = 24 * 60 * 60;
@@ -74,63 +75,85 @@ export const useSettingsStore = defineStore('settings', {
7475
await this.load();
7576
}
7677
},
77-
async load() {
78+
async load({ save }: { save?: boolean } = {}) {
7879
if (typeof localStorage === 'undefined') {
7980
console.error('localStorage is not supported');
8081
return;
8182
}
82-
// Fetch from localStorage first, if exists
83+
const client = getClient();
84+
85+
// Fetch from server, fall back to localStorage
86+
const server_settings = await client.get_settings();
87+
88+
const all_keys = [
89+
...Object.keys(localStorage).filter(key => {
90+
// Skip built-in properties like length, setItem, etc.
91+
return Object.prototype.hasOwnProperty.call(localStorage, key);
92+
}),
93+
...Object.keys(server_settings),
94+
].filter(key => {
95+
// Skip keys starting with underscore, as they are local to the vuex store.
96+
return !key.startsWith('_');
97+
});
98+
console.log('all_keys', all_keys);
99+
83100
const storage = {};
84-
for (const key in localStorage) {
85-
// Skip built-in properties like length, setItem, etc.
86-
// Also skip keys starting with underscore, as they are local to the vuex store.
87-
if (Object.prototype.hasOwnProperty.call(localStorage, key) && !key.startsWith('_')) {
88-
const value = localStorage.getItem(key);
89-
//console.log(`${key}: ${value}`);
90-
91-
// Keys ending with 'Data' are JSON-serialized objects
92-
if (key.includes('Data')) {
93-
try {
94-
storage[key] = JSON.parse(value);
95-
} catch (e) {
96-
console.error('failed to parse', key, value);
97-
}
98-
} else if (value === 'true' || value === 'false') {
99-
storage[key] = value === 'true';
100-
} else {
101-
storage[key] = value;
101+
for (const key of all_keys) {
102+
// If key is set in server, use that value, otherwise use localStorage
103+
const set_in_server = server_settings[key] !== undefined;
104+
const value = set_in_server ? server_settings[key] : localStorage.getItem(key);
105+
const locstr = set_in_server ? '[server]' : '[localStorage]';
106+
console.log(`${locstr} ${key}:`, value);
107+
108+
// Keys ending with 'Data' are JSON-serialized objects
109+
if (key.includes('Data') && !set_in_server) {
110+
try {
111+
storage[key] = JSON.parse(value);
112+
} catch (e) {
113+
console.error('failed to parse', key, value);
102114
}
115+
} else if (value === 'true' || value === 'false') {
116+
storage[key] = value === 'true';
117+
} else {
118+
storage[key] = value;
103119
}
104120
}
105121
this.$patch({ ...storage, _loaded: true });
106122

107-
// TODO: Then fetch from server
108-
//const getSettingsFromServer = async () => {
109-
// const { data } = await this.$aw._get('/0/settings');
110-
// return data;
111-
//};
123+
if (save) {
124+
await this.save();
125+
}
112126
},
113127
async save() {
114-
// First save to localStorage
128+
// We want to avoid saving to localStorage to not accidentally mess up pre-migration data
129+
// For example, if the user is using several browsers, and opened in their non-main browser on first run after upgrade.
130+
const saveToLocalStorage = false;
131+
132+
// Save to localStorage and backend
133+
// NOTE: localStorage deprecated, will be removed in future
134+
const client = getClient();
115135
for (const key of Object.keys(this.$state)) {
116136
const value = this.$state[key];
117-
if (typeof value === 'object') {
118-
localStorage.setItem(key, JSON.stringify(value));
119-
} else {
120-
localStorage.setItem(key, value);
137+
138+
// Save to localStorage
139+
if (saveToLocalStorage) {
140+
if (typeof value === 'object') {
141+
localStorage.setItem(key, JSON.stringify(value));
142+
} else {
143+
localStorage.setItem(key, value);
144+
}
121145
}
122-
}
123146

124-
// TODO: Save to backend
125-
//const updateSettingOnServer = async (key: string, value: string) => {
126-
// console.log({ key, value });
127-
// const headers = { 'Content-Type': 'application/json' };
128-
// const { data } = await this.$aw._post('/0/settings', { key, value }, headers);
129-
// return data;
130-
//};
147+
// Save to backend
148+
await client.req.post('/0/settings/' + key, value, {
149+
headers: {
150+
'Content-Type': 'application/json',
151+
},
152+
});
153+
}
131154

132-
// After save, reload from localStorage
133-
await this.load();
155+
// After save, reload
156+
await this.load({ save: false });
134157
},
135158
async update(new_state: Record<string, any>) {
136159
console.log('Updating state', new_state);

src/util/awclient.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,18 @@ export function createClient(force?: boolean): AWClient {
2020
_client = new AWClient('aw-webui', {
2121
testing: !production,
2222
baseURL,
23-
timeout: 1000 * useSettingsStore().requestTimeout,
2423
});
2524
} else {
2625
throw 'Tried to instantiate global AWClient twice!';
2726
}
2827
return _client;
2928
}
3029

30+
export function configureClient(): void {
31+
const settings = useSettingsStore();
32+
_client.req.defaults.timeout = 1000 * settings.requestTimeout;
33+
}
34+
3135
export function getClient(): AWClient {
3236
if (!_client) {
3337
throw 'Tried to get global AWClient before instantiating it!';

src/views/settings/Settings.vue

-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
div
33
h3 Settings
44

5-
b-alert(variant="warning", show) #[b Note:] These settings are only saved in your browser and will not remain if you switch browser. We are working on getting this fixed, see #[a(href="https://github.com/ActivityWatch/aw-server-rust/issues/394", target="_blank") issue #394].
6-
75
hr
86

97
DaystartSettings

0 commit comments

Comments
 (0)