From 385297d99317607248d9a8b8ba8f143220e2ceff Mon Sep 17 00:00:00 2001 From: Bryan Housel Date: Mon, 27 Nov 2017 22:03:11 -0500 Subject: [PATCH] Use uiDisclosure for data pane subsections --- modules/ui/background.js | 6 +- modules/ui/disclosure.js | 5 +- modules/ui/map_data.js | 783 ++++++++++++++++++++------------------- 3 files changed, 402 insertions(+), 392 deletions(-) diff --git a/modules/ui/background.js b/modules/ui/background.js index 5ad890c3e..732188deb 100644 --- a/modules/ui/background.js +++ b/modules/ui/background.js @@ -147,7 +147,7 @@ export function uiBackground(context) { } - function drawList(layerList, type, change, filter) { + function drawListItems(layerList, type, change, filter) { var sources = context.background() .sources(context.map().extent()) .filter(filter); @@ -204,10 +204,10 @@ export function uiBackground(context) { function update() { backgroundList - .call(drawList, 'radio', clickSetSource, function(d) { return !d.isHidden() && !d.overlay; }); + .call(drawListItems, 'radio', clickSetSource, function(d) { return !d.isHidden() && !d.overlay; }); overlayList - .call(drawList, 'checkbox', clickSetOverlay, function(d) { return !d.isHidden() && d.overlay; }); + .call(drawListItems, 'checkbox', clickSetOverlay, function(d) { return !d.isHidden() && d.overlay; }); selectLayer(); diff --git a/modules/ui/disclosure.js b/modules/ui/disclosure.js index 9d84c02f8..45c6315a5 100644 --- a/modules/ui/disclosure.js +++ b/modules/ui/disclosure.js @@ -20,7 +20,7 @@ export function uiDisclosure(context, key, expandedDefault) { hideToggle = hideToggle.enter() .append('a') .attr('href', '#') - .attr('class', 'hide-toggle') + .attr('class', 'hide-toggle hide-toggle-' + key) .merge(hideToggle); hideToggle @@ -29,11 +29,12 @@ export function uiDisclosure(context, key, expandedDefault) { .classed('expanded', _expanded); - var wrap = selection.selectAll('div') + var wrap = selection.selectAll('.disclosure-wrap') .data([0]); wrap = wrap.enter() .append('div') + .attr('class', 'disclosure-wrap disclosure-wrap-' + key) .merge(wrap); wrap diff --git a/modules/ui/map_data.js b/modules/ui/map_data.js index fb522c098..a928bd649 100644 --- a/modules/ui/map_data.js +++ b/modules/ui/map_data.js @@ -5,380 +5,424 @@ import { import { d3keybinding as d3_keybinding } from '../lib/d3.keybinding.js'; -import { t, textDirection } from '../util/locale'; import { svgIcon } from '../svg'; -import { uiTooltipHtml } from './tooltipHtml'; +import { t, textDirection } from '../util/locale'; import { tooltip } from '../util/tooltip'; +import { uiDisclosure } from './disclosure'; +import { uiTooltipHtml } from './tooltipHtml'; export function uiMapData(context) { - var key = t('map_data.key'), - features = context.features().keys(), - layers = context.layers(), - fills = ['wireframe', 'partial', 'full'], - fillDefault = context.storage('area-fill') || 'partial', - fillSelected = fillDefault; + var key = t('map_data.key'); + var features = context.features().keys(); + var layers = context.layers(); + var fills = ['wireframe', 'partial', 'full']; + + var _fillDefault = context.storage('area-fill') || 'partial'; + var _fillSelected = _fillDefault; + var _shown = false; + var _dataLayerContainer = d3_select(null); + var _fillList = d3_select(null); + var _featureList = d3_select(null); - function map_data(selection) { - function showsFeature(d) { - return context.features().enabled(d); + function showsFeature(d) { + return context.features().enabled(d); + } + + + function autoHiddenFeature(d) { + return context.features().autoHidden(d); + } + + + function clickFeature(d) { + context.features().toggle(d); + update(); + } + + + function showsFill(d) { + return _fillSelected === d; + } + + + function setFill(d) { + fills.forEach(function(opt) { + context.surface().classed('fill-' + opt, Boolean(opt === d)); + }); + + _fillSelected = d; + if (d !== 'wireframe') { + _fillDefault = d; + context.storage('area-fill', d); } + update(); + } - function autoHiddenFeature(d) { - return context.features().autoHidden(d); + function showsLayer(which) { + var layer = layers.layer(which); + if (layer) { + return layer.enabled(); } + return false; + } - function clickFeature(d) { - context.features().toggle(d); + function setLayer(which, enabled) { + var layer = layers.layer(which); + if (layer) { + layer.enabled(enabled); update(); } + } - function showsFill(d) { - return fillSelected === d; + function toggleLayer(which) { + setLayer(which, !showsLayer(which)); + } + + + function drawPhotoItems(selection) { + var photoKeys = ['mapillary-images', 'mapillary-signs', 'openstreetcam-images']; + var photoLayers = layers.all().filter(function(obj) { return photoKeys.indexOf(obj.id) !== -1; }); + var data = photoLayers.filter(function(obj) { return obj.layer.supported(); }); + + function layerSupported(d) { + return d.layer && d.layer.supported(); + } + function layerEnabled(d) { + return layerSupported(d) && d.layer.enabled(); } + var ul = selection + .selectAll('.layer-list-photos') + .data([0]); - function setFill(d) { - fills.forEach(function(opt) { - context.surface().classed('fill-' + opt, Boolean(opt === d)); + ul = ul.enter() + .append('ul') + .attr('class', 'layer-list layer-list-photos') + .merge(ul); + + var li = ul.selectAll('.list-item-photos') + .data(data); + + li.exit() + .remove(); + + var liEnter = li.enter() + .append('li') + .attr('class', function(d) { return 'list-item-photos list-item-' + d.id; }); + + var labelEnter = liEnter + .append('label') + .each(function(d) { + d3_select(this) + .call(tooltip() + .title(t(d.id.replace('-', '_') + '.tooltip')) + .placement('top') + ); }); - fillSelected = d; - if (d !== 'wireframe') { - fillDefault = d; - context.storage('area-fill', d); - } - update(); - } + labelEnter + .append('input') + .attr('type', 'checkbox') + .on('change', function(d) { toggleLayer(d.id); }); + + labelEnter + .append('span') + .text(function(d) { return t(d.id.replace('-', '_') + '.title'); }); - function showsLayer(which) { - var layer = layers.layer(which); - if (layer) { - return layer.enabled(); - } - return false; - } + // Update + li = li + .merge(liEnter); + + li + .classed('active', layerEnabled) + .selectAll('input') + .property('checked', layerEnabled); + } - function setLayer(which, enabled) { - var layer = layers.layer(which); - if (layer) { - layer.enabled(enabled); - update(); - } - } + function drawOsmItem(selection) { + var osm = layers.layer('osm'), + showsOsm = osm.enabled(); + + var ul = selection + .selectAll('.layer-list-osm') + .data(osm ? [0] : []); + + // Exit + ul.exit() + .remove(); + + // Enter + var ulEnter = ul.enter() + .append('ul') + .attr('class', 'layer-list layer-list-osm'); + + var liEnter = ulEnter + .append('li') + .attr('class', 'list-item-osm'); + + var labelEnter = liEnter + .append('label') + .call(tooltip() + .title(t('map_data.layers.osm.tooltip')) + .placement('top') + ); + + labelEnter + .append('input') + .attr('type', 'checkbox') + .on('change', function() { toggleLayer('osm'); }); + + labelEnter + .append('span') + .text(t('map_data.layers.osm.title')); + + // Update + ul = ul + .merge(ulEnter); + + ul.selectAll('.list-item-osm') + .classed('active', showsOsm) + .selectAll('input') + .property('checked', showsOsm); + } - function toggleLayer(which) { - setLayer(which, !showsLayer(which)); - } + function drawGpxItem(selection) { + var gpx = layers.layer('gpx'), + hasGpx = gpx && gpx.hasGpx(), + showsGpx = hasGpx && gpx.enabled(); + var ul = selection + .selectAll('.layer-list-gpx') + .data(gpx ? [0] : []); - function drawPhotoItems(selection) { - var photoKeys = ['mapillary-images', 'mapillary-signs', 'openstreetcam-images']; - var photoLayers = layers.all().filter(function(obj) { return photoKeys.indexOf(obj.id) !== -1; }); - var data = photoLayers.filter(function(obj) { return obj.layer.supported(); }); + // Exit + ul.exit() + .remove(); - function layerSupported(d) { - return d.layer && d.layer.supported(); - } - function layerEnabled(d) { - return layerSupported(d) && d.layer.enabled(); - } + // Enter + var ulEnter = ul.enter() + .append('ul') + .attr('class', 'layer-list layer-list-gpx'); - var ul = selection - .selectAll('.layer-list-photos') - .data([0]); + var liEnter = ulEnter + .append('li') + .attr('class', 'list-item-gpx'); - ul = ul.enter() - .append('ul') - .attr('class', 'layer-list layer-list-photos') - .merge(ul); + liEnter + .append('button') + .attr('class', 'list-item-gpx-extent') + .call(tooltip() + .title(t('gpx.zoom')) + .placement((textDirection === 'rtl') ? 'right' : 'left') + ) + .on('click', function() { + d3_event.preventDefault(); + d3_event.stopPropagation(); + gpx.fitZoom(); + }) + .call(svgIcon('#icon-search')); - var li = ul.selectAll('.list-item-photos') - .data(data); - - li.exit() - .remove(); - - var liEnter = li.enter() - .append('li') - .attr('class', function(d) { return 'list-item-photos list-item-' + d.id; }); - - var labelEnter = liEnter - .append('label') - .each(function(d) { - d3_select(this) - .call(tooltip() - .title(t(d.id.replace('-', '_') + '.tooltip')) - .placement('top') - ); - }); - - labelEnter - .append('input') - .attr('type', 'checkbox') - .on('change', function(d) { toggleLayer(d.id); }); - - labelEnter - .append('span') - .text(function(d) { return t(d.id.replace('-', '_') + '.title'); }); - - - // Update - li = li - .merge(liEnter); - - li - .classed('active', layerEnabled) - .selectAll('input') - .property('checked', layerEnabled); - } - - - function drawOsmItem(selection) { - var osm = layers.layer('osm'), - showsOsm = osm.enabled(); - - var ul = selection - .selectAll('.layer-list-osm') - .data(osm ? [0] : []); - - // Exit - ul.exit() - .remove(); - - // Enter - var ulEnter = ul.enter() - .append('ul') - .attr('class', 'layer-list layer-list-osm'); - - var liEnter = ulEnter - .append('li') - .attr('class', 'list-item-osm'); - - var labelEnter = liEnter - .append('label') - .call(tooltip() - .title(t('map_data.layers.osm.tooltip')) - .placement('top') - ); - - labelEnter - .append('input') - .attr('type', 'checkbox') - .on('change', function() { toggleLayer('osm'); }); - - labelEnter - .append('span') - .text(t('map_data.layers.osm.title')); - - // Update - ul = ul - .merge(ulEnter); - - ul.selectAll('.list-item-osm') - .classed('active', showsOsm) - .selectAll('input') - .property('checked', showsOsm); - } - - - function drawGpxItem(selection) { - var gpx = layers.layer('gpx'), - hasGpx = gpx && gpx.hasGpx(), - showsGpx = hasGpx && gpx.enabled(); - - var ul = selection - .selectAll('.layer-list-gpx') - .data(gpx ? [0] : []); - - // Exit - ul.exit() - .remove(); - - // Enter - var ulEnter = ul.enter() - .append('ul') - .attr('class', 'layer-list layer-list-gpx'); - - var liEnter = ulEnter - .append('li') - .attr('class', 'list-item-gpx'); - - liEnter - .append('button') - .attr('class', 'list-item-gpx-extent') - .call(tooltip() - .title(t('gpx.zoom')) - .placement((textDirection === 'rtl') ? 'right' : 'left')) - .on('click', function() { - d3_event.preventDefault(); - d3_event.stopPropagation(); - gpx.fitZoom(); - }) - .call(svgIcon('#icon-search')); - - liEnter - .append('button') - .attr('class', 'list-item-gpx-browse') - .call(tooltip() - .title(t('gpx.browse')) - .placement((textDirection === 'rtl') ? 'right' : 'left') - ) - .on('click', function() { - d3_select(document.createElement('input')) - .attr('type', 'file') - .on('change', function() { - gpx.files(d3_event.target.files); - }) - .node().click(); - }) - .call(svgIcon('#icon-geolocate')); - - var labelEnter = liEnter - .append('label') - .call(tooltip() - .title(t('gpx.drag_drop')) - .placement('top') - ); - - labelEnter - .append('input') - .attr('type', 'checkbox') - .on('change', function() { toggleLayer('gpx'); }); - - labelEnter - .append('span') - .text(t('gpx.local_layer')); - - // Update - ul = ul - .merge(ulEnter); - - ul.selectAll('.list-item-gpx') - .classed('active', showsGpx) - .selectAll('label') - .classed('deemphasize', !hasGpx) - .selectAll('input') - .property('disabled', !hasGpx) - .property('checked', showsGpx); - } - - - function drawList(selection, data, type, name, change, active) { - var items = selection.selectAll('li') - .data(data); - - // Exit - items.exit() - .remove(); - - // Enter - var enter = items.enter() - .append('li') - .attr('class', 'layer') - .call(tooltip() - .html(true) - .title(function(d) { - var tip = t(name + '.' + d + '.tooltip'), - key = (d === 'wireframe' ? t('area_fill.wireframe.key') : null); - - if (name === 'feature' && autoHiddenFeature(d)) { - var msg = showsLayer('osm') ? t('map_data.autohidden') : t('map_data.osmhidden'); - tip += '
' + msg + '
'; - } - return uiTooltipHtml(tip, key); + liEnter + .append('button') + .attr('class', 'list-item-gpx-browse') + .call(tooltip() + .title(t('gpx.browse')) + .placement((textDirection === 'rtl') ? 'right' : 'left') + ) + .on('click', function() { + d3_select(document.createElement('input')) + .attr('type', 'file') + .on('change', function() { + gpx.files(d3_event.target.files); }) - .placement('top') - ); + .node().click(); + }) + .call(svgIcon('#icon-geolocate')); - var label = enter - .append('label'); + var labelEnter = liEnter + .append('label') + .call(tooltip() + .title(t('gpx.drag_drop')) + .placement('top') + ); - label - .append('input') - .attr('type', type) - .attr('name', name) - .on('change', change); + labelEnter + .append('input') + .attr('type', 'checkbox') + .on('change', function() { toggleLayer('gpx'); }); - label - .append('span') - .text(function(d) { return t(name + '.' + d + '.description'); }); + labelEnter + .append('span') + .text(t('gpx.local_layer')); - // Update - items = items - .merge(enter); + // Update + ul = ul + .merge(ulEnter); - items - .classed('active', active) - .selectAll('input') - .property('checked', active) - .property('indeterminate', function(d) { - return (name === 'feature' && autoHiddenFeature(d)); - }); + ul.selectAll('.list-item-gpx') + .classed('active', showsGpx) + .selectAll('label') + .classed('deemphasize', !hasGpx) + .selectAll('input') + .property('disabled', !hasGpx) + .property('checked', showsGpx); + } + + + function drawListItems(selection, data, type, name, change, active) { + var items = selection.selectAll('li') + .data(data); + + // Exit + items.exit() + .remove(); + + // Enter + var enter = items.enter() + .append('li') + .attr('class', 'layer') + .call(tooltip() + .html(true) + .title(function(d) { + var tip = t(name + '.' + d + '.tooltip'), + key = (d === 'wireframe' ? t('area_fill.wireframe.key') : null); + + if (name === 'feature' && autoHiddenFeature(d)) { + var msg = showsLayer('osm') ? t('map_data.autohidden') : t('map_data.osmhidden'); + tip += '
' + msg + '
'; + } + return uiTooltipHtml(tip, key); + }) + .placement('top') + ); + + var label = enter + .append('label'); + + label + .append('input') + .attr('type', type) + .attr('name', name) + .on('change', change); + + label + .append('span') + .text(function(d) { return t(name + '.' + d + '.description'); }); + + // Update + items = items + .merge(enter); + + items + .classed('active', active) + .selectAll('input') + .property('checked', active) + .property('indeterminate', function(d) { + return (name === 'feature' && autoHiddenFeature(d)); + }); + } + + + function renderDataLayers(selection) { + var container = selection.selectAll('data-layer-container') + .data([0]); + + _dataLayerContainer = container.enter() + .append('div') + .attr('class', 'data-layer-container') + .merge(container); + } + + + function renderFillList(selection) { + var container = selection.selectAll('layer-fill-list') + .data([0]); + + _fillList = container.enter() + .append('ul') + .attr('class', 'layer-list layer-fill-list') + .merge(container); + } + + + function renderFeatureList(selection) { + var container = selection.selectAll('layer-feature-list') + .data([0]); + + _featureList = container.enter() + .append('ul') + .attr('class', 'layer-list layer-feature-list') + .merge(container); + } + + + function update() { + _dataLayerContainer + .call(drawOsmItem) + .call(drawPhotoItems) + .call(drawGpxItem); + + _fillList + .call(drawListItems, fills, 'radio', 'area_fill', setFill, showsFill); + + _featureList + .call(drawListItems, features, 'checkbox', 'feature', clickFeature, showsFeature); + } + + + function toggleWireframe() { + if (d3_event) { + d3_event.preventDefault(); + d3_event.stopPropagation(); } + setFill((_fillSelected === 'wireframe' ? _fillDefault : 'wireframe')); + context.map().pan([0,0]); // trigger a redraw + } - function update() { - dataLayerContainer - .call(drawOsmItem) - .call(drawPhotoItems) - .call(drawGpxItem); + function mapData(selection) { - fillList - .call(drawList, fills, 'radio', 'area_fill', setFill, showsFill); - - featureList - .call(drawList, features, 'checkbox', 'feature', clickFeature, showsFeature); - } - - - function hidePanel() { + function hidePane() { setVisible(false); } - - function togglePanel() { + function togglePane() { if (d3_event) d3_event.preventDefault(); - tooltipBehavior.hide(button); + paneTooltip.hide(button); setVisible(!button.classed('active')); } - - function toggleWireframe() { - if (d3_event) { - d3_event.preventDefault(); - d3_event.stopPropagation(); - } - setFill((fillSelected === 'wireframe' ? fillDefault : 'wireframe')); - context.map().pan([0,0]); // trigger a redraw - } - - function setVisible(show) { - if (show !== shown) { + if (show !== _shown) { button.classed('active', show); - shown = show; + _shown = show; if (show) { update(); + selection.on('mousedown.map_data-inside', function() { return d3_event.stopPropagation(); }); - content.style('display', 'block') + + pane + .style('display', 'block') .style('right', '-300px') .transition() .duration(200) .style('right', '0px'); + } else { - content.style('display', 'block') + pane + .style('display', 'block') .style('right', '0px') .transition() .duration(200) @@ -386,113 +430,78 @@ export function uiMapData(context) { .on('end', function() { d3_select(this).style('display', 'none'); }); + selection.on('mousedown.map_data-inside', null); } } } - var content = selection - .append('div') - .attr('class', 'fillL map-overlay col3 content hide'), - tooltipBehavior = tooltip() - .placement((textDirection === 'rtl') ? 'right' : 'left') - .html(true) - .title(uiTooltipHtml(t('map_data.description'), key)), - button = selection - .append('button') - .attr('tabindex', -1) - .on('click', togglePanel) - .call(svgIcon('#icon-data', 'light')) - .call(tooltipBehavior), - shown = false; + var pane = selection + .append('div') + .attr('class', 'fillL map-overlay col3 content hide'); - content - .append('h4') + var paneTooltip = tooltip() + .placement((textDirection === 'rtl') ? 'right' : 'left') + .html(true) + .title(uiTooltipHtml(t('map_data.description'), key)); + + var button = selection + .append('button') + .attr('tabindex', -1) + .on('click', togglePane) + .call(svgIcon('#icon-data', 'light')) + .call(paneTooltip); + + + pane + .append('h3') .text(t('map_data.title')); // data layers - content - .append('a') - .text(t('map_data.data_layers')) - .attr('href', '#') - .classed('hide-toggle', true) - .classed('expanded', true) - .on('click', function() { - var exp = d3_select(this).classed('expanded'); - dataLayerContainer.style('display', exp ? 'none' : 'block'); - d3_select(this).classed('expanded', !exp); - d3_event.preventDefault(); - }); - - var dataLayerContainer = content + pane .append('div') - .attr('class', 'data-data-layers') - .style('display', 'block'); - + .attr('class', 'map-data-data-layers') + .call(uiDisclosure(context, 'data_layers', true) + .title(t('map_data.data_layers')) + .content(renderDataLayers) + ); // area fills - content - .append('a') - .text(t('map_data.fill_area')) - .attr('href', '#') - .classed('hide-toggle', true) - .classed('expanded', false) - .on('click', function() { - var exp = d3_select(this).classed('expanded'); - fillContainer.style('display', exp ? 'none' : 'block'); - d3_select(this).classed('expanded', !exp); - d3_event.preventDefault(); - }); - - var fillContainer = content + pane .append('div') - .attr('class', 'data-area-fills') - .style('display', 'none'); - - var fillList = fillContainer - .append('ul') - .attr('class', 'layer-list layer-fill-list'); - + .attr('class', 'map-data-area-fills') + .call(uiDisclosure(context, 'fill_area', false) + .title(t('map_data.fill_area')) + .content(renderFillList) + ); // feature filters - content - .append('a') - .text(t('map_data.map_features')) - .attr('href', '#') - .classed('hide-toggle', true) - .classed('expanded', false) - .on('click', function() { - var exp = d3_select(this).classed('expanded'); - featureContainer.style('display', exp ? 'none' : 'block'); - d3_select(this).classed('expanded', !exp); - d3_event.preventDefault(); - }); - - var featureContainer = content + pane .append('div') - .attr('class', 'data-feature-filters') - .style('display', 'none'); - - var featureList = featureContainer - .append('ul') - .attr('class', 'layer-list layer-feature-list'); + .attr('class', 'map-data-feature-filters') + .call(uiDisclosure(context, 'map_features', false) + .title(t('map_data.map_features')) + .content(renderFeatureList) + ); + // add listeners context.features() .on('change.map_data-update', update); - setFill(fillDefault); + update(); + setFill(_fillDefault); var keybinding = d3_keybinding('features') - .on(key, togglePanel) + .on(key, togglePane) .on(t('area_fill.wireframe.key'), toggleWireframe) - .on([t('background.key'), t('help.key')], hidePanel); + .on([t('background.key'), t('help.key')], hidePane); d3_select(document) .call(keybinding); } - return map_data; + return mapData; }