diff --git a/css/80_app.css b/css/80_app.css index a99b480a3..cd40b8049 100644 --- a/css/80_app.css +++ b/css/80_app.css @@ -3306,6 +3306,10 @@ button.autofix.action.active { padding: 0 10px; } +.layer-list.issues-list { + margin-bottom: 5px; +} + .layer-list.issues-list li.issue { border-color: inherit; /* override .layer-list styles */ color: inherit; @@ -3325,15 +3329,17 @@ button.autofix.action.active { padding: 5px; } - .issues-none { + margin-top: 5px; + margin-bottom: 5px; +} +.issues-none .box { border-radius: 4px; border: 1px solid #72d979; background: #c6ffca; padding: 5px !important; display: flex; - margin-top: 5px; - margin-bottom: 15px; + margin-bottom: 5px; } .issues-none .icon { color: #05ac10; diff --git a/data/core.yaml b/data/core.yaml index c28e1ed36..9740ac13c 100644 --- a/data/core.yaml +++ b/data/core.yaml @@ -1281,8 +1281,25 @@ en: rules: title: Rules no_issues: - message: Everything looks fine - info: Any issues will show up here as you edit + message: + everything: Everything looks fine + everything_in_view: Everything in view looks fine + edits: Your edits look fine + edits_in_view: Your edits in view look fine + hidden_issues: + none: Detected issues will appear here + elsewhere: + single: "1 issue elsewhere" + multiple: "{count} issues elsewhere" + other_features: + single: "1 issue with other features" + multiple: "{count} issues with other features" + disabled_rules: + single: "1 issue from disabled rules" + multiple: "{count} issues from disabled rules" + ignored_issues: + single: "1 ignored issue" + multiple: "{count} ignored issues" options: what: title: "Check:" @@ -1295,6 +1312,7 @@ en: suggested: "Suggested updates:" enable_all: Enable All disable_all: Disable All + reset_ignored: Reset ignored ({count}) fix_one: title: fix fix_all: diff --git a/dist/locales/en.json b/dist/locales/en.json index 990331031..0b68596f5 100644 --- a/dist/locales/en.json +++ b/dist/locales/en.json @@ -1571,8 +1571,31 @@ "title": "Rules" }, "no_issues": { - "message": "Everything looks fine", - "info": "Any issues will show up here as you edit" + "message": { + "everything": "Everything looks fine", + "everything_in_view": "Everything in view looks fine", + "edits": "Your edits look fine", + "edits_in_view": "Your edits in view look fine" + }, + "hidden_issues": { + "none": "Detected issues will appear here", + "elsewhere": { + "single": "1 issue elsewhere", + "multiple": "{count} issues elsewhere" + }, + "other_features": { + "single": "1 issue with other features", + "multiple": "{count} issues with other features" + }, + "disabled_rules": { + "single": "1 issue from disabled rules", + "multiple": "{count} issues from disabled rules" + }, + "ignored_issues": { + "single": "1 ignored issue", + "multiple": "{count} ignored issues" + } + } }, "options": { "what": { @@ -1589,6 +1612,7 @@ "suggested": "Suggested updates:", "enable_all": "Enable All", "disable_all": "Disable All", + "reset_ignored": "Reset ignored ({count})", "fix_one": { "title": "fix" }, diff --git a/modules/core/validation/models.js b/modules/core/validation/models.js index 18be90de2..5a57fe284 100644 --- a/modules/core/validation/models.js +++ b/modules/core/validation/models.js @@ -1,5 +1,4 @@ import { geoExtent } from '../../geo'; -import { osmEntity } from '../../osm/entity'; export function validationIssue(attrs) { this.type = attrs.type; // required - name of rule that created the issue (e.g. 'missing_tag') @@ -39,15 +38,12 @@ export function validationIssue(attrs) { return parts.join(':'); } - var _extent; this.extent = function(resolver) { - if (_extent) return _extent; - if (this.loc) { - return _extent = geoExtent(this.loc); + return geoExtent(this.loc); } if (this.entityIds && this.entityIds.length) { - return _extent = this.entityIds.reduce(function(extent, entityId) { + return this.entityIds.reduce(function(extent, entityId) { return extent.extend(resolver.entity(entityId).extent(resolver)); }, geoExtent()); } diff --git a/modules/core/validator.js b/modules/core/validator.js index d36c34900..87e4291ad 100644 --- a/modules/core/validator.js +++ b/modules/core/validator.js @@ -45,6 +45,7 @@ export function coreValidator(context) { // validator.reset = function() { // clear caches + _ignoredIssueIDs = {}; _issuesByIssueID = {}; _issuesByEntityID = {}; _validatedGraph = null; @@ -56,11 +57,16 @@ export function coreValidator(context) { } }; + validator.resetIgnoredIssues = function() { + _ignoredIssueIDs = {}; + // reload UI + dispatch.call('validated'); + }; // options = { - // what: 'edited', // 'all' or 'edited' - // where: 'visible', // 'all' or 'visible' - // includeIgnored: false // true or false + // what: 'all', // 'all' or 'edited' + // where: 'all', // 'all' or 'visible' + // includeIgnored: false // true, false, or 'only' // }; validator.getIssues = function(options) { var opts = Object.assign({ what: 'all', where: 'all', includeIgnored: false }, options); @@ -83,6 +89,8 @@ export function coreValidator(context) { } } + if (opts.includeIgnored === 'only' && !_ignoredIssueIDs[issue.id]) return false; + if (!opts.includeIgnored && _ignoredIssueIDs[issue.id]) return false; if (opts.what === 'edited') { @@ -120,7 +128,11 @@ export function coreValidator(context) { validator.getRuleKeys = function() { return Object.keys(_rules) - .filter(function(key) { return key !== 'maprules'; }); + .filter(function(key) { return key !== 'maprules'; }) + .sort(function(key1, key2) { + // alphabetize by localized title + return t('issues.' + key1 + '.title') < t('issues.' + key2 + '.title') ? -1 : 1; + }); }; diff --git a/modules/ui/issues.js b/modules/ui/issues.js index a34bf51bc..96e642a52 100644 --- a/modules/ui/issues.js +++ b/modules/ui/issues.js @@ -191,6 +191,10 @@ export function uiIssues(context) { .attr('class', 'autofix-all-link-icon') .call(svgIcon('#iD-icon-wrench')); + if (which === 'warnings') { + renderIgnoredIssuesReset(selection); + } + // update autoFixAll = autoFixAll .merge(autoFixAllEnter); @@ -272,23 +276,57 @@ export function uiIssues(context) { function renderNoIssuesBox(selection) { - selection + + var box = selection.append('div') + .attr('class', 'box'); + + box .append('div') .call(svgIcon('#iD-icon-apply', 'pre-text')); - var noIssuesMessage = selection + var noIssuesMessage = box .append('span'); noIssuesMessage .append('strong') - .text(t('issues.no_issues.message')); + .text(t('issues.no_issues.message.everything')); noIssuesMessage .append('br'); noIssuesMessage .append('span') - .text(t('issues.no_issues.info')); + .text(t('issues.no_issues.hidden_issues.none')); + } + + function renderIgnoredIssuesReset(selection) { + + var ignoredIssues = context.validator() + .getIssues(Object.assign({ includeIgnored: 'only' }, _options)); + + var resetIgnored = selection.selectAll('.reset-ignored') + .data(ignoredIssues.length ? [0] : []); + + // exit + resetIgnored.exit() + .remove(); + + // enter + var resetIgnoredEnter = resetIgnored.enter() + .append('a') + .attr('class', 'reset-ignored') + .attr('href', '#'); + + // update + resetIgnored = resetIgnored + .merge(resetIgnoredEnter); + + resetIgnored + .text(t('issues.reset_ignored', { count: ignoredIssues.length.toString() })); + + resetIgnored.on('click', function() { + context.validator().resetIgnoredIssues(); + }); } @@ -401,8 +439,13 @@ export function uiIssues(context) { } } - _pane.select('.issues-none') - .classed('hide', _warnings.length > 0 || _errors.length > 0); + var hasIssues = _warnings.length > 0 || _errors.length > 0; + + var issuesNone = _pane.select('.issues-none'); + issuesNone.classed('hide', hasIssues); + if (!hasIssues) { + renderIgnoredIssuesReset(issuesNone); + } if (!_pane.select('.disclosure-wrap-issues_rules').classed('hide')) { updateRulesList();