import _debounce from 'lodash-es/debounce'; import { descending as d3_descending, ascending as d3_ascending } from 'd3-array'; import { event as d3_event, select as d3_select } from 'd3-selection'; import { d3keybinding as d3_keybinding } from '../lib/d3.keybinding.js'; import { t, textDirection } from '../util/locale'; import { svgIcon } from '../svg'; import { uiBackgroundOffset } from './background_offset'; import { uiCmd } from './cmd'; import { uiDisclosure } from './disclosure'; import { uiMapInMap } from './map_in_map'; import { uiTooltipHtml } from './tooltipHtml'; import { utilDetect } from '../util/detect'; import { utilSetTransform, utilCallWhenIdle } from '../util'; import { tooltip } from '../util/tooltip'; export function uiBackground(context) { var key = t('background.key'); var detected = utilDetect(); var opacities = [1, 0.75, 0.5, 0.25]; var _opacityDefault = (context.storage('background-opacity') !== null) ? (+context.storage('background-opacity')) : 1.0; var _customSource = context.background().findSource('custom'); var _previousBackground; var _shown = false; var _backgroundList = d3_select(null); var _overlayList = d3_select(null); var _displayOptions = d3_select(null); var _offsetContainer = d3_select(null); var backgroundOffset = uiBackgroundOffset(context); // Can be 0 from <1.3.0 use or due to issue #1923. if (_opacityDefault === 0) _opacityDefault = 1.0; function setOpacity(d) { var bg = context.container().selectAll('.layer-background') .transition() .style('opacity', d) .attr('data-opacity', d); if (!detected.opera) { utilSetTransform(bg, 0, 0); } _displayOptions.selectAll('opacity-options li') .classed('active', function(_) { return _ === d; }); context.storage('background-opacity', d); } function setTooltips(selection) { selection.each(function(d, i, nodes) { var item = d3_select(this).select('label'), span = item.select('span'), placement = (i < nodes.length / 2) ? 'bottom' : 'top', description = d.description(), isOverflowing = (span.property('clientWidth') !== span.property('scrollWidth')); if (d === _previousBackground) { item.call(tooltip() .placement(placement) .html(true) .title(function() { var tip = '
' + t('background.switch') + '
'; return uiTooltipHtml(tip, uiCmd('⌘' + key)); }) ); } else if (description || isOverflowing) { item.call(tooltip() .placement(placement) .title(description || d.name()) ); } else { item.call(tooltip().destroy); } }); } function updateLayerSelections(selection) { function active(d) { return context.background().showsLayer(d); } selection.selectAll('.layer') .classed('active', active) .classed('switch', function(d) { return d === _previousBackground; }) .call(setTooltips) .selectAll('input') .property('checked', active); } function chooseBackground(d) { if (d.id === 'custom' && !d.template()) { return editCustom(); } d3_event.preventDefault(); _previousBackground = context.background().baseLayerSource(); context.background().baseLayerSource(d); _backgroundList.call(updateLayerSelections); document.activeElement.blur(); } function editCustom() { d3_event.preventDefault(); var example = 'https://{switch:a,b,c}.tile.openstreetmap.org/{zoom}/{x}/{y}.png'; var template = window.prompt( t('background.custom_prompt', { example: example }), _customSource.template() || example ); if (template) { context.storage('background-custom-template', template); _customSource.template(template); chooseBackground(_customSource); } else { _backgroundList.call(updateLayerSelections); } } function chooseOverlay(d) { d3_event.preventDefault(); context.background().toggleOverlayLayer(d); _overlayList.call(updateLayerSelections); document.activeElement.blur(); } function drawListItems(layerList, type, change, filter) { var sources = context.background() .sources(context.map().extent()) .filter(filter); var layerLinks = layerList.selectAll('li.layer') .data(sources, function(d) { return d.name(); }); layerLinks.exit() .remove(); var enter = layerLinks.enter() .append('li') .attr('class', 'layer') .classed('layer-custom', function(d) { return d.id === 'custom'; }) .classed('best', function(d) { return d.best(); }); enter.filter(function(d) { return d.id === 'custom'; }) .append('button') .attr('class', 'layer-browse') .call(tooltip() .title(t('background.custom_button')) .placement((textDirection === 'rtl') ? 'right' : 'left') ) .on('click', editCustom) .call(svgIcon('#icon-search')); enter.filter(function(d) { return d.best(); }) .append('div') .attr('class', 'best') .call(tooltip() .title(t('background.best_imagery')) .placement((textDirection === 'rtl') ? 'right' : 'left') ) .append('span') .html('★'); var label = enter .append('label'); label .append('input') .attr('type', type) .attr('name', 'layers') .on('change', change); label .append('span') .text(function(d) { return d.name(); }); layerList.selectAll('li.layer') .sort(sortSources) .style('display', layerList.selectAll('li.layer').data().length > 0 ? 'block' : 'none'); layerList .call(updateLayerSelections); function sortSources(a, b) { return a.best() && !b.best() ? -1 : b.best() && !a.best() ? 1 : d3_descending(a.area(), b.area()) || d3_ascending(a.name(), b.name()) || 0; } } function renderBackgroundList(selection) { var container = selection.selectAll('layer-background-list') .data([0]); _backgroundList = container.enter() .append('ul') .attr('class', 'layer-list layer-background-list') .attr('dir', 'auto') .merge(container); } function renderOverlayList(selection) { var container = selection.selectAll('layer-overlay-list') .data([0]); _overlayList = container.enter() .append('ul') .attr('class', 'layer-list layer-overlay-list') .attr('dir', 'auto') .merge(container); } function renderDisplayOptions(selection) { var container = selection.selectAll('display-options-container') .data([0]); var containerEnter = container.enter() .append('div') .attr('class', 'display-options-container controls-list'); /* add opacity switcher */ var opacityDivEnter = containerEnter .append('div') .attr('class', 'opacity-options-wrapper'); opacityDivEnter .append('h5') .text(t('background.brightness')); var opacityUlEnter = opacityDivEnter.append('ul') .attr('class', 'opacity-options'); opacityUlEnter.selectAll('div.opacity') .data(opacities) .enter() .append('li') .attr('data-original-title', function(d) { return t('background.percent_brightness', { opacity: (d * 100) }); }) .on('click.set-opacity', setOpacity) .html('
') .call(tooltip() .placement((textDirection === 'rtl') ? 'right' : 'left') ) .append('div') .attr('class', 'opacity') .style('opacity', function(d) { return 1.25 - d; }); /* add minimap toggle */ var minimapEnter = containerEnter .append('div') .attr('class', 'minimap-toggle-wrap'); var minimapLabelEnter = minimapEnter .append('label') .call(tooltip() .html(true) .title(uiTooltipHtml(t('background.minimap.tooltip'), t('background.minimap.key'))) .placement('top') ); minimapLabelEnter .classed('minimap-toggle', true) .append('input') .attr('type', 'checkbox') .on('change', function() { uiMapInMap.toggle(); d3_event.preventDefault(); }); minimapLabelEnter .append('span') .text(t('background.minimap.description')); _displayOptions = containerEnter .merge(container); } function update() { _backgroundList .call(drawListItems, 'radio', chooseBackground, function(d) { return !d.isHidden() && !d.overlay; }); _overlayList .call(drawListItems, 'checkbox', chooseOverlay, function(d) { return !d.isHidden() && d.overlay; }); _offsetContainer .call(backgroundOffset); } function quickSwitch() { if (d3_event) { d3_event.stopImmediatePropagation(); d3_event.preventDefault(); } if (_previousBackground) { chooseBackground(_previousBackground); } } function background(selection) { function hidePane() { setVisible(false); } function togglePane() { if (d3_event) d3_event.preventDefault(); paneTooltip.hide(button); setVisible(!button.classed('active')); } function setVisible(show) { if (show !== _shown) { button.classed('active', show); _shown = show; if (show) { selection .on('mousedown.background-inside', function() { d3_event.stopPropagation(); }); pane .style('display', 'block') .style('right', '-300px') .transition() .duration(200) .style('right', '0px'); pane.selectAll('.layer') .call(setTooltips); } else { pane .style('display', 'block') .style('right', '0px') .transition() .duration(200) .style('right', '-300px') .on('end', function() { d3_select(this).style('display', 'none'); }); selection .on('mousedown.background-inside', null); } } } var pane = selection .append('div') .attr('class', 'fillL map-overlay col3 content hide'); var paneTooltip = tooltip() .placement((textDirection === 'rtl') ? 'right' : 'left') .html(true) .title(uiTooltipHtml(t('background.description'), key)); var button = selection .append('button') .attr('tabindex', -1) .on('click', togglePane) .call(svgIcon('#icon-layers', 'light')) .call(paneTooltip); pane .append('h2') .text(t('background.title')); // background list pane .append('div') .attr('class', 'background-background-list-container') .call(uiDisclosure(context, 'background_list', true) .title(t('background.backgrounds')) .content(renderBackgroundList) ); // _backgroundList = pane // .append('ul') // .attr('class', 'layer-list') // .attr('dir', 'auto'); // "Where does this imagery come from?" // pane // .append('div') // .attr('class', 'imagery-faq') // .append('a') // .attr('target', '_blank') // .attr('tabindex', -1) // .call(svgIcon('#icon-out-link', 'inline')) // .attr('href', 'https://github.com/openstreetmap/iD/blob/master/FAQ.md#how-can-i-report-an-issue-with-background-imagery') // .append('span') // .text(t('background.imagery_source_faq')); // overlay list pane .append('div') .attr('class', 'background-overlay-list-container') .call(uiDisclosure(context, 'overlay_list', true) .title(t('background.overlays')) .content(renderOverlayList) ); // display settings pane .append('div') .attr('class', 'background-display-options-container') .call(uiDisclosure(context, 'background_display_options', true) .title(t('background.display_options')) .content(renderDisplayOptions) ); // offset controls _offsetContainer = pane .append('div') .attr('class', 'background-offset'); // add listeners context.map() .on('move.background-update', _debounce(utilCallWhenIdle(update), 1000)); context.background() .on('change.background-update', update); update(); setOpacity(_opacityDefault); var keybinding = d3_keybinding('background') .on(key, togglePane) .on(uiCmd('⌘' + key), quickSwitch) .on([t('map_data.key'), t('help.key')], hidePane); d3_select(document) .call(keybinding); } return background; }