Great big ESLint

This commit is contained in:
Joel Collins 2019-11-11 16:33:43 +00:00
parent 051eabbdc3
commit ebcb938da1
48 changed files with 3890 additions and 2536 deletions

View file

@ -1,57 +1,55 @@
<template>
<div id="app" v-bind:class="handleTheme">
<div id="app" :class="handleTheme">
<!-- Grid managing whole app -->
<div uk-grid class="uk-height-1-1 uk-margin-remove uk-padding-remove" margin=0>
<panelLeft/>
<panelRight/>
<div
uk-grid
class="uk-height-1-1 uk-margin-remove uk-padding-remove"
margin="0"
>
<panelLeft />
<panelRight />
</div>
</div>
</template>
<script>
// Import axios for HTTP requests
import axios from 'axios'
// Import components
import panelLeft from './components/panelLeft.vue'
import panelRight from './components/panelRight.vue'
import panelLeft from "./components/panelLeft.vue";
import panelRight from "./components/panelRight.vue";
// Export main app
export default {
name: 'app',
name: "App",
components: {
panelRight,
panelLeft
},
data: function () {
return {}
},
created: function () {
window.addEventListener('beforeunload', this.handleExit)
},
methods: {
handleExit: function(event) {
console.log("Triggered beforeunload")
this.$root.$emit('globalTogglePreview', false)
}
data: function() {
return {};
},
computed: {
handleTheme: function () {
handleTheme: function() {
return {
'uk-light': this.$store.state.globalSettings.darkMode,
'uk-background-secondary': this.$store.state.globalSettings.darkMode
}
"uk-light": this.$store.state.globalSettings.darkMode,
"uk-background-secondary": this.$store.state.globalSettings.darkMode
};
}
},
created: function() {
window.addEventListener("beforeunload", this.handleExit);
},
methods: {
handleExit: function() {
console.log("Triggered beforeunload");
this.$root.$emit("globalTogglePreview", false);
}
}
}
};
</script>
<style lang="less">
@ -63,7 +61,8 @@ export default {
// We override the custom-electron-titlebar z-index
// UIKit lightbox must be able to draw over the titlebar
// as it currently always spawns at the root of the DOM
.titlebar, .titlebar > * {
.titlebar,
.titlebar > * {
z-index: 1000 !important;
}
@ -71,10 +70,11 @@ export default {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: left;
height: 100%
height: 100%;
}
body, html {
body,
html {
height: 100%;
overflow: hidden;
}
@ -83,5 +83,4 @@ body, html {
pointer-events: none;
opacity: 0.4;
}
</style>

View file

@ -1,14 +1,15 @@
// Theming specific to the electron app
.menubar-menu-container {
box-shadow: 0.5px 1px 4px 0px rgba(0,0,0,0.3) !important;
box-shadow: 0.5px 1px 4px 0px rgba(0, 0, 0, 0.3) !important;
}
.menubar-menu-container > * {
box-shadow: none !important;
.menubar-menu-container>* {
box-shadow: none !important;
}
.menubar-menu-container .action-menu-item, .menubar-menu-container .action-label {
.menubar-menu-container .action-menu-item,
.menubar-menu-container .action-label {
cursor: default
}

View file

@ -8,35 +8,49 @@
}
.hljs-comment,
.hljs-meta { color: #969896; }
.hljs-meta {
color: #969896;
}
.hljs-string,
.hljs-variable,
.hljs-template-variable,
.hljs-strong,
.hljs-emphasis,
.hljs-quote { color: @global-danger-background; }
.hljs-quote {
color: @global-danger-background;
}
.hljs-keyword,
.hljs-selector-tag,
.hljs-type { color: #1f34aa; }
.hljs-type {
color: #1f34aa;
}
.hljs-literal,
.hljs-symbol,
.hljs-bullet,
.hljs-attribute { color: #0086b3; }
.hljs-attribute {
color: #0086b3;
}
.hljs-section,
.hljs-name { color: #63a35c; }
.hljs-name {
color: #63a35c;
}
.hljs-tag { color: #333; }
.hljs-tag {
color: #333;
}
.hljs-title,
.hljs-attr,
.hljs-selector-id,
.hljs-selector-class,
.hljs-selector-attr,
.hljs-selector-pseudo { color: #795da3; }
.hljs-selector-pseudo {
color: #795da3;
}
.hljs-addition {
color: #55a532;
@ -48,4 +62,6 @@
background-color: #ffecec;
}
.hljs-link { text-decoration: underline; }
.hljs-link {
text-decoration: underline;
}

View file

@ -15,31 +15,31 @@
// UIkit
// ========================================================================
@global-font-family: ProximaNova, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
@global-font-size: 14px;
@global-font-family: ProximaNova, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
@global-font-size: 14px;
@global-xxlarge-font-size: 38px;
@global-xlarge-font-size: 30px;
@global-large-font-size: 24px;
@global-medium-font-size: 20px;
@global-small-font-size: 14px;
@global-xxlarge-font-size: 38px;
@global-xlarge-font-size: 30px;
@global-large-font-size: 24px;
@global-medium-font-size: 20px;
@global-small-font-size: 14px;
@global-emphasis-color: #222;
@global-emphasis-color: #222;
//
// Base
//
@base-code-font-family: 'Roboto Mono', monospace;
@base-code-font-size: 12px;
@base-code-font-family: 'Roboto Mono', monospace;
@base-code-font-size: 12px;
@base-heading-font-weight: 300;
@base-heading-font-weight: 300;
@base-pre-font-size: 12px;
@base-pre-padding: 25px;
@base-pre-background: @global-muted-background;
@base-pre-border-width: 0;
@base-pre-border-radius: 0;
@base-pre-font-size: 12px;
@base-pre-padding: 25px;
@base-pre-background: @global-muted-background;
@base-pre-border-width: 0;
@base-pre-border-radius: 0;
.hook-base-body() {
-webkit-font-smoothing: antialiased;
@ -51,96 +51,103 @@
// Container
//
@container-max-width: 1380px;
@container-small-max-width: 650px;
@container-max-width: 1380px;
@container-small-max-width: 650px;
//
// Navbar
//
@inverse-navbar-nav-item-color: @inverse-global-color;
@inverse-navbar-nav-item-hover-color: @inverse-global-emphasis-color;
@inverse-navbar-nav-item-color: @inverse-global-color;
@inverse-navbar-nav-item-hover-color: @inverse-global-emphasis-color;
//
// Nav
//
@nav-header-font-size: 12px;
@nav-header-font-size: 12px;
//
// Subnav
//
@inverse-subnav-item-color: @inverse-global-color;
@inverse-subnav-item-hover-color: @inverse-global-emphasis-color;
@inverse-subnav-item-color: @inverse-global-color;
@inverse-subnav-item-hover-color: @inverse-global-emphasis-color;
//
// Tab
//
@tab-item-padding-horizontal: 10px;
@tab-item-padding-vertical: 9px;
@tab-item-border-width: 2px;
@tab-item-font-size: 12px;
@tab-item-padding-horizontal: 10px;
@tab-item-padding-vertical: 9px;
@tab-item-border-width: 2px;
@tab-item-font-size: 12px;
.hook-tab-item() { line-height: 20px; }
.hook-tab-item() {
line-height: 20px;
}
//
// Table
//
@table-header-cell-font-size: 12px;
@table-header-cell-font-size: 12px;
//
// Label
//
@label-font-size: 12px;
@label-font-size: 12px;
//
// Text
//
.hook-text-lead() { font-weight: 300; }
.hook-text-large() { font-weight: 300; }
.hook-text-lead() {
font-weight: 300;
}
.hook-text-large() {
font-weight: 300;
}
//
// Utility
//
@inverse-logo-color: @inverse-global-emphasis-color;
@inverse-logo-hover-color: @inverse-global-emphasis-color;
@inverse-logo-color: @inverse-global-emphasis-color;
@inverse-logo-hover-color: @inverse-global-emphasis-color;
//
// Off-canvas
//
@offcanvas-bar-background: #fff;
@offcanvas-bar-color-mode: dark;
@offcanvas-bar-background: #fff;
@offcanvas-bar-color-mode: dark;
//
// Inverse
//
@inverse-global-color: fade(@global-inverse-color, 80%);
@inverse-global-muted-color: fade(@global-inverse-color, 60%);
@inverse-global-color: fade(@global-inverse-color, 80%);
@inverse-global-muted-color: fade(@global-inverse-color, 60%);
//
// Paper
//
@paper-border-radius: 4px;
@paper-border-radius: 4px;
/* ========================================================================
Theme
========================================================================== */
@sidebar-left-width: 240px;
@sidebar-left-width-xl: 300px;
@sidebar-left-width: 240px;
@sidebar-left-width-xl: 300px;
@sidebar-right-width: 200px;
@sidebar-right-left: 0px;
@sidebar-right-left-xl: 60px;
@sidebar-right-width: 200px;
@sidebar-right-left: 0px;
@sidebar-right-left-xl: 60px;
@ -152,11 +159,11 @@
*/
// Line Mode
@navbar-nav-item-line-margin-vertical: 20px;
@navbar-nav-item-line-margin-horizontal: @navbar-nav-item-padding-horizontal;
@navbar-nav-item-line-height: 1px;
@navbar-nav-item-line-background: currentColor;
@navbar-nav-item-line-transition-duration: 0.3s;
@navbar-nav-item-line-margin-vertical: 20px;
@navbar-nav-item-line-margin-horizontal: @navbar-nav-item-padding-horizontal;
@navbar-nav-item-line-height: 1px;
@navbar-nav-item-line-background: currentColor;
@navbar-nav-item-line-transition-duration: 0.3s;
.uk-navbar-dropdown.uk-open {
border-radius: @paper-border-radius;
@ -168,7 +175,7 @@
.uk-card {
border-radius: @paper-border-radius;
border: 1px solid rgba(180, 180, 180, 0.25);
border: 1px solid rgba(180, 180, 180, 0.25);
}
.uk-card-media-top img {
@ -176,14 +183,15 @@
}
.uk-card-default .uk-card-footer {
border-top: 1px solid rgba(180, 180, 180, 0.25);
border-top: 1px solid rgba(180, 180, 180, 0.25);
}
.uk-card-primary .uk-card-footer {
border-top: 1px solid rgba(0, 0, 0, 0.075);
border-top: 1px solid rgba(0, 0, 0, 0.075);
}
.hook-inverse() {
// Override background colour in dark mode
.uk-card-default {
background-color: #2a2a2a !important;
@ -228,16 +236,22 @@
/*
* Links
*/
.uk-link, .uk-button-link {
.uk-link,
.uk-button-link {
color: @global-muted-color;
}
.uk-link:hover, .uk-button-link:hover, a:hover {
.uk-link:hover,
.uk-button-link:hover,
a:hover {
color: @global-primary-background;
}
.hook-inverse() {
.uk-link:hover, .uk-button-link:hover, a:hover {
.uk-link:hover,
.uk-button-link:hover,
a:hover {
color: @inverse-primary-muted-color;
}
}
@ -256,7 +270,7 @@
}
.hook-inverse() {
.uk-button-primary{
.uk-button-primary {
background-color: rgba(180, 180, 180, 0.15);
color: @inverse-primary-muted-color;
border-color: @inverse-primary-muted-color;
@ -268,7 +282,8 @@
}
}
.uk-icon-button, .uk-icon-button:hover {
.uk-icon-button,
.uk-icon-button:hover {
text-decoration: none
}
@ -296,7 +311,7 @@
background-color: #333;
}
.uk-open > .uk-accordion-title::before {
.uk-open>.uk-accordion-title::before {
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M7 10l5 5 5-5z'/%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3C/svg%3E");
mask-position: 100% 50%;
background-image: none;

View file

@ -1,30 +1,70 @@
<template>
<div id="paneCapture">
<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">
<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>
<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>
<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>
<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>
<hr />
<p><label><input v-model="resizeCapture" class="uk-checkbox" type="checkbox"> Resize capture</label></p>
<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-bind:class="resizeClass" v-model="resizeDims[0]" class="uk-input uk-form-width-medium uk-form-small" type="number" name="inputResizeW">
<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-bind:class="resizeClass" v-model="resizeDims[1]" class="uk-input uk-form-width-medium uk-form-small" type="number" name="inputResizeH">
<input
v-model="resizeDims[1]"
:class="resizeClass"
class="uk-input uk-form-width-medium uk-form-small"
type="number"
name="inputResizeH"
/>
</div>
</div>
@ -33,7 +73,12 @@
<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>
<textarea
v-model="captureNotes"
class="uk-textarea"
rows="5"
placeholder="Capture notes"
></textarea>
</div>
</div>
</li>
@ -41,84 +86,129 @@
<li>
<a class="uk-accordion-title" href="#">Metadata</a>
<div class="uk-accordion-content">
<keyvalList v-model="metadata"/>
<keyvalList v-model="metadata" />
</div>
</li>
<li>
<a class="uk-accordion-title" href="#">Tags</a>
<div class="uk-accordion-content">
<tagList v-model="tags"/>
<tagList v-model="tags" />
</div>
</li>
</ul>
<hr>
<hr />
<ul uk-accordion="multiple: true; animation: false">
<!--Show stack and scan if default plugin is enabled-->
<li v-if="this.$store.state.apiState.plugin.includes('default_scan')">
<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>
<label
><input
v-model="scanCapture"
class="uk-checkbox"
type="checkbox"
/>
Scan capture</label
>
</div>
<div v-bind:class="{ 'uk-disabled': !scanCapture }" >
<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>
<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">
<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>
<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">
<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>
<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="inputPositionZx">
<input
v-model="scanStepSize.z"
class="uk-input uk-form-small"
type="number"
name="inputPositionZx"
/>
</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>
<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">
<input
v-model="scanSteps.x"
class="uk-input uk-form-small"
type="number"
name="inputPositionX"
/>
</div>
</div>
<div>
<label class="uk-form-label" for="form-stacked-text">y steps</label>
<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">
<input
v-model="scanSteps.y"
class="uk-input uk-form-small"
type="number"
name="inputPositionY"
/>
</div>
</div>
<div>
<label class="uk-form-label" for="form-stacked-text">z steps</label>
<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="inputPositionZx">
<input
v-model="scanSteps.z"
class="uk-input uk-form-small"
type="number"
name="inputPositionZx"
/>
</div>
</div>
</div>
<div class="uk-margin-small uk-margin-remove-bottom">
<label class="uk-form-label" for="form-stacked-text">Autofocus</label>
<label class="uk-form-label" for="form-stacked-text"
>Autofocus</label
>
<select v-model="scanDeltaZ" class="uk-select">
<option>Off</option>
<option>Coarse</option>
@ -129,44 +219,51 @@
</div>
<div class="uk-margin-small uk-margin-remove-bottom">
<label class="uk-form-label" for="form-stacked-text">Scan Style</label>
<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>
</select>
</div>
</div>
</div>
</li>
</ul>
<taskSubmitter v-if="scanCapture"
v-bind:submitURL="scanApiUri"
v-bind:submitData="scanPayload"
v-bind:submitLabel="'Start Scan'"
v-on:submit="onScanSubmit"
v-on:response="onScanResponse"
v-on:error="onScanError">
<taskSubmitter
v-if="scanCapture"
:submit-u-r-l="scanApiUri"
:submit-data="scanPayload"
:submit-label="'Start Scan'"
@submit="onScanSubmit"
@response="onScanResponse"
@error="onScanError"
>
</taskSubmitter>
<button v-else v-on:click="handleCapture()" class="uk-button uk-button-primary uk-form-small uk-margin uk-margin-remove-top uk-float-right uk-width-1-1">Capture</button>
<button
v-else
class="uk-button uk-button-primary uk-form-small uk-margin uk-margin-remove-top uk-float-right uk-width-1-1"
@click="handleCapture()"
>
Capture
</button>
</div>
</template>
<script>
import axios from 'axios'
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 {
name: 'paneCapture',
name: "PaneCapture",
components: {
tagList,
@ -174,26 +271,25 @@ export default {
taskSubmitter
},
data: function () {
data: function() {
return {
filename: '',
filename: "",
temporary: false,
fullResolution: false,
storeBayer: false,
resizeCapture: false,
captureNotes: "",
scanCapture: false,
scanDeltaZ: 'Medium',
scanStyle: 'Raster',
scanDeltaZ: "Medium",
scanStyle: "Raster",
scanStepSize: {
x: parseInt(0.8*this.$store.state.apiConfig.fov[0]),
y: parseInt(0.8*this.$store.state.apiConfig.fov[1]),
x: parseInt(0.8 * this.$store.state.apiConfig.fov[0]),
y: parseInt(0.8 * this.$store.state.apiConfig.fov[1]),
z: 50
},
scanSteps: {
x: 3,
y: 3,
x: 3,
y: 3,
z: 5
},
resizeDims: [640, 480],
@ -201,73 +297,27 @@ export default {
metadata: {
Client: `${process.env.PACKAGE.name}.${process.env.PACKAGE.version}`
}
}
},
methods: {
handleCapture: function() {
var payload = this.basePayload
// Do capture
this.newCaptureRequest(payload)
},
handleScan: function() {
var payload = this.scanPayload
// Do capture
this.newScanRequest(payload)
},
newCaptureRequest: function(params) {
// Send move request
axios.post(this.captureApiUri, params)
.then(response => {
// Flash the stream (capture animation)
this.$root.$emit('globalFlashStream')
// Update the global capture list
this.$root.$emit('globalUpdateCaptureList')
})
.catch(error => {
this.modalError(error) // Let mixin handle error
})
},
onScanSubmit: function(submitData) {
// We don't need to do anything on this event yet
console.log("onScanSubmit event triggered")
},
onScanResponse: function(responseData) {
console.log("Scan finished with response data: ", responseData)
this.modalNotify("Finished scan.")
},
onScanError: function(error) {
this.modalError(error)
}
};
},
computed: {
resizeClass: function () {
resizeClass: function() {
return {
'uk-disabled': !this.resizeCapture
}
"uk-disabled": !this.resizeCapture
};
},
captureApiUri: function () {
return this.$store.getters.uri + "/camera/capture"
captureApiUri: function() {
return this.$store.getters.uri + "/camera/capture";
},
scanApiUri: function () {
return this.$store.getters.uri + "/plugin/default/scan/tile"
scanApiUri: function() {
return this.$store.getters.uri + "/plugin/default/scan/tile";
},
basePayload: function () {
var payload = {}
basePayload: function() {
var payload = {};
// Filename
if (Boolean(this.filename)) {
payload.filename = this.filename
if (this.filename) {
payload.filename = this.filename;
}
// Basic boolean params
@ -280,28 +330,32 @@ export default {
payload.size = {
width: this.resizeDims[0],
height: this.resizeDims[1]
}
};
}
// Additional metadata
payload.metadata = this.metadata
payload.tags = this.tags
payload.metadata = this.metadata;
payload.tags = this.tags;
// Attach notes
if (this.captureNotes) {
payload.metadata['Notes'] = this.captureNotes
payload.metadata["Notes"] = this.captureNotes;
}
return payload
return payload;
},
scanPayload: function() {
var payload = this.basePayload
var payload = this.basePayload;
// Scan params
payload.grid = [this.scanSteps.x, this.scanSteps.y, this.scanSteps.z]
payload.step_size = [this.scanStepSize.x, this.scanStepSize.y, this.scanStepSize.z]
payload.style = this.scanStyle.toLowerCase()
payload.grid = [this.scanSteps.x, this.scanSteps.y, this.scanSteps.z];
payload.step_size = [
this.scanStepSize.x,
this.scanStepSize.y,
this.scanStepSize.z
];
payload.style = this.scanStyle.toLowerCase();
// Convert AF selector to dz
var afDeltas = {
@ -310,17 +364,57 @@ export default {
Medium: 30,
Fine: 10,
Fast: 1500
}
};
payload.autofocus_dz = afDeltas[this.scanDeltaZ]
payload.fast_autofocus = this.scanDeltaZ == "Fast"
payload.autofocus_dz = afDeltas[this.scanDeltaZ];
payload.fast_autofocus = this.scanDeltaZ == "Fast";
return payload
return payload;
}
},
methods: {
handleCapture: function() {
var payload = this.basePayload;
// Do capture
this.newCaptureRequest(payload);
},
}
handleScan: function() {
var payload = this.scanPayload;
}
// Do capture
this.newScanRequest(payload);
},
newCaptureRequest: function(params) {
// Send move request
axios
.post(this.captureApiUri, params)
.then(() => {
// Flash the stream (capture animation)
this.$root.$emit("globalFlashStream");
// Update the global capture list
this.$root.$emit("globalUpdateCaptureList");
})
.catch(error => {
this.modalError(error); // Let mixin handle error
});
},
onScanSubmit: function() {},
onScanResponse: function(responseData) {
console.log("Scan finished with response data: ", responseData);
this.modalNotify("Finished scan.");
},
onScanError: function(error) {
this.modalError(error);
}
}
};
</script>
<style lang="less">
@ -331,5 +425,4 @@ export default {
.deletable-label:hover {
background-color: #f0506e;
}
</style>
</style>

View file

@ -1,98 +1,144 @@
<template>
<div id="paneNavigate">
<ul uk-accordion="multiple: true; animation: false">
<li>
<a class="uk-accordion-title" href="#">Configure</a>
<div class="uk-accordion-content">
<div class="uk-child-width-1-2" uk-grid>
<div>
<label class="uk-form-label" for="form-stacked-text">x-y step size</label>
<div class="uk-form-controls">
<input v-model="stepXy" class="uk-input uk-form-width-medium uk-form-small" type="number" name="inputStepXy">
</div>
</div>
<div>
<label class="uk-form-label" for="form-stacked-text">z step size</label>
<div class="uk-form-controls">
<input v-model="stepZz" class="uk-input uk-form-width-medium uk-form-small" type="number" name="inputStepZz">
</div>
</div>
</div>
</div>
</li>
<li class="uk-open">
<a class="uk-accordion-title" href="#">Move-to</a>
<div class="uk-accordion-content">
<form @submit.prevent="handleSubmit">
<div class="uk-grid-small uk-child-width-1-3" uk-grid>
<div id="paneNavigate">
<ul uk-accordion="multiple: true; animation: false">
<li>
<a class="uk-accordion-title" href="#">Configure</a>
<div class="uk-accordion-content">
<div class="uk-child-width-1-2" uk-grid>
<div>
<label class="uk-form-label" for="form-stacked-text">x</label>
<label class="uk-form-label" for="form-stacked-text"
>x-y step size</label
>
<div class="uk-form-controls">
<input v-model="setPosition.x" class="uk-input uk-form-small" type="number" name="inputPositionX">
<input
v-model="stepXy"
class="uk-input uk-form-width-medium uk-form-small"
type="number"
name="inputStepXy"
/>
</div>
</div>
<div>
<label class="uk-form-label" for="form-stacked-text">y</label>
<label class="uk-form-label" for="form-stacked-text"
>z step size</label
>
<div class="uk-form-controls">
<input v-model="setPosition.y" class="uk-input uk-form-small" type="number" name="inputPositionY">
<input
v-model="stepZz"
class="uk-input uk-form-width-medium uk-form-small"
type="number"
name="inputStepZz"
/>
</div>
</div>
</div>
</div>
</li>
<li class="uk-open">
<a class="uk-accordion-title" href="#">Move-to</a>
<div class="uk-accordion-content">
<form @submit.prevent="handleSubmit">
<div class="uk-grid-small uk-child-width-1-3" uk-grid>
<div>
<label class="uk-form-label" for="form-stacked-text">x</label>
<div class="uk-form-controls">
<input
v-model="setPosition.x"
class="uk-input uk-form-small"
type="number"
name="inputPositionX"
/>
</div>
</div>
<div>
<label class="uk-form-label" for="form-stacked-text">y</label>
<div class="uk-form-controls">
<input
v-model="setPosition.y"
class="uk-input uk-form-small"
type="number"
name="inputPositionY"
/>
</div>
</div>
<div>
<label class="uk-form-label" for="form-stacked-text">z</label>
<div class="uk-form-controls">
<input
v-model="setPosition.z"
class="uk-input uk-form-small"
type="number"
name="inputPositionZx"
/>
</div>
</div>
</div>
<p>
<button
class="uk-button uk-button-primary uk-form-small uk-float-right uk-width-1-1"
>
Move
</button>
</p>
</form>
</div>
</li>
<!--Show autofocus if default plugin is enabled-->
<li
v-if="this.$store.state.apiState.plugin.includes('default_autofocus')"
class="uk-open"
>
<a class="uk-accordion-title" href="#">Autofocus</a>
<div class="uk-accordion-content">
<div v-if="isAutofocusing">
<progressBar />
</div>
<div
class="uk-grid-small uk-child-width-1-3"
:hidden="isAutofocusing"
uk-grid
>
<div>
<label class="uk-form-label" for="form-stacked-text">z</label>
<div class="uk-form-controls">
<input v-model="setPosition.z" class="uk-input uk-form-small" type="number" name="inputPositionZx">
</div>
<button
class="uk-button uk-button-default uk-form-small uk-float-right uk-width-1-1"
@click="runFastAutofocus(2000, 300)"
>
Fast
</button>
</div>
<div>
<button
class="uk-button uk-button-default uk-form-small uk-float-right uk-width-1-1"
@click="runAutofocus([-60, -30, 0, 30, 60])"
>
Medium
</button>
</div>
<div>
<button
class="uk-button uk-button-default uk-form-small uk-float-right uk-width-1-1"
@click="runAutofocus([-20, -10, 0, 10, 20])"
>
Fine
</button>
</div>
</div>
<p>
<button class="uk-button uk-button-primary uk-form-small uk-float-right uk-width-1-1">Move</button>
</p>
</form>
</div>
</li>
<!--Show autofocus if default plugin is enabled-->
<li v-if="this.$store.state.apiState.plugin.includes('default_autofocus')" class="uk-open">
<a class="uk-accordion-title" href="#">Autofocus</a>
<div class="uk-accordion-content">
<div v-if="isAutofocusing">
<progressBar/>
</div>
<div class="uk-grid-small uk-child-width-1-3" v-bind:hidden="isAutofocusing" uk-grid>
<div>
<button v-on:click="runFastAutofocus(2000, 300);" class="uk-button uk-button-default uk-form-small uk-float-right uk-width-1-1">Fast</button>
</div>
<div>
<button v-on:click="runAutofocus([-60,-30,0,30,60]);" class="uk-button uk-button-default uk-form-small uk-float-right uk-width-1-1">Medium</button>
</div>
<div>
<button v-on:click="runAutofocus([-20,-10,0,10,20]);" class="uk-button uk-button-default uk-form-small uk-float-right uk-width-1-1">Fine</button>
</div>
</div>
</div>
</li>
</ul>
</div>
</li>
</ul>
</div>
</template>
<script>
import axios from 'axios'
import progressBar from "../genericComponents/progressBar"
import axios from "axios";
import progressBar from "../genericComponents/progressBar";
// Key Codes
const keyCodes = {
@ -104,17 +150,17 @@ const keyCodes = {
down: 40,
enter: 13,
esc: 27
}
};
// Export main app
export default {
name: 'paneNavigate',
name: "PaneNavigate",
components: {
progressBar
},
data: function () {
data: function() {
return {
keysDown: {},
stepXy: 200,
@ -122,40 +168,57 @@ export default {
setPosition: this.$store.state.apiState.stage.position,
isAutofocusing: false,
moveLock: false
};
},
computed: {
positionApiUri: function() {
return this.$store.getters.uri + "/stage/position";
},
autofocusApiUri: function() {
return this.$store.getters.uri + "/plugin/default/autofocus/autofocus";
},
fastAutofocusApiUri: function() {
return (
this.$store.getters.uri + "/plugin/default/autofocus/fast_autofocus"
);
}
},
created: function () {
window.addEventListener('keydown', this.keyDownMonitor);
created: function() {
window.addEventListener("keydown", this.keyDownMonitor);
window.addEventListener("keyup", this.keyUpMonitor);
window.addEventListener('wheel', this.wheelMonitor);
window.addEventListener("wheel", this.wheelMonitor);
},
mounted() {
// A global signal listener to perform a move action
this.$root.$on('globalMoveEvent', (x, y, z, absolute) => {
this.moveRequest(x, y, z, absolute)
})
this.$root.$on("globalMoveEvent", (x, y, z, absolute) => {
this.moveRequest(x, y, z, absolute);
});
},
beforeDestroy () {
beforeDestroy() {
// Remove global signal listener to perform a move action
this.$root.$off('globalMoveEvent')
this.$root.$off("globalMoveEvent");
},
destroyed: function () {
window.removeEventListener('keydown', this.keyDownMonitor);
destroyed: function() {
window.removeEventListener("keydown", this.keyDownMonitor);
window.removeEventListener("keyup", this.keyUpMonitor);
window.removeEventListener('wheel', this.wheelMonitor);
window.removeEventListener("wheel", this.wheelMonitor);
},
methods: {
// Handle global mouse wheel events to be associated with navigation
wheelMonitor: function(event) {
// Only capture scroll if the event target's parent contains the "scrollTarget" class
if (event.target.parentNode.classList.contains("scrollTarget") || event.target.classList.contains("scrollTarget")) {
var z_rel = (event.deltaY)/100 * this.stepZz
this.moveRequest(0, 0, z_rel, false)
if (
event.target.parentNode.classList.contains("scrollTarget") ||
event.target.classList.contains("scrollTarget")
) {
var z_rel = (event.deltaY / 100) * this.stepZz;
this.moveRequest(0, 0, z_rel, false);
}
},
@ -164,9 +227,15 @@ export default {
this.keysDown[event.keyCode] = true; //Add key to array
// Convert keyCode dict into a list of key codes
var keyCodeList = Object.keys(keyCodes).map(function(key){return keyCodes[key];});
var keyCodeList = Object.keys(keyCodes).map(function(key) {
return keyCodes[key];
});
if (!(event.target instanceof HTMLInputElement) && !(event.target.classList.contains('lightbox-link')) && keyCodeList.includes(event.keyCode)) {
if (
!(event.target instanceof HTMLInputElement) &&
!event.target.classList.contains("lightbox-link") &&
keyCodeList.includes(event.keyCode)
) {
//console.log(this.keysDown)
// Calculate movement array
var x_rel = 0;
@ -192,7 +261,7 @@ export default {
}
// Make a position request
this.moveRequest(x_rel, y_rel, z_rel, false)
this.moveRequest(x_rel, y_rel, z_rel, false);
}
},
@ -200,105 +269,102 @@ export default {
delete this.keysDown[event.keyCode]; //Remove key from array
},
handleSubmit: function(event) {
handleSubmit: function() {
this.moveRequest(
this.setPosition.x,
this.setPosition.y,
this.setPosition.z,
true,
)
true
);
},
moveRequest: function(x, y, z, absolute) {
console.log(`Sending move request of ${x}, ${y}, ${z}`)
console.log(`Sending move request of ${x}, ${y}, ${z}`);
// If not movement-locked
if (!this.moveLock) {
// Lock move requests
this.moveLock = true
this.moveLock = true;
// Send move request
axios.post(this.positionApiUri, {
x: x,
y: y,
z: z,
absolute: absolute
})
.then(response => {
this.$store.dispatch('updateState'); // Update store state
this.setPosition = response.data.stage.position; // Update boxes from response
})
.catch(error => {
this.modalError(error) // Let mixin handle error
})
.then(() => {
this.moveLock = false // Release the move lock
})
axios
.post(this.positionApiUri, {
x: x,
y: y,
z: z,
absolute: absolute
})
.then(response => {
this.$store.dispatch("updateState"); // Update store state
this.setPosition = response.data.stage.position; // Update boxes from response
})
.catch(error => {
this.modalError(error); // Let mixin handle error
})
.then(() => {
this.moveLock = false; // Release the move lock
});
}
},
runAutofocus: function(dz) {
if (!this.moveLock) {
// Lock move requests
this.moveLock = true
this.isAutofocusing = true
axios.post(this.autofocusApiUri, {dz: dz})
.then(response => {
console.log("Autofocus Task ID: " + response.data.id)
// Start the store polling TaskId for success
return this.$store.dispatch('pollTask', [response.data.id, null, null])
})
.then(() => {
console.log("Successfully finished autofocus")
})
.catch(error => {
this.modalError(error) // Let mixin handle error
})
.finally(() => {
console.log("Cleaning up after autofocus.")
this.isAutofocusing = false;
this.moveLock = false // Release the move lock
})
this.moveLock = true;
this.isAutofocusing = true;
axios
.post(this.autofocusApiUri, { dz: dz })
.then(response => {
console.log("Autofocus Task ID: " + response.data.id);
// Start the store polling TaskId for success
return this.$store.dispatch("pollTask", [
response.data.id,
null,
null
]);
})
.then(() => {
console.log("Successfully finished autofocus");
})
.catch(error => {
this.modalError(error); // Let mixin handle error
})
.finally(() => {
console.log("Cleaning up after autofocus.");
this.isAutofocusing = false;
this.moveLock = false; // Release the move lock
});
}
},
runFastAutofocus: function(dz, backlash) {
if (!this.moveLock) {
// Lock move requests
this.moveLock = true
this.isAutofocusing = true
axios.post(this.fastAutofocusApiUri, {dz: dz, backlash: backlash})
.then(response => {
console.log("Autofocus Task ID: " + response.data.id)
// Start the store polling TaskId for success
return this.$store.dispatch('pollTask', [response.data.id, null, null])
})
.then(() => {
console.log("Successfully finished autofocus")
})
.catch(error => {
this.modalError(error) // Let mixin handle error
})
.finally(() => {
console.log("Cleaning up after autofocus.")
this.isAutofocusing = false
this.moveLock = false // Release the move lock
})
this.moveLock = true;
this.isAutofocusing = true;
axios
.post(this.fastAutofocusApiUri, { dz: dz, backlash: backlash })
.then(response => {
console.log("Autofocus Task ID: " + response.data.id);
// Start the store polling TaskId for success
return this.$store.dispatch("pollTask", [
response.data.id,
null,
null
]);
})
.then(() => {
console.log("Successfully finished autofocus");
})
.catch(error => {
this.modalError(error); // Let mixin handle error
})
.finally(() => {
console.log("Cleaning up after autofocus.");
this.isAutofocusing = false;
this.moveLock = false; // Release the move lock
});
}
}
},
computed: {
positionApiUri: function () {
return this.$store.getters.uri + "/stage/position"
},
autofocusApiUri: function () {
return this.$store.getters.uri + "/plugin/default/autofocus/autofocus"
},
fastAutofocusApiUri: function () {
return this.$store.getters.uri + "/plugin/default/autofocus/fast_autofocus"
}
}
}
</script>
};
</script>

View file

@ -1,17 +1,17 @@
<template>
<div id="panePlugins">
<h3>Plugins</h3>
<div class="uk-placeholder uk-text-center">Plugin support coming soon...</div>
<div class="uk-placeholder uk-text-center">
Plugin support coming soon...
</div>
</div>
</template>
<script>
// Export main app
export default {
name: 'panePlugins',
}
name: "PanePlugins"
};
</script>
<style lang="less">
</style>
<style lang="less"></style>

View file

@ -1,38 +1,35 @@
<template>
<div id="paneSettings">
<appSettings/>
<appSettings />
<ul uk-accordion="multiple: true; animation: false">
<li>
<a class="uk-accordion-title" href="#">Stream settings</a>
<div class="uk-accordion-content"><streamSettings/></div>
<div class="uk-accordion-content"><streamSettings /></div>
</li>
<li v-if="$store.getters.ready">
<a class="uk-accordion-title" href="#">Camera settings</a>
<div class="uk-accordion-content"><cameraSettings/></div>
<div class="uk-accordion-content"><cameraSettings /></div>
</li>
<li v-if="$store.getters.ready">
<a class="uk-accordion-title" href="#">Microscope settings</a>
<div class="uk-accordion-content"><microscopeSettings/></div>
<div class="uk-accordion-content"><microscopeSettings /></div>
</li>
</ul>
</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 streamSettings from "./settingsComponents/streamSettings.vue";
import microscopeSettings from "./settingsComponents/microscopeSettings.vue";
import cameraSettings from "./settingsComponents/cameraSettings.vue";
import appSettings from "./settingsComponents/appSettings.vue";
// Export main app
export default {
name: 'paneSettings',
name: "PaneSettings",
components: {
streamSettings,
@ -40,9 +37,7 @@ export default {
microscopeSettings,
appSettings
}
}
};
</script>
<style lang="less">
</style>
<style lang="less"></style>

View file

@ -1,70 +1,71 @@
<template>
<div class="host-input">
<div v-if="$store.getters.ready">
<div v-if="$store.state.apiConfig.name"><b>Device name:</b> <br> {{ $store.state.apiConfig.name }}</div>
<hr>
<div class="uk-margin-small-bottom">
<b>Host:</b>
<br>
{{ $store.state.host }}
<div class="host-input">
<div v-if="$store.getters.ready">
<div v-if="$store.state.apiConfig.name">
<b>Device name:</b> <br />
{{ $store.state.apiConfig.name }}
</div>
<hr />
<div class="uk-margin-small-bottom">
<b>Host:</b>
<br />
{{ $store.state.host }}
</div>
<div>
<b>Server version:</b> <br />
{{ $store.state.apiState.version }}
</div>
<hr />
<div class="uk-margin-small-bottom">
<b>Camera:</b>
<br />
<div v-if="$store.state.apiState.camera.board">
{{ $store.state.apiState.camera.board }}
</div>
<div v-else class="uk-text-danger"><b>No camera connected</b></div>
</div>
<div>
<b>Stage:</b>
<br />
<div v-if="$store.state.apiState.stage.board">
{{ $store.state.apiState.stage.board }}
</div>
<div v-else class="uk-text-danger"><b>No stage connected</b></div>
</div>
</div>
<div><b>Server version:</b> <br> {{ $store.state.apiState.version }}</div>
<hr>
<div class="uk-margin-small-bottom">
<b>Camera:</b>
<br>
<div v-if="$store.state.apiState.camera.board">{{ $store.state.apiState.camera.board }}</div>
<div v-else class="uk-text-danger"><b>No camera connected</b></div>
<div v-else-if="$store.state.waiting">
<progressBar></progressBar>
</div>
<div>
<b>Stage:</b>
<br>
<div v-if="$store.state.apiState.stage.board">{{ $store.state.apiState.stage.board }}</div>
<div v-else class="uk-text-danger"><b>No stage connected</b></div>
<div v-else-if="$store.state.error">
<b>Error:</b> {{ $store.state.error }}
</div>
<div v-else>No active connection</div>
</div>
<div v-else-if="$store.state.waiting">
<progressBar></progressBar>
</div>
<div v-else-if="$store.state.error"><b>Error:</b> {{ $store.state.error }}</div>
<div v-else>No active connection</div>
</div>
</template>
<script>
import progressBar from "../genericComponents/progressBar"
import progressBar from "../genericComponents/progressBar";
export default {
name: 'paneStatus',
name: "PaneStatus",
components: {
progressBar
},
data: function () {
return {
}
data: function() {
return {};
},
mounted() {
},
computed: {},
watch: {
},
watch: {},
methods: {
},
mounted() {},
computed: {
}
}
methods: {}
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="less">
</style>
<style scoped lang="less"></style>

View file

@ -1,31 +1,21 @@
<template>
<div id="appSettings">
<p><label><input v-model="darkMode" class="uk-checkbox" type="checkbox"> Enable dark theme</label></p>
</div>
<div id="appSettings">
<p>
<label
><input v-model="darkMode" class="uk-checkbox" type="checkbox" /> Enable
dark theme</label
>
</p>
</div>
</template>
<script>
// Export main app
export default {
name: 'appSettings',
name: "AppSettings",
data: function () {
return {}
},
mounted() {
// Try loading settings from localStorage. If null, don't change.
this.darkMode = this.getLocalStorageObj('darkMode') || this.darkMode
},
watch: {
darkMode(newdarkMode) {
console.log("Saving darkmode setting")
this.setLocalStorageObj('darkMode', this.darkMode)
}
data: function() {
return {};
},
computed: {
@ -34,13 +24,23 @@ export default {
return this.$store.state.globalSettings.darkMode;
},
set(value) {
this.$store.commit("changeSetting", ['darkMode', value]);
this.$store.commit("changeSetting", ["darkMode", value]);
}
}
}
},
}
watch: {
darkMode() {
console.log("Saving darkmode setting");
this.setLocalStorageObj("darkMode", this.darkMode);
}
},
mounted() {
// Try loading settings from localStorage. If null, don't change.
this.darkMode = this.getLocalStorageObj("darkMode") || this.darkMode;
}
};
</script>
<style lang="less">
</style>
<style lang="less"></style>

View file

@ -1,138 +1,181 @@
<template>
<div id="cameraSettings">
<form @submit.prevent="applyConfigRequest">
<div id="cameraSettings">
<form @submit.prevent="applyConfigRequest">
<div>
<label class="uk-form-label" for="form-stacked-text">Exposure time</label>
<label class="uk-form-label" for="form-stacked-text"
>Exposure time</label
>
<div class="uk-form-controls">
<input v-bind:value="displayShutterSpeed" v-on:input="shutterSpeed = $event.target.value" class="uk-input uk-form-small" type="number">
<input
:value="displayShutterSpeed"
class="uk-input uk-form-small"
type="number"
@input="shutterSpeed = $event.target.value"
/>
</div>
</div>
<div>
<label class="uk-form-label" for="form-stacked-text">Analogue gain</label>
<label class="uk-form-label" for="form-stacked-text"
>Analogue gain</label
>
<div class="uk-form-controls">
<input v-bind:value="displayAnalogGain" v-on:input="analogGain = $event.target.value" class="uk-input uk-form-small" type="number" step="0.1">
<input
:value="displayAnalogGain"
class="uk-input uk-form-small"
type="number"
step="0.1"
@input="analogGain = $event.target.value"
/>
</div>
</div>
<div>
<label class="uk-form-label" for="form-stacked-text">Digital gain</label>
<label class="uk-form-label" for="form-stacked-text"
>Digital gain</label
>
<div class="uk-form-controls">
<input v-bind:value="displayDigitalGain" v-on:input="digitalGain = $event.target.value" class="uk-input uk-form-small" type="number" step="0.1">
<input
:value="displayDigitalGain"
class="uk-input uk-form-small"
type="number"
step="0.1"
@input="digitalGain = $event.target.value"
/>
</div>
</div>
<button type="submit" class="uk-button uk-button-primary uk-form-small uk-float-right uk-margin-small uk-width-1-1">Apply Settings</button>
<!--Show auto calibrate if default plugin is enabled-->
<div v-if="this.$store.state.apiState.plugin.includes('default_camera_calibration')">
<taskSubmitter
v-bind:canTerminate="false"
v-bind:requiresConfirmation="true"
v-bind:confirmationMessage="'Start recalibration? This may take a while, and the microscope will be locked during this time.'"
v-bind:submitURL="recalibrateApiUri"
v-bind:submitLabel="'Auto-Calibrate (Task)'"
v-on:response="onRecalibrateResponse"
v-on:error="onRecalibrateError">
</taskSubmitter>
</div>
</form>
</div>
<button
type="submit"
class="uk-button uk-button-primary uk-form-small uk-float-right uk-margin-small uk-width-1-1"
>
Apply Settings
</button>
<!--Show auto calibrate if default plugin is enabled-->
<div
v-if="
this.$store.state.apiState.plugin.includes(
'default_camera_calibration'
)
"
>
<taskSubmitter
:can-terminate="false"
:requires-confirmation="true"
:confirmation-message="
'Start recalibration? This may take a while, and the microscope will be locked during this time.'
"
:submit-u-r-l="recalibrateApiUri"
:submit-label="'Auto-Calibrate (Task)'"
@response="onRecalibrateResponse"
@error="onRecalibrateError"
>
</taskSubmitter>
</div>
</form>
</div>
</template>
<script>
import axios from 'axios'
import taskSubmitter from "../../genericComponents/taskSubmitter"
import axios from "axios";
import taskSubmitter from "../../genericComponents/taskSubmitter";
// Export main app
export default {
name: 'microscopeSettings',
name: "MicroscopeSettings",
components: {
taskSubmitter
},
data: function () {
data: function() {
return {
shutterSpeed: this.$store.state.apiConfig.camera_settings.picamera_settings.shutter_speed,
analogGain: this.$store.state.apiConfig.camera_settings.picamera_settings.analog_gain,
digitalGain: this.$store.state.apiConfig.camera_settings.picamera_settings.digital_gain,
shutterSpeed: this.$store.state.apiConfig.camera_settings
.picamera_settings.shutter_speed,
analogGain: this.$store.state.apiConfig.camera_settings.picamera_settings
.analog_gain,
digitalGain: this.$store.state.apiConfig.camera_settings.picamera_settings
.digital_gain,
isCalibrating: false
};
},
computed: {
displayDigitalGain: function() {
return Number(this.digitalGain).toFixed(2);
},
displayAnalogGain: function() {
return Number(this.analogGain).toFixed(2);
},
displayShutterSpeed: function() {
return this.shutterSpeed != "0" ? this.shutterSpeed : "auto";
},
recalibrateApiUri: function() {
return (
this.$store.getters.uri +
"/plugin/default/camera_calibration/recalibrate"
);
},
configApiUri: function() {
return this.$store.getters.uri + "/config";
}
},
methods: {
updateInputValues: function () {
this.shutterSpeed = this.$store.state.apiConfig.camera_settings.picamera_settings.shutter_speed
this.digitalGain = this.$store.state.apiConfig.camera_settings.picamera_settings.digital_gain
this.analogGain = this.$store.state.apiConfig.camera_settings.picamera_settings.analog_gain
updateInputValues: function() {
this.shutterSpeed = this.$store.state.apiConfig.camera_settings.picamera_settings.shutter_speed;
this.digitalGain = this.$store.state.apiConfig.camera_settings.picamera_settings.digital_gain;
this.analogGain = this.$store.state.apiConfig.camera_settings.picamera_settings.analog_gain;
},
applyConfigRequest: function() {
console.log("Applying config to the microscope")
console.log("Applying config to the microscope");
var payload = {
camera_settings: {
picamera_settings: {}
}
}
};
//if (this.shutterSpeed != this.$store.state.apiConfig.picamera_settings.shutter_speed) {
payload.camera_settings.picamera_settings.shutter_speed = this.shutterSpeed
payload.camera_settings.picamera_settings.analog_gain = this.analogGain
payload.camera_settings.picamera_settings.digital_gain = this.digitalGain
payload.camera_settings.picamera_settings.shutter_speed = this.shutterSpeed;
payload.camera_settings.picamera_settings.analog_gain = this.analogGain;
payload.camera_settings.picamera_settings.digital_gain = this.digitalGain;
//};
// Send request
axios.post(this.configApiUri, payload)
.then(response => { return new Promise(r => setTimeout(r, 500))}) // why is there no built-in for this??!
.then(response => {
return this.$store.dispatch('updateConfig');
axios
.post(this.configApiUri, payload)
.then(() => {
return new Promise(r => setTimeout(r, 500));
}) // why is there no built-in for this??!
.then(() => {
return this.$store.dispatch("updateConfig");
})
.then(this.updateInputValues)
.then(r=>{console.log("Updated Config: ", payload)})
.catch(error => {
this.modalError(error) // Let mixin handle error
.then(() => {
console.log("Updated Config: ", payload);
})
.catch(error => {
this.modalError(error); // Let mixin handle error
});
},
onRecalibrateResponse: function() {
this.modalNotify("Finished recalibration.")
return new Promise(r => setTimeout(r, 500)) // wait 500ms before updating config, so it's fresh
this.modalNotify("Finished recalibration.");
return new Promise(r => setTimeout(r, 500)); // wait 500ms before updating config, so it's fresh
},
onRecalibrateError: function(error) {
this.modalError(error) // Let mixin handle error
}
},
computed: {
displayDigitalGain: function () {
return Number(this.digitalGain).toFixed(2)
},
displayAnalogGain: function () {
return Number(this.analogGain).toFixed(2)
},
displayShutterSpeed: function () {
return (this.shutterSpeed != "0") ? this.shutterSpeed : "auto"
},
recalibrateApiUri: function () {
return this.$store.getters.uri + "/plugin/default/camera_calibration/recalibrate"
},
configApiUri: function () {
return this.$store.getters.uri + "/config"
this.modalError(error); // Let mixin handle error
}
}
}
};
</script>
<style lang="less">
.center-spinner {
margin-left: auto;
margin-right: auto
margin-right: auto;
}
</style>

View file

@ -1,108 +1,133 @@
<template>
<div id="microscopeSettings">
<form @submit.prevent="applyConfigRequest">
<h4>Stage</h4>
<div id="microscopeSettings">
<form @submit.prevent="applyConfigRequest">
<h4>Stage</h4>
<label class="uk-form-label" for="form-stacked-text">Backlash compensation</label>
<div class="uk-grid-small uk-child-width-1-3" uk-grid>
<div>
<label class="uk-form-label" for="form-stacked-text">x</label>
<div class="uk-form-controls">
<input v-model="stageBacklash.x" class="uk-input uk-form-small" type="number">
<label class="uk-form-label" for="form-stacked-text"
>Backlash compensation</label
>
<div class="uk-grid-small uk-child-width-1-3" uk-grid>
<div>
<label class="uk-form-label" for="form-stacked-text">x</label>
<div class="uk-form-controls">
<input
v-model="stageBacklash.x"
class="uk-input uk-form-small"
type="number"
/>
</div>
</div>
<div>
<label class="uk-form-label" for="form-stacked-text">y</label>
<div class="uk-form-controls">
<input
v-model="stageBacklash.y"
class="uk-input uk-form-small"
type="number"
/>
</div>
</div>
<div>
<label class="uk-form-label" for="form-stacked-text">z</label>
<div class="uk-form-controls">
<input
v-model="stageBacklash.z"
class="uk-input uk-form-small"
type="number"
/>
</div>
</div>
</div>
<div>
<label class="uk-form-label" for="form-stacked-text">y</label>
<div class="uk-form-controls">
<input v-model="stageBacklash.y" class="uk-input uk-form-small" type="number">
</div>
</div>
<h4>Microscope</h4>
<div>
<label class="uk-form-label" for="form-stacked-text">z</label>
<div class="uk-form-controls">
<input v-model="stageBacklash.z" class="uk-input uk-form-small" type="number">
</div>
<label class="uk-form-label" for="form-stacked-text"
>Microscope name</label
>
<input
v-model="microscopeName"
class="uk-input uk-width-1-1 uk-form-small"
name="inputFilename"
placeholder="Leave blank for default"
/>
</div>
</div>
<h4>Microscope</h4>
<div>
<label class="uk-form-label" for="form-stacked-text">Microscope name</label>
<input v-model="microscopeName" class="uk-input uk-width-1-1 uk-form-small" name="inputFilename" placeholder="Leave blank for default">
</div>
<button type="submit" class="uk-button uk-button-primary uk-form-small uk-float-right uk-margin-small uk-width-1-1">Apply Settings</button>
</form>
</div>
<button
type="submit"
class="uk-button uk-button-primary uk-form-small uk-float-right uk-margin-small uk-width-1-1"
>
Apply Settings
</button>
</form>
</div>
</template>
<script>
import axios from 'axios'
import axios from "axios";
// Export main app
export default {
name: 'microscopeSettings',
name: "MicroscopeSettings",
data: function () {
data: function() {
return {
microscopeName: this.$store.state.apiConfig.name,
stageBacklash: this.$store.state.apiConfig.stage_settings.backlash
};
},
computed: {
configApiUri: function() {
return this.$store.getters.uri + "/config";
}
},
methods: {
updateInputValues: function () {
updateInputValues: function() {
this.microscopeName = this.$store.state.apiConfig.name;
this.stageBacklash = this.$store.state.apiConfig.stage_settings.backlash
this.stageBacklash = this.$store.state.apiConfig.stage_settings.backlash;
},
applyConfigRequest: function() {
var payload = {
stage_settings: {}
}
if (this.microscopeName != this.$store.state.apiConfig.name) {
payload.name = this.microscopeName
};
if (this.stageBacklash != this.$store.state.apiConfig.stage_settings.backlash) {
payload.stage_settings.backlash = this.stageBacklash
if (this.microscopeName != this.$store.state.apiConfig.name) {
payload.name = this.microscopeName;
}
console.log(payload)
if (
this.stageBacklash !=
this.$store.state.apiConfig.stage_settings.backlash
) {
payload.stage_settings.backlash = this.stageBacklash;
}
console.log(payload);
// Send request to update config
axios.post(this.configApiUri, payload)
.then(response => {
this.$store.dispatch('updateConfig');
this.updateInputValues
this.modalNotify("Microscope config applied.")
axios
.post(this.configApiUri, payload)
.then(() => {
this.$store.dispatch("updateConfig");
this.updateInputValues;
this.modalNotify("Microscope config applied.");
})
.catch(error => {
this.modalError(error) // Let mixin handle error
})
}
},
computed: {
configApiUri: function () {
return this.$store.getters.uri + "/config"
this.modalError(error); // Let mixin handle error
});
}
}
}
};
</script>
<style lang="less">
.center-spinner {
margin-left: auto;
margin-right: auto
margin-right: auto;
}
</style>

View file

@ -1,34 +1,49 @@
<template>
<div id="streamSettings">
<div id="streamSettings">
<p>
<label
><input v-model="disableStream" class="uk-checkbox" type="checkbox" />
Disable live stream</label
>
</p>
<p><label><input v-model="disableStream" class="uk-checkbox" type="checkbox"> Disable live stream</label></p>
<div class="uk-child-width-1-2" uk-grid>
<p><label v-bind:class="[{'uk-disabled': !this.$store.getters.ready}]"><input v-model="autoGpuPreview" class="uk-checkbox" type="checkbox"> GPU preview</label></p>
<p><label v-bind:class="[{'uk-disabled': !this.$store.getters.ready}]"><input v-model="trackWindow" class="uk-checkbox" type="checkbox"> Track window</label></p>
<div class="uk-child-width-1-2" uk-grid>
<p>
<label :class="[{ 'uk-disabled': !this.$store.getters.ready }]"
><input
v-model="autoGpuPreview"
class="uk-checkbox"
type="checkbox"
/>
GPU preview</label
>
</p>
<p>
<label :class="[{ 'uk-disabled': !this.$store.getters.ready }]"
><input v-model="trackWindow" class="uk-checkbox" type="checkbox" />
Track window</label
>
</p>
</div>
</div>
</div>
</template>
<script>
// Export main app
export default {
name: 'streamSettings',
name: "StreamSettings",
data: function () {
return {}
data: function() {
return {};
},
computed: {
disableStream: {
get() {
return this.$store.state.globalSettings.disableStream;
},
set(value) {
this.$store.commit("changeSetting", ['disableStream', value]);
this.$store.commit("changeSetting", ["disableStream", value]);
}
},
@ -37,8 +52,8 @@ export default {
return this.$store.state.globalSettings.autoGpuPreview;
},
set(value) {
this.$store.commit("changeSetting", ['autoGpuPreview', value]);
this.$root.$emit('globalTogglePreview', value)
this.$store.commit("changeSetting", ["autoGpuPreview", value]);
this.$root.$emit("globalTogglePreview", value);
}
},
@ -47,13 +62,11 @@ export default {
return this.$store.state.globalSettings.trackWindow;
},
set(value) {
this.$store.commit("changeSetting", ['trackWindow', value]);
this.$store.commit("changeSetting", ["trackWindow", value]);
}
}
}
}
};
</script>
<style lang="less">
</style>
<style lang="less"></style>

View file

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

View file

@ -6,14 +6,23 @@
<script>
export default {
name: 'htmlBlock',
name: "HtmlBlock",
props: [
'label',
'name',
'content'
]
}
props: {
label: {
type: String,
required: true
},
name: {
type: String,
required: true
},
content: {
type: String,
required: true
}
}
};
</script>
<style scoped></style>
<style scoped></style>

View file

@ -2,83 +2,117 @@
<div>
<form @submit.prevent="handleMetadataSubmit">
<div class="uk-margin-remove uk-flex uk-flex-middle">
<div class="uk-margin-remove-top uk-padding-remove uk-grid-small uk-width-expand" uk-grid>
<div class="uk-margin-remove uk-width-1-2"><input v-model="newMetadata.key" class="uk-input uk-form-width-small uk-form-small" type="text" name="flavor" placeholder="Key"></div>
<div class="uk-margin-remove uk-width-1-2"><input v-model="newMetadata.value" class="uk-input uk-form-width-small uk-form-small" type="text" name="flavor" placeholder="Value"></div>
<div
class="uk-margin-remove-top uk-padding-remove uk-grid-small uk-width-expand"
uk-grid
>
<div class="uk-margin-remove uk-width-1-2">
<input
v-model="newMetadata.key"
class="uk-input uk-form-width-small uk-form-small"
type="text"
name="flavor"
placeholder="Key"
/>
</div>
<div class="uk-margin-remove uk-width-1-2">
<input
v-model="newMetadata.value"
class="uk-input uk-form-width-small uk-form-small"
type="text"
name="flavor"
placeholder="Value"
/>
</div>
</div>
<a href="#" v-on:click="handleMetadataSubmit()" class="uk-icon uk-margin-left"><i class="material-icons">add_circle</i></a>
<a
href="#"
class="uk-icon uk-margin-left"
@click="handleMetadataSubmit()"
><i class="material-icons">add_circle</i></a
>
</div>
</form>
<div v-for="(val, key) in value" :key="key" class="uk-width-1-1 uk-margin-small uk-margin-remove-left uk-margin-remove-right uk-flex uk-flex-middle">
<div
v-for="(val, key) in value"
:key="key"
class="uk-width-1-1 uk-margin-small uk-margin-remove-left uk-margin-remove-right uk-flex uk-flex-middle"
>
<div class="uk-margin-remove-top uk-padding-remove uk-width-expand">
<labelInput :label="key" :value="value[key]" @input="value[key]=$event"/>
<labelInput
:label="key"
:value="value[key]"
@input="value[key] = $event"
/>
</div>
<a href="#" v-on:click="delMetadataKey(key)" class="uk-icon uk-width-auto"><i class="material-icons">delete</i></a>
<a href="#" class="uk-icon uk-width-auto" @click="delMetadataKey(key)"
><i class="material-icons">delete</i></a
>
</div>
</div>
</template>
<script>
import labelInput from "../fieldComponents/labelInput"
import labelInput from "../fieldComponents/labelInput";
export default {
name: 'keyvalList',
name: "KeyvalList",
components: {
labelInput
},
data: function () {
props: {
value: {
type: Object,
required: true
}
},
data: function() {
return {
newMetadata: {
key: "",
value: ""
}
}
};
},
props: [
'value'
],
methods: {
handleMetadataSubmit: function () {
var newSelected = {}
handleMetadataSubmit: function() {
var newSelected = {};
if (this.value != null) {
Object.assign(newSelected, this.value)
Object.assign(newSelected, this.value);
}
newSelected[this.newMetadata.key] = this.newMetadata.value
newSelected[this.newMetadata.key] = this.newMetadata.value;
this.newMetadata.key = "";
this.newMetadata.value = "";
this.$emit('input', newSelected)
this.$emit("input", newSelected);
},
delMetadataKey: function (key) {
var newSelected = {}
delMetadataKey: function(key) {
var newSelected = {};
if (this.value != null) {
Object.assign(newSelected, this.value)
Object.assign(newSelected, this.value);
}
this.$delete(newSelected, key)
this.$delete(newSelected, key);
this.$emit('input', newSelected)
this.$emit("input", newSelected);
},
modifyValue: function (e, v) {
console.log(e)
console.log(v)
modifyValue: function(e, v) {
console.log(e);
console.log(v);
}
}
}
};
</script>
<style scoped></style>
<style scoped></style>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,38 +1,37 @@
<template>
<div class="progress uk-margin-top uk-margin-horizontal-remove uk-padding-remove">
<div class="indeterminate"></div>
</div>
<div
class="progress uk-margin-top uk-margin-horizontal-remove uk-padding-remove"
>
<div class="indeterminate"></div>
</div>
</template>
<script>
export default {
name: 'progressBar',
name: "ProgressBar",
props: {},
methods: {
setThisTab(event, value) {
this.$emit('set-tab', event, this.id);
computed: {
tooltipOptions: function() {
var title = this.id.charAt(0).toUpperCase() + this.id.slice(1);
return `pos: right; title: ${title}; delay: 500`;
},
classObject: function() {
return {
"tabicon-active": this.currentTab == this.id,
"uk-disabled": this.requireConnection && !this.$store.getters.ready
};
}
},
computed: {
tooltipOptions: function () {
var title = this.id.charAt(0).toUpperCase() + this.id.slice(1);
return `pos: right; title: ${title}; delay: 500`
},
classObject: function () {
return {
'tabicon-active': this.currentTab == this.id,
'uk-disabled': (this.requireConnection && !this.$store.getters.ready)
}
methods: {
setThisTab(event) {
this.$emit("set-tab", event, this.id);
}
}
}
};
</script>
<style lang="less" scoped>
@ -47,7 +46,7 @@ export default {
border-radius: 2px;
background-clip: padding-box;
margin: 0.5rem 0 1rem 0;
overflow: hidden;
overflow: hidden;
}
.progress .determinate {
@ -55,88 +54,104 @@ export default {
background-color: inherit;
top: 0;
bottom: 0;
transition: width .3s linear;
transition: width 0.3s linear;
}
.progress .indeterminate, .progress .determinate {
background-color: @global-primary-background;
.progress .indeterminate,
.progress .determinate {
background-color: @global-primary-background;
}
.hook-inverse() {
.progress .indeterminate, .progress .determinate{
background-color: @inverse-primary-muted-color;
}
.progress .indeterminate,
.progress .determinate {
background-color: @inverse-primary-muted-color;
}
}
.progress .indeterminate:before {
content: '';
content: "";
position: absolute;
background-color: inherit;
top: 0;
left: 0;
bottom: 0;
will-change: left, right;
-webkit-animation: indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite;
animation: indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite;
-webkit-animation: indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395)
infinite;
animation: indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite;
}
.progress .indeterminate:after {
content: '';
content: "";
position: absolute;
background-color: inherit;
top: 0;
left: 0;
bottom: 0;
will-change: left, right;
-webkit-animation: indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;
animation: indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;
-webkit-animation: indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1)
infinite;
animation: indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1)
infinite;
-webkit-animation-delay: 1.15s;
animation-delay: 1.15s;
animation-delay: 1.15s;
}
@-webkit-keyframes indeterminate {
0% {
left: -35%;
right: 100%; }
right: 100%;
}
60% {
left: 100%;
right: -90%; }
right: -90%;
}
100% {
left: 100%;
right: -90%; }
right: -90%;
}
}
@keyframes indeterminate {
0% {
left: -35%;
right: 100%; }
right: 100%;
}
60% {
left: 100%;
right: -90%; }
right: -90%;
}
100% {
left: 100%;
right: -90%; }
right: -90%;
}
}
@-webkit-keyframes indeterminate-short {
0% {
left: -200%;
right: 100%; }
right: 100%;
}
60% {
left: 107%;
right: -8%; }
right: -8%;
}
100% {
left: 107%;
right: -8%; }
right: -8%;
}
}
@keyframes indeterminate-short {
0% {
left: -200%;
right: 100%; }
right: 100%;
}
60% {
left: 107%;
right: -8%; }
right: -8%;
}
100% {
left: 107%;
right: -8%; }
right: -8%;
}
}
</style>
</style>

View file

@ -1,36 +1,36 @@
<template>
<div
<div
v-if="!(this.requireConnection && !this.$store.getters.ready)"
v-bind:hidden="currentTab!=id"
:hidden="currentTab != id"
class="uk-width-expand"
>
<div class="section-heading">{{ id }}</div>
<div class="section-content"><slot></slot></div>
</div>
</template>
<script>
export default {
name: 'tabContent',
name: "TabContent",
props: {
id: String,
currentTab: String,
requireConnection: Boolean,
id: {
type: String,
required: true
},
currentTab: {
type: String,
required: true
},
requireConnection: Boolean
},
computed: {},
methods: {},
computed: {}
}
methods: {}
};
</script>
<style lang="less" scoped>
.section-heading {
display: block;
font-size: 12px;
@ -39,8 +39,8 @@ export default {
cursor: default;
}
.section-content, .section-heading {
.section-content,
.section-heading {
padding: 9px 10px;
}
</style>
</style>

View file

@ -1,48 +1,51 @@
<template>
<a
href="#"
<a
href="#"
class="uk-link"
:class="classObject"
:class="classObject"
:uk-tooltip="tooltipOptions"
@click="setThisTab">
@click="setThisTab"
>
<slot></slot>
</a>
</template>
<script>
export default {
name: 'tabIcon',
name: "TabIcon",
props: {
id: String,
currentTab: String,
id: {
type: String,
required: true
},
currentTab: {
type: String,
required: true
},
requireConnection: Boolean
},
methods: {
setThisTab(event, value) {
this.$emit('set-tab', event, this.id);
computed: {
tooltipOptions: function() {
var title = this.id.charAt(0).toUpperCase() + this.id.slice(1);
return `pos: right; title: ${title}; delay: 500`;
},
classObject: function() {
return {
"tabicon-active": this.currentTab == this.id,
"uk-disabled": this.requireConnection && !this.$store.getters.ready
};
}
},
computed: {
tooltipOptions: function () {
var title = this.id.charAt(0).toUpperCase() + this.id.slice(1);
return `pos: right; title: ${title}; delay: 500`
},
classObject: function () {
return {
'tabicon-active': this.currentTab == this.id,
'uk-disabled': (this.requireConnection && !this.$store.getters.ready)
}
methods: {
setThisTab(event) {
this.$emit("set-tab", event, this.id);
}
}
}
};
</script>
<style lang="less" scoped>
@ -54,9 +57,8 @@ export default {
}
.hook-inverse() {
.tabicon-active {
color: @inverse-primary-muted-color !important;
}
.tabicon-active {
color: @inverse-primary-muted-color !important;
}
}
</style>
</style>

View file

@ -1,21 +1,31 @@
<template>
<div>
<div>
<div
class="progress uk-margin-top uk-margin-horizontal-remove uk-padding-remove"
>
<div
v-if="progress"
class="determinate"
:style="barWidthFromProgress"
></div>
<div v-else class="indeterminate"></div>
</div>
<div class="progress uk-margin-top uk-margin-horizontal-remove uk-padding-remove">
<div v-if="progress" class="determinate" :style="barWidthFromProgress"></div>
<div v-else class="indeterminate"></div>
<button
type="button"
class="uk-button uk-button-danger uk-form-small uk-float-right uk-width-1-1"
@click="terminateTask()"
>
Terminate
</button>
</div>
<button type="button" v-on:click="terminateTask()" class="uk-button uk-button-danger uk-form-small uk-float-right uk-width-1-1">Terminate</button>
</div>
</template>
<script>
import axios from 'axios'
import axios from "axios";
export default {
name: 'taskProgress',
name: "TaskProgress",
props: {
taskId: {
@ -26,54 +36,54 @@ export default {
type: Number,
required: false,
default: 500
},
}
},
data: function () {
data: function() {
return {
polling: null,
polling: null,
progress: null
};
},
computed: {
barWidthFromProgress: function() {
var progress = this.progress <= 100 ? this.progress : 100;
var styleString = `width: ${progress}%`;
return styleString;
}
},
created() {
this.polling = setInterval(() => {
this.pollProgress()
}, this.pollInterval)
this.pollProgress();
}, this.pollInterval);
},
beforeDestroy () {
clearInterval(this.polling)
beforeDestroy() {
clearInterval(this.polling);
},
methods: {
pollProgress: function() {
console.log("Starting progress polling")
axios.get(`${this.$store.getters.uri}/task/${this.taskId}`)
.then(response => {
console.log("PROGRESS RESPONSE: ", response.data.progress)
this.progress = response.data.progress
})
console.log("Starting progress polling");
axios
.get(`${this.$store.getters.uri}/task/${this.taskId}`)
.then(response => {
console.log("PROGRESS RESPONSE: ", response.data.progress);
this.progress = response.data.progress;
});
},
terminateTask: function() {
axios.delete(`${this.$store.getters.uri}/task/${this.taskId}`)
.then(response => {
console.log("TERMINATION RESPONSE: ", response.data)
})
},
},
computed: {
barWidthFromProgress: function () {
var progress = ((this.progress <= 100) ? this.progress : 100)
var styleString = `width: ${progress}%`
return styleString
},
axios
.delete(`${this.$store.getters.uri}/task/${this.taskId}`)
.then(response => {
console.log("TERMINATION RESPONSE: ", response.data);
});
}
}
}
};
</script>
<style lang="less" scoped>
@ -88,7 +98,7 @@ export default {
border-radius: 2px;
background-clip: padding-box;
margin: 0.5rem 0 1rem 0;
overflow: hidden;
overflow: hidden;
}
.progress .determinate {
@ -96,88 +106,104 @@ export default {
background-color: inherit;
top: 0;
bottom: 0;
transition: width .3s linear;
transition: width 0.3s linear;
}
.progress .indeterminate, .progress .determinate {
background-color: @global-primary-background;
.progress .indeterminate,
.progress .determinate {
background-color: @global-primary-background;
}
.hook-inverse() {
.progress .indeterminate, .progress .determinate{
background-color: @inverse-primary-muted-color;
}
.progress .indeterminate,
.progress .determinate {
background-color: @inverse-primary-muted-color;
}
}
.progress .indeterminate:before {
content: '';
content: "";
position: absolute;
background-color: inherit;
top: 0;
left: 0;
bottom: 0;
will-change: left, right;
-webkit-animation: indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite;
animation: indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite;
-webkit-animation: indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395)
infinite;
animation: indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite;
}
.progress .indeterminate:after {
content: '';
content: "";
position: absolute;
background-color: inherit;
top: 0;
left: 0;
bottom: 0;
will-change: left, right;
-webkit-animation: indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;
animation: indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;
-webkit-animation: indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1)
infinite;
animation: indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1)
infinite;
-webkit-animation-delay: 1.15s;
animation-delay: 1.15s;
animation-delay: 1.15s;
}
@-webkit-keyframes indeterminate {
0% {
left: -35%;
right: 100%; }
right: 100%;
}
60% {
left: 100%;
right: -90%; }
right: -90%;
}
100% {
left: 100%;
right: -90%; }
right: -90%;
}
}
@keyframes indeterminate {
0% {
left: -35%;
right: 100%; }
right: 100%;
}
60% {
left: 100%;
right: -90%; }
right: -90%;
}
100% {
left: 100%;
right: -90%; }
right: -90%;
}
}
@-webkit-keyframes indeterminate-short {
0% {
left: -200%;
right: 100%; }
right: 100%;
}
60% {
left: 107%;
right: -8%; }
right: -8%;
}
100% {
left: 107%;
right: -8%; }
right: -8%;
}
}
@keyframes indeterminate-short {
0% {
left: -200%;
right: 100%; }
right: 100%;
}
60% {
left: 107%;
right: -8%; }
right: -8%;
}
100% {
left: 107%;
right: -8%; }
right: -8%;
}
}
</style>
</style>

View file

@ -1,25 +1,43 @@
<template>
<div class="uk-margin-horizontal-remove uk-padding-remove">
<div
class="uk-margin-horizontal-remove uk-margin-small-top uk-padding-remove"
>
<div v-if="taskRunning" ref="isPollingElement">
<div class="progress">
<div
v-if="progress"
class="determinate"
:style="barWidthFromProgress"
></div>
<div v-else class="indeterminate"></div>
</div>
<div v-if="taskRunning" ref="isPollingElement">
<div class="progress">
<div v-if="progress" class="determinate" :style="barWidthFromProgress"></div>
<div v-else class="indeterminate"></div>
<button
v-if="canTerminate"
type="button"
class="uk-button uk-button-danger uk-form-small uk-margin uk-float-right uk-width-1-1"
@click="terminateTask()"
>
Terminate
</button>
</div>
<button v-if="canTerminate" type="button" v-on:click="terminateTask()" class="uk-button uk-button-danger uk-form-small uk-margin uk-float-right uk-width-1-1">Terminate</button>
<button
type="button"
:hidden="taskRunning"
class="uk-button uk-button-primary uk-form-small uk-margin uk-float-right uk-width-1-1"
@click="bootstrapTask()"
>
{{ submitLabel }}
</button>
</div>
<button type="button" v-on:click="bootstrapTask()" v-bind:hidden="taskRunning" class="uk-button uk-button-primary uk-form-small uk-margin uk-float-right uk-width-1-1">{{ submitLabel }}</button>
</div>
</template>
<script>
import axios from 'axios'
import axios from "axios";
export default {
name: 'taskSubmitter',
name: "TaskSubmitter",
props: {
submitURL: {
@ -63,148 +81,152 @@ export default {
}
},
data: function () {
data: function() {
return {
taskId: null,
polling: null,
polling: null,
progress: null,
taskRunning: false
};
},
computed: {
barWidthFromProgress: function() {
var progress = this.progress <= 100 ? this.progress : 100;
var styleString = `width: ${progress}%`;
return styleString;
}
},
created() {
},
created() {},
beforeDestroy () {
},
beforeDestroy() {},
methods: {
bootstrapTask: function() {
if (this.requiresConfirmation) {
this.modalConfirm(this.confirmationMessage)
.then(() => {
this.startTask()
}, () => {})
}
else {
this.startTask()
this.modalConfirm(this.confirmationMessage).then(
() => {
this.startTask();
},
() => {}
);
} else {
this.startTask();
}
},
startTask: function() {
console.log("Task start clicked")
this.$emit('submit', this.submitData)
console.log("Task start clicked");
this.$emit("submit", this.submitData);
// Send a request to start a task
axios.post(this.submitURL, this.submitData)
// Get the returned Task ID
.then(response => {
// Fetch the task ID
console.log("Task ID: " + response.data.id)
this.taskId = response.data.id
// Start the store polling TaskId for success
this.taskRunning = true
// Return the poll-task promise (starts polling the task)
return this.pollTask(this.taskId, this.pollTimeout, this.pollInterval)
})
.then(response => {
// Do something with the final response
console.log("Emitting onResponse: ", response)
this.$emit('response', response)
})
.catch(error => {
if (!error) {
error = Error("Unknown error")
}
console.log("Emitting onError: ", error)
this.$emit('error', error)
})
.finally(() => {
console.log("Cleaning up after task.")
// Reset taskRunning and taskId
this.taskRunning = false
this.taskId = null
// Update the form data if we're self-updating
if (this.selfUpdate) {
this.getFormData()
}
})
axios
.post(this.submitURL, this.submitData)
// Get the returned Task ID
.then(response => {
// Fetch the task ID
console.log("Task ID: " + response.data.id);
this.taskId = response.data.id;
// Start the store polling TaskId for success
this.taskRunning = true;
// Return the poll-task promise (starts polling the task)
return this.pollTask(
this.taskId,
this.pollTimeout,
this.pollInterval
);
})
.then(response => {
// Do something with the final response
console.log("Emitting onResponse: ", response);
this.$emit("response", response);
})
.catch(error => {
if (!error) {
error = Error("Unknown error");
}
console.log("Emitting onError: ", error);
this.$emit("error", error);
})
.finally(() => {
console.log("Cleaning up after task.");
// Reset taskRunning and taskId
this.taskRunning = false;
this.taskId = null;
// Update the form data if we're self-updating
if (this.selfUpdate) {
this.getFormData();
}
});
},
pollTask: function(taskId, timeout, interval) {
var endTime = Number(new Date()) + (timeout*1000 || 30000);
interval = interval*1000 || 500;
var endTime = Number(new Date()) + (timeout * 1000 || 30000);
interval = interval * 1000 || 500;
var checkCondition = (resolve, reject) => {
// If the condition is met, we're done!
axios.get(`${this.$store.getters.uri}/task/${taskId}`)
.then(response => {
console.log(response.data.status)
var result = response.data.status
// If the task ends with success
if(result == 'success') {
resolve(response.data)
}
// If task ends with an error
else if (result == 'error') {
// Pass the error string back with reject
reject(new Error(response.data.return))
}
// If task ends with termination
else if (result == 'terminated') {
// Pass a generic termination error back with reject
reject(new Error("Task terminated"))
}
// If task ends in any other way
else if (result != 'running') {
// Pass status string as error
reject(new Error(result))
}
// If the condition isn't met but the timeout hasn't elapsed, go again
else if (Number(new Date()) < endTime) {
// Since the task is still running, we can update the progress bar
this.progress = response.data.progress
// Check again after timeout
setTimeout(checkCondition, interval, resolve, reject)
}
// Didn't match and too much time, reject!
else {
reject(new Error('Polling timed out'))
}
})
// If the condition is met, we're done!
axios
.get(`${this.$store.getters.uri}/task/${taskId}`)
.then(response => {
console.log(response.data.status);
var result = response.data.status;
// If the task ends with success
if (result == "success") {
resolve(response.data);
}
// If task ends with an error
else if (result == "error") {
// Pass the error string back with reject
reject(new Error(response.data.return));
}
// If task ends with termination
else if (result == "terminated") {
// Pass a generic termination error back with reject
reject(new Error("Task terminated"));
}
// If task ends in any other way
else if (result != "running") {
// Pass status string as error
reject(new Error(result));
}
// If the condition isn't met but the timeout hasn't elapsed, go again
else if (Number(new Date()) < endTime) {
// Since the task is still running, we can update the progress bar
this.progress = response.data.progress;
// Check again after timeout
setTimeout(checkCondition, interval, resolve, reject);
}
// Didn't match and too much time, reject!
else {
reject(new Error("Polling timed out"));
}
});
};
return new Promise(checkCondition);
},
pollProgress: function() {
console.log("Starting progress polling")
axios.get(`${this.$store.getters.uri}/task/${this.taskId}`)
.then(response => {
console.log("PROGRESS RESPONSE: ", response.data.progress)
this.progress = response.data.progress
})
console.log("Starting progress polling");
axios
.get(`${this.$store.getters.uri}/task/${this.taskId}`)
.then(response => {
console.log("PROGRESS RESPONSE: ", response.data.progress);
this.progress = response.data.progress;
});
},
terminateTask: function() {
axios.delete(`${this.$store.getters.uri}/task/${this.taskId}`)
.then(response => {
console.log("TERMINATION RESPONSE: ", response.data)
})
},
},
computed: {
barWidthFromProgress: function () {
var progress = ((this.progress <= 100) ? this.progress : 100)
var styleString = `width: ${progress}%`
return styleString
},
axios
.delete(`${this.$store.getters.uri}/task/${this.taskId}`)
.then(response => {
console.log("TERMINATION RESPONSE: ", response.data);
});
}
}
}
};
</script>
<style lang="less" scoped>
@ -219,7 +241,7 @@ export default {
border-radius: 2px;
background-clip: padding-box;
margin: 0.5rem 0 1rem 0;
overflow: hidden;
overflow: hidden;
}
.progress .determinate {
@ -227,88 +249,104 @@ export default {
background-color: inherit;
top: 0;
bottom: 0;
transition: width .3s linear;
transition: width 0.3s linear;
}
.progress .indeterminate, .progress .determinate {
background-color: @global-primary-background;
.progress .indeterminate,
.progress .determinate {
background-color: @global-primary-background;
}
.hook-inverse() {
.progress .indeterminate, .progress .determinate{
background-color: @inverse-primary-muted-color;
}
.progress .indeterminate,
.progress .determinate {
background-color: @inverse-primary-muted-color;
}
}
.progress .indeterminate:before {
content: '';
content: "";
position: absolute;
background-color: inherit;
top: 0;
left: 0;
bottom: 0;
will-change: left, right;
-webkit-animation: indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite;
animation: indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite;
-webkit-animation: indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395)
infinite;
animation: indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite;
}
.progress .indeterminate:after {
content: '';
content: "";
position: absolute;
background-color: inherit;
top: 0;
left: 0;
bottom: 0;
will-change: left, right;
-webkit-animation: indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;
animation: indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;
-webkit-animation: indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1)
infinite;
animation: indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1)
infinite;
-webkit-animation-delay: 1.15s;
animation-delay: 1.15s;
animation-delay: 1.15s;
}
@-webkit-keyframes indeterminate {
0% {
left: -35%;
right: 100%; }
right: 100%;
}
60% {
left: 100%;
right: -90%; }
right: -90%;
}
100% {
left: 100%;
right: -90%; }
right: -90%;
}
}
@keyframes indeterminate {
0% {
left: -35%;
right: 100%; }
right: 100%;
}
60% {
left: 100%;
right: -90%; }
right: -90%;
}
100% {
left: 100%;
right: -90%; }
right: -90%;
}
}
@-webkit-keyframes indeterminate-short {
0% {
left: -200%;
right: 100%; }
right: 100%;
}
60% {
left: 107%;
right: -8%; }
right: -8%;
}
100% {
left: 107%;
right: -8%; }
right: -8%;
}
}
@keyframes indeterminate-short {
0% {
left: -200%;
right: 100%; }
right: 100%;
}
60% {
left: 107%;
right: -8%; }
right: -8%;
}
100% {
left: 107%;
right: -8%; }
right: -8%;
}
}
</style>
</style>

