Merge branch 'master' into click_to_move

This commit is contained in:
Richard Bowman 2020-04-10 15:32:10 +01:00
commit aebec9b116
23 changed files with 1286 additions and 840 deletions

View file

@ -4,6 +4,7 @@ const { dialog } = require("electron");
const updater = require("electron-updater"); const updater = require("electron-updater");
const autoUpdater = updater.autoUpdater; const autoUpdater = updater.autoUpdater;
const contextMenu = require("electron-context-menu"); const contextMenu = require("electron-context-menu");
const ProgressBar = require("electron-progressbar");
const path = require("path"); const path = require("path");
// Attach settings store // Attach settings store
@ -26,12 +27,44 @@ autoUpdater.on("update-available", function(info) {
}, },
buttonIndex => { buttonIndex => {
if (buttonIndex === 0) { if (buttonIndex === 0) {
autoUpdater.downloadUpdate(); console.log("Downloading update selected");
handleDownloadUpdate();
} }
} }
); );
}); });
// Download, with a progress bar
function handleDownloadUpdate() {
console.log("Downloading update");
var progressBar = new ProgressBar({
indeterminate: false,
text: "Downloading update...",
detail: "Please wait...",
browserWindow: {
webPreferences: {
nodeIntegration: true
}
}
});
progressBar.on("ready", function() {
autoUpdater.on("download-progress", function(info) {
progressBar.value = info.percent;
progressBar.detail = `${Math.floor(info.percent)}% ${info.bytesPerSecond /
1000}kb/s`;
});
autoUpdater.on("update-downloaded", function() {
progressBar.close();
});
autoUpdater.on("error", function() {
progressBar.close();
});
autoUpdater.downloadUpdate();
});
}
// Trigger update installation
autoUpdater.on("update-downloaded", function(info) { autoUpdater.on("update-downloaded", function(info) {
sendStatusToWindow("Update downloaded." + info); sendStatusToWindow("Update downloaded." + info);
dialog.showMessageBox( dialog.showMessageBox(
@ -110,8 +143,8 @@ function createWindow() {
// Create the browser window. // Create the browser window.
mainWindow = new BrowserWindow({ mainWindow = new BrowserWindow({
frame: !store.get("drawCustomTitleBar"), frame: !store.get("drawCustomTitleBar"),
width: 1124, width: 1200,
height: 800, height: 900,
icon: path.join(__dirname, "/icons/png/64x64.png"), icon: path.join(__dirname, "/icons/png/64x64.png"),
webPreferences: { webPreferences: {
nodeIntegration: true nodeIntegration: true

View file

@ -26,5 +26,6 @@ files:
- '!**/app/installers${/*}' - '!**/app/installers${/*}'
- '!**/hooks${/*}' - '!**/hooks${/*}'
- '!**/platforms${/*}' - '!**/platforms${/*}'
- '!**/dist-lite*${/*}'
- '!**/release-builds${/*}' - '!**/release-builds${/*}'
- '!src${/*}' - '!src${/*}'

View file

@ -1,6 +1,5 @@
extends: app/builder-config-base.yaml extends: app/builder-config-base.yaml
artifactName: ${name}-${os}-${arch}.${ext} artifactName: ${name}-${os}-${arch}.${ext}
electronVersion: 3.0.13
linux: linux:
target: target:
- target: deb - target: deb

View file

@ -1,5 +1,4 @@
extends: app/builder-config-base.yaml extends: app/builder-config-base.yaml
electronVersion: 3.0.13
linux: linux:
target: target:
- target: deb - target: deb

View file

@ -1,5 +1,4 @@
extends: app/builder-config-base.yaml extends: app/builder-config-base.yaml
electronVersion: 4.1.5
win: win:
target: target:
- NSIS - NSIS

1290
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -30,13 +30,14 @@
"about-window": "^1.13.2", "about-window": "^1.13.2",
"custom-electron-titlebar": "^3.2.2", "custom-electron-titlebar": "^3.2.2",
"electron-context-menu": "^0.15.2", "electron-context-menu": "^0.15.2",
"electron-progressbar": "^1.2.0",
"electron-store": "^5.1.1", "electron-store": "^5.1.1",
"electron-updater": "^4.2.2", "electron-updater": "^4.2.5",
"material-design-icons": "^3.0.1" "material-design-icons": "^3.0.1"
}, },
"devDependencies": { "devDependencies": {
"@vue/cli-plugin-babel": "^3.12.1", "@vue/cli-plugin-babel": "^3.12.1",
"@vue/cli-service": "^4.2.3", "@vue/cli-service": "^4.3.0",
"axios": "^0.19.2", "axios": "^0.19.2",
"babel-eslint": "^10.1.0", "babel-eslint": "^10.1.0",
"css-loader": "^3.4.2", "css-loader": "^3.4.2",
@ -44,17 +45,18 @@
"electron-builder": "^21.2.0", "electron-builder": "^21.2.0",
"electron-icon-builder": "^1.0.2", "electron-icon-builder": "^1.0.2",
"eslint": "^6.8.0", "eslint": "^6.8.0",
"eslint-config-prettier": "^6.10.0", "eslint-config-prettier": "^6.10.1",
"eslint-plugin-prettier": "^3.1.2", "eslint-plugin-prettier": "^3.1.2",
"eslint-plugin-vue": "^6.2.1", "eslint-plugin-vue": "^6.2.2",
"less": "^3.11.1", "less": "^3.11.1",
"less-loader": "^5.0.0", "less-loader": "^5.0.0",
"mdns-js": "^1.0.3", "mdns-js": "^1.0.3",
"uikit": "^3.3.3", "uikit": "^3.3.7",
"vue": "^2.6.11", "vue": "^2.6.11",
"vue-plugin-load-script": "^1.2.0", "vue-plugin-load-script": "^1.2.0",
"vue-template-compiler": "^2.6.11", "vue-template-compiler": "^2.6.11",
"vuex": "^3.1.2" "vue-tour": "^1.3.0",
"vuex": "^3.1.3"
}, },
"optionalDependencies": { "optionalDependencies": {
"electron-windows-store": "^2.1.0" "electron-windows-store": "^2.1.0"

View file

@ -4,26 +4,57 @@
class="uk-height-1-1 uk-margin-remove uk-padding-remove" class="uk-height-1-1 uk-margin-remove uk-padding-remove"
:class="handleTheme" :class="handleTheme"
> >
<panelLeft /> <div id="tour-header"></div>
<appContent />
<v-tour
name="guidedTour"
:steps="tourSteps"
:callbacks="tourCallbacks"
:options="{ highlight: true }"
></v-tour>
</div> </div>
</template> </template>
<script> <script>
import isElectron from "./modules/isElectron";
// Import components // Import components
import panelLeft from "./components/panelLeft.vue"; import appContent from "./components/appContent.vue";
// Key Codes
const keyCodes = {
pgup: 33,
pgdn: 34,
left: 37,
up: 38,
right: 39,
down: 40,
enter: 13,
esc: 27,
shift: 16,
alt: 18,
t: 84
};
// Export main app // Export main app
export default { export default {
name: "App", name: "App",
components: { components: {
panelLeft appContent
}, },
data: function() { data: function() {
return { return {
keysDown: {},
systemDark: undefined, systemDark: undefined,
themeObserver: undefined themeObserver: undefined,
liteMode: process.env.VUE_APP_LITEMODE == "true" ? true : false,
isElectron: isElectron(),
tourCallbacks: {
onStop: () => {
this.setLocalStorageObj("completedTour", true);
}
}
}; };
}, },
@ -51,6 +82,88 @@ export default {
"uk-light": isDark, "uk-light": isDark,
"uk-background-secondary": isDark "uk-background-secondary": isDark
}; };
},
tourSteps: function() {
return [
{
target: "#tour-header", // We're using document.querySelector() under the hood
header: {
title: "Welcome to OpenFlexure eV"
},
content: `Click Next to learn how to use OpenFlexure eV`,
params: {
placement: "bottom"
}
},
...(!this.liteMode
? [
{
target: "#new-connection-card",
header: {
title: "New connection"
},
content: `Connect locally if you're running on a microscope, \nor open a new remote connection to a microscope`
}
]
: []),
...(this.isElectron && !this.liteMode
? [
{
target: "#nearby-connections-grid",
header: {
title: "Nearby microscopes"
},
content: `Connect to microscopes found on your network`
}
]
: []),
...(!this.liteMode
? [
{
target: "#saved-connections-grid",
header: {
title: "Saved microscopes"
},
content: `Connect to your saved microscopes for faster access`
}
]
: []),
{
target: "#gallery-tab-icon",
header: {
title: "Capture gallery"
},
content: `View and download your microscope images from the gallery tab`
},
{
target: "#navigate-tab-icon",
header: {
title: "Navigate around your sample"
},
content: `Move your microscope stage and perform autofocus from the navigate tab`
},
{
target: "#capture-tab-icon",
header: {
title: "Capture microscope images"
},
content: `Take images and simple tile scans from the capture tab`
},
{
target: "#settings-tab-icon",
header: {
title: "Change settings"
},
content: `Change app and microscope settings, including microscope calibration, from the settings tab`
},
{
target: "#extension-tab-divider",
header: {
title: "Microscope extensions"
},
content: `Extensions installed on your microscope will appear below this line`
}
];
} }
}, },
@ -72,10 +185,20 @@ export default {
this.systemDark = false; this.systemDark = false;
} }
}); });
// Handle guided tour
// If the user has already completed or skipped the guided tour
var completedTour = this.getLocalStorageObj("completedTour") || false;
if (!completedTour) {
this.$tours["guidedTour"].start();
}
}, },
created: function() { created: function() {
window.addEventListener("beforeunload", this.handleExit); window.addEventListener("beforeunload", this.handleExit);
// Key events
window.addEventListener("keydown", this.keyDownMonitor);
window.addEventListener("keyup", this.keyUpMonitor);
window.addEventListener("wheel", this.wheelMonitor);
}, },
beforeDestroy: function() { beforeDestroy: function() {
@ -83,12 +206,109 @@ export default {
if (this.themeObserver) { if (this.themeObserver) {
this.themeObserver.disconnect(); this.themeObserver.disconnect();
} }
// Remove key listeners
window.removeEventListener("keydown", this.keyDownMonitor);
window.removeEventListener("keyup", this.keyUpMonitor);
window.removeEventListener("wheel", this.wheelMonitor);
}, },
methods: { methods: {
handleExit: function() { handleExit: function() {
console.log("Triggered beforeunload"); console.log("Triggered beforeunload");
this.$root.$emit("globalTogglePreview", false); this.$root.$emit("globalTogglePreview", false);
},
// 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;
// Emit a signal to move, acted on by panelNavigate.vue
this.$root.$emit("globalMoveStepEvent", 0, 0, z_rel, false);
}
},
// Handle global key press events to be associated with navigation
keyDownMonitor: function(event) {
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];
});
if (
// If not inside an element we want to ignore
!(event.target instanceof HTMLInputElement) &&
!event.target.classList.contains("lightbox-link") &&
// If it's a recognised key
keyCodeList.includes(event.keyCode)
) {
this.navigateKeyHandler(keyCodes);
this.captureKeyHandler(keyCodes);
this.letterKeyHandler(keyCodes);
}
},
keyUpMonitor: function(event) {
delete this.keysDown[event.keyCode]; //Remove key from array
},
navigateKeyHandler: function(keyCodes) {
const moveKeys = [
keyCodes.left,
keyCodes.right,
keyCodes.up,
keyCodes.down,
keyCodes.pgup,
keyCodes.pgdn
];
if (
moveKeys.some(r => Object.keys(this.keysDown).includes(r.toString()))
) {
// Calculate movement array
var x_rel = 0;
var y_rel = 0;
var z_rel = 0;
if (keyCodes.left in this.keysDown) {
x_rel = x_rel + 1;
}
if (keyCodes.right in this.keysDown) {
x_rel = x_rel - 1;
}
if (keyCodes.up in this.keysDown) {
y_rel = y_rel + 1;
}
if (keyCodes.down in this.keysDown) {
y_rel = y_rel - 1;
}
if (keyCodes.pgup in this.keysDown) {
z_rel = z_rel - 1;
}
if (keyCodes.pgdn in this.keysDown) {
z_rel = z_rel + 1;
}
// Make a position request
// Emit a signal to move, acted on by panelNavigate.vue
this.$root.$emit("globalMoveStepEvent", x_rel, y_rel, z_rel);
}
},
captureKeyHandler: function(keyCodes) {
if (keyCodes.shift in this.keysDown && keyCodes.enter in this.keysDown) {
console.log("Capturing");
this.$root.$emit("globalCaptureEvent");
}
},
letterKeyHandler: function(keyCodes) {
if (keyCodes.alt in this.keysDown && keyCodes.t in this.keysDown) {
this.$tours["guidedTour"].start();
}
} }
} }
}; };
@ -132,7 +352,7 @@ html {
width: 300px; width: 300px;
height: 100%; height: 100%;
padding: 0; padding: 0;
background-color: rgba(180, 180, 180, 0.055); background-color: rgba(180, 180, 180, 0.03);
border-width: 0 1px 0 0; border-width: 0 1px 0 0;
border-style: solid; border-style: solid;
border-color: rgba(180, 180, 180, 0.25); border-color: rgba(180, 180, 180, 0.25);
@ -144,4 +364,58 @@ html {
height: 100%; height: 100%;
padding: 0; padding: 0;
} }
// Style tour
.v-tour__target--highlighted {
box-shadow: 0px 40px 200px 30px rgba(0, 0, 0, 0.5),
0px 0px 0px 4px rgba(128, 128, 128, 0.5) !important;
border-radius: 5px;
opacity: 100% !important;
pointer-events: none !important;
}
.v-step {
background: @global-primary-background !important;
}
.v-step__header {
background-color: darken(@global-primary-background, 7%) !important;
}
.v-step__button {
font-size: 0.9rem !important;
}
// Change step arrow colour
// This is awful and hacky and makes me sad, but needs must
.v-step .v-step__arrow {
border-color: darken(@global-primary-background, 7%) !important;
&--dark {
border-color: darken(@global-primary-background, 7%) !important;
}
}
.v-step[x-placement^="top"] .v-step__arrow {
border-left-color: transparent !important;
border-right-color: transparent !important;
border-bottom-color: transparent !important;
}
.v-step[x-placement^="bottom"] .v-step__arrow {
border-left-color: transparent !important;
border-right-color: transparent !important;
border-top-color: transparent !important;
}
.v-step[x-placement^="right"] .v-step__arrow {
border-left-color: transparent !important;
border-top-color: transparent !important;
border-bottom-color: transparent !important;
}
.v-step[x-placement^="left"] .v-step__arrow {
border-top-color: transparent !important;
border-right-color: transparent !important;
border-bottom-color: transparent !important;
}
</style> </style>

