diff --git a/index.html b/index.html
index 9c5d8ff83..9295cdbb8 100755
--- a/index.html
+++ b/index.html
@@ -46,7 +46,7 @@
-
+
diff --git a/js/iD/Util.js b/js/iD/Util.js
index f4d29b3cf..55f908233 100644
--- a/js/iD/Util.js
+++ b/js/iD/Util.js
@@ -1,9 +1,9 @@
iD.Util = {};
-iD.Util._id = 0;
+iD.Util._id = -1;
iD.Util.id = function() {
- return iD.Util._id++;
+ return iD.Util._id--;
};
iD.Util.friendlyName = function(entity) {
diff --git a/js/iD/actions/AddNodeToWayAction.js b/js/iD/actions/AddNodeToWayAction.js
deleted file mode 100644
index e719bb906..000000000
--- a/js/iD/actions/AddNodeToWayAction.js
+++ /dev/null
@@ -1,69 +0,0 @@
-// iD/actions/AddNodeToWayAction.js
-
-define(['dojo/_base/declare','iD/actions/UndoableAction'], function(declare){
-
- // ----------------------------------------------------------------------
- // AddNodeToWayAction class
-
- declare("iD.actions.AddNodeToWayAction", [iD.actions.UndoableEntityAction], {
-
- node: null,
- nodeList: null,
- index: 0,
- firstNode: null,
- autoDelete: true,
-
- constructor: function(way, node, nodeList, index, autoDelete) {
- // summary: Add a node to a way at a specified index, or -1 for the end of the way.
- this.entity = way;
- this.node = node;
- this.nodeList = nodeList;
- this.index = index;
- this.autoDelete = autoDelete;
- },
-
- doAction: function() {
- var way = this.entity; // shorthand
-
- // undelete way if it was deleted before (only happens on redo)
- if (way.deleted) {
- way.setDeletedState(false);
- if (!this.firstNode.hasParentWays()) {
- this.firstNode.connection.unregisterPOI(firstNode);
- }
- this.firstNode.addParent(way);
- }
-
- // add the node
- if (this.index === -1) this.index = this.nodeList.length;
- this.node.entity.addParent(way);
- this.node.connection.unregisterPOI(this.node);
- this.nodeList.splice(this.index, 0, this.node);
- this.markDirty();
- way.expandBbox(this.node);
-
- return this.SUCCESS;
- },
-
- undoAction: function() {
- // summary: Remove the added node. Fixme: if the way is now 1-length, we should
- // do something like deleting it and converting the remaining node to a POI.
- var way=this.entity; // shorthand
- if (this.autoDelete && way.length() === 2 &&
- way.parentRelations().length()) return this.FAIL;
-
- // remove node
- var removed=nodeList.splice(index, 1);
- if (!_.contains(this.nodeList, removed[0])) {
- removed[0].removeParent(way);
- }
- this.markClean();
- way.connection.refreshEntity(way);
-
- return this.SUCCESS;
- }
- });
-
- // ----------------------------------------------------------------------
- // End of module
-});
diff --git a/js/iD/actions/AddPlace.js b/js/iD/actions/AddPlace.js
deleted file mode 100644
index 8b1378917..000000000
--- a/js/iD/actions/AddPlace.js
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/js/iD/actions/CreateEntityAction.js b/js/iD/actions/CreateEntityAction.js
deleted file mode 100644
index 407b1bb8a..000000000
--- a/js/iD/actions/CreateEntityAction.js
+++ /dev/null
@@ -1,50 +0,0 @@
-// iD/actions/UndoableAction.js
-
-define(['dojo/_base/declare','iD/actions/UndoableAction'], function(declare) {
-
- // ----------------------------------------------------------------------
- // CreateEntityAction class
-
- declare("iD.actions.CreateEntityAction", [iD.actions.UndoableEntityAction], {
-
- setCreate:null,
- deleteAction:null,
-
- constructor: function(entity,setCreate) {
- // summary: Create a new entity - way, node or relation.
- this.setCreate = setCreate;
- this.setName("Create " + entity.entityType);
- },
-
- run: function() {
- // summary: Call out to the specified method (in the Controller) to create the entity.
- // See undoAction for explanation of special redo handling.
- if (this.deleteAction!==null) {
- this.deleteAction.undoAction(); // redo
- } else {
- this.setCreate(this.entity, false); // first time
- }
- this.markDirty();
- return this.SUCCESS;
- },
-
- undo: function() {
- // summary: Special handling for undoing a create. When undo is called, instead
- // of simply removing the entity, we work through to make a Delete[Entity]Action,
- // call that, and store it for later. Then, when this action is called again
- // (i.e. a redo), instead of creating yet another entity, we call the deleteAction.undoAction.
- if (this.deleteAction===null) { this.entity.remove(this.setAction); }
- this.deleteAction.doAction();
- this.markClean();
- return this.SUCCESS;
- },
-
- setAction: function(action) {
- // summary: Set the associated delete action (see undoAction for explanation).
- deleteAction = action;
- }
- });
-
- // ----------------------------------------------------------------------
- // End of module
-});
diff --git a/js/iD/actions/MoveNodeAction.js b/js/iD/actions/MoveNodeAction.js
deleted file mode 100644
index d377f4dc4..000000000
--- a/js/iD/actions/MoveNodeAction.js
+++ /dev/null
@@ -1,62 +0,0 @@
-// iD/actions/MoveNodeAction.js
-
-define(['dojo/_base/declare','iD/actions/UndoableAction'],
- function(declare) {
-
- // ----------------------------------------------------------------------
- // MoveNodeAction class
-
- declare("iD.actions.MoveNodeAction", [iD.actions.UndoableEntityAction], {
-
- createTime: NaN,
- oldLat: NaN,
- oldLon: NaN,
- newLat: NaN,
- newLon: NaN,
- setLatLon: null,
-
- constructor: function(node, newLat, newLon, setLatLon) {
- // summary: Move a node to a new position.
- this.entity = node;
- this.newLat = newLat;
- this.newLon = newLon;
- this.setLatLon = setLatLon;
- this.createTime = new Date().getTime();
- },
-
- run: function() {
- var node = this.entity;
- this.oldLat = node.lat;
- this.oldLon = node.lon;
- if (this.oldLat === this.newLat &&
- this.oldLon === this.newLon) {
- return NO_CHANGE;
- }
- this.setLatLon(this.newLat, this.newLon);
- this.markDirty();
- node.refresh();
- return true;
- },
-
- undo: function() {
- this.setLatLon(this.oldLat, this.oldLon);
- this.markClean();
- this.refresh();
- return true;
- },
-
- mergePrevious: function(prev) {
- if (prev.declaredClass!=this.declaredClass) { return false; }
-
- if (prev.entity == this.entity && prev.createTime+1000>this.createTime) {
- this.oldLat = prev.oldLat;
- this.oldLon = prev.oldLon;
- return true;
- }
- return false;
- }
- });
-
- // ----------------------------------------------------------------------
- // End of module
- });
diff --git a/js/iD/actions/UndoStack.js b/js/iD/actions/UndoStack.js
deleted file mode 100644
index 9129dab59..000000000
--- a/js/iD/actions/UndoStack.js
+++ /dev/null
@@ -1,104 +0,0 @@
-// iD/actions/UndoStack.js
-// ** FIXME: a couple of AS3-isms in undoIfAction/removeLastIfAction
-
-// ----------------------------------------------------------------------
-// UndoStack base class
-
-if (typeof iD === 'undefined') iD = {};
-
-iD.UndoStack = function() {
-
- var stack = {},
- undoActions = [],
- redoActions = [];
-
- var FAIL = 0,
- SUCCESS = 1,
- NO_CHANGE = 2;
-
- stack.add = function(action) {
- // summary: Add an action to the undo stack
- if (undoActions.length > 0) {
- var previous = undoActions[undoActions.length - 1];
- if (action.mergePrevious(previous)) {
- action.wasDirty = previous.wasDirty;
- action.connectionWasDirty = previous.connectionWasDirty;
- undoActions.pop();
- }
- }
- undoActions.push(action);
- redoActions = [];
- };
-
- stack.breakUndo = function() {
- // summary: Wipe the undo stack - typically used after saving.
- undoActions = [];
- redoActions = [];
- };
-
- stack.canUndo = function() {
- // summary: Are there any items on the undo stack?
- return undoActions.length > 0;
- };
-
- stack.canRedo = function() {
- // summary: Are there any redoable actions?
- return redoActions.length > 0;
- };
-
- stack.undo = function() {
- // summary: Undo the most recent action, and add it to the top of the redo stack.
- if (!stack.canUndo()) { return; }
- var action = undoActions.pop();
- action.undo();
- redoActions.push(action);
- };
-
- stack.undoIfAction = function(_action) {
- // summary: Undo the most recent action _only_ if it was of a certain type.
- // Fixme: isInstanceOf needs to be made into JavaScript.
- if (!undoActions.length) { return; }
- if (undoActions[undoActions.length-1].isInstanceOf(_action)) {
- undo();
- return true;
- }
- return false;
- };
-
- stack.removeLastIfAction = function(_action) {
- // summary: Remove the most recent action from the stack _only_ if it was of a certain type.
- // Fixme: isInstanceOf needs to be made into JavaScript.
- if (undoActions.length &&
- undoActions[undoActions.length - 1].isInstanceOf(_action)) {
- undoActions.pop();
- }
- };
-
- stack.getUndoDescription = function() {
- // summary: Get the name of the topmost item on the undo stack.
- if (!undoActions.length) return null;
- if (undoActions[undoActions.length - 1].name) {
- return undoActions[undoActions.length - 1].name;
- }
- return null;
- };
-
- stack.getRedoDescription = function() {
- // summary: Get the name of the topmost item on the redo stack.
- if (!redoActions.length) return null;
- if (redoActions[redoActions.length - 1].name) {
- return redoActions[redoActions.length - 1].name;
- }
- return null;
- };
-
- stack.redo = function() {
- // summary: Takes the action most recently undone, does it, and adds it to the undo stack.
- if (!stack.canRedo()) { return; }
- var action = redoActions.pop();
- action.run();
- undoActions.push(action);
- };
-
- return stack;
-};
diff --git a/js/iD/actions/UndoableAction.js b/js/iD/actions/UndoableAction.js
deleted file mode 100644
index 0971af7e8..000000000
--- a/js/iD/actions/UndoableAction.js
+++ /dev/null
@@ -1,133 +0,0 @@
-// iD/actions/UndoableAction.js
-// we don't currently do connectionWasDirty, and we should
-
-define(['dojo/_base/declare'], function(declare){
-
-// ----------------------------------------------------------------------
-// UndoableAction base class
-
-declare("iD.actions.UndoableAction", null, {
- mergePrevious:function() {
- // summary: If two successive actions can be merged (in particular, two successive node moves), do that.
- return false;
- },
- setName:function(_name) {
- // summary: Set the name of an action. For UI and debugging purposes.
- this.name=_name;
- }
-});
-
-// ----------------------------------------------------------------------
-// UndoableEntityAction class
-
-declare("iD.actions.UndoableEntityAction", [iD.actions.UndoableAction], {
-
- wasDirty: false,
- connectionWasDirty: false,
- initialised: false,
- entity: null,
-
- constructor:function(_entity) {
- // summary: An UndoableEntityAction is a user-induced change to the map data
- // (held within the Connection) which can be undone, with a reference
- // to a particular entity (to which the change was made).
- this.entity=_entity;
- },
-
- markDirty:function() {
- // summary: Mark a change to the entity ('dirtying' it).
- if (!this.initialised) this.init();
- if (!this.wasDirty) this.entity._markDirty();
- if (!this.connectionWasDirty) this.entity.connection.modified = true;
- },
-
- markClean:function() {
- // summary: If the entity was clean before, revert the dirty flag to that state.
- if (!this.initialised) this.init();
- if (!this.wasDirty) this.entity._markClean();
- if (!this.connectionWasDirty) this.entity.connection.modified = false;
- },
-
- init:function() {
- // summary: Record whether or not the entity and connection were clean before this action started
- this.wasDirty = this.entity.modified;
- this.connectionWasDirty = this.entity.connection.modified;
- this.initialised = true;
- },
-
- toString:function() {
- return this.name + " " + this.entity.entityType + " " + this.entity.id;
- }
-
-});
-
-
-// ----------------------------------------------------------------------
-// CompositeUndoableAction class
-
-declare("iD.actions.CompositeUndoableAction", [iD.actions.UndoableAction], {
-
- actions: null,
- actionsDone: false,
-
- constructor:function() {
- // summary: A CompositeUndoableAction is a group of user-induced changes to the map data
- // (held within the Connection) which are executed, and therefore undone,
- // in a batch. For example, creating a new node and a new way containing it.
- this.actions = [];
- },
-
- push:function(action) {
- // summary: Add a single action to the list of actions comprising this CompositeUndoableAction.
- this.actions.push(action);
- },
-
- clearActions:function() {
- // summary: Clear the list of actions.
- this.actions = [];
- },
-
- doAction:function() {
- // summary: Execute all the actions one-by-one as a transaction (i.e. roll them all back if one fails).
- if (this.actionsDone) { return this.FAIL; }
- var somethingDone = false;
- for (var i = 0; i < this.actions.length; i++) {
- var action = this.actions[i];
- var result = action.doAction();
- switch (result) {
- case this.NO_CHANGE:
- // splice this one out as it doesn't do anything
- this.actions.splice(i,1);
- i--;
- break;
- case this.FAIL:
- this._undoFrom(i);
- return this.FAIL;
- default:
- somethingDone=true;
- break;
- }
- }
- this.actionsDone = true;
- return somethingDone ? this.SUCCESS : this.NO_CHANGE;
- },
-
- undoAction:function() {
- // summary: Roll back all the actions one-by-one.
- if (!this.actionsDone) { return this.FAIL; }
- this._undoFrom(this.actions.length);
- return true;
- },
-
- _undoFrom:function(index) {
- // summary: Undo all the actions after a certain index.
- for (var i=index-1; i>=0; i--) {
- this.actions[i].undoAction();
- }
- this.actionsDone=false;
- }
-});
-
-// ----------------------------------------------------------------------
-// End of module
-});
diff --git a/js/iD/actions/actions.js b/js/iD/actions/actions.js
index 156cd532c..2584b35ed 100644
--- a/js/iD/actions/actions.js
+++ b/js/iD/actions/actions.js
@@ -8,36 +8,49 @@ iD.actions.AddPlace = {
iD.actions.AddPlace.enter();
});
},
+ node: function(ll) {
+ return {
+ type: 'node',
+ lat: ll[1],
+ lon: ll[0],
+ id: iD.Util.id(),
+ tags: {}
+ };
+ },
enter: function() {
d3.selectAll('button').classed('active', false);
d3.selectAll('button#place').classed('active', true);
var surface = this.map.surface;
var teaser = surface.selectAll('g#temp-g')
- .append('g').attr('id', 'teaser-g');
+ .append('g').attr('id', 'addplace');
teaser.append('circle')
.attr('class', 'teaser-point')
.attr('r', 10);
- surface.on('mousemove.shift', function() {
+ surface.on('mousemove.addplace', function() {
teaser.attr('transform', function() {
var off = d3.mouse(surface.node());
return 'translate(' + off + ')';
});
});
- surface.on('click', function() {
- var off = d3.mouse(surface.node());
+ surface.on('click.addplace', function() {
+ var ll = this.map.projection.invert(
+ d3.mouse(surface.node()));
+ iD.operations.addNode(this.map, this.node(ll));
this.exit();
}.bind(this));
- // Bind clicks to the map to 'add a place' and
- // add little floaty place
+ d3.select(document).on('keydown.addplace', function() {
+ if (d3.event.keyCode === 27) this.exit();
+ }.bind(this));
},
exit: function() {
- this.map.surface.on('mousemove.shift', null);
- d3.selectAll('#teaser-g').remove();
+ this.map.surface.on('.addplace', null);
+ d3.select(document).on('.addplace', null);
+ d3.selectAll('#addplace').remove();
d3.selectAll('button#place').classed('active', false);
}
};
@@ -53,9 +66,6 @@ iD.actions.AddRoad = {
enter: function() {
d3.selectAll('button').classed('active', false);
d3.selectAll('button#road').classed('active', true);
-
- // Bind clicks to the map to 'add a road' and
- // add little floaty point
},
exit: function() {
d3.selectAll('button#road').classed('active', false);
@@ -73,9 +83,6 @@ iD.actions.AddArea = {
enter: function() {
d3.selectAll('button').classed('active', false);
d3.selectAll('button#area').classed('active', true);
-
- // Bind clicks to the map to 'add an area' and
- // add little floaty point
},
exit: function() {
d3.selectAll('button#area').classed('active', false);
@@ -108,12 +115,5 @@ iD.controller = function(map) {
controller.go(iD.actions.Move);
- // Pressing 'escape' should exit any action.
- d3.select(document).on('keydown', function() {
- if (d3.event.keyCode === 27) {
- controller.go(iD.actions.Move);
- }
- });
-
return controller;
};
diff --git a/js/iD/actions/operations.js b/js/iD/actions/operations.js
new file mode 100644
index 000000000..a10d56bb3
--- /dev/null
+++ b/js/iD/actions/operations.js
@@ -0,0 +1,9 @@
+iD.operations = {};
+
+iD.operations.addNode = function(map, node) {
+ map.graph.modify(function(graph) {
+ var o = {};
+ o[node.id] = node;
+ return graph.set(o);
+ }, 'Added a new unidentified place');
+};
diff --git a/js/iD/graph/Graph.js b/js/iD/graph/Graph.js
index 3a4036e8c..88ac49b16 100644
--- a/js/iD/graph/Graph.js
+++ b/js/iD/graph/Graph.js
@@ -7,6 +7,9 @@ iD.Graph.prototype = {
// stack of previous versions of this datastructure
prev: [],
+ // messages
+ annotations: [],
+
insert: function(a) {
for (var i = 0; i < a.length; i++) {
if (this.head[a[i].id]) return;
@@ -14,13 +17,24 @@ iD.Graph.prototype = {
}
},
- modify: function(callback) {
- // Previous version pushed onto stack
- var o = pdata.object(this.head).get();
- prev.push(o);
+ modify: function(callback, annotation) {
+ // create a pdata wrapper of current head
+ var o = pdata.object(this.head);
- // Make head a copy with no common history
- this.head = pdata.object(this.head).get();
+ // Archive current version
+ this.prev.push(o.get());
+
+ // Let the operation make modification of a safe
+ // copy
+ var modified = callback(o);
+
+ // Archive this version
+ this.prev.push(modified.get());
+ // Annotate this version
+ this.annotations.push(annotation);
+
+ // Make head the top of the previous stack
+ this.head = this.prev[this.prev.length - 1];
},
intersects: function(version, extent) {
diff --git a/js/iD/renderer/Map.js b/js/iD/renderer/Map.js
index 8f2e1f363..8cfe517ec 100755
--- a/js/iD/renderer/Map.js
+++ b/js/iD/renderer/Map.js
@@ -56,9 +56,7 @@ iD.Map = function(elem) {
nodeline = function(d) {
return linegen(d.nodes);
},
- // Abstract a key function that looks for uids. This is given
- // as a second argument to `.data()`.
- key = function(d) { return d.uid; };
+ key = function(d) { return d.id; };
// Creating containers
// -------------------
@@ -112,7 +110,7 @@ iD.Map = function(elem) {
casings = casing_g.selectAll('path.casing').data(ways, key),
strokes = stroke_g.selectAll('path.stroke').data(ways, key),
markers = hit_g.selectAll('image.marker')
- .data(points.filter(iD.markerimage), key);
+ .data(points, key);
// Fills
fills.exit().remove();
@@ -142,16 +140,16 @@ iD.Map = function(elem) {
.attr('class', class_marker)
.on('click', selectClick)
.attr({ width: 16, height: 16 })
- .attr('xlink:href', iD.markerimage)
+ .attr('xlink:href', iD.Style.markerimage)
.call(dragbehavior);
markers.attr('transform', function(d) {
return 'translate(' + projection([d.lon, d.lat]) + ')';
});
if (selection.length) {
- var uid = selection[0];
+ var id = selection[0];
var active_entity = all.filter(function(a) {
- return a.uid === uid && a.entityType === 'way';
+ return a.id === id && a.entityType === 'way';
});
var handles = hit_g.selectAll('circle.handle')
@@ -189,7 +187,7 @@ iD.Map = function(elem) {
}
function selectClick(d) {
- selection = [d.uid];
+ selection = [d.id];
drawVector();
d3.select('.inspector-wrap')
.style('display', 'block')
@@ -201,7 +199,7 @@ iD.Map = function(elem) {
function augmentSelect(fn) {
return function(d) {
var c = fn(d);
- if (selection.indexOf(d.uid) !== -1) {
+ if (selection.indexOf(d.id) !== -1) {
c += ' active';
}
return c;
diff --git a/js/iD/renderer/markers.js b/js/iD/renderer/markers.js
index bcb4a18c4..15c31220c 100644
--- a/js/iD/renderer/markers.js
+++ b/js/iD/renderer/markers.js
@@ -562,13 +562,3 @@ iD._markertable = (function(markers) {
}
return table;
})(iD._markers);
-
-iD.markerimage = function(d) {
- // TODO: optimize
- for (var k in d.tags) {
- var key = k + '=' + d.tags[k];
- if (iD._markertable[key]) {
- return 'icons/' + iD._markertable[key] + '.png';
- }
- }
-};
diff --git a/js/iD/renderer/style.js b/js/iD/renderer/style.js
index 1999ba232..3abfab6fc 100644
--- a/js/iD/renderer/style.js
+++ b/js/iD/renderer/style.js
@@ -30,6 +30,17 @@ iD.Style.waystack = function(a, b) {
};
+iD.Style.markerimage = function(d) {
+ // TODO: optimize
+ for (var k in d.tags) {
+ var key = k + '=' + d.tags[k];
+ if (iD._markertable[key]) {
+ return 'icons/' + iD._markertable[key] + '.png';
+ }
+ }
+ return 'icons/unknown.png';
+};
+
iD.Style.TAG_CLASSES = {
'highway': true,
'railway': true,