diff --git a/Makefile b/Makefile
index 1f681c269..3a7f865dc 100644
--- a/Makefile
+++ b/Makefile
@@ -45,7 +45,8 @@ $(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/validations.js \
+ js/lib/id/util.js
js/lib/id/actions.js: modules/
node_modules/.bin/rollup -f umd -n iD.actions modules/actions/index.js --no-strict > $@
@@ -56,6 +57,9 @@ js/lib/id/presets.js: modules/
js/lib/id/validations.js: modules/
node_modules/.bin/rollup -f umd -n iD.validations modules/validations/index.js --no-strict > $@
+js/lib/id/util.js: modules/
+ node_modules/.bin/rollup -f umd -n iD.util modules/util/index.js --no-strict > $@
+
dist/iD.js: \
js/lib/bootstrap-tooltip.js \
js/lib/d3.v3.js \
@@ -85,9 +89,6 @@ dist/iD.js: \
js/id/services/taginfo.js \
js/id/services/wikidata.js \
js/id/services/wikipedia.js \
- js/id/util.js \
- js/id/util/session_mutex.js \
- js/id/util/suggest_names.js \
js/id/geo.js \
js/id/geo/extent.js \
js/id/geo/intersection.js \
diff --git a/index.html b/index.html
index 4473a9e5b..77c775929 100644
--- a/index.html
+++ b/index.html
@@ -36,10 +36,7 @@
-
-
-
-
+
diff --git a/js/lib/id/util.js b/js/lib/id/util.js
new file mode 100644
index 000000000..01c2ffdb0
--- /dev/null
+++ b/js/lib/id/util.js
@@ -0,0 +1,272 @@
+(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.util = global.iD.util || {})));
+}(this, function (exports) { 'use strict';
+
+ function tagText(entity) {
+ return d3.entries(entity.tags).map(function(e) {
+ return e.key + '=' + e.value;
+ }).join(', ');
+ }
+
+ function entitySelector(ids) {
+ return ids.length ? '.' + ids.join(',.') : 'nothing';
+ }
+
+ function entityOrMemberSelector(ids, graph) {
+ var s = entitySelector(ids);
+
+ ids.forEach(function(id) {
+ var entity = graph.hasEntity(id);
+ if (entity && entity.type === 'relation') {
+ entity.members.forEach(function(member) {
+ s += ',.' + member.id;
+ });
+ }
+ });
+
+ return s;
+ }
+
+ function displayName(entity) {
+ var localeName = 'name:' + iD.detect().locale.toLowerCase().split('-')[0];
+ return entity.tags[localeName] || entity.tags.name || entity.tags.ref;
+ }
+
+ function displayType(id) {
+ return {
+ n: t('inspector.node'),
+ w: t('inspector.way'),
+ r: t('inspector.relation')
+ }[id.charAt(0)];
+ }
+
+ function stringQs(str) {
+ return str.split('&').reduce(function(obj, pair){
+ var parts = pair.split('=');
+ if (parts.length === 2) {
+ obj[parts[0]] = (null === parts[1]) ? '' : decodeURIComponent(parts[1]);
+ }
+ return obj;
+ }, {});
+ }
+
+ function qsString(obj, noencode) {
+ function softEncode(s) {
+ // encode everything except special characters used in certain hash parameters:
+ // "/" in map states, ":", ",", {" and "}" in background
+ return encodeURIComponent(s).replace(/(%2F|%3A|%2C|%7B|%7D)/g, decodeURIComponent);
+ }
+ return Object.keys(obj).sort().map(function(key) {
+ return encodeURIComponent(key) + '=' + (
+ noencode ? softEncode(obj[key]) : encodeURIComponent(obj[key]));
+ }).join('&');
+ }
+
+ function prefixDOMProperty(property) {
+ var prefixes = ['webkit', 'ms', 'moz', 'o'],
+ i = -1,
+ n = prefixes.length,
+ s = document.body;
+
+ if (property in s)
+ return property;
+
+ property = property.substr(0, 1).toUpperCase() + property.substr(1);
+
+ while (++i < n)
+ if (prefixes[i] + property in s)
+ return prefixes[i] + property;
+
+ return false;
+ }
+
+ function prefixCSSProperty(property) {
+ var prefixes = ['webkit', 'ms', 'Moz', 'O'],
+ i = -1,
+ n = prefixes.length,
+ s = document.body.style;
+
+ if (property.toLowerCase() in s)
+ return property.toLowerCase();
+
+ while (++i < n)
+ if (prefixes[i] + property in s)
+ return '-' + prefixes[i].toLowerCase() + property.replace(/([A-Z])/g, '-$1').toLowerCase();
+
+ return false;
+ }
+
+
+ var transformProperty;
+ function setTransform(el, x, y, scale) {
+ var prop = transformProperty = transformProperty || prefixCSSProperty('Transform'),
+ translate = iD.detect().opera ?
+ 'translate(' + x + 'px,' + y + 'px)' :
+ 'translate3d(' + x + 'px,' + y + 'px,0)';
+ return el.style(prop, translate + (scale ? ' scale(' + scale + ')' : ''));
+ }
+
+ function getStyle(selector) {
+ for (var i = 0; i < document.styleSheets.length; i++) {
+ var rules = document.styleSheets[i].rules || document.styleSheets[i].cssRules || [];
+ for (var k = 0; k < rules.length; k++) {
+ var selectorText = rules[k].selectorText && rules[k].selectorText.split(', ');
+ if (_.includes(selectorText, selector)) {
+ return rules[k];
+ }
+ }
+ }
+ }
+
+ function editDistance(a, b) {
+ if (a.length === 0) return b.length;
+ if (b.length === 0) return a.length;
+ var matrix = [];
+ for (var i = 0; i <= b.length; i++) { matrix[i] = [i]; }
+ for (var j = 0; j <= a.length; j++) { matrix[0][j] = j; }
+ for (i = 1; i <= b.length; i++) {
+ for (j = 1; j <= a.length; j++) {
+ if (b.charAt(i-1) === a.charAt(j-1)) {
+ matrix[i][j] = matrix[i-1][j-1];
+ } else {
+ matrix[i][j] = Math.min(matrix[i-1][j-1] + 1, // substitution
+ Math.min(matrix[i][j-1] + 1, // insertion
+ matrix[i-1][j] + 1)); // deletion
+ }
+ }
+ }
+ return matrix[b.length][a.length];
+ }
+
+ // a d3.mouse-alike which
+ // 1. Only works on HTML elements, not SVG
+ // 2. Does not cause style recalculation
+ function fastMouse(container) {
+ var rect = container.getBoundingClientRect(),
+ rectLeft = rect.left,
+ rectTop = rect.top,
+ clientLeft = +container.clientLeft,
+ clientTop = +container.clientTop;
+ return function(e) {
+ return [
+ e.clientX - rectLeft - clientLeft,
+ e.clientY - rectTop - clientTop];
+ };
+ }
+
+ /* eslint-disable no-proto */
+ const getPrototypeOf = Object.getPrototypeOf || function(obj) { return obj.__proto__; };
+ /* eslint-enable no-proto */
+
+ function asyncMap(inputs, func, callback) {
+ var remaining = inputs.length,
+ results = [],
+ errors = [];
+
+ inputs.forEach(function(d, i) {
+ func(d, function done(err, data) {
+ errors[i] = err;
+ results[i] = data;
+ remaining--;
+ if (!remaining) callback(errors, results);
+ });
+ });
+ }
+
+ // wraps an index to an interval [0..length-1]
+ function wrap(index, length) {
+ if (index < 0)
+ index += Math.ceil(-index/length)*length;
+ return index % length;
+ }
+
+ // A per-domain session mutex backed by a cookie and dead man's
+ // switch. If the session crashes, the mutex will auto-release
+ // after 5 seconds.
+
+ function SessionMutex(name) {
+ var mutex = {},
+ intervalID;
+
+ function renew() {
+ var expires = new Date();
+ expires.setSeconds(expires.getSeconds() + 5);
+ document.cookie = name + '=1; expires=' + expires.toUTCString();
+ }
+
+ mutex.lock = function() {
+ if (intervalID) return true;
+ var cookie = document.cookie.replace(new RegExp('(?:(?:^|.*;)\\s*' + name + '\\s*\\=\\s*([^;]*).*$)|^.*$'), '$1');
+ if (cookie) return false;
+ renew();
+ intervalID = window.setInterval(renew, 4000);
+ return true;
+ };
+
+ mutex.unlock = function() {
+ if (!intervalID) return;
+ document.cookie = name + '=; expires=Thu, 01 Jan 1970 00:00:00 GMT';
+ clearInterval(intervalID);
+ intervalID = null;
+ };
+
+ mutex.locked = function() {
+ return !!intervalID;
+ };
+
+ return mutex;
+ }
+
+ function SuggestNames(preset, suggestions) {
+ preset = preset.id.split('/', 2);
+ var k = preset[0],
+ v = preset[1];
+
+ return function(value, callback) {
+ var result = [];
+ if (value && value.length > 2) {
+ if (suggestions[k] && suggestions[k][v]) {
+ for (var sugg in suggestions[k][v]) {
+ var dist = iD.util.editDistance(value, sugg.substring(0, value.length));
+ if (dist < 3) {
+ result.push({
+ title: sugg,
+ value: sugg,
+ dist: dist
+ });
+ }
+ }
+ }
+ result.sort(function(a, b) {
+ return a.dist - b.dist;
+ });
+ }
+ result = result.slice(0,3);
+ callback(result);
+ };
+ }
+
+ exports.tagText = tagText;
+ exports.entitySelector = entitySelector;
+ exports.entityOrMemberSelector = entityOrMemberSelector;
+ exports.displayName = displayName;
+ exports.displayType = displayType;
+ exports.stringQs = stringQs;
+ exports.qsString = qsString;
+ exports.prefixDOMProperty = prefixDOMProperty;
+ exports.prefixCSSProperty = prefixCSSProperty;
+ exports.setTransform = setTransform;
+ exports.getStyle = getStyle;
+ exports.editDistance = editDistance;
+ exports.fastMouse = fastMouse;
+ exports.getPrototypeOf = getPrototypeOf;
+ exports.asyncMap = asyncMap;
+ exports.wrap = wrap;
+ exports.SessionMutex = SessionMutex;
+ exports.SuggestNames = SuggestNames;
+
+ Object.defineProperty(exports, '__esModule', { value: true });
+
+}));
\ No newline at end of file
diff --git a/modules/util/index.js b/modules/util/index.js
new file mode 100644
index 000000000..92780dffa
--- /dev/null
+++ b/modules/util/index.js
@@ -0,0 +1,18 @@
+export { tagText } from './util';
+export { entitySelector } from './util';
+export { entityOrMemberSelector } from './util';
+export { displayName } from './util';
+export { displayType } from './util';
+export { stringQs } from './util';
+export { qsString } from './util';
+export { prefixDOMProperty } from './util';
+export { prefixCSSProperty } from './util';
+export { setTransform } from './util';
+export { getStyle } from './util';
+export { editDistance } from './util';
+export { fastMouse } from './util';
+export { getPrototypeOf } from './util';
+export { asyncMap } from './util';
+export { wrap } from './util';
+export { SessionMutex } from './session_mutex';
+export { SuggestNames } from './suggest_names';
diff --git a/js/id/util/session_mutex.js b/modules/util/session_mutex.js
similarity index 95%
rename from js/id/util/session_mutex.js
rename to modules/util/session_mutex.js
index da9a195c3..f71a6e413 100644
--- a/js/id/util/session_mutex.js
+++ b/modules/util/session_mutex.js
@@ -2,7 +2,7 @@
// switch. If the session crashes, the mutex will auto-release
// after 5 seconds.
-iD.util.SessionMutex = function(name) {
+export function SessionMutex(name) {
var mutex = {},
intervalID;
@@ -33,4 +33,4 @@ iD.util.SessionMutex = function(name) {
};
return mutex;
-};
+}
diff --git a/js/id/util/suggest_names.js b/modules/util/suggest_names.js
similarity index 93%
rename from js/id/util/suggest_names.js
rename to modules/util/suggest_names.js
index 79040c003..a667c3a7f 100644
--- a/js/id/util/suggest_names.js
+++ b/modules/util/suggest_names.js
@@ -1,4 +1,4 @@
-iD.util.SuggestNames = function(preset, suggestions) {
+export function SuggestNames(preset, suggestions) {
preset = preset.id.split('/', 2);
var k = preset[0],
v = preset[1];
@@ -25,4 +25,4 @@ iD.util.SuggestNames = function(preset, suggestions) {
result = result.slice(0,3);
callback(result);
};
-};
+}
diff --git a/js/id/util.js b/modules/util/util.js
similarity index 82%
rename from js/id/util.js
rename to modules/util/util.js
index 050355c84..a7020d7a5 100644
--- a/js/id/util.js
+++ b/modules/util/util.js
@@ -1,17 +1,15 @@
-iD.util = {};
-
-iD.util.tagText = function(entity) {
+export function tagText(entity) {
return d3.entries(entity.tags).map(function(e) {
return e.key + '=' + e.value;
}).join(', ');
-};
+}
-iD.util.entitySelector = function(ids) {
+export function entitySelector(ids) {
return ids.length ? '.' + ids.join(',.') : 'nothing';
-};
+}
-iD.util.entityOrMemberSelector = function(ids, graph) {
- var s = iD.util.entitySelector(ids);
+export function entityOrMemberSelector(ids, graph) {
+ var s = entitySelector(ids);
ids.forEach(function(id) {
var entity = graph.hasEntity(id);
@@ -23,22 +21,22 @@ iD.util.entityOrMemberSelector = function(ids, graph) {
});
return s;
-};
+}
-iD.util.displayName = function(entity) {
+export function displayName(entity) {
var localeName = 'name:' + iD.detect().locale.toLowerCase().split('-')[0];
return entity.tags[localeName] || entity.tags.name || entity.tags.ref;
-};
+}
-iD.util.displayType = function(id) {
+export function displayType(id) {
return {
n: t('inspector.node'),
w: t('inspector.way'),
r: t('inspector.relation')
}[id.charAt(0)];
-};
+}
-iD.util.stringQs = function(str) {
+export function stringQs(str) {
return str.split('&').reduce(function(obj, pair){
var parts = pair.split('=');
if (parts.length === 2) {
@@ -46,9 +44,9 @@ iD.util.stringQs = function(str) {
}
return obj;
}, {});
-};
+}
-iD.util.qsString = function(obj, noencode) {
+export function qsString(obj, noencode) {
function softEncode(s) {
// encode everything except special characters used in certain hash parameters:
// "/" in map states, ":", ",", {" and "}" in background
@@ -58,9 +56,9 @@ iD.util.qsString = function(obj, noencode) {
return encodeURIComponent(key) + '=' + (
noencode ? softEncode(obj[key]) : encodeURIComponent(obj[key]));
}).join('&');
-};
+}
-iD.util.prefixDOMProperty = function(property) {
+export function prefixDOMProperty(property) {
var prefixes = ['webkit', 'ms', 'moz', 'o'],
i = -1,
n = prefixes.length,
@@ -76,9 +74,9 @@ iD.util.prefixDOMProperty = function(property) {
return prefixes[i] + property;
return false;
-};
+}
-iD.util.prefixCSSProperty = function(property) {
+export function prefixCSSProperty(property) {
var prefixes = ['webkit', 'ms', 'Moz', 'O'],
i = -1,
n = prefixes.length,
@@ -92,18 +90,19 @@ iD.util.prefixCSSProperty = function(property) {
return '-' + prefixes[i].toLowerCase() + property.replace(/([A-Z])/g, '-$1').toLowerCase();
return false;
-};
+}
-iD.util.setTransform = function(el, x, y, scale) {
- var prop = iD.util.transformProperty = iD.util.transformProperty || iD.util.prefixCSSProperty('Transform'),
+var transformProperty;
+export function setTransform(el, x, y, scale) {
+ var prop = transformProperty = transformProperty || prefixCSSProperty('Transform'),
translate = iD.detect().opera ?
'translate(' + x + 'px,' + y + 'px)' :
'translate3d(' + x + 'px,' + y + 'px,0)';
return el.style(prop, translate + (scale ? ' scale(' + scale + ')' : ''));
-};
+}
-iD.util.getStyle = function(selector) {
+export function getStyle(selector) {
for (var i = 0; i < document.styleSheets.length; i++) {
var rules = document.styleSheets[i].rules || document.styleSheets[i].cssRules || [];
for (var k = 0; k < rules.length; k++) {
@@ -113,9 +112,9 @@ iD.util.getStyle = function(selector) {
}
}
}
-};
+}
-iD.util.editDistance = function(a, b) {
+export function editDistance(a, b) {
if (a.length === 0) return b.length;
if (b.length === 0) return a.length;
var matrix = [];
@@ -133,12 +132,12 @@ iD.util.editDistance = function(a, b) {
}
}
return matrix[b.length][a.length];
-};
+}
// a d3.mouse-alike which
// 1. Only works on HTML elements, not SVG
// 2. Does not cause style recalculation
-iD.util.fastMouse = function(container) {
+export function fastMouse(container) {
var rect = container.getBoundingClientRect(),
rectLeft = rect.left,
rectTop = rect.top,
@@ -149,13 +148,13 @@ iD.util.fastMouse = function(container) {
e.clientX - rectLeft - clientLeft,
e.clientY - rectTop - clientTop];
};
-};
+}
/* eslint-disable no-proto */
-iD.util.getPrototypeOf = Object.getPrototypeOf || function(obj) { return obj.__proto__; };
+export const getPrototypeOf = Object.getPrototypeOf || function(obj) { return obj.__proto__; };
/* eslint-enable no-proto */
-iD.util.asyncMap = function(inputs, func, callback) {
+export function asyncMap(inputs, func, callback) {
var remaining = inputs.length,
results = [],
errors = [];
@@ -168,11 +167,11 @@ iD.util.asyncMap = function(inputs, func, callback) {
if (!remaining) callback(errors, results);
});
});
-};
+}
// wraps an index to an interval [0..length-1]
-iD.util.wrap = function(index, length) {
+export function wrap(index, length) {
if (index < 0)
index += Math.ceil(-index/length)*length;
return index % length;
-};
+}
diff --git a/test/index.html b/test/index.html
index 80564fdfb..00226e2c6 100644
--- a/test/index.html
+++ b/test/index.html
@@ -42,10 +42,14 @@
+<<<<<<< 6c7786ab274f46879f15cbb3ba2016a540cf8df5
+=======
+
+>>>>>>> refactor "util" into ES6 modules for #3118
@@ -186,8 +190,22 @@
+<<<<<<< 6c7786ab274f46879f15cbb3ba2016a540cf8df5
+=======
+
+
+
+
+
+
+
+
+
+
+
+>>>>>>> refactor "util" into ES6 modules for #3118