From 152022aec4ebed3285fd09c9265bcd2ef6de3d12 Mon Sep 17 00:00:00 2001 From: Bryan Housel Date: Tue, 13 Nov 2018 20:57:21 -0500 Subject: [PATCH] Use context.keybinding for keybindings that don't change (closes #5487) --- modules/behavior/add_way.js | 18 +++---- modules/behavior/breathe.js | 8 +-- modules/behavior/copy.js | 35 ++++++-------- modules/behavior/drag.js | 35 ++++++-------- modules/behavior/draw.js | 14 +++--- modules/behavior/edit.js | 7 ++- modules/behavior/hash.js | 11 ++--- modules/behavior/hover.js | 14 +++--- modules/behavior/operation.js | 76 ++++++++++++++--------------- modules/behavior/paste.js | 34 ++++--------- modules/behavior/select.js | 4 +- modules/behavior/tail.js | 10 ++-- modules/core/context.js | 40 ++++++--------- modules/modes/move.js | 3 +- modules/modes/rotate.js | 3 +- modules/modes/save.js | 2 +- modules/modes/select.js | 5 +- modules/modes/select_data.js | 8 ++- modules/modes/select_note.js | 8 ++- modules/ui/background.js | 10 ++-- modules/ui/conflicts.js | 2 +- modules/ui/feature_list.js | 7 +-- modules/ui/full_screen.js | 21 +++----- modules/ui/help.js | 9 +--- modules/ui/info.js | 16 +++--- modules/ui/init.js | 7 +-- modules/ui/map_data.js | 9 +--- modules/ui/map_in_map.js | 7 +-- modules/ui/modal.js | 3 +- modules/ui/modes.js | 8 +-- modules/ui/panels/background.js | 4 +- modules/ui/save.js | 12 +---- modules/ui/shortcuts.js | 9 +--- modules/ui/undo_redo.js | 6 +-- modules/ui/zoom.js | 14 ++---- modules/util/keybinding.js | 86 ++++++++++++++++++++++----------- test/spec/util/keybinding.js | 6 +-- 37 files changed, 252 insertions(+), 319 deletions(-) diff --git a/modules/behavior/add_way.js b/modules/behavior/add_way.js index 8fb6b3ca1..decb20370 100644 --- a/modules/behavior/add_way.js +++ b/modules/behavior/add_way.js @@ -9,26 +9,26 @@ export function behaviorAddWay(context) { var dispatch = d3_dispatch('start', 'startFromWay', 'startFromNode'); var draw = behaviorDraw(context); - var addWay = function(surface) { + function behavior(surface) { draw.on('click', function() { dispatch.apply('start', this, arguments); }) .on('clickWay', function() { dispatch.apply('startFromWay', this, arguments); }) .on('clickNode', function() { dispatch.apply('startFromNode', this, arguments); }) - .on('cancel', addWay.cancel) - .on('finish', addWay.cancel); + .on('cancel', behavior.cancel) + .on('finish', behavior.cancel); context.map() .dblclickEnable(false); surface.call(draw); - }; + } - addWay.off = function(surface) { + behavior.off = function(surface) { surface.call(draw.off); }; - addWay.cancel = function() { + behavior.cancel = function() { window.setTimeout(function() { context.map().dblclickEnable(true); }, 1000); @@ -37,11 +37,11 @@ export function behaviorAddWay(context) { }; - addWay.tail = function(text) { + behavior.tail = function(text) { draw.tail(text); - return addWay; + return behavior; }; - return utilRebind(addWay, dispatch, 'on'); + return utilRebind(behavior, dispatch, 'on'); } diff --git a/modules/behavior/breathe.js b/modules/behavior/breathe.js index 6b09fe476..09e0a2dc6 100644 --- a/modules/behavior/breathe.js +++ b/modules/behavior/breathe.js @@ -136,7 +136,7 @@ export function behaviorBreathe() { } - var breathe = function(surface) { + function behavior(surface) { _done = false; _timer = d3_timer(function() { // wait for elements to actually become selected @@ -148,10 +148,10 @@ export function behaviorBreathe() { _timer.stop(); return true; }, 20); - }; + } - breathe.off = function() { + behavior.off = function() { _done = true; if (_timer) { _timer.stop(); @@ -162,5 +162,5 @@ export function behaviorBreathe() { }; - return breathe; + return behavior; } diff --git a/modules/behavior/copy.js b/modules/behavior/copy.js index 247bc6ba2..d465aeedd 100644 --- a/modules/behavior/copy.js +++ b/modules/behavior/copy.js @@ -2,18 +2,11 @@ import _extend from 'lodash-es/extend'; import _groupBy from 'lodash-es/groupBy'; import _map from 'lodash-es/map'; -import { - event as d3_event, - select as d3_select -} from 'd3-selection'; - -import { utilKeybinding } from '../util'; +import { event as d3_event } from 'd3-selection'; import { uiCmd } from '../ui'; export function behaviorCopy(context) { - var keybinding = utilKeybinding('copy'); - function groupEntities(ids, graph) { var entities = ids.map(function (id) { return graph.entity(id); }); @@ -47,8 +40,15 @@ export function behaviorCopy(context) { } + function getSelectionText() { + return window.getSelection().toString(); + } + + function doCopy() { - if (!getSelectionText()) d3_event.preventDefault(); + if (!getSelectionText()) { + d3_event.preventDefault(); + } var graph = context.graph(); var selected = groupEntities(context.selectedIDs(), graph); @@ -82,20 +82,15 @@ export function behaviorCopy(context) { } - function copy() { - keybinding.on(uiCmd('⌘C'), doCopy); - d3_select(document).call(keybinding); - return copy; + function behavior() { + context.keybinding().on(uiCmd('⌘C'), doCopy); + return behavior; } - function getSelectionText() { - return window.getSelection().toString(); - } - - copy.off = function() { - d3_select(document).call(keybinding.off); + behavior.off = function() { + context.keybinding().off(uiCmd('⌘C')); }; - return copy; + return behavior; } diff --git a/modules/behavior/drag.js b/modules/behavior/drag.js index 9735b77ae..0221149ec 100644 --- a/modules/behavior/drag.js +++ b/modules/behavior/drag.js @@ -10,13 +10,8 @@ import { } from 'd3-selection'; import { osmNote } from '../osm'; - import { utilRebind } from '../util/rebind'; - -import { - utilPrefixCSSProperty, - utilPrefixDOMProperty -} from '../util'; +import { utilPrefixCSSProperty, utilPrefixDOMProperty } from '../util'; /* @@ -62,7 +57,7 @@ export function behaviorDrag() { function eventOf(thiz, argumentz) { return function(e1) { - e1.target = drag; + e1.target = behavior; d3_customEvent(e1, dispatch.apply, dispatch, [e1.type, thiz, argumentz]); }; } @@ -154,7 +149,7 @@ export function behaviorDrag() { } - function drag(selection) { + function behavior(selection) { var matchesSelector = utilPrefixDOMProperty('matchesSelector'); var delegate = dragstart; @@ -181,49 +176,49 @@ export function behaviorDrag() { } - drag.off = function(selection) { + behavior.off = function(selection) { selection .on('mousedown.drag' + _selector, null) .on('touchstart.drag' + _selector, null); }; - drag.selector = function(_) { + behavior.selector = function(_) { if (!arguments.length) return _selector; _selector = _; - return drag; + return behavior; }; - drag.origin = function (_) { + behavior.origin = function(_) { if (!arguments.length) return _origin; _origin = _; - return drag; + return behavior; }; - drag.cancel = function() { + behavior.cancel = function() { d3_select(window) .on('mousemove.drag', null) .on('mouseup.drag', null); - return drag; + return behavior; }; - drag.target = function() { + behavior.target = function() { if (!arguments.length) return _target; _target = arguments[0]; _event = eventOf(_target, Array.prototype.slice.call(arguments, 1)); - return drag; + return behavior; }; - drag.surface = function() { + behavior.surface = function() { if (!arguments.length) return _surface; _surface = arguments[0]; - return drag; + return behavior; }; - return utilRebind(drag, dispatch, 'on'); + return utilRebind(behavior, dispatch, 'on'); } diff --git a/modules/behavior/draw.js b/modules/behavior/draw.js index 8640b4ce0..3081d2153 100644 --- a/modules/behavior/draw.js +++ b/modules/behavior/draw.js @@ -190,7 +190,7 @@ export function behaviorDraw(context) { } - function draw(selection) { + function behavior(selection) { context.install(hover); context.install(edit); @@ -215,11 +215,11 @@ export function behaviorDraw(context) { d3_select(document) .call(keybinding); - return draw; + return behavior; } - draw.off = function(selection) { + behavior.off = function(selection) { context.ui().sidebar.hover.cancel(); context.uninstall(hover); context.uninstall(edit); @@ -240,15 +240,15 @@ export function behaviorDraw(context) { // note: keyup.space-block, click.draw-block should remain d3_select(document) - .call(keybinding.off); + .call(keybinding.unbind); }; - draw.tail = function(_) { + behavior.tail = function(_) { tail.text(_); - return draw; + return behavior; }; - return utilRebind(draw, dispatch, 'on'); + return utilRebind(behavior, dispatch, 'on'); } diff --git a/modules/behavior/edit.js b/modules/behavior/edit.js index d4ed0f6b9..09749b821 100644 --- a/modules/behavior/edit.js +++ b/modules/behavior/edit.js @@ -1,16 +1,15 @@ export function behaviorEdit(context) { - function edit() { + function behavior() { context.map() .minzoom(context.minEditableZoom()); } - edit.off = function() { + behavior.off = function() { context.map() .minzoom(0); }; - - return edit; + return behavior; } diff --git a/modules/behavior/hash.js b/modules/behavior/hash.js index 2aa76ff2b..72e20edf4 100644 --- a/modules/behavior/hash.js +++ b/modules/behavior/hash.js @@ -86,7 +86,7 @@ export function behaviorHash(context) { } - function hash() { + function behavior() { context.map() .on('move.hash', throttledUpdate); @@ -97,7 +97,6 @@ export function behaviorHash(context) { .on('hashchange.hash', hashchange); if (window.location.hash) { - var q = utilStringQs(window.location.hash.substring(1)); if (q.id) { @@ -119,19 +118,19 @@ export function behaviorHash(context) { } if (q.walkthrough === 'true') { - hash.startWalkthrough = true; + behavior.startWalkthrough = true; } hashchange(); if (q.map) { - hash.hadHash = true; + behavior.hadHash = true; } } } - hash.off = function() { + behavior.off = function() { throttledUpdate.cancel(); context.map() @@ -147,5 +146,5 @@ export function behaviorHash(context) { }; - return hash; + return behavior; } diff --git a/modules/behavior/hover.js b/modules/behavior/hover.js index fc0ef8a7a..b7bd503de 100644 --- a/modules/behavior/hover.js +++ b/modules/behavior/hover.js @@ -55,7 +55,7 @@ export function behaviorHover(context) { } - var hover = function(selection) { + function behavior(selection) { _selection = selection; _newId = null; @@ -150,10 +150,10 @@ export function behaviorHover(context) { dispatch.call('hover', this, null); } } - }; + } - hover.off = function(selection) { + behavior.off = function(selection) { selection.selectAll('.hover') .classed('hover', false); selection.selectAll('.hover-suppressed') @@ -172,12 +172,12 @@ export function behaviorHover(context) { }; - hover.altDisables = function(_) { + behavior.altDisables = function(val) { if (!arguments.length) return _altDisables; - _altDisables = _; - return hover; + _altDisables = val; + return behavior; }; - return utilRebind(hover, dispatch, 'on'); + return utilRebind(behavior, dispatch, 'on'); } diff --git a/modules/behavior/operation.js b/modules/behavior/operation.js index 5e3e03897..91d61bb4b 100644 --- a/modules/behavior/operation.js +++ b/modules/behavior/operation.js @@ -1,57 +1,51 @@ -import { - event as d3_event, - select as d3_select -} from 'd3-selection'; - +import { event as d3_event } from 'd3-selection'; import { uiFlash } from '../ui'; -import { utilKeybinding } from '../util'; /* Creates a keybinding behavior for an operation */ -export function behaviorOperation() { - var keybinding; +export function behaviorOperation(context) { var _operation; - var behavior = function () { + function keypress() { + d3_event.preventDefault(); + var disabled = _operation.disabled(); + var flash; + + if (disabled) { + flash = uiFlash() + .duration(4000) + .iconName('#iD-operation-' + _operation.id) + .iconClass('operation disabled') + .text(_operation.tooltip); + + flash(); + + } else { + flash = uiFlash() + .duration(2000) + .iconName('#iD-operation-' + _operation.id) + .iconClass('operation') + .text(_operation.annotation() || _operation.title); + + flash(); + _operation(); + } + } + + + function behavior() { if (_operation && _operation.available()) { - keybinding = utilKeybinding('behavior.key.' + _operation.id); - keybinding.on(_operation.keys, function() { - d3_event.preventDefault(); - var disabled = _operation.disabled(); - var flash; - - if (disabled) { - flash = uiFlash() - .duration(4000) - .iconName('#iD-operation-' + _operation.id) - .iconClass('operation disabled') - .text(_operation.tooltip); - - flash(); - - } else { - flash = uiFlash() - .duration(2000) - .iconName('#iD-operation-' + _operation.id) - .iconClass('operation') - .text(_operation.annotation() || _operation.title); - - flash(); - _operation(); - } - }); - - d3_select(document).call(keybinding); + context.keybinding() + .on(_operation.keys, keypress); } return behavior; - }; + } behavior.off = function() { - if (keybinding) { - d3_select(document).call(keybinding.off); - } + context.keybinding() + .off(_operation.keys); }; diff --git a/modules/behavior/paste.js b/modules/behavior/paste.js index a663309b8..596ed9c2c 100644 --- a/modules/behavior/paste.js +++ b/modules/behavior/paste.js @@ -1,30 +1,15 @@ import _invert from 'lodash-es/invert'; import _mapValues from 'lodash-es/mapValues'; -import { - event as d3_event, - select as d3_select -} from 'd3-selection'; - -import { - actionCopyEntities, - actionMove -} from '../actions'; - -import { - geoExtent, - geoPointInPolygon, - geoVecSubtract -} from '../geo'; +import { event as d3_event } from 'd3-selection'; +import { actionCopyEntities, actionMove } from '../actions'; +import { geoExtent, geoPointInPolygon, geoVecSubtract } from '../geo'; import { modeMove } from '../modes'; import { uiCmd } from '../ui'; -import { utilKeybinding } from '../util'; export function behaviorPaste(context) { - var keybinding = utilKeybinding('paste'); - function doPaste() { d3_event.preventDefault(); @@ -78,17 +63,16 @@ export function behaviorPaste(context) { } - function paste() { - keybinding.on(uiCmd('⌘V'), doPaste); - d3_select(document).call(keybinding); - return paste; + function behavior() { + context.keybinding().on(uiCmd('⌘V'), doPaste); + return behavior; } - paste.off = function() { - d3_select(document).call(keybinding.off); + behavior.off = function() { + context.keybinding().off(uiCmd('⌘V')); }; - return paste; + return behavior; } diff --git a/modules/behavior/select.js b/modules/behavior/select.js index 1dd051e65..bbb6df526 100644 --- a/modules/behavior/select.js +++ b/modules/behavior/select.js @@ -180,7 +180,7 @@ export function behaviorSelect(context) { } - var behavior = function(selection) { + function behavior(selection) { lastMouse = null; suppressMenu = true; p1 = null; @@ -208,7 +208,7 @@ export function behaviorSelect(context) { context.surface() .classed('behavior-multiselect', true); } - }; + } behavior.off = function(selection) { diff --git a/modules/behavior/tail.js b/modules/behavior/tail.js index 3b4919516..bd284c072 100644 --- a/modules/behavior/tail.js +++ b/modules/behavior/tail.js @@ -15,7 +15,7 @@ export function behaviorTail() { var _text; - function tail(selection) { + function behavior(selection) { if (!_text) return; d3_select(window) @@ -71,7 +71,7 @@ export function behaviorTail() { } - tail.off = function(selection) { + behavior.off = function(selection) { if (!_text) return; container @@ -88,12 +88,12 @@ export function behaviorTail() { }; - tail.text = function(val) { + behavior.text = function(val) { if (!arguments.length) return _text; _text = val; - return tail; + return behavior; }; - return tail; + return behavior; } diff --git a/modules/core/context.js b/modules/core/context.js index 7efd9e7b2..8d5c3095d 100644 --- a/modules/core/context.js +++ b/modules/core/context.js @@ -10,38 +10,18 @@ import { dispatch as d3_dispatch } from 'd3-dispatch'; import { json as d3_json } from 'd3-request'; import { select as d3_select } from 'd3-selection'; -import { - t, - currentLocale, - addTranslation, - setLocale -} from '../util/locale'; +import { t, currentLocale, addTranslation, setLocale } from '../util/locale'; import { coreHistory } from './history'; - -import { - dataLocales, - dataEn -} from '../../data'; - +import { dataLocales, dataEn } from '../../data'; import { geoRawMercator } from '../geo/raw_mercator'; import { modeSelect } from '../modes/select'; import { presetIndex } from '../presets'; - -import { - rendererBackground, - rendererFeatures, - rendererMap -} from '../renderer'; - +import { rendererBackground, rendererFeatures, rendererMap } from '../renderer'; import { services } from '../services'; import { uiInit } from '../ui/init'; import { utilDetect } from '../util/detect'; - -import { - utilCallWhenIdle, - utilRebind -} from '../util'; +import { utilCallWhenIdle, utilKeybinding, utilRebind } from '../util'; export var areaKeys = {}; @@ -104,9 +84,17 @@ export function coreContext() { }; - /* Straight accessors. Avoid using these if you can. */ - var ui, connection, history; + /* User interface and keybinding */ + var ui; context.ui = function() { return ui; }; + + var keybinding = utilKeybinding('context'); + context.keybinding = function() { return keybinding; }; + d3_select(document).call(keybinding); + + + /* Straight accessors. Avoid using these if you can. */ + var connection, history; context.connection = function() { return connection; }; context.history = function() { return history; }; diff --git a/modules/modes/move.js b/modules/modes/move.js index 1a9704e4b..d46e8fd5f 100644 --- a/modules/modes/move.js +++ b/modules/modes/move.js @@ -159,7 +159,8 @@ export function modeMove(context, entityIDs, baseGraph) { context.history() .on('undone.move', null); - keybinding.off(); + d3_select(document) + .call(keybinding.unbind); }; diff --git a/modules/modes/rotate.js b/modules/modes/rotate.js index 1b0f30248..d9d98d209 100644 --- a/modules/modes/rotate.js +++ b/modules/modes/rotate.js @@ -142,7 +142,8 @@ export function modeRotate(context, entityIDs) { context.history() .on('undone.rotate', null); - keybinding.off(); + d3_select(document) + .call(keybinding.unbind); }; diff --git a/modules/modes/save.js b/modules/modes/save.js index 5b1ef162f..52c09511c 100644 --- a/modules/modes/save.js +++ b/modules/modes/save.js @@ -487,7 +487,7 @@ export function modeSave(context) { function keybindingOff() { d3_select(document) - .call(keybinding.off); + .call(keybinding.unbind); } diff --git a/modules/modes/select.js b/modules/modes/select.js index 8e47d6aed..add5b6479 100644 --- a/modules/modes/select.js +++ b/modules/modes/select.js @@ -511,7 +511,10 @@ export function modeSelect(context, selectedIDs) { if (inspector) wrap.call(inspector.close); behaviors.forEach(context.uninstall); - keybinding.off(); + + d3_select(document) + .call(keybinding.unbind); + closeMenu(); editMenu = undefined; diff --git a/modules/modes/select_data.js b/modules/modes/select_data.js index e7b9f8bf8..fa1e413c7 100644 --- a/modules/modes/select_data.js +++ b/modules/modes/select_data.js @@ -63,7 +63,9 @@ export function modeSelectData(context, selectedDatum) { mode.enter = function() { behaviors.forEach(context.install); keybinding.on('⎋', esc, true); - d3_select(document).call(keybinding); + + d3_select(document) + .call(keybinding); selectData(); @@ -81,7 +83,9 @@ export function modeSelectData(context, selectedDatum) { mode.exit = function() { behaviors.forEach(context.uninstall); - keybinding.off(); + + d3_select(document) + .call(keybinding.unbind); context.surface() .selectAll('.layer-mapdata .selected') diff --git a/modules/modes/select_note.js b/modules/modes/select_note.js index 43606540d..f2dcc3835 100644 --- a/modules/modes/select_note.js +++ b/modules/modes/select_note.js @@ -95,7 +95,9 @@ export function modeSelectNote(context, selectedNoteID) { behaviors.forEach(context.install); keybinding.on('⎋', esc, true); - d3_select(document).call(keybinding); + + d3_select(document) + .call(keybinding); selectNote(); @@ -112,7 +114,9 @@ export function modeSelectNote(context, selectedNoteID) { mode.exit = function() { behaviors.forEach(context.uninstall); - keybinding.off(); + + d3_select(document) + .call(keybinding.unbind); context.surface() .selectAll('.layer-notes .selected') diff --git a/modules/ui/background.js b/modules/ui/background.js index c95fc59c2..4d98aedfd 100644 --- a/modules/ui/background.js +++ b/modules/ui/background.js @@ -21,7 +21,7 @@ import { uiMapData } from './map_data'; import { uiMapInMap } from './map_in_map'; import { uiSettingsCustomBackground } from './settings/custom_background'; import { uiTooltipHtml } from './tooltipHtml'; -import { utilCallWhenIdle, utilKeybinding } from '../util'; +import { utilCallWhenIdle } from '../util'; import { tooltip } from '../util/tooltip'; @@ -410,13 +410,9 @@ export function uiBackground(context) { update(); - var keybinding = utilKeybinding('background') + context.keybinding() .on(key, togglePane) - .on(uiCmd('⌘' + key), quickSwitch) - .on([t('map_data.key'), t('help.key')], hidePane); - - d3_select(document) - .call(keybinding); + .on(uiCmd('⌘' + key), quickSwitch); uiBackground.hidePane = hidePane; uiBackground.togglePane = togglePane; diff --git a/modules/ui/conflicts.js b/modules/ui/conflicts.js index fed273f45..9bb0e1252 100644 --- a/modules/ui/conflicts.js +++ b/modules/ui/conflicts.js @@ -34,7 +34,7 @@ export function uiConflicts(context) { function keybindingOff() { d3_select(document) - .call(keybinding.off); + .call(keybinding.unbind); } function tryAgain() { diff --git a/modules/ui/feature_list.js b/modules/ui/feature_list.js index c086e952b..5bef2e1ba 100644 --- a/modules/ui/feature_list.js +++ b/modules/ui/feature_list.js @@ -18,13 +18,11 @@ import { utilDisplayName, utilDisplayType, utilEntityOrMemberSelector, - utilKeybinding, utilNoAuto } from '../util'; export function uiFeatureList(context) { - var keybinding = utilKeybinding('uiFeatureList'); var _geocodeResults; @@ -66,12 +64,9 @@ export function uiFeatureList(context) { context.map() .on('drawn.feature-list', mapDrawn); - keybinding + context.keybinding() .on(uiCmd('⌘F'), focusSearch); - d3_select(document) - .call(keybinding); - function focusSearch() { var mode = context.mode() && context.mode().id; diff --git a/modules/ui/full_screen.js b/modules/ui/full_screen.js index 32bb4cdb4..9422afbf1 100644 --- a/modules/ui/full_screen.js +++ b/modules/ui/full_screen.js @@ -1,17 +1,12 @@ -import { - event as d3_event, - select as d3_select -} from 'd3-selection'; +import { event as d3_event } from 'd3-selection'; import { uiCmd } from './cmd'; -import { utilKeybinding } from '../util'; import { utilDetect } from '../util/detect'; export function uiFullScreen(context) { - var element = context.container().node(), - keybinding = utilKeybinding('full-screen'); - // button; + var element = context.container().node(); + // var button = d3_select(null); function getFullScreenFn() { @@ -66,8 +61,7 @@ export function uiFullScreen(context) { return function() { // selection) { - if (!isSupported()) - return; + if (!isSupported()) return; // button = selection.append('button') // .attr('title', t('full_screen')) @@ -79,10 +73,7 @@ export function uiFullScreen(context) { // .attr('class', 'icon full-screen'); var detected = utilDetect(); - var keys = detected.os === 'mac' ? [uiCmd('⌃⌘F'), 'f11'] : ['f11']; - keybinding.on(keys, fullScreen); - - d3_select(document) - .call(keybinding); + var keys = (detected.os === 'mac' ? [uiCmd('⌃⌘F'), 'f11'] : ['f11']); + context.keybinding().on(keys, fullScreen); }; } diff --git a/modules/ui/help.js b/modules/ui/help.js index a9035e140..195c34819 100644 --- a/modules/ui/help.js +++ b/modules/ui/help.js @@ -11,7 +11,6 @@ import { uiIntro } from './intro'; import { uiMapData } from './map_data'; import { uiShortcuts } from './shortcuts'; import { uiTooltipHtml } from './tooltipHtml'; -import { utilKeybinding } from '../util'; import { t, textDirection } from '../util/locale'; import { tooltip } from '../util/tooltip'; @@ -476,12 +475,8 @@ export function uiHelp(context) { clickHelp(docs[0], 0); - var keybinding = utilKeybinding('help') - .on(key, togglePane) - .on([t('background.key'), t('map_data.key')], hidePane); - - d3_select(document) - .call(keybinding); + context.keybinding() + .on(key, togglePane); uiHelp.hidePane = hidePane; uiHelp.togglePane = togglePane; diff --git a/modules/ui/info.js b/modules/ui/info.js index 20553c996..3e8bfae27 100644 --- a/modules/ui/info.js +++ b/modules/ui/info.js @@ -7,14 +7,13 @@ import { t } from '../util/locale'; import { svgIcon } from '../svg'; import { uiCmd } from './cmd'; import { uiInfoPanels } from './panels'; -import { utilKeybinding } from '../util'; export function uiInfo(context) { - var ids = Object.keys(uiInfoPanels), - wasActive = ['measurement'], - panels = {}, - active = {}; + var ids = Object.keys(uiInfoPanels); + var wasActive = ['measurement']; + var panels = {}; + var active = {}; // create panels ids.forEach(function(k) { @@ -117,18 +116,15 @@ export function uiInfo(context) { redraw(); - var keybinding = utilKeybinding('info') + context.keybinding() .on(uiCmd('⌘' + t('info_panels.key')), toggle); ids.forEach(function(k) { var key = t('info_panels.' + k + '.key', { default: null }); if (!key) return; - keybinding + context.keybinding() .on(uiCmd('⌘⇧' + key), function() { toggle(k); }); }); - - d3_select(document) - .call(keybinding); } return info; diff --git a/modules/ui/init.js b/modules/ui/init.js index 93c8c9c5b..fcc8ce493 100644 --- a/modules/ui/init.js +++ b/modules/ui/init.js @@ -10,7 +10,6 @@ import { behaviorHash } from '../behavior'; import { modeBrowse } from '../modes'; import { svgDefs, svgIcon } from '../svg'; import { utilGetDimensions } from '../util/dimensions'; -import { utilKeybinding } from '../util'; import { uiAccount } from './account'; import { uiAttribution } from './attribution'; @@ -297,7 +296,7 @@ export function uiInit(context) { var panPixels = 80; - var keybinding = utilKeybinding('main') + context.keybinding() .on('⌫', function() { d3_event.preventDefault(); }) .on(t('sidebar.key'), ui.sidebar.toggle) .on('←', pan([panPixels, 0])) @@ -309,9 +308,6 @@ export function uiInit(context) { .on(['⇧→', uiCmd('⌘→')], pan([-map.dimensions()[0], 0])) .on(['⇧↓', uiCmd('⌘↓')], pan([0, -map.dimensions()[1]])); - d3_select(document) - .call(keybinding); - context.enter(modeBrowse(context)); if (!_initCounter++) { @@ -372,6 +368,7 @@ export function uiInit(context) { ui.restart = function(arg) { + context.keybinding().clear(); context.locale(arg); context.loadLocale(function(err) { if (!err) { diff --git a/modules/ui/map_data.js b/modules/ui/map_data.js index aeae728d7..3fa8cfb8f 100644 --- a/modules/ui/map_data.js +++ b/modules/ui/map_data.js @@ -13,7 +13,6 @@ import { uiDisclosure } from './disclosure'; import { uiHelp } from './help'; import { uiSettingsCustomData } from './settings/custom_data'; import { uiTooltipHtml } from './tooltipHtml'; -import { utilKeybinding } from '../util'; export function uiMapData(context) { @@ -645,13 +644,9 @@ export function uiMapData(context) { update(); setFill(_fillSelected); - var keybinding = utilKeybinding('features') + context.keybinding() .on(key, togglePane) - .on(t('area_fill.wireframe.key'), toggleWireframe) - .on([t('background.key'), t('help.key')], hidePane); - - d3_select(document) - .call(keybinding); + .on(t('area_fill.wireframe.key'), toggleWireframe); uiMapData.hidePane = hidePane; uiMapData.togglePane = togglePane; diff --git a/modules/ui/map_in_map.js b/modules/ui/map_in_map.js index 0cf229705..55b9b4ed7 100644 --- a/modules/ui/map_in_map.js +++ b/modules/ui/map_in_map.js @@ -21,7 +21,7 @@ import { import { rendererTileLayer } from '../renderer'; import { svgDebug, svgData } from '../svg'; -import { utilKeybinding, utilSetTransform } from '../util'; +import { utilSetTransform } from '../util'; import { utilGetDimensions } from '../util/dimensions'; @@ -334,11 +334,8 @@ export function uiMapInMap(context) { redraw(); - var keybinding = utilKeybinding('map-in-map') + context.keybinding() .on(t('background.minimap.key'), toggle); - - d3_select(document) - .call(keybinding); } return map_in_map; diff --git a/modules/ui/modal.js b/modules/ui/modal.js index 91de76e8a..9ffc14ade 100644 --- a/modules/ui/modal.js +++ b/modules/ui/modal.js @@ -34,7 +34,8 @@ export function uiModal(selection, blocking) { .duration(200) .style('top','0px'); - keybinding.off(); + d3_select(document) + .call(keybinding.unbind); }; diff --git a/modules/ui/modes.js b/modules/ui/modes.js index 503544d95..d27f31dcf 100644 --- a/modules/ui/modes.js +++ b/modules/ui/modes.js @@ -12,7 +12,6 @@ import { import { svgIcon } from '../svg'; import { tooltip } from '../util/tooltip'; -import { utilKeybinding } from '../util'; import { uiTooltipHtml } from './tooltipHtml'; @@ -56,10 +55,8 @@ export function uiModes(context) { .classed('mode-' + exited.id, false); }); - var keybinding = utilKeybinding('mode-buttons'); - modes.forEach(function(mode) { - keybinding.on(mode.key, function() { + context.keybinding().on(mode.key, function() { if (mode.id === 'add-note' && !(notesEnabled() && notesEditable())) return; if (mode.id !== 'add-note' && !editable()) return; @@ -71,9 +68,6 @@ export function uiModes(context) { }); }); - d3_select(document) - .call(keybinding); - var debouncedUpdate = _debounce(update, 500, { leading: true, trailing: true }); diff --git a/modules/ui/panels/background.js b/modules/ui/panels/background.js index 5f869405a..38e81fd23 100644 --- a/modules/ui/panels/background.js +++ b/modules/ui/panels/background.js @@ -20,8 +20,8 @@ export function uiPanelBackground(context) { var debouncedRedraw = _debounce(redraw, 250); function redraw(selection) { - var source = background.baseLayerSource(), - isDG = (source.id.match(/^DigitalGlobe/i) !== null); + var source = background.baseLayerSource(); + var isDG = (source.id.match(/^DigitalGlobe/i) !== null); if (currSourceName !== source.name()) { currSourceName = source.name(); diff --git a/modules/ui/save.js b/modules/ui/save.js index 6b4f0d90d..686f548c6 100644 --- a/modules/ui/save.js +++ b/modules/ui/save.js @@ -1,16 +1,11 @@ import { interpolateRgb as d3_interpolateRgb } from 'd3-interpolate'; - -import { - event as d3_event, - select as d3_select -} from 'd3-selection'; +import { event as d3_event } from 'd3-selection'; import { t } from '../util/locale'; import { modeSave } from '../modes'; import { svgIcon } from '../svg'; import { uiCmd } from './cmd'; import { uiTooltipHtml } from './tooltipHtml'; -import { utilKeybinding } from '../util'; import { tooltip } from '../util/tooltip'; @@ -100,12 +95,9 @@ export function uiSave(context) { updateCount(); - var keybinding = utilKeybinding('uiSave') + context.keybinding() .on(key, save, true); - d3_select(document) - .call(keybinding); - context.history() .on('change.save', updateCount); diff --git a/modules/ui/shortcuts.js b/modules/ui/shortcuts.js index 05c124829..43ee2b0b5 100644 --- a/modules/ui/shortcuts.js +++ b/modules/ui/shortcuts.js @@ -8,18 +8,17 @@ import { dataShortcuts } from '../../data'; import { svgIcon } from '../svg'; import { uiCmd } from './cmd'; import { uiModal } from './modal'; -import { utilKeybinding } from '../util'; import { utilDetect } from '../util/detect'; -export function uiShortcuts() { +export function uiShortcuts(context) { var detected = utilDetect(); var _activeTab = 0; var _modalSelection; var _selection = d3_select(null); - var keybinding = utilKeybinding('shortcuts') + context.keybinding() .on(t('shortcuts.toggle.key'), function () { if (d3_selectAll('.modal-shortcuts').size()) { // already showing if (_modalSelection) { @@ -32,10 +31,6 @@ export function uiShortcuts() { } }); - d3_select(document) - .call(keybinding); - - function shortcutsModal(_modalSelection) { _modalSelection.select('.modal') diff --git a/modules/ui/undo_redo.js b/modules/ui/undo_redo.js index 256219678..eef37b5d2 100644 --- a/modules/ui/undo_redo.js +++ b/modules/ui/undo_redo.js @@ -9,7 +9,6 @@ import { t, textDirection } from '../util/locale'; import { svgIcon } from '../svg'; import { uiCmd } from './cmd'; import { uiTooltipHtml } from './tooltipHtml'; -import { utilKeybinding } from '../util'; import { tooltip } from '../util/tooltip'; @@ -64,13 +63,10 @@ export function uiUndoRedo(context) { .call(svgIcon('#iD-icon-' + iconName)); }); - var keybinding = utilKeybinding('undo') + context.keybinding() .on(commands[0].cmd, function() { d3_event.preventDefault(); commands[0].action(); }) .on(commands[1].cmd, function() { d3_event.preventDefault(); commands[1].action(); }); - d3_select(document) - .call(keybinding); - var debouncedUpdate = _debounce(update, 500, { leading: true, trailing: true }); diff --git a/modules/ui/zoom.js b/modules/ui/zoom.js index 27e6286a2..baf4b539b 100644 --- a/modules/ui/zoom.js +++ b/modules/ui/zoom.js @@ -7,7 +7,6 @@ import { t, textDirection } from '../util/locale'; import { svgIcon } from '../svg'; import { uiCmd } from './cmd'; import { uiTooltipHtml } from './tooltipHtml'; -import { utilKeybinding } from '../util'; import { tooltip } from '../util/tooltip'; @@ -72,19 +71,14 @@ export function uiZoom(context) { .call(svgIcon('#iD-icon-' + d.icon, 'light')); }); - var keybinding = utilKeybinding('zoom'); - ['plus', 'ffplus', '=', 'ffequals'].forEach(function(key) { - keybinding.on([key], zoomIn); - keybinding.on([uiCmd('⌘' + key)], zoomInFurther); + context.keybinding().on([key], zoomIn); + context.keybinding().on([uiCmd('⌘' + key)], zoomInFurther); }); ['_', '-', 'ffminus', 'dash'].forEach(function(key) { - keybinding.on([key], zoomOut); - keybinding.on([uiCmd('⌘' + key)], zoomOutFurther); + context.keybinding().on([key], zoomOut); + context.keybinding().on([uiCmd('⌘' + key)], zoomOutFurther); }); - - d3_select(document) - .call(keybinding); }; } diff --git a/modules/util/keybinding.js b/modules/util/keybinding.js index 88019df27..ec643b09c 100644 --- a/modules/util/keybinding.js +++ b/modules/util/keybinding.js @@ -1,3 +1,5 @@ +import _isFunction from 'lodash-es/isFunction'; + import { event as d3_event, select as d3_select @@ -5,11 +7,12 @@ import { export function utilKeybinding(namespace) { - var _keybindings = []; + var _keybindings = {}; function testBindings(isCapturing) { var didMatch = false; + var bindings = Object.keys(_keybindings).map(function(id) { return _keybindings[id]; }); var i, binding; // Most key shortcuts will accept either lower or uppercase ('h' or 'H'), @@ -18,8 +21,8 @@ export function utilKeybinding(namespace) { // (This lets us differentiate between '←'/'⇧←' or '⌘Z'/'⌘⇧Z') // priority match shifted keybindings first - for (i = 0; i < _keybindings.length; i++) { - binding = _keybindings[i]; + for (i = 0; i < bindings.length; i++) { + binding = bindings[i]; if (!binding.event.modifiers.shiftKey) continue; // no shift if (!!binding.capture !== isCapturing) continue; if (matches(binding, true)) { @@ -30,8 +33,8 @@ export function utilKeybinding(namespace) { // then unshifted keybindings if (didMatch) return; - for (i = 0; i < _keybindings.length; i++) { - binding = _keybindings[i]; + for (i = 0; i < bindings.length; i++) { + binding = bindings[i]; if (binding.event.modifiers.shiftKey) continue; // shift if (!!binding.capture !== isCapturing) continue; if (matches(binding, false)) { @@ -99,26 +102,53 @@ export function utilKeybinding(namespace) { function keybinding(selection) { selection = selection || d3_select(document); - selection.on('keydown.capture' + namespace, capture, true); - selection.on('keydown.bubble' + namespace, bubble, false); + selection.on('keydown.capture.' + namespace, capture, true); + selection.on('keydown.bubble.' + namespace, bubble, false); return keybinding; } - - keybinding.off = function(selection) { + // was: keybinding.off() + keybinding.unbind = function(selection) { _keybindings = []; selection = selection || d3_select(document); - selection.on('keydown.capture' + namespace, null); - selection.on('keydown.bubble' + namespace, null); + selection.on('keydown.capture.' + namespace, null); + selection.on('keydown.bubble.' + namespace, null); return keybinding; }; - keybinding.on = function(codes, callback, capture) { + keybinding.clear = function() { + _keybindings = {}; + return keybinding; + }; + + + // Remove one or more keycode bindings. + keybinding.off = function(codes, capture) { var arr = [].concat(codes); + for (var i = 0; i < arr.length; i++) { - var code = arr[i]; + var id = arr[i] + (capture ? '-capture' : '-bubble'); + delete _keybindings[id]; + } + return keybinding; + }; + + + // Add one or more keycode bindings. + keybinding.on = function(codes, callback, capture) { + if (!_isFunction(callback)) { + return keybinding.off(codes, capture); + } + + var arr = [].concat(codes); + + for (var i = 0; i < arr.length; i++) { + var id = arr[i] + (capture ? '-capture' : '-bubble'); var binding = { + id: id, + capture: capture, + callback: callback, event: { key: undefined, // preferred keyCode: 0, // fallback @@ -128,33 +158,36 @@ export function utilKeybinding(namespace) { altKey: false, metaKey: false } - }, - capture: capture, - callback: callback + } }; - code = code.toLowerCase().match(/(?:(?:[^+⇧⌃⌥⌘])+|[⇧⌃⌥⌘]|\+\+|^\+$)/g); + if (_keybindings[id]) { + console.warn('warning: duplicate keybinding for "' + id + '"'); // eslint-disable-line no-console + } - for (var j = 0; j < code.length; j++) { + _keybindings[id] = binding; + + var matches = arr[i].toLowerCase().match(/(?:(?:[^+⇧⌃⌥⌘])+|[⇧⌃⌥⌘]|\+\+|^\+$)/g); + for (var j = 0; j < matches.length; j++) { // Normalise matching errors - if (code[j] === '++') code[j] = '+'; + if (matches[j] === '++') matches[j] = '+'; - if (code[j] in utilKeybinding.modifierCodes) { - binding.event.modifiers[utilKeybinding.modifierProperties[utilKeybinding.modifierCodes[code[j]]]] = true; + if (matches[j] in utilKeybinding.modifierCodes) { + var prop = utilKeybinding.modifierProperties[utilKeybinding.modifierCodes[matches[j]]]; + binding.event.modifiers[prop] = true; } else { - binding.event.key = utilKeybinding.keys[code[j]] || code[j]; - if (code[j] in utilKeybinding.keyCodes) { - binding.event.keyCode = utilKeybinding.keyCodes[code[j]]; + binding.event.key = utilKeybinding.keys[matches[j]] || matches[j]; + if (matches[j] in utilKeybinding.keyCodes) { + binding.event.keyCode = utilKeybinding.keyCodes[matches[j]]; } } } - - _keybindings.push(binding); } return keybinding; }; + return keybinding; } @@ -163,7 +196,6 @@ export function utilKeybinding(namespace) { * See https://github.com/keithamus/jwerty */ - utilKeybinding.modifierCodes = { // Shift key, ⇧ '⇧': 16, shift: 16, diff --git a/test/spec/util/keybinding.js b/test/spec/util/keybinding.js index 672060dba..0b12c4040 100644 --- a/test/spec/util/keybinding.js +++ b/test/spec/util/keybinding.js @@ -9,7 +9,7 @@ describe('utilKeybinding', function() { }); afterEach(function () { - keybinding.off(d3.select(document)); + d3.select(document).call(keybinding.unbind); input.remove(); }); @@ -86,13 +86,13 @@ describe('utilKeybinding', function() { expect(spy).to.have.been.calledOnce; }); - it('resets bindings when keybinding.off is called', function () { + it('resets bindings when keybinding.unbind is called', function () { d3.select(document).call(keybinding.on('A', spy)); happen.keydown(document, {keyCode: 65}); expect(spy).to.have.been.calledOnce; spy = sinon.spy(); - d3.select(document).call(keybinding.off); + d3.select(document).call(keybinding.unbind); d3.select(document).call(keybinding.on('A', spy)); happen.keydown(document, {keyCode: 65}); expect(spy).to.have.been.calledOnce;