Skip to content

Commit

Permalink
feat: server-side settings (#493)
Browse files Browse the repository at this point in the history
  • Loading branch information
ErikBjare authored Oct 19, 2023
1 parent 608384c commit 1131f9b
Show file tree
Hide file tree
Showing 8 changed files with 90 additions and 58 deletions.
16 changes: 7 additions & 9 deletions src/App.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template lang="pug">
div#wrapper
div#wrapper(v-if="loaded")
aw-header

div(:class="{'container': !fullContainer, 'container-fluid': fullContainer}").px-0.px-md-2
Expand All @@ -21,6 +21,7 @@ export default {
return {
activityViews: [],
isNewReleaseCheckEnabled: !process.env.VUE_APP_ON_ANDROID,
loaded: false,
};
},
Expand All @@ -30,9 +31,11 @@ export default {
},
},
beforeCreate() {
async beforeCreate() {
// Get Theme From LocalStorage
const theme = localStorage.getItem('theme');
const settingsStore = useSettingsStore();
await settingsStore.ensureLoaded();
const theme = settingsStore.theme;
// Check Application Mode (Light | Dark)
if (theme !== null && theme === 'dark') {
// Create Dark Theme Element
Expand All @@ -42,15 +45,10 @@ export default {
// Append Dark Theme Element If Selected Mode Is Dark
theme === 'dark' ? document.querySelector('head').appendChild(themeLink) : '';
}
this.loaded = true;
},
mounted: async function () {
// Load settings
// TODO: Move fetch of server-side settings to after getInfo
const settingsStore = useSettingsStore();
await settingsStore.ensureLoaded();
const serverStore = useServerStore();
await serverStore.getInfo();
},
Expand Down
2 changes: 2 additions & 0 deletions src/components/SelectableVisualization.vue
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,8 @@ export default {
return date;
},
timeline_daterange: function () {
if (this.activityStore.query_options === null) return null;
let date = this.activityStore.query_options.date;
if (!date) {
date = this.activityStore.query_options.timeperiod.start;
Expand Down
14 changes: 10 additions & 4 deletions src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@ Vue.prototype.COMMIT_HASH = COMMIT_HASH;
// Set the $isAndroid constant
Vue.prototype.$isAndroid = process.env.VUE_APP_ON_ANDROID;

// Create an instance of AWClient as this.$aw
// NOTE: needs to be created before the Vue app is created,
// since stores rely on it having been run.
import { createClient, getClient, configureClient } from './util/awclient';
createClient();

// Setup Vue app
import App from './App';
new Vue({
Expand All @@ -81,8 +87,8 @@ new Vue({
pinia,
});

// Create an instance of AWClient as this.$aw
import { createClient, getClient } from './util/awclient';

createClient();
// Set the $aw global
Vue.prototype.$aw = getClient();

// Must be run after vue init since it relies on the settings store
configureClient();
4 changes: 3 additions & 1 deletion src/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ const browser_appnames = {
'Microsoft-edge',
],
arc: [
"Arc" // macOS
'Arc', // macOS
],
vivaldi: ['Vivaldi-stable', 'Vivaldi-snapshot', 'vivaldi.exe'],
orion: ['Orion'],
Expand Down Expand Up @@ -411,6 +411,8 @@ export function editorActivityQuery(editorbuckets: string[]): string[] {
// Returns a query that yields a single event with the duration set to
// the sum of all non-afk time in the queried period
// TODO: Would ideally account for `filter_afk` and `always_active_pattern`
// TODO: rename to something like `activeDurationQuery`
// FIXME: Doesn't respect audible-as-active and always-active-pattern
export function activityQuery(afkbuckets: string[]): string[] {
let q = ['not_afk = [];'];
for (const afkbucket of afkbuckets) {
Expand Down
1 change: 0 additions & 1 deletion src/stores/activity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,6 @@ export const useActivityStore = defineStore('activity', {
if (!this.category.top) {
return null;
}
console.log(this.category.top);
const uncategorized = this.category.top.filter(e => {
return _.isEqual(e.data['$category'], ['Uncategorized']);
});
Expand Down
103 changes: 63 additions & 40 deletions src/stores/settings.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { defineStore } from 'pinia';
import moment, { Moment } from 'moment';
import { getClient } from '~/util/awclient';

// Backoffs for NewReleaseNotification
export const SHORT_BACKOFF_PERIOD = 24 * 60 * 60;
Expand Down Expand Up @@ -74,63 +75,85 @@ export const useSettingsStore = defineStore('settings', {
await this.load();
}
},
async load() {
async load({ save }: { save?: boolean } = {}) {
if (typeof localStorage === 'undefined') {
console.error('localStorage is not supported');
return;
}
// Fetch from localStorage first, if exists
const client = getClient();

// Fetch from server, fall back to localStorage
const server_settings = await client.get_settings();

const all_keys = [
...Object.keys(localStorage).filter(key => {
// Skip built-in properties like length, setItem, etc.
return Object.prototype.hasOwnProperty.call(localStorage, key);
}),
...Object.keys(server_settings),
].filter(key => {
// Skip keys starting with underscore, as they are local to the vuex store.
return !key.startsWith('_');
});
console.log('all_keys', all_keys);

const storage = {};
for (const key in localStorage) {
// Skip built-in properties like length, setItem, etc.
// Also skip keys starting with underscore, as they are local to the vuex store.
if (Object.prototype.hasOwnProperty.call(localStorage, key) && !key.startsWith('_')) {
const value = localStorage.getItem(key);
//console.log(`${key}: ${value}`);

// Keys ending with 'Data' are JSON-serialized objects
if (key.includes('Data')) {
try {
storage[key] = JSON.parse(value);
} catch (e) {
console.error('failed to parse', key, value);
}
} else if (value === 'true' || value === 'false') {
storage[key] = value === 'true';
} else {
storage[key] = value;
for (const key of all_keys) {
// If key is set in server, use that value, otherwise use localStorage
const set_in_server = server_settings[key] !== undefined;
const value = set_in_server ? server_settings[key] : localStorage.getItem(key);
const locstr = set_in_server ? '[server]' : '[localStorage]';
console.log(`${locstr} ${key}:`, value);

// Keys ending with 'Data' are JSON-serialized objects
if (key.includes('Data') && !set_in_server) {
try {
storage[key] = JSON.parse(value);
} catch (e) {
console.error('failed to parse', key, value);
}
} else if (value === 'true' || value === 'false') {
storage[key] = value === 'true';
} else {
storage[key] = value;
}
}
this.$patch({ ...storage, _loaded: true });

// TODO: Then fetch from server
//const getSettingsFromServer = async () => {
// const { data } = await this.$aw._get('/0/settings');
// return data;
//};
if (save) {
await this.save();
}
},
async save() {
// First save to localStorage
// We want to avoid saving to localStorage to not accidentally mess up pre-migration data
// For example, if the user is using several browsers, and opened in their non-main browser on first run after upgrade.
const saveToLocalStorage = false;

// Save to localStorage and backend
// NOTE: localStorage deprecated, will be removed in future
const client = getClient();
for (const key of Object.keys(this.$state)) {
const value = this.$state[key];
if (typeof value === 'object') {
localStorage.setItem(key, JSON.stringify(value));
} else {
localStorage.setItem(key, value);

// Save to localStorage
if (saveToLocalStorage) {
if (typeof value === 'object') {
localStorage.setItem(key, JSON.stringify(value));
} else {
localStorage.setItem(key, value);
}
}
}

// TODO: Save to backend
//const updateSettingOnServer = async (key: string, value: string) => {
// console.log({ key, value });
// const headers = { 'Content-Type': 'application/json' };
// const { data } = await this.$aw._post('/0/settings', { key, value }, headers);
// return data;
//};
// Save to backend
await client.req.post('/0/settings/' + key, value, {
headers: {
'Content-Type': 'application/json',
},
});
}

// After save, reload from localStorage
await this.load();
// After save, reload
await this.load({ save: false });
},
async update(new_state: Record<string, any>) {
console.log('Updating state', new_state);
Expand Down
6 changes: 5 additions & 1 deletion src/util/awclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,18 @@ export function createClient(force?: boolean): AWClient {
_client = new AWClient('aw-webui', {
testing: !production,
baseURL,
timeout: 1000 * useSettingsStore().requestTimeout,
});
} else {
throw 'Tried to instantiate global AWClient twice!';
}
return _client;
}

export function configureClient(): void {
const settings = useSettingsStore();
_client.req.defaults.timeout = 1000 * settings.requestTimeout;
}

export function getClient(): AWClient {
if (!_client) {
throw 'Tried to get global AWClient before instantiating it!';
Expand Down
2 changes: 0 additions & 2 deletions src/views/settings/Settings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
div
h3 Settings

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].

hr

DaystartSettings
Expand Down

0 comments on commit 1131f9b

Please sign in to comment.