Merge branch 'schema-builder-v5' into develop

This commit is contained in:
Martin Raifer
2022-10-13 13:53:31 +02:00
11 changed files with 166 additions and 31 deletions
+2
View File
@@ -44,10 +44,12 @@ _Breaking developer changes, which may affect downstream projects or sites that
#### :bug: Bugfixes
* Fix selection of best background source when starting on a zoomed-out view ([#9325])
#### :rocket: Presets
* add support for tagging schema v5 ([#9320])
* Render `natural=strait` features in blue color ([#9294])
#### :hammer: Development
[#9294]: https://github.com/openstreetmap/iD/issues/9294
[#9320]: https://github.com/openstreetmap/iD/pull/9320
[#9325]: https://github.com/openstreetmap/iD/issues/9325
+19 -6
View File
@@ -6,7 +6,8 @@ import { utilSafeClassName } from '../util/util';
// `presetField` decorates a given `field` Object
// with some extra methods for searching and matching geometry
//
export function presetField(fieldID, field) {
export function presetField(fieldID, field, allFields) {
allFields = allFields || {};
let _this = Object.assign({}, field); // shallow copy
_this.id = fieldID;
@@ -25,17 +26,29 @@ export function presetField(fieldID, field) {
_this.t.append = (scope, options) => t.append(`_tagging.presets.fields.${fieldID}.${scope}`, options);
_this.hasTextForStringId = (scope) => localizer.hasTextForStringId(`_tagging.presets.fields.${fieldID}.${scope}`);
_this.title = () => _this.overrideLabel || _this.t('label', { 'default': fieldID });
_this.resolveReference = which => {
const referenceRegex = /^\{(.*)\}$/;
const match = (field[which] || '').match(referenceRegex);
if (match) {
const field = allFields[match[1]];
if (field) {
return field;
}
console.error(`Unable to resolve referenced field: ${match[1]}`); // eslint-disable-line no-console
}
return _this;
};
_this.title = () => _this.overrideLabel || _this.resolveReference('label').t('label', { 'default': fieldID });
_this.label = () => _this.overrideLabel ?
selection => selection.text(_this.overrideLabel) :
_this.t.append('label', { 'default': fieldID });
_this.resolveReference('label').t.append('label', { 'default': fieldID });
const _placeholder = _this.placeholder;
_this.placeholder = () => _this.t('placeholder', { 'default': _placeholder });
_this.placeholder = () => _this.resolveReference('placeholder').t('placeholder', { 'default': '' });
_this.originalTerms = (_this.terms || []).join();
_this.terms = () => _this.t('terms', { 'default': _this.originalTerms })
_this.terms = () => _this.resolveReference('label').t('terms', { 'default': _this.originalTerms })
.toLowerCase().trim().split(/\s*,+\s*/);
_this.increment = _this.type === 'number' ? (_this.increment || 1) : undefined;
+1 -1
View File
@@ -96,7 +96,7 @@ export function presetIndex() {
let f = d.fields[fieldID];
if (f) { // add or replace
f = presetField(fieldID, f);
f = presetField(fieldID, f, _fields);
if (f.locationSet) newLocationSets.push(f);
_fields[fieldID] = f;
+36 -15
View File
@@ -11,15 +11,17 @@ import { utilSafeClassName } from '../util/util';
export function presetPreset(presetID, preset, addable, allFields, allPresets) {
allFields = allFields || {};
allPresets = allPresets || {};
let _this = Object.assign({}, preset); // shallow copy
let _this = Object.assign({}, preset); // shallow copy
let _addable = addable || false;
let _resolvedFields; // cache
let _resolvedMoreFields; // cache
let _searchName; // cache
let _searchNameStripped; // cache
let _searchAliases; // cache
let _resolvedFields; // cache
let _resolvedMoreFields; // cache
let _searchName; // cache
let _searchNameStripped; // cache
let _searchAliases; // cache
let _searchAliasesStripped; // cache
const referenceRegex = /^\{(.*)\}$/;
_this.id = presetID;
_this.safeid = utilSafeClassName(presetID); // for use in css classes, selectors, element ids
@@ -38,9 +40,9 @@ export function presetPreset(presetID, preset, addable, allFields, allPresets) {
_this.originalMoreFields = (_this.moreFields || []);
_this.fields = () => _resolvedFields || (_resolvedFields = resolve('fields'));
_this.fields = () => _resolvedFields || (_resolvedFields = resolveFields('fields'));
_this.moreFields = () => _resolvedMoreFields || (_resolvedMoreFields = resolve('moreFields'));
_this.moreFields = () => _resolvedMoreFields || (_resolvedMoreFields = resolveFields('moreFields'));
_this.resetFields = () => _resolvedFields = _resolvedMoreFields = null;
@@ -99,12 +101,27 @@ export function presetPreset(presetID, preset, addable, allFields, allPresets) {
return t.append(textID, options);
};
function resolveReference(which) {
const match = (_this[which] || '').match(referenceRegex);
if (match) {
const preset = allPresets[match[1]];
if (preset) {
return preset;
}
console.error(`Unable to resolve referenced preset: ${match[1]}`); // eslint-disable-line no-console
}
return _this;
}
_this.name = () => {
return _this.t('name', { 'default': _this.originalName });
return resolveReference('originalName')
.t('name', { 'default': _this.originalName || presetID });
};
_this.nameLabel = () => _this.t.append('name', { 'default': _this.originalName });
_this.nameLabel = () => {
return resolveReference('originalName')
.t.append('name', { 'default': _this.originalName || presetID });
};
_this.subtitle = () => {
if (_this.suggestion) {
@@ -125,11 +142,15 @@ export function presetPreset(presetID, preset, addable, allFields, allPresets) {
};
_this.aliases = () => {
return _this.t('aliases', { 'default': _this.originalAliases }).trim().split(/\s*[\r\n]+\s*/);
return resolveReference('originalName')
.t('aliases', { 'default': _this.originalAliases }).trim().split(/\s*[\r\n]+\s*/);
};
_this.terms = () => _this.t('terms', { 'default': _this.originalTerms })
.toLowerCase().trim().split(/\s*,+\s*/);
_this.terms = () => {
return resolveReference('originalName')
.t('terms', { 'default': _this.originalTerms })
.toLowerCase().trim().split(/\s*,+\s*/);
};
_this.searchName = () => {
if (!_searchName) {
@@ -267,12 +288,12 @@ export function presetPreset(presetID, preset, addable, allFields, allPresets) {
// For a preset without fields, use the fields of the parent preset.
// Replace {preset} placeholders with the fields of the specified presets.
function resolve(which) {
function resolveFields(which) {
const fieldIDs = (which === 'fields' ? _this.originalFields : _this.originalMoreFields);
let resolved = [];
fieldIDs.forEach(fieldID => {
const match = fieldID.match(/\{(.*)\}/);
const match = fieldID.match(referenceRegex);
if (match !== null) { // a presetID wrapped in braces {}
resolved = resolved.concat(inheritFields(match[1], which));
} else if (allFields[fieldID]) { // a normal fieldID
+2 -1
View File
@@ -98,9 +98,10 @@ export function uiFieldAccess(field, context) {
options.splice(options.length - 4, 0, 'dismount');
}
var stringsField = field.resolveReference('stringsCrossReference');
return options.map(function(option) {
return {
title: field.t('options.' + option + '.description'),
title: stringsField.t('options.' + option + '.description'),
value: option
};
});
+2 -1
View File
@@ -33,10 +33,11 @@ export function uiFieldCheck(field, context) {
if (options) {
var stringsField = field.resolveReference('stringsCrossReference');
for (var i in options) {
var v = options[i];
values.push(v === 'undefined' ? undefined : v);
texts.push(field.t.html('options.' + v, { 'default': v }));
texts.push(stringsField.t.html('options.' + v, { 'default': v }));
}
} else {
values = [undefined, 'yes'];
+6 -4
View File
@@ -90,8 +90,9 @@ export function uiFieldCombo(field, context) {
function displayValue(tval) {
tval = tval || '';
if (field.hasTextForStringId('options.' + tval)) {
return field.t('options.' + tval, { default: tval });
var stringsField = field.resolveReference('stringsCrossReference');
if (stringsField.hasTextForStringId('options.' + tval)) {
return stringsField.t('options.' + tval, { default: tval });
}
if (field.type === 'typeCombo' && tval.toLowerCase() === 'yes') {
@@ -107,8 +108,9 @@ export function uiFieldCombo(field, context) {
function renderValue(tval) {
tval = tval || '';
if (field.hasTextForStringId('options.' + tval)) {
return field.t.append('options.' + tval, { default: tval });
var stringsField = field.resolveReference('stringsCrossReference');
if (stringsField.hasTextForStringId('options.' + tval)) {
return stringsField.t.append('options.' + tval, { default: tval });
}
if (field.type === 'typeCombo' && tval.toLowerCase() === 'yes') {
+2 -1
View File
@@ -115,9 +115,10 @@ export function uiFieldCycleway(field, context) {
cycleway.options = function() {
var stringsField = field.resolveReference('stringsCrossReference');
return field.options.map(function(option) {
return {
title: field.t('options.' + option + '.description'),
title: stringsField.t('options.' + option + '.description'),
value: option
};
});
+3 -2
View File
@@ -55,16 +55,17 @@ export function uiFieldRadio(field, context) {
enter = labels.enter()
.append('label');
var stringsField = field.resolveReference('stringsCrossReference');
enter
.append('input')
.attr('type', 'radio')
.attr('name', field.id)
.attr('value', function(d) { return field.t('options.' + d, { 'default': d }); })
.attr('value', function(d) { return stringsField.t('options.' + d, { 'default': d }); })
.attr('checked', false);
enter
.append('span')
.html(function(d) { return field.t.html('options.' + d, { 'default': d }); });
.each(function(d) { stringsField.t.append('options.' + d, { 'default': d })(d3_select(this)); });
labels = labels
.merge(enter);
+61
View File
@@ -0,0 +1,61 @@
describe('iD.presetField', function() {
describe('#references', function() {
it('references label and terms of another field', function() {
var allFields = {};
var other = iD.presetField('other', {}, allFields);
var field = iD.presetField('test', {label: '{other}'}, allFields);
allFields.other = other;
allFields.preset = field;
// mock localizer
sinon.spy(other, 't');
sinon.spy(field, 't');
field.title();
expect(other.t).to.have.been.calledOnce;
expect(field.t).not.to.have.been.called;
other.t.resetHistory();
field.t.resetHistory();
field.terms();
expect(other.t).to.have.been.calledOnce;
expect(field.t).not.to.have.been.called;
});
it('references placeholder of another field', function() {
var allFields = {};
var other = iD.presetField('other', {}, allFields);
var field = iD.presetField('test', {placeholder: '{other}'}, allFields);
allFields.other = other;
allFields.preset = field;
// mock localizer
sinon.spy(other, 't');
sinon.spy(field, 't');
field.placeholder();
expect(other.t).to.have.been.calledOnce;
expect(field.t).not.to.have.been.called;
});
it('references string options of another field', function() {
var allFields = {};
var other = iD.presetField('other', {}, allFields);
var field = iD.presetField('test', {stringsCrossReference: '{other}', options: ['v'], key: 'k'}, allFields);
allFields.other = other;
allFields.preset = field;
// mock localizer
sinon.spy(other, 't');
sinon.spy(field, 't');
sinon.stub(other, 'hasTextForStringId').returns(true);
var context = iD.coreContext().assetPath('../dist/').init();
var uiField = iD.uiFieldCombo(field, context);
uiField.tags({k: 'v'});
expect(field.t).not.to.have.been.called;
expect(other.t).to.have.been.calledOnce;
});
});
});
+32
View File
@@ -228,4 +228,36 @@ describe('iD.presetPreset', function() {
expect(preset.addable()).to.be.true;
});
});
describe('#references', function() {
it('references name, aliases and terms of another preset', function() {
var allPresets = {};
var other = iD.presetPreset('other', {}, undefined, undefined, allPresets);
var preset = iD.presetPreset('test', {name: '{other}'}, undefined, undefined, allPresets);
allPresets.other = other;
allPresets.preset = preset;
// mock localizer
sinon.spy(other, 't');
sinon.spy(preset, 't');
preset.name();
expect(other.t).to.have.been.calledOnce;
expect(preset.t).not.to.have.been.called;
other.t.resetHistory();
preset.t.resetHistory();
preset.aliases();
expect(other.t).to.have.been.calledOnce;
expect(preset.t).not.to.have.been.called;
other.t.resetHistory();
preset.t.resetHistory();
preset.terms();
expect(other.t).to.have.been.calledOnce;
expect(preset.t).not.to.have.been.called;
});
});
});