diff --git a/index.html b/index.html index b2b53e6c9..09a96b9ed 100644 --- a/index.html +++ b/index.html @@ -20,6 +20,7 @@ + diff --git a/js/id/actions/modes.js b/js/id/actions/modes.js index eaee613c1..357d0a2df 100644 --- a/js/id/actions/modes.js +++ b/js/id/actions/modes.js @@ -22,7 +22,8 @@ iD.modes.chooseIndex = function(way, point, map) { }; iD.modes.AddPlace = { - title: "+ Place", + id: 'add-place', + title: '+ Place', enter: function() { var surface = this.map.surface, teaser = surface.selectAll('g#temp-g') @@ -66,7 +67,8 @@ iD.modes.AddPlace = { // user has clicked 'add road' or pressed a keybinding, and now has // a teaser node and needs to click on the map to start a road iD.modes.AddRoad = { - title: "+ Road", + id: 'add-road', + title: '+ Road', way: function() { return iD.Way({ tags: { highway: 'residential', elastic: 'true' } }); }, @@ -235,7 +237,8 @@ iD.modes.DrawRoad = function(way_id, direction) { }; iD.modes.AddArea = { - title: "+ Area", + id: 'add-area', + title: '+ Area', way: function() { return iD.Way({ tags: { building: 'yes', area: 'yes', elastic: 'true' } diff --git a/js/id/id.js b/js/id/id.js index ae006df1c..fdd9969cf 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -27,7 +27,10 @@ window.iD = function(container) { var buttons = bar.selectAll('button.add-button') .data([iD.modes.AddPlace, iD.modes.AddRoad, iD.modes.AddArea]) - .enter().append('button').attr('class', 'add-button') + .enter().append('button') + .attr('class', function(d) { + return 'add-button ' + d.id; + }) .text(function (mode) { return mode.title; }) .on('click', function (mode) { controller.enter(mode); }); @@ -137,17 +140,21 @@ window.iD = function(container) { map.size(m.size()); }; - d3.select(document).on('keydown', function() { - // cmd-z - if (d3.event.which === 90 && d3.event.metaKey) { - history.undo(); - } - // cmd-shift-z - if (d3.event.which === 90 && d3.event.metaKey && d3.event.shiftKey) { - history.redo(); - } - }); - + var keybinding = d3.keybinding() + .on('a', function(evt, mods) { + controller.enter(iD.modes.AddArea); + }) + .on('p', function(evt, mods) { + controller.enter(iD.modes.AddPlace); + }) + .on('r', function(evt, mods) { + controller.enter(iD.modes.AddRoad); + }) + .on('z', function(evt, mods) { + if (mods === '⇧⌘') history.redo(); + if (mods === '⌘') history.undo(); + }); + d3.select(document).call(keybinding); var hash = iD.Hash().map(map); if (!hash.hadHash) { diff --git a/js/lib/d3.keybinding.js b/js/lib/d3.keybinding.js new file mode 100644 index 000000000..f5958a2c7 --- /dev/null +++ b/js/lib/d3.keybinding.js @@ -0,0 +1,137 @@ +d3.keybinding = function() { + var _keys = { + // MOD aka toggleable keys + mods: { + // Shift key, ⇧ + '⇧': 16, + // CTRL key, on Mac: ⌃ + '⌃': 17, + // ALT key, on Mac: ⌥ (Alt) + '⌥': 18, + // META, on Mac: ⌘ (CMD), on Windows (Win), on Linux (Super) + '⌘': 91 + }, + + // Normal keys + keys: { + // Backspace key, on Mac: ⌫ (Backspace) + '⌫': 8, backspace: 8, + // Tab Key, on Mac: ⇥ (Tab), on Windows ⇥⇥ + '⇥': 9, '⇆': 9, tab: 9, + // Return key, ↩ + '↩': 13, 'return': 13, enter: 13, '⌅': 13, + // Pause/Break key + 'pause': 19, 'pause-break': 19, + // Caps Lock key, ⇪ + '⇪': 20, caps: 20, 'caps-lock': 20, + // Escape key, on Mac: ⎋, on Windows: Esc + '⎋': 27, escape: 27, esc: 27, + // Space key + space: 32, + // Page-Up key, or pgup, on Mac: ↖ + '↖': 33, pgup: 33, 'page-up': 33, + // Page-Down key, or pgdown, on Mac: ↘ + '↘': 34, pgdown: 34, 'page-down': 34, + // END key, on Mac: ⇟ + '⇟': 35, end: 35, + // HOME key, on Mac: ⇞ + '⇞': 36, home: 36, + // Insert key, or ins + ins: 45, insert: 45, + // Delete key, on Mac: ⌫ (Delete) + del: 46, 'delete': 46, + + // Left Arrow Key, or ← + '←': 37, left: 37, 'arrow-left': 37, + // Up Arrow Key, or ↑ + '↑': 38, up: 38, 'arrow-up': 38, + // Right Arrow Key, or → + '→': 39, right: 39, 'arrow-right': 39, + // Up Arrow Key, or ↓ + '↓': 40, down: 40, 'arrow-down': 40, + + // odities, printing characters that come out wrong: + // Num-Multiply, or * + '*': 106, star: 106, asterisk: 106, multiply: 106, + // Num-Plus or + + '+': 107, 'plus': 107, + // Num-Subtract, or - + '-': 109, subtract: 109, + // Semicolon + ';': 186, semicolon:186, + // = or equals + '=': 187, 'equals': 187, + // Comma, or , + ',': 188, comma: 188, + //'-': 189, //??? + // Period, or ., or full-stop + '.': 190, period: 190, 'full-stop': 190, + // Slash, or /, or forward-slash + '/': 191, slash: 191, 'forward-slash': 191, + // Tick, or `, or back-quote + '`': 192, tick: 192, 'back-quote': 192, + // Open bracket, or [ + '[': 219, 'open-bracket': 219, + // Back slash, or \ + '\\': 220, 'back-slash': 220, + // Close backet, or ] + ']': 221, 'close-bracket': 221, + // Apostraphe, or Quote, or ' + '\'': 222, quote: 222, apostraphe: 222 + } + }; + // To minimise code bloat, add all of the NUMPAD 0-9 keys in a loop + var i = 95, n = 0; + while (++i < 106) _keys.keys['num-' + n] = i; ++n; + // To minimise code bloat, add all of the top row 0-9 keys in a loop + i = 47, n = 0; + while (++i < 58) _keys.keys[n] = i; ++n; + // To minimise code bloat, add all of the F1-F25 keys in a loop + i = 111, n = 1; + while (++i < 136) _keys.keys['f' + n] = i; ++n; + // To minimise code bloat, add all of the letters of the alphabet in a loop + i = 64; + while(++i < 91) _keys.keys[String.fromCharCode(i).toLowerCase()] = i; + + var pairs = d3.entries(_keys.keys), + key_shortcuts = pairs.map(function(d) { + return d.key; + }); + var mods = d3.entries(_keys.mods), + mod_shortcuts = mods.map(function(d) { + return d.key; + }); + + var event = d3.dispatch.apply(d3, key_shortcuts), + modifiers = []; + + function keydown() { + var tagName = d3.select(d3.event.target).node().tagName; + if (tagName == 'INPUT' || tagName == 'SELECT' || tagName == 'TEXTAREA') { + return; + } + + modifiers = modifiers.concat(mods.filter(function(d) { + return d.value === d3.event.keyCode; + }).map(function(d) { return d.key; })); + + pairs.filter(function(d) { + return d.value === d3.event.keyCode; + }).forEach(function(d) { + event[d.key](d3.event, modifiers.slice().sort().join('')); + }); + } + + function keyup() { + modifiers = []; + } + + function keys(selection) { + d3.select(window).on('focus', keyup); + selection + .on('keyup', keyup) + .on('keydown', keydown); + } + + return d3.rebind(keys, event, 'on'); +};