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'],