Merge branch 'local-storage-in-store' into 'v3'
Local storage in store See merge request openflexure/openflexure-microscope-server!430
This commit is contained in:
commit
0b4c90ed9d
7 changed files with 112 additions and 120 deletions
|
|
@ -23,23 +23,15 @@ export default {
|
||||||
|
|
||||||
data: function() {
|
data: function() {
|
||||||
return {
|
return {
|
||||||
newOrigin: this.$store.state.origin,
|
newOrigin: this.$store.state.overrideOrigin,
|
||||||
reloadWhenOverridingOrigin: true,
|
reloadWhenOverridingOrigin: true,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
|
||||||
if (localStorage.overrideOrigin) {
|
|
||||||
this.newOrigin = localStorage.overrideOrigin;
|
|
||||||
} else {
|
|
||||||
this.newOrigin = "http://microscope.local:5000";
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
overrideAPIHost: function(event) {
|
overrideAPIHost: function(event) {
|
||||||
// Save the origin override, so that if we reload the web app, you can easily
|
// 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
|
// 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.
|
// in the store. Otherwise, the form's default action will do the job for us.
|
||||||
|
|
|
||||||
|
|
@ -10,12 +10,7 @@
|
||||||
<div>
|
<div>
|
||||||
<label class="uk-form-label" for="form-stacked-text">x</label>
|
<label class="uk-form-label" for="form-stacked-text">x</label>
|
||||||
<div class="uk-form-controls">
|
<div class="uk-form-controls">
|
||||||
<input
|
<input v-model="stepSize.x" class="uk-input uk-form-small" type="number" />
|
||||||
v-model="stepSize.x"
|
|
||||||
class="uk-input uk-form-small"
|
|
||||||
type="number"
|
|
||||||
name="inputStepXy"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<label class="uk-margin-small-right"
|
<label class="uk-margin-small-right"
|
||||||
><input v-model="invert.x" class="uk-checkbox" type="checkbox" /> Invert x</label
|
><input v-model="invert.x" class="uk-checkbox" type="checkbox" /> Invert x</label
|
||||||
|
|
@ -25,12 +20,7 @@
|
||||||
<div>
|
<div>
|
||||||
<label class="uk-form-label" for="form-stacked-text">y</label>
|
<label class="uk-form-label" for="form-stacked-text">y</label>
|
||||||
<div class="uk-form-controls">
|
<div class="uk-form-controls">
|
||||||
<input
|
<input v-model="stepSize.y" class="uk-input uk-form-small" type="number" />
|
||||||
v-model="stepSize.y"
|
|
||||||
class="uk-input uk-form-small"
|
|
||||||
type="number"
|
|
||||||
name="inputStepY"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<label class="uk-margin-small-right"
|
<label class="uk-margin-small-right"
|
||||||
><input v-model="invert.y" class="uk-checkbox" type="checkbox" /> Invert y</label
|
><input v-model="invert.y" class="uk-checkbox" type="checkbox" /> Invert y</label
|
||||||
|
|
@ -40,12 +30,7 @@
|
||||||
<div>
|
<div>
|
||||||
<label class="uk-form-label" for="form-stacked-text">z</label>
|
<label class="uk-form-label" for="form-stacked-text">z</label>
|
||||||
<div class="uk-form-controls">
|
<div class="uk-form-controls">
|
||||||
<input
|
<input v-model="stepSize.z" class="uk-input uk-form-small" type="number" />
|
||||||
v-model="stepSize.z"
|
|
||||||
class="uk-input uk-form-small"
|
|
||||||
type="number"
|
|
||||||
name="inputStepZz"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -177,18 +162,6 @@ export default {
|
||||||
|
|
||||||
data: function() {
|
data: function() {
|
||||||
return {
|
return {
|
||||||
stepXy: 200,
|
|
||||||
stepZz: 50,
|
|
||||||
stepSize: {
|
|
||||||
x: 200,
|
|
||||||
y: 200,
|
|
||||||
z: 50,
|
|
||||||
},
|
|
||||||
invert: {
|
|
||||||
x: false,
|
|
||||||
y: false,
|
|
||||||
z: false,
|
|
||||||
},
|
|
||||||
setPosition: null,
|
setPosition: null,
|
||||||
isAutofocusing: 0,
|
isAutofocusing: 0,
|
||||||
moveLock: false,
|
moveLock: false,
|
||||||
|
|
@ -196,6 +169,15 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
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() {
|
baseUri: function() {
|
||||||
return this.$store.getters.baseUri;
|
return this.$store.getters.baseUri;
|
||||||
},
|
},
|
||||||
|
|
@ -210,22 +192,19 @@ export default {
|
||||||
watch: {
|
watch: {
|
||||||
stepSize: {
|
stepSize: {
|
||||||
deep: true,
|
deep: true,
|
||||||
handler() {
|
handler(newVal) {
|
||||||
this.setLocalStorageObj("navigation_stepSize", this.stepSize);
|
this.$store.commit("changeNavigationStepSize", newVal);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
invert: {
|
invert: {
|
||||||
deep: true,
|
deep: true,
|
||||||
handler() {
|
handler(newVal) {
|
||||||
this.setLocalStorageObj("navigation_invert", this.invert);
|
this.$store.commit("changeNavigationInvert", newVal);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
async mounted() {
|
async mounted() {
|
||||||
// Reload saved settings
|
|
||||||
this.stepSize = this.getLocalStorageObj("navigation_stepSize") || this.stepSize;
|
|
||||||
this.invert = this.getLocalStorageObj("navigation_invert") || this.invert;
|
|
||||||
let self = this;
|
let self = this;
|
||||||
// A global signal listener to perform a move action
|
// A global signal listener to perform a move action
|
||||||
this.$root.$on("globalMoveEvent", self.move);
|
this.$root.$on("globalMoveEvent", self.move);
|
||||||
|
|
|
||||||
|
|
@ -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: {
|
methods: {
|
||||||
async toggleFullscreen() {
|
async toggleFullscreen() {
|
||||||
if (!document.fullscreenElement) {
|
if (!document.fullscreenElement) {
|
||||||
|
|
|
||||||
|
|
@ -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);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -193,22 +193,6 @@ Vue.mixin({
|
||||||
toggleModalElement: function(element) {
|
toggleModalElement: function(element) {
|
||||||
UIkit.modal(element).toggle();
|
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);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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<Key>`
|
||||||
|
* 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<Key>` 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<Key>` 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({
|
export default new Vuex.Store({
|
||||||
modules: {
|
modules: {
|
||||||
wot: wotStoreModule,
|
wot: wotStoreModule,
|
||||||
|
|
@ -26,13 +64,29 @@ export default new Vuex.Store({
|
||||||
available: false,
|
available: false,
|
||||||
waiting: false,
|
waiting: false,
|
||||||
error: "",
|
error: "",
|
||||||
disableStream: false,
|
|
||||||
autoGpuPreview: false,
|
autoGpuPreview: false,
|
||||||
trackWindow: true,
|
trackWindow: true,
|
||||||
galleryEnabled: true,
|
galleryEnabled: true,
|
||||||
appTheme: "system",
|
|
||||||
activeStreams: {},
|
activeStreams: {},
|
||||||
microscopeHostname: "",
|
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: {
|
mutations: {
|
||||||
|
|
@ -78,6 +132,15 @@ export default new Vuex.Store({
|
||||||
changeMicroscopeHostname(state, value) {
|
changeMicroscopeHostname(state, value) {
|
||||||
state.microscopeHostname = value;
|
state.microscopeHostname = value;
|
||||||
},
|
},
|
||||||
|
changeOverrideOrigin(state, value) {
|
||||||
|
state.overrideOrigin = value;
|
||||||
|
},
|
||||||
|
changeNavigationStepSize(state, value) {
|
||||||
|
state.navigationStepSize = value;
|
||||||
|
},
|
||||||
|
changeNavigationInvert(state, value) {
|
||||||
|
state.navigationInvert = value;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
actions: {},
|
actions: {},
|
||||||
|
|
@ -86,4 +149,33 @@ export default new Vuex.Store({
|
||||||
baseUri: state => state.origin,
|
baseUri: state => state.origin,
|
||||||
ready: state => state.available,
|
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]));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
],
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -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 });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue