ui_migration refactor(store) use pinia stores in components

This commit is contained in:
Antonio Anaya 2026-04-21 23:29:24 -06:00
parent 694a5a690f
commit 3a5a7488dd
25 changed files with 284 additions and 273 deletions

View file

@ -2,7 +2,7 @@
<div>
<form class="uk-form-stacked" action="" method="GET" @submit="overrideAPIHost">
<label class="uk-form-label">Override API origin</label>
<input v-model="newOrigin" name="overrideOrigin" class="uk-input" type="text" />
<input v-model="overrideOrigin" name="overrideOrigin" class="uk-input" type="text" />
<label class="uk-form-label">
<input v-model="reloadWhenOverridingOrigin" class="uk-checkbox" type="checkbox" />
Reload web app with new origin
@ -13,31 +13,30 @@
</template>
<script>
// Export main app
import { mapWritableState } from "pinia";
import { useSettingsStore } from "@/stores/settings.js";
export default {
name: "DevTools",
components: {},
data: function () {
data() {
return {
newOrigin: this.$store.state.overrideOrigin,
reloadWhenOverridingOrigin: true,
};
},
methods: {
overrideAPIHost: function (event) {
// Save the origin override, so that if we reload the web app, you can easily
this.$store.commit("changeOverrideOrigin", this.newOrigin);
computed: {
...mapWritableState(useSettingsStore, ["overrideOrigin", "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.
// TODO: preserve other query parameters when reloading
methods: {
overrideAPIHost(event) {
if (!this.reloadWhenOverridingOrigin) {
this.$store.commit("changeOrigin", this.newOrigin);
this.origin = this.overrideOrigin;
event.preventDefault();
}
// if reloadWhenOverridingOrigin is true, form submits normally
// passing overrideOrigin as a query param in the URL
},
},
};

View file

@ -202,7 +202,7 @@ export default {
},
async checkIfStartedExternally() {
if (!this.taskStarted | !this.taskRunning) {
const ongoingTask = await this.getOngingAction(this.thing, this.action);
const ongoingTask = await this.getOngoingAction(this.thing, this.action);
if (ongoingTask) {
this.resetData();
const taskUrl = ongoingTask.links.find((t) => t.rel == "self").href;

View file

@ -79,6 +79,8 @@
<script>
import { eventBus } from "@/eventBus.js";
import { mapWritableState } from "pinia";
import { useSettingsStore } from "@/stores/settings.js";
export default {
name: "StageControlButtons",
@ -97,6 +99,13 @@ export default {
jogDistance: 600,
jogTime: 300,
}),
computed: {
...mapWritableState(useSettingsStore, ["navigationInvert"]),
},
// TODO: jogInterval may need to be cleared on "beforeUnmount()"
methods: {
/**
* Jog d-pad and focus buttons.
@ -116,11 +125,10 @@ export default {
// pointer is.
pointerEvent.target.setPointerCapture(pointerEvent.pointerId);
const navigationInvert = this.$store.state.navigationInvert;
let invokeJog = () =>
this.invokeAction("stage", "jog", {
x: x * this.jogDistance * (navigationInvert.x ? -1 : 1),
y: y * this.jogDistance * (navigationInvert.y ? -1 : 1),
x: x * this.jogDistance * (this.navigationInvert.x ? -1 : 1),
y: y * this.jogDistance * (this.navigationInvert.y ? -1 : 1),
z: z * this.jogDistance,
});
invokeJog();

View file

@ -92,6 +92,8 @@ import axios from "axios";
import PaginateLinks from "@/components/genericComponents/paginateLinks.vue";
import EndpointButton from "../labThingsComponents/endpointButton.vue";
import { useIntersectionObserver } from "@vueuse/core";
import { useSettingsStore } from "@/stores/settings.js";
import { mapState } from "pinia";
export default {
name: "LoggingContent",
@ -114,6 +116,7 @@ export default {
},
computed: {
...mapState(useSettingsStore, ["baseUri"]),
filteredLevels: function () {
let cutoffIndex = this.allLevels.indexOf(this.filterLevel);
return this.allLevels.slice(cutoffIndex, -1);
@ -130,10 +133,10 @@ export default {
return items;
},
logURI: function () {
return `${this.$store.getters.baseUri}/log/`;
return `${this.baseUri}/log/`;
},
logFileURI: function () {
return `${this.$store.getters.baseUri}/logfile/`;
return `${this.baseUri}/logfile/`;
},
pagedItems: function () {
let startIndex = (this.currentPage - 1) * this.maxitems;

View file

@ -29,6 +29,9 @@
<script>
import axios from "axios";
import { useWotStore } from "@/stores/wot.js";
import { mapActions } from "pinia";
import { useSettingsStore } from "@/stores/settings.js";
export default {
name: "PowerContent",
@ -52,6 +55,8 @@ export default {
},
methods: {
...mapActions(useSettingsStore, ["resetState"]),
...mapActions(useWotStore, ["deleteAllThingDescriptions"]),
systemRequest: function (action) {
let message = "";
if (action == "reboot") {
@ -61,8 +66,8 @@ export default {
}
this.modalConfirm(message).then(
() => {
this.$store.commit("resetState");
this.$store.commit("wot/deleteAllThingDescriptions");
this.resetState();
this.deleteAllThingDescriptions();
// Post and silence errors
axios.post(this.thingActionUrl("system", action)).catch(() => {});
},

View file

@ -82,6 +82,8 @@
import axios from "axios";
import actionButton from "../../labThingsComponents/actionButton.vue";
import EndpointButton from "../../labThingsComponents/endpointButton.vue";
import { mapState } from "pinia";
import { useSettingsStore } from "@/stores/settings.js";
// Export main app
export default {
@ -109,11 +111,12 @@ export default {
emits: ["viewer-requested", "update-requested"],
computed: {
...mapState(useSettingsStore, ["baseUri"]),
downloadStitchFile() {
return `${this.$store.getters.baseUri}/smart_scan/get_stitch/${this.scanData.name}`;
return `${this.baseUri}/smart_scan/get_stitch/${this.scanData.name}`;
},
thumbnailPath() {
return `${this.$store.getters.baseUri}/smart_scan/scans/stitched_thumbnail.jpg?scan_name=${this.scanData.name}&modified=${this.scanData.modified}`;
return `${this.baseUri}/smart_scan/scans/stitched_thumbnail.jpg?scan_name=${this.scanData.name}&modified=${this.scanData.modified}`;
},
},

View file

@ -41,18 +41,10 @@
</div>
</nav>
<ScanViewerModal
ref="scanViewer"
:selected-scan="selectedScan"
:base-uri="$store.getters.baseUri"
/>
<ScanViewerModal ref="scanViewer" :selected-scan="selectedScan" :base-uri="baseUri" />
<!-- Gallery -->
<div
v-if="$store.getters.ready"
class="uk-padding-remove-top"
uk-lightbox="toggle: .lightbox-link"
>
<div v-if="ready" class="uk-padding-remove-top" uk-lightbox="toggle: .lightbox-link">
<!-- Gallery capture cards -->
<div class="gallery-grid uk-grid-match" uk-grid>
<div v-if="scansEmpty">
@ -86,6 +78,9 @@ import scanCard from "./scanListComponents/scanCard.vue";
import ScanViewerModal from "./scanListComponents/scanViewer.vue";
import { eventBus } from "../../eventBus.js";
import { useIntersectionObserver } from "@vueuse/core";
import { useSettingsStore } from "@/stores/settings.js";
import { mapState, storeToRefs } from "pinia";
import { watch } from "vue";
// Export main app
export default {
@ -111,6 +106,7 @@ export default {
},
computed: {
...mapState(useSettingsStore, ["baseUri", "ready"]),
scansUri() {
return this.thingPropertyUrl("smart_scan", "scans");
},
@ -119,7 +115,7 @@ export default {
},
selectedScanDZI() {
if (this.selectedScan && this.selectedScan.dzi != "") {
return `${this.$store.getters.baseUri}/data/smart_scan/${this.selectedScan.name}/images/${this.selectedScan.dzi}`;
return `${this.baseUri}/data/smart_scan/${this.selectedScan.name}/images/${this.selectedScan.dzi}`;
} else {
return null;
}
@ -134,6 +130,15 @@ export default {
},
async mounted() {
const store = useSettingsStore();
const { ready } = storeToRefs(store);
watch(ready, (isReady) => {
if (isReady) {
this.updateScans();
} else {
this.scans = [];
}
});
useIntersectionObserver(
this.$refs.galleryDisplay,
([{ isIntersecting }]) => {
@ -155,24 +160,6 @@ export default {
});
},
created: function () {
// Watch for host 'ready', then update status
this.unwatchStoreFunction = this.$store.watch(
(state, getters) => {
return getters.ready;
},
(ready) => {
if (ready) {
// If the connection is now ready, update capture list
this.updateScans();
} else {
// If the connection is now disconnected, empty capture list
this.captures = {};
}
},
);
},
beforeUnmount() {
// Remove global signal listener to perform a gallery refresh
eventBus.off("globalUpdateScans", this.updateScans);

View file

@ -25,6 +25,8 @@
import cameraCalibrationSettings from "./cameraSettingsComponents/cameraCalibrationSettings.vue";
import miniStreamDisplay from "../../genericComponents/miniStreamDisplay.vue";
import ServerSpecifiedPropertyControl from "../../labThingsComponents/serverSpecifiedPropertyControl.vue";
import { mapState } from "pinia";
import { useSettingsStore } from "@/stores/settings.js";
// Export main app
export default {
@ -43,8 +45,9 @@ export default {
},
computed: {
cameraUri: function () {
return `${this.$store.getters.baseUri}/camera/`;
...mapState(useSettingsStore, ["baseUri"]),
cameraUri() {
return `${this.baseUri}/camera/`;
},
},

View file

@ -19,6 +19,9 @@
</div>
</template>
<script>
import { useSettingsStore } from "@/stores/settings.js";
import { mapWritableState } from "pinia";
// Export main app
export default {
name: "AppSettings",
@ -28,14 +31,7 @@ export default {
},
computed: {
appTheme: {
get() {
return this.$store.state.appTheme;
},
set(value) {
this.$store.commit("changeAppTheme", value);
},
},
...mapWritableState(useSettingsStore, ["appTheme"]),
},
methods: {

View file

@ -12,6 +12,9 @@
</template>
<script>
import { mapState } from "pinia";
import { useSettingsStore } from "@/stores/settings.js";
// Export main app
export default {
name: "StreamSettings",
@ -21,12 +24,13 @@ export default {
},
computed: {
...mapState(useSettingsStore, ["disableStream"]),
disableStream: {
get() {
return this.$store.state.disableStream;
return this.disableStream;
},
set(value) {
this.$store.commit("changeDisableStream", value);
this.disableStream = value;
},
},
},

View file

@ -10,29 +10,27 @@
<div>
<label class="uk-form-label" for="form-stacked-text">x</label>
<div class="uk-form-controls">
<input v-model="stepSize.x" class="uk-input uk-form-small" type="number" />
<input v-model="navigationStepSize.x" class="uk-input uk-form-small" type="number" />
</div>
<label class="uk-margin-small-right">
<input v-model="invert.x" class="uk-checkbox" type="checkbox" />
<input v-model="navigationInvert.x" class="uk-checkbox" type="checkbox" />
Invert x
</label>
</div>
<div>
<label class="uk-form-label" for="form-stacked-text">y</label>
<div class="uk-form-controls">
<input v-model="stepSize.y" class="uk-input uk-form-small" type="number" />
<input v-model="navigationStepSize.y" class="uk-input uk-form-small" type="number" />
</div>
<label class="uk-margin-small-right">
<input v-model="invert.y" class="uk-checkbox" type="checkbox" />
<input v-model="navigationInvert.y" class="uk-checkbox" type="checkbox" />
Invert y
</label>
</div>
<div>
<label class="uk-form-label" for="form-stacked-text">z</label>
<div class="uk-form-controls">
<input v-model="stepSize.z" class="uk-input uk-form-small" type="number" />
<input v-model="navigationStepSize.z" class="uk-input uk-form-small" type="number" />
</div>
</div>
</div>
@ -40,35 +38,16 @@
</template>
<script>
import { storeToRefs } from "pinia";
import { useSettingsStore } from "@/stores/settings.js";
export default {
name: "StageControlSettings",
computed: {
// Note that as stepSize and invert are mutated (i.e. we change stepSize.x not stepSize)
// rather than directly set we cannot use get() and set() computed to interact with the
// store as changed won't be detected by set(). Instead use a deep watcher to
// update the store (see ``watch:`` below)
stepSize() {
return this.$store.state.navigationStepSize;
},
invert() {
return this.$store.state.navigationInvert;
},
},
watch: {
stepSize: {
deep: true,
handler(newVal) {
this.$store.commit("changeNavigationStepSize", newVal);
},
},
invert: {
deep: true,
handler(newVal) {
this.$store.commit("changeNavigationInvert", newVal);
},
},
setup() {
const store = useSettingsStore();
const { navigationStepSize, navigationInvert } = storeToRefs(store);
return { navigationStepSize, navigationInvert };
},
};
</script>

View file

@ -27,6 +27,7 @@
</div>
<div class="uk-margin">
<propertyControl
v-if="thingAvailable('smart_scan')"
thing-name="smart_scan"
property-name="stitch_tiff"
label="When Stitching, Produce a Pyramidal TIFF"
@ -41,6 +42,8 @@
import propertyControl from "@/components/labThingsComponents/propertyControl.vue";
import ServerSpecifiedInterface from "@/components/labThingsComponents/serverSpecifiedInterface.vue";
import { useIntersectionObserver } from "@vueuse/core";
import { mapWritableState } from "pinia";
import { useSettingsStore } from "@/stores/settings.js";
export default {
name: "SlideScanControls",
@ -81,6 +84,7 @@ export default {
},
methods: {
...mapWritableState(useSettingsStore, ["ready"]),
visibilityChanged(isVisible) {
if (isVisible) {
this.readSettings();

View file

@ -61,7 +61,8 @@ import actionTab from "./actionTab.vue";
import slideScanControls from "./slideScanComponents/slideScanControls.vue";
import streamDisplay from "./streamContent.vue";
import ActionButton from "../labThingsComponents/actionButton.vue";
import { mapState } from "pinia";
import { useSettingsStore } from "@/stores/settings.js";
export default {
name: "SlideScanContent",
@ -84,6 +85,7 @@ export default {
},
computed: {
...mapState(useSettingsStore, ["baseUri"]),
scanning() {
return this.taskId && this.taskUrl;
},
@ -138,7 +140,7 @@ export default {
// while the scan is running
let mtime = await this.readThingProperty("smart_scan", "latest_preview_stitch_time", true);
if (mtime !== null) {
this.lastStitchedImage = `${this.$store.getters.baseUri}/smart_scan/latest_preview_stitch.jpg?t=${mtime}`;
this.lastStitchedImage = `${this.baseUri}/smart_scan/latest_preview_stitch.jpg?t=${mtime}`;
}
this.lastScanName = await this.readThingProperty("smart_scan", "latest_scan_name", true);

View file

@ -15,14 +15,11 @@
/>
<div v-if="!streamEnabled" class="uk-height-1-1">
<div v-if="$store.state.waiting" class="uk-position-center">
<div v-if="waiting" class="uk-position-center">
<div uk-spinner="ratio: 4.5"></div>
</div>
<div
v-else-if="$store.state.disableStream"
class="uk-position-center position-relative text-center"
>
<div v-else-if="disableStream" class="uk-position-center position-relative text-center">
Stream preview disabled
</div>
@ -36,11 +33,20 @@
<script>
import { eventBus } from "../../eventBus.js";
import { useIntersectionObserver } from "@vueuse/core";
import { useSettingsStore } from "@/stores/settings.js";
import { mapState, mapWritableState } from "pinia";
// Export main app
export default {
name: "StreamDisplay",
setup() {
const store = useSettingsStore();
return {
store,
};
},
data: function () {
return {
isVisible: false,
@ -51,15 +57,17 @@ export default {
},
computed: {
...mapState(useSettingsStore, ["ready", "baseUri"]),
...mapWritableState(useSettingsStore, ["disableStream", "waiting"]),
streamEnabled: function () {
return this.$store.getters.ready && !this.$store.state.disableStream;
return this.ready && !this.disableStream;
},
thisStreamOpen: function () {
// Only a single MJPEG connection should be open at a time
return !(this.displaySize[0] == 0) && !(this.displaySize[1] == 0);
},
streamImgUri: function () {
return `${this.$store.getters.baseUri}/camera/mjpeg_stream`;
return `${this.baseUri}/camera/mjpeg_stream`;
},
},
@ -101,7 +109,7 @@ export default {
// Disconnect the size observer
this.sizeObserver.disconnect();
// Remove from the array of active streams
this.$store.commit("removeStream", this._uid);
this.store.removeStream(this.$.uid);
},
methods: {
@ -154,12 +162,12 @@ export default {
// Handle closed stream
if (this.displaySize[0] == 0 && this.displaySize[1] == 0) {
// If this stream was previously active
if (this.$store.state.activeStreams[this._uid] == true) {
this.$store.commit("removeStream", this._uid);
if (this.store.activeStreams[this.$.uid] == true) {
this.store.removeStream(this.$.uid);
}
// If resized to anything other than zero
} else {
this.$store.commit("addStream", this._uid);
this.store.addStream(this.$.uid);
}
},