Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Repost: Support translatable options in all combo fields #8238

Merged
merged 2 commits into from
Dec 9, 2020
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions css/80_app.css
Original file line number Diff line number Diff line change
@@ -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
------------------------------------------------------- */
12 changes: 6 additions & 6 deletions modules/core/file_fetcher.js
Original file line number Diff line number Diff line change
@@ -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',
6 changes: 5 additions & 1 deletion modules/core/localizer.js
Original file line number Diff line number Diff line change
@@ -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;
3 changes: 2 additions & 1 deletion modules/presets/field.js
Original file line number Diff line number Diff line change
@@ -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 });
4 changes: 3 additions & 1 deletion modules/ui/combobox.js
Original file line number Diff line number Diff line change
@@ -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)
2 changes: 1 addition & 1 deletion modules/ui/fields/address.js
Original file line number Diff line number Diff line change
@@ -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);
}
});
9 changes: 5 additions & 4 deletions modules/ui/fields/check.js
Original file line number Diff line number Diff line change
@@ -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'];
125 changes: 66 additions & 59 deletions modules/ui/fields/combo.js
Original file line number Diff line number Diff line change
@@ -8,6 +8,8 @@ import { osmEntity } from '../../osm/entity';
import { t } from '../../core/localizer';
import { services } from '../../services';
import { uiCombobox } from '../combobox';

import { utilKeybinding } from '../../util/keybinding';
import { utilArrayUniq, utilGetSetValue, utilNoAuto, utilRebind, utilTotalExtent, utilUnicodeCharsCount } from '../../util';

export {
@@ -24,8 +26,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)
@@ -54,11 +57,7 @@ export function uiFieldCombo(field, context) {


function snake(s) {
return s.replace(/\s+/g, '_');
}

function unsnake(s) {
return s.replace(/_+/g, ' ');
return s.replace(/\s+/g, '_').toLowerCase();
}

function clean(s) {
@@ -73,14 +72,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 +90,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 +117,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 +202,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 +486,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 +509,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,10 +542,33 @@ 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;

var isReadOnly = !_allowCustomValues || isKnownValue;

utilGetSetValue(_input, !isMixed ? displayValue(tags[field.key]) : '')
.classed('raw-value', isRawValue)
.classed('known-value', isKnownValue)
.attr('readonly', isReadOnly ? 'readonly' : undefined)
.attr('title', isMixed ? mixedValues.join('\n') : undefined)
.attr('placeholder', isMixed ? t('inspector.multiple_values') : _staticPlaceholder || '')
.classed('mixed', isMixed);
.classed('mixed', isMixed)
.on('keydown.deleteCapture', function(d3_event) {
if (isReadOnly &&
isKnownValue &&
(d3_event.keyCode === utilKeybinding.keyCodes['⌫'] ||
d3_event.keyCode === utilKeybinding.keyCodes['⌦'])) {

d3_event.preventDefault();
d3_event.stopPropagation();

var t = {};
t[field.key] = undefined;
dispatch.call('change', this, t);
}
});
}
};

2 changes: 1 addition & 1 deletion modules/ui/fields/cycleway.js
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions modules/ui/fields/index.js
Original file line number Diff line number Diff line change
@@ -65,6 +65,7 @@ export var uiFields = {
lanes: uiFieldLanes,
localized: uiFieldLocalized,
roadspeed: uiFieldRoadspeed,
roadheight: uiFieldText,
manyCombo: uiFieldManyCombo,
multiCombo: uiFieldMultiCombo,
networkCombo: uiFieldNetworkCombo,
2 changes: 1 addition & 1 deletion modules/ui/fields/input.js
Original file line number Diff line number Diff line change
@@ -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)
2 changes: 1 addition & 1 deletion modules/ui/fields/radio.js
Original file line number Diff line number Diff line change
@@ -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 = {};
6 changes: 3 additions & 3 deletions scripts/build_data.js
Original file line number Diff line number Diff line change
@@ -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'),