diff --git a/js/id/behavior/draw_way.js b/js/id/behavior/draw_way.js index 60a5af663..a9f1f5c91 100644 --- a/js/id/behavior/draw_way.js +++ b/js/id/behavior/draw_way.js @@ -4,13 +4,17 @@ iD.behavior.DrawWay = function(wayId, headId, tailId, index, mode) { controller = mode.controller, event = d3.dispatch('add', 'addHead', 'addTail', 'addNode', 'addWay'), way = mode.history.graph().entity(wayId), - nodeId = way.nodes[index], hover, draw; + var node = iD.Node({loc: map.mouseCoordinates()}), + nodeId = node.id; + + history[way.isDegenerate() ? 'replace' : 'perform']( + iD.actions.AddNode(node), + iD.actions.AddWayNode(wayId, node.id, index)); + function move() { - history.replace( - iD.actions.MoveNode(nodeId, map.mouseCoordinates()), - history.undoAnnotation()); + history.replace(iD.actions.MoveNode(nodeId, map.mouseCoordinates())); } function add() { @@ -34,12 +38,7 @@ iD.behavior.DrawWay = function(wayId, headId, tailId, index, mode) { } function undone() { - var way = history.graph().entity(wayId); - if (way) { - controller.enter(mode); - } else { - controller.enter(iD.modes.Browse()); - } + controller.enter(iD.modes.Browse()); } var drawWay = function(surface) { @@ -73,10 +72,18 @@ iD.behavior.DrawWay = function(wayId, headId, tailId, index, mode) { history.on('undone.draw', null); }; + function ReplaceTemporaryNode(newNode) { + return function(graph) { + return graph + .replace(way.removeNode(nodeId).addNode(newNode.id, index)) + .remove(node); + } + } + // Connect the way to an existing node and continue drawing. drawWay.addNode = function(node, annotation) { history.perform( - iD.actions.AddWayNode(wayId, node.id, index), + ReplaceTemporaryNode(node), annotation); controller.enter(mode); @@ -88,8 +95,8 @@ iD.behavior.DrawWay = function(wayId, headId, tailId, index, mode) { history.perform( iD.actions.AddNode(newNode), - iD.actions.AddWayNode(wayId, newNode.id, index), iD.actions.AddWayNode(way.id, newNode.id, wayIndex), + ReplaceTemporaryNode(newNode), annotation); controller.enter(mode); @@ -99,9 +106,9 @@ iD.behavior.DrawWay = function(wayId, headId, tailId, index, mode) { drawWay.add = function(loc, annotation) { var newNode = iD.Node({loc: loc}); - history.perform( + history.replace( iD.actions.AddNode(newNode), - iD.actions.AddWayNode(wayId, newNode.id, index), + ReplaceTemporaryNode(newNode), annotation); controller.enter(mode); @@ -110,9 +117,7 @@ iD.behavior.DrawWay = function(wayId, headId, tailId, index, mode) { // Finish the draw operation, removing the temporary node. If the way has enough // nodes to be valid, it's selected. Otherwise, return to browse mode. drawWay.finish = function() { - history.replace( - iD.actions.DeleteNode(nodeId), - history.undoAnnotation()); + history.pop(); var way = history.graph().entity(wayId); if (way) { diff --git a/js/id/graph/history.js b/js/id/graph/history.js index db2afda9e..29b652554 100644 --- a/js/id/graph/history.js +++ b/js/id/graph/history.js @@ -54,9 +54,26 @@ iD.History = function() { change(previous); }, + pop: function () { + var previous = stack[index].graph; + + if (index > 0) { + index--; + stack.pop(); + change(previous); + } + }, + undo: function () { var previous = stack[index].graph; + // Pop to the first annotated state. + while (index > 0) { + if (stack[index].annotation) break; + index--; + } + + // Pop to the next annotated state. while (index > 0) { index--; if (stack[index].annotation) break; diff --git a/js/id/modes/add_area.js b/js/id/modes/add_area.js index a83ed20a1..e00031723 100644 --- a/js/id/modes/add_area.js +++ b/js/id/modes/add_area.js @@ -14,52 +14,40 @@ iD.modes.AddArea = function() { history = mode.history, controller = mode.controller; - function startFromNode(a) { - var way = iD.Way({tags: defaultTags}), - b = iD.Node({loc: a.loc}); + function startFromNode(node) { + var way = iD.Way({tags: defaultTags}); history.perform( - iD.actions.AddNode(b), iD.actions.AddWay(way), - iD.actions.AddWayNode(way.id, a.id), - iD.actions.AddWayNode(way.id, b.id), - iD.actions.AddWayNode(way.id, a.id), - 'started an area'); + iD.actions.AddWayNode(way.id, node.id), + iD.actions.AddWayNode(way.id, node.id)); controller.enter(iD.modes.DrawArea(way.id)); } function startFromWay(other, loc, index) { - var a = iD.Node({loc: loc}), - b = iD.Node({loc: loc}), + var node = iD.Node({loc: loc}), way = iD.Way({tags: defaultTags}); history.perform( - iD.actions.AddNode(a), - iD.actions.AddNode(b), + iD.actions.AddNode(node), iD.actions.AddWay(way), - iD.actions.AddWayNode(way.id, a.id), - iD.actions.AddWayNode(way.id, b.id), - iD.actions.AddWayNode(way.id, a.id), - iD.actions.AddWayNode(other.id, a.id, index), - 'started an area'); + iD.actions.AddWayNode(way.id, node.id), + iD.actions.AddWayNode(way.id, node.id), + iD.actions.AddWayNode(other.id, node.id, index)); controller.enter(iD.modes.DrawArea(way.id)); } function start(loc) { - var a = iD.Node({loc: loc}), - b = iD.Node({loc: loc}), + var node = iD.Node({loc: loc}), way = iD.Way({tags: defaultTags}); history.perform( - iD.actions.AddNode(a), - iD.actions.AddNode(b), + iD.actions.AddNode(node), iD.actions.AddWay(way), - iD.actions.AddWayNode(way.id, a.id), - iD.actions.AddWayNode(way.id, b.id), - iD.actions.AddWayNode(way.id, a.id), - 'started an area'); + iD.actions.AddWayNode(way.id, node.id), + iD.actions.AddWayNode(way.id, node.id)); controller.enter(iD.modes.DrawArea(way.id)); } diff --git a/js/id/modes/add_line.js b/js/id/modes/add_line.js index 729c382f4..ad446a0f0 100644 --- a/js/id/modes/add_line.js +++ b/js/id/modes/add_line.js @@ -14,71 +14,49 @@ iD.modes.AddLine = function() { history = mode.history, controller = mode.controller; - function startFromNode(a) { - var b = iD.Node({loc: a.loc}), - graph = history.graph(), - parent = graph.parentWays(a)[0], + function startFromNode(node) { + var graph = history.graph(), + parent = graph.parentWays(node)[0], isLine = parent && parent.geometry(graph) === 'line'; - if (isLine && parent.first() === a.id) { - history.perform( - iD.actions.AddNode(b), - iD.actions.AddWayNode(parent.id, b.id, 0), - 'continued a line'); - + if (isLine && parent.first() === node.id) { controller.enter(iD.modes.DrawLine(parent.id, 'backward')); - } else if (isLine && parent.last() === a.id) { - history.perform( - iD.actions.AddNode(b), - iD.actions.AddWayNode(parent.id, b.id), - 'continued a line'); - + } else if (isLine && parent.last() === node.id) { controller.enter(iD.modes.DrawLine(parent.id, 'forward')); } else { var way = iD.Way({tags: defaultTags}); history.perform( - iD.actions.AddNode(b), iD.actions.AddWay(way), - iD.actions.AddWayNode(way.id, a.id), - iD.actions.AddWayNode(way.id, b.id), - 'continued a line'); + iD.actions.AddWayNode(way.id, node.id)); controller.enter(iD.modes.DrawLine(way.id, 'forward')); } } function startFromWay(other, loc, index) { - var a = iD.Node({loc: loc}), - b = iD.Node({loc: loc}), + var node = iD.Node({loc: loc}), way = iD.Way({tags: defaultTags}); history.perform( - iD.actions.AddNode(a), - iD.actions.AddNode(b), + iD.actions.AddNode(node), iD.actions.AddWay(way), - iD.actions.AddWayNode(way.id, a.id), - iD.actions.AddWayNode(way.id, b.id), - iD.actions.AddWayNode(other.id, a.id, index), - 'started a line'); + iD.actions.AddWayNode(way.id, node.id), + iD.actions.AddWayNode(other.id, node.id, index)); controller.enter(iD.modes.DrawLine(way.id, 'forward')); } function start(loc) { - var a = iD.Node({loc: loc}), - b = iD.Node({loc: loc}), + var node = iD.Node({loc: loc}), way = iD.Way({tags: defaultTags}); history.perform( - iD.actions.AddNode(a), - iD.actions.AddNode(b), + iD.actions.AddNode(node), iD.actions.AddWay(way), - iD.actions.AddWayNode(way.id, a.id), - iD.actions.AddWayNode(way.id, b.id), - 'started a line'); + iD.actions.AddWayNode(way.id, node.id)); controller.enter(iD.modes.DrawLine(way.id, 'forward')); } diff --git a/js/id/modes/draw_area.js b/js/id/modes/draw_area.js index fe053e793..9e2bd6a32 100644 --- a/js/id/modes/draw_area.js +++ b/js/id/modes/draw_area.js @@ -8,24 +8,25 @@ iD.modes.DrawArea = function(wayId) { mode.enter = function() { var way = mode.history.graph().entity(wayId), - index = way.nodes.length - 2, - headId = way.nodes[index - 1], - tailId = way.first(); + index = -1, + headId = way.nodes[way.nodes.length - 2], + tailId = way.first(), + annotation = way.isDegenerate() ? 'started an area' : 'continued an area'; function addHeadTail() { behavior.finish(); } function addNode(node) { - behavior.addNode(node, way.nodes.length > 2 ? 'added to an area' : ''); + behavior.addNode(node, annotation); } function addWay(way, loc, index) { - behavior.addWay(way, loc, index, way.nodes.length > 2 ? 'added to an area' : ''); + behavior.addWay(way, loc, index, annotation); } function add(loc) { - behavior.add(loc, way.nodes.length > 2 ? 'added to an area' : ''); + behavior.add(loc, annotation); } behavior = iD.behavior.DrawWay(wayId, headId, tailId, index, mode) diff --git a/js/id/modes/draw_line.js b/js/id/modes/draw_line.js index 4179392a0..7980b0549 100644 --- a/js/id/modes/draw_line.js +++ b/js/id/modes/draw_line.js @@ -8,9 +8,10 @@ iD.modes.DrawLine = function(wayId, direction) { mode.enter = function() { var way = mode.history.graph().entity(wayId), - index = (direction === 'forward') ? way.nodes.length - 1 : 0, - headId = (direction === 'forward') ? way.nodes[index - 1] : way.nodes[index + 1], - tailId = (direction === 'forward') ? way.first() : way.last(); + index = (direction === 'forward') ? undefined : 0, + headId = (direction === 'forward') ? way.last() : way.first(), + tailId = (direction === 'forward') ? way.first() : way.last(), + annotation = way.isDegenerate() ? 'started a line' : 'continued a line'; function addHead() { behavior.finish(); @@ -19,22 +20,22 @@ iD.modes.DrawLine = function(wayId, direction) { function addTail(node) { // connect the way in a loop if (way.nodes.length > 2) { - behavior.addNode(node, 'added to a line'); + behavior.addNode(node, annotation); } else { behavior.cancel(); } } function addNode(node) { - behavior.addNode(node, 'added to a line'); + behavior.addNode(node, annotation); } function addWay(way, loc, index) { - behavior.addWay(way, loc, index, 'added to a line'); + behavior.addWay(way, loc, index, annotation); } function add(loc) { - behavior.add(loc, 'added to a line'); + behavior.add(loc, annotation); } behavior = iD.behavior.DrawWay(wayId, headId, tailId, index, mode) diff --git a/test/spec/graph/history.js b/test/spec/graph/history.js index 0b6f21608..b59190c0f 100644 --- a/test/spec/graph/history.js +++ b/test/spec/graph/history.js @@ -70,6 +70,27 @@ describe("iD.History", function () { }); }); + describe("#pop", function () { + it("updates the graph", function () { + history.perform(action, "annotation"); + history.pop(); + expect(history.undoAnnotation()).to.be.undefined; + }); + + it("does not push the redo stack", function () { + history.perform(action, "annotation"); + history.pop(); + expect(history.redoAnnotation()).to.be.undefined; + }); + + it("emits a change event", function () { + history.perform(action); + history.on('change', spy); + history.pop(); + expect(spy).to.have.been.calledWith([]); + }); + }); + describe("#undo", function () { it("pops the undo stack", function () { history.perform(action, "annotation"); @@ -77,6 +98,13 @@ describe("iD.History", function () { expect(history.undoAnnotation()).to.be.undefined; }); + it("pops past unannotated states", function () { + history.perform(action, "annotation"); + history.perform(action); + history.undo(); + expect(history.undoAnnotation()).to.be.undefined; + }); + it("pushes the redo stack", function () { history.perform(action, "annotation"); history.undo();