Merge branch 'action-button' into 'v3'
Replace tasksubmitter with action-button See merge request openflexure/openflexure-microscope-server!176
This commit is contained in:
commit
61d94e205d
23 changed files with 229 additions and 2339 deletions
|
|
@ -429,7 +429,7 @@ html {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-fit{
|
.image-fit {
|
||||||
height: 80%;
|
height: 80%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@
|
||||||
<!-- Initialisation modals -->
|
<!-- Initialisation modals -->
|
||||||
<calibrationModal
|
<calibrationModal
|
||||||
ref="calibrationModal"
|
ref="calibrationModal"
|
||||||
:available-plugins="plugins"
|
|
||||||
@onClose="enterApp()"
|
@onClose="enterApp()"
|
||||||
></calibrationModal>
|
></calibrationModal>
|
||||||
<!-- Vertical tab bar -->
|
<!-- Vertical tab bar -->
|
||||||
|
|
@ -41,22 +40,6 @@
|
||||||
<hr v-if="item.divide" :key="'tab-divider-' + index" />
|
<hr v-if="item.divide" :key="'tab-divider-' + index" />
|
||||||
</template>
|
</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
|
<tabIcon
|
||||||
v-for="imjoyTab in imjoyTabs"
|
v-for="imjoyTab in imjoyTabs"
|
||||||
:key="imjoyTab.id"
|
:key="imjoyTab.id"
|
||||||
|
|
@ -115,23 +98,6 @@
|
||||||
<component :is="item.component"></component>
|
<component :is="item.component"></component>
|
||||||
</tabContent>
|
</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
|
<tabContent
|
||||||
v-for="imjoyTab in imjoyTabs"
|
v-for="imjoyTab in imjoyTabs"
|
||||||
:key="imjoyTab.id"
|
:key="imjoyTab.id"
|
||||||
|
|
@ -166,19 +132,13 @@ import tabContent from "./genericComponents/tabContent";
|
||||||
|
|
||||||
// Import new content components
|
// Import new content components
|
||||||
import navigateContent from "./tabContentComponents/navigateContent.vue";
|
import navigateContent from "./tabContentComponents/navigateContent.vue";
|
||||||
import captureContent from "./tabContentComponents/captureContent.vue";
|
|
||||||
import slideScanContent from "./tabContentComponents/slideScanContent.vue";
|
import slideScanContent from "./tabContentComponents/slideScanContent.vue";
|
||||||
import backgroundDetectContent from "./tabContentComponents/backgroundDetectContent.vue";
|
import backgroundDetectContent from "./tabContentComponents/backgroundDetectContent.vue";
|
||||||
import viewContent from "./tabContentComponents/viewContent.vue";
|
import viewContent from "./tabContentComponents/viewContent.vue";
|
||||||
import settingsContent from "./tabContentComponents/settingsContent.vue";
|
import settingsContent from "./tabContentComponents/settingsContent.vue";
|
||||||
import extensionContent from "./tabContentComponents/extensionContent.vue";
|
|
||||||
import aboutContent from "./tabContentComponents/aboutContent.vue";
|
import aboutContent from "./tabContentComponents/aboutContent.vue";
|
||||||
import loggingContent from "./tabContentComponents/loggingContent.vue";
|
import loggingContent from "./tabContentComponents/loggingContent.vue";
|
||||||
// ImJoy and the gallery are loaded asynchronously to allow them to be disabled if needed
|
// ImJoy is loaded asynchronously to allow it to be disabled if needed
|
||||||
const galleryContent = () =>
|
|
||||||
import(
|
|
||||||
/* webpackChunkName: "gallery" */ "./tabContentComponents/galleryContent.vue"
|
|
||||||
);
|
|
||||||
const imjoyContent = () =>
|
const imjoyContent = () =>
|
||||||
import(
|
import(
|
||||||
/* webpackChunkName: "imjoy" */ "./tabContentComponents/imjoyContent.vue"
|
/* webpackChunkName: "imjoy" */ "./tabContentComponents/imjoyContent.vue"
|
||||||
|
|
@ -197,12 +157,9 @@ export default {
|
||||||
tabIcon,
|
tabIcon,
|
||||||
tabContent,
|
tabContent,
|
||||||
navigateContent,
|
navigateContent,
|
||||||
captureContent,
|
|
||||||
slideScanContent,
|
slideScanContent,
|
||||||
viewContent,
|
viewContent,
|
||||||
settingsContent,
|
settingsContent,
|
||||||
galleryContent,
|
|
||||||
extensionContent,
|
|
||||||
calibrationModal,
|
calibrationModal,
|
||||||
aboutContent,
|
aboutContent,
|
||||||
loggingContent,
|
loggingContent,
|
||||||
|
|
@ -211,7 +168,6 @@ export default {
|
||||||
},
|
},
|
||||||
data: function() {
|
data: function() {
|
||||||
return {
|
return {
|
||||||
plugins: [],
|
|
||||||
currentTab: "view",
|
currentTab: "view",
|
||||||
bottomTabs: [
|
bottomTabs: [
|
||||||
{
|
{
|
||||||
|
|
@ -235,25 +191,11 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
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() {
|
tabOrder: function() {
|
||||||
var ind = [];
|
var ind = [];
|
||||||
for (const tab of this.topTabs) {
|
for (const tab of this.topTabs) {
|
||||||
ind.push(tab.id);
|
ind.push(tab.id);
|
||||||
}
|
}
|
||||||
for (const plugin of this.pluginsGuiList) {
|
|
||||||
ind.push(plugin.id);
|
|
||||||
}
|
|
||||||
for (const tab of this.bottomTabs) {
|
for (const tab of this.bottomTabs) {
|
||||||
ind.push(tab.id);
|
ind.push(tab.id);
|
||||||
}
|
}
|
||||||
|
|
@ -272,11 +214,6 @@ export default {
|
||||||
icon: "gamepad",
|
icon: "gamepad",
|
||||||
component: navigateContent
|
component: navigateContent
|
||||||
},
|
},
|
||||||
/*{
|
|
||||||
id: "gallery",
|
|
||||||
icon: "photo_library",
|
|
||||||
component: galleryContent
|
|
||||||
},*/
|
|
||||||
{
|
{
|
||||||
id: "slidescan",
|
id: "slidescan",
|
||||||
icon: "settings_overscan",
|
icon: "settings_overscan",
|
||||||
|
|
@ -316,16 +253,6 @@ export default {
|
||||||
...mapState({ imjoyEnabled: "imjoyEnabled" })
|
...mapState({ imjoyEnabled: "imjoyEnabled" })
|
||||||
},
|
},
|
||||||
|
|
||||||
created: function() {
|
|
||||||
if (this.$store.getters.ready) {
|
|
||||||
// Update plugins
|
|
||||||
this.updatePlugins().then(() => {
|
|
||||||
// Start initialisation modals
|
|
||||||
this.startModals();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
// A global signal listener to switch tab
|
// A global signal listener to switch tab
|
||||||
this.$root.$on("globalSwitchTab", tabID => {
|
this.$root.$on("globalSwitchTab", tabID => {
|
||||||
|
|
@ -339,22 +266,12 @@ export default {
|
||||||
this.$root.$on("globalDecrementTab", () => {
|
this.$root.$on("globalDecrementTab", () => {
|
||||||
this.incrementTabBy(-1);
|
this.incrementTabBy(-1);
|
||||||
});
|
});
|
||||||
|
if (this.$store.getters.ready) {
|
||||||
|
this.startModals();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
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) {
|
setTab: function(event, tab) {
|
||||||
if (!(this.currentTab == tab)) {
|
if (!(this.currentTab == tab)) {
|
||||||
this.currentTab = tab;
|
this.currentTab = tab;
|
||||||
|
|
@ -369,7 +286,7 @@ export default {
|
||||||
this.currentTab = newId;
|
this.currentTab = newId;
|
||||||
},
|
},
|
||||||
startModals: function() {
|
startModals: function() {
|
||||||
this.$refs["calibrationModal"].show();
|
this.$refs.calibrationModal.show();
|
||||||
},
|
},
|
||||||
enterApp: function() {
|
enterApp: function() {
|
||||||
// Stuff to do once connected and all init modals are finished
|
// Stuff to do once connected and all init modals are finished
|
||||||
|
|
|
||||||
|
|
@ -72,15 +72,19 @@
|
||||||
<script>
|
<script>
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import UIkit from "uikit";
|
import UIkit from "uikit";
|
||||||
import actionProgressBar from "./actionProgressBar.vue";
|
import ActionProgressBar from "./actionProgressBar.vue";
|
||||||
import ActionLogDisplay from "./actionLogDisplay.vue";
|
import ActionLogDisplay from "./actionLogDisplay.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "TaskSubmitter",
|
name: "ActionButton",
|
||||||
components: { actionProgressBar, ActionLogDisplay },
|
components: { ActionProgressBar, ActionLogDisplay },
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
submitUrl: {
|
action: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
thing: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
|
@ -142,6 +146,12 @@ export default {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
submitUrl() {
|
||||||
|
return this.thingActionUrl(this.thing, this.action);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
progress(newval) {
|
progress(newval) {
|
||||||
this.$emit("update:progress", newval);
|
this.$emit("update:progress", newval);
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
<template>
|
<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-if="log">
|
||||||
<div v-for="(item, index) in log" :key="`log_entry_${index}`">
|
<div v-for="(item, index) in log" :key="`log_entry_${index}`">
|
||||||
{{ item.message }}
|
{{ item.message }}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<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">
|
<div class="input-and-buttons-container">
|
||||||
<input
|
<input
|
||||||
v-model="value"
|
v-model="value"
|
||||||
|
|
@ -30,7 +31,8 @@
|
||||||
<span class="material-symbols-outlined">refresh</span>
|
<span class="material-symbols-outlined">refresh</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</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">
|
<div class="input-and-buttons-container">
|
||||||
<input
|
<input
|
||||||
v-for="i in valueLength"
|
v-for="i in valueLength"
|
||||||
|
|
@ -47,7 +49,8 @@
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</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">
|
<div class="input-and-buttons-container">
|
||||||
<input
|
<input
|
||||||
v-for="(_v, key) in value"
|
v-for="(_v, key) in value"
|
||||||
|
|
@ -64,7 +67,8 @@
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</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">
|
<div class="input-and-buttons-container">
|
||||||
<input
|
<input
|
||||||
:value="value"
|
:value="value"
|
||||||
|
|
|
||||||
|
|
@ -160,13 +160,6 @@ export default {
|
||||||
miniStreamDisplay
|
miniStreamDisplay
|
||||||
},
|
},
|
||||||
|
|
||||||
props: {
|
|
||||||
availablePlugins: {
|
|
||||||
type: Array,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
data: function() {
|
data: function() {
|
||||||
return {
|
return {
|
||||||
ready: false,
|
ready: false,
|
||||||
|
|
|
||||||
|
|
@ -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 />
|
<br />
|
||||||
{{ $store.state.origin }}
|
{{ $store.state.origin }}
|
||||||
</div>
|
</div>
|
||||||
<task-submitter
|
<action-button
|
||||||
v-if="flashLedUri"
|
thing="stage"
|
||||||
:submit-url="flashLedUri"
|
action="flash_led"
|
||||||
submit-label="Flash Illumination"
|
submit-label="Flash Illumination"
|
||||||
:can-terminate="false"
|
:can-terminate="false"
|
||||||
:submit-data="{'dt': 0.25}"
|
:submit-data="{ dt: 0.25 }"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -83,18 +83,15 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import taskSubmitter from '../../genericComponents/taskSubmitter.vue';
|
import ActionButton from "../../labThingsComponents/actionButton.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { taskSubmitter },
|
|
||||||
name: "StatusPane",
|
name: "StatusPane",
|
||||||
|
components: { ActionButton },
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
things: function() {
|
things: function() {
|
||||||
return this.$store.getters["wot/thingDescriptions"];
|
return this.$store.getters["wot/thingDescriptions"];
|
||||||
},
|
|
||||||
flashLedUri() {
|
|
||||||
return this.thingActionUrl("stage", "flash_led");
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,39 +5,41 @@
|
||||||
</div>
|
</div>
|
||||||
<div v-show="backendOK">
|
<div v-show="backendOK">
|
||||||
<ul uk-accordion="multiple: true">
|
<ul uk-accordion="multiple: true">
|
||||||
<li>
|
<li>
|
||||||
<a class="uk-accordion-title" href="#">Settings</a>
|
<a class="uk-accordion-title" href="#">Settings</a>
|
||||||
<div class="uk-accordion-content">
|
<div class="uk-accordion-content">
|
||||||
<div class="uk-margin">
|
<div class="uk-margin">
|
||||||
<propertyControl
|
<propertyControl
|
||||||
thing-name="background_detect"
|
thing-name="background_detect"
|
||||||
property-name="tolerance"
|
property-name="tolerance"
|
||||||
label="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>
|
|
||||||
</div>
|
</div>
|
||||||
</li>
|
<div class="uk-margin">
|
||||||
</ul>
|
<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">
|
<div class="uk-margin">
|
||||||
<taskSubmitter
|
<action-button
|
||||||
:submit-url="setBackgroundUri"
|
thing="background_detect"
|
||||||
|
action="set_background"
|
||||||
submit-label="Set background"
|
submit-label="Set background"
|
||||||
:can-terminate="false"
|
:can-terminate="false"
|
||||||
:poll-interval="0.1"
|
:poll-interval="0.1"
|
||||||
|
|
@ -45,8 +47,9 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="uk-margin">
|
<div class="uk-margin">
|
||||||
<taskSubmitter
|
<action-button
|
||||||
:submit-url="labelImageUri"
|
thing="background_detect"
|
||||||
|
action="image_is_sample"
|
||||||
submit-label="Check current image"
|
submit-label="Check current image"
|
||||||
:can-terminate="false"
|
:can-terminate="false"
|
||||||
:poll-interval="0.1"
|
:poll-interval="0.1"
|
||||||
|
|
@ -59,29 +62,18 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import taskSubmitter from "../../genericComponents/taskSubmitter";
|
import ActionButton from "../../labThingsComponents/actionButton.vue";
|
||||||
import propertyControl from "../../labThingsComponents/propertyControl.vue";
|
import propertyControl from "../../labThingsComponents/propertyControl.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
taskSubmitter,
|
ActionButton,
|
||||||
propertyControl
|
propertyControl
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
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() {
|
backendOK() {
|
||||||
return (
|
return this.thingAvailable("background_detect");
|
||||||
(this.backgroundFractionUri != null) & (this.setBackgroundUri != null)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 uk-grid class="uk-height-1-1 uk-margin-remove uk-padding-remove">
|
||||||
<div class="control-component">
|
<div class="control-component">
|
||||||
<vue-friendly-iframe v-if="frame" :src="frame.href"></vue-friendly-iframe>
|
<vue-friendly-iframe v-if="frame" :src="frame.href"></vue-friendly-iframe>
|
||||||
<!-- Handle OpenFlexure Forms -->
|
<!-- OpenFlexure Forms used to be handled here - should now be done another way. -->
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="view-component uk-width-expand">
|
<div class="view-component uk-width-expand">
|
||||||
<galleryContent v-if="viewPanel == 'gallery'" />
|
<galleryContent v-if="viewPanel == 'gallery'" />
|
||||||
|
|
@ -30,7 +14,6 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import JsonForm from "../pluginComponents/JsonForm";
|
|
||||||
import streamDisplay from "./streamContent.vue";
|
import streamDisplay from "./streamContent.vue";
|
||||||
import galleryContent from "../tabContentComponents/galleryContent.vue";
|
import galleryContent from "../tabContentComponents/galleryContent.vue";
|
||||||
import settingsContent from "../tabContentComponents/settingsContent.vue";
|
import settingsContent from "../tabContentComponents/settingsContent.vue";
|
||||||
|
|
@ -39,7 +22,6 @@ export default {
|
||||||
name: "ExtensionContent",
|
name: "ExtensionContent",
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
JsonForm,
|
|
||||||
streamDisplay,
|
streamDisplay,
|
||||||
galleryContent,
|
galleryContent,
|
||||||
settingsContent
|
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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<taskSubmitter
|
<action-button
|
||||||
:submit-url="zeroActionUri"
|
thing="stage"
|
||||||
:submit-label="'Zero Coordinates'"
|
action="set_zero_position"
|
||||||
|
submit-label="Zero Coordinates"
|
||||||
:can-terminate="false"
|
:can-terminate="false"
|
||||||
@finished="updatePosition"
|
@finished="updatePosition"
|
||||||
@error="modalError"
|
@error="modalError"
|
||||||
></taskSubmitter>
|
/>
|
||||||
<taskSubmitter
|
<action-button
|
||||||
:submit-url="recentreActionUri"
|
thing="auto_recentre_stage"
|
||||||
:submit-label="'Re-centre Stage'"
|
action="recentre"
|
||||||
|
submit-label="Re-centre Stage"
|
||||||
:can-terminate="true"
|
:can-terminate="true"
|
||||||
:requires-confirmation="true"
|
:requires-confirmation="true"
|
||||||
:modal-progress="true"
|
:modal-progress="true"
|
||||||
|
|
@ -86,7 +88,7 @@
|
||||||
"
|
"
|
||||||
@finished="updatePosition"
|
@finished="updatePosition"
|
||||||
@error="modalError"
|
@error="modalError"
|
||||||
></taskSubmitter>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
|
@ -109,9 +111,10 @@
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<p>
|
<p>
|
||||||
<taskSubmitter
|
<action-button
|
||||||
ref="moveTaskSubmitter"
|
ref="moveButton"
|
||||||
:submit-url="absoluteMoveUri"
|
thing="stage"
|
||||||
|
action="move_absolute"
|
||||||
:submit-data="setPosition"
|
:submit-data="setPosition"
|
||||||
:submit-label="'Move'"
|
:submit-label="'Move'"
|
||||||
:can-terminate="true"
|
:can-terminate="true"
|
||||||
|
|
@ -122,21 +125,21 @@
|
||||||
moveLock = false;
|
moveLock = false;
|
||||||
"
|
"
|
||||||
@error="modalError"
|
@error="modalError"
|
||||||
></taskSubmitter>
|
/>
|
||||||
</p>
|
</p>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<!--Show autofocus if default plugin is enabled-->
|
<!--Show autofocus if default plugin is enabled-->
|
||||||
<li v-show="fastAutofocusUri || normalAutofocusUri" class="uk-open">
|
<li class="uk-open">
|
||||||
<a class="uk-accordion-title" href="#">Autofocus</a>
|
<a class="uk-accordion-title" href="#">Autofocus</a>
|
||||||
<div class="uk-accordion-content">
|
<div class="uk-accordion-content">
|
||||||
<div class="uk-grid-small uk-child-width-expand" uk-grid>
|
<div class="uk-grid-small uk-child-width-expand" uk-grid>
|
||||||
<div v-show="!isAutofocusing || isAutofocusing == 1">
|
<div v-show="!isAutofocusing || isAutofocusing == 1">
|
||||||
<taskSubmitter
|
<action-button
|
||||||
v-if="fastAutofocusUri"
|
thing="autofocus"
|
||||||
:submit-url="fastAutofocusUri"
|
action="fast_autofocus"
|
||||||
:submit-data="{ dz: 2000 }"
|
:submit-data="{ dz: 2000 }"
|
||||||
:submit-label="'Autofocus'"
|
:submit-label="'Autofocus'"
|
||||||
:button-primary="false"
|
:button-primary="false"
|
||||||
|
|
@ -147,36 +150,36 @@
|
||||||
isAutofocusing = 0;
|
isAutofocusing = 0;
|
||||||
"
|
"
|
||||||
@error="modalError"
|
@error="modalError"
|
||||||
></taskSubmitter>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li v-show="captureUri" class="uk-open">
|
<li class="uk-open">
|
||||||
<a class="uk-accordion-title" href="#">Image Capture</a>
|
<a class="uk-accordion-title" href="#">Image Capture</a>
|
||||||
<div class="uk-accordion-content">
|
<div class="uk-accordion-content">
|
||||||
<div class="uk-margin">
|
<div class="uk-margin">
|
||||||
<taskSubmitter
|
<action-button
|
||||||
v-if="captureUri"
|
thing="camera"
|
||||||
:submit-url="captureUri"
|
action="capture_jpeg"
|
||||||
:submit-data="{ resolution: 'main' }"
|
:submit-data="{ resolution: 'main' }"
|
||||||
:submit-label="'Low Resolution'"
|
:submit-label="'Low Resolution'"
|
||||||
:submit-on-event="'globalCaptureEvent'"
|
:submit-on-event="'globalCaptureEvent'"
|
||||||
@response="handleCaptureResponse"
|
@response="handleCaptureResponse"
|
||||||
@error="modalError"
|
@error="modalError"
|
||||||
></taskSubmitter>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="uk-margin">
|
<div class="uk-margin">
|
||||||
<taskSubmitter
|
<action-button
|
||||||
v-if="captureUri"
|
thing="camera"
|
||||||
:submit-url="captureUri"
|
action="capture_jpeg"
|
||||||
:submit-data="{ resolution: 'full' }"
|
:submit-data="{ resolution: 'full' }"
|
||||||
submit-label="Full Resolution"
|
submit-label="Full Resolution"
|
||||||
submit-on-event="globalCaptureEvent"
|
:submit-on-event="'globalCaptureEvent'"
|
||||||
@response="handleCaptureResponse"
|
@response="handleCaptureResponse"
|
||||||
@error="modalError"
|
@error="modalError"
|
||||||
></taskSubmitter>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
|
@ -194,14 +197,14 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import taskSubmitter from "../../genericComponents/taskSubmitter";
|
import ActionButton from "../../labThingsComponents/actionButton.vue";
|
||||||
|
|
||||||
// Export main app
|
// Export main app
|
||||||
export default {
|
export default {
|
||||||
name: "PaneNavigate",
|
name: "PaneNavigate",
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
taskSubmitter
|
ActionButton
|
||||||
},
|
},
|
||||||
|
|
||||||
data: function() {
|
data: function() {
|
||||||
|
|
@ -228,24 +231,9 @@ export default {
|
||||||
baseUri: function() {
|
baseUri: function() {
|
||||||
return this.$store.getters.baseUri;
|
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() {
|
positionStatusUri: function() {
|
||||||
return `${this.baseUri}/stage/position`;
|
return `${this.baseUri}/stage/position`;
|
||||||
},
|
},
|
||||||
fastAutofocusUri: function() {
|
|
||||||
return this.thingActionUrl("autofocus", "fast_autofocus");
|
|
||||||
},
|
|
||||||
captureUri: function() {
|
|
||||||
return this.thingActionUrl("camera", "capture_jpeg");
|
|
||||||
},
|
|
||||||
moveInImageCoordinatesUri: function() {
|
moveInImageCoordinatesUri: function() {
|
||||||
return this.thingActionUrl(
|
return this.thingActionUrl(
|
||||||
"camera_stage_mapping",
|
"camera_stage_mapping",
|
||||||
|
|
@ -327,7 +315,7 @@ export default {
|
||||||
await this.startMoveTask();
|
await this.startMoveTask();
|
||||||
},
|
},
|
||||||
async startMoveTask() {
|
async startMoveTask() {
|
||||||
await this.$refs.moveTaskSubmitter.startTask();
|
await this.$refs.moveButton.startTask();
|
||||||
},
|
},
|
||||||
moveInImageCoordinatesRequest: function(x, y) {
|
moveInImageCoordinatesRequest: function(x, y) {
|
||||||
// If not movement-locked
|
// 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() {
|
async updatePosition() {
|
||||||
this.setPosition = await this.readThingProperty("stage", "position");
|
this.setPosition = await this.readThingProperty("stage", "position");
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
v-observe-visibility="visibilityChanged"
|
v-observe-visibility="visibilityChanged"
|
||||||
class="galleryDisplay uk-padding uk-padding-remove-top"
|
class="galleryDisplay uk-padding uk-padding-remove-top"
|
||||||
>
|
>
|
||||||
<!-- Gallery nav bar -->
|
<!-- Gallery nav bar -->
|
||||||
<nav
|
<nav
|
||||||
class="gallery-navbar uk-navbar-container uk-navbar-transparent"
|
class="gallery-navbar uk-navbar-container uk-navbar-transparent"
|
||||||
|
|
@ -45,24 +45,26 @@
|
||||||
<div class="uk-card">
|
<div class="uk-card">
|
||||||
<div class="uk-card-body">
|
<div class="uk-card-body">
|
||||||
<h3 class="uk-card-title">{{ item.name }}</h3>
|
<h3 class="uk-card-title">{{ item.name }}</h3>
|
||||||
<task-submitter
|
<action-button
|
||||||
|
thing="smart_scan"
|
||||||
|
action="create_zip_of_scan"
|
||||||
submit-label="Download ZIP"
|
submit-label="Download ZIP"
|
||||||
:can-terminate="false"
|
:can-terminate="false"
|
||||||
:submit-data="{'scan_name': item.name}"
|
:submit-data="{ scan_name: item.name }"
|
||||||
:button-primary="true"
|
:button-primary="true"
|
||||||
:submit-url="createZipOfScanUri"
|
|
||||||
@response="downloadZipFile"
|
@response="downloadZipFile"
|
||||||
@error="modalError"
|
@error="modalError"
|
||||||
/>
|
/>
|
||||||
<button class="uk-button" @click="deleteScan(item.name)">
|
<button class="uk-button" @click="deleteScan(item.name)">
|
||||||
Delete
|
Delete
|
||||||
</button>
|
</button>
|
||||||
<task-submitter
|
<action-button
|
||||||
submit-label="Stitch Images"
|
submit-label="Stitch Images"
|
||||||
|
thing="smart_scan"
|
||||||
|
action="stitch_scan"
|
||||||
:can-terminate="false"
|
:can-terminate="false"
|
||||||
:submit-data="{'scan_name': item.name}"
|
:submit-data="{ scan_name: item.name }"
|
||||||
:button-primary="false"
|
:button-primary="false"
|
||||||
:submit-url="stitchUri"
|
|
||||||
:modal-progress="true"
|
:modal-progress="true"
|
||||||
@error="modalError"
|
@error="modalError"
|
||||||
/>
|
/>
|
||||||
|
|
@ -87,12 +89,12 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import taskSubmitter from '../genericComponents/taskSubmitter.vue';
|
import actionButton from "../labThingsComponents/actionButton.vue";
|
||||||
|
|
||||||
// Export main app
|
// Export main app
|
||||||
export default {
|
export default {
|
||||||
components: { taskSubmitter },
|
|
||||||
name: "ScanListContent",
|
name: "ScanListContent",
|
||||||
|
components: { actionButton },
|
||||||
|
|
||||||
data: function() {
|
data: function() {
|
||||||
return {
|
return {
|
||||||
|
|
@ -108,12 +110,6 @@ export default {
|
||||||
"readproperty",
|
"readproperty",
|
||||||
true
|
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() {
|
async updateScans() {
|
||||||
let scans = await this.readThingProperty("smart_scan", "scans");
|
let scans = await this.readThingProperty("smart_scan", "scans");
|
||||||
if (!scans | scans.length == 0) {
|
if (!scans | (scans.length == 0)) {
|
||||||
this.scans = scans;
|
this.scans = scans;
|
||||||
}
|
}
|
||||||
scans.forEach(scan => {
|
scans.forEach(scan => {
|
||||||
|
|
@ -216,7 +212,7 @@ export default {
|
||||||
},
|
},
|
||||||
async downloadZipFile(response) {
|
async downloadZipFile(response) {
|
||||||
const scan_name = response.input.scan_name;
|
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 url = response.output.href;
|
||||||
const link = document.createElement("a");
|
const link = document.createElement("a");
|
||||||
link.href = url;
|
link.href = url;
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,15 @@
|
||||||
<div id="CSMCalibrationSettings">
|
<div id="CSMCalibrationSettings">
|
||||||
<!--Show auto calibrate if default plugin is enabled-->
|
<!--Show auto calibrate if default plugin is enabled-->
|
||||||
<div v-if="'calibrate_xy' in actions" class="uk-margin-small">
|
<div v-if="'calibrate_xy' in actions" class="uk-margin-small">
|
||||||
<taskSubmitter
|
<action-button
|
||||||
:button-primary="true"
|
:button-primary="true"
|
||||||
:can-terminate="true"
|
:can-terminate="true"
|
||||||
:requires-confirmation="true"
|
:requires-confirmation="true"
|
||||||
:confirmation-message="
|
:confirmation-message="
|
||||||
'Start recalibration of the stage to the camera? This may take a while, and the microscope will be locked during this time.'
|
'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_mapping"
|
||||||
|
action="calibrate_xy"
|
||||||
:submit-label="'Auto-Calibrate using camera'"
|
:submit-label="'Auto-Calibrate using camera'"
|
||||||
:modal-progress="true"
|
:modal-progress="true"
|
||||||
@response="onRecalibrateResponse"
|
@response="onRecalibrateResponse"
|
||||||
|
|
@ -29,14 +30,14 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import taskSubmitter from "../../../genericComponents/taskSubmitter";
|
import ActionButton from "../../../labThingsComponents/actionButton.vue";
|
||||||
|
|
||||||
// Export main app
|
// Export main app
|
||||||
export default {
|
export default {
|
||||||
name: "CSMCalibrationSettings",
|
name: "CSMCalibrationSettings",
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
taskSubmitter
|
ActionButton
|
||||||
},
|
},
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
|
|
@ -55,9 +56,6 @@ export default {
|
||||||
properties() {
|
properties() {
|
||||||
return this.$store.getters["wot/thingDescription"]("camera_stage_mapping")
|
return this.$store.getters["wot/thingDescription"]("camera_stage_mapping")
|
||||||
.properties;
|
.properties;
|
||||||
},
|
|
||||||
calibrateXYUri() {
|
|
||||||
return this.thingActionUrl("camera_stage_mapping", "calibrate_xy");
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,55 +2,55 @@
|
||||||
<div>
|
<div>
|
||||||
<!--Show auto calibrate if default plugin is enabled-->
|
<!--Show auto calibrate if default plugin is enabled-->
|
||||||
<div v-if="'full_auto_calibrate' in actions" class="uk-margin-small">
|
<div v-if="'full_auto_calibrate' in actions" class="uk-margin-small">
|
||||||
<taskSubmitter
|
<action-button
|
||||||
:can-terminate="false"
|
:can-terminate="false"
|
||||||
:requires-confirmation="true"
|
:requires-confirmation="true"
|
||||||
:confirmation-message="
|
:confirmation-message="
|
||||||
'Start recalibration? This may take a while, and the microscope will be locked during this time.'
|
'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'"
|
:submit-label="'Full Auto-Calibrate'"
|
||||||
@response="onRecalibrateResponse"
|
@response="onRecalibrateResponse"
|
||||||
@error="modalError"
|
@error="modalError"
|
||||||
>
|
/>
|
||||||
</taskSubmitter>
|
|
||||||
</div>
|
</div>
|
||||||
<div v-if="'auto_expose_from_minimum' in actions" class="uk-margin-small">
|
<div v-if="'auto_expose_from_minimum' in actions" class="uk-margin-small">
|
||||||
<taskSubmitter
|
<action-button
|
||||||
:can-terminate="false"
|
:can-terminate="false"
|
||||||
:requires-confirmation="false"
|
:requires-confirmation="false"
|
||||||
:submit-url="cameraUri + 'auto_expose_from_minimum'"
|
thing="camera"
|
||||||
|
action="auto_expose_from_minimum"
|
||||||
:submit-label="'Auto gain & shutter speed'"
|
:submit-label="'Auto gain & shutter speed'"
|
||||||
@response="onRecalibrateResponse"
|
@response="onRecalibrateResponse"
|
||||||
@error="modalError"
|
@error="modalError"
|
||||||
>
|
/>
|
||||||
</taskSubmitter>
|
|
||||||
</div>
|
</div>
|
||||||
<div v-if="'calibrate_white_balance' in actions" class="uk-margin-small">
|
<div v-if="'calibrate_white_balance' in actions" class="uk-margin-small">
|
||||||
<taskSubmitter
|
<action-button
|
||||||
:can-terminate="false"
|
:can-terminate="false"
|
||||||
:requires-confirmation="false"
|
:requires-confirmation="false"
|
||||||
:submit-url="cameraUri + 'calibrate_white_balance'"
|
thing="camera"
|
||||||
|
action="calibrate_white_balance"
|
||||||
:submit-label="'Auto white balance'"
|
:submit-label="'Auto white balance'"
|
||||||
@response="onRecalibrateResponse"
|
@response="onRecalibrateResponse"
|
||||||
@error="modalError"
|
@error="modalError"
|
||||||
>
|
/>
|
||||||
</taskSubmitter>
|
|
||||||
</div>
|
</div>
|
||||||
<div v-if="'calibrate_lens_shading' in actions" class="uk-margin-small">
|
<div v-if="'calibrate_lens_shading' in actions" class="uk-margin-small">
|
||||||
<taskSubmitter
|
<action-button
|
||||||
:can-terminate="false"
|
:can-terminate="false"
|
||||||
:requires-confirmation="true"
|
:requires-confirmation="true"
|
||||||
:confirmation-message="
|
:confirmation-message="
|
||||||
'Is the microscope looking at an evenly illuminated, empty field of view? ' +
|
'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.'
|
'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'"
|
:submit-label="'Auto flat field correction'"
|
||||||
@response="onRecalibrateResponse"
|
@response="onRecalibrateResponse"
|
||||||
@error="modalError"
|
@error="modalError"
|
||||||
>
|
/>
|
||||||
</taskSubmitter>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
|
@ -58,44 +58,43 @@
|
||||||
v-if="'flatten_lens_shading_table' in actions"
|
v-if="'flatten_lens_shading_table' in actions"
|
||||||
class="uk-child-width-expand"
|
class="uk-child-width-expand"
|
||||||
>
|
>
|
||||||
<taskSubmitter
|
<action-button
|
||||||
:can-terminate="false"
|
:can-terminate="false"
|
||||||
:requires-confirmation="false"
|
:requires-confirmation="false"
|
||||||
:submit-url="cameraUri + 'flat_lens_shading'"
|
thing="camera"
|
||||||
|
action="flat_lens_shading"
|
||||||
:submit-label="'Disable flat field correction'"
|
:submit-label="'Disable flat field correction'"
|
||||||
@response="onRecalibrateResponse"
|
@response="onRecalibrateResponse"
|
||||||
@error="modalError"
|
@error="modalError"
|
||||||
>
|
/>
|
||||||
</taskSubmitter>
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-show="showExtraSettings"
|
v-show="showExtraSettings"
|
||||||
v-if="'reset_lens_shading' in actions"
|
v-if="'reset_lens_shading' in actions"
|
||||||
class="uk-child-width-expand"
|
class="uk-child-width-expand"
|
||||||
>
|
>
|
||||||
<taskSubmitter
|
<action-button
|
||||||
:can-terminate="false"
|
:can-terminate="false"
|
||||||
:requires-confirmation="false"
|
:requires-confirmation="false"
|
||||||
:submit-url="cameraUri + 'reset_lens_shading'"
|
thing="camera"
|
||||||
|
action="reset_lens_shading"
|
||||||
:submit-label="'Reset flat field correction'"
|
:submit-label="'Reset flat field correction'"
|
||||||
@response="onRecalibrateResponse"
|
@response="onRecalibrateResponse"
|
||||||
@error="modalError"
|
@error="modalError"
|
||||||
>
|
/>
|
||||||
</taskSubmitter>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import axios from "axios";
|
import ActionButton from "../../../labThingsComponents/actionButton.vue";
|
||||||
import taskSubmitter from "../../../genericComponents/taskSubmitter";
|
|
||||||
|
|
||||||
// Export main app
|
// Export main app
|
||||||
export default {
|
export default {
|
||||||
name: "CameraCalibrationSettings",
|
name: "CameraCalibrationSettings",
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
taskSubmitter
|
ActionButton
|
||||||
},
|
},
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
|
|
@ -110,38 +109,15 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
data: function() {
|
computed: {
|
||||||
return {
|
actions() {
|
||||||
actions: {},
|
return this.$store.getters["wot/thingDescription"]("camera").actions;
|
||||||
isCalibrating: false,
|
}
|
||||||
LstDownloadEnabled: false
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
mounted() {
|
|
||||||
this.updateActions();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
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() {
|
onRecalibrateResponse: function() {
|
||||||
this.modalNotify("Finished recalibration.");
|
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>
|
</tabIcon>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<task-submitter
|
<action-button
|
||||||
:submit-url="saveAllThingSettingsUri"
|
thing="settings"
|
||||||
|
action="save_all_thing_settings"
|
||||||
submit-label="Save all settings"
|
submit-label="Save all settings"
|
||||||
class="uk-margin"
|
class="uk-margin"
|
||||||
/>
|
/>
|
||||||
|
|
@ -142,7 +143,7 @@ import stageSettings from "./settingsComponents/stageSettings.vue";
|
||||||
// Import generic components
|
// Import generic components
|
||||||
import tabIcon from "../genericComponents/tabIcon";
|
import tabIcon from "../genericComponents/tabIcon";
|
||||||
import tabContent from "../genericComponents/tabContent";
|
import tabContent from "../genericComponents/tabContent";
|
||||||
import TaskSubmitter from '../genericComponents/taskSubmitter.vue';
|
import ActionButton from "../labThingsComponents/actionButton.vue";
|
||||||
|
|
||||||
// Export main app
|
// Export main app
|
||||||
export default {
|
export default {
|
||||||
|
|
@ -157,7 +158,7 @@ export default {
|
||||||
featuresSettings,
|
featuresSettings,
|
||||||
tabIcon,
|
tabIcon,
|
||||||
tabContent,
|
tabContent,
|
||||||
TaskSubmitter
|
ActionButton
|
||||||
},
|
},
|
||||||
|
|
||||||
data: function() {
|
data: function() {
|
||||||
|
|
@ -167,12 +168,6 @@ export default {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
|
||||||
saveAllThingSettingsUri() {
|
|
||||||
return this.thingActionUrl("settings", "save_all_thing_settings");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
setTab: function(event, tab) {
|
setTab: function(event, tab) {
|
||||||
if (!(this.currentTab == tab)) {
|
if (!(this.currentTab == tab)) {
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@
|
||||||
label="Image overlap (0-1)"
|
label="Image overlap (0-1)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="uk-margin">
|
<div class="uk-margin">
|
||||||
<propertyControl
|
<propertyControl
|
||||||
thing-name="smart_scan"
|
thing-name="smart_scan"
|
||||||
property-name="stitch_tiff"
|
property-name="stitch_tiff"
|
||||||
|
|
@ -70,9 +70,10 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="uk-margin">
|
<div class="uk-margin">
|
||||||
<taskSubmitter
|
<action-button
|
||||||
ref="smartScanTaskSubmitter"
|
ref="smartScanButton"
|
||||||
:submit-url="smartScanUri"
|
thing="smart_scan"
|
||||||
|
action="sample_scan"
|
||||||
:submit-data="{ scan_name: scan_name }"
|
:submit-data="{ scan_name: scan_name }"
|
||||||
submit-label="Start smart scan"
|
submit-label="Start smart scan"
|
||||||
:can-terminate="true"
|
:can-terminate="true"
|
||||||
|
|
@ -84,9 +85,9 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-show="scanning">
|
<div v-show="scanning">
|
||||||
<h2 v-if="displayImageOnRight">
|
<h2 v-if="displayImageOnRight">
|
||||||
Live stitching preview
|
Live stitching preview
|
||||||
</h2>
|
</h2>
|
||||||
<mini-stream-display v-if="displayImageOnRight" />
|
<mini-stream-display v-if="displayImageOnRight" />
|
||||||
<action-log-display
|
<action-log-display
|
||||||
id="log-display"
|
id="log-display"
|
||||||
|
|
@ -98,36 +99,47 @@
|
||||||
v-if="cancellable"
|
v-if="cancellable"
|
||||||
type="button"
|
type="button"
|
||||||
class="uk-button uk-button-danger uk-width-1-1"
|
class="uk-button uk-button-danger uk-width-1-1"
|
||||||
@click="$refs.smartScanTaskSubmitter.terminateTask()"
|
@click="$refs.smartScanButton.terminateTask()"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
<div class="uk-margin uk-grid-small uk-child-width-expand" v-if="!cancellable" uk-grid>
|
<div
|
||||||
<button
|
v-if="!cancellable"
|
||||||
type="button"
|
class="uk-margin uk-grid-small uk-child-width-expand"
|
||||||
class="uk-button"
|
uk-grid
|
||||||
@click="scanning = false; lastStitchedImage=null;"
|
|
||||||
>
|
>
|
||||||
Close
|
<button
|
||||||
</button>
|
type="button"
|
||||||
<task-submitter
|
class="uk-button"
|
||||||
class="uk-button"
|
@click="
|
||||||
submit-label="Download ZIP"
|
scanning = false;
|
||||||
:can-terminate="false"
|
lastStitchedImage = null;
|
||||||
:submit-data="{'scan_name': lastScanName}"
|
"
|
||||||
:button-primary="true"
|
>
|
||||||
:submit-url="createZipOfScanUri"
|
Close
|
||||||
@response="downloadZipFile"
|
</button>
|
||||||
@error="modalError"
|
<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>
|
||||||
</div>
|
</div>
|
||||||
<h3 v-if="scanning">
|
<h3 v-if="scanning">Scan ID: {{ lastScanName }}</h3>
|
||||||
Scan ID: {{ lastScanName }}
|
|
||||||
</h3>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="view-image uk-width-expand uk-height-1-1">
|
<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 />
|
<streamDisplay v-else />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -135,22 +147,22 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import streamDisplay from "./streamContent.vue";
|
import streamDisplay from "./streamContent.vue";
|
||||||
import taskSubmitter from "../genericComponents/taskSubmitter";
|
|
||||||
import propertyControl from "../labThingsComponents/propertyControl.vue";
|
import propertyControl from "../labThingsComponents/propertyControl.vue";
|
||||||
import actionLogDisplay from "../genericComponents/actionLogDisplay.vue";
|
import actionLogDisplay from "../labThingsComponents/actionLogDisplay.vue";
|
||||||
import actionProgressBar from "../genericComponents/actionProgressBar.vue";
|
import actionProgressBar from "../labThingsComponents/actionProgressBar.vue";
|
||||||
import MiniStreamDisplay from '../genericComponents/miniStreamDisplay.vue';
|
import MiniStreamDisplay from "../genericComponents/miniStreamDisplay.vue";
|
||||||
|
import ActionButton from "../labThingsComponents/actionButton.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "SlideScanContent",
|
name: "SlideScanContent",
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
streamDisplay,
|
streamDisplay,
|
||||||
taskSubmitter,
|
|
||||||
propertyControl,
|
propertyControl,
|
||||||
actionLogDisplay,
|
actionLogDisplay,
|
||||||
actionProgressBar,
|
actionProgressBar,
|
||||||
MiniStreamDisplay
|
MiniStreamDisplay,
|
||||||
|
ActionButton
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
|
|
@ -168,20 +180,14 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
createZipOfScanUri() {
|
|
||||||
return this.thingActionUrl("smart_scan", "create_zip_of_scan");
|
|
||||||
},
|
|
||||||
backendOK() {
|
backendOK() {
|
||||||
return this.thingAvailable("smart_scan");
|
return this.thingAvailable("smart_scan");
|
||||||
},
|
},
|
||||||
smartScanUri() {
|
|
||||||
return this.thingActionUrl("smart_scan", "sample_scan");
|
|
||||||
},
|
|
||||||
cancellable() {
|
cancellable() {
|
||||||
return (this.taskStatus == "running") | (this.taskStatus == "pending");
|
return (this.taskStatus == "running") | (this.taskStatus == "pending");
|
||||||
},
|
},
|
||||||
displayImageOnRight() {
|
displayImageOnRight() {
|
||||||
return this.scanning & this.lastStitchedImage !== null;
|
return this.scanning & (this.lastStitchedImage !== null);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -190,27 +196,32 @@ export default {
|
||||||
this.scanRunning = false;
|
this.scanRunning = false;
|
||||||
this.modalError(error);
|
this.modalError(error);
|
||||||
},
|
},
|
||||||
correlateCurrentScan() {
|
correlateCurrentScan() {},
|
||||||
|
|
||||||
},
|
|
||||||
startScanning() {
|
startScanning() {
|
||||||
this.lastStitchedImage = null;
|
this.lastStitchedImage = null;
|
||||||
this.scanning = true;
|
this.scanning = true;
|
||||||
setTimeout(this.pollScan, 1000);
|
setTimeout(this.pollScan, 1000);
|
||||||
},
|
},
|
||||||
async pollScan() {
|
async pollScan() {
|
||||||
if (this.cancellable) { // while the scan is running
|
if (this.cancellable) {
|
||||||
let mtime = await this.readThingProperty("smart_scan", "latest_preview_stitch_time");
|
// while the scan is running
|
||||||
|
let mtime = await this.readThingProperty(
|
||||||
|
"smart_scan",
|
||||||
|
"latest_preview_stitch_time"
|
||||||
|
);
|
||||||
if (mtime !== null) {
|
if (mtime !== null) {
|
||||||
this.lastStitchedImage = `${this.$store.getters.baseUri}/smart_scan/latest_preview_stitch.jpg?t=${mtime}`;
|
this.lastStitchedImage = `${this.$store.getters.baseUri}/smart_scan/latest_preview_stitch.jpg?t=${mtime}`;
|
||||||
}
|
}
|
||||||
this.lastScanName = await this.readThingProperty("smart_scan", "latest_scan_name");
|
this.lastScanName = await this.readThingProperty(
|
||||||
setTimeout(this.pollScan, 1000); // keep rescheduling until it's stopped
|
"smart_scan",
|
||||||
|
"latest_scan_name"
|
||||||
|
);
|
||||||
|
setTimeout(this.pollScan, 1000); // keep rescheduling until it's stopped
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async downloadZipFile(response) {
|
async downloadZipFile(response) {
|
||||||
const scan_name = response.input.scan_name;
|
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 url = response.output.href;
|
||||||
const link = document.createElement("a");
|
const link = document.createElement("a");
|
||||||
link.href = url;
|
link.href = url;
|
||||||
|
|
|
||||||
|
|
@ -66,12 +66,12 @@ Vue.mixin({
|
||||||
// `false` fails because axios somehow eats it!
|
// `false` fails because axios somehow eats it!
|
||||||
// Other values should not be stringified or pydantic
|
// Other values should not be stringified or pydantic
|
||||||
// can't parse them.
|
// can't parse them.
|
||||||
if (value===false | value===true) {
|
if ((value === false) | (value === true)) {
|
||||||
value=JSON.stringify(value);
|
value = JSON.stringify(value);
|
||||||
}
|
}
|
||||||
await axios.put(url, value);
|
await axios.put(url, value);
|
||||||
},
|
},
|
||||||
thingActionUrl(thing, action, allow_missing=false) {
|
thingActionUrl(thing, action, allow_missing = false) {
|
||||||
let url = this.$store.getters["wot/thingActionUrl"](
|
let url = this.$store.getters["wot/thingActionUrl"](
|
||||||
thing,
|
thing,
|
||||||
action,
|
action,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue