Merge branch 'format-js' into 'v3'

Enable JS formatting checks in CI, reformat to match

See merge request openflexure/openflexure-microscope-server!422
This commit is contained in:
Julian Stirling 2025-10-28 16:27:36 +00:00
commit a89306b3b8
70 changed files with 727 additions and 1544 deletions

View file

@ -2,17 +2,17 @@ module.exports = {
root: true,
env: {
node: true
node: true,
},
parserOptions: {
parser: "babel-eslint"
parser: "babel-eslint",
},
rules: {
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off"
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
},
extends: ["plugin:vue/recommended", "eslint:recommended", "@vue/prettier"]
extends: ["plugin:vue/recommended", "eslint:recommended", "@vue/prettier"],
};

7
webapp/.prettierrc.json Normal file
View file

@ -0,0 +1,7 @@
{
"trailingComma": "all",
"semi": true,
"singleQuote": false,
"printWidth": 100,
"tabWidth": 2
}

View file

@ -1,3 +1,3 @@
module.exports = {
presets: ["@vue/app"]
presets: ["@vue/app"],
};

View file

@ -8,7 +8,7 @@
"gv": "genversion src/version.js",
"serve": "npm run gv && vue-cli-service serve --mode development",
"build": "npm run gv && vue-cli-service build --openssl-legacy-provider --mode production",
"lint": "vue-cli-service lint",
"lint": "vue-cli-service lint --no-fix --max-warnings 0",
"lint:fix": "vue-cli-service lint --fix"
},
"dependencies": {

View file

@ -1,18 +1,9 @@
<template>
<div
id="app"
class="uk-height-1-1 uk-margin-remove uk-padding-remove"
:class="handleTheme"
>
<div id="app" class="uk-height-1-1 uk-margin-remove uk-padding-remove" :class="handleTheme">
<loadingContent v-if="!$store.getters.ready" />
<appContent v-if="$store.getters.ready" />
<!-- Runtime modals -->
<div
id="modal-center"
ref="keyboardManualModal"
class="uk-flex-top"
uk-modal
>
<div id="modal-center" ref="keyboardManualModal" class="uk-flex-top" uk-modal>
<div class="uk-modal-dialog uk-modal-body uk-margin-auto-vertical">
<button class="uk-modal-close-default" type="button" uk-close></button>
<div
@ -62,7 +53,7 @@ export default {
components: {
appContent,
loadingContent
loadingContent,
},
data: function() {
@ -71,16 +62,13 @@ export default {
arrowKeysDown: {},
keyboardManual: [],
systemDark: undefined,
themeObserver: undefined
themeObserver: undefined,
};
},
computed: {
isSystemDark: function() {
if (
window.matchMedia &&
window.matchMedia("(prefers-color-scheme: dark)").matches
) {
if (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) {
return true;
} else {
return false;
@ -97,9 +85,9 @@ export default {
}
return {
"uk-light": isDark,
"uk-background-secondary": isDark
"uk-background-secondary": isDark,
};
}
},
},
mounted() {
@ -132,7 +120,7 @@ export default {
},
() => {
this.checkConnection();
}
},
);
// Keyboard shortcuts
@ -147,18 +135,18 @@ export default {
this.arrowKeysDown[event.keyCode] = true; //Add key to array
this.navigateKeyHandler();
},
"keydown"
"keydown",
);
Mousetrap.bind(
["up", "down", "left", "right"],
event => {
delete this.arrowKeysDown[event.keyCode]; //Remove key from array
},
"keyup"
"keyup",
);
this.keyboardManual.push({
shortcut: "←↑→↓",
description: "Move the microscope stage"
description: "Move the microscope stage",
});
// Focus keys
@ -170,7 +158,7 @@ export default {
});
this.keyboardManual.push({
shortcut: "pgup / pgdn",
description: "Move the microscope focus"
description: "Move the microscope focus",
});
// Capture
@ -179,7 +167,7 @@ export default {
});
this.keyboardManual.push({
shortcut: "c",
description: "Take a capture"
description: "Take a capture",
});
// Autofocus
@ -188,7 +176,7 @@ export default {
});
this.keyboardManual.push({
shortcut: "a",
description: "Fast autofocus"
description: "Fast autofocus",
});
// Increment/decrement tab
@ -200,7 +188,7 @@ export default {
});
this.keyboardManual.push({
shortcut: "shift+↑ / shift+↓",
description: "Switch tab"
description: "Switch tab",
});
},
@ -224,15 +212,10 @@ export default {
// TODO: more robust check - e.g. use a microscope Thing
// TODO: should we purge existing consumedThings?
try {
await this.$store.dispatch(
"wot/fetchThingDescriptions",
`${baseUri}/thing_descriptions/`
);
await this.$store.dispatch("wot/fetchThingDescriptions", `${baseUri}/thing_descriptions/`);
for (let requiredThing of ["camera", "stage"]) {
if (!this.$store.getters["wot/thingAvailable"](requiredThing)) {
throw new Error(
`No ${requiredThing} found, the GUI won't work without one.`
);
throw new Error(`No ${requiredThing} found, the GUI won't work without one.`);
}
}
try {
@ -291,8 +274,8 @@ export default {
// Make a position request
// Emit a signal to move, acted on by panelControl.vue
this.$root.$emit("globalMoveStepEvent", x_rel, y_rel, z_rel);
}
}
},
},
};
</script>
@ -366,5 +349,4 @@ html {
object-fit: contain;
overflow-y: hidden;
}
</style>

View file

@ -1,14 +1,7 @@
<template>
<div
id="app-content"
class="uk-margin-remove uk-padding-remove uk-height-1-1"
uk-grid
>
<div id="app-content" class="uk-margin-remove uk-padding-remove uk-height-1-1" uk-grid>
<!-- Initialisation modals -->
<calibrationWizard
ref="calibrationWizard"
@onClose="enterApp()"
></calibrationWizard>
<calibrationWizard ref="calibrationWizard" @onClose="enterApp()"></calibrationWizard>
<!-- Vertical tab bar -->
<div id="switcher-left-container">
<div
@ -63,10 +56,7 @@
</div>
<!-- Corresponding vertical tab content -->
<div
id="container-left"
class="uk-padding-remove uk-height-1-1 uk-width-expand"
>
<div id="container-left" class="uk-padding-remove uk-height-1-1 uk-width-expand">
<!-- For each top tab -->
<tabContent
v-for="item in topTabs"
@ -79,7 +69,6 @@
<component :is="item.component"></component>
</tabContent>
<!-- For each bottom tab -->
<tabContent
v-for="item in bottomTabs"
@ -96,7 +85,6 @@
</template>
<script>
// Import generic components
import tabIcon from "./genericComponents/tabIcon";
import tabContent from "./genericComponents/tabContent";
@ -141,24 +129,24 @@ export default {
id: "settings",
icon: "settings",
component: settingsContent,
class: "uk-margin-auto-top"
class: "uk-margin-auto-top",
},
{
id: "logging",
icon: "assignment_late",
component: loggingContent
component: loggingContent,
},
{
id: "about",
icon: "info",
component: aboutContent
component: aboutContent,
},
{
id: "power",
icon: "power_settings_new",
component: powerContent
}
]
component: powerContent,
},
],
};
},
@ -179,28 +167,28 @@ export default {
{
id: "view",
icon: "visibility",
component: viewContent
component: viewContent,
},
{
id: "control",
icon: "gamepad",
component: controlContent
component: controlContent,
},
{
id: "background detect",
icon: "background_replace",
component: backgroundDetectContent
component: backgroundDetectContent,
},
{
id: "slide scan",
icon: "settings_overscan",
component: slideScanContent
component: slideScanContent,
},
{
id: "scan list",
icon: "photo_library",
component: ScanListContent
}
component: ScanListContent,
},
];
if (!this.$store.state.galleryEnabled) {
tabs = tabs.filter(tab => tab.id != "gallery");
@ -210,7 +198,7 @@ export default {
currentTabIndex: function() {
return this.tabOrder.indexOf(this.currentTab);
}
},
},
mounted() {
@ -239,8 +227,7 @@ export default {
},
incrementTabBy: function(n) {
const newIndex =
(((this.currentTabIndex + n) % this.tabOrder.length) +
this.tabOrder.length) %
(((this.currentTabIndex + n) % this.tabOrder.length) + this.tabOrder.length) %
this.tabOrder.length;
const newId = this.tabOrder[newIndex];
this.currentTab = newId;
@ -250,8 +237,8 @@ export default {
},
enterApp: function() {
// Stuff to do once connected and all init modals are finished
}
}
},
},
};
</script>

View file

@ -1,70 +0,0 @@
<template>
<div>
<label>{{ label }}</label>
<div class="uk-form-controls">
<div v-for="option in options" :key="option">
<label>
<input
class="uk-checkbox"
type="checkbox"
:value="option"
:checked="value && value.includes(option)"
v-bind="$attrs"
@change="updateValue($event.target)"
/>
{{ option }}
</label>
</div>
</div>
</div>
</template>
<script>
export default {
name: "CheckList",
props: {
value: {
type: Array,
required: false,
default: function() {
return [];
}
},
options: {
type: Array,
required: true
},
name: {
type: String,
required: true
},
label: {
type: String,
required: true
}
},
methods: {
updateValue(target) {
var newSelected = this.value != null ? [...this.value] : []; // Clone value array
if (target.checked) {
if (!newSelected.includes(target.value)) {
newSelected.push(target.value);
}
} else {
if (newSelected.includes(target.value)) {
newSelected = newSelected.filter(function(value) {
return value != target.value;
});
}
}
this.$emit("input", newSelected);
}
}
};
</script>
<style scoped></style>

View file

@ -1,30 +0,0 @@
<template>
<div>
<!-- eslint-disable-next-line vue/no-v-html -->
<p v-html="content"></p>
</div>
</template>
<script>
export default {
name: "HtmlBlock",
props: {
label: {
type: String,
required: false,
default: ""
},
name: {
type: String,
required: true
},
content: {
type: String,
required: true
}
}
};
</script>
<style scoped></style>

View file

@ -1,121 +0,0 @@
<template>
<div>
<form @submit.prevent="handleMetadataSubmit">
<div class="uk-margin-remove uk-flex uk-flex-middle">
<div
class="uk-margin-remove-top uk-padding-remove uk-grid-small uk-width-expand"
uk-grid
>
<div class="uk-margin-remove uk-width-1-2">
<input
ref="textboxKey"
v-model="newMetadata.key"
class="uk-input uk-form-width-small uk-form-small"
type="text"
name="flavor"
placeholder="Key"
/>
</div>
<div class="uk-margin-remove uk-width-1-2">
<input
v-model="newMetadata.value"
class="uk-input uk-form-width-small uk-form-small"
type="text"
name="flavor"
placeholder="Value"
@keyup.enter="handleMetadataSubmit()"
/>
</div>
</div>
<a
href="#"
class="uk-icon uk-margin-left"
@click="handleMetadataSubmit()"
><span class="material-symbols-outlined">add_circle</span></a
>
</div>
</form>
<div
v-for="(val, key) in value"
:key="key"
class="uk-width-1-1 uk-margin-small uk-margin-remove-left uk-margin-remove-right uk-flex uk-flex-middle"
>
<div class="uk-margin-remove-top uk-padding-remove uk-width-expand">
<labelInput
:name="key"
:label="key"
:value="value[key]"
@input="value[key] = $event"
/>
</div>
<a href="#" class="uk-icon uk-width-auto" @click="delMetadataKey(key)"
><span class="material-symbols-outlined">delete</span></a
>
</div>
</div>
</template>
<script>
import labelInput from "../fieldComponents/labelInput";
export default {
name: "KeyvalList",
components: {
labelInput
},
props: {
value: {
type: Object,
required: true
}
},
data: function() {
return {
newMetadata: {
key: "",
value: ""
}
};
},
methods: {
handleMetadataSubmit: function() {
var newSelected = {};
if (this.value != null) {
Object.assign(newSelected, this.value);
}
newSelected[this.newMetadata.key] = this.newMetadata.value;
this.newMetadata.key = "";
this.newMetadata.value = "";
this.$emit("input", newSelected);
// Move focus back to key textbox
if (this.$refs.textboxKey) {
this.$refs.textboxKey.focus();
}
},
delMetadataKey: function(key) {
var newSelected = {};
if (this.value != null) {
Object.assign(newSelected, this.value);
}
this.$delete(newSelected, key);
this.$emit("input", newSelected);
}
}
};
</script>
<style scoped></style>

View file

