diff --git a/modules/ui/edit_menu.js b/modules/ui/edit_menu.js index c9861a353..5bc6aec0b 100644 --- a/modules/ui/edit_menu.js +++ b/modules/ui/edit_menu.js @@ -81,6 +81,8 @@ export function uiEditMenu(context) { .style('width', _buttonWidth + 'px') .style('height', _buttonHeight + 'px') .on('click', click) + // don't listen for `mouseup` because we only care about non-mouse pointer types + .on('pointerup', pointerup) .on('pointerdown mousedown', pointerdown); var tooltipSide = tooltipPosition(viewport, menuLeft); @@ -101,11 +103,30 @@ export function uiEditMenu(context) { .merge(buttons) .classed('disabled', function(d) { return d.disabled(); }); + + var lastPointerUpType; + // `pointerup` is always called before `click` + function pointerup() { + lastPointerUpType = d3_event.pointerType; + } + function click(operation) { d3_event.stopPropagation(); - if (operation.disabled()) return; - operation(); - editMenu.close(); + if (operation.disabled()) { + if (lastPointerUpType === 'touch' || + lastPointerUpType === 'pen') { + // there are no tooltips for touch interactions so flash feedback instead + context.ui().flash + .duration(4000) + .iconName('#iD-operation-' + operation.id) + .iconClass('operation disabled') + .text(operation.tooltip)(); + } + } else { + operation(); + editMenu.close(); + } + lastPointerUpType = null; } function pointerdown() { diff --git a/modules/ui/tools/undo_redo.js b/modules/ui/tools/undo_redo.js index 7eefe1c2f..e62b6144b 100644 --- a/modules/ui/tools/undo_redo.js +++ b/modules/ui/tools/undo_redo.js @@ -21,13 +21,23 @@ export function uiToolUndoRedo(context) { var commands = [{ id: 'undo', cmd: uiCmd('⌘Z'), - action: function() { if (editable()) context.undo(); }, - annotation: function() { return context.history().undoAnnotation(); } + action: function() { + context.undo(); + }, + annotation: function() { + return context.history().undoAnnotation(); + }, + icon: 'iD-icon-' + (localizer.textDirection() === 'rtl' ? 'redo' : 'undo') }, { id: 'redo', cmd: uiCmd('⌘⇧Z'), - action: function() { if (editable()) context.redo(); }, - annotation: function() { return context.history().redoAnnotation(); } + action: function() { + context.redo(); + }, + annotation: function() { + return context.history().redoAnnotation(); + }, + icon: 'iD-icon-' + (localizer.textDirection() === 'rtl' ? 'undo' : 'redo') }]; @@ -41,7 +51,7 @@ export function uiToolUndoRedo(context) { .placement('bottom') .title(function (d) { return d.annotation() ? - t(d.id + '.tooltip', {action: d.annotation()}) : + t(d.id + '.tooltip', { action: d.annotation() }) : t(d.id + '.nothing'); }) .keys(function(d) { @@ -49,30 +59,57 @@ export function uiToolUndoRedo(context) { }) .scrollContainer(context.container().select('.top-toolbar')); + var lastPointerUpType; + var buttons = selection.selectAll('button') .data(commands) .enter() .append('button') .attr('class', function(d) { return 'disabled ' + d.id + '-button bar-button'; }) - .on('click', function(d) { return d.action(); }) + .on('pointerup', function() { + // `pointerup` is always called before `click` + lastPointerUpType = d3_event.pointerType; + }) + .on('click', function(d) { + d3_event.preventDefault(); + + var annotation = d.annotation(); + + if (editable() && annotation) { + d.action(); + } + + if (lastPointerUpType === 'touch' || + lastPointerUpType === 'pen') { + // there are no tooltips for touch interactions so flash feedback instead + + var text = annotation ? + t(d.id + '.tooltip', { action: annotation }) : + t(d.id + '.nothing'); + context.ui().flash + .duration(2000) + .iconName('#' + d.icon) + .iconClass(annotation ? '' : 'disabled') + .text(text)(); + } + lastPointerUpType = null; + }) .call(tooltipBehavior); buttons.each(function(d) { - var iconName = d.id; - if (localizer.textDirection() === 'rtl') { - if (iconName === 'undo') { - iconName = 'redo'; - } else if (iconName === 'redo') { - iconName = 'undo'; - } - } d3_select(this) - .call(svgIcon('#iD-icon-' + iconName)); + .call(svgIcon('#' + d.icon)); }); context.keybinding() - .on(commands[0].cmd, function() { d3_event.preventDefault(); commands[0].action(); }) - .on(commands[1].cmd, function() { d3_event.preventDefault(); commands[1].action(); }); + .on(commands[0].cmd, function() { + d3_event.preventDefault(); + if (editable()) commands[0].action(); + }) + .on(commands[1].cmd, function() { + d3_event.preventDefault(); + if (editable()) commands[1].action(); + }); var debouncedUpdate = _debounce(update, 500, { leading: true, trailing: true }); @@ -92,7 +129,6 @@ export function uiToolUndoRedo(context) { function update() { buttons - .property('disabled', !editable()) .classed('disabled', function(d) { return !editable() || !d.annotation(); })