mirror of
https://github.com/FoggedLens/iD.git
synced 2026-02-14 09:42:56 +00:00
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
195 lines
6.1 KiB
JavaScript
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;
|
|
}
|