Skip to content

Commit

Permalink
Show "multiple values" if a tag is only set on one feature
Browse files Browse the repository at this point in the history
(closes #1493)

Also, show a generic preset icon when multiple types are selected
  • Loading branch information
bhousel committed Dec 13, 2024
1 parent 9ce8306 commit 97802ee
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 51 deletions.
74 changes: 33 additions & 41 deletions modules/ui/entity_editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -412,65 +412,57 @@ export function uiEntityEditor(context) {
// width: [ '3', undefined ]
// }
function _getCombinedTags(entityIDs, graph) {
const combined = {};
const tagCounts = {};
const combined = new Map(); // Map<key, Set<value>
const counts = new Map(); // Map<kv, number>

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


Expand Down
20 changes: 12 additions & 8 deletions modules/ui/preset_icon.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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) {
Expand All @@ -245,16 +247,18 @@ 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
const showVertex = isPreset && (geom === 'vertex');
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);
Expand All @@ -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 = [];
Expand Down
4 changes: 2 additions & 2 deletions modules/ui/sections/feature_type.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 ? [
Expand Down

0 comments on commit 97802ee

Please sign in to comment.