diff --git a/css/80_app.css b/css/80_app.css index 85c09cb12..2dbde672a 100644 --- a/css/80_app.css +++ b/css/80_app.css @@ -3126,9 +3126,8 @@ div.full-screen > button:hover { padding: 5px 0; } .issue-label .issue-autofix { - width: 36px; flex: 0 0 auto; - padding: 5px 7px; + padding: 5px 8px; } .issue-label .issue-info-button { height: unset; @@ -3149,19 +3148,39 @@ div.full-screen > button:hover { border-radius: 4px 0 0 4px; } -.issue-autofix button.autofix.action { +button.autofix.action { flex: 0 0 20px; height: 20px; width: 20px; background: #7092ff; color: #fff; } -.issue-label button.autofix.action:focus, -.issue-label button.autofix.action:hover, -.issue-label button.autofix.action.active { +button.autofix.action:focus, +button.autofix.action:hover, +button.autofix.action.active { background: #597be7; } +/* fix all */ +.autofix-all { + display: flex; + flex-flow: row nowrap; + flex-direction: row-reverse; + height: 30px; + padding-top: 5px; +} +.autofix-all-link-text { + padding: 0 5px; +} +.autofix-all-link-icon svg { + margin: 0 9px; + background: currentColor; + border-radius: 4px; +} +.autofix-all-link-icon svg use { + color: #fff; +} + /* warning styles */ .warnings-list, .warnings-list *, @@ -3245,6 +3264,10 @@ div.full-screen > button:hover { color: #ff0c05; } +.layer-list.issues-list { + margin-bottom: 0; +} + /* Issues Pane */ .issues-options-container { diff --git a/data/core.yaml b/data/core.yaml index f0988cd67..468ec58a7 100644 --- a/data/core.yaml +++ b/data/core.yaml @@ -1270,6 +1270,11 @@ en: visible: "In View" all: "Everywhere" suggested: "Suggested updates:" + fix_one: + title: fix + fix_all: + title: Fix All + annotation: Fixed several validation issues. almost_junction: title: Almost Junctions message: "{feature} is very close but not connected to {feature2}" diff --git a/dist/locales/en.json b/dist/locales/en.json index 89c866465..7ee8f606b 100644 --- a/dist/locales/en.json +++ b/dist/locales/en.json @@ -1549,6 +1549,13 @@ } }, "suggested": "Suggested updates:", + "fix_one": { + "title": "fix" + }, + "fix_all": { + "title": "Fix All", + "annotation": "Fixed several validation issues." + }, "almost_junction": { "title": "Almost Junctions", "message": "{feature} is very close but not connected to {feature2}", diff --git a/modules/core/validator.js b/modules/core/validator.js index 8392ab911..4f12241d0 100644 --- a/modules/core/validator.js +++ b/modules/core/validator.js @@ -318,11 +318,13 @@ export function coreValidator(context) { // if (!difference) return; // validator.validate(); // }); + context.history() + .on('undone.validator', validator.validate) + .on('redone.validator', validator.validate); // re-run validation when the user switches editing modes (less frequent) - context.on('exit.validator', function() { - validator.validate(); - }); + context + .on('exit.validator', validator.validate); return validator; @@ -342,7 +344,7 @@ export function validationIssue(attrs) { this.hash = attrs.hash; // optional - string to further differentiate the issue this.id = generateID.apply(this); // generated - see below - this.auto = null; // generated - if autofix exists, will be set below + this.autoFix = null; // generated - if autofix exists, will be set below // A unique, deterministic string hash. // Issues with identical id values are considered identical. @@ -389,8 +391,8 @@ export function validationIssue(attrs) { for (var i = 0; i < this.fixes.length; i++) { var fix = this.fixes[i]; fix.issue = this; - if (fix.auto) { - this.auto = fix; + if (fix.autoArgs) { + this.autoFix = fix; } } } @@ -398,11 +400,11 @@ export function validationIssue(attrs) { export function validationIssueFix(attrs) { - this.title = attrs.title; // Required - this.onClick = attrs.onClick; // Required - this.icon = attrs.icon; // Optional - shows 'iD-icon-wrench' if not set - this.entityIds = attrs.entityIds || []; // Optional - Used for hover-higlighting. - this.auto = attrs.auto; // Optional - pass true if this fix can be an auto fix + this.title = attrs.title; // Required + this.onClick = attrs.onClick; // Required + this.icon = attrs.icon; // Optional - shows 'iD-icon-wrench' if not set + this.entityIds = attrs.entityIds || []; // Optional - Used for hover-higlighting. + this.autoArgs = attrs.autoArgs; // Optional - pass [actions, annotation] arglist if this fix can automatically run this.issue = null; // Generated link - added by ValidationIssue constructor } diff --git a/modules/ui/issues.js b/modules/ui/issues.js index 146181c09..6cff86b1d 100644 --- a/modules/ui/issues.js +++ b/modules/ui/issues.js @@ -4,6 +4,8 @@ import { event as d3_event, select as d3_select } from 'd3-selection'; import { t, textDirection } from '../util/locale'; import { tooltip } from '../util/tooltip'; + +import { actionNoop } from '../actions'; import { geoSphericalDistance } from '../geo'; import { modeSelect } from '../modes'; import { svgIcon } from '../svg'; @@ -17,8 +19,8 @@ import { utilCallWhenIdle, utilHighlightEntities } from '../util'; export function uiIssues(context) { var key = t('issues.key'); - var _errorsList = d3_select(null); - var _warningsList = d3_select(null); + var _errorsSelection = d3_select(null); + var _warningsSelection = d3_select(null); var _rulesList = d3_select(null); var _pane = d3_select(null); var _toggleButton = d3_select(null); @@ -51,35 +53,30 @@ export function uiIssues(context) { .attr('fill', 'currentColor'); } + function renderErrorsList(selection) { - _errorsList = selection.selectAll('.errors-list') - .data([0]); - - _errorsList = _errorsList.enter() - .append('ul') - .attr('class', 'layer-list errors-list issues-list') - .merge(_errorsList); - - _errorsList - .call(drawIssuesList, _errors); + _errorsSelection = selection + .call(drawIssuesList, 'errors', _errors); } + function renderWarningsList(selection) { - _warningsList = selection.selectAll('.warnings-list') - .data([0]); - - _warningsList = _warningsList.enter() - .append('ul') - .attr('class', 'layer-list warnings-list issues-list') - .merge(_warningsList); - - _warningsList - .call(drawIssuesList, _warnings); + _warningsSelection = selection + .call(drawIssuesList, 'warnings', _warnings); } - function drawIssuesList(selection, issues) { - var items = selection.selectAll('li') + function drawIssuesList(selection, which, issues) { + var list = selection.selectAll('.issues-list') + .data([0]); + + list = list.enter() + .append('ul') + .attr('class', 'layer-list issues-list ' + which + '-list') + .merge(list); + + + var items = list.selectAll('li') .data(issues, function(d) { return d.id; }); // Exit @@ -143,15 +140,16 @@ export function uiIssues(context) { .append('span') .attr('class', 'issue-autofix') .each(function(d) { - if (!d.auto) return; + if (!d.autoFix) return; d3_select(this) .append('button') - .datum(d.auto) // set button datum to the autofix + .attr('title', t('issues.fix_one.title')) + .datum(d.autoFix) // set button datum to the autofix .attr('class', 'autofix action') .on('click', function(d) { utilHighlightEntities(d.entityIds, false, context); - d.onClick(); + context.perform.apply(context, d.autoArgs); context.validator().validate(); }) .call(svgIcon('#iD-icon-wrench')); @@ -162,6 +160,55 @@ export function uiIssues(context) { items = items .merge(itemsEnter) .order(); + + + // autofix + var canAutoFix = issues.filter(function(issue) { return issue.autoFix; }); + + var autoFixAll = selection.selectAll('.autofix-all') + .data(canAutoFix.length ? [0] : []); + + // exit + autoFixAll.exit() + .remove(); + + // enter + var autoFixAllEnter = autoFixAll.enter() + .append('div') + .attr('class', 'autofix-all'); + + var linkEnter = autoFixAllEnter + .append('a') + .attr('class', 'autofix-all-link') + .attr('href', '#'); + + linkEnter + .append('span') + .attr('class', 'autofix-all-link-text') + .text(t('issues.fix_all.title')); + + linkEnter + .append('span') + .attr('class', 'autofix-all-link-icon') + .call(svgIcon('#iD-icon-wrench')); + + // update + autoFixAll = autoFixAll + .merge(autoFixAllEnter); + + autoFixAll.selectAll('.autofix-all-link') + .on('click', function() { + context.perform(actionNoop()); + canAutoFix.forEach(function(issue) { + var args = issue.autoFix.autoArgs.slice(); // copy + if (typeof args[args.length - 1] !== 'function') { + args.pop(); + } + args.push(t('issues.fix_all.annotation')); + context.replace.apply(context, args); + }); + context.validator().validate(); + }); } @@ -292,27 +339,28 @@ export function uiIssues(context) { .classed('warning', (_errors.length === 0 && _warnings.length > 0)) .classed('hide', (_errors.length === 0 && _warnings.length === 0)); - _pane.select('.issues-errors') + + _pane.selectAll('.issues-errors') .classed('hide', _errors.length === 0); if (_errors.length > 0) { - _pane.select('.hide-toggle-issues_errors .hide-toggle-text') + _pane.selectAll('.hide-toggle-issues_errors .hide-toggle-text') .text(t('issues.errors.list_title', { count: errorCount })); if (!_pane.select('.disclosure-wrap-issues_errors').classed('hide')) { - _errorsList - .call(drawIssuesList, _errors); + _errorsSelection + .call(drawIssuesList, 'errors', _errors); } } - _pane.select('.issues-warnings') + _pane.selectAll('.issues-warnings') .classed('hide', _warnings.length === 0); if (_warnings.length > 0) { - _pane.select('.hide-toggle-issues_warnings .hide-toggle-text') + _pane.selectAll('.hide-toggle-issues_warnings .hide-toggle-text') .text(t('issues.warnings.list_title', { count: warningCount })); if (!_pane.select('.disclosure-wrap-issues_warnings').classed('hide')) { - _warningsList - .call(drawIssuesList, _warnings); + _warningsSelection + .call(drawIssuesList, 'warnings', _warnings); } } diff --git a/modules/validations/old_multipolygon.js b/modules/validations/old_multipolygon.js index e87683996..fd45392bd 100644 --- a/modules/validations/old_multipolygon.js +++ b/modules/validations/old_multipolygon.js @@ -34,26 +34,23 @@ export function validationOldMultipolygon() { entities: [outerWay, multipolygon], fixes: [ new validationIssueFix({ - auto: true, + autoArgs: [doUpgrade, t('issues.fix.move_tags.annotation')], title: t('issues.fix.move_tags.title'), onClick: function() { - var outerWay = this.issue.entities[0]; - var multipolygon = this.issue.entities[1]; - context.perform( - function(graph) { - multipolygon = multipolygon.mergeTags(outerWay.tags); - graph = graph.replace(multipolygon); - graph = actionChangeTags(outerWay.id, {})(graph); - return graph; - }, - t('issues.fix.move_tags.annotation') - ); + context.perform(doUpgrade, t('issues.fix.move_tags.annotation')); } }) ] })]; + function doUpgrade(graph) { + multipolygon = multipolygon.mergeTags(outerWay.tags); + graph = graph.replace(multipolygon); + return actionChangeTags(outerWay.id, {})(graph); + } + + function showReference(selection) { selection.selectAll('.issue-reference') .data([0]) diff --git a/modules/validations/outdated_tags.js b/modules/validations/outdated_tags.js index 2e8ae5ec3..4da96e5d2 100644 --- a/modules/validations/outdated_tags.js +++ b/modules/validations/outdated_tags.js @@ -63,26 +63,23 @@ export function validationOutdatedTags() { message: t('issues.outdated_tags.message', { feature: utilDisplayLabel(entity, context) }), reference: showReference, entities: [entity], - data: { - newTags: newTags - }, fixes: [ new validationIssueFix({ - auto: true, + autoArgs: [doUpgrade, t('issues.fix.upgrade_tags.annotation')], title: t('issues.fix.upgrade_tags.title'), onClick: function() { - var entityID = this.issue.entities[0].id; - var newTags = this.issue.data.newTags; - context.perform( - actionChangeTags(entityID, newTags), - t('issues.fix.upgrade_tags.annotation') - ); + context.perform(doUpgrade, t('issues.fix.upgrade_tags.annotation')); } }) ] })]; + function doUpgrade(graph) { + return actionChangeTags(entity.id, newTags)(graph); + } + + function showReference(selection) { var enter = selection.selectAll('.issue-reference') .data([0])