Merge branch 'master' into validation

This commit is contained in:
Quincy Morgan
2019-02-11 07:59:29 -05:00
24 changed files with 488 additions and 232 deletions
+8 -4
View File
@@ -1114,11 +1114,12 @@ a.hide-toggle {
img.tag-reference-wiki-image {
float: right;
width: 33.3333%;
width: -webkit-calc(33.3333% - 10px);
width: calc(33.3333% - 10px);
border-radius: 4px;
max-height: 200px;
margin: 10px 5px 15px 20px;
margin: 10px 5px 15px 10px;
}
[dir='rtl'] img.tag-reference-wiki-image {
float: left;
margin: 10px 10px 15px 5px;
}
@@ -2630,6 +2631,9 @@ input.key-trap {
.error-details-description-text::first-letter {
text-transform: capitalize;
}
[dir='rtl'] .error-details-description-text::first-letter {
text-transform: none; /* #5877 */
}
.note-save .new-comment-input,
.error-save .new-comment-input {
+3 -2
View File
@@ -425,7 +425,7 @@ en:
role: Role
choose: Select feature type
results: "{n} results for {search}"
no_documentation_key: There is no documentation available for this key
no_documentation_key: "There is no documentation available."
edit_reference: "edit/translate"
wiki_reference: View documentation
wiki_en_reference: View documentation in English
@@ -668,6 +668,7 @@ en:
mr:
title: Missing Geometry
description: '{num_trips} recorded trips in this area suggest there may be unmapped {geometry_type} here.'
description_alt: 'Data from a 3rd party suggests there may be unmapped {geometry_type} here.'
tr:
title: Missing Turn Restriction
description: '{num_passed} of {num_trips} recorded trips (travelling {travel_direction}) make a turn from {from_way} to {to_way} at {junction}. There may be a missing "{turn_restriction}" restriction.'
@@ -1641,4 +1642,4 @@ en:
wikidata:
identifier: "Identifier"
label: "Label"
description: "Description"
description: "Description"
+3 -2
View File
@@ -520,7 +520,7 @@
"role": "Role",
"choose": "Select feature type",
"results": "{n} results for {search}",
"no_documentation_key": "There is no documentation available for this key",
"no_documentation_key": "There is no documentation available.",
"edit_reference": "edit/translate",
"wiki_reference": "View documentation",
"wiki_en_reference": "View documentation in English",
@@ -811,7 +811,8 @@
},
"mr": {
"title": "Missing Geometry",
"description": "{num_trips} recorded trips in this area suggest there may be unmapped {geometry_type} here."
"description": "{num_trips} recorded trips in this area suggest there may be unmapped {geometry_type} here.",
"description_alt": "Data from a 3rd party suggests there may be unmapped {geometry_type} here."
},
"tr": {
"title": "Missing Turn Restriction",
+6 -2
View File
@@ -13,6 +13,7 @@ import { behaviorTail } from './tail';
import { geoChooseEdge, geoVecLength } from '../geo';
import { utilKeybinding, utilRebind } from '../util';
import _isEmpty from 'lodash-es/isEmpty';
var _usedTails = {};
var _disableSpace = false;
@@ -26,7 +27,7 @@ export function behaviorDraw(context) {
var keybinding = utilKeybinding('draw');
var hover = behaviorHover(context).altDisables(true)
var hover = behaviorHover(context).altDisables(true).ignoreVertex(true)
.on('hover', context.ui().sidebar.hover);
var tail = behaviorTail();
var edit = behaviorEdit(context);
@@ -116,6 +117,9 @@ export function behaviorDraw(context) {
_mouseLeave = true;
}
function allowsVertex(d) {
return _isEmpty(d.tags) || context.presets().allowsVertex(d, context.graph());
}
// related code
// - `mode/drag_node.js` `doMode()`
@@ -125,7 +129,7 @@ export function behaviorDraw(context) {
var d = datum();
var target = d && d.properties && d.properties.entity;
if (target && target.type === 'node') { // Snap to a node
if (target && target.type === 'node' && allowsVertex(target)) { // Snap to a node
dispatch.call('clickNode', this, target, d);
return;
+5 -1
View File
@@ -17,6 +17,7 @@ import { modeBrowse, modeSelect } from '../modes';
import { osmNode } from '../osm';
import { utilKeybinding } from '../util';
import _isEmpty from 'lodash-es/isEmpty';
export function behaviorDrawWay(context, wayId, index, mode, startGraph) {
var origWay = context.entity(wayId);
@@ -65,6 +66,9 @@ export function behaviorDrawWay(context, wayId, index, mode, startGraph) {
}
}
function allowsVertex(d) {
return _isEmpty(d.tags) || context.presets().allowsVertex(d, context.graph());
}
// related code
// - `mode/drag_node.js` `doMode()`
@@ -73,7 +77,7 @@ export function behaviorDrawWay(context, wayId, index, mode, startGraph) {
function move(datum) {
context.surface().classed('nope-disabled', d3_event.altKey);
var targetLoc = datum && datum.properties && datum.properties.entity && datum.properties.entity.loc;
var targetLoc = datum && datum.properties && datum.properties.entity && allowsVertex(datum.properties.entity) && datum.properties.entity.loc;
var targetNodes = datum && datum.properties && datum.properties.nodes;
var loc = context.map().mouseCoordinates();
+11 -2
View File
@@ -8,6 +8,7 @@ import {
import { osmEntity, osmNote, qaError } from '../osm';
import { utilKeybinding, utilRebind } from '../util';
import _isEmpty from 'lodash-es/isEmpty';
/*
The hover behavior adds the `.hover` class on mouseover to all elements to which
@@ -24,6 +25,7 @@ export function behaviorHover(context) {
var _newId = null;
var _buttonDown;
var _altDisables;
var _vertex;
var _target;
@@ -96,6 +98,9 @@ export function behaviorHover(context) {
.on('mouseup.hover', null, true);
}
function allowsVertex(d) {
return _isEmpty(d.tags) || context.presets().allowsVertex(d, context.graph());
}
function enter(datum) {
if (datum === _target) return;
@@ -126,7 +131,6 @@ export function behaviorHover(context) {
if (entity.type === 'relation') {
entity.members.forEach(function(member) { selector += ', .' + member.id; });
}
} else if (datum && datum.properties && (datum.properties.entity instanceof osmEntity)) {
entity = datum.properties.entity;
selector = '.' + entity.id;
@@ -144,7 +148,7 @@ export function behaviorHover(context) {
return;
}
var suppressed = _altDisables && d3_event && d3_event.altKey;
var suppressed = (_altDisables && d3_event && d3_event.altKey) || (_vertex && !allowsVertex(entity, context.graph()));
_selection.selectAll(selector)
.classed(suppressed ? 'hover-suppressed' : 'hover', true);
@@ -182,6 +186,11 @@ export function behaviorHover(context) {
return behavior;
};
behavior.ignoreVertex = function(val) {
if (!arguments.length) return _vertex;
_vertex = val;
return behavior;
};
return utilRebind(behavior, dispatch, 'on');
}
-1
View File
@@ -354,7 +354,6 @@ export function modeDragNode(context) {
}
}
function end(entity) {
if (_isCancelled) return;
+2 -1
View File
@@ -148,8 +148,9 @@ export function modeSelectError(context, selectedErrorID, selectedErrorService)
.hide();
context.selectedErrorID(null);
context.features().forceVisible([]);
};
return mode;
}
}
+36 -1
View File
@@ -53,7 +53,7 @@ export function presetIndex() {
for (var k in entity.tags) {
// If any part of an address is present,
// allow fallback to "Address" preset - #4353
if (k.match(/^addr:/) !== null && geometryMatches['addr:*']) {
if (/^addr:/.test(k) && geometryMatches['addr:*']) {
address = geometryMatches['addr:*'][0];
}
@@ -67,6 +67,7 @@ export function presetIndex() {
match = keyMatches[i];
}
}
}
if (address && (!match || match.isFallback())) {
@@ -76,6 +77,40 @@ export function presetIndex() {
});
};
all.allowsVertex = function(entity, resolver) {
return resolver.transient(entity, 'vertexMatch', function() {
var vertexPresets = _index.vertex;
var match;
if (entity.isOnAddressLine(resolver)) {
match = true;
} else {
for (var k in entity.tags) {
var keyMatches = vertexPresets[k];
if (!keyMatches) continue;
for (var i = 0; i < keyMatches.length; i++) {
var preset = keyMatches[i];
if (preset.searchable !== false) {
if (preset.matchScore(entity) > -1) {
match = preset;
break;
}
}
}
if (!match && /^addr:/.test(k) && vertexPresets['addr:*']) {
match = true;
}
if (match) break;
}
}
return match;
});
};
// Because of the open nature of tagging, iD will never have a complete
// list of tags used in OSM, so we want it to have logic like "assume
+7
View File
@@ -166,6 +166,13 @@ export function presetPreset(id, preset, fields, visible, rawPresets) {
var reference = preset.reference || {};
preset.reference = function(geometry) {
// Lookup documentation on Wikidata...
var qid = preset.tags.wikidata || preset.tags['brand:wikidata'] || preset.tags['operator:wikidata'];
if (qid) {
return { qid: qid };
}
// Lookup documentation on OSM Wikibase...
var key = reference.key || Object.keys(_omit(preset.tags, 'name'))[0];
var value = reference.value || preset.tags[key];
+26 -17
View File
@@ -8,10 +8,7 @@ import { dispatch as d3_dispatch } from 'd3-dispatch';
import { osmEntity } from '../osm';
import { utilRebind } from '../util/rebind';
import {
utilQsString,
utilStringQs
} from '../util';
import { utilQsString, utilStringQs } from '../util';
export function rendererFeatures(context) {
@@ -58,13 +55,14 @@ export function rendererFeatures(context) {
'obliterated': true
};
var dispatch = d3_dispatch('change', 'redraw'),
_cullFactor = 1,
_cache = {},
_features = {},
_stats = {},
_keys = [],
_hidden = [];
var dispatch = d3_dispatch('change', 'redraw');
var _cullFactor = 1;
var _cache = {};
var _features = {};
var _stats = {};
var _keys = [];
var _hidden = [];
var _forceVisible = {};
function update() {
@@ -277,10 +275,10 @@ export function rendererFeatures(context) {
features.gatherStats = function(d, resolver, dimensions) {
var needsRedraw = false,
type = _groupBy(d, function(ent) { return ent.type; }),
entities = [].concat(type.relation || [], type.way || [], type.node || []),
currHidden, geometry, matches, i, j;
var needsRedraw = false;
var type = _groupBy(d, function(ent) { return ent.type; });
var entities = [].concat(type.relation || [], type.way || [], type.node || []);
var currHidden, geometry, matches, i, j;
for (i = 0; i < _keys.length; i++) {
_features[_keys[i]].count = 0;
@@ -346,8 +344,8 @@ export function rendererFeatures(context) {
}
if (!_cache[ent].matches) {
var matches = {},
hasMatch = false;
var matches = {};
var hasMatch = false;
for (var i = 0; i < _keys.length; i++) {
if (_keys[i] === 'others') {
@@ -462,6 +460,7 @@ export function rendererFeatures(context) {
features.isHidden = function(entity, resolver, geometry) {
if (!_hidden.length) return false;
if (!entity.version) return false;
if (_forceVisible[entity.id]) return false;
var fn = (geometry === 'vertex' ? features.isHiddenChild : features.isHiddenFeature);
return fn(entity, resolver, geometry);
@@ -482,6 +481,16 @@ export function rendererFeatures(context) {
};
features.forceVisible = function(entityIDs) {
if (!arguments.length) return Object.keys(_forceVisible);
_forceVisible = {};
for (var i = 0; i < entityIDs.length; i++) {
_forceVisible[entityIDs[i]] = true;
}
return features;
};
features.init = function() {
var storage = context.storage('disabled-features');
if (storage) {
+6 -1
View File
@@ -267,6 +267,11 @@ export default {
geometry_type: t('QA.improveOSM.geometry_types.' + geoType)
};
// -1 trips indicates data came from a 3rd party
if (feature.numberOfTrips === -1) {
d.desc = t('QA.improveOSM.error_types.mr.description_alt', d.replacements);
}
_erCache.data[d.id] = d;
_erCache.rtree.insert(encodeErrorRtree(d));
});
@@ -476,4 +481,4 @@ export default {
getClosedIDs: function() {
return Object.keys(_erCache.closed).sort();
}
};
};
+40 -21
View File
@@ -66,6 +66,7 @@ export default {
if (!entity.claims[property]) return undefined;
var locale = _localeIDs[langCode];
var preferredPick, localePick;
_forEach(entity.claims[property], function(stmt) {
// If exists, use value limited to the needed language (has a qualifier P26 = locale)
// Or if not found, use the first value with the "preferred" rank
@@ -78,8 +79,8 @@ export default {
localePick = stmt;
}
});
var result = localePick || preferredPick;
var result = localePick || preferredPick;
if (result) {
var datavalue = result.mainsnak.datavalue;
return datavalue.type === 'wikibase-entityid' ? datavalue.value.id : datavalue.value;
@@ -96,6 +97,7 @@ export default {
*/
monolingualClaimToValueObj: function(entity, property) {
if (!entity || !entity.claims[property]) return undefined;
return entity.claims[property].reduce(function(acc, obj) {
var value = obj.mainsnak.datavalue.value;
acc[value.language] = value.text;
@@ -105,7 +107,7 @@ export default {
toSitelink: function(key, value) {
var result = value ? 'Tag:' + key + '=' + value : 'Key:' + key;
var result = value ? ('Tag:' + key + '=' + value) : 'Key:' + key;
return result.replace(/_/g, ' ').trim();
},
@@ -113,21 +115,20 @@ export default {
//
// Pass params object of the form:
// {
// key: 'string', // required
// value: 'string' // optional
// }
// -or-
// {
// rtype: 'rtype' // relation type (e.g. 'multipolygon')
// key: 'string',
// value: 'string',
// rtype: 'string',
// langCode: 'string'
// }
//
getEntity: function(params, callback) {
var doRequest = params.debounce ? debouncedRequest : request;
var self = this;
var that = this;
var titles = [];
var result = {};
var keySitelink = this.toSitelink(params.key);
var tagSitelink = params.value ? this.toSitelink(params.key, params.value) : false;
var rtypeSitelink = params.rtype ? ('Relation:' + params.rtype).replace(/_/g, ' ').trim() : false;
var keySitelink = params.key ? this.toSitelink(params.key) : false;
var tagSitelink = (params.key && params.value) ? this.toSitelink(params.key, params.value) : false;
var localeSitelink;
if (params.langCode && _localeIDs[params.langCode] === undefined) {
@@ -138,10 +139,20 @@ export default {
titles.push(localeSitelink);
}
if (_wikibaseCache[keySitelink]) {
result.key = _wikibaseCache[keySitelink];
} else {
titles.push(keySitelink);
if (rtypeSitelink) {
if (_wikibaseCache[rtypeSitelink]) {
result.rtype = _wikibaseCache[rtypeSitelink];
} else {
titles.push(rtypeSitelink);
}
}
if (keySitelink) {
if (_wikibaseCache[keySitelink]) {
result.key = _wikibaseCache[keySitelink];
} else {
titles.push(keySitelink);
}
}
if (tagSitelink) {
@@ -185,11 +196,15 @@ export default {
var localeID = false;
_forEach(d.entities, function(res) {
if (res.missing !== '') {
var title = res.sitelinks.wiki.title;
// Simplify access to the localized values
res.description = localizedToString(res.descriptions, params.langCode);
res.label = localizedToString(res.labels, params.langCode);
if (title === keySitelink) {
var title = res.sitelinks.wiki.title;
if (title === rtypeSitelink) {
_wikibaseCache[rtypeSitelink] = res;
result.rtype = res;
} else if (title === keySitelink) {
_wikibaseCache[keySitelink] = res;
result.key = res;
} else if (title === tagSitelink) {
@@ -205,7 +220,7 @@ export default {
if (localeSitelink) {
// If locale ID is not found, store false to prevent repeated queries
self.addLocale(params.langCode, localeID);
that.addLocale(params.langCode, localeID);
}
callback(null, result);
@@ -245,7 +260,7 @@ export default {
return;
}
var entity = data.tag || data.key;
var entity = data.rtype || data.tag || data.key;
if (!entity) {
callback('No entity');
return;
@@ -273,8 +288,7 @@ export default {
if (imageroot && image) {
result.imageURL = imageroot + '?' + utilQsString({
title: 'Special:Redirect/file/' + image,
width: 100,
height: 100
width: 400
});
}
}
@@ -282,6 +296,7 @@ export default {
// Try to get a wiki page from tag data item first, followed by the corresponding key data item.
// If neither tag nor key data item contain a wiki page in the needed language nor English,
// get the first found wiki page from either the tag or the key item.
var rtypeWiki = that.monolingualClaimToValueObj(data.rtype, 'P31');
var tagWiki = that.monolingualClaimToValueObj(data.tag, 'P31');
var keyWiki = that.monolingualClaimToValueObj(data.key, 'P31');
@@ -289,7 +304,11 @@ export default {
// BUG: in some cases, a more elaborate fallback logic might be needed
var langPrefix = langCode.split('-', 2)[0];
// use the first acceptable wiki page
result.wiki =
getWikiInfo(rtypeWiki, langCode, 'inspector.wiki_reference') ||
getWikiInfo(rtypeWiki, langPrefix, 'inspector.wiki_reference') ||
getWikiInfo(rtypeWiki, 'en', 'inspector.wiki_en_reference') ||
getWikiInfo(tagWiki, langCode, 'inspector.wiki_reference') ||
getWikiInfo(tagWiki, langPrefix, 'inspector.wiki_reference') ||
getWikiInfo(tagWiki, 'en', 'inspector.wiki_en_reference') ||
+108 -14
View File
@@ -5,7 +5,7 @@ import { json as d3_json } from 'd3-request';
import { utilQsString } from '../util';
import { currentLocale } from '../util/locale';
var endpoint = 'https://www.wikidata.org/w/api.php?';
var apibase = 'https://www.wikidata.org/w/api.php?';
var _wikidataCache = {};
export default {
@@ -21,13 +21,13 @@ export default {
// corresponding Wikidata entities.
itemsByTitle: function(lang, title, callback) {
if (!title) {
callback('', {});
callback('No title', {});
return;
}
lang = lang || 'en';
d3_json(endpoint + utilQsString({
d3_json(apibase + utilQsString({
action: 'wbgetentities',
format: 'json',
formatversion: 2,
@@ -36,10 +36,13 @@ export default {
languages: 'en', // shrink response by filtering to one language
origin: '*'
}), function(err, data) {
if (err || !data || data.error) {
callback('', {});
if (data && data.error) {
err = data.error;
}
if (err) {
callback(err, {});
} else {
callback(title, data.entities || {});
callback(null, data.entities || {});
}
});
},
@@ -47,11 +50,11 @@ export default {
entityByQID: function(qid, callback) {
if (!qid) {
callback('', {});
callback('No qid', {});
return;
}
if (_wikidataCache[qid]) {
callback('', _wikidataCache[qid]);
callback(null, _wikidataCache[qid]);
return;
}
@@ -61,24 +64,115 @@ export default {
'en'
]);
d3_json(endpoint + utilQsString({
d3_json(apibase + utilQsString({
action: 'wbgetentities',
format: 'json',
formatversion: 2,
ids: qid,
props: /*sitelinks|*/'labels|descriptions',
//sitefilter: lang + 'wiki',
props: 'labels|descriptions|claims|sitelinks',
sitefilter: langs.map(function(d) { return d + 'wiki'; }).join('|'),
languages: langs.join('|'),
languagefallback: 1,
origin: '*'
}), function(err, data) {
if (err || !data || data.error) {
callback('', {});
if (data && data.error) {
err = data.error;
}
if (err) {
callback(err, {});
} else {
_wikidataCache[qid] = data.entities[qid];
callback(qid, data.entities[qid] || {});
callback(null, data.entities[qid] || {});
}
});
},
// Pass `params` object of the form:
// {
// qid: 'string' // brand wikidata (e.g. 'Q37158')
// }
//
// Get an result object used to display tag documentation
// {
// title: 'string',
// description: 'string',
// editURL: 'string',
// imageURL: 'string',
// wiki: { title: 'string', text: 'string', url: 'string' }
// }
//
getDocs: function(params, callback) {
this.entityByQID(params.qid, function(err, entity) {
if (err || !entity) {
callback(err || 'No entity');
return;
}
var i;
var description;
if (entity.descriptions && Object.keys(entity.descriptions).length > 0) {
description = entity.descriptions[Object.keys(entity.descriptions)[0]].value;
}
// prepare result
var result = {
title: entity.id,
description: description,
editURL: 'https://www.wikidata.org/wiki/' + entity.id
};
// add image
if (entity.claims) {
var imageroot = 'https://commons.wikimedia.org/w/index.php';
var props = ['P154','P18']; // logo image, image
var prop, image;
for (i = 0; i < props.length; i++) {
prop = entity.claims[props[i]];
if (prop && Object.keys(prop).length > 0) {
image = prop[Object.keys(prop)[0]].mainsnak.datavalue.value;
if (image) {
result.imageURL = imageroot + '?' + utilQsString({
title: 'Special:Redirect/file/' + image,
width: 400
});
break;
}
}
}
}
if (entity.sitelinks) {
// must be one of these that we requested..
var langs = _uniq([
currentLocale.toLowerCase(),
currentLocale.split('-', 2)[0].toLowerCase(),
'en'
]);
var englishLocale = (currentLocale.split('-', 2)[0].toLowerCase() === 'en');
for (i = 0; i < langs.length; i++) { // check each, in order of preference
var w = langs[i] + 'wiki';
if (entity.sitelinks[w]) {
var title = entity.sitelinks[w].title;
var tKey = 'inspector.wiki_reference';
if (!englishLocale && langs[i] === 'en') { // user's currentLocale isn't English but
tKey = 'inspector.wiki_en_reference'; // we are sending them to enwiki anyway..
}
result.wiki = {
title: title,
text: tKey,
url: 'https://' + langs[i] + '.wikipedia.org/wiki/' + title.replace(/ /g, '_')
};
break;
}
}
}
callback(null, result);
});
}
};
+1 -1
View File
@@ -297,7 +297,7 @@ export function uiFieldCombo(field, context) {
.data([0]);
var listClass = 'chiplist';
// Use a separate line for each value in the Destinations field
// to mimic highway exit signs
if (field.id === 'destination_oneway') {
+42 -31
View File
@@ -138,43 +138,54 @@ export function uiFieldWikidata(field) {
wiki.tags = function(tags) {
var value = tags[field.key] || '';
var matches = value.match(/^Q[0-9]*$/);
utilGetSetValue(title, value);
// value in correct format
if (matches) {
_wikiURL = 'https://wikidata.org/wiki/' + value;
wikidata.entityByQID(value, function(qid, entity) {
var label = '';
var description = '';
if (!/^Q[0-9]*$/.test(value)) { // not a proper QID
unrecognized();
return;
}
if (entity.labels && Object.keys(entity.labels).length > 0) {
label = entity.labels[Object.keys(entity.labels)[0]].value;
}
if (entity.descriptions && Object.keys(entity.descriptions).length > 0) {
description = entity.descriptions[Object.keys(entity.descriptions)[0]].value;
}
// QID value in correct format
_wikiURL = 'https://wikidata.org/wiki/' + value;
wikidata.entityByQID(value, function(err, entity) {
if (err) {
unrecognized();
return;
}
d3_select('.preset-wikidata-label')
.style('display', function(){
return label.length > 0 ? 'flex' : 'none';
})
.select('input')
.attr('value', label);
var label = '';
var description = '';
d3_select('.preset-wikidata-description')
.style('display', function(){
return description.length > 0 ? 'flex' : 'none';
})
.select('input')
.attr('value', description);
});
if (entity.labels && Object.keys(entity.labels).length > 0) {
label = entity.labels[Object.keys(entity.labels)[0]].value;
}
if (entity.descriptions && Object.keys(entity.descriptions).length > 0) {
description = entity.descriptions[Object.keys(entity.descriptions)[0]].value;
}
d3_select('.preset-wikidata-label')
.style('display', function(){
return label.length > 0 ? 'flex' : 'none';
})
.select('input')
.attr('value', label);
d3_select('.preset-wikidata-description')
.style('display', function(){
return description.length > 0 ? 'flex' : 'none';
})
.select('input')
.attr('value', description);
});
// not a proper QID
function unrecognized() {
d3_select('.preset-wikidata-label')
.style('display', 'none');
d3_select('.preset-wikidata-description')
.style('display', 'none');
// unrecognized value format
} else {
d3_select('.preset-wikidata-label').style('display', 'none');
d3_select('.preset-wikidata-description').style('display', 'none');
if (value && value !== '') {
_wikiURL = 'https://wikidata.org/wiki/Special:Search?search=' + value;
} else {
+3 -1
View File
@@ -200,7 +200,9 @@ export function uiFieldWikipedia(field, context) {
var initGraph = context.graph();
var initEntityID = _entity.id;
wikidata.itemsByTitle(language()[2], value, function(title, data) {
wikidata.itemsByTitle(language()[2], value, function(err, data) {
if (err) return;
// If graph has changed, we can't apply this update.
if (context.graph() !== initGraph) return;
+9
View File
@@ -17,6 +17,9 @@ export function uiImproveOsmDetails(context) {
var unknown = t('inspector.unknown');
if (!d) return unknown;
if (d.desc) return d.desc;
var errorType = d.error_key;
var et = dataEn.QA.improveOSM.error_types[errorType];
@@ -61,6 +64,7 @@ export function uiImproveOsmDetails(context) {
.html(errorDetail);
// If there are entity links in the error message..
var relatedEntities = [];
descriptionEnter.selectAll('.error_entity_link, .error_object_link')
.each(function() {
var link = d3_select(this);
@@ -70,6 +74,8 @@ export function uiImproveOsmDetails(context) {
: this.textContent;
var entity = context.hasEntity(entityID);
relatedEntities.push(entityID);
// Add click handler
link
.on('mouseover', function() {
@@ -113,6 +119,9 @@ export function uiImproveOsmDetails(context) {
}
}
});
// Don't hide entities related to this error - #5880
context.features().forceVisible(relatedEntities);
}
+6
View File
@@ -66,6 +66,7 @@ export function uiKeepRightDetails(context) {
.html(errorDetail);
// If there are entity links in the error message..
var relatedEntities = [];
descriptionEnter.selectAll('.error_entity_link, .error_object_link')
.each(function() {
var link = d3_select(this);
@@ -75,6 +76,8 @@ export function uiKeepRightDetails(context) {
: this.textContent;
var entity = context.hasEntity(entityID);
relatedEntities.push(entityID);
// Add click handler
link
.on('mouseover', function() {
@@ -118,6 +121,9 @@ export function uiKeepRightDetails(context) {
}
}
});
// Don't hide entities related to this error - #5880
context.features().forceVisible(relatedEntities);
}
+25 -14
View File
@@ -20,6 +20,7 @@ export function uiRawTagEditor(context) {
var _showBlank = false;
var _updatePreference = true;
var _expanded = false;
var _pendingChange = null;
var _state;
var _preset;
var _tags;
@@ -211,7 +212,7 @@ export function uiRawTagEditor(context) {
.property('disabled', isReadOnly);
items.selectAll('button.remove')
.on('click', removeTag);
.on('mousedown', removeTag); // 'click' fires too late - #5878
@@ -335,11 +336,11 @@ export function uiRawTagEditor(context) {
}
}
var t = {};
_pendingChange = _pendingChange || {};
if (kOld) {
t[kOld] = undefined;
_pendingChange[kOld] = undefined;
}
t[kNew] = vNew;
_pendingChange[kNew] = vNew;
d.key = kNew; // update datum to avoid exit/enter on tag update
d.value = vNew;
@@ -347,15 +348,16 @@ export function uiRawTagEditor(context) {
this.value = kNew;
utilGetSetValue(inputVal, vNew);
dispatch.call('change', this, t);
scheduleChange();
}
function valueChange(d) {
if (isReadOnly(d)) return;
var t = {};
t[d.key] = this.value;
dispatch.call('change', this, t);
_pendingChange = _pendingChange || {};
_pendingChange[d.key] = this.value;
scheduleChange();
}
@@ -366,23 +368,32 @@ export function uiRawTagEditor(context) {
_showBlank = false;
content(wrap);
} else {
var t = {};
t[d.key] = undefined;
dispatch.call('change', this, t);
_pendingChange = _pendingChange || {};
_pendingChange[d.key] = undefined;
scheduleChange();
}
}
function addTag() {
// Wrapped in a setTimeout in case it's being called from a blur
// handler. Without the setTimeout, the call to `content` would
// wipe out the pending value change.
// Delay render in case this click is blurring an edited combo.
// Without the setTimeout, the `content` render would wipe out the pending tag change.
window.setTimeout(function() {
_showBlank = true;
content(wrap);
list.selectAll('li:last-child input.key').node().focus();
}, 20);
}
function scheduleChange() {
// Delay change in case this change is blurring an edited combo. - #5878
window.setTimeout(function() {
dispatch.call('change', this, _pendingChange);
_pendingChange = null;
}, 10);
}
}
+9 -6
View File
@@ -68,6 +68,7 @@ export function uiSidebar(context) {
})
.on('drag', function() {
var isRTL = (textDirection === 'rtl');
var scaleX = isRTL ? 0 : 1;
var xMarginProperty = isRTL ? 'margin-right' : 'margin-left';
var x = d3_event.x - dragOffset;
@@ -84,7 +85,7 @@ export function uiSidebar(context) {
.style(xMarginProperty, '-400px')
.style('width', '400px');
context.ui().onResize([sidebarWidth - d3_event.dx, 0]);
context.ui().onResize([(sidebarWidth - d3_event.dx) * scaleX, 0]);
}
} else {
@@ -94,9 +95,9 @@ export function uiSidebar(context) {
.style('width', widthPct + '%');
if (isCollapsed) {
context.ui().onResize([-sidebarWidth, 0]);
context.ui().onResize([-sidebarWidth * scaleX, 0]);
} else {
context.ui().onResize([-d3_event.dx, 0]);
context.ui().onResize([-d3_event.dx * scaleX, 0]);
}
}
})
@@ -298,7 +299,9 @@ export function uiSidebar(context) {
var isCollapsed = selection.classed('collapsed');
var isCollapsing = !isCollapsed;
var xMarginProperty = textDirection === 'rtl' ? 'margin-right' : 'margin-left';
var isRTL = (textDirection === 'rtl');
var scaleX = isRTL ? 0 : 1;
var xMarginProperty = isRTL ? 'margin-right' : 'margin-left';
sidebarWidth = selection.node().getBoundingClientRect().width;
@@ -321,7 +324,7 @@ export function uiSidebar(context) {
return function(t) {
var dx = lastMargin - Math.round(i(t));
lastMargin = lastMargin - dx;
context.ui().onResize(moveMap ? undefined : [dx, 0]);
context.ui().onResize(moveMap ? undefined : [dx * scaleX, 0]);
};
})
.on('end', function() {
@@ -354,4 +357,4 @@ export function uiSidebar(context) {
sidebar.toggle = function() {};
return sidebar;
}
}
+66 -57
View File
@@ -8,17 +8,22 @@ import { services } from '../services';
import { svgIcon } from '../svg';
// Pass `tag` object of the form:
// Pass `which` object of the form:
// {
// key: 'string', // required
// value: 'string' // optional
// }
// -or-
// {
// rtype: 'rtype' // relation type (e.g. 'multipolygon')
// rtype: 'string' // relation type (e.g. 'multipolygon')
// }
export function uiTagReference(tag) {
var wikibase = services.osmWikibase;
// -or-
// {
// qid: 'string' // brand wikidata (e.g. 'Q37158')
// }
//
export function uiTagReference(what) {
var wikibase = what.qid ? services.wikidata : services.osmWikibase;
var tagReference = {};
var _button = d3_select(null);
@@ -33,66 +38,69 @@ export function uiTagReference(tag) {
_button
.classed('tag-reference-loading', true);
wikibase.getDocs(tag, function(err, docs) {
_body.html('');
wikibase.getDocs(what, gotDocs);
}
if (!docs || !docs.title) {
_body
.append('p')
.attr('class', 'tag-reference-description')
.text(t('inspector.no_documentation_key'));
done();
return;
}
if (docs.imageURL) {
_body
.append('img')
.attr('class', 'tag-reference-wiki-image')
.attr('src', docs.imageURL)
.on('load', function() { done(); })
.on('error', function() { d3_select(this).remove(); done(); });
} else {
done();
}
function gotDocs(err, docs) {
_body.html('');
if (!docs || !docs.title) {
_body
.append('p')
.attr('class', 'tag-reference-description')
.text(docs.description || t('inspector.no_documentation_key'))
.text(t('inspector.no_documentation_key'));
done();
return;
}
if (docs.imageURL) {
_body
.append('img')
.attr('class', 'tag-reference-wiki-image')
.attr('src', docs.imageURL)
.on('load', function() { done(); })
.on('error', function() { d3_select(this).remove(); done(); });
} else {
done();
}
_body
.append('p')
.attr('class', 'tag-reference-description')
.text(docs.description || t('inspector.no_documentation_key'))
.append('a')
.attr('class', 'tag-reference-edit')
.attr('target', '_blank')
.attr('tabindex', -1)
.attr('title', t('inspector.edit_reference'))
.attr('href', docs.editURL)
.call(svgIcon('#iD-icon-edit', 'inline'));
if (docs.wiki) {
_body
.append('a')
.attr('class', 'tag-reference-link')
.attr('target', '_blank')
.attr('tabindex', -1)
.attr('href', docs.wiki.url)
.call(svgIcon('#iD-icon-out-link', 'inline'))
.append('span')
.text(t(docs.wiki.text));
}
// Add link to info about "good changeset comments" - #2923
if (what.key === 'comment') {
_body
.append('a')
.attr('class', 'tag-reference-edit')
.attr('class', 'tag-reference-comment-link')
.attr('target', '_blank')
.attr('tabindex', -1)
.attr('title', t('inspector.edit_reference'))
.attr('href', docs.editURL)
.call(svgIcon('#iD-icon-edit', 'inline'));
if (docs.wiki) {
_body
.append('a')
.attr('class', 'tag-reference-link')
.attr('target', '_blank')
.attr('tabindex', -1)
.attr('href', docs.wiki.url)
.call(svgIcon('#iD-icon-out-link', 'inline'))
.append('span')
.text(t(docs.wiki.text));
}
// Add link to info about "good changeset comments" - #2923
if (tag.key === 'comment') {
_body
.append('a')
.attr('class', 'tag-reference-comment-link')
.attr('target', '_blank')
.attr('tabindex', -1)
.call(svgIcon('#iD-icon-out-link', 'inline'))
.attr('href', t('commit.about_changeset_comments_link'))
.append('span')
.text(t('commit.about_changeset_comments'));
}
});
.call(svgIcon('#iD-icon-out-link', 'inline'))
.attr('href', t('commit.about_changeset_comments_link'))
.append('span')
.text(t('commit.about_changeset_comments'));
}
}
@@ -143,6 +151,7 @@ export function uiTagReference(tag) {
.on('click', function () {
d3_event.stopPropagation();
d3_event.preventDefault();
this.blur(); // avoid keeping focus on the button - #4641
if (_showing) {
hide();
} else if (_loaded) {
@@ -155,9 +164,9 @@ export function uiTagReference(tag) {
tagReference.body = function(selection) {
var tagid = tag.rtype || (tag.key + '-' + tag.value);
var itemID = what.qid || what.rtype || (what.key + '-' + what.value);
_body = selection.selectAll('.tag-reference-body')
.data([tagid], function(d) { return d; });
.data([itemID], function(d) { return d; });
_body.exit()
.remove();
+65 -52
View File
@@ -1,6 +1,6 @@
describe('iD.Features', function() {
var dimensions = [1000, 1000],
context, features;
var dimensions = [1000, 1000];
var context, features;
function _values(obj) {
var result = [];
@@ -64,18 +64,18 @@ describe('iD.Features', function() {
describe('#gatherStats', function() {
it('counts features', function() {
var graph = iD.coreGraph([
iD.osmNode({id: 'point_bar', tags: {amenity: 'bar'}, version: 1}),
iD.osmNode({id: 'point_dock', tags: {waterway: 'dock'}, version: 1}),
iD.osmNode({id: 'point_rail_station', tags: {railway: 'station'}, version: 1}),
iD.osmNode({id: 'point_generator', tags: {power: 'generator'}, version: 1}),
iD.osmNode({id: 'point_old_rail_station', tags: {railway: 'station', disused: 'yes'}, version: 1}),
iD.osmWay({id: 'motorway', tags: {highway: 'motorway'}, version: 1}),
iD.osmWay({id: 'building_yes', tags: {area: 'yes', amenity: 'school', building: 'yes'}, version: 1}),
iD.osmWay({id: 'boundary', tags: {boundary: 'administrative'}, version: 1}),
iD.osmWay({id: 'fence', tags: {barrier: 'fence'}, version: 1})
]),
all = _values(graph.base().entities),
stats;
iD.osmNode({id: 'point_bar', tags: {amenity: 'bar'}, version: 1}),
iD.osmNode({id: 'point_dock', tags: {waterway: 'dock'}, version: 1}),
iD.osmNode({id: 'point_rail_station', tags: {railway: 'station'}, version: 1}),
iD.osmNode({id: 'point_generator', tags: {power: 'generator'}, version: 1}),
iD.osmNode({id: 'point_old_rail_station', tags: {railway: 'station', disused: 'yes'}, version: 1}),
iD.osmWay({id: 'motorway', tags: {highway: 'motorway'}, version: 1}),
iD.osmWay({id: 'building_yes', tags: {area: 'yes', amenity: 'school', building: 'yes'}, version: 1}),
iD.osmWay({id: 'boundary', tags: {boundary: 'administrative'}, version: 1}),
iD.osmWay({id: 'fence', tags: {barrier: 'fence'}, version: 1})
]);
var all = _values(graph.base().entities);
var stats;
features.gatherStats(all, graph, dimensions);
stats = features.stats();
@@ -205,8 +205,8 @@ describe('iD.Features', function() {
version: 1
})
]),
all = _values(graph.base().entities);
]);
var all = _values(graph.base().entities);
function doMatch(ids) {
@@ -435,12 +435,12 @@ describe('iD.Features', function() {
describe('hiding', function() {
it('hides child vertices on a hidden way', function() {
var a = iD.osmNode({id: 'a', version: 1}),
b = iD.osmNode({id: 'b', version: 1}),
w = iD.osmWay({id: 'w', nodes: [a.id, b.id], tags: {highway: 'path'}, version: 1}),
graph = iD.coreGraph([a, b, w]),
geometry = a.geometry(graph),
all = _values(graph.base().entities);
var a = iD.osmNode({id: 'a', version: 1});
var b = iD.osmNode({id: 'b', version: 1});
var w = iD.osmWay({id: 'w', nodes: [a.id, b.id], tags: {highway: 'path'}, version: 1});
var graph = iD.coreGraph([a, b, w]);
var geometry = a.geometry(graph);
var all = _values(graph.base().entities);
features.disable('paths');
features.gatherStats(all, graph, dimensions);
@@ -452,23 +452,23 @@ describe('iD.Features', function() {
});
it('hides uninteresting (e.g. untagged or "other") member ways on a hidden multipolygon relation', function() {
var outer = iD.osmWay({id: 'outer', tags: {area: 'yes', natural: 'wood'}, version: 1}),
inner1 = iD.osmWay({id: 'inner1', tags: {barrier: 'fence'}, version: 1}),
inner2 = iD.osmWay({id: 'inner2', version: 1}),
inner3 = iD.osmWay({id: 'inner3', tags: {highway: 'residential'}, version: 1}),
r = iD.osmRelation({
id: 'r',
tags: {type: 'multipolygon'},
members: [
{id: outer.id, role: 'outer', type: 'way'},
{id: inner1.id, role: 'inner', type: 'way'},
{id: inner2.id, role: 'inner', type: 'way'},
{id: inner3.id, role: 'inner', type: 'way'}
],
version: 1
}),
graph = iD.coreGraph([outer, inner1, inner2, inner3, r]),
all = _values(graph.base().entities);
var outer = iD.osmWay({id: 'outer', tags: {area: 'yes', natural: 'wood'}, version: 1});
var inner1 = iD.osmWay({id: 'inner1', tags: {barrier: 'fence'}, version: 1});
var inner2 = iD.osmWay({id: 'inner2', version: 1});
var inner3 = iD.osmWay({id: 'inner3', tags: {highway: 'residential'}, version: 1});
var r = iD.osmRelation({
id: 'r',
tags: {type: 'multipolygon'},
members: [
{id: outer.id, role: 'outer', type: 'way'},
{id: inner1.id, role: 'inner', type: 'way'},
{id: inner2.id, role: 'inner', type: 'way'},
{id: inner3.id, role: 'inner', type: 'way'}
],
version: 1
});
var graph = iD.coreGraph([outer, inner1, inner2, inner3, r]);
var all = _values(graph.base().entities);
features.disable('landuse');
features.gatherStats(all, graph, dimensions);
@@ -480,12 +480,12 @@ describe('iD.Features', function() {
});
it('hides only versioned entities', function() {
var a = iD.osmNode({id: 'a', version: 1}),
b = iD.osmNode({id: 'b'}),
graph = iD.coreGraph([a, b]),
ageo = a.geometry(graph),
bgeo = b.geometry(graph),
all = _values(graph.base().entities);
var a = iD.osmNode({id: 'a', version: 1});
var b = iD.osmNode({id: 'b'});
var graph = iD.coreGraph([a, b]);
var ageo = a.geometry(graph);
var bgeo = b.geometry(graph);
var all = _values(graph.base().entities);
features.disable('points');
features.gatherStats(all, graph, dimensions);
@@ -494,10 +494,23 @@ describe('iD.Features', function() {
expect(features.isHidden(b, graph, bgeo)).to.be.false;
});
it('#forceVisible', function() {
var a = iD.osmNode({id: 'a', version: 1});
var graph = iD.coreGraph([a]);
var ageo = a.geometry(graph);
var all = _values(graph.base().entities);
features.disable('points');
features.gatherStats(all, graph, dimensions);
features.forceVisible(['a']);
expect(features.isHidden(a, graph, ageo)).to.be.false;
});
it('auto-hides features', function() {
var graph = iD.coreGraph([]),
maxPoints = 200,
all, hidden, autoHidden, i, msg;
var graph = iD.coreGraph([]);
var maxPoints = 200;
var all, hidden, autoHidden, i, msg;
for (i = 0; i < maxPoints; i++) {
graph.rebase([iD.osmNode({version: 1})], [graph]);
@@ -525,10 +538,10 @@ describe('iD.Features', function() {
});
it('doubles auto-hide threshold when doubling viewport size', function() {
var graph = iD.coreGraph([]),
maxPoints = 400,
dimensions = [2000, 1000],
all, hidden, autoHidden, i, msg;
var graph = iD.coreGraph([]);
var maxPoints = 400;
var dimensions = [2000, 1000];
var all, hidden, autoHidden, i, msg;
for (i = 0; i < maxPoints; i++) {
graph.rebase([iD.osmNode({version: 1})], [graph]);
+1 -1
View File
@@ -53,7 +53,7 @@ describe('iD.uiRawTagEditor', function() {
expect(tags).to.eql({highway: undefined});
done();
});
iD.utilTriggerEvent(element.selectAll('button.remove'), 'click');
iD.utilTriggerEvent(element.selectAll('button.remove'), 'mousedown');
});
it('adds tags when pressing the TAB key on last input.value', function (done) {