@ -1,65 +0,0 @@
<template>
<div>
<label class="uk-form-label uk-text-bold">{{ label }}</label>
<div
uk-tooltip="title: Click to edit value; delay: 250"
@click="setEditing(true)"
>
<div v-show="editing == false">
<label> {{ value }} </label>
</div>
<input
v-show="editing == true"
ref="textinput"
class="uk-input uk-form-small"
type="text"
:name="name"
:value="value"
autofocus
@blur="setEditing(false)"
@keyup.enter="setEditing(false)"
@input="$emit('input', $event.target.value)"
/>
</div>
</div>
</template>
<script>
export default {
name: "LabelInput",
props: {
label: {
type: String,
required: true
},
name: {
type: String,
required: true
},
value: {
type: String,
required: true
}
},
data: function() {
return {
editing: false
};
},
methods: {
setEditing(editing) {
this.editing = editing;
if (editing == true) {
this.$nextTick(() => this.$refs.textinput.focus());
}
}
}
};
</script>
<style scoped></style>

View file

@ -1,44 +0,0 @@
<template>
<div>
<label class="uk-form-label">{{ label }}</label>
<input
class="uk-input uk-form-small"
type="number"
:name="name"
:value="value"
:placeholder="placeholder"
v-bind="$attrs"
@input="$emit('input', Number($event.target.value))"
/>
</div>
</template>
<script>
export default {
name: "NumberInput",
props: {
value: {
type: Number,
required: false,
default: 0
},
placeholder: {
type: Number,
required: false,
default: 0
},
name: {
type: String,
required: true
},
label: {
type: String,
required: true
}
}
};
</script>
<style scoped></style>

View file

@ -1,49 +0,0 @@
<template>
<div>
<label>{{ label }}</label>
<div class="uk-form-controls">
<div v-for="option in options" :key="option">
<label
><input
class="uk-radio"
type="radio"
:name="name"
:value="option"
:checked="value == option"
@input="$emit('input', $event.target.value)"
/>
{{ option }}</label
>
</div>
</div>
</div>
</template>
<script>
export default {
name: "RadioList",
props: {
value: {
type: String,
required: false,
default: ""
},
options: {
type: Array,
required: true
},
name: {
type: String,
required: true
},
label: {
type: String,
required: true
}
}
};
</script>
<style scoped></style>

View file

@ -1,44 +0,0 @@
<template>
<div>
<label class="uk-form-label">{{ label }}</label>
<select
class="uk-select uk-form-small"
:value="value"
v-bind="$attrs"
@input="$emit('input', $event.target.value)"
>
<option v-for="option in options" :key="option">
{{ option }}
</option>
</select>
</div>
</template>
<script>
export default {
name: "SelectList",
props: {
value: {
type: String,
required: false,
default: ""
},
options: {
type: Array,
required: true
},
name: {
type: String,
required: true
},
label: {
type: String,
required: true
}
}
};
</script>
<style scoped></style>

View file

@ -1,79 +0,0 @@
<template>
<div>
<form @submit.prevent="handleTagSubmit">
<div class="uk-margin-small uk-flex uk-flex-middle">
<div class="uk-margin-remove-top uk-width-expand">
<div class="uk-inline uk-width-1-1">
<span class="uk-form-icon"
><span class="material-symbols-outlined">label</span></span
>
<input
v-model="newTag"
class="uk-input uk-form-small"
type="text"
name="flavor"
placeholder="Tag"
/>
</div>
</div>
<a href="#" class="uk-icon uk-margin-left" @click="handleTagSubmit()"
><span class="material-symbols-outlined">add_circle</span></a
>
</div>
</form>
<span
v-for="tag in value"
:key="tag"
class="uk-label uk-margin-small-right deletable-label"
@click="delTag(tag)"
>
{{ tag }}
</span>
</div>
</template>
<script>
export default {
name: "TagList",
props: {
value: {
type: Array,
required: true
}
},
data: function() {
return {
newTag: ""
};
},
methods: {
handleTagSubmit: function() {
var newSelected = this.value != null ? [...this.value] : []; // Clone value array
newSelected.push(this.newTag);
this.newTag = "";
this.$emit("input", newSelected);
},
delTag: function(tag) {
var newSelected = this.value != null ? [...this.value] : []; // Clone value array
if (newSelected.includes(tag)) {
newSelected = newSelected.filter(function(value) {
return value != tag;
});
}
this.$emit("input", newSelected);
}
}
};
</script>
<style scoped></style>

View file

@ -1,45 +0,0 @@
<template>
<div>
<label v-if="label" class="uk-form-label">{{ label }}</label>
<input
class="uk-input uk-form-small"
type="text"
:name="name"
:value="value"
:placeholder="placeholder"
v-bind="$attrs"
@input="$emit('input', $event.target.value)"
/>
</div>
</template>
<script>
export default {
name: "TextInput",
props: {
placeholder: {
type: String,
required: false,
default: null
},
label: {
type: String,
required: false,
default: null
},
name: {
type: String,
required: true
},
value: {
type: String,
required: false,
default: ""
}
}
};
</script>
<style scoped></style>

View file

@ -22,20 +22,20 @@ export default {
data: function() {
return {
isVisible: false
isVisible: false,
};
},
computed: {
streamImgUri: function() {
return `${this.$store.getters.baseUri}/camera/mjpeg_stream`;
}
},
},
methods: {
visibilityChanged(isVisible) {
this.isVisible = isVisible;
}
}
},
},
};
</script>

View file

@ -1,7 +1,5 @@
<template>
<div
class="progress uk-margin-top uk-margin-horizontal-remove uk-padding-remove"
>
<div class="progress uk-margin-top uk-margin-horizontal-remove uk-padding-remove">
<div class="indeterminate"></div>
</div>
</template>
@ -21,16 +19,16 @@ export default {
classObject: function() {
return {
"tabicon-active": this.currentTab == this.id,
"uk-disabled": this.requireConnection && !this.$store.getters.ready
"uk-disabled": this.requireConnection && !this.$store.getters.ready,
};
}
},
},
methods: {
setThisTab(event) {
this.$emit("set-tab", event, this.id);
}
}
},
},
};
</script>
@ -77,8 +75,7 @@ export default {
left: 0;
bottom: 0;
will-change: left, right;
-webkit-animation: indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395)
infinite;
-webkit-animation: indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite;
animation: indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite;
}
@ -90,10 +87,8 @@ export default {
left: 0;
bottom: 0;
will-change: left, right;
-webkit-animation: indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1)
infinite;
animation: indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1)
infinite;
-webkit-animation: indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;
animation: indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;
-webkit-animation-delay: 1.15s;
animation-delay: 1.15s;
}

View file

@ -15,17 +15,17 @@ export default {
props: {
tabID: {
type: String,
required: true
required: true,
},
currentTab: {
type: String,
required: true
required: true,
},
requireConnection: Boolean
requireConnection: Boolean,
},
computed: {},
methods: {}
methods: {},
};
</script>

View file

@ -1,11 +1,5 @@
<template>
<a
href="#"
class="uk-link"
:class="classObject"
:uk-tooltip="tooltipOptions"
@click="setThisTab"
>
<a href="#" class="uk-link" :class="classObject" :uk-tooltip="tooltipOptions" @click="setThisTab">
<slot></slot>
<div v-if="showTitle" class="tabtitle">
{{ computedTitle }}
@ -20,33 +14,33 @@ export default {
props: {
tabID: {
type: String,
required: true
required: true,
},
title: {
type: String,
required: false,
default: undefined
default: undefined,
},
showTitle: {
type: Boolean,
required: false,
default: true
default: true,
},
showTooltip: {
type: Boolean,
required: false,
default: true
default: true,
},
currentTab: {
type: String,
required: true
required: true,
},
clickCallback: {
type: Function,
required: false,
default: null
default: null,
},
requireConnection: Boolean
requireConnection: Boolean,
},
computed: {
@ -72,9 +66,9 @@ export default {
classObject: function() {
return {
"tabicon-active": this.currentTab == this.tabID,
"uk-disabled": this.requireConnection && !this.$store.getters.ready
"uk-disabled": this.requireConnection && !this.$store.getters.ready,
};
}
},
},
methods: {
@ -83,8 +77,8 @@ export default {
if (this.clickCallback) {
this.clickCallback();
}
}
}
},
},
};
</script>

View file

