modules/util/util.js (187 lines of code) (raw):
import { Extent } from '@id-sdk/math';
import { utilEntityOrDeepMemberSelector } from '@id-sdk/util';
import { fixRTLTextForSvg, rtlRegex } from './svg_paths_rtl_fix';
import { presetManager } from '../presets';
import { t, localizer } from '../core/localizer';
import { utilDetect } from './detect';
// Accepts an array of entities -or- entityIDs
export function utilTotalExtent(array, graph) {
return array.reduce(function(extent, val) {
var entity = (typeof val === 'string' ? graph.hasEntity(val) : val);
if (entity) {
var other = entity.extent(graph);
// update extent in place
extent.min = [ Math.min(extent.min[0], other.min[0]), Math.min(extent.min[1], other.min[1]) ];
extent.max = [ Math.max(extent.max[0], other.max[0]), Math.max(extent.max[1], other.max[1]) ];
}
return extent;
}, new Extent());
}
// Adds or removes highlight styling for the specified entities
export function utilHighlightEntities(ids, highlighted, context) {
context.surface()
.selectAll(utilEntityOrDeepMemberSelector(ids, context.graph()))
.classed('highlighted', highlighted);
}
/**
* @param {boolean} hideNetwork If true, the `network` tag will not be used in the name to prevent
* it being shown twice (see PR #8707#discussion_r712658175)
*/
export function utilDisplayName(entity, hideNetwork) {
var localizedNameKey = 'name:' + localizer.languageCode().toLowerCase();
var name = entity.tags[localizedNameKey] || entity.tags.name || '';
var tags = {
name,
direction: entity.tags.direction,
from: entity.tags.from,
network: hideNetwork ? undefined : (entity.tags.cycle_network || entity.tags.network),
ref: entity.tags.ref,
to: entity.tags.to,
via: entity.tags.via
};
// for routes, prefer `network+ref+name` or `ref+name` over `name`
if (name && tags.ref && entity.tags.route) {
return tags.network
? t('inspector.display_name.network_ref_name', tags)
: t('inspector.display_name.ref_name', tags);
}
if (name) return name;
var keyComponents = [];
if (tags.network) {
keyComponents.push('network');
}
if (tags.ref) {
keyComponents.push('ref');
}
// Routes may need more disambiguation based on direction or destination
if (entity.tags.route) {
if (tags.direction) {
keyComponents.push('direction');
} else if (tags.from && tags.to) {
keyComponents.push('from');
keyComponents.push('to');
if (tags.via) {
keyComponents.push('via');
}
}
}
if (keyComponents.length) {
name = t('inspector.display_name.' + keyComponents.join('_'), tags);
}
// if there's still no name found, try addr:housename
if (!name && entity.tags['addr:housename']) {
name = entity.tags['addr:housename'];
}
// as a last resort, use the street address as a name
if (!name && entity.tags['addr:housenumber'] && entity.tags['addr:street']) {
name = entity.tags['addr:housenumber'] + ' ' + entity.tags['addr:street'];
}
return name;
}
export function utilDisplayNameForPath(entity) {
var name = utilDisplayName(entity);
var isFirefox = utilDetect().browser.toLowerCase().indexOf('firefox') > -1;
var isNewChromium = Number(utilDetect().version.split('.')[0]) >= 96.0;
if (!isFirefox && !isNewChromium && name && rtlRegex.test(name)) {
name = fixRTLTextForSvg(name);
}
return name;
}
export function utilDisplayType(id) {
return {
n: t('inspector.node'),
w: t('inspector.way'),
r: t('inspector.relation')
}[id.charAt(0)];
}
// `utilDisplayLabel`
// Returns a string suitable for display
// By default returns something like name/ref, fallback to preset type, fallback to OSM type
// "Main Street" or "Tertiary Road"
// If `verbose=true`, include both preset name and feature name.
// "Tertiary Road Main Street"
//
export function utilDisplayLabel(entity, graphOrGeometry, verbose) {
var result;
var displayName = utilDisplayName(entity);
var preset = typeof graphOrGeometry === 'string' ?
presetManager.matchTags(entity.tags, graphOrGeometry) :
presetManager.match(entity, graphOrGeometry);
var presetName = preset && (preset.suggestion ? preset.subtitle() : preset.name());
if (verbose) {
result = [presetName, displayName].filter(Boolean).join(' ');
} else {
result = displayName || presetName;
}
// Fallback to the OSM type (node/way/relation)
return result || utilDisplayType(entity.id);
}
export function utilPrefixDOMProperty(property) {
var prefixes = ['webkit', 'ms', 'moz', 'o'];
var i = -1;
var n = prefixes.length;
var s = document.body;
if (property in s) return property;
property = property.substr(0, 1).toUpperCase() + property.substr(1);
while (++i < n) {
if (prefixes[i] + property in s) {
return prefixes[i] + property;
}
}
return false;
}
export function utilPrefixCSSProperty(property) {
var prefixes = ['webkit', 'ms', 'Moz', 'O'];
var i = -1;
var n = prefixes.length;
var s = document.body.style;
if (property.toLowerCase() in s) {
return property.toLowerCase();
}
while (++i < n) {
if (prefixes[i] + property in s) {
return '-' + prefixes[i].toLowerCase() + property.replace(/([A-Z])/g, '-$1').toLowerCase();
}
}
return false;
}
var transformProperty;
export function utilSetTransform(el, x, y, scale) {
var prop = transformProperty = transformProperty || utilPrefixCSSProperty('Transform');
var translate = utilDetect().opera ? 'translate(' + x + 'px,' + y + 'px)'
: 'translate3d(' + x + 'px,' + y + 'px,0)';
return el.style(prop, translate + (scale ? ' scale(' + scale + ')' : ''));
}
// a d3.mouse-alike which
// 1. Only works on HTML elements, not SVG
// 2. Does not cause style recalculation
export function utilFastMouse(container) {
var rect = container.getBoundingClientRect();
var rectLeft = rect.left;
var rectTop = rect.top;
var clientLeft = +container.clientLeft;
var clientTop = +container.clientTop;
return function(e) {
return [
e.clientX - rectLeft - clientLeft,
e.clientY - rectTop - clientTop
];
};
}
export function utilAsyncMap(inputs, func, callback) {
var remaining = inputs.length;
var results = [];
var errors = [];
inputs.forEach(function(d, i) {
func(d, function done(err, data) {
errors[i] = err;
results[i] = data;
remaining--;
if (!remaining) callback(errors, results);
});
});
}
// wraps an index to an interval [0..length-1]
export function utilWrap(index, length) {
if (index < 0) {
index += Math.ceil(-index/length)*length;
}
return index % length;
}
/**
* a replacement for functor
*
* @param {*} value any value
* @returns {Function} a function that returns that value or the value if it's a function
*/
export function utilFunctor(value) {
if (typeof value === 'function') return value;
return function() {
return value;
};
}
export function utilNoAuto(selection) {
var isText = (selection.size() && selection.node().tagName.toLowerCase() === 'textarea');
return selection
// assign 'new-password' even for non-password fields to prevent browsers (Chrome) ignoring 'off'
.attr('autocomplete', 'new-password')
.attr('autocorrect', 'off')
.attr('autocapitalize', 'off')
.attr('spellcheck', isText ? 'true' : 'false');
}