diff --git a/modules/ui/index.js b/modules/ui/index.js index e663bed92..256d57a7f 100644 --- a/modules/ui/index.js +++ b/modules/ui/index.js @@ -1,8 +1,6 @@ export { uiInit } from './init'; export { uiAccount } from './account'; export { uiAttribution } from './attribution'; -export { uiBackgroundDisplayOptions } from './background_display_options'; -export { uiBackgroundOffset } from './background_offset'; export { uiChangesetEditor } from './changeset_editor'; export { uiCmd } from './cmd'; export { uiCombobox } from './combobox'; diff --git a/modules/ui/init.js b/modules/ui/init.js index b893cbf60..7c0d33160 100644 --- a/modules/ui/init.js +++ b/modules/ui/init.js @@ -37,11 +37,11 @@ import { uiVersion } from './version'; import { uiZoom } from './zoom'; import { uiCmd } from './cmd'; -import { uiBackground } from './panes/background'; -import { uiHelp } from './panes/help'; -import { uiIssues } from './panes/issues'; -import { uiMapData } from './panes/map_data'; -import { uiPreferences } from './panes/preferences'; +import { uiPaneBackground } from './panes/background'; +import { uiPaneHelp } from './panes/help'; +import { uiPaneIssues } from './panes/issues'; +import { uiPaneMapData } from './panes/map_data'; +import { uiPanePreferences } from './panes/preferences'; export function uiInit(context) { var _initCounter = 0; @@ -107,11 +107,11 @@ export function uiInit(context) { .call(uiGeolocate(context)); var uiPanes = [ - uiBackground(context), - uiMapData(context), - uiIssues(context), - uiPreferences(context), - uiHelp(context) + uiPaneBackground(context), + uiPaneMapData(context), + uiPaneIssues(context), + uiPanePreferences(context), + uiPaneHelp(context) ]; uiPanes.forEach(function(pane) { diff --git a/modules/ui/panes/background.js b/modules/ui/panes/background.js index 90c9f8f36..88d4eba13 100644 --- a/modules/ui/panes/background.js +++ b/modules/ui/panes/background.js @@ -1,304 +1,36 @@ 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 { t, textDirection } from '../../util/locale'; -import { tooltip } from '../../util/tooltip'; -import { svgIcon } from '../../svg/icon'; -import { uiBackgroundDisplayOptions } from '../background_display_options'; -import { uiBackgroundOffset } from '../background_offset'; +import { t } from '../../util/locale'; import { uiCmd } from '../cmd'; -import { uiDisclosure } from '../disclosure'; -import { uiMapInMap } from '../map_in_map'; -import { uiSettingsCustomBackground } from '../settings/custom_background'; -import { uiTooltipHtml } from '../tooltipHtml'; import { uiPane } from '../pane'; +import { uiBackgroundDisplayOptions } from '../sections/background_display_options'; +import { uiBackgroundList } from '../sections/background_list'; +import { uiBackgroundOffset } from '../sections/background_offset'; +import { uiOverlayList } from '../sections/overlay_list'; -export function uiBackground(context) { +export function uiPaneBackground(context) { var _key = t('background.key'); - var _customSource = context.background().findSource('custom'); - var _previousBackground = context.background().findSource(context.storage('background-last-used-toggle')); - - var _backgroundList = d3_select(null); - var _overlayList = d3_select(null); + var _backgroundListContainer = d3_select(null); + var _overlayListContainer = d3_select(null); var _displayOptionsContainer = d3_select(null); var _offsetContainer = d3_select(null); + var backgroundList = uiBackgroundList(context); var backgroundDisplayOptions = uiBackgroundDisplayOptions(context); var backgroundOffset = uiBackgroundOffset(context); - - var settingsCustomBackground = uiSettingsCustomBackground(context) - .on('change', customChanged); - - - function setTooltips(selection) { - selection.each(function(d, i, nodes) { - var item = d3_select(this).select('label'); - var span = item.select('span'); - var placement = (i < nodes.length / 2) ? 'bottom' : 'top'; - var description = d.description(); - var isOverflowing = (span.property('clientWidth') !== span.property('scrollWidth')); - - item.call(tooltip().destroyAny); - - 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()) - ); - } - }); - } - - - function updateLayerSelections(selection) { - function active(d) { - return context.background().showsLayer(d); - } - - selection.selectAll('li') - .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.storage('background-last-used-toggle', _previousBackground.id); - context.storage('background-last-used', d.id); - context.background().baseLayerSource(d); - _backgroundList.call(updateLayerSelections); - document.activeElement.blur(); - } - - - function customChanged(d) { - if (d && d.template) { - _customSource.template(d.template); - chooseBackground(_customSource); - } else { - _customSource.template(''); - chooseBackground(context.background().findSource('none')); - } - } - - - function editCustom() { - d3_event.preventDefault(); - context.container() - .call(settingsCustomBackground); - } - - - 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(), context.map().zoom(), true) - .filter(filter); - - var layerLinks = layerList.selectAll('li') - .data(sources, function(d) { return d.name(); }); - - layerLinks.exit() - .remove(); - - var enter = layerLinks.enter() - .append('li') - .classed('layer-custom', function(d) { return d.id === 'custom'; }) - .classed('best', function(d) { return d.best(); }); - - 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(); }); - - enter.filter(function(d) { return d.id === 'custom'; }) - .append('button') - .attr('class', 'layer-browse') - .call(tooltip() - .title(t('settings.custom_background.tooltip')) - .placement((textDirection === 'rtl') ? 'right' : 'left') - ) - .on('click', editCustom) - .call(svgIcon('#iD-icon-more')); - - 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('★'); - - - layerList.selectAll('li') - .sort(sortSources); - - 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) { - - // the background list - 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); - - - // add minimap toggle below list - var bgExtrasListEnter = selection.selectAll('.bg-extras-list') - .data([0]) - .enter() - .append('ul') - .attr('class', 'layer-list bg-extras-list'); - - var minimapLabelEnter = bgExtrasListEnter - .append('li') - .attr('class', 'minimap-toggle-item') - .append('label') - .call(tooltip() - .html(true) - .title(uiTooltipHtml(t('background.minimap.tooltip'), t('background.minimap.key'))) - .placement('top') - ); - - minimapLabelEnter - .append('input') - .attr('type', 'checkbox') - .on('change', function() { - d3_event.preventDefault(); - uiMapInMap.toggle(); - }); - - minimapLabelEnter - .append('span') - .text(t('background.minimap.description')); - - - var panelLabelEnter = bgExtrasListEnter - .append('li') - .attr('class', 'background-panel-toggle-item') - .append('label') - .call(tooltip() - .html(true) - .title(uiTooltipHtml(t('background.panel.tooltip'), uiCmd('⌘⇧' + t('info_panels.background.key')))) - .placement('top') - ); - - panelLabelEnter - .append('input') - .attr('type', 'checkbox') - .on('change', function() { - d3_event.preventDefault(); - context.ui().info.toggle('background'); - }); - - panelLabelEnter - .append('span') - .text(t('background.panel.description')); - - - // "Info / Report a Problem" link - selection.selectAll('.imagery-faq') - .data([0]) - .enter() - .append('div') - .attr('class', 'imagery-faq') - .append('a') - .attr('target', '_blank') - .call(svgIcon('#iD-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_problem_faq')); - - updateBackgroundList(); - } - - - 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); - - updateOverlayList(); - } - - function updateBackgroundList() { - _backgroundList - .call(drawListItems, 'radio', chooseBackground, function(d) { return !d.isHidden() && !d.overlay; }); - } - - function updateOverlayList() { - _overlayList - .call(drawListItems, 'checkbox', chooseOverlay, function(d) { return !d.isHidden() && d.overlay; }); - } - + var overlayList = uiOverlayList(context); function update() { - if (!backgroundPane.selection().select('.disclosure-wrap-background_list').classed('hide')) { - updateBackgroundList(); - } + _backgroundListContainer + .call(backgroundList); - if (!backgroundPane.selection().select('.disclosure-wrap-overlay_list').classed('hide')) { - updateOverlayList(); - } + _overlayListContainer + .call(overlayList); _displayOptionsContainer .call(backgroundDisplayOptions); @@ -307,18 +39,21 @@ export function uiBackground(context) { .call(backgroundOffset); } - function quickSwitch() { if (d3_event) { d3_event.stopImmediatePropagation(); d3_event.preventDefault(); } - if (_previousBackground) { - chooseBackground(_previousBackground); + var previousBackground = context.background().findSource(context.storage('background-last-used-toggle')); + if (previousBackground) { + var newPreviousBackground = context.background().baseLayerSource(); + context.storage('background-last-used-toggle', newPreviousBackground.id); + context.storage('background-last-used', previousBackground.id); + context.background().baseLayerSource(previousBackground); + document.activeElement.blur(); } } - var backgroundPane = uiPane('background', context) .key(_key) .title(t('background.title')) @@ -328,22 +63,14 @@ export function uiBackground(context) { backgroundPane.renderContent = function(content) { // background list - content + _backgroundListContainer = content .append('div') - .attr('class', 'background-background-list-container') - .call(uiDisclosure(context, 'background_list', true) - .title(t('background.backgrounds')) - .content(renderBackgroundList) - ); + .attr('class', 'background-background-list-container'); // overlay list - content + _overlayListContainer = content .append('div') - .attr('class', 'background-overlay-list-container') - .call(uiDisclosure(context, 'overlay_list', true) - .title(t('background.overlays')) - .content(renderOverlayList) - ); + .attr('class', 'background-overlay-list-container'); // display options _displayOptionsContainer = content @@ -355,6 +82,8 @@ export function uiBackground(context) { .append('div') .attr('class', 'background-offset'); + update(); + // add listeners context.map() @@ -362,13 +91,9 @@ export function uiBackground(context) { _debounce(function() { window.requestIdleCallback(update); }, 1000) ); - context.background() .on('change.background-update', update); - - update(); - context.keybinding() .on(uiCmd('⌘' + _key), quickSwitch); }; diff --git a/modules/ui/panes/help.js b/modules/ui/panes/help.js index c23bde88e..eec1a42d3 100644 --- a/modules/ui/panes/help.js +++ b/modules/ui/panes/help.js @@ -11,7 +11,7 @@ import { t, textDirection } from '../../util/locale'; import { tooltip } from '../../util/tooltip'; import { icon } from '../intro/helper'; -export function uiHelp(context) { +export function uiPaneHelp(context) { var docKeys = [ ['help', [ diff --git a/modules/ui/panes/issues.js b/modules/ui/panes/issues.js index 38705d204..ad0e2885e 100644 --- a/modules/ui/panes/issues.js +++ b/modules/ui/panes/issues.js @@ -13,7 +13,7 @@ import { utilGetSetValue, utilHighlightEntities, utilNoAuto } from '../../util'; import { uiPane } from '../pane'; -export function uiIssues(context) { +export function uiPaneIssues(context) { var MINSQUARE = 0; var MAXSQUARE = 20; diff --git a/modules/ui/panes/map_data.js b/modules/ui/panes/map_data.js index 9686f87fc..15dd49eec 100644 --- a/modules/ui/panes/map_data.js +++ b/modules/ui/panes/map_data.js @@ -15,7 +15,7 @@ import { uiCmd } from '../cmd'; import { uiPane } from '../pane'; -export function uiMapData(context) { +export function uiPaneMapData(context) { var osmDataToggleKey = uiCmd('⌥' + t('area_fill.wireframe.key')); var features = context.features().keys(); var layers = context.layers(); diff --git a/modules/ui/panes/preferences.js b/modules/ui/panes/preferences.js index db284ace5..af16b0fe9 100644 --- a/modules/ui/panes/preferences.js +++ b/modules/ui/panes/preferences.js @@ -7,7 +7,7 @@ import { uiDisclosure } from '../disclosure'; import { uiPane } from '../pane'; -export function uiPreferences(context) { +export function uiPanePreferences(context) { let _showThirdPartyIcons = context.storage('preferences.privacy.thirdpartyicons') || 'true'; function renderPrivacyOptions(selection) { diff --git a/modules/ui/background_display_options.js b/modules/ui/sections/background_display_options.js similarity index 95% rename from modules/ui/background_display_options.js rename to modules/ui/sections/background_display_options.js index f22f5d7e8..9632dcb4c 100644 --- a/modules/ui/background_display_options.js +++ b/modules/ui/sections/background_display_options.js @@ -3,10 +3,10 @@ import { select as d3_select } from 'd3-selection'; -import { t, textDirection } from '../util/locale'; -import { svgIcon } from '../svg/icon'; -import { uiDisclosure } from './disclosure'; -import { utilDetect } from '../util/detect'; +import { t, textDirection } from '../../util/locale'; +import { svgIcon } from '../../svg/icon'; +import { uiDisclosure } from '../disclosure'; +import { utilDetect } from '../../util/detect'; export function uiBackgroundDisplayOptions(context) { diff --git a/modules/ui/sections/background_list.js b/modules/ui/sections/background_list.js new file mode 100644 index 000000000..49c5265a5 --- /dev/null +++ b/modules/ui/sections/background_list.js @@ -0,0 +1,268 @@ +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 { t, textDirection } from '../../util/locale'; +import { tooltip } from '../../util/tooltip'; +import { svgIcon } from '../../svg/icon'; +import { uiCmd } from '../cmd'; +import { uiDisclosure } from '../disclosure'; +import { uiSettingsCustomBackground } from '../settings/custom_background'; +import { uiMapInMap } from '../map_in_map'; +import { uiTooltipHtml } from '../tooltipHtml'; + +export function uiBackgroundList(context) { + + var _backgroundList = d3_select(null); + + var _customSource = context.background().findSource('custom'); + + var settingsCustomBackground = uiSettingsCustomBackground(context) + .on('change', customChanged); + + function previousBackgroundID() { + return context.storage('background-last-used-toggle'); + } + + function render(selection) { + + // the background list + 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); + + + // add minimap toggle below list + var bgExtrasListEnter = selection.selectAll('.bg-extras-list') + .data([0]) + .enter() + .append('ul') + .attr('class', 'layer-list bg-extras-list'); + + var minimapLabelEnter = bgExtrasListEnter + .append('li') + .attr('class', 'minimap-toggle-item') + .append('label') + .call(tooltip() + .html(true) + .title(uiTooltipHtml(t('background.minimap.tooltip'), t('background.minimap.key'))) + .placement('top') + ); + + minimapLabelEnter + .append('input') + .attr('type', 'checkbox') + .on('change', function() { + d3_event.preventDefault(); + uiMapInMap.toggle(); + }); + + minimapLabelEnter + .append('span') + .text(t('background.minimap.description')); + + + var panelLabelEnter = bgExtrasListEnter + .append('li') + .attr('class', 'background-panel-toggle-item') + .append('label') + .call(tooltip() + .html(true) + .title(uiTooltipHtml(t('background.panel.tooltip'), uiCmd('⌘⇧' + t('info_panels.background.key')))) + .placement('top') + ); + + panelLabelEnter + .append('input') + .attr('type', 'checkbox') + .on('change', function() { + d3_event.preventDefault(); + context.ui().info.toggle('background'); + }); + + panelLabelEnter + .append('span') + .text(t('background.panel.description')); + + + // "Info / Report a Problem" link + selection.selectAll('.imagery-faq') + .data([0]) + .enter() + .append('div') + .attr('class', 'imagery-faq') + .append('a') + .attr('target', '_blank') + .call(svgIcon('#iD-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_problem_faq')); + + updateBackgroundList(); + } + + function setTooltips(selection) { + selection.each(function(d, i, nodes) { + var item = d3_select(this).select('label'); + var span = item.select('span'); + var placement = (i < nodes.length / 2) ? 'bottom' : 'top'; + var description = d.description(); + var isOverflowing = (span.property('clientWidth') !== span.property('scrollWidth')); + + item.call(tooltip().destroyAny); + + if (d.id === previousBackgroundID()) { + item.call(tooltip() + .placement(placement) + .html(true) + .title(function() { + var tip = '
' + t('background.switch') + '
'; + return uiTooltipHtml(tip, uiCmd('⌘' + t('background.key'))); + }) + ); + } else if (description || isOverflowing) { + item.call(tooltip() + .placement(placement) + .title(description || d.name()) + ); + } + }); + } + + function updateBackgroundList() { + _backgroundList + .call(drawListItems, 'radio', chooseBackground, function(d) { return !d.isHidden() && !d.overlay; }); + } + + function drawListItems(layerList, type, change, filter) { + var sources = context.background() + .sources(context.map().extent(), context.map().zoom(), true) + .filter(filter); + + var layerLinks = layerList.selectAll('li') + .data(sources, function(d) { return d.name(); }); + + layerLinks.exit() + .remove(); + + var enter = layerLinks.enter() + .append('li') + .classed('layer-custom', function(d) { return d.id === 'custom'; }) + .classed('best', function(d) { return d.best(); }); + + 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(); }); + + enter.filter(function(d) { return d.id === 'custom'; }) + .append('button') + .attr('class', 'layer-browse') + .call(tooltip() + .title(t('settings.custom_background.tooltip')) + .placement((textDirection === 'rtl') ? 'right' : 'left') + ) + .on('click', editCustom) + .call(svgIcon('#iD-icon-more')); + + 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('★'); + + + layerList.selectAll('li') + .sort(sortSources); + + 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 updateLayerSelections(selection) { + function active(d) { + return context.background().showsLayer(d); + } + + selection.selectAll('li') + .classed('active', active) + .classed('switch', function(d) { return d.id === previousBackgroundID(); }) + .call(setTooltips) + .selectAll('input') + .property('checked', active); + } + + + function chooseBackground(d) { + if (d.id === 'custom' && !d.template()) { + return editCustom(); + } + + d3_event.preventDefault(); + var previousBackground = context.background().baseLayerSource(); + context.storage('background-last-used-toggle', previousBackground.id); + context.storage('background-last-used', d.id); + context.background().baseLayerSource(d); + document.activeElement.blur(); + } + + + function customChanged(d) { + if (d && d.template) { + _customSource.template(d.template); + chooseBackground(_customSource); + } else { + _customSource.template(''); + chooseBackground(context.background().findSource('none')); + } + } + + + function editCustom() { + d3_event.preventDefault(); + context.container() + .call(settingsCustomBackground); + } + + + function backgroundList(selection) { + selection + .call(uiDisclosure(context, 'background_list', true) + .title(t('background.backgrounds')) + .content(render) + ); + } + + context.background() + .on('change.background_list', function() { + _backgroundList.call(updateLayerSelections); + }); + + return backgroundList; +} diff --git a/modules/ui/background_offset.js b/modules/ui/sections/background_offset.js similarity index 96% rename from modules/ui/background_offset.js rename to modules/ui/sections/background_offset.js index 00016ab1c..407626576 100644 --- a/modules/ui/background_offset.js +++ b/modules/ui/sections/background_offset.js @@ -4,10 +4,10 @@ import { selectAll as d3_selectAll } from 'd3-selection'; -import { t, textDirection } from '../util/locale'; -import { geoMetersToOffset, geoOffsetToMeters } from '../geo'; -import { svgIcon } from '../svg/icon'; -import { uiDisclosure } from './disclosure'; +import { t, textDirection } from '../../util/locale'; +import { geoMetersToOffset, geoOffsetToMeters } from '../../geo'; +import { svgIcon } from '../../svg/icon'; +import { uiDisclosure } from '../disclosure'; export function uiBackgroundOffset(context) { diff --git a/modules/ui/sections/overlay_list.js b/modules/ui/sections/overlay_list.js new file mode 100644 index 000000000..757d5ebdc --- /dev/null +++ b/modules/ui/sections/overlay_list.js @@ -0,0 +1,124 @@ +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 { t } from '../../util/locale'; +import { tooltip } from '../../util/tooltip'; +import { uiDisclosure } from '../disclosure'; + +export function uiOverlayList(context) { + + var _overlayList = d3_select(null); + + function render(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); + + updateOverlayList(); + } + + function setTooltips(selection) { + selection.each(function(d, i, nodes) { + var item = d3_select(this).select('label'); + var span = item.select('span'); + var placement = (i < nodes.length / 2) ? 'bottom' : 'top'; + var description = d.description(); + var isOverflowing = (span.property('clientWidth') !== span.property('scrollWidth')); + + item.call(tooltip().destroyAny); + + if (description || isOverflowing) { + item.call(tooltip() + .placement(placement) + .title(description || d.name()) + ); + } + }); + } + + function updateLayerSelections(selection) { + function active(d) { + return context.background().showsLayer(d); + } + + selection.selectAll('li') + .classed('active', active) + .call(setTooltips) + .selectAll('input') + .property('checked', active); + } + + + 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(), context.map().zoom(), true) + .filter(filter); + + var layerLinks = layerList.selectAll('li') + .data(sources, function(d) { return d.name(); }); + + layerLinks.exit() + .remove(); + + var enter = layerLinks.enter() + .append('li'); + + 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') + .sort(sortSources); + + 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 updateOverlayList() { + _overlayList + .call(drawListItems, 'checkbox', chooseOverlay, function(d) { return !d.isHidden() && d.overlay; }); + } + + function overlayList(selection) { + selection + .call(uiDisclosure(context, 'overlay_list', true) + .title(t('background.overlays')) + .content(render) + ); + } + + return overlayList; +}