Replace tasksubmitter with action-button
I've changed tasksubmitter to accept a "thing" and "action" rather than a single URL. This seems more in keeping with propertyControl, and eliminates the need for loads of computed properties. I've also deleted (rather than just commenting out) chunks of the web app that are no longer in use. These may be reinstated in the future - but we can get them from git history. It's less confusing not to have vestigial code in the repo. This builds OK but is not, as yet, tested with hardware.
This commit is contained in:
parent
612b88d58c
commit
cbd8b090ec
22 changed files with 225 additions and 2325 deletions
|
|
@ -429,7 +429,7 @@ html {
|
|||
padding: 0;
|
||||
}
|
||||
|
||||
.image-fit{
|
||||
.image-fit {
|
||||
height: 80%;
|
||||
width: 100%;
|
||||
object-fit: contain;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
<!-- Initialisation modals -->
|
||||
<calibrationModal
|
||||
ref="calibrationModal"
|
||||
:available-plugins="plugins"
|
||||
@onClose="enterApp()"
|
||||
></calibrationModal>
|
||||
<!-- Vertical tab bar -->
|
||||
|
|
@ -41,22 +40,6 @@
|
|||
<hr v-if="item.divide" :key="'tab-divider-' + index" />
|
||||
</template>
|
||||
|
||||
<!-- For each plugin tab -->
|
||||
<tabIcon
|
||||
v-for="plugin in pluginsGuiList"
|
||||
:key="plugin.id"
|
||||
:tab-i-d="plugin.id"
|
||||
:title="plugin.title"
|
||||
:require-connection="plugin.requiresConnection"
|
||||
:current-tab="currentTab"
|
||||
:click-callback="updatePlugins"
|
||||
@set-tab="setTab"
|
||||
>
|
||||
<span class="material-symbols-outlined">{{
|
||||
plugin.icon || "extension"
|
||||
}}</span>
|
||||
</tabIcon>
|
||||
|
||||
<tabIcon
|
||||
v-for="imjoyTab in imjoyTabs"
|
||||
:key="imjoyTab.id"
|
||||
|
|
@ -115,23 +98,6 @@
|
|||
<component :is="item.component"></component>
|
||||
</tabContent>
|
||||
|
||||
<!-- For each plugin tab -->
|
||||
<tabContent
|
||||
v-for="plugin in pluginsGuiList"
|
||||
:key="plugin.id"
|
||||
:tab-i-d="plugin.id"
|
||||
:require-connection="plugin.requiresConnection"
|
||||
:current-tab="currentTab"
|
||||
>
|
||||
<extensionContent
|
||||
:forms="plugin.forms"
|
||||
:frame="plugin.frame"
|
||||
:web-component="plugin.wc"
|
||||
:view-panel="plugin.viewPanel"
|
||||
@reloadForms="updatePlugins()"
|
||||
/>
|
||||
</tabContent>
|
||||
|
||||
<tabContent
|
||||
v-for="imjoyTab in imjoyTabs"
|
||||
:key="imjoyTab.id"
|
||||
|
|
@ -166,19 +132,13 @@ import tabContent from "./genericComponents/tabContent";
|
|||
|
||||
// Import new content components
|
||||
import navigateContent from "./tabContentComponents/navigateContent.vue";
|
||||
import captureContent from "./tabContentComponents/captureContent.vue";
|
||||
import slideScanContent from "./tabContentComponents/slideScanContent.vue";
|
||||
import backgroundDetectContent from "./tabContentComponents/backgroundDetectContent.vue";
|
||||
import viewContent from "./tabContentComponents/viewContent.vue";
|
||||
import settingsContent from "./tabContentComponents/settingsContent.vue";
|
||||
import extensionContent from "./tabContentComponents/extensionContent.vue";
|
||||
import aboutContent from "./tabContentComponents/aboutContent.vue";
|
||||
import loggingContent from "./tabContentComponents/loggingContent.vue";
|
||||
// ImJoy and the gallery are loaded asynchronously to allow them to be disabled if needed
|
||||
const galleryContent = () =>
|
||||
import(
|
||||
/* webpackChunkName: "gallery" */ "./tabContentComponents/galleryContent.vue"
|
||||
);
|
||||
// ImJoy is loaded asynchronously to allow it to be disabled if needed
|
||||
const imjoyContent = () =>
|
||||
import(
|
||||
/* webpackChunkName: "imjoy" */ "./tabContentComponents/imjoyContent.vue"
|
||||
|
|
@ -197,12 +157,9 @@ export default {
|
|||
tabIcon,
|
||||
tabContent,
|
||||
navigateContent,
|
||||
captureContent,
|
||||
slideScanContent,
|
||||
viewContent,
|
||||
settingsContent,
|
||||
galleryContent,
|
||||
extensionContent,
|
||||
calibrationModal,
|
||||
aboutContent,
|
||||
loggingContent,
|
||||
|
|
@ -211,7 +168,6 @@ export default {
|
|||
},
|
||||
data: function() {
|
||||
return {
|
||||
plugins: [],
|
||||
currentTab: "view",
|
||||
bottomTabs: [
|
||||
{
|
||||
|
|
@ -235,25 +191,11 @@ export default {
|
|||
},
|
||||
|
||||
computed: {
|
||||
pluginsGuiList: function() {
|
||||
// List of plugin GUIs, obtained from this.plugins values
|
||||
var pluginGuis = [];
|
||||
for (let plugin of Object.values(this.plugins)) {
|
||||
if (plugin.meta.gui) {
|
||||
pluginGuis.push(plugin.meta.gui);
|
||||
}
|
||||
}
|
||||
return pluginGuis;
|
||||
},
|
||||
|
||||
tabOrder: function() {
|
||||
var ind = [];
|
||||
for (const tab of this.topTabs) {
|
||||
ind.push(tab.id);
|
||||
}
|
||||
for (const plugin of this.pluginsGuiList) {
|
||||
ind.push(plugin.id);
|
||||
}
|
||||
for (const tab of this.bottomTabs) {
|
||||
ind.push(tab.id);
|
||||
}
|
||||
|
|
@ -272,11 +214,6 @@ export default {
|
|||
icon: "gamepad",
|
||||
component: navigateContent
|
||||
},
|
||||
/*{
|
||||
id: "gallery",
|
||||
icon: "photo_library",
|
||||
component: galleryContent
|
||||
},*/
|
||||
{
|
||||
id: "slidescan",
|
||||
icon: "settings_overscan",
|
||||
|
|
@ -318,11 +255,7 @@ export default {
|
|||
|
||||
created: function() {
|
||||
if (this.$store.getters.ready) {
|
||||
// Update plugins
|
||||
this.updatePlugins().then(() => {
|
||||
// Start initialisation modals
|
||||
this.startModals();
|
||||
});
|
||||
this.startModals();
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -342,19 +275,6 @@ export default {
|
|||
},
|
||||
|
||||
methods: {
|
||||
updatePlugins: function() {
|
||||
/*return axios
|
||||
.get(this.pluginsUri)
|
||||
.then(response => {
|
||||
this.plugins = response.data;
|
||||
})
|
||||
.catch(error => {
|
||||
this.modalError(error); // Let mixin handle error
|
||||
});*/
|
||||
return new Promise(resolve => {
|
||||
resolve({});
|
||||
});
|
||||
},
|
||||
setTab: function(event, tab) {
|
||||
if (!(this.currentTab == tab)) {
|
||||
this.currentTab = tab;
|
||||
|
|
|
|||
|
|
@ -72,15 +72,19 @@
|
|||
<script>
|
||||
import axios from "axios";
|
||||
import UIkit from "uikit";
|
||||
import actionProgressBar from "./actionProgressBar.vue";
|
||||
import ActionProgressBar from "./actionProgressBar.vue";
|
||||
import ActionLogDisplay from "./actionLogDisplay.vue";
|
||||
|
||||
export default {
|
||||
name: "TaskSubmitter",
|
||||
components: { actionProgressBar, ActionLogDisplay },
|
||||
name: "ActionButton",
|
||||
components: { ActionProgressBar, ActionLogDisplay },
|
||||
|
||||
props: {
|
||||
submitUrl: {
|
||||
action: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
thing: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
|
|
@ -142,6 +146,12 @@ export default {
|
|||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
submitUrl() {
|
||||
return this.thingActionUrl(this.thing, this.action);
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
progress(newval) {
|
||||
this.$emit("update:progress", newval);
|
||||
|
|
@ -1,5 +1,8 @@
|
|||
<template>
|
||||
<div ref="logContainer" class="log-container uk-margin-left uk-margin-right uk-margin">
|
||||
<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 }}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
<template>
|
||||
<div>
|
||||
<label v-if="dataType == 'number'" class="uk-form-label">{{ label }}
|
||||
<label v-if="dataType == 'number'" class="uk-form-label"
|
||||
>{{ label }}
|
||||
<div class="input-and-buttons-container">
|
||||
<input
|
||||
v-model="value"
|
||||
|
|
@ -30,7 +31,8 @@
|
|||
<span class="material-symbols-outlined">refresh</span>
|
||||
</a>
|
||||
</div>
|
||||
<label v-if="dataType == 'number_array'" class="uk-form-label">{{ label }}
|
||||
<label v-if="dataType == 'number_array'" class="uk-form-label"
|
||||
>{{ label }}
|
||||
<div class="input-and-buttons-container">
|
||||
<input
|
||||
v-for="i in valueLength"
|
||||
|
|
@ -47,7 +49,8 @@
|
|||
</a>
|
||||
</div>
|
||||
</label>
|
||||
<label v-if="dataType == 'number_object'" class="uk-form-label">{{ label }}
|
||||
<label v-if="dataType == 'number_object'" class="uk-form-label"
|
||||
>{{ label }}
|
||||
<div class="input-and-buttons-container">
|
||||
<input
|
||||
v-for="(_v, key) in value"
|
||||
|
|
@ -64,7 +67,8 @@
|
|||
</a>
|
||||
</div>
|
||||
</label>
|
||||
<label v-if="dataType == 'other'" class="uk-form-label">{{ label }}
|
||||
<label v-if="dataType == 'other'" class="uk-form-label"
|
||||
>{{ label }}
|
||||
<div class="input-and-buttons-container">
|
||||
<input
|
||||
:value="value"
|
||||
|
|
|
|||
|
|
@ -1,249 +0,0 @@
|
|||
<template>
|
||||
<div class="uk-padding-small">
|
||||
<div class="uk-flex">
|
||||
<div class="uk-text-bold uk-text-uppercase uk-width-expand">
|
||||
{{ name }}
|
||||
</div>
|
||||
<a href="#" class="uk-icon uk-width-auto" @click="updateForm()"
|
||||
><span class="material-symbols-outlined">cached</span></a
|
||||
>
|
||||
</div>
|
||||
|
||||
<form ref="formContainer" class="uk-form-stacked" @submit.prevent="">
|
||||
<div v-for="(field, index) in schema" :key="index">
|
||||
<div
|
||||
v-if="Array.isArray(field)"
|
||||
class="uk-grid-small uk-width-1-1 uk-child-width-expand"
|
||||
uk-grid
|
||||
>
|
||||
<div v-for="(subfield, subindex) in field" :key="subindex">
|
||||
<component
|
||||
:is="subfield.fieldType"
|
||||
v-model="formData[subfield.name]"
|
||||
v-bind="subfield"
|
||||
>
|
||||
</component>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<component
|
||||
:is="field.fieldType"
|
||||
v-model="formData[field.name]"
|
||||
v-bind="field"
|
||||
>
|
||||
</component>
|
||||
</div>
|
||||
|
||||
<div v-if="isTask" class="uk-margin">
|
||||
<taskSubmitter
|
||||
:submit-url="submitApiUri"
|
||||
:submit-data="formData"
|
||||
:submit-label="submitLabel"
|
||||
@submit="onTaskSubmit"
|
||||
@response="onTaskResponse"
|
||||
@error="onTaskError"
|
||||
>
|
||||
</taskSubmitter>
|
||||
</div>
|
||||
|
||||
<div v-else class="uk-margin">
|
||||
<button
|
||||
type="button"
|
||||
class="uk-button uk-button-primary uk-width-1-1"
|
||||
@click="newQuickRequest(formData)"
|
||||
>
|
||||
{{ submitLabel }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from "axios";
|
||||
|
||||
import numberInput from "../fieldComponents/numberInput";
|
||||
import selectList from "../fieldComponents/selectList";
|
||||
import textInput from "../fieldComponents/textInput";
|
||||
import htmlBlock from "../fieldComponents/htmlBlock";
|
||||
import radioList from "../fieldComponents/radioList";
|
||||
import checkList from "../fieldComponents/checkList";
|
||||
import tagList from "../fieldComponents/tagList";
|
||||
import keyvalList from "../fieldComponents/keyvalList";
|
||||
|
||||
import taskSubmitter from "../genericComponents/taskSubmitter";
|
||||
|
||||
export default {
|
||||
name: "JsonForm",
|
||||
|
||||
components: {
|
||||
numberInput,
|
||||
selectList,
|
||||
textInput,
|
||||
htmlBlock,
|
||||
radioList,
|
||||
checkList,
|
||||
tagList,
|
||||
keyvalList,
|
||||
taskSubmitter
|
||||
},
|
||||
|
||||
props: {
|
||||
name: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: "Plugin"
|
||||
},
|
||||
schema: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
route: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
isTask: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
submitLabel: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: "Submit"
|
||||
},
|
||||
emitOnResponse: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
|
||||
data: function() {
|
||||
return {
|
||||
formData: {}
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
pluginApiUri: function() {
|
||||
return `${this.$store.getters.baseUri}/api/v2/extensions`;
|
||||
},
|
||||
|
||||
submitApiUri: function() {
|
||||
return this.pluginApiUri + this.route;
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
// Whenever the form schema updates, re-check server for field values
|
||||
schema: function() {
|
||||
this.updateFormValues();
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.initialiseFormData();
|
||||
},
|
||||
|
||||
methods: {
|
||||
initialiseFormData() {
|
||||
/*
|
||||
This function initialises the form data.
|
||||
Limitations in Vue mean that newly created formData properties are not reactive.
|
||||
GETting formData values from the server creates the properties, but the form components
|
||||
don't get updated because of this limitation.
|
||||
Here, we step through the form schema, and properly create reactive properties for each component.
|
||||
*/
|
||||
for (const field of this.schema) {
|
||||
if (Array.isArray(field)) {
|
||||
var defaultValue; // Initial value of the form component
|
||||
for (const subfield of field) {
|
||||
// If a default value is given in the schema, use this
|
||||
if (subfield.value) {
|
||||
defaultValue = subfield.value;
|
||||
} else if (subfield.default) {
|
||||
defaultValue = subfield.default;
|
||||
} else {
|
||||
defaultValue = null;
|
||||
}
|
||||
this.$set(this.formData, subfield.name, defaultValue);
|
||||
}
|
||||
} else {
|
||||
// If a default value is given in the schema, use this
|
||||
if (field.value) {
|
||||
defaultValue = field.value;
|
||||
} else if (field.default) {
|
||||
defaultValue = field.default;
|
||||
} else {
|
||||
defaultValue = null;
|
||||
}
|
||||
this.$set(this.formData, field.name, defaultValue);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
updateFormValues() {
|
||||
for (const field of this.schema) {
|
||||
if (Array.isArray(field)) {
|
||||
for (const subfield of field) {
|
||||
// If a default value is given in the schema, use this
|
||||
if (subfield.value) {
|
||||
this.$set(this.formData, subfield.name, subfield.value);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (field.value) {
|
||||
this.$set(this.formData, field.name, field.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
updateForm() {
|
||||
this.$emit("reloadForms");
|
||||
},
|
||||
|
||||
onSubmissionCompleted: function() {
|
||||
if (this.emitOnResponse) {
|
||||
this.$root.$emit(this.emitOnResponse);
|
||||
}
|
||||
this.updateForm();
|
||||
},
|
||||
|
||||
newQuickRequest: function(params) {
|
||||
// Send a quick request
|
||||
axios
|
||||
.post(this.submitApiUri, params)
|
||||
.then(() => {
|
||||
// Do all the finished request stuff
|
||||
this.onSubmissionCompleted();
|
||||
})
|
||||
.catch(error => {
|
||||
this.modalError(error); // Let mixin handle error
|
||||
});
|
||||
},
|
||||
|
||||
onTaskSubmit: function() {},
|
||||
|
||||
onTaskResponse: function() {
|
||||
// Do all the finished request stuff
|
||||
this.onSubmissionCompleted();
|
||||
},
|
||||
|
||||
onTaskError: function(error) {
|
||||
this.modalError(error);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.flex-container {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.flex-container > div {
|
||||
flex-basis: 100%;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -12,12 +12,12 @@
|
|||
<br />
|
||||
{{ $store.state.origin }}
|
||||
</div>
|
||||
<task-submitter
|
||||
v-if="flashLedUri"
|
||||
:submit-url="flashLedUri"
|
||||
<action-button
|
||||
thing="stage"
|
||||
action="flash_led"
|
||||
submit-label="Flash Illumination"
|
||||
:can-terminate="false"
|
||||
:submit-data="{'dt': 0.25}"
|
||||
:submit-data="{ dt: 0.25 }"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -83,18 +83,15 @@
|
|||
|
||||
<script>
|
||||
import axios from "axios";
|
||||
import taskSubmitter from '../../genericComponents/taskSubmitter.vue';
|
||||
import ActionButton from "../../labThingsComponents/actionButton.vue";
|
||||
|
||||
export default {
|
||||
components: { taskSubmitter },
|
||||
name: "StatusPane",
|
||||
components: { ActionButton },
|
||||
|
||||
computed: {
|
||||
things: function() {
|
||||
return this.$store.getters["wot/thingDescriptions"];
|
||||
},
|
||||
flashLedUri() {
|
||||
return this.thingActionUrl("stage", "flash_led");
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -5,39 +5,41 @@
|
|||
</div>
|
||||
<div v-show="backendOK">
|
||||
<ul uk-accordion="multiple: true">
|
||||
<li>
|
||||
<a class="uk-accordion-title" href="#">Settings</a>
|
||||
<div class="uk-accordion-content">
|
||||
<div class="uk-margin">
|
||||
<propertyControl
|
||||
thing-name="background_detect"
|
||||
property-name="tolerance"
|
||||
label="Tolerance"
|
||||
/>
|
||||
</div>
|
||||
<div class="uk-margin">
|
||||
<propertyControl
|
||||
thing-name="background_detect"
|
||||
property-name="fraction"
|
||||
label="Sample coverage required (%)"
|
||||
/>
|
||||
</div>
|
||||
<div class="uk-margin">
|
||||
<taskSubmitter
|
||||
:submit-url="backgroundFractionUri"
|
||||
submit-label="Check coverage"
|
||||
:can-terminate="false"
|
||||
:poll-interval="0.1"
|
||||
@response="alertBackgroundFraction"
|
||||
@error="backgroundDetectError"
|
||||
/>
|
||||
</div>
|
||||
<li>
|
||||
<a class="uk-accordion-title" href="#">Settings</a>
|
||||
<div class="uk-accordion-content">
|
||||
<div class="uk-margin">
|
||||
<propertyControl
|
||||
thing-name="background_detect"
|
||||
property-name="tolerance"
|
||||
label="Tolerance"
|
||||
/>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="uk-margin">
|
||||
<propertyControl
|
||||
thing-name="background_detect"
|
||||
property-name="fraction"
|
||||
label="Sample coverage required (%)"
|
||||
/>
|
||||
</div>
|
||||
<div class="uk-margin">
|
||||
<action-button
|
||||
thing="background_detect"
|
||||
action="background_fraction"
|
||||
submit-label="Check coverage"
|
||||
:can-terminate="false"
|
||||
:poll-interval="0.1"
|
||||
@response="alertBackgroundFraction"
|
||||
@error="backgroundDetectError"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="uk-margin">
|
||||
<taskSubmitter
|
||||
:submit-url="setBackgroundUri"
|
||||
<action-button
|
||||
thing="background_detect"
|
||||
action="set_background"
|
||||
submit-label="Set background"
|
||||
:can-terminate="false"
|
||||
:poll-interval="0.1"
|
||||
|
|
@ -45,8 +47,9 @@
|
|||
/>
|
||||
</div>
|
||||
<div class="uk-margin">
|
||||
<taskSubmitter
|
||||
:submit-url="labelImageUri"
|
||||
<action-button
|
||||
thing="background_detect"
|
||||
action="image_is_sample"
|
||||
submit-label="Check current image"
|
||||
:can-terminate="false"
|
||||
:poll-interval="0.1"
|
||||
|
|
@ -59,29 +62,18 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import taskSubmitter from "../../genericComponents/taskSubmitter";
|
||||
import ActionButton from "../../labThingsComponents/actionButton.vue";
|
||||
import propertyControl from "../../labThingsComponents/propertyControl.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
taskSubmitter,
|
||||
ActionButton,
|
||||
propertyControl
|
||||
},
|
||||
|
||||
computed: {
|
||||
labelImageUri() {
|
||||
return this.thingActionUrl("background_detect", "image_is_sample");
|
||||
},
|
||||
backgroundFractionUri() {
|
||||
return this.thingActionUrl("background_detect", "background_fraction");
|
||||
},
|
||||
setBackgroundUri() {
|
||||
return this.thingActionUrl("background_detect", "set_background");
|
||||
},
|
||||
backendOK() {
|
||||
return (
|
||||
(this.backgroundFractionUri != null) & (this.setBackgroundUri != null)
|
||||
);
|
||||
return this.thingAvailable("background_detect");
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -1,547 +0,0 @@
|
|||
<template>
|
||||
<div id="paneCapture" class="uk-padding-small">
|
||||
<div>
|
||||
<label class="uk-form-label" for="form-stacked-text">Filename</label>
|
||||
<input
|
||||
v-model="filename"
|
||||
class="uk-input uk-width-1-1 uk-form-small"
|
||||
name="inputFilename"
|
||||
placeholder="Leave blank for default"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<p uk-tooltip="title: Capture will be removed automatically; delay: 500">
|
||||
<label
|
||||
><input v-model="temporary" class="uk-checkbox" type="checkbox" />
|
||||
Temporary</label
|
||||
>
|
||||
</p>
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="uk-child-width-1-2" uk-grid>
|
||||
<p>
|
||||
<label
|
||||
><input
|
||||
v-model="fullResolution"
|
||||
class="uk-checkbox"
|
||||
type="checkbox"
|
||||
/>
|
||||
Full resolution</label
|
||||
>
|
||||
</p>
|
||||
<p>
|
||||
<label
|
||||
><input v-model="storeBayer" class="uk-checkbox" type="checkbox" />
|
||||
Store raw data</label
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<p>
|
||||
<label
|
||||
><input v-model="resizeCapture" class="uk-checkbox" type="checkbox" />
|
||||
Resize capture</label
|
||||
>
|
||||
</p>
|
||||
|
||||
<div class="uk-child-width-1-2" uk-grid>
|
||||
<div>
|
||||
<input
|
||||
v-model="resizeDims[0]"
|
||||
:class="resizeClass"
|
||||
class="uk-input uk-form-width-medium uk-form-small"
|
||||
type="number"
|
||||
name="inputResizeW"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
v-model="resizeDims[1]"
|
||||
:class="resizeClass"
|
||||
class="uk-input uk-form-width-medium uk-form-small"
|
||||
type="number"
|
||||
name="inputResizeH"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul uk-accordion="multiple: true">
|
||||
<li>
|
||||
<a class="uk-accordion-title" href="#">Notes</a>
|
||||
<div class="uk-accordion-content">
|
||||
<div class="uk-margin-small">
|
||||
<textarea
|
||||
v-model="captureNotes"
|
||||
class="uk-textarea"
|
||||
rows="5"
|
||||
placeholder="Capture notes"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a class="uk-accordion-title" href="#">Annotations</a>
|
||||
<div class="uk-accordion-content">
|
||||
<keyvalList v-model="annotations" />
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a class="uk-accordion-title" href="#">Tags</a>
|
||||
<div class="uk-accordion-content">
|
||||
<tagList v-model="tags" />
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<hr />
|
||||
|
||||
<ul uk-accordion="multiple: true">
|
||||
<!--Show stack and scan if scan plugin is enabled-->
|
||||
<li v-if="thingAvailable('scan')" :class="{ 'uk-open': scanCapture }">
|
||||
<a class="uk-accordion-title" href="#">Stack and Scan</a>
|
||||
<div class="uk-accordion-content">
|
||||
<div class="uk-margin">
|
||||
<label
|
||||
><input
|
||||
v-model="scanCapture"
|
||||
class="uk-checkbox"
|
||||
type="checkbox"
|
||||
/>
|
||||
Scan capture</label
|
||||
>
|
||||
</div>
|
||||
|
||||
<div :class="{ 'uk-disabled': !scanCapture }">
|
||||
<div class="uk-grid-small uk-child-width-1-3" uk-grid>
|
||||
<div>
|
||||
<label class="uk-form-label" for="form-stacked-text"
|
||||
>x step-size</label
|
||||
>
|
||||
<div class="uk-form-controls">
|
||||
<input
|
||||
v-model="scanStepSize.x"
|
||||
class="uk-input uk-form-small"
|
||||
type="number"
|
||||
name="inputPositionX"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="uk-form-label" for="form-stacked-text"
|
||||
>y step-size</label
|
||||
>
|
||||
<div class="uk-form-controls">
|
||||
<input
|
||||
v-model="scanStepSize.y"
|
||||
class="uk-input uk-form-small"
|
||||
type="number"
|
||||
name="inputPositionY"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="uk-form-label" for="form-stacked-text"
|
||||
>z step-size</label
|
||||
>
|
||||
<div class="uk-form-controls">
|
||||
<input
|
||||
v-model="scanStepSize.z"
|
||||
class="uk-input uk-form-small"
|
||||
type="number"
|
||||
name="inputPositionZ"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="uk-grid-small uk-child-width-1-3" uk-grid>
|
||||
<div>
|
||||
<label class="uk-form-label" for="form-stacked-text"
|
||||
>x steps</label
|
||||
>
|
||||
<div class="uk-form-controls">
|
||||
<input
|
||||
v-model="scanSteps.x"
|
||||
class="uk-input uk-form-small"
|
||||
type="number"
|
||||
name="inputPositionX"
|
||||
min="1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="uk-form-label" for="form-stacked-text"
|
||||
>y steps</label
|
||||
>
|
||||
<div class="uk-form-controls">
|
||||
<input
|
||||
v-model="scanSteps.y"
|
||||
class="uk-input uk-form-small"
|
||||
type="number"
|
||||
name="inputPositionY"
|
||||
min="1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="uk-form-label" for="form-stacked-text"
|
||||
>z steps</label
|
||||
>
|
||||
<div class="uk-form-controls">
|
||||
<input
|
||||
v-model="scanSteps.z"
|
||||
class="uk-input uk-form-small"
|
||||
type="number"
|
||||
name="inputPositionZ"
|
||||
min="1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="uk-margin-small uk-margin-remove-bottom">
|
||||
<label class="uk-form-label" for="form-stacked-text"
|
||||
>Autofocus</label
|
||||
>
|
||||
<select v-model="scanDeltaZ" class="uk-select">
|
||||
<option>Off</option>
|
||||
<option>Coarse</option>
|
||||
<option>Medium</option>
|
||||
<option>Fine</option>
|
||||
<option>Fast</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="thingAvailable('background_detect')"
|
||||
class="uk-margin-small uk-margin-remove-bottom"
|
||||
>
|
||||
<label class="uk-form-label" for="form-stacked-text">
|
||||
<input
|
||||
v-model="detectEmptyFieldsAndSkipAutofocus"
|
||||
class="uk-checkbox"
|
||||
type="checkbox"
|
||||
/>
|
||||
Skip autofocus for empty fields of view
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="uk-margin-small uk-margin-remove-bottom">
|
||||
<label class="uk-form-label" for="form-stacked-text"
|
||||
>Scan Style</label
|
||||
>
|
||||
<select v-model="scanStyle" class="uk-select">
|
||||
<option>Raster</option>
|
||||
<option>Snake</option>
|
||||
<option>Spiral</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="uk-margin-small uk-margin-remove-bottom">
|
||||
<label class="uk-form-label" for="form-stacked-text"
|
||||
>Naming style</label
|
||||
>
|
||||
<select v-model="namingStyle" class="uk-select">
|
||||
<option>Coordinates</option>
|
||||
<option>Number</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<!--Show stack and scan if scan plugin is enabled-->
|
||||
<li
|
||||
v-if="thingAvailable('smart_scan')"
|
||||
:class="{ 'uk-open': smartStack && scanCapture }"
|
||||
>
|
||||
<a class="uk-accordion-title" href="#">Smart Stack</a>
|
||||
<div
|
||||
class="uk-accordion-content"
|
||||
:class="{ 'uk-disabled': !scanCapture }"
|
||||
>
|
||||
<div class="uk-margin">
|
||||
<label
|
||||
><input
|
||||
v-model="smartStack"
|
||||
class="uk-checkbox"
|
||||
type="checkbox"
|
||||
/>
|
||||
Enable smart stacking</label
|
||||
>
|
||||
</div>
|
||||
<p>
|
||||
Smart stacking is an experimental closed-loop Z stack, that checks
|
||||
the Z stack is centred on the focus. It requires Z stacking to be
|
||||
enabled above (we recommend 9 images in Z) and you must select
|
||||
"fast" autofocus.
|
||||
</p>
|
||||
<div :class="{ 'uk-disabled': !smartStack }">
|
||||
<div class="uk-margin-small uk-margin-remove-bottom">
|
||||
<label class="uk-form-label" for="form-stacked-text"
|
||||
>Autofocus range (steps)</label
|
||||
>
|
||||
<input
|
||||
v-model="smartStackAutofocusDz"
|
||||
class="uk-input uk-form-small"
|
||||
type="number"
|
||||
min="1000"
|
||||
/>
|
||||
</div>
|
||||
<div class="uk-margin-small uk-margin-remove-bottom">
|
||||
<label class="uk-form-label" for="form-stacked-text"
|
||||
>Alignment range (steps)</label
|
||||
>
|
||||
<input
|
||||
v-model="smartStackAlignDist"
|
||||
class="uk-input uk-form-small"
|
||||
type="number"
|
||||
min="100"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div v-if="scanCapture" class="uk-margin uk-margin-remove-top">
|
||||
<taskSubmitter
|
||||
v-if="smartStack"
|
||||
:submit-url="smartScanUri"
|
||||
:submit-data="smartScanPayload"
|
||||
:submit-label="'Start Smart Scan'"
|
||||
:button-primary="true"
|
||||
@response="onScanResponse"
|
||||
@error="modalError"
|
||||
>
|
||||
</taskSubmitter>
|
||||
<taskSubmitter
|
||||
v-if="!smartStack"
|
||||
:submit-url="scanUri"
|
||||
:submit-data="scanPayload"
|
||||
:submit-label="'Start Scan'"
|
||||
:button-primary="true"
|
||||
@response="onScanResponse"
|
||||
@error="modalError"
|
||||
>
|
||||
</taskSubmitter>
|
||||
</div>
|
||||
|
||||
<button
|
||||
v-else
|
||||
class="uk-button uk-button-primary uk-margin uk-margin-remove-top uk-width-1-1"
|
||||
@click="handleCapture()"
|
||||
>
|
||||
Capture
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from "axios";
|
||||
|
||||
import tagList from "../../fieldComponents/tagList";
|
||||
import keyvalList from "../../fieldComponents/keyvalList";
|
||||
import taskSubmitter from "../../genericComponents/taskSubmitter";
|
||||
import { syncDataWithLocalStorage } from "../../../syncDataWithLocalStorage";
|
||||
|
||||
/**
|
||||
* The capture settings that should persist in local storage are these ones
|
||||
*/
|
||||
function defaultCaptureSettings() {
|
||||
return {
|
||||
filename: "",
|
||||
temporary: false,
|
||||
fullResolution: false,
|
||||
storeBayer: false,
|
||||
resizeCapture: false,
|
||||
captureNotes: "",
|
||||
scanDeltaZ: "Fast",
|
||||
scanStyle: "Raster",
|
||||
namingStyle: "Coordinates",
|
||||
scanStepSize: {
|
||||
x: 800,
|
||||
y: 640,
|
||||
z: 50
|
||||
},
|
||||
scanSteps: {
|
||||
x: 3,
|
||||
y: 3,
|
||||
z: 5
|
||||
},
|
||||
resizeDims: [640, 480],
|
||||
tags: [],
|
||||
annotations: {
|
||||
Client: "openflexure-microscope-jsclient:builtin"
|
||||
},
|
||||
detectEmptyFieldsAndSkipAutofocus: false,
|
||||
smartStack: false,
|
||||
smartStackThreshold: 0.9,
|
||||
smartStackPeakWidth: 200,
|
||||
smartStackAlignDist: 900,
|
||||
smartStackBacklash: 100,
|
||||
smartStackFitStyle: "chevy",
|
||||
smartStackMAndMIndex: 10,
|
||||
smartStackAutofocusDz: 3000
|
||||
};
|
||||
}
|
||||
|
||||
// Export main app
|
||||
export default {
|
||||
name: "PaneCapture",
|
||||
|
||||
components: {
|
||||
tagList,
|
||||
keyvalList,
|
||||
taskSubmitter
|
||||
},
|
||||
|
||||
data: function() {
|
||||
return {
|
||||
...defaultCaptureSettings(),
|
||||
scanCapture: false // Don't remember the "scan" tickbox in local storage.
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
resizeClass: function() {
|
||||
return {
|
||||
"uk-disabled": !this.resizeCapture
|
||||
};
|
||||
},
|
||||
captureActionUri: function() {
|
||||
return this.thingActionUrl("camera", "capture");
|
||||
},
|
||||
scanUri: function() {
|
||||
return this.thingActionUrl("scan", "scan");
|
||||
},
|
||||
smartScanUri: function() {
|
||||
return this.thingActionUrl("scan", "scan");
|
||||
},
|
||||
basePayload: function() {
|
||||
var payload = {};
|
||||
|
||||
// Filename
|
||||
if (this.filename) {
|
||||
payload.filename = this.filename;
|
||||
}
|
||||
|
||||
// Basic boolean params
|
||||
payload.temporary = this.temporary;
|
||||
payload.use_video_port = !this.fullResolution;
|
||||
payload.bayer = this.storeBayer;
|
||||
|
||||
// Resizing
|
||||
if (this.resizeCapture) {
|
||||
payload.resize = {
|
||||
width: this.resizeDims[0],
|
||||
height: this.resizeDims[1]
|
||||
};
|
||||
}
|
||||
|
||||
// Additional annotations
|
||||
payload.annotations = this.annotations;
|
||||
payload.tags = this.tags;
|
||||
|
||||
// Attach notes
|
||||
if (this.captureNotes) {
|
||||
payload.annotations["Notes"] = this.captureNotes;
|
||||
}
|
||||
|
||||
return payload;
|
||||
},
|
||||
|
||||
scanPayload: function() {
|
||||
// Convert AF selector to dz
|
||||
var afDeltas = {
|
||||
Off: 0,
|
||||
Coarse: 100,
|
||||
Medium: 30,
|
||||
Fine: 10,
|
||||
Fast: 2000
|
||||
};
|
||||
return {
|
||||
...this.basePayload,
|
||||
// Scan params
|
||||
grid: [this.scanSteps.x, this.scanSteps.y, this.scanSteps.z],
|
||||
stride_size: [
|
||||
this.scanStepSize.x,
|
||||
this.scanStepSize.y,
|
||||
this.scanStepSize.z
|
||||
],
|
||||
style: this.scanStyle.toLowerCase(),
|
||||
namemode: this.namingStyle.toLowerCase(),
|
||||
autofocus_dz: afDeltas[this.scanDeltaZ],
|
||||
fast_autofocus: this.scanDeltaZ == "Fast",
|
||||
detect_empty_fields_and_skip_autofocus: this
|
||||
.detectEmptyFieldsAndSkipAutofocus
|
||||
};
|
||||
},
|
||||
smartScanPayload: function() {
|
||||
return {
|
||||
...this.scanPayload,
|
||||
threshold: this.smartStackThreshold,
|
||||
width: this.smartStackPeakWidth,
|
||||
align_dist: this.smartStackAlignDist,
|
||||
backlash: this.smartStackBacklash,
|
||||
fit_style: this.smartStackFitStyle,
|
||||
m_and_m_index: this.smartStackMAndMIndex,
|
||||
autofocus_dz: this.smartStackAutofocusDz
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
// Load settings if they have been saved, and set up watchers to sync with local storage
|
||||
syncDataWithLocalStorage("captureSettings", this, defaultCaptureSettings());
|
||||
|
||||
// A global signal listener to perform a capture action
|
||||
this.$root.$on("globalCaptureEvent", () => {
|
||||
this.handleCapture();
|
||||
});
|
||||
},
|
||||
|
||||
methods: {
|
||||
handleCapture: function() {
|
||||
var payload = this.basePayload;
|
||||
|
||||
// Do capture
|
||||
axios
|
||||
.post(this.captureActionUri, payload)
|
||||
.then(() => {
|
||||
// Flash the stream (capture animation)
|
||||
this.$root.$emit("globalFlashStream");
|
||||
// Update the global capture list
|
||||
this.$root.$emit("globalUpdateCaptures");
|
||||
})
|
||||
.catch(error => {
|
||||
this.modalError(error); // Let mixin handle error
|
||||
});
|
||||
},
|
||||
|
||||
onScanResponse: function() {
|
||||
this.modalNotify("Finished scan.");
|
||||
// Emit signal to update capture list
|
||||
this.$root.$emit("globalUpdateCaptures");
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.deletable-label {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.deletable-label:hover {
|
||||
background-color: #f0506e;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -3,23 +3,7 @@
|
|||
<div uk-grid class="uk-height-1-1 uk-margin-remove uk-padding-remove">
|
||||
<div class="control-component">
|
||||
<vue-friendly-iframe v-if="frame" :src="frame.href"></vue-friendly-iframe>
|
||||
<!-- Handle OpenFlexure Forms -->
|
||||
<div
|
||||
v-for="form in forms"
|
||||
v-else-if="forms"
|
||||
:key="`${form.route}/${form.name}`.replace(/\s+/g, '-').toLowerCase()"
|
||||
class="uk-height-1-1 uk-width-1-1"
|
||||
>
|
||||
<JsonForm
|
||||
:name="form.name"
|
||||
:route="form.route"
|
||||
:is-task="form.isTask"
|
||||
:submit-label="form.submitLabel"
|
||||
:schema="form.schema"
|
||||
:emit-on-response="form.emitOnResponse"
|
||||
v-on="$listeners"
|
||||
/>
|
||||
</div>
|
||||
<!-- OpenFlexure Forms used to be handled here - should now be done another way. -->
|
||||
</div>
|
||||
<div class="view-component uk-width-expand">
|
||||
<galleryContent v-if="viewPanel == 'gallery'" />
|
||||
|
|
@ -30,7 +14,6 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import JsonForm from "../pluginComponents/JsonForm";
|
||||
import streamDisplay from "./streamContent.vue";
|
||||
import galleryContent from "../tabContentComponents/galleryContent.vue";
|
||||
import settingsContent from "../tabContentComponents/settingsContent.vue";
|
||||
|
|
@ -39,7 +22,6 @@ export default {
|
|||
name: "ExtensionContent",
|
||||
|
||||
components: {
|
||||
JsonForm,
|
||||
streamDisplay,
|
||||
galleryContent,
|
||||
settingsContent
|
||||
|
|
|
|||
|
|
@ -1,392 +0,0 @@
|
|||
<template>
|
||||
<div
|
||||
class="capture-card uk-card uk-card-default uk-padding-remove uk-width-medium"
|
||||
:class="{ 'uk-card-secondary': $store.state.darkMode }"
|
||||
>
|
||||
<div class="uk-card-media-top">
|
||||
<a class="lightbox-link" :href="imgURL" :data-caption="name">
|
||||
<img
|
||||
class="uk-width-1-1"
|
||||
:data-src="thumbURL"
|
||||
:alt="id"
|
||||
width="300"
|
||||
height="225"
|
||||
uk-img
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="uk-card-body uk-padding-small">
|
||||
<div
|
||||
class="uk-width-1-1 uk-margin-small uk-margin-remove-left uk-margin-remove-right"
|
||||
uk-grid
|
||||
>
|
||||
<div class="uk-margin-remove-top uk-padding-remove uk-width-expand">
|
||||
{{ name }}
|
||||
</div>
|
||||
<div class="uk-margin-remove-top uk-padding-remove uk-width-auto">
|
||||
<a href="#" class="uk-icon" @click="delCaptureConfirm()">
|
||||
<span class="material-symbols-outlined">delete</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="uk-text-meta uk-margin-remove-top uk-padding-remove uk-width-expand"
|
||||
>
|
||||
<time>{{ time }}</time>
|
||||
</div>
|
||||
<div
|
||||
class="uk-text-meta uk-margin-remove-top uk-padding-remove uk-width-auto"
|
||||
>
|
||||
<a :href="metadataModalTarget" uk-toggle>More...</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="uk-card-footer uk-padding-small">
|
||||
<button
|
||||
v-if="openInImjoyMenuItems.length != 0"
|
||||
class="uk-icon"
|
||||
type="button"
|
||||
>
|
||||
<img
|
||||
style="width:25px;"
|
||||
src="https://imjoy.io/static/img/imjoy-icon.svg"
|
||||
/>
|
||||
</button>
|
||||
<div uk-dropdown="pos: top-center">
|
||||
<ul class="uk-nav uk-dropdown-nav">
|
||||
<li v-for="item in openInImjoyMenuItems" :key="item.name">
|
||||
<a href="#" @click="item.callback(name, imgURL)"
|
||||
><span class="material-symbols-outlined">launch</span
|
||||
>{{ item.title }}</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div v-for="tag in tags" :key="tag" class="uk-display-inline">
|
||||
<span
|
||||
v-if="tag === 'temporary'"
|
||||
class="uk-label uk-label-danger uk-margin-small-right"
|
||||
uk-tooltip="title: Capture will be removed automatically; delay: 500"
|
||||
>Temporary</span
|
||||
>
|
||||
<span
|
||||
v-else
|
||||
class="uk-label uk-margin-small-right deletable-label"
|
||||
@click="delTagConfirm(tag)"
|
||||
>
|
||||
{{ tag }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<a :href="tagModalTarget" uk-toggle>
|
||||
<span class="uk-label uk-label-success uk-margin-small-right">Add</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Metadata modal -->
|
||||
<div :id="metadataModalID" uk-modal>
|
||||
<div
|
||||
class="uk-modal-dialog uk-modal-body"
|
||||
:class="{
|
||||
'uk-light uk-background-secondary': $store.state.darkMode
|
||||
}"
|
||||
>
|
||||
<button class="uk-modal-close-default" type="button" uk-close></button>
|
||||
<h2 class="uk-modal-title">{{ name }}</h2>
|
||||
<p><b>Path: </b>{{ path }}</p>
|
||||
<p><b>Time: </b>{{ time }}</p>
|
||||
<p><b>ID: </b>{{ id }}</p>
|
||||
<p><b>Format: </b>{{ format }}</p>
|
||||
|
||||
<hr />
|
||||
|
||||
<div v-for="(value, key) in annotations" :key="key">
|
||||
<p>
|
||||
<b>{{ key }}: </b>{{ value }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="uk-flex-bottom" uk-grid>
|
||||
<div class="uk-width-2-3">
|
||||
<keyvalList v-model="newAnnotations" />
|
||||
</div>
|
||||
<div class="uk-width-1-3">
|
||||
<button
|
||||
class="uk-button uk-button-primary uk-form-small uk-width-1-1"
|
||||
@click="handleAnnotationsSubmit()"
|
||||
>
|
||||
Add annotation
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- New tag modal -->
|
||||
<div :id="tagModalID" uk-modal>
|
||||
<form
|
||||
class="uk-modal-dialog uk-modal-body uk-margin-auto-vertical"
|
||||
:class="{
|
||||
'uk-light uk-background-secondary': $store.state.darkMode
|
||||
}"
|
||||
@submit.prevent="handleTagSubmit"
|
||||
>
|
||||
<div class="uk-inline">
|
||||
<span class="uk-form-icon"
|
||||
><span class="material-symbols-outlined">label</span></span
|
||||
>
|
||||
<input
|
||||
v-model="newTag"
|
||||
autofocus
|
||||
class="uk-input uk-form-width-medium uk-form-small"
|
||||
type="text"
|
||||
name="tagname"
|
||||
placeholder="tag"
|
||||
/>
|
||||
|
||||
<button
|
||||
class="uk-button uk-button-default uk-margin-left uk-form-small uk-modal-close"
|
||||
type="button"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
class="uk-button uk-button-primary uk-margin-left uk-form-small"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import UIkit from "uikit";
|
||||
import axios from "axios";
|
||||
import { mapState } from "vuex";
|
||||
|
||||
import keyvalList from "../../fieldComponents/keyvalList";
|
||||
|
||||
// Export main app
|
||||
export default {
|
||||
name: "CaptureCard",
|
||||
|
||||
components: {
|
||||
keyvalList
|
||||
},
|
||||
|
||||
props: {
|
||||
id: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
time: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
path: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
format: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
links: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
tags: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: function() {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
annotations: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: function() {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
data: function() {
|
||||
return {
|
||||
newTag: "",
|
||||
newAnnotations: {}
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
tagModalID: function() {
|
||||
return this.makeModalName("tag-modal-");
|
||||
},
|
||||
tagModalTarget: function() {
|
||||
return "#" + this.tagModalID;
|
||||
},
|
||||
metadataModalID: function() {
|
||||
return this.makeModalName("metadata-modal-");
|
||||
},
|
||||
metadataModalTarget: function() {
|
||||
return "#" + this.metadataModalID;
|
||||
},
|
||||
thumbURL: function() {
|
||||
return `${this.links.download.href}?thumbnail=true`;
|
||||
},
|
||||
imgURL: function() {
|
||||
return this.links.download.href;
|
||||
},
|
||||
tagsURL: function() {
|
||||
return this.links.tags.href;
|
||||
},
|
||||
annotationsURL: function() {
|
||||
return this.links.annotations.href;
|
||||
},
|
||||
captureURL: function() {
|
||||
return this.links.self.href;
|
||||
},
|
||||
...mapState("imjoy", { openInImjoyMenuItems: "openImageMenu" })
|
||||
},
|
||||
|
||||
created: function() {},
|
||||
|
||||
methods: {
|
||||
getMetadata: function() {
|
||||
// Send metadata request
|
||||
axios
|
||||
.get(this.captureURL)
|
||||
.then(response => {
|
||||
this.$emit("update:tags", response.data.metadata.image.tags);
|
||||
this.$emit(
|
||||
"update:annotations",
|
||||
response.data.metadata.image.annotations
|
||||
);
|
||||
})
|
||||
.catch(error => {
|
||||
this.modalError(error); // Let mixin handle error
|
||||
});
|
||||
},
|
||||
handleTagSubmit: function(event) {
|
||||
if (this.newTag !== "") {
|
||||
this.newTagRequest(this.newTag);
|
||||
this.newTag = "";
|
||||
}
|
||||
UIkit.modal(event.target.parentNode).hide();
|
||||
},
|
||||
|
||||
handleAnnotationsSubmit: function() {
|
||||
this.putAnnotationsRequest(this.newAnnotations);
|
||||
this.newAnnotations = {};
|
||||
},
|
||||
|
||||
delCaptureConfirm: function() {
|
||||
var context = this;
|
||||
this.modalConfirm("Permanantly delete capture?").then(function() {
|
||||
context.delCaptureRequest();
|
||||
});
|
||||
},
|
||||
|
||||
delCaptureRequest: function() {
|
||||
// Send tag DELETE request
|
||||
axios
|
||||
.delete(this.captureURL)
|
||||
.then(() => {
|
||||
// Emit signal to update capture list
|
||||
this.$root.$emit("globalUpdateCaptures");
|
||||
})
|
||||
.catch(error => {
|
||||
this.modalError(error); // Let mixin handle error
|
||||
});
|
||||
},
|
||||
|
||||
newTagRequest: function(tagString) {
|
||||
// Send tag PUT request
|
||||
axios
|
||||
.put(this.tagsURL, [tagString])
|
||||
.then(() => {
|
||||
// Update tag array
|
||||
this.getTagRequest();
|
||||
})
|
||||
.catch(error => {
|
||||
this.modalError(error); // Let mixin handle error
|
||||
});
|
||||
},
|
||||
|
||||
putAnnotationsRequest: function(annotationsObject) {
|
||||
// Send metadata PUT request
|
||||
axios
|
||||
.put(this.annotationsURL, annotationsObject)
|
||||
.then(() => {
|
||||
// Update metadata object
|
||||
this.getAnnotationsRequest();
|
||||
})
|
||||
.catch(error => {
|
||||
this.modalError(error); // Let mixin handle error
|
||||
});
|
||||
},
|
||||
|
||||
delTagConfirm: function(tagString) {
|
||||
var context = this;
|
||||
this.modalConfirm(`Remove tag '${tagString}'?`).then(function() {
|
||||
context.delTagRequest(tagString);
|
||||
});
|
||||
},
|
||||
|
||||
delTagRequest: function(tagString) {
|
||||
// Send tag DELETE request
|
||||
axios
|
||||
.delete(this.tagsURL, { data: [tagString] })
|
||||
.then(() => {
|
||||
// Update tag array
|
||||
this.getTagRequest();
|
||||
})
|
||||
.catch(error => {
|
||||
this.modalError(error); // Let mixin handle error
|
||||
});
|
||||
},
|
||||
|
||||
getTagRequest: function() {
|
||||
// Send tag request
|
||||
axios
|
||||
.get(this.tagsURL)
|
||||
.then(response => {
|
||||
// Pass the update tag array to the parent
|
||||
this.$emit("update:tags", response.data);
|
||||
})
|
||||
.catch(error => {
|
||||
this.modalError(error); // Let mixin handle error
|
||||
});
|
||||
},
|
||||
|
||||
getAnnotationsRequest: function() {
|
||||
// Send tag request
|
||||
axios
|
||||
.get(this.annotationsURL)
|
||||
.then(response => {
|
||||
// Pass the update annotation array to the parent
|
||||
this.$emit("update:annotations", response.data);
|
||||
})
|
||||
.catch(error => {
|
||||
this.modalError(error); // Let mixin handle error
|
||||
});
|
||||
},
|
||||
|
||||
makeModalName: function(prefix) {
|
||||
return prefix + this.id;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
@ -1,154 +0,0 @@
|
|||
<template>
|
||||
<div
|
||||
class="capture-card uk-card uk-card-primary uk-padding-remove uk-width-medium"
|
||||
>
|
||||
<div class="uk-card-media-top">
|
||||
<a href="#">
|
||||
<img
|
||||
class="uk-width-1-1"
|
||||
:data-src="thumbnail"
|
||||
:alt="id"
|
||||
width="300"
|
||||
height="225"
|
||||
uk-img
|
||||
@click="onClick"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="uk-card-body uk-padding-small">
|
||||
<div
|
||||
class="uk-width-1-1 uk-margin-small uk-margin-remove-left uk-margin-remove-right"
|
||||
uk-grid
|
||||
>
|
||||
<div class="uk-margin-remove-top uk-padding-remove uk-width-expand">
|
||||
<b>{{ type }}: </b>
|
||||
{{ name }}
|
||||
</div>
|
||||
<div class="uk-margin-remove-top uk-padding-remove uk-width-auto">
|
||||
<a href="#" class="uk-icon" @click="delAllConfirm()">
|
||||
<span class="material-symbols-outlined">delete</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="uk-text-meta uk-margin-remove-top uk-padding-remove uk-width-expand"
|
||||
>
|
||||
<time>{{ time }}</time>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="uk-card-footer uk-padding-small">
|
||||
<button
|
||||
v-if="openInImjoyMenuItems.length != 0"
|
||||
class="uk-icon"
|
||||
type="button"
|
||||
>
|
||||
<img
|
||||
style="width:25px;"
|
||||
src="https://imjoy.io/static/img/imjoy-icon.svg"
|
||||
/>
|
||||
</button>
|
||||
<div uk-dropdown="pos: top-center">
|
||||
<ul class="uk-nav uk-dropdown-nav">
|
||||
<li v-for="item in openInImjoyMenuItems" :key="item.name">
|
||||
<a
|
||||
href="#"
|
||||
style="color:black"
|
||||
@click="item.callback(name, allURLs)"
|
||||
><span class="material-symbols-outlined">launch</span
|
||||
>{{ item.title }}</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<span
|
||||
v-for="tag in tags"
|
||||
:key="tag"
|
||||
class="uk-label uk-margin-small-right deletable-label"
|
||||
>
|
||||
{{ tag }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from "axios";
|
||||
import { mapState } from "vuex";
|
||||
|
||||
// Export main app
|
||||
export default {
|
||||
name: "ScanCard",
|
||||
|
||||
props: {
|
||||
id: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
time: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: "Dataset"
|
||||
},
|
||||
thumbnail: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
captures: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
tags: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: function() {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
allURLs: function() {
|
||||
var urls = [];
|
||||
for (var capture of this.captures) {
|
||||
urls.push(capture.links.self.href);
|
||||
}
|
||||
return urls;
|
||||
},
|
||||
...mapState("imjoy", { openInImjoyMenuItems: "openScanMenu" })
|
||||
},
|
||||
|
||||
methods: {
|
||||
onClick: function() {
|
||||
this.$emit("selectFolder", this.id);
|
||||
},
|
||||
|
||||
delAllConfirm: function() {
|
||||
var context = this;
|
||||
this.modalConfirm(
|
||||
"Permanantly delete all captures in this dataset?"
|
||||
).then(function() {
|
||||
context.deleteAll();
|
||||
});
|
||||
},
|
||||
|
||||
deleteAll: function() {
|
||||
axios.all(this.allURLs.map(l => axios.delete(l))).then(() => {
|
||||
// Emit signal to update capture list
|
||||
this.$root.$emit("globalUpdateCaptures");
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
@ -1,163 +0,0 @@
|
|||
<template>
|
||||
<div v-if="zipBuilderUri" class="uk-width-small">
|
||||
<div v-show="downloadReady">
|
||||
<button
|
||||
:disabled="isDownloading"
|
||||
type="button"
|
||||
class="uk-button uk-button-default uk-width-1-1"
|
||||
@click="downloadWithAxios()"
|
||||
>
|
||||
{{ isDownloading ? `${downloadProgress}%` : "Download" }}
|
||||
</button>
|
||||
</div>
|
||||
<div v-show="!downloadReady">
|
||||
<taskSubmitter
|
||||
v-if="zipBuilderUri"
|
||||
:can-terminate="false"
|
||||
:submit-url="zipBuilderUri"
|
||||
:submit-label="'Create ZIP'"
|
||||
:submit-data="captureIds"
|
||||
@response="onResponse"
|
||||
@error="modalError"
|
||||
>
|
||||
</taskSubmitter>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from "axios";
|
||||
import taskSubmitter from "../../genericComponents/taskSubmitter";
|
||||
|
||||
// Export main app
|
||||
export default {
|
||||
name: "ZipDownloader",
|
||||
|
||||
components: {
|
||||
taskSubmitter
|
||||
},
|
||||
|
||||
props: {
|
||||
captureIds: {
|
||||
type: Array,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
data: function() {
|
||||
return {
|
||||
downloadProgress: 0,
|
||||
isDownloading: false,
|
||||
downloadReady: false,
|
||||
downloadUrl: null,
|
||||
zipBuilderUri: null,
|
||||
zipGetterUri: null,
|
||||
lastSessionId: null
|
||||
};
|
||||
},
|
||||
|
||||
mounted: function() {
|
||||
this.updateZipperUri();
|
||||
},
|
||||
|
||||
created: function() {
|
||||
// Watch for host 'ready', then update status
|
||||
this.unwatchStoreFunction = this.$store.watch(
|
||||
(state, getters) => {
|
||||
return getters.ready;
|
||||
},
|
||||
ready => {
|
||||
if (ready) {
|
||||
// If the connection is now ready, update zipper URL
|
||||
this.updateZipperUri();
|
||||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
methods: {
|
||||
updateZipperUri: function() {
|
||||
if (this.$store.state.available) {
|
||||
/*axios
|
||||
.get(this.pluginsUri) // Get a list of plugins
|
||||
.then(response => {
|
||||
var plugins = response.data;
|
||||
var foundExtension = plugins.find(
|
||||
e => e.title === "org.openflexure.zipbuilder"
|
||||
);
|
||||
// if ZipBuilderPlugin is enabled
|
||||
if (foundExtension) {
|
||||
// Get plugin action links
|
||||
this.zipBuilderUri = foundExtension.links.build.href;
|
||||
this.zipGetterUri = foundExtension.links.get.href;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
this.modalError(error); // Let mixin handle error
|
||||
});*/
|
||||
} else {
|
||||
this.zipBuilderUri = null;
|
||||
this.zipGetterUri = null;
|
||||
}
|
||||
},
|
||||
|
||||
resetZipper: function() {
|
||||
this.downloadReady = false;
|
||||
},
|
||||
|
||||
forceFileDownload(response) {
|
||||
// Start file download by creating and clicking an invisible link
|
||||
// One day I hope for a better solution to exist for this...
|
||||
// A man can dream.....
|
||||
const url = window.URL.createObjectURL(new Blob([response.data]));
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.setAttribute("download", `${this.lastSessionId}.zip`); //or any other extension
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
},
|
||||
|
||||
downloadWithAxios() {
|
||||
// Configure progress indicator and response type
|
||||
let config = {
|
||||
responseType: "blob",
|
||||
onDownloadProgress: progressEvent => {
|
||||
this.downloadProgress = Math.floor(
|
||||
(progressEvent.loaded * 100) / progressEvent.total
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// Set downloading flag
|
||||
this.isDownloading = true;
|
||||
|
||||
// Start download
|
||||
axios
|
||||
.get(this.downloadUrl, config)
|
||||
.then(response => {
|
||||
this.forceFileDownload(response);
|
||||
this.deleteLastZip();
|
||||
this.resetZipper();
|
||||
})
|
||||
.catch(error => {
|
||||
this.modalError(error); // Let mixin handle error
|
||||
})
|
||||
.finally(() => {
|
||||
this.isDownloading = false;
|
||||
});
|
||||
},
|
||||
|
||||
deleteLastZip() {
|
||||
axios.delete(this.downloadUrl).catch(error => {
|
||||
this.modalError(error); // Let mixin handle error
|
||||
});
|
||||
},
|
||||
|
||||
onResponse: function(response) {
|
||||
this.lastSessionId = response.output.id;
|
||||
this.downloadUrl = `${this.zipGetterUri}/${this.lastSessionId}`;
|
||||
this.downloadReady = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
@ -1,455 +0,0 @@
|
|||
<template>
|
||||
<div 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"
|
||||
>
|
||||
<!-- Left side controls -->
|
||||
<div
|
||||
class="uk-navbar-left uk-padding-remove-top uk-padding-remove-bottom"
|
||||
>
|
||||
<ul class="uk-navbar-nav">
|
||||
<li :class="[sortDescending ? 'uk-active' : '']">
|
||||
<a class="uk-icon" href="#" @click="sortDescending = true">
|
||||
<span class="material-symbols-outlined">keyboard_arrow_down</span>
|
||||
</a>
|
||||
</li>
|
||||
<li :class="[!sortDescending ? 'uk-active' : '']">
|
||||
<a class="uk-icon" href="#" @click="sortDescending = false">
|
||||
<span class="material-symbols-outlined">keyboard_arrow_up</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#">Filter</a>
|
||||
<div
|
||||
:class="{
|
||||
'uk-light uk-background-secondary': $store.state.darkMode
|
||||
}"
|
||||
class="uk-navbar-dropdown"
|
||||
>
|
||||
<ul class="uk-nav uk-navbar-dropdown-nav">
|
||||
<form class="uk-form-stacked">
|
||||
<div
|
||||
v-for="tag in allTags"
|
||||
:key="tag"
|
||||
class="uk-margin-small"
|
||||
>
|
||||
<label>
|
||||
<input
|
||||
:id="tag"
|
||||
v-model="checkedTags"
|
||||
:value="tag"
|
||||
checked
|
||||
class="uk-checkbox"
|
||||
type="checkbox"
|
||||
/>
|
||||
{{ tag }}
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Right side buttons -->
|
||||
<div class="uk-navbar-right">
|
||||
<div class="uk-grid">
|
||||
<div>
|
||||
<button
|
||||
class="uk-button uk-button-default uk-width-1-1"
|
||||
type="button"
|
||||
@click="updateCaptures()"
|
||||
>
|
||||
Refresh Captures
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<ZipDownloader :capture-ids="Object.keys(filteredCaptures)" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Gallery -->
|
||||
<div
|
||||
v-if="$store.getters.ready"
|
||||
class="uk-padding-remove-top"
|
||||
uk-lightbox="toggle: .lightbox-link"
|
||||
>
|
||||
<!-- Folder heading -->
|
||||
<div
|
||||
v-if="galleryFolder"
|
||||
class="gallery-folder-heading uk-flex uk-flex-middle"
|
||||
>
|
||||
<a class="uk-icon uk-margin-remove" href="#" @click="galleryBack()">
|
||||
<span class="material-symbols-outlined">arrow_back</span>
|
||||
</a>
|
||||
<div class="uk-margin-left">
|
||||
<h3 class="uk-margin-remove uk-margin-left">
|
||||
<b>SCAN</b>
|
||||
{{ allScans[galleryFolder].name }}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Gallery capture cards -->
|
||||
<div class="gallery-grid">
|
||||
<div v-for="item in pagedItems" :key="item.id">
|
||||
<scanCard
|
||||
v-if="'isScan' in item"
|
||||
:id="item.id"
|
||||
:name="item.name"
|
||||
:time="item.time"
|
||||
:tags="item.tags"
|
||||
:type="item.type"
|
||||
:captures="item.captures"
|
||||
:thumbnail="item.thumbnail"
|
||||
@selectFolder="selectFolder"
|
||||
/>
|
||||
<captureCard
|
||||
v-else
|
||||
:id="item.id"
|
||||
:links="item.links"
|
||||
:name="item.name"
|
||||
:format="item.format"
|
||||
:path="item.path"
|
||||
:time="item.time"
|
||||
:tags="item.tags"
|
||||
:annotations="item.annotations"
|
||||
@update:tags="item.tags = $event"
|
||||
@update:annotations="item.annotations = $event"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Paginate
|
||||
v-model="page"
|
||||
:page-count="numberOfPages"
|
||||
:page-range="3"
|
||||
:margin-pages="1"
|
||||
:container-class="'uk-pagination uk-flex-center'"
|
||||
:prev-text="'Prev'"
|
||||
:next-text="'Next'"
|
||||
:page-class="'page-item'"
|
||||
:active-class="'uk-active'"
|
||||
:disabled-class="'uk-disabled'"
|
||||
:click-handler="scrollToTop()"
|
||||
>
|
||||
</Paginate>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from "axios";
|
||||
import Paginate from "vuejs-paginate";
|
||||
|
||||
import captureCard from "./galleryComponents/captureCard.vue";
|
||||
import scanCard from "./galleryComponents/scanCard.vue";
|
||||
import ZipDownloader from "./galleryComponents/zipDownloader";
|
||||
|
||||
// Export main app
|
||||
export default {
|
||||
name: "GalleryDisplay",
|
||||
|
||||
components: {
|
||||
captureCard,
|
||||
scanCard,
|
||||
ZipDownloader,
|
||||
Paginate
|
||||
},
|
||||
|
||||
data: function() {
|
||||
return {
|
||||
captures: [],
|
||||
checkedTags: [],
|
||||
sortDescending: true,
|
||||
galleryFolder: "",
|
||||
scanTag: "scan",
|
||||
unwatchStoreFunction: null,
|
||||
maxitems: 10,
|
||||
page: 1
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
capturesUri: function() {
|
||||
return `${this.$store.getters.baseUri}/api/v2/captures`;
|
||||
},
|
||||
allTags: function() {
|
||||
// Return an array of unique tags across all captures
|
||||
var tags = [];
|
||||
for (var capture of this.captures) {
|
||||
for (var tag of capture.tags) {
|
||||
if (!tags.includes(tag)) {
|
||||
tags.push(tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
return tags.sort();
|
||||
},
|
||||
|
||||
noScanCaptures: function() {
|
||||
// List of captures that are not part of a scan
|
||||
var captures = [];
|
||||
for (var capture of this.captures) {
|
||||
// Add to capture list if matched
|
||||
if (!this.isDatasetPopulated(capture.dataset)) {
|
||||
captures.push(capture);
|
||||
}
|
||||
}
|
||||
|
||||
return captures;
|
||||
},
|
||||
|
||||
allScans: function() {
|
||||
// List of scans as capture-like objects
|
||||
var scans = {};
|
||||
|
||||
for (var capture of this.captures) {
|
||||
var dataset = capture.dataset;
|
||||
|
||||
if (this.isDatasetPopulated(dataset)) {
|
||||
var id = dataset["id"];
|
||||
|
||||
// If this scan ID hasn't been seen before
|
||||
if (!(id in scans)) {
|
||||
scans[id] = {};
|
||||
scans[id].id = id;
|
||||
scans[id].name = dataset.name;
|
||||
scans[id].type = dataset.type;
|
||||
// Use time of first found capture in dataset
|
||||
scans[id].time = capture.time;
|
||||
scans[id].isScan = true;
|
||||
scans[id].captures = [];
|
||||
scans[id].tags = [];
|
||||
}
|
||||
// Add the capture object to the scan
|
||||
scans[id].captures.push(capture);
|
||||
|
||||
// Append missing tags
|
||||
for (var tag of capture.tags) {
|
||||
if (!scans[id].tags.includes(tag)) {
|
||||
scans[id].tags.push(tag);
|
||||
}
|
||||
}
|
||||
|
||||
// Create a preview thumbnail
|
||||
if (!("thumbnail" in scans[id])) {
|
||||
scans[
|
||||
id
|
||||
].thumbnail = `${capture.links.download.href}?thumbnail=true`;
|
||||
}
|
||||
}
|
||||
}
|
||||
return scans;
|
||||
},
|
||||
|
||||
scanList: function() {
|
||||
// List of scans, obtained from this.allScans values
|
||||
return Object.values(this.allScans);
|
||||
},
|
||||
|
||||
itemList: function() {
|
||||
// Get list of current items to show
|
||||
// If galleryFolder (ie inside a scan folder), show scan captures
|
||||
// Otherwise, show root captures and scan cards
|
||||
if (this.galleryFolder) {
|
||||
return this.allScans[this.galleryFolder].captures;
|
||||
} else {
|
||||
return this.noScanCaptures.concat(this.scanList);
|
||||
}
|
||||
},
|
||||
|
||||
filteredItems: function() {
|
||||
// Filter itemList by checkedTags
|
||||
return this.filterCaptures(this.itemList, this.checkedTags);
|
||||
},
|
||||
|
||||
filteredCaptures: function() {
|
||||
var captures = {};
|
||||
|
||||
for (var item of this.filteredItems) {
|
||||
// If it's a dataset
|
||||
if ("captures" in item) {
|
||||
for (var capture of item.captures) {
|
||||
// Get the ID of each capture in the set
|
||||
captures[capture.id] = capture;
|
||||
}
|
||||
} else {
|
||||
// If it's a single capture, get the ID of the capture
|
||||
captures[item.id] = item;
|
||||
}
|
||||
}
|
||||
|
||||
return captures;
|
||||
},
|
||||
|
||||
sortedItems: function() {
|
||||
// Sort filteredItems using sortCaptures function
|
||||
return this.sortCaptures(this.filteredItems);
|
||||
},
|
||||
|
||||
pagedItems: function() {
|
||||
let startIndex = (this.page - 1) * this.maxitems;
|
||||
return this.sortedItems.slice(startIndex, startIndex + this.maxitems);
|
||||
},
|
||||
|
||||
numberOfPages: function() {
|
||||
return Math.floor(this.sortedItems.length / this.maxitems);
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
// Update on mount (does nothing if not connected)
|
||||
this.updateCaptures();
|
||||
// A global signal listener to perform a gallery refresh
|
||||
this.$root.$on("globalUpdateCaptures", () => {
|
||||
this.updateCaptures();
|
||||
});
|
||||
},
|
||||
|
||||
created: function() {
|
||||
// Watch for host 'ready', then update status
|
||||
this.unwatchStoreFunction = this.$store.watch(
|
||||
(state, getters) => {
|
||||
return getters.ready;
|
||||
},
|
||||
ready => {
|
||||
if (ready) {
|
||||
// If the connection is now ready, update capture list
|
||||
this.updateCaptures();
|
||||
} else {
|
||||
// If the connection is now disconnected, empty capture list
|
||||
this.captures = {};
|
||||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
// Remove global signal listener to perform a gallery refresh
|
||||
this.$root.$off("globalUpdateCaptures");
|
||||
// Then we call that function here to unwatch
|
||||
if (this.unwatchStoreFunction) {
|
||||
this.unwatchStoreFunction();
|
||||
this.unwatchStoreFunction = null;
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
scrollToTop() {
|
||||
const el = document.querySelector("#container-left");
|
||||
if (el) {
|
||||
el.scrollTop = 0;
|
||||
}
|
||||
},
|
||||
|
||||
updateCaptures: function() {
|
||||
if (this.$store.state.available) {
|
||||
axios
|
||||
.get(this.capturesUri)
|
||||
.then(response => {
|
||||
this.captures = response.data;
|
||||
})
|
||||
.catch(error => {
|
||||
this.modalError(error); // Let mixin handle error
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
isDatasetPopulated: function(dataset) {
|
||||
if (
|
||||
!dataset || // If no dataset key
|
||||
(dataset.constructor === Object && // Or dataset is an object...
|
||||
Object.keys(dataset).length === 0) // ...but it's empty
|
||||
) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
filterCaptures: function(list, filterTags) {
|
||||
// Filter a list of captures by an array of tags
|
||||
var result = [];
|
||||
for (var capture of list) {
|
||||
// Assume exclusion
|
||||
var includeCapture = false;
|
||||
|
||||
// Filter by selected tags
|
||||
var tags = capture.tags;
|
||||
let checker = (arr, target) => target.every(v => arr.includes(v));
|
||||
// True if all tags match
|
||||
includeCapture = checker(tags, filterTags);
|
||||
|
||||
// Add to capture list if matched
|
||||
if (includeCapture == true) {
|
||||
result.push(capture);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
sortCaptures: function(list) {
|
||||
// Sort a list of captures by metadata time
|
||||
function compare(a, b) {
|
||||
if (a.time < b.time) return -1;
|
||||
if (a.time > b.time) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (this.sortDescending == true) {
|
||||
return list.sort(compare).reverse();
|
||||
} else {
|
||||
return list.sort(compare);
|
||||
}
|
||||
},
|
||||
|
||||
galleryBack: function() {
|
||||
this.galleryFolder = "";
|
||||
this.page = 1;
|
||||
},
|
||||
|
||||
selectFolder: function(folderID) {
|
||||
this.galleryFolder = folderID;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.gallery-navbar {
|
||||
border-width: 0 0 1px 0;
|
||||
border-style: solid;
|
||||
border-color: rgba(180, 180, 180, 0.25);
|
||||
}
|
||||
|
||||
.gallery-navbar,
|
||||
.gallery-folder-heading {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.gallery-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, 320px);
|
||||
justify-content: center;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.gallery-grid > div {
|
||||
display: inline-block;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/deep/ .capture-card {
|
||||
width: 300px;
|
||||
height: 100%; // Used to have all cards in a row match their heights
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -68,16 +68,18 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<taskSubmitter
|
||||
:submit-url="zeroActionUri"
|
||||
:submit-label="'Zero Coordinates'"
|
||||
<action-button
|
||||
thing="stage"
|
||||
action="set_zero_position"
|
||||
submit-label="Zero Coordinates"
|
||||
:can-terminate="false"
|
||||
@finished="updatePosition"
|
||||
@error="modalError"
|
||||
></taskSubmitter>
|
||||
<taskSubmitter
|
||||
:submit-url="recentreActionUri"
|
||||
:submit-label="'Re-centre Stage'"
|
||||
/>
|
||||
<action-button
|
||||
thing="auto_recentre_stage"
|
||||
action="recentre"
|
||||
submit-label="Re-centre Stage"
|
||||
:can-terminate="true"
|
||||
:requires-confirmation="true"
|
||||
:modal-progress="true"
|
||||
|
|
@ -86,7 +88,7 @@
|
|||
"
|
||||
@finished="updatePosition"
|
||||
@error="modalError"
|
||||
></taskSubmitter>
|
||||
/>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
|
|
@ -109,9 +111,10 @@
|
|||
</a>
|
||||
</div>
|
||||
<p>
|
||||
<taskSubmitter
|
||||
ref="moveTaskSubmitter"
|
||||
:submit-url="absoluteMoveUri"
|
||||
<action-button
|
||||
ref="moveButton"
|
||||
thing="stage"
|
||||
action="move_absolute"
|
||||
:submit-data="setPosition"
|
||||
:submit-label="'Move'"
|
||||
:can-terminate="true"
|
||||
|
|
@ -122,7 +125,7 @@
|
|||
moveLock = false;
|
||||
"
|
||||
@error="modalError"
|
||||
></taskSubmitter>
|
||||
/>
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
|
|
@ -134,9 +137,9 @@
|
|||
<div class="uk-accordion-content">
|
||||
<div class="uk-grid-small uk-child-width-expand" uk-grid>
|
||||
<div v-show="!isAutofocusing || isAutofocusing == 1">
|
||||
<taskSubmitter
|
||||
v-if="fastAutofocusUri"
|
||||
:submit-url="fastAutofocusUri"
|
||||
<action-button
|
||||
thing="autofocus"
|
||||
action="fast_autofocus"
|
||||
:submit-data="{ dz: 2000 }"
|
||||
:submit-label="'Autofocus'"
|
||||
:button-primary="false"
|
||||
|
|
@ -147,36 +150,36 @@
|
|||
isAutofocusing = 0;
|
||||
"
|
||||
@error="modalError"
|
||||
></taskSubmitter>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li v-show="captureUri" class="uk-open">
|
||||
<li class="uk-open">
|
||||
<a class="uk-accordion-title" href="#">Image Capture</a>
|
||||
<div class="uk-accordion-content">
|
||||
<div class="uk-margin">
|
||||
<taskSubmitter
|
||||
v-if="captureUri"
|
||||
:submit-url="captureUri"
|
||||
<action-button
|
||||
thing="camera"
|
||||
action="capture_jpeg"
|
||||
:submit-data="{ resolution: 'main' }"
|
||||
:submit-label="'Low Resolution'"
|
||||
:submit-on-event="'globalCaptureEvent'"
|
||||
@response="handleCaptureResponse"
|
||||
@error="modalError"
|
||||
></taskSubmitter>
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="uk-margin">
|
||||
<taskSubmitter
|
||||
v-if="captureUri"
|
||||
:submit-url="captureUri"
|
||||
<action-button
|
||||
thing="camera"
|
||||
action="capture_jpeg"
|
||||
:submit-data="{ resolution: 'full' }"
|
||||
submit-label="Full Resolution"
|
||||
submit-on-event="globalCaptureEvent"
|
||||
:submit-on-event="'globalCaptureEvent'"
|
||||
@response="handleCaptureResponse"
|
||||
@error="modalError"
|
||||
></taskSubmitter>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
|
|
@ -194,14 +197,14 @@
|
|||
|
||||
<script>
|
||||
import axios from "axios";
|
||||
import taskSubmitter from "../../genericComponents/taskSubmitter";
|
||||
import ActionButton from "../../labThingsComponents/actionButton.vue";
|
||||
|
||||
// Export main app
|
||||
export default {
|
||||
name: "PaneNavigate",
|
||||
|
||||
components: {
|
||||
taskSubmitter
|
||||
ActionButton
|
||||
},
|
||||
|
||||
data: function() {
|
||||
|
|
@ -228,24 +231,9 @@ export default {
|
|||
baseUri: function() {
|
||||
return this.$store.getters.baseUri;
|
||||
},
|
||||
absoluteMoveUri: function() {
|
||||
return this.thingActionUrl("stage", "move_absolute");
|
||||
},
|
||||
zeroActionUri: function() {
|
||||
return this.thingActionUrl("stage", "set_zero_position");
|
||||
},
|
||||
recentreActionUri: function() {
|
||||
return this.thingActionUrl("auto_recentre_stage", "recentre");
|
||||
},
|
||||
positionStatusUri: function() {
|
||||
return `${this.baseUri}/stage/position`;
|
||||
},
|
||||
fastAutofocusUri: function() {
|
||||
return this.thingActionUrl("autofocus", "fast_autofocus");
|
||||
},
|
||||
captureUri: function() {
|
||||
return this.thingActionUrl("camera", "capture_jpeg");
|
||||
},
|
||||
moveInImageCoordinatesUri: function() {
|
||||
return this.thingActionUrl(
|
||||
"camera_stage_mapping",
|
||||
|
|
@ -327,7 +315,7 @@ export default {
|
|||
await this.startMoveTask();
|
||||
},
|
||||
async startMoveTask() {
|
||||
await this.$refs.moveTaskSubmitter.startTask();
|
||||
await this.$refs.moveButton.startTask();
|
||||
},
|
||||
moveInImageCoordinatesRequest: function(x, y) {
|
||||
// If not movement-locked
|
||||
|
|
@ -359,18 +347,6 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
zeroRequest: function() {
|
||||
// Send move request
|
||||
axios
|
||||
.post(this.zeroActionUri)
|
||||
.then(() => {
|
||||
this.updatePosition(); // Update the position in text boxes
|
||||
})
|
||||
.catch(error => {
|
||||
this.modalError(error); // Let mixin handle error
|
||||
});
|
||||
},
|
||||
|
||||
async updatePosition() {
|
||||
this.setPosition = await this.readThingProperty("stage", "position");
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
<template>
|
||||
<div
|
||||
<div
|
||||
v-observe-visibility="visibilityChanged"
|
||||
class="galleryDisplay uk-padding uk-padding-remove-top"
|
||||
>
|
||||
>
|
||||
<!-- Gallery nav bar -->
|
||||
<nav
|
||||
class="gallery-navbar uk-navbar-container uk-navbar-transparent"
|
||||
|
|
@ -45,24 +45,26 @@
|
|||
<div class="uk-card">
|
||||
<div class="uk-card-body">
|
||||
<h3 class="uk-card-title">{{ item.name }}</h3>
|
||||
<task-submitter
|
||||
<action-button
|
||||
thing="smart_scan"
|
||||
action="create_zip_of_scan"
|
||||
submit-label="Download ZIP"
|
||||
:can-terminate="false"
|
||||
:submit-data="{'scan_name': item.name}"
|
||||
:submit-data="{ scan_name: item.name }"
|
||||
:button-primary="true"
|
||||
:submit-url="createZipOfScanUri"
|
||||
@response="downloadZipFile"
|
||||
@error="modalError"
|
||||
/>
|
||||
<button class="uk-button" @click="deleteScan(item.name)">
|
||||
Delete
|
||||
</button>
|
||||
<task-submitter
|
||||
<action-button
|
||||
submit-label="Stitch Images"
|
||||
thing="smart_scan"
|
||||
action="stitch_scan"
|
||||
:can-terminate="false"
|
||||
:submit-data="{'scan_name': item.name}"
|
||||
:submit-data="{ scan_name: item.name }"
|
||||
:button-primary="false"
|
||||
:submit-url="stitchUri"
|
||||
:modal-progress="true"
|
||||
@error="modalError"
|
||||
/>
|
||||
|
|
@ -87,12 +89,12 @@
|
|||
|
||||
<script>
|
||||
import axios from "axios";
|
||||
import taskSubmitter from '../genericComponents/taskSubmitter.vue';
|
||||
import actionButton from "../labThingsComponents/actionButton.vue";
|
||||
|
||||
// Export main app
|
||||
export default {
|
||||
components: { taskSubmitter },
|
||||
name: "ScanListContent",
|
||||
components: { actionButton },
|
||||
|
||||
data: function() {
|
||||
return {
|
||||
|
|
@ -108,12 +110,6 @@ export default {
|
|||
"readproperty",
|
||||
true
|
||||
);
|
||||
},
|
||||
createZipOfScanUri() {
|
||||
return this.thingActionUrl("smart_scan", "create_zip_of_scan");
|
||||
},
|
||||
stitchUri() {
|
||||
return this.thingActionUrl("smart_scan", "stitch_scan");
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -162,7 +158,7 @@ export default {
|
|||
},
|
||||
async updateScans() {
|
||||
let scans = await this.readThingProperty("smart_scan", "scans");
|
||||
if (!scans | scans.length == 0) {
|
||||
if (!scans | (scans.length == 0)) {
|
||||
this.scans = scans;
|
||||
}
|
||||
scans.forEach(scan => {
|
||||
|
|
@ -216,7 +212,7 @@ export default {
|
|||
},
|
||||
async downloadZipFile(response) {
|
||||
const scan_name = response.input.scan_name;
|
||||
const filename = `${scan_name}_images.zip`
|
||||
const filename = `${scan_name}_images.zip`;
|
||||
const url = response.output.href;
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
|
|
|
|||
|
|
@ -2,14 +2,15 @@
|
|||
<div id="CSMCalibrationSettings">
|
||||
<!--Show auto calibrate if default plugin is enabled-->
|
||||
<div v-if="'calibrate_xy' in actions" class="uk-margin-small">
|
||||
<taskSubmitter
|
||||
<action-button
|
||||
:button-primary="true"
|
||||
:can-terminate="true"
|
||||
:requires-confirmation="true"
|
||||
:confirmation-message="
|
||||
'Start recalibration of the stage to the camera? This may take a while, and the microscope will be locked during this time.'
|
||||
"
|
||||
:submit-url="calibrateXYUri"
|
||||
thing="camera_stage_mapper"
|
||||
action="calibrate_xy"
|
||||
:submit-label="'Auto-Calibrate using camera'"
|
||||
:modal-progress="true"
|
||||
@response="onRecalibrateResponse"
|
||||
|
|
@ -29,14 +30,14 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import taskSubmitter from "../../../genericComponents/taskSubmitter";
|
||||
import ActionButton from "../../../labThingsComponents/actionButton.vue";
|
||||
|
||||
// Export main app
|
||||
export default {
|
||||
name: "CSMCalibrationSettings",
|
||||
|
||||
components: {
|
||||
taskSubmitter
|
||||
ActionButton
|
||||
},
|
||||
|
||||
props: {
|
||||
|
|
@ -55,9 +56,6 @@ export default {
|
|||
properties() {
|
||||
return this.$store.getters["wot/thingDescription"]("camera_stage_mapping")
|
||||
.properties;
|
||||
},
|
||||
calibrateXYUri() {
|
||||
return this.thingActionUrl("camera_stage_mapping", "calibrate_xy");
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -2,55 +2,55 @@
|
|||
<div>
|
||||
<!--Show auto calibrate if default plugin is enabled-->
|
||||
<div v-if="'full_auto_calibrate' in actions" class="uk-margin-small">
|
||||
<taskSubmitter
|
||||
<action-button
|
||||
:can-terminate="false"
|
||||
:requires-confirmation="true"
|
||||
:confirmation-message="
|
||||
'Start recalibration? This may take a while, and the microscope will be locked during this time.'
|
||||
"
|
||||
:submit-url="cameraUri + 'full_auto_calibrate'"
|
||||
thing="camera"
|
||||
action="full_auto_calibrate"
|
||||
:submit-label="'Full Auto-Calibrate'"
|
||||
@response="onRecalibrateResponse"
|
||||
@error="modalError"
|
||||
>
|
||||
</taskSubmitter>
|
||||
/>
|
||||
</div>
|
||||
<div v-if="'auto_expose_from_minimum' in actions" class="uk-margin-small">
|
||||
<taskSubmitter
|
||||
<action-button
|
||||
:can-terminate="false"
|
||||
:requires-confirmation="false"
|
||||
:submit-url="cameraUri + 'auto_expose_from_minimum'"
|
||||
thing="camera"
|
||||
action="auto_expose_from_minimum"
|
||||
:submit-label="'Auto gain & shutter speed'"
|
||||
@response="onRecalibrateResponse"
|
||||
@error="modalError"
|
||||
>
|
||||
</taskSubmitter>
|
||||
/>
|
||||
</div>
|
||||
<div v-if="'calibrate_white_balance' in actions" class="uk-margin-small">
|
||||
<taskSubmitter
|
||||
<action-button
|
||||
:can-terminate="false"
|
||||
:requires-confirmation="false"
|
||||
:submit-url="cameraUri + 'calibrate_white_balance'"
|
||||
thing="camera"
|
||||
action="calibrate_white_balance"
|
||||
:submit-label="'Auto white balance'"
|
||||
@response="onRecalibrateResponse"
|
||||
@error="modalError"
|
||||
>
|
||||
</taskSubmitter>
|
||||
/>
|
||||
</div>
|
||||
<div v-if="'calibrate_lens_shading' in actions" class="uk-margin-small">
|
||||
<taskSubmitter
|
||||
<action-button
|
||||
:can-terminate="false"
|
||||
:requires-confirmation="true"
|
||||
:confirmation-message="
|
||||
'Is the microscope looking at an evenly illuminated, empty field of view? ' +
|
||||
'If not, the current image will show through in any images captured afterwards.'
|
||||
"
|
||||
:submit-url="cameraUri + 'calibrate_lens_shading'"
|
||||
thing="camera"
|
||||
action="calibrate_lens_shading"
|
||||
:submit-label="'Auto flat field correction'"
|
||||
@response="onRecalibrateResponse"
|
||||
@error="modalError"
|
||||
>
|
||||
</taskSubmitter>
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
|
|
@ -58,44 +58,43 @@
|
|||
v-if="'flatten_lens_shading_table' in actions"
|
||||
class="uk-child-width-expand"
|
||||
>
|
||||
<taskSubmitter
|
||||
<action-button
|
||||
:can-terminate="false"
|
||||
:requires-confirmation="false"
|
||||
:submit-url="cameraUri + 'flat_lens_shading'"
|
||||
thing="camera"
|
||||
action="flat_lens_shading"
|
||||
:submit-label="'Disable flat field correction'"
|
||||
@response="onRecalibrateResponse"
|
||||
@error="modalError"
|
||||
>
|
||||
</taskSubmitter>
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-show="showExtraSettings"
|
||||
v-if="'reset_lens_shading' in actions"
|
||||
class="uk-child-width-expand"
|
||||
>
|
||||
<taskSubmitter
|
||||
<action-button
|
||||
:can-terminate="false"
|
||||
:requires-confirmation="false"
|
||||
:submit-url="cameraUri + 'reset_lens_shading'"
|
||||
thing="camera"
|
||||
action="reset_lens_shading"
|
||||
:submit-label="'Reset flat field correction'"
|
||||
@response="onRecalibrateResponse"
|
||||
@error="modalError"
|
||||
>
|
||||
</taskSubmitter>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from "axios";
|
||||
import taskSubmitter from "../../../genericComponents/taskSubmitter";
|
||||
import ActionButton from "../../../labThingsComponents/actionButton.vue";
|
||||
|
||||
// Export main app
|
||||
export default {
|
||||
name: "CameraCalibrationSettings",
|
||||
|
||||
components: {
|
||||
taskSubmitter
|
||||
ActionButton
|
||||
},
|
||||
|
||||
props: {
|
||||
|
|
@ -110,38 +109,15 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
data: function() {
|
||||
return {
|
||||
actions: {},
|
||||
isCalibrating: false,
|
||||
LstDownloadEnabled: false
|
||||
};
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.updateActions();
|
||||
computed: {
|
||||
actions() {
|
||||
return this.$store.getters["wot/thingDescription"]("camera").actions;
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
updateActions: async function() {
|
||||
try {
|
||||
let response = await axios.get(this.cameraUri); // Get the thing description
|
||||
let td = response.data;
|
||||
this.actions = td.actions;
|
||||
} catch (error) {
|
||||
this.modalError(error); // Let mixin handle error
|
||||
}
|
||||
},
|
||||
|
||||
onRecalibrateResponse: function() {
|
||||
this.modalNotify("Finished recalibration.");
|
||||
},
|
||||
|
||||
flattenLensShadingTableRequest: function() {
|
||||
axios.post(this.recalibrationLinks.flatten_lens_shading_table.href);
|
||||
},
|
||||
deleteLensShadingTableRequest: function() {
|
||||
axios.post(this.recalibrationLinks.delete_lens_shading_table.href);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -71,8 +71,9 @@
|
|||
</tabIcon>
|
||||
</li>
|
||||
</ul>
|
||||
<task-submitter
|
||||
:submit-url="saveAllThingSettingsUri"
|
||||
<action-button
|
||||
thing="settings"
|
||||
action="save_all_thing_settings"
|
||||
submit-label="Save all settings"
|
||||
class="uk-margin"
|
||||
/>
|
||||
|
|
@ -142,7 +143,7 @@ import stageSettings from "./settingsComponents/stageSettings.vue";
|
|||
// Import generic components
|
||||
import tabIcon from "../genericComponents/tabIcon";
|
||||
import tabContent from "../genericComponents/tabContent";
|
||||
import TaskSubmitter from '../genericComponents/taskSubmitter.vue';
|
||||
import ActionButton from "../labThingsComponents/actionButton.vue";
|
||||
|
||||
// Export main app
|
||||
export default {
|
||||
|
|
@ -157,7 +158,7 @@ export default {
|
|||
featuresSettings,
|
||||
tabIcon,
|
||||
tabContent,
|
||||
TaskSubmitter
|
||||
ActionButton
|
||||
},
|
||||
|
||||
data: function() {
|
||||
|
|
@ -167,12 +168,6 @@ export default {
|
|||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
saveAllThingSettingsUri() {
|
||||
return this.thingActionUrl("settings", "save_all_thing_settings");
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
setTab: function(event, tab) {
|
||||
if (!(this.currentTab == tab)) {
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@
|
|||
label="Image overlap (0-1)"
|
||||
/>
|
||||
</div>
|
||||
<div class="uk-margin">
|
||||
<div class="uk-margin">
|
||||
<propertyControl
|
||||
thing-name="smart_scan"
|
||||
property-name="stitch_tiff"
|
||||
|
|
@ -70,9 +70,10 @@
|
|||
/>
|
||||
</div>
|
||||
<div class="uk-margin">
|
||||
<taskSubmitter
|
||||
ref="smartScanTaskSubmitter"
|
||||
:submit-url="smartScanUri"
|
||||
<action-button
|
||||
ref="smartScanButton"
|
||||
thing="smart_scan"
|
||||
action="sample_scan"
|
||||
:submit-data="{ scan_name: scan_name }"
|
||||
submit-label="Start smart scan"
|
||||
:can-terminate="true"
|
||||
|
|
@ -84,9 +85,9 @@
|
|||
</div>
|
||||
</div>
|
||||
<div v-show="scanning">
|
||||
<h2 v-if="displayImageOnRight">
|
||||
Live stitching preview
|
||||
</h2>
|
||||
<h2 v-if="displayImageOnRight">
|
||||
Live stitching preview
|
||||
</h2>
|
||||
<mini-stream-display v-if="displayImageOnRight" />
|
||||
<action-log-display
|
||||
id="log-display"
|
||||
|
|
@ -98,36 +99,47 @@
|
|||
v-if="cancellable"
|
||||
type="button"
|
||||
class="uk-button uk-button-danger uk-width-1-1"
|
||||
@click="$refs.smartScanTaskSubmitter.terminateTask()"
|
||||
@click="$refs.smartScanButton.terminateTask()"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<div class="uk-margin uk-grid-small uk-child-width-expand" v-if="!cancellable" uk-grid>
|
||||
<button
|
||||
type="button"
|
||||
class="uk-button"
|
||||
@click="scanning = false; lastStitchedImage=null;"
|
||||
<div
|
||||
v-if="!cancellable"
|
||||
class="uk-margin uk-grid-small uk-child-width-expand"
|
||||
uk-grid
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
<task-submitter
|
||||
class="uk-button"
|
||||
submit-label="Download ZIP"
|
||||
:can-terminate="false"
|
||||
:submit-data="{'scan_name': lastScanName}"
|
||||
:button-primary="true"
|
||||
:submit-url="createZipOfScanUri"
|
||||
@response="downloadZipFile"
|
||||
@error="modalError"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="uk-button"
|
||||
@click="
|
||||
scanning = false;
|
||||
lastStitchedImage = null;
|
||||
"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
<action-button
|
||||
class="uk-button"
|
||||
thing="smart_scan"
|
||||
action="create_zip_of_scan"
|
||||
submit-label="Download ZIP"
|
||||
:can-terminate="false"
|
||||
:submit-data="{ scan_name: lastScanName }"
|
||||
:button-primary="true"
|
||||
@response="downloadZipFile"
|
||||
@error="modalError"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<h3 v-if="scanning">
|
||||
Scan ID: {{ lastScanName }}
|
||||
</h3>
|
||||
<h3 v-if="scanning">Scan ID: {{ lastScanName }}</h3>
|
||||
</div>
|
||||
<div class="view-image uk-width-expand uk-height-1-1">
|
||||
<img v-if="displayImageOnRight" class=image-fit :src="lastStitchedImage" id="last-stitched-image">
|
||||
<img
|
||||
v-if="displayImageOnRight"
|
||||
id="last-stitched-image"
|
||||
class="image-fit"
|
||||
:src="lastStitchedImage"
|
||||
/>
|
||||
<streamDisplay v-else />
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -135,22 +147,22 @@
|
|||
|
||||
<script>
|
||||
import streamDisplay from "./streamContent.vue";
|
||||
import taskSubmitter from "../genericComponents/taskSubmitter";
|
||||
import propertyControl from "../labThingsComponents/propertyControl.vue";
|
||||
import actionLogDisplay from "../genericComponents/actionLogDisplay.vue";
|
||||
import actionProgressBar from "../genericComponents/actionProgressBar.vue";
|
||||
import MiniStreamDisplay from '../genericComponents/miniStreamDisplay.vue';
|
||||
import actionLogDisplay from "../labThingsComponents/actionLogDisplay.vue";
|
||||
import actionProgressBar from "../labThingsComponents/actionProgressBar.vue";
|
||||
import MiniStreamDisplay from "../genericComponents/miniStreamDisplay.vue";
|
||||
import ActionButton from "../labThingsComponents/actionButton.vue";
|
||||
|
||||
export default {
|
||||
name: "SlideScanContent",
|
||||
|
||||
components: {
|
||||
streamDisplay,
|
||||
taskSubmitter,
|
||||
propertyControl,
|
||||
actionLogDisplay,
|
||||
actionProgressBar,
|
||||
MiniStreamDisplay
|
||||
MiniStreamDisplay,
|
||||
ActionButton
|
||||
},
|
||||
|
||||
data() {
|
||||
|
|
@ -168,20 +180,14 @@ export default {
|
|||
},
|
||||
|
||||
computed: {
|
||||
createZipOfScanUri() {
|
||||
return this.thingActionUrl("smart_scan", "create_zip_of_scan");
|
||||
},
|
||||
backendOK() {
|
||||
return this.thingAvailable("smart_scan");
|
||||
},
|
||||
smartScanUri() {
|
||||
return this.thingActionUrl("smart_scan", "sample_scan");
|
||||
},
|
||||
cancellable() {
|
||||
return (this.taskStatus == "running") | (this.taskStatus == "pending");
|
||||
},
|
||||
displayImageOnRight() {
|
||||
return this.scanning & this.lastStitchedImage !== null;
|
||||
return this.scanning & (this.lastStitchedImage !== null);
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -190,27 +196,32 @@ export default {
|
|||
this.scanRunning = false;
|
||||
this.modalError(error);
|
||||
},
|
||||
correlateCurrentScan() {
|
||||
|
||||
},
|
||||
correlateCurrentScan() {},
|
||||
startScanning() {
|
||||
this.lastStitchedImage = null;
|
||||
this.scanning = true;
|
||||
setTimeout(this.pollScan, 1000);
|
||||
},
|
||||
async pollScan() {
|
||||
if (this.cancellable) { // while the scan is running
|
||||
let mtime = await this.readThingProperty("smart_scan", "latest_preview_stitch_time");
|
||||
if (this.cancellable) {
|
||||
// while the scan is running
|
||||
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");
|
||||
setTimeout(this.pollScan, 1000); // keep rescheduling until it's stopped
|
||||
this.lastScanName = await this.readThingProperty(
|
||||
"smart_scan",
|
||||
"latest_scan_name"
|
||||
);
|
||||
setTimeout(this.pollScan, 1000); // keep rescheduling until it's stopped
|
||||
}
|
||||
},
|
||||
async downloadZipFile(response) {
|
||||
const scan_name = response.input.scan_name;
|
||||
const filename = `${scan_name}_images.zip`
|
||||
const filename = `${scan_name}_images.zip`;
|
||||
const url = response.output.href;
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
|
|
|
|||
|
|
@ -66,12 +66,12 @@ Vue.mixin({
|
|||
// `false` fails because axios somehow eats it!
|
||||
// Other values should not be stringified or pydantic
|
||||
// can't parse them.
|
||||
if (value===false | value===true) {
|
||||
value=JSON.stringify(value);
|
||||
if ((value === false) | (value === true)) {
|
||||
value = JSON.stringify(value);
|
||||
}
|
||||
await axios.put(url, value);
|
||||
},
|
||||
thingActionUrl(thing, action, allow_missing=false) {
|
||||
thingActionUrl(thing, action, allow_missing = false) {
|
||||
let url = this.$store.getters["wot/thingActionUrl"](
|
||||
thing,
|
||||
action,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue