From cd3129061c5f08b7fd22e6771aff0a03bc2d31f2 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Thu, 6 Dec 2012 17:33:02 -0500 Subject: [PATCH 01/40] Refactor dragging, include latedrag --- index.html | 1 + js/id/modes/browse.js | 34 ++++++++++++++++++++++++++++++ js/id/modes/select.js | 4 +++- js/id/renderer/map.js | 48 ++++++++----------------------------------- js/lib/d3.latedrag.js | 22 ++++++++++++++++++++ 5 files changed, 69 insertions(+), 40 deletions(-) create mode 100644 js/lib/d3.latedrag.js diff --git a/index.html b/index.html index 38a17504b..9cf8c269f 100644 --- a/index.html +++ b/index.html @@ -20,6 +20,7 @@ + diff --git a/js/id/modes/browse.js b/js/id/modes/browse.js index 0e6789f77..01eae38c9 100644 --- a/js/id/modes/browse.js +++ b/js/id/modes/browse.js @@ -6,7 +6,41 @@ iD.modes.Browse = function() { description: 'Pan and zoom the map' }; + 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(); + if (!dragging) { + if (entity.accuracy) { + var way = history.graph().entity(entity.way), + index = entity.index; + entity = iD.Node(entity); + mode.history.perform(iD.actions.AddWayNode(way, entity, index)); + } + dragging = iD.util.trueObj([entity.id].concat( + _.pluck(mode.history.graph().parentWays(entity.id), 'id'))); + mode.history.perform(iD.actions.Noop()); + } + var to = mode.map.projection.invert([d3.event.x, d3.event.y]); + mode.history.replace(iD.actions.Move(entity, to)); + }) + .on('dragend', function () { + if (!dragging) return; + dragging = undefined; + }); + mode.enter = function() { + mode.map.surface + .call(dragbehavior) + .call(d3.latedrag() + .filter(function(d) { + return d.type === 'node'; + })); mode.map.surface.on('click.browse', function () { var datum = d3.select(d3.event.target).datum(); if (datum instanceof iD.Entity) { diff --git a/js/id/modes/select.js b/js/id/modes/select.js index fe18dba6d..d4ad71c74 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -23,7 +23,9 @@ iD.modes.Select = function (entity) { 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]); + 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)); }); diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index 4e678aefa..545c5db89 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -13,37 +13,8 @@ iD.Map = function() { .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'), @@ -90,7 +61,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('►----'); @@ -175,10 +146,11 @@ iD.Map = function() { } 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); @@ -193,8 +165,7 @@ iD.Map = function() { .data(waynodes, key); 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 +207,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') diff --git a/js/lib/d3.latedrag.js b/js/lib/d3.latedrag.js new file mode 100644 index 000000000..06970bca1 --- /dev/null +++ b/js/lib/d3.latedrag.js @@ -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(d3.event.target, [datum]); + } + }); + } + + latedrag.filter = function(_) { + if (!arguments.length) return filter; + filter = _; + return latedrag; + }; + + return latedrag; +}; From 6e9d9ba274dc8f85c6df5b20484b085b2bc98275 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Thu, 6 Dec 2012 17:54:32 -0500 Subject: [PATCH 02/40] Refactor modes, use latedrag --- js/id/modes/add_area.js | 2 +- js/id/modes/add_road.js | 2 +- js/id/modes/draw_road.js | 2 +- js/id/renderer/map.js | 3 ++- js/id/renderer/style.js | 2 +- js/lib/d3.latedrag.js | 2 +- 6 files changed, 7 insertions(+), 6 deletions(-) diff --git a/js/id/modes/add_area.js b/js/id/modes/add_area.js index 06857ba32..15fe6fd84 100644 --- a/js/id/modes/add_area.js +++ b/js/id/modes/add_area.js @@ -13,7 +13,7 @@ iD.modes.AddArea = function() { mode.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') { diff --git a/js/id/modes/add_road.js b/js/id/modes/add_road.js index efb3db113..8f7495b45 100644 --- a/js/id/modes/add_road.js +++ b/js/id/modes/add_road.js @@ -16,7 +16,7 @@ iD.modes.AddRoad = function() { node, direction = 'forward', start = true, - way = iD.Way({ tags: { highway: 'residential', elastic: 'true' } }); + way = iD.Way({ tags: { highway: 'residential' } }); if (datum.type === 'node') { // continue an existing way diff --git a/js/id/modes/draw_road.js b/js/id/modes/draw_road.js index bd4ad1507..5e9ad0415 100644 --- a/js/id/modes/draw_road.js +++ b/js/id/modes/draw_road.js @@ -12,7 +12,7 @@ iD.modes.DrawRoad = function(way_id, direction) { 'end the road.'); var index = (direction === 'forward') ? undefined : -1, - node = iD.Node({loc: mode.map.mouseCoordinates(), tags: { elastic: true } }), + node = iD.Node({loc: mode.map.mouseCoordinates() }), way = mode.history.graph().entity(way_id), firstNode = way.nodes[0], lastNode = _.last(way.nodes); diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index 545c5db89..7fd0780d2 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -154,7 +154,8 @@ iD.Map = function() { }); 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); diff --git a/js/id/renderer/style.js b/js/id/renderer/style.js index bf6206c88..01bece5c8 100644 --- a/js/id/renderer/style.js +++ b/js/id/renderer/style.js @@ -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) { diff --git a/js/lib/d3.latedrag.js b/js/lib/d3.latedrag.js index 06970bca1..6ef3edd1e 100644 --- a/js/lib/d3.latedrag.js +++ b/js/lib/d3.latedrag.js @@ -7,7 +7,7 @@ d3.latedrag = function() { selection.on('mousedown.latedrag', function() { var datum = d3.select(d3.event.target).datum(); if (datum && filter(datum)) { - mousedown.apply(d3.event.target, [datum]); + mousedown.apply(selection.node(), [datum]); } }); } From 3b7af751f4d8335fc8e2f11367c5a16d5b1265cb Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Thu, 6 Dec 2012 17:55:26 -0500 Subject: [PATCH 03/40] Remove listeners when we're done --- js/id/modes/browse.js | 1 + 1 file changed, 1 insertion(+) diff --git a/js/id/modes/browse.js b/js/id/modes/browse.js index 01eae38c9..51d65289c 100644 --- a/js/id/modes/browse.js +++ b/js/id/modes/browse.js @@ -50,6 +50,7 @@ iD.modes.Browse = function() { }; mode.exit = function() { + mode.map.surface.on('mousedown.latedrag', null); mode.map.surface.on('click.browse', null); }; From 54ff10ca6afcc953cc4b0f1e96428dd62b1f7abc Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Thu, 6 Dec 2012 18:01:57 -0500 Subject: [PATCH 04/40] Only listen for load once. Remove tiles on error --- js/id/renderer/background.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/js/id/renderer/background.js b/js/id/renderer/background.js index 275a6f2dc..35fb4ea29 100644 --- a/js/id/renderer/background.js +++ b/js/id/renderer/background.js @@ -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; From 7fea779e8c12bcceb334edf150dc49770389289d Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Thu, 6 Dec 2012 18:09:30 -0500 Subject: [PATCH 05/40] Fixup fastenable with drawing areas --- js/id/modes/draw_area.js | 3 ++- js/id/modes/draw_road.js | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/js/id/modes/draw_area.js b/js/id/modes/draw_area.js index 70ef0aaee..83cc80030 100644 --- a/js/id/modes/draw_area.js +++ b/js/id/modes/draw_area.js @@ -7,7 +7,7 @@ iD.modes.DrawArea = function(way_id) { mode.map.hint('Click on the map to add points to your area. Finish the ' + 'area by clicking on your first point'); - mode.map.dblclickEnable(false); + mode.map.dblclickEnable(false).fastEnable(false); var way = mode.history.graph().entity(way_id), firstnode_id = _.first(way.nodes), @@ -68,6 +68,7 @@ iD.modes.DrawArea = function(way_id) { mode.exit = function() { mode.map.hint(false); + mode.map.fastEnable(true); mode.map.surface .on('mousemove.drawarea', null) .on('click.drawarea', null); diff --git a/js/id/modes/draw_road.js b/js/id/modes/draw_road.js index 5e9ad0415..e8a637e3f 100644 --- a/js/id/modes/draw_road.js +++ b/js/id/modes/draw_road.js @@ -6,8 +6,9 @@ iD.modes.DrawRoad = function(way_id, direction) { mode.enter = function() { mode.map.dblclickEnable(false) .dragEnable(false) - .fastEnable(false) - .hint('Click to add more points to the road. ' + + .fastEnable(false); + + mode.map.hint('Click to add more points to the road. ' + 'Click on other roads to connect to them, and double-click to ' + 'end the road.'); From 27af384a3f30e707d2b7de3947cbcbbb5be4f392 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 6 Dec 2012 16:13:52 -0500 Subject: [PATCH 06/40] Allow History#{perform,replace} to run multiple actions --- js/id/graph/history.js | 20 ++++++++++++++---- test/spec/graph/history.js | 42 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 4 deletions(-) diff --git a/js/id/graph/history.js b/js/id/graph/history.js index 11a6111ce..df444840b 100644 --- a/js/id/graph/history.js +++ b/js/id/graph/history.js @@ -19,16 +19,28 @@ iD.History = function() { } }, - perform: function (action) { + perform: function () { stack = stack.slice(0, index + 1); - stack.push(action(this.graph())); + + var graph = this.graph(); + for (var i = 0; i < arguments.length; i++) { + graph = arguments[i](graph); + } + + stack.push(graph); index++; maybeChange(); }, - replace: function (action) { + replace: function () { // assert(index == stack.length - 1) - stack[index] = action(this.graph()); + + var graph = this.graph(); + for (var i = 0; i < arguments.length; i++) { + graph = arguments[i](graph); + } + + stack[index] = graph; maybeChange(); }, diff --git a/test/spec/graph/history.js b/test/spec/graph/history.js index dda4f3168..49f4fe397 100644 --- a/test/spec/graph/history.js +++ b/test/spec/graph/history.js @@ -36,6 +36,48 @@ describe("History", function () { history.perform(iD.actions.Noop); expect(spy).not.to.have.been.called; }); + + it("performs multiple actions", function () { + var action1 = sinon.stub().returns(graph), + action2 = sinon.stub().returns(graph); + history.perform(action1, action2); + expect(action1).to.have.been.called; + expect(action2).to.have.been.called; + }); + }); + + describe("#replace", function () { + it("updates the graph", function () { + history.replace(action); + expect(history.graph()).to.equal(graph); + }); + + it("replaces the undo stack", function () { + history.perform(action); + history.replace(action); + history.undo(); + expect(history.undoAnnotation()).to.be.undefined; + }); + + it("emits a change event", function () { + history.on('change', spy); + history.replace(action); + expect(spy).to.have.been.called; + }); + + it("does not emit a change event when performing a noop", function () { + history.on('change', spy); + history.replace(iD.actions.Noop); + expect(spy).not.to.have.been.called; + }); + + it("performs multiple actions", function () { + var action1 = sinon.stub().returns(graph), + action2 = sinon.stub().returns(graph); + history.replace(action1, action2); + expect(action1).to.have.been.called; + expect(action2).to.have.been.called; + }); }); describe("#undo", function () { From 79e2d08c0d13cf52520afdbfa6df40bcc041c558 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 6 Dec 2012 16:54:03 -0500 Subject: [PATCH 07/40] StartWay -> AddWay (for parallelism with AddNode) --- index.html | 2 +- js/id/actions/{start_way.js => add_way.js} | 2 +- js/id/modes/add_area.js | 2 +- js/id/modes/add_road.js | 2 +- test/index.html | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) rename js/id/actions/{start_way.js => add_way.js} (70%) diff --git a/index.html b/index.html index 9cf8c269f..33706d3dd 100644 --- a/index.html +++ b/index.html @@ -45,6 +45,7 @@ + @@ -54,7 +55,6 @@ - diff --git a/js/id/actions/start_way.js b/js/id/actions/add_way.js similarity index 70% rename from js/id/actions/start_way.js rename to js/id/actions/add_way.js index 6c82e389d..4b53a8ea3 100644 --- a/js/id/actions/start_way.js +++ b/js/id/actions/add_way.js @@ -1,4 +1,4 @@ -iD.actions.StartWay = function(way) { +iD.actions.AddWay = function(way) { return function(graph) { return graph.replace(way, 'started a road'); }; diff --git a/js/id/modes/add_area.js b/js/id/modes/add_area.js index 15fe6fd84..ca81bea7e 100644 --- a/js/id/modes/add_area.js +++ b/js/id/modes/add_area.js @@ -22,7 +22,7 @@ iD.modes.AddArea = function() { node = iD.Node({loc: mode.map.mouseCoordinates()}); } - mode.history.perform(iD.actions.StartWay(way)); + mode.history.perform(iD.actions.AddWay(way)); mode.history.perform(iD.actions.AddWayNode(way, node)); mode.controller.enter(iD.modes.DrawArea(way.id)); diff --git a/js/id/modes/add_road.js b/js/id/modes/add_road.js index 8f7495b45..c731353b2 100644 --- a/js/id/modes/add_road.js +++ b/js/id/modes/add_road.js @@ -47,7 +47,7 @@ iD.modes.AddRoad = function() { } if (start) { - mode.history.perform(iD.actions.StartWay(way)); + mode.history.perform(iD.actions.AddWay(way)); mode.history.perform(iD.actions.AddWayNode(way, node)); } diff --git a/test/index.html b/test/index.html index f5234875f..dbfd7cba6 100644 --- a/test/index.html +++ b/test/index.html @@ -45,6 +45,7 @@ + @@ -54,7 +55,6 @@ - From 9743ee282b3c5e84dc0d9f8cae38a323fb41fdd3 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 6 Dec 2012 18:25:49 -0500 Subject: [PATCH 08/40] More mode and action overhaul Pass entities to actions via id; this allows safe composition of actions that modify the same entity. Fix remaining ghost node cases (#213). Create more logical undo states when drawing. --- js/id/actions/add_way_node.js | 10 +- js/id/actions/change_entity_tags.js | 7 +- js/id/actions/delete_node.js | 12 ++- js/id/actions/delete_way.js | 16 +-- js/id/actions/move.js | 3 +- js/id/actions/remove_relation_member.js | 5 +- js/id/actions/remove_way_node.js | 5 +- js/id/actions/reverse_way.js | 8 +- js/id/modes/add_area.js | 14 +-- js/id/modes/add_road.js | 51 +++++----- js/id/modes/browse.js | 10 +- js/id/modes/draw_area.js | 90 +++++++++-------- js/id/modes/draw_road.js | 128 +++++++++++------------- js/id/modes/select.js | 11 +- test/spec/actions/add_way_node.js | 4 +- test/spec/actions/delete_node.js | 6 +- test/spec/actions/delete_way.js | 12 +-- test/spec/actions/remove_way_node.js | 2 +- 18 files changed, 196 insertions(+), 198 deletions(-) diff --git a/js/id/actions/add_way_node.js b/js/id/actions/add_way_node.js index 7c8772dba..f3c4521aa 100644 --- a/js/id/actions/add_way_node.js +++ b/js/id/actions/add_way_node.js @@ -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 || nodes.length, 0, nodeId); + return graph.replace(way.update({nodes: nodes}), 'added to a road'); }; }; diff --git a/js/id/actions/change_entity_tags.js b/js/id/actions/change_entity_tags.js index 97c67f0aa..25cf5911c 100644 --- a/js/id/actions/change_entity_tags.js +++ b/js/id/actions/change_entity_tags.js @@ -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}), 'changed tags'); }; }; diff --git a/js/id/actions/delete_node.js b/js/id/actions/delete_node.js index c7813ce48..fb858d75f 100644 --- a/js/id/actions/delete_node.js +++ b/js/id/actions/delete_node.js @@ -1,14 +1,16 @@ // 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'); diff --git a/js/id/actions/delete_way.js b/js/id/actions/delete_way.js index 641f61af2..86fe3a3bc 100644 --- a/js/id/actions/delete_way.js +++ b/js/id/actions/delete_way.js @@ -1,17 +1,19 @@ // 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); + graph = iD.actions.RemoveWayNode(wayId, nodeId)(graph); - if (!graph.parentWays(id).length && !graph.parentRelations(id).length) { + if (!graph.parentWays(nodeId).length && !graph.parentRelations(nodeId).length) { if (!node.hasInterestingTags()) { graph = graph.remove(node); } else { diff --git a/js/id/actions/move.js b/js/id/actions/move.js index 6c15e8bdc..42c82db56 100644 --- a/js/id/actions/move.js +++ b/js/id/actions/move.js @@ -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) { + var entity = graph.entity(entityId); return graph.replace(entity.update({loc: loc}), 'moved an element'); }; }; diff --git a/js/id/actions/remove_relation_member.js b/js/id/actions/remove_relation_member.js index b10224c26..f4061d177 100644 --- a/js/id/actions/remove_relation_member.js +++ b/js/id/actions/remove_relation_member.js @@ -1,6 +1,7 @@ -iD.actions.RemoveRelationMember = function(relation, member) { +iD.actions.RemoveRelationMember = function(relationId, memberId) { return function(graph) { - var members = _.without(relation.members, member.id); + var relation = graph.entity(relationId), + members = _.without(relation.members, memberId); return graph.replace(relation.update({members: members}), 'removed from a relation'); }; }; diff --git a/js/id/actions/remove_way_node.js b/js/id/actions/remove_way_node.js index bf645b6b2..5988e0f0b 100644 --- a/js/id/actions/remove_way_node.js +++ b/js/id/actions/remove_way_node.js @@ -1,6 +1,7 @@ -iD.actions.RemoveWayNode = function(way, node) { +iD.actions.RemoveWayNode = function(wayId, nodeId) { return function(graph) { - var nodes = _.without(way.nodes, node.id); + var way = graph.entity(wayId), + nodes = _.without(way.nodes, nodeId); return graph.replace(way.update({nodes: nodes}), 'removed from a road'); }; }; diff --git a/js/id/actions/reverse_way.js b/js/id/actions/reverse_way.js index 4d14eb8f1..b170bf883 100644 --- a/js/id/actions/reverse_way.js +++ b/js/id/actions/reverse_way.js @@ -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(); + return graph.replace(way.update({nodes: nodes}), 'changed way direction'); }; }; diff --git a/js/id/modes/add_area.js b/js/id/modes/add_area.js index ca81bea7e..7a76a98db 100644 --- a/js/id/modes/add_area.js +++ b/js/id/modes/add_area.js @@ -12,19 +12,21 @@ iD.modes.AddArea = function() { mode.map.surface.on('click.addarea', function() { var datum = d3.select(d3.event.target).datum() || {}, - node, way = iD.Way({tags: { building: 'yes', area: 'yes' }}); // connect a way to an existing way if (datum.type === 'node') { - node = datum; + mode.history.perform( + iD.actions.AddWay(way), + iD.actions.AddWayNode(way.id, datum.id)); } else { - node = iD.Node({loc: mode.map.mouseCoordinates()}); + var node = iD.Node({loc: mode.map.mouseCoordinates()}); + mode.history.perform( + iD.actions.AddWay(way), + iD.actions.AddNode(node), + iD.actions.AddWayNode(way.id, node.id)); } - mode.history.perform(iD.actions.AddWay(way)); - mode.history.perform(iD.actions.AddWayNode(way, node)); - mode.controller.enter(iD.modes.DrawArea(way.id)); }); diff --git a/js/id/modes/add_road.js b/js/id/modes/add_road.js index c731353b2..a194b3295 100644 --- a/js/id/modes/add_road.js +++ b/js/id/modes/add_road.js @@ -7,54 +7,53 @@ iD.modes.AddRoad = function() { }; mode.enter = function() { - mode.map.dblclickEnable(false); + var map = mode.map; - 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' } }); 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; - } + 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 { + mode.history.perform( + iD.actions.AddWay(way), + iD.actions.AddWayNode(way.id, datum.id)); } } else if (datum.type === 'way') { // begin a new way starting from an existing way - node = iD.Node({loc: mode.map.mouseCoordinates()}); + var node = iD.Node({loc: map.mouseCoordinates()}), + index = iD.util.geo.chooseIndex(datum, d3.mouse(map.surface.node()), map); - 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)); + mode.history.perform( + iD.actions.AddWay(way), + iD.actions.AddWayNode(datum.id, node, index), + iD.actions.AddWayNode(way.id, node.id)); } else { // begin a new way - node = iD.Node({loc: mode.map.mouseCoordinates()}); - } + var node = iD.Node({loc: map.mouseCoordinates()}); - if (start) { - mode.history.perform(iD.actions.AddWay(way)); - mode.history.perform(iD.actions.AddWayNode(way, node)); + mode.history.perform( + iD.actions.AddWay(way), + iD.actions.AddNode(node), + iD.actions.AddWayNode(way.id, node.id)); } mode.controller.enter(iD.modes.DrawRoad(way.id, direction)); }); - mode.map.keybinding().on('⎋.addroad', function() { + map.keybinding().on('⎋.addroad', function() { mode.controller.exit(); }); }; diff --git a/js/id/modes/browse.js b/js/id/modes/browse.js index 51d65289c..034330359 100644 --- a/js/id/modes/browse.js +++ b/js/id/modes/browse.js @@ -17,17 +17,17 @@ iD.modes.Browse = function() { d3.event.sourceEvent.stopPropagation(); if (!dragging) { if (entity.accuracy) { - var way = history.graph().entity(entity.way), - index = entity.index; - entity = iD.Node(entity); - mode.history.perform(iD.actions.AddWayNode(way, entity, index)); + var node = iD.Node(entity); + mode.history.perform( + iD.actions.AddNode(node), + iD.actions.AddWayNode(entity.way, node.id, entity.index)); } dragging = iD.util.trueObj([entity.id].concat( _.pluck(mode.history.graph().parentWays(entity.id), 'id'))); mode.history.perform(iD.actions.Noop()); } var to = mode.map.projection.invert([d3.event.x, d3.event.y]); - mode.history.replace(iD.actions.Move(entity, to)); + mode.history.replace(iD.actions.Move(entity.id, to)); }) .on('dragend', function () { if (!dragging) return; diff --git a/js/id/modes/draw_area.js b/js/id/modes/draw_area.js index 83cc80030..9f5d5a575 100644 --- a/js/id/modes/draw_area.js +++ b/js/id/modes/draw_area.js @@ -1,68 +1,70 @@ -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).fastEnable(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)); - 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), + iD.actions.ChangeEntityTags(way.id, _.omit(way.tags, 'elastic'))); - 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)); + + 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)); + 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), + iD.actions.ChangeEntityTags(wayId, _.omit(way.tags, 'elastic'))); + + 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)); }); }; diff --git a/js/id/modes/draw_road.js b/js/id/modes/draw_road.js index e8a637e3f..f1e3693a9 100644 --- a/js/id/modes/draw_road.js +++ b/js/id/modes/draw_road.js @@ -1,95 +1,83 @@ -iD.modes.DrawRoad = function(way_id, direction) { +iD.modes.DrawRoad = function(wayId, direction) { var mode = { button: 'road' }; mode.enter = function() { - mode.map.dblclickEnable(false) + var map = mode.map, + history = mode.history, + controller = mode.controller, + way = history.graph().entity(wayId), + node = iD.Node({loc: map.mouseCoordinates(), tags: {elastic: true}}), + index = (direction === 'forward') ? undefined : -1, + headId = (direction === 'forward') ? _.last(way.nodes) : _.first(way.nodes), + tailId = (direction === 'forward') ? _.first(way.nodes) : _.last(way.nodes); + + map.dblclickEnable(false) .dragEnable(false) - .fastEnable(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.'); - mode.map.hint('Click to add more points to the road. ' + - 'Click on other roads to connect to them, and double-click to ' + - 'end the road.'); + history.perform( + iD.actions.AddNode(node), + iD.actions.AddWayNode(wayId, node.id, index)); - var index = (direction === 'forward') ? undefined : -1, - node = iD.Node({loc: mode.map.mouseCoordinates() }), - way = mode.history.graph().entity(way_id), - firstNode = way.nodes[0], - lastNode = _.last(way.nodes); - - 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), + iD.actions.ChangeEntityTags(wayId, _.omit(way.tags, 'elastic'))); + + 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)); + + 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)); + + 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)); + 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), + iD.actions.ChangeEntityTags(wayId, _.omit(way.tags, 'elastic'))); + + 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)); }); }; diff --git a/js/id/modes/select.js b/js/id/modes/select.js index d4ad71c74..24c58cbc4 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -26,8 +26,7 @@ iD.modes.Select = function (entity) { 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)); + mode.history.replace(iD.actions.Move(node.id, end)); }); }) .on('dragend', function () { @@ -39,10 +38,10 @@ 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)); break; case 'node': - mode.history.perform(iD.actions.DeleteNode(entity)); + mode.history.perform(iD.actions.DeleteNode(entity.id)); } mode.controller.exit(); @@ -59,9 +58,9 @@ 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)); }).on('changeWayDirection', function(d) { - mode.history.perform(iD.actions.ReverseWay(d)); + mode.history.perform(iD.actions.ReverseWay(d.id)); }).on('remove', function() { remove(); }).on('close', function() { diff --git a/test/spec/actions/add_way_node.js b/test/spec/actions/add_way_node.js index e1df86158..aaed97ecc 100644 --- a/test/spec/actions/add_way_node.js +++ b/test/spec/actions/add_way_node.js @@ -2,14 +2,14 @@ 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 () { 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, 1)(iD.Graph([way, node])); expect(graph.entity(way.id).nodes).to.eql(["n1", "n2", "n3"]); }); }); diff --git a/test/spec/actions/delete_node.js b/test/spec/actions/delete_node.js index 82388b96c..a296a75b5 100644 --- a/test/spec/actions/delete_node.js +++ b/test/spec/actions/delete_node.js @@ -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,7 +9,7 @@ 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); }); @@ -17,7 +17,7 @@ describe("iD.actions.DeleteNode", function () { it("removes the node from parent relations", function () { var node = iD.Node(), relation = iD.Relation({members: [node.id]}), - action = iD.actions.DeleteNode(node), + action = iD.actions.DeleteNode(node.id), graph = action(iD.Graph([node, relation])); expect(graph.entity(relation.id).members).not.to.contain(node.id); }); diff --git a/test/spec/actions/delete_way.js b/test/spec/actions/delete_way.js index 0323b75ee..cb67e9081 100644 --- a/test/spec/actions/delete_way.js +++ b/test/spec/actions/delete_way.js @@ -1,7 +1,7 @@ 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; }); @@ -9,7 +9,7 @@ describe("iD.actions.DeleteWay", function () { it("removes a way from parent relations", function () { var way = iD.Way(), relation = iD.Relation({members: [way.id]}), - action = iD.actions.DeleteWay(way), + action = iD.actions.DeleteWay(way.id), graph = action(iD.Graph([way, relation])); expect(graph.entity(relation.id).members).not.to.contain(way.id); }); @@ -17,7 +17,7 @@ describe("iD.actions.DeleteWay", function () { 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; }); diff --git a/test/spec/actions/remove_way_node.js b/test/spec/actions/remove_way_node.js index 56a302ec0..6e5751b76 100644 --- a/test/spec/actions/remove_way_node.js +++ b/test/spec/actions/remove_way_node.js @@ -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([]); }); }); From caf382c1dc292416c26616ad605d61fed0cb30e9 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 6 Dec 2012 18:48:13 -0500 Subject: [PATCH 09/40] Remove elastic vestiges --- css/map.css | 4 ---- js/id/modes/draw_area.js | 6 ++---- js/id/modes/draw_road.js | 8 +++----- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/css/map.css b/css/map.css index 1f31beca4..0e3465cc5 100644 --- a/css/map.css +++ b/css/map.css @@ -44,10 +44,6 @@ path.casing { stroke-width: 3; } -.elastic-true { - pointer-events:none; -} - path.casing.hover { stroke:#FF0F0F !important; opacity:0.8; diff --git a/js/id/modes/draw_area.js b/js/id/modes/draw_area.js index 9f5d5a575..2b952a897 100644 --- a/js/id/modes/draw_area.js +++ b/js/id/modes/draw_area.js @@ -31,8 +31,7 @@ iD.modes.DrawArea = function(wayId) { if (datum.id === tailId) { history.replace( iD.actions.DeleteNode(node.id), - iD.actions.AddWayNode(way.id, tailId), - iD.actions.ChangeEntityTags(way.id, _.omit(way.tags, 'elastic'))); + iD.actions.AddWayNode(way.id, tailId)); controller.enter(iD.modes.Select(way)); @@ -51,8 +50,7 @@ iD.modes.DrawArea = function(wayId) { map.keybinding().on('⎋.drawarea', function() { history.replace( - iD.actions.DeleteNode(node.id), - iD.actions.ChangeEntityTags(wayId, _.omit(way.tags, 'elastic'))); + iD.actions.DeleteNode(node.id)); controller.enter(iD.modes.Browse()); }); diff --git a/js/id/modes/draw_road.js b/js/id/modes/draw_road.js index f1e3693a9..46ac3e818 100644 --- a/js/id/modes/draw_road.js +++ b/js/id/modes/draw_road.js @@ -8,7 +8,7 @@ iD.modes.DrawRoad = function(wayId, direction) { history = mode.history, controller = mode.controller, way = history.graph().entity(wayId), - node = iD.Node({loc: map.mouseCoordinates(), tags: {elastic: true}}), + node = iD.Node({loc: map.mouseCoordinates()}), index = (direction === 'forward') ? undefined : -1, headId = (direction === 'forward') ? _.last(way.nodes) : _.first(way.nodes), tailId = (direction === 'forward') ? _.first(way.nodes) : _.last(way.nodes); @@ -35,8 +35,7 @@ iD.modes.DrawRoad = function(wayId, direction) { // connect the way in a loop history.replace( iD.actions.DeleteNode(node.id), - iD.actions.AddWayNode(wayId, tailId, index), - iD.actions.ChangeEntityTags(wayId, _.omit(way.tags, 'elastic'))); + iD.actions.AddWayNode(wayId, tailId, index)); controller.enter(iD.modes.Select(way)); @@ -64,8 +63,7 @@ iD.modes.DrawRoad = function(wayId, direction) { map.keybinding().on('⎋.drawroad', function() { history.replace( - iD.actions.DeleteNode(node.id), - iD.actions.ChangeEntityTags(wayId, _.omit(way.tags, 'elastic'))); + iD.actions.DeleteNode(node.id)); controller.enter(iD.modes.Browse()); }); From 0ad62189211af934914df46a8a2135a4b4b29d31 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 6 Dec 2012 18:51:03 -0500 Subject: [PATCH 10/40] Keep browse button active in Select mode --- js/id/modes/select.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/js/id/modes/select.js b/js/id/modes/select.js index 24c58cbc4..07dc36723 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -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() From 0ce26b91319d1ec1ba8471ad6dd621cde3b527b0 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Thu, 6 Dec 2012 19:03:13 -0500 Subject: [PATCH 11/40] Move support3d to util --- js/id/renderer/map.js | 20 +------------------- js/id/util.js | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index 7fd0780d2..a4366cb1f 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -12,7 +12,6 @@ iD.Map = function() { .scaleExtent([1024, 256 * Math.pow(2, 24)]) .on('zoom', zoomPan), dblclickEnabled = true, - dragEnabled = true, dragging = false, fastEnabled = true, background = iD.Background() @@ -22,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() { @@ -347,12 +335,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 = _; diff --git a/js/id/util.js b/js/id/util.js index ddc6fc070..d5440016a 100644 --- a/js/id/util.js +++ b/js/id/util.js @@ -74,6 +74,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) { From 384ca15040da2893497a0f56f4f6b09cc9597c91 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 6 Dec 2012 18:59:20 -0500 Subject: [PATCH 12/40] Continuing mode clean up --- js/id/modes/add_area.js | 25 +++++++++++++++---------- js/id/modes/add_place.js | 18 +++++++++++------- js/id/modes/add_road.js | 20 +++++++++++--------- 3 files changed, 37 insertions(+), 26 deletions(-) diff --git a/js/id/modes/add_area.js b/js/id/modes/add_area.js index 7a76a98db..d9ecdbcce 100644 --- a/js/id/modes/add_area.js +++ b/js/id/modes/add_area.js @@ -7,31 +7,36 @@ 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() || {}, way = iD.Way({tags: { building: 'yes', area: 'yes' }}); - // connect a way to an existing way if (datum.type === 'node') { - mode.history.perform( + // start from an existing node + history.perform( iD.actions.AddWay(way), iD.actions.AddWayNode(way.id, datum.id)); } else { - var node = iD.Node({loc: mode.map.mouseCoordinates()}); - mode.history.perform( + // 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)); } - 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(); }); }; diff --git a/js/id/modes/add_place.js b/js/id/modes/add_place.js index 780023e76..56e54959d 100644 --- a/js/id/modes/add_place.js +++ b/js/id/modes/add_place.js @@ -6,16 +6,20 @@ 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)); + controller.enter(iD.modes.Select(node)); }); - mode.map.keybinding().on('⎋.addplace', function() { - mode.controller.exit(); + map.keybinding().on('⎋.addplace', function() { + controller.exit(); }); }; diff --git a/js/id/modes/add_road.js b/js/id/modes/add_road.js index a194b3295..879f930c8 100644 --- a/js/id/modes/add_road.js +++ b/js/id/modes/add_road.js @@ -7,27 +7,29 @@ iD.modes.AddRoad = function() { }; mode.enter = function() { - var map = mode.map; + var map = mode.map, + history = mode.history, + controller = mode.controller; map.dblclickEnable(false) .hint('Click on the map to start drawing an road, path, or route.'); map.surface.on('click.addroad', function() { var datum = d3.select(d3.event.target).datum() || {}, - direction = 'forward', - way = iD.Way({ tags: { highway: 'residential' } }); + way = iD.Way({ tags: { highway: 'residential' } }), + direction = 'forward'; if (datum.type === 'node') { // continue an existing way var id = datum.id; - var parents = mode.history.graph().parentWays(id); + 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 { - mode.history.perform( + history.perform( iD.actions.AddWay(way), iD.actions.AddWayNode(way.id, datum.id)); } @@ -36,7 +38,7 @@ iD.modes.AddRoad = function() { var node = iD.Node({loc: map.mouseCoordinates()}), index = iD.util.geo.chooseIndex(datum, d3.mouse(map.surface.node()), map); - mode.history.perform( + history.perform( iD.actions.AddWay(way), iD.actions.AddWayNode(datum.id, node, index), iD.actions.AddWayNode(way.id, node.id)); @@ -44,17 +46,17 @@ iD.modes.AddRoad = function() { // begin a new way var node = iD.Node({loc: map.mouseCoordinates()}); - mode.history.perform( + history.perform( iD.actions.AddWay(way), iD.actions.AddNode(node), iD.actions.AddWayNode(way.id, node.id)); } - mode.controller.enter(iD.modes.DrawRoad(way.id, direction)); + controller.enter(iD.modes.DrawRoad(way.id, direction)); }); map.keybinding().on('⎋.addroad', function() { - mode.controller.exit(); + controller.exit(); }); }; From 67ffd2e71bdfc9f808220e4e971945dbb6ac36bb Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 6 Dec 2012 19:00:58 -0500 Subject: [PATCH 13/40] Revert "Copy entity in Graph#fetch" This reverts commit 12d02e0a6bf79f7b6bf2dec5011521f7c04d6cd2. --- js/id/graph/graph.js | 2 +- test/spec/graph/graph.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/js/id/graph/graph.js b/js/id/graph/graph.js index a830214d4..fe878de2c 100644 --- a/js/id/graph/graph.js +++ b/js/id/graph/graph.js @@ -70,7 +70,7 @@ 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.nodes || !entity.nodes.length) return entity; for (var i = 0, l = entity.nodes.length; i < l; i++) { nodes[i] = this.fetch(entity.nodes[i]); } diff --git a/test/spec/graph/graph.js b/test/spec/graph/graph.js index 16cbf6995..b0f71cd91 100644 --- a/test/spec/graph/graph.js +++ b/test/spec/graph/graph.js @@ -83,7 +83,7 @@ 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]); }); }); From 8854dac6235b8249273c3bb028dd6c20262bb70a Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 6 Dec 2012 19:05:01 -0500 Subject: [PATCH 14/40] More action specs; fix ReverseWay --- js/id/actions/reverse_way.js | 2 +- test/index.html | 8 ++++++++ test/index_packaged.html | 8 ++++++++ test/spec/actions/add_node.js | 7 +++++++ test/spec/actions/add_way.js | 7 +++++++ test/spec/actions/change_entity_tags.js | 8 ++++++++ test/spec/actions/move.js | 8 ++++++++ test/spec/actions/noop.js | 7 +++++++ test/spec/actions/remove_relation_member.js | 8 ++++++++ test/spec/actions/reverse_way.js | 9 +++++++++ 10 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 test/spec/actions/add_node.js create mode 100644 test/spec/actions/add_way.js create mode 100644 test/spec/actions/change_entity_tags.js create mode 100644 test/spec/actions/move.js create mode 100644 test/spec/actions/noop.js create mode 100644 test/spec/actions/remove_relation_member.js create mode 100644 test/spec/actions/reverse_way.js diff --git a/js/id/actions/reverse_way.js b/js/id/actions/reverse_way.js index b170bf883..5b93f5578 100644 --- a/js/id/actions/reverse_way.js +++ b/js/id/actions/reverse_way.js @@ -2,7 +2,7 @@ iD.actions.ReverseWay = function(wayId) { return function(graph) { var way = graph.entity(wayId), - nodes = way.nodes.slice(); + nodes = way.nodes.slice().reverse(); return graph.replace(way.update({nodes: nodes}), 'changed way direction'); }; }; diff --git a/test/index.html b/test/index.html index dbfd7cba6..e904c9276 100644 --- a/test/index.html +++ b/test/index.html @@ -83,10 +83,18 @@ + + + + + + + + diff --git a/test/index_packaged.html b/test/index_packaged.html index f1e70b069..8271e3886 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -25,10 +25,18 @@ + + + + + + + + diff --git a/test/spec/actions/add_node.js b/test/spec/actions/add_node.js new file mode 100644 index 000000000..ae066edc5 --- /dev/null +++ b/test/spec/actions/add_node.js @@ -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); + }); +}); diff --git a/test/spec/actions/add_way.js b/test/spec/actions/add_way.js new file mode 100644 index 000000000..19948c5b2 --- /dev/null +++ b/test/spec/actions/add_way.js @@ -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); + }); +}); diff --git a/test/spec/actions/change_entity_tags.js b/test/spec/actions/change_entity_tags.js new file mode 100644 index 000000000..bfa9830c6 --- /dev/null +++ b/test/spec/actions/change_entity_tags.js @@ -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); + }); +}); diff --git a/test/spec/actions/move.js b/test/spec/actions/move.js new file mode 100644 index 000000000..5e4163af7 --- /dev/null +++ b/test/spec/actions/move.js @@ -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); + }); +}); diff --git a/test/spec/actions/noop.js b/test/spec/actions/noop.js new file mode 100644 index 000000000..677c3a2e0 --- /dev/null +++ b/test/spec/actions/noop.js @@ -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); + }); +}); diff --git a/test/spec/actions/remove_relation_member.js b/test/spec/actions/remove_relation_member.js new file mode 100644 index 000000000..2c7c329ab --- /dev/null +++ b/test/spec/actions/remove_relation_member.js @@ -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: [node.id]}), + graph = iD.actions.RemoveRelationMember(relation.id, node.id)(iD.Graph([node, relation])); + expect(graph.entity(relation.id).members).to.eql([]); + }); +}); diff --git a/test/spec/actions/reverse_way.js b/test/spec/actions/reverse_way.js new file mode 100644 index 000000000..0df898ae7 --- /dev/null +++ b/test/spec/actions/reverse_way.js @@ -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]); + }); +}); From ccd0f0805f416099ee35a8c1e6a85a8f4751b3cb Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 6 Dec 2012 19:18:39 -0500 Subject: [PATCH 15/40] dragEnable is gone --- js/id/modes/draw_road.js | 2 -- js/id/modes/select.js | 4 +--- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/js/id/modes/draw_road.js b/js/id/modes/draw_road.js index 46ac3e818..769c4412c 100644 --- a/js/id/modes/draw_road.js +++ b/js/id/modes/draw_road.js @@ -14,7 +14,6 @@ iD.modes.DrawRoad = function(wayId, direction) { tailId = (direction === 'forward') ? _.first(way.nodes) : _.last(way.nodes); map.dblclickEnable(false) - .dragEnable(false) .fastEnable(false) .hint('Click to add more points to the road. ' + 'Click on other roads to connect to them, and double-click to ' + @@ -90,7 +89,6 @@ iD.modes.DrawRoad = function(wayId, direction) { .on('⌫.drawroad', null); window.setTimeout(function() { mode.map.dblclickEnable(true); - mode.map.dragEnable(true); }, 1000); }; diff --git a/js/id/modes/select.js b/js/id/modes/select.js index 07dc36723..d34d144bc 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -12,8 +12,6 @@ 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) { @@ -31,7 +29,7 @@ iD.modes.Select = function (entity) { }); }) .on('dragend', function () { - if (!mode.map.dragEnable() || !dragging) return; + if (!dragging) return; dragging = undefined; mode.map.redraw(); }); From 16f102b410b39bd104c391c87e04aa38344edefd Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Thu, 6 Dec 2012 19:26:52 -0500 Subject: [PATCH 16/40] Fix accuracy handy disappearance --- js/id/renderer/map.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index a4366cb1f..c0ce189e8 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -116,9 +116,9 @@ iD.Map = function() { handles.push({ loc: iD.util.geo.interp(way.nodes[i].loc, way.nodes[i + 1].loc, 0.5), way: way.id, + id: way.id, index: i + 1, - accuracy: true, - tags: { name: 'Improve way accuracy' } + accuracy: true }); } } @@ -149,8 +149,9 @@ iD.Map = function() { .sort(olderOnTop); } - function drawAccuracyHandles(waynodes) { + function drawAccuracyHandles(waynodes, filter) { var handles = g.hit.selectAll('circle.accuracy-handle') + .filter(filter) .data(waynodes, key); handles.exit().remove(); handles.enter().append('circle') From 9b71f48521502b17d3d9e9fca80410089c87ae2f Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Thu, 6 Dec 2012 19:41:43 -0500 Subject: [PATCH 17/40] Fix accuracy nodes --- js/id/modes/browse.js | 9 ++++++--- js/id/renderer/map.js | 1 - 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/js/id/modes/browse.js b/js/id/modes/browse.js index 034330359..75be48ee3 100644 --- a/js/id/modes/browse.js +++ b/js/id/modes/browse.js @@ -6,7 +6,7 @@ iD.modes.Browse = function() { description: 'Pan and zoom the map' }; - var dragging; + var dragging, incarnated; var dragbehavior = d3.behavior.drag() .origin(function(entity) { @@ -17,21 +17,24 @@ iD.modes.Browse = function() { d3.event.sourceEvent.stopPropagation(); if (!dragging) { if (entity.accuracy) { - var node = iD.Node(entity); + var node = iD.Node({ loc: entity.loc }); mode.history.perform( iD.actions.AddNode(node), iD.actions.AddWayNode(entity.way, node.id, entity.index)); + incarnated = node.id; } dragging = iD.util.trueObj([entity.id].concat( _.pluck(mode.history.graph().parentWays(entity.id), 'id'))); mode.history.perform(iD.actions.Noop()); } + if (incarnated) entity = mode.history.graph().entity(incarnated); var to = mode.map.projection.invert([d3.event.x, d3.event.y]); mode.history.replace(iD.actions.Move(entity.id, to)); }) .on('dragend', function () { if (!dragging) return; dragging = undefined; + incarnated = undefined; }); mode.enter = function() { @@ -39,7 +42,7 @@ iD.modes.Browse = function() { .call(dragbehavior) .call(d3.latedrag() .filter(function(d) { - return d.type === 'node'; + return (d.type === 'node' || d.accuracy); })); mode.map.surface.on('click.browse', function () { var datum = d3.select(d3.event.target).datum(); diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index c0ce189e8..a15ccf3bf 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -116,7 +116,6 @@ iD.Map = function() { handles.push({ loc: iD.util.geo.interp(way.nodes[i].loc, way.nodes[i + 1].loc, 0.5), way: way.id, - id: way.id, index: i + 1, accuracy: true }); From 7cb6cfbe040a96b4f684e5e8383d21ec8559164a Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Fri, 7 Dec 2012 09:57:30 -0500 Subject: [PATCH 18/40] Refactor drag features into sub-control and allow dragging in selection mode --- index.html | 1 + js/id/modes/browse.js | 38 +---------------------------------- js/id/modes/drag_features.js | 39 ++++++++++++++++++++++++++++++++++++ js/id/modes/select.js | 4 ++++ js/id/renderer/map.js | 14 ++++++------- 5 files changed, 52 insertions(+), 44 deletions(-) create mode 100644 js/id/modes/drag_features.js diff --git a/index.html b/index.html index 33706d3dd..0e70233cc 100644 --- a/index.html +++ b/index.html @@ -57,6 +57,7 @@ + diff --git a/js/id/modes/browse.js b/js/id/modes/browse.js index 75be48ee3..a94511d7a 100644 --- a/js/id/modes/browse.js +++ b/js/id/modes/browse.js @@ -6,44 +6,8 @@ iD.modes.Browse = function() { description: 'Pan and zoom the map' }; - var dragging, incarnated; - - 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(); - if (!dragging) { - if (entity.accuracy) { - var node = iD.Node({ loc: entity.loc }); - mode.history.perform( - iD.actions.AddNode(node), - iD.actions.AddWayNode(entity.way, node.id, entity.index)); - incarnated = node.id; - } - dragging = iD.util.trueObj([entity.id].concat( - _.pluck(mode.history.graph().parentWays(entity.id), 'id'))); - mode.history.perform(iD.actions.Noop()); - } - if (incarnated) entity = mode.history.graph().entity(incarnated); - var to = mode.map.projection.invert([d3.event.x, d3.event.y]); - mode.history.replace(iD.actions.Move(entity.id, to)); - }) - .on('dragend', function () { - if (!dragging) return; - dragging = undefined; - incarnated = undefined; - }); - mode.enter = function() { - mode.map.surface - .call(dragbehavior) - .call(d3.latedrag() - .filter(function(d) { - return (d.type === 'node' || d.accuracy); - })); + iD.modes._dragFeatures(mode); mode.map.surface.on('click.browse', function () { var datum = d3.select(d3.event.target).datum(); if (datum instanceof iD.Entity) { diff --git a/js/id/modes/drag_features.js b/js/id/modes/drag_features.js new file mode 100644 index 000000000..82fb7e59a --- /dev/null +++ b/js/id/modes/drag_features.js @@ -0,0 +1,39 @@ +iD.modes._dragFeatures = function(mode) { + var dragging, incarnated; + + 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(); + if (!dragging) { + if (entity.accuracy) { + var node = iD.Node({ loc: entity.loc }); + mode.history.perform( + iD.actions.AddNode(node), + iD.actions.AddWayNode(entity.way, node.id, entity.index)); + incarnated = node.id; + } + dragging = iD.util.trueObj([entity.id].concat( + _.pluck(mode.history.graph().parentWays(entity.id), 'id'))); + mode.history.perform(iD.actions.Noop()); + } + if (incarnated) entity = mode.history.graph().entity(incarnated); + var to = mode.map.projection.invert([d3.event.x, d3.event.y]); + mode.history.replace(iD.actions.Move(entity.id, to)); + }) + .on('dragend', function () { + if (!dragging) return; + dragging = undefined; + incarnated = undefined; + }); + + mode.map.surface + .call(dragbehavior) + .call(d3.latedrag() + .filter(function(d) { + return (d.type === 'node' || d.accuracy); + })); +}; diff --git a/js/id/modes/select.js b/js/id/modes/select.js index d34d144bc..10e086eb4 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -47,6 +47,8 @@ iD.modes.Select = function (entity) { } mode.enter = function () { + iD.modes._dragFeatures(mode); + target = mode.map.surface.selectAll("*") .filter(function (d) { return d === entity; }); @@ -88,6 +90,8 @@ iD.modes.Select = function (entity) { }; mode.exit = function () { + mode.map.surface.on('mousedown.latedrag', null); + d3.select('.inspector-wrap') .style('display', 'none'); diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index a15ccf3bf..22c113642 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -286,7 +286,7 @@ iD.Map = function() { 'translate(' + ~~(a[0] - b[0]) + 'px,' + ~~(a[1] - b[1]) + 'px)'); } } else { - redraw(); + redraw({ moved: true }); translateStart = null; } } @@ -299,14 +299,14 @@ iD.Map = function() { redraw(); } - function redraw() { - if (!dragging) { + function redraw(e) { + if (e && e.moved) { dispatch.move(map); tilegroup.call(background); } if (map.zoom() > 16) { connection.loadTiles(projection); - drawVector(dragging); + drawVector(); } else { hideVector(); } @@ -356,7 +356,7 @@ iD.Map = function() { t[1] += center[1] - l[1]; projection.translate(t); zoom.translate(projection.translate()); - return redraw(); + return redraw({ moved: true }); }; map.size = function(_) { @@ -367,7 +367,7 @@ iD.Map = function() { .selectAll('#clip-rect') .size(dimensions); background.size(dimensions); - return redraw(); + return redraw({ moved: true }); }; map.zoomIn = function() { return map.zoom(Math.ceil(map.zoom() + 1)); }; @@ -384,7 +384,7 @@ iD.Map = function() { t[0] - ll[0] + c[0], t[1] - ll[1] + c[1]]); zoom.translate(projection.translate()); - return redraw(); + return redraw({ moved: true }); } }; From 6626c8740190e5afd96fb37a78c4250cc62a7f23 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Fri, 7 Dec 2012 10:01:07 -0500 Subject: [PATCH 19/40] Fix layer switching --- js/id/ui/layerswitcher.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/id/ui/layerswitcher.js b/js/id/ui/layerswitcher.js index 7484fb0c7..f95b926f2 100644 --- a/js/id/ui/layerswitcher.js +++ b/js/id/ui/layerswitcher.js @@ -72,7 +72,7 @@ iD.layerswitcher = function(map) { .on('click', function(d) { d3.event.preventDefault(); map.background.source(d.source); - map.redraw(); + map.redraw({ moved: true }); selectLayer(d); }); selectLayer(map.background.source()); From a9e68ce3eecd2deb360b2392ba6b0b22288f4912 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Fri, 7 Dec 2012 10:02:55 -0500 Subject: [PATCH 20/40] Undo moved technique --- js/id/renderer/map.js | 16 +++++++--------- js/id/ui/layerswitcher.js | 2 +- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index 22c113642..d879300c2 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -286,7 +286,7 @@ iD.Map = function() { 'translate(' + ~~(a[0] - b[0]) + 'px,' + ~~(a[1] - b[1]) + 'px)'); } } else { - redraw({ moved: true }); + redraw(); translateStart = null; } } @@ -299,11 +299,9 @@ iD.Map = function() { redraw(); } - function redraw(e) { - if (e && e.moved) { - dispatch.move(map); - tilegroup.call(background); - } + function redraw() { + dispatch.move(map); + tilegroup.call(background); if (map.zoom() > 16) { connection.loadTiles(projection); drawVector(); @@ -356,7 +354,7 @@ iD.Map = function() { t[1] += center[1] - l[1]; projection.translate(t); zoom.translate(projection.translate()); - return redraw({ moved: true }); + return redraw(); }; map.size = function(_) { @@ -367,7 +365,7 @@ iD.Map = function() { .selectAll('#clip-rect') .size(dimensions); background.size(dimensions); - return redraw({ moved: true }); + return redraw(); }; map.zoomIn = function() { return map.zoom(Math.ceil(map.zoom() + 1)); }; @@ -384,7 +382,7 @@ iD.Map = function() { t[0] - ll[0] + c[0], t[1] - ll[1] + c[1]]); zoom.translate(projection.translate()); - return redraw({ moved: true }); + return redraw(); } }; diff --git a/js/id/ui/layerswitcher.js b/js/id/ui/layerswitcher.js index f95b926f2..7484fb0c7 100644 --- a/js/id/ui/layerswitcher.js +++ b/js/id/ui/layerswitcher.js @@ -72,7 +72,7 @@ iD.layerswitcher = function(map) { .on('click', function(d) { d3.event.preventDefault(); map.background.source(d.source); - map.redraw({ moved: true }); + map.redraw(); selectLayer(d); }); selectLayer(map.background.source()); From 7c82710dbf4604c31a4f95417be0e485cabc40e7 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Fri, 7 Dec 2012 10:11:42 -0500 Subject: [PATCH 21/40] Fix deleting areas --- js/id/actions/delete_way.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/js/id/actions/delete_way.js b/js/id/actions/delete_way.js index 86fe3a3bc..a16309e56 100644 --- a/js/id/actions/delete_way.js +++ b/js/id/actions/delete_way.js @@ -11,6 +11,10 @@ iD.actions.DeleteWay = function(wayId) { way.nodes.forEach(function (nodeId) { var node = graph.entity(nodeId); + // Circular ways include nodes more than once, so they + // can be deleted on earlier iterations of this loop. + if (!node) return; + graph = iD.actions.RemoveWayNode(wayId, nodeId)(graph); if (!graph.parentWays(nodeId).length && !graph.parentRelations(nodeId).length) { From 23856cc1c53a632c008f9c0793a0f546fa7578c5 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Fri, 7 Dec 2012 10:25:09 -0500 Subject: [PATCH 22/40] Reclose ways if you delete the repeated node. Fixes #221 --- js/id/actions/remove_way_node.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/js/id/actions/remove_way_node.js b/js/id/actions/remove_way_node.js index 5988e0f0b..073f7f42d 100644 --- a/js/id/actions/remove_way_node.js +++ b/js/id/actions/remove_way_node.js @@ -1,7 +1,17 @@ iD.actions.RemoveWayNode = function(wayId, nodeId) { return function(graph) { - var way = graph.entity(wayId), + 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}), 'removed from a road'); }; }; From 7eec00774090a8950e9126e77e31f250aa15d0d1 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 7 Dec 2012 07:42:03 -0500 Subject: [PATCH 23/40] Get internal ID logic in one place --- js/id/connection.js | 3 +-- js/id/graph/entity.js | 26 ++++++++++++++++++++++---- js/id/renderer/map.js | 2 +- js/id/ui/inspector.js | 2 +- js/id/util.js | 8 -------- test/spec/graph/entity.js | 26 ++++++++++++++++++++++---- test/spec/util.js | 10 ---------- 7 files changed, 47 insertions(+), 30 deletions(-) diff --git a/js/id/connection.js b/js/id/connection.js index e06260810..d24aeffc1 100644 --- a/js/id/connection.js +++ b/js/id/connection.js @@ -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); } diff --git a/js/id/graph/entity.js b/js/id/graph/entity.js index b3f698704..5c89e4890 100644 --- a/js/id/graph/entity.js +++ b/js/id/graph/entity.js @@ -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) { diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index d879300c2..0bce46944 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -129,7 +129,7 @@ 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') diff --git a/js/id/ui/inspector.js b/js/id/ui/inspector.js index 60a84819a..6ec4e230d 100644 --- a/js/id/ui/inspector.js +++ b/js/id/ui/inspector.js @@ -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') diff --git a/js/id/util.js b/js/id/util.js index d5440016a..bf5a2ef2d 100644 --- a/js/id/util.js +++ b/js/id/util.js @@ -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; diff --git a/test/spec/graph/entity.js b/test/spec/graph/entity.js index 7b15a4788..ba0d6f05d 100644 --- a/test/spec/graph/entity.js +++ b/test/spec/graph/entity.js @@ -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; diff --git a/test/spec/util.js b/test/spec/util.js index 1dccda623..02d8ca2b1 100644 --- a/test/spec/util.js +++ b/test/spec/util.js @@ -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({}); From 3e45afbc5e6b8413f20b47ec96b81cc6b42e2857 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 7 Dec 2012 10:32:44 -0500 Subject: [PATCH 24/40] Move history annotation to History#perform/replace Actions are too low-level for annotations. --- js/id/actions/add_node.js | 2 +- js/id/actions/add_way.js | 2 +- js/id/actions/add_way_node.js | 2 +- js/id/actions/change_entity_tags.js | 2 +- js/id/actions/delete_node.js | 2 +- js/id/actions/delete_way.js | 2 +- js/id/actions/move.js | 2 +- js/id/actions/remove_relation_member.js | 2 +- js/id/actions/remove_way_node.js | 2 +- js/id/actions/reverse_way.js | 2 +- js/id/graph/graph.js | 16 +++---- js/id/graph/history.js | 53 +++++++++++---------- js/id/modes/add_area.js | 7 ++- js/id/modes/add_place.js | 6 ++- js/id/modes/add_road.js | 11 +++-- js/id/modes/drag_features.js | 31 +++++++------ js/id/modes/draw_area.js | 10 +++- js/id/modes/draw_road.js | 13 ++++-- js/id/modes/select.js | 22 ++++++--- test/spec/graph/graph.js | 5 -- test/spec/graph/history.js | 62 +++++++++++-------------- 21 files changed, 142 insertions(+), 114 deletions(-) diff --git a/js/id/actions/add_node.js b/js/id/actions/add_node.js index f8916e50b..669a81d49 100644 --- a/js/id/actions/add_node.js +++ b/js/id/actions/add_node.js @@ -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); }; }; diff --git a/js/id/actions/add_way.js b/js/id/actions/add_way.js index 4b53a8ea3..2be062d3a 100644 --- a/js/id/actions/add_way.js +++ b/js/id/actions/add_way.js @@ -1,5 +1,5 @@ iD.actions.AddWay = function(way) { return function(graph) { - return graph.replace(way, 'started a road'); + return graph.replace(way); }; }; diff --git a/js/id/actions/add_way_node.js b/js/id/actions/add_way_node.js index f3c4521aa..69361b8dc 100644 --- a/js/id/actions/add_way_node.js +++ b/js/id/actions/add_way_node.js @@ -5,6 +5,6 @@ iD.actions.AddWayNode = function(wayId, nodeId, index) { node = graph.entity(nodeId), nodes = way.nodes.slice(); nodes.splice(index || nodes.length, 0, nodeId); - return graph.replace(way.update({nodes: nodes}), 'added to a road'); + return graph.replace(way.update({nodes: nodes})); }; }; diff --git a/js/id/actions/change_entity_tags.js b/js/id/actions/change_entity_tags.js index 25cf5911c..96901c2eb 100644 --- a/js/id/actions/change_entity_tags.js +++ b/js/id/actions/change_entity_tags.js @@ -1,6 +1,6 @@ iD.actions.ChangeEntityTags = function(entityId, tags) { return function(graph) { var entity = graph.entity(entityId); - return graph.replace(entity.update({tags: tags}), 'changed tags'); + return graph.replace(entity.update({tags: tags})); }; }; diff --git a/js/id/actions/delete_node.js b/js/id/actions/delete_node.js index fb858d75f..e8b6d34da 100644 --- a/js/id/actions/delete_node.js +++ b/js/id/actions/delete_node.js @@ -13,6 +13,6 @@ iD.actions.DeleteNode = function(nodeId) { graph = iD.actions.RemoveRelationMember(parent.id, nodeId)(graph); }); - return graph.remove(node, 'removed a node'); + return graph.remove(node); }; }; diff --git a/js/id/actions/delete_way.js b/js/id/actions/delete_way.js index a16309e56..120ac17f5 100644 --- a/js/id/actions/delete_way.js +++ b/js/id/actions/delete_way.js @@ -26,6 +26,6 @@ iD.actions.DeleteWay = function(wayId) { } }); - return graph.remove(way, 'removed a way'); + return graph.remove(way); }; }; diff --git a/js/id/actions/move.js b/js/id/actions/move.js index 42c82db56..21870c8d4 100644 --- a/js/id/actions/move.js +++ b/js/id/actions/move.js @@ -3,6 +3,6 @@ iD.actions.Move = function(entityId, loc) { return function(graph) { var entity = graph.entity(entityId); - return graph.replace(entity.update({loc: loc}), 'moved an element'); + return graph.replace(entity.update({loc: loc})); }; }; diff --git a/js/id/actions/remove_relation_member.js b/js/id/actions/remove_relation_member.js index f4061d177..f89bbe803 100644 --- a/js/id/actions/remove_relation_member.js +++ b/js/id/actions/remove_relation_member.js @@ -2,6 +2,6 @@ iD.actions.RemoveRelationMember = function(relationId, memberId) { return function(graph) { var relation = graph.entity(relationId), members = _.without(relation.members, memberId); - return graph.replace(relation.update({members: members}), 'removed from a relation'); + return graph.replace(relation.update({members: members})); }; }; diff --git a/js/id/actions/remove_way_node.js b/js/id/actions/remove_way_node.js index 073f7f42d..8c589a20c 100644 --- a/js/id/actions/remove_way_node.js +++ b/js/id/actions/remove_way_node.js @@ -12,6 +12,6 @@ iD.actions.RemoveWayNode = function(wayId, nodeId) { } else { nodes = _.without(way.nodes, nodeId); } - return graph.replace(way.update({nodes: nodes}), 'removed from a road'); + return graph.replace(way.update({nodes: nodes})); }; }; diff --git a/js/id/actions/reverse_way.js b/js/id/actions/reverse_way.js index 5b93f5578..facc75edf 100644 --- a/js/id/actions/reverse_way.js +++ b/js/id/actions/reverse_way.js @@ -3,6 +3,6 @@ iD.actions.ReverseWay = function(wayId) { return function(graph) { var way = graph.entity(wayId), nodes = way.nodes.slice().reverse(); - return graph.replace(way.update({nodes: nodes}), 'changed way direction'); + return graph.replace(way.update({nodes: nodes})); }; }; diff --git a/js/id/graph/graph.js b/js/id/graph/graph.js index fe878de2c..0626a492f 100644 --- a/js/id/graph/graph.js +++ b/js/id/graph/graph.js @@ -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); @@ -40,19 +38,19 @@ iD.Graph.prototype = { 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); + return iD.Graph(entities); }, // get all objects that intersect an extent. diff --git a/js/id/graph/history.js b/js/id/graph/history.js index df444840b..eec93f3e8 100644 --- a/js/id/graph/history.js +++ b/js/id/graph/history.js @@ -2,6 +2,23 @@ iD.History = function() { var stack, index, dispatch = d3.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 maybeChange() { if (stack[index].annotation) { dispatch.change(); @@ -10,38 +27,26 @@ iD.History = function() { 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 () { stack = stack.slice(0, index + 1); - - var graph = this.graph(); - for (var i = 0; i < arguments.length; i++) { - graph = arguments[i](graph); - } - - stack.push(graph); + stack.push(perform(arguments)); index++; - maybeChange(); + dispatch.change(); }, replace: function () { // assert(index == stack.length - 1) - - var graph = this.graph(); - for (var i = 0; i < arguments.length; i++) { - graph = arguments[i](graph); - } - - stack[index] = graph; - maybeChange(); + stack[index] = perform(arguments); + dispatch.change(); }, undo: function () { @@ -78,19 +83,19 @@ iD.History = function() { // generate reports of changes for changesets to use modify: function () { - return stack[index].modifications(); + return stack[index].graph.modifications(); }, create: function () { - return stack[index].creations(); + return stack[index].graph.creations(); }, 'delete': function () { return _.difference( - _.pluck(stack[0].entities, 'id'), - _.pluck(stack[index].entities, 'id') + _.pluck(stack[0].graph.entities, 'id'), + _.pluck(stack[index].graph.entities, 'id') ).map(function (id) { - return stack[0].fetch(id); + return stack[0].graph.fetch(id); }); }, @@ -103,7 +108,7 @@ iD.History = function() { }, reset: function () { - stack = [iD.Graph()]; + stack = [{graph: iD.Graph()}]; index = 0; dispatch.change(); } diff --git a/js/id/modes/add_area.js b/js/id/modes/add_area.js index d9ecdbcce..cdee57082 100644 --- a/js/id/modes/add_area.js +++ b/js/id/modes/add_area.js @@ -22,14 +22,17 @@ iD.modes.AddArea = function() { // 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 { // 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'); } controller.enter(iD.modes.DrawArea(way.id)); diff --git a/js/id/modes/add_place.js b/js/id/modes/add_place.js index 56e54959d..82b423368 100644 --- a/js/id/modes/add_place.js +++ b/js/id/modes/add_place.js @@ -14,7 +14,11 @@ iD.modes.AddPlace = function() { map.surface.on('click.addplace', function() { var node = iD.Node({loc: map.mouseCoordinates(), _poi: true}); - history.perform(iD.actions.AddNode(node)); + + history.perform( + iD.actions.AddNode(node), + 'added a place'); + controller.enter(iD.modes.Select(node)); }); diff --git a/js/id/modes/add_road.js b/js/id/modes/add_road.js index 879f930c8..5f05c3f77 100644 --- a/js/id/modes/add_road.js +++ b/js/id/modes/add_road.js @@ -31,8 +31,10 @@ iD.modes.AddRoad = function() { } else { history.perform( iD.actions.AddWay(way), - iD.actions.AddWayNode(way.id, datum.id)); + iD.actions.AddWayNode(way.id, datum.id), + 'started a road'); } + } else if (datum.type === 'way') { // begin a new way starting from an existing way var node = iD.Node({loc: map.mouseCoordinates()}), @@ -41,7 +43,9 @@ iD.modes.AddRoad = function() { history.perform( iD.actions.AddWay(way), iD.actions.AddWayNode(datum.id, node, index), - iD.actions.AddWayNode(way.id, node.id)); + iD.actions.AddWayNode(way.id, node.id), + 'started a road'); + } else { // begin a new way var node = iD.Node({loc: map.mouseCoordinates()}); @@ -49,7 +53,8 @@ iD.modes.AddRoad = function() { 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 a road'); } controller.enter(iD.modes.DrawRoad(way.id, direction)); diff --git a/js/id/modes/drag_features.js b/js/id/modes/drag_features.js index 82fb7e59a..7a38883ed 100644 --- a/js/id/modes/drag_features.js +++ b/js/id/modes/drag_features.js @@ -1,5 +1,5 @@ iD.modes._dragFeatures = function(mode) { - var dragging, incarnated; + var dragging; var dragbehavior = d3.behavior.drag() .origin(function(entity) { @@ -8,26 +8,31 @@ iD.modes._dragFeatures = function(mode) { }) .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) { - var node = iD.Node({ loc: entity.loc }); + dragging = iD.Node({loc: loc}); mode.history.perform( - iD.actions.AddNode(node), - iD.actions.AddWayNode(entity.way, node.id, entity.index)); - incarnated = node.id; + 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)); } - dragging = iD.util.trueObj([entity.id].concat( - _.pluck(mode.history.graph().parentWays(entity.id), 'id'))); - mode.history.perform(iD.actions.Noop()); } - if (incarnated) entity = mode.history.graph().entity(incarnated); - var to = mode.map.projection.invert([d3.event.x, d3.event.y]); - mode.history.replace(iD.actions.Move(entity.id, to)); + + mode.history.replace(iD.actions.Move(dragging.id, loc)); }) - .on('dragend', function () { + .on('dragend', function (entity) { if (!dragging) return; dragging = undefined; - incarnated = undefined; + + mode.history.replace( + iD.actions.Noop(), + entity.accuracy ? 'added a node to a way' : 'moved a node'); }); mode.map.surface diff --git a/js/id/modes/draw_area.js b/js/id/modes/draw_area.js index 2b952a897..c52000538 100644 --- a/js/id/modes/draw_area.js +++ b/js/id/modes/draw_area.js @@ -31,7 +31,8 @@ iD.modes.DrawArea = function(wayId) { if (datum.id === tailId) { history.replace( iD.actions.DeleteNode(node.id), - iD.actions.AddWayNode(way.id, tailId)); + iD.actions.AddWayNode(way.id, tailId), + 'added to an area'); controller.enter(iD.modes.Select(way)); @@ -39,11 +40,16 @@ iD.modes.DrawArea = function(wayId) { // connect the way to an existing node history.replace( iD.actions.DeleteNode(node.id), - iD.actions.AddWayNode(way.id, datum.id)); + iD.actions.AddWayNode(way.id, datum.id), + 'added to an area'); controller.enter(iD.modes.DrawArea(wayId)); } else { + history.replace( + iD.actions.Noop(), + 'added to an area'); + controller.enter(iD.modes.DrawArea(wayId)); } }); diff --git a/js/id/modes/draw_road.js b/js/id/modes/draw_road.js index 769c4412c..50f605660 100644 --- a/js/id/modes/draw_road.js +++ b/js/id/modes/draw_road.js @@ -34,7 +34,8 @@ iD.modes.DrawRoad = function(wayId, direction) { // connect the way in a loop history.replace( iD.actions.DeleteNode(node.id), - iD.actions.AddWayNode(wayId, tailId, index)); + iD.actions.AddWayNode(wayId, tailId, index), + 'added to a road'); controller.enter(iD.modes.Select(way)); @@ -42,7 +43,8 @@ iD.modes.DrawRoad = function(wayId, direction) { // connect the way to an existing node history.replace( iD.actions.DeleteNode(node.id), - iD.actions.AddWayNode(wayId, datum.id, index)); + iD.actions.AddWayNode(wayId, datum.id, index), + 'added to a road'); controller.enter(iD.modes.DrawRoad(wayId, direction)); @@ -51,11 +53,16 @@ iD.modes.DrawRoad = function(wayId, direction) { var connectedIndex = iD.modes.chooseIndex(datum, d3.mouse(map.surface.node()), map); history.replace( - iD.actions.AddWayNode(datum.id, node.id, connectedIndex)); + iD.actions.AddWayNode(datum.id, node.id, connectedIndex), + 'added to a road'); controller.enter(iD.modes.DrawRoad(wayId, direction)); } else { + history.replace( + iD.actions.Noop(), + 'added to a road'); + controller.enter(iD.modes.DrawRoad(wayId, direction)); } }); diff --git a/js/id/modes/select.js b/js/id/modes/select.js index 10e086eb4..2f917e77f 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -15,8 +15,7 @@ iD.modes.Select = function (entity) { 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()); } @@ -37,10 +36,14 @@ iD.modes.Select = function (entity) { function remove() { switch (entity.type) { case 'way': - mode.history.perform(iD.actions.DeleteWay(entity.id)); + mode.history.perform( + iD.actions.DeleteWay(entity.id), + 'deleted a way'); break; case 'node': - mode.history.perform(iD.actions.DeleteNode(entity.id)); + mode.history.perform( + iD.actions.DeleteNode(entity.id), + 'deleted a node'); } mode.controller.exit(); @@ -59,11 +62,18 @@ iD.modes.Select = function (entity) { .call(inspector); inspector.on('changeTags', function(d, tags) { - mode.history.perform(iD.actions.ChangeEntityTags(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.id)); + mode.history.perform( + iD.actions.ReverseWay(d.id), + 'reversed a way'); + }).on('remove', function() { remove(); + }).on('close', function() { mode.controller.exit(); }); diff --git a/test/spec/graph/graph.js b/test/spec/graph/graph.js index b0f71cd91..248056c0d 100644 --- a/test/spec/graph/graph.js +++ b/test/spec/graph/graph.js @@ -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; diff --git a/test/spec/graph/history.js b/test/spec/graph/history.js index 49f4fe397..efabcc32a 100644 --- a/test/spec/graph/history.js +++ b/test/spec/graph/history.js @@ -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,13 +15,14 @@ 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 () { @@ -31,32 +31,27 @@ describe("History", function () { expect(spy).to.have.been.called; }); - it("does not emit a change event when performing a noop", function () { - history.on('change', spy); - history.perform(iD.actions.Noop); - expect(spy).not.to.have.been.called; - }); - it("performs multiple actions", function () { - var action1 = sinon.stub().returns(graph), - action2 = sinon.stub().returns(graph); - history.perform(action1, action2); + 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 () { - history.replace(action); + var graph = iD.Graph(); + history.replace(d3.functor(graph)); expect(history.graph()).to.equal(graph); }); - it("replaces the undo stack", function () { - history.perform(action); - history.replace(action); - history.undo(); - expect(history.undoAnnotation()).to.be.undefined; + 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 () { @@ -65,32 +60,27 @@ describe("History", function () { expect(spy).to.have.been.called; }); - it("does not emit a change event when performing a noop", function () { - history.on('change', spy); - history.replace(iD.actions.Noop); - expect(spy).not.to.have.been.called; - }); - it("performs multiple actions", function () { - var action1 = sinon.stub().returns(graph), - action2 = sinon.stub().returns(graph); - history.replace(action1, action2); + 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 () { @@ -113,8 +103,8 @@ describe("History", function () { 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; From f49728872e33b77244f04fde708f99e9476e82ec Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Fri, 7 Dec 2012 10:42:16 -0500 Subject: [PATCH 25/40] Fix way finishing, start on split way --- index.html | 1 + js/id/actions/split_way.js | 7 +++++++ js/id/modes/draw_road.js | 6 ++++++ js/id/ui/inspector.js | 13 ++++++++++--- 4 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 js/id/actions/split_way.js diff --git a/index.html b/index.html index 0e70233cc..640dfa51a 100644 --- a/index.html +++ b/index.html @@ -55,6 +55,7 @@ + diff --git a/js/id/actions/split_way.js b/js/id/actions/split_way.js new file mode 100644 index 000000000..4b4152a99 --- /dev/null +++ b/js/id/actions/split_way.js @@ -0,0 +1,7 @@ +// https://github.com/systemed/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/SplitWayAction.as +iD.actions.SplitWay = function(nodeId, wayId) { + return function(graph) { + var way = graph.entity(wayId); + return graph.replace(way.update({nodes: nodes}), 'changed way direction'); + }; +}; diff --git a/js/id/modes/draw_road.js b/js/id/modes/draw_road.js index 769c4412c..2a69be178 100644 --- a/js/id/modes/draw_road.js +++ b/js/id/modes/draw_road.js @@ -38,6 +38,12 @@ iD.modes.DrawRoad = function(wayId, direction) { 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( diff --git a/js/id/ui/inspector.js b/js/id/ui/inspector.js index 60a84819a..f24a4b56d 100644 --- a/js/id/ui/inspector.js +++ b/js/id/ui/inspector.js @@ -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)); }); } } From f74b29d93ae066a942dac87a71e5121a3aa70ff6 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Fri, 7 Dec 2012 11:03:15 -0500 Subject: [PATCH 26/40] Rudientary splitting of ways --- js/id/actions/split_way.js | 15 ++++++++++++--- js/id/modes/select.js | 5 +++++ js/id/ui/inspector.js | 2 +- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/js/id/actions/split_way.js b/js/id/actions/split_way.js index 4b4152a99..d9081bb9d 100644 --- a/js/id/actions/split_way.js +++ b/js/id/actions/split_way.js @@ -1,7 +1,16 @@ // https://github.com/systemed/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/SplitWayAction.as -iD.actions.SplitWay = function(nodeId, wayId) { +iD.actions.SplitWay = function(nodeId) { return function(graph) { - var way = graph.entity(wayId); - return graph.replace(way.update({nodes: nodes}), 'changed way direction'); + var parents = graph.parentWays(nodeId); + parents.forEach(function(way) { + var idx = _.indexOf(way.nodes, nodeId); + // Create a 'b' way that contains all of the tags in the second + // half of this way + var b = iD.Way({ tags: _.clone(way.tags), nodes: way.nodes.slice(idx) }); + graph = graph.replace(b); + // 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'); + }); + return graph; }; }; diff --git a/js/id/modes/select.js b/js/id/modes/select.js index 2f917e77f..60fcbadfd 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -71,6 +71,11 @@ iD.modes.Select = function (entity) { 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(); diff --git a/js/id/ui/inspector.js b/js/id/ui/inspector.js index 1a9865277..6c51d0d99 100644 --- a/js/id/ui/inspector.js +++ b/js/id/ui/inspector.js @@ -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) { From 9546a6f7434b4661704702c665284c8bb59d08f2 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Fri, 7 Dec 2012 11:47:48 -0500 Subject: [PATCH 27/40] Limit download threads. Fixes #83 --- index.html | 1 + js/id/connection.js | 18 ++++++---- js/lib/queue.js | 84 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 97 insertions(+), 6 deletions(-) create mode 100644 js/lib/queue.js diff --git a/index.html b/index.html index 640dfa51a..4cf24afff 100644 --- a/index.html +++ b/index.html @@ -23,6 +23,7 @@ + diff --git a/js/id/connection.js b/js/id/connection.js index d24aeffc1..0c10d37d3 100644 --- a/js/id/connection.js +++ b/js/id/connection.js @@ -149,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(), @@ -176,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(_) { diff --git a/js/lib/queue.js b/js/lib/queue.js new file mode 100644 index 000000000..9a3b9da47 --- /dev/null +++ b/js/lib/queue.js @@ -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() {} +})(); From bb6c0489a004f2b62379c0f81831fc8fb3c6c753 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Fri, 7 Dec 2012 12:01:48 -0500 Subject: [PATCH 28/40] Fix adding to ways in the reverse direction --- js/id/actions/add_way_node.js | 2 +- js/id/modes/add_road.js | 5 +++-- js/id/modes/draw_road.js | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/js/id/actions/add_way_node.js b/js/id/actions/add_way_node.js index 69361b8dc..d685c3295 100644 --- a/js/id/actions/add_way_node.js +++ b/js/id/actions/add_way_node.js @@ -4,7 +4,7 @@ iD.actions.AddWayNode = function(wayId, nodeId, index) { var way = graph.entity(wayId), node = graph.entity(nodeId), nodes = way.nodes.slice(); - nodes.splice(index || nodes.length, 0, nodeId); + nodes.splice((index === undefined) ? nodes.length : index, 0, nodeId); return graph.replace(way.update({nodes: nodes})); }; }; diff --git a/js/id/modes/add_road.js b/js/id/modes/add_road.js index 5f05c3f77..d3e57b066 100644 --- a/js/id/modes/add_road.js +++ b/js/id/modes/add_road.js @@ -8,6 +8,7 @@ iD.modes.AddRoad = function() { mode.enter = function() { var map = mode.map, + node, history = mode.history, controller = mode.controller; @@ -37,7 +38,7 @@ iD.modes.AddRoad = function() { } else if (datum.type === 'way') { // begin a new way starting from an existing way - var node = iD.Node({loc: map.mouseCoordinates()}), + node = iD.Node({loc: map.mouseCoordinates()}), index = iD.util.geo.chooseIndex(datum, d3.mouse(map.surface.node()), map); history.perform( @@ -48,7 +49,7 @@ iD.modes.AddRoad = function() { } else { // begin a new way - var node = iD.Node({loc: map.mouseCoordinates()}); + node = iD.Node({loc: map.mouseCoordinates()}); history.perform( iD.actions.AddWay(way), diff --git a/js/id/modes/draw_road.js b/js/id/modes/draw_road.js index a09a69368..56058e017 100644 --- a/js/id/modes/draw_road.js +++ b/js/id/modes/draw_road.js @@ -9,7 +9,7 @@ iD.modes.DrawRoad = function(wayId, direction) { controller = mode.controller, way = history.graph().entity(wayId), node = iD.Node({loc: map.mouseCoordinates()}), - index = (direction === 'forward') ? undefined : -1, + index = (direction === 'forward') ? undefined : 0, headId = (direction === 'forward') ? _.last(way.nodes) : _.first(way.nodes), tailId = (direction === 'forward') ? _.first(way.nodes) : _.last(way.nodes); From 0e00998bc4578cdf8607eb70ce7f868e86429ffd Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Fri, 7 Dec 2012 12:02:15 -0500 Subject: [PATCH 29/40] Hide user panel when it would just be showing login. Fixes #209 --- js/id/ui/userpanel.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/js/id/ui/userpanel.js b/js/id/ui/userpanel.js index c38c9dffa..1904e0a7c 100644 --- a/js/id/ui/userpanel.js +++ b/js/id/ui/userpanel.js @@ -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); From 255e3aaabdb704d18da4e33f13e7ca45e50c1411 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 7 Dec 2012 11:54:22 -0500 Subject: [PATCH 30/40] Modify Graph so it can track deleted ids --- js/id/format/xml.js | 6 +-- js/id/graph/graph.js | 48 +++++++++++------ js/id/graph/history.js | 33 +++++------- test/spec/graph/graph.js | 106 +++++++++++++++++++++++-------------- test/spec/graph/history.js | 25 +++++++++ 5 files changed, 137 insertions(+), 81 deletions(-) diff --git a/js/id/format/xml.js b/js/id/format/xml.js index 1c0fe8ad6..ec036120f 100644 --- a/js/id/format/xml.js +++ b/js/id/format/xml.js @@ -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; diff --git a/js/id/graph/graph.js b/js/id/graph/graph.js index 0626a492f..b05129aee 100644 --- a/js/id/graph/graph.js +++ b/js/id/graph/graph.js @@ -24,14 +24,14 @@ 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' && e.members.indexOf(id) !== -1; }); }, @@ -49,7 +49,13 @@ iD.Graph.prototype = { remove: function(entity) { var entities = _.clone(this.entities); - delete entities[entity.id]; + + if (entity.created()) { + delete entities[entity.id]; + } else { + entities[entity.id] = undefined; + } + return iD.Graph(entities); }, @@ -58,7 +64,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)); } } @@ -68,26 +74,34 @@ 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 entity; + 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)); + modified: function() { + var result = []; + _.each(this.entities, function(entity, id) { + if (entity && entity.modified()) result.push(id); + }); + return result; }, - creations: function() { - return _.filter(this.entities, function(entity) { - return entity.created(); - }).map(function(e) { - return this.fetch(e.id); - }.bind(this)); + 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; } }; diff --git a/js/id/graph/history.js b/js/id/graph/history.js index eec93f3e8..683b0af16 100644 --- a/js/id/graph/history.js +++ b/js/id/graph/history.js @@ -81,29 +81,20 @@ iD.History = function() { } }, - // generate reports of changes for changesets to use - modify: function () { - return stack[index].graph.modifications(); - }, - - create: function () { - return stack[index].graph.creations(); - }, - - 'delete': function () { - return _.difference( - _.pluck(stack[0].graph.entities, 'id'), - _.pluck(stack[index].graph.entities, 'id') - ).map(function (id) { - return stack[0].graph.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); + }) }; }, diff --git a/test/spec/graph/graph.js b/test/spec/graph/graph.js index 248056c0d..1341996d8 100644 --- a/test/spec/graph/graph.js +++ b/test/spec/graph/graph.js @@ -21,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); }); }); @@ -82,21 +93,36 @@ describe('iD.Graph', function() { }); }); - 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("#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("#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("#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([]); }); }); }); diff --git a/test/spec/graph/history.js b/test/spec/graph/history.js index efabcc32a..be7237292 100644 --- a/test/spec/graph/history.js +++ b/test/spec/graph/history.js @@ -101,6 +101,31 @@ describe("iD.History", function () { }); }); + 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, "annotation"); From ea4b93d88be2aeb1c7772659cca74ef0691923d0 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 7 Dec 2012 12:24:38 -0500 Subject: [PATCH 31/40] Graph#difference --- js/id/graph/graph.js | 18 ++++++++++++++++++ test/spec/graph/graph.js | 19 +++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/js/id/graph/graph.js b/js/id/graph/graph.js index b05129aee..593124927 100644 --- a/js/id/graph/graph.js +++ b/js/id/graph/graph.js @@ -81,6 +81,24 @@ iD.Graph.prototype = { return iD.Entity(entity, {nodes: nodes}); }, + 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(); + }, + modified: function() { var result = []; _.each(this.entities, function(entity, id) { diff --git a/test/spec/graph/graph.js b/test/spec/graph/graph.js index 1341996d8..a1407c09a 100644 --- a/test/spec/graph/graph.js +++ b/test/spec/graph/graph.js @@ -93,6 +93,25 @@ describe('iD.Graph', function() { }); }); + 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("#modified", function () { it("returns an Array of ids of modified entities", function () { var node1 = iD.Node({id: 'n1', _updated: true}), From c5c65ccdfd6c421424a3153afcf3ae64d5e73198 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 7 Dec 2012 12:29:17 -0500 Subject: [PATCH 32/40] Include difference in history change events --- js/id/graph/history.js | 26 ++++++++++++++++++-------- test/spec/graph/history.js | 8 ++++---- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/js/id/graph/history.js b/js/id/graph/history.js index 683b0af16..21c1d9326 100644 --- a/js/id/graph/history.js +++ b/js/id/graph/history.js @@ -19,10 +19,8 @@ iD.History = function() { return {graph: graph, annotation: annotation}; } - function maybeChange() { - if (stack[index].annotation) { - dispatch.change(); - } + function change(previous) { + dispatch.change(history.graph().difference(previous)); } var history = { @@ -37,32 +35,44 @@ iD.History = function() { }, perform: function () { + var previous = stack[index].graph; + stack = stack.slice(0, index + 1); stack.push(perform(arguments)); index++; - dispatch.change(); + + change(previous); }, replace: function () { + var previous = stack[index].graph; + // assert(index == stack.length - 1) stack[index] = perform(arguments); - dispatch.change(); + + 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 () { diff --git a/test/spec/graph/history.js b/test/spec/graph/history.js index be7237292..15fe10964 100644 --- a/test/spec/graph/history.js +++ b/test/spec/graph/history.js @@ -28,7 +28,7 @@ describe("iD.History", function () { 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("performs multiple actions", function () { @@ -57,7 +57,7 @@ describe("iD.History", function () { it("emits a change event", function () { history.on('change', spy); history.replace(action); - expect(spy).to.have.been.called; + expect(spy).to.have.been.calledWith([]); }); it("performs multiple actions", function () { @@ -87,7 +87,7 @@ describe("iD.History", function () { history.perform(action); history.on('change', spy); history.undo(); - expect(spy).to.have.been.called; + expect(spy).to.have.been.calledWith([]); }); }); @@ -97,7 +97,7 @@ describe("iD.History", function () { history.undo(); history.on('change', spy); history.redo(); - expect(spy).to.have.been.called; + expect(spy).to.have.been.calledWith([]); }); }); From ff5839a2773256ad2a6e95426ec60fc3c6b77520 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 7 Dec 2012 14:01:52 -0500 Subject: [PATCH 33/40] Reimplement differenced rendering (fixes #219) --- js/id/renderer/map.js | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index 0bce46944..b21e5804c 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -69,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.id] : 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++) { @@ -151,7 +160,7 @@ iD.Map = function() { function drawAccuracyHandles(waynodes, filter) { var handles = g.hit.selectAll('circle.accuracy-handle') .filter(filter) - .data(waynodes, key); + .data(waynodes, function (d) { return [d.way.id, d.index].join(","); }); handles.exit().remove(); handles.enter().append('circle') .attr({ r: 2, 'class': 'accuracy-handle' }); @@ -241,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); } } @@ -257,7 +266,7 @@ iD.Map = function() { if (hover) { var oldHover = hover; hover = null; - drawVector(iD.util.trueObj([oldHover])); + drawVector([oldHover]); d3.select('.messages').text(''); } } @@ -299,12 +308,12 @@ iD.Map = function() { redraw(); } - function redraw() { + function redraw(difference) { dispatch.move(map); tilegroup.call(background); if (map.zoom() > 16) { connection.loadTiles(projection); - drawVector(); + drawVector(difference); } else { hideVector(); } From 6d7b135b223874852ddcf18420e12118a8ff5a7d Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Fri, 7 Dec 2012 14:03:03 -0500 Subject: [PATCH 34/40] Push more on split_way --- js/id/actions/split_way.js | 32 ++++++++++++++++++++++++-------- js/id/graph/history.js | 2 +- js/id/id.js | 1 + 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/js/id/actions/split_way.js b/js/id/actions/split_way.js index d9081bb9d..55623ce5e 100644 --- a/js/id/actions/split_way.js +++ b/js/id/actions/split_way.js @@ -1,16 +1,32 @@ // 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); - parents.forEach(function(way) { - var idx = _.indexOf(way.nodes, nodeId); - // Create a 'b' way that contains all of the tags in the second - // half of this way - var b = iD.Way({ tags: _.clone(way.tags), nodes: way.nodes.slice(idx) }); - graph = graph.replace(b); - // 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'); + + // 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 b = iD.Way({ tags: _.clone(way.tags), nodes: way.nodes.slice(idx) }); + graph = graph.replace(b); + // 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); + + parentRelations.forEach(function(relation) { + console.log(relation); + if (relation.tags.type === 'restriction') { + console.log(relation); + } }); + return graph; }; }; diff --git a/js/id/graph/history.js b/js/id/graph/history.js index eec93f3e8..b4f75d847 100644 --- a/js/id/graph/history.js +++ b/js/id/graph/history.js @@ -3,7 +3,7 @@ iD.History = function() { dispatch = d3.dispatch('change'); function perform(actions) { - actions = Array.prototype.slice.call(actions) + actions = Array.prototype.slice.call(actions); var annotation; diff --git a/js/id/id.js b/js/id/id.js index bd314bed8..2dff2a791 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -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) From 8c76b7d1b120ea43064b4c3cacb9c91b7b360958 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Fri, 7 Dec 2012 14:06:47 -0500 Subject: [PATCH 35/40] Fix accuracy handles updating --- js/id/renderer/map.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index b21e5804c..fafbea5bc 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -90,7 +90,7 @@ iD.Map = function() { } }); all = _.values(only); - filter = function(d) { return d.accuracy ? only[d.way.id] : only[d.id]; }; + filter = function(d) { return d.accuracy ? only[d.way] : only[d.id]; }; } if (all.length > 200000) return hideVector(); @@ -160,7 +160,7 @@ iD.Map = function() { function drawAccuracyHandles(waynodes, filter) { var handles = g.hit.selectAll('circle.accuracy-handle') .filter(filter) - .data(waynodes, function (d) { return [d.way.id, d.index].join(","); }); + .data(waynodes, function (d) { return [d.way, d.index].join(","); }); handles.exit().remove(); handles.enter().append('circle') .attr({ r: 2, 'class': 'accuracy-handle' }); From 0db5193e0c7aa7806c3d8063b9f5e02bb67eecd5 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Fri, 7 Dec 2012 14:16:11 -0500 Subject: [PATCH 36/40] Fix parentRelations --- js/id/graph/graph.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/js/id/graph/graph.js b/js/id/graph/graph.js index 593124927..d5ea1dcf8 100644 --- a/js/id/graph/graph.js +++ b/js/id/graph/graph.js @@ -31,7 +31,8 @@ iD.Graph.prototype = { parentRelations: function(id) { // This is slow and a bad hack. return _.filter(this.entities, function(e) { - return e && e.type === 'relation' && e.members.indexOf(id) !== -1; + return e && e.type === 'relation' && + _.pluck(e.members, 'id').indexOf(id) !== -1; }); }, From d4832e2e95f279845e67a384039055628178be4b Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 7 Dec 2012 14:21:22 -0500 Subject: [PATCH 37/40] Add closing segment of drawn areas (fixes #217) --- js/id/modes/add_area.js | 2 ++ js/id/modes/draw_area.js | 6 +++--- test/spec/actions/add_way_node.js | 16 +++++++++++++++- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/js/id/modes/add_area.js b/js/id/modes/add_area.js index cdee57082..9a0989198 100644 --- a/js/id/modes/add_area.js +++ b/js/id/modes/add_area.js @@ -23,6 +23,7 @@ iD.modes.AddArea = function() { history.perform( iD.actions.AddWay(way), iD.actions.AddWayNode(way.id, datum.id), + iD.actions.AddWayNode(way.id, datum.id), 'started an area'); } else { @@ -32,6 +33,7 @@ iD.modes.AddArea = function() { 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'); } diff --git a/js/id/modes/draw_area.js b/js/id/modes/draw_area.js index c52000538..0d3fa7c9d 100644 --- a/js/id/modes/draw_area.js +++ b/js/id/modes/draw_area.js @@ -19,7 +19,7 @@ iD.modes.DrawArea = function(wayId) { history.perform( iD.actions.AddNode(node), - iD.actions.AddWayNode(way.id, node.id)); + iD.actions.AddWayNode(way.id, node.id, -1)); map.surface.on('mousemove.drawarea', function() { history.replace(iD.actions.Move(node.id, map.mouseCoordinates())); @@ -31,7 +31,7 @@ iD.modes.DrawArea = function(wayId) { if (datum.id === tailId) { history.replace( iD.actions.DeleteNode(node.id), - iD.actions.AddWayNode(way.id, tailId), + iD.actions.AddWayNode(way.id, tailId, -1), 'added to an area'); controller.enter(iD.modes.Select(way)); @@ -40,7 +40,7 @@ iD.modes.DrawArea = function(wayId) { // connect the way to an existing node history.replace( iD.actions.DeleteNode(node.id), - iD.actions.AddWayNode(way.id, datum.id), + iD.actions.AddWayNode(way.id, datum.id, -1), 'added to an area'); controller.enter(iD.modes.DrawArea(wayId)); diff --git a/test/spec/actions/add_way_node.js b/test/spec/actions/add_way_node.js index aaed97ecc..5c774e5e4 100644 --- a/test/spec/actions/add_way_node.js +++ b/test/spec/actions/add_way_node.js @@ -6,10 +6,24 @@ describe("iD.actions.AddWayNode", function () { 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.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"]); + }); }); From 5371d42cdf8fa7a9e008dfb6083f3bdafc2edf0f Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Fri, 7 Dec 2012 14:33:16 -0500 Subject: [PATCH 38/40] Continue with splitWay --- js/id/actions/split_way.js | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/js/id/actions/split_way.js b/js/id/actions/split_way.js index 55623ce5e..18fc36992 100644 --- a/js/id/actions/split_way.js +++ b/js/id/actions/split_way.js @@ -13,17 +13,32 @@ iD.actions.SplitWay = function(nodeId) { // Create a 'b' way that contains all of the tags in the second // half of this way - var b = iD.Way({ tags: _.clone(way.tags), nodes: way.nodes.slice(idx) }); - graph = graph.replace(b); + 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); + 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) { - console.log(relation); if (relation.tags.type === 'restriction') { - console.log(relation); + 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' + })); + } } }); From bd18cc9353ef4e9e4520410458955a2151ebec29 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 7 Dec 2012 14:46:12 -0500 Subject: [PATCH 39/40] Fix changeset creation (fixes #230) --- js/id/ui/commit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/id/ui/commit.js b/js/id/ui/commit.js index 3a401d2f8..e5f3de828 100644 --- a/js/id/ui/commit.js +++ b/js/id/ui/commit.js @@ -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() From adb8ab53cec99bd7f432482115a2055912e164df Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Fri, 7 Dec 2012 16:01:14 -0500 Subject: [PATCH 40/40] Update relation tests --- js/id/actions/remove_relation_member.js | 4 +++- test/spec/actions/delete_node.js | 2 +- test/spec/actions/delete_way.js | 4 ++-- test/spec/actions/remove_relation_member.js | 2 +- test/spec/graph/graph.js | 2 +- 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/js/id/actions/remove_relation_member.js b/js/id/actions/remove_relation_member.js index f89bbe803..e72dd7ca0 100644 --- a/js/id/actions/remove_relation_member.js +++ b/js/id/actions/remove_relation_member.js @@ -1,7 +1,9 @@ iD.actions.RemoveRelationMember = function(relationId, memberId) { return function(graph) { var relation = graph.entity(relationId), - members = _.without(relation.members, memberId); + members = _.reject(relation.members, function(r) { + return r.id === memberId; + }); return graph.replace(relation.update({members: members})); }; }; diff --git a/test/spec/actions/delete_node.js b/test/spec/actions/delete_node.js index a296a75b5..382999b4f 100644 --- a/test/spec/actions/delete_node.js +++ b/test/spec/actions/delete_node.js @@ -16,7 +16,7 @@ describe("iD.actions.DeleteNode", function () { it("removes the node from parent relations", function () { var node = iD.Node(), - relation = iD.Relation({members: [node.id]}), + 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); diff --git a/test/spec/actions/delete_way.js b/test/spec/actions/delete_way.js index cb67e9081..2a569cb0e 100644 --- a/test/spec/actions/delete_way.js +++ b/test/spec/actions/delete_way.js @@ -8,10 +8,10 @@ describe("iD.actions.DeleteWay", function () { it("removes a way from parent relations", function () { var way = iD.Way(), - relation = iD.Relation({members: [way.id]}), + 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 () { diff --git a/test/spec/actions/remove_relation_member.js b/test/spec/actions/remove_relation_member.js index 2c7c329ab..d989b749b 100644 --- a/test/spec/actions/remove_relation_member.js +++ b/test/spec/actions/remove_relation_member.js @@ -1,7 +1,7 @@ describe("iD.actions.RemoveRelationMember", function () { it("removes a member from a relation", function () { var node = iD.Node(), - relation = iD.Way({members: [node.id]}), + relation = iD.Way({members: [{ id: node.id }]}), graph = iD.actions.RemoveRelationMember(relation.id, node.id)(iD.Graph([node, relation])); expect(graph.entity(relation.id).members).to.eql([]); }); diff --git a/test/spec/graph/graph.js b/test/spec/graph/graph.js index a1407c09a..20e1d0777 100644 --- a/test/spec/graph/graph.js +++ b/test/spec/graph/graph.js @@ -77,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([]);