ui_migration fix(deps): --preliminary-- added stronger type checking on things properties
This commit is contained in:
parent
f4a10ec897
commit
5bbe88c9f7
6 changed files with 75 additions and 40 deletions
|
|
@ -236,19 +236,24 @@ export default {
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
async checkExistingTasks() {
|
async checkExistingTasks() {
|
||||||
|
console.log("Checking for existing tasks for action ", this.action);
|
||||||
let response = await this.findOngoingActions(this.thing, this.action);
|
let response = await this.findOngoingActions(this.thing, this.action);
|
||||||
// Exit if response is null, due to an error.
|
// Exit if response is null, due to an error.
|
||||||
if (response == null) return;
|
if (response == null) return;
|
||||||
// Check for a task that is ongoing.
|
// Check for a task that is ongoing.
|
||||||
// We can't handle multiple tasks ongoing, so this picks the first.
|
// 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 = response.data.find((t) => ["pending", "running"].includes(t.status));
|
||||||
|
console.log("Existing tasks: ", response.data, "Ongoing task: ", ongoingTask);
|
||||||
if (ongoingTask) {
|
if (ongoingTask) {
|
||||||
// There is a started task
|
// There is a started task
|
||||||
|
console.log("Found ongoing task, resuming polling: ", ongoingTask);
|
||||||
this.taskStarted = true;
|
this.taskStarted = true;
|
||||||
this.$emit("taskStarted");
|
this.$emit("taskStarted");
|
||||||
// Find its URL
|
// Find its URL
|
||||||
|
console.log("Task links: ", ongoingTask.links);
|
||||||
const taskUrl = ongoingTask.links.find((t) => t.rel == "self").href;
|
const taskUrl = ongoingTask.links.find((t) => t.rel == "self").href;
|
||||||
this.startPollingTask(ongoingTask.id, taskUrl);
|
this.startPollingTask(ongoingTask.id, taskUrl);
|
||||||
|
console.log("Resumed polling on existing task with URL: ", taskUrl);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -324,6 +329,7 @@ export default {
|
||||||
this.taskUrl = null;
|
this.taskUrl = null;
|
||||||
this.taskRunning = false;
|
this.taskRunning = false;
|
||||||
this.taskStarted = false;
|
this.taskStarted = false;
|
||||||
|
console.log("Task ended with status: ", this.taskStatus);
|
||||||
this.$emit("finished");
|
this.$emit("finished");
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@
|
||||||
</label>
|
</label>
|
||||||
<label v-if="dataType == 'number_object'" class="uk-form-label"
|
<label v-if="dataType == 'number_object'" class="uk-form-label"
|
||||||
>{{ label }}
|
>{{ label }}
|
||||||
<div v-for="(val, key) in value" :key="key">
|
<div v-for="(val, key) in modelValue" :key="key">
|
||||||
<label>{{ internalLabels[key] }}</label>
|
<label>{{ internalLabels[key] }}</label>
|
||||||
<div class="input-and-buttons-container">
|
<div class="input-and-buttons-container">
|
||||||
<input
|
<input
|
||||||
|
|
@ -109,6 +109,7 @@ export default {
|
||||||
syncPropertyButton
|
syncPropertyButton
|
||||||
},
|
},
|
||||||
|
|
||||||
|
compatConfig: { COMPONENT_V_MODEL: false },
|
||||||
props: {
|
props: {
|
||||||
dataSchema: {
|
dataSchema: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
|
@ -125,14 +126,14 @@ export default {
|
||||||
},
|
},
|
||||||
animate: {
|
animate: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
// Initialise with a copy to try to prevent the this.value prop being mutated if
|
// Initialise with a copy to try to prevent the this.modelValue prop being mutated if
|
||||||
// the value is an array or object. For future updates we stringify and parse
|
// the modelValue 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.modelValue)
|
internalValue: Array.isArray(this.modelValue)
|
||||||
|
|
@ -142,7 +143,7 @@ export default {
|
||||||
: this.modelValue,
|
: 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: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
|
@ -210,8 +211,8 @@ export default {
|
||||||
watch: {
|
watch: {
|
||||||
modelValue: {
|
modelValue: {
|
||||||
handler() {
|
handler() {
|
||||||
// Fire updateIsEdited on both value and internal value change,
|
// Fire updateIsEdited on both modelValue and internal modelValue change,
|
||||||
// as change in value may not causse internalValue to change.
|
// as change in modelValue may not causse internalValue to change.
|
||||||
this.updateIsEdited();
|
this.updateIsEdited();
|
||||||
this.resetInternalValue();
|
this.resetInternalValue();
|
||||||
},
|
},
|
||||||
|
|
@ -236,8 +237,8 @@ export default {
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
resetInternalValue: function () {
|
resetInternalValue: function () {
|
||||||
// Whenever updatirng th internal value stringify and parse as a form of deepcopy.
|
// Whenever updatirng th internal modelValue 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.modelValue prop is not mutated for when elements of arrays
|
||||||
// or objects are updated.
|
// or objects are updated.
|
||||||
this.internalValue = JSON.parse(JSON.stringify(this.modelValue));
|
this.internalValue = JSON.parse(JSON.stringify(this.modelValue));
|
||||||
},
|
},
|
||||||
|
|
@ -254,11 +255,11 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
focusIn: function (event) {
|
focusIn: function (event) {
|
||||||
this.valueOnEnter = event.target.value;
|
this.valueOnEnter = event.target.modelValue;
|
||||||
},
|
},
|
||||||
focusOut: function (event) {
|
focusOut: function (event) {
|
||||||
if (this.valueOnEnter != event.target.value) {
|
if (this.valueOnEnter != event.target.modelValue) {
|
||||||
this.sendValue(event.target.value);
|
this.sendValue(event.target.modelValue);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
keyDown: function (event) {
|
keyDown: function (event) {
|
||||||
|
|
@ -271,12 +272,12 @@ export default {
|
||||||
this.isEdited = this.deepStringify(this.internalValue) !== this.deepStringify(this.modelValue);
|
this.isEdited = this.deepStringify(this.internalValue) !== this.deepStringify(this.modelValue);
|
||||||
},
|
},
|
||||||
animationEnd: function () {
|
animationEnd: function () {
|
||||||
this.animateUpdate = false;
|
this.animateUpdate = null;
|
||||||
this.$emit("animationShown");
|
this.$emit("animationShown");
|
||||||
},
|
},
|
||||||
deepStringify: function (val) {
|
deepStringify: function (val) {
|
||||||
// Create a json string where all internal numbers are also JSON strings. This is
|
// Create a json string where all internal numbers are also JSON strings. This is
|
||||||
// needed to robustly check if the value is updated because the raw value may be a
|
// needed to robustly check if the modelValue is updated because the raw modelValue may be a
|
||||||
// number but anything typed in the input is a string. In the case of arrays or
|
// number but anything typed in the input is a string. In the case of arrays or
|
||||||
// objects even with JSON.stringify we end up comparing ["1", 3] with [1, 3] and
|
// objects even with JSON.stringify we end up comparing ["1", 3] with [1, 3] and
|
||||||
// find them as not equal.
|
// find them as not equal.
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
<template>
|
<template>
|
||||||
<div uk-grid class="uk-height-1-1 uk-margin-remove uk-padding-remove">
|
<div uk-grid class="uk-height-1-1 uk-margin-remove uk-padding-remove">
|
||||||
<div class="control-component uk-padding-small">
|
<div class="control-component uk-padding-small">
|
||||||
<div v-show="!scanning" v-observe-visibility="visibilityChanged" class="uk-padding-small">
|
<div v-show="!scanning" ref="slideScanView" class="uk-padding-small">
|
||||||
<!-- Workflow Selection Dropdown -->
|
<!-- Workflow Selection Dropdown -->
|
||||||
<div class="uk-margin">
|
<div class="uk-margin">
|
||||||
<label class="uk-form-label">Workflow</label>
|
<label class="uk-form-label">Workflow</label>
|
||||||
|
|
||||||
<select
|
<select
|
||||||
class="uk-select uk-form-small"
|
class="uk-select uk-form-small"
|
||||||
:value="workflowName"
|
:modelValue="workflowName"
|
||||||
@change="setWorkflow($event.target.value)"
|
@change="setWorkflow($event.target.modelValue)"
|
||||||
>
|
>
|
||||||
<option v-for="(label, name) in workflowOptions" :key="name" :value="name">
|
<option v-for="(label, name) in workflowOptions" :key="name" :modelValue="name">
|
||||||
{{ label }}
|
{{ label }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
@ -176,12 +176,12 @@ export default {
|
||||||
|
|
||||||
async created() {
|
async created() {
|
||||||
this.readSettings();
|
this.readSettings();
|
||||||
this.workflowOptions = await this.readThingProperty("smart_scan", "workflow_display_names");
|
this.workflowOptions = await this.readThingProperty("smart_scan", "workflow_display_names", true);
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
useIntersectionObserver(
|
useIntersectionObserver(
|
||||||
this.$refs.slideScanContent,
|
this.$refs.slideScanView,
|
||||||
([{ isIntersecting }]) => {
|
([{ isIntersecting }]) => {
|
||||||
this.visibilityChanged(isIntersecting);
|
this.visibilityChanged(isIntersecting);
|
||||||
},
|
},
|
||||||
|
|
@ -198,13 +198,22 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async readSettings() {
|
async readSettings() {
|
||||||
this.workflowName = await this.readThingProperty("smart_scan", "workflow_name");
|
this.workflowOptions = await this.readThingProperty("smart_scan", "workflow_display_names", true) || {};
|
||||||
|
|
||||||
|
this.workflowName = await this.readThingProperty("smart_scan", "workflow_name", false);
|
||||||
|
|
||||||
|
if (!this.workflowName) {
|
||||||
|
console.warn("Could not read workflow_name, using default");
|
||||||
|
this.workflowName = "histo_scan_workflow";
|
||||||
|
}
|
||||||
|
|
||||||
if (this.workflowName) {
|
if (this.workflowName) {
|
||||||
this.ready = await this.readThingProperty(this.workflowName, "ready");
|
console.log("Current workflow name: ", this.workflowName);
|
||||||
this.workflowSettings = await this.readThingProperty(this.workflowName, "settings_ui");
|
this.ready = await this.readThingProperty(this.workflowName, "ready", true);
|
||||||
|
this.workflowSettings = await this.readThingProperty(this.workflowName, "settings_ui", true) || [];
|
||||||
console.log(this.workflowSettings);
|
console.log(this.workflowSettings);
|
||||||
this.workflowDisplayName = await this.readThingProperty(this.workflowName, "display_name");
|
this.workflowDisplayName = await this.readThingProperty(this.workflowName, "display_name", true);
|
||||||
this.workflowBlurb = await this.readThingProperty(this.workflowName, "ui_blurb");
|
this.workflowBlurb = await this.readThingProperty(this.workflowName, "ui_blurb", true);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onScanError: function (error) {
|
onScanError: function (error) {
|
||||||
|
|
@ -231,34 +240,47 @@ export default {
|
||||||
* - starts the polling loop that fetches scan progress and preview images
|
* - starts the polling loop that fetches scan progress and preview images
|
||||||
*/
|
*/
|
||||||
startScanning() {
|
startScanning() {
|
||||||
|
console.log("Starting scan...");
|
||||||
this.lastStitchedImage = null;
|
this.lastStitchedImage = null;
|
||||||
this.scanning = true;
|
this.scanning = true;
|
||||||
setTimeout(this.pollScan, 1000);
|
setTimeout(this.pollScan, 1000);
|
||||||
},
|
},
|
||||||
async pollScan() {
|
async pollScan() {
|
||||||
|
console.log("Polling for scan progress...");
|
||||||
if (this.cancellable) {
|
if (this.cancellable) {
|
||||||
// while the scan is running
|
// while the scan is running
|
||||||
let mtime = await this.readThingProperty("smart_scan", "latest_preview_stitch_time");
|
console.log("Polling for scan updates...");
|
||||||
|
let mtime = await this.readThingProperty("smart_scan", "latest_preview_stitch_time", true);
|
||||||
|
console.log("smart scan latest preview stitch time: ", mtime);
|
||||||
if (mtime !== null) {
|
if (mtime !== null) {
|
||||||
this.lastStitchedImage = `${this.$store.getters.baseUri}/smart_scan/latest_preview_stitch.jpg?t=${mtime}`;
|
this.lastStitchedImage = `${this.$store.getters.baseUri}/smart_scan/latest_preview_stitch.jpg?t=${mtime}`;
|
||||||
|
console.log("Updated preview image URL: ", this.lastStitchedImage);
|
||||||
}
|
}
|
||||||
this.lastScanName = await this.readThingProperty("smart_scan", "latest_scan_name");
|
|
||||||
|
this.lastScanName = await this.readThingProperty("smart_scan", "latest_scan_name", true);
|
||||||
setTimeout(this.pollScan, 1000); // keep rescheduling until it's stopped
|
setTimeout(this.pollScan, 1000); // keep rescheduling until it's stopped
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async setWorkflow(name) {
|
async setWorkflow(name) {
|
||||||
|
console.log("Setting workflow to ", name);
|
||||||
try {
|
try {
|
||||||
this.workflowName = name;
|
this.workflowName = name;
|
||||||
|
|
||||||
|
if (!this.workflowName) {
|
||||||
|
console.warn("Could not read workflow_name, using default");
|
||||||
|
this.workflowName = "histo_scan_workflow";
|
||||||
|
}
|
||||||
|
|
||||||
await this.writeThingProperty("smart_scan", "workflow_name", name);
|
await this.writeThingProperty("smart_scan", "workflow_name", name);
|
||||||
|
|
||||||
// refresh UI
|
// refresh UI
|
||||||
await this.readSettings();
|
await this.readSettings();
|
||||||
|
console.log("readsettings values: ", this.workflowName, this.workflowSettings, this.workflowDisplayName, this.workflowBlurb);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.modalError(err);
|
this.modalError(err);
|
||||||
|
|
||||||
// revert if server rejected
|
// revert if server rejected
|
||||||
this.workflowName = await this.readThingProperty("smart_scan", "workflow_name");
|
this.workflowName = await this.readThingProperty("smart_scan", "workflow_name", true);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async downloadZipFile(response) {
|
async downloadZipFile(response) {
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,12 @@ import App from "./App.vue";
|
||||||
import store from "./store";
|
import store from "./store";
|
||||||
import UIkit from "uikit";
|
import UIkit from "uikit";
|
||||||
|
|
||||||
import VueObserveVisibility from "vue-observe-visibility";
|
|
||||||
|
|
||||||
// Import MD icons
|
// Import MD icons
|
||||||
import "material-symbols/outlined.css";
|
import "material-symbols/outlined.css";
|
||||||
|
|
||||||
import queryMixin from "@/mixins/labThingsMixins.js";
|
//import queryMixin from "@/mixins/labThingsMixins.js";
|
||||||
import modalMixin from "@/mixins/modalMixins.js";
|
import modalMixin from "@/mixins/modalMixins.js";
|
||||||
|
import labThingsMixins from './mixins/labThingsMixins';
|
||||||
|
|
||||||
// UIKit overrides
|
// UIKit overrides
|
||||||
UIkit.mixin(
|
UIkit.mixin(
|
||||||
|
|
@ -25,11 +24,12 @@ UIkit.mixin(
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
|
|
||||||
// Use visibility observer
|
// Use visibility observer
|
||||||
app.use(VueObserveVisibility);
|
//app.use(VueObserveVisibility);
|
||||||
|
|
||||||
// Use global mixins
|
// Use global mixins
|
||||||
app.mixin(queryMixin);
|
// app.mixin(queryMixin);
|
||||||
app.mixin(modalMixin);
|
app.mixin(modalMixin);
|
||||||
|
app.mixin(labThingsMixins);
|
||||||
|
|
||||||
// Use Vuex store
|
// Use Vuex store
|
||||||
app.use(store);
|
app.use(store);
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ export default {
|
||||||
// `false` fails because axios somehow eats it!
|
// `false` fails because axios somehow eats it!
|
||||||
// Other values should not be stringified or pydantic
|
// Other values should not be stringified or pydantic
|
||||||
// can't parse them.
|
// can't parse them.
|
||||||
if ((value === false) | (value === true)) {
|
if ((value === false) || (value === true)) {
|
||||||
value = JSON.stringify(value);
|
value = JSON.stringify(value);
|
||||||
}
|
}
|
||||||
await axios.put(url, value);
|
await axios.put(url, value);
|
||||||
|
|
@ -91,7 +91,7 @@ export default {
|
||||||
response = await axios.get(taskUrl, { baseURL: this.$store.getters.baseUri });
|
response = await axios.get(taskUrl, { baseURL: this.$store.getters.baseUri });
|
||||||
const result = response.data.status;
|
const result = response.data.status;
|
||||||
|
|
||||||
if ((result == "running") | (result == "pending")) {
|
if ((result === "running") || (result === "pending")) {
|
||||||
ongoingMethod?.(response);
|
ongoingMethod?.(response);
|
||||||
this.pollTimers[taskUrl] = setTimeout(() => {
|
this.pollTimers[taskUrl] = setTimeout(() => {
|
||||||
this.pollUntilComplete(taskUrl, ongoingMethod, finalMethod, interval);
|
this.pollUntilComplete(taskUrl, ongoingMethod, finalMethod, interval);
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ export const wotStoreModule = {
|
||||||
// Deduplication should be done elsewhere.
|
// Deduplication should be done elsewhere.
|
||||||
let response = await axios.get(uri);
|
let response = await axios.get(uri);
|
||||||
let td = response.data;
|
let td = response.data;
|
||||||
let thing_name = name | uri.replace(/\/$/, "").split("/").pop();
|
let thing_name = name || uri.replace(/\/$/, "").split("/").pop();
|
||||||
commit("addThingDescription", {
|
commit("addThingDescription", {
|
||||||
thingName: thing_name,
|
thingName: thing_name,
|
||||||
thingDescription: td,
|
thingDescription: td,
|
||||||
|
|
@ -37,7 +37,7 @@ export const wotStoreModule = {
|
||||||
async fetchThingDescriptions({ commit }, uri) {
|
async fetchThingDescriptions({ commit }, uri) {
|
||||||
// Fetch thing descriptions from the given URI
|
// Fetch thing descriptions from the given URI
|
||||||
let response = await axios.get(uri);
|
let response = await axios.get(uri);
|
||||||
if (response.status != 200) throw "Could not retrieve thing descriptions";
|
if (response.status !== 200) throw "Could not retrieve thing descriptions";
|
||||||
for (const k in response.data) {
|
for (const k in response.data) {
|
||||||
let thing_name = k.replace(/\/$/, "").replace(/^\//, "");
|
let thing_name = k.replace(/\/$/, "").replace(/^\//, "");
|
||||||
commit("addThingDescription", {
|
commit("addThingDescription", {
|
||||||
|
|
@ -77,8 +77,14 @@ export const wotStoreModule = {
|
||||||
throw `Could not find form for ${affordanceType} ${thing}/${affordance} with op ${op}`;
|
throw `Could not find form for ${affordanceType} ${thing}/${affordance} with op ${op}`;
|
||||||
}
|
}
|
||||||
let affordances = td[affordanceType];
|
let affordances = td[affordanceType];
|
||||||
|
|
||||||
|
if (!affordances || !(affordance in affordances)) {
|
||||||
|
if (allowUndefined) return undefined;
|
||||||
|
throw `Could not find form for ${affordanceType} ${thing}/${affordance} with op ${op}`;
|
||||||
|
}
|
||||||
|
|
||||||
let href = findFormHref(affordances[affordance], op);
|
let href = findFormHref(affordances[affordance], op);
|
||||||
if (href == undefined) {
|
if (href === undefined) {
|
||||||
if (allowUndefined) return undefined;
|
if (allowUndefined) return undefined;
|
||||||
throw `Could not find form for ${affordanceType} ${thing}/${affordance} with op ${op}`;
|
throw `Could not find form for ${affordanceType} ${thing}/${affordance} with op ${op}`;
|
||||||
}
|
}
|
||||||
|
|
@ -105,10 +111,10 @@ export const wotStoreModule = {
|
||||||
|
|
||||||
export function findFormHref(affordance, op) {
|
export function findFormHref(affordance, op) {
|
||||||
// Find the form in the affordance that matches the given operation type
|
// Find the form in the affordance that matches the given operation type
|
||||||
if (affordance == undefined) return undefined;
|
if (affordance === undefined) return undefined;
|
||||||
let forms = affordance.forms;
|
let forms = affordance.forms;
|
||||||
let matchingForm = forms.find((f) => f.op == op || f.op.includes(op));
|
let matchingForm = forms.find((f) => f.op == op || f.op.includes(op));
|
||||||
if (matchingForm == undefined) return undefined;
|
if (matchingForm === undefined) return undefined;
|
||||||
return matchingForm.href;
|
return matchingForm.href;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue