Merge pull request #3168 from beaugunderson/3118-presets

migrate presets to ES6 modules for #3118
This commit is contained in:
Tom MacWright
2016-06-14 23:59:30 -04:00
committed by GitHub
13 changed files with 509 additions and 38 deletions

View File

@@ -44,11 +44,15 @@ $(BUILDJS_TARGETS): $(BUILDJS_SOURCES) build.js
MODULE_TARGETS = \
js/lib/id/actions.js \
js/lib/id/presets.js \
js/lib/id/validations.js
js/lib/id/actions.js: modules/
node_modules/.bin/rollup -f umd -n iD.actions modules/actions/index.js --no-strict > $@
js/lib/id/presets.js: modules/
node_modules/.bin/rollup -f umd -n iD.presets modules/presets/index.js --no-strict > $@
js/lib/id/validations.js: modules/
node_modules/.bin/rollup -f umd -n iD.validations modules/validations/index.js --no-strict > $@
@@ -225,11 +229,6 @@ dist/iD.js: \
js/id/ui/intro/navigation.js \
js/id/ui/intro/point.js \
js/id/ui/intro/start_editing.js \
js/id/presets.js \
js/id/presets/category.js \
js/id/presets/collection.js \
js/id/presets/field.js \
js/id/presets/preset.js \
js/id/end.js \
js/lib/locale.js \
data/introGraph.js

View File

@@ -35,6 +35,7 @@
<script src='js/id/id.js'></script>
<script src='js/lib/id/actions.js'></script>
<script src='js/lib/id/presets.js'></script>
<script src='js/id/util.js'></script>
<script src='js/id/util/session_mutex.js'></script>
@@ -200,12 +201,6 @@
<script src='js/id/core/tree.js'></script>
<script src='js/id/core/tags.js'></script>
<script src='js/id/presets.js'></script>
<script src='js/id/presets/preset.js'></script>
<script src='js/id/presets/category.js'></script>
<script src='js/id/presets/collection.js'></script>
<script src='js/id/presets/field.js'></script>
<script src='js/id/validations.js'></script>
<script src='js/id/validations/deprecated_tag.js'></script>
<script src='js/id/validations/many_deletions.js'></script>

View File

@@ -379,7 +379,7 @@ window.iD = function () {
context.zoomOutFurther = map.zoomOutFurther;
context.redrawEnable = map.redrawEnable;
presets = iD.presets();
presets = iD.presets.presets();
return d3.rebind(context, dispatch, 'on');
};

481
js/lib/id/presets.js Normal file
View File

