From 97802ee73a2e2d5b396cc782ebc9a779eece0b94 Mon Sep 17 00:00:00 2001 From: Bryan Housel Date: Fri, 13 Dec 2024 12:28:21 -0500 Subject: [PATCH] Show "multiple values" if a tag is only set on one feature (closes #1493) Also, show a generic preset icon when multiple types are selected --- modules/ui/entity_editor.js | 74 +++++++++++++---------------- modules/ui/preset_icon.js | 20 ++++---- modules/ui/sections/feature_type.js | 4 +- 3 files changed, 47 insertions(+), 51 deletions(-) diff --git a/modules/ui/entity_editor.js b/modules/ui/entity_editor.js index dd899448dd..e100e6305d 100644 --- a/modules/ui/entity_editor.js +++ b/modules/ui/entity_editor.js @@ -412,65 +412,57 @@ export function uiEntityEditor(context) { // width: [ '3', undefined ] // } function _getCombinedTags(entityIDs, graph) { - const combined = {}; - const tagCounts = {}; + const combined = new Map(); // Map + const counts = new Map(); // Map const entities = entityIDs.map(entityID => graph.hasEntity(entityID)).filter(Boolean); - // gather the keys - const allKeys = new Set(); + // Gather the keys for (const entity of entities) { for (const k of Object.keys(entity.tags)) { if (k) { - allKeys.add(k); + combined.set(k, new Set()); } } } - // gather the values + // Gather the values for (const entity of entities) { - for (const k of allKeys) { - const v = entity.tags[k]; // purposely allow `undefined` - const vals = combined[k]; - - if (!vals) { // first value found, just save the value - combined[k] = v; - } else { - if (!Array.isArray(vals)) { - if (vals !== v) { // additional value found, convert to Array of values - combined[k] = [vals, v]; - } - } else { - if (!vals.includes(v)) { // additional value found, append to Array of values - vals.push(v); - } - } - } + for (const [k, vals] of combined) { + const v = entity.tags[k]; + vals.add(v); // `v` may be 'undefined', we need to collect these also. const kv = `${k}=${v}`; - tagCounts[kv] = (tagCounts[kv] ?? 0) + 1; + const count = counts.get(kv) ?? 0; + counts.set(kv, count + 1); } } - // sort the Array-like values - for (const [k, vals] of Object.entries(combined)) { - if (!Array.isArray(vals)) continue; - - // sort in place, by frequency then alphabetically - vals.sort((val1, val2) => { - const count1 = tagCounts[`${k}=${val1}`] ?? 0; - const count2 = tagCounts[`${k}=${val2}`] ?? 0; - if (count2 !== count1) { - return count2 - count1; - } - if (val2 && val1) { - return val1.localeCompare(val2); - } - return val1 ? 1 : -1; - }); + // Return results as an Object, where the values are either single values or Arrays + const results = {}; + for (const [k, vals] of combined) { + const arr = [...vals]; + + if (arr.length === 1) { // entities all have same value.. + results[k] = arr[0]; + + } else { // entities have different values.. + // sort in place, by frequency then alphabetically + results[k] = arr.sort((v1, v2) => { + const count1 = counts.get(`${k}=${v1}`) ?? 0; + const count2 = counts.get(`${k}=${v2}`) ?? 0; + if (count2 !== count1) { + return count2 - count1; + } + if (v2 && v1) { + return v1.localeCompare(v2); + } + return v1 ? 1 : -1; + }); + } } - return combined; + return results; } diff --git a/modules/ui/preset_icon.js b/modules/ui/preset_icon.js index 8aaea60fcd..67ede38ac7 100644 --- a/modules/ui/preset_icon.js +++ b/modules/ui/preset_icon.js @@ -9,6 +9,7 @@ export function uiPresetIcon(context) { function getIcon(p, geom) { + if (Array.isArray(p)) return 'rapid-icon-data'; if (p.icon) return p.icon; if (geom === 'line') return 'rapid-other-line'; if (geom === 'vertex') return p.isFallback() ? '' : 'temaki-vertex'; @@ -230,9 +231,10 @@ export function uiPresetIcon(context) { let geom = _geometry; if (!p || !geom) return; // nothing to display - // 'p' is either a preset or a category - const isPreset = (typeof p.setTags === 'function'); - const isCategory = !isPreset; + // 'p' is either an array, a preset or a category + const isMulti = Array.isArray(p); + const isPreset = !isMulti && (typeof p.setTags === 'function'); + const isCategory = !isMulti && !isPreset; const tags = isPreset ? p.setTags({}, geom) : {}; for (let k in tags) { @@ -245,8 +247,10 @@ export function uiPresetIcon(context) { geom = 'route'; } - const prefs = context.systems.storage; - const showThirdPartyIcons = (prefs.getItem('preferences.privacy.thirdpartyicons') ?? 'true') === 'true'; + const storage = context.systems.storage; + const styles = context.systems.styles; + + const showThirdPartyIcons = (storage.getItem('preferences.privacy.thirdpartyicons') ?? 'true') === 'true'; const imageURL = showThirdPartyIcons && p.imageURL; const picon = getIcon(p, geom); // const showPoint = isPreset && (geom === 'point'); // not actually used @@ -254,7 +258,7 @@ export function uiPresetIcon(context) { const showLine = isPreset && (geom === 'line'); const showArea = isPreset && (geom === 'area'); const showRoute = isPreset && (geom === 'route') && (p.id !== 'type/route'); - const style = context.systems.styles.styleMatch(tags); + const style = styles.styleMatch(tags); container .classed('showing-img', !!imageURL); @@ -269,8 +273,8 @@ export function uiPresetIcon(context) { // Render Icon if (picon) { - const isRaised = showLine || showRoute; // move the icon up a little - const isShrunk = isCategory || showLine || showRoute; // make it smaller + const isRaised = showLine || showRoute; // move the icon up a little + const isShrunk = isMulti || isCategory || showLine || showRoute; // make it smaller const isRapidIcon = /^rapid-/.test(picon); let klass = []; diff --git a/modules/ui/sections/feature_type.js b/modules/ui/sections/feature_type.js index bcc665e337..4c1b6c7694 100644 --- a/modules/ui/sections/feature_type.js +++ b/modules/ui/sections/feature_type.js @@ -86,8 +86,8 @@ export function uiSectionFeatureType(context) { let geometries = entityGeometries(); selection.select('.preset-list-item button') .call(uiPresetIcon(context) - .geometry(_presets.length === 1 ? (geometries.length === 1 && geometries[0]) : null) - .preset(_presets.length === 1 ? _presets[0] : context.systems.presets.item('point')) + .geometry(geometries.length === 1 ? geometries[0] : geometries) + .preset(_presets.length === 1 ? _presets[0] : _presets) ); let names = _presets.length === 1 ? [