@ -1,10 +1,11 @@
<template>
<div
v-observe-visibility="visibilityChanged"
class="uk-margin-remove uk-padding-remove"
>
<div v-observe-visibility="visibilityChanged" class="uk-margin-remove uk-padding-remove">
<div v-if="taskStarted" ref="isPollingElement">
<action-progress-bar v-if="taskStarted && hideOnRun" :progress="progress" :task-status="taskStatus" />
<action-progress-bar
v-if="taskStarted && hideOnRun"
:progress="progress"
:task-status="taskStatus"
/>
<!-- hideOnRun selects if the button hides, don't show progress bar if button doesn't hide. -->
<button
v-if="canTerminate && taskRunning"
@ -22,7 +23,10 @@
:disabled="isDisabled"
:hidden="taskStarted && hideOnRun"
class="uk-button uk-width-1-1"
:class="[isDisabled ? 'uk-button-disabled' : '', buttonPrimary ? 'uk-button-primary' : 'uk-button-default']"
:class="[
isDisabled ? 'uk-button-disabled' : '',
buttonPrimary ? 'uk-button-primary' : 'uk-button-default',
]"
@click="bootstrapTask()"
>
{{ submitLabel }}
@ -35,18 +39,12 @@
class=""
uk-modal="bg-close: false; esc-close: false; stack: true;"
>
<div
id="status-modal"
class="uk-modal-dialog uk-modal-body uk-margin-auto-vertical"
>
<div id="status-modal" class="uk-modal-dialog uk-modal-body uk-margin-auto-vertical">
<h2>{{ submitLabel }}</h2>
<action-log-display :log="log" :task-status="taskStatus" />
<div id="progress-and-cancel-row">
<div class="stretchy">
<action-progress-bar
:progress="progress"
:task-status="taskStatus"
/>
<action-progress-bar :progress="progress" :task-status="taskStatus" />
</div>
<button
@ -84,67 +82,67 @@ export default {
props: {
action: {
type: String,
required: true
required: true,
},
thing: {
type: String,
required: true
required: true,
},
submitData: {
type: [Object, Array],
required: false,
default: () => ({})
default: () => ({}),
},
pollInterval: {
type: Number,
required: false,
default: 1
default: 1,
},
submitLabel: {
type: String,
required: false,
default: "Submit"
default: "Submit",
},
canTerminate: {
type: Boolean,
required: false,
default: true
default: true,
},
requiresConfirmation: {
type: Boolean,
required: false,
default: false
default: false,
},
confirmationMessage: {
type: String,
required: false,
default: "Start task?"
default: "Start task?",
},
buttonPrimary: {
type: Boolean,
required: false,
default: true
default: true,
},
submitOnEvent: {
type: String,
required: false,
default: null
default: null,
},
modalProgress: {
type: Boolean,
required: false,
default: false
default: false,
},
isDisabled: {
type: Boolean,
required: false,
default: false
default: false,
},
hideOnRun: {
type: Boolean,
required: false,
default: true
}
default: true,
},
},
data: function() {
@ -154,14 +152,14 @@ export default {
taskStarted: false,
taskRunning: false,
log: [],
taskStatus: ""
taskStatus: "",
};
},
computed: {
submitUrl() {
return this.thingActionUrl(this.thing, this.action);
}
},
},
watch: {
@ -179,7 +177,7 @@ export default {
},
taskStatus(newval) {
this.$emit("update:taskStatus", newval);
}
},
},
mounted() {
@ -195,13 +193,14 @@ export default {
// Otherwise, this is just normal property access
return this[n];
}
const TypedArray = Reflect.getPrototypeOf(Int8Array);
for (const C of [Array, String, TypedArray]) {
Object.defineProperty(C.prototype, "from_index",
{ value: from_index,
writable: true,
enumerable: false,
configurable: true });
const TypedArray = Reflect.getPrototypeOf(Int8Array);
for (const C of [Array, String, TypedArray]) {
Object.defineProperty(C.prototype, "from_index", {
value: from_index,
writable: true,
enumerable: false,
configurable: true,
});
}
// Check for already running tasks
if (this.taskStarted != true) {
@ -234,10 +233,7 @@ export default {
if (task.status == "pending" || task.status == "running") {
this.taskStarted = true;
this.$emit("taskStarted");
this.startPolling(
task.id,
task.links.find(t => t.rel == "self").href
);
this.startPolling(task.id, task.links.find(t => t.rel == "self").href);
}
}
});
@ -250,7 +246,7 @@ export default {
() => {
this.startTask();
},
() => {}
() => {},
);
} else {
this.startTask();
@ -271,10 +267,7 @@ export default {
UIkit.modal(this.$refs.statusModal).show();
}
// Start the store polling TaskId for success
response = await this.startPolling(
response.data.id,
response.data.href
);
response = await this.startPolling(response.data.id, response.data.href);
if (response.status == "completed") {
this.$emit("response", response);
this.$emit("completed", response.output);
@ -312,43 +305,46 @@ export default {
var checkCondition = (resolve, reject) => {
// If the condition is met, we're done!
axios
.get(this.taskUrl, { baseURL: this.$store.getters.baseUri })
.then(response => {
var result = response.data.status;
this.taskStatus = result;
if ((result == "running") | (result == "pending")) {
// If the task is still running, we update progress/log,
// and schedule another poll
this.progress = response.data.progress;
this.log = response.data.log;
// Check again after timeout
setTimeout(checkCondition, interval, resolve, reject);
axios.get(this.taskUrl, { baseURL: this.$store.getters.baseUri }).then(response => {
var result = response.data.status;
this.taskStatus = result;
if ((result == "running") | (result == "pending")) {
// If the task is still running, we update progress/log,
// and schedule another poll
this.progress = response.data.progress;
this.log = response.data.log;
// Check again after timeout
setTimeout(checkCondition, interval, resolve, reject);
}
// If task ends with an error
else if (result == "error") {
// Pass the error string back with reject
if (!this.progress) this.progress = 1;
// Test whether the log is empty or the most recent message is not from an error
// If so, return a default message
if (
(response.data.log.length == 0) |
(response.data.log.from_index(-1).levelname != "ERROR")
) {
var message = "Unexpected error, please check the logs";
}
// If task ends with an error
else if (result == "error") {
// Pass the error string back with reject
if (!this.progress) this.progress = 1;
// Test whether the log is empty or the most recent message is not from an error
// If so, return a default message
if (response.data.log.length == 0 | response.data.log.from_index(-1).levelname != "ERROR") {
var message = "Unexpected error, please check the logs";
}
// As LabThings Actions add the message from any raised exception to the log, the
// last message in the log is the message from the Exception.
//If the Exception was raised with no message, use a default.
else {
message = response.data.log.from_index(-1).message||"Unexpected error, please check the logs";
}
// Raise an Error with the chosen message
reject(new Error(message));
}
// If task ends without reporting an error
// (NB this includes cancellation)
// As LabThings Actions add the message from any raised exception to the log, the
// last message in the log is the message from the Exception.
//If the Exception was raised with no message, use a default.
else {
resolve(response.data);
message =
response.data.log.from_index(-1).message ||
"Unexpected error, please check the logs";
}
});
// Raise an Error with the chosen message
reject(new Error(message));
}
// If task ends without reporting an error
// (NB this includes cancellation)
else {
resolve(response.data);
}
});
};
return new Promise(checkCondition);
@ -362,8 +358,8 @@ export default {
terminateTask: function() {
axios.delete(this.taskUrl, { baseURL: this.$store.getters.baseUri });
this.$root.$emit("modalClosed");
}
}
},
},
};
</script>

View file

@ -1,31 +1,24 @@
<template>
<div
class="log-wrapper"
@mouseenter="onMouseEnter"
@mouseleave="onMouseLeave"
>
<div
ref="logContainer"
class="log-container uk-margin-left uk-margin-right uk-margin"
>
<div v-if="log">
<div v-for="(item, index) in log" :key="`log_entry_${index}`">
{{ item.message }}
<div class="log-wrapper" @mouseenter="onMouseEnter" @mouseleave="onMouseLeave">
<div ref="logContainer" class="log-container uk-margin-left uk-margin-right uk-margin">
<div v-if="log">
<div v-for="(item, index) in log" :key="`log_entry_${index}`">
{{ item.message }}
</div>
</div>
</div>
<div v-if="taskStatus == 'error'" class="uk-alert uk-alert-danger">
The task failed due to an error. There may be more information in the log.
</div>
<div v-if="taskStatus == 'cancelled'" class="uk-alert uk-alert-warning">
The task was cancelled.
</div>
<div v-if="taskStatus == 'completed'" class="uk-alert uk-alert-success">
The task completed successfully.
<div v-if="taskStatus == 'error'" class="uk-alert uk-alert-danger">
The task failed due to an error. There may be more information in the log.
</div>
<div v-if="taskStatus == 'cancelled'" class="uk-alert uk-alert-warning">
The task was cancelled.
</div>
<div v-if="taskStatus == 'completed'" class="uk-alert uk-alert-success">
The task completed successfully.
</div>
<!-- Paused banner outside scroll container, positioned relative to wrapper -->
<div v-if="userIsHovering" class="paused-banner">
Auto-scroll paused
<div v-if="userIsHovering" class="paused-banner">
Auto-scroll paused
</div>
</div>
</div>
@ -38,12 +31,12 @@ export default {
props: {
taskStatus: {
type: String,
required: true
required: true,
},
log: {
type: Array,
required: true
}
required: true,
},
},
data() {
@ -58,7 +51,7 @@ export default {
},
taskStatus: function() {
this.scrollToBottom();
}
},
},
methods: {
@ -70,15 +63,15 @@ export default {
},
scrollToBottom() {
/*Scroll to bottom of log unless the user is hovering over the log.*/
/*Scroll to bottom of log unless the user is hovering over the log.*/
this.$nextTick(() => {
if (this.userIsHovering) return;
const viewer = this.$refs.logContainer;
viewer.scrollTop = viewer.scrollHeight;
});
}
}
},
},
};
</script>

View file

@ -13,12 +13,12 @@ export default {
progress: {
type: Number,
required: false,
default: null
default: null,
},
taskStatus: {
type: String,
required: true
}
required: true,
},
},
computed: {
@ -33,8 +33,8 @@ export default {
return true;
}
return false;
}
}
},
},
};
</script>
@ -81,8 +81,7 @@ export default {
left: 0;
bottom: 0;
will-change: left, right;
-webkit-animation: indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395)
infinite;
-webkit-animation: indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite;
animation: indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite;
}
@ -94,10 +93,8 @@ export default {
left: 0;
bottom: 0;
will-change: left, right;
-webkit-animation: indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1)
infinite;
animation: indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1)
infinite;
-webkit-animation: indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;
animation: indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;
-webkit-animation-delay: 1.15s;
animation-delay: 1.15s;
}

View file

@ -1,15 +1,18 @@
<template>
<a
class="uk-button"
:class="[isDisabled ? 'uk-button-disabled' : '', buttonPrimary ? 'uk-button-primary' : 'uk-button-default']"
:href="URL"
download
> {{ buttonLabel }}</a
>
<a
class="uk-button"
:class="[
isDisabled ? 'uk-button-disabled' : '',
buttonPrimary ? 'uk-button-primary' : 'uk-button-default',
]"
:href="url"
download
>
{{ buttonLabel }}</a
>
</template>
<script>
export default {
name: "EndpointButton",
@ -17,27 +20,27 @@ export default {
buttonPrimary: {
type: Boolean,
required: false,
default: true
default: true,
},
URL: {
url: {
type: String,
required: true,
},
isDisabled: {
type: Boolean,
required: false,
default: false
default: false,
},
destinationName: {
type: String,
required: false,
default: null
default: null,
},
buttonLabel: {
type: String,
required: false,
default: "Download File"
}
default: "Download File",
},
},
};
</script>

View file

@ -51,8 +51,8 @@
<label v-if="dataType == 'number_object'" class="uk-form-label"
>{{ label }}
<div v-for="(val, key) in value" :key="key">
<label>{{internalLabels[key]}}</label>
<div class="input-and-buttons-container" >
<label>{{ internalLabels[key] }}</label>
<div class="input-and-buttons-container">
<input
v-model="internalValue[key]"
class="uk-form-small numeric-setting-line-input"
@ -89,24 +89,26 @@ export default {
name: "InputFromSchema",
components: {
syncPropertyButton
syncPropertyButton,
},
props: {
dataSchema: {
type: Object
type: Object,
required: true,
},
value: {
type: null
type: null,
required: true,
},
label: {
type: String,
default: ""
default: "",
},
animate: {
type: Boolean,
default: false
}
default: false,
},
},
data() {
@ -116,44 +118,21 @@ export default {
// (see resetInternalValue). If we do this here there is a chance we get errors
// as internalValue is still null when rendering starts.
internalValue: Array.isArray(this.value)
? [...this.value]
: typeof this.value === 'object'
? { ...this.value }
: this.value,
? [...this.value]
: typeof this.value === "object"
? { ...this.value }
: this.value,
// Is edited can't be computed as we mutate internalValue
isEdited: false,
animateUpdate: false,
};
},
mounted() {
if (this.value !== undefined) {
this.resetInternalValue();
}
},
watch: {
value() {
// Fire updateIsEdited on both value and internal value change,
// as change in value may not causse internalValue to change.
this.updateIsEdited();
this.resetInternalValue();
},
internalValue() {
this.updateIsEdited();
},
animate(updated) {
if (updated) {
this.animateUpdate = true;
}
}
},
computed: {
internalLabels: function() {
if (this.dataType == "number_object") {
let labels = {};
for (const key in this.internalValue){
labels[key] = this.dataSchema.properties[key].title
for (const key in this.internalValue) {
labels[key] = this.dataSchema.properties[key].title;
}
return labels;
}
@ -204,6 +183,29 @@ export default {
}
}
return "other";
},
},
watch: {
value() {
// Fire updateIsEdited on both value and internal value change,
// as change in value may not causse internalValue to change.
this.updateIsEdited();
this.resetInternalValue();
},
internalValue() {
this.updateIsEdited();
},
animate(updated) {
if (updated) {
this.animateUpdate = true;
}
},
},
mounted() {
if (this.value !== undefined) {
this.resetInternalValue();
}
},
@ -215,10 +217,10 @@ export default {
this.internalValue = JSON.parse(JSON.stringify(this.value));
},
requestUpdate: async function() {
this.$emit("requestUpdate")
this.$emit("requestUpdate");
},
sendValue: async function() {
this.$emit("sendValue", this.internalValue)
this.$emit("sendValue", this.internalValue);
},
checkboxUpdated: function() {
if (this.internalValue != this.$refs.checkbox.checked) {
@ -245,7 +247,7 @@ export default {
},
animationEnd: function() {
this.animateUpdate = false;
this.$emit('animationShown');
this.$emit("animationShown");
},
deepStringify: function(val) {
// Create a json string where all internal numbers are also JSON strings. This is
@ -256,16 +258,13 @@ export default {
if (Array.isArray(val)) {
return JSON.stringify(val.map(String));
}
if (val && typeof val === 'object') {
const normalized = Object.fromEntries(
Object.entries(val).map(([k, v]) => [k, String(v)])
);
if (val && typeof val === "object") {
const normalized = Object.fromEntries(Object.entries(val).map(([k, v]) => [k, String(v)]));
return JSON.stringify(normalized);
}
return JSON.stringify(String(val));
}
}
},
},
};
</script>
@ -288,8 +287,12 @@ export default {
background-color: #fff3cd;
}
@keyframes green-flash {
0% { background-color: #3fda63; }
100% { background-color: white; }
0% {
background-color: #3fda63;
}
100% {
background-color: white;
}
}
.flash {
animation: green-flash 0.7s ease;

View file

@ -18,58 +18,56 @@ export default {
name: "PropertyControl",
components: {
InputFromSchema
InputFromSchema,
},
props: {
label: {
type: String,
default: ""
default: "",
},
propertyName: {
type: String,
required: true
required: true,
},
thingName: {
type: String,
required: true
required: true,
},
readBack: {
type: Boolean,
required: false,
default: false
default: false,
},
readBackDelay: {
type: Number,
default: 1000,
required: false
}
required: false,
},
},
data() {
return {
value: undefined,
animate: false
animate: false,
};
},
computed: {
propertyDescription: function() {
try {
return this.thingDescription(this.thingName).properties[
this.propertyName
];
return this.thingDescription(this.thingName).properties[this.propertyName];
} catch (error) {
return undefined;
}
}
},
},
watch: {
propertyDescription: function() {
// Ensure we read the property once the URL is known
this.readProperty();
}
},
},
mounted: function() {
@ -83,21 +81,14 @@ export default {
methods: {
readProperty: async function() {
let data = await this.readThingProperty(
this.thingName,
this.propertyName
);
let data = await this.readThingProperty(this.thingName, this.propertyName);
this.value = data;
return data;
},
writeProperty: async function(requestedValue) {
try {
this.value=requestedValue;
await this.writeThingProperty(
this.thingName,
this.propertyName,
requestedValue
);
this.value = requestedValue;
await this.writeThingProperty(this.thingName, this.propertyName, requestedValue);
if (this.readBack) {
await new Promise(r => setTimeout(r, this.readBackDelay));
let newVal = await this.readProperty();
@ -106,7 +97,9 @@ export default {
} else {
this.animate = true;
await this.modalNotify(
`Set ${this.label} to ${formatValue(newVal)} (closest valid value to requested ${formatValue(requestedValue)}).`
`Set ${this.label} to ${formatValue(
newVal,
)} (closest valid value to requested ${formatValue(requestedValue)}).`,
);
}
} else {
@ -120,9 +113,9 @@ export default {
}
},
resetAnimate: function() {
this.animate = false;
}
}
this.animate = false;
},
},
};
</script>

View file

@ -22,15 +22,14 @@ export default {
name: "ServerSpecifiedActionButton",
components: {
ActionButton
ActionButton,
},
props: {
actionData: {
type: Object,
required: true,
}
},
},
methods: {
@ -38,8 +37,8 @@ export default {
if (this.actionData.notify_on_success) {
this.modalNotify(this.actionData.success_message);
}
}
}
},
},
};
</script>