View file

@ -8,9 +8,21 @@
@import "./app.less"; @import "./app.less";
// Custom OpenFlexure theming // Custom OpenFlexure theming
//
// Colors
//
@global-color: #666;
@global-emphasis-color: #333;
@global-muted-color: #666;
@global-primary-background: #C32280; @global-primary-background: #C32280;
@inverse-primary-muted-color: lighten(@global-primary-background, 15%); @inverse-primary-muted-color: lighten(@global-primary-background, 15%);
@inverse-global-color: fade(@global-inverse-color, 80%);
@global-border: #d5d5d5;
// UIkit // UIkit
// ======================================================================== // ========================================================================
@ -176,6 +188,8 @@
.uk-card { .uk-card {
border-radius: @paper-border-radius; border-radius: @paper-border-radius;
border: 1px solid rgba(180, 180, 180, 0.25); border: 1px solid rgba(180, 180, 180, 0.25);
box-shadow: none;
-webkit-box-shabox-shadow: none;
} }
.uk-card-media-top img { .uk-card-media-top img {
@ -190,11 +204,16 @@
border-top: 1px solid rgba(0, 0, 0, 0.075); border-top: 1px solid rgba(0, 0, 0, 0.075);
} }
.uk-card-default {
color: @global-color;
}
.hook-inverse() { .hook-inverse() {
// Override background colour in dark mode // Override background colour in dark mode
.uk-card-default { .uk-card-default {
background-color: #2a2a2a !important; background-color: #2a2a2a !important;
color: @inverse-global-color;
} }
// Lighten on hover to show depth in dark mode // Lighten on hover to show depth in dark mode
@ -214,7 +233,11 @@
} }
.uk-modal-footer { .uk-modal-footer {
border-radius: @paper-border-radius; border-radius: 0 0 @paper-border-radius @paper-border-radius;
}
.uk-modal-header {
border-radius: @paper-border-radius @paper-border-radius 0 0;
} }
/* /*
@ -267,6 +290,11 @@ a:hover {
border-radius: 2px; border-radius: 2px;
} }
.uk-button-default {
background-color: rgba(180, 180, 180, 0.10);
border-color: @global-border;
}
.uk-button-danger { .uk-button-danger {
background-color: transparent; background-color: transparent;
color: #f0506e; color: #f0506e;

View file

@ -1,6 +1,6 @@
<template> <template>
<div <div
id="panel-left" id="app-content"
class="uk-margin-remove uk-padding-remove uk-height-1-1" class="uk-margin-remove uk-padding-remove uk-height-1-1"
uk-grid uk-grid
> >
@ -11,7 +11,8 @@
> >
<tabIcon <tabIcon
v-show="!liteMode" v-show="!liteMode"
id="connect" id="connect-tab-icon"
tab-i-d="connect"
:require-connection="false" :require-connection="false"
:current-tab="currentTab" :current-tab="currentTab"
@set-tab="setTab" @set-tab="setTab"
@ -19,7 +20,8 @@
<i class="material-icons">bug_report</i> <i class="material-icons">bug_report</i>
</tabIcon> </tabIcon>
<tabIcon <tabIcon
id="gallery" id="gallery-tab-icon"
tab-i-d="gallery"
:require-connection="true" :require-connection="true"
:current-tab="currentTab" :current-tab="currentTab"
@set-tab="setTab" @set-tab="setTab"
@ -30,7 +32,8 @@
<hr /> <hr />
<tabIcon <tabIcon
id="navigate" id="navigate-tab-icon"
tab-i-d="navigate"
:require-connection="true" :require-connection="true"
:current-tab="currentTab" :current-tab="currentTab"
@set-tab="setTab" @set-tab="setTab"
@ -38,7 +41,8 @@
<i class="material-icons">gamepad</i> <i class="material-icons">gamepad</i>
</tabIcon> </tabIcon>
<tabIcon <tabIcon
id="capture" id="capture-tab-icon"
tab-i-d="capture"
:require-connection="true" :require-connection="true"
:current-tab="currentTab" :current-tab="currentTab"
@set-tab="setTab" @set-tab="setTab"
@ -46,7 +50,8 @@
<i class="material-icons">camera_alt</i> <i class="material-icons">camera_alt</i>
</tabIcon> </tabIcon>
<tabIcon <tabIcon
id="settings" id="settings-tab-icon"
tab-i-d="settings"
:require-connection="false" :require-connection="false"
:current-tab="currentTab" :current-tab="currentTab"
@set-tab="setTab" @set-tab="setTab"
@ -54,12 +59,12 @@
<i class="material-icons">settings</i> <i class="material-icons">settings</i>
</tabIcon> </tabIcon>
<hr /> <hr id="extension-tab-divider" />
<tabIcon <tabIcon
v-for="plugin in pluginsGuiList" v-for="plugin in pluginsGuiList"
:id="plugin.id"
:key="plugin.id" :key="plugin.id"
:tab-i-d="plugin.id"
:title="plugin.title" :title="plugin.title"
:require-connection="plugin.requiresConnection" :require-connection="plugin.requiresConnection"
:current-tab="currentTab" :current-tab="currentTab"
@ -76,35 +81,35 @@
class="uk-padding-remove uk-height-1-1 uk-width-expand" class="uk-padding-remove uk-height-1-1 uk-width-expand"
> >
<tabContent <tabContent
id="connect" tab-i-d="connect"
:require-connection="false" :require-connection="false"
:current-tab="currentTab" :current-tab="currentTab"
> >
<connectContent /> <connectContent />
</tabContent> </tabContent>
<tabContent <tabContent
id="gallery" tab-i-d="gallery"
:require-connection="false" :require-connection="false"
:current-tab="currentTab" :current-tab="currentTab"
> >
<galleryContent /> <galleryContent />
</tabContent> </tabContent>
<tabContent <tabContent
id="navigate" tab-i-d="navigate"
:require-connection="true" :require-connection="true"
:current-tab="currentTab" :current-tab="currentTab"
> >
<navigateContent /> <navigateContent />
</tabContent> </tabContent>
<tabContent <tabContent
id="capture" tab-i-d="capture"
:require-connection="true" :require-connection="true"
:current-tab="currentTab" :current-tab="currentTab"
> >
<captureContent /> <captureContent />
</tabContent> </tabContent>
<tabContent <tabContent
id="settings" tab-i-d="settings"
:require-connection="false" :require-connection="false"
:current-tab="currentTab" :current-tab="currentTab"
> >
@ -113,8 +118,8 @@
<tabContent <tabContent
v-for="plugin in pluginsGuiList" v-for="plugin in pluginsGuiList"
:id="plugin.id"
:key="plugin.id" :key="plugin.id"
:tab-i-d="plugin.id"
:require-connection="plugin.requiresConnection" :require-connection="plugin.requiresConnection"
:current-tab="currentTab" :current-tab="currentTab"
> >
@ -204,6 +209,8 @@ export default {
); );
}, },
mounted: function() {},
beforeDestroy() { beforeDestroy() {
// Then we call that function here to unwatch // Then we call that function here to unwatch
if (this.unwatchStoreFunction) { if (this.unwatchStoreFunction) {

View file

@ -382,6 +382,10 @@ export default {
mounted() { mounted() {
this.updateScanUri(); this.updateScanUri();
this.updateScanStepSize(); this.updateScanStepSize();
// A global signal listener to perform a capture action
this.$root.$on("globalCaptureEvent", () => {
this.handleCapture();
});
}, },
methods: { methods: {

View file

@ -156,18 +156,6 @@
import axios from "axios"; import axios from "axios";
import taskSubmitter from "../genericComponents/taskSubmitter"; import taskSubmitter from "../genericComponents/taskSubmitter";
// Key Codes
const keyCodes = {
pgup: 33,
pgdn: 34,
left: 37,
up: 38,
right: 39,
down: 40,
enter: 13,
esc: 27
};
// Export main app // Export main app
export default { export default {
name: "PaneNavigate", name: "PaneNavigate",
@ -178,7 +166,6 @@ export default {
data: function() { data: function() {
return { return {
keysDown: {},
stepXy: 200, stepXy: 200,
stepZz: 50, stepZz: 50,
setPosition: null, setPosition: null,
@ -205,21 +192,23 @@ export default {
} }
}, },
created: function() {
window.addEventListener("keydown", this.keyDownMonitor);
window.addEventListener("keyup", this.keyUpMonitor);
window.addEventListener("wheel", this.wheelMonitor);
},
mounted() { mounted() {
// A global signal listener to perform a move action // A global signal listener to perform a move action
this.$root.$on("globalMoveEvent", (x, y, z, absolute) => { this.$root.$on("globalMoveEvent", (x, y, z, absolute) => {
this.moveRequest(x, y, z, absolute); this.moveRequest(x, y, z, absolute);
}); });
// A global signal listener to perform a move action // A global signal listener to perform a move action in pixels
this.$root.$on("globalMoveInImageCoordinatesEvent", (x, y, absolute) => { this.$root.$on("globalMoveInImageCoordinatesEvent", (x, y, absolute) => {
this.moveInImageCoordinatesRequest(x, y, absolute); this.moveInImageCoordinatesRequest(x, y, absolute);
}); });
this.$root.$on("globalMoveStepEvent", (x_steps, y_steps, z_steps) => {
this.moveRequest(
x_steps * this.stepXy,
y_steps * this.stepXy,
z_steps * this.stepZz,
false
);
});
// Update the current position in text boxes // Update the current position in text boxes
this.updatePosition(); this.updatePosition();
// Look for autofocus plugin // Look for autofocus plugin
@ -232,72 +221,7 @@ export default {
this.$root.$off("globalMoveEvent"); this.$root.$off("globalMoveEvent");
}, },
destroyed: function() {
window.removeEventListener("keydown", this.keyDownMonitor);
window.removeEventListener("keyup", this.keyUpMonitor);
window.removeEventListener("wheel", this.wheelMonitor);
},
methods: { 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);
}
},
// Handle global key press events to be associated with navigation
keyDownMonitor: function(event) {
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];
});
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;
var y_rel = 0;
var z_rel = 0;
if (keyCodes.left in this.keysDown) {
x_rel = x_rel + this.stepXy;
}
if (keyCodes.right in this.keysDown) {
x_rel = x_rel - this.stepXy;
}
if (keyCodes.up in this.keysDown) {
y_rel = y_rel + this.stepXy;
}
if (keyCodes.down in this.keysDown) {
y_rel = y_rel - this.stepXy;
}
if (keyCodes.pgup in this.keysDown) {
z_rel = z_rel - this.stepZz;
}
if (keyCodes.pgdn in this.keysDown) {
z_rel = z_rel + this.stepZz;
}
// Make a position request
this.moveRequest(x_rel, y_rel, z_rel, false);
}
},
keyUpMonitor: function(event) {
delete this.keysDown[event.keyCode]; //Remove key from array
},
handleSubmit: function() { handleSubmit: function() {
this.moveRequest( this.moveRequest(
this.setPosition.x, this.setPosition.x,

View file

@ -1,7 +1,7 @@
<template> <template>
<div <div
v-if="!(requireConnection && !$store.getters.ready)" v-if="!(requireConnection && !$store.getters.ready)"
:hidden="currentTab != id" :hidden="currentTab != tabID"
class="uk-width-expand uk-height-1-1" class="uk-width-expand uk-height-1-1"
> >
<div class="section-content"><slot></slot></div> <div class="section-content"><slot></slot></div>
@ -13,7 +13,7 @@ export default {
name: "TabContent", name: "TabContent",
props: { props: {
id: { tabID: {
type: String, type: String,
required: true required: true
}, },

View file

@ -18,7 +18,7 @@ export default {
name: "TabIcon", name: "TabIcon",
props: { props: {
id: { tabID: {
type: String, type: String,
required: true required: true
}, },
@ -45,7 +45,7 @@ export default {
return this.title; return this.title;
} else { } else {
// Get the last section of a fully qualified name // Get the last section of a fully qualified name
var topName = this.id.split(".").pop(); var topName = this.tabID.split(".").pop();
// Make first character uppercase, then add the rest of the string // Make first character uppercase, then add the rest of the string
return topName.charAt(0).toUpperCase() + topName.slice(1); return topName.charAt(0).toUpperCase() + topName.slice(1);
} }
@ -57,7 +57,7 @@ export default {
classObject: function() { classObject: function() {
return { return {
"tabicon-active": this.currentTab == this.id, "tabicon-active": this.currentTab == this.tabID,
"uk-disabled": this.requireConnection && !this.$store.getters.ready "uk-disabled": this.requireConnection && !this.$store.getters.ready
}; };
} }
@ -65,7 +65,7 @@ export default {
methods: { methods: {
setThisTab(event) { setThisTab(event) {
this.$emit("set-tab", event, this.id); this.$emit("set-tab", event, this.tabID);
if (this.clickCallback) { if (this.clickCallback) {
this.clickCallback(); this.clickCallback();
} }

View file

@ -1,170 +0,0 @@
<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
id="tabContainer"
class="uk-flex-none uk-flex-center uk-margin-remove-bottom uk-text-center"
uk-tab="swiping: false"
>
<!-- Connect tab button -->
<li
v-show="!liteMode"
:class="[{ 'uk-active': currentTab == 'connect' }]"
>
<a href="#" uk-switcher-item="connect" @click="currentTab = 'connect'"
>Connect</a
>
</li>
<!-- Preview tab button -->
<li
:class="[
{ 'uk-disabled': !$store.getters.ready },
{ 'uk-active': currentTab == 'preview' }
]"
>
<a href="#" uk-switcher-item="preview" @click="currentTab = 'preview'"
>Live</a
>
</li>
<!-- Gallery tab button -->
<li
:class="[
{ 'uk-disabled': !$store.getters.ready },
{ 'uk-active': currentTab == 'gallery' }
]"
>
<a href="#" uk-switcher-item="gallery" @click="currentTab = 'gallery'"
>Gallery</a
>
</li>
</ul>
<ul
class="uk-flex uk-flex-1 uk-overflow-auto uk-margin-remove uk-padding-remove"
>
<!-- Connect tab -->
<div
v-show="currentTab == 'connect'"
id="connectDisplayTab"
class="uk-height-1-1 uk-width-1-1"
>
<connectDisplayLite v-if="liteMode" />
<connectDisplay v-else />
</div>
<!-- Preview tab -->
<div
v-if="$store.getters.ready"
v-show="currentTab == 'preview'"
id="streamDisplayTab"
class="uk-height-1-1 uk-width-1-1"
>
<streamDisplay />
</div>
<!-- Gallery tab -->
<div
v-if="$store.getters.ready"
v-show="currentTab == 'gallery'"
id="galleryDisplayTab"
class="uk-height-1-1 uk-width-1-1"
>
<galleryDisplay />
</div>
</ul>
</div>
</template>
<script>
// Import basic UIkit
import UIkit from "uikit";
// Import components
import connectDisplay from "./viewComponents/connectDisplay.vue";
import connectDisplayLite from "./viewComponents/connectDisplayLite.vue";
import streamDisplay from "./viewComponents/streamDisplay.vue";
import galleryDisplay from "./viewComponents/galleryDisplay.vue";
// Export main app
export default {
name: "PanelRight",
components: {
connectDisplay,
connectDisplayLite,
streamDisplay,
galleryDisplay
},
data: function() {
return {
currentTab: "connect",
unwatchStoreFunction: null,
liteMode: process.env.VUE_APP_LITEMODE == "true" ? true : false
};
},
watch: {
currentTab: function(index) {
// If entering the gallery
if (index == "gallery") {
console.log("Gallery tab entered");
this.$root.$emit("globalUpdateCaptures");
}
// If entering the stream
if (index == "preview") {
console.log("Preview tab entered");
this.$root.$emit("globalTogglePreview", true);
}
// If leaving the stream
else {
console.log("Preview tab hidden");
this.$root.$emit("globalTogglePreview", false);
}
}
},
created: function() {
// Watch for host 'ready', then update status
this.unwatchStoreFunction = this.$store.watch(
(state, getters) => {
return getters.ready;
},
ready => {
if (ready) {
console.log("Right panel now ready");
this.currentTab = "preview";
} else {
console.log("Right panel now disabled");
this.currentTab = "connect";
}
}
);
},
beforeDestroy() {
// Then we call that function here to unwatch
if (this.unwatchStoreFunction) {
this.unwatchStoreFunction();
this.unwatchStoreFunction = null;
}
},
methods: {
switchTab: function(index) {
var switcherObj = UIkit.switcher("#tabContainer");
console.log(switcherObj);
console.log(switcherObj.toggles);
console.log(`Switching to ${index}`);
var a = switcherObj.show(index);
console.log(a);
}
}
};
</script>
<style scoped lang="less">
.uk-tab {
padding-left: 0;
}
</style>

View file

@ -1,5 +1,5 @@
<template> <template>
<div> <div class="uk-padding-small">
<div v-if="error" class="uk-padding-small uk-text-danger"> <div v-if="error" class="uk-padding-small uk-text-danger">
<b>{{ error }}</b> <b>{{ error }}</b>
</div> </div>

View file

@ -1,6 +1,6 @@
<template> <template>
<div <div
class="hostCard uk-card uk-card-default uk-card-hover uk-padding-remove uk-width-medium" class="hostCard uk-card uk-card-default uk-padding-remove uk-width-medium"
:class="{ 'uk-card-secondary': $store.state.globalSettings.darkMode }" :class="{ 'uk-card-secondary': $store.state.globalSettings.darkMode }"
> >
<div class="uk-card-media-top"> <div class="uk-card-media-top">
@ -32,6 +32,7 @@
<div class="host-description">{{ hostname }}:{{ port }}</div> <div class="host-description">{{ hostname }}:{{ port }}</div>
</div> </div>
<a <a
v-if="deletable"
href="#" href="#"
class="uk-icon uk-width-auto host-delete" class="uk-icon uk-width-auto host-delete"
@click="$emit('delete')" @click="$emit('delete')"
@ -70,6 +71,11 @@ export default {
port: { port: {
type: Number, type: Number,
required: true required: true
},
deletable: {
type: Boolean,
required: false,
default: true
} }
}, },

View file

@ -2,12 +2,13 @@
<div class="connectDisplay uk-padding uk-padding-remove-left"> <div class="connectDisplay uk-padding uk-padding-remove-left">
<div <div
uk-grid uk-grid
class="uk-height-1-1 uk-margin-remove uk-padding-remove" class="uk-height-1-1 uk-margin-remove uk-padding-remove uk-flex-column"
margin="0" margin="0"
> >
<div class="uk-width-auto"> <div class="uk-width-auto">
<div <div
class="uk-card uk-card-default uk-card-hover uk-padding-remove uk-width-medium connect-card-align-top" id="new-connection-card"
class="uk-card uk-card-default uk-padding-remove uk-width-medium connect-card-align-top"
> >
<div class="uk-card-body uk-padding-small"> <div class="uk-card-body uk-padding-small">
<form id="formConnectToHost" @submit.prevent="handleSubmit"> <form id="formConnectToHost" @submit.prevent="handleSubmit">
@ -94,24 +95,7 @@
<div class="uk-width-expand"> <div class="uk-width-expand">
<ul uk-accordion="multiple: true; animation: false"> <ul uk-accordion="multiple: true; animation: false">
<li class="uk-open"> <li v-show="isElectron" id="nearby-connections-grid" 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"
:port="host.port"
@connect="handleConnectButton(host)"
@delete="delSavedHost(host)"
></hostCard>
</div>
</div>
</div>
</li>
<li class="uk-open">
<a class="uk-accordion-title" href="#">Nearby devices</a> <a class="uk-accordion-title" href="#">Nearby devices</a>
<div class="uk-accordion-content"> <div class="uk-accordion-content">
<div class="uk-grid-medium uk-grid-match uk-margin-top" uk-grid> <div class="uk-grid-medium uk-grid-match uk-margin-top" uk-grid>
@ -127,6 +111,23 @@
</div> </div>
</div> </div>
</li> </li>
<li id="saved-connections-grid" 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"
:port="host.port"
@connect="handleConnectButton(host)"
@delete="delSavedHost(host)"
></hostCard>
</div>
</div>
</div>
</li>
</ul> </ul>
</div> </div>
</div> </div>
@ -154,6 +155,7 @@ export default {
data: function() { data: function() {
return { return {
isElectron: isElectron(),
localMode: true, localMode: true,
hostname: "", hostname: "",
port: 5000, port: 5000,
@ -407,8 +409,4 @@ export default {
</script> </script>
<!-- Add "scoped" attribute to limit CSS to this component only --> <!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="less"> <style scoped lang="less"></style>
.connect-card-align-top {
margin-top: 52px;
}
</style>

View file

@ -1,6 +1,6 @@
<template> <template>
<div <div
class="capture-card uk-card uk-card-default uk-card-hover uk-padding-remove uk-width-medium" class="capture-card uk-card uk-card-default uk-padding-remove uk-width-medium"
:class="{ 'uk-card-secondary': $store.state.globalSettings.darkMode }" :class="{ 'uk-card-secondary': $store.state.globalSettings.darkMode }"
> >
<div class="uk-card-media-top"> <div class="uk-card-media-top">

View file

@ -1,6 +1,6 @@
<template> <template>
<div <div
class="capture-card uk-card uk-card-primary uk-card-hover uk-padding-remove uk-width-medium" class="capture-card uk-card uk-card-primary uk-padding-remove uk-width-medium"
> >
<div class="uk-card-media-top"> <div class="uk-card-media-top">
<a href="#"> <a href="#">

View file

@ -47,39 +47,48 @@
<button <button
type="submit" type="submit"
class="uk-button uk-button-primary uk-form-small uk-float-right uk-margin-small uk-width-1-1" class="uk-button uk-button-primary uk-form-small uk-margin-small uk-width-1-1"
> >
Apply Settings Apply Settings
</button> </button>
<!--Show auto calibrate if default plugin is enabled-->
<div v-if="'recalibrate' in recalibrationLinks" class="uk-margin-small">
<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-url="recalibrationLinks.recalibrate.href"
:submit-label="'Auto-Calibrate'"
@response="onRecalibrateResponse"
@error="onRecalibrateError"
>
</taskSubmitter>
</div>
</form> </form>
<div v-if="'flatten_lens_shading_table' in recalibrationLinks" class="uk-margin-small"> <hr />
<!--Show auto calibrate if default plugin is enabled-->
<div v-if="'recalibrate' in recalibrationLinks" class="uk-margin-small">
<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-url="recalibrationLinks.recalibrate.href"
:submit-label="'Auto-Calibrate'"
@response="onRecalibrateResponse"
@error="onRecalibrateError"
>
</taskSubmitter>
</div>
<div
v-if="'flatten_lens_shading_table' in recalibrationLinks"
class="uk-margin-small"
>
<button <button
class="uk-button uk-button-danger uk-form-small uk-float-right uk-margin-small uk-margin-remove-bottom uk-width-1-1" class="uk-button uk-button-danger uk-form-small uk-width-1-1"
@click="flattenLensShadingTableRequest" @click="flattenLensShadingTableRequest"
> >
Disable flat-field correction Disable flat-field correction
</button> </button>
</div> </div>
<div v-if="'delete_lens_shading_table' in recalibrationLinks" class="uk-margin-small uk-margin-remove-top">
<div
v-if="'delete_lens_shading_table' in recalibrationLinks"
class="uk-margin-small"
>
<button <button
class="uk-button uk-button-danger uk-form-small uk-float-right uk-margin-small uk-width-1-1" class="uk-button uk-button-danger uk-form-small uk-width-1-1"
@click="deleteLensShadingTableRequest" @click="deleteLensShadingTableRequest"
> >
Adaptive flat-field correction Adaptive flat-field correction
@ -102,7 +111,7 @@ export default {
data: function() { data: function() {
return { return {
settings: null, settings: {},
recalibrationLinks: {}, recalibrationLinks: {},
isCalibrating: false isCalibrating: false
}; };
@ -147,7 +156,7 @@ export default {
// Get plugin action link // Get plugin action link
this.recalibrationLinks = foundExtension.links; this.recalibrationLinks = foundExtension.links;
} else { } else {
this.recalibrationLinks = {} this.recalibrationLinks = {};
} }
}) })
.catch(error => { .catch(error => {
@ -194,10 +203,10 @@ export default {
}, },
flattenLensShadingTableRequest: function() { flattenLensShadingTableRequest: function() {
axios.post(this.recalibrationLinks.flatten_lens_shading_table.href) axios.post(this.recalibrationLinks.flatten_lens_shading_table.href);
}, },
deleteLensShadingTableRequest: function() { deleteLensShadingTableRequest: function() {
axios.post(this.recalibrationLinks.delete_lens_shading_table.href) axios.post(this.recalibrationLinks.delete_lens_shading_table.href);
} }
} }
}; };

View file

@ -108,6 +108,14 @@ export default {
}, },
methods: { methods: {
flashStream: function() {
// Run an animation that flashes the stream (for capture feedback)
let element = this.$refs.streamDisplay;
element.classList.remove("uk-animation-fade");
element.offsetHeight; /* trigger reflow */
element.classList.add("uk-animation-fade");
},
clickMonitor: function(event) { clickMonitor: function(event) {
// Calculate steps from event coordinates and store config FOV // Calculate steps from event coordinates and store config FOV
let xCoordinate = event.offsetX; let xCoordinate = event.offsetX;
@ -124,14 +132,6 @@ export default {
this.$root.$emit("globalMoveInImageCoordinatesEvent", -xRelative, -yRelative); this.$root.$emit("globalMoveInImageCoordinatesEvent", -xRelative, -yRelative);
}, },
flashStream: function() {
// Run an animation that flashes the stream (for capture feedback)
let element = this.$refs.streamDisplay;
element.classList.remove("uk-animation-fade");
element.offsetHeight; /* trigger reflow */
element.classList.add("uk-animation-fade");
},
handleResize: function() { handleResize: function() {
// Only fires resize event after no resize in 500ms (prevents resize event spam) // Only fires resize event after no resize in 500ms (prevents resize event spam)
clearTimeout(this.resizeTimeoutId); clearTimeout(this.resizeTimeoutId);

View file

@ -1,16 +1,21 @@
import Vue from "vue"; import Vue from "vue";
import App from "./App.vue"; import App from "./App.vue";
import store from "./store"; import store from "./store";
import UIkit from "uikit"; import UIkit from "uikit";
import VueTour from "vue-tour";
import LoadScript from "vue-plugin-load-script";
require("vue-tour/dist/vue-tour.css");
// Import MD icons // Import MD icons
import "material-design-icons/iconfont/material-icons.css"; import "material-design-icons/iconfont/material-icons.css";
// Import load-script module // Use load-script module
import LoadScript from "vue-plugin-load-script";
Vue.use(LoadScript); Vue.use(LoadScript);
// Use vue-tour module
Vue.use(VueTour);
Vue.config.productionTip = false; Vue.config.productionTip = false;
Vue.mixin({ Vue.mixin({