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/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/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 4bcf61fc..444a7c1c 100644
--- a/webapp/src/store.js
+++ b/webapp/src/store.js
@@ -17,6 +17,44 @@ 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);
+}
+
+const LOCALSTORAGE_KEYS = [
+ "appTheme",
+ "disableStream",
+ "overrideOrigin",
+ "navigationStepSize",
+ "navigationInvert",
+];
+
export default new Vuex.Store({
modules: {
wot: wotStoreModule,
@@ -26,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: {
@@ -78,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: {},
@@ -86,4 +149,33 @@ export default new Vuex.Store({
baseUri: state => state.origin,
ready: state => state.available,
},
+
+ plugins: [
+ store => {
+ // Load initial state from localStorage
+ LOCALSTORAGE_KEYS.forEach(key => {
+ console.log(key);
+ const saved = localStorage.getItem(key);
+ if (saved !== null) {
+ 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);
+ }
+ }
+ });
+
+ // 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]));
+ }
+ });
+ },
+ ],
});
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 });
- }
-}