Rearranged main components
This commit is contained in:
parent
250fd07e9a
commit
eff03bfa2c
34 changed files with 734 additions and 938 deletions
196
openflexure_microscope/api/static/package-lock.json
generated
196
openflexure_microscope/api/static/package-lock.json
generated
|
|
@ -2828,6 +2828,16 @@
|
|||
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
|
||||
"dev": true
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"color-convert": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"browserslist": {
|
||||
"version": "4.14.3",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.14.3.tgz",
|
||||
|
|
@ -2872,12 +2882,36 @@
|
|||
"integrity": "sha512-ziNcheTGTHlu9g34EVoHQdIu5g4foc8EsxMGC7Xkokmvw0dqNtX8BS8RgCgFBaAiSp2IdjvBxNdh0ssib28eVQ==",
|
||||
"dev": true
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"color-name": "~1.1.4"
|
||||
}
|
||||
},
|
||||
"color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"electron-to-chromium": {
|
||||
"version": "1.3.570",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.570.tgz",
|
||||
"integrity": "sha512-Y6OCoVQgFQBP5py6A/06+yWxUZHDlNr/gNDGatjH8AZqXl8X0tE4LfjLJsXGz/JmWJz8a6K7bR1k+QzZ+k//fg==",
|
||||
"dev": true
|
||||
},
|
||||
"emojis-list": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
|
||||
"integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
|
|
@ -2911,6 +2945,35 @@
|
|||
"integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==",
|
||||
"dev": true
|
||||
},
|
||||
"has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"json5": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz",
|
||||
"integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"minimist": "^1.2.5"
|
||||
}
|
||||
},
|
||||
"loader-utils": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
|
||||
"integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"big.js": "^5.2.2",
|
||||
"emojis-list": "^3.0.0",
|
||||
"json5": "^2.1.2"
|
||||
}
|
||||
},
|
||||
"locate-path": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
|
||||
|
|
@ -3053,6 +3116,16 @@
|
|||
"ansi-regex": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"has-flag": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"terser-webpack-plugin": {
|
||||
"version": "2.3.8",
|
||||
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-2.3.8.tgz",
|
||||
|
|
@ -3079,6 +3152,31 @@
|
|||
"psl": "^1.1.28",
|
||||
"punycode": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"vue-loader-v16": {
|
||||
"version": "npm:vue-loader@16.0.0-beta.8",
|
||||
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.0.0-beta.8.tgz",
|
||||
"integrity": "sha512-oouKUQWWHbSihqSD7mhymGPX1OQ4hedzAHyvm8RdyHh6m3oIvoRF+NM45i/bhNOlo8jCnuJhaSUf/6oDjv978g==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"chalk": "^4.1.0",
|
||||
"hash-sum": "^2.0.0",
|
||||
"loader-utils": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"chalk": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
|
||||
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -13291,104 +13389,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"vue-loader-v16": {
|
||||
"version": "npm:vue-loader@16.0.0-beta.8",
|
||||
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.0.0-beta.8.tgz",
|
||||
"integrity": "sha512-oouKUQWWHbSihqSD7mhymGPX1OQ4hedzAHyvm8RdyHh6m3oIvoRF+NM45i/bhNOlo8jCnuJhaSUf/6oDjv978g==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"chalk": "^4.1.0",
|
||||
"hash-sum": "^2.0.0",
|
||||
"loader-utils": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"color-convert": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"chalk": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
|
||||
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
}
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"color-name": "~1.1.4"
|
||||
}
|
||||
},
|
||||
"color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"emojis-list": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
|
||||
"integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"json5": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz",
|
||||
"integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"minimist": "^1.2.5"
|
||||
}
|
||||
},
|
||||
"loader-utils": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
|
||||
"integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"big.js": "^5.2.2",
|
||||
"emojis-list": "^3.0.0",
|
||||
"json5": "^2.1.2"
|
||||
}
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"has-flag": "^4.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"vue-observe-visibility": {
|
||||
"version": "0.4.6",
|
||||
"resolved": "https://registry.npmjs.org/vue-observe-visibility/-/vue-observe-visibility-0.4.6.tgz",
|
||||
|
|
|
|||
|
|
@ -200,13 +200,13 @@ export default {
|
|||
// If task ends with an error
|
||||
else if (result == "error") {
|
||||
// Pass the error string back with reject
|
||||
console.log("Rejecting pollTask due to error")
|
||||
console.log("Rejecting pollTask due to error");
|
||||
reject(new Error(response.data.output));
|
||||
}
|
||||
// If task ends with termination
|
||||
else if (result == "cancelled") {
|
||||
// Pass a generic termination error back with reject
|
||||
console.log("Rejecting pollTask due to cancellation")
|
||||
console.log("Rejecting pollTask due to cancellation");
|
||||
reject(new Error("Task cancelled"));
|
||||
} else {
|
||||
// Since the task is still running, we can update the progress bar
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import devTools from "./controlComponents/devTools.vue";
|
||||
import devTools from "./tabContentComponents/aboutComponents/devTools.vue";
|
||||
|
||||
// Export main app
|
||||
export default {
|
||||
|
|
|
|||
|
|
@ -148,9 +148,9 @@
|
|||
<script>
|
||||
import axios from "axios";
|
||||
|
||||
import cameraCalibrationSettings from "../viewComponents/settingsComponents/cameraSettingsComponents/cameraCalibrationSettings.vue";
|
||||
import CSMCalibrationSettings from "../viewComponents/settingsComponents/CSMSettingsComponents/CSMCalibrationSettings.vue";
|
||||
import miniStreamDisplay from "../viewComponents/miniStreamDisplay.vue";
|
||||
import cameraCalibrationSettings from "../tabContentComponents/settingsComponents/cameraSettingsComponents/cameraCalibrationSettings.vue";
|
||||
import CSMCalibrationSettings from "../tabContentComponents/settingsComponents/CSMSettingsComponents/CSMCalibrationSettings.vue";
|
||||
import miniStreamDisplay from "../genericComponents/miniStreamDisplay.vue";
|
||||
|
||||
export default {
|
||||
name: "CalibrationModal",
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import devTools from "../controlComponents/devTools.vue";
|
||||
import statusPane from "../viewComponents/statusPane.vue";
|
||||
import devTools from "./aboutComponents/devTools.vue";
|
||||
import statusPane from "./aboutComponents/statusPane.vue";
|
||||
|
||||
export default {
|
||||
name: "AboutContent",
|
||||
|
|
|
|||
|
|
@ -269,10 +269,10 @@
|
|||
<script>
|
||||
import axios from "axios";
|
||||
|
||||
import tagList from "../fieldComponents/tagList";
|
||||
import keyvalList from "../fieldComponents/keyvalList";
|
||||
import tagList from "../../fieldComponents/tagList";
|
||||
import keyvalList from "../../fieldComponents/keyvalList";
|
||||
|
||||
import taskSubmitter from "../genericComponents/taskSubmitter";
|
||||
import taskSubmitter from "../../genericComponents/taskSubmitter";
|
||||
|
||||
// Export main app
|
||||
export default {
|
||||
|
|
@ -11,8 +11,8 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import paneCapture from "../controlComponents/paneCapture";
|
||||
import streamDisplay from "../viewComponents/streamDisplay.vue";
|
||||
import paneCapture from "./captureComponents/paneCapture";
|
||||
import streamDisplay from "./streamContent.vue";
|
||||
|
||||
export default {
|
||||
name: "NavigateContent",
|
||||
|
|
|
|||
|
|
@ -28,8 +28,8 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="view-component uk-width-expand">
|
||||
<galleryDisplay v-if="viewPanel == 'gallery'" />
|
||||
<settingsDisplay v-else-if="viewPanel == 'settings'" />
|
||||
<galleryContent v-if="viewPanel == 'gallery'" />
|
||||
<settingsContent v-else-if="viewPanel == 'settings'" />
|
||||
<streamDisplay v-else />
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -38,9 +38,9 @@
|
|||
<script>
|
||||
import JsonForm from "../pluginComponents/JsonForm";
|
||||
import WebComponentLoader from "../pluginComponents/WebComponentLoader";
|
||||
import streamDisplay from "../viewComponents/streamDisplay.vue";
|
||||
import galleryDisplay from "../viewComponents/galleryDisplay.vue";
|
||||
import settingsDisplay from "../viewComponents/settingsDisplay.vue";
|
||||
import streamDisplay from "./streamContent.vue";
|
||||
import galleryDisplay from "../tabContentComponents/galleryContent.vue";
|
||||
import settingsDisplay from "../tabContentComponents/settingsContent.vue";
|
||||
|
||||
export default {
|
||||
name: "ExtensionContent",
|
||||
|
|
@ -49,8 +49,8 @@ export default {
|
|||
JsonForm,
|
||||
WebComponentLoader,
|
||||
streamDisplay,
|
||||
galleryDisplay,
|
||||
settingsDisplay
|
||||
galleryContent,
|
||||
settingsContent
|
||||
},
|
||||
|
||||
props: {
|
||||
|
|
|
|||
|
|
@ -1,19 +1,412 @@
|
|||
<template>
|
||||
<!-- Grid managing tab content -->
|
||||
<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">
|
||||
<i class="material-icons">keyboard_arrow_down</i>
|
||||
</a>
|
||||
</li>
|
||||
<li :class="[!sortDescending ? 'uk-active' : '']">
|
||||
<a class="uk-icon" href="#" @click="sortDescending = false">
|
||||
<i class="material-icons">keyboard_arrow_up</i>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#">Filter</a>
|
||||
<div
|
||||
:class="{
|
||||
'uk-light uk-background-secondary':
|
||||
$store.state.globalSettings.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>
|
||||
|
||||
<div class="view-component uk-height-1-1 uk-width-expand">
|
||||
<galleryDisplay />
|
||||
<!-- 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="galleryFolder = ''"
|
||||
>
|
||||
<i class="material-icons">arrow_back</i>
|
||||
</a>
|
||||
<div class="uk-margin-left">
|
||||
<h3 class="uk-margin-remove uk-margin-left">
|
||||
<b>SCAN</b>
|
||||
{{ allScans[galleryFolder].metadata.image.name }}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Gallery capture cards -->
|
||||
<div class="gallery-grid">
|
||||
<div v-for="item in sortedItems" :key="item.metadata.id">
|
||||
<scanCard
|
||||
v-if="'isScan' in item"
|
||||
:captures="item.captures"
|
||||
:metadata="item.metadata"
|
||||
:thumbnail="item.thumbnail"
|
||||
/>
|
||||
<captureCard v-else :capture-state="item" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import galleryDisplay from "../viewComponents/galleryDisplay.vue";
|
||||
import axios from "axios";
|
||||
import captureCard from "./galleryComponents/captureCard.vue";
|
||||
import scanCard from "./galleryComponents/scanCard.vue";
|
||||
|
||||
import ZipDownloader from "./galleryComponents/zipDownloader";
|
||||
|
||||
// Export main app
|
||||
export default {
|
||||
name: "NavigateContent",
|
||||
name: "GalleryDisplay",
|
||||
|
||||
components: {
|
||||
galleryDisplay
|
||||
captureCard,
|
||||
scanCard,
|
||||
ZipDownloader
|
||||
},
|
||||
|
||||
data: function() {
|
||||
return {
|
||||
captures: [],
|
||||
checkedTags: [],
|
||||
sortDescending: true,
|
||||
galleryFolder: "",
|
||||
scanTag: "scan",
|
||||
unwatchStoreFunction: null
|
||||
};
|
||||
},
|
||||
|
||||
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.metadata.image.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 (!capture.metadata.dataset) {
|
||||
captures.push(capture);
|
||||
}
|
||||
}
|
||||
|
||||
return captures;
|
||||
},
|
||||
|
||||
allScans: function() {
|
||||
// List of scans as capture-like objects
|
||||
var scans = {};
|
||||
|
||||
for (var capture of this.captures) {
|
||||
var annotations = capture.metadata.image.annotations;
|
||||
var tags = capture.metadata.image.tags;
|
||||
var dataset = capture.metadata.dataset;
|
||||
|
||||
if (dataset) {
|
||||
var id = dataset["id"];
|
||||
|
||||
// If this scan ID hasn't been seen before
|
||||
if (!(id in scans)) {
|
||||
scans[id] = {};
|
||||
scans[id].isScan = true;
|
||||
scans[id].captures = [];
|
||||
scans[id].metadata = {
|
||||
image: {
|
||||
name: dataset.name,
|
||||
acquisitionDate: dataset.acquisitionDate,
|
||||
id: dataset.id,
|
||||
tags: [],
|
||||
annotations: {}
|
||||
},
|
||||
type: dataset.type
|
||||
};
|
||||
}
|
||||
|
||||
// Add the capture object to the scan
|
||||
scans[id].captures.push(capture);
|
||||
|
||||
// Add missing scan metadata, prioritising first capture
|
||||
for (var key of Object.keys(annotations)) {
|
||||
if (!(key in scans[id].metadata.image.annotations)) {
|
||||
scans[id].metadata.image.annotations[key] = annotations[key];
|
||||
}
|
||||
}
|
||||
|
||||
// Append missing tags
|
||||
for (var tag of tags) {
|
||||
if (!scans[id].metadata.image.tags.includes(tag)) {
|
||||
scans[id].metadata.image.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) {
|
||||
console.log(this.allScans[this.galleryFolder].captures);
|
||||
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.metadata.image.id] = capture;
|
||||
}
|
||||
} else {
|
||||
// If it's a single capture, get the ID of the capture
|
||||
captures[item.metadata.image.id] = item;
|
||||
}
|
||||
}
|
||||
|
||||
return captures;
|
||||
},
|
||||
|
||||
sortedItems: function() {
|
||||
// Sort filteredItems using sortCaptures function
|
||||
return this.sortCaptures(this.filteredItems);
|
||||
}
|
||||
},
|
||||
|
||||
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();
|
||||
});
|
||||
// A global signal listener to set the gallery folder
|
||||
this.$root.$on("globalUpdateCaptureFolder", folder => {
|
||||
this.galleryFolder = folder;
|
||||
});
|
||||
},
|
||||
|
||||
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");
|
||||
// Remove global signal listener to set the gallery folder
|
||||
this.$root.$off("globalUpdateCaptureFolder");
|
||||
// Then we call that function here to unwatch
|
||||
if (this.unwatchStoreFunction) {
|
||||
this.unwatchStoreFunction();
|
||||
this.unwatchStoreFunction = null;
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
updateCaptures: function() {
|
||||
if (this.$store.state.available) {
|
||||
console.log("Updating capture list...");
|
||||
axios
|
||||
.get(this.capturesUri)
|
||||
.then(response => {
|
||||
this.captures = response.data;
|
||||
})
|
||||
.catch(error => {
|
||||
this.modalError(error); // Let mixin handle error
|
||||
});
|
||||
} else {
|
||||
console.log("Delaying capture update until connection is available");
|
||||
}
|
||||
},
|
||||
|
||||
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.metadata.image.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.metadata.image.acquisitionDate < b.metadata.image.acquisitionDate)
|
||||
return -1;
|
||||
if (a.metadata.image.acquisitionDate > b.metadata.image.acquisitionDate)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (this.sortDescending == true) {
|
||||
return list.sort(compare).reverse();
|
||||
} else {
|
||||
return list.sort(compare);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</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;
|
||||
}
|
||||
|
||||
.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>
|
||||
|
|
|
|||
|
|
@ -158,7 +158,7 @@
|
|||
|
||||
<script>
|
||||
import axios from "axios";
|
||||
import taskSubmitter from "../genericComponents/taskSubmitter";
|
||||
import taskSubmitter from "../../genericComponents/taskSubmitter";
|
||||
|
||||
// Export main app
|
||||
export default {
|
||||
|
|
@ -11,8 +11,8 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import paneNavigate from "../controlComponents/paneNavigate";
|
||||
import streamDisplay from "../viewComponents/streamDisplay.vue";
|
||||
import paneNavigate from "./navigateComponents/paneNavigate";
|
||||
import streamDisplay from "./streamContent.vue";
|
||||
|
||||
export default {
|
||||
name: "NavigateContent",
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
<script>
|
||||
import axios from "axios";
|
||||
import CSMCalibrationSettings from "./CSMSettingsComponents/CSMCalibrationSettings.vue";
|
||||
import miniStreamDisplay from "../miniStreamDisplay.vue";
|
||||
import miniStreamDisplay from "../../genericComponents/miniStreamDisplay.vue";
|
||||
|
||||
// Export main app
|
||||
export default {
|
||||
|
|
@ -69,7 +69,7 @@
|
|||
<script>
|
||||
import axios from "axios";
|
||||
import cameraCalibrationSettings from "./cameraSettingsComponents/cameraCalibrationSettings.vue";
|
||||
import miniStreamDisplay from "../miniStreamDisplay.vue";
|
||||
import miniStreamDisplay from "../../genericComponents/miniStreamDisplay.vue";
|
||||
|
||||
// Export main app
|
||||
export default {
|
||||
|
|
@ -1,20 +1,224 @@
|
|||
<template>
|
||||
<!-- Grid managing tab content -->
|
||||
<div uk-grid class="uk-height-1-1 uk-margin-remove uk-padding-remove">
|
||||
<div class="settings-nav">
|
||||
<ul class="uk-nav uk-nav-default">
|
||||
<li class="uk-nav-header">Application settings</li>
|
||||
<li>
|
||||
<tabIcon
|
||||
id="settings-display-icon"
|
||||
tab-i-d="display"
|
||||
:show-title="false"
|
||||
:show-tooltip="false"
|
||||
:require-connection="false"
|
||||
:current-tab="currentTab"
|
||||
@set-tab="setTab"
|
||||
>
|
||||
Display
|
||||
</tabIcon>
|
||||
</li>
|
||||
<li>
|
||||
<tabIcon
|
||||
id="settings-features-icon"
|
||||
tab-i-d="features"
|
||||
:show-title="false"
|
||||
:show-tooltip="false"
|
||||
:require-connection="false"
|
||||
:current-tab="currentTab"
|
||||
@set-tab="setTab"
|
||||
>
|
||||
Features
|
||||
</tabIcon>
|
||||
</li>
|
||||
<li class="uk-nav-header">Microscope settings</li>
|
||||
<li>
|
||||
<tabIcon
|
||||
id="settings-camera-icon"
|
||||
tab-i-d="camera"
|
||||
:show-title="false"
|
||||
:show-tooltip="false"
|
||||
:require-connection="true"
|
||||
:current-tab="currentTab"
|
||||
@set-tab="setTab"
|
||||
>
|
||||
Camera
|
||||
</tabIcon>
|
||||
</li>
|
||||
<li>
|
||||
<tabIcon
|
||||
id="settings-stage-icon"
|
||||
tab-i-d="stage"
|
||||
:show-title="false"
|
||||
:show-tooltip="false"
|
||||
:require-connection="true"
|
||||
:current-tab="currentTab"
|
||||
@set-tab="setTab"
|
||||
>
|
||||
Stage
|
||||
</tabIcon>
|
||||
</li>
|
||||
<li>
|
||||
<tabIcon
|
||||
id="settings-mapping-icon"
|
||||
tab-i-d="mapping"
|
||||
:show-title="false"
|
||||
:show-tooltip="false"
|
||||
:require-connection="true"
|
||||
:current-tab="currentTab"
|
||||
@set-tab="setTab"
|
||||
>
|
||||
Camera/stage mapping
|
||||
</tabIcon>
|
||||
</li>
|
||||
<li>
|
||||
<tabIcon
|
||||
id="settings-microscope-icon"
|
||||
tab-i-d="microscope"
|
||||
:show-title="false"
|
||||
:show-tooltip="false"
|
||||
:require-connection="true"
|
||||
:current-tab="currentTab"
|
||||
@set-tab="setTab"
|
||||
>
|
||||
General
|
||||
</tabIcon>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="view-component uk-width-expand uk-padding-small">
|
||||
<settingsDisplay />
|
||||
<tabContent
|
||||
tab-i-d="display"
|
||||
:require-connection="false"
|
||||
:current-tab="currentTab"
|
||||
>
|
||||
<div class="settings-pane uk-padding-small">
|
||||
<appSettings />
|
||||
<streamSettings />
|
||||
</div>
|
||||
</tabContent>
|
||||
|
||||
<tabContent
|
||||
tab-i-d="features"
|
||||
:require-connection="false"
|
||||
:current-tab="currentTab"
|
||||
>
|
||||
<div class="settings-pane uk-padding-small">
|
||||
<featuresSettings />
|
||||
</div>
|
||||
</tabContent>
|
||||
|
||||
<tabContent
|
||||
tab-i-d="camera"
|
||||
:require-connection="true"
|
||||
:current-tab="currentTab"
|
||||
>
|
||||
<div class="settings-pane uk-padding-small">
|
||||
<cameraSettings />
|
||||
</div>
|
||||
</tabContent>
|
||||
|
||||
<tabContent
|
||||
tab-i-d="stage"
|
||||
:require-connection="true"
|
||||
:current-tab="currentTab"
|
||||
>
|
||||
<div class="settings-pane uk-padding-small">
|
||||
<stageSettings />
|
||||
</div>
|
||||
</tabContent>
|
||||
|
||||
<tabContent
|
||||
tab-i-d="mapping"
|
||||
:require-connection="true"
|
||||
:current-tab="currentTab"
|
||||
>
|
||||
<div class="settings-pane uk-padding-small">
|
||||
<h3>Camera/stage mapping</h3>
|
||||
<p>
|
||||
Camera/stage mapping allows the stage to move relative to the camera
|
||||
view. This enables functions like click-to-move, and more precise
|
||||
tile scans.
|
||||
</p>
|
||||
<CSMSettings />
|
||||
</div>
|
||||
</tabContent>
|
||||
|
||||
<tabContent
|
||||
tab-i-d="microscope"
|
||||
:require-connection="true"
|
||||
:current-tab="currentTab"
|
||||
>
|
||||
<div class="settings-pane uk-padding-small">
|
||||
<h3>Microscope settings</h3>
|
||||
<microscopeSettings />
|
||||
</div>
|
||||
</tabContent>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import settingsDisplay from "../viewComponents/settingsDisplay.vue";
|
||||
import streamSettings from "./settingsComponents/streamSettings.vue";
|
||||
import microscopeSettings from "./settingsComponents/microscopeSettings.vue";
|
||||
import cameraSettings from "./settingsComponents/cameraSettings.vue";
|
||||
import appSettings from "./settingsComponents/appSettings.vue";
|
||||
import featuresSettings from "./settingsComponents/featuresSettings.vue";
|
||||
import CSMSettings from "./settingsComponents/CSMSettings.vue";
|
||||
import stageSettings from "./settingsComponents/stageSettings.vue";
|
||||
// Import generic components
|
||||
import tabIcon from "../genericComponents/tabIcon";
|
||||
import tabContent from "../genericComponents/tabContent";
|
||||
|
||||
// Export main app
|
||||
export default {
|
||||
name: "NavigateContent",
|
||||
name: "SettingsContent",
|
||||
|
||||
components: {
|
||||
settingsDisplay
|
||||
streamSettings,
|
||||
cameraSettings,
|
||||
stageSettings,
|
||||
microscopeSettings,
|
||||
CSMSettings,
|
||||
appSettings,
|
||||
featuresSettings,
|
||||
tabIcon,
|
||||
tabContent
|
||||
},
|
||||
|
||||
data: function() {
|
||||
return {
|
||||
selected: "display",
|
||||
currentTab: "display"
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
setTab: function(event, tab) {
|
||||
if (!(this.currentTab == tab)) {
|
||||
this.currentTab = tab;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
// Custom UIkit CSS modifications
|
||||
@import "../../assets/less/theme.less";
|
||||
|
||||
.settings-nav {
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
width: 250px;
|
||||
padding: 10px;
|
||||
background-color: rgba(180, 180, 180, 0.03);
|
||||
border-width: 0 1px 0 0;
|
||||
border-style: solid;
|
||||
border-color: rgba(180, 180, 180, 0.25);
|
||||
}
|
||||
|
||||
.settings-nav li > a {
|
||||
padding-left: 6px !important;
|
||||
border-radius: @button-border-radius;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -135,7 +135,7 @@
|
|||
|
||||
<script>
|
||||
import axios from "axios";
|
||||
import taskSubmitter from "../genericComponents/taskSubmitter";
|
||||
import taskSubmitter from "../../genericComponents/taskSubmitter";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
|
@ -11,8 +11,8 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import paneSlideScan from "../controlComponents/paneSlideScan";
|
||||
import streamDisplay from "../viewComponents/streamDisplay.vue";
|
||||
import paneSlideScan from "./slideScanComponents/paneSlideScan";
|
||||
import streamDisplay from "./streamContent.vue";
|
||||
|
||||
export default {
|
||||
name: "SlideScanContent",
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import streamDisplay from "../viewComponents/streamDisplay.vue";
|
||||
import streamDisplay from "./streamContent.vue";
|
||||
|
||||
export default {
|
||||
name: "ViewContent",
|
||||
|
|
|
|||
|
|
@ -1,168 +0,0 @@
|
|||
<template>
|
||||
<div
|
||||
class="hostCard uk-card uk-card-default uk-padding-remove uk-width-medium"
|
||||
:class="{ 'uk-card-secondary': $store.state.globalSettings.darkMode }"
|
||||
>
|
||||
<div class="uk-card-media-top">
|
||||
<div class="snapshot-container uk-width-1-1 uk-height-auto">
|
||||
<img
|
||||
class="uk-width-1-1"
|
||||
:data-src="snapshotSrc"
|
||||
:src="snapshotSrc"
|
||||
width="300"
|
||||
height="225"
|
||||
/>
|
||||
<div
|
||||
v-if="!snapshotAvailable"
|
||||
class="uk-position-cover uk-flex uk-flex-center uk-flex-middle"
|
||||
>
|
||||
Snapshot unavailable
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="uk-card-body uk-padding-small">
|
||||
<div
|
||||
class="uk-margin-small uk-margin-remove-left uk-margin-remove-right uk-flex uk-flex-middle"
|
||||
>
|
||||
<div class="uk-width-expand host-description">
|
||||
<div class="host-description">
|
||||
<b>{{ name }}</b>
|
||||
</div>
|
||||
<div class="host-description">{{ hostname }}:{{ port }}</div>
|
||||
</div>
|
||||
<a
|
||||
v-if="deletable"
|
||||
href="#"
|
||||
class="uk-icon uk-width-auto host-delete"
|
||||
@click="$emit('delete')"
|
||||
><i class="material-icons">delete</i></a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="uk-card-footer uk-padding-small">
|
||||
<button
|
||||
class="uk-button uk-button-default uk-form-small uk-float-right uk-margin uk-margin-remove-top uk-width-1-1"
|
||||
@click="$emit('connect')"
|
||||
>
|
||||
Connect
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from "axios";
|
||||
|
||||
// Export main app
|
||||
export default {
|
||||
name: "HostCard",
|
||||
|
||||
props: {
|
||||
name: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
hostname: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
port: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
deletable: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
|
||||
data: function() {
|
||||
return {
|
||||
snapshotRule: "/api/v2/streams/snapshot",
|
||||
snapshotSrc: "",
|
||||
snapshotAvailable: false,
|
||||
polling: null
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
routesURL: function() {
|
||||
return `http://${this.hostname}:${this.port}/routes`;
|
||||
},
|
||||
snapshotURL: function() {
|
||||
return `http://${this.hostname}:${this.port}${this.snapshotRule}`;
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
// Try to load the microscope snapshot when mounted
|
||||
this.checkSnapshotAvailable();
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
// Clear the polling timer
|
||||
if (this.polling !== null) {
|
||||
clearInterval(this.polling);
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
checkSnapshotAvailable: function() {
|
||||
// Fetches the list of routes from the host, and starts
|
||||
// a snapshot timer if the snapshot route is available
|
||||
|
||||
// Send tag request
|
||||
axios
|
||||
.get(this.routesURL)
|
||||
.then(response => {
|
||||
// If the host has a snapshot route available
|
||||
if (this.snapshotRule in response.data) {
|
||||
// Tell the view that a snapshot is available
|
||||
this.snapshotAvailable = true;
|
||||
// Update the snapshot URL
|
||||
this.updateSnapshotURL();
|
||||
// If not already polling, start polling
|
||||
if (this.polling === null) {
|
||||
this.updateSnapshotPoll();
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// Tell the view that a snapshot is not available, and ignore
|
||||
this.snapshotAvailable = false;
|
||||
});
|
||||
},
|
||||
|
||||
updateSnapshotURL: function() {
|
||||
// Set the snapshot image src to the snapshot URL,
|
||||
// adding a timestamp argument to act as a cache-breaker
|
||||
this.snapshotSrc = `${this.snapshotURL}?${Date.now()}`;
|
||||
},
|
||||
|
||||
updateSnapshotPoll: function() {
|
||||
// Start a timer to call updateSnapshotURL periodically
|
||||
this.polling = setInterval(() => {
|
||||
this.updateSnapshotURL();
|
||||
}, 15000);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.host-description {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.snapshot-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
img[data-src][src*="data:image"] {
|
||||
background: rgba(127, 127, 127, 0.1);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,409 +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">
|
||||
<i class="material-icons">keyboard_arrow_down</i>
|
||||
</a>
|
||||
</li>
|
||||
<li :class="[!sortDescending ? 'uk-active' : '']">
|
||||
<a class="uk-icon" href="#" @click="sortDescending = false">
|
||||
<i class="material-icons">keyboard_arrow_up</i>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#">Filter</a>
|
||||
<div
|
||||
class="uk-navbar-dropdown"
|
||||
:class="{
|
||||
'uk-light uk-background-secondary':
|
||||
$store.state.globalSettings.darkMode
|
||||
}"
|
||||
>
|
||||
<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"
|
||||
class="uk-checkbox"
|
||||
type="checkbox"
|
||||
:value="tag"
|
||||
checked
|
||||
/>
|
||||
{{ tag }}
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Right side buttons -->
|
||||
<div class="uk-navbar-right">
|
||||
<div class="uk-grid">
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
class="uk-button uk-button-default uk-width-1-1"
|
||||
@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
|
||||
href="#"
|
||||
class="uk-icon uk-margin-remove"
|
||||
@click="galleryFolder = ''"
|
||||
>
|
||||
<i class="material-icons">arrow_back</i>
|
||||
</a>
|
||||
<div class="uk-margin-left">
|
||||
<h3 class="uk-margin-remove uk-margin-left">
|
||||
<b>SCAN</b>
|
||||
{{ allScans[galleryFolder].metadata.image.name }}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Gallery capture cards -->
|
||||
<div class="gallery-grid">
|
||||
<div v-for="item in sortedItems" :key="item.metadata.id">
|
||||
<scanCard
|
||||
v-if="'isScan' in item"
|
||||
:metadata="item.metadata"
|
||||
:thumbnail="item.thumbnail"
|
||||
:captures="item.captures"
|
||||
/>
|
||||
<captureCard v-else :capture-state="item" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from "axios";
|
||||
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
|
||||
},
|
||||
|
||||
data: function() {
|
||||
return {
|
||||
captures: [],
|
||||
checkedTags: [],
|
||||
sortDescending: true,
|
||||
galleryFolder: "",
|
||||
scanTag: "scan",
|
||||
unwatchStoreFunction: null
|
||||
};
|
||||
},
|
||||
|
||||
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.metadata.image.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 (!capture.metadata.dataset) {
|
||||
captures.push(capture);
|
||||
}
|
||||
}
|
||||
|
||||
return captures;
|
||||
},
|
||||
|
||||
allScans: function() {
|
||||
// List of scans as capture-like objects
|
||||
var scans = {};
|
||||
|
||||
for (var capture of this.captures) {
|
||||
var annotations = capture.metadata.image.annotations;
|
||||
var tags = capture.metadata.image.tags;
|
||||
var dataset = capture.metadata.dataset;
|
||||
|
||||
if (dataset) {
|
||||
var id = dataset["id"];
|
||||
|
||||
// If this scan ID hasn't been seen before
|
||||
if (!(id in scans)) {
|
||||
scans[id] = {};
|
||||
scans[id].isScan = true;
|
||||
scans[id].captures = [];
|
||||
scans[id].metadata = {
|
||||
image: {
|
||||
name: dataset.name,
|
||||
acquisitionDate: dataset.acquisitionDate,
|
||||
id: dataset.id,
|
||||
tags: [],
|
||||
annotations: {}
|
||||
},
|
||||
type: dataset.type
|
||||
};
|
||||
}
|
||||
|
||||
// Add the capture object to the scan
|
||||
scans[id].captures.push(capture);
|
||||
|
||||
// Add missing scan metadata, prioritising first capture
|
||||
for (var key of Object.keys(annotations)) {
|
||||
if (!(key in scans[id].metadata.image.annotations)) {
|
||||
scans[id].metadata.image.annotations[key] = annotations[key];
|
||||
}
|
||||
}
|
||||
|
||||
// Append missing tags
|
||||
for (var tag of tags) {
|
||||
if (!scans[id].metadata.image.tags.includes(tag)) {
|
||||
scans[id].metadata.image.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) {
|
||||
console.log(this.allScans[this.galleryFolder].captures);
|
||||
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.metadata.image.id] = capture;
|
||||
}
|
||||
} else {
|
||||
// If it's a single capture, get the ID of the capture
|
||||
captures[item.metadata.image.id] = item;
|
||||
}
|
||||
}
|
||||
|
||||
return captures;
|
||||
},
|
||||
|
||||
sortedItems: function() {
|
||||
// Sort filteredItems using sortCaptures function
|
||||
return this.sortCaptures(this.filteredItems);
|
||||
}
|
||||
},
|
||||
|
||||
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();
|
||||
});
|
||||
// A global signal listener to set the gallery folder
|
||||
this.$root.$on("globalUpdateCaptureFolder", folder => {
|
||||
this.galleryFolder = folder;
|
||||
});
|
||||
},
|
||||
|
||||
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");
|
||||
// Remove global signal listener to set the gallery folder
|
||||
this.$root.$off("globalUpdateCaptureFolder");
|
||||
// Then we call that function here to unwatch
|
||||
if (this.unwatchStoreFunction) {
|
||||
this.unwatchStoreFunction();
|
||||
this.unwatchStoreFunction = null;
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
updateCaptures: function() {
|
||||
if (this.$store.state.available) {
|
||||
console.log("Updating capture list...");
|
||||
axios
|
||||
.get(this.capturesUri)
|
||||
.then(response => {
|
||||
this.captures = response.data;
|
||||
})
|
||||
.catch(error => {
|
||||
this.modalError(error); // Let mixin handle error
|
||||
});
|
||||
} else {
|
||||
console.log("Delaying capture update until connection is available");
|
||||
}
|
||||
},
|
||||
|
||||
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.metadata.image.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.metadata.image.acquisitionDate < b.metadata.image.acquisitionDate)
|
||||
return -1;
|
||||
if (a.metadata.image.acquisitionDate > b.metadata.image.acquisitionDate)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (this.sortDescending == true) {
|
||||
return list.sort(compare).reverse();
|
||||
} else {
|
||||
return list.sort(compare);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.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;
|
||||
}
|
||||
.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>
|
||||
|
|
@ -1,224 +0,0 @@
|
|||
<template>
|
||||
<!-- Grid managing tab content -->
|
||||
<div uk-grid class="uk-height-1-1 uk-margin-remove uk-padding-remove">
|
||||
<div class="settings-nav">
|
||||
<ul class="uk-nav uk-nav-default">
|
||||
<li class="uk-nav-header">Application settings</li>
|
||||
<li>
|
||||
<tabIcon
|
||||
id="settings-display-icon"
|
||||
tab-i-d="display"
|
||||
:show-title="false"
|
||||
:show-tooltip="false"
|
||||
:require-connection="false"
|
||||
:current-tab="currentTab"
|
||||
@set-tab="setTab"
|
||||
>
|
||||
Display
|
||||
</tabIcon>
|
||||
</li>
|
||||
<li>
|
||||
<tabIcon
|
||||
id="settings-features-icon"
|
||||
tab-i-d="features"
|
||||
:show-title="false"
|
||||
:show-tooltip="false"
|
||||
:require-connection="false"
|
||||
:current-tab="currentTab"
|
||||
@set-tab="setTab"
|
||||
>
|
||||
Features
|
||||
</tabIcon>
|
||||
</li>
|
||||
<li class="uk-nav-header">Microscope settings</li>
|
||||
<li>
|
||||
<tabIcon
|
||||
id="settings-camera-icon"
|
||||
tab-i-d="camera"
|
||||
:show-title="false"
|
||||
:show-tooltip="false"
|
||||
:require-connection="true"
|
||||
:current-tab="currentTab"
|
||||
@set-tab="setTab"
|
||||
>
|
||||
Camera
|
||||
</tabIcon>
|
||||
</li>
|
||||
<li>
|
||||
<tabIcon
|
||||
id="settings-stage-icon"
|
||||
tab-i-d="stage"
|
||||
:show-title="false"
|
||||
:show-tooltip="false"
|
||||
:require-connection="true"
|
||||
:current-tab="currentTab"
|
||||
@set-tab="setTab"
|
||||
>
|
||||
Stage
|
||||
</tabIcon>
|
||||
</li>
|
||||
<li>
|
||||
<tabIcon
|
||||
id="settings-mapping-icon"
|
||||
tab-i-d="mapping"
|
||||
:show-title="false"
|
||||
:show-tooltip="false"
|
||||
:require-connection="true"
|
||||
:current-tab="currentTab"
|
||||
@set-tab="setTab"
|
||||
>
|
||||
Camera/stage mapping
|
||||
</tabIcon>
|
||||
</li>
|
||||
<li>
|
||||
<tabIcon
|
||||
id="settings-microscope-icon"
|
||||
tab-i-d="microscope"
|
||||
:show-title="false"
|
||||
:show-tooltip="false"
|
||||
:require-connection="true"
|
||||
:current-tab="currentTab"
|
||||
@set-tab="setTab"
|
||||
>
|
||||
General
|
||||
</tabIcon>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="view-component uk-width-expand uk-padding-small">
|
||||
<tabContent
|
||||
tab-i-d="display"
|
||||
:require-connection="false"
|
||||
:current-tab="currentTab"
|
||||
>
|
||||
<div class="settings-pane uk-padding-small">
|
||||
<appSettings />
|
||||
<streamSettings />
|
||||
</div>
|
||||
</tabContent>
|
||||
|
||||
<tabContent
|
||||
tab-i-d="features"
|
||||
:require-connection="false"
|
||||
:current-tab="currentTab"
|
||||
>
|
||||
<div class="settings-pane uk-padding-small">
|
||||
<featuresSettings />
|
||||
</div>
|
||||
</tabContent>
|
||||
|
||||
<tabContent
|
||||
tab-i-d="camera"
|
||||
:require-connection="true"
|
||||
:current-tab="currentTab"
|
||||
>
|
||||
<div class="settings-pane uk-padding-small">
|
||||
<cameraSettings />
|
||||
</div>
|
||||
</tabContent>
|
||||
|
||||
<tabContent
|
||||
tab-i-d="stage"
|
||||
:require-connection="true"
|
||||
:current-tab="currentTab"
|
||||
>
|
||||
<div class="settings-pane uk-padding-small">
|
||||
<stageSettings />
|
||||
</div>
|
||||
</tabContent>
|
||||
|
||||
<tabContent
|
||||
tab-i-d="mapping"
|
||||
:require-connection="true"
|
||||
:current-tab="currentTab"
|
||||
>
|
||||
<div class="settings-pane uk-padding-small">
|
||||
<h3>Camera/stage mapping</h3>
|
||||
<p>
|
||||
Camera/stage mapping allows the stage to move relative to the camera
|
||||
view. This enables functions like click-to-move, and more precise
|
||||
tile scans.
|
||||
</p>
|
||||
<CSMSettings />
|
||||
</div>
|
||||
</tabContent>
|
||||
|
||||
<tabContent
|
||||
tab-i-d="microscope"
|
||||
:require-connection="true"
|
||||
:current-tab="currentTab"
|
||||
>
|
||||
<div class="settings-pane uk-padding-small">
|
||||
<h3>Microscope settings</h3>
|
||||
<microscopeSettings />
|
||||
</div>
|
||||
</tabContent>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import streamSettings from "./settingsComponents/streamSettings.vue";
|
||||
import microscopeSettings from "./settingsComponents/microscopeSettings.vue";
|
||||
import cameraSettings from "./settingsComponents/cameraSettings.vue";
|
||||
import appSettings from "./settingsComponents/appSettings.vue";
|
||||
import featuresSettings from "./settingsComponents/featuresSettings.vue";
|
||||
import CSMSettings from "./settingsComponents/CSMSettings.vue";
|
||||
import stageSettings from "./settingsComponents/stageSettings.vue";
|
||||
// Import generic components
|
||||
import tabIcon from "../genericComponents/tabIcon";
|
||||
import tabContent from "../genericComponents/tabContent";
|
||||
|
||||
// Export main app
|
||||
export default {
|
||||
name: "SettingsDisplay",
|
||||
|
||||
components: {
|
||||
streamSettings,
|
||||
cameraSettings,
|
||||
stageSettings,
|
||||
microscopeSettings,
|
||||
CSMSettings,
|
||||
appSettings,
|
||||
featuresSettings,
|
||||
tabIcon,
|
||||
tabContent
|
||||
},
|
||||
|
||||
data: function() {
|
||||
return {
|
||||
selected: "display",
|
||||
currentTab: "display"
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
setTab: function(event, tab) {
|
||||
if (!(this.currentTab == tab)) {
|
||||
this.currentTab = tab;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
// Custom UIkit CSS modifications
|
||||
@import "../../assets/less/theme.less";
|
||||
|
||||
.settings-nav {
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
width: 250px;
|
||||
padding: 10px;
|
||||
background-color: rgba(180, 180, 180, 0.03);
|
||||
border-width: 0 1px 0 0;
|
||||
border-style: solid;
|
||||
border-color: rgba(180, 180, 180, 0.25);
|
||||
}
|
||||
|
||||
.settings-nav li > a {
|
||||
padding-left: 6px !important;
|
||||
border-radius: @button-border-radius;
|
||||
}
|
||||
</style>
|
||||
Loading…
Add table
Add a link
Reference in a new issue