From d4e9d4c3485f358583913a70cd5b862641bd02f1 Mon Sep 17 00:00:00 2001 From: Julian Stirling Date: Sun, 2 Nov 2025 12:06:10 +0000 Subject: [PATCH 1/3] Remove unused syncDataWithLocalStorage.js --- webapp/src/syncDataWithLocalStorage.js | 36 -------------------------- 1 file changed, 36 deletions(-) delete mode 100644 webapp/src/syncDataWithLocalStorage.js diff --git a/webapp/src/syncDataWithLocalStorage.js b/webapp/src/syncDataWithLocalStorage.js deleted file mode 100644 index 6fb7c222..00000000 --- a/webapp/src/syncDataWithLocalStorage.js +++ /dev/null @@ -1,36 +0,0 @@ -/** - * This function should be called from a Vue component's "created" hook - * It will initialise a list of properties from local storage. - * It then sets up watchers for said properties to sync them back - * so that next time we load, the values persist. - * - * Arguments: - * keyName: a string, used as the key in local storage - * syncedObject: the object whose data you want to sync (usually `this`) - * syncedData: an object, the keys of which set the properties to be synced - */ -export function syncDataWithLocalStorage(keyName, syncedObject, syncedData) { - const syncedKeys = Object.keys(syncedData); - // First, we try to retrieve the stored data - const storedString = localStorage.getItem(keyName); - if (storedString) { - const storedValues = JSON.parse(storedString); - for (const item of syncedKeys) { - if (item in storedValues) syncedObject[item] = storedValues[item]; - } - } - - // This function will update local storage with current values - let updateStoredValues = function() { - let newData = {}; - for (const item of syncedKeys) { - newData[item] = syncedObject[item]; - } - localStorage.setItem(keyName, JSON.stringify(newData)); - }; - - // Now, set up watchers to update local storage when things change - for (const item of syncedKeys) { - syncedObject.$watch(item, updateStoredValues, { deep: true }); - } -} From e9eec20027d1a42a47407f2ad37c8114270d9099 Mon Sep 17 00:00:00 2001 From: Julian Stirling Date: Sun, 2 Nov 2025 12:07:24 +0000 Subject: [PATCH 2/3] Store can optionally (per key) read/load from local storage tested with appTheme --- .../settingsComponents/appSettings.vue | 11 ---- webapp/src/store.js | 54 +++++++++++++++++++ 2 files changed, 54 insertions(+), 11 deletions(-) diff --git a/webapp/src/components/tabContentComponents/settingsComponents/appSettings.vue b/webapp/src/components/tabContentComponents/settingsComponents/appSettings.vue index 01ca210d..a3eed683 100644 --- a/webapp/src/components/tabContentComponents/settingsComponents/appSettings.vue +++ b/webapp/src/components/tabContentComponents/settingsComponents/appSettings.vue @@ -38,17 +38,6 @@ export default { }, }, - watch: { - appTheme: function() { - this.setLocalStorageObj("appTheme", this.appTheme); - }, - }, - - mounted() { - // Try loading settings from localStorage. If null, don't change. - this.appTheme = this.getLocalStorageObj("appTheme") || this.appTheme; - }, - methods: { async toggleFullscreen() { if (!document.fullscreenElement) { diff --git a/webapp/src/store.js b/webapp/src/store.js index 4bcf61fc..a0b67189 100644 --- a/webapp/src/store.js +++ b/webapp/src/store.js @@ -4,6 +4,8 @@ import wotStoreModule from "./wot-client"; Vue.use(Vuex); +const LOCALSTORAGE_KEYS = ["appTheme"]; + function getOriginFromLocation() { // This will default to the same origin that's serving // the web app - but can be overridden by the URL. @@ -17,6 +19,36 @@ function getOriginFromLocation() { } } +/** + * Converts a Vuex state key (e.g. "appTheme") into a corresponding + * Vuex mutation name (e.g. "changeAppTheme") using the `change` + * convention. + * + * @param {string} key - The Vuex state key to convert. + * @returns {string} - The formatted mutation name. + */ +function keyToMutationName(key) { + return `change${key.charAt(0).toUpperCase() + key.slice(1)}`; +} + +/** + * Converts a Vuex mutation name (e.g. "changeAppTheme") back into + * the corresponding state key (e.g. "appTheme") using the + * `change` convention. + * + * @param {string} mutationName - The Vuex mutation name to reverse. + * @returns {string|null} - The derived state key, or null if the + * mutation name doesn't match the `change` convention. + */ +function mutationToKey(mutationName) { + const prefix = "change"; + if (!mutationName.startsWith(prefix)) { + return null; // Not a mutation we care about + } + const key = mutationName.slice(prefix.length); + return key.charAt(0).toLowerCase() + key.slice(1); +} + export default new Vuex.Store({ modules: { wot: wotStoreModule, @@ -86,4 +118,26 @@ export default new Vuex.Store({ baseUri: state => state.origin, ready: state => state.available, }, + + plugins: [ + store => { + // Load initial state from localStorage + LOCALSTORAGE_KEYS.forEach(key => { + const saved = localStorage.getItem(key); + if (saved !== null) { + const mutationName = keyToMutationName(key); + store.commit(mutationName, JSON.parse(saved)); + } + }); + + // Subscribe to mutations + store.subscribe((mutation, state) => { + const key = mutationToKey(mutation.type); + // If the mutation is chacning a local storage key then update localStorage + if (key && LOCALSTORAGE_KEYS.includes(key)) { + localStorage.setItem(key, JSON.stringify(state[key])); + } + }); + }, + ], }); From 97fa0b03b2346bf689350fa61c613b6d6633e754 Mon Sep 17 00:00:00 2001 From: Julian Stirling Date: Sun, 2 Nov 2025 13:16:07 +0000 Subject: [PATCH 3/3] Move all other local storage into the store. --- .../aboutComponents/devTools.vue | 12 +---- .../controlComponents/paneControl.vue | 53 ++++++------------- .../settingsComponents/streamSettings.vue | 8 --- webapp/src/main.js | 16 ------ webapp/src/store.js | 50 ++++++++++++++--- 5 files changed, 62 insertions(+), 77 deletions(-) diff --git a/webapp/src/components/tabContentComponents/aboutComponents/devTools.vue b/webapp/src/components/tabContentComponents/aboutComponents/devTools.vue index a0f59a09..2459ca7d 100644 --- a/webapp/src/components/tabContentComponents/aboutComponents/devTools.vue +++ b/webapp/src/components/tabContentComponents/aboutComponents/devTools.vue @@ -23,23 +23,15 @@ export default { data: function() { return { - newOrigin: this.$store.state.origin, + newOrigin: this.$store.state.overrideOrigin, reloadWhenOverridingOrigin: true, }; }, - mounted() { - if (localStorage.overrideOrigin) { - this.newOrigin = localStorage.overrideOrigin; - } else { - this.newOrigin = "http://microscope.local:5000"; - } - }, - methods: { overrideAPIHost: function(event) { // Save the origin override, so that if we reload the web app, you can easily - localStorage.overrideOrigin = this.newOrigin; + this.$store.commit("changeOverrideOrigin", this.newOrigin); // If we have elected not to reload the interface, just update the origin // in the store. Otherwise, the form's default action will do the job for us. diff --git a/webapp/src/components/tabContentComponents/controlComponents/paneControl.vue b/webapp/src/components/tabContentComponents/controlComponents/paneControl.vue index 14b69988..a17a2537 100644 --- a/webapp/src/components/tabContentComponents/controlComponents/paneControl.vue +++ b/webapp/src/components/tabContentComponents/controlComponents/paneControl.vue @@ -10,12 +10,7 @@
- +
- +
- +
@@ -177,18 +162,6 @@ export default { data: function() { return { - stepXy: 200, - stepZz: 50, - stepSize: { - x: 200, - y: 200, - z: 50, - }, - invert: { - x: false, - y: false, - z: false, - }, setPosition: null, isAutofocusing: 0, moveLock: false, @@ -196,6 +169,15 @@ export default { }, computed: { + // Note that as stepSize and invert are set based on internals we can use + // get() and set() to interact with the store. Instead use a deep watcher to + // update the store (see ``watch:`` below) + stepSize() { + return this.$store.state.navigationStepSize; + }, + invert() { + return this.$store.state.navigationInvert; + }, baseUri: function() { return this.$store.getters.baseUri; }, @@ -210,22 +192,19 @@ export default { watch: { stepSize: { deep: true, - handler() { - this.setLocalStorageObj("navigation_stepSize", this.stepSize); + handler(newVal) { + this.$store.commit("changeNavigationStepSize", newVal); }, }, invert: { deep: true, - handler() { - this.setLocalStorageObj("navigation_invert", this.invert); + handler(newVal) { + this.$store.commit("changeNavigationInvert", newVal); }, }, }, async mounted() { - // Reload saved settings - this.stepSize = this.getLocalStorageObj("navigation_stepSize") || this.stepSize; - this.invert = this.getLocalStorageObj("navigation_invert") || this.invert; let self = this; // A global signal listener to perform a move action this.$root.$on("globalMoveEvent", self.move); diff --git a/webapp/src/components/tabContentComponents/settingsComponents/streamSettings.vue b/webapp/src/components/tabContentComponents/settingsComponents/streamSettings.vue index 1a84d7ee..aa5fc8e9 100644 --- a/webapp/src/components/tabContentComponents/settingsComponents/streamSettings.vue +++ b/webapp/src/components/tabContentComponents/settingsComponents/streamSettings.vue @@ -32,14 +32,6 @@ export default { }, }, }, - - watch: { - // Cache the stream settings to local storage for persistence - // (the next 3 functions all relate to this) - disableStream: function(newValue) { - this.setLocalStorageObj("disableStream", newValue); - }, - }, }; diff --git a/webapp/src/main.js b/webapp/src/main.js index 13566876..b5a5ce4f 100644 --- a/webapp/src/main.js +++ b/webapp/src/main.js @@ -193,22 +193,6 @@ Vue.mixin({ toggleModalElement: function(element) { UIkit.modal(element).toggle(); }, - - getLocalStorageObj: function(keyName) { - if (localStorage.getItem(keyName)) { - try { - return JSON.parse(localStorage.getItem(keyName)); - } catch (e) { - localStorage.removeItem(keyName); - return null; - } - } - }, - - setLocalStorageObj: function(keyName, object) { - const parsed = JSON.stringify(object); - localStorage.setItem(keyName, parsed); - }, }, }); diff --git a/webapp/src/store.js b/webapp/src/store.js index a0b67189..444a7c1c 100644 --- a/webapp/src/store.js +++ b/webapp/src/store.js @@ -4,8 +4,6 @@ import wotStoreModule from "./wot-client"; Vue.use(Vuex); -const LOCALSTORAGE_KEYS = ["appTheme"]; - function getOriginFromLocation() { // This will default to the same origin that's serving // the web app - but can be overridden by the URL. @@ -49,6 +47,14 @@ function mutationToKey(mutationName) { return key.charAt(0).toLowerCase() + key.slice(1); } +const LOCALSTORAGE_KEYS = [ + "appTheme", + "disableStream", + "overrideOrigin", + "navigationStepSize", + "navigationInvert", +]; + export default new Vuex.Store({ modules: { wot: wotStoreModule, @@ -58,13 +64,29 @@ export default new Vuex.Store({ available: false, waiting: false, error: "", - disableStream: false, autoGpuPreview: false, trackWindow: true, galleryEnabled: true, - appTheme: "system", activeStreams: {}, microscopeHostname: "", + // Persistent items: + // The app theme (e.g. light/dark) + appTheme: "system", + disableStream: false, + // The origin to use if overriding with dev tools + overrideOrigin: "http://microscope.local:5000", + // The step sizes for navigation via control pane/keys presses + navigationStepSize: { + x: 200, + y: 200, + z: 50, + }, + // The axis inversion for navigation via control pane/keys presses + navigationInvert: { + x: false, + y: false, + z: false, + }, }, mutations: { @@ -110,6 +132,15 @@ export default new Vuex.Store({ changeMicroscopeHostname(state, value) { state.microscopeHostname = value; }, + changeOverrideOrigin(state, value) { + state.overrideOrigin = value; + }, + changeNavigationStepSize(state, value) { + state.navigationStepSize = value; + }, + changeNavigationInvert(state, value) { + state.navigationInvert = value; + }, }, actions: {}, @@ -123,10 +154,17 @@ export default new Vuex.Store({ store => { // Load initial state from localStorage LOCALSTORAGE_KEYS.forEach(key => { + console.log(key); const saved = localStorage.getItem(key); if (saved !== null) { - const mutationName = keyToMutationName(key); - store.commit(mutationName, JSON.parse(saved)); + try { + const parsed = JSON.parse(saved); + const mutationName = keyToMutationName(key); + store.commit(mutationName, parsed); + } catch (e) { + console.warn(`Failed to parse localStorage key "${key}":`, e); + localStorage.removeItem(key); + } } });