View file

@ -17,15 +17,15 @@ export default {
name: "ServerSpecifiedPropertyControl",
components: {
PropertyControl
PropertyControl,
},
props: {
propertyData: {
type: Object,
required: true,
}
}
},
},
};
</script>

View file

@ -1,5 +1,5 @@
<template>
<a @click.prevent="$emit('click')" class="sync-button">
<a class="sync-button" @click.prevent="$emit('click')">
<span
class="material-symbols-outlined sync-icon"
title="Reread value from microscope. Required if microscope is updated externally"
@ -11,7 +11,7 @@
<script>
export default {
name: "syncPropertyButton"
name: "SyncPropertyButton",
};
</script>
@ -24,14 +24,13 @@ export default {
cursor: pointer;
}
.material-symbols-outlined.sync-icon{
.material-symbols-outlined.sync-icon {
color: #888;
transition: transform 0.3s ease, color 0.3s ease;
}
.material-symbols-outlined.sync-icon:hover{
.material-symbols-outlined.sync-icon:hover {
transform: rotate(-90deg);
color: #c5247f;
}
</style>

View file

@ -1,14 +1,8 @@
<template>
<div class="uk-flex uk-flex-column uk-flex-center uk-height-1-1">
<div
v-if="$store.state.waiting"
class="uk-align-center"
uk-spinner="ratio: 3"
></div>
<div v-if="$store.state.waiting" class="uk-align-center" uk-spinner="ratio: 3"></div>
<div v-if="$store.state.waiting" class="uk-align-center">Loading...</div>
<span class="material-symbols-outlined uk-align-center error-icon"
>error_outline</span
>
<span class="material-symbols-outlined uk-align-center error-icon">error_outline</span>
<div v-if="$store.state.error" class="uk-align-center">
{{ $store.state.error }}
</div>
@ -29,7 +23,7 @@ export default {
data: function() {
return {};
}
},
};
</script>

View file

@ -4,13 +4,13 @@
<h2 class="uk-modal-title">Microscope Calibration</h2>
<component
v-if="currentTask"
:is="currentTask.component"
v-if="currentTask"
:key="taskIndex"
v-bind="currentTask.props"
:first="isFirstTask"
:final="isFinalTask"
:startOnLast="movingBackward"
:start-on-last="movingBackward"
@next="nextTask"
@back="previousTask"
/>
@ -26,7 +26,7 @@ import cameraStageMappingTask from "./calibrationWizardComponents/cameraStageMap
import finalStep from "./calibrationWizardComponents/finalStep.vue";
export default {
name: "calibrationWizard",
name: "CalibrationWizard",
components: {},
@ -36,7 +36,7 @@ export default {
availableCalibrationTasks: {},
tasks: [],
taskIndex: 0,
movingBackward: false
movingBackward: false,
};
},
@ -49,7 +49,7 @@ export default {
},
isFinalTask() {
return this.taskIndex === this.tasks.length - 1;
}
},
},
mounted() {
@ -57,12 +57,10 @@ export default {
// Check which Things are available on mount.
const allCalibrationTasks = {
camera: cameraCalibrationTask,
camera_stage_mapping: cameraStageMappingTask
camera_stage_mapping: cameraStageMappingTask,
};
this.availableCalibrationTasks = Object.fromEntries(
Object.entries(allCalibrationTasks).filter(([thing]) =>
this.thingAvailable(thing)
)
Object.entries(allCalibrationTasks).filter(([thing]) => this.thingAvailable(thing)),
);
},
@ -81,10 +79,7 @@ export default {
for (const name of calibrateableThings) {
try {
const thingNeedsCal = await this.readThingProperty(
name,
"calibration_required"
);
const thingNeedsCal = await this.readThingProperty(name, "calibration_required");
if (thingNeedsCal) {
needsCalibration.push(name);
}
@ -111,7 +106,7 @@ export default {
if (includeWelcome) {
tasks.push({
component: singleStepTask,
props: { stepComponent: welcomeStep }
props: { stepComponent: welcomeStep },
});
}
@ -124,7 +119,7 @@ export default {
// Always include the final step
tasks.push({
component: singleStepTask,
props: { stepComponent: finalStep }
props: { stepComponent: finalStep },
});
this.tasks = tasks;
@ -192,7 +187,7 @@ export default {
} else {
this.hide();
}
}
}
},
},
};
</script>

View file

@ -23,8 +23,8 @@ gets very confusing.
<div>
<h3 v-if="title">{{ title }}</h3>
<component
v-if="currentStep"
:is="currentStep.component"
v-if="currentStep"
:key="stepIndex"
v-bind="currentStep.props"
/>
@ -37,11 +37,7 @@ gets very confusing.
>
Back
</button>
<button
class="uk-button uk-button-primary uk-margin-left"
type="button"
@click="nextStep"
>
<button class="uk-button uk-button-primary uk-margin-left" type="button" @click="nextStep">
{{ nextButtonText }}
</button>
</p>
@ -50,28 +46,28 @@ gets very confusing.
<script>
export default {
name: "calibrationWizardTask",
name: "CalibrationWizardTask",
props: {
title: {
type: String,
default: null
default: null,
},
first: Boolean,
final: Boolean,
startOnLast: {
type: Boolean,
default: false
default: false,
},
steps: {
type: Array,
required: true
}
required: true,
},
},
data() {
return {
stepIndex: this.startOnLast ? this.steps.length - 1 : 0
stepIndex: this.startOnLast ? this.steps.length - 1 : 0,
};
},
@ -83,10 +79,8 @@ export default {
return !this.first || this.stepIndex > 0;
},
nextButtonText() {
return this.final && this.stepIndex === this.steps.length - 1
? "Finish"
: "Next";
}
return this.final && this.stepIndex === this.steps.length - 1 ? "Finish" : "Next";
},
},
methods: {
@ -111,7 +105,7 @@ export default {
} else {
this.$emit("next");
}
}
}
},
},
};
</script>

View file

@ -14,6 +14,6 @@
<script>
export default {
name: "camCalibrationExplanation"
name: "CamCalibrationExplanation",
};
</script>

View file

@ -6,10 +6,7 @@
<template #below-stream>
<div class="action-button-container">
<cameraCalibrationSettings
:show-extra-settings="false"
:camera-uri="cameraUri"
/>
<cameraCalibrationSettings :show-extra-settings="false" :camera-uri="cameraUri" />
</div>
</template>
</stepTemplateWithStream>
@ -20,18 +17,18 @@ import stepTemplateWithStream from "../stepTemplateWithStream.vue";
import cameraCalibrationSettings from "../../../tabContentComponents/settingsComponents/cameraSettingsComponents/cameraCalibrationSettings.vue";
export default {
name: "cameraMainCalibrationStep",
name: "CameraMainCalibrationStep",
components: {
stepTemplateWithStream,
cameraCalibrationSettings
cameraCalibrationSettings,
},
computed: {
cameraUri: function() {
return `${this.$store.getters.baseUri}/camera/`;
}
}
},
},
};
</script>

View file

@ -3,7 +3,7 @@
title="Camera Calibration"
:first="first"
:final="final"
:startOnLast="startOnLast"
:start-on-last="startOnLast"
:steps="steps"
@next="$emit('next')"
@back="$emit('back')"
@ -16,7 +16,7 @@ import camCalibrationExplanation from "./cameraCalibrationSteps/camCalibrationEx
import cameraMainCalibrationStep from "./cameraCalibrationSteps/cameraMainCalibrationStep.vue";
export default {
name: "cameraCalibrationTask",
name: "CameraCalibrationTask",
components: { calibrationWizardTask },
props: {
// Standard calibrationWizardTask props below:
@ -24,17 +24,14 @@ export default {
final: Boolean,
startOnLast: {
type: Boolean,
default: false
}
default: false,
},
},
data: function() {
return {
steps: [
{ component: camCalibrationExplanation },
{ component: cameraMainCalibrationStep }
]
steps: [{ component: camCalibrationExplanation }, { component: cameraMainCalibrationStep }],
};
}
},
};
</script>

View file

@ -3,7 +3,7 @@
title="Camera-Stage Mapping"
:first="first"
:final="final"
:startOnLast="startOnLast"
:start-on-last="startOnLast"
:steps="steps"
@next="$emit('next')"
@back="$emit('back')"
@ -17,7 +17,7 @@ import focusStep from "./csmSteps/focusStep.vue";
import runCsmStep from "./csmSteps/runCsmStep.vue";
export default {
name: "cameraCalibrationTask",
name: "CameraCalibrationTask",
components: { calibrationWizardTask },
props: {
// Standard calibrationWizardTask props below:
@ -25,18 +25,14 @@ export default {
final: Boolean,
startOnLast: {
type: Boolean,
default: false
}
default: false,
},
},
data: function() {
return {
steps: [
{ component: csmExplanation },
{ component: focusStep },
{ component: runCsmStep }
]
steps: [{ component: csmExplanation }, { component: focusStep }, { component: runCsmStep }],
};
}
},
};
</script>

View file

@ -17,6 +17,6 @@
<script>
export default {
name: "csmExplanation"
name: "CsmExplanation",
};
</script>

View file

@ -3,10 +3,7 @@
<p>
Use the buttons below to bring the sample into focus.
</p>
<p>
You may also adjust the z position with <b>page up</b> and
<b>page down</b>.
</p>
<p>You may also adjust the z position with <b>page up</b> and <b>page down</b>.</p>
<template #below-stream>
<div class="action-button-container">
<action-button
@ -16,7 +13,7 @@
:button-primary="false"
:submit-data="{ x: 0, y: 0, z: -100 }"
:submit-label="' - '"
:hideOnRun="false"
:hide-on-run="false"
:can-terminate="false"
/>
<action-button
@ -27,7 +24,7 @@
:submit-data="{ x: 0, y: 0, z: 100 }"
:submit-label="'+'"
:can-terminate="false"
:hideOnRun="false"
:hide-on-run="false"
/>
</div>
</template>
@ -38,12 +35,12 @@
import stepTemplateWithStream from "../stepTemplateWithStream.vue";
import ActionButton from "../../../labThingsComponents/actionButton.vue";
export default {
name: "cameraMainCalibrationStep",
name: "CameraMainCalibrationStep",
components: {
stepTemplateWithStream,
ActionButton
}
ActionButton,
},
};
</script>
<style scoped>

View file

@ -17,12 +17,12 @@ import stepTemplateWithStream from "../stepTemplateWithStream.vue";
import CSMCalibrationSettings from "../../../tabContentComponents/settingsComponents/CSMSettingsComponents/CSMCalibrationSettings.vue";
export default {
name: "cameraMainCalibrationStep",
name: "CameraMainCalibrationStep",
components: {
stepTemplateWithStream,
CSMCalibrationSettings
}
CSMCalibrationSettings,
},
};
</script>

View file

@ -14,6 +14,6 @@
<script>
export default {
name: "finalStep"
name: "FinalStep",
};
</script>

View file

@ -15,7 +15,7 @@ supplied by the wizard.
:title="title"
:first="first"
:final="final"
:startOnLast="startOnLast"
:start-on-last="startOnLast"
:steps="steps"
@next="$emit('next')"
@back="$emit('back')"
@ -26,36 +26,36 @@ supplied by the wizard.
import calibrationWizardTask from "./calibrationWizardTask.vue";
export default {
name: "singleStepTask",
name: "SingleStepTask",
components: { calibrationWizardTask },
props: {
// This must be sent
stepComponent: {
type: Object,
required: true
required: true,
},
// This is optional.
stepProps: {
type: Object,
default: () => ({})
default: () => ({}),
},
// Standard calibrationWizardTask props below:
title: {
type: String,
default: null
default: null,
},
first: Boolean,
final: Boolean,
startOnLast: {
type: Boolean,
default: false
}
default: false,
},
},
data: function() {
return {
steps: [{ component: this.stepComponent, props: this.stepProps }]
steps: [{ component: this.stepComponent, props: this.stepProps }],
};
}
},
};
</script>

