Merge branch 'develop' into vegbilder

This commit is contained in:
Martin Raifer
2023-07-20 17:21:45 +02:00
307 changed files with 67709 additions and 3411 deletions
+1 -1
View File
@@ -137,7 +137,7 @@ export function uiFeatureList(context) {
}
// A location search takes priority over an ID search
var idMatch = !locationMatch && q.match(/(?:^|\W)(node|way|relation|[nwr])\W?0*([1-9]\d*)(?:\W|$)/i);
var idMatch = !locationMatch && q.match(/(?:^|\W)(node|way|relation|[nwr])\W{0,2}0*([1-9]\d*)(?:\W|$)/i);
if (idMatch) {
var elemType = idMatch[1].charAt(0);
+147 -79
View File
@@ -1,6 +1,6 @@
import { dispatch as d3_dispatch } from 'd3-dispatch';
import { select as d3_select } from 'd3-selection';
import * as countryCoder from '@ideditor/country-coder';
import * as countryCoder from '@rapideditor/country-coder';
import { presetManager } from '../../presets';
import { fileFetcher } from '../../core/file_fetcher';
@@ -36,91 +36,97 @@ export function uiFieldAddress(field, context) {
.catch(function() { /* ignore */ });
function getNearStreets() {
function getNear(isAddressable, type, searchRadius, resultProp) {
var extent = combinedEntityExtent();
var l = extent.center();
var box = geoExtent(l).padByMeters(200);
var box = geoExtent(l).padByMeters(searchRadius);
var streets = context.history().intersects(box)
var features = context.history().intersects(box)
.filter(isAddressable)
.map(function(d) {
var loc = context.projection([
(extent[0][0] + extent[1][0]) / 2,
(extent[0][1] + extent[1][1]) / 2
]);
var choice = geoChooseEdge(context.graph().childNodes(d), loc, context.projection);
.map(d => {
let dist = geoSphericalDistance(d.extent(context.graph()).center(), l);
if (d.geometry(context.graph()) === 'line') {
var loc = context.projection([
(extent[0][0] + extent[1][0]) / 2,
(extent[0][1] + extent[1][1]) / 2
]);
var choice = geoChooseEdge(context.graph().childNodes(d), loc, context.projection);
dist = geoSphericalDistance(choice.loc, l);
}
const value = resultProp && d.tags[resultProp] ? d.tags[resultProp] : d.tags.name;
let title = value;
if (type === 'street') {
title = `${addrField.t('placeholders.street')}: ${title}`;
} else if (type === 'place') {
title = `${addrField.t('placeholders.place')}: ${title}`;
}
return {
title: d.tags.name,
value: d.tags.name,
dist: choice.distance
title,
value,
dist,
type,
klass: `address-${type}`
};
})
.sort(function(a, b) {
return a.dist - b.dist;
});
return utilArrayUniqBy(streets, 'value');
return utilArrayUniqBy(features, 'value');
}
function getNearStreets() {
function isAddressable(d) {
return d.tags.highway && d.tags.name && d.type === 'way';
}
return getNear(isAddressable, 'street', 200);
}
function getNearCities() {
var extent = combinedEntityExtent();
var l = extent.center();
var box = geoExtent(l).padByMeters(200);
var cities = context.history().intersects(box)
.filter(isAddressable)
.map(function(d) {
return {
title: d.tags['addr:city'] || d.tags.name,
value: d.tags['addr:city'] || d.tags.name,
dist: geoSphericalDistance(d.extent(context.graph()).center(), l)
};
})
.sort(function(a, b) {
return a.dist - b.dist;
});
return utilArrayUniqBy(cities, 'value');
function getNearPlaces() {
function isAddressable(d) {
if (d.tags.name) {
if (d.tags.admin_level === '8' && d.tags.boundary === 'administrative') return true;
if (d.tags.place) return true;
if (d.tags.boundary === 'administrative' && d.tags.admin_level > 8) return true;
}
return false;
}
return getNear(isAddressable, 'place', 200);
}
function getNearCities() {
function isAddressable(d) {
if (d.tags.name) {
if (d.tags.boundary === 'administrative' && d.tags.admin_level === '8') return true;
if (d.tags.border_type === 'city') return true;
if (d.tags.place === 'city' || d.tags.place === 'town' || d.tags.place === 'village') return true;
}
if (d.tags['addr:city']) return true;
if (d.tags[`${field.key}:city`]) return true;
return false;
}
return getNear(isAddressable, 'city', 200, `${field.key}:city`);
}
function getNearPostcodes() {
return [... new Set([]
.concat(getNearValues('postcode'))
.concat(getNear(d => d.tags.postal_code, 'postcode', 200, 'postal_code')))];
}
function getNearValues(key) {
var extent = combinedEntityExtent();
var l = extent.center();
var box = geoExtent(l).padByMeters(200);
const tagKey = `${field.key}:${key}`;
var results = context.history().intersects(box)
.filter(function hasTag(d) { return _entityIDs.indexOf(d.id) === -1 && d.tags[key]; })
.map(function(d) {
return {
title: d.tags[key],
value: d.tags[key],
dist: geoSphericalDistance(d.extent(context.graph()).center(), l)
};
})
.sort(function(a, b) {
return a.dist - b.dist;
});
function hasTag(d) {
return _entityIDs.indexOf(d.id) === -1 && d.tags[tagKey];
}
return utilArrayUniqBy(results, 'value');
return getNear(hasTag, key, 200, tagKey);
}
@@ -142,11 +148,11 @@ export function uiFieldAddress(field, context) {
var dropdowns = addressFormat.dropdowns || [
'city', 'county', 'country', 'district', 'hamlet',
'neighbourhood', 'place', 'postcode', 'province',
'quarter', 'state', 'street', 'subdistrict', 'suburb'
'quarter', 'state', 'street', 'street+place', 'subdistrict', 'suburb'
];
var widths = addressFormat.widths || {
housenumber: 1/3, street: 2/3,
housenumber: 1/5, unit: 1/5, street: 1/2, place: 1/2,
city: 2/3, state: 1/4, postcode: 1/3
};
@@ -191,16 +197,45 @@ export function uiFieldAddress(field, context) {
function addDropdown(d) {
if (dropdowns.indexOf(d.id) === -1) return; // not a dropdown
var nearValues = (d.id === 'street') ? getNearStreets
: (d.id === 'city') ? getNearCities
: getNearValues;
var nearValues;
switch (d.id) {
case 'street':
nearValues = getNearStreets;
break;
case 'place':
nearValues = getNearPlaces;
break;
case 'street+place':
nearValues = () => []
.concat(getNearStreets())
.concat(getNearPlaces());
d.isAutoStreetPlace = true;
d.id = _tags[`${field.key}:place`] ? 'place' : 'street';
break;
case 'city':
nearValues = getNearCities;
break;
case 'postcode':
nearValues = getNearPostcodes;
break;
default:
nearValues = getNearValues;
}
d3_select(this)
.call(uiCombobox(context, 'address-' + d.id)
.call(uiCombobox(context, `address-${d.isAutoStreetPlace ? 'street-place' : d.id}`)
.minItems(1)
.caseSensitive(true)
.fetcher(function(value, callback) {
callback(nearValues('addr:' + d.id));
.fetcher(function(typedValue, callback) {
typedValue = typedValue.toLowerCase();
callback(nearValues(d.id)
.filter(v => v.value.toLowerCase().indexOf(typedValue) !== -1));
})
.on('accept', function(selected) {
if (d.isAutoStreetPlace) {
// set subtag depending on selected entry
d.id = selected ? selected.type : 'street';
}
})
);
}
@@ -248,42 +283,75 @@ export function uiFieldAddress(field, context) {
function change(onInput) {
return function() {
var tags = {};
setTimeout(() => {
var tags = {};
_wrap.selectAll('input')
.each(function (subfield) {
var key = field.key + ':' + subfield.id;
_wrap.selectAll('input')
.each(function (subfield) {
var key = field.key + ':' + subfield.id;
var value = this.value;
if (!onInput) value = context.cleanTagValue(value);
var value = this.value;
if (!onInput) value = context.cleanTagValue(value);
// don't override multiple values with blank string
if (Array.isArray(_tags[key]) && !value) return;
// don't override multiple values with blank string
if (Array.isArray(_tags[key]) && !value) return;
tags[key] = value || undefined;
});
if (subfield.isAutoStreetPlace) {
if (subfield.id === 'street') {
tags[`${field.key}:place`] = undefined;
} else if (subfield.id === 'place') {
tags[`${field.key}:street`] = undefined;
}
}
dispatch.call('change', this, tags, onInput);
tags[key] = value || undefined;
});
dispatch.call('change', this, tags, onInput);
}, 0);
};
}
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.hasTextForStringId('placeholders.' + localkey) ? localkey : subfield.id;
return addrField.t('placeholders.' + tkey);
if (subfield.isAutoStreetPlace) {
return `${getLocalPlaceholder('street')} / ${getLocalPlaceholder('place')}`;
}
return getLocalPlaceholder(subfield.id);
});
}
function getLocalPlaceholder(key) {
if (_countryCode) {
var localkey = key + '!' + _countryCode;
var tkey = addrField.hasTextForStringId('placeholders.' + localkey) ? localkey : key;
return addrField.t('placeholders.' + tkey);
}
}
function updateTags(tags) {
utilGetSetValue(_wrap.selectAll('input'), function (subfield) {
var val = tags[field.key + ':' + subfield.id];
utilGetSetValue(_wrap.selectAll('input'), subfield => {
var val;
if (subfield.isAutoStreetPlace) {
const streetKey = `${field.key}:street`;
const placeKey = `${field.key}:place`;
if (tags[streetKey] !== undefined || tags[placeKey] === undefined) {
val = tags[streetKey];
subfield.id = 'street';
} else {
val = tags[placeKey];
subfield.id = 'place';
}
} else {
val = tags[`${field.key}:${subfield.id}`];
}
return typeof val === 'string' ? val : '';
})
.attr('title', function(subfield) {
+86 -34
View File
@@ -1,7 +1,7 @@
import { dispatch as d3_dispatch } from 'd3-dispatch';
import { select as d3_select } from 'd3-selection';
import { drag as d3_drag } from 'd3-drag';
import * as countryCoder from '@ideditor/country-coder';
import * as countryCoder from '@rapideditor/country-coder';
import { fileFetcher } from '../../core/file_fetcher';
import { osmEntity } from '../../osm/entity';
@@ -73,7 +73,7 @@ export function uiFieldCombo(field, context) {
function tagValue(dval) {
dval = clean(dval || '');
var found = getOptions().find(function(o) {
var found = getOptions(true).find(function(o) {
return o.key && clean(o.value) === dval;
});
if (found) return found.key;
@@ -151,7 +151,7 @@ export function uiFieldCombo(field, context) {
function objectDifference(a, b) {
return a.filter(function(d1) {
return !b.some(function(d2) {
return !d2.isMixed && d1.value === d2.value;
return d1.value === d2.value;
});
});
}
@@ -167,15 +167,21 @@ export function uiFieldCombo(field, context) {
setTaginfoValues('', setPlaceholder);
} else {
selection.call(_combobox, attachTo);
setStaticValues(setPlaceholder);
setTimeout(() => setStaticValues(setPlaceholder), 0);
}
}
function getOptions() {
function getOptions(allOptions) {
var stringsField = field.resolveReference('stringsCrossReference');
if (!(field.options || stringsField.options)) return [];
return (field.options || stringsField.options).map(function(v) {
let options;
if (allOptions !== true) {
options = field.options || stringsField.options;
} else {
options = [].concat(field.options, stringsField.options).filter(Boolean);
}
return options.map(function(v) {
const labelId = getLabelId(stringsField, v);
return {
key: v,
@@ -325,6 +331,12 @@ export function uiFieldCombo(field, context) {
_container.selectAll('input')
.attr('placeholder', ph);
// Hide 'Add' button if this field uses fixed set of
// options and they're all currently used
var hideAdd = (!_allowCustomValues && !values.length);
_container.selectAll('.chiplist .input-wrap')
.style('display', hideAdd ? 'none' : null);
}
@@ -404,6 +416,17 @@ export function uiFieldCombo(field, context) {
}
function invertMultikey(d3_event, d) {
d3_event.preventDefault();
d3_event.stopPropagation();
var t = {};
if (_isMulti) {
t[d.key] = _tags[d.key] === 'yes' ? 'no' : 'yes';
}
dispatch.call('change', this, t);
}
function combo(selection) {
_container = selection.selectAll('.form-field-input-wrap')
.data([0]);
@@ -443,6 +466,11 @@ export function uiFieldCombo(field, context) {
.attr('class', 'input-wrap')
.merge(_inputWrap);
// Hide 'Add' button if this field uses fixed set of
// options and they're all currently used
var hideAdd = (!_allowCustomValues && !_comboData.length);
_inputWrap.style('display', hideAdd ? 'none' : null);
_input = _inputWrap.selectAll('input')
.data([0]);
} else {
@@ -515,11 +543,15 @@ export function uiFieldCombo(field, context) {
function updateIcon(value) {
value = tagValue(value);
let container = _container;
if (field.type === 'multiCombo' || field.type === 'semiCombo') {
container = _container.select('.input-wrap');
}
const iconsField = field.resolveReference('iconsCrossReference');
if (iconsField.icons) {
_container.selectAll('.tag-value-icon').remove();
container.selectAll('.tag-value-icon').remove();
if (iconsField.icons[value]) {
_container.selectAll('.tag-value-icon')
container.selectAll('.tag-value-icon')
.data([value])
.enter()
.insert('div', 'input')
@@ -533,6 +565,14 @@ export function uiFieldCombo(field, context) {
_tags = tags;
var stringsField = field.resolveReference('stringsCrossReference');
var isMixed = Array.isArray(tags[field.key]);
var showsValue = value => !isMixed && value && !(field.type === 'typeCombo' && value === 'yes');
var isRawValue = value => showsValue(value)
&& !stringsField.hasTextForStringId(`options.${value}`)
&& !stringsField.hasTextForStringId(`options.${value}.title`);
var isKnownValue = value => showsValue(value) && !isRawValue(value);
var isReadOnly = !_allowCustomValues;
if (_isMulti || _isSemi) {
_multiData = [];
@@ -545,13 +585,13 @@ export function uiFieldCombo(field, context) {
if (!field.key && field.keys.indexOf(k) === -1) continue;
var v = tags[k];
if (!v || (typeof v === 'string' && v.toLowerCase() === 'no')) continue;
var suffix = field.key ? k.slice(field.key.length) : k;
_multiData.push({
key: k,
value: displayValue(suffix),
display: renderValue(suffix),
display: addComboboxIcons(renderValue(suffix), suffix),
state: typeof v === 'string' ? v.toLowerCase() : '',
isMixed: Array.isArray(v)
});
}
@@ -592,7 +632,7 @@ export function uiFieldCombo(field, context) {
return {
key: v,
value: displayValue(v),
display: renderValue(v),
display: addComboboxIcons(renderValue(v), v),
isMixed: !commonValues.includes(v)
};
});
@@ -610,20 +650,13 @@ 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);
// Hide 'Add' button if this field uses fixed set of
// options and they're all currently used,
// or if the field is already at its character limit
var hideAdd = (!_allowCustomValues && !available.length) || maxLength <= 0;
// Hide 'Add' button if this field is already at its character limit
var hideAdd = maxLength <= 0 || (!_allowCustomValues && !_comboData.length);
_container.selectAll('.chiplist .input-wrap')
.style('display', hideAdd ? 'none' : null);
var allowDragAndDrop = _isSemi // only semiCombo values are ordered
&& !Array.isArray(tags[field.key]);
// Render chips
var chips = _container.selectAll('.chip')
@@ -651,8 +684,24 @@ export function uiFieldCombo(field, context) {
return d.isMixed;
})
.attr('title', function(d) {
return d.isMixed ? t('inspector.unshared_value_tooltip') : null;
});
if (d.isMixed) {
return t('inspector.unshared_value_tooltip');
}
if (!['yes', 'no'].includes(d.state)) {
return d.state;
}
return null;
})
.classed('negated', d => d.state === 'no');
if (!_isSemi) {
chips.selectAll('input[type=checkbox]').remove();
chips.insert('input', 'span')
.attr('type', 'checkbox')
.property('checked', d => d.state === 'yes')
.property('indeterminate', d => d.isMixed || !['yes', 'no'].includes(d.state))
.on('click', invertMultikey);
}
if (allowDragAndDrop) {
registerDragAndDrop(chips);
@@ -674,21 +723,14 @@ export function uiFieldCombo(field, context) {
.attr('class', 'remove')
.text('×');
updateIcon('');
} else {
var isMixed = Array.isArray(tags[field.key]);
var mixedValues = isMixed && tags[field.key].map(function(val) {
return displayValue(val);
}).filter(Boolean);
var showsValue = !isMixed && tags[field.key] && !(field.type === 'typeCombo' && tags[field.key] === 'yes');
var isRawValue = showsValue && !stringsField.hasTextForStringId(`options.${tags[field.key]}`)
&& !stringsField.hasTextForStringId(`options.${tags[field.key]}.title`);
var isKnownValue = showsValue && !isRawValue;
var isReadOnly = !_allowCustomValues || isKnownValue;
utilGetSetValue(_input, !isMixed ? displayValue(tags[field.key]) : '')
.data([tags[field.key]])
.classed('raw-value', isRawValue)
.classed('known-value', isKnownValue)
.attr('readonly', isReadOnly ? 'readonly' : undefined)
@@ -697,7 +739,7 @@ export function uiFieldCombo(field, context) {
.classed('mixed', isMixed)
.on('keydown.deleteCapture', function(d3_event) {
if (isReadOnly &&
isKnownValue &&
isKnownValue(tags[field.key]) &&
(d3_event.keyCode === utilKeybinding.keyCodes['⌫'] ||
d3_event.keyCode === utilKeybinding.keyCodes['⌦'])) {
@@ -718,6 +760,16 @@ export function uiFieldCombo(field, context) {
_lengthIndicator.update(tags[field.key]);
}
}
const refreshStyles = () => {
_input
.data([tagValue(utilGetSetValue(_input))])
.classed('raw-value', isRawValue)
.classed('known-value', isKnownValue);
};
_input.on('input.refreshStyles', refreshStyles);
_combobox.on('update.refreshStyles', refreshStyles);
refreshStyles();
};
function registerDragAndDrop(selection) {
+111 -27
View File
@@ -1,7 +1,7 @@
import { dispatch as d3_dispatch } from 'd3-dispatch';
import { select as d3_select } from 'd3-selection';
import _debounce from 'lodash-es/debounce';
import * as countryCoder from '@ideditor/country-coder';
import * as countryCoder from '@rapideditor/country-coder';
import { presetManager } from '../../presets';
import { fileFetcher } from '../../core/file_fetcher';
@@ -11,6 +11,7 @@ import { svgIcon } from '../../svg/icon';
import { cardinal } from '../../osm/node';
import { uiLengthIndicator } from '..';
import { uiTooltip } from '../tooltip';
import { isEqual } from 'lodash-es';
export {
uiFieldText as uiFieldColour,
@@ -18,9 +19,11 @@ export {
uiFieldText as uiFieldIdentifier,
uiFieldText as uiFieldNumber,
uiFieldText as uiFieldTel,
uiFieldText as uiFieldUrl
uiFieldText as uiFieldUrl,
likelyRawNumberFormat
};
const likelyRawNumberFormat = /^-?(0\.\d*|\d*\.\d{0,2}(\d{4,})?|\d{4,}\.\d{3})$/;
export function uiFieldText(field, context) {
var dispatch = d3_dispatch('change');
@@ -32,6 +35,9 @@ export function uiFieldText(field, context) {
var _tags;
var _phoneFormats = {};
const isDirectionField = field.key.split(':').some(keyPart => keyPart === 'direction');
const formatFloat = localizer.floatFormatter(localizer.languageCode());
const parseLocaleFloat = localizer.floatParser(localizer.languageCode());
const countDecimalPlaces = localizer.decimalPlaceCounter(localizer.languageCode());
if (field.type === 'tel') {
fileFetcher.get('phone_formats')
@@ -132,18 +138,20 @@ export function uiFieldText(field, context) {
var raw_vals = input.node().value || '0';
var vals = raw_vals.split(';');
vals = vals.map(function(v) {
var num = Number(v);
v = v.trim();
const isRawNumber = likelyRawNumberFormat.test(v);
var num = isRawNumber ? parseFloat(v) : parseLocaleFloat(v);
if (isDirectionField) {
const compassDir = cardinal[v.trim().toLowerCase()];
const compassDir = cardinal[v.toLowerCase()];
if (compassDir !== undefined) {
num = compassDir;
}
}
if (!isFinite(num)) {
// do nothing if the value is neither a number, nor a cardinal direction
return v.trim();
}
// do nothing if the value is neither a number, nor a cardinal direction
if (!isFinite(num)) return v;
num = parseFloat(num);
if (!isFinite(num)) return v;
num += d;
// clamp to 0..359 degree range if it's a direction field
@@ -152,8 +160,9 @@ export function uiFieldText(field, context) {
num = ((num % 360) + 360) % 360;
}
// make sure no extra decimals are introduced
const numDecimals = v.includes('.') ? v.split('.')[1].length : 0;
return clamped(num).toFixed(numDecimals);
return formatFloat(clamped(num), isRawNumber
? (v.includes('.') ? v.split('.')[1].length : 0)
: countDecimalPlaces(v));
});
input.node().value = vals.join(';');
change()();
@@ -186,6 +195,7 @@ export function uiFieldText(field, context) {
window.open(url, '_blank');
}
})
.classed('disabled', () => !validIdentifierValueForLink())
.merge(outlinkButton);
} else if (field.type === 'url') {
input.attr('type', 'text');
@@ -366,7 +376,7 @@ export function uiFieldText(field, context) {
}
}
if (field.type === 'identifier' && field.pattern) {
return value && value.match(new RegExp(field.pattern))[0];
return value && value.match(new RegExp(field.pattern))?.[0];
}
return null;
}
@@ -384,6 +394,26 @@ export function uiFieldText(field, context) {
}
// returns all values of a (potential) multiselection and/or multi-key field
function getVals(tags) {
if (field.keys) {
const multiSelection = context.selectedIDs();
tags = multiSelection.length > 1
? context.selectedIDs()
.map(id => context.graph().entity(id))
.map(entity => entity.tags)
: [tags];
return tags.map(tags => new Set(field.keys
.reduce((acc, key) => acc.concat(tags[key]), [])
.filter(Boolean)))
.map(vals => vals.size === 0 ? new Set([undefined]) : vals)
.reduce((a, b) => new Set([...a, ...b]));
} else {
return new Set([].concat(tags[field.key]));
}
}
function change(onInput) {
return function() {
var t = {};
@@ -391,21 +421,42 @@ export function uiFieldText(field, context) {
if (!onInput) val = context.cleanTagValue(val);
// don't override multiple values with blank string
if (!val && Array.isArray(_tags[field.key])) return;
if (!val && getVals(_tags).size > 1) return;
if (!onInput) {
if (field.type === 'number' && val) {
var vals = val.split(';');
vals = vals.map(function(v) {
var num = Number(v);
return isFinite(num) ? clamped(num) : v.trim();
});
val = vals.join(';');
}
utilGetSetValue(input, val);
var displayVal = val;
if (field.type === 'number' && val) {
var numbers = val.split(';');
numbers = numbers.map(function(v) {
if (likelyRawNumberFormat.test(v)) {
// input number likely in "raw" format
return v;
}
var num = parseLocaleFloat(v);
const fractionDigits = countDecimalPlaces(v);
return isFinite(num) ? clamped(num).toFixed(fractionDigits) : v;
});
val = numbers.join(';');
}
if (!onInput) utilGetSetValue(input, displayVal);
t[field.key] = val || undefined;
dispatch.call('change', this, t, onInput);
if (field.keys) {
// for multi-key fields with: handle alternative tag keys gracefully
// https://github.com/openstreetmap/id-tagging-schema/issues/905
dispatch.call('change', this, tags => {
if (field.keys.some(key => tags[key])) {
// use exiting key(s)
field.keys.filter(key => tags[key]).forEach(key => {
tags[key] = val || undefined;
});
} else {
// fall back to default key if none of the `keys` is preset
tags[field.key] = val || undefined;
}
return tags;
}, onInput);
} else {
dispatch.call('change', this, t, onInput);
}
};
}
@@ -416,14 +467,47 @@ export function uiFieldText(field, context) {
return i;
};
i.tags = function(tags) {
_tags = tags;
var isMixed = Array.isArray(tags[field.key]);
const vals = getVals(tags);
const isMixed = vals.size > 1;
var val = vals.size === 1 ? [...vals][0] : '';
var shouldUpdate;
utilGetSetValue(input, !isMixed && tags[field.key] ? tags[field.key] : '')
.attr('title', isMixed ? tags[field.key].filter(Boolean).join('\n') : undefined)
if (field.type === 'number' && val) {
var numbers = val.split(';');
var oriNumbers = utilGetSetValue(input).split(';');
if (numbers.length !== oriNumbers.length) shouldUpdate = true;
numbers = numbers.map(function(v) {
v = v.trim();
var num = Number(v);
if (!isFinite(num) || v === '') return v;
const fractionDigits = v.includes('.') ? v.split('.')[1].length : 0;
return formatFloat(num, fractionDigits);
});
val = numbers.join(';');
// for number fields, we don't want to override the content of the
// input element with the same number using a different formatting
// (e.g. when entering "1234.5", this should not be reformatted to
// "1.234,5" which could otherwise cause the cursor to be in the
// wrong location after the change)
// but if the actual numeric value of the field has changed (e.g.
// by pressing the +/- buttons or using the raw tag editor), we
// can and should update the content of the input element.
shouldUpdate = (inputValue, setValue) => {
const inputNums = inputValue.split(';').map(setVal =>
likelyRawNumberFormat.test(setVal)
? parseFloat(setVal)
: parseLocaleFloat(setVal)
);
const setNums = setValue.split(';').map(parseLocaleFloat);
return !isEqual(inputNums, setNums);
};
}
utilGetSetValue(input, val, shouldUpdate)
.attr('title', isMixed ? [...vals].join('\n') : undefined)
.attr('placeholder', isMixed ? t('inspector.multiple_values') : (field.placeholder() || t('inspector.unknown')))
.classed('mixed', isMixed);
+2 -2
View File
@@ -1,6 +1,6 @@
import { dispatch as d3_dispatch } from 'd3-dispatch';
import { select as d3_select } from 'd3-selection';
import * as countryCoder from '@ideditor/country-coder';
import * as countryCoder from '@rapideditor/country-coder';
import { presetManager } from '../../presets';
import { fileFetcher } from '../../core/file_fetcher';
@@ -96,7 +96,7 @@ export function uiFieldLocalized(field, context) {
var preset = presetManager.match(entity, context.graph());
if (preset) {
var isSuggestion = preset.suggestion;
var fields = preset.fields();
var fields = preset.fields(entity.extent(context.graph()).center());
var showsBrandField = fields.some(function(d) { return d.id === 'brand'; });
var showsOperatorField = fields.some(function(d) { return d.id === 'operator'; });
var setsName = preset.addTags.name;
+37 -13
View File
@@ -1,10 +1,11 @@
import { dispatch as d3_dispatch } from 'd3-dispatch';
import { select as d3_select } from 'd3-selection';
import * as countryCoder from '@ideditor/country-coder';
import * as countryCoder from '@rapideditor/country-coder';
import { uiCombobox } from '../combobox';
import { t } from '../../core/localizer';
import { t, localizer } from '../../core/localizer';
import { utilGetSetValue, utilNoAuto, utilRebind, utilTotalExtent } from '../../util';
import { likelyRawNumberFormat } from './input';
export function uiFieldRoadheight(field, context) {
@@ -16,6 +17,8 @@ export function uiFieldRoadheight(field, context) {
var _entityIDs = [];
var _tags;
var _isImperial;
var formatFloat = localizer.floatFormatter(localizer.languageCode());
var parseLocaleFloat = localizer.floatParser(localizer.languageCode());
var primaryUnits = [
{
@@ -129,16 +132,27 @@ export function uiFieldRoadheight(field, context) {
if (!primaryValue && !secondaryValue) {
tag[field.key] = undefined;
} else if (isNaN(primaryValue) || isNaN(secondaryValue) || !_isImperial) {
tag[field.key] = context.cleanTagValue(primaryValue);
} else {
if (primaryValue !== '') {
primaryValue = context.cleanTagValue(primaryValue + '\'');
var rawPrimaryValue = likelyRawNumberFormat.test(primaryValue)
? parseFloat(primaryValue)
: parseLocaleFloat(primaryValue);
if (isNaN(rawPrimaryValue)) rawPrimaryValue = primaryValue;
var rawSecondaryValue = likelyRawNumberFormat.test(secondaryValue)
? parseFloat(secondaryValue)
: parseLocaleFloat(secondaryValue);
if (isNaN(rawSecondaryValue)) rawSecondaryValue = secondaryValue;
if (isNaN(rawPrimaryValue) || isNaN(rawSecondaryValue) || !_isImperial) {
tag[field.key] = context.cleanTagValue(rawPrimaryValue);
} else {
if (rawPrimaryValue !== '') {
rawPrimaryValue = rawPrimaryValue + '\'';
}
if (rawSecondaryValue !== '') {
rawSecondaryValue = rawSecondaryValue + '"';
}
tag[field.key] = context.cleanTagValue(rawPrimaryValue + rawSecondaryValue);
}
if (secondaryValue !== '') {
secondaryValue = context.cleanTagValue(secondaryValue + '"');
}
tag[field.key] = primaryValue + secondaryValue;
}
dispatch.call('change', this, tag);
@@ -156,26 +170,36 @@ export function uiFieldRoadheight(field, context) {
if (primaryValue && (primaryValue.indexOf('\'') >= 0 || primaryValue.indexOf('"') >= 0)) {
secondaryValue = primaryValue.match(/(-?[\d.]+)"/);
if (secondaryValue !== null) {
secondaryValue = secondaryValue[1];
secondaryValue = formatFloat(parseFloat(secondaryValue[1]));
}
primaryValue = primaryValue.match(/(-?[\d.]+)'/);
if (primaryValue !== null) {
primaryValue = primaryValue[1];
primaryValue = formatFloat(parseFloat(primaryValue[1]));
}
_isImperial = true;
} else if (primaryValue) {
var rawValue = primaryValue;
primaryValue = parseFloat(rawValue);
if (isNaN(primaryValue)) {
primaryValue = rawValue;
} else {
primaryValue = formatFloat(primaryValue);
}
_isImperial = false;
}
}
setUnitSuggestions();
// If feet are specified but inches are omitted, assume zero inches.
var inchesPlaceholder = formatFloat(0);
utilGetSetValue(primaryInput, typeof primaryValue === 'string' ? primaryValue : '')
.attr('title', isMixed ? primaryValue.filter(Boolean).join('\n') : null)
.attr('placeholder', isMixed ? t('inspector.multiple_values') : t('inspector.unknown'))
.classed('mixed', isMixed);
utilGetSetValue(secondaryInput, typeof secondaryValue === 'string' ? secondaryValue : '')
.attr('placeholder', isMixed ? t('inspector.multiple_values') : (_isImperial ? '0' : null))
.attr('placeholder', isMixed ? t('inspector.multiple_values') : (_isImperial ? inchesPlaceholder : null))
.classed('mixed', isMixed)
.classed('disabled', !_isImperial)
.attr('readonly', _isImperial ? null : 'readonly');
+27 -11
View File
@@ -1,10 +1,11 @@
import { dispatch as d3_dispatch } from 'd3-dispatch';
import { select as d3_select } from 'd3-selection';
import * as countryCoder from '@ideditor/country-coder';
import * as countryCoder from '@rapideditor/country-coder';
import { uiCombobox } from '../combobox';
import { t } from '../../core/localizer';
import { t, localizer } from '../../core/localizer';
import { utilGetSetValue, utilNoAuto, utilRebind, utilTotalExtent } from '../../util';
import { likelyRawNumberFormat } from './input';
export function uiFieldRoadspeed(field, context) {
@@ -14,6 +15,8 @@ export function uiFieldRoadspeed(field, context) {
var _entityIDs = [];
var _tags;
var _isImperial;
var formatFloat = localizer.floatFormatter(localizer.languageCode());
var parseLocaleFloat = localizer.floatParser(localizer.languageCode());
var speedCombo = uiCombobox(context, 'roadspeed');
var unitCombo = uiCombobox(context, 'roadspeed-unit')
@@ -91,8 +94,8 @@ export function uiFieldRoadspeed(field, context) {
function comboValues(d) {
return {
value: d.toString(),
title: d.toString()
value: formatFloat(d),
title: formatFloat(d)
};
}
@@ -106,10 +109,16 @@ export function uiFieldRoadspeed(field, context) {
if (!value) {
tag[field.key] = undefined;
} else if (isNaN(value) || !_isImperial) {
tag[field.key] = context.cleanTagValue(value);
} else {
tag[field.key] = context.cleanTagValue(value + ' mph');
var rawValue = likelyRawNumberFormat.test(value)
? parseFloat(value)
: parseLocaleFloat(value);
if (isNaN(rawValue)) rawValue = value;
if (isNaN(rawValue) || !_isImperial) {
tag[field.key] = context.cleanTagValue(rawValue);
} else {
tag[field.key] = context.cleanTagValue(rawValue + ' mph');
}
}
dispatch.call('change', this, tag);
@@ -119,16 +128,23 @@ export function uiFieldRoadspeed(field, context) {
roadspeed.tags = function(tags) {
_tags = tags;
var value = tags[field.key];
var rawValue = tags[field.key];
var value = rawValue;
var isMixed = Array.isArray(value);
if (!isMixed) {
if (value && value.indexOf('mph') >= 0) {
value = parseInt(value, 10).toString();
if (rawValue && rawValue.indexOf('mph') >= 0) {
_isImperial = true;
} else if (value) {
} else if (rawValue) {
_isImperial = false;
}
value = parseInt(value, 10);
if (isNaN(value)) {
value = rawValue;
} else {
value = formatFloat(value);
}
}
setUnitSuggestions();
+4 -1
View File
@@ -146,7 +146,10 @@ export function uiFieldWikidata(field, context) {
}
wikidata.itemsForSearchQuery(q, function(err, data) {
if (err) return;
if (err) {
if (err !== 'No query') console.error(err); // eslint-disable-line
return;
}
var result = data.map(function (item) {
return {
+9 -2
View File
@@ -92,7 +92,7 @@ export function uiPhotoviewer(context) {
target.style('height', newHeight + 'px');
}
dispatch.call(eventName, target, utilGetDimensions(target, true));
dispatch.call(eventName, target, subtractPadding(utilGetDimensions(target, true), target));
}
function clamp(num, min, max) {
@@ -151,9 +151,16 @@ export function uiPhotoviewer(context) {
.style('width', setPhotoDimensions[0] + 'px')
.style('height', setPhotoDimensions[1] + 'px');
dispatch.call('resize', photoviewer, setPhotoDimensions);
dispatch.call('resize', photoviewer, subtractPadding(setPhotoDimensions, photoviewer));
}
};
function subtractPadding(dimensions, selection) {
return [
dimensions[0] - parseFloat(selection.style('padding-left')) - parseFloat(selection.style('padding-right')),
dimensions[1] - parseFloat(selection.style('padding-top')) - parseFloat(selection.style('padding-bottom'))
];
}
return utilRebind(photoviewer, dispatch, 'on');
}
+8 -2
View File
@@ -4,6 +4,7 @@ import { presetManager } from '../../presets';
import { t, localizer } from '../../core/localizer';
import { utilArrayIdentical } from '../../util/array';
import { utilArrayUnion, utilRebind } from '../../util';
import { geoExtent } from '../../geo/extent';
import { uiField } from '../field';
import { uiFormFields } from '../form_fields';
import { uiSection } from '../section';
@@ -32,6 +33,11 @@ export function uiSectionPresetFields(context) {
return geoms;
}, {}));
const loc = _entityIDs.reduce(function(extent, entityID) {
var entity = context.graph().entity(entityID);
return extent.extend(entity.extent(context.graph()));
}, geoExtent()).center();
var presetsManager = presetManager;
var allFields = [];
@@ -39,8 +45,8 @@ export function uiSectionPresetFields(context) {
var sharedTotalFields;
_presets.forEach(function(preset) {
var fields = preset.fields();
var moreFields = preset.moreFields();
var fields = preset.fields(loc);
var moreFields = preset.moreFields(loc);
allFields = utilArrayUnion(allFields, fields);
allMoreFields = utilArrayUnion(allMoreFields, moreFields);
+10 -3
View File
@@ -11,6 +11,7 @@ import { localizer, t } from '../../core/localizer';
import { utilArrayDifference, utilArrayIdentical } from '../../util/array';
import { utilGetSetValue, utilNoAuto, utilRebind, utilTagDiff } from '../../util';
import { uiTooltip } from '..';
import { allowUpperCaseTagValues } from '../../osm/tags';
export function uiSectionRawTagEditor(id, context) {
@@ -421,7 +422,9 @@ export function uiSectionRawTagEditor(id, context) {
query: value
}, function(err, data) {
if (!err) {
var filtered = data.filter(function(d) { return _tags[d.value] === undefined; });
const filtered = data
.filter(d => _tags[d.value] === undefined)
.filter(d => d.value.toLowerCase().includes(value.toLowerCase()));
callback(sort(value, filtered));
}
});
@@ -435,9 +438,13 @@ export function uiSectionRawTagEditor(id, context) {
geometry: geometry,
query: value
}, function(err, data) {
if (!err) callback(sort(value, data));
if (!err) {
const filtered = data.filter(d => d.value.toLowerCase().includes(value.toLowerCase()));
callback(sort(value, filtered));
}
});
}));
})
.caseSensitive(allowUpperCaseTagValues.test(utilGetSetValue(key))));
function sort(value, data) {