@@ -0,0 +1,481 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(factory((global.iD = global.iD || {}, global.iD.presets = global.iD.presets || {})));
}(this, function (exports) { 'use strict';
function Category(id, category, all) {
category = _.clone(category);
category.id = id;
category.members = iD.presets.Collection(category.members.map(function(id) {
return all.item(id);
}));
category.matchGeometry = function(geometry) {
return category.geometry.indexOf(geometry) >= 0;
};
category.matchScore = function() { return -1; };
category.name = function() {
return t('presets.categories.' + id + '.name', {'default': id});
};
category.terms = function() {
return [];
};
return category;
}
function Collection(collection) {
var maxSearchResults = 50,
maxSuggestionResults = 10;
var presets = {
collection: collection,
item: function(id) {
return _.find(collection, function(d) {
return d.id === id;
});
},
matchGeometry: function(geometry) {
return iD.presets.Collection(collection.filter(function(d) {
return d.matchGeometry(geometry);
}));
},
search: function(value, geometry) {
if (!value) return this;
value = value.toLowerCase();
var searchable = _.filter(collection, function(a) {
return a.searchable !== false && a.suggestion !== true;
}),
suggestions = _.filter(collection, function(a) {
return a.suggestion === true;
});
function leading(a) {
var index = a.indexOf(value);
return index === 0 || a[index - 1] === ' ';
}
// matches value to preset.name
var leading_name = _.filter(searchable, function(a) {
return leading(a.name().toLowerCase());
}).sort(function(a, b) {
var i = a.name().toLowerCase().indexOf(value) - b.name().toLowerCase().indexOf(value);
if (i === 0) return a.name().length - b.name().length;
else return i;
});
// matches value to preset.terms values
var leading_terms = _.filter(searchable, function(a) {
return _.some(a.terms() || [], leading);
});
// matches value to preset.tags values
var leading_tag_values = _.filter(searchable, function(a) {
return _.some(_.without(_.values(a.tags || {}), '*'), leading);
});
// finds close matches to value in preset.name
var levenstein_name = searchable.map(function(a) {
return {
preset: a,
dist: iD.util.editDistance(value, a.name().toLowerCase())
};
}).filter(function(a) {
return a.dist + Math.min(value.length - a.preset.name().length, 0) < 3;
}).sort(function(a, b) {
return a.dist - b.dist;
}).map(function(a) {
return a.preset;
});
// finds close matches to value in preset.terms
var leventstein_terms = _.filter(searchable, function(a) {
return _.some(a.terms() || [], function(b) {
return iD.util.editDistance(value, b) + Math.min(value.length - b.length, 0) < 3;
});
});
function suggestionName(name) {
var nameArray = name.split(' - ');
if (nameArray.length > 1) {
name = nameArray.slice(0, nameArray.length-1).join(' - ');
}
return name.toLowerCase();
}
var leading_suggestions = _.filter(suggestions, function(a) {
return leading(suggestionName(a.name()));
}).sort(function(a, b) {
a = suggestionName(a.name());
b = suggestionName(b.name());
var i = a.indexOf(value) - b.indexOf(value);
if (i === 0) return a.length - b.length;
else return i;
});
var leven_suggestions = suggestions.map(function(a) {
return {
preset: a,
dist: iD.util.editDistance(value, suggestionName(a.name()))
};
}).filter(function(a) {
return a.dist + Math.min(value.length - suggestionName(a.preset.name()).length, 0) < 1;
}).sort(function(a, b) {
return a.dist - b.dist;
}).map(function(a) {
return a.preset;
});
var other = presets.item(geometry);
var results = leading_name.concat(
leading_terms,
leading_tag_values,
leading_suggestions.slice(0, maxSuggestionResults+5),
levenstein_name,
leventstein_terms,
leven_suggestions.slice(0, maxSuggestionResults)
).slice(0, maxSearchResults-1);
return iD.presets.Collection(_.uniq(
results.concat(other)
));
}
};
return presets;
}
function Field(id, field) {
field = _.clone(field);
field.id = id;
field.matchGeometry = function(geometry) {
return !field.geometry || field.geometry === geometry;
};
field.t = function(scope, options) {
return t('presets.fields.' + id + '.' + scope, options);
};
field.label = function() {
return field.t('label', {'default': id});
};
var placeholder = field.placeholder;
field.placeholder = function() {
return field.t('placeholder', {'default': placeholder});
};
return field;
}
function Preset(id, preset, fields) {
preset = _.clone(preset);
preset.id = id;
preset.fields = (preset.fields || []).map(getFields);
preset.geometry = (preset.geometry || []);
function getFields(f) {
return fields[f];
}
preset.matchGeometry = function(geometry) {
return preset.geometry.indexOf(geometry) >= 0;
};
var matchScore = preset.matchScore || 1;
preset.matchScore = function(entity) {
var tags = preset.tags,
score = 0;
for (var t in tags) {
if (entity.tags[t] === tags[t]) {
score += matchScore;
} else if (tags[t] === '*' && t in entity.tags) {
score += matchScore / 2;
} else {
return -1;
}
}
return score;
};
preset.t = function(scope, options) {
return t('presets.presets.' + id + '.' + scope, options);
};
var name = preset.name;
preset.name = function() {
if (preset.suggestion) {
id = id.split('/');
id = id[0] + '/' + id[1];
return name + ' - ' + t('presets.presets.' + id + '.name');
}
return preset.t('name', {'default': name});
};
preset.terms = function() {
return preset.t('terms', {'default': ''}).toLowerCase().trim().split(/\s*,+\s*/);
};
preset.isFallback = function() {
var tagCount = Object.keys(preset.tags).length;
return tagCount === 0 || (tagCount === 1 && preset.tags.hasOwnProperty('area'));
};
preset.reference = function(geometry) {
var key = Object.keys(preset.tags)[0],
value = preset.tags[key];
if (geometry === 'relation' && key === 'type') {
return { rtype: value };
} else if (value === '*') {
return { key: key };
} else {
return { key: key, value: value };
}
};
var removeTags = preset.removeTags || preset.tags;
preset.removeTags = function(tags, geometry) {
tags = _.omit(tags, _.keys(removeTags));
for (var f in preset.fields) {
var field = preset.fields[f];
if (field.matchGeometry(geometry) && field.default === tags[field.key]) {
delete tags[field.key];
}
}
delete tags.area;
return tags;
};
var applyTags = preset.addTags || preset.tags;
preset.applyTags = function(tags, geometry) {
var k;
tags = _.clone(tags);
for (k in applyTags) {
if (applyTags[k] === '*') {
tags[k] = 'yes';
} else {
tags[k] = applyTags[k];
}
}
// Add area=yes if necessary.
// This is necessary if the geometry is already an area (e.g. user drew an area) AND any of:
// 1. chosen preset could be either an area or a line (`barrier=city_wall`)
// 2. chosen preset doesn't have a key in areaKeys (`railway=station`)
if (geometry === 'area') {
var needsAreaTag = true;
if (preset.geometry.indexOf('line') === -1) {
for (k in applyTags) {
if (k in iD.areaKeys) {
needsAreaTag = false;
break;
}
}
}
if (needsAreaTag) {
tags.area = 'yes';
}
}
for (var f in preset.fields) {
var field = preset.fields[f];
if (field.matchGeometry(geometry) && field.key && !tags[field.key] && field.default) {
tags[field.key] = field.default;
}
}
return tags;
};
return preset;
}
function presets() {
// an iD.presets.Collection with methods for
// loading new data and returning defaults
var all = iD.presets.Collection([]),
defaults = { area: all, line: all, point: all, vertex: all, relation: all },
fields = {},
universal = [],
recent = iD.presets.Collection([]);
// Index of presets by (geometry, tag key).
var index = {
point: {},
vertex: {},
line: {},
area: {},
relation: {}
};
all.match = function(entity, resolver) {
var geometry = entity.geometry(resolver),
geometryMatches = index[geometry],
best = -1,
match;
for (var k in entity.tags) {
var keyMatches = geometryMatches[k];
if (!keyMatches) continue;
for (var i = 0; i < keyMatches.length; i++) {
var score = keyMatches[i].matchScore(entity);
if (score > best) {
best = score;
match = keyMatches[i];
}
}
}
return match || all.item(geometry);
};
// Because of the open nature of tagging, iD will never have a complete
// list of tags used in OSM, so we want it to have logic like "assume
// that a closed way with an amenity tag is an area, unless the amenity
// is one of these specific types". This function computes a structure
// that allows testing of such conditions, based on the presets designated
// as as supporting (or not supporting) the area geometry.
//
// The returned object L is a whitelist/blacklist of tags. A closed way
// with a tag (k, v) is considered to be an area if `k in L && !(v in L[k])`
// (see `iD.Way#isArea()`). In other words, the keys of L form the whitelist,
// and the subkeys form the blacklist.
all.areaKeys = function() {
var areaKeys = {},
ignore = ['barrier', 'highway', 'footway', 'railway', 'type'],
presets = _.reject(all.collection, 'suggestion');
// whitelist
presets.forEach(function(d) {
for (var key in d.tags) break;
if (!key) return;
if (ignore.indexOf(key) !== -1) return;
if (d.geometry.indexOf('area') !== -1) {
areaKeys[key] = areaKeys[key] || {};
}
});
// blacklist
presets.forEach(function(d) {
for (var key in d.tags) break;
if (!key) return;
if (ignore.indexOf(key) !== -1) return;
var value = d.tags[key];
if (d.geometry.indexOf('area') === -1 &&
d.geometry.indexOf('line') !== -1 &&
key in areaKeys && value !== '*') {
areaKeys[key][value] = true;
}
});
return areaKeys;
};
all.load = function(d) {
if (d.fields) {
_.forEach(d.fields, function(d, id) {
fields[id] = iD.presets.Field(id, d);
if (d.universal) universal.push(fields[id]);
});
}
if (d.presets) {
_.forEach(d.presets, function(d, id) {
all.collection.push(iD.presets.Preset(id, d, fields));
});
}
if (d.categories) {
_.forEach(d.categories, function(d, id) {
all.collection.push(iD.presets.Category(id, d, all));
});
}
if (d.defaults) {
var getItem = _.bind(all.item, all);
defaults = {
area: iD.presets.Collection(d.defaults.area.map(getItem)),
line: iD.presets.Collection(d.defaults.line.map(getItem)),
point: iD.presets.Collection(d.defaults.point.map(getItem)),
vertex: iD.presets.Collection(d.defaults.vertex.map(getItem)),
relation: iD.presets.Collection(d.defaults.relation.map(getItem))
};
}
for (var i = 0; i < all.collection.length; i++) {
var preset = all.collection[i],
geometry = preset.geometry;
for (var j = 0; j < geometry.length; j++) {
var g = index[geometry[j]];
for (var k in preset.tags) {
(g[k] = g[k] || []).push(preset);
}
}
}
return all;
};
all.field = function(id) {
return fields[id];
};
all.universal = function() {
return universal;
};
all.defaults = function(geometry, n) {
var rec = recent.matchGeometry(geometry).collection.slice(0, 4),
def = _.uniq(rec.concat(defaults[geometry].collection)).slice(0, n - 1);
return iD.presets.Collection(_.uniq(rec.concat(def).concat(all.item(geometry))));
};
all.choose = function(preset) {
if (!preset.isFallback()) {
recent = iD.presets.Collection(_.uniq([preset].concat(recent.collection)));
}
return all;
};
return all;
}
exports.Category = Category;
exports.Collection = Collection;
exports.Field = Field;
exports.Preset = Preset;
exports.presets = presets;
Object.defineProperty(exports, '__esModule', { value: true });
}));

