From 4fe9057eb762515d71cbeb39cb05efd1eb394dc2 Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Thu, 20 Feb 2020 13:02:09 -0800 Subject: [PATCH] Move remaining issues pane sections to their own objects --- css/80_app.css | 4 +- modules/ui/panes/issues.js | 284 +--------------------- modules/ui/section.js | 40 ++- modules/ui/sections/validation_issues.js | 30 +-- modules/ui/sections/validation_options.js | 76 ++++++ modules/ui/sections/validation_status.js | 174 +++++++++++++ 6 files changed, 305 insertions(+), 303 deletions(-) create mode 100644 modules/ui/sections/validation_options.js create mode 100644 modules/ui/sections/validation_status.js diff --git a/css/80_app.css b/css/80_app.css index 72ba5f369..a930b4c73 100644 --- a/css/80_app.css +++ b/css/80_app.css @@ -3258,14 +3258,14 @@ button.autofix.action.active { padding: 5px; } -.issues-none .box { +.section-issues-status .box { border-radius: 4px; border: 1px solid #72d979; background: #c6ffca; padding: 5px !important; display: flex; } -.issues-none .icon { +.section-issues-status .icon { color: #05ac10; } diff --git a/modules/ui/panes/issues.js b/modules/ui/panes/issues.js index 84753b9b0..03b47b218 100644 --- a/modules/ui/panes/issues.js +++ b/modules/ui/panes/issues.js @@ -1,286 +1,26 @@ -import _debounce from 'lodash-es/debounce'; -import { event as d3_event } from 'd3-selection'; import { t } from '../../util/locale'; -import { svgIcon } from '../../svg/icon'; import { uiPane } from '../pane'; -import { uiSectionValidationIssues } from '../sections/validation_issues'; -import { uiSectionValidationRules } from '../sections/validation_rules'; +import { uiSectionValidationIssues } from '../sections/validation_issues'; +import { uiSectionValidationOptions } from '../sections/validation_options'; +import { uiSectionValidationRules } from '../sections/validation_rules'; +import { uiSectionValidationStatus } from '../sections/validation_status'; export function uiPaneIssues(context) { - var _validationRules = uiSectionValidationRules(context); - var _validationErrors = uiSectionValidationIssues('issues-errors', 'error', context); - var _validationWarnings = uiSectionValidationIssues('issues-warnings', 'warning', context); - - function getOptions() { - return { - what: context.storage('validate-what') || 'edited', // 'all', 'edited' - where: context.storage('validate-where') || 'all' // 'all', 'visible' - }; - } - - // listen for updates that affect the "no issues" box - context.validator().on('validated.uiPaneIssues', - function() { window.requestIdleCallback(update); } - ); - context.map().on('move.uiPaneIssues', - _debounce(function() { window.requestIdleCallback(update); }, 1000) - ); - - - function updateOptionValue(d, val) { - if (!val && d3_event && d3_event.target) { - val = d3_event.target.value; - } - - context.storage('validate-' + d, val); - context.validator().validate(); - } - - - function renderIssuesOptions(selection) { - var container = selection.selectAll('.issues-options-container') - .data([0]); - - container = container.enter() - .append('div') - .attr('class', 'issues-options-container') - .merge(container); - - var data = [ - { key: 'what', values: ['edited', 'all'] }, - { key: 'where', values: ['visible', 'all'] } - ]; - - var options = container.selectAll('.issues-option') - .data(data, function(d) { return d.key; }); - - var optionsEnter = options.enter() - .append('div') - .attr('class', function(d) { return 'issues-option issues-option-' + d.key; }); - - optionsEnter - .append('div') - .attr('class', 'issues-option-title') - .text(function(d) { return t('issues.options.' + d.key + '.title'); }); - - var valuesEnter = optionsEnter.selectAll('label') - .data(function(d) { - return d.values.map(function(val) { return { value: val, key: d.key }; }); - }) - .enter() - .append('label'); - - valuesEnter - .append('input') - .attr('type', 'radio') - .attr('name', function(d) { return 'issues-option-' + d.key; }) - .attr('value', function(d) { return d.value; }) - .property('checked', function(d) { return getOptions()[d.key] === d.value; }) - .on('change', function(d) { updateOptionValue(d.key, d.value); }); - - valuesEnter - .append('span') - .text(function(d) { return t('issues.options.' + d.key + '.' + d.value); }); - } - - - function renderNoIssuesBox(selection) { - - var box = selection.append('div') - .attr('class', 'box'); - - box - .append('div') - .call(svgIcon('#iD-icon-apply', 'pre-text')); - - var noIssuesMessage = box - .append('span'); - - noIssuesMessage - .append('strong') - .attr('class', 'message'); - - noIssuesMessage - .append('br'); - - noIssuesMessage - .append('span') - .attr('class', 'details'); - } - - function renderIgnoredIssuesReset(selection) { - - var ignoredIssues = context.validator() - .getIssues({ what: 'all', where: 'all', includeDisabledRules: true, includeIgnored: 'only' }); - - var resetIgnored = selection.selectAll('.reset-ignored') - .data(ignoredIssues.length ? [0] : []); - - // exit - resetIgnored.exit() - .remove(); - - // enter - var resetIgnoredEnter = resetIgnored.enter() - .append('div') - .attr('class', 'reset-ignored section-footer'); - - resetIgnoredEnter - .append('a') - .attr('href', '#'); - - // update - resetIgnored = resetIgnored - .merge(resetIgnoredEnter); - - resetIgnored.select('a') - .text(t('issues.reset_ignored', { count: ignoredIssues.length.toString() })); - - resetIgnored.on('click', function() { - context.validator().resetIgnoredIssues(); - }); - } - - function setNoIssuesText() { - - var opts = getOptions(); - - function checkForHiddenIssues(cases) { - for (var type in cases) { - var hiddenOpts = cases[type]; - var hiddenIssues = context.validator().getIssues(hiddenOpts); - if (hiddenIssues.length) { - issuesPane.selection().select('.issues-none .details') - .text(t( - 'issues.no_issues.hidden_issues.' + type, - { count: hiddenIssues.length.toString() } - )); - return; - } - } - issuesPane.selection().select('.issues-none .details') - .text(t('issues.no_issues.hidden_issues.none')); - } - - var messageType; - - if (opts.what === 'edited' && opts.where === 'visible') { - - messageType = 'edits_in_view'; - - checkForHiddenIssues({ - elsewhere: { what: 'edited', where: 'all' }, - everything_else: { what: 'all', where: 'visible' }, - disabled_rules: { what: 'edited', where: 'visible', includeDisabledRules: 'only' }, - everything_else_elsewhere: { what: 'all', where: 'all' }, - disabled_rules_elsewhere: { what: 'edited', where: 'all', includeDisabledRules: 'only' }, - ignored_issues: { what: 'edited', where: 'visible', includeIgnored: 'only' }, - ignored_issues_elsewhere: { what: 'edited', where: 'all', includeIgnored: 'only' } - }); - - } else if (opts.what === 'edited' && opts.where === 'all') { - - messageType = 'edits'; - - checkForHiddenIssues({ - everything_else: { what: 'all', where: 'all' }, - disabled_rules: { what: 'edited', where: 'all', includeDisabledRules: 'only' }, - ignored_issues: { what: 'edited', where: 'all', includeIgnored: 'only' } - }); - - } else if (opts.what === 'all' && opts.where === 'visible') { - - messageType = 'everything_in_view'; - - checkForHiddenIssues({ - elsewhere: { what: 'all', where: 'all' }, - disabled_rules: { what: 'all', where: 'visible', includeDisabledRules: 'only' }, - disabled_rules_elsewhere: { what: 'all', where: 'all', includeDisabledRules: 'only' }, - ignored_issues: { what: 'all', where: 'visible', includeIgnored: 'only' }, - ignored_issues_elsewhere: { what: 'all', where: 'all', includeIgnored: 'only' } - }); - } else if (opts.what === 'all' && opts.where === 'all') { - - messageType = 'everything'; - - checkForHiddenIssues({ - disabled_rules: { what: 'all', where: 'all', includeDisabledRules: 'only' }, - ignored_issues: { what: 'all', where: 'all', includeIgnored: 'only' } - }); - } - - if (opts.what === 'edited' && context.history().difference().summary().length === 0) { - messageType = 'no_edits'; - } - - issuesPane.selection().select('.issues-none .message') - .text(t('issues.no_issues.message.' + messageType)); - - } - - - function update() { - var issues = context.validator().getIssues(getOptions()); - - var hasIssues = issues.length > 0; - - var issuesNone = issuesPane.selection().select('.issues-none'); - issuesNone.classed('hide', hasIssues); - if (!hasIssues) { - renderIgnoredIssuesReset(issuesNone); - setNoIssuesText(); - } - - issuesPane.selection().select('.issues-errors') - .call(_validationErrors.render); - - issuesPane.selection().select('.issues-warnings') - .call(_validationWarnings.render); - - issuesPane.selection().select('.issues-rules') - .call(_validationRules.render); - } - - var issuesPane = uiPane('issues', context) .key(t('issues.key')) .title(t('issues.title')) .description(t('issues.title')) - .iconName('iD-icon-alert'); - - - issuesPane.renderContent = function(content) { - - content - .append('div') - .attr('class', 'issues-options') - .call(renderIssuesOptions); - - content - .append('div') - .attr('class', 'issues-none') - .call(renderNoIssuesBox); - - // errors - content - .append('div') - .attr('class', 'issues-errors'); - - // warnings - content - .append('div') - .attr('class', 'issues-warnings'); - - // rules list - content - .append('div') - .attr('class', 'issues-rules'); - - update(); - }; + .iconName('iD-icon-alert') + .sections([ + uiSectionValidationOptions(context), + uiSectionValidationStatus(context), + uiSectionValidationIssues('issues-errors', 'error', context), + uiSectionValidationIssues('issues-warnings', 'warning', context), + uiSectionValidationRules(context) + ]); return issuesPane; } diff --git a/modules/ui/section.js b/modules/ui/section.js index 27daa814f..c629fe00b 100644 --- a/modules/ui/section.js +++ b/modules/ui/section.js @@ -3,6 +3,7 @@ import { } from 'd3-selection'; import { uiDisclosure } from './disclosure'; +import { utilFunctor } from '../util'; // A unit of controls or info to be used in a layout, such as within a pane. // Can be labeled and collapsible. @@ -10,7 +11,8 @@ export function uiSection(id, context) { var _disclosure; var _title; - var _expandedByDefault = true; + var _expandedByDefault = utilFunctor(true); + var _shouldDisplay; var _containerSelection = d3_select(null); @@ -20,13 +22,19 @@ export function uiSection(id, context) { section.title = function(val) { if (!arguments.length) return _title; - _title = val; + _title = utilFunctor(val); return section; }; section.expandedByDefault = function(val) { if (!arguments.length) return _expandedByDefault; - _expandedByDefault = val; + _expandedByDefault = utilFunctor(val); + return section; + }; + + section.shouldDisplay = function(val) { + if (!arguments.length) return _shouldDisplay; + _shouldDisplay = utilFunctor(val); return section; }; @@ -46,19 +54,31 @@ export function uiSection(id, context) { .merge(_containerSelection); _containerSelection - .call(section.renderContent); + .call(renderContent); }; section.containerSelection = function() { return _containerSelection; }; + function renderContent(selection) { + if (_shouldDisplay) { + var shouldDisplay = _shouldDisplay(); + selection.classed('hide', !shouldDisplay); + if (!shouldDisplay) { + selection.html(''); + return; + } + } + section.renderContent(selection); + } + // may be called multiple times section.renderContent = function(containerSelection) { if (section.renderDisclosureContent) { if (!_disclosure) { - _disclosure = uiDisclosure(context, id.replace(/-/g, '_'), _expandedByDefault) + _disclosure = uiDisclosure(context, id.replace(/-/g, '_'), _expandedByDefault()) .title(_title || '') .content(section.renderDisclosureContent); } @@ -67,13 +87,13 @@ export function uiSection(id, context) { } }; + section.rerenderContent = function() { + _containerSelection + .call(renderContent); + }; + // override to enable disclosure section.renderDisclosureContent = undefined; - section.rerenderContent = function() { - _containerSelection - .call(section.renderContent); - }; - return section; } diff --git a/modules/ui/sections/validation_issues.js b/modules/ui/sections/validation_issues.js index eb722b5db..2fd0672a0 100644 --- a/modules/ui/sections/validation_issues.js +++ b/modules/ui/sections/validation_issues.js @@ -19,6 +19,9 @@ export function uiSectionValidationIssues(id, severity, context) { if (!_issues) return ''; var issueCountText = _issues.length > 1000 ? '1000+' : String(_issues.length); return t('issues.' + severity + 's.list_title', { count: issueCountText }); + }) + .shouldDisplay(function() { + return _issues && _issues.length; }); function getOptions() { @@ -33,19 +36,6 @@ export function uiSectionValidationIssues(id, severity, context) { _issues = context.validator().getIssuesBySeverity(getOptions())[severity]; } - var _parentRenderContent = section.renderContent; - - section.renderContent = function(selection) { - - var isHidden = !_issues || !_issues.length; - - selection.classed('hide', isHidden); - - if (!isHidden) { - selection.call(_parentRenderContent); - } - }; - section.renderDisclosureContent = function(selection) { var center = context.map().center(); @@ -225,12 +215,14 @@ export function uiSectionValidationIssues(id, severity, context) { context.map().on('move.uiSectionValidationIssues' + id, _debounce(function() { - if (getOptions().where === 'visible') { - // must refetch issues if they are viewport-dependent - reloadIssues(); - } - // always reload list to re-sort-by-distance - window.requestIdleCallback(section.rerenderContent); + window.requestIdleCallback(function() { + if (getOptions().where === 'visible') { + // must refetch issues if they are viewport-dependent + reloadIssues(); + } + // always reload list to re-sort-by-distance + section.rerenderContent(); + }); }, 1000) ); diff --git a/modules/ui/sections/validation_options.js b/modules/ui/sections/validation_options.js new file mode 100644 index 000000000..18158297a --- /dev/null +++ b/modules/ui/sections/validation_options.js @@ -0,0 +1,76 @@ +import { + event as d3_event +} from 'd3-selection'; + +import { t } from '../../util/locale'; +import { uiSection } from '../section'; + +export function uiSectionValidationOptions(context) { + + var section = uiSection('issues-options', context); + + section.renderContent = function(selection) { + + var container = selection.selectAll('.issues-options-container') + .data([0]); + + container = container.enter() + .append('div') + .attr('class', 'issues-options-container') + .merge(container); + + var data = [ + { key: 'what', values: ['edited', 'all'] }, + { key: 'where', values: ['visible', 'all'] } + ]; + + var options = container.selectAll('.issues-option') + .data(data, function(d) { return d.key; }); + + var optionsEnter = options.enter() + .append('div') + .attr('class', function(d) { return 'issues-option issues-option-' + d.key; }); + + optionsEnter + .append('div') + .attr('class', 'issues-option-title') + .text(function(d) { return t('issues.options.' + d.key + '.title'); }); + + var valuesEnter = optionsEnter.selectAll('label') + .data(function(d) { + return d.values.map(function(val) { return { value: val, key: d.key }; }); + }) + .enter() + .append('label'); + + valuesEnter + .append('input') + .attr('type', 'radio') + .attr('name', function(d) { return 'issues-option-' + d.key; }) + .attr('value', function(d) { return d.value; }) + .property('checked', function(d) { return getOptions()[d.key] === d.value; }) + .on('change', function(d) { updateOptionValue(d.key, d.value); }); + + valuesEnter + .append('span') + .text(function(d) { return t('issues.options.' + d.key + '.' + d.value); }); + }; + + function getOptions() { + return { + what: context.storage('validate-what') || 'edited', // 'all', 'edited' + where: context.storage('validate-where') || 'all' // 'all', 'visible' + }; + } + + function updateOptionValue(d, val) { + if (!val && d3_event && d3_event.target) { + val = d3_event.target.value; + } + + context.storage('validate-' + d, val); + context.validator().validate(); + } + + return section; +} diff --git a/modules/ui/sections/validation_status.js b/modules/ui/sections/validation_status.js new file mode 100644 index 000000000..6b90e5d66 --- /dev/null +++ b/modules/ui/sections/validation_status.js @@ -0,0 +1,174 @@ +import _debounce from 'lodash-es/debounce'; + +import { svgIcon } from '../../svg/icon'; +import { t } from '../../util/locale'; +import { uiSection } from '../section'; + +export function uiSectionValidationStatus(context) { + + var section = uiSection('issues-status', context) + .shouldDisplay(function() { + var issues = context.validator().getIssues(getOptions()); + return issues.length === 0; + }); + + function getOptions() { + return { + what: context.storage('validate-what') || 'edited', + where: context.storage('validate-where') || 'all' + }; + } + + section.renderContent = function(selection) { + + var box = selection.selectAll('.box') + .data([0]); + + var boxEnter = box.enter() + .append('div') + .attr('class', 'box'); + + boxEnter + .append('div') + .call(svgIcon('#iD-icon-apply', 'pre-text')); + + var noIssuesMessage = boxEnter + .append('span'); + + noIssuesMessage + .append('strong') + .attr('class', 'message'); + + noIssuesMessage + .append('br'); + + noIssuesMessage + .append('span') + .attr('class', 'details'); + + renderIgnoredIssuesReset(selection); + setNoIssuesText(selection); + }; + + function renderIgnoredIssuesReset(selection) { + + var ignoredIssues = context.validator() + .getIssues({ what: 'all', where: 'all', includeDisabledRules: true, includeIgnored: 'only' }); + + var resetIgnored = selection.selectAll('.reset-ignored') + .data(ignoredIssues.length ? [0] : []); + + // exit + resetIgnored.exit() + .remove(); + + // enter + var resetIgnoredEnter = resetIgnored.enter() + .append('div') + .attr('class', 'reset-ignored section-footer'); + + resetIgnoredEnter + .append('a') + .attr('href', '#'); + + // update + resetIgnored = resetIgnored + .merge(resetIgnoredEnter); + + resetIgnored.select('a') + .text(t('issues.reset_ignored', { count: ignoredIssues.length.toString() })); + + resetIgnored.on('click', function() { + context.validator().resetIgnoredIssues(); + }); + } + + function setNoIssuesText(selection) { + + var opts = getOptions(); + + function checkForHiddenIssues(cases) { + for (var type in cases) { + var hiddenOpts = cases[type]; + var hiddenIssues = context.validator().getIssues(hiddenOpts); + if (hiddenIssues.length) { + selection.select('.box .details') + .text(t( + 'issues.no_issues.hidden_issues.' + type, + { count: hiddenIssues.length.toString() } + )); + return; + } + } + selection.select('.box .details') + .text(t('issues.no_issues.hidden_issues.none')); + } + + var messageType; + + if (opts.what === 'edited' && opts.where === 'visible') { + + messageType = 'edits_in_view'; + + checkForHiddenIssues({ + elsewhere: { what: 'edited', where: 'all' }, + everything_else: { what: 'all', where: 'visible' }, + disabled_rules: { what: 'edited', where: 'visible', includeDisabledRules: 'only' }, + everything_else_elsewhere: { what: 'all', where: 'all' }, + disabled_rules_elsewhere: { what: 'edited', where: 'all', includeDisabledRules: 'only' }, + ignored_issues: { what: 'edited', where: 'visible', includeIgnored: 'only' }, + ignored_issues_elsewhere: { what: 'edited', where: 'all', includeIgnored: 'only' } + }); + + } else if (opts.what === 'edited' && opts.where === 'all') { + + messageType = 'edits'; + + checkForHiddenIssues({ + everything_else: { what: 'all', where: 'all' }, + disabled_rules: { what: 'edited', where: 'all', includeDisabledRules: 'only' }, + ignored_issues: { what: 'edited', where: 'all', includeIgnored: 'only' } + }); + + } else if (opts.what === 'all' && opts.where === 'visible') { + + messageType = 'everything_in_view'; + + checkForHiddenIssues({ + elsewhere: { what: 'all', where: 'all' }, + disabled_rules: { what: 'all', where: 'visible', includeDisabledRules: 'only' }, + disabled_rules_elsewhere: { what: 'all', where: 'all', includeDisabledRules: 'only' }, + ignored_issues: { what: 'all', where: 'visible', includeIgnored: 'only' }, + ignored_issues_elsewhere: { what: 'all', where: 'all', includeIgnored: 'only' } + }); + } else if (opts.what === 'all' && opts.where === 'all') { + + messageType = 'everything'; + + checkForHiddenIssues({ + disabled_rules: { what: 'all', where: 'all', includeDisabledRules: 'only' }, + ignored_issues: { what: 'all', where: 'all', includeIgnored: 'only' } + }); + } + + if (opts.what === 'edited' && context.history().difference().summary().length === 0) { + messageType = 'no_edits'; + } + + selection.select('.box .message') + .text(t('issues.no_issues.message.' + messageType)); + + } + + context.validator().on('validated.uiSectionValidationStatus', function() { + window.requestIdleCallback(section.rerenderContent); + }); + + context.map().on('move.uiSectionValidationStatus', + _debounce(function() { + window.requestIdleCallback(section.rerenderContent); + }, 1000) + ); + + return section; +}