From ea9643e08b900dd5e480c968b9b9e50760b65730 Mon Sep 17 00:00:00 2001 From: Bryan Housel Date: Mon, 8 Jan 2018 15:47:29 -0500 Subject: [PATCH] Allow Alt/option key to disable geometry check and nope cursor (re: #4646) --- css/20_map.css | 4 +-- modules/behavior/draw_way.js | 68 +++++++++++++++++++++++++++++++++--- modules/modes/drag_node.js | 60 +++++++++++++++++++++++++++---- 3 files changed, 120 insertions(+), 12 deletions(-) diff --git a/css/20_map.css b/css/20_map.css index d162eef12..f5d8e168a 100644 --- a/css/20_map.css +++ b/css/20_map.css @@ -34,8 +34,8 @@ } /* `.target-nope` objects are explicitly forbidden to join to */ -.node.target.target-nope, -.way.target.target-nope { +.surface:not(.nope-disabled) .node.target.target-nope, +.surface:not(.nope-disabled) .way.target.target-nope { cursor: not-allowed; } diff --git a/modules/behavior/draw_way.js b/modules/behavior/draw_way.js index ee2cde230..009a2a5ad 100644 --- a/modules/behavior/draw_way.js +++ b/modules/behavior/draw_way.js @@ -1,5 +1,12 @@ import { t } from '../util/locale'; +import { + event as d3_event, + select as d3_select +} from 'd3-selection'; + +import { d3keybinding as d3_keybinding } from '../lib/d3.keybinding.js'; + import { actionAddMidpoint, actionMoveNode, @@ -34,11 +41,40 @@ export function behaviorDrawWay(context, wayId, index, mode, startGraph) { _tempEdits++; + + function keydown() { + if (d3_event.keyCode === d3_keybinding.modifierCodes.alt) { + if (context.surface().classed('nope')) { + context.surface() + .classed('nope-suppressed', true); + } + context.surface() + .classed('nope', false) + .classed('nope-disabled', true); + } + } + + + function keyup() { + if (d3_event.keyCode === d3_keybinding.modifierCodes.alt) { + if (context.surface().classed('nope-suppressed')) { + context.surface() + .classed('nope', true); + } + context.surface() + .classed('nope-suppressed', false) + .classed('nope-disabled', false); + } + } + + // related code // - `mode/drag_node.js` `doMode()` // - `behavior/draw.js` `click()` // - `behavior/draw_way.js` `move()` function move(datum) { + context.surface().classed('nope-disabled', d3_event.altKey); + var nodeLoc = datum && datum.properties && datum.properties.entity && datum.properties.entity.loc; var nodeGroups = datum && datum.properties && datum.properties.nodes; var loc = context.map().mouseCoordinates(); @@ -68,9 +104,18 @@ export function behaviorDrawWay(context, wayId, index, mode, startGraph) { // If so, class the surface with a nope cursor. // `skipLast` - include closing segment in the check, see #4655 function checkGeometry(skipLast) { - var doBlock = isInvalidGeometry(end, context.graph(), skipLast); - context.surface() - .classed('nope', doBlock); + var nopeDisabled = context.surface().classed('nope-disabled'); + var isInvalid = isInvalidGeometry(end, context.graph(), skipLast); + + if (nopeDisabled) { + context.surface() + .classed('nope', false) + .classed('nope-suppressed', isInvalid); + } else { + context.surface() + .classed('nope', isInvalid) + .classed('nope-suppressed', false); + } } @@ -122,6 +167,10 @@ export function behaviorDrawWay(context, wayId, index, mode, startGraph) { .on('cancel', drawWay.cancel) .on('finish', drawWay.finish); + d3_select(window) + .on('keydown.drawWay', keydown) + .on('keyup.drawWay', keyup); + context.map() .dblclickEnable(false) .on('drawn.draw', setActiveElements); @@ -153,6 +202,15 @@ export function behaviorDrawWay(context, wayId, index, mode, startGraph) { .selectAll('.active') .classed('active', false); + surface + .classed('nope', false) + .classed('nope-suppressed', false) + .classed('nope-disabled', false); + + d3_select(window) + .on('keydown.hover', null) + .on('keyup.hover', null); + context.history() .on('undone.draw', null); }; @@ -274,7 +332,9 @@ export function behaviorDrawWay(context, wayId, index, mode, startGraph) { }, 1000); context.surface() - .classed('nope', false); + .classed('nope', false) + .classed('nope-disabled', false) + .classed('nope-suppressed', false); context.enter(modeBrowse(context)); }; diff --git a/modules/modes/drag_node.js b/modules/modes/drag_node.js index faf603f66..201515ed2 100644 --- a/modules/modes/drag_node.js +++ b/modules/modes/drag_node.js @@ -5,6 +5,8 @@ import { select as d3_select } from 'd3-selection'; +import { d3keybinding as d3_keybinding } from '../lib/d3.keybinding.js'; + import { t } from '../util/locale'; import { @@ -83,6 +85,32 @@ export function modeDragNode(context) { } + function keydown() { + if (d3_event.keyCode === d3_keybinding.modifierCodes.alt) { + if (context.surface().classed('nope')) { + context.surface() + .classed('nope-suppressed', true); + } + context.surface() + .classed('nope', false) + .classed('nope-disabled', true); + } + } + + + function keyup() { + if (d3_event.keyCode === d3_keybinding.modifierCodes.alt) { + if (context.surface().classed('nope-suppressed')) { + context.surface() + .classed('nope', true); + } + context.surface() + .classed('nope-suppressed', false) + .classed('nope-disabled', false); + } + } + + function start(entity) { _wasMidpoint = entity.type === 'midpoint'; var hasHidden = context.features().hasHiddenConnections(entity, context.graph()); @@ -173,15 +201,23 @@ export function modeDragNode(context) { // check if this movement causes the geometry to break - var doBlock = invalidGeometry(entity, context.graph()); - context.surface() - .classed('nope', doBlock); + var nopeDisabled = context.surface().classed('nope-disabled'); + var isInvalid = isInvalidGeometry(entity, context.graph()); + if (nopeDisabled) { + context.surface() + .classed('nope', false) + .classed('nope-suppressed', isInvalid); + } else { + context.surface() + .classed('nope', isInvalid) + .classed('nope-suppressed', false); + } _lastLoc = loc; } - function invalidGeometry(entity, graph) { + function isInvalidGeometry(entity, graph) { var parents = graph.parentWays(entity); var i, j, k; @@ -223,7 +259,7 @@ export function modeDragNode(context) { // If we still haven't tested this node's parent way for self-intersections. // (because it's not a member of a multipolygon), test it now. - if (activeIndex !== null && parent.isClosed()) { + if (activeIndex === null && parent.isClosed()) { nodes = parent.nodes.map(function(nodeID) { return graph.entity(nodeID); }); if (nodes.length && geoHasSelfIntersections(nodes, entity.id)) { return true; @@ -238,8 +274,10 @@ export function modeDragNode(context) { function move(entity) { if (_isCancelled) return; - d3_event.sourceEvent.stopPropagation(); + + context.surface().classed('nope-disabled', d3_event.sourceEvent.altKey); + _lastLoc = context.projection.invert(d3_event.point); doMove(entity); @@ -337,6 +375,10 @@ export function modeDragNode(context) { context.install(hover); context.install(edit); + d3_select(window) + .on('keydown.drawWay', keydown) + .on('keyup.drawWay', keyup); + context.history() .on('undone.drag-node', cancel); }; @@ -347,6 +389,10 @@ export function modeDragNode(context) { context.uninstall(hover); context.uninstall(edit); + d3_select(window) + .on('keydown.hover', null) + .on('keyup.hover', null); + context.history() .on('undone.drag-node', null); @@ -357,6 +403,8 @@ export function modeDragNode(context) { context.surface() .classed('nope', false) + .classed('nope-suppressed', false) + .classed('nope-disabled', false) .selectAll('.active') .classed('active', false);