diff --git a/Makefile b/Makefile
index c88d64244..6d585865f 100644
--- a/Makefile
+++ b/Makefile
@@ -39,6 +39,8 @@ all: \
js/id/format/*.js \
js/id/graph/*.js \
js/id/renderer/*.js \
+ js/id/svg.js \
+ js/id/svg/*.js \
js/id/ui/*.js \
js/id/end.js
diff --git a/css/map.css b/css/map.css
index 4c6506a65..ba96b32ee 100644
--- a/css/map.css
+++ b/css/map.css
@@ -302,7 +302,6 @@ text.tag-oneway {
}
.mode-browse .line,
-.mode-browse .oneway,
.mode-select .line {
cursor: url(../img/cursor-select-line.png), pointer;
}
diff --git a/index.html b/index.html
index 77684f97c..5c2624e5b 100644
--- a/index.html
+++ b/index.html
@@ -43,6 +43,7 @@
+
diff --git a/js/id/graph/graph.js b/js/id/graph/graph.js
index 86fa337a4..868c17de5 100644
--- a/js/id/graph/graph.js
+++ b/js/id/graph/graph.js
@@ -35,17 +35,6 @@ iD.Graph.prototype = {
return transients[key] = fn.call(entity);
},
- parentStructure: function(ways) {
- var nodes = {};
- ways.forEach(function(w) {
- _.uniq(w.nodes).forEach(function(n) {
- if (typeof nodes[n.id] === 'undefined') nodes[n.id] = 0;
- nodes[n.id]++;
- });
- });
- return nodes;
- },
-
parentWays: function(entity) {
var graph = this;
return this.transient(entity, 'parentWays',
@@ -63,7 +52,6 @@ iD.Graph.prototype = {
},
parentRelations: function(entity) {
- // This is slow and a bad hack.
var graph = this;
return this.transient(entity, 'parentRelations',
function generateParentRelations() {
diff --git a/js/id/modes/draw_area.js b/js/id/modes/draw_area.js
index d1ebe3ef0..d868a7620 100644
--- a/js/id/modes/draw_area.js
+++ b/js/id/modes/draw_area.js
@@ -41,22 +41,8 @@ iD.modes.DrawArea = function(wayId) {
function click() {
var datum = d3.select(d3.event.target).datum() || {};
- if (datum.id === tailId) {
- history.replace(
- iD.actions.DeleteNode(node.id),
- iD.actions.AddWayNode(way.id, tailId, -1),
- 'added to an area');
-
- controller.enter(iD.modes.Select(way));
-
- } else if (datum.id === headId) {
-
- // finish the way
- history.replace(
- iD.actions.DeleteNode(node.id),
- iD.actions.AddWayNode(way.id, tailId, -1),
- 'added to an area');
-
+ if (datum.id === tailId || datum.id === headId) {
+ history.replace(iD.actions.DeleteNode(node.id));
controller.enter(iD.modes.Select(way));
} else if (datum.type === 'node' && datum.id !== node.id) {
@@ -77,13 +63,6 @@ iD.modes.DrawArea = function(wayId) {
}
}
- function esc() {
- history.replace(
- iD.actions.DeleteNode(node.id));
-
- controller.enter(iD.modes.Browse());
- }
-
function backspace() {
d3.event.preventDefault();
@@ -109,11 +88,8 @@ iD.modes.DrawArea = function(wayId) {
function ret() {
d3.event.preventDefault();
- history.replace(
- iD.actions.DeleteNode(node.id),
- iD.actions.AddWayNode(way.id, tailId, -1),
- 'added to an area');
- controller.enter(iD.modes.Browse());
+ history.replace(iD.actions.DeleteNode(node.id));
+ controller.enter(iD.modes.Select(way));
}
surface
@@ -122,9 +98,9 @@ iD.modes.DrawArea = function(wayId) {
.on('click.drawarea', click);
map.keybinding()
- .on('⎋.drawarea', esc)
.on('⌫.drawarea', backspace)
.on('⌦.drawarea', del)
+ .on('⎋.drawarea', ret)
.on('↩.drawarea', ret);
};
diff --git a/js/id/modes/draw_line.js b/js/id/modes/draw_line.js
index 7400de80f..e55474270 100644
--- a/js/id/modes/draw_line.js
+++ b/js/id/modes/draw_line.js
@@ -90,13 +90,6 @@ iD.modes.DrawLine = function(wayId, direction) {
}
}
- function esc() {
- history.replace(
- iD.actions.DeleteNode(node.id));
-
- controller.enter(iD.modes.Browse());
- }
-
function backspace() {
d3.event.preventDefault();
@@ -121,7 +114,7 @@ iD.modes.DrawLine = function(wayId, direction) {
function ret() {
d3.event.preventDefault();
history.replace(iD.actions.DeleteNode(node.id));
- controller.enter(iD.modes.Browse());
+ controller.enter(iD.modes.Select(way));
}
function undo() {
@@ -134,9 +127,9 @@ iD.modes.DrawLine = function(wayId, direction) {
.on('click.drawline', click);
map.keybinding()
- .on('⎋.drawline', esc)
.on('⌫.drawline', backspace)
.on('⌦.drawline', del)
+ .on('⎋.drawline', ret)
.on('↩.drawline', ret)
.on('z.drawline', function(evt, mods) {
if (mods === '⌘' || mods === '⌃') undo();
diff --git a/js/id/renderer/style.js b/js/id/renderer/style.js
index ed61f44a8..734ce10ca 100644
--- a/js/id/renderer/style.js
+++ b/js/id/renderer/style.js
@@ -36,32 +36,3 @@ iD.Style.waystack = function(a, b) {
}
return as - bs;
};
-
-iD.Style.TAG_CLASSES = iD.util.trueObj([
- 'highway', 'railway', 'motorway', 'amenity', 'natural',
- 'landuse', 'building', 'oneway', 'bridge'
-]);
-
-iD.Style.styleClasses = function() {
- var tagClassRe = /^tag-/;
- return function(selection) {
- selection.each(function(d, i) {
- var classes, value = this.className;
-
- if (value.baseVal !== undefined) value = value.baseVal;
-
- classes = value.trim().split(/\s+/).filter(function(name) {
- return name.length && !tagClassRe.test(name);
- });
-
- var tags = d.tags;
- for (var k in tags) {
- if (!iD.Style.TAG_CLASSES[k]) continue;
- classes.push('tag-' + k);
- classes.push('tag-' + k + '-' + tags[k]);
- }
-
- return d3.select(this).attr('class', classes.join(' '));
- });
- };
-};
diff --git a/js/id/svg/areas.js b/js/id/svg/areas.js
index ec77d95c2..e0c2cfed0 100644
--- a/js/id/svg/areas.js
+++ b/js/id/svg/areas.js
@@ -28,7 +28,7 @@ iD.svg.Areas = function() {
paths
.order()
.attr('d', lineString)
- .call(iD.Style.styleClasses());
+ .call(iD.svg.TagClasses());
paths.exit()
.remove();
diff --git a/js/id/svg/lines.js b/js/id/svg/lines.js
index 2f0d6cece..521c0bcd1 100644
--- a/js/id/svg/lines.js
+++ b/js/id/svg/lines.js
@@ -33,7 +33,7 @@ iD.svg.Lines = function() {
paths
.order()
.attr('d', lineString)
- .call(iD.Style.styleClasses());
+ .call(iD.svg.TagClasses());
paths.exit()
.remove();
diff --git a/js/id/svg/points.js b/js/id/svg/points.js
index 7a012cb56..9015f1d5d 100644
--- a/js/id/svg/points.js
+++ b/js/id/svg/points.js
@@ -8,7 +8,7 @@ iD.svg.Points = function() {
}
}
return 'icons/unknown.png';
- };
+ }
return function(surface, graph, entities, filter, projection) {
var points = [];
@@ -40,7 +40,8 @@ iD.svg.Points = function() {
.attr({ width: 16, height: 16 })
.attr('transform', 'translate(-8, -8)');
- groups.attr('transform', iD.svg.PointTransform(projection));
+ groups.attr('transform', iD.svg.PointTransform(projection))
+ .call(iD.svg.TagClasses());
// Selecting the following implicitly
// sets the data (point entity) on the element
diff --git a/js/id/svg/tag_classes.js b/js/id/svg/tag_classes.js
new file mode 100644
index 000000000..39e54d062
--- /dev/null
+++ b/js/id/svg/tag_classes.js
@@ -0,0 +1,27 @@
+iD.svg.TagClasses = function() {
+ var keys = iD.util.trueObj([
+ 'highway', 'railway', 'motorway', 'amenity', 'natural',
+ 'landuse', 'building', 'oneway', 'bridge'
+ ]), tagClassRe = /^tag-/;
+
+ return function(selection) {
+ selection.each(function(d, i) {
+ var classes, value = this.className;
+
+ if (value.baseVal !== undefined) value = value.baseVal;
+
+ classes = value.trim().split(/\s+/).filter(function(name) {
+ return name.length && !tagClassRe.test(name);
+ });
+
+ var tags = d.tags;
+ for (var k in tags) {
+ if (!keys[k]) continue;
+ classes.push('tag-' + k);
+ classes.push('tag-' + k + '-' + tags[k]);
+ }
+
+ return d3.select(this).attr('class', classes.join(' '));
+ });
+ };
+};
diff --git a/js/id/svg/vertices.js b/js/id/svg/vertices.js
index f263dc1bf..36363fbff 100644
--- a/js/id/svg/vertices.js
+++ b/js/id/svg/vertices.js
@@ -9,8 +9,6 @@ iD.svg.Vertices = function() {
}
}
- var parentStructure = graph.parentStructure(vertices);
-
var groups = surface.select('.layer-hit').selectAll('g.vertex')
.filter(filter)
.data(vertices, iD.Entity.key);
@@ -28,7 +26,8 @@ iD.svg.Vertices = function() {
.attr('r', 4);
groups.attr('transform', iD.svg.PointTransform(projection))
- .classed('shared', function(d) { return parentStructure[d.id] > 1; });
+ .call(iD.svg.TagClasses())
+ .classed('shared', function(entity) { return graph.parentWays(entity).length > 1; });
// Selecting the following implicitly
// sets the data (vertix entity) on the elements
diff --git a/test/index.html b/test/index.html
index 03b4b9d1a..6d46e4607 100644
--- a/test/index.html
+++ b/test/index.html
@@ -45,6 +45,7 @@
+
@@ -105,6 +106,8 @@
var expect = chai.expect;
+
+
@@ -137,7 +140,10 @@
-
+
+
+
+
diff --git a/test/index_packaged.html b/test/index_packaged.html
index 22d613ec3..46339e735 100644
--- a/test/index_packaged.html
+++ b/test/index_packaged.html
@@ -24,6 +24,8 @@
var expect = chai.expect;
+
+
@@ -56,7 +58,10 @@
-
+
+
+
+
diff --git a/test/spec/renderer/style.js b/test/spec/renderer/style.js
index 1fccc9658..dad18866b 100644
--- a/test/spec/renderer/style.js
+++ b/test/spec/renderer/style.js
@@ -14,50 +14,4 @@ describe('iD.Style', function() {
expect(iD.Style.waystack(b, a)).to.equal(-1);
});
});
-
- describe('#styleClasses', function() {
- var selection;
-
- beforeEach(function () {
- selection = d3.select(document.createElement('div'));
- });
-
- it('adds no classes to elements whose datum has no tags', function() {
- selection
- .datum(iD.Entity())
- .call(iD.Style.styleClasses());
- expect(selection.attr('class')).to.equal('');
- });
-
- it('adds classes for highway tags', function() {
- selection
- .datum(iD.Entity({tags: {highway: 'primary'}}))
- .call(iD.Style.styleClasses());
- expect(selection.attr('class')).to.equal('tag-highway tag-highway-primary');
- });
-
- it('removes classes for tags that are no longer present', function() {
- selection
- .attr('class', 'tag-highway tag-highway-primary')
- .datum(iD.Entity())
- .call(iD.Style.styleClasses());
- expect(selection.attr('class')).to.equal('');
- });
-
- it('preserves existing non-"tag-"-prefixed classes', function() {
- selection
- .attr('class', 'selected')
- .datum(iD.Entity())
- .call(iD.Style.styleClasses());
- expect(selection.attr('class')).to.equal('selected');
- });
-
- it('works on SVG elements', function() {
- selection = d3.select(document.createElementNS('http://www.w3.org/2000/svg', 'g'));
- selection
- .datum(iD.Entity())
- .call(iD.Style.styleClasses());
- expect(selection.attr('class')).to.equal('');
- });
- });
});
diff --git a/test/spec/spec_helpers.js b/test/spec/spec_helpers.js
new file mode 100644
index 000000000..b8ebdf307
--- /dev/null
+++ b/test/spec/spec_helpers.js
@@ -0,0 +1,12 @@
+chai.use(function (chai, utils) {
+ var flag = utils.flag;
+
+ chai.Assertion.addMethod('classed', function (className) {
+ this.assert(
+ flag(this, 'object').classed(className)
+ , 'expected #{this} to be classed #{exp}'
+ , 'expected #{this} not to be classed #{exp}'
+ , className
+ );
+ });
+});
diff --git a/test/spec/svg/points.js b/test/spec/svg/points.js
new file mode 100644
index 000000000..18a2cf359
--- /dev/null
+++ b/test/spec/svg/points.js
@@ -0,0 +1,22 @@
+describe("iD.svg.Points", function () {
+ var surface,
+ projection = d3.geo.mercator(),
+ filter = d3.functor(true);
+
+ beforeEach(function () {
+ surface = d3.select(document.createElementNS('http://www.w3.org/2000/svg', 'svg'));
+
+ surface.append('g')
+ .attr('class', 'layer-hit');
+ });
+
+ it("adds tag classes", function () {
+ var node = iD.Node({tags: {amenity: "cafe"}, loc: [0, 0], _poi: true}),
+ graph = iD.Graph([node]);
+
+ surface.call(iD.svg.Points(), graph, [node], filter, projection);
+
+ expect(surface.select('.point')).to.be.classed('tag-amenity');
+ expect(surface.select('.point')).to.be.classed('tag-amenity-cafe');
+ });
+});
diff --git a/test/spec/svg/tag_classes.js b/test/spec/svg/tag_classes.js
new file mode 100644
index 000000000..49fd8fa20
--- /dev/null
+++ b/test/spec/svg/tag_classes.js
@@ -0,0 +1,45 @@
+describe("iD.svg.TagClasses", function () {
+ var selection;
+
+ beforeEach(function () {
+ selection = d3.select(document.createElement('div'));
+ });
+
+ it('adds no classes to elements whose datum has no tags', function() {
+ selection
+ .datum(iD.Entity())
+ .call(iD.svg.TagClasses());
+ expect(selection.attr('class')).to.equal('');
+ });
+
+ it('adds classes for highway tags', function() {
+ selection
+ .datum(iD.Entity({tags: {highway: 'primary'}}))
+ .call(iD.svg.TagClasses());
+ expect(selection.attr('class')).to.equal('tag-highway tag-highway-primary');
+ });
+
+ it('removes classes for tags that are no longer present', function() {
+ selection
+ .attr('class', 'tag-highway tag-highway-primary')
+ .datum(iD.Entity())
+ .call(iD.svg.TagClasses());
+ expect(selection.attr('class')).to.equal('');
+ });
+
+ it('preserves existing non-"tag-"-prefixed classes', function() {
+ selection
+ .attr('class', 'selected')
+ .datum(iD.Entity())
+ .call(iD.svg.TagClasses());
+ expect(selection.attr('class')).to.equal('selected');
+ });
+
+ it('works on SVG elements', function() {
+ selection = d3.select(document.createElementNS('http://www.w3.org/2000/svg', 'g'));
+ selection
+ .datum(iD.Entity())
+ .call(iD.svg.TagClasses());
+ expect(selection.attr('class')).to.equal('');
+ });
+});
diff --git a/test/spec/svg/vertices.js b/test/spec/svg/vertices.js
new file mode 100644
index 000000000..4977f3e52
--- /dev/null
+++ b/test/spec/svg/vertices.js
@@ -0,0 +1,33 @@
+describe("iD.svg.Vertices", function () {
+ var surface,
+ projection = d3.geo.mercator(),
+ filter = d3.functor(true);
+
+ beforeEach(function () {
+ surface = d3.select(document.createElementNS('http://www.w3.org/2000/svg', 'svg'));
+
+ surface.append('g')
+ .attr('class', 'layer-hit');
+ });
+
+ it("adds tag classes", function () {
+ var node = iD.Node({tags: {highway: "traffic_signals"}, loc: [0, 0]}),
+ graph = iD.Graph([node]);
+
+ surface.call(iD.svg.Vertices(), graph, [node], filter, projection);
+
+ expect(surface.select('.vertex')).to.be.classed('tag-highway');
+ expect(surface.select('.vertex')).to.be.classed('tag-highway-traffic_signals');
+ });
+
+ it("adds the .shared class to vertices that are members of two or more ways", function () {
+ var node = iD.Node({loc: [0, 0]}),
+ way1 = iD.Way({nodes: [node.id]}),
+ way2 = iD.Way({nodes: [node.id]}),
+ graph = iD.Graph([node, way1, way2]);
+
+ surface.call(iD.svg.Vertices(), graph, [node], filter, projection);
+
+ expect(surface.select('.vertex')).to.be.classed('shared');
+ });
+});