diff --git a/css/80_app.css b/css/80_app.css index b8662d899..2e8b95715 100644 --- a/css/80_app.css +++ b/css/80_app.css @@ -2307,7 +2307,7 @@ div.full-screen > button:hover { padding: 5px 10px; cursor: pointer; color: #7092FF; - border-radius: 3px; + border-top: 1px solid #ccc; } .minimap-toggle.active { @@ -2463,42 +2463,39 @@ div.full-screen > button:hover { } .background-control .nudge.right::after { - border-top: 5px solid transparent; - border-bottom: 5px solid transparent; - border-left: 5px solid #222; + border-top: 5px solid transparent; + border-bottom: 5px solid transparent; + border-left: 5px solid #222; } .background-control .nudge.left::after { - border-top: 5px solid transparent; - border-bottom: 5px solid transparent; - border-right: 5px solid #222; + border-top: 5px solid transparent; + border-bottom: 5px solid transparent; + border-right: 5px solid #222; } .background-control .nudge.top::after { - border-right: 5px solid transparent; - border-left: 5px solid transparent; - border-bottom: 5px solid #222; + border-right: 5px solid transparent; + border-left: 5px solid transparent; + border-bottom: 5px solid #222; } .background-control .nudge.bottom::after { - border-right: 5px solid transparent; - border-left: 5px solid transparent; - border-top: 5px solid #222; + border-right: 5px solid transparent; + border-left: 5px solid transparent; + border-top: 5px solid #222; +} + +.opacity-options-wrapper { + padding: 10px; } .opacity-options { background: url(img/background-pattern-opacity.png) 0 0 repeat; height: 20px; width: 82px; - position: absolute; - right: 50px; - top: 20px; border: 1px solid #ccc; } -[dir='rtl'] .opacity-options { - left: 50px; - right: auto; -} .opacity-options li { height: 100%; @@ -2513,8 +2510,6 @@ div.full-screen > button:hover { z-index: 9999; } -.map-data-control li:hover .select-box, -.map-data-control li.selected .select-box, .background-control li:hover .select-box, .background-control li.selected .select-box { border: 2px solid #7092ff; @@ -2522,8 +2517,6 @@ div.full-screen > button:hover { opacity: .5; } -.map-data-control li.selected:hover .select-box, -.map-data-control li.selected .select-box, .background-control li.selected:hover .select-box, .background-control li.selected .select-box { opacity: 1; diff --git a/data/core.yaml b/data/core.yaml index e40b52190..1344b733a 100644 --- a/data/core.yaml +++ b/data/core.yaml @@ -372,21 +372,25 @@ en: title: Background description: Background settings key: B - percent_brightness: "{opacity}% brightness" + backgrounds: Backgrounds none: None best_imagery: Best known imagery source for this location switch: Switch back to this background custom: Custom custom_button: Edit custom background custom_prompt: "Enter a tile URL template. Valid tokens are:\n - {zoom}/{z}, {x}, {y} for Z/X/Y tile scheme\n - {ty} for flipped TMS-style Y coordinates\n - {u} for quadtile scheme\n - {switch:a,b,c} for DNS server multiplexing\n\nExample:\n{example}" - fix_misalignment: Adjust imagery offset + overlays: Overlays imagery_source_faq: Where does this imagery come from? reset: reset - offset: "Drag anywhere in the gray area below to adjust the imagery offset, or enter the offset values in meters." + display_options: Display Options + brightness: Brightness + percent_brightness: "{opacity}% brightness" minimap: - description: Minimap + description: Show Minimap tooltip: Show a zoomed out map to help locate the area currently displayed. key: '/' + fix_misalignment: Adjust imagery offset + offset: "Drag anywhere in the gray area below to adjust the imagery offset, or enter the offset values in meters." map_data: title: Map Data description: Map Data diff --git a/dist/locales/en.json b/dist/locales/en.json index 0cdefe708..c41d2f005 100644 --- a/dist/locales/en.json +++ b/dist/locales/en.json @@ -460,22 +460,26 @@ "title": "Background", "description": "Background settings", "key": "B", - "percent_brightness": "{opacity}% brightness", + "backgrounds": "Backgrounds", "none": "None", "best_imagery": "Best known imagery source for this location", "switch": "Switch back to this background", "custom": "Custom", "custom_button": "Edit custom background", "custom_prompt": "Enter a tile URL template. Valid tokens are:\n - {zoom}/{z}, {x}, {y} for Z/X/Y tile scheme\n - {ty} for flipped TMS-style Y coordinates\n - {u} for quadtile scheme\n - {switch:a,b,c} for DNS server multiplexing\n\nExample:\n{example}", - "fix_misalignment": "Adjust imagery offset", + "overlays": "Overlays", "imagery_source_faq": "Where does this imagery come from?", "reset": "reset", - "offset": "Drag anywhere in the gray area below to adjust the imagery offset, or enter the offset values in meters.", + "display_options": "Display Options", + "brightness": "Brightness", + "percent_brightness": "{opacity}% brightness", "minimap": { - "description": "Minimap", + "description": "Show Minimap", "tooltip": "Show a zoomed out map to help locate the area currently displayed.", "key": "/" - } + }, + "fix_misalignment": "Adjust imagery offset", + "offset": "Drag anywhere in the gray area below to adjust the imagery offset, or enter the offset values in meters." }, "map_data": { "title": "Map Data", diff --git a/modules/ui/background.js b/modules/ui/background.js index 732188deb..d23287a7f 100644 --- a/modules/ui/background.js +++ b/modules/ui/background.js @@ -16,6 +16,7 @@ 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'; @@ -24,227 +25,325 @@ import { tooltip } from '../util/tooltip'; export function uiBackground(context) { - var key = t('background.key'), - detected = utilDetect(), - opacities = [1, 0.75, 0.5, 0.25], - opacityDefault = (context.storage('background-opacity') !== null) ? - (+context.storage('background-opacity')) : 1.0, - customSource = context.background().findSource('custom'), - previous; + 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; + if (_opacityDefault === 0) _opacityDefault = 1.0; - function background(selection) { + 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 setOpacity(d) { - var bg = context.container().selectAll('.layer-background') - .transition() - .style('opacity', d) - .attr('data-opacity', d); + function renderBackgroundList(selection) { + var container = selection.selectAll('layer-background-list') + .data([0]); - if (!detected.opera) { - utilSetTransform(bg, 0, 0); - } - - opacityList.selectAll('li') - .classed('active', function(_) { return _ === d; }); - - context.storage('background-opacity', d); - } + _backgroundList = container.enter() + .append('ul') + .attr('class', 'layer-list layer-background-list') + .attr('dir', 'auto') + .merge(container); + } - 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')); + function renderOverlayList(selection) { + var container = selection.selectAll('layer-overlay-list') + .data([0]); - if (d === previous) { - 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); - } - }); - } + _overlayList = container.enter() + .append('ul') + .attr('class', 'layer-list layer-overlay-list') + .attr('dir', 'auto') + .merge(container); + } - function selectLayer() { - function active(d) { - return context.background().showsLayer(d); - } + function renderDisplayOptions(selection) { + var container = selection.selectAll('display-options-container') + .data([0]); - content.selectAll('.layer') - .classed('active', active) - .classed('switch', function(d) { return d === previous; }) - .call(setTooltips) - .selectAll('input') - .property('checked', active); - } + 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; }); - function clickSetSource(d) { - if (d.id === 'custom' && !d.template()) { - return editCustom(); - } + /* add minimap toggle */ + var minimapEnter = containerEnter + .append('div') + .attr('class', 'minimap-toggle-wrap'); - d3_event.preventDefault(); - previous = context.background().baseLayerSource(); - context.background().baseLayerSource(d); - selectLayer(); - 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 + var minimapLabelEnter = minimapEnter + .append('label') + .call(tooltip() + .html(true) + .title(uiTooltipHtml(t('background.minimap.tooltip'), t('background.minimap.key'))) + .placement('top') ); - if (template) { - context.storage('background-custom-template', template); - customSource.template(template); - clickSetSource(customSource); - } else { - selectLayer(); - } - } + 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 clickSetOverlay(d) { + 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(); - context.background().toggleOverlayLayer(d); - selectLayer(); - 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'); + if (_previousBackground) { + chooseBackground(_previousBackground); } + } - function update() { - backgroundList - .call(drawListItems, 'radio', clickSetSource, function(d) { return !d.isHidden() && !d.overlay; }); + function background(selection) { - overlayList - .call(drawListItems, 'checkbox', clickSetOverlay, function(d) { return !d.isHidden() && d.overlay; }); - - selectLayer(); - - offsetContainer - .call(backgroundOffset); - } - - - function hide() { + function hidePane() { setVisible(false); } - - function toggle() { - if (d3_event) { - d3_event.preventDefault(); - } - tooltipBehavior.hide(button); + function togglePane() { + if (d3_event) d3_event.preventDefault(); + paneTooltip.hide(button); setVisible(!button.classed('active')); } - - function quickSwitch() { - if (d3_event) { - d3_event.stopImmediatePropagation(); - d3_event.preventDefault(); - } - if (previous) { - clickSetSource(previous); - } - } - - function setVisible(show) { - if (show !== shown) { + if (show !== _shown) { button.classed('active', show); - shown = show; + _shown = show; if (show) { selection @@ -252,18 +351,18 @@ export function uiBackground(context) { d3_event.stopPropagation(); }); - content + pane .style('display', 'block') .style('right', '-300px') .transition() .duration(200) .style('right', '0px'); - content.selectAll('.layer') + pane.selectAll('.layer') .call(setTooltips); } else { - content + pane .style('display', 'block') .style('right', '0px') .transition() @@ -280,11 +379,11 @@ export function uiBackground(context) { } - var content = selection + var pane = selection .append('div') .attr('class', 'fillL map-overlay col3 content hide'); - var tooltipBehavior = tooltip() + var paneTooltip = tooltip() .placement((textDirection === 'rtl') ? 'right' : 'left') .html(true) .title(uiTooltipHtml(t('background.description'), key)); @@ -292,50 +391,30 @@ export function uiBackground(context) { var button = selection .append('button') .attr('tabindex', -1) - .on('click', toggle) + .on('click', togglePane) .call(svgIcon('#icon-layers', 'light')) - .call(tooltipBehavior); + .call(paneTooltip); - var shown = false; - - - /* add opacity switcher */ - var opawrap = content - .append('div') - .attr('class', 'opacity-options-wrapper'); - - opawrap - .append('h4') + pane + .append('h3') .text(t('background.title')); - var opacityList = opawrap - .append('ul') - .attr('class', 'opacity-options'); - - opacityList.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')) + // background list + pane .append('div') - .attr('class', 'opacity') - .style('opacity', function(d) { return 1.25 - d; }); + .attr('class', 'background-background-list-container') + .call(uiDisclosure(context, 'background_list', true) + .title(t('background.backgrounds')) + .content(renderBackgroundList) + ); - - /* add background list */ - var backgroundList = content - .append('ul') - .attr('class', 'layer-list') - .attr('dir', 'auto'); + // _backgroundList = pane + // .append('ul') + // .attr('class', 'layer-list') + // .attr('dir', 'auto'); // "Where does this imagery come from?" - // content + // pane // .append('div') // .attr('class', 'imagery-faq') // .append('a') @@ -347,46 +426,31 @@ export function uiBackground(context) { // .text(t('background.imagery_source_faq')); - /* add overlay list */ - var overlayList = content - .append('ul') - .attr('class', 'layer-list'); - - var controls = content + // overlay list + pane .append('div') - .attr('class', 'controls-list'); - - - /* add minimap toggle */ - var minimapLabel = controls - .append('label') - .call(tooltip() - .html(true) - .title(uiTooltipHtml(t('background.minimap.tooltip'), t('background.minimap.key'))) - .placement('top') + .attr('class', 'background-overlay-list-container') + .call(uiDisclosure(context, 'overlay_list', true) + .title(t('background.overlays')) + .content(renderOverlayList) ); - minimapLabel - .classed('minimap-toggle', true) - .append('input') - .attr('type', 'checkbox') - .on('change', function() { - uiMapInMap.toggle(); - d3_event.preventDefault(); - }); + // 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) + ); - minimapLabel - .append('span') - .text(t('background.minimap.description')); - - - /* add offset controls */ - var offsetContainer = content + // offset controls + _offsetContainer = pane .append('div') .attr('class', 'background-offset'); - /* add listeners */ + // add listeners context.map() .on('move.background-update', _debounce(utilCallWhenIdle(update), 1000)); @@ -395,12 +459,12 @@ export function uiBackground(context) { update(); - setOpacity(opacityDefault); + setOpacity(_opacityDefault); var keybinding = d3_keybinding('background') - .on(key, toggle) + .on(key, togglePane) .on(uiCmd('⌘' + key), quickSwitch) - .on([t('map_data.key'), t('help.key')], hide); + .on([t('map_data.key'), t('help.key')], hidePane); d3_select(document) .call(keybinding);