View file

@ -1,95 +1,147 @@
<template>
<div id="panel-left" class="uk-margin-remove uk-padding-remove uk-height-1-1" uk-grid>
<div
id="panel-left"
class="uk-margin-remove uk-padding-remove uk-height-1-1"
uk-grid
>
<!-- Vertical tab bar -->
<div id="switcher-left" class="uk-flex uk-flex-column uk-padding-remove uk-width-auto uk-height-1-1">
<tabIcon id="status" :requireConnection="false" :currentTab="currentTab" @set-tab="setTab">
<div
id="switcher-left"
class="uk-flex uk-flex-column uk-padding-remove uk-width-auto uk-height-1-1"
>
<tabIcon
id="status"
:require-connection="false"
:current-tab="currentTab"
@set-tab="setTab"
>
<i class="material-icons">bug_report</i>
</tabIcon>
<tabIcon id="navigate" :requireConnection="true" :currentTab="currentTab" @set-tab="setTab">
<i class="material-icons">gamepad</i>
<tabIcon
id="navigate"
:require-connection="true"
:current-tab="currentTab"
@set-tab="setTab"
>
<i class="material-icons">gamepad</i>
</tabIcon>
<tabIcon id="capture" :requireConnection="true" :currentTab="currentTab" @set-tab="setTab">
<tabIcon
id="capture"
:require-connection="true"
:current-tab="currentTab"
@set-tab="setTab"
>
<i class="material-icons">camera_alt</i>
</tabIcon>
<tabIcon id="settings" :requireConnection="false" :currentTab="currentTab" @set-tab="setTab">
<i class="material-icons">settings</i>
<tabIcon
id="settings"
:require-connection="false"
:current-tab="currentTab"
@set-tab="setTab"
>
<i class="material-icons">settings</i>
</tabIcon>
<hr>
<hr />
<tabIcon v-for="plugin in $store.state.apiPlugins" @set-tab="setTab"
:key="plugin.id"
:id="plugin.id"
:requireConnection="plugin.requiresConnection"
:currentTab="currentTab">
<i class="material-icons">{{ plugin.icon || "extension" }}</i>
<tabIcon
v-for="plugin in $store.state.apiPlugins"
:id="plugin.id"
:key="plugin.id"
:require-connection="plugin.requiresConnection"
:current-tab="currentTab"
@set-tab="setTab"
>
<i class="material-icons">{{ plugin.icon || "extension" }}</i>
</tabIcon>
</div>
<!-- Corresponding vertical tab content -->
<div v-bind:hidden="!showControlBar" id="container-left" class="uk-padding-remove uk-height-1-1 uk-width-expand">
<div id="component-left" class="uk-padding-remove uk-flex uk-flex-1 panel-content">
<tabContent id="status" :requireConnection="false" :currentTab="currentTab">
<paneStatus/>
<div
id="container-left"
:hidden="!showControlBar"
class="uk-padding-remove uk-height-1-1 uk-width-expand"
>
<div
id="component-left"
class="uk-padding-remove uk-flex uk-flex-1 panel-content"
>
<tabContent
id="status"
:require-connection="false"
:current-tab="currentTab"
>
<paneStatus />
</tabContent>
<tabContent id="navigate" :requireConnection="true" :currentTab="currentTab">
<paneNavigate/>
<tabContent
id="navigate"
:require-connection="true"
:current-tab="currentTab"
>
<paneNavigate />
</tabContent>
<tabContent id="capture" :requireConnection="true" :currentTab="currentTab">
<paneCapture/>
<tabContent
id="capture"
:require-connection="true"
:current-tab="currentTab"
>
<paneCapture />
</tabContent>
<tabContent id="settings" :requireConnection="false" :currentTab="currentTab">
<paneSettings/>
<tabContent
id="settings"
:require-connection="false"
:current-tab="currentTab"
>
<paneSettings />
</tabContent>
<tabContent v-for="plugin in $store.state.apiPlugins"
:key="plugin.id"
:id="plugin.id"
:requireConnection="plugin.requiresConnection"
:currentTab="currentTab">
<div class="uk-flex uk-flex-column" v-for="form in plugin.forms" :key="`${form.route}/${form.name}`.replace(/\s+/g, '-').toLowerCase()" >
<JsonForm
<tabContent
v-for="plugin in $store.state.apiPlugins"
:id="plugin.id"
:key="plugin.id"
:require-connection="plugin.requiresConnection"
:current-tab="currentTab"
>
<div
v-for="form in plugin.forms"
:key="
`${form.route}/${form.name}`.replace(/\s+/g, '-').toLowerCase()
"
class="uk-flex uk-flex-column"
>
<JsonForm
:name="form.name"
:route="form.route"
:isTask="form.isTask"
:submitLabel="form.submitLabel"
:selfUpdate="form.selfUpdate"
:schema="form.schema"/>
<hr>
:is-task="form.isTask"
:submit-label="form.submitLabel"
:self-update="form.selfUpdate"
:schema="form.schema"
/>
<hr />
</div>
</tabContent>
</div>
</div>
</div>
</template>
<script>
// Import axios for HTTP requests
import axios from 'axios'
// Import generic components
import tabIcon from './genericComponents/tabIcon'
import tabContent from './genericComponents/tabContent'
import tabIcon from "./genericComponents/tabIcon";
import tabContent from "./genericComponents/tabContent";
// Import pane components
import paneStatus from './controlComponents/paneStatus'
import paneNavigate from './controlComponents/paneNavigate'
import paneCapture from './controlComponents/paneCapture'
import paneSettings from './controlComponents/paneSettings'
import paneStatus from "./controlComponents/paneStatus";
import paneNavigate from "./controlComponents/paneNavigate";
import paneCapture from "./controlComponents/paneCapture";
import paneSettings from "./controlComponents/paneSettings";
// Import plugin components
import JsonForm from './pluginComponents/JsonForm'
import JsonForm from "./pluginComponents/JsonForm";
// Export main app
export default {
name: 'panelLeft',
name: "PanelLeft",
components: {
tabIcon,
@ -101,38 +153,34 @@ export default {
JsonForm
},
data: function () {
data: function() {
return {
currentTab: 'status',
currentTab: "status",
showControlBar: true
}
};
},
computed: {
pluginApiUri: function() {
return this.$store.getters.uri + "/plugin";
}
},
methods: {
setTab: function(event, tab) {
if (this.currentTab == tab) {
this.showControlBar = !this.showControlBar
this.currentTab = 'none'
this.showControlBar = !this.showControlBar;
this.currentTab = "none";
} else {
this.showControlBar = true;
this.currentTab = tab;
}
else {
this.showControlBar = true
this.currentTab = tab
}
},
},
computed: {
pluginApiUri: function () {
return this.$store.getters.uri + "/plugin"
},
}
}
}
};
</script>
<style scoped lang="less">
#component-left {
width: 300px;
}
@ -142,19 +190,19 @@ export default {
background-color: rgba(180, 180, 180, 0.025);
}
#container-left, #switcher-left {
#container-left,
#switcher-left {
border-width: 0 1px 0 0;
border-style: solid;
border-color: rgba(180, 180, 180, 0.25)
border-color: rgba(180, 180, 180, 0.25);
}
#switcher-left a{
#switcher-left a {
padding: 10px 16px;
}
#switcher-left{
#switcher-left {
background-color: rgba(180, 180, 180, 0.1);
padding-top: 2px !important;
}
</style>

