ui_migration fix(deps): --preliminary-- Replace value to modelvalue, and global emitters. BUG:keys-arrows-wont-move

This commit is contained in:
Antonio Anaya 2026-01-25 02:07:46 -06:00
parent 2a9ee759c4
commit 0296cef089
12 changed files with 165 additions and 62 deletions

View file

@ -11,6 +11,7 @@
"dependencies": { "dependencies": {
"@vitejs/plugin-vue": "^5.0.0", "@vitejs/plugin-vue": "^5.0.0",
"@vue/compat": "^3.2.0", "@vue/compat": "^3.2.0",
"@vueuse/core": "^14.1.0",
"axios": "^1.7.7", "axios": "^1.7.7",
"material-design-icons": "^3.0", "material-design-icons": "^3.0",
"mitt": "^3.0.1", "mitt": "^3.0.1",
@ -1393,6 +1394,12 @@
"undici-types": "~5.26.4" "undici-types": "~5.26.4"
} }
}, },
"node_modules/@types/web-bluetooth": {
"version": "0.0.21",
"resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz",
"integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==",
"license": "MIT"
},
"node_modules/@ungap/structured-clone": { "node_modules/@ungap/structured-clone": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
@ -1592,6 +1599,44 @@
"vue-component-type-helpers": "^2.0.0" "vue-component-type-helpers": "^2.0.0"
} }
}, },
"node_modules/@vueuse/core": {
"version": "14.1.0",
"resolved": "https://registry.npmjs.org/@vueuse/core/-/core-14.1.0.tgz",
"integrity": "sha512-rgBinKs07hAYyPF834mDTigH7BtPqvZ3Pryuzt1SD/lg5wEcWqvwzXXYGEDb2/cP0Sj5zSvHl3WkmMELr5kfWw==",
"license": "MIT",
"dependencies": {
"@types/web-bluetooth": "^0.0.21",
"@vueuse/metadata": "14.1.0",
"@vueuse/shared": "14.1.0"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
},
"peerDependencies": {
"vue": "^3.5.0"
}
},
"node_modules/@vueuse/metadata": {
"version": "14.1.0",
"resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-14.1.0.tgz",
"integrity": "sha512-7hK4g015rWn2PhKcZ99NyT+ZD9sbwm7SGvp7k+k+rKGWnLjS/oQozoIZzWfCewSUeBmnJkIb+CNr7Zc/EyRnnA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/@vueuse/shared": {
"version": "14.1.0",
"resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-14.1.0.tgz",
"integrity": "sha512-EcKxtYvn6gx1F8z9J5/rsg3+lTQnvOruQd8fUecW99DCK04BkWD7z5KQ/wTAx+DazyoEE9dJt/zV8OIEQbM6kw==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/antfu"
},
"peerDependencies": {
"vue": "^3.5.0"
}
},
"node_modules/abbrev": { "node_modules/abbrev": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz",

View file

@ -14,6 +14,7 @@
"dependencies": { "dependencies": {
"@vitejs/plugin-vue": "^5.0.0", "@vitejs/plugin-vue": "^5.0.0",
"@vue/compat": "^3.2.0", "@vue/compat": "^3.2.0",
"@vueuse/core": "^14.1.0",
"axios": "^1.7.7", "axios": "^1.7.7",
"material-design-icons": "^3.0", "material-design-icons": "^3.0",
"mitt": "^3.0.1", "mitt": "^3.0.1",

View file

@ -1,5 +1,11 @@
<template> <template>
<a href="#" class="uk-link" :class="classObject" :uk-tooltip="tooltipOptions" @click="setThisTab"> <a
href="#"
class="uk-link"
:class="classObject"
:uk-tooltip="tooltipOptions ? tooltipOptions : null"
@click="setThisTab"
>
<slot></slot> <slot></slot>
<div v-if="showTitle" class="tabtitle"> <div v-if="showTitle" class="tabtitle">
{{ computedTitle }} {{ computedTitle }}

View file

@ -135,19 +135,19 @@ export default {
watch: { watch: {
progress(newval) { progress(newval) {
eventBus.emit("update:progress", newval); eventBus.emit("beforeUpdate:progress", newval);
}, },
taskStarted(newval) { taskStarted(newval) {
eventBus.emit("update:taskStarted", newval); eventBus.emit("beforeUpdate:taskStarted", newval);
}, },
taskRunning(newval) { taskRunning(newval) {
eventBus.emit("update:taskRunning", newval); eventBus.emit("beforeUpdate:taskRunning", newval);
}, },
log(newval) { log(newval) {
eventBus.emit("update:log", newval); eventBus.emit("beforeUpdate:log", newval);
}, },
taskStatus(newval) { taskStatus(newval) {
eventBus.emit("update:taskStatus", newval); eventBus.emit("beforeUpdate:taskStatus", newval);
}, },
}, },

View file

@ -115,9 +115,10 @@ export default {
type: Object, type: Object,
required: true, required: true,
}, },
value: { modelValue: {
type: null, type: null,
required: true, required: false,
default: undefined,
}, },
label: { label: {
type: String, type: String,
@ -135,11 +136,11 @@ export default {
// the value is an array or object. For future updates we stringify and parse // the value is an array or object. For future updates we stringify and parse
// (see resetInternalValue). If we do this here there is a chance we get errors // (see resetInternalValue). If we do this here there is a chance we get errors
// as internalValue is still null when rendering starts. // as internalValue is still null when rendering starts.
internalValue: Array.isArray(this.value) internalValue: Array.isArray(this.modelValue)
? [...this.value] ? [...this.modelValue]
: typeof this.value === "object" : typeof this.modelValue === "object"
? { ...this.value } ? { ...this.modelValue }
: this.value, : this.modelValue,
// Is edited can't be computed as we mutate internalValue // Is edited can't be computed as we mutate internalValue
isEdited: false, isEdited: false,
animateUpdate: false, animateUpdate: false,
@ -208,14 +209,20 @@ export default {
}, },
watch: { watch: {
value() { modelValue: {
// Fire updateIsEdited on both value and internal value change, handler() {
// as change in value may not causse internalValue to change. // Fire updateIsEdited on both value and internal value change,
this.updateIsEdited(); // as change in value may not causse internalValue to change.
this.resetInternalValue(); this.updateIsEdited();
this.resetInternalValue();
},
deep: true,
}, },
internalValue() { internalValue: {
this.updateIsEdited(); handler() {
this.updateIsEdited();
},
deep: true,
}, },
animate(updated) { animate(updated) {
if (updated) { if (updated) {
@ -225,7 +232,7 @@ export default {
}, },
mounted() { mounted() {
if (this.value !== undefined) { if (this.modelValue !== undefined) {
this.resetInternalValue(); this.resetInternalValue();
} }
}, },
@ -235,7 +242,7 @@ export default {
// Whenever updatirng th internal value stringify and parse as a form of deepcopy. // Whenever updatirng th internal value stringify and parse as a form of deepcopy.
// This ensure that the this.value prop is not mutated for when elements of arrays // This ensure that the this.value prop is not mutated for when elements of arrays
// or objects are updated. // or objects are updated.
this.internalValue = JSON.parse(JSON.stringify(this.value)); this.internalValue = JSON.parse(JSON.stringify(this.modelValue));
}, },
requestUpdate: async function () { requestUpdate: async function () {
this.$emit("requestUpdate"); this.$emit("requestUpdate");
@ -264,7 +271,7 @@ export default {
} }
}, },
updateIsEdited: function () { updateIsEdited: function () {
this.isEdited = this.deepStringify(this.internalValue) !== this.deepStringify(this.value); this.isEdited = this.deepStringify(this.internalValue) !== this.deepStringify(this.modelValue);
}, },
animationEnd: function () { animationEnd: function () {
this.animateUpdate = false; this.animateUpdate = false;

View file

@ -1,6 +1,6 @@
<template> <template>
<input-from-schema <input-from-schema
v-model="value" v-model="modelValue"
:data-schema="propertyDescription" :data-schema="propertyDescription"
:label="label" :label="label"
:animate="animate" :animate="animate"
@ -50,7 +50,7 @@ export default {
data() { data() {
return { return {
value: undefined, modelValue: undefined,
animate: false, animate: false,
}; };
}, },
@ -76,7 +76,7 @@ export default {
// Read the property when we're mounted - usually this won't // Read the property when we're mounted - usually this won't
// work because the URL isn't set yet. However, it's helpful if // work because the URL isn't set yet. However, it's helpful if
// the app is reloaded (e.g. from a dev server). // the app is reloaded (e.g. from a dev server).
if (this.value == undefined) { if (this.modelValue == undefined) {
this.readProperty(); this.readProperty();
} }
}, },
@ -84,12 +84,12 @@ export default {
methods: { methods: {
readProperty: async function () { readProperty: async function () {
let data = await this.readThingProperty(this.thingName, this.propertyName); let data = await this.readThingProperty(this.thingName, this.propertyName);
this.value = data; this.modelValue = data;
return data; return data;
}, },
writeProperty: async function (requestedValue) { writeProperty: async function (requestedValue) {
try { try {
this.value = requestedValue; this.modelValue = requestedValue;
await this.writeThingProperty(this.thingName, this.propertyName, requestedValue); await this.writeThingProperty(this.thingName, this.propertyName, requestedValue);
if (this.readBack) { if (this.readBack) {
await new Promise((r) => setTimeout(r, this.readBackDelay)); await new Promise((r) => setTimeout(r, this.readBackDelay));

View file

@ -66,14 +66,20 @@ export default {
}; };
}, },
async created() {
this.readSettings();
},
methods: { methods: {
async safeReadSettings() {
if (!this.$store.state.connected) return;
try {
await this.readSettings();
} catch (error) {
console.error("Error reading background detector settings:", error);
}
},
visibilityChanged(isVisible) { visibilityChanged(isVisible) {
if (isVisible) { if (isVisible) {
this.readSettings(); this.safeReadSettings();
} }
}, },
alertBackgroundSet() { alertBackgroundSet() {

View file

@ -88,40 +88,49 @@ export default {
}, },
async mounted() { async mounted() {
let self = this;
// A global signal listener to perform a move action // A global signal listener to perform a move action
eventBus.on("globalMoveEvent", self.move); eventBus.on("globalMoveEvent", this.move);
eventBus.on("globalUpdatePositionEvent", self.updatePosition);
eventBus.on("globalUpdatePositionEvent", this.updatePosition);
// A global signal listener to perform a move action in pixels // A global signal listener to perform a move action in pixels
eventBus.on("globalMoveInImageCoordinatesEvent", (x, y, absolute) => {
this.moveInImageCoordinatesRequest(x, y, absolute); this.onMoveImage = (payload) => {
}); const { x, y, absolute } = payload;
this.moveInImageCoordinatesRequest(payload.x, payload.y, payload.absolute);
};
eventBus.on("globalMoveInImageCoordinatesEvent", this.onMoveImage);
// A global signal listener to perform a move in multiples of a step size // A global signal listener to perform a move in multiples of a step size
eventBus.on("globalMoveStepEvent", (x_steps, y_steps, z_steps) => {
this.onMoveStep = (payload) => {
const { x_steps, y_steps, z_steps } = payload;
const navigationStepSize = this.$store.state.navigationStepSize; const navigationStepSize = this.$store.state.navigationStepSize;
const navigationInvert = this.$store.state.navigationInvert; const navigationInvert = this.$store.state.navigationInvert;
const x = x_steps * navigationStepSize.x * (navigationInvert.x ? -1 : 1); const x = x_steps * navigationStepSize.x * (navigationInvert.x ? -1 : 1);
const y = y_steps * navigationStepSize.y * (navigationInvert.y ? -1 : 1); const y = y_steps * navigationStepSize.y * (navigationInvert.y ? -1 : 1);
const z = z_steps * navigationStepSize.z * (navigationInvert.z ? -1 : 1); const z = z_steps * navigationStepSize.z * (navigationInvert.z ? -1 : 1);
eventBus.emit("globalMoveEvent", x, y, z, false); eventBus.emit("globalMoveEvent", {x, y, z, absolute: false});
}); };
eventBus.on("globalMoveStepEvent", this.onMoveStep);
// Update the current position in text boxes // Update the current position in text boxes
await this.updatePosition(); await this.updatePosition();
}, },
beforeUnmount() { beforeUnmount() {
// Remove global signal listener to perform a move action // Remove global signal listener to perform a move action
eventBus.off("globalMoveEvent"); eventBus.off("globalMoveEvent", this.move);
eventBus.off("globalMoveInImageCoordinatesEvent"); eventBus.off("globalMoveInImageCoordinatesEvent", this.onMoveImage);
eventBus.off("globalMoveStepEvent"); eventBus.off("globalMoveStepEvent", this.onMoveStep);
eventBus.off("globalUpdatePositionEvent"); eventBus.off("globalUpdatePositionEvent", this.updatePosition);
}, },
methods: { methods: {
timeout(ms) { timeout(ms) {
return new Promise((resolve) => setTimeout(resolve, ms)); return new Promise((resolve) => setTimeout(resolve, ms));
}, },
async move(x, y, z, absolute) { async move(payload) {
const { x, y, z, absolute } = payload;
// Move the stage, by updating the controls and starting a move task // Move the stage, by updating the controls and starting a move task
// This is equivalent to clicking the "move" button. // This is equivalent to clicking the "move" button.
if (this.moveLock) return; // Discard move requests if we're already moving if (this.moveLock) return; // Discard move requests if we're already moving

View file

@ -37,6 +37,8 @@
</template> </template>
<script> <script>
import { eventBus } from "../../../eventBus.js";
export default { export default {
name: "StageControlButtons", name: "StageControlButtons",
methods: { methods: {

View file

@ -180,13 +180,13 @@ export default {
beforeUnmount() { beforeUnmount() {
// Remove global signal listener to perform a gallery refresh // Remove global signal listener to perform a gallery refresh
eventBus.off("globalUpdateScans"); eventBus.off("globalUpdateScans", this.updateScans);
// Then we call that function here to unwatch // Then we call that function here to unwatch
if (this.unwatchStoreFunction) { if (this.unwatchStoreFunction) {
this.unwatchStoreFunction(); this.unwatchStoreFunction();
this.unwatchStoreFunction = null; this.unwatchStoreFunction = null;
} }
eventBus.off("modalClosed"); // Clean up event listener eventBus.off("modalClosed", this.updateScans); // Clean up event listener
}, },
methods: { methods: {

View file

@ -90,9 +90,9 @@
submit-label="Start Smart Scan" submit-label="Start Smart Scan"
:can-terminate="true" :can-terminate="true"
@taskStarted="startScanning" @taskStarted="startScanning"
@update:taskStatus="taskStatus = $event" @beforeUpdate:taskStatus="taskStatus = $event"
@update:progress="progress = $event" @beforeUpdate:progress="progress = $event"
@update:log="log = $event" @beforeUpdate:log="log = $event"
/> />
</div> </div>
</div> </div>

View file

@ -2,7 +2,6 @@
<div <div
id="stream-display" id="stream-display"
ref="streamDisplay" ref="streamDisplay"
v-observe-visibility="visibilityChanged"
class="stream-display uk-width-1-1 uk-height-1-1 scrollTarget" class="stream-display uk-width-1-1 uk-height-1-1 scrollTarget"
> >
<img <img
@ -37,14 +36,36 @@
<script> <script>
// vue3 migration // vue3 migration
import { eventBus } from "../../eventBus.js"; import { eventBus } from "../../eventBus.js";
import { ref } from "vue";
import { useIntersectionObserver } from "@vueuse/core";
// Export main app // Export main app
export default { export default {
name: "StreamDisplay", name: "StreamDisplay",
setup() {
const streamDisplay = ref(null);
const isVisible = ref(false);
useIntersectionObserver(
streamDisplay,
([{ isIntersecting }]) => {
isVisible.value = isIntersecting;
},
{
threshold: 0.1,
}
);
return {
streamDisplay,
isVisible,
};
},
data: function () { data: function () {
return { return {
isVisible: false,
displaySize: [0, 0], displaySize: [0, 0],
displayPosition: [0, 0], displayPosition: [0, 0],
resizeTimeoutId: setTimeout(this.doneResizing, 500), resizeTimeoutId: setTimeout(this.doneResizing, 500),
@ -66,18 +87,21 @@ export default {
mounted() { mounted() {
// A global signal listener to flash the stream element // A global signal listener to flash the stream element
eventBus.on("globalFlashStream", () => { this.onFlashStream = () => {
this.flashStream(); this.flashStream();
}); };
eventBus.on("globalFlashStream", this.onFlashStream);
// Mutation observer // Mutation observer
this.sizeObserver = new ResizeObserver(() => { this.sizeObserver = new ResizeObserver(() => {
this.handleResize(); // For any element attached to the observer, run handleResize() on change this.handleResize(); // For any element attached to the observer, run handleResize() on change
}); });
// Fetch streamDisplay component by ref // Fetch streamDisplay component by ref
const streamDisplayElement = this.$refs.streamDisplay.parentNode; if (this.$refs.streamDisplay && this.$refs.streamDisplay.parentNode) {
// Attach streamDisplay to the size observer const streamDisplayElement = this.$refs.streamDisplay.parentNode;
this.sizeObserver.observe(streamDisplayElement); // Attach streamDisplay to the size observer
this.sizeObserver.observe(streamDisplayElement);
}
}, },
created: function () { created: function () {
@ -86,9 +110,9 @@ export default {
beforeUnmount: function () { beforeUnmount: function () {
// Remove global signal listener to change the GPU preview state // Remove global signal listener to change the GPU preview state
eventBus.off("globalTogglePreview"); //eventBus.off("globalTogglePreview", true);
// Remove global signal listener to flash the stream element // Remove global signal listener to flash the stream element
eventBus.off("globalFlashStream"); eventBus.off("globalFlashStream", this.onFlashStream);
// Disconnect the size observer // Disconnect the size observer
this.sizeObserver.disconnect(); this.sizeObserver.disconnect();
// Remove from the array of active streams // Remove from the array of active streams
@ -129,7 +153,10 @@ export default {
let yRelative = (0.5 * event.target.offsetHeight - yCoordinate) * scale; let yRelative = (0.5 * event.target.offsetHeight - yCoordinate) * scale;
// Emit a signal to move, acted on by paneControl.vue // Emit a signal to move, acted on by paneControl.vue
eventBus.emit("globalMoveInImageCoordinatesEvent", -xRelative, -yRelative); eventBus.emit("globalMoveInImageCoordinatesEvent", {
x:-xRelative,
y:-yRelative
});
}, },
handleResize: function () { handleResize: function () {