View File

@@ -1,4 +1,4 @@
iD.presets.Category = function(id, category, all) {
export function Category(id, category, all) {
category = _.clone(category);
category.id = id;
@@ -22,4 +22,4 @@ iD.presets.Category = function(id, category, all) {
};
return category;
};
}

View File

@@ -1,5 +1,4 @@
iD.presets.Collection = function(collection) {
export function Collection(collection) {
var maxSearchResults = 50,
maxSuggestionResults = 10;
@@ -126,4 +125,4 @@ iD.presets.Collection = function(collection) {
};
return presets;
};
}

View File

@@ -1,4 +1,4 @@
iD.presets.Field = function(id, field) {
export function Field(id, field) {
field = _.clone(field);
field.id = id;
@@ -21,4 +21,4 @@ iD.presets.Field = function(id, field) {
};
return field;
};
}

5
modules/presets/index.js Normal file
View File

@@ -0,0 +1,5 @@
export { Category } from './category.js';
export { Collection } from './collection.js';
export { Field } from './field.js';
export { Preset } from './preset.js';
export { presets } from './presets.js';

View File

@@ -1,4 +1,4 @@
iD.presets.Preset = function(id, preset, fields) {
export function Preset(id, preset, fields) {
preset = _.clone(preset);
preset.id = id;
@@ -126,4 +126,4 @@ iD.presets.Preset = function(id, preset, fields) {
};
return preset;
};
}

