From cb7f1c2b3ca93216df28c7b70a44b5e46f23c9e9 Mon Sep 17 00:00:00 2001 From: Quincy Morgan <2046746+quincylvania@users.noreply.github.com> Date: Tue, 8 Dec 2020 15:23:51 -0500 Subject: [PATCH] Update to id-tagging-schema v3.0.0 Add UI for translatable combo fields --- css/80_app.css | 17 ++++++ modules/core/file_fetcher.js | 12 ++-- modules/core/localizer.js | 6 +- modules/presets/field.js | 3 +- modules/ui/combobox.js | 4 +- modules/ui/fields/address.js | 2 +- modules/ui/fields/check.js | 9 +-- modules/ui/fields/combo.js | 103 +++++++++++++++------------------- modules/ui/fields/cycleway.js | 2 +- modules/ui/fields/index.js | 1 + modules/ui/fields/input.js | 2 +- modules/ui/fields/radio.js | 2 +- scripts/build_data.js | 6 +- 13 files changed, 92 insertions(+), 77 deletions(-) diff --git a/css/80_app.css b/css/80_app.css index 8481658d05..1b566f93de 100644 --- a/css/80_app.css +++ b/css/80_app.css @@ -1601,6 +1601,13 @@ a.hide-toggle { display: none; } +.form-field-input-combo input.raw-value { + font-family: monospace; +} +.form-field-input-combo input.known-value { + color: #7092ff; +} + .form-field-input-multicombo ul.chiplist { padding: 5px 8px 5px 8px; background: #fff; @@ -1628,6 +1635,7 @@ a.hide-toggle { background-color: #eff2f7; border: 1px solid #ccd5e3; max-width: 100%; + color: #7092ff; } .ideditor[dir='ltr'] .form-field-input-multicombo li.chip { padding: 2px 0px 2px 5px; @@ -1643,6 +1651,10 @@ a.hide-toggle { z-index: 3000; cursor: grabbing; } +.form-field-input-multicombo li.chip.raw-value { + font-family: monospace; + color: #333; +} .form-field-input-multicombo li.mixed { border-color: #eff2f7; color: #888; @@ -2226,6 +2238,11 @@ div.combobox { border-right: 5px solid transparent; } +.combobox .combobox-option.raw-option { + font-family: monospace; + color: #333; +} + /* Field Help ------------------------------------------------------- */ diff --git a/modules/core/file_fetcher.js b/modules/core/file_fetcher.js index 055933c1de..a2fef1c5cf 100644 --- a/modules/core/file_fetcher.js +++ b/modules/core/file_fetcher.js @@ -12,8 +12,8 @@ export function coreFileFetcher() { let _inflight = {}; let _fileMap = { 'address_formats': 'data/address_formats.min.json', - 'deprecated': 'https://cdn.jsdelivr.net/npm/@openstreetmap/id-tagging-schema@2/dist/deprecated.min.json', - 'discarded': 'https://cdn.jsdelivr.net/npm/@openstreetmap/id-tagging-schema@2/dist/discarded.min.json', + 'deprecated': 'https://cdn.jsdelivr.net/npm/@openstreetmap/id-tagging-schema@3/dist/deprecated.min.json', + 'discarded': 'https://cdn.jsdelivr.net/npm/@openstreetmap/id-tagging-schema@3/dist/discarded.min.json', 'imagery': 'data/imagery.min.json', 'intro_graph': 'data/intro_graph.min.json', 'keepRight': 'data/keepRight.min.json', @@ -23,10 +23,10 @@ export function coreFileFetcher() { 'nsi_filters': 'https://cdn.jsdelivr.net/npm/name-suggestion-index@4/dist/filters.min.json', 'oci_features': 'https://cdn.jsdelivr.net/npm/osm-community-index@2/dist/features.min.json', 'oci_resources': 'https://cdn.jsdelivr.net/npm/osm-community-index@2/dist/resources.min.json', - 'preset_categories': 'https://cdn.jsdelivr.net/npm/@openstreetmap/id-tagging-schema@2/dist/preset_categories.min.json', - 'preset_defaults': 'https://cdn.jsdelivr.net/npm/@openstreetmap/id-tagging-schema@2/dist/preset_defaults.min.json', - 'preset_fields': 'https://cdn.jsdelivr.net/npm/@openstreetmap/id-tagging-schema@2/dist/fields.min.json', - 'preset_presets': 'https://cdn.jsdelivr.net/npm/@openstreetmap/id-tagging-schema@2/dist/presets.min.json', + 'preset_categories': 'https://cdn.jsdelivr.net/npm/@openstreetmap/id-tagging-schema@3/dist/preset_categories.min.json', + 'preset_defaults': 'https://cdn.jsdelivr.net/npm/@openstreetmap/id-tagging-schema@3/dist/preset_defaults.min.json', + 'preset_fields': 'https://cdn.jsdelivr.net/npm/@openstreetmap/id-tagging-schema@3/dist/fields.min.json', + 'preset_presets': 'https://cdn.jsdelivr.net/npm/@openstreetmap/id-tagging-schema@3/dist/presets.min.json', 'phone_formats': 'data/phone_formats.min.json', 'qa_data': 'data/qa_data.min.json', 'shortcuts': 'data/shortcuts.min.json', diff --git a/modules/core/localizer.js b/modules/core/localizer.js index 5a5f89c2f7..30074fe867 100644 --- a/modules/core/localizer.js +++ b/modules/core/localizer.js @@ -90,7 +90,7 @@ export function coreLocalizer() { const localeDirs = { general: 'locales', - tagging: 'https://cdn.jsdelivr.net/npm/@openstreetmap/id-tagging-schema@2/dist/translations' + tagging: 'https://cdn.jsdelivr.net/npm/@openstreetmap/id-tagging-schema@3/dist/translations' }; let fileMap = fileFetcher.fileMap(); @@ -338,6 +338,10 @@ export function coreLocalizer() { }; }; + localizer.hasTextForStringId = function(stringId) { + return !!localizer.tInfo(stringId, { default: 'nothing found'}).locale; + }; + // Returns only the localized text, discarding the locale info localizer.t = function(stringId, replacements, locale) { return localizer.tInfo(stringId, replacements, locale).text; diff --git a/modules/presets/field.js b/modules/presets/field.js index becacb4a49..e249184540 100644 --- a/modules/presets/field.js +++ b/modules/presets/field.js @@ -1,4 +1,4 @@ -import { t } from '../core/localizer'; +import { localizer, t } from '../core/localizer'; import { utilSafeClassName } from '../util/util'; @@ -22,6 +22,7 @@ export function presetField(fieldID, field) { _this.t = (scope, options) => t(`_tagging.presets.fields.${fieldID}.${scope}`, options); _this.t.html = (scope, options) => t.html(`_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.label = () => _this.overrideLabel || _this.t.html('label', { 'default': fieldID }); diff --git a/modules/ui/combobox.js b/modules/ui/combobox.js index 9526b63b11..57555078f6 100644 --- a/modules/ui/combobox.js +++ b/modules/ui/combobox.js @@ -380,7 +380,9 @@ export function uiCombobox(context, klass) { // enter/update options.enter() .append('a') - .attr('class', 'combobox-option') + .attr('class', function(d) { + return 'combobox-option ' + (d.klass || ''); + }) .attr('title', function(d) { return d.title; }) .html(function(d) { return d.display || d.value; }) .on('mouseenter', _mouseEnterHandler) diff --git a/modules/ui/fields/address.js b/modules/ui/fields/address.js index 7abefddc86..7830079298 100644 --- a/modules/ui/fields/address.js +++ b/modules/ui/fields/address.js @@ -274,7 +274,7 @@ export function uiFieldAddress(field, context) { } if (_countryCode) { var localkey = subfield.id + '!' + _countryCode; - var tkey = addrField.strings.placeholders[localkey] ? localkey : subfield.id; + var tkey = addrField.hasTextForStringId('placeholders.' + localkey) ? localkey : subfield.id; return addrField.t('placeholders.' + tkey); } }); diff --git a/modules/ui/fields/check.js b/modules/ui/fields/check.js index f1d2245425..0c10926028 100644 --- a/modules/ui/fields/check.js +++ b/modules/ui/fields/check.js @@ -16,7 +16,7 @@ export { uiFieldCheck as uiFieldOnewayCheck }; export function uiFieldCheck(field, context) { var dispatch = d3_dispatch('change'); - var options = field.strings && field.strings.options; + var options = field.options; var values = []; var texts = []; @@ -33,9 +33,10 @@ export function uiFieldCheck(field, context) { if (options) { - for (var k in options) { - values.push(k === 'undefined' ? undefined : k); - texts.push(field.t.html('options.' + k, { 'default': options[k] })); + for (var i in options) { + var v = options[i]; + values.push(v === 'undefined' ? undefined : v); + texts.push(field.t.html('options.' + v, { 'default': v })); } } else { values = [undefined, 'yes']; diff --git a/modules/ui/fields/combo.js b/modules/ui/fields/combo.js index 59458c66c2..04a33f1a77 100644 --- a/modules/ui/fields/combo.js +++ b/modules/ui/fields/combo.js @@ -24,8 +24,9 @@ export function uiFieldCombo(field, context) { var _isMulti = (field.type === 'multiCombo' || field.type === 'manyCombo'); var _isNetwork = (field.type === 'networkCombo'); var _isSemi = (field.type === 'semiCombo'); - var _optstrings = field.strings && field.strings.options; var _optarray = field.options; + var _showTagInfoSuggestions = field.type !== 'manyCombo' && field.autoSuggestions !== false; + var _allowCustomValues = field.type !== 'manyCombo' && field.customValues !== false; var _snake_case = (field.snake_case || (field.snake_case === undefined)); var _combobox = uiCombobox(context, 'combo-' + field.safeid) .caseSensitive(field.caseSensitive) @@ -57,10 +58,6 @@ export function uiFieldCombo(field, context) { return s.replace(/\s+/g, '_'); } - function unsnake(s) { - return s.replace(/_+/g, ' '); - } - function clean(s) { return s.split(';') .map(function(s) { return s.trim(); }) @@ -73,14 +70,10 @@ export function uiFieldCombo(field, context) { function tagValue(dval) { dval = clean(dval || ''); - if (_optstrings) { - var found = _comboData.find(function(o) { - return o.key && clean(o.value) === dval; - }); - if (found) { - return found.key; - } - } + var found = _comboData.find(function(o) { + return o.key && clean(o.value) === dval; + }); + if (found) return found.key; if (field.type === 'typeCombo' && !dval) { return 'yes'; @@ -95,20 +88,15 @@ export function uiFieldCombo(field, context) { function displayValue(tval) { tval = tval || ''; - if (_optstrings) { - var found = _comboData.find(function(o) { - return o.key === tval && o.value; - }); - if (found) { - return found.value; - } + if (field.hasTextForStringId('options.' + tval)) { + return field.t('options.' + tval, { default: tval }); } if (field.type === 'typeCombo' && tval.toLowerCase() === 'yes') { return ''; } - return _snake_case ? unsnake(tval) : tval; + return tval; } @@ -127,46 +115,33 @@ export function uiFieldCombo(field, context) { function initCombo(selection, attachTo) { - if (_optstrings) { + if (!_allowCustomValues) { selection.attr('readonly', 'readonly'); - selection.call(_combobox, attachTo); - setStaticValues(setPlaceholder); - - } else if (_optarray) { - selection.call(_combobox, attachTo); - setStaticValues(setPlaceholder); + } - } else if (services.taginfo) { + if (_showTagInfoSuggestions && services.taginfo) { selection.call(_combobox.fetcher(setTaginfoValues), attachTo); setTaginfoValues('', setPlaceholder); + + } else { + selection.call(_combobox, attachTo); + setStaticValues(setPlaceholder); } } function setStaticValues(callback) { - if (!(_optstrings || _optarray)) return; - - if (_optstrings) { - _comboData = Object.keys(_optstrings).map(function(k) { - var v = field.t('options.' + k, { 'default': _optstrings[k] }); - return { - key: k, - value: v, - title: v, - display: field.t.html('options.' + k, { 'default': _optstrings[k] }) - }; - }); - - } else if (_optarray) { - _comboData = _optarray.map(function(k) { - var v = _snake_case ? unsnake(k) : k; - return { - key: k, - value: v, - title: v - }; - }); - } + if (!_optarray) return; + + _comboData = _optarray.map(function(v) { + return { + key: v, + value: field.t('options.' + v, { default: v }), + title: v, + display: field.t.html('options.' + v, { default: v }), + klass: field.hasTextForStringId('options.' + v) ? '' : 'raw-option' + }; + }); _combobox.data(objectDifference(_comboData, _multiData)); if (callback) callback(_comboData); @@ -225,11 +200,13 @@ export function uiFieldCombo(field, context) { _comboData = data.map(function(d) { var k = d.value; if (_isMulti) k = k.replace(field.key, ''); - var v = _snake_case ? unsnake(k) : k; + var label = field.t('options.' + k, { default: k }); return { key: k, - value: v, - title: _isMulti ? v : d.title + value: label, + display: field.t.html('options.' + k, { default: k }), + title: d.title || label, + klass: field.hasTextForStringId('options.' + k) ? '' : 'raw-option' }; }); @@ -507,9 +484,9 @@ export function uiFieldCombo(field, context) { _combobox.data(available); // Hide 'Add' button if this field uses fixed set of - // translateable _optstrings and they're all currently used, + // options and they're all currently used, // or if the field is already at its character limit - var hideAdd = (_optstrings && !available.length) || maxLength <= 0; + var hideAdd = (!_allowCustomValues && !available.length) || maxLength <= 0; _container.selectAll('.chiplist .input-wrap') .style('display', hideAdd ? 'none' : null); @@ -530,6 +507,11 @@ export function uiFieldCombo(field, context) { chips = chips.merge(enter) .order() + .classed('raw-value', function(d) { + var k = d.key; + if (_isMulti) k = k.replace(field.key, ''); + return !field.hasTextForStringId('options.' + k); + }) .classed('draggable', allowDragAndDrop) .classed('mixed', function(d) { return d.isMixed; @@ -558,7 +540,14 @@ export function uiFieldCombo(field, context) { return displayValue(val); }).filter(Boolean); + var showsValue = !isMixed && tags[field.key] && !(field.type === 'typeCombo' && tags[field.key] === 'yes'); + var isRawValue = showsValue && !field.hasTextForStringId('options.' + tags[field.key]); + var isKnownValue = showsValue && !isRawValue; + utilGetSetValue(_input, !isMixed ? displayValue(tags[field.key]) : '') + .classed('raw-value', isRawValue) + .classed('known-value', isKnownValue) + .attr('readonly', (!_allowCustomValues || isKnownValue) ? 'readonly' : undefined) .attr('title', isMixed ? mixedValues.join('\n') : undefined) .attr('placeholder', isMixed ? t('inspector.multiple_values') : _staticPlaceholder || '') .classed('mixed', isMixed); diff --git a/modules/ui/fields/cycleway.js b/modules/ui/fields/cycleway.js index 957cea217f..5976dbea96 100644 --- a/modules/ui/fields/cycleway.js +++ b/modules/ui/fields/cycleway.js @@ -115,7 +115,7 @@ export function uiFieldCycleway(field, context) { cycleway.options = function() { - return Object.keys(field.strings.options).map(function(option) { + return field.options.map(function(option) { return { title: field.t('options.' + option + '.description'), value: option diff --git a/modules/ui/fields/index.js b/modules/ui/fields/index.js index d02fea4d32..4d071b292b 100644 --- a/modules/ui/fields/index.js +++ b/modules/ui/fields/index.js @@ -65,6 +65,7 @@ export var uiFields = { lanes: uiFieldLanes, localized: uiFieldLocalized, roadspeed: uiFieldRoadspeed, + roadheight: uiFieldText, manyCombo: uiFieldManyCombo, multiCombo: uiFieldMultiCombo, networkCombo: uiFieldNetworkCombo, diff --git a/modules/ui/fields/input.js b/modules/ui/fields/input.js index b812e57f54..809f6d552f 100644 --- a/modules/ui/fields/input.js +++ b/modules/ui/fields/input.js @@ -53,7 +53,7 @@ export function uiFieldText(field, context) { input = input.enter() .append('input') - .attr('type', field.type === 'identifier' ? 'text' : field.type) + .attr('type', field.type === 'identifier' || field.type === 'roadheight' ? 'text' : field.type) .attr('id', field.domId) .classed(field.type, true) .call(utilNoAuto) diff --git a/modules/ui/fields/radio.js b/modules/ui/fields/radio.js index 6b035c7bdd..17be41a735 100644 --- a/modules/ui/fields/radio.js +++ b/modules/ui/fields/radio.js @@ -16,7 +16,7 @@ export function uiFieldRadio(field, context) { var wrap = d3_select(null); var labels = d3_select(null); var radios = d3_select(null); - var radioData = (field.options || (field.strings && field.strings.options && Object.keys(field.strings.options)) || field.keys).slice(); // shallow copy + var radioData = (field.options || field.keys).slice(); // shallow copy var typeField; var layerField; var _oldType = {}; diff --git a/scripts/build_data.js b/scripts/build_data.js index 0231a1a5b9..cebdb3552c 100644 --- a/scripts/build_data.js +++ b/scripts/build_data.js @@ -89,9 +89,9 @@ function buildData() { minifyJSON('data/territory_languages.json', 'dist/data/territory_languages.min.json'), Promise.all([ // Fetch the icons that are needed by the expected tagging schema version - fetch('https://cdn.jsdelivr.net/npm/@openstreetmap/id-tagging-schema@2/dist/presets.min.json'), - fetch('https://cdn.jsdelivr.net/npm/@openstreetmap/id-tagging-schema@2/dist/preset_categories.min.json'), - fetch('https://cdn.jsdelivr.net/npm/@openstreetmap/id-tagging-schema@2/dist/fields.min.json'), + fetch('https://cdn.jsdelivr.net/npm/@openstreetmap/id-tagging-schema@3/dist/presets.min.json'), + fetch('https://cdn.jsdelivr.net/npm/@openstreetmap/id-tagging-schema@3/dist/preset_categories.min.json'), + fetch('https://cdn.jsdelivr.net/npm/@openstreetmap/id-tagging-schema@3/dist/fields.min.json'), // WARNING: we fetch the bleeding edge data too to make sure we're always hosting the // latest icons, but note that the format could break at any time fetch('https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/presets.min.json'),