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'); + }); +});