View File

@@ -1,5 +1,4 @@
iD.presets = function() {
export function presets() {
// an iD.presets.Collection with methods for
// loading new data and returning defaults
@@ -153,4 +152,4 @@ iD.presets = function() {
};
return all;
};
}

View File

@@ -42,6 +42,7 @@
<script src='../js/id/id.js'></script>
<script src='../js/lib/id/actions.js'></script>
<script src='../js/lib/id/presets.js'></script>
<script src='../js/lib/id/validations.js'></script>
<script src='../js/id/util.js'></script>
@@ -185,12 +186,6 @@
<script src='../js/id/core/tree.js'></script>
<script src='../js/id/core/tags.js'></script>
<script src='../js/id/presets.js'></script>
<script src='../js/id/presets/preset.js'></script>
<script src='../js/id/presets/category.js'></script>
<script src='../js/id/presets/collection.js'></script>
<script src='../js/id/presets/field.js'></script>
<script src='../js/id/util/session_mutex.js'></script>
<script src='../js/id/util/suggest_names.js'></script>

View File

@@ -34,9 +34,7 @@
<script src='../js/id/core/relation.js'></script>
<script src='../js/id/core/way.js'></script>
<script src='../js/id/presets.js'></script>
<script src='../js/id/presets/collection.js'></script>
<script src='../js/id/presets/preset.js'></script>
<script src='../js/lib/id/presets.js'></script>
<form style="position: fixed; right: 10px; bottom: 10px">
<input id="background-color" type="range" min="0" max="255" value="255">
@@ -61,7 +59,7 @@
};
context.presets = function() {
return iD.presets().load({
return iD.presets.presets().load({
presets: {
"amenity/restaurant": {
geometry: ['point'],

View File

@@ -1,4 +1,4 @@
describe("iD.presets", function() {
describe("iD.presets.presets", function() {
var p = {
point: {
tags: {},
@@ -22,7 +22,7 @@ describe("iD.presets", function() {
}
};
var c = iD.presets().load({presets: p});
var c = iD.presets.presets().load({presets: p});
describe("#match", function() {
it("returns a collection containing presets matching a geometry and tags", function() {
@@ -41,7 +41,7 @@ describe("iD.presets", function() {
});
describe("#areaKeys", function() {
var presets = iD.presets().load({
var presets = iD.presets.presets().load({
presets: {
'amenity/fuel/shell': {
tags: { 'amenity': 'fuel' },
@@ -110,7 +110,7 @@ describe("iD.presets", function() {
var presets;
before(function() {
presets = iD.presets().load(iD.data.presets);
presets = iD.presets.presets().load(iD.data.presets);
});
it("prefers building to multipolygon", function() {