mirror of
https://github.com/FoggedLens/iD.git
synced 2026-04-28 22:56:31 +02:00
523a467836
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
481 lines
14 KiB
JavaScript
481 lines
14 KiB
JavaScript
import {
|
|
event as d3_event,
|
|
select as d3_select,
|
|
selectAll as d3_selectAll
|
|
} from 'd3-selection';
|
|
|
|
import { t, textDirection } from '../util/locale';
|
|
import { tooltip } from '../util/tooltip';
|
|
|
|
import { behaviorHash } from '../behavior';
|
|
import { modeBrowse } from '../modes/browse';
|
|
import { svgDefs, svgIcon } from '../svg';
|
|
import { utilGetDimensions } from '../util/dimensions';
|
|
|
|
import { uiAccount } from './account';
|
|
import { uiAttribution } from './attribution';
|
|
import { uiBackground } from './background';
|
|
import { uiContributors } from './contributors';
|
|
import { uiFeatureInfo } from './feature_info';
|
|
import { uiFullScreen } from './full_screen';
|
|
import { uiGeolocate } from './geolocate';
|
|
import { uiHelp } from './help';
|
|
import { uiInfo } from './info';
|
|
import { uiIntro } from './intro';
|
|
import { uiIssues } from './issues';
|
|
import { uiIssuesInfo } from './issues_info';
|
|
import { uiLoading } from './loading';
|
|
import { uiMapData } from './map_data';
|
|
import { uiMapInMap } from './map_in_map';
|
|
import { uiNotice } from './notice';
|
|
import { uiPhotoviewer } from './photoviewer';
|
|
import { uiRestore } from './restore';
|
|
import { uiScale } from './scale';
|
|
import { uiShortcuts } from './shortcuts';
|
|
import { uiSidebar } from './sidebar';
|
|
import { uiSpinner } from './spinner';
|
|
import { uiSplash } from './splash';
|
|
import { uiStatus } from './status';
|
|
import { uiTopToolbar } from './top_toolbar';
|
|
import { uiVersion } from './version';
|
|
import { uiZoom } from './zoom';
|
|
import { uiCmd } from './cmd';
|
|
|
|
|
|
export function uiInit(context) {
|
|
var _initCounter = 0;
|
|
var _initCallback;
|
|
var _needWidth = {};
|
|
|
|
|
|
function render(container) {
|
|
container
|
|
.attr('dir', textDirection);
|
|
|
|
// setup fullscreen keybindings (no button shown at this time)
|
|
container
|
|
.call(uiFullScreen(context));
|
|
|
|
var map = context.map();
|
|
map.redrawEnable(false); // don't draw until we've set zoom/lat/long
|
|
|
|
container
|
|
.append('svg')
|
|
.attr('id', 'defs')
|
|
.call(svgDefs(context));
|
|
|
|
container
|
|
.append('div')
|
|
.attr('id', 'sidebar')
|
|
.call(ui.sidebar);
|
|
|
|
var content = container
|
|
.append('div')
|
|
.attr('id', 'content')
|
|
.attr('class', 'active');
|
|
|
|
// Top toolbar
|
|
content
|
|
.append('div')
|
|
.attr('id', 'bar-wrap')
|
|
.append('div')
|
|
.attr('id', 'bar')
|
|
.attr('class', 'fillD')
|
|
.call(uiTopToolbar(context));
|
|
|
|
content
|
|
.append('div')
|
|
.attr('id', 'map')
|
|
.attr('dir', 'ltr')
|
|
.call(map);
|
|
|
|
|
|
// Map controls
|
|
var controls = content
|
|
.append('div')
|
|
.attr('class', 'map-controls');
|
|
|
|
controls
|
|
.append('div')
|
|
.attr('class', 'map-control zoombuttons')
|
|
.call(uiZoom(context));
|
|
|
|
controls
|
|
.append('div')
|
|
.attr('class', 'map-control geolocate-control')
|
|
.call(uiGeolocate(context));
|
|
|
|
var background = uiBackground(context);
|
|
controls
|
|
.append('div')
|
|
.attr('class', 'map-control background-control')
|
|
.call(background.renderToggleButton);
|
|
|
|
var mapData = uiMapData(context);
|
|
controls
|
|
.append('div')
|
|
.attr('class', 'map-control map-data-control')
|
|
.call(mapData.renderToggleButton);
|
|
|
|
var issues = uiIssues(context);
|
|
controls
|
|
.append('div')
|
|
.attr('class', 'map-control map-issues-control')
|
|
.call(issues.renderToggleButton);
|
|
|
|
var help = uiHelp(context);
|
|
controls
|
|
.append('div')
|
|
.attr('class', 'map-control help-control')
|
|
.call(help.renderToggleButton);
|
|
|
|
content
|
|
.append('div')
|
|
.attr('class', 'spinner')
|
|
.call(uiSpinner(context));
|
|
|
|
// Add attribution and footer
|
|
var about = content
|
|
.append('div')
|
|
.attr('id', 'about');
|
|
|
|
about
|
|
.append('div')
|
|
.attr('id', 'attrib')
|
|
.attr('dir', 'ltr')
|
|
.call(uiAttribution(context));
|
|
|
|
about
|
|
.append('div')
|
|
.attr('class', 'api-status')
|
|
.call(uiStatus(context));
|
|
|
|
|
|
var footer = about
|
|
.append('div')
|
|
.attr('id', 'footer')
|
|
.attr('class', 'fillD');
|
|
|
|
footer
|
|
.append('div')
|
|
.attr('id', 'flash-wrap')
|
|
.attr('class', 'footer-hide');
|
|
|
|
var footerWrap = footer
|
|
.append('div')
|
|
.attr('id', 'footer-wrap')
|
|
.attr('class', 'footer-show');
|
|
|
|
footerWrap
|
|
.append('div')
|
|
.attr('id', 'scale-block')
|
|
.call(uiScale(context));
|
|
|
|
var aboutList = footerWrap
|
|
.append('div')
|
|
.attr('id', 'info-block')
|
|
.append('ul')
|
|
.attr('id', 'about-list');
|
|
|
|
if (!context.embed()) {
|
|
aboutList
|
|
.call(uiAccount(context));
|
|
}
|
|
|
|
aboutList
|
|
.append('li')
|
|
.attr('class', 'version')
|
|
.call(uiVersion(context));
|
|
|
|
var issueLinks = aboutList
|
|
.append('li');
|
|
|
|
issueLinks
|
|
.append('a')
|
|
.attr('target', '_blank')
|
|
.attr('href', 'https://github.com/openstreetmap/iD/issues')
|
|
.call(svgIcon('#iD-icon-bug', 'light'))
|
|
.call(tooltip().title(t('report_a_bug')).placement('top'));
|
|
|
|
issueLinks
|
|
.append('a')
|
|
.attr('target', '_blank')
|
|
.attr('href', 'https://github.com/openstreetmap/iD/blob/master/CONTRIBUTING.md#translating')
|
|
.call(svgIcon('#iD-icon-translate', 'light'))
|
|
.call(tooltip().title(t('help_translate')).placement('top'));
|
|
|
|
aboutList
|
|
.append('li')
|
|
.attr('class', 'feature-warning')
|
|
.attr('tabindex', -1)
|
|
.call(uiFeatureInfo(context));
|
|
|
|
aboutList
|
|
.append('li')
|
|
.attr('class', 'issues-info')
|
|
.attr('tabindex', -1)
|
|
.call(uiIssuesInfo(context));
|
|
|
|
aboutList
|
|
.append('li')
|
|
.attr('class', 'user-list')
|
|
.attr('tabindex', -1)
|
|
.call(uiContributors(context));
|
|
|
|
|
|
// Setup map dimensions and move map to initial center/zoom.
|
|
// This should happen after #content and toolbars exist.
|
|
ui.onResize();
|
|
map.redrawEnable(true);
|
|
|
|
ui.hash = behaviorHash(context);
|
|
ui.hash();
|
|
if (!ui.hash.hadHash) {
|
|
map.centerZoom([0, 0], 2);
|
|
}
|
|
|
|
|
|
var overMap = content
|
|
.append('div')
|
|
.attr('class', 'over-map');
|
|
|
|
// Add panes
|
|
// This should happen after map is initialized, as some require surface()
|
|
var panes = overMap
|
|
.append('div')
|
|
.attr('class', 'map-panes');
|
|
|
|
panes
|
|
.call(background.renderPane)
|
|
.call(mapData.renderPane)
|
|
.call(issues.renderPane)
|
|
.call(help.renderPane);
|
|
|
|
ui.info = uiInfo(context);
|
|
|
|
// Add absolutely-positioned elements that sit on top of the map
|
|
// This should happen after the map is ready (center/zoom)
|
|
overMap
|
|
.call(uiMapInMap(context))
|
|
.call(ui.info)
|
|
.call(uiNotice(context));
|
|
|
|
|
|
overMap
|
|
.append('div')
|
|
.attr('id', 'photoviewer')
|
|
.classed('al', true) // 'al'=left, 'ar'=right
|
|
.classed('hide', true)
|
|
.call(ui.photoviewer);
|
|
|
|
|
|
// Bind events
|
|
window.onbeforeunload = function() {
|
|
return context.save();
|
|
};
|
|
window.onunload = function() {
|
|
context.history().unlock();
|
|
};
|
|
|
|
d3_select(window)
|
|
.on('gesturestart.editor', eventCancel)
|
|
.on('gesturechange.editor', eventCancel)
|
|
.on('gestureend.editor', eventCancel)
|
|
.on('resize.editor', ui.onResize);
|
|
|
|
|
|
var panPixels = 80;
|
|
context.keybinding()
|
|
.on('⌫', function() { d3_event.preventDefault(); })
|
|
.on([t('sidebar.key'), '`', '²', '@'], ui.sidebar.toggle) // #5663, #6864 - common QWERTY, AZERTY
|
|
.on('←', pan([panPixels, 0]))
|
|
.on('↑', pan([0, panPixels]))
|
|
.on('→', pan([-panPixels, 0]))
|
|
.on('↓', pan([0, -panPixels]))
|
|
.on(['⇧←', uiCmd('⌘←')], pan([map.dimensions()[0], 0]))
|
|
.on(['⇧↑', uiCmd('⌘↑')], pan([0, map.dimensions()[1]]))
|
|
.on(['⇧→', uiCmd('⌘→')], pan([-map.dimensions()[0], 0]))
|
|
.on(['⇧↓', uiCmd('⌘↓')], pan([0, -map.dimensions()[1]]));
|
|
|
|
context.enter(modeBrowse(context));
|
|
|
|
if (!_initCounter++) {
|
|
if (!ui.hash.startWalkthrough) {
|
|
context.container()
|
|
.call(uiSplash(context))
|
|
.call(uiRestore(context));
|
|
}
|
|
|
|
context.container()
|
|
.call(uiShortcuts(context));
|
|
}
|
|
|
|
var osm = context.connection();
|
|
var auth = uiLoading(context).message(t('loading_auth')).blocking(true);
|
|
|
|
if (osm && auth) {
|
|
osm
|
|
.on('authLoading.ui', function() {
|
|
context.container()
|
|
.call(auth);
|
|
})
|
|
.on('authDone.ui', function() {
|
|
auth.close();
|
|
});
|
|
}
|
|
|
|
_initCounter++;
|
|
|
|
if (ui.hash.startWalkthrough) {
|
|
ui.hash.startWalkthrough = false;
|
|
context.container().call(uiIntro(context));
|
|
}
|
|
|
|
|
|
function pan(d) {
|
|
return function() {
|
|
if (d3_select('.combobox').size()) return;
|
|
d3_event.preventDefault();
|
|
context.pan(d, 100);
|
|
};
|
|
}
|
|
|
|
function eventCancel() {
|
|
d3_event.preventDefault();
|
|
}
|
|
}
|
|
|
|
|
|
function ui(node, callback) {
|
|
_initCallback = callback;
|
|
var container = d3_select(node);
|
|
context.container(container);
|
|
context.loadLocale(function(err) {
|
|
if (!err) {
|
|
render(container);
|
|
}
|
|
if (callback) {
|
|
callback(err);
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
ui.restart = function(arg) {
|
|
context.keybinding().clear();
|
|
context.locale(arg);
|
|
context.loadLocale(function(err) {
|
|
if (!err) {
|
|
context.container().selectAll('*').remove();
|
|
render(context.container());
|
|
if (_initCallback) _initCallback();
|
|
}
|
|
});
|
|
};
|
|
|
|
ui.sidebar = uiSidebar(context);
|
|
|
|
ui.photoviewer = uiPhotoviewer(context);
|
|
|
|
ui.onResize = function(withPan) {
|
|
var map = context.map();
|
|
|
|
// Recalc dimensions of map and sidebar.. (`true` = force recalc)
|
|
// This will call `getBoundingClientRect` and trigger reflow,
|
|
// but the values will be cached for later use.
|
|
var mapDimensions = utilGetDimensions(d3_select('#content'), true);
|
|
utilGetDimensions(d3_select('#sidebar'), true);
|
|
|
|
if (withPan !== undefined) {
|
|
map.redrawEnable(false);
|
|
map.pan(withPan);
|
|
map.redrawEnable(true);
|
|
}
|
|
map.dimensions(mapDimensions);
|
|
|
|
ui.photoviewer.onMapResize();
|
|
|
|
// check if header or footer have overflowed
|
|
ui.checkOverflow('#bar');
|
|
ui.checkOverflow('#footer');
|
|
|
|
// Use outdated code so it works on Explorer
|
|
var resizeWindowEvent = document.createEvent('Event');
|
|
|
|
resizeWindowEvent.initEvent('resizeWindow', true, true);
|
|
|
|
document.dispatchEvent(resizeWindowEvent);
|
|
};
|
|
|
|
|
|
// Call checkOverflow when resizing or whenever the contents change.
|
|
ui.checkOverflow = function(selector, reset) {
|
|
if (reset) {
|
|
delete _needWidth[selector];
|
|
}
|
|
|
|
var element = d3_select(selector);
|
|
var scrollWidth = element.property('scrollWidth');
|
|
var clientWidth = element.property('clientWidth');
|
|
var needed = _needWidth[selector] || scrollWidth;
|
|
|
|
if (scrollWidth > clientWidth) { // overflow happening
|
|
element.classed('narrow', true);
|
|
if (!_needWidth[selector]) {
|
|
_needWidth[selector] = scrollWidth;
|
|
}
|
|
|
|
} else if (scrollWidth >= needed) {
|
|
element.classed('narrow', false);
|
|
}
|
|
};
|
|
|
|
ui.togglePanes = function(showPane) {
|
|
var shownPanes = d3_selectAll('.map-pane.shown');
|
|
|
|
var side = textDirection === 'ltr' ? 'right' : 'left';
|
|
|
|
shownPanes
|
|
.classed('shown', false);
|
|
|
|
d3_selectAll('.map-control button')
|
|
.classed('active', false);
|
|
|
|
if (showPane) {
|
|
shownPanes
|
|
.style('display', 'none')
|
|
.style(side, '-500px');
|
|
|
|
d3_selectAll('.' + showPane.attr('pane') + '-control button')
|
|
.classed('active', true);
|
|
|
|
showPane
|
|
.classed('shown', true)
|
|
.style('display', 'block');
|
|
if (shownPanes.empty()) {
|
|
showPane
|
|
.style('display', 'block')
|
|
.style(side, '-500px')
|
|
.transition()
|
|
.duration(200)
|
|
.style(side, '0px');
|
|
} else {
|
|
showPane
|
|
.style(side, '0px');
|
|
}
|
|
} else {
|
|
shownPanes
|
|
.style('display', 'block')
|
|
.style(side, '0px')
|
|
.transition()
|
|
.duration(200)
|
|
.style(side, '-500px')
|
|
.on('end', function() {
|
|
d3_select(this).style('display', 'none');
|
|
});
|
|
}
|
|
};
|
|
|
|
return ui;
|
|
}
|