Files
iD/modules/ui/edit_menu.js
Quincy Morgan 523a467836 2.x: Make toolbar horizontally scrollable when it overflows (re: #6755, re: 7545f67063b5e7007ef2d8367e3181e59c04a487)
Generalize tooltip into popover control
Use the same popover control for tooltip as the preset browser and tools list popovers
Smartly position the preset browser popover and menu bar tooltips to stay fully onscreen
Position most tooltips closer to their controls
Fix small gap that could appear between a tooltip and its arrow
Allow wider toolbar tooltips
2019-12-16 13:30:07 -05:00

195 lines
6.1 KiB
JavaScript

import { event as d3_event, select as d3_select } from 'd3-selection';
import { geoVecAdd, geoVecFloor } from '../geo';
import { textDirection } from '../util/locale';
import { uiTooltipHtml } from './tooltipHtml';
export function uiEditMenu(context, operations) {
var menu;
var center = [0, 0];
var offset = [0, 0];
var tooltip;
var p = 8; // top padding
var m = 4; // top margin
var h = 15; // height of icon
var vpBottomMargin = 45; // viewport bottom margin
var vpSideMargin = 35; // viewport side margin
var buttonWidth = 44;
var buttonHeight = (2 * p + h);
var menuWidth = buttonWidth;
var menuHeight = (2 * m) + operations.length * buttonHeight;
var menuSideMargin = 10;
var tooltipWidth = 200;
var tooltipHeight = 200; // a reasonable guess, real height depends on tooltip contents
var editMenu = function (selection) {
if (!operations.length) return;
selection.node().parentNode.focus();
var isRTL = textDirection === 'rtl';
var viewport = context.surfaceRect();
if (!isRTL && (center[0] + menuSideMargin + menuWidth) > (viewport.width - vpSideMargin)) {
// menu is going left-to-right and near right viewport edge, go left instead
isRTL = true;
} else if (isRTL && (center[0] - menuSideMargin - menuWidth) < vpSideMargin) {
// menu is going right-to-left and near left viewport edge, go right instead
isRTL = false;
}
offset[0] = (isRTL ? -1 * (menuSideMargin + menuWidth) : menuSideMargin);
if (center[1] + menuHeight > (viewport.height - vpBottomMargin)) {
// menu is near bottom viewport edge, shift upwards
offset[1] = -1 * (center[1] + menuHeight - viewport.height + vpBottomMargin);
}
var origin = geoVecAdd(center, offset);
menu = selection
.append('g')
.attr('class', 'edit-menu')
.attr('transform', 'translate(' + origin + ')')
.attr('opacity', 0);
menu
.transition()
.attr('opacity', 1);
menu
.append('rect')
.attr('class', 'edit-menu-background')
.attr('x', 4)
.attr('rx', 4)
.attr('ry', 4)
.attr('width', menuWidth)
.attr('height', menuHeight)
.attr('stroke-linecap', 'round');
var buttons = menu.selectAll('.edit-menu-item')
.data(operations);
// enter
var buttonsEnter = buttons.enter()
.append('g')
.attr('class', function (d) { return 'edit-menu-item edit-menu-item-' + d.id; })
.attr('transform', function(d, i) {
return 'translate(' + geoVecFloor([0, m + i * buttonHeight]).join(',') + ')';
});
buttonsEnter
.append('rect')
.attr('x', 4)
.attr('width', buttonWidth)
.attr('height', buttonHeight)
.on('click', click)
.on('mousedown', mousedown)
.on('mouseover', mouseover)
.on('mouseout', mouseout);
buttonsEnter
.append('use')
.attr('width', '20')
.attr('height', '20')
.attr('transform', function () { return 'translate(' + [2 * p, 5] + ')'; })
.attr('xlink:href', function (d) { return '#iD-operation-' + d.id; });
// update
buttons = buttonsEnter
.merge(buttons)
.classed('disabled', function(d) { return d.disabled(); });
tooltip = d3_select('#id-container')
.append('div')
.attr('class', 'popover tooltip edit-menu-tooltip');
tooltip
.append('div')
.attr('class', 'popover-inner');
function click(operation) {
d3_event.stopPropagation();
if (operation.disabled()) return;
operation();
editMenu.close();
}
function mousedown() {
d3_event.stopPropagation(); // https://github.com/openstreetmap/iD/issues/1869
}
function mouseover(d, i) {
var tipX, tipY;
if (!isRTL) {
tipX = viewport.left + origin[0] + menuSideMargin + menuWidth;
} else {
tipX = viewport.left + origin[0] - 4 - tooltipWidth;
}
if (tipX + tooltipWidth > viewport.right) {
// tip is going left-to-right and near right viewport edge, go left instead
tipX = viewport.left + origin[0] - 4 - tooltipWidth;
} else if (tipX < viewport.left) {
// tip is going right-to-left and near left viewport edge, go right instead
tipX = viewport.left + origin[0] + menuSideMargin + menuWidth;
}
tipY = viewport.top + origin[1] + (i * buttonHeight);
if (tipY + tooltipHeight > viewport.bottom) {
// tip is near bottom viewport edge, shift upwards
tipY -= tipY + tooltipHeight - viewport.bottom;
}
tooltip
.style('left', tipX + 'px')
.style('top', tipY + 'px')
.style('display', 'block')
.selectAll('.popover-inner')
.html(uiTooltipHtml(d.tooltip(), d.keys[0], d.title));
// update disabled again, just in case tooltip and disabled state disagree
// https://github.com/openstreetmap/iD/issues/6296#issuecomment-489259027
d3_select(this.parentNode)
.classed('disabled', d.disabled());
}
function mouseout() {
tooltip.style('display', 'none');
}
};
editMenu.close = function () {
if (menu) {
menu
.style('pointer-events', 'none')
.transition()
.attr('opacity', 0)
.remove();
}
if (tooltip) {
tooltip.remove();
}
};
editMenu.center = function(val) {
if (!arguments.length) return center;
center = val;
return editMenu;
};
return editMenu;
}