mirror of
https://github.com/FoggedLens/iD.git
synced 2026-04-25 21:26:29 +02:00
Add mechanism for fields to support editing during multiselection (re: #7276)
Add `utilCombinedTags` method and use it for the raw tag editor as well as fields Pass `entityIDs` array into fields instead of single `entity` object Give field revertion its own path separate from `change` Add multiselection editing to fields in files: access, address, check, combo, cycleway, input, maxspeed, textarea, and wikidata
This commit is contained in:
+13
-3
@@ -220,6 +220,11 @@ input[type="radio"] {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
input.mixed::placeholder,
|
||||
textarea.mixed::placeholder {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* tables */
|
||||
table {
|
||||
background-color: #fff;
|
||||
@@ -1591,6 +1596,11 @@ a.hide-toggle {
|
||||
z-index: 3000;
|
||||
cursor: grabbing;
|
||||
}
|
||||
.form-field-input-multicombo li.mixed {
|
||||
border-color: #eff2f7;
|
||||
color: #888;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.form-field-input-multicombo li.chip span {
|
||||
display: block;
|
||||
@@ -1708,6 +1718,9 @@ a.hide-toggle {
|
||||
.form-field-input-check > span {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
.form-field-input-check > span.mixed {
|
||||
font-style: italic;
|
||||
}
|
||||
.form-field-input-check > .reverser.button {
|
||||
flex: 0 1 auto;
|
||||
background-color: #eff2f7;
|
||||
@@ -2371,9 +2384,6 @@ button.raw-tag-option svg.icon {
|
||||
[dir='rtl'] .tag-row input.value {
|
||||
border-left: 1px solid #ccc;
|
||||
}
|
||||
.tag-row input.value.conflicting::placeholder {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.tag-row:first-child input.key {
|
||||
border-top: 1px solid #ccc;
|
||||
|
||||
@@ -13,6 +13,12 @@ export function presetField(id, field) {
|
||||
return !field.geometry || field.geometry === geometry;
|
||||
};
|
||||
|
||||
field.matchAllGeometry = function(geometries) {
|
||||
return !field.geometry || geometries.every(function(geometry) {
|
||||
return field.geometry.indexOf(geometry) !== -1;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
field.t = function(scope, options) {
|
||||
return t('presets.fields.' + id + '.' + scope, options);
|
||||
|
||||
@@ -19,7 +19,7 @@ import { uiEntityIssues } from './entity_issues';
|
||||
import { uiSelectionList } from './selection_list';
|
||||
import { uiTooltipHtml } from './tooltipHtml';
|
||||
import { utilArrayIdentical } from '../util/array';
|
||||
import { utilCleanTags, utilRebind } from '../util';
|
||||
import { utilCleanTags, utilCombinedTags, utilRebind } from '../util';
|
||||
|
||||
|
||||
export function uiEntityEditor(context) {
|
||||
@@ -36,7 +36,7 @@ export function uiEntityEditor(context) {
|
||||
var selectionList = uiSelectionList(context);
|
||||
var entityIssues = uiEntityIssues(context);
|
||||
var quickLinks = uiQuickLinks();
|
||||
var presetEditor = uiPresetEditor(context).on('change', changeTags);
|
||||
var presetEditor = uiPresetEditor(context).on('change', changeTags).on('revert', revertTags);
|
||||
var rawTagEditor = uiRawTagEditor(context).on('change', changeTags);
|
||||
var rawMemberEditor = uiRawMemberEditor(context);
|
||||
var rawMembershipEditor = uiRawMembershipEditor(context);
|
||||
@@ -46,6 +46,8 @@ export function uiEntityEditor(context) {
|
||||
var singularEntityID = _entityIDs.length === 1 && _entityIDs[0];
|
||||
var singularEntity = singularEntityID && context.entity(singularEntityID);
|
||||
|
||||
var combinedTags = utilCombinedTags(_entityIDs, context.graph());
|
||||
|
||||
// Header
|
||||
var header = selection.selectAll('.header')
|
||||
.data([0]);
|
||||
@@ -234,13 +236,13 @@ export function uiEntityEditor(context) {
|
||||
}
|
||||
}, {
|
||||
klass: 'preset-editor',
|
||||
shouldHave: singularEntityID,
|
||||
shouldHave: true,
|
||||
update: function(section) {
|
||||
section
|
||||
.call(presetEditor
|
||||
.preset(_activePresets[0])
|
||||
.entityID(singularEntityID)
|
||||
.tags(Object.assign({}, singularEntity.tags))
|
||||
.presets(_activePresets)
|
||||
.entityIDs(_entityIDs)
|
||||
.tags(combinedTags)
|
||||
.state(_state)
|
||||
);
|
||||
}
|
||||
@@ -252,6 +254,7 @@ export function uiEntityEditor(context) {
|
||||
.call(rawTagEditor
|
||||
.preset(_activePresets[0])
|
||||
.entityIDs(_entityIDs)
|
||||
.tags(combinedTags)
|
||||
.state(_state)
|
||||
);
|
||||
}
|
||||
@@ -410,6 +413,60 @@ export function uiEntityEditor(context) {
|
||||
}
|
||||
}
|
||||
|
||||
function revertTags(keys) {
|
||||
|
||||
var actions = [];
|
||||
for (var i in _entityIDs) {
|
||||
var entityID = _entityIDs[i];
|
||||
|
||||
var original = context.graph().base().entities[entityID];
|
||||
var changed = {};
|
||||
for (var j in keys) {
|
||||
var key = keys[j];
|
||||
changed[key] = original ? original.tags[key] : undefined;
|
||||
}
|
||||
|
||||
var entity = context.entity(entityID);
|
||||
var tags = Object.assign({}, entity.tags); // shallow copy
|
||||
|
||||
for (var k in changed) {
|
||||
if (!k) continue;
|
||||
var v = changed[k];
|
||||
if (v !== undefined || tags.hasOwnProperty(k)) {
|
||||
tags[k] = v;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
tags = utilCleanTags(tags);
|
||||
|
||||
if (!deepEqual(entity.tags, tags)) {
|
||||
actions.push(actionChangeTags(entityID, tags));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (actions.length) {
|
||||
var combinedAction = function(graph) {
|
||||
actions.forEach(function(action) {
|
||||
graph = action(graph);
|
||||
});
|
||||
return graph;
|
||||
};
|
||||
|
||||
var annotation = t('operations.change_tags.annotation');
|
||||
|
||||
if (_coalesceChanges) {
|
||||
context.overwrite(combinedAction, annotation);
|
||||
} else {
|
||||
context.perform(combinedAction, annotation);
|
||||
_coalesceChanges = false;
|
||||
}
|
||||
}
|
||||
|
||||
context.validator().validate();
|
||||
}
|
||||
|
||||
|
||||
entityEditor.modified = function(val) {
|
||||
if (!arguments.length) return _modified;
|
||||
|
||||
+44
-33
@@ -6,13 +6,14 @@ import { t } from '../util/locale';
|
||||
import { textDirection } from '../util/locale';
|
||||
import { svgIcon } from '../svg/icon';
|
||||
import { tooltip } from '../util/tooltip';
|
||||
import { geoExtent } from '../geo/extent';
|
||||
import { uiFieldHelp } from './field_help';
|
||||
import { uiFields } from './fields';
|
||||
import { uiTagReference } from './tag_reference';
|
||||
import { utilRebind } from '../util';
|
||||
|
||||
|
||||
export function uiField(context, presetField, entity, options) {
|
||||
export function uiField(context, presetField, entityIDs, options) {
|
||||
options = Object.assign({
|
||||
show: true,
|
||||
wrap: true,
|
||||
@@ -21,7 +22,7 @@ export function uiField(context, presetField, entity, options) {
|
||||
info: true
|
||||
}, options);
|
||||
|
||||
var dispatch = d3_dispatch('change');
|
||||
var dispatch = d3_dispatch('change', 'revert');
|
||||
var field = Object.assign({}, presetField); // shallow copy
|
||||
var _show = options.show;
|
||||
var _state = '';
|
||||
@@ -48,21 +49,24 @@ export function uiField(context, presetField, entity, options) {
|
||||
dispatch.call('change', field, t, onInput);
|
||||
});
|
||||
|
||||
if (entity) {
|
||||
field.entityID = entity.id;
|
||||
// if this field cares about the entity, pass it along
|
||||
if (field.impl.entity) {
|
||||
field.impl.entity(entity);
|
||||
if (entityIDs) {
|
||||
field.entityIDs = entityIDs;
|
||||
// if this field cares about the entities, pass them along
|
||||
if (field.impl.entityIDs) {
|
||||
field.impl.entityIDs(entityIDs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function isModified() {
|
||||
if (!entity) return false;
|
||||
var original = context.graph().base().entities[entity.id];
|
||||
return field.keys.some(function(key) {
|
||||
return original ? _tags[key] !== original.tags[key] : _tags[key];
|
||||
if (!entityIDs || !entityIDs.length) return false;
|
||||
return entityIDs.some(function(entityID) {
|
||||
var original = context.graph().base().entities[entityID];
|
||||
var latest = context.graph().entity(entityID);
|
||||
return field.keys.some(function(key) {
|
||||
return original ? latest.tags[key] !== original.tags[key] : latest.tags[key];
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -85,15 +89,9 @@ export function uiField(context, presetField, entity, options) {
|
||||
function revert(d) {
|
||||
d3_event.stopPropagation();
|
||||
d3_event.preventDefault();
|
||||
if (!entity || _locked) return;
|
||||
if (!entityIDs || _locked) return;
|
||||
|
||||
var original = context.graph().base().entities[entity.id];
|
||||
var t = {};
|
||||
d.keys.forEach(function(key) {
|
||||
t[key] = original ? original.tags[key] : undefined;
|
||||
});
|
||||
|
||||
dispatch.call('change', d, t);
|
||||
dispatch.call('revert', d, d.keys);
|
||||
}
|
||||
|
||||
|
||||
@@ -297,11 +295,13 @@ export function uiField(context, presetField, entity, options) {
|
||||
// A non-allowed field is hidden from the user altogether
|
||||
field.isAllowed = function() {
|
||||
|
||||
var latest = entity && context.hasEntity(entity.id); // check the most current copy of the entity
|
||||
if (!latest) return true;
|
||||
if (entityIDs.length > 1 && uiFields[field.type].supportsMultiselection === false) return;
|
||||
|
||||
if (field.countryCodes || field.notCountryCodes) {
|
||||
var center = latest.extent(context.graph()).center();
|
||||
var extent = combinedEntityExtent();
|
||||
if (!extent) return true;
|
||||
|
||||
var center = extent.center();
|
||||
var countryCode = countryCoder.iso1A2Code(center);
|
||||
|
||||
if (!countryCode) return false;
|
||||
@@ -320,19 +320,22 @@ export function uiField(context, presetField, entity, options) {
|
||||
|
||||
if (!tagsContainFieldKey() && // ignore tagging prerequisites if a value is already present
|
||||
prerequisiteTag) {
|
||||
if (prerequisiteTag.key) {
|
||||
var value = latest.tags[prerequisiteTag.key];
|
||||
if (!value) return false;
|
||||
return entityIDs.some(function(entityID) {
|
||||
var entity = context.graph().entity(entityID);
|
||||
if (prerequisiteTag.key) {
|
||||
var value = entity.tags[prerequisiteTag.key];
|
||||
if (!value) return false;
|
||||
|
||||
if (prerequisiteTag.valueNot) {
|
||||
return prerequisiteTag.valueNot !== value;
|
||||
if (prerequisiteTag.valueNot) {
|
||||
return prerequisiteTag.valueNot !== value;
|
||||
}
|
||||
if (prerequisiteTag.value) {
|
||||
return prerequisiteTag.value === value;
|
||||
}
|
||||
} else if (prerequisiteTag.keyNot) {
|
||||
if (entity.tags[prerequisiteTag.keyNot]) return false;
|
||||
}
|
||||
if (prerequisiteTag.value) {
|
||||
return prerequisiteTag.value === value;
|
||||
}
|
||||
} else if (prerequisiteTag.keyNot) {
|
||||
if (latest.tags[prerequisiteTag.keyNot]) return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -346,5 +349,13 @@ export function uiField(context, presetField, entity, options) {
|
||||
};
|
||||
|
||||
|
||||
function combinedEntityExtent() {
|
||||
return entityIDs && entityIDs.length && entityIDs.reduce(function(extent, entityID) {
|
||||
var entity = context.graph().entity(entityID);
|
||||
return extent.extend(entity.extent(context.graph()));
|
||||
}, geoExtent());
|
||||
}
|
||||
|
||||
|
||||
return utilRebind(field, dispatch, 'on');
|
||||
}
|
||||
|
||||
+50
-20
@@ -3,11 +3,12 @@ import { select as d3_select } from 'd3-selection';
|
||||
|
||||
import { uiCombobox } from '../combobox';
|
||||
import { utilGetSetValue, utilNoAuto, utilRebind } from '../../util';
|
||||
|
||||
import { t } from '../../util/locale';
|
||||
|
||||
export function uiFieldAccess(field, context) {
|
||||
var dispatch = d3_dispatch('change');
|
||||
var items = d3_select(null);
|
||||
var _tags;
|
||||
|
||||
function access(selection) {
|
||||
var wrap = selection.selectAll('.form-field-input-wrap')
|
||||
@@ -68,7 +69,12 @@ export function uiFieldAccess(field, context) {
|
||||
|
||||
function change(d) {
|
||||
var tag = {};
|
||||
tag[d] = utilGetSetValue(d3_select(this)) || undefined;
|
||||
var value = utilGetSetValue(d3_select(this));
|
||||
|
||||
// don't override multiple values with blank string
|
||||
if (!value && typeof _tags[d] !== 'string') return;
|
||||
|
||||
tag[d] = value || undefined;
|
||||
dispatch.call('change', this, tag);
|
||||
}
|
||||
|
||||
@@ -94,7 +100,7 @@ export function uiFieldAccess(field, context) {
|
||||
};
|
||||
|
||||
|
||||
var placeholders = {
|
||||
var placeholdersByHighway = {
|
||||
footway: {
|
||||
foot: 'designated',
|
||||
motor_vehicle: 'no'
|
||||
@@ -199,24 +205,48 @@ export function uiFieldAccess(field, context) {
|
||||
|
||||
|
||||
access.tags = function(tags) {
|
||||
utilGetSetValue(items.selectAll('.preset-input-access'),
|
||||
function(d) { return tags[d] || ''; })
|
||||
.attr('placeholder', function() {
|
||||
return tags.access ? tags.access : field.placeholder();
|
||||
_tags = tags;
|
||||
|
||||
utilGetSetValue(items.selectAll('.preset-input-access'), function(d) {
|
||||
return typeof tags[d] === 'string' ? tags[d] : '';
|
||||
})
|
||||
.classed('mixed', function(d) {
|
||||
return tags[d] && Array.isArray(tags[d]);
|
||||
})
|
||||
.attr('title', function(d) {
|
||||
return tags[d] && Array.isArray(tags[d]) && tags[d].filter(Boolean).join('; ');
|
||||
})
|
||||
.attr('placeholder', function(d) {
|
||||
if (tags[d] && Array.isArray(tags[d])) {
|
||||
return t('inspector.multiple_values');
|
||||
}
|
||||
if (d === 'access') {
|
||||
return 'yes';
|
||||
}
|
||||
if (tags.access && typeof tags.access === 'string') {
|
||||
return tags.access;
|
||||
}
|
||||
if (tags.highway) {
|
||||
if (typeof tags.highway === 'string') {
|
||||
if (placeholdersByHighway[tags.highway] &&
|
||||
placeholdersByHighway[tags.highway][d]) {
|
||||
|
||||
return placeholdersByHighway[tags.highway][d];
|
||||
}
|
||||
} else {
|
||||
var impliedAccesses = tags.highway.filter(Boolean).map(function(highwayVal) {
|
||||
return placeholdersByHighway[highwayVal] && placeholdersByHighway[highwayVal][d];
|
||||
}).filter(Boolean);
|
||||
|
||||
if (impliedAccesses.length === tags.highway.length &&
|
||||
new Set(impliedAccesses).size === 1) {
|
||||
// if all the highway values have the same implied access for this type then use that
|
||||
return impliedAccesses[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
return field.placeholder();
|
||||
});
|
||||
|
||||
items.selectAll('.preset-input-access-access')
|
||||
.attr('placeholder', 'yes');
|
||||
|
||||
var which = tags.highway;
|
||||
if (!placeholders[which]) return;
|
||||
|
||||
var keys = Object.keys(placeholders[which]);
|
||||
keys.forEach(function(k) {
|
||||
var v = placeholders[which][k];
|
||||
items.selectAll('.preset-input-access-' + k)
|
||||
.attr('placeholder', tags.access || v);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -14,7 +14,9 @@ export function uiFieldAddress(field, context) {
|
||||
var addrField = context.presets().field('address'); // needed for placeholder strings
|
||||
|
||||
var _isInitialized = false;
|
||||
var _entity;
|
||||
var _entityIDs = [];
|
||||
var _tags;
|
||||
var _countryCode;
|
||||
var _addressFormats = [{
|
||||
format: [
|
||||
['housenumber', 'street'],
|
||||
@@ -28,7 +30,7 @@ export function uiFieldAddress(field, context) {
|
||||
|
||||
|
||||
function getNearStreets() {
|
||||
var extent = _entity.extent(context.graph());
|
||||
var extent = combinedEntityExtent();
|
||||
var l = extent.center();
|
||||
var box = geoExtent(l).padByMeters(200);
|
||||
|
||||
@@ -60,7 +62,7 @@ export function uiFieldAddress(field, context) {
|
||||
|
||||
|
||||
function getNearCities() {
|
||||
var extent = _entity.extent(context.graph());
|
||||
var extent = combinedEntityExtent();
|
||||
var l = extent.center();
|
||||
var box = geoExtent(l).padByMeters(200);
|
||||
|
||||
@@ -98,12 +100,12 @@ export function uiFieldAddress(field, context) {
|
||||
}
|
||||
|
||||
function getNearValues(key) {
|
||||
var extent = _entity.extent(context.graph());
|
||||
var extent = combinedEntityExtent();
|
||||
var l = extent.center();
|
||||
var box = geoExtent(l).padByMeters(200);
|
||||
|
||||
var results = context.intersects(box)
|
||||
.filter(function hasTag(d) { return d.id !== _entity.id && d.tags[key]; })
|
||||
.filter(function hasTag(d) { return _entityIDs.indexOf(d.id) === -1 && d.tags[key]; })
|
||||
.map(function(d) {
|
||||
return {
|
||||
title: d.tags[key],
|
||||
@@ -119,15 +121,16 @@ export function uiFieldAddress(field, context) {
|
||||
}
|
||||
|
||||
|
||||
function updateForCountryCode(countryCode) {
|
||||
countryCode = countryCode.toLowerCase();
|
||||
function updateForCountryCode() {
|
||||
|
||||
if (!_countryCode) return;
|
||||
|
||||
var addressFormat;
|
||||
for (var i = 0; i < _addressFormats.length; i++) {
|
||||
var format = _addressFormats[i];
|
||||
if (!format.countryCodes) {
|
||||
addressFormat = format; // choose the default format, keep going
|
||||
} else if (format.countryCodes.indexOf(countryCode) !== -1) {
|
||||
} else if (format.countryCodes.indexOf(_countryCode) !== -1) {
|
||||
addressFormat = format; // choose the country format, stop here
|
||||
break;
|
||||
}
|
||||
@@ -168,11 +171,7 @@ export function uiFieldAddress(field, context) {
|
||||
.enter()
|
||||
.append('input')
|
||||
.property('type', 'text')
|
||||
.attr('placeholder', function (d) {
|
||||
var localkey = d.id + '!' + countryCode;
|
||||
var tkey = addrField.strings.placeholders[localkey] ? localkey : d.id;
|
||||
return addrField.t('placeholders.' + tkey);
|
||||
})
|
||||
.call(updatePlaceholder)
|
||||
.attr('maxlength', context.maxCharsForTagValue())
|
||||
.attr('class', function (d) { return 'addr-' + d.id; })
|
||||
.call(utilNoAuto)
|
||||
@@ -220,16 +219,21 @@ export function uiFieldAddress(field, context) {
|
||||
.attr('class', 'form-field-input-wrap form-field-input-' + field.type)
|
||||
.merge(wrap);
|
||||
|
||||
if (_entity) {
|
||||
var extent = combinedEntityExtent();
|
||||
|
||||
if (extent) {
|
||||
var countryCode;
|
||||
if (context.inIntro()) {
|
||||
// localize the address format for the walkthrough
|
||||
countryCode = t('intro.graph.countrycode');
|
||||
} else {
|
||||
var center = _entity.extent(context.graph()).center();
|
||||
var center = extent.center();
|
||||
countryCode = countryCoder.iso1A2Code(center);
|
||||
}
|
||||
if (countryCode) updateForCountryCode(countryCode);
|
||||
if (countryCode) {
|
||||
_countryCode = countryCode.toLowerCase();
|
||||
updateForCountryCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,29 +244,65 @@ export function uiFieldAddress(field, context) {
|
||||
|
||||
wrap.selectAll('input')
|
||||
.each(function (subfield) {
|
||||
tags[field.key + ':' + subfield.id] = this.value || undefined;
|
||||
var key = field.key + ':' + subfield.id;
|
||||
|
||||
// don't override multiple values with blank string
|
||||
if (Array.isArray(_tags[key]) && !this.value) return;
|
||||
|
||||
tags[key] = this.value || undefined;
|
||||
});
|
||||
|
||||
dispatch.call('change', this, tags, onInput);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function updateTags(tags) {
|
||||
utilGetSetValue(wrap.selectAll('input'), function (subfield) {
|
||||
return tags[field.key + ':' + subfield.id] || '';
|
||||
function updatePlaceholder(inputSelection) {
|
||||
return inputSelection.attr('placeholder', function(subfield) {
|
||||
if (_tags && Array.isArray(_tags[field.key + ':' + subfield.id])) {
|
||||
return t('inspector.multiple_values');
|
||||
}
|
||||
if (_countryCode) {
|
||||
var localkey = subfield.id + '!' + _countryCode;
|
||||
var tkey = addrField.strings.placeholders[localkey] ? localkey : subfield.id;
|
||||
return addrField.t('placeholders.' + tkey);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
address.entity = function(val) {
|
||||
if (!arguments.length) return _entity;
|
||||
_entity = val;
|
||||
function updateTags(tags) {
|
||||
utilGetSetValue(wrap.selectAll('input'), function (subfield) {
|
||||
var val = tags[field.key + ':' + subfield.id];
|
||||
return typeof val === 'string' ? val : '';
|
||||
})
|
||||
.attr('title', function(subfield) {
|
||||
var val = tags[field.key + ':' + subfield.id];
|
||||
return val && Array.isArray(val) && val.filter(Boolean).join('; ');
|
||||
})
|
||||
.classed('mixed', function(subfield) {
|
||||
return Array.isArray(tags[field.key + ':' + subfield.id]);
|
||||
})
|
||||
.call(updatePlaceholder);
|
||||
}
|
||||
|
||||
|
||||
function combinedEntityExtent() {
|
||||
return _entityIDs && _entityIDs.length && _entityIDs.reduce(function(extent, entityID) {
|
||||
var entity = context.graph().entity(entityID);
|
||||
return extent.extend(entity.extent(context.graph()));
|
||||
}, geoExtent());
|
||||
}
|
||||
|
||||
|
||||
address.entityIDs = function(val) {
|
||||
if (!arguments.length) return _entityIDs;
|
||||
_entityIDs = val;
|
||||
return address;
|
||||
};
|
||||
|
||||
|
||||
address.tags = function(tags) {
|
||||
_tags = tags;
|
||||
if (_isInitialized) {
|
||||
updateTags(tags);
|
||||
} else {
|
||||
|
||||
+34
-12
@@ -21,13 +21,15 @@ export function uiFieldCheck(field, context) {
|
||||
var values = [];
|
||||
var texts = [];
|
||||
|
||||
var _tags;
|
||||
|
||||
var input = d3_select(null);
|
||||
var text = d3_select(null);
|
||||
var label = d3_select(null);
|
||||
var reverser = d3_select(null);
|
||||
|
||||
var _impliedYes;
|
||||
var _entityID;
|
||||
var _entityIDs = [];
|
||||
var _value;
|
||||
|
||||
|
||||
@@ -53,7 +55,7 @@ export function uiFieldCheck(field, context) {
|
||||
// hack: pretend `oneway` field is a `oneway_yes` field
|
||||
// where implied oneway tag exists (e.g. `junction=roundabout`) #2220, #1841
|
||||
if (field.id === 'oneway') {
|
||||
var entity = context.entity(_entityID);
|
||||
var entity = context.entity(_entityIDs[0]);
|
||||
for (var key in entity.tags) {
|
||||
if (key in osmOneWayTags && (entity.tags[key] in osmOneWayTags[key])) {
|
||||
_impliedYes = true;
|
||||
@@ -72,7 +74,7 @@ export function uiFieldCheck(field, context) {
|
||||
|
||||
|
||||
function reverserSetText(selection) {
|
||||
var entity = context.hasEntity(_entityID);
|
||||
var entity = _entityIDs.length && context.hasEntity(_entityIDs[0]);
|
||||
if (reverserHidden() || !entity) return selection;
|
||||
|
||||
var first = entity.first();
|
||||
@@ -127,7 +129,16 @@ export function uiFieldCheck(field, context) {
|
||||
.on('click', function() {
|
||||
d3_event.stopPropagation();
|
||||
var t = {};
|
||||
t[field.key] = values[(values.indexOf(_value) + 1) % values.length];
|
||||
|
||||
if (Array.isArray(_tags[field.key])) {
|
||||
if (values.indexOf('yes') !== -1) {
|
||||
t[field.key] = 'yes';
|
||||
} else {
|
||||
t[field.key] = values[0];
|
||||
}
|
||||
} else {
|
||||
t[field.key] = values[(values.indexOf(_value) + 1) % values.length];
|
||||
}
|
||||
|
||||
// Don't cycle through `alternating` or `reversible` states - #4970
|
||||
// (They are supported as translated strings, but should not toggle with clicks)
|
||||
@@ -147,10 +158,15 @@ export function uiFieldCheck(field, context) {
|
||||
d3_event.preventDefault();
|
||||
d3_event.stopPropagation();
|
||||
context.perform(
|
||||
actionReverse(_entityID),
|
||||
function(graph) {
|
||||
for (var i in _entityIDs) {
|
||||
graph = actionReverse(_entityIDs[i])(graph);
|
||||
}
|
||||
return graph;
|
||||
},
|
||||
t('operations.reverse.annotation')
|
||||
);
|
||||
|
||||
|
||||
// must manually revalidate since no 'change' event was called
|
||||
context.validator().validate();
|
||||
|
||||
@@ -161,15 +177,17 @@ export function uiFieldCheck(field, context) {
|
||||
};
|
||||
|
||||
|
||||
check.entity = function(_) {
|
||||
if (!arguments.length) return context.hasEntity(_entityID);
|
||||
_entityID = _.id;
|
||||
check.entityIDs = function(val) {
|
||||
if (!arguments.length) return _entityIDs;
|
||||
_entityIDs = val;
|
||||
return check;
|
||||
};
|
||||
|
||||
|
||||
check.tags = function(tags) {
|
||||
|
||||
_tags = tags;
|
||||
|
||||
function isChecked(val) {
|
||||
return val !== 'no' && val !== '' && val !== undefined && val !== null;
|
||||
}
|
||||
@@ -181,18 +199,22 @@ export function uiFieldCheck(field, context) {
|
||||
}
|
||||
|
||||
checkImpliedYes();
|
||||
_value = tags[field.key] && tags[field.key].toLowerCase();
|
||||
|
||||
var isMixed = Array.isArray(tags[field.key]);
|
||||
|
||||
_value = !isMixed && tags[field.key] && tags[field.key].toLowerCase();
|
||||
|
||||
if (field.type === 'onewayCheck' && (_value === '1' || _value === '-1')) {
|
||||
_value = 'yes';
|
||||
}
|
||||
|
||||
input
|
||||
.property('indeterminate', field.type !== 'defaultCheck' && !_value)
|
||||
.property('indeterminate', isMixed || (field.type !== 'defaultCheck' && !_value))
|
||||
.property('checked', isChecked(_value));
|
||||
|
||||
text
|
||||
.text(textFor(_value));
|
||||
.text(isMixed ? t('inspector.multiple_values') : textFor(_value))
|
||||
.classed('mixed', isMixed);
|
||||
|
||||
label
|
||||
.classed('set', !!_value);
|
||||
|
||||
+95
-32
@@ -3,6 +3,7 @@ import { event as d3_event, select as d3_select } from 'd3-selection';
|
||||
import { drag as d3_drag } from 'd3-drag';
|
||||
import * as countryCoder from '@ideditor/country-coder';
|
||||
|
||||
import { geoExtent } from '../../geo';
|
||||
import { osmEntity } from '../../osm/entity';
|
||||
import { t } from '../../util/locale';
|
||||
import { services } from '../../services';
|
||||
@@ -35,8 +36,10 @@ export function uiFieldCombo(field, context) {
|
||||
var input = d3_select(null);
|
||||
var _comboData = [];
|
||||
var _multiData = [];
|
||||
var _entity;
|
||||
var _entityIDs = [];
|
||||
var _tags;
|
||||
var _countryCode;
|
||||
var _staticPlaceholder;
|
||||
|
||||
// initialize deprecated tags array
|
||||
var _dataDeprecated = [];
|
||||
@@ -117,7 +120,9 @@ export function uiFieldCombo(field, context) {
|
||||
//
|
||||
function objectDifference(a, b) {
|
||||
return a.filter(function(d1) {
|
||||
return !b.some(function(d2) { return d1.value === d2.value; });
|
||||
return !b.some(function(d2) {
|
||||
return !d2.isMixed && d1.value === d2.value;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -182,8 +187,8 @@ export function uiFieldCombo(field, context) {
|
||||
query: query
|
||||
};
|
||||
|
||||
if (_entity) {
|
||||
params.geometry = context.geometry(_entity.id);
|
||||
if (_entityIDs.length) {
|
||||
params.geometry = context.geometry(_entityIDs[0]);
|
||||
}
|
||||
|
||||
taginfo[fn](params, function(err, data) {
|
||||
@@ -235,21 +240,27 @@ export function uiFieldCombo(field, context) {
|
||||
|
||||
|
||||
function setPlaceholder(values) {
|
||||
var ph;
|
||||
|
||||
if (isMulti || isSemi) {
|
||||
ph = field.placeholder() || t('inspector.add');
|
||||
_staticPlaceholder = field.placeholder() || t('inspector.add');
|
||||
} else {
|
||||
var vals = values
|
||||
.map(function(d) { return d.value; })
|
||||
.filter(function(s) { return s.length < 20; });
|
||||
|
||||
var placeholders = vals.length > 1 ? vals : values.map(function(d) { return d.key; });
|
||||
ph = field.placeholder() || placeholders.slice(0, 3).join(', ');
|
||||
_staticPlaceholder = field.placeholder() || placeholders.slice(0, 3).join(', ');
|
||||
}
|
||||
|
||||
if (!/(…|\.\.\.)$/.test(ph)) {
|
||||
ph += '…';
|
||||
if (!/(…|\.\.\.)$/.test(_staticPlaceholder)) {
|
||||
_staticPlaceholder += '…';
|
||||
}
|
||||
|
||||
var ph;
|
||||
if (!isMulti && !isSemi && _tags && Array.isArray(_tags[field.key])) {
|
||||
ph = t('inspector.multiple_values');
|
||||
} else {
|
||||
ph = _staticPlaceholder;
|
||||
}
|
||||
|
||||
container.selectAll('input')
|
||||
@@ -272,11 +283,11 @@ export function uiFieldCombo(field, context) {
|
||||
if (isMulti) {
|
||||
utilArrayUniq(vals).forEach(function(v) {
|
||||
var key = field.key + v;
|
||||
if (_entity) {
|
||||
if (_tags) {
|
||||
// don't set a multicombo value to 'yes' if it already has a non-'no' value
|
||||
// e.g. `language:de=main`
|
||||
var old = _entity.tags[key] || '';
|
||||
if (old && old.toLowerCase() !== 'no') return;
|
||||
var old = _tags[key];
|
||||
if (typeof old === 'string' && old.toLowerCase() !== 'no') return;
|
||||
}
|
||||
field.keys.push(key);
|
||||
t[key] = 'yes';
|
||||
@@ -291,7 +302,12 @@ export function uiFieldCombo(field, context) {
|
||||
window.setTimeout(function() { input.node().focus(); }, 10);
|
||||
|
||||
} else {
|
||||
val = tagValue(utilGetSetValue(input));
|
||||
var rawValue = utilGetSetValue(input);
|
||||
|
||||
// don't override multiple values with blank string
|
||||
if (!rawValue && Array.isArray(_tags[field.key])) return;
|
||||
|
||||
val = tagValue(rawValue);
|
||||
t[field.key] = val;
|
||||
}
|
||||
|
||||
@@ -371,9 +387,9 @@ export function uiFieldCombo(field, context) {
|
||||
.call(initCombo, selection)
|
||||
.merge(input);
|
||||
|
||||
if (isNetwork && _entity) {
|
||||
var center = _entity.extent(context.graph()).center();
|
||||
var countryCode = countryCoder.iso1A2Code(center);
|
||||
if (isNetwork) {
|
||||
var extent = combinedEntityExtent();
|
||||
var countryCode = extent && countryCoder.iso1A2Code(extent.center());
|
||||
_countryCode = countryCode && countryCode.toLowerCase();
|
||||
}
|
||||
|
||||
@@ -405,6 +421,8 @@ export function uiFieldCombo(field, context) {
|
||||
|
||||
|
||||
combo.tags = function(tags) {
|
||||
_tags = tags;
|
||||
|
||||
if (isMulti || isSemi) {
|
||||
_multiData = [];
|
||||
|
||||
@@ -414,13 +432,14 @@ export function uiFieldCombo(field, context) {
|
||||
// Build _multiData array containing keys already set..
|
||||
for (var k in tags) {
|
||||
if (k.indexOf(field.key) !== 0) continue;
|
||||
var v = (tags[k] || '').toLowerCase();
|
||||
if (v === '' || v === 'no') continue;
|
||||
var v = tags[k];
|
||||
if (!v || (typeof v === 'string' && v.toLowerCase() === 'no')) continue;
|
||||
|
||||
var suffix = k.substring(field.key.length);
|
||||
_multiData.push({
|
||||
key: k,
|
||||
value: displayValue(suffix)
|
||||
value: displayValue(suffix),
|
||||
isMixed: Array.isArray(v)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -431,15 +450,36 @@ export function uiFieldCombo(field, context) {
|
||||
maxLength = context.maxCharsForTagKey() - field.key.length;
|
||||
|
||||
} else if (isSemi) {
|
||||
var arr = utilArrayUniq((tags[field.key] || '').split(';')).filter(Boolean);
|
||||
_multiData = arr.map(function(k) {
|
||||
|
||||
var allValues = [];
|
||||
var commonValues;
|
||||
if (Array.isArray(tags[field.key])) {
|
||||
|
||||
tags[field.key].forEach(function(tagValue) {
|
||||
var thisVals = utilArrayUniq((tagValue || '').split(';')).filter(Boolean);
|
||||
allValues = allValues.concat(thisVals);
|
||||
if (!commonValues) {
|
||||
commonValues = thisVals;
|
||||
} else {
|
||||
commonValues = commonValues.filter(value => thisVals.includes(value));
|
||||
}
|
||||
});
|
||||
allValues = utilArrayUniq(allValues).filter(Boolean);
|
||||
|
||||
} else {
|
||||
allValues = utilArrayUniq((tags[field.key] || '').split(';')).filter(Boolean);
|
||||
commonValues = allValues;
|
||||
}
|
||||
|
||||
_multiData = allValues.map(function(v) {
|
||||
return {
|
||||
key: k,
|
||||
value: displayValue(k)
|
||||
key: v,
|
||||
value: displayValue(v),
|
||||
isMixed: !commonValues.includes(v)
|
||||
};
|
||||
});
|
||||
|
||||
var currLength = arr.join(';').length;
|
||||
var currLength = commonValues.join(';').length;
|
||||
|
||||
// limit the input length to the remaining available characters
|
||||
maxLength = context.maxCharsForTagValue() - currLength;
|
||||
@@ -452,6 +492,9 @@ export function uiFieldCombo(field, context) {
|
||||
// a negative maxlength doesn't make sense
|
||||
maxLength = Math.max(0, maxLength);
|
||||
|
||||
var allowDragAndDrop = isSemi // only semiCombo values are ordered
|
||||
&& !Array.isArray(tags[field.key]);
|
||||
|
||||
// Exclude existing multikeys from combo options..
|
||||
var available = objectDifference(_comboData, _multiData);
|
||||
combobox.data(available);
|
||||
@@ -473,16 +516,19 @@ export function uiFieldCombo(field, context) {
|
||||
|
||||
var enter = chips.enter()
|
||||
.insert('li', '.input-wrap')
|
||||
.attr('class', 'chip')
|
||||
.classed('draggable', isSemi);
|
||||
.attr('class', 'chip');
|
||||
|
||||
enter.append('span');
|
||||
enter.append('a');
|
||||
|
||||
chips = chips.merge(enter)
|
||||
.order();
|
||||
.order()
|
||||
.classed('draggable', allowDragAndDrop)
|
||||
.classed('mixed', function(d) {
|
||||
return d.isMixed;
|
||||
});
|
||||
|
||||
if (isSemi) { // only semiCombo values are ordered
|
||||
if (allowDragAndDrop) {
|
||||
registerDragAndDrop(chips);
|
||||
}
|
||||
|
||||
@@ -498,7 +544,16 @@ export function uiFieldCombo(field, context) {
|
||||
.attr('maxlength', maxLength);
|
||||
|
||||
} else {
|
||||
utilGetSetValue(input, displayValue(tags[field.key]));
|
||||
var isMixed = Array.isArray(tags[field.key]);
|
||||
|
||||
var mixedValues = isMixed && tags[field.key].map(function(val) {
|
||||
return displayValue(val);
|
||||
}).filter(Boolean);
|
||||
|
||||
utilGetSetValue(input, !isMixed ? displayValue(tags[field.key]) : '')
|
||||
.attr('title', isMixed ? mixedValues.join('; ') : undefined)
|
||||
.attr('placeholder', isMixed ? t('inspector.multiple_values') : _staticPlaceholder || '')
|
||||
.classed('mixed', isMixed);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -627,12 +682,20 @@ export function uiFieldCombo(field, context) {
|
||||
};
|
||||
|
||||
|
||||
combo.entity = function(val) {
|
||||
if (!arguments.length) return _entity;
|
||||
_entity = val;
|
||||
combo.entityIDs = function(val) {
|
||||
if (!arguments.length) return _entityIDs;
|
||||
_entityIDs = val;
|
||||
return combo;
|
||||
};
|
||||
|
||||
|
||||
function combinedEntityExtent() {
|
||||
return _entityIDs && _entityIDs.length && _entityIDs.reduce(function(extent, entityID) {
|
||||
var entity = context.graph().entity(entityID);
|
||||
return extent.extend(entity.extent(context.graph()));
|
||||
}, geoExtent());
|
||||
}
|
||||
|
||||
|
||||
return utilRebind(combo, dispatch, 'on');
|
||||
}
|
||||
|
||||
@@ -3,12 +3,14 @@ import { select as d3_select } from 'd3-selection';
|
||||
|
||||
import { uiCombobox } from '../combobox';
|
||||
import { utilGetSetValue, utilNoAuto, utilRebind } from '../../util';
|
||||
import { t } from '../../util/locale';
|
||||
|
||||
|
||||
export function uiFieldCycleway(field, context) {
|
||||
var dispatch = d3_dispatch('change');
|
||||
var items = d3_select(null);
|
||||
var wrap = d3_select(null);
|
||||
var _tags;
|
||||
|
||||
function cycleway(selection) {
|
||||
|
||||
@@ -73,29 +75,40 @@ export function uiFieldCycleway(field, context) {
|
||||
}
|
||||
|
||||
|
||||
function change() {
|
||||
var left = utilGetSetValue(d3_select('.preset-input-cyclewayleft'));
|
||||
var right = utilGetSetValue(d3_select('.preset-input-cyclewayright'));
|
||||
function change(key) {
|
||||
|
||||
var newValue = utilGetSetValue(d3_select(this));
|
||||
|
||||
// don't override multiple values with blank string
|
||||
if (!newValue && (Array.isArray(_tags.cycleway) || Array.isArray(_tags[key]))) return;
|
||||
|
||||
if (newValue === 'none' || newValue === '') { newValue = undefined; }
|
||||
|
||||
var otherKey = key === 'cycleway:left' ? 'cycleway:right' : 'cycleway:left';
|
||||
var otherValue = typeof _tags.cycleway === 'string' ? _tags.cycleway : _tags[otherKey];
|
||||
if (otherValue && Array.isArray(otherValue)) {
|
||||
// we must always have an explicit value for comparison
|
||||
otherValue = otherValue[0];
|
||||
}
|
||||
if (otherValue === 'none' || otherValue === '') { otherValue = undefined; }
|
||||
|
||||
var tag = {};
|
||||
|
||||
if (left === 'none' || left === '') { left = undefined; }
|
||||
if (right === 'none' || right === '') { right = undefined; }
|
||||
|
||||
// Always set both left and right as changing one can affect the other
|
||||
tag = {
|
||||
cycleway: undefined,
|
||||
'cycleway:left': left,
|
||||
'cycleway:right': right
|
||||
};
|
||||
|
||||
// If the left and right tags match, use the cycleway tag to tag both
|
||||
// sides the same way
|
||||
if (left === right) {
|
||||
if (newValue === otherValue) {
|
||||
tag = {
|
||||
cycleway: left,
|
||||
cycleway: newValue,
|
||||
'cycleway:left': undefined,
|
||||
'cycleway:right': undefined
|
||||
};
|
||||
} else {
|
||||
// Always set both left and right as changing one can affect the other
|
||||
tag = {
|
||||
cycleway: undefined
|
||||
};
|
||||
tag[key] = newValue;
|
||||
tag[otherKey] = otherValue;
|
||||
}
|
||||
|
||||
dispatch.call('change', this, tag);
|
||||
@@ -113,14 +126,37 @@ export function uiFieldCycleway(field, context) {
|
||||
|
||||
|
||||
cycleway.tags = function(tags) {
|
||||
_tags = tags;
|
||||
|
||||
// If cycleway is set, use that instead of individual values
|
||||
var commonValue = typeof tags.cycleway === 'string' && tags.cycleway;
|
||||
|
||||
utilGetSetValue(items.selectAll('.preset-input-cycleway'), function(d) {
|
||||
// If cycleway is set, always return that
|
||||
if (tags.cycleway) {
|
||||
return tags.cycleway;
|
||||
}
|
||||
return tags[d] || '';
|
||||
if (commonValue) return commonValue;
|
||||
return !tags.cycleway && typeof tags[d] === 'string' ? tags[d] : '';
|
||||
})
|
||||
.attr('placeholder', field.placeholder());
|
||||
.attr('title', function(d) {
|
||||
if (Array.isArray(tags.cycleway) || Array.isArray(tags[d])) {
|
||||
var vals = [];
|
||||
if (Array.isArray(tags.cycleway)) {
|
||||
vals = vals.concat(tags.cycleway);
|
||||
}
|
||||
if (Array.isArray(tags[d])) {
|
||||
vals = vals.concat(tags[d]);
|
||||
}
|
||||
return vals.filter(Boolean).join('; ');
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.attr('placeholder', function(d) {
|
||||
if (Array.isArray(tags.cycleway) || Array.isArray(tags[d])) {
|
||||
return t('inspector.multiple_values');
|
||||
}
|
||||
return field.placeholder();
|
||||
})
|
||||
.classed('mixed', function(d) {
|
||||
return Array.isArray(tags.cycleway) || Array.isArray(tags[d]);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
+29
-11
@@ -3,6 +3,7 @@ import { select as d3_select, event as d3_event } from 'd3-selection';
|
||||
import * as countryCoder from '@ideditor/country-coder';
|
||||
|
||||
import { t, textDirection } from '../../util/locale';
|
||||
import { geoExtent } from '../../geo';
|
||||
import { utilGetSetValue, utilNoAuto, utilRebind } from '../../util';
|
||||
import { svgIcon } from '../../svg/icon';
|
||||
|
||||
@@ -19,7 +20,8 @@ export function uiFieldText(field, context) {
|
||||
var dispatch = d3_dispatch('change');
|
||||
var input = d3_select(null);
|
||||
var outlinkButton = d3_select(null);
|
||||
var _entity;
|
||||
var _entityIDs = [];
|
||||
var _tags;
|
||||
var _phoneFormats = {};
|
||||
|
||||
if (field.type === 'tel') {
|
||||
@@ -29,7 +31,8 @@ export function uiFieldText(field, context) {
|
||||
}
|
||||
|
||||
function i(selection) {
|
||||
var preset = _entity && context.presets().match(_entity, context.graph());
|
||||
var entity = _entityIDs.length && context.hasEntity(_entityIDs[0]);
|
||||
var preset = entity && context.presets().match(entity, context.graph());
|
||||
var isLocked = preset && preset.suggestion && field.id === 'brand';
|
||||
field.locked(isLocked);
|
||||
|
||||
@@ -50,7 +53,6 @@ export function uiFieldText(field, context) {
|
||||
.append('input')
|
||||
.attr('type', field.type === 'identifier' ? 'text' : field.type)
|
||||
.attr('id', fieldID)
|
||||
.attr('placeholder', field.placeholder() || t('inspector.unknown'))
|
||||
.attr('maxlength', context.maxCharsForTagValue())
|
||||
.classed(field.type, true)
|
||||
.call(utilNoAuto)
|
||||
@@ -64,9 +66,9 @@ export function uiFieldText(field, context) {
|
||||
.on('change', change());
|
||||
|
||||
|
||||
if (field.type === 'tel' && _entity) {
|
||||
var center = _entity.extent(context.graph()).center();
|
||||
var countryCode = countryCoder.iso1A2Code(center);
|
||||
if (field.type === 'tel') {
|
||||
var extent = combinedEntityExtent();
|
||||
var countryCode = extent && countryCoder.iso1A2Code(extent.center());
|
||||
var format = countryCode && _phoneFormats[countryCode.toLowerCase()];
|
||||
if (format) {
|
||||
wrap.selectAll('#' + fieldID)
|
||||
@@ -112,7 +114,6 @@ export function uiFieldText(field, context) {
|
||||
.attr('tabindex', -1)
|
||||
.call(svgIcon('#iD-icon-out-link'))
|
||||
.attr('class', 'form-field-button foreign-id-permalink')
|
||||
.classed('disabled', !validIdentifierValueForLink())
|
||||
.attr('title', function() {
|
||||
var domainResults = /^https?:\/\/(.{1,}?)\//.exec(field.urlFormat);
|
||||
if (domainResults.length >= 2 && domainResults[1]) {
|
||||
@@ -161,6 +162,9 @@ export function uiFieldText(field, context) {
|
||||
var t = {};
|
||||
var val = utilGetSetValue(input).trim() || undefined;
|
||||
|
||||
// don't override multiple values with blank string
|
||||
if (!val && Array.isArray(_tags[field.key])) return;
|
||||
|
||||
if (!onInput) {
|
||||
if (field.type === 'number' && val !== undefined) {
|
||||
var vals = val.split(';');
|
||||
@@ -178,15 +182,22 @@ export function uiFieldText(field, context) {
|
||||
}
|
||||
|
||||
|
||||
i.entity = function(val) {
|
||||
if (!arguments.length) return _entity;
|
||||
_entity = val;
|
||||
i.entityIDs = function(val) {
|
||||
if (!arguments.length) return _entityIDs;
|
||||
_entityIDs = val;
|
||||
return i;
|
||||
};
|
||||
|
||||
|
||||
i.tags = function(tags) {
|
||||
utilGetSetValue(input, tags[field.key] || '');
|
||||
_tags = tags;
|
||||
|
||||
var isMixed = Array.isArray(tags[field.key]);
|
||||
|
||||
utilGetSetValue(input, !isMixed && tags[field.key] ? tags[field.key] : '')
|
||||
.attr('title', isMixed ? tags[field.key].filter(Boolean).join('; ') : undefined)
|
||||
.attr('placeholder', isMixed ? t('inspector.multiple_values') : (field.placeholder() || t('inspector.unknown')))
|
||||
.classed('mixed', isMixed);
|
||||
|
||||
if (outlinkButton && !outlinkButton.empty()) {
|
||||
var disabled = !validIdentifierValueForLink();
|
||||
@@ -200,5 +211,12 @@ export function uiFieldText(field, context) {
|
||||
if (node) node.focus();
|
||||
};
|
||||
|
||||
function combinedEntityExtent() {
|
||||
return _entityIDs && _entityIDs.length && _entityIDs.reduce(function(extent, entityID) {
|
||||
var entity = context.graph().entity(entityID);
|
||||
return extent.extend(entity.extent(context.graph()));
|
||||
}, geoExtent());
|
||||
}
|
||||
|
||||
return utilRebind(i, dispatch, 'on');
|
||||
}
|
||||
|
||||
@@ -9,10 +9,10 @@ export function uiFieldLanes(field, context) {
|
||||
var dispatch = d3_dispatch('change');
|
||||
var LANE_WIDTH = 40;
|
||||
var LANE_HEIGHT = 200;
|
||||
var _entityID;
|
||||
var _entityIDs = [];
|
||||
|
||||
function lanes(selection) {
|
||||
var lanesData = context.entity(_entityID).lanes();
|
||||
var lanesData = context.entity(_entityIDs[0]).lanes();
|
||||
|
||||
if (!d3_select('.inspector-wrap.inspector-hidden').empty() || !selection.node().parentNode) {
|
||||
selection.call(lanes.off);
|
||||
@@ -122,10 +122,8 @@ export function uiFieldLanes(field, context) {
|
||||
}
|
||||
|
||||
|
||||
lanes.entity = function(val) {
|
||||
if (!_entityID || _entityID !== val.id) {
|
||||
_entityID = val.id;
|
||||
}
|
||||
lanes.entityIDs = function(val) {
|
||||
_entityIDs = val;
|
||||
};
|
||||
|
||||
lanes.tags = function() {};
|
||||
@@ -134,3 +132,5 @@ export function uiFieldLanes(field, context) {
|
||||
|
||||
return utilRebind(lanes, dispatch, 'on');
|
||||
}
|
||||
|
||||
uiFieldLanes.supportsMultiselection = false;
|
||||
|
||||
@@ -3,6 +3,7 @@ import { select as d3_select, event as d3_event } from 'd3-selection';
|
||||
import * as countryCoder from '@ideditor/country-coder';
|
||||
|
||||
import { currentLocale, t, languageName } from '../../util/locale';
|
||||
import { geoExtent } from '../../geo';
|
||||
import { services } from '../../services';
|
||||
import { svgIcon } from '../../svg';
|
||||
import { tooltip } from '../../util/tooltip';
|
||||
@@ -19,6 +20,7 @@ export function uiFieldLocalized(field, context) {
|
||||
var input = d3_select(null);
|
||||
var localizedInputs = d3_select(null);
|
||||
var _countryCode;
|
||||
var _tags;
|
||||
|
||||
context.data().get('languages')
|
||||
.then(loadLanguagesArray)
|
||||
@@ -49,7 +51,7 @@ export function uiFieldLocalized(field, context) {
|
||||
.title(t('translate.translate'))
|
||||
.placement('left');
|
||||
var _wikiTitles;
|
||||
var _entity;
|
||||
var _entityIDs = [];
|
||||
|
||||
|
||||
function loadLanguagesArray(dataLanguages) {
|
||||
@@ -77,18 +79,20 @@ export function uiFieldLocalized(field, context) {
|
||||
|
||||
|
||||
function calcLocked() {
|
||||
if (!_entity) { // the original entity
|
||||
if (!_entityIDs || _entityIDs.length !== 1) { // the original entity
|
||||
field.locked(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var latest = context.hasEntity(_entity.id);
|
||||
var latest = context.hasEntity(_entityIDs[0]);
|
||||
if (!latest) { // get current entity, possibly edited
|
||||
field.locked(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var hasOriginalName = (latest.tags.name && latest.tags.name === _entity.tags.name);
|
||||
var original = context.graph().base().entities[_entityIDs[0]];
|
||||
|
||||
var hasOriginalName = original && latest.tags.name && latest.tags.name === original.tags.name;
|
||||
var hasWikidata = latest.tags.wikidata || latest.tags['name:etymology:wikidata'];
|
||||
var preset = context.presets().match(latest, context.graph());
|
||||
var isSuggestion = preset && preset.suggestion;
|
||||
@@ -133,8 +137,8 @@ export function uiFieldLocalized(field, context) {
|
||||
_selection = selection;
|
||||
calcLocked();
|
||||
var isLocked = field.locked();
|
||||
var entity = _entity && context.hasEntity(_entity.id); // get latest
|
||||
var preset = entity && context.presets().match(entity, context.graph());
|
||||
var singularEntity = _entityIDs.length === 1 && context.hasEntity(_entityIDs[0]);
|
||||
var preset = singularEntity && context.presets().match(singularEntity, context.graph());
|
||||
|
||||
var wrap = selection.selectAll('.form-field-input-wrap')
|
||||
.data([0]);
|
||||
@@ -216,8 +220,8 @@ export function uiFieldLocalized(field, context) {
|
||||
.on('click', addNew);
|
||||
|
||||
|
||||
if (entity && !_multilingual.length) {
|
||||
calcMultilingual(entity.tags);
|
||||
if (_tags && !_multilingual.length) {
|
||||
calcMultilingual(_tags);
|
||||
}
|
||||
|
||||
localizedInputs = selection.selectAll('.localized-multilingual')
|
||||
@@ -241,7 +245,7 @@ export function uiFieldLocalized(field, context) {
|
||||
// (This can happen if the user actives the combo, arrows down, and then clicks off to blur)
|
||||
// So compare the current field value against the suggestions one last time.
|
||||
function checkBrandOnBlur() {
|
||||
var latest = context.hasEntity(_entity.id);
|
||||
var latest = _entityIDs.length === 1 && context.hasEntity(_entityIDs[0]);
|
||||
if (!latest) return; // deleting the entity blurred the field?
|
||||
|
||||
var preset = context.presets().match(latest, context.graph());
|
||||
@@ -260,12 +264,14 @@ export function uiFieldLocalized(field, context) {
|
||||
|
||||
|
||||
function acceptBrand(d) {
|
||||
if (!d) {
|
||||
|
||||
var entity = _entityIDs.length === 1 && context.hasEntity(_entityIDs[0]);
|
||||
|
||||
if (!d || !entity) {
|
||||
cancelBrand();
|
||||
return;
|
||||
}
|
||||
|
||||
var entity = context.entity(_entity.id); // get latest
|
||||
var tags = entity.tags;
|
||||
var geometry = entity.geometry(context.graph());
|
||||
var removed = preset.unsetTags(tags, geometry);
|
||||
@@ -554,6 +560,8 @@ export function uiFieldLocalized(field, context) {
|
||||
|
||||
|
||||
localized.tags = function(tags) {
|
||||
_tags = tags;
|
||||
|
||||
// Fetch translations from wikipedia
|
||||
if (tags.wikipedia && !_wikiTitles) {
|
||||
_wikiTitles = {};
|
||||
@@ -580,19 +588,28 @@ export function uiFieldLocalized(field, context) {
|
||||
};
|
||||
|
||||
|
||||
localized.entity = function(val) {
|
||||
if (!arguments.length) return _entity;
|
||||
_entity = val;
|
||||
localized.entityIDs = function(val) {
|
||||
if (!arguments.length) return _entityIDs;
|
||||
_entityIDs = val;
|
||||
_multilingual = [];
|
||||
loadCountryCode();
|
||||
return localized;
|
||||
};
|
||||
|
||||
function loadCountryCode() {
|
||||
var center = _entity.extent(context.graph()).center();
|
||||
var countryCode = countryCoder.iso1A2Code(center);
|
||||
var extent = combinedEntityExtent();
|
||||
var countryCode = extent && countryCoder.iso1A2Code(extent.center());
|
||||
_countryCode = countryCode && countryCode.toLowerCase();
|
||||
}
|
||||
|
||||
function combinedEntityExtent() {
|
||||
return _entityIDs && _entityIDs.length && _entityIDs.reduce(function(extent, entityID) {
|
||||
var entity = context.graph().entity(entityID);
|
||||
return extent.extend(entity.extent(context.graph()));
|
||||
}, geoExtent());
|
||||
}
|
||||
|
||||
return utilRebind(localized, dispatch, 'on');
|
||||
}
|
||||
|
||||
uiFieldLocalized.supportsMultiselection = false;
|
||||
|
||||
@@ -2,7 +2,9 @@ import { dispatch as d3_dispatch } from 'd3-dispatch';
|
||||
import { select as d3_select } from 'd3-selection';
|
||||
import * as countryCoder from '@ideditor/country-coder';
|
||||
|
||||
import { geoExtent } from '../../geo';
|
||||
import { uiCombobox } from '../combobox';
|
||||
import { t } from '../../util/locale';
|
||||
import { utilGetSetValue, utilNoAuto, utilRebind } from '../../util';
|
||||
|
||||
|
||||
@@ -10,7 +12,8 @@ export function uiFieldMaxspeed(field, context) {
|
||||
var dispatch = d3_dispatch('change');
|
||||
var unitInput = d3_select(null);
|
||||
var input = d3_select(null);
|
||||
var _entity;
|
||||
var _entityIDs = [];
|
||||
var _tags;
|
||||
var _isImperial;
|
||||
|
||||
var speedCombo = uiCombobox(context, 'maxspeed');
|
||||
@@ -40,7 +43,6 @@ export function uiFieldMaxspeed(field, context) {
|
||||
.attr('type', 'text')
|
||||
.attr('id', 'preset-input-' + field.safeid)
|
||||
.attr('maxlength', context.maxCharsForTagValue() - 4)
|
||||
.attr('placeholder', field.placeholder())
|
||||
.call(utilNoAuto)
|
||||
.call(speedCombo)
|
||||
.merge(input);
|
||||
@@ -49,8 +51,7 @@ export function uiFieldMaxspeed(field, context) {
|
||||
.on('change', change)
|
||||
.on('blur', change);
|
||||
|
||||
var loc = _entity.extent(context.graph()).center();
|
||||
|
||||
var loc = combinedEntityExtent().center();
|
||||
_isImperial = countryCoder.roadSpeedUnit(loc) === 'mph';
|
||||
|
||||
unitInput = wrap.selectAll('input.maxspeed-unit')
|
||||
@@ -71,13 +72,13 @@ export function uiFieldMaxspeed(field, context) {
|
||||
function changeUnits() {
|
||||
_isImperial = utilGetSetValue(unitInput) === 'mph';
|
||||
utilGetSetValue(unitInput, _isImperial ? 'mph' : 'km/h');
|
||||
setSuggestions();
|
||||
setUnitSuggestions();
|
||||
change();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function setSuggestions() {
|
||||
function setUnitSuggestions() {
|
||||
speedCombo.data((_isImperial ? imperialValues : metricValues).map(comboValues));
|
||||
utilGetSetValue(unitInput, _isImperial ? 'mph' : 'km/h');
|
||||
}
|
||||
@@ -95,6 +96,9 @@ export function uiFieldMaxspeed(field, context) {
|
||||
var tag = {};
|
||||
var value = utilGetSetValue(input);
|
||||
|
||||
// don't override multiple values with blank string
|
||||
if (!value && Array.isArray(_tags[field.key])) return;
|
||||
|
||||
if (!value) {
|
||||
tag[field.key] = undefined;
|
||||
} else if (isNaN(value) || !_isImperial) {
|
||||
@@ -108,17 +112,26 @@ export function uiFieldMaxspeed(field, context) {
|
||||
|
||||
|
||||
maxspeed.tags = function(tags) {
|
||||
var value = tags[field.key];
|
||||
_tags = tags;
|
||||
|
||||
if (value && value.indexOf('mph') >= 0) {
|
||||
value = parseInt(value, 10);
|
||||
_isImperial = true;
|
||||
} else if (value) {
|
||||
_isImperial = false;
|
||||
var value = tags[field.key];
|
||||
var isMixed = Array.isArray(value);
|
||||
|
||||
if (!isMixed) {
|
||||
if (value && value.indexOf('mph') >= 0) {
|
||||
value = parseInt(value, 10).toString();
|
||||
_isImperial = true;
|
||||
} else if (value) {
|
||||
_isImperial = false;
|
||||
}
|
||||
}
|
||||
|
||||
setSuggestions();
|
||||
utilGetSetValue(input, value || '');
|
||||
setUnitSuggestions();
|
||||
|
||||
utilGetSetValue(input, typeof value === 'string' ? value : '')
|
||||
.attr('title', isMixed ? value.filter(Boolean).join('; ') : null)
|
||||
.attr('placeholder', isMixed ? t('inspector.multiple_values') : field.placeholder())
|
||||
.classed('mixed', isMixed);
|
||||
};
|
||||
|
||||
|
||||
@@ -127,10 +140,18 @@ export function uiFieldMaxspeed(field, context) {
|
||||
};
|
||||
|
||||
|
||||
maxspeed.entity = function(val) {
|
||||
_entity = val;
|
||||
maxspeed.entityIDs = function(val) {
|
||||
_entityIDs = val;
|
||||
};
|
||||
|
||||
|
||||
function combinedEntityExtent() {
|
||||
return _entityIDs && _entityIDs.length && _entityIDs.reduce(function(extent, entityID) {
|
||||
var entity = context.graph().entity(entityID);
|
||||
return extent.extend(entity.extent(context.graph()));
|
||||
}, geoExtent());
|
||||
}
|
||||
|
||||
|
||||
return utilRebind(maxspeed, dispatch, 'on');
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ export function uiFieldRadio(field, context) {
|
||||
var typeField;
|
||||
var layerField;
|
||||
var _oldType = {};
|
||||
var _entity;
|
||||
var _entityIDs = [];
|
||||
|
||||
|
||||
function selectedKey() {
|
||||
@@ -104,7 +104,7 @@ export function uiFieldRadio(field, context) {
|
||||
// Type
|
||||
if (type) {
|
||||
if (!typeField || typeField.id !== selected) {
|
||||
typeField = uiField(context, type, _entity, { wrap: false })
|
||||
typeField = uiField(context, type, _entityIDs, { wrap: false })
|
||||
.on('change', changeType);
|
||||
}
|
||||
typeField.tags(tags);
|
||||
@@ -147,7 +147,7 @@ export function uiFieldRadio(field, context) {
|
||||
// Layer
|
||||
if (layer && showLayer) {
|
||||
if (!layerField) {
|
||||
layerField = uiField(context, layer, _entity, { wrap: false })
|
||||
layerField = uiField(context, layer, _entityIDs, { wrap: false })
|
||||
.on('change', changeLayer);
|
||||
}
|
||||
layerField.tags(tags);
|
||||
@@ -301,13 +301,20 @@ export function uiFieldRadio(field, context) {
|
||||
};
|
||||
|
||||
|
||||
radio.entity = function(val) {
|
||||
if (!arguments.length) return _entity;
|
||||
_entity = val;
|
||||
radio.entityIDs = function(val) {
|
||||
if (!arguments.length) return _entityIDs;
|
||||
_entityIDs = val;
|
||||
_oldType = {};
|
||||
return radio;
|
||||
};
|
||||
|
||||
|
||||
radio.isAllowed = function() {
|
||||
return _entityIDs.length === 1;
|
||||
};
|
||||
|
||||
|
||||
return utilRebind(radio, dispatch, 'on');
|
||||
}
|
||||
|
||||
uiFieldRadio.supportsMultiselection = false;
|
||||
|
||||
@@ -615,11 +615,11 @@ export function uiFieldRestrictions(field, context) {
|
||||
}
|
||||
|
||||
|
||||
restrictions.entity = function(val) {
|
||||
restrictions.entityIDs = function(val) {
|
||||
_intersection = null;
|
||||
_fromWayID = null;
|
||||
_oldTurns = null;
|
||||
_vertexID = val.id;
|
||||
_vertexID = val[0];
|
||||
};
|
||||
|
||||
|
||||
@@ -642,3 +642,5 @@ export function uiFieldRestrictions(field, context) {
|
||||
|
||||
return utilRebind(restrictions, dispatch, 'on');
|
||||
}
|
||||
|
||||
uiFieldRestrictions.supportsMultiselection = false;
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
export function uiFieldTextarea(field, context) {
|
||||
var dispatch = d3_dispatch('change');
|
||||
var input = d3_select(null);
|
||||
var _tags;
|
||||
|
||||
|
||||
function textarea(selection) {
|
||||
@@ -29,7 +30,6 @@ export function uiFieldTextarea(field, context) {
|
||||
input = input.enter()
|
||||
.append('textarea')
|
||||
.attr('id', 'preset-input-' + field.safeid)
|
||||
.attr('placeholder', field.placeholder() || t('inspector.unknown'))
|
||||
.attr('maxlength', context.maxCharsForTagValue())
|
||||
.call(utilNoAuto)
|
||||
.on('input', change(true))
|
||||
@@ -41,15 +41,28 @@ export function uiFieldTextarea(field, context) {
|
||||
|
||||
function change(onInput) {
|
||||
return function() {
|
||||
|
||||
var val = utilGetSetValue(input) || undefined;
|
||||
|
||||
// don't override multiple values with blank string
|
||||
if (!val && Array.isArray(_tags[field.key])) return;
|
||||
|
||||
var t = {};
|
||||
t[field.key] = utilGetSetValue(input) || undefined;
|
||||
t[field.key] = val;
|
||||
dispatch.call('change', this, t, onInput);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
textarea.tags = function(tags) {
|
||||
utilGetSetValue(input, tags[field.key] || '');
|
||||
_tags = tags;
|
||||
|
||||
var isMixed = Array.isArray(tags[field.key]);
|
||||
|
||||
utilGetSetValue(input, !isMixed && tags[field.key] ? tags[field.key] : '')
|
||||
.attr('title', isMixed ? tags[field.key].filter(Boolean).join('; ') : undefined)
|
||||
.attr('placeholder', isMixed ? t('inspector.multiple_values') : (field.placeholder() || t('inspector.unknown')))
|
||||
.classed('mixed', isMixed);
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ export function uiFieldWikidata(field, context) {
|
||||
var _qid = null;
|
||||
var _wikidataEntity = null;
|
||||
var _wikiURL = '';
|
||||
var _entity;
|
||||
var _entityIDs = [];
|
||||
|
||||
var _wikipediaKey = field.keys && field.keys.find(function(key) {
|
||||
return key.includes('wikipedia');
|
||||
@@ -141,8 +141,15 @@ export function uiFieldWikidata(field, context) {
|
||||
|
||||
function fetchWikidataItems(q, callback) {
|
||||
|
||||
if (!q && _entity) {
|
||||
q = (_hintKey && context.entity(_entity.id).tags[_hintKey]) || '';
|
||||
if (!q && _hintKey) {
|
||||
// other tags may be good search terms
|
||||
for (var i in _entityIDs) {
|
||||
var entity = context.hasEntity(_entityIDs[i]);
|
||||
if (entity.tags[_hintKey]) {
|
||||
q = entity.tags[_hintKey];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wikidata.itemsForSearchQuery(q, function(err, data) {
|
||||
@@ -165,7 +172,7 @@ export function uiFieldWikidata(field, context) {
|
||||
|
||||
// attempt asynchronous update of wikidata tag..
|
||||
var initGraph = context.graph();
|
||||
var initEntityID = _entity.id;
|
||||
var initEntityIDs = _entityIDs;
|
||||
|
||||
wikidata.entityByQID(_qid, function(err, entity) {
|
||||
if (err) return;
|
||||
@@ -189,7 +196,7 @@ export function uiFieldWikidata(field, context) {
|
||||
}
|
||||
});
|
||||
|
||||
var currTags = Object.assign({}, context.entity(initEntityID).tags); // shallow copy
|
||||
var newWikipediaValue;
|
||||
|
||||
if (_wikipediaKey) {
|
||||
var foundPreferred;
|
||||
@@ -198,7 +205,7 @@ export function uiFieldWikidata(field, context) {
|
||||
var siteID = lang.replace('-', '_') + 'wiki';
|
||||
if (entity.sitelinks[siteID]) {
|
||||
foundPreferred = true;
|
||||
currTags[_wikipediaKey] = (lang + ':' + entity.sitelinks[siteID].title).substr(0, context.maxCharsForTagValue());
|
||||
newWikipediaValue = lang + ':' + entity.sitelinks[siteID].title;
|
||||
// use the first match
|
||||
break;
|
||||
}
|
||||
@@ -214,26 +221,52 @@ export function uiFieldWikidata(field, context) {
|
||||
|
||||
if (wikiSiteKeys.length === 0) {
|
||||
// if no wikipedia pages are linked to this wikidata entity, delete that tag
|
||||
if (currTags[_wikipediaKey]) {
|
||||
delete currTags[_wikipediaKey];
|
||||
}
|
||||
newWikipediaValue = null;
|
||||
} else {
|
||||
var wikiLang = wikiSiteKeys[0].slice(0, -4).replace('_', '-');
|
||||
var wikiTitle = entity.sitelinks[wikiSiteKeys[0]].title;
|
||||
currTags[_wikipediaKey] = (wikiLang + ':' + wikiTitle).substr(0, context.maxCharsForTagValue());
|
||||
newWikipediaValue = wikiLang + ':' + wikiTitle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (newWikipediaValue) {
|
||||
newWikipediaValue = newWikipediaValue.substr(0, context.maxCharsForTagValue());
|
||||
}
|
||||
|
||||
if (typeof newWikipediaValue === 'undefined') return;
|
||||
|
||||
var actions = initEntityIDs.map(function(entityID) {
|
||||
var entity = context.hasEntity(entityID);
|
||||
if (!entity) return;
|
||||
|
||||
var currTags = Object.assign({}, entity.tags); // shallow copy
|
||||
if (newWikipediaValue === null) {
|
||||
if (!currTags[_wikipediaKey]) return;
|
||||
|
||||
delete currTags[_wikipediaKey];
|
||||
} else {
|
||||
currTags[_wikipediaKey] = newWikipediaValue;
|
||||
}
|
||||
|
||||
return actionChangeTags(entityID, currTags);
|
||||
}).filter(Boolean);
|
||||
|
||||
if (!actions.length) return;
|
||||
|
||||
// Coalesce the update of wikidata tag into the previous tag change
|
||||
context.overwrite(
|
||||
actionChangeTags(initEntityID, currTags),
|
||||
function actionUpdateWikipediaTags(graph) {
|
||||
actions.forEach(function(action) {
|
||||
graph = action(graph);
|
||||
});
|
||||
return graph;
|
||||
},
|
||||
context.history().undoAnnotation()
|
||||
);
|
||||
|
||||
// do not dispatch.call('change') here, because entity_editor
|
||||
// changeTags() is not intended to be called asynchronously
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@@ -250,7 +283,14 @@ export function uiFieldWikidata(field, context) {
|
||||
|
||||
|
||||
wiki.tags = function(tags) {
|
||||
_qid = tags[field.key] || '';
|
||||
|
||||
var isMixed = Array.isArray(tags[field.key]);
|
||||
d3_select('li.wikidata-search input')
|
||||
.attr('title', isMixed ? tags[field.key].filter(Boolean).join('; ') : null)
|
||||
.attr('placeholder', isMixed ? t('inspector.multiple_values') : '')
|
||||
.classed('mixed', isMixed);
|
||||
|
||||
_qid = typeof tags[field.key] === 'string' && tags[field.key] || '';
|
||||
|
||||
if (!/^Q[0-9]*$/.test(_qid)) { // not a proper QID
|
||||
unrecognized();
|
||||
@@ -327,9 +367,9 @@ export function uiFieldWikidata(field, context) {
|
||||
}
|
||||
|
||||
|
||||
wiki.entity = function(val) {
|
||||
if (!arguments.length) return _entity;
|
||||
_entity = val;
|
||||
wiki.entityIDs = function(val) {
|
||||
if (!arguments.length) return _entityIDs;
|
||||
_entityIDs = val;
|
||||
return wiki;
|
||||
};
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ export function uiFieldWikipedia(field, context) {
|
||||
let _lang = d3_select(null);
|
||||
let _title = d3_select(null);
|
||||
let _wikiURL = '';
|
||||
let _entity;
|
||||
let _entityIDs;
|
||||
|
||||
// A concern here in switching to async data means that _dataWikipedia will not
|
||||
// be available the first time through, so things like the fetchers and
|
||||
@@ -43,8 +43,15 @@ export function uiFieldWikipedia(field, context) {
|
||||
|
||||
const titleCombo = uiCombobox(context, 'wikipedia-title')
|
||||
.fetcher((value, callback) => {
|
||||
if (!value && _entity) {
|
||||
value = context.entity(_entity.id).tags.name || '';
|
||||
if (!value) {
|
||||
value = '';
|
||||
for (let i in _entityIDs) {
|
||||
let entity = context.hasEntity(_entityIDs[i]);
|
||||
if (entity.tags.name) {
|
||||
value = entity.tags.name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
const searchfn = value.length > 7 ? wikipedia.search : wikipedia.suggestions;
|
||||
searchfn(language()[2], value, (query, data) => {
|
||||
@@ -196,7 +203,7 @@ export function uiFieldWikipedia(field, context) {
|
||||
|
||||
// attempt asynchronous update of wikidata tag..
|
||||
const initGraph = context.graph();
|
||||
const initEntityID = _entity.id;
|
||||
const initEntityIDs = _entityIDs;
|
||||
|
||||
wikidata.itemsByTitle(language()[2], value, (err, data) => {
|
||||
if (err || !data || !Object.keys(data).length) return;
|
||||
@@ -206,13 +213,26 @@ export function uiFieldWikipedia(field, context) {
|
||||
|
||||
const qids = Object.keys(data);
|
||||
const value = qids && qids.find(id => id.match(/^Q\d+$/));
|
||||
let currTags = Object.assign({}, context.entity(initEntityID).tags); // shallow copy
|
||||
|
||||
currTags.wikidata = value;
|
||||
let actions = initEntityIDs.map((entityID) => {
|
||||
let entity = context.entity(entityID).tags;
|
||||
let currTags = Object.assign({}, entity); // shallow copy
|
||||
if (currTags.wikidata !== value) {
|
||||
currTags.wikidata = value;
|
||||
return actionChangeTags(entityID, currTags);
|
||||
}
|
||||
}).filter(Boolean);
|
||||
|
||||
if (!actions.length) return;
|
||||
|
||||
// Coalesce the update of wikidata tag into the previous tag change
|
||||
context.overwrite(
|
||||
actionChangeTags(initEntityID, currTags),
|
||||
function actionUpdateWikidataTags(graph) {
|
||||
actions.forEach(function(action) {
|
||||
graph = action(graph);
|
||||
});
|
||||
return graph;
|
||||
},
|
||||
context.history().undoAnnotation()
|
||||
);
|
||||
|
||||
@@ -223,7 +243,7 @@ export function uiFieldWikipedia(field, context) {
|
||||
|
||||
|
||||
wiki.tags = (tags) => {
|
||||
const value = tags[field.key] || '';
|
||||
const value = typeof tags[field.key] === 'string' ? tags[field.key] : '';
|
||||
const m = value.match(/([^:]+):([^#]+)(?:#(.+))?/);
|
||||
const l = m && _dataWikipedia.find(d => m[1] === d[2]);
|
||||
let anchor = m && m[3];
|
||||
@@ -256,9 +276,9 @@ export function uiFieldWikipedia(field, context) {
|
||||
};
|
||||
|
||||
|
||||
wiki.entity = function(val) {
|
||||
if (!arguments.length) return _entity;
|
||||
_entity = val;
|
||||
wiki.entityIDs = (val) => {
|
||||
if (!arguments.length) return _entityIDs;
|
||||
_entityIDs = val;
|
||||
return wiki;
|
||||
};
|
||||
|
||||
@@ -270,3 +290,5 @@ export function uiFieldWikipedia(field, context) {
|
||||
|
||||
return utilRebind(wiki, dispatch, 'on');
|
||||
}
|
||||
|
||||
uiFieldWikipedia.supportsMultiselection = false;
|
||||
|
||||
@@ -28,7 +28,7 @@ export function uiFormFields(context) {
|
||||
|
||||
|
||||
var fields = container.selectAll('.wrap-form-field')
|
||||
.data(shown, function(d) { return d.id + (d.entityID || ''); });
|
||||
.data(shown, function(d) { return d.id + (d.entityIDs ? d.entityIDs.join() : ''); });
|
||||
|
||||
fields.exit()
|
||||
.remove();
|
||||
|
||||
+51
-25
@@ -10,17 +10,18 @@ import { modeBrowse } from '../modes/browse';
|
||||
import { uiDisclosure } from './disclosure';
|
||||
import { uiField } from './field';
|
||||
import { uiFormFields } from './form_fields';
|
||||
import { utilArrayIdentical } from '../util/array';
|
||||
import { utilArrayUnion, utilRebind } from '../util';
|
||||
|
||||
|
||||
export function uiPresetEditor(context) {
|
||||
var dispatch = d3_dispatch('change');
|
||||
var dispatch = d3_dispatch('change', 'revert');
|
||||
var formFields = uiFormFields(context);
|
||||
var _state;
|
||||
var _fieldsArr;
|
||||
var _preset;
|
||||
var _presets = [];
|
||||
var _tags;
|
||||
var _entityID;
|
||||
var _entityIDs;
|
||||
|
||||
|
||||
function presetEditor(selection) {
|
||||
@@ -33,36 +34,56 @@ export function uiPresetEditor(context) {
|
||||
|
||||
function render(selection) {
|
||||
if (!_fieldsArr) {
|
||||
var entity = context.entity(_entityID);
|
||||
var geometry = context.geometry(_entityID);
|
||||
var presets = context.presets();
|
||||
|
||||
var graph = context.graph();
|
||||
|
||||
var geometries = Object.keys(_entityIDs.reduce(function(geoms, entityID) {
|
||||
return geoms[graph.entity(entityID).geometry(graph)] = true;
|
||||
}, {}));
|
||||
|
||||
var presetsManager = context.presets();
|
||||
|
||||
var combinedFields = _presets.reduce(function(fields, preset) {
|
||||
if (!fields.length) return preset.fields;
|
||||
return fields.filter(function(field) {
|
||||
return preset.fields.indexOf(field) !== -1 || preset.moreFields.indexOf(field) !== -1;
|
||||
});
|
||||
}, []);
|
||||
|
||||
var combinedMoreFields = _presets.reduce(function(fields, preset) {
|
||||
if (!fields.length) return preset.moreFields;
|
||||
return fields.filter(function(field) {
|
||||
return preset.fields.indexOf(field) !== -1 || preset.moreFields.indexOf(field) !== -1;
|
||||
});
|
||||
}, []);
|
||||
|
||||
_fieldsArr = [];
|
||||
|
||||
_preset.fields.forEach(function(field) {
|
||||
if (field.matchGeometry(geometry)) {
|
||||
combinedFields.forEach(function(field) {
|
||||
if (field.matchAllGeometry(geometries)) {
|
||||
_fieldsArr.push(
|
||||
uiField(context, field, entity)
|
||||
uiField(context, field, _entityIDs)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
if (entity.isHighwayIntersection(context.graph()) && presets.field('restrictions')) {
|
||||
var singularEntity = _entityIDs.length === 1 && graph.hasEntity(_entityIDs[0]);
|
||||
if (singularEntity && singularEntity.isHighwayIntersection(graph) && presetsManager.field('restrictions')) {
|
||||
_fieldsArr.push(
|
||||
uiField(context, presets.field('restrictions'), entity)
|
||||
uiField(context, presetsManager.field('restrictions'), _entityIDs)
|
||||
);
|
||||
}
|
||||
|
||||
var additionalFields = utilArrayUnion(_preset.moreFields, presets.universal());
|
||||
var additionalFields = utilArrayUnion(combinedMoreFields, presetsManager.universal());
|
||||
additionalFields.sort(function(field1, field2) {
|
||||
return field1.label().localeCompare(field2.label(), currentLocale);
|
||||
});
|
||||
|
||||
additionalFields.forEach(function(field) {
|
||||
if (_preset.fields.indexOf(field) === -1 &&
|
||||
field.matchGeometry(geometry)) {
|
||||
if (combinedFields.indexOf(field) === -1 &&
|
||||
field.matchAllGeometry(geometries)) {
|
||||
_fieldsArr.push(
|
||||
uiField(context, field, entity, { show: false })
|
||||
uiField(context, field, _entityIDs, { show: false })
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -71,6 +92,9 @@ export function uiPresetEditor(context) {
|
||||
field
|
||||
.on('change', function(t, onInput) {
|
||||
dispatch.call('change', field, t, onInput);
|
||||
})
|
||||
.on('revert', function(keys) {
|
||||
dispatch.call('revert', field, keys);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -100,11 +124,12 @@ export function uiPresetEditor(context) {
|
||||
}
|
||||
|
||||
|
||||
presetEditor.preset = function(val) {
|
||||
if (!arguments.length) return _preset;
|
||||
if (_preset && _preset.id === val.id) return presetEditor;
|
||||
_preset = val;
|
||||
_fieldsArr = null;
|
||||
presetEditor.presets = function(val) {
|
||||
if (!arguments.length) return _presets;
|
||||
if (!_presets || !val || !utilArrayIdentical(_presets, val)) {
|
||||
_presets = val;
|
||||
_fieldsArr = null;
|
||||
}
|
||||
return presetEditor;
|
||||
};
|
||||
|
||||
@@ -124,11 +149,12 @@ export function uiPresetEditor(context) {
|
||||
};
|
||||
|
||||
|
||||
presetEditor.entityID = function(val) {
|
||||
if (!arguments.length) return _entityID;
|
||||
if (_entityID === val) return presetEditor;
|
||||
_entityID = val;
|
||||
_fieldsArr = null;
|
||||
presetEditor.entityIDs = function(val) {
|
||||
if (!arguments.length) return _entityIDs;
|
||||
if (!val || !_entityIDs || !utilArrayIdentical(_entityIDs, val)) {
|
||||
_entityIDs = val;
|
||||
_fieldsArr = null;
|
||||
}
|
||||
return presetEditor;
|
||||
};
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import { actionChangePreset } from '../actions/change_preset';
|
||||
import { operationDelete } from '../operations/delete';
|
||||
import { svgIcon } from '../svg/index';
|
||||
import { tooltip } from '../util/tooltip';
|
||||
import { geoExtent } from '../geo/extent';
|
||||
import { uiPresetIcon } from './preset_icon';
|
||||
import { uiTagReference } from './tag_reference';
|
||||
import { utilKeybinding, utilNoAuto, utilRebind } from '../util';
|
||||
@@ -511,17 +512,10 @@ export function uiPresetList(context) {
|
||||
}
|
||||
|
||||
function combinedEntityExtent() {
|
||||
var extent;
|
||||
_entityIDs.forEach(function(entityID) {
|
||||
return _entityIDs.reduce(function(extent, entityID) {
|
||||
var entity = context.graph().entity(entityID);
|
||||
var entityExtent = entity.extent(context.graph());
|
||||
if (!extent) {
|
||||
extent = entityExtent;
|
||||
} else {
|
||||
extent = extent.extend(entityExtent);
|
||||
}
|
||||
});
|
||||
return extent;
|
||||
return extent.extend(entity.extent(context.graph()));
|
||||
}, geoExtent());
|
||||
}
|
||||
|
||||
return utilRebind(presetList, dispatch, 'on');
|
||||
|
||||
@@ -23,7 +23,6 @@ export function uiRawTagEditor(context) {
|
||||
var _readOnlyTags = [];
|
||||
// the keys in the order we want them to display
|
||||
var _orderedKeys = [];
|
||||
var _keyValues = null;
|
||||
var _showBlank = false;
|
||||
var _updatePreference = true;
|
||||
var _expanded = false;
|
||||
@@ -283,10 +282,10 @@ export function uiRawTagEditor(context) {
|
||||
|
||||
items.selectAll('input.value')
|
||||
.attr('title', function(d) {
|
||||
return typeof d.value === 'string' ? d.value : Array.from(_keyValues[d.key]).sort().join('; ');
|
||||
return Array.isArray(d.value) ? d.value.filter(Boolean).join('; ') : d.value;
|
||||
})
|
||||
.classed('conflicting', function(d) {
|
||||
return typeof d.value !== 'string';
|
||||
.classed('mixed', function(d) {
|
||||
return Array.isArray(d.value);
|
||||
})
|
||||
.attr('placeholder', function(d) {
|
||||
return typeof d.value === 'string' ? null : t('inspector.multiple_values');
|
||||
@@ -346,7 +345,7 @@ export function uiRawTagEditor(context) {
|
||||
.filter(function(row) { return row.key && row.key.trim() !== ''; })
|
||||
.map(function(row) {
|
||||
var rawVal = row.value;
|
||||
if (rawVal === true) rawVal = '*';
|
||||
if (typeof rawVal !== 'string') rawVal = '*';
|
||||
var val = rawVal ? stringify(rawVal) : '';
|
||||
return stringify(row.key) + '=' + val;
|
||||
})
|
||||
@@ -382,7 +381,7 @@ export function uiRawTagEditor(context) {
|
||||
if (isReadOnly({ key: change.key })) return;
|
||||
|
||||
// skip unchanged multiselection placeholders
|
||||
if (change.newVal === '*' && change.oldVal === true) return;
|
||||
if (change.newVal === '*' && typeof change.oldVal !== 'string') return;
|
||||
|
||||
if (change.type === '-') {
|
||||
_pendingChange[change.key] = undefined;
|
||||
@@ -413,13 +412,13 @@ export function uiRawTagEditor(context) {
|
||||
function bindTypeahead(key, value) {
|
||||
if (isReadOnly(key.datum())) return;
|
||||
|
||||
if (typeof value.datum().value !== 'string' && _keyValues) {
|
||||
if (Array.isArray(value.datum().value)) {
|
||||
value.call(uiCombobox(context, 'tag-value')
|
||||
.minItems(1)
|
||||
.fetcher(function(value, callback) {
|
||||
var keyString = utilGetSetValue(key);
|
||||
if (!_keyValues[keyString]) return;
|
||||
var data = Array.from(_keyValues[keyString]).map(function(tagValue) {
|
||||
if (!_tags[keyString]) return;
|
||||
var data = _tags[keyString].filter(Boolean).map(function(tagValue) {
|
||||
return {
|
||||
value: tagValue,
|
||||
title: tagValue
|
||||
@@ -633,59 +632,6 @@ export function uiRawTagEditor(context) {
|
||||
_entityIDs = val;
|
||||
_orderedKeys = [];
|
||||
}
|
||||
|
||||
var combinedTags = {};
|
||||
var sharedKeys = null;
|
||||
_keyValues = {};
|
||||
|
||||
_entityIDs.forEach(function(entityID) {
|
||||
var entity = context.entity(entityID);
|
||||
var entityTags = entity.tags;
|
||||
var entityKey;
|
||||
|
||||
if (sharedKeys === null) {
|
||||
sharedKeys = {};
|
||||
for (entityKey in entityTags) {
|
||||
sharedKeys[entityKey] = true;
|
||||
}
|
||||
} else {
|
||||
for (var sharedKey in sharedKeys) {
|
||||
if (!entityTags.hasOwnProperty(sharedKey)) {
|
||||
delete sharedKeys[sharedKey];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (entityKey in entityTags) {
|
||||
|
||||
var entityValue = entityTags[entityKey];
|
||||
|
||||
if (!_keyValues.hasOwnProperty(entityKey)) {
|
||||
_keyValues[entityKey] = new Set();
|
||||
}
|
||||
_keyValues[entityKey].add(entityValue);
|
||||
|
||||
if (combinedTags.hasOwnProperty(entityKey)) {
|
||||
var combinedValue = combinedTags[entityKey];
|
||||
if (combinedValue !== true &&
|
||||
combinedValue !== entityValue) {
|
||||
|
||||
combinedTags[entityKey] = true;
|
||||
}
|
||||
} else {
|
||||
combinedTags[entityKey] = entityValue;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
for (var key in combinedTags) {
|
||||
if (!sharedKeys.hasOwnProperty(key)) {
|
||||
// treat tags that aren't shared by all entities the same as if there are multiple values
|
||||
combinedTags[key] = true;
|
||||
}
|
||||
}
|
||||
|
||||
rawTagEditor.tags(combinedTags);
|
||||
return rawTagEditor;
|
||||
};
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ export { utilArrayUniqBy } from './array';
|
||||
|
||||
export { utilAsyncMap } from './util';
|
||||
export { utilCleanTags } from './clean_tags';
|
||||
export { utilCombinedTags } from './util';
|
||||
export { utilDeepMemberSelector } from './util';
|
||||
export { utilDetect } from './detect';
|
||||
export { utilDisplayName } from './util';
|
||||
|
||||
@@ -230,6 +230,93 @@ export function utilEntityRoot(entityType) {
|
||||
}
|
||||
|
||||
|
||||
// Returns a single object containing the tags of all the given entities.
|
||||
// Example:
|
||||
// {
|
||||
// highway: 'service',
|
||||
// service: 'parking_aisle'
|
||||
// }
|
||||
// +
|
||||
// {
|
||||
// highway: 'service',
|
||||
// service: 'driveway',
|
||||
// width: '3'
|
||||
// }
|
||||
// =
|
||||
// {
|
||||
// highway: 'service',
|
||||
// service: [ 'driveway', 'parking_aisle' ],
|
||||
// width: [ '3', undefined ]
|
||||
// }
|
||||
export function utilCombinedTags(entityIDs, graph) {
|
||||
|
||||
var tags = {};
|
||||
var tagCounts = {};
|
||||
var allKeys = new Set();
|
||||
|
||||
var entities = entityIDs.map(function(entityID) {
|
||||
return graph.hasEntity(entityID);
|
||||
}).filter(Boolean);
|
||||
|
||||
// gather the aggregate keys
|
||||
entities.forEach(function(entity) {
|
||||
var keys = Object.keys(entity.tags).filter(Boolean);
|
||||
keys.forEach(function(key) {
|
||||
allKeys.add(key);
|
||||
});
|
||||
});
|
||||
|
||||
entities.forEach(function(entity) {
|
||||
|
||||
allKeys.forEach(function(key) {
|
||||
|
||||
var value = entity.tags[key]; // purposely allow `undefined`
|
||||
|
||||
if (!tags.hasOwnProperty(key)) {
|
||||
// first value, set as raw
|
||||
tags[key] = value;
|
||||
} else {
|
||||
if (!Array.isArray(tags[key])) {
|
||||
if (tags[key] !== value) {
|
||||
// first alternate value, replace single value with array
|
||||
tags[key] = [tags[key], value];
|
||||
}
|
||||
} else { // type is array
|
||||
if (tags[key].indexOf(value) === -1) {
|
||||
// subsequent alternate value, add to array
|
||||
tags[key].push(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var tagHash = key + '=' + value;
|
||||
if (!tagCounts[tagHash]) tagCounts[tagHash] = 0;
|
||||
tagCounts[tagHash] += 1;
|
||||
});
|
||||
});
|
||||
|
||||
for (var key in tags) {
|
||||
if (!Array.isArray(tags[key])) continue;
|
||||
|
||||
// sort values by frequency then alphabetically
|
||||
tags[key] = tags[key].sort(function(val1, val2) {
|
||||
var key = key; // capture
|
||||
var count2 = tagCounts[key + '=' + val2];
|
||||
var count1 = tagCounts[key + '=' + val1];
|
||||
if (count2 !== count1) {
|
||||
return count2 - count1;
|
||||
}
|
||||
if (val2 && val1) {
|
||||
return val1.localeCompare(val2);
|
||||
}
|
||||
return val1 ? 1 : -1;
|
||||
});
|
||||
}
|
||||
|
||||
return tags;
|
||||
}
|
||||
|
||||
|
||||
export function utilStringQs(str) {
|
||||
return str.split('&').reduce(function(obj, pair){
|
||||
var parts = pair.split('=');
|
||||
|
||||
@@ -79,7 +79,7 @@ describe('iD.uiFieldWikipedia', function() {
|
||||
});
|
||||
|
||||
it('sets language, value', function(done) {
|
||||
var wikipedia = iD.uiFieldWikipedia(field, context).entity(entity);
|
||||
var wikipedia = iD.uiFieldWikipedia(field, context).entityIDs([entity.id]);
|
||||
window.setTimeout(function() { // async, so data will be available
|
||||
wikipedia.on('change', changeTags);
|
||||
selection.call(wikipedia);
|
||||
@@ -105,7 +105,7 @@ describe('iD.uiFieldWikipedia', function() {
|
||||
});
|
||||
|
||||
it('recognizes pasted URLs', function(done) {
|
||||
var wikipedia = iD.uiFieldWikipedia(field, context).entity(entity);
|
||||
var wikipedia = iD.uiFieldWikipedia(field, context).entityIDs([entity.id]);
|
||||
window.setTimeout(function() { // async, so data will be available
|
||||
wikipedia.on('change', changeTags);
|
||||
selection.call(wikipedia);
|
||||
@@ -136,7 +136,7 @@ describe('iD.uiFieldWikipedia', function() {
|
||||
});
|
||||
|
||||
it.skip('does not set delayed wikidata tag if graph has changed', function(done) {
|
||||
var wikipedia = iD.uiFieldWikipedia(field, context).entity(entity);
|
||||
var wikipedia = iD.uiFieldWikipedia(field, context).entityIDs([entity.id]);
|
||||
wikipedia.on('change', changeTags);
|
||||
selection.call(wikipedia);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user