Merge branch 'goal_1' into v3

This commit is contained in:
Antonio Anaya 2026-02-13 06:36:37 -06:00
commit f4a10ec897
53 changed files with 2534 additions and 10836 deletions

View file

@ -64,10 +64,13 @@
<script>
import ActionButton from "../../labThingsComponents/actionButton.vue";
// vue3 migration
export default {
name: "StatusPane",
components: { ActionButton },
components: {
ActionButton
},
data: function () {
return {

View file

@ -24,13 +24,14 @@
<script>
import devTools from "./aboutComponents/devTools.vue";
import statusPane from "./aboutComponents/statusPane.vue";
//vue3 migration
export default {
name: "AboutContent",
components: {
devTools,
statusPane,
statusPane
},
};
</script>

View file

@ -1,5 +1,5 @@
<template>
<div v-observe-visibility="visibilityChanged" class="uk-padding-small">
<div ref="backgroundDetectContent" class="uk-padding-small">
<div>
<ul uk-accordion="multiple: true">
<li>
@ -47,11 +47,13 @@
<script>
import ActionButton from "../../labThingsComponents/actionButton.vue";
import ServerSpecifiedPropertyControl from "../../labThingsComponents/serverSpecifiedPropertyControl.vue";
// vue3 migration
import { useIntersectionObserver } from "@vueuse/core";
export default {
components: {
ActionButton,
ServerSpecifiedPropertyControl,
ServerSpecifiedPropertyControl
},
data() {
@ -64,14 +66,20 @@ export default {
};
},
async created() {
this.readSettings();
},
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) {
if (isVisible) {
this.readSettings();
this.safeReadSettings();
}
},
alertBackgroundSet() {
@ -99,6 +107,18 @@ export default {
}
},
},
mounted() {
useIntersectionObserver(
this.$refs.backgroundDetectContent,
([{ isIntersecting }]) => {
this.visibilityChanged(isIntersecting);
},
{
threshold: 0.0,
},
);
},
};
</script>
<style scoped lang="less">

View file

@ -13,13 +13,14 @@
<script>
import paneBackgroundDetect from "./backgroundDetectComponents/paneBackgroundDetect";
import streamDisplay from "./streamContent.vue";
// vue3 migration
export default {
name: "BackgroundDetectContent",
components: {
paneBackgroundDetect,
streamDisplay,
streamDisplay
},
};
</script>

View file

@ -18,12 +18,14 @@
</template>
<script>
import ActionButton from "../../labThingsComponents/actionButton.vue";
// vue3 migration
import { eventBus } from "../../../eventBus.js";
export default {
name: "AutofocusControl",
components: {
ActionButton,
ActionButton
},
data: function () {
@ -38,7 +40,7 @@ export default {
},
afterAutofocus() {
this.isAutofocusing = false;
this.$root.$emit("globalUpdatePositionEvent");
eventBus.emit("globalUpdatePositionEvent");
},
},
};

View file

@ -34,6 +34,7 @@ import axios from "axios";
import ActionButton from "../../labThingsComponents/actionButton.vue";
import positionControl from "./positionControl.vue";
import autofocusControl from "./autofocusControl.vue";
// vue3 migration
export default {
name: "PaneControl",
@ -41,7 +42,7 @@ export default {
components: {
ActionButton,
positionControl,
autofocusControl,
autofocusControl
},
computed: {

View file

@ -15,8 +15,8 @@ and zero position buttons. It also includes the d-pad.
<!-- Text boxes to set and view position -->
<div class="input-and-buttons-container">
<input
v-for="(_v, key) in setPosition"
:key="`setPosition_${key}`"
v-for="(_v, key) in setPosition"
v-model="setPosition[key]"
class="uk-form-small numeric-setting-line-input"
type="number"
@ -61,13 +61,16 @@ and zero position buttons. It also includes the d-pad.
import ActionButton from "../../labThingsComponents/actionButton.vue";
import syncPropertyButton from "../../labThingsComponents/syncPropertyButton.vue";
import stageControlButtons from "./stageControlButtons.vue";
// vue3 migration
import { eventBus } from "../../../eventBus.js";
export default {
name: "PaneControl",
components: {
ActionButton,
syncPropertyButton,
stageControlButtons,
stageControlButtons
},
data: function () {
@ -79,45 +82,57 @@ export default {
computed: {
positionStatusUri: function () {
console.log("Computing position status URI", this.thingActionUrl("stage", "position"));
return this.thingActionUrl("stage", "position");
},
},
async mounted() {
let self = this;
// A global signal listener to perform a move action
this.$root.$on("globalMoveEvent", self.move);
this.$root.$on("globalUpdatePositionEvent", self.updatePosition);
eventBus.on("globalMoveEvent", this.move);
// A global signal listener to update position text boxes
eventBus.on("globalUpdatePositionEvent", this.updatePosition);
// A global signal listener to perform a move action in pixels
this.$root.$on("globalMoveInImageCoordinatesEvent", (x, y, absolute) => {
this.moveInImageCoordinatesRequest(x, y, absolute);
});
eventBus.on("globalMoveInImageCoordinatesEvent", this.onMoveImage);
// A global signal listener to perform a move in multiples of a step size
this.$root.$on("globalMoveStepEvent", (x_steps, y_steps, z_steps) => {
const navigationStepSize = this.$store.state.navigationStepSize;
const navigationInvert = this.$store.state.navigationInvert;
const x = x_steps * navigationStepSize.x * (navigationInvert.x ? -1 : 1);
const y = y_steps * navigationStepSize.y * (navigationInvert.y ? -1 : 1);
const z = z_steps * navigationStepSize.z * (navigationInvert.z ? -1 : 1);
this.$root.$emit("globalMoveEvent", x, y, z, false);
});
eventBus.on("globalMoveStepEvent", this.onMoveStep);
// Update the current position in text boxes
await this.updatePosition();
},
beforeDestroy() {
beforeUnmount() {
// Remove global signal listener to perform a move action
this.$root.$off("globalMoveEvent");
this.$root.$off("globalMoveInImageCoordinatesEvent");
this.$root.$off("globalMoveStepEvent");
this.$root.$off("globalUpdatePositionEvent");
eventBus.off("globalMoveEvent", this.move);
eventBus.off("globalUpdatePositionEvent", this.updatePosition);
eventBus.off("globalMoveInImageCoordinatesEvent", this.onMoveImage);
eventBus.off("globalMoveStepEvent", this.onMoveStep);
},
methods: {
timeout(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
},
async move(x, y, z, absolute) {
onMoveImage(payload) {
console.log("Received move in image coordinates:", payload);
this.moveInImageCoordinatesRequest(payload.x, payload.y, payload.absolute);
},
onMoveStep(payload) {
console.log("Received key move step event:", payload);
const { x: x_steps, y: y_steps, z: z_steps } = payload;
const navigationStepSize = this.$store.state.navigationStepSize;
const navigationInvert = this.$store.state.navigationInvert;
const x = x_steps * navigationStepSize.x * (navigationInvert.x ? -1 : 1);
const y = y_steps * navigationStepSize.y * (navigationInvert.y ? -1 : 1);
const z = z_steps * navigationStepSize.z * (navigationInvert.z ? -1 : 1);
const movePayload = { x, y, z, absolute: false };
eventBus.emit("globalMoveEvent", movePayload);
console.log("Emitted global move event with:", movePayload);
},
async move(payload) {
const { x, y, z, absolute } = payload;
// Move the stage, by updating the controls and starting a move task
// This is equivalent to clicking the "move" button.
if (this.moveLock) return; // Discard move requests if we're already moving
@ -133,15 +148,17 @@ export default {
y: this.setPosition.y + y,
z: this.setPosition.z + z,
};
console.log("Updated setPosition for relative move:", this.setPosition);
}
await this.timeout(1); // Wait for Vue to update the position
await this.$nextTick(); // Wait for Vue to update the position
await this.startMoveTask();
},
async startMoveTask() {
this.moveLock = true;
//this.moveLock = true;
await this.$refs.moveButton.startTask();
},
moveComplete() {
console.log("Move completed.");
this.updatePosition();
this.moveLock = false;
},

View file

@ -37,11 +37,13 @@
</template>
<script>
import { eventBus } from "../../../eventBus.js";
export default {
name: "StageControlButtons",
methods: {
move(x, y, z) {
this.$root.$emit("globalMoveStepEvent", x, y, z);
eventBus.emit("globalMoveStepEvent", {x, y, z, absolute: false});
},
},
};

View file

@ -11,15 +11,16 @@
</template>
<script>
import paneControl from "./controlComponents/paneControl";
import paneControl from "./controlComponents/paneControl.vue";
import streamDisplay from "./streamContent.vue";
// vue3 migration
export default {
name: "ControlContent",
components: {
paneControl,
streamDisplay,
streamDisplay
},
};
</script>

View file

@ -1,5 +1,5 @@
<template>
<div v-observe-visibility="visibilityChanged" class="uk-padding uk-padding-remove-top">
<div ref="loggingDisplay" class="uk-padding uk-padding-remove-top">
<!-- Logging nav bar -->
<nav class="logging-navbar uk-navbar-container uk-navbar-transparent" uk-navbar="mode: click">
<!-- Left side controls -->
@ -83,13 +83,15 @@
import axios from "axios";
import Paginate from "vuejs-paginate";
import EndpointButton from "../labThingsComponents/endpointButton.vue";
//vue3 migration
import { useIntersectionObserver } from "@vueuse/core";
export default {
name: "LoggingContent",
components: {
Paginate,
EndpointButton,
EndpointButton
},
data: function () {
@ -133,6 +135,18 @@ export default {
},
},
mounted() {
useIntersectionObserver(
this.$refs.loggingDisplay,
([{ isIntersecting }]) => {
this.visibilityChanged(isIntersecting);
},
{
threshold: 0.0,
},
);
},
methods: {
scrollToTop() {
this.$emit("scrollTop");

View file

@ -1,11 +1,13 @@
<template>
<div v-observe-visibility="visibilityChanged">
<div ref="osdViewerContainer" class="osd-viewer-container uk-height-1-1">
<div id="openseadragon" ref="osdContainer"></div>
</div>
</template>
<script>
import OpenSeaDragon from "openseadragon";
// vue3 migration
import { useIntersectionObserver } from "@vueuse/core";
export default {
name: "OpenSeadragonViewer",
@ -54,24 +56,43 @@ export default {
},
async mounted() {
useIntersectionObserver(
this.$refs.osdViewerContainer,
([{ isIntersecting }]) => {
this.visibilityChanged(isIntersecting);
},
{ threshold: 0.0 }
);
if (this.src) {
this.loadOpenSeaDragon(this.src);
}
},
beforeDestroy() {
beforeUnmount() {
// Remove global signal listener to perform a gallery refresh
this.osdViewer.destroy();
if (this.osdViewer) {
this.osdViewer.destroy();
}
},
methods: {
visibilityChanged(isVisible) {
// adding this check to avoid error when the viewer is not yet initialized.
if (isVisible) {
this.loadOpenSeaDragon();
if (!this.osdViewer && this.src) {
this.loadOpenSeaDragon(this.src);
}
} else {
this.osdViewer.destroy();
if (this.osdViewer) {
this.osdViewer.destroy();
this.osdViewer = null;
}
}
},
async loadOpenSeaDragon() {
if (this.osdViewer) {
this.osdViewer.destroy();

View file

@ -82,11 +82,15 @@
import axios from "axios";
import actionButton from "../../labThingsComponents/actionButton.vue";
import EndpointButton from "../../labThingsComponents/endpointButton.vue";
// vue3 migration
// Export main app
export default {
name: "ScanCard",
components: { actionButton, EndpointButton },
components: {
actionButton,
EndpointButton
},
props: {
scanData: {

View file

@ -55,10 +55,13 @@
<script>
import UIkit from "uikit";
import OpenSeadragonViewer from "./openSeadragonViewer.vue";
// vue3 migration
export default {
name: "ScanViewerModal",
components: { OpenSeadragonViewer },
components: {
OpenSeadragonViewer
},
props: {
selectedScan: {
type: Object,

View file

@ -1,6 +1,6 @@
<template>
<div
v-observe-visibility="visibilityChanged"
ref="galleryDisplay"
class="galleryDisplay uk-padding uk-padding-remove-top"
>
<!-- Gallery nav bar -->
@ -100,11 +100,18 @@ import axios from "axios";
import actionButton from "../labThingsComponents/actionButton.vue";
import scanCard from "./scanListComponents/scanCard.vue";
import ScanViewerModal from "./scanListComponents/scanViewer.vue";
// vue3 migration
import { eventBus } from "../../eventBus.js";
import { useIntersectionObserver } from "@vueuse/core";
// Export main app
export default {
name: "ScanListContent",
components: { actionButton, scanCard, ScanViewerModal },
components: {
actionButton,
scanCard,
ScanViewerModal
},
data: function () {
return {
@ -141,13 +148,22 @@ export default {
},
async mounted() {
useIntersectionObserver(
this.$refs.galleryDisplay,
([{ isIntersecting }]) => {
this.visibilityChanged(isIntersecting);
},
{
threshold: 0.0, // Adjust as needed
},
);
// Update on mount (does nothing if not connected)
await this.updateScans();
// A global signal listener to perform a gallery refresh
this.$root.$on("globalUpdateScans", () => {
eventBus.on("globalUpdateScans", () => {
this.updateScans();
});
this.$root.$on("modalClosed", () => {
eventBus.on("modalClosed", () => {
// Handle the modal closed event here
this.updateScans();
});
@ -171,15 +187,15 @@ export default {
);
},
beforeDestroy() {
beforeUnmount() {
// Remove global signal listener to perform a gallery refresh
this.$root.$off("globalUpdateScans");
eventBus.off("globalUpdateScans", this.updateScans);
// Then we call that function here to unwatch
if (this.unwatchStoreFunction) {
this.unwatchStoreFunction();
this.unwatchStoreFunction = null;
}
this.$root.$off("modalClosed"); // Clean up event listener
eventBus.off("modalClosed", this.updateScans); // Clean up event listener
},
methods: {

View file

@ -17,6 +17,7 @@
<script>
import CSMCalibrationSettings from "./CSMSettingsComponents/CSMCalibrationSettings.vue";
import miniStreamDisplay from "../../genericComponents/miniStreamDisplay.vue";
// vue3 migration
// Export main app
export default {
@ -24,7 +25,7 @@ export default {
components: {
CSMCalibrationSettings,
miniStreamDisplay,
miniStreamDisplay
},
};
</script>

View file

@ -1,5 +1,5 @@
<template>
<div id="CSMCalibrationSettings" v-observe-visibility="visibilityChanged">
<div id="CSMCalibrationSettings" ref="CSMCalibrationSettingsContainer" class="uk-width-large">
<!--Show auto calibrate if default plugin is enabled-->
<div v-if="'calibrate_xy' in actions" class="uk-margin-small">
<action-button
@ -57,6 +57,8 @@
<script>
import ActionButton from "@/components/labThingsComponents/actionButton.vue";
import matrixDisplay from "@/components/ui/matrixDisplay.vue";
// vue3 migration
import { useIntersectionObserver } from "@vueuse/core";
// Export main app
export default {
@ -64,7 +66,7 @@ export default {
components: {
ActionButton,
matrixDisplay,
matrixDisplay
},
props: {
@ -93,6 +95,16 @@ export default {
},
},
mounted() {
useIntersectionObserver(
this.$refs.CSMCalibrationSettingsContainer,
([{ isIntersecting }]) => {
this.visibilityChanged(isIntersecting);
},
{ threshold: 0.0 }
);
},
methods: {
visibilityChanged(isVisible) {
if (isVisible) {

View file

@ -25,6 +25,7 @@
import cameraCalibrationSettings from "./cameraSettingsComponents/cameraCalibrationSettings.vue";
import miniStreamDisplay from "../../genericComponents/miniStreamDisplay.vue";
import ServerSpecifiedPropertyControl from "../../labThingsComponents/serverSpecifiedPropertyControl.vue";
// vue3 migration
// Export main app
export default {
@ -33,7 +34,7 @@ export default {
components: {
cameraCalibrationSettings,
miniStreamDisplay,
ServerSpecifiedPropertyControl,
ServerSpecifiedPropertyControl
},
data() {

View file

@ -22,13 +22,15 @@
<script>
import ServerSpecifiedActionButton from "../../../labThingsComponents/serverSpecifiedActionButton.vue";
// vue3 migration
import { markRaw } from "vue";
// Export main app
export default {
name: "CameraCalibrationSettings",
components: {
ServerSpecifiedActionButton,
ServerSpecifiedActionButton
},
props: {

View file

@ -15,7 +15,7 @@ export default {
components: {
streamSettings,
appSettings,
appSettings
},
};
</script>

View file

@ -1,5 +1,5 @@
<template>
<div id="stageSettings" v-observe-visibility="visibilityChanged" class="uk-width-large">
<div id="stageSettings" ref="stageSettingsContainer" class="uk-width-large">
The microscope stage is a <b>{{ stageType }}</b>
<div>
<div class="uk-margin">
@ -29,12 +29,14 @@
<script>
import ActionButton from "../../labThingsComponents/actionButton.vue";
// vue3 migration
import { useIntersectionObserver } from "@vueuse/core";
export default {
name: "StageSettings",
components: {
ActionButton,
ActionButton
},
data: function () {
@ -49,6 +51,16 @@ export default {
},
},
mounted() {
useIntersectionObserver(
this.$refs.stageSettingsContainer,
([{ isIntersecting }]) => {
this.visibilityChanged(isIntersecting);
},
{ threshold: 0.0 }
);
},
methods: {
visibilityChanged(isVisible) {
if (isVisible) {

View file

@ -65,8 +65,11 @@ import cameraSettings from "./settingsComponents/cameraSettings.vue";
import CSMSettings from "./settingsComponents/CSMSettings.vue";
import stageSettings from "./settingsComponents/stageSettings.vue";
// Import generic components
import tabIcon from "../genericComponents/tabIcon";
import tabContent from "../genericComponents/tabContent";
import tabIcon from "../genericComponents/tabIcon.vue";
import tabContent from "../genericComponents/tabContent.vue";
// vue3 migration
import { markRaw } from "vue";
// Export main app
export default {
@ -75,7 +78,7 @@ export default {
components: {
tabIcon,
tabContent,
calibrationWizard,
calibrationWizard
},
data: function () {
@ -87,14 +90,14 @@ export default {
id: "display",
title: "Display",
requireConnection: false,
component: displaySettings,
component: markRaw(displaySettings),
requiredThings: [],
},
{
id: "stage-control",
title: "Stage Control Preferences",
requireConnection: false,
component: stageControlSettings,
component: markRaw(stageControlSettings),
requiredThings: ["stage"],
},
],
@ -103,21 +106,21 @@ export default {
id: "camera",
title: "Camera",
requireConnection: true,
component: cameraSettings,
component: markRaw(cameraSettings),
requiredThings: [],
},
{
id: "stage",
title: "Stage",
requireConnection: true,
component: stageSettings,
component: markRaw(stageSettings),
requiredThings: ["stage"],
},
{
id: "mapping",
title: "Camera to Stage Mapping",
requireConnection: true,
component: CSMSettings,
component: markRaw(CSMSettings),
requiredThings: ["camera_stage_mapping"],
},
],

View file

@ -66,9 +66,9 @@
submit-label="Start Smart Scan"
:can-terminate="true"
@taskStarted="startScanning"
@update:taskStatus="taskStatus = $event"
@update:progress="progress = $event"
@update:log="log = $event"
@beforeUpdate:taskStatus="taskStatus = $event"
@beforeUpdate:progress="progress = $event"
@beforeUpdate:log="log = $event"
/>
</div>
</div>
@ -130,6 +130,8 @@ import actionLogDisplay from "../labThingsComponents/actionLogDisplay.vue";
import actionProgressBar from "../labThingsComponents/actionProgressBar.vue";
import MiniStreamDisplay from "../genericComponents/miniStreamDisplay.vue";
import ActionButton from "../labThingsComponents/actionButton.vue";
// vue3 migration
import { useIntersectionObserver } from "@vueuse/core";
export default {
name: "SlideScanContent",
@ -137,11 +139,11 @@ export default {
components: {
streamDisplay,
propertyControl,
ServerSpecifiedPropertyControl,
actionLogDisplay,
actionProgressBar,
MiniStreamDisplay,
ActionButton,
ServerSpecifiedPropertyControl
},
data() {
@ -177,6 +179,18 @@ export default {
this.workflowOptions = await this.readThingProperty("smart_scan", "workflow_display_names");
},
mounted() {
useIntersectionObserver(
this.$refs.slideScanContent,
([{ isIntersecting }]) => {
this.visibilityChanged(isIntersecting);
},
{
threshold: 0.0, // Adjust as needed
},
);
},
methods: {
visibilityChanged(isVisible) {
if (isVisible) {

View file

@ -2,7 +2,6 @@
<div
id="stream-display"
ref="streamDisplay"
v-observe-visibility="visibilityChanged"
class="stream-display uk-width-1-1 uk-height-1-1 scrollTarget"
>
<img
@ -35,6 +34,10 @@
</template>
<script>
// vue3 migration
import { eventBus } from "../../eventBus.js";
import { useIntersectionObserver } from "@vueuse/core";
// Export main app
export default {
name: "StreamDisplay",
@ -47,7 +50,7 @@ export default {
resizeTimeoutId: setTimeout(this.doneResizing, 500),
};
},
computed: {
streamEnabled: function () {
return this.$store.getters.ready && !this.$store.state.disableStream;
@ -62,30 +65,44 @@ export default {
},
mounted() {
//set up an intersection observer
useIntersectionObserver(
this.$refs.streamDisplay,
([{ isIntersecting }]) => {
this.isVisible = isIntersecting;
},
{
threshold: 0.0,
}
);
// A global signal listener to flash the stream element
this.$root.$on("globalFlashStream", () => {
this.onFlashStream = () => {
this.flashStream();
});
};
eventBus.on("globalFlashStream", this.onFlashStream);
// Mutation observer
this.sizeObserver = new ResizeObserver(() => {
this.handleResize(); // For any element attached to the observer, run handleResize() on change
});
// Fetch streamDisplay component by ref
const streamDisplayElement = this.$refs.streamDisplay.parentNode;
// Attach streamDisplay to the size observer
this.sizeObserver.observe(streamDisplayElement);
if (this.$refs.streamDisplay && this.$refs.streamDisplay.parentNode) {
const streamDisplayElement = this.$refs.streamDisplay.parentNode;
// Attach streamDisplay to the size observer
this.sizeObserver.observe(streamDisplayElement);
}
},
created: function () {
// Do nothing: preview stream now runs all the time
},
beforeDestroy: function () {
beforeUnmount: function () {
// Remove global signal listener to change the GPU preview state
this.$root.$off("globalTogglePreview");
//eventBus.off("globalTogglePreview", true);
// Remove global signal listener to flash the stream element
this.$root.$off("globalFlashStream");
eventBus.off("globalFlashStream", this.onFlashStream);
// Disconnect the size observer
this.sizeObserver.disconnect();
// Remove from the array of active streams
@ -93,9 +110,6 @@ export default {
},
methods: {
visibilityChanged(isVisible) {
this.isVisible = isVisible;
},
flashStream: function () {
// Run an animation that flashes the stream (for capture feedback)
let element = this.$refs.streamDisplay;
@ -126,7 +140,10 @@ export default {
let yRelative = (0.5 * event.target.offsetHeight - yCoordinate) * scale;
// Emit a signal to move, acted on by paneControl.vue
this.$root.$emit("globalMoveInImageCoordinatesEvent", -xRelative, -yRelative);
eventBus.emit("globalMoveInImageCoordinatesEvent", {
x:-xRelative,
y:-yRelative
});
},
handleResize: function () {

View file

@ -9,6 +9,8 @@
<script>
import streamDisplay from "./streamContent.vue";
// vue3 migration
import { markRaw } from "vue";
export default {
name: "ViewContent",