From 3c6ba9703ad483925c0aa9658f47225be8cf6ede Mon Sep 17 00:00:00 2001 From: Bryan Housel Date: Wed, 5 Feb 2020 21:13:15 -0500 Subject: [PATCH] presetIndex.build -> presetIndex.merge, and make it merge data --- API.md | 5 +- modules/presets/category.js | 8 +- modules/presets/field.js | 4 + modules/presets/index.js | 155 +++++++++++++------------------- modules/presets/preset.js | 4 + test/spec/presets/category.js | 32 +++---- test/spec/presets/collection.js | 22 ++--- 7 files changed, 103 insertions(+), 127 deletions(-) diff --git a/API.md b/API.md index 8727abd26..8dfc397a8 100644 --- a/API.md +++ b/API.md @@ -43,9 +43,8 @@ of iD (e.g. `http://preview.ideditor.com/release/`), the following parameters ar * __`photo_overlay`__ - The street-level photo overlay layers to enable.
_Example:_ `photo_overlay=streetside,mapillary,openstreetcam`
_Available values:_ `streetside` (Microsoft Bing), `mapillary`, `mapillary-signs`, `mapillary-map-features`, `openstreetcam` -* __`presets`__ - A path to an external presets file or a comma-separated list of preset IDs. These will be the only presets the user may select.
- _Example:_ `presets=https://path/to/presets.json` - _Example 2:_ `presets=building,highway/residential,highway/unclassified` +* __`presets`__ - A comma-separated list of preset IDs. These will be the only presets the user may select.
+ _Example:_ `presets=building,highway/residential,highway/unclassified` * __`rtl=true`__ - Force iD into right-to-left mode (useful for testing). * __`source`__ - Prefills the changeset source. Pass a url encoded string.
_Example:_ `source=Bing%3BMapillary` diff --git a/modules/presets/category.js b/modules/presets/category.js index 622b72d66..25296801e 100644 --- a/modules/presets/category.js +++ b/modules/presets/category.js @@ -2,12 +2,18 @@ import { t } from '../util/locale'; import { presetCollection } from './collection'; +// +// `presetCategory` builds a `presetCollection` of member presets, +// decorated with some extra methods for searching and matching geometry +// export function presetCategory(categoryID, category, all) { let _this = Object.assign({}, category); // shallow copy _this.id = categoryID; - _this.members = presetCollection(_this.members.map(presetID => all.item(presetID))); + _this.members = presetCollection( + category.members.map(presetID => all.item(presetID)).filter(Boolean) + ); _this.geometry = _this.members.collection .reduce((acc, preset) => { diff --git a/modules/presets/field.js b/modules/presets/field.js index 60f527bfc..cb6e3b2bc 100644 --- a/modules/presets/field.js +++ b/modules/presets/field.js @@ -2,6 +2,10 @@ import { t } from '../util/locale'; import { utilSafeClassName } from '../util/util'; +// +// `presetField` decorates a given `field` Object +// with some extra methods for searching and matching geometry +// export function presetField(fieldID, field) { let _this = Object.assign({}, field); // shallow copy diff --git a/modules/presets/index.js b/modules/presets/index.js index f8a0ed6b8..835808ef3 100644 --- a/modules/presets/index.js +++ b/modules/presets/index.js @@ -1,5 +1,4 @@ import { dispatch as d3_dispatch } from 'd3-dispatch'; -// import { json as d3_json } from 'd3-fetch'; import { osmNodeGeometriesForTags } from '../osm/tags'; import { presetCategory } from './category'; @@ -29,9 +28,10 @@ export function presetIndex(context) { const LINE = presetPreset('line', { name: 'Line', tags: {}, geometry: ['line'], matchScore: 0.1 } ); const AREA = presetPreset('area', { name: 'Area', tags: { area: 'yes' }, geometry: ['area'], matchScore: 0.1 } ); const RELATION = presetPreset('relation', { name: 'Relation', tags: {}, geometry: ['relation'], matchScore: 0.1 } ); - const FALLBACKS = [POINT, VERTEX, LINE, AREA, RELATION]; - let _this = presetCollection(FALLBACKS); + let _this = presetCollection([POINT, VERTEX, LINE, AREA, RELATION]); + let _presets = { point: POINT, vertex: VERTEX, line: LINE, area: AREA, relation: RELATION }; + let _defaults = { point: presetCollection([POINT]), vertex: presetCollection([VERTEX]), @@ -41,9 +41,10 @@ export function presetIndex(context) { }; let _fields = {}; + let _categories = {}; let _universal = []; let _recents; - // let _addablePresetIDs; // presets that the user can add + let _addablePresetIDs; // presets that the user can add // Index of presets by (geometry, tag key). let _geometryIndex = { point: {}, vertex: {}, line: {}, area: {}, relation: {} }; @@ -70,120 +71,87 @@ export function presetIndex(context) { } - // _this.init = (addablePresetIDs) => { _this.init = () => { - // _addablePresetIDs = addablePresetIDs; - - // let addable = true; - // if (addablePresetIDs) { - // addable = (presetID) => addablePresetIDs.indexOf(presetID) !== -1; - // } - // return _this.build(d, addable); - return ensurePresetData() - .then(d => _this.build(d)); + .then(_this.merge); }; - // _this.reset = () => { - // _defaults = { - // point: presetCollection([]), - // vertex: presetCollection([]), - // line: presetCollection([]), - // area: presetCollection([]), - // relation: presetCollection([]) - // }; - - // _this.collection = []; - // _recents = null; - // _fields = {}; - // _universal = []; - // _geometryIndex = { point: {}, vertex: {}, line: {}, area: {}, relation: {} }; - - // return _this; - // }; - - - // _this.fromExternal = (external, done) => { - // _this.reset(); - // d3_json(external) - // .then(externalPresets => { - // _this.build(data.presets, false); // load the default presets as non-addable to start - // _addablePresetIDs = externalPresets.presets && Object.keys(externalPresets.presets); - // _this.build(externalPresets, true); // then load the external presets as addable - // }) - // .catch(() => _this.init()) - // .finally(() => done(_this)); - // }; - - // _this.build = (d, addable) => { - _this.build = (d) => { - if (d.fields && Object.keys(d.fields).length) { + _this.merge = (d) => { + // Merge Fields + if (d.fields) { Object.keys(d.fields).forEach(fieldID => { const f = d.fields[fieldID]; - _fields[fieldID] = presetField(fieldID, f); - if (f.universal) { - _universal.push(_fields[fieldID]); + if (f) { // add or replace + _fields[fieldID] = presetField(fieldID, f); + } else { // remove + delete _fields[fieldID]; } }); } - if (d.presets && Object.keys(d.presets).length) { - const rawPresets = d.presets; + // Merge Presets + if (d.presets) { Object.keys(d.presets).forEach(presetID => { const p = d.presets[presetID]; - const existing = _this.index(presetID); - // const isAddable = typeof addable === 'function' ? addable(presetID, p) : addable; - const isAddable = true; - if (existing !== -1) { - _this.collection[existing] = presetPreset(presetID, p, _fields, isAddable, rawPresets); - } else { - _this.collection.push(presetPreset(presetID, p, _fields, isAddable, rawPresets)); + if (p) { // add or replace + // _presets[presetID] = presetPreset(presetID, p, _fields, isAddable, _presets); + _presets[presetID] = presetPreset(presetID, p, _fields, true); + } else { // remove (but not if it's a fallback) + const existing = _presets[presetID]; + if (existing && !existing.isFallback()) { + delete _presets[presetID]; + } } }); } - if (d.categories && Object.keys(d.categories).length) { + // Merge Categories + if (d.categories) { Object.keys(d.categories).forEach(categoryID => { const c = d.categories[categoryID]; - const existing = _this.index(categoryID); - if (existing !== -1) { - _this.collection[existing] = presetCategory(categoryID, c, _this); - } else { - _this.collection.push(presetCategory(categoryID, c, _this)); + if (c) { // add or replace + _categories[categoryID] = presetCategory(categoryID, c, _this); + } else { // remove + delete _categories[categoryID]; } }); } - const getItem = (_this.item).bind(_this); - // if (_addablePresetIDs) { - // ['area', 'line', 'point', 'vertex', 'relation'].forEach(geometry => { - // _defaults[geometry] = presetCollection( - // _addablePresetIDs.map(getItem).filter(preset => preset.geometry.indexOf(geometry) !== -1) - // ); - // }); - // } else if (d.defaults) { - if (d.defaults && Object.keys(d.defaults).length) { - _defaults = { - point: presetCollection(d.defaults.point.map(getItem)), - vertex: presetCollection(d.defaults.vertex.map(getItem)), - line: presetCollection(d.defaults.line.map(getItem)), - area: presetCollection(d.defaults.area.map(getItem)), - relation: presetCollection(d.defaults.relation.map(getItem)) - }; - } - - for (let i = 0; i < _this.collection.length; i++) { - const preset = _this.collection[i]; - const geometry = preset.geometry; - - for (let j = 0; j < geometry.length; j++) { - let g = _geometryIndex[geometry[j]]; - for (let k in preset.tags) { - (g[k] = g[k] || []).push(preset); + // Merge Defaults + if (d.defaults) { + Object.keys(d.defaults).forEach(geometry => { + const def = d.defaults[geometry]; + if (Array.isArray(def)) { // add or replace + _defaults[geometry] = presetCollection( + def.map(presetID => _presets[presetID]).filter(Boolean) + ); + } else { // remove + delete _defaults[geometry]; } - } + }); } + + // Rebuild universal fields + _universal = Object.values(_fields).reduce((acc, field) => { + if (field.universal) acc.push(field); + return acc; + }, []); + + // Rebuild _this.collection + _this.collection = Object.values(_presets).concat(Object.values(_categories)); + + // Rebuild geometry index + _geometryIndex = { point: {}, vertex: {}, line: {}, area: {}, relation: {} }; + _this.collection.forEach(preset => { + (preset.geometry || []).forEach(geometry => { + let g = _geometryIndex[geometry]; + for (let key in preset.tags) { + (g[key] = g[key] || []).push(preset); + } + }); + }); + return _this; }; @@ -482,5 +450,6 @@ export function presetIndex(context) { setRecents(items); }; + return utilRebind(_this, dispatch, 'on'); } diff --git a/modules/presets/preset.js b/modules/presets/preset.js index cb8e28800..c8d0a8b8e 100644 --- a/modules/presets/preset.js +++ b/modules/presets/preset.js @@ -4,6 +4,10 @@ import { utilArrayUniq, utilObjectOmit } from '../util'; import { utilSafeClassName } from '../util/util'; +// +// `presetPreset` decorates a given `preset` Object +// with some extra methods for searching and matching geometry +// export function presetPreset(presetID, preset, fields, addable, rawPresets) { let _this = Object.assign({}, preset); // shallow copy diff --git a/test/spec/presets/category.js b/test/spec/presets/category.js index 46ea133cf..aea5ca898 100644 --- a/test/spec/presets/category.js +++ b/test/spec/presets/category.js @@ -1,31 +1,25 @@ describe('iD.presetCategory', function() { - var category, residential; + var category = { + 'geometry': 'line', + 'icon': 'highway', + 'name': 'roads', + 'members': [ 'highway/residential' ] + }; + + var residential = iD.presetPreset('highway/residential', + { tags: { highway: 'residential' }, geometry: ['line'] } + ); + var all = iD.presetCollection([residential]); - beforeEach(function() { - category = { - 'geometry': 'line', - 'icon': 'highway', - 'name': 'roads', - 'members': [ - 'highway/residential' - ] - }; - residential = iD.presetPreset('highway/residential', { - tags: { - highway: 'residential' - }, - geometry: ['line'] - }); - }); it('maps members names to preset instances', function() { - var c = iD.presetCategory('road', category, iD.presetCollection([residential])); + var c = iD.presetCategory('road', category, all); expect(c.members.collection[0]).to.eql(residential); }); describe('#matchGeometry', function() { it('matches the type of an entity', function() { - var c = iD.presetCategory('road', category, iD.presetCollection([residential])); + var c = iD.presetCategory('road', category, all); expect(c.matchGeometry('line')).to.eql(true); expect(c.matchGeometry('point')).to.eql(false); }); diff --git a/test/spec/presets/collection.js b/test/spec/presets/collection.js index 02ca8bd7d..249fde827 100644 --- a/test/spec/presets/collection.js +++ b/test/spec/presets/collection.js @@ -15,56 +15,56 @@ describe('iD.presetCollection', function() { tags: {}, geometry: ['area'] }), - grill: iD.presetPreset('__test/amenity/bbq', { + grill: iD.presetPreset('amenity/bbq', { name: 'Grill', tags: { amenity: 'bbq' }, geometry: ['point'], terms: [] }), - sandpit: iD.presetPreset('__test/amenity/grit_bin', { + sandpit: iD.presetPreset('amenity/grit_bin', { name: 'Sandpit', tags: { amenity: 'grit_bin' }, geometry: ['point'], terms: [] }), - residential: iD.presetPreset('__test/highway/residential', { + residential: iD.presetPreset('highway/residential', { name: 'Residential Area', tags: { highway: 'residential' }, geometry: ['point', 'area'], terms: [] }), - grass1: iD.presetPreset('__test/landuse/grass1', { + grass1: iD.presetPreset('landuse/grass1', { name: 'Grass', tags: { landuse: 'grass' }, geometry: ['point', 'area'], terms: [] }), - grass2: iD.presetPreset('__test/landuse/grass2', { + grass2: iD.presetPreset('landuse/grass2', { name: 'Ğṝȁß', tags: { landuse: 'ğṝȁß' }, geometry: ['point', 'area'], terms: [] }), - park: iD.presetPreset('__test/leisure/park', { + park: iD.presetPreset('leisure/park', { name: 'Park', tags: { leisure: 'park' }, geometry: ['point', 'area'], terms: [ 'grass' ], matchScore: 0.5 }), - parking: iD.presetPreset('__test/amenity/parking', { + parking: iD.presetPreset('amenity/parking', { name: 'Parking', tags: { amenity: 'parking' }, geometry: ['point', 'area'], terms: [ 'cars' ] }), - soccer: iD.presetPreset('__test/leisure/pitch/soccer', { + soccer: iD.presetPreset('leisure/pitch/soccer', { name: 'Soccer Field', tags: { leisure: 'pitch', sport: 'soccer' }, geometry: ['point', 'area'], terms: ['fußball'] }), - football: iD.presetPreset('__test/leisure/pitch/american_football', { + football: iD.presetPreset('leisure/pitch/american_football', { name: 'Football Field', tags: { leisure: 'pitch', sport: 'american_football' }, geometry: ['point', 'area'], @@ -80,7 +80,7 @@ describe('iD.presetCollection', function() { describe('#item', function() { it('fetches a preset by id', function() { - expect(c.item('__test/highway/residential')).to.equal(p.residential); + expect(c.item('highway/residential')).to.equal(p.residential); }); }); @@ -148,7 +148,7 @@ describe('iD.presetCollection', function() { }); it('excludes presets with searchable: false', function() { - var excluded = iD.presetPreset('__test/excluded', { + var excluded = iD.presetPreset('excluded', { name: 'excluded', tags: { amenity: 'excluded' }, geometry: ['point'],