View file

@ -10,11 +10,11 @@
import miniStreamDisplay from "../../genericComponents/miniStreamDisplay.vue";
export default {
name: "stepTemplateWithStream",
name: "StepTemplateWithStream",
components: {
miniStreamDisplay
}
miniStreamDisplay,
},
};
</script>

View file

@ -6,8 +6,8 @@
</b>
</p>
<p>
Your microscope will still function, however some functionality will be
limited, and image quality will likely suffer.
Your microscope will still function, however some functionality will be limited, and image
quality will likely suffer.
</p>
<p>
<b>Click Next to begin microscope calibration.</b>
@ -17,6 +17,6 @@
<script>
export default {
name: "welcomeStep"
name: "WelcomeStep",
};
</script>

View file

@ -1,24 +1,10 @@
<template>
<div>
<form
class="uk-form-stacked"
action=""
method="GET"
@submit="overrideAPIHost"
>
<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="newOrigin" name="overrideOrigin" class="uk-input" type="text" />
<label class="uk-form-label">
<input
v-model="reloadWhenOverridingOrigin"
class="uk-input uk-checkbox"
type="checkbox"
/>
<input v-model="reloadWhenOverridingOrigin" class="uk-input uk-checkbox" type="checkbox" />
Reload web app with new origin
</label>
<button class="uk-button uk-button-default uk-margin-small">
@ -38,7 +24,7 @@ export default {
data: function() {
return {
newOrigin: this.$store.state.origin,
reloadWhenOverridingOrigin: true
reloadWhenOverridingOrigin: true,
};
},
@ -62,8 +48,8 @@ export default {
this.$store.commit("changeOrigin", this.newOrigin);
event.preventDefault();
}
}
}
},
},
};
</script>

View file

@ -53,9 +53,7 @@
<div v-else-if="$store.state.waiting">
Loading...
</div>
<div v-else-if="$store.state.error">
<b>Error:</b> {{ $store.state.error }}
</div>
<div v-else-if="$store.state.error"><b>Error:</b> {{ $store.state.error }}</div>
<div v-else>No active connection</div>
</div>
</template>
@ -70,37 +68,33 @@ export default {
data: function() {
return {
version: undefined,
version_source: undefined
}
},
async mounted() {
let version_data = await this.readThingProperty(
"system",
"version_data"
);
this.version = version_data.version;
this.version_source = this.truncate(version_data.version_source);
version_source: undefined,
};
},
computed: {
things: function() {
return this.$store.getters["wot/thingDescriptions"];
}
},
},
async mounted() {
let version_data = await this.readThingProperty("system", "version_data");
this.version = version_data.version;
this.version_source = this.truncate(version_data.version_source);
},
methods: {
truncate(string, max_length=15) {
if (!(typeof string === 'string' || string instanceof String)) {
truncate(string, max_length = 15) {
if (!(typeof string === "string" || string instanceof String)) {
return string;
}
if (string.length <= max_length) {
return string;
}
return string.slice(0, max_length - 3) + "...";
}
}
},
},
};
</script>

View file

@ -30,7 +30,7 @@ export default {
components: {
devTools,
statusPane
}
statusPane,
},
};
</script>

View file

@ -1,5 +1,5 @@
<template>
<div class="uk-padding-small" v-observe-visibility="visibilityChanged">
<div v-observe-visibility="visibilityChanged" class="uk-padding-small">
<div>
<ul uk-accordion="multiple: true">
<li>
@ -35,7 +35,7 @@
thing="camera"
action="image_is_sample"
submit-label="Check Current Image"
:isDisabled="!ready"
:is-disabled="!ready"
:can-terminate="false"
:poll-interval="0.1"
@response="alertImageLabel"
@ -53,13 +53,13 @@ import InputFromSchema from "../../labThingsComponents/inputFromSchema.vue";
export default {
components: {
ActionButton,
InputFromSchema
InputFromSchema,
},
data() {
return {
backgroundDetectorStatus: undefined,
animate: false
animate: false,
};
},
@ -67,7 +67,13 @@ export default {
ready() {
const status = this.backgroundDetectorStatus;
return status && status.ready === true;
}
},
},
async created() {
this.backgroundDetectorStatus = await this.readThingProperty(
"camera",
"background_detector_status",
);
},
methods: {
@ -86,27 +92,17 @@ export default {
readSettings: async function() {
this.backgroundDetectorStatus = await this.readThingProperty(
"camera",
"background_detector_status"
"background_detector_status",
);
},
writeSettings: async function(requestedValue) {
await this.invokeAction(
"camera",
"update_detector_settings",
{"data": requestedValue}
);
await this.invokeAction("camera", "update_detector_settings", { data: requestedValue });
this.animate = true;
this.readSettings();
},
resetAnimate: function() {
this.animate = false;
}
this.animate = false;
},
},
async created() {
this.backgroundDetectorStatus = await this.readThingProperty(
"camera",
"background_detector_status"
);
}
};
</script>

View file

@ -19,7 +19,7 @@ export default {
components: {
paneBackgroundDetect,
streamDisplay
}
streamDisplay,
},
};
</script>

View file

@ -18,12 +18,7 @@
/>
</div>
<label class="uk-margin-small-right"
><input
v-model="invert.x"
class="uk-checkbox"
type="checkbox"
/>
Invert x</label
><input v-model="invert.x" class="uk-checkbox" type="checkbox" /> Invert x</label
>
</div>
@ -38,12 +33,7 @@
/>
</div>
<label class="uk-margin-small-right"
><input
v-model="invert.y"
class="uk-checkbox"
type="checkbox"
/>
Invert y</label
><input v-model="invert.y" class="uk-checkbox" type="checkbox" /> Invert y</label
>
</div>
@ -59,7 +49,7 @@
</div>
</div>
</div>
<br>
<br />
<action-button
thing="stage"
action="set_zero_position"
@ -165,8 +155,7 @@
<div v-else class="uk-text-warning">
<p>Stage positioning is disabled since no stage is connected.</p>
<p>
Please check all data and power connections to your motor controller
board.
Please check all data and power connections to your motor controller board.
</p>
</div>
</div>
@ -183,7 +172,7 @@ export default {
components: {
ActionButton,
syncPropertyButton
syncPropertyButton,
},
data: function() {
@ -193,16 +182,16 @@ export default {
stepSize: {
x: 200,
y: 200,
z: 50
z: 50,
},
invert: {
x: false,
y: false,
z: false
z: false,
},
setPosition: null,
isAutofocusing: 0,
moveLock: false
moveLock: false,
};
},
@ -214,11 +203,8 @@ export default {
return `${this.baseUri}/stage/position`;
},
moveInImageCoordinatesUri: function() {
return this.thingActionUrl(
"camera_stage_mapping",
"move_in_image_coordinates"
);
}
return this.thingActionUrl("camera_stage_mapping", "move_in_image_coordinates");
},
},
watch: {
@ -226,20 +212,19 @@ export default {
deep: true,
handler() {
this.setLocalStorageObj("navigation_stepSize", this.stepSize);
}
},
},
invert: {
deep: true,
handler() {
this.setLocalStorageObj("navigation_invert", this.invert);
}
}
},
},
},
async mounted() {
// Reload saved settings
this.stepSize =
this.getLocalStorageObj("navigation_stepSize") || this.stepSize;
this.stepSize = this.getLocalStorageObj("navigation_stepSize") || this.stepSize;
this.invert = this.getLocalStorageObj("navigation_invert") || this.invert;
let self = this;
// A global signal listener to perform a move action
@ -255,7 +240,7 @@ export default {
x_steps * this.stepSize.x * (this.invert.x ? -1 : 1),
y_steps * this.stepSize.y * (this.invert.y ? -1 : 1),
z_steps * this.stepSize.z * (this.invert.z ? -1 : 1),
false
false,
);
});
// Update the current position in text boxes
@ -287,7 +272,7 @@ export default {
this.setPosition = {
x: this.setPosition.x + x,
y: this.setPosition.y + y,
z: this.setPosition.z + z
z: this.setPosition.z + z,
};
}
await this.timeout(1); // Wait for Vue to update the position
@ -304,7 +289,7 @@ export default {
if (!this.moveInImageCoordinatesUri) {
this.modalError(
"Moving in image coordinates is not supported - you will need to upgrade your microscope's software."
"Moving in image coordinates is not supported - you will need to upgrade your microscope's software.",
);
}
@ -312,7 +297,7 @@ export default {
axios
.post(this.moveInImageCoordinatesUri, {
x: x, // NB the coordinates are numpy/PIL style, meaning X and Y are swapped.
y: y
y: y,
})
.then(() => {
this.updatePosition(); // Update the position in text boxes
@ -346,8 +331,8 @@ export default {
link.setAttribute("download", `OFM_${new Date().toISOString()}.jpeg`);
document.body.appendChild(link);
link.click();
}
}
},
},
};
</script>

View file

@ -19,7 +19,7 @@ export default {
components: {
paneControl,
streamDisplay
}
streamDisplay,
},
};
</script>

View file

@ -1,17 +1,9 @@
<template>
<div
v-observe-visibility="visibilityChanged"
class="uk-padding uk-padding-remove-top"
>
<div v-observe-visibility="visibilityChanged" class="uk-padding uk-padding-remove-top">
<!-- Logging nav bar -->
<nav
class="logging-navbar uk-navbar-container uk-navbar-transparent"
uk-navbar="mode: click"
>
<nav class="logging-navbar uk-navbar-container uk-navbar-transparent" uk-navbar="mode: click">
<!-- Left side controls -->
<div
class="uk-navbar-left uk-padding-remove-top uk-padding-remove-bottom"
>
<div class="uk-navbar-left uk-padding-remove-top uk-padding-remove-bottom">
<select v-model="filterLevel" class="uk-select">
<option v-for="level in allLevels" :key="level">{{ level }}</option>
</select>
@ -30,11 +22,11 @@
</button>
</div>
<div>
<EndpointButton
class="uk-button uk-width-1-1"
:URL="logFileURI"
buttonLabel="Download Log File"
:buttonPrimary="false"
<EndpointButton
class="uk-button uk-width-1-1"
:url="logFileURI"
button-label="Download Log File"
:button-primary="false"
/>
</div>
</div>
@ -50,7 +42,7 @@
class="logging-entry"
:class="{
'uk-alert-warning uk-alert': item.level == 'WARNING',
'uk-alert-danger uk-alert': item.level == 'ERROR'
'uk-alert-danger uk-alert': item.level == 'ERROR',
}"
>
<b>{{ formatDateTime(item.timestamp) }}: {{ item.level }}</b>
@ -64,11 +56,7 @@
More info...
</a>
<!-- eslint-disable vue/no-v-html -->
<div
v-if="item.expanded"
class="logging-message"
v-html="item.message"
></div>
<div v-if="item.expanded" class="logging-message" v-html="item.message"></div>
<!-- eslint-enable -->
</div>
</div>
@ -101,7 +89,7 @@ export default {
components: {
Paginate,
EndpointButton
EndpointButton,
},
data: function() {
@ -110,7 +98,7 @@ export default {
page: 1,
logs: [],
allLevels: ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
filterLevel: "WARNING"
filterLevel: "WARNING",
};
},
@ -142,7 +130,7 @@ export default {
},
numberOfPages: function() {
return Math.floor(this.filteredItems.length / this.maxitems);
}
},
},
methods: {
@ -172,7 +160,7 @@ export default {
summary: m[3],
message: this.escapeText(m[3]),
sequence: logs.length,
expanded: false
expanded: false,
});
} else if (logs) {
// If a line does not look like a log entry, append it to the last
@ -198,9 +186,7 @@ export default {
} else {
// if there's no existing log message to append to, discard lines
// until we find one.
console.log(
"Ignored non-matching lines at the start of the log file."
);
console.log("Ignored non-matching lines at the start of the log file.");
continue;
}
}
@ -216,8 +202,8 @@ export default {
let div = document.createElement("div");
div.innerText = unsafeText;
return div.innerHTML;
}
}
},
},
};
</script>

View file

