Skip to content

Commit

Permalink
feat(client): add diagram-related keyboard bindings
Browse files Browse the repository at this point in the history
* properly integrate with command stack when working with properties panel
* handle menu accelerators that can't be overridden on Windows/Linux

Closes #1027
  • Loading branch information
philippfromme committed Nov 29, 2018
1 parent d770f51 commit 4b3ab1d
Show file tree
Hide file tree
Showing 10 changed files with 574 additions and 13 deletions.
164 changes: 164 additions & 0 deletions client/src/app/tabs/DiagramKeyboardBindings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import { active as isInputActive } from '../../util/dom/isInput';

import { isArray } from 'min-dash';


/**
* Fixes two issues when regarding keyboard:
*
* 1. Ensures proper command stack integration when working with properties
* panel.
*
* 2. Fixes issue with Electron applications on Linux and Windows. Checks for
* menu item accelarators that can't be overridden (CommandOrControl+A/C/V) and
* manually triggers corresponding editor actions.
*
* See https://github.com/electron/electron/issues/7165.
*/
export default class DiagramKeyboardBindings {

/**
* Constructor.
*
* @param {Object} options - Options.
* @param {boolean} [options.canCopyPaste] - Wether or not editor can copy and
* paste.
* @param {Object} options.editorActions - Editor actions module.
* @param {boolean} [options.isMac] - Wether platform is Mac or not.
* @param {Object} options.propertiesPanelParent - Parent DOM element of properties
* panel.
*/
constructor(options) {
this.canCopyPaste = options.canCopyPaste;
this.editorActions = options.editorActions;
this.isMac = options.isMac;
this.propertiesPanelParent = options.propertiesPanelParent;
}

bind() {
window.addEventListener('keydown', this._keyHandler);
}

unbind() {
window.removeEventListener('keydown', this._keyHandler);
}

_keyHandler = (event) => {
const { editorActions } = this;

const activeElement = getActiveElement();

const inputActive = isInputActive(activeElement),
isPropertiesPanel = this.propertiesPanelParent.contains(activeElement);

// undo
// CmdOrCtrl + Z
if (isUndo(event) && inputActive && isPropertiesPanel) {
editorActions.trigger('undo');

event.preventDefault();

return;
}

// redo
// CmdOrCtrl + Y
if (isRedo(event) && inputActive && isPropertiesPanel) {
editorActions.trigger('redo');

event.preventDefault();

return;
}

if (this.isMac) {
return;
}

// select all elements
// CmdOrCtrl + A
if (isSelectAll(event) && !inputActive && !isPropertiesPanel) {
editorActions.trigger('selectElements');

event.preventDefault();

return;
}

if (!this.canCopyPaste) {
return;
}

// copy
// CmdOrCtrl + C
if (isCopy(event) && !inputActive && !isPropertiesPanel) {
editorActions.trigger('copy');

event.preventDefault();

return;
}

// paste
// CmdOrCtrl + V
if (isPaste(event) && !inputActive && !isPropertiesPanel) {
editorActions.trigger('paste');

event.preventDefault();

return;
}
}
}

// helpers //////////

function getActiveElement() {
return document.activeElement;
}

function isUndo(event) {
return isKey(['z', 'Z'], event) && isCmdOrCtrl(event);
}

function isRedo(event) {
return isKey(['y', 'Y'], event) && isCmdOrCtrl(event);
}

function isCopy(event) {
return isKey(['c', 'C'], event) && isCmdOrCtrl(event);
}

function isPaste(event) {
return isKey(['v', 'V'], event) && isCmdOrCtrl(event);
}

function isSelectAll(event) {
return isKey(['a', 'A'], event) && isCmdOrCtrl(event);
}

/**
* @param {KeyboardEvent} event
*/
export function isCmdOrCtrl(event) {

// ensure we don't react to AltGr
// (mapped to CTRL + ALT)
if (event.altKey) {
return false;
}

return event.ctrlKey || event.metaKey;
}

/**
* Checks if key pressed is one of provided keys.
*
* @param {String|String[]} keys
* @param {KeyboardEvent} event
*/
export function isKey(keys, event) {
keys = isArray(keys) ? keys : [ keys ];

return keys.indexOf(event.key) > -1;
}
Loading

0 comments on commit 4b3ab1d

Please sign in to comment.