Merge branch 'action-tab-2' into 'v3'
Split up slide scan tab and create a generic action tab (attempt 2) See merge request openflexure/openflexure-microscope-server!536
This commit is contained in:
commit
65ac551688
5 changed files with 465 additions and 205 deletions
|
|
@ -50,6 +50,18 @@ export default {
|
|||
type: String,
|
||||
required: true,
|
||||
},
|
||||
// forceId and forceUrl can be used if it is known that when this button is mounted
|
||||
// the action should be running.
|
||||
forceId: {
|
||||
type: [String, null],
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
forceUrl: {
|
||||
type: [String, null],
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
submitData: {
|
||||
type: [Object, Array],
|
||||
required: false,
|
||||
|
|
@ -197,6 +209,10 @@ export default {
|
|||
},
|
||||
);
|
||||
|
||||
if (this.forceId && this.forceUrl) {
|
||||
this.taskStarted = true;
|
||||
this.startPollingTask(this.forceId, this.forceUrl);
|
||||
} else {
|
||||
// Check for already running tasks
|
||||
if (this.taskStarted != true) {
|
||||
this.checkExistingTasks();
|
||||
|
|
@ -209,6 +225,7 @@ export default {
|
|||
this.bootstrapTask();
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
beforeUnmount() {
|
||||
|
|
@ -240,18 +257,13 @@ export default {
|
|||
*
|
||||
*/
|
||||
async checkExistingTasks() {
|
||||
let response = await this.findOngoingActions(this.thing, this.action);
|
||||
// Exit if response is null, due to an error.
|
||||
if (response == null) return;
|
||||
// Check for a task that is ongoing.
|
||||
// We can't handle multiple tasks ongoing, so this picks the first.
|
||||
const ongoingTask = response.data.find((t) => ["pending", "running"].includes(t.status));
|
||||
const ongoingTask = await this.getOngingAction(this.thing, this.action);
|
||||
if (ongoingTask) {
|
||||
// There is a started task
|
||||
this.taskStarted = true;
|
||||
this.$emit("taskStarted");
|
||||
// Find its URL
|
||||
const taskUrl = ongoingTask.links.find((t) => t.rel == "self").href;
|
||||
this.$emit("taskStarted", ongoingTask.id, taskUrl);
|
||||
this.startPollingTask(ongoingTask.id, taskUrl);
|
||||
}
|
||||
},
|
||||
|
|
@ -275,7 +287,6 @@ export default {
|
|||
this.$emit("submit", this.submitData);
|
||||
// Send a request to start a task
|
||||
this.taskStarted = true;
|
||||
this.$emit("taskStarted");
|
||||
let response;
|
||||
try {
|
||||
response = await this.invokeAction(
|
||||
|
|
@ -289,6 +300,9 @@ export default {
|
|||
this.onTaskEnd();
|
||||
return;
|
||||
}
|
||||
// Wait until after the task is acknowledged before confirming to parent
|
||||
// component that the task is started.
|
||||
this.$emit("taskStarted", response.data.id, response.data.href);
|
||||
if (this.modalProgress) {
|
||||
this.$refs.statusModal.show();
|
||||
}
|
||||
|
|
|
|||
224
webapp/src/components/tabContentComponents/actionTab.vue
Normal file
224
webapp/src/components/tabContentComponents/actionTab.vue
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
<!-- A tab that changes its sidebar when an action is started.
|
||||
|
||||
## Overview
|
||||
|
||||
This is a generic component used to build tabs that need to switch view when an action is started,
|
||||
such as the Slide Scan tab.
|
||||
|
||||
The task runs in 3 modes:
|
||||
|
||||
* **Unstarted** - No task has been started.
|
||||
* **Running** - The task is running.
|
||||
* **Completed** - The task has completed.
|
||||
|
||||
## HTML Slots and Template
|
||||
|
||||
The template has 3 slots.
|
||||
|
||||
* The main slot is the main view on the right hand side of the tab. To display the microscope stream
|
||||
use `<streamDisplay />` here
|
||||
* **controls** - A named slot for the controls that are shown in the sidebar in **Unstarted** mode.
|
||||
* **task-info** - A slot for any information or controls to show at the bottom of the sidebar in
|
||||
**Running** or **Completed** mode.
|
||||
|
||||
In **Running** or **Completed** modes the sidebar automatically shows:
|
||||
|
||||
* **A tile** if `taskInfoTitle` prop is set.
|
||||
* **A mini stream** if `taskInfoStream` prop is `true`
|
||||
* **A log view**
|
||||
* **A cancel button** or a **Close** button depending on if the mode is **Running** or **Completed**
|
||||
|
||||
## Changing mode
|
||||
|
||||
To notify the tab that the action running the `taskId` and `taskUrl` props should be set. These values
|
||||
are the data send from the `taskStarted` event of `actionButton`.
|
||||
|
||||
**Note:** This component will check whether the task has been started externally (for example, by
|
||||
another window). A `actionStartedExternally` event with `taskId` and `taskUrl` data will be emitted in
|
||||
this case. The tab does not mutate its own props so it the parent component should subscribe to the
|
||||
event and set the props accordingly.
|
||||
|
||||
When the task completes a `completed` event is emitted. No action is needed on this event, it is emitted
|
||||
so that the parent component can update any other data or UI elements if needed.
|
||||
|
||||
When the close button is clicked a `closeTask` event is emitted. It is the responsibility of the parent
|
||||
component to set the `taskId` and `taskUrl` props back to `null`, returning the tab to **Unstarted**
|
||||
mode.
|
||||
-->
|
||||
<template>
|
||||
<div ref="actionTab" uk-grid class="uk-height-1-1 uk-margin-remove uk-padding-remove">
|
||||
<div class="control-component uk-padding-small">
|
||||
<div v-if="!taskStarted" class="uk-padding-small">
|
||||
<slot name="controls"></slot>
|
||||
</div>
|
||||
<div v-if="taskStarted">
|
||||
<h2 v-if="taskInfoTitle" style="text-align: center">{{ taskInfoTitle }}</h2>
|
||||
<mini-stream-display v-if="taskInfoStream" />
|
||||
<action-log-display id="log-display" :log="log" :task-status="taskStatus" />
|
||||
<div class="uk-width-1-1 uk-flex uk-flex-center">
|
||||
<div class="uk-width-2-3">
|
||||
<action-button
|
||||
v-if="taskRunning"
|
||||
:thing="thing"
|
||||
:action="action"
|
||||
:force-id="taskId"
|
||||
:force-url="taskUrl"
|
||||
submit-label=""
|
||||
:can-terminate="true"
|
||||
@completed="$emit('completed')"
|
||||
@update:task-status="taskStatus = $event"
|
||||
@update:progress="progress = $event"
|
||||
@update:log="log = $event"
|
||||
/>
|
||||
<button
|
||||
v-if="!taskRunning"
|
||||
type="button"
|
||||
class="uk-button uk-width-1-1 uk-position-relative"
|
||||
@click="closeTask"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<slot name="task-info"></slot>
|
||||
</div>
|
||||
</div>
|
||||
<div class="main-view uk-width-expand uk-height-1-1">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import actionLogDisplay from "../labThingsComponents/actionLogDisplay.vue";
|
||||
import MiniStreamDisplay from "../genericComponents/miniStreamDisplay.vue";
|
||||
import ActionButton from "../labThingsComponents/actionButton.vue";
|
||||
import { useIntersectionObserver } from "@vueuse/core";
|
||||
|
||||
export default {
|
||||
name: "ActionTab",
|
||||
|
||||
components: {
|
||||
actionLogDisplay,
|
||||
MiniStreamDisplay,
|
||||
ActionButton,
|
||||
},
|
||||
|
||||
props: {
|
||||
action: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
thing: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
taskId: {
|
||||
type: [String, null],
|
||||
required: true,
|
||||
},
|
||||
taskUrl: {
|
||||
type: [String, null],
|
||||
required: true,
|
||||
},
|
||||
taskInfoTitle: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
taskInfoStream: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
emits: ["closeTask", "completed", "actionStartedExternally"],
|
||||
|
||||
data() {
|
||||
return {
|
||||
taskStatus: "pending",
|
||||
progress: null,
|
||||
log: [],
|
||||
pollTimer: null,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
taskStarted() {
|
||||
return this.taskId && this.taskUrl;
|
||||
},
|
||||
taskRunning() {
|
||||
return (this.taskStatus == "running") | (this.taskStatus == "pending");
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
useIntersectionObserver(
|
||||
this.$refs.actionTab,
|
||||
([{ isIntersecting }]) => {
|
||||
this.visibilityChanged(isIntersecting);
|
||||
},
|
||||
{
|
||||
threshold: 0.0, // Adjust as needed
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* Reset the data needed for actions to be polled.
|
||||
*/
|
||||
resetData() {
|
||||
this.taskStatus = "pending";
|
||||
this.log = [];
|
||||
},
|
||||
closeTask() {
|
||||
// Reset task status to pending so that it is re-read next time an action starts.
|
||||
this.resetData();
|
||||
this.$emit("closeTask");
|
||||
},
|
||||
visibilityChanged(isVisible) {
|
||||
this.isVisible = isVisible;
|
||||
|
||||
if (this.isVisible) {
|
||||
this.startTimer();
|
||||
} else {
|
||||
this.stopTimer();
|
||||
}
|
||||
},
|
||||
startTimer() {
|
||||
if (this.pollTimer) return;
|
||||
|
||||
this.pollTimer = setInterval(() => {
|
||||
this.checkIfStartedExternally();
|
||||
}, 1000);
|
||||
},
|
||||
stopTimer() {
|
||||
if (!this.pollTimer) return;
|
||||
|
||||
clearInterval(this.pollTimer);
|
||||
this.pollTimer = null;
|
||||
},
|
||||
async checkIfStartedExternally() {
|
||||
if (!this.taskStarted | !this.taskRunning) {
|
||||
const ongoingTask = await this.getOngingAction(this.thing, this.action);
|
||||
if (ongoingTask) {
|
||||
this.resetData();
|
||||
const taskUrl = ongoingTask.links.find((t) => t.rel == "self").href;
|
||||
this.$emit("actionStartedExternally", ongoingTask.id, taskUrl);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.control-component {
|
||||
width: 33%;
|
||||
}
|
||||
#log-display {
|
||||
height: 20em;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
<template>
|
||||
<!-- Workflow Selection Dropdown -->
|
||||
<div ref="slideScanControls" class="uk-margin">
|
||||
<label class="uk-form-label">Workflow</label>
|
||||
|
||||
<select
|
||||
class="uk-select uk-form-small"
|
||||
:value="workflowName"
|
||||
@change="setWorkflow($event.target.value)"
|
||||
>
|
||||
<option v-for="(label, name) in workflowOptions" :key="name" :value="name">
|
||||
{{ label }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<server-specified-interface :elements="workflowSettings" @request-update="readSettings" />
|
||||
<ul uk-accordion="multiple: true">
|
||||
<li class="uk-open">
|
||||
<a class="uk-accordion-title" href="#">Stitching Settings</a>
|
||||
<div class="uk-accordion-content">
|
||||
<div class="uk-margin">
|
||||
<propertyControl
|
||||
thing-name="smart_scan"
|
||||
property-name="stitch_automatically"
|
||||
label="Automatically Stitch Images Together"
|
||||
/>
|
||||
</div>
|
||||
<div class="uk-margin">
|
||||
<propertyControl
|
||||
thing-name="smart_scan"
|
||||
property-name="stitch_tiff"
|
||||
label="When Stitching, Produce a Pyramidal TIFF"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import propertyControl from "@/components/labThingsComponents/propertyControl.vue";
|
||||
import ServerSpecifiedInterface from "@/components/labThingsComponents/serverSpecifiedInterface.vue";
|
||||
import { useIntersectionObserver } from "@vueuse/core";
|
||||
|
||||
export default {
|
||||
name: "SlideScanControls",
|
||||
|
||||
components: {
|
||||
propertyControl,
|
||||
ServerSpecifiedInterface,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
workflowName: undefined,
|
||||
workflowSettings: [],
|
||||
workflowOptions: [],
|
||||
};
|
||||
},
|
||||
|
||||
async created() {
|
||||
this.readSettings();
|
||||
this.workflowOptions = await this.readThingProperty(
|
||||
"smart_scan",
|
||||
"workflow_display_names",
|
||||
true,
|
||||
);
|
||||
},
|
||||
|
||||
mounted() {
|
||||
// sets visibilityChanged to true or false, which can then update settings if needed
|
||||
useIntersectionObserver(
|
||||
this.$refs.slideScanControls,
|
||||
([{ isIntersecting }]) => {
|
||||
this.visibilityChanged(isIntersecting);
|
||||
},
|
||||
{
|
||||
threshold: 0.0,
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
methods: {
|
||||
visibilityChanged(isVisible) {
|
||||
if (isVisible) {
|
||||
this.readSettings();
|
||||
}
|
||||
},
|
||||
async readSettings() {
|
||||
this.workflowName = await this.readThingProperty("smart_scan", "workflow_name");
|
||||
|
||||
if (!this.workflowName) {
|
||||
console.warn("Could not read workflow_name, using default");
|
||||
this.workflowName = "histo_scan_workflow";
|
||||
}
|
||||
|
||||
if (this.workflowName) {
|
||||
this.ready = await this.readThingProperty(this.workflowName, "ready", true);
|
||||
this.workflowSettings =
|
||||
(await this.getThingEndpoint(this.workflowName, "settings_ui")) || [];
|
||||
}
|
||||
},
|
||||
async setWorkflow(name) {
|
||||
try {
|
||||
this.workflowName = name;
|
||||
|
||||
await this.writeThingProperty("smart_scan", "workflow_name", name);
|
||||
|
||||
// refresh UI
|
||||
await this.readSettings();
|
||||
} catch (err) {
|
||||
this.modalError(err);
|
||||
|
||||
// revert if server rejected
|
||||
this.workflowName = await this.readThingProperty("smart_scan", "workflow_name", true);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -1,43 +1,25 @@
|
|||
<template>
|
||||
<div uk-grid class="uk-height-1-1 uk-margin-remove uk-padding-remove">
|
||||
<div class="control-component uk-padding-small">
|
||||
<div v-show="!scanning" ref="slideScanView" class="uk-padding-small">
|
||||
<!-- Workflow Selection Dropdown -->
|
||||
<div class="uk-margin">
|
||||
<label class="uk-form-label">Workflow</label>
|
||||
|
||||
<select
|
||||
class="uk-select uk-form-small"
|
||||
:value="workflowName"
|
||||
@change="setWorkflow($event.target.value)"
|
||||
<actionTab
|
||||
thing="smart_scan"
|
||||
action="sample_scan"
|
||||
:task-id="taskId"
|
||||
:task-url="taskUrl"
|
||||
:task-info-title="infoPaneTitle"
|
||||
:task-info-stream="displayImageOnRight"
|
||||
@completed="onScanCompleted"
|
||||
@close-task="closeTask"
|
||||
@action-started-externally="startScanning"
|
||||
>
|
||||
<option v-for="(label, name) in workflowOptions" :key="name" :value="name">
|
||||
{{ label }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<server-specified-interface :elements="workflowSettings" @request-update="readSettings" />
|
||||
<ul uk-accordion="multiple: true">
|
||||
<li class="uk-open">
|
||||
<a class="uk-accordion-title" href="#">Stitching Settings</a>
|
||||
<div class="uk-accordion-content">
|
||||
<div class="uk-margin">
|
||||
<propertyControl
|
||||
thing-name="smart_scan"
|
||||
property-name="stitch_automatically"
|
||||
label="Automatically Stitch Images Together"
|
||||
<!-- MainView -->
|
||||
<img
|
||||
v-if="displayImageOnRight"
|
||||
id="last-stitched-image"
|
||||
class="image-fit"
|
||||
:src="lastStitchedImage"
|
||||
/>
|
||||
</div>
|
||||
<div class="uk-margin">
|
||||
<propertyControl
|
||||
thing-name="smart_scan"
|
||||
property-name="stitch_tiff"
|
||||
label="When Stitching, Produce a Pyramidal TIFF"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<streamDisplay v-else />
|
||||
<template #controls>
|
||||
<slideScanControls />
|
||||
<label class="uk-form-label" for="form-stacked-text">Sample ID</label>
|
||||
<div class="uk-form-controls">
|
||||
<input v-model="scan_name" class="uk-input uk-form-small" type="text" name="Scan Name" />
|
||||
|
|
@ -49,39 +31,15 @@
|
|||
action="sample_scan"
|
||||
:submit-data="{ scan_name: scan_name }"
|
||||
submit-label="Start Smart Scan"
|
||||
:can-terminate="true"
|
||||
@task-started="startScanning"
|
||||
@update:task-status="taskStatus = $event"
|
||||
@update:progress="progress = $event"
|
||||
@update:log="log = $event"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-show="scanning">
|
||||
<h2 v-if="displayImageOnRight" style="text-align: center">Live stitching preview</h2>
|
||||
<mini-stream-display v-if="displayImageOnRight" />
|
||||
<action-log-display id="log-display" :log="log" :task-status="taskStatus" />
|
||||
<action-progress-bar :progress="progress" :task-status="taskStatus" />
|
||||
<button
|
||||
v-if="cancellable"
|
||||
type="button"
|
||||
class="uk-button uk-button-danger uk-width-1-1"
|
||||
@click="$refs.smartScanButton.terminateTask()"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<div v-if="!cancellable" class="uk-margin uk-grid-small uk-child-width-expand" uk-grid>
|
||||
<button
|
||||
type="button"
|
||||
class="uk-button"
|
||||
@click="
|
||||
scanning = false;
|
||||
lastStitchedImage = null;
|
||||
"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</template>
|
||||
<template #task-info>
|
||||
<div class="uk-width-1-1 uk-flex uk-flex-center">
|
||||
<div class="uk-width-2-3">
|
||||
<action-button
|
||||
v-if="scanComplete"
|
||||
thing="smart_scan"
|
||||
action="download_zip"
|
||||
submit-label="Download ZIP"
|
||||
|
|
@ -93,114 +51,52 @@
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<h3 v-if="scanning">Scan ID: {{ lastScanName }}</h3>
|
||||
</div>
|
||||
<div class="view-image uk-width-expand uk-height-1-1">
|
||||
<img
|
||||
v-if="displayImageOnRight"
|
||||
id="last-stitched-image"
|
||||
class="image-fit"
|
||||
:src="lastStitchedImage"
|
||||
/>
|
||||
<streamDisplay v-else />
|
||||
</div>
|
||||
</div>
|
||||
<h3 v-if="scanning" class="uk-margin-left">Scan ID: {{ lastScanName }}</h3>
|
||||
</template>
|
||||
</actionTab>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import actionTab from "./actionTab.vue";
|
||||
import slideScanControls from "./slideScanComponents/slideScanControls.vue";
|
||||
import streamDisplay from "./streamContent.vue";
|
||||
import propertyControl from "../labThingsComponents/propertyControl.vue";
|
||||
import ServerSpecifiedInterface from "../labThingsComponents/serverSpecifiedInterface.vue";
|
||||
import actionLogDisplay from "../labThingsComponents/actionLogDisplay.vue";
|
||||
import actionProgressBar from "../labThingsComponents/actionProgressBar.vue";
|
||||
import MiniStreamDisplay from "../genericComponents/miniStreamDisplay.vue";
|
||||
import ActionButton from "../labThingsComponents/actionButton.vue";
|
||||
import { useIntersectionObserver } from "@vueuse/core";
|
||||
|
||||
export default {
|
||||
name: "SlideScanContent",
|
||||
|
||||
components: {
|
||||
actionTab,
|
||||
slideScanControls,
|
||||
streamDisplay,
|
||||
propertyControl,
|
||||
actionLogDisplay,
|
||||
actionProgressBar,
|
||||
MiniStreamDisplay,
|
||||
ActionButton,
|
||||
ServerSpecifiedInterface,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
lastScanName: null,
|
||||
scanning: false,
|
||||
taskStatus: "pending",
|
||||
correlateStatus: "",
|
||||
stitchFromStageStatus: "",
|
||||
progress: null,
|
||||
log: [],
|
||||
taskId: null,
|
||||
taskUrl: null,
|
||||
lastStitchedImage: null,
|
||||
scanComplete: false,
|
||||
scan_name: "",
|
||||
workflowName: undefined,
|
||||
workflowSettings: [],
|
||||
workflowOptions: [],
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
cancellable() {
|
||||
return (this.taskStatus == "running") | (this.taskStatus == "pending");
|
||||
scanning() {
|
||||
return this.taskId && this.taskUrl;
|
||||
},
|
||||
displayImageOnRight() {
|
||||
return this.scanning & (this.lastStitchedImage !== null);
|
||||
return this.scanning && this.lastStitchedImage !== null;
|
||||
},
|
||||
infoPaneTitle() {
|
||||
if (!this.displayImageOnRight) return null;
|
||||
return "Live stitching preview";
|
||||
},
|
||||
|
||||
async created() {
|
||||
this.readSettings();
|
||||
this.workflowOptions = await this.readThingProperty(
|
||||
"smart_scan",
|
||||
"workflow_display_names",
|
||||
true,
|
||||
);
|
||||
},
|
||||
|
||||
mounted() {
|
||||
useIntersectionObserver(
|
||||
this.$refs.slideScanView,
|
||||
([{ isIntersecting }]) => {
|
||||
this.visibilityChanged(isIntersecting);
|
||||
},
|
||||
{
|
||||
threshold: 0.0,
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
methods: {
|
||||
visibilityChanged(isVisible) {
|
||||
if (isVisible) {
|
||||
this.readSettings();
|
||||
}
|
||||
},
|
||||
async readSettings() {
|
||||
this.workflowName = await this.readThingProperty("smart_scan", "workflow_name");
|
||||
|
||||
if (!this.workflowName) {
|
||||
console.warn("Could not read workflow_name, using default");
|
||||
this.workflowName = "histo_scan_workflow";
|
||||
}
|
||||
|
||||
if (this.workflowName) {
|
||||
this.ready = await this.readThingProperty(this.workflowName, "ready", true);
|
||||
this.workflowSettings =
|
||||
(await this.getThingEndpoint(this.workflowName, "settings_ui")) || [];
|
||||
}
|
||||
},
|
||||
onScanError: function (error) {
|
||||
this.scanRunning = false;
|
||||
this.modalError(error);
|
||||
},
|
||||
/**
|
||||
* Transition the UI into "scanning" mode and begin polling for scan updates.
|
||||
*
|
||||
|
|
@ -220,13 +116,25 @@ export default {
|
|||
* - clears any previous preview image
|
||||
* - starts the polling loop that fetches scan progress and preview images
|
||||
*/
|
||||
startScanning() {
|
||||
startScanning(id, url) {
|
||||
this.lastStitchedImage = null;
|
||||
this.scanning = true;
|
||||
this.lastScanName = null;
|
||||
this.taskId = id;
|
||||
this.taskUrl = url;
|
||||
this.scanComplete = false;
|
||||
setTimeout(this.pollScan, 1000);
|
||||
},
|
||||
onScanCompleted() {
|
||||
this.scanComplete = true;
|
||||
},
|
||||
closeTask() {
|
||||
this.taskId = null;
|
||||
this.taskUrl = null;
|
||||
this.lastStitchedImage = null;
|
||||
this.scanComplete = false;
|
||||
},
|
||||
async pollScan() {
|
||||
if (this.cancellable) {
|
||||
if (!this.scanComplete) {
|
||||
// while the scan is running
|
||||
let mtime = await this.readThingProperty("smart_scan", "latest_preview_stitch_time", true);
|
||||
if (mtime !== null) {
|
||||
|
|
@ -237,21 +145,6 @@ export default {
|
|||
setTimeout(this.pollScan, 1000); // keep rescheduling until it's stopped
|
||||
}
|
||||
},
|
||||
async setWorkflow(name) {
|
||||
try {
|
||||
this.workflowName = name;
|
||||
|
||||
await this.writeThingProperty("smart_scan", "workflow_name", name);
|
||||
|
||||
// refresh UI
|
||||
await this.readSettings();
|
||||
} catch (err) {
|
||||
this.modalError(err);
|
||||
|
||||
// revert if server rejected
|
||||
this.workflowName = await this.readThingProperty("smart_scan", "workflow_name", true);
|
||||
}
|
||||
},
|
||||
async downloadZipFile(response) {
|
||||
const scan_name = response.input.scan_name;
|
||||
const filename = `${scan_name}_images.zip`;
|
||||
|
|
@ -266,11 +159,4 @@ export default {
|
|||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
#log-display {
|
||||
height: 20em;
|
||||
}
|
||||
.control-component {
|
||||
width: 33%;
|
||||
}
|
||||
</style>
|
||||
<style scoped></style>
|
||||
|
|
|
|||
|
|
@ -126,6 +126,20 @@ export default {
|
|||
return null;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Find and ongoing Action and return its task response.
|
||||
*
|
||||
* Returns null if there is no ongoing action
|
||||
*/
|
||||
async getOngingAction(thing, action) {
|
||||
let response = await this.findOngoingActions(thing, action);
|
||||
// Exit if response is null, due to an error.
|
||||
if (response == null) return null;
|
||||
// Check for a task that is ongoing.
|
||||
// We can't handle multiple tasks ongoing, so this picks the first.
|
||||
// ?? Is the "Nullish Coalescing-Operator" to turn undefined into null.
|
||||
return response.data.find((t) => ["pending", "running"].includes(t.status)) ?? null;
|
||||
},
|
||||
thingActionUrl(thing, action, allowUndefined = false) {
|
||||
let url = this.$store.getters["wot/thingActionUrl"](
|
||||
thing,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue