From 9101aa87727658634ce19da8d314461fbfac8d88 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Mon, 5 Nov 2012 14:23:01 -0500 Subject: [PATCH] First working revision of undo redo --- index.html | 3 ++ js/iD/graph/Graph.js | 23 +++++++++++ js/iD/renderer/Map.js | 92 +++++++++++++++++++++++++++++-------------- 3 files changed, 88 insertions(+), 30 deletions(-) diff --git a/index.html b/index.html index 64b11fa72..7b384ffce 100755 --- a/index.html +++ b/index.html @@ -95,6 +95,9 @@ d3.select('.zoombuttons .zoom-in').on('click', map.zoomIn); d3.select('.zoombuttons .zoom-out').on('click', map.zoomOut); + + d3.select('#undo').on('click', map.undo); + d3.select('#redo').on('click', map.redo); diff --git a/js/iD/graph/Graph.js b/js/iD/graph/Graph.js index ba7064c63..918160392 100644 --- a/js/iD/graph/Graph.js +++ b/js/iD/graph/Graph.js @@ -4,6 +4,7 @@ iD.Graph.prototype = { // a pointer to the top of the stack. head: {}, + annotation: null, // stack of previous versions of this datastructure prev: [], @@ -29,6 +30,27 @@ iD.Graph.prototype = { return pois; }, + undo: function() { + if (this.prev.length && this.prev[0] !== this.head) { + // skip changes without annotations + for (var idx = this.prev.indexOf(this.head) - 1; idx > 0; idx--) { + if (this.annotations[idx]) break; + } + this.head = this.prev[idx]; + this.annotation = this.annotations[idx]; + } + }, + + redo: function() { + if (this.prev.length && this.prev[this.prev.length - 1] !== this.head) { + for (var idx = this.prev.indexOf(this.head) + 1; idx < this.prev.length - 1; idx++) { + if (this.annotations[idx]) break; + } + this.head = this.prev[idx]; + this.annotation = this.annotations[idx]; + } + }, + insert: function(a) { for (var i = 0; i < a.length; i++) { if (this.head[a[i].id]) return; @@ -54,6 +76,7 @@ iD.Graph.prototype = { // Make head the top of the previous stack this.head = this.prev[this.prev.length - 1]; + this.annotation = this.annotations[this.annotations.length - 1]; }, intersects: function(version, extent) { diff --git a/js/iD/renderer/Map.js b/js/iD/renderer/Map.js index 7ee38a67a..57950e3d4 100755 --- a/js/iD/renderer/Map.js +++ b/js/iD/renderer/Map.js @@ -10,6 +10,16 @@ iD.Map = function(elem) { var version = 0; + // lon/lat object to array + function ll2a(o) { + return [o.lon, o.lat]; + } + + // array to lon/lat object + function a2ll(o) { + return { lon: o[0], lat: o[1] }; + } + var map = {}, width, height, dispatch = d3.dispatch('move', 'update'), @@ -30,8 +40,13 @@ iD.Map = function(elem) { // this is used with handles dragbehavior = d3.behavior.drag() .origin(function(d) { - var n = graph.head[d]; - var p = projection([n.lon, n.lat]); + var data = (typeof d === 'number') ? graph.head[d] : d; + graph.modify(function(o) { + var c = {}; + c[data.id] = pdata.object(data).set({ modified: true }).get(); + return o.set(c); + }, ''); + p = projection(ll2a(data)); return { x: p[0], y: p[1] }; }) .on('drag', function(d) { @@ -42,20 +57,28 @@ iD.Map = function(elem) { graph.head[d].lon = ll[0]; graph.head[d].lat = ll[1]; drawVector(); + }) + .on('dragend', function(d) { + var data = (typeof d === 'number') ? graph.head[d] : d; + graph.modify(function(o) { + var c = {}; + c[data.id] = pdata.object(c[data.id]).get(); + o.set(c); + return o; + }, 'moved an element'); + map.update(); }), // geo linegen = d3.svg.line() - .defined(function(d) { - return !!graph.head[d]; - }) - .x(function(d) { - var node = graph.head[d]; - return projection([node.lon, node.lat])[0]; - }) - .y(function(d) { - var node = graph.head[d]; - return projection([node.lon, node.lat])[1]; - }), + .defined(function(d) { + return !!graph.head[d]; + }) + .x(function(d) { + return projection(ll2a(graph.head[d]))[0]; + }) + .y(function(d) { + return projection(ll2a(graph.head[d]))[1]; + }), // Abstract linegen so that it pulls from `.children`. This // makes it possible to call simply `.attr('d', nodeline)`. nodeline = function(d) { @@ -63,13 +86,12 @@ iD.Map = function(elem) { }, key = function(d) { return d.id; }; - // Creating containers - // ------------------- + // Containers + // ---------- // The map uses SVG groups in order to restrict // visual and event ordering - fills below casings, casings below // strokes, and so on. - var surface = parent.append('svg') - .call(zoombehavior); + var surface = parent.append('svg').call(zoombehavior); surface.append('defs').append('clipPath') .attr('id', 'clip') @@ -91,10 +113,10 @@ iD.Map = function(elem) { temp = r.append('g').attr('id', 'temp-g'); var class_stroke = iD.Style.styleClasses('stroke'), - class_fill = iD.Style.styleClasses('stroke'), - class_area = iD.Style.styleClasses('area'), - class_marker = iD.Style.styleClasses('marker'), - class_casing = iD.Style.styleClasses('casing'); + class_fill = iD.Style.styleClasses('stroke'), + class_area = iD.Style.styleClasses('area'), + class_marker = iD.Style.styleClasses('marker'), + class_casing = iD.Style.styleClasses('casing'); var tileclient = iD.Tiles(tilegroup, projection); @@ -172,8 +194,7 @@ iD.Map = function(elem) { .attr('r', 5) .call(dragbehavior); handles.attr('transform', function(d) { - var node = graph.head[d]; - return 'translate(' + projection([node.lon, node.lat]) + ')'; + return 'translate(' + projection(ll2a(graph.head[d])) + ')'; }); } @@ -227,9 +248,21 @@ iD.Map = function(elem) { // ----------- var undolabel = d3.select('button#undo small'); dispatch.on('update', function() { - undolabel.text(graph.annotations[graph.annotations.length - 1]); + undolabel.text(graph.annotation); + redraw(); }); + // Undo/redo + function undo() { + graph.undo(); + map.update(); + } + + function redo() { + graph.redo(); + map.update(); + } + // Getters & setters for map state // ------------------------------- // The map state can be expressed entirely as the combination @@ -281,13 +314,9 @@ iD.Map = function(elem) { function zoomOut() { return setZoom(Math.floor(getZoom() - 1)); } function getCenter() { - var ll = projection.invert([ + return a2ll(projection.invert([ width / 2, - height / 2]); - return { - lon: ll[0], - lat: ll[1] - }; + height / 2])); } function setCenter(loc) { @@ -322,6 +351,9 @@ iD.Map = function(elem) { map.graph = graph; map.surface = surface; + map.undo = undo; + map.redo = redo; + setSize(parent.node().offsetWidth, parent.node().offsetHeight); redraw();