Merge branch 'master' into design-overhaul

This commit is contained in:
Saman Bemel-Benrud
2012-12-07 17:41:08 -05:00
54 changed files with 1015 additions and 502 deletions
-4
View File
@@ -44,10 +44,6 @@ path.casing {
stroke-width: 3;
}
.elastic-true {
pointer-events:none;
}
path.casing.hover {
stroke:#FF0F0F !important;
opacity:0.8;
+5 -1
View File
@@ -20,8 +20,10 @@
<script src='js/lib/d3.typeahead.js'></script>
<script src='js/lib/d3.geo.tile.js'></script>
<script src='js/lib/d3.size.js'></script>
<script src='js/lib/d3.latedrag.js'></script>
<script src='js/lib/d3.keybinding.js'></script>
<script src='js/lib/d3-compat.js'></script>
<script src='js/lib/queue.js'></script>
<script src='js/lib/bootstrap-tooltip.js'></script>
<script src='js/id/id.js'></script>
@@ -44,6 +46,7 @@
<script src='js/id/actions.js'></script>
<script src='js/id/actions/add_node.js'></script>
<script src='js/id/actions/add_way.js'></script>
<script src='js/id/actions/add_way_node.js'></script>
<script src='js/id/actions/change_entity_tags.js'></script>
<script src="js/id/actions/delete_node.js"></script>
@@ -53,9 +56,10 @@
<script src='js/id/actions/remove_relation_member.js'></script>
<script src='js/id/actions/remove_way_node.js'></script>
<script src='js/id/actions/reverse_way.js'></script>
<script src='js/id/actions/start_way.js'></script>
<script src='js/id/actions/split_way.js'></script>
<script src='js/id/modes.js'></script>
<script src='js/id/modes/drag_features.js'></script>
<script src='js/id/modes/add_area.js'></script>
<script src='js/id/modes/add_place.js'></script>
<script src='js/id/modes/add_road.js'></script>
+1 -1
View File
@@ -1,6 +1,6 @@
// https://github.com/openstreetmap/josm/blob/mirror/src/org/openstreetmap/josm/command/AddCommand.java
iD.actions.AddNode = function(node) {
return function(graph) {
return graph.replace(node, 'added a place');
return graph.replace(node);
};
};
+5
View File
@@ -0,0 +1,5 @@
iD.actions.AddWay = function(way) {
return function(graph) {
return graph.replace(way);
};
};
+6 -4
View File
@@ -1,8 +1,10 @@
// https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/AddNodeToWayAction.as
iD.actions.AddWayNode = function(way, node, index) {
iD.actions.AddWayNode = function(wayId, nodeId, index) {
return function(graph) {
var nodes = way.nodes.slice();
nodes.splice(index || nodes.length, 0, node.id);
return graph.replace(way.update({nodes: nodes})).replace(node, 'added to a road');
var way = graph.entity(wayId),
node = graph.entity(nodeId),
nodes = way.nodes.slice();
nodes.splice((index === undefined) ? nodes.length : index, 0, nodeId);
return graph.replace(way.update({nodes: nodes}));
};
};
+3 -4
View File
@@ -1,7 +1,6 @@
iD.actions.ChangeEntityTags = function(entity, tags) {
iD.actions.ChangeEntityTags = function(entityId, tags) {
return function(graph) {
return graph.replace(entity.update({
tags: tags
}), 'changed tags');
var entity = graph.entity(entityId);
return graph.replace(entity.update({tags: tags}));
};
};
+8 -6
View File
@@ -1,16 +1,18 @@
// https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/DeleteNodeAction.as
iD.actions.DeleteNode = function(node) {
iD.actions.DeleteNode = function(nodeId) {
return function(graph) {
graph.parentWays(node.id)
var node = graph.entity(nodeId);
graph.parentWays(nodeId)
.forEach(function(parent) {
graph = iD.actions.RemoveWayNode(parent, node)(graph);
graph = iD.actions.RemoveWayNode(parent.id, nodeId)(graph);
});
graph.parentRelations(node.id)
graph.parentRelations(nodeId)
.forEach(function(parent) {
graph = iD.actions.RemoveRelationMember(parent, node)(graph);
graph = iD.actions.RemoveRelationMember(parent.id, nodeId)(graph);
});
return graph.remove(node, 'removed a node');
return graph.remove(node);
};
};
+14 -8
View File
@@ -1,17 +1,23 @@
// https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/DeleteWayAction.as
iD.actions.DeleteWay = function(way) {
iD.actions.DeleteWay = function(wayId) {
return function(graph) {
graph.parentRelations(way.id)
var way = graph.entity(wayId);
graph.parentRelations(wayId)
.forEach(function(parent) {
graph = iD.actions.RemoveRelationMember(parent, way)(graph);
graph = iD.actions.RemoveRelationMember(parent.id, wayId)(graph);
});
way.nodes.forEach(function (id) {
var node = graph.entity(id);
way.nodes.forEach(function (nodeId) {
var node = graph.entity(nodeId);
graph = iD.actions.RemoveWayNode(way, node)(graph);
// Circular ways include nodes more than once, so they
// can be deleted on earlier iterations of this loop.
if (!node) return;
if (!graph.parentWays(id).length && !graph.parentRelations(id).length) {
graph = iD.actions.RemoveWayNode(wayId, nodeId)(graph);
if (!graph.parentWays(nodeId).length && !graph.parentRelations(nodeId).length) {
if (!node.hasInterestingTags()) {
graph = graph.remove(node);
} else {
@@ -20,6 +26,6 @@ iD.actions.DeleteWay = function(way) {
}
});
return graph.remove(way, 'removed a way');
return graph.remove(way);
};
};
+3 -2
View File
@@ -1,7 +1,8 @@
// https://github.com/openstreetmap/josm/blob/mirror/src/org/openstreetmap/josm/command/MoveCommand.java
// https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/MoveNodeAction.as
iD.actions.Move = function(entity, loc) {
iD.actions.Move = function(entityId, loc) {
return function(graph) {
return graph.replace(entity.update({loc: loc}), 'moved an element');
var entity = graph.entity(entityId);
return graph.replace(entity.update({loc: loc}));
};
};
+6 -3
View File
@@ -1,6 +1,9 @@
iD.actions.RemoveRelationMember = function(relation, member) {
iD.actions.RemoveRelationMember = function(relationId, memberId) {
return function(graph) {
var members = _.without(relation.members, member.id);
return graph.replace(relation.update({members: members}), 'removed from a relation');
var relation = graph.entity(relationId),
members = _.reject(relation.members, function(r) {
return r.id === memberId;
});
return graph.replace(relation.update({members: members}));
};
};
+14 -3
View File
@@ -1,6 +1,17 @@
iD.actions.RemoveWayNode = function(way, node) {
iD.actions.RemoveWayNode = function(wayId, nodeId) {
return function(graph) {
var nodes = _.without(way.nodes, node.id);
return graph.replace(way.update({nodes: nodes}), 'removed from a road');
var way = graph.entity(wayId), nodes;
// If this is the connecting node in a closed area
if (way.nodes.length > 1 &&
_.indexOf(way.nodes, nodeId) === 0 &&
_.lastIndexOf(way.nodes, nodeId) === way.nodes.length - 1) {
// Remove the node
nodes = _.without(way.nodes, nodeId);
// And reclose the way on the new first node.
nodes.push(nodes[0]);
} else {
nodes = _.without(way.nodes, nodeId);
}
return graph.replace(way.update({nodes: nodes}));
};
};
+4 -4
View File
@@ -1,8 +1,8 @@
// https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/AddNodeToWayAction.as
iD.actions.ReverseWay = function(way) {
iD.actions.ReverseWay = function(wayId) {
return function(graph) {
return graph.replace(way.update({
nodes: way.nodes.slice()
}), 'changed way direction');
var way = graph.entity(wayId),
nodes = way.nodes.slice().reverse();
return graph.replace(way.update({nodes: nodes}));
};
};
+47
View File
@@ -0,0 +1,47 @@
// https://github.com/systemed/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/SplitWayAction.as
iD.actions.SplitWay = function(nodeId) {
return function(graph) {
var parents = graph.parentWays(nodeId);
// splitting ways at intersections TODO
if (parents.length !== 1) return graph;
var way = parents[0];
var idx = _.indexOf(way.nodes, nodeId);
// Create a 'b' way that contains all of the tags in the second
// half of this way
var newWay = iD.Way({ tags: _.clone(way.tags), nodes: way.nodes.slice(idx) });
graph = graph.replace(newWay);
// Reduce the original way to only contain the first set of nodes
graph = graph.replace(way.update({ nodes: way.nodes.slice(0, idx + 1) }), 'changed way direction');
var parentRelations = graph.parentRelations(way.id);
function isVia(x) { return x.role = 'via'; }
function isSelf(x) { return x.id = way.id; }
parentRelations.forEach(function(relation) {
if (relation.tags.type === 'restriction') {
var via = _.find(relation.members, isVia);
var ownrole = _.find(relation.members, isSelf).role;
if (via && !_.contains(newWay.nodes, via.id)) {
// the new way doesn't contain the node that's important
// to the turn restriction, so we don't need to worry
// about adding it to the turn restriction.
} else {
graph = graph.replace(iD.actions.AddRelationMember(relation.id, {
role: ownrole,
id: newWay.id,
type: 'way'
}));
}
}
});
return graph;
};
};
-5
View File
@@ -1,5 +0,0 @@
iD.actions.StartWay = function(way) {
return function(graph) {
return graph.replace(way, 'started a road');
};
};
+13 -8
View File
@@ -67,8 +67,7 @@ iD.Connection = function() {
delete o.lon;
delete o.lat;
}
o._id = o.id;
o.id = o.type[0] + o.id;
o.id = iD.Entity.id.fromOSM(o.type, o.id);
return iD.Entity(o);
}
@@ -150,10 +149,6 @@ iD.Connection = function() {
return true;
}
function apiRequestExtent(extent) {
bboxFromAPI(extent, event.load);
}
function loadTiles(projection) {
var scaleExtent = [16, 16],
s = projection.scale(),
@@ -177,10 +172,20 @@ iD.Connection = function() {
projection.invert([x + ts, y + ts])];
}
return tiles
var q = queue(2);
var bboxes = tiles
.filter(tileAlreadyLoaded)
.map(apiExtentBox)
.map(apiRequestExtent);
.forEach(function(e) {
q.defer(bboxFromAPI, e);
});
q.awaitAll(function(err, res) {
var g = iD.Graph();
res.forEach(function(r) { g = g.merge(r); });
event.load(err, g);
});
}
connection.url = function(_) {
+3 -3
View File
@@ -52,17 +52,17 @@ iD.format.XML = {
'@version': 0.3,
'@generator': 'iD',
// TODO: copy elements first
create: nest(changes.create.map(function(c) {
create: nest(changes.created.map(function(c) {
var x = iD.Entity(c);
x.changeset = changeset_id;
return x;
}).map(iD.format.XML.rep)),
modify: changes.modify.map(function(c) {
modify: changes.modified.map(function(c) {
var x = iD.Entity(c);
x.changeset = changeset_id;
return x;
}).map(iD.format.XML.rep),
'delete': changes['delete'].map(function(c) {
'delete': changes.deleted.map(function(c) {
var x = iD.Entity(c);
x.changeset = changeset_id;
return x;
+22 -4
View File
@@ -14,8 +14,8 @@ iD.Entity = function(a, b, c) {
}
}
if (!this.id) {
this.id = iD.util.id(this.type);
if (!this.id && this.type) {
this.id = iD.Entity.id(this.type);
this._updated = true;
}
@@ -28,17 +28,35 @@ iD.Entity = function(a, b, c) {
}
};
iD.Entity.id = function (type) {
return iD.Entity.id.fromOSM(type, iD.Entity.id.next[type]--);
};
iD.Entity.id.next = {node: -1, way: -1, relation: -1};
iD.Entity.id.fromOSM = function (type, id) {
return type[0] + id;
};
iD.Entity.id.toOSM = function (id) {
return +id.slice(1);
};
iD.Entity.prototype = {
osmId: function() {
return iD.Entity.id.toOSM(this.id);
},
update: function(attrs) {
return iD.Entity(this, attrs, {_updated: true});
},
created: function() {
return this._updated && +this.id.slice(1) < 0;
return this._updated && this.osmId() < 0;
},
modified: function() {
return this._updated && +this.id.slice(1) > 0;
return this._updated && this.osmId() > 0;
},
intersects: function(extent, resolver) {
+57 -26
View File
@@ -1,5 +1,5 @@
iD.Graph = function(entities, annotation) {
if (!(this instanceof iD.Graph)) return new iD.Graph(entities, annotation);
iD.Graph = function(entities) {
if (!(this instanceof iD.Graph)) return new iD.Graph(entities);
if (_.isArray(entities)) {
this.entities = {};
@@ -10,8 +10,6 @@ iD.Graph = function(entities, annotation) {
this.entities = entities || {};
}
this.annotation = annotation;
if (iD.debug) {
Object.freeze(this);
Object.freeze(this.entities);
@@ -26,33 +24,40 @@ iD.Graph.prototype = {
parentWays: function(id) {
// This is slow and a bad hack.
return _.filter(this.entities, function(e) {
return e.type === 'way' && e.nodes.indexOf(id) !== -1;
return e && e.type === 'way' && e.nodes.indexOf(id) !== -1;
});
},
parentRelations: function(id) {
// This is slow and a bad hack.
return _.filter(this.entities, function(e) {
return e.type === 'relation' && e.members.indexOf(id) !== -1;
return e && e.type === 'relation' &&
_.pluck(e.members, 'id').indexOf(id) !== -1;
});
},
merge: function(graph) {
var entities = _.clone(this.entities);
_.defaults(entities, graph.entities);
return iD.Graph(entities, this.annotation);
return iD.Graph(entities);
},
replace: function(entity, annotation) {
replace: function(entity) {
var entities = _.clone(this.entities);
entities[entity.id] = entity;
return iD.Graph(entities, annotation);
return iD.Graph(entities);
},
remove: function(entity, annotation) {
remove: function(entity) {
var entities = _.clone(this.entities);
delete entities[entity.id];
return iD.Graph(entities, annotation);
if (entity.created()) {
delete entities[entity.id];
} else {
entities[entity.id] = undefined;
}
return iD.Graph(entities);
},
// get all objects that intersect an extent.
@@ -60,7 +65,7 @@ iD.Graph.prototype = {
var items = [];
for (var i in this.entities) {
var entity = this.entities[i];
if (entity.intersects(extent, this)) {
if (entity && entity.intersects(extent, this)) {
items.push(this.fetch(entity.id));
}
}
@@ -70,26 +75,52 @@ iD.Graph.prototype = {
// Resolve the id references in a way, replacing them with actual objects.
fetch: function(id) {
var entity = this.entities[id], nodes = [];
if (!entity.nodes || !entity.nodes.length) return iD.Entity(entity); // TODO: shouldn't be necessary
if (!entity || !entity.nodes || !entity.nodes.length) return entity;
for (var i = 0, l = entity.nodes.length; i < l; i++) {
nodes[i] = this.fetch(entity.nodes[i]);
}
return iD.Entity(entity, {nodes: nodes});
},
modifications: function() {
return _.filter(this.entities, function(entity) {
return entity.modified();
}).map(function(e) {
return this.fetch(e.id);
}.bind(this));
difference: function (graph) {
var result = [];
_.each(this.entities, function(entity, id) {
if (entity !== graph.entities[id]) {
result.push(id);
}
});
_.each(graph.entities, function(entity, id) {
if (entity && !this.entities.hasOwnProperty(id)) {
result.push(id);
}
}, this);
return result.sort();
},
creations: function() {
return _.filter(this.entities, function(entity) {
return entity.created();
}).map(function(e) {
return this.fetch(e.id);
}.bind(this));
modified: function() {
var result = [];
_.each(this.entities, function(entity, id) {
if (entity && entity.modified()) result.push(id);
});
return result;
},
created: function() {
var result = [];
_.each(this.entities, function(entity, id) {
if (entity && entity.created()) result.push(id);
});
return result;
},
deleted: function() {
var result = [];
_.each(this.entities, function(entity, id) {
if (!entity) result.push(id);
});
return result;
}
};
+53 -35
View File
@@ -2,50 +2,77 @@ iD.History = function() {
var stack, index,
dispatch = d3.dispatch('change');
function maybeChange() {
if (stack[index].annotation) {
dispatch.change();
function perform(actions) {
actions = Array.prototype.slice.call(actions);
var annotation;
if (_.isString(_.last(actions))) {
annotation = actions.pop();
}
var graph = stack[index].graph;
for (var i = 0; i < actions.length; i++) {
graph = actions[i](graph);
}
return {graph: graph, annotation: annotation};
}
function change(previous) {
dispatch.change(history.graph().difference(previous));
}
var history = {
graph: function () {
return stack[index];
return stack[index].graph;
},
merge: function (graph) {
for (var i = 0; i < stack.length; i++) {
stack[i] = stack[i].merge(graph);
stack[i].graph = stack[i].graph.merge(graph);
}
},
perform: function (action) {
perform: function () {
var previous = stack[index].graph;
stack = stack.slice(0, index + 1);
stack.push(action(this.graph()));
stack.push(perform(arguments));
index++;
maybeChange();
change(previous);
},
replace: function (action) {
replace: function () {
var previous = stack[index].graph;
// assert(index == stack.length - 1)
stack[index] = action(this.graph());
maybeChange();
stack[index] = perform(arguments);
change(previous);
},
undo: function () {
var previous = stack[index].graph;
while (index > 0) {
index--;
if (stack[index].annotation) break;
}
dispatch.change();
change(previous);
},
redo: function () {
var previous = stack[index].graph;
while (index < stack.length - 1) {
index++;
if (stack[index].annotation) break;
}
dispatch.change();
change(previous);
},
undoAnnotation: function () {
@@ -64,34 +91,25 @@ iD.History = function() {
}
},
// generate reports of changes for changesets to use
modify: function () {
return stack[index].modifications();
},
create: function () {
return stack[index].creations();
},
'delete': function () {
return _.difference(
_.pluck(stack[0].entities, 'id'),
_.pluck(stack[index].entities, 'id')
).map(function (id) {
return stack[0].fetch(id);
});
},
changes: function () {
var initial = stack[0].graph,
current = stack[index].graph;
return {
modify: this.modify(),
create: this.create(),
'delete': this['delete']()
modified: current.modified().map(function (id) {
return current.fetch(id);
}),
created: current.created().map(function (id) {
return current.fetch(id);
}),
deleted: current.deleted().map(function (id) {
return initial.fetch(id);
})
};
},
reset: function () {
stack = [iD.Graph()];
stack = [{graph: iD.Graph()}];
index = 0;
dispatch.change();
}
+1
View File
@@ -1,6 +1,7 @@
window.iD = function(container) {
var connection = iD.Connection()
.url('http://api06.dev.openstreetmap.org'),
// .url('http://www.openstreetmap.org'),
history = iD.History(),
map = iD.Map()
.connection(connection)
+26 -14
View File
@@ -7,29 +7,41 @@ iD.modes.AddArea = function() {
};
mode.enter = function() {
mode.map.dblclickEnable(false);
mode.map.hint('Click on the map to start drawing an area, like a park, lake, or building.');
var map = mode.map,
history = mode.history,
controller = mode.controller;
mode.map.surface.on('click.addarea', function() {
map.dblclickEnable(false)
.hint('Click on the map to start drawing an area, like a park, lake, or building.');
map.surface.on('click.addarea', function() {
var datum = d3.select(d3.event.target).datum() || {},
node,
way = iD.Way({tags: { building: 'yes', area: 'yes', elastic: 'true' }});
way = iD.Way({tags: { building: 'yes', area: 'yes' }});
// connect a way to an existing way
if (datum.type === 'node') {
node = datum;
// start from an existing node
history.perform(
iD.actions.AddWay(way),
iD.actions.AddWayNode(way.id, datum.id),
iD.actions.AddWayNode(way.id, datum.id),
'started an area');
} else {
node = iD.Node({loc: mode.map.mouseCoordinates()});
// start from a new node
var node = iD.Node({loc: map.mouseCoordinates()});
history.perform(
iD.actions.AddWay(way),
iD.actions.AddNode(node),
iD.actions.AddWayNode(way.id, node.id),
iD.actions.AddWayNode(way.id, node.id),
'started an area');
}
mode.history.perform(iD.actions.StartWay(way));
mode.history.perform(iD.actions.AddWayNode(way, node));
mode.controller.enter(iD.modes.DrawArea(way.id));
controller.enter(iD.modes.DrawArea(way.id));
});
mode.map.keybinding().on('⎋.addarea', function() {
mode.controller.exit();
map.keybinding().on('⎋.addarea', function() {
controller.exit();
});
};
+15 -7
View File
@@ -6,16 +6,24 @@ iD.modes.AddPlace = function() {
};
mode.enter = function() {
mode.map.hint('Click on the map to add a place.');
var map = mode.map,
history = mode.history,
controller = mode.controller;
mode.map.surface.on('click.addplace', function() {
var node = iD.Node({loc: mode.map.mouseCoordinates(), _poi: true});
mode.history.perform(iD.actions.AddNode(node));
mode.controller.enter(iD.modes.Select(node));
map.hint('Click on the map to add a place.');
map.surface.on('click.addplace', function() {
var node = iD.Node({loc: map.mouseCoordinates(), _poi: true});
history.perform(
iD.actions.AddNode(node),
'added a place');
controller.enter(iD.modes.Select(node));
});
mode.map.keybinding().on('⎋.addplace', function() {
mode.controller.exit();
map.keybinding().on('⎋.addplace', function() {
controller.exit();
});
};
+39 -32
View File
@@ -7,55 +7,62 @@ iD.modes.AddRoad = function() {
};
mode.enter = function() {
mode.map.dblclickEnable(false);
var map = mode.map,
node,
history = mode.history,
controller = mode.controller;
mode.map.hint('Click on the map to start drawing an road, path, or route.');
map.dblclickEnable(false)
.hint('Click on the map to start drawing an road, path, or route.');
mode.map.surface.on('click.addroad', function() {
map.surface.on('click.addroad', function() {
var datum = d3.select(d3.event.target).datum() || {},
node,
direction = 'forward',
start = true,
way = iD.Way({ tags: { highway: 'residential', elastic: 'true' } });
way = iD.Way({ tags: { highway: 'residential' } }),
direction = 'forward';
if (datum.type === 'node') {
// continue an existing way
node = datum;
var id = datum.id;
var parents = mode.history.graph().parentWays(id);
if (parents.length) {
if (parents[0].nodes[0] === id) {
way = parents[0];
direction = 'backward';
start = false;
} else if (_.last(parents[0].nodes) === id) {
way = parents[0];
start = false;
}
var parents = history.graph().parentWays(id);
if (parents.length && parents[0].nodes[0] === id) {
way = parents[0];
direction = 'backward';
} else if (parents.length && _.last(parents[0].nodes) === id) {
way = parents[0];
} else {
history.perform(
iD.actions.AddWay(way),
iD.actions.AddWayNode(way.id, datum.id),
'started a road');
}
} else if (datum.type === 'way') {
// begin a new way starting from an existing way
node = iD.Node({loc: mode.map.mouseCoordinates()});
node = iD.Node({loc: map.mouseCoordinates()}),
index = iD.util.geo.chooseIndex(datum, d3.mouse(map.surface.node()), map);
history.perform(
iD.actions.AddWay(way),
iD.actions.AddWayNode(datum.id, node, index),
iD.actions.AddWayNode(way.id, node.id),
'started a road');
var index = iD.util.geo.chooseIndex(datum, d3.mouse(mode.map.surface.node()), mode.map);
var connectedWay = mode.history.graph().entity(datum.id);
mode.history.perform(iD.actions.AddWayNode(connectedWay, node, index));
} else {
// begin a new way
node = iD.Node({loc: mode.map.mouseCoordinates()});
node = iD.Node({loc: map.mouseCoordinates()});
history.perform(
iD.actions.AddWay(way),
iD.actions.AddNode(node),
iD.actions.AddWayNode(way.id, node.id),
'started a road');
}
if (start) {
mode.history.perform(iD.actions.StartWay(way));
mode.history.perform(iD.actions.AddWayNode(way, node));
}
mode.controller.enter(iD.modes.DrawRoad(way.id, direction));
controller.enter(iD.modes.DrawRoad(way.id, direction));
});
mode.map.keybinding().on('⎋.addroad', function() {
mode.controller.exit();
map.keybinding().on('⎋.addroad', function() {
controller.exit();
});
};
+2
View File
@@ -7,6 +7,7 @@ iD.modes.Browse = function() {
};
mode.enter = function() {
iD.modes._dragFeatures(mode);
mode.map.surface.on('click.browse', function () {
var datum = d3.select(d3.event.target).datum();
if (datum instanceof iD.Entity) {
@@ -16,6 +17,7 @@ iD.modes.Browse = function() {
};
mode.exit = function() {
mode.map.surface.on('mousedown.latedrag', null);
mode.map.surface.on('click.browse', null);
};
+44
View File
@@ -0,0 +1,44 @@
iD.modes._dragFeatures = function(mode) {
var dragging;
var dragbehavior = d3.behavior.drag()
.origin(function(entity) {
var p = mode.map.projection(entity.loc);
return { x: p[0], y: p[1] };
})
.on('drag', function(entity) {
d3.event.sourceEvent.stopPropagation();
var loc = mode.map.projection.invert([d3.event.x, d3.event.y]);
if (!dragging) {
if (entity.accuracy) {
dragging = iD.Node({loc: loc});
mode.history.perform(
iD.actions.AddNode(dragging),
iD.actions.AddWayNode(entity.way, dragging.id, entity.index));
} else {
dragging = entity;
mode.history.perform(
iD.actions.Move(dragging.id, loc));
}
}
mode.history.replace(iD.actions.Move(dragging.id, loc));
})
.on('dragend', function (entity) {
if (!dragging) return;
dragging = undefined;
mode.history.replace(
iD.actions.Noop(),
entity.accuracy ? 'added a node to a way' : 'moved a node');
});
mode.map.surface
.call(dragbehavior)
.call(d3.latedrag()
.filter(function(d) {
return (d.type === 'node' || d.accuracy);
}));
};
+51 -44
View File
@@ -1,73 +1,80 @@
iD.modes.DrawArea = function(way_id) {
iD.modes.DrawArea = function(wayId) {
var mode = {
button: 'area'
};
mode.enter = function() {
var map = mode.map,
history = mode.history,
controller = mode.controller,
way = history.graph().entity(wayId),
headId = _.last(way.nodes),
tailId = _.first(way.nodes),
node = iD.Node({loc: map.mouseCoordinates()});
mode.map.hint('Click on the map to add points to your area. Finish the ' +
map.dblclickEnable(false)
.fastEnable(false)
.hint('Click on the map to add points to your area. Finish the ' +
'area by clicking on your first point');
mode.map.dblclickEnable(false);
var way = mode.history.graph().entity(way_id),
firstnode_id = _.first(way.nodes),
node = iD.Node({loc: mode.map.mouseCoordinates()});
history.perform(
iD.actions.AddNode(node),
iD.actions.AddWayNode(way.id, node.id, -1));
function finish(next) {
way = mode.history.graph().entity(way.id);
way.tags = _.omit(way.tags, 'elastic');
mode.history.perform(iD.actions.ChangeEntityTags(way, way.tags));
return mode.controller.enter(next);
}
mode.history.perform(iD.actions.AddWayNode(way, node));
mode.map.surface.on('mousemove.drawarea', function() {
mode.history.replace(iD.actions.AddWayNode(way, node.update({loc: mode.map.mouseCoordinates()})));
map.surface.on('mousemove.drawarea', function() {
history.replace(iD.actions.Move(node.id, map.mouseCoordinates()));
});
mode.map.surface.on('click.drawarea', function() {
d3.event.stopPropagation();
map.surface.on('click.drawarea', function() {
var datum = d3.select(d3.event.target).datum() || {};
var datum = d3.select(d3.event.target).datum();
if (datum.id === tailId) {
history.replace(
iD.actions.DeleteNode(node.id),
iD.actions.AddWayNode(way.id, tailId, -1),
'added to an area');
if (datum.type === 'node') {
if (datum.id == firstnode_id) {
mode.history.replace(iD.actions.DeleteNode(node));
mode.history.replace(iD.actions.AddWayNode(way,
mode.history.graph().entity(way.nodes[0])));
controller.enter(iD.modes.Select(way));
} else if (datum.type === 'node' && datum.id !== node.id) {
// connect the way to an existing node
history.replace(
iD.actions.DeleteNode(node.id),
iD.actions.AddWayNode(way.id, datum.id, -1),
'added to an area');
controller.enter(iD.modes.DrawArea(wayId));
return finish(iD.modes.Select(way));
} else {
// connect a way to an existing way
mode.history.replace(iD.actions.AddWayNode(way, datum));
}
} else {
node = node.update({loc: mode.map.mouseCoordinates()});
mode.history.replace(iD.actions.AddWayNode(way, node));
history.replace(
iD.actions.Noop(),
'added to an area');
controller.enter(iD.modes.DrawArea(wayId));
}
mode.controller.enter(iD.modes.DrawArea(way_id));
});
mode.map.keybinding().on('⎋.drawarea', function() {
finish(iD.modes.Browse());
map.keybinding().on('⎋.drawarea', function() {
history.replace(
iD.actions.DeleteNode(node.id));
controller.enter(iD.modes.Browse());
});
mode.map.keybinding().on('⌫.drawarea', function() {
map.keybinding().on('⌫.drawarea', function() {
d3.event.preventDefault();
var lastNode = _.last(way.nodes);
mode.history.replace(iD.actions.RemoveWayNode(way,
mode.history.graph().entity(lastNode)));
mode.history.replace(iD.actions.DeleteNode(
mode.history.graph().entity(lastNode)));
mode.history.replace(iD.actions.DeleteNode(node));
mode.controller.enter(iD.modes.DrawArea(way_id));
history.replace(
iD.actions.DeleteNode(node.id),
iD.actions.DeleteNode(headId));
controller.enter(iD.modes.DrawArea(wayId));
});
};
mode.exit = function() {
mode.map.hint(false);
mode.map.fastEnable(true);
mode.map.surface
.on('mousemove.drawarea', null)
.on('click.drawarea', null);
+67 -69
View File
@@ -1,94 +1,93 @@
iD.modes.DrawRoad = function(way_id, direction) {
iD.modes.DrawRoad = function(wayId, direction) {
var mode = {
button: 'road'
};
mode.enter = function() {
mode.map.dblclickEnable(false)
.dragEnable(false)
var map = mode.map,
history = mode.history,
controller = mode.controller,
way = history.graph().entity(wayId),
node = iD.Node({loc: map.mouseCoordinates()}),
index = (direction === 'forward') ? undefined : 0,
headId = (direction === 'forward') ? _.last(way.nodes) : _.first(way.nodes),
tailId = (direction === 'forward') ? _.first(way.nodes) : _.last(way.nodes);
map.dblclickEnable(false)
.fastEnable(false)
.hint('Click to add more points to the road. ' +
'Click on other roads to connect to them, and double-click to ' +
'end the road.');
'Click on other roads to connect to them, and double-click to ' +
'end the road.');
var index = (direction === 'forward') ? undefined : -1,
node = iD.Node({loc: mode.map.mouseCoordinates(), tags: { elastic: true } }),
way = mode.history.graph().entity(way_id),
firstNode = way.nodes[0],
lastNode = _.last(way.nodes);
history.perform(
iD.actions.AddNode(node),
iD.actions.AddWayNode(wayId, node.id, index));
function finish(next) {
way.tags = _.omit(way.tags, 'elastic');
mode.history.perform(iD.actions.ChangeEntityTags(way, way.tags));
return mode.controller.enter(next);
}
mode.history.perform(iD.actions.AddWayNode(way, node, index));
mode.map.surface.on('mousemove.drawroad', function() {
mode.history.replace(iD.actions.AddWayNode(way,
node.update({ loc: mode.map.mouseCoordinates() }), index));
map.surface.on('mousemove.drawroad', function() {
history.replace(iD.actions.Move(node.id, map.mouseCoordinates()));
});
mode.map.surface.on('click.drawroad', function() {
// d3.event.stopPropagation();
map.surface.on('click.drawroad', function() {
var datum = d3.select(d3.event.target).datum() || {};
if (datum.id === tailId) {
// connect the way in a loop
history.replace(
iD.actions.DeleteNode(node.id),
iD.actions.AddWayNode(wayId, tailId, index),
'added to a road');
controller.enter(iD.modes.Select(way));
} else if (datum.id === headId) {
// finish the way
history.replace(iD.actions.DeleteNode(node.id));
controller.enter(iD.modes.Select(way));
} else if (datum.type === 'node' && datum.id !== node.id) {
// connect the way to an existing node
history.replace(
iD.actions.DeleteNode(node.id),
iD.actions.AddWayNode(wayId, datum.id, index),
'added to a road');
controller.enter(iD.modes.DrawRoad(wayId, direction));
if (datum.type === 'node') {
if (datum.id == firstNode || datum.id == lastNode) {
// If mode is drawing a loop and mode is not the drawing
// end of the stick, finish the circle
if (direction === 'forward' && datum.id == firstNode) {
mode.history.replace(iD.actions.AddWayNode(way,
mode.history.graph().entity(firstNode), index));
} else if (direction === 'backward' && datum.id == lastNode) {
mode.history.replace(iD.actions.AddWayNode(way,
mode.history.graph().entity(lastNode), index));
}
mode.history.replace(iD.actions.DeleteNode(node));
return finish(iD.modes.Select(way));
} else if (datum.id == node.id) {
datum = datum.update({ tags: {} });
mode.history.replace(iD.actions.ChangeEntityTags(datum, {}));
mode.history.replace(iD.actions.DeleteNode(node));
mode.history.replace(iD.actions.AddWayNode(way, datum, index));
} else {
// connect a way to an existing way
mode.history.replace(iD.actions.DeleteNode(node));
mode.history.replace(iD.actions.AddWayNode(way, datum, index));
}
} else if (datum.type === 'way') {
node = node.update({loc: mode.map.mouseCoordinates() });
mode.history.replace(iD.actions.AddWayNode(way, node, index));
// connect the way to an existing way
var connectedIndex = iD.modes.chooseIndex(datum, d3.mouse(map.surface.node()), map);
history.replace(
iD.actions.AddWayNode(datum.id, node.id, connectedIndex),
'added to a road');
controller.enter(iD.modes.DrawRoad(wayId, direction));
var connectedWay = mode.history.graph().entity(datum.id);
var connectedIndex = iD.modes.chooseIndex(datum,
d3.mouse(mode.map.surface.node()),
mode.map);
mode.history.perform(iD.actions.AddWayNode(connectedWay,
node,
connectedIndex));
} else {
mode.history.replace(iD.actions.AddWayNode(way, node, index));
history.replace(
iD.actions.Noop(),
'added to a road');
controller.enter(iD.modes.DrawRoad(wayId, direction));
}
mode.controller.enter(iD.modes.DrawRoad(way_id, direction));
});
mode.map.keybinding().on('⎋.drawroad', function() {
finish(iD.modes.Browse());
map.keybinding().on('⎋.drawroad', function() {
history.replace(
iD.actions.DeleteNode(node.id));
controller.enter(iD.modes.Browse());
});
mode.map.keybinding().on('⌫.drawroad', function() {
map.keybinding().on('⌫.drawroad', function() {
d3.event.preventDefault();
mode.history.replace(iD.actions.RemoveWayNode(way,
mode.history.graph().entity(lastNode)));
mode.history.replace(iD.actions.DeleteNode(
mode.history.graph().entity(lastNode)));
mode.history.replace(iD.actions.DeleteNode(node));
mode.controller.enter(iD.modes.DrawRoad(way_id, direction));
history.replace(
iD.actions.DeleteNode(node.id),
iD.actions.DeleteNode(headId));
controller.enter(iD.modes.DrawRoad(wayId, direction));
});
};
@@ -103,7 +102,6 @@ iD.modes.DrawRoad = function(way_id, direction) {
.on('⌫.drawroad', null);
window.setTimeout(function() {
mode.map.dblclickEnable(true);
mode.map.dragEnable(true);
}, 1000);
};
+34 -15
View File
@@ -1,8 +1,9 @@
iD.modes.Select = function (entity) {
var mode = {
button: ''
},
inspector = iD.Inspector(),
button: 'browse'
};
var inspector = iD.Inspector(),
dragging, target;
var dragWay = d3.behavior.drag()
@@ -11,25 +12,23 @@ iD.modes.Select = function (entity) {
return { x: p[0], y: p[1] };
})
.on('drag', function(entity) {
if (!mode.map.dragEnable()) return;
d3.event.sourceEvent.stopPropagation();
if (!dragging) {
dragging = iD.util.trueObj([entity.id].concat(
_.pluck(mode.history.graph().parentWays(entity.id), 'id')));
dragging = true;
mode.history.perform(iD.actions.Noop());
}
entity.nodes.forEach(function(node) {
var start = mode.map.projection(node.loc);
var end = mode.map.projection.invert([start[0] + d3.event.dx, start[1] + d3.event.dy]);
node.loc = end;
mode.history.replace(iD.actions.Move(node, end));
var end = mode.map.projection.invert([
start[0] + d3.event.dx,
start[1] + d3.event.dy]);
mode.history.replace(iD.actions.Move(node.id, end));
});
})
.on('dragend', function () {
if (!mode.map.dragEnable() || !dragging) return;
if (!dragging) return;
dragging = undefined;
mode.map.redraw();
});
@@ -37,16 +36,22 @@ iD.modes.Select = function (entity) {
function remove() {
switch (entity.type) {
case 'way':
mode.history.perform(iD.actions.DeleteWay(entity));
mode.history.perform(
iD.actions.DeleteWay(entity.id),
'deleted a way');
break;
case 'node':
mode.history.perform(iD.actions.DeleteNode(entity));
mode.history.perform(
iD.actions.DeleteNode(entity.id),
'deleted a node');
}
mode.controller.exit();
}
mode.enter = function () {
iD.modes._dragFeatures(mode);
target = mode.map.surface.selectAll("*")
.filter(function (d) { return d === entity; });
@@ -57,11 +62,23 @@ iD.modes.Select = function (entity) {
.call(inspector);
inspector.on('changeTags', function(d, tags) {
mode.history.perform(iD.actions.ChangeEntityTags(mode.history.graph().entity(d.id), tags));
mode.history.perform(
iD.actions.ChangeEntityTags(d.id, tags),
'changed tags');
}).on('changeWayDirection', function(d) {
mode.history.perform(iD.actions.ReverseWay(d));
mode.history.perform(
iD.actions.ReverseWay(d.id),
'reversed a way');
}).on('splitWay', function(d) {
mode.history.perform(
iD.actions.SplitWay(d.id),
'split a way on a node');
}).on('remove', function() {
remove();
}).on('close', function() {
mode.controller.exit();
});
@@ -88,6 +105,8 @@ iD.modes.Select = function (entity) {
};
mode.exit = function () {
mode.map.surface.on('mousedown.latedrag', null);
d3.select('.inspector-wrap')
.style('display', 'none');
+11 -3
View File
@@ -67,12 +67,20 @@ iD.Background = function() {
image.exit().remove();
function load(d) {
cache[d] = true;
d3.select(this).on('load', null);
}
function error() {
d3.select(this).remove();
}
image.enter().append('img')
.attr('class', 'tile')
.attr('src', function(d) { return d[3]; })
.on('load', function(d) {
cache[d] = true;
});
.on('error', error)
.on('load', load);
function tileSize(d) {
return Math.ceil(256 * Math.pow(2, z - d[2])) / 256;
+40 -80
View File
@@ -12,38 +12,8 @@ iD.Map = function() {
.scaleExtent([1024, 256 * Math.pow(2, 24)])
.on('zoom', zoomPan),
dblclickEnabled = true,
dragEnabled = true,
dragging = false,
fastEnabled = true,
dragging,
dragbehavior = d3.behavior.drag()
.origin(function(entity) {
if (!dragEnabled) return { x: 0, y: 0 };
var p = projection(entity.loc);
return { x: p[0], y: p[1] };
})
.on('drag', function(entity) {
d3.event.sourceEvent.stopPropagation();
if (!dragging) {
if (entity.accuracy) {
var way = history.graph().entity(entity.way),
index = entity.index;
entity = iD.Node(entity);
history.perform(iD.actions.AddWayNode(way, entity, index));
}
dragging = iD.util.trueObj([entity.id].concat(
_.pluck(history.graph().parentWays(entity.id), 'id')));
history.perform(iD.actions.Noop());
}
var to = projection.invert([d3.event.x, d3.event.y]);
history.replace(iD.actions.Move(entity, to));
})
.on('dragend', function () {
if (!dragEnabled || !dragging) return;
dragging = undefined;
}),
background = iD.Background()
.projection(projection),
class_stroke = iD.Style.styleClasses('stroke'),
@@ -51,18 +21,7 @@ iD.Map = function() {
class_area = iD.Style.styleClasses('area'),
class_casing = iD.Style.styleClasses('casing'),
transformProp = iD.util.prefixProperty('Transform'),
support3d = (function() {
// test for translate3d support. Based on https://gist.github.com/3794226 by lorenzopolidori and webinista
var el = document.createElement('div'),
has3d = false;
document.body.insertBefore(el,null);
if (el.style[transformProp] !== undefined) {
el.style[transformProp] = 'translate3d(1px,1px,1px)';
has3d = window.getComputedStyle(el).getPropertyValue(transformProp);
}
document.body.removeChild(el);
return (has3d && has3d.length>0 && has3d!=="none");
})(),
support3d = iD.util.support3d(),
supersurface, surface, defs, tilegroup, r, g, alength;
function map() {
@@ -90,7 +49,7 @@ iD.Map = function() {
.attr('clip-path', 'url(#clip)');
g = ['fill', 'casing', 'stroke', 'text', 'hit', 'temp'].reduce(function(mem, i) {
return (mem[i] = r.append('g').attr('class', 'layer-g')) && mem;
return (mem[i] = r.append('g').attr('class', 'layer-g layer-' + i)) && mem;
}, {});
var arrow = surface.append('text').text('►----');
@@ -110,21 +69,30 @@ iD.Map = function() {
return 'M' + _.pluck(d.nodes, 'loc').map(projection).map(iD.util.geo.roundCoords).join('L');
}
function drawVector(only) {
function drawVector(difference) {
if (surface.style(transformProp) != 'none') return;
var all = [], ways = [], areas = [], points = [], waynodes = [],
var filter, all, ways = [], areas = [], points = [], waynodes = [],
extent = map.extent(),
graph = history.graph();
if (!only) {
if (!difference) {
all = graph.intersects(extent);
filter = d3.functor(true);
} else {
for (var id in only) all.push(graph.fetch(id));
var only = {};
difference.forEach(function (id) {
var entity = graph.fetch(id);
if (entity) {
only[id] = entity;
graph.parentWays(id).forEach(function (entity) {
only[entity.id] = graph.fetch(entity.id);
});
}
});
all = _.values(only);
filter = function(d) { return d.accuracy ? only[d.way] : only[d.id]; };
}
var filter = only ?
function(d) { return only[d.id]; } : function() { return true; };
if (all.length > 200000) return hideVector();
for (var i = 0; i < all.length; i++) {
@@ -158,8 +126,7 @@ iD.Map = function() {
loc: iD.util.geo.interp(way.nodes[i].loc, way.nodes[i + 1].loc, 0.5),
way: way.id,
index: i + 1,
accuracy: true,
tags: { name: 'Improve way accuracy' }
accuracy: true
});
}
}
@@ -171,30 +138,32 @@ iD.Map = function() {
.filter(filter)
.data(waynodes, key);
function olderOnTop(a, b) {
return (+a.id.slice(1)) - (+b.id.slice(1));
return a.osmId() - b.osmId();
}
handles.exit().remove();
handles.enter().append('image')
.attr({ width: 6, height: 6, 'class': 'handle', 'xlink:href': 'css/handle.png' })
.each(function(d) {
if (d.tags && d.tags.elastic) return;
d3.select(this).call(dragbehavior);
.attr({
width: 6,
height: 6,
'class': 'handle',
'xlink:href': 'css/handle.png'
});
handles.attr('transform', function(entity) {
var p = projection(entity.loc);
return 'translate(' + [~~p[0], ~~p[1]] + ') translate(-3, -3) rotate(45, 3, 3)';
return 'translate(' + [~~p[0], ~~p[1]] +
') translate(-3, -3) rotate(45, 3, 3)';
})
.classed('active', classActive)
.sort(olderOnTop);
}
function drawAccuracyHandles(waynodes) {
function drawAccuracyHandles(waynodes, filter) {
var handles = g.hit.selectAll('circle.accuracy-handle')
.data(waynodes, key);
.filter(filter)
.data(waynodes, function (d) { return [d.way, d.index].join(","); });
handles.exit().remove();
handles.enter().append('circle')
.attr({ r: 2, 'class': 'accuracy-handle' })
.call(dragbehavior);
.attr({ r: 2, 'class': 'accuracy-handle' });
handles.attr('transform', function(entity) {
var p = projection(entity.loc);
return 'translate(' + [~~p[0], ~~p[1]] + ')';
@@ -236,8 +205,7 @@ iD.Map = function() {
.data(points, key);
markers.exit().remove();
var marker = markers.enter().append('g')
.attr('class', 'marker')
.call(dragbehavior);
.attr('class', 'marker');
marker.append('circle')
.attr({ r: 10, cx: 8, cy: 8 });
marker.append('image')
@@ -282,14 +250,14 @@ iD.Map = function() {
function connectionLoad(err, result) {
history.merge(result);
drawVector(iD.util.trueObj(Object.keys(result.entities)));
drawVector(Object.keys(result.entities));
}
function hoverIn() {
var datum = d3.select(d3.event.target).datum();
if (datum instanceof iD.Entity) {
hover = datum.id;
drawVector(iD.util.trueObj([hover]));
drawVector([hover]);
d3.select('.messages').text(datum.tags.name || '#' + datum.id);
}
}
@@ -298,7 +266,7 @@ iD.Map = function() {
if (hover) {
var oldHover = hover;
hover = null;
drawVector(iD.util.trueObj([oldHover]));
drawVector([oldHover]);
d3.select('.messages').text('');
}
}
@@ -340,14 +308,12 @@ iD.Map = function() {
redraw();
}
function redraw() {
if (!dragging) {
dispatch.move(map);
tilegroup.call(background);
}
function redraw(difference) {
dispatch.move(map);
tilegroup.call(background);
if (map.zoom() > 16) {
connection.loadTiles(projection);
drawVector(dragging);
drawVector(difference);
} else {
hideVector();
}
@@ -376,12 +342,6 @@ iD.Map = function() {
return map;
};
map.dragEnable = function(_) {
if (!arguments.length) return dragEnabled;
dragEnabled = _;
return map;
};
map.fastEnable = function(_) {
if (!arguments.length) return fastEnabled;
fastEnabled = _;
+1 -1
View File
@@ -50,7 +50,7 @@ iD.Style.markerimage = function(d) {
iD.Style.TAG_CLASSES = iD.util.trueObj([
'highway', 'railway', 'motorway', 'amenity', 'natural',
'landuse', 'building', 'oneway', 'bridge', 'elastic'
'landuse', 'building', 'oneway', 'bridge'
]);
iD.Style.styleClasses = function(pre) {
+1 -1
View File
@@ -10,7 +10,7 @@ iD.commit = function() {
header.append('p').text('the changes you upload will be visible on all maps using OpenStreetMap data');
var section = body.selectAll('div.commit-section')
.data(['modify', 'delete', 'create'].filter(function(d) {
.data(['modified', 'deleted', 'created'].filter(function(d) {
return changes[d].length;
}))
.enter()
+12 -5
View File
@@ -1,5 +1,5 @@
iD.Inspector = function() {
var event = d3.dispatch('changeTags', 'changeWayDirection', 'update', 'remove', 'close'),
var event = d3.dispatch('changeTags', 'changeWayDirection', 'update', 'remove', 'close', 'splitWay'),
taginfo = iD.taginfo();
function drawhead(selection) {
@@ -10,7 +10,7 @@ iD.Inspector = function() {
.attr('class', 'permalink')
.attr('href', function(d) {
return 'http://www.openstreetmap.org/browse/' +
d.type + '/' + d.id.slice(1);
d.type + '/' + d.osmId();
})
.text('View on OSM');
selection.append('a')
@@ -32,9 +32,16 @@ iD.Inspector = function() {
.attr('href', '#')
.text('Reverse Direction')
.on('click', function(d) {
event.changeWayDirection(iD.Entity(d, {
nodes: _.pluck(d.nodes.reverse(), 'id')
}));
event.changeWayDirection(iD.Entity(d));
});
}
if (selection.datum().type === 'node' && !selection.datum()._poi) {
selection.append('a')
.attr('class', 'permalink')
.attr('href', '#')
.text('Split Way')
.on('click', function(d) {
event.splitWay(iD.Entity(d));
});
}
}
+2 -6
View File
@@ -5,6 +5,7 @@ iD.userpanel = function(connection) {
function update() {
selection.html('');
if (connection.authenticated()) {
selection.style('display', 'block');
connection.userDetails(function(user_details) {
selection.append('span')
.text('signed in as ')
@@ -24,12 +25,7 @@ iD.userpanel = function(connection) {
});
});
} else {
selection
.append('a')
.attr('class', 'login')
.attr('href', '#')
.text('login')
.on('click', event.login);
selection.html('').style('display', 'none');
}
}
connection.on('auth', update);
+14 -8
View File
@@ -1,13 +1,5 @@
iD.util = {};
iD.util._counters = {};
iD.util.id = function(counter) {
counter = counter || 'default';
if (!iD.util._counters[counter]) iD.util._counters[counter] = 0;
return counter[0] + (--iD.util._counters[counter]);
};
iD.util.trueObj = function(arr) {
var o = {};
for (var i = 0, l = arr.length; i < l; i++) o[arr[i]] = true;
@@ -74,6 +66,20 @@ iD.util.prefixProperty = function(property) {
})(prefixes);
};
iD.util.support3d = function() {
// test for translate3d support. Based on https://gist.github.com/3794226 by lorenzopolidori and webinista
var transformProp = iD.util.prefixProperty('Transform');
var el = document.createElement('div'),
has3d = false;
document.body.insertBefore(el, null);
if (el.style[transformProp] !== undefined) {
el.style[transformProp] = 'translate3d(1px,1px,1px)';
has3d = window.getComputedStyle(el).getPropertyValue(transformProp);
}
document.body.removeChild(el);
return (has3d && has3d.length>0 && has3d!=="none");
};
iD.util.geo = {};
iD.util.geo.roundCoords = function(c) {
+22
View File
@@ -0,0 +1,22 @@
d3.latedrag = function() {
var filter = d3.functor(true);
function latedrag(selection) {
var mousedown = selection.on('mousedown.drag');
selection.on('mousedown.drag', null);
selection.on('mousedown.latedrag', function() {
var datum = d3.select(d3.event.target).datum();
if (datum && filter(datum)) {
mousedown.apply(selection.node(), [datum]);
}
});
}
latedrag.filter = function(_) {
if (!arguments.length) return filter;
filter = _;
return latedrag;
};
return latedrag;
};
+84
View File
@@ -0,0 +1,84 @@
(function() {
if (typeof module === "undefined") self.queue = queue;
else module.exports = queue;
queue.version = "1.0.0";
function queue(parallelism) {
var queue = {},
active = 0, // number of in-flight deferrals
remaining = 0, // number of deferrals remaining
head, tail, // singly-linked list of deferrals
error = null,
results = [],
await = noop,
awaitAll;
if (arguments.length < 1) parallelism = Infinity;
queue.defer = function() {
if (!error) {
var node = arguments;
node.index = results.push(undefined) - 1;
if (tail) tail.next = node, tail = tail.next;
else head = tail = node;
++remaining;
pop();
}
return queue;
};
queue.await = function(f) {
await = f;
awaitAll = false;
if (!remaining) notify();
return queue;
};
queue.awaitAll = function(f) {
await = f;
awaitAll = true;
if (!remaining) notify();
return queue;
};
function pop() {
if (head && active < parallelism) {
var node = head,
f = node[0],
a = Array.prototype.slice.call(node, 1),
i = node.index;
if (head === tail) head = tail = null;
else head = head.next;
++active;
a.push(function(e, r) {
--active;
if (error != null) return;
if (e != null) {
// clearing remaining cancels subsequent callbacks
// clearing head stops queued tasks from being executed
// setting error ignores subsequent calls to defer
error = e;
remaining = results = head = tail = null;
notify();
} else {
results[i] = r;
if (--remaining) pop();
else notify();
}
});
f.apply(null, a);
}
}
function notify() {
if (error != null) await(error);
else if (awaitAll) await(null, results);
else await.apply(null, [null].concat(results));
}
return queue;
}
function noop() {}
})();
+9 -1
View File
@@ -45,6 +45,7 @@
<script src='../js/id/actions.js'></script>
<script src='../js/id/actions/add_node.js'></script>
<script src='../js/id/actions/add_way.js'></script>
<script src='../js/id/actions/add_way_node.js'></script>
<script src='../js/id/actions/change_entity_tags.js'></script>
<script src="../js/id/actions/delete_node.js"></script>
@@ -54,7 +55,6 @@
<script src='../js/id/actions/remove_relation_member.js'></script>
<script src='../js/id/actions/remove_way_node.js'></script>
<script src='../js/id/actions/reverse_way.js'></script>
<script src='../js/id/actions/start_way.js'></script>
<script src='../js/id/modes.js'></script>
<script src='../js/id/modes/add_area.js'></script>
@@ -83,10 +83,18 @@
</script>
<!-- include spec files here... -->
<script src="spec/actions/add_node.js"></script>
<script src="spec/actions/add_way.js"></script>
<script src="spec/actions/add_way_node.js"></script>
<script src="spec/actions/change_entity_tags.js"></script>
<script src="spec/actions/delete_node.js"></script>
<script src="spec/actions/delete_way.js"></script>
<script src="spec/actions/move.js"></script>
<script src="spec/actions/noop.js"></script>
<script src="spec/actions/remove_way_node.js"></script>
<script src="spec/actions/remove_relation_member.js"></script>
<script src="spec/actions/reverse_way.js"></script>
<script src="spec/format/geojson.js"></script>
<script src="spec/format/xml.js"></script>
<script src="spec/graph/graph.js"></script>
+8
View File
@@ -25,10 +25,18 @@
</script>
<!-- include spec files here... -->
<script src="spec/actions/add_node.js"></script>
<script src="spec/actions/add_way.js"></script>
<script src="spec/actions/add_way_node.js"></script>
<script src="spec/actions/change_entity_tags.js"></script>
<script src="spec/actions/delete_node.js"></script>
<script src="spec/actions/delete_way.js"></script>
<script src="spec/actions/move.js"></script>
<script src="spec/actions/noop.js"></script>
<script src="spec/actions/remove_way_node.js"></script>
<script src="spec/actions/remove_relation_member.js"></script>
<script src="spec/actions/reverse_way.js"></script>
<script src="spec/format/geojson.js"></script>
<script src="spec/format/xml.js"></script>
<script src="spec/graph/graph.js"></script>
+7
View File
@@ -0,0 +1,7 @@
describe("iD.actions.AddNode", function () {
it("adds a node to the graph", function () {
var node = iD.Node(),
graph = iD.actions.AddNode(node)(iD.Graph());
expect(graph.entity(node.id)).to.equal(node);
});
});
+7
View File
@@ -0,0 +1,7 @@
describe("iD.actions.AddWay", function () {
it("adds a way to the graph", function () {
var way = iD.Way(),
graph = iD.actions.AddWay(way)(iD.Graph());
expect(graph.entity(way.id)).to.equal(way);
});
});
+17 -3
View File
@@ -2,14 +2,28 @@ describe("iD.actions.AddWayNode", function () {
it("adds a node to the end of a way", function () {
var way = iD.Way(),
node = iD.Node({id: "n1"}),
graph = iD.actions.AddWayNode(way, node)(iD.Graph());
graph = iD.actions.AddWayNode(way.id, node.id)(iD.Graph([way, node]));
expect(graph.entity(way.id).nodes).to.eql(["n1"]);
});
it("adds a node to a way at the specified index", function () {
it("adds a node to a way at index 0", function () {
var way = iD.Way({nodes: ["n1", "n3"]}),
node = iD.Node({id: "n2"}),
graph = iD.actions.AddWayNode(way, node, 1)(iD.Graph());
graph = iD.actions.AddWayNode(way.id, node.id, 0)(iD.Graph([way, node]));
expect(graph.entity(way.id).nodes).to.eql(["n2", "n1", "n3"]);
});
it("adds a node to a way at a positive index", function () {
var way = iD.Way({nodes: ["n1", "n3"]}),
node = iD.Node({id: "n2"}),
graph = iD.actions.AddWayNode(way.id, node.id, 1)(iD.Graph([way, node]));
expect(graph.entity(way.id).nodes).to.eql(["n1", "n2", "n3"]);
});
it("adds a node to a way at a negative index", function () {
var way = iD.Way({nodes: ["n1", "n3"]}),
node = iD.Node({id: "n2"}),
graph = iD.actions.AddWayNode(way.id, node.id, -1)(iD.Graph([way, node]));
expect(graph.entity(way.id).nodes).to.eql(["n1", "n2", "n3"]);
});
});
+8
View File
@@ -0,0 +1,8 @@
describe("iD.actions.ChangeEntityTags", function () {
it("changes an entity's tags", function () {
var entity = iD.Entity(),
tags = {foo: 'bar'},
graph = iD.actions.ChangeEntityTags(entity.id, tags)(iD.Graph([entity]));
expect(graph.entity(entity.id).tags).to.eql(tags);
});
});
+4 -4
View File
@@ -1,7 +1,7 @@
describe("iD.actions.DeleteNode", function () {
it("removes the node from the graph", function () {
var node = iD.Node(),
action = iD.actions.DeleteNode(node),
action = iD.actions.DeleteNode(node.id),
graph = action(iD.Graph([node]));
expect(graph.entity(node.id)).to.be.undefined;
});
@@ -9,15 +9,15 @@ describe("iD.actions.DeleteNode", function () {
it("removes the node from parent ways", function () {
var node = iD.Node(),
way = iD.Way({nodes: [node.id]}),
action = iD.actions.DeleteNode(node),
action = iD.actions.DeleteNode(node.id),
graph = action(iD.Graph([node, way]));
expect(graph.entity(way.id).nodes).not.to.contain(node.id);
});
it("removes the node from parent relations", function () {
var node = iD.Node(),
relation = iD.Relation({members: [node.id]}),
action = iD.actions.DeleteNode(node),
relation = iD.Relation({members: [{ id: node.id }]}),
action = iD.actions.DeleteNode(node.id),
graph = action(iD.Graph([node, relation]));
expect(graph.entity(relation.id).members).not.to.contain(node.id);
});
+8 -8
View File
@@ -1,23 +1,23 @@
describe("iD.actions.DeleteWay", function () {
it("removes the way from the graph", function () {
var way = iD.Way(),
action = iD.actions.DeleteWay(way),
action = iD.actions.DeleteWay(way.id),
graph = action(iD.Graph([way]));
expect(graph.entity(way.id)).to.be.undefined;
});
it("removes a way from parent relations", function () {
var way = iD.Way(),
relation = iD.Relation({members: [way.id]}),
action = iD.actions.DeleteWay(way),
relation = iD.Relation({members: [{ id: way.id }]}),
action = iD.actions.DeleteWay(way.id),
graph = action(iD.Graph([way, relation]));
expect(graph.entity(relation.id).members).not.to.contain(way.id);
expect(_.pluck(graph.entity(relation.id).members, 'id')).not.to.contain(way.id);
});
it("deletes member nodes not referenced by another parent", function () {
var node = iD.Node(),
way = iD.Way({nodes: [node.id]}),
action = iD.actions.DeleteWay(way),
action = iD.actions.DeleteWay(way.id),
graph = action(iD.Graph([node, way]));
expect(graph.entity(node.id)).to.be.undefined;
});
@@ -26,7 +26,7 @@ describe("iD.actions.DeleteWay", function () {
var node = iD.Node(),
way1 = iD.Way({nodes: [node.id]}),
way2 = iD.Way({nodes: [node.id]}),
action = iD.actions.DeleteWay(way1),
action = iD.actions.DeleteWay(way1.id),
graph = action(iD.Graph([node, way1, way2]));
expect(graph.entity(node.id)).not.to.be.undefined;
});
@@ -34,7 +34,7 @@ describe("iD.actions.DeleteWay", function () {
it("does not delete member nodes with interesting tags", function () {
var node = iD.Node({tags: {highway: 'traffic_signals'}}),
way = iD.Way({nodes: [node.id]}),
action = iD.actions.DeleteWay(way),
action = iD.actions.DeleteWay(way.id),
graph = action(iD.Graph([node, way]));
expect(graph.entity(node.id)).not.to.be.undefined;
});
@@ -42,7 +42,7 @@ describe("iD.actions.DeleteWay", function () {
it("registers member nodes with interesting tags as POIs", function () {
var node = iD.Node({tags: {highway: 'traffic_signals'}}),
way = iD.Way({nodes: [node.id]}),
action = iD.actions.DeleteWay(way),
action = iD.actions.DeleteWay(way.id),
graph = action(iD.Graph([node, way]));
expect(graph.entity(node.id)._poi).to.be.ok;
});
+8
View File
@@ -0,0 +1,8 @@
describe("iD.actions.Move", function () {
it("changes an entity's location", function () {
var entity = iD.Entity(),
loc = [2, 3],
graph = iD.actions.Move(entity.id, loc)(iD.Graph([entity]));
expect(graph.entity(entity.id).loc).to.eql(loc);
});
});
+7
View File
@@ -0,0 +1,7 @@
describe("iD.actions.Noop", function () {
it("does nothing", function () {
var graph = iD.Graph(),
action = iD.actions.Noop(graph);
expect(action(graph)).to.equal(graph);
});
});
@@ -0,0 +1,8 @@
describe("iD.actions.RemoveRelationMember", function () {
it("removes a member from a relation", function () {
var node = iD.Node(),
relation = iD.Way({members: [{ id: node.id }]}),
graph = iD.actions.RemoveRelationMember(relation.id, node.id)(iD.Graph([node, relation]));
expect(graph.entity(relation.id).members).to.eql([]);
});
});
+1 -1
View File
@@ -2,7 +2,7 @@ describe("iD.actions.RemoveWayNode", function () {
it("removes a node from a way", function () {
var node = iD.Node({id: "n1"}),
way = iD.Way({id: "w1", nodes: ["n1"]}),
graph = iD.actions.RemoveWayNode(way, node)(iD.Graph({n1: node, w1: way}));
graph = iD.actions.RemoveWayNode(way.id, node.id)(iD.Graph({n1: node, w1: way}));
expect(graph.entity(way.id).nodes).to.eql([]);
});
});
+9
View File
@@ -0,0 +1,9 @@
describe("iD.actions.ReverseWay", 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]));
expect(graph.entity(way.id).nodes).to.eql([node2.id, node1.id]);
});
});
+22 -4
View File
@@ -1,4 +1,4 @@
describe('Entity', function () {
describe('iD.Entity', function () {
if (iD.debug) {
it("is frozen", function () {
expect(Object.isFrozen(iD.Entity())).to.be.true;
@@ -13,6 +13,24 @@ describe('Entity', function () {
});
}
describe(".id", function () {
it("generates unique IDs", function () {
expect(iD.Entity.id('node')).not.to.equal(iD.Entity.id('node'));
});
describe(".fromOSM", function () {
it("returns a ID string unique across entity types", function () {
expect(iD.Entity.id.fromOSM('node', 1)).to.equal("n1");
});
});
describe(".toOSM", function () {
it("reverses fromOSM", function () {
expect(iD.Entity.id.toOSM(iD.Entity.id.fromOSM('node', 1))).to.equal(1);
});
});
});
describe("#update", function () {
it("returns a new Entity", function () {
var a = iD.Entity(),
@@ -105,7 +123,7 @@ describe('Entity', function () {
});
});
describe('Node', function () {
describe('iD.Node', function () {
it("returns a node", function () {
expect(iD.Node().type).to.equal("node");
});
@@ -138,7 +156,7 @@ describe('Node', function () {
});
});
describe('Way', function () {
describe('iD.Way', function () {
if (iD.debug) {
it("freezes nodes", function () {
expect(Object.isFrozen(iD.Way().nodes)).to.be.true;
@@ -191,7 +209,7 @@ describe('Way', function () {
});
});
describe('Relation', function () {
describe('iD.Relation', function () {
if (iD.debug) {
it("freezes nodes", function () {
expect(Object.isFrozen(iD.Relation().members)).to.be.true;
+87 -47
View File
@@ -11,11 +11,6 @@ describe('iD.Graph', function() {
expect(graph.entity(entity.id)).to.equal(entity);
});
it('can be constructed with an annotation', function() {
var graph = iD.Graph({}, 'first graph');
expect(graph.annotation).to.equal('first graph');
});
if (iD.debug) {
it("is frozen", function () {
expect(Object.isFrozen(iD.Graph())).to.be.true;
@@ -26,35 +21,46 @@ describe('iD.Graph', function() {
});
}
describe('operations', function() {
it('#remove', function() {
var entities = { 'n-1': {
type: 'node',
loc: [-80, 30],
id: 'n-1'
}
};
var graph = iD.Graph(entities, 'first graph');
var g2 = graph.remove(entities['n-1'], 'Removed node');
expect(graph.entity('n-1')).to.equal(entities['n-1']);
expect(g2.entity('n-1')).to.equal(undefined);
describe("#remove", function () {
it("returns a new graph", function () {
var node = iD.Node(),
graph = iD.Graph([node]);
expect(graph.remove(node)).not.to.equal(graph);
});
it('#replace', function() {
var entities = { 'n-1': {
type: 'node',
loc: [-80, 30],
id: 'n-1'
}
};
var replacement = {
type: 'node',
loc: [-80, 40],
id: 'n-1'
};
var graph = iD.Graph(entities, 'first graph');
var g2 = graph.replace(replacement, 'Removed node');
expect(graph.entity('n-1').loc[1]).to.equal(30);
expect(g2.entity('n-1').loc[1]).to.equal(40);
it("doesn't modify the receiver", function () {
var node = iD.Node(),
graph = iD.Graph([node]);
graph.remove(node);
expect(graph.entity(node.id)).to.equal(node);
});
it("removes the entity from the result", function () {
var node = iD.Node(),
graph = iD.Graph([node]);
expect(graph.remove(node).entity(node.id)).to.be.undefined;
});
});
describe("#replace", function () {
it("returns a new graph", function () {
var node = iD.Node(),
graph = iD.Graph([node]);
expect(graph.replace(node)).not.to.equal(graph);
});
it("doesn't modify the receiver", function () {
var node = iD.Node(),
graph = iD.Graph([node]);
graph.replace(node);
expect(graph.entity(node.id)).to.equal(node);
});
it("replaces the entity in the result", function () {
var node1 = iD.Node(),
node2 = node1.update({}),
graph = iD.Graph([node1]);
expect(graph.replace(node2).entity(node2.id)).to.equal(node2);
});
});
@@ -71,7 +77,7 @@ describe('iD.Graph', function() {
describe("#parentRelations", function() {
it("returns an array of relations that contain the given entity id", function () {
var node = iD.Node({id: "n1"}),
relation = iD.Relation({id: "r1", members: ["n1"]}),
relation = iD.Relation({id: "r1", members: [{ id: "n1", role: 'from' }]}),
graph = iD.Graph({n1: node, r1: relation});
expect(graph.parentRelations("n1")).to.eql([relation]);
expect(graph.parentRelations("n2")).to.eql([]);
@@ -83,25 +89,59 @@ describe('iD.Graph', function() {
var node = iD.Node({id: "n1"}),
way = iD.Way({id: "w1", nodes: ["n1"]}),
graph = iD.Graph({n1: node, w1: way});
expect(graph.fetch("w1").nodes[0].id).to.equal("n1");
expect(graph.fetch("w1").nodes).to.eql([node]);
});
});
describe("#modifications", function () {
it("filters entities by modified", function () {
var a = {id: 'a', modified: function () { return true; }},
b = {id: 'b', modified: function () { return false; }},
graph = iD.Graph({ 'a': a, 'b': b });
expect(graph.modifications()).to.eql([graph.fetch('a')]);
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 that were subsequently deleted", function () {
var node = iD.Node(),
graph1 = iD.Graph([node]),
graph2 = graph1.remove(node);
expect(graph2.difference(graph1)).to.eql([node.id]);
});
});
describe("#creations", function () {
it("filters entities by created", function () {
var a = {id: 'a', created: function () { return true; }},
b = {id: 'b', created: function () { return false; }},
graph = iD.Graph({ 'a': a, 'b': b });
expect(graph.creations()).to.eql([graph.fetch('a')]);
describe("#modified", function () {
it("returns an Array of ids of modified entities", function () {
var node1 = iD.Node({id: 'n1', _updated: true}),
node2 = iD.Node({id: 'n2'}),
graph = iD.Graph([node1, node2]);
expect(graph.modified()).to.eql([node1.id]);
});
});
describe("#created", function () {
it("returns an Array of ids of created entities", function () {
var node1 = iD.Node({id: 'n-1', _updated: true}),
node2 = iD.Node({id: 'n2'}),
graph = iD.Graph([node1, node2]);
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([node]).remove(node);
expect(graph.deleted()).to.eql([]);
});
});
});
+75 -18
View File
@@ -1,7 +1,6 @@
describe("History", function () {
describe("iD.History", function () {
var history, spy,
graph = iD.Graph([], "action"),
action = function() { return graph; };
action = function() { return iD.Graph(); };
beforeEach(function () {
history = iD.History();
@@ -16,46 +15,79 @@ describe("History", function () {
describe("#perform", function () {
it("updates the graph", function () {
history.perform(action);
var graph = iD.Graph();
history.perform(d3.functor(graph));
expect(history.graph()).to.equal(graph);
});
it("pushes the undo stack", function () {
history.perform(action);
expect(history.undoAnnotation()).to.equal("action");
it("pushes an undo annotation", function () {
history.perform(action, "annotation");
expect(history.undoAnnotation()).to.equal("annotation");
});
it("emits a change event", function () {
history.on('change', spy);
history.perform(action);
expect(spy).to.have.been.called;
expect(spy).to.have.been.calledWith([]);
});
it("does not emit a change event when performing a noop", function () {
it("performs multiple actions", function () {
var action1 = sinon.stub().returns(iD.Graph()),
action2 = sinon.stub().returns(iD.Graph());
history.perform(action1, action2, "annotation");
expect(action1).to.have.been.called;
expect(action2).to.have.been.called;
expect(history.undoAnnotation()).to.equal("annotation");
});
});
describe("#replace", function () {
it("updates the graph", function () {
var graph = iD.Graph();
history.replace(d3.functor(graph));
expect(history.graph()).to.equal(graph);
});
it("replaces the undo annotation", function () {
history.perform(action, "annotation1");
history.replace(action, "annotation2");
expect(history.undoAnnotation()).to.equal("annotation2");
});
it("emits a change event", function () {
history.on('change', spy);
history.perform(iD.actions.Noop);
expect(spy).not.to.have.been.called;
history.replace(action);
expect(spy).to.have.been.calledWith([]);
});
it("performs multiple actions", function () {
var action1 = sinon.stub().returns(iD.Graph()),
action2 = sinon.stub().returns(iD.Graph());
history.replace(action1, action2, "annotation");
expect(action1).to.have.been.called;
expect(action2).to.have.been.called;
expect(history.undoAnnotation()).to.equal("annotation");
});
});
describe("#undo", function () {
it("pops the undo stack", function () {
history.perform(action);
history.perform(action, "annotation");
history.undo();
expect(history.undoAnnotation()).to.be.undefined;
});
it("pushes the redo stack", function () {
history.perform(action);
history.perform(action, "annotation");
history.undo();
expect(history.redoAnnotation()).to.equal("action");
expect(history.redoAnnotation()).to.equal("annotation");
});
it("emits a change event", function () {
history.perform(action);
history.on('change', spy);
history.undo();
expect(spy).to.have.been.called;
expect(spy).to.have.been.calledWith([]);
});
});
@@ -65,14 +97,39 @@ describe("History", function () {
history.undo();
history.on('change', spy);
history.redo();
expect(spy).to.have.been.called;
expect(spy).to.have.been.calledWith([]);
});
});
describe("#changes", function () {
it("includes created entities", function () {
var node = iD.Node();
history.perform(function (graph) { return graph.replace(node); });
expect(history.changes().created).to.eql([node]);
});
it("includes modified entities", function () {
var node1 = iD.Node({id: "n1"}),
node2 = node1.update({}),
graph = iD.Graph([node1]);
history.merge(graph);
history.perform(function (graph) { return graph.replace(node2); });
expect(history.changes().modified).to.eql([node2]);
});
it("includes deleted entities", function () {
var node = iD.Node({id: "n1"}),
graph = iD.Graph([node]);
history.merge(graph);
history.perform(function (graph) { return graph.remove(node); });
expect(history.changes().deleted).to.eql([node]);
});
});
describe("#reset", function () {
it("clears the version stack", function () {
history.perform(action);
history.perform(action);
history.perform(action, "annotation");
history.perform(action, "annotation");
history.undo();
history.reset();
expect(history.undoAnnotation()).to.be.undefined;
-10
View File
@@ -1,16 +1,6 @@
describe('Util', function() {
var util;
it('#id', function() {
var a = iD.util.id(),
b = iD.util.id(),
c = iD.util.id(),
d = iD.util.id();
expect(a === b).to.equal(false);
expect(b === c).to.equal(false);
expect(c === d).to.equal(false);
});
it('#trueObj', function() {
expect(iD.util.trueObj(['a', 'b', 'c'])).to.eql({ a: true, b: true, c: true });
expect(iD.util.trueObj([])).to.eql({});