diff --git a/css/app.css b/css/app.css
index 1923a1cc8..9e70092af 100644
--- a/css/app.css
+++ b/css/app.css
@@ -106,7 +106,6 @@ input[type=text] {
background-color: black;
border:1px solid rgba(255, 255, 255, .25);
color: white;
- border-radius: 4px;
}
textarea:focus,
@@ -181,17 +180,13 @@ ul li { list-style: none;}
ul.toggle-list li a {
font-weight: bold;
- color: #c1c1c1;
+ color: #333;
padding: 10px;
border-top: 1px solid white;
display:block;
border-top: 1px solid rgba(0, 0, 0, .5);
}
-ul.toggle-list li a.selected {
- color: #333;
-}
-
ul.toggle-list .icon {
float: left;
margin-right: 5px;
@@ -945,6 +940,17 @@ div.typeahead a:first-child {
display: block;
}
+.modal-flash .content {
+ box-shadow: none;
+ border-radius: 4px;
+ background: #111;
+ color: #eee;
+}
+
+.modal-flash .close-modal {
+ display:none;
+}
+
.changeset-list li {
border-top:1px solid #ccc;
padding:5px 10px;
@@ -975,18 +981,19 @@ div.typeahead a:first-child {
------------------------------------------------------- */
.notice {
- position:absolute;
- top:11px;
- left:11px;
- width:278px;
+ float:left;
+ width:33.333%;
+ padding-right: 10px;
+}
+
+.notice .notice-text {
+ height: 40px;
text-align: center;
- height:38px;
- padding:10px 20px;
- background:#fff;
- color:#000;
font-weight: normal;
- line-height: 21px;
- border-radius:5px;
+ border-radius: 5px;
+ line-height: 40px;
+ background: #fff;
+ color: #000;
opacity: 0.9;
}
diff --git a/index.html b/index.html
index 318bc54a8..d65a7dcb7 100644
--- a/index.html
+++ b/index.html
@@ -73,7 +73,6 @@
-
@@ -82,11 +81,9 @@
-
-
-
+
diff --git a/js/id/actions/add_relation_member.js b/js/id/actions/add_relation_member.js
deleted file mode 100644
index 45e24a0b0..000000000
--- a/js/id/actions/add_relation_member.js
+++ /dev/null
@@ -1,9 +0,0 @@
-iD.actions.AddRelationMember = function(relationId, member, index) {
- return function(graph) {
- var relation = graph.entity(relationId),
- members = relation.members.slice();
-
- members.splice((index === undefined) ? members.length : index, 0, member);
- return graph.replace(relation.update({members: members}));
- };
-};
diff --git a/js/id/actions/add_way_node.js b/js/id/actions/add_way_node.js
index d685c3295..3e4fffcfc 100644
--- a/js/id/actions/add_way_node.js
+++ b/js/id/actions/add_way_node.js
@@ -1,10 +1,6 @@
// https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/AddNodeToWayAction.as
iD.actions.AddWayNode = function(wayId, nodeId, index) {
return function(graph) {
- var way = graph.entity(wayId),
- node = graph.entity(nodeId),
- nodes = way.nodes.slice();
- nodes.splice((index === undefined) ? nodes.length : index, 0, nodeId);
- return graph.replace(way.update({nodes: nodes}));
+ return graph.replace(graph.entity(wayId).addNode(nodeId, index));
};
};
diff --git a/js/id/actions/delete_node.js b/js/id/actions/delete_node.js
index a2d54fecb..48e704ac3 100644
--- a/js/id/actions/delete_node.js
+++ b/js/id/actions/delete_node.js
@@ -1,18 +1,29 @@
// https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/DeleteNodeAction.as
iD.actions.DeleteNode = function(nodeId) {
- return function(graph) {
+ var action = function(graph) {
var node = graph.entity(nodeId);
graph.parentWays(node)
.forEach(function(parent) {
- graph = iD.actions.RemoveWayNode(parent.id, nodeId)(graph);
+ parent = parent.removeNode(nodeId);
+ graph = graph.replace(parent);
+
+ if (parent.isDegenerate()) {
+ graph = iD.actions.DeleteWay(parent.id)(graph);
+ }
});
graph.parentRelations(node)
.forEach(function(parent) {
- graph = iD.actions.RemoveRelationMember(parent.id, nodeId)(graph);
+ graph = graph.replace(parent.removeMember(nodeId));
});
return graph.remove(node);
};
+
+ action.enabled = function(graph) {
+ return true;
+ };
+
+ return action;
};
diff --git a/js/id/actions/delete_way.js b/js/id/actions/delete_way.js
index c2065a756..224537c81 100644
--- a/js/id/actions/delete_way.js
+++ b/js/id/actions/delete_way.js
@@ -1,11 +1,11 @@
// https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/DeleteWayAction.as
iD.actions.DeleteWay = function(wayId) {
- return function(graph) {
+ var action = function(graph) {
var way = graph.entity(wayId);
graph.parentRelations(way)
.forEach(function(parent) {
- graph = iD.actions.RemoveRelationMember(parent.id, wayId)(graph);
+ graph = graph.replace(parent.removeMember(wayId));
});
way.nodes.forEach(function (nodeId) {
@@ -15,7 +15,7 @@ iD.actions.DeleteWay = function(wayId) {
// can be deleted on earlier iterations of this loop.
if (!node) return;
- graph = iD.actions.RemoveWayNode(wayId, nodeId)(graph);
+ graph = graph.replace(way.removeNode(nodeId));
if (!graph.parentWays(node).length &&
!graph.parentRelations(node).length) {
@@ -29,4 +29,10 @@ iD.actions.DeleteWay = function(wayId) {
return graph.remove(way);
};
+
+ action.enabled = function(graph) {
+ return true;
+ }
+
+ return action;
};
diff --git a/js/id/actions/move_node.js b/js/id/actions/move_node.js
index 9204fcb67..433242ff6 100644
--- a/js/id/actions/move_node.js
+++ b/js/id/actions/move_node.js
@@ -2,7 +2,6 @@
// https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/MoveNodeAction.as
iD.actions.MoveNode = function(nodeId, loc) {
return function(graph) {
- var node = graph.entity(nodeId);
- return graph.replace(node.update({loc: loc}));
+ return graph.replace(graph.entity(nodeId).move(loc));
};
};
diff --git a/js/id/actions/move_way.js b/js/id/actions/move_way.js
index ea547cc44..09ab8fd50 100644
--- a/js/id/actions/move_way.js
+++ b/js/id/actions/move_way.js
@@ -6,7 +6,7 @@ iD.actions.MoveWay = function(wayId, delta, projection) {
var node = graph.entity(id),
start = projection(node.loc),
end = projection.invert([start[0] + delta[0], start[1] + delta[1]]);
- graph = iD.actions.MoveNode(id, end)(graph);
+ graph = graph.replace(node.move(end));
});
return graph;
diff --git a/js/id/actions/remove_relation_member.js b/js/id/actions/remove_relation_member.js
deleted file mode 100644
index e72dd7ca0..000000000
--- a/js/id/actions/remove_relation_member.js
+++ /dev/null
@@ -1,9 +0,0 @@
-iD.actions.RemoveRelationMember = function(relationId, memberId) {
- return function(graph) {
- var relation = graph.entity(relationId),
- members = _.reject(relation.members, function(r) {
- return r.id === memberId;
- });
- return graph.replace(relation.update({members: members}));
- };
-};
diff --git a/js/id/actions/remove_way_node.js b/js/id/actions/remove_way_node.js
deleted file mode 100644
index 8c589a20c..000000000
--- a/js/id/actions/remove_way_node.js
+++ /dev/null
@@ -1,17 +0,0 @@
-iD.actions.RemoveWayNode = function(wayId, nodeId) {
- return function(graph) {
- var way = graph.entity(wayId), nodes;
- // If this is the connecting node in a closed area
- if (way.nodes.length > 1 &&
- _.indexOf(way.nodes, nodeId) === 0 &&
- _.lastIndexOf(way.nodes, nodeId) === way.nodes.length - 1) {
- // Remove the node
- nodes = _.without(way.nodes, nodeId);
- // And reclose the way on the new first node.
- nodes.push(nodes[0]);
- } else {
- nodes = _.without(way.nodes, nodeId);
- }
- return graph.replace(way.update({nodes: nodes}));
- };
-};
diff --git a/js/id/actions/reverse_way.js b/js/id/actions/reverse_way.js
index a845fe844..4a7d392b7 100644
--- a/js/id/actions/reverse_way.js
+++ b/js/id/actions/reverse_way.js
@@ -53,7 +53,7 @@ iD.actions.ReverseWay = function(wayId) {
}
}
- return function(graph) {
+ var action = function(graph) {
var way = graph.entity(wayId),
nodes = way.nodes.slice().reverse(),
tags = {}, key, role;
@@ -65,11 +65,18 @@ iD.actions.ReverseWay = function(wayId) {
graph.parentRelations(way).forEach(function (relation) {
relation.members.forEach(function (member, index) {
if (member.id === way.id && (role = {forward: 'backward', backward: 'forward'}[member.role])) {
- graph = iD.actions.UpdateRelationMember(relation.id, {role: role}, index)(graph);
+ relation = relation.updateMember({role: role}, index);
+ graph = graph.replace(relation);
}
});
});
return graph.replace(way.update({nodes: nodes, tags: tags}));
};
+
+ action.enabled = function(graph) {
+ return true;
+ };
+
+ return action;
};
diff --git a/js/id/actions/split_way.js b/js/id/actions/split_way.js
index d716880cf..6fbd08595 100644
--- a/js/id/actions/split_way.js
+++ b/js/id/actions/split_way.js
@@ -19,7 +19,7 @@ iD.actions.SplitWay = function(nodeId, newWayId) {
}
var action = function(graph) {
- if (!action.permitted(graph))
+ if (!action.enabled(graph))
return graph;
var way = candidateWays(graph)[0],
@@ -37,11 +37,8 @@ iD.actions.SplitWay = function(nodeId, newWayId) {
if (relation.isRestriction()) {
var via = relation.memberByRole('via');
if (via && newWay.contains(via.id)) {
- graph = iD.actions.UpdateRelationMember(
- relation.id,
- {id: newWay.id},
- relation.memberById(way.id).index
- )(graph);
+ relation = relation.updateMember({id: newWay.id}, relation.memberById(way.id).index);
+ graph = graph.replace(relation);
}
} else {
var role = relation.memberById(way.id).role,
@@ -50,23 +47,21 @@ iD.actions.SplitWay = function(nodeId, newWayId) {
j;
for (j = 0; j < relation.members.length; j++) {
- if (relation.members[j].type === 'way' && graph.entity(relation.members[j].id).contains(last)) {
+ var entity = graph.entity(relation.members[j].id);
+ if (entity && entity.type === 'way' && entity.contains(last)) {
break;
}
}
- graph = iD.actions.AddRelationMember(
- relation.id,
- {id: newWay.id, type: 'way', role: role},
- i <= j ? i + 1 : i
- )(graph);
+ relation = relation.addMember({id: newWay.id, type: 'way', role: role}, i <= j ? i + 1 : i);
+ graph = graph.replace(relation);
}
});
return graph;
};
- action.permitted = function(graph) {
+ action.enabled = function(graph) {
return candidateWays(graph).length === 1;
};
diff --git a/js/id/actions/unjoin_node.js b/js/id/actions/unjoin_node.js
new file mode 100644
index 000000000..39e05a9cf
--- /dev/null
+++ b/js/id/actions/unjoin_node.js
@@ -0,0 +1,37 @@
+// Unjoin the ways at the given node.
+//
+// For testing convenience, accepts an ID to assign to the (first) new node.
+// Normally, this will be undefined and the way will automatically
+// be assigned a new ID.
+//
+// Reference:
+// https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/UnjoinNodeAction.as
+// https://github.com/openstreetmap/josm/blob/mirror/src/org/openstreetmap/josm/actions/UnGlueAction.java
+//
+iD.actions.UnjoinNode = function(nodeId, newNodeId) {
+ var action = function(graph) {
+ if (!action.enabled(graph))
+ return graph;
+
+ var node = graph.entity(nodeId);
+
+ graph.parentWays(node).forEach(function(parent, i) {
+ if (i === 0)
+ return;
+
+ var index = parent.nodes.indexOf(nodeId),
+ newNode = iD.Node({id: newNodeId, loc: node.loc, tags: node.tags});
+
+ graph = graph.replace(newNode);
+ graph = graph.replace(parent.updateNode(newNode.id, index));
+ });
+
+ return graph;
+ };
+
+ action.enabled = function(graph) {
+ return graph.parentWays(graph.entity(nodeId)).length >= 2;
+ };
+
+ return action;
+};
diff --git a/js/id/actions/update_relation_member.js b/js/id/actions/update_relation_member.js
deleted file mode 100644
index 29eb79afe..000000000
--- a/js/id/actions/update_relation_member.js
+++ /dev/null
@@ -1,9 +0,0 @@
-iD.actions.UpdateRelationMember = function(relationId, properties, index) {
- return function(graph) {
- var relation = graph.entity(relationId),
- members = relation.members.slice();
-
- members.splice(index, 1, _.extend({}, members[index], properties));
- return graph.replace(relation.update({members: members}));
- };
-};
diff --git a/js/id/connection.js b/js/id/connection.js
index 003750ff2..7b9f98a27 100644
--- a/js/id/connection.js
+++ b/js/id/connection.js
@@ -77,15 +77,8 @@ iD.Connection = function() {
delete o.lat;
}
o.id = iD.Entity.id.fromOSM(o.type, o.id);
- switch (o.type) {
- case 'node':
- o._poi = !refNodes[o.id];
- return iD.Node(o);
- case 'way':
- return iD.Way(o);
- case 'relation':
- return iD.Relation(o);
- }
+ if (o.type === 'node') o._poi = !refNodes[o.id];
+ return new iD.Entity[o.type](o);
}
function parse(dom) {
diff --git a/js/id/geo.js b/js/id/geo.js
index 06b63ae14..b05a185fa 100644
--- a/js/id/geo.js
+++ b/js/id/geo.js
@@ -1 +1,75 @@
iD.geo = {};
+
+iD.geo.roundCoords = function(c) {
+ return [Math.floor(c[0]), Math.floor(c[1])];
+};
+
+iD.geo.interp = function(p1, p2, t) {
+ return [p1[0] + (p2[0] - p1[0]) * t,
+ p1[1] + (p2[1] - p1[1]) * t];
+};
+
+iD.geo.dist = function(a, b) {
+ return Math.sqrt(Math.pow(a[0] - b[0], 2) +
+ Math.pow(a[1] - b[1], 2));
+};
+
+iD.geo.chooseIndex = function(way, point, map) {
+ var dist = iD.geo.dist,
+ projNodes = way.nodes.map(function(n) {
+ return map.projection(n.loc);
+ });
+
+ for (var i = 0, changes = []; i < projNodes.length - 1; i++) {
+ changes[i] =
+ (dist(projNodes[i], point) + dist(point, projNodes[i + 1])) /
+ dist(projNodes[i], projNodes[i + 1]);
+ }
+
+ var idx = _.indexOf(changes, _.min(changes)),
+ ratio = dist(projNodes[idx], point) / dist(projNodes[idx], projNodes[idx + 1]),
+ loc = iD.geo.interp(way.nodes[idx].loc, way.nodes[idx + 1].loc, ratio);
+
+ return {
+ index: idx + 1,
+ loc: loc
+ };
+};
+
+// Return whether point is contained in polygon.
+//
+// `point` should be a 2-item array of coordinates.
+// `polygon` should be an array of 2-item arrays of coordinates.
+//
+// From https://github.com/substack/point-in-polygon.
+// ray-casting algorithm based on
+// http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
+//
+iD.geo.pointInPolygon = function(point, polygon) {
+ var x = point[0],
+ y = point[1],
+ inside = false;
+
+ for (var i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
+ var xi = polygon[i][0], yi = polygon[i][1];
+ var xj = polygon[j][0], yj = polygon[j][1];
+
+ var intersect = ((yi > y) != (yj > y)) &&
+ (x < (xj - xi) * (y - yi) / (yj - yi) + xi);
+ if (intersect) inside = !inside;
+ }
+
+ return inside;
+};
+
+iD.geo.polygonContainsPolygon = function(outer, inner) {
+ return _.every(inner, function (point) {
+ return iD.geo.pointInPolygon(point, outer);
+ });
+};
+
+iD.geo.polygonIntersectsPolygon = function(outer, inner) {
+ return _.some(inner, function (point) {
+ return iD.geo.pointInPolygon(point, outer);
+ });
+};
diff --git a/js/id/graph/entity.js b/js/id/graph/entity.js
index 8c8f9a6fb..254607a7b 100644
--- a/js/id/graph/entity.js
+++ b/js/id/graph/entity.js
@@ -50,6 +50,7 @@ iD.Entity.prototype = {
Object.freeze(this);
Object.freeze(this.tags);
+ if (this.loc) Object.freeze(this.loc);
if (this.nodes) Object.freeze(this.nodes);
if (this.members) Object.freeze(this.members);
}
@@ -109,16 +110,3 @@ iD.Entity.prototype = {
return n.length === 0 ? 'unknown' : n.join('; ');
}
};
-
-iD.Entity.extend = function(properties) {
- var Subclass = function() {
- if (this instanceof Subclass) return;
- return (new Subclass()).initialize(arguments);
- };
-
- Subclass.prototype = new iD.Entity();
- _.extend(Subclass.prototype, properties);
- iD.Entity[properties.type] = Subclass;
-
- return Subclass;
-};
diff --git a/js/id/graph/history.js b/js/id/graph/history.js
index 3d027d042..142a9eeca 100644
--- a/js/id/graph/history.js
+++ b/js/id/graph/history.js
@@ -1,7 +1,7 @@
iD.History = function() {
var stack, index,
imagery_used = 'Bing',
- dispatch = d3.dispatch('change');
+ dispatch = d3.dispatch('change', 'undone', 'redone');
function perform(actions) {
actions = Array.prototype.slice.call(actions);
@@ -62,6 +62,7 @@ iD.History = function() {
if (stack[index].annotation) break;
}
+ dispatch.undone();
change(previous);
},
@@ -73,6 +74,7 @@ iD.History = function() {
if (stack[index].annotation) break;
}
+ dispatch.redone();
change(previous);
},
diff --git a/js/id/graph/node.js b/js/id/graph/node.js
index fd3a1cd69..df9c253a4 100644
--- a/js/id/graph/node.js
+++ b/js/id/graph/node.js
@@ -1,4 +1,14 @@
-iD.Node = iD.Entity.extend({
+iD.Node = iD.Entity.node = function iD_Node() {
+ if (!(this instanceof iD_Node)) {
+ return (new iD_Node()).initialize(arguments);
+ } else if (arguments.length) {
+ this.initialize(arguments);
+ }
+};
+
+iD.Node.prototype = Object.create(iD.Entity.prototype);
+
+_.extend(iD.Node.prototype, {
type: "node",
extent: function() {
@@ -7,5 +17,9 @@ iD.Node = iD.Entity.extend({
geometry: function() {
return this._poi ? 'point' : 'vertex';
+ },
+
+ move: function(loc) {
+ return this.update({loc: loc});
}
});
diff --git a/js/id/graph/relation.js b/js/id/graph/relation.js
index dc298ecbd..48a86c920 100644
--- a/js/id/graph/relation.js
+++ b/js/id/graph/relation.js
@@ -1,4 +1,14 @@
-iD.Relation = iD.Entity.extend({
+iD.Relation = iD.Entity.relation = function iD_Relation() {
+ if (!(this instanceof iD_Relation)) {
+ return (new iD_Relation()).initialize(arguments);
+ } else if (arguments.length) {
+ this.initialize(arguments);
+ }
+};
+
+iD.Relation.prototype = Object.create(iD.Entity.prototype);
+
+_.extend(iD.Relation.prototype, {
type: "relation",
members: [],
@@ -38,6 +48,23 @@ iD.Relation = iD.Entity.extend({
}
},
+ addMember: function(member, index) {
+ var members = this.members.slice();
+ members.splice(index === undefined ? members.length : index, 0, member);
+ return this.update({members: members});
+ },
+
+ updateMember: function(member, index) {
+ var members = this.members.slice();
+ members.splice(index, 1, _.extend({}, members[index], member));
+ return this.update({members: members});
+ },
+
+ removeMember: function(id) {
+ var members = _.reject(this.members, function(m) { return m.id === id; });
+ return this.update({members: members});
+ },
+
isRestriction: function() {
return !!(this.tags.type && this.tags.type.match(/^restriction:?/));
},
@@ -110,13 +137,13 @@ iD.Relation = iD.Entity.extend({
for (o = 0; o < outers.length; o++) {
outer = _.pluck(outers[o], 'loc');
- if (iD.util.geo.polygonContainsPolygon(outer, inner))
+ if (iD.geo.polygonContainsPolygon(outer, inner))
return o;
}
for (o = 0; o < outers.length; o++) {
outer = _.pluck(outers[o], 'loc');
- if (iD.util.geo.polygonIntersectsPolygon(outer, inner))
+ if (iD.geo.polygonIntersectsPolygon(outer, inner))
return o;
}
}
@@ -130,7 +157,7 @@ iD.Relation = iD.Entity.extend({
if (o !== undefined)
result[o].push(inners[i]);
else
- result.push(inners[i]); // Invalid geometry
+ result.push([inners[i]]); // Invalid geometry
}
return result;
diff --git a/js/id/graph/way.js b/js/id/graph/way.js
index 9efd4fe6c..0de0e55a1 100644
--- a/js/id/graph/way.js
+++ b/js/id/graph/way.js
@@ -1,4 +1,14 @@
-iD.Way = iD.Entity.extend({
+iD.Way = iD.Entity.way = function iD_Way() {
+ if (!(this instanceof iD_Way)) {
+ return (new iD_Way()).initialize(arguments);
+ } else if (arguments.length) {
+ this.initialize(arguments);
+ }
+};
+
+iD.Way.prototype = Object.create(iD.Entity.prototype);
+
+_.extend(iD.Way.prototype, {
type: "way",
nodes: [],
@@ -48,7 +58,34 @@ iD.Way = iD.Entity.extend({
!this.tags.barrier);
},
+ isDegenerate: function() {
+ return _.uniq(this.nodes).length < (this.isArea() ? 3 : 2);
+ },
+
geometry: function() {
return this.isArea() ? 'area' : 'line';
+ },
+
+ addNode: function(id, index) {
+ var nodes = this.nodes.slice();
+ nodes.splice(index === undefined ? nodes.length : index, 0, id);
+ return this.update({nodes: nodes});
+ },
+
+ updateNode: function(id, index) {
+ var nodes = this.nodes.slice();
+ nodes.splice(index, 1, id);
+ return this.update({nodes: nodes});
+ },
+
+ removeNode: function(id) {
+ var nodes = _.without(this.nodes, id);
+
+ // Preserve circularity
+ if (this.nodes.length > 1 && this.first() === id && this.last() === id) {
+ nodes.push(nodes[0]);
+ }
+
+ return this.update({nodes: nodes});
}
});
diff --git a/js/id/id.js b/js/id/id.js
index 31ff26836..c0f64a415 100644
--- a/js/id/id.js
+++ b/js/id/id.js
@@ -50,9 +50,7 @@ window.iD = function(container) {
}
}
- var notice = iD.ui.notice(limiter
- .append('div')
- .attr('class', 'notice'));
+ var notice = iD.ui.notice(limiter).message(null);
map.on('move.disable-buttons', disableTooHigh)
.on('move.contributors', _.debounce(function() {
@@ -184,26 +182,18 @@ window.iD = function(container) {
map.size(m.size());
});
- map.keybinding()
- .on('a', function(evt, mods) {
- if (mods) return;
- controller.enter(iD.modes.AddArea());
- })
- .on('⌫.prevent_navigation', function(evt, mods) {
- evt.preventDefault();
- })
- .on('p', function(evt, mods) {
- if (mods) return;
- controller.enter(iD.modes.AddPoint());
- })
- .on('l', function(evt, mods) {
- if (mods) return;
- controller.enter(iD.modes.AddLine());
- })
- .on('z', function(evt, mods) {
- if (mods === '⇧⌘' || mods === '⌃⇧') history.redo();
- if (mods === '⌘' || mods === '⌃') history.undo();
- });
+ var keybinding = d3.keybinding('main')
+ .on('P', function() { controller.enter(iD.modes.AddPoint()); })
+ .on('L', function() { controller.enter(iD.modes.AddLine()); })
+ .on('A', function() { controller.enter(iD.modes.AddArea()); })
+ .on('⌘+Z', function() { history.undo(); })
+ .on('⌃+Z', function() { history.undo(); })
+ .on('⌘+⇧+Z', function() { history.redo(); })
+ .on('⌃+⇧+Z', function() { history.redo(); })
+ .on('⌫', function() { d3.event.preventDefault(); });
+
+ d3.select(document)
+ .call(keybinding);
var hash = iD.Hash().controller(controller).map(map);
diff --git a/js/id/modes/add_area.js b/js/id/modes/add_area.js
index a9fcb830e..d3936229e 100644
--- a/js/id/modes/add_area.js
+++ b/js/id/modes/add_area.js
@@ -6,6 +6,8 @@ iD.modes.AddArea = function() {
description: 'Add parks, buildings, lakes, or other areas to the map.'
};
+ var keybinding = d3.keybinding('add-area');
+
mode.enter = function() {
var map = mode.map,
history = mode.history,
@@ -38,9 +40,12 @@ iD.modes.AddArea = function() {
controller.enter(iD.modes.DrawArea(way.id));
});
- map.keybinding().on('⎋.addarea', function() {
+ keybinding.on('⎋', function() {
controller.exit();
});
+
+ d3.select(document)
+ .call(keybinding);
};
mode.exit = function() {
@@ -49,7 +54,7 @@ iD.modes.AddArea = function() {
}, 1000);
mode.map.tail(false);
mode.map.surface.on('click.addarea', null);
- mode.map.keybinding().on('⎋.addarea', null);
+ keybinding.off();
};
return mode;
diff --git a/js/id/modes/add_line.js b/js/id/modes/add_line.js
index b83202097..8baa8e30e 100644
--- a/js/id/modes/add_line.js
+++ b/js/id/modes/add_line.js
@@ -6,6 +6,8 @@ iD.modes.AddLine = function() {
description: 'Lines can be highways, streets, pedestrian paths, or even canals.'
};
+ var keybinding = d3.keybinding('add-line');
+
mode.enter = function() {
var map = mode.map,
node,
@@ -38,7 +40,7 @@ iD.modes.AddLine = function() {
} else if (datum.type === 'way') {
// begin a new way starting from an existing way
- var choice = iD.util.geo.chooseIndex(datum, d3.mouse(map.surface.node()), map);
+ var choice = iD.geo.chooseIndex(datum, d3.mouse(map.surface.node()), map);
node = iD.Node({ loc: choice.loc });
history.perform(
@@ -60,16 +62,19 @@ iD.modes.AddLine = function() {
controller.enter(iD.modes.DrawLine(way.id, direction));
});
- map.keybinding().on('⎋.addline', function() {
+ keybinding.on('⎋', function() {
controller.exit();
});
+
+ d3.select(document)
+ .call(keybinding);
};
mode.exit = function() {
mode.map.dblclickEnable(true);
mode.map.tail(false);
mode.map.surface.on('click.addline', null);
- mode.map.keybinding().on('⎋.addline', null);
+ keybinding.off();
};
return mode;
diff --git a/js/id/modes/add_point.js b/js/id/modes/add_point.js
index 82ad283c2..70275d050 100644
--- a/js/id/modes/add_point.js
+++ b/js/id/modes/add_point.js
@@ -5,6 +5,8 @@ iD.modes.AddPoint = function() {
description: 'Restaurants, monuments, and postal boxes are points.'
};
+ var keybinding = d3.keybinding('add-point');
+
mode.enter = function() {
var map = mode.map,
history = mode.history,
@@ -22,15 +24,18 @@ iD.modes.AddPoint = function() {
controller.enter(iD.modes.Select(node, true));
});
- map.keybinding().on('⎋.addpoint', function() {
+ keybinding.on('⎋', function() {
controller.exit();
});
+
+ d3.select(document)
+ .call(keybinding);
};
mode.exit = function() {
mode.map.tail(false);
mode.map.surface.on('click.addpoint', null);
- mode.map.keybinding().on('⎋.addpoint', null);
+ keybinding.off();
};
return mode;
diff --git a/js/id/modes/draw_area.js b/js/id/modes/draw_area.js
index 2f1fa6304..494d1affb 100644
--- a/js/id/modes/draw_area.js
+++ b/js/id/modes/draw_area.js
@@ -4,15 +4,16 @@ iD.modes.DrawArea = function(wayId) {
id: 'draw-area'
};
+ var keybinding = d3.keybinding('draw-area');
+
mode.enter = function() {
var map = mode.map,
surface = map.surface,
history = mode.history,
controller = mode.controller,
way = history.graph().entity(wayId),
- headId = (way.nodes.length == 1) ?
- way.nodes[0] :
- way.nodes[way.nodes.length - 2],
+ index = way.nodes.length - 1,
+ headId = way.nodes[index - 1],
tailId = way.first(),
node = iD.Node({loc: map.mouseCoordinates()});
@@ -22,12 +23,20 @@ iD.modes.DrawArea = function(wayId) {
history.perform(
iD.actions.AddNode(node),
- iD.actions.AddWayNode(way.id, node.id, -1));
+ iD.actions.AddWayNode(way.id, node.id, index));
surface.selectAll('.way, .node')
.filter(function (d) { return d.id === wayId || d.id === node.id; })
.classed('active', true);
+ function ReplaceTemporaryNode(replacementId) {
+ return function(graph) {
+ graph = graph.replace(graph.entity(wayId).updateNode(replacementId, index));
+ graph = graph.remove(node);
+ return graph;
+ }
+ }
+
function mousemove() {
history.replace(iD.actions.MoveNode(node.id, map.mouseCoordinates()));
}
@@ -53,8 +62,7 @@ iD.modes.DrawArea = function(wayId) {
} else if (datum.type === 'node' && datum.id !== node.id) {
// connect the way to an existing node
history.replace(
- iD.actions.DeleteNode(node.id),
- iD.actions.AddWayNode(way.id, datum.id, -1),
+ ReplaceTemporaryNode(datum.id),
way.nodes.length > 2 ? 'added to an area' : '');
controller.enter(iD.modes.DrawArea(wayId));
@@ -75,13 +83,11 @@ iD.modes.DrawArea = function(wayId) {
iD.actions.DeleteNode(node.id),
iD.actions.DeleteNode(headId));
- if (history.graph().fetch(wayId).nodes.length === 2) {
- history.replace(
- iD.actions.DeleteNode(way.nodes[0]),
- iD.actions.DeleteWay(wayId));
- controller.enter(iD.modes.Browse());
- } else {
+ if (history.graph().entity(wayId)) {
controller.enter(iD.modes.DrawArea(wayId));
+ } else {
+ // The way was deleted because it had too few nodes.
+ controller.enter(iD.modes.Browse());
}
}
@@ -93,8 +99,15 @@ iD.modes.DrawArea = function(wayId) {
function ret() {
d3.event.preventDefault();
+
history.replace(iD.actions.DeleteNode(node.id));
- controller.enter(iD.modes.Select(way, true));
+
+ if (history.graph().entity(wayId)) {
+ controller.enter(iD.modes.Select(way, true));
+ } else {
+ // The way was deleted because it had too few nodes.
+ controller.enter(iD.modes.Browse());
+ }
}
surface
@@ -102,31 +115,38 @@ iD.modes.DrawArea = function(wayId) {
.on('mouseover.drawarea', mouseover)
.on('click.drawarea', click);
- map.keybinding()
- .on('⌫.drawarea', backspace)
- .on('⌦.drawarea', del)
- .on('⎋.drawarea', ret)
- .on('↩.drawarea', ret);
+ keybinding
+ .on('⌫', backspace)
+ .on('⌦', del)
+ .on('⎋', ret)
+ .on('↩', ret);
+
+ d3.select(document)
+ .call(keybinding);
+
+ history.on('undone.drawarea', function () {
+ controller.enter(iD.modes.Browse());
+ });
};
mode.exit = function() {
- var surface = mode.map.surface;
+ var map = mode.map,
+ surface = map.surface,
+ history = mode.history;
surface.selectAll('.way, .node')
.classed('active', false);
- mode.map.tail(false);
- mode.map.fastEnable(true);
+ map.tail(false);
+ map.fastEnable(true);
surface
.on('mousemove.drawarea', null)
.on('click.drawarea', null);
- mode.map.keybinding()
- .on('⎋.drawarea', null)
- .on('⌫.drawarea', null)
- .on('⌦.drawarea', null)
- .on('↩.drawarea', null);
+ keybinding.off();
+
+ history.on('undone.drawarea', null);
window.setTimeout(function() {
mode.map.dblclickEnable(true);
diff --git a/js/id/modes/draw_line.js b/js/id/modes/draw_line.js
index 8442fbf80..b8fabeca5 100644
--- a/js/id/modes/draw_line.js
+++ b/js/id/modes/draw_line.js
@@ -4,6 +4,8 @@ iD.modes.DrawLine = function(wayId, direction) {
id: 'draw-line'
};
+ var keybinding = d3.keybinding('draw-line');
+
mode.enter = function() {
var map = mode.map,
surface = map.surface,
@@ -11,7 +13,7 @@ iD.modes.DrawLine = function(wayId, direction) {
controller = mode.controller,
way = history.graph().entity(wayId),
node = iD.Node({loc: map.mouseCoordinates()}),
- index = (direction === 'forward') ? undefined : 0,
+ index = (direction === 'forward') ? way.nodes.length : 0,
headId = (direction === 'forward') ? way.last() : way.first(),
tailId = (direction === 'forward') ? way.first() : way.last();
@@ -33,6 +35,14 @@ iD.modes.DrawLine = function(wayId, direction) {
.filter(function (d) { return d.id === wayId || d.id === node.id; })
.classed('active', true);
+ function ReplaceTemporaryNode(replacementId) {
+ return function(graph) {
+ graph = graph.replace(graph.entity(wayId).updateNode(replacementId, index));
+ graph = graph.remove(node);
+ return graph;
+ }
+ }
+
function mousemove() {
history.replace(iD.actions.MoveNode(node.id, map.mouseCoordinates()));
}
@@ -44,8 +54,7 @@ iD.modes.DrawLine = function(wayId, direction) {
// connect the way in a loop
if (way.nodes.length > 2) {
history.replace(
- iD.actions.DeleteNode(node.id),
- iD.actions.AddWayNode(wayId, tailId, index),
+ ReplaceTemporaryNode(tailId),
'added to a line');
controller.enter(iD.modes.Select(way, true));
@@ -64,8 +73,7 @@ iD.modes.DrawLine = function(wayId, direction) {
} else if (datum.type === 'node' && datum.id !== node.id) {
// connect the way to an existing node
history.replace(
- iD.actions.DeleteNode(node.id),
- iD.actions.AddWayNode(wayId, datum.id, index),
+ ReplaceTemporaryNode(datum.id),
'added to a line');
controller.enter(iD.modes.DrawLine(wayId, direction));
@@ -78,7 +86,7 @@ iD.modes.DrawLine = function(wayId, direction) {
datum.id = datum.way;
choice = datum;
} else {
- choice = iD.util.geo.chooseIndex(datum, d3.mouse(surface.node()), map);
+ choice = iD.geo.chooseIndex(datum, d3.mouse(surface.node()), map);
}
history.replace(
@@ -104,11 +112,11 @@ iD.modes.DrawLine = function(wayId, direction) {
iD.actions.DeleteNode(node.id),
iD.actions.DeleteNode(headId));
- if (history.graph().fetch(wayId).nodes.length === 0) {
- history.replace(iD.actions.DeleteWay(wayId));
- controller.enter(iD.modes.Browse());
- } else {
+ if (history.graph().entity(wayId)) {
controller.enter(iD.modes.DrawLine(wayId, direction));
+ } else {
+ // The way was deleted because it had too few nodes.
+ controller.enter(iD.modes.Browse());
}
}
@@ -120,53 +128,54 @@ iD.modes.DrawLine = function(wayId, direction) {
function ret() {
d3.event.preventDefault();
- history.replace(iD.actions.DeleteNode(node.id));
- controller.enter(iD.modes.Select(way, true));
- }
- function undo() {
- history.undo();
- controller.enter(iD.modes.Browse());
+ history.replace(iD.actions.DeleteNode(node.id));
+
+ if (history.graph().entity(wayId)) {
+ controller.enter(iD.modes.Select(way, true));
+ } else {
+ // The way was deleted because it had too few nodes.
+ controller.enter(iD.modes.Browse());
+ }
}
surface
.on('mousemove.drawline', mousemove)
.on('click.drawline', click);
- map.keybinding()
- .on('⌫.drawline', backspace)
- .on('⌦.drawline', del)
- .on('⎋.drawline', ret)
- .on('↩.drawline', ret)
- .on('z.drawline', function(evt, mods) {
- if (mods === '⌘' || mods === '⌃') undo();
- });
+ keybinding
+ .on('⌫', backspace)
+ .on('⌦', del)
+ .on('⎋', ret)
+ .on('↩', ret);
- d3.select('#undo').on('click.drawline', undo);
+ d3.select(document)
+ .call(keybinding);
+
+ history.on('undone.drawline', function () {
+ controller.enter(iD.modes.Browse());
+ });
};
mode.exit = function() {
- var surface = mode.map.surface;
+ var map = mode.map,
+ surface = map.surface,
+ history = mode.history;
surface.selectAll('.way, .node')
.classed('active', false);
- mode.map.tail(false);
- mode.map.fastEnable(true);
- mode.map.minzoom(0);
+ map.tail(false);
+ map.fastEnable(true);
+ map.minzoom(0);
surface
.on('mousemove.drawline', null)
.on('click.drawline', null);
- mode.map.keybinding()
- .on('⌫.drawline', null)
- .on('⌦.drawline', null)
- .on('⎋.drawline', null)
- .on('↩.drawline', null)
- .on('z.drawline', null);
+ keybinding.off();
- d3.select('#undo').on('click.drawline', null);
+ history.on('undone.drawline', null);
window.setTimeout(function() {
mode.map.dblclickEnable(true);
diff --git a/js/id/modes/select.js b/js/id/modes/select.js
index 883acacb1..dc298e36a 100644
--- a/js/id/modes/select.js
+++ b/js/id/modes/select.js
@@ -6,6 +6,7 @@ iD.modes.Select = function(entity, initial) {
};
var inspector = iD.ui.inspector().initial(!!initial),
+ keybinding = d3.keybinding('select'),
behaviors;
function remove() {
@@ -14,13 +15,9 @@ iD.modes.Select = function(entity, initial) {
iD.actions.DeleteWay(entity.id),
'deleted a way');
} else if (entity.type === 'node') {
- var parents = mode.history.graph().parentWays(entity),
- operations = [iD.actions.DeleteNode(entity.id)];
- parents.forEach(function(parent) {
- if (_.uniq(parent.nodes).length === 1) operations.push(iD.actions.DeleteWay(parent.id));
- });
- mode.history.perform.apply(mode.history,
- operations.concat(['deleted a node']));
+ mode.history.perform(
+ iD.actions.DeleteNode(entity.id),
+ 'deleted a node');
}
mode.controller.exit();
@@ -48,9 +45,9 @@ iD.modes.Select = function(entity, initial) {
});
var q = iD.util.stringQs(location.hash.substring(1));
- location.hash = '#' + iD.util.qsString(_.assign(q, {
+ location.replace('#' + iD.util.qsString(_.assign(q, {
id: entity.id
- }), true);
+ }), true));
d3.select('.inspector-wrap')
.style('display', 'block')
@@ -74,7 +71,7 @@ iD.modes.Select = function(entity, initial) {
inspector
.on('changeTags', changeTags)
- .on('changeWayDirection', function(d) {
+ .on('reverseWay', function(d) {
mode.history.perform(
iD.actions.ReverseWay(d.id),
'reversed a way');
@@ -84,6 +81,11 @@ iD.modes.Select = function(entity, initial) {
iD.actions.SplitWay(d.id),
'split a way');
+ }).on('unjoin', function(d) {
+ mode.history.perform(
+ iD.actions.UnjoinNode(d.id),
+ 'unjoined ways');
+
}).on('remove', function() {
remove();
@@ -115,7 +117,7 @@ iD.modes.Select = function(entity, initial) {
var datum = d3.select(d3.event.target).datum();
if (datum instanceof iD.Entity &&
(datum.geometry() === 'area' || datum.geometry() === 'line')) {
- var choice = iD.util.geo.chooseIndex(datum,
+ var choice = iD.geo.chooseIndex(datum,
d3.mouse(mode.map.surface.node()), mode.map),
node = iD.Node({ loc: choice.loc });
@@ -130,12 +132,12 @@ iD.modes.Select = function(entity, initial) {
}
surface.on('click.select', click)
- .on('dblclick.browse', dblclick);
+ .on('dblclick.select', dblclick);
- mode.map.keybinding().on('⌫.select', function(e) {
- remove();
- e.preventDefault();
- });
+ keybinding.on('⌫', remove);
+
+ d3.select(document)
+ .call(keybinding);
surface.selectAll("*")
.filter(function (d) {
@@ -164,10 +166,13 @@ iD.modes.Select = function(entity, initial) {
});
var q = iD.util.stringQs(location.hash.substring(1));
- location.hash = '#' + iD.util.qsString(_.omit(q, 'id'), true);
+ location.replace('#' + iD.util.qsString(_.omit(q, 'id'), true));
+
+ keybinding.off();
+
+ surface.on('click.select', null)
+ .on('dblclick.select', null);
- surface.on("click.select", null);
- mode.map.keybinding().on('⌫.select', null);
mode.history.on('change.entity-undone', null);
surface.selectAll(".selected")
diff --git a/js/id/renderer/background.js b/js/id/renderer/background.js
index f861d2226..c63362356 100644
--- a/js/id/renderer/background.js
+++ b/js/id/renderer/background.js
@@ -38,6 +38,12 @@ iD.Background = function() {
return Math.ceil(256 * Math.pow(2, z - d[2])) / 256;
}
+ function lookUp(d) {
+ for (var up = -1; up > -d[2]; up--) {
+ if (cache[atZoom(d, up)] !== false) return atZoom(d, up);
+ }
+ }
+
// derive the tiles onscreen, remove those offscreen and position tiles
// correctly for the currentstate of `projection`
function background() {
@@ -70,16 +76,13 @@ iD.Background = function() {
// if this tile has not finished, req the one above
} else if (cache[d] === undefined &&
+ lookUp(d)) {
- // but the tile above is in the cache
- cache[atZoom(d, -1)] &&
-
- // and another tile has not already requested the
- // tile above
- !ups[atZoom(d, -1)]) {
-
- ups[atZoom(d, -1)] = true;
- tiles.push(atZoom(d, -1));
+ var upTile = lookUp(d);
+ if (!ups[upTile]) {
+ ups[upTile] = true;
+ tiles.push(upTile);
+ }
// if this tile has not yet completed, try keeping the
// tiles below it
@@ -98,8 +101,6 @@ iD.Background = function() {
.selectAll('img')
.data(tiles, function(d) { return d; });
- image.exit().remove();
-
function load(d) {
cache[d.slice(0, 3)] = true;
d3.select(this).on('load', null);
@@ -116,6 +117,8 @@ iD.Background = function() {
.on('error', error)
.on('load', load);
+ image.exit().remove();
+
image.style(transformProp, function(d) {
var _ts = 256 * Math.pow(2, z - d[2]);
var scale = tileSize(d, z);
diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js
index af43a2957..100dc37fb 100644
--- a/js/id/renderer/map.js
+++ b/js/id/renderer/map.js
@@ -2,7 +2,6 @@ iD.Map = function() {
var connection, history,
dimensions = [],
dispatch = d3.dispatch('move', 'drawn'),
- keybinding = d3.keybinding(),
projection = d3.geo.mercator().scale(1024),
roundedProjection = iD.svg.RoundProjection(projection),
zoom = d3.behavior.zoom()
@@ -12,6 +11,7 @@ iD.Map = function() {
.on('zoom', zoomPan),
dblclickEnabled = true,
fastEnabled = true,
+ transformStart,
minzoom = 0,
background = iD.Background()
.projection(projection),
@@ -49,8 +49,6 @@ iD.Map = function() {
supersurface
.call(tail);
-
- d3.select(document).call(keybinding);
}
function pxCenter() { return [dimensions[0] / 2, dimensions[1] / 2]; }
@@ -274,9 +272,10 @@ iD.Map = function() {
map.centerEase = function(loc) {
var from = map.center().slice(), t = 0;
d3.timer(function() {
- map.center(iD.util.geo.interp(from, loc, (t += 1) / 10));
+ map.center(iD.geo.interp(from, loc, (t += 1) / 10));
return t == 10;
}, 20);
+ return map;
};
map.extent = function(_) {
@@ -346,12 +345,6 @@ iD.Map = function() {
return map;
};
- map.keybinding = function (_) {
- if (!arguments.length) return keybinding;
- keybinding = _;
- return map;
- };
-
map.background = background;
map.projection = projection;
map.redraw = redraw;
diff --git a/js/id/svg.js b/js/id/svg.js
index e2b1dc2ad..a3b174ddb 100644
--- a/js/id/svg.js
+++ b/js/id/svg.js
@@ -1,7 +1,7 @@
iD.svg = {
RoundProjection: function (projection) {
return function (d) {
- return iD.util.geo.roundCoords(projection(d));
+ return iD.geo.roundCoords(projection(d));
};
},
diff --git a/js/id/svg/midpoints.js b/js/id/svg/midpoints.js
index 7c7c7a0c3..a04526f83 100644
--- a/js/id/svg/midpoints.js
+++ b/js/id/svg/midpoints.js
@@ -11,9 +11,9 @@ iD.svg.Midpoints = function(projection) {
for (var j = 0; j < entity.nodes.length - 1; j++) {
var a = projection(entity.nodes[j].loc);
var b = projection(entity.nodes[j + 1].loc);
- if (iD.util.geo.dist(a, b) > 40) {
+ if (iD.geo.dist(a, b) > 40) {
midpoints.push({
- loc: iD.util.geo.interp(entity.nodes[j].loc, entity.nodes[j + 1].loc, 0.5),
+ loc: iD.geo.interp(entity.nodes[j].loc, entity.nodes[j + 1].loc, 0.5),
way: entity.id,
index: j + 1,
midpoint: true
diff --git a/js/id/ui/flash.js b/js/id/ui/flash.js
index 4ce6a3b0a..1e4d7379d 100644
--- a/js/id/ui/flash.js
+++ b/js/id/ui/flash.js
@@ -1,7 +1,7 @@
iD.ui.flash = function() {
var modal = iD.ui.modal();
- modal.select('.modal').classed('modal-alert', true);
+ modal.select('.modal').classed('modal-flash', true);
modal.select('.content')
.classed('modal-section', true)
@@ -13,7 +13,7 @@ iD.ui.flash = function() {
setTimeout(function() {
modal.remove();
return true;
- }, 1000);
+ }, 1500);
return modal;
};
diff --git a/js/id/ui/geocoder.js b/js/id/ui/geocoder.js
index ba3e0db2f..9a9c1e1d3 100644
--- a/js/id/ui/geocoder.js
+++ b/js/id/ui/geocoder.js
@@ -13,6 +13,7 @@ iD.ui.geocoder = function() {
if (!resp.results.length) {
return iD.ui.flash()
.select('.content')
+ .append('h3')
.text('No location found for "' + resp.query[0] + '"');
}
var bounds = resp.results[0][0].bounds;
diff --git a/js/id/ui/inspector.js b/js/id/ui/inspector.js
index 7837ba21f..22fac522d 100644
--- a/js/id/ui/inspector.js
+++ b/js/id/ui/inspector.js
@@ -1,6 +1,6 @@
iD.ui.inspector = function() {
- var event = d3.dispatch('changeTags', 'changeWayDirection',
- 'update', 'remove', 'close', 'splitWay'),
+ var event = d3.dispatch('changeTags', 'reverseWay',
+ 'update', 'remove', 'close', 'splitWay', 'unjoin'),
taginfo = iD.taginfo(),
initial = false,
tagList;
@@ -58,8 +58,10 @@ iD.ui.inspector = function() {
function drawButtons(selection) {
var entity = selection.datum();
+
var inspectorButtonWrap = selection.append('div')
.attr('class','button-wrap joined fl');
+
var inspectorButton1 = inspectorButtonWrap.append('button')
.attr('class', 'apply col6 action')
.on('click', apply);
@@ -80,17 +82,24 @@ iD.ui.inspector = function() {
.attr('href', 'http://www.openstreetmap.org/browse/' + entity.type + '/' + entity.osmId())
.attr('target', '_blank')
.text('View on OSM');
+
if (entity.type === 'way') {
minorButtons.append('a')
.attr('href', '#')
.text('Reverse Direction')
- .on('click', function() { event.changeWayDirection(entity); });
+ .on('click', function() { event.reverseWay(entity); });
}
+
if (entity.geometry() === 'vertex') {
minorButtons.append('a')
.attr('href', '#')
.text('Split Way')
.on('click', function() { event.splitWay(entity); });
+
+ minorButtons.append('a')
+ .attr('href', '#')
+ .text('Unjoin')
+ .on('click', function() { event.unjoin(entity); });
}
}
@@ -169,6 +178,7 @@ iD.ui.inspector = function() {
} else {
iD.ui.flash()
.select('.content')
+ .append('h3')
.text('This is no documentation available for this tag combination');
}
});
@@ -186,6 +196,7 @@ iD.ui.inspector = function() {
} else {
iD.ui.flash()
.select('.content')
+ .append('h3')
.text('This is no documentation available for this key');
}
});
diff --git a/js/id/ui/loading.js b/js/id/ui/loading.js
index 6a98a917a..23d5b7c2d 100644
--- a/js/id/ui/loading.js
+++ b/js/id/ui/loading.js
@@ -1,5 +1,5 @@
-iD.ui.loading = function(message) {
- var modal = iD.ui.modal();
+iD.ui.loading = function(message, blocking) {
+ var modal = iD.ui.modal(blocking);
var loadertext = modal.select('.content')
.classed('loading-modal', true)
diff --git a/js/id/ui/modal.js b/js/id/ui/modal.js
index fed97a0a1..498f1e241 100644
--- a/js/id/ui/modal.js
+++ b/js/id/ui/modal.js
@@ -1,4 +1,4 @@
-iD.ui.modal = function() {
+iD.ui.modal = function(blocking) {
var animate = d3.select('div.modal').empty();
d3.select('div.modal').transition()
@@ -9,7 +9,7 @@ iD.ui.modal = function() {
.attr('class', 'shaded')
.style('opacity', 0)
.on('click.remove-modal', function() {
- if (d3.event.target == this) d3.select(this).remove();
+ if (d3.event.target == this && !blocking) d3.select(this).remove();
});
var modal = shaded.append('div')
@@ -18,7 +18,7 @@ iD.ui.modal = function() {
modal.append('button')
.attr('class', 'icon remove close-modal')
.on('click', function() {
- shaded.remove();
+ if (!blocking) shaded.remove();
});
modal.append('div')
diff --git a/js/id/ui/notice.js b/js/id/ui/notice.js
index 1c87c5553..67838c31e 100644
--- a/js/id/ui/notice.js
+++ b/js/id/ui/notice.js
@@ -2,21 +2,40 @@ iD.ui.notice = function(selection) {
var message = '',
notice = {};
+ var div = selection.append('div')
+ .attr('class', 'notice')
+ .style('display', 'none');
+
+ var txt = div.append('div')
+ .attr('class', 'notice-text');
+
+ function replace(a, b) {
+ a.style('opacity', 1)
+ .transition()
+ .each('end', function() {
+ a.style('display', 'none');
+ b.style('display', 'inline-block')
+ .style('opacity', 0)
+ .transition()
+ .style('opacity', 1);
+ })
+ .style('opacity', 0);
+ }
+
notice.message = function(_) {
- selection.attr('class', 'notice inner');
+ div.attr('class', 'notice inner');
if (!arguments.length) return _;
if (!message && _) {
- selection
- .text(_)
- .transition()
- .style('display', '');
+ txt.text(_);
+ replace(selection.select('.button-wrap'), div);
} else if (_ && message !== _) {
- selection.text(_);
+ txt.text(_);
+ selection.select('.button-wrap').style('display', 'none');
} else if (!_) {
- selection
- .text('')
- .transition()
- .style('display', 'none');
+ txt.text('');
+ if (message) {
+ replace(div, selection.select('.button-wrap'));
+ }
}
message = _;
return notice;
diff --git a/js/id/ui/save.js b/js/id/ui/save.js
index afaab5023..c0036d334 100644
--- a/js/id/ui/save.js
+++ b/js/id/ui/save.js
@@ -14,60 +14,61 @@ iD.ui.save = function() {
.placement('bottom'))
.on('click', function() {
- function commit(e) {
- d3.select('.shaded').remove();
- var l = iD.ui.loading('Uploading changes to OpenStreetMap.');
- connection.putChangeset(history.changes(), e.comment, history.imagery_used(), function(err, changeset_id) {
- l.remove();
- history.reset();
- map.flush().redraw();
- if (err) {
- var desc = iD.ui.confirm()
- .select('.description');
- desc.append('h2')
- .text('An error occurred while trying to save');
- desc.append('p').text(err.responseText);
- } else {
- var modal = iD.ui.modal();
- modal.select('.content')
- .classed('success-modal', true)
- .datum({
- id: changeset_id,
- comment: e.comment
- })
- .call(iD.ui.success()
- .on('cancel', function() {
- modal.remove();
- }));
- }
- });
- }
-
- if (history.hasChanges()) {
- connection.authenticate(function(err) {
+ function commit(e) {
+ d3.select('.shaded').remove();
+ var l = iD.ui.loading('Uploading changes to OpenStreetMap.', true);
+ connection.putChangeset(history.changes(), e.comment, history.imagery_used(), function(err, changeset_id) {
+ l.remove();
+ history.reset();
+ map.flush().redraw();
+ if (err) {
+ var desc = iD.ui.confirm()
+ .select('.description');
+ desc.append('h2')
+ .text('An error occurred while trying to save');
+ desc.append('p').text(err.responseText);
+ } else {
var modal = iD.ui.modal();
- var changes = history.changes();
- changes.connection = connection;
modal.select('.content')
- .classed('commit-modal', true)
- .datum(changes)
- .call(iD.ui.commit()
+ .classed('success-modal', true)
+ .datum({
+ id: changeset_id,
+ comment: e.comment
+ })
+ .call(iD.ui.success()
.on('cancel', function() {
modal.remove();
- })
- .on('fix', function(d) {
- map.extent(d.entity.extent(map.history().graph()));
- if (map.zoom() > 19) map.zoom(19);
- controller.enter(iD.modes.Select(d.entity));
- modal.remove();
- })
- .on('save', commit));
- });
- } else {
- iD.ui.confirm().select('.description')
- .append('h3').text('You don\'t have any changes to save.');
- }
- });
+ }));
+ }
+ });
+ }
+
+ if (history.hasChanges()) {
+ connection.authenticate(function(err) {
+ var modal = iD.ui.modal();
+ var changes = history.changes();
+ changes.connection = connection;
+ modal.select('.content')
+ .classed('commit-modal', true)
+ .datum(changes)
+ .call(iD.ui.commit()
+ .on('cancel', function() {
+ modal.remove();
+ })
+ .on('fix', function(d) {
+ map.extent(d.entity.extent(map.history().graph()));
+ if (map.zoom() > 19) map.zoom(19);
+ controller.enter(iD.modes.Select(d.entity));
+ modal.remove();
+ })
+ .on('save', commit));
+ });
+ } else {
+ iD.ui.confirm().select('.description')
+ .append('h3').text('You don\'t have any changes to save.');
+ }
+
+ });
selection.append('span')
.attr('class', 'count');
diff --git a/js/lib/d3.keybinding.js b/js/lib/d3.keybinding.js
index 01256aa20..e16ade725 100644
--- a/js/lib/d3.keybinding.js
+++ b/js/lib/d3.keybinding.js
@@ -1,120 +1,197 @@
-d3.keybinding = function() {
- // via https://github.com/keithamus/jwerty/
- // and https://github.com/madrobby/keymaster
- var _keys = {
- // MOD aka toggleable keys
- mods: {
- // Shift key, ⇧
- '⇧': 16,
- // CTRL key, on Mac: ⌃
- '⌃': 17,
- // ALT key, on Mac: ⌥ (Alt)
- '⌥': 18,
- // META, on Mac: ⌘ (CMD), on Windows (Win), on Linux (Super)
- '⌘': 91
- },
- // Normal keys
- keys: {
- // Backspace key, on Mac: ⌫ (Backspace)
- '⌫': 8, backspace: 8,
- // Tab Key, on Mac: ⇥ (Tab), on Windows ⇥⇥
- '⇥': 9, '⇆': 9, tab: 9,
- // Return key, ↩
- '↩': 13, 'return': 13, enter: 13, '⌅': 13,
- // Pause/Break key
- 'pause': 19, 'pause-break': 19,
- // Caps Lock key, ⇪
- '⇪': 20, caps: 20, 'caps-lock': 20,
- // Escape key, on Mac: ⎋, on Windows: Esc
- '⎋': 27, escape: 27, esc: 27,
- // Space key
- space: 32,
- // Page-Up key, or pgup, on Mac: ↖
- '↖': 33, pgup: 33, 'page-up': 33,
- // Page-Down key, or pgdown, on Mac: ↘
- '↘': 34, pgdown: 34, 'page-down': 34,
- // END key, on Mac: ⇟
- '⇟': 35, end: 35,
- // HOME key, on Mac: ⇞
- '⇞': 36, home: 36,
- // Insert key, or ins
- ins: 45, insert: 45,
- // Delete key, on Mac: ⌦ (Delete)
- '⌦': 46, del: 46, 'delete': 46,
- // Left Arrow Key, or ←
- '←': 37, left: 37, 'arrow-left': 37,
- // Up Arrow Key, or ↑
- '↑': 38, up: 38, 'arrow-up': 38,
- // Right Arrow Key, or →
- '→': 39, right: 39, 'arrow-right': 39,
- // Up Arrow Key, or ↓
- '↓': 40, down: 40, 'arrow-down': 40,
- // odities, printing characters that come out wrong:
- // Num-Multiply, or *
- '*': 106, star: 106, asterisk: 106, multiply: 106,
- // Num-Plus or +
- '+': 107, 'plus': 107,
- // Num-Subtract, or -
- '-': 109, subtract: 109,
- // Semicolon
- ';': 186, semicolon:186,
- // = or equals
- '=': 187, 'equals': 187,
- // Comma, or ,
- ',': 188, comma: 188,
- //'-': 189, //???
- // Period, or ., or full-stop
- '.': 190, period: 190, 'full-stop': 190,
- // Slash, or /, or forward-slash
- '/': 191, slash: 191, 'forward-slash': 191,
- // Tick, or `, or back-quote
- '`': 192, tick: 192, 'back-quote': 192,
- // Open bracket, or [
- '[': 219, 'open-bracket': 219,
- // Back slash, or \
- '\\': 220, 'back-slash': 220,
- // Close backet, or ]
- ']': 221, 'close-bracket': 221,
- // Apostraphe, or Quote, or '
- '\'': 222, quote: 222, apostraphe: 222
+/*
+ * This code is licensed under the MIT license.
+ *
+ * Copyright © 2013, iD authors.
+ *
+ * Portions copyright © 2011, Keith Cirkel
+ * See https://github.com/keithamus/jwerty
+ *
+ */
+d3.keybinding = function(namespace) {
+ var bindings = [];
+
+ function matches(binding, event) {
+ for (var p in binding.event) {
+ if (event[p] != binding.event[p])
+ return false;
}
- };
- // To minimise code bloat, add all of the NUMPAD 0-9 keys in a loop
- var i = 95, n = 0;
- while (++i < 106) _keys.keys['num-' + n] = i; ++n;
- // To minimise code bloat, add all of the top row 0-9 keys in a loop
- i = 47, n = 0;
- while (++i < 58) _keys.keys[n] = i; ++n;
- // To minimise code bloat, add all of the F1-F25 keys in a loop
- i = 111, n = 1;
- while (++i < 136) _keys.keys['f' + n] = i; ++n;
- // To minimise code bloat, add all of the letters of the alphabet in a loop
- i = 64;
- while(++i < 91) _keys.keys[String.fromCharCode(i).toLowerCase()] = i;
- var pairs = d3.entries(_keys.keys),
- event = d3.dispatch.apply(d3, d3.keys(_keys.keys));
-
- function keys(selection) {
- selection.on('keydown', function () {
- var tagName = d3.select(d3.event.target).node().tagName;
- if (tagName == 'INPUT' || tagName == 'SELECT' || tagName == 'TEXTAREA') {
- return;
- }
-
- var modifiers = '';
- if (d3.event.shiftKey) modifiers += '⇧';
- if (d3.event.ctrlKey) modifiers += '⌃';
- if (d3.event.altKey) modifiers += '⌥';
- if (d3.event.metaKey) modifiers += '⌘';
-
- pairs.filter(function(d) {
- return d.value === d3.event.keyCode;
- }).forEach(function(d) {
- event[d.key](d3.event, modifiers);
- });
- });
+ return (!binding.capture) === (event.eventPhase !== Event.CAPTURING_PHASE);
}
- return d3.rebind(keys, event, 'on');
+ function capture() {
+ for (var i = 0; i < bindings.length; i++) {
+ var binding = bindings[i];
+ if (matches(binding, d3.event)) {
+ binding.callback();
+ }
+ }
+ }
+
+ function bubble() {
+ var tagName = d3.select(d3.event.target).node().tagName;
+ if (tagName == 'INPUT' || tagName == 'SELECT' || tagName == 'TEXTAREA') {
+ return;
+ }
+ capture();
+ }
+
+ function keybinding(selection) {
+ selection = selection || d3.select(document);
+ selection.on('keydown.capture' + namespace, capture, true);
+ selection.on('keydown.bubble' + namespace, bubble, false);
+ return keybinding;
+ }
+
+ keybinding.off = function(selection) {
+ selection = selection || d3.select(document);
+ selection.on('keydown.capture' + namespace, null);
+ selection.on('keydown.bubble' + namespace, null);
+ return keybinding;
+ };
+
+ keybinding.on = function(code, callback, capture) {
+ var binding = {
+ event: {
+ keyCode: 0,
+ shiftKey: false,
+ ctrlKey: false,
+ altKey: false,
+ metaKey: false
+ },
+ capture: capture,
+ callback: callback
+ };
+
+ code = code.toLowerCase().match(/(?:(?:[^+])+|\+\+|^\+$)/g);
+
+ for (var i = 0; i < code.length; i++) {
+ // Normalise matching errors
+ if (code[i] === '++') code[i] = '+';
+
+ if (code[i] in d3.keybinding.modifierCodes) {
+ binding.event[d3.keybinding.modifierProperties[d3.keybinding.modifierCodes[code[i]]]] = true;
+ } else if (code[i] in d3.keybinding.keyCodes) {
+ binding.event.keyCode = d3.keybinding.keyCodes[code[i]];
+ }
+ }
+
+ bindings.push(binding);
+
+ return keybinding;
+ };
+
+ return keybinding;
};
+
+(function () {
+ d3.keybinding.modifierCodes = {
+ // Shift key, ⇧
+ '⇧': 16, shift: 16,
+ // CTRL key, on Mac: ⌃
+ '⌃': 17, ctrl: 17,
+ // ALT key, on Mac: ⌥ (Alt)
+ '⌥': 18, alt: 18, option: 18,
+ // META, on Mac: ⌘ (CMD), on Windows (Win), on Linux (Super)
+ '⌘': 91, meta: 91, cmd: 91, 'super': 91, win: 91
+ };
+
+ d3.keybinding.modifierProperties = {
+ 16: 'shiftKey',
+ 17: 'ctrlKey',
+ 18: 'altKey',
+ 91: 'metaKey'
+ };
+
+ d3.keybinding.keyCodes = {
+ // Backspace key, on Mac: ⌫ (Backspace)
+ '⌫': 8, backspace: 8,
+ // Tab Key, on Mac: ⇥ (Tab), on Windows ⇥⇥
+ '⇥': 9, '⇆': 9, tab: 9,
+ // Return key, ↩
+ '↩': 13, 'return': 13, enter: 13, '⌅': 13,
+ // Pause/Break key
+ 'pause': 19, 'pause-break': 19,
+ // Caps Lock key, ⇪
+ '⇪': 20, caps: 20, 'caps-lock': 20,
+ // Escape key, on Mac: ⎋, on Windows: Esc
+ '⎋': 27, escape: 27, esc: 27,
+ // Space key
+ space: 32,
+ // Page-Up key, or pgup, on Mac: ↖
+ '↖': 33, pgup: 33, 'page-up': 33,
+ // Page-Down key, or pgdown, on Mac: ↘
+ '↘': 34, pgdown: 34, 'page-down': 34,
+ // END key, on Mac: ⇟
+ '⇟': 35, end: 35,
+ // HOME key, on Mac: ⇞
+ '⇞': 36, home: 36,
+ // Insert key, or ins
+ ins: 45, insert: 45,
+ // Delete key, on Mac: ⌦ (Delete)
+ '⌦': 46, del: 46, 'delete': 46,
+ // Left Arrow Key, or ←
+ '←': 37, left: 37, 'arrow-left': 37,
+ // Up Arrow Key, or ↑
+ '↑': 38, up: 38, 'arrow-up': 38,
+ // Right Arrow Key, or →
+ '→': 39, right: 39, 'arrow-right': 39,
+ // Up Arrow Key, or ↓
+ '↓': 40, down: 40, 'arrow-down': 40,
+ // odities, printing characters that come out wrong:
+ // Num-Multiply, or *
+ '*': 106, star: 106, asterisk: 106, multiply: 106,
+ // Num-Plus or +
+ '+': 107, 'plus': 107,
+ // Num-Subtract, or -
+ '-': 109, subtract: 109,
+ // Semicolon
+ ';': 186, semicolon:186,
+ // = or equals
+ '=': 187, 'equals': 187,
+ // Comma, or ,
+ ',': 188, comma: 188,
+ //'-': 189, //???
+ // Period, or ., or full-stop
+ '.': 190, period: 190, 'full-stop': 190,
+ // Slash, or /, or forward-slash
+ '/': 191, slash: 191, 'forward-slash': 191,
+ // Tick, or `, or back-quote
+ '`': 192, tick: 192, 'back-quote': 192,
+ // Open bracket, or [
+ '[': 219, 'open-bracket': 219,
+ // Back slash, or \
+ '\\': 220, 'back-slash': 220,
+ // Close backet, or ]
+ ']': 221, 'close-bracket': 221,
+ // Apostrophe, or Quote, or '
+ '\'': 222, quote: 222, apostrophe: 222
+ };
+
+ // NUMPAD 0-9
+ var i = 95, n = 0;
+ while (++i < 106) {
+ d3.keybinding.keyCodes['num-' + n] = i;
+ ++n;
+ }
+
+ // 0-9
+ i = 47; n = 0;
+ while (++i < 58) {
+ d3.keybinding.keyCodes[n] = i;
+ ++n;
+ }
+
+ // F1-F25
+ i = 111; n = 1;
+ while (++i < 136) {
+ d3.keybinding.keyCodes['f' + n] = i;
+ ++n;
+ }
+
+ // a-z
+ i = 64;
+ while (++i < 91) {
+ d3.keybinding.keyCodes[String.fromCharCode(i).toLowerCase()] = i;
+ }
+})();
diff --git a/test/index.html b/test/index.html
index 425258d20..e7e6701a6 100644
--- a/test/index.html
+++ b/test/index.html
@@ -66,7 +66,6 @@
-
@@ -75,11 +74,9 @@
-
-
-
+
@@ -124,21 +121,19 @@
+
+
-
-
-
-
-
+
diff --git a/test/index_packaged.html b/test/index_packaged.html
index 8def4c0da..5a013b45c 100644
--- a/test/index_packaged.html
+++ b/test/index_packaged.html
@@ -30,8 +30,9 @@
+
+
-
@@ -40,11 +41,9 @@
-
-
-
+
@@ -76,6 +75,11 @@
+
+
+
+
+
diff --git a/test/lib/happen.js b/test/lib/happen.js
index 7c46d238d..2f7f5e6cb 100644
--- a/test/lib/happen.js
+++ b/test/lib/happen.js
@@ -19,10 +19,10 @@
evt = new Event(o.type);
evt.keyCode = o.keyCode || 0;
evt.charCode = o.charCode || 0;
- evt.shift = o.shift || false;
- evt.meta = o.meta || false;
- evt.ctrl = o.ctrl || false;
- evt.alt = o.alt || false;
+ evt.shiftKey = o.shiftKey || false;
+ evt.metaKey = o.metaKey || false;
+ evt.ctrlKey = o.ctrlKey || false;
+ evt.altKey = o.altKey || false;
} else {
evt = document.createEvent('KeyboardEvent');
// https://developer.mozilla.org/en/DOM/event.initKeyEvent
@@ -33,13 +33,30 @@
true, // in boolean canBubbleArg,
true, // in boolean cancelableArg,
null, // in nsIDOMAbstractView viewArg, Specifies UIEvent.view. This value may be null.
- o.ctrl || false, // in boolean ctrlKeyArg,
- o.alt || false, // in boolean altKeyArg,
- o.shift || false, // in boolean shiftKeyArg,
- o.meta || false, // in boolean metaKeyArg,
+ o.ctrlKey || false, // in boolean ctrlKeyArg,
+ o.altKey || false, // in boolean altKeyArg,
+ o.shiftKey || false, // in boolean shiftKeyArg,
+ o.metaKey || false, // in boolean metaKeyArg,
o.keyCode || 0, // in unsigned long keyCodeArg,
o.charCode || 0 // in unsigned long charCodeArg);
);
+
+ // Workaround for https://bugs.webkit.org/show_bug.cgi?id=16735
+ if (evt.ctrlKey != (o.ctrlKey || 0) ||
+ evt.altKey != (o.altKey || 0) ||
+ evt.shiftKey != (o.shiftKey || 0) ||
+ evt.metaKey != (o.metaKey || 0) ||
+ evt.keyCode != (o.keyCode || 0) ||
+ evt.charCode != (o.charCode || 0)) {
+ evt = document.createEvent('Event');
+ evt.initEvent(o.type, true, true);
+ evt.ctrlKey = o.ctrlKey || false;
+ evt.altKey = o.altKey || false;
+ evt.shiftKey = o.shiftKey || false;
+ evt.metaKey = o.metaKey || false;
+ evt.keyCode = o.keyCode || 0;
+ evt.charCode = o.charCode || 0;
+ }
}
} else {
evt = document.createEvent('MouseEvents');
@@ -53,10 +70,10 @@
o.screenY || 0, // screenY
o.clientX || 0, // clientX
o.clientY || 0, // clientY
- o.ctrl || 0, // ctrl
- o.alt || false, // alt
- o.shift || false, // shift
- o.meta || false, // meta
+ o.ctrlKey || 0, // ctrl
+ o.altKey || false, // alt
+ o.shiftKey || false, // shift
+ o.metaKey || false, // meta
o.button || false, // mouse button
null // relatedTarget
);
@@ -65,7 +82,8 @@
x.dispatchEvent(evt);
};
- var shortcuts = ['click', 'mousedown', 'mouseup', 'mousemove', 'keydown', 'keyup', 'keypress'],
+ var shortcuts = ['click', 'mousedown', 'mouseup', 'mousemove',
+ 'mouseover', 'mouseout', 'keydown', 'keyup', 'keypress'],
s, i = 0;
while (s = shortcuts[i++]) {
diff --git a/test/spec/actions/add_relation_member.js b/test/spec/actions/add_relation_member.js
deleted file mode 100644
index aeae8e353..000000000
--- a/test/spec/actions/add_relation_member.js
+++ /dev/null
@@ -1,37 +0,0 @@
-describe("iD.actions.AddRelationMember", function () {
- it("adds a member at the end of the relation", function () {
- var relation = iD.Relation(),
- graph = iD.Graph([relation]);
-
- graph = iD.actions.AddRelationMember(relation.id, {id: '1'})(graph);
-
- expect(graph.entity(relation.id).members).to.eql([{id: '1'}]);
- });
-
- it("adds a member at index 0", function () {
- var relation = iD.Relation({members: [{id: '1'}]}),
- graph = iD.Graph([relation]);
-
- graph = iD.actions.AddRelationMember(relation.id, {id: '2'}, 0)(graph);
-
- expect(graph.entity(relation.id).members).to.eql([{id: '2'}, {id: '1'}]);
- });
-
- it("adds a member at a positive index", function () {
- var relation = iD.Relation({members: [{id: '1'}, {id: '3'}]}),
- graph = iD.Graph([relation]);
-
- graph = iD.actions.AddRelationMember(relation.id, {id: '2'}, 1)(graph);
-
- expect(graph.entity(relation.id).members).to.eql([{id: '1'}, {id: '2'}, {id: '3'}]);
- });
-
- it("adds a member at a negative index", function () {
- var relation = iD.Relation({members: [{id: '1'}, {id: '3'}]}),
- graph = iD.Graph([relation]);
-
- graph = iD.actions.AddRelationMember(relation.id, {id: '2'}, -1)(graph);
-
- expect(graph.entity(relation.id).members).to.eql([{id: '1'}, {id: '2'}, {id: '3'}]);
- });
-});
diff --git a/test/spec/actions/add_way_node.js b/test/spec/actions/add_way_node.js
deleted file mode 100644
index 5c774e5e4..000000000
--- a/test/spec/actions/add_way_node.js
+++ /dev/null
@@ -1,29 +0,0 @@
-describe("iD.actions.AddWayNode", function () {
- it("adds a node to the end of a way", function () {
- var way = iD.Way(),
- node = iD.Node({id: "n1"}),
- graph = iD.actions.AddWayNode(way.id, node.id)(iD.Graph([way, node]));
- expect(graph.entity(way.id).nodes).to.eql(["n1"]);
- });
-
- it("adds a node to a way at index 0", function () {
- var way = iD.Way({nodes: ["n1", "n3"]}),
- node = iD.Node({id: "n2"}),
- graph = iD.actions.AddWayNode(way.id, node.id, 0)(iD.Graph([way, node]));
- expect(graph.entity(way.id).nodes).to.eql(["n2", "n1", "n3"]);
- });
-
- it("adds a node to a way at a positive index", function () {
- var way = iD.Way({nodes: ["n1", "n3"]}),
- node = iD.Node({id: "n2"}),
- graph = iD.actions.AddWayNode(way.id, node.id, 1)(iD.Graph([way, node]));
- expect(graph.entity(way.id).nodes).to.eql(["n1", "n2", "n3"]);
- });
-
- it("adds a node to a way at a negative index", function () {
- var way = iD.Way({nodes: ["n1", "n3"]}),
- node = iD.Node({id: "n2"}),
- graph = iD.actions.AddWayNode(way.id, node.id, -1)(iD.Graph([way, node]));
- expect(graph.entity(way.id).nodes).to.eql(["n1", "n2", "n3"]);
- });
-});
diff --git a/test/spec/actions/delete_node.js b/test/spec/actions/delete_node.js
index a73f7e33f..93860117f 100644
--- a/test/spec/actions/delete_node.js
+++ b/test/spec/actions/delete_node.js
@@ -24,4 +24,22 @@ describe("iD.actions.DeleteNode", function () {
graph = action(iD.Graph([node1, node2, relation]));
expect(graph.entity(relation.id).members).to.eql([{ id: node2.id }]);
});
+
+ it("deletes parent ways that would otherwise have less than two nodes", function () {
+ var node1 = iD.Node(),
+ node2 = iD.Node(),
+ way = iD.Way({nodes: [node1.id, node2.id]}),
+ action = iD.actions.DeleteNode(node1.id),
+ graph = action(iD.Graph([node1, node2, way]));
+ expect(graph.entity(way.id)).to.be.undefined;
+ });
+
+ it("deletes degenerate circular ways", function () {
+ var node1 = iD.Node(),
+ node2 = iD.Node(),
+ way = iD.Way({nodes: [node1.id, node2.id, node1.id]}),
+ action = iD.actions.DeleteNode(node2.id),
+ graph = action(iD.Graph([node1, node2, way]));
+ expect(graph.entity(way.id)).to.be.undefined;
+ });
});
diff --git a/test/spec/actions/remove_relation_member.js b/test/spec/actions/remove_relation_member.js
deleted file mode 100644
index d989b749b..000000000
--- a/test/spec/actions/remove_relation_member.js
+++ /dev/null
@@ -1,8 +0,0 @@
-describe("iD.actions.RemoveRelationMember", function () {
- it("removes a member from a relation", function () {
- var node = iD.Node(),
- relation = iD.Way({members: [{ id: node.id }]}),
- graph = iD.actions.RemoveRelationMember(relation.id, node.id)(iD.Graph([node, relation]));
- expect(graph.entity(relation.id).members).to.eql([]);
- });
-});
diff --git a/test/spec/actions/remove_way_node.js b/test/spec/actions/remove_way_node.js
deleted file mode 100644
index 6e5751b76..000000000
--- a/test/spec/actions/remove_way_node.js
+++ /dev/null
@@ -1,8 +0,0 @@
-describe("iD.actions.RemoveWayNode", function () {
- it("removes a node from a way", function () {
- var node = iD.Node({id: "n1"}),
- way = iD.Way({id: "w1", nodes: ["n1"]}),
- graph = iD.actions.RemoveWayNode(way.id, node.id)(iD.Graph({n1: node, w1: way}));
- expect(graph.entity(way.id).nodes).to.eql([]);
- });
-});
diff --git a/test/spec/actions/split_way.js b/test/spec/actions/split_way.js
index 09bacb82a..2958521a9 100644
--- a/test/spec/actions/split_way.js
+++ b/test/spec/actions/split_way.js
@@ -1,5 +1,5 @@
describe("iD.actions.SplitWay", function () {
- describe("#permitted", function () {
+ describe("#enabled", function () {
it("returns true for a non-end node of a single way", function () {
var graph = iD.Graph({
'a': iD.Node({id: 'a'}),
@@ -8,7 +8,7 @@ describe("iD.actions.SplitWay", function () {
'-': iD.Way({id: '-', nodes: ['a', 'b', 'c']})
});
- expect(iD.actions.SplitWay('b').permitted(graph)).to.be.true;
+ expect(iD.actions.SplitWay('b').enabled(graph)).to.be.true;
});
it("returns false for the first node of a single way", function () {
@@ -18,7 +18,7 @@ describe("iD.actions.SplitWay", function () {
'-': iD.Way({id: '-', nodes: ['a', 'b']})
});
- expect(iD.actions.SplitWay('a').permitted(graph)).to.be.false;
+ expect(iD.actions.SplitWay('a').enabled(graph)).to.be.false;
});
it("returns false for the last node of a single way", function () {
@@ -28,7 +28,7 @@ describe("iD.actions.SplitWay", function () {
'-': iD.Way({id: '-', nodes: ['a', 'b']})
});
- expect(iD.actions.SplitWay('b').permitted(graph)).to.be.false;
+ expect(iD.actions.SplitWay('b').enabled(graph)).to.be.false;
});
});
@@ -175,6 +175,20 @@ describe("iD.actions.SplitWay", function () {
expect(_.pluck(graph.entity('r').members, 'id')).to.eql(['~', '=', '-']);
});
+ it("handles incomplete relations", function () {
+ var graph = iD.Graph({
+ 'a': iD.Node({id: 'a'}),
+ 'b': iD.Node({id: 'b'}),
+ 'c': iD.Node({id: 'c'}),
+ '-': iD.Way({id: '-', nodes: ['a', 'b', 'c']}),
+ 'r': iD.Relation({id: 'r', members: [{id: '~', type: 'way'}, {id: '-', type: 'way'}]})
+ });
+
+ graph = iD.actions.SplitWay('b', '=')(graph);
+
+ expect(_.pluck(graph.entity('r').members, 'id')).to.eql(['~', '-', '=']);
+ });
+
['restriction', 'restriction:bus'].forEach(function (type) {
it("updates a restriction's 'from' role", function () {
// Situation:
diff --git a/test/spec/actions/unjoin_node.js b/test/spec/actions/unjoin_node.js
new file mode 100644
index 000000000..5901e97da
--- /dev/null
+++ b/test/spec/actions/unjoin_node.js
@@ -0,0 +1,77 @@
+describe("iD.actions.UnjoinNode", function () {
+ describe("#enabled", function () {
+ it("returns false for a node shared by less than two ways", function () {
+ var graph = iD.Graph({'a': iD.Node()});
+
+ expect(iD.actions.UnjoinNode('a').enabled(graph)).to.equal(false);
+ });
+
+ it("returns true for a node shared by two or more ways", function () {
+ // a ---- b ---- c
+ // |
+ // d
+ var graph = iD.Graph({
+ 'a': iD.Node({id: 'a'}),
+ 'b': iD.Node({id: 'b'}),
+ 'c': iD.Node({id: 'c'}),
+ 'd': iD.Node({id: 'd'}),
+ '-': iD.Way({id: '-', nodes: ['a', 'b', 'c']}),
+ '|': iD.Way({id: '|', nodes: ['d', 'b']})
+ });
+
+ expect(iD.actions.UnjoinNode('b').enabled(graph)).to.equal(true);
+ });
+ });
+
+ it("replaces the node with a new node in all but the first way", function () {
+ // Situation:
+ // a ---- b ---- c
+ // |
+ // d
+ // Split at b.
+ //
+ // Expected result:
+ // a ---- b ---- c
+ //
+ // e
+ // |
+ // d
+ //
+ var graph = iD.Graph({
+ 'a': iD.Node({id: 'a'}),
+ 'b': iD.Node({id: 'b'}),
+ 'c': iD.Node({id: 'c'}),
+ 'd': iD.Node({id: 'd'}),
+ '-': iD.Way({id: '-', nodes: ['a', 'b', 'c']}),
+ '|': iD.Way({id: '|', nodes: ['d', 'b']})
+ });
+
+ graph = iD.actions.UnjoinNode('b', 'e')(graph);
+
+ expect(graph.entity('-').nodes).to.eql(['a', 'b', 'c']);
+ expect(graph.entity('|').nodes).to.eql(['d', 'e']);
+ });
+
+ it("copies location and tags to the new nodes", function () {
+ var tags = {highway: 'traffic_signals'},
+ loc = [1, 2],
+ graph = iD.Graph({
+ 'a': iD.Node({id: 'a'}),
+ 'b': iD.Node({id: 'b', loc: loc, tags: tags}),
+ 'c': iD.Node({id: 'c'}),
+ 'd': iD.Node({id: 'd'}),
+ '-': iD.Way({id: '-', nodes: ['a', 'b', 'c']}),
+ '|': iD.Way({id: '|', nodes: ['d', 'b']})
+ });
+
+ graph = iD.actions.UnjoinNode('b', 'e')(graph);
+
+ // Immutable loc => should be shared by identity.
+ expect(graph.entity('b').loc).to.equal(loc);
+ expect(graph.entity('e').loc).to.equal(loc);
+
+ // Immutable tags => should be shared by identity.
+ expect(graph.entity('b').tags).to.equal(tags);
+ expect(graph.entity('e').tags).to.equal(tags);
+ });
+});
diff --git a/test/spec/actions/update_relation_member.js b/test/spec/actions/update_relation_member.js
deleted file mode 100644
index c85d72b34..000000000
--- a/test/spec/actions/update_relation_member.js
+++ /dev/null
@@ -1,8 +0,0 @@
-describe("iD.actions.UpdateRelationMember", function () {
- it("updates the properties of the relation member at the specified index", function () {
- var node = iD.Node(),
- relation = iD.Relation({members: [{id: node.id, role: 'forward'}]}),
- graph = iD.actions.UpdateRelationMember(relation.id, {role: 'backward'}, 0)(iD.Graph([node, relation]));
- expect(graph.entity(relation.id).members).to.eql([{id: node.id, role: 'backward'}]);
- });
-});
diff --git a/test/spec/connection.js b/test/spec/connection.js
index 70f018322..dbf7722bb 100644
--- a/test/spec/connection.js
+++ b/test/spec/connection.js
@@ -1,4 +1,4 @@
-describe('Connection', function() {
+describe('iD.Connection', function() {
var c;
beforeEach(function() {
diff --git a/test/spec/format/geojson.js b/test/spec/format/geojson.js
index f9b58f66f..e17c8573b 100644
--- a/test/spec/format/geojson.js
+++ b/test/spec/format/geojson.js
@@ -1,4 +1,4 @@
-describe('GeoJSON', function() {
+describe('iD.format.GeoJSON', function() {
describe('#mapping', function() {
it('should be able to map a node to geojson', function() {
diff --git a/test/spec/format/xml.js b/test/spec/format/xml.js
index db0e3226b..e70ed464f 100644
--- a/test/spec/format/xml.js
+++ b/test/spec/format/xml.js
@@ -1,4 +1,4 @@
-describe('XML', function() {
+describe('iD.format.XML', function() {
var node = iD.Node({ id: 'n-1', type: 'node', loc: [-77, 38] }),
way = iD.Way({ id: 'w-1', type: 'way', nodes: [] });
diff --git a/test/spec/graph/history.js b/test/spec/graph/history.js
index 554b22a82..309d396a1 100644
--- a/test/spec/graph/history.js
+++ b/test/spec/graph/history.js
@@ -83,6 +83,13 @@ describe("iD.History", function () {
expect(history.redoAnnotation()).to.equal("annotation");
});
+ it("emits an undone event", function () {
+ history.perform(action);
+ history.on('undone', spy);
+ history.undo();
+ expect(spy).to.have.been.called;
+ });
+
it("emits a change event", function () {
history.perform(action);
history.on('change', spy);
@@ -92,6 +99,14 @@ describe("iD.History", function () {
});
describe("#redo", function () {
+ it("emits an redone event", function () {
+ history.perform(action);
+ history.undo();
+ history.on('change', spy);
+ history.redo();
+ expect(spy).to.have.been.called;
+ });
+
it("emits a change event", function () {
history.perform(action);
history.undo();
diff --git a/test/spec/graph/relation.js b/test/spec/graph/relation.js
index f8b49a213..d8619726a 100644
--- a/test/spec/graph/relation.js
+++ b/test/spec/graph/relation.js
@@ -104,6 +104,42 @@ describe('iD.Relation', function () {
});
});
+ describe("#addMember", function () {
+ it("adds a member at the end of the relation", function () {
+ var r = iD.Relation();
+ expect(r.addMember({id: '1'}).members).to.eql([{id: '1'}]);
+ });
+
+ it("adds a member at index 0", function () {
+ var r = iD.Relation({members: [{id: '1'}]});
+ expect(r.addMember({id: '2'}, 0).members).to.eql([{id: '2'}, {id: '1'}]);
+ });
+
+ it("adds a member at a positive index", function () {
+ var r = iD.Relation({members: [{id: '1'}, {id: '3'}]});
+ expect(r.addMember({id: '2'}, 1).members).to.eql([{id: '1'}, {id: '2'}, {id: '3'}]);
+ });
+
+ it("adds a member at a negative index", function () {
+ var r = iD.Relation({members: [{id: '1'}, {id: '3'}]});
+ expect(r.addMember({id: '2'}, -1).members).to.eql([{id: '1'}, {id: '2'}, {id: '3'}]);
+ });
+ });
+
+ describe("#updateMember", function () {
+ it("updates the properties of the relation member at the specified index", function () {
+ var r = iD.Relation({members: [{role: 'forward'}]});
+ expect(r.updateMember({role: 'backward'}, 0).members).to.eql([{role: 'backward'}]);
+ });
+ });
+
+ describe("#removeMember", function () {
+ it("removes a member", function () {
+ var r = iD.Relation({members: [{id: 'a'}]});
+ expect(r.removeMember('a').members).to.eql([]);
+ });
+ });
+
describe("#multipolygon", function () {
specify("single polygon consisting of a single way", function () {
var a = iD.Node(),
@@ -298,6 +334,17 @@ describe('iD.Relation', function () {
expect(r.multipolygon(graph)).to.eql([[[a, b, c, a], [d, e, f, d]], [[g, h, i, g]]]);
});
+ specify("invalid geometry: unmatched inner", function () {
+ var a = iD.Node(),
+ b = iD.Node(),
+ c = iD.Node(),
+ w = iD.Way({nodes: [a.id, b.id, c.id, a.id]}),
+ r = iD.Relation({members: [{id: w.id, role: 'inner', type: 'way'}]}),
+ g = iD.Graph([a, b, c, w, r]);
+
+ expect(r.multipolygon(g)).to.eql([[[a, b, c, a]]]);
+ });
+
specify("incomplete relation", function () {
var a = iD.Node(),
b = iD.Node(),
diff --git a/test/spec/graph/way.js b/test/spec/graph/way.js
index 37df6b648..87d7d0029 100644
--- a/test/spec/graph/way.js
+++ b/test/spec/graph/way.js
@@ -117,6 +117,31 @@ describe('iD.Way', function() {
});
});
+ describe("#isDegenerate", function() {
+ it("returns true for a linear way with zero or one nodes", function () {
+ expect(iD.Way({nodes: []}).isDegenerate()).to.equal(true);
+ expect(iD.Way({nodes: ['a']}).isDegenerate()).to.equal(true);
+ });
+
+ it("returns true for a circular way with only one unique node", function () {
+ expect(iD.Way({nodes: ['a', 'a']}).isDegenerate()).to.equal(true);
+ });
+
+ it("returns false for a linear way with two or more nodes", function () {
+ expect(iD.Way({nodes: ['a', 'b']}).isDegenerate()).to.equal(false);
+ });
+
+ it("returns true for an area with zero, one, or two unique nodes", function () {
+ expect(iD.Way({tags: {area: 'yes'}, nodes: []}).isDegenerate()).to.equal(true);
+ expect(iD.Way({tags: {area: 'yes'}, nodes: ['a', 'a']}).isDegenerate()).to.equal(true);
+ expect(iD.Way({tags: {area: 'yes'}, nodes: ['a', 'b', 'a']}).isDegenerate()).to.equal(true);
+ });
+
+ it("returns false for an area with three or more unique nodes", function () {
+ expect(iD.Way({tags: {area: 'yes'}, nodes: ['a', 'b', 'c', 'a']}).isDegenerate()).to.equal(false);
+ });
+ });
+
describe("#geometry", function() {
it("returns 'line' when the way is not an area", function () {
expect(iD.Way().geometry()).to.equal('line');
@@ -126,4 +151,52 @@ describe('iD.Way', function() {
expect(iD.Way({tags: { area: 'yes' }}).geometry()).to.equal('area');
});
});
+
+ describe("#addNode", function () {
+ it("adds a node to the end of a way", function () {
+ var w = iD.Way();
+ expect(w.addNode('a').nodes).to.eql(['a']);
+ });
+
+ it("adds a node to a way at index 0", function () {
+ var w = iD.Way({nodes: ['a', 'b']});
+ expect(w.addNode('c', 0).nodes).to.eql(['c', 'a', 'b']);
+ });
+
+ it("adds a node to a way at a positive index", function () {
+ var w = iD.Way({nodes: ['a', 'b']});
+ expect(w.addNode('c', 1).nodes).to.eql(['a', 'c', 'b']);
+ });
+
+ it("adds a node to a way at a negative index", function () {
+ var w = iD.Way({nodes: ['a', 'b']});
+ expect(w.addNode('c', -1).nodes).to.eql(['a', 'c', 'b']);
+ });
+ });
+
+ describe("#updateNode", function () {
+ it("updates the node id at the specified index", function () {
+ var w = iD.Way({nodes: ['a', 'b', 'c']});
+ expect(w.updateNode('d', 1).nodes).to.eql(['a', 'd', 'c']);
+ });
+ });
+
+ describe("#removeNode", function () {
+ it("removes the node", function () {
+ var a = iD.Node({id: 'a'}),
+ w = iD.Way({nodes: ['a']});
+
+ expect(w.removeNode('a').nodes).to.eql([]);
+ });
+
+ it("preserves circularity", function () {
+ var a = iD.Node({id: 'a'}),
+ b = iD.Node({id: 'b'}),
+ c = iD.Node({id: 'c'}),
+ d = iD.Node({id: 'd'}),
+ w = iD.Way({nodes: ['a', 'b', 'c', 'd', 'a']});
+
+ expect(w.removeNode('a').nodes).to.eql(['b', 'c', 'd', 'b']);
+ });
+ });
});
diff --git a/test/spec/integration/create_poi.js b/test/spec/integration/create_poi.js
deleted file mode 100644
index f1d08d011..000000000
--- a/test/spec/integration/create_poi.js
+++ /dev/null
@@ -1,22 +0,0 @@
-describe("iD", function () {
- var container, editor;
-
- beforeEach(function() {
- container = d3.select('body').append('div');
- editor = iD()
- editor.map().background.source(null);
- editor.call(container);
- });
-
- afterEach(function() {
- container.remove();
- });
-
- it("allows an editor to add a place", function () {
- // click 'Add Place'
- // click on map
- // select tags
- // save
- // check uploaded XML
- });
-});
diff --git a/test/spec/lib/d3.keybinding.js b/test/spec/lib/d3.keybinding.js
new file mode 100644
index 000000000..23e1a0e0c
--- /dev/null
+++ b/test/spec/lib/d3.keybinding.js
@@ -0,0 +1,55 @@
+describe("d3.keybinding", function() {
+ var keybinding, spy, input;
+
+ beforeEach(function () {
+ keybinding = d3.keybinding('keybinding-test');
+ spy = sinon.spy();
+ input = d3.select('body')
+ .append('input');
+ });
+
+ afterEach(function () {
+ keybinding.off(d3.select(document));
+ input.remove();
+ });
+
+ describe("#on", function () {
+ it("returns self", function () {
+ expect(keybinding.on('a', spy)).to.equal(keybinding);
+ });
+
+ it("adds a binding for the specified bare key", function () {
+ d3.select(document).call(keybinding.on('A', spy));
+
+ happen.keydown(document, {keyCode: 65, metaKey: true});
+ expect(spy).not.to.have.been.called;
+
+ happen.keydown(document, {keyCode: 65});
+ expect(spy).to.have.been.called;
+ });
+
+ it("adds a binding for the specified key combination", function () {
+ d3.select(document).call(keybinding.on('⌘+A', spy));
+
+ happen.keydown(document, {keyCode: 65});
+ expect(spy).not.to.have.been.called;
+
+ happen.keydown(document, {keyCode: 65, metaKey: true});
+ expect(spy).to.have.been.called;
+ });
+
+ it("does not dispatch when focus is in input elements by default", function () {
+ d3.select(document).call(keybinding.on('A', spy));
+
+ happen.keydown(input.node(), {keyCode: 65});
+ expect(spy).not.to.have.been.called;
+ });
+
+ it("dispatches when focus is in input elements when the capture flag was passed", function () {
+ d3.select(document).call(keybinding.on('A', spy, true));
+
+ happen.keydown(input.node(), {keyCode: 65});
+ expect(spy).to.have.been.called;
+ });
+ });
+});
diff --git a/test/spec/modes/add_point.js b/test/spec/modes/add_point.js
index 26cfcd013..725a7a396 100644
--- a/test/spec/modes/add_point.js
+++ b/test/spec/modes/add_point.js
@@ -33,7 +33,7 @@ describe("iD.modes.AddPoint", function () {
});
describe("pressing ⎋", function () {
- xit("exits to browse mode", function () {
+ it("exits to browse mode", function () {
happen.keydown(document, {keyCode: 27});
expect(controller.mode.id).to.equal('browse');
});
diff --git a/test/spec/renderer/background.js b/test/spec/renderer/background.js
index 0d7b0de10..6ba871d11 100644
--- a/test/spec/renderer/background.js
+++ b/test/spec/renderer/background.js
@@ -1,4 +1,4 @@
-describe('Background', function() {
+describe('iD.Background', function() {
var c, d;
beforeEach(function() {
diff --git a/test/spec/renderer/hash.js b/test/spec/renderer/hash.js
index 1304602f1..bece2e155 100644
--- a/test/spec/renderer/hash.js
+++ b/test/spec/renderer/hash.js
@@ -1,4 +1,4 @@
-describe("hash", function () {
+describe("iD.Hash", function () {
var hash, map, controller;
beforeEach(function () {
diff --git a/test/spec/renderer/map.js b/test/spec/renderer/map.js
index 611a2d1ff..5070bdffa 100644
--- a/test/spec/renderer/map.js
+++ b/test/spec/renderer/map.js
@@ -1,4 +1,4 @@
-describe('Map', function() {
+describe('iD.Map', function() {
var container, map;
beforeEach(function() {
@@ -42,6 +42,12 @@ describe('Map', function() {
});
});
+ describe('#minzoom', function() {
+ it('is zero by default', function() {
+ expect(map.minzoom()).to.equal(0);
+ });
+ });
+
describe('#center', function() {
it('gets and sets center', function() {
expect(map.center([0, 0])).to.equal(map);
@@ -52,6 +58,27 @@ describe('Map', function() {
});
});
+ describe('#centerEase', function() {
+ it('sets center', function(done) {
+ expect(map.center([10, 10])).to.equal(map);
+ expect(map.centerEase([20, 20])).to.equal(map);
+ window.setTimeout(function() {
+ expect(map.center()[0]).to.be.closeTo(20, 0.5);
+ expect(map.center()[1]).to.be.closeTo(20, 0.5);
+ done();
+ }, 1000);
+ });
+ });
+
+ describe('#centerZoom', function() {
+ it('gets and sets center and zoom', function() {
+ expect(map.centerZoom([20, 25], 4)).to.equal(map);
+ expect(map.center()[0]).to.be.closeTo(20, 0.5);
+ expect(map.center()[1]).to.be.closeTo(25, 0.5);
+ expect(map.zoom()).to.be.equal(4);
+ });
+ });
+
describe('#extent', function() {
it('gets and sets extent', function() {
map.size([100, 100])
diff --git a/test/spec/renderer/style.js b/test/spec/renderer/style.js
deleted file mode 100644
index dad18866b..000000000
--- a/test/spec/renderer/style.js
+++ /dev/null
@@ -1,17 +0,0 @@
-describe('iD.Style', function() {
- describe('#waystack', function() {
- it('stacks bridges over non-bridges', function() {
- var a = { tags: { bridge: 'yes' } },
- b = { tags: {} };
- expect(iD.Style.waystack(a, b)).to.equal(1);
- expect(iD.Style.waystack(b, a)).to.equal(-1);
- });
-
- it('stacks layers', function() {
- var a = { tags: { layer: 1 } },
- b = { tags: { layer: 0 } };
- expect(iD.Style.waystack(a, b)).to.equal(1);
- expect(iD.Style.waystack(b, a)).to.equal(-1);
- });
- });
-});
diff --git a/test/spec/ui/flash.js b/test/spec/ui/flash.js
index 2b43ef83e..b0314ffeb 100644
--- a/test/spec/ui/flash.js
+++ b/test/spec/ui/flash.js
@@ -11,7 +11,7 @@ describe("iD.ui.flash", function () {
it('leaves after 1000 ms', function () {
var flash = iD.ui.flash();
- clock.tick(1010);
+ clock.tick(1610);
expect(flash.node().parentNode).to.be.null;
});
});
diff --git a/test/spec/util.js b/test/spec/util.js
index 2cfa8dc16..c2c645673 100644
--- a/test/spec/util.js
+++ b/test/spec/util.js
@@ -1,4 +1,4 @@
-describe('Util', function() {
+describe('iD.Util', function() {
var util;
it('#trueObj', function() {
@@ -26,21 +26,21 @@ describe('Util', function() {
describe('geo', function() {
describe('#roundCoords', function() {
- expect(iD.util.geo.roundCoords([0.1, 1])).to.eql([0, 1]);
- expect(iD.util.geo.roundCoords([0, 1])).to.eql([0, 1]);
- expect(iD.util.geo.roundCoords([0, 1.1])).to.eql([0, 1]);
+ expect(iD.geo.roundCoords([0.1, 1])).to.eql([0, 1]);
+ expect(iD.geo.roundCoords([0, 1])).to.eql([0, 1]);
+ expect(iD.geo.roundCoords([0, 1.1])).to.eql([0, 1]);
});
describe('#interp', function() {
it('interpolates halfway', function() {
var a = [0, 0],
b = [10, 10];
- expect(iD.util.geo.interp(a, b, 0.5)).to.eql([5, 5]);
+ expect(iD.geo.interp(a, b, 0.5)).to.eql([5, 5]);
});
it('interpolates to one side', function() {
var a = [0, 0],
b = [10, 10];
- expect(iD.util.geo.interp(a, b, 0)).to.eql([0, 0]);
+ expect(iD.geo.interp(a, b, 0)).to.eql([0, 0]);
});
});
@@ -48,17 +48,17 @@ describe('Util', function() {
it('distance between two same points is zero', function() {
var a = [0, 0],
b = [0, 0];
- expect(iD.util.geo.dist(a, b)).to.eql(0);
+ expect(iD.geo.dist(a, b)).to.eql(0);
});
it('a straight 10 unit line is 10', function() {
var a = [0, 0],
b = [10, 0];
- expect(iD.util.geo.dist(a, b)).to.eql(10);
+ expect(iD.geo.dist(a, b)).to.eql(10);
});
it('a pythagorean triangle is right', function() {
var a = [0, 0],
b = [4, 3];
- expect(iD.util.geo.dist(a, b)).to.eql(5);
+ expect(iD.geo.dist(a, b)).to.eql(5);
});
});
@@ -66,7 +66,7 @@ describe('Util', function() {
it('says a point in a polygon is on a polygon', function() {
var poly = [[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]];
var point = [0.5, 0.5];
- expect(iD.util.geo.pointInPolygon(point, poly)).to.be.true;
+ expect(iD.geo.pointInPolygon(point, poly)).to.be.true;
});
it('says a point outside of a polygon is outside', function() {
var poly = [
@@ -76,7 +76,7 @@ describe('Util', function() {
[1, 0],
[0, 0]];
var point = [0.5, 1.5];
- expect(iD.util.geo.pointInPolygon(point, poly)).to.be.false;
+ expect(iD.geo.pointInPolygon(point, poly)).to.be.false;
});
});
@@ -84,12 +84,12 @@ describe('Util', function() {
it('says a polygon in a polygon is in', function() {
var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]];
var inner = [[1, 1], [1, 2], [2, 2], [2, 1], [1, 1]];
- expect(iD.util.geo.polygonContainsPolygon(outer, inner)).to.be.true;
+ expect(iD.geo.polygonContainsPolygon(outer, inner)).to.be.true;
});
it('says a polygon outside of a polygon is out', function() {
var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]];
var inner = [[1, 1], [1, 9], [2, 2], [2, 1], [1, 1]];
- expect(iD.util.geo.polygonContainsPolygon(outer, inner)).to.be.false;
+ expect(iD.geo.polygonContainsPolygon(outer, inner)).to.be.false;
});
});
@@ -97,19 +97,19 @@ describe('Util', function() {
it('says a polygon in a polygon intersects it', function() {
var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]];
var inner = [[1, 1], [1, 2], [2, 2], [2, 1], [1, 1]];
- expect(iD.util.geo.polygonIntersectsPolygon(outer, inner)).to.be.true;
+ expect(iD.geo.polygonIntersectsPolygon(outer, inner)).to.be.true;
});
it('says a polygon that partially intersects does', function() {
var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]];
var inner = [[-1, -1], [1, 2], [2, 2], [2, 1], [1, 1]];
- expect(iD.util.geo.polygonIntersectsPolygon(outer, inner)).to.be.true;
+ expect(iD.geo.polygonIntersectsPolygon(outer, inner)).to.be.true;
});
it('says totally disjoint polygons do not intersect', function() {
var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]];
var inner = [[-1, -1], [-1, -2], [-2, -2], [-2, -1], [-1, -1]];
- expect(iD.util.geo.polygonIntersectsPolygon(outer, inner)).to.be.false;
+ expect(iD.geo.polygonIntersectsPolygon(outer, inner)).to.be.false;
});
});
});