@ -1,11 +1,12 @@
<template>
<!-- Grid managing tab content -->
<div class="container">
<h1 id="power-title"> Power</h1>
<p id="power-msg"> It's essential to turn off your OpenFlexure Microscope here before unplugging it.
<br>
<br>
Unplugging the microscope unexpectedly can damage the SD card or onboard computer.
<h1 id="power-title">Power</h1>
<p id="power-msg">
It's essential to turn off your OpenFlexure Microscope here before unplugging it.
<br />
<br />
Unplugging the microscope unexpectedly can damage the SD card or onboard computer.
</p>
<div class="buttons-container">
<button
@ -37,44 +38,38 @@ export default {
data: function() {
return {
isRaspberrypi: undefined,
}
};
},
computed: {
things: function() {
return this.$store.getters["wot/thingDescriptions"];
}
},
},
async mounted() {
this.isRaspberrypi = await this.readThingProperty(
"system",
"is_raspberrypi"
);
this.isRaspberrypi = await this.readThingProperty("system", "is_raspberrypi");
},
methods: {
systemRequest: function(action) {
let message = "";
if (action == "reboot") {
message = "Restart microscope?"
}
else {
message = "Shutdown microscope?"
message = "Restart microscope?";
} else {
message = "Shutdown microscope?";
}
this.modalConfirm(message).then(
() => {
this.$store.commit("resetState");
this.$store.commit("wot/deleteAllThingDescriptions");
// Post and silence errors
axios
.post(this.thingActionUrl("system", action))
.catch(() => {});
axios.post(this.thingActionUrl("system", action)).catch(() => {});
},
() => {}
() => {},
);
}
}
},
},
};
</script>
@ -82,7 +77,6 @@ export default {
// Custom UIkit CSS modifications
@import "../../assets/less/theme.less";
.container {
display: flex;
flex-direction: column;
@ -109,7 +103,7 @@ export default {
.shutdown-button {
display: inline;
text-align:center;
text-align: center;
margin: 30px 30px 30px 30px;
}
</style>
</style>

View file

@ -1,7 +1,7 @@
<template>
<div v-observe-visibility="visibilityChanged">
<div id="openseadragon"></div>
</div>
<div v-observe-visibility="visibilityChanged">
<div id="openseadragon"></div>
</div>
</template>
<script>
@ -13,26 +13,25 @@ export default {
props: {
src: {
type: String,
required: true
required: true,
},
brightness: {
type: Number,
required: true
required: true,
},
contrast: {
type: Number,
required: true
required: true,
},
saturation: {
type: Number,
required: true
}
required: true,
},
},
data: function() {
return {
osdViewer: null
osdViewer: null,
};
},
@ -41,7 +40,7 @@ export default {
immediate: true,
handler(newVal) {
this.loadOpenSeaDragon(newVal);
}
},
},
brightness() {
this.updateFilter();
@ -51,7 +50,7 @@ export default {
},
saturation() {
this.updateFilter();
}
},
},
async mounted() {
@ -69,7 +68,7 @@ export default {
visibilityChanged(isVisible) {
if (isVisible) {
this.loadOpenSeaDragon();
}else{
} else {
this.osdViewer.destroy();
}
},
@ -79,13 +78,13 @@ export default {
}
this.osdViewer = OpenSeaDragon({
id: "openseadragon",
crossOriginPolicy: 'Anonymous',
crossOriginPolicy: "Anonymous",
tileSources: this.src,
showNavigationControl: false,
maxZoomPixelRatio: 2,
gestureSettingsMouse: {
clickToZoom: false
}
clickToZoom: false,
},
});
this.updateFilter();
@ -106,7 +105,7 @@ export default {
this.osdViewer.setFullScreen(true);
}
},
}
},
};
</script>
@ -117,4 +116,4 @@ export default {
background-color: black;
z-index: 1;
}
</style>
</style>

View file

@ -2,9 +2,7 @@
<div class="uk-card">
<div class="uk-card-body">
<div class="uk-card-media-top">
<div
class="view-image uk-padding-remove uk-height-1-1"
>
<div class="view-image uk-padding-remove uk-height-1-1">
<img
id="thumbnail-stitched-image"
class="thumbnail-fit"
@ -15,39 +13,36 @@
</div>
</div>
<h3 class="uk-card-title scan-card-title">{{ scanData.name }}</h3>
<h4 class="ongoing-msg" v-if="ongoing">Scan in progress</h4>
<div class="button-container" v-if="!ongoing">
<h4 v-if="ongoing" class="ongoing-msg">Scan in progress</h4>
<div v-if="!ongoing" class="button-container">
<div class="uk-button-group scan-card-buttons">
<action-button
class="uk-width-1-2"
thing="smart_scan"
action="download_zip"
submit-label="Download All"
:can-terminate="false"
:submit-data="{ scan_name: scanData.name }"
:button-primary="true"
@response="downloadZipFile"
@error="modalError"
class="uk-width-1-2"
thing="smart_scan"
action="download_zip"
submit-label="Download All"
:can-terminate="false"
:submit-data="{ scan_name: scanData.name }"
:button-primary="true"
@response="downloadZipFile"
@error="modalError"
/>
<EndpointButton
class="uk-width-1-2"
:buttonPrimary=true
:isDisabled=!scanData.stitch_available
:URL="downloadStitchFile"
buttonLabel="Download JPEG"
/>
:button-primary="true"
:is-disabled="!scanData.stitch_available"
:url="downloadStitchFile"
button-label="Download JPEG"
/>
</div>
<button
class="uk-button uk-button-default uk-width-1-1"
@click="deleteScan"
>
<button class="uk-button uk-button-default uk-width-1-1" @click="deleteScan">
Delete
</button>
<action-button
v-if="scanData.can_stitch | (scanData.stitch_available & !scanData.dzi)"
submit-label="Stitch Images"
thing="smart_scan"
action="stitch_scan"
v-if="scanData.can_stitch | (scanData.stitch_available & !scanData.dzi)"
:can-terminate="true"
:submit-data="{ scan_name: scanData.name }"
:button-primary="false"
@ -55,10 +50,11 @@
@error="modalError"
/>
<button
v-if="scanData.dzi" class="uk-button uk-button-default uk-width-1-1"
v-if="scanData.dzi"
class="uk-button uk-button-default uk-width-1-1"
@click="requestViewer"
>
Show Stitched Scan
Show Stitched Scan
</button>
</div>
<div class="scan-info">
@ -68,9 +64,15 @@
<li>duration: {{ formatDuration(scanData.duration) }}</li>
</ul>
<ul v-if="!ongoing">
<li v-if="scanData.number_of_images<3" class="warning-msg">Not enough images to stitch</li>
<li v-else-if="!scanData.dzi && scanData.stitch_available" class="alert-msg">Interactive preview not available</li>
<li v-else-if="!scanData.stitch_available" class="alert-msg">High quality stitch not available</li>
<li v-if="scanData.number_of_images < 3" class="warning-msg">
Not enough images to stitch
</li>
<li v-else-if="!scanData.dzi && scanData.stitch_available" class="alert-msg">
Interactive preview not available
</li>
<li v-else-if="!scanData.stitch_available" class="alert-msg">
High quality stitch not available
</li>
</ul>
</div>
</div>
@ -90,16 +92,16 @@ export default {
props: {
scanData: {
type: Object,
required: true
required: true,
},
scansUri: {
type: String,
required: true
required: true,
},
ongoing: {
type: Boolean,
required: true
}
required: true,
},
},
computed: {
@ -114,7 +116,7 @@ export default {
methods: {
formatDate(timestamp) {
// Multiply by 1000 as JS uses ms not s
let d = new Date(timestamp*1000);
let d = new Date(timestamp * 1000);
// Convert to a string in a very javascript way!
let yyyy = d.getFullYear();
let mm = d.getMonth() + 1;
@ -146,14 +148,14 @@ export default {
requestViewer() {
// Notify parent that thumbnail was clicked
if (!this.ongoing) {
this.$emit('viewer-requested', this.scanData);
this.$emit("viewer-requested", this.scanData);
}
},
async deleteScan() {
try {
await this.modalConfirm(`Are you sure you want to delete ${this.scanData.name}?`);
await axios.delete(`${this.scansUri}/${this.scanData.name}`);
this.$emit('update-requested');
this.$emit("update-requested");
this.modalNotify(`Deleted ${this.scanData.name}`);
} catch (e) {
// if the confirmation was cancelled, it's rejected with null error
@ -170,15 +172,15 @@ export default {
console.log(link);
document.body.appendChild(link);
link.click();
}
}
}
},
},
};
</script>
<style lang="less" scoped>
ul {
display: block;
text-align: center;
list-style-type:none;
list-style-type: none;
margin: 5px 0px 10px 0px;
padding: 0;
}
@ -207,5 +209,4 @@ ul {
text-align: center;
padding: 2rem 0;
}
</style>
</style>

View file

@ -1,10 +1,6 @@
<template>
<div id="scan-modal" ref="scanModal" uk-modal>
<div
id="scan-modal-body"
class="uk-modal-dialog uk-modal-body"
v-if="selectedScan"
>
<div v-if="selectedScan" id="scan-modal-body" class="uk-modal-dialog uk-modal-body">
<h2 id="scan-modal-title" class="uk-modal-title">
{{ selectedScan.name }}
<button class="uk-modal-close uk-float-right" type="button">
@ -16,11 +12,7 @@
</h2>
<!-- Viewer -->
<div
v-if="selectedScanDZI"
id="viewer_container"
class="viewer_container"
>
<div v-if="selectedScanDZI" id="viewer_container" class="viewer_container">
<OpenSeadragonViewer
id="openseadragon"
ref="openseadragon"
@ -32,40 +24,19 @@
</div>
<!-- Controls -->
<div
v-if="selectedScanDZI"
class="viewer-controls"
>
<div v-if="selectedScanDZI" class="viewer-controls">
<div class="controlsContainer">
<label>
Brightness
<input
type="range"
min="0.2"
max="1.8"
step="0.01"
v-model.number="brightness"
/>
<input v-model.number="brightness" type="range" min="0.2" max="1.8" step="0.01" />
</label>
<label>
Contrast
<input
type="range"
min="0.2"
max="1.8"
step="0.01"
v-model.number="contrast"
/>
<input v-model.number="contrast" type="range" min="0.2" max="1.8" step="0.01" />
</label>
<label>
Saturation
<input
type="range"
min="0"
max="2"
step="0.01"
v-model.number="saturation"
/>
<input v-model.number="saturation" type="range" min="0" max="2" step="0.01" />
</label>
</div>
@ -155,26 +126,26 @@ input[type="range"] {
}
.controlsContainer {
display: flex;
justify-content: center;
gap: 1.5rem;
display: flex;
justify-content: center;
gap: 1.5rem;
}
.viewer-controls {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
margin-top: 0.5rem;
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
margin-top: 0.5rem;
}
.viewer_container {
flex: 1 1 auto;
position: relative;
overflow: hidden;
flex: 1 1 auto;
position: relative;
overflow: hidden;
}
.reset-button{
.reset-button {
margin-top: 0.5rem;
}
</style>

View file

@ -1,12 +1,10 @@
<template>
<div
v-observe-visibility="visibilityChanged"
class="galleryDisplay uk-padding uk-padding-remove-top">
class="galleryDisplay uk-padding uk-padding-remove-top"
>
<!-- Gallery nav bar -->
<nav
class="gallery-navbar uk-navbar-container uk-navbar-transparent"
uk-navbar="mode: click"
>
<nav class="gallery-navbar uk-navbar-container uk-navbar-transparent" uk-navbar="mode: click">
<!-- Right side buttons -->
<div class="uk-navbar-right">
<div class="uk-grid">
@ -50,18 +48,17 @@
<ScanViewerModal
ref="scanViewer"
:selectedScan="selectedScan"
:baseUri="$store.getters.baseUri"
:selected-scan="selectedScan"
:base-uri="$store.getters.baseUri"
/>
<!-- Gallery -->
<div
v-if="$store.getters.ready"
class="uk-padding-remove-top"
uk-lightbox="toggle: .lightbox-link"
>
<!-- Gallery capture cards -->
<!-- Gallery capture cards -->
<div class="gallery-grid uk-grid-match" uk-grid>
<div v-if="scansEmpty">
<h2>No scans available</h2>
@ -99,7 +96,7 @@ export default {
scans: [],
ongoing: null,
selectedScan: null,
osdViewer: null
osdViewer: null,
};
},
@ -109,19 +106,19 @@ export default {
"smart_scan",
"scans",
"readproperty",
true
true,
);
},
scansEmpty() {
return this.scans.length == 0;
},
selectedScanDZI() {
if (this.selectedScan && this.selectedScan.dzi!="") {
if (this.selectedScan && this.selectedScan.dzi != "") {
return `${this.$store.getters.baseUri}/scans/${this.selectedScan.name}/images/${this.selectedScan.dzi}`;
} else {
return null;
}
}
},
},
async mounted() {
@ -132,7 +129,7 @@ export default {
this.updateScans();
});
this.$root.$on("modalClosed", () => {
// Handle the modal closed event here
// Handle the modal closed event here
this.updateScans();
});
},
@ -151,7 +148,7 @@ export default {
// If the connection is now disconnected, empty capture list
this.captures = {};
}
}
},
);
},
@ -175,8 +172,8 @@ export default {
async updateScans() {
try {
let scans_information = await this.readThingProperty("smart_scan", "scans");
let scans = scans_information.scans
this.ongoing = scans_information.ongoing
let scans = scans_information.scans;
this.ongoing = scans_information.ongoing;
if (!scans | (scans.length == 0)) {
this.scans = scans;
}
@ -194,13 +191,13 @@ export default {
}
},
isOngoing(name) {
return name === this.ongoing
return name === this.ongoing;
},
async deleteAllScans() {
try {
await this.modalConfirm(
"Are you sure you want to delete all scans from the microscope? " +
"This is <b>irreversible</b>!"
"This is <b>irreversible</b>!",
);
await axios.delete(`${this.scansUri}`);
await this.updateScans();
@ -217,9 +214,9 @@ export default {
} else {
this.modalError("Scan not stitched for viewing in webapp, please download or stitch");
}
}
}
}
},
},
};
</script>
<style lang="less" scoped>
@ -238,4 +235,4 @@ export default {
margin-top: 5px;
margin-bottom: 2px;
}
</style>
</style>

