From 5c04ad3eb526d27b466baca192e76ce7a1cd0c74 Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Tue, 18 Feb 2020 20:02:55 -0800 Subject: [PATCH] Add uiSection class as standard component for pane sections Move validation rules and privacy preferences to their own section objects --- modules/ui/pane.js | 17 +- modules/ui/panes/background.js | 84 ++------ modules/ui/panes/issues.js | 204 ++---------------- modules/ui/panes/preferences.js | 79 +------ modules/ui/section.js | 75 +++++++ .../ui/sections/background_display_options.js | 52 ++--- modules/ui/sections/background_list.js | 40 ++-- modules/ui/sections/background_offset.js | 27 +-- modules/ui/sections/overlay_list.js | 50 ++--- modules/ui/sections/privacy.js | 74 +++++++ modules/ui/sections/validation_rules.js | 183 ++++++++++++++++ 11 files changed, 454 insertions(+), 431 deletions(-) create mode 100644 modules/ui/section.js create mode 100644 modules/ui/sections/privacy.js create mode 100644 modules/ui/sections/validation_rules.js diff --git a/modules/ui/pane.js b/modules/ui/pane.js index 1d221ba59..c96caa105 100644 --- a/modules/ui/pane.js +++ b/modules/ui/pane.js @@ -15,6 +15,7 @@ export function uiPane(id, context) { var _title = ''; var _description = ''; var _iconName = ''; + var _sections; // array of uiSection objects var _paneSelection = d3_select(null); @@ -48,6 +49,12 @@ export function uiPane(id, context) { return pane; }; + pane.sections = function(val) { + if (!arguments.length) return _sections; + _sections = val; + return pane; + }; + pane.selection = function() { return _paneSelection; }; @@ -78,8 +85,14 @@ export function uiPane(id, context) { .call(_paneTooltip); }; - pane.renderContent = function() { - // override + pane.renderContent = function(selection) { + // override to fully customize content + + if (_sections) { + _sections.forEach(function(section) { + selection.call(section.render); + }); + } }; pane.renderPane = function(selection) { diff --git a/modules/ui/panes/background.js b/modules/ui/panes/background.js index 88d4eba13..ddc894c86 100644 --- a/modules/ui/panes/background.js +++ b/modules/ui/panes/background.js @@ -1,6 +1,4 @@ -import _debounce from 'lodash-es/debounce'; - -import { event as d3_event, select as d3_select } from 'd3-selection'; +import { event as d3_event } from 'd3-selection'; import { t } from '../../util/locale'; import { uiCmd } from '../cmd'; @@ -13,31 +11,8 @@ import { uiOverlayList } from '../sections/overlay_list'; export function uiPaneBackground(context) { - var _key = t('background.key'); - - 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 overlayList = uiOverlayList(context); - - function update() { - _backgroundListContainer - .call(backgroundList); - - _overlayListContainer - .call(overlayList); - - _displayOptionsContainer - .call(backgroundDisplayOptions); - - _offsetContainer - .call(backgroundOffset); - } + context.keybinding() + .on(uiCmd('⌘' + t('background.key')), quickSwitch); function quickSwitch() { if (d3_event) { @@ -46,57 +21,24 @@ export function uiPaneBackground(context) { } 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); + var currentBackground = context.background().baseLayerSource(); + context.storage('background-last-used-toggle', currentBackground.id); context.storage('background-last-used', previousBackground.id); context.background().baseLayerSource(previousBackground); - document.activeElement.blur(); } } var backgroundPane = uiPane('background', context) - .key(_key) + .key(t('background.key')) .title(t('background.title')) .description(t('background.description')) - .iconName('iD-icon-layers'); - - backgroundPane.renderContent = function(content) { - - // background list - _backgroundListContainer = content - .append('div') - .attr('class', 'background-background-list-container'); - - // overlay list - _overlayListContainer = content - .append('div') - .attr('class', 'background-overlay-list-container'); - - // display options - _displayOptionsContainer = content - .append('div') - .attr('class', 'background-display-options'); - - // offset controls - _offsetContainer = content - .append('div') - .attr('class', 'background-offset'); - - update(); - - - // add listeners - context.map() - .on('move.background-update', - _debounce(function() { window.requestIdleCallback(update); }, 1000) - ); - - context.background() - .on('change.background-update', update); - - context.keybinding() - .on(uiCmd('⌘' + _key), quickSwitch); - }; + .iconName('iD-icon-layers') + .sections([ + uiBackgroundList(context), + uiOverlayList(context), + uiBackgroundDisplayOptions(context), + uiBackgroundOffset(context) + ]); return backgroundPane; } diff --git a/modules/ui/panes/issues.js b/modules/ui/panes/issues.js index ad0e2885e..9088697fd 100644 --- a/modules/ui/panes/issues.js +++ b/modules/ui/panes/issues.js @@ -3,25 +3,24 @@ import _debounce from 'lodash-es/debounce'; import { event as d3_event, select as d3_select } from 'd3-selection'; import { t } from '../../util/locale'; -import { tooltip } from '../../util/tooltip'; //import { actionNoop } from '../actions/noop'; import { geoSphericalDistance } from '../../geo'; import { svgIcon } from '../../svg/icon'; import { uiDisclosure } from '../disclosure'; -import { utilGetSetValue, utilHighlightEntities, utilNoAuto } from '../../util'; +import { utilHighlightEntities } from '../../util'; import { uiPane } from '../pane'; +import { uiValidationRules } from '../sections/validation_rules'; export function uiPaneIssues(context) { - var MINSQUARE = 0; - var MAXSQUARE = 20; - var DEFAULTSQUARE = 5; // see also unsquare_way.js - var _errorsSelection = d3_select(null); var _warningsSelection = d3_select(null); - var _rulesList = d3_select(null); + + var _rulesListContainer = d3_select(null); + + var _validationRules = uiValidationRules(context); var _errors = []; var _warnings = []; @@ -313,69 +312,6 @@ export function uiPaneIssues(context) { }); } - - function renderRulesList(selection) { - var container = selection.selectAll('.issues-rulelist-container') - .data([0]); - - var containerEnter = container.enter() - .append('div') - .attr('class', 'issues-rulelist-container'); - - containerEnter - .append('ul') - .attr('class', 'layer-list issue-rules-list'); - - var ruleLinks = containerEnter - .append('div') - .attr('class', 'issue-rules-links section-footer'); - - ruleLinks - .append('a') - .attr('class', 'issue-rules-link') - .attr('href', '#') - .text(t('issues.enable_all')) - .on('click', function() { - context.validator().disableRules([]); - }); - - ruleLinks - .append('a') - .attr('class', 'issue-rules-link') - .attr('href', '#') - .text(t('issues.disable_all')) - .on('click', function() { - var keys = context.validator().getRuleKeys(); - context.validator().disableRules(keys); - }); - - - // Update - container = container - .merge(containerEnter); - - _rulesList = container.selectAll('.issue-rules-list'); - - updateRulesList(); - } - - - function updateRulesList() { - var ruleKeys = context.validator().getRuleKeys(); - _rulesList - .call(drawListItems, ruleKeys, 'checkbox', 'rule', toggleRule, isRuleEnabled); - } - - - function isRuleEnabled(d) { - return context.validator().isRuleEnabled(d); - } - - - function toggleRule(d) { - context.validator().toggleRule(d); - } - function setNoIssuesText() { function checkForHiddenIssues(cases) { @@ -502,10 +438,8 @@ export function uiPaneIssues(context) { setNoIssuesText(); } - if (!issuesPane.selection().select('.disclosure-wrap-issues_rules').classed('hide')) { - updateRulesList(); - } - + _rulesListContainer + .call(_validationRules.render); function byDistance(a, b) { return a.dist - b.dist; @@ -519,116 +453,6 @@ export function uiPaneIssues(context) { } - 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'); - - if (name === 'rule') { - enter - .call(tooltip() - .title(function(d) { return t('issues.' + d + '.tip'); }) - .placement('top') - ); - } - - var label = enter - .append('label'); - - label - .append('input') - .attr('type', type) - .attr('name', name) - .on('change', change); - - label - .append('span') - .html(function(d) { - var params = {}; - if (d === 'unsquare_way') { - params.val = ''; - } - return t('issues.' + d + '.title', params); - }); - - // Update - items = items - .merge(enter); - - items - .classed('active', active) - .selectAll('input') - .property('checked', active) - .property('indeterminate', false); - - - // user-configurable square threshold - var degStr = context.storage('validate-square-degrees'); - if (degStr === null) { - degStr = '' + DEFAULTSQUARE; - } - - var span = items.selectAll('.square-degrees'); - var input = span.selectAll('.square-degrees-input') - .data([0]); - - // enter / update - input.enter() - .append('input') - .attr('type', 'number') - .attr('min', '' + MINSQUARE) - .attr('max', '' + MAXSQUARE) - .attr('step', '0.5') - .attr('class', 'square-degrees-input') - .call(utilNoAuto) - .on('click', function () { - d3_event.preventDefault(); - d3_event.stopPropagation(); - this.select(); - }) - .on('keyup', function () { - if (d3_event.keyCode === 13) { // enter - this.blur(); - this.select(); - } - }) - .on('blur', changeSquare) - .merge(input) - .property('value', degStr); - } - - - function changeSquare() { - var input = d3_select(this); - var degStr = utilGetSetValue(input).trim(); - var degNum = parseFloat(degStr, 10); - - if (!isFinite(degNum)) { - degNum = DEFAULTSQUARE; - } else if (degNum > MAXSQUARE) { - degNum = MAXSQUARE; - } else if (degNum < MINSQUARE) { - degNum = MINSQUARE; - } - - degNum = Math.round(degNum * 10 ) / 10; // round to 1 decimal - degStr = '' + degNum; - - input - .property('value', degStr); - - context.storage('validate-square-degrees', degStr); - context.validator().reloadUnsquareIssues(); - } - - var issuesPane = uiPane('issues', context) .key(t('issues.key')) .title(t('issues.title')) @@ -664,16 +488,12 @@ export function uiPaneIssues(context) { .content(renderWarningsList) ); - // rules - content + // rules list + _rulesListContainer = content .append('div') - .attr('class', 'issues-rules') - .call(uiDisclosure(context, 'issues_rules', false) - .title(t('issues.rules.title')) - .content(renderRulesList) - ); + .attr('class', 'issues-rules'); - // update(); + update(); }; return issuesPane; diff --git a/modules/ui/panes/preferences.js b/modules/ui/panes/preferences.js index af16b0fe9..907a6c374 100644 --- a/modules/ui/panes/preferences.js +++ b/modules/ui/panes/preferences.js @@ -1,87 +1,18 @@ -import { event as d3_event } from 'd3-selection'; -import { svgIcon } from '../../svg/icon'; import { t } from '../../util/locale'; -import { tooltip } from '../../util/tooltip'; -import { uiDisclosure } from '../disclosure'; import { uiPane } from '../pane'; - +import { uiSectionPrivacy } from '../sections/privacy'; export function uiPanePreferences(context) { - let _showThirdPartyIcons = context.storage('preferences.privacy.thirdpartyicons') || 'true'; - - function renderPrivacyOptions(selection) { - // enter - let privacyOptionsListEnter = selection.selectAll('.privacy-options-list') - .data([0]) - .enter() - .append('ul') - .attr('class', 'layer-list privacy-options-list'); - - let thirdPartyIconsEnter = privacyOptionsListEnter - .append('li') - .attr('class', 'privacy-third-party-icons-item') - .append('label') - .call(tooltip() - .title(t('preferences.privacy.third_party_icons.tooltip')) - .placement('bottom') - ); - - thirdPartyIconsEnter - .append('input') - .attr('type', 'checkbox') - .on('change', () => { - d3_event.preventDefault(); - _showThirdPartyIcons = (_showThirdPartyIcons === 'true') ? 'false' : 'true'; - context.storage('preferences.privacy.thirdpartyicons', _showThirdPartyIcons); - update(); - }); - - thirdPartyIconsEnter - .append('span') - .text(t('preferences.privacy.third_party_icons.description')); - - - // Privacy Policy link - selection.selectAll('.privacy-link') - .data([0]) - .enter() - .append('div') - .attr('class', 'privacy-link') - .append('a') - .attr('target', '_blank') - .call(svgIcon('#iD-icon-out-link', 'inline')) - .attr('href', 'https://github.com/openstreetmap/iD/blob/master/PRIVACY.md') - .append('span') - .text(t('preferences.privacy.privacy_link')); - - update(); - - - function update() { - selection.selectAll('.privacy-third-party-icons-item') - .classed('active', (_showThirdPartyIcons === 'true')) - .select('input') - .property('checked', (_showThirdPartyIcons === 'true')); - } - } let preferencesPane = uiPane('preferences', context) .key(t('preferences.key')) .title(t('preferences.title')) .description(t('preferences.description')) - .iconName('fas-user-cog'); - - preferencesPane.renderContent = (content) => { - - content - .append('div') - .attr('class', 'preferences-privacy') - .call(uiDisclosure(context, 'preferences_third_party', true) - .title(t('preferences.privacy.title')) - .content(renderPrivacyOptions) - ); - }; + .iconName('fas-user-cog') + .sections([ + uiSectionPrivacy(context) + ]); return preferencesPane; } diff --git a/modules/ui/section.js b/modules/ui/section.js new file mode 100644 index 000000000..a5f7372f4 --- /dev/null +++ b/modules/ui/section.js @@ -0,0 +1,75 @@ +import { + select as d3_select +} from 'd3-selection'; + +import { uiDisclosure } from './disclosure'; + +// A unit of controls or info to be used in a layout, such as within a pane. +// Can be labeled and collapsible. +export function uiSection(id, context) { + + var _disclosure; + var _title; + var _expandedByDefault = true; + + var _containerSelection = d3_select(null); + + var section = { + id: id + }; + + section.title = function(val) { + if (!arguments.length) return _title; + _title = val; + return section; + }; + + section.expandedByDefault = function(val) { + if (!arguments.length) return _expandedByDefault; + _expandedByDefault = val; + return section; + }; + + // may be called multiple times + section.render = function(selection) { + + _containerSelection = selection + .selectAll('.section-' + id) + .data([0]); + + var sectionEnter = _containerSelection + .enter() + .append('div') + .attr('class', 'section section-' + id); + + _containerSelection = sectionEnter + .merge(_containerSelection); + + _containerSelection + .call(section.renderContent); + }; + + // may be called multiple times + section.renderContent = function(containerSelection) { + + if (section.renderDisclosureContent && _title) { + if (!_disclosure) { + _disclosure = uiDisclosure(context, id.replace(/-/g, '_'), _expandedByDefault) + .title(_title) + .content(section.renderDisclosureContent); + } + containerSelection + .call(_disclosure); + } + }; + + // override to enable disclosure + section.renderDisclosureContent = undefined; + + section.rerenderContent = function() { + _containerSelection + .call(section.renderContent); + }; + + return section; +} diff --git a/modules/ui/sections/background_display_options.js b/modules/ui/sections/background_display_options.js index 9632dcb4c..7113caedc 100644 --- a/modules/ui/sections/background_display_options.js +++ b/modules/ui/sections/background_display_options.js @@ -5,29 +5,31 @@ import { import { t, textDirection } from '../../util/locale'; import { svgIcon } from '../../svg/icon'; -import { uiDisclosure } from '../disclosure'; +import { uiSection } from '../section'; import { utilDetect } from '../../util/detect'; export function uiBackgroundDisplayOptions(context) { - var detected = utilDetect(); - var storedOpacity = context.storage('background-opacity'); - var minVal = 0.25; - var maxVal = detected.cssfilters ? 2 : 1; - var sliders = detected.cssfilters + var section = uiSection('background-display-options', context) + .title(t('background.display_options')); + + var _detected = utilDetect(); + var _storedOpacity = context.storage('background-opacity'); + var _minVal = 0.25; + var _maxVal = _detected.cssfilters ? 2 : 1; + + var _sliders = _detected.cssfilters ? ['brightness', 'contrast', 'saturation', 'sharpness'] : ['brightness']; var _options = { - brightness: (storedOpacity !== null ? (+storedOpacity) : 1), + brightness: (_storedOpacity !== null ? (+_storedOpacity) : 1), contrast: 1, saturation: 1, sharpness: 1 }; - var _selection = d3_select(null); - function clamp(x, min, max) { return Math.max(min, Math.min(x, max)); @@ -39,7 +41,7 @@ export function uiBackgroundDisplayOptions(context) { val = d3_event.target.value; } - val = clamp(val, minVal, maxVal); + val = clamp(val, _minVal, _maxVal); _options[d] = val; context.background()[d](val); @@ -48,12 +50,11 @@ export function uiBackgroundDisplayOptions(context) { context.storage('background-opacity', val); } - _selection - .call(render); + section.rerenderContent(); } - function render(selection) { + section.renderDisclosureContent = function(selection) { var container = selection.selectAll('.display-options-container') .data([0]); @@ -63,7 +64,7 @@ export function uiBackgroundDisplayOptions(context) { // add slider controls var slidersEnter = containerEnter.selectAll('.display-control') - .data(sliders) + .data(_sliders) .enter() .append('div') .attr('class', function(d) { return 'display-control display-control-' + d; }); @@ -78,8 +79,8 @@ export function uiBackgroundDisplayOptions(context) { .append('input') .attr('class', function(d) { return 'display-option-input display-option-input-' + d; }) .attr('type', 'range') - .attr('min', minVal) - .attr('max', maxVal) + .attr('min', _minVal) + .attr('max', _maxVal) .attr('step', '0.05') .on('input', function(d) { var val = d3_select(this).property('value'); @@ -103,8 +104,8 @@ export function uiBackgroundDisplayOptions(context) { .attr('href', '#') .text(t('background.reset_all')) .on('click', function() { - for (var i = 0; i < sliders.length; i++) { - updateValue(sliders[i],1); + for (var i = 0; i < _sliders.length; i++) { + updateValue(_sliders[i],1); } }); @@ -125,19 +126,8 @@ export function uiBackgroundDisplayOptions(context) { if (containerEnter.size() && _options.brightness !== 1) { context.background().brightness(_options.brightness); } - } + }; - function backgroundDisplayOptions(selection) { - _selection = selection; - - selection - .call(uiDisclosure(context, 'background_display_options', true) - .title(t('background.display_options')) - .content(render) - ); - } - - - return backgroundDisplayOptions; + return section; } diff --git a/modules/ui/sections/background_list.js b/modules/ui/sections/background_list.js index 49c5265a5..20c9ed340 100644 --- a/modules/ui/sections/background_list.js +++ b/modules/ui/sections/background_list.js @@ -1,3 +1,4 @@ +import _debounce from 'lodash-es/debounce'; import { descending as d3_descending, ascending as d3_ascending } from 'd3-array'; import { event as d3_event, @@ -8,9 +9,9 @@ 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 { uiSection } from '../section'; import { uiTooltipHtml } from '../tooltipHtml'; export function uiBackgroundList(context) { @@ -19,14 +20,17 @@ export function uiBackgroundList(context) { var _customSource = context.background().findSource('custom'); - var settingsCustomBackground = uiSettingsCustomBackground(context) + var _settingsCustomBackground = uiSettingsCustomBackground(context) .on('change', customChanged); + var section = uiSection('background-list', context) + .title(t('background.backgrounds')); + function previousBackgroundID() { return context.storage('background-last-used-toggle'); } - function render(selection) { + section.renderDisclosureContent = function(selection) { // the background list var container = selection.selectAll('.layer-background-list') @@ -105,8 +109,9 @@ export function uiBackgroundList(context) { .append('span') .text(t('background.imagery_problem_faq')); - updateBackgroundList(); - } + _backgroundList + .call(drawListItems, 'radio', chooseBackground, function(d) { return !d.isHidden() && !d.overlay; }); + }; function setTooltips(selection) { selection.each(function(d, i, nodes) { @@ -136,11 +141,6 @@ export function uiBackgroundList(context) { }); } - 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) @@ -247,22 +247,22 @@ export function uiBackgroundList(context) { function editCustom() { d3_event.preventDefault(); context.container() - .call(settingsCustomBackground); + .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; + context.map() + .on('move.background_list', + _debounce(function() { + // layers in-view may have changed due to map move + window.requestIdleCallback(section.rerenderContent); + }, 1000) + ); + + return section; } diff --git a/modules/ui/sections/background_offset.js b/modules/ui/sections/background_offset.js index 407626576..4bbcac373 100644 --- a/modules/ui/sections/background_offset.js +++ b/modules/ui/sections/background_offset.js @@ -7,11 +7,16 @@ import { import { t, textDirection } from '../../util/locale'; import { geoMetersToOffset, geoOffsetToMeters } from '../../geo'; import { svgIcon } from '../../svg/icon'; -import { uiDisclosure } from '../disclosure'; +import { uiSection } from '../section'; export function uiBackgroundOffset(context) { - var directions = [ + + var section = uiSection('background-offset', context) + .title(t('background.fix_misalignment')) + .expandedByDefault(false); + + var _directions = [ ['right', [0.5, 0]], ['top', [0, -0.5]], ['left', [-0.5, 0]], @@ -129,7 +134,7 @@ export function uiBackgroundOffset(context) { } - function render(selection) { + section.renderDisclosureContent = function(selection) { var container = selection.selectAll('.nudge-container') .data([0]); @@ -156,7 +161,7 @@ export function uiBackgroundOffset(context) { containerEnter .append('div') .selectAll('button') - .data(directions).enter() + .data(_directions).enter() .append('button') .attr('class', function(d) { return d[0] + ' nudge'; }) .on('contextmenu', d3_eventCancel) @@ -177,20 +182,10 @@ export function uiBackgroundOffset(context) { .call(svgIcon('#iD-icon-' + (textDirection === 'rtl' ? 'redo' : 'undo'))); updateValue(); - } - - - function backgroundOffset(selection) { - selection - .call(uiDisclosure(context, 'background_offset', false) - .title(t('background.fix_misalignment')) - .content(render) - ); - } - + }; context.background() .on('change.backgroundOffset-update', updateValue); - return backgroundOffset; + return section; } diff --git a/modules/ui/sections/overlay_list.js b/modules/ui/sections/overlay_list.js index 757d5ebdc..f8797dce0 100644 --- a/modules/ui/sections/overlay_list.js +++ b/modules/ui/sections/overlay_list.js @@ -1,3 +1,4 @@ +import _debounce from 'lodash-es/debounce'; import { descending as d3_descending, ascending as d3_ascending } from 'd3-array'; import { event as d3_event, @@ -6,26 +7,15 @@ import { import { t } from '../../util/locale'; import { tooltip } from '../../util/tooltip'; -import { uiDisclosure } from '../disclosure'; +import { uiSection } from '../section'; export function uiOverlayList(context) { + var section = uiSection('overlay-list', context) + .title(t('background.overlays')); + 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'); @@ -107,18 +97,28 @@ export function uiOverlayList(context) { } } - function updateOverlayList() { + section.renderDisclosureContent = function(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); + _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) - ); - } + context.map() + .on('move.overlay_list', + _debounce(function() { + // layers in-view may have changed due to map move + window.requestIdleCallback(section.rerenderContent); + }, 1000) + ); - return overlayList; + return section; } diff --git a/modules/ui/sections/privacy.js b/modules/ui/sections/privacy.js new file mode 100644 index 000000000..ffb168f50 --- /dev/null +++ b/modules/ui/sections/privacy.js @@ -0,0 +1,74 @@ +import { + event as d3_event +} from 'd3-selection'; + +import { t } from '../../util/locale'; +import { tooltip } from '../../util/tooltip'; +import { svgIcon } from '../../svg/icon'; +import { uiSection } from '../section'; + +export function uiSectionPrivacy(context) { + + let section = uiSection('preferences-third-party', context) + .title(t('preferences.privacy.title')); + + let _showThirdPartyIcons = context.storage('preferences.privacy.thirdpartyicons') || 'true'; + + section.renderDisclosureContent = function(selection) { + // enter + let privacyOptionsListEnter = selection.selectAll('.privacy-options-list') + .data([0]) + .enter() + .append('ul') + .attr('class', 'layer-list privacy-options-list'); + + let thirdPartyIconsEnter = privacyOptionsListEnter + .append('li') + .attr('class', 'privacy-third-party-icons-item') + .append('label') + .call(tooltip() + .title(t('preferences.privacy.third_party_icons.tooltip')) + .placement('bottom') + ); + + thirdPartyIconsEnter + .append('input') + .attr('type', 'checkbox') + .on('change', () => { + d3_event.preventDefault(); + _showThirdPartyIcons = (_showThirdPartyIcons === 'true') ? 'false' : 'true'; + context.storage('preferences.privacy.thirdpartyicons', _showThirdPartyIcons); + update(); + }); + + thirdPartyIconsEnter + .append('span') + .text(t('preferences.privacy.third_party_icons.description')); + + + // Privacy Policy link + selection.selectAll('.privacy-link') + .data([0]) + .enter() + .append('div') + .attr('class', 'privacy-link') + .append('a') + .attr('target', '_blank') + .call(svgIcon('#iD-icon-out-link', 'inline')) + .attr('href', 'https://github.com/openstreetmap/iD/blob/master/PRIVACY.md') + .append('span') + .text(t('preferences.privacy.privacy_link')); + + update(); + + + function update() { + selection.selectAll('.privacy-third-party-icons-item') + .classed('active', (_showThirdPartyIcons === 'true')) + .select('input') + .property('checked', (_showThirdPartyIcons === 'true')); + } + }; + + return section; +} diff --git a/modules/ui/sections/validation_rules.js b/modules/ui/sections/validation_rules.js new file mode 100644 index 000000000..ca8eb532a --- /dev/null +++ b/modules/ui/sections/validation_rules.js @@ -0,0 +1,183 @@ +import { + event as d3_event, + select as d3_select +} from 'd3-selection'; + +import { t } from '../../util/locale'; +import { utilGetSetValue, utilNoAuto } from '../../util'; +import { tooltip } from '../../util/tooltip'; +import { uiSection } from '../section'; + +export function uiValidationRules(context) { + + var MINSQUARE = 0; + var MAXSQUARE = 20; + var DEFAULTSQUARE = 5; // see also unsquare_way.js + + var section = uiSection('issues-rules', context) + .title(t('issues.rules.title')); + + section.renderDisclosureContent = function(selection) { + var container = selection.selectAll('.issues-rulelist-container') + .data([0]); + + var containerEnter = container.enter() + .append('div') + .attr('class', 'issues-rulelist-container'); + + containerEnter + .append('ul') + .attr('class', 'layer-list issue-rules-list'); + + var ruleLinks = containerEnter + .append('div') + .attr('class', 'issue-rules-links section-footer'); + + ruleLinks + .append('a') + .attr('class', 'issue-rules-link') + .attr('href', '#') + .text(t('issues.enable_all')) + .on('click', function() { + context.validator().disableRules([]); + }); + + ruleLinks + .append('a') + .attr('class', 'issue-rules-link') + .attr('href', '#') + .text(t('issues.disable_all')) + .on('click', function() { + var keys = context.validator().getRuleKeys(); + context.validator().disableRules(keys); + }); + + + // Update + container = container + .merge(containerEnter); + + var ruleKeys = context.validator().getRuleKeys(); + + container.selectAll('.issue-rules-list') + .call(drawListItems, ruleKeys, 'checkbox', 'rule', toggleRule, isRuleEnabled); + }; + + 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'); + + if (name === 'rule') { + enter + .call(tooltip() + .title(function(d) { return t('issues.' + d + '.tip'); }) + .placement('top') + ); + } + + var label = enter + .append('label'); + + label + .append('input') + .attr('type', type) + .attr('name', name) + .on('change', change); + + label + .append('span') + .html(function(d) { + var params = {}; + if (d === 'unsquare_way') { + params.val = ''; + } + return t('issues.' + d + '.title', params); + }); + + // Update + items = items + .merge(enter); + + items + .classed('active', active) + .selectAll('input') + .property('checked', active) + .property('indeterminate', false); + + + // user-configurable square threshold + var degStr = context.storage('validate-square-degrees'); + if (degStr === null) { + degStr = '' + DEFAULTSQUARE; + } + + var span = items.selectAll('.square-degrees'); + var input = span.selectAll('.square-degrees-input') + .data([0]); + + // enter / update + input.enter() + .append('input') + .attr('type', 'number') + .attr('min', '' + MINSQUARE) + .attr('max', '' + MAXSQUARE) + .attr('step', '0.5') + .attr('class', 'square-degrees-input') + .call(utilNoAuto) + .on('click', function () { + d3_event.preventDefault(); + d3_event.stopPropagation(); + this.select(); + }) + .on('keyup', function () { + if (d3_event.keyCode === 13) { // enter + this.blur(); + this.select(); + } + }) + .on('blur', changeSquare) + .merge(input) + .property('value', degStr); + } + + function changeSquare() { + var input = d3_select(this); + var degStr = utilGetSetValue(input).trim(); + var degNum = parseFloat(degStr, 10); + + if (!isFinite(degNum)) { + degNum = DEFAULTSQUARE; + } else if (degNum > MAXSQUARE) { + degNum = MAXSQUARE; + } else if (degNum < MINSQUARE) { + degNum = MINSQUARE; + } + + degNum = Math.round(degNum * 10 ) / 10; // round to 1 decimal + degStr = '' + degNum; + + input + .property('value', degStr); + + context.storage('validate-square-degrees', degStr); + context.validator().reloadUnsquareIssues(); + } + + function isRuleEnabled(d) { + return context.validator().isRuleEnabled(d); + } + + function toggleRule(d) { + context.validator().toggleRule(d); + } + + return section; +}