From 490143d1a80dd886be58c14e567128ed9c2e98c4 Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Wed, 19 Dec 2018 16:12:33 -0500 Subject: [PATCH] Added Issues section to the entity sidebar Fixed bug where stale issues would stick around in the issues pane --- css/80_app.css | 34 ++++++++-- modules/ui/entity_editor.js | 18 ++++++ modules/ui/entity_issues.js | 82 +++++++++++++++++++++++++ modules/ui/issues.js | 4 +- modules/validations/issueManager.js | 15 +++++ modules/validations/validation_issue.js | 7 ++- 6 files changed, 151 insertions(+), 9 deletions(-) create mode 100644 modules/ui/entity_issues.js diff --git a/css/80_app.css b/css/80_app.css index a4adba0ad..1efee8c7d 100644 --- a/css/80_app.css +++ b/css/80_app.css @@ -2837,26 +2837,48 @@ div.full-screen > button:hover { color: inherit; } -.issues-list li.severity-warning { +.issue.severity-warning { background: #ffb; } -.issues-list li.severity-warning:hover { +.issue.severity-warning:hover { background: #FFFF99; } -.issues-list li.severity-warning .icon { +.issue.severity-warning .icon { color: #FFB300 } -.issues-list li.severity-error { +.issue.severity-error { background: #FFD5D4; } -.issues-list li.severity-error:hover { +.issue.severity-error:hover { background: #ffc9c7; } -.issues-list li.severity-error .icon { +.issue.severity-error .icon { color: #DD1400 } +/* Entity Issues List */ + +.entity-issues { + padding: 0 20px 20px 20px; + margin-bottom: 20px; +} +.entity-issues .issue { + border-radius: 4px; + border-width: 1px; + border-style: solid; + padding: 5px; +} +.entity-issues .issue.severity-warning { + border-color: #FFDF5C; +} +.entity-issues .issue.severity-error { + border-color: #F5817D; +} +.entity-issues .issue:not(:last-of-type) { + margin-bottom: 20px; +} + /* Background - Display Options Sliders ------------------------------------------------------- */ .display-options-container { diff --git a/modules/ui/entity_editor.js b/modules/ui/entity_editor.js index 68f41dea6..eda779b25 100644 --- a/modules/ui/entity_editor.js +++ b/modules/ui/entity_editor.js @@ -20,6 +20,7 @@ import { uiRawMembershipEditor } from './raw_membership_editor'; import { uiRawTagEditor } from './raw_tag_editor'; import { uiTagReference } from './tag_reference'; import { uiPresetEditor } from './preset_editor'; +import { uiEntityIssues } from './entity_issues'; import { utilCleanTags, utilRebind } from '../util'; @@ -33,6 +34,7 @@ export function uiEntityEditor(context) { var _activePreset; var _tagReference; + var entityIssues = uiEntityIssues(context); var presetEditor = uiPresetEditor(context).on('change', changeTags); var rawTagEditor = uiRawTagEditor(context).on('change', changeTags); var rawMemberEditor = uiRawMemberEditor(context); @@ -98,6 +100,10 @@ export function uiEntityEditor(context) { .append('div') .attr('class', 'label-inner'); + enter + .append('div') + .attr('class', 'inspector-border entity-issues'); + enter .append('div') .attr('class', 'inspector-border preset-editor'); @@ -157,6 +163,10 @@ export function uiEntityEditor(context) { .attr('class', 'namepart') .text(function(d) { return d; }); + body.select('.entity-issues') + .call(entityIssues + .entityID(_entityID) + ); body.select('.preset-editor') .call(presetEditor @@ -185,6 +195,14 @@ export function uiEntityEditor(context) { .style('display', 'none'); } + if (context.issueManager().getIssuesForEntityWithID(_entityID).length > 0) { + body.select('.entity-issues') + .style('display', 'block'); + } else { + body.select('.entity-issues') + .style('display', 'none'); + } + body.select('.raw-membership-editor') .call(rawMembershipEditor .entityID(_entityID) diff --git a/modules/ui/entity_issues.js b/modules/ui/entity_issues.js new file mode 100644 index 000000000..8d2792704 --- /dev/null +++ b/modules/ui/entity_issues.js @@ -0,0 +1,82 @@ +import { dispatch as d3_dispatch } from 'd3-dispatch'; + +import { + select as d3_select +} from 'd3-selection'; + +import { t } from '../util/locale'; +import { svgIcon } from '../svg'; +import { uiDisclosure } from './disclosure'; +import { utilRebind } from '../util'; +import { tooltip } from '../util/tooltip'; +import { uiTooltipHtml } from './tooltipHtml'; + + +export function uiEntityIssues(context) { + var dispatch = d3_dispatch('change'); + var _entityID; + + function entityIssues(selection) { + selection.call(uiDisclosure(context, 'entity_issues', true) + .title(t('issues.title')) + .content(render) + ); + } + + + function render(selection) { + var issues = context.issueManager().getIssuesForEntityWithID(_entityID); + + var items = selection.selectAll('.issue') + .data(issues, function(d) { return d.id(); }); + + // Exit + items.exit() + .remove(); + + // Enter + var enter = items.enter() + .append('div') + .attr('class', function (d) { + return 'issue severity-' + d.severity; + }) + .call(tooltip() + .html(true) + .title(function(d) { + var tip = d.tooltip ? d.tooltip : ''; + return uiTooltipHtml(tip); + }) + .placement('bottom') + ) + .on('click', function(d) { + + }); + + var label = enter + .append('label'); + + label.each(function(d) { + var iconSuffix = d.severity === 'warning' ? 'alert' : 'error'; + d3_select(this) + .call(svgIcon('#iD-icon-' + iconSuffix, 'pre-text')); + }); + + label + .append('span') + .text(function(d) { return d.message; }); + + // Update + items = items + .merge(enter); + } + + entityIssues.entityID = function(val) { + if (!arguments.length) return _entityID; + if (_entityID === val) return entityIssues; + _entityID = val; + return entityIssues; + }; + + + return utilRebind(entityIssues, dispatch, 'on'); +} diff --git a/modules/ui/issues.js b/modules/ui/issues.js index 204a8dfdd..8e6c4163d 100644 --- a/modules/ui/issues.js +++ b/modules/ui/issues.js @@ -107,7 +107,7 @@ export function uiIssues(context) { }, {});*/ var items = selection.selectAll('li') - .data(issues); + .data(issues, function(d) { return d.id(); }); // Exit items.exit() @@ -117,7 +117,7 @@ export function uiIssues(context) { var enter = items.enter() .append('li') .attr('class', function (d) { - return 'layer severity-' + d.severity; + return 'layer issue severity-' + d.severity; }) .call(tooltip() .html(true) diff --git a/modules/validations/issueManager.js b/modules/validations/issueManager.js index b290d181f..14540af49 100644 --- a/modules/validations/issueManager.js +++ b/modules/validations/issueManager.js @@ -1,4 +1,7 @@ import * as d3 from 'd3'; + +import _filter from 'lodash-es/filter'; + import { utilRebind } from '../util/rebind'; export function IssueManager(context) { @@ -24,6 +27,18 @@ export function IssueManager(context) { return issues; }; + self.getIssuesForEntityWithID = function(entityID) { + var issues = self.getIssues(); + return _filter(issues, function(issue) { + for (var i = 0; i < issue.entities.length; i++) { + if (issue.entities[i].id === entityID) { + return true; + } + } + return false; + }); + }; + self.validate = function() { var changes = context.history().changes(); issues = context.history().validate(changes); diff --git a/modules/validations/validation_issue.js b/modules/validations/validation_issue.js index 2b159c6a9..a3aad341b 100644 --- a/modules/validations/validation_issue.js +++ b/modules/validations/validation_issue.js @@ -1,5 +1,5 @@ import _isObject from 'lodash-es/isObject'; - +import { osmEntity } from '../osm'; var ValidationIssueType = Object.freeze({ deprecated_tags: 'deprecated_tags', @@ -22,6 +22,11 @@ export { ValidationIssueType, ValidationIssueSeverity }; export function validationIssue(attrs) { + + this.id = function () { + return this.type + osmEntity.key(this.entities[0]); + }; + if (!_isObject(attrs)) throw new Error('Input attrs is not an object'); if (!attrs.type || !ValidationIssueType.hasOwnProperty(attrs.type)) { throw new Error('Invalid attrs.type: ' + attrs.type);