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 { createPinia } from "pinia";
|
||||
import piniaPluginPersistedstate from "pinia-plugin-persistedstate";
|
||||
import App from "./App.vue";
|
||||
import store from "./store";
|
||||
import UIkit from "uikit";
|
||||
|
|
@ -21,14 +23,13 @@ UIkit.mixin(
|
|||
|
||||
// Create Vue app
|
||||
const app = createApp(App);
|
||||
|
||||
// Use visibility observer
|
||||
//app.use(VueObserveVisibility);
|
||||
const pinia = createPinia();
|
||||
pinia.use(piniaPluginPersistedstate);
|
||||
|
||||
// Use global mixins
|
||||
app.mixin(modalMixin);
|
||||
app.mixin(labThingsMixins);
|
||||
|
||||
// Use Vuex store
|
||||
app.use(store);
|
||||
app.use(pinia);
|
||||
app.mount("#app");
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { createStore } from "vuex";
|
||||
import wotStoreModule from "./wot-client";
|
||||
import { defineStore } from "pinia";
|
||||
import { ref, computed } from "vue";
|
||||
|
||||
function getOriginFromLocation() {
|
||||
// This will default to the same origin that's serving
|
||||
|
|
@ -14,156 +14,98 @@ 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)}`;
|
||||
}
|
||||
// Define Pinia store
|
||||
export const useDefaultStore = defineStore(
|
||||
"default",
|
||||
() => {
|
||||
// State
|
||||
const origin = ref(getOriginFromLocation());
|
||||
const available = ref(false);
|
||||
const waiting = ref(false);
|
||||
const error = ref("");
|
||||
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:
|
||||
// The app theme (e.g. light/dark)
|
||||
appTheme: "system",
|
||||
disableStream: false,
|
||||
const appTheme = ref("system");
|
||||
const disableStream = ref(false);
|
||||
// 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
|
||||
navigationStepSize: {
|
||||
const navigationStepSize = ref({
|
||||
x: 200,
|
||||
y: 200,
|
||||
z: 50,
|
||||
},
|
||||
});
|
||||
// The axis inversion for navigation via control pane/keys presses
|
||||
navigationInvert: {
|
||||
const navigationInvert = ref({
|
||||
x: false,
|
||||
y: 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";
|
||||
|
||||
export const wotStoreModule = {
|
||||
namespaced: true,
|
||||
state: () => ({
|
||||
thingDescriptions: {},
|
||||
servient: null,
|
||||
helpers: null,
|
||||
}),
|
||||
mutations: {
|
||||
addThingDescription(state, { thingName, thingDescription }) {
|
||||
state.thingDescriptions[thingName] = thingDescription;
|
||||
},
|
||||
removeThingDescription(state, thingName) {
|
||||
delete state.thingDescriptions[thingName];
|
||||
},
|
||||
removeAllThingDescriptions(state) {
|
||||
state.thingDescriptions = {};
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
async start() {
|
||||
// Set up thing client - not currently used.
|
||||
},
|
||||
async fetchThingDescription({ commit }, { uri, name = null }) {
|
||||
export const useWotStore = defineStore("wot", () => {
|
||||
// State
|
||||
const thingDescriptions = ref({});
|
||||
const servient = ref(null);
|
||||
const helpers = ref(null);
|
||||
// Actions
|
||||
function addThingDescription(thingName, thingDescription) {
|
||||
thingDescriptions.value[thingName] = thingDescription;
|
||||
}
|
||||
|
||||
function removeThingDescription(thingName) {
|
||||
delete thingDescriptions.value[thingName];
|
||||
}
|
||||
|
||||
function removeAllThingDescriptions() {
|
||||
thingDescriptions.value = {};
|
||||
}
|
||||
|
||||
async function fetchThingDescription(uri, name = null) {
|
||||
// Fetch the thing description from the given URI and consume it
|
||||
// NB this should only be called once, or we'll duplicate effort.
|
||||
// Deduplication should be done elsewhere.
|
||||
let response = await axios.get(uri);
|
||||
let td = response.data;
|
||||
let thing_name = name || uri.replace(/\/$/, "").split("/").pop();
|
||||
commit("addThingDescription", {
|
||||
thingName: thing_name,
|
||||
thingDescription: td,
|
||||
});
|
||||
},
|
||||
async fetchThingDescriptions({ commit }, uri) {
|
||||
const response = await axios.get(uri);
|
||||
const td = response.data;
|
||||
const thingName = name || uri.replace(/\/$/, "").split("/").pop();
|
||||
addThingDescription(thingName, td);
|
||||
}
|
||||
|
||||
async function fetchThingDescriptions(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";
|
||||
for (const k in response.data) {
|
||||
let thing_name = k.replace(/\/$/, "").replace(/^\//, "");
|
||||
commit("addThingDescription", {
|
||||
thingName: thing_name,
|
||||
thingDescription: response.data[k],
|
||||
});
|
||||
let thingName = k.replace(/\/$/, "").replace(/^\//, "");
|
||||
addThingDescription(thingName, response.data[k]);
|
||||
}
|
||||
},
|
||||
},
|
||||
getters: {
|
||||
thingDescriptions: (state) => {
|
||||
return state.thingDescriptions;
|
||||
},
|
||||
thingList: (state) => {
|
||||
return Object.keys(state.thingDescriptions);
|
||||
},
|
||||
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) {
|
||||
}
|
||||
|
||||
const thingList = computed(() => Object.keys(thingDescriptions.value));
|
||||
|
||||
const thingAvailable = (thingName) => thingName in thingDescriptions.value;
|
||||
|
||||
const thingAffordanceAvailable = (thing, affordanceType, affordance) => {
|
||||
const td = thingDescriptions.value[thing];
|
||||
if (!td || !td[affordanceType]) {
|
||||
return false;
|
||||
}
|
||||
return affordance in td[affordanceType];
|
||||
},
|
||||
thingFormUrl:
|
||||
(state) =>
|
||||
(thing, affordanceType, affordance, op, allowUndefined = true) => {
|
||||
};
|
||||
|
||||
const thingFormUrl = (thing, affordanceType, affordance, op, allowUndefined = true) => {
|
||||
// Find the URL for a particular operation
|
||||
let td = state.thingDescriptions[thing];
|
||||
const td = thingDescriptions.value[thing];
|
||||
if (!td) {
|
||||
if (allowUndefined) return undefined;
|
||||
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 (allowUndefined) return undefined;
|
||||
|
|
@ -97,17 +80,33 @@ export const wotStoreModule = {
|
|||
return base + href;
|
||||
}
|
||||
return href;
|
||||
},
|
||||
thingPropertyUrl: (_state, getters) => (thing, property, op, allowUndefined) => {
|
||||
};
|
||||
|
||||
const thingPropertyUrl = (thing, property, op, allowUndefined) =>
|
||||
// Find the URL for a particular property
|
||||
return getters.thingFormUrl(thing, "properties", property, op, allowUndefined);
|
||||
},
|
||||
thingActionUrl: (_state, getters) => (thing, action, op, allowUndefined) => {
|
||||
thingFormUrl(thing, "properties", property, op, allowUndefined);
|
||||
|
||||
const thingActionUrl = (thing, action, op, allowUndefined) =>
|
||||
// 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) {
|
||||
// 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;
|
||||
return matchingForm.href;
|
||||
}
|
||||
|
||||
export default wotStoreModule;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue