From 9b7d79a3fec6f0eb5e292c3b89a2912fd8751197 Mon Sep 17 00:00:00 2001 From: Bryan Housel Date: Mon, 14 Nov 2016 15:44:02 -0500 Subject: [PATCH] Use live bound object for presets object, change context.presets() getter --- API.md | 59 +++++++- CHANGELOG.md | 3 + data/index.js | 17 ++- data/presets/README.md | 6 + dist/index.html | 4 +- index.html | 1 - modules/core/context.js | 21 ++- modules/modes/select.js | 16 ++- modules/presets/index.js | 182 +++++++++++++++++++++++- modules/presets/init.js | 168 ---------------------- modules/ui/commit.js | 13 +- modules/ui/feature_list.js | 10 +- modules/ui/preset.js | 15 +- modules/ui/raw_member_editor.js | 7 +- modules/ui/raw_membership_editor.js | 6 +- test/index.html | 2 +- test/rendering.html | 2 +- test/spec/actions/split.js | 3 +- test/spec/core/context.js | 48 ------- test/spec/modes/add_point.js | 1 - test/spec/osm/way.js | 2 +- test/spec/presets/{init.js => index.js} | 129 +++++++++++------ test/spec/ui/fields/access.js | 3 +- test/spec/ui/fields/wikipedia.js | 6 +- 24 files changed, 393 insertions(+), 331 deletions(-) delete mode 100644 modules/presets/init.js rename test/spec/presets/{init.js => index.js} (55%) diff --git a/API.md b/API.md index 8038fa3dfc..0ed7f2705e 100644 --- a/API.md +++ b/API.md @@ -145,7 +145,7 @@ certain parts of the iD code to be replaced at runtime by custom code or data. iD is written in a modular style and bundled with [rollup.js](http://rollupjs.org/), which makes hot code replacement tricky. (ES6 module exports are -[immutable bindings](http://www.2ality.com/2015/07/es6-module-exports.html)). +[immutable live bindings](http://www.2ality.com/2015/07/es6-module-exports.html)). Because of this, the parts of iD which are designed for customization are exported as live bound objects that can be overriden at runtime _before initializing the iD context_. @@ -169,7 +169,9 @@ delete iD.services.mapillary; ### Background Imagery iD's background imagery database is stored in the `iD.data.imagery` array and can be -overridden. (Note that the "None" and "Custom" options will always be shown in the list) +overridden or modified prior to creating the iD context. + +Note that the "None" and "Custom" options will always be shown in the list. To remove all imagery from iD: ```js @@ -213,16 +215,59 @@ For more details about the `iD.data.imagery` structure, see ### Presets -iD can use external presets exclusively or along with the default OpenStreetMap presets. This is configured using the `context.presets` accessor. To use external presets alone, initialize the iD context with a custom `Presets` object: +iD's preset database is stored in the `iD.data.presets` object and can be overridden +or modified prior to creating the iD context. -```js +The format of the `presets` object is +[documented here](https://github.com/openstreetmap/iD/tree/master/data/presets#custom-presets). -var id = iD.Context() - .presets(customPresets); +To add a new preset to iD's existing preset database. +```js +iD.data.presets.presets["aerialway/zipline"] = { + geometry: ["line"], + fields: ["incline"], + tags: { "aerialway": "zip_line" }, + name: "Zipline" +}; +``` +To completely replace iD's default presets with your own: +```js +iD.data.presets = myPresets; ``` -The format of the Preset object is [documented here](https://github.com/openstreetmap/iD/tree/master/data/presets#custom-presets). +To run iD with the minimal set of presets that only match basic geometry types: +```js +iD.data.presets = { + presets: { + "area": { + "name": "Area", + "tags": {}, + "geometry": ["area"] + }, + "line": { + "name": "Line", + "tags": {}, + "geometry": ["line"] + }, + "point": { + "name": "Point", + "tags": {}, + "geometry": ["point"] + }, + "vertex": { + "name": "Vertex", + "tags": {}, + "geometry": ["vertex"] + }, + "relation": { + "name": "Relation", + "tags": {}, + "geometry": ["relation"] + } + } +}; +``` ### Minimum Editable Zoom diff --git a/CHANGELOG.md b/CHANGELOG.md index 233eec3363..a7422101f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ * :warning: Flattened namespace means that all functions have changed names (#3479) * e.g. `iD.actions.Move` -> `iD.actionMove`, `iD.geo.Extent` -> `iD.geoExtent` * Many deprecated names are still exported as symbols, e.g. `iD.Context` - we will remove these eventually +* :warning: Customized iD deployments can manipulate live objects, rather than iD.Context accessors + * No longer need to call things like `presets()`, `imagery()`, `taginfo()` when creating `iD.Context` + * See [API.md](https://github.com/openstreetmap/iD/blob/master/API.md#customized-deployments) for details on customized deployments * :warning: iD has upgraded to the latest released versions of d3, lodash, rbush, etc. * d3 no longer adds itself to the global namespace, but can now be accessed via `iD.d3` * :warning: iD now uses `npm` scripts for all build processes diff --git a/data/index.js b/data/index.js index 970d5544d5..3387e7524d 100644 --- a/data/index.js +++ b/data/index.js @@ -12,19 +12,18 @@ export { default as dataImperial } from './imperial.json'; export { default as dataDriveLeft } from './drive-left.json'; export { en as dataEn } from '../dist/locales/en.json'; +import { dataImagery } from './imagery.json'; import { presets } from './presets/presets.json'; import { defaults } from './presets/defaults.json'; import { categories } from './presets/categories.json'; import { fields } from './presets/fields.json'; -export var dataPresets = { - presets: presets, - defaults: defaults, - categories: categories, - fields: fields -}; - -import { dataImagery } from './imagery.json'; export var data = { - imagery: dataImagery + imagery: dataImagery, + presets: { + presets: presets, + defaults: defaults, + categories: categories, + fields: fields + } }; diff --git a/data/presets/README.md b/data/presets/README.md index ac5d0986ff..42114967cc 100644 --- a/data/presets/README.md +++ b/data/presets/README.md @@ -274,6 +274,12 @@ For example: "tags": {}, "geometry": ["vertex"], "matchScore": 0.1 +}, +"relation": { + "name": "Relation", + "tags": {}, + "geometry": ["relation"], + "matchScore": 0.1 } ``` diff --git a/dist/index.html b/dist/index.html index 99f55093ed..7ea898aafa 100644 --- a/dist/index.html +++ b/dist/index.html @@ -37,9 +37,7 @@ document.getElementById('id-container').innerHTML = 'Sorry, your browser is not currently supported. Please use Potlatch 2 to edit the map.'; document.getElementById('id-container').className = 'unsupported'; } else { - var id = iD.Context() - .presets(iD.dataPresets); - + var id = iD.Context(); id.ui()(document.getElementById('id-container')); } diff --git a/index.html b/index.html index be8418bbda..7168ff60b4 100644 --- a/index.html +++ b/index.html @@ -19,7 +19,6 @@ - + diff --git a/test/rendering.html b/test/rendering.html index 7573a3a0d5..ae3c5d69a9 100644 --- a/test/rendering.html +++ b/test/rendering.html @@ -46,7 +46,7 @@ }; context.presets = function() { - return iD.presetInit().load({ + return iD.presetIndex().load({ presets: { 'amenity/restaurant': { geometry: ['point'], diff --git a/test/spec/actions/split.js b/test/spec/actions/split.js index 94c304a515..009c71195f 100644 --- a/test/spec/actions/split.js +++ b/test/spec/actions/split.js @@ -1,8 +1,7 @@ describe('iD.actionSplit', function () { beforeEach(function () { - iD.areaKeys = iD.Context() - .presets(iD.dataPresets).presets().areaKeys(); + iD.areaKeys = iD.Context().presets().areaKeys(); }); describe('#disabled', function () { diff --git a/test/spec/core/context.js b/test/spec/core/context.js index 036b79d27e..04f665400b 100644 --- a/test/spec/core/context.js +++ b/test/spec/core/context.js @@ -51,54 +51,6 @@ describe('iD.Context', function() { }); }); - describe('#presets', function() { - it('supports custom presets', function() { - var presetsCollection = { - presets: { - 'mines': { - geometry: ['point', 'area'], - name: 'Mining Concession', - tags: { 'concession': 'mining' } - }, - 'area': { - 'name': 'Area', - 'tags': {}, - 'geometry': ['area'] - }, - 'point': { - 'name': 'Point', - 'tags': {}, - 'geometry': ['point'] - }, - 'line': { - 'name': 'Line', - 'tags': {}, - 'geometry': ['line'] - }, - 'vertex': { - 'name': 'Other', - 'tags': {}, - 'geometry': ['vertex'] - } - }, - fields: { - 'name': { - 'key': 'name', - 'type': 'localized', - 'label': 'Name', - 'placeholder': 'Common name (if any)' - } - } - }; - - var context = iD.Context().presets(presetsCollection), - way = iD.Way({tags: {concession: 'mining', area: 'yes'}}), - graph = iD.Graph([way]); - - expect(context.presets().match(way, graph).id).to.eql('mines'); - }); - }); - describe('#debug', function() { it('sets and gets debug flags', function() { var context = iD.Context(), diff --git a/test/spec/modes/add_point.js b/test/spec/modes/add_point.js index b8fff59f80..66916c577d 100644 --- a/test/spec/modes/add_point.js +++ b/test/spec/modes/add_point.js @@ -5,7 +5,6 @@ describe.skip('iD.modeAddPoint', function() { var container = d3.select(document.createElement('div')); context = iD.Context() - .presets(iD.dataPresets) .container(container); context.loadTiles = function () {}; diff --git a/test/spec/osm/way.js b/test/spec/osm/way.js index a5879cdf28..a49ea40821 100644 --- a/test/spec/osm/way.js +++ b/test/spec/osm/way.js @@ -318,7 +318,7 @@ describe('iD.osmWay', function() { describe('#isArea', function() { before(function() { - iD.Context().presets(iD.dataPresets); + iD.Context(); }); it('returns false when the way has no tags', function() { diff --git a/test/spec/presets/init.js b/test/spec/presets/index.js similarity index 55% rename from test/spec/presets/init.js rename to test/spec/presets/index.js index e14a18f615..163dd9a2d4 100644 --- a/test/spec/presets/init.js +++ b/test/spec/presets/index.js @@ -1,65 +1,84 @@ -describe('iD.presetInit', function() { - var p = { - point: { - tags: {}, - geometry: ['point'] - }, - line: { - tags: {}, - geometry: ['line'] - }, - vertex: { - tags: {}, - geometry: ['vertex'] - }, - residential: { - tags: { highway: 'residential' }, - geometry: ['line'] - }, - park: { - tags: { leisure: 'park' }, - geometry: ['point', 'area'] - } - }; - - var c = iD.presetInit().load({presets: p}); +describe('iD.presetIndex', function() { + var savedPresets; + + before(function () { + savedPresets = iD.data.presets; + }); + + after(function () { + iD.data.presets = savedPresets; + }); describe('#match', function() { + var testPresets = { + presets: { + point: { + tags: {}, + geometry: ['point'] + }, + line: { + tags: {}, + geometry: ['line'] + }, + vertex: { + tags: {}, + geometry: ['vertex'] + }, + residential: { + tags: { highway: 'residential' }, + geometry: ['line'] + }, + park: { + tags: { leisure: 'park' }, + geometry: ['point', 'area'] + } + } + }; + it('returns a collection containing presets matching a geometry and tags', function() { - var way = iD.Way({ tags: { highway: 'residential' } }), + iD.data.presets = testPresets; + var presets = iD.Context().presets(), + way = iD.Way({ tags: { highway: 'residential' } }), graph = iD.Graph([way]); - expect(c.match(way, graph).id).to.eql('residential'); + + expect(presets.match(way, graph).id).to.eql('residential'); }); it('returns the appropriate fallback preset when no tags match', function() { - var point = iD.Node(), + iD.data.presets = testPresets; + var presets = iD.Context().presets(), + point = iD.Node(), line = iD.Way({ tags: { foo: 'bar' } }), graph = iD.Graph([point, line]); - expect(c.match(point, graph).id).to.eql('point'); - expect(c.match(line, graph).id).to.eql('line'); + expect(presets.match(point, graph).id).to.eql('point'); + expect(presets.match(line, graph).id).to.eql('line'); }); it('matches vertices on a line as vertices', function() { - var point = iD.Node({ tags: { leisure: 'park' } }), + iD.data.presets = testPresets; + var presets = iD.Context().presets(), + point = iD.Node({ tags: { leisure: 'park' } }), line = iD.Way({ nodes: [point.id], tags: { 'highway': 'residential' } }), graph = iD.Graph([point, line]); - expect(c.match(point, graph).id).to.eql('vertex'); + expect(presets.match(point, graph).id).to.eql('vertex'); }); it('matches vertices on an addr:interpolation line as points', function() { - var point = iD.Node({ tags: { leisure: 'park' } }), + iD.data.presets = testPresets; + var presets = iD.Context().presets(), + point = iD.Node({ tags: { leisure: 'park' } }), line = iD.Way({ nodes: [point.id], tags: { 'addr:interpolation': 'even' } }), graph = iD.Graph([point, line]); - expect(c.match(point, graph).id).to.eql('park'); + expect(presets.match(point, graph).id).to.eql('park'); }); - }); + describe('#areaKeys', function() { - var presets = iD.presetInit().load({ + var testPresets = { presets: { 'amenity/fuel/shell': { tags: { 'amenity': 'fuel' }, @@ -91,62 +110,78 @@ describe('iD.presetInit', function() { geometry: ['point', 'area'] } } - }); + }; it('whitelists keys for presets with area geometry', function() { + iD.data.presets = testPresets; + var presets = iD.Context().presets(); expect(presets.areaKeys()).to.include.keys('natural'); }); it('blacklists key-values for presets with a line geometry', function() { + iD.data.presets = testPresets; + var presets = iD.Context().presets(); expect(presets.areaKeys().natural).to.include.keys('tree_row'); expect(presets.areaKeys().natural.tree_row).to.be.true; }); it('does not blacklist key-values for presets with both area and line geometry', function() { + iD.data.presets = testPresets; + var presets = iD.Context().presets(); expect(presets.areaKeys().golf).not.to.include.keys('water_hazard'); }); it('does not blacklist key-values for presets with neither area nor line geometry', function() { + iD.data.presets = testPresets; + var presets = iD.Context().presets(); expect(presets.areaKeys().natural).not.to.include.keys('peak'); }); it('does not blacklist generic \'*\' key-values', function() { + iD.data.presets = testPresets; + var presets = iD.Context().presets(); expect(presets.areaKeys().natural).not.to.include.keys('natural'); }); it('ignores keys like \'highway\' that are assumed to be lines', function() { + iD.data.presets = testPresets; + var presets = iD.Context().presets(); expect(presets.areaKeys()).not.to.include.keys('highway'); }); it('ignores suggestion presets', function() { + iD.data.presets = testPresets; + var presets = iD.Context().presets(); expect(presets.areaKeys()).not.to.include.keys('amenity'); }); - }); - describe('expected matches', function() { - var presets; - before(function() { - presets = iD.presetInit().load(iD.dataPresets); - }); + describe('expected matches', function() { it('prefers building to multipolygon', function() { - var relation = iD.Relation({tags: {type: 'multipolygon', building: 'yes'}}), - graph = iD.Graph([relation]); + iD.data.presets = savedPresets; + var presets = iD.Context().presets(), + relation = iD.Relation({ tags: { type: 'multipolygon', building: 'yes' }}), + graph = iD.Graph([relation]); expect(presets.match(relation, graph).id).to.eql('building'); }); it('prefers building to address', function() { - var way = iD.Way({tags: {area: 'yes', building: 'yes', 'addr:housenumber': '1234'}}), + iD.data.presets = savedPresets; + var presets = iD.Context().presets(), + way = iD.Way({ tags: { area: 'yes', building: 'yes', 'addr:housenumber': '1234' }}), graph = iD.Graph([way]); expect(presets.match(way, graph).id).to.eql('building'); }); it('prefers pedestrian to area', function() { - var way = iD.Way({tags: {area: 'yes', highway: 'pedestrian'}}), + iD.data.presets = savedPresets; + var presets = iD.Context().presets(), + way = iD.Way({ tags: { area: 'yes', highway: 'pedestrian' }}), graph = iD.Graph([way]); expect(presets.match(way, graph).id).to.eql('highway/pedestrian'); }); }); + }); diff --git a/test/spec/ui/fields/access.js b/test/spec/ui/fields/access.js index 772a543ee7..4e84c5dd69 100644 --- a/test/spec/ui/fields/access.js +++ b/test/spec/ui/fields/access.js @@ -2,8 +2,7 @@ describe('iD.uiFieldAccess', function() { var selection, field; beforeEach(function() { selection = d3.select(document.createElement('div')); - field = iD.Context() - .presets(iD.dataPresets).presets().field('access'); + field = iD.Context().presets().field('access'); }); it('creates inputs for a variety of modes of access', function() { diff --git a/test/spec/ui/fields/wikipedia.js b/test/spec/ui/fields/wikipedia.js index 237a216716..6ffc13a3a8 100644 --- a/test/spec/ui/fields/wikipedia.js +++ b/test/spec/ui/fields/wikipedia.js @@ -14,7 +14,7 @@ describe('iD.uiFieldWikipedia', function() { context = iD.Context(); context.history().merge([entity]); selection = d3.select(document.createElement('div')); - field = context.presets(iD.dataPresets).presets().field('wikipedia'); + field = context.presets().field('wikipedia'); window.JSONP_DELAY = 0; window.JSONP_FIX = { entities: { @@ -114,7 +114,7 @@ describe('iD.uiFieldWikipedia', function() { // skip delayed wikidata for 'Skip' // 'Skip' wikidata +20ms expect(spy.getCall(4)).to.have.been.calledWith({ wikipedia: 'de:Title', wikidata: 'Q216353' }); // 'Title' wikidata +40ms done(); - }, 50); + }, 100); }); it('does not set delayed wikidata tag if selected entity has changed', function(done) { @@ -140,7 +140,7 @@ describe('iD.uiFieldWikipedia', function() { expect(spy.getCall(1)).to.have.been.calledWith({ wikipedia: 'de:Title' }); // 'Title' on blur // wikidata tag not changed because another entity is now selected done(); - }, 50); + }, 100); }); });