From b921ca3cb4e777cd0f9f703139335a699688d7ce Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Wed, 19 Feb 2020 11:23:02 -0800 Subject: [PATCH] Move map features and map style options to their own pane section objects (re: #7368) Handle area fill and change highlighting logic in the rendererMap object and issue change events --- modules/renderer/map.js | 48 +++++- modules/ui/panes/map_data.js | 201 ++++------------------- modules/ui/sections/map_features.js | 127 ++++++++++++++ modules/ui/sections/map_style_options.js | 101 ++++++++++++ 4 files changed, 303 insertions(+), 174 deletions(-) create mode 100644 modules/ui/sections/map_features.js create mode 100644 modules/ui/sections/map_style_options.js diff --git a/modules/renderer/map.js b/modules/renderer/map.js index 4db517f5e..cdd2b37f0 100644 --- a/modules/renderer/map.js +++ b/modules/renderer/map.js @@ -30,7 +30,7 @@ function clamp(num, min, max) { export function rendererMap(context) { - var dispatch = d3_dispatch('move', 'drawn', 'crossEditableZoom'); + var dispatch = d3_dispatch('move', 'drawn', 'crossEditableZoom', 'changeHighlighting', 'changeAreaFill'); var projection = context.projection; var curtainProjection = context.curtainProjection; var drawLayers = svgLayers(projection, context); @@ -182,6 +182,9 @@ export function rendererMap(context) { } }); + // must call after surface init + updateAreaFill(); + context.on('enter.map', function() { if (map.editableDataEnabled(true /* skip zoom check */) && !_isTransformed) { // redraw immediately any objects affected by a change in selectedIDs. @@ -980,6 +983,49 @@ export function rendererMap(context) { }; + map.toggleHighlightEdited = function() { + surface.classed('highlight-edited', !surface.classed('highlight-edited')); + map.pan([0,0]); // trigger a redraw + dispatch.call('changeHighlighting', this); + }; + + + map.areaFillOptions = ['wireframe', 'partial', 'full']; + + map.activeAreaFill = function(val) { + if (!arguments.length) return context.storage('area-fill') || 'partial'; + + context.storage('area-fill', val); + if (val !== 'wireframe') { + context.storage('area-fill-toggle', val); + } + updateAreaFill(); + map.pan([0,0]); // trigger a redraw + dispatch.call('changeAreaFill', this); + return map; + }; + + map.toggleWireframe = function() { + + var activeFill = map.activeAreaFill(); + + if (activeFill === 'wireframe') { + activeFill = context.storage('area-fill-toggle') || 'partial'; + } else { + activeFill = 'wireframe'; + } + + map.activeAreaFill(activeFill); + }; + + function updateAreaFill() { + var activeFill = map.activeAreaFill(); + map.areaFillOptions.forEach(function(opt) { + surface.classed('fill-' + opt, Boolean(opt === activeFill)); + }); + } + + map.layers = drawLayers; diff --git a/modules/ui/panes/map_data.js b/modules/ui/panes/map_data.js index 15dd49eec..0649a5f18 100644 --- a/modules/ui/panes/map_data.js +++ b/modules/ui/panes/map_data.js @@ -14,29 +14,22 @@ import { uiTooltipHtml } from '../tooltipHtml'; import { uiCmd } from '../cmd'; import { uiPane } from '../pane'; +import { uiSectionMapFeatures } from '../sections/map_features'; +import { uiSectionMapStyleOptions } from '../sections/map_style_options'; export function uiPaneMapData(context) { var osmDataToggleKey = uiCmd('⌥' + t('area_fill.wireframe.key')); - var features = context.features().keys(); var layers = context.layers(); - var fills = ['wireframe', 'partial', 'full']; var settingsCustomData = uiSettingsCustomData(context) .on('change', customChanged); - var _fillSelected = context.storage('area-fill') || 'partial'; var _dataLayerContainer = d3_select(null); var _photoOverlayContainer = d3_select(null); - var _fillList = d3_select(null); - var _featureList = d3_select(null); - var _visualDiffList = d3_select(null); var _QAList = d3_select(null); - - function showsFeature(d) { - return context.features().enabled(d); - } - + var _mapStyleOptionsSection = uiSectionMapStyleOptions(context); + var _mapFeaturesSection = uiSectionMapFeatures(context); function autoHiddenFeature(d) { if (d.type === 'kr_error') return context.errors().autoHidden(d); @@ -44,12 +37,6 @@ export function uiPaneMapData(context) { } - function clickFeature(d) { - context.features().toggle(d); - update(); - } - - function showsQA(d) { var QAKeys = [d]; var QALayers = layers.all().filter(function(obj) { return QAKeys.indexOf(obj.id) !== -1; }); @@ -66,32 +53,9 @@ export function uiPaneMapData(context) { } - function showsFill(d) { - return _fillSelected === d; - } - - - function setFill(d) { - fills.forEach(function(opt) { - context.surface().classed('fill-' + opt, Boolean(opt === d)); - }); - - _fillSelected = d; - context.storage('area-fill', d); - if (d !== 'wireframe') { - context.storage('area-fill-toggle', d); - } - update(); - } - - function toggleHighlightEdited() { d3_event.preventDefault(); - var surface = context.surface(); - surface.classed('highlight-edited', !surface.classed('highlight-edited')); - updateVisualDiffList(); - - context.map().pan([0,0]); // trigger a redraw + context.map().toggleHighlightEdited(); } @@ -610,14 +574,11 @@ export function uiPaneMapData(context) { .html(true) .title(function(d) { var tip = t(name + '.' + d + '.tooltip'); - var key = (d === 'wireframe' ? t('area_fill.wireframe.key') : null); - if (d === 'highlight_edits') key = t('map_data.highlight_edits.key'); - - if ((name === 'feature' || name === 'keepRight') && autoHiddenFeature(d)) { + if (name === 'keepRight' && autoHiddenFeature(d)) { var msg = showsLayer('osm') ? t('map_data.autohidden') : t('map_data.osmhidden'); tip += '
' + msg + '
'; } - return uiTooltipHtml(tip, key); + return uiTooltipHtml(tip); }) .placement('top') ); @@ -644,7 +605,7 @@ export function uiPaneMapData(context) { .selectAll('input') .property('checked', active) .property('indeterminate', function(d) { - return ((name === 'feature' || name === 'keepRight') && autoHiddenFeature(d)); + return name === 'keepRight' && autoHiddenFeature(d); }); } @@ -673,73 +634,6 @@ export function uiPaneMapData(context) { updatePhotoOverlays(); } - - function renderStyleOptions(selection) { - var container = selection.selectAll('.layer-fill-list') - .data([0]); - - _fillList = container.enter() - .append('ul') - .attr('class', 'layer-list layer-fill-list') - .merge(container); - - updateFillList(); - - var container2 = selection.selectAll('.layer-visual-diff-list') - .data([0]); - - _visualDiffList = container2.enter() - .append('ul') - .attr('class', 'layer-list layer-visual-diff-list') - .merge(container2); - - updateVisualDiffList(); - } - - - function renderFeatureList(selection) { - var container = selection.selectAll('.layer-feature-list-container') - .data([0]); - - var containerEnter = container.enter() - .append('div') - .attr('class', 'layer-feature-list-container'); - - containerEnter - .append('ul') - .attr('class', 'layer-list layer-feature-list'); - - var footer = containerEnter - .append('div') - .attr('class', 'feature-list-links section-footer'); - - footer - .append('a') - .attr('class', 'feature-list-link') - .attr('href', '#') - .text(t('issues.enable_all')) - .on('click', function() { - context.features().enableAll(); - }); - - footer - .append('a') - .attr('class', 'feature-list-link') - .attr('href', '#') - .text(t('issues.disable_all')) - .on('click', function() { - context.features().disableAll(); - }); - - // Update - container = container - .merge(containerEnter); - - _featureList = container.selectAll('.layer-feature-list'); - - updateFeatureList(); - } - function updatePhotoOverlays() { _photoOverlayContainer .call(drawPhotoItems) @@ -754,40 +648,23 @@ export function uiPaneMapData(context) { .call(drawVectorItems); // Beta - Detroit mapping challenge } - function updateFillList() { - _fillList - .call(drawListItems, fills, 'radio', 'area_fill', setFill, showsFill); - } - - function updateVisualDiffList() { - _visualDiffList - .call(drawListItems, ['highlight_edits'], 'checkbox', 'visual_diff', toggleHighlightEdited, function() { - return context.surface().classed('highlight-edited'); - }); - } - - function updateFeatureList() { - _featureList - .call(drawListItems, features, 'checkbox', 'feature', clickFeature, showsFeature); - } - function update() { if (!mapDataPane.selection().select('.disclosure-wrap-data_layers').classed('hide')) { updateDataLayers(); + + _QAList + .call(drawListItems, ['keep-right'], 'checkbox', 'QA', function(d) { toggleLayer(d); }, showsQA); } if (!mapDataPane.selection().select('.disclosure-wrap-photo_overlays').classed('hide')) { updatePhotoOverlays(); } - if (!mapDataPane.selection().select('.disclosure-wrap-fill_area').classed('hide')) { - updateFillList(); - } - if (!mapDataPane.selection().select('.disclosure-wrap-map_features').classed('hide')) { - updateFeatureList(); - } - _QAList - .call(drawListItems, ['keep-right'], 'checkbox', 'QA', function(d) { toggleLayer(d); }, showsQA); + mapDataPane.selection().select('.map-data-area-fills') + .call(_mapStyleOptionsSection.render); + + mapDataPane.selection().select('.map-data-feature-filters') + .call(_mapFeaturesSection.render); } @@ -796,15 +673,7 @@ export function uiPaneMapData(context) { d3_event.preventDefault(); d3_event.stopPropagation(); } - - if (_fillSelected === 'wireframe') { - _fillSelected = context.storage('area-fill-toggle') || 'partial'; - } else { - _fillSelected = 'wireframe'; - } - - setFill(_fillSelected); - context.map().pan([0,0]); // trigger a redraw + context.map().toggleWireframe(); } var mapDataPane = uiPane('map-data', context) @@ -836,38 +705,24 @@ export function uiPaneMapData(context) { // area fills content .append('div') - .attr('class', 'map-data-area-fills') - .call(uiDisclosure(context, 'fill_area', false) - .title(t('map_data.style_options')) - .content(renderStyleOptions) - ); + .attr('class', 'map-data-area-fills'); // feature filters content .append('div') - .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); + .attr('class', 'map-data-feature-filters'); update(); - setFill(_fillSelected); - - context.keybinding() - .on(t('area_fill.wireframe.key'), toggleWireframe) - .on(osmDataToggleKey, function() { - d3_event.preventDefault(); - d3_event.stopPropagation(); - toggleLayer('osm'); - }) - .on(t('map_data.highlight_edits.key'), toggleHighlightEdited); }; + context.keybinding() + .on(t('area_fill.wireframe.key'), toggleWireframe) + .on(osmDataToggleKey, function() { + d3_event.preventDefault(); + d3_event.stopPropagation(); + toggleLayer('osm'); + }) + .on(t('map_data.highlight_edits.key'), toggleHighlightEdited); + return mapDataPane; } diff --git a/modules/ui/sections/map_features.js b/modules/ui/sections/map_features.js new file mode 100644 index 000000000..54e2c27f0 --- /dev/null +++ b/modules/ui/sections/map_features.js @@ -0,0 +1,127 @@ +import { t } from '../../util/locale'; +import { tooltip } from '../../util/tooltip'; +import { uiSection } from '../section'; +import { uiTooltipHtml } from '../tooltipHtml'; + +export function uiSectionMapFeatures(context) { + + var _features = context.features().keys(); + + var section = uiSection('map-features', context) + .title(t('map_data.map_features')) + .expandedByDefault(false); + + section.renderDisclosureContent = function(selection) { + + var container = selection.selectAll('.layer-feature-list-container') + .data([0]); + + var containerEnter = container.enter() + .append('div') + .attr('class', 'layer-feature-list-container'); + + containerEnter + .append('ul') + .attr('class', 'layer-list layer-feature-list'); + + var footer = containerEnter + .append('div') + .attr('class', 'feature-list-links section-footer'); + + footer + .append('a') + .attr('class', 'feature-list-link') + .attr('href', '#') + .text(t('issues.enable_all')) + .on('click', function() { + context.features().enableAll(); + }); + + footer + .append('a') + .attr('class', 'feature-list-link') + .attr('href', '#') + .text(t('issues.disable_all')) + .on('click', function() { + context.features().disableAll(); + }); + + // Update + container = container + .merge(containerEnter); + + container.selectAll('.layer-feature-list') + .call(drawListItems, _features, 'checkbox', 'feature', clickFeature, showsFeature); + }; + + 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') + .call(tooltip() + .html(true) + .title(function(d) { + var tip = t(name + '.' + d + '.tooltip'); + if (autoHiddenFeature(d)) { + var msg = showsLayer('osm') ? t('map_data.autohidden') : t('map_data.osmhidden'); + tip += '
' + msg + '
'; + } + return uiTooltipHtml(tip); + }) + .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', autoHiddenFeature); + } + + function autoHiddenFeature(d) { + return context.features().autoHidden(d); + } + + function showsFeature(d) { + return context.features().enabled(d); + } + + function clickFeature(d) { + context.features().toggle(d); + } + + function showsLayer(id) { + var layer = context.layers().layer(id); + return layer && layer.enabled(); + } + + // add listeners + context.features() + .on('change.map_features', section.rerenderContent); + + return section; +} diff --git a/modules/ui/sections/map_style_options.js b/modules/ui/sections/map_style_options.js new file mode 100644 index 000000000..5dadb75f1 --- /dev/null +++ b/modules/ui/sections/map_style_options.js @@ -0,0 +1,101 @@ +import { + event as d3_event +} from 'd3-selection'; + +import { t } from '../../util/locale'; +import { tooltip } from '../../util/tooltip'; +import { uiSection } from '../section'; +import { uiTooltipHtml } from '../tooltipHtml'; + +export function uiSectionMapStyleOptions(context) { + + var section = uiSection('fill-area', context) + .title(t('map_data.style_options')) + .expandedByDefault(false); + + section.renderDisclosureContent = function(selection) { + var container = selection.selectAll('.layer-fill-list') + .data([0]); + + container.enter() + .append('ul') + .attr('class', 'layer-list layer-fill-list') + .merge(container) + .call(drawListItems, context.map().areaFillOptions, 'radio', 'area_fill', setFill, isActiveFill); + + var container2 = selection.selectAll('.layer-visual-diff-list') + .data([0]); + + container2.enter() + .append('ul') + .attr('class', 'layer-list layer-visual-diff-list') + .merge(container2) + .call(drawListItems, ['highlight_edits'], 'checkbox', 'visual_diff', toggleHighlightEdited, function() { + return context.surface().classed('highlight-edited'); + }); + }; + + 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') + .call(tooltip() + .html(true) + .title(function(d) { + var tip = t(name + '.' + d + '.tooltip'); + var key = (d === 'wireframe' ? t('area_fill.wireframe.key') : null); + if (d === 'highlight_edits') key = t('map_data.highlight_edits.key'); + 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', false); + } + + function isActiveFill(d) { + return context.map().activeAreaFill() === d; + } + + function toggleHighlightEdited() { + d3_event.preventDefault(); + context.map().toggleHighlightEdited(); + } + + function setFill(d) { + context.map().activeAreaFill(d); + } + + context.map() + .on('changeHighlighting.ui_style, changeAreaFill.ui_style', section.rerenderContent); + + return section; +}