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,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>