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;
+}