Merge branch 'frame' into 'master'

Only build locally-served interface

See merge request openflexure/openflexure-microscope-jsclient!54
This commit is contained in:
Joel Collins 2020-06-01 12:58:43 +00:00
commit ddcf26d412
48 changed files with 205 additions and 4765 deletions

View file

@ -1,3 +0,0 @@
NODE_ENV=production
VUE_APP_PLATFORM=web
VUE_APP_TARGET=electron-renderer

View file

@ -1,69 +0,0 @@
stages:
- build
- deploy
# Cache modules in between jobs
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
# Electron app build
build:
stage: build
image: electronuserland/builder:wine
script:
# Install node dependencies
- npm install
# Create build directory
- mkdir -p "release-builds"
# Build electron app
- npm run build:app
- npm run release
artifacts:
name: "dist"
expire_in: 1 week
paths:
- "./release-builds/*.AppImage"
- "./release-builds/*.exe"
- "./release-builds/*.exe.blockmap"
- "./release-builds/latest*.yml"
- "./release-builds/beta*.yml"
only:
- merge_requests
- master
- web
- tags
# Deploy to builds.openflexure.org
deploy:
stage: deploy
dependencies:
- build
image: ubuntu:latest
before_script:
- 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
- eval $(ssh-agent -s)
- ssh-add <(echo "$SSH_PRIVATE_KEY_BATH_OPENFLEXURE_BASE64" | base64 --decode)
- mkdir -p ~/.ssh
- echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config
script:
# Install rsync if not already installed
- 'which rsync || ( apt-get update -y && apt-get install rsync -y )'
# Upload the builds folder to openflexure-microscope builds
- rsync -hrvz -e ssh release-builds/ ci-user@openflexure.bath.ac.uk:/var/www/build/openflexure-ev/
# Run update-latest.py on the build server
- ssh -t ci-user@openflexure.bath.ac.uk "/var/www/build/update-latest.py"
only:
- tags

View file

@ -1,27 +0,0 @@
const electron = require("electron");
const contextMenu = require("electron-context-menu");
const path = require("path");
const app = electron.app;
const BrowserWindow = electron.BrowserWindow;
let url = "http://localhost:8080/";
// Set the application menu
require("./menu.js");
app.on("ready", () => {
let window = new BrowserWindow({
width: 1124,
height: 800,
icon: path.join(__dirname, "/icons/png/64x64.png")
});
contextMenu({
showCopyImageAddress: true,
showSaveImageAs: true,
showInspectElement: true
});
window.loadURL(url);
});

View file

@ -1,197 +0,0 @@
// Required packages
const electron = require("electron");
const { dialog } = require("electron");
const updater = require("electron-updater");
const autoUpdater = updater.autoUpdater;
const contextMenu = require("electron-context-menu");
const ProgressBar = require("electron-progressbar");
const path = require("path");
// Attach settings store
const { store } = require("./store");
// Auto upadater
autoUpdater.autoDownload = false;
autoUpdater.on("checking-for-update", function() {});
autoUpdater.on("update-available", function(info) {
sendStatusToWindow("Update available." + info);
dialog.showMessageBox(
mainWindow,
{
type: "info",
title: "Update available",
message: "A new version of OpenFlexure eV is available.",
buttons: ["Download", "Later"]
},
buttonIndex => {
if (buttonIndex === 0) {
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) {
sendStatusToWindow("Update downloaded." + info);
dialog.showMessageBox(
mainWindow,
{
type: "info",
title: "Update downloaded",
message: "Please restart the application to apply the update.",
buttons: ["Update", "Later"]
},
buttonIndex => {
if (buttonIndex === 0) {
autoUpdater.quitAndInstall();
}
}
);
});
autoUpdater.on("update-not-available", function() {
sendStatusToWindow("Update not available.");
});
autoUpdater.on("error", function() {
sendStatusToWindow("Error in auto-updater.");
});
autoUpdater.on("download-progress", function(progressObj) {
let log_message = "Download speed: " + progressObj.bytesPerSecond;
log_message =
log_message + " - Downloaded " + parseInt(progressObj.percent) + "%";
log_message =
log_message +
" (" +
progressObj.transferred +
"/" +
progressObj.total +
")";
sendStatusToWindow(log_message);
});
function sendStatusToWindow(message) {
console.log(message);
}
// Set up the app
const app = electron.app;
const BrowserWindow = electron.BrowserWindow;
// Set the window content
let url = `file://${path.join(__dirname, "/dist/index.html")}`;
let mainWindow;
// Set the application menu
require("./menu.js");
// Handle redrawing the mainWindow
let recreatingWindowInProgress = false;
function toggleCustomTitleBar() {
// Mark window as being recreated, to prevent stopping the application
recreatingWindowInProgress = true;
// Invert the drawCustomTitleBar setting
store.set("drawCustomTitleBar", !store.get("drawCustomTitleBar"));
// Destroy old window
mainWindow.destroy();
// Create new window
createWindow();
// Mark window as no longer being recreated
recreatingWindowInProgress = false;
}
function createWindow() {
// Create the browser window.
mainWindow = new BrowserWindow({
frame: !store.get("drawCustomTitleBar"),
width: 1200,
height: 900,
icon: path.join(__dirname, "/icons/png/64x64.png"),
webPreferences: {
nodeIntegration: true
}
});
// Make a context menu
contextMenu({
showCopyImageAddress: true,
showSaveImageAs: true,
showInspectElement: false
});
// Load window contents
mainWindow.loadURL(url);
// Check for updates
autoUpdater.checkForUpdates();
// Emitted when the window is closed.
mainWindow.on("closed", function() {
mainWindow = null; // Dereference the window object
});
mainWindow.webContents.on("new-window", function(event, url) {
event.preventDefault();
electron.shell.openExternal(url);
});
}
// Some APIs can only be used after this event occurs.
app.on("ready", () => {
createWindow();
});
// Quit when all windows are closed.
app.on("window-all-closed", function() {
if (process.platform !== "darwin" && recreatingWindowInProgress != true) {
app.quit();
}
});
app.on("activate", function() {
if (mainWindow === null) {
createWindow();
}
});
// Export toggleCustomTitleBar for use in ./menu.js
module.exports.toggleCustomTitleBar = toggleCustomTitleBar;

View file

@ -1,12 +0,0 @@
extends: app/builder-config-base.yaml
win:
target:
- appx
icon: app/icons/win/icon.ico
appx:
applicationId: OpenFlexureEV
identityName: 60425J.T.Collins.OpenFlexureeV
publisher: CN=CC867BEF-0715-4D48-8035-8881993DBB68
publisherDisplayName: J. T. Collins
backgroundColor: '#C5247F'
electronVersion: 4.1.5

View file

@ -1,30 +0,0 @@
directories:
output: release-builds
buildResources: app/resources
appId: org.openflexure.ev
artifactName: ${name}-${version}-${os}-${arch}.${ext}
asar: true
publish:
- provider: generic
url: https://build.openflexure.org/openflexure-ev/
files:
- filter:
- '**/*'
- '!**/node_modules/*/{CHANGELOG.md,README.md,README,readme.md,readme}'
- '!**/node_modules/*/{test,__tests__,tests,powered-test,example,examples}'
- '!**/node_modules/*.d.ts'
- '!**/node_modules/.bin'
- '!**/*.{iml,o,hprof,orig,pyc,pyo,rbc,swp,csproj,sln,xproj}'
- '!.editorconfig'
- '!**/._*'
- '!**/{.DS_Store,.git,.hg,.svn,CVS,RCS,SCCS,.gitignore,.gitattributes}'
- '!**/{__pycache__,thumbs.db,.flowconfig,.idea,.vs,.nyc_output}'
- '!**/{appveyor.yml,.travis.yml,circle.yml}'
- '!**/{npm-debug.log,yarn.lock,.yarn-integrity,.yarn-metadata.json}'
- '!**/.env.*'
- '!**/{.idea,.vscode}${/*}'
- '!**/app/installers${/*}'
- '!**/hooks${/*}'
- '!**/platforms${/*}'
- '!**/release-builds${/*}'
- '!src${/*}'

View file

@ -1,13 +0,0 @@
extends: app/builder-config-base.yaml
linux:
target:
- target: deb
arch:
- armv7l
- target: appImage
arch:
- armv7l
icon: app/icons/png/
category: Science
deb:
fpm: ['--architecture', 'armhf']

View file

@ -1,26 +0,0 @@
extends: app/builder-config-base.yaml
win:
target:
- NSIS
icon: app/icons/win/icon.ico
linux:
target:
- target: deb
arch:
- x64
- target: appImage
arch:
- x64
icon: app/icons/png/
category: Science
mac:
icon: app/icons/mac/icon.icns
category: public.app-category.utilities
dmg:
contents:
- x: 110
y: 150
- x: 240
y: 150
type: link
path: /Applications

View file

@ -1,3 +0,0 @@
provider: generic
url: https://build.openflexure.org/openflexure-ev/
channel: latest

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 885 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 733 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 199 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 353 KiB

View file

@ -1,103 +0,0 @@
#!/bin/bash
packageName='openflexure-ev'
scriptPath=$(dirname "$0")
packagePath="$(dirname "$scriptPath")"
packageJson="$packagePath/package.json"
installerPath="$packagePath/openflexure-ev-win.exe"
installerHash=`sha256sum ${installerPath} | awk '{ print $1 }'`
echo "$installerHash"
outpath="$packagePath/release-builds/choco"
toolspath="$outpath/tools"
mkdir -p "$toolspath"
echo "$scriptPath"
echo "$packageJson"
echo "$outpath"
# Get package version from package.json
packageVersion=$(grep version < "$packageJson" | head -1 \
| awk -F: '{ print $2 }' \
| sed 's/[",]//g' \
| tr -d ':space:')
echo "Package version: $packageVersion"
# Convert into a nupkg-safe version string
majVer="$(echo "$packageVersion" | cut -d'-' -f1)"
minVer="$(echo "$packageVersion" | cut -d'-' -f2)"
if [ "$majVer" = "$minVer" ]; then
version="$majVer"
else
minVer="${minVer//.}"
version="$majVer-$minVer"
fi
echo "Nupkg version: $version"
# Build installer URL
pipelineURL="$CI_PROJECT_URL/-/jobs/artifacts/$CI_COMMIT_REF_NAME/raw"
instURL="$pipelineURL/openflexure-ev-win.exe?job=package"
nuspecURL="$CI_JOB_URL/artifacts/browse"
echo "$instURL"
# Build nuspec
cat > "$outpath/openflexure-ev.nuspec" <<- EOL
<?xml version="1.0"?>
<package xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<metadata>
<id>$packageName</id>
<title>OpenFlexure eV</title>
<version>$version</version>
<authors>OpenFlexure</authors>
<owners>Joel Collins</owners>
<summary>OpenFlexure Microscope client</summary>
<description>An electron-based user client for the OpenFlexure Microscope Server</description>
<projectUrl>https://www.openflexure.org/</projectUrl>
<docsUrl>https://www.openflexure.org/projects/microscope/</docsUrl>
<bugTrackerUrl>https://gitlab.com/openflexure/openflexure-microscope-jsclient/issues</bugTrackerUrl>
<projectSourceUrl>https://gitlab.com/openflexure/openflexure-microscope-jsclient/</projectSourceUrl>
<packageSourceUrl>$nuspecURL</packageSourceUrl>
<tags>openflexure microscope ev</tags>
<licenseUrl>https://gitlab.com/openflexure/openflexure-microscope-jsclient/raw/master/LICENSE</licenseUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<iconUrl>https://glcdn.githack.com/openflexure/openflexure-microscope-jsclient/raw/v1.1.0/app/icons/png/512x512.png</iconUrl>
</metadata>
<files>
<file src="tools/**" target="tools" />
</files>
</package>
EOL
# Build installer PS1 content
cat > "$toolspath/chocolateyInstall.ps1" <<- EOL
\$ErrorActionPreference = "Stop";
\$toolsDir = "\$(Split-Path -parent $MyInvocation.MyCommand.Definition)"
\$url = "$instURL"
\$packageArgs = @{
packageName = "openflexure-ev"
unzipLocation = \$toolsDir
fileType = "exe"
url = \$url
url64bit = \$url
softwareName = "OpenFlexure eV"
checksum = "${installerHash}"
checksumType = "sha256"
silentArgs = "/S"
}
Install-ChocolateyPackage @packageArgs
EOL

View file

@ -1,122 +0,0 @@
const { app, shell, Menu } = require("electron");
const updater = require("electron-updater");
const autoUpdater = updater.autoUpdater;
const path = require("path");
const openAboutWindow = require("about-window").default;
const main = require("./app");
const { store } = require("./store");
const template = [
{
label: "Edit",
submenu: [
{ role: "undo" },
{ role: "redo" },
{ type: "separator" },
{ role: "cut" },
{ role: "copy" },
{ role: "paste" },
{ role: "delete" },
{ role: "selectall" }
]
},
{
label: "View",
submenu: [
{ role: "reload" },
{ role: "forcereload" },
{ role: "toggledevtools" },
{ type: "separator" },
{ role: "togglefullscreen" },
{ type: "separator" },
{
type: "checkbox",
checked: store.get("drawCustomTitleBar"),
label: "Custom titlebar",
click() {
main.toggleCustomTitleBar();
}
}
]
},
{
role: "window",
submenu: [{ role: "minimize" }, { role: "close" }]
},
{
role: "help",
submenu: [
{
label: "About",
click() {
openAboutWindow({
package_json_dir: path.join(__dirname, ".."),
icon_path: path.join(__dirname, "/icons/png/512x512.png"),
bug_report_url:
"https://gitlab.com/openflexure/openflexure-microscope-jsclient/issues",
homepage:
"https://gitlab.com/openflexure/openflexure-microscope-jsclient"
});
}
},
{
label: "Homepage",
click() {
shell.openExternal("https://openflexure.org");
}
},
{ type: "separator" },
{
label: "Check for Updates",
click() {
autoUpdater.checkForUpdates();
}
}
]
}
];
if (process.platform === "darwin") {
template.unshift({
label: app.getName(),
submenu: [
{ role: "about" },
{ type: "separator" },
{ role: "services" },
{ type: "separator" },
{ role: "hide" },
{ role: "hideothers" },
{ role: "unhide" },
{ type: "separator" },
{ role: "quit" }
]
});
// Edit menu
template[1].submenu.push(
{ type: "separator" },
{
label: "Speech",
submenu: [{ role: "startspeaking" }, { role: "stopspeaking" }]
}
);
// Window menu
template[3].submenu = [
{ role: "close" },
{ role: "minimize" },
{ role: "zoom" },
{ type: "separator" },
{ role: "front" }
];
} else {
template.unshift({
label: "File",
submenu: [{ role: "quit" }]
});
}
const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

View file

@ -1,9 +0,0 @@
const Store = require("electron-store");
const store = new Store();
if (store.has("drawCustomTitleBar") !== true) {
// Default to false if on MacOS, otherwise true
store.set("drawCustomTitleBar", process.platform === "win32" ? true : false);
}
module.exports.store = store;

3641
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -2,34 +2,18 @@
"name": "openflexure-ev",
"version": "2.1.2",
"private": true,
"description": "An electron-based user client for the OpenFlexure Microscope Server",
"description": "User client for the OpenFlexure Microscope Server",
"author": "OpenFlexure <contact@openflexure.org> (https://www.openflexure.org)",
"homepage": "https://gitlab.com/openflexure/openflexure-microscope-jsclient",
"productName": "OpenFlexure eV",
"publisher": "Bath Open Instrumentation Group",
"displayName": "OpenFlexure eV",
"license": "GNU General Public License v3.0 ",
"main": "./app/app.js",
"scripts": {
"build:web": "vue-cli-service build --mode production.web",
"build:app": "vue-cli-service build --mode production.app",
"serve:web": "vue-cli-service serve --mode development.web",
"start": "npm run build:app && electron app/app.js",
"dist:linux": "electron-builder --linux --config app/builder-config-x86_64.yaml",
"dist:raspi": "electron-builder --linux --config app/builder-config-raspi.yaml",
"dist:win": "electron-builder --win --config app/builder-config-x86_64.yaml",
"dist:dmg": "electron-builder --mac --config app/builder-config-x86_64.yaml",
"dist:appx": "electron-builder --win --config app/builder-config-appx.yaml",
"release": "electron-builder --linux --config app/builder-config-raspi.yaml & electron-builder --win --linux --config app/builder-config-x86_64.yaml",
"make-icons": "electron-icon-builder --input=./app/icons/icon_base.png --output=./app/"
"build": "vue-cli-service build --mode production",
"serve": "vue-cli-service serve --mode development"
},
"dependencies": {
"about-window": "^1.13.2",
"custom-electron-titlebar": "^3.2.2",
"electron-context-menu": "^0.15.2",
"electron-progressbar": "^1.2.0",
"electron-store": "^5.1.1",
"electron-updater": "^4.3.1",
"material-design-icons": "^3.0.1"
},
"devDependencies": {
@ -38,9 +22,6 @@
"axios": "^0.19.2",
"babel-eslint": "^10.1.0",
"css-loader": "^3.5.3",
"electron": "^6.1.12",
"electron-builder": "^21.2.0",
"electron-icon-builder": "^1.0.2",
"eslint": "^6.8.0",
"eslint-config-prettier": "^6.11.0",
"eslint-plugin-prettier": "^3.1.3",
@ -56,9 +37,6 @@
"vue-tour": "^1.3.1",
"vuex": "^3.4.0"
},
"optionalDependencies": {
"electron-windows-store": "^2.1.0"
},
"postcss": {
"plugins": {
"autoprefixer": {}

View file

@ -4,9 +4,11 @@
class="uk-height-1-1 uk-margin-remove uk-padding-remove"
:class="handleTheme"
>
<div id="tour-header"></div>
<appContent />
<loadingContent v-if="!$store.getters.ready" />
<div v-if="$store.getters.ready" id="tour-header"></div>
<appContent v-if="$store.getters.ready" />
<v-tour
v-if="$store.getters.ready"
name="guidedTour"
:steps="tourSteps"
:callbacks="tourCallbacks"
@ -16,9 +18,11 @@
</template>
<script>
import isElectron from "./modules/isElectron";
// Import components
import appContent from "./components/appContent.vue";
import loadingContent from "./components/loadingContent.vue";
import axios from "axios";
// Key Codes
const keyCodes = {
@ -40,15 +44,16 @@ export default {
name: "App",
components: {
appContent
appContent,
loadingContent
},
data: function() {
return {
appAvailable: false,
keysDown: {},
systemDark: undefined,
themeObserver: undefined,
isElectron: isElectron(),
tourCallbacks: {
onStop: () => {
this.setLocalStorageObj("completedTour", true);
@ -94,33 +99,6 @@ export default {
placement: "bottom"
}
},
{
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
? [
{
target: "#nearby-connections-grid",
header: {
title: "Nearby microscopes"
},
content: `Connect to microscopes found on your network`
}
]
: []),
{
target: "#saved-connections-grid",
header: {
title: "Saved microscopes"
},
content: `Connect to your saved microscopes for faster access`
},
{
target: "#gallery-tab-icon",
header: {
@ -175,8 +153,11 @@ export default {
this.systemDark = false;
}
});
// Check connection to API
this.checkConnection();
// Handle guided tour
// If the user has already completed or skipped the guided tour
// TODO: Only run this if connected to the API
var completedTour = this.getLocalStorageObj("completedTour") || false;
if (!completedTour) {
this.$tours["guidedTour"].start();
@ -189,6 +170,16 @@ export default {
window.addEventListener("keydown", this.keyDownMonitor);
window.addEventListener("keyup", this.keyUpMonitor);
window.addEventListener("wheel", this.wheelMonitor);
// Watch for origin changes
this.unwatchOriginFunction = this.$store.watch(
(state, getters) => {
return getters.uriV2;
},
uriV2 => {
this.checkConnection();
console.log(uriV2);
}
);
},
beforeDestroy: function() {
@ -200,9 +191,28 @@ export default {
window.removeEventListener("keydown", this.keyDownMonitor);
window.removeEventListener("keyup", this.keyUpMonitor);
window.removeEventListener("wheel", this.wheelMonitor);
// Remove origin watcher
this.unwatchOriginFunction();
},
methods: {
checkConnection: function() {
var uriV2 = this.$store.getters.uriV2;
this.$store.commit("changeWaiting", true);
axios
.get(uriV2)
.then(() => {
this.$store.commit("setConnected");
this.$store.commit("setErrorMessage", null);
})
.catch(error => {
this.$store.commit("setErrorMessage", error);
})
.finally(() => {
this.$store.commit("changeWaiting", false);
});
},
handleExit: function() {
console.log("Triggered beforeunload");
this.$root.$emit("globalTogglePreview", false);

View file

@ -16,14 +16,15 @@
class="uk-flex uk-flex-column uk-padding-remove uk-width-auto uk-height-1-1 uk-text-center"
>
<tabIcon
id="connect-tab-icon"
tab-i-d="connect"
:require-connection="false"
id="view-tab-icon"
tab-i-d="view"
:require-connection="true"
:current-tab="currentTab"
@set-tab="setTab"
>
<i class="material-icons">bug_report</i>
<i class="material-icons">visibility</i>
</tabIcon>
<tabIcon
id="gallery-tab-icon"
tab-i-d="gallery"
@ -36,15 +37,6 @@
<hr />
<tabIcon
id="view-tab-icon"
tab-i-d="view"
:require-connection="true"
:current-tab="currentTab"
@set-tab="setTab"
>
<i class="material-icons">visibility</i>
</tabIcon>
<tabIcon
id="navigate-tab-icon"
tab-i-d="navigate"
@ -95,11 +87,11 @@
class="uk-padding-remove uk-height-1-1 uk-width-expand"
>
<tabContent
tab-i-d="connect"
:require-connection="false"
tab-i-d="view"
:require-connection="true"
:current-tab="currentTab"
>
<connectContent />
<viewContent />
</tabContent>
<tabContent
tab-i-d="gallery"
@ -108,13 +100,6 @@
>
<galleryContent />
</tabContent>
<tabContent
tab-i-d="view"
:require-connection="true"
:current-tab="currentTab"
>
<viewContent />
</tabContent>
<tabContent
tab-i-d="navigate"
:require-connection="true"
@ -164,7 +149,6 @@ import tabIcon from "./genericComponents/tabIcon";
import tabContent from "./genericComponents/tabContent";
// Import new content components
import connectContent from "./tabContentComponents/connectContent.vue";
import navigateContent from "./tabContentComponents/navigateContent.vue";
import captureContent from "./tabContentComponents/captureContent.vue";
import viewContent from "./tabContentComponents/viewContent.vue";
@ -182,7 +166,6 @@ export default {
components: {
tabIcon,
tabContent,
connectContent,
navigateContent,
captureContent,
viewContent,
@ -195,7 +178,7 @@ export default {
data: function() {
return {
plugins: [],
currentTab: "connect",
currentTab: "view",
unwatchStoreFunction: null
};
},

View file

@ -0,0 +1,33 @@
<template>
<div class="uk-flex uk-flex-column uk-flex-center uk-height-1-1">
<div
v-if="$store.state.waiting"
class="uk-align-center"
uk-spinner="ratio: 3"
></div>
<div v-if="$store.state.waiting" class="uk-align-center">Loading...</div>
<i class="material-icons uk-align-center error-icon">error_outline</i>
<div v-if="$store.state.error" class="uk-align-center">
{{ $store.state.error }}
</div>
</div>
</template>
<script>
// Export main app
export default {
name: "LoadingContent",
components: {},
data: function() {
return {};
}
};
</script>
<style scoped lang="less">
.error-icon {
font-size: 120px;
}
</style>

View file

@ -1,24 +0,0 @@
<template>
<!-- Grid managing tab content -->
<div uk-grid class="uk-height-1-1 uk-margin-remove uk-padding-remove">
<div class="view-component uk-width-expand">
<connectDisplay />
</div>
</div>
</template>
<script>
import connectDisplay from "../viewComponents/connectDisplay.vue";
export default {
name: "ConnectContent",
components: {
connectDisplay
},
data: function() {
return {};
}
};
</script>

View file

@ -1,410 +0,0 @@
<template>
<div class="connectDisplay uk-padding uk-padding-remove-left">
<div
uk-grid
class="uk-height-1-1 uk-margin-remove uk-padding-remove uk-flex-column"
margin="0"
>
<div class="uk-width-auto">
<div
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">
<form id="formConnectToHost" @submit.prevent="handleSubmit">
<div class="uk-form-controls uk-margin">
<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"
: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
>
<div class="uk-form-controls">
<input
id="form-stacked-text"
v-model="computedPort"
class="uk-input uk-form-small"
type="number"
value="5000"
/>
</div>
</div>
</form>
</div>
<div class="uk-card-footer uk-padding-small">
<button
type="submit"
form="formConnectToHost"
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"
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"
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 v-show="isElectron" id="nearby-connections-grid" 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"
:port="host.port"
:deletable="false"
@connect="handleConnectButton(host)"
></hostCard>
</div>
</div>
</div>
</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>
</div>
</div>
</div>
</template>
<script>
// Import mDNS package if running in Electron
import isElectron from "../../modules/isElectron";
if (isElectron()) {
var mdns = require("mdns-js");
}
import axios from "axios";
import hostCard from "./connectComponents/hostCard.vue";
// Export main app
export default {
name: "ConnectDisplay",
components: {
hostCard
},
data: function() {
return {
isElectron: isElectron(),
localMode: true,
hostname: "",
port: 5000,
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);
// Try loading savedHosts from localStorage. If null, don't change.
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"));
// Start discovering services when ready
browser.on("ready", function() {
browser.discover();
});
browser.on("update", data => {
console.log(data);
// We will be checking if it's a valid openflexure device
var validDevice = false;
// Go through each type of the host, make valid if 'openflexure' appears
data.type.forEach(element => {
console.log(element);
if (element.name === "openflexure") {
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);
}
console.log(this.foundHosts);
});
}
},
methods: {
connectToHost: function(host) {
console.log(host);
// Set the global form values
this.hostname = host.hostname;
this.port = host.port;
// Commit the hostname and port to store
this.$store.commit("resetState");
this.$store.commit("changeHost", [host.hostname, host.port]);
this.$store.commit("changeWaiting", true);
// Try to get list of available routes
axios
.get(`${this.$store.getters.baseUri}/routes`)
.then(response => {
console.log(response.data);
// If the /api/v2 route is available
if (Object.keys(response.data).includes("/api/v2/")) {
console.log("API v2 available");
// Tell Vuex store that we're connected
this.$store.commit("setConnected");
// Check client and server match
this.checkServerVersion();
} else {
// Error and no-connect if API v2 is missing
var ApiVersionError = `Your microscope is running an old API version.\
You must update your microscope software to continue.\
<br><br>
For API upgrades, burning the latest Raspbian-OpenFlexure to your SD card is recommended.\
<br><br>
<b>Downloads and instructions can be found <a target="_blank" href="https://openflexure.org/projects/microscope/install">here</a></b>`;
this.modalDialog("API upgrade required", ApiVersionError);
this.$store.commit("setError");
}
})
.catch(error => {
this.$store.commit("setError");
this.modalError(error); // Let mixin handle error
});
},
handleSubmit: function() {
// Split host and port if needed
if (this.hostname.includes(":")) {
this.port = this.computedPort;
this.hostname = this.hostname.split(":")[0];
}
// Handle auto-local-connect
var hostname;
if (this.localMode) {
hostname = "localhost";
} else {
hostname = this.hostname;
}
var selectedHost = {
hostname: hostname,
port: this.port
};
this.connectToHost(selectedHost);
},
handleConnectButton: function(host) {
this.computedLocalMode = false;
this.connectToHost(host);
},
checkServerVersion: function() {
var versionUri = `${this.$store.getters.baseUri}/api/v2/instrument/configuration/application/version`;
axios
.get(versionUri)
.then(response => {
var serverVersion = response.data;
var clientVersion = process.env.PACKAGE.version;
var clientVersionMajor = clientVersion.substring(0, 3);
console.log(clientVersionMajor);
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.\
Please update your microscope software.\
<b>Client version:</b> ${clientVersion}<br> \
<b>Server version:</b> ${serverVersion}<br><br>`;
if (serverVersion < 1.1) {
versionWarning =
versionWarning +
"You will need to install a newer server version on a clean SD card.";
} else {
versionWarning =
versionWarning +
"Run <b><tt>ofm update</tt></b> and then <b><tt>ofm upgrade</tt></b> on your microscope.";
}
this.modalDialog("Version mismatch", versionWarning);
}
})
.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]);
},
delSavedHost: function(host) {
console.log(host);
var index = this.savedHosts.indexOf(host);
if (index > -1) {
this.savedHosts.splice(index, 1);
}
},
saveHost: function() {
// URI to get hostname directly from settings
var hostnameUri = `${this.$store.getters.baseUri}/api/v2/instrument/settings/name`;
axios
.get(hostnameUri)
.then(response => {
// Get device name from response
var deviceName = response.data;
// We use unshift instead of push to add the entry to the beginning of the array
this.savedHosts.unshift({
name: deviceName,
hostname: this.$store.state.host,
port: this.$store.state.port
});
})
.catch(error => {
this.modalError(error); // Let mixin handle error
});
}
}
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="less"></style>

View file

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

View file

@ -76,7 +76,15 @@ Vue.mixin({
},
modalError: function(error) {
console.log(error);
var errormsg = this.getErrorMessage(error);
this.$store.commit("setErrorMessage", errormsg);
UIkit.notification({
message: `${errormsg}`,
status: "danger"
});
},
getErrorMessage: function(error) {
var errormsg = "";
// If a response was obtained
@ -100,11 +108,7 @@ Vue.mixin({
errormsg = `${error.message}`;
console.log(errormsg);
}
this.$store.commit("setError", errormsg);
UIkit.notification({
message: `${errormsg}`,
status: "danger"
});
return errormsg;
},
showModalElement: function(element) {

View file

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

View file

@ -5,8 +5,7 @@ Vue.use(Vuex);
export default new Vuex.Store({
state: {
host: "",
port: 5000,
origin: window.location.origin,
available: false,
waiting: false,
error: "",
@ -20,9 +19,8 @@ export default new Vuex.Store({
},
mutations: {
changeHost(state, [host, port]) {
state.host = host;
state.port = port;
changeOrigin(state, origin) {
state.origin = origin;
},
changeWaiting(state, waiting) {
state.waiting = waiting;
@ -39,8 +37,7 @@ export default new Vuex.Store({
state.waiting = false;
state.available = true;
},
setError(state, msg) {
state.waiting = false;
setErrorMessage(state, msg) {
state.error = msg;
},
addStream(state, id) {
@ -54,8 +51,8 @@ export default new Vuex.Store({
actions: {},
getters: {
uriV2: state => `http://${state.host}:${state.port}/api/v2`,
baseUri: state => `http://${state.host}:${state.port}`,
uriV2: state => `${state.origin}/api/v2`,
baseUri: state => state.origin,
ready: state => state.available
}
});

View file

@ -1,32 +1,15 @@
var webpack = require("webpack");
module.exports = {
configureWebpack: config => {
(config.target = process.env.VUE_APP_TARGET || "web"),
(config.entry =
process.env.VUE_APP_TARGET == "electron-renderer"
? {
main: "./src/main.js",
app: "./src/main.app.js"
}
: {
main: "./src/main.js"
}),
config.plugins.push(
new webpack.DefinePlugin({
"process.env": {
PACKAGE: JSON.stringify(require("./package.json"))
}
})
);
configureWebpack: {
plugins: [
new webpack.DefinePlugin({
"process.env": {
PACKAGE: JSON.stringify(require("./package.json"))
}
})
]
},
outputDir: (function() {
if (process.env.VUE_APP_TARGET == "electron-renderer") {
return "./app/dist";
} else {
return "./dist";
}
})(),
publicPath: ""
};