View file

@ -1,81 +1,98 @@
<template>
<!-- Tabbed panel for gallery and live views -->
<div id="panel-right" class="uk-flex uk-flex-column uk-margin-remove uk-padding-remove uk-width-expand uk-height-1-1">
<ul class="uk-flex-none uk-flex-center uk-margin-remove-bottom uk-text-center" id="tabContainer" uk-tab="swiping: false">
<div
id="panel-right"
class="uk-flex uk-flex-column uk-margin-remove uk-padding-remove uk-width-expand uk-height-1-1"
>
<ul
id="tabContainer"
class="uk-flex-none uk-flex-center uk-margin-remove-bottom uk-text-center"
uk-tab="swiping: false"
>
<li><a href="#" uk-switcher-item="connect">Connect</a></li>
<li v-bind:class="{'uk-disabled': !this.$store.getters.ready}"><a href="#" uk-switcher-item="preview">Live</a></li>
<li v-bind:class="{'uk-disabled': !this.$store.getters.ready}"><a href="#" uk-switcher-item="gallery">Gallery</a></li>
<li :class="{ 'uk-disabled': !this.$store.getters.ready }">
<a href="#" uk-switcher-item="preview">Live</a>
</li>
<li :class="{ 'uk-disabled': !this.$store.getters.ready }">
<a href="#" uk-switcher-item="gallery">Gallery</a>
</li>
</ul>
<ul class="uk-switcher uk-flex uk-flex-1 uk-overflow-auto">
<li class="uk-height-1-1 uk-width-1-1" id="connectDisplayTab"><connectDisplay/></li>
<li class="uk-height-1-1 uk-width-1-1 clickableTab" id="streamDisplayTab"><streamDisplay/></li>
<li class="uk-height-1-1 uk-width-1-1" id="galleryDisplayTab"><galleryDisplay/></li>
<li id="connectDisplayTab" class="uk-height-1-1 uk-width-1-1">
<connectDisplay />
</li>
<li id="streamDisplayTab" class="uk-height-1-1 uk-width-1-1 clickableTab">
<streamDisplay />
</li>
<li id="galleryDisplayTab" class="uk-height-1-1 uk-width-1-1">
<galleryDisplay />
</li>
</ul>
</div>
</template>
<script>
// Import axios for HTTP requests
import axios from 'axios'
// Import basic UIkit
import UIkit from 'uikit';
import UIkit from "uikit";
// Import components
import connectDisplay from './viewComponents/connectDisplay.vue'
import streamDisplay from './viewComponents/streamDisplay.vue'
import galleryDisplay from './viewComponents/galleryDisplay.vue'
import connectDisplay from "./viewComponents/connectDisplay.vue";
import streamDisplay from "./viewComponents/streamDisplay.vue";
import galleryDisplay from "./viewComponents/galleryDisplay.vue";
// Export main app
export default {
name: 'panelRight',
name: "PanelRight",
components: {
connectDisplay,
streamDisplay,
galleryDisplay,
galleryDisplay
},
mounted() {
// Attach methods to UIkit events for tab switching
var context = this;
// Gallery tab
UIkit.util.on('#streamDisplayTab', 'hidden', function(event, area) {
console.log("Stream tab hidden")
// Stream tab leave
UIkit.util.on("#streamDisplayTab", "hidden", function() {
console.log("Stream tab hidden");
if (context.$store.state.globalSettings.trackWindow == true) {
context.$root.$emit('globalTogglePreview', false)
context.$root.$emit("globalTogglePreview", false);
}
context.$root.$emit('globalUpdateCaptureList');
});
// Stream tab
UIkit.util.on('#streamDisplayTab', 'shown', function(event, area) {
console.log("Stream tab entered")
UIkit.update()
// Stream tab enter
UIkit.util.on("#streamDisplayTab", "shown", function() {
console.log("Stream tab entered");
UIkit.update();
if (context.$store.state.globalSettings.trackWindow == true) {
context.$root.$emit('globalTogglePreview', true)
context.$root.$emit("globalTogglePreview", true);
}
});
// Gallery tab enter
UIkit.util.on("#galleryDisplayTab", "shown", function() {
console.log("Gallery tab entered");
UIkit.update();
context.$root.$emit("globalUpdateCaptureList");
});
// Create a global event to switch the active tab
var switcherObj = UIkit.tab('#tabContainer')
console.log(switcherObj)
this.$root.$on('globalTogglePanelRightTab', (state) => {
console.log(`Toggling panelRight tab to ${state}`)
switcherObj.show(1)
})
var switcherObj = UIkit.tab("#tabContainer");
console.log(switcherObj);
this.$root.$on("globalTogglePanelRightTab", state => {
console.log(`Toggling panelRight tab to ${state}`);
switcherObj.show(1);
});
},
methods: {
}
}
methods: {}
};
</script>
<style scoped lang="less">
.uk-tab {
padding-left: 0;
}
</style>

View file

@ -1,68 +1,86 @@
<template>
<div>
<div class="uk-flex">
<div class="uk-text-bold uk-text-uppercase uk-width-expand">{{ name }}</div>
<a href="#" v-if="selfUpdate" v-on:click="getFormData()" class="uk-icon uk-width-auto"><i class="material-icons">cached</i></a>
<div class="uk-text-bold uk-text-uppercase uk-width-expand">
{{ name }}
</div>
<a
v-if="selfUpdate"
href="#"
class="uk-icon uk-width-auto"
@click="getFormData()"
><i class="material-icons">cached</i></a
>
</div>
<form @submit.prevent="" class="uk-form-stacked" ref="formContainer">
<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">
<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">
v-bind="subfield"
>
</component>
</div>
</div>
<component
:is="field.fieldType"
v-model="formData[field.name]"
v-bind="field">
v-bind="field"
>
</component>
</div>
<taskSubmitter v-if="isTask"
v-bind:submitURL="submitApiUri"
v-bind:submitData="formData"
v-bind:submitLabel="submitLabel"
v-on:submit="onTaskSubmit"
v-on:response="onTaskResponse"
v-on:error="onTaskError">
<taskSubmitter
v-if="isTask"
:submit-u-r-l="submitApiUri"
:submit-data="formData"
:submit-label="submitLabel"
@submit="onTaskSubmit"
@response="onTaskResponse"
@error="onTaskError"
>
</taskSubmitter>
<button v-else type="button" v-on:click="newQuickRequest(formData);" class="uk-button uk-button-primary uk-form-small uk-float-right uk-margin-small uk-width-1-1">{{ submitLabel }}</button>
<button
v-else
type="button"
class="uk-button uk-button-primary uk-form-small uk-float-right uk-margin-small uk-width-1-1"
@click="newQuickRequest(formData)"
>
{{ submitLabel }}
</button>
</form>
</div>
</template>
<script>
import axios from 'axios'
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 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"
import taskSubmitter from "../genericComponents/taskSubmitter";
export default {
name: 'JsonForm',
name: "JsonForm",
components: {
numberInput,
selectList,
components: {
numberInput,
selectList,
textInput,
htmlBlock,
radioList,
@ -73,47 +91,57 @@ export default {
},
props: {
'name': {
name: {
type: String,
required: false,
default: "Plugin"
},
'schema': {
schema: {
type: Array,
required: true
},
'route': {
route: {
type: String,
required: true
},
'isTask': {
isTask: {
type: Boolean,
required: false,
default: false
},
'submitLabel': {
submitLabel: {
type: String,
required: false,
default: "Submit"
},
'selfUpdate': {
selfUpdate: {
type: Boolean,
required: false,
default: false
},
}
},
data: function () {
data: function() {
return {
formData: {}
};
},
computed: {
pluginApiUri: function() {
return this.$store.getters.uri + "/plugin";
},
submitApiUri: function() {
return this.pluginApiUri + this.route;
}
},
created() {
this.initialiseFormData()
this.initialiseFormData();
if (this.selfUpdate) {
this.getFormData()
this.getFormData();
}
},
@ -129,83 +157,67 @@ export default {
for (const field of this.schema) {
if (Array.isArray(field)) {
for (const subfield of field) {
console.log(subfield.name)
this.$set(this.formData, subfield.name, null)
console.log(subfield.name);
this.$set(this.formData, subfield.name, null);
}
} else {
console.log(field.name);
this.$set(this.formData, field.name, null);
}
else {
console.log(field.name)
this.$set(this.formData, field.name, null)
}
}
},
updateForm(fieldName, value) {
this.$set(this.formData, fieldName, value);
this.$emit('input', this.formData)
this.$emit("input", this.formData);
},
getFormData: function() {
// Send a quick request
axios.get(this.submitApiUri)
.then(response => {
console.log(response.data)
Object.assign(this.formData, response.data)
axios
.get(this.submitApiUri)
.then(response => {
console.log(response.data);
Object.assign(this.formData, response.data);
})
.catch(error => {
this.modalError(error) // Let mixin handle error
})
this.modalError(error); // Let mixin handle error
});
},
newQuickRequest: function(params) {
console.log(this.submitApiUri)
console.log(params)
console.log(this.submitApiUri);
console.log(params);
// Send a quick request
axios.post(this.submitApiUri, params)
.then(response => {
axios
.post(this.submitApiUri, params)
.then(response => {
// Do something with the response
console.log(response)
console.log(response);
// Update the form data if we're self-updating
if (this.selfUpdate) {
this.getFormData()
this.getFormData();
}
})
.catch(error => {
this.modalError(error) // Let mixin handle error
})
this.modalError(error); // Let mixin handle error
});
},
onTaskSubmit: function(submitData) {
// We don't need to do anything on this event yet
console.log("onTaskSubmit event triggered")
},
onTaskSubmit: function() {},
onTaskResponse: function(responseData) {
console.log("Task finished with response data: ", responseData)
console.log("Task finished with response data: ", responseData);
if (this.selfUpdate) {
this.getFormData()
this.getFormData();
}
},
onTaskError: function(error) {
this.modalError(error)
this.modalError(error);
}
},
computed: {
pluginApiUri: function () {
return this.$store.getters.uri + "/plugin"
},
submitApiUri: function () {
return this.pluginApiUri + this.route
},
}
}
};
</script>
<style scoped>
@ -214,6 +226,6 @@ export default {
}
.flex-container > div {
flex-basis: 100%
flex-basis: 100%;
}
</style>
</style>

View file

@ -1,40 +1,62 @@
<template>
<div class="hostCard uk-card uk-card-default uk-card-hover uk-padding-remove uk-width-medium" v-bind:class="{ 'uk-card-secondary': $store.state.globalSettings.darkMode }">
<div
class="hostCard uk-card uk-card-default uk-card-hover 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" v-bind:data-src="snapshotSrc" width="300" height="225" uk-img>
<div v-if="!snapshotAvailable" class="uk-position-cover uk-flex uk-flex-center uk-flex-middle">Snapshot unavailable</div>
<img
class="uk-width-1-1"
:data-src="snapshotSrc"
width="300"
height="225"
uk-img
/>
<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-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">
<b>{{ name }}</b>
</div>
<div class="host-description">{{ hostname }}:{{ port }}</div>
</div>
<a href="#" v-on:click="$emit('delete')" class="uk-icon uk-width-auto host-delete"><i class="material-icons">delete</i></a>
<a
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
<button
class="uk-button uk-button-default uk-form-small uk-float-right uk-margin uk-margin-remove-top uk-width-1-1"
v-on:click="$emit('connect')"
>Connect</button>
@click="$emit('connect')"
>
Connect
</button>
</div>
</div>
</div>
</template>
<script>
import UIkit from 'uikit';
import axios from 'axios'
import axios from "axios";
// Export main app
export default {
name: 'hostCard',
name: "HostCard",
props: {
name: {
@ -51,23 +73,32 @@ export default {
}
},
data: function () {
data: function() {
return {
snapshotSrc: "",
snapshotAvailable: false,
polling: null
}
};
},
computed: {
routesURL: function() {
return `http://${this.hostname}:${this.port}/routes`;
},
snapshotURL: function() {
return `http://${this.hostname}:${this.port}/api/v1/snapshot`;
}
},
mounted() {
// Try to load the microscope snapshot when mounted
this.checkSnapshotAvailable()
this.checkSnapshotAvailable();
},
beforeDestroy () {
beforeDestroy() {
// Clear the polling timer
if (this.polling !== null) {
clearInterval(this.polling)
clearInterval(this.polling);
}
},
@ -77,52 +108,42 @@ export default {
// 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 ("/api/v1/snapshot" 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()
axios
.get(this.routesURL)
.then(response => {
// If the host has a snapshot route available
if ("/api/v1/snapshot" 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(error => {
// Tell the view that a snapshot is not available, and ignore
this.snapshotAvailable = false
})
})
.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()}`
console.log(`Updating snapshot URL to ${this.snapshotSrc}`)
this.snapshotSrc = `${this.snapshotURL}?${Date.now()}`;
console.log(`Updating snapshot URL to ${this.snapshotSrc}`);
},
updateSnapshotPoll: function() {
// Start a timer to call updateSnapshotURL periodically
this.polling = setInterval(() => {
this.updateSnapshotURL()
}, 15000)
this.updateSnapshotURL();
}, 15000);
}
},
computed: {
routesURL: function () {
return `http://${this.hostname}:${this.port}/routes`
},
snapshotURL: function () {
return `http://${this.hostname}:${this.port}/api/v1/snapshot`
},
}
}
};
</script>
<style>
@ -135,8 +156,7 @@ export default {
position: relative;
}
img[data-src][src*='data:image'] {
background: rgba(127,127,127,0.1);
img[data-src][src*="data:image"] {
background: rgba(127, 127, 127, 0.1);
}
</style>
</style>

View file

@ -1,26 +1,63 @@
<template>
<div class="connectDisplay uk-padding uk-padding-remove-left">
<div uk-grid class="uk-height-1-1 uk-margin-remove uk-padding-remove" margin=0>
<div class="connectDisplay uk-padding uk-padding-remove-left">
<div
uk-grid
class="uk-height-1-1 uk-margin-remove uk-padding-remove"
margin="0"
>
<div class="uk-width-auto">
<div class="uk-card uk-card-default uk-card-hover uk-padding-remove uk-width-medium connect-card-align-top">
<div
class="uk-card uk-card-default uk-card-hover uk-padding-remove uk-width-medium connect-card-align-top"
>
<div class="uk-card-body uk-padding-small">
<form @submit.prevent="handleSubmit">
<div class="uk-form-controls uk-margin">
<label><input class="uk-radio" type="radio" name="radio_local" v-model="computedLocalMode" v-bind:value="true"> Connect locally</label><br>
<label><input class="uk-radio" type="radio" name="radio_local" v-model="computedLocalMode" v-bind:value="false" checked> Connect remotely</label>
<label
><input
v-model="computedLocalMode"
class="uk-radio"
type="radio"
name="radio_local"
:value="true"
/>
Connect locally</label
><br />
<label
><input
v-model="computedLocalMode"
class="uk-radio"
type="radio"
name="radio_local"
:value="false"
checked
/>
Connect remotely</label
>
</div>
<div v-if="!localMode">
<div class="uk-inline uk-width-1-1">
<span class="uk-form-icon"><i class="material-icons">dns</i></span>
<input v-model="hostname" v-bind:class="IpFormClasses" class="uk-input uk-form-small" type="text" placeholder="Hostname or IP address">
<span class="uk-form-icon"
><i class="material-icons">dns</i></span
>
<input
v-model="hostname"
:class="IpFormClasses"
class="uk-input uk-form-small"
type="text"
placeholder="Hostname or IP address"
/>
</div>
<label class="uk-form-label" for="form-stacked-text">Port</label>
<label class="uk-form-label" for="form-stacked-text"
>Port</label
>
<div class="uk-form-controls">
<input v-model="computedPort" class="uk-input uk-form-small" id="form-stacked-text" type="number" value=5000>
<input
id="form-stacked-text"
v-model="computedPort"
class="uk-input uk-form-small"
type="number"
value="5000"
/>
</div>
</div>
</form>
@ -28,102 +65,90 @@
<div class="uk-card-footer uk-padding-small">
<button
v-on:click="handleSubmit"
class="uk-button uk-button-primary uk-form-small uk-float-right uk-margin uk-margin-remove-top uk-width-1-1">
class="uk-button uk-button-primary uk-form-small uk-float-right uk-margin uk-margin-remove-top uk-width-1-1"
@click="handleSubmit"
>
Connect
</button>
<button
v-if="$store.getters.ready"
v-on:click="saveHost()"
class="uk-button uk-button-default uk-form-small uk-margin-small uk-width-1-1">
<button
v-if="$store.getters.ready"
class="uk-button uk-button-default uk-form-small uk-margin-small uk-width-1-1"
@click="saveHost()"
>
Save Current
</button>
<button
v-if="$store.getters.ready"
v-on:click="$store.commit('resetState')"
class="uk-button uk-button-danger uk-form-small uk-float-right uk-margin uk-margin-remove-top uk-width-1-1">
<button
v-if="$store.getters.ready"
class="uk-button uk-button-danger uk-form-small uk-float-right uk-margin uk-margin-remove-top uk-width-1-1"
@click="$store.commit('resetState')"
>
Disconnect
</button>
</div>
</div>
</div>
<div class="uk-width-expand">
<ul uk-accordion="multiple: true; animation: false">
<li class="uk-open">
<a class="uk-accordion-title" href="#">Saved devices</a>
<div class="uk-accordion-content">
<div class="uk-grid-medium uk-grid-match uk-margin-top" uk-grid>
<div v-for="host in savedHosts" :key="host.name">
<hostCard
:name="host.name"
:hostname="host.hostname"
<hostCard
:name="host.name"
:hostname="host.hostname"
:port="host.port"
v-on:connect="handleConnectButton(host)"
v-on:delete="delSavedHost(host)"
@connect="handleConnectButton(host)"
@delete="delSavedHost(host)"
></hostCard>
</div>
</div>
</div>
</li>
<li class="uk-open">
<a class="uk-accordion-title" href="#">Nearby devices</a>
<div class="uk-accordion-content">
<div class="uk-grid-medium uk-grid-match uk-margin-top" uk-grid>
<div v-for="host in foundHostArray" :key="host.name">
<hostCard
:name="host.name"
:hostname="host.hostname"
<hostCard
:name="host.name"
:hostname="host.hostname"
:port="host.port"
:deletable="false"
v-on:connect="handleConnectButton(host)"
@connect="handleConnectButton(host)"
></hostCard>
</div>
</div>
</div>
</li>
</ul>
</div>
</div>
</div>
</div>
</template>
<script>
import { version } from 'punycode';
// Import mDNS package if running in Electron
import isElectron from '../../modules/isElectron';
import isElectron from "../../modules/isElectron";
if (isElectron()) {
var mdns = require('mdns-js');
var mdns = require("mdns-js");
}
import hostCard from './connectComponents/hostCard.vue'
import hostCard from "./connectComponents/hostCard.vue";
// Export main app
export default {
name: 'connectDisplay',
name: "ConnectDisplay",
components: {
hostCard
},
data: function () {
data: function() {
return {
localMode: true,
hostname: "",
@ -131,161 +156,205 @@ export default {
selectedHost: "",
savedHosts: [],
foundHosts: {}
}
};
},
computed: {
foundHostArray: function() {
return Object.values(this.foundHosts);
},
// Stylises the hostname input box based on connection status
IpFormClasses: function() {
return {
"uk-form-danger": !this.$store.state.available,
"uk-form-success": this.$store.getters.ready
};
},
computedLocalMode: {
get: function() {
return this.localMode;
},
set: function(state) {
this.localMode = state;
this.setLocalMode(state);
}
},
computedPort: {
get: function() {
if (this.hostname.includes(":")) {
var port = parseInt(this.hostname.split(":")[1]);
return !isNaN(port) ? port : this.port;
} else {
return this.port;
}
},
set: function(newPort) {
this.port = newPort;
if (this.hostname.includes(":")) {
this.hostname = this.hostname.split(":")[0] + ":" + this.port;
}
}
}
},
// When savedHosts changes, serialise to JSON and save to localStorage
watch: {
savedHosts() {
this.setLocalStorageObj("savedHosts", this.savedHosts);
}
},
mounted() {
// Propagate localMode settings
this.setLocalMode(this.localMode)
this.setLocalMode(this.localMode);
// Try loading savedHosts from localStorage. If null, don't change.
this.savedHosts = this.getLocalStorageObj('savedHosts') || this.savedHosts
this.savedHosts = this.getLocalStorageObj("savedHosts") || this.savedHosts;
// Handle mDNS browsing
if (mdns) {
// TODO: Have this run periodically on a timer
// Create a browser to search for 'openflexure' type services
var browser = mdns.createBrowser(mdns.tcp('openflexure'));
var browser = mdns.createBrowser(mdns.tcp("openflexure"));
// Start discovering services when ready
browser.on('ready', function () {
browser.discover();
browser.on("ready", function() {
browser.discover();
});
browser.on('update', (data) => {
console.log(data)
browser.on("update", data => {
console.log(data);
// We will be checking if it's a valid openflexure device
var validDevice = false
var validDevice = false;
// Go through each type of the host, make valid if 'openflexure' appears
data.type.forEach((element) => {
console.log(element)
data.type.forEach(element => {
console.log(element);
if (element.name === "openflexure") {
validDevice = true
validDevice = true;
}
})
});
var hostData = {
hostname: data.addresses[0],
name: `${data.host} on ${data.networkInterface}`,
port: data.port
};
if (typeof data.port !== "undefined" && validDevice === true) {
this.$set(this.foundHosts, hostData.hostname, hostData);
}
if ((typeof data.port !== "undefined") && (validDevice === true)) {
this.$set(this.foundHosts, hostData.hostname, hostData)
}
console.log(this.foundHosts)
console.log(this.foundHosts);
});
}
},
// When savedHosts changes, serialise to JSON and save to localStorage
watch: {
savedHosts(newSavedHosts) {
this.setLocalStorageObj('savedHosts', this.savedHosts)
}
},
methods: {
connectToHost: function(host) {
console.log(host)
console.log(host);
// Set the global form values
this.hostname = host.hostname
this.port = host.port
this.hostname = host.hostname;
this.port = host.port;
// Commit the hostname and port to store
this.$store.commit('changeHost', [
host.hostname,
host.port
]);
this.$store.commit("changeHost", [host.hostname, host.port]);
// Try to get config and state JSON from the newly submitted host
this.$store.dispatch('firstConnect')
.then (() => {
console.log("Connected!")
// Check client and server match
this.checkServerVersion()
// Switch to live view tab
this.$root.$emit('globalTogglePanelRightTab', 'preview')
})
.catch(error => {
this.modalError(error) // Let mixin handle error
})
this.$store
.dispatch("firstConnect")
.then(() => {
console.log("Connected!");
// Check client and server match
this.checkServerVersion();
// Switch to live view tab
this.$root.$emit("globalTogglePanelRightTab", "preview");
})
.catch(error => {
this.modalError(error); // Let mixin handle error
});
},
handleSubmit: function(event) {
handleSubmit: function() {
// Split host and port if needed
if (this.hostname.includes(':')) {
if (this.hostname.includes(":")) {
this.port = this.computedPort;
this.hostname = this.hostname.split(':')[0];
this.hostname = this.hostname.split(":")[0];
}
// Handle auto-local-connect
var hostname;
if (this.localMode) {
var hostname = "localhost";
}
else {
var hostname = this.hostname;
hostname = "localhost";
} else {
hostname = this.hostname;
}
var selectedHost = {
hostname: hostname,
port: this.port
}
};
this.connectToHost(selectedHost)
this.connectToHost(selectedHost);
},
handleConnectButton: function(host) {
this.computedLocalMode = false
this.connectToHost(host)
this.computedLocalMode = false;
this.connectToHost(host);
},
checkServerVersion: function () {
this.$store.dispatch('updateState')
.then (() => {
var clientVersion = process.env.PACKAGE.version
var clientVersionMajor = clientVersion.substring(0, 3)
console.log(clientVersionMajor)
checkServerVersion: function() {
this.$store
.dispatch("updateState")
.then(() => {
var clientVersion = process.env.PACKAGE.version;
var clientVersionMajor = clientVersion.substring(0, 3);
console.log(clientVersionMajor);
var serverVersion = this.$store.state.apiState.version
if (serverVersion != undefined) {
var serverVersionMajor = serverVersion.substring(0, 3)
}
console.log(serverVersionMajor)
var serverVersion = this.$store.state.apiState.version;
if (serverVersion != undefined) {
var serverVersionMajor = serverVersion.substring(0, 3);
}
console.log(serverVersionMajor);
if ((serverVersion == undefined) || (serverVersionMajor != clientVersionMajor)) {
var versionWarning = `Client and microscope versions do not match.\
if (
serverVersion == undefined ||
serverVersionMajor != clientVersionMajor
) {
var versionWarning = `Client and microscope versions do not match.\
Consider updating your microscope software.\
Some functionality may currently be broken.<br><br> \
<b>Client version:</b> ${clientVersion}<br> \
<b>Server version:</b> ${serverVersion}<br><br>`
if (serverVersion < 1.1) {
versionWarning = versionWarning + "You may need to install a never server version on a clean SD card."
<b>Server version:</b> ${serverVersion}<br><br>`;
if (serverVersion < 1.1) {
versionWarning =
versionWarning +
"You may need to install a never server version on a clean SD card.";
} else {
versionWarning =
versionWarning +
"Try running 'ofm upgrade' on your microscope.";
}
this.modalDialog("Version mismatch", versionWarning);
}
else {
versionWarning = versionWarning + "Try running 'ofm upgrade' on your microscope."
}
this.modalDialog("Version mismatch", versionWarning, status='warning')
}
})
.catch(error => {
this.modalError(error) // Let mixin handle error
})
})
.catch(error => {
this.modalError(error); // Let mixin handle error
});
},
setLocalMode: function (state) {
this.$store.commit("changeSetting", ['trackWindow', state]);
this.$store.commit("changeSetting", ['disableStream', state]);
this.$store.commit("changeSetting", ['autoGpuPreview', state]);
setLocalMode: function(state) {
this.$store.commit("changeSetting", ["trackWindow", state]);
this.$store.commit("changeSetting", ["disableStream", state]);
this.$store.commit("changeSetting", ["autoGpuPreview", state]);
//this.$root.$emit('globalTogglePreview', state)
},
delSavedHost: function (host) {
console.log(host)
delSavedHost: function(host) {
console.log(host);
var index = this.savedHosts.indexOf(host);
if (index > -1) {
this.savedHosts.splice(index, 1);
@ -294,67 +363,19 @@ export default {
saveHost: function() {
// We use unshift instead of push to add the entry to the beginning of the array
this.savedHosts.unshift(
{
name: this.$store.state.apiConfig.name,
hostname: this.$store.state.host,
port: this.$store.state.port
}
)
},
},
computed: {
foundHostArray: function () {
return Object.values(this.foundHosts)
},
// Stylises the hostname input box based on connection status
IpFormClasses: function () {
return {
'uk-form-danger': !this.$store.state.available,
'uk-form-success': this.$store.getters.ready
}
},
computedLocalMode: {
get: function() {
return this.localMode
},
set: function(state) {
this.localMode = state;
this.setLocalMode(state)
}
},
computedPort: {
get: function() {
if (this.hostname.includes(':')) {
var port = parseInt(this.hostname.split(':')[1]);
return !isNaN(port) ? port : this.port
}
else {
return this.port
}
},
set: function(newPort) {
this.port = newPort
if (this.hostname.includes(':')) {
this.hostname = this.hostname.split(':')[0] + ":" + this.port
}
}
},
this.savedHosts.unshift({
name: this.$store.state.apiConfig.name,
hostname: this.$store.state.host,
port: this.$store.state.port
});
}
}
}
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="less">
.connect-card-align-top {
margin-top: 52px;
}
</style>

View file

@ -1,86 +1,137 @@
<template>
<div class="captureCard uk-card uk-card-default uk-card-hover uk-padding-remove uk-width-medium" v-bind:class="{ 'uk-card-secondary': $store.state.globalSettings.darkMode }">
<div
class="captureCard uk-card uk-card-default uk-card-hover uk-padding-remove uk-width-medium"
:class="{ 'uk-card-secondary': $store.state.globalSettings.darkMode }"
>
<div class="uk-card-media-top">
<a class="lightbox-link" v-bind:href="imgURL" v-bind:data-caption="metadata.filename">
<img class="uk-width-1-1" v-bind:data-src="thumbURL" v-bind:alt="metadata.id" width="300" height="225" uk-img>
<a class="lightbox-link" :href="imgURL" :data-caption="metadata.filename">
<img
class="uk-width-1-1"
:data-src="thumbURL"
:alt="metadata.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">{{ metadata.filename }}</div>
<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">
{{ metadata.filename }}
</div>
<div class="uk-margin-remove-top uk-padding-remove uk-width-auto">
<a href="#" v-on:click="delCaptureConfirm()" class="uk-icon">
<a href="#" class="uk-icon" @click="delCaptureConfirm()">
<i class="material-icons">delete</i>
</a>
</div>
</div>
<div class="uk-text-meta uk-margin-remove-top uk-padding-remove uk-width-expand"><time>{{ betterTimestring }}</time></div>
<div class="uk-text-meta uk-margin-remove-top uk-padding-remove uk-width-auto">
<a v-bind:href="metadataModalTarget" uk-toggle>More...</a>
<div
class="uk-text-meta uk-margin-remove-top uk-padding-remove uk-width-expand"
>
<time>{{ betterTimestring }}</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">
<div v-for="tag in tags" :key="tag">
<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 v-on:click="delTagConfirm(tag)" class="uk-label uk-margin-small-right deletable-label"> {{ tag }} </span>
<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 v-bind:href="tagModalTarget" uk-toggle>
<a :href="tagModalTarget" uk-toggle>
<span class="uk-label uk-label-success uk-margin-small-right">Add</span>
</a>
</div>
<div v-bind:id="metadataModalID" uk-modal>
<div :id="metadataModalID" uk-modal>
<div
class="uk-modal-dialog uk-modal-body"
:class="{
'uk-light uk-background-secondary':
$store.state.globalSettings.darkMode
}"
>
<button class="uk-modal-close-default" type="button" uk-close></button>
<h2 class="uk-modal-title">{{ metadata.filename }}</h2>
<p><b>Path: </b>{{ path }}</p>
<p><b>Time: </b>{{ betterTimestring }}</p>
<p><b>ID: </b>{{ metadata.id }}</p>
<p><b>Format: </b>{{ metadata.format }}</p>
<div class="uk-modal-dialog uk-modal-body" v-bind:class="{ 'uk-light uk-background-secondary': $store.state.globalSettings.darkMode }" >
<button class="uk-modal-close-default" type="button" uk-close></button>
<h2 class="uk-modal-title">{{ metadata.filename }}</h2>
<p><b>Path: </b>{{ path }}</p>
<p><b>Time: </b>{{ betterTimestring }}</p>
<p><b>ID: </b>{{ metadata.id }}</p>
<p><b>Format: </b>{{ metadata.format }}</p>
<div v-for="(value, key) in metadata.custom" :key="key" >
<p><b>{{ key }}: </b>{{ value }}</p>
</div>
<div v-for="(value, key) in metadata.custom" :key="key">
<p>
<b>{{ key }}: </b>{{ value }}
</p>
</div>
</div>
</div>
<div v-bind:id="tagModalID" uk-modal>
<form class="uk-modal-dialog uk-modal-body uk-margin-auto-vertical" v-bind:class="{ 'uk-light uk-background-secondary': $store.state.globalSettings.darkMode }" @submit.prevent="handleTagSubmit">
<div class="uk-inline">
<span class="uk-form-icon"><i class="material-icons">label</i></span>
<input v-model="newtag" 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>
<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.globalSettings.darkMode
}"
@submit.prevent="handleTagSubmit"
>
<div class="uk-inline">
<span class="uk-form-icon"><i class="material-icons">label</i></span>
<input
v-model="newtag"
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>
</div>
</template>
<script>
import UIkit from 'uikit';
import axios from 'axios'
import UIkit from "uikit";
import axios from "axios";
// Export main app
export default {
name: 'captureCard',
name: "CaptureCard",
props: {
metadata: {
@ -93,11 +144,48 @@ export default {
}
},
data: function () {
data: function() {
return {
tags: [],
newtag: "",
}
newtag: ""
};
},
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.captureURL + "/download?thumbnail=true";
},
imgURL: function() {
return this.captureURL + "/download/" + this.metadata.filename;
},
tagURL: function() {
return this.captureURL + "/tags";
},
captureURL: function() {
return this.$store.getters.uri + "/camera/capture/" + this.metadata.id;
},
betterTimestring: function() {
var dtSplit = this.metadata.time.split("_");
var date = dtSplit[0];
var time = dtSplit[1].replace(/-/g, ":");
return date + " " + time;
}
},
created: function() {
this.getTagRequest();
},
methods: {
@ -106,114 +194,78 @@ export default {
console.log(this.newtag);
this.newTagRequest(this.newtag);
this.newtag = "";
UIkit.modal(event.target.parentNode).hide(); // TODO: Remove somehow
UIkit.modal(event.target.parentNode).hide(); // TODO: Remove somehow
},
delCaptureConfirm: function(tag_string) {
var context = this
this.modalConfirm('Permanantly delete capture?')
.then(function() {
context.delCaptureRequest()
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(response => {
// Emit signal to update capture list
this.$root.$emit('globalUpdateCaptureList')
})
.catch(error => {
this.modalError(error) // Let mixin handle error
})
axios
.delete(this.captureURL)
.then(() => {
// Emit signal to update capture list
this.$root.$emit("globalUpdateCaptureList");
})
.catch(error => {
this.modalError(error); // Let mixin handle error
});
},
newTagRequest: function(tag_string) {
// Send tag PUT request
axios.put(this.tagURL, [tag_string])
.then(response => {
// Update tag array
this.getTagRequest()
})
.catch(error => {
this.modalError(error) // Let mixin handle error
})
axios
.put(this.tagURL, [tag_string])
.then(() => {
// Update tag array
this.getTagRequest();
})
.catch(error => {
this.modalError(error); // Let mixin handle error
});
},
delTagConfirm: function(tag_string) {
var context = this;
this.modalConfirm(`Remove tag '${tag_string}'?`).then(function() {
context.delTagRequest(tag_string)
context.delTagRequest(tag_string);
});
},
delTagRequest: function(tag_string) {
console.log(tag_string)
console.log(tag_string);
// Send tag DELETE request
axios.delete(this.tagURL, {data: [tag_string]})
.then(response => {
// Update tag array
this.getTagRequest()
})
.catch(error => {
this.modalError(error) // Let mixin handle error
})
axios
.delete(this.tagURL, { data: [tag_string] })
.then(() => {
// Update tag array
this.getTagRequest();
})
.catch(error => {
this.modalError(error); // Let mixin handle error
});
},
getTagRequest: function() {
// Send tag request
axios.get(this.tagURL)
.then(response => {
this.tags = response.data.metadata.tags
})
.catch(error => {
this.modalError(error) // Let mixin handle error
})
axios
.get(this.tagURL)
.then(response => {
this.tags = response.data.metadata.tags;
})
.catch(error => {
this.modalError(error); // Let mixin handle error
});
},
makeModalName: function(prefix) {
return prefix + this.metadata.id
}
},
created: function () {
this.getTagRequest()
},
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.captureURL + "/download?thumbnail=true"
},
imgURL: function () {
return this.captureURL + "/download/" + this.metadata.filename
},
tagURL: function () {
return this.captureURL + "/tags"
},
captureURL: function () {
return this.$store.getters.uri + "/camera/capture/" + this.metadata.id
},
betterTimestring: function () {
var dtSplit = this.metadata.time.split("_");
var date = dtSplit[0]
var time = dtSplit[1].replace(/-/g, ":")
return date + " " + time
return prefix + this.metadata.id;
}
}
}
};
</script>

View file

@ -1,54 +1,76 @@
<template>
<div class="captureCard uk-card uk-card-primary uk-card-hover uk-padding-remove uk-width-medium">
<div
class="captureCard uk-card uk-card-primary uk-card-hover uk-padding-remove uk-width-medium"
>
<div class="uk-card-media-top">
<a href="#" >
<img class="uk-width-1-1" v-bind:data-src="thumbnail" v-bind:alt="metadata.scan_id" width="300" height="225" v-on:click="$root.$emit('globalUpdateCaptureFolder', metadata.custom.scan_id)" uk-img>
<a href="#">
<img
class="uk-width-1-1"
:data-src="thumbnail"
:alt="metadata.scan_id"
width="300"
height="225"
uk-img
@click="
$root.$emit('globalUpdateCaptureFolder', metadata.custom.scan_id)
"
/>
</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>Scan:</b> {{ metadata.custom.basename }}</div>
<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>Scan:</b> {{ metadata.custom.basename }}
</div>
</div>
<div class="uk-text-meta uk-margin-remove-top uk-padding-remove uk-width-expand"><time>{{ betterTimestring }}</time></div>
<div class="uk-text-meta uk-margin-remove-top uk-padding-remove uk-width-auto">
<a v-bind:href="metadataModalTarget" uk-toggle>More...</a>
<div
class="uk-text-meta uk-margin-remove-top uk-padding-remove uk-width-expand"
>
<time>{{ betterTimestring }}</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">
<span v-for="tag in metadata.tags" :key="tag" class="uk-label uk-margin-small-right deletable-label"> {{ tag }} </span>
<span
v-for="tag in metadata.tags"
:key="tag"
class="uk-label uk-margin-small-right deletable-label"
>
{{ tag }}
</span>
</div>
<div v-bind:id="metadataModalID" uk-modal>
<div :id="metadataModalID" uk-modal>
<div class="uk-modal-dialog uk-modal-body">
<button class="uk-modal-close-default" type="button" uk-close></button>
<h2 class="uk-modal-title">{{ metadata.basename }}</h2>
<p><b>Time: </b>{{ betterTimestring }}</p>
<p><b>Scan ID: </b>{{ metadata.custom.scan_id }}</p>
<div v-for="(value, key) in metadata.custom" :key="key" >
<p><b>{{ key }}: </b>{{ value }}</p>
<div v-for="(value, key) in metadata.custom" :key="key">
<p>
<b>{{ key }}: </b>{{ value }}
</p>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import axios from 'axios'
// Export main app
export default {
name: 'captureCard',
name: "CaptureCard",
props: {
metadata: {
@ -61,33 +83,31 @@ export default {
}
},
methods: {
makeModalName: function(prefix) {
return prefix + this.metadata.id
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;
},
betterTimestring: function() {
var dtSplit = this.metadata.custom.time.split("_");
var date = dtSplit[0];
var time = dtSplit[1].replace(/-/g, ":");
return date + " " + time;
}
},
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
},
betterTimestring: function () {
var dtSplit = this.metadata.custom.time.split("_");
var date = dtSplit[0]
var time = dtSplit[1].replace(/-/g, ":")
return date + " " + time
methods: {
makeModalName: function(prefix) {
return prefix + this.metadata.id;
}
}
}
};
</script>

View file

@ -1,108 +1,255 @@
<template>
<div class="galleryDisplay uk-padding uk-padding-remove-top">
<nav class="uk-navbar-container uk-navbar-transparent navbar" uk-navbar="mode: click">
<div class="uk-navbar-left uk-padding-remove-top uk-padding-remove-bottom">
<ul class="uk-navbar-nav">
<li v-bind:class="[sortDescending ? 'uk-active' : '']"><a v-on:click="sortDescending=true;" class="uk-icon" href="#"><i class="material-icons">keyboard_arrow_down</i></a></li>
<li v-bind:class="[!sortDescending ? 'uk-active' : '']"><a v-on:click="sortDescending=false;" class="uk-icon" href="#"><i class="material-icons">keyboard_arrow_up</i></a></li>
<li>
<a href="#">Filter</a>
<div class="uk-navbar-dropdown" v-bind: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 class="uk-checkbox" type="checkbox" v-bind:id="tag" v-bind:value="tag" v-model="checkedTags" checked> {{ tag }}</label>
</div>
</form>
</ul>
</div>
</li>
</ul>
</div>
<div class="galleryDisplay uk-padding uk-padding-remove-top">
<nav
class="uk-navbar-container uk-navbar-transparent navbar"
uk-navbar="mode: click"
>
<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>
</nav>
<div v-if="$store.getters.ready" class="uk-padding-remove-top" uk-lightbox="toggle: .lightbox-link">
<div v-if="(galleryFolder)" class="uk-flex uk-flex-middle uk-padding uk-padding-remove-horizontal uk-padding-remove-bottom">
<a href="#" v-on:click="galleryFolder=''" class="uk-icon uk-margin-remove"><i class="material-icons">arrow_back</i></a>
<div
v-if="$store.getters.ready"
class="uk-padding-remove-top"
uk-lightbox="toggle: .lightbox-link"
>
<div
v-if="galleryFolder"
class="uk-flex uk-flex-middle uk-padding uk-padding-remove-horizontal uk-padding-remove-bottom"
>
<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.filename }}</h3>
<h3 class="uk-margin-remove uk-margin-left">
<b>SCAN</b> {{ allScans[galleryFolder].metadata.filename }}
</h3>
</div>
</div>
<div class="uk-grid-medium uk-grid-match uk-margin-top" uk-grid>
<div v-for="item in sortedItems" :key="item.metadata.id">
<scanCard
<scanCard
v-if="'isScan' in item"
:metadata="item.metadata"
:thumbnail="item.thumbnail"
/>
<captureCard
<captureCard
v-else
:metadata="item.metadata"
:temporary="item.temporary"
:path="item.path"
/>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import axios from 'axios'
import captureCard from './galleryComponents/captureCard.vue'
import scanCard from './galleryComponents/scanCard.vue'
import axios from "axios";
import captureCard from "./galleryComponents/captureCard.vue";
import scanCard from "./galleryComponents/scanCard.vue";
// Export main app
export default {
name: 'galleryDisplay',
name: "GalleryDisplay",
components: {
captureCard,
scanCard
},
data: function () {
data: function() {
return {
captureList: [],
checkedTags: [],
sortDescending: true,
galleryFolder: "",
scanTag: 'scan'
}
scanTag: "scan"
};
},
computed: {
captureApiUri: function() {
return this.$store.getters.uri + "/camera/capture";
},
allTags: function() {
// Return an array of unique tags across all captures
var tags = [];
for (var capture of this.captureList) {
for (var tag of capture.metadata.tags) {
if (!tags.includes(tag)) {
tags.push(tag);
}
}
}
return tags.sort();
},
noScanCaptureList: function() {
var captures = [];
for (var capture of this.captureList) {
// Filter by selected tags
var tags = capture.metadata.tags;
// Add to capture list if matched
if (!tags.includes(this.scanTag)) {
captures.push(capture);
}
}
return captures;
},
allScans: function() {
// Return an array of unique tags across all captures
var scans = {};
for (var capture of this.captureList) {
var custom = capture.metadata.custom;
var tags = capture.metadata.tags;
if ("scan_id" in custom) {
var id = custom["scan_id"];
// If this scan ID hasn't been seen before
if (!(id in scans)) {
scans[id] = {};
scans[id].isScan = true;
scans[id].captureList = [];
scans[id].metadata = {
filename: custom.basename,
time: custom.time,
id: custom.scan_id
};
scans[id].metadata.tags = [];
scans[id].metadata.custom = {};
}
// Add the capture object to the scan
scans[id].captureList.push(capture);
// Add missing scan metadata, prioritising first capture
for (var key of Object.keys(custom)) {
if (!(key in scans[id].metadata.custom)) {
scans[id].metadata.custom[key] = custom[key];
}
}
// Append missing tags
for (var tag of tags) {
if (!scans[id].metadata.tags.includes(tag)) {
scans[id].metadata.tags.push(tag);
}
}
// Create a preview thumbnail
if (!("thumbnail" in scans[id])) {
scans[id].thumbnail =
this.$store.getters.uri +
"/camera/capture/" +
capture.metadata.id +
"/download?thumbnail=true";
}
}
}
return scans;
},
scanList: function() {
return Object.values(this.allScans);
},
itemList: function() {
if (this.galleryFolder) {
console.log(this.allScans[this.galleryFolder].captureList);
return this.allScans[this.galleryFolder].captureList;
} else {
return this.noScanCaptureList.concat(this.scanList);
}
},
filteredItems: function() {
return this.filterCaptureList(this.itemList, this.checkedTags);
},
sortedItems: function() {
return this.sortCaptureList(this.filteredItems);
}
},
mounted() {
// A global signal listener to perform a gallery refresh
this.$root.$on('globalUpdateCaptureList', () => {
this.updateCaptureList()
})
this.$root.$on("globalUpdateCaptureList", () => {
this.updateCaptureList();
});
// A global signal listener to set the gallery folder
this.$root.$on('globalUpdateCaptureFolder', (folder) => {
this.galleryFolder = folder
})
this.$root.$on("globalUpdateCaptureFolder", folder => {
this.galleryFolder = folder;
});
},
methods: {
updateCaptureList: function() {
console.log("Updating capture list...")
console.log("Updating capture list...");
// Send move request
axios.get(this.captureApiUri)
.then(response => {
this.$store.dispatch('updateState'); // Update store state for good measure
this.captureList = response.data; // Update boxes from response
})
.catch(error => {
this.modalError(error) // Let mixin handle error
})
axios
.get(this.captureApiUri)
.then(response => {
this.$store.dispatch("updateState"); // Update store state for good measure
this.captureList = response.data; // Update boxes from response
})
.catch(error => {
this.modalError(error); // Let mixin handle error
});
},
filterCaptureList: function(list, filterTags) {
@ -120,153 +267,33 @@ export default {
// Add to capture list if matched
if (includeCapture == true) {
result.push(capture);
};
}
}
};
return result
return result;
},
sortCaptureList: function(list) {
function compare(a, b) {
if (a.metadata.time < b.metadata.time)
return -1;
if (a.metadata.time > b.metadata.time)
return 1;
if (a.metadata.time < b.metadata.time) return -1;
if (a.metadata.time > b.metadata.time) return 1;
return 0;
}
if (this.sortDescending == true) {
return list.sort(compare).reverse();
}
else {
} else {
return list.sort(compare);
}
}
},
computed: {
captureApiUri: function () {
return this.$store.getters.uri + "/camera/capture"
},
allTags: function () {
// Return an array of unique tags across all captures
var tags = [];
for (var capture of this.captureList) {
for (var tag of capture.metadata.tags) {
if (!tags.includes(tag)) {
tags.push(tag);
};
};
};
return tags.sort()
},
noScanCaptureList: function () {
var captures = [];
for (var capture of this.captureList) {
// Assume exclusion
var includeCapture = false;
// Filter by selected tags
var tags = capture.metadata.tags;
// Add to capture list if matched
if (!tags.includes(this.scanTag)) {
captures.push(capture);
};
};
return captures
},
allScans: function () {
// Return an array of unique tags across all captures
var scans = {};
for (var capture of this.captureList) {
var custom = capture.metadata.custom;
var tags = capture.metadata.tags;
if ('scan_id' in custom) {
var id = custom['scan_id']
// If this scan ID hasn't been seen before
if (!(id in scans)) {
scans[id] = {}
scans[id].isScan = true
scans[id].captureList = []
scans[id].metadata = {
filename: custom.basename,
time: custom.time,
id: custom.scan_id
}
scans[id].metadata.tags = []
scans[id].metadata.custom = {}
};
// Add the capture object to the scan
scans[id].captureList.push(capture)
// Add missing scan metadata, prioritising first capture
for (var key of Object.keys(custom)) {
if (!(key in scans[id].metadata.custom)) {
scans[id].metadata.custom[key] = custom[key]
};
};
// Append missing tags
for (var tag of tags) {
if (!scans[id].metadata.tags.includes(tag)) {
scans[id].metadata.tags.push(tag)
};
};
// Create a preview thumbnail
if (!('thumbnail' in scans[id])) {
scans[id].thumbnail = this.$store.getters.uri + "/camera/capture/" + capture.metadata.id + "/download?thumbnail=true"
}
}
};
return scans
},
scanList: function () {
return Object.values(this.allScans)
},
itemList: function () {
if (this.galleryFolder) {
console.log(this.allScans[this.galleryFolder].captureList)
return this.allScans[this.galleryFolder].captureList
}
else {
return this.noScanCaptureList.concat(this.scanList)
}
},
filteredItems: function () {
return this.filterCaptureList(this.itemList, this.checkedTags)
},
sortedItems: function () {
return this.sortCaptureList(this.filteredItems)
}
}
}
};
</script>
<style scoped lang="less">
.navbar {
border-width: 0 0 1px 0;
border-style: solid;
border-color: rgba(180, 180, 180, 0.25)
border-color: rgba(180, 180, 180, 0.25);
}
</style>

View file

@ -1,221 +1,246 @@
<template>
<div ref="streamDisplay" class="stream-display uk-width-1-1 uk-height-1-1 scrollTarget" id="stream-display">
<div
id="stream-display"
ref="streamDisplay"
class="stream-display uk-width-1-1 uk-height-1-1 scrollTarget"
>
<img
ref="click-frame"
class="uk-align-center uk-margin-remove-bottom"
:hidden="!showStream"
:src="streamImgUri"
alt="Stream"
@dblclick="clickMonitor"
/>
<img class="uk-align-center uk-margin-remove-bottom" v-on:dblclick="clickMonitor" v-bind:hidden="!showStream" v-bind:src="streamImgUri" alt="Stream" ref="click-frame">
<div v-if="!showStream">
<div v-if="$store.state.waiting" class="uk-position-center">
<div uk-spinner="ratio: 4.5"></div>
</div>
<div v-if="!showStream">
<div v-if="$store.state.waiting" class="uk-position-center">
<div uk-spinner="ratio: 4.5" ></div>
</div>
<div
v-else-if="$store.state.globalSettings.disableStream"
class="uk-position-center position-relative text-center"
>
Stream preview disabled
</div>
<div v-else-if="$store.state.globalSettings.disableStream" class="uk-position-center position-relative text-center">
Stream preview disabled
</div>
<div v-else class="uk-position-center position-relative text-center">
No active connection
</div>
</div>
</div>
<div v-else class="uk-position-center position-relative text-center">
No active connection
</div>
</div>
</div>
</template>
<script>
import axios from 'axios'
import axios from "axios";
// Export main app
export default {
name: 'stream-display',
name: "StreamDisplay",
data: function () {
data: function() {
return {
displaySize: [0, 0],
displayPosition: [0, 0],
GpuPreviewActive: false,
resizeTimeoutId: setTimeout(this.doneResizing, 500)
displaySize: [0, 0],
displayPosition: [0, 0],
GpuPreviewActive: false,
resizeTimeoutId: setTimeout(this.doneResizing, 500)
};
},
computed: {
showStream: function() {
return (
this.$store.getters.ready &&
!this.$store.state.globalSettings.disableStream
);
},
streamImgUri: function() {
return this.$store.getters.uri + "/stream";
},
startPreviewUri: function() {
return this.$store.getters.uri + "/camera/preview/start";
},
stopPreviewUri: function() {
return this.$store.getters.uri + "/camera/preview/stop";
}
},
mounted() {
// A global signal listener to change the GPU preview state
this.$root.$on('globalTogglePreview', (state) => {
console.log(`Toggling preview to ${state}`)
this.previewRequest(state)
})
this.$root.$on("globalTogglePreview", state => {
console.log(`Toggling preview to ${state}`);
this.previewRequest(state);
});
// A global signal listener to flash the stream element
this.$root.$on('globalFlashStream', (state) => {
this.flashStream()
})
this.$root.$on("globalFlashStream", () => {
this.flashStream();
});
// Mutation observer
this.sizeObserver = new ResizeObserver(entries => {
this.handleResize() // For any element attached to the observer, run handleResize() on change
entries.forEach(entry => {}) // Optional: Run something per entry
});
// Fetch streamDisplay by ref
const streamDisplayElement = this.$refs.streamDisplay.parentNode;
// Attach streamDisplay to the size observer
this.sizeObserver.observe(streamDisplayElement);
// Mutation observer
this.sizeObserver = new ResizeObserver(() => {
this.handleResize(); // For any element attached to the observer, run handleResize() on change
});
// Fetch streamDisplay by ref
const streamDisplayElement = this.$refs.streamDisplay.parentNode;
// Attach streamDisplay to the size observer
this.sizeObserver.observe(streamDisplayElement);
},
},
created: function() {
// Watch for host 'ready'
this.$store.watch(
(state, getters) => {
return getters.ready;
},
(newValue, oldValue) => {
// 'ready' changed, so do something
console.log(`Updating from ${oldValue} to ${newValue}`);
this.previewRequest(this.$store.state.globalSettings.autoGpuPreview);
}
);
},
created: function () {
// Watch for host 'ready'
this.$store.watch(
(state)=>{
return this.$store.getters.ready
},
(newValue, oldValue)=>{
// 'ready' changed, so do something
this.previewRequest(this.$store.state.globalSettings.autoGpuPreview)
}
)
},
beforeDestroy: function () {
// Disconnect the size observer
this.sizeObserver.disconnect()
},
beforeDestroy: function() {
// Disconnect the size observer
this.sizeObserver.disconnect();
},
methods: {
clickMonitor: function(event) {
// Calculate steps from event coordinates and store config FOV
let xCoordinate = event.offsetX;
let yCoordinate = event.offsetY;
// Calculate steps from event coordinates and store config FOV
let xCoordinate = event.offsetX;
let yCoordinate = event.offsetY;
let xRelative = (0.5*event.target.offsetWidth - xCoordinate)/event.target.offsetWidth;
let yRelative = (0.5*event.target.offsetHeight - yCoordinate)/event.target.offsetHeight;
let xRelative =
(0.5 * event.target.offsetWidth - xCoordinate) /
event.target.offsetWidth;
let yRelative =
(0.5 * event.target.offsetHeight - yCoordinate) /
event.target.offsetHeight;
let xSteps = xRelative * this.$store.state.apiConfig.fov[0];
let ySteps = yRelative * this.$store.state.apiConfig.fov[1];
let xSteps = xRelative * this.$store.state.apiConfig.fov[0];
let ySteps = yRelative * this.$store.state.apiConfig.fov[1];
// Emit a signal to move, acted on by panelNavigate.vue
this.$root.$emit('globalMoveEvent', xSteps, ySteps, 0, false)
},
flashStream: function() {
let element = this.$refs.streamDisplay
element.classList.remove("uk-animation-fade");
element.offsetHeight; /* trigger reflow */
element.classList.add("uk-animation-fade");
},
handleResize: function(event) {
// Only fires resize event after no resize in 500ms (prevents resize event spam)
clearTimeout(this.resizeTimeoutId);
this.resizeTimeoutId = setTimeout(this.handleDoneResize, 250)
},
handleDoneResize: function() {
// Recalculate size
console.log("Recalculating frame size")
this.recalculateSize();
if (this.$store.state.globalSettings.autoGpuPreview == true && this.GpuPreviewActive == true) {
// Reload preview
this.$root.$emit('globalTogglePreview', true)
}
},
recalculateSize: function () {
let element = this.$refs.streamDisplay.parentNode;
let bound = element.getBoundingClientRect()
let elementSize = [bound.width, bound.height]
let elementPositionOnWindow = [bound.left, bound.top]
let windowPositionOnDisplay = [window.screenX, window.screenY]
let windowChromeHeight = window.outerHeight - window.innerHeight
let elementPositionOnDisplay = [
Math.max(0, windowPositionOnDisplay[0] + elementPositionOnWindow[0]),
Math.max(0, windowPositionOnDisplay[1] + elementPositionOnWindow[1] + windowChromeHeight)
]
this.displaySize = elementSize
this.displayPosition = elementPositionOnDisplay
console.log(`Size: ${this.displaySize}`)
console.log(`Position: ${this.displayPosition}`)
},
previewRequest: function(state) {
if (this.$store.getters.ready == true) {
// Create URI and set this.GpuPreviewActive depending on if starting or stopping preview
if (state == true) {
this.GpuPreviewActive = true
var requestUri = this.startPreviewUri;
}
else {
this.GpuPreviewActive = false;
var requestUri = this.stopPreviewUri;
}
// Generate payload if tracking window position
if (this.$store.state.globalSettings.trackWindow == true && state == true) {
// Recalculate frame dimensions and position
this.recalculateSize()
// Copy data into payload array
var payload = {
window : [
this.displayPosition[0],
this.displayPosition[1],
this.displaySize[0],
this.displaySize[1],
]
}
}
else {
var payload = {}
}
// Send preview request
axios.post(requestUri, payload)
.then(response => {
this.$store.dispatch('updateState'); // Update store state
})
.catch(error => {
this.modalError(error) // Let mixin handle error
})
}
// Emit a signal to move, acted on by panelNavigate.vue
this.$root.$emit("globalMoveEvent", xSteps, ySteps, 0, false);
},
},
flashStream: function() {
let element = this.$refs.streamDisplay;
element.classList.remove("uk-animation-fade");
element.offsetHeight; /* trigger reflow */
element.classList.add("uk-animation-fade");
},
computed: {
showStream: function () {
return this.$store.getters.ready && !this.$store.state.globalSettings.disableStream
},
streamImgUri: function () {
return this.$store.getters.uri + "/stream"
},
startPreviewUri: function () {
return this.$store.getters.uri + "/camera/preview/start"
},
stopPreviewUri: function () {
return this.$store.getters.uri + "/camera/preview/stop"
}
}
handleResize: function() {
// Only fires resize event after no resize in 500ms (prevents resize event spam)
clearTimeout(this.resizeTimeoutId);
this.resizeTimeoutId = setTimeout(this.handleDoneResize, 250);
},
}
handleDoneResize: function() {
// Recalculate size
console.log("Recalculating frame size");
this.recalculateSize();
if (
this.$store.state.globalSettings.autoGpuPreview == true &&
this.GpuPreviewActive == true
) {
// Reload preview
this.$root.$emit("globalTogglePreview", true);
}
},
recalculateSize: function() {
let element = this.$refs.streamDisplay.parentNode;
let bound = element.getBoundingClientRect();
let elementSize = [bound.width, bound.height];
let elementPositionOnWindow = [bound.left, bound.top];
let windowPositionOnDisplay = [window.screenX, window.screenY];
let windowChromeHeight = window.outerHeight - window.innerHeight;
let elementPositionOnDisplay = [
Math.max(0, windowPositionOnDisplay[0] + elementPositionOnWindow[0]),
Math.max(
0,
windowPositionOnDisplay[1] +
elementPositionOnWindow[1] +
windowChromeHeight
)
];
this.displaySize = elementSize;
this.displayPosition = elementPositionOnDisplay;
console.log(`Size: ${this.displaySize}`);
console.log(`Position: ${this.displayPosition}`);
},
previewRequest: function(state) {
if (this.$store.getters.ready == true) {
var requestUri = null;
// Create URI and set this.GpuPreviewActive depending on if starting or stopping preview
if (state == true) {
this.GpuPreviewActive = true;
requestUri = this.startPreviewUri;
} else {
this.GpuPreviewActive = false;
requestUri = this.stopPreviewUri;
}
// Generate payload if tracking window position
var payload = {};
if (
this.$store.state.globalSettings.trackWindow == true &&
state == true
) {
// Recalculate frame dimensions and position
this.recalculateSize();
// Copy data into payload array
payload = {
window: [
this.displayPosition[0],
this.displayPosition[1],
this.displaySize[0],
this.displaySize[1]
]
};
}
// Send preview request
axios
.post(requestUri, payload)
.then(() => {
this.$store.dispatch("updateState"); // Update store state
})
.catch(error => {
this.modalError(error); // Let mixin handle error
});
}
}
}
};
</script>
<style scoped lang="less">
.stream-display img {
height: 100%;
text-align: center;
object-fit: contain
height: 100%;
text-align: center;
object-fit: contain;
}
.stream-display {
width: 100%;
height: 100%;
width: 100%;
height: 100%;
}
.position-relative {
position: relative !important;
position: relative !important;
}
.text-center {
text-align: center;
text-align: center;
}
</style>

View file

@ -1,15 +1,15 @@
// Load settings store from main process
const { store } = require('electron').remote.require('./store')
const { store } = require("electron").remote.require("./store");
// Load extra modules we'll be using
const { Titlebar, Color } = require('custom-electron-titlebar');
const { Titlebar, Color } = require("custom-electron-titlebar");
// Only show custom menubar if current window has no frame
if (store.get('drawCustomTitleBar') == true) {
new Titlebar({
backgroundColor: Color.fromHex('#c5247f'),
icon: './titleicon.svg'
});
if (store.get("drawCustomTitleBar") == true) {
new Titlebar({
backgroundColor: Color.fromHex("#c5247f"),
icon: "./titleicon.svg"
});
}
console.log("Loaded main.app.js for electron-renderer functionality")
console.log("Loaded main.app.js for electron-renderer functionality");

View file

@ -1,43 +1,50 @@
import Vue from 'vue'
import App from './App.vue'
import store from './store'
import Vue from "vue";
import App from "./App.vue";
import store from "./store";
import UIkit from 'uikit';
import UIkit from "uikit";
// Import MD icons
import 'material-design-icons/iconfont/material-icons.css'
import "material-design-icons/iconfont/material-icons.css";
Vue.config.productionTip = false
Vue.config.productionTip = false;
Vue.mixin({
methods: {
modalConfirm: function(modalText) {
var context = this
var context = this;
// Stop GPU preview to show modal
context.$root.$emit('globalTogglePreview', false)
context.$root.$emit("globalTogglePreview", false);
var showModal = function(resolve, reject) {
UIkit.modal.confirm(modalText)
.then(function() {
resolve()
}, function () {
reject()
})
.finally(function() {
// Reenable the GPU preview, if it was active before the modal
console.log("Re-enabling GPU preview")
if (context.$store.state.globalSettings.autoGpuPreview) {
console.log("Re-enabling preview")
context.$root.$emit('globalTogglePreview', true)
}
})
}
return new Promise(showModal)
UIkit.modal
.confirm(modalText)
.then(
function() {
resolve();
},
function() {
reject();
}
)
.finally(function() {
// Reenable the GPU preview, if it was active before the modal
console.log("Re-enabling GPU preview");
if (context.$store.state.globalSettings.autoGpuPreview) {
console.log("Re-enabling preview");
context.$root.$emit("globalTogglePreview", true);
}
});
};
return new Promise(showModal);
},
modalNotify: function(message, status = 'success') {
UIkit.notification({message: message, status: status})
modalNotify: function(message, status = "success") {
UIkit.notification({
message: message,
status: status
});
},
modalDialog: function(title, message) {
@ -53,55 +60,57 @@ Vue.mixin({
},
modalError: function(error) {
console.log(error)
var errormsg = ''
console.log(error);
var errormsg = "";
// If a response was obtained
if (error.response) {
// If the response is a nicely formatted JSON response from the server
if (error.response.data.message) {
errormsg = `${error.response.status}: ${error.response.data.message}`
console.log(errormsg)
errormsg = `${error.response.status}: ${error.response.data.message}`;
console.log(errormsg);
}
// If the response is just some generic error response
else {
errormsg = `${error.response.status}: ${error.response.data}`
console.log(errormsg)
errormsg = `${error.response.status}: ${error.response.data}`;
console.log(errormsg);
}
// If the error occured during the request
// If the error occured during the request
} else if (error.request) {
errormsg = `${error.message}`
console.log(errormsg)
// Everything else
errormsg = `${error.message}`;
console.log(errormsg);
// Everything else
} else {
errormsg = `${error.message}`
console.log(errormsg)
errormsg = `${error.message}`;
console.log(errormsg);
}
this.$store.commit('setError', errormsg);
UIkit.notification({message: `${errormsg}`, status: 'danger'})
this.$store.commit("setError", errormsg);
UIkit.notification({
message: `${errormsg}`,
status: "danger"
});
},
getLocalStorageObj: function(keyName) {
if (localStorage.getItem(keyName)) {
try {
return JSON.parse(localStorage.getItem(keyName))
} catch(e) {
console.log("Malformed entry. Removing from localStorage")
localStorage.removeItem(keyName)
return null
return JSON.parse(localStorage.getItem(keyName));
} catch (e) {
console.log("Malformed entry. Removing from localStorage");
localStorage.removeItem(keyName);
return null;
}
}
},
setLocalStorageObj: function(keyName, object) {
const parsed = JSON.stringify(object)
const parsed = JSON.stringify(object);
localStorage.setItem(keyName, parsed);
}
}
})
});
new Vue({
store,
render: h => h(App)
}).$mount('#app')
}).$mount("#app");

View file

@ -1,20 +1,32 @@
function isElectron() {
// Renderer process
if (typeof window !== 'undefined' && typeof window.process === 'object' && window.process.type === 'renderer') {
return true;
}
// Renderer process
if (
typeof window !== "undefined" &&
typeof window.process === "object" &&
window.process.type === "renderer"
) {
return true;
}
// Main process
if (typeof process !== 'undefined' && typeof process.versions === 'object' && !!process.versions.electron) {
return true;
}
// Main process
if (
typeof process !== "undefined" &&
typeof process.versions === "object" &&
!!process.versions.electron
) {
return true;
}
// Detect the user agent when the `nodeIntegration` option is set to true
if (typeof navigator === 'object' && typeof navigator.userAgent === 'string' && navigator.userAgent.indexOf('Electron') >= 0) {
return true;
}
// Detect the user agent when the `nodeIntegration` option is set to true
if (
typeof navigator === "object" &&
typeof navigator.userAgent === "string" &&
navigator.userAgent.indexOf("Electron") >= 0
) {
return true;
}
return false;
return false;
}
export default isElectron;
export default isElectron;

View file

@ -1,18 +1,17 @@
import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'
import Vue from "vue";
import Vuex from "vuex";
import axios from "axios";
Vue.use(Vuex)
Vue.use(Vuex);
export default new Vuex.Store({
state: {
host: '',
host: "",
port: 5000,
apiVer: 'v1',
apiVer: "v1",
available: true,
waiting: false,
error: '',
error: "",
apiConfig: {},
apiState: {},
apiPlugins: [],
@ -25,13 +24,13 @@ export default new Vuex.Store({
},
mutations: {
changeHost(state, [host, port, apiVer='v1']) {
changeHost(state, [host, port, apiVer = "v1"]) {
state.host = host;
state.port = port;
state.apiVer = apiVer;
},
changeWaiting(state, waiting) {
state.waiting = waiting
state.waiting = waiting;
},
commitConfig(state, configData) {
state.apiConfig = configData;
@ -46,155 +45,158 @@ export default new Vuex.Store({
state.globalSettings[key] = value;
},
resetState(state) {
state.waiting = false
state.available = true
state.error = null
state.apiConfig = {}
state.apiState = {}
state.apiPlugins = []
state.waiting = false;
state.available = true;
state.error = null;
state.apiConfig = {};
state.apiState = {};
state.apiPlugins = [];
},
setConnected(state) {
state.waiting = false
state.available = true
state.waiting = false;
state.available = true;
},
setError(state, msg) {
state.waiting = false
state.available = false
state.error = msg
state.waiting = false;
state.available = false;
state.error = msg;
}
},
actions: {
firstConnect(context) {
// Reset the state when reconnecting starts
context.commit('resetState')
context.commit("resetState");
// Mark as loading
context.commit('changeWaiting', true)
context.commit("changeWaiting", true);
var sendRequest = function(resolve, reject) {
// Do requests
context.dispatch('updateConfig')
.then (() => context.dispatch('updateState'))
.then (() => context.dispatch('updatePlugins'))
.then (() => {
console.log("Finished first connect")
resolve()
})
.catch(error => {
reject(error)
})
}
return new Promise(sendRequest)
context
.dispatch("updateConfig")
.then(() => context.dispatch("updateState"))
.then(() => context.dispatch("updatePlugins"))
.then(() => {
console.log("Finished first connect");
resolve();
})
.catch(error => {
reject(error);
});
};
return new Promise(sendRequest);
},
updateConfig(context, uri=`${context.getters.uri}/config`) {
console.log("Updating config")
context.commit('changeWaiting', true)
updateConfig(context, uri = `${context.getters.uri}/config`) {
console.log("Updating config");
context.commit("changeWaiting", true);
var sendRequest = function(resolve, reject) {
axios.get(uri)
.then(response => {
context.commit('commitConfig', response.data)
context.commit('setConnected')
console.log("Updating config finished")
resolve()
})
.catch(error => {
reject(new Error(error))
})
}
return new Promise(sendRequest)
axios
.get(uri)
.then(response => {
context.commit("commitConfig", response.data);
context.commit("setConnected");
console.log("Updating config finished");
resolve();
})
.catch(error => {
reject(new Error(error));
});
};
return new Promise(sendRequest);
},
updateState(context, uri=`${context.getters.uri}/state`) {
console.log("Updating state")
context.commit('changeWaiting', true)
updateState(context, uri = `${context.getters.uri}/state`) {
console.log("Updating state");
context.commit("changeWaiting", true);
var sendRequest = function(resolve, reject) {
axios.get(uri)
.then(response => {
context.commit('commitState', response.data)
context.commit('setConnected')
console.log("Updating state finished")
resolve()
})
.catch(error => {
reject(new Error(error))
})
}
return new Promise(sendRequest)
axios
.get(uri)
.then(response => {
context.commit("commitState", response.data);
context.commit("setConnected");
console.log("Updating state finished");
resolve();
})
.catch(error => {
reject(new Error(error));
});
};
return new Promise(sendRequest);
},
updatePlugins(context, uri=`${context.getters.uri}/plugin`) {
console.log("Updating plugins")
context.commit('changeWaiting', true)
updatePlugins(context, uri = `${context.getters.uri}/plugin`) {
console.log("Updating plugins");
context.commit("changeWaiting", true);
var sendRequest = function(resolve, reject) {
axios.get(uri)
.then(response => {
console.log("PLUGINS")
console.log(response.data)
context.commit('commitPlugins', response.data)
context.commit('setConnected')
console.log("Updating plugins finished")
resolve()
})
.catch(error => {
reject(new Error(error))
})
}
return new Promise(sendRequest)
axios
.get(uri)
.then(response => {
console.log("PLUGINS");
console.log(response.data);
context.commit("commitPlugins", response.data);
context.commit("setConnected");
console.log("Updating plugins finished");
resolve();
})
.catch(error => {
reject(new Error(error));
});
};
return new Promise(sendRequest);
},
pollTask(context, [taskId, timeout, interval]) {
var endTime = Number(new Date()) + (timeout*1000 || 30000);
interval = interval*1000 || 500;
var endTime = Number(new Date()) + (timeout * 1000 || 30000);
interval = interval * 1000 || 500;
var checkCondition = function(resolve, reject) {
// If the condition is met, we're done!
axios.get(`${context.getters.uri}/task/${taskId}`)
.then(response => {
console.log(response.data.status)
var result = response.data.status
// If the condition is met, we're done!
axios.get(`${context.getters.uri}/task/${taskId}`).then(response => {
console.log(response.data.status);
var result = response.data.status;
// If the task ends with success
if(result == 'success') {
resolve(response.data)
if (result == "success") {
resolve(response.data);
}
// If task ends with an error
else if (result == 'error') {
else if (result == "error") {
// Pass the error string back with reject
reject(new Error(response.data.return))
reject(new Error(response.data.return));
}
// If task ends with termination
else if (result == 'terminated') {
else if (result == "terminated") {
// Pass a generic termination error back with reject
reject(new Error("Task terminated"))
reject(new Error("Task terminated"));
}
// If task ends in any other way
else if (result != 'running') {
else if (result != "running") {
// Pass status string as error
reject(new Error(result))
reject(new Error(result));
}
// If the condition isn't met but the timeout hasn't elapsed, go again
else if (Number(new Date()) < endTime) {
setTimeout(checkCondition, interval, resolve, reject)
setTimeout(checkCondition, interval, resolve, reject);
}
// Didn't match and too much time, reject!
else {
reject(new Error('Polling timed out'))
reject(new Error("Polling timed out"));
}
})
});
};
return new Promise(checkCondition);
}
},
getters: {
uri: state => `http://${state.host}:${state.port}/api/${state.apiVer}`,
ready: state => ((state.available) && (Object.keys(state.apiConfig).length !== 0) && (Object.keys(state.apiState).length !== 0))
ready: state =>
state.available &&
Object.keys(state.apiConfig).length !== 0 &&
Object.keys(state.apiState).length !== 0
}
})
});