From 571efb914466ce00f357e308ba6238def1c7d8b6 Mon Sep 17 00:00:00 2001 From: Maciej Barelkowski Date: Wed, 24 Oct 2018 15:48:35 +0200 Subject: [PATCH] feat(keyboard): move arrow bindings to keyboard-move feature Moving canvas with keyboard will now be controlled by KeyboardMove feature. To use it, press Cmd/Ctrl and arrows. Use Shift for accelerated move speed. Configure speed by adding `keyboardMove` to `config` with following properties: - moveSpeed - moveSpeedAccelerated BREAKING CHANGES: - remove canvas movement controls from Keyboard - move canvas with keyboard arrows and Cmd/Ctrl --- lib/features/keyboard/KeyboardBindings.js | 51 ---- lib/navigation/keyboard-move/KeyboardMove.js | 82 ++++++ lib/navigation/keyboard-move/index.js | 14 + lib/navigation/movecanvas/MoveCanvas.js | 38 ++- test/spec/features/keyboard/MoveCanvasSpec.js | 157 ---------- .../keyboard-move/KeyboardMoveSpec.js | 275 ++++++++++++++++++ 6 files changed, 397 insertions(+), 220 deletions(-) create mode 100644 lib/navigation/keyboard-move/KeyboardMove.js create mode 100644 lib/navigation/keyboard-move/index.js delete mode 100644 test/spec/features/keyboard/MoveCanvasSpec.js create mode 100755 test/spec/navigation/keyboard-move/KeyboardMoveSpec.js diff --git a/lib/features/keyboard/KeyboardBindings.js b/lib/features/keyboard/KeyboardBindings.js index 4e28aef2b..18db9b3dc 100644 --- a/lib/features/keyboard/KeyboardBindings.js +++ b/lib/features/keyboard/KeyboardBindings.js @@ -13,8 +13,6 @@ import { */ export default function KeyboardBindings(keyboard, editorActions) { - var config = keyboard._config; - // undo // (CTRL|CMD) + Z function undo(context) { @@ -133,53 +131,6 @@ export default function KeyboardBindings(keyboard, editorActions) { } } - // move canvas left - // left arrow - // - // 37 = Left - // 38 = Up - // 39 = Right - // 40 = Down - function moveCanvas(context) { - - var event = context.event; - - if (isKey([ - 'ArrowLeft', 'Left', - 'ArrowUp', 'Up', - 'ArrowDown', 'Down', - 'ArrowRight', 'Right' - ], event)) { - - var opts = { - invertY: config.invertY, - speed: (config.speed || 50) - }; - - switch (event.key) { - case 'ArrowLeft': - case 'Left': - opts.direction = 'left'; - break; - case 'ArrowUp': - case 'Up': - opts.direction = 'up'; - break; - case 'ArrowRight': - case 'Right': - opts.direction = 'right'; - break; - case 'ArrowDown': - case 'Down': - opts.direction = 'down'; - break; - } - - editorActions.trigger('moveCanvas', opts); - - return true; - } - } keyboard.addListener(copy); keyboard.addListener(paste); @@ -192,8 +143,6 @@ export default function KeyboardBindings(keyboard, editorActions) { keyboard.addListener(zoomDefault); keyboard.addListener(zoomIn); keyboard.addListener(zoomOut); - - keyboard.addListener(moveCanvas); } KeyboardBindings.$inject = [ diff --git a/lib/navigation/keyboard-move/KeyboardMove.js b/lib/navigation/keyboard-move/KeyboardMove.js new file mode 100644 index 000000000..c2d248512 --- /dev/null +++ b/lib/navigation/keyboard-move/KeyboardMove.js @@ -0,0 +1,82 @@ +import { assign } from 'min-dash'; + + +var DEFAULT_CONFIG = { + moveSpeed: 50, + moveSpeedAccelerated: 200 +}; + + +/** + * + * @param {Object} config + * @param {Number} [config.moveSpeed=50] + * @param {Number} [config.moveSpeedAccelerated=200] + * @param {Keyboard} keyboard + * @param {EditorActions} editorActions + */ +export default function KeyboardNavigation( + config, + keyboard, + editorActions +) { + + var self = this; + + this._config = assign({}, DEFAULT_CONFIG, config || {}); + + keyboard.addListener(arrowsListener); + + + function arrowsListener(context) { + + var event = context.event, + config = self._config; + + if (!keyboard.isCmd(event)) { + return; + } + + if (keyboard.isKey([ + 'ArrowLeft', 'Left', + 'ArrowUp', 'Up', + 'ArrowDown', 'Down', + 'ArrowRight', 'Right' + ], event)) { + + var opts = { + speed: keyboard.isShift(event) ? config.moveSpeedAccelerated : config.moveSpeed + }; + + switch (event.key) { + case 'ArrowLeft': + case 'Left': + opts.direction = 'left'; + break; + case 'ArrowUp': + case 'Up': + opts.direction = 'up'; + break; + case 'ArrowRight': + case 'Right': + opts.direction = 'right'; + break; + case 'ArrowDown': + case 'Down': + opts.direction = 'down'; + break; + } + + editorActions.trigger('moveCanvas', opts); + + return true; + } + } +} + + +KeyboardNavigation.$inject = [ + 'config.keyboardMove', + 'keyboard', + 'editorActions' +]; diff --git a/lib/navigation/keyboard-move/index.js b/lib/navigation/keyboard-move/index.js new file mode 100644 index 000000000..81bd4b38f --- /dev/null +++ b/lib/navigation/keyboard-move/index.js @@ -0,0 +1,14 @@ +import EditorActions from '../../features/editor-actions'; +import KeyboardModule from '../../features/keyboard'; + +import KeyboardMove from './KeyboardMove'; + + +export default { + __depends__: [ + EditorActions, + KeyboardModule + ], + __init__: [ 'keyboardMove' ], + keyboardMove: [ 'type', KeyboardMove ] +}; \ No newline at end of file diff --git a/lib/navigation/movecanvas/MoveCanvas.js b/lib/navigation/movecanvas/MoveCanvas.js index c4e669558..c8f34d6f4 100644 --- a/lib/navigation/movecanvas/MoveCanvas.js +++ b/lib/navigation/movecanvas/MoveCanvas.js @@ -21,17 +21,28 @@ import { } from '../../util/Event'; -function length(point) { - return Math.sqrt(Math.pow(point.x, 2) + Math.pow(point.y, 2)); -} - var THRESHOLD = 15; +/** + * @param {EventBus} eventBus + * @param {Canvas} canvas + * @param {Keyboard} keyboard + * @param {EditorActions} editorActions + */ export default function MoveCanvas(eventBus, canvas) { var context; + + // listen for move on element mouse down; + // allow others to hook into the event before us though + // (dragging / element moving will do this) + eventBus.on('element.mousedown', 500, function(e) { + return handleStart(e.originalEvent); + }); + + function handleMove(event) { var start = context.start, @@ -96,15 +107,18 @@ export default function MoveCanvas(eventBus, canvas) { // we've handled the event return true; } +} + + +MoveCanvas.$inject = [ + 'eventBus', + 'canvas' +]; - // listen for move on element mouse down; - // allow others to hook into the event before us though - // (dragging / element moving will do this) - eventBus.on('element.mousedown', 500, function(e) { - return handleStart(e.originalEvent); - }); -} +// helpers /////// -MoveCanvas.$inject = [ 'eventBus', 'canvas' ]; +function length(point) { + return Math.sqrt(Math.pow(point.x, 2) + Math.pow(point.y, 2)); +} diff --git a/test/spec/features/keyboard/MoveCanvasSpec.js b/test/spec/features/keyboard/MoveCanvasSpec.js deleted file mode 100644 index ffc0f9892..000000000 --- a/test/spec/features/keyboard/MoveCanvasSpec.js +++ /dev/null @@ -1,157 +0,0 @@ -import { - bootstrapDiagram, - getDiagramJS, - inject -} from 'test/TestHelper'; - -import { - assign, - forEach -} from 'min-dash'; - -import modelingModule from 'lib/features/modeling'; -import editorActionsModule from 'lib/features/editor-actions'; -import keyboardModule from 'lib/features/keyboard'; - -import { createKeyEvent } from 'test/util/KeyEvents'; - -var KEYS = { - LEFT: [ 'ArrowLeft', 'Left' ], - UP: [ 'ArrowUp', 'Up' ], - RIGHT: [ 'ArrowRight', 'Right' ], - DOWN: [ 'ArrowDown', 'Down' ] -}; - - -describe('features/keyboard - move canvas', function() { - - var defaultDiagramConfig = { - modules: [ - modelingModule, - keyboardModule, - editorActionsModule - ], - canvas: { - deferUpdate: false - } - }; - - beforeEach(bootstrapDiagram(defaultDiagramConfig)); - - - describe('with default config', function() { - - var decisionTable = [{ - desc: 'left arrow', - keys: KEYS.LEFT, - shiftKey: false, - x: -50, - y: 0 - }, { - desc: 'right arrow', - keys: KEYS.RIGHT, - shiftKey: false, - x: 50, - y: 0 - }, { - desc: 'up arrow', - keys: KEYS.UP, - shiftKey: false, - x: 0, - y: -50 - }, { - desc: 'down arrow', - keys: KEYS.DOWN, - shiftKey: false, - x: 0, - y: 50 - }]; - - forEach(decisionTable, function(testCase) { - - forEach(testCase.keys, function(key) { - - it('should handle ' + testCase.desc, inject(function(canvas, keyboard) { - - // given - var event = createKeyEvent(key, { shiftKey: testCase.shiftKey }); - - // when - keyboard._keyHandler(event); - - // then - expect(canvas.viewbox().x).to.eql(testCase.x); - expect(canvas.viewbox().y).to.eql(testCase.y); - })); - - }); - - }); - - }); - - - describe('with custom config', function() { - - it('should use custom speed', function() { - - // given - var keyboardConfig = { - keyboard: { - speed: 23 - } - }; - - bootstrapDiagram(assign(defaultDiagramConfig, keyboardConfig))(); - - var keyDownEvent = createKeyEvent(KEYS.DOWN[0]); - - getDiagramJS().invoke(function(canvas, keyboard) { - - // when - keyboard._keyHandler(keyDownEvent); - - // then - expect(canvas.viewbox().x).to.eql(0); - expect(canvas.viewbox().y).to.eql(23); - }); - }); - - - it('should use natural scrolling if enabled', function() { - - // given - var keyboardConfig = { - keyboard: { - invertY: true - } - }; - - bootstrapDiagram(assign(defaultDiagramConfig, keyboardConfig))(); - - var keyDownEvent = createKeyEvent(KEYS.DOWN[0]), - keyUpEvent = createKeyEvent(KEYS.UP[0]); - - getDiagramJS().invoke(function(canvas, keyboard) { - - // when - keyboard._keyHandler(keyDownEvent); - - // then - expect(canvas.viewbox().x).to.eql(0); - expect(canvas.viewbox().y).to.eql(-50); - - // when - keyboard._keyHandler(keyUpEvent); - - // then - expect(canvas.viewbox().x).to.eql(0); - expect(canvas.viewbox().y).to.eql(0); - - }); - - }); - - }); - -}); diff --git a/test/spec/navigation/keyboard-move/KeyboardMoveSpec.js b/test/spec/navigation/keyboard-move/KeyboardMoveSpec.js new file mode 100755 index 000000000..f08ec89df --- /dev/null +++ b/test/spec/navigation/keyboard-move/KeyboardMoveSpec.js @@ -0,0 +1,275 @@ +import { + bootstrapDiagram, + getDiagramJS, + inject +} from 'test/TestHelper'; + +import { createKeyEvent } from 'test/util/KeyEvents'; + +import { + assign, + forEach +} from 'min-dash'; + +import modelingModule from 'lib/features/modeling'; +import keyboardMoveModule from 'lib/navigation/keyboard-move'; + + +describe('navigation/keyboard-move', function() { + + var defaultDiagramConfig = { + modules: [ + modelingModule, + keyboardMoveModule + ], + canvas: { + deferUpdate: false + } + }; + + describe('bootstrap', function() { + + beforeEach(bootstrapDiagram(defaultDiagramConfig)); + + + it('should bootstrap', inject(function(keyboardMove, canvas) { + + canvas.addShape({ + id: 'test', + width: 100, + height: 100, + x: 100, + y: 100 + }); + + expect(keyboardMove).not.to.be.null; + })); + + }); + + describe('arrow bindings', function() { + + var KEYS = { + LEFT: [ 'ArrowLeft', 'Left' ], + UP: [ 'ArrowUp', 'Up' ], + RIGHT: [ 'ArrowRight', 'Right' ], + DOWN: [ 'ArrowDown', 'Down' ], + }; + + beforeEach(bootstrapDiagram(defaultDiagramConfig)); + + + describe('with default config', function() { + + describe('with Ctrl/Cmd', function() { + + it('should not move when neither Ctrl nor Cmd is pressed down', + inject(function(canvas, keyboard) { + + // given + var event = createKeyEvent(KEYS.RIGHT[0], { ctrlKey: false, metaKey: false }); + + // when + keyboard._keyHandler(event); + + // then + expect(canvas.viewbox().x).to.eql(0); + expect(canvas.viewbox().y).to.eql(0); + }) + ); + + }); + + + describe('with Ctrl/Cmd', function() { + + var BASE_SPEED = 50, + HIGH_SPEED = 200; + + describe('without shift', function() { + + var decisionTable = [ + { + desc: 'move left', + keys: KEYS.LEFT, + x: -BASE_SPEED, + y: 0 + }, + { + desc: 'move right', + keys: KEYS.RIGHT, + x: BASE_SPEED, + y: 0 + }, + { + desc: 'move up', + keys: KEYS.UP, + x: 0, + y: -BASE_SPEED + }, + { + desc: 'move down', + keys: KEYS.DOWN, + x: 0, + y: BASE_SPEED + } + ]; + + forEach(decisionTable, function(testCase) { + + forEach(testCase.keys, function(key) { + + it('should ' + testCase.desc + ' with CtrlKey', inject(function(canvas, keyboard) { + + // given + var event = createKeyEvent(key, { + ctrlKey: true, + shiftKey: false + }); + + // when + keyboard._keyHandler(event); + + // then + expect(canvas.viewbox().x).to.eql(testCase.x); + expect(canvas.viewbox().y).to.eql(testCase.y); + })); + + + it('should ' + testCase.desc + ' with CmdKey', inject(function(canvas, keyboard) { + + // given + var event = createKeyEvent(key, { + metaKey: true, + shiftKey: false + }); + + // when + keyboard._keyHandler(event); + + // then + expect(canvas.viewbox().x).to.eql(testCase.x); + expect(canvas.viewbox().y).to.eql(testCase.y); + })); + + }); + + }); + + }); + + describe('with shift', function() { + + var decisionTable = [ + { + desc: 'move left', + keys: KEYS.LEFT, + x: -HIGH_SPEED, + y: 0 + }, + { + desc: 'move right', + keys: KEYS.RIGHT, + x: HIGH_SPEED, + y: 0 + }, + { + desc: 'move up', + keys: KEYS.UP, + x: 0, + y: -HIGH_SPEED + }, + { + desc: 'move down', + keys: KEYS.DOWN, + x: 0, + y: HIGH_SPEED + }, + ]; + + forEach(decisionTable, function(testCase) { + + forEach(testCase.keys, function(key) { + + it('should ' + testCase.desc, inject(function(canvas, keyboard) { + + // given + var event = createKeyEvent(key, { ctrlKey: true, shiftKey: true }); + + // when + keyboard._keyHandler(event); + + // then + expect(canvas.viewbox().x).to.eql(testCase.x); + expect(canvas.viewbox().y).to.eql(testCase.y); + })); + + }); + + }); + + }); + + }); + + }); + + + describe('with custom config', function() { + + it('should use custom speed', function() { + + // given + var customConfig = { + keyboardMove: { + moveSpeed: 23 + } + }; + + bootstrapDiagram(assign({}, defaultDiagramConfig, customConfig))(); + + var keyDownEvent = createKeyEvent(KEYS.DOWN[0], { ctrlKey: true }); + + getDiagramJS().invoke(function(canvas, keyboard) { + + // when + keyboard._keyHandler(keyDownEvent); + + // then + expect(canvas.viewbox().x).to.eql(0); + expect(canvas.viewbox().y).to.eql(23); + + }); + }); + + + it('should use custom modified speed', function() { + + // given + var customConfig = { + keyboardMove: { + moveSpeedAccelerated: 1 + } + }; + + bootstrapDiagram(assign({}, defaultDiagramConfig, customConfig))(); + + var keyDownEvent = createKeyEvent(KEYS.DOWN[0], { ctrlKey: true, shiftKey: true }); + + getDiagramJS().invoke(function(canvas, keyboard) { + + // when + keyboard._keyHandler(keyDownEvent); + + // then + expect(canvas.viewbox().x).to.eql(0); + expect(canvas.viewbox().y).to.eql(1); + + }); + }); + + }); + + }); + +});