Files
iD/modules/ui/init.js
T
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

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;
}