View file

@ -1,15 +1,10 @@
<template>
<div
id="CSMSettings"
class="uk-grid uk-grid-divider uk-child-width-expand"
uk-grid
>
<div id="CSMSettings" class="uk-grid uk-grid-divider uk-child-width-expand" uk-grid>
<div class="uk-width-xlarge">
<h3>Camera to Stage Mapping</h3>
<p>
Camera-stage mapping allows the stage to move relative to the camera
view. This enables functions like click-to-move, and more precise tile
scans.
Camera-stage mapping allows the stage to move relative to the camera view. This enables
functions like click-to-move, and more precise tile scans.
</p>
<CSMCalibrationSettings />
</div>
@ -29,8 +24,8 @@ export default {
components: {
CSMCalibrationSettings,
miniStreamDisplay
}
miniStreamDisplay,
},
};
</script>

View file

@ -1,5 +1,5 @@
<template>
<div v-observe-visibility="visibilityChanged" id="CSMCalibrationSettings">
<div id="CSMCalibrationSettings" v-observe-visibility="visibilityChanged">
<!--Show auto calibrate if default plugin is enabled-->
<div v-if="'calibrate_xy' in actions" class="uk-margin-small">
<action-button
@ -26,15 +26,18 @@
>
Download Calibration Data
</button>
<div v-if="this.csmMatrix!='undefined'" style="margin:10px;">
<details><summary>Calibration Details</summary>
<strong>CSM calculated for images with a resolution of {{ csmResolution }}</strong>
<ul>
<li>Current CSM Matrix: <tt>{{ csmMatrix }}</tt></li>
<li>Pixels per motor step: {{ csmRatio }}</li>
<li>Full field of view: {{ csmFOV }} motor steps</li>
</ul>
</details>
<div v-if="csmMatrix != 'undefined'" style="margin:10px;">
<details
><summary>Calibration Details</summary>
<strong>CSM calculated for images with a resolution of {{ csmResolution }}</strong>
<ul>
<li>
Current CSM Matrix: <tt>{{ csmMatrix }}</tt>
</li>
<li>Pixels per motor step: {{ csmRatio }}</li>
<li>Full field of view: {{ csmFOV }} motor steps</li>
</ul>
</details>
</div>
</div>
</template>
@ -47,36 +50,32 @@ export default {
name: "CSMCalibrationSettings",
components: {
ActionButton
ActionButton,
},
props: {
showExtraSettings: {
type: Boolean,
required: false,
default: true
}
default: true,
},
},
data() {
return {
csmMatrix: "undefined",
csmResolution: "undefined",
csmRatio: "undefined"
csmRatio: "undefined",
};
},
computed: {
actions() {
return this.$store.getters["wot/thingDescription"]("camera_stage_mapping")
.actions;
return this.$store.getters["wot/thingDescription"]("camera_stage_mapping").actions;
},
properties() {
return this.$store.getters["wot/thingDescription"]("camera_stage_mapping")
.properties;
}
return this.$store.getters["wot/thingDescription"]("camera_stage_mapping").properties;
},
},
methods: {
@ -87,10 +86,7 @@ export default {
},
getCalibrationData: async function() {
try {
let data = await this.readThingProperty(
"camera_stage_mapping",
"last_calibration"
);
let data = await this.readThingProperty("camera_stage_mapping", "last_calibration");
if (data == {}) {
throw "No calibration data available.";
}
@ -107,33 +103,29 @@ export default {
},
updateDisplayedCSM: async function() {
let csmMatrix = await this.readThingProperty(
"camera_stage_mapping",
"image_to_stage_displacement_matrix"
);
this.csmResolution = await this.readThingProperty(
"camera_stage_mapping",
"image_resolution"
);
let streamResolution = await this.readThingProperty(
"camera",
"stream_resolution"
"image_to_stage_displacement_matrix",
);
this.csmResolution = await this.readThingProperty("camera_stage_mapping", "image_resolution");
let streamResolution = await this.readThingProperty("camera", "stream_resolution");
csmMatrix[0][0] = Number(csmMatrix[0][0].toFixed(3));
csmMatrix[0][1] = Number(csmMatrix[0][1].toFixed(3));
csmMatrix[1][0] = Number(csmMatrix[1][0].toFixed(3));
csmMatrix[1][1] = Number(csmMatrix[1][1].toFixed(3));
this.csmMatrix = csmMatrix;
this.csmRatio = Number((Math.abs(csmMatrix[1][0]) + Math.abs(csmMatrix[0][1])) / 2).toFixed(3);
this.csmRatio = Number((Math.abs(csmMatrix[1][0]) + Math.abs(csmMatrix[0][1])) / 2).toFixed(
3,
);
this.csmFOV = [
Number((streamResolution[0]**2 * this.csmRatio / this.csmResolution[1]).toFixed(0)),
Number((streamResolution[1]**2 * this.csmRatio / this.csmResolution[0]).toFixed(0)),
]
Number(((streamResolution[0] ** 2 * this.csmRatio) / this.csmResolution[1]).toFixed(0)),
Number(((streamResolution[1] ** 2 * this.csmRatio) / this.csmResolution[0]).toFixed(0)),
];
},
onRecalibrateResponse: function() {
this.modalNotify("Finished stage-to-camera calibration.");
this.updateDisplayedCSM();
}
}
},
},
};
</script>

View file

@ -34,8 +34,19 @@ export default {
},
set(value) {
this.$store.commit("changeAppTheme", value);
}
}
},
},
},
watch: {
appTheme: function() {
this.setLocalStorageObj("appTheme", this.appTheme);
},
},
mounted() {
// Try loading settings from localStorage. If null, don't change.
this.appTheme = this.getLocalStorageObj("appTheme") || this.appTheme;
},
methods: {
@ -45,19 +56,8 @@ export default {
} else {
await document.exitFullscreen();
}
}
},
},
watch: {
appTheme: function() {
this.setLocalStorageObj("appTheme", this.appTheme);
}
},
mounted() {
// Try loading settings from localStorage. If null, don't change.
this.appTheme = this.getLocalStorageObj("appTheme") || this.appTheme;
}
};
</script>

View file

@ -33,7 +33,7 @@ export default {
components: {
cameraCalibrationSettings,
miniStreamDisplay,
ServerSpecifiedPropertyControl
ServerSpecifiedPropertyControl,
},
data() {
@ -45,15 +45,12 @@ export default {
computed: {
cameraUri: function() {
return `${this.$store.getters.baseUri}/camera/`;
}
},
},
async created() {
this.manualCameraSettings = await this.readThingProperty(
"camera",
"manual_camera_settings"
);
}
this.manualCameraSettings = await this.readThingProperty("camera", "manual_camera_settings");
},
};
</script>

View file

@ -28,44 +28,44 @@ export default {
name: "CameraCalibrationSettings",
components: {
ServerSpecifiedActionButton
ServerSpecifiedActionButton,
},
props: {
showExtraSettings: {
type: Boolean,
required: false,
default: true
default: true,
},
cameraUri: {
type: String,
required: true
}
required: true,
},
},
data() {
return {
primaryCalibrationActions: [],
secondaryCalibrationActions: []
secondaryCalibrationActions: [],
};
},
computed: {
actions() {
return this.$store.getters["wot/thingDescription"]("camera").actions;
}
},
},
async created() {
this.primaryCalibrationActions = await this.readThingProperty(
"camera",
"primary_calibration_actions"
"primary_calibration_actions",
);
this.secondaryCalibrationActions = await this.readThingProperty(
"camera",
"secondary_calibration_actions"
"secondary_calibration_actions",
);
}
},
};
</script>

View file

@ -1,31 +1,30 @@
<template>
<div id="stageSettings" class="uk-width-large" v-observe-visibility="visibilityChanged">
<div id="stageSettings" v-observe-visibility="visibilityChanged" class="uk-width-large">
The microscope stage is a <b>{{ stageType }}</b>
<div>
<div class="uk-margin">
<p>Your z motor is currently {{ z_inverted }} inverted.</p>
<p>
<br>
Your z motor is currently {{ this.z_inverted }} inverted.
<br>
<br>
We expect that moving in +z:
We expect that moving in +z:
</p>
<ul>
<li> Moves your objective up, towards the sample and illumination</li>
<li> Turns the exposed z gear anti-clockwise (when viewed from above)</li>
<li>Moves your objective up, towards the sample and illumination</li>
<li>Turns the exposed z gear anti-clockwise (when viewed from above)</li>
</ul>
If this is not the case, click the button below to switch.
<p>
If this is not the case, click the button below to switch.
</p>
<div class="uk-margin">
<div class="uk-margin">
<action-button
class="uk-width-1-2"
thing="stage"
action="invert_axis_direction"
:submit-data="{ axis: 'z' }"
submit-label="Invert z"
@response=readAxis()
/>
</div>
<action-button
class="uk-width-1-2"
thing="stage"
action="invert_axis_direction"
:submit-data="{ axis: 'z' }"
submit-label="Invert z"
@response="readAxis()"
/>
</div>
</div>
</div>
</div>
@ -48,26 +47,23 @@ export default {
};
},
methods:{
computed: {
stageType: function() {
return this.thingDescription("stage").title;
},
},
methods: {
visibilityChanged(isVisible) {
if (isVisible) {
this.readAxis();
}
},
async readAxis() {
let axes_inverted = await this.readThingProperty(
"stage",
"axis_inverted"
);
this.z_inverted = axes_inverted['z'] ? "" : "not ";
}
let axes_inverted = await this.readThingProperty("stage", "axis_inverted");
this.z_inverted = axes_inverted["z"] ? "" : "not ";
},
},
computed: {
stageType: function() {
return this.thingDescription("stage").title;
}
}
};
</script>

View file

@ -3,8 +3,8 @@
<div>
<h3>Stream Settings</h3>
<label
><input v-model="disableStream" class="uk-checkbox" type="checkbox" />
Disable Web Stream</label
><input v-model="disableStream" class="uk-checkbox" type="checkbox" /> Disable Web
Stream</label
>
<p class="uk-margin-small">
This will disable the embedded web stream of the camera.
@ -29,8 +29,8 @@ export default {
},
set(value) {
this.$store.commit("changeDisableStream", value);
}
}
},
},
},
watch: {
@ -38,8 +38,8 @@ export default {
// (the next 3 functions all relate to this)
disableStream: function(newValue) {
this.setLocalStorageObj("disableStream", newValue);
}
}
},
},
};
</script>

View file

@ -1,9 +1,7 @@
<template>
<!-- Grid managing tab content -->
<div uk-grid class="uk-height-1-1 uk-margin-remove uk-padding-remove">
<calibrationWizard
ref="calibrationWizard"
></calibrationWizard>
<calibrationWizard ref="calibrationWizard"></calibrationWizard>
<div class="settings-nav">
<ul class="uk-nav uk-nav-default">
<li class="uk-nav-header">Application Settings</li>
@ -22,11 +20,13 @@
</li>
<li class="uk-nav-header">Microscope Settings</li>
<button
submit-label="Launch Calibration Wizard"
type="button"
class="uk-button uk-button-default uk-width-1-1"
@click="startModals"
>Launch Calibration Wizard</button>
submit-label="Launch Calibration Wizard"
type="button"
class="uk-button uk-button-default uk-width-1-1"
@click="startModals"
>
Launch Calibration Wizard
</button>
<li>
<tabIcon
id="settings-camera-icon"
@ -69,42 +69,26 @@
</ul>
</div>
<div class="view-component uk-width-expand uk-padding-small">
<tabContent
tab-i-d="display"
:require-connection="false"
:current-tab="currentTab"
>
<tabContent tab-i-d="display" :require-connection="false" :current-tab="currentTab">
<div class="settings-pane uk-padding-small">
<appSettings />
<streamSettings />
</div>
</tabContent>
<tabContent
tab-i-d="camera"
:require-connection="true"
:current-tab="currentTab"
>
<tabContent tab-i-d="camera" :require-connection="true" :current-tab="currentTab">
<div class="settings-pane uk-padding-small">
<cameraSettings />
</div>
</tabContent>
<tabContent
tab-i-d="stage"
:require-connection="true"
:current-tab="currentTab"
>
<tabContent tab-i-d="stage" :require-connection="true" :current-tab="currentTab">
<div class="settings-pane uk-padding-small">
<stageSettings />
</div>
</tabContent>
<tabContent
tab-i-d="mapping"
:require-connection="true"
:current-tab="currentTab"
>
<tabContent tab-i-d="mapping" :require-connection="true" :current-tab="currentTab">
<div class="settings-pane uk-padding-small">
<CSMSettings />
</div>
@ -136,13 +120,13 @@ export default {
appSettings,
tabIcon,
tabContent,
calibrationWizard
calibrationWizard,
},
data: function() {
return {
selected: "display",
currentTab: "display"
currentTab: "display",
};
},
@ -154,8 +138,8 @@ export default {
},
startModals: function() {
this.$refs.calibrationWizard.force_show();
}
}
},
},
};
</script>

View file

@ -29,7 +29,7 @@
thing-name="autofocus"
property-name="stack_dz"
label="Stack dz (steps)"
/>
/>
</div>
<div class="uk-margin">
<propertyControl
@ -83,12 +83,7 @@
</ul>
<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"
/>
<input v-model="scan_name" class="uk-input uk-form-small" type="text" name="Scan Name" />
</div>
<div class="uk-margin">
<action-button
@ -110,11 +105,7 @@
Live stitching preview
</h2>
<mini-stream-display v-if="displayImageOnRight" />
<action-log-display
id="log-display"
:log="log"
:task-status="taskStatus"
/>
<action-log-display id="log-display" :log="log" :task-status="taskStatus" />
<action-progress-bar :progress="progress" :task-status="taskStatus" />
<button
v-if="cancellable"
@ -124,11 +115,7 @@
>
Cancel
</button>
<div
v-if="!cancellable"
class="uk-margin uk-grid-small uk-child-width-expand"
uk-grid
>
<div v-if="!cancellable" class="uk-margin uk-grid-small uk-child-width-expand" uk-grid>
<button
type="button"
class="uk-button"
@ -182,7 +169,7 @@ export default {
actionLogDisplay,
actionProgressBar,
MiniStreamDisplay,
ActionButton
ActionButton,
},
data() {
@ -195,7 +182,7 @@ export default {
progress: null,
log: [],
lastStitchedImage: null,
scan_name: ""
scan_name: "",
};
},
@ -208,7 +195,7 @@ export default {
},
displayImageOnRight() {
return this.scanning & (this.lastStitchedImage !== null);
}
},
},
methods: {
@ -225,17 +212,11 @@ export default {
async pollScan() {
if (this.cancellable) {
// while the scan is running
let mtime = await this.readThingProperty(
"smart_scan",
"latest_preview_stitch_time"
);
let mtime = await this.readThingProperty("smart_scan", "latest_preview_stitch_time");
if (mtime !== null) {
this.lastStitchedImage = `${this.$store.getters.baseUri}/smart_scan/latest_preview_stitch.jpg?t=${mtime}`;
}
this.lastScanName = await this.readThingProperty(
"smart_scan",
"latest_scan_name"
);
this.lastScanName = await this.readThingProperty("smart_scan", "latest_scan_name");
setTimeout(this.pollScan, 1000); // keep rescheduling until it's stopped
}
},
@ -249,8 +230,8 @@ export default {
console.log(link);
document.body.appendChild(link);
link.click();
}
}
},
},
};
</script>

View file

@ -44,7 +44,7 @@ export default {
isVisible: false,
displaySize: [0, 0],
displayPosition: [0, 0],
resizeTimeoutId: setTimeout(this.doneResizing, 500)
resizeTimeoutId: setTimeout(this.doneResizing, 500),
};
},
@ -58,7 +58,7 @@ export default {
},
streamImgUri: function() {
return `${this.$store.getters.baseUri}/camera/mjpeg_stream`;
}
},
},
mounted() {
@ -119,18 +119,14 @@ export default {
// factor.
let scale = Math.max(
event.target.naturalWidth / event.target.offsetWidth,
event.target.naturalHeight / event.target.offsetHeight
event.target.naturalHeight / event.target.offsetHeight,
);
let xRelative = (0.5 * event.target.offsetWidth - xCoordinate) * scale;
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
);
this.$root.$emit("globalMoveInImageCoordinatesEvent", -xRelative, -yRelative);
},
handleResize: function() {
@ -167,18 +163,13 @@ export default {
let windowChromeHeight = window.outerHeight - window.innerHeight;
let elementPositionOnDisplay = [
Math.max(0, windowPositionOnDisplay[0] + elementPositionOnWindow[0]),
Math.max(
0,
windowPositionOnDisplay[1] +
elementPositionOnWindow[1] +
windowChromeHeight
)
Math.max(0, windowPositionOnDisplay[1] + elementPositionOnWindow[1] + windowChromeHeight),
];
this.displaySize = elementSize;
this.displayPosition = elementPositionOnDisplay;
}
}
},
},
};
</script>

View file

@ -14,7 +14,7 @@ export default {
name: "ViewContent",
components: {
streamDisplay
}
streamDisplay,
},
};
</script>

View file

@ -6,19 +6,18 @@ import UIkit from "uikit";
import VueObserveVisibility from "vue-observe-visibility";
// Import MD icons
import "material-symbols/outlined.css";
import version from './version.js'
import version from "./version.js";
// UIKit overrides
UIkit.mixin(
{
data: {
animation: false
}
animation: false,
},
},
"accordion"
"accordion",
);
// Use visibility observer
@ -38,12 +37,7 @@ Vue.mixin({
return this.$store.getters["wot/thingAvailable"](thing);
},
async readThingProperty(thing, property, silence_errors = false) {
let url = this.$store.getters["wot/thingPropertyUrl"](
thing,
property,
"readproperty",
false
);
let url = this.$store.getters["wot/thingPropertyUrl"](thing, property, "readproperty", false);
try {
let response = await axios.get(url);
return response.data;
@ -57,7 +51,7 @@ Vue.mixin({
thing,
property,
"writeproperty",
false
false,
);
// `false` fails because axios somehow eats it!
// Other values should not be stringified or pydantic
@ -68,12 +62,7 @@ Vue.mixin({
await axios.put(url, value);
},
async invokeAction(thing, action, data) {
let url = this.$store.getters["wot/thingActionUrl"](
thing,
action,
"invokeaction",
false
);
let url = this.$store.getters["wot/thingActionUrl"](thing, action, "invokeaction", false);
try {
let response = await axios.post(url, data);
return response;
@ -87,7 +76,7 @@ Vue.mixin({
thing,
action,
"invokeaction",
allow_missing
allow_missing,
);
return url;
},
@ -109,14 +98,14 @@ Vue.mixin({
},
function() {
reject();
}
},
)
.finally(function() {
// Re-enable the GPU preview, if it was active before the modal
if (context.$store.state.autoGpuPreview) {
context.$root.$emit("globalTogglePreview", true);
}
context.$root.$emit("modalClosed");
context.$root.$emit("modalClosed");
});
};
return new Promise(showModal);
@ -125,7 +114,7 @@ Vue.mixin({
modalNotify: function(message, status = "success") {
UIkit.notification({
message: message,
status: status
status: status,
});
},
@ -140,7 +129,7 @@ Vue.mixin({
<p>${message}</p>
</div>
`,
{ stack: true }
{ stack: true },
);
},
@ -149,25 +138,25 @@ Vue.mixin({
this.$store.commit("setErrorMessage", errormsg);
UIkit.notification({
message: `${errormsg}`,
status: "danger"
status: "danger",
});
},
getErrorMessage: function(error) {
// Format the error.
let data = this.getErrorData(error)
let data = this.getErrorData(error);
// Get error data may get an object. Handle edge cases and if not try to use
// JSON to get the best string. This stops "objectObject" showing as an error.
if (data === null) return "null";
if (data === undefined) return "undefined";
if (typeof data === 'string') return data;
if (typeof data === "string") return data;
try {
return JSON.stringify(data, null, 2);
} catch (err) {
return String(data);
}
},
getErrorData: function(error){
getErrorData: function(error) {
// If a response was obtained, extract the most specific message
if (error.response) {
// If the response is a nicely formatted JSON response from the server
@ -176,7 +165,7 @@ Vue.mixin({
}
if (error.response.data.detail) {
try {
return error.response.data.detail[0].msg
return error.response.data.detail[0].msg;
} catch (err) {
return error.response.data.detail;
}
@ -219,11 +208,11 @@ Vue.mixin({
setLocalStorageObj: function(keyName, object) {
const parsed = JSON.stringify(object);
localStorage.setItem(keyName, parsed);
}
}
},
},
});
new Vue({
store,
render: h => h(App)
render: h => h(App),
}).$mount("#app");

View file

@ -19,7 +19,7 @@ function getOriginFromLocation() {
export default new Vuex.Store({
modules: {
wot: wotStoreModule
wot: wotStoreModule,
},
state: {
origin: getOriginFromLocation(),
@ -32,7 +32,7 @@ export default new Vuex.Store({
galleryEnabled: true,
appTheme: "system",
activeStreams: {},
microscopeHostname: ""
microscopeHostname: "",
},
mutations: {
@ -77,13 +77,13 @@ export default new Vuex.Store({
},
changeMicroscopeHostname(state, value) {
state.microscopeHostname = value;
}
},
},
actions: {},
getters: {
baseUri: state => state.origin,
ready: state => state.available
}
ready: state => state.available,
},
});

View file

@ -5,7 +5,7 @@ export const wotStoreModule = {
state: () => ({
thingDescriptions: {},
servient: null,
helpers: null
helpers: null,
}),
mutations: {
addThingDescription(state, { thingName, thingDescription }) {
@ -16,7 +16,7 @@ export const wotStoreModule = {
},
removeAllThingDescriptions(state) {
state.thingDescriptions = {};
}
},
},
actions: {
async start() {
@ -36,7 +36,7 @@ export const wotStoreModule = {
.pop();
commit("addThingDescription", {
thingName: thing_name,
thingDescription: td
thingDescription: td,
});
},
async fetchThingDescriptions({ commit }, uri) {
@ -47,10 +47,10 @@ export const wotStoreModule = {
let thing_name = k.replace(/\/$/, "").replace(/^\//, "");
commit("addThingDescription", {
thingName: thing_name,
thingDescription: response.data[k]
thingDescription: response.data[k],
});
}
}
},
},
getters: {
thingDescriptions: state => {
@ -62,13 +62,7 @@ export const wotStoreModule = {
thingAvailable: state => thingName => {
return thingName in state.thingDescriptions;
},
thingFormUrl: state => (
thing,
affordanceType,
affordance,
op,
allowUndefined = true
) => {
thingFormUrl: state => (thing, affordanceType, affordance, op, allowUndefined = true) => {
// Find the URL for a particular operation
let td = state.thingDescriptions[thing];
if (!td) {
@ -91,31 +85,15 @@ export const wotStoreModule = {
}
return href;
},
thingPropertyUrl: (_state, getters) => (
thing,
property,
op,
allowUndefined
) => {
thingPropertyUrl: (_state, getters) => (thing, property, op, allowUndefined) => {
// Find the URL for a particular property
return getters.thingFormUrl(
thing,
"properties",
property,
op,
allowUndefined
);
return getters.thingFormUrl(thing, "properties", property, op, allowUndefined);
},
thingActionUrl: (_state, getters) => (
thing,
action,
op,
allowUndefined
) => {
thingActionUrl: (_state, getters) => (thing, action, op, allowUndefined) => {
// Find the URL for a particular action
return getters.thingFormUrl(thing, "actions", action, op, allowUndefined);
}
}
},
},
};
export function findFormHref(affordance, op) {

View file

@ -1,3 +1,3 @@
module.exports = {
outputDir: "../src/openflexure_microscope_server/static"
outputDir: "../src/openflexure_microscope_server/static",
};