Merge remote-tracking branch 'systemed/master' into labels

Conflicts:
	js/id/util.js
This commit is contained in:
Ansis Brammanis
2013-01-23 16:15:29 -05:00
66 changed files with 1129 additions and 625 deletions
+23 -16
View File
@@ -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;
}
+1 -4
View File
@@ -73,7 +73,6 @@
<script src='js/id/actions.js'></script>
<script src='js/id/actions/add_node.js'></script>
<script src='js/id/actions/add_relation_member.js'></script>
<script src='js/id/actions/add_way.js'></script>
<script src='js/id/actions/add_way_node.js'></script>
<script src='js/id/actions/change_entity_tags.js'></script>
@@ -82,11 +81,9 @@
<script src='js/id/actions/move_node.js'></script>
<script src='js/id/actions/move_way.js'></script>
<script src='js/id/actions/noop.js'></script>
<script src='js/id/actions/remove_relation_member.js'></script>
<script src='js/id/actions/remove_way_node.js'></script>
<script src='js/id/actions/reverse_way.js'></script>
<script src='js/id/actions/split_way.js'></script>
<script src='js/id/actions/update_relation_member.js'></script>
<script src='js/id/actions/unjoin_node.js'></script>
<script src='js/id/behavior.js'></script>
<script src='js/id/behavior/drag.js'></script>
-9
View File
@@ -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}));
};
};
+1 -5
View File
@@ -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));
};
};
+14 -3
View File
@@ -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;
};
+9 -3
View File
@@ -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;
};
+1 -2
View File
@@ -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));
};
};
+1 -1
View File
@@ -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;
-9
View File
@@ -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}));
};
};
-17
View File
@@ -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}));
};
};
+9 -2
View File
@@ -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;
};
+8 -13
View File
@@ -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;
};
+37
View File
@@ -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;
};
-9
View File
@@ -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}));
};
};
+2 -9
View File
@@ -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) {
+74
View File
@@ -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);
});
};
+1 -13
View File
@@ -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;
};
+3 -1
View File
@@ -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);
},
+15 -1
View File
@@ -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});
}
});
+31 -4
View File
@@ -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;
+38 -1
View File
@@ -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});
}
});
+13 -23
View File
@@ -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);
+7 -2
View File
@@ -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;
+8 -3
View File
@@ -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;
+7 -2
View File
@@ -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;
+46 -26
View File
@@ -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);
+45 -36
View File
@@ -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);
+24 -19
View File
@@ -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")
+14 -11
View File
@@ -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);
+3 -10
View File
@@ -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;
+1 -1
View File
@@ -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));
};
},
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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;
};
+1
View File
@@ -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;
+14 -3
View File
@@ -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');
}
});
+2 -2
View File
@@ -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)
+3 -3
View File
@@ -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')
+29 -10
View File
@@ -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;
+51 -50
View File
@@ -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');
+192 -115
View File
@@ -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;
}
})();
+4 -9
View File
@@ -66,7 +66,6 @@
<script src='../js/id/actions.js'></script>
<script src='../js/id/actions/add_node.js'></script>
<script src="../js/id/actions/add_relation_member.js"></script>
<script src='../js/id/actions/add_way.js'></script>
<script src='../js/id/actions/add_way_node.js'></script>
<script src='../js/id/actions/change_entity_tags.js'></script>
@@ -75,11 +74,9 @@
<script src='../js/id/actions/move_node.js'></script>
<script src='../js/id/actions/move_way.js'></script>
<script src='../js/id/actions/noop.js'></script>
<script src='../js/id/actions/remove_relation_member.js'></script>
<script src='../js/id/actions/remove_way_node.js'></script>
<script src='../js/id/actions/reverse_way.js'></script>
<script src='../js/id/actions/split_way.js'></script>
<script src='../js/id/actions/update_relation_member.js'></script>
<script src='../js/id/actions/unjoin_node.js'></script>
<script src='../js/id/behavior.js'></script>
<script src='../js/id/behavior/drag.js'></script>
@@ -124,21 +121,19 @@
<script src="spec/spec_helpers.js"></script>
<!-- include spec files here... -->
<script src="spec/lib/d3.keybinding.js"></script>
<script src="spec/actions/add_node.js"></script>
<script src="spec/actions/add_relation_member.js"></script>
<script src="spec/actions/add_way.js"></script>
<script src="spec/actions/add_way_node.js"></script>
<script src="spec/actions/change_entity_tags.js"></script>
<script src="spec/actions/delete_node.js"></script>
<script src="spec/actions/delete_way.js"></script>
<script src="spec/actions/move_node.js"></script>
<script src="spec/actions/move_way.js"></script>
<script src="spec/actions/noop.js"></script>
<script src="spec/actions/remove_way_node.js"></script>
<script src="spec/actions/remove_relation_member.js"></script>
<script src="spec/actions/reverse_way.js"></script>
<script src="spec/actions/split_way.js"></script>
<script src="spec/actions/update_relation_member.js"></script>
<script src='spec/actions/unjoin_node.js'></script>
<script src="spec/behavior/hover.js"></script>
+8 -4
View File
@@ -30,8 +30,9 @@
<script src="spec/spec_helpers.js"></script>
<!-- include spec files here... -->
<script src="spec/lib/d3.keybinding.js"></script>
<script src="spec/actions/add_node.js"></script>
<script src="spec/actions/add_relation_member.js"></script>
<script src="spec/actions/add_way.js"></script>
<script src="spec/actions/add_way_node.js"></script>
<script src="spec/actions/change_entity_tags.js"></script>
@@ -40,11 +41,9 @@
<script src="spec/actions/move_node.js"></script>
<script src="spec/actions/move_way.js"></script>
<script src="spec/actions/noop.js"></script>
<script src="spec/actions/remove_way_node.js"></script>
<script src="spec/actions/remove_relation_member.js"></script>
<script src="spec/actions/reverse_way.js"></script>
<script src="spec/actions/split_way.js"></script>
<script src="spec/actions/update_relation_member.js"></script>
<script src='spec/actions/unjoin_node.js'></script>
<script src="spec/behavior/hover.js"></script>
@@ -76,6 +75,11 @@
<script src="spec/svg/tag_classes.js"></script>
<script src="spec/ui/inspector.js"></script>
<script src="spec/ui/geocoder.js"></script>
<script src="spec/ui/modal.js"></script>
<script src="spec/ui/flash.js"></script>
<script src="spec/ui/confirm.js"></script>
<script src="spec/connection.js"></script>
<script src="spec/oauth.js"></script>
<script src="spec/taginfo.js"></script>
+31 -13
View File
@@ -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++]) {
-37
View File
@@ -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'}]);
});
});
-29
View File
@@ -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"]);
});
});
+18
View File
@@ -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;
});
});
@@ -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([]);
});
});
-8
View File
@@ -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([]);
});
});
+18 -4
View File
@@ -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:
+77
View File
@@ -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);
});
});
@@ -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'}]);
});
});
+1 -1
View File
@@ -1,4 +1,4 @@
describe('Connection', function() {
describe('iD.Connection', function() {
var c;
beforeEach(function() {
+1 -1
View File
@@ -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() {
+1 -1
View File
@@ -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: [] });
+15
View File
@@ -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();
+47
View File
@@ -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(),
+73
View File
@@ -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']);
});
});
});
-22
View File
@@ -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
});
});
+55
View File
@@ -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;
});
});
});
+1 -1
View File
@@ -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');
});
+1 -1
View File
@@ -1,4 +1,4 @@
describe('Background', function() {
describe('iD.Background', function() {
var c, d;
beforeEach(function() {
+1 -1
View File
@@ -1,4 +1,4 @@
describe("hash", function () {
describe("iD.Hash", function () {
var hash, map, controller;
beforeEach(function () {
+28 -1
View File
@@ -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])
-17
View File
@@ -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);
});
});
});
+1 -1
View File
@@ -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;
});
});
+16 -16
View File
@@ -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;
});
});
});