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 @@
+ + -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 {