Move uiTooltipHtml functionality into uiTooltip

Refactor uiEditMenu to use standard UI patterns instead of SVG
This commit is contained in:
Quincy Morgan
2020-03-25 12:22:10 -07:00
parent f1d1bfa402
commit 9731b93cec
22 changed files with 156 additions and 221 deletions
+13 -21
View File
@@ -5269,30 +5269,22 @@ li.hide + li.version .badge .tooltip .popover-arrow {
/* Contextual Edit Menu
------------------------------------------------------- */
.edit-menu-tooltip {
.edit-menu {
position: absolute;
display: flex;
flex-direction: column;
background: #fff;
border-radius: 4px;
/* padding set in code */
}
.edit-menu .tooltip {
width: 200px;
}
.edit-menu-background {
fill: #eee;
}
.edit-menu-item rect {
fill: #eee;
cursor: default;
}
.edit-menu-item rect:active,
.edit-menu-item rect:hover {
fill: #ccc;
}
.edit-menu-item.disabled rect {
cursor: not-allowed;
}
.edit-menu-item.disabled rect:hover {
cursor: not-allowed;
fill: #eee;
.edit-menu-item {
border-radius: 0;
/* width and height set in code */
}
.edit-menu-item use {
+3 -3
View File
@@ -147,7 +147,7 @@ export function modeSelect(context, selectedIDs) {
function closeMenu() {
if (editMenu) {
context.surface().call(editMenu.close);
context.map().supersurface.call(editMenu.close);
}
}
@@ -178,13 +178,13 @@ export function modeSelect(context, selectedIDs) {
// disable menu if in wide selection, for example
if (!context.map().editableDataEnabled()) return;
context.surface().call(editMenu);
context.map().supersurface.call(editMenu);
}
}
function toggleMenu() {
if (context.surface().select('.edit-menu').empty()) {
if (context.map().supersurface.select('.edit-menu').empty()) {
positionMenu();
showMenu();
} else {
+2 -2
View File
@@ -157,7 +157,7 @@ export function rendererMap(context) {
.call(_zoomerPanner.transform, projection.transform())
.on('dblclick.zoom', null); // override d3-zoom dblclick handling
supersurface = selection.append('div')
map.supersurface = supersurface = selection.append('div')
.attr('class', 'supersurface')
.call(utilSetTransform, 0, 0);
@@ -602,7 +602,7 @@ export function rendererMap(context) {
function resetTransform() {
if (!_isTransformed) return false;
surface.selectAll('.edit-menu').interrupt().remove();
supersurface.selectAll('.edit-menu').interrupt().remove();
utilSetTransform(supersurface, 0, 0);
_isTransformed = false;
if (context.inIntro()) {
+29 -97
View File
@@ -1,28 +1,27 @@
import { event as d3_event, select as d3_select } from 'd3-selection';
import { geoVecAdd, geoVecFloor } from '../geo';
import { geoVecAdd } from '../geo';
import { textDirection } from '../util/locale';
import { uiTooltipHtml } from './tooltipHtml';
import { uiTooltip } from './tooltip';
import { svgIcon } from '../svg/icon';
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
// hardcode these values to make menu positioning easier
var buttonWidth = 44;
var buttonHeight = (2 * p + h);
var buttonHeight = 34;
var menuWidth = buttonWidth;
var menuHeight = (2 * m) + operations.length * buttonHeight;
var verticalPadding = 4;
// offset the menu slightly from the target location
var menuSideMargin = 10;
var tooltipWidth = 200;
var tooltipHeight = 200; // a reasonable guess, real height depends on tooltip contents
var editMenu = function (selection) {
@@ -33,6 +32,8 @@ export function uiEditMenu(context, operations) {
var isRTL = textDirection === 'rtl';
var viewport = context.surfaceRect();
var menuHeight = verticalPadding * 2 + operations.length * buttonHeight;
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;
@@ -51,54 +52,39 @@ export function uiEditMenu(context, operations) {
var origin = geoVecAdd(center, offset);
menu = selection
.append('g')
.append('div')
.attr('class', 'edit-menu')
.attr('transform', 'translate(' + origin + ')')
.style('padding', verticalPadding + 'px 0')
.style('left', origin[0] + 'px')
.style('top', origin[1] + 'px')
.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')
.append('button')
.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)
.style('width', buttonWidth + 'px')
.style('height', buttonHeight + 'px')
.on('click', click)
.on('mousedown', mousedown)
.on('mouseover', mouseover)
.on('mouseout', mouseout);
.on('mousedown', mousedown);
buttonsEnter
.append('use')
.attr('class', 'operation-icon')
.attr('width', '20')
.attr('height', '20')
.attr('transform', function () { return 'translate(' + [2 * p, 5] + ')'; })
.attr('xlink:href', function (d) { return '#iD-operation-' + d.id; });
buttonsEnter.each(function(d) {
d3_select(this)
.call(svgIcon('#iD-operation-' + d.id, 'operation-icon'))
.call(uiTooltip()
.heading(d.title)
.title(d.tooltip())
.keys([d.keys[0]])
.placement('right')
);
});
// update
buttons = buttonsEnter
@@ -106,15 +92,6 @@ export function uiEditMenu(context, operations) {
.classed('disabled', function(d) { return d.disabled(); });
tooltip = context.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;
@@ -125,47 +102,6 @@ export function uiEditMenu(context, operations) {
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');
}
};
@@ -177,10 +113,6 @@ export function uiEditMenu(context, operations) {
.attr('opacity', 0)
.remove();
}
if (tooltip) {
tooltip.remove();
}
};
+1 -3
View File
@@ -1,7 +1,6 @@
import { event as d3_event } from 'd3-selection';
import { t } from '../util/locale';
import { uiTooltipHtml } from './tooltipHtml';
import { uiTooltip } from './tooltip';
@@ -22,9 +21,8 @@ export function uiFeatureInfo(context) {
if (hiddenList.length) {
var tooltipBehavior = uiTooltip()
.placement('top')
.html(true)
.title(function() {
return uiTooltipHtml(hiddenList.join('<br/>'));
return hiddenList.join('<br/>');
});
selection.append('a')
+2 -3
View File
@@ -7,7 +7,6 @@ import { modeBrowse } from '../modes/browse';
import { svgIcon } from '../svg/icon';
import { uiFlash } from './flash';
import { uiLoading } from './loading';
import { uiTooltipHtml } from './tooltipHtml';
export function uiGeolocate(context) {
var _geolocationOptions = {
@@ -89,8 +88,8 @@ export function uiGeolocate(context) {
.call(svgIcon('#iD-icon-geolocate', 'light'))
.call(uiTooltip()
.placement((textDirection === 'rtl') ? 'right' : 'left')
.html(true)
.title(uiTooltipHtml(t('geolocate.title'), t('geolocate.key')))
.title(t('geolocate.title'))
.keys([t('geolocate.key')])
);
context.keybinding().on(t('geolocate.key'), click);
-1
View File
@@ -56,7 +56,6 @@ export { uiSuccess } from './success';
export { uiTagReference } from './tag_reference';
export { uiToggle } from './toggle';
export { uiTooltip } from './tooltip';
export { uiTooltipHtml } from './tooltipHtml';
export { uiVersion } from './version';
export { uiViewOnOSM } from './view_on_osm';
export { uiViewOnKeepRight } from './view_on_keepRight';
+2 -3
View File
@@ -6,7 +6,6 @@ import {
import { svgIcon } from '../svg/icon';
import { textDirection } from '../util/locale';
import { uiTooltip } from './tooltip';
import { uiTooltipHtml } from './tooltipHtml';
export function uiPane(id, context) {
@@ -74,8 +73,8 @@ export function uiPane(id, context) {
if (!_paneTooltip) {
_paneTooltip = uiTooltip()
.placement((textDirection === 'rtl') ? 'right' : 'left')
.html(true)
.title(uiTooltipHtml(_description, _key));
.title(_description)
.keys([_key]);
}
selection
+2 -3
View File
@@ -4,7 +4,6 @@ import { svgIcon } from '../../svg/icon';
import { uiCmd } from '../cmd';
import { uiIntro } from '../intro/intro';
import { uiShortcuts } from '../shortcuts';
import { uiTooltipHtml } from '../tooltipHtml';
import { uiPane } from '../pane';
import { t, textDirection } from '../../util/locale';
@@ -365,8 +364,8 @@ export function uiPaneHelp(context) {
.append('li')
.attr('class', 'shortcuts')
.call(uiTooltip()
.html(true)
.title(uiTooltipHtml(t('shortcuts.tooltip'), '?'))
.title(t('shortcuts.tooltip'))
.keys(['?'])
.placement('top')
)
.append('a')
+6 -10
View File
@@ -12,7 +12,6 @@ import { uiCmd } from '../cmd';
import { uiSettingsCustomBackground } from '../settings/custom_background';
import { uiMapInMap } from '../map_in_map';
import { uiSection } from '../section';
import { uiTooltipHtml } from '../tooltipHtml';
export function uiSectionBackgroundList(context) {
@@ -56,8 +55,8 @@ export function uiSectionBackgroundList(context) {
.attr('class', 'minimap-toggle-item')
.append('label')
.call(uiTooltip()
.html(true)
.title(uiTooltipHtml(t('background.minimap.tooltip'), t('background.minimap.key')))
.title(t('background.minimap.tooltip'))
.keys([t('background.minimap.key')])
.placement('top')
);
@@ -79,8 +78,8 @@ export function uiSectionBackgroundList(context) {
.attr('class', 'background-panel-toggle-item')
.append('label')
.call(uiTooltip()
.html(true)
.title(uiTooltipHtml(t('background.panel.tooltip'), uiCmd('⌘⇧' + t('info_panels.background.key'))))
.title(t('background.panel.tooltip'))
.keys([uiCmd('⌘⇧' + t('info_panels.background.key'))])
.placement('top')
);
@@ -127,11 +126,8 @@ export function uiSectionBackgroundList(context) {
if (d.id === previousBackgroundID()) {
item.call(uiTooltip()
.placement(placement)
.html(true)
.title(function() {
var tip = '<div>' + t('background.switch') + '</div>';
return uiTooltipHtml(tip, uiCmd('⌘' + t('background.key')));
})
.title('<div>' + t('background.switch') + '</div>')
.keys([uiCmd('⌘' + t('background.key'))])
);
} else if (description || isOverflowing) {
item.call(uiTooltip()
+2 -3
View File
@@ -12,7 +12,6 @@ import { modeBrowse } from '../../modes/browse';
import { uiCmd } from '../cmd';
import { uiSection } from '../section';
import { uiSettingsCustomData } from '../settings/custom_data';
import { uiTooltipHtml } from '../tooltipHtml';
export function uiSectionDataLayers(context) {
@@ -95,8 +94,8 @@ export function uiSectionDataLayers(context) {
if (d.id === 'osm') {
d3_select(this)
.call(uiTooltip()
.html(true)
.title(uiTooltipHtml(t('map_data.layers.' + d.id + '.tooltip'), uiCmd('⌥' + t('area_fill.wireframe.key'))))
.title(t('map_data.layers.' + d.id + '.tooltip'))
.keys([uiCmd('⌥' + t('area_fill.wireframe.key'))])
.placement('bottom')
);
} else {
+1 -3
View File
@@ -1,7 +1,6 @@
import { t } from '../../util/locale';
import { uiTooltip } from '../tooltip';
import { uiSection } from '../section';
import { uiTooltipHtml } from '../tooltipHtml';
export function uiSectionMapFeatures(context) {
@@ -67,14 +66,13 @@ export function uiSectionMapFeatures(context) {
var enter = items.enter()
.append('li')
.call(uiTooltip()
.html(true)
.title(function(d) {
var tip = t(name + '.' + d + '.tooltip');
if (autoHiddenFeature(d)) {
var msg = showsLayer('osm') ? t('map_data.autohidden') : t('map_data.osmhidden');
tip += '<div>' + msg + '</div>';
}
return uiTooltipHtml(tip);
return tip;
})
.placement('top')
);
+4 -4
View File
@@ -5,7 +5,6 @@ import {
import { t } from '../../util/locale';
import { uiTooltip } from '../tooltip';
import { uiSection } from '../section';
import { uiTooltipHtml } from '../tooltipHtml';
export function uiSectionMapStyleOptions(context) {
@@ -48,12 +47,13 @@ export function uiSectionMapStyleOptions(context) {
var enter = items.enter()
.append('li')
.call(uiTooltip()
.html(true)
.title(function(d) {
var tip = t(name + '.' + d + '.tooltip');
return t(name + '.' + d + '.tooltip');
})
.keys(function(d) {
var key = (d === 'wireframe' ? t('area_fill.wireframe.key') : null);
if (d === 'highlight_edits') key = t('map_data.highlight_edits.key');
return uiTooltipHtml(tip, key);
return [key];
})
.placement('top')
);
+2 -3
View File
@@ -12,7 +12,6 @@ import {
import { t } from '../../util/locale';
import { svgIcon } from '../../svg';
import { uiTooltip } from '../tooltip';
import { uiTooltipHtml } from '../tooltipHtml';
export function uiToolOldDrawModes(context) {
@@ -128,8 +127,8 @@ export function uiToolOldDrawModes(context) {
})
.call(uiTooltip()
.placement('bottom')
.html(true)
.title(function(d) { return uiTooltipHtml(d.description, d.key); })
.title(function(d) { return d.description; })
.keys(function(d) { return [d.key]; })
.scrollContainer(context.container().select('.top-toolbar'))
);
+2 -3
View File
@@ -10,7 +10,6 @@ import {
import { t } from '../../util/locale';
import { svgIcon } from '../../svg';
import { uiTooltip } from '../tooltip';
import { uiTooltipHtml } from '../tooltipHtml';
export function uiToolNotes(context) {
@@ -105,8 +104,8 @@ export function uiToolNotes(context) {
})
.call(uiTooltip()
.placement('bottom')
.html(true)
.title(function(d) { return uiTooltipHtml(d.description, d.key); })
.title(function(d) { return d.description; })
.keys(function(d) { return [d.key]; })
.scrollContainer(context.container().select('.top-toolbar'))
);
+4 -6
View File
@@ -5,7 +5,6 @@ import { t } from '../../util/locale';
import { modeSave } from '../../modes';
import { svgIcon } from '../../svg';
import { uiCmd } from '../cmd';
import { uiTooltipHtml } from '../tooltipHtml';
import { uiTooltip } from '../tooltip';
@@ -59,9 +58,8 @@ export function uiToolSave(context) {
if (tooltipBehavior) {
tooltipBehavior
.title(uiTooltipHtml(
t(_numChanges > 0 ? 'save.help' : 'save.no_changes'), key)
);
.title(t(_numChanges > 0 ? 'save.help' : 'save.no_changes'))
.keys([key]);
}
if (button) {
@@ -78,8 +76,8 @@ export function uiToolSave(context) {
tool.render = function(selection) {
tooltipBehavior = uiTooltip()
.placement('bottom')
.html(true)
.title(uiTooltipHtml(t('save.no_changes'), key))
.title(t('save.no_changes'))
.keys([key])
.scrollContainer(context.container().select('.top-toolbar'));
button = selection
+2 -3
View File
@@ -1,6 +1,5 @@
import { t, textDirection } from '../../util/locale';
import { svgIcon } from '../../svg';
import { uiTooltipHtml } from '../tooltipHtml';
import { uiTooltip } from '../tooltip';
export function uiToolSidebarToggle(context) {
@@ -19,8 +18,8 @@ export function uiToolSidebarToggle(context) {
})
.call(uiTooltip()
.placement('bottom')
.html(true)
.title(uiTooltipHtml(t('sidebar.tooltip'), t('sidebar.key')))
.title(t('sidebar.tooltip'))
.keys([t('sidebar.key')])
.scrollContainer(context.container().select('.top-toolbar'))
)
.call(svgIcon('#iD-icon-sidebar-' + (textDirection === 'rtl' ? 'right' : 'left')));
+5 -4
View File
@@ -8,7 +8,6 @@ import {
import { t, textDirection } from '../../util/locale';
import { svgIcon } from '../../svg';
import { uiCmd } from '../cmd';
import { uiTooltipHtml } from '../tooltipHtml';
import { uiTooltip } from '../tooltip';
@@ -40,11 +39,13 @@ export function uiToolUndoRedo(context) {
tool.render = function(selection) {
var tooltipBehavior = uiTooltip()
.placement('bottom')
.html(true)
.title(function (d) {
return uiTooltipHtml(d.annotation() ?
return d.annotation() ?
t(d.id + '.tooltip', {action: d.annotation()}) :
t(d.id + '.nothing'), d.cmd);
t(d.id + '.nothing');
})
.keys(function(d) {
return [d.cmd];
})
.scrollContainer(context.container().select('.top-toolbar'));
+68 -17
View File
@@ -1,4 +1,5 @@
import { utilFunctor } from '../util/util';
import { t } from '../util/locale';
import { uiPopover } from './popover';
export function uiTooltip(klass) {
@@ -17,34 +18,84 @@ export function uiTooltip(klass) {
}
return title;
};
var _html = utilFunctor(false);
var _heading = utilFunctor(null);
var _keys = utilFunctor(null);
tooltip.title = function(val) {
if (arguments.length) {
_title = utilFunctor(val);
return tooltip;
} else {
return _title;
}
if (!arguments.length) return _title;
_title = utilFunctor(val);
return tooltip;
};
tooltip.heading = function(val) {
if (!arguments.length) return _heading;
_heading = utilFunctor(val);
return tooltip;
};
tooltip.html = function(val) {
if (arguments.length) {
_html = utilFunctor(val);
return tooltip;
} else {
return _html;
}
tooltip.keys = function(val) {
if (!arguments.length) return _keys;
_keys = utilFunctor(val);
return tooltip;
};
tooltip.content(function() {
var content = _title.apply(this, arguments);
var markup = _html.apply(this, arguments);
var heading = _heading.apply(this, arguments);
var text = _title.apply(this, arguments);
var keys = _keys.apply(this, arguments);
return function(selection) {
selection[markup ? 'html' : 'text'](content);
var headingSelect = selection
.selectAll('.tooltip-heading')
.data(heading ? [heading] :[]);
headingSelect.exit()
.remove();
headingSelect.enter()
.append('div')
.attr('class', 'tooltip-heading')
.html(heading);
var textSelect = selection
.selectAll('.tooltip-text')
.data(text ? [text] :[]);
textSelect.exit()
.remove();
textSelect.enter()
.append('div')
.attr('class', 'tooltip-text')
.html(text);
var keyhintWrap = selection
.selectAll('.keyhint-wrap')
.data(keys && keys.length ? [0] : []);
keyhintWrap.exit()
.remove();
var keyhintWrapEnter = keyhintWrap.enter()
.append('div')
.attr('class', 'keyhint-wrap');
keyhintWrapEnter
.append('span')
.html(t('tooltip_keyhint'));
keyhintWrap = keyhintWrapEnter.merge(keyhintWrap);
keyhintWrap.selectAll('kbd.shortcut')
.data(keys && keys.length ? keys : [])
.enter()
.append('kbd')
.attr('class', 'shortcut')
.html(function(d) {
return d;
});
};
});
-23
View File
@@ -1,23 +0,0 @@
import { t } from '../util/locale';
export function uiTooltipHtml(text, keys, heading) {
var s = '';
if (heading) {
s += '<div class="tooltip-heading"><span>' + heading + '</span></div>';
}
if (text) {
s += '<div class="tooltip-text"><span>' + text + '</span></div>';
}
if (keys) {
if (!Array.isArray(keys)) keys = [keys];
s += '<div class="keyhint-wrap"><span>' + t('tooltip_keyhint') + '</span>';
keys.forEach(function(key) {
s += '<kbd class="shortcut">' + key + '</kbd>';
});
s += '</div>';
}
return s;
}
+4 -3
View File
@@ -6,7 +6,6 @@ import {
import { t, textDirection } from '../util/locale';
import { svgIcon } from '../svg/icon';
import { uiCmd } from './cmd';
import { uiTooltipHtml } from './tooltipHtml';
import { uiTooltip } from './tooltip';
@@ -59,9 +58,11 @@ export function uiZoom(context) {
})
.call(uiTooltip()
.placement((textDirection === 'rtl') ? 'right' : 'left')
.html(true)
.title(function(d) {
return uiTooltipHtml(d.title, d.key);
return d.title;
})
.keys(function(d) {
return [d.key];
})
);
+2 -3
View File
@@ -2,7 +2,6 @@ import { select as d3_select } from 'd3-selection';
import { t, textDirection } from '../util/locale';
import { uiTooltip } from './tooltip';
import { uiTooltipHtml } from './tooltipHtml';
import { svgIcon } from '../svg/icon';
export function uiZoomToSelection(context) {
@@ -34,8 +33,8 @@ export function uiZoomToSelection(context) {
.call(svgIcon('#iD-icon-framed-dot', 'light'))
.call(uiTooltip()
.placement((textDirection === 'rtl') ? 'right' : 'left')
.html(true)
.title(uiTooltipHtml(t('inspector.zoom_to.title'), t('inspector.zoom_to.key')))
.title(t('inspector.zoom_to.title'))
.keys([t('inspector.zoom_to.key')])
);
setEnabledState();