Skip to content

Commit

Permalink
12762 table keynav (#12975)
Browse files Browse the repository at this point in the history
* Experimental feature: shortcut visual hints

* Long way around to a custom modifier for keyboard shortcuts

* dynamic table and list iterative shortcuts

* Progress with regular old tether

* Delogging

* Table Keynav tether fix, server and client navs, and fix to shiftless on modified arrow keys
  • Loading branch information
philrenaud authored May 17, 2022
1 parent 9ddcfa0 commit 6ab02c5
Show file tree
Hide file tree
Showing 25 changed files with 360 additions and 69 deletions.
48 changes: 36 additions & 12 deletions ui/app/components/keyboard-shortcuts-modal.hbs
Original file line number Diff line number Diff line change
@@ -1,14 +1,38 @@
{{keyboard-commands this.keyCommands}}
{{#if this.keyboard.shortcutsVisible}}
{{keyboard-commands (array this.escapeCommand)}}
<ul class="keyboard-shortcuts">
<button
class="button is-borderless dismiss"
type="button"
{{on "click" (toggle "keyboard.shortcutsVisible" this)}}
>
{{x-icon "cancel"}}
</button>
{{#each this.commands as |command|}}
<li>
<strong>{{command.label}}</strong>
<span class="keys">
{{#each command.pattern as |key|}}
<span>{{key}}</span>
{{/each}}
</span>
</li>
{{/each}}
</ul>
{{/if}}

<ul class="keyboard-shortcuts">
{{#each this.keyboard.keyCommands as |command|}}
<li>
<strong>{{command.label}}</strong>
<span class="keys">
{{#each command.pattern as |key|}}
<span>{{key}}</span>
{{/each}}
</span>
</li>
{{#if this.keyboard.displayHints}}
{{#each this.hints as |hint|}}
<span
{{did-insert this.tetherToElement element=hint.element hint=hint}}
{{will-destroy this.untetherFromElement element=hint.element hint=hint}}
data-shortcut={{hint.pattern}}
class="{{if hint.menuLevel "menu-level"}}"
aria-hidden="true"
>
{{#each hint.pattern as |key|}}
<span>{{key}}</span>
{{/each}}
</span>
{{/each}}
</ul>
{{/if}}
53 changes: 45 additions & 8 deletions ui/app/components/keyboard-shortcuts-modal.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,53 @@
import Component from '@glimmer/component';
import { inject as service } from '@ember/service';
import { htmlSafe } from '@ember/template';
import { computed } from '@ember/object';
import Tether from 'tether';

export default class KeyboardShortcutsModalComponent extends Component {
@service keyboard;

keyCommands = [
{
label: 'Hide Keyboard Shortcuts',
pattern: ['Escape'],
action: () => {
this.keyboard.shortcutsVisible = false;
},
escapeCommand = {
label: 'Hide Keyboard Shortcuts',
pattern: ['Escape'],
action: () => {
this.keyboard.shortcutsVisible = false;
},
];
};

/**
* commands: filter keyCommands to those that have an action and a label,
* to distinguish between those that are just visual hints of existing commands
*/
@computed('keyboard.keyCommands.length')
get commands() {
return this.keyboard.keyCommands.filter((c) => c.label && c.action);
}

/**
* hints: filter keyCommands to those that have an element property,
* and then compute a position on screen to place the hint.
*/
@computed('keyboard.keyCommands.length', 'keyboard.displayHints')
get hints() {
if (this.keyboard.displayHints) {
return this.keyboard.keyCommands.filter((c) => c.element);
} else {
return [];
}
}

tetherToElement(self, _, { element, hint }) {
let binder = new Tether({
element: self,
target: element,
attachment: 'top left',
targetAttachment: 'top left',
targetModifier: 'visible',
});
hint.binder = binder;
}
untetherFromElement(self, _, { element, hint }) {
hint.binder.destroy();
}
}
6 changes: 5 additions & 1 deletion ui/app/components/server-agent-row.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,13 @@ export default class ServerAgentRow extends Component {
return currentURL.replace(/%40/g, '@') === targetURL.replace(/%40/g, '@');
}

click() {
goToAgent() {
const transition = () =>
this.router.transitionTo('servers.server', this.agent);
lazyClick([transition, event]);
}

click() {
this.goToAgent();
}
}
5 changes: 0 additions & 5 deletions ui/app/controllers/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,6 @@ export default class ApplicationController extends Controller {
this.keyboard.listenForKeypress();
}

@observes('this.keyboard.matchedCommand')
function() {
console.log('uhhh', this.keyboard.matchedCommand);
}

queryParams = [
{
region: 'region',
Expand Down
1 change: 1 addition & 0 deletions ui/app/controllers/csi/volumes/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export default class IndexController extends Controller.extend(
) {
@service system;
@service userSettings;
@service keyboard;
@controller('csi/volumes') volumesController;

@alias('volumesController.isForbidden')
Expand Down
4 changes: 3 additions & 1 deletion ui/app/controllers/evaluations/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,9 @@ export default class EvaluationsController extends Controller {
async handleEvaluationClick(evaluation, e) {
if (
e instanceof MouseEvent ||
(e instanceof KeyboardEvent && (e.code === 'Enter' || e.code === 'Space'))
(e instanceof KeyboardEvent &&
(e.code === 'Enter' || e.code === 'Space')) ||
!e
) {
this.statechart.send('LOAD_EVALUATION', { evaluation });
}
Expand Down
2 changes: 2 additions & 0 deletions ui/app/helpers/keyboard-commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ export default class keyboardCommands extends Helper {
@service keyboard;

constructor() {
console.log('kc const', ...arguments);
super(...arguments);
}

compute([commands]) {
console.log('computing', commands);
if (commands) {
this.commands = commands;
this.keyboard.addCommands(commands);
Expand Down
2 changes: 1 addition & 1 deletion ui/app/helpers/lazy-click.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import Helper from '@ember/component/helper';
* that should be handled instead.
*/
export function lazyClick([onClick, event]) {
if (!['a', 'button'].includes(event.target.tagName.toLowerCase())) {
if (!['a', 'button'].includes(event?.target.tagName.toLowerCase())) {
onClick(event);
}
}
Expand Down
49 changes: 49 additions & 0 deletions ui/app/modifiers/keyboard-shortcut.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { inject as service } from '@ember/service';
import Modifier from 'ember-modifier';
import { registerDestructor } from '@ember/destroyable';
import { assert } from '@ember/debug';

export default class KeyboardShortcutModifier extends Modifier {
@service keyboard;

/**
* For Dynamic/iterative keyboard shortcuts, our patterns look like "Shift+0" by default
* Do a couple things to make them more human-friendly:
* 1. Make them 1-based, instead of 0-based
* 2. Prefix numbers 1-9 with "0" to make it so "Shift+10" doesn't trigger "Shift+1" then "0", etc.
* ^--- stops being a good solution with 100+ row lists/tables, but a better UX than waiting for shift key-up otherwise
*
* @param {string[]} pattern
*/
cleanPattern(pattern) {
let patternNumber = pattern.length === 1 && pattern[0].match(/\d+/g);
if (!patternNumber) {
return pattern;
} else {
patternNumber = +patternNumber[0]; // regex'd string[0] to num
patternNumber = patternNumber + 1; // first item should be Shift+1, not Shift+0
assert(
'Dynamic keyboard shortcuts only work up to 99 digits',
patternNumber < 100
);
pattern = [`Shift+${('0' + patternNumber).slice(-2)}`]; // Shift+01, not Shift+1
}
return pattern;
}

modify(element, [eventName], { label, pattern, action, menuLevel = false }) {
let commands = [
{
label,
action,
pattern: this.cleanPattern(pattern),
element,
menuLevel,
},
];
this.keyboard.addCommands(commands);
registerDestructor(this, () => {
this.keyboard.removeCommands(commands);
});
}
}
Loading

0 comments on commit 6ab02c5

Please sign in to comment.