mirror of
https://github.com/FoggedLens/iD.git
synced 2026-02-13 09:12:52 +00:00
190 lines
6.0 KiB
JavaScript
190 lines
6.0 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', 'tooltip-inner edit-menu-tooltip');
|
|
|
|
|
|
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')
|
|
.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;
|
|
}
|