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'); }