diff --git a/openflexure_microscope/api/static/README.md b/openflexure_microscope/api/static/README.md
index 1e8fbe26..4e5d9005 100644
--- a/openflexure_microscope/api/static/README.md
+++ b/openflexure_microscope/api/static/README.md
@@ -10,21 +10,11 @@ To prevent the editor from interfering with ESLint, add to your project `setting
{
"editor.tabSize": 2,
"cSpell.enabled": false,
- "eslint.validate": [{
- "language": "vue",
- "autoFix": true
- },
- {
- "language": "javascript",
- "autoFix": true
- },
- {
- "language": "javascriptreact",
- "autoFix": true
- }
- ],
- "eslint.autoFixOnSave": true,
+ "eslint.validate": ["vue","javascript", "javascriptreact"],
"editor.formatOnSave": false,
- "vetur.validation.template": false
+ "vetur.validation.template": false,
+ "editor.codeActionsOnSave": {
+ "source.fixAll.eslint": true
+ }
}
```
\ No newline at end of file
diff --git a/openflexure_microscope/api/static/package-lock.json b/openflexure_microscope/api/static/package-lock.json
index 8a0083a6..88511dc4 100644
--- a/openflexure_microscope/api/static/package-lock.json
+++ b/openflexure_microscope/api/static/package-lock.json
@@ -9210,6 +9210,11 @@
"minimist": "^1.2.5"
}
},
+ "mousetrap": {
+ "version": "1.6.5",
+ "resolved": "https://registry.npmjs.org/mousetrap/-/mousetrap-1.6.5.tgz",
+ "integrity": "sha512-QNo4kEepaIBwiT8CDhP98umTetp+JNfQYBWvC1pc6/OAibuXtRcxZ58Qz8skvEHYvURne/7R8T5VoOI7rDsEUA=="
+ },
"move-concurrently": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
diff --git a/openflexure_microscope/api/static/package.json b/openflexure_microscope/api/static/package.json
index bcdca6a0..055d4b50 100644
--- a/openflexure_microscope/api/static/package.json
+++ b/openflexure_microscope/api/static/package.json
@@ -13,7 +13,8 @@
"serve": "vue-cli-service serve --mode development"
},
"dependencies": {
- "material-design-icons": "^3.0.1"
+ "material-design-icons": "^3.0.1",
+ "mousetrap": "^1.6.5"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^3.12.1",
diff --git a/openflexure_microscope/api/static/src/App.vue b/openflexure_microscope/api/static/src/App.vue
index 0fc0f38c..9e1a31c1 100644
--- a/openflexure_microscope/api/static/src/App.vue
+++ b/openflexure_microscope/api/static/src/App.vue
@@ -7,6 +7,26 @@
+
+
+
+
+
+
{{ shortcut.shortcut }}
+
{{ shortcut.description }}
+
+
+
-1) {
+ return false;
+ }
+
+ // if we're in a lightbox, stop mousetrap
+ if (element.classList.contains("lightbox-link")) {
+ return true;
+ }
+
+ // stop for input, select, and textarea
+ return (
+ element.tagName == "INPUT" ||
+ element.tagName == "SELECT" ||
+ element.tagName == "TEXTAREA" ||
+ (element.contentEditable && element.contentEditable == "true")
+ );
};
// Export main app
@@ -51,7 +77,8 @@ export default {
data: function() {
return {
appAvailable: false,
- keysDown: {},
+ arrowKeysDown: {},
+ keyboardManual: [],
systemDark: undefined,
themeObserver: undefined,
tourCallbacks: {
@@ -166,9 +193,7 @@ export default {
created: function() {
window.addEventListener("beforeunload", this.handleExit);
- // Key events
- window.addEventListener("keydown", this.keyDownMonitor);
- window.addEventListener("keyup", this.keyUpMonitor);
+ // Scrollwheel listener
window.addEventListener("wheel", this.wheelMonitor);
// Watch for origin changes
this.unwatchOriginFunction = this.$store.watch(
@@ -180,6 +205,82 @@ export default {
console.log(uriV2);
}
);
+
+ // Keyboard shortcuts
+
+ // TODO: Shortcut guide
+ Mousetrap.bind("?", () => {
+ console.log(this.keyboardManual);
+ this.toggleModalElement(this.$refs["keyboardManualModal"]); // Calls the mixin
+ });
+
+ // Arrow keys
+ Mousetrap.bind(
+ ["up", "down", "left", "right"],
+ event => {
+ this.arrowKeysDown[event.keyCode] = true; //Add key to array
+ this.navigateKeyHandler();
+ },
+ "keydown"
+ );
+ Mousetrap.bind(
+ ["up", "down", "left", "right"],
+ event => {
+ delete this.arrowKeysDown[event.keyCode]; //Remove key from array
+ },
+ "keyup"
+ );
+ this.keyboardManual.push({
+ shortcut: "←↑→↓",
+ description: "Move the microscope stage"
+ });
+
+ // Focus keys
+ Mousetrap.bind("pageup", () => {
+ this.$root.$emit("globalMoveStepEvent", 0, 0, 1);
+ });
+ Mousetrap.bind("pagedown", () => {
+ this.$root.$emit("globalMoveStepEvent", 0, 0, -1);
+ });
+ this.keyboardManual.push({
+ shortcut: "pgup / pgdn",
+ description: "Move the microscope focus"
+ });
+
+ // Capture
+ Mousetrap.bind("c", () => {
+ this.$root.$emit("globalCaptureEvent");
+ });
+ this.keyboardManual.push({
+ shortcut: "c",
+ description: "Take a capture"
+ });
+
+ // Autofocus
+ Mousetrap.bind("a", () => {
+ this.$root.$emit("globalFastAutofocusEvent");
+ });
+ this.keyboardManual.push({
+ shortcut: "a",
+ description: "Fast autofocus"
+ });
+
+ // Increment/decrement tab
+ Mousetrap.bind("shift+down", () => {
+ this.$root.$emit("globalIncrementTab");
+ });
+ Mousetrap.bind("shift+up", () => {
+ this.$root.$emit("globalDecrementTab");
+ });
+ this.keyboardManual.push({
+ shortcut: "shift+↑ / shift+↓",
+ description: "Switch tab"
+ });
+
+ // Re-run tour
+ Mousetrap.bind("alt+t", () => {
+ this.$tours["guidedTour"].start();
+ });
},
beforeDestroy: function() {
@@ -187,12 +288,13 @@ export default {
if (this.themeObserver) {
this.themeObserver.disconnect();
}
- // Remove key listeners
- window.removeEventListener("keydown", this.keyDownMonitor);
- window.removeEventListener("keyup", this.keyUpMonitor);
+ // Remove scrollwheel listener
window.removeEventListener("wheel", this.wheelMonitor);
// Remove origin watcher
this.unwatchOriginFunction();
+ // Remove key listeners
+ console.log("Resetting Mousetrap");
+ Mousetrap.reset();
},
methods: {
@@ -231,84 +333,26 @@ export default {
}
},
- // Handle global key press events to be associated with navigation
- keyDownMonitor: function(event) {
- this.keysDown[event.keyCode] = true; //Add key to array
-
- // Convert keyCode dict into a list of key codes
- var keyCodeList = Object.keys(keyCodes).map(function(key) {
- return keyCodes[key];
- });
-
- if (
- // If not inside an element we want to ignore
- !(event.target instanceof HTMLInputElement) &&
- !event.target.classList.contains("lightbox-link") &&
- // If it's a recognised key
- keyCodeList.includes(event.keyCode)
- ) {
- this.navigateKeyHandler(keyCodes);
- this.captureKeyHandler(keyCodes);
- this.letterKeyHandler(keyCodes);
+ navigateKeyHandler: function() {
+ // Calculate movement array
+ var x_rel = 0;
+ var y_rel = 0;
+ var z_rel = 0;
+ if (37 in this.arrowKeysDown) {
+ x_rel = x_rel + 1;
}
- },
-
- keyUpMonitor: function(event) {
- delete this.keysDown[event.keyCode]; //Remove key from array
- },
-
- navigateKeyHandler: function(keyCodes) {
- const moveKeys = [
- keyCodes.left,
- keyCodes.right,
- keyCodes.up,
- keyCodes.down,
- keyCodes.pgup,
- keyCodes.pgdn
- ];
-
- if (
- moveKeys.some(r => Object.keys(this.keysDown).includes(r.toString()))
- ) {
- // Calculate movement array
- var x_rel = 0;
- var y_rel = 0;
- var z_rel = 0;
- if (keyCodes.left in this.keysDown) {
- x_rel = x_rel + 1;
- }
- if (keyCodes.right in this.keysDown) {
- x_rel = x_rel - 1;
- }
- if (keyCodes.up in this.keysDown) {
- y_rel = y_rel + 1;
- }
- if (keyCodes.down in this.keysDown) {
- y_rel = y_rel - 1;
- }
- if (keyCodes.pgup in this.keysDown) {
- z_rel = z_rel - 1;
- }
- if (keyCodes.pgdn in this.keysDown) {
- z_rel = z_rel + 1;
- }
- // Make a position request
- // Emit a signal to move, acted on by panelNavigate.vue
- this.$root.$emit("globalMoveStepEvent", x_rel, y_rel, z_rel);
+ if (39 in this.arrowKeysDown) {
+ x_rel = x_rel - 1;
}
- },
-
- captureKeyHandler: function(keyCodes) {
- if (keyCodes.shift in this.keysDown && keyCodes.enter in this.keysDown) {
- console.log("Capturing");
- this.$root.$emit("globalCaptureEvent");
+ if (38 in this.arrowKeysDown) {
+ y_rel = y_rel + 1;
}
- },
-
- letterKeyHandler: function(keyCodes) {
- if (keyCodes.alt in this.keysDown && keyCodes.t in this.keysDown) {
- this.$tours["guidedTour"].start();
+ if (40 in this.arrowKeysDown) {
+ y_rel = y_rel - 1;
}
+ // Make a position request
+ // Emit a signal to move, acted on by panelNavigate.vue
+ this.$root.$emit("globalMoveStepEvent", x_rel, y_rel, z_rel);
}
}
};
diff --git a/openflexure_microscope/api/static/src/assets/less/theme.less b/openflexure_microscope/api/static/src/assets/less/theme.less
index 17d014a2..7ab97249 100644
--- a/openflexure_microscope/api/static/src/assets/less/theme.less
+++ b/openflexure_microscope/api/static/src/assets/less/theme.less
@@ -59,6 +59,12 @@
text-rendering: optimizeLegibility;
}
+//
+// Decorations
+//
+@small-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
+@big-shadow: 0 14px 25px rgba(0, 0, 0, 0.16);
+
//
// Container
//
@@ -148,6 +154,7 @@
// Paper
//
@paper-border-radius: 4px;
+@button-border-radius: 3px;
/* ========================================================================
@@ -187,9 +194,8 @@
.uk-card {
border-radius: @paper-border-radius;
- border: 1px solid rgba(180, 180, 180, 0.25);
- box-shadow: none;
- -webkit-box-shabox-shadow: none;
+ //border: 1px solid rgba(180, 180, 180, 0.25);
+ box-shadow: @small-shadow;
}
.uk-card-media-top img {
@@ -228,7 +234,7 @@
*/
.uk-modal-dialog {
border-radius: @paper-border-radius;
- box-shadow: 0 14px 25px rgba(0, 0, 0, 0.16);
+ box-shadow: @big-shadow;
transition: 0.3s ease !important;
}
@@ -245,7 +251,7 @@
*/
.uk-notification-message {
border-radius: @paper-border-radius;
- box-shadow: 0 14px 25px rgba(0, 0, 0, 0.16);
+ box-shadow: @big-shadow;
}
.uk-notification-message-danger {
@@ -287,7 +293,7 @@ a:hover {
* Buttons
*/
.uk-button {
- border-radius: 2px;
+ border-radius: @button-border-radius;
padding: 0 8px;
}
@@ -296,6 +302,12 @@ a:hover {
border-color: @global-border;
}
+.uk-button-primary {
+ background-color: rgba(180, 180, 180, 0.10);
+ border-color: @global-primary-background;
+ color: @button-text-color;
+}
+
.uk-button-danger {
background-color: transparent;
color: #f0506e;
diff --git a/openflexure_microscope/api/static/src/components/appContent.vue b/openflexure_microscope/api/static/src/components/appContent.vue
index 319ddc35..24439529 100644
--- a/openflexure_microscope/api/static/src/components/appContent.vue
+++ b/openflexure_microscope/api/static/src/components/appContent.vue
@@ -218,6 +218,18 @@ export default {
}
}
return pluginGuis;
+ },
+ tabOrder: function() {
+ // TODO: There must be a better way to do this, somehow reading the order of the HTML elements?
+ var ind = ["view", "gallery", "navigate", "capture", "settings"];
+ for (const plugin of this.pluginsGuiList) {
+ ind.push(plugin.id);
+ }
+ ind.push("about");
+ return ind;
+ },
+ currentTabIndex: function() {
+ return this.tabOrder.indexOf(this.currentTab);
}
},
@@ -246,6 +258,14 @@ export default {
this.$root.$on("globalSwitchTab", tabID => {
this.currentTab = tabID;
});
+ // A global signal listener to increment tab
+ this.$root.$on("globalIncrementTab", () => {
+ this.incrementTabBy(1);
+ });
+ // A global signal listener to decrement tab
+ this.$root.$on("globalDecrementTab", () => {
+ this.incrementTabBy(-1);
+ });
},
beforeDestroy() {
@@ -274,6 +294,14 @@ export default {
this.currentTab = tab;
}
},
+ incrementTabBy: function(n) {
+ const newIndex =
+ (((this.currentTabIndex + n) % this.tabOrder.length) +
+ this.tabOrder.length) %
+ this.tabOrder.length;
+ const newId = this.tabOrder[newIndex];
+ this.currentTab = newId;
+ },
startModals: function() {
this.$refs["calibrationModal"].show();
},
diff --git a/openflexure_microscope/api/static/src/components/controlComponents/paneNavigate.vue b/openflexure_microscope/api/static/src/components/controlComponents/paneNavigate.vue
index f5040ff5..fbaa567c 100644
--- a/openflexure_microscope/api/static/src/components/controlComponents/paneNavigate.vue
+++ b/openflexure_microscope/api/static/src/components/controlComponents/paneNavigate.vue
@@ -111,6 +111,8 @@
:submit-url="fastAutofocusUri"
:submit-data="{ dz: 2000 }"
:submit-label="'Fast'"
+ :button-primary="false"
+ :submit-on-event="'globalFastAutofocusEvent'"
@taskStarted="isAutofocusing = 1"
@finished="isAutofocusing = 0"
>
@@ -122,6 +124,7 @@
:submit-url="normalAutofocusUri"
:submit-data="{ dz: [-60, -30, 0, 30, 60] }"
:submit-label="'Medium'"
+ :button-primary="false"
@taskStarted="isAutofocusing = 2"
@finished="isAutofocusing = 0"
>
@@ -133,6 +136,7 @@
:submit-url="normalAutofocusUri"
:submit-data="{ dz: [-20, -10, 0, 10, 20] }"
:submit-label="'Fine'"
+ :button-primary="false"
@taskStarted="isAutofocusing = 3"
@finished="isAutofocusing = 0"
>
@@ -201,6 +205,7 @@ export default {
this.$root.$on("globalMoveInImageCoordinatesEvent", (x, y, absolute) => {
this.moveInImageCoordinatesRequest(x, y, absolute);
});
+ // A global signal listener to perform a move in multiples of a step size
this.$root.$on("globalMoveStepEvent", (x_steps, y_steps, z_steps) => {
this.moveRequest(
x_steps * this.stepXy,
@@ -219,6 +224,8 @@ export default {
beforeDestroy() {
// Remove global signal listener to perform a move action
this.$root.$off("globalMoveEvent");
+ this.$root.$off("globalMoveInImageCoordinatesEvent");
+ this.$root.$off("globalMoveStepEvent");
},
methods: {
diff --git a/openflexure_microscope/api/static/src/components/genericComponents/tabIcon.vue b/openflexure_microscope/api/static/src/components/genericComponents/tabIcon.vue
index 15dfd38f..e352f334 100644
--- a/openflexure_microscope/api/static/src/components/genericComponents/tabIcon.vue
+++ b/openflexure_microscope/api/static/src/components/genericComponents/tabIcon.vue
@@ -93,13 +93,9 @@ export default {
@import "../../assets/less/theme.less";
.tabicon-active {
- color: @global-primary-background !important;
-}
-
-.hook-inverse() {
- .tabicon-active {
- color: @inverse-primary-muted-color !important;
- }
+ color: #fff !important;
+ background-color: @global-primary-background !important;
+ box-shadow: @small-shadow;
}
.tabtitle {
diff --git a/openflexure_microscope/api/static/src/components/genericComponents/taskSubmitter.vue b/openflexure_microscope/api/static/src/components/genericComponents/taskSubmitter.vue
index 845bad9e..da5e695d 100644
--- a/openflexure_microscope/api/static/src/components/genericComponents/taskSubmitter.vue
+++ b/openflexure_microscope/api/static/src/components/genericComponents/taskSubmitter.vue
@@ -78,7 +78,12 @@ export default {
buttonPrimary: {
type: Boolean,
required: false,
- default: false
+ default: true
+ },
+ submitOnEvent: {
+ type: String,
+ required: false,
+ default: null
}
},
@@ -101,7 +106,20 @@ export default {
created() {},
- beforeDestroy() {},
+ mounted() {
+ // A global signal listener to perform a move action
+ if (this.submitOnEvent) {
+ this.$root.$on(this.submitOnEvent, () => {
+ this.bootstrapTask();
+ });
+ }
+ },
+
+ beforeDestroy() {
+ if (this.submitOnEvent) {
+ this.$root.$off(this.submitOnEvent);
+ }
+ },
methods: {
bootstrapTask: function() {
diff --git a/openflexure_microscope/api/static/src/components/tabContentComponents/aboutContent.vue b/openflexure_microscope/api/static/src/components/tabContentComponents/aboutContent.vue
index 5133f558..3f5ebd6e 100644
--- a/openflexure_microscope/api/static/src/components/tabContentComponents/aboutContent.vue
+++ b/openflexure_microscope/api/static/src/components/tabContentComponents/aboutContent.vue
@@ -9,7 +9,7 @@
Report an issue
diff --git a/openflexure_microscope/api/static/src/components/viewComponents/settingsDisplay.vue b/openflexure_microscope/api/static/src/components/viewComponents/settingsDisplay.vue
index 303c5cf2..9d3266dd 100644
--- a/openflexure_microscope/api/static/src/components/viewComponents/settingsDisplay.vue
+++ b/openflexure_microscope/api/static/src/components/viewComponents/settingsDisplay.vue
@@ -153,11 +153,11 @@ export default {
};
-
diff --git a/openflexure_microscope/api/static/src/main.js b/openflexure_microscope/api/static/src/main.js
index 567298e6..b53b43a4 100644
--- a/openflexure_microscope/api/static/src/main.js
+++ b/openflexure_microscope/api/static/src/main.js
@@ -129,6 +129,10 @@ Vue.mixin({
UIkit.modal(element).hide();
},
+ toggleModalElement: function(element) {
+ UIkit.modal(element).toggle();
+ },
+
getLocalStorageObj: function(keyName) {
if (localStorage.getItem(keyName)) {
try {