Merge branch 'fewer-ui-assumptions' into 'v3'
UI customises to available Things Closes #510 and #385 See merge request openflexure/openflexure-microscope-server!435
This commit is contained in:
commit
5074f75442
27 changed files with 886 additions and 811 deletions
|
|
@ -13,6 +13,7 @@
|
|||
{{ $store.state.origin }}
|
||||
</div>
|
||||
<action-button
|
||||
v-if="stageType"
|
||||
thing="stage"
|
||||
action="flash_led"
|
||||
submit-label="Flash Illumination"
|
||||
|
|
@ -34,18 +35,16 @@
|
|||
<div class="uk-margin-small-bottom">
|
||||
<b>Camera:</b>
|
||||
<br />
|
||||
<div v-if="'camera' in things">
|
||||
{{ things.camera.title }}
|
||||
<div>
|
||||
{{ cameraType }}
|
||||
</div>
|
||||
<div v-else class="uk-text-danger"><b>No camera configured</b></div>
|
||||
</div>
|
||||
<div>
|
||||
<div v-if="stageType">
|
||||
<b>Stage:</b>
|
||||
<br />
|
||||
<div v-if="'stage' in things">
|
||||
{{ things.stage.title }}
|
||||
<div>
|
||||
{{ stageType }}
|
||||
</div>
|
||||
<div v-else class="uk-text-danger"><b>No stage configured</b></div>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
|
@ -71,8 +70,12 @@ export default {
|
|||
},
|
||||
|
||||
computed: {
|
||||
things: function () {
|
||||
return this.$store.getters["wot/thingDescriptions"];
|
||||
cameraType() {
|
||||
// No need to check as the microscope won't start up without a camera defined
|
||||
return this.thingDescription("camera").title;
|
||||
},
|
||||
stageType() {
|
||||
return this.thingAvailable("stage") ? this.thingDescription("stage").title : undefined;
|
||||
},
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
<template>
|
||||
<div>
|
||||
<p>Autofocus</p>
|
||||
<div class="uk-margin">
|
||||
<action-button
|
||||
thing="autofocus"
|
||||
action="fast_autofocus"
|
||||
:submit-data="{ dz: 2000 }"
|
||||
:submit-label="'Autofocus'"
|
||||
:button-primary="true"
|
||||
:submit-on-event="'globalFastAutofocusEvent'"
|
||||
:is-disabled="isAutofocusing"
|
||||
@taskStarted="onAutofocus"
|
||||
@finished="afterAutofocus"
|
||||
@error="modalError"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import ActionButton from "../../labThingsComponents/actionButton.vue";
|
||||
|
||||
export default {
|
||||
name: "AutofocusControl",
|
||||
|
||||
components: {
|
||||
ActionButton,
|
||||
},
|
||||
|
||||
data: function () {
|
||||
return {
|
||||
isAutofocusing: false,
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
onAutofocus() {
|
||||
this.isAutofocusing = true;
|
||||
},
|
||||
afterAutofocus() {
|
||||
this.isAutofocusing = false;
|
||||
this.$root.$emit("globalUpdatePositionEvent");
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -1,145 +1,30 @@
|
|||
<template>
|
||||
<div id="paneControl" class="uk-padding-small">
|
||||
<div v-if="setPosition">
|
||||
<ul uk-accordion="multiple: true">
|
||||
<li>
|
||||
<a class="uk-accordion-title" href="#">Configure</a>
|
||||
<div class="uk-accordion-content">
|
||||
<b>Keyboard Step Size</b>
|
||||
<div class="uk-grid-small uk-child-width-1-3" uk-grid>
|
||||
<div>
|
||||
<label class="uk-form-label" for="form-stacked-text">x</label>
|
||||
<div class="uk-form-controls">
|
||||
<input v-model="stepSize.x" class="uk-input uk-form-small" type="number" />
|
||||
</div>
|
||||
<label class="uk-margin-small-right"
|
||||
><input v-model="invert.x" class="uk-checkbox" type="checkbox" /> Invert x</label
|
||||
>
|
||||
</div>
|
||||
<positionControl v-if="stageAvailable" />
|
||||
<autofocusControl v-if="autofocusAvailable" />
|
||||
<p>Image Capture</p>
|
||||
|
||||
<div>
|
||||
<label class="uk-form-label" for="form-stacked-text">y</label>
|
||||
<div class="uk-form-controls">
|
||||
<input v-model="stepSize.y" class="uk-input uk-form-small" type="number" />
|
||||
</div>
|
||||
<label class="uk-margin-small-right"
|
||||
><input v-model="invert.y" class="uk-checkbox" type="checkbox" /> Invert y</label
|
||||
>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="uk-form-label" for="form-stacked-text">z</label>
|
||||
<div class="uk-form-controls">
|
||||
<input v-model="stepSize.z" class="uk-input uk-form-small" type="number" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<action-button
|
||||
thing="stage"
|
||||
action="set_zero_position"
|
||||
submit-label="Zero Coordinates"
|
||||
:can-terminate="false"
|
||||
@finished="updatePosition"
|
||||
@error="modalError"
|
||||
/>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="uk-open">
|
||||
<a class="uk-accordion-title" href="#">Position</a>
|
||||
<div class="uk-accordion-content">
|
||||
<form>
|
||||
<!-- Text boxes to set and view position -->
|
||||
<div class="input-and-buttons-container">
|
||||
<input
|
||||
v-for="(_v, key) in setPosition"
|
||||
:key="`setPosition_${key}`"
|
||||
v-model="setPosition[key]"
|
||||
class="uk-form-small numeric-setting-line-input"
|
||||
type="number"
|
||||
@keyup.enter="startMoveTask"
|
||||
/>
|
||||
<sync-property-button @click="updatePosition" />
|
||||
</div>
|
||||
<p>
|
||||
<action-button
|
||||
ref="moveButton"
|
||||
thing="stage"
|
||||
action="move_absolute"
|
||||
:submit-data="setPosition"
|
||||
:submit-label="'Move'"
|
||||
:can-terminate="true"
|
||||
:poll-interval="0.05"
|
||||
@taskStarted="moveLock = true"
|
||||
@finished="
|
||||
updatePosition();
|
||||
moveLock = false;
|
||||
"
|
||||
@error="modalError"
|
||||
/>
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<!--Show autofocus if default plugin is enabled-->
|
||||
<li class="uk-open">
|
||||
<a class="uk-accordion-title" href="#">Autofocus</a>
|
||||
<div class="uk-accordion-content">
|
||||
<div class="uk-grid-small uk-child-width-expand" uk-grid>
|
||||
<div v-show="!isAutofocusing || isAutofocusing == 1">
|
||||
<action-button
|
||||
thing="autofocus"
|
||||
action="fast_autofocus"
|
||||
:submit-data="{ dz: 2000 }"
|
||||
:submit-label="'Autofocus'"
|
||||
:button-primary="true"
|
||||
:submit-on-event="'globalFastAutofocusEvent'"
|
||||
@taskStarted="isAutofocusing = 1"
|
||||
@finished="
|
||||
updatePosition();
|
||||
isAutofocusing = 0;
|
||||
"
|
||||
@error="modalError"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="uk-open">
|
||||
<a class="uk-accordion-title" href="#">Image Capture</a>
|
||||
<div class="uk-accordion-content">
|
||||
<div class="uk-margin">
|
||||
<action-button
|
||||
thing="camera"
|
||||
action="capture_jpeg"
|
||||
:submit-data="{ stream_name: 'main' }"
|
||||
:submit-label="'Low Resolution'"
|
||||
:submit-on-event="'globalCaptureEvent'"
|
||||
@response="handleCaptureResponse"
|
||||
@error="modalError"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="uk-margin">
|
||||
<action-button
|
||||
thing="camera"
|
||||
action="capture_jpeg"
|
||||
:submit-data="{ stream_name: 'full' }"
|
||||
submit-label="Full Resolution"
|
||||
:submit-on-event="'globalCaptureEvent'"
|
||||
@response="handleCaptureResponse"
|
||||
@error="modalError"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="uk-margin">
|
||||
<action-button
|
||||
thing="camera"
|
||||
action="capture_jpeg"
|
||||
:submit-data="{ stream_name: 'main' }"
|
||||
:submit-label="'Low Resolution'"
|
||||
@response="handleCaptureResponse"
|
||||
@error="modalError"
|
||||
/>
|
||||
</div>
|
||||
<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.</p>
|
||||
|
||||
<div class="uk-margin">
|
||||
<action-button
|
||||
thing="camera"
|
||||
action="capture_jpeg"
|
||||
:submit-data="{ stream_name: 'full' }"
|
||||
submit-label="Full Resolution"
|
||||
:submit-on-event="'globalCaptureEvent'"
|
||||
@response="handleCaptureResponse"
|
||||
@error="modalError"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -147,151 +32,28 @@
|
|||
<script>
|
||||
import axios from "axios";
|
||||
import ActionButton from "../../labThingsComponents/actionButton.vue";
|
||||
import syncPropertyButton from "../../labThingsComponents/syncPropertyButton.vue";
|
||||
import positionControl from "./positionControl.vue";
|
||||
import autofocusControl from "./autofocusControl.vue";
|
||||
|
||||
// Export main app
|
||||
export default {
|
||||
name: "PaneControl",
|
||||
|
||||
components: {
|
||||
ActionButton,
|
||||
syncPropertyButton,
|
||||
},
|
||||
|
||||
data: function () {
|
||||
return {
|
||||
setPosition: null,
|
||||
isAutofocusing: 0,
|
||||
moveLock: false,
|
||||
};
|
||||
positionControl,
|
||||
autofocusControl,
|
||||
},
|
||||
|
||||
computed: {
|
||||
// Note that as stepSize and invert are set based on internals we can use
|
||||
// get() and set() to interact with the store. Instead use a deep watcher to
|
||||
// update the store (see ``watch:`` below)
|
||||
stepSize() {
|
||||
return this.$store.state.navigationStepSize;
|
||||
stageAvailable() {
|
||||
return this.thingAvailable("stage");
|
||||
},
|
||||
invert() {
|
||||
return this.$store.state.navigationInvert;
|
||||
autofocusAvailable() {
|
||||
return this.thingAvailable("autofocus");
|
||||
},
|
||||
baseUri: function () {
|
||||
return this.$store.getters.baseUri;
|
||||
},
|
||||
positionStatusUri: function () {
|
||||
return `${this.baseUri}/stage/position`;
|
||||
},
|
||||
moveInImageCoordinatesUri: function () {
|
||||
return this.thingActionUrl("camera_stage_mapping", "move_in_image_coordinates");
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
stepSize: {
|
||||
deep: true,
|
||||
handler(newVal) {
|
||||
this.$store.commit("changeNavigationStepSize", newVal);
|
||||
},
|
||||
},
|
||||
invert: {
|
||||
deep: true,
|
||||
handler(newVal) {
|
||||
this.$store.commit("changeNavigationInvert", newVal);
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
async mounted() {
|
||||
let self = this;
|
||||
// A global signal listener to perform a move action
|
||||
this.$root.$on("globalMoveEvent", self.move);
|
||||
// A global signal listener to perform a move action in pixels
|
||||
this.$root.$on("globalMoveInImageCoordinatesEvent", (x, y, absolute) => {
|
||||
this.moveInImageCoordinatesRequest(x, y, absolute);
|
||||
});
|
||||
// A global signal listener to perform a move in multiples of a step size
|
||||
this.$root.$on("globalMoveStepEvent", (x_steps, y_steps, z_steps) => {
|
||||
this.$root.$emit(
|
||||
"globalMoveEvent",
|
||||
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,
|
||||
);
|
||||
});
|
||||
// Update the current position in text boxes
|
||||
await this.updatePosition();
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
// Remove global signal listener to perform a move action
|
||||
this.$root.$off("globalMoveEvent");
|
||||
this.$root.$off("globalMoveInImageCoordinatesEvent");
|
||||
this.$root.$off("globalMoveStepEvent");
|
||||
},
|
||||
|
||||
methods: {
|
||||
timeout(ms) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
},
|
||||
async move(x, y, z, absolute) {
|
||||
// Move the stage, by updating the controls and starting a move task
|
||||
// This is equivalent to clicking the "move" button.
|
||||
if (this.moveLock) return; // Discard move requests if we're already moving
|
||||
// NB moveLock is just boolean flag - it's not as safe as a "proper" lock.
|
||||
this.moveLock = true; // This will also be set by the task submitter, but
|
||||
// setting it here avoids multiple moves being requested simultaneously.
|
||||
if (absolute) {
|
||||
this.setPosition = { x: x, y: y, z: z };
|
||||
} else {
|
||||
await this.updatePosition();
|
||||
this.setPosition = {
|
||||
x: this.setPosition.x + x,
|
||||
y: this.setPosition.y + y,
|
||||
z: this.setPosition.z + z,
|
||||
};
|
||||
}
|
||||
await this.timeout(1); // Wait for Vue to update the position
|
||||
await this.startMoveTask();
|
||||
},
|
||||
async startMoveTask() {
|
||||
await this.$refs.moveButton.startTask();
|
||||
},
|
||||
moveInImageCoordinatesRequest: function (x, y) {
|
||||
// If not movement-locked
|
||||
if (!this.moveLock) {
|
||||
// Lock move requests
|
||||
this.moveLock = true;
|
||||
|
||||
if (!this.moveInImageCoordinatesUri) {
|
||||
this.modalError(
|
||||
"Moving in image coordinates is not supported - you will need to upgrade your microscope's software.",
|
||||
);
|
||||
}
|
||||
|
||||
// Send move request
|
||||
axios
|
||||
.post(this.moveInImageCoordinatesUri, {
|
||||
x: x, // NB the coordinates are numpy/PIL style, meaning X and Y are swapped.
|
||||
y: y,
|
||||
})
|
||||
.then(() => {
|
||||
this.updatePosition(); // Update the position in text boxes
|
||||
})
|
||||
.catch((error) => {
|
||||
this.modalError(error); // Let mixin handle error
|
||||
})
|
||||
.then(() => {
|
||||
this.moveLock = false; // Release the move lock
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
async updatePosition() {
|
||||
this.setPosition = await this.readThingProperty("stage", "position");
|
||||
},
|
||||
|
||||
handleCaptureResponse: async function (response) {
|
||||
// Retrieve the captured image and save it
|
||||
let imageUri = response.output.href;
|
||||
|
|
@ -312,29 +74,3 @@ export default {
|
|||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.input-and-buttons-container {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
justify-content: flex-start;
|
||||
align-content: stretch;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
.numeric-setting-line-input {
|
||||
flex-grow: 1;
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
width: 3em;
|
||||
/* Stop Firefox showing input spinners, other
|
||||
browsers set with block below */
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
/* Chrome, Safari, Edge, Opera */
|
||||
.numeric-setting-line-input::-webkit-outer-spin-button,
|
||||
.numeric-setting-line-input::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,198 @@
|
|||
<!-- This is the main position component for the control tab.
|
||||
|
||||
Do not reuse this component until we remove global listeners for move.
|
||||
|
||||
This component shows themotor positions in a closed accordion along with the move
|
||||
and zero position buttons. It also includes the d-pad.
|
||||
-->
|
||||
<template>
|
||||
<div>
|
||||
<ul uk-accordion="multiple: true">
|
||||
<li class="uk-closed">
|
||||
<a class="uk-accordion-title" href="#">Position</a>
|
||||
<div class="uk-accordion-content">
|
||||
<form>
|
||||
<!-- Text boxes to set and view position -->
|
||||
<div class="input-and-buttons-container">
|
||||
<input
|
||||
v-for="(_v, key) in setPosition"
|
||||
:key="`setPosition_${key}`"
|
||||
v-model="setPosition[key]"
|
||||
class="uk-form-small numeric-setting-line-input"
|
||||
type="number"
|
||||
@keyup.enter="startMoveTask"
|
||||
/>
|
||||
<sync-property-button @click="updatePosition" />
|
||||
</div>
|
||||
<p>
|
||||
<action-button
|
||||
ref="moveButton"
|
||||
thing="stage"
|
||||
action="move_absolute"
|
||||
:submit-data="setPosition"
|
||||
:submit-label="'Move'"
|
||||
:can-terminate="true"
|
||||
:poll-interval="0.05"
|
||||
@finished="moveComplete"
|
||||
@error="modalError"
|
||||
/>
|
||||
</p>
|
||||
</form>
|
||||
<action-button
|
||||
thing="stage"
|
||||
action="set_zero_position"
|
||||
submit-label="Zero Coordinates"
|
||||
:can-terminate="false"
|
||||
@finished="updatePosition"
|
||||
@error="modalError"
|
||||
/>
|
||||
<div class="uk-flex uk-flex-center uk-margin">
|
||||
<hr class="uk-divider-small" />
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<stageControlButtons />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ActionButton from "../../labThingsComponents/actionButton.vue";
|
||||
import syncPropertyButton from "../../labThingsComponents/syncPropertyButton.vue";
|
||||
import stageControlButtons from "./stageControlButtons.vue";
|
||||
export default {
|
||||
name: "PaneControl",
|
||||
|
||||
components: {
|
||||
ActionButton,
|
||||
syncPropertyButton,
|
||||
stageControlButtons,
|
||||
},
|
||||
|
||||
data: function () {
|
||||
return {
|
||||
setPosition: null,
|
||||
moveLock: false,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
positionStatusUri: function () {
|
||||
return this.thingActionUrl("stage", "position");
|
||||
},
|
||||
},
|
||||
|
||||
async mounted() {
|
||||
let self = this;
|
||||
// A global signal listener to perform a move action
|
||||
this.$root.$on("globalMoveEvent", self.move);
|
||||
this.$root.$on("globalUpdatePositionEvent", self.updatePosition);
|
||||
// A global signal listener to perform a move action in pixels
|
||||
this.$root.$on("globalMoveInImageCoordinatesEvent", (x, y, absolute) => {
|
||||
this.moveInImageCoordinatesRequest(x, y, absolute);
|
||||
});
|
||||
// A global signal listener to perform a move in multiples of a step size
|
||||
this.$root.$on("globalMoveStepEvent", (x_steps, y_steps, z_steps) => {
|
||||
const navigationStepSize = this.$store.state.navigationStepSize;
|
||||
const navigationInvert = this.$store.state.navigationInvert;
|
||||
const x = x_steps * navigationStepSize.x * (navigationInvert.x ? -1 : 1);
|
||||
const y = y_steps * navigationStepSize.y * (navigationInvert.y ? -1 : 1);
|
||||
const z = z_steps * navigationStepSize.z * (navigationInvert.z ? -1 : 1);
|
||||
this.$root.$emit("globalMoveEvent", x, y, z, false);
|
||||
});
|
||||
// Update the current position in text boxes
|
||||
await this.updatePosition();
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
// Remove global signal listener to perform a move action
|
||||
this.$root.$off("globalMoveEvent");
|
||||
this.$root.$off("globalMoveInImageCoordinatesEvent");
|
||||
this.$root.$off("globalMoveStepEvent");
|
||||
this.$root.$off("globalUpdatePositionEvent");
|
||||
},
|
||||
|
||||
methods: {
|
||||
timeout(ms) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
},
|
||||
async move(x, y, z, absolute) {
|
||||
// Move the stage, by updating the controls and starting a move task
|
||||
// This is equivalent to clicking the "move" button.
|
||||
if (this.moveLock) return; // Discard move requests if we're already moving
|
||||
// NB moveLock is just boolean flag - it's not as safe as a "proper" lock.
|
||||
this.moveLock = true; // This will also be set by the task submitter, but
|
||||
// setting it here avoids multiple moves being requested simultaneously.
|
||||
if (absolute) {
|
||||
this.setPosition = { x: x, y: y, z: z };
|
||||
} else {
|
||||
await this.updatePosition();
|
||||
this.setPosition = {
|
||||
x: this.setPosition.x + x,
|
||||
y: this.setPosition.y + y,
|
||||
z: this.setPosition.z + z,
|
||||
};
|
||||
}
|
||||
await this.timeout(1); // Wait for Vue to update the position
|
||||
await this.startMoveTask();
|
||||
},
|
||||
async startMoveTask() {
|
||||
this.moveLock = true;
|
||||
await this.$refs.moveButton.startTask();
|
||||
},
|
||||
moveComplete() {
|
||||
this.updatePosition();
|
||||
this.moveLock = false;
|
||||
},
|
||||
async moveInImageCoordinatesRequest(x, y) {
|
||||
// If not movement-locked
|
||||
if (!this.moveLock) {
|
||||
// Lock move requests
|
||||
this.moveLock = true;
|
||||
const response = await this.invokeAction(
|
||||
"camera_stage_mapping",
|
||||
"move_in_image_coordinates",
|
||||
{
|
||||
x: x,
|
||||
y: y,
|
||||
},
|
||||
);
|
||||
this.pollUntilComplete(
|
||||
response.data.href,
|
||||
null, // Nothing to do while ongoing
|
||||
this.moveComplete, // Call move complete once done.
|
||||
200,
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
async updatePosition() {
|
||||
this.setPosition = await this.readThingProperty("stage", "position");
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.input-and-buttons-container {
|
||||
display: flex;
|
||||
flex-flow: column wrap;
|
||||
justify-content: flex-start;
|
||||
align-content: stretch;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
.numeric-setting-line-input {
|
||||
flex-grow: 1;
|
||||
margin: 5px 0px;
|
||||
width: 5em;
|
||||
/* Stop Firefox showing input spinners, other
|
||||
browsers set with block below */
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
/* Chrome, Safari, Edge, Opera */
|
||||
.numeric-setting-line-input::-webkit-outer-spin-button,
|
||||
.numeric-setting-line-input::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
<template>
|
||||
<div class="uk-flex uk-flex-center uk-flex-middle uk-margin">
|
||||
<div class="dpad-grid">
|
||||
<button id="up-button" class="uk-button uk-button-primary dpad-btn" @click="move(0, 1, 0)">
|
||||
<span class="material-symbols-outlined sync-icon"> arrow_upward </span>
|
||||
</button>
|
||||
|
||||
<button id="left-button" class="uk-button uk-button-primary dpad-btn" @click="move(-1, 0, 0)">
|
||||
<span class="material-symbols-outlined sync-icon"> arrow_back </span>
|
||||
</button>
|
||||
|
||||
<button id="right-button" class="uk-button uk-button-primary dpad-btn" @click="move(1, 0, 0)">
|
||||
<span class="material-symbols-outlined sync-icon"> arrow_forward </span>
|
||||
</button>
|
||||
|
||||
<button id="down-button" class="uk-button uk-button-primary dpad-btn" @click="move(0, -1, 0)">
|
||||
<span class="material-symbols-outlined sync-icon"> arrow_downward </span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
id="focus-out-button"
|
||||
class="uk-button uk-button-primary dpad-btn"
|
||||
@click="move(0, 0, -1)"
|
||||
>
|
||||
<span class="material-symbols-outlined sync-icon"> remove </span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
id="focus-in-button"
|
||||
class="uk-button uk-button-primary dpad-btn"
|
||||
@click="move(0, 0, 1)"
|
||||
>
|
||||
<span class="material-symbols-outlined sync-icon"> add </span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "StageControlButtons",
|
||||
methods: {
|
||||
move(x, y, z) {
|
||||
this.$root.$emit("globalMoveStepEvent", x, y, z);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dpad-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 40px);
|
||||
grid-template-rows: 40px 40px 40px 20px 40px;
|
||||
gap: 1px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* Place buttons within grid */
|
||||
.dpad-grid #up-button {
|
||||
grid-column: 2;
|
||||
grid-row: 1;
|
||||
}
|
||||
.dpad-grid #left-button {
|
||||
grid-column: 1;
|
||||
grid-row: 2;
|
||||
}
|
||||
.dpad-grid #right-button {
|
||||
grid-column: 3;
|
||||
grid-row: 2;
|
||||
}
|
||||
.dpad-grid #down-button {
|
||||
grid-column: 2;
|
||||
grid-row: 3;
|
||||
}
|
||||
.dpad-grid #focus-out-button {
|
||||
grid-column: 1;
|
||||
grid-row: 5;
|
||||
}
|
||||
.dpad-grid #focus-in-button {
|
||||
grid-column: 3;
|
||||
grid-row: 5;
|
||||
}
|
||||
|
||||
.dpad-btn {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -23,3 +23,9 @@ export default {
|
|||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.control-component {
|
||||
width: 160px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
</p>
|
||||
<div class="buttons-container">
|
||||
<button
|
||||
v-show="'shutdown' in things.system.actions"
|
||||
v-show="shutdownAvailable"
|
||||
class="uk-button uk-button-primary uk-width-1-3 shutdown-button"
|
||||
@click="systemRequest('shutdown')"
|
||||
>
|
||||
|
|
@ -42,8 +42,8 @@ export default {
|
|||
},
|
||||
|
||||
computed: {
|
||||
things: function () {
|
||||
return this.$store.getters["wot/thingDescriptions"];
|
||||
shutdownAvailable() {
|
||||
return this.thingActionAvailable("system", "shutdown");
|
||||
},
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -119,12 +119,7 @@ export default {
|
|||
|
||||
computed: {
|
||||
scansUri() {
|
||||
return this.$store.getters["wot/thingPropertyUrl"](
|
||||
"smart_scan",
|
||||
"scans",
|
||||
"readproperty",
|
||||
true,
|
||||
);
|
||||
return this.thingPropertyUrl("smart_scan", "scans");
|
||||
},
|
||||
scansEmpty() {
|
||||
return this.scans.length == 0;
|
||||
|
|
|
|||
|
|
@ -86,10 +86,10 @@ export default {
|
|||
|
||||
computed: {
|
||||
actions() {
|
||||
return this.$store.getters["wot/thingDescription"]("camera_stage_mapping").actions;
|
||||
return this.thingDescription("camera_stage_mapping").actions;
|
||||
},
|
||||
properties() {
|
||||
return this.$store.getters["wot/thingDescription"]("camera_stage_mapping").properties;
|
||||
return this.thingDescription("camera_stage_mapping").properties;
|
||||
},
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ export default {
|
|||
|
||||
computed: {
|
||||
actions() {
|
||||
return this.$store.getters["wot/thingDescription"]("camera").actions;
|
||||
return this.thingDescription("camera").actions;
|
||||
},
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
<template>
|
||||
<div>
|
||||
<appSettings />
|
||||
<streamSettings />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import appSettings from "./displaySetttingsComponents/appSettings.vue";
|
||||
import streamSettings from "./displaySetttingsComponents/streamSettings.vue";
|
||||
|
||||
// Export main app
|
||||
export default {
|
||||
name: "DisplaySettings",
|
||||
|
||||
components: {
|
||||
streamSettings,
|
||||
appSettings,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
<template>
|
||||
<div class="uk-width-large">
|
||||
<p><b>Single Move Step Size</b></p>
|
||||
<p>
|
||||
This sets the size (and direction) of movements made using the navigation buttons in the
|
||||
control tab or using the keyboard (arrow keys, page up/down).
|
||||
</p>
|
||||
<p>These settings do not affect the operation of other actions your microscope performs.</p>
|
||||
<div class="uk-grid-small uk-child-width-1-3" uk-grid>
|
||||
<div>
|
||||
<label class="uk-form-label" for="form-stacked-text">x</label>
|
||||
<div class="uk-form-controls">
|
||||
<input v-model="stepSize.x" class="uk-input uk-form-small" type="number" />
|
||||
</div>
|
||||
<label class="uk-margin-small-right">
|
||||
<input v-model="invert.x" class="uk-checkbox" type="checkbox" />
|
||||
Invert x
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="uk-form-label" for="form-stacked-text">y</label>
|
||||
<div class="uk-form-controls">
|
||||
<input v-model="stepSize.y" class="uk-input uk-form-small" type="number" />
|
||||
</div>
|
||||
<label class="uk-margin-small-right">
|
||||
<input v-model="invert.y" class="uk-checkbox" type="checkbox" />
|
||||
Invert y
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="uk-form-label" for="form-stacked-text">z</label>
|
||||
<div class="uk-form-controls">
|
||||
<input v-model="stepSize.z" class="uk-input uk-form-small" type="number" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "StageControlSettings",
|
||||
|
||||
computed: {
|
||||
// Note that as stepSize and invert are mutated (i.e. we change stepSize.x not stepSize)
|
||||
// rather than directly set we cannot use get() and set() computed to interact with the
|
||||
// store as changed won't be detected by set(). Instead use a deep watcher to
|
||||
// update the store (see ``watch:`` below)
|
||||
stepSize() {
|
||||
return this.$store.state.navigationStepSize;
|
||||
},
|
||||
invert() {
|
||||
return this.$store.state.navigationInvert;
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
stepSize: {
|
||||
deep: true,
|
||||
handler(newVal) {
|
||||
this.$store.commit("changeNavigationStepSize", newVal);
|
||||
},
|
||||
},
|
||||
invert: {
|
||||
deep: true,
|
||||
handler(newVal) {
|
||||
this.$store.commit("changeNavigationInvert", newVal);
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -5,17 +5,17 @@
|
|||
<div class="settings-nav">
|
||||
<ul class="uk-nav uk-nav-default">
|
||||
<li class="uk-nav-header">Application Settings</li>
|
||||
<li>
|
||||
<li v-for="item in appTabs" :key="'setting-' + item.id + '-tab-content'">
|
||||
<tabIcon
|
||||
id="settings-display-icon"
|
||||
tab-i-d="display"
|
||||
:id="'setting-' + item.id + '-tab-content'"
|
||||
:tab-i-d="item.id"
|
||||
:show-title="false"
|
||||
:show-tooltip="false"
|
||||
:require-connection="false"
|
||||
:require-connection="item.requireConnection"
|
||||
:current-tab="currentTab"
|
||||
@set-tab="setTab"
|
||||
>
|
||||
Display
|
||||
{{ item.title }}
|
||||
</tabIcon>
|
||||
</li>
|
||||
<li class="uk-nav-header">Microscope Settings</li>
|
||||
|
|
@ -27,81 +27,41 @@
|
|||
>
|
||||
Launch Calibration Wizard
|
||||
</button>
|
||||
<li>
|
||||
<li v-for="item in calibrationTabs" :key="'setting-' + item.id + '-tab-icon'">
|
||||
<tabIcon
|
||||
id="settings-camera-icon"
|
||||
tab-i-d="camera"
|
||||
:id="'setting-' + item.id + '-tab-icon'"
|
||||
:tab-i-d="item.id"
|
||||
:show-title="false"
|
||||
:show-tooltip="false"
|
||||
:require-connection="true"
|
||||
:require-connection="item.requireConnection"
|
||||
:current-tab="currentTab"
|
||||
@set-tab="setTab"
|
||||
>
|
||||
Camera
|
||||
</tabIcon>
|
||||
</li>
|
||||
<li>
|
||||
<tabIcon
|
||||
id="settings-stage-icon"
|
||||
tab-i-d="stage"
|
||||
:show-title="false"
|
||||
:show-tooltip="false"
|
||||
:require-connection="true"
|
||||
:current-tab="currentTab"
|
||||
@set-tab="setTab"
|
||||
>
|
||||
Stage
|
||||
</tabIcon>
|
||||
</li>
|
||||
<li>
|
||||
<tabIcon
|
||||
id="settings-mapping-icon"
|
||||
tab-i-d="mapping"
|
||||
:show-title="false"
|
||||
:show-tooltip="false"
|
||||
:require-connection="true"
|
||||
:current-tab="currentTab"
|
||||
@set-tab="setTab"
|
||||
>
|
||||
Camera to Stage Mapping
|
||||
{{ item.title }}
|
||||
</tabIcon>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="view-component uk-width-expand uk-padding-small">
|
||||
<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">
|
||||
<div class="settings-pane uk-padding-small">
|
||||
<cameraSettings />
|
||||
</div>
|
||||
</tabContent>
|
||||
|
||||
<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">
|
||||
<div class="settings-pane uk-padding-small">
|
||||
<CSMSettings />
|
||||
</div>
|
||||
<tabContent
|
||||
v-for="item in allTabs"
|
||||
:id="'setting-' + item.id + '-tab-content'"
|
||||
:key="'setting-' + item.id + '-tab-content'"
|
||||
:tab-i-d="item.id"
|
||||
:require-connection="item.requireConnection"
|
||||
:current-tab="currentTab"
|
||||
>
|
||||
<component :is="item.component"></component>
|
||||
</tabContent>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import streamSettings from "./settingsComponents/streamSettings.vue";
|
||||
import displaySettings from "./settingsComponents/displaySettings.vue";
|
||||
import stageControlSettings from "./settingsComponents/stageControlSettings.vue";
|
||||
import calibrationWizard from "../modalComponents/calibrationWizard.vue";
|
||||
import cameraSettings from "./settingsComponents/cameraSettings.vue";
|
||||
import appSettings from "./settingsComponents/appSettings.vue";
|
||||
import CSMSettings from "./settingsComponents/CSMSettings.vue";
|
||||
import stageSettings from "./settingsComponents/stageSettings.vue";
|
||||
// Import generic components
|
||||
|
|
@ -113,11 +73,6 @@ export default {
|
|||
name: "SettingsContent",
|
||||
|
||||
components: {
|
||||
streamSettings,
|
||||
cameraSettings,
|
||||
stageSettings,
|
||||
CSMSettings,
|
||||
appSettings,
|
||||
tabIcon,
|
||||
tabContent,
|
||||
calibrationWizard,
|
||||
|
|
@ -127,9 +82,67 @@ export default {
|
|||
return {
|
||||
selected: "display",
|
||||
currentTab: "display",
|
||||
coreAppTabs: [
|
||||
{
|
||||
id: "display",
|
||||
title: "Display",
|
||||
requireConnection: false,
|
||||
component: displaySettings,
|
||||
requiredThings: [],
|
||||
},
|
||||
{
|
||||
id: "stage-control",
|
||||
title: "Stage Control Preferences",
|
||||
requireConnection: false,
|
||||
component: stageControlSettings,
|
||||
requiredThings: ["stage"],
|
||||
},
|
||||
],
|
||||
coreCalibrationTabs: [
|
||||
{
|
||||
id: "camera",
|
||||
title: "Camera",
|
||||
requireConnection: true,
|
||||
component: cameraSettings,
|
||||
requiredThings: [],
|
||||
},
|
||||
{
|
||||
id: "stage",
|
||||
title: "Stage",
|
||||
requireConnection: true,
|
||||
component: stageSettings,
|
||||
requiredThings: ["stage"],
|
||||
},
|
||||
{
|
||||
id: "mapping",
|
||||
title: "Camera to Stage Mapping",
|
||||
requireConnection: true,
|
||||
component: CSMSettings,
|
||||
requiredThings: ["camera_stage_mapping"],
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
allTabs() {
|
||||
return [...this.appTabs, ...this.calibrationTabs];
|
||||
},
|
||||
appTabs() {
|
||||
// Filter core top tabs based on available Things.
|
||||
return this.coreAppTabs.filter((tab) => {
|
||||
if (!tab.requiredThings || tab.requiredThings.length === 0) return true;
|
||||
return tab.requiredThings.every((thing) => this.thingAvailable(thing));
|
||||
});
|
||||
},
|
||||
calibrationTabs() {
|
||||
// Filter core top tabs based on available Things.
|
||||
return this.coreCalibrationTabs.filter((tab) => {
|
||||
if (!tab.requiredThings || tab.requiredThings.length === 0) return true;
|
||||
return tab.requiredThings.every((thing) => this.thingAvailable(thing));
|
||||
});
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
setTab: function (event, tab) {
|
||||
if (!(this.currentTab == tab)) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
<template>
|
||||
<div v-if="!backendOK" class="uk-alert-danger">No scan back-end found.</div>
|
||||
<!-- Grid managing tab content -->
|
||||
<div v-else uk-grid class="uk-height-1-1 uk-margin-remove uk-padding-remove">
|
||||
<div uk-grid class="uk-height-1-1 uk-margin-remove uk-padding-remove">
|
||||
<div class="control-component uk-padding-small">
|
||||
<div v-show="!scanning" class="uk-padding-small">
|
||||
<ul uk-accordion="multiple: true">
|
||||
|
|
@ -183,9 +181,6 @@ export default {
|
|||
},
|
||||
|
||||
computed: {
|
||||
backendOK() {
|
||||
return this.thingAvailable("smart_scan");
|
||||
},
|
||||
cancellable() {
|
||||
return (this.taskStatus == "running") | (this.taskStatus == "pending");
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue