Merge branch 'master' of github.com:systemed/iD

This commit is contained in:
Saman Bemel-Benrud
2013-02-04 11:30:59 -05:00
82 changed files with 3160 additions and 2080 deletions
-1
View File
@@ -41,7 +41,6 @@ all: \
js/id/modes/*.js \
js/id/operations.js \
js/id/operations/*.js \
js/id/controller.js \
js/id/graph/*.js \
js/id/renderer/*.js \
js/id/svg.js \
+170
View File
@@ -0,0 +1,170 @@
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<title>iD</title>
<link rel='stylesheet' href='css/reset.css'>
<link rel='stylesheet' href='css/map.css'>
<link rel='stylesheet' href='css/app.css'>
<!-- mobile devices -->
<meta name='viewport' content='initial-scale=1.0 maximum-scale=1.0'>
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<script src='js/lib/lodash.js'></script>
<script src='js/lib/d3.v3.js'></script>
<script src='js/lib/sha.js'></script>
<script src='js/lib/ohauth.js'></script>
<script src='js/lib/jxon.js'></script>
<script src='js/lib/d3.typeahead.js'></script>
<script src='js/lib/d3.combobox.js'></script>
<script src='js/lib/d3.geo.tile.js'></script>
<script src='js/lib/d3.size.js'></script>
<script src='js/lib/d3.trigger.js'></script>
<script src='js/lib/d3.keybinding.js'></script>
<script src='js/lib/d3.tail.js'></script>
<script src='js/lib/d3-compat.js'></script>
<script src='js/lib/bootstrap-tooltip.js'></script>
<script src='js/lib/rtree.js'></script>
<script src='js/id/id.js'></script>
<script src='js/id/util.js'></script>
<script src='js/id/oauth.js'></script>
<script src='js/id/services/taginfo.js'></script>
<script src="js/id/geo.js"></script>
<script src="js/id/geo/extent.js"></script>
<script src='js/id/renderer/background.js'></script>
<script src='js/id/renderer/background_source.js'></script>
<script src='js/id/renderer/map.js'></script>
<script src='js/id/renderer/hash.js'></script>
<script src="js/id/svg.js"></script>
<script src="js/id/svg/areas.js"></script>
<script src="js/id/svg/lines.js"></script>
<script src="js/id/svg/member_classes.js"></script>
<script src="js/id/svg/midpoints.js"></script>
<script src="js/id/svg/multipolygons.js"></script>
<script src="js/id/svg/points.js"></script>
<script src="js/id/svg/surface.js"></script>
<script src="js/id/svg/tag_classes.js"></script>
<script src="js/id/svg/vertices.js"></script>
<script src="js/id/svg/labels.js"></script>
<script src="js/id/ui.js"></script>
<script src='js/id/ui/radial_menu.js'></script>
<script src='js/id/ui/inspector.js'></script>
<script src='js/id/ui/modal.js'></script>
<script src='js/id/ui/confirm.js'></script>
<script src='js/id/ui/commit.js'></script>
<script src='js/id/ui/success.js'></script>
<script src='js/id/ui/loading.js'></script>
<script src='js/id/ui/userpanel.js'></script>
<script src='js/id/ui/layerswitcher.js'></script>
<script src='js/id/ui/contributors.js'></script>
<script src='js/id/ui/geocoder.js'></script>
<script src='js/id/ui/geolocate.js'></script>
<script src='js/id/ui/notice.js'></script>
<script src='js/id/ui/flash.js'></script>
<script src='js/id/ui/save.js'></script>
<script src='js/id/ui/tag_reference.js'></script>
<script src='js/id/ui/key_reference.js'></script>
<script src='js/id/actions.js'></script>
<script src="js/id/actions/add_midpoint.js"></script>
<script src='js/id/actions/add_entity.js'></script>
<script src='js/id/actions/add_vertex.js'></script>
<script src='js/id/actions/change_tags.js'></script>
<script src='js/id/actions/delete_node.js'></script>
<script src="js/id/actions/delete_way.js"></script>
<script src='js/id/actions/disconnect.js'></script>
<script src='js/id/actions/move_node.js'></script>
<script src='js/id/actions/move_way.js'></script>
<script src='js/id/actions/circularize.js'></script>
<script src='js/id/actions/noop.js'></script>
<script src='js/id/actions/reverse.js'></script>
<script src='js/id/actions/split.js'></script>
<script src='js/id/behavior.js'></script>
<script src='js/id/behavior/add_way.js'></script>
<script src='js/id/behavior/drag.js'></script>
<script src='js/id/behavior/drag_midpoint.js'></script>
<script src='js/id/behavior/drag_node.js'></script>
<script src='js/id/behavior/draw.js'></script>
<script src='js/id/behavior/draw_way.js'></script>
<script src='js/id/behavior/hover.js'></script>
<script src='js/id/behavior/select.js'></script>
<script src='js/id/modes.js'></script>
<script src='js/id/modes/add_area.js'></script>
<script src='js/id/modes/add_point.js'></script>
<script src='js/id/modes/add_line.js'></script>
<script src='js/id/modes/browse.js'></script>
<script src='js/id/modes/draw_area.js'></script>
<script src='js/id/modes/draw_line.js'></script>
<script src='js/id/modes/move_way.js'></script>
<script src='js/id/modes/select.js'></script>
<script src='js/id/operations.js'></script>
<script src='js/id/operations/circularize.js'></script>
<script src='js/id/operations/delete.js'></script>
<script src='js/id/operations/move.js'></script>
<script src='js/id/operations/reverse.js'></script>
<script src='js/id/operations/split.js'></script>
<script src='js/id/operations/unjoin.js'></script>
<script src='js/id/graph/entity.js'></script>
<script src='js/id/graph/graph.js'></script>
<script src='js/id/graph/history.js'></script>
<script src='js/id/graph/node.js'></script>
<script src='js/id/graph/relation.js'></script>
<script src='js/id/graph/way.js'></script>
<script src='js/id/controller.js'></script>
<script src='js/id/validate.js'></script>
<script src='js/id/connection.js'></script>
<script src='locale/locale.js'></script>
<script src='locale/en.js'></script>
<style>
body {
margin:20px;
}
#foo, #bar {
position:relative;
}
</style>
</head>
<body>
<div id='foo'><input /></div>
<div id='bar'><input /></div>
<div id="iD"></div><script>
var options = d3.range(0, 50).map(function(i) {
return {
value: i * 10,
title: i * 10
};
});
d3.select('#foo').call(d3.combobox()
.data(function(selection, cb) {
cb(options);
}));
d3.select('#bar').call(d3.combobox()
.data(function(selection, cb) {
cb(options);
}));
</script></body>
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-38039653-2']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
</html>
+44 -1
View File
@@ -476,7 +476,7 @@ button[disabled] .icon.nearby { background-position: -340px -40px;}
.icon-operation-circularize { background-position: -20px -140px;}
.icon-operation-straighten { background-position: -40px -140px;}
.icon-operation-split { background-position: -60px -140px;}
.icon-operation-unjoin { background-position: -80px -140px;}
.icon-operation-disconnect { background-position: -80px -140px;}
.icon-operation-reverse { background-position: -100px -140px;}
.icon-operation-move { background-position: -120px -140px;}
.icon-operation-merge { background-position: -140px -140px;}
@@ -1398,3 +1398,46 @@ a.success-action {
.icon.icon-pre-text { margin-right: 0px;}
.save .label, .apply .label, .cancel .label { display: block;}
}
div.combobox {
width:155px;
z-index: 9999;
display: none;
box-shadow: 0 5px 10px 0 rgba(0,0,0,.2);
margin-top: -1px;
background: white;
max-height: 180px;
overflow: auto;
border: 1px solid #ccc;
}
div.combobox a {
height: 25px;
line-height: 25px;
cursor: pointer;
display: block;
border-top:1px solid #ccc;
background-color: #fff;
padding:1px 4px;
white-space: nowrap;
}
div.combobox a:hover,
div.combobox a.selected {
background: #e1e8ff;
color: #154dff;
}
div.combobox a:first-child {
border-top: 0;
}
div.combobox-carat {
cursor: pointer;
padding:0 5px;
vertical-align:middle;
}
+1 -5
View File
@@ -150,11 +150,6 @@ path.stroke {
stroke-width: 2;
}
path.stroke,
path.casing {
shape-rendering: optimizeSpeed;
}
path.shadow {
pointer-events: stroke;
stroke-width: 10;
@@ -725,6 +720,7 @@ text.point {
}
/* Ensure drawing doesn't interact with area fills. */
.mode-add-point .area,
.mode-draw-line .area,
.mode-draw-area .area,
.mode-add-line .area,
+19 -11
View File
@@ -76,15 +76,18 @@
<script src='js/id/actions/add_entity.js'></script>
<script src='js/id/actions/add_vertex.js'></script>
<script src='js/id/actions/change_tags.js'></script>
<script src='js/id/actions/delete_multiple.js'></script>
<script src='js/id/actions/delete_node.js'></script>
<script src="js/id/actions/delete_relation.js"></script>
<script src="js/id/actions/delete_way.js"></script>
<script src='js/id/actions/disconnect.js'></script>
<script src='js/id/actions/join.js'></script>
<script src='js/id/actions/move_node.js'></script>
<script src='js/id/actions/move_way.js'></script>
<script src='js/id/actions/circularize.js'></script>
<script src='js/id/actions/noop.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/unjoin_node.js'></script>
<script src='js/id/actions/reverse.js'></script>
<script src='js/id/actions/split.js'></script>
<script src='js/id/behavior.js'></script>
<script src='js/id/behavior/add_way.js'></script>
@@ -110,11 +113,13 @@
<script src='js/id/operations.js'></script>
<script src='js/id/operations/circularize.js'></script>
<script src='js/id/operations/delete.js'></script>
<script src='js/id/operations/disconnect.js'></script>
<script src='js/id/operations/merge.js'></script>
<script src='js/id/operations/move.js'></script>
<script src='js/id/operations/reverse.js'></script>
<script src='js/id/operations/split.js'></script>
<script src='js/id/operations/unjoin.js'></script>
<script src='js/id/graph/difference.js'></script>
<script src='js/id/graph/entity.js'></script>
<script src='js/id/graph/graph.js'></script>
<script src='js/id/graph/history.js'></script>
@@ -122,9 +127,8 @@
<script src='js/id/graph/relation.js'></script>
<script src='js/id/graph/way.js'></script>
<script src='js/id/controller.js'></script>
<script src='js/id/validate.js'></script>
<script src='js/id/connection.js'></script>
<script src='js/id/validate.js'></script>
<script src='locale/locale.js'></script>
<script src='locale/en.js'></script>
@@ -134,12 +138,15 @@
locale.current = 'en';
d3.json('keys.json', function(err, keys) {
var id = iD();
id.connection().keys(keys)
.url('http://api06.dev.openstreetmap.org');
d3.select("#iD").call(id);
});
</script></body>
id.connection()
.keys(keys)
.url('http://api06.dev.openstreetmap.org');
d3.select("#iD")
.call(id.ui())
});
</script>
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-38039653-2']);
@@ -150,4 +157,5 @@ _gaq.push(['_trackPageview']);
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
</body>
</html>
+11 -3
View File
@@ -16,8 +16,16 @@
</head>
<body>
<div id="iD"></div><script>
var id = iD();
id.connection().url('http://api06.dev.openstreetmap.org');
d3.select("#iD").call(id);
locale.current = 'en';
d3.json('keys.json', function(err, keys) {
var id = iD();
id.connection()
.keys(keys)
.url('http://api06.dev.openstreetmap.org');
d3.select("#iD")
.call(id.ui())
});
</script></body>
</html>
+16 -14
View File
@@ -1,12 +1,11 @@
iD.actions.Circularize = function(wayId, map) {
iD.actions.Circularize = function(wayId, projection) {
var action = function(graph) {
var way = graph.entity(wayId),
nodes = graph.childNodes(way),
tags = {}, key, role;
nodes = _.uniq(graph.childNodes(way));
var points = nodes.map(function(n) {
return map.projection(n.loc);
return projection(n.loc);
}),
centroid = d3.geom.polygon(points).centroid(),
radius = d3.median(points, function(p) {
@@ -15,14 +14,12 @@ iD.actions.Circularize = function(wayId, map) {
circular_nodes = [];
for (var i = 0; i < 12; i++) {
circular_nodes.push(iD.Node({ loc: map.projection.invert([
circular_nodes.push(iD.Node({ loc: projection.invert([
centroid[0] + Math.cos((i / 12) * Math.PI * 2) * radius,
centroid[1] + Math.sin((i / 12) * Math.PI * 2) * radius])
}));
}
circular_nodes.push(circular_nodes[0]);
for (i = 0; i < nodes.length; i++) {
if (graph.parentWays(nodes[i]).length > 1) {
var closest, closest_dist = Infinity, dist;
@@ -34,10 +31,6 @@ iD.actions.Circularize = function(wayId, map) {
}
}
circular_nodes.splice(closest, 1, nodes[i]);
if (closest === 0) circular_nodes.splice(circular_nodes.length - 1, 1, nodes[i]);
else if (closest === circular_nodes.length - 1) circular_nodes.splice(0, 1, nodes[i]);
} else {
graph = graph.remove(nodes[i]);
}
}
@@ -45,9 +38,18 @@ iD.actions.Circularize = function(wayId, map) {
graph = graph.replace(circular_nodes[i]);
}
return graph.replace(way.update({
nodes: _.pluck(circular_nodes, 'id')
}));
var ids = _.pluck(circular_nodes, 'id'),
difference = _.difference(_.uniq(way.nodes), ids);
ids.push(ids[0]);
graph = graph.replace(way.update({nodes: ids}));
for (i = 0; i < difference.length; i++) {
graph = iD.actions.DeleteNode(difference[i])(graph);
}
return graph;
};
action.enabled = function(graph) {
+15
View File
@@ -0,0 +1,15 @@
iD.actions.DeleteMultiple = function(ids) {
return function(graph) {
var actions = {
way: iD.actions.DeleteWay,
node: iD.actions.DeleteNode,
relation: iD.actions.DeleteRelation
};
ids.forEach(function (id) {
graph = actions[graph.entity(id).type](id)(graph);
});
return graph;
};
};
+13
View File
@@ -0,0 +1,13 @@
// https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/DeleteRelationAction.as
iD.actions.DeleteRelation = function(relationId) {
return function(graph) {
var relation = graph.entity(relationId);
graph.parentRelations(relation)
.forEach(function(parent) {
graph = graph.replace(parent.removeMember(relationId));
});
return graph.remove(relation);
};
};
@@ -1,4 +1,4 @@
// Unjoin the ways at the given node.
// Disconect 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
@@ -8,7 +8,7 @@
// 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) {
iD.actions.Disconnect = function(nodeId, newNodeId) {
var action = function(graph) {
if (!action.enabled(graph))
return graph;
+65
View File
@@ -0,0 +1,65 @@
// Join ways at the end node they share.
//
// This is the inverse of `iD.actions.Split`.
//
// Reference:
// https://github.com/systemed/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/MergeWaysAction.as
// https://github.com/openstreetmap/josm/blob/mirror/src/org/openstreetmap/josm/actions/CombineWayAction.java
//
iD.actions.Join = function(idA, idB) {
var action = function(graph) {
var a = graph.entity(idA),
b = graph.entity(idB),
nodes, tags;
if (a.first() === b.first()) {
// a <-- b ==> c
// Expected result:
// a <-- b <-- c
nodes = b.nodes.slice().reverse().concat(a.nodes.slice(1));
} else if (a.first() === b.last()) {
// a <-- b <== c
// Expected result:
// a <-- b <-- c
nodes = b.nodes.concat(a.nodes.slice(1));
} else if (a.last() === b.first()) {
// a --> b ==> c
// Expected result:
// a --> b --> c
nodes = a.nodes.concat(b.nodes.slice(1));
} else if (a.last() === b.last()) {
// a --> b <== c
// Expected result:
// a --> b --> c
nodes = a.nodes.concat(b.nodes.slice().reverse().slice(1));
}
graph.parentRelations(b)
.forEach(function (parent) {
var memberA = parent.memberById(idA),
memberB = parent.memberById(idB);
if (!memberA) {
graph = graph.replace(parent.addMember({id: idA, role: memberB.role}));
}
});
graph = graph.replace(a.mergeTags(b.tags).update({nodes: nodes}));
graph = iD.actions.DeleteWay(idB)(graph);
return graph;
};
action.enabled = function(graph) {
var a = graph.entity(idA),
b = graph.entity(idB);
return a.first() === b.first() ||
a.first() === b.last() ||
a.last() === b.first() ||
a.last() === b.last();
};
return action;
};
@@ -27,7 +27,7 @@
http://wiki.openstreetmap.org/wiki/Route#Members
http://josm.openstreetmap.de/browser/josm/trunk/src/org/openstreetmap/josm/corrector/ReverseWayTagCorrector.java
*/
iD.actions.ReverseWay = function(wayId) {
iD.actions.Reverse = function(wayId) {
var replacements = [
[/:right$/, ':left'], [/:left$/, ':right'],
[/:forward$/, ':backward'], [/:backward$/, ':forward']
@@ -1,5 +1,7 @@
// Split a way at the given node.
//
// This is the inverse of `iD.actions.Join`.
//
// For testing convenience, accepts an ID to assign to the new way.
// Normally, this will be undefined and the way will automatically
// be assigned a new ID.
@@ -7,7 +9,7 @@
// Reference:
// https://github.com/systemed/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/SplitWayAction.as
//
iD.actions.SplitWay = function(nodeId, newWayId) {
iD.actions.Split = function(nodeId, newWayId) {
function candidateWays(graph) {
var node = graph.entity(nodeId),
parents = graph.parentWays(node);
+9 -9
View File
@@ -1,8 +1,6 @@
iD.behavior.AddWay = function(mode) {
var map = mode.map,
controller = mode.controller,
event = d3.dispatch('start', 'startFromWay', 'startFromNode', 'startFromMidpoint'),
draw = iD.behavior.Draw(map);
iD.behavior.AddWay = function(context) {
var event = d3.dispatch('start', 'startFromWay', 'startFromNode', 'startFromMidpoint'),
draw = iD.behavior.Draw(context);
var addWay = function(surface) {
draw.on('click', event.start)
@@ -12,7 +10,8 @@ iD.behavior.AddWay = function(mode) {
.on('cancel', addWay.cancel)
.on('finish', addWay.cancel);
map.fastEnable(false)
context.map()
.fastEnable(false)
.minzoom(16)
.dblclickEnable(false);
@@ -20,19 +19,20 @@ iD.behavior.AddWay = function(mode) {
};
addWay.off = function(surface) {
map.fastEnable(true)
context.map()
.fastEnable(true)
.minzoom(0)
.tail(false);
window.setTimeout(function() {
map.dblclickEnable(true);
context.map().dblclickEnable(true);
}, 1000);
surface.call(draw.off);
};
addWay.cancel = function() {
controller.enter(iD.modes.Browse());
context.enter(iD.modes.Browse(context));
};
return d3.rebind(addWay, event, 'on');
+6 -9
View File
@@ -1,16 +1,13 @@
iD.behavior.DragMidpoint = function(mode) {
var history = mode.history,
projection = mode.map.projection;
iD.behavior.DragMidpoint = function(context) {
var behavior = iD.behavior.drag()
.delegate(".midpoint")
.origin(function(d) {
return projection(d.loc);
return context.projection(d.loc);
})
.on('start', function(d) {
var node = iD.Node();
history.perform(iD.actions.AddMidpoint(d, node));
context.perform(iD.actions.AddMidpoint(d, node));
var vertex = d3.selectAll('.vertex')
.filter(function(data) { return data.id === node.id; });
@@ -19,11 +16,11 @@ iD.behavior.DragMidpoint = function(mode) {
})
.on('move', function(d) {
d3.event.sourceEvent.stopPropagation();
history.replace(
iD.actions.MoveNode(d.id, projection.invert(d3.event.point)));
context.replace(
iD.actions.MoveNode(d.id, context.projection.invert(d3.event.point)));
})
.on('end', function() {
history.replace(
context.replace(
iD.actions.Noop(),
t('operations.add.annotation.vertex'));
});
+10 -12
View File
@@ -1,8 +1,6 @@
iD.behavior.DragNode = function(mode) {
var history = mode.history,
size = mode.map.size(),
nudgeInterval,
projection = mode.map.projection;
iD.behavior.DragNode = function(context) {
var size = context.map().size(),
nudgeInterval;
function edge(point) {
var pad = [30, 100, 30, 100];
@@ -16,7 +14,7 @@ iD.behavior.DragNode = function(mode) {
function startNudge(nudge) {
if (nudgeInterval) window.clearInterval(nudgeInterval);
nudgeInterval = window.setInterval(function() {
mode.map.pan(nudge).redraw();
context.map().pan(nudge).redraw();
}, 50);
}
@@ -26,16 +24,16 @@ iD.behavior.DragNode = function(mode) {
}
function annotation(entity) {
return t('operations.move.annotation.' + entity.geometry(mode.history.graph()));
return t('operations.move.annotation.' + entity.geometry(context.graph()));
}
return iD.behavior.drag()
.delegate(".node")
.origin(function(entity) {
return projection(entity.loc);
return context.projection(entity.loc);
})
.on('start', function() {
history.perform(
context.perform(
iD.actions.Noop());
})
.on('move', function(entity) {
@@ -45,13 +43,13 @@ iD.behavior.DragNode = function(mode) {
if (nudge) startNudge(nudge);
else stopNudge();
history.replace(
iD.actions.MoveNode(entity.id, projection.invert(d3.event.point)),
context.replace(
iD.actions.MoveNode(entity.id, context.projection.invert(d3.event.point)),
annotation(entity));
})
.on('end', function(entity) {
stopNudge();
history.replace(
context.replace(
iD.actions.Noop(),
annotation(entity));
});
+12 -12
View File
@@ -1,7 +1,8 @@
iD.behavior.Draw = function(map) {
iD.behavior.Draw = function(context) {
var event = d3.dispatch('move', 'click', 'clickWay', 'clickNode', 'clickMidpoint', 'undo', 'cancel', 'finish'),
keybinding = d3.keybinding('draw'),
down, surface, hover;
hover = iD.behavior.Hover(),
down;
function datum() {
if (d3.event.altKey) {
@@ -28,7 +29,7 @@ iD.behavior.Draw = function(map) {
function click() {
var d = datum();
if (d.type === 'way') {
var choice = iD.geo.chooseIndex(d, d3.mouse(map.surface.node()), map);
var choice = iD.geo.chooseIndex(d, d3.mouse(context.surface().node()), context);
event.clickWay(d, choice.loc, choice.index);
} else if (d.type === 'node') {
@@ -38,19 +39,19 @@ iD.behavior.Draw = function(map) {
event.clickMidpoint(d);
} else {
event.click(map.mouseCoordinates());
event.click(context.map().mouseCoordinates());
}
}
function keydown() {
if (d3.event.keyCode === d3.keybinding.modifierCodes.alt) {
surface.call(hover.off);
context.uninstall(hover);
}
}
function keyup() {
if (d3.event.keyCode === d3.keybinding.modifierCodes.alt) {
surface.call(hover);
context.install(hover);
}
}
@@ -70,8 +71,7 @@ iD.behavior.Draw = function(map) {
}
function draw(selection) {
surface = selection;
hover = iD.behavior.Hover();
context.install(hover);
keybinding
.on('⌫', backspace)
@@ -83,8 +83,7 @@ iD.behavior.Draw = function(map) {
.on('mousedown.draw', mousedown)
.on('mouseup.draw', mouseup)
.on('mousemove.draw', mousemove)
.on('click.draw', click)
.call(hover);
.on('click.draw', click);
d3.select(document)
.call(keybinding)
@@ -95,12 +94,13 @@ iD.behavior.Draw = function(map) {
}
draw.off = function(selection) {
context.uninstall(hover);
selection
.on('mousedown.draw', null)
.on('mouseup.draw', null)
.on('mousemove.draw', null)
.on('click.draw', null)
.call(hover.off);
.on('click.draw', null);
d3.select(document)
.call(keybinding.off)
+40 -36
View File
@@ -1,35 +1,35 @@
iD.behavior.DrawWay = function(wayId, index, mode, baseGraph) {
var map = mode.map,
history = mode.history,
controller = mode.controller,
way = history.graph().entity(wayId),
iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) {
var way = context.entity(wayId),
finished = false,
annotation = t((way.isDegenerate() ?
'operations.start.annotation.' :
'operations.continue.annotation.') + way.geometry(history.graph())),
draw = iD.behavior.Draw(map);
'operations.continue.annotation.') + context.geometry(wayId)),
draw = iD.behavior.Draw(context);
var node = iD.Node({loc: map.mouseCoordinates()}),
var node = iD.Node({loc: context.map().mouseCoordinates()}),
nodeId = node.id;
history[way.isDegenerate() ? 'replace' : 'perform'](
context[way.isDegenerate() ? 'replace' : 'perform'](
iD.actions.AddEntity(node),
iD.actions.AddVertex(wayId, node.id, index));
function move(datum) {
var loc = map.mouseCoordinates();
var loc = context.map().mouseCoordinates();
if (datum.type === 'node' || datum.type === 'midpoint') {
if (datum.type === 'node') {
loc = datum.loc;
} else if (datum.type === 'way') {
loc = iD.geo.chooseIndex(datum, d3.mouse(map.surface.node()), map).loc;
} else if (datum.type === 'midpoint' || datum.type === 'way') {
var way = datum.type === 'way' ?
datum :
context.entity(datum.ways[0].id);
loc = iD.geo.chooseIndex(way, d3.mouse(context.surface().node()), context).loc;
}
history.replace(iD.actions.MoveNode(nodeId, loc));
context.replace(iD.actions.MoveNode(nodeId, loc));
}
function undone() {
controller.enter(iD.modes.Browse());
context.enter(iD.modes.Browse(context));
}
var drawWay = function(surface) {
@@ -38,11 +38,12 @@ iD.behavior.DrawWay = function(wayId, index, mode, baseGraph) {
.on('clickWay', drawWay.addWay)
.on('clickNode', drawWay.addNode)
.on('clickMidpoint', drawWay.addMidpoint)
.on('undo', history.undo)
.on('undo', context.undo)
.on('cancel', drawWay.cancel)
.on('finish', drawWay.finish);
map.fastEnable(false)
context.map()
.fastEnable(false)
.minzoom(16)
.dblclickEnable(false);
@@ -51,26 +52,29 @@ iD.behavior.DrawWay = function(wayId, index, mode, baseGraph) {
.filter(function (d) { return d.id === wayId || d.id === nodeId; })
.classed('active', true);
history.on('undone.draw', undone);
context.history()
.on('undone.draw', undone);
};
drawWay.off = function(surface) {
if (!finished)
history.pop();
context.pop();
map.fastEnable(true)
context.map()
.fastEnable(true)
.minzoom(0)
.tail(false);
window.setTimeout(function() {
map.dblclickEnable(true);
context.map().dblclickEnable(true);
}, 1000);
surface.call(draw.off)
.selectAll('.way, .node')
.classed('active', false);
history.on('undone.draw', null);
context.history()
.on('undone.draw', null);
};
function ReplaceTemporaryNode(newNode) {
@@ -85,74 +89,74 @@ iD.behavior.DrawWay = function(wayId, index, mode, baseGraph) {
drawWay.add = function(loc) {
var newNode = iD.Node({loc: loc});
history.replace(
context.replace(
iD.actions.AddEntity(newNode),
ReplaceTemporaryNode(newNode),
annotation);
finished = true;
controller.enter(mode);
context.enter(mode);
};
// Connect the way to an existing way.
drawWay.addWay = function(way, loc, wayIndex) {
var newNode = iD.Node({loc: loc});
history.perform(
context.perform(
iD.actions.AddEntity(newNode),
iD.actions.AddVertex(way.id, newNode.id, wayIndex),
ReplaceTemporaryNode(newNode),
annotation);
finished = true;
controller.enter(mode);
context.enter(mode);
};
// Connect the way to an existing node and continue drawing.
drawWay.addNode = function(node) {
history.perform(
context.perform(
ReplaceTemporaryNode(node),
annotation);
finished = true;
controller.enter(mode);
context.enter(mode);
};
// Add a midpoint, connect the way to it, and continue drawing.
drawWay.addMidpoint = function(midpoint) {
var node = iD.Node();
history.perform(
context.perform(
iD.actions.AddMidpoint(midpoint, node),
ReplaceTemporaryNode(node),
annotation);
finished = true;
controller.enter(mode);
context.enter(mode);
};
// Finish the draw operation, removing the temporary node. If the way has enough
// nodes to be valid, it's selected. Otherwise, return to browse mode.
drawWay.finish = function() {
history.pop();
context.pop();
finished = true;
var way = history.graph().entity(wayId);
var way = context.entity(wayId);
if (way) {
controller.enter(iD.modes.Select([way.id], true));
context.enter(iD.modes.Select(context, [way.id], true));
} else {
controller.enter(iD.modes.Browse());
context.enter(iD.modes.Browse(context));
}
};
// Cancel the draw operation and return to browse, deleting everything drawn.
drawWay.cancel = function() {
history.perform(
context.perform(
d3.functor(baseGraph),
t('operations.cancel_draw.annotation'));
finished = true;
controller.enter(iD.modes.Browse());
context.enter(iD.modes.Browse(context));
};
return d3.rebind(drawWay, event, 'on');
+15 -14
View File
@@ -1,4 +1,4 @@
iD.behavior.Hash = function(controller, map) {
iD.behavior.Hash = function(context) {
var s0 = null, // cached location.hash
lat = 90 - 1e-8; // allowable latitude range
@@ -27,13 +27,13 @@ iD.behavior.Hash = function(controller, map) {
};
var move = _.throttle(function() {
var s1 = formatter(map);
var s1 = formatter(context.map());
if (s0 !== s1) location.replace(s0 = s1); // don't recenter the map!
}, 100);
function hashchange() {
if (location.hash === s0) return; // ignore spurious hashchange events
if (parser(map, (s0 = location.hash).substring(1))) {
if (parser(context.map(), (s0 = location.hash).substring(1))) {
move(); // replace bogus hash
}
}
@@ -42,24 +42,24 @@ iD.behavior.Hash = function(controller, map) {
// do so before any features are loaded. thus wait for the feature to
// be loaded and then select
function willselect(id) {
map.on('drawn.hash', function() {
var entity = map.history().graph().entity(id);
if (entity === undefined) return;
else selectoff();
controller.enter(iD.modes.Select([entity.id]));
map.on('drawn.hash', null);
context.map().on('drawn.hash', function() {
if (!context.entity(id)) return;
selectoff();
context.enter(iD.modes.Select(context, [id]));
});
controller.on('enter.hash', function() {
if (controller.mode.id !== 'browse') selectoff();
context.on('enter.hash', function() {
if (context.mode().id !== 'browse') selectoff();
});
}
function selectoff() {
map.on('drawn.hash', null);
context.map().on('drawn.hash', null);
}
function hash() {
map.on('move.hash', move);
context.map()
.on('move.hash', move);
d3.select(window)
.on('hashchange.hash', hashchange);
@@ -75,7 +75,8 @@ iD.behavior.Hash = function(controller, map) {
}
hash.off = function() {
map.on('move.hash', null);
context.map()
.on('move.hash', null);
d3.select(window)
.on('hashchange.hash', null);
+8 -6
View File
@@ -1,12 +1,14 @@
iD.behavior.Select = function(mode) {
var controller = mode.controller;
iD.behavior.Select = function(context) {
function click() {
var datum = d3.select(d3.event.target).datum();
if (datum instanceof iD.Entity) {
controller.enter(iD.modes.Select([datum.id]));
} else {
controller.enter(iD.modes.Browse());
if (d3.event.shiftKey) {
context.enter(iD.modes.Select(context, context.selection().concat([datum.id])));
} else {
context.enter(iD.modes.Select(context, [datum.id]));
}
} else if (!d3.event.shiftKey) {
context.enter(iD.modes.Browse(context));
}
}
+3 -10
View File
@@ -1,14 +1,13 @@
iD.Connection = function() {
iD.Connection = function(context) {
var event = d3.dispatch('auth', 'load'),
url = 'http://www.openstreetmap.org',
connection = {},
user = {},
version,
keys,
inflight = {},
loadedTiles = {},
oauth = iD.OAuth().url(url);
oauth = iD.OAuth(context).url(url);
function changesetUrl(changesetId) {
return url + '/browse/changeset/' + changesetId;
@@ -186,7 +185,7 @@ iD.Connection = function() {
content: JXON.stringify(connection.changesetJXON({
imagery_used: imagery_used.join(';'),
comment: comment,
created_by: 'iD ' + (version || '')
created_by: 'iD ' + iD.version
}))
}, function (err, changeset_id) {
if (err) return callback(err);
@@ -322,12 +321,6 @@ iD.Connection = function() {
return oauth.authenticate(done);
};
connection.version = function(_) {
if (!arguments.length) return version;
version = _;
return connection;
};
connection.bboxFromAPI = bboxFromAPI;
connection.changesetUrl = changesetUrl;
connection.loadFromURL = loadFromURL;
-23
View File
@@ -1,23 +0,0 @@
// A controller holds a single action at a time and calls `.enter` and `.exit`
// to bind and unbind actions.
iD.Controller = function(map, history) {
var event = d3.dispatch('enter', 'exit');
var controller = { mode: null };
controller.enter = function(mode) {
mode.controller = controller;
mode.history = history;
mode.map = map;
if (controller.mode) {
controller.mode.exit();
event.exit(controller.mode);
}
mode.enter();
controller.mode = mode;
event.enter(mode);
};
return d3.rebind(controller, event, 'on');
};
+3 -3
View File
@@ -14,11 +14,11 @@ iD.geo.dist = function(a, b) {
Math.pow(a[1] - b[1], 2));
};
iD.geo.chooseIndex = function(way, point, map) {
iD.geo.chooseIndex = function(way, point, context) {
var dist = iD.geo.dist,
graph = map.history().graph(),
graph = context.graph(),
nodes = graph.childNodes(way),
projNodes = nodes.map(function(n) { return map.projection(n.loc); });
projNodes = nodes.map(function(n) { return context.projection(n.loc); });
for (var i = 0, changes = []; i < projNodes.length - 1; i++) {
changes[i] =
+121
View File
@@ -0,0 +1,121 @@
/*
iD.Difference represents the difference between two graphs.
It knows how to calculate the set of entities that were
created, modified, or deleted, and also contains the logic
for recursively extending a difference to the complete set
of entities that will require a redraw, taking into account
child and parent relationships.
*/
iD.Difference = function (base, head) {
var changes = {}, length = 0;
_.each(head.entities, function(h, id) {
var b = base.entities[id];
if (h !== b) {
changes[id] = {base: b, head: h};
length++;
}
});
_.each(base.entities, function(b, id) {
var h = head.entities[id];
if (!changes[id] && h !== b) {
changes[id] = {base: b, head: h};
length++;
}
});
var difference = {};
difference.length = function () {
return length;
};
difference.changes = function() {
return changes;
};
difference.extantIDs = function() {
var result = [];
_.each(changes, function(change, id) {
if (change.head) result.push(id);
});
return result;
};
difference.modified = function() {
var result = [];
_.each(changes, function(change) {
if (change.base && change.head) result.push(change.head);
});
return result;
};
difference.created = function() {
var result = [];
_.each(changes, function(change) {
if (!change.base && change.head) result.push(change.head);
});
return result;
};
difference.deleted = function() {
var result = [];
_.each(changes, function(change) {
if (change.base && !change.head) result.push(change.base);
});
return result;
};
difference.complete = function(extent) {
var result = {}, id, change;
function addParents(parents) {
for (var i = 0; i < parents.length; i++) {
var parent = parents[i];
if (parent.id in result)
continue;
result[parent.id] = parent;
addParents(head.parentRelations(parent));
}
}
for (id in changes) {
change = changes[id];
var h = change.head,
b = change.base,
entity = h || b;
if (extent && !entity.intersects(extent, h ? head : base))
continue;
result[id] = h;
if (entity.type === 'way') {
var nh = h ? h.nodes : [],
nb = b ? b.nodes : [],
diff;
diff = _.difference(nh, nb);
for (var i = 0; i < diff.length; i++) {
result[diff[i]] = head.entity(diff[i]);
}
diff = _.difference(nb, nh);
for (var i = 0; i < diff.length; i++) {
result[diff[i]] = head.entity(diff[i]);
}
}
addParents(head.parentWays(entity));
addParents(head.parentRelations(entity));
}
return result;
};
return difference;
};
+13 -8
View File
@@ -43,7 +43,6 @@ iD.Entity.prototype = {
if (!this.id && this.type) {
this.id = iD.Entity.id(this.type);
this._updated = true;
}
if (iD.debug) {
@@ -63,15 +62,21 @@ iD.Entity.prototype = {
},
update: function(attrs) {
return iD.Entity(this, attrs, {_updated: true});
return iD.Entity(this, attrs);
},
created: function() {
return this._updated && this.osmId().charAt(0) === '-';
},
modified: function() {
return this._updated && this.osmId().charAt(0) !== '-';
mergeTags: function(tags) {
var merged = _.clone(this.tags);
for (var k in tags) {
var t1 = merged[k],
t2 = tags[k];
if (t1 && t1 !== t2) {
merged[k] = t1 + "; " + t2;
} else {
merged[k] = t2;
}
}
return this.update({tags: merged});
},
intersects: function(extent, resolver) {
-60
View File
@@ -232,65 +232,5 @@ iD.Graph.prototype = {
}
}
return items;
},
difference: function (graph) {
function diff(a, b) {
var result = [],
keys = Object.keys(a.entities),
entity, oldentity, id, i;
for (i = 0; i < keys.length; i++) {
id = keys[i];
entity = a.entities[id];
oldentity = b.entities[id];
if (entity !== oldentity) {
// maybe adding affected children better belongs in renderer/map.js?
if (entity && entity.type === 'way' &&
oldentity && oldentity.type === 'way') {
result = result
.concat(_.difference(entity.nodes, oldentity.nodes))
.concat(_.difference(oldentity.nodes, entity.nodes));
} else if (entity && entity.type === 'way') {
result = result.concat(entity.nodes);
} else if (oldentity && oldentity.type === 'way') {
result = result.concat(oldentity.nodes);
}
result.push(id);
}
}
return result;
}
return _.unique(diff(this, graph).concat(diff(graph, this)).sort());
},
modified: function() {
var result = [], base = this.base().entities;
_.each(this.entities, function(entity, id) {
if (entity && base[id]) result.push(id);
});
return result;
},
created: function() {
var result = [], base = this.base().entities;
_.each(this.entities, function(entity, id) {
if (entity && !base[id]) result.push(id);
});
return result;
},
deleted: function() {
var result = [], base = this.base().entities;
_.each(this.entities, function(entity, id) {
if (!entity && base[id]) result.push(id);
});
return result;
}
};
+23 -23
View File
@@ -21,7 +21,9 @@ iD.History = function() {
}
function change(previous) {
dispatch.change(history.graph().difference(previous));
var difference = iD.Difference(previous, history.graph());
dispatch.change(difference);
return difference;
}
var history = {
@@ -33,6 +35,8 @@ iD.History = function() {
for (var i = 0; i < stack.length; i++) {
stack[i].graph.rebase(entities);
}
dispatch.change();
},
perform: function () {
@@ -42,7 +46,7 @@ iD.History = function() {
stack.push(perform(arguments));
index++;
change(previous);
return change(previous);
},
replace: function () {
@@ -51,7 +55,7 @@ iD.History = function() {
// assert(index == stack.length - 1)
stack[index] = perform(arguments);
change(previous);
return change(previous);
},
pop: function () {
@@ -60,7 +64,7 @@ iD.History = function() {
if (index > 0) {
index--;
stack.pop();
change(previous);
return change(previous);
}
},
@@ -80,7 +84,7 @@ iD.History = function() {
}
dispatch.undone();
change(previous);
return change(previous);
},
redo: function () {
@@ -92,7 +96,7 @@ iD.History = function() {
}
dispatch.redone();
change(previous);
return change(previous);
},
undoAnnotation: function () {
@@ -111,31 +115,27 @@ iD.History = function() {
}
},
changes: function () {
var initial = stack[0].graph,
current = stack[index].graph;
difference: function () {
var base = stack[0].graph,
head = stack[index].graph;
return iD.Difference(base, head);
},
changes: function () {
var difference = history.difference();
return {
modified: current.modified().map(function (id) {
return current.entity(id);
}),
created: current.created().map(function (id) {
return current.entity(id);
}),
deleted: current.deleted().map(function (id) {
return initial.entity(id);
})
};
modified: difference.modified(),
created: difference.created(),
deleted: difference.deleted()
}
},
hasChanges: function() {
return !!this.numChanges();
return this.difference().length() > 0;
},
numChanges: function() {
return d3.sum(d3.values(this.changes()).map(function(c) {
return c.length;
}));
return this.difference().length();
},
imagery_used: function(source) {
+89 -266
View File
@@ -1,280 +1,103 @@
window.iD = function(container) {
// the reported, displayed version of iD.
var version = '0.0.0-alpha1';
var connection = iD.Connection()
.version(version),
window.iD = function () {
var context = {},
history = iD.History(),
map = iD.Map()
.connection(connection)
.history(history),
controller = iD.Controller(map, history);
storage = localStorage || {},
dispatch = d3.dispatch('enter', 'exit'),
mode,
container,
ui = iD.ui(context),
map = iD.Map(context);
map.background.source(iD.BackgroundSource.Bing);
function editor(container) {
if (!iD.supported()) {
container.html('This editor is supported in Firefox, Chrome, Safari, Opera, ' +
'and Internet Explorer 9 and above. Please upgrade your browser ' +
'or use Potlatch 2 to edit the map.')
.style('text-align:center;font-style:italic;');
return;
}
function hintprefix(x, y) {
return '<span>' + y + '</span>' + '<div class="keyhint-wrap"><span class="keyhint"> ' + x + '</span></div>';
}
var m = container.append('div')
.attr('id', 'map')
.call(map);
var bar = container.append('div')
.attr('id', 'bar')
.attr('class','pad1 fillD');
var limiter = bar.append('div')
.attr('class', 'limiter');
var buttons_joined = limiter.append('div')
.attr('class', 'button-wrap joined col4');
var buttons = buttons_joined.selectAll('button.add-button')
.data([iD.modes.Browse(), iD.modes.AddPoint(), iD.modes.AddLine(), iD.modes.AddArea()])
.enter().append('button')
.attr('tabindex', -1)
.attr('class', function (mode) { return mode.title + ' add-button col3'; })
.call(bootstrap.tooltip().placement('bottom').html(true))
.attr('data-original-title', function (mode) {
return hintprefix(mode.key, mode.description);
})
.on('click.editor', function (mode) { controller.enter(mode); });
function disableTooHigh() {
if (map.editable()) {
notice.message(false);
buttons.attr('disabled', null);
} else {
buttons.attr('disabled', 'disabled');
notice.message(true);
controller.enter(iD.modes.Browse());
}
}
var notice = iD.ui.notice(limiter)
.message(false)
.on('zoom', function() { map.zoom(16); });
map.on('move.editor', _.debounce(function() {
disableTooHigh();
contributors.call(iD.ui.contributors(map));
}, 500));
buttons.append('span')
.attr('class', function(d) {
return d.id + ' icon icon-pre-text';
});
buttons.append('span').attr('class', 'label').text(function (mode) { return mode.title; });
controller.on('enter.editor', function (entered) {
buttons.classed('active', function (mode) { return entered.button === mode.button; });
container.classed("mode-" + entered.id, true);
});
controller.on('exit.editor', function (exited) {
container.classed("mode-" + exited.id, false);
});
var undo_buttons = limiter.append('div')
.attr('class', 'button-wrap joined col1'),
undo_tooltip = bootstrap.tooltip().placement('bottom').html(true);
undo_buttons.append('button')
.attr({ id: 'undo', 'class': 'col6' })
.property('disabled', true)
.html("<span class='undo icon'></span><small></small>")
.on('click.editor', history.undo)
.call(undo_tooltip);
undo_buttons.append('button')
.attr({ id: 'redo', 'class': 'col6' })
.property('disabled', true)
.html("<span class='redo icon'><small></small>")
.on('click.editor', history.redo)
.call(undo_tooltip);
var save_button = limiter.append('div').attr('class','button-wrap col1').append('button')
.attr('class', 'save col12')
.call(iD.ui.save().map(map).controller(controller));
var zoom = container.append('div')
.attr('class', 'zoombuttons map-control')
.selectAll('button')
.data([['zoom-in', '+', map.zoomIn, 'Zoom In'], ['zoom-out', '-', map.zoomOut, 'Zoom Out']])
.enter()
.append('button')
.attr('tabindex', -1)
.attr('class', function(d) { return d[0]; })
.attr('title', function(d) { return d[3]; })
.on('click.editor', function(d) { return d[2](); })
.append('span')
.attr('class', function(d) {
return d[0] + ' icon';
});
if (navigator.geolocation) {
container.append('div')
.call(iD.ui.geolocate(map));
}
var gc = container.append('div').attr('class', 'geocode-control map-control')
.call(iD.ui.geocoder().map(map));
container.append('div').attr('class', 'map-control layerswitcher-control')
.call(iD.ui.layerswitcher(map));
container.append('div')
.style('display', 'none')
.attr('class', 'inspector-wrap fr col5');
var about = container.append('div')
.attr('class','col12 about-block fillD pad1');
about.append('div')
.attr('class', 'user-container')
.append('div')
.attr('class', 'hello');
var aboutList = about.append('ul')
.attr('id','about')
.attr('class','link-list');
var linkList = aboutList.append('ul')
.attr('id','about')
.attr('class','pad1 fillD about-block link-list');
linkList.append('li').append('a').attr('target', '_blank')
.attr('href', 'http://github.com/systemed/iD').text(version);
linkList.append('li').append('a').attr('target', '_blank')
.attr('href', 'http://github.com/systemed/iD/issues').text('report a bug');
var imagery = linkList.append('li').attr('id', 'attribution');
imagery.append('span').text('imagery');
imagery.append('a').attr('target', '_blank')
.attr('href', 'http://opengeodata.org/microsoft-imagery-details').text(' provided by bing');
linkList.append('li').attr('class', 'source-switch').append('a').attr('href', '#')
.text('dev')
.on('click.editor', function() {
d3.event.preventDefault();
if (d3.select(this).classed('live')) {
map.flush().connection()
.url('http://api06.dev.openstreetmap.org');
d3.select(this).text('dev').classed('live', false);
} else {
map.flush().connection()
.url('http://www.openstreetmap.org');
d3.select(this).text('live').classed('live', true);
}
});
var contributors = linkList.append('li')
.attr('id', 'user-list');
contributors.append('span')
.attr('class', 'icon nearby icon-pre-text');
contributors.append('span')
.text('Viewing contributions by ');
contributors.append('span')
.attr('class', 'contributor-list');
contributors.append('span')
.attr('class', 'contributor-count');
history.on('change.editor', function() {
window.onbeforeunload = history.hasChanges() ? function() {
return 'You have unsaved changes.';
} : null;
var undo = history.undoAnnotation(),
redo = history.redoAnnotation();
function refreshTooltip(selection) {
if (selection.property('disabled')) {
selection.call(undo_tooltip.hide);
} else if (selection.property('tooltipVisible')) {
selection.call(undo_tooltip.show);
}
}
limiter.select('#undo')
.property('disabled', !undo)
.attr('data-original-title', hintprefix('⌘ + Z', undo))
.call(refreshTooltip);
limiter.select('#redo')
.property('disabled', !redo)
.attr('data-original-title', hintprefix('⌘ + ⇧ + Z', redo))
.call(refreshTooltip);
});
d3.select(window).on('resize.editor', function() {
map.size(m.size());
});
var keybinding = d3.keybinding('main')
.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(); });
[iD.modes.Browse(), iD.modes.AddPoint(), iD.modes.AddLine(), iD.modes.AddArea()].forEach(function(m) {
keybinding.on(m.key, function() { if (map.editable()) controller.enter(m); });
});
d3.select(document)
.call(keybinding);
var hash = iD.behavior.Hash(controller, map);
hash();
if (!hash.hadHash) {
map.centerZoom([-77.02271, 38.90085], 20);
}
d3.select('.user-container').call(iD.ui.userpanel(connection)
.on('logout.editor', connection.logout)
.on('login.editor', connection.authenticate));
controller.enter(iD.modes.Browse());
if (!localStorage.sawSplash) {
iD.ui.splash();
localStorage.sawSplash = true;
}
}
editor.connection = function(_) {
if (!arguments.length) return connection;
connection = _;
return editor;
context.storage = function(k, v) {
if (arguments.length === 1) return storage[k];
else storage[k] = v;
};
editor.map = function() {
return map;
// the connection requires .storage() to be available on calling.
var connection = iD.Connection(context);
connection.on('load.context', function (err, result) {
history.merge(result);
});
/* Straight accessors. Avoid using these if you can. */
context.ui = function() { return ui; };
context.connection = function() { return connection; };
context.history = function() { return history; };
context.map = function() { return map; };
/* History */
context.graph = history.graph;
context.perform = history.perform;
context.replace = history.replace;
context.pop = history.pop;
context.undo = history.undo;
context.redo = history.undo;
context.changes = history.changes;
/* Graph */
context.entity = function(id) {
return history.graph().entity(id);
};
editor.controller = function() {
return controller;
context.geometry = function(id) {
return context.entity(id).geometry(history.graph());
};
if (arguments.length) {
d3.select(container).call(editor);
}
/* Modes */
context.enter = function(newMode) {
if (mode) {
mode.exit();
dispatch.exit(mode);
}
return editor;
mode = newMode;
mode.enter();
dispatch.enter(mode);
};
context.mode = function() {
return mode;
};
context.selection = function() {
if (mode.id === 'select') {
return mode.selection();
} else {
return [];
}
};
/* Behaviors */
context.install = function(behavior) {
context.surface().call(behavior);
};
context.uninstall = function(behavior) {
context.surface().call(behavior.off);
};
/* Map */
context.background = function() { return map.background; };
context.surface = function() { return map.surface; };
context.projection = map.projection;
context.tail = map.tail;
context.redraw = map.redraw;
context.container = function(_) {
if (!arguments.length) return container;
container = _;
return context;
};
context.background()
.source(iD.BackgroundSource.Bing);
return d3.rebind(context, dispatch, 'on');
};
iD.version = '0.0.0-alpha1';
iD.supported = function() {
if (navigator.appName !== 'Microsoft Internet Explorer') {
return true;
+63 -69
View File
@@ -1,4 +1,4 @@
iD.modes.AddArea = function() {
iD.modes.AddArea = function(context) {
var mode = {
id: 'add-area',
button: 'area',
@@ -7,81 +7,75 @@ iD.modes.AddArea = function() {
key: t('modes.add_area.key')
};
var behavior,
defaultTags = {area: 'yes'};
mode.enter = function() {
var map = mode.map,
history = mode.history,
controller = mode.controller;
function start(loc) {
var graph = history.graph(),
node = iD.Node({loc: loc}),
way = iD.Way({tags: defaultTags});
history.perform(
iD.actions.AddEntity(node),
iD.actions.AddEntity(way),
iD.actions.AddVertex(way.id, node.id),
iD.actions.AddVertex(way.id, node.id));
controller.enter(iD.modes.DrawArea(way.id, graph));
}
function startFromWay(other, loc, index) {
var graph = history.graph(),
node = iD.Node({loc: loc}),
way = iD.Way({tags: defaultTags});
history.perform(
iD.actions.AddEntity(node),
iD.actions.AddEntity(way),
iD.actions.AddVertex(way.id, node.id),
iD.actions.AddVertex(way.id, node.id),
iD.actions.AddVertex(other.id, node.id, index));
controller.enter(iD.modes.DrawArea(way.id, graph));
}
function startFromNode(node) {
var graph = history.graph(),
way = iD.Way({tags: defaultTags});
history.perform(
iD.actions.AddEntity(way),
iD.actions.AddVertex(way.id, node.id),
iD.actions.AddVertex(way.id, node.id));
controller.enter(iD.modes.DrawArea(way.id, graph));
}
function startFromMidpoint(midpoint) {
var graph = history.graph(),
node = iD.Node(),
way = iD.Way({tags: defaultTags});
history.perform(
iD.actions.AddMidpoint(midpoint, node),
iD.actions.AddEntity(way),
iD.actions.AddVertex(way.id, node.id),
iD.actions.AddVertex(way.id, node.id));
controller.enter(iD.modes.DrawArea(way.id, graph));
}
behavior = iD.behavior.AddWay(mode)
var behavior = iD.behavior.AddWay(context)
.on('start', start)
.on('startFromWay', startFromWay)
.on('startFromNode', startFromNode)
.on('startFromMidpoint', startFromMidpoint);
.on('startFromMidpoint', startFromMidpoint),
defaultTags = {area: 'yes'};
mode.map.surface.call(behavior);
mode.map.tail(t('modes.add_area.tail'));
function start(loc) {
var graph = context.graph(),
node = iD.Node({loc: loc}),
way = iD.Way({tags: defaultTags});
context.perform(
iD.actions.AddEntity(node),
iD.actions.AddEntity(way),
iD.actions.AddVertex(way.id, node.id),
iD.actions.AddVertex(way.id, node.id));
context.enter(iD.modes.DrawArea(context, way.id, graph));
}
function startFromWay(other, loc, index) {
var graph = context.graph(),
node = iD.Node({loc: loc}),
way = iD.Way({tags: defaultTags});
context.perform(
iD.actions.AddEntity(node),
iD.actions.AddEntity(way),
iD.actions.AddVertex(way.id, node.id),
iD.actions.AddVertex(way.id, node.id),
iD.actions.AddVertex(other.id, node.id, index));
context.enter(iD.modes.DrawArea(context, way.id, graph));
}
function startFromNode(node) {
var graph = context.graph(),
way = iD.Way({tags: defaultTags});
context.perform(
iD.actions.AddEntity(way),
iD.actions.AddVertex(way.id, node.id),
iD.actions.AddVertex(way.id, node.id));
context.enter(iD.modes.DrawArea(context, way.id, graph));
}
function startFromMidpoint(midpoint) {
var graph = context.graph(),
node = iD.Node(),
way = iD.Way({tags: defaultTags});
context.perform(
iD.actions.AddMidpoint(midpoint, node),
iD.actions.AddEntity(way),
iD.actions.AddVertex(way.id, node.id),
iD.actions.AddVertex(way.id, node.id));
context.enter(iD.modes.DrawArea(context, way.id, graph));
}
mode.enter = function() {
context.install(behavior);
context.tail(t('modes.add_area.tail'));
};
mode.exit = function() {
mode.map.surface.call(behavior.off);
context.uninstall(behavior);
};
return mode;
+70 -76
View File
@@ -1,4 +1,4 @@
iD.modes.AddLine = function() {
iD.modes.AddLine = function(context) {
var mode = {
id: 'add-line',
button: 'line',
@@ -7,88 +7,82 @@ iD.modes.AddLine = function() {
key: t('modes.add_line.key')
};
var behavior,
defaultTags = {highway: 'residential'};
mode.enter = function() {
var map = mode.map,
history = mode.history,
controller = mode.controller;
function start(loc) {
var graph = history.graph(),
node = iD.Node({loc: loc}),
way = iD.Way({tags: defaultTags});
history.perform(
iD.actions.AddEntity(node),
iD.actions.AddEntity(way),
iD.actions.AddVertex(way.id, node.id));
controller.enter(iD.modes.DrawLine(way.id, 'forward', graph));
}
function startFromWay(other, loc, index) {
var graph = history.graph(),
node = iD.Node({loc: loc}),
way = iD.Way({tags: defaultTags});
history.perform(
iD.actions.AddEntity(node),
iD.actions.AddEntity(way),
iD.actions.AddVertex(way.id, node.id),
iD.actions.AddVertex(other.id, node.id, index));
controller.enter(iD.modes.DrawLine(way.id, 'forward', graph));
}
function startFromNode(node) {
var graph = history.graph(),
parent = graph.parentWays(node)[0],
isLine = parent && parent.geometry(graph) === 'line';
if (isLine && parent.first() === node.id) {
controller.enter(iD.modes.DrawLine(parent.id, 'backward', graph));
} else if (isLine && parent.last() === node.id) {
controller.enter(iD.modes.DrawLine(parent.id, 'forward', graph));
} else {
var way = iD.Way({tags: defaultTags});
history.perform(
iD.actions.AddEntity(way),
iD.actions.AddVertex(way.id, node.id));
controller.enter(iD.modes.DrawLine(way.id, 'forward', graph));
}
}
function startFromMidpoint(midpoint) {
var graph = history.graph(),
node = iD.Node(),
way = iD.Way({tags: defaultTags});
history.perform(
iD.actions.AddMidpoint(midpoint, node),
iD.actions.AddEntity(way),
iD.actions.AddVertex(way.id, node.id));
controller.enter(iD.modes.DrawLine(way.id, 'forward', graph));
}
behavior = iD.behavior.AddWay(mode)
var behavior = iD.behavior.AddWay(context)
.on('start', start)
.on('startFromWay', startFromWay)
.on('startFromNode', startFromNode)
.on('startFromMidpoint', startFromMidpoint);
.on('startFromMidpoint', startFromMidpoint),
defaultTags = {highway: 'residential'};
mode.map.surface.call(behavior);
mode.map.tail(t('modes.add_line.tail'));
function start(loc) {
var graph = context.graph(),
node = iD.Node({loc: loc}),
way = iD.Way({tags: defaultTags});
context.perform(
iD.actions.AddEntity(node),
iD.actions.AddEntity(way),
iD.actions.AddVertex(way.id, node.id));
context.enter(iD.modes.DrawLine(context, way.id, 'forward', graph));
}
function startFromWay(other, loc, index) {
var graph = context.graph(),
node = iD.Node({loc: loc}),
way = iD.Way({tags: defaultTags});
context.perform(
iD.actions.AddEntity(node),
iD.actions.AddEntity(way),
iD.actions.AddVertex(way.id, node.id),
iD.actions.AddVertex(other.id, node.id, index));
context.enter(iD.modes.DrawLine(context, way.id, 'forward', graph));
}
function startFromNode(node) {
var graph = context.graph(),
parent = graph.parentWays(node)[0],
isLine = parent && parent.geometry(graph) === 'line';
if (isLine && parent.first() === node.id) {
context.enter(iD.modes.DrawLine(context, parent.id, 'backward', graph));
} else if (isLine && parent.last() === node.id) {
context.enter(iD.modes.DrawLine(context, parent.id, 'forward', graph));
} else {
var way = iD.Way({tags: defaultTags});
context.perform(
iD.actions.AddEntity(way),
iD.actions.AddVertex(way.id, node.id));
context.enter(iD.modes.DrawLine(context, way.id, 'forward', graph));
}
}
function startFromMidpoint(midpoint) {
var graph = context.graph(),
node = iD.Node(),
way = iD.Way({tags: defaultTags});
context.perform(
iD.actions.AddMidpoint(midpoint, node),
iD.actions.AddEntity(way),
iD.actions.AddVertex(way.id, node.id));
context.enter(iD.modes.DrawLine(context, way.id, 'forward', graph));
}
mode.enter = function() {
context.install(behavior);
context.tail(t('modes.add_line.tail'));
};
mode.exit = function() {
mode.map.surface.call(behavior.off);
context.uninstall(behavior);
};
return mode;
+34 -43
View File
@@ -1,4 +1,4 @@
iD.modes.AddPoint = function() {
iD.modes.AddPoint = function(context) {
var mode = {
id: 'add-point',
title: t('modes.add_point.title'),
@@ -6,53 +6,44 @@ iD.modes.AddPoint = function() {
key: t('modes.add_point.key')
};
var behavior;
var behavior = iD.behavior.Draw(context)
.on('click', add)
.on('clickWay', addWay)
.on('clickNode', addNode)
.on('clickMidpoint', addNode)
.on('cancel', cancel)
.on('finish', cancel);
function add(loc) {
var node = iD.Node({loc: loc});
context.perform(
iD.actions.AddEntity(node),
t('operations.add.annotation.point'));
context.enter(iD.modes.Select(context, [node.id], true));
}
function addWay(way, loc, index) {
add(loc);
}
function addNode(node) {
add(node.loc);
}
function cancel() {
context.enter(iD.modes.Browse(context));
}
mode.enter = function() {
var map = mode.map,
history = mode.history,
controller = mode.controller;
function add(loc) {
var node = iD.Node({loc: loc});
history.perform(
iD.actions.AddEntity(node),
t('operations.add.annotation.point'));
controller.enter(iD.modes.Select([node.id], true));
}
function addWay(way, loc, index) {
add(loc);
}
function addNode(node) {
add(node.loc);
}
function cancel() {
controller.enter(iD.modes.Browse());
}
behavior = iD.behavior.Draw(map)
.on('click', add)
.on('clickWay', addWay)
.on('clickNode', addNode)
.on('clickMidpoint', addNode)
.on('cancel', cancel)
.on('finish', cancel);
mode.map.surface.call(behavior);
mode.map.tail(t('modes.add_point.tail'));
context.install(behavior);
context.tail(t('modes.add_point.tail'));
};
mode.exit = function() {
var map = mode.map,
surface = map.surface;
map.tail(false);
behavior.off(surface);
context.uninstall(behavior);
context.tail(false);
};
return mode;
+8 -14
View File
@@ -1,4 +1,4 @@
iD.modes.Browse = function() {
iD.modes.Browse = function(context) {
var mode = {
button: 'browse',
id: 'browse',
@@ -7,27 +7,21 @@ iD.modes.Browse = function() {
key: t('modes.browse.key')
};
var behaviors;
var behaviors = [
iD.behavior.Hover(),
iD.behavior.Select(context),
iD.behavior.DragNode(context),
iD.behavior.DragMidpoint(context)];
mode.enter = function() {
var surface = mode.map.surface;
behaviors = [
iD.behavior.Hover(),
iD.behavior.Select(mode),
iD.behavior.DragNode(mode),
iD.behavior.DragMidpoint(mode)];
behaviors.forEach(function(behavior) {
behavior(surface);
context.install(behavior);
});
};
mode.exit = function() {
var surface = mode.map.surface;
behaviors.forEach(function(behavior) {
behavior.off(surface);
context.uninstall(behavior);
});
};
+6 -6
View File
@@ -1,4 +1,4 @@
iD.modes.DrawArea = function(wayId, baseGraph) {
iD.modes.DrawArea = function(context, wayId, baseGraph) {
var mode = {
button: 'area',
id: 'draw-area'
@@ -7,11 +7,11 @@ iD.modes.DrawArea = function(wayId, baseGraph) {
var behavior;
mode.enter = function() {
var way = mode.history.graph().entity(wayId),
var way = context.entity(wayId),
headId = way.nodes[way.nodes.length - 2],
tailId = way.first();
behavior = iD.behavior.DrawWay(wayId, -1, mode, baseGraph);
behavior = iD.behavior.DrawWay(context, wayId, -1, mode, baseGraph);
var addNode = behavior.addNode;
@@ -23,12 +23,12 @@ iD.modes.DrawArea = function(wayId, baseGraph) {
}
};
mode.map.surface.call(behavior);
mode.map.tail(t('modes.draw_area.tail'));
context.install(behavior);
context.tail(t('modes.draw_area.tail'));
};
mode.exit = function() {
mode.map.surface.call(behavior.off);
context.uninstall(behavior);
};
return mode;
+6 -6
View File
@@ -1,4 +1,4 @@
iD.modes.DrawLine = function(wayId, direction, baseGraph) {
iD.modes.DrawLine = function(context, wayId, direction, baseGraph) {
var mode = {
button: 'line',
id: 'draw-line'
@@ -7,11 +7,11 @@ iD.modes.DrawLine = function(wayId, direction, baseGraph) {
var behavior;
mode.enter = function() {
var way = mode.history.graph().entity(wayId),
var way = context.entity(wayId),
index = (direction === 'forward') ? undefined : 0,
headId = (direction === 'forward') ? way.last() : way.first();
behavior = iD.behavior.DrawWay(wayId, index, mode, baseGraph);
behavior = iD.behavior.DrawWay(context, wayId, index, mode, baseGraph);
var addNode = behavior.addNode;
@@ -23,12 +23,12 @@ iD.modes.DrawLine = function(wayId, direction, baseGraph) {
}
};
mode.map.surface.call(behavior);
mode.map.tail(t('modes.draw_line.tail'));
context.install(behavior);
context.tail(t('modes.draw_line.tail'));
};
mode.exit = function() {
mode.map.surface.call(behavior.off);
context.uninstall(behavior);
};
return mode;
+21 -26
View File
@@ -1,4 +1,4 @@
iD.modes.MoveWay = function(wayId) {
iD.modes.MoveWay = function(context, wayId) {
var mode = {
id: 'move-way'
};
@@ -6,55 +6,53 @@ iD.modes.MoveWay = function(wayId) {
var keybinding = d3.keybinding('move-way');
mode.enter = function() {
var map = mode.map,
history = mode.history,
graph = history.graph(),
selection = map.surface,
controller = mode.controller,
projection = map.projection,
way = graph.entity(wayId),
origin = d3.mouse(selection.node()),
annotation = t('operations.move.annotation.' + way.geometry(graph));
var origin = point(),
annotation = t('operations.move.annotation.' + context.geometry(wayId));
// If intiated via keyboard
if (!origin[0] && !origin[1]) origin = null;
history.perform(
context.perform(
iD.actions.Noop(),
annotation);
function point() {
return d3.mouse(context.surface().node());
}
function move() {
var p = d3.mouse(selection.node()),
var p = point(),
delta = origin ?
[p[0] - origin[0], p[1] - origin[1]] :
[0, 0];
origin = p;
history.replace(
iD.actions.MoveWay(wayId, delta, projection),
context.replace(
iD.actions.MoveWay(wayId, delta, context.projection),
annotation);
}
function finish() {
d3.event.stopPropagation();
controller.enter(iD.modes.Select([way.id], true));
context.enter(iD.modes.Select(context, [wayId], true));
}
function cancel() {
history.pop();
controller.enter(iD.modes.Select([way.id], true));
context.pop();
context.enter(iD.modes.Select(context, [wayId], true));
}
function undone() {
controller.enter(iD.modes.Browse());
context.enter(iD.modes.Browse(context));
}
selection
context.surface()
.on('mousemove.move-way', move)
.on('click.move-way', finish);
history.on('undone.move-way', undone);
context.history()
.on('undone.move-way', undone);
keybinding
.on('⎋', cancel)
@@ -65,15 +63,12 @@ iD.modes.MoveWay = function(wayId) {
};
mode.exit = function() {
var map = mode.map,
history = mode.history,
selection = map.surface;
selection
context.surface()
.on('mousemove.move-way', null)
.on('click.move-way', null);
history.on('undone.move-way', null);
context.history()
.on('undone.move-way', null);
keybinding.off();
};
+49 -53
View File
@@ -1,4 +1,4 @@
iD.modes.Select = function(selection, initial) {
iD.modes.Select = function(context, selection, initial) {
var mode = {
id: 'select',
button: 'browse'
@@ -6,12 +6,16 @@ iD.modes.Select = function(selection, initial) {
var inspector = iD.ui.inspector().initial(!!initial),
keybinding = d3.keybinding('select'),
behaviors,
behaviors = [
iD.behavior.Hover(),
iD.behavior.Select(context),
iD.behavior.DragNode(context),
iD.behavior.DragMidpoint(context)],
radialMenu;
function changeTags(d, tags) {
if (!_.isEqual(singular().tags, tags)) {
mode.history.perform(
context.perform(
iD.actions.ChangeTags(d.id, tags),
t('operations.change_tags.annotation'));
}
@@ -19,7 +23,7 @@ iD.modes.Select = function(selection, initial) {
function singular() {
if (selection.length === 1) {
return mode.map.history().graph().entity(selection[0]);
return context.entity(selection[0]);
}
}
@@ -28,24 +32,14 @@ iD.modes.Select = function(selection, initial) {
};
mode.enter = function() {
var map = mode.map,
history = map.history(),
graph = history.graph(),
surface = map.surface,
entity = singular();
behaviors = [
iD.behavior.Hover(),
iD.behavior.Select(mode),
iD.behavior.DragNode(mode),
iD.behavior.DragMidpoint(mode)];
var entity = singular();
behaviors.forEach(function(behavior) {
behavior(surface);
context.install(behavior);
});
var operations = d3.values(iD.operations)
.map(function (o) { return o(selection, mode); })
.map(function (o) { return o(selection, context); })
.filter(function (o) { return o.available(); });
operations.forEach(function(operation) {
@@ -62,9 +56,10 @@ iD.modes.Select = function(selection, initial) {
}), true));
if (entity) {
inspector.graph(graph);
inspector.graph(context.graph());
d3.select('.inspector-wrap')
context.container()
.select('.inspector-wrap')
.style('display', 'block')
.style('opacity', 1)
.datum(entity)
@@ -73,38 +68,39 @@ iD.modes.Select = function(selection, initial) {
if (d3.event) {
// Pan the map if the clicked feature intersects with the position
// of the inspector
var inspector_size = d3.select('.inspector-wrap').size(),
map_size = mode.map.size(),
var inspector_size = context.container().select('.inspector-wrap').size(),
map_size = context.map().size(),
offset = 50,
shift_left = d3.event.x - map_size[0] + inspector_size[0] + offset,
center = (map_size[0] / 2) + shift_left + offset;
if (shift_left > 0 && inspector_size[1] > d3.event.y) {
mode.map.centerEase(mode.map.projection.invert([center, map_size[1]/2]));
context.map().centerEase(context.projection.invert([center, map_size[1]/2]));
}
}
inspector
.on('changeTags', changeTags)
.on('close', function() { mode.controller.enter(iD.modes.Browse()); });
history.on('change.select', function() {
// Exit mode if selected entity gets undone
var oldEntity = entity,
newEntity = history.graph().entity(selection[0]);
if (!newEntity) {
mode.controller.enter(iD.modes.Browse());
} else if (!_.isEqual(oldEntity.tags, newEntity.tags)) {
inspector.tags(newEntity.tags);
}
surface.call(radialMenu.close);
});
.on('close', function() { context.enter(iD.modes.Browse(context)); });
}
map.on('move.select', function() {
surface.call(radialMenu.close);
context.history().on('change.select', function() {
context.surface().call(radialMenu.close);
if (_.any(selection, function (id) { return !context.entity(id); })) {
// Exit mode if selected entity gets undone
context.enter(iD.modes.Browse(context));
} else if (entity) {
var newEntity = context.entity(selection[0]);
if (!_.isEqual(entity.tags, newEntity.tags)) {
inspector.tags(newEntity.tags);
}
}
});
context.map().on('move.select', function() {
context.surface().call(radialMenu.close);
});
function dblclick() {
@@ -113,10 +109,10 @@ iD.modes.Select = function(selection, initial) {
if (datum instanceof iD.Way && !target.classed('fill')) {
var choice = iD.geo.chooseIndex(datum,
d3.mouse(mode.map.surface.node()), mode.map),
d3.mouse(context.surface().node()), context),
node = iD.Node({ loc: choice.loc });
history.perform(
context.perform(
iD.actions.AddEntity(node),
iD.actions.AddVertex(datum.id, node.id, choice.index),
t('operations.add.annotation.vertex'));
@@ -129,7 +125,8 @@ iD.modes.Select = function(selection, initial) {
d3.select(document)
.call(keybinding);
surface.on('dblclick.select', dblclick)
context.surface()
.on('dblclick.select', dblclick)
.selectAll("*")
.filter(function (d) { return d && selection.indexOf(d.id) >= 0; })
.classed('selected', true);
@@ -137,25 +134,23 @@ iD.modes.Select = function(selection, initial) {
radialMenu = iD.ui.RadialMenu(operations);
if (d3.event && !initial) {
var loc = map.mouseCoordinates();
var loc = context.map().mouseCoordinates();
if (entity && entity.type === 'node') {
loc = entity.loc;
}
surface.call(radialMenu, map.projection(loc));
context.surface().call(radialMenu, context.projection(loc));
}
};
mode.exit = function () {
var surface = mode.map.surface,
history = mode.history;
if (singular()) {
changeTags(singular(), inspector.tags());
}
d3.select('.inspector-wrap')
context.container()
.select('.inspector-wrap')
.style('display', 'none')
.html('');
@@ -164,7 +159,7 @@ iD.modes.Select = function(selection, initial) {
d3.selectAll('div.typeahead').remove();
behaviors.forEach(function(behavior) {
behavior.off(surface);
context.uninstall(behavior);
});
var q = iD.util.stringQs(location.hash.substring(1));
@@ -172,13 +167,14 @@ iD.modes.Select = function(selection, initial) {
keybinding.off();
history.on('change.select', null);
context.history()
.on('change.select', null);
surface.on('dblclick.select', null)
context.surface()
.call(radialMenu.close)
.on('dblclick.select', null)
.selectAll(".selected")
.classed('selected', false);
surface.call(radialMenu.close);
};
return mode;
+7 -10
View File
@@ -1,4 +1,4 @@
iD.OAuth = function() {
iD.OAuth = function(context) {
var baseurl = 'http://www.openstreetmap.org',
o = {},
keys,
@@ -6,10 +6,6 @@ iD.OAuth = function() {
function keyclean(x) { return x.replace(/\W/g, ''); }
if (token('oauth_token')) {
o.oauth_token = token('oauth_token');
}
function timenonce(o) {
o.oauth_timestamp = ohauth.timestamp();
o.oauth_nonce = ohauth.nonce();
@@ -17,11 +13,12 @@ iD.OAuth = function() {
}
// token getter/setter, namespaced to the current `apibase` value.
function token(k, x) {
if (arguments.length == 2) {
localStorage[keyclean(baseurl) + k] = x;
}
return localStorage[keyclean(baseurl) + k];
function token() {
return context.storage.apply(context, arguments);
}
if (token('oauth_token')) {
o.oauth_token = token('oauth_token');
}
oauth.authenticated = function() {
+7 -13
View File
@@ -1,25 +1,19 @@
iD.operations.Circularize = function(selection, mode) {
iD.operations.Circularize = function(selection, context) {
var entityId = selection[0],
history = mode.map.history(),
action = iD.actions.Circularize(entityId, mode.map);
action = iD.actions.Circularize(entityId, context.projection);
var operation = function() {
var graph = history.graph(),
entity = graph.entity(entityId),
annotation = t('operations.circularize.annotation.' + entity.geometry(graph));
history.perform(action, annotation);
var annotation = t('operations.circularize.annotation.' + context.geometry(entityId));
context.perform(action, annotation);
};
operation.available = function() {
var graph = history.graph(),
entity = graph.entity(entityId);
return selection.length === 1 && entity.type === 'way';
return selection.length === 1 &&
context.entity(entityId).type === 'way';
};
operation.enabled = function() {
var graph = history.graph();
return action.enabled(graph);
return action.enabled(context.graph());
};
operation.id = "circularize";
+11 -14
View File
@@ -1,23 +1,20 @@
iD.operations.Delete = function(selection, mode) {
var entityId = selection[0],
history = mode.map.history();
iD.operations.Delete = function(selection, context) {
var operation = function() {
var graph = history.graph(),
entity = graph.entity(entityId),
action = {way: iD.actions.DeleteWay, node: iD.actions.DeleteNode}[entity.type],
annotation = t('operations.delete.annotation.' + entity.geometry(graph));
var annotation;
history.perform(
action(entityId),
if (selection.length === 1) {
annotation = t('operations.delete.annotation.' + context.geometry(selection[0]));
} else {
annotation = t('operations.delete.annotation.multiple', {n: selection.length});
}
context.perform(
iD.actions.DeleteMultiple(selection),
annotation);
};
operation.available = function() {
var graph = history.graph(),
entity = graph.entity(entityId);
return selection.length === 1 &&
(entity.type === 'way' || entity.type === 'node');
return true;
};
operation.enabled = function() {
+24
View File
@@ -0,0 +1,24 @@
iD.operations.Disconnect = function(selection, context) {
var entityId = selection[0],
action = iD.actions.Disconnect(entityId);
var operation = function() {
context.perform(action, t('operations.disconnect.annotation'));
};
operation.available = function() {
return selection.length === 1 &&
context.geometry(entityId) === 'vertex';
};
operation.enabled = function() {
return action.enabled(context.graph());
};
operation.id = "disconnect";
operation.key = t('operations.disconnect.key');
operation.title = t('operations.disconnect.title');
operation.description = t('operations.disconnect.description');
return operation;
};
+27
View File
@@ -0,0 +1,27 @@
iD.operations.Merge = function(selection, context) {
var action = iD.actions.Join(selection[0], selection[1]);
var operation = function() {
var annotation = t('operations.merge.annotation', {n: selection.length}),
difference = context.perform(action, annotation);
context.enter(iD.modes.Select(context, difference.extantIDs()));
};
operation.available = function() {
return selection.length === 2 &&
_.all(selection, function (id) {
return context.geometry(id) === 'line';
});
};
operation.enabled = function() {
return action.enabled(context.graph());
};
operation.id = "merge";
operation.key = t('operations.merge.key');
operation.title = t('operations.merge.title');
operation.description = t('operations.merge.description');
return operation;
};
+4 -6
View File
@@ -1,15 +1,13 @@
iD.operations.Move = function(selection, mode) {
var entityId = selection[0],
history = mode.map.history();
iD.operations.Move = function(selection, context) {
var entityId = selection[0];
var operation = function() {
mode.controller.enter(iD.modes.MoveWay(entityId));
context.enter(iD.modes.MoveWay(context, entityId));
};
operation.available = function() {
var graph = history.graph();
return selection.length === 1 &&
graph.entity(entityId).type === 'way';
context.entity(entityId).type === 'way';
};
operation.enabled = function() {
+5 -8
View File
@@ -1,18 +1,15 @@
iD.operations.Reverse = function(selection, mode) {
var entityId = selection[0],
history = mode.map.history();
iD.operations.Reverse = function(selection, context) {
var entityId = selection[0];
var operation = function() {
history.perform(
iD.actions.ReverseWay(entityId),
context.perform(
iD.actions.Reverse(entityId),
t('operations.reverse.annotation'));
};
operation.available = function() {
var graph = history.graph(),
entity = graph.entity(entityId);
return selection.length === 1 &&
entity.geometry(graph) === 'line';
context.geometry(entityId) === 'line';
};
operation.enabled = function() {
+7 -9
View File
@@ -1,22 +1,20 @@
iD.operations.Split = function(selection, mode) {
iD.operations.Split = function(selection, context) {
var entityId = selection[0],
history = mode.map.history(),
action = iD.actions.SplitWay(entityId);
action = iD.actions.Split(entityId);
var operation = function() {
history.perform(action, t('operations.split.annotation'));
var annotation = t('operations.split.annotation'),
difference = context.perform(action, annotation);
context.enter(iD.modes.Select(context, difference.extantIDs()));
};
operation.available = function() {
var graph = history.graph(),
entity = graph.entity(entityId);
return selection.length === 1 &&
entity.geometry(graph) === 'vertex';
context.geometry(entityId) === 'vertex';
};
operation.enabled = function() {
var graph = history.graph();
return action.enabled(graph);
return action.enabled(context.graph());
};
operation.id = "split";
-28
View File
@@ -1,28 +0,0 @@
iD.operations.Unjoin = function(selection, mode) {
var entityId = selection[0],
history = mode.map.history(),
action = iD.actions.UnjoinNode(entityId);
var operation = function() {
history.perform(action, 'Unjoined lines.');
};
operation.available = function() {
var graph = history.graph(),
entity = graph.entity(entityId);
return selection.length === 1 &&
entity.geometry(graph) === 'vertex';
};
operation.enabled = function() {
var graph = history.graph();
return action.enabled(graph);
};
operation.id = "unjoin";
operation.key = t('operations.unjoin.key');
operation.title = t('operations.unjoin.title');
operation.description = t('operations.unjoin.description');
return operation;
};
+5 -2
View File
@@ -83,7 +83,10 @@ iD.Background = function() {
}
});
requests = uniqueBy(requests, 3);
requests = uniqueBy(requests, 3).filter(function(r) {
// don't re-request tiles which have failed in the past
return cache[r[3]] !== false;
});
function load(d) {
cache[d[3]] = true;
@@ -125,7 +128,7 @@ iD.Background = function() {
.attr('src', function(d) { return d[3]; })
.on('error', error)
.on('load', load);
image.style(transformProp, imageTransform);
if (Object.keys(cache).length > 100) cache = {};
+13 -75
View File
@@ -1,6 +1,5 @@
iD.Map = function() {
var connection, history,
dimensions = [],
iD.Map = function(context) {
var dimensions = [],
dispatch = d3.dispatch('move', 'drawn'),
projection = d3.geo.mercator().scale(1024),
roundedProjection = iD.svg.RoundProjection(projection),
@@ -27,6 +26,9 @@ iD.Map = function() {
surface, tilegroup;
function map(selection) {
context.history()
.on('change.map', redraw);
selection.call(zoom);
tilegroup = selection.append('div')
@@ -55,49 +57,23 @@ iD.Map = function() {
function pxCenter() { return [dimensions[0] / 2, dimensions[1] / 2]; }
function drawVector(difference) {
if (surface.style(transformProp) != 'none') return;
var filter, all,
extent = map.extent(),
graph = history.graph();
function addParents(parents) {
for (var i = 0; i < parents.length; i++) {
var parent = parents[i];
if (only[parent.id] === undefined) {
only[parent.id] = parent;
addParents(graph.parentRelations(parent));
}
}
}
graph = context.graph();
if (!difference) {
all = graph.intersects(extent);
filter = d3.functor(true);
} else {
var only = {};
for (var j = 0; j < difference.length; j++) {
var id = difference[j],
entity = graph.entity(id);
// Even if the entity is false (deleted), it needs to be
// removed from the surface
only[id] = entity;
if (entity && entity.intersects(extent, graph)) {
addParents(graph.parentWays(only[id]));
addParents(graph.parentRelations(only[id]));
}
}
all = _.compact(_.values(only));
var complete = difference.complete(extent);
all = _.compact(_.values(complete));
filter = function(d) {
if (d.type === 'midpoint') {
for (var i = 0; i < d.ways.length; i++) {
if (d.ways[i].id in only) return true;
if (d.ways[i].id in complete) return true;
}
} else {
return d.id in only;
return d.id in complete;
}
};
}
@@ -121,11 +97,6 @@ iD.Map = function() {
surface.selectAll('.layer *').remove();
}
function connectionLoad(err, result) {
history.merge(result);
redraw(Object.keys(result));
}
function zoomPan() {
if (d3.event && d3.event.sourceEvent.type === 'dblclick') {
if (!dblclickEnabled) {
@@ -183,7 +154,7 @@ iD.Map = function() {
tilegroup.call(background);
if (map.editable()) {
connection.loadTiles(projection, dimensions);
context.connection().loadTiles(projection, dimensions);
drawVector(difference);
} else {
editOff();
@@ -259,7 +230,6 @@ iD.Map = function() {
t[0] - ll[0] + c[0],
t[1] - ll[1] + c[1]]);
zoom.translate(projection.translate());
dispatch.move(map);
return true;
}
@@ -348,15 +318,8 @@ iD.Map = function() {
};
map.flush = function () {
connection.flush();
history.reset();
return map;
};
map.connection = function(_) {
if (!arguments.length) return connection;
connection = _;
connection.on('load.tile', connectionLoad);
context.connection().flush();
context.history().reset();
return map;
};
@@ -369,24 +332,6 @@ iD.Map = function() {
return map;
};
map.hint = function (_) {
if (_ === false) {
d3.select('div.inspector-wrap')
.style('opacity', 0)
.style('display', 'none');
} else {
d3.select('div.inspector-wrap')
.html('')
.style('display', 'block')
.transition()
.style('opacity', 1);
d3.select('div.inspector-wrap')
.append('div')
.attr('class','inspector-inner')
.text(_);
}
};
map.editable = function() {
return map.zoom() >= 16;
};
@@ -397,13 +342,6 @@ iD.Map = function() {
return map;
};
map.history = function (_) {
if (!arguments.length) return history;
history = _;
history.on('change.map', redraw);
return map;
};
map.background = background;
map.projection = projection;
map.redraw = redraw;
+1 -1
View File
@@ -3,7 +3,7 @@ iD.svg.Surface = function() {
selection.append('defs');
var layers = selection.selectAll('.layer')
.data(['shadow', 'fill', 'casing', 'stroke', 'text', 'hit', 'halo', 'label']);
.data(['fill', 'shadow', 'casing', 'stroke', 'text', 'hit', 'halo', 'label']);
layers.enter().append('g')
.attr('class', function(d) { return 'layer layer-' + d; });
+257 -1
View File
@@ -1 +1,257 @@
iD.ui = {};
iD.ui = function (context) {
return function(container) {
context.container(container);
var connection = context.connection(),
history = context.history(),
map = context.map();
if (!iD.supported()) {
container.html('This editor is supported in Firefox, Chrome, Safari, Opera, ' +
'and Internet Explorer 9 and above. Please upgrade your browser ' +
'or use Potlatch 2 to edit the map.')
.style('text-align:center;font-style:italic;');
return;
}
function hintprefix(x, y) {
return '<span>' + y + '</span>' + '<div class="keyhint-wrap"><span class="keyhint"> ' + x + '</span></div>';
}
var m = container.append('div')
.attr('id', 'map')
.call(map);
var bar = container.append('div')
.attr('id', 'bar')
.attr('class','pad1 fillD');
var limiter = bar.append('div')
.attr('class', 'limiter');
var buttons_joined = limiter.append('div')
.attr('class', 'button-wrap joined col4');
var modes = [
iD.modes.Browse(context),
iD.modes.AddPoint(context),
iD.modes.AddLine(context),
iD.modes.AddArea(context)];
var buttons = buttons_joined.selectAll('button.add-button')
.data(modes)
.enter().append('button')
.attr('tabindex', -1)
.attr('class', function (mode) { return mode.title + ' add-button col3'; })
.call(bootstrap.tooltip().placement('bottom').html(true))
.attr('data-original-title', function (mode) {
return hintprefix(mode.key, mode.description);
})
.on('click.editor', function (mode) { context.enter(mode); });
function disableTooHigh() {
if (map.editable()) {
notice.message(false);
buttons.attr('disabled', null);
} else {
buttons.attr('disabled', 'disabled');
notice.message(true);
context.enter(iD.modes.Browse(context));
}
}
var notice = iD.ui.notice(limiter)
.message(false)
.on('zoom', function() { map.zoom(16); });
map.on('move.editor', _.debounce(function() {
disableTooHigh();
contributors.call(iD.ui.contributors(context));
}, 500));
buttons.append('span')
.attr('class', function(d) {
return d.id + ' icon icon-pre-text';
});
buttons.append('span').attr('class', 'label').text(function (mode) { return mode.title; });
context.on('enter.editor', function (entered) {
buttons.classed('active', function (mode) { return entered.button === mode.button; });
container.classed("mode-" + entered.id, true);
});
context.on('exit.editor', function (exited) {
container.classed("mode-" + exited.id, false);
});
var undo_buttons = limiter.append('div')
.attr('class', 'button-wrap joined col1'),
undo_tooltip = bootstrap.tooltip().placement('bottom').html(true);
undo_buttons.append('button')
.attr({ id: 'undo', 'class': 'col6' })
.property('disabled', true)
.html("<span class='undo icon'></span><small></small>")
.on('click.editor', history.undo)
.call(undo_tooltip);
undo_buttons.append('button')
.attr({ id: 'redo', 'class': 'col6' })
.property('disabled', true)
.html("<span class='redo icon'><small></small>")
.on('click.editor', history.redo)
.call(undo_tooltip);
limiter.append('div').attr('class','button-wrap col1').append('button')
.attr('class', 'save col12')
.call(iD.ui.save(context));
var zoom = container.append('div')
.attr('class', 'zoombuttons map-control')
.selectAll('button')
.data([['zoom-in', '+', map.zoomIn, 'Zoom In'], ['zoom-out', '-', map.zoomOut, 'Zoom Out']])
.enter()
.append('button')
.attr('tabindex', -1)
.attr('class', function(d) { return d[0]; })
.attr('title', function(d) { return d[3]; })
.on('click.editor', function(d) { return d[2](); })
.append('span')
.attr('class', function(d) {
return d[0] + ' icon';
});
if (navigator.geolocation) {
container.append('div')
.call(iD.ui.geolocate(map));
}
container.append('div').attr('class', 'geocode-control map-control')
.call(iD.ui.geocoder().map(map));
container.append('div').attr('class', 'map-control layerswitcher-control')
.call(iD.ui.layerswitcher(context));
container.append('div')
.style('display', 'none')
.attr('class', 'inspector-wrap fr col5');
var about = container.append('div')
.attr('class','col12 about-block fillD pad1');
about.append('div')
.attr('class', 'user-container')
.append('div')
.attr('class', 'hello');
var aboutList = about.append('ul')
.attr('id','about')
.attr('class','link-list');
var linkList = aboutList.append('ul')
.attr('id','about')
.attr('class','pad1 fillD about-block link-list');
linkList.append('li').append('a').attr('target', '_blank')
.attr('href', 'http://github.com/systemed/iD').text(iD.version);
linkList.append('li').append('a').attr('target', '_blank')
.attr('href', 'http://github.com/systemed/iD/issues').text('report a bug');
var imagery = linkList.append('li').attr('id', 'attribution');
imagery.append('span').text('imagery');
imagery.append('a').attr('target', '_blank')
.attr('href', 'http://opengeodata.org/microsoft-imagery-details').text(' provided by bing');
linkList.append('li').attr('class', 'source-switch').append('a').attr('href', '#')
.text('dev')
.on('click.editor', function() {
d3.event.preventDefault();
if (d3.select(this).classed('live')) {
map.flush();
context.connection()
.url('http://api06.dev.openstreetmap.org');
d3.select(this).text('dev').classed('live', false);
} else {
map.flush();
context.connection()
.url('http://www.openstreetmap.org');
d3.select(this).text('live').classed('live', true);
}
});
var contributors = linkList.append('li')
.attr('id', 'user-list');
contributors.append('span')
.attr('class', 'icon nearby icon-pre-text');
contributors.append('span')
.text('Viewing contributions by ');
contributors.append('span')
.attr('class', 'contributor-list');
contributors.append('span')
.attr('class', 'contributor-count');
history.on('change.editor', function() {
window.onbeforeunload = history.hasChanges() ? function() {
return 'You have unsaved changes.';
} : null;
var undo = history.undoAnnotation(),
redo = history.redoAnnotation();
function refreshTooltip(selection) {
if (selection.property('disabled')) {
selection.call(undo_tooltip.hide);
} else if (selection.property('tooltipVisible')) {
selection.call(undo_tooltip.show);
}
}
limiter.select('#undo')
.property('disabled', !undo)
.attr('data-original-title', hintprefix('⌘ + Z', undo))
.call(refreshTooltip);
limiter.select('#redo')
.property('disabled', !redo)
.attr('data-original-title', hintprefix('⌘ + ⇧ + Z', redo))
.call(refreshTooltip);
});
d3.select(window).on('resize.editor', function() {
map.size(m.size());
});
var keybinding = d3.keybinding('main')
.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(); });
modes.forEach(function(m) {
keybinding.on(m.key, function() { if (map.editable()) context.enter(m); });
});
d3.select(document)
.call(keybinding);
var hash = iD.behavior.Hash(context);
hash();
if (!hash.hadHash) {
map.centerZoom([-77.02271, 38.90085], 20);
}
d3.select('.user-container').call(iD.ui.userpanel(connection)
.on('logout.editor', connection.logout)
.on('login.editor', connection.authenticate));
context.enter(iD.modes.Browse(context));
if (!context.storage('sawSplash')) {
iD.ui.splash();
context.storage('sawSplash', true);
}
};
};
+3 -3
View File
@@ -1,4 +1,4 @@
iD.ui.commit = function(map) {
iD.ui.commit = function(context) {
var event = d3.dispatch('cancel', 'save', 'fix');
function zipSame(d) {
@@ -38,7 +38,7 @@ iD.ui.commit = function(map) {
comment_section.append('textarea')
.attr('class', 'changeset-comment')
.attr('placeholder', 'Brief Description of your contributions')
.property('value', localStorage.comment || '')
.property('value', context.storage('comment') || '')
.node().select();
var commit_info =
@@ -89,7 +89,7 @@ iD.ui.commit = function(map) {
cancelbutton.append('span').attr('class','label').text('Cancel');
var warnings = body.selectAll('div.warning-section')
.data(iD.validate(changes, map.history().graph()))
.data(iD.validate(changes, context.graph()))
.enter()
.append('div').attr('class', 'modal-section warning-section fillL');
+4 -4
View File
@@ -1,10 +1,10 @@
iD.ui.contributors = function(map) {
iD.ui.contributors = function(context) {
function contributors(selection) {
var users = {},
limit = 3,
entities = map.history().graph().intersects(map.extent());
entities = context.graph().intersects(context.map().extent());
for (var i in entities) {
if (entities[i].user) users[entities[i].user] = true;
@@ -21,7 +21,7 @@ iD.ui.contributors = function(map) {
l.enter().append('a')
.attr('class', 'user-link')
.attr('href', function(d) { return map.connection().userUrl(d); })
.attr('href', function(d) { return context.connection().userUrl(d); })
.attr('target', '_blank')
.text(String);
@@ -37,7 +37,7 @@ iD.ui.contributors = function(map) {
.append('a')
.attr('target', '_blank')
.attr('href', function() {
var ext = map.extent();
var ext = context.map().extent();
return 'http://www.openstreetmap.org/browse/changesets?bbox=' + [
ext[0][0], ext[0][1],
ext[1][0], ext[1][1]];
+10 -8
View File
@@ -6,18 +6,20 @@ iD.ui.geocoder = function() {
function keydown() {
if (d3.event.keyCode !== 13) return;
d3.event.preventDefault();
d3.json('http://a.tiles.mapbox.com/v3/openstreetmap.map-hn253zqn/geocode/' +
encodeURIComponent(this.value) + '.json', function(err, resp) {
var searchVal = this.value;
d3.json('http://nominatim.openstreetmap.org/search/' +
encodeURIComponent(searchVal) + '?limit=10&format=json', function(err, resp) {
if (err) return hide();
hide();
if (!resp.results.length) {
if (!resp.length) {
return iD.ui.flash()
.select('.content')
.append('h3')
.text('No location found for "' + resp.query[0] + '"');
.text('No location found for "' + searchVal + '"');
}
var bounds = resp.results[0][0].bounds;
map.extent(iD.geo.Extent([bounds[0], bounds[1]], [bounds[2], bounds[3]]));
var bounds = resp[0].boundingbox;
map.extent(iD.geo.Extent([parseFloat(bounds[3]), parseFloat(bounds[0])], [parseFloat(bounds[2]), parseFloat(bounds[1])]));
if (map.zoom() > 19) map.zoom(19);
});
}
@@ -43,7 +45,7 @@ iD.ui.geocoder = function() {
var button = selection.append('button')
.attr('tabindex', -1)
.attr('title', 'Find A Location')
.attr('title', t('geocoder.find_location'))
.html('<span class=\'geocode icon\'></span>')
.on('click', toggle);
@@ -51,7 +53,7 @@ iD.ui.geocoder = function() {
gcForm.attr('class','content fillD map-overlay hide')
.append('input')
.attr({ type: 'text', placeholder: 'find a place' })
.attr({ type: 'text', placeholder: t('geocoder.find_a_place') })
.on('keydown', keydown);
selection.call(clickoutside);
+4 -4
View File
@@ -35,7 +35,7 @@ iD.ui.inspector = function() {
});
newTag.append('span').attr('class', 'icon icon-pre-text plus');
newTag.append('span').attr('class','label').text('New tag')
newTag.append('span').attr('class','label').text(t('inspector.new_tag'));
drawTags(entity.tags);
@@ -63,7 +63,7 @@ iD.ui.inspector = function() {
.attr('class', 'apply action')
.on('click', apply);
inspectorButton.append('span').attr('class','label').text('Okay');
inspectorButton.append('span').attr('class','label').text(t('okay'));
var minorButtons = selection.append('div').attr('class','minor-buttons fl');
@@ -148,7 +148,7 @@ iD.ui.inspector = function() {
iD.ui.flash()
.select('.content')
.append('h3')
.text(t('no_documentation_combination'));
.text(t('inspector.no_documentation_combination'));
}
});
} else if (d.key) {
@@ -166,7 +166,7 @@ iD.ui.inspector = function() {
iD.ui.flash()
.select('.content')
.append('h3')
.text(t('no_documentation_key'));
.text(t('inspector.no_documentation_key'));
}
});
}
+12 -12
View File
@@ -1,4 +1,4 @@
iD.ui.layerswitcher = function(map) {
iD.ui.layerswitcher = function(context) {
var event = d3.dispatch('cancel', 'save'),
sources = [{
name: 'Bing',
@@ -59,7 +59,7 @@ iD.ui.layerswitcher = function(map) {
.append('div')
.attr('class', 'opacity-options-wrapper');
opa.append('h4').text(t('layers'));
opa.append('h4').text(t('layerswitcher.layers'));
opa.append('ul')
.attr('class', 'opacity-options')
@@ -68,7 +68,7 @@ iD.ui.layerswitcher = function(map) {
.enter()
.append('li')
.attr('data-original-title', function(d) {
return t('percent_opacity', { opacity: (d * 100) });
return t('layerswitcher.percent_brightness', { opacity: (d * 100) });
})
.on('click.set-opacity', function(d) {
d3.select('#tile-g')
@@ -94,7 +94,7 @@ iD.ui.layerswitcher = function(map) {
function selectLayer(d) {
content.selectAll('a.layer')
.classed('selected', function(d) {
return d.source === map.background.source();
return d.source === context.background().source();
});
d3.select('#attribution a')
.attr('href', d.link)
@@ -126,9 +126,9 @@ iD.ui.layerswitcher = function(map) {
d.source = configured;
d.name = 'Custom (configured)';
}
map.background.source(d.source);
map.history().imagery_used(d.name);
map.redraw();
context.background().source(d.source);
context.history().imagery_used(d.name);
context.redraw();
selectLayer(d);
})
.insert('span')
@@ -145,8 +145,8 @@ iD.ui.layerswitcher = function(map) {
['bottom', [0, 1]]];
function nudge(d) {
map.background.nudge(d[1]);
map.redraw();
context.background().nudge(d[1]);
context.redraw();
}
adjustments.append('a')
@@ -181,12 +181,12 @@ iD.ui.layerswitcher = function(map) {
.text('reset')
.attr('class', 'reset')
.on('click', function() {
map.background.offset([0, 0]);
map.redraw();
context.background().offset([0, 0]);
context.redraw();
});
selection.call(clickoutside);
selectLayer(map.background.source());
selectLayer(context.background().source());
}
return d3.rebind(layerswitcher, event, 'on');
+8 -25
View File
@@ -1,11 +1,8 @@
iD.ui.save = function() {
var map, controller;
function save(selection) {
var history = map.history(),
connection = map.connection(),
iD.ui.save = function(context) {
return function (selection) {
var map = context.map(),
history = context.history(),
connection = context.connection(),
tooltip = bootstrap.tooltip()
.placement('bottom');
@@ -53,14 +50,14 @@ iD.ui.save = function() {
modal.select('.content')
.classed('commit-modal', true)
.datum(changes)
.call(iD.ui.commit(map)
.call(iD.ui.commit(context)
.on('cancel', function() {
modal.remove();
})
.on('fix', function(d) {
map.extent(d.entity.extent(map.history().graph()));
map.extent(d.entity.extent(context.graph()));
if (map.zoom() > 19) map.zoom(19);
controller.enter(iD.modes.Select([d.entity.id]));
context.enter(iD.modes.Select(context, [d.entity.id]));
modal.remove();
})
.on('save', commit));
@@ -88,19 +85,5 @@ iD.ui.save = function() {
selection.call(tooltip.hide);
}
});
}
save.map = function(_) {
if (!arguments.length) return map;
map = _;
return save;
};
save.controller = function(_) {
if (!arguments.length) return controller;
controller = _;
return save;
};
return save;
};
+19 -21
View File
@@ -15,31 +15,29 @@ iD.validate = function(changes, graph) {
if (tags.building && tags.building === 'yes') return 'building=yes';
}
if (changes.created.length) {
for (var i = 0; i < changes.created.length; i++) {
change = changes.created[i];
for (var i = 0; i < changes.created.length; i++) {
change = changes.created[i];
if (change.geometry(graph) === 'point' && _.isEmpty(change.tags)) {
warnings.push({
message: t('validations.untagged_point'),
entity: change
});
}
if (change.geometry(graph) === 'point' && _.isEmpty(change.tags)) {
warnings.push({
message: t('validations.untagged_point'),
entity: change
});
}
if (change.geometry(graph) === 'line' && _.isEmpty(change.tags)) {
warnings.push({ message: t('validations.untagged_line'), entity: change });
}
if (change.geometry(graph) === 'line' && _.isEmpty(change.tags)) {
warnings.push({ message: t('validations.untagged_line'), entity: change });
}
if (change.geometry(graph) === 'area' && _.isEmpty(change.tags)) {
warnings.push({ message: t('validations.untagged_area'), entity: change });
}
if (change.geometry(graph) === 'area' && _.isEmpty(change.tags)) {
warnings.push({ message: t('validations.untagged_area'), entity: change });
}
if (change.geometry(graph) === 'line' && tagSuggestsArea(change)) {
warnings.push({
message: t('validations.tag_suggests_area', {tag: tagSuggestsArea(change)}),
entity: change
});
}
if (change.geometry(graph) === 'line' && tagSuggestsArea(change)) {
warnings.push({
message: t('validations.tag_suggests_area', {tag: tagSuggestsArea(change)}),
entity: change
});
}
}
+147
View File
@@ -0,0 +1,147 @@
d3.combobox = function() {
var event = d3.dispatch('accept'),
autohighlight = false,
autofilter = false,
input,
container,
data;
var typeahead = function(selection) {
var hidden, idx = autohighlight ? 0 : -1;
var rect = selection.select('input').node().getBoundingClientRect();
input = selection.select('input');
container = selection
.insert('div', ':first-child')
.attr('class', 'combobox')
.style({
position: 'absolute',
display: 'none',
left: '0px',
width: rect.width + 'px',
top: rect.height + 'px'
});
carat = selection
.insert('div', ':first-child')
.attr('class', 'combobox-carat')
.text('+')
.style({
position: 'absolute',
left: (rect.width - 20) + 'px',
top: '0px'
})
.on('click', function() {
update();
show();
});
selection
.on('keyup.typeahead', key);
hidden = false;
function hide() {
idx = autohighlight ? 0 : -1;
hidden = true;
}
function show() {
container.style('display', 'block');
}
function slowHide() {
if (autohighlight && container.select('a.selected').node()) {
select(container.select('a.selected').datum());
event.accept();
}
window.setTimeout(hide, 150);
}
selection
.on('focus.typeahead', show)
.on('blur.typeahead', slowHide);
function key() {
var len = container.selectAll('a').data().length;
if (d3.event.keyCode === 40) {
idx = Math.min(idx + 1, len - 1);
return highlight();
} else if (d3.event.keyCode === 38) {
idx = Math.max(idx - 1, 0);
return highlight();
} else if (d3.event.keyCode === 13) {
if (container.select('a.selected').node()) {
select(container.select('a.selected').datum());
}
event.accept();
hide();
} else {
update();
}
}
function highlight() {
container
.selectAll('a')
.classed('selected', function(d, i) { return i == idx; });
}
function update() {
function run(data) {
container.style('display', function() {
return data.length ? 'block' : 'none';
});
var options = container
.selectAll('a')
.data(data, function(d) { return d.value; });
options.enter()
.append('a')
.text(function(d) { return d.value; })
.attr('title', function(d) { return d.title; })
.on('click', select);
options.exit().remove();
options
.classed('selected', function(d, i) { return i == idx; });
}
if (typeof data === 'function') data(selection, run);
else run(data);
}
function select(d) {
input
.property('value', d.value)
.trigger('change');
container.style('display', 'none');
}
};
typeahead.data = function(_) {
if (!arguments.length) return data;
data = _;
return typeahead;
};
typeahead.autofilter = function(_) {
if (!arguments.length) return autofilter;
autofilter = _;
return typeahead;
};
typeahead.autohighlight = function(_) {
if (!arguments.length) return autohighlight;
autohighlight = _;
return typeahead;
};
return d3.rebind(typeahead, event, 'on');
};
+900 -704
View File
File diff suppressed because it is too large Load Diff
+27 -14
View File
@@ -73,9 +73,22 @@ locale.en = {
point: "Deleted a point.",
vertex: "Deleted a node from a way.",
line: "Deleted a line.",
area: "Deleted an area."
area: "Deleted an area.",
multiple: "Deleted {n} objects."
}
},
disconnect: {
title: "Disconnect",
description: "Disconnect these ways from each other.",
key: "D",
annotation: "Disconnected ways."
},
merge: {
title: "Merge",
description: "Merge these lines.",
key: "C",
annotation: "Merged {n} lines."
},
move: {
title: "Move",
description: "Move this to a different location.",
@@ -98,12 +111,6 @@ locale.en = {
description: "Split this into two ways at this point.",
key: "X",
annotation: "Split a way."
},
unjoin: {
title: "Unjoin",
description: "Disconnect these ways from each other.",
key: "⇧-J",
annotation: "Unjoined ways."
}
},
@@ -129,8 +136,11 @@ locale.en = {
"layer_settings": "Layer Settings",
"no_documentation_combination": "This is no documentation available for this tag combination",
"no_documentation_key": "This is no documentation available for this key",
inspector: {
no_documentation_combination: "This is no documentation available for this tag combination",
no_documentation_key: "This is no documentation available for this key",
new_tag: "New Tag"
},
"view_on_osm": "View on OSM",
@@ -138,13 +148,16 @@ locale.en = {
"edit_tags": "Edit tags",
"find_location": "Find A Location",
"find_placeholder": "find a place",
geocoder: {
"find_location": "Find A Location",
"find_a_place": "find a place"
},
"description": "Description",
"logout": "logout",
"layers": "Layers",
"percent_opacity": "{opacity}% opacity"
layerswitcher: {
layers: "Layers",
percent_brightness: "{opacity}% brightness"
}
};
+22 -13
View File
@@ -73,14 +73,17 @@
<script src='../js/id/actions/add_vertex.js'></script>
<script src='../js/id/actions/change_tags.js'></script>
<script src='../js/id/actions/circularize.js'></script>
<script src="../js/id/actions/delete_multiple.js"></script>
<script src="../js/id/actions/delete_node.js"></script>
<script src="../js/id/actions/delete_relation.js"></script>
<script src="../js/id/actions/delete_way.js"></script>
<script src='../js/id/actions/disconnect.js'></script>
<script src='../js/id/actions/join.js'></script>
<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/reverse_way.js'></script>
<script src='../js/id/actions/split_way.js'></script>
<script src='../js/id/actions/unjoin_node.js'></script>
<script src='../js/id/actions/reverse.js'></script>
<script src='../js/id/actions/split.js'></script>
<script src='../js/id/behavior.js'></script>
<script src='../js/id/behavior/add_way.js'></script>
@@ -106,11 +109,13 @@
<script src='../js/id/operations.js'></script>
<script src='../js/id/operations/circularize.js'></script>
<script src='../js/id/operations/delete.js'></script>
<script src='../js/id/operations/disconnect.js'></script>
<script src='../js/id/operations/merge.js'></script>
<script src='../js/id/operations/move.js'></script>
<script src='../js/id/operations/reverse.js'></script>
<script src='../js/id/operations/split.js'></script>
<script src='../js/id/operations/unjoin.js'></script>
<script src='../js/id/graph/difference.js'></script>
<script src='../js/id/graph/entity.js'></script>
<script src='../js/id/graph/graph.js'></script>
<script src='../js/id/graph/history.js'></script>
@@ -119,7 +124,6 @@
<script src='../js/id/graph/way.js'></script>
<script src='../js/id/connection.js'></script>
<script src='../js/id/controller.js'></script>
<script src='../locale/locale.js'></script>
<script src='../locale/en.js'></script>
@@ -141,17 +145,17 @@
<script src="spec/actions/add_midpoint.js"></script>
<script src="spec/actions/add_entity.js"></script>
<script src="spec/actions/change_tags.js"></script>
<script src="spec/actions/delete_multiple.js"></script>
<script src="spec/actions/delete_node.js"></script>
<script src="spec/actions/delete_relation.js"></script>
<script src="spec/actions/delete_way.js"></script>
<script src='spec/actions/disconnect.js'></script>
<script src="spec/actions/join.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/reverse_way.js"></script>
<script src="spec/actions/split_way.js"></script>
<script src='spec/actions/unjoin_node.js'></script>
<script src="spec/behavior/hash.js"></script>
<script src="spec/behavior/hover.js"></script>
<script src="spec/actions/reverse.js"></script>
<script src="spec/actions/split.js"></script>
<script src="spec/geo/extent.js"></script>
@@ -161,8 +165,7 @@
<script src="spec/graph/way.js"></script>
<script src="spec/graph/relation.js"></script>
<script src="spec/graph/history.js"></script>
<script src="spec/modes/add_point.js"></script>
<script src="spec/graph/difference.js"></script>
<script src="spec/renderer/background.js"></script>
<script src="spec/renderer/map.js"></script>
@@ -189,6 +192,12 @@
<script src="spec/taginfo.js"></script>
<script src="spec/util.js"></script>
<script src="spec/behavior/hash.js"></script>
<script src="spec/behavior/hover.js"></script>
<script src="spec/behavior/select.js"></script>
<script src="spec/modes/add_point.js"></script>
<script>
(window.mochaPhantomJS || window.mocha).run();
</script>
+13 -8
View File
@@ -35,17 +35,17 @@
<script src="spec/actions/add_midpoint.js"></script>
<script src="spec/actions/add_entity.js"></script>
<script src="spec/actions/change_tags.js"></script>
<script src="spec/actions/delete_multiple.js"></script>
<script src="spec/actions/delete_node.js"></script>
<script src="spec/actions/delete_relation.js"></script>
<script src="spec/actions/delete_way.js"></script>
<script src='spec/actions/disconnect.js'></script>
<script src="spec/actions/join.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/reverse_way.js"></script>
<script src="spec/actions/split_way.js"></script>
<script src='spec/actions/unjoin_node.js'></script>
<script src="spec/behavior/hash.js"></script>
<script src="spec/behavior/hover.js"></script>
<script src="spec/actions/reverse.js"></script>
<script src="spec/actions/split.js"></script>
<script src="spec/geo/extent.js"></script>
@@ -55,8 +55,7 @@
<script src="spec/graph/way.js"></script>
<script src="spec/graph/relation.js"></script>
<script src="spec/graph/history.js"></script>
<script src="spec/modes/add_point.js"></script>
<script src="spec/graph/difference.js"></script>
<script src="spec/renderer/background.js"></script>
<script src="spec/renderer/map.js"></script>
@@ -83,6 +82,12 @@
<script src="spec/taginfo.js"></script>
<script src="spec/util.js"></script>
<script src="spec/behavior/hash.js"></script>
<script src="spec/behavior/hover.js"></script>
<script src="spec/behavior/select.js"></script>
<script src="spec/modes/add_point.js"></script>
<script>
(window.mochaPhantomJS || window.mocha).run();
</script>
+12
View File
@@ -0,0 +1,12 @@
describe("iD.actions.DeleteMultiple", function () {
it("deletes multiple entities of heterogeneous types", function () {
var n = iD.Node(),
w = iD.Way(),
r = iD.Relation(),
action = iD.actions.DeleteMultiple([n.id, w.id, r.id]),
graph = action(iD.Graph([n, w, r]));
expect(graph.entity(n.id)).to.be.undefined;
expect(graph.entity(w.id)).to.be.undefined;
expect(graph.entity(r.id)).to.be.undefined;
});
});
+17
View File
@@ -0,0 +1,17 @@
describe("iD.actions.DeleteRelation", function () {
it("removes the relation from the graph", function () {
var relation = iD.Relation(),
action = iD.actions.DeleteRelation(relation.id),
graph = action(iD.Graph([relation]));
expect(graph.entity(relation.id)).to.be.undefined;
});
it("removes the relation from parent relations", function () {
var a = iD.Relation(),
b = iD.Relation(),
parent = iD.Relation({members: [{ id: a.id }, { id: b.id }]}),
action = iD.actions.DeleteRelation(a.id),
graph = action(iD.Graph([a, b, parent]));
expect(graph.entity(parent.id).members).to.eql([{ id: b.id }]);
});
});
@@ -1,9 +1,9 @@
describe("iD.actions.UnjoinNode", function () {
describe("iD.actions.Disconnect", 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);
expect(iD.actions.Disconnect('a').enabled(graph)).to.equal(false);
});
it("returns true for a node shared by two or more ways", function () {
@@ -19,7 +19,7 @@ describe("iD.actions.UnjoinNode", function () {
'|': iD.Way({id: '|', nodes: ['d', 'b']})
});
expect(iD.actions.UnjoinNode('b').enabled(graph)).to.equal(true);
expect(iD.actions.Disconnect('b').enabled(graph)).to.equal(true);
});
});
@@ -46,7 +46,7 @@ describe("iD.actions.UnjoinNode", function () {
'|': iD.Way({id: '|', nodes: ['d', 'b']})
});
graph = iD.actions.UnjoinNode('b', 'e')(graph);
graph = iD.actions.Disconnect('b', 'e')(graph);
expect(graph.entity('-').nodes).to.eql(['a', 'b', 'c']);
expect(graph.entity('|').nodes).to.eql(['d', 'e']);
@@ -64,7 +64,7 @@ describe("iD.actions.UnjoinNode", function () {
'|': iD.Way({id: '|', nodes: ['d', 'b']})
});
graph = iD.actions.UnjoinNode('b', 'e')(graph);
graph = iD.actions.Disconnect('b', 'e')(graph);
// Immutable loc => should be shared by identity.
expect(graph.entity('b').loc).to.equal(loc);
+172
View File
@@ -0,0 +1,172 @@
describe("iD.actions.Join", function () {
describe("#enabled", function () {
it("returns true for ways that share an end/start node", function () {
// a --> b ==> c
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']}),
'=': iD.Way({id: '=', nodes: ['b', 'c']})
});
expect(iD.actions.Join('-', '=').enabled(graph)).to.be.true;
});
it("returns true for ways that share a start/end node", function () {
// a <-- b <== c
var graph = iD.Graph({
'a': iD.Node({id: 'a'}),
'b': iD.Node({id: 'b'}),
'c': iD.Node({id: 'c'}),
'-': iD.Way({id: '-', nodes: ['b', 'a']}),
'=': iD.Way({id: '=', nodes: ['c', 'b']})
});
expect(iD.actions.Join('-', '=').enabled(graph)).to.be.true;
});
it("returns true for ways that share a start/start node", function () {
// a <-- b ==> c
var graph = iD.Graph({
'a': iD.Node({id: 'a'}),
'b': iD.Node({id: 'b'}),
'c': iD.Node({id: 'c'}),
'-': iD.Way({id: '-', nodes: ['b', 'a']}),
'=': iD.Way({id: '=', nodes: ['b', 'c']})
});
expect(iD.actions.Join('-', '=').enabled(graph)).to.be.true;
});
it("returns true for ways that share an end/end node", function () {
// a --> b <== c
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']}),
'=': iD.Way({id: '=', nodes: ['c', 'b']})
});
expect(iD.actions.Join('-', '=').enabled(graph)).to.be.true;
});
it("returns false for ways that don't share the necessary nodes", 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: ['b', 'd']})
});
expect(iD.actions.Join('-', '=').enabled(graph)).to.be.false;
});
});
it("joins a --> b ==> c", function () {
// Expected result:
// a --> b --> c
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']}),
'=': iD.Way({id: '=', nodes: ['b', 'c']})
});
graph = iD.actions.Join('-', '=')(graph);
expect(graph.entity('-').nodes).to.eql(['a', 'b', 'c']);
expect(graph.entity('=')).to.be.undefined;
});
it("joins a <-- b <== c", function () {
// Expected result:
// a <-- b <-- c
var graph = iD.Graph({
'a': iD.Node({id: 'a'}),
'b': iD.Node({id: 'b'}),
'c': iD.Node({id: 'c'}),
'-': iD.Way({id: '-', nodes: ['b', 'a']}),
'=': iD.Way({id: '=', nodes: ['c', 'b']})
});
graph = iD.actions.Join('-', '=')(graph);
expect(graph.entity('-').nodes).to.eql(['c', 'b', 'a']);
expect(graph.entity('=')).to.be.undefined;
});
it("joins a <-- b ==> c", function () {
// Expected result:
// a <-- b <-- c
// tags on === reversed
var graph = iD.Graph({
'a': iD.Node({id: 'a'}),
'b': iD.Node({id: 'b'}),
'c': iD.Node({id: 'c'}),
'-': iD.Way({id: '-', nodes: ['b', 'a']}),
'=': iD.Way({id: '=', nodes: ['b', 'c']})
});
graph = iD.actions.Join('-', '=')(graph);
expect(graph.entity('-').nodes).to.eql(['c', 'b', 'a']);
expect(graph.entity('=')).to.be.undefined;
});
it("joins a --> b <== c", function () {
// Expected result:
// a --> b --> c
// tags on === reversed
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']}),
'=': iD.Way({id: '=', nodes: ['c', 'b']})
});
graph = iD.actions.Join('-', '=')(graph);
expect(graph.entity('-').nodes).to.eql(['a', 'b', 'c']);
expect(graph.entity('=')).to.be.undefined;
});
it("merges tags", 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'], tags: {a: 'a', b: '-', c: 'c'}}),
'=': iD.Way({id: '=', nodes: ['b', 'c'], tags: {a: 'a', b: '=', d: 'd'}})
});
graph = iD.actions.Join('-', '=')(graph);
expect(graph.entity('-').tags).to.eql({a: 'a', b: '-; =', c: 'c', d: 'd'});
});
it("merges 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']}),
'=': iD.Way({id: '=', nodes: ['b', 'c']}),
'r1': iD.Relation({id: 'r1', members: [{id: '=', role: 'r1'}]}),
'r2': iD.Relation({id: 'r2', members: [{id: '=', role: 'r1'}, {id: '-', role: 'r2'}]})
});
graph = iD.actions.Join('-', '=')(graph);
expect(graph.entity('r1').members).to.eql([{id: '-', role: 'r1'}]);
expect(graph.entity('r2').members).to.eql([{id: '-', role: 'r2'}]);
});
});
@@ -1,9 +1,9 @@
describe("iD.actions.ReverseWay", function () {
describe("iD.actions.Reverse", function () {
it("reverses the order of nodes in the way", function () {
var node1 = iD.Node(),
node2 = iD.Node(),
way = iD.Way({nodes: [node1.id, node2.id]}),
graph = iD.actions.ReverseWay(way.id)(iD.Graph([node1, node2, way]));
graph = iD.actions.Reverse(way.id)(iD.Graph([node1, node2, way]));
expect(graph.entity(way.id).nodes).to.eql([node2.id, node1.id]);
});
@@ -11,7 +11,7 @@ describe("iD.actions.ReverseWay", function () {
var way = iD.Way({tags: {'highway': 'residential'}}),
graph = iD.Graph([way]);
graph = iD.actions.ReverseWay(way.id)(graph);
graph = iD.actions.Reverse(way.id)(graph);
expect(graph.entity(way.id).tags).to.eql({'highway': 'residential'});
});
@@ -19,7 +19,7 @@ describe("iD.actions.ReverseWay", function () {
var way = iD.Way({tags: {'oneway': 'yes'}}),
graph = iD.Graph([way]);
graph = iD.actions.ReverseWay(way.id)(graph);
graph = iD.actions.Reverse(way.id)(graph);
expect(graph.entity(way.id).tags).to.eql({'oneway': 'yes'});
});
@@ -27,10 +27,10 @@ describe("iD.actions.ReverseWay", function () {
var way = iD.Way({tags: {'cycleway:right': 'lane'}}),
graph = iD.Graph([way]);
graph = iD.actions.ReverseWay(way.id)(graph);
graph = iD.actions.Reverse(way.id)(graph);
expect(graph.entity(way.id).tags).to.eql({'cycleway:left': 'lane'});
graph = iD.actions.ReverseWay(way.id)(graph);
graph = iD.actions.Reverse(way.id)(graph);
expect(graph.entity(way.id).tags).to.eql({'cycleway:right': 'lane'});
});
@@ -38,10 +38,10 @@ describe("iD.actions.ReverseWay", function () {
var way = iD.Way({tags: {'maxspeed:forward': '25'}}),
graph = iD.Graph([way]);
graph = iD.actions.ReverseWay(way.id)(graph);
graph = iD.actions.Reverse(way.id)(graph);
expect(graph.entity(way.id).tags).to.eql({'maxspeed:backward': '25'});
graph = iD.actions.ReverseWay(way.id)(graph);
graph = iD.actions.Reverse(way.id)(graph);
expect(graph.entity(way.id).tags).to.eql({'maxspeed:forward': '25'});
});
@@ -49,10 +49,10 @@ describe("iD.actions.ReverseWay", function () {
var way = iD.Way({tags: {'incline': 'up'}}),
graph = iD.Graph([way]);
graph = iD.actions.ReverseWay(way.id)(graph);
graph = iD.actions.Reverse(way.id)(graph);
expect(graph.entity(way.id).tags).to.eql({'incline': 'down'});
graph = iD.actions.ReverseWay(way.id)(graph);
graph = iD.actions.Reverse(way.id)(graph);
expect(graph.entity(way.id).tags).to.eql({'incline': 'up'});
});
@@ -60,10 +60,10 @@ describe("iD.actions.ReverseWay", function () {
var way = iD.Way({tags: {'incline': 'up'}}),
graph = iD.Graph([way]);
graph = iD.actions.ReverseWay(way.id)(graph);
graph = iD.actions.Reverse(way.id)(graph);
expect(graph.entity(way.id).tags).to.eql({'incline': 'down'});
graph = iD.actions.ReverseWay(way.id)(graph);
graph = iD.actions.Reverse(way.id)(graph);
expect(graph.entity(way.id).tags).to.eql({'incline': 'up'});
});
@@ -71,16 +71,16 @@ describe("iD.actions.ReverseWay", function () {
var way = iD.Way({tags: {'incline': '5%'}}),
graph = iD.Graph([way]);
graph = iD.actions.ReverseWay(way.id)(graph);
graph = iD.actions.Reverse(way.id)(graph);
expect(graph.entity(way.id).tags).to.eql({'incline': '-5%'});
graph = iD.actions.ReverseWay(way.id)(graph);
graph = iD.actions.Reverse(way.id)(graph);
expect(graph.entity(way.id).tags).to.eql({'incline': '5%'});
way = iD.Way({tags: {'incline': '.8°'}});
graph = iD.Graph([way]);
graph = iD.actions.ReverseWay(way.id)(graph);
graph = iD.actions.Reverse(way.id)(graph);
expect(graph.entity(way.id).tags).to.eql({'incline': '-.8°'});
});
@@ -88,10 +88,10 @@ describe("iD.actions.ReverseWay", function () {
var way = iD.Way({tags: {'sidewalk': 'right'}}),
graph = iD.Graph([way]);
graph = iD.actions.ReverseWay(way.id)(graph);
graph = iD.actions.Reverse(way.id)(graph);
expect(graph.entity(way.id).tags).to.eql({'sidewalk': 'left'});
graph = iD.actions.ReverseWay(way.id)(graph);
graph = iD.actions.Reverse(way.id)(graph);
expect(graph.entity(way.id).tags).to.eql({'sidewalk': 'right'});
});
@@ -99,7 +99,7 @@ describe("iD.actions.ReverseWay", function () {
var way = iD.Way({tags: {'maxspeed:forward': '25', 'maxspeed:backward': '30'}}),
graph = iD.Graph([way]);
graph = iD.actions.ReverseWay(way.id)(graph);
graph = iD.actions.Reverse(way.id)(graph);
expect(graph.entity(way.id).tags).to.eql({'maxspeed:backward': '25', 'maxspeed:forward': '30'});
});
@@ -108,10 +108,10 @@ describe("iD.actions.ReverseWay", function () {
relation = iD.Relation({members: [{type: 'way', id: way.id, role: 'forward'}]}),
graph = iD.Graph([way, relation]);
graph = iD.actions.ReverseWay(way.id)(graph);
graph = iD.actions.Reverse(way.id)(graph);
expect(graph.entity(relation.id).members[0].role).to.eql('backward');
graph = iD.actions.ReverseWay(way.id)(graph);
graph = iD.actions.Reverse(way.id)(graph);
expect(graph.entity(relation.id).members[0].role).to.eql('forward');
});
});
@@ -1,4 +1,4 @@
describe("iD.actions.SplitWay", function () {
describe("iD.actions.Split", function () {
describe("#enabled", function () {
it("returns true for a non-end node of a single way", function () {
var graph = iD.Graph({
@@ -8,7 +8,7 @@ describe("iD.actions.SplitWay", function () {
'-': iD.Way({id: '-', nodes: ['a', 'b', 'c']})
});
expect(iD.actions.SplitWay('b').enabled(graph)).to.be.true;
expect(iD.actions.Split('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').enabled(graph)).to.be.false;
expect(iD.actions.Split('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').enabled(graph)).to.be.false;
expect(iD.actions.Split('b').enabled(graph)).to.be.false;
});
});
@@ -48,7 +48,7 @@ describe("iD.actions.SplitWay", function () {
'-': iD.Way({id: '-', nodes: ['a', 'b', 'c']})
});
graph = iD.actions.SplitWay('b', '=')(graph);
graph = iD.actions.Split('b', '=')(graph);
expect(graph.entity('-').nodes).to.eql(['a', 'b']);
expect(graph.entity('=').nodes).to.eql(['b', 'c']);
@@ -63,7 +63,7 @@ describe("iD.actions.SplitWay", function () {
'-': iD.Way({id: '-', nodes: ['a', 'b', 'c'], tags: tags})
});
graph = iD.actions.SplitWay('b', '=')(graph);
graph = iD.actions.Split('b', '=')(graph);
// Immutable tags => should be shared by identity.
expect(graph.entity('-').tags).to.equal(tags);
@@ -92,7 +92,7 @@ describe("iD.actions.SplitWay", function () {
'|': iD.Way({id: '|', nodes: ['d', 'b']})
});
graph = iD.actions.SplitWay('b', '=')(graph);
graph = iD.actions.Split('b', '=')(graph);
expect(graph.entity('-').nodes).to.eql(['a', 'b']);
expect(graph.entity('=').nodes).to.eql(['b', 'c']);
@@ -118,7 +118,7 @@ describe("iD.actions.SplitWay", function () {
'r': iD.Relation({id: 'r', members: [{id: '-', type: 'way'}]})
});
graph = iD.actions.SplitWay('b', '=')(graph);
graph = iD.actions.Split('b', '=')(graph);
expect(_.pluck(graph.entity('r').members, 'id')).to.eql(['-', '=']);
});
@@ -144,7 +144,7 @@ describe("iD.actions.SplitWay", function () {
'r': iD.Relation({id: 'r', members: [{id: '-', type: 'way'}, {id: '~', type: 'way'}]})
});
graph = iD.actions.SplitWay('b', '=')(graph);
graph = iD.actions.Split('b', '=')(graph);
expect(_.pluck(graph.entity('r').members, 'id')).to.eql(['-', '=', '~']);
});
@@ -170,7 +170,7 @@ describe("iD.actions.SplitWay", function () {
'r': iD.Relation({id: 'r', members: [{id: '~', type: 'way'}, {id: '-', type: 'way'}]})
});
graph = iD.actions.SplitWay('b', '=')(graph);
graph = iD.actions.Split('b', '=')(graph);
expect(_.pluck(graph.entity('r').members, 'id')).to.eql(['~', '=', '-']);
});
@@ -184,7 +184,7 @@ describe("iD.actions.SplitWay", function () {
'r': iD.Relation({id: 'r', members: [{id: '~', type: 'way'}, {id: '-', type: 'way'}]})
});
graph = iD.actions.SplitWay('b', '=')(graph);
graph = iD.actions.Split('b', '=')(graph);
expect(_.pluck(graph.entity('r').members, 'id')).to.eql(['~', '-', '=']);
});
@@ -214,7 +214,7 @@ describe("iD.actions.SplitWay", function () {
{id: 'c', role: 'via'}]})
});
graph = iD.actions.SplitWay('b', '=')(graph);
graph = iD.actions.Split('b', '=')(graph);
expect(graph.entity('r').members).to.eql([
{id: '=', role: 'from'},
@@ -246,7 +246,7 @@ describe("iD.actions.SplitWay", function () {
{id: 'c', role: 'via'}]})
});
graph = iD.actions.SplitWay('b', '=')(graph);
graph = iD.actions.Split('b', '=')(graph);
expect(graph.entity('r').members).to.eql([
{id: '~', role: 'from'},
@@ -278,7 +278,7 @@ describe("iD.actions.SplitWay", function () {
{id: 'c', role: 'via'}]})
});
graph = iD.actions.SplitWay('b', '=')(graph);
graph = iD.actions.Split('b', '=')(graph);
expect(graph.entity('r').members).to.eql([
{id: '-', role: 'from'},
+33 -37
View File
@@ -1,19 +1,18 @@
describe("iD.behavior.Hash", function () {
var hash, map, controller;
mocha.globals('__onhashchange.hash');
var hash, context;
beforeEach(function () {
map = {
on: function () { return map; },
zoom: function () { return arguments.length ? map : 0; },
center: function () { return arguments.length ? map : [0, 0]; },
centerZoom: function () { return arguments.length ? map : [0, 0]; }
};
context = iD();
controller = {
on: function () { return controller; }
};
// Neuter connection
context.connection().loadTiles = function () {};
hash = iD.behavior.Hash(controller, map);
hash = iD.behavior.Hash(context);
d3.select(document.createElement('div'))
.call(context.map());
});
afterEach(function () {
@@ -22,44 +21,41 @@ describe("iD.behavior.Hash", function () {
it("sets hadHash if location.hash is present", function () {
location.hash = "map=20.00/38.87952/-77.02405";
hash();
expect(hash.hadHash).to.be.true;
});
it("centerZooms map to requested level", function () {
location.hash = "map=20.00/38.87952/-77.02405";
sinon.spy(map, 'centerZoom');
hash();
expect(map.centerZoom).to.have.been.calledWith([-77.02405,38.87952], 20.0);
expect(context.map().center()[0]).to.be.closeTo(-77.02405, 0.1);
expect(context.map().center()[1]).to.be.closeTo(38.87952, 0.1);
expect(context.map().zoom()).to.equal(20.0);
});
describe("on window hashchange events", function () {
beforeEach(function () {
hash();
it("centerZooms map at requested coordinates on hash change", function (done) {
hash();
d3.select(window).one('hashchange', function () {
expect(context.map().center()[0]).to.be.closeTo(-77.02405, 0.1);
expect(context.map().center()[1]).to.be.closeTo(38.87952, 0.1);
expect(context.map().zoom()).to.equal(20.0);
done();
});
function onhashchange(fn) {
d3.select(window).one("hashchange", fn);
}
it("centerZooms map at requested coordinates", function (done) {
onhashchange(function () {
expect(map.centerZoom).to.have.been.calledWith([-77.02405,38.87952], 20.0);
done();
});
sinon.spy(map, 'centerZoom');
location.hash = "#map=20.00/38.87952/-77.02405";
});
location.hash = "#map=20.00/38.87952/-77.02405";
});
describe("on map move events", function () {
it("stores the current zoom and coordinates in location.hash", function () {
sinon.stub(map, 'on')
.withArgs("move.hash", sinon.match.instanceOf(Function))
.yields();
hash();
expect(location.hash).to.equal("#map=0.00/0/0");
});
it("stores the current zoom and coordinates in location.hash on map move events", function () {
hash();
context.map().center([38.9, -77.0]);
context.map().zoom(2.0);
expect(location.hash).to.equal("#map=2.00/-77.0/38.9");
});
});
+54
View File
@@ -0,0 +1,54 @@
describe("iD.behavior.Select", function() {
var a, b, context, behavior, container;
beforeEach(function() {
container = d3.select('body').append('div');
context = iD().container(container);
a = iD.Node({loc: [0, 0]});
b = iD.Node({loc: [0, 0]});
context.perform(iD.actions.AddEntity(a), iD.actions.AddEntity(b));
container.call(context.map())
.append('div')
.attr('class', 'inspector-wrap');
context.surface().selectAll('circle')
.data([a, b])
.enter().append('circle')
.attr('class', function(d) { return d.id; });
behavior = iD.behavior.Select(context);
context.install(behavior);
});
afterEach(function() {
context.uninstall(behavior);
container.remove();
});
specify("click on entity selects the entity", function() {
happen.click(context.surface().select('.' + a.id).node());
expect(context.selection()).to.eql([a.id]);
});
specify("click on empty space clears the selection", function() {
context.enter(iD.modes.Select(context, [a.id]));
happen.click(context.surface().node());
expect(context.selection()).to.eql([]);
});
specify("shift-click on entity adds the entity to the selection", function() {
context.enter(iD.modes.Select(context, [a.id]));
happen.click(context.surface().select('.' + b.id).node(), {shiftKey: true});
expect(context.selection()).to.eql([a.id, b.id]);
});
specify("shift-click on empty space leaves the selection unchanged", function() {
context.enter(iD.modes.Select(context, [a.id]));
happen.click(context.surface().node(), {shiftKey: true});
expect(context.selection()).to.eql([a.id]);
});
});
+2 -1
View File
@@ -2,7 +2,8 @@ describe('iD.Connection', function () {
var c;
beforeEach(function () {
c = new iD.Connection();
context = iD();
c = new iD.Connection(context);
});
it('is instantiated', function () {
+229
View File
@@ -0,0 +1,229 @@
describe("iD.Difference", function () {
describe("#changes", function () {
it("includes created entities", function () {
var node = iD.Node({id: 'n'}),
base = iD.Graph(),
head = base.replace(node),
diff = iD.Difference(base, head);
expect(diff.changes()).to.eql({n: {base: undefined, head: node}});
});
it("includes undone created entities", function () {
var node = iD.Node({id: 'n'}),
base = iD.Graph(),
head = base.replace(node),
diff = iD.Difference(head, base);
expect(diff.changes()).to.eql({n: {base: node, head: undefined}});
});
it("includes modified entities", function () {
var n1 = iD.Node({id: 'n'}),
n2 = n1.update(),
base = iD.Graph([n1]),
head = base.replace(n2),
diff = iD.Difference(base, head);
expect(diff.changes()).to.eql({n: {base: n1, head: n2}});
});
it("includes undone modified entities", function () {
var n1 = iD.Node({id: 'n'}),
n2 = n1.update(),
base = iD.Graph([n1]),
head = base.replace(n2),
diff = iD.Difference(head, base);
expect(diff.changes()).to.eql({n: {base: n2, head: n1}});
});
it("includes deleted entities", function () {
var node = iD.Node({id: 'n'}),
base = iD.Graph([node]),
head = base.remove(node),
diff = iD.Difference(base, head);
expect(diff.changes()).to.eql({n: {base: node, head: undefined}});
});
it("includes undone deleted entities", function () {
var node = iD.Node({id: 'n'}),
base = iD.Graph([node]),
head = base.remove(node),
diff = iD.Difference(head, base);
expect(diff.changes()).to.eql({n: {base: undefined, head: node}});
});
it("doesn't include created entities that were subsequently deleted", function () {
var node = iD.Node(),
base = iD.Graph(),
head = base.replace(node).remove(node),
diff = iD.Difference(base, head);
expect(diff.changes()).to.eql({});
});
});
describe("#extantIDs", function () {
it("includes the ids of created entities", function () {
var node = iD.Node({id: 'n'}),
base = iD.Graph(),
head = base.replace(node),
diff = iD.Difference(base, head);
expect(diff.extantIDs()).to.eql(['n']);
});
it("includes the ids of modified entities", function () {
var n1 = iD.Node({id: 'n'}),
n2 = n1.move([1, 2]),
base = iD.Graph([n1]),
head = base.replace(n2),
diff = iD.Difference(base, head);
expect(diff.extantIDs()).to.eql(['n']);
});
it("omits the ids of deleted entities", function () {
var node = iD.Node({id: 'n'}),
base = iD.Graph([node]),
head = base.remove(node),
diff = iD.Difference(base, head);
expect(diff.extantIDs()).to.eql([]);
});
});
describe("#created", function () {
it("returns an array of created entities", function () {
var node = iD.Node({id: 'n'}),
base = iD.Graph(),
head = base.replace(node),
diff = iD.Difference(base, head);
expect(diff.created()).to.eql([node]);
});
});
describe("#modified", function () {
it("returns an array of modified entities", function () {
var n1 = iD.Node({id: 'n'}),
n2 = n1.move([1, 2]),
base = iD.Graph([n1]),
head = base.replace(n2),
diff = iD.Difference(base, head);
expect(diff.modified()).to.eql([n2]);
});
});
describe("#deleted", function () {
it("returns an array of deleted entities", function () {
var node = iD.Node({id: 'n'}),
base = iD.Graph([node]),
head = base.remove(node),
diff = iD.Difference(base, head);
expect(diff.deleted()).to.eql([node]);
});
});
describe("#complete", function () {
it("includes created entities", function () {
var node = iD.Node({id: 'n'}),
base = iD.Graph(),
head = base.replace(node),
diff = iD.Difference(base, head);
expect(diff.complete()['n']).to.equal(node);
});
it("includes modified entities", function () {
var n1 = iD.Node({id: 'n'}),
n2 = n1.move([1, 2]),
base = iD.Graph([n1]),
head = base.replace(n2),
diff = iD.Difference(base, head);
expect(diff.complete()['n']).to.equal(n2);
});
it("includes deleted entities", function () {
var node = iD.Node({id: 'n'}),
base = iD.Graph([node]),
head = base.remove(node),
diff = iD.Difference(base, head);
expect(diff.complete()).to.eql({n: undefined});
});
it("includes nodes added to a way", function () {
var n1 = iD.Node({id: 'n1'}),
n2 = iD.Node({id: 'n2'}),
w1 = iD.Way({id: 'w', nodes: ['n1']}),
w2 = w1.addNode('n2'),
base = iD.Graph([n1, n2, w1]),
head = base.replace(w2),
diff = iD.Difference(base, head);
expect(diff.complete()['n2']).to.equal(n2);
});
it("includes nodes removed from a way", function () {
var n1 = iD.Node({id: 'n1'}),
n2 = iD.Node({id: 'n2'}),
w1 = iD.Way({id: 'w', nodes: ['n1', 'n2']}),
w2 = w1.removeNode('n2'),
base = iD.Graph([n1, n2, w1]),
head = base.replace(w2),
diff = iD.Difference(base, head);
expect(diff.complete()['n2']).to.equal(n2);
});
it("includes parent ways of modified nodes", function () {
var n1 = iD.Node({id: 'n'}),
n2 = n1.move([1, 2]),
way = iD.Way({id: 'w', nodes: ['n']}),
base = iD.Graph([n1, way]),
head = base.replace(n2),
diff = iD.Difference(base, head);
expect(diff.complete()['w']).to.equal(way);
});
it("includes parent relations of modified entities", function () {
var n1 = iD.Node({id: 'n'}),
n2 = n1.move([1, 2]),
rel = iD.Relation({id: 'r', members: [{id: 'n'}]}),
base = iD.Graph([n1, rel]),
head = base.replace(n2),
diff = iD.Difference(base, head);
expect(diff.complete()['r']).to.equal(rel);
});
it("includes parent relations of modified entities, recursively", function () {
var n1 = iD.Node({id: 'n'}),
n2 = n1.move([1, 2]),
rel1 = iD.Relation({id: 'r1', members: [{id: 'n'}]}),
rel2 = iD.Relation({id: 'r2', members: [{id: 'r1'}]}),
base = iD.Graph([n1, rel1, rel2]),
head = base.replace(n2),
diff = iD.Difference(base, head);
expect(diff.complete()['r2']).to.equal(rel2);
});
it("includes parent relations of parent ways of modified nodes", function () {
var n1 = iD.Node({id: 'n'}),
n2 = n1.move([1, 2]),
way = iD.Way({id: 'w', nodes: ['n']}),
rel = iD.Relation({id: 'r', members: [{id: 'w'}]}),
base = iD.Graph([n1, way, rel]),
head = base.replace(n2),
diff = iD.Difference(base, head);
expect(diff.complete()['r']).to.equal(rel);
});
it("copes with recursive relations", function () {
var node = iD.Node({id: 'n'}),
rel1 = iD.Relation({id: 'r1', members: [{id: 'n'}, {id: 'r2'}]}),
rel2 = iD.Relation({id: 'r2', members: [{id: 'r1'}]}),
base = iD.Graph([node, rel1, rel2]),
head = base.replace(node.move([1, 2])),
diff = iD.Difference(base, head);
expect(diff.complete()).to.be.ok;
});
it("limits changes to those within a given extent");
});
});
+27 -42
View File
@@ -52,12 +52,6 @@ describe('iD.Entity', function () {
expect(e.id).to.equal('w1');
});
it("tags the entity as updated", function () {
var tags = {foo: 'bar'},
e = iD.Entity().update({tags: tags});
expect(e._updated).to.to.be.true;
});
it("doesn't modify the input", function () {
var attrs = {tags: {foo: 'bar'}},
e = iD.Entity().update(attrs);
@@ -69,6 +63,33 @@ describe('iD.Entity', function () {
});
});
describe("#mergeTags", function () {
it("returns a new Entity", function () {
var a = iD.Entity(),
b = a.mergeTags({});
expect(b instanceof iD.Entity).to.be.true;
expect(a).not.to.equal(b);
});
it("merges tags", function () {
var a = iD.Entity({tags: {a: 'a'}}),
b = a.mergeTags({b: 'b'});
expect(b.tags).to.eql({a: 'a', b: 'b'});
});
it("combines non-conflicting tags", function () {
var a = iD.Entity({tags: {a: 'a'}}),
b = a.mergeTags({a: 'a'});
expect(b.tags).to.eql({a: 'a'});
});
it("combines conflicting tags with semicolons", function () {
var a = iD.Entity({tags: {a: 'a'}}),
b = a.mergeTags({a: 'b'});
expect(b.tags).to.eql({a: 'a; b'});
});
});
describe("#osmId", function () {
it("returns an OSM ID as a string", function () {
expect(iD.Entity({id: 'w1234'}).osmId()).to.eql('1234');
@@ -77,42 +98,6 @@ describe('iD.Entity', function () {
});
});
describe("#created", function () {
it("returns falsy by default", function () {
expect(iD.Entity({id: 'w1234'}).created()).not.to.be.ok;
});
it("returns falsy for an unmodified Entity", function () {
expect(iD.Entity({id: 'w1234'}).created()).not.to.be.ok;
});
it("returns falsy for a modified Entity with positive ID", function () {
expect(iD.Entity({id: 'w1234'}).update({}).created()).not.to.be.ok;
});
it("returns truthy for a modified Entity with negative ID", function () {
expect(iD.Entity({id: 'w-1234'}).update({}).created()).to.be.ok;
});
});
describe("#modified", function () {
it("returns falsy by default", function () {
expect(iD.Entity({id: 'w1234'}).modified()).not.to.be.ok;
});
it("returns falsy for an unmodified Entity", function () {
expect(iD.Entity({id: 'w1234'}).modified()).not.to.be.ok;
});
it("returns truthy for a modified Entity with positive ID", function () {
expect(iD.Entity({id: 'w1234'}).update({}).modified()).to.be.ok;
});
it("returns falsy for a modified Entity with negative ID", function () {
expect(iD.Entity({id: 'w-1234'}).update({}).modified()).not.to.be.ok;
});
});
describe("#intersects", function () {
it("returns true for a way with a node within the given extent", function () {
var node = iD.Node({loc: [0, 0]}),
-82
View File
@@ -333,86 +333,4 @@ describe('iD.Graph', function() {
expect(graph.childNodes(way)).to.eql([node]);
});
});
describe("#difference", function () {
it("returns an Array of ids of changed entities", function () {
var initial = iD.Node({id: "n1"}),
updated = initial.update({}),
created = iD.Node(),
deleted = iD.Node({id: 'n2'}),
graph1 = iD.Graph([initial, deleted]),
graph2 = graph1.replace(updated).replace(created).remove(deleted);
expect(graph2.difference(graph1)).to.eql([created.id, updated.id, deleted.id]);
});
it("includes created entities, and reverse", function () {
var node = iD.Node(),
graph1 = iD.Graph(),
graph2 = graph1.replace(node);
expect(graph2.difference(graph1)).to.eql([node.id]);
expect(graph1.difference(graph2)).to.eql([node.id]);
});
it("includes entities changed from base, and reverse", function () {
var node = iD.Node(),
graph1 = iD.Graph(node),
graph2 = graph1.replace(node.update());
expect(graph2.difference(graph1)).to.eql([node.id]);
expect(graph1.difference(graph2)).to.eql([node.id]);
});
it("includes already changed entities that were updated, and reverse", function () {
var node = iD.Node(),
graph1 = iD.Graph().replace(node),
graph2 = graph1.replace(node.update());
expect(graph2.difference(graph1)).to.eql([node.id]);
expect(graph1.difference(graph2)).to.eql([node.id]);
});
it("includes affected child nodes", function () {
var n = iD.Node({id: 'n'}),
n2 = iD.Node({id: 'n2'}),
w1 = iD.Way({id: 'w1', nodes: ['n']}),
w1_ = iD.Way({id: 'w1', nodes: ['n', 'n2']}),
graph1 = iD.Graph([n, n2, w1]),
graph2 = graph1.replace(w1_);
expect(graph2.difference(graph1)).to.eql(['n2', 'w1']);
expect(graph1.difference(graph2)).to.eql(['n2', 'w1']);
});
});
describe("#modified", function () {
it("returns an Array of ids of modified entities", function () {
var node = iD.Node({id: 'n1'}),
node_ = iD.Node({id: 'n1'}),
graph = iD.Graph([node]).replace(node_);
expect(graph.modified()).to.eql([node.id]);
});
});
describe("#created", function () {
it("returns an Array of ids of created entities", function () {
var node1 = iD.Node({id: 'n-1'}),
node2 = iD.Node({id: 'n2'}),
graph = iD.Graph([node2]).replace(node1);
expect(graph.created()).to.eql([node1.id]);
});
});
describe("#deleted", function () {
it("returns an Array of ids of deleted entities", function () {
var node1 = iD.Node({id: "n1"}),
node2 = iD.Node(),
graph = iD.Graph([node1, node2]).remove(node1);
expect(graph.deleted()).to.eql([node1.id]);
});
it("doesn't include created entities that were subsequently deleted", function () {
var node = iD.Node(),
graph = iD.Graph().replace(node).remove(node);
expect(graph.deleted()).to.eql([]);
});
});
});
+45 -10
View File
@@ -13,7 +13,25 @@ describe("iD.History", function () {
});
});
describe("#merge", function () {
it("merges the entities into all graph versions", function () {
var n = iD.Node({id: 'n'});
history.merge({n: n});
expect(history.graph().entity('n')).to.equal(n);
});
it("emits a change event", function () {
history.on('change', spy);
history.merge({});
expect(spy).to.have.been.called;
});
});
describe("#perform", function () {
it("returns a difference", function () {
expect(history.perform(action).changes()).to.eql({});
});
it("updates the graph", function () {
var node = iD.Node();
history.perform(function (graph) { return graph.replace(node); });
@@ -27,8 +45,8 @@ describe("iD.History", function () {
it("emits a change event", function () {
history.on('change', spy);
history.perform(action);
expect(spy).to.have.been.calledWith([]);
var difference = history.perform(action);
expect(spy).to.have.been.calledWith(difference);
});
it("performs multiple actions", function () {
@@ -42,6 +60,10 @@ describe("iD.History", function () {
});
describe("#replace", function () {
it("returns a difference", function () {
expect(history.replace(action).changes()).to.eql({});
});
it("updates the graph", function () {
var node = iD.Node();
history.replace(function (graph) { return graph.replace(node); });
@@ -56,8 +78,8 @@ describe("iD.History", function () {
it("emits a change event", function () {
history.on('change', spy);
history.replace(action);
expect(spy).to.have.been.calledWith([]);
var difference = history.replace(action);
expect(spy).to.have.been.calledWith(difference);
});
it("performs multiple actions", function () {
@@ -71,6 +93,11 @@ describe("iD.History", function () {
});
describe("#pop", function () {
it("returns a difference", function () {
history.perform(action, "annotation");
expect(history.pop().changes()).to.eql({});
});
it("updates the graph", function () {
history.perform(action, "annotation");
history.pop();
@@ -86,12 +113,16 @@ describe("iD.History", function () {
it("emits a change event", function () {
history.perform(action);
history.on('change', spy);
history.pop();
expect(spy).to.have.been.calledWith([]);
var difference = history.pop();
expect(spy).to.have.been.calledWith(difference);
});
});
describe("#undo", function () {
it("returns a difference", function () {
expect(history.undo().changes()).to.eql({});
});
it("pops the undo stack", function () {
history.perform(action, "annotation");
history.undo();
@@ -121,12 +152,16 @@ describe("iD.History", function () {
it("emits a change event", function () {
history.perform(action);
history.on('change', spy);
history.undo();
expect(spy).to.have.been.calledWith([]);
var difference = history.undo();
expect(spy).to.have.been.calledWith(difference);
});
});
describe("#redo", function () {
it("returns a difference", function () {
expect(history.redo().changes()).to.eql({});
});
it("emits an redone event", function () {
history.perform(action);
history.undo();
@@ -139,8 +174,8 @@ describe("iD.History", function () {
history.perform(action);
history.undo();
history.on('change', spy);
history.redo();
expect(spy).to.have.been.calledWith([]);
var difference = history.redo();
expect(spy).to.have.been.calledWith(difference);
});
});
-9
View File
@@ -4,15 +4,6 @@ describe('iD.Node', function () {
expect(iD.Node().type).to.equal("node");
});
it("returns a created Entity if no ID is specified", function () {
expect(iD.Node().created()).to.be.ok;
});
it("returns an unmodified Entity if ID is specified", function () {
expect(iD.Node({id: 'n1234'}).created()).not.to.be.ok;
expect(iD.Node({id: 'n1234'}).modified()).not.to.be.ok;
});
it("defaults tags to an empty object", function () {
expect(iD.Node().tags).to.eql({});
});
-9
View File
@@ -10,15 +10,6 @@ describe('iD.Relation', function () {
expect(iD.Relation().type).to.equal("relation");
});
it("returns a created Entity if no ID is specified", function () {
expect(iD.Relation().created()).to.be.ok;
});
it("returns an unmodified Entity if ID is specified", function () {
expect(iD.Relation({id: 'r1234'}).created()).not.to.be.ok;
expect(iD.Relation({id: 'r1234'}).modified()).not.to.be.ok;
});
it("defaults members to an empty array", function () {
expect(iD.Relation().members).to.eql([]);
});
-9
View File
@@ -10,15 +10,6 @@ describe('iD.Way', function() {
expect(iD.Way().type).to.equal("way");
});
it("returns a created Entity if no ID is specified", function () {
expect(iD.Way().created()).to.be.ok;
});
it("returns an unmodified Entity if ID is specified", function () {
expect(iD.Way({id: 'w1234'}).created()).not.to.be.ok;
expect(iD.Way({id: 'w1234'}).modified()).not.to.be.ok;
});
it("defaults nodes to an empty array", function () {
expect(iD.Way().nodes).to.eql([]);
});
+14 -19
View File
@@ -1,41 +1,36 @@
describe("iD.modes.AddPoint", function () {
var container, map, history, controller, mode;
var context;
beforeEach(function () {
container = d3.select('body').append('div');
history = iD.History();
map = iD.Map().history(history);
controller = iD.Controller(map, history);
var container = d3.select(document.createElement('div'));
container.call(map);
container.append('div')
context = iD()
.container(container);
container.call(context.map())
.append('div')
.attr('class', 'inspector-wrap');
mode = iD.modes.AddPoint();
controller.enter(mode);
});
afterEach(function() {
container.remove();
context.enter(iD.modes.AddPoint(context));
});
describe("clicking the map", function () {
it("adds a node", function () {
happen.click(map.surface.node(), {});
expect(history.changes().created).to.have.length(1);
happen.click(context.surface().node(), {});
expect(context.changes().created).to.have.length(1);
});
it("selects the node", function () {
happen.click(map.surface.node(), {});
expect(controller.mode.id).to.equal('select');
expect(controller.mode.selection()).to.eql([history.changes().created[0].id]);
happen.click(context.surface().node(), {});
expect(context.mode().id).to.equal('select');
expect(context.mode().selection()).to.eql([context.changes().created[0].id]);
});
});
describe("pressing ⎋", function () {
it("exits to browse mode", function () {
happen.keydown(document, {keyCode: 27});
expect(controller.mode.id).to.equal('browse');
expect(context.mode().id).to.equal('browse');
});
});
});
+2 -1
View File
@@ -2,7 +2,8 @@ describe('iD.OAuth', function() {
var o;
beforeEach(function() {
o = iD.OAuth();
context = iD();
o = iD.OAuth(context);
});
describe('#logout', function() {
+4 -16
View File
@@ -1,22 +1,10 @@
describe('iD.Map', function() {
var container, map;
var map;
beforeEach(function() {
container = d3.select('body').append('div');
map = iD.Map();
container.call(map);
});
afterEach(function() {
container.remove();
});
describe('#connection', function() {
it('gets and sets connection', function() {
var connection = iD.Connection();
expect(map.connection(connection)).to.equal(map);
expect(map.connection()).to.equal(connection);
});
map = iD().map();
d3.select(document.createElement('div'))
.call(map);
});
describe('#zoom', function() {
+3
View File
@@ -2,10 +2,13 @@ describe("iD.ui.confirm", function () {
it('can be instantiated', function () {
var confirm = iD.ui.confirm();
expect(confirm).to.be.ok;
happen.keydown(document, {keyCode: 27}); // dismiss
});
it('can be dismissed', function () {
var confirm = iD.ui.confirm();
happen.click(confirm.select('button').node());
expect(confirm.node().parentNode).to.be.null;
happen.keydown(document, {keyCode: 27}); // dismiss
});
});
+1
View File
@@ -4,5 +4,6 @@ describe("iD.ui.modal", function () {
.select('.content')
.text('foo');
expect(modal).to.be.ok;
happen.keydown(document, {keyCode: 27}); // dismiss
});
});