ui_migration refactor(store) migrate vuex to pinia
This commit is contained in:
parent
b580a63675
commit
2701ad0eba
3 changed files with 157 additions and 217 deletions
|
|
@ -1,4 +1,6 @@
|
||||||
import { createApp } from "vue";
|
import { createApp } from "vue";
|
||||||
|
import { createPinia } from "pinia";
|
||||||
|
import piniaPluginPersistedstate from "pinia-plugin-persistedstate";
|
||||||
import App from "./App.vue";
|
import App from "./App.vue";
|
||||||
import store from "./store";
|
import store from "./store";
|
||||||
import UIkit from "uikit";
|
import UIkit from "uikit";
|
||||||
|
|
@ -21,14 +23,13 @@ UIkit.mixin(
|
||||||
|
|
||||||
// Create Vue app
|
// Create Vue app
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
|
const pinia = createPinia();
|
||||||
// Use visibility observer
|
pinia.use(piniaPluginPersistedstate);
|
||||||
//app.use(VueObserveVisibility);
|
|
||||||
|
|
||||||
// Use global mixins
|
// Use global mixins
|
||||||
app.mixin(modalMixin);
|
app.mixin(modalMixin);
|
||||||
app.mixin(labThingsMixins);
|
app.mixin(labThingsMixins);
|
||||||
|
|
||||||
// Use Vuex store
|
// Use Vuex store
|
||||||
app.use(store);
|
app.use(pinia);
|
||||||
app.mount("#app");
|
app.mount("#app");
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { createStore } from "vuex";
|
import { defineStore } from "pinia";
|
||||||
import wotStoreModule from "./wot-client";
|
import { ref, computed } from "vue";
|
||||||
|
|
||||||
function getOriginFromLocation() {
|
function getOriginFromLocation() {
|
||||||
// This will default to the same origin that's serving
|
// This will default to the same origin that's serving
|
||||||
|
|
@ -14,156 +14,98 @@ function getOriginFromLocation() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Define Pinia store
|
||||||
* Converts a Vuex state key (e.g. "appTheme") into a corresponding
|
export const useDefaultStore = defineStore(
|
||||||
* Vuex mutation name (e.g. "changeAppTheme") using the `change<Key>`
|
"default",
|
||||||
* convention.
|
() => {
|
||||||
*
|
// State
|
||||||
* @param {string} key - The Vuex state key to convert.
|
const origin = ref(getOriginFromLocation());
|
||||||
* @returns {string} - The formatted mutation name.
|
const available = ref(false);
|
||||||
*/
|
const waiting = ref(false);
|
||||||
function keyToMutationName(key) {
|
const error = ref("");
|
||||||
return `change${key.charAt(0).toUpperCase() + key.slice(1)}`;
|
const trackWindow = ref(true);
|
||||||
}
|
const activeStreams = ref({});
|
||||||
|
const microscopeHostname = ref("");
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 createStore({
|
|
||||||
modules: {
|
|
||||||
wot: wotStoreModule,
|
|
||||||
},
|
|
||||||
state: {
|
|
||||||
origin: getOriginFromLocation(),
|
|
||||||
available: false,
|
|
||||||
waiting: false,
|
|
||||||
error: "",
|
|
||||||
trackWindow: true,
|
|
||||||
activeStreams: {},
|
|
||||||
microscopeHostname: "",
|
|
||||||
// Persistent items:
|
// Persistent items:
|
||||||
// The app theme (e.g. light/dark)
|
// The app theme (e.g. light/dark)
|
||||||
appTheme: "system",
|
const appTheme = ref("system");
|
||||||
disableStream: false,
|
const disableStream = ref(false);
|
||||||
// The origin to use if overriding with dev tools
|
// The origin to use if overriding with dev tools
|
||||||
overrideOrigin: "http://microscope.local:5000",
|
const overrideOrigin = ref("http://microscope.local:5000");
|
||||||
// The step sizes for navigation via control pane/keys presses
|
// The step sizes for navigation via control pane/keys presses
|
||||||
navigationStepSize: {
|
const navigationStepSize = ref({
|
||||||
x: 200,
|
x: 200,
|
||||||
y: 200,
|
y: 200,
|
||||||
z: 50,
|
z: 50,
|
||||||
},
|
});
|
||||||
// The axis inversion for navigation via control pane/keys presses
|
// The axis inversion for navigation via control pane/keys presses
|
||||||
navigationInvert: {
|
const navigationInvert = ref({
|
||||||
x: false,
|
x: false,
|
||||||
y: false,
|
y: false,
|
||||||
z: false,
|
z: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
function resetState() {
|
||||||
|
waiting.value = false;
|
||||||
|
available.value = false;
|
||||||
|
error.value = ""; // TODO: verify that "" is needed to match initial state
|
||||||
|
}
|
||||||
|
|
||||||
|
function setConnected() {
|
||||||
|
waiting.value = false;
|
||||||
|
available.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addStream(id) {
|
||||||
|
activeStreams.value[id] = true;
|
||||||
|
}
|
||||||
|
function removeStream(id) {
|
||||||
|
activeStreams.value[id] = false;
|
||||||
|
}
|
||||||
|
// TODO: Replace for direct access to state.
|
||||||
|
// Getters
|
||||||
|
const baseUri = computed(() => origin.value);
|
||||||
|
const ready = computed(() => available.value);
|
||||||
|
|
||||||
|
// Export
|
||||||
|
return {
|
||||||
|
//State
|
||||||
|
origin,
|
||||||
|
available,
|
||||||
|
waiting,
|
||||||
|
error,
|
||||||
|
trackWindow,
|
||||||
|
activeStreams,
|
||||||
|
microscopeHostname,
|
||||||
|
appTheme,
|
||||||
|
disableStream,
|
||||||
|
overrideOrigin,
|
||||||
|
navigationStepSize,
|
||||||
|
navigationInvert,
|
||||||
|
|
||||||
|
//Getters
|
||||||
|
baseUri,
|
||||||
|
ready,
|
||||||
|
|
||||||
|
//Actions
|
||||||
|
resetState,
|
||||||
|
setConnected,
|
||||||
|
addStream,
|
||||||
|
removeStream,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// PiniaPluginPersistedState will now automatically persist ONLY these specific refs to localStorage
|
||||||
|
persist: {
|
||||||
|
paths: [
|
||||||
|
"appTheme",
|
||||||
|
"disableStream",
|
||||||
|
"overrideOrigin",
|
||||||
|
"navigationStepSize",
|
||||||
|
"navigationInvert",
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
);
|
||||||
mutations: {
|
|
||||||
changeOrigin(state, origin) {
|
|
||||||
state.origin = origin;
|
|
||||||
},
|
|
||||||
changeWaiting(state, waiting) {
|
|
||||||
state.waiting = waiting;
|
|
||||||
},
|
|
||||||
changeDisableStream(state, disabled) {
|
|
||||||
state.disableStream = disabled;
|
|
||||||
},
|
|
||||||
changeTrackWindow(state, enabled) {
|
|
||||||
state.trackWindow = enabled;
|
|
||||||
},
|
|
||||||
changeAppTheme(state, theme) {
|
|
||||||
state.appTheme = theme;
|
|
||||||
},
|
|
||||||
resetState(state) {
|
|
||||||
state.waiting = false;
|
|
||||||
state.available = false;
|
|
||||||
state.error = null;
|
|
||||||
},
|
|
||||||
setConnected(state) {
|
|
||||||
state.waiting = false;
|
|
||||||
state.available = true;
|
|
||||||
},
|
|
||||||
setErrorMessage(state, msg) {
|
|
||||||
state.error = msg;
|
|
||||||
},
|
|
||||||
addStream(state, id) {
|
|
||||||
state.activeStreams[id] = true;
|
|
||||||
},
|
|
||||||
removeStream(state, id) {
|
|
||||||
state.activeStreams[id] = false;
|
|
||||||
},
|
|
||||||
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: {},
|
|
||||||
|
|
||||||
getters: {
|
|
||||||
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) {
|
|
||||||
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,82 +1,65 @@
|
||||||
|
import { defineStore } from "pinia";
|
||||||
|
import { ref, computed } from "vue";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
|
||||||
export const wotStoreModule = {
|
export const useWotStore = defineStore("wot", () => {
|
||||||
namespaced: true,
|
// State
|
||||||
state: () => ({
|
const thingDescriptions = ref({});
|
||||||
thingDescriptions: {},
|
const servient = ref(null);
|
||||||
servient: null,
|
const helpers = ref(null);
|
||||||
helpers: null,
|
// Actions
|
||||||
}),
|
function addThingDescription(thingName, thingDescription) {
|
||||||
mutations: {
|
thingDescriptions.value[thingName] = thingDescription;
|
||||||
addThingDescription(state, { thingName, thingDescription }) {
|
}
|
||||||
state.thingDescriptions[thingName] = thingDescription;
|
|
||||||
},
|
function removeThingDescription(thingName) {
|
||||||
removeThingDescription(state, thingName) {
|
delete thingDescriptions.value[thingName];
|
||||||
delete state.thingDescriptions[thingName];
|
}
|
||||||
},
|
|
||||||
removeAllThingDescriptions(state) {
|
function removeAllThingDescriptions() {
|
||||||
state.thingDescriptions = {};
|
thingDescriptions.value = {};
|
||||||
},
|
}
|
||||||
},
|
|
||||||
actions: {
|
async function fetchThingDescription(uri, name = null) {
|
||||||
async start() {
|
|
||||||
// Set up thing client - not currently used.
|
|
||||||
},
|
|
||||||
async fetchThingDescription({ commit }, { uri, name = null }) {
|
|
||||||
// Fetch the thing description from the given URI and consume it
|
// Fetch the thing description from the given URI and consume it
|
||||||
// NB this should only be called once, or we'll duplicate effort.
|
// NB this should only be called once, or we'll duplicate effort.
|
||||||
// Deduplication should be done elsewhere.
|
// Deduplication should be done elsewhere.
|
||||||
let response = await axios.get(uri);
|
const response = await axios.get(uri);
|
||||||
let td = response.data;
|
const td = response.data;
|
||||||
let thing_name = name || uri.replace(/\/$/, "").split("/").pop();
|
const thingName = name || uri.replace(/\/$/, "").split("/").pop();
|
||||||
commit("addThingDescription", {
|
addThingDescription(thingName, td);
|
||||||
thingName: thing_name,
|
}
|
||||||
thingDescription: td,
|
|
||||||
});
|
async function fetchThingDescriptions(uri) {
|
||||||
},
|
|
||||||
async fetchThingDescriptions({ commit }, uri) {
|
|
||||||
// Fetch thing descriptions from the given URI
|
// Fetch thing descriptions from the given URI
|
||||||
let response = await axios.get(uri);
|
const response = await axios.get(uri);
|
||||||
if (response.status !== 200) throw "Could not retrieve thing descriptions";
|
if (response.status !== 200) throw "Could not retrieve thing descriptions";
|
||||||
for (const k in response.data) {
|
for (const k in response.data) {
|
||||||
let thing_name = k.replace(/\/$/, "").replace(/^\//, "");
|
let thingName = k.replace(/\/$/, "").replace(/^\//, "");
|
||||||
commit("addThingDescription", {
|
addThingDescription(thingName, response.data[k]);
|
||||||
thingName: thing_name,
|
|
||||||
thingDescription: response.data[k],
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
},
|
|
||||||
getters: {
|
const thingList = computed(() => Object.keys(thingDescriptions.value));
|
||||||
thingDescriptions: (state) => {
|
|
||||||
return state.thingDescriptions;
|
const thingAvailable = (thingName) => thingName in thingDescriptions.value;
|
||||||
},
|
|
||||||
thingList: (state) => {
|
const thingAffordanceAvailable = (thing, affordanceType, affordance) => {
|
||||||
return Object.keys(state.thingDescriptions);
|
const td = thingDescriptions.value[thing];
|
||||||
},
|
if (!td || !td[affordanceType]) {
|
||||||
thingDescription: (state) => (thingName) => {
|
|
||||||
return state.thingDescriptions[thingName];
|
|
||||||
},
|
|
||||||
thingAvailable: (state) => (thingName) => {
|
|
||||||
return thingName in state.thingDescriptions;
|
|
||||||
},
|
|
||||||
thingAffordanceAvailable: (state) => (thing, affordanceType, affordance) => {
|
|
||||||
let td = state.thingDescriptions[thing];
|
|
||||||
if (!td) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return affordance in td[affordanceType];
|
return affordance in td[affordanceType];
|
||||||
},
|
};
|
||||||
thingFormUrl:
|
|
||||||
(state) =>
|
const thingFormUrl = (thing, affordanceType, affordance, op, allowUndefined = true) => {
|
||||||
(thing, affordanceType, affordance, op, allowUndefined = true) => {
|
|
||||||
// Find the URL for a particular operation
|
// Find the URL for a particular operation
|
||||||
let td = state.thingDescriptions[thing];
|
const td = thingDescriptions.value[thing];
|
||||||
if (!td) {
|
if (!td) {
|
||||||
if (allowUndefined) return undefined;
|
if (allowUndefined) return undefined;
|
||||||
throw `Could not find form for ${affordanceType} ${thing}/${affordance} with op ${op}`;
|
throw `Could not find form for ${affordanceType} ${thing}/${affordance} with op ${op}`;
|
||||||
}
|
}
|
||||||
let affordances = td[affordanceType];
|
const affordances = td[affordanceType];
|
||||||
|
|
||||||
if (!affordances || !(affordance in affordances)) {
|
if (!affordances || !(affordance in affordances)) {
|
||||||
if (allowUndefined) return undefined;
|
if (allowUndefined) return undefined;
|
||||||
|
|
@ -97,17 +80,33 @@ export const wotStoreModule = {
|
||||||
return base + href;
|
return base + href;
|
||||||
}
|
}
|
||||||
return href;
|
return href;
|
||||||
},
|
};
|
||||||
thingPropertyUrl: (_state, getters) => (thing, property, op, allowUndefined) => {
|
|
||||||
|
const thingPropertyUrl = (thing, property, op, allowUndefined) =>
|
||||||
// Find the URL for a particular property
|
// Find the URL for a particular property
|
||||||
return getters.thingFormUrl(thing, "properties", property, op, allowUndefined);
|
thingFormUrl(thing, "properties", property, op, allowUndefined);
|
||||||
},
|
|
||||||
thingActionUrl: (_state, getters) => (thing, action, op, allowUndefined) => {
|
const thingActionUrl = (thing, action, op, allowUndefined) =>
|
||||||
// Find the URL for a particular action
|
// Find the URL for a particular action
|
||||||
return getters.thingFormUrl(thing, "actions", action, op, allowUndefined);
|
thingFormUrl(thing, "actions", action, op, allowUndefined);
|
||||||
},
|
|
||||||
},
|
return {
|
||||||
|
thingDescriptions,
|
||||||
|
servient,
|
||||||
|
helpers, // State
|
||||||
|
thingList,
|
||||||
|
thingAvailable, // Helpers
|
||||||
|
thingFormUrl,
|
||||||
|
thingPropertyUrl,
|
||||||
|
thingActionUrl,
|
||||||
|
fetchThingDescription,
|
||||||
|
fetchThingDescriptions, // Actions
|
||||||
|
addThingDescription,
|
||||||
|
removeThingDescription,
|
||||||
|
removeAllThingDescriptions,
|
||||||
|
thingAffordanceAvailable,
|
||||||
};
|
};
|
||||||
|
});
|
||||||
|
|
||||||
export function findFormHref(affordance, op) {
|
export function findFormHref(affordance, op) {
|
||||||
// Find the form in the affordance that matches the given operation type
|
// Find the form in the affordance that matches the given operation type
|
||||||
|
|
@ -117,5 +116,3 @@ export function findFormHref(affordance, op) {
|
||||||
if (matchingForm === undefined) return undefined;
|
if (matchingForm === undefined) return undefined;
|
||||||
return matchingForm.href;
|
return matchingForm.href;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default wotStoreModule;
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue