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

24
.eslintrc.js Normal file
View file

@ -0,0 +1,24 @@
module.exports = {
root: true,
env: {
node: true
},
extends: [
"plugin:vue/recommended",
"eslint:recommended",
"prettier/vue",
"plugin:prettier/recommended"
],
rules: {
"vue/component-name-in-template-casing": ["error", "PascalCase"],
"no-console": process.env.NODE_ENV === "production" ? "error" : "off",
"no-debugger": process.env.NODE_ENV === "production" ? "error" : "off"
},
globals: {
$nuxt: true
},
parserOptions: {
parser: "babel-eslint"
}
};

View file

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

View file

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

View file

@ -1,129 +1,122 @@
const { app, shell, Menu } = require('electron')
const { app, shell, Menu } = require("electron");
const updater = require("electron-updater");
const autoUpdater = updater.autoUpdater;
const path = require('path')
const path = require("path");
const openAboutWindow = require('about-window').default
const openAboutWindow = require("about-window").default;
const main = require('./app')
const { store } = require('./store')
const main = require("./app");
const { store } = require("./store");
const template = [
{
label: 'Edit',
label: "Edit",
submenu: [
{ role: 'undo' },
{ role: 'redo' },
{ type: 'separator' },
{ role: 'cut' },
{ role: 'copy' },
{ role: 'paste' },
{ role: 'delete' },
{ role: 'selectall' }
{ role: "undo" },
{ role: "redo" },
{ type: "separator" },
{ role: "cut" },
{ role: "copy" },
{ role: "paste" },
{ role: "delete" },
{ role: "selectall" }
]
},
{
label: 'View',
label: "View",
submenu: [
{ role: 'reload' },
{ role: 'forcereload' },
{ role: 'toggledevtools' },
{ type: 'separator' },
{ role: 'togglefullscreen' },
{ type: 'separator' },
{ role: "reload" },
{ role: "forcereload" },
{ role: "toggledevtools" },
{ type: "separator" },
{ role: "togglefullscreen" },
{ type: "separator" },
{
type: 'checkbox',
checked: store.get('drawCustomTitleBar'),
label: 'Custom titlebar',
click () {
main.toggleCustomTitleBar()
type: "checkbox",
checked: store.get("drawCustomTitleBar"),
label: "Custom titlebar",
click() {
main.toggleCustomTitleBar();
}
}
]
},
{
role: 'window',
submenu: [
{ role: 'minimize' },
{ role: 'close' }
]
role: "window",
submenu: [{ role: "minimize" }, { role: "close" }]
},
{
role: 'help',
role: "help",
submenu: [
{
label: 'About',
click () {
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",
})
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')
label: "Homepage",
click() {
shell.openExternal("https://openflexure.org");
}
},
{ type: 'separator' },
{ type: "separator" },
{
label: 'Check for Updates',
click () {
label: "Check for Updates",
click() {
autoUpdater.checkForUpdates();
}
}
]
}
]
];
if (process.platform === 'darwin') {
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' }
{ 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' },
{ type: "separator" },
{
label: 'Speech',
submenu: [
{ role: 'startspeaking' },
{ role: 'stopspeaking' }
]
label: "Speech",
submenu: [{ role: "startspeaking" }, { role: "stopspeaking" }]
}
)
);
// Window menu
template[3].submenu = [
{ role: 'close' },
{ role: 'minimize' },
{ role: 'zoom' },
{ type: 'separator' },
{ role: 'front' }
]
}
else {
{ role: "close" },
{ role: "minimize" },
{ role: "zoom" },
{ type: "separator" },
{ role: "front" }
];
} else {
template.unshift({
label: 'File',
submenu: [
{ role: 'quit' }
]
})
label: "File",
submenu: [{ role: "quit" }]
});
}
const menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu)
const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);

View file

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

627
package-lock.json generated
View file

@ -1538,6 +1538,12 @@
"integrity": "sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA==",
"dev": true
},
"acorn-jsx": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.1.0.tgz",
"integrity": "sha512-tMUqwBWfLFbJbizRmEcWSLw6HnFzfdJs2sOJEOwwtVPMoH/0Ay+E703oZz78VSXZiiDcZrQ5XKjPIUQixhmgVw==",
"dev": true
},
"acorn-walk": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.2.0.tgz",
@ -2097,6 +2103,31 @@
}
}
},
"babel-eslint": {
"version": "10.0.3",
"resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.0.3.tgz",
"integrity": "sha512-z3U7eMY6r/3f3/JB9mTsLjyxrv0Yb1zb8PCWCLpguxfCzBIZUwy23R1t/XKewP+8mEN2Ck8Dtr4q20z6ce6SoA==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.0.0",
"@babel/parser": "^7.0.0",
"@babel/traverse": "^7.0.0",
"@babel/types": "^7.0.0",
"eslint-visitor-keys": "^1.0.0",
"resolve": "^1.12.0"
},
"dependencies": {
"resolve": {
"version": "1.12.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz",
"integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==",
"dev": true,
"requires": {
"path-parse": "^1.0.6"
}
}
}
},
"babel-loader": {
"version": "8.0.6",
"resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.0.6.tgz",
@ -2814,8 +2845,7 @@
"chardet": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
"optional": true
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA=="
},
"check-types": {
"version": "8.0.3",
@ -2981,8 +3011,7 @@
"cli-width": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz",
"integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=",
"optional": true
"integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk="
},
"clipboardy": {
"version": "2.1.0",
@ -4150,6 +4179,12 @@
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
"dev": true
},
"deep-is": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
"integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
"dev": true
},
"deepmerge": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-1.5.2.tgz",
@ -4502,6 +4537,15 @@
"buffer-indexof": "^1.0.0"
}
},
"doctrine": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
"integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
"dev": true,
"requires": {
"esutils": "^2.0.2"
}
},
"dom-converter": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz",
@ -5131,6 +5175,249 @@
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
},
"eslint": {
"version": "6.6.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-6.6.0.tgz",
"integrity": "sha512-PpEBq7b6qY/qrOmpYQ/jTMDYfuQMELR4g4WI1M/NaSDDD/bdcMb+dj4Hgks7p41kW2caXsPsEZAEAyAgjVVC0g==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.0.0",
"ajv": "^6.10.0",
"chalk": "^2.1.0",
"cross-spawn": "^6.0.5",
"debug": "^4.0.1",
"doctrine": "^3.0.0",
"eslint-scope": "^5.0.0",
"eslint-utils": "^1.4.3",
"eslint-visitor-keys": "^1.1.0",
"espree": "^6.1.2",
"esquery": "^1.0.1",
"esutils": "^2.0.2",
"file-entry-cache": "^5.0.1",
"functional-red-black-tree": "^1.0.1",
"glob-parent": "^5.0.0",
"globals": "^11.7.0",
"ignore": "^4.0.6",
"import-fresh": "^3.0.0",
"imurmurhash": "^0.1.4",
"inquirer": "^7.0.0",
"is-glob": "^4.0.0",
"js-yaml": "^3.13.1",
"json-stable-stringify-without-jsonify": "^1.0.1",
"levn": "^0.3.0",
"lodash": "^4.17.14",
"minimatch": "^3.0.4",
"mkdirp": "^0.5.1",
"natural-compare": "^1.4.0",
"optionator": "^0.8.2",
"progress": "^2.0.0",
"regexpp": "^2.0.1",
"semver": "^6.1.2",
"strip-ansi": "^5.2.0",
"strip-json-comments": "^3.0.1",
"table": "^5.2.3",
"text-table": "^0.2.0",
"v8-compile-cache": "^2.0.3"
},
"dependencies": {
"ansi-escapes": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.2.1.tgz",
"integrity": "sha512-Cg3ymMAdN10wOk/VYfLV7KCQyv7EDirJ64500sU7n9UlmioEtDuU5Gd+hj73hXSU/ex7tHJSssmyftDdkMLO8Q==",
"dev": true,
"requires": {
"type-fest": "^0.5.2"
}
},
"cli-cursor": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
"integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==",
"dev": true,
"requires": {
"restore-cursor": "^3.1.0"
}
},
"emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true
},
"eslint-scope": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz",
"integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==",
"dev": true,
"requires": {
"esrecurse": "^4.1.0",
"estraverse": "^4.1.1"
}
},
"figures": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/figures/-/figures-3.1.0.tgz",
"integrity": "sha512-ravh8VRXqHuMvZt/d8GblBeqDMkdJMBdv/2KntFH+ra5MXkO7nxNKpzQ3n6QD/2da1kH0aWmNISdvhM7gl2gVg==",
"dev": true,
"requires": {
"escape-string-regexp": "^1.0.5"
}
},
"glob-parent": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz",
"integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==",
"dev": true,
"requires": {
"is-glob": "^4.0.1"
}
},
"ignore": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
"integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
"dev": true
},
"import-fresh": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.1.0.tgz",
"integrity": "sha512-PpuksHKGt8rXfWEr9m9EHIpgyyaltBy8+eF6GJM0QCAxMgxCfucMF3mjecK2QsJr0amJW7gTqh5/wht0z2UhEQ==",
"dev": true,
"requires": {
"parent-module": "^1.0.0",
"resolve-from": "^4.0.0"
}
},
"inquirer": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.0.0.tgz",
"integrity": "sha512-rSdC7zelHdRQFkWnhsMu2+2SO41mpv2oF2zy4tMhmiLWkcKbOAs87fWAJhVXttKVwhdZvymvnuM95EyEXg2/tQ==",
"dev": true,
"requires": {
"ansi-escapes": "^4.2.1",
"chalk": "^2.4.2",
"cli-cursor": "^3.1.0",
"cli-width": "^2.0.0",
"external-editor": "^3.0.3",
"figures": "^3.0.0",
"lodash": "^4.17.15",
"mute-stream": "0.0.8",
"run-async": "^2.2.0",
"rxjs": "^6.4.0",
"string-width": "^4.1.0",
"strip-ansi": "^5.1.0",
"through": "^2.3.6"
}
},
"is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"dev": true
},
"mimic-fn": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
"dev": true
},
"mute-stream": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
"integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==",
"dev": true
},
"onetime": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz",
"integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==",
"dev": true,
"requires": {
"mimic-fn": "^2.1.0"
}
},
"resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
"dev": true
},
"restore-cursor": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
"integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==",
"dev": true,
"requires": {
"onetime": "^5.1.0",
"signal-exit": "^3.0.2"
}
},
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
"dev": true
},
"string-width": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.1.0.tgz",
"integrity": "sha512-NrX+1dVVh+6Y9dnQ19pR0pP4FiEIlUvdTGn8pw6CKTNq5sgib2nIhmUNT5TAmhWmvKr3WcxBcP3E8nWezuipuQ==",
"dev": true,
"requires": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^5.2.0"
}
},
"strip-json-comments": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz",
"integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==",
"dev": true
},
"type-fest": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.5.2.tgz",
"integrity": "sha512-DWkS49EQKVX//Tbupb9TFa19c7+MK1XmzkrZUR8TAktmE/DizXoaoJV6TZ/tSIPXipqNiRI6CyAe7x69Jb6RSw==",
"dev": true
}
}
},
"eslint-config-prettier": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.5.0.tgz",
"integrity": "sha512-cjXp8SbO9VFGW/Z7mbTydqS9to8Z58E5aYhj3e1+Hx7lS9s6gL5ILKNpCqZAFOVYRcSkWPFYljHrEh8QFEK5EQ==",
"dev": true,
"requires": {
"get-stdin": "^6.0.0"
},
"dependencies": {
"get-stdin": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz",
"integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==",
"dev": true
}
}
},
"eslint-plugin-prettier": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.1.tgz",
"integrity": "sha512-A+TZuHZ0KU0cnn56/9mfR7/KjUJ9QNVXUhwvRFSR7PGPe0zQR6PTkmyqg1AtUUEOzTqeRsUwyKFh0oVZKVCrtA==",
"dev": true,
"requires": {
"prettier-linter-helpers": "^1.0.0"
}
},
"eslint-plugin-vue": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-6.0.0.tgz",
"integrity": "sha512-+LxTJCd6nDt+AKQ1X+ySD48xJHft8OkeQmAhiq6UoAMxRFTiEKIDusiGgEUJLwKyiwGUGWbbqEbbWvupH5TSjg==",
"dev": true,
"requires": {
"vue-eslint-parser": "^6.0.4"
}
},
"eslint-scope": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz",
@ -5141,11 +5428,54 @@
"estraverse": "^4.1.1"
}
},
"eslint-utils": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz",
"integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==",
"dev": true,
"requires": {
"eslint-visitor-keys": "^1.1.0"
}
},
"eslint-visitor-keys": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz",
"integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==",
"dev": true
},
"espree": {
"version": "6.1.2",
"resolved": "https://registry.npmjs.org/espree/-/espree-6.1.2.tgz",
"integrity": "sha512-2iUPuuPP+yW1PZaMSDM9eyVf8D5P0Hi8h83YtZ5bPc/zHYjII5khoixIUTMO794NOY8F/ThF1Bo8ncZILarUTA==",
"dev": true,
"requires": {
"acorn": "^7.1.0",
"acorn-jsx": "^5.1.0",
"eslint-visitor-keys": "^1.1.0"
},
"dependencies": {
"acorn": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.0.tgz",
"integrity": "sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==",
"dev": true
}
}
},
"esprima": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
},
"esquery": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz",
"integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==",
"dev": true,
"requires": {
"estraverse": "^4.0.0"
}
},
"esrecurse": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz",
@ -5384,7 +5714,6 @@
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.3.tgz",
"integrity": "sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==",
"optional": true,
"requires": {
"chardet": "^0.7.0",
"iconv-lite": "^0.4.24",
@ -5496,6 +5825,12 @@
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
"integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk="
},
"fast-diff": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz",
"integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==",
"dev": true
},
"fast-glob": {
"version": "2.2.7",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.7.tgz",
@ -5515,6 +5850,12 @@
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
"integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I="
},
"fast-levenshtein": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
"integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
"dev": true
},
"fastparse": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz",
@ -5554,6 +5895,15 @@
"escape-string-regexp": "^1.0.5"
}
},
"file-entry-cache": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz",
"integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==",
"dev": true,
"requires": {
"flat-cache": "^2.0.1"
}
},
"file-loader": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/file-loader/-/file-loader-3.0.1.tgz",
@ -5663,6 +6013,34 @@
"locate-path": "^2.0.0"
}
},
"flat-cache": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz",
"integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==",
"dev": true,
"requires": {
"flatted": "^2.0.0",
"rimraf": "2.6.3",
"write": "1.0.3"
},
"dependencies": {
"rimraf": {
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
"integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
"dev": true,
"requires": {
"glob": "^7.1.3"
}
}
}
},
"flatted": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz",
"integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==",
"dev": true
},
"flush-write-stream": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz",
@ -5795,7 +6173,8 @@
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"aproba": {
"version": "1.2.0",
@ -5816,12 +6195,14 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -5836,17 +6217,20 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"core-util-is": {
"version": "1.0.2",
@ -5963,7 +6347,8 @@
"inherits": {
"version": "2.0.3",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"ini": {
"version": "1.3.5",
@ -5975,6 +6360,7 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@ -5989,6 +6375,7 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@ -5996,12 +6383,14 @@
"minimist": {
"version": "0.0.8",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"minipass": {
"version": "2.3.5",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@ -6020,6 +6409,7 @@
"version": "0.5.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"minimist": "0.0.8"
}
@ -6100,7 +6490,8 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"object-assign": {
"version": "4.1.1",
@ -6112,6 +6503,7 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"wrappy": "1"
}
@ -6197,7 +6589,8 @@
"safe-buffer": {
"version": "5.1.2",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"safer-buffer": {
"version": "2.1.2",
@ -6233,6 +6626,7 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@ -6252,6 +6646,7 @@
"version": "3.0.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@ -6295,12 +6690,14 @@
"wrappy": {
"version": "1.0.2",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"yallist": {
"version": "3.0.3",
"bundled": true,
"dev": true
"dev": true,
"optional": true
}
}
},
@ -6310,6 +6707,12 @@
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
"dev": true
},
"functional-red-black-tree": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
"integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=",
"dev": true
},
"get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
@ -7330,8 +7733,7 @@
"is-promise": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz",
"integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=",
"optional": true
"integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o="
},
"is-regex": {
"version": "1.0.4",
@ -7513,6 +7915,12 @@
"resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-7.0.1.tgz",
"integrity": "sha512-IqUK+Cqc8/MqHsCvv1TMccbKdBzoATOLHXZAF5UDu70/CCxo648cHUig24hc+XTK53TyeNk1UeVTlc2Haovtsw=="
},
"json-stable-stringify-without-jsonify": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
"integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=",
"dev": true
},
"json-stringify-safe": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
@ -7675,6 +8083,16 @@
}
}
},
"levn": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
"integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=",
"dev": true,
"requires": {
"prelude-ls": "~1.1.2",
"type-check": "~0.3.2"
}
},
"lines-and-columns": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz",
@ -8329,6 +8747,12 @@
"to-regex": "^3.0.1"
}
},
"natural-compare": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
"integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
"dev": true
},
"negotiator": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
@ -8688,6 +9112,20 @@
"is-wsl": "^1.1.0"
}
},
"optionator": {
"version": "0.8.3",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz",
"integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==",
"dev": true,
"requires": {
"deep-is": "~0.1.3",
"fast-levenshtein": "~2.0.6",
"levn": "~0.3.0",
"prelude-ls": "~1.1.2",
"type-check": "~0.3.2",
"word-wrap": "~1.2.3"
}
},
"ora": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/ora/-/ora-3.4.0.tgz",
@ -8731,8 +9169,7 @@
"os-tmpdir": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
"integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
"optional": true
"integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ="
},
"p-cancelable": {
"version": "1.1.0",
@ -8842,6 +9279,23 @@
"no-case": "^2.2.0"
}
},
"parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
"dev": true,
"requires": {
"callsites": "^3.0.0"
},
"dependencies": {
"callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
"dev": true
}
}
},
"parse-asn1": {
"version": "5.1.5",
"resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.5.tgz",
@ -9601,6 +10055,12 @@
"integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==",
"dev": true
},
"prelude-ls": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
"integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=",
"dev": true
},
"prepend-http": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz",
@ -9613,6 +10073,15 @@
"integrity": "sha512-kn/GU6SMRYPxUakNXhpP0EedT/KmaPzr0H5lIsDogrykbaxOpOfAFfk5XA7DZrJyMAv1wlMV3CPcZruGXVVUZw==",
"dev": true
},
"prettier-linter-helpers": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
"integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
"dev": true,
"requires": {
"fast-diff": "^1.1.2"
}
},
"pretty-bytes": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-1.0.4.tgz",
@ -9651,6 +10120,12 @@
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
"dev": true
},
"progress": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
"dev": true
},
"progress-stream": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/progress-stream/-/progress-stream-1.2.0.tgz",
@ -10135,6 +10610,12 @@
"define-properties": "^1.1.2"
}
},
"regexpp": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz",
"integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==",
"dev": true
},
"regexpu-core": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.6.0.tgz",
@ -10443,7 +10924,6 @@
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz",
"integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=",
"optional": true,
"requires": {
"is-promise": "^2.1.0"
}
@ -10461,7 +10941,6 @@
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.2.tgz",
"integrity": "sha512-HUb7j3kvb7p7eCUHE3FqjoDsC1xfZQ4AHFWfTKSpZ+sAhhz5X1WX0ZuUqWbzB2QhSLp3DoLUG+hMdEDKqWo2Zg==",
"optional": true,
"requires": {
"tslib": "^1.9.0"
}
@ -11465,6 +11944,43 @@
"util.promisify": "~1.0.0"
}
},
"table": {
"version": "5.4.6",
"resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz",
"integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==",
"dev": true,
"requires": {
"ajv": "^6.10.2",
"lodash": "^4.17.14",
"slice-ansi": "^2.1.0",
"string-width": "^3.0.0"
},
"dependencies": {
"ajv": {
"version": "6.10.2",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz",
"integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==",
"dev": true,
"requires": {
"fast-deep-equal": "^2.0.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
}
},
"string-width": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
"integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
"dev": true,
"requires": {
"emoji-regex": "^7.0.1",
"is-fullwidth-code-point": "^2.0.0",
"strip-ansi": "^5.1.0"
}
}
}
},
"tapable": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz",
@ -11603,6 +12119,12 @@
}
}
},
"text-table": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
"integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
"dev": true
},
"thenify": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.0.tgz",
@ -11641,8 +12163,7 @@
"through": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
"optional": true
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU="
},
"through2": {
"version": "2.0.5",
@ -11679,7 +12200,6 @@
"version": "0.0.33",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
"integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
"optional": true,
"requires": {
"os-tmpdir": "~1.0.2"
}
@ -11827,6 +12347,15 @@
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
"dev": true
},
"type-check": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
"integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=",
"dev": true,
"requires": {
"prelude-ls": "~1.1.2"
}
},
"type-fest": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz",
@ -12206,6 +12735,12 @@
"integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==",
"dev": true
},
"v8-compile-cache": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz",
"integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==",
"dev": true
},
"validate-npm-package-license": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
@ -12251,6 +12786,33 @@
"integrity": "sha512-ImThpeNU9HbdZL3utgMCq0oiMzAkt1mcgy3/E6zWC/G6AaQoeuFdsl9nDhTDU3X1R6FK7nsIUuRACVcjI+A2GQ==",
"dev": true
},
"vue-eslint-parser": {
"version": "6.0.5",
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-6.0.5.tgz",
"integrity": "sha512-Bvjlx7rH1Ulvus56KHeLXOjEi3JMOYTa1GAqZr9lBQhd8weK8mV7U7V2l85yokBZEWHJQjLn6X3nosY8TzkOKg==",
"dev": true,
"requires": {
"debug": "^4.1.1",
"eslint-scope": "^4.0.0",
"eslint-visitor-keys": "^1.0.0",
"espree": "^5.0.0",
"esquery": "^1.0.1",
"lodash": "^4.17.11"
},
"dependencies": {
"espree": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/espree/-/espree-5.0.1.tgz",
"integrity": "sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A==",
"dev": true,
"requires": {
"acorn": "^6.0.7",
"acorn-jsx": "^5.0.0",
"eslint-visitor-keys": "^1.0.0"
}
}
}
},
"vue-hot-reload-api": {
"version": "2.3.4",
"resolved": "https://registry.npmjs.org/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz",
@ -12696,6 +13258,12 @@
"string-width": "^2.1.1"
}
},
"word-wrap": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
"dev": true
},
"worker-farm": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz",
@ -12758,6 +13326,15 @@
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"dev": true
},
"write": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz",
"integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==",
"dev": true,
"requires": {
"mkdirp": "^0.5.1"
}
},
"write-file-atomic": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz",

View file

@ -34,9 +34,14 @@
"@vue/cli-plugin-babel": "^3.11.0",
"@vue/cli-service": "^3.11.0",
"axios": "^0.19.0",
"babel-eslint": "^10.0.3",
"css-loader": "^3.2.0",
"electron": "^6.0.10",
"electron-builder": "^21.2.0",
"eslint": "^6.6.0",
"eslint-config-prettier": "^6.5.0",
"eslint-plugin-prettier": "^3.1.1",
"eslint-plugin-vue": "^6.0.0",
"less": "^3.10.3",
"less-loader": "^5.0.0",
"mdns-js": "^1.0.3",
@ -58,4 +63,4 @@
"last 2 versions",
"not ie <= 8"
]
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,12 +1,12 @@
// this file is for cases where we need to access the
// webpack config as a file when using CLI commands.
let service = process.VUE_CLI_SERVICE
let service = process.VUE_CLI_SERVICE;
if (!service || process.env.VUE_CLI_API_MODE) {
const Service = require('@vue/cli-service/lib/Service')
service = new Service(process.env.VUE_CLI_CONTEXT || process.cwd())
service.init(process.env.VUE_CLI_MODE || process.env.NODE_ENV)
const Service = require("@vue/cli-service/lib/Service");
service = new Service(process.env.VUE_CLI_CONTEXT || process.cwd());
service.init(process.env.VUE_CLI_MODE || process.env.NODE_ENV);
}
module.exports = service.resolveWebpackConfig()
module.exports = service.resolveWebpackConfig();