From c5d691d79d723545852236f45c3738efb5dc58d9 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Mon, 28 Jan 2013 13:35:47 -0500 Subject: [PATCH 001/415] Graph#rebase --- js/id/graph/graph.js | 35 ++++++----- js/id/graph/history.js | 8 ++- test/spec/graph/graph.js | 121 +++++++++++++++++++++++++++++++++------ 3 files changed, 131 insertions(+), 33 deletions(-) diff --git a/js/id/graph/graph.js b/js/id/graph/graph.js index 120fa2fe7..3eea6a59a 100644 --- a/js/id/graph/graph.js +++ b/js/id/graph/graph.js @@ -1,13 +1,15 @@ -iD.Graph = function(entities, mutable) { - if (!(this instanceof iD.Graph)) return new iD.Graph(entities, mutable); +iD.Graph = function(other, mutable) { + if (!(this instanceof iD.Graph)) return new iD.Graph(other, mutable); - if (_.isArray(entities)) { - this.entities = {}; - for (var i = 0; i < entities.length; i++) { - this.entities[entities[i].id] = entities[i]; + if (_.isArray(other)) { + this.entities = Object.create({}); + for (var i = 0; i < other.length; i++) { + this.entities[other[i].id] = other[i]; } + } else if (other instanceof iD.Graph) { + this.rebase(other.base(), other.entities); } else { - this.entities = entities || {}; + this.entities = Object.create(other || {}); } this.transients = {}; @@ -97,10 +99,18 @@ iD.Graph.prototype = { return (this._childNodes[entity.id] = nodes); }, - merge: function(graph) { - return this.update(function () { - _.defaults(this.entities, graph.entities); - }); + base: function() { + return Object.getPrototypeOf ? + Object.getPrototypeOf(this.entities) : + this.entities.__proto__; + }, + + // Unlike other graph methods, rebase mutates in place. This is because it + // is used only during the history operation that merges newly downloaded + // data into each state. To external consumers, it should appear as if the + // graph always contained the newly downloaded data. + rebase: function(base, entities) { + this.entities = _.assign(Object.create(base), entities || this.entities); }, replace: function(entity) { @@ -120,7 +130,7 @@ iD.Graph.prototype = { }, update: function() { - var graph = this.frozen ? iD.Graph(_.clone(this.entities), true) : this; + var graph = this.frozen ? iD.Graph(this, true) : this; for (var i = 0; i < arguments.length; i++) { arguments[i].call(graph, graph); @@ -133,7 +143,6 @@ iD.Graph.prototype = { this.frozen = true; if (iD.debug) { - Object.freeze(this); Object.freeze(this.entities); } diff --git a/js/id/graph/history.js b/js/id/graph/history.js index 29b652554..c9e3e19f5 100644 --- a/js/id/graph/history.js +++ b/js/id/graph/history.js @@ -30,8 +30,12 @@ iD.History = function() { }, merge: function (graph) { - for (var i = 0; i < stack.length; i++) { - stack[i].graph = stack[i].graph.merge(graph); + var base = stack[0].graph.base(); + + _.defaults(base, graph.entities); + + for (var i = 1; i < stack.length; i++) { + stack[i].graph.rebase(base); } }, diff --git a/test/spec/graph/graph.js b/test/spec/graph/graph.js index 25382e7c9..fa83a3a03 100644 --- a/test/spec/graph/graph.js +++ b/test/spec/graph/graph.js @@ -1,25 +1,110 @@ describe('iD.Graph', function() { - it("can be constructed with an entities Object", function () { - var entity = iD.Entity(), - graph = iD.Graph({'n-1': entity}); - expect(graph.entity('n-1')).to.equal(entity); - }); - - it("can be constructed with an entities Array", function () { - var entity = iD.Entity(), - graph = iD.Graph([entity]); - expect(graph.entity(entity.id)).to.equal(entity); - }); - - if (iD.debug) { - it("is frozen", function () { - expect(Object.isFrozen(iD.Graph())).to.be.true; + describe("constructor", function () { + it("accepts an entities Object", function () { + var entity = iD.Entity(), + graph = iD.Graph({'n-1': entity}); + expect(graph.entity('n-1')).to.equal(entity); }); - it("freezes entities", function () { - expect(Object.isFrozen(iD.Graph().entities)).to.be.true; + it("accepts an entities Array", function () { + var entity = iD.Entity(), + graph = iD.Graph([entity]); + expect(graph.entity(entity.id)).to.equal(entity); }); - } + + it("accepts a Graph", function () { + var entity = iD.Entity(), + graph = iD.Graph(iD.Graph([entity])); + expect(graph.entity(entity.id)).to.equal(entity); + }); + + it("copies other's entities", function () { + var entity = iD.Entity(), + base = iD.Graph([entity]), + graph = iD.Graph(base); + expect(graph.entities).not.to.equal(base.entities); + }); + + it("rebases on other's base", function () { + var base = iD.Graph(), + graph = iD.Graph(base); + expect(graph.base()).to.equal(base.base()); + }); + + it("freezes by default", function () { + expect(iD.Graph().frozen).to.be.true; + }); + + it("remains mutable if passed true as second argument", function () { + expect(iD.Graph([], true).frozen).not.to.be.true; + }); + }); + + describe("#freeze", function () { + it("sets the frozen flag", function () { + expect(iD.Graph([], true).freeze().frozen).to.be.true; + }); + + if (iD.debug) { + it("freezes entities", function () { + expect(Object.isFrozen(iD.Graph().entities)).to.be.true; + }); + } + }); + + describe("#rebase", function () { + it("preserves existing entities", function () { + var node = iD.Node({id: 'n'}), + graph = iD.Graph([node]); + graph.rebase({}); + expect(graph.entity('n')).to.equal(node); + }); + + it("includes new entities", function () { + var node = iD.Node({id: 'n'}), + graph = iD.Graph(); + graph.rebase({'n': node}); + expect(graph.entity('n')).to.equal(node); + }); + + it("gives precedence to existing entities", function () { + var a = iD.Node({id: 'n'}), + b = iD.Node({id: 'n'}), + graph = iD.Graph([a]); + graph.rebase({'n': b}); + expect(graph.entity('n')).to.equal(a); + }); + + it("inherits entities from base prototypally", function () { + var graph = iD.Graph(); + graph.rebase({'n': iD.Node()}); + expect(graph.entities).not.to.have.ownProperty('n'); + }); + + xit("updates parentWays", function () { + var n = iD.Node({id: 'n'}), + w1 = iD.Way({id: 'w1', nodes: ['n']}), + w2 = iD.Way({id: 'w2', nodes: ['n']}), + graph = iD.Graph([n, w1]); + + graph.parentWays(n); + graph.rebase({'w2': w2}); + + expect(graph.parentWays(n)).to.eql([w1, w2]); + }); + + xit("updates parentRelations", function () { + var n = iD.Node({id: 'n'}), + r1 = iD.Relation({id: 'r1', members: [{id: 'n'}]}), + r2 = iD.Relation({id: 'r2', members: [{id: 'n'}]}), + graph = iD.Graph([n, r1]); + + graph.parentRelations(n); + graph.rebase({'r2': r2}); + + expect(graph.parentRelations(n)).to.eql([r1, r2]); + }); + }); describe("#remove", function () { it("returns a new graph", function () { From 7d32af52640fc8d5519849eb7c47a120e3ce0dae Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Tue, 29 Jan 2013 12:49:00 -0500 Subject: [PATCH 002/415] work towards using prototypes for entities --- js/id/graph/graph.js | 30 ++++++++++++++++++------------ js/id/graph/history.js | 9 ++++++--- js/id/renderer/map.js | 2 +- js/id/util.js | 2 ++ 4 files changed, 27 insertions(+), 16 deletions(-) diff --git a/js/id/graph/graph.js b/js/id/graph/graph.js index 3eea6a59a..d35f616a0 100644 --- a/js/id/graph/graph.js +++ b/js/id/graph/graph.js @@ -1,19 +1,25 @@ iD.Graph = function(other, mutable) { if (!(this instanceof iD.Graph)) return new iD.Graph(other, mutable); - if (_.isArray(other)) { - this.entities = Object.create({}); - for (var i = 0; i < other.length; i++) { - this.entities[other[i].id] = other[i]; - } - } else if (other instanceof iD.Graph) { + if (other instanceof iD.Graph) { + var base = other.base(); + this.entities = _.assign(Object.create(base.entities), other.entities); + this._parentWays = _.assign(Object.create(base.parentWays), other._parentWays); this.rebase(other.base(), other.entities); + } else { - this.entities = Object.create(other || {}); + if (_.isArray(other)) { + this.entities = Object.create({}); + for (var i = 0; i < other.length; i++) { + this.entities[other[i].id] = other[i]; + } + } else { + this.entities = Object.create(other || {}); + } + this._parentWays = Object.create({}); } this.transients = {}; - this._parentWays = {}; this._parentRels = {}; this._childNodes = {}; @@ -100,9 +106,10 @@ iD.Graph.prototype = { }, base: function() { - return Object.getPrototypeOf ? - Object.getPrototypeOf(this.entities) : - this.entities.__proto__; + return { + 'entities': iD.util.getPrototypeOf(this.entities), + 'parentWays': iD.util.getPrototypeOf(this._parentWays) + }; }, // Unlike other graph methods, rebase mutates in place. This is because it @@ -110,7 +117,6 @@ iD.Graph.prototype = { // data into each state. To external consumers, it should appear as if the // graph always contained the newly downloaded data. rebase: function(base, entities) { - this.entities = _.assign(Object.create(base), entities || this.entities); }, replace: function(entity) { diff --git a/js/id/graph/history.js b/js/id/graph/history.js index c9e3e19f5..e5bfd353f 100644 --- a/js/id/graph/history.js +++ b/js/id/graph/history.js @@ -30,11 +30,14 @@ iD.History = function() { }, merge: function (graph) { - var base = stack[0].graph.base(); + var base = stack[0].graph.base(), + mergee = graph.base(); - _.defaults(base, graph.entities); + for (var i in base) { + _.defaults(base[i], mergee[i]); + } - for (var i = 1; i < stack.length; i++) { + for (i = 1; i < stack.length; i++) { stack[i].graph.rebase(base); } }, diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index bc4ca8e03..e27fdc5b9 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -123,7 +123,7 @@ iD.Map = function() { function connectionLoad(err, result) { history.merge(result); - redraw(Object.keys(result.entities)); + redraw(Object.keys(iD.util.getPrototypeOf(result.entities))); } function zoomPan() { diff --git a/js/id/util.js b/js/id/util.js index da31a162b..645d15363 100644 --- a/js/id/util.js +++ b/js/id/util.js @@ -74,3 +74,5 @@ iD.util.getStyle = function(selector) { } } }; + +iD.util.getPrototypeOf = Object.getPrototypeOf || function(obj) { return obj.__proto__; }; From 6c1e4e5b4d4f019b2643f358a2152ab71f67d8fe Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Tue, 29 Jan 2013 13:32:39 -0500 Subject: [PATCH 003/415] Working more-efficient parentWays --- js/id/graph/graph.js | 57 ++++++++++++++++++++++++++++++++---------- js/id/graph/history.js | 9 ++++--- js/id/renderer/map.js | 2 +- 3 files changed, 50 insertions(+), 18 deletions(-) diff --git a/js/id/graph/graph.js b/js/id/graph/graph.js index d35f616a0..2705fd029 100644 --- a/js/id/graph/graph.js +++ b/js/id/graph/graph.js @@ -8,15 +8,12 @@ iD.Graph = function(other, mutable) { this.rebase(other.base(), other.entities); } else { - if (_.isArray(other)) { - this.entities = Object.create({}); - for (var i = 0; i < other.length; i++) { - this.entities[other[i].id] = other[i]; - } - } else { - this.entities = Object.create(other || {}); - } + this.entities = other || Object.create({}); this._parentWays = Object.create({}); + + for (var i in this.entities) { + this._updateCalculated(undefined, this.entities[i]); + } } this.transients = {}; @@ -119,19 +116,53 @@ iD.Graph.prototype = { rebase: function(base, entities) { }, + _updateCalculated: function(oldentity, entity) { + + var type = entity && entity.type || oldentity && oldentity.type, + removed, added, parentWays, i; + + + if (type === 'way') { + + // Update parentWays + if (oldentity && entity) { + removed = _.difference(oldentity.nodes, entity.nodes); + added = _.difference(entity.nodes, oldentity.nodes); + } else if (oldentity) { + removed = oldentity.nodes; + added = []; + } else if (entity) { + removed = []; + added = entity.nodes; + } + for (i = 0; i < removed.length; i++) { + this._parentWays[removed[i]] = _.without(this._parentWays[removed[i]], oldentity.id); + } + for (i = 0; i < added.length; i++) { + parentWays = _.without(this._parentWays[added[i]], entity.id); + parentWays.push(entity.id); + this._parentWays[added[i]] = parentWays; + } + } else if (type === 'node') { + + } else if (type === 'relation') { + + // TODO: iterate over members + + } + }, + replace: function(entity) { return this.update(function () { + this._updateCalculated(this.entities[entity.id], entity); this.entities[entity.id] = entity; }); }, remove: function(entity) { return this.update(function () { - if (entity.created()) { - delete this.entities[entity.id]; - } else { - this.entities[entity.id] = undefined; - } + this._updateCalculated(entity, undefined); + this.entities[entity.id] = undefined; }); }, diff --git a/js/id/graph/history.js b/js/id/graph/history.js index e5bfd353f..95e499ef2 100644 --- a/js/id/graph/history.js +++ b/js/id/graph/history.js @@ -30,12 +30,13 @@ iD.History = function() { }, merge: function (graph) { - var base = stack[0].graph.base(), - mergee = graph.base(); + var base = stack[0].graph.base(); - for (var i in base) { - _.defaults(base[i], mergee[i]); + _.defaults(base.entities, graph.entities); + for (var i in graph.entities) { + base.parentWays[i] = _.unique((base.parentWays[id] || []).concat(graph._parentWays[i] || [])); } + _.defaults(base.parentWays, graph._parentWays); for (i = 1; i < stack.length; i++) { stack[i].graph.rebase(base); diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index e27fdc5b9..bc4ca8e03 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -123,7 +123,7 @@ iD.Map = function() { function connectionLoad(err, result) { history.merge(result); - redraw(Object.keys(iD.util.getPrototypeOf(result.entities))); + redraw(Object.keys(result.entities)); } function zoomPan() { From 852973f61d702fdfd5af117bfa9646909b5068c0 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 29 Jan 2013 15:03:13 -0500 Subject: [PATCH 004/415] Fix global leak --- js/lib/d3.tail.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/lib/d3.tail.js b/js/lib/d3.tail.js index 0667227ee..4afaf97ff 100644 --- a/js/lib/d3.tail.js +++ b/js/lib/d3.tail.js @@ -32,7 +32,7 @@ d3.tail = function() { function mousemove() { if (text === false) return; var xoffset = ((d3.event.x + tooltip_size[0] + xmargin) > selection_size[0]) ? - -tooltip_size[0] - xmargin : xoffset = xmargin; + -tooltip_size[0] - xmargin : xmargin; container.style(transformProp, 'translate(' + (~~d3.event.x + xoffset) + 'px,' + ~~d3.event.y + 'px)'); From 699041019f300ecdf162331596c5d0078328f507 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Tue, 29 Jan 2013 17:02:56 -0500 Subject: [PATCH 005/415] Pan map when dragging to pad. Fixes #534 --- js/id/behavior/drag_node.js | 29 +++++++++++++++++++++++++++++ js/id/renderer/map.js | 10 ++++++++++ 2 files changed, 39 insertions(+) diff --git a/js/id/behavior/drag_node.js b/js/id/behavior/drag_node.js index 084c81895..1bfddfed6 100644 --- a/js/id/behavior/drag_node.js +++ b/js/id/behavior/drag_node.js @@ -1,7 +1,30 @@ iD.behavior.DragNode = function(mode) { var history = mode.history, + size = mode.map.size(), + nudgeInterval, projection = mode.map.projection; + function edge(point) { + var pad = [30, 100, 30, 100]; + if (point[0] > size[0] - pad[0]) return [-10, 0]; + else if (point[0] < pad[2]) return [10, 0]; + else if (point[1] > size[1] - pad[1]) return [0, -10]; + else if (point[1] < pad[3]) return [0, 10]; + return null; + } + + function startNudge(nudge) { + if (nudgeInterval) window.clearInterval(nudgeInterval); + nudgeInterval = window.setInterval(function() { + mode.map.pan(nudge).redraw(); + }, 50); + } + + function stopNudge(nudge) { + if (nudgeInterval) window.clearInterval(nudgeInterval); + nudgeInterval = null; + } + return iD.behavior.drag() .delegate(".node") .origin(function(entity) { @@ -13,11 +36,17 @@ iD.behavior.DragNode = function(mode) { }) .on('move', function(entity) { d3.event.sourceEvent.stopPropagation(); + + var nudge = edge(d3.event.point); + if (nudge) startNudge(nudge); + else stopNudge(); + history.replace( iD.actions.MoveNode(entity.id, projection.invert(d3.event.point)), 'moved a node'); }) .on('end', function() { + stopNudge(); history.replace( iD.actions.Noop(), 'moved a node'); diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index 60ad3de39..487ac736a 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -258,9 +258,19 @@ iD.Map = function() { t[0] - ll[0] + c[0], t[1] - ll[1] + c[1]]); zoom.translate(projection.translate()); + dispatch.move(map); return true; } + map.pan = function(d) { + var t = projection.translate(); + t[0] += d[0]; + t[1] += d[1]; + projection.translate(t); + zoom.translate(projection.translate()); + return map; + }; + map.size = function(_) { if (!arguments.length) return dimensions; dimensions = _; From f39b502c5a7c6e5a837ae77798ee8dc49ce45a96 Mon Sep 17 00:00:00 2001 From: Saman Bemel-Benrud Date: Tue, 29 Jan 2013 17:41:08 -0500 Subject: [PATCH 006/415] adding operations icons. --- img/source/radial-menu.svg | 13941 +++-------------------------------- img/source/sprite.svg | 294 +- img/sprite.png | Bin 12916 -> 14238 bytes 3 files changed, 1120 insertions(+), 13115 deletions(-) diff --git a/img/source/radial-menu.svg b/img/source/radial-menu.svg index 7c43c61d8..2fa1e44f9 100644 --- a/img/source/radial-menu.svg +++ b/img/source/radial-menu.svg @@ -50,15 +50,15 @@ borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" - inkscape:zoom="1" - inkscape:cx="-252.25018" - inkscape:cy="571.71412" + inkscape:zoom="4" + inkscape:cx="53.004316" + inkscape:cy="144.37657" inkscape:document-units="px" inkscape:current-layer="layer1" showgrid="false" - inkscape:window-width="1287" + inkscape:window-width="1280" inkscape:window-height="753" - inkscape:window-x="159" + inkscape:window-x="37" inkscape:window-y="0" inkscape:window-maximized="0" inkscape:snap-bbox="true" @@ -102,6 +102,14 @@ orientation="0,1" position="1052,739" id="guide15758" /> + + @@ -111,7 +119,7 @@ image/svg+xml - + @@ -119,6959 +127,17 @@ inkscape:label="Layer 1" inkscape:groupmode="layer" id="layer1"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Split way + + + + + + + + + + + + Delete + Circularize + Straighten + + Split + + + + Unjoin + Reverse + + + + + + + + + Move + + + + + + + + + + + + Merge + Orthogonalize + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/img/source/sprite.svg b/img/source/sprite.svg index 5589fc2d5..8af12723b 100644 --- a/img/source/sprite.svg +++ b/img/source/sprite.svg @@ -38,22 +38,22 @@ borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" - inkscape:zoom="1" - inkscape:cx="336.77382" - inkscape:cy="126.79999" + inkscape:zoom="16" + inkscape:cx="174.72757" + inkscape:cy="45.850291" inkscape:document-units="px" inkscape:current-layer="layer12" - showgrid="false" + showgrid="true" inkscape:window-width="1280" inkscape:window-height="756" - inkscape:window-x="119" + inkscape:window-x="240" inkscape:window-y="0" inkscape:window-maximized="0" fit-margin-top="0" fit-margin-left="0" fit-margin-right="0" fit-margin-bottom="0" - showguides="false" + showguides="true" inkscape:guide-bbox="true" inkscape:snap-bbox="true" inkscape:snap-nodes="true"> @@ -181,7 +181,7 @@ image/svg+xml - + @@ -587,20 +587,6 @@ id="path55334" d="m 35,4 c 0,1 0,4 0,4 0,0 0,0.5 -0.5,0.5 C 34,8.5 34.070717,8.22484 34,8 33.695312,7.03125 33.132813,5.63541 33,5 32.795405,4.02115 32.333333,4 32,4 31,4 31,5 31,5 l 1,4 0,3 C 32,12 31.5,11.5 30.5,10.5 29.945312,9.94531 29.257659,9.7508 28.8125,10.00781 28.377049,10.25922 28.150942,10.89541 28.5,11.5 28.853553,12.11237 32,16 32,16 c 1,1 2,1 4,1 2.666667,0 1,0 3,0 2,0 2.288488,-2.86546 3,-5 C 43,9 43.5,7 43.5,7 43.613427,6.57668 43.45711,6.154 43,6 42.119539,5.70338 41.63994,6.35278 41.5,7 41.25,8.15625 41,9 41,9 40.90625,9.31383 41.0013,9.5 40.5,9.5 39.99086,9.5 40,9 40,9 40,9 40,6.33333 40,5 40,4 39,4 39,4 c 0,0 -1,0 -1,1 0,1 0,1.66667 0,3 0,0 0.01305,0.5 -0.5,0.5 C 36.998673,8.5 37,8 37,8 37,8 37,5 37,4 37,3 36,3 36,3 36,3 35,3 35,4 z" style="color:#000000;fill:#222222;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;filter:url(#filter8013-4);enable-background:accumulate" /> - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/img/sprite.png b/img/sprite.png index be9ba0fdbdefcc9c8159a1ffdb21b104a6664f18..2bdbc9d8606bc12ffe5b2beded0983ec36c2bc9d 100644 GIT binary patch literal 14238 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 From bfafc8e929c3ac72cc7928e561dd6ae4e4f1f21b Mon Sep 17 00:00:00 2001 From: Saman Bemel-Benrud Date: Tue, 29 Jan 2013 17:56:47 -0500 Subject: [PATCH 007/415] adding css classes for operation icons. --- css/app.css | 44 +++++++++++++++++++++---------------------- img/source/sprite.svg | 35 ++++++++++------------------------ 2 files changed, 32 insertions(+), 47 deletions(-) diff --git a/css/app.css b/css/app.css index 4cf72174d..d82d7d292 100644 --- a/css/app.css +++ b/css/app.css @@ -416,12 +416,12 @@ button[disabled] .label { } /* Definitions for every icon */ -.icon.browse { background-position: 0px -20px;} -.icon.add-point { background-position: -20px -20px;} -.icon.add-line { background-position: -40px -20px;} -.icon.add-area { background-position: -60px -20px;} -.icon.undo { background-position: -80px -20px;} -.icon.redo { background-position: -100px -20px;} +.icon.browse { background-position: 0px 0px;} +.icon.add-point { background-position: -20px 0px;} +.icon.add-line { background-position: -40px 0px;} +.icon.add-area { background-position: -60px 0px;} +.icon.undo { background-position: -80px 0px;} +.icon.redo { background-position: -100px 0px;} .icon.apply { background-position: -120px 0px;} .icon.save { background-position: -140px 0px;} @@ -437,18 +437,7 @@ button[disabled] .label { .icon.nearby { background-position: -340px 0px;} .icon.geolocate { background-position: -360px 0px;} .icon.warning { background-position: -380px 0px;} - .icon.close-modal{ background-position: -200px -40px;} - -.icon.invert.zoom-in { background-position: -240px -40px;} - -.icon.browse { background-position: 0px 0px;} -.icon.add-point { background-position: -20px 0px;} -.icon.add-line { background-position: -40px 0px;} -.icon.add-area { background-position: -60px 0px;} -.icon.undo { background-position: -80px 0px;} -.icon.redo { background-position: -100px 0px;} - .fillD .icon.avatar { background-position: -320px -20px;} .fillD .icon.nearby { background-position: -340px -20px;} @@ -471,13 +460,24 @@ button[disabled] .icon.layers { background-position: -300px -40px;} button[disabled] .icon.avatar { background-position: -320px -40px;} button[disabled] .icon.nearby { background-position: -340px -40px;} -.icon.big-line { background-position: 0px -80px;} -.icon.big-point { background-position: -40px -80px;} -.icon.big-area { background-position: -80px -80px;} -.icon.big-vertex { background-position: -120px -80px;} -.icon.big-inspect { background-position: -160px -80px;} +.icon.big-line { background-position: 0px -80px;} +.icon.big-point { background-position: -40px -80px;} +.icon.big-area { background-position: -80px -80px;} +.icon.big-vertex { background-position: -120px -80px;} +.icon.big-inspect { background-position: -160px -80px;} .icon.big-relation { background-position: -200px -80px;} +.icon.operation.delete { background-position: 0px -140px;} +.icon.operation.circularize { background-position: -20px -140px;} +.icon.operation.straighten { background-position: -40px -140px;} +.icon.operation.split { background-position: -60px -140px;} +.icon.operation.unjoin { background-position: -80px -140px;} +.icon.operation.reverse { background-position: -100px -140px;} +.icon.operation.move { background-position: -120px -140px;} +.icon.operation.merge { background-position: -140px -140px;} +.icon.operation.orthogonalize { background-position: -160px -140px;} + + /* Toggle icon is special */ .toggle.icon { background-position: 0px -180px;} a:hover .toggle.icon { background-position: -20px -180px;} diff --git a/img/source/sprite.svg b/img/source/sprite.svg index 8af12723b..25984e5b2 100644 --- a/img/source/sprite.svg +++ b/img/source/sprite.svg @@ -38,22 +38,22 @@ borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" - inkscape:zoom="16" - inkscape:cx="174.72757" - inkscape:cy="45.850291" + inkscape:zoom="1" + inkscape:cx="123.06085" + inkscape:cy="91.554201" inkscape:document-units="px" inkscape:current-layer="layer12" showgrid="true" inkscape:window-width="1280" inkscape:window-height="756" - inkscape:window-x="240" + inkscape:window-x="69" inkscape:window-y="0" inkscape:window-maximized="0" fit-margin-top="0" fit-margin-left="0" fit-margin-right="0" fit-margin-bottom="0" - showguides="true" + showguides="false" inkscape:guide-bbox="true" inkscape:snap-bbox="true" inkscape:snap-nodes="true"> @@ -181,7 +181,7 @@ image/svg+xml - + @@ -822,26 +822,11 @@ transform="matrix(2,0,0,2,-1601,221.27564)" style="display:inline"> + style="color:#000000;fill:#c1c1c1;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + d="M 136 92 L 132 96 L 125 96 L 125 104 L 132 104 L 136 108 L 144 108 L 148 104 L 155 104 L 155 96 L 148 96 L 144 92 L 136 92 z M 140 96 C 142.20912 96 144 97.79086 144 100 C 144 102.20914 142.20912 104 140 104 C 137.79088 104 136 102.20914 136 100 C 136 97.79086 137.79088 96 140 96 z " + transform="matrix(0.5,0,0,0.5,813,-110.63782)" + id="path4439" /> - - Date: Tue, 29 Jan 2013 18:34:40 -0500 Subject: [PATCH 008/415] Micro translation framework. --- index.html | 4 ++++ js/id/ui/inspector.js | 6 +++--- js/id/ui/layerswitcher.js | 6 +++--- js/id/ui/notice.js | 2 +- js/id/ui/save.js | 10 +++++----- js/id/ui/success.js | 4 ++-- js/id/ui/tag_reference.js | 2 +- js/id/ui/userpanel.js | 2 +- locale/en.js | 40 +++++++++++++++++++++++++++++++++++++++ locale/locale.js | 11 +++++++++++ 10 files changed, 71 insertions(+), 16 deletions(-) create mode 100644 locale/en.js create mode 100644 locale/locale.js diff --git a/index.html b/index.html index deadb5add..4b12084e5 100644 --- a/index.html +++ b/index.html @@ -125,9 +125,13 @@ + + +
+ + + + From 8d78aa060bf6d973a092ca1c62b79d1b5e7b3ee1 Mon Sep 17 00:00:00 2001 From: Saman Bemel-Benrud Date: Tue, 29 Jan 2013 18:57:14 -0500 Subject: [PATCH 016/415] fix layout for tag reference image. --- css/app.css | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/css/app.css b/css/app.css index 211efc4df..5064b435e 100644 --- a/css/app.css +++ b/css/app.css @@ -988,9 +988,8 @@ div.typeahead a:first-child { } .modal-section img.wiki-image { - max-width: 400px; + max-width: 100%; max-height: 300px; - padding: 10px; display: block; } From 13e7d03395c458f0f0704a089df8af98a84d6907 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 29 Jan 2013 19:01:49 -0500 Subject: [PATCH 017/415] Fix build --- Makefile | 4 +++- test/index_packaged.html | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 70ac208d6..ddeef62b9 100644 --- a/Makefile +++ b/Makefile @@ -47,7 +47,9 @@ all: \ js/id/svg/*.js \ js/id/ui.js \ js/id/ui/*.js \ - js/id/end.js + js/id/end.js \ + locale/locale.js \ + locale/en.js iD.js: Makefile @rm -f $@ diff --git a/test/index_packaged.html b/test/index_packaged.html index c4eca2cca..7ae72a056 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -34,7 +34,6 @@ - From c787d9f4a21f59091900ae25996a9965141cadf4 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Wed, 30 Jan 2013 10:36:11 -0500 Subject: [PATCH 018/415] Add areas to rendering test --- test/rendering.html | 63 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/test/rendering.html b/test/rendering.html index e5347d0b4..9b6bedfe2 100644 --- a/test/rendering.html +++ b/test/rendering.html @@ -4,6 +4,7 @@ Rendering Tests + @@ -123,5 +124,67 @@ .classed(d.mode, d.mode !== 'base'); }); + + + + + + + +
BaseSelected
+ + + From 0dd7bbc1fff84d00a256804accff64f7411a61a6 Mon Sep 17 00:00:00 2001 From: Saman Bemel-Benrud Date: Wed, 30 Jan 2013 10:39:42 -0500 Subject: [PATCH 019/415] update source graphics. --- img/source/sprite.svg | 16 ++++++++-------- img/sprite.png | Bin 14580 -> 14583 bytes 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/img/source/sprite.svg b/img/source/sprite.svg index 6ea063f7d..4d9d877ea 100644 --- a/img/source/sprite.svg +++ b/img/source/sprite.svg @@ -38,22 +38,22 @@ borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" - inkscape:zoom="4" - inkscape:cx="411.11786" - inkscape:cy="180.48311" + inkscape:zoom="1" + inkscape:cx="257.08321" + inkscape:cy="180.29859" inkscape:document-units="px" inkscape:current-layer="layer12" - showgrid="true" + showgrid="false" inkscape:window-width="1280" - inkscape:window-height="756" - inkscape:window-x="69" + inkscape:window-height="700" + inkscape:window-x="0" inkscape:window-y="0" inkscape:window-maximized="0" fit-margin-top="0" fit-margin-left="0" fit-margin-right="0" fit-margin-bottom="0" - showguides="true" + showguides="false" inkscape:guide-bbox="true" inkscape:snap-bbox="true" inkscape:snap-nodes="true"> @@ -1032,7 +1032,7 @@ d="m 354.5,22.999997 c -1.5,0 -2.5,2 -2.5,3 0,0.666667 0,1.333333 0,2 0,1 1,2.153847 1,2.153847 l 0,0.846153 -1.69231,0.384617 c -1.45419,0.330499 -2.02608,1.236079 -2.15384,2.76923 L 349,35.999998 l 12,0 -0.15385,-1.846154 c -0.12776,-1.533151 -0.69965,-2.438731 -2.15384,-2.76923 L 357,30.999997 l 0,-0.846153 c 0,0 1,-1.153847 1,-2.153847 0,-0.666667 0,-1.333333 0,-2 0,-1 -1,-3 -2.5,-3 z" style="color:#000000;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /> UX% 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 delta 13864 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 kItWuLEn3}E-+q#kpb Date: Wed, 30 Jan 2013 10:45:24 -0500 Subject: [PATCH 020/415] Add background color slider --- test/rendering.html | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/test/rendering.html b/test/rendering.html index 9b6bedfe2..2ec58da95 100644 --- a/test/rendering.html +++ b/test/rendering.html @@ -4,7 +4,6 @@ Rendering Tests - @@ -35,6 +34,20 @@ +
+ + +
+ + + From bfa823f71d500d4b66c14560caadfbf0f0314051 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Wed, 30 Jan 2013 10:57:26 -0500 Subject: [PATCH 021/415] Fix hover --- test/rendering.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/rendering.html b/test/rendering.html index 2ec58da95..196122b2c 100644 --- a/test/rendering.html +++ b/test/rendering.html @@ -119,6 +119,7 @@ }) .enter() .append('td') + .attr('class', function (d) { return d.mode === 'selected' ? 'mode-select' : 'mode-browse'; }) .append('svg') .attr('width', 200) .attr('height', 30) @@ -129,7 +130,7 @@ graph = iD.Graph([a, b, highway]); d3.select(this) - .attr('class', d.mode === 'selected' ? 'mode-select' : 'mode-browse') + .attr('class', 'behavior-hover') .call(vertices, graph, [a, b], filter) .call(lines, graph, [highway], filter) .call(midpoints, graph, [highway], filter) From ec4f4266ade33516ee9cdf6f18da442df855f051 Mon Sep 17 00:00:00 2001 From: Saman Bemel-Benrud Date: Wed, 30 Jan 2013 11:27:03 -0500 Subject: [PATCH 022/415] fix overflow in commit dialog. --- css/app.css | 11 +---------- js/id/ui/commit.js | 1 + 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/css/app.css b/css/app.css index 440bed8e3..f69ba9a07 100644 --- a/css/app.css +++ b/css/app.css @@ -948,11 +948,11 @@ div.typeahead a:first-child { border:1px solid #ccc; background:#fff; max-height: 160px; - overflow: visible; } .commit-modal .warning-section .changeset-list { margin-right: 20px; + overflow-x: visible; } .commit-section.modal-section { @@ -961,15 +961,6 @@ div.typeahead a:first-child { .commit-section.modal-section:last-child { padding-bottom: 20px;} -.commit-modal .changeset-list li { - position: relative; -} - -.commit-modal .changeset-list li button { - position: absolute; - right: -30px; -} - .modal-section { padding: 20px; } diff --git a/js/id/ui/commit.js b/js/id/ui/commit.js index d883b1748..29cf64b2f 100644 --- a/js/id/ui/commit.js +++ b/js/id/ui/commit.js @@ -86,6 +86,7 @@ iD.ui.commit = function(map) { .on('click.cancel', function() { event.cancel(); }); + cancelbutton.append('span').attr('class','icon close icon-pre-text'); cancelbutton.append('span').attr('class','label').text('Cancel'); var warnings = body.selectAll('div.warning-section') From 30d7296a00830ad759a76151c080b06bd782b9ca Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Wed, 30 Jan 2013 11:38:53 -0500 Subject: [PATCH 023/415] Relative path for loader --- js/id/ui/loading.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/id/ui/loading.js b/js/id/ui/loading.js index 23d5b7c2d..ec6568e5d 100644 --- a/js/id/ui/loading.js +++ b/js/id/ui/loading.js @@ -4,7 +4,7 @@ iD.ui.loading = function(message, blocking) { var loadertext = modal.select('.content') .classed('loading-modal', true) .append('div').attr('class','modal-section fillL'); - loadertext.append('img').attr('class','loader').attr('src', '/img/loader.gif'); + loadertext.append('img').attr('class','loader').attr('src', 'img/loader.gif'); loadertext.append('h3').text(message || ''); return modal; From 6576ec650110f146a05d99bac24c1b6bf2d8bc41 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Wed, 30 Jan 2013 11:56:49 -0500 Subject: [PATCH 024/415] Operation sprites --- css/app.css | 20 +++++++++---------- index.html | 4 ++-- js/id/actions/{circular.js => circularize.js} | 2 +- .../{circular.js => circularize.js} | 8 ++++---- js/id/ui/radial_menu.js | 14 ++++++++----- test/index.html | 4 ++-- 6 files changed, 28 insertions(+), 24 deletions(-) rename js/id/actions/{circular.js => circularize.js} (97%) rename js/id/operations/{circular.js => circularize.js} (83%) diff --git a/css/app.css b/css/app.css index f69ba9a07..1da04f481 100644 --- a/css/app.css +++ b/css/app.css @@ -470,15 +470,15 @@ button[disabled] .icon.nearby { background-position: -340px -40px;} .icon.big-inspect { background-position: -160px -80px;} .icon.big-relation { background-position: -200px -80px;} -.icon.operation.delete { background-position: 0px -140px;} -.icon.operation.circularize { background-position: -20px -140px;} -.icon.operation.straighten { background-position: -40px -140px;} -.icon.operation.split { background-position: -60px -140px;} -.icon.operation.unjoin { background-position: -80px -140px;} -.icon.operation.reverse { background-position: -100px -140px;} -.icon.operation.move { background-position: -120px -140px;} -.icon.operation.merge { background-position: -140px -140px;} -.icon.operation.orthogonalize { background-position: -160px -140px;} +.icon-operation-delete { background-position: 0px -140px;} +.icon-operation-circularize { background-position: -20px -140px;} +.icon-operation-straighten { background-position: -40px -140px;} +.icon-operation-split { background-position: -60px -140px;} +.icon-operation-unjoin { background-position: -80px -140px;} +.icon-operation-reverse { background-position: -100px -140px;} +.icon-operation-move { background-position: -120px -140px;} +.icon-operation-merge { background-position: -140px -140px;} +.icon-operation-orthogonalize { background-position: -160px -140px;} /* Toggle icon is special */ @@ -1195,7 +1195,7 @@ a.success-action { fill: rgba(255,255,255,.5); } -.radial-menu image { +.radial-menu .icon { pointer-events: none; } diff --git a/index.html b/index.html index 4b12084e5..fae1fc940 100644 --- a/index.html +++ b/index.html @@ -81,7 +81,7 @@ - + @@ -107,7 +107,7 @@ - + diff --git a/js/id/actions/circular.js b/js/id/actions/circularize.js similarity index 97% rename from js/id/actions/circular.js rename to js/id/actions/circularize.js index da7965c79..edbd0d8b3 100644 --- a/js/id/actions/circular.js +++ b/js/id/actions/circularize.js @@ -1,4 +1,4 @@ -iD.actions.Circular = function(wayId, map) { +iD.actions.Circularize = function(wayId, map) { var action = function(graph) { var way = graph.entity(wayId), diff --git a/js/id/operations/circular.js b/js/id/operations/circularize.js similarity index 83% rename from js/id/operations/circular.js rename to js/id/operations/circularize.js index eb94150fc..49956430e 100644 --- a/js/id/operations/circular.js +++ b/js/id/operations/circularize.js @@ -1,6 +1,6 @@ -iD.operations.Circular = function(entityId, mode) { +iD.operations.Circularize = function(entityId, mode) { var history = mode.map.history(), - action = iD.actions.Circular(entityId, mode.map); + action = iD.actions.Circularize(entityId, mode.map); var operation = function() { var graph = history.graph(), @@ -30,9 +30,9 @@ iD.operations.Circular = function(entityId, mode) { return action.enabled(graph); }; - operation.id = "circular"; + operation.id = "circularize"; operation.key = "O"; - operation.title = "Circular"; + operation.title = "Circularize"; operation.description = "Make this round"; return operation; diff --git a/js/id/ui/radial_menu.js b/js/id/ui/radial_menu.js index f06fa362e..fe0d7ee86 100644 --- a/js/id/ui/radial_menu.js +++ b/js/id/ui/radial_menu.js @@ -50,11 +50,15 @@ iD.ui.RadialMenu = function(operations) { .on('mouseover', mouseover) .on('mouseout', mouseout); - button.append('image') - .attr('width', 16) - .attr('height', 16) - .attr('transform', 'translate(-8, -8)') - .attr('xlink:href', 'icons/helipad.png'); + var image = button.append('foreignObject') + .style('pointer-events', 'none') + .attr('width', 20) + .attr('height', 20) + .attr('x', -10) + .attr('y', -10); + + image.append('xhtml:span') + .attr('class', function (d) { return 'icon icon-operation icon-operation-' + d.id; }); var tooltip = menu.append('foreignObject') .style('display', 'none') diff --git a/test/index.html b/test/index.html index b682f2cdd..4bdd4b5ba 100644 --- a/test/index.html +++ b/test/index.html @@ -72,7 +72,7 @@ - + @@ -102,7 +102,7 @@ - + From 217577da95f2d8e0b4f195259ab526b84f88a366 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Wed, 30 Jan 2013 12:02:49 -0500 Subject: [PATCH 025/415] Exit MoveWay mode on undo (fixes #562) --- js/id/modes/move_way.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/js/id/modes/move_way.js b/js/id/modes/move_way.js index 7fe07d1ce..1f559da82 100644 --- a/js/id/modes/move_way.js +++ b/js/id/modes/move_way.js @@ -42,10 +42,16 @@ iD.modes.MoveWay = function(wayId) { controller.enter(iD.modes.Select(way, true)); } + function undone() { + controller.enter(iD.modes.Browse()); + } + selection .on('mousemove.move-way', move) .on('click.move-way', finish); + history.on('undone.move-way', undone); + keybinding .on('⎋', cancel) .on('↩', finish); @@ -56,12 +62,15 @@ iD.modes.MoveWay = function(wayId) { mode.exit = function() { var map = mode.map, + history = mode.history, selection = map.surface; selection .on('mousemove.move-way', null) .on('click.move-way', null); + history.on('undone.move-way', null); + keybinding.off(); }; From ba68a238fe2cd70e7c08382c9c08f6afca8a8542 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Wed, 30 Jan 2013 12:26:40 -0500 Subject: [PATCH 026/415] Working rebase --- js/id/graph/graph.js | 90 ++++++++++++++++++++++------------------ js/id/graph/history.js | 12 +----- test/spec/graph/graph.js | 35 ++++++++++------ 3 files changed, 74 insertions(+), 63 deletions(-) diff --git a/js/id/graph/graph.js b/js/id/graph/graph.js index 2705fd029..76842cbf5 100644 --- a/js/id/graph/graph.js +++ b/js/id/graph/graph.js @@ -5,15 +5,19 @@ iD.Graph = function(other, mutable) { var base = other.base(); this.entities = _.assign(Object.create(base.entities), other.entities); this._parentWays = _.assign(Object.create(base.parentWays), other._parentWays); - this.rebase(other.base(), other.entities); + this.inherited = true; } else { - this.entities = other || Object.create({}); - this._parentWays = Object.create({}); - - for (var i in this.entities) { - this._updateCalculated(undefined, this.entities[i]); + if (_.isArray(other)) { + var entities = {}; + for (var i = 0; i < other.length; i++) { + entities[other[i].id] = other[i]; + } + other = entities; } + this.entities = Object.create({}); + this._parentWays = Object.create({}); + this.rebase(other || {}); } this.transients = {}; @@ -43,25 +47,7 @@ iD.Graph.prototype = { }, parentWays: function(entity) { - var ent, id, parents; - - if (!this._parentWays.calculated) { - for (var i in this.entities) { - ent = this.entities[i]; - if (ent && ent.type === 'way') { - for (var j = 0; j < ent.nodes.length; j++) { - id = ent.nodes[j]; - parents = this._parentWays[id] = this._parentWays[id] || []; - if (parents.indexOf(ent) < 0) { - parents.push(ent); - } - } - } - } - this._parentWays.calculated = true; - } - - return this._parentWays[entity.id] || []; + return _.map(this._parentWays[entity.id], _.bind(this.entity, this)); }, isPoi: function(entity) { @@ -113,13 +99,31 @@ iD.Graph.prototype = { // is used only during the history operation that merges newly downloaded // data into each state. To external consumers, it should appear as if the // graph always contained the newly downloaded data. - rebase: function(base, entities) { + rebase: function(entities) { + var i; + // Merging of data only needed if graph is the base graph + if (!this.inherited) { + _.defaults(this.base().entities, entities); + for (i in entities) { + this._updateCalculated(undefined, entities[i], this.base().parentWays); + } + } + + var keys = Object.keys(this._parentWays); + for (i = 0; i < keys.length; i++) { + this._parentWays[keys[i]] = _.unique((this._parentWays[keys[i]] || []) + .concat(this.base().parentWays[keys[i]] || [])); + } }, - _updateCalculated: function(oldentity, entity) { + // Updates calculated properties (parentWays, parentRels) for the specified change + _updateCalculated: function(oldentity, entity, parentWays, parentRels) { + + parentWays = parentWays || this._parentWays; + parentRels = parentRels || this._parentRels; var type = entity && entity.type || oldentity && oldentity.type, - removed, added, parentWays, i; + removed, added, ways, i; if (type === 'way') { @@ -136,12 +140,12 @@ iD.Graph.prototype = { added = entity.nodes; } for (i = 0; i < removed.length; i++) { - this._parentWays[removed[i]] = _.without(this._parentWays[removed[i]], oldentity.id); + parentWays[removed[i]] = _.without(parentWays[removed[i]], oldentity.id); } for (i = 0; i < added.length; i++) { - parentWays = _.without(this._parentWays[added[i]], entity.id); - parentWays.push(entity.id); - this._parentWays[added[i]] = parentWays; + ways = _.without(parentWays[added[i]], entity.id); + ways.push(entity.id); + parentWays[added[i]] = ways; } } else if (type === 'node') { @@ -199,9 +203,12 @@ iD.Graph.prototype = { }, difference: function (graph) { - var result = [], entity, oldentity, id; + var result = [], + keys = Object.keys(this.entities), + entity, oldentity, id, i; - for (id in this.entities) { + for (i = 0; i < keys.length; i++) { + id = keys[i]; entity = this.entities[id]; oldentity = graph.entities[id]; if (entity !== oldentity) { @@ -223,7 +230,8 @@ iD.Graph.prototype = { } } - for (id in graph.entities) { + keys = Object.keys(graph.entities); + for (i = 0; i < keys.length; i++) { entity = graph.entities[id]; if (entity && !this.entities.hasOwnProperty(id)) { result.push(id); @@ -235,25 +243,25 @@ iD.Graph.prototype = { }, modified: function() { - var result = []; + var result = [], base = this.base().entities; _.each(this.entities, function(entity, id) { - if (entity && entity.modified()) result.push(id); + if (entity && base[id]) result.push(id); }); return result; }, created: function() { - var result = []; + var result = [], base = this.base().entities; _.each(this.entities, function(entity, id) { - if (entity && entity.created()) result.push(id); + if (entity && !base[id]) result.push(id); }); return result; }, deleted: function() { - var result = []; + var result = [], base = this.base().entities; _.each(this.entities, function(entity, id) { - if (!entity) result.push(id); + if (!entity && base[id]) result.push(id); }); return result; } diff --git a/js/id/graph/history.js b/js/id/graph/history.js index 95e499ef2..3630bb915 100644 --- a/js/id/graph/history.js +++ b/js/id/graph/history.js @@ -30,16 +30,8 @@ iD.History = function() { }, merge: function (graph) { - var base = stack[0].graph.base(); - - _.defaults(base.entities, graph.entities); - for (var i in graph.entities) { - base.parentWays[i] = _.unique((base.parentWays[id] || []).concat(graph._parentWays[i] || [])); - } - _.defaults(base.parentWays, graph._parentWays); - - for (i = 1; i < stack.length; i++) { - stack[i].graph.rebase(base); + for (var i = 0; i < stack.length; i++) { + stack[i].graph.rebase(graph.base().entities); } }, diff --git a/test/spec/graph/graph.js b/test/spec/graph/graph.js index fa83a3a03..61b620ef3 100644 --- a/test/spec/graph/graph.js +++ b/test/spec/graph/graph.js @@ -28,7 +28,7 @@ describe('iD.Graph', function() { it("rebases on other's base", function () { var base = iD.Graph(), graph = iD.Graph(base); - expect(graph.base()).to.equal(base.base()); + expect(graph.base().entities).to.equal(base.base().entities); }); it("freezes by default", function () { @@ -81,16 +81,27 @@ describe('iD.Graph', function() { expect(graph.entities).not.to.have.ownProperty('n'); }); - xit("updates parentWays", function () { + it("updates parentWays", function () { var n = iD.Node({id: 'n'}), w1 = iD.Way({id: 'w1', nodes: ['n']}), w2 = iD.Way({id: 'w2', nodes: ['n']}), + w3 = iD.Way({id: 'w3', nodes: ['n']}), graph = iD.Graph([n, w1]); - graph.parentWays(n); - graph.rebase({'w2': w2}); + var graph2 = graph.replace(w2); + graph.rebase({ 'w3': w3 }); + graph2.rebase({ 'w3': w3 }); - expect(graph.parentWays(n)).to.eql([w1, w2]); + expect(graph2.parentWays(n)).to.eql([w1, w2, w3]); + }); + + it("only sets non-base parentWays when necessary", function () { + var n = iD.Node({id: 'n'}), + w1 = iD.Way({id: 'w1', nodes: ['n']}), + graph = iD.Graph([n, w1]); + graph.rebase({ 'w1': w1 }); + expect(graph._parentWays.hasOwnProperty('n')).to.be.false; + expect(graph.parentWays(n)).to.eql([w1]); }); xit("updates parentRelations", function () { @@ -244,18 +255,18 @@ describe('iD.Graph', function() { describe("#modified", function () { it("returns an Array of ids of modified entities", function () { - var node1 = iD.Node({id: 'n1', _updated: true}), - node2 = iD.Node({id: 'n2'}), - graph = iD.Graph([node1, node2]); - expect(graph.modified()).to.eql([node1.id]); + var node = iD.Node({id: 'n1'}), + node_ = iD.Node({id: 'n1'}), + graph = iD.Graph([node]).replace(node_); + expect(graph.modified()).to.eql([node.id]); }); }); describe("#created", function () { it("returns an Array of ids of created entities", function () { - var node1 = iD.Node({id: 'n-1', _updated: true}), + var node1 = iD.Node({id: 'n-1'}), node2 = iD.Node({id: 'n2'}), - graph = iD.Graph([node1, node2]); + graph = iD.Graph([node2]).replace(node1); expect(graph.created()).to.eql([node1.id]); }); }); @@ -270,7 +281,7 @@ describe('iD.Graph', function() { it("doesn't include created entities that were subsequently deleted", function () { var node = iD.Node(), - graph = iD.Graph([node]).remove(node); + graph = iD.Graph().replace(node).remove(node); expect(graph.deleted()).to.eql([]); }); }); From e08364d492685224ef3abeb5db60851be053bc91 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Wed, 30 Jan 2013 13:18:44 -0500 Subject: [PATCH 027/415] working parentRels --- js/id/graph/graph.js | 57 ++++++++++++++++++++++++---------------- test/spec/graph/graph.js | 40 +++++++++++++++++++--------- 2 files changed, 61 insertions(+), 36 deletions(-) diff --git a/js/id/graph/graph.js b/js/id/graph/graph.js index 76842cbf5..055ab8df8 100644 --- a/js/id/graph/graph.js +++ b/js/id/graph/graph.js @@ -5,6 +5,7 @@ iD.Graph = function(other, mutable) { var base = other.base(); this.entities = _.assign(Object.create(base.entities), other.entities); this._parentWays = _.assign(Object.create(base.parentWays), other._parentWays); + this._parentRels = _.assign(Object.create(base.parentRels), other._parentRels); this.inherited = true; } else { @@ -17,11 +18,11 @@ iD.Graph = function(other, mutable) { } this.entities = Object.create({}); this._parentWays = Object.create({}); + this._parentRels = Object.create({}); this.rebase(other || {}); } this.transients = {}; - this._parentRels = {}; this._childNodes = {}; if (!mutable) { @@ -55,23 +56,9 @@ iD.Graph.prototype = { }, parentRelations: function(entity) { + return _.map(this._parentRels[entity.id], _.bind(this.entity, this)); var ent, id, parents; - if (!this._parentRels.calculated) { - for (var i in this.entities) { - ent = this.entities[i]; - if (ent && ent.type === 'relation') { - for (var j = 0; j < ent.members.length; j++) { - id = ent.members[j].id; - parents = this._parentRels[id] = this._parentRels[id] || []; - if (parents.indexOf(ent) < 0) { - parents.push(ent); - } - } - } - } - this._parentRels.calculated = true; - } return this._parentRels[entity.id] || []; }, @@ -91,7 +78,8 @@ iD.Graph.prototype = { base: function() { return { 'entities': iD.util.getPrototypeOf(this.entities), - 'parentWays': iD.util.getPrototypeOf(this._parentWays) + 'parentWays': iD.util.getPrototypeOf(this._parentWays), + 'parentRels': iD.util.getPrototypeOf(this._parentRels) }; }, @@ -100,20 +88,26 @@ iD.Graph.prototype = { // data into each state. To external consumers, it should appear as if the // graph always contained the newly downloaded data. rebase: function(entities) { - var i; + var i, keys; // Merging of data only needed if graph is the base graph if (!this.inherited) { _.defaults(this.base().entities, entities); for (i in entities) { - this._updateCalculated(undefined, entities[i], this.base().parentWays); + this._updateCalculated(undefined, entities[i], this.base().parentWays, this.base().parentRels); } } - var keys = Object.keys(this._parentWays); + keys = Object.keys(this._parentWays); for (i = 0; i < keys.length; i++) { this._parentWays[keys[i]] = _.unique((this._parentWays[keys[i]] || []) .concat(this.base().parentWays[keys[i]] || [])); } + + keys = Object.keys(this._parentRels); + for (i = 0; i < keys.length; i++) { + this._parentRels[keys[i]] = _.unique((this._parentRels[keys[i]] || []) + .concat(this.base().parentRels[keys[i]] || [])); + } }, // Updates calculated properties (parentWays, parentRels) for the specified change @@ -123,7 +117,7 @@ iD.Graph.prototype = { parentRels = parentRels || this._parentRels; var type = entity && entity.type || oldentity && oldentity.type, - removed, added, ways, i; + removed, added, ways, rels, i; if (type === 'way') { @@ -151,8 +145,25 @@ iD.Graph.prototype = { } else if (type === 'relation') { - // TODO: iterate over members - + // Update parentRels + if (oldentity && entity) { + removed = _.difference(oldentity.members, entity.members); + added = _.difference(entity.members, oldentity); + } else if (oldentity) { + removed = oldentity.members; + added = []; + } else if (entity) { + removed = []; + added = entity.members; + } + for (i = 0; i < removed.length; i++) { + parentRels[removed[i].id] = _.without(parentRels[removed[i].id], oldentity.id); + } + for (i = 0; i < added.length; i++) { + rels = _.without(parentRels[added[i].id], entity.id); + rels.push(entity.id); + parentRels[added[i].id] = rels; + } } }, diff --git a/test/spec/graph/graph.js b/test/spec/graph/graph.js index 61b620ef3..9d918ae04 100644 --- a/test/spec/graph/graph.js +++ b/test/spec/graph/graph.js @@ -85,35 +85,49 @@ describe('iD.Graph', function() { var n = iD.Node({id: 'n'}), w1 = iD.Way({id: 'w1', nodes: ['n']}), w2 = iD.Way({id: 'w2', nodes: ['n']}), - w3 = iD.Way({id: 'w3', nodes: ['n']}), graph = iD.Graph([n, w1]); - var graph2 = graph.replace(w2); + graph.rebase({ 'w2': w2 }); + expect(graph.parentWays(n)).to.eql([w1, w2]); + expect(graph._parentWays.hasOwnProperty('n')).to.be.false; + }); + + it("updates parentWays for nodes with modified parentWays", function () { + var n = iD.Node({id: 'n'}), + w1 = iD.Way({id: 'w1', nodes: ['n']}), + w2 = iD.Way({id: 'w2', nodes: ['n']}), + w3 = iD.Way({id: 'w3', nodes: ['n']}), + graph = iD.Graph([n, w1]), + graph2 = graph.replace(w2); graph.rebase({ 'w3': w3 }); graph2.rebase({ 'w3': w3 }); expect(graph2.parentWays(n)).to.eql([w1, w2, w3]); }); - it("only sets non-base parentWays when necessary", function () { - var n = iD.Node({id: 'n'}), - w1 = iD.Way({id: 'w1', nodes: ['n']}), - graph = iD.Graph([n, w1]); - graph.rebase({ 'w1': w1 }); - expect(graph._parentWays.hasOwnProperty('n')).to.be.false; - expect(graph.parentWays(n)).to.eql([w1]); - }); - - xit("updates parentRelations", function () { + it("updates parentRelations", function () { var n = iD.Node({id: 'n'}), r1 = iD.Relation({id: 'r1', members: [{id: 'n'}]}), r2 = iD.Relation({id: 'r2', members: [{id: 'n'}]}), graph = iD.Graph([n, r1]); - graph.parentRelations(n); graph.rebase({'r2': r2}); expect(graph.parentRelations(n)).to.eql([r1, r2]); + expect(graph._parentRels.hasOwnProperty('n')).to.be.false; + }); + + it("updates parentWays for nodes with modified parentWays", function () { + var n = iD.Node({id: 'n'}), + r1 = iD.Relation({id: 'r1', members: [{id: 'n'}]}), + r2 = iD.Relation({id: 'r2', members: [{id: 'n'}]}), + r3 = iD.Relation({id: 'r3', members: [{id: 'n'}]}), + graph = iD.Graph([n, r1]), + graph2 = graph.replace(r2); + + graph.rebase({'r3': r3}); + graph2.rebase({'r3': r3}); + expect(graph2.parentRelations(n)).to.eql([r1, r2, r3]); }); }); From 5a35b6bfa4c84509ffa2d41ddb7a641c90fafe87 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Wed, 30 Jan 2013 14:11:54 -0500 Subject: [PATCH 028/415] Fix difference --- js/id/graph/graph.js | 1 + 1 file changed, 1 insertion(+) diff --git a/js/id/graph/graph.js b/js/id/graph/graph.js index 055ab8df8..f2525daf5 100644 --- a/js/id/graph/graph.js +++ b/js/id/graph/graph.js @@ -243,6 +243,7 @@ iD.Graph.prototype = { keys = Object.keys(graph.entities); for (i = 0; i < keys.length; i++) { + id = keys[i]; entity = graph.entities[id]; if (entity && !this.entities.hasOwnProperty(id)) { result.push(id); From 1b5a1b8268675f53fce8b9138550a063e3d6174e Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Wed, 30 Jan 2013 14:20:39 -0500 Subject: [PATCH 029/415] Merge takes an entities object, not graph --- js/id/connection.js | 2 +- js/id/graph/history.js | 4 ++-- js/id/renderer/map.js | 2 +- test/spec/connection.js | 8 ++++---- test/spec/graph/history.js | 12 +++++------- 5 files changed, 13 insertions(+), 15 deletions(-) diff --git a/js/id/connection.js b/js/id/connection.js index 53c96bd96..5eca647cd 100644 --- a/js/id/connection.js +++ b/js/id/connection.js @@ -120,7 +120,7 @@ iD.Connection = function() { } } - return iD.Graph(entities); + return entities; } function authenticated() { diff --git a/js/id/graph/history.js b/js/id/graph/history.js index 3630bb915..42e1b1604 100644 --- a/js/id/graph/history.js +++ b/js/id/graph/history.js @@ -29,9 +29,9 @@ iD.History = function() { return stack[index].graph; }, - merge: function (graph) { + merge: function (entities) { for (var i = 0; i < stack.length; i++) { - stack[i].graph.rebase(graph.base().entities); + stack[i].graph.rebase(entities); } }, diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index bc4ca8e03..139abb4c7 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -123,7 +123,7 @@ iD.Map = function() { function connectionLoad(err, result) { history.merge(result); - redraw(Object.keys(result.entities)); + redraw(Object.keys(result)); } function zoomPan() { diff --git a/test/spec/connection.js b/test/spec/connection.js index 2f5d93808..652c1419b 100644 --- a/test/spec/connection.js +++ b/test/spec/connection.js @@ -26,24 +26,24 @@ describe('iD.Connection', function () { c.loadFromURL('data/node.xml', done); }); - it('returns a graph', function (done) { + it('returns an object', function (done) { c.loadFromURL('data/node.xml', function (err, graph) { expect(err).to.not.be.ok; - expect(graph).to.be.instanceOf(iD.Graph); + expect(typeof graph).to.eql('object'); done(); }); }); it('parses a node', function (done) { c.loadFromURL('data/node.xml', function (err, graph) { - expect(graph.entity('n356552551')).to.be.instanceOf(iD.Entity); + expect(graph.n356552551).to.be.instanceOf(iD.Entity); done(); }); }); it('parses a way', function (done) { c.loadFromURL('data/way.xml', function (err, graph) { - expect(graph.entity('w19698713')).to.be.instanceOf(iD.Entity); + expect(graph.w19698713).to.be.instanceOf(iD.Entity); done(); }); }); diff --git a/test/spec/graph/history.js b/test/spec/graph/history.js index b59190c0f..dc685b446 100644 --- a/test/spec/graph/history.js +++ b/test/spec/graph/history.js @@ -153,17 +153,15 @@ describe("iD.History", function () { it("includes modified entities", function () { var node1 = iD.Node({id: "n1"}), - node2 = node1.update({}), - graph = iD.Graph([node1]); - history.merge(graph); + node2 = node1.update({}); + history.merge({ n1: node1}); history.perform(function (graph) { return graph.replace(node2); }); expect(history.changes().modified).to.eql([node2]); }); it("includes deleted entities", function () { - var node = iD.Node({id: "n1"}), - graph = iD.Graph([node]); - history.merge(graph); + var node = iD.Node({id: "n1"}); + history.merge({ n1: node }); history.perform(function (graph) { return graph.remove(node); }); expect(history.changes().deleted).to.eql([node]); }); @@ -189,7 +187,7 @@ describe("iD.History", function () { it("is the sum of all types of changes", function() { var node1 = iD.Node({id: "n1"}), node2 = iD.Node(); - history.merge(iD.Graph([node1])); + history.merge({ n1: node1 }); history.perform(function (graph) { return graph.remove(node1); }); expect(history.numChanges()).to.eql(1); history.perform(function (graph) { return graph.replace(node2); }); From b50160b4d9607380500f2c39484d589da6751289 Mon Sep 17 00:00:00 2001 From: Saman Bemel-Benrud Date: Wed, 30 Jan 2013 15:29:10 -0500 Subject: [PATCH 030/415] more screwing around with button styles. --- css/app.css | 50 ++++++++++------- img/source/sprite.svg | 127 ++++++++++++++---------------------------- img/sprite.png | Bin 14583 -> 14025 bytes js/id/ui/commit.js | 1 - js/id/ui/inspector.js | 6 +- 5 files changed, 77 insertions(+), 107 deletions(-) diff --git a/css/app.css b/css/app.css index 1da04f481..0ba223a99 100644 --- a/css/app.css +++ b/css/app.css @@ -283,11 +283,11 @@ button.minor { width: 20px; border: 0; box-shadow: none; - background-color: transparent; + background: rgba(0,0,0,.5); } button.minor:hover { - background: white; + background: #222; } button.centered { @@ -319,6 +319,8 @@ button.centered { border-radius:0 4px 4px 0; } +button.Browse .label { display: none;} + button.action { background: #7092ff; } @@ -431,13 +433,13 @@ button[disabled] .label { .icon.inspect { background-position: -220px 0px;} .icon.zoom-in { background-position: -240px 0px;} .icon.zoom-out { background-position: -260px 0px;} +.icon.plus { background-position: -240px 0px;} .icon.geocode { background-position: -280px 0px;} .icon.layers { background-position: -300px 0px;} .icon.avatar { background-position: -320px 0px;} .icon.nearby { background-position: -340px 0px;} .icon.geolocate { background-position: -360px 0px;} .icon.warning { background-position: -380px 0px;} -.icon.plus { background-position: -400px 0px;} .icon.close-modal { background-position: -200px -40px;} @@ -613,16 +615,20 @@ a.selected:hover .toggle.icon { background-position: -40px -180px;} } .inspector-inner .add-tag { - width: 40%; + width: 20%; height: 30px; - border: 1px solid #ccc; border-top: 0; - background: white; + background: rgba(0,0,0,.5); border-radius: 0 0 4px 4px; - } - .inspector-inner .add-tag:hover { - background: #ececec; - } +} +.inspector-inner .add-tag:hover { + background: rgba(0,0,0,.8); +} + +.inspector-inner .add-tag .label { + display: none; +} + /* Map Controls */ .map-control { @@ -632,12 +638,13 @@ a.selected:hover .toggle.icon { background-position: -40px -180px;} .map-control button { width: 40px; - background: rgba(0,0,0,.8); - border-radius: 0 4px 4px 0; + background: rgba(0,0,0,.5); + border-radius: 0; + border-bottom: 1px solid rgba(0, 0, 0, 1); } .map-control button:hover { - background: rgba(0, 0, 0, .9); + background: rgba(0, 0, 0, .8); } .map-control button.active:hover { @@ -662,18 +669,16 @@ a.selected:hover .toggle.icon { background-position: -40px -180px;} .zoombuttons button.zoom-in { border-radius:0 4px 0 0; - border-bottom: 1px solid rgba(0, 0, 0, .5); } .zoombuttons button.zoom-out { border-top:0; - border-radius:0 0 4px 0; } /* Layer Switcher */ .layerswitcher-control { - top:210px; + top:190px; } .layerswitcher-control .adjustments button { @@ -744,7 +749,7 @@ a.selected:hover .toggle.icon { background-position: -40px -180px;} /* Geocoder */ .geocode-control { - top:160px; + top:150px; } .geocode-control input { @@ -753,8 +758,15 @@ a.selected:hover .toggle.icon { background-position: -40px -180px;} margin: 4px; } +/* Geolocator */ + .geolocate-control { - top:260px; + top:230px; +} + +.geolocate-control button { + border-radius: 0 0 4px 0; + border-bottom: 0; } /* Map @@ -1214,5 +1226,5 @@ a.success-action { span.label {display: none;} /* override hide for save button */ .icon.icon-pre-text { margin-right: 0px;} - .save .label { display: block;} + .save .label, .apply .label { display: block;} } diff --git a/img/source/sprite.svg b/img/source/sprite.svg index 4d9d877ea..4cc5abb47 100644 --- a/img/source/sprite.svg +++ b/img/source/sprite.svg @@ -38,9 +38,9 @@ borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" - inkscape:zoom="1" - inkscape:cx="257.08321" - inkscape:cy="180.29859" + inkscape:zoom="4" + inkscape:cx="230.7911" + inkscape:cy="190.13176" inkscape:document-units="px" inkscape:current-layer="layer12" showgrid="false" @@ -53,7 +53,7 @@ fit-margin-left="0" fit-margin-right="0" fit-margin-bottom="0" - showguides="false" + showguides="true" inkscape:guide-bbox="true" inkscape:snap-bbox="true" inkscape:snap-nodes="true"> @@ -279,6 +279,11 @@ id="layer12" inkscape:label="sprite" transform="translate(-25,3.0625001e-6)"> + - - - - - - - - - - - - - - - - + + + + + + + diff --git a/img/sprite.png b/img/sprite.png index 3c4a93b63c75d86cae923c21d238c571aa4570fc..b0f45f2cd6b06d74235373880618d7a550ef047f 100644 GIT binary patch literal 14025 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 Date: Wed, 30 Jan 2013 15:44:03 -0500 Subject: [PATCH 031/415] Don't re-add removed parentways, don't duplicate --- js/id/graph/graph.js | 28 +++++++++++++++++++++------- test/spec/graph/graph.js | 23 ++++++++++++++++++++++- 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/js/id/graph/graph.js b/js/id/graph/graph.js index f2525daf5..5ef0f54b1 100644 --- a/js/id/graph/graph.js +++ b/js/id/graph/graph.js @@ -88,25 +88,39 @@ iD.Graph.prototype = { // data into each state. To external consumers, it should appear as if the // graph always contained the newly downloaded data. rebase: function(entities) { - var i, keys; + var base = this.base(), + i, k, child, id, keys; // Merging of data only needed if graph is the base graph if (!this.inherited) { - _.defaults(this.base().entities, entities); for (i in entities) { - this._updateCalculated(undefined, entities[i], this.base().parentWays, this.base().parentRels); + if (!base.entities[i]) { + base.entities[i] = entities[i]; + this._updateCalculated(undefined, entities[i], + base.parentWays, base.parentRels); + } } } keys = Object.keys(this._parentWays); for (i = 0; i < keys.length; i++) { - this._parentWays[keys[i]] = _.unique((this._parentWays[keys[i]] || []) - .concat(this.base().parentWays[keys[i]] || [])); + child = keys[i]; + for (k = 0; k < base.parentWays[child].length; k++) { + id = base.parentWays[child][k]; + if (this.entity(id) && !_.contains(this._parentWays[child], id)) { + this._parentWays[child].push(id); + } + } } keys = Object.keys(this._parentRels); for (i = 0; i < keys.length; i++) { - this._parentRels[keys[i]] = _.unique((this._parentRels[keys[i]] || []) - .concat(this.base().parentRels[keys[i]] || [])); + child = keys[i]; + for (k = 0; k < base.parentRels[child].length; k++) { + id = base.parentRels[child][k]; + if (this.entity(id) && !_.contains(this._parentRels[child], id)) { + this._parentRels[child].push(id); + } + } } }, diff --git a/test/spec/graph/graph.js b/test/spec/graph/graph.js index 9d918ae04..667cfe149 100644 --- a/test/spec/graph/graph.js +++ b/test/spec/graph/graph.js @@ -105,6 +105,16 @@ describe('iD.Graph', function() { expect(graph2.parentWays(n)).to.eql([w1, w2, w3]); }); + it("avoids re-adding removed parentWays", function() { + var n = iD.Node({id: 'n'}), + w1 = iD.Way({id: 'w1', nodes: ['n']}), + graph = iD.Graph([n, w1]), + graph2 = graph.remove(w1); + graph.rebase({ 'w1': w1 }); + graph2.rebase({ 'w1': w1 }); + expect(graph2.parentWays(n)).to.eql([]); + }); + it("updates parentRelations", function () { var n = iD.Node({id: 'n'}), r1 = iD.Relation({id: 'r1', members: [{id: 'n'}]}), @@ -117,7 +127,17 @@ describe('iD.Graph', function() { expect(graph._parentRels.hasOwnProperty('n')).to.be.false; }); - it("updates parentWays for nodes with modified parentWays", function () { + it("avoids re-adding removed parentRels", function() { + var n = iD.Node({id: 'n'}), + r1 = iD.Relation({id: 'r1', members: [{id: 'n'}]}), + graph = iD.Graph([n, r1]), + graph2 = graph.remove(r1); + graph.rebase({ 'w1': r1 }); + graph2.rebase({ 'w1': r1 }); + expect(graph2.parentWays(n)).to.eql([]); + }); + + it("updates parentRels for nodes with modified parentWays", function () { var n = iD.Node({id: 'n'}), r1 = iD.Relation({id: 'r1', members: [{id: 'n'}]}), r2 = iD.Relation({id: 'r2', members: [{id: 'n'}]}), @@ -129,6 +149,7 @@ describe('iD.Graph', function() { graph2.rebase({'r3': r3}); expect(graph2.parentRelations(n)).to.eql([r1, r2, r3]); }); + }); describe("#remove", function () { From 459dc00ce51c8d8868f7f8e98d19b687dd9ab7e4 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Wed, 30 Jan 2013 12:42:34 -0500 Subject: [PATCH 032/415] Change the midpoint data a bit Store the ways which share the segment and the index of the segment. This will be used in both DragWay and Draw behaviors. --- js/id/behavior/drag_midpoint.js | 34 ++++++++++----------- js/id/svg/midpoints.js | 20 ++++++------- test/index.html | 1 + test/index_packaged.html | 1 + test/spec/svg/midpoints.js | 52 +++++++++++++++++++++++++++++++++ 5 files changed, 79 insertions(+), 29 deletions(-) create mode 100644 test/spec/svg/midpoints.js diff --git a/js/id/behavior/drag_midpoint.js b/js/id/behavior/drag_midpoint.js index 9f73a7115..e0b0c7a1a 100644 --- a/js/id/behavior/drag_midpoint.js +++ b/js/id/behavior/drag_midpoint.js @@ -1,29 +1,26 @@ iD.behavior.DragMidpoint = function(mode) { var history = mode.history, - projection = mode.map.projection, - behavior = iD.behavior.drag() + projection = mode.map.projection; + + var behavior = iD.behavior.drag() .delegate(".midpoint") .origin(function(d) { return projection(d.loc); }) .on('start', function(d) { - var w, nds; - d.node = iD.Node({loc: d.loc}); - var args = [iD.actions.AddNode(d.node)]; - for (var i = 0; i < d.ways.length; i++) { - w = d.ways[i], nds = w.nodes; - for (var j = 0; j < nds.length; j++) { - if ((nds[j] === d.nodes[0] && nds[j + 1] === d.nodes[1]) || - (nds[j] === d.nodes[1] && nds[j + 1] === d.nodes[0])) { - args.push(iD.actions.AddWayNode(w.id, d.node.id, j + 1)); - } - } - } - history.perform.apply(history, args); - var node = d3.selectAll('.node.vertex') - .filter(function(data) { return data.id === d.node.id; }); - behavior.target(node.node(), node.datum()); + var node = iD.Node({loc: d.loc}); + var args = [iD.actions.AddNode(node)]; + for (var i = 0; i < d.ways.length; i++) { + args.push(iD.actions.AddWayNode(d.ways[i].id, node.id, d.ways[i].index)); + } + + history.perform.apply(history, args); + + var vertex = d3.selectAll('.vertex') + .filter(function(data) { return data.id === node.id; }); + + behavior.target(vertex.node(), vertex.datum()); }) .on('move', function(d) { d3.event.sourceEvent.stopPropagation(); @@ -35,5 +32,6 @@ iD.behavior.DragMidpoint = function(mode) { iD.actions.Noop(), 'added a node to a way'); }); + return behavior; }; diff --git a/js/id/svg/midpoints.js b/js/id/svg/midpoints.js index 655e35013..da4af659c 100644 --- a/js/id/svg/midpoints.js +++ b/js/id/svg/midpoints.js @@ -15,19 +15,17 @@ iD.svg.Midpoints = function(projection) { b = nodes[j + 1], id = [a.id, b.id].sort().join('-'); - if (!midpoints[id] && - iD.geo.dist(projection(a.loc), projection(b.loc)) > 40) { - - var midpoint_loc = iD.geo.interp(a.loc, b.loc, 0.5), - parents = _.intersection(graph.parentWays(a), - graph.parentWays(b)); + if (midpoints[id]) { + midpoints[id].ways.push({id: entity.id, index: j + 1}); + } else if (iD.geo.dist(projection(a.loc), projection(b.loc)) > 40) { midpoints[id] = { - loc: midpoint_loc, - ways: parents, - nodes: [a.id, b.id], + midpoint: true, id: id, - midpoint: true + loc: iD.geo.interp(a.loc, b.loc, 0.5), + a: a.id, + b: b.id, + ways: [{id: entity.id, index: j + 1}] }; } } @@ -35,7 +33,7 @@ iD.svg.Midpoints = function(projection) { var groups = surface.select('.layer-hit').selectAll('g.midpoint') .filter(filter) - .data(_.values(midpoints), function (d) { return [d.parents, d.id].join(","); }); + .data(_.values(midpoints), function (d) { return d.id; }); var group = groups.enter() .insert('g', ':first-child') diff --git a/test/index.html b/test/index.html index 4bdd4b5ba..97cd63b9b 100644 --- a/test/index.html +++ b/test/index.html @@ -170,6 +170,7 @@ + diff --git a/test/index_packaged.html b/test/index_packaged.html index 7ae72a056..25093d5b3 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -65,6 +65,7 @@ + diff --git a/test/spec/svg/midpoints.js b/test/spec/svg/midpoints.js new file mode 100644 index 000000000..c34ac0350 --- /dev/null +++ b/test/spec/svg/midpoints.js @@ -0,0 +1,52 @@ +describe("iD.svg.Midpoints", function () { + var surface, + projection = Object, + filter = d3.functor(true); + + beforeEach(function () { + surface = d3.select(document.createElementNS('http://www.w3.org/2000/svg', 'svg')) + .call(iD.svg.Surface()); + }); + + it("finds the location of the midpoints", function () { + var a = iD.Node({loc: [0, 0]}), + b = iD.Node({loc: [50, 0]}), + line = iD.Way({nodes: [a.id, b.id]}), + graph = iD.Graph([a, b, line]); + + surface.call(iD.svg.Midpoints(projection), graph, [line], filter); + + expect(surface.select('.midpoint').datum().loc).to.eql([25, 0]); + }); + + it("doesn't create midpoints on segments with pixel length less than 40", function () { + var a = iD.Node({loc: [0, 0]}), + b = iD.Node({loc: [39, 0]}), + line = iD.Way({nodes: [a.id, b.id]}), + graph = iD.Graph([a, b, line]); + + surface.call(iD.svg.Midpoints(projection), graph, [line], filter); + + expect(surface.selectAll('.midpoint')[0]).to.have.length(0); + }); + + it("binds a datum whose 'ways' property lists ways which include the segement", function () { + var a = iD.Node({loc: [0, 0]}), + b = iD.Node({loc: [50, 0]}), + c = iD.Node({loc: [1, 1]}), + d = iD.Node({loc: [2, 2]}), + l1 = iD.Way({nodes: [a.id, b.id]}), + l2 = iD.Way({nodes: [b.id, a.id]}), + l3 = iD.Way({nodes: [c.id, a.id, b.id, d.id]}), + l4 = iD.Way({nodes: [a.id, d.id, b.id]}), + graph = iD.Graph([a, b, c, d, l1, l2, l3, l4]), + ab = function (d) { return d.id === [a.id, b.id].sort().join("-"); }; + + surface.call(iD.svg.Midpoints(projection), graph, [l1, l2, l3, l4], filter); + + expect(surface.selectAll('.midpoint').filter(ab).datum().ways).to.eql([ + {id: l1.id, index: 1}, + {id: l2.id, index: 1}, + {id: l3.id, index: 2}]); + }); +}); From e381b6ab261b9d83875c7c2ad6edf92bbaa5e860 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Wed, 30 Jan 2013 15:44:21 -0500 Subject: [PATCH 033/415] Extract AddMidpoint action --- index.html | 1 + js/id/actions/add_midpoint.js | 11 +++++++++++ js/id/behavior/drag_midpoint.js | 9 ++------- test/index.html | 2 ++ test/index_packaged.html | 1 + test/spec/actions/add_midpoint.js | 22 ++++++++++++++++++++++ 6 files changed, 39 insertions(+), 7 deletions(-) create mode 100644 js/id/actions/add_midpoint.js create mode 100644 test/spec/actions/add_midpoint.js diff --git a/index.html b/index.html index fae1fc940..5f62b0f8f 100644 --- a/index.html +++ b/index.html @@ -73,6 +73,7 @@ + diff --git a/js/id/actions/add_midpoint.js b/js/id/actions/add_midpoint.js new file mode 100644 index 000000000..c0cb97f59 --- /dev/null +++ b/js/id/actions/add_midpoint.js @@ -0,0 +1,11 @@ +iD.actions.AddMidpoint = function(midpoint, node) { + return function(graph) { + graph = graph.replace(node.move(midpoint.loc)); + + midpoint.ways.forEach(function(way) { + graph = graph.replace(graph.entity(way.id).addNode(node.id, way.index)); + }); + + return graph; + }; +}; diff --git a/js/id/behavior/drag_midpoint.js b/js/id/behavior/drag_midpoint.js index e0b0c7a1a..72691a512 100644 --- a/js/id/behavior/drag_midpoint.js +++ b/js/id/behavior/drag_midpoint.js @@ -8,14 +8,9 @@ iD.behavior.DragMidpoint = function(mode) { return projection(d.loc); }) .on('start', function(d) { - var node = iD.Node({loc: d.loc}); + var node = iD.Node(); - var args = [iD.actions.AddNode(node)]; - for (var i = 0; i < d.ways.length; i++) { - args.push(iD.actions.AddWayNode(d.ways[i].id, node.id, d.ways[i].index)); - } - - history.perform.apply(history, args); + history.perform(iD.actions.AddMidpoint(d, node)); var vertex = d3.selectAll('.vertex') .filter(function(data) { return data.id === node.id; }); diff --git a/test/index.html b/test/index.html index 97cd63b9b..731800f62 100644 --- a/test/index.html +++ b/test/index.html @@ -68,6 +68,7 @@ + @@ -137,6 +138,7 @@ + diff --git a/test/index_packaged.html b/test/index_packaged.html index 25093d5b3..aba99ba04 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -32,6 +32,7 @@ + diff --git a/test/spec/actions/add_midpoint.js b/test/spec/actions/add_midpoint.js new file mode 100644 index 000000000..1f749217e --- /dev/null +++ b/test/spec/actions/add_midpoint.js @@ -0,0 +1,22 @@ +describe("iD.actions.AddMidpoint", function () { + it("adds the node at the midpoint location", function () { + var node = iD.Node(), + midpoint = {loc: [1, 2], ways: []}, + graph = iD.actions.AddMidpoint(midpoint, node)(iD.Graph()); + + expect(graph.entity(node.id).loc).to.eql([1, 2]); + }); + + it("adds the node to all ways at the respective indexes", function () { + var node = iD.Node(), + a = iD.Node(), + b = iD.Node(), + w1 = iD.Way(), + w2 = iD.Way({nodes: [a.id, b.id]}), + midpoint = {loc: [1, 2], ways: [{id: w1.id, index: 0}, {id: w2.id, index: 1}]}, + graph = iD.actions.AddMidpoint(midpoint, node)(iD.Graph([a, b, w1, w2])); + + expect(graph.entity(w1.id).nodes).to.eql([node.id]); + expect(graph.entity(w2.id).nodes).to.eql([a.id, node.id, b.id]); + }); +}); From c1348e231e0c7bcd18f92689170ba390c5d53ded Mon Sep 17 00:00:00 2001 From: Saman Bemel-Benrud Date: Wed, 30 Jan 2013 15:54:36 -0500 Subject: [PATCH 034/415] fix inspect icon on warnings. --- css/app.css | 98 +++++++++++++++++++++--------------------- img/source/sprite.svg | 6 +-- img/sprite.png | Bin 14025 -> 14014 bytes 3 files changed, 53 insertions(+), 51 deletions(-) diff --git a/css/app.css b/css/app.css index 0ba223a99..054863230 100644 --- a/css/app.css +++ b/css/app.css @@ -929,50 +929,6 @@ div.typeahead a:first-child { left:0px; right:0px; top:0px; bottom:0px; } -.commit-modal .user-info { - display: inline-block; -} - -.commit-modal .commit-info { - margin-top: 10px; -} - -.commit-modal .user-info img { - float: left; -} - -.commit-modal h3 small.count { - margin-right: 10px; - text-align: center; - float: left; - height: 12px; - min-width: 12px; - font-size:12px; - line-height: 12px; - border-radius:24px; - padding:5px; - background:#7092ff; - color:#fff; -} - -.commit-modal .changeset-list { - overflow: auto; - border:1px solid #ccc; - background:#fff; - max-height: 160px; -} - -.commit-modal .warning-section .changeset-list { - margin-right: 20px; - overflow-x: visible; -} - -.commit-section.modal-section { - padding-bottom: 0; -} - -.commit-section.modal-section:last-child { padding-bottom: 20px;} - .modal-section { padding: 20px; } @@ -1007,6 +963,56 @@ div.typeahead a:first-child { display:none; } +.loading-modal { + text-align: center; +} + +/* Commit Modal +------------------------------------------------------- */ + +.commit-modal .user-info { + display: inline-block; +} + +.commit-modal .commit-info { + margin-top: 10px; +} + +.commit-modal .user-info img { + float: left; +} + +.commit-modal h3 small.count { + margin-right: 10px; + text-align: center; + float: left; + height: 12px; + min-width: 12px; + font-size:12px; + line-height: 12px; + border-radius:24px; + padding:5px; + background:#7092ff; + color:#fff; +} + +.commit-modal .changeset-list { + overflow: auto; + border:1px solid #ccc; + background:#fff; + max-height: 160px; +} + +.commit-modal .warning-section .changeset-list button { + float: right; +} + +.commit-section.modal-section { + padding-bottom: 0; +} + +.commit-section.modal-section:last-child { padding-bottom: 20px;} + .changeset-list li { border-top:1px solid #ccc; padding:5px 10px; @@ -1029,10 +1035,6 @@ div.typeahead a:first-child { font:normal 12px/20px 'Helvetica Neue', Arial, sans-serif; } -.loading-modal { - text-align: center; -} - /* Success ------------------------------------------------------- */ a.success-action { diff --git a/img/source/sprite.svg b/img/source/sprite.svg index 4cc5abb47..01a28e513 100644 --- a/img/source/sprite.svg +++ b/img/source/sprite.svg @@ -39,8 +39,8 @@ inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="4" - inkscape:cx="230.7911" - inkscape:cy="190.13176" + inkscape:cx="332.2911" + inkscape:cy="175.13176" inkscape:document-units="px" inkscape:current-layer="layer12" showgrid="false" @@ -733,7 +733,7 @@ inkscape:export-xdpi="90" inkscape:export-ydpi="90" /> diff --git a/img/sprite.png b/img/sprite.png index b0f45f2cd6b06d74235373880618d7a550ef047f..5922c06c17edfe79b8f96c5f48cc97092487fadf 100644 GIT binary patch delta 13286 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 `&vlbT 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 delta 13300 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 O^-xV4S*&6f`2PSXCA5VA From 2fa48df9a15c81198af4ef9a78f0b119c0285ff9 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Wed, 30 Jan 2013 16:09:15 -0500 Subject: [PATCH 035/415] Small parentWays, parentRelations perf improvements --- js/id/graph/graph.js | 17 ++++++++++------- js/id/renderer/map.js | 2 +- js/id/svg/vertices.js | 2 +- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/js/id/graph/graph.js b/js/id/graph/graph.js index 5ef0f54b1..bd5b87f2e 100644 --- a/js/id/graph/graph.js +++ b/js/id/graph/graph.js @@ -24,6 +24,7 @@ iD.Graph = function(other, mutable) { this.transients = {}; this._childNodes = {}; + this.getEntity = _.bind(this.entity, this); if (!mutable) { this.freeze(); @@ -48,19 +49,21 @@ iD.Graph.prototype = { }, parentWays: function(entity) { - return _.map(this._parentWays[entity.id], _.bind(this.entity, this)); + return _.map(this._parentWays[entity.id], this.getEntity); }, isPoi: function(entity) { - return this.parentWays(entity).length === 0; + var parentWays = this._parentWays[entity.id]; + return !parentWays || parentWays.length === 0; + }, + + isShared: function(entity) { + var parentWays = this._parentWays[entity.id]; + return parentWays && parentWays.length > 1; }, parentRelations: function(entity) { - return _.map(this._parentRels[entity.id], _.bind(this.entity, this)); - var ent, id, parents; - - - return this._parentRels[entity.id] || []; + return _.map(this._parentRels[entity.id], this.getEntity); }, childNodes: function(entity) { diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index 139abb4c7..0a22e1911 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -64,7 +64,7 @@ iD.Map = function() { for (var i = 0; i < parents.length; i++) { var parent = parents[i]; if (only[parent.id] === undefined) { - only[parent.id] = graph.entity(parent.id); + only[parent.id] = parent; addParents(graph.parentRelations(parent)); } } diff --git a/js/id/svg/vertices.js b/js/id/svg/vertices.js index 06be536ec..ba28c968a 100644 --- a/js/id/svg/vertices.js +++ b/js/id/svg/vertices.js @@ -36,7 +36,7 @@ iD.svg.Vertices = function(projection) { groups.attr('transform', iD.svg.PointTransform(projection)) .call(iD.svg.TagClasses()) .call(iD.svg.MemberClasses(graph)) - .classed('shared', function(entity) { return graph.parentWays(entity).length > 1; }); + .classed('shared', function(entity) { return graph.isShared(entity); }); // Selecting the following implicitly // sets the data (vertix entity) on the elements From dbac370c71979dc7992619d2634cf01c19d812ac Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Wed, 30 Jan 2013 14:07:52 -0500 Subject: [PATCH 036/415] More draw refactoring --- js/id/behavior/add_way.js | 28 ++++++--------------- js/id/behavior/draw.js | 17 ++++++++++--- js/id/behavior/draw_way.js | 51 +++++++++++++++----------------------- js/id/modes/add_point.js | 18 +++++++++++--- js/id/modes/draw_area.js | 34 ++++++++----------------- js/id/modes/draw_line.js | 41 ++++++++---------------------- 6 files changed, 76 insertions(+), 113 deletions(-) diff --git a/js/id/behavior/add_way.js b/js/id/behavior/add_way.js index 021377890..8eacd9372 100644 --- a/js/id/behavior/add_way.js +++ b/js/id/behavior/add_way.js @@ -1,25 +1,16 @@ iD.behavior.AddWay = function(mode) { var map = mode.map, - history = mode.history, controller = mode.controller, event = d3.dispatch('startFromNode', 'startFromWay', 'start'), - draw; - - function add(datum) { - if (datum.type === 'node') { - event.startFromNode(datum); - } else if (datum.type === 'way') { - var choice = iD.geo.chooseIndex(datum, d3.mouse(map.surface.node()), map); - event.startFromWay(datum, choice.loc, choice.index); - } else if (datum.midpoint) { - var way = history.graph().entity(datum.way); - event.startFromWay(way, datum.loc, datum.index); - } else { - event.start(map.mouseCoordinates()); - } - } + draw = iD.behavior.Draw(map); var addWay = function(surface) { + draw.on('click', event.start) + .on('clickNode', event.startFromNode) + .on('clickWay', event.startFromWay) + .on('cancel', addWay.cancel) + .on('finish', addWay.cancel); + map.fastEnable(false) .minzoom(16) .dblclickEnable(false); @@ -43,10 +34,5 @@ iD.behavior.AddWay = function(mode) { controller.exit(); }; - draw = iD.behavior.Draw() - .on('add', add) - .on('cancel', addWay.cancel) - .on('finish', addWay.cancel); - return d3.rebind(addWay, event, 'on'); }; diff --git a/js/id/behavior/draw.js b/js/id/behavior/draw.js index 4d0153505..41e7fe2a9 100644 --- a/js/id/behavior/draw.js +++ b/js/id/behavior/draw.js @@ -1,5 +1,5 @@ -iD.behavior.Draw = function () { - var event = d3.dispatch('move', 'add', 'undo', 'cancel', 'finish'), +iD.behavior.Draw = function(map) { + var event = d3.dispatch('move', 'click', 'clickNode', 'clickWay', 'undo', 'cancel', 'finish'), keybinding = d3.keybinding('draw'), down, surface, hover; @@ -26,7 +26,18 @@ iD.behavior.Draw = function () { } function click() { - event.add(datum()); + var d = datum(); + if (d.type === 'node') { + event.clickNode(d); + } else if (d.type === 'way') { + var choice = iD.geo.chooseIndex(d, d3.mouse(map.surface.node()), map); + event.clickWay(d, choice.loc, choice.index); + } else if (d.midpoint) { + var way = history.graph().entity(d.way); + event.clickWay(way, d.loc, d.index); + } else { + event.click(map.mouseCoordinates()); + } } function keydown() { diff --git a/js/id/behavior/draw_way.js b/js/id/behavior/draw_way.js index 63526df7a..3bcef869d 100644 --- a/js/id/behavior/draw_way.js +++ b/js/id/behavior/draw_way.js @@ -1,11 +1,11 @@ -iD.behavior.DrawWay = function(wayId, headId, tailId, index, mode, baseGraph) { +iD.behavior.DrawWay = function(wayId, index, mode, baseGraph) { var map = mode.map, history = mode.history, controller = mode.controller, - event = d3.dispatch('add', 'addHead', 'addTail', 'addNode', 'addWay'), way = mode.history.graph().entity(wayId), finished = false, - draw; + annotation = 'added to a way', + draw = iD.behavior.Draw(map); var node = iD.Node({loc: map.mouseCoordinates()}), nodeId = node.id; @@ -26,29 +26,19 @@ iD.behavior.DrawWay = function(wayId, headId, tailId, index, mode, baseGraph) { history.replace(iD.actions.MoveNode(nodeId, loc)); } - function add(datum) { - if (datum.id === headId) { - event.addHead(datum); - } else if (datum.id === tailId) { - event.addTail(datum); - } else if (datum.type === 'node' && datum.id !== nodeId) { - event.addNode(datum); - } else if (datum.type === 'way') { - var choice = iD.geo.chooseIndex(datum, d3.mouse(map.surface.node()), map); - event.addWay(datum, choice.loc, choice.index); - } else if (datum.midpoint) { - var way = history.graph().entity(datum.way); - event.addWay(way, datum.loc, datum.index); - } else { - event.add(map.mouseCoordinates()); - } - } - function undone() { controller.enter(iD.modes.Browse()); } var drawWay = function(surface) { + draw.on('move', move) + .on('click', drawWay.add) + .on('clickNode', drawWay.addNode) + .on('clickWay', drawWay.addWay) + .on('undo', history.undo) + .on('cancel', drawWay.cancel) + .on('finish', drawWay.finish); + map.fastEnable(false) .minzoom(16) .dblclickEnable(false); @@ -80,6 +70,12 @@ iD.behavior.DrawWay = function(wayId, headId, tailId, index, mode, baseGraph) { history.on('undone.draw', null); }; + drawWay.annotation = function(_) { + if (!arguments.length) return annotation; + annotation = _; + return drawWay; + }; + function ReplaceTemporaryNode(newNode) { return function(graph) { return graph @@ -89,7 +85,7 @@ iD.behavior.DrawWay = function(wayId, headId, tailId, index, mode, baseGraph) { } // Connect the way to an existing node and continue drawing. - drawWay.addNode = function(node, annotation) { + drawWay.addNode = function(node) { history.perform( ReplaceTemporaryNode(node), annotation); @@ -99,7 +95,7 @@ iD.behavior.DrawWay = function(wayId, headId, tailId, index, mode, baseGraph) { }; // Connect the way to an existing way. - drawWay.addWay = function(way, loc, wayIndex, annotation) { + drawWay.addWay = function(way, loc, wayIndex) { var newNode = iD.Node({loc: loc}); history.perform( @@ -113,7 +109,7 @@ iD.behavior.DrawWay = function(wayId, headId, tailId, index, mode, baseGraph) { }; // Accept the current position of the temporary node and continue drawing. - drawWay.add = function(loc, annotation) { + drawWay.add = function(loc) { var newNode = iD.Node({loc: loc}); history.replace( @@ -149,12 +145,5 @@ iD.behavior.DrawWay = function(wayId, headId, tailId, index, mode, baseGraph) { controller.enter(iD.modes.Browse()); }; - draw = iD.behavior.Draw() - .on('move', move) - .on('add', add) - .on('undo', history.undo) - .on('cancel', drawWay.cancel) - .on('finish', drawWay.finish); - return d3.rebind(drawWay, event, 'on'); }; diff --git a/js/id/modes/add_point.js b/js/id/modes/add_point.js index 1a51c314d..1bcfc5d04 100644 --- a/js/id/modes/add_point.js +++ b/js/id/modes/add_point.js @@ -16,8 +16,8 @@ iD.modes.AddPoint = function() { map.tail('Click on the map to add a point.', true); - function add() { - var node = iD.Node({loc: map.mouseCoordinates()}); + function add(loc) { + var node = iD.Node({loc: loc}); history.perform( iD.actions.AddNode(node), @@ -26,12 +26,22 @@ iD.modes.AddPoint = function() { controller.enter(iD.modes.Select(node, true)); } + function addWay(way, loc, index) { + add(loc); + } + + function addNode(node) { + add(node.loc); + } + function cancel() { controller.exit(); } - behavior = iD.behavior.Draw() - .on('add', add) + behavior = iD.behavior.Draw(map) + .on('click', add) + .on('clickWay', addWay) + .on('clickNode', addNode) .on('cancel', cancel) .on('finish', cancel) (surface); diff --git a/js/id/modes/draw_area.js b/js/id/modes/draw_area.js index 721452bae..8423ff436 100644 --- a/js/id/modes/draw_area.js +++ b/js/id/modes/draw_area.js @@ -8,33 +8,21 @@ iD.modes.DrawArea = function(wayId, baseGraph) { mode.enter = function() { var way = mode.history.graph().entity(wayId), - index = -1, headId = way.nodes[way.nodes.length - 2], - tailId = way.first(), - annotation = way.isDegenerate() ? 'started an area' : 'continued an area'; + tailId = way.first(); - function addHeadTail() { - behavior.finish(); - } + behavior = iD.behavior.DrawWay(wayId, -1, mode, baseGraph) + .annotation(way.isDegenerate() ? 'started an area' : 'continued an area'); - function addNode(node) { - behavior.addNode(node, annotation); - } + var addNode = behavior.addNode; - function addWay(way, loc, index) { - behavior.addWay(way, loc, index, annotation); - } - - function add(loc) { - behavior.add(loc, annotation); - } - - behavior = iD.behavior.DrawWay(wayId, headId, tailId, index, mode, baseGraph) - .on('addHead', addHeadTail) - .on('addTail', addHeadTail) - .on('addNode', addNode) - .on('addWay', addWay) - .on('add', add); + behavior.addNode = function(node) { + if (node.id === headId || node.id === tailId) { + behavior.finish(); + } else { + addNode(node); + } + }; mode.map.surface.call(behavior); mode.map.tail('Click to add points to your area. Click the first point to finish the area.', true); diff --git a/js/id/modes/draw_line.js b/js/id/modes/draw_line.js index 289d91c14..5dee157b3 100644 --- a/js/id/modes/draw_line.js +++ b/js/id/modes/draw_line.js @@ -9,41 +9,20 @@ iD.modes.DrawLine = function(wayId, direction, baseGraph) { mode.enter = function() { var way = mode.history.graph().entity(wayId), 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'; + headId = (direction === 'forward') ? way.last() : way.first(); - function addHead() { - behavior.finish(); - } + behavior = iD.behavior.DrawWay(wayId, index, mode, baseGraph) + .annotation(way.isDegenerate() ? 'started a line' : 'continued a line'); - function addTail(node) { - // connect the way in a loop - if (way.nodes.length > 2) { - behavior.addNode(node, annotation); + var addNode = behavior.addNode; + + behavior.addNode = function(node) { + if (node.id === headId) { + behavior.finish(); } else { - behavior.cancel(); + addNode(node); } - } - - function addNode(node) { - behavior.addNode(node, annotation); - } - - function addWay(way, loc, index) { - behavior.addWay(way, loc, index, annotation); - } - - function add(loc) { - behavior.add(loc, annotation); - } - - behavior = iD.behavior.DrawWay(wayId, headId, tailId, index, mode, baseGraph) - .on('addHead', addHead) - .on('addTail', addTail) - .on('addNode', addNode) - .on('addWay', addWay) - .on('add', add); + }; mode.map.surface.call(behavior); mode.map.tail('Click to add more points to the line. ' + From 168a3b5e2668daace5a025a951cca785d654bfe7 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Wed, 30 Jan 2013 15:59:13 -0500 Subject: [PATCH 037/415] Fix drawing on midpoints (fixes #559) --- js/id/behavior/add_way.js | 5 +++-- js/id/behavior/draw.js | 14 ++++++++------ js/id/behavior/draw_way.js | 16 +++++++++++++++- js/id/modes/add_area.js | 15 +++++++++++++++ js/id/modes/add_line.js | 14 ++++++++++++++ js/id/modes/add_point.js | 1 + 6 files changed, 56 insertions(+), 9 deletions(-) diff --git a/js/id/behavior/add_way.js b/js/id/behavior/add_way.js index 8eacd9372..fa038d201 100644 --- a/js/id/behavior/add_way.js +++ b/js/id/behavior/add_way.js @@ -1,13 +1,14 @@ iD.behavior.AddWay = function(mode) { var map = mode.map, controller = mode.controller, - event = d3.dispatch('startFromNode', 'startFromWay', 'start'), + event = d3.dispatch('start', 'startFromWay', 'startFromNode', 'startFromMidpoint'), draw = iD.behavior.Draw(map); var addWay = function(surface) { draw.on('click', event.start) - .on('clickNode', event.startFromNode) .on('clickWay', event.startFromWay) + .on('clickNode', event.startFromNode) + .on('clickMidpoint', event.startFromMidpoint) .on('cancel', addWay.cancel) .on('finish', addWay.cancel); diff --git a/js/id/behavior/draw.js b/js/id/behavior/draw.js index 41e7fe2a9..783d9e42c 100644 --- a/js/id/behavior/draw.js +++ b/js/id/behavior/draw.js @@ -1,5 +1,5 @@ iD.behavior.Draw = function(map) { - var event = d3.dispatch('move', 'click', 'clickNode', 'clickWay', 'undo', 'cancel', 'finish'), + var event = d3.dispatch('move', 'click', 'clickWay', 'clickNode', 'clickMidpoint', 'undo', 'cancel', 'finish'), keybinding = d3.keybinding('draw'), down, surface, hover; @@ -27,14 +27,16 @@ iD.behavior.Draw = function(map) { function click() { var d = datum(); - if (d.type === 'node') { - event.clickNode(d); - } else if (d.type === 'way') { + if (d.type === 'way') { var choice = iD.geo.chooseIndex(d, d3.mouse(map.surface.node()), map); event.clickWay(d, choice.loc, choice.index); + + } else if (d.type === 'node') { + event.clickNode(d); + } else if (d.midpoint) { - var way = history.graph().entity(d.way); - event.clickWay(way, d.loc, d.index); + event.clickMidpoint(d); + } else { event.click(map.mouseCoordinates()); } diff --git a/js/id/behavior/draw_way.js b/js/id/behavior/draw_way.js index 3bcef869d..1103bc143 100644 --- a/js/id/behavior/draw_way.js +++ b/js/id/behavior/draw_way.js @@ -33,8 +33,9 @@ iD.behavior.DrawWay = function(wayId, index, mode, baseGraph) { var drawWay = function(surface) { draw.on('move', move) .on('click', drawWay.add) - .on('clickNode', drawWay.addNode) .on('clickWay', drawWay.addWay) + .on('clickNode', drawWay.addNode) + .on('clickMidpoint', drawWay.addMidpoint) .on('undo', history.undo) .on('cancel', drawWay.cancel) .on('finish', drawWay.finish); @@ -121,6 +122,19 @@ iD.behavior.DrawWay = function(wayId, index, mode, baseGraph) { controller.enter(mode); }; + // Add a midpoint, connect the way to it, and continue drawing. + drawWay.addMidpoint = function(midpoint) { + var node = iD.Node(); + + history.perform( + iD.actions.AddMidpoint(midpoint, node), + ReplaceTemporaryNode(node), + annotation); + + finished = true; + controller.enter(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() { diff --git a/js/id/modes/add_area.js b/js/id/modes/add_area.js index d1a3efb08..153060b3a 100644 --- a/js/id/modes/add_area.js +++ b/js/id/modes/add_area.js @@ -15,6 +15,20 @@ iD.modes.AddArea = function() { history = mode.history, controller = mode.controller; + function startFromMidpoint(midpoint) { + var graph = history.graph(), + node = iD.Node(), + way = iD.Way({tags: defaultTags}); + + history.perform( + iD.actions.AddMidpoint(midpoint, node), + iD.actions.AddWay(way), + iD.actions.AddWayNode(way.id, node.id), + iD.actions.AddWayNode(way.id, node.id)); + + controller.enter(iD.modes.DrawArea(way.id, graph)); + } + function startFromNode(node) { var graph = history.graph(), way = iD.Way({tags: defaultTags}); @@ -57,6 +71,7 @@ iD.modes.AddArea = function() { } behavior = iD.behavior.AddWay(mode) + .on('startFromMidpoint', startFromMidpoint) .on('startFromNode', startFromNode) .on('startFromWay', startFromWay) .on('start', start); diff --git a/js/id/modes/add_line.js b/js/id/modes/add_line.js index bf419d2bb..df6f33a57 100644 --- a/js/id/modes/add_line.js +++ b/js/id/modes/add_line.js @@ -15,6 +15,19 @@ iD.modes.AddLine = function() { history = mode.history, controller = mode.controller; + function startFromMidpoint(midpoint) { + var graph = history.graph(), + node = iD.Node(), + way = iD.Way({tags: defaultTags}); + + history.perform( + iD.actions.AddMidpoint(midpoint, node), + iD.actions.AddWay(way), + iD.actions.AddWayNode(way.id, node.id)); + + controller.enter(iD.modes.DrawLine(way.id, 'forward', graph)); + } + function startFromNode(node) { var graph = history.graph(), parent = graph.parentWays(node)[0], @@ -65,6 +78,7 @@ iD.modes.AddLine = function() { } behavior = iD.behavior.AddWay(mode) + .on('startFromMidpoint', startFromMidpoint) .on('startFromNode', startFromNode) .on('startFromWay', startFromWay) .on('start', start); diff --git a/js/id/modes/add_point.js b/js/id/modes/add_point.js index 1bcfc5d04..723b1935b 100644 --- a/js/id/modes/add_point.js +++ b/js/id/modes/add_point.js @@ -42,6 +42,7 @@ iD.modes.AddPoint = function() { .on('click', add) .on('clickWay', addWay) .on('clickNode', addNode) + .on('clickMidpoint', addNode) .on('cancel', cancel) .on('finish', cancel) (surface); From 363cd53af14939accb8f124d8f06775513c2ee06 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Wed, 30 Jan 2013 16:17:30 -0500 Subject: [PATCH 038/415] Rearrange code --- js/id/behavior/draw_way.js | 22 +++++++++--------- js/id/modes/add_area.js | 42 +++++++++++++++++----------------- js/id/modes/add_line.js | 46 +++++++++++++++++++------------------- 3 files changed, 55 insertions(+), 55 deletions(-) diff --git a/js/id/behavior/draw_way.js b/js/id/behavior/draw_way.js index 1103bc143..65231a277 100644 --- a/js/id/behavior/draw_way.js +++ b/js/id/behavior/draw_way.js @@ -85,10 +85,13 @@ iD.behavior.DrawWay = function(wayId, index, mode, baseGraph) { } } - // Connect the way to an existing node and continue drawing. - drawWay.addNode = function(node) { - history.perform( - ReplaceTemporaryNode(node), + // Accept the current position of the temporary node and continue drawing. + drawWay.add = function(loc) { + var newNode = iD.Node({loc: loc}); + + history.replace( + iD.actions.AddNode(newNode), + ReplaceTemporaryNode(newNode), annotation); finished = true; @@ -109,13 +112,10 @@ iD.behavior.DrawWay = function(wayId, index, mode, baseGraph) { controller.enter(mode); }; - // Accept the current position of the temporary node and continue drawing. - drawWay.add = function(loc) { - var newNode = iD.Node({loc: loc}); - - history.replace( - iD.actions.AddNode(newNode), - ReplaceTemporaryNode(newNode), + // Connect the way to an existing node and continue drawing. + drawWay.addNode = function(node) { + history.perform( + ReplaceTemporaryNode(node), annotation); finished = true; diff --git a/js/id/modes/add_area.js b/js/id/modes/add_area.js index 153060b3a..614c66677 100644 --- a/js/id/modes/add_area.js +++ b/js/id/modes/add_area.js @@ -15,25 +15,13 @@ iD.modes.AddArea = function() { history = mode.history, controller = mode.controller; - function startFromMidpoint(midpoint) { - var graph = history.graph(), - node = iD.Node(), - way = iD.Way({tags: defaultTags}); - - history.perform( - iD.actions.AddMidpoint(midpoint, node), - iD.actions.AddWay(way), - iD.actions.AddWayNode(way.id, node.id), - iD.actions.AddWayNode(way.id, node.id)); - - controller.enter(iD.modes.DrawArea(way.id, graph)); - } - - function startFromNode(node) { + function start(loc) { var graph = history.graph(), + node = iD.Node({loc: loc}), way = iD.Way({tags: defaultTags}); history.perform( + iD.actions.AddNode(node), iD.actions.AddWay(way), iD.actions.AddWayNode(way.id, node.id), iD.actions.AddWayNode(way.id, node.id)); @@ -56,13 +44,25 @@ iD.modes.AddArea = function() { controller.enter(iD.modes.DrawArea(way.id, graph)); } - function start(loc) { + function startFromNode(node) { var graph = history.graph(), - node = iD.Node({loc: loc}), way = iD.Way({tags: defaultTags}); history.perform( - iD.actions.AddNode(node), + iD.actions.AddWay(way), + iD.actions.AddWayNode(way.id, node.id), + iD.actions.AddWayNode(way.id, node.id)); + + controller.enter(iD.modes.DrawArea(way.id, graph)); + } + + function startFromMidpoint(midpoint) { + var graph = history.graph(), + node = iD.Node(), + way = iD.Way({tags: defaultTags}); + + history.perform( + iD.actions.AddMidpoint(midpoint, node), iD.actions.AddWay(way), iD.actions.AddWayNode(way.id, node.id), iD.actions.AddWayNode(way.id, node.id)); @@ -71,10 +71,10 @@ iD.modes.AddArea = function() { } behavior = iD.behavior.AddWay(mode) - .on('startFromMidpoint', startFromMidpoint) - .on('startFromNode', startFromNode) + .on('start', start) .on('startFromWay', startFromWay) - .on('start', start); + .on('startFromNode', startFromNode) + .on('startFromMidpoint', startFromMidpoint); mode.map.surface.call(behavior); mode.map.tail('Click on the map to start drawing an area, like a park, lake, or building.', true); diff --git a/js/id/modes/add_line.js b/js/id/modes/add_line.js index df6f33a57..90adac738 100644 --- a/js/id/modes/add_line.js +++ b/js/id/modes/add_line.js @@ -15,19 +15,33 @@ iD.modes.AddLine = function() { history = mode.history, controller = mode.controller; - function startFromMidpoint(midpoint) { + function start(loc) { var graph = history.graph(), - node = iD.Node(), + node = iD.Node({loc: loc}), way = iD.Way({tags: defaultTags}); history.perform( - iD.actions.AddMidpoint(midpoint, node), + iD.actions.AddNode(node), iD.actions.AddWay(way), iD.actions.AddWayNode(way.id, node.id)); controller.enter(iD.modes.DrawLine(way.id, 'forward', graph)); } + function startFromWay(other, loc, index) { + var graph = history.graph(), + node = iD.Node({loc: loc}), + way = iD.Way({tags: defaultTags}); + + history.perform( + iD.actions.AddNode(node), + iD.actions.AddWay(way), + iD.actions.AddWayNode(way.id, node.id), + iD.actions.AddWayNode(other.id, node.id, index)); + + controller.enter(iD.modes.DrawLine(way.id, 'forward', graph)); + } + function startFromNode(node) { var graph = history.graph(), parent = graph.parentWays(node)[0], @@ -50,27 +64,13 @@ iD.modes.AddLine = function() { } } - function startFromWay(other, loc, index) { + function startFromMidpoint(midpoint) { var graph = history.graph(), - node = iD.Node({loc: loc}), + node = iD.Node(), way = iD.Way({tags: defaultTags}); history.perform( - iD.actions.AddNode(node), - iD.actions.AddWay(way), - iD.actions.AddWayNode(way.id, node.id), - iD.actions.AddWayNode(other.id, node.id, index)); - - controller.enter(iD.modes.DrawLine(way.id, 'forward', graph)); - } - - function start(loc) { - var graph = history.graph(), - node = iD.Node({loc: loc}), - way = iD.Way({tags: defaultTags}); - - history.perform( - iD.actions.AddNode(node), + iD.actions.AddMidpoint(midpoint, node), iD.actions.AddWay(way), iD.actions.AddWayNode(way.id, node.id)); @@ -78,10 +78,10 @@ iD.modes.AddLine = function() { } behavior = iD.behavior.AddWay(mode) - .on('startFromMidpoint', startFromMidpoint) - .on('startFromNode', startFromNode) + .on('start', start) .on('startFromWay', startFromWay) - .on('start', start); + .on('startFromNode', startFromNode) + .on('startFromMidpoint', startFromMidpoint); mode.map.surface.call(behavior); mode.map.tail('Click on the map to start drawing an road, path, or route.', true); From e4c513ae87b235310c77204448cf3f48e92e69b0 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Wed, 30 Jan 2013 16:24:33 -0500 Subject: [PATCH 039/415] midpoint = true -> type = 'midpoint' Makes it more consistent with entities. --- js/id/behavior/draw.js | 2 +- js/id/behavior/draw_way.js | 2 +- js/id/renderer/map.js | 2 +- js/id/svg/midpoints.js | 4 +--- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/js/id/behavior/draw.js b/js/id/behavior/draw.js index 783d9e42c..bce4470b6 100644 --- a/js/id/behavior/draw.js +++ b/js/id/behavior/draw.js @@ -34,7 +34,7 @@ iD.behavior.Draw = function(map) { } else if (d.type === 'node') { event.clickNode(d); - } else if (d.midpoint) { + } else if (d.type === 'midpoint') { event.clickMidpoint(d); } else { diff --git a/js/id/behavior/draw_way.js b/js/id/behavior/draw_way.js index 65231a277..884d72ddb 100644 --- a/js/id/behavior/draw_way.js +++ b/js/id/behavior/draw_way.js @@ -17,7 +17,7 @@ iD.behavior.DrawWay = function(wayId, index, mode, baseGraph) { function move(datum) { var loc = map.mouseCoordinates(); - if (datum.type === 'node' || datum.midpoint) { + if (datum.type === 'node' || datum.type === 'midpoint') { loc = datum.loc; } else if (datum.type === 'way') { loc = iD.geo.chooseIndex(datum, d3.mouse(map.surface.node()), map).loc; diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index 487ac736a..c7c1ed632 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -92,7 +92,7 @@ iD.Map = function() { all = _.compact(_.values(only)); filter = function(d) { - if (d.midpoint) { + if (d.type === 'midpoint') { for (var i = 0; i < d.ways.length; i++) { if (d.ways[i].id in only) return true; } diff --git a/js/id/svg/midpoints.js b/js/id/svg/midpoints.js index da4af659c..ee8d2c5e2 100644 --- a/js/id/svg/midpoints.js +++ b/js/id/svg/midpoints.js @@ -20,11 +20,9 @@ iD.svg.Midpoints = function(projection) { } else if (iD.geo.dist(projection(a.loc), projection(b.loc)) > 40) { midpoints[id] = { - midpoint: true, + type: 'midpoint', id: id, loc: iD.geo.interp(a.loc, b.loc, 0.5), - a: a.id, - b: b.id, ways: [{id: entity.id, index: j + 1}] }; } From a82b81a3c227ec02d7f36302bd8f4904cb8700b0 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Wed, 30 Jan 2013 16:40:00 -0500 Subject: [PATCH 040/415] Add tests for #replace #remove #rebase --- test/spec/graph/graph.js | 65 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/test/spec/graph/graph.js b/test/spec/graph/graph.js index 667cfe149..327d66295 100644 --- a/test/spec/graph/graph.js +++ b/test/spec/graph/graph.js @@ -92,6 +92,14 @@ describe('iD.Graph', function() { expect(graph._parentWays.hasOwnProperty('n')).to.be.false; }); + it("avoids adding duplicate parentWays", function () { + var n = iD.Node({id: 'n'}), + w1 = iD.Way({id: 'w1', nodes: ['n']}), + graph = iD.Graph([n, w1]); + graph.rebase({ 'w1': w1 }); + expect(graph.parentWays(n)).to.eql([w1]); + }); + it("updates parentWays for nodes with modified parentWays", function () { var n = iD.Node({id: 'n'}), w1 = iD.Way({id: 'w1', nodes: ['n']}), @@ -171,6 +179,20 @@ describe('iD.Graph', function() { graph = iD.Graph([node]); expect(graph.remove(node).entity(node.id)).to.be.undefined; }); + + it("removes the entity as a parentWay", function () { + var node = iD.Node({id: 'n' }), + w1 = iD.Way({id: 'w', nodes: ['n']}), + graph = iD.Graph([node, w1]); + expect(graph.remove(w1).parentWays(node)).to.eql([]); + }); + + it("removes the entity as a parentRelation", function () { + var node = iD.Node({id: 'n' }), + r1 = iD.Relation({id: 'w', members: [{id: 'n' }]}), + graph = iD.Graph([node, r1]); + expect(graph.remove(r1).parentRelations(node)).to.eql([]); + }); }); describe("#replace", function () { @@ -193,6 +215,49 @@ describe('iD.Graph', function() { graph = iD.Graph([node1]); expect(graph.replace(node2).entity(node2.id)).to.equal(node2); }); + + it("adds parentWays", function () { + var node = iD.Node({id: 'n' }), + w1 = iD.Way({id: 'w', nodes: ['n']}), + graph = iD.Graph([node]); + expect(graph.replace(w1).parentWays(node)).to.eql([w1]); + }); + + it("removes parentWays", function () { + var node = iD.Node({id: 'n' }), + w1 = iD.Way({id: 'w', nodes: ['n']}), + graph = iD.Graph([node, w1]); + expect(graph.remove(w1).parentWays(node)).to.eql([]); + }); + + it("doesn't add duplicate parentWays", function () { + var node = iD.Node({id: 'n' }), + w1 = iD.Way({id: 'w', nodes: ['n']}), + graph = iD.Graph([node, w1]); + expect(graph.replace(w1).parentWays(node)).to.eql([w1]); + }); + + it("adds parentRels", function () { + var node = iD.Node({id: 'n' }), + r1 = iD.Relation({id: 'w', members: [{id: 'n'}]}), + graph = iD.Graph([node]); + expect(graph.replace(r1).parentRelations(node)).to.eql([r1]); + }); + + it("removes parentRelations", function () { + var node = iD.Node({id: 'n' }), + r1 = iD.Relation({id: 'w', members: [{id: 'n'}]}), + graph = iD.Graph([node, r1]); + expect(graph.remove(r1).parentRelations(node)).to.eql([]); + }); + + it("doesn't add duplicate parentRelations", function () { + var node = iD.Node({id: 'n' }), + r1 = iD.Relation({id: 'w', members: [{id: 'n'}]}), + graph = iD.Graph([node, r1]); + expect(graph.replace(r1).parentRelations(node)).to.eql([r1]); + }); + }); describe("#update", function () { From 9862238a842756b5beb825e35d8222fb2b375576 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Wed, 30 Jan 2013 17:04:51 -0500 Subject: [PATCH 041/415] Fix graph.rebase() --- js/id/graph/graph.js | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/js/id/graph/graph.js b/js/id/graph/graph.js index bd5b87f2e..5e865b9ec 100644 --- a/js/id/graph/graph.js +++ b/js/id/graph/graph.js @@ -107,10 +107,12 @@ iD.Graph.prototype = { keys = Object.keys(this._parentWays); for (i = 0; i < keys.length; i++) { child = keys[i]; - for (k = 0; k < base.parentWays[child].length; k++) { - id = base.parentWays[child][k]; - if (this.entity(id) && !_.contains(this._parentWays[child], id)) { - this._parentWays[child].push(id); + if (base.parentWays[child]) { + for (k = 0; k < base.parentWays[child].length; k++) { + id = base.parentWays[child][k]; + if (this.entity(id) && !_.contains(this._parentWays[child], id)) { + this._parentWays[child].push(id); + } } } } @@ -118,10 +120,12 @@ iD.Graph.prototype = { keys = Object.keys(this._parentRels); for (i = 0; i < keys.length; i++) { child = keys[i]; - for (k = 0; k < base.parentRels[child].length; k++) { - id = base.parentRels[child][k]; - if (this.entity(id) && !_.contains(this._parentRels[child], id)) { - this._parentRels[child].push(id); + if (base.parentRels[child]) { + for (k = 0; k < base.parentRels[child].length; k++) { + id = base.parentRels[child][k]; + if (this.entity(id) && !_.contains(this._parentRels[child], id)) { + this._parentRels[child].push(id); + } } } } From 792531d48a4f49933034a922e2bf45d1beaec252 Mon Sep 17 00:00:00 2001 From: Saman Bemel-Benrud Date: Wed, 30 Jan 2013 17:27:28 -0500 Subject: [PATCH 042/415] fixed tooltip sizing / positioning/ --- css/app.css | 109 ++++++++++++++++++++++++++-------------------------- 1 file changed, 55 insertions(+), 54 deletions(-) diff --git a/css/app.css b/css/app.css index 054863230..4ec4bd91d 100644 --- a/css/app.css +++ b/css/app.css @@ -246,7 +246,6 @@ form.hide { button { line-height:20px; - position: relative; border:0; color:#222; background: white; @@ -346,16 +345,16 @@ button.save .count { button.save.has-count .count { display: block; position: absolute; - left: 115%; top: 0; bottom: 0; - background: rgba(255,255,255,.5); + background: rgba(255, 255, 255, .5); color: #333; padding: 10px; height: 30px; line-height: 12px; border-radius: 4px; margin: auto; + margin-left: 8.3333%; } button.save.has-count .count::before { @@ -1072,12 +1071,10 @@ a.success-action { ------------------------------------------------------- */ .tooltip { - white-space: normal; + width: 200px; position: absolute; - left: 0; right: 0; margin: auto; z-index: -1000; height: 0; - padding: 5px; opacity: 0; display: block; } @@ -1089,77 +1086,81 @@ a.success-action { } .tooltip.top { - margin-top: -5px; + margin-top: -10px; + text-align: center; } .tooltip.right { - margin-left: 5px; + margin-left: 10px; + text-align: left; } .tooltip.bottom { - margin-top: 5px; + margin-top: 10px; + text-align: center; } .tooltip.left { - margin-left: -5px; + margin-left: -10px; + text-align: right; } .tooltip-inner { - text-align: left; - width: 200px; - font-size: 11px; - font-weight: bold; - line-height: 20px; - padding: 5px 10px; - color: #333; - background-color: white; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; + color: #333; + display: inline-block; + padding: 5px 10px; + font-size: 11px; + font-weight: bold; + line-height: 20px; + background-color: white; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + } .tooltip-arrow { - position: absolute; - width: 0; - height: 0; - border-color: transparent; - border-style: solid; + position: absolute; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; } .tooltip.top .tooltip-arrow { - bottom: 0; - left: 50%; - margin-left: -5px; - border-top-color: white; - border-width: 5px 5px 0; + bottom: -5px; + left: 50%; + margin-left: -5px; + border-top-color: white; + border-width: 5px 5px 0; } .tooltip.right .tooltip-arrow { - top: 50%; - left: 0; - margin-top: -5px; - border-right-color: white; - border-width: 5px 5px 5px 0; + top: 50%; + left: -5px; + margin-top: -5px; + border-right-color: white; + border-width: 5px 5px 5px 0; } .tooltip.left .tooltip-arrow { - top: 50%; - right: 0; - margin-top: -5px; - border-left-color: white; - border-width: 5px 0 5px 5px; + top: 50%; + right: 5px; + margin-top: -5px; + border-left-color: white; + border-width: 5px 0 5px 5px; } .tooltip.bottom .tooltip-arrow { - top: 0; - left: 50%; - margin-left: -5px; - border-bottom-color: white; - border-width: 0 5px 5px; + top: -5px; + left: 50%; + margin-left: -5px; + border-bottom-color: white; + border-width: 0 5px 5px; } .Browse .tooltip .tooltip-arrow { - left: 30px; + left: 30px; } .tooltip .keyhint { @@ -1174,13 +1175,13 @@ a.success-action { } .tail { - pointer-events:none; - position: absolute; - background: rgba(255, 255, 255, 0.7); - max-width: 250px; - margin-top: -15px; - padding: 5px; - -webkit-border-radius: 4px; + pointer-events:none; + position: absolute; + background: rgba(255, 255, 255, 0.7); + max-width: 250px; + margin-top: -15px; + padding: 5px; + -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; } From 35cefceba67fcf2ee1bfa63bb1511a51f4eeee2d Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Wed, 30 Jan 2013 17:43:14 -0500 Subject: [PATCH 043/415] Fix partial redraws --- js/id/renderer/map.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index 79fcd3240..0d1532948 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -165,7 +165,8 @@ iD.Map = function() { } function resetTransform() { - if (!surface.style(transformProp)) return false; + var prop = surface.style(transformProp); + if (!prop || prop === 'none') return false; surface.style(transformProp, ''); tilegroup.style(transformProp, ''); return true; From eb504a3e23ad1697eb942ae1bd1ba7b1c81aebf5 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Wed, 30 Jan 2013 18:18:19 -0500 Subject: [PATCH 044/415] Correctly rewrite users --- js/id/ui/contributors.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/js/id/ui/contributors.js b/js/id/ui/contributors.js index c0036a677..c64d8a2ac 100644 --- a/js/id/ui/contributors.js +++ b/js/id/ui/contributors.js @@ -16,7 +16,7 @@ iD.ui.contributors = function(map) { var l = selection .select('.contributor-list') .selectAll('a.user-link') - .data(subset); + .data(subset, function(d) { return d; }); l.enter().append('a') @@ -43,6 +43,10 @@ iD.ui.contributors = function(map) { ext[1][0], ext[1][1]]; }) .text(' and ' + (u.length - limit) + ' others'); + } else { + selection + .select('.contributor-count') + .html(''); } if (!u.length) { From 1b495870c0424b53b6f9cbbb12522316497158c8 Mon Sep 17 00:00:00 2001 From: Saman Bemel-Benrud Date: Wed, 30 Jan 2013 19:09:10 -0500 Subject: [PATCH 045/415] grammar for tooltips, keybinding hints, other tooltip fixes. --- css/app.css | 42 +++++++++++++++++++++++++++------ js/id/behavior/drag_midpoint.js | 2 +- js/id/behavior/draw_way.js | 4 ++-- js/id/id.js | 8 +++---- js/id/modes/add_point.js | 2 +- js/id/modes/browse.js | 2 +- js/id/modes/draw_line.js | 2 +- js/id/modes/move_way.js | 4 ++-- js/id/modes/select.js | 4 ++-- js/id/operations/circularize.js | 4 ++-- js/id/operations/delete.js | 10 ++++---- js/id/operations/reverse.js | 4 ++-- js/id/operations/split.js | 4 ++-- js/id/operations/unjoin.js | 4 ++-- 14 files changed, 62 insertions(+), 34 deletions(-) diff --git a/css/app.css b/css/app.css index 4ec4bd91d..98ba49e57 100644 --- a/css/app.css +++ b/css/app.css @@ -1159,19 +1159,47 @@ a.success-action { border-width: 0 5px 5px; } +.Browse .tooltip { + left: -20px !important; } .Browse .tooltip .tooltip-arrow { - left: 30px; + left: 60px; +} + +.tooltip .keyhint-wrap { + padding: 5px 0 5px 0; } .tooltip .keyhint { - float: right; - background: #eee; + display: block; + color: #222; font-size: 10px; - padding: 0 4px; - background:#aaa; - color:#fff; + padding: 0px 7px; + text-transform: uppercase; + font-weight: bold; + display: inline-block; border-radius: 2px; - margin-left: 4px; + border: 1px solid #CCC; + position: relative; + z-index: 1; + text-align: left; + clear: both; +} + +.tooltip .keyhint .keyhint-label{ + display: inline-block; +} + +.tooltip .keyhint::after { + content: ""; + position: absolute; + border-radius: 2px; + height: 10px; + width: 100%; + z-index: 0; + bottom: -4px; + left: -1px; + border: 1px solid #CCC; + border-top: 0; } .tail { diff --git a/js/id/behavior/drag_midpoint.js b/js/id/behavior/drag_midpoint.js index 72691a512..bd1a40bd9 100644 --- a/js/id/behavior/drag_midpoint.js +++ b/js/id/behavior/drag_midpoint.js @@ -25,7 +25,7 @@ iD.behavior.DragMidpoint = function(mode) { .on('end', function() { history.replace( iD.actions.Noop(), - 'added a node to a way'); + 'Added a node to a way.'); }); return behavior; diff --git a/js/id/behavior/draw_way.js b/js/id/behavior/draw_way.js index 884d72ddb..21c13d2fa 100644 --- a/js/id/behavior/draw_way.js +++ b/js/id/behavior/draw_way.js @@ -4,7 +4,7 @@ iD.behavior.DrawWay = function(wayId, index, mode, baseGraph) { controller = mode.controller, way = mode.history.graph().entity(wayId), finished = false, - annotation = 'added to a way', + annotation = 'Added to a way.', draw = iD.behavior.Draw(map); var node = iD.Node({loc: map.mouseCoordinates()}), @@ -153,7 +153,7 @@ iD.behavior.DrawWay = function(wayId, index, mode, baseGraph) { drawWay.cancel = function() { history.perform( d3.functor(baseGraph), - 'cancelled drawing'); + 'Cancelled drawing.'); finished = true; controller.enter(iD.modes.Browse()); diff --git a/js/id/id.js b/js/id/id.js index ab6dd37f2..69e64e5fe 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -22,7 +22,7 @@ window.iD = function(container) { } function hintprefix(x, y) { - return '' + x + ' ' + y; + return '' + y + '' + '

' + x + '
'; } var m = container.append('div') @@ -44,10 +44,10 @@ window.iD = function(container) { .enter().append('button') .attr('tabindex', -1) .attr('class', function (mode) { return mode.title + ' add-button col3'; }) + .call(bootstrap.tooltip().placement('bottom').html(true)) .attr('data-original-title', function (mode) { return hintprefix(mode.key, mode.description); }) - .call(bootstrap.tooltip().placement('bottom').html(true)) .on('click.editor', function (mode) { controller.enter(mode); }); function disableTooHigh() { @@ -207,12 +207,12 @@ window.iD = function(container) { limiter.select('#undo') .property('disabled', !undo) - .attr('data-original-title', hintprefix('⌘Z', undo)) + .attr('data-original-title', hintprefix('⌘ + Z', undo)) .call(refreshTooltip); limiter.select('#redo') .property('disabled', !redo) - .attr('data-original-title', hintprefix('⌘⇧Z', redo)) + .attr('data-original-title', hintprefix('⌘ + ⇧ + Z', redo)) .call(refreshTooltip); }); diff --git a/js/id/modes/add_point.js b/js/id/modes/add_point.js index 723b1935b..1b9781a9d 100644 --- a/js/id/modes/add_point.js +++ b/js/id/modes/add_point.js @@ -21,7 +21,7 @@ iD.modes.AddPoint = function() { history.perform( iD.actions.AddNode(node), - 'added a point'); + 'Added a point.'); controller.enter(iD.modes.Select(node, true)); } diff --git a/js/id/modes/browse.js b/js/id/modes/browse.js index 9782bac27..ab6bc80cb 100644 --- a/js/id/modes/browse.js +++ b/js/id/modes/browse.js @@ -3,7 +3,7 @@ iD.modes.Browse = function() { button: 'browse', id: 'browse', title: 'Browse', - description: 'Pan and zoom the map', + description: 'Pan and zoom the map.', key: 'b' }; diff --git a/js/id/modes/draw_line.js b/js/id/modes/draw_line.js index 5dee157b3..ae54ab2e1 100644 --- a/js/id/modes/draw_line.js +++ b/js/id/modes/draw_line.js @@ -12,7 +12,7 @@ iD.modes.DrawLine = function(wayId, direction, baseGraph) { headId = (direction === 'forward') ? way.last() : way.first(); behavior = iD.behavior.DrawWay(wayId, index, mode, baseGraph) - .annotation(way.isDegenerate() ? 'started a line' : 'continued a line'); + .annotation(way.isDegenerate() ? 'Started a line.' : 'Continued a line.'); var addNode = behavior.addNode; diff --git a/js/id/modes/move_way.js b/js/id/modes/move_way.js index 1f559da82..18c6ee619 100644 --- a/js/id/modes/move_way.js +++ b/js/id/modes/move_way.js @@ -18,7 +18,7 @@ iD.modes.MoveWay = function(wayId) { history.perform( iD.actions.Noop(), - 'moved a way'); + 'Moved a way.'); function move() { var p = d3.mouse(selection.node()), @@ -29,7 +29,7 @@ iD.modes.MoveWay = function(wayId) { history.replace( iD.actions.MoveWay(wayId, delta, projection), - 'moved a way'); + 'Moved a way.'); } function finish() { diff --git a/js/id/modes/select.js b/js/id/modes/select.js index ad06bda35..1ddd4ffc4 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -14,7 +14,7 @@ iD.modes.Select = function(entity, initial) { if (!_.isEqual(entity.tags, tags)) { mode.history.perform( iD.actions.ChangeEntityTags(d.id, tags), - 'changed tags'); + 'Changed tags.'); } } @@ -114,7 +114,7 @@ iD.modes.Select = function(entity, initial) { history.perform( iD.actions.AddNode(node), iD.actions.AddWayNode(datum.id, node.id, choice.index), - 'added a point to a road'); + 'Added a point to a road.'); d3.event.preventDefault(); d3.event.stopPropagation(); diff --git a/js/id/operations/circularize.js b/js/id/operations/circularize.js index 49956430e..ce161aef4 100644 --- a/js/id/operations/circularize.js +++ b/js/id/operations/circularize.js @@ -10,12 +10,12 @@ iD.operations.Circularize = function(entityId, mode) { if (geometry === 'line') { history.perform( action, - 'made a line circular'); + 'Made a line circular.'); } else if (geometry === 'area') { history.perform( action, - 'made an area circular'); + 'Made an area circular.'); } }; diff --git a/js/id/operations/delete.js b/js/id/operations/delete.js index 03d788261..cc3750360 100644 --- a/js/id/operations/delete.js +++ b/js/id/operations/delete.js @@ -9,22 +9,22 @@ iD.operations.Delete = function(entityId, mode) { if (geometry === 'vertex') { history.perform( iD.actions.DeleteNode(entityId), - 'deleted a vertex'); + 'Deleted a vertex.'); } else if (geometry === 'point') { history.perform( iD.actions.DeleteNode(entityId), - 'deleted a point'); + 'Deleted a point.'); } else if (geometry === 'line') { history.perform( iD.actions.DeleteWay(entityId), - 'deleted a line'); + 'Deleted a line.'); } else if (geometry === 'area') { history.perform( iD.actions.DeleteWay(entityId), - 'deleted an area'); + 'Deleted an area.'); } }; @@ -41,7 +41,7 @@ iD.operations.Delete = function(entityId, mode) { operation.id = "delete"; operation.key = "⌫"; operation.title = "Delete"; - operation.description = "Remove this from the map"; + operation.description = "Remove this from the map."; return operation; }; diff --git a/js/id/operations/reverse.js b/js/id/operations/reverse.js index b36dfd60c..62fd8810b 100644 --- a/js/id/operations/reverse.js +++ b/js/id/operations/reverse.js @@ -4,7 +4,7 @@ iD.operations.Reverse = function(entityId, mode) { var operation = function() { history.perform( iD.actions.ReverseWay(entityId), - 'reversed a line'); + 'Reversed a line.'); }; operation.available = function() { @@ -20,7 +20,7 @@ iD.operations.Reverse = function(entityId, mode) { operation.id = "reverse"; operation.key = "V"; operation.title = "Reverse"; - operation.description = "Make this way go in the opposite direction"; + operation.description = "Make this way go in the opposite direction."; return operation; }; diff --git a/js/id/operations/split.js b/js/id/operations/split.js index 3dcab7481..40aa55fd0 100644 --- a/js/id/operations/split.js +++ b/js/id/operations/split.js @@ -3,7 +3,7 @@ iD.operations.Split = function(entityId, mode) { action = iD.actions.SplitWay(entityId); var operation = function() { - history.perform(action, 'split a way'); + history.perform(action, 'Split a way.'); }; operation.available = function() { @@ -20,7 +20,7 @@ iD.operations.Split = function(entityId, mode) { operation.id = "split"; operation.key = "X"; operation.title = "Split"; - operation.description = "Split this into two ways at this point"; + operation.description = "Split this into two ways at this point."; return operation; }; diff --git a/js/id/operations/unjoin.js b/js/id/operations/unjoin.js index c18de3029..f38333bb4 100644 --- a/js/id/operations/unjoin.js +++ b/js/id/operations/unjoin.js @@ -3,7 +3,7 @@ iD.operations.Unjoin = function(entityId, mode) { action = iD.actions.UnjoinNode(entityId); var operation = function() { - history.perform(action, 'unjoined lines'); + history.perform(action, 'Unjoined lines.'); }; operation.available = function() { @@ -20,7 +20,7 @@ iD.operations.Unjoin = function(entityId, mode) { operation.id = "unjoin"; operation.key = "⇧-J"; operation.title = "Unjoin"; - operation.description = "Disconnect these ways from each other"; + operation.description = "Disconnect these ways from each other."; return operation; }; From 6ae360a533115cefc2edcbec9475f46598145eda Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 31 Jan 2013 08:28:10 -0500 Subject: [PATCH 046/415] These tests now pass --- test/spec/actions/delete_way.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/spec/actions/delete_way.js b/test/spec/actions/delete_way.js index 863b0fa2f..d550803fa 100644 --- a/test/spec/actions/delete_way.js +++ b/test/spec/actions/delete_way.js @@ -31,8 +31,7 @@ describe("iD.actions.DeleteWay", function () { expect(graph.entity(node.id)).not.to.be.undefined; }); - // See #508 - xit("deletes multiple member nodes", function () { + it("deletes multiple member nodes", function () { var a = iD.Node(), b = iD.Node(), way = iD.Way({nodes: [a.id, b.id]}), @@ -42,7 +41,7 @@ describe("iD.actions.DeleteWay", function () { expect(graph.entity(b.id)).to.be.undefined; }); - xit("deletes a circular way's start/end node", function () { + it("deletes a circular way's start/end node", function () { var a = iD.Node(), b = iD.Node(), c = iD.Node(), From 208ed6a2ed9425a8c2be4c0e8d6a179ede9c4bbe Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 31 Jan 2013 08:30:46 -0500 Subject: [PATCH 047/415] Move iD.geo tests to own file --- test/index.html | 1 + test/index_packaged.html | 1 + test/spec/geo.js | 96 +++++++++++++++++++++++++++++++++++ test/spec/util.js | 107 ++------------------------------------- 4 files changed, 102 insertions(+), 103 deletions(-) create mode 100644 test/spec/geo.js diff --git a/test/index.html b/test/index.html index 731800f62..d8d3f8f6f 100644 --- a/test/index.html +++ b/test/index.html @@ -185,6 +185,7 @@ + diff --git a/test/index_packaged.html b/test/index_packaged.html index aba99ba04..719a5a999 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -79,6 +79,7 @@ + diff --git a/test/spec/geo.js b/test/spec/geo.js new file mode 100644 index 000000000..113d4795d --- /dev/null +++ b/test/spec/geo.js @@ -0,0 +1,96 @@ +describe('iD.geo', function() { + describe('.roundCoords', function() { + expect(iD.geo.roundCoords([0.1, 1])).to.eql([0, 1]); + expect(iD.geo.roundCoords([0, 1])).to.eql([0, 1]); + expect(iD.geo.roundCoords([0, 1.1])).to.eql([0, 1]); + }); + + describe('.interp', function() { + it('interpolates halfway', function() { + var a = [0, 0], + b = [10, 10]; + expect(iD.geo.interp(a, b, 0.5)).to.eql([5, 5]); + }); + it('interpolates to one side', function() { + var a = [0, 0], + b = [10, 10]; + expect(iD.geo.interp(a, b, 0)).to.eql([0, 0]); + }); + }); + + describe('.dist', function() { + it('distance between two same points is zero', function() { + var a = [0, 0], + b = [0, 0]; + expect(iD.geo.dist(a, b)).to.eql(0); + }); + it('a straight 10 unit line is 10', function() { + var a = [0, 0], + b = [10, 0]; + expect(iD.geo.dist(a, b)).to.eql(10); + }); + it('a pythagorean triangle is right', function() { + var a = [0, 0], + b = [4, 3]; + expect(iD.geo.dist(a, b)).to.eql(5); + }); + }); + + describe('.pointInPolygon', function() { + it('says a point in a polygon is on a polygon', function() { + var poly = [[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]; + var point = [0.5, 0.5]; + expect(iD.geo.pointInPolygon(point, poly)).to.be.true; + }); + it('says a point outside of a polygon is outside', function() { + var poly = [ + [0, 0], + [0, 1], + [1, 1], + [1, 0], + [0, 0]]; + var point = [0.5, 1.5]; + expect(iD.geo.pointInPolygon(point, poly)).to.be.false; + }); + }); + + describe('.polygonContainsPolygon', function() { + it('says a polygon in a polygon is in', function() { + var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]]; + var inner = [[1, 1], [1, 2], [2, 2], [2, 1], [1, 1]]; + expect(iD.geo.polygonContainsPolygon(outer, inner)).to.be.true; + }); + it('says a polygon outside of a polygon is out', function() { + var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]]; + var inner = [[1, 1], [1, 9], [2, 2], [2, 1], [1, 1]]; + expect(iD.geo.polygonContainsPolygon(outer, inner)).to.be.false; + }); + }); + + describe('.polygonIntersectsPolygon', function() { + it('says a polygon in a polygon intersects it', function() { + var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]]; + var inner = [[1, 1], [1, 2], [2, 2], [2, 1], [1, 1]]; + expect(iD.geo.polygonIntersectsPolygon(outer, inner)).to.be.true; + }); + + it('says a polygon that partially intersects does', function() { + var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]]; + var inner = [[-1, -1], [1, 2], [2, 2], [2, 1], [1, 1]]; + expect(iD.geo.polygonIntersectsPolygon(outer, inner)).to.be.true; + }); + + it('says totally disjoint polygons do not intersect', function() { + var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]]; + var inner = [[-1, -1], [-1, -2], [-2, -2], [-2, -1], [-1, -1]]; + expect(iD.geo.polygonIntersectsPolygon(outer, inner)).to.be.false; + }); + }); + + describe('.pathLength', function() { + it('calculates a simple path length', function() { + var path = [[0, 0], [0, 1], [3, 5]]; + expect(iD.geo.pathLength(path)).to.eql(6); + }); + }); +}); diff --git a/test/spec/util.js b/test/spec/util.js index 9d5cf082b..de3fab735 100644 --- a/test/spec/util.js +++ b/test/spec/util.js @@ -1,123 +1,24 @@ describe('iD.Util', function() { - var util; - - it('#trueObj', function() { + it('.trueObj', function() { expect(iD.util.trueObj(['a', 'b', 'c'])).to.eql({ a: true, b: true, c: true }); expect(iD.util.trueObj([])).to.eql({}); }); - it('#tagText', function() { + it('.tagText', function() { expect(iD.util.tagText({})).to.eql(''); expect(iD.util.tagText({tags:{foo:'bar'}})).to.eql('foo: bar'); expect(iD.util.tagText({tags:{foo:'bar',two:'three'}})).to.eql('foo: bar\ntwo: three'); }); - it('#stringQs', function() { + it('.stringQs', function() { expect(iD.util.stringQs('foo=bar')).to.eql({foo: 'bar'}); expect(iD.util.stringQs('foo=bar&one=2')).to.eql({foo: 'bar', one: '2' }); expect(iD.util.stringQs('')).to.eql({}); }); - it('#qsString', function() { + it('.qsString', function() { expect(iD.util.qsString({ foo: 'bar' })).to.eql('foo=bar'); expect(iD.util.qsString({ foo: 'bar', one: 2 })).to.eql('foo=bar&one=2'); expect(iD.util.qsString({})).to.eql(''); }); - - describe('geo', function() { - describe('#roundCoords', function() { - expect(iD.geo.roundCoords([0.1, 1])).to.eql([0, 1]); - expect(iD.geo.roundCoords([0, 1])).to.eql([0, 1]); - expect(iD.geo.roundCoords([0, 1.1])).to.eql([0, 1]); - }); - - describe('#interp', function() { - it('interpolates halfway', function() { - var a = [0, 0], - b = [10, 10]; - expect(iD.geo.interp(a, b, 0.5)).to.eql([5, 5]); - }); - it('interpolates to one side', function() { - var a = [0, 0], - b = [10, 10]; - expect(iD.geo.interp(a, b, 0)).to.eql([0, 0]); - }); - }); - - describe('#dist', function() { - it('distance between two same points is zero', function() { - var a = [0, 0], - b = [0, 0]; - expect(iD.geo.dist(a, b)).to.eql(0); - }); - it('a straight 10 unit line is 10', function() { - var a = [0, 0], - b = [10, 0]; - expect(iD.geo.dist(a, b)).to.eql(10); - }); - it('a pythagorean triangle is right', function() { - var a = [0, 0], - b = [4, 3]; - expect(iD.geo.dist(a, b)).to.eql(5); - }); - }); - - describe('#pointInPolygon', function() { - it('says a point in a polygon is on a polygon', function() { - var poly = [[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]; - var point = [0.5, 0.5]; - expect(iD.geo.pointInPolygon(point, poly)).to.be.true; - }); - it('says a point outside of a polygon is outside', function() { - var poly = [ - [0, 0], - [0, 1], - [1, 1], - [1, 0], - [0, 0]]; - var point = [0.5, 1.5]; - expect(iD.geo.pointInPolygon(point, poly)).to.be.false; - }); - }); - - describe('#polygonContainsPolygon', function() { - it('says a polygon in a polygon is in', function() { - var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]]; - var inner = [[1, 1], [1, 2], [2, 2], [2, 1], [1, 1]]; - expect(iD.geo.polygonContainsPolygon(outer, inner)).to.be.true; - }); - it('says a polygon outside of a polygon is out', function() { - var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]]; - var inner = [[1, 1], [1, 9], [2, 2], [2, 1], [1, 1]]; - expect(iD.geo.polygonContainsPolygon(outer, inner)).to.be.false; - }); - }); - - describe('#polygonIntersectsPolygon', function() { - it('says a polygon in a polygon intersects it', function() { - var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]]; - var inner = [[1, 1], [1, 2], [2, 2], [2, 1], [1, 1]]; - expect(iD.geo.polygonIntersectsPolygon(outer, inner)).to.be.true; - }); - - it('says a polygon that partially intersects does', function() { - var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]]; - var inner = [[-1, -1], [1, 2], [2, 2], [2, 1], [1, 1]]; - expect(iD.geo.polygonIntersectsPolygon(outer, inner)).to.be.true; - }); - - it('says totally disjoint polygons do not intersect', function() { - var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]]; - var inner = [[-1, -1], [-1, -2], [-2, -2], [-2, -1], [-1, -1]]; - expect(iD.geo.polygonIntersectsPolygon(outer, inner)).to.be.false; - }); - }); - - describe('#pathLength', function() { - it('calculates a simple path length', function() { - var path = [[0, 0], [0, 1], [3, 5]]; - expect(iD.geo.pathLength(path)).to.eql(6); - }); - }); - }); }); From edb3a3b68210e26b3eb0548dfae2dceb4ee00180 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Thu, 31 Jan 2013 10:03:43 -0500 Subject: [PATCH 048/415] Entire notice is clickable. Fixes #262 --- css/app.css | 16 ++++++++++++---- js/id/ui/notice.js | 21 +++++++++------------ 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/css/app.css b/css/app.css index 98ba49e57..4c149843d 100644 --- a/css/app.css +++ b/css/app.css @@ -1052,7 +1052,8 @@ a.success-action { text-align:center; } -.notice .notice-inner { +.notice .zoom-to { + width:100%; height: 40px; border-radius: 5px; line-height: 40px; @@ -1061,12 +1062,19 @@ a.success-action { opacity: 0.9; } -.notice .notice-inner .zoom-to { - width:40px; - height:40px; +.notice .zoom-to:hover { + background: #bde5aa; +} + +.notice .zoom-to .icon { + margin-top:10px; margin-right:10px; } +.icon.zoom-in-invert { + background-position: -240px -40px; +} + /* Tooltips ------------------------------------------------------- */ diff --git a/js/id/ui/notice.js b/js/id/ui/notice.js index bcf44b7c9..12b1a2c09 100644 --- a/js/id/ui/notice.js +++ b/js/id/ui/notice.js @@ -4,20 +4,17 @@ iD.ui.notice = function(selection) { notice = {}; var div = selection.append('div') - .attr('class', 'notice') - .append('div') - .attr('class', 'notice-inner'); + .attr('class', 'notice'); - div.append('button') - .attr('class', 'zoom-to') - .on('click', function() { - event.zoom(); - }) - .append('span') - .attr('class', 'icon invert zoom-in'); + var button = div.append('button') + .attr('class', 'zoom-to notice') + .on('click', event.zoom); - div.append('span') - .attr('class', 'notice-text') + button.append('span') + .attr('class', 'icon zoom-in-invert'); + + button.append('span') + .attr('class', 'label') .text(t('zoom_in_edit')); notice.message = function(_) { From 5c1d3cc18e41b4aba60d6f4058da47da0a818489 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Thu, 31 Jan 2013 10:34:57 -0500 Subject: [PATCH 049/415] Preserve label tree between partial redraws --- js/id/renderer/map.js | 2 +- js/id/svg/labels.js | 29 ++++++++++++++++++++++------- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index 0d1532948..08c18e915 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -112,7 +112,7 @@ iD.Map = function() { .call(areas, graph, all, filter) .call(multipolygons, graph, all, filter) .call(midpoints, graph, all, filter) - .call(labels, graph, all, filter, dimensions); + .call(labels, graph, all, filter, dimensions, !difference); } dispatch.drawn(map); } diff --git a/js/id/svg/labels.js b/js/id/svg/labels.js index 2fcc2fcc1..5f300f147 100644 --- a/js/id/svg/labels.js +++ b/js/id/svg/labels.js @@ -183,14 +183,26 @@ iD.svg.Labels = function(projection) { } - return function drawLabels(surface, graph, entities, filter, dimensions) { + var rtree = new RTree(), + rectangles = {}; + + return function drawLabels(surface, graph, entities, filter, dimensions, fullRedraw) { + - var rtree = new RTree(); var hidePoints = !d3.select('.node.point').node(); var labelable = [], i, k, entity; for (i = 0; i < label_stack.length; i++) labelable.push([]); + if (fullRedraw) { + rtree = new RTree(); + rectangles = {}; + } else { + for (i = 0; i < entities.length; i++) { + rtree.remove(rectangles[entities[i].id], entities[i].id); + } + } + // Split entities into groups specified by label_stack for (i = 0; i < entities.length; i++) { entity = entities[i]; @@ -252,7 +264,7 @@ iD.svg.Labels = function(projection) { textAnchor: offset[2] }; var rect = new RTree.Rectangle(p.x - m, p.y - m, width + 2*m, height + 2*m); - if (tryInsert(rect)) return p; + if (tryInsert(rect, entity.id)) return p; } @@ -275,7 +287,7 @@ iD.svg.Labels = function(projection) { Math.abs(sub[0][1] - sub[sub.length - 1][1]) + 30 ); if (rev) sub = sub.reverse(); - if (tryInsert(rect)) return { + if (tryInsert(rect, entity.id)) return { 'font-size': height + 2, lineString: lineString(sub), startOffset: offset + '%' @@ -298,16 +310,19 @@ iD.svg.Labels = function(projection) { height: height }; var rect = new RTree.Rectangle(p.x - width/2, p.y, width, height); - if (tryInsert(rect)) return p; + if (tryInsert(rect, entity.id)) return p; } - function tryInsert(rect) { + function tryInsert(rect, id) { // Check that label is visible if (rect.x1 < 0 || rect.y1 < 0 || rect.x2 > dimensions[0] || rect.y2 > dimensions[1]) return false; var v = rtree.search(rect, true).length === 0; - if (v) rtree.insert(rect); + if (v) { + rtree.insert(rect, id); + rectangles[id] = rect; + } return v; } From c69345e0398cf7c94b633dfcc5f0db621bf3a63d Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Thu, 31 Jan 2013 10:50:14 -0500 Subject: [PATCH 050/415] Fix comparisons in `subpath` --- js/id/svg/labels.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/id/svg/labels.js b/js/id/svg/labels.js index 5f300f147..3962c4915 100644 --- a/js/id/svg/labels.js +++ b/js/id/svg/labels.js @@ -156,7 +156,7 @@ iD.svg.Labels = function(projection) { for (var i = 0; i < nodes.length - 1; i++) { var current = segmentLength(i); var portion; - if (!start && sofar + current > from) { + if (!start && sofar + current >= from) { portion = (from - sofar) / current; start = [ nodes[i][0] + portion * (nodes[i + 1][0] - nodes[i][0]), @@ -164,7 +164,7 @@ iD.svg.Labels = function(projection) { ]; i0 = i + 1; } - if (!end && sofar + current > to) { + if (!end && sofar + current >= to) { portion = (to - sofar) / current; end = [ nodes[i][0] + portion * (nodes[i + 1][0] - nodes[i][0]), From 2c2efe3b65ea3ccb37a4c113defc9940285c1962 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Thu, 31 Jan 2013 11:02:41 -0500 Subject: [PATCH 051/415] Background refinements. Fixes #561, fixes #456 --- js/id/renderer/background.js | 136 ++++++++++++++++++----------------- 1 file changed, 70 insertions(+), 66 deletions(-) diff --git a/js/id/renderer/background.js b/js/id/renderer/background.js index 6476e7a28..752853e6b 100644 --- a/js/id/renderer/background.js +++ b/js/id/renderer/background.js @@ -1,4 +1,10 @@ iD.Background = function() { + + var deviceRatio = (window.devicePixelRatio && + window.devicePixelRatio === 2) ? 0.5 : 1; + // tileSize = (deviceRatio === 0.5) ? [128,128] : [256,256]; + tileSize = [256, 256]; + var tile = d3.geo.tile(), projection, cache = {}, @@ -13,7 +19,12 @@ iD.Background = function() { '-o-transform-origin:0 0;' + '-webkit-user-select: none;' + '-webkit-user-drag: none;' + - '-moz-user-drag: none;'; + '-moz-user-drag: none;' + + 'opacity:0;'; + + function tileSizeAtZoom(d, z) { + return Math.ceil(tileSize[0] * Math.pow(2, z - d[2])) / tileSize[0]; + } function atZoom(t, distance) { var power = Math.pow(2, distance); @@ -21,108 +32,101 @@ iD.Background = function() { Math.floor(t[0] * power), Math.floor(t[1] * power), t[2] + distance]; - az.push(source(az)); return az; } - function upZoom(t, distance) { - var az = atZoom(t, distance), tiles = []; - for (var x = 0; x < 2; x++) { - for (var y = 0; y < 2; y++) { - var up = [az[0] + x, az[1] + y, az[2]]; - up.push(source(up)); - tiles.push(up); - } - } - return tiles; - } - - function tileSize(d, z) { - return Math.ceil(256 * Math.pow(2, z - d[2])) / 256; - } - function lookUp(d) { for (var up = -1; up > -d[2]; up--) { if (cache[atZoom(d, up)] !== false) return atZoom(d, up); } } + function uniqueBy(a, n) { + var o = [], seen = {}; + for (var i = 0; i < a.length; i++) { + if (seen[a[i][n]] === undefined) { + o.push(a[i]); + seen[a[i][n]] = true; + } + } + return o; + } + + function addSource(d) { + d.push(source(d)); + return d; + } + // derive the tiles onscreen, remove those offscreen and position tiles // correctly for the currentstate of `projection` function background() { - var tiles = tile + var sel = this, + tiles = tile .scale(projection.scale()) .scaleExtent(source.scaleExtent || [1, 17]) .translate(projection.translate())(), + requests = [], scaleExtent = tile.scaleExtent(), z = Math.max(Math.log(projection.scale()) / Math.log(2) - 8, 0), - rz = Math.max(scaleExtent[0], Math.min(scaleExtent[1], Math.floor(z))), - ts = 256 * Math.pow(2, z - rz), + rz = Math.max(scaleExtent[0], + Math.min(scaleExtent[1], Math.floor(z))), + ts = tileSize[0] * Math.pow(2, z - rz), tile_origin = [ projection.scale() / 2 - projection.translate()[0], - projection.scale() / 2 - projection.translate()[1]], - ups = {}; + projection.scale() / 2 - projection.translate()[1]]; tiles.forEach(function(d) { - - if (cache[d] === true) { - d.push(source(d)); - } else if (cache[d] === false && - cache[atZoom(d, -1)] !== false && - !ups[atZoom(d, -1)]) { - - ups[atZoom(d, -1)] = true; - tiles.push(atZoom(d, -1)); - - } else if (cache[d] === undefined && - lookUp(d)) { - - var upTile = lookUp(d); - if (!ups[upTile]) { - ups[upTile] = true; - tiles.push(upTile); - } - - } else if (cache[d] === undefined || - cache[d] === false) { - upZoom(d, 1).forEach(function(u) { - if (cache[u] && !ups[u]) { - ups[u] = true; - tiles.push(u); - } - }); + addSource(d); + requests.push(d); + if (!cache[d[3]] && lookUp(d)) { + requests.push(addSource(lookUp(d))); } }); - var image = this - .selectAll('img') - .data(tiles, function(d) { return d; }); + requests = uniqueBy(requests, 3); function load(d) { - cache[d.slice(0, 3)] = true; - d3.select(this).on('load', null); + cache[d[3]] = true; + d3.select(this) + .on('load', null) + .transition() + .style('opacity', 1); + background.apply(sel); } function error(d) { - cache[d.slice(0, 3)] = false; + cache[d[3]] = false; + d3.select(this).on('load', null); d3.select(this).remove(); + background.apply(sel); } + function imageTransform(d) { + var _ts = tileSize[0] * Math.pow(2, z - d[2]); + var scale = tileSizeAtZoom(d, z); + return 'translate(' + + (Math.round((d[0] * _ts) - tile_origin[0]) + offset[0]) + 'px,' + + (Math.round((d[1] * _ts) - tile_origin[1]) + offset[1]) + 'px)' + + 'scale(' + scale + ',' + scale + ')'; + } + + var image = this + .selectAll('img') + .data(requests, function(d) { return d[3]; }); + + image.exit() + .style(transformProp, imageTransform) + .transition() + .style('opacity', 0) + .remove(); + image.enter().append('img') .attr('style', imgstyle) .attr('src', function(d) { return d[3]; }) .on('error', error) .on('load', load); - - image.exit().remove(); - - image.style(transformProp, function(d) { - var _ts = 256 * Math.pow(2, z - d[2]); - var scale = tileSize(d, z); - return 'translate(' + - (Math.round((d[0] * _ts) - tile_origin[0]) + offset[0]) + 'px,' + - (Math.round((d[1] * _ts) - tile_origin[1]) + offset[1]) + 'px) scale(' + scale + ',' + scale + ')'; - }); + + image.style(transformProp, imageTransform); if (Object.keys(cache).length > 100) cache = {}; } From f0414f7ca11915eb1110bed71cd82dd6214cacc3 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Thu, 31 Jan 2013 11:17:32 -0500 Subject: [PATCH 052/415] Fix call to flash --- js/id/renderer/map.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index 08c18e915..04521bdd7 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -136,7 +136,7 @@ iD.Map = function() { } if (Math.log(d3.event.scale / Math.LN2 - 8) < minzoom + 1) { - iD.flash() + iD.ui.flash() .select('.content') .text('Cannot zoom out further in current mode.'); return map.zoom(16); From 7e8126ded984102eea9e286b25bc76f4e4fd1ad4 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 31 Jan 2013 11:20:34 -0500 Subject: [PATCH 053/415] I18n for modes and operations --- js/id/behavior/drag_midpoint.js | 2 +- js/id/behavior/drag_node.js | 10 ++- js/id/behavior/draw_way.js | 14 ++-- js/id/graph/validate.js | 8 +-- js/id/modes/add_area.js | 8 +-- js/id/modes/add_line.js | 8 +-- js/id/modes/add_point.js | 17 +++-- js/id/modes/browse.js | 6 +- js/id/modes/draw_area.js | 5 +- js/id/modes/draw_line.js | 7 +- js/id/modes/move_way.js | 12 ++-- js/id/modes/select.js | 4 +- js/id/operations/circularize.js | 21 ++---- js/id/operations/delete.js | 34 +++------ js/id/operations/move.js | 6 +- js/id/operations/reverse.js | 8 +-- js/id/operations/split.js | 8 +-- js/id/operations/unjoin.js | 6 +- locale/en.js | 120 ++++++++++++++++++++++++++++++-- locale/locale.js | 8 ++- 20 files changed, 198 insertions(+), 114 deletions(-) diff --git a/js/id/behavior/drag_midpoint.js b/js/id/behavior/drag_midpoint.js index bd1a40bd9..6041946cd 100644 --- a/js/id/behavior/drag_midpoint.js +++ b/js/id/behavior/drag_midpoint.js @@ -25,7 +25,7 @@ iD.behavior.DragMidpoint = function(mode) { .on('end', function() { history.replace( iD.actions.Noop(), - 'Added a node to a way.'); + t('operations.add.annotation.vertex')); }); return behavior; diff --git a/js/id/behavior/drag_node.js b/js/id/behavior/drag_node.js index 1bfddfed6..b9846617d 100644 --- a/js/id/behavior/drag_node.js +++ b/js/id/behavior/drag_node.js @@ -25,6 +25,10 @@ iD.behavior.DragNode = function(mode) { nudgeInterval = null; } + function annotation(entity) { + return t('operations.move.annotation.' + entity.geometry(mode.history.graph())); + } + return iD.behavior.drag() .delegate(".node") .origin(function(entity) { @@ -43,12 +47,12 @@ iD.behavior.DragNode = function(mode) { history.replace( iD.actions.MoveNode(entity.id, projection.invert(d3.event.point)), - 'moved a node'); + annotation(entity)); }) - .on('end', function() { + .on('end', function(entity) { stopNudge(); history.replace( iD.actions.Noop(), - 'moved a node'); + annotation(entity)); }); }; diff --git a/js/id/behavior/draw_way.js b/js/id/behavior/draw_way.js index 21c13d2fa..c8341337c 100644 --- a/js/id/behavior/draw_way.js +++ b/js/id/behavior/draw_way.js @@ -2,9 +2,11 @@ iD.behavior.DrawWay = function(wayId, index, mode, baseGraph) { var map = mode.map, history = mode.history, controller = mode.controller, - way = mode.history.graph().entity(wayId), + way = history.graph().entity(wayId), finished = false, - annotation = 'Added to a way.', + annotation = t((way.isDegenerate() ? + 'operations.start.annotation.' : + 'operations.continue.annotation.') + way.geometry(history.graph())), draw = iD.behavior.Draw(map); var node = iD.Node({loc: map.mouseCoordinates()}), @@ -71,12 +73,6 @@ iD.behavior.DrawWay = function(wayId, index, mode, baseGraph) { history.on('undone.draw', null); }; - drawWay.annotation = function(_) { - if (!arguments.length) return annotation; - annotation = _; - return drawWay; - }; - function ReplaceTemporaryNode(newNode) { return function(graph) { return graph @@ -153,7 +149,7 @@ iD.behavior.DrawWay = function(wayId, index, mode, baseGraph) { drawWay.cancel = function() { history.perform( d3.functor(baseGraph), - 'Cancelled drawing.'); + t('operations.cancel_draw.annotation')); finished = true; controller.enter(iD.modes.Browse()); diff --git a/js/id/graph/validate.js b/js/id/graph/validate.js index 805b775c3..aa7d8771b 100644 --- a/js/id/graph/validate.js +++ b/js/id/graph/validate.js @@ -21,22 +21,22 @@ iD.validate = function(changes, graph) { if (change.geometry(graph) === 'point' && _.isEmpty(change.tags)) { warnings.push({ - message: 'Untagged point which is not part of a line or area', + message: t('validations.untagged_point'), entity: change }); } if (change.geometry(graph) === 'line' && _.isEmpty(change.tags)) { - warnings.push({ message: 'Untagged line', entity: change }); + warnings.push({ message: t('validations.untagged_line'), entity: change }); } if (change.geometry(graph) === 'area' && _.isEmpty(change.tags)) { - warnings.push({ message: 'Untagged area', entity: change }); + warnings.push({ message: t('validations.untagged_area'), entity: change }); } if (change.geometry(graph) === 'line' && tagSuggestsArea(change)) { warnings.push({ - message: 'The tag ' + tagSuggestsArea(change) + ' suggests line should be area, but it is not and area', + message: t('validations.tag_suggests_area', {tag: tagSuggestsArea(change)}), entity: change }); } diff --git a/js/id/modes/add_area.js b/js/id/modes/add_area.js index 614c66677..03b7582cc 100644 --- a/js/id/modes/add_area.js +++ b/js/id/modes/add_area.js @@ -2,9 +2,9 @@ iD.modes.AddArea = function() { var mode = { id: 'add-area', button: 'area', - title: 'Area', - description: 'Add parks, buildings, lakes, or other areas to the map.', - key: 'a' + title: t('modes.add_area.title'), + description: t('modes.add_area.description'), + key: t('modes.add_area.key') }; var behavior, @@ -77,7 +77,7 @@ iD.modes.AddArea = function() { .on('startFromMidpoint', startFromMidpoint); mode.map.surface.call(behavior); - mode.map.tail('Click on the map to start drawing an area, like a park, lake, or building.', true); + mode.map.tail(t('modes.add_area.tail'), true); }; mode.exit = function() { diff --git a/js/id/modes/add_line.js b/js/id/modes/add_line.js index 90adac738..c40a78fce 100644 --- a/js/id/modes/add_line.js +++ b/js/id/modes/add_line.js @@ -2,9 +2,9 @@ iD.modes.AddLine = function() { var mode = { id: 'add-line', button: 'line', - title: 'Line', - description: 'Lines can be highways, streets, pedestrian paths, or even canals.', - key: 'l' + title: t('modes.add_line.title'), + description: t('modes.add_line.description'), + key: t('modes.add_line.key') }; var behavior, @@ -84,7 +84,7 @@ iD.modes.AddLine = function() { .on('startFromMidpoint', startFromMidpoint); mode.map.surface.call(behavior); - mode.map.tail('Click on the map to start drawing an road, path, or route.', true); + mode.map.tail(t('modes.add_line.tail'), true); }; mode.exit = function() { diff --git a/js/id/modes/add_point.js b/js/id/modes/add_point.js index 1b9781a9d..5db73dc0f 100644 --- a/js/id/modes/add_point.js +++ b/js/id/modes/add_point.js @@ -1,27 +1,24 @@ iD.modes.AddPoint = function() { var mode = { id: 'add-point', - title: 'Point', - description: 'Restaurants, monuments, and postal boxes are points.', - key: 'p' + title: t('modes.add_point.title'), + description: t('modes.add_point.description'), + key: t('modes.add_point.key') }; var behavior; mode.enter = function() { var map = mode.map, - surface = map.surface, history = mode.history, controller = mode.controller; - map.tail('Click on the map to add a point.', true); - function add(loc) { var node = iD.Node({loc: loc}); history.perform( iD.actions.AddNode(node), - 'Added a point.'); + t('operations.add.annotation.point')); controller.enter(iD.modes.Select(node, true)); } @@ -44,8 +41,10 @@ iD.modes.AddPoint = function() { .on('clickNode', addNode) .on('clickMidpoint', addNode) .on('cancel', cancel) - .on('finish', cancel) - (surface); + .on('finish', cancel); + + mode.map.surface.call(behavior); + mode.map.tail(t('modes.add_point.tail'), true); }; mode.exit = function() { diff --git a/js/id/modes/browse.js b/js/id/modes/browse.js index ab6bc80cb..c7358bdea 100644 --- a/js/id/modes/browse.js +++ b/js/id/modes/browse.js @@ -2,9 +2,9 @@ iD.modes.Browse = function() { var mode = { button: 'browse', id: 'browse', - title: 'Browse', - description: 'Pan and zoom the map.', - key: 'b' + title: t('modes.browse.title'), + description: t('modes.browse.description'), + key: t('modes.browse.key') }; var behaviors; diff --git a/js/id/modes/draw_area.js b/js/id/modes/draw_area.js index 8423ff436..fd26d5eda 100644 --- a/js/id/modes/draw_area.js +++ b/js/id/modes/draw_area.js @@ -11,8 +11,7 @@ iD.modes.DrawArea = function(wayId, baseGraph) { headId = way.nodes[way.nodes.length - 2], tailId = way.first(); - behavior = iD.behavior.DrawWay(wayId, -1, mode, baseGraph) - .annotation(way.isDegenerate() ? 'started an area' : 'continued an area'); + behavior = iD.behavior.DrawWay(wayId, -1, mode, baseGraph); var addNode = behavior.addNode; @@ -25,7 +24,7 @@ iD.modes.DrawArea = function(wayId, baseGraph) { }; mode.map.surface.call(behavior); - mode.map.tail('Click to add points to your area. Click the first point to finish the area.', true); + mode.map.tail(t('modes.draw_area.tail'), true); }; mode.exit = function() { diff --git a/js/id/modes/draw_line.js b/js/id/modes/draw_line.js index ae54ab2e1..0596c9065 100644 --- a/js/id/modes/draw_line.js +++ b/js/id/modes/draw_line.js @@ -11,8 +11,7 @@ iD.modes.DrawLine = function(wayId, direction, baseGraph) { index = (direction === 'forward') ? undefined : 0, headId = (direction === 'forward') ? way.last() : way.first(); - behavior = iD.behavior.DrawWay(wayId, index, mode, baseGraph) - .annotation(way.isDegenerate() ? 'Started a line.' : 'Continued a line.'); + behavior = iD.behavior.DrawWay(wayId, index, mode, baseGraph); var addNode = behavior.addNode; @@ -25,9 +24,7 @@ iD.modes.DrawLine = function(wayId, direction, baseGraph) { }; mode.map.surface.call(behavior); - mode.map.tail('Click to add more points to the line. ' + - 'Click on other lines to connect to them, and double-click to ' + - 'end the line.', true); + mode.map.tail(t('modes.draw_line.tail'), true); }; mode.exit = function() { diff --git a/js/id/modes/move_way.js b/js/id/modes/move_way.js index 18c6ee619..d977f6719 100644 --- a/js/id/modes/move_way.js +++ b/js/id/modes/move_way.js @@ -11,14 +11,14 @@ iD.modes.MoveWay = function(wayId) { graph = history.graph(), selection = map.surface, controller = mode.controller, - projection = map.projection; - - var way = graph.entity(wayId), - origin = d3.mouse(selection.node()); + projection = map.projection, + way = graph.entity(wayId), + origin = d3.mouse(selection.node()), + annotation = t('operations.move.annotation.' + way.geometry(graph)); history.perform( iD.actions.Noop(), - 'Moved a way.'); + annotation); function move() { var p = d3.mouse(selection.node()), @@ -29,7 +29,7 @@ iD.modes.MoveWay = function(wayId) { history.replace( iD.actions.MoveWay(wayId, delta, projection), - 'Moved a way.'); + annotation); } function finish() { diff --git a/js/id/modes/select.js b/js/id/modes/select.js index 1ddd4ffc4..455f94913 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -14,7 +14,7 @@ iD.modes.Select = function(entity, initial) { if (!_.isEqual(entity.tags, tags)) { mode.history.perform( iD.actions.ChangeEntityTags(d.id, tags), - 'Changed tags.'); + t('operations.change_tags.annotation')); } } @@ -114,7 +114,7 @@ iD.modes.Select = function(entity, initial) { history.perform( iD.actions.AddNode(node), iD.actions.AddWayNode(datum.id, node.id, choice.index), - 'Added a point to a road.'); + t('operations.add.annotation.vertex')); d3.event.preventDefault(); d3.event.stopPropagation(); diff --git a/js/id/operations/circularize.js b/js/id/operations/circularize.js index ce161aef4..91921dc2a 100644 --- a/js/id/operations/circularize.js +++ b/js/id/operations/circularize.js @@ -5,24 +5,15 @@ iD.operations.Circularize = function(entityId, mode) { var operation = function() { var graph = history.graph(), entity = graph.entity(entityId), - geometry = entity.geometry(graph); + annotation = t('operations.circularize.annotation.' + entity.geometry(graph)); - if (geometry === 'line') { - history.perform( - action, - 'Made a line circular.'); - - } else if (geometry === 'area') { - history.perform( - action, - 'Made an area circular.'); - } + history.perform(action, annotation); }; operation.available = function() { var graph = history.graph(), entity = graph.entity(entityId); - return entity.geometry(graph) === 'area' || entity.geometry(graph) === 'line'; + return entity.type === 'way'; }; operation.enabled = function() { @@ -31,9 +22,9 @@ iD.operations.Circularize = function(entityId, mode) { }; operation.id = "circularize"; - operation.key = "O"; - operation.title = "Circularize"; - operation.description = "Make this round"; + operation.key = t('operations.circularize.key'); + operation.title = t('operations.circularize.title'); + operation.description = t('operations.circularize.description'); return operation; }; diff --git a/js/id/operations/delete.js b/js/id/operations/delete.js index cc3750360..993d3eef4 100644 --- a/js/id/operations/delete.js +++ b/js/id/operations/delete.js @@ -4,34 +4,18 @@ iD.operations.Delete = function(entityId, mode) { var operation = function() { var graph = history.graph(), entity = graph.entity(entityId), - geometry = entity.geometry(graph); + action = {way: iD.actions.DeleteWay, node: iD.actions.DeleteNode}[entity.type], + annotation = t('operations.delete.annotation.' + entity.geometry(graph)); - if (geometry === 'vertex') { - history.perform( - iD.actions.DeleteNode(entityId), - 'Deleted a vertex.'); - - } else if (geometry === 'point') { - history.perform( - iD.actions.DeleteNode(entityId), - 'Deleted a point.'); - - } else if (geometry === 'line') { - history.perform( - iD.actions.DeleteWay(entityId), - 'Deleted a line.'); - - } else if (geometry === 'area') { - history.perform( - iD.actions.DeleteWay(entityId), - 'Deleted an area.'); - } + history.perform( + action(entityId), + annotation); }; operation.available = function() { var graph = history.graph(), entity = graph.entity(entityId); - return _.contains(['vertex', 'point', 'line', 'area'], entity.geometry(graph)); + return entity.type === 'way' || entity.type === 'node'; }; operation.enabled = function() { @@ -39,9 +23,9 @@ iD.operations.Delete = function(entityId, mode) { }; operation.id = "delete"; - operation.key = "⌫"; - operation.title = "Delete"; - operation.description = "Remove this from the map."; + operation.key = t('operations.delete.key'); + operation.title = t('operations.delete.title'); + operation.description = t('operations.delete.description'); return operation; }; diff --git a/js/id/operations/move.js b/js/id/operations/move.js index 383ca4206..5034c2f75 100644 --- a/js/id/operations/move.js +++ b/js/id/operations/move.js @@ -15,9 +15,9 @@ iD.operations.Move = function(entityId, mode) { }; operation.id = "move"; - operation.key = "M"; - operation.title = "Move"; - operation.description = "Move this to a different location"; + operation.key = t('operations.move.key'); + operation.title = t('operations.move.title'); + operation.description = t('operations.move.description'); return operation; }; diff --git a/js/id/operations/reverse.js b/js/id/operations/reverse.js index 62fd8810b..ca9dd6440 100644 --- a/js/id/operations/reverse.js +++ b/js/id/operations/reverse.js @@ -4,7 +4,7 @@ iD.operations.Reverse = function(entityId, mode) { var operation = function() { history.perform( iD.actions.ReverseWay(entityId), - 'Reversed a line.'); + t('operations.reverse.annotation')); }; operation.available = function() { @@ -18,9 +18,9 @@ iD.operations.Reverse = function(entityId, mode) { }; operation.id = "reverse"; - operation.key = "V"; - operation.title = "Reverse"; - operation.description = "Make this way go in the opposite direction."; + operation.key = t('operations.reverse.key'); + operation.title = t('operations.reverse.title'); + operation.description = t('operations.reverse.description'); return operation; }; diff --git a/js/id/operations/split.js b/js/id/operations/split.js index 40aa55fd0..ff3f8af20 100644 --- a/js/id/operations/split.js +++ b/js/id/operations/split.js @@ -3,7 +3,7 @@ iD.operations.Split = function(entityId, mode) { action = iD.actions.SplitWay(entityId); var operation = function() { - history.perform(action, 'Split a way.'); + history.perform(action, t('operations.split.annotation')); }; operation.available = function() { @@ -18,9 +18,9 @@ iD.operations.Split = function(entityId, mode) { }; operation.id = "split"; - operation.key = "X"; - operation.title = "Split"; - operation.description = "Split this into two ways at this point."; + operation.key = t('operations.split.key'); + operation.title = t('operations.split.title'); + operation.description = t('operations.split.description'); return operation; }; diff --git a/js/id/operations/unjoin.js b/js/id/operations/unjoin.js index f38333bb4..175c13973 100644 --- a/js/id/operations/unjoin.js +++ b/js/id/operations/unjoin.js @@ -18,9 +18,9 @@ iD.operations.Unjoin = function(entityId, mode) { }; operation.id = "unjoin"; - operation.key = "⇧-J"; - operation.title = "Unjoin"; - operation.description = "Disconnect these ways from each other."; + operation.key = t('operations.unjoin.key'); + operation.title = t('operations.unjoin.title'); + operation.description = t('operations.unjoin.description'); return operation; }; diff --git a/locale/en.js b/locale/en.js index 2a011a734..d7fc3628f 100644 --- a/locale/en.js +++ b/locale/en.js @@ -1,8 +1,118 @@ locale.en = { - "browse": "Browse", - "point": "Point", - "line": "Line", - "area": "Area", + modes: { + add_area: { + title: "Area", + description: "Add parks, buildings, lakes, or other areas to the map.", + tail: "Click on the map to start drawing an area, like a park, lake, or building.", + key: "A" + }, + add_line: { + title: "Line", + description: "Lines can be highways, streets, pedestrian paths, or even canals.", + tail: "Click on the map to start drawing an road, path, or route.", + key: "L" + }, + add_point: { + title: "Point", + description: "Restaurants, monuments, and postal boxes are points.", + tail: "Click on the map to add a point.", + key: "P" + }, + browse: { + title: "Browse", + description: "Pan and zoom the map.", + key: "B" + }, + draw_area: { + tail: "Click to add points to your area. Click the first point to finish the area." + }, + draw_line: { + tail: "Click to add more points to the line. Click on other lines to connect to them, and double-click to end the line." + } + }, + + operations: { + add: { + annotation: { + point: "Added a point.", + vertex: "Added a node to a way." + } + }, + start: { + annotation: { + line: "Started a line.", + area: "Started an area." + } + }, + continue: { + annotation: { + line: "Continued a line.", + area: "Continued an area." + } + }, + cancel_draw: { + annotation: "Cancelled drawing." + }, + change_tags: { + annotation: "Changed tags." + }, + circularize: { + title: "Circularize", + description: "Make this round.", + key: "O", + annotation: { + line: "Made a line circular.", + area: "Made an area circular." + } + }, + delete: { + title: "Delete", + description: "Remove this from the map.", + key: "⌫", + annotation: { + point: "Deleted a point.", + vertex: "Deleted a node from a way.", + line: "Deleted a line.", + area: "Deleted an area." + } + }, + move: { + title: "Move", + description: "Move this to a different location.", + key: "M", + annotation: { + point: "Moved a point.", + vertex: "Moved a node in a way.", + line: "Moved a line.", + area: "Moved an area." + } + }, + reverse: { + title: "Reverse", + description: "Make this line go in the opposite direction.", + key: "V", + annotation: "Reversed a line." + }, + split: { + title: "Split", + description: "Split this into two ways at this point.", + key: "X", + annotation: "Split a way." + }, + unjoin: { + title: "Unjoin", + description: "Disconnect these ways from each other.", + key: "⇧-J", + annotation: "Unjoined ways." + } + }, + + validations: { + untagged_point: "Untagged point which is not part of a line or area", + untagged_line: "Untagged line", + untagged_area: "Untagged area", + tag_suggests_area: "The tag {tag} suggests line should be area, but it is not an area" + }, "save": "Save", "save_help": "Save changes to OpenStreetMap, making them visible to other users", @@ -37,4 +147,4 @@ locale.en = { "layers": "Layers", "percent_opacity": "{opacity}% opacity" -} +}; diff --git a/locale/locale.js b/locale/locale.js index 9e4786059..291b255a0 100644 --- a/locale/locale.js +++ b/locale/locale.js @@ -1,8 +1,12 @@ var locale = { current: 'en' }; function t(s, o) { - if (locale[locale.current][s] !== undefined) { - var rep = locale[locale.current][s]; + var path = s.split(".").reverse(), + rep = locale[locale.current]; + + while (rep !== undefined && path.length) rep = rep[path.pop()]; + + if (rep !== undefined) { if (o) for (var k in o) rep = rep.replace('{' + k + '}', o[k]); return rep; } else { From 806ec9fae329c280e962fc41c53a884969ae1cec Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Thu, 31 Jan 2013 11:39:51 -0500 Subject: [PATCH 054/415] Add google analytics. --- index.html | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/index.html b/index.html index 5f62b0f8f..8c777292e 100644 --- a/index.html +++ b/index.html @@ -140,4 +140,15 @@ d3.select("#iD").call(id); }); + + From 2655810efe1721d78bf1f7101bb405a9937a99fa Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 31 Jan 2013 11:55:01 -0500 Subject: [PATCH 055/415] Remove unused parameter --- js/id/modes/add_area.js | 2 +- js/id/modes/add_line.js | 2 +- js/id/modes/add_point.js | 2 +- js/id/modes/draw_area.js | 2 +- js/id/modes/draw_line.js | 2 +- js/id/renderer/map.js | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/js/id/modes/add_area.js b/js/id/modes/add_area.js index 03b7582cc..4519c3d7e 100644 --- a/js/id/modes/add_area.js +++ b/js/id/modes/add_area.js @@ -77,7 +77,7 @@ iD.modes.AddArea = function() { .on('startFromMidpoint', startFromMidpoint); mode.map.surface.call(behavior); - mode.map.tail(t('modes.add_area.tail'), true); + mode.map.tail(t('modes.add_area.tail')); }; mode.exit = function() { diff --git a/js/id/modes/add_line.js b/js/id/modes/add_line.js index c40a78fce..4a49e4955 100644 --- a/js/id/modes/add_line.js +++ b/js/id/modes/add_line.js @@ -84,7 +84,7 @@ iD.modes.AddLine = function() { .on('startFromMidpoint', startFromMidpoint); mode.map.surface.call(behavior); - mode.map.tail(t('modes.add_line.tail'), true); + mode.map.tail(t('modes.add_line.tail')); }; mode.exit = function() { diff --git a/js/id/modes/add_point.js b/js/id/modes/add_point.js index 5db73dc0f..74389cb0c 100644 --- a/js/id/modes/add_point.js +++ b/js/id/modes/add_point.js @@ -44,7 +44,7 @@ iD.modes.AddPoint = function() { .on('finish', cancel); mode.map.surface.call(behavior); - mode.map.tail(t('modes.add_point.tail'), true); + mode.map.tail(t('modes.add_point.tail')); }; mode.exit = function() { diff --git a/js/id/modes/draw_area.js b/js/id/modes/draw_area.js index fd26d5eda..9801a092c 100644 --- a/js/id/modes/draw_area.js +++ b/js/id/modes/draw_area.js @@ -24,7 +24,7 @@ iD.modes.DrawArea = function(wayId, baseGraph) { }; mode.map.surface.call(behavior); - mode.map.tail(t('modes.draw_area.tail'), true); + mode.map.tail(t('modes.draw_area.tail')); }; mode.exit = function() { diff --git a/js/id/modes/draw_line.js b/js/id/modes/draw_line.js index 0596c9065..5ae8c2703 100644 --- a/js/id/modes/draw_line.js +++ b/js/id/modes/draw_line.js @@ -24,7 +24,7 @@ iD.modes.DrawLine = function(wayId, direction, baseGraph) { }; mode.map.surface.call(behavior); - mode.map.tail(t('modes.draw_line.tail'), true); + mode.map.tail(t('modes.draw_line.tail')); }; mode.exit = function() { diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index 04521bdd7..a2c4781ef 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -361,7 +361,7 @@ iD.Map = function() { }; var usedTails = {}; - map.tail = function (_, once) { + map.tail = function (_) { if (!_ || usedTails[_] === undefined) { tail.text(_); usedTails[_] = true; From 1bfe1359ecf32c104817108e77ddded0784b1f8b Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 31 Jan 2013 11:57:41 -0500 Subject: [PATCH 056/415] AddWayNode -> AddVertex --- index.html | 2 +- .../actions/{add_way_node.js => add_vertex.js} | 2 +- js/id/behavior/draw_way.js | 4 ++-- js/id/modes/add_area.js | 18 +++++++++--------- js/id/modes/add_line.js | 10 +++++----- js/id/modes/select.js | 2 +- test/index.html | 2 +- 7 files changed, 20 insertions(+), 20 deletions(-) rename js/id/actions/{add_way_node.js => add_vertex.js} (80%) diff --git a/index.html b/index.html index 8c777292e..ede3e9a26 100644 --- a/index.html +++ b/index.html @@ -76,7 +76,7 @@ - + diff --git a/js/id/actions/add_way_node.js b/js/id/actions/add_vertex.js similarity index 80% rename from js/id/actions/add_way_node.js rename to js/id/actions/add_vertex.js index 3e4fffcfc..279737c05 100644 --- a/js/id/actions/add_way_node.js +++ b/js/id/actions/add_vertex.js @@ -1,5 +1,5 @@ // https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/AddNodeToWayAction.as -iD.actions.AddWayNode = function(wayId, nodeId, index) { +iD.actions.AddVertex = function(wayId, nodeId, index) { return function(graph) { return graph.replace(graph.entity(wayId).addNode(nodeId, index)); }; diff --git a/js/id/behavior/draw_way.js b/js/id/behavior/draw_way.js index c8341337c..be64ac9c2 100644 --- a/js/id/behavior/draw_way.js +++ b/js/id/behavior/draw_way.js @@ -14,7 +14,7 @@ iD.behavior.DrawWay = function(wayId, index, mode, baseGraph) { history[way.isDegenerate() ? 'replace' : 'perform']( iD.actions.AddNode(node), - iD.actions.AddWayNode(wayId, node.id, index)); + iD.actions.AddVertex(wayId, node.id, index)); function move(datum) { var loc = map.mouseCoordinates(); @@ -100,7 +100,7 @@ iD.behavior.DrawWay = function(wayId, index, mode, baseGraph) { history.perform( iD.actions.AddNode(newNode), - iD.actions.AddWayNode(way.id, newNode.id, wayIndex), + iD.actions.AddVertex(way.id, newNode.id, wayIndex), ReplaceTemporaryNode(newNode), annotation); diff --git a/js/id/modes/add_area.js b/js/id/modes/add_area.js index 4519c3d7e..b90ba1891 100644 --- a/js/id/modes/add_area.js +++ b/js/id/modes/add_area.js @@ -23,8 +23,8 @@ iD.modes.AddArea = function() { history.perform( iD.actions.AddNode(node), iD.actions.AddWay(way), - iD.actions.AddWayNode(way.id, node.id), - iD.actions.AddWayNode(way.id, node.id)); + iD.actions.AddVertex(way.id, node.id), + iD.actions.AddVertex(way.id, node.id)); controller.enter(iD.modes.DrawArea(way.id, graph)); } @@ -37,9 +37,9 @@ iD.modes.AddArea = function() { history.perform( iD.actions.AddNode(node), iD.actions.AddWay(way), - iD.actions.AddWayNode(way.id, node.id), - iD.actions.AddWayNode(way.id, node.id), - iD.actions.AddWayNode(other.id, node.id, index)); + iD.actions.AddVertex(way.id, node.id), + iD.actions.AddVertex(way.id, node.id), + iD.actions.AddVertex(other.id, node.id, index)); controller.enter(iD.modes.DrawArea(way.id, graph)); } @@ -50,8 +50,8 @@ iD.modes.AddArea = function() { history.perform( iD.actions.AddWay(way), - iD.actions.AddWayNode(way.id, node.id), - iD.actions.AddWayNode(way.id, node.id)); + iD.actions.AddVertex(way.id, node.id), + iD.actions.AddVertex(way.id, node.id)); controller.enter(iD.modes.DrawArea(way.id, graph)); } @@ -64,8 +64,8 @@ iD.modes.AddArea = function() { history.perform( iD.actions.AddMidpoint(midpoint, node), iD.actions.AddWay(way), - iD.actions.AddWayNode(way.id, node.id), - iD.actions.AddWayNode(way.id, node.id)); + iD.actions.AddVertex(way.id, node.id), + iD.actions.AddVertex(way.id, node.id)); controller.enter(iD.modes.DrawArea(way.id, graph)); } diff --git a/js/id/modes/add_line.js b/js/id/modes/add_line.js index 4a49e4955..f14d8ed06 100644 --- a/js/id/modes/add_line.js +++ b/js/id/modes/add_line.js @@ -23,7 +23,7 @@ iD.modes.AddLine = function() { history.perform( iD.actions.AddNode(node), iD.actions.AddWay(way), - iD.actions.AddWayNode(way.id, node.id)); + iD.actions.AddVertex(way.id, node.id)); controller.enter(iD.modes.DrawLine(way.id, 'forward', graph)); } @@ -36,8 +36,8 @@ iD.modes.AddLine = function() { history.perform( iD.actions.AddNode(node), iD.actions.AddWay(way), - iD.actions.AddWayNode(way.id, node.id), - iD.actions.AddWayNode(other.id, node.id, index)); + iD.actions.AddVertex(way.id, node.id), + iD.actions.AddVertex(other.id, node.id, index)); controller.enter(iD.modes.DrawLine(way.id, 'forward', graph)); } @@ -58,7 +58,7 @@ iD.modes.AddLine = function() { history.perform( iD.actions.AddWay(way), - iD.actions.AddWayNode(way.id, node.id)); + iD.actions.AddVertex(way.id, node.id)); controller.enter(iD.modes.DrawLine(way.id, 'forward', graph)); } @@ -72,7 +72,7 @@ iD.modes.AddLine = function() { history.perform( iD.actions.AddMidpoint(midpoint, node), iD.actions.AddWay(way), - iD.actions.AddWayNode(way.id, node.id)); + iD.actions.AddVertex(way.id, node.id)); controller.enter(iD.modes.DrawLine(way.id, 'forward', graph)); } diff --git a/js/id/modes/select.js b/js/id/modes/select.js index 455f94913..49bdbc37c 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -113,7 +113,7 @@ iD.modes.Select = function(entity, initial) { history.perform( iD.actions.AddNode(node), - iD.actions.AddWayNode(datum.id, node.id, choice.index), + iD.actions.AddVertex(datum.id, node.id, choice.index), t('operations.add.annotation.vertex')); d3.event.preventDefault(); diff --git a/test/index.html b/test/index.html index d8d3f8f6f..57ceab240 100644 --- a/test/index.html +++ b/test/index.html @@ -71,7 +71,7 @@ - + From 6f7079d11cc755e692239380b6f93e37f68575d6 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 31 Jan 2013 12:04:21 -0500 Subject: [PATCH 057/415] AddWay/AddNode -> AddEntity --- index.html | 3 +-- js/id/actions/{add_way.js => add_entity.js} | 2 +- js/id/actions/add_node.js | 6 ------ js/id/behavior/draw_way.js | 6 +++--- js/id/modes/add_area.js | 12 ++++++------ js/id/modes/add_line.js | 12 ++++++------ js/id/modes/add_point.js | 2 +- js/id/modes/select.js | 2 +- test/index.html | 6 ++---- test/index_packaged.html | 3 +-- test/spec/actions/add_entity.js | 7 +++++++ test/spec/actions/add_node.js | 7 ------- test/spec/actions/add_way.js | 7 ------- 13 files changed, 29 insertions(+), 46 deletions(-) rename js/id/actions/{add_way.js => add_entity.js} (65%) delete mode 100644 js/id/actions/add_node.js create mode 100644 test/spec/actions/add_entity.js delete mode 100644 test/spec/actions/add_node.js delete mode 100644 test/spec/actions/add_way.js diff --git a/index.html b/index.html index ede3e9a26..ff98e791c 100644 --- a/index.html +++ b/index.html @@ -74,8 +74,7 @@ - - + diff --git a/js/id/actions/add_way.js b/js/id/actions/add_entity.js similarity index 65% rename from js/id/actions/add_way.js rename to js/id/actions/add_entity.js index 2be062d3a..0011d0fe5 100644 --- a/js/id/actions/add_way.js +++ b/js/id/actions/add_entity.js @@ -1,4 +1,4 @@ -iD.actions.AddWay = function(way) { +iD.actions.AddEntity = function(way) { return function(graph) { return graph.replace(way); }; diff --git a/js/id/actions/add_node.js b/js/id/actions/add_node.js deleted file mode 100644 index 669a81d49..000000000 --- a/js/id/actions/add_node.js +++ /dev/null @@ -1,6 +0,0 @@ -// https://github.com/openstreetmap/josm/blob/mirror/src/org/openstreetmap/josm/command/AddCommand.java -iD.actions.AddNode = function(node) { - return function(graph) { - return graph.replace(node); - }; -}; diff --git a/js/id/behavior/draw_way.js b/js/id/behavior/draw_way.js index be64ac9c2..ba28fe14b 100644 --- a/js/id/behavior/draw_way.js +++ b/js/id/behavior/draw_way.js @@ -13,7 +13,7 @@ iD.behavior.DrawWay = function(wayId, index, mode, baseGraph) { nodeId = node.id; history[way.isDegenerate() ? 'replace' : 'perform']( - iD.actions.AddNode(node), + iD.actions.AddEntity(node), iD.actions.AddVertex(wayId, node.id, index)); function move(datum) { @@ -86,7 +86,7 @@ iD.behavior.DrawWay = function(wayId, index, mode, baseGraph) { var newNode = iD.Node({loc: loc}); history.replace( - iD.actions.AddNode(newNode), + iD.actions.AddEntity(newNode), ReplaceTemporaryNode(newNode), annotation); @@ -99,7 +99,7 @@ iD.behavior.DrawWay = function(wayId, index, mode, baseGraph) { var newNode = iD.Node({loc: loc}); history.perform( - iD.actions.AddNode(newNode), + iD.actions.AddEntity(newNode), iD.actions.AddVertex(way.id, newNode.id, wayIndex), ReplaceTemporaryNode(newNode), annotation); diff --git a/js/id/modes/add_area.js b/js/id/modes/add_area.js index b90ba1891..bda4e0daa 100644 --- a/js/id/modes/add_area.js +++ b/js/id/modes/add_area.js @@ -21,8 +21,8 @@ iD.modes.AddArea = function() { way = iD.Way({tags: defaultTags}); history.perform( - iD.actions.AddNode(node), - iD.actions.AddWay(way), + iD.actions.AddEntity(node), + iD.actions.AddEntity(way), iD.actions.AddVertex(way.id, node.id), iD.actions.AddVertex(way.id, node.id)); @@ -35,8 +35,8 @@ iD.modes.AddArea = function() { way = iD.Way({tags: defaultTags}); history.perform( - iD.actions.AddNode(node), - iD.actions.AddWay(way), + iD.actions.AddEntity(node), + iD.actions.AddEntity(way), iD.actions.AddVertex(way.id, node.id), iD.actions.AddVertex(way.id, node.id), iD.actions.AddVertex(other.id, node.id, index)); @@ -49,7 +49,7 @@ iD.modes.AddArea = function() { way = iD.Way({tags: defaultTags}); history.perform( - iD.actions.AddWay(way), + iD.actions.AddEntity(way), iD.actions.AddVertex(way.id, node.id), iD.actions.AddVertex(way.id, node.id)); @@ -63,7 +63,7 @@ iD.modes.AddArea = function() { history.perform( iD.actions.AddMidpoint(midpoint, node), - iD.actions.AddWay(way), + iD.actions.AddEntity(way), iD.actions.AddVertex(way.id, node.id), iD.actions.AddVertex(way.id, node.id)); diff --git a/js/id/modes/add_line.js b/js/id/modes/add_line.js index f14d8ed06..92e9a64d7 100644 --- a/js/id/modes/add_line.js +++ b/js/id/modes/add_line.js @@ -21,8 +21,8 @@ iD.modes.AddLine = function() { way = iD.Way({tags: defaultTags}); history.perform( - iD.actions.AddNode(node), - iD.actions.AddWay(way), + iD.actions.AddEntity(node), + iD.actions.AddEntity(way), iD.actions.AddVertex(way.id, node.id)); controller.enter(iD.modes.DrawLine(way.id, 'forward', graph)); @@ -34,8 +34,8 @@ iD.modes.AddLine = function() { way = iD.Way({tags: defaultTags}); history.perform( - iD.actions.AddNode(node), - iD.actions.AddWay(way), + iD.actions.AddEntity(node), + iD.actions.AddEntity(way), iD.actions.AddVertex(way.id, node.id), iD.actions.AddVertex(other.id, node.id, index)); @@ -57,7 +57,7 @@ iD.modes.AddLine = function() { var way = iD.Way({tags: defaultTags}); history.perform( - iD.actions.AddWay(way), + iD.actions.AddEntity(way), iD.actions.AddVertex(way.id, node.id)); controller.enter(iD.modes.DrawLine(way.id, 'forward', graph)); @@ -71,7 +71,7 @@ iD.modes.AddLine = function() { history.perform( iD.actions.AddMidpoint(midpoint, node), - iD.actions.AddWay(way), + iD.actions.AddEntity(way), iD.actions.AddVertex(way.id, node.id)); controller.enter(iD.modes.DrawLine(way.id, 'forward', graph)); diff --git a/js/id/modes/add_point.js b/js/id/modes/add_point.js index 74389cb0c..61ff51424 100644 --- a/js/id/modes/add_point.js +++ b/js/id/modes/add_point.js @@ -17,7 +17,7 @@ iD.modes.AddPoint = function() { var node = iD.Node({loc: loc}); history.perform( - iD.actions.AddNode(node), + iD.actions.AddEntity(node), t('operations.add.annotation.point')); controller.enter(iD.modes.Select(node, true)); diff --git a/js/id/modes/select.js b/js/id/modes/select.js index 49bdbc37c..3e0acc5a1 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -112,7 +112,7 @@ iD.modes.Select = function(entity, initial) { node = iD.Node({ loc: choice.loc }); history.perform( - iD.actions.AddNode(node), + iD.actions.AddEntity(node), iD.actions.AddVertex(datum.id, node.id, choice.index), t('operations.add.annotation.vertex')); diff --git a/test/index.html b/test/index.html index 57ceab240..f9da20905 100644 --- a/test/index.html +++ b/test/index.html @@ -69,8 +69,7 @@ - - + @@ -139,8 +138,7 @@ - - + diff --git a/test/index_packaged.html b/test/index_packaged.html index 719a5a999..291f47f71 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -33,8 +33,7 @@ - - + diff --git a/test/spec/actions/add_entity.js b/test/spec/actions/add_entity.js new file mode 100644 index 000000000..1e2e092ca --- /dev/null +++ b/test/spec/actions/add_entity.js @@ -0,0 +1,7 @@ +describe("iD.actions.AddEntity", function () { + it("adds an entity to the graph", function () { + var entity = iD.Entity(), + graph = iD.actions.AddEntity(entity)(iD.Graph()); + expect(graph.entity(entity.id)).to.equal(entity); + }); +}); diff --git a/test/spec/actions/add_node.js b/test/spec/actions/add_node.js deleted file mode 100644 index ae066edc5..000000000 --- a/test/spec/actions/add_node.js +++ /dev/null @@ -1,7 +0,0 @@ -describe("iD.actions.AddNode", function () { - it("adds a node to the graph", function () { - var node = iD.Node(), - graph = iD.actions.AddNode(node)(iD.Graph()); - expect(graph.entity(node.id)).to.equal(node); - }); -}); diff --git a/test/spec/actions/add_way.js b/test/spec/actions/add_way.js deleted file mode 100644 index 19948c5b2..000000000 --- a/test/spec/actions/add_way.js +++ /dev/null @@ -1,7 +0,0 @@ -describe("iD.actions.AddWay", function () { - it("adds a way to the graph", function () { - var way = iD.Way(), - graph = iD.actions.AddWay(way)(iD.Graph()); - expect(graph.entity(way.id)).to.equal(way); - }); -}); From df72ef254f984218cf650e87fbca5d44a1d3f732 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 31 Jan 2013 12:07:54 -0500 Subject: [PATCH 058/415] ChangeEntityTags -> ChangeTags --- js/id/actions/{change_entity_tags.js => change_tags.js} | 2 +- js/id/modes/select.js | 2 +- test/spec/actions/change_entity_tags.js | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) rename js/id/actions/{change_entity_tags.js => change_tags.js} (71%) diff --git a/js/id/actions/change_entity_tags.js b/js/id/actions/change_tags.js similarity index 71% rename from js/id/actions/change_entity_tags.js rename to js/id/actions/change_tags.js index 96901c2eb..c5972644e 100644 --- a/js/id/actions/change_entity_tags.js +++ b/js/id/actions/change_tags.js @@ -1,4 +1,4 @@ -iD.actions.ChangeEntityTags = function(entityId, tags) { +iD.actions.ChangeTags = function(entityId, tags) { return function(graph) { var entity = graph.entity(entityId); return graph.replace(entity.update({tags: tags})); diff --git a/js/id/modes/select.js b/js/id/modes/select.js index 3e0acc5a1..83e0d72b8 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -13,7 +13,7 @@ iD.modes.Select = function(entity, initial) { function changeTags(d, tags) { if (!_.isEqual(entity.tags, tags)) { mode.history.perform( - iD.actions.ChangeEntityTags(d.id, tags), + iD.actions.ChangeTags(d.id, tags), t('operations.change_tags.annotation')); } } diff --git a/test/spec/actions/change_entity_tags.js b/test/spec/actions/change_entity_tags.js index bfa9830c6..0cc198230 100644 --- a/test/spec/actions/change_entity_tags.js +++ b/test/spec/actions/change_entity_tags.js @@ -1,8 +1,8 @@ -describe("iD.actions.ChangeEntityTags", function () { +describe("iD.actions.ChangeTags", function () { it("changes an entity's tags", function () { var entity = iD.Entity(), tags = {foo: 'bar'}, - graph = iD.actions.ChangeEntityTags(entity.id, tags)(iD.Graph([entity])); + graph = iD.actions.ChangeTags(entity.id, tags)(iD.Graph([entity])); expect(graph.entity(entity.id).tags).to.eql(tags); }); }); From 3605895dfad0dde494cde95745d797286df08865 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 31 Jan 2013 12:41:29 -0500 Subject: [PATCH 059/415] ChangeEntityTags -> ChangeTags --- index.html | 2 +- test/index.html | 4 ++-- test/index_packaged.html | 2 +- test/spec/actions/{change_entity_tags.js => change_tags.js} | 0 4 files changed, 4 insertions(+), 4 deletions(-) rename test/spec/actions/{change_entity_tags.js => change_tags.js} (100%) diff --git a/index.html b/index.html index ff98e791c..03bf6386f 100644 --- a/index.html +++ b/index.html @@ -76,7 +76,7 @@ - + diff --git a/test/index.html b/test/index.html index f9da20905..90e5bc9fa 100644 --- a/test/index.html +++ b/test/index.html @@ -71,7 +71,7 @@ - + @@ -139,7 +139,7 @@ - + diff --git a/test/index_packaged.html b/test/index_packaged.html index 291f47f71..007392f3b 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -34,7 +34,7 @@ - + diff --git a/test/spec/actions/change_entity_tags.js b/test/spec/actions/change_tags.js similarity index 100% rename from test/spec/actions/change_entity_tags.js rename to test/spec/actions/change_tags.js From 3bdd314eb8b6a87efe8e02bb0c5f4fd474970ba8 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 31 Jan 2013 12:42:37 -0500 Subject: [PATCH 060/415] Fix global leak --- js/id/renderer/background.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/id/renderer/background.js b/js/id/renderer/background.js index 752853e6b..54e3835ee 100644 --- a/js/id/renderer/background.js +++ b/js/id/renderer/background.js @@ -3,7 +3,7 @@ iD.Background = function() { var deviceRatio = (window.devicePixelRatio && window.devicePixelRatio === 2) ? 0.5 : 1; // tileSize = (deviceRatio === 0.5) ? [128,128] : [256,256]; - tileSize = [256, 256]; + var tileSize = [256, 256]; var tile = d3.geo.tile(), projection, From 514ec1e28b75f90f7e6325e2f8b3408dd3b280ba Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 31 Jan 2013 12:44:11 -0500 Subject: [PATCH 061/415] Move validate --- Makefile | 1 + index.html | 2 +- js/id/{graph => }/validate.js | 0 3 files changed, 2 insertions(+), 1 deletion(-) rename js/id/{graph => }/validate.js (100%) diff --git a/Makefile b/Makefile index ddeef62b9..38244edc3 100644 --- a/Makefile +++ b/Makefile @@ -47,6 +47,7 @@ all: \ js/id/svg/*.js \ js/id/ui.js \ js/id/ui/*.js \ + js/id/validate.js \ js/id/end.js \ locale/locale.js \ locale/en.js diff --git a/index.html b/index.html index 03bf6386f..3bb3152e4 100644 --- a/index.html +++ b/index.html @@ -122,7 +122,7 @@ - + diff --git a/js/id/graph/validate.js b/js/id/validate.js similarity index 100% rename from js/id/graph/validate.js rename to js/id/validate.js From 15e1bee3ecc04fac789a80de9444758b71173891 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 31 Jan 2013 12:45:39 -0500 Subject: [PATCH 062/415] Move controller --- Makefile | 2 +- index.html | 5 ++--- js/id/{controller => }/controller.js | 0 test/index.html | 3 +-- 4 files changed, 4 insertions(+), 6 deletions(-) rename js/id/{controller => }/controller.js (100%) diff --git a/Makefile b/Makefile index 38244edc3..a501d0d20 100644 --- a/Makefile +++ b/Makefile @@ -40,7 +40,7 @@ all: \ js/id/modes/*.js \ js/id/operations.js \ js/id/operations/*.js \ - js/id/controller/*.js \ + js/id/controller.js \ js/id/graph/*.js \ js/id/renderer/*.js \ js/id/svg.js \ diff --git a/index.html b/index.html index 3bb3152e4..e5e202c4a 100644 --- a/index.html +++ b/index.html @@ -114,16 +114,15 @@ - - - + + diff --git a/js/id/controller/controller.js b/js/id/controller.js similarity index 100% rename from js/id/controller/controller.js rename to js/id/controller.js diff --git a/test/index.html b/test/index.html index 90e5bc9fa..52070c414 100644 --- a/test/index.html +++ b/test/index.html @@ -109,8 +109,6 @@ - - @@ -119,6 +117,7 @@ + From 5c8aa46e1f6739d93d6a57e47812af9d54bbc535 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Thu, 31 Jan 2013 12:47:02 -0500 Subject: [PATCH 063/415] Fix graph.difference --- js/id/graph/graph.js | 54 ++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 30 deletions(-) diff --git a/js/id/graph/graph.js b/js/id/graph/graph.js index 5e865b9ec..26735ff2e 100644 --- a/js/id/graph/graph.js +++ b/js/id/graph/graph.js @@ -235,44 +235,38 @@ iD.Graph.prototype = { }, difference: function (graph) { - var result = [], - keys = Object.keys(this.entities), - entity, oldentity, id, i; - for (i = 0; i < keys.length; i++) { - id = keys[i]; - entity = this.entities[id]; - oldentity = graph.entities[id]; - if (entity !== oldentity) { + function diff(a, b) { + var result = [], + keys = Object.keys(a.entities), + entity, oldentity, id, i; - if (entity && entity.type === 'way' && - oldentity && oldentity.type === 'way') { - result = result - .concat(_.difference(entity.nodes, oldentity.nodes)) - .concat(_.difference(oldentity.nodes, entity.nodes)); + for (i = 0; i < keys.length; i++) { + id = keys[i]; + entity = a.entities[id]; + oldentity = b.entities[id]; + if (entity !== oldentity) { - } else if (entity && entity.type === 'way') { - result = result.concat(entity.nodes); + if (entity && entity.type === 'way' && + oldentity && oldentity.type === 'way') { + result = result + .concat(_.difference(entity.nodes, oldentity.nodes)) + .concat(_.difference(oldentity.nodes, entity.nodes)); - } else if (oldentity && oldentity.type === 'way') { - result = result.concat(oldentity.nodes); + } else if (entity && entity.type === 'way') { + result = result.concat(entity.nodes); + + } else if (oldentity && oldentity.type === 'way') { + result = result.concat(oldentity.nodes); + } + + result.push(id); } - - result.push(id); } + return result; } - keys = Object.keys(graph.entities); - for (i = 0; i < keys.length; i++) { - id = keys[i]; - entity = graph.entities[id]; - if (entity && !this.entities.hasOwnProperty(id)) { - result.push(id); - if (entity.type === 'way') result = result.concat(entity.nodes); - } - } - - return result.sort(); + return _.unique(diff(this, graph).concat(diff(graph, this)).sort()); }, modified: function() { From d5133b1a5ea8cf6052b67f8db9ea4361b4cf0656 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 31 Jan 2013 12:54:01 -0500 Subject: [PATCH 064/415] Fix build --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index a501d0d20..1db4f2efb 100644 --- a/Makefile +++ b/Makefile @@ -23,6 +23,7 @@ all: \ js/lib/jxon.js \ js/lib/lodash.js \ js/lib/ohauth.js \ + js/lib/rtree.js \ js/lib/sha.js \ js/id/start.js \ js/id/id.js \ From 8bece3de15547b1f79fa9f3e6f7602fd99818be7 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 31 Jan 2013 12:55:35 -0500 Subject: [PATCH 065/415] Remove unused --- index.html | 1 - js/lib/queue.js | 84 ------------------------------------------------- 2 files changed, 85 deletions(-) delete mode 100644 js/lib/queue.js diff --git a/index.html b/index.html index e5e202c4a..f286e8a52 100644 --- a/index.html +++ b/index.html @@ -24,7 +24,6 @@ - diff --git a/js/lib/queue.js b/js/lib/queue.js deleted file mode 100644 index 9a3b9da47..000000000 --- a/js/lib/queue.js +++ /dev/null @@ -1,84 +0,0 @@ -(function() { - if (typeof module === "undefined") self.queue = queue; - else module.exports = queue; - - queue.version = "1.0.0"; - - function queue(parallelism) { - var queue = {}, - active = 0, // number of in-flight deferrals - remaining = 0, // number of deferrals remaining - head, tail, // singly-linked list of deferrals - error = null, - results = [], - await = noop, - awaitAll; - - if (arguments.length < 1) parallelism = Infinity; - - queue.defer = function() { - if (!error) { - var node = arguments; - node.index = results.push(undefined) - 1; - if (tail) tail.next = node, tail = tail.next; - else head = tail = node; - ++remaining; - pop(); - } - return queue; - }; - - queue.await = function(f) { - await = f; - awaitAll = false; - if (!remaining) notify(); - return queue; - }; - - queue.awaitAll = function(f) { - await = f; - awaitAll = true; - if (!remaining) notify(); - return queue; - }; - - function pop() { - if (head && active < parallelism) { - var node = head, - f = node[0], - a = Array.prototype.slice.call(node, 1), - i = node.index; - if (head === tail) head = tail = null; - else head = head.next; - ++active; - a.push(function(e, r) { - --active; - if (error != null) return; - if (e != null) { - // clearing remaining cancels subsequent callbacks - // clearing head stops queued tasks from being executed - // setting error ignores subsequent calls to defer - error = e; - remaining = results = head = tail = null; - notify(); - } else { - results[i] = r; - if (--remaining) pop(); - else notify(); - } - }); - f.apply(null, a); - } - } - - function notify() { - if (error != null) await(error); - else if (awaitAll) await(null, results); - else await.apply(null, [null].concat(results)); - } - - return queue; - } - - function noop() {} -})(); From a48453c6eddd1d8be71a28c061b91feb2f365f96 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 31 Jan 2013 13:03:50 -0500 Subject: [PATCH 066/415] Selected style should take precedence over hover (fixes #568) --- css/map.css | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/css/map.css b/css/map.css index 7b3038d6c..7589ac042 100644 --- a/css/map.css +++ b/css/map.css @@ -16,7 +16,7 @@ g.point .shadow { transition: transform 100ms linear; -moz-transition: fill 100ms linear; } -.behavior-hover g.point.hover .shadow { +.behavior-hover g.point.hover:not(.selected) .shadow { fill: #E96666; fill-opacity: 0.3; } @@ -106,7 +106,7 @@ g.vertex .shadow { transition: transform 100ms linear; -moz-transition: fill 100ms linear; } -.behavior-hover g.vertex.hover .shadow { +.behavior-hover g.vertex.hover:not(.selected) .shadow { fill: #E96666; fill-opacity: 0.3; } @@ -120,7 +120,7 @@ g.vertex.selected .shadow { g.midpoint .fill { fill:#aaa; } -.behavior-hover g.midpoint .fill.hover { +.behavior-hover g.midpoint .fill.hover:not(.selected) { fill:#fff; stroke:#000; } @@ -133,7 +133,7 @@ g.midpoint .shadow { transition: transform 100ms linear; -moz-transition: fill 100ms linear; } -.behavior-hover g.midpoint .shadow.hover { +.behavior-hover g.midpoint .shadow.hover:not(.selected) { fill:#E96666; fill-opacity: 0.3; } @@ -161,7 +161,7 @@ path.shadow { -webkit-transition: stroke 100ms linear; } -.behavior-hover path.shadow.hover { +.behavior-hover path.shadow.hover:not(.selected) { stroke: #E96666; stroke-opacity: 0.3; } From 846cf6e43de7c99a42ba147ad3dfb3f14667673e Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Thu, 31 Jan 2013 13:37:54 -0500 Subject: [PATCH 067/415] Add difference tests --- js/id/graph/graph.js | 1 + test/spec/graph/graph.js | 36 +++++++++++++++++++++++++++++++++--- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/js/id/graph/graph.js b/js/id/graph/graph.js index 26735ff2e..a19bfe094 100644 --- a/js/id/graph/graph.js +++ b/js/id/graph/graph.js @@ -247,6 +247,7 @@ iD.Graph.prototype = { oldentity = b.entities[id]; if (entity !== oldentity) { + // maybe adding affected children better belongs in renderer/map.js? if (entity && entity.type === 'way' && oldentity && oldentity.type === 'way') { result = result diff --git a/test/spec/graph/graph.js b/test/spec/graph/graph.js index 327d66295..05bcbbe89 100644 --- a/test/spec/graph/graph.js +++ b/test/spec/graph/graph.js @@ -345,12 +345,42 @@ describe('iD.Graph', function() { expect(graph2.difference(graph1)).to.eql([created.id, updated.id, deleted.id]); }); - it("includes created entities that were subsequently deleted", function () { + + it("includes created entities, and reverse", function () { var node = iD.Node(), - graph1 = iD.Graph([node]), - graph2 = graph1.remove(node); + graph1 = iD.Graph(), + graph2 = graph1.replace(node); expect(graph2.difference(graph1)).to.eql([node.id]); + expect(graph1.difference(graph2)).to.eql([node.id]); }); + + it("includes entities changed from base, and reverse", function () { + var node = iD.Node(), + graph1 = iD.Graph(node), + graph2 = graph1.replace(node.update()); + expect(graph2.difference(graph1)).to.eql([node.id]); + expect(graph1.difference(graph2)).to.eql([node.id]); + }); + + it("includes already changed entities that were updated, and reverse", function () { + var node = iD.Node(), + graph1 = iD.Graph().replace(node), + graph2 = graph1.replace(node.update()); + expect(graph2.difference(graph1)).to.eql([node.id]); + expect(graph1.difference(graph2)).to.eql([node.id]); + }); + + it("includes affected child nodes", function () { + var n = iD.Node({id: 'n'}), + n2 = iD.Node({id: 'n2'}), + w1 = iD.Way({id: 'w1', nodes: ['n']}), + w1_ = iD.Way({id: 'w1', nodes: ['n', 'n2']}), + graph1 = iD.Graph([n, n2, w1]), + graph2 = graph1.replace(w1_); + expect(graph2.difference(graph1)).to.eql(['n2', 'w1']); + expect(graph1.difference(graph2)).to.eql(['n2', 'w1']); + }); + }); describe("#modified", function () { From 402a9424c0af236bcaffa00c889b9c8b3f6aed61 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 31 Jan 2013 13:27:57 -0500 Subject: [PATCH 068/415] Make selection an array of entity IDs Should have no visible effect yet. --- js/id/behavior/draw_way.js | 2 +- js/id/modes/add_point.js | 2 +- js/id/modes/browse.js | 2 +- js/id/modes/move_way.js | 4 +- js/id/modes/select.js | 115 +++++++++++++++++--------------- js/id/operations/circularize.js | 7 +- js/id/operations/delete.js | 8 ++- js/id/operations/move.js | 8 ++- js/id/operations/reverse.js | 8 ++- js/id/operations/split.js | 8 ++- js/id/operations/unjoin.js | 8 ++- js/id/renderer/hash.js | 2 +- js/id/ui/save.js | 2 +- test/spec/modes/add_point.js | 2 +- 14 files changed, 100 insertions(+), 78 deletions(-) diff --git a/js/id/behavior/draw_way.js b/js/id/behavior/draw_way.js index ba28fe14b..1cc62b755 100644 --- a/js/id/behavior/draw_way.js +++ b/js/id/behavior/draw_way.js @@ -139,7 +139,7 @@ iD.behavior.DrawWay = function(wayId, index, mode, baseGraph) { var way = history.graph().entity(wayId); if (way) { - controller.enter(iD.modes.Select(way, true)); + controller.enter(iD.modes.Select([way.id], true)); } else { controller.enter(iD.modes.Browse()); } diff --git a/js/id/modes/add_point.js b/js/id/modes/add_point.js index 61ff51424..feff66f43 100644 --- a/js/id/modes/add_point.js +++ b/js/id/modes/add_point.js @@ -20,7 +20,7 @@ iD.modes.AddPoint = function() { iD.actions.AddEntity(node), t('operations.add.annotation.point')); - controller.enter(iD.modes.Select(node, true)); + controller.enter(iD.modes.Select([node.id], true)); } function addWay(way, loc, index) { diff --git a/js/id/modes/browse.js b/js/id/modes/browse.js index c7358bdea..b1fb1c8ae 100644 --- a/js/id/modes/browse.js +++ b/js/id/modes/browse.js @@ -24,7 +24,7 @@ iD.modes.Browse = function() { surface.on('click.browse', function () { var datum = d3.select(d3.event.target).datum(); if (datum instanceof iD.Entity) { - mode.controller.enter(iD.modes.Select(datum)); + mode.controller.enter(iD.modes.Select([datum.id])); } }); }; diff --git a/js/id/modes/move_way.js b/js/id/modes/move_way.js index d977f6719..50c228d8e 100644 --- a/js/id/modes/move_way.js +++ b/js/id/modes/move_way.js @@ -34,12 +34,12 @@ iD.modes.MoveWay = function(wayId) { function finish() { d3.event.stopPropagation(); - controller.enter(iD.modes.Select(way, true)); + controller.enter(iD.modes.Select([way.id], true)); } function cancel() { history.pop(); - controller.enter(iD.modes.Select(way, true)); + controller.enter(iD.modes.Select([way.id], true)); } function undone() { diff --git a/js/id/modes/select.js b/js/id/modes/select.js index 83e0d72b8..37b2dee1d 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -1,8 +1,7 @@ -iD.modes.Select = function(entity, initial) { +iD.modes.Select = function(selection, initial) { var mode = { id: 'select', - button: 'browse', - entity: entity + button: 'browse' }; var inspector = iD.ui.inspector().initial(!!initial), @@ -11,20 +10,29 @@ iD.modes.Select = function(entity, initial) { radialMenu; function changeTags(d, tags) { - if (!_.isEqual(entity.tags, tags)) { + if (!_.isEqual(singular().tags, tags)) { mode.history.perform( iD.actions.ChangeTags(d.id, tags), t('operations.change_tags.annotation')); } } + function singular() { + if (selection.length === 1) { + return mode.map.history().graph().entity(selection[0]); + } + } + + mode.selection = function() { + return selection; + }; + mode.enter = function() { var map = mode.map, - graph = map.history().graph(), history = map.history(), - surface = mode.map.surface; - - inspector.graph(graph); + graph = history.graph(), + surface = map.surface, + entity = singular(); behaviors = [ iD.behavior.Hover(), @@ -36,7 +44,7 @@ iD.modes.Select = function(entity, initial) { }); var operations = d3.values(iD.operations) - .map(function (o) { return o(entity.id, mode); }) + .map(function (o) { return o(selection, mode); }) .filter(function (o) { return o.available(); }); operations.forEach(function(operation) { @@ -49,46 +57,51 @@ iD.modes.Select = function(entity, initial) { var q = iD.util.stringQs(location.hash.substring(1)); location.replace('#' + iD.util.qsString(_.assign(q, { - id: entity.id + id: selection.join(',') }), true)); - d3.select('.inspector-wrap') - .style('display', 'block') - .style('opacity', 1) - .datum(entity) - .call(inspector); + if (entity) { + inspector.graph(graph); - if (d3.event) { - // Pan the map if the clicked feature intersects with the position - // of the inspector - var inspector_size = d3.select('.inspector-wrap').size(), - map_size = mode.map.size(), - offset = 50, - shift_left = d3.event.x - map_size[0] + inspector_size[0] + offset, - center = (map_size[0] / 2) + shift_left + offset; + d3.select('.inspector-wrap') + .style('display', 'block') + .style('opacity', 1) + .datum(entity) + .call(inspector); - if (shift_left > 0 && inspector_size[1] > d3.event.y) { - mode.map.centerEase(mode.map.projection.invert([center, map_size[1]/2])); + if (d3.event) { + // Pan the map if the clicked feature intersects with the position + // of the inspector + var inspector_size = d3.select('.inspector-wrap').size(), + map_size = mode.map.size(), + offset = 50, + shift_left = d3.event.x - map_size[0] + inspector_size[0] + offset, + center = (map_size[0] / 2) + shift_left + offset; + + if (shift_left > 0 && inspector_size[1] > d3.event.y) { + mode.map.centerEase(mode.map.projection.invert([center, map_size[1]/2])); + } } + + inspector + .on('changeTags', changeTags) + .on('close', function() { mode.controller.exit(); }); + + history.on('change.select', function() { + // Exit mode if selected entity gets undone + var oldEntity = entity, + newEntity = history.graph().entity(selection[0]); + + if (!newEntity) { + mode.controller.enter(iD.modes.Browse()); + } else if (!_.isEqual(oldEntity.tags, newEntity.tags)) { + inspector.tags(newEntity.tags); + } + + surface.call(radialMenu.close); + }); } - inspector - .on('changeTags', changeTags) - .on('close', function() { mode.controller.exit(); }); - - history.on('change.select', function() { - // Exit mode if selected entity gets undone - var old = entity; - entity = history.graph().entity(entity.id); - if (!entity) { - mode.controller.enter(iD.modes.Browse()); - } else if(!_.isEqual(entity.tags, old.tags)) { - inspector.tags(entity.tags); - } - - surface.call(radialMenu.close); - }); - map.on('move.select', function() { surface.call(radialMenu.close); }); @@ -96,17 +109,17 @@ iD.modes.Select = function(entity, initial) { function click() { var datum = d3.select(d3.event.target).datum(); if (datum instanceof iD.Entity) { - mode.controller.enter(iD.modes.Select(datum)); + mode.controller.enter(iD.modes.Select([datum.id])); } else { mode.controller.enter(iD.modes.Browse()); } } function dblclick() { - var selection = d3.select(d3.event.target), - datum = selection.datum(); + var target = d3.select(d3.event.target), + datum = target.datum(); - if (datum instanceof iD.Way && !selection.classed('fill')) { + if (datum instanceof iD.Way && !target.classed('fill')) { var choice = iD.geo.chooseIndex(datum, d3.mouse(mode.map.surface.node()), mode.map), node = iD.Node({ loc: choice.loc }); @@ -128,9 +141,7 @@ iD.modes.Select = function(entity, initial) { .call(keybinding); surface.selectAll("*") - .filter(function (d) { - return d && entity && d.id === entity.id; - }) + .filter(function (d) { return d && selection.indexOf(d.id) >= 0; }) .classed('selected', true); radialMenu = iD.ui.RadialMenu(operations); @@ -138,7 +149,7 @@ iD.modes.Select = function(entity, initial) { if (d3.event && !initial) { var loc = map.mouseCoordinates(); - if (entity.type === 'node') { + if (entity && entity.type === 'node') { loc = entity.loc; } @@ -150,8 +161,8 @@ iD.modes.Select = function(entity, initial) { var surface = mode.map.surface, history = mode.history; - if (entity) { - changeTags(entity, inspector.tags()); + if (singular()) { + changeTags(singular(), inspector.tags()); } d3.select('.inspector-wrap') diff --git a/js/id/operations/circularize.js b/js/id/operations/circularize.js index 91921dc2a..4b0f27bf9 100644 --- a/js/id/operations/circularize.js +++ b/js/id/operations/circularize.js @@ -1,5 +1,6 @@ -iD.operations.Circularize = function(entityId, mode) { - var history = mode.map.history(), +iD.operations.Circularize = function(selection, mode) { + var entityId = selection[0], + history = mode.map.history(), action = iD.actions.Circularize(entityId, mode.map); var operation = function() { @@ -13,7 +14,7 @@ iD.operations.Circularize = function(entityId, mode) { operation.available = function() { var graph = history.graph(), entity = graph.entity(entityId); - return entity.type === 'way'; + return selection.length === 1 && entity.type === 'way'; }; operation.enabled = function() { diff --git a/js/id/operations/delete.js b/js/id/operations/delete.js index 993d3eef4..12df22a09 100644 --- a/js/id/operations/delete.js +++ b/js/id/operations/delete.js @@ -1,5 +1,6 @@ -iD.operations.Delete = function(entityId, mode) { - var history = mode.map.history(); +iD.operations.Delete = function(selection, mode) { + var entityId = selection[0], + history = mode.map.history(); var operation = function() { var graph = history.graph(), @@ -15,7 +16,8 @@ iD.operations.Delete = function(entityId, mode) { operation.available = function() { var graph = history.graph(), entity = graph.entity(entityId); - return entity.type === 'way' || entity.type === 'node'; + return selection.length === 1 && + (entity.type === 'way' || entity.type === 'node'); }; operation.enabled = function() { diff --git a/js/id/operations/move.js b/js/id/operations/move.js index 5034c2f75..10405cc53 100644 --- a/js/id/operations/move.js +++ b/js/id/operations/move.js @@ -1,5 +1,6 @@ -iD.operations.Move = function(entityId, mode) { - var history = mode.map.history(); +iD.operations.Move = function(selection, mode) { + var entityId = selection[0], + history = mode.map.history(); var operation = function() { mode.controller.enter(iD.modes.MoveWay(entityId)); @@ -7,7 +8,8 @@ iD.operations.Move = function(entityId, mode) { operation.available = function() { var graph = history.graph(); - return graph.entity(entityId).type === 'way'; + return selection.length === 1 && + graph.entity(entityId).type === 'way'; }; operation.enabled = function() { diff --git a/js/id/operations/reverse.js b/js/id/operations/reverse.js index ca9dd6440..78b8789dd 100644 --- a/js/id/operations/reverse.js +++ b/js/id/operations/reverse.js @@ -1,5 +1,6 @@ -iD.operations.Reverse = function(entityId, mode) { - var history = mode.map.history(); +iD.operations.Reverse = function(selection, mode) { + var entityId = selection[0], + history = mode.map.history(); var operation = function() { history.perform( @@ -10,7 +11,8 @@ iD.operations.Reverse = function(entityId, mode) { operation.available = function() { var graph = history.graph(), entity = graph.entity(entityId); - return entity.geometry(graph) === 'line'; + return selection.length === 1 && + entity.geometry(graph) === 'line'; }; operation.enabled = function() { diff --git a/js/id/operations/split.js b/js/id/operations/split.js index ff3f8af20..4274fc41b 100644 --- a/js/id/operations/split.js +++ b/js/id/operations/split.js @@ -1,5 +1,6 @@ -iD.operations.Split = function(entityId, mode) { - var history = mode.map.history(), +iD.operations.Split = function(selection, mode) { + var entityId = selection[0], + history = mode.map.history(), action = iD.actions.SplitWay(entityId); var operation = function() { @@ -9,7 +10,8 @@ iD.operations.Split = function(entityId, mode) { operation.available = function() { var graph = history.graph(), entity = graph.entity(entityId); - return entity.geometry(graph) === 'vertex'; + return selection.length === 1 && + entity.geometry(graph) === 'vertex'; }; operation.enabled = function() { diff --git a/js/id/operations/unjoin.js b/js/id/operations/unjoin.js index 175c13973..2a40ec067 100644 --- a/js/id/operations/unjoin.js +++ b/js/id/operations/unjoin.js @@ -1,5 +1,6 @@ -iD.operations.Unjoin = function(entityId, mode) { - var history = mode.map.history(), +iD.operations.Unjoin = function(selection, mode) { + var entityId = selection[0], + history = mode.map.history(), action = iD.actions.UnjoinNode(entityId); var operation = function() { @@ -9,7 +10,8 @@ iD.operations.Unjoin = function(entityId, mode) { operation.available = function() { var graph = history.graph(), entity = graph.entity(entityId); - return entity.geometry(graph) === 'vertex'; + return selection.length === 1 && + entity.geometry(graph) === 'vertex'; }; operation.enabled = function() { diff --git a/js/id/renderer/hash.js b/js/id/renderer/hash.js index 384705543..f4204531f 100644 --- a/js/id/renderer/hash.js +++ b/js/id/renderer/hash.js @@ -49,7 +49,7 @@ iD.Hash = function() { var entity = map.history().graph().entity(id); if (entity === undefined) return; else selectoff(); - controller.enter(iD.modes.Select(entity)); + controller.enter(iD.modes.Select([entity.id])); map.on('drawn.hash', null); }); controller.on('enter.hash', function() { diff --git a/js/id/ui/save.js b/js/id/ui/save.js index 8784b2ca4..b8085e21a 100644 --- a/js/id/ui/save.js +++ b/js/id/ui/save.js @@ -60,7 +60,7 @@ iD.ui.save = function() { .on('fix', function(d) { map.extent(d.entity.extent(map.history().graph())); if (map.zoom() > 19) map.zoom(19); - controller.enter(iD.modes.Select(d.entity)); + controller.enter(iD.modes.Select([d.entity.id])); modal.remove(); }) .on('save', commit)); diff --git a/test/spec/modes/add_point.js b/test/spec/modes/add_point.js index 68fe80403..a284cf682 100644 --- a/test/spec/modes/add_point.js +++ b/test/spec/modes/add_point.js @@ -28,7 +28,7 @@ describe("iD.modes.AddPoint", function () { it("selects the node", function () { happen.click(map.surface.node(), {}); expect(controller.mode.id).to.equal('select'); - expect(controller.mode.entity).to.equal(history.changes().created[0]); + expect(controller.mode.selection()).to.eql([history.changes().created[0].id]); }); }); From 26dfaf81614b521ebfc137a51bdffa0691a5d699 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Thu, 31 Jan 2013 14:43:41 -0500 Subject: [PATCH 069/415] Revise label stack --- css/map.css | 7 +------ js/id/svg/labels.js | 30 ++++++++++++++++++++++++------ 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/css/map.css b/css/map.css index 7589ac042..e3e05a044 100644 --- a/css/map.css +++ b/css/map.css @@ -628,12 +628,7 @@ text.pointlabel { } -text.area.tag-leisure-park { - font-size: 16px; -} - -text.point.tag-shop, -text.point.tag-amenity { +text.point { font-size: 9px; } diff --git a/js/id/svg/labels.js b/js/id/svg/labels.js index 3962c4915..774fc264e 100644 --- a/js/id/svg/labels.js +++ b/js/id/svg/labels.js @@ -2,18 +2,36 @@ iD.svg.Labels = function(projection) { // Replace with dict and iterate over entities tags instead? var label_stack = [ + ['line', 'aeroway'], ['line', 'highway'], - ['area', 'building', 'yes'], - ['area', 'leisure', 'park'], + ['line', 'railway'], + ['line', 'waterway'], + ['area', 'aeroway'], + ['area', 'amenity'], + ['area', 'building'], + ['area', 'historic'], + ['area', 'leisure'], + ['area', 'man_made'], ['area', 'natural'], + ['area', 'shop'], + ['area', 'tourism'], + ['point', 'aeroway'], ['point', 'amenity'], - ['point', 'shop'] + ['point', 'building'], + ['point', 'historic'], + ['point', 'leisure'], + ['point', 'man_made'], + ['point', 'natural'], + ['point', 'shop'], + ['point', 'tourism'], + ['line', 'name'], + ['area', 'name'], + ['point', 'name'] ]; var default_size = 12; var font_sizes = label_stack.map(function(d) { - var style = iD.util.getStyle( - 'text.' + d[0] + '.tag-' + d.slice(1).join('-')); + var style = iD.util.getStyle('text.' + d[0] + '.tag-' + d[1]); var m = style && style.cssText.match("font-size: ([0-9]{1,2})px;"); if (!m) return default_size; return parseInt(m[1], 10); @@ -210,7 +228,7 @@ iD.svg.Labels = function(projection) { if (hidePoints && entity.geometry(graph) === 'point') continue; for (k = 0; k < label_stack.length; k ++) { if (entity.geometry(graph) === label_stack[k][0] && - entity.tags[label_stack[k][1]] && !entity.tags[label_stack[k][2]]) { + entity.tags[label_stack[k][1]]) { labelable[k].push(entity); break; } From 8dcb215fe31fa7a2ff65ee81a49ab464ba04d10f Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Thu, 31 Jan 2013 15:40:17 -0500 Subject: [PATCH 070/415] Hide labels on mouseover --- css/map.css | 8 ++++++++ js/id/svg/labels.js | 19 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/css/map.css b/css/map.css index e3e05a044..8ad55c790 100644 --- a/css/map.css +++ b/css/map.css @@ -616,6 +616,14 @@ text.pointlabel { pointer-events: none; } +.layer-halo rect, +.layer-halo path, +.layer-label text { + -webkit-transition: opacity 100ms linear; + transition: opacity 100ms linear; + -moz-transition: opacity 100ms linear; +} + .pathlabel .textpath { dominant-baseline: middle; } diff --git a/js/id/svg/labels.js b/js/id/svg/labels.js index 774fc264e..eb4297bca 100644 --- a/js/id/svg/labels.js +++ b/js/id/svg/labels.js @@ -200,12 +200,31 @@ iD.svg.Labels = function(projection) { } + function hideOnMouseover() { + var mouse = d3.mouse(this), + pad = 50, + rect = new RTree.Rectangle(mouse[0] - pad, mouse[1] - pad, 2*pad, 2*pad), + labels = _.pluck(rtree.search(rect, this), 'leaf'), + selection = d3.select(this); + + selection.selectAll('.layer-label text, .layer-halo path, .layer-halo rect') + .style('opacity', ''); + + if (!labels.length) return; + selection.selectAll('.layer-label text, .layer-halo path, .layer-halo rect') + .filter(function(d) { + return _.contains(labels, d.id); + }) + .style('opacity', 0); + } var rtree = new RTree(), rectangles = {}; return function drawLabels(surface, graph, entities, filter, dimensions, fullRedraw) { + d3.select(surface.node().parentNode) + .on('mousemove.hidelabels', hideOnMouseover); var hidePoints = !d3.select('.node.point').node(); From 7ea7326f9227198df3e846115019af222d08cb18 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 31 Jan 2013 14:11:53 -0500 Subject: [PATCH 071/415] Extract Select behavior --- index.html | 1 + js/id/behavior/select.js | 22 ++++++++++++++++++++++ js/id/modes/browse.js | 10 +--------- js/id/modes/select.js | 22 +++++----------------- test/index.html | 1 + 5 files changed, 30 insertions(+), 26 deletions(-) create mode 100644 js/id/behavior/select.js diff --git a/index.html b/index.html index f286e8a52..4fe1bd621 100644 --- a/index.html +++ b/index.html @@ -94,6 +94,7 @@ + diff --git a/js/id/behavior/select.js b/js/id/behavior/select.js new file mode 100644 index 000000000..cee2cc940 --- /dev/null +++ b/js/id/behavior/select.js @@ -0,0 +1,22 @@ +iD.behavior.Select = function(mode) { + var controller = mode.controller; + + function click() { + var datum = d3.select(d3.event.target).datum(); + if (datum instanceof iD.Entity) { + controller.enter(iD.modes.Select([datum.id])); + } else { + controller.enter(iD.modes.Browse()); + } + } + + var behavior = function(selection) { + selection.on('click.select', click); + }; + + behavior.off = function(selection) { + selection.on('click.select', null); + }; + + return behavior; +}; diff --git a/js/id/modes/browse.js b/js/id/modes/browse.js index b1fb1c8ae..7652576ea 100644 --- a/js/id/modes/browse.js +++ b/js/id/modes/browse.js @@ -14,19 +14,13 @@ iD.modes.Browse = function() { behaviors = [ iD.behavior.Hover(), + iD.behavior.Select(mode), iD.behavior.DragNode(mode), iD.behavior.DragMidpoint(mode)]; behaviors.forEach(function(behavior) { behavior(surface); }); - - surface.on('click.browse', function () { - var datum = d3.select(d3.event.target).datum(); - if (datum instanceof iD.Entity) { - mode.controller.enter(iD.modes.Select([datum.id])); - } - }); }; mode.exit = function() { @@ -35,8 +29,6 @@ iD.modes.Browse = function() { behaviors.forEach(function(behavior) { behavior.off(surface); }); - - surface.on('click.browse', null); }; return mode; diff --git a/js/id/modes/select.js b/js/id/modes/select.js index 37b2dee1d..b369bfca7 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -36,6 +36,7 @@ iD.modes.Select = function(selection, initial) { behaviors = [ iD.behavior.Hover(), + iD.behavior.Select(mode), iD.behavior.DragNode(mode), iD.behavior.DragMidpoint(mode)]; @@ -106,15 +107,6 @@ iD.modes.Select = function(selection, initial) { surface.call(radialMenu.close); }); - function click() { - var datum = d3.select(d3.event.target).datum(); - if (datum instanceof iD.Entity) { - mode.controller.enter(iD.modes.Select([datum.id])); - } else { - mode.controller.enter(iD.modes.Browse()); - } - } - function dblclick() { var target = d3.select(d3.event.target), datum = target.datum(); @@ -134,13 +126,11 @@ iD.modes.Select = function(selection, initial) { } } - surface.on('click.select', click) - .on('dblclick.select', dblclick); - d3.select(document) .call(keybinding); - surface.selectAll("*") + surface.on('dblclick.select', dblclick) + .selectAll("*") .filter(function (d) { return d && selection.indexOf(d.id) >= 0; }) .classed('selected', true); @@ -182,12 +172,10 @@ iD.modes.Select = function(selection, initial) { keybinding.off(); - surface.on('click.select', null) - .on('dblclick.select', null); - history.on('change.select', null); - surface.selectAll(".selected") + surface.on('dblclick.select', null) + .selectAll(".selected") .classed('selected', false); surface.call(radialMenu.close); diff --git a/test/index.html b/test/index.html index 52070c414..370b238fe 100644 --- a/test/index.html +++ b/test/index.html @@ -90,6 +90,7 @@ + From 5f41b74955e981e4265c66875c284daa044487d7 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 31 Jan 2013 16:00:02 -0500 Subject: [PATCH 072/415] Remove controller.exit --- js/id/behavior/add_way.js | 2 +- js/id/controller.js | 4 ---- js/id/modes/add_point.js | 2 +- js/id/modes/select.js | 2 +- 4 files changed, 3 insertions(+), 7 deletions(-) diff --git a/js/id/behavior/add_way.js b/js/id/behavior/add_way.js index fa038d201..89ba7f1cd 100644 --- a/js/id/behavior/add_way.js +++ b/js/id/behavior/add_way.js @@ -32,7 +32,7 @@ iD.behavior.AddWay = function(mode) { }; addWay.cancel = function() { - controller.exit(); + controller.enter(iD.modes.Browse()); }; return d3.rebind(addWay, event, 'on'); diff --git a/js/id/controller.js b/js/id/controller.js index 7e74c56d4..6c4e0fd1e 100644 --- a/js/id/controller.js +++ b/js/id/controller.js @@ -19,9 +19,5 @@ iD.Controller = function(map, history) { event.enter(mode); }; - controller.exit = function() { - controller.enter(iD.modes.Browse()); - }; - return d3.rebind(controller, event, 'on'); }; diff --git a/js/id/modes/add_point.js b/js/id/modes/add_point.js index feff66f43..6dd524bb5 100644 --- a/js/id/modes/add_point.js +++ b/js/id/modes/add_point.js @@ -32,7 +32,7 @@ iD.modes.AddPoint = function() { } function cancel() { - controller.exit(); + controller.enter(iD.modes.Browse()); } behavior = iD.behavior.Draw(map) diff --git a/js/id/modes/select.js b/js/id/modes/select.js index b369bfca7..8d29450d6 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -86,7 +86,7 @@ iD.modes.Select = function(selection, initial) { inspector .on('changeTags', changeTags) - .on('close', function() { mode.controller.exit(); }); + .on('close', function() { mode.controller.enter(iD.modes.Browse()); }); history.on('change.select', function() { // Exit mode if selected entity gets undone From 7ca6934f8a51888705a42c3bd05c0b8d0c48d5c2 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Thu, 31 Jan 2013 16:42:28 -0500 Subject: [PATCH 073/415] Fix #580 --- js/id/svg/lines.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/id/svg/lines.js b/js/id/svg/lines.js index 65cfccd27..83c533255 100644 --- a/js/id/svg/lines.js +++ b/js/id/svg/lines.js @@ -35,7 +35,7 @@ iD.svg.Lines = function(projection) { return function drawLines(surface, graph, entities, filter) { function drawPaths(group, lines, filter, classes, lineString) { - var paths = group.selectAll('path') + var paths = group.selectAll('path.line') .filter(filter) .data(lines, iD.Entity.key); From f430ecd6223d967f91ec710c61cf9d8f2f52b64a Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Thu, 31 Jan 2013 17:12:37 -0500 Subject: [PATCH 074/415] Fix keyboard-initiated way moving --- js/id/modes/move_way.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/js/id/modes/move_way.js b/js/id/modes/move_way.js index 50c228d8e..5c262e259 100644 --- a/js/id/modes/move_way.js +++ b/js/id/modes/move_way.js @@ -16,14 +16,18 @@ iD.modes.MoveWay = function(wayId) { origin = d3.mouse(selection.node()), annotation = t('operations.move.annotation.' + way.geometry(graph)); + // If intiated via keyboard + if (!origin[0] && !origin[1]) origin = null; + history.perform( iD.actions.Noop(), annotation); function move() { var p = d3.mouse(selection.node()), - delta = [p[0] - origin[0], - p[1] - origin[1]]; + delta = origin ? + [p[0] - origin[0], p[1] - origin[1]] : + [0, 0]; origin = p; From b9d77a8de6bab1c43396df97e70911f7cf3bf842 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Thu, 31 Jan 2013 17:44:14 -0500 Subject: [PATCH 075/415] Set data on midpoint circles --- js/id/svg/midpoints.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/js/id/svg/midpoints.js b/js/id/svg/midpoints.js index ee8d2c5e2..16cccd7f3 100644 --- a/js/id/svg/midpoints.js +++ b/js/id/svg/midpoints.js @@ -47,6 +47,9 @@ iD.svg.Midpoints = function(projection) { groups.attr('transform', iD.svg.PointTransform(projection)); + groups.selectAll('circle') + .data(_.values(midpoints), function (d) { return d.id; }); + groups.exit() .remove(); }; From 9cb0879818cc8c4fae3d04a8c6065fb34ad914c0 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Thu, 31 Jan 2013 17:47:24 -0500 Subject: [PATCH 076/415] Selecting implicitly sets data --- js/id/svg/midpoints.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/js/id/svg/midpoints.js b/js/id/svg/midpoints.js index 16cccd7f3..935f84719 100644 --- a/js/id/svg/midpoints.js +++ b/js/id/svg/midpoints.js @@ -47,8 +47,7 @@ iD.svg.Midpoints = function(projection) { groups.attr('transform', iD.svg.PointTransform(projection)); - groups.selectAll('circle') - .data(_.values(midpoints), function (d) { return d.id; }); + groups.select('circle'); groups.exit() .remove(); From bbccec75be436b5a32ef93a6578fc80d07fafa66 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 31 Jan 2013 18:20:57 -0500 Subject: [PATCH 077/415] Convert Hash to a behavior --- index.html | 2 +- js/id/{renderer => behavior}/hash.js | 52 ++++++++++------------- js/id/id.js | 4 +- test/index.html | 4 +- test/index_packaged.html | 2 +- test/spec/{renderer => behavior}/hash.js | 53 ++++++++---------------- 6 files changed, 47 insertions(+), 70 deletions(-) rename js/id/{renderer => behavior}/hash.js (68%) rename test/spec/{renderer => behavior}/hash.js (51%) diff --git a/index.html b/index.html index 4fe1bd621..870436dd1 100644 --- a/index.html +++ b/index.html @@ -38,7 +38,6 @@ - @@ -93,6 +92,7 @@ + diff --git a/js/id/renderer/hash.js b/js/id/behavior/hash.js similarity index 68% rename from js/id/renderer/hash.js rename to js/id/behavior/hash.js index f4204531f..65e748e28 100644 --- a/js/id/renderer/hash.js +++ b/js/id/behavior/hash.js @@ -1,9 +1,6 @@ -iD.Hash = function() { - var hash = { hadHash: false }, - s0 = null, // cached location.hash - lat = 90 - 1e-8, // allowable latitude range - controller, - map; +iD.behavior.Hash = function(controller, map) { + var s0 = null, // cached location.hash + lat = 90 - 1e-8; // allowable latitude range var parser = function(map, s) { var q = iD.util.stringQs(s); @@ -61,32 +58,29 @@ iD.Hash = function() { map.on('drawn.hash', null); } - hash.controller = function(_) { - if (!arguments.length) return controller; - controller = _; - return hash; - }; + function hash() { + map.on('move.hash', move); - hash.map = function(x) { - if (!arguments.length) return map; - if (map) { - map.on("move.hash", null); - window.removeEventListener("hashchange", hashchange, false); - } - map = x; - if (x) { - map.on("move.hash", move); - window.addEventListener("hashchange", hashchange, false); - if (location.hash) { - var q = iD.util.stringQs(location.hash.substring(1)); - if (q.id) { - willselect(q.id); - } - hashchange(); - hash.hadHash = true; + d3.select(window) + .on('hashchange.hash', hashchange); + + if (location.hash) { + var q = iD.util.stringQs(location.hash.substring(1)); + if (q.id) { + willselect(q.id); } + hashchange(); + hash.hadHash = true; } - return hash; + } + + hash.off = function() { + map.on('move.hash', null); + + d3.select(window) + .on('hashchange.hash', null); + + location.hash = ""; }; return hash; diff --git a/js/id/id.js b/js/id/id.js index 69e64e5fe..7fa1d76e4 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -234,7 +234,9 @@ window.iD = function(container) { d3.select(document) .call(keybinding); - var hash = iD.Hash().controller(controller).map(map); + var hash = iD.behavior.Hash(controller, map); + + hash(); if (!hash.hadHash) { map.centerZoom([-77.02271, 38.90085], 20); diff --git a/test/index.html b/test/index.html index 370b238fe..d03c5a6c7 100644 --- a/test/index.html +++ b/test/index.html @@ -41,7 +41,6 @@ - @@ -89,6 +88,7 @@ + @@ -149,6 +149,7 @@ + @@ -163,7 +164,6 @@ - diff --git a/test/index_packaged.html b/test/index_packaged.html index 007392f3b..60979e236 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -44,6 +44,7 @@ + @@ -58,7 +59,6 @@ - diff --git a/test/spec/renderer/hash.js b/test/spec/behavior/hash.js similarity index 51% rename from test/spec/renderer/hash.js rename to test/spec/behavior/hash.js index b0e5b567f..7616bf536 100644 --- a/test/spec/renderer/hash.js +++ b/test/spec/behavior/hash.js @@ -1,60 +1,41 @@ -describe("iD.Hash", function () { +describe("iD.behavior.Hash", function () { var hash, map, controller; beforeEach(function () { - hash = iD.Hash(); map = { on: function () { return map; }, zoom: function () { return arguments.length ? map : 0; }, center: function () { return arguments.length ? map : [0, 0]; }, centerZoom: function () { return arguments.length ? map : [0, 0]; } }; + controller = { on: function () { return controller; } }; + + hash = iD.behavior.Hash(controller, map); }); afterEach(function () { - hash.map(null); - location.hash = ""; + hash.off(); }); - describe("#map()", function () { - it("gets and sets map", function () { - expect(hash.controller(controller).map(map)).to.equal(hash); - expect(hash.map()).to.equal(map); - }); + it("sets hadHash if location.hash is present", function () { + location.hash = "map=20.00/38.87952/-77.02405"; + hash(); + expect(hash.hadHash).to.be.true; + }); - it("sets hadHash if location.hash is present", function () { - location.hash = "map=20.00/38.87952/-77.02405"; - hash.map(map); - expect(hash.hadHash).to.be.true; - }); - - it("centerZooms map to requested level", function () { - location.hash = "map=20.00/38.87952/-77.02405"; - sinon.spy(map, 'centerZoom'); - hash.map(map); - expect(map.centerZoom).to.have.been.calledWith([-77.02405,38.87952], 20.0); - }); - - it("binds the map's move event", function () { - sinon.spy(map, 'on'); - hash.map(map); - expect(map.on).to.have.been.calledWith('move.hash', sinon.match.instanceOf(Function)); - }); - - it("unbinds the map's move event", function () { - sinon.spy(map, 'on'); - hash.map(map); - hash.map(null); - expect(map.on).to.have.been.calledWith('move.hash', null); - }); + it("centerZooms map to requested level", function () { + location.hash = "map=20.00/38.87952/-77.02405"; + sinon.spy(map, 'centerZoom'); + hash(); + expect(map.centerZoom).to.have.been.calledWith([-77.02405,38.87952], 20.0); }); describe("on window hashchange events", function () { beforeEach(function () { - hash.map(map); + hash(); }); function onhashchange(fn) { @@ -77,7 +58,7 @@ describe("iD.Hash", function () { sinon.stub(map, 'on') .withArgs("move.hash", sinon.match.instanceOf(Function)) .yields(); - hash.map(map); + hash(); expect(location.hash).to.equal("#map=0.00/0/0"); }); }); From dcfe8ac85b65c52c13a45cd86212e30c3e4e998f Mon Sep 17 00:00:00 2001 From: Saman Bemel-Benrud Date: Thu, 31 Jan 2013 19:20:13 -0500 Subject: [PATCH 078/415] design pass on alignment toggles. --- css/app.css | 105 +++- img/source/sprite.svg | 982 +++++++++++++++++++++++++++++++++++++- js/id/ui/layerswitcher.js | 19 +- 3 files changed, 1075 insertions(+), 31 deletions(-) diff --git a/css/app.css b/css/app.css index fa750db1e..c979ea095 100644 --- a/css/app.css +++ b/css/app.css @@ -33,6 +33,7 @@ div, textarea, input, span, ul, li, ol, a, button { a, button, input, textarea { -webkit-tap-highlight-color:rgba(0,0,0,0); -webkit-touch-callout:none; + cursor:url(../img/cursor-pointer.png) 6 1, auto; } h2 { @@ -186,6 +187,7 @@ ul.toggle-list li a { display:block; border-top: 1px solid rgba(0, 0, 0, .5); } +ul.toggle-list li a:hover { background-color: #ececec;} ul.toggle-list .icon { float: left; @@ -253,7 +255,6 @@ button { font-size:12px; display: inline-block; height:40px; - cursor:url(../img/cursor-pointer.png) 6 1, auto; border-radius:4px; -webkit-transition: background 100ms; -moz-transition: background 100ms; @@ -620,6 +621,7 @@ a.selected:hover .toggle.icon { background-position: -40px -180px;} background: rgba(0,0,0,.5); border-radius: 0 0 4px 4px; } + .inspector-inner .add-tag:hover { background: rgba(0,0,0,.8); } @@ -635,25 +637,25 @@ a.selected:hover .toggle.icon { background-position: -40px -180px;} position:absolute; } -.map-control button { - width: 40px; +.map-control > button { + width: 30px; background: rgba(0,0,0,.5); border-radius: 0; border-bottom: 1px solid rgba(0, 0, 0, 1); } -.map-control button:hover { +.map-control > button:hover { background: rgba(0, 0, 0, .8); } -.map-control button.active:hover { +.map-control > button.active:hover { background: #6bc641; } .map-overlay { width: 150px; position:absolute; - left:50px; + left:40px; top:0; display: block; border-radius: 4px; @@ -663,7 +665,7 @@ a.selected:hover .toggle.icon { background-position: -40px -180px;} .zoombuttons { top:70px; - width: 40px; + width: 30px; } .zoombuttons button.zoom-in { @@ -680,24 +682,94 @@ a.selected:hover .toggle.icon { background-position: -40px -180px;} top:190px; } +.nudge-container { + margin-top: 10px; +} + .layerswitcher-control .adjustments button { - opacity:0.5; - height:20px; + height:30px; font-size:10px; - font-weight:normal; padding:0 5px 3px 5px; background: white; - border: 1px solid #ddd; - border-radius: 0; + border:0; + text-transform: uppercase; + font-weight: bold; + } .layerswitcher-control .adjustments button:hover { - opacity: 1; + background:#ececec; +} + +.layerswitcher-control .alignment-toggle:before { + content: ''; + display: inline-block; + height: 0; + width: 0; + margin-right: 5px; + border-top: 4px solid transparent; + border-bottom: 4px solid transparent; + border-left: 8px solid #7092ff; +} + +.layerswitcher-control .alignment-toggle.expanded:before { + border-top: 8px solid #7092ff; + border-bottom: 0; + border-right: 4px solid transparent; + border-left: 4px solid transparent; + } .layerswitcher-control .nudge { + text-indent: -9999px; + overflow: hidden; width:20px; - margin-right:2px; + border-radius: 0; + margin-right:1px; + position: relative; +} + +.layerswitcher-control .nudge::after { + content: ''; + display: block; + position: absolute; + margin: auto; + left: 0; right: 0; top: 0; bottom: 0; + height: 0; + width: 0; +} + +.layerswitcher-control .nudge.left::after { + border-top: 5px solid transparent; + border-bottom: 5px solid transparent; + border-left: 5px solid #222; +} + +.layerswitcher-control .nudge.right::after { + border-top: 5px solid transparent; + border-bottom: 5px solid transparent; + border-right: 5px solid #222; +} + +.layerswitcher-control .nudge.top::after { + border-right: 5px solid transparent; + border-left: 5px solid transparent; + border-bottom: 5px solid #222; +} + +.layerswitcher-control .nudge.bottom::after { + border-right: 5px solid transparent; + border-left: 5px solid transparent; + border-top: 5px solid #222; +} + +.layerswitcher-control .nudge:first-child { + border-radius: 4px 0 0 4px; +} + +.layerswitcher-control .reset { + width: 45px; + border-radius: 0 4px 4px 0; } .opacity-options-wrapper { @@ -711,6 +783,7 @@ a.selected:hover .toggle.icon { background-position: -40px -180px;} position: absolute; right: 10px; top: 10px; + border: 1px solid #ddd; } .opacity-options li { @@ -723,7 +796,7 @@ a.selected:hover .toggle.icon { background-position: -40px -180px;} .opacity-options li .select-box{ position: absolute; width:20px; - height:20px; + height:18px; z-index: 9999; } @@ -742,7 +815,7 @@ a.selected:hover .toggle.icon { background-position: -40px -180px;} background:#222; display:inline-block; width:20px; - height:20px; + height:18px; } /* Geocoder */ diff --git a/img/source/sprite.svg b/img/source/sprite.svg index 64b75e286..6248e28cc 100644 --- a/img/source/sprite.svg +++ b/img/source/sprite.svg @@ -7,6 +7,7 @@ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="420" @@ -38,9 +39,9 @@ borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" - inkscape:zoom="4" - inkscape:cx="202.2911" - inkscape:cy="164.38176" + inkscape:zoom="1" + inkscape:cx="581.64693" + inkscape:cy="71.693021" inkscape:document-units="px" inkscape:current-layer="layer12" showgrid="false" @@ -53,10 +54,10 @@ fit-margin-left="0" fit-margin-right="0" fit-margin-bottom="0" - showguides="true" + showguides="false" inkscape:guide-bbox="true" inkscape:snap-bbox="true" - inkscape:snap-nodes="true"> + inkscape:snap-nodes="false"> + + + + + + + + + + + + fix misalignment + + + + + RESET
diff --git a/js/id/ui/layerswitcher.js b/js/id/ui/layerswitcher.js index ea87cb7a9..298c469e0 100644 --- a/js/id/ui/layerswitcher.js +++ b/js/id/ui/layerswitcher.js @@ -17,12 +17,12 @@ iD.ui.layerswitcher = function(map) { }, { name: 'MapBox', source: iD.BackgroundSource.MapBox, - description: 'Satellite and Aerial Imagery', + description: 'Satellite and aerial imagery.', link: 'http://mapbox.com' }, { name: 'Custom', source: iD.BackgroundSource.Custom, - description: 'A custom layer (requires configuration)' + description: 'A custom layer (requires configuration).' }], opacities = [1, 0.5, 0]; @@ -139,10 +139,10 @@ iD.ui.layerswitcher = function(map) { .attr('class', 'adjustments pad1'); var directions = [ - ['←', [-1, 0]], - ['↑', [0, -1]], - ['→', [1, 0]], - ['↓', [0, 1]]]; + ['left', [-1, 0]], + ['top', [0, -1]], + ['right', [1, 0]], + ['bottom', [0, 1]]]; function nudge(d) { map.background.nudge(d[1]); @@ -150,17 +150,16 @@ iD.ui.layerswitcher = function(map) { } adjustments.append('a') - .text('▶ fix misalignment') + .text('Fix misalignment') .attr('href', '#') + .classed('alignment-toggle', true) .classed('expanded', false) .on('click', function() { var exp = d3.select(this).classed('expanded'); if (!exp) { nudge_container.style('display', 'block'); - d3.select(this).text('▼ fix misalignment'); } else { nudge_container.style('display', 'none'); - d3.select(this).text('▶ fix misalignment'); } d3.select(this).classed('expanded', !exp); d3.event.preventDefault(); @@ -174,7 +173,7 @@ iD.ui.layerswitcher = function(map) { nudge_container.selectAll('button') .data(directions).enter() .append('button') - .attr('class', 'nudge') + .attr('class', function(d) { return d[0] + ' nudge'; }) .text(function(d) { return d[0]; }) .on('click', nudge); From 844e674ea5b09d22ef4a860af99cd3064c620d00 Mon Sep 17 00:00:00 2001 From: Saman Bemel-Benrud Date: Thu, 31 Jan 2013 19:24:35 -0500 Subject: [PATCH 079/415] refine misalignment ui. --- css/app.css | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/css/app.css b/css/app.css index b0507e02f..718c0c39a 100644 --- a/css/app.css +++ b/css/app.css @@ -701,12 +701,20 @@ a.selected:hover .toggle.icon { background-position: -40px -180px;} background:#ececec; } +.layerswitcher-control .alignment-toggle { + display: block; + padding-left: 12px; + position: relative; +} + .layerswitcher-control .alignment-toggle:before { content: ''; - display: inline-block; + display: block; + position: absolute; height: 0; width: 0; - margin-right: 5px; + left: 0; + top: 4px; border-top: 4px solid transparent; border-bottom: 4px solid transparent; border-left: 8px solid #7092ff; From 8eb04ddb4d301fa22647ff4a1c6701fcbfbed05d Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Thu, 31 Jan 2013 19:41:52 -0500 Subject: [PATCH 080/415] combobox --- combobox.html | 170 ++++++++++++++++++++++++++++++++++++++++++ css/app.css | 43 +++++++++++ js/lib/d3.combobox.js | 147 ++++++++++++++++++++++++++++++++++++ 3 files changed, 360 insertions(+) create mode 100644 combobox.html create mode 100644 js/lib/d3.combobox.js diff --git a/combobox.html b/combobox.html new file mode 100644 index 000000000..0d97698b0 --- /dev/null +++ b/combobox.html @@ -0,0 +1,170 @@ + + + + + iD + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + diff --git a/css/app.css b/css/app.css index 4c149843d..2be1b819b 100644 --- a/css/app.css +++ b/css/app.css @@ -1267,3 +1267,46 @@ a.success-action { .icon.icon-pre-text { margin-right: 0px;} .save .label, .apply .label { display: block;} } + + + + + +div.combobox { + width:155px; + z-index: 9999; + display: none; + box-shadow: 0 5px 10px 0 rgba(0,0,0,.2); + margin-top: -1px; + background: white; + max-height: 180px; + overflow: auto; + border: 1px solid #ccc; +} + +div.combobox a { + height: 25px; + line-height: 25px; + cursor: pointer; + display: block; + border-top:1px solid #ccc; + background-color: #fff; + padding:1px 4px; + white-space: nowrap; +} + +div.combobox a:hover, +div.combobox a.selected { + background: #e1e8ff; + color: #154dff; +} + +div.combobox a:first-child { + border-top: 0; +} + +div.combobox-carat { + cursor: pointer; + padding:0 5px; + vertical-align:middle; +} diff --git a/js/lib/d3.combobox.js b/js/lib/d3.combobox.js new file mode 100644 index 000000000..165456563 --- /dev/null +++ b/js/lib/d3.combobox.js @@ -0,0 +1,147 @@ +d3.combobox = function() { + var event = d3.dispatch('accept'), + autohighlight = false, + autofilter = false, + input, + container, + data; + + var typeahead = function(selection) { + var hidden, idx = autohighlight ? 0 : -1; + + var rect = selection.select('input').node().getBoundingClientRect(); + + input = selection.select('input'); + + container = selection + .insert('div', ':first-child') + .attr('class', 'combobox') + .style({ + position: 'absolute', + display: 'none', + left: '0px', + width: rect.width + 'px', + top: rect.height + 'px' + }); + + carat = selection + .insert('div', ':first-child') + .attr('class', 'combobox-carat') + .text('+') + .style({ + position: 'absolute', + left: (rect.width - 20) + 'px', + top: '0px' + }) + .on('click', function() { + update(); + show(); + }); + + selection + .on('keyup.typeahead', key); + + hidden = false; + + function hide() { + idx = autohighlight ? 0 : -1; + hidden = true; + } + + function show() { + container.style('display', 'block'); + } + + function slowHide() { + if (autohighlight && container.select('a.selected').node()) { + select(container.select('a.selected').datum()); + event.accept(); + } + window.setTimeout(hide, 150); + } + + selection + .on('focus.typeahead', show) + .on('blur.typeahead', slowHide); + + function key() { + var len = container.selectAll('a').data().length; + if (d3.event.keyCode === 40) { + idx = Math.min(idx + 1, len - 1); + return highlight(); + } else if (d3.event.keyCode === 38) { + idx = Math.max(idx - 1, 0); + return highlight(); + } else if (d3.event.keyCode === 13) { + if (container.select('a.selected').node()) { + select(container.select('a.selected').datum()); + } + event.accept(); + hide(); + } else { + update(); + } + } + + function highlight() { + container + .selectAll('a') + .classed('selected', function(d, i) { return i == idx; }); + } + + function update() { + + function run(data) { + container.style('display', function() { + return data.length ? 'block' : 'none'; + }); + + var options = container + .selectAll('a') + .data(data, function(d) { return d.value; }); + + options.enter() + .append('a') + .text(function(d) { return d.value; }) + .attr('title', function(d) { return d.title; }) + .on('click', select); + + options.exit().remove(); + + options + .classed('selected', function(d, i) { return i == idx; }); + } + + if (typeof data === 'function') data(selection, run); + else run(data); + } + + function select(d) { + input + .property('value', d.value) + .trigger('change'); + container.style('display', 'none'); + } + + }; + + typeahead.data = function(_) { + if (!arguments.length) return data; + data = _; + return typeahead; + }; + + typeahead.autofilter = function(_) { + if (!arguments.length) return autofilter; + autofilter = _; + return typeahead; + }; + + typeahead.autohighlight = function(_) { + if (!arguments.length) return autohighlight; + autohighlight = _; + return typeahead; + }; + + return d3.rebind(typeahead, event, 'on'); +}; From 9494d8d468fd188bae5687703e8036488bc01c7c Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Feb 2013 10:16:18 -0500 Subject: [PATCH 081/415] Remove unused --- js/id/renderer/map.js | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index a2c4781ef..c02ff65cb 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -369,24 +369,6 @@ iD.Map = function() { return map; }; - map.hint = function (_) { - if (_ === false) { - d3.select('div.inspector-wrap') - .style('opacity', 0) - .style('display', 'none'); - } else { - d3.select('div.inspector-wrap') - .html('') - .style('display', 'block') - .transition() - .style('opacity', 1); - d3.select('div.inspector-wrap') - .append('div') - .attr('class','inspector-inner') - .text(_); - } - }; - map.editable = function() { return map.zoom() >= 16; }; From 559f3c9037f1e87ce87c7edbd5a30fdb4f04abec Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Fri, 1 Feb 2013 11:00:55 -0500 Subject: [PATCH 082/415] Remove unecessary line --- js/id/renderer/map.js | 1 - 1 file changed, 1 deletion(-) diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index c02ff65cb..cd99de738 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -55,7 +55,6 @@ iD.Map = function() { function pxCenter() { return [dimensions[0] / 2, dimensions[1] / 2]; } function drawVector(difference) { - if (surface.style(transformProp) != 'none') return; var filter, all, extent = map.extent(), graph = history.graph(); From ab7290a86595c2c3a440c2921200bb08413e9392 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Fri, 1 Feb 2013 11:31:31 -0500 Subject: [PATCH 083/415] Fix re-requesting failed tiles. Fixes #594 --- js/id/renderer/background.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/js/id/renderer/background.js b/js/id/renderer/background.js index 54e3835ee..cae44ca09 100644 --- a/js/id/renderer/background.js +++ b/js/id/renderer/background.js @@ -83,7 +83,10 @@ iD.Background = function() { } }); - requests = uniqueBy(requests, 3); + requests = uniqueBy(requests, 3).filter(function(r) { + // don't re-request tiles which have failed in the past + return cache[r[3]] !== false; + }); function load(d) { cache[d[3]] = true; @@ -125,7 +128,7 @@ iD.Background = function() { .attr('src', function(d) { return d[3]; }) .on('error', error) .on('load', load); - + image.style(transformProp, imageTransform); if (Object.keys(cache).length > 100) cache = {}; From 7e68e8e114020da318e33537cce9d57d1698cfeb Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 31 Jan 2013 15:50:00 -0500 Subject: [PATCH 084/415] Add iD.Context This is a facade interface that ties together a bunch of different internal objects and will make it easier to write tests for behaviors, modes, and operations. --- Makefile | 1 + index.html | 3 +- js/id/behavior/add_way.js | 18 ++++---- js/id/behavior/drag_midpoint.js | 15 +++--- js/id/behavior/drag_node.js | 22 ++++----- js/id/behavior/draw.js | 24 +++++----- js/id/behavior/draw_way.js | 69 ++++++++++++++-------------- js/id/behavior/hash.js | 29 ++++++------ js/id/behavior/select.js | 8 ++-- js/id/context.js | 44 ++++++++++++++++++ js/id/controller.js | 6 +-- js/id/id.js | 39 ++++++++++------ js/id/modes/add_area.js | 38 +++++++--------- js/id/modes/add_line.js | 42 ++++++++--------- js/id/modes/add_point.js | 25 ++++------ js/id/modes/browse.js | 22 ++++----- js/id/modes/draw_area.js | 12 ++--- js/id/modes/draw_line.js | 12 ++--- js/id/modes/move_way.js | 47 +++++++++---------- js/id/modes/select.js | 81 ++++++++++++++++----------------- js/id/operations/circularize.js | 20 +++----- js/id/operations/delete.js | 15 +++--- js/id/operations/move.js | 10 ++-- js/id/operations/reverse.js | 11 ++--- js/id/operations/split.js | 12 ++--- js/id/operations/unjoin.js | 12 ++--- js/id/ui/save.js | 29 +++--------- test/index.html | 11 +++-- test/index_packaged.html | 10 ++-- test/spec/behavior/hash.js | 70 ++++++++++++++-------------- test/spec/modes/add_point.js | 33 ++++++-------- test/spec/ui/confirm.js | 3 ++ test/spec/ui/modal.js | 1 + 33 files changed, 384 insertions(+), 410 deletions(-) create mode 100644 js/id/context.js diff --git a/Makefile b/Makefile index 1db4f2efb..598bce278 100644 --- a/Makefile +++ b/Makefile @@ -27,6 +27,7 @@ all: \ js/lib/sha.js \ js/id/start.js \ js/id/id.js \ + js/id/context.js \ js/id/connection.js \ js/id/oauth.js \ js/id/services/*.js \ diff --git a/index.html b/index.html index c8ddd7562..306575804 100644 --- a/index.html +++ b/index.html @@ -122,9 +122,10 @@ + + - diff --git a/js/id/behavior/add_way.js b/js/id/behavior/add_way.js index 89ba7f1cd..207c88566 100644 --- a/js/id/behavior/add_way.js +++ b/js/id/behavior/add_way.js @@ -1,8 +1,6 @@ -iD.behavior.AddWay = function(mode) { - var map = mode.map, - controller = mode.controller, - event = d3.dispatch('start', 'startFromWay', 'startFromNode', 'startFromMidpoint'), - draw = iD.behavior.Draw(map); +iD.behavior.AddWay = function(context) { + var event = d3.dispatch('start', 'startFromWay', 'startFromNode', 'startFromMidpoint'), + draw = iD.behavior.Draw(context); var addWay = function(surface) { draw.on('click', event.start) @@ -12,7 +10,8 @@ iD.behavior.AddWay = function(mode) { .on('cancel', addWay.cancel) .on('finish', addWay.cancel); - map.fastEnable(false) + context.map() + .fastEnable(false) .minzoom(16) .dblclickEnable(false); @@ -20,19 +19,20 @@ iD.behavior.AddWay = function(mode) { }; addWay.off = function(surface) { - map.fastEnable(true) + context.map() + .fastEnable(true) .minzoom(0) .tail(false); window.setTimeout(function() { - map.dblclickEnable(true); + context.map().dblclickEnable(true); }, 1000); surface.call(draw.off); }; addWay.cancel = function() { - controller.enter(iD.modes.Browse()); + context.enter(iD.modes.Browse(context)); }; return d3.rebind(addWay, event, 'on'); diff --git a/js/id/behavior/drag_midpoint.js b/js/id/behavior/drag_midpoint.js index 6041946cd..42099963c 100644 --- a/js/id/behavior/drag_midpoint.js +++ b/js/id/behavior/drag_midpoint.js @@ -1,16 +1,13 @@ -iD.behavior.DragMidpoint = function(mode) { - var history = mode.history, - projection = mode.map.projection; - +iD.behavior.DragMidpoint = function(context) { var behavior = iD.behavior.drag() .delegate(".midpoint") .origin(function(d) { - return projection(d.loc); + return context.projection(d.loc); }) .on('start', function(d) { var node = iD.Node(); - history.perform(iD.actions.AddMidpoint(d, node)); + context.perform(iD.actions.AddMidpoint(d, node)); var vertex = d3.selectAll('.vertex') .filter(function(data) { return data.id === node.id; }); @@ -19,11 +16,11 @@ iD.behavior.DragMidpoint = function(mode) { }) .on('move', function(d) { d3.event.sourceEvent.stopPropagation(); - history.replace( - iD.actions.MoveNode(d.id, projection.invert(d3.event.point))); + context.replace( + iD.actions.MoveNode(d.id, context.projection.invert(d3.event.point))); }) .on('end', function() { - history.replace( + context.replace( iD.actions.Noop(), t('operations.add.annotation.vertex')); }); diff --git a/js/id/behavior/drag_node.js b/js/id/behavior/drag_node.js index b9846617d..b62162e94 100644 --- a/js/id/behavior/drag_node.js +++ b/js/id/behavior/drag_node.js @@ -1,8 +1,6 @@ -iD.behavior.DragNode = function(mode) { - var history = mode.history, - size = mode.map.size(), - nudgeInterval, - projection = mode.map.projection; +iD.behavior.DragNode = function(context) { + var size = context.map().size(), + nudgeInterval; function edge(point) { var pad = [30, 100, 30, 100]; @@ -16,7 +14,7 @@ iD.behavior.DragNode = function(mode) { function startNudge(nudge) { if (nudgeInterval) window.clearInterval(nudgeInterval); nudgeInterval = window.setInterval(function() { - mode.map.pan(nudge).redraw(); + context.map().pan(nudge).redraw(); }, 50); } @@ -26,16 +24,16 @@ iD.behavior.DragNode = function(mode) { } function annotation(entity) { - return t('operations.move.annotation.' + entity.geometry(mode.history.graph())); + return t('operations.move.annotation.' + entity.geometry(context.graph())); } return iD.behavior.drag() .delegate(".node") .origin(function(entity) { - return projection(entity.loc); + return context.projection(entity.loc); }) .on('start', function() { - history.perform( + context.perform( iD.actions.Noop()); }) .on('move', function(entity) { @@ -45,13 +43,13 @@ iD.behavior.DragNode = function(mode) { if (nudge) startNudge(nudge); else stopNudge(); - history.replace( - iD.actions.MoveNode(entity.id, projection.invert(d3.event.point)), + context.replace( + iD.actions.MoveNode(entity.id, context.projection.invert(d3.event.point)), annotation(entity)); }) .on('end', function(entity) { stopNudge(); - history.replace( + context.replace( iD.actions.Noop(), annotation(entity)); }); diff --git a/js/id/behavior/draw.js b/js/id/behavior/draw.js index bce4470b6..8405ac341 100644 --- a/js/id/behavior/draw.js +++ b/js/id/behavior/draw.js @@ -1,7 +1,8 @@ -iD.behavior.Draw = function(map) { +iD.behavior.Draw = function(context) { var event = d3.dispatch('move', 'click', 'clickWay', 'clickNode', 'clickMidpoint', 'undo', 'cancel', 'finish'), keybinding = d3.keybinding('draw'), - down, surface, hover; + hover = iD.behavior.Hover(), + down; function datum() { if (d3.event.altKey) { @@ -28,7 +29,7 @@ iD.behavior.Draw = function(map) { function click() { var d = datum(); if (d.type === 'way') { - var choice = iD.geo.chooseIndex(d, d3.mouse(map.surface.node()), map); + var choice = iD.geo.chooseIndex(d, d3.mouse(context.surface().node()), context.map()); event.clickWay(d, choice.loc, choice.index); } else if (d.type === 'node') { @@ -38,19 +39,19 @@ iD.behavior.Draw = function(map) { event.clickMidpoint(d); } else { - event.click(map.mouseCoordinates()); + event.click(context.map().mouseCoordinates()); } } function keydown() { if (d3.event.keyCode === d3.keybinding.modifierCodes.alt) { - surface.call(hover.off); + context.uninstall(hover); } } function keyup() { if (d3.event.keyCode === d3.keybinding.modifierCodes.alt) { - surface.call(hover); + context.install(hover); } } @@ -70,8 +71,7 @@ iD.behavior.Draw = function(map) { } function draw(selection) { - surface = selection; - hover = iD.behavior.Hover(); + context.install(hover); keybinding .on('⌫', backspace) @@ -83,8 +83,7 @@ iD.behavior.Draw = function(map) { .on('mousedown.draw', mousedown) .on('mouseup.draw', mouseup) .on('mousemove.draw', mousemove) - .on('click.draw', click) - .call(hover); + .on('click.draw', click); d3.select(document) .call(keybinding) @@ -95,12 +94,13 @@ iD.behavior.Draw = function(map) { } draw.off = function(selection) { + context.uninstall(hover); + selection .on('mousedown.draw', null) .on('mouseup.draw', null) .on('mousemove.draw', null) - .on('click.draw', null) - .call(hover.off); + .on('click.draw', null); d3.select(document) .call(keybinding.off) diff --git a/js/id/behavior/draw_way.js b/js/id/behavior/draw_way.js index 1cc62b755..28a8fbb38 100644 --- a/js/id/behavior/draw_way.js +++ b/js/id/behavior/draw_way.js @@ -1,35 +1,32 @@ -iD.behavior.DrawWay = function(wayId, index, mode, baseGraph) { - var map = mode.map, - history = mode.history, - controller = mode.controller, - way = history.graph().entity(wayId), +iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) { + var way = context.entity(wayId), finished = false, annotation = t((way.isDegenerate() ? 'operations.start.annotation.' : - 'operations.continue.annotation.') + way.geometry(history.graph())), - draw = iD.behavior.Draw(map); + 'operations.continue.annotation.') + context.geometry(wayId)), + draw = iD.behavior.Draw(context); - var node = iD.Node({loc: map.mouseCoordinates()}), + var node = iD.Node({loc: context.map().mouseCoordinates()}), nodeId = node.id; - history[way.isDegenerate() ? 'replace' : 'perform']( + context[way.isDegenerate() ? 'replace' : 'perform']( iD.actions.AddEntity(node), iD.actions.AddVertex(wayId, node.id, index)); function move(datum) { - var loc = map.mouseCoordinates(); + var loc = context.map().mouseCoordinates(); if (datum.type === 'node' || datum.type === 'midpoint') { loc = datum.loc; } else if (datum.type === 'way') { - loc = iD.geo.chooseIndex(datum, d3.mouse(map.surface.node()), map).loc; + loc = iD.geo.chooseIndex(datum, d3.mouse(context.surface().node()), context.map()).loc; } - history.replace(iD.actions.MoveNode(nodeId, loc)); + context.replace(iD.actions.MoveNode(nodeId, loc)); } function undone() { - controller.enter(iD.modes.Browse()); + context.enter(iD.modes.Browse(context)); } var drawWay = function(surface) { @@ -38,11 +35,12 @@ iD.behavior.DrawWay = function(wayId, index, mode, baseGraph) { .on('clickWay', drawWay.addWay) .on('clickNode', drawWay.addNode) .on('clickMidpoint', drawWay.addMidpoint) - .on('undo', history.undo) + .on('undo', context.undo) .on('cancel', drawWay.cancel) .on('finish', drawWay.finish); - map.fastEnable(false) + context.map() + .fastEnable(false) .minzoom(16) .dblclickEnable(false); @@ -51,26 +49,29 @@ iD.behavior.DrawWay = function(wayId, index, mode, baseGraph) { .filter(function (d) { return d.id === wayId || d.id === nodeId; }) .classed('active', true); - history.on('undone.draw', undone); + context.history() + .on('undone.draw', undone); }; drawWay.off = function(surface) { if (!finished) - history.pop(); + context.pop(); - map.fastEnable(true) + context.map() + .fastEnable(true) .minzoom(0) .tail(false); window.setTimeout(function() { - map.dblclickEnable(true); + context.map().dblclickEnable(true); }, 1000); surface.call(draw.off) .selectAll('.way, .node') .classed('active', false); - history.on('undone.draw', null); + context.history() + .on('undone.draw', null); }; function ReplaceTemporaryNode(newNode) { @@ -85,74 +86,74 @@ iD.behavior.DrawWay = function(wayId, index, mode, baseGraph) { drawWay.add = function(loc) { var newNode = iD.Node({loc: loc}); - history.replace( + context.replace( iD.actions.AddEntity(newNode), ReplaceTemporaryNode(newNode), annotation); finished = true; - controller.enter(mode); + context.enter(mode); }; // Connect the way to an existing way. drawWay.addWay = function(way, loc, wayIndex) { var newNode = iD.Node({loc: loc}); - history.perform( + context.perform( iD.actions.AddEntity(newNode), iD.actions.AddVertex(way.id, newNode.id, wayIndex), ReplaceTemporaryNode(newNode), annotation); finished = true; - controller.enter(mode); + context.enter(mode); }; // Connect the way to an existing node and continue drawing. drawWay.addNode = function(node) { - history.perform( + context.perform( ReplaceTemporaryNode(node), annotation); finished = true; - controller.enter(mode); + context.enter(mode); }; // Add a midpoint, connect the way to it, and continue drawing. drawWay.addMidpoint = function(midpoint) { var node = iD.Node(); - history.perform( + context.perform( iD.actions.AddMidpoint(midpoint, node), ReplaceTemporaryNode(node), annotation); finished = true; - controller.enter(mode); + context.enter(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.pop(); + context.pop(); finished = true; - var way = history.graph().entity(wayId); + var way = context.entity(wayId); if (way) { - controller.enter(iD.modes.Select([way.id], true)); + context.enter(iD.modes.Select(context, [way.id], true)); } else { - controller.enter(iD.modes.Browse()); + context.enter(iD.modes.Browse(context)); } }; // Cancel the draw operation and return to browse, deleting everything drawn. drawWay.cancel = function() { - history.perform( + context.perform( d3.functor(baseGraph), t('operations.cancel_draw.annotation')); finished = true; - controller.enter(iD.modes.Browse()); + context.enter(iD.modes.Browse(context)); }; return d3.rebind(drawWay, event, 'on'); diff --git a/js/id/behavior/hash.js b/js/id/behavior/hash.js index 65e748e28..871b73a35 100644 --- a/js/id/behavior/hash.js +++ b/js/id/behavior/hash.js @@ -1,4 +1,4 @@ -iD.behavior.Hash = function(controller, map) { +iD.behavior.Hash = function(context) { var s0 = null, // cached location.hash lat = 90 - 1e-8; // allowable latitude range @@ -27,13 +27,13 @@ iD.behavior.Hash = function(controller, map) { }; var move = _.throttle(function() { - var s1 = formatter(map); + var s1 = formatter(context.map()); if (s0 !== s1) location.replace(s0 = s1); // don't recenter the map! }, 100); function hashchange() { if (location.hash === s0) return; // ignore spurious hashchange events - if (parser(map, (s0 = location.hash).substring(1))) { + if (parser(context.map(), (s0 = location.hash).substring(1))) { move(); // replace bogus hash } } @@ -42,24 +42,24 @@ iD.behavior.Hash = function(controller, map) { // do so before any features are loaded. thus wait for the feature to // be loaded and then select function willselect(id) { - map.on('drawn.hash', function() { - var entity = map.history().graph().entity(id); - if (entity === undefined) return; - else selectoff(); - controller.enter(iD.modes.Select([entity.id])); - map.on('drawn.hash', null); + context.map().on('drawn.hash', function() { + if (!context.entity(id)) return; + selectoff(); + context.enter(iD.modes.Select([id])); }); - controller.on('enter.hash', function() { - if (controller.mode.id !== 'browse') selectoff(); + + context.controller().on('enter.hash', function() { + if (context.mode().id !== 'browse') selectoff(); }); } function selectoff() { - map.on('drawn.hash', null); + context.map().on('drawn.hash', null); } function hash() { - map.on('move.hash', move); + context.map() + .on('move.hash', move); d3.select(window) .on('hashchange.hash', hashchange); @@ -75,7 +75,8 @@ iD.behavior.Hash = function(controller, map) { } hash.off = function() { - map.on('move.hash', null); + context.map() + .on('move.hash', null); d3.select(window) .on('hashchange.hash', null); diff --git a/js/id/behavior/select.js b/js/id/behavior/select.js index cee2cc940..e078fdbf1 100644 --- a/js/id/behavior/select.js +++ b/js/id/behavior/select.js @@ -1,12 +1,10 @@ -iD.behavior.Select = function(mode) { - var controller = mode.controller; - +iD.behavior.Select = function(context) { function click() { var datum = d3.select(d3.event.target).datum(); if (datum instanceof iD.Entity) { - controller.enter(iD.modes.Select([datum.id])); + context.enter(iD.modes.Select(context, [datum.id])); } else { - controller.enter(iD.modes.Browse()); + context.enter(iD.modes.Browse(context)); } } diff --git a/js/id/context.js b/js/id/context.js new file mode 100644 index 000000000..60f878b32 --- /dev/null +++ b/js/id/context.js @@ -0,0 +1,44 @@ +iD.Context = function() { + var history = iD.History(), + connection = iD.Connection(), + controller = iD.Controller(), + container, + map = iD.Map().connection(connection).history(history); + + var context = {}; + + context.container = function (_) { + if (!arguments.length) return container; + container = _; + return context; + }; + + context.connection = function () { return connection; }; + + context.history = function () { return history; }; + context.graph = history.graph; + context.perform = history.perform; + context.replace = history.replace; + context.pop = history.pop; + context.undo = history.undo; + context.redo = history.undo; + context.changes = history.changes; + + context.entity = function (id) { return history.graph().entity(id); }; + context.geometry = function (id) { return context.entity(id).geometry(history.graph()); }; + + context.controller = function () { return controller; }; + context.enter = controller.enter; + context.mode = function () { return controller.mode; }; + + context.install = function (behavior) { context.surface().call(behavior); }; + context.uninstall = function (behavior) { context.surface().call(behavior.off); }; + + context.map = function () { return map; }; + context.background = function () { return map.background; }; + context.surface = function () { return map.surface; }; + context.projection = map.projection; + context.tail = map.tail; + + return context; +}; diff --git a/js/id/controller.js b/js/id/controller.js index 6c4e0fd1e..af1e7c388 100644 --- a/js/id/controller.js +++ b/js/id/controller.js @@ -1,14 +1,10 @@ // A controller holds a single action at a time and calls `.enter` and `.exit` // to bind and unbind actions. -iD.Controller = function(map, history) { +iD.Controller = function() { var event = d3.dispatch('enter', 'exit'); var controller = { mode: null }; controller.enter = function(mode) { - mode.controller = controller; - mode.history = history; - mode.map = map; - if (controller.mode) { controller.mode.exit(); event.exit(controller.mode); diff --git a/js/id/id.js b/js/id/id.js index 3e8aabcc3..3c2d0c482 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -2,17 +2,22 @@ window.iD = function(container) { // the reported, displayed version of iD. var version = '0.0.0-alpha1'; - var connection = iD.Connection() - .version(version), - history = iD.History(), - map = iD.Map() - .connection(connection) - .history(history), - controller = iD.Controller(map, history); + var context = iD.Context(); - map.background.source(iD.BackgroundSource.Bing); + var connection = context.connection(), + history = context.history(), + map = context.map(), + controller = context.controller(); + + context.connection() + .version(version); + + context.background() + .source(iD.BackgroundSource.Bing); function editor(container) { + context.container(container); + if (!iD.supported()) { container.html('This editor is supported in Firefox, Chrome, Safari, Opera, ' + 'and Internet Explorer 9 and above. Please upgrade your browser ' + @@ -39,8 +44,14 @@ window.iD = function(container) { var buttons_joined = limiter.append('div') .attr('class', 'button-wrap joined col4'); + var modes = [ + iD.modes.Browse(context), + iD.modes.AddPoint(context), + iD.modes.AddLine(context), + iD.modes.AddArea(context)]; + var buttons = buttons_joined.selectAll('button.add-button') - .data([iD.modes.Browse(), iD.modes.AddPoint(), iD.modes.AddLine(), iD.modes.AddArea()]) + .data(modes) .enter().append('button') .attr('tabindex', -1) .attr('class', function (mode) { return mode.title + ' add-button col3'; }) @@ -57,7 +68,7 @@ window.iD = function(container) { } else { buttons.attr('disabled', 'disabled'); notice.message(true); - controller.enter(iD.modes.Browse()); + controller.enter(iD.modes.Browse(context)); } } @@ -106,7 +117,7 @@ window.iD = function(container) { var save_button = limiter.append('div').attr('class','button-wrap col1').append('button') .attr('class', 'save col12') - .call(iD.ui.save().map(map).controller(controller)); + .call(iD.ui.save(context)); var zoom = container.append('div') .attr('class', 'zoombuttons map-control') @@ -227,14 +238,14 @@ window.iD = function(container) { .on('⌃+⇧+Z', function() { history.redo(); }) .on('⌫', function() { d3.event.preventDefault(); }); - [iD.modes.Browse(), iD.modes.AddPoint(), iD.modes.AddLine(), iD.modes.AddArea()].forEach(function(m) { + modes.forEach(function(m) { keybinding.on(m.key, function() { if (map.editable()) controller.enter(m); }); }); d3.select(document) .call(keybinding); - var hash = iD.behavior.Hash(controller, map); + var hash = iD.behavior.Hash(context); hash(); @@ -246,7 +257,7 @@ window.iD = function(container) { .on('logout.editor', connection.logout) .on('login.editor', connection.authenticate)); - controller.enter(iD.modes.Browse()); + controller.enter(iD.modes.Browse(context)); if (!localStorage.sawSplash) { iD.ui.splash(); diff --git a/js/id/modes/add_area.js b/js/id/modes/add_area.js index bda4e0daa..ae2e008ee 100644 --- a/js/id/modes/add_area.js +++ b/js/id/modes/add_area.js @@ -1,4 +1,4 @@ -iD.modes.AddArea = function() { +iD.modes.AddArea = function(context) { var mode = { id: 'add-area', button: 'area', @@ -11,77 +11,73 @@ iD.modes.AddArea = function() { defaultTags = {area: 'yes'}; mode.enter = function() { - var map = mode.map, - history = mode.history, - controller = mode.controller; - function start(loc) { - var graph = history.graph(), + var graph = context.graph(), node = iD.Node({loc: loc}), way = iD.Way({tags: defaultTags}); - history.perform( + context.perform( iD.actions.AddEntity(node), iD.actions.AddEntity(way), iD.actions.AddVertex(way.id, node.id), iD.actions.AddVertex(way.id, node.id)); - controller.enter(iD.modes.DrawArea(way.id, graph)); + context.enter(iD.modes.DrawArea(context, way.id, graph)); } function startFromWay(other, loc, index) { - var graph = history.graph(), + var graph = context.graph(), node = iD.Node({loc: loc}), way = iD.Way({tags: defaultTags}); - history.perform( + context.perform( iD.actions.AddEntity(node), iD.actions.AddEntity(way), iD.actions.AddVertex(way.id, node.id), iD.actions.AddVertex(way.id, node.id), iD.actions.AddVertex(other.id, node.id, index)); - controller.enter(iD.modes.DrawArea(way.id, graph)); + context.enter(iD.modes.DrawArea(context, way.id, graph)); } function startFromNode(node) { - var graph = history.graph(), + var graph = context.graph(), way = iD.Way({tags: defaultTags}); - history.perform( + context.perform( iD.actions.AddEntity(way), iD.actions.AddVertex(way.id, node.id), iD.actions.AddVertex(way.id, node.id)); - controller.enter(iD.modes.DrawArea(way.id, graph)); + context.enter(iD.modes.DrawArea(context, way.id, graph)); } function startFromMidpoint(midpoint) { - var graph = history.graph(), + var graph = context.graph(), node = iD.Node(), way = iD.Way({tags: defaultTags}); - history.perform( + context.perform( iD.actions.AddMidpoint(midpoint, node), iD.actions.AddEntity(way), iD.actions.AddVertex(way.id, node.id), iD.actions.AddVertex(way.id, node.id)); - controller.enter(iD.modes.DrawArea(way.id, graph)); + context.enter(iD.modes.DrawArea(context, way.id, graph)); } - behavior = iD.behavior.AddWay(mode) + behavior = iD.behavior.AddWay(context) .on('start', start) .on('startFromWay', startFromWay) .on('startFromNode', startFromNode) .on('startFromMidpoint', startFromMidpoint); - mode.map.surface.call(behavior); - mode.map.tail(t('modes.add_area.tail')); + context.install(behavior); + context.tail(t('modes.add_area.tail')); }; mode.exit = function() { - mode.map.surface.call(behavior.off); + context.uninstall(behavior); }; return mode; diff --git a/js/id/modes/add_line.js b/js/id/modes/add_line.js index 92e9a64d7..79c5f3031 100644 --- a/js/id/modes/add_line.js +++ b/js/id/modes/add_line.js @@ -1,4 +1,4 @@ -iD.modes.AddLine = function() { +iD.modes.AddLine = function(context) { var mode = { id: 'add-line', button: 'line', @@ -11,84 +11,80 @@ iD.modes.AddLine = function() { defaultTags = {highway: 'residential'}; mode.enter = function() { - var map = mode.map, - history = mode.history, - controller = mode.controller; - function start(loc) { - var graph = history.graph(), + var graph = context.graph(), node = iD.Node({loc: loc}), way = iD.Way({tags: defaultTags}); - history.perform( + context.perform( iD.actions.AddEntity(node), iD.actions.AddEntity(way), iD.actions.AddVertex(way.id, node.id)); - controller.enter(iD.modes.DrawLine(way.id, 'forward', graph)); + context.enter(iD.modes.DrawLine(context, way.id, 'forward', graph)); } function startFromWay(other, loc, index) { - var graph = history.graph(), + var graph = context.graph(), node = iD.Node({loc: loc}), way = iD.Way({tags: defaultTags}); - history.perform( + context.perform( iD.actions.AddEntity(node), iD.actions.AddEntity(way), iD.actions.AddVertex(way.id, node.id), iD.actions.AddVertex(other.id, node.id, index)); - controller.enter(iD.modes.DrawLine(way.id, 'forward', graph)); + context.enter(iD.modes.DrawLine(context, way.id, 'forward', graph)); } function startFromNode(node) { - var graph = history.graph(), + var graph = context.graph(), parent = graph.parentWays(node)[0], isLine = parent && parent.geometry(graph) === 'line'; if (isLine && parent.first() === node.id) { - controller.enter(iD.modes.DrawLine(parent.id, 'backward', graph)); + context.enter(iD.modes.DrawLine(context, parent.id, 'backward', graph)); } else if (isLine && parent.last() === node.id) { - controller.enter(iD.modes.DrawLine(parent.id, 'forward', graph)); + context.enter(iD.modes.DrawLine(context, parent.id, 'forward', graph)); } else { var way = iD.Way({tags: defaultTags}); - history.perform( + context.perform( iD.actions.AddEntity(way), iD.actions.AddVertex(way.id, node.id)); - controller.enter(iD.modes.DrawLine(way.id, 'forward', graph)); + context.enter(iD.modes.DrawLine(context, way.id, 'forward', graph)); } } function startFromMidpoint(midpoint) { - var graph = history.graph(), + var graph = context.graph(), node = iD.Node(), way = iD.Way({tags: defaultTags}); - history.perform( + context.perform( iD.actions.AddMidpoint(midpoint, node), iD.actions.AddEntity(way), iD.actions.AddVertex(way.id, node.id)); - controller.enter(iD.modes.DrawLine(way.id, 'forward', graph)); + context.enter(iD.modes.DrawLine(context, way.id, 'forward', graph)); } - behavior = iD.behavior.AddWay(mode) + behavior = iD.behavior.AddWay(context) .on('start', start) .on('startFromWay', startFromWay) .on('startFromNode', startFromNode) .on('startFromMidpoint', startFromMidpoint); - mode.map.surface.call(behavior); - mode.map.tail(t('modes.add_line.tail')); + context.install(behavior); + context.tail(t('modes.add_line.tail')); }; mode.exit = function() { - mode.map.surface.call(behavior.off); + context.uninstall(behavior); }; return mode; diff --git a/js/id/modes/add_point.js b/js/id/modes/add_point.js index 6dd524bb5..c3dc7f075 100644 --- a/js/id/modes/add_point.js +++ b/js/id/modes/add_point.js @@ -1,4 +1,4 @@ -iD.modes.AddPoint = function() { +iD.modes.AddPoint = function(context) { var mode = { id: 'add-point', title: t('modes.add_point.title'), @@ -9,18 +9,14 @@ iD.modes.AddPoint = function() { var behavior; mode.enter = function() { - var map = mode.map, - history = mode.history, - controller = mode.controller; - function add(loc) { var node = iD.Node({loc: loc}); - history.perform( + context.perform( iD.actions.AddEntity(node), t('operations.add.annotation.point')); - controller.enter(iD.modes.Select([node.id], true)); + context.enter(iD.modes.Select(context, [node.id], true)); } function addWay(way, loc, index) { @@ -32,10 +28,10 @@ iD.modes.AddPoint = function() { } function cancel() { - controller.enter(iD.modes.Browse()); + context.enter(iD.modes.Browse(context)); } - behavior = iD.behavior.Draw(map) + behavior = iD.behavior.Draw(context) .on('click', add) .on('clickWay', addWay) .on('clickNode', addNode) @@ -43,16 +39,13 @@ iD.modes.AddPoint = function() { .on('cancel', cancel) .on('finish', cancel); - mode.map.surface.call(behavior); - mode.map.tail(t('modes.add_point.tail')); + context.install(behavior); + context.tail(t('modes.add_point.tail')); }; mode.exit = function() { - var map = mode.map, - surface = map.surface; - - map.tail(false); - behavior.off(surface); + context.tail(false); + context.uninstall(behavior); }; return mode; diff --git a/js/id/modes/browse.js b/js/id/modes/browse.js index 7652576ea..33add1262 100644 --- a/js/id/modes/browse.js +++ b/js/id/modes/browse.js @@ -1,4 +1,4 @@ -iD.modes.Browse = function() { +iD.modes.Browse = function(context) { var mode = { button: 'browse', id: 'browse', @@ -7,27 +7,21 @@ iD.modes.Browse = function() { key: t('modes.browse.key') }; - var behaviors; + var behaviors = [ + iD.behavior.Hover(), + iD.behavior.Select(context), + iD.behavior.DragNode(context), + iD.behavior.DragMidpoint(context)]; mode.enter = function() { - var surface = mode.map.surface; - - behaviors = [ - iD.behavior.Hover(), - iD.behavior.Select(mode), - iD.behavior.DragNode(mode), - iD.behavior.DragMidpoint(mode)]; - behaviors.forEach(function(behavior) { - behavior(surface); + context.install(behavior); }); }; mode.exit = function() { - var surface = mode.map.surface; - behaviors.forEach(function(behavior) { - behavior.off(surface); + context.uninstall(behavior); }); }; diff --git a/js/id/modes/draw_area.js b/js/id/modes/draw_area.js index 9801a092c..aee2f153c 100644 --- a/js/id/modes/draw_area.js +++ b/js/id/modes/draw_area.js @@ -1,4 +1,4 @@ -iD.modes.DrawArea = function(wayId, baseGraph) { +iD.modes.DrawArea = function(context, wayId, baseGraph) { var mode = { button: 'area', id: 'draw-area' @@ -7,11 +7,11 @@ iD.modes.DrawArea = function(wayId, baseGraph) { var behavior; mode.enter = function() { - var way = mode.history.graph().entity(wayId), + var way = context.entity(wayId), headId = way.nodes[way.nodes.length - 2], tailId = way.first(); - behavior = iD.behavior.DrawWay(wayId, -1, mode, baseGraph); + behavior = iD.behavior.DrawWay(context, wayId, -1, mode, baseGraph); var addNode = behavior.addNode; @@ -23,12 +23,12 @@ iD.modes.DrawArea = function(wayId, baseGraph) { } }; - mode.map.surface.call(behavior); - mode.map.tail(t('modes.draw_area.tail')); + context.install(behavior); + context.tail(t('modes.draw_area.tail')); }; mode.exit = function() { - mode.map.surface.call(behavior.off); + context.uninstall(behavior); }; return mode; diff --git a/js/id/modes/draw_line.js b/js/id/modes/draw_line.js index 5ae8c2703..9fd07c937 100644 --- a/js/id/modes/draw_line.js +++ b/js/id/modes/draw_line.js @@ -1,4 +1,4 @@ -iD.modes.DrawLine = function(wayId, direction, baseGraph) { +iD.modes.DrawLine = function(context, wayId, direction, baseGraph) { var mode = { button: 'line', id: 'draw-line' @@ -7,11 +7,11 @@ iD.modes.DrawLine = function(wayId, direction, baseGraph) { var behavior; mode.enter = function() { - var way = mode.history.graph().entity(wayId), + var way = context.entity(wayId), index = (direction === 'forward') ? undefined : 0, headId = (direction === 'forward') ? way.last() : way.first(); - behavior = iD.behavior.DrawWay(wayId, index, mode, baseGraph); + behavior = iD.behavior.DrawWay(context, wayId, index, mode, baseGraph); var addNode = behavior.addNode; @@ -23,12 +23,12 @@ iD.modes.DrawLine = function(wayId, direction, baseGraph) { } }; - mode.map.surface.call(behavior); - mode.map.tail(t('modes.draw_line.tail')); + context.install(behavior); + context.tail(t('modes.draw_line.tail')); }; mode.exit = function() { - mode.map.surface.call(behavior.off); + context.uninstall(behavior); }; return mode; diff --git a/js/id/modes/move_way.js b/js/id/modes/move_way.js index 5c262e259..30c473406 100644 --- a/js/id/modes/move_way.js +++ b/js/id/modes/move_way.js @@ -1,4 +1,4 @@ -iD.modes.MoveWay = function(wayId) { +iD.modes.MoveWay = function(context, wayId) { var mode = { id: 'move-way' }; @@ -6,55 +6,53 @@ iD.modes.MoveWay = function(wayId) { var keybinding = d3.keybinding('move-way'); mode.enter = function() { - var map = mode.map, - history = mode.history, - graph = history.graph(), - selection = map.surface, - controller = mode.controller, - projection = map.projection, - way = graph.entity(wayId), - origin = d3.mouse(selection.node()), - annotation = t('operations.move.annotation.' + way.geometry(graph)); + var origin = point(), + annotation = t('operations.move.annotation.' + context.geometry(wayId)); // If intiated via keyboard if (!origin[0] && !origin[1]) origin = null; - history.perform( + context.perform( iD.actions.Noop(), annotation); + function point() { + return d3.mouse(context.surface().node()); + } + function move() { - var p = d3.mouse(selection.node()), + var p = point(), delta = origin ? [p[0] - origin[0], p[1] - origin[1]] : [0, 0]; origin = p; - history.replace( - iD.actions.MoveWay(wayId, delta, projection), + context.replace( + iD.actions.MoveWay(wayId, delta, context.projection), annotation); } function finish() { d3.event.stopPropagation(); - controller.enter(iD.modes.Select([way.id], true)); + context.enter(iD.modes.Select(context, [wayId], true)); } function cancel() { - history.pop(); - controller.enter(iD.modes.Select([way.id], true)); + context.pop(); + context.enter(iD.modes.Select(context, [wayId], true)); } function undone() { - controller.enter(iD.modes.Browse()); + context.enter(iD.modes.Browse(context)); } - selection + context.selection() .on('mousemove.move-way', move) .on('click.move-way', finish); - history.on('undone.move-way', undone); + context.history() + .on('undone.move-way', undone); keybinding .on('⎋', cancel) @@ -65,15 +63,12 @@ iD.modes.MoveWay = function(wayId) { }; mode.exit = function() { - var map = mode.map, - history = mode.history, - selection = map.surface; - - selection + context.selection() .on('mousemove.move-way', null) .on('click.move-way', null); - history.on('undone.move-way', null); + context.history() + .on('undone.move-way', null); keybinding.off(); }; diff --git a/js/id/modes/select.js b/js/id/modes/select.js index 8d29450d6..df445862d 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -1,4 +1,4 @@ -iD.modes.Select = function(selection, initial) { +iD.modes.Select = function(context, selection, initial) { var mode = { id: 'select', button: 'browse' @@ -6,12 +6,16 @@ iD.modes.Select = function(selection, initial) { var inspector = iD.ui.inspector().initial(!!initial), keybinding = d3.keybinding('select'), - behaviors, + behaviors = [ + iD.behavior.Hover(), + iD.behavior.Select(context), + iD.behavior.DragNode(context), + iD.behavior.DragMidpoint(context)], radialMenu; function changeTags(d, tags) { if (!_.isEqual(singular().tags, tags)) { - mode.history.perform( + context.perform( iD.actions.ChangeTags(d.id, tags), t('operations.change_tags.annotation')); } @@ -19,7 +23,7 @@ iD.modes.Select = function(selection, initial) { function singular() { if (selection.length === 1) { - return mode.map.history().graph().entity(selection[0]); + return context.entity(selection[0]); } } @@ -28,24 +32,14 @@ iD.modes.Select = function(selection, initial) { }; mode.enter = function() { - var map = mode.map, - history = map.history(), - graph = history.graph(), - surface = map.surface, - entity = singular(); - - behaviors = [ - iD.behavior.Hover(), - iD.behavior.Select(mode), - iD.behavior.DragNode(mode), - iD.behavior.DragMidpoint(mode)]; + var entity = singular(); behaviors.forEach(function(behavior) { - behavior(surface); + context.install(behavior); }); var operations = d3.values(iD.operations) - .map(function (o) { return o(selection, mode); }) + .map(function (o) { return o(selection, context); }) .filter(function (o) { return o.available(); }); operations.forEach(function(operation) { @@ -62,9 +56,10 @@ iD.modes.Select = function(selection, initial) { }), true)); if (entity) { - inspector.graph(graph); + inspector.graph(context.graph()); - d3.select('.inspector-wrap') + context.container() + .select('.inspector-wrap') .style('display', 'block') .style('opacity', 1) .datum(entity) @@ -73,38 +68,38 @@ iD.modes.Select = function(selection, initial) { if (d3.event) { // Pan the map if the clicked feature intersects with the position // of the inspector - var inspector_size = d3.select('.inspector-wrap').size(), - map_size = mode.map.size(), + var inspector_size = context.container().select('.inspector-wrap').size(), + map_size = context.map().size(), offset = 50, shift_left = d3.event.x - map_size[0] + inspector_size[0] + offset, center = (map_size[0] / 2) + shift_left + offset; if (shift_left > 0 && inspector_size[1] > d3.event.y) { - mode.map.centerEase(mode.map.projection.invert([center, map_size[1]/2])); + context.map().centerEase(context.projection.invert([center, map_size[1]/2])); } } inspector .on('changeTags', changeTags) - .on('close', function() { mode.controller.enter(iD.modes.Browse()); }); + .on('close', function() { context.enter(iD.modes.Browse(context)); }); - history.on('change.select', function() { + context.history().on('change.select', function() { // Exit mode if selected entity gets undone var oldEntity = entity, - newEntity = history.graph().entity(selection[0]); + newEntity = context.entity(selection[0]); if (!newEntity) { - mode.controller.enter(iD.modes.Browse()); + context.enter(iD.modes.Browse(context)); } else if (!_.isEqual(oldEntity.tags, newEntity.tags)) { inspector.tags(newEntity.tags); } - surface.call(radialMenu.close); + context.surface().call(radialMenu.close); }); } - map.on('move.select', function() { - surface.call(radialMenu.close); + context.map().on('move.select', function() { + context.surface().call(radialMenu.close); }); function dblclick() { @@ -113,10 +108,10 @@ iD.modes.Select = function(selection, initial) { if (datum instanceof iD.Way && !target.classed('fill')) { var choice = iD.geo.chooseIndex(datum, - d3.mouse(mode.map.surface.node()), mode.map), + d3.mouse(context.surface().node()), context.map()), node = iD.Node({ loc: choice.loc }); - history.perform( + context.perform( iD.actions.AddEntity(node), iD.actions.AddVertex(datum.id, node.id, choice.index), t('operations.add.annotation.vertex')); @@ -129,7 +124,8 @@ iD.modes.Select = function(selection, initial) { d3.select(document) .call(keybinding); - surface.on('dblclick.select', dblclick) + context.surface() + .on('dblclick.select', dblclick) .selectAll("*") .filter(function (d) { return d && selection.indexOf(d.id) >= 0; }) .classed('selected', true); @@ -137,25 +133,23 @@ iD.modes.Select = function(selection, initial) { radialMenu = iD.ui.RadialMenu(operations); if (d3.event && !initial) { - var loc = map.mouseCoordinates(); + var loc = context.map().mouseCoordinates(); if (entity && entity.type === 'node') { loc = entity.loc; } - surface.call(radialMenu, map.projection(loc)); + context.surface().call(radialMenu, context.projection(loc)); } }; mode.exit = function () { - var surface = mode.map.surface, - history = mode.history; - if (singular()) { changeTags(singular(), inspector.tags()); } - d3.select('.inspector-wrap') + context.container() + .select('.inspector-wrap') .style('display', 'none') .html(''); @@ -164,7 +158,7 @@ iD.modes.Select = function(selection, initial) { d3.selectAll('div.typeahead').remove(); behaviors.forEach(function(behavior) { - behavior.off(surface); + context.uninstall(behavior); }); var q = iD.util.stringQs(location.hash.substring(1)); @@ -172,13 +166,14 @@ iD.modes.Select = function(selection, initial) { keybinding.off(); - history.on('change.select', null); + context.history() + .on('change.select', null); - surface.on('dblclick.select', null) + context.surface() + .call(radialMenu.close) + .on('dblclick.select', null) .selectAll(".selected") .classed('selected', false); - - surface.call(radialMenu.close); }; return mode; diff --git a/js/id/operations/circularize.js b/js/id/operations/circularize.js index 4b0f27bf9..419a6e9eb 100644 --- a/js/id/operations/circularize.js +++ b/js/id/operations/circularize.js @@ -1,25 +1,19 @@ -iD.operations.Circularize = function(selection, mode) { +iD.operations.Circularize = function(selection, context) { var entityId = selection[0], - history = mode.map.history(), - action = iD.actions.Circularize(entityId, mode.map); + action = iD.actions.Circularize(entityId, context.map()); var operation = function() { - var graph = history.graph(), - entity = graph.entity(entityId), - annotation = t('operations.circularize.annotation.' + entity.geometry(graph)); - - history.perform(action, annotation); + var annotation = t('operations.circularize.annotation.' + context.geometry(entityId)); + context.perform(action, annotation); }; operation.available = function() { - var graph = history.graph(), - entity = graph.entity(entityId); - return selection.length === 1 && entity.type === 'way'; + return selection.length === 1 && + context.entity(entityId).type === 'way'; }; operation.enabled = function() { - var graph = history.graph(); - return action.enabled(graph); + return action.enabled(context.graph()); }; operation.id = "circularize"; diff --git a/js/id/operations/delete.js b/js/id/operations/delete.js index 12df22a09..d631a2ed7 100644 --- a/js/id/operations/delete.js +++ b/js/id/operations/delete.js @@ -1,21 +1,18 @@ -iD.operations.Delete = function(selection, mode) { - var entityId = selection[0], - history = mode.map.history(); +iD.operations.Delete = function(selection, context) { + var entityId = selection[0]; var operation = function() { - var graph = history.graph(), - entity = graph.entity(entityId), + var entity = context.entity(entityId), action = {way: iD.actions.DeleteWay, node: iD.actions.DeleteNode}[entity.type], - annotation = t('operations.delete.annotation.' + entity.geometry(graph)); + annotation = t('operations.delete.annotation.' + context.geometry(entityId)); - history.perform( + context.perform( action(entityId), annotation); }; operation.available = function() { - var graph = history.graph(), - entity = graph.entity(entityId); + var entity = context.entity(entityId); return selection.length === 1 && (entity.type === 'way' || entity.type === 'node'); }; diff --git a/js/id/operations/move.js b/js/id/operations/move.js index 10405cc53..d9da39f64 100644 --- a/js/id/operations/move.js +++ b/js/id/operations/move.js @@ -1,15 +1,13 @@ -iD.operations.Move = function(selection, mode) { - var entityId = selection[0], - history = mode.map.history(); +iD.operations.Move = function(selection, context) { + var entityId = selection[0]; var operation = function() { - mode.controller.enter(iD.modes.MoveWay(entityId)); + context.enter(iD.modes.MoveWay(context, entityId)); }; operation.available = function() { - var graph = history.graph(); return selection.length === 1 && - graph.entity(entityId).type === 'way'; + context.entity(entityId).type === 'way'; }; operation.enabled = function() { diff --git a/js/id/operations/reverse.js b/js/id/operations/reverse.js index 78b8789dd..77bacc64e 100644 --- a/js/id/operations/reverse.js +++ b/js/id/operations/reverse.js @@ -1,18 +1,15 @@ -iD.operations.Reverse = function(selection, mode) { - var entityId = selection[0], - history = mode.map.history(); +iD.operations.Reverse = function(selection, context) { + var entityId = selection[0]; var operation = function() { - history.perform( + context.perform( iD.actions.ReverseWay(entityId), t('operations.reverse.annotation')); }; operation.available = function() { - var graph = history.graph(), - entity = graph.entity(entityId); return selection.length === 1 && - entity.geometry(graph) === 'line'; + context.geometry(entityId) === 'line'; }; operation.enabled = function() { diff --git a/js/id/operations/split.js b/js/id/operations/split.js index 4274fc41b..0d7ff6ae4 100644 --- a/js/id/operations/split.js +++ b/js/id/operations/split.js @@ -1,22 +1,18 @@ -iD.operations.Split = function(selection, mode) { +iD.operations.Split = function(selection, context) { var entityId = selection[0], - history = mode.map.history(), action = iD.actions.SplitWay(entityId); var operation = function() { - history.perform(action, t('operations.split.annotation')); + context.perform(action, t('operations.split.annotation')); }; operation.available = function() { - var graph = history.graph(), - entity = graph.entity(entityId); return selection.length === 1 && - entity.geometry(graph) === 'vertex'; + context.geometry(entityId) === 'vertex'; }; operation.enabled = function() { - var graph = history.graph(); - return action.enabled(graph); + return action.enabled(context.graph()); }; operation.id = "split"; diff --git a/js/id/operations/unjoin.js b/js/id/operations/unjoin.js index 2a40ec067..d4fcc1426 100644 --- a/js/id/operations/unjoin.js +++ b/js/id/operations/unjoin.js @@ -1,22 +1,18 @@ -iD.operations.Unjoin = function(selection, mode) { +iD.operations.Unjoin = function(selection, context) { var entityId = selection[0], - history = mode.map.history(), action = iD.actions.UnjoinNode(entityId); var operation = function() { - history.perform(action, 'Unjoined lines.'); + context.perform(action, 'Unjoined lines.'); }; operation.available = function() { - var graph = history.graph(), - entity = graph.entity(entityId); return selection.length === 1 && - entity.geometry(graph) === 'vertex'; + context.geometry(entityId) === 'vertex'; }; operation.enabled = function() { - var graph = history.graph(); - return action.enabled(graph); + return action.enabled(context.graph()); }; operation.id = "unjoin"; diff --git a/js/id/ui/save.js b/js/id/ui/save.js index b8085e21a..6098519da 100644 --- a/js/id/ui/save.js +++ b/js/id/ui/save.js @@ -1,11 +1,8 @@ -iD.ui.save = function() { - - var map, controller; - - function save(selection) { - - var history = map.history(), - connection = map.connection(), +iD.ui.save = function(context) { + return function (selection) { + var map = context.map(), + history = context.history(), + connection = context.connection(), tooltip = bootstrap.tooltip() .placement('bottom'); @@ -60,7 +57,7 @@ iD.ui.save = function() { .on('fix', function(d) { map.extent(d.entity.extent(map.history().graph())); if (map.zoom() > 19) map.zoom(19); - controller.enter(iD.modes.Select([d.entity.id])); + context.enter(iD.modes.Select(context, [d.entity.id])); modal.remove(); }) .on('save', commit)); @@ -88,19 +85,5 @@ iD.ui.save = function() { selection.call(tooltip.hide); } }); - } - - save.map = function(_) { - if (!arguments.length) return map; - map = _; - return save; }; - - save.controller = function(_) { - if (!arguments.length) return controller; - controller = _; - return save; - }; - - return save; }; diff --git a/test/index.html b/test/index.html index b85eb1b56..b56645ee7 100644 --- a/test/index.html +++ b/test/index.html @@ -119,6 +119,7 @@ + @@ -150,9 +151,6 @@ - - - @@ -162,8 +160,6 @@ - - @@ -189,6 +185,11 @@ + + + + + diff --git a/test/index_packaged.html b/test/index_packaged.html index 60979e236..11f8368f4 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -44,9 +44,6 @@ - - - @@ -56,8 +53,6 @@ - - @@ -83,6 +78,11 @@ + + + + + diff --git a/test/spec/behavior/hash.js b/test/spec/behavior/hash.js index 7616bf536..5aceb53fc 100644 --- a/test/spec/behavior/hash.js +++ b/test/spec/behavior/hash.js @@ -1,19 +1,18 @@ describe("iD.behavior.Hash", function () { - var hash, map, controller; + mocha.globals('__onhashchange.hash'); + + var hash, context; beforeEach(function () { - map = { - on: function () { return map; }, - zoom: function () { return arguments.length ? map : 0; }, - center: function () { return arguments.length ? map : [0, 0]; }, - centerZoom: function () { return arguments.length ? map : [0, 0]; } - }; + context = iD.Context(); - controller = { - on: function () { return controller; } - }; + // Neuter connection + context.connection().loadTiles = function () {}; - hash = iD.behavior.Hash(controller, map); + hash = iD.behavior.Hash(context); + + d3.select(document.createElement('div')) + .call(context.map()); }); afterEach(function () { @@ -22,44 +21,41 @@ describe("iD.behavior.Hash", function () { it("sets hadHash if location.hash is present", function () { location.hash = "map=20.00/38.87952/-77.02405"; + hash(); + expect(hash.hadHash).to.be.true; }); it("centerZooms map to requested level", function () { location.hash = "map=20.00/38.87952/-77.02405"; - sinon.spy(map, 'centerZoom'); + hash(); - expect(map.centerZoom).to.have.been.calledWith([-77.02405,38.87952], 20.0); + + expect(context.map().center()[0]).to.be.closeTo(-77.02405, 0.1); + expect(context.map().center()[1]).to.be.closeTo(38.87952, 0.1); + expect(context.map().zoom()).to.equal(20.0); }); - describe("on window hashchange events", function () { - beforeEach(function () { - hash(); + it("centerZooms map at requested coordinates on hash change", function (done) { + hash(); + + d3.select(window).one('hashchange', function () { + expect(context.map().center()[0]).to.be.closeTo(-77.02405, 0.1); + expect(context.map().center()[1]).to.be.closeTo(38.87952, 0.1); + expect(context.map().zoom()).to.equal(20.0); + done(); }); - function onhashchange(fn) { - d3.select(window).one("hashchange", fn); - } - - it("centerZooms map at requested coordinates", function (done) { - onhashchange(function () { - expect(map.centerZoom).to.have.been.calledWith([-77.02405,38.87952], 20.0); - done(); - }); - - sinon.spy(map, 'centerZoom'); - location.hash = "#map=20.00/38.87952/-77.02405"; - }); + location.hash = "#map=20.00/38.87952/-77.02405"; }); - describe("on map move events", function () { - it("stores the current zoom and coordinates in location.hash", function () { - sinon.stub(map, 'on') - .withArgs("move.hash", sinon.match.instanceOf(Function)) - .yields(); - hash(); - expect(location.hash).to.equal("#map=0.00/0/0"); - }); + it("stores the current zoom and coordinates in location.hash on map move events", function () { + hash(); + + context.map().center([38.9, -77.0]); + context.map().zoom(2.0); + + expect(location.hash).to.equal("#map=2.00/-77.0/38.9"); }); }); diff --git a/test/spec/modes/add_point.js b/test/spec/modes/add_point.js index a284cf682..5c544c0c5 100644 --- a/test/spec/modes/add_point.js +++ b/test/spec/modes/add_point.js @@ -1,41 +1,36 @@ describe("iD.modes.AddPoint", function () { - var container, map, history, controller, mode; + var context; beforeEach(function () { - container = d3.select('body').append('div'); - history = iD.History(); - map = iD.Map().history(history); - controller = iD.Controller(map, history); + var container = d3.select(document.createElement('div')); - container.call(map); - container.append('div') + context = iD.Context() + .container(container); + + container.call(context.map()) + .append('div') .attr('class', 'inspector-wrap'); - mode = iD.modes.AddPoint(); - controller.enter(mode); - }); - - afterEach(function() { - container.remove(); + context.enter(iD.modes.AddPoint(context)); }); describe("clicking the map", function () { it("adds a node", function () { - happen.click(map.surface.node(), {}); - expect(history.changes().created).to.have.length(1); + happen.click(context.surface().node(), {}); + expect(context.changes().created).to.have.length(1); }); it("selects the node", function () { - happen.click(map.surface.node(), {}); - expect(controller.mode.id).to.equal('select'); - expect(controller.mode.selection()).to.eql([history.changes().created[0].id]); + happen.click(context.surface().node(), {}); + expect(context.mode().id).to.equal('select'); + expect(context.mode().selection()).to.eql([context.changes().created[0].id]); }); }); describe("pressing ⎋", function () { it("exits to browse mode", function () { happen.keydown(document, {keyCode: 27}); - expect(controller.mode.id).to.equal('browse'); + expect(context.mode().id).to.equal('browse'); }); }); }); diff --git a/test/spec/ui/confirm.js b/test/spec/ui/confirm.js index a9380e1cc..428a8aa6c 100644 --- a/test/spec/ui/confirm.js +++ b/test/spec/ui/confirm.js @@ -2,10 +2,13 @@ describe("iD.ui.confirm", function () { it('can be instantiated', function () { var confirm = iD.ui.confirm(); expect(confirm).to.be.ok; + happen.keydown(document, {keyCode: 27}); // dismiss }); + it('can be dismissed', function () { var confirm = iD.ui.confirm(); happen.click(confirm.select('button').node()); expect(confirm.node().parentNode).to.be.null; + happen.keydown(document, {keyCode: 27}); // dismiss }); }); diff --git a/test/spec/ui/modal.js b/test/spec/ui/modal.js index 32a42878a..d7f818d80 100644 --- a/test/spec/ui/modal.js +++ b/test/spec/ui/modal.js @@ -4,5 +4,6 @@ describe("iD.ui.modal", function () { .select('.content') .text('foo'); expect(modal).to.be.ok; + happen.keydown(document, {keyCode: 27}); // dismiss }); }); From 579d6325636938443f34837fc05dd856db65539f Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 31 Jan 2013 17:19:01 -0500 Subject: [PATCH 085/415] Hoist functions up a scope --- js/id/modes/add_area.js | 120 +++++++++++++++++------------------ js/id/modes/add_line.js | 134 +++++++++++++++++++-------------------- js/id/modes/add_point.js | 62 +++++++++--------- 3 files changed, 155 insertions(+), 161 deletions(-) diff --git a/js/id/modes/add_area.js b/js/id/modes/add_area.js index ae2e008ee..0a9496bb6 100644 --- a/js/id/modes/add_area.js +++ b/js/id/modes/add_area.js @@ -7,71 +7,69 @@ iD.modes.AddArea = function(context) { key: t('modes.add_area.key') }; - var behavior, - defaultTags = {area: 'yes'}; - - mode.enter = function() { - function start(loc) { - var graph = context.graph(), - node = iD.Node({loc: loc}), - way = iD.Way({tags: defaultTags}); - - context.perform( - iD.actions.AddEntity(node), - iD.actions.AddEntity(way), - iD.actions.AddVertex(way.id, node.id), - iD.actions.AddVertex(way.id, node.id)); - - context.enter(iD.modes.DrawArea(context, way.id, graph)); - } - - function startFromWay(other, loc, index) { - var graph = context.graph(), - node = iD.Node({loc: loc}), - way = iD.Way({tags: defaultTags}); - - context.perform( - iD.actions.AddEntity(node), - iD.actions.AddEntity(way), - iD.actions.AddVertex(way.id, node.id), - iD.actions.AddVertex(way.id, node.id), - iD.actions.AddVertex(other.id, node.id, index)); - - context.enter(iD.modes.DrawArea(context, way.id, graph)); - } - - function startFromNode(node) { - var graph = context.graph(), - way = iD.Way({tags: defaultTags}); - - context.perform( - iD.actions.AddEntity(way), - iD.actions.AddVertex(way.id, node.id), - iD.actions.AddVertex(way.id, node.id)); - - context.enter(iD.modes.DrawArea(context, way.id, graph)); - } - - function startFromMidpoint(midpoint) { - var graph = context.graph(), - node = iD.Node(), - way = iD.Way({tags: defaultTags}); - - context.perform( - iD.actions.AddMidpoint(midpoint, node), - iD.actions.AddEntity(way), - iD.actions.AddVertex(way.id, node.id), - iD.actions.AddVertex(way.id, node.id)); - - context.enter(iD.modes.DrawArea(context, way.id, graph)); - } - - behavior = iD.behavior.AddWay(context) + var behavior = iD.behavior.AddWay(context) .on('start', start) .on('startFromWay', startFromWay) .on('startFromNode', startFromNode) - .on('startFromMidpoint', startFromMidpoint); + .on('startFromMidpoint', startFromMidpoint), + defaultTags = {area: 'yes'}; + function start(loc) { + var graph = context.graph(), + node = iD.Node({loc: loc}), + way = iD.Way({tags: defaultTags}); + + context.perform( + iD.actions.AddEntity(node), + iD.actions.AddEntity(way), + iD.actions.AddVertex(way.id, node.id), + iD.actions.AddVertex(way.id, node.id)); + + context.enter(iD.modes.DrawArea(context, way.id, graph)); + } + + function startFromWay(other, loc, index) { + var graph = context.graph(), + node = iD.Node({loc: loc}), + way = iD.Way({tags: defaultTags}); + + context.perform( + iD.actions.AddEntity(node), + iD.actions.AddEntity(way), + iD.actions.AddVertex(way.id, node.id), + iD.actions.AddVertex(way.id, node.id), + iD.actions.AddVertex(other.id, node.id, index)); + + context.enter(iD.modes.DrawArea(context, way.id, graph)); + } + + function startFromNode(node) { + var graph = context.graph(), + way = iD.Way({tags: defaultTags}); + + context.perform( + iD.actions.AddEntity(way), + iD.actions.AddVertex(way.id, node.id), + iD.actions.AddVertex(way.id, node.id)); + + context.enter(iD.modes.DrawArea(context, way.id, graph)); + } + + function startFromMidpoint(midpoint) { + var graph = context.graph(), + node = iD.Node(), + way = iD.Way({tags: defaultTags}); + + context.perform( + iD.actions.AddMidpoint(midpoint, node), + iD.actions.AddEntity(way), + iD.actions.AddVertex(way.id, node.id), + iD.actions.AddVertex(way.id, node.id)); + + context.enter(iD.modes.DrawArea(context, way.id, graph)); + } + + mode.enter = function() { context.install(behavior); context.tail(t('modes.add_area.tail')); }; diff --git a/js/id/modes/add_line.js b/js/id/modes/add_line.js index 79c5f3031..7845e6e6f 100644 --- a/js/id/modes/add_line.js +++ b/js/id/modes/add_line.js @@ -7,78 +7,76 @@ iD.modes.AddLine = function(context) { key: t('modes.add_line.key') }; - var behavior, - defaultTags = {highway: 'residential'}; - - mode.enter = function() { - function start(loc) { - var graph = context.graph(), - node = iD.Node({loc: loc}), - way = iD.Way({tags: defaultTags}); - - context.perform( - iD.actions.AddEntity(node), - iD.actions.AddEntity(way), - iD.actions.AddVertex(way.id, node.id)); - - context.enter(iD.modes.DrawLine(context, way.id, 'forward', graph)); - } - - function startFromWay(other, loc, index) { - var graph = context.graph(), - node = iD.Node({loc: loc}), - way = iD.Way({tags: defaultTags}); - - context.perform( - iD.actions.AddEntity(node), - iD.actions.AddEntity(way), - iD.actions.AddVertex(way.id, node.id), - iD.actions.AddVertex(other.id, node.id, index)); - - context.enter(iD.modes.DrawLine(context, way.id, 'forward', graph)); - } - - function startFromNode(node) { - var graph = context.graph(), - parent = graph.parentWays(node)[0], - isLine = parent && parent.geometry(graph) === 'line'; - - if (isLine && parent.first() === node.id) { - context.enter(iD.modes.DrawLine(context, parent.id, 'backward', graph)); - - } else if (isLine && parent.last() === node.id) { - context.enter(iD.modes.DrawLine(context, parent.id, 'forward', graph)); - - } else { - var way = iD.Way({tags: defaultTags}); - - context.perform( - iD.actions.AddEntity(way), - iD.actions.AddVertex(way.id, node.id)); - - context.enter(iD.modes.DrawLine(context, way.id, 'forward', graph)); - } - } - - function startFromMidpoint(midpoint) { - var graph = context.graph(), - node = iD.Node(), - way = iD.Way({tags: defaultTags}); - - context.perform( - iD.actions.AddMidpoint(midpoint, node), - iD.actions.AddEntity(way), - iD.actions.AddVertex(way.id, node.id)); - - context.enter(iD.modes.DrawLine(context, way.id, 'forward', graph)); - } - - behavior = iD.behavior.AddWay(context) + var behavior = iD.behavior.AddWay(context) .on('start', start) .on('startFromWay', startFromWay) .on('startFromNode', startFromNode) - .on('startFromMidpoint', startFromMidpoint); + .on('startFromMidpoint', startFromMidpoint), + defaultTags = {highway: 'residential'}; + function start(loc) { + var graph = context.graph(), + node = iD.Node({loc: loc}), + way = iD.Way({tags: defaultTags}); + + context.perform( + iD.actions.AddEntity(node), + iD.actions.AddEntity(way), + iD.actions.AddVertex(way.id, node.id)); + + context.enter(iD.modes.DrawLine(context, way.id, 'forward', graph)); + } + + function startFromWay(other, loc, index) { + var graph = context.graph(), + node = iD.Node({loc: loc}), + way = iD.Way({tags: defaultTags}); + + context.perform( + iD.actions.AddEntity(node), + iD.actions.AddEntity(way), + iD.actions.AddVertex(way.id, node.id), + iD.actions.AddVertex(other.id, node.id, index)); + + context.enter(iD.modes.DrawLine(context, way.id, 'forward', graph)); + } + + function startFromNode(node) { + var graph = context.graph(), + parent = graph.parentWays(node)[0], + isLine = parent && parent.geometry(graph) === 'line'; + + if (isLine && parent.first() === node.id) { + context.enter(iD.modes.DrawLine(context, parent.id, 'backward', graph)); + + } else if (isLine && parent.last() === node.id) { + context.enter(iD.modes.DrawLine(context, parent.id, 'forward', graph)); + + } else { + var way = iD.Way({tags: defaultTags}); + + context.perform( + iD.actions.AddEntity(way), + iD.actions.AddVertex(way.id, node.id)); + + context.enter(iD.modes.DrawLine(context, way.id, 'forward', graph)); + } + } + + function startFromMidpoint(midpoint) { + var graph = context.graph(), + node = iD.Node(), + way = iD.Way({tags: defaultTags}); + + context.perform( + iD.actions.AddMidpoint(midpoint, node), + iD.actions.AddEntity(way), + iD.actions.AddVertex(way.id, node.id)); + + context.enter(iD.modes.DrawLine(context, way.id, 'forward', graph)); + } + + mode.enter = function() { context.install(behavior); context.tail(t('modes.add_line.tail')); }; diff --git a/js/id/modes/add_point.js b/js/id/modes/add_point.js index c3dc7f075..5a24dad25 100644 --- a/js/id/modes/add_point.js +++ b/js/id/modes/add_point.js @@ -6,46 +6,44 @@ iD.modes.AddPoint = function(context) { key: t('modes.add_point.key') }; - var behavior; + var behavior = iD.behavior.Draw(context) + .on('click', add) + .on('clickWay', addWay) + .on('clickNode', addNode) + .on('clickMidpoint', addNode) + .on('cancel', cancel) + .on('finish', cancel); + + function add(loc) { + var node = iD.Node({loc: loc}); + + context.perform( + iD.actions.AddEntity(node), + t('operations.add.annotation.point')); + + context.enter(iD.modes.Select(context, [node.id], true)); + } + + function addWay(way, loc, index) { + add(loc); + } + + function addNode(node) { + add(node.loc); + } + + function cancel() { + context.enter(iD.modes.Browse(context)); + } mode.enter = function() { - function add(loc) { - var node = iD.Node({loc: loc}); - - context.perform( - iD.actions.AddEntity(node), - t('operations.add.annotation.point')); - - context.enter(iD.modes.Select(context, [node.id], true)); - } - - function addWay(way, loc, index) { - add(loc); - } - - function addNode(node) { - add(node.loc); - } - - function cancel() { - context.enter(iD.modes.Browse(context)); - } - - behavior = iD.behavior.Draw(context) - .on('click', add) - .on('clickWay', addWay) - .on('clickNode', addNode) - .on('clickMidpoint', addNode) - .on('cancel', cancel) - .on('finish', cancel); - context.install(behavior); context.tail(t('modes.add_point.tail')); }; mode.exit = function() { - context.tail(false); context.uninstall(behavior); + context.tail(false); }; return mode; From 000ceb6467d7c79e22522a2575b413bd8d09b8f3 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Feb 2013 11:36:53 -0500 Subject: [PATCH 086/415] iD.Context -> iD All the UI setup code moved to iD.ui. --- Makefile | 1 - index.html | 9 +- index_packaged.html | 14 +- js/id/context.js | 44 ----- js/id/id.js | 323 +++++------------------------------ js/id/ui.js | 257 +++++++++++++++++++++++++++- test/index.html | 1 - test/spec/behavior/hash.js | 2 +- test/spec/modes/add_point.js | 2 +- 9 files changed, 319 insertions(+), 334 deletions(-) delete mode 100644 js/id/context.js diff --git a/Makefile b/Makefile index 598bce278..1db4f2efb 100644 --- a/Makefile +++ b/Makefile @@ -27,7 +27,6 @@ all: \ js/lib/sha.js \ js/id/start.js \ js/id/id.js \ - js/id/context.js \ js/id/connection.js \ js/id/oauth.js \ js/id/services/*.js \ diff --git a/index.html b/index.html index 306575804..ab6070fbc 100644 --- a/index.html +++ b/index.html @@ -123,7 +123,6 @@ - @@ -135,9 +134,13 @@ locale.current = 'en'; d3.json('keys.json', function(err, keys) { var id = iD(); - id.connection().keys(keys) + + id.connection() + .keys(keys) .url('http://api06.dev.openstreetmap.org'); - d3.select("#iD").call(id); + + d3.select("#iD") + .call(id.ui()) }); diff --git a/index_packaged.html b/index_packaged.html index c48a21873..a961a5202 100644 --- a/index_packaged.html +++ b/index_packaged.html @@ -16,8 +16,16 @@
diff --git a/js/id/context.js b/js/id/context.js deleted file mode 100644 index 60f878b32..000000000 --- a/js/id/context.js +++ /dev/null @@ -1,44 +0,0 @@ -iD.Context = function() { - var history = iD.History(), - connection = iD.Connection(), - controller = iD.Controller(), - container, - map = iD.Map().connection(connection).history(history); - - var context = {}; - - context.container = function (_) { - if (!arguments.length) return container; - container = _; - return context; - }; - - context.connection = function () { return connection; }; - - context.history = function () { return history; }; - context.graph = history.graph; - context.perform = history.perform; - context.replace = history.replace; - context.pop = history.pop; - context.undo = history.undo; - context.redo = history.undo; - context.changes = history.changes; - - context.entity = function (id) { return history.graph().entity(id); }; - context.geometry = function (id) { return context.entity(id).geometry(history.graph()); }; - - context.controller = function () { return controller; }; - context.enter = controller.enter; - context.mode = function () { return controller.mode; }; - - context.install = function (behavior) { context.surface().call(behavior); }; - context.uninstall = function (behavior) { context.surface().call(behavior.off); }; - - context.map = function () { return map; }; - context.background = function () { return map.background; }; - context.surface = function () { return map.surface; }; - context.projection = map.projection; - context.tail = map.tail; - - return context; -}; diff --git a/js/id/id.js b/js/id/id.js index 3c2d0c482..42a8cfb11 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -1,291 +1,56 @@ -window.iD = function(container) { - // the reported, displayed version of iD. - var version = '0.0.0-alpha1'; +window.iD = function () { + var context = {}, + history = iD.History(), + connection = iD.Connection().version(iD.version), + controller = iD.Controller(), + container, + ui = iD.ui(context), + map = iD.Map().connection(connection).history(history); - var context = iD.Context(); + /* Straight accessors. Avoid using these if you can. */ + context.ui = function () { return ui; }; + context.connection = function () { return connection; }; + context.history = function () { return history; }; + context.controller = function () { return controller; }; + context.map = function () { return map; }; - var connection = context.connection(), - history = context.history(), - map = context.map(), - controller = context.controller(); + /* History delegation. */ + context.graph = history.graph; + context.perform = history.perform; + context.replace = history.replace; + context.pop = history.pop; + context.undo = history.undo; + context.redo = history.undo; + context.changes = history.changes; - context.connection() - .version(version); + context.entity = function (id) { return history.graph().entity(id); }; + context.geometry = function (id) { return context.entity(id).geometry(history.graph()); }; + + context.enter = controller.enter; + context.mode = function () { return controller.mode; }; + + context.install = function (behavior) { context.surface().call(behavior); }; + context.uninstall = function (behavior) { context.surface().call(behavior.off); }; + + context.background = function () { return map.background; }; + context.surface = function () { return map.surface; }; + context.projection = map.projection; + context.tail = map.tail; + + context.container = function (_) { + if (!arguments.length) return container; + container = _; + return context; + }; context.background() .source(iD.BackgroundSource.Bing); - function editor(container) { - context.container(container); - - if (!iD.supported()) { - container.html('This editor is supported in Firefox, Chrome, Safari, Opera, ' + - 'and Internet Explorer 9 and above. Please upgrade your browser ' + - 'or use Potlatch 2 to edit the map.') - .style('text-align:center;font-style:italic;'); - return; - } - - function hintprefix(x, y) { - return '' + y + '' + '
' + x + '
'; - } - - var m = container.append('div') - .attr('id', 'map') - .call(map); - - var bar = container.append('div') - .attr('id', 'bar') - .attr('class','pad1 fillD'); - - var limiter = bar.append('div') - .attr('class', 'limiter'); - - var buttons_joined = limiter.append('div') - .attr('class', 'button-wrap joined col4'); - - var modes = [ - iD.modes.Browse(context), - iD.modes.AddPoint(context), - iD.modes.AddLine(context), - iD.modes.AddArea(context)]; - - var buttons = buttons_joined.selectAll('button.add-button') - .data(modes) - .enter().append('button') - .attr('tabindex', -1) - .attr('class', function (mode) { return mode.title + ' add-button col3'; }) - .call(bootstrap.tooltip().placement('bottom').html(true)) - .attr('data-original-title', function (mode) { - return hintprefix(mode.key, mode.description); - }) - .on('click.editor', function (mode) { controller.enter(mode); }); - - function disableTooHigh() { - if (map.editable()) { - notice.message(false); - buttons.attr('disabled', null); - } else { - buttons.attr('disabled', 'disabled'); - notice.message(true); - controller.enter(iD.modes.Browse(context)); - } - } - - var notice = iD.ui.notice(limiter) - .message(false) - .on('zoom', function() { map.zoom(16); }); - - map.on('move.editor', _.debounce(function() { - disableTooHigh(); - contributors.call(iD.ui.contributors(map)); - }, 500)); - - buttons.append('span') - .attr('class', function(d) { - return d.id + ' icon icon-pre-text'; - }); - - buttons.append('span').attr('class', 'label').text(function (mode) { return mode.title; }); - - controller.on('enter.editor', function (entered) { - buttons.classed('active', function (mode) { return entered.button === mode.button; }); - container.classed("mode-" + entered.id, true); - }); - - controller.on('exit.editor', function (exited) { - container.classed("mode-" + exited.id, false); - }); - - var undo_buttons = limiter.append('div') - .attr('class', 'button-wrap joined col1'), - undo_tooltip = bootstrap.tooltip().placement('bottom').html(true); - - undo_buttons.append('button') - .attr({ id: 'undo', 'class': 'col6' }) - .property('disabled', true) - .html("") - .on('click.editor', history.undo) - .call(undo_tooltip); - - undo_buttons.append('button') - .attr({ id: 'redo', 'class': 'col6' }) - .property('disabled', true) - .html("") - .on('click.editor', history.redo) - .call(undo_tooltip); - - var save_button = limiter.append('div').attr('class','button-wrap col1').append('button') - .attr('class', 'save col12') - .call(iD.ui.save(context)); - - var zoom = container.append('div') - .attr('class', 'zoombuttons map-control') - .selectAll('button') - .data([['zoom-in', '+', map.zoomIn, 'Zoom In'], ['zoom-out', '-', map.zoomOut, 'Zoom Out']]) - .enter() - .append('button') - .attr('tabindex', -1) - .attr('class', function(d) { return d[0]; }) - .attr('title', function(d) { return d[3]; }) - .on('click.editor', function(d) { return d[2](); }) - .append('span') - .attr('class', function(d) { - return d[0] + ' icon'; - }); - - if (navigator.geolocation) { - container.append('div') - .call(iD.ui.geolocate(map)); - } - - var gc = container.append('div').attr('class', 'geocode-control map-control') - .call(iD.ui.geocoder().map(map)); - - container.append('div').attr('class', 'map-control layerswitcher-control') - .call(iD.ui.layerswitcher(map)); - - container.append('div') - .style('display', 'none') - .attr('class', 'inspector-wrap fr col5'); - - var about = container.append('div') - .attr('class','col12 about-block fillD pad1'); - - about.append('div') - .attr('class', 'user-container') - .append('div') - .attr('class', 'hello'); - - var aboutList = about.append('ul') - .attr('id','about') - .attr('class','link-list'); - - var linkList = aboutList.append('ul') - .attr('id','about') - .attr('class','pad1 fillD about-block link-list'); - linkList.append('li').append('a').attr('target', '_blank') - .attr('href', 'http://github.com/systemed/iD').text(version); - linkList.append('li').append('a').attr('target', '_blank') - .attr('href', 'http://github.com/systemed/iD/issues').text('report a bug'); - - var imagery = linkList.append('li').attr('id', 'attribution'); - imagery.append('span').text('imagery'); - imagery.append('a').attr('target', '_blank') - .attr('href', 'http://opengeodata.org/microsoft-imagery-details').text(' provided by bing'); - - linkList.append('li').attr('class', 'source-switch').append('a').attr('href', '#') - .text('dev') - .on('click.editor', function() { - d3.event.preventDefault(); - if (d3.select(this).classed('live')) { - map.flush().connection() - .url('http://api06.dev.openstreetmap.org'); - d3.select(this).text('dev').classed('live', false); - } else { - map.flush().connection() - .url('http://www.openstreetmap.org'); - d3.select(this).text('live').classed('live', true); - } - }); - - var contributors = linkList.append('li') - .attr('id', 'user-list'); - contributors.append('span') - .attr('class', 'icon nearby icon-pre-text'); - contributors.append('span') - .text('Viewing contributions by '); - contributors.append('span') - .attr('class', 'contributor-list'); - contributors.append('span') - .attr('class', 'contributor-count'); - - history.on('change.editor', function() { - window.onbeforeunload = history.hasChanges() ? function() { - return 'You have unsaved changes.'; - } : null; - - var undo = history.undoAnnotation(), - redo = history.redoAnnotation(); - - function refreshTooltip(selection) { - if (selection.property('disabled')) { - selection.call(undo_tooltip.hide); - } else if (selection.property('tooltipVisible')) { - selection.call(undo_tooltip.show); - } - } - - limiter.select('#undo') - .property('disabled', !undo) - .attr('data-original-title', hintprefix('⌘ + Z', undo)) - .call(refreshTooltip); - - limiter.select('#redo') - .property('disabled', !redo) - .attr('data-original-title', hintprefix('⌘ + ⇧ + Z', redo)) - .call(refreshTooltip); - }); - - d3.select(window).on('resize.editor', function() { - map.size(m.size()); - }); - - var keybinding = d3.keybinding('main') - .on('⌘+Z', function() { history.undo(); }) - .on('⌃+Z', function() { history.undo(); }) - .on('⌘+⇧+Z', function() { history.redo(); }) - .on('⌃+⇧+Z', function() { history.redo(); }) - .on('⌫', function() { d3.event.preventDefault(); }); - - modes.forEach(function(m) { - keybinding.on(m.key, function() { if (map.editable()) controller.enter(m); }); - }); - - d3.select(document) - .call(keybinding); - - var hash = iD.behavior.Hash(context); - - hash(); - - if (!hash.hadHash) { - map.centerZoom([-77.02271, 38.90085], 20); - } - - d3.select('.user-container').call(iD.ui.userpanel(connection) - .on('logout.editor', connection.logout) - .on('login.editor', connection.authenticate)); - - controller.enter(iD.modes.Browse(context)); - - if (!localStorage.sawSplash) { - iD.ui.splash(); - localStorage.sawSplash = true; - } - } - - editor.connection = function(_) { - if (!arguments.length) return connection; - connection = _; - return editor; - }; - - editor.map = function() { - return map; - }; - - editor.controller = function() { - return controller; - }; - - if (arguments.length) { - d3.select(container).call(editor); - } - - return editor; + return context; }; +iD.version = '0.0.0-alpha1'; + iD.supported = function() { if (navigator.appName !== 'Microsoft Internet Explorer') { return true; diff --git a/js/id/ui.js b/js/id/ui.js index 8901175f4..978d9175e 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -1 +1,256 @@ -iD.ui = {}; +iD.ui = function (context) { + return function(container) { + context.container(container); + + var connection = context.connection(), + history = context.history(), + map = context.map(), + controller = context.controller(); + + if (!iD.supported()) { + container.html('This editor is supported in Firefox, Chrome, Safari, Opera, ' + + 'and Internet Explorer 9 and above. Please upgrade your browser ' + + 'or use Potlatch 2 to edit the map.') + .style('text-align:center;font-style:italic;'); + return; + } + + function hintprefix(x, y) { + return '' + y + '' + '
' + x + '
'; + } + + var m = container.append('div') + .attr('id', 'map') + .call(map); + + var bar = container.append('div') + .attr('id', 'bar') + .attr('class','pad1 fillD'); + + var limiter = bar.append('div') + .attr('class', 'limiter'); + + var buttons_joined = limiter.append('div') + .attr('class', 'button-wrap joined col4'); + + var modes = [ + iD.modes.Browse(context), + iD.modes.AddPoint(context), + iD.modes.AddLine(context), + iD.modes.AddArea(context)]; + + var buttons = buttons_joined.selectAll('button.add-button') + .data(modes) + .enter().append('button') + .attr('tabindex', -1) + .attr('class', function (mode) { return mode.title + ' add-button col3'; }) + .call(bootstrap.tooltip().placement('bottom').html(true)) + .attr('data-original-title', function (mode) { + return hintprefix(mode.key, mode.description); + }) + .on('click.editor', function (mode) { controller.enter(mode); }); + + function disableTooHigh() { + if (map.editable()) { + notice.message(false); + buttons.attr('disabled', null); + } else { + buttons.attr('disabled', 'disabled'); + notice.message(true); + controller.enter(iD.modes.Browse(context)); + } + } + + var notice = iD.ui.notice(limiter) + .message(false) + .on('zoom', function() { map.zoom(16); }); + + map.on('move.editor', _.debounce(function() { + disableTooHigh(); + contributors.call(iD.ui.contributors(map)); + }, 500)); + + buttons.append('span') + .attr('class', function(d) { + return d.id + ' icon icon-pre-text'; + }); + + buttons.append('span').attr('class', 'label').text(function (mode) { return mode.title; }); + + controller.on('enter.editor', function (entered) { + buttons.classed('active', function (mode) { return entered.button === mode.button; }); + container.classed("mode-" + entered.id, true); + }); + + controller.on('exit.editor', function (exited) { + container.classed("mode-" + exited.id, false); + }); + + var undo_buttons = limiter.append('div') + .attr('class', 'button-wrap joined col1'), + undo_tooltip = bootstrap.tooltip().placement('bottom').html(true); + + undo_buttons.append('button') + .attr({ id: 'undo', 'class': 'col6' }) + .property('disabled', true) + .html("") + .on('click.editor', history.undo) + .call(undo_tooltip); + + undo_buttons.append('button') + .attr({ id: 'redo', 'class': 'col6' }) + .property('disabled', true) + .html("") + .on('click.editor', history.redo) + .call(undo_tooltip); + + var save_button = limiter.append('div').attr('class','button-wrap col1').append('button') + .attr('class', 'save col12') + .call(iD.ui.save(context)); + + var zoom = container.append('div') + .attr('class', 'zoombuttons map-control') + .selectAll('button') + .data([['zoom-in', '+', map.zoomIn, 'Zoom In'], ['zoom-out', '-', map.zoomOut, 'Zoom Out']]) + .enter() + .append('button') + .attr('tabindex', -1) + .attr('class', function(d) { return d[0]; }) + .attr('title', function(d) { return d[3]; }) + .on('click.editor', function(d) { return d[2](); }) + .append('span') + .attr('class', function(d) { + return d[0] + ' icon'; + }); + + if (navigator.geolocation) { + container.append('div') + .call(iD.ui.geolocate(map)); + } + + var gc = container.append('div').attr('class', 'geocode-control map-control') + .call(iD.ui.geocoder().map(map)); + + container.append('div').attr('class', 'map-control layerswitcher-control') + .call(iD.ui.layerswitcher(map)); + + container.append('div') + .style('display', 'none') + .attr('class', 'inspector-wrap fr col5'); + + var about = container.append('div') + .attr('class','col12 about-block fillD pad1'); + + about.append('div') + .attr('class', 'user-container') + .append('div') + .attr('class', 'hello'); + + var aboutList = about.append('ul') + .attr('id','about') + .attr('class','link-list'); + + var linkList = aboutList.append('ul') + .attr('id','about') + .attr('class','pad1 fillD about-block link-list'); + linkList.append('li').append('a').attr('target', '_blank') + .attr('href', 'http://github.com/systemed/iD').text(iD.version); + linkList.append('li').append('a').attr('target', '_blank') + .attr('href', 'http://github.com/systemed/iD/issues').text('report a bug'); + + var imagery = linkList.append('li').attr('id', 'attribution'); + imagery.append('span').text('imagery'); + imagery.append('a').attr('target', '_blank') + .attr('href', 'http://opengeodata.org/microsoft-imagery-details').text(' provided by bing'); + + linkList.append('li').attr('class', 'source-switch').append('a').attr('href', '#') + .text('dev') + .on('click.editor', function() { + d3.event.preventDefault(); + if (d3.select(this).classed('live')) { + map.flush().connection() + .url('http://api06.dev.openstreetmap.org'); + d3.select(this).text('dev').classed('live', false); + } else { + map.flush().connection() + .url('http://www.openstreetmap.org'); + d3.select(this).text('live').classed('live', true); + } + }); + + var contributors = linkList.append('li') + .attr('id', 'user-list'); + contributors.append('span') + .attr('class', 'icon nearby icon-pre-text'); + contributors.append('span') + .text('Viewing contributions by '); + contributors.append('span') + .attr('class', 'contributor-list'); + contributors.append('span') + .attr('class', 'contributor-count'); + + history.on('change.editor', function() { + window.onbeforeunload = history.hasChanges() ? function() { + return 'You have unsaved changes.'; + } : null; + + var undo = history.undoAnnotation(), + redo = history.redoAnnotation(); + + function refreshTooltip(selection) { + if (selection.property('disabled')) { + selection.call(undo_tooltip.hide); + } else if (selection.property('tooltipVisible')) { + selection.call(undo_tooltip.show); + } + } + + limiter.select('#undo') + .property('disabled', !undo) + .attr('data-original-title', hintprefix('⌘ + Z', undo)) + .call(refreshTooltip); + + limiter.select('#redo') + .property('disabled', !redo) + .attr('data-original-title', hintprefix('⌘ + ⇧ + Z', redo)) + .call(refreshTooltip); + }); + + d3.select(window).on('resize.editor', function() { + map.size(m.size()); + }); + + var keybinding = d3.keybinding('main') + .on('⌘+Z', function() { history.undo(); }) + .on('⌃+Z', function() { history.undo(); }) + .on('⌘+⇧+Z', function() { history.redo(); }) + .on('⌃+⇧+Z', function() { history.redo(); }) + .on('⌫', function() { d3.event.preventDefault(); }); + + modes.forEach(function(m) { + keybinding.on(m.key, function() { if (map.editable()) controller.enter(m); }); + }); + + d3.select(document) + .call(keybinding); + + var hash = iD.behavior.Hash(context); + + hash(); + + if (!hash.hadHash) { + map.centerZoom([-77.02271, 38.90085], 20); + } + + d3.select('.user-container').call(iD.ui.userpanel(connection) + .on('logout.editor', connection.logout) + .on('login.editor', connection.authenticate)); + + controller.enter(iD.modes.Browse(context)); + + if (!localStorage.sawSplash) { + iD.ui.splash(); + localStorage.sawSplash = true; + } + }; +}; diff --git a/test/index.html b/test/index.html index b56645ee7..879cc6171 100644 --- a/test/index.html +++ b/test/index.html @@ -119,7 +119,6 @@ - diff --git a/test/spec/behavior/hash.js b/test/spec/behavior/hash.js index 5aceb53fc..7f9200ae1 100644 --- a/test/spec/behavior/hash.js +++ b/test/spec/behavior/hash.js @@ -4,7 +4,7 @@ describe("iD.behavior.Hash", function () { var hash, context; beforeEach(function () { - context = iD.Context(); + context = iD(); // Neuter connection context.connection().loadTiles = function () {}; diff --git a/test/spec/modes/add_point.js b/test/spec/modes/add_point.js index 5c544c0c5..38a614198 100644 --- a/test/spec/modes/add_point.js +++ b/test/spec/modes/add_point.js @@ -4,7 +4,7 @@ describe("iD.modes.AddPoint", function () { beforeEach(function () { var container = d3.select(document.createElement('div')); - context = iD.Context() + context = iD() .container(container); container.call(context.map()) From a78aeeb62591770ed2856389d07bceeb09b9f8b5 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Feb 2013 11:46:46 -0500 Subject: [PATCH 087/415] Merge controller into iD --- Makefile | 1 - index.html | 1 - js/id/behavior/hash.js | 2 +- js/id/controller.js | 19 ------------------- js/id/id.js | 22 +++++++++++++++++----- js/id/ui.js | 15 +++++++-------- test/index.html | 1 - 7 files changed, 25 insertions(+), 36 deletions(-) delete mode 100644 js/id/controller.js diff --git a/Makefile b/Makefile index 1db4f2efb..3b0f2d1b7 100644 --- a/Makefile +++ b/Makefile @@ -41,7 +41,6 @@ all: \ js/id/modes/*.js \ js/id/operations.js \ js/id/operations/*.js \ - js/id/controller.js \ js/id/graph/*.js \ js/id/renderer/*.js \ js/id/svg.js \ diff --git a/index.html b/index.html index ab6070fbc..52ff209f2 100644 --- a/index.html +++ b/index.html @@ -123,7 +123,6 @@ - diff --git a/js/id/behavior/hash.js b/js/id/behavior/hash.js index 871b73a35..9056db393 100644 --- a/js/id/behavior/hash.js +++ b/js/id/behavior/hash.js @@ -48,7 +48,7 @@ iD.behavior.Hash = function(context) { context.enter(iD.modes.Select([id])); }); - context.controller().on('enter.hash', function() { + context.on('enter.hash', function() { if (context.mode().id !== 'browse') selectoff(); }); } diff --git a/js/id/controller.js b/js/id/controller.js deleted file mode 100644 index af1e7c388..000000000 --- a/js/id/controller.js +++ /dev/null @@ -1,19 +0,0 @@ -// A controller holds a single action at a time and calls `.enter` and `.exit` -// to bind and unbind actions. -iD.Controller = function() { - var event = d3.dispatch('enter', 'exit'); - var controller = { mode: null }; - - controller.enter = function(mode) { - if (controller.mode) { - controller.mode.exit(); - event.exit(controller.mode); - } - - mode.enter(); - controller.mode = mode; - event.enter(mode); - }; - - return d3.rebind(controller, event, 'on'); -}; diff --git a/js/id/id.js b/js/id/id.js index 42a8cfb11..b2d28fb3f 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -2,7 +2,8 @@ window.iD = function () { var context = {}, history = iD.History(), connection = iD.Connection().version(iD.version), - controller = iD.Controller(), + dispatch = d3.dispatch('enter', 'exit'), + mode, container, ui = iD.ui(context), map = iD.Map().connection(connection).history(history); @@ -11,7 +12,6 @@ window.iD = function () { context.ui = function () { return ui; }; context.connection = function () { return connection; }; context.history = function () { return history; }; - context.controller = function () { return controller; }; context.map = function () { return map; }; /* History delegation. */ @@ -26,8 +26,20 @@ window.iD = function () { context.entity = function (id) { return history.graph().entity(id); }; context.geometry = function (id) { return context.entity(id).geometry(history.graph()); }; - context.enter = controller.enter; - context.mode = function () { return controller.mode; }; + context.enter = function(newMode) { + if (mode) { + mode.exit(); + dispatch.exit(mode); + } + + mode = newMode; + mode.enter(); + dispatch.enter(mode); + }; + + context.mode = function() { + return mode; + }; context.install = function (behavior) { context.surface().call(behavior); }; context.uninstall = function (behavior) { context.surface().call(behavior.off); }; @@ -46,7 +58,7 @@ window.iD = function () { context.background() .source(iD.BackgroundSource.Bing); - return context; + return d3.rebind(context, dispatch, 'on'); }; iD.version = '0.0.0-alpha1'; diff --git a/js/id/ui.js b/js/id/ui.js index 978d9175e..6b9b3cc08 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -4,8 +4,7 @@ iD.ui = function (context) { var connection = context.connection(), history = context.history(), - map = context.map(), - controller = context.controller(); + map = context.map(); if (!iD.supported()) { container.html('This editor is supported in Firefox, Chrome, Safari, Opera, ' + @@ -48,7 +47,7 @@ iD.ui = function (context) { .attr('data-original-title', function (mode) { return hintprefix(mode.key, mode.description); }) - .on('click.editor', function (mode) { controller.enter(mode); }); + .on('click.editor', function (mode) { context.enter(mode); }); function disableTooHigh() { if (map.editable()) { @@ -57,7 +56,7 @@ iD.ui = function (context) { } else { buttons.attr('disabled', 'disabled'); notice.message(true); - controller.enter(iD.modes.Browse(context)); + context.enter(iD.modes.Browse(context)); } } @@ -77,12 +76,12 @@ iD.ui = function (context) { buttons.append('span').attr('class', 'label').text(function (mode) { return mode.title; }); - controller.on('enter.editor', function (entered) { + context.on('enter.editor', function (entered) { buttons.classed('active', function (mode) { return entered.button === mode.button; }); container.classed("mode-" + entered.id, true); }); - controller.on('exit.editor', function (exited) { + context.on('exit.editor', function (exited) { container.classed("mode-" + exited.id, false); }); @@ -228,7 +227,7 @@ iD.ui = function (context) { .on('⌫', function() { d3.event.preventDefault(); }); modes.forEach(function(m) { - keybinding.on(m.key, function() { if (map.editable()) controller.enter(m); }); + keybinding.on(m.key, function() { if (map.editable()) context.enter(m); }); }); d3.select(document) @@ -246,7 +245,7 @@ iD.ui = function (context) { .on('logout.editor', connection.logout) .on('login.editor', connection.authenticate)); - controller.enter(iD.modes.Browse(context)); + context.enter(iD.modes.Browse(context)); if (!localStorage.sawSplash) { iD.ui.splash(); diff --git a/test/index.html b/test/index.html index 879cc6171..d32331ba9 100644 --- a/test/index.html +++ b/test/index.html @@ -119,7 +119,6 @@ - From 6188d128f8d12e73a874b71c46ad8f0c6c078ca6 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Feb 2013 11:50:07 -0500 Subject: [PATCH 088/415] Arrange --- js/id/id.js | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/js/id/id.js b/js/id/id.js index b2d28fb3f..ce8131bc9 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -14,7 +14,7 @@ window.iD = function () { context.history = function () { return history; }; context.map = function () { return map; }; - /* History delegation. */ + /* History */ context.graph = history.graph; context.perform = history.perform; context.replace = history.replace; @@ -23,9 +23,16 @@ window.iD = function () { context.redo = history.undo; context.changes = history.changes; - context.entity = function (id) { return history.graph().entity(id); }; - context.geometry = function (id) { return context.entity(id).geometry(history.graph()); }; + /* Graph */ + context.entity = function (id) { + return history.graph().entity(id); + }; + context.geometry = function (id) { + return context.entity(id).geometry(history.graph()); + }; + + /* Modes */ context.enter = function(newMode) { if (mode) { mode.exit(); @@ -41,9 +48,16 @@ window.iD = function () { return mode; }; - context.install = function (behavior) { context.surface().call(behavior); }; - context.uninstall = function (behavior) { context.surface().call(behavior.off); }; + /* Behaviors */ + context.install = function (behavior) { + context.surface().call(behavior); + }; + context.uninstall = function (behavior) { + context.surface().call(behavior.off); + }; + + /* Map */ context.background = function () { return map.background; }; context.surface = function () { return map.surface; }; context.projection = map.projection; From fe49e8fb77637e0598d9c381b7f24385de367c27 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Feb 2013 12:17:24 -0500 Subject: [PATCH 089/415] Pass around context rather than map --- js/id/behavior/draw.js | 2 +- js/id/behavior/draw_way.js | 2 +- js/id/geo.js | 6 +++--- js/id/id.js | 3 ++- js/id/modes/select.js | 2 +- js/id/renderer/map.js | 35 +++++++++++++---------------------- js/id/ui.js | 4 ++-- js/id/ui/commit.js | 4 ++-- js/id/ui/contributors.js | 8 ++++---- js/id/ui/layerswitcher.js | 20 ++++++++++---------- js/id/ui/save.js | 4 ++-- test/spec/renderer/map.js | 20 ++++---------------- 12 files changed, 45 insertions(+), 65 deletions(-) diff --git a/js/id/behavior/draw.js b/js/id/behavior/draw.js index 8405ac341..625fc04a4 100644 --- a/js/id/behavior/draw.js +++ b/js/id/behavior/draw.js @@ -29,7 +29,7 @@ iD.behavior.Draw = function(context) { function click() { var d = datum(); if (d.type === 'way') { - var choice = iD.geo.chooseIndex(d, d3.mouse(context.surface().node()), context.map()); + var choice = iD.geo.chooseIndex(d, d3.mouse(context.surface().node()), context); event.clickWay(d, choice.loc, choice.index); } else if (d.type === 'node') { diff --git a/js/id/behavior/draw_way.js b/js/id/behavior/draw_way.js index 28a8fbb38..9efcd5bb9 100644 --- a/js/id/behavior/draw_way.js +++ b/js/id/behavior/draw_way.js @@ -19,7 +19,7 @@ iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) { if (datum.type === 'node' || datum.type === 'midpoint') { loc = datum.loc; } else if (datum.type === 'way') { - loc = iD.geo.chooseIndex(datum, d3.mouse(context.surface().node()), context.map()).loc; + loc = iD.geo.chooseIndex(datum, d3.mouse(context.surface().node()), context).loc; } context.replace(iD.actions.MoveNode(nodeId, loc)); diff --git a/js/id/geo.js b/js/id/geo.js index fcce74b9a..dcc11717f 100644 --- a/js/id/geo.js +++ b/js/id/geo.js @@ -14,11 +14,11 @@ iD.geo.dist = function(a, b) { Math.pow(a[1] - b[1], 2)); }; -iD.geo.chooseIndex = function(way, point, map) { +iD.geo.chooseIndex = function(way, point, context) { var dist = iD.geo.dist, - graph = map.history().graph(), + graph = context.graph(), nodes = graph.childNodes(way), - projNodes = nodes.map(function(n) { return map.projection(n.loc); }); + projNodes = nodes.map(function(n) { return context.projection(n.loc); }); for (var i = 0, changes = []; i < projNodes.length - 1; i++) { changes[i] = diff --git a/js/id/id.js b/js/id/id.js index ce8131bc9..713377f15 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -6,7 +6,7 @@ window.iD = function () { mode, container, ui = iD.ui(context), - map = iD.Map().connection(connection).history(history); + map = iD.Map(context); /* Straight accessors. Avoid using these if you can. */ context.ui = function () { return ui; }; @@ -62,6 +62,7 @@ window.iD = function () { context.surface = function () { return map.surface; }; context.projection = map.projection; context.tail = map.tail; + context.redraw = map.redraw; context.container = function (_) { if (!arguments.length) return container; diff --git a/js/id/modes/select.js b/js/id/modes/select.js index df445862d..36066d7d0 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -108,7 +108,7 @@ iD.modes.Select = function(context, selection, initial) { if (datum instanceof iD.Way && !target.classed('fill')) { var choice = iD.geo.chooseIndex(datum, - d3.mouse(context.surface().node()), context.map()), + d3.mouse(context.surface().node()), context), node = iD.Node({ loc: choice.loc }); context.perform( diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index cd99de738..b738feca4 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -1,6 +1,5 @@ -iD.Map = function() { - var connection, history, - dimensions = [], +iD.Map = function(context) { + var dimensions = [], dispatch = d3.dispatch('move', 'drawn'), projection = d3.geo.mercator().scale(1024), roundedProjection = iD.svg.RoundProjection(projection), @@ -27,6 +26,12 @@ iD.Map = function() { surface, tilegroup; function map(selection) { + context.connection() + .on('load.tile', connectionLoad); + + context.history() + .on('change.map', redraw); + selection.call(zoom); tilegroup = selection.append('div') @@ -57,7 +62,7 @@ iD.Map = function() { function drawVector(difference) { var filter, all, extent = map.extent(), - graph = history.graph(); + graph = context.graph(); function addParents(parents) { for (var i = 0; i < parents.length; i++) { @@ -121,7 +126,7 @@ iD.Map = function() { } function connectionLoad(err, result) { - history.merge(result); + context.history().merge(result); redraw(Object.keys(result)); } @@ -182,7 +187,7 @@ iD.Map = function() { tilegroup.call(background); if (map.editable()) { - connection.loadTiles(projection, dimensions); + context.connection().loadTiles(projection, dimensions); drawVector(difference); } else { editOff(); @@ -347,15 +352,8 @@ iD.Map = function() { }; map.flush = function () { - connection.flush(); - history.reset(); - return map; - }; - - map.connection = function(_) { - if (!arguments.length) return connection; - connection = _; - connection.on('load.tile', connectionLoad); + context.connection().flush(); + context.history().reset(); return map; }; @@ -378,13 +376,6 @@ iD.Map = function() { return map; }; - map.history = function (_) { - if (!arguments.length) return history; - history = _; - history.on('change.map', redraw); - return map; - }; - map.background = background; map.projection = projection; map.redraw = redraw; diff --git a/js/id/ui.js b/js/id/ui.js index 6b9b3cc08..5714e05ca 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -66,7 +66,7 @@ iD.ui = function (context) { map.on('move.editor', _.debounce(function() { disableTooHigh(); - contributors.call(iD.ui.contributors(map)); + contributors.call(iD.ui.contributors(context)); }, 500)); buttons.append('span') @@ -131,7 +131,7 @@ iD.ui = function (context) { .call(iD.ui.geocoder().map(map)); container.append('div').attr('class', 'map-control layerswitcher-control') - .call(iD.ui.layerswitcher(map)); + .call(iD.ui.layerswitcher(context)); container.append('div') .style('display', 'none') diff --git a/js/id/ui/commit.js b/js/id/ui/commit.js index d883b1748..6c1133b35 100644 --- a/js/id/ui/commit.js +++ b/js/id/ui/commit.js @@ -1,4 +1,4 @@ -iD.ui.commit = function(map) { +iD.ui.commit = function(context) { var event = d3.dispatch('cancel', 'save', 'fix'); function zipSame(d) { @@ -89,7 +89,7 @@ iD.ui.commit = function(map) { cancelbutton.append('span').attr('class','label').text('Cancel'); var warnings = body.selectAll('div.warning-section') - .data(iD.validate(changes, map.history().graph())) + .data(iD.validate(changes, context.graph())) .enter() .append('div').attr('class', 'modal-section warning-section fillL'); diff --git a/js/id/ui/contributors.js b/js/id/ui/contributors.js index c64d8a2ac..e029bfd04 100644 --- a/js/id/ui/contributors.js +++ b/js/id/ui/contributors.js @@ -1,10 +1,10 @@ -iD.ui.contributors = function(map) { +iD.ui.contributors = function(context) { function contributors(selection) { var users = {}, limit = 3, - entities = map.history().graph().intersects(map.extent()); + entities = context.graph().intersects(context.map().extent()); for (var i in entities) { if (entities[i].user) users[entities[i].user] = true; @@ -21,7 +21,7 @@ iD.ui.contributors = function(map) { l.enter().append('a') .attr('class', 'user-link') - .attr('href', function(d) { return map.connection().userUrl(d); }) + .attr('href', function(d) { return context.connection().userUrl(d); }) .attr('target', '_blank') .text(String); @@ -37,7 +37,7 @@ iD.ui.contributors = function(map) { .append('a') .attr('target', '_blank') .attr('href', function() { - var ext = map.extent(); + var ext = context.map().extent(); return 'http://www.openstreetmap.org/browse/changesets?bbox=' + [ ext[0][0], ext[0][1], ext[1][0], ext[1][1]]; diff --git a/js/id/ui/layerswitcher.js b/js/id/ui/layerswitcher.js index 298c469e0..c64457ab8 100644 --- a/js/id/ui/layerswitcher.js +++ b/js/id/ui/layerswitcher.js @@ -1,4 +1,4 @@ -iD.ui.layerswitcher = function(map) { +iD.ui.layerswitcher = function(context) { var event = d3.dispatch('cancel', 'save'), sources = [{ name: 'Bing', @@ -94,7 +94,7 @@ iD.ui.layerswitcher = function(map) { function selectLayer(d) { content.selectAll('a.layer') .classed('selected', function(d) { - return d.source === map.background.source(); + return d.source === context.background().source(); }); d3.select('#attribution a') .attr('href', d.link) @@ -126,9 +126,9 @@ iD.ui.layerswitcher = function(map) { d.source = configured; d.name = 'Custom (configured)'; } - map.background.source(d.source); - map.history().imagery_used(d.name); - map.redraw(); + context.background().source(d.source); + context.history().imagery_used(d.name); + context.redraw(); selectLayer(d); }) .insert('span') @@ -145,8 +145,8 @@ iD.ui.layerswitcher = function(map) { ['bottom', [0, 1]]]; function nudge(d) { - map.background.nudge(d[1]); - map.redraw(); + context.background().nudge(d[1]); + context.redraw(); } adjustments.append('a') @@ -181,12 +181,12 @@ iD.ui.layerswitcher = function(map) { .text('reset') .attr('class', 'reset') .on('click', function() { - map.background.offset([0, 0]); - map.redraw(); + context.background().offset([0, 0]); + context.redraw(); }); selection.call(clickoutside); - selectLayer(map.background.source()); + selectLayer(context.background().source()); } return d3.rebind(layerswitcher, event, 'on'); diff --git a/js/id/ui/save.js b/js/id/ui/save.js index 6098519da..ba217adcf 100644 --- a/js/id/ui/save.js +++ b/js/id/ui/save.js @@ -50,12 +50,12 @@ iD.ui.save = function(context) { modal.select('.content') .classed('commit-modal', true) .datum(changes) - .call(iD.ui.commit(map) + .call(iD.ui.commit(context) .on('cancel', function() { modal.remove(); }) .on('fix', function(d) { - map.extent(d.entity.extent(map.history().graph())); + map.extent(d.entity.extent(context.graph())); if (map.zoom() > 19) map.zoom(19); context.enter(iD.modes.Select(context, [d.entity.id])); modal.remove(); diff --git a/test/spec/renderer/map.js b/test/spec/renderer/map.js index d7d0e3440..de8abdf61 100644 --- a/test/spec/renderer/map.js +++ b/test/spec/renderer/map.js @@ -1,22 +1,10 @@ describe('iD.Map', function() { - var container, map; + var map; beforeEach(function() { - container = d3.select('body').append('div'); - map = iD.Map(); - container.call(map); - }); - - afterEach(function() { - container.remove(); - }); - - describe('#connection', function() { - it('gets and sets connection', function() { - var connection = iD.Connection(); - expect(map.connection(connection)).to.equal(map); - expect(map.connection()).to.equal(connection); - }); + map = iD().map(); + d3.select(document.createElement('div')) + .call(map); }); describe('#zoom', function() { From 0bcb0520d8eb826298d532695f3fe83b3b165e6a Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Feb 2013 12:46:01 -0500 Subject: [PATCH 090/415] Use iD.version --- js/id/connection.js | 9 +-------- js/id/id.js | 2 +- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/js/id/connection.js b/js/id/connection.js index d2afa6281..399c48d86 100644 --- a/js/id/connection.js +++ b/js/id/connection.js @@ -4,7 +4,6 @@ iD.Connection = function() { url = 'http://www.openstreetmap.org', connection = {}, user = {}, - version, keys, inflight = {}, loadedTiles = {}, @@ -186,7 +185,7 @@ iD.Connection = function() { content: JXON.stringify(connection.changesetJXON({ imagery_used: imagery_used.join(';'), comment: comment, - created_by: 'iD ' + (version || '') + created_by: 'iD ' + iD.version })) }, function (err, changeset_id) { if (err) return callback(err); @@ -322,12 +321,6 @@ iD.Connection = function() { return oauth.authenticate(done); }; - connection.version = function(_) { - if (!arguments.length) return version; - version = _; - return connection; - }; - connection.bboxFromAPI = bboxFromAPI; connection.changesetUrl = changesetUrl; connection.loadFromURL = loadFromURL; diff --git a/js/id/id.js b/js/id/id.js index 713377f15..ef026a097 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -1,7 +1,7 @@ window.iD = function () { var context = {}, history = iD.History(), - connection = iD.Connection().version(iD.version), + connection = iD.Connection(), dispatch = d3.dispatch('enter', 'exit'), mode, container, From f1b6f5b14a10a958862942eacf452932bbd063f1 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Fri, 1 Feb 2013 12:47:57 -0500 Subject: [PATCH 091/415] Fix Google Analytics include --- index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.html b/index.html index 52ff209f2..b0e46d55d 100644 --- a/index.html +++ b/index.html @@ -141,8 +141,7 @@ d3.select("#iD") .call(id.ui()) }); - - + + From 031c8d655e02f1686bf654d4a168f0decc16a7cf Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Fri, 1 Feb 2013 13:12:46 -0500 Subject: [PATCH 092/415] Fallback for browsers that do not support localStorage. Fixes #591 --- js/id/connection.js | 4 ++-- js/id/id.js | 44 ++++++++++++++++++++++++----------------- js/id/oauth.js | 15 ++++++-------- js/id/ui.js | 4 ++-- js/id/ui/commit.js | 2 +- test/spec/connection.js | 3 ++- test/spec/oauth.js | 3 ++- 7 files changed, 41 insertions(+), 34 deletions(-) diff --git a/js/id/connection.js b/js/id/connection.js index 399c48d86..794232682 100644 --- a/js/id/connection.js +++ b/js/id/connection.js @@ -1,4 +1,4 @@ -iD.Connection = function() { +iD.Connection = function(context) { var event = d3.dispatch('auth', 'load'), url = 'http://www.openstreetmap.org', @@ -7,7 +7,7 @@ iD.Connection = function() { keys, inflight = {}, loadedTiles = {}, - oauth = iD.OAuth().url(url); + oauth = iD.OAuth(context).url(url); function changesetUrl(changesetId) { return url + '/browse/changeset/' + changesetId; diff --git a/js/id/id.js b/js/id/id.js index ef026a097..0b2bbd617 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -1,34 +1,42 @@ window.iD = function () { var context = {}, history = iD.History(), - connection = iD.Connection(), + storage = localStorage || {}, dispatch = d3.dispatch('enter', 'exit'), mode, container, ui = iD.ui(context), map = iD.Map(context); + context.storage = function(k, v) { + if (arguments.length === 1) return storage[k]; + else storage[k] = v; + }; + + // the connection requires .storage() to be available on calling. + var connection = iD.Connection(context); + /* Straight accessors. Avoid using these if you can. */ - context.ui = function () { return ui; }; - context.connection = function () { return connection; }; - context.history = function () { return history; }; - context.map = function () { return map; }; + context.ui = function() { return ui; }; + context.connection = function() { return connection; }; + context.history = function() { return history; }; + context.map = function() { return map; }; /* History */ - context.graph = history.graph; + context.graph = history.graph; context.perform = history.perform; context.replace = history.replace; - context.pop = history.pop; - context.undo = history.undo; - context.redo = history.undo; + context.pop = history.pop; + context.undo = history.undo; + context.redo = history.undo; context.changes = history.changes; /* Graph */ - context.entity = function (id) { + context.entity = function(id) { return history.graph().entity(id); }; - context.geometry = function (id) { + context.geometry = function(id) { return context.entity(id).geometry(history.graph()); }; @@ -49,22 +57,22 @@ window.iD = function () { }; /* Behaviors */ - context.install = function (behavior) { + context.install = function(behavior) { context.surface().call(behavior); }; - context.uninstall = function (behavior) { + context.uninstall = function(behavior) { context.surface().call(behavior.off); }; /* Map */ - context.background = function () { return map.background; }; - context.surface = function () { return map.surface; }; + context.background = function() { return map.background; }; + context.surface = function() { return map.surface; }; context.projection = map.projection; - context.tail = map.tail; - context.redraw = map.redraw; + context.tail = map.tail; + context.redraw = map.redraw; - context.container = function (_) { + context.container = function(_) { if (!arguments.length) return container; container = _; return context; diff --git a/js/id/oauth.js b/js/id/oauth.js index a09f2affc..4aa1d1876 100644 --- a/js/id/oauth.js +++ b/js/id/oauth.js @@ -1,4 +1,4 @@ -iD.OAuth = function() { +iD.OAuth = function(context) { var baseurl = 'http://www.openstreetmap.org', o = {}, keys, @@ -6,10 +6,6 @@ iD.OAuth = function() { function keyclean(x) { return x.replace(/\W/g, ''); } - if (token('oauth_token')) { - o.oauth_token = token('oauth_token'); - } - function timenonce(o) { o.oauth_timestamp = ohauth.timestamp(); o.oauth_nonce = ohauth.nonce(); @@ -18,10 +14,11 @@ iD.OAuth = function() { // token getter/setter, namespaced to the current `apibase` value. function token(k, x) { - if (arguments.length == 2) { - localStorage[keyclean(baseurl) + k] = x; - } - return localStorage[keyclean(baseurl) + k]; + return context.storage(keyclean(baseurl) + k, x); + } + + if (token('oauth_token')) { + o.oauth_token = token('oauth_token'); } oauth.authenticated = function() { diff --git a/js/id/ui.js b/js/id/ui.js index 5714e05ca..c28ca1282 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -247,9 +247,9 @@ iD.ui = function (context) { context.enter(iD.modes.Browse(context)); - if (!localStorage.sawSplash) { + if (!context.storage('sawSplash')) { iD.ui.splash(); - localStorage.sawSplash = true; + context.storage('sawSplash', true); } }; }; diff --git a/js/id/ui/commit.js b/js/id/ui/commit.js index 6c1133b35..09bb99404 100644 --- a/js/id/ui/commit.js +++ b/js/id/ui/commit.js @@ -38,7 +38,7 @@ iD.ui.commit = function(context) { comment_section.append('textarea') .attr('class', 'changeset-comment') .attr('placeholder', 'Brief Description of your contributions') - .property('value', localStorage.comment || '') + .property('value', context.storage('comment') || '') .node().select(); var commit_info = diff --git a/test/spec/connection.js b/test/spec/connection.js index 652c1419b..cdc439a46 100644 --- a/test/spec/connection.js +++ b/test/spec/connection.js @@ -2,7 +2,8 @@ describe('iD.Connection', function () { var c; beforeEach(function () { - c = new iD.Connection(); + context = iD(); + c = new iD.Connection(context); }); it('is instantiated', function () { diff --git a/test/spec/oauth.js b/test/spec/oauth.js index 8fd57c18c..b7ae1074f 100644 --- a/test/spec/oauth.js +++ b/test/spec/oauth.js @@ -2,7 +2,8 @@ describe('iD.OAuth', function() { var o; beforeEach(function() { - o = iD.OAuth(); + context = iD(); + o = iD.OAuth(context); }); describe('#logout', function() { From 619216b33b27f5acae41881c499a4b32b61a1b02 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Feb 2013 13:13:21 -0500 Subject: [PATCH 093/415] Remove double dispatch --- js/id/renderer/map.js | 1 - 1 file changed, 1 deletion(-) diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index b738feca4..1f22a7c81 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -263,7 +263,6 @@ iD.Map = function(context) { t[0] - ll[0] + c[0], t[1] - ll[1] + c[1]]); zoom.translate(projection.translate()); - dispatch.move(map); return true; } From 175cdc553d07477447299810375060ad39b7a073 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Fri, 1 Feb 2013 13:17:19 -0500 Subject: [PATCH 094/415] Fix localStorage number of args in oauth --- js/id/oauth.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/id/oauth.js b/js/id/oauth.js index 4aa1d1876..d7cb095a8 100644 --- a/js/id/oauth.js +++ b/js/id/oauth.js @@ -13,8 +13,8 @@ iD.OAuth = function(context) { } // token getter/setter, namespaced to the current `apibase` value. - function token(k, x) { - return context.storage(keyclean(baseurl) + k, x); + function token() { + return context.storage.apply(context, arguments); } if (token('oauth_token')) { From cdda561f6977482316d9cf5f4baf3d5c76bad607 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Feb 2013 13:39:39 -0500 Subject: [PATCH 095/415] Fix dev/live toggle --- js/id/ui.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/js/id/ui.js b/js/id/ui.js index c28ca1282..8d764d695 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -167,11 +167,13 @@ iD.ui = function (context) { .on('click.editor', function() { d3.event.preventDefault(); if (d3.select(this).classed('live')) { - map.flush().connection() + map.flush(); + context.connection() .url('http://api06.dev.openstreetmap.org'); d3.select(this).text('dev').classed('live', false); } else { - map.flush().connection() + map.flush(); + context.connection() .url('http://www.openstreetmap.org'); d3.select(this).text('live').classed('live', true); } From 4fbbd1cf822a08310cdbdb819fde6f9cb5fa5162 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Fri, 1 Feb 2013 14:11:32 -0500 Subject: [PATCH 096/415] Update to pre-release 3.0.5 d3 --- js/lib/d3.v3.js | 1604 ++++++++++++++++++++++++++--------------------- 1 file changed, 900 insertions(+), 704 deletions(-) diff --git a/js/lib/d3.v3.js b/js/lib/d3.v3.js index 93b676f46..818bab9dc 100644 --- a/js/lib/d3.v3.js +++ b/js/lib/d3.v3.js @@ -12,12 +12,9 @@ }; } d3 = { - version: "3.0.0pre" + version: "3.0.5" }; - var π = Math.PI, ε = 1e-6, εε = .001, d3_radians = π / 180, d3_degrees = 180 / π; - function d3_zero() { - return 0; - } + var π = Math.PI, ε = 1e-6, d3_radians = π / 180, d3_degrees = 180 / π; function d3_target(d) { return d.target; } @@ -234,7 +231,7 @@ return s; }; d3.quantile = function(values, p) { - var H = (values.length - 1) * p + 1, h = Math.floor(H), v = values[h - 1], e = H - h; + var H = (values.length - 1) * p + 1, h = Math.floor(H), v = +values[h - 1], e = H - h; return e ? v + e * (values[h] - v) : v; }; d3.shuffle = function(array) { @@ -454,8 +451,13 @@ d3.rebind(xhr, dispatch, "on"); if (arguments.length === 2 && typeof mimeType === "function") callback = mimeType, mimeType = null; - return callback == null ? xhr : xhr.get(callback); + return callback == null ? xhr : xhr.get(d3_xhr_fixCallback(callback)); }; + function d3_xhr_fixCallback(callback) { + return callback.length === 1 ? function(error, request) { + callback(error == null ? request : null); + } : callback; + } d3.text = function() { return d3.xhr.apply(d3, arguments).response(d3_text); }; @@ -1687,13 +1689,13 @@ return value; } function bind(group, groupData) { - var i, n = group.length, m = groupData.length, n0 = Math.min(n, m), n1 = Math.max(n, m), updateNodes = [], enterNodes = [], exitNodes = [], node, nodeData; + var i, n = group.length, m = groupData.length, n0 = Math.min(n, m), updateNodes = new Array(m), enterNodes = new Array(m), exitNodes = new Array(n), node, nodeData; if (key) { - var nodeByKeyValue = new d3_Map(), keyValues = [], keyValue, j = groupData.length; + var nodeByKeyValue = new d3_Map(), dataByKeyValue = new d3_Map(), keyValues = [], keyValue; for (i = -1; ++i < n; ) { keyValue = key.call(node = group[i], node.__data__, i); if (nodeByKeyValue.has(keyValue)) { - exitNodes[j++] = node; + exitNodes[i] = node; } else { nodeByKeyValue.set(keyValue, node); } @@ -1701,14 +1703,13 @@ } for (i = -1; ++i < m; ) { keyValue = key.call(groupData, nodeData = groupData[i], i); - if (nodeByKeyValue.has(keyValue)) { - updateNodes[i] = node = nodeByKeyValue.get(keyValue); + if (node = nodeByKeyValue.get(keyValue)) { + updateNodes[i] = node; node.__data__ = nodeData; - enterNodes[i] = exitNodes[i] = null; - } else { + } else if (!dataByKeyValue.has(keyValue)) { enterNodes[i] = d3_selection_dataNode(nodeData); - updateNodes[i] = exitNodes[i] = null; } + dataByKeyValue.set(keyValue, nodeData); nodeByKeyValue.remove(keyValue); } for (i = -1; ++i < n; ) { @@ -1723,19 +1724,15 @@ if (node) { node.__data__ = nodeData; updateNodes[i] = node; - enterNodes[i] = exitNodes[i] = null; } else { enterNodes[i] = d3_selection_dataNode(nodeData); - updateNodes[i] = exitNodes[i] = null; } } for (;i < m; ++i) { enterNodes[i] = d3_selection_dataNode(groupData[i]); - updateNodes[i] = exitNodes[i] = null; } - for (;i < n1; ++i) { + for (;i < n; ++i) { exitNodes[i] = group[i]; - enterNodes[i] = updateNodes[i] = null; } } enterNodes.update = updateNodes; @@ -3769,7 +3766,7 @@ d3.behavior.zoom = function() { var translate = [ 0, 0 ], translate0, scale = 1, scale0, scaleExtent = d3_behavior_zoomInfinity, event = d3_eventDispatch(zoom, "zoom"), x0, x1, y0, y1, touchtime; function zoom() { - this.on("mousedown.zoom", mousedown).on("mousewheel.zoom", mousewheel).on("mousemove.zoom", mousemove).on("DOMMouseScroll.zoom", mousewheel).on("dblclick.zoom", dblclick).on("touchstart.zoom", touchstart).on("touchmove.zoom", touchmove).on("touchend.zoom", touchstart); + this.on("mousedown.zoom", mousedown).on("mousemove.zoom", mousemove).on(d3_behavior_zoomWheel + ".zoom", mousewheel).on("dblclick.zoom", dblclick).on("touchstart.zoom", touchstart).on("touchmove.zoom", touchmove).on("touchend.zoom", touchstart); } zoom.translate = function(x) { if (!arguments.length) return translate; @@ -3901,21 +3898,14 @@ } return d3.rebind(zoom, event, "on"); }; - var d3_behavior_zoomDiv, d3_behavior_zoomInfinity = [ 0, Infinity ]; - function d3_behavior_zoomDelta() { - if (!d3_behavior_zoomDiv) { - d3_behavior_zoomDiv = d3.select("body").append("div").style("visibility", "hidden").style("top", 0).style("height", 0).style("width", 0).style("overflow-y", "scroll").append("div").style("height", "2000px").node().parentNode; - } - var e = d3.event, delta; - try { - d3_behavior_zoomDiv.scrollTop = 1e3; - d3_behavior_zoomDiv.dispatchEvent(e); - delta = 1e3 - d3_behavior_zoomDiv.scrollTop; - } catch (error) { - delta = e.wheelDelta || -e.detail * 5; - } - return delta; - } + var d3_behavior_zoomInfinity = [ 0, Infinity ]; + var d3_behavior_zoomDelta, d3_behavior_zoomWheel = "onwheel" in document ? (d3_behavior_zoomDelta = function() { + return -d3.event.deltaY * (d3.event.deltaMode ? 120 : 1); + }, "wheel") : "onmousewheel" in document ? (d3_behavior_zoomDelta = function() { + return d3.event.wheelDelta; + }, "mousewheel") : (d3_behavior_zoomDelta = function() { + return -d3.event.detail; + }, "MozMousePixelScroll"); d3.layout = {}; d3.layout.bundle = function() { return function(links) { @@ -4625,10 +4615,8 @@ } d3.layout.hierarchy = function() { var sort = d3_layout_hierarchySort, children = d3_layout_hierarchyChildren, value = d3_layout_hierarchyValue; - function recurse(data, depth, nodes) { - var childs = children.call(hierarchy, data, depth), node = d3_layout_hierarchyInline ? data : { - data: data - }; + function recurse(node, depth, nodes) { + var childs = children.call(hierarchy, node, depth); node.depth = depth; nodes.push(node); if (childs && (n = childs.length)) { @@ -4642,7 +4630,7 @@ if (sort) c.sort(sort); if (value) node.value = v; } else if (value) { - node.value = +value.call(hierarchy, data, depth) || 0; + node.value = +value.call(hierarchy, node, depth) || 0; } return node; } @@ -4652,7 +4640,7 @@ var i = -1, n, j = depth + 1; while (++i < n) v += revalue(children[i], j); } else if (value) { - v = +value.call(hierarchy, d3_layout_hierarchyInline ? node : node.data, depth) || 0; + v = +value.call(hierarchy, node, depth) || 0; } if (value) node.value = v; return v; @@ -4685,11 +4673,8 @@ }; function d3_layout_hierarchyRebind(object, hierarchy) { d3.rebind(object, hierarchy, "sort", "children", "value"); + object.nodes = object; object.links = d3_layout_hierarchyLinks; - object.nodes = function(d) { - d3_layout_hierarchyInline = true; - return (object.nodes = object)(d); - }; return object; } function d3_layout_hierarchyChildren(d) { @@ -4711,7 +4696,6 @@ }); })); } - var d3_layout_hierarchyInline = false; d3.layout.pack = function() { var hierarchy = d3.layout.hierarchy().sort(d3_layout_packSort), padding = 0, size = [ 1, 1 ]; function pack(d, i) { @@ -5096,7 +5080,7 @@ function squarify(node) { var children = node.children; if (children && children.length) { - var rect = pad(node), row = [], remaining = children.slice(), child, best = Infinity, score, u = mode === "slice" ? rect.dx : mode === "dice" || mode === "slice-dice" && node.depth & 1 ? rect.dy : Math.min(rect.dx, rect.dy), n; + var rect = pad(node), row = [], remaining = children.slice(), child, best = Infinity, score, u = mode === "slice" ? rect.dx : mode === "dice" ? rect.dy : mode === "slice-dice" ? node.depth & 1 ? rect.dy : rect.dx : Math.min(rect.dx, rect.dy), n; scale(remaining, rect.dx * rect.dy / node.value); row.area = 0; while ((n = remaining.length) > 0) { @@ -5333,78 +5317,168 @@ d3.csv = d3_dsv(",", "text/csv"); d3.tsv = d3_dsv(" ", "text/tab-separated-values"); d3.geo = {}; - function d3_geo_type(types) { - for (var type in d3_geo_typeDefaults) { - if (!(type in types)) { - types[type] = d3_geo_typeDefaults[type]; + d3.geo.stream = function(object, listener) { + if (d3_geo_streamObjectType.hasOwnProperty(object.type)) { + d3_geo_streamObjectType[object.type](object, listener); + } else { + d3_geo_streamGeometry(object, listener); + } + }; + function d3_geo_streamGeometry(geometry, listener) { + if (d3_geo_streamGeometryType.hasOwnProperty(geometry.type)) { + d3_geo_streamGeometryType[geometry.type](geometry, listener); + } + } + var d3_geo_streamObjectType = { + Feature: function(feature, listener) { + d3_geo_streamGeometry(feature.geometry, listener); + }, + FeatureCollection: function(object, listener) { + var features = object.features, i = -1, n = features.length; + while (++i < n) d3_geo_streamGeometry(features[i].geometry, listener); + } + }; + var d3_geo_streamGeometryType = { + Sphere: function(object, listener) { + listener.sphere(); + }, + Point: function(object, listener) { + var coordinate = object.coordinates; + listener.point(coordinate[0], coordinate[1]); + }, + MultiPoint: function(object, listener) { + var coordinates = object.coordinates, i = -1, n = coordinates.length, coordinate; + while (++i < n) coordinate = coordinates[i], listener.point(coordinate[0], coordinate[1]); + }, + LineString: function(object, listener) { + d3_geo_streamLine(object.coordinates, listener, 0); + }, + MultiLineString: function(object, listener) { + var coordinates = object.coordinates, i = -1, n = coordinates.length; + while (++i < n) d3_geo_streamLine(coordinates[i], listener, 0); + }, + Polygon: function(object, listener) { + d3_geo_streamPolygon(object.coordinates, listener); + }, + MultiPolygon: function(object, listener) { + var coordinates = object.coordinates, i = -1, n = coordinates.length; + while (++i < n) d3_geo_streamPolygon(coordinates[i], listener); + }, + GeometryCollection: function(object, listener) { + var geometries = object.geometries, i = -1, n = geometries.length; + while (++i < n) d3_geo_streamGeometry(geometries[i], listener); + } + }; + function d3_geo_streamLine(coordinates, listener, closed) { + var i = -1, n = coordinates.length - closed, coordinate; + listener.lineStart(); + while (++i < n) coordinate = coordinates[i], listener.point(coordinate[0], coordinate[1]); + listener.lineEnd(); + } + function d3_geo_streamPolygon(coordinates, listener) { + var i = -1, n = coordinates.length; + listener.polygonStart(); + while (++i < n) d3_geo_streamLine(coordinates[i], listener, 1); + listener.polygonEnd(); + } + function d3_geo_spherical(cartesian) { + return [ Math.atan2(cartesian[1], cartesian[0]), Math.asin(Math.max(-1, Math.min(1, cartesian[2]))) ]; + } + function d3_geo_sphericalEqual(a, b) { + return Math.abs(a[0] - b[0]) < ε && Math.abs(a[1] - b[1]) < ε; + } + function d3_geo_cartesian(spherical) { + var λ = spherical[0], φ = spherical[1], cosφ = Math.cos(φ); + return [ cosφ * Math.cos(λ), cosφ * Math.sin(λ), Math.sin(φ) ]; + } + function d3_geo_cartesianDot(a, b) { + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; + } + function d3_geo_cartesianCross(a, b) { + return [ a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0] ]; + } + function d3_geo_cartesianAdd(a, b) { + a[0] += b[0]; + a[1] += b[1]; + a[2] += b[2]; + } + function d3_geo_cartesianScale(vector, k) { + return [ vector[0] * k, vector[1] * k, vector[2] * k ]; + } + function d3_geo_cartesianNormalize(d) { + var l = Math.sqrt(d[0] * d[0] + d[1] * d[1] + d[2] * d[2]); + d[0] /= l; + d[1] /= l; + d[2] /= l; + } + function d3_geo_resample(project) { + var δ2 = .5, maxDepth = 16; + function resample(stream) { + var λ0, x0, y0, a0, b0, c0; + var resample = { + point: point, + lineStart: lineStart, + lineEnd: lineEnd, + polygonStart: function() { + stream.polygonStart(); + resample.lineStart = polygonLineStart; + }, + polygonEnd: function() { + stream.polygonEnd(); + resample.lineStart = lineStart; + } + }; + function point(x, y) { + x = project(x, y); + stream.point(x[0], x[1]); + } + function lineStart() { + x0 = NaN; + resample.point = linePoint; + stream.lineStart(); + } + function linePoint(λ, φ) { + var c = d3_geo_cartesian([ λ, φ ]), p = project(λ, φ); + resampleLineTo(x0, y0, λ0, a0, b0, c0, x0 = p[0], y0 = p[1], λ0 = λ, a0 = c[0], b0 = c[1], c0 = c[2], maxDepth, stream); + stream.point(x0, y0); + } + function lineEnd() { + resample.point = point; + stream.lineEnd(); + } + function polygonLineStart() { + var λ00, φ00, x00, y00, a00, b00, c00; + lineStart(); + resample.point = function(λ, φ) { + linePoint(λ00 = λ, φ00 = φ), x00 = x0, y00 = y0, a00 = a0, b00 = b0, c00 = c0; + resample.point = linePoint; + }; + resample.lineEnd = function() { + resampleLineTo(x0, y0, λ0, a0, b0, c0, x00, y00, λ00, a00, b00, c00, maxDepth, stream); + resample.lineEnd = lineEnd; + lineEnd(); + }; + } + return resample; + } + function resampleLineTo(x0, y0, λ0, a0, b0, c0, x1, y1, λ1, a1, b1, c1, depth, stream) { + var dx = x1 - x0, dy = y1 - y0, d2 = dx * dx + dy * dy; + if (d2 > 4 * δ2 && depth--) { + var a = a0 + a1, b = b0 + b1, c = c0 + c1, m = Math.sqrt(a * a + b * b + c * c), φ2 = Math.asin(c /= m), λ2 = Math.abs(Math.abs(c) - 1) < ε ? (λ0 + λ1) / 2 : Math.atan2(b, a), p = project(λ2, φ2), x2 = p[0], y2 = p[1], dx2 = x2 - x0, dy2 = y2 - y0, dz = dy * dx2 - dx * dy2; + if (dz * dz / d2 > δ2 || Math.abs((dx * dx2 + dy * dy2) / d2 - .5) > .3) { + resampleLineTo(x0, y0, λ0, a0, b0, c0, x2, y2, λ2, a /= m, b /= m, c, depth, stream); + stream.point(x2, y2); + resampleLineTo(x2, y2, λ2, a, b, c, x1, y1, λ1, a1, b1, c1, depth, stream); + } } } - return types; + resample.precision = function(_) { + if (!arguments.length) return Math.sqrt(δ2); + maxDepth = (δ2 = _ * _) > 0 && 16; + return resample; + }; + return resample; } - var d3_geo_typeDefaults = { - Feature: function(feature) { - this.geometry(feature.geometry); - }, - FeatureCollection: function(collection) { - var features = collection.features, i = -1, n = features.length; - while (++i < n) this.Feature(features[i]); - }, - GeometryCollection: function(collection) { - var geometries = collection.geometries, i = -1, n = geometries.length; - while (++i < n) this.geometry(geometries[i]); - }, - LineString: function(lineString) { - this.line(lineString.coordinates); - }, - MultiLineString: function(multiLineString) { - var coordinates = multiLineString.coordinates, i = -1, n = coordinates.length; - while (++i < n) this.line(coordinates[i]); - }, - MultiPoint: function(multiPoint) { - var coordinates = multiPoint.coordinates, i = -1, n = coordinates.length; - while (++i < n) this.point(coordinates[i]); - }, - MultiPolygon: function(multiPolygon) { - var coordinates = multiPolygon.coordinates, i = -1, n = coordinates.length; - while (++i < n) this.polygon(coordinates[i]); - }, - Point: function(point) { - this.point(point.coordinates); - }, - Polygon: function(polygon) { - this.polygon(polygon.coordinates); - }, - Sphere: d3_noop, - object: function(object) { - return d3_geo_typeObjects.hasOwnProperty(object.type) ? this[object.type](object) : this.geometry(object); - }, - geometry: function(geometry) { - return d3_geo_typeGeometries.hasOwnProperty(geometry.type) ? this[geometry.type](geometry) : null; - }, - point: d3_noop, - line: function(coordinates) { - var i = -1, n = coordinates.length; - while (++i < n) this.point(coordinates[i]); - }, - polygon: function(coordinates) { - var i = -1, n = coordinates.length; - while (++i < n) this.line(coordinates[i]); - } - }; - var d3_geo_typeGeometries = { - LineString: 1, - MultiLineString: 1, - MultiPoint: 1, - MultiPolygon: 1, - Point: 1, - Polygon: 1, - Sphere: 1 - }; - var d3_geo_typeObjects = { - Feature: 1, - FeatureCollection: 1, - GeometryCollection: 1 - }; d3.geo.albersUsa = function() { var lower48 = d3.geo.albers(); var alaska = d3.geo.albers().rotate([ 160, 0 ]).center([ 0, 60 ]).parallels([ 55, 65 ]); @@ -5417,15 +5491,6 @@ var lon = point[0], lat = point[1]; return lat > 50 ? alaska : lon < -140 ? hawaii : lat < 21 ? puertoRico : lower48; } - albersUsa.point = function(coordinates, context) { - return projection(coordinates).point(coordinates, context); - }; - albersUsa.line = function(coordinates, context) { - return projection(coordinates[0]).line(coordinates, context); - }; - albersUsa.polygon = function(coordinates, context) { - return projection(coordinates[0][0]).polygon(coordinates, context); - }; albersUsa.scale = function(x) { if (!arguments.length) return lower48.scale(); lower48.scale(x); @@ -5481,38 +5546,123 @@ return d3_geo_projection(d3_geo_azimuthalEquidistant); }).raw = d3_geo_azimuthalEquidistant; d3.geo.bounds = d3_geo_bounds(d3_identity); - function d3_geo_bounds(projection) { - var x0, y0, x1, y1, bounds = d3_geo_type({ - point: function(point) { - point = projection(point); - var x = point[0], y = point[1]; - if (x < x0) x0 = x; - if (x > x1) x1 = x; - if (y < y0) y0 = y; - if (y > y1) y1 = y; + function d3_geo_bounds(projectStream) { + var x0, y0, x1, y1; + var bound = { + point: boundPoint, + lineStart: d3_noop, + lineEnd: d3_noop, + polygonStart: function() { + bound.lineEnd = boundPolygonLineEnd; }, - polygon: function(coordinates) { - this.line(coordinates[0]); + polygonEnd: function() { + bound.point = boundPoint; } - }); + }; + function boundPoint(x, y) { + if (x < x0) x0 = x; + if (x > x1) x1 = x; + if (y < y0) y0 = y; + if (y > y1) y1 = y; + } + function boundPolygonLineEnd() { + bound.point = bound.lineEnd = d3_noop; + } return function(feature) { y1 = x1 = -(x0 = y0 = Infinity); - bounds.object(feature); + d3.geo.stream(feature, projectStream(bound)); return [ [ x0, y0 ], [ x1, y1 ] ]; }; } + d3.geo.centroid = function(object) { + d3_geo_centroidDimension = d3_geo_centroidW = d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0; + d3.geo.stream(object, d3_geo_centroid); + var m; + if (d3_geo_centroidW && Math.abs(m = Math.sqrt(d3_geo_centroidX * d3_geo_centroidX + d3_geo_centroidY * d3_geo_centroidY + d3_geo_centroidZ * d3_geo_centroidZ)) > ε) { + return [ Math.atan2(d3_geo_centroidY, d3_geo_centroidX) * d3_degrees, Math.asin(Math.max(-1, Math.min(1, d3_geo_centroidZ / m))) * d3_degrees ]; + } + }; + var d3_geo_centroidDimension, d3_geo_centroidW, d3_geo_centroidX, d3_geo_centroidY, d3_geo_centroidZ; + var d3_geo_centroid = { + sphere: function() { + if (d3_geo_centroidDimension < 2) { + d3_geo_centroidDimension = 2; + d3_geo_centroidW = d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0; + } + }, + point: d3_geo_centroidPoint, + lineStart: d3_geo_centroidLineStart, + lineEnd: d3_geo_centroidLineEnd, + polygonStart: function() { + if (d3_geo_centroidDimension < 2) { + d3_geo_centroidDimension = 2; + d3_geo_centroidW = d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0; + } + d3_geo_centroid.lineStart = d3_geo_centroidRingStart; + }, + polygonEnd: function() { + d3_geo_centroid.lineStart = d3_geo_centroidLineStart; + } + }; + function d3_geo_centroidPoint(λ, φ) { + if (d3_geo_centroidDimension) return; + ++d3_geo_centroidW; + λ *= d3_radians; + var cosφ = Math.cos(φ *= d3_radians); + d3_geo_centroidX += (cosφ * Math.cos(λ) - d3_geo_centroidX) / d3_geo_centroidW; + d3_geo_centroidY += (cosφ * Math.sin(λ) - d3_geo_centroidY) / d3_geo_centroidW; + d3_geo_centroidZ += (Math.sin(φ) - d3_geo_centroidZ) / d3_geo_centroidW; + } + function d3_geo_centroidRingStart() { + var λ00, φ00; + d3_geo_centroidDimension = 1; + d3_geo_centroidLineStart(); + d3_geo_centroidDimension = 2; + var linePoint = d3_geo_centroid.point; + d3_geo_centroid.point = function(λ, φ) { + linePoint(λ00 = λ, φ00 = φ); + }; + d3_geo_centroid.lineEnd = function() { + d3_geo_centroid.point(λ00, φ00); + d3_geo_centroidLineEnd(); + d3_geo_centroid.lineEnd = d3_geo_centroidLineEnd; + }; + } + function d3_geo_centroidLineStart() { + var x0, y0, z0; + if (d3_geo_centroidDimension > 1) return; + if (d3_geo_centroidDimension < 1) { + d3_geo_centroidDimension = 1; + d3_geo_centroidW = d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0; + } + d3_geo_centroid.point = function(λ, φ) { + λ *= d3_radians; + var cosφ = Math.cos(φ *= d3_radians); + x0 = cosφ * Math.cos(λ); + y0 = cosφ * Math.sin(λ); + z0 = Math.sin(φ); + d3_geo_centroid.point = nextPoint; + }; + function nextPoint(λ, φ) { + λ *= d3_radians; + var cosφ = Math.cos(φ *= d3_radians), x = cosφ * Math.cos(λ), y = cosφ * Math.sin(λ), z = Math.sin(φ), w = Math.atan2(Math.sqrt((w = y0 * z - z0 * y) * w + (w = z0 * x - x0 * z) * w + (w = x0 * y - y0 * x) * w), x0 * x + y0 * y + z0 * z); + d3_geo_centroidW += w; + d3_geo_centroidX += w * (x0 + (x0 = x)); + d3_geo_centroidY += w * (y0 + (y0 = y)); + d3_geo_centroidZ += w * (z0 + (z0 = z)); + } + } + function d3_geo_centroidLineEnd() { + d3_geo_centroid.point = d3_geo_centroidPoint; + } d3.geo.circle = function() { - var origin = [ 0, 0 ], angle, precision = 6, rotate, interpolate; + var origin = [ 0, 0 ], angle, precision = 6, interpolate; function circle() { - var o = typeof origin === "function" ? origin.apply(this, arguments) : origin; - rotate = d3_geo_rotation(-o[0] * d3_radians, -o[1] * d3_radians, 0); - var ring = []; + var center = typeof origin === "function" ? origin.apply(this, arguments) : origin, rotate = d3_geo_rotation(-center[0] * d3_radians, -center[1] * d3_radians, 0).invert, ring = []; interpolate(null, null, 1, { - lineTo: function(λ, φ) { - var point = rotate.invert(λ, φ); - point[0] *= d3_degrees; - point[1] *= d3_degrees; - ring.push(point); + point: function(x, y) { + ring.push(x = rotate(x, y)); + x[0] *= d3_degrees, x[1] *= d3_degrees; } }); return { @@ -5537,84 +5687,9 @@ }; return circle.angle(90); }; - function d3_geo_circleClip(degrees, rotate) { - var radians = degrees * d3_radians, cr = Math.cos(radians), interpolate = d3_geo_circleInterpolate(radians, 6 * d3_radians); - return { - point: function(coordinates, context) { - if (visible(coordinates = rotate(coordinates))) { - context.point(coordinates[0], coordinates[1]); - } - }, - line: function(coordinates, context) { - clipLine(coordinates, context); - }, - polygon: function(polygon, context) { - d3_geo_circleClipPolygon(polygon, context, clipLine, interpolate); - }, - sphere: function(context) { - d3_geo_projectionSphere(context, interpolate); - } - }; - function visible(point) { - return Math.cos(point[1]) * Math.cos(point[0]) > cr; - } - function clipLine(coordinates, context, ring) { - if (!(n = coordinates.length)) return [ ring && 0, false ]; - var point0 = rotate(coordinates[0]), point1, point2, v0 = visible(point0), v00 = ring && v0, v, n, clean = ring, area = 0, p, x0, x, y0, y; - if (clean) { - x0 = (p = d3_geo_stereographic(point0[0] + (v0 ? 0 : π), point0[1]))[0]; - y0 = p[1]; - } - if (v0) context.moveTo(point0[0], point0[1]); - for (var i = 1; i < n; i++) { - point1 = rotate(coordinates[i]); - v = visible(point1); - if (v !== v0) { - point2 = intersect(point0, point1); - if (d3_geo_circlePointsEqual(point0, point2) || d3_geo_circlePointsEqual(point1, point2)) { - point1[0] += ε; - point1[1] += ε; - v = visible(point1); - } - } - if (v !== v0) { - clean = false; - if (v0 = v) { - point2 = intersect(point1, point0); - context.moveTo(point2[0], point2[1]); - } else { - point2 = intersect(point0, point1); - context.lineTo(point2[0], point2[1]); - } - point0 = point2; - } - if (clean) { - p = d3_geo_stereographic(point1[0] + (v ? 0 : π), point1[1]); - x = p[0]; - y = p[1]; - area += y0 * x - x0 * y; - x0 = x; - y0 = y; - } - if (v && !d3_geo_circlePointsEqual(point0, point1)) context.lineTo(point1[0], point1[1]); - point0 = point1; - } - return [ clean && area * .5, v00 && v ]; - } - function intersect(a, b) { - var pa = d3_geo_circleCartesian(a, [ 0, 0, 0 ]), pb = d3_geo_circleCartesian(b, [ 0, 0, 0 ]); - var n1 = [ 1, 0, 0 ], n2 = d3_geo_circleCross(pa, pb), n2n2 = d3_geo_circleDot(n2, n2), n1n2 = n2[0], determinant = n2n2 - n1n2 * n1n2; - if (!determinant) return a; - var c1 = cr * n2n2 / determinant, c2 = -cr * n1n2 / determinant, n1xn2 = d3_geo_circleCross(n1, n2), A = d3_geo_circleScale(n1, c1), B = d3_geo_circleScale(n2, c2); - d3_geo_circleAdd(A, B); - var u = n1xn2, w = d3_geo_circleDot(A, u), uu = d3_geo_circleDot(u, u), t = Math.sqrt(w * w - uu * (d3_geo_circleDot(A, A) - 1)), q = d3_geo_circleScale(u, (-w - t) / uu); - d3_geo_circleAdd(q, A); - return d3_geo_circleSpherical(q); - } - } function d3_geo_circleInterpolate(radians, precision) { var cr = Math.cos(radians), sr = Math.sin(radians); - return function(from, to, direction, context) { + return function(from, to, direction, listener) { if (from != null) { from = d3_geo_circleAngle(cr, from); to = d3_geo_circleAngle(cr, to); @@ -5623,38 +5698,110 @@ from = radians + direction * 2 * π; to = radians; } + var point; for (var step = direction * precision, t = from; direction > 0 ? t > to : t < to; t -= step) { - var c = Math.cos(t), s = Math.sin(t), point = d3_geo_circleSpherical([ cr, -sr * c, -sr * s ]); - context.lineTo(point[0], point[1]); + listener.point((point = d3_geo_spherical([ cr, -sr * Math.cos(t), -sr * Math.sin(t) ]))[0], point[1]); } }; } - function d3_geo_circleClipPolygon(coordinates, context, clipLine, interpolate) { - var subject = [], clip = [], segments = [], buffer = d3_geo_circleBufferSegments(clipLine), draw = [], visibleArea = 0, invisibleArea = 0, invisible = false; - coordinates.forEach(function(ring) { - var x = buffer(ring, context), ringSegments = x[1], segment, n = ringSegments.length; - if (!n) { - invisible = true; - invisibleArea += x[0][0]; - return; + function d3_geo_circleAngle(cr, point) { + var a = d3_geo_cartesian(point); + a[0] -= cr; + d3_geo_cartesianNormalize(a); + var angle = Math.acos(Math.max(-1, Math.min(1, -a[1]))); + return ((-a[2] < 0 ? -angle : angle) + 2 * Math.PI - ε) % (2 * Math.PI); + } + function d3_geo_clip(pointVisible, clipLine, interpolate) { + return function(listener) { + var line = clipLine(listener); + var clip = { + point: point, + lineStart: lineStart, + lineEnd: lineEnd, + polygonStart: function() { + clip.point = pointRing; + clip.lineStart = ringStart; + clip.lineEnd = ringEnd; + invisible = false; + invisibleArea = visibleArea = 0; + segments = []; + listener.polygonStart(); + }, + polygonEnd: function() { + clip.point = point; + clip.lineStart = lineStart; + clip.lineEnd = lineEnd; + segments = d3.merge(segments); + if (segments.length) { + d3_geo_clipPolygon(segments, interpolate, listener); + } else if (visibleArea < -ε || invisible && invisibleArea < -ε) { + listener.lineStart(); + interpolate(null, null, 1, listener); + listener.lineEnd(); + } + listener.polygonEnd(); + segments = null; + }, + sphere: function() { + listener.polygonStart(); + listener.lineStart(); + interpolate(null, null, 1, listener); + listener.lineEnd(); + listener.polygonEnd(); + } + }; + function point(λ, φ) { + if (pointVisible(λ, φ)) listener.point(λ, φ); } - if (x[0][0] !== false) { - visibleArea += x[0][0]; - draw.push(segment = ringSegments[0]); - var point = segment[0], n = segment.length - 1, i = 0; - context.moveTo(point[0], point[1]); - while (++i < n) context.lineTo((point = segment[i])[0], point[1]); - context.closePath(); - return; + function pointLine(λ, φ) { + line.point(λ, φ); } - if (n > 1 && x[0][1]) ringSegments.push(ringSegments.pop().concat(ringSegments.shift())); - segments = segments.concat(ringSegments.filter(d3_geo_circleSegmentLength1)); - }); - if (!segments.length) { - if (visibleArea < 0 || invisible && invisibleArea < 0) { - d3_geo_projectionSphere(context, interpolate); + function lineStart() { + clip.point = pointLine; + line.lineStart(); } - } + function lineEnd() { + clip.point = point; + line.lineEnd(); + } + var segments, visibleArea, invisibleArea, invisible; + var buffer = d3_geo_clipBufferListener(), ringListener = clipLine(buffer), ring; + function pointRing(λ, φ) { + ringListener.point(λ, φ); + ring.push([ λ, φ ]); + } + function ringStart() { + ringListener.lineStart(); + ring = []; + } + function ringEnd() { + pointRing(ring[0][0], ring[0][1]); + ringListener.lineEnd(); + var clean = ringListener.clean(), ringSegments = buffer.buffer(), segment, n = ringSegments.length; + if (!n) { + invisible = true; + invisibleArea += d3_geo_clipAreaRing(ring, -1); + ring = null; + return; + } + ring = null; + if (clean & 1) { + segment = ringSegments[0]; + visibleArea += d3_geo_clipAreaRing(segment, 1); + var n = segment.length - 1, i = -1, point; + listener.lineStart(); + while (++i < n) listener.point((point = segment[i])[0], point[1]); + listener.lineEnd(); + return; + } + if (n > 1 && clean & 2) ringSegments.push(ringSegments.pop().concat(ringSegments.shift())); + segments.push(ringSegments.filter(d3_geo_clipSegmentLength1)); + } + return clip; + }; + } + function d3_geo_clipPolygon(segments, interpolate, listener) { + var subject = [], clip = []; segments.forEach(function(segment) { var n = segment.length; if (n <= 1) return; @@ -5696,112 +5843,224 @@ subject.push(a); clip.push(b); }); - clip.sort(d3_geo_circleClipSort); - d3_geo_circleLinkCircular(subject); - d3_geo_circleLinkCircular(clip); + clip.sort(d3_geo_clipSort); + d3_geo_clipLinkCircular(subject); + d3_geo_clipLinkCircular(clip); if (!subject.length) return; var start = subject[0], current, points, point; while (1) { current = start; while (current.visited) if ((current = current.next) === start) return; points = current.points; - context.moveTo((point = points.shift())[0], point[1]); + listener.lineStart(); do { current.visited = current.other.visited = true; if (current.entry) { if (current.subject) { - for (var i = 0; i < points.length; i++) context.lineTo((point = points[i])[0], point[1]); + for (var i = 0; i < points.length; i++) listener.point((point = points[i])[0], point[1]); } else { - interpolate(current.point, current.next.point, 1, context); + interpolate(current.point, current.next.point, 1, listener); } current = current.next; } else { if (current.subject) { points = current.prev.points; - for (var i = points.length; --i >= 0; ) context.lineTo((point = points[i])[0], point[1]); + for (var i = points.length; --i >= 0; ) listener.point((point = points[i])[0], point[1]); } else { - interpolate(current.point, current.prev.point, -1, context); + interpolate(current.point, current.prev.point, -1, listener); } current = current.prev; } current = current.other; points = current.points; } while (!current.visited); - context.closePath(); + listener.lineEnd(); } } - function d3_geo_circleLinkCircular(array) { - for (var i = 0, a = array[0], b, n = array.length; i < n; ) { - a.next = b = array[++i % n]; + function d3_geo_clipLinkCircular(array) { + if (!(n = array.length)) return; + var n, i = 0, a = array[0], b; + while (++i < n) { + a.next = b = array[i]; b.prev = a; a = b; } + a.next = b = array[0]; + b.prev = a; } - function d3_geo_circleClipSort(a, b) { + function d3_geo_clipSort(a, b) { return ((a = a.point)[0] < 0 ? a[1] - π / 2 - ε : π / 2 - a[1]) - ((b = b.point)[0] < 0 ? b[1] - π / 2 - ε : π / 2 - b[1]); } - function d3_geo_circleAngle(cr, point) { - var a = d3_geo_circleCartesian(point, [ cr, 0, 0 ]); - d3_geo_circleNormalize(a); - var angle = Math.acos(Math.max(-1, Math.min(1, -a[1]))); - return ((-a[2] < 0 ? -angle : angle) + 2 * Math.PI - ε) % (2 * Math.PI); - } - function d3_geo_circleCartesian(point, origin) { - var p0 = point[0], p1 = point[1], c1 = Math.cos(p1); - return [ c1 * Math.cos(p0) - origin[0], c1 * Math.sin(p0) - origin[1], Math.sin(p1) - origin[2] ]; - } - function d3_geo_circleSpherical(point) { - return [ Math.atan2(point[1], point[0]), Math.asin(Math.max(-1, Math.min(1, point[2]))) ]; - } - function d3_geo_circleDot(a, b) { - return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; - } - function d3_geo_circleCross(a, b) { - return [ a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0] ]; - } - function d3_geo_circleAdd(a, b) { - a[0] += b[0]; - a[1] += b[1]; - a[2] += b[2]; - } - function d3_geo_circleScale(vector, s) { - return [ vector[0] * s, vector[1] * s, vector[2] * s ]; - } - function d3_geo_circleNormalize(d) { - var l = Math.sqrt(d[0] * d[0] + d[1] * d[1] + d[2] * d[2]); - d[0] /= l; - d[1] /= l; - d[2] /= l; - } - function d3_geo_circleBufferSegments(f) { - return function(coordinates) { - var segments = [], segment; - return [ f(coordinates, { - moveTo: function(x, y) { - segments.push(segment = [ [ x, y ] ]); - }, - lineTo: function(x, y) { - segment.push([ x, y ]); - } - }, true), segments ]; - }; - } - function d3_geo_circlePointsEqual(a, b) { - return Math.abs(a[0] - b[0]) < ε && Math.abs(a[1] - b[1]) < ε; - } - function d3_geo_circleSegmentLength1(segment) { + function d3_geo_clipSegmentLength1(segment) { return segment.length > 1; } + function d3_geo_clipBufferListener() { + var lines = [], line; + return { + lineStart: function() { + lines.push(line = []); + }, + point: function(λ, φ) { + line.push([ λ, φ ]); + }, + lineEnd: d3_noop, + buffer: function() { + var buffer = lines; + lines = []; + line = null; + return buffer; + } + }; + } + function d3_geo_clipAreaRing(ring, invisible) { + if (!(n = ring.length)) return 0; + var n, i = 0, area = 0, p = ring[0], λ = p[0], φ = p[1], cosφ = Math.cos(φ), x0 = Math.atan2(invisible * Math.sin(λ) * cosφ, Math.sin(φ)), y0 = 1 - invisible * Math.cos(λ) * cosφ, x1 = x0, x, y; + while (++i < n) { + p = ring[i]; + cosφ = Math.cos(φ = p[1]); + x = Math.atan2(invisible * Math.sin(λ = p[0]) * cosφ, Math.sin(φ)); + y = 1 - invisible * Math.cos(λ) * cosφ; + if (Math.abs(y0 - 2) < ε && Math.abs(y - 2) < ε) continue; + if (Math.abs(y) < ε || Math.abs(y0) < ε) {} else if (Math.abs(Math.abs(x - x0) - π) < ε) { + if (y + y0 > 2) area += 4 * (x - x0); + } else if (Math.abs(y0 - 2) < ε) area += 4 * (x - x1); else area += ((3 * π + x - x0) % (2 * π) - π) * (y0 + y); + x1 = x0, x0 = x, y0 = y; + } + return area; + } + var d3_geo_clipAntimeridian = d3_geo_clip(d3_true, d3_geo_clipAntimeridianLine, d3_geo_clipAntimeridianInterpolate); + function d3_geo_clipAntimeridianLine(listener) { + var λ0 = NaN, φ0 = NaN, sλ0 = NaN, clean; + return { + lineStart: function() { + listener.lineStart(); + clean = 1; + }, + point: function(λ1, φ1) { + var sλ1 = λ1 > 0 ? π : -π, dλ = Math.abs(λ1 - λ0); + if (Math.abs(dλ - π) < ε) { + listener.point(λ0, φ0 = (φ0 + φ1) / 2 > 0 ? π / 2 : -π / 2); + listener.point(sλ0, φ0); + listener.lineEnd(); + listener.lineStart(); + listener.point(sλ1, φ0); + listener.point(λ1, φ0); + clean = 0; + } else if (sλ0 !== sλ1 && dλ >= π) { + if (Math.abs(λ0 - sλ0) < ε) λ0 -= sλ0 * ε; + if (Math.abs(λ1 - sλ1) < ε) λ1 -= sλ1 * ε; + φ0 = d3_geo_clipAntimeridianIntersect(λ0, φ0, λ1, φ1); + listener.point(sλ0, φ0); + listener.lineEnd(); + listener.lineStart(); + listener.point(sλ1, φ0); + clean = 0; + } + listener.point(λ0 = λ1, φ0 = φ1); + sλ0 = sλ1; + }, + lineEnd: function() { + listener.lineEnd(); + λ0 = φ0 = NaN; + }, + clean: function() { + return 2 - clean; + } + }; + } + function d3_geo_clipAntimeridianIntersect(λ0, φ0, λ1, φ1) { + var cosφ0, cosφ1, sinλ0_λ1 = Math.sin(λ0 - λ1); + return Math.abs(sinλ0_λ1) > ε ? Math.atan((Math.sin(φ0) * (cosφ1 = Math.cos(φ1)) * Math.sin(λ1) - Math.sin(φ1) * (cosφ0 = Math.cos(φ0)) * Math.sin(λ0)) / (cosφ0 * cosφ1 * sinλ0_λ1)) : (φ0 + φ1) / 2; + } + function d3_geo_clipAntimeridianInterpolate(from, to, direction, listener) { + var φ; + if (from == null) { + φ = direction * π / 2; + listener.point(-π, φ); + listener.point(0, φ); + listener.point(π, φ); + listener.point(π, 0); + listener.point(π, -φ); + listener.point(0, -φ); + listener.point(-π, -φ); + listener.point(-π, 0); + listener.point(-π, φ); + } else if (Math.abs(from[0] - to[0]) > ε) { + var s = (from[0] < to[0] ? 1 : -1) * π; + φ = direction * s / 2; + listener.point(-s, φ); + listener.point(0, φ); + listener.point(s, φ); + } else { + listener.point(to[0], to[1]); + } + } + function d3_geo_clipCircle(degrees) { + var radians = degrees * d3_radians, cr = Math.cos(radians), interpolate = d3_geo_circleInterpolate(radians, 6 * d3_radians); + return d3_geo_clip(visible, clipLine, interpolate); + function visible(λ, φ) { + return Math.cos(λ) * Math.cos(φ) > cr; + } + function clipLine(listener) { + var point0, v0, v00, clean; + return { + lineStart: function() { + v00 = v0 = false; + clean = 1; + }, + point: function(λ, φ) { + var point1 = [ λ, φ ], point2, v = visible(λ, φ); + if (!point0 && (v00 = v0 = v)) listener.lineStart(); + if (v !== v0) { + point2 = intersect(point0, point1); + if (d3_geo_sphericalEqual(point0, point2) || d3_geo_sphericalEqual(point1, point2)) { + point1[0] += ε; + point1[1] += ε; + v = visible(point1[0], point1[1]); + } + } + if (v !== v0) { + clean = 0; + if (v0 = v) { + listener.lineStart(); + point2 = intersect(point1, point0); + listener.point(point2[0], point2[1]); + } else { + point2 = intersect(point0, point1); + listener.point(point2[0], point2[1]); + listener.lineEnd(); + } + point0 = point2; + } + if (v && (!point0 || !d3_geo_sphericalEqual(point0, point1))) listener.point(point1[0], point1[1]); + point0 = point1; + }, + lineEnd: function() { + if (v0) listener.lineEnd(); + point0 = null; + }, + clean: function() { + return clean | (v00 && v0) << 1; + } + }; + } + function intersect(a, b) { + var pa = d3_geo_cartesian(a, 0), pb = d3_geo_cartesian(b, 0); + var n1 = [ 1, 0, 0 ], n2 = d3_geo_cartesianCross(pa, pb), n2n2 = d3_geo_cartesianDot(n2, n2), n1n2 = n2[0], determinant = n2n2 - n1n2 * n1n2; + if (!determinant) return a; + var c1 = cr * n2n2 / determinant, c2 = -cr * n1n2 / determinant, n1xn2 = d3_geo_cartesianCross(n1, n2), A = d3_geo_cartesianScale(n1, c1), B = d3_geo_cartesianScale(n2, c2); + d3_geo_cartesianAdd(A, B); + var u = n1xn2, w = d3_geo_cartesianDot(A, u), uu = d3_geo_cartesianDot(u, u), t = Math.sqrt(w * w - uu * (d3_geo_cartesianDot(A, A) - 1)), q = d3_geo_cartesianScale(u, (-w - t) / uu); + d3_geo_cartesianAdd(q, A); + return d3_geo_spherical(q); + } + } function d3_geo_compose(a, b) { - if (a === d3_geo_equirectangular) return b; - if (b === d3_geo_equirectangular) return a; - function compose(λ, φ) { - var coordinates = a(λ, φ); - return b(coordinates[0], coordinates[1]); + function compose(x, y) { + return x = a(x, y), b(x[0], x[1]); } if (a.invert && b.invert) compose.invert = function(x, y) { - var coordinates = b.invert(x, y); - return a.invert(coordinates[0], coordinates[1]); + return x = b.invert(x, y), x && a.invert(x[0], x[1]); }; return compose; } @@ -5821,12 +6080,15 @@ var x1, x0, y1, y0, dx = 22.5, dy = dx, x, y, precision = 2.5; function graticule() { return { - type: "GeometryCollection", - geometries: graticule.lines() + type: "MultiLineString", + coordinates: lines() }; } + function lines() { + return d3.range(Math.ceil(x0 / dx) * dx, x1, dx).map(x).concat(d3.range(Math.ceil(y0 / dy) * dy, y1, dy).map(y)); + } graticule.lines = function() { - return d3.range(Math.ceil(x0 / dx) * dx, x1, dx).map(x).concat(d3.range(Math.ceil(y0 / dy) * dy, y1, dy).map(y)).map(function(coordinates) { + return lines().map(function(coordinates) { return { type: "LineString", coordinates: coordinates @@ -5877,11 +6139,23 @@ }); }; } + d3.geo.interpolate = function(source, target) { + return d3_geo_interpolate(source[0] * d3_radians, source[1] * d3_radians, target[0] * d3_radians, target[1] * d3_radians); + }; + function d3_geo_interpolate(x0, y0, x1, y1) { + var cy0 = Math.cos(y0), sy0 = Math.sin(y0), cy1 = Math.cos(y1), sy1 = Math.sin(y1), kx0 = cy0 * Math.cos(x0), ky0 = cy0 * Math.sin(x0), kx1 = cy1 * Math.cos(x1), ky1 = cy1 * Math.sin(x1), d = Math.acos(Math.max(-1, Math.min(1, sy0 * sy1 + cy0 * cy1 * Math.cos(x1 - x0)))), k = 1 / Math.sin(d); + function interpolate(t) { + var B = Math.sin(t *= d) * k, A = Math.sin(d - t) * k, x = A * kx0 + B * kx1, y = A * ky0 + B * ky1, z = A * sy0 + B * sy1; + return [ Math.atan2(y, x) / d3_radians, Math.atan2(z, Math.sqrt(x * x + y * y)) / d3_radians ]; + } + interpolate.distance = d; + return interpolate; + } d3.geo.greatArc = function() { - var source = d3_source, p0, target = d3_target, p1, precision = 6 * d3_radians, interpolate = d3_geo_greatArcInterpolator(); + var source = d3_source, source_, target = d3_target, target_, precision = 6 * d3_radians, interpolate; function greatArc() { - var d = greatArc.distance.apply(this, arguments), t = 0, dt = precision / d, coordinates = [ p0 ]; - while ((t += dt) < 1) coordinates.push(interpolate(t)); + var p0 = source_ || source.apply(this, arguments), p1 = target_ || target.apply(this, arguments), i = interpolate || d3.geo.interpolate(p0, p1), t = 0, dt = precision / i.distance, coordinates = [ p0 ]; + while ((t += dt) < 1) coordinates.push(i(t)); coordinates.push(p1); return { type: "LineString", @@ -5889,20 +6163,18 @@ }; } greatArc.distance = function() { - if (typeof source === "function") interpolate.source(p0 = source.apply(this, arguments)); - if (typeof target === "function") interpolate.target(p1 = target.apply(this, arguments)); - return interpolate.distance(); + return (interpolate || d3.geo.interpolate(source_ || source.apply(this, arguments), target_ || target.apply(this, arguments))).distance; }; greatArc.source = function(_) { if (!arguments.length) return source; - source = _; - if (typeof source !== "function") interpolate.source(p0 = source); + source = _, source_ = typeof _ === "function" ? null : _; + interpolate = source_ && target_ ? d3.geo.interpolate(source_, target_) : null; return greatArc; }; greatArc.target = function(_) { if (!arguments.length) return target; - target = _; - if (typeof target !== "function") interpolate.target(p1 = target); + target = _, target_ = typeof _ === "function" ? null : _; + interpolate = source_ && target_ ? d3.geo.interpolate(source_, target_) : null; return greatArc; }; greatArc.precision = function(_) { @@ -5912,36 +6184,6 @@ }; return greatArc; }; - function d3_geo_greatArcInterpolator() { - var x0, y0, cy0, sy0, kx0, ky0, x1, y1, cy1, sy1, kx1, ky1, d, k; - function interpolate(t) { - var B = Math.sin(t *= d) * k, A = Math.sin(d - t) * k, x = A * kx0 + B * kx1, y = A * ky0 + B * ky1, z = A * sy0 + B * sy1; - return [ Math.atan2(y, x) / d3_radians, Math.atan2(z, Math.sqrt(x * x + y * y)) / d3_radians ]; - } - interpolate.distance = function() { - if (d == null) k = 1 / Math.sin(d = Math.acos(Math.max(-1, Math.min(1, sy0 * sy1 + cy0 * cy1 * Math.cos(x1 - x0))))); - return d; - }; - interpolate.source = function(_) { - var cx0 = Math.cos(x0 = _[0] * d3_radians), sx0 = Math.sin(x0); - cy0 = Math.cos(y0 = _[1] * d3_radians); - sy0 = Math.sin(y0); - kx0 = cy0 * cx0; - ky0 = cy0 * sx0; - d = null; - return interpolate; - }; - interpolate.target = function(_) { - var cx1 = Math.cos(x1 = _[0] * d3_radians), sx1 = Math.sin(x1); - cy1 = Math.cos(y1 = _[1] * d3_radians); - sy1 = Math.sin(y1); - kx1 = cy1 * cx1; - ky1 = cy1 * sx1; - d = null; - return interpolate; - }; - return interpolate; - } function d3_geo_mercator(λ, φ) { return [ λ / (2 * π), Math.max(-.5, Math.min(+.5, Math.log(Math.tan(π / 4 + φ / 2)) / (2 * π))) ]; } @@ -5958,203 +6200,290 @@ return d3_geo_projection(d3_geo_orthographic); }).raw = d3_geo_orthographic; d3.geo.path = function() { - var pointRadius = 4.5, pointCircle = d3_geo_pathCircle(pointRadius), projection = d3.geo.albersUsa(), bounds, buffer = []; - var bufferContext = { - point: function(x, y) { - buffer.push("M", x, ",", y, pointCircle); - }, - moveTo: function(x, y) { - buffer.push("M", x, ",", y); - }, - lineTo: function(x, y) { - buffer.push("L", x, ",", y); - }, - closePath: function() { - buffer.push("Z"); - } - }; - var area, centroidWeight, x00, y00, x0, y0, cx, cy; - var areaContext = { - point: d3_noop, - moveTo: moveTo, - lineTo: function(x, y) { - area += y0 * x - x0 * y; - x0 = x; - y0 = y; - }, - closePath: closePath - }; - var lineCentroidContext = { - point: function(x, y) { - cx += x; - cy += y; - ++centroidWeight; - }, - moveTo: moveTo, - lineTo: function(x, y) { - var dx = x - x0, dy = y - y0, δ = Math.sqrt(dx * dx + dy * dy); - centroidWeight += δ; - cx += δ * (x0 + x) / 2; - cy += δ * (y0 + y) / 2; - x0 = x; - y0 = y; - }, - closePath: closePath - }; - var polygonCentroidContext = { - point: d3_noop, - moveTo: moveTo, - lineTo: function(x, y) { - var δ = y0 * x - x0 * y; - centroidWeight += δ * 3; - cx += δ * (x0 + x); - cy += δ * (y0 + y); - x0 = x; - y0 = y; - }, - closePath: closePath - }; - function moveTo(x, y) { - x00 = x0 = x; - y00 = y0 = y; - } - function closePath() { - this.lineTo(x00, y00); - } - var context = bufferContext; + var pointRadius = 4.5, projection, context, projectStream, contextStream; function path(object) { - var result = null; - if (object != result) { - if (typeof pointRadius === "function") pointCircle = d3_geo_pathCircle(pointRadius.apply(this, arguments)); - pathType.object(object); - if (buffer.length) result = buffer.join(""), buffer = []; - } - return result; - } - var pathType = d3_geo_type({ - line: function(coordinates) { - projection.line(coordinates, context); - }, - polygon: function(coordinates) { - projection.polygon(coordinates, context); - }, - point: function(coordinates) { - projection.point(coordinates, context); - }, - Sphere: function() { - projection.sphere(context); - } - }); - var areaType = d3_geo_type({ - Feature: function(feature) { - return areaType.geometry(feature.geometry); - }, - FeatureCollection: function(collection) { - return d3.sum(collection.features, areaType.Feature); - }, - GeometryCollection: function(collection) { - return d3.sum(collection.geometries, areaType.geometry); - }, - LineString: d3_zero, - MultiLineString: d3_zero, - MultiPoint: d3_zero, - MultiPolygon: function(multiPolygon) { - return d3.sum(multiPolygon.coordinates, polygonArea); - }, - Point: d3_zero, - Polygon: function(polygon) { - return polygonArea(polygon.coordinates); - }, - Sphere: sphereArea - }); - function polygonArea(coordinates) { - area = 0; - projection.polygon(coordinates, areaContext); - return Math.abs(area) / 2; - } - function sphereArea() { - area = 0; - projection.sphere(areaContext); - return Math.abs(area) / 2; + if (object) d3.geo.stream(object, projectStream(contextStream.pointRadius(typeof pointRadius === "function" ? +pointRadius.apply(this, arguments) : pointRadius))); + return contextStream.result(); } path.area = function(object) { - return areaType.object(object); - }; - var centroidType = d3_geo_type({ - Feature: function(feature) { - return centroidType.geometry(feature.geometry); - }, - LineString: weightedCentroid(function(lineString) { - projection.line(lineString.coordinates, lineCentroidContext); - }), - MultiLineString: weightedCentroid(function(multiLineString) { - var coordinates = multiLineString.coordinates, i = -1, n = coordinates.length; - while (++i < n) projection.line(coordinates[i], lineCentroidContext); - }), - MultiPoint: weightedCentroid(function(multiPoint) { - var coordinates = multiPoint.coordinates, i = -1, n = coordinates.length; - while (++i < n) projection.point(coordinates[i], lineCentroidContext); - }), - MultiPolygon: weightedCentroid(function(multiPolygon) { - var coordinates = multiPolygon.coordinates, i = -1, n = coordinates.length; - while (++i < n) projection.polygon(coordinates[i], polygonCentroidContext); - }), - Point: weightedCentroid(function(point) { - projection.point(point.coordinates, lineCentroidContext); - }), - Polygon: weightedCentroid(function(polygon) { - projection.polygon(polygon.coordinates, polygonCentroidContext); - }), - Sphere: weightedCentroid(function() { - projection.sphere(polygonCentroidContext); - }) - }); - function weightedCentroid(f) { - return function() { - centroidWeight = cx = cy = 0; - f.apply(this, arguments); - return centroidWeight ? [ cx / centroidWeight, cy / centroidWeight ] : null; - }; - } - path.bounds = function(object) { - return (bounds || (bounds = d3_geo_bounds(projection)))(object); + d3_geo_pathAreaSum = 0; + d3.geo.stream(object, projectStream(d3_geo_pathArea)); + return d3_geo_pathAreaSum; }; path.centroid = function(object) { - return centroidType.object(object); + d3_geo_centroidDimension = d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0; + d3.geo.stream(object, projectStream(d3_geo_pathCentroid)); + return d3_geo_centroidZ ? [ d3_geo_centroidX / d3_geo_centroidZ, d3_geo_centroidY / d3_geo_centroidZ ] : undefined; + }; + path.bounds = function(object) { + return d3_geo_bounds(projectStream)(object); }; path.projection = function(_) { if (!arguments.length) return projection; - projection = _; - bounds = null; + projectStream = (projection = _) ? _.stream || d3_geo_pathProjectStream(_) : d3_identity; return path; }; path.context = function(_) { - if (!arguments.length) return context === bufferContext ? null : context; - context = _; - if (context == null) context = bufferContext; + if (!arguments.length) return context; + contextStream = (context = _) == null ? new d3_geo_pathBuffer() : new d3_geo_pathContext(_); return path; }; - path.pointRadius = function(x) { + path.pointRadius = function(_) { if (!arguments.length) return pointRadius; - if (typeof x === "function") pointRadius = x; else pointCircle = d3_geo_pathCircle(pointRadius = +x); + pointRadius = typeof _ === "function" ? _ : +_; return path; }; - return path; + return path.projection(d3.geo.albersUsa()).context(null); }; function d3_geo_pathCircle(radius) { return "m0," + radius + "a" + radius + "," + radius + " 0 1,1 0," + -2 * radius + "a" + radius + "," + radius + " 0 1,1 0," + +2 * radius + "z"; } - var d3_geo_pathIdentity = d3.geo.path().projection({ - polygon: function(polygon, context) { - polygon.forEach(function(ring) { - var n = ring.length, i = 0, point; - context.moveTo((point = ring[0])[0], point[1]); - while (++i < n) context.lineTo((point = ring[i])[0], point[1]); - context.closePath(); - }); + function d3_geo_pathProjectStream(project) { + var resample = d3_geo_resample(function(λ, φ) { + return project([ λ * d3_degrees, φ * d3_degrees ]); + }); + return function(stream) { + stream = resample(stream); + return { + point: function(λ, φ) { + stream.point(λ * d3_radians, φ * d3_radians); + }, + sphere: function() { + stream.sphere(); + }, + lineStart: function() { + stream.lineStart(); + }, + lineEnd: function() { + stream.lineEnd(); + }, + polygonStart: function() { + stream.polygonStart(); + }, + polygonEnd: function() { + stream.polygonEnd(); + } + }; + }; + } + function d3_geo_pathBuffer() { + var pointCircle = d3_geo_pathCircle(4.5), buffer = []; + var stream = { + point: point, + lineStart: function() { + stream.point = pointLineStart; + }, + lineEnd: lineEnd, + polygonStart: function() { + stream.lineEnd = lineEndPolygon; + }, + polygonEnd: function() { + stream.lineEnd = lineEnd; + stream.point = point; + }, + pointRadius: function(_) { + pointCircle = d3_geo_pathCircle(_); + return stream; + }, + result: function() { + if (buffer.length) { + var result = buffer.join(""); + buffer = []; + return result; + } + } + }; + function point(x, y) { + buffer.push("M", x, ",", y, pointCircle); } - }); - d3.geo.centroid = d3_geo_pathIdentity.centroid; + function pointLineStart(x, y) { + buffer.push("M", x, ",", y); + stream.point = pointLine; + } + function pointLine(x, y) { + buffer.push("L", x, ",", y); + } + function lineEnd() { + stream.point = point; + } + function lineEndPolygon() { + buffer.push("Z"); + } + return stream; + } + function d3_geo_pathContext(context) { + var pointRadius = 4.5; + var stream = { + point: point, + lineStart: function() { + stream.point = pointLineStart; + }, + lineEnd: lineEnd, + polygonStart: function() { + stream.lineEnd = lineEndPolygon; + }, + polygonEnd: function() { + stream.lineEnd = lineEnd; + stream.point = point; + }, + pointRadius: function(_) { + pointRadius = _; + return stream; + }, + result: d3_noop + }; + function point(x, y) { + context.moveTo(x, y); + context.arc(x, y, pointRadius, 0, 2 * π); + } + function pointLineStart(x, y) { + context.moveTo(x, y); + stream.point = pointLine; + } + function pointLine(x, y) { + context.lineTo(x, y); + } + function lineEnd() { + stream.point = point; + } + function lineEndPolygon() { + context.closePath(); + } + return stream; + } + var d3_geo_pathAreaSum, d3_geo_pathAreaPolygon, d3_geo_pathArea = { + point: d3_noop, + lineStart: d3_noop, + lineEnd: d3_noop, + polygonStart: function() { + d3_geo_pathAreaPolygon = 0; + d3_geo_pathArea.lineStart = d3_geo_pathAreaRingStart; + }, + polygonEnd: function() { + d3_geo_pathArea.lineStart = d3_geo_pathArea.lineEnd = d3_geo_pathArea.point = d3_noop; + d3_geo_pathAreaSum += Math.abs(d3_geo_pathAreaPolygon / 2); + } + }; + function d3_geo_pathAreaRingStart() { + var x00, y00, x0, y0; + d3_geo_pathArea.point = function(x, y) { + d3_geo_pathArea.point = nextPoint; + x00 = x0 = x, y00 = y0 = y; + }; + function nextPoint(x, y) { + d3_geo_pathAreaPolygon += y0 * x - x0 * y; + x0 = x, y0 = y; + } + d3_geo_pathArea.lineEnd = function() { + nextPoint(x00, y00); + }; + } + var d3_geo_pathCentroid = { + point: d3_geo_pathCentroidPoint, + lineStart: d3_geo_pathCentroidLineStart, + lineEnd: d3_geo_pathCentroidLineEnd, + polygonStart: function() { + d3_geo_pathCentroid.lineStart = d3_geo_pathCentroidRingStart; + }, + polygonEnd: function() { + d3_geo_pathCentroid.point = d3_geo_pathCentroidPoint; + d3_geo_pathCentroid.lineStart = d3_geo_pathCentroidLineStart; + d3_geo_pathCentroid.lineEnd = d3_geo_pathCentroidLineEnd; + } + }; + function d3_geo_pathCentroidPoint(x, y) { + if (d3_geo_centroidDimension) return; + d3_geo_centroidX += x; + d3_geo_centroidY += y; + ++d3_geo_centroidZ; + } + function d3_geo_pathCentroidLineStart() { + var x0, y0; + if (d3_geo_centroidDimension !== 1) { + if (d3_geo_centroidDimension < 1) { + d3_geo_centroidDimension = 1; + d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0; + } else return; + } + d3_geo_pathCentroid.point = function(x, y) { + d3_geo_pathCentroid.point = nextPoint; + x0 = x, y0 = y; + }; + function nextPoint(x, y) { + var dx = x - x0, dy = y - y0, z = Math.sqrt(dx * dx + dy * dy); + d3_geo_centroidX += z * (x0 + x) / 2; + d3_geo_centroidY += z * (y0 + y) / 2; + d3_geo_centroidZ += z; + x0 = x, y0 = y; + } + } + function d3_geo_pathCentroidLineEnd() { + d3_geo_pathCentroid.point = d3_geo_pathCentroidPoint; + } + function d3_geo_pathCentroidRingStart() { + var x00, y00, x0, y0; + if (d3_geo_centroidDimension < 2) { + d3_geo_centroidDimension = 2; + d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0; + } + d3_geo_pathCentroid.point = function(x, y) { + d3_geo_pathCentroid.point = nextPoint; + x00 = x0 = x, y00 = y0 = y; + }; + function nextPoint(x, y) { + var z = y0 * x - x0 * y; + d3_geo_centroidX += z * (x0 + x); + d3_geo_centroidY += z * (y0 + y); + d3_geo_centroidZ += z * 3; + x0 = x, y0 = y; + } + d3_geo_pathCentroid.lineEnd = function() { + nextPoint(x00, y00); + }; + } + d3.geo.area = function(object) { + d3_geo_areaSum = 0; + d3.geo.stream(object, d3_geo_area); + return d3_geo_areaSum; + }; + var d3_geo_areaSum, d3_geo_areaRing; + var d3_geo_area = { + sphere: function() { + d3_geo_areaSum += 4 * π; + }, + point: d3_noop, + lineStart: d3_noop, + lineEnd: d3_noop, + polygonStart: function() { + d3_geo_areaRing = 0; + d3_geo_area.lineStart = d3_geo_areaRingStart; + }, + polygonEnd: function() { + d3_geo_areaSum += d3_geo_areaRing < 0 ? 4 * π + d3_geo_areaRing : d3_geo_areaRing; + d3_geo_area.lineStart = d3_geo_area.lineEnd = d3_geo_area.point = d3_noop; + } + }; + function d3_geo_areaRingStart() { + var λ00, φ00, λ1, λ0, φ0, cosφ0, sinφ0; + d3_geo_area.point = function(λ, φ) { + d3_geo_area.point = nextPoint; + λ1 = λ0 = (λ00 = λ) * d3_radians, φ0 = (φ00 = φ) * d3_radians, cosφ0 = Math.cos(φ0), + sinφ0 = Math.sin(φ0); + }; + function nextPoint(λ, φ) { + λ *= d3_radians, φ *= d3_radians; + if (Math.abs(Math.abs(φ0) - π / 2) < ε && Math.abs(Math.abs(φ) - π / 2) < ε) return; + var cosφ = Math.cos(φ), sinφ = Math.sin(φ); + if (Math.abs(φ0 - π / 2) < ε) d3_geo_areaRing += (λ - λ1) * 2; else { + var dλ = λ - λ0, cosdλ = Math.cos(dλ), d = Math.atan2(Math.sqrt((d = cosφ * Math.sin(dλ)) * d + (d = cosφ0 * sinφ - sinφ0 * cosφ * cosdλ) * d), sinφ0 * sinφ + cosφ0 * cosφ * cosdλ), s = (d + π + φ0 + φ) / 4; + d3_geo_areaRing += (dλ < 0 && dλ > -π || dλ > π ? -4 : 4) * Math.atan(Math.sqrt(Math.abs(Math.tan(s) * Math.tan(s - d / 2) * Math.tan(s - π / 4 - φ0 / 2) * Math.tan(s - π / 4 - φ / 2)))); + } + λ1 = λ0, λ0 = λ, φ0 = φ, cosφ0 = cosφ, sinφ0 = sinφ; + } + d3_geo_area.lineEnd = function() { + nextPoint(λ00, φ00); + }; + } d3.geo.projection = d3_geo_projection; d3.geo.projectionMutator = d3_geo_projectionMutator; function d3_geo_projection(project) { @@ -6163,86 +6492,26 @@ })(); } function d3_geo_projectionMutator(projectAt) { - var project, rotate, projectRotate, k = 150, x = 480, y = 250, λ = 0, φ = 0, δλ = 0, δφ = 0, δγ = 0, δx = x, δy = y, δ2 = .5, clip = d3_geo_projectionCutAntemeridian(rotatePoint), clipAngle = null, context; - function projection(coordinates) { - coordinates = projectRotate(coordinates[0] * d3_radians, coordinates[1] * d3_radians); - return [ coordinates[0] * k + δx, δy - coordinates[1] * k ]; + var project, rotate, projectRotate, projectResample = d3_geo_resample(function(x, y) { + x = project(x, y); + return [ x[0] * k + δx, δy - x[1] * k ]; + }), k = 150, x = 480, y = 250, λ = 0, φ = 0, δλ = 0, δφ = 0, δγ = 0, δx, δy, clip = d3_geo_clipAntimeridian, clipAngle = null; + function projection(point) { + point = projectRotate(point[0] * d3_radians, point[1] * d3_radians); + return [ point[0] * k + δx, δy - point[1] * k ]; } - function invert(coordinates) { - coordinates = projectRotate.invert((coordinates[0] - δx) / k, (δy - coordinates[1]) / k); - return [ coordinates[0] * d3_degrees, coordinates[1] * d3_degrees ]; + function invert(point) { + point = projectRotate.invert((point[0] - δx) / k, (δy - point[1]) / k); + return point && [ point[0] * d3_degrees, point[1] * d3_degrees ]; } - projection.point = function(coordinates, c) { - context = c; - clip.point(coordinates, resample); - context = null; - }; - projection.line = function(coordinates, c) { - context = c; - clip.line(coordinates, resample); - context = null; - }; - projection.polygon = function(coordinates, c) { - context = c; - clip.polygon(coordinates, resample); - context = null; - }; - projection.sphere = function(c) { - context = c; - clip.sphere(resample); - context = null; + projection.stream = function(stream) { + return d3_geo_projectionRadiansRotate(rotate, clip(projectResample(stream))); }; projection.clipAngle = function(_) { if (!arguments.length) return clipAngle; - clip = _ == null ? (clipAngle = _, d3_geo_projectionCutAntemeridian(rotatePoint)) : d3_geo_circleClip(clipAngle = +_, rotatePoint); + clip = _ == null ? (clipAngle = _, d3_geo_clipAntimeridian) : d3_geo_clipCircle(clipAngle = +_); return projection; }; - var λ00, φ00, λ0, sinφ0, cosφ0, x0, y0, maxDepth = 16; - function point(λ, φ) { - var p = projectPoint(λ, φ); - context.point(p[0], p[1]); - } - function moveTo(λ, φ) { - var p = projectPoint(λ00 = λ0 = λ, φ00 = φ); - sinφ0 = Math.sin(φ); - cosφ0 = Math.cos(φ); - context.moveTo(x0 = p[0], y0 = p[1]); - } - function lineTo(λ, φ) { - var p = projectPoint(λ, φ); - resampleLineTo(x0, y0, λ0, sinφ0, cosφ0, x0 = p[0], y0 = p[1], λ0 = λ, sinφ0 = Math.sin(φ), cosφ0 = Math.cos(φ), maxDepth); - context.lineTo(x0, y0); - } - function resampleLineTo(x0, y0, λ0, sinφ0, cosφ0, x1, y1, λ1, sinφ1, cosφ1, depth) { - var dx = x1 - x0, dy = y1 - y0, distance2 = dx * dx + dy * dy; - if (distance2 > 4 * δ2 && depth--) { - var cosΩ = sinφ0 * sinφ1 + cosφ0 * cosφ1 * Math.cos(λ1 - λ0), k = 1 / (Math.SQRT2 * Math.sqrt(1 + cosΩ)), x = k * (cosφ0 * Math.cos(λ0) + cosφ1 * Math.cos(λ1)), y = k * (cosφ0 * Math.sin(λ0) + cosφ1 * Math.sin(λ1)), z = Math.max(-1, Math.min(1, k * (sinφ0 + sinφ1))), φ2 = Math.asin(z), zε = Math.abs(Math.abs(z) - 1), λ2 = zε < ε || zε < εε && (Math.abs(cosφ0) < εε || Math.abs(cosφ1) < εε) ? (λ0 + λ1) / 2 : Math.atan2(y, x), p = projectPoint(λ2, φ2), x2 = p[0], y2 = p[1], dx2 = x0 - x2, dy2 = y0 - y2, dz = dx * dy2 - dy * dx2; - if (dz * dz / distance2 > δ2) { - var cosφ2 = Math.cos(φ2); - resampleLineTo(x0, y0, λ0, sinφ0, cosφ0, x2, y2, λ2, z, cosφ2, depth); - context.lineTo(x2, y2); - resampleLineTo(x2, y2, λ2, z, cosφ2, x1, y1, λ1, sinφ1, cosφ1, depth); - } - } - } - function closePath() { - var p = projectPoint(λ00, φ00); - resampleLineTo(x0, y0, λ0, sinφ0, cosφ0, p[0], p[1], λ00, Math.sin(φ00), Math.cos(φ00), maxDepth); - context.closePath(); - } - var resample = { - point: point, - moveTo: moveTo, - lineTo: lineTo, - closePath: closePath - }; - function rotatePoint(coordinates) { - return rotate(coordinates[0] * d3_radians, coordinates[1] * d3_radians); - } - function projectPoint(λ, φ) { - var point = project(λ, φ); - return [ point[0] * k + δx, δy - point[1] * k ]; - } projection.scale = function(_) { if (!arguments.length) return k; k = +_; @@ -6267,11 +6536,7 @@ δγ = _.length > 2 ? _[2] % 360 * d3_radians : 0; return reset(); }; - projection.precision = function(_) { - if (!arguments.length) return Math.sqrt(δ2); - maxDepth = (δ2 = _ * _) > 0 && 16; - return projection; - }; + d3.rebind(projection, projectResample, "precision"); function reset() { projectRotate = d3_geo_compose(rotate = d3_geo_rotation(δλ, δφ, δγ), project); var center = project(λ, φ); @@ -6285,104 +6550,35 @@ return reset(); }; } - function d3_geo_projectionIntersectAntemeridian(λ0, φ0, λ1, φ1) { - var cosφ0, cosφ1, sinλ0_λ1 = Math.sin(λ0 - λ1); - return Math.abs(sinλ0_λ1) > ε ? Math.atan((Math.sin(φ0) * (cosφ1 = Math.cos(φ1)) * Math.sin(λ1) - Math.sin(φ1) * (cosφ0 = Math.cos(φ0)) * Math.sin(λ0)) / (cosφ0 * cosφ1 * sinλ0_λ1)) : (φ0 + φ1) / 2; - } - function d3_geo_projectionCutAntemeridian(rotatePoint) { - var clip = { - point: function(coordinates, context) { - var point = rotatePoint(coordinates); - context.point(point[0], point[1]); + function d3_geo_projectionRadiansRotate(rotate, stream) { + return { + point: function(x, y) { + y = rotate(x * d3_radians, y * d3_radians), x = y[0]; + stream.point(x > π ? x - 2 * π : x < -π ? x + 2 * π : x, y[1]); }, - line: function(coordinates, context, ring) { - if (!(n = coordinates.length)) return [ ring && 0, false ]; - var point = rotatePoint(coordinates[0]), λ0 = point[0], φ0 = point[1], λ1, φ1, sλ0 = λ0 > 0 ? π : -π, sλ1, dλ, i = 0, n, clean = ring, area = 0, x0 = (point = d3_geo_stereographic(λ0, φ0))[0], x, y0 = point[1], y; - context.moveTo(λ0, φ0); - while (++i < n) { - point = rotatePoint(coordinates[i]); - λ1 = point[0]; - φ1 = point[1]; - sλ1 = λ1 > 0 ? π : -π; - dλ = Math.abs(λ1 - λ0); - if (Math.abs(dλ - π) < ε) { - context.lineTo(λ0, φ0 = (φ0 + φ1) / 2 > 0 ? π / 2 : -π / 2); - context.lineTo(sλ0, φ0); - context.moveTo(sλ1, φ0); - context.lineTo(λ1, φ0); - clean = false; - } else if (sλ0 !== sλ1 && dλ >= π) { - if (Math.abs(λ0 - sλ0) < ε) λ0 -= sλ0 * ε; - if (Math.abs(λ1 - sλ1) < ε) λ1 -= sλ1 * ε; - φ0 = d3_geo_projectionIntersectAntemeridian(λ0, φ0, λ1, φ1); - context.lineTo(sλ0, φ0); - context.moveTo(sλ1, φ0); - clean = false; - } - if (clean) { - x = (point = d3_geo_stereographic(λ1, φ1))[0]; - y = point[1]; - area += y0 * x - x0 * y; - x0 = x; - y0 = y; - } - context.lineTo(λ0 = λ1, φ0 = φ1); - sλ0 = sλ1; - } - return [ clean && area, true ]; + sphere: function() { + stream.sphere(); }, - polygon: function(polygon, context) { - d3_geo_circleClipPolygon(polygon, context, clip.line, d3_geo_antemeridianInterpolate); + lineStart: function() { + stream.lineStart(); }, - sphere: function(context) { - d3_geo_projectionSphere(context, d3_geo_antemeridianInterpolate); + lineEnd: function() { + stream.lineEnd(); + }, + polygonStart: function() { + stream.polygonStart(); + }, + polygonEnd: function() { + stream.polygonEnd(); } }; - return clip; - } - function d3_geo_antemeridianInterpolate(from, to, direction, context) { - var φ; - if (from == null) { - φ = direction * π / 2; - context.lineTo(-π, φ); - context.lineTo(0, φ); - context.lineTo(π, φ); - context.lineTo(π, 0); - context.lineTo(π, -φ); - context.lineTo(0, -φ); - context.lineTo(-π, -φ); - context.lineTo(-π, 0); - } else if (Math.abs(from[0] - to[0]) > ε) { - var s = (from[0] < to[0] ? 1 : -1) * π; - φ = direction * s / 2; - context.lineTo(-s, φ); - context.lineTo(0, φ); - context.lineTo(s, φ); - } else { - context.lineTo(to[0], to[1]); - } - } - function d3_geo_projectionSphere(context, interpolate) { - var moved = false; - interpolate(null, null, 1, { - lineTo: function(x, y) { - (moved ? context.lineTo : (moved = true, context.moveTo))(x, y); - } - }); - context.closePath(); } function d3_geo_rotation(δλ, δφ, δγ) { - return δλ ? δφ || δγ ? d3_geo_compose(d3_geo_rotationλ(δλ), d3_geo_rotationφγ(δφ, δγ)) : d3_geo_rotationλ(δλ) : δφ || δγ ? d3_geo_rotationφγ(δφ, δγ) : d3_geo_identityRotation; + return δλ ? δφ || δγ ? d3_geo_compose(d3_geo_rotationλ(δλ), d3_geo_rotationφγ(δφ, δγ)) : d3_geo_rotationλ(δλ) : δφ || δγ ? d3_geo_rotationφγ(δφ, δγ) : d3_geo_equirectangular; } - function d3_geo_identityRotation(λ, φ) { - return [ λ > π ? λ - 2 * π : λ < -π ? λ + 2 * π : λ, φ ]; - } - d3_geo_identityRotation.invert = function(x, y) { - return [ x, y ]; - }; function d3_geo_forwardRotationλ(δλ) { return function(λ, φ) { - return [ (λ += δλ) > π ? λ - 2 * π : λ < -π ? λ + 2 * π : λ, φ ]; + return λ += δλ, [ λ > π ? λ - 2 * π : λ < -π ? λ + 2 * π : λ, φ ]; }; } function d3_geo_rotationλ(δλ) { From ae8891c48581e8b19b17b36c5d341d17658d779f Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Feb 2013 14:05:21 -0500 Subject: [PATCH 097/415] Unused vars --- js/id/ui.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/id/ui.js b/js/id/ui.js index 8d764d695..87cc3745a 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -103,7 +103,7 @@ iD.ui = function (context) { .on('click.editor', history.redo) .call(undo_tooltip); - var save_button = limiter.append('div').attr('class','button-wrap col1').append('button') + limiter.append('div').attr('class','button-wrap col1').append('button') .attr('class', 'save col12') .call(iD.ui.save(context)); @@ -127,7 +127,7 @@ iD.ui = function (context) { .call(iD.ui.geolocate(map)); } - var gc = container.append('div').attr('class', 'geocode-control map-control') + container.append('div').attr('class', 'geocode-control map-control') .call(iD.ui.geocoder().map(map)); container.append('div').attr('class', 'map-control layerswitcher-control') From 3da0e70c0fdc663a25e8e4cb3043a2b154e3cc0f Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Feb 2013 14:30:46 -0500 Subject: [PATCH 098/415] Shift-selection --- js/id/behavior/select.js | 8 ++++-- js/id/id.js | 8 ++++++ test/index.html | 1 + test/index_packaged.html | 1 + test/spec/behavior/select.js | 54 ++++++++++++++++++++++++++++++++++++ 5 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 test/spec/behavior/select.js diff --git a/js/id/behavior/select.js b/js/id/behavior/select.js index e078fdbf1..b5276a4d0 100644 --- a/js/id/behavior/select.js +++ b/js/id/behavior/select.js @@ -2,8 +2,12 @@ iD.behavior.Select = function(context) { function click() { var datum = d3.select(d3.event.target).datum(); if (datum instanceof iD.Entity) { - context.enter(iD.modes.Select(context, [datum.id])); - } else { + if (d3.event.shiftKey) { + context.enter(iD.modes.Select(context, context.selection().concat([datum.id]))); + } else { + context.enter(iD.modes.Select(context, [datum.id])); + } + } else if (!d3.event.shiftKey) { context.enter(iD.modes.Browse(context)); } } diff --git a/js/id/id.js b/js/id/id.js index 0b2bbd617..358610525 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -56,6 +56,14 @@ window.iD = function () { return mode; }; + context.selection = function() { + if (mode.id === 'select') { + return mode.selection(); + } else { + return []; + } + }; + /* Behaviors */ context.install = function(behavior) { context.surface().call(behavior); diff --git a/test/index.html b/test/index.html index d32331ba9..16f86b781 100644 --- a/test/index.html +++ b/test/index.html @@ -185,6 +185,7 @@ + diff --git a/test/index_packaged.html b/test/index_packaged.html index 11f8368f4..588c8e0a3 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -80,6 +80,7 @@ + diff --git a/test/spec/behavior/select.js b/test/spec/behavior/select.js new file mode 100644 index 000000000..42c568cca --- /dev/null +++ b/test/spec/behavior/select.js @@ -0,0 +1,54 @@ +describe("iD.behavior.Select", function() { + var a, b, context, behavior, container; + + beforeEach(function() { + container = d3.select('body').append('div'); + + context = iD().container(container); + + a = iD.Node({loc: [0, 0]}); + b = iD.Node({loc: [0, 0]}); + + context.perform(iD.actions.AddEntity(a), iD.actions.AddEntity(b)); + + container.call(context.map()) + .append('div') + .attr('class', 'inspector-wrap'); + + context.surface().selectAll('circle') + .data([a, b]) + .enter().append('circle') + .attr('class', function(d) { return d.id; }); + + behavior = iD.behavior.Select(context); + context.install(behavior); + }); + + afterEach(function() { + context.uninstall(behavior); + container.remove(); + }); + + specify("click on entity selects the entity", function() { + happen.click(context.surface().select('.' + a.id).node()); + expect(context.selection()).to.eql([a.id]); + }); + + specify("click on empty space clears the selection", function() { + context.enter(iD.modes.Select(context, [a.id])); + happen.click(context.surface().node()); + expect(context.selection()).to.eql([]); + }); + + specify("shift-click on entity adds the entity to the selection", function() { + context.enter(iD.modes.Select(context, [a.id])); + happen.click(context.surface().select('.' + b.id).node(), {shiftKey: true}); + expect(context.selection()).to.eql([a.id, b.id]); + }); + + specify("shift-click on empty space leaves the selection unchanged", function() { + context.enter(iD.modes.Select(context, [a.id])); + happen.click(context.surface().node(), {shiftKey: true}); + expect(context.selection()).to.eql([a.id]); + }); +}); From 7ba31f05d2db3ec3d92f954cb97160cab5e1cc25 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Feb 2013 14:46:59 -0500 Subject: [PATCH 099/415] Support deleting relations --- index.html | 1 + js/id/actions/delete_relation.js | 13 +++++++++++++ js/id/operations/delete.js | 10 ++++++---- test/index.html | 2 ++ test/index_packaged.html | 1 + test/spec/actions/delete_relation.js | 17 +++++++++++++++++ 6 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 js/id/actions/delete_relation.js create mode 100644 test/spec/actions/delete_relation.js diff --git a/index.html b/index.html index b0e46d55d..5cd8ff735 100644 --- a/index.html +++ b/index.html @@ -77,6 +77,7 @@ + diff --git a/js/id/actions/delete_relation.js b/js/id/actions/delete_relation.js new file mode 100644 index 000000000..48c62f1e1 --- /dev/null +++ b/js/id/actions/delete_relation.js @@ -0,0 +1,13 @@ +// https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/DeleteRelationAction.as +iD.actions.DeleteRelation = function(relationId) { + return function(graph) { + var relation = graph.entity(relationId); + + graph.parentRelations(relation) + .forEach(function(parent) { + graph = graph.replace(parent.removeMember(relationId)); + }); + + return graph.remove(relation); + }; +}; diff --git a/js/id/operations/delete.js b/js/id/operations/delete.js index d631a2ed7..33c638ce0 100644 --- a/js/id/operations/delete.js +++ b/js/id/operations/delete.js @@ -3,7 +3,11 @@ iD.operations.Delete = function(selection, context) { var operation = function() { var entity = context.entity(entityId), - action = {way: iD.actions.DeleteWay, node: iD.actions.DeleteNode}[entity.type], + action = { + way: iD.actions.DeleteWay, + node: iD.actions.DeleteNode, + relation: iD.actions.DeleteRelation + }[entity.type], annotation = t('operations.delete.annotation.' + context.geometry(entityId)); context.perform( @@ -12,9 +16,7 @@ iD.operations.Delete = function(selection, context) { }; operation.available = function() { - var entity = context.entity(entityId); - return selection.length === 1 && - (entity.type === 'way' || entity.type === 'node'); + return selection.length === 1; }; operation.enabled = function() { diff --git a/test/index.html b/test/index.html index 16f86b781..120938b5f 100644 --- a/test/index.html +++ b/test/index.html @@ -74,6 +74,7 @@ + @@ -141,6 +142,7 @@ + diff --git a/test/index_packaged.html b/test/index_packaged.html index 588c8e0a3..1764e5a9e 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -36,6 +36,7 @@ + diff --git a/test/spec/actions/delete_relation.js b/test/spec/actions/delete_relation.js new file mode 100644 index 000000000..c96909f3f --- /dev/null +++ b/test/spec/actions/delete_relation.js @@ -0,0 +1,17 @@ +describe("iD.actions.DeleteRelation", function () { + it("removes the relation from the graph", function () { + var relation = iD.Relation(), + action = iD.actions.DeleteRelation(relation.id), + graph = action(iD.Graph([relation])); + expect(graph.entity(relation.id)).to.be.undefined; + }); + + it("removes the relation from parent relations", function () { + var a = iD.Relation(), + b = iD.Relation(), + parent = iD.Relation({members: [{ id: a.id }, { id: b.id }]}), + action = iD.actions.DeleteRelation(a.id), + graph = action(iD.Graph([a, b, parent])); + expect(graph.entity(parent.id).members).to.eql([{ id: b.id }]); + }); +}); From 58fcf746a2f030210a79947f168434f4dbcb1627 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Feb 2013 15:08:41 -0500 Subject: [PATCH 100/415] Delete multiple --- index.html | 1 + js/id/actions/delete_multiple.js | 15 +++++++++++++++ js/id/operations/delete.js | 20 +++++++++----------- locale/en.js | 3 ++- test/index.html | 2 ++ test/index_packaged.html | 1 + test/spec/actions/delete_multiple.js | 12 ++++++++++++ 7 files changed, 42 insertions(+), 12 deletions(-) create mode 100644 js/id/actions/delete_multiple.js create mode 100644 test/spec/actions/delete_multiple.js diff --git a/index.html b/index.html index 5cd8ff735..f94a11019 100644 --- a/index.html +++ b/index.html @@ -76,6 +76,7 @@ + diff --git a/js/id/actions/delete_multiple.js b/js/id/actions/delete_multiple.js new file mode 100644 index 000000000..839c1d780 --- /dev/null +++ b/js/id/actions/delete_multiple.js @@ -0,0 +1,15 @@ +iD.actions.DeleteMultiple = function(ids) { + return function(graph) { + var actions = { + way: iD.actions.DeleteWay, + node: iD.actions.DeleteNode, + relation: iD.actions.DeleteRelation + }; + + ids.forEach(function (id) { + graph = actions[graph.entity(id).type](id)(graph); + }); + + return graph; + }; +}; diff --git a/js/id/operations/delete.js b/js/id/operations/delete.js index 33c638ce0..2c9765b62 100644 --- a/js/id/operations/delete.js +++ b/js/id/operations/delete.js @@ -1,22 +1,20 @@ iD.operations.Delete = function(selection, context) { - var entityId = selection[0]; - var operation = function() { - var entity = context.entity(entityId), - action = { - way: iD.actions.DeleteWay, - node: iD.actions.DeleteNode, - relation: iD.actions.DeleteRelation - }[entity.type], - annotation = t('operations.delete.annotation.' + context.geometry(entityId)); + var annotation; + + if (selection.length === 1) { + annotation = t('operations.delete.annotation.' + context.geometry(selection[0])); + } else { + annotation = t('operations.delete.annotation.multiple', {n: selection.length}); + } context.perform( - action(entityId), + iD.actions.DeleteMultiple(selection), annotation); }; operation.available = function() { - return selection.length === 1; + return true; }; operation.enabled = function() { diff --git a/locale/en.js b/locale/en.js index d7fc3628f..b32149ee3 100644 --- a/locale/en.js +++ b/locale/en.js @@ -73,7 +73,8 @@ locale.en = { point: "Deleted a point.", vertex: "Deleted a node from a way.", line: "Deleted a line.", - area: "Deleted an area." + area: "Deleted an area.", + multiple: "Deleted {n} objects." } }, move: { diff --git a/test/index.html b/test/index.html index 120938b5f..1ed505174 100644 --- a/test/index.html +++ b/test/index.html @@ -73,6 +73,7 @@ + @@ -141,6 +142,7 @@ + diff --git a/test/index_packaged.html b/test/index_packaged.html index 1764e5a9e..4b44086c5 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -35,6 +35,7 @@ + diff --git a/test/spec/actions/delete_multiple.js b/test/spec/actions/delete_multiple.js new file mode 100644 index 000000000..3a70adc38 --- /dev/null +++ b/test/spec/actions/delete_multiple.js @@ -0,0 +1,12 @@ +describe("iD.actions.DeleteMultiple", function () { + it("deletes multiple entities of heterogeneous types", function () { + var n = iD.Node(), + w = iD.Way(), + r = iD.Relation(), + action = iD.actions.DeleteMultiple([n.id, w.id, r.id]), + graph = action(iD.Graph([n, w, r])); + expect(graph.entity(n.id)).to.be.undefined; + expect(graph.entity(w.id)).to.be.undefined; + expect(graph.entity(r.id)).to.be.undefined; + }); +}); From 7235632a6304ce1916c4b60e56a61518e4842a7c Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Feb 2013 15:13:44 -0500 Subject: [PATCH 101/415] Update logic for multi-select --- js/id/modes/select.js | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/js/id/modes/select.js b/js/id/modes/select.js index 36066d7d0..02db6a57f 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -82,21 +82,22 @@ iD.modes.Select = function(context, selection, initial) { inspector .on('changeTags', changeTags) .on('close', function() { context.enter(iD.modes.Browse(context)); }); + } - context.history().on('change.select', function() { + context.history().on('change.select', function() { + context.surface().call(radialMenu.close); + + if (_.any(selection, function (id) { return !context.entity(id); })) { // Exit mode if selected entity gets undone - var oldEntity = entity, - newEntity = context.entity(selection[0]); + context.enter(iD.modes.Browse(context)); - if (!newEntity) { - context.enter(iD.modes.Browse(context)); - } else if (!_.isEqual(oldEntity.tags, newEntity.tags)) { + } else if (entity) { + var newEntity = context.entity(selection[0]); + if (!_.isEqual(entity.tags, newEntity.tags)) { inspector.tags(newEntity.tags); } - - context.surface().call(radialMenu.close); - }); - } + } + }); context.map().on('move.select', function() { context.surface().call(radialMenu.close); From 7bfdf4de1a232df610f1f543b3c30fcc2b7126c3 Mon Sep 17 00:00:00 2001 From: Dr Ian Date: Fri, 1 Feb 2013 22:23:30 +0100 Subject: [PATCH 102/415] Change search to use OSM Nominatim API --- js/id/renderer/map.js | 4 ++++ js/id/ui/geocoder.js | 13 +++++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index 1f22a7c81..e076e33a5 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -346,6 +346,10 @@ iD.Map = function(context) { vZoomDiff = Math.log(Math.abs(vFactor)) / Math.LN2, newZoom = map.zoom() - Math.max(hZoomDiff, vZoomDiff); + if(newZoom > 19){ + newZoom = 19; + } + map.centerZoom(extent.center(), newZoom); } }; diff --git a/js/id/ui/geocoder.js b/js/id/ui/geocoder.js index ed37d2cb1..c54b8d6ad 100644 --- a/js/id/ui/geocoder.js +++ b/js/id/ui/geocoder.js @@ -6,18 +6,19 @@ iD.ui.geocoder = function() { function keydown() { if (d3.event.keyCode !== 13) return; d3.event.preventDefault(); - d3.json('http://a.tiles.mapbox.com/v3/openstreetmap.map-hn253zqn/geocode/' + - encodeURIComponent(this.value) + '.json', function(err, resp) { + var searchVal = this.value; + d3.json('http://nominatim.openstreetmap.org/search/' + + encodeURIComponent(searchVal) + '?limit=10&format=json', function(err, resp) { if (err) return hide(); hide(); - if (!resp.results.length) { + if (!resp.length) { return iD.ui.flash() .select('.content') .append('h3') - .text('No location found for "' + resp.query[0] + '"'); + .text('No location found for "' + searchVal + '"'); } - var bounds = resp.results[0][0].bounds; - map.extent(iD.geo.Extent([bounds[0], bounds[1]], [bounds[2], bounds[3]])); + var bounds = resp[0].boundingbox; + map.extent(iD.geo.Extent([parseFloat(bounds[3]), parseFloat(bounds[0])], [parseFloat(bounds[2]), parseFloat(bounds[1])])); }); } From 839844de274d3d3f6bcce3f39460fe598330629e Mon Sep 17 00:00:00 2001 From: Dr Ian Date: Fri, 1 Feb 2013 22:28:43 +0100 Subject: [PATCH 103/415] Convert tabs to spaces --- js/id/renderer/map.js | 6 +++--- js/id/ui/geocoder.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index e076e33a5..5258f8701 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -346,9 +346,9 @@ iD.Map = function(context) { vZoomDiff = Math.log(Math.abs(vFactor)) / Math.LN2, newZoom = map.zoom() - Math.max(hZoomDiff, vZoomDiff); - if(newZoom > 19){ - newZoom = 19; - } + if(newZoom > 19){ + newZoom = 19; + } map.centerZoom(extent.center(), newZoom); } diff --git a/js/id/ui/geocoder.js b/js/id/ui/geocoder.js index c54b8d6ad..e6695792f 100644 --- a/js/id/ui/geocoder.js +++ b/js/id/ui/geocoder.js @@ -6,7 +6,7 @@ iD.ui.geocoder = function() { function keydown() { if (d3.event.keyCode !== 13) return; d3.event.preventDefault(); - var searchVal = this.value; + var searchVal = this.value; d3.json('http://nominatim.openstreetmap.org/search/' + encodeURIComponent(searchVal) + '?limit=10&format=json', function(err, resp) { if (err) return hide(); From f4975447f89cf9c2eba368ac03658d5e8951e53a Mon Sep 17 00:00:00 2001 From: Dr Ian Date: Fri, 1 Feb 2013 23:05:47 +0100 Subject: [PATCH 104/415] Limit zoom to 19 if geocode bounding box too small --- js/id/renderer/map.js | 4 ---- js/id/ui/geocoder.js | 1 + 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index 5258f8701..1f22a7c81 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -346,10 +346,6 @@ iD.Map = function(context) { vZoomDiff = Math.log(Math.abs(vFactor)) / Math.LN2, newZoom = map.zoom() - Math.max(hZoomDiff, vZoomDiff); - if(newZoom > 19){ - newZoom = 19; - } - map.centerZoom(extent.center(), newZoom); } }; diff --git a/js/id/ui/geocoder.js b/js/id/ui/geocoder.js index e6695792f..6d4d39d03 100644 --- a/js/id/ui/geocoder.js +++ b/js/id/ui/geocoder.js @@ -19,6 +19,7 @@ iD.ui.geocoder = function() { } var bounds = resp[0].boundingbox; map.extent(iD.geo.Extent([parseFloat(bounds[3]), parseFloat(bounds[0])], [parseFloat(bounds[2]), parseFloat(bounds[1])])); + if (map.zoom() > 19) map.zoom(19); }); } From 01e8f5f606ad5ce5affa54c8aac5fb7b91171af3 Mon Sep 17 00:00:00 2001 From: Dr Ian Date: Fri, 1 Feb 2013 23:08:11 +0100 Subject: [PATCH 105/415] Convert tabs to spaces --- js/id/ui/geocoder.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/id/ui/geocoder.js b/js/id/ui/geocoder.js index 6d4d39d03..d7de24b37 100644 --- a/js/id/ui/geocoder.js +++ b/js/id/ui/geocoder.js @@ -19,7 +19,7 @@ iD.ui.geocoder = function() { } var bounds = resp[0].boundingbox; map.extent(iD.geo.Extent([parseFloat(bounds[3]), parseFloat(bounds[0])], [parseFloat(bounds[2]), parseFloat(bounds[1])])); - if (map.zoom() > 19) map.zoom(19); + if (map.zoom() > 19) map.zoom(19); }); } From e1d5a0cb0324ae5334e6973bb959c56ad8137a79 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Feb 2013 15:45:45 -0500 Subject: [PATCH 106/415] Unjoin -> Disconnect --- combobox.html | 2 +- css/app.css | 2 +- index.html | 4 ++-- .../actions/{unjoin_node.js => disconnect.js} | 4 ++-- js/id/operations/disconnect.js | 24 +++++++++++++++++++ js/id/operations/unjoin.js | 24 ------------------- locale/en.js | 12 +++++----- test/index.html | 6 ++--- test/index_packaged.html | 2 +- .../actions/{unjoin_node.js => disconnect.js} | 10 ++++---- 10 files changed, 45 insertions(+), 45 deletions(-) rename js/id/actions/{unjoin_node.js => disconnect.js} (92%) create mode 100644 js/id/operations/disconnect.js delete mode 100644 js/id/operations/unjoin.js rename test/spec/actions/{unjoin_node.js => disconnect.js} (89%) diff --git a/combobox.html b/combobox.html index 0d97698b0..469e73823 100644 --- a/combobox.html +++ b/combobox.html @@ -79,13 +79,13 @@ + - diff --git a/css/app.css b/css/app.css index 1a880ad02..7a0751d02 100644 --- a/css/app.css +++ b/css/app.css @@ -476,7 +476,7 @@ button[disabled] .icon.nearby { background-position: -340px -40px;} .icon-operation-circularize { background-position: -20px -140px;} .icon-operation-straighten { background-position: -40px -140px;} .icon-operation-split { background-position: -60px -140px;} -.icon-operation-unjoin { background-position: -80px -140px;} +.icon-operation-disconnect { background-position: -80px -140px;} .icon-operation-reverse { background-position: -100px -140px;} .icon-operation-move { background-position: -120px -140px;} .icon-operation-merge { background-position: -140px -140px;} diff --git a/index.html b/index.html index f94a11019..3ef8abad5 100644 --- a/index.html +++ b/index.html @@ -80,13 +80,13 @@ + - @@ -112,10 +112,10 @@ + - diff --git a/js/id/actions/unjoin_node.js b/js/id/actions/disconnect.js similarity index 92% rename from js/id/actions/unjoin_node.js rename to js/id/actions/disconnect.js index 39e05a9cf..8645c436b 100644 --- a/js/id/actions/unjoin_node.js +++ b/js/id/actions/disconnect.js @@ -1,4 +1,4 @@ -// Unjoin the ways at the given node. +// Disconect the ways at the given node. // // For testing convenience, accepts an ID to assign to the (first) new node. // Normally, this will be undefined and the way will automatically @@ -8,7 +8,7 @@ // https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/UnjoinNodeAction.as // https://github.com/openstreetmap/josm/blob/mirror/src/org/openstreetmap/josm/actions/UnGlueAction.java // -iD.actions.UnjoinNode = function(nodeId, newNodeId) { +iD.actions.Disconnect = function(nodeId, newNodeId) { var action = function(graph) { if (!action.enabled(graph)) return graph; diff --git a/js/id/operations/disconnect.js b/js/id/operations/disconnect.js new file mode 100644 index 000000000..c8cc0d014 --- /dev/null +++ b/js/id/operations/disconnect.js @@ -0,0 +1,24 @@ +iD.operations.Disconnect = function(selection, context) { + var entityId = selection[0], + action = iD.actions.Disconnect(entityId); + + var operation = function() { + context.perform(action, t('operations.disconnect.annotation')); + }; + + operation.available = function() { + return selection.length === 1 && + context.geometry(entityId) === 'vertex'; + }; + + operation.enabled = function() { + return action.enabled(context.graph()); + }; + + operation.id = "disconnect"; + operation.key = t('operations.disconnect.key'); + operation.title = t('operations.disconnect.title'); + operation.description = t('operations.disconnect.description'); + + return operation; +}; diff --git a/js/id/operations/unjoin.js b/js/id/operations/unjoin.js deleted file mode 100644 index d4fcc1426..000000000 --- a/js/id/operations/unjoin.js +++ /dev/null @@ -1,24 +0,0 @@ -iD.operations.Unjoin = function(selection, context) { - var entityId = selection[0], - action = iD.actions.UnjoinNode(entityId); - - var operation = function() { - context.perform(action, 'Unjoined lines.'); - }; - - operation.available = function() { - return selection.length === 1 && - context.geometry(entityId) === 'vertex'; - }; - - operation.enabled = function() { - return action.enabled(context.graph()); - }; - - operation.id = "unjoin"; - operation.key = t('operations.unjoin.key'); - operation.title = t('operations.unjoin.title'); - operation.description = t('operations.unjoin.description'); - - return operation; -}; diff --git a/locale/en.js b/locale/en.js index b32149ee3..7d1bed947 100644 --- a/locale/en.js +++ b/locale/en.js @@ -77,6 +77,12 @@ locale.en = { multiple: "Deleted {n} objects." } }, + disconnect: { + title: "Disconnect", + description: "Disconnect these ways from each other.", + key: "D", + annotation: "Disconnected ways." + }, move: { title: "Move", description: "Move this to a different location.", @@ -99,12 +105,6 @@ locale.en = { description: "Split this into two ways at this point.", key: "X", annotation: "Split a way." - }, - unjoin: { - title: "Unjoin", - description: "Disconnect these ways from each other.", - key: "⇧-J", - annotation: "Unjoined ways." } }, diff --git a/test/index.html b/test/index.html index 1ed505174..e06e18a4b 100644 --- a/test/index.html +++ b/test/index.html @@ -77,12 +77,12 @@ + - @@ -108,10 +108,10 @@ + - @@ -146,12 +146,12 @@ + - diff --git a/test/index_packaged.html b/test/index_packaged.html index 4b44086c5..673ea46e8 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -39,12 +39,12 @@ + - diff --git a/test/spec/actions/unjoin_node.js b/test/spec/actions/disconnect.js similarity index 89% rename from test/spec/actions/unjoin_node.js rename to test/spec/actions/disconnect.js index 5901e97da..4ce298feb 100644 --- a/test/spec/actions/unjoin_node.js +++ b/test/spec/actions/disconnect.js @@ -1,9 +1,9 @@ -describe("iD.actions.UnjoinNode", function () { +describe("iD.actions.Disconnect", function () { describe("#enabled", function () { it("returns false for a node shared by less than two ways", function () { var graph = iD.Graph({'a': iD.Node()}); - expect(iD.actions.UnjoinNode('a').enabled(graph)).to.equal(false); + expect(iD.actions.Disconnect('a').enabled(graph)).to.equal(false); }); it("returns true for a node shared by two or more ways", function () { @@ -19,7 +19,7 @@ describe("iD.actions.UnjoinNode", function () { '|': iD.Way({id: '|', nodes: ['d', 'b']}) }); - expect(iD.actions.UnjoinNode('b').enabled(graph)).to.equal(true); + expect(iD.actions.Disconnect('b').enabled(graph)).to.equal(true); }); }); @@ -46,7 +46,7 @@ describe("iD.actions.UnjoinNode", function () { '|': iD.Way({id: '|', nodes: ['d', 'b']}) }); - graph = iD.actions.UnjoinNode('b', 'e')(graph); + graph = iD.actions.Disconnect('b', 'e')(graph); expect(graph.entity('-').nodes).to.eql(['a', 'b', 'c']); expect(graph.entity('|').nodes).to.eql(['d', 'e']); @@ -64,7 +64,7 @@ describe("iD.actions.UnjoinNode", function () { '|': iD.Way({id: '|', nodes: ['d', 'b']}) }); - graph = iD.actions.UnjoinNode('b', 'e')(graph); + graph = iD.actions.Disconnect('b', 'e')(graph); // Immutable loc => should be shared by identity. expect(graph.entity('b').loc).to.equal(loc); From c86792a77b9e7e1c02cb0de6ec1cac6107faf9eb Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Feb 2013 15:56:48 -0500 Subject: [PATCH 107/415] SplitWay -> Split --- combobox.html | 2 +- index.html | 2 +- js/id/actions/{split_way.js => split.js} | 2 +- js/id/operations/split.js | 2 +- test/index.html | 4 +-- test/index_packaged.html | 2 +- test/spec/actions/{split_way.js => split.js} | 28 ++++++++++---------- 7 files changed, 21 insertions(+), 21 deletions(-) rename js/id/actions/{split_way.js => split.js} (97%) rename test/spec/actions/{split_way.js => split.js} (92%) diff --git a/combobox.html b/combobox.html index 469e73823..914d834e7 100644 --- a/combobox.html +++ b/combobox.html @@ -85,7 +85,7 @@ - + diff --git a/index.html b/index.html index 3ef8abad5..defb5403b 100644 --- a/index.html +++ b/index.html @@ -86,7 +86,7 @@ - + diff --git a/js/id/actions/split_way.js b/js/id/actions/split.js similarity index 97% rename from js/id/actions/split_way.js rename to js/id/actions/split.js index 6fbd08595..0c648bd26 100644 --- a/js/id/actions/split_way.js +++ b/js/id/actions/split.js @@ -7,7 +7,7 @@ // Reference: // https://github.com/systemed/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/SplitWayAction.as // -iD.actions.SplitWay = function(nodeId, newWayId) { +iD.actions.Split = function(nodeId, newWayId) { function candidateWays(graph) { var node = graph.entity(nodeId), parents = graph.parentWays(node); diff --git a/js/id/operations/split.js b/js/id/operations/split.js index 0d7ff6ae4..07d7d56ff 100644 --- a/js/id/operations/split.js +++ b/js/id/operations/split.js @@ -1,6 +1,6 @@ iD.operations.Split = function(selection, context) { var entityId = selection[0], - action = iD.actions.SplitWay(entityId); + action = iD.actions.Split(entityId); var operation = function() { context.perform(action, t('operations.split.annotation')); diff --git a/test/index.html b/test/index.html index e06e18a4b..ceb1e7fbc 100644 --- a/test/index.html +++ b/test/index.html @@ -82,7 +82,7 @@ - + @@ -151,7 +151,7 @@ - + diff --git a/test/index_packaged.html b/test/index_packaged.html index 673ea46e8..050badd7d 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -44,7 +44,7 @@ - + diff --git a/test/spec/actions/split_way.js b/test/spec/actions/split.js similarity index 92% rename from test/spec/actions/split_way.js rename to test/spec/actions/split.js index 2958521a9..5ee68e67e 100644 --- a/test/spec/actions/split_way.js +++ b/test/spec/actions/split.js @@ -1,4 +1,4 @@ -describe("iD.actions.SplitWay", function () { +describe("iD.actions.Split", function () { describe("#enabled", function () { it("returns true for a non-end node of a single way", function () { var graph = iD.Graph({ @@ -8,7 +8,7 @@ describe("iD.actions.SplitWay", function () { '-': iD.Way({id: '-', nodes: ['a', 'b', 'c']}) }); - expect(iD.actions.SplitWay('b').enabled(graph)).to.be.true; + expect(iD.actions.Split('b').enabled(graph)).to.be.true; }); it("returns false for the first node of a single way", function () { @@ -18,7 +18,7 @@ describe("iD.actions.SplitWay", function () { '-': iD.Way({id: '-', nodes: ['a', 'b']}) }); - expect(iD.actions.SplitWay('a').enabled(graph)).to.be.false; + expect(iD.actions.Split('a').enabled(graph)).to.be.false; }); it("returns false for the last node of a single way", function () { @@ -28,7 +28,7 @@ describe("iD.actions.SplitWay", function () { '-': iD.Way({id: '-', nodes: ['a', 'b']}) }); - expect(iD.actions.SplitWay('b').enabled(graph)).to.be.false; + expect(iD.actions.Split('b').enabled(graph)).to.be.false; }); }); @@ -48,7 +48,7 @@ describe("iD.actions.SplitWay", function () { '-': iD.Way({id: '-', nodes: ['a', 'b', 'c']}) }); - graph = iD.actions.SplitWay('b', '=')(graph); + graph = iD.actions.Split('b', '=')(graph); expect(graph.entity('-').nodes).to.eql(['a', 'b']); expect(graph.entity('=').nodes).to.eql(['b', 'c']); @@ -63,7 +63,7 @@ describe("iD.actions.SplitWay", function () { '-': iD.Way({id: '-', nodes: ['a', 'b', 'c'], tags: tags}) }); - graph = iD.actions.SplitWay('b', '=')(graph); + graph = iD.actions.Split('b', '=')(graph); // Immutable tags => should be shared by identity. expect(graph.entity('-').tags).to.equal(tags); @@ -92,7 +92,7 @@ describe("iD.actions.SplitWay", function () { '|': iD.Way({id: '|', nodes: ['d', 'b']}) }); - graph = iD.actions.SplitWay('b', '=')(graph); + graph = iD.actions.Split('b', '=')(graph); expect(graph.entity('-').nodes).to.eql(['a', 'b']); expect(graph.entity('=').nodes).to.eql(['b', 'c']); @@ -118,7 +118,7 @@ describe("iD.actions.SplitWay", function () { 'r': iD.Relation({id: 'r', members: [{id: '-', type: 'way'}]}) }); - graph = iD.actions.SplitWay('b', '=')(graph); + graph = iD.actions.Split('b', '=')(graph); expect(_.pluck(graph.entity('r').members, 'id')).to.eql(['-', '=']); }); @@ -144,7 +144,7 @@ describe("iD.actions.SplitWay", function () { 'r': iD.Relation({id: 'r', members: [{id: '-', type: 'way'}, {id: '~', type: 'way'}]}) }); - graph = iD.actions.SplitWay('b', '=')(graph); + graph = iD.actions.Split('b', '=')(graph); expect(_.pluck(graph.entity('r').members, 'id')).to.eql(['-', '=', '~']); }); @@ -170,7 +170,7 @@ describe("iD.actions.SplitWay", function () { 'r': iD.Relation({id: 'r', members: [{id: '~', type: 'way'}, {id: '-', type: 'way'}]}) }); - graph = iD.actions.SplitWay('b', '=')(graph); + graph = iD.actions.Split('b', '=')(graph); expect(_.pluck(graph.entity('r').members, 'id')).to.eql(['~', '=', '-']); }); @@ -184,7 +184,7 @@ describe("iD.actions.SplitWay", function () { 'r': iD.Relation({id: 'r', members: [{id: '~', type: 'way'}, {id: '-', type: 'way'}]}) }); - graph = iD.actions.SplitWay('b', '=')(graph); + graph = iD.actions.Split('b', '=')(graph); expect(_.pluck(graph.entity('r').members, 'id')).to.eql(['~', '-', '=']); }); @@ -214,7 +214,7 @@ describe("iD.actions.SplitWay", function () { {id: 'c', role: 'via'}]}) }); - graph = iD.actions.SplitWay('b', '=')(graph); + graph = iD.actions.Split('b', '=')(graph); expect(graph.entity('r').members).to.eql([ {id: '=', role: 'from'}, @@ -246,7 +246,7 @@ describe("iD.actions.SplitWay", function () { {id: 'c', role: 'via'}]}) }); - graph = iD.actions.SplitWay('b', '=')(graph); + graph = iD.actions.Split('b', '=')(graph); expect(graph.entity('r').members).to.eql([ {id: '~', role: 'from'}, @@ -278,7 +278,7 @@ describe("iD.actions.SplitWay", function () { {id: 'c', role: 'via'}]}) }); - graph = iD.actions.SplitWay('b', '=')(graph); + graph = iD.actions.Split('b', '=')(graph); expect(graph.entity('r').members).to.eql([ {id: '-', role: 'from'}, From 9120f33aa7c5ea68a09a7084b51abdaabe1dd471 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Feb 2013 15:58:34 -0500 Subject: [PATCH 108/415] ReverseWay -> Reverse --- combobox.html | 2 +- index.html | 2 +- js/id/actions/{reverse_way.js => reverse.js} | 2 +- js/id/operations/reverse.js | 2 +- test/index.html | 4 +- test/index_packaged.html | 2 +- .../actions/{reverse_way.js => reverse.js} | 40 +++++++++---------- 7 files changed, 27 insertions(+), 27 deletions(-) rename js/id/actions/{reverse_way.js => reverse.js} (98%) rename test/spec/actions/{reverse_way.js => reverse.js} (76%) diff --git a/combobox.html b/combobox.html index 914d834e7..c739f4898 100644 --- a/combobox.html +++ b/combobox.html @@ -84,7 +84,7 @@ - + diff --git a/index.html b/index.html index defb5403b..87e93c643 100644 --- a/index.html +++ b/index.html @@ -85,7 +85,7 @@ - + diff --git a/js/id/actions/reverse_way.js b/js/id/actions/reverse.js similarity index 98% rename from js/id/actions/reverse_way.js rename to js/id/actions/reverse.js index 160017637..5c8315467 100644 --- a/js/id/actions/reverse_way.js +++ b/js/id/actions/reverse.js @@ -27,7 +27,7 @@ http://wiki.openstreetmap.org/wiki/Route#Members http://josm.openstreetmap.de/browser/josm/trunk/src/org/openstreetmap/josm/corrector/ReverseWayTagCorrector.java */ -iD.actions.ReverseWay = function(wayId) { +iD.actions.Reverse = function(wayId) { var replacements = [ [/:right$/, ':left'], [/:left$/, ':right'], [/:forward$/, ':backward'], [/:backward$/, ':forward'] diff --git a/js/id/operations/reverse.js b/js/id/operations/reverse.js index 77bacc64e..f750c134d 100644 --- a/js/id/operations/reverse.js +++ b/js/id/operations/reverse.js @@ -3,7 +3,7 @@ iD.operations.Reverse = function(selection, context) { var operation = function() { context.perform( - iD.actions.ReverseWay(entityId), + iD.actions.Reverse(entityId), t('operations.reverse.annotation')); }; diff --git a/test/index.html b/test/index.html index ceb1e7fbc..512a1ee1e 100644 --- a/test/index.html +++ b/test/index.html @@ -81,7 +81,7 @@ - + @@ -150,7 +150,7 @@ - + diff --git a/test/index_packaged.html b/test/index_packaged.html index 050badd7d..5adaf06f6 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -43,7 +43,7 @@ - + diff --git a/test/spec/actions/reverse_way.js b/test/spec/actions/reverse.js similarity index 76% rename from test/spec/actions/reverse_way.js rename to test/spec/actions/reverse.js index ca61cb778..40d0f5500 100644 --- a/test/spec/actions/reverse_way.js +++ b/test/spec/actions/reverse.js @@ -1,9 +1,9 @@ -describe("iD.actions.ReverseWay", function () { +describe("iD.actions.Reverse", function () { it("reverses the order of nodes in the way", function () { var node1 = iD.Node(), node2 = iD.Node(), way = iD.Way({nodes: [node1.id, node2.id]}), - graph = iD.actions.ReverseWay(way.id)(iD.Graph([node1, node2, way])); + graph = iD.actions.Reverse(way.id)(iD.Graph([node1, node2, way])); expect(graph.entity(way.id).nodes).to.eql([node2.id, node1.id]); }); @@ -11,7 +11,7 @@ describe("iD.actions.ReverseWay", function () { var way = iD.Way({tags: {'highway': 'residential'}}), graph = iD.Graph([way]); - graph = iD.actions.ReverseWay(way.id)(graph); + graph = iD.actions.Reverse(way.id)(graph); expect(graph.entity(way.id).tags).to.eql({'highway': 'residential'}); }); @@ -19,7 +19,7 @@ describe("iD.actions.ReverseWay", function () { var way = iD.Way({tags: {'oneway': 'yes'}}), graph = iD.Graph([way]); - graph = iD.actions.ReverseWay(way.id)(graph); + graph = iD.actions.Reverse(way.id)(graph); expect(graph.entity(way.id).tags).to.eql({'oneway': 'yes'}); }); @@ -27,10 +27,10 @@ describe("iD.actions.ReverseWay", function () { var way = iD.Way({tags: {'cycleway:right': 'lane'}}), graph = iD.Graph([way]); - graph = iD.actions.ReverseWay(way.id)(graph); + graph = iD.actions.Reverse(way.id)(graph); expect(graph.entity(way.id).tags).to.eql({'cycleway:left': 'lane'}); - graph = iD.actions.ReverseWay(way.id)(graph); + graph = iD.actions.Reverse(way.id)(graph); expect(graph.entity(way.id).tags).to.eql({'cycleway:right': 'lane'}); }); @@ -38,10 +38,10 @@ describe("iD.actions.ReverseWay", function () { var way = iD.Way({tags: {'maxspeed:forward': '25'}}), graph = iD.Graph([way]); - graph = iD.actions.ReverseWay(way.id)(graph); + graph = iD.actions.Reverse(way.id)(graph); expect(graph.entity(way.id).tags).to.eql({'maxspeed:backward': '25'}); - graph = iD.actions.ReverseWay(way.id)(graph); + graph = iD.actions.Reverse(way.id)(graph); expect(graph.entity(way.id).tags).to.eql({'maxspeed:forward': '25'}); }); @@ -49,10 +49,10 @@ describe("iD.actions.ReverseWay", function () { var way = iD.Way({tags: {'incline': 'up'}}), graph = iD.Graph([way]); - graph = iD.actions.ReverseWay(way.id)(graph); + graph = iD.actions.Reverse(way.id)(graph); expect(graph.entity(way.id).tags).to.eql({'incline': 'down'}); - graph = iD.actions.ReverseWay(way.id)(graph); + graph = iD.actions.Reverse(way.id)(graph); expect(graph.entity(way.id).tags).to.eql({'incline': 'up'}); }); @@ -60,10 +60,10 @@ describe("iD.actions.ReverseWay", function () { var way = iD.Way({tags: {'incline': 'up'}}), graph = iD.Graph([way]); - graph = iD.actions.ReverseWay(way.id)(graph); + graph = iD.actions.Reverse(way.id)(graph); expect(graph.entity(way.id).tags).to.eql({'incline': 'down'}); - graph = iD.actions.ReverseWay(way.id)(graph); + graph = iD.actions.Reverse(way.id)(graph); expect(graph.entity(way.id).tags).to.eql({'incline': 'up'}); }); @@ -71,16 +71,16 @@ describe("iD.actions.ReverseWay", function () { var way = iD.Way({tags: {'incline': '5%'}}), graph = iD.Graph([way]); - graph = iD.actions.ReverseWay(way.id)(graph); + graph = iD.actions.Reverse(way.id)(graph); expect(graph.entity(way.id).tags).to.eql({'incline': '-5%'}); - graph = iD.actions.ReverseWay(way.id)(graph); + graph = iD.actions.Reverse(way.id)(graph); expect(graph.entity(way.id).tags).to.eql({'incline': '5%'}); way = iD.Way({tags: {'incline': '.8°'}}); graph = iD.Graph([way]); - graph = iD.actions.ReverseWay(way.id)(graph); + graph = iD.actions.Reverse(way.id)(graph); expect(graph.entity(way.id).tags).to.eql({'incline': '-.8°'}); }); @@ -88,10 +88,10 @@ describe("iD.actions.ReverseWay", function () { var way = iD.Way({tags: {'sidewalk': 'right'}}), graph = iD.Graph([way]); - graph = iD.actions.ReverseWay(way.id)(graph); + graph = iD.actions.Reverse(way.id)(graph); expect(graph.entity(way.id).tags).to.eql({'sidewalk': 'left'}); - graph = iD.actions.ReverseWay(way.id)(graph); + graph = iD.actions.Reverse(way.id)(graph); expect(graph.entity(way.id).tags).to.eql({'sidewalk': 'right'}); }); @@ -99,7 +99,7 @@ describe("iD.actions.ReverseWay", function () { var way = iD.Way({tags: {'maxspeed:forward': '25', 'maxspeed:backward': '30'}}), graph = iD.Graph([way]); - graph = iD.actions.ReverseWay(way.id)(graph); + graph = iD.actions.Reverse(way.id)(graph); expect(graph.entity(way.id).tags).to.eql({'maxspeed:backward': '25', 'maxspeed:forward': '30'}); }); @@ -108,10 +108,10 @@ describe("iD.actions.ReverseWay", function () { relation = iD.Relation({members: [{type: 'way', id: way.id, role: 'forward'}]}), graph = iD.Graph([way, relation]); - graph = iD.actions.ReverseWay(way.id)(graph); + graph = iD.actions.Reverse(way.id)(graph); expect(graph.entity(relation.id).members[0].role).to.eql('backward'); - graph = iD.actions.ReverseWay(way.id)(graph); + graph = iD.actions.Reverse(way.id)(graph); expect(graph.entity(relation.id).members[0].role).to.eql('forward'); }); }); From 20730e5f1a442e766416951cb88a73d49709e9a7 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Feb 2013 17:24:16 -0500 Subject: [PATCH 109/415] Entity#mergeTags --- js/id/graph/entity.js | 14 ++++++++++++++ test/spec/graph/entity.js | 27 +++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/js/id/graph/entity.js b/js/id/graph/entity.js index f91bb89d9..ae612c268 100644 --- a/js/id/graph/entity.js +++ b/js/id/graph/entity.js @@ -66,6 +66,20 @@ iD.Entity.prototype = { return iD.Entity(this, attrs, {_updated: true}); }, + mergeTags: function(tags) { + var merged = _.clone(this.tags); + for (var k in tags) { + var t1 = merged[k], + t2 = tags[k]; + if (t1 && t1 !== t2) { + merged[k] = t1 + "; " + t2; + } else { + merged[k] = t2; + } + } + return this.update({tags: merged}); + }, + created: function() { return this._updated && this.osmId().charAt(0) === '-'; }, diff --git a/test/spec/graph/entity.js b/test/spec/graph/entity.js index a573f4c8c..088ab5516 100644 --- a/test/spec/graph/entity.js +++ b/test/spec/graph/entity.js @@ -69,6 +69,33 @@ describe('iD.Entity', function () { }); }); + describe("#mergeTags", function () { + it("returns a new Entity", function () { + var a = iD.Entity(), + b = a.mergeTags({}); + expect(b instanceof iD.Entity).to.be.true; + expect(a).not.to.equal(b); + }); + + it("merges tags", function () { + var a = iD.Entity({tags: {a: 'a'}}), + b = a.mergeTags({b: 'b'}); + expect(b.tags).to.eql({a: 'a', b: 'b'}); + }); + + it("combines non-conflicting tags", function () { + var a = iD.Entity({tags: {a: 'a'}}), + b = a.mergeTags({a: 'a'}); + expect(b.tags).to.eql({a: 'a'}); + }); + + it("combines conflicting tags with semicolons", function () { + var a = iD.Entity({tags: {a: 'a'}}), + b = a.mergeTags({a: 'b'}); + expect(b.tags).to.eql({a: 'a; b'}); + }); + }); + describe("#osmId", function () { it("returns an OSM ID as a string", function () { expect(iD.Entity({id: 'w1234'}).osmId()).to.eql('1234'); From a8410be6eb9042935fb25533888fcada2f6e06f0 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Feb 2013 17:24:50 -0500 Subject: [PATCH 110/415] iD.actions.Join --- index.html | 1 + js/id/actions/join.js | 65 ++++++++++++++ js/id/actions/split.js | 2 + test/index.html | 2 + test/index_packaged.html | 1 + test/spec/actions/join.js | 172 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 243 insertions(+) create mode 100644 js/id/actions/join.js create mode 100644 test/spec/actions/join.js diff --git a/index.html b/index.html index 87e93c643..07063cf81 100644 --- a/index.html +++ b/index.html @@ -81,6 +81,7 @@ + diff --git a/js/id/actions/join.js b/js/id/actions/join.js new file mode 100644 index 000000000..fd6862a9b --- /dev/null +++ b/js/id/actions/join.js @@ -0,0 +1,65 @@ +// Join ways at the end node they share. +// +// This is the inverse of `iD.actions.Split`. +// +// Reference: +// https://github.com/systemed/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/MergeWaysAction.as +// https://github.com/openstreetmap/josm/blob/mirror/src/org/openstreetmap/josm/actions/CombineWayAction.java +// +iD.actions.Join = function(idA, idB) { + var action = function(graph) { + var a = graph.entity(idA), + b = graph.entity(idB), + nodes, tags; + + if (a.first() === b.first()) { + // a <-- b ==> c + // Expected result: + // a <-- b <-- c + nodes = b.nodes.slice().reverse().concat(a.nodes.slice(1)); + + } else if (a.first() === b.last()) { + // a <-- b <== c + // Expected result: + // a <-- b <-- c + nodes = b.nodes.concat(a.nodes.slice(1)); + + } else if (a.last() === b.first()) { + // a --> b ==> c + // Expected result: + // a --> b --> c + nodes = a.nodes.concat(b.nodes.slice(1)); + + } else if (a.last() === b.last()) { + // a --> b <== c + // Expected result: + // a --> b --> c + nodes = a.nodes.concat(b.nodes.slice().reverse().slice(1)); + } + + graph.parentRelations(b) + .forEach(function (parent) { + var memberA = parent.memberById(idA), + memberB = parent.memberById(idB); + if (!memberA) { + graph = graph.replace(parent.addMember({id: idA, role: memberB.role})); + } + }); + + graph = graph.replace(a.mergeTags(b.tags).update({nodes: nodes})); + graph = iD.actions.DeleteWay(idB)(graph); + + return graph; + }; + + action.enabled = function(graph) { + var a = graph.entity(idA), + b = graph.entity(idB); + return a.first() === b.first() || + a.first() === b.last() || + a.last() === b.first() || + a.last() === b.last(); + }; + + return action; +}; diff --git a/js/id/actions/split.js b/js/id/actions/split.js index 0c648bd26..19a71c619 100644 --- a/js/id/actions/split.js +++ b/js/id/actions/split.js @@ -1,5 +1,7 @@ // Split a way at the given node. // +// This is the inverse of `iD.actions.Join`. +// // For testing convenience, accepts an ID to assign to the new way. // Normally, this will be undefined and the way will automatically // be assigned a new ID. diff --git a/test/index.html b/test/index.html index 512a1ee1e..524894c36 100644 --- a/test/index.html +++ b/test/index.html @@ -78,6 +78,7 @@ + @@ -147,6 +148,7 @@ + diff --git a/test/index_packaged.html b/test/index_packaged.html index 5adaf06f6..948ed0ae1 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -40,6 +40,7 @@ + diff --git a/test/spec/actions/join.js b/test/spec/actions/join.js new file mode 100644 index 000000000..1cddb94bf --- /dev/null +++ b/test/spec/actions/join.js @@ -0,0 +1,172 @@ +describe("iD.actions.Join", function () { + describe("#enabled", function () { + it("returns true for ways that share an end/start node", function () { + // a --> b ==> c + var graph = iD.Graph({ + 'a': iD.Node({id: 'a'}), + 'b': iD.Node({id: 'b'}), + 'c': iD.Node({id: 'c'}), + '-': iD.Way({id: '-', nodes: ['a', 'b']}), + '=': iD.Way({id: '=', nodes: ['b', 'c']}) + }); + + expect(iD.actions.Join('-', '=').enabled(graph)).to.be.true; + }); + + it("returns true for ways that share a start/end node", function () { + // a <-- b <== c + var graph = iD.Graph({ + 'a': iD.Node({id: 'a'}), + 'b': iD.Node({id: 'b'}), + 'c': iD.Node({id: 'c'}), + '-': iD.Way({id: '-', nodes: ['b', 'a']}), + '=': iD.Way({id: '=', nodes: ['c', 'b']}) + }); + + expect(iD.actions.Join('-', '=').enabled(graph)).to.be.true; + }); + + it("returns true for ways that share a start/start node", function () { + // a <-- b ==> c + var graph = iD.Graph({ + 'a': iD.Node({id: 'a'}), + 'b': iD.Node({id: 'b'}), + 'c': iD.Node({id: 'c'}), + '-': iD.Way({id: '-', nodes: ['b', 'a']}), + '=': iD.Way({id: '=', nodes: ['b', 'c']}) + }); + + expect(iD.actions.Join('-', '=').enabled(graph)).to.be.true; + }); + + it("returns true for ways that share an end/end node", function () { + // a --> b <== c + var graph = iD.Graph({ + 'a': iD.Node({id: 'a'}), + 'b': iD.Node({id: 'b'}), + 'c': iD.Node({id: 'c'}), + '-': iD.Way({id: '-', nodes: ['a', 'b']}), + '=': iD.Way({id: '=', nodes: ['c', 'b']}) + }); + + expect(iD.actions.Join('-', '=').enabled(graph)).to.be.true; + }); + + it("returns false for ways that don't share the necessary nodes", function () { + // a -- b -- c + // | + // d + var graph = iD.Graph({ + 'a': iD.Node({id: 'a'}), + 'b': iD.Node({id: 'b'}), + 'c': iD.Node({id: 'c'}), + 'd': iD.Node({id: 'd'}), + '-': iD.Way({id: '-', nodes: ['a', 'b', 'c']}), + '=': iD.Way({id: '=', nodes: ['b', 'd']}) + }); + + expect(iD.actions.Join('-', '=').enabled(graph)).to.be.false; + }); + }); + + it("joins a --> b ==> c", function () { + // Expected result: + // a --> b --> c + var graph = iD.Graph({ + 'a': iD.Node({id: 'a'}), + 'b': iD.Node({id: 'b'}), + 'c': iD.Node({id: 'c'}), + '-': iD.Way({id: '-', nodes: ['a', 'b']}), + '=': iD.Way({id: '=', nodes: ['b', 'c']}) + }); + + graph = iD.actions.Join('-', '=')(graph); + + expect(graph.entity('-').nodes).to.eql(['a', 'b', 'c']); + expect(graph.entity('=')).to.be.undefined; + }); + + it("joins a <-- b <== c", function () { + // Expected result: + // a <-- b <-- c + var graph = iD.Graph({ + 'a': iD.Node({id: 'a'}), + 'b': iD.Node({id: 'b'}), + 'c': iD.Node({id: 'c'}), + '-': iD.Way({id: '-', nodes: ['b', 'a']}), + '=': iD.Way({id: '=', nodes: ['c', 'b']}) + }); + + graph = iD.actions.Join('-', '=')(graph); + + expect(graph.entity('-').nodes).to.eql(['c', 'b', 'a']); + expect(graph.entity('=')).to.be.undefined; + }); + + it("joins a <-- b ==> c", function () { + // Expected result: + // a <-- b <-- c + // tags on === reversed + var graph = iD.Graph({ + 'a': iD.Node({id: 'a'}), + 'b': iD.Node({id: 'b'}), + 'c': iD.Node({id: 'c'}), + '-': iD.Way({id: '-', nodes: ['b', 'a']}), + '=': iD.Way({id: '=', nodes: ['b', 'c']}) + }); + + graph = iD.actions.Join('-', '=')(graph); + + expect(graph.entity('-').nodes).to.eql(['c', 'b', 'a']); + expect(graph.entity('=')).to.be.undefined; + }); + + it("joins a --> b <== c", function () { + // Expected result: + // a --> b --> c + // tags on === reversed + var graph = iD.Graph({ + 'a': iD.Node({id: 'a'}), + 'b': iD.Node({id: 'b'}), + 'c': iD.Node({id: 'c'}), + '-': iD.Way({id: '-', nodes: ['a', 'b']}), + '=': iD.Way({id: '=', nodes: ['c', 'b']}) + }); + + graph = iD.actions.Join('-', '=')(graph); + + expect(graph.entity('-').nodes).to.eql(['a', 'b', 'c']); + expect(graph.entity('=')).to.be.undefined; + }); + + it("merges tags", function () { + var graph = iD.Graph({ + 'a': iD.Node({id: 'a'}), + 'b': iD.Node({id: 'b'}), + 'c': iD.Node({id: 'c'}), + '-': iD.Way({id: '-', nodes: ['a', 'b'], tags: {a: 'a', b: '-', c: 'c'}}), + '=': iD.Way({id: '=', nodes: ['b', 'c'], tags: {a: 'a', b: '=', d: 'd'}}) + }); + + graph = iD.actions.Join('-', '=')(graph); + + expect(graph.entity('-').tags).to.eql({a: 'a', b: '-; =', c: 'c', d: 'd'}); + }); + + it("merges relations", function () { + var graph = iD.Graph({ + 'a': iD.Node({id: 'a'}), + 'b': iD.Node({id: 'b'}), + 'c': iD.Node({id: 'c'}), + '-': iD.Way({id: '-', nodes: ['a', 'b']}), + '=': iD.Way({id: '=', nodes: ['b', 'c']}), + 'r1': iD.Relation({id: 'r1', members: [{id: '=', role: 'r1'}]}), + 'r2': iD.Relation({id: 'r2', members: [{id: '=', role: 'r1'}, {id: '-', role: 'r2'}]}) + }); + + graph = iD.actions.Join('-', '=')(graph); + + expect(graph.entity('r1').members).to.eql([{id: '-', role: 'r1'}]); + expect(graph.entity('r2').members).to.eql([{id: '-', role: 'r2'}]); + }); +}); From f5036db97894bdca33f02349e93dd55f8bfb195d Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Feb 2013 17:28:50 -0500 Subject: [PATCH 111/415] Start iD.operations.Merge (#435) It's currently limited to merging (joining) exactly two lines. Fixes #370. --- index.html | 1 + js/id/operations/merge.js | 27 +++++++++++++++++++++++++++ locale/en.js | 6 ++++++ test/index.html | 1 + 4 files changed, 35 insertions(+) create mode 100644 js/id/operations/merge.js diff --git a/index.html b/index.html index 07063cf81..d8be52dcd 100644 --- a/index.html +++ b/index.html @@ -114,6 +114,7 @@ + diff --git a/js/id/operations/merge.js b/js/id/operations/merge.js new file mode 100644 index 000000000..08517e9b2 --- /dev/null +++ b/js/id/operations/merge.js @@ -0,0 +1,27 @@ +iD.operations.Merge = function(selection, context) { + var action = iD.actions.Join(selection[0], selection[1]); + + var operation = function() { + context.perform( + action, + t('operations.merge.annotation', {n: selection.length})); + }; + + operation.available = function() { + return selection.length === 2 && + _.all(selection, function (id) { + return context.geometry(id) === 'line'; + }); + }; + + operation.enabled = function() { + return action.enabled(context.graph()); + }; + + operation.id = "merge"; + operation.key = t('operations.merge.key'); + operation.title = t('operations.merge.title'); + operation.description = t('operations.merge.description'); + + return operation; +}; diff --git a/locale/en.js b/locale/en.js index 7d1bed947..9e4eb5b2a 100644 --- a/locale/en.js +++ b/locale/en.js @@ -83,6 +83,12 @@ locale.en = { key: "D", annotation: "Disconnected ways." }, + merge: { + title: "Merge", + description: "Merge these lines.", + key: "C", + annotation: "Merged {n} lines." + }, move: { title: "Move", description: "Move this to a different location.", diff --git a/test/index.html b/test/index.html index 524894c36..da3cd475c 100644 --- a/test/index.html +++ b/test/index.html @@ -110,6 +110,7 @@ + From 3e404df3394152c0234152af8b5061373b30f061 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Fri, 1 Feb 2013 19:16:01 -0500 Subject: [PATCH 112/415] Add more translations, opacity to brightness YOU WIN @ansis --- js/id/ui/geocoder.js | 4 ++-- js/id/ui/inspector.js | 8 ++++---- js/id/ui/layerswitcher.js | 4 ++-- locale/en.js | 20 +++++++++++++------- 4 files changed, 21 insertions(+), 15 deletions(-) diff --git a/js/id/ui/geocoder.js b/js/id/ui/geocoder.js index d7de24b37..4aac10bf2 100644 --- a/js/id/ui/geocoder.js +++ b/js/id/ui/geocoder.js @@ -45,7 +45,7 @@ iD.ui.geocoder = function() { var button = selection.append('button') .attr('tabindex', -1) - .attr('title', 'Find A Location') + .attr('title', t('geocoder.find_location')) .html('') .on('click', toggle); @@ -53,7 +53,7 @@ iD.ui.geocoder = function() { gcForm.attr('class','content fillD map-overlay hide') .append('input') - .attr({ type: 'text', placeholder: 'find a place' }) + .attr({ type: 'text', placeholder: t('geocoder.find_a_place') }) .on('keydown', keydown); selection.call(clickoutside); diff --git a/js/id/ui/inspector.js b/js/id/ui/inspector.js index 9612cadd9..32e3fc857 100644 --- a/js/id/ui/inspector.js +++ b/js/id/ui/inspector.js @@ -35,7 +35,7 @@ iD.ui.inspector = function() { }); newTag.append('span').attr('class', 'icon icon-pre-text plus'); - newTag.append('span').attr('class','label').text('New tag') + newTag.append('span').attr('class','label').text(t('inspector.new_tag')); drawTags(entity.tags); @@ -63,7 +63,7 @@ iD.ui.inspector = function() { .attr('class', 'apply action') .on('click', apply); - inspectorButton.append('span').attr('class','label').text('Okay'); + inspectorButton.append('span').attr('class','label').text(t('okay')); var minorButtons = selection.append('div').attr('class','minor-buttons fl'); @@ -148,7 +148,7 @@ iD.ui.inspector = function() { iD.ui.flash() .select('.content') .append('h3') - .text(t('no_documentation_combination')); + .text(t('inspector.no_documentation_combination')); } }); } else if (d.key) { @@ -166,7 +166,7 @@ iD.ui.inspector = function() { iD.ui.flash() .select('.content') .append('h3') - .text(t('no_documentation_key')); + .text(t('inspector.no_documentation_key')); } }); } diff --git a/js/id/ui/layerswitcher.js b/js/id/ui/layerswitcher.js index c64457ab8..5c4835757 100644 --- a/js/id/ui/layerswitcher.js +++ b/js/id/ui/layerswitcher.js @@ -59,7 +59,7 @@ iD.ui.layerswitcher = function(context) { .append('div') .attr('class', 'opacity-options-wrapper'); - opa.append('h4').text(t('layers')); + opa.append('h4').text(t('layerswitcher.layers')); opa.append('ul') .attr('class', 'opacity-options') @@ -68,7 +68,7 @@ iD.ui.layerswitcher = function(context) { .enter() .append('li') .attr('data-original-title', function(d) { - return t('percent_opacity', { opacity: (d * 100) }); + return t('layerswitcher.percent_brightness', { opacity: (d * 100) }); }) .on('click.set-opacity', function(d) { d3.select('#tile-g') diff --git a/locale/en.js b/locale/en.js index 9e4eb5b2a..866c7eca3 100644 --- a/locale/en.js +++ b/locale/en.js @@ -136,8 +136,11 @@ locale.en = { "layer_settings": "Layer Settings", - "no_documentation_combination": "This is no documentation available for this tag combination", - "no_documentation_key": "This is no documentation available for this key", + inspector: { + no_documentation_combination: "This is no documentation available for this tag combination", + no_documentation_key: "This is no documentation available for this key", + new_tag: "New Tag" + }, "view_on_osm": "View on OSM", @@ -145,13 +148,16 @@ locale.en = { "edit_tags": "Edit tags", - "find_location": "Find A Location", - "find_placeholder": "find a place", + geocoder: { + "find_location": "Find A Location", + "find_a_place": "find a place" + }, "description": "Description", "logout": "logout", - - "layers": "Layers", - "percent_opacity": "{opacity}% opacity" + layerswitcher: { + layers: "Layers", + percent_brightness: "{opacity}% brightness" + } }; From 09dac581bebf0db7698da32eec8930256dbbb882 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sat, 2 Feb 2013 12:55:06 -0500 Subject: [PATCH 113/415] Fix MoveWay mode (fixes #602) --- js/id/modes/move_way.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/id/modes/move_way.js b/js/id/modes/move_way.js index 30c473406..986eef5bd 100644 --- a/js/id/modes/move_way.js +++ b/js/id/modes/move_way.js @@ -47,7 +47,7 @@ iD.modes.MoveWay = function(context, wayId) { context.enter(iD.modes.Browse(context)); } - context.selection() + context.surface() .on('mousemove.move-way', move) .on('click.move-way', finish); @@ -63,7 +63,7 @@ iD.modes.MoveWay = function(context, wayId) { }; mode.exit = function() { - context.selection() + context.surface() .on('mousemove.move-way', null) .on('click.move-way', null); From 8b6bb964ad85dc710dd3fb6c725afba89f4add59 Mon Sep 17 00:00:00 2001 From: Dr Ian Date: Sat, 2 Feb 2013 23:52:16 +0100 Subject: [PATCH 114/415] Added orthogonalize function (square corners) --- index.html | 2 + js/id/actions/orthogonalize.js | 132 ++++++++++++++++++++++++++++++ js/id/operations/orthogonalize.js | 25 ++++++ locale/en.js | 9 ++ 4 files changed, 168 insertions(+) create mode 100644 js/id/actions/orthogonalize.js create mode 100644 js/id/operations/orthogonalize.js diff --git a/index.html b/index.html index d8be52dcd..adfe92752 100644 --- a/index.html +++ b/index.html @@ -85,6 +85,7 @@ + @@ -112,6 +113,7 @@ + diff --git a/js/id/actions/orthogonalize.js b/js/id/actions/orthogonalize.js new file mode 100644 index 000000000..c0a9d64ed --- /dev/null +++ b/js/id/actions/orthogonalize.js @@ -0,0 +1,132 @@ +iD.actions.Orthogonalize = function(wayId, map) { + + var action = function(graph) { + var way = graph.entity(wayId), + nodes = graph.childNodes(way), + tags = {},key,role; + + var points = nodes.map(function(n) { + return map.projection(n.loc); + }), + quad_nodes = []; + + var score = squareness(); + for (var i = 0; i < 1000; ++i) { + var motions = points.map(stepMap); + //return false; + for (var j = 0; j < motions.length; ++j) { + points[j] = addPoints(points[j],motions[j]); + } + var newScore = squareness(); + if (newScore > score) { + return false; + } + score = newScore; + if (score < 1.0e-8) { + break; + } + } + for (var i = 0; i < points.length; i++) { + quad_nodes.push(iD.Node({ loc: map.projection.invert(points[i]) })); + } + quad_nodes.push(quad_nodes[0]); + + for (var i = 0; i < nodes.length; i++) { + graph = graph.remove(nodes[i]); + } + + for (var i = 0; i < quad_nodes.length; i++) { + graph = graph.replace(quad_nodes[i]); + } + + return graph.replace(way.update({ + nodes: _.pluck(quad_nodes, 'id') + })); + + + function stepMap(b,i,array){ + var a,c,p,q = []; + a = array[(i-1+array.length) % array.length]; + c = array[(i+1) % array.length]; + p = subtractPoints(a,b); + q = subtractPoints(c,b); + + + var scale = p.length + q.length; + p = normalizePoint(p,1.0); + q = normalizePoint(q,1.0); + var dotp = p[0]*q[0] + p[1]*q[1]; + // nasty hack to deal with almost-straight segments (angle is closer to 180 than to 90/270). + if (dotp < -0.707106781186547) { + dotp += 1.0; + } + var v = []; + v = addPoints(p,q); + v = normalizePoint(v,0.1 * dotp * scale); + return v; + } + + function squareness(){ + + var g = 0.0; + for (var i = 1; i < points.length - 1; i++) { + var score = scoreOfPoints(points[i-1], points[i], points[i+1]); + g += score; + } + var startScore = scoreOfPoints(points[points.length-1], points[0], points[1]); + var endScore = scoreOfPoints(points[points.length-2], points[points.length-1], points[0]); + g += startScore; + g += endScore; + return g; + } + + function scoreOfPoints(a, b, c) { + var p,q = []; + p = subtractPoints(a,b); + q = subtractPoints(c,b); + + p = normalizePoint(p,1.0); + q = normalizePoint(q,1.0); + + var dotp = p[0]*q[0] + p[1]*q[1]; + // score is constructed so that +1, -1 and 0 are all scored 0, any other angle + // is scored higher. + var score = 2.0 * Math.min(Math.abs(dotp-1.0), Math.min(Math.abs(dotp), Math.abs(dotp+1))); + return score; + } + + function subtractPoints(a,b){ + var vector = [0,0]; + vector[0] = a[0]-b[0]; + vector[1] = a[1]-b[1]; + return vector; + } + + function addPoints(a,b){ + var vector = [0,0]; + vector[0] = a[0]+b[0]; + vector[1] = a[1]+b[1]; + return vector; + } + + function normalizePoint(point,thickness){ + var vector = [0,0]; + var length = Math.sqrt( point[0] * point[0] + point[1] * point[1]); + if(length != 0){ + vector[0] = point[0]/length; + vector[1] = point[1]/length; + } + + vector[0] *= thickness; + vector[1] *= thickness; + + return vector; + } + }; + + action.enabled = function(graph) { + return graph.entity(wayId).isClosed(); + }; + + return action; +}; diff --git a/js/id/operations/orthogonalize.js b/js/id/operations/orthogonalize.js new file mode 100644 index 000000000..888d0015e --- /dev/null +++ b/js/id/operations/orthogonalize.js @@ -0,0 +1,25 @@ +iD.operations.Orthogonalize = function(selection, context) { + var entityId = selection[0], + action = iD.actions.Orthogonalize(entityId, context.map()); + + var operation = function() { + var annotation = t('operations.orthogonalize.annotation.' + context.geometry(entityId)); + context.perform(action, annotation); + }; + + operation.available = function() { + return selection.length === 1 && + context.entity(entityId).type === 'way'; + }; + + operation.enabled = function() { + return action.enabled(context.graph()); + }; + + operation.id = "orthogonalize"; + operation.key = t('operations.orthogonalize.key'); + operation.title = t('operations.orthogonalize.title'); + operation.description = t('operations.orthogonalize.description'); + + return operation; +}; diff --git a/locale/en.js b/locale/en.js index 866c7eca3..31d6a13de 100644 --- a/locale/en.js +++ b/locale/en.js @@ -65,6 +65,15 @@ locale.en = { area: "Made an area circular." } }, + orthogonalize: { + title: "Orthogonalize", + description: "Square these corners.", + key: "Q", + annotation: { + line: "Squared the corners of a line.", + area: "Squared the corners of an area." + } + }, delete: { title: "Delete", description: "Remove this from the map.", From 26b8b8789e1415c0e3adca277deb2131c929ca28 Mon Sep 17 00:00:00 2001 From: Dr Ian Date: Sun, 3 Feb 2013 00:05:11 +0100 Subject: [PATCH 115/415] Fix case where parentnodes present --- js/id/actions/orthogonalize.js | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/js/id/actions/orthogonalize.js b/js/id/actions/orthogonalize.js index c0a9d64ed..80c6a12c8 100644 --- a/js/id/actions/orthogonalize.js +++ b/js/id/actions/orthogonalize.js @@ -19,7 +19,7 @@ iD.actions.Orthogonalize = function(wayId, map) { } var newScore = squareness(); if (newScore > score) { - return false; + return graph; } score = newScore; if (score < 1.0e-8) { @@ -31,8 +31,22 @@ iD.actions.Orthogonalize = function(wayId, map) { } quad_nodes.push(quad_nodes[0]); - for (var i = 0; i < nodes.length; i++) { + for (i = 0; i < nodes.length; i++) { + if (graph.parentWays(nodes[i]).length > 1) { + var closest, closest_dist = Infinity, dist; + for (var j = 0; j < quad_nodes.length; j++) { + dist = iD.geo.dist(quad_nodes[j].loc, nodes[i].loc); + if (dist < closest_dist) { + closest_dist = dist; + closest = j; + } + } + quad_nodes.splice(closest, 1, nodes[i]); + if (closest === 0) quad_nodes.splice(quad_nodes.length - 1, 1, nodes[i]); + else if (closest === quad_nodes.length - 1) quad_nodes.splice(0, 1, nodes[i]); + } else { graph = graph.remove(nodes[i]); + } } for (var i = 0; i < quad_nodes.length; i++) { From ddc5e324f6cfa7fdcc25bfc679b0f25c70ad97b2 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sat, 2 Feb 2013 18:28:44 -0500 Subject: [PATCH 116/415] Extract iD.Difference iD.Difference represents the difference between two graphs. It knows how to calculate the set of entities that were created, modified, or deleted, and also contains the logic for recursively extending a difference to the complete set of entities that will require a redraw, taking into account child and parent relationships. Additionally, all history mutators now return a difference. --- index.html | 1 + js/id/graph/difference.js | 113 +++++++++++++++++++ js/id/graph/graph.js | 60 ---------- js/id/graph/history.js | 44 ++++---- js/id/renderer/map.js | 33 +----- test/index.html | 2 + test/index_packaged.html | 1 + test/spec/graph/difference.js | 202 ++++++++++++++++++++++++++++++++++ test/spec/graph/graph.js | 82 -------------- test/spec/graph/history.js | 41 +++++-- 10 files changed, 375 insertions(+), 204 deletions(-) create mode 100644 js/id/graph/difference.js create mode 100644 test/spec/graph/difference.js diff --git a/index.html b/index.html index d8be52dcd..f9b627c16 100644 --- a/index.html +++ b/index.html @@ -119,6 +119,7 @@ + diff --git a/js/id/graph/difference.js b/js/id/graph/difference.js new file mode 100644 index 000000000..8bea5664c --- /dev/null +++ b/js/id/graph/difference.js @@ -0,0 +1,113 @@ +/* + iD.Difference represents the difference between two graphs. + It knows how to calculate the set of entities that were + created, modified, or deleted, and also contains the logic + for recursively extending a difference to the complete set + of entities that will require a redraw, taking into account + child and parent relationships. + */ +iD.Difference = function (base, head) { + var changes = {}, length = 0; + + _.each(head.entities, function(h, id) { + var b = base.entities[id]; + if (h !== b) { + changes[id] = {base: b, head: h}; + length++; + } + }); + + _.each(base.entities, function(b, id) { + var h = head.entities[id]; + if (!changes[id] && h !== b) { + changes[id] = {base: b, head: h}; + length++; + } + }); + + var difference = {}; + + difference.length = function () { + return length; + }; + + difference.changes = function() { + return changes; + }; + + difference.modified = function() { + var result = []; + _.each(changes, function(change) { + if (change.base && change.head) result.push(change.head); + }); + return result; + }; + + difference.created = function() { + var result = []; + _.each(changes, function(change) { + if (!change.base && change.head) result.push(change.head); + }); + return result; + }; + + difference.deleted = function() { + var result = []; + _.each(changes, function(change) { + if (change.base && !change.head) result.push(change.base); + }); + return result; + }; + + difference.complete = function(extent) { + var result = {}, id, change; + + function addParents(parents) { + for (var i = 0; i < parents.length; i++) { + var parent = parents[i]; + + if (parent.id in result) + continue; + + result[parent.id] = parent; + addParents(head.parentRelations(parent)); + } + } + + for (id in changes) { + change = changes[id]; + + var h = change.head, + b = change.base, + entity = h || b; + + if (extent && !entity.intersects(extent, h ? head : base)) + continue; + + result[id] = h; + + if (entity.type === 'way') { + var nh = h ? h.nodes : [], + nb = b ? b.nodes : [], + diff; + + diff = _.difference(nh, nb); + for (var i = 0; i < diff.length; i++) { + result[diff[i]] = head.entity(diff[i]); + } + + diff = _.difference(nb, nh); + for (var i = 0; i < diff.length; i++) { + result[diff[i]] = head.entity(diff[i]); + } + } + + addParents(head.parentWays(entity)); + addParents(head.parentRelations(entity)); + } + + return result; + }; + + return difference; +}; diff --git a/js/id/graph/graph.js b/js/id/graph/graph.js index a19bfe094..2692aeba6 100644 --- a/js/id/graph/graph.js +++ b/js/id/graph/graph.js @@ -232,65 +232,5 @@ iD.Graph.prototype = { } } return items; - }, - - difference: function (graph) { - - function diff(a, b) { - var result = [], - keys = Object.keys(a.entities), - entity, oldentity, id, i; - - for (i = 0; i < keys.length; i++) { - id = keys[i]; - entity = a.entities[id]; - oldentity = b.entities[id]; - if (entity !== oldentity) { - - // maybe adding affected children better belongs in renderer/map.js? - if (entity && entity.type === 'way' && - oldentity && oldentity.type === 'way') { - result = result - .concat(_.difference(entity.nodes, oldentity.nodes)) - .concat(_.difference(oldentity.nodes, entity.nodes)); - - } else if (entity && entity.type === 'way') { - result = result.concat(entity.nodes); - - } else if (oldentity && oldentity.type === 'way') { - result = result.concat(oldentity.nodes); - } - - result.push(id); - } - } - return result; - } - - return _.unique(diff(this, graph).concat(diff(graph, this)).sort()); - }, - - modified: function() { - var result = [], base = this.base().entities; - _.each(this.entities, function(entity, id) { - if (entity && base[id]) result.push(id); - }); - return result; - }, - - created: function() { - var result = [], base = this.base().entities; - _.each(this.entities, function(entity, id) { - if (entity && !base[id]) result.push(id); - }); - return result; - }, - - deleted: function() { - var result = [], base = this.base().entities; - _.each(this.entities, function(entity, id) { - if (!entity && base[id]) result.push(id); - }); - return result; } }; diff --git a/js/id/graph/history.js b/js/id/graph/history.js index 42e1b1604..cbadbf87f 100644 --- a/js/id/graph/history.js +++ b/js/id/graph/history.js @@ -21,7 +21,9 @@ iD.History = function() { } function change(previous) { - dispatch.change(history.graph().difference(previous)); + var difference = iD.Difference(previous, history.graph()); + dispatch.change(difference); + return difference; } var history = { @@ -42,7 +44,7 @@ iD.History = function() { stack.push(perform(arguments)); index++; - change(previous); + return change(previous); }, replace: function () { @@ -51,7 +53,7 @@ iD.History = function() { // assert(index == stack.length - 1) stack[index] = perform(arguments); - change(previous); + return change(previous); }, pop: function () { @@ -60,7 +62,7 @@ iD.History = function() { if (index > 0) { index--; stack.pop(); - change(previous); + return change(previous); } }, @@ -80,7 +82,7 @@ iD.History = function() { } dispatch.undone(); - change(previous); + return change(previous); }, redo: function () { @@ -92,7 +94,7 @@ iD.History = function() { } dispatch.redone(); - change(previous); + return change(previous); }, undoAnnotation: function () { @@ -111,31 +113,27 @@ iD.History = function() { } }, - changes: function () { - var initial = stack[0].graph, - current = stack[index].graph; + difference: function () { + var base = stack[0].graph, + head = stack[index].graph; + return iD.Difference(base, head); + }, + changes: function () { + var difference = history.difference(); return { - modified: current.modified().map(function (id) { - return current.entity(id); - }), - created: current.created().map(function (id) { - return current.entity(id); - }), - deleted: current.deleted().map(function (id) { - return initial.entity(id); - }) - }; + modified: difference.modified(), + created: difference.created(), + deleted: difference.deleted() + } }, hasChanges: function() { - return !!this.numChanges(); + return this.difference().length() > 0; }, numChanges: function() { - return d3.sum(d3.values(this.changes()).map(function(c) { - return c.length; - })); + return this.difference().length(); }, imagery_used: function(source) { diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index 1f22a7c81..839aa18a3 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -64,44 +64,19 @@ iD.Map = function(context) { extent = map.extent(), graph = context.graph(); - function addParents(parents) { - for (var i = 0; i < parents.length; i++) { - var parent = parents[i]; - if (only[parent.id] === undefined) { - only[parent.id] = parent; - addParents(graph.parentRelations(parent)); - } - } - } - if (!difference) { all = graph.intersects(extent); filter = d3.functor(true); } else { - var only = {}; - - for (var j = 0; j < difference.length; j++) { - var id = difference[j], - entity = graph.entity(id); - - // Even if the entity is false (deleted), it needs to be - // removed from the surface - only[id] = entity; - - if (entity && entity.intersects(extent, graph)) { - addParents(graph.parentWays(only[id])); - addParents(graph.parentRelations(only[id])); - } - } - - all = _.compact(_.values(only)); + var complete = difference.complete(extent); + all = _.compact(_.values(complete)); filter = function(d) { if (d.type === 'midpoint') { for (var i = 0; i < d.ways.length; i++) { - if (d.ways[i].id in only) return true; + if (d.ways[i].id in complete) return true; } } else { - return d.id in only; + return d.id in complete; } }; } diff --git a/test/index.html b/test/index.html index da3cd475c..b6d925923 100644 --- a/test/index.html +++ b/test/index.html @@ -115,6 +115,7 @@ + @@ -164,6 +165,7 @@ + diff --git a/test/index_packaged.html b/test/index_packaged.html index 948ed0ae1..ef91e2823 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -55,6 +55,7 @@ + diff --git a/test/spec/graph/difference.js b/test/spec/graph/difference.js new file mode 100644 index 000000000..f12a77254 --- /dev/null +++ b/test/spec/graph/difference.js @@ -0,0 +1,202 @@ +describe("iD.Difference", function () { + describe("#changes", function () { + it("includes created entities", function () { + var node = iD.Node({id: 'n'}), + base = iD.Graph(), + head = base.replace(node), + diff = iD.Difference(base, head); + expect(diff.changes()).to.eql({n: {base: undefined, head: node}}); + }); + + it("includes undone created entities", function () { + var node = iD.Node({id: 'n'}), + base = iD.Graph(), + head = base.replace(node), + diff = iD.Difference(head, base); + expect(diff.changes()).to.eql({n: {base: node, head: undefined}}); + }); + + it("includes modified entities", function () { + var n1 = iD.Node({id: 'n'}), + n2 = n1.update(), + base = iD.Graph([n1]), + head = base.replace(n2), + diff = iD.Difference(base, head); + expect(diff.changes()).to.eql({n: {base: n1, head: n2}}); + }); + + it("includes undone modified entities", function () { + var n1 = iD.Node({id: 'n'}), + n2 = n1.update(), + base = iD.Graph([n1]), + head = base.replace(n2), + diff = iD.Difference(head, base); + expect(diff.changes()).to.eql({n: {base: n2, head: n1}}); + }); + + it("includes deleted entities", function () { + var node = iD.Node({id: 'n'}), + base = iD.Graph([node]), + head = base.remove(node), + diff = iD.Difference(base, head); + expect(diff.changes()).to.eql({n: {base: node, head: undefined}}); + }); + + it("includes undone deleted entities", function () { + var node = iD.Node({id: 'n'}), + base = iD.Graph([node]), + head = base.remove(node), + diff = iD.Difference(head, base); + expect(diff.changes()).to.eql({n: {base: undefined, head: node}}); + }); + + it("doesn't include created entities that were subsequently deleted", function () { + var node = iD.Node(), + base = iD.Graph(), + head = base.replace(node).remove(node), + diff = iD.Difference(base, head); + expect(diff.changes()).to.eql({}); + }); + }); + + describe("#created", function () { + it("returns an array of created entities", function () { + var node = iD.Node({id: 'n'}), + base = iD.Graph(), + head = base.replace(node), + diff = iD.Difference(base, head); + expect(diff.created()).to.eql([node]); + }); + }); + + describe("#modified", function () { + it("returns an array of modified entities", function () { + var n1 = iD.Node({id: 'n'}), + n2 = n1.move([1, 2]), + base = iD.Graph([n1]), + head = base.replace(n2), + diff = iD.Difference(base, head); + expect(diff.modified()).to.eql([n2]); + }); + }); + + describe("#deleted", function () { + it("returns an array of deleted entities", function () { + var node = iD.Node({id: 'n'}), + base = iD.Graph([node]), + head = base.remove(node), + diff = iD.Difference(base, head); + expect(diff.deleted()).to.eql([node]); + }); + }); + + describe("#complete", function () { + it("includes created entities", function () { + var node = iD.Node({id: 'n'}), + base = iD.Graph(), + head = base.replace(node), + diff = iD.Difference(base, head); + expect(diff.complete()['n']).to.equal(node); + }); + + it("includes modified entities", function () { + var n1 = iD.Node({id: 'n'}), + n2 = n1.move([1, 2]), + base = iD.Graph([n1]), + head = base.replace(n2), + diff = iD.Difference(base, head); + expect(diff.complete()['n']).to.equal(n2); + }); + + it("includes deleted entities", function () { + var node = iD.Node({id: 'n'}), + base = iD.Graph([node]), + head = base.remove(node), + diff = iD.Difference(base, head); + expect(diff.complete()).to.eql({n: undefined}); + }); + + it("includes nodes added to a way", function () { + var n1 = iD.Node({id: 'n1'}), + n2 = iD.Node({id: 'n2'}), + w1 = iD.Way({id: 'w', nodes: ['n1']}), + w2 = w1.addNode('n2'), + base = iD.Graph([n1, n2, w1]), + head = base.replace(w2), + diff = iD.Difference(base, head); + + expect(diff.complete()['n2']).to.equal(n2); + }); + + it("includes nodes removed from a way", function () { + var n1 = iD.Node({id: 'n1'}), + n2 = iD.Node({id: 'n2'}), + w1 = iD.Way({id: 'w', nodes: ['n1', 'n2']}), + w2 = w1.removeNode('n2'), + base = iD.Graph([n1, n2, w1]), + head = base.replace(w2), + diff = iD.Difference(base, head); + + expect(diff.complete()['n2']).to.equal(n2); + }); + + it("includes parent ways of modified nodes", function () { + var n1 = iD.Node({id: 'n'}), + n2 = n1.move([1, 2]), + way = iD.Way({id: 'w', nodes: ['n']}), + base = iD.Graph([n1, way]), + head = base.replace(n2), + diff = iD.Difference(base, head); + + expect(diff.complete()['w']).to.equal(way); + }); + + it("includes parent relations of modified entities", function () { + var n1 = iD.Node({id: 'n'}), + n2 = n1.move([1, 2]), + rel = iD.Relation({id: 'r', members: [{id: 'n'}]}), + base = iD.Graph([n1, rel]), + head = base.replace(n2), + diff = iD.Difference(base, head); + + expect(diff.complete()['r']).to.equal(rel); + }); + + it("includes parent relations of modified entities, recursively", function () { + var n1 = iD.Node({id: 'n'}), + n2 = n1.move([1, 2]), + rel1 = iD.Relation({id: 'r1', members: [{id: 'n'}]}), + rel2 = iD.Relation({id: 'r2', members: [{id: 'r1'}]}), + base = iD.Graph([n1, rel1, rel2]), + head = base.replace(n2), + diff = iD.Difference(base, head); + + expect(diff.complete()['r2']).to.equal(rel2); + }); + + it("includes parent relations of parent ways of modified nodes", function () { + var n1 = iD.Node({id: 'n'}), + n2 = n1.move([1, 2]), + way = iD.Way({id: 'w', nodes: ['n']}), + rel = iD.Relation({id: 'r', members: [{id: 'w'}]}), + base = iD.Graph([n1, way, rel]), + head = base.replace(n2), + diff = iD.Difference(base, head); + + expect(diff.complete()['r']).to.equal(rel); + }); + + it("copes with recursive relations", function () { + var node = iD.Node({id: 'n'}), + rel1 = iD.Relation({id: 'r1', members: [{id: 'n'}, {id: 'r2'}]}), + rel2 = iD.Relation({id: 'r2', members: [{id: 'r1'}]}), + base = iD.Graph([node, rel1, rel2]), + head = base.replace(node.move([1, 2])), + diff = iD.Difference(base, head); + + expect(diff.complete()).to.be.ok; + }); + + it("limits changes to those within a given extent"); + }); +}); diff --git a/test/spec/graph/graph.js b/test/spec/graph/graph.js index 05bcbbe89..a357ff360 100644 --- a/test/spec/graph/graph.js +++ b/test/spec/graph/graph.js @@ -333,86 +333,4 @@ describe('iD.Graph', function() { expect(graph.childNodes(way)).to.eql([node]); }); }); - - describe("#difference", function () { - it("returns an Array of ids of changed entities", function () { - var initial = iD.Node({id: "n1"}), - updated = initial.update({}), - created = iD.Node(), - deleted = iD.Node({id: 'n2'}), - graph1 = iD.Graph([initial, deleted]), - graph2 = graph1.replace(updated).replace(created).remove(deleted); - expect(graph2.difference(graph1)).to.eql([created.id, updated.id, deleted.id]); - }); - - - it("includes created entities, and reverse", function () { - var node = iD.Node(), - graph1 = iD.Graph(), - graph2 = graph1.replace(node); - expect(graph2.difference(graph1)).to.eql([node.id]); - expect(graph1.difference(graph2)).to.eql([node.id]); - }); - - it("includes entities changed from base, and reverse", function () { - var node = iD.Node(), - graph1 = iD.Graph(node), - graph2 = graph1.replace(node.update()); - expect(graph2.difference(graph1)).to.eql([node.id]); - expect(graph1.difference(graph2)).to.eql([node.id]); - }); - - it("includes already changed entities that were updated, and reverse", function () { - var node = iD.Node(), - graph1 = iD.Graph().replace(node), - graph2 = graph1.replace(node.update()); - expect(graph2.difference(graph1)).to.eql([node.id]); - expect(graph1.difference(graph2)).to.eql([node.id]); - }); - - it("includes affected child nodes", function () { - var n = iD.Node({id: 'n'}), - n2 = iD.Node({id: 'n2'}), - w1 = iD.Way({id: 'w1', nodes: ['n']}), - w1_ = iD.Way({id: 'w1', nodes: ['n', 'n2']}), - graph1 = iD.Graph([n, n2, w1]), - graph2 = graph1.replace(w1_); - expect(graph2.difference(graph1)).to.eql(['n2', 'w1']); - expect(graph1.difference(graph2)).to.eql(['n2', 'w1']); - }); - - }); - - describe("#modified", function () { - it("returns an Array of ids of modified entities", function () { - var node = iD.Node({id: 'n1'}), - node_ = iD.Node({id: 'n1'}), - graph = iD.Graph([node]).replace(node_); - expect(graph.modified()).to.eql([node.id]); - }); - }); - - describe("#created", function () { - it("returns an Array of ids of created entities", function () { - var node1 = iD.Node({id: 'n-1'}), - node2 = iD.Node({id: 'n2'}), - graph = iD.Graph([node2]).replace(node1); - expect(graph.created()).to.eql([node1.id]); - }); - }); - - describe("#deleted", function () { - it("returns an Array of ids of deleted entities", function () { - var node1 = iD.Node({id: "n1"}), - node2 = iD.Node(), - graph = iD.Graph([node1, node2]).remove(node1); - expect(graph.deleted()).to.eql([node1.id]); - }); - - it("doesn't include created entities that were subsequently deleted", function () { - var node = iD.Node(), - graph = iD.Graph().replace(node).remove(node); - expect(graph.deleted()).to.eql([]); - }); - }); }); diff --git a/test/spec/graph/history.js b/test/spec/graph/history.js index dc685b446..3f28f437e 100644 --- a/test/spec/graph/history.js +++ b/test/spec/graph/history.js @@ -14,6 +14,10 @@ describe("iD.History", function () { }); describe("#perform", function () { + it("returns a difference", function () { + expect(history.perform(action).changes()).to.eql({}); + }); + it("updates the graph", function () { var node = iD.Node(); history.perform(function (graph) { return graph.replace(node); }); @@ -27,8 +31,8 @@ describe("iD.History", function () { it("emits a change event", function () { history.on('change', spy); - history.perform(action); - expect(spy).to.have.been.calledWith([]); + var difference = history.perform(action); + expect(spy).to.have.been.calledWith(difference); }); it("performs multiple actions", function () { @@ -42,6 +46,10 @@ describe("iD.History", function () { }); describe("#replace", function () { + it("returns a difference", function () { + expect(history.replace(action).changes()).to.eql({}); + }); + it("updates the graph", function () { var node = iD.Node(); history.replace(function (graph) { return graph.replace(node); }); @@ -56,8 +64,8 @@ describe("iD.History", function () { it("emits a change event", function () { history.on('change', spy); - history.replace(action); - expect(spy).to.have.been.calledWith([]); + var difference = history.replace(action); + expect(spy).to.have.been.calledWith(difference); }); it("performs multiple actions", function () { @@ -71,6 +79,11 @@ describe("iD.History", function () { }); describe("#pop", function () { + it("returns a difference", function () { + history.perform(action, "annotation"); + expect(history.pop().changes()).to.eql({}); + }); + it("updates the graph", function () { history.perform(action, "annotation"); history.pop(); @@ -86,12 +99,16 @@ describe("iD.History", function () { it("emits a change event", function () { history.perform(action); history.on('change', spy); - history.pop(); - expect(spy).to.have.been.calledWith([]); + var difference = history.pop(); + expect(spy).to.have.been.calledWith(difference); }); }); describe("#undo", function () { + it("returns a difference", function () { + expect(history.undo().changes()).to.eql({}); + }); + it("pops the undo stack", function () { history.perform(action, "annotation"); history.undo(); @@ -121,12 +138,16 @@ describe("iD.History", function () { it("emits a change event", function () { history.perform(action); history.on('change', spy); - history.undo(); - expect(spy).to.have.been.calledWith([]); + var difference = history.undo(); + expect(spy).to.have.been.calledWith(difference); }); }); describe("#redo", function () { + it("returns a difference", function () { + expect(history.redo().changes()).to.eql({}); + }); + it("emits an redone event", function () { history.perform(action); history.undo(); @@ -139,8 +160,8 @@ describe("iD.History", function () { history.perform(action); history.undo(); history.on('change', spy); - history.redo(); - expect(spy).to.have.been.calledWith([]); + var difference = history.redo(); + expect(spy).to.have.been.calledWith(difference); }); }); From 11d723819dc222e946074b70a5980c59c9890c1d Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sat, 2 Feb 2013 19:13:53 -0500 Subject: [PATCH 117/415] Difference#extantIDs --- js/id/graph/difference.js | 8 ++++++++ test/spec/graph/difference.js | 27 +++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/js/id/graph/difference.js b/js/id/graph/difference.js index 8bea5664c..e2159271a 100644 --- a/js/id/graph/difference.js +++ b/js/id/graph/difference.js @@ -35,6 +35,14 @@ iD.Difference = function (base, head) { return changes; }; + difference.extantIDs = function() { + var result = []; + _.each(changes, function(change, id) { + if (change.head) result.push(id); + }); + return result; + }; + difference.modified = function() { var result = []; _.each(changes, function(change) { diff --git a/test/spec/graph/difference.js b/test/spec/graph/difference.js index f12a77254..c9647bbea 100644 --- a/test/spec/graph/difference.js +++ b/test/spec/graph/difference.js @@ -59,6 +59,33 @@ describe("iD.Difference", function () { }); }); + describe("#extantIDs", function () { + it("includes the ids of created entities", function () { + var node = iD.Node({id: 'n'}), + base = iD.Graph(), + head = base.replace(node), + diff = iD.Difference(base, head); + expect(diff.extantIDs()).to.eql(['n']); + }); + + it("includes the ids of modified entities", function () { + var n1 = iD.Node({id: 'n'}), + n2 = n1.move([1, 2]), + base = iD.Graph([n1]), + head = base.replace(n2), + diff = iD.Difference(base, head); + expect(diff.extantIDs()).to.eql(['n']); + }); + + it("omits the ids of deleted entities", function () { + var node = iD.Node({id: 'n'}), + base = iD.Graph([node]), + head = base.remove(node), + diff = iD.Difference(base, head); + expect(diff.extantIDs()).to.eql([]); + }); + }); + describe("#created", function () { it("returns an array of created entities", function () { var node = iD.Node({id: 'n'}), From 1de05b518dcec864ca39429a3e0a394b31958763 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sat, 2 Feb 2013 19:15:28 -0500 Subject: [PATCH 118/415] Select result when splitting or joining ways Fixes #601. Fixes #603. --- js/id/operations/merge.js | 6 +++--- js/id/operations/split.js | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/js/id/operations/merge.js b/js/id/operations/merge.js index 08517e9b2..d02222368 100644 --- a/js/id/operations/merge.js +++ b/js/id/operations/merge.js @@ -2,9 +2,9 @@ iD.operations.Merge = function(selection, context) { var action = iD.actions.Join(selection[0], selection[1]); var operation = function() { - context.perform( - action, - t('operations.merge.annotation', {n: selection.length})); + var annotation = t('operations.merge.annotation', {n: selection.length}), + difference = context.perform(action, annotation); + context.enter(iD.modes.Select(context, difference.extantIDs())); }; operation.available = function() { diff --git a/js/id/operations/split.js b/js/id/operations/split.js index 07d7d56ff..f49f4b9ed 100644 --- a/js/id/operations/split.js +++ b/js/id/operations/split.js @@ -3,7 +3,9 @@ iD.operations.Split = function(selection, context) { action = iD.actions.Split(entityId); var operation = function() { - context.perform(action, t('operations.split.annotation')); + var annotation = t('operations.split.annotation'), + difference = context.perform(action, annotation); + context.enter(iD.modes.Select(context, difference.extantIDs())); }; operation.available = function() { From fc00f154a913788b5d16fed1a917a4c3fce80491 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sat, 2 Feb 2013 19:44:47 -0500 Subject: [PATCH 119/415] Dispatch a change event on merge --- js/id/graph/history.js | 2 ++ js/id/renderer/map.js | 1 - test/spec/graph/history.js | 14 ++++++++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/js/id/graph/history.js b/js/id/graph/history.js index cbadbf87f..3cc1ace01 100644 --- a/js/id/graph/history.js +++ b/js/id/graph/history.js @@ -35,6 +35,8 @@ iD.History = function() { for (var i = 0; i < stack.length; i++) { stack[i].graph.rebase(entities); } + + dispatch.change(); }, perform: function () { diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index 839aa18a3..c3b528da3 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -102,7 +102,6 @@ iD.Map = function(context) { function connectionLoad(err, result) { context.history().merge(result); - redraw(Object.keys(result)); } function zoomPan() { diff --git a/test/spec/graph/history.js b/test/spec/graph/history.js index 3f28f437e..0ecbed5d3 100644 --- a/test/spec/graph/history.js +++ b/test/spec/graph/history.js @@ -13,6 +13,20 @@ describe("iD.History", function () { }); }); + describe("#merge", function () { + it("merges the entities into all graph versions", function () { + var n = iD.Node({id: 'n'}); + history.merge({n: n}); + expect(history.graph().entity('n')).to.equal(n); + }); + + it("emits a change event", function () { + history.on('change', spy); + history.merge({}); + expect(spy).to.have.been.called; + }); + }); + describe("#perform", function () { it("returns a difference", function () { expect(history.perform(action).changes()).to.eql({}); From ec602a7db7ef139baa1614bcb5c9c8cb0ec43f54 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sat, 2 Feb 2013 19:46:59 -0500 Subject: [PATCH 120/415] Hook up connection and history in context --- js/id/id.js | 4 ++++ js/id/renderer/map.js | 7 ------- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/js/id/id.js b/js/id/id.js index 358610525..56d3e481a 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -16,6 +16,10 @@ window.iD = function () { // the connection requires .storage() to be available on calling. var connection = iD.Connection(context); + connection.on('load.context', function (err, result) { + history.merge(result); + }); + /* Straight accessors. Avoid using these if you can. */ context.ui = function() { return ui; }; context.connection = function() { return connection; }; diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index c3b528da3..b2561f21f 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -26,9 +26,6 @@ iD.Map = function(context) { surface, tilegroup; function map(selection) { - context.connection() - .on('load.tile', connectionLoad); - context.history() .on('change.map', redraw); @@ -100,10 +97,6 @@ iD.Map = function(context) { surface.selectAll('.layer *').remove(); } - function connectionLoad(err, result) { - context.history().merge(result); - } - function zoomPan() { if (d3.event && d3.event.sourceEvent.type === 'dblclick') { if (!dblclickEnabled) { From 80a5a083b0affb576065facd7260073835ff4d0d Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sat, 2 Feb 2013 19:55:52 -0500 Subject: [PATCH 121/415] Remove unused --- js/id/graph/entity.js | 11 +--------- test/spec/graph/entity.js | 42 ------------------------------------- test/spec/graph/node.js | 9 -------- test/spec/graph/relation.js | 9 -------- test/spec/graph/way.js | 9 -------- 5 files changed, 1 insertion(+), 79 deletions(-) diff --git a/js/id/graph/entity.js b/js/id/graph/entity.js index ae612c268..bce9675ee 100644 --- a/js/id/graph/entity.js +++ b/js/id/graph/entity.js @@ -43,7 +43,6 @@ iD.Entity.prototype = { if (!this.id && this.type) { this.id = iD.Entity.id(this.type); - this._updated = true; } if (iD.debug) { @@ -63,7 +62,7 @@ iD.Entity.prototype = { }, update: function(attrs) { - return iD.Entity(this, attrs, {_updated: true}); + return iD.Entity(this, attrs); }, mergeTags: function(tags) { @@ -80,14 +79,6 @@ iD.Entity.prototype = { return this.update({tags: merged}); }, - created: function() { - return this._updated && this.osmId().charAt(0) === '-'; - }, - - modified: function() { - return this._updated && this.osmId().charAt(0) !== '-'; - }, - intersects: function(extent, resolver) { return this.extent(resolver).intersects(extent); }, diff --git a/test/spec/graph/entity.js b/test/spec/graph/entity.js index 088ab5516..f2ac85f17 100644 --- a/test/spec/graph/entity.js +++ b/test/spec/graph/entity.js @@ -52,12 +52,6 @@ describe('iD.Entity', function () { expect(e.id).to.equal('w1'); }); - it("tags the entity as updated", function () { - var tags = {foo: 'bar'}, - e = iD.Entity().update({tags: tags}); - expect(e._updated).to.to.be.true; - }); - it("doesn't modify the input", function () { var attrs = {tags: {foo: 'bar'}}, e = iD.Entity().update(attrs); @@ -104,42 +98,6 @@ describe('iD.Entity', function () { }); }); - describe("#created", function () { - it("returns falsy by default", function () { - expect(iD.Entity({id: 'w1234'}).created()).not.to.be.ok; - }); - - it("returns falsy for an unmodified Entity", function () { - expect(iD.Entity({id: 'w1234'}).created()).not.to.be.ok; - }); - - it("returns falsy for a modified Entity with positive ID", function () { - expect(iD.Entity({id: 'w1234'}).update({}).created()).not.to.be.ok; - }); - - it("returns truthy for a modified Entity with negative ID", function () { - expect(iD.Entity({id: 'w-1234'}).update({}).created()).to.be.ok; - }); - }); - - describe("#modified", function () { - it("returns falsy by default", function () { - expect(iD.Entity({id: 'w1234'}).modified()).not.to.be.ok; - }); - - it("returns falsy for an unmodified Entity", function () { - expect(iD.Entity({id: 'w1234'}).modified()).not.to.be.ok; - }); - - it("returns truthy for a modified Entity with positive ID", function () { - expect(iD.Entity({id: 'w1234'}).update({}).modified()).to.be.ok; - }); - - it("returns falsy for a modified Entity with negative ID", function () { - expect(iD.Entity({id: 'w-1234'}).update({}).modified()).not.to.be.ok; - }); - }); - describe("#intersects", function () { it("returns true for a way with a node within the given extent", function () { var node = iD.Node({loc: [0, 0]}), diff --git a/test/spec/graph/node.js b/test/spec/graph/node.js index ad149261e..71a2c5e95 100644 --- a/test/spec/graph/node.js +++ b/test/spec/graph/node.js @@ -4,15 +4,6 @@ describe('iD.Node', function () { expect(iD.Node().type).to.equal("node"); }); - it("returns a created Entity if no ID is specified", function () { - expect(iD.Node().created()).to.be.ok; - }); - - it("returns an unmodified Entity if ID is specified", function () { - expect(iD.Node({id: 'n1234'}).created()).not.to.be.ok; - expect(iD.Node({id: 'n1234'}).modified()).not.to.be.ok; - }); - it("defaults tags to an empty object", function () { expect(iD.Node().tags).to.eql({}); }); diff --git a/test/spec/graph/relation.js b/test/spec/graph/relation.js index 8edf9c1e7..6e00dcacb 100644 --- a/test/spec/graph/relation.js +++ b/test/spec/graph/relation.js @@ -10,15 +10,6 @@ describe('iD.Relation', function () { expect(iD.Relation().type).to.equal("relation"); }); - it("returns a created Entity if no ID is specified", function () { - expect(iD.Relation().created()).to.be.ok; - }); - - it("returns an unmodified Entity if ID is specified", function () { - expect(iD.Relation({id: 'r1234'}).created()).not.to.be.ok; - expect(iD.Relation({id: 'r1234'}).modified()).not.to.be.ok; - }); - it("defaults members to an empty array", function () { expect(iD.Relation().members).to.eql([]); }); diff --git a/test/spec/graph/way.js b/test/spec/graph/way.js index ec41edec6..6b6b1543f 100644 --- a/test/spec/graph/way.js +++ b/test/spec/graph/way.js @@ -10,15 +10,6 @@ describe('iD.Way', function() { expect(iD.Way().type).to.equal("way"); }); - it("returns a created Entity if no ID is specified", function () { - expect(iD.Way().created()).to.be.ok; - }); - - it("returns an unmodified Entity if ID is specified", function () { - expect(iD.Way({id: 'w1234'}).created()).not.to.be.ok; - expect(iD.Way({id: 'w1234'}).modified()).not.to.be.ok; - }); - it("defaults nodes to an empty array", function () { expect(iD.Way().nodes).to.eql([]); }); From b08722fd3fb3cbfdf19af48926d4b3185cbbcdf2 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sat, 2 Feb 2013 19:59:05 -0500 Subject: [PATCH 122/415] Pass just the projection to Circularize --- js/id/actions/circularize.js | 6 +++--- js/id/operations/circularize.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/js/id/actions/circularize.js b/js/id/actions/circularize.js index edbd0d8b3..d844134eb 100644 --- a/js/id/actions/circularize.js +++ b/js/id/actions/circularize.js @@ -1,4 +1,4 @@ -iD.actions.Circularize = function(wayId, map) { +iD.actions.Circularize = function(wayId, projection) { var action = function(graph) { var way = graph.entity(wayId), @@ -6,7 +6,7 @@ iD.actions.Circularize = function(wayId, map) { tags = {}, key, role; var points = nodes.map(function(n) { - return map.projection(n.loc); + return projection(n.loc); }), centroid = d3.geom.polygon(points).centroid(), radius = d3.median(points, function(p) { @@ -15,7 +15,7 @@ iD.actions.Circularize = function(wayId, map) { circular_nodes = []; for (var i = 0; i < 12; i++) { - circular_nodes.push(iD.Node({ loc: map.projection.invert([ + circular_nodes.push(iD.Node({ loc: projection.invert([ centroid[0] + Math.cos((i / 12) * Math.PI * 2) * radius, centroid[1] + Math.sin((i / 12) * Math.PI * 2) * radius]) })); diff --git a/js/id/operations/circularize.js b/js/id/operations/circularize.js index 419a6e9eb..2680da4ec 100644 --- a/js/id/operations/circularize.js +++ b/js/id/operations/circularize.js @@ -1,6 +1,6 @@ iD.operations.Circularize = function(selection, context) { var entityId = selection[0], - action = iD.actions.Circularize(entityId, context.map()); + action = iD.actions.Circularize(entityId, context.projection); var operation = function() { var annotation = t('operations.circularize.annotation.' + context.geometry(entityId)); From 9e878b1cf927817c4cf7e619bcf5722278bd72e8 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sat, 2 Feb 2013 20:00:05 -0500 Subject: [PATCH 123/415] Remove redundant conditional --- js/id/validate.js | 40 +++++++++++++++++++--------------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/js/id/validate.js b/js/id/validate.js index aa7d8771b..6329b626f 100644 --- a/js/id/validate.js +++ b/js/id/validate.js @@ -15,31 +15,29 @@ iD.validate = function(changes, graph) { if (tags.building && tags.building === 'yes') return 'building=yes'; } - if (changes.created.length) { - for (var i = 0; i < changes.created.length; i++) { - change = changes.created[i]; + for (var i = 0; i < changes.created.length; i++) { + change = changes.created[i]; - if (change.geometry(graph) === 'point' && _.isEmpty(change.tags)) { - warnings.push({ - message: t('validations.untagged_point'), - entity: change - }); - } + if (change.geometry(graph) === 'point' && _.isEmpty(change.tags)) { + warnings.push({ + message: t('validations.untagged_point'), + entity: change + }); + } - if (change.geometry(graph) === 'line' && _.isEmpty(change.tags)) { - warnings.push({ message: t('validations.untagged_line'), entity: change }); - } + if (change.geometry(graph) === 'line' && _.isEmpty(change.tags)) { + warnings.push({ message: t('validations.untagged_line'), entity: change }); + } - if (change.geometry(graph) === 'area' && _.isEmpty(change.tags)) { - warnings.push({ message: t('validations.untagged_area'), entity: change }); - } + if (change.geometry(graph) === 'area' && _.isEmpty(change.tags)) { + warnings.push({ message: t('validations.untagged_area'), entity: change }); + } - if (change.geometry(graph) === 'line' && tagSuggestsArea(change)) { - warnings.push({ - message: t('validations.tag_suggests_area', {tag: tagSuggestsArea(change)}), - entity: change - }); - } + if (change.geometry(graph) === 'line' && tagSuggestsArea(change)) { + warnings.push({ + message: t('validations.tag_suggests_area', {tag: tagSuggestsArea(change)}), + entity: change + }); } } From 0b3e0fb3db7556438da8d560d9fab7dffd829e7f Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sat, 2 Feb 2013 20:29:01 -0500 Subject: [PATCH 124/415] Use iD.actions.DeleteNode when removing nodes They need to be removed from any parent relations. Also, make sure to uniq child nodes, otherwise the start/end node over-contributes to the centroid calculation. This action needs tests. --- js/id/actions/circularize.js | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/js/id/actions/circularize.js b/js/id/actions/circularize.js index d844134eb..2c72047d9 100644 --- a/js/id/actions/circularize.js +++ b/js/id/actions/circularize.js @@ -2,7 +2,7 @@ iD.actions.Circularize = function(wayId, projection) { var action = function(graph) { var way = graph.entity(wayId), - nodes = graph.childNodes(way), + nodes = _.uniq(graph.childNodes(way)), tags = {}, key, role; var points = nodes.map(function(n) { @@ -36,8 +36,6 @@ iD.actions.Circularize = function(wayId, projection) { circular_nodes.splice(closest, 1, nodes[i]); if (closest === 0) circular_nodes.splice(circular_nodes.length - 1, 1, nodes[i]); else if (closest === circular_nodes.length - 1) circular_nodes.splice(0, 1, nodes[i]); - } else { - graph = graph.remove(nodes[i]); } } @@ -45,9 +43,16 @@ iD.actions.Circularize = function(wayId, projection) { graph = graph.replace(circular_nodes[i]); } - return graph.replace(way.update({ - nodes: _.pluck(circular_nodes, 'id') - })); + var ids = _.pluck(circular_nodes, 'id'), + difference = _.difference(_.uniq(way.nodes), ids); + + graph = graph.replace(way.update({nodes: ids})); + + for (i = 0; i < difference.length; i++) { + graph = iD.actions.DeleteNode(difference[i])(graph); + } + + return graph; }; action.enabled = function(graph) { From 33f5db4d96b317be8c24d1b2db52405d2063d584 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sat, 2 Feb 2013 21:17:43 -0500 Subject: [PATCH 125/415] Fix hash with selected id --- js/id/behavior/hash.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/id/behavior/hash.js b/js/id/behavior/hash.js index 9056db393..c8762fb13 100644 --- a/js/id/behavior/hash.js +++ b/js/id/behavior/hash.js @@ -45,7 +45,7 @@ iD.behavior.Hash = function(context) { context.map().on('drawn.hash', function() { if (!context.entity(id)) return; selectoff(); - context.enter(iD.modes.Select([id])); + context.enter(iD.modes.Select(context, [id])); }); context.on('enter.hash', function() { From 441be74539c1b78d95b143109add18ab48504fba Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sat, 2 Feb 2013 21:26:29 -0500 Subject: [PATCH 126/415] Simplify --- js/id/actions/circularize.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/js/id/actions/circularize.js b/js/id/actions/circularize.js index 2c72047d9..532dcdb45 100644 --- a/js/id/actions/circularize.js +++ b/js/id/actions/circularize.js @@ -2,8 +2,7 @@ iD.actions.Circularize = function(wayId, projection) { var action = function(graph) { var way = graph.entity(wayId), - nodes = _.uniq(graph.childNodes(way)), - tags = {}, key, role; + nodes = _.uniq(graph.childNodes(way)); var points = nodes.map(function(n) { return projection(n.loc); @@ -21,8 +20,6 @@ iD.actions.Circularize = function(wayId, projection) { })); } - circular_nodes.push(circular_nodes[0]); - for (i = 0; i < nodes.length; i++) { if (graph.parentWays(nodes[i]).length > 1) { var closest, closest_dist = Infinity, dist; @@ -34,8 +31,6 @@ iD.actions.Circularize = function(wayId, projection) { } } circular_nodes.splice(closest, 1, nodes[i]); - if (closest === 0) circular_nodes.splice(circular_nodes.length - 1, 1, nodes[i]); - else if (closest === circular_nodes.length - 1) circular_nodes.splice(0, 1, nodes[i]); } } @@ -46,6 +41,8 @@ iD.actions.Circularize = function(wayId, projection) { var ids = _.pluck(circular_nodes, 'id'), difference = _.difference(_.uniq(way.nodes), ids); + ids.push(ids[0]); + graph = graph.replace(way.update({nodes: ids})); for (i = 0; i < difference.length; i++) { From 13b0b540a7cbc15d0c67f067f72a3f3f16092e62 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Sat, 2 Feb 2013 23:47:29 -0500 Subject: [PATCH 127/415] Don't snap to midpoints, snap to their parent way --- js/id/behavior/draw_way.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/js/id/behavior/draw_way.js b/js/id/behavior/draw_way.js index 9efcd5bb9..f724613da 100644 --- a/js/id/behavior/draw_way.js +++ b/js/id/behavior/draw_way.js @@ -16,10 +16,13 @@ iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) { function move(datum) { var loc = context.map().mouseCoordinates(); - if (datum.type === 'node' || datum.type === 'midpoint') { + if (datum.type === 'node') { loc = datum.loc; - } else if (datum.type === 'way') { - loc = iD.geo.chooseIndex(datum, d3.mouse(context.surface().node()), context).loc; + } else if (datum.type === 'midpoint' || datum.type === 'way') { + var way = datum.type === 'way' ? + datum : + baseGraph.entity(datum.ways[0].id); + loc = iD.geo.chooseIndex(way, d3.mouse(context.surface().node()), context).loc; } context.replace(iD.actions.MoveNode(nodeId, loc)); From 3000bc89ceeb9693a8ab258a103e2edea46a1475 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sun, 3 Feb 2013 08:49:43 -0800 Subject: [PATCH 128/415] shadow should be above fill Makes it much easier to select lines that are within areas. --- js/id/svg/surface.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/id/svg/surface.js b/js/id/svg/surface.js index 7561802e5..a11f8fbae 100644 --- a/js/id/svg/surface.js +++ b/js/id/svg/surface.js @@ -3,7 +3,7 @@ iD.svg.Surface = function() { selection.append('defs'); var layers = selection.selectAll('.layer') - .data(['shadow', 'fill', 'casing', 'stroke', 'text', 'hit', 'halo', 'label']); + .data(['fill', 'shadow', 'casing', 'stroke', 'text', 'hit', 'halo', 'label']); layers.enter().append('g') .attr('class', function(d) { return 'layer layer-' + d; }); From b912097ee42fc540be636542c03be88b235a8f7d Mon Sep 17 00:00:00 2001 From: Dr Ian Date: Sun, 3 Feb 2013 08:17:03 +0100 Subject: [PATCH 129/415] Tidy up: fix indents, spaces etc. - Update orthogonalize to match new circularize. - Add orthoganalize to test index.html - Revert whitespace on index.html --- js/id/actions/orthogonalize.js | 109 ++++++++++++++---------------- js/id/operations/orthogonalize.js | 2 +- test/index.html | 2 + 3 files changed, 54 insertions(+), 59 deletions(-) diff --git a/js/id/actions/orthogonalize.js b/js/id/actions/orthogonalize.js index 80c6a12c8..830d1a1eb 100644 --- a/js/id/actions/orthogonalize.js +++ b/js/id/actions/orthogonalize.js @@ -1,20 +1,18 @@ -iD.actions.Orthogonalize = function(wayId, map) { +iD.actions.Orthogonalize = function(wayId, projection) { var action = function(graph) { var way = graph.entity(wayId), - nodes = graph.childNodes(way), - tags = {},key,role; + nodes = graph.childNodes(way); var points = nodes.map(function(n) { - return map.projection(n.loc); - }), - quad_nodes = []; + return projection(n.loc); + }), + quad_nodes = []; var score = squareness(); - for (var i = 0; i < 1000; ++i) { + for (var i = 0; i < 1000; i++) { var motions = points.map(stepMap); - //return false; - for (var j = 0; j < motions.length; ++j) { + for (var j = 0; j < motions.length; j++) { points[j] = addPoints(points[j],motions[j]); } var newScore = squareness(); @@ -26,10 +24,9 @@ iD.actions.Orthogonalize = function(wayId, map) { break; } } - for (var i = 0; i < points.length; i++) { - quad_nodes.push(iD.Node({ loc: map.projection.invert(points[i]) })); + for (i = 0; i < points.length - 1; i++) { + quad_nodes.push(iD.Node({ loc: projection.invert(points[i]) })); } - quad_nodes.push(quad_nodes[0]); for (i = 0; i < nodes.length; i++) { if (graph.parentWays(nodes[i]).length > 1) { @@ -42,93 +39,89 @@ iD.actions.Orthogonalize = function(wayId, map) { } } quad_nodes.splice(closest, 1, nodes[i]); - if (closest === 0) quad_nodes.splice(quad_nodes.length - 1, 1, nodes[i]); - else if (closest === quad_nodes.length - 1) quad_nodes.splice(0, 1, nodes[i]); - } else { - graph = graph.remove(nodes[i]); } } - for (var i = 0; i < quad_nodes.length; i++) { - graph = graph.replace(quad_nodes[i]); + for (i = 0; i < quad_nodes.length; i++) { + graph = graph.replace(quad_nodes[i]); } - return graph.replace(way.update({ - nodes: _.pluck(quad_nodes, 'id') - })); + var ids = _.pluck(quad_nodes, 'id'), + difference = _.difference(_.uniq(way.nodes), ids); + ids.push(ids[0]); - function stepMap(b,i,array){ - var a,c,p,q = []; - a = array[(i-1+array.length) % array.length]; - c = array[(i+1) % array.length]; - p = subtractPoints(a,b); - q = subtractPoints(c,b); + graph = graph.replace(way.update({nodes: ids})); + for (i = 0; i < difference.length; i++) { + graph = iD.actions.DeleteNode(difference[i])(graph); + } + + return graph; + + function stepMap(b, i, array) { + var a, c, p, q = []; + a = array[(i - 1 + array.length) % array.length]; + c = array[(i + 1) % array.length]; + p = subtractPoints(a, b); + q = subtractPoints(c, b); var scale = p.length + q.length; - p = normalizePoint(p,1.0); - q = normalizePoint(q,1.0); - var dotp = p[0]*q[0] + p[1]*q[1]; + p = normalizePoint(p, 1.0); + q = normalizePoint(q, 1.0); + var dotp = p[0] *q[0] + p[1] *q[1]; // nasty hack to deal with almost-straight segments (angle is closer to 180 than to 90/270). if (dotp < -0.707106781186547) { dotp += 1.0; } var v = []; - v = addPoints(p,q); - v = normalizePoint(v,0.1 * dotp * scale); + v = addPoints(p, q); + v = normalizePoint(v, 0.1 * dotp * scale); return v; } - function squareness(){ + function squareness() { var g = 0.0; for (var i = 1; i < points.length - 1; i++) { - var score = scoreOfPoints(points[i-1], points[i], points[i+1]); + var score = scoreOfPoints(points[i - 1], points[i], points[i + 1]); g += score; } - var startScore = scoreOfPoints(points[points.length-1], points[0], points[1]); - var endScore = scoreOfPoints(points[points.length-2], points[points.length-1], points[0]); + var startScore = scoreOfPoints(points[points.length - 1], points[0], points[1]); + var endScore = scoreOfPoints(points[points.length - 2], points[points.length - 1], points[0]); g += startScore; g += endScore; return g; } function scoreOfPoints(a, b, c) { - var p,q = []; - p = subtractPoints(a,b); - q = subtractPoints(c,b); + var p, q = []; + p = subtractPoints(a, b); + q = subtractPoints(c, b); + p = normalizePoint(p, 1.0); + q = normalizePoint(q, 1.0); - p = normalizePoint(p,1.0); - q = normalizePoint(q,1.0); - - var dotp = p[0]*q[0] + p[1]*q[1]; + var dotp = p[0] * q[0] + p[1] * q[1]; // score is constructed so that +1, -1 and 0 are all scored 0, any other angle // is scored higher. - var score = 2.0 * Math.min(Math.abs(dotp-1.0), Math.min(Math.abs(dotp), Math.abs(dotp+1))); + var score = 2.0 * Math.min(Math.abs(dotp - 1.0), Math.min(Math.abs(dotp), Math.abs(dotp + 1))); return score; } - function subtractPoints(a,b){ - var vector = [0,0]; - vector[0] = a[0]-b[0]; - vector[1] = a[1]-b[1]; - return vector; + function subtractPoints(a, b) { + return [a[0] - b[0], a[1] - b[1]]; } - function addPoints(a,b){ - var vector = [0,0]; - vector[0] = a[0]+b[0]; - vector[1] = a[1]+b[1]; - return vector; + function addPoints(a, b) { + return [a[0]+b[0],a[1]+b[1]]; } - function normalizePoint(point,thickness){ + function normalizePoint(point, thickness) { var vector = [0,0]; - var length = Math.sqrt( point[0] * point[0] + point[1] * point[1]); + var length = Math.sqrt(point[0] * point[0] + point[1] * point[1]); if(length != 0){ - vector[0] = point[0]/length; - vector[1] = point[1]/length; + vector[0] = point[0] / length; + vector[1] = point[1] / length; } vector[0] *= thickness; diff --git a/js/id/operations/orthogonalize.js b/js/id/operations/orthogonalize.js index 888d0015e..c59ed13e4 100644 --- a/js/id/operations/orthogonalize.js +++ b/js/id/operations/orthogonalize.js @@ -1,6 +1,6 @@ iD.operations.Orthogonalize = function(selection, context) { var entityId = selection[0], - action = iD.actions.Orthogonalize(entityId, context.map()); + action = iD.actions.Orthogonalize(entityId, context.projection); var operation = function() { var annotation = t('operations.orthogonalize.annotation.' + context.geometry(entityId)); diff --git a/test/index.html b/test/index.html index b6d925923..551a87621 100644 --- a/test/index.html +++ b/test/index.html @@ -73,6 +73,7 @@ + @@ -108,6 +109,7 @@ + From 03800ec8419b01ede93d1f2d509572ebc730a8fd Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sun, 3 Feb 2013 13:21:27 -0800 Subject: [PATCH 130/415] Don't force point to area boundary (fixes #614) --- css/map.css | 1 + 1 file changed, 1 insertion(+) diff --git a/css/map.css b/css/map.css index 8ad55c790..34d56f0a9 100644 --- a/css/map.css +++ b/css/map.css @@ -725,6 +725,7 @@ text.point { } /* Ensure drawing doesn't interact with area fills. */ +.mode-add-point .area, .mode-draw-line .area, .mode-draw-area .area, .mode-add-line .area, From e0d4f5e87d19a1e9d19a4bb70f2959688c6d4b5f Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Mon, 4 Feb 2013 07:13:49 -0800 Subject: [PATCH 131/415] Use default shape-rendering On Firefox, optimizeSpeed = aliased. Fixes #592. --- css/map.css | 5 ----- 1 file changed, 5 deletions(-) diff --git a/css/map.css b/css/map.css index 34d56f0a9..ab2a352c9 100644 --- a/css/map.css +++ b/css/map.css @@ -150,11 +150,6 @@ path.stroke { stroke-width: 2; } -path.stroke, -path.casing { - shape-rendering: optimizeSpeed; -} - path.shadow { pointer-events: stroke; stroke-width: 10; From 240d83c1fd9b6cca93314ace4a591c81cc9f62b8 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Mon, 4 Feb 2013 10:54:39 -0500 Subject: [PATCH 132/415] Fix #623, get enitity from current graph --- js/id/behavior/draw_way.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/id/behavior/draw_way.js b/js/id/behavior/draw_way.js index f724613da..3089bd6ee 100644 --- a/js/id/behavior/draw_way.js +++ b/js/id/behavior/draw_way.js @@ -21,7 +21,7 @@ iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) { } else if (datum.type === 'midpoint' || datum.type === 'way') { var way = datum.type === 'way' ? datum : - baseGraph.entity(datum.ways[0].id); + context.entity(datum.ways[0].id); loc = iD.geo.chooseIndex(way, d3.mouse(context.surface().node()), context).loc; } From 3a075e68702af0f21aab713319b28222442d8726 Mon Sep 17 00:00:00 2001 From: Saman Bemel-Benrud Date: Mon, 4 Feb 2013 11:30:25 -0500 Subject: [PATCH 133/415] updated source svg. --- img/source/sprite.svg | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/img/source/sprite.svg b/img/source/sprite.svg index 6248e28cc..29e87e2b8 100644 --- a/img/source/sprite.svg +++ b/img/source/sprite.svg @@ -40,14 +40,14 @@ inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="1" - inkscape:cx="581.64693" - inkscape:cy="71.693021" + inkscape:cx="329.64693" + inkscape:cy="58.693021" inkscape:document-units="px" - inkscape:current-layer="layer12" + inkscape:current-layer="layer1" showgrid="false" inkscape:window-width="1280" inkscape:window-height="700" - inkscape:window-x="0" + inkscape:window-x="48" inkscape:window-y="0" inkscape:window-maximized="0" fit-margin-top="0" From 285ef7584d0f42560ea8f8a3e5c887580756026a Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Mon, 4 Feb 2013 11:55:25 -0500 Subject: [PATCH 134/415] Contributing --- CONTRIBUTING.md | 77 +++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 3 +- 2 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..5a4bdcca8 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,77 @@ +# Contributing to iD + +Thinking of contributing to iD? High five! Here are some basics for our habits +so that you can write code that fits in perfectly. + +## Javascript + +We use the [Airbnb style for Javascript](https://github.com/airbnb/javascript) with +only one difference: + +**4 space soft tabs always for Javascript, not 2.** + +No aligned `=`, no aligned arguments, spaces are either indents or the 1 +space between expressions. No hard tabs, ever. + +Javascript code should pass through [JSHint](http://www.jshint.com/) with no +warnings. + +## HTML + +There isn't much HTML in iD, but what there is is similar to JS: 4 spaces +always, indented by the level of the tree: + +```html +
+
+
+``` + +## CSS + +Just like HTML and Javascript, 4 space soft tabs always. + +```css +.radial-menu-tooltip { + background: rgba(255, 255, 255, 0.8); +} +``` + +We write vanilla CSS with no preprocessing step. Since iD targets modern browsers, +feel free to use newer features wisely. + +## Tests + +Test your code and make sure it passes. Our testing harness requires [node.js](http://nodejs.org/) +and a few modules: + +1. [Install node.js](http://nodejs.org/) - 'Install' will download a package for your OS +2. Go to the directory where you have checked out `iD` +3. Run `npm install` +4. Run `npm test` to see whether your tests pass or fail. + +## Licensing + +iD is under the [WTFPL](http://www.wtfpl.net/). Some of the libraries it uses +are under different licenses. If you're contributing to iD, you're contributing +WTFPL code. + +## Submitting Changes + +Let's say that you've thought of a great improvement to iD - a change that +turns everything red (please do not do this, we like colors other than red). + +In your local copy, make a branch for this change: + + git checkout -b make-red + +Make your changes to source files. By source files we mean the files in `js/`. +the `iD.js` and `iD.min.js` files in this project are autogenerated - don't edit +them. + +So let's say you've changed `js/ui/confirm.js`. + +1. Run `jshint src` to make sure your code is clean +2. Run tests with `npm test` +3. Commit your changes with an informative commit message +4. [Submit a pull request](https://help.github.com/articles/using-pull-requests) to the `systemed/iD` project. diff --git a/README.md b/README.md index 711cb5418..08c6764c0 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,7 @@ ## Participate! -* [Read NOTES.md, our ongoing dev journal](https://github.com/systemed/iD/blob/master/NOTES.md) -* Fork this project. We eagerly accept pull requests. +* [Read up on Contributing and the code style of iD](CONTRIBUTING.md) * See [open issues in the issue tracker if you're looking for something to do](https://github.com/systemed/iD/issues?state=open) To run the code locally, just fork this project and run it from a local webserver. From e5ad28feb75e79173892b1f54208851deea2f2cc Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Mon, 4 Feb 2013 12:10:23 -0500 Subject: [PATCH 135/415] JShint fixups --- js/id/actions/split.js | 2 +- js/id/behavior/draw_way.js | 2 +- js/id/graph/history.js | 2 +- js/id/graph/node.js | 2 +- js/id/operations.js | 2 +- js/id/services/taginfo.js | 2 +- js/id/svg.js | 18 ++++++++++-------- js/id/ui/splash.js | 13 +++++++++---- 8 files changed, 25 insertions(+), 18 deletions(-) diff --git a/js/id/actions/split.js b/js/id/actions/split.js index 19a71c619..24f2b4318 100644 --- a/js/id/actions/split.js +++ b/js/id/actions/split.js @@ -17,7 +17,7 @@ iD.actions.Split = function(nodeId, newWayId) { return parents.filter(function (parent) { return parent.first() !== nodeId && parent.last() !== nodeId; - }) + }); } var action = function(graph) { diff --git a/js/id/behavior/draw_way.js b/js/id/behavior/draw_way.js index 3089bd6ee..1cc52ab1f 100644 --- a/js/id/behavior/draw_way.js +++ b/js/id/behavior/draw_way.js @@ -82,7 +82,7 @@ iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) { return graph .replace(way.removeNode(nodeId).addNode(newNode.id, index)) .remove(node); - } + }; } // Accept the current position of the temporary node and continue drawing. diff --git a/js/id/graph/history.js b/js/id/graph/history.js index 3cc1ace01..7d1098027 100644 --- a/js/id/graph/history.js +++ b/js/id/graph/history.js @@ -127,7 +127,7 @@ iD.History = function() { modified: difference.modified(), created: difference.created(), deleted: difference.deleted() - } + }; }, hasChanges: function() { diff --git a/js/id/graph/node.js b/js/id/graph/node.js index b9a171752..56fc14eab 100644 --- a/js/id/graph/node.js +++ b/js/id/graph/node.js @@ -47,6 +47,6 @@ _.extend(iD.Node.prototype, { type: 'Point', coordinates: this.loc } - } + }; } }); diff --git a/js/id/operations.js b/js/id/operations.js index 2786d046f..a72fe1d82 100644 --- a/js/id/operations.js +++ b/js/id/operations.js @@ -1 +1 @@ -iD.operations = {} +iD.operations = {}; diff --git a/js/id/services/taginfo.js b/js/id/services/taginfo.js index aa89a37fa..fc4e72542 100644 --- a/js/id/services/taginfo.js +++ b/js/id/services/taginfo.js @@ -40,7 +40,7 @@ iD.taginfo = function() { } function popularValues(parameters) { - return function(d) { return parseFloat(d['fraction']) > 0.01; }; + return function(d) { return parseFloat(d.fraction) > 0.01; }; } function valKey(d) { return { value: d.key }; } diff --git a/js/id/svg.js b/js/id/svg.js index 5b07b3a53..aadb6b830 100644 --- a/js/id/svg.js +++ b/js/id/svg.js @@ -1,19 +1,19 @@ iD.svg = { - RoundProjection: function (projection) { - return function (d) { + RoundProjection: function(projection) { + return function(d) { return iD.geo.roundCoords(projection(d)); }; }, - PointTransform: function (projection) { - return function (entity) { + PointTransform: function(projection) { + return function(entity) { return 'translate(' + projection(entity.loc) + ')'; }; }, - LineString: function (projection, graph) { + LineString: function(projection, graph) { var cache = {}; - return function (entity) { + return function(entity) { if (cache[entity.id] !== undefined) { return cache[entity.id]; } @@ -23,7 +23,9 @@ iD.svg = { } return (cache[entity.id] = - 'M' + graph.childNodes(entity).map(function (n) { return projection(n.loc); }).join('L')); - } + 'M' + graph.childNodes(entity).map(function(n) { + return projection(n.loc); + }).join('L')); + }; } }; diff --git a/js/id/ui/splash.js b/js/id/ui/splash.js index 43489fab1..a4d2a915e 100644 --- a/js/id/ui/splash.js +++ b/js/id/ui/splash.js @@ -2,15 +2,20 @@ iD.ui.splash = function() { var modal = iD.ui.modal(); modal.select('.modal') - .attr('class', 'modal-splash modal') + .attr('class', 'modal-splash modal'); var introModal = modal.select('.content') .append('div') .attr('class', 'modal-section fillL'); - introModal.append('div').attr('class','logo'); + introModal.append('div') + .attr('class','logo'); - introModal.append('div').html("

Welcome to the iD OpenStreetMap editor

This is development version 0.0.0-alpha1. For more information see ideditor.com and report bugs at github.com.systemed/iD.

"); + introModal.append('div') + .html("

Welcome to the iD OpenStreetMap editor

" + + "This is development version 0.0.0-alpha1. " + + "For more information see ideditor.com" + + " and report bugs at github.com.systemed/iD.

"); return modal; -}; \ No newline at end of file +}; From 8699b4a49e62108a9bf31b845534c68209cade9a Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Mon, 4 Feb 2013 09:17:00 -0800 Subject: [PATCH 136/415] Adjust for style, formatting, and jshint --- js/id/actions/orthogonalize.js | 51 +++++++++++++++------------------- 1 file changed, 22 insertions(+), 29 deletions(-) diff --git a/js/id/actions/orthogonalize.js b/js/id/actions/orthogonalize.js index 830d1a1eb..068db55bd 100644 --- a/js/id/actions/orthogonalize.js +++ b/js/id/actions/orthogonalize.js @@ -1,18 +1,14 @@ iD.actions.Orthogonalize = function(wayId, projection) { - var action = function(graph) { var way = graph.entity(wayId), - nodes = graph.childNodes(way); - - var points = nodes.map(function(n) { - return projection(n.loc); - }), - quad_nodes = []; + nodes = graph.childNodes(way), + points = nodes.map(function(n) { return projection(n.loc); }), + quad_nodes = [], i, j; var score = squareness(); - for (var i = 0; i < 1000; i++) { + for (i = 0; i < 1000; i++) { var motions = points.map(stepMap); - for (var j = 0; j < motions.length; j++) { + for (j = 0; j < motions.length; j++) { points[j] = addPoints(points[j],motions[j]); } var newScore = squareness(); @@ -24,6 +20,7 @@ iD.actions.Orthogonalize = function(wayId, projection) { break; } } + for (i = 0; i < points.length - 1; i++) { quad_nodes.push(iD.Node({ loc: projection.invert(points[i]) })); } @@ -31,7 +28,7 @@ iD.actions.Orthogonalize = function(wayId, projection) { for (i = 0; i < nodes.length; i++) { if (graph.parentWays(nodes[i]).length > 1) { var closest, closest_dist = Infinity, dist; - for (var j = 0; j < quad_nodes.length; j++) { + for (j = 0; j < quad_nodes.length; j++) { dist = iD.geo.dist(quad_nodes[j].loc, nodes[i].loc); if (dist < closest_dist) { closest_dist = dist; @@ -47,7 +44,7 @@ iD.actions.Orthogonalize = function(wayId, projection) { } var ids = _.pluck(quad_nodes, 'id'), - difference = _.difference(_.uniq(way.nodes), ids); + difference = _.difference(_.uniq(way.nodes), ids); ids.push(ids[0]); @@ -60,28 +57,25 @@ iD.actions.Orthogonalize = function(wayId, projection) { return graph; function stepMap(b, i, array) { - var a, c, p, q = []; - a = array[(i - 1 + array.length) % array.length]; - c = array[(i + 1) % array.length]; - p = subtractPoints(a, b); - q = subtractPoints(c, b); + var a = array[(i - 1 + array.length) % array.length], + c = array[(i + 1) % array.length], + p = subtractPoints(a, b), + q = subtractPoints(c, b); var scale = p.length + q.length; p = normalizePoint(p, 1.0); q = normalizePoint(q, 1.0); + var dotp = p[0] *q[0] + p[1] *q[1]; // nasty hack to deal with almost-straight segments (angle is closer to 180 than to 90/270). if (dotp < -0.707106781186547) { dotp += 1.0; } - var v = []; - v = addPoints(p, q); - v = normalizePoint(v, 0.1 * dotp * scale); - return v; + + return normalizePoint(addPoints(p, q), 0.1 * dotp * scale); } function squareness() { - var g = 0.0; for (var i = 1; i < points.length - 1; i++) { var score = scoreOfPoints(points[i - 1], points[i], points[i + 1]); @@ -95,17 +89,16 @@ iD.actions.Orthogonalize = function(wayId, projection) { } function scoreOfPoints(a, b, c) { - var p, q = []; - p = subtractPoints(a, b); - q = subtractPoints(c, b); + var p = subtractPoints(a, b), + q = subtractPoints(c, b); + p = normalizePoint(p, 1.0); q = normalizePoint(q, 1.0); var dotp = p[0] * q[0] + p[1] * q[1]; // score is constructed so that +1, -1 and 0 are all scored 0, any other angle // is scored higher. - var score = 2.0 * Math.min(Math.abs(dotp - 1.0), Math.min(Math.abs(dotp), Math.abs(dotp + 1))); - return score; + return 2.0 * Math.min(Math.abs(dotp - 1.0), Math.min(Math.abs(dotp), Math.abs(dotp + 1))); } function subtractPoints(a, b) { @@ -113,13 +106,13 @@ iD.actions.Orthogonalize = function(wayId, projection) { } function addPoints(a, b) { - return [a[0]+b[0],a[1]+b[1]]; + return [a[0] + b[0], a[1] + b[1]]; } function normalizePoint(point, thickness) { - var vector = [0,0]; + var vector = [0, 0]; var length = Math.sqrt(point[0] * point[0] + point[1] * point[1]); - if(length != 0){ + if (length !== 0) { vector[0] = point[0] / length; vector[1] = point[1] / length; } From 7036758833aedff52e37af03753132d298a9cbda Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Mon, 4 Feb 2013 09:20:56 -0800 Subject: [PATCH 137/415] Fix directory --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5a4bdcca8..952266aa2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -71,7 +71,7 @@ them. So let's say you've changed `js/ui/confirm.js`. -1. Run `jshint src` to make sure your code is clean +1. Run `jshint js/id` to make sure your code is clean 2. Run tests with `npm test` 3. Commit your changes with an informative commit message 4. [Submit a pull request](https://help.github.com/articles/using-pull-requests) to the `systemed/iD` project. From 4ee620cb09835425945a1076b18abc10313b63ac Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Mon, 4 Feb 2013 12:23:46 -0500 Subject: [PATCH 138/415] Namespace selectors to iD-owned elements --- js/id/behavior/drag_midpoint.js | 2 +- js/id/renderer/map.js | 1 + js/id/svg/labels.js | 3 +-- js/id/ui/commit.js | 9 +++++---- js/id/ui/layerswitcher.js | 11 ++++++----- 5 files changed, 14 insertions(+), 12 deletions(-) diff --git a/js/id/behavior/drag_midpoint.js b/js/id/behavior/drag_midpoint.js index 42099963c..4e39be2c6 100644 --- a/js/id/behavior/drag_midpoint.js +++ b/js/id/behavior/drag_midpoint.js @@ -9,7 +9,7 @@ iD.behavior.DragMidpoint = function(context) { context.perform(iD.actions.AddMidpoint(d, node)); - var vertex = d3.selectAll('.vertex') + var vertex = context.surface().selectAll('.vertex') .filter(function(data) { return data.id === node.id; }); behavior.target(vertex.node(), vertex.datum()); diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index b2561f21f..19354d7f3 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -49,6 +49,7 @@ iD.Map = function(context) { map.size(selection.size()); map.surface = surface; + map.tilesurface = tilegroup; supersurface .call(tail); diff --git a/js/id/svg/labels.js b/js/id/svg/labels.js index eb4297bca..551c29e81 100644 --- a/js/id/svg/labels.js +++ b/js/id/svg/labels.js @@ -226,7 +226,7 @@ iD.svg.Labels = function(projection) { d3.select(surface.node().parentNode) .on('mousemove.hidelabels', hideOnMouseover); - var hidePoints = !d3.select('.node.point').node(); + var hidePoints = !surface.select('.node.point').node(); var labelable = [], i, k, entity; for (i = 0; i < label_stack.length; i++) labelable.push([]); @@ -254,7 +254,6 @@ iD.svg.Labels = function(projection) { } } - var positions = { point: [], line: [], diff --git a/js/id/ui/commit.js b/js/id/ui/commit.js index 09bb99404..8d724a732 100644 --- a/js/id/ui/commit.js +++ b/js/id/ui/commit.js @@ -35,11 +35,12 @@ iD.ui.commit = function(context) { // Comment Box var comment_section = body.append('div').attr('class','modal-section fillD'); - comment_section.append('textarea') + var commentField = comment_section.append('textarea') .attr('class', 'changeset-comment') .attr('placeholder', 'Brief Description of your contributions') - .property('value', context.storage('comment') || '') - .node().select(); + .property('value', context.storage('comment') || ''); + + commentField.node().select(); var commit_info = comment_section @@ -73,7 +74,7 @@ iD.ui.commit = function(context) { .append('button') .attr('class', 'save action col6 button') .on('click.save', function() { - var comment = d3.select('textarea.changeset-comment').node().value; + var comment = commentField.node().value; localStorage.comment = comment; event.save({ comment: comment diff --git a/js/id/ui/layerswitcher.js b/js/id/ui/layerswitcher.js index 5c4835757..9e5e4dd4f 100644 --- a/js/id/ui/layerswitcher.js +++ b/js/id/ui/layerswitcher.js @@ -61,9 +61,10 @@ iD.ui.layerswitcher = function(context) { opa.append('h4').text(t('layerswitcher.layers')); - opa.append('ul') - .attr('class', 'opacity-options') - .selectAll('div.opacity') + var opacityList = opa.append('ul') + .attr('class', 'opacity-options'); + + opacityList.selectAll('div.opacity') .data(opacities) .enter() .append('li') @@ -71,11 +72,11 @@ iD.ui.layerswitcher = function(context) { return t('layerswitcher.percent_brightness', { opacity: (d * 100) }); }) .on('click.set-opacity', function(d) { - d3.select('#tile-g') + context.map().tilesurface .transition() .style('opacity', d) .attr('data-opacity', d); - d3.selectAll('.opacity-options li') + opacityList.selectAll('li') .classed('selected', false); d3.select(this) .classed('selected', true); From 8d90fb777b29b6d6b49afe7aea48382639eb95d2 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Mon, 4 Feb 2013 12:32:14 -0500 Subject: [PATCH 139/415] Purge a few more uses of d3.select, refs #595 --- js/id/ui.js | 8 +++++--- js/id/ui/geocoder.js | 7 +++---- js/id/ui/save.js | 2 ++ 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/js/id/ui.js b/js/id/ui.js index 87cc3745a..69f20c2fd 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -140,8 +140,10 @@ iD.ui = function (context) { var about = container.append('div') .attr('class','col12 about-block fillD pad1'); - about.append('div') - .attr('class', 'user-container') + var userContainer = about.append('div') + .attr('class', 'user-container'); + + userContainer .append('div') .attr('class', 'hello'); @@ -243,7 +245,7 @@ iD.ui = function (context) { map.centerZoom([-77.02271, 38.90085], 20); } - d3.select('.user-container').call(iD.ui.userpanel(connection) + userContainer.call(iD.ui.userpanel(connection) .on('logout.editor', connection.logout) .on('login.editor', connection.authenticate)); diff --git a/js/id/ui/geocoder.js b/js/id/ui/geocoder.js index 4aac10bf2..063e8b517 100644 --- a/js/id/ui/geocoder.js +++ b/js/id/ui/geocoder.js @@ -38,9 +38,8 @@ iD.ui.geocoder = function() { function setVisible(show) { button.classed('active', show); gcForm.classed('hide', !show); - var input_node = d3.select('.map-overlay input').node(); - if (show) input_node.focus(); - else input_node.blur(); + if (show) inputNode.node().focus(); + else inputNode.node().blur(); } var button = selection.append('button') @@ -51,7 +50,7 @@ iD.ui.geocoder = function() { var gcForm = selection.append('form'); - gcForm.attr('class','content fillD map-overlay hide') + var inputNode = gcForm.attr('class','content fillD map-overlay hide') .append('input') .attr({ type: 'text', placeholder: t('geocoder.find_a_place') }) .on('keydown', keydown); diff --git a/js/id/ui/save.js b/js/id/ui/save.js index ba217adcf..773ae4d50 100644 --- a/js/id/ui/save.js +++ b/js/id/ui/save.js @@ -14,6 +14,7 @@ iD.ui.save = function(context) { .on('click', function() { function commit(e) { + d3.select('.shaded').remove(); var l = iD.ui.loading(t('uploading_changes'), true); connection.putChangeset(history.changes(), e.comment, history.imagery_used(), function(err, changeset_id) { @@ -40,6 +41,7 @@ iD.ui.save = function(context) { })); } }); + } if (history.hasChanges()) { From 4bfc91cc8508f7ba7fb248b4e5ecdbd1e1be490e Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Mon, 4 Feb 2013 12:34:11 -0500 Subject: [PATCH 140/415] Fix dragging cursor off of edge while drawing --- js/id/behavior/draw.js | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/js/id/behavior/draw.js b/js/id/behavior/draw.js index 625fc04a4..6536b590f 100644 --- a/js/id/behavior/draw.js +++ b/js/id/behavior/draw.js @@ -1,8 +1,7 @@ iD.behavior.Draw = function(context) { var event = d3.dispatch('move', 'click', 'clickWay', 'clickNode', 'clickMidpoint', 'undo', 'cancel', 'finish'), keybinding = d3.keybinding('draw'), - hover = iD.behavior.Hover(), - down; + hover = iD.behavior.Hover(); function datum() { if (d3.event.altKey) { @@ -13,17 +12,17 @@ iD.behavior.Draw = function(context) { } function mousedown() { - down = true; - } + var selection = d3.select(this); + selection.on('mousemove.draw', null); - function mouseup() { - down = false; + d3.select(window) + .on('mouseup.draw', function() { + selection.on('mousemove.draw', mousemove); + }); } function mousemove() { - if (!down) { - event.move(datum()); - } + event.move(datum()); } function click() { @@ -81,7 +80,6 @@ iD.behavior.Draw = function(context) { selection .on('mousedown.draw', mousedown) - .on('mouseup.draw', mouseup) .on('mousemove.draw', mousemove) .on('click.draw', click); @@ -98,7 +96,6 @@ iD.behavior.Draw = function(context) { selection .on('mousedown.draw', null) - .on('mouseup.draw', null) .on('mousemove.draw', null) .on('click.draw', null); From 59b6acdb08b0de54099c0837225e27ac4adc2c63 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Mon, 4 Feb 2013 12:52:58 -0500 Subject: [PATCH 141/415] Add notes on reporting issues --- CONTRIBUTING.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 952266aa2..09dbab7ba 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,6 +3,30 @@ Thinking of contributing to iD? High five! Here are some basics for our habits so that you can write code that fits in perfectly. +## Reporting Issues + +We'd love to hear what you think about iD, about any specific problems or +concerns you have. Here's a quick list of things to consider: + +Please [search for your issue before filing it: many bugs and improvements have already been reported](https://github.com/systemed/iD/issues/search?q=) + +To report a bug: + +* Write specifically what browser (type and version, like Firefox 22), OS, and browser extensions you have installed +* Write steps to replicate the error: when did it happen? What did you expect to happen? What happened instead? +* Please keep bug reports professional and straightforward: trust us, we share your dismay at software breaking. +* If you can, [enable web developer extensions](http://macwright.org/enable-web-developer-extensions/) and report the + Javascript error message. + +When in doubt, be over-descriptive of the bug and how you discovered it. + +To request a feature: + +* If the feature is available in some other software (like Potlatch), link to that software and the implementation. + We care about prior art. +* Understand that iD is meant to be a simple editor and doesn't aim to be + as complete or complicated as JOSM or similar. + ## Javascript We use the [Airbnb style for Javascript](https://github.com/airbnb/javascript) with From 2ee0cac3752ea514d19f592c0d87db2c98cb9d7f Mon Sep 17 00:00:00 2001 From: Saman Bemel-Benrud Date: Mon, 4 Feb 2013 13:15:07 -0500 Subject: [PATCH 142/415] fix intro modal position. --- css/app.css | 1 - 1 file changed, 1 deletion(-) diff --git a/css/app.css b/css/app.css index 7a0751d02..e660d5989 100644 --- a/css/app.css +++ b/css/app.css @@ -1060,7 +1060,6 @@ div.typeahead a:first-child { .modal-splash { width: 33.3333%; - left: 33.3333%; } .logo { From e4a8fbd0f6de74edbca05734d142ab368a2b9750 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Mon, 4 Feb 2013 13:34:14 -0500 Subject: [PATCH 143/415] Eliminate flickering --- js/id/behavior/draw_way.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/js/id/behavior/draw_way.js b/js/id/behavior/draw_way.js index 1cc52ab1f..a0a6aa308 100644 --- a/js/id/behavior/draw_way.js +++ b/js/id/behavior/draw_way.js @@ -17,7 +17,13 @@ iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) { var loc = context.map().mouseCoordinates(); if (datum.type === 'node') { - loc = datum.loc; + if (datum.id === nodeId) { + context.surface().selectAll('.way, .node') + .filter(function (d) { return d.id === nodeId; }) + .classed('active', true); + } else { + loc = datum.loc; + } } else if (datum.type === 'midpoint' || datum.type === 'way') { var way = datum.type === 'way' ? datum : From c2ddf67cc0639661d2534ab9c9c90c2b0374a03b Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Mon, 4 Feb 2013 11:20:23 -0800 Subject: [PATCH 144/415] Show appropriate tooltips for disabled undo/redo buttons Implementing this cross-browser requires using a `.disabled` class rather than the `disabled` property. No browsers except Opera dispatch mouse events on disabled buttons. Fixes #620. --- css/app.css | 11 ++++++++--- js/id/ui.js | 12 ++++++------ locale/en.js | 3 +++ 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/css/app.css b/css/app.css index e660d5989..a43133f3d 100644 --- a/css/app.css +++ b/css/app.css @@ -273,7 +273,12 @@ button.active { cursor:url(../img/cursor-pointing.png) 6 1, auto; } -button.active:not([disabled]) { +button.disabled { + background: #6c6c6c; + cursor: auto; +} + +button.active:not([disabled]):not(.disabled) { background: #6bc641; } @@ -450,8 +455,8 @@ button[disabled] .icon.browse { background-position: 0px -40px;} button[disabled] .icon.add-point { background-position: -20px -40px;} button[disabled] .icon.add-line { background-position: -40px -40px;} button[disabled] .icon.add-area { background-position: -60px -40px;} -button[disabled] .icon.undo { background-position: -80px -40px;} -button[disabled] .icon.redo { background-position: -100px -40px;} +button.disabled .icon.undo { background-position: -80px -40px;} +button.disabled .icon.redo { background-position: -100px -40px;} button[disabled] .apply.icon { background-position: -120px -40px;} button[disabled] .save.icon { background-position: -140px -40px;} button[disabled] .close.icon { background-position: -160px -40px;} diff --git a/js/id/ui.js b/js/id/ui.js index 69f20c2fd..f8c58903f 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -91,14 +91,14 @@ iD.ui = function (context) { undo_buttons.append('button') .attr({ id: 'undo', 'class': 'col6' }) - .property('disabled', true) + .classed('disabled', true) .html("") .on('click.editor', history.undo) .call(undo_tooltip); undo_buttons.append('button') .attr({ id: 'redo', 'class': 'col6' }) - .property('disabled', true) + .classed('disabled', true) .html("") .on('click.editor', history.redo) .call(undo_tooltip); @@ -209,13 +209,13 @@ iD.ui = function (context) { } limiter.select('#undo') - .property('disabled', !undo) - .attr('data-original-title', hintprefix('⌘ + Z', undo)) + .classed('disabled', !undo) + .attr('data-original-title', hintprefix('⌘ + Z', undo || t('nothing_to_undo'))) .call(refreshTooltip); limiter.select('#redo') - .property('disabled', !redo) - .attr('data-original-title', hintprefix('⌘ + ⇧ + Z', redo)) + .classed('disabled', !redo) + .attr('data-original-title', hintprefix('⌘ + ⇧ + Z', redo || t('nothing_to_redo'))) .call(refreshTooltip); }); diff --git a/locale/en.js b/locale/en.js index 31d6a13de..b446c4ae4 100644 --- a/locale/en.js +++ b/locale/en.js @@ -141,6 +141,9 @@ locale.en = { "zoom-in": "Zoom In", "zoom-out": "Zoom Out", + nothing_to_undo: "Nothing to undo.", + nothing_to_redo: "Nothing to redo.", + "browser_notice": "This editor is supported in Firefox, Chrome, Safari, Opera, and Internet Explorer 9 and above. Please upgrade your browser or use Potlatch 2 to edit the map.", "layer_settings": "Layer Settings", From 6e74456b3134c49e72391fa6b3133dcf6ab07d93 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Mon, 4 Feb 2013 14:57:43 -0500 Subject: [PATCH 145/415] Fix dragging around resized edges. Fixes #552 --- js/id/behavior/drag_node.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/js/id/behavior/drag_node.js b/js/id/behavior/drag_node.js index b62162e94..a0cb06e75 100644 --- a/js/id/behavior/drag_node.js +++ b/js/id/behavior/drag_node.js @@ -1,8 +1,7 @@ iD.behavior.DragNode = function(context) { - var size = context.map().size(), - nudgeInterval; + var nudgeInterval; - function edge(point) { + function edge(point, size) { var pad = [30, 100, 30, 100]; if (point[0] > size[0] - pad[0]) return [-10, 0]; else if (point[0] < pad[2]) return [10, 0]; @@ -39,7 +38,7 @@ iD.behavior.DragNode = function(context) { .on('move', function(entity) { d3.event.sourceEvent.stopPropagation(); - var nudge = edge(d3.event.point); + var nudge = edge(d3.event.point, context.map().size()); if (nudge) startNudge(nudge); else stopNudge(); From ad5c819f49617bf5626045978af43e4c9ffeb39c Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Mon, 4 Feb 2013 15:01:18 -0500 Subject: [PATCH 146/415] Do not use keywords --- locale/en.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locale/en.js b/locale/en.js index b446c4ae4..d99e88908 100644 --- a/locale/en.js +++ b/locale/en.js @@ -44,7 +44,7 @@ locale.en = { area: "Started an area." } }, - continue: { + 'continue': { annotation: { line: "Continued a line.", area: "Continued an area." @@ -74,7 +74,7 @@ locale.en = { area: "Squared the corners of an area." } }, - delete: { + 'delete': { title: "Delete", description: "Remove this from the map.", key: "⌫", From 0450e57acfc2ad217f8b946660f18586540a1c15 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Mon, 4 Feb 2013 12:05:41 -0800 Subject: [PATCH 147/415] Layers -> Background (#525) --- js/id/ui/layerswitcher.js | 4 ++-- locale/en.js | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/js/id/ui/layerswitcher.js b/js/id/ui/layerswitcher.js index 9e5e4dd4f..ecdbab0f9 100644 --- a/js/id/ui/layerswitcher.js +++ b/js/id/ui/layerswitcher.js @@ -35,7 +35,7 @@ iD.ui.layerswitcher = function(context) { .append('button') .attr('tabindex', -1) .attr('class', 'fillD') - .attr('title', t('layer_settings')) + .attr('title', t('layerswitcher.description')) .html("") .on('click.layerswitcher-toggle', toggle); @@ -59,7 +59,7 @@ iD.ui.layerswitcher = function(context) { .append('div') .attr('class', 'opacity-options-wrapper'); - opa.append('h4').text(t('layerswitcher.layers')); + opa.append('h4').text(t('layerswitcher.title')); var opacityList = opa.append('ul') .attr('class', 'opacity-options'); diff --git a/locale/en.js b/locale/en.js index d99e88908..88861bdee 100644 --- a/locale/en.js +++ b/locale/en.js @@ -146,8 +146,6 @@ locale.en = { "browser_notice": "This editor is supported in Firefox, Chrome, Safari, Opera, and Internet Explorer 9 and above. Please upgrade your browser or use Potlatch 2 to edit the map.", - "layer_settings": "Layer Settings", - inspector: { no_documentation_combination: "This is no documentation available for this tag combination", no_documentation_key: "This is no documentation available for this key", @@ -168,8 +166,10 @@ locale.en = { "description": "Description", "logout": "logout", + layerswitcher: { - layers: "Layers", + title: "Background", + description: "Background Settings", percent_brightness: "{opacity}% brightness" } }; From 7ddfcaed3962536ce11308aaf3a1679745fe672d Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Mon, 4 Feb 2013 12:08:30 -0800 Subject: [PATCH 148/415] i18n --- js/id/ui/layerswitcher.js | 4 ++-- locale/en.js | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/js/id/ui/layerswitcher.js b/js/id/ui/layerswitcher.js index ecdbab0f9..9eb063517 100644 --- a/js/id/ui/layerswitcher.js +++ b/js/id/ui/layerswitcher.js @@ -151,7 +151,7 @@ iD.ui.layerswitcher = function(context) { } adjustments.append('a') - .text('Fix misalignment') + .text(t('layerswitcher.fix_misalignment')) .attr('href', '#') .classed('alignment-toggle', true) .classed('expanded', false) @@ -179,7 +179,7 @@ iD.ui.layerswitcher = function(context) { .on('click', nudge); nudge_container.append('button') - .text('reset') + .text(t('layerswitcher.reset')) .attr('class', 'reset') .on('click', function() { context.background().offset([0, 0]); diff --git a/locale/en.js b/locale/en.js index 88861bdee..0fa3c1ea2 100644 --- a/locale/en.js +++ b/locale/en.js @@ -170,6 +170,8 @@ locale.en = { layerswitcher: { title: "Background", description: "Background Settings", - percent_brightness: "{opacity}% brightness" + percent_brightness: "{opacity}% brightness", + fix_misalignment: "Fix misalignment", + reset: "reset" } }; From 940a7a9a6af301c38fdb949fcf474547899da4f8 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Mon, 4 Feb 2013 12:18:11 -0800 Subject: [PATCH 149/415] Revise README --- README.md | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 08c6764c0..8cf1daeeb 100644 --- a/README.md +++ b/README.md @@ -2,30 +2,28 @@ [![Build Status](https://secure.travis-ci.org/systemed/iD.png)](https://travis-ci.org/systemed/iD) -[![](http://ideditor.com/img/editor.png)](http://geowiki.com/iD/) - -[Try the online demo of the most recent code.](http://geowiki.com/iD/) and -[open issues for bugs and ideas!](https://github.com/systemed/iD/issues) +[![](http://ideditor.com/img/editor.png)](http://ideditor.com/) ## Basics * iD is a JavaScript [OpenStreetMap](http://www.openstreetmap.org/) editor. * It's intentionally simple. It lets you do the most basic tasks while not breaking other people's data. -* We support modern browsers. Data is rendered with [d3](http://d3js.org/). +* It supports modern browsers. Data is rendered with [d3](http://d3js.org/). ## Participate! +* [Try out the latest stable release](http://geowiki.com/iD/) * [Read up on Contributing and the code style of iD](CONTRIBUTING.md) -* See [open issues in the issue tracker if you're looking for something to do](https://github.com/systemed/iD/issues?state=open) +* See [open issues in the issue tracker](https://github.com/systemed/iD/issues?state=open) if you're looking for something to do -To run the code locally, just fork this project and run it from a local webserver. -With a Mac, you can enable Web Sharing and drop this in your website directory. - -If you have Python handy, just `cd` into `iD` and run +To run the current development version, fork this project and serve it locally. +If you have Python handy, just `cd` into the project root directory and run python -m SimpleHTTPServer +Or, with a Mac, you can enable Web Sharing and clone iD into your website directory. + Come on in, the water's lovely. More help? Ping RichardF, tmcw, or jfire on IRC (`irc.oftc.net`, in `#osm-dev` or `#osm`), on the OSM mailing lists or at richard@systemeD.net. From 6dd1bebe89b8d7456114ca9c8e10eaff47a16fd8 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Mon, 4 Feb 2013 12:26:57 -0800 Subject: [PATCH 150/415] Fix drawing in Firefox (fixes #628) --- js/id/behavior/draw_way.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/id/behavior/draw_way.js b/js/id/behavior/draw_way.js index a0a6aa308..717218c8d 100644 --- a/js/id/behavior/draw_way.js +++ b/js/id/behavior/draw_way.js @@ -165,5 +165,5 @@ iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) { context.enter(iD.modes.Browse(context)); }; - return d3.rebind(drawWay, event, 'on'); + return drawWay; }; From 83e241083d5cd5d678a56b874c8b13711327065f Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Mon, 4 Feb 2013 15:30:59 -0500 Subject: [PATCH 151/415] Remove event --- js/id/behavior/draw.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/js/id/behavior/draw.js b/js/id/behavior/draw.js index 6536b590f..eb207eeb5 100644 --- a/js/id/behavior/draw.js +++ b/js/id/behavior/draw.js @@ -99,6 +99,8 @@ iD.behavior.Draw = function(context) { .on('mousemove.draw', null) .on('click.draw', null); + d3.select(window).on('mouseup.draw', null); + d3.select(document) .call(keybinding.off) .on('keydown.draw', null) From 3449a680a75cea1ea45e360a4bb61d97926b1379 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Mon, 4 Feb 2013 16:02:34 -0500 Subject: [PATCH 152/415] Add tag deprecation action and data, not yet integrated. --- data/data.js | 1 + data/deprecated.js | 112 ++++++++++++++++++++++++++++ index.html | 3 + js/id/actions/deprecate_tags.js | 36 +++++++++ test/index.html | 5 ++ test/spec/actions/deprecate_tags.js | 40 ++++++++++ 6 files changed, 197 insertions(+) create mode 100644 data/data.js create mode 100644 data/deprecated.js create mode 100644 js/id/actions/deprecate_tags.js create mode 100644 test/spec/actions/deprecate_tags.js diff --git a/data/data.js b/data/data.js new file mode 100644 index 000000000..279b1ea86 --- /dev/null +++ b/data/data.js @@ -0,0 +1 @@ +iD.data = {}; diff --git a/data/deprecated.js b/data/deprecated.js new file mode 100644 index 000000000..53457c9f3 --- /dev/null +++ b/data/deprecated.js @@ -0,0 +1,112 @@ +// from http://wiki.openstreetmap.org/wiki/Deprecated_features +// TODO: deal with deprecated 'class' tag +// does not deal with landuse=wood because of indecision +// we will not care about http://taginfo.openstreetmap.org/tags/bicycle_parking=sheffield +iD.data.deprecated = [ + { + old: { barrier: 'wire_fence' }, + replace: { + barrier: 'fence', + fence_type: 'chain' + } + }, + { + old: { barrier: 'wood_fence' }, + replace: { + barrier: 'fence', + fence_type: 'wood' + } + }, + { + old: { highway: 'ford' }, + replace: { + ford: 'yes' + } + }, + { + old: { highway: 'ford' }, + replace: { + ford: 'yes' + } + }, + { + old: { highway: 'ford' }, + replace: { + ford: 'yes' + } + }, + { + old: { highway: 'stile' }, + replace: { + barrier: 'stile' + } + }, + { + old: { highway: 'incline' }, + replace: { + highway: 'road', + incline: 'up' + } + }, + { + old: { highway: 'incline_steep' }, + replace: { + highway: 'road', + incline: 'up' + } + }, + { + old: { highway: 'unsurfaced' }, + replace: { + highway: 'road', + incline: 'unpaved' + } + }, + { + old: { highway: 'unsurfaced' }, + replace: { + highway: 'road', + incline: 'unpaved' + } + }, + { + old: { landuse: 'wood' }, + replace: { + highway: 'road', + incline: 'unpaved' + } + }, + { + old: { natural: 'marsh' }, + replace: { + natural: 'wetland', + wetland: 'marsh' + } + }, + { + old: { shop: 'organic' }, + replace: { + shop: 'supermarket', + organic: 'only' + } + }, + { + old: { power_source: '*' }, + replace: { + 'generator:source': '$1' + } + }, + { + old: { power_rating: '*' }, + replace: { + 'generator:output': '$1' + } + }, + { + old: { bicycle_parking: 'organic' }, + replace: { + shop: 'supermarket', + organic: 'only' + } + } +]; diff --git a/index.html b/index.html index 83ae066eb..c03ac29ae 100644 --- a/index.html +++ b/index.html @@ -134,6 +134,9 @@ + + +
+ @@ -130,6 +131,9 @@ + + + + diff --git a/test/spec/actions/deprecate_tags.js b/test/spec/actions/deprecate_tags.js new file mode 100644 index 000000000..4f202e97f --- /dev/null +++ b/test/spec/actions/deprecate_tags.js @@ -0,0 +1,40 @@ +describe('iD.actions.DeprecateTags', function () { + it('deprecates tags', function () { + var entity = iD.Entity({ tags: { barrier: 'wire_fence' } }), + graph = iD.actions.DeprecateTags(entity.id)(iD.Graph([entity])), + undeprecated = { + barrier: 'fence', + fence_type: 'chain' + }; + expect(graph.entity(entity.id).tags).to.eql(undeprecated); + }); + + it('does not overwrite explicit tags', function () { + var entity = iD.Entity({ tags: { barrier: 'wire_fence', fence_type: 'foo' } }), + graph = iD.actions.DeprecateTags(entity.id)(iD.Graph([entity])), + undeprecated = { + barrier: 'fence', + fence_type: 'foo' + }; + expect(graph.entity(entity.id).tags).to.eql(undeprecated); + }); + + it('leaves other tags alone', function () { + var entity = iD.Entity({ tags: { highway: 'ford', name: 'Foo' } }), + graph = iD.actions.DeprecateTags(entity.id)(iD.Graph([entity])), + undeprecated = { + ford: 'yes', + name: 'Foo' + }; + expect(graph.entity(entity.id).tags).to.eql(undeprecated); + }); + + it('replaces keys', function () { + var entity = iD.Entity({ tags: { power_rating: '1 billion volts' } }), + graph = iD.actions.DeprecateTags(entity.id)(iD.Graph([entity])), + undeprecated = { + 'generator:output': '1 billion volts' + }; + expect(graph.entity(entity.id).tags).to.eql(undeprecated); + }); +}); From 3d8f2ffb84646a81fa8458c470e18322a01e97a4 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Mon, 4 Feb 2013 16:08:34 -0500 Subject: [PATCH 153/415] Consistify syntax --- js/id/graph/history.js | 24 ++++++++++++------------ js/id/modes/select.js | 12 ++++++------ js/id/ui.js | 16 ++++++++-------- js/id/ui/save.js | 4 +++- 4 files changed, 29 insertions(+), 27 deletions(-) diff --git a/js/id/graph/history.js b/js/id/graph/history.js index 7d1098027..a0930203c 100644 --- a/js/id/graph/history.js +++ b/js/id/graph/history.js @@ -27,11 +27,11 @@ iD.History = function() { } var history = { - graph: function () { + graph: function() { return stack[index].graph; }, - merge: function (entities) { + merge: function(entities) { for (var i = 0; i < stack.length; i++) { stack[i].graph.rebase(entities); } @@ -39,7 +39,7 @@ iD.History = function() { dispatch.change(); }, - perform: function () { + perform: function() { var previous = stack[index].graph; stack = stack.slice(0, index + 1); @@ -49,7 +49,7 @@ iD.History = function() { return change(previous); }, - replace: function () { + replace: function() { var previous = stack[index].graph; // assert(index == stack.length - 1) @@ -58,7 +58,7 @@ iD.History = function() { return change(previous); }, - pop: function () { + pop: function() { var previous = stack[index].graph; if (index > 0) { @@ -68,7 +68,7 @@ iD.History = function() { } }, - undo: function () { + undo: function() { var previous = stack[index].graph; // Pop to the first annotated state. @@ -87,7 +87,7 @@ iD.History = function() { return change(previous); }, - redo: function () { + redo: function() { var previous = stack[index].graph; while (index < stack.length - 1) { @@ -99,7 +99,7 @@ iD.History = function() { return change(previous); }, - undoAnnotation: function () { + undoAnnotation: function() { var i = index; while (i >= 0) { if (stack[i].annotation) return stack[i].annotation; @@ -107,7 +107,7 @@ iD.History = function() { } }, - redoAnnotation: function () { + redoAnnotation: function() { var i = index + 1; while (i <= stack.length - 1) { if (stack[i].annotation) return stack[i].annotation; @@ -115,13 +115,13 @@ iD.History = function() { } }, - difference: function () { + difference: function() { var base = stack[0].graph, head = stack[index].graph; return iD.Difference(base, head); }, - changes: function () { + changes: function() { var difference = history.difference(); return { modified: difference.modified(), @@ -145,7 +145,7 @@ iD.History = function() { undefined, 'Custom'); }, - reset: function () { + reset: function() { stack = [{graph: iD.Graph()}]; index = 0; dispatch.change(); diff --git a/js/id/modes/select.js b/js/id/modes/select.js index 02db6a57f..ec6017ae3 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -39,11 +39,11 @@ iD.modes.Select = function(context, selection, initial) { }); var operations = d3.values(iD.operations) - .map(function (o) { return o(selection, context); }) - .filter(function (o) { return o.available(); }); + .map(function(o) { return o(selection, context); }) + .filter(function(o) { return o.available(); }); operations.forEach(function(operation) { - keybinding.on(operation.key, function () { + keybinding.on(operation.key, function() { if (operation.enabled()) { operation(); } @@ -87,7 +87,7 @@ iD.modes.Select = function(context, selection, initial) { context.history().on('change.select', function() { context.surface().call(radialMenu.close); - if (_.any(selection, function (id) { return !context.entity(id); })) { + if (_.any(selection, function(id) { return !context.entity(id); })) { // Exit mode if selected entity gets undone context.enter(iD.modes.Browse(context)); @@ -128,7 +128,7 @@ iD.modes.Select = function(context, selection, initial) { context.surface() .on('dblclick.select', dblclick) .selectAll("*") - .filter(function (d) { return d && selection.indexOf(d.id) >= 0; }) + .filter(function(d) { return d && selection.indexOf(d.id) >= 0; }) .classed('selected', true); radialMenu = iD.ui.RadialMenu(operations); @@ -144,7 +144,7 @@ iD.modes.Select = function(context, selection, initial) { } }; - mode.exit = function () { + mode.exit = function() { if (singular()) { changeTags(singular(), inspector.tags()); } diff --git a/js/id/ui.js b/js/id/ui.js index f8c58903f..03e5c6199 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -1,4 +1,4 @@ -iD.ui = function (context) { +iD.ui = function(context) { return function(container) { context.container(container); @@ -42,12 +42,12 @@ iD.ui = function (context) { .data(modes) .enter().append('button') .attr('tabindex', -1) - .attr('class', function (mode) { return mode.title + ' add-button col3'; }) + .attr('class', function(mode) { return mode.title + ' add-button col3'; }) .call(bootstrap.tooltip().placement('bottom').html(true)) - .attr('data-original-title', function (mode) { + .attr('data-original-title', function(mode) { return hintprefix(mode.key, mode.description); }) - .on('click.editor', function (mode) { context.enter(mode); }); + .on('click.editor', function(mode) { context.enter(mode); }); function disableTooHigh() { if (map.editable()) { @@ -74,14 +74,14 @@ iD.ui = function (context) { return d.id + ' icon icon-pre-text'; }); - buttons.append('span').attr('class', 'label').text(function (mode) { return mode.title; }); + buttons.append('span').attr('class', 'label').text(function(mode) { return mode.title; }); - context.on('enter.editor', function (entered) { - buttons.classed('active', function (mode) { return entered.button === mode.button; }); + context.on('enter.editor', function(entered) { + buttons.classed('active', function(mode) { return entered.button === mode.button; }); container.classed("mode-" + entered.id, true); }); - context.on('exit.editor', function (exited) { + context.on('exit.editor', function(exited) { container.classed("mode-" + exited.id, false); }); diff --git a/js/id/ui/save.js b/js/id/ui/save.js index 773ae4d50..fc593c463 100644 --- a/js/id/ui/save.js +++ b/js/id/ui/save.js @@ -17,7 +17,9 @@ iD.ui.save = function(context) { d3.select('.shaded').remove(); var l = iD.ui.loading(t('uploading_changes'), true); - connection.putChangeset(history.changes(), e.comment, history.imagery_used(), function(err, changeset_id) { + connection.putChangeset(history.changes(), + e.comment, + history.imagery_used(), function(err, changeset_id) { l.remove(); history.reset(); map.flush().redraw(); From d756e3c2a2b76139c397c76abf9bc8c19d20ae20 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Mon, 4 Feb 2013 16:30:19 -0500 Subject: [PATCH 154/415] Let ways be self-intersected --- js/id/behavior/draw_way.js | 45 ++++++++++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/js/id/behavior/draw_way.js b/js/id/behavior/draw_way.js index 717218c8d..32d05538f 100644 --- a/js/id/behavior/draw_way.js +++ b/js/id/behavior/draw_way.js @@ -1,25 +1,37 @@ iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) { var way = context.entity(wayId), + isArea = way.geometry() === 'area', finished = false, annotation = t((way.isDegenerate() ? 'operations.start.annotation.' : 'operations.continue.annotation.') + context.geometry(wayId)), draw = iD.behavior.Draw(context); - var node = iD.Node({loc: context.map().mouseCoordinates()}), - nodeId = node.id; + var startIndex = typeof index === 'undefined' ? way.nodes.length - 1 : 0, + start = iD.Node({loc: context.graph().entity(way.nodes[startIndex]).loc}), + end = iD.Node({loc: context.map().mouseCoordinates()}), + segment = iD.Way({ + nodes: [start.id, end.id], + tags: _.clone(way.tags) + }); - context[way.isDegenerate() ? 'replace' : 'perform']( - iD.actions.AddEntity(node), - iD.actions.AddVertex(wayId, node.id, index)); + var f = context[way.isDegenerate() ? 'replace' : 'perform']; + if (isArea) { + f(iD.actions.AddEntity(end), + iD.actions.AddVertex(wayId, end.id, index)); + } else { + f(iD.actions.AddEntity(start), + iD.actions.AddEntity(end), + iD.actions.AddEntity(segment)); + } function move(datum) { var loc = context.map().mouseCoordinates(); if (datum.type === 'node') { - if (datum.id === nodeId) { + if (datum.id === end.id) { context.surface().selectAll('.way, .node') - .filter(function (d) { return d.id === nodeId; }) + .filter(function (d) { return d.id === end.id; }) .classed('active', true); } else { loc = datum.loc; @@ -31,7 +43,7 @@ iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) { loc = iD.geo.chooseIndex(way, d3.mouse(context.surface().node()), context).loc; } - context.replace(iD.actions.MoveNode(nodeId, loc)); + context.replace(iD.actions.MoveNode(end.id, loc)); } function undone() { @@ -55,7 +67,7 @@ iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) { surface.call(draw) .selectAll('.way, .node') - .filter(function (d) { return d.id === wayId || d.id === nodeId; }) + .filter(function (d) { return d.id === segment.id || d.id === start.id || d.id === end.id; }) .classed('active', true); context.history() @@ -85,9 +97,18 @@ iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) { function ReplaceTemporaryNode(newNode) { return function(graph) { - return graph - .replace(way.removeNode(nodeId).addNode(newNode.id, index)) - .remove(node); + if (isArea) { + return graph + .replace(way.removeNode(end.id).addNode(newNode.id, index)) + .remove(end); + + } else { + return graph + .replace(graph.entity(wayId).addNode(newNode.id, index)) + .remove(end) + .remove(segment) + .remove(start); + } }; } From bd7f30273006de51326b58e5a6a76ed2ad0bfecc Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Mon, 4 Feb 2013 16:45:18 -0500 Subject: [PATCH 155/415] Show deprecated tags in save commit, validate them, add deprecatedTags to entity type. --- js/id/graph/entity.js | 27 ++++++++++++++++++++++----- js/id/ui/save.js | 1 + js/id/util.js | 4 ++-- js/id/validate.js | 8 ++++++++ locale/en.js | 3 ++- test/spec/graph/entity.js | 10 ++++++++++ test/spec/util.js | 4 ++-- 7 files changed, 47 insertions(+), 10 deletions(-) diff --git a/js/id/graph/entity.js b/js/id/graph/entity.js index bce9675ee..bd402552e 100644 --- a/js/id/graph/entity.js +++ b/js/id/graph/entity.js @@ -9,22 +9,22 @@ iD.Entity = function(attrs) { return (new iD.Entity()).initialize(arguments); }; -iD.Entity.id = function (type) { +iD.Entity.id = function(type) { return iD.Entity.id.fromOSM(type, iD.Entity.id.next[type]--); }; iD.Entity.id.next = {node: -1, way: -1, relation: -1}; -iD.Entity.id.fromOSM = function (type, id) { +iD.Entity.id.fromOSM = function(type, id) { return type[0] + id; }; -iD.Entity.id.toOSM = function (id) { +iD.Entity.id.toOSM = function(id) { return id.slice(1); }; // A function suitable for use as the second argument to d3.selection#data(). -iD.Entity.key = function (entity) { +iD.Entity.key = function(entity) { return entity.id; }; @@ -84,7 +84,7 @@ iD.Entity.prototype = { }, hasInterestingTags: function() { - return _.keys(this.tags).some(function (key) { + return _.keys(this.tags).some(function(key) { return key != "attribution" && key != "created_by" && key != "source" && @@ -93,6 +93,23 @@ iD.Entity.prototype = { }); }, + deprecatedTags: function() { + var tags = _.pairs(this.tags); + var deprecated = {}; + + iD.data.deprecated.forEach(function(d) { + var match = _.pairs(d.old)[0]; + tags.forEach(function(t) { + if (t[0] == match[0] && + (t[1] == match[1] || match[1] == '*')) { + deprecated[t[0]] = t[1]; + } + }); + }); + + return deprecated; + }, + friendlyName: function() { // Generate a string such as 'river' or 'Fred's House' for an entity. if (!this.tags || !Object.keys(this.tags).length) { return ''; } diff --git a/js/id/ui/save.js b/js/id/ui/save.js index fc593c463..4e3bf883c 100644 --- a/js/id/ui/save.js +++ b/js/id/ui/save.js @@ -17,6 +17,7 @@ iD.ui.save = function(context) { d3.select('.shaded').remove(); var l = iD.ui.loading(t('uploading_changes'), true); + connection.putChangeset(history.changes(), e.comment, history.imagery_used(), function(err, changeset_id) { diff --git a/js/id/util.js b/js/id/util.js index 645d15363..9ebd4d761 100644 --- a/js/id/util.js +++ b/js/id/util.js @@ -8,8 +8,8 @@ iD.util.trueObj = function(arr) { iD.util.tagText = function(entity) { return d3.entries(entity.tags).map(function(e) { - return e.key + ': ' + e.value; - }).join('\n'); + return e.key + '=' + e.value; + }).join(', '); }; iD.util.stringQs = function(str) { diff --git a/js/id/validate.js b/js/id/validate.js index 6329b626f..dacff7889 100644 --- a/js/id/validate.js +++ b/js/id/validate.js @@ -29,6 +29,14 @@ iD.validate = function(changes, graph) { warnings.push({ message: t('validations.untagged_line'), entity: change }); } + var deprecatedTags = change.deprecatedTags(); + if (!_.isEmpty(deprecatedTags)) { + warnings.push({ + message: t('validations.deprecated_tags', { + tags: iD.util.tagText({ tags: deprecatedTags }) + }), entity: change }); + } + if (change.geometry(graph) === 'area' && _.isEmpty(change.tags)) { warnings.push({ message: t('validations.untagged_area'), entity: change }); } diff --git a/locale/en.js b/locale/en.js index 0fa3c1ea2..8f00590c6 100644 --- a/locale/en.js +++ b/locale/en.js @@ -127,7 +127,8 @@ locale.en = { untagged_point: "Untagged point which is not part of a line or area", untagged_line: "Untagged line", untagged_area: "Untagged area", - tag_suggests_area: "The tag {tag} suggests line should be area, but it is not an area" + tag_suggests_area: "The tag {tag} suggests line should be area, but it is not an area", + deprecated_tags: "Deprecated tags: {tags}" }, "save": "Save", diff --git a/test/spec/graph/entity.js b/test/spec/graph/entity.js index f2ac85f17..f0b7e0b19 100644 --- a/test/spec/graph/entity.js +++ b/test/spec/graph/entity.js @@ -114,6 +114,16 @@ describe('iD.Entity', function () { }); }); + describe("#hasDeprecatedTags", function () { + it("returns false if entity has no tags", function () { + expect(iD.Entity().deprecatedTags()).to.eql({}); + }); + + it("returns true if entity has deprecated tags", function () { + expect(iD.Entity({ tags: { barrier: 'wire_fence' } }).deprecatedTags()).to.eql({ barrier: 'wire_fence' }); + }); + }); + describe("#hasInterestingTags", function () { it("returns false if the entity has no tags", function () { expect(iD.Entity().hasInterestingTags()).to.equal(false); diff --git a/test/spec/util.js b/test/spec/util.js index de3fab735..d866a7b39 100644 --- a/test/spec/util.js +++ b/test/spec/util.js @@ -6,8 +6,8 @@ describe('iD.Util', function() { it('.tagText', function() { expect(iD.util.tagText({})).to.eql(''); - expect(iD.util.tagText({tags:{foo:'bar'}})).to.eql('foo: bar'); - expect(iD.util.tagText({tags:{foo:'bar',two:'three'}})).to.eql('foo: bar\ntwo: three'); + expect(iD.util.tagText({tags:{foo:'bar'}})).to.eql('foo=bar'); + expect(iD.util.tagText({tags:{foo:'bar',two:'three'}})).to.eql('foo=bar, two=three'); }); it('.stringQs', function() { From 9ec7491645dd5977646e6b23fef64074f64e4097 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Mon, 4 Feb 2013 16:45:34 -0500 Subject: [PATCH 156/415] Fix baseline shifting for opera --- css/map.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/css/map.css b/css/map.css index ab2a352c9..f1998277c 100644 --- a/css/map.css +++ b/css/map.css @@ -620,7 +620,7 @@ text.pointlabel { } .pathlabel .textpath { - dominant-baseline: middle; + baseline-shift: -33%; } .pointlabel-halo, From b7cfaf08da7db5507944d012c4d8a4998b1d90bb Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Mon, 4 Feb 2013 16:48:44 -0500 Subject: [PATCH 157/415] userDetails should handle errors properly --- js/id/connection.js | 1 + js/id/ui/userpanel.js | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/js/id/connection.js b/js/id/connection.js index 794232682..5f5f17482 100644 --- a/js/id/connection.js +++ b/js/id/connection.js @@ -208,6 +208,7 @@ iD.Connection = function(context) { function userDetails(callback) { function done(err, user_details) { + if (err) return callback(err); var u = user_details.getElementsByTagName('user')[0], img = u.getElementsByTagName('img'), image_url = ''; diff --git a/js/id/ui/userpanel.js b/js/id/ui/userpanel.js index 2b28411a6..fda2fc463 100644 --- a/js/id/ui/userpanel.js +++ b/js/id/ui/userpanel.js @@ -5,10 +5,12 @@ iD.ui.userpanel = function(connection) { function update() { if (connection.authenticated()) { selection.style('display', 'block'); - connection.userDetails(function(user_details) { + connection.userDetails(function(err, user_details) { selection.html(''); + if (err) return; + // Link var userLink = selection.append('a') .attr('href', connection.url() + '/user/' + From d0d1a16c0ac4ff8d419b51406a9a8026a91b1c7e Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Mon, 4 Feb 2013 17:04:33 -0500 Subject: [PATCH 158/415] Fix label halo sizing --- js/id/svg/labels.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/js/id/svg/labels.js b/js/id/svg/labels.js index 551c29e81..9b2cbd1e9 100644 --- a/js/id/svg/labels.js +++ b/js/id/svg/labels.js @@ -33,8 +33,11 @@ iD.svg.Labels = function(projection) { var font_sizes = label_stack.map(function(d) { var style = iD.util.getStyle('text.' + d[0] + '.tag-' + d[1]); var m = style && style.cssText.match("font-size: ([0-9]{1,2})px;"); - if (!m) return default_size; - return parseInt(m[1], 10); + if (m) return parseInt(m[1], 10); + style = iD.util.getStyle('text.' + d[0]); + m = style && style.cssText.match("font-size: ([0-9]{1,2})px;"); + if (m) return parseInt(m[1], 10); + return default_size; }); var pointOffsets = [ @@ -281,7 +284,7 @@ iD.svg.Labels = function(projection) { p = getAreaLabel(entity, width, font_size); } if (p) { - p.classes = entity.geometry(graph) + ' tag-' + label_stack[k].slice(1).join('-'); + p.classes = entity.geometry(graph) + ' tag-' + label_stack[k][1]; positions[entity.geometry(graph)].push(p); labelled[entity.geometry(graph)].push(entity); } From 1b70a68214c559b7c4d0bbde1f5cfbc8ba3adf85 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Mon, 4 Feb 2013 17:14:56 -0500 Subject: [PATCH 159/415] Revert "Don't snap to midpoints, snap to their parent way" This reverts commit 13b0b540a7cbc15d0c67f067f72a3f3f16092e62. Conflicts: js/id/behavior/draw_way.js --- js/id/behavior/draw_way.js | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/js/id/behavior/draw_way.js b/js/id/behavior/draw_way.js index 32d05538f..64ec15e51 100644 --- a/js/id/behavior/draw_way.js +++ b/js/id/behavior/draw_way.js @@ -28,19 +28,10 @@ iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) { function move(datum) { var loc = context.map().mouseCoordinates(); - if (datum.type === 'node') { - if (datum.id === end.id) { - context.surface().selectAll('.way, .node') - .filter(function (d) { return d.id === end.id; }) - .classed('active', true); - } else { - loc = datum.loc; - } - } else if (datum.type === 'midpoint' || datum.type === 'way') { - var way = datum.type === 'way' ? - datum : - context.entity(datum.ways[0].id); - loc = iD.geo.chooseIndex(way, d3.mouse(context.surface().node()), context).loc; + if (datum.type === 'node' || datum.type === 'midpoint') { + loc = datum.loc; + } else if (datum.type === 'way') { + loc = iD.geo.chooseIndex(datum, d3.mouse(context.surface().node()), context).loc; } context.replace(iD.actions.MoveNode(end.id, loc)); From 3417a1639c1a3fd60c11a46782bb833e48ab3746 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Mon, 4 Feb 2013 17:16:14 -0500 Subject: [PATCH 160/415] Hide midpoints when drawing (no snapping) --- css/map.css | 8 ++++++++ js/id/behavior/draw_way.js | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/css/map.css b/css/map.css index f1998277c..63950671f 100644 --- a/css/map.css +++ b/css/map.css @@ -117,6 +117,14 @@ g.vertex.selected .shadow { /* midpoints */ +.mode-draw-area g.midpoint, +.mode-draw-line g.midpoint, +.mode-add-area g.midpoint, +.mode-add-line g.midpoint, +.mode-add-point g.midpoint { + display: none; +} + g.midpoint .fill { fill:#aaa; } diff --git a/js/id/behavior/draw_way.js b/js/id/behavior/draw_way.js index 64ec15e51..fa2c24c87 100644 --- a/js/id/behavior/draw_way.js +++ b/js/id/behavior/draw_way.js @@ -28,7 +28,7 @@ iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) { function move(datum) { var loc = context.map().mouseCoordinates(); - if (datum.type === 'node' || datum.type === 'midpoint') { + if (datum.type === 'node') { loc = datum.loc; } else if (datum.type === 'way') { loc = iD.geo.chooseIndex(datum, d3.mouse(context.surface().node()), context).loc; From 3e71dd56cd90915938f9d3fe433d53b69be8cd00 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Mon, 4 Feb 2013 17:20:06 -0500 Subject: [PATCH 161/415] Wipe out some tags entirely, refs #585 --- data/deprecated.js | 10 +++++++++- js/id/actions/deprecate_tags.js | 6 +++--- test/spec/actions/deprecate_tags.js | 7 +++++++ 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/data/deprecated.js b/data/deprecated.js index 53457c9f3..7cc6904ed 100644 --- a/data/deprecated.js +++ b/data/deprecated.js @@ -108,5 +108,13 @@ iD.data.deprecated = [ shop: 'supermarket', organic: 'only' } - } + }, + // entirely discarded tags + { old: { 'tiger:upload_uuid': '*' } }, + { old: { 'tiger:tlid': '*' } }, + { old: { 'tiger:source': '*' } }, + { old: { 'tiger:separated': '*' } }, + { old: { 'geobase:datasetName': '*' } }, + { old: { 'geobase:uuid': '*' } }, + { old: { 'sub_sea:type': '*' } } ]; diff --git a/js/id/actions/deprecate_tags.js b/js/id/actions/deprecate_tags.js index 8f7a56404..68b9f9d2e 100644 --- a/js/id/actions/deprecate_tags.js +++ b/js/id/actions/deprecate_tags.js @@ -10,19 +10,19 @@ iD.actions.DeprecateTags = function(entityId) { rule = iD.data.deprecated[i]; var match = _.pairs(rule.old)[0], - replacements = _.pairs(rule.replace); + replacements = rule.replace ? _.pairs(rule.replace) : null; if (entity.tags[match[0]] && match[1] === '*') { var value = entity.tags[match[0]]; - if (!newtags[replacements[0][0]]) { + if (replacements && !newtags[replacements[0][0]]) { newtags[replacements[0][0]] = value; } delete newtags[match[0]]; change = true; } else if (entity.tags[match[0]] === match[1]) { - newtags = _.assign({}, rule.replace, _.omit(newtags, match[0])); + newtags = _.assign({}, rule.replace || {}, _.omit(newtags, match[0])); change = true; } } diff --git a/test/spec/actions/deprecate_tags.js b/test/spec/actions/deprecate_tags.js index 4f202e97f..61d1da4fb 100644 --- a/test/spec/actions/deprecate_tags.js +++ b/test/spec/actions/deprecate_tags.js @@ -29,6 +29,13 @@ describe('iD.actions.DeprecateTags', function () { expect(graph.entity(entity.id).tags).to.eql(undeprecated); }); + it('wipes out tags that should be entirely removed', function () { + var entity = iD.Entity({ tags: { 'tiger:source': 'foo' } }), + graph = iD.actions.DeprecateTags(entity.id)(iD.Graph([entity])), + undeprecated = { }; + expect(graph.entity(entity.id).tags).to.eql(undeprecated); + }); + it('replaces keys', function () { var entity = iD.Entity({ tags: { power_rating: '1 billion volts' } }), graph = iD.actions.DeprecateTags(entity.id)(iD.Graph([entity])), From 7e997af9b8391e7189b9eac023cbdea60ff758d6 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Mon, 4 Feb 2013 17:24:00 -0500 Subject: [PATCH 162/415] Hide midpoints when vertices hidden --- js/id/svg/midpoints.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/js/id/svg/midpoints.js b/js/id/svg/midpoints.js index 935f84719..c59ef0bca 100644 --- a/js/id/svg/midpoints.js +++ b/js/id/svg/midpoints.js @@ -2,6 +2,10 @@ iD.svg.Midpoints = function(projection) { return function drawMidpoints(surface, graph, entities, filter) { var midpoints = {}; + if (!surface.select('.layer-hit').select('g.vertex').node()) { + return surface.select('.layer-hit').selectAll('g.midpoint').remove(); + } + for (var i = 0; i < entities.length; i++) { if (entities[i].type !== 'way') continue; From d5937907a45f7c405a072e96ff5e39eb9c2b71e4 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Mon, 4 Feb 2013 17:39:16 -0500 Subject: [PATCH 163/415] Hide midpoints without breaking drawing I always forget d3 works this way. --- js/id/svg/midpoints.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/id/svg/midpoints.js b/js/id/svg/midpoints.js index c59ef0bca..96048b1ed 100644 --- a/js/id/svg/midpoints.js +++ b/js/id/svg/midpoints.js @@ -2,8 +2,8 @@ iD.svg.Midpoints = function(projection) { return function drawMidpoints(surface, graph, entities, filter) { var midpoints = {}; - if (!surface.select('.layer-hit').select('g.vertex').node()) { - return surface.select('.layer-hit').selectAll('g.midpoint').remove(); + if (!surface.select('.layer-hit.g.vertex').node()) { + return surface.select('.layer-hit.g.midpoint').remove(); } for (var i = 0; i < entities.length; i++) { From c8ab057f19b58e724fba124360c4d05840a28726 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Mon, 4 Feb 2013 18:26:45 -0500 Subject: [PATCH 164/415] Remove unused midpoint code --- js/id/behavior/add_way.js | 3 +-- js/id/behavior/draw.js | 5 +---- js/id/behavior/draw_way.js | 14 -------------- js/id/modes/add_area.js | 15 --------------- js/id/modes/add_line.js | 14 -------------- js/id/modes/add_point.js | 1 - 6 files changed, 2 insertions(+), 50 deletions(-) diff --git a/js/id/behavior/add_way.js b/js/id/behavior/add_way.js index 207c88566..782d7f183 100644 --- a/js/id/behavior/add_way.js +++ b/js/id/behavior/add_way.js @@ -1,12 +1,11 @@ iD.behavior.AddWay = function(context) { - var event = d3.dispatch('start', 'startFromWay', 'startFromNode', 'startFromMidpoint'), + var event = d3.dispatch('start', 'startFromWay', 'startFromNode') draw = iD.behavior.Draw(context); var addWay = function(surface) { draw.on('click', event.start) .on('clickWay', event.startFromWay) .on('clickNode', event.startFromNode) - .on('clickMidpoint', event.startFromMidpoint) .on('cancel', addWay.cancel) .on('finish', addWay.cancel); diff --git a/js/id/behavior/draw.js b/js/id/behavior/draw.js index eb207eeb5..656eb7988 100644 --- a/js/id/behavior/draw.js +++ b/js/id/behavior/draw.js @@ -1,5 +1,5 @@ iD.behavior.Draw = function(context) { - var event = d3.dispatch('move', 'click', 'clickWay', 'clickNode', 'clickMidpoint', 'undo', 'cancel', 'finish'), + var event = d3.dispatch('move', 'click', 'clickWay', 'clickNode', 'undo', 'cancel', 'finish'), keybinding = d3.keybinding('draw'), hover = iD.behavior.Hover(); @@ -34,9 +34,6 @@ iD.behavior.Draw = function(context) { } else if (d.type === 'node') { event.clickNode(d); - } else if (d.type === 'midpoint') { - event.clickMidpoint(d); - } else { event.click(context.map().mouseCoordinates()); } diff --git a/js/id/behavior/draw_way.js b/js/id/behavior/draw_way.js index fa2c24c87..6b648db0e 100644 --- a/js/id/behavior/draw_way.js +++ b/js/id/behavior/draw_way.js @@ -46,7 +46,6 @@ iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) { .on('click', drawWay.add) .on('clickWay', drawWay.addWay) .on('clickNode', drawWay.addNode) - .on('clickMidpoint', drawWay.addMidpoint) .on('undo', context.undo) .on('cancel', drawWay.cancel) .on('finish', drawWay.finish); @@ -140,19 +139,6 @@ iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) { context.enter(mode); }; - // Add a midpoint, connect the way to it, and continue drawing. - drawWay.addMidpoint = function(midpoint) { - var node = iD.Node(); - - context.perform( - iD.actions.AddMidpoint(midpoint, node), - ReplaceTemporaryNode(node), - annotation); - - finished = true; - context.enter(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() { diff --git a/js/id/modes/add_area.js b/js/id/modes/add_area.js index 0a9496bb6..ae83423cb 100644 --- a/js/id/modes/add_area.js +++ b/js/id/modes/add_area.js @@ -11,7 +11,6 @@ iD.modes.AddArea = function(context) { .on('start', start) .on('startFromWay', startFromWay) .on('startFromNode', startFromNode) - .on('startFromMidpoint', startFromMidpoint), defaultTags = {area: 'yes'}; function start(loc) { @@ -55,20 +54,6 @@ iD.modes.AddArea = function(context) { context.enter(iD.modes.DrawArea(context, way.id, graph)); } - function startFromMidpoint(midpoint) { - var graph = context.graph(), - node = iD.Node(), - way = iD.Way({tags: defaultTags}); - - context.perform( - iD.actions.AddMidpoint(midpoint, node), - iD.actions.AddEntity(way), - iD.actions.AddVertex(way.id, node.id), - iD.actions.AddVertex(way.id, node.id)); - - context.enter(iD.modes.DrawArea(context, way.id, graph)); - } - mode.enter = function() { context.install(behavior); context.tail(t('modes.add_area.tail')); diff --git a/js/id/modes/add_line.js b/js/id/modes/add_line.js index 7845e6e6f..3d45df2a0 100644 --- a/js/id/modes/add_line.js +++ b/js/id/modes/add_line.js @@ -11,7 +11,6 @@ iD.modes.AddLine = function(context) { .on('start', start) .on('startFromWay', startFromWay) .on('startFromNode', startFromNode) - .on('startFromMidpoint', startFromMidpoint), defaultTags = {highway: 'residential'}; function start(loc) { @@ -63,19 +62,6 @@ iD.modes.AddLine = function(context) { } } - function startFromMidpoint(midpoint) { - var graph = context.graph(), - node = iD.Node(), - way = iD.Way({tags: defaultTags}); - - context.perform( - iD.actions.AddMidpoint(midpoint, node), - iD.actions.AddEntity(way), - iD.actions.AddVertex(way.id, node.id)); - - context.enter(iD.modes.DrawLine(context, way.id, 'forward', graph)); - } - mode.enter = function() { context.install(behavior); context.tail(t('modes.add_line.tail')); diff --git a/js/id/modes/add_point.js b/js/id/modes/add_point.js index 5a24dad25..457ed87a4 100644 --- a/js/id/modes/add_point.js +++ b/js/id/modes/add_point.js @@ -10,7 +10,6 @@ iD.modes.AddPoint = function(context) { .on('click', add) .on('clickWay', addWay) .on('clickNode', addNode) - .on('clickMidpoint', addNode) .on('cancel', cancel) .on('finish', cancel); From ab7c1fa80aa41cee16ef29daed005c6b8171ff0f Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Mon, 4 Feb 2013 18:32:16 -0500 Subject: [PATCH 165/415] Fix midpoint hiding. really --- js/id/svg/midpoints.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/id/svg/midpoints.js b/js/id/svg/midpoints.js index 96048b1ed..1db622612 100644 --- a/js/id/svg/midpoints.js +++ b/js/id/svg/midpoints.js @@ -2,8 +2,8 @@ iD.svg.Midpoints = function(projection) { return function drawMidpoints(surface, graph, entities, filter) { var midpoints = {}; - if (!surface.select('.layer-hit.g.vertex').node()) { - return surface.select('.layer-hit.g.midpoint').remove(); + if (!surface.select('.layer-hit g.vertex').node()) { + return surface.selectAll('.layer-hit g.midpoint').remove(); } for (var i = 0; i < entities.length; i++) { From d3c7c4be4b1e04eb594d174ed0a1182707a06ee8 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Mon, 4 Feb 2013 18:48:49 -0500 Subject: [PATCH 166/415] Add imagery convert script, source, and output --- data/imagery.json | 490 ++++++++++++++++++++++++++++++++++++++++ data/imagery.xml | 231 +++++++++++++++++++ data/imagery_convert.js | 45 ++++ 3 files changed, 766 insertions(+) create mode 100644 data/imagery.json create mode 100644 data/imagery.xml create mode 100644 data/imagery_convert.js diff --git a/data/imagery.json b/data/imagery.json new file mode 100644 index 000000000..2055c1cf5 --- /dev/null +++ b/data/imagery.json @@ -0,0 +1,490 @@ +[ + { + "name": "Bing aerial imagery", + "url": "http://ecn.t0.tiles.virtualearth.net/tiles/a{q}uadkey.jpeg?g=587&mkt=en-gb&n=z", + "sourcetag": "Bing", + "logo": "bing_maps.png", + "logo_url": "http://www.bing.com/maps", + "terms_url": "http://opengeodata.org/microsoft-imagery-details" + }, + { + "name": "MapBox Satellite", + "url": "http://{t}.tiles.mapbox.com/v3/openstreetmap.map-4wvf9l0l/{z}/{x}/{y}.png", + "subdomains": [ + "a", + "b", + "c" + ] + }, + { + "name": "MapQuest Open Aerial", + "url": "http://oatile1.mqcdn.com/tiles/1.0.0/sat/{z}/{x}/{y}.jpg" + }, + { + "name": "OSM - Mapnik", + "url": "http://{t}.tile.openstreetmap.org/{z}/{x}/{y}.png", + "subdomains": [ + "a", + "b", + "c" + ] + }, + { + "name": "OSM - OpenCycleMap", + "url": "http://tile.opencyclemap.org/cycle/{z}/{x}/{y}.png" + }, + { + "name": "OSM - MapQuest", + "url": "http://otile1.mqcdn.com/tiles/1.0.0/osm/{z}/{x}/{y}.jpg" + }, + { + "name": "OSM - Tiger Edited Map", + "url": "http://tiger-osm.mapquest.com/tiles/1.0.0/tiger/{z}/{x}/{y}.png", + "extent": [ + 24.055, + -124.81, + 49.386, + -66.865 + ] + }, + { + "name": "OSM - Tiger Edited Map", + "url": "http://tiger-osm.mapquest.com/tiles/1.0.0/tiger/{z}/{x}/{y}.png", + "extent": [ + 50.858, + -179.754, + 71.463, + -129.899 + ] + }, + { + "name": "OSM - Tiger Edited Map", + "url": "http://tiger-osm.mapquest.com/tiles/1.0.0/tiger/{z}/{x}/{y}.png", + "extent": [ + 18.702, + -174.46, + 26.501, + -154.516 + ] + }, + { + "name": "OSM US TIGER 2012 Roads Overlay", + "url": "http://{t}.tile.openstreetmap.us/tiger2012_roads_expanded/{z}/{x}/{y}.png", + "subdomains": [ + "a", + "b", + "c" + ], + "extent": [ + 24.055, + -124.81, + 49.386, + -66.865 + ] + }, + { + "name": "OSM US TIGER 2012 Roads Overlay", + "url": "http://{t}.tile.openstreetmap.us/tiger2012_roads_expanded/{z}/{x}/{y}.png", + "subdomains": [ + "a", + "b", + "c" + ], + "extent": [ + 50.858, + -179.754, + 71.463, + -129.899 + ] + }, + { + "name": "OSM US TIGER 2012 Roads Overlay", + "url": "http://{t}.tile.openstreetmap.us/tiger2012_roads_expanded/{z}/{x}/{y}.png", + "subdomains": [ + "a", + "b", + "c" + ], + "extent": [ + 18.702, + -174.46, + 26.501, + -154.516 + ] + }, + { + "name": "OSM US USGS Topographic Maps", + "url": "http://{t}.tile.openstreetmap.us/usgs_scanned_topos/{z}/{x}/{y}.png", + "subdomains": [ + "a", + "b", + "c" + ], + "extent": [ + 24.005, + -125.991, + 50.009, + -65.988 + ] + }, + { + "name": "OSM US USGS Topographic Maps", + "url": "http://{t}.tile.openstreetmap.us/usgs_scanned_topos/{z}/{x}/{y}.png", + "subdomains": [ + "a", + "b", + "c" + ], + "extent": [ + 18.902, + -160.579, + 22.508, + -154.793 + ] + }, + { + "name": "OSM US USGS Topographic Maps", + "url": "http://{t}.tile.openstreetmap.us/usgs_scanned_topos/{z}/{x}/{y}.png", + "subdomains": [ + "a", + "b", + "c" + ], + "extent": [ + 51.255, + -178.001, + 71.999, + -130.004 + ] + }, + { + "name": "OSM US USGS Large Scale Aerial Imagery", + "url": "http://{t}.tile.openstreetmap.us/usgs_large_scale/{z}/{x}/{y}.jpg", + "subdomains": [ + "a", + "b", + "c" + ], + "extent": [ + 24.496, + -124.819, + 49.443, + -66.931 + ] + }, + { + "name": "British Columbia bc_mosaic", + "url": "http://{t}.imagery.paulnorman.ca/tiles/bc_mosaic/{z}/{x}/{y}.png", + "subdomains": [ + "a", + "b", + "c", + "d" + ], + "extent": [ + 48.995, + -123.441, + 50.426, + -121.346 + ], + "sourcetag": "bc_mosaic", + "terms_url": "http://imagery.paulnorman.ca/tiles/about.html" + }, + { + "name": "OS OpenData Streetview", + "url": "http://os.openstreetmap.org/sv/{z}/{x}/{y}.png", + "extent": [ + 49.86, + -8.72, + 60.92, + 1.84 + ], + "sourcetag": "OS_OpenData_StreetView" + }, + { + "name": "OS OpenData Locator", + "url": "http://tiles.itoworld.com/os_locator/{z}/{x}/{y}.png", + "extent": [ + 49.8, + -9, + 61.1, + 1.9 + ], + "sourcetag": "OS_OpenData_Locator" + }, + { + "name": "OS 1:25k historic (OSM)", + "url": "http://ooc.openstreetmap.org/os1/{z}/{x}/{y}.jpg", + "extent": [ + 49.8, + -9, + 61.1, + 1.9 + ], + "sourcetag": "OS 1:25k" + }, + { + "name": "OS 1:25k historic (NLS)", + "url": "http://geo.nls.uk/mapdata2/os/25000/{z}/{x}/{y}.png", + "extent": [ + 49.8, + -9, + 61.1, + 1.9 + ], + "sourcetag": "OS 1:25k", + "logo": "icons/logo_nls70-nq8.png", + "logo_url": "http://geo.nls.uk/maps/" + }, + { + "name": "OS 7th Series historic (OSM)", + "url": "http://ooc.openstreetmap.org/os7/{z}/{x}/{y}.jpg", + "extent": [ + 49.8, + -9, + 61.1, + 1.9 + ], + "sourcetag": "OS7" + }, + { + "name": "OS 7th Series historic (NLS)", + "url": "http://geo.nls.uk/mapdata2/os/seventh/{z}/{x}/{y}.png", + "extent": [ + 49.8, + -9, + 61.1, + 1.9 + ], + "sourcetag": "OS7", + "logo": "icons/logo_nls70-nq8.png", + "logo_url": "http://geo.nls.uk/maps/" + }, + { + "name": "OS New Popular Edition historic", + "url": "http://ooc.openstreetmap.org/npe/{z}/{x}/{y}.png", + "extent": [ + 49.8, + -5.8, + 55.8, + 1.9 + ], + "sourcetag": "NPE" + }, + { + "name": "OS Scottish Popular historic", + "url": "http://ooc.openstreetmap.org/npescotland/tiles/{z}/{x}/{y}.jpg", + "extent": [ + 54.5, + -7.8, + 61.1, + -1.1 + ], + "sourcetag": "NPE" + }, + { + "name": "Surrey aerial", + "url": "http://gravitystorm.dev.openstreetmap.org/surrey/{z}/{x}/{y}.png", + "extent": [ + 51.071, + -0.856, + 51.473, + 0.062 + ], + "sourcetag": "Surrey aerial" + }, + { + "name": "Haiti - GeoEye Jan 13", + "url": "http://gravitystorm.dev.openstreetmap.org/imagery/haiti/{z}/{x}/{y}.jpg", + "extent": [ + 17.95, + -74.5, + 20.12, + -71.58 + ], + "sourcetag": "Haiti GeoEye" + }, + { + "name": "Haiti - GeoEye Jan 13+", + "url": "http://maps.nypl.org/tilecache/1/geoeye/{z}/{x}/{y}.jpg", + "extent": [ + 17.95, + -74.5, + 20.12, + -71.58 + ], + "sourcetag": "Haiti GeoEye" + }, + { + "name": "Haiti - DigitalGlobe", + "url": "http://maps.nypl.org/tilecache/1/dg_crisis/{z}/{x}/{y}.jpg", + "extent": [ + 17.95, + -74.5, + 20.12, + -71.58 + ], + "sourcetag": "Haiti DigitalGlobe" + }, + { + "name": "Haiti - Street names", + "url": "http://hypercube.telascience.org/tiles/1.0.0/haiti-city/{z}/{x}/{y}.jpg", + "extent": [ + 17.95, + -74.5, + 20.12, + -71.58 + ], + "sourcetag": "Haiti streetnames" + }, + { + "name": "National Agriculture Imagery Program", + "url": "http://cube.telascience.org/tilecache/tilecache.py/NAIP_ALL/{z}/{x}/{y}.png", + "extent": [ + 24.2, + -125.8, + 49.5, + -62.3 + ], + "sourcetag": "NAIP" + }, + { + "name": "National Agriculture Imagery Program", + "url": "http://cube.telascience.org/tilecache/tilecache.py/NAIP_ALL/{z}/{x}/{y}.png", + "extent": [ + 55.3, + -168.5, + 71.5, + -140 + ], + "sourcetag": "NAIP" + }, + { + "name": "Ireland - NLS Historic Maps", + "url": "http://geo.nls.uk/maps/ireland/gsgs4136/{z}/{x}/{y}.png", + "extent": [ + 51.32, + -10.71, + 55.46, + -5.37 + ], + "sourcetag": "NLS Historic Maps", + "logo": "icons/logo_nls70-nq8.png", + "logo_url": "http://geo.nls.uk/maps/" + }, + { + "name": "Denmark - Fugro Aerial Imagery", + "url": "http://tile.openstreetmap.dk/fugro2005/{z}/{x}/{y}.jpg", + "extent": [ + 54.44, + 7.81, + 57.86, + 15.49 + ], + "sourcetag": "Fugro (2005)" + }, + { + "name": "Denmark - Stevns Kommune", + "url": "http://tile.openstreetmap.dk/stevns/2009/{z}/{x}/{y}.jpg", + "extent": [ + 55.23403, + 12.09144, + 55.43647, + 12.47712 + ], + "sourcetag": "Stevns Kommune (2009)" + }, + { + "name": "Austria - geoimage.at", + "url": "http://geoimage.openstreetmap.at/4d80de696cd562a63ce463a58a61488d/{z}/{x}/{y}.jpg", + "extent": [ + 46.33, + 9.36, + 49.09, + 17.28 + ], + "sourcetag": "geoimage.at" + }, + { + "name": "Russia - Kosmosnimki.ru IRS Satellite", + "url": "http://irs.gis-lab.info/?layers=irs&request=GetTile&z={z}&x={x}&y={y}", + "extent": [ + 40.96, + 19.02, + 70.48, + 77.34 + ], + "sourcetag": "Kosmosnimki.ru IRS" + }, + { + "name": "Belarus - Kosmosnimki.ru SPOT4 Satellite", + "url": "http://irs.gis-lab.info/?layers=spot&request=GetTile&z={z}&x={x}&y={y}", + "extent": [ + 51.25, + 23.16, + 56.19, + 32.83 + ], + "sourcetag": "Kosmosnimki.ru SPOT4" + }, + { + "name": "Australia - Geographic Reference Image", + "url": "http://agri.openstreetmap.org/{z}/{x}/{y}.png", + "extent": [ + -44, + 96, + -9, + 168 + ], + "sourcetag": "AGRI" + }, + { + "name": "Switzerland - Canton Aargau - AGIS 25cm 2011", + "url": "http://tiles.poole.ch/AGIS/OF2011/{z}/{x}/{y}.png", + "extent": [ + 47.13, + 7.69, + 47.63, + 8.48 + ], + "sourcetag": "AGIS OF2011" + }, + { + "name": "Switzerland - Canton Solothurn - SOGIS 2007", + "url": "http://mapproxy.sosm.ch:8080/tiles/sogis2007/EPSG900913/{z}/{x}/{y}.png?origin=nw", + "extent": [ + 47.06, + 7.33, + 47.5, + 8.04 + ], + "sourcetag": "Orthofoto 2007 WMS Solothurn" + }, + { + "name": "Poland - Media-Lab fleet GPS masstracks", + "url": "http://masstracks.media-lab.com.pl/{z}/{x}/{y}.png", + "extent": [ + 48.9, + 14, + 55, + 24.2 + ], + "sourcetag": "masstracks" + }, + { + "name": "South Africa - CD:NGI Aerial", + "url": "http://{t}.aerial.openstreetmap.org.za/ngi-aerial/{z}/{x}/{y}.jpg", + "subdomains": [ + "a", + "b", + "c" + ], + "extent": [ + -34.95, + 17.64, + -22.05, + 32.87 + ], + "sourcetag": "ngi-aerial" + } +] \ No newline at end of file diff --git a/data/imagery.xml b/data/imagery.xml new file mode 100644 index 000000000..c829ab141 --- /dev/null +++ b/data/imagery.xml @@ -0,0 +1,231 @@ + + + + Bing aerial imagery + http://ecn.t0.tiles.virtualearth.net/tiles/a$quadkey.jpeg?g=587&mkt=en-gb&n=z + microsoft + Bing + http://dev.virtualearth.net/REST/v1/Imagery/Metadata/Aerial/0,0?zl=1&mapVersion=v1&key=Arzdiw4nlOJzRwOz__qailc8NiR31Tt51dN2D7cm57NrnceZnCpgOkmJhNpGoppU&include=ImageryProviders&output=xml + bing_maps.png + http://www.bing.com/maps + http://opengeodata.org/microsoft-imagery-details + yes + + + MapBox Satellite + http://${a|b|c}.tiles.mapbox.com/v3/openstreetmap.map-4wvf9l0l/$z/$x/$y.png + + + MapQuest Open Aerial + http://oatile1.mqcdn.com/tiles/1.0.0/sat/$z/$x/$y.jpg + + + OSM - Mapnik + http://${a|b|c}.tile.openstreetmap.org/$z/$x/$y.png + + + OSM - OpenCycleMap + http://tile.opencyclemap.org/cycle/$z/$x/$y.png + + + OSM - MapQuest + http://otile1.mqcdn.com/tiles/1.0.0/osm/$z/$x/$y.jpg + + + OSM - Tiger Edited Map + 900913 + http://tiger-osm.mapquest.com/tiles/1.0.0/tiger/$z/$x/$y.png + + + OSM - Tiger Edited Map + 900913 + http://tiger-osm.mapquest.com/tiles/1.0.0/tiger/$z/$x/$y.png + + + OSM - Tiger Edited Map + 900913 + http://tiger-osm.mapquest.com/tiles/1.0.0/tiger/$z/$x/$y.png + + + OSM US TIGER 2012 Roads Overlay + 900913 + http://${a|b|c}.tile.openstreetmap.us/tiger2012_roads_expanded/$z/$x/$y.png + + + OSM US TIGER 2012 Roads Overlay + 900913 + http://${a|b|c}.tile.openstreetmap.us/tiger2012_roads_expanded/$z/$x/$y.png + + + OSM US TIGER 2012 Roads Overlay + 900913 + http://${a|b|c}.tile.openstreetmap.us/tiger2012_roads_expanded/$z/$x/$y.png + + + OSM US USGS Topographic Maps + 900913 + http://${a|b|c}.tile.openstreetmap.us/usgs_scanned_topos/$z/$x/$y.png + + + OSM US USGS Topographic Maps + 900913 + http://${a|b|c}.tile.openstreetmap.us/usgs_scanned_topos/$z/$x/$y.png + + + OSM US USGS Topographic Maps + 900913 + http://${a|b|c}.tile.openstreetmap.us/usgs_scanned_topos/$z/$x/$y.png + + + OSM US USGS Large Scale Aerial Imagery + 900913 + http://${a|b|c}.tile.openstreetmap.us/usgs_large_scale/$z/$x/$y.jpg + + + British Columbia bc_mosaic + 900913 + http://${a|b|c|d}.imagery.paulnorman.ca/tiles/bc_mosaic/$z/$x/$y.png + http://imagery.paulnorman.ca/tiles/about.html + bc_mosaic + + + OS OpenData Streetview + http://os.openstreetmap.org/sv/$z/$x/$y.png + OS_OpenData_StreetView + + + OS OpenData Locator + http://tiles.itoworld.com/os_locator/$z/$x/$y.png + OS_OpenData_Locator + source:name + + + OS 1:25k historic (OSM) + http://ooc.openstreetmap.org/os1/$z/$x/$y.jpg + OS 1:25k + + + OS 1:25k historic (NLS) + tms + http://geo.nls.uk/mapdata2/os/25000/$z/$x/$y.png + icons/logo_nls70-nq8.png + http://geo.nls.uk/maps/ + OS 1:25k + + + OS 7th Series historic (OSM) + http://ooc.openstreetmap.org/os7/$z/$x/$y.jpg + OS7 + + + OS 7th Series historic (NLS) + tms + http://geo.nls.uk/mapdata2/os/seventh/$z/$x/$y.png + icons/logo_nls70-nq8.png + http://geo.nls.uk/maps/ + OS7 + + + OS New Popular Edition historic + http://ooc.openstreetmap.org/npe/$z/$x/$y.png + NPE + + + OS Scottish Popular historic + http://ooc.openstreetmap.org/npescotland/tiles/$z/$x/$y.jpg + NPE + + + Surrey aerial + http://gravitystorm.dev.openstreetmap.org/surrey/$z/$x/$y.png + Surrey aerial + + + Haiti - GeoEye Jan 13 + http://gravitystorm.dev.openstreetmap.org/imagery/haiti/$z/$x/$y.jpg + Haiti GeoEye + + + Haiti - GeoEye Jan 13+ + http://maps.nypl.org/tilecache/1/geoeye/$z/$x/$y.jpg + Haiti GeoEye + + + Haiti - DigitalGlobe + http://maps.nypl.org/tilecache/1/dg_crisis/$z/$x/$y.jpg + Haiti DigitalGlobe + + + Haiti - Street names + http://hypercube.telascience.org/tiles/1.0.0/haiti-city/$z/$x/$y.jpg + Haiti streetnames + + + National Agriculture Imagery Program + http://cube.telascience.org/tilecache/tilecache.py/NAIP_ALL/$z/$x/$y.png + NAIP + + + National Agriculture Imagery Program + http://cube.telascience.org/tilecache/tilecache.py/NAIP_ALL/$z/$x/$y.png + NAIP + + + Ireland - NLS Historic Maps + tms + NLS Historic Maps + http://geo.nls.uk/maps/ireland/gsgs4136/$z/$x/$y.png + icons/logo_nls70-nq8.png + http://geo.nls.uk/maps/ + + + Denmark - Fugro Aerial Imagery + http://tile.openstreetmap.dk/fugro2005/$z/$x/$y.jpg + Fugro (2005) + + + Denmark - Stevns Kommune + http://tile.openstreetmap.dk/stevns/2009/$z/$x/$y.jpg + Stevns Kommune (2009) + + + Austria - geoimage.at + http://geoimage.openstreetmap.at/4d80de696cd562a63ce463a58a61488d/$z/$x/$y.jpg + geoimage.at + + + Russia - Kosmosnimki.ru IRS Satellite + http://irs.gis-lab.info/?layers=irs&request=GetTile&z=$z&x=$x&y=$y + Kosmosnimki.ru IRS + + + Belarus - Kosmosnimki.ru SPOT4 Satellite + http://irs.gis-lab.info/?layers=spot&request=GetTile&z=$z&x=$x&y=$y + Kosmosnimki.ru SPOT4 + + + Australia - Geographic Reference Image + http://agri.openstreetmap.org/$z/$x/$y.png + AGRI + + + Switzerland - Canton Aargau - AGIS 25cm 2011 + http://tiles.poole.ch/AGIS/OF2011/$z/$x/$y.png + AGIS OF2011 + + + Switzerland - Canton Solothurn - SOGIS 2007 + http://mapproxy.sosm.ch:8080/tiles/sogis2007/EPSG900913/$z/$x/$y.png?origin=nw + Orthofoto 2007 WMS Solothurn + + + Poland - Media-Lab fleet GPS masstracks + http://masstracks.media-lab.com.pl/$z/$x/$y.png + masstracks + + + South Africa - CD:NGI Aerial + http://${a|b|c}.aerial.openstreetmap.org.za/ngi-aerial/$z/$x/$y.jpg + ngi-aerial + + diff --git a/data/imagery_convert.js b/data/imagery_convert.js new file mode 100644 index 000000000..16cc23317 --- /dev/null +++ b/data/imagery_convert.js @@ -0,0 +1,45 @@ +var fs = require('fs'), + cheerio = require('cheerio'); + +$ = cheerio.load(fs.readFileSync('imagery.xml')); + +var imagery = []; + +$('set').each(function(i) { + var elem = $(this); + + var im = { + name: $(this).find('name').first().text(), + url: $(this).find('url').first().text() + }; + + var subdomains = []; + + im.url = im.url + .replace(/\$(\w)/g, function(m) { + return '{' + m[1] + '}'; + }) + .replace(/\$\{([^}.]+)\}/g, function(m) { + subdomains = m.slice(2, m.length - 1).split('|'); + return '{t}'; + }); + + if (subdomains.length) im.subdomains = subdomains; + + if (elem.attr('minlat')) { + im.extent = [ + +elem.attr('minlat'), + +elem.attr('minlon'), + +elem.attr('maxlat'), + +elem.attr('maxlon')]; + } + + ['sourcetag', 'logo', 'logo_url', 'terms_url'].forEach(function(a) { + if (elem.find(a).length) { + im[a] = elem.find(a).first().text(); + } + }); + imagery.push(im); +}); + +fs.writeFileSync('imagery.json', JSON.stringify(imagery, null, 4)); From 5764012b2328e36eea7ee3e6425bfdc7eb93608b Mon Sep 17 00:00:00 2001 From: Saman Bemel-Benrud Date: Mon, 4 Feb 2013 19:37:10 -0500 Subject: [PATCH 167/415] better styles for map features. --- css/map.css | 136 +++-- icons/tree.png | Bin 102 -> 158 bytes icons/unknown.png | Bin 1476 -> 338 bytes img/source/sprite.svg | 1284 +++++++--------------------------------- js/id/svg/lines.js | 2 +- js/id/svg/midpoints.js | 2 +- js/id/svg/vertices.js | 2 +- 7 files changed, 306 insertions(+), 1120 deletions(-) diff --git a/css/map.css b/css/map.css index ab2a352c9..0b2da4397 100644 --- a/css/map.css +++ b/css/map.css @@ -17,11 +17,11 @@ g.point .shadow { -moz-transition: fill 100ms linear; } .behavior-hover g.point.hover:not(.selected) .shadow { - fill: #E96666; - fill-opacity: 0.3; + fill: #f6634f; + fill-opacity: 0.5; } g.point.selected .shadow { - fill: #E96666; + fill: #f6634f; fill-opacity: 0.7; } @@ -30,8 +30,10 @@ g.point.selected .shadow { g.vertex .fill { fill:white; } + g.vertex .stroke { - stroke:#333; + stroke:black; + stroke-opacity: .5; stroke-width:2; fill:white; } @@ -60,69 +62,72 @@ svg[data-zoom="17"] g.vertex .fill { transform:scale(0.7, 0.7); } -g.vertex.shared .shadow { +g.vertex.sha#f6634f .shadow { -webkit-transform:scale(1.2, 1.2); -moz-transform:scale(1.2, 1.2); transform:scale(1.2, 1.2); } -g.vertex.shared .fill, -g.vertex.shared .stroke { +g.vertex.sha#f6634f .fill, +g.vertex.sha#f6634f .stroke { -webkit-transform:scale(1.1, 1.1); -moz-transform:scale(1.1, 1.1); transform:scale(1.1, 1.1); } -svg[data-zoom="16"] g.vertex.shared .shadow { +svg[data-zoom="16"] g.vertex.sha#f6634f .shadow { -webkit-transform:scale(0.9, 0.9); -moz-transform:scale(0.9, 0.9); transform:scale(0.9, 0.9); } -svg[data-zoom="16"] g.vertex.shared .fill, -svg[data-zoom="16"] g.vertex.shared .stroke { +svg[data-zoom="16"] g.vertex.sha#f6634f .fill, +svg[data-zoom="16"] g.vertex.sha#f6634f .stroke { -webkit-transform:scale(0.8, 0.8); -moz-transform:scale(0.8, 0.8); transform:scale(0.8, 0.8); } -svg[data-zoom="17"] g.vertex.shared .shadow { +svg[data-zoom="17"] g.vertex.sha#f6634f .shadow { -webkit-transform:scale(1, 1); -moz-transform:scale(1, 1); transform:scale(1, 1); } -svg[data-zoom="17"] g.vertex.shared .fill, -svg[data-zoom="17"] g.vertex.shared .stroke { +svg[data-zoom="17"] g.vertex.sha#f6634f .fill, +svg[data-zoom="17"] g.vertex.sha#f6634f .stroke { -webkit-transform:scale(0.9, 0.9); -moz-transform:scale(0.9, 0.9); transform:scale(0.9, 0.9); } -g.vertex.shared .fill { +g.vertex.sha#f6634f .fill { fill:#aaa; } g.vertex .shadow { fill: none; pointer-events: all; - stroke-width: 10; + stroke-width: 20; -webkit-transition: -webkit-transform 100ms linear; transition: transform 100ms linear; -moz-transition: fill 100ms linear; } .behavior-hover g.vertex.hover:not(.selected) .shadow { - fill: #E96666; + fill: #f6634f; fill-opacity: 0.3; -} +} g.vertex.selected .shadow { - fill: #E96666; - fill-opacity: 0.7; + fill: #f6634f; + fill-opacity: 0.5; } /* midpoints */ g.midpoint .fill { - fill:#aaa; + fill:#ddd; + stroke:black; + stroke-opacity: .5; + opacity: .5; } .behavior-hover g.midpoint .fill.hover:not(.selected) { - fill:#fff; - stroke:#000; + fill:white; + opacity: .75; } g.midpoint .shadow { @@ -134,7 +139,7 @@ g.midpoint .shadow { -moz-transition: fill 100ms linear; } .behavior-hover g.midpoint .shadow.hover:not(.selected) { - fill:#E96666; + fill:#f6634f; fill-opacity: 0.3; } @@ -146,8 +151,8 @@ path.line { } path.stroke { - stroke: #222; - stroke-width: 2; + stroke: black; + stroke-width: 4; } path.shadow { @@ -157,12 +162,12 @@ path.shadow { } .behavior-hover path.shadow.hover:not(.selected) { - stroke: #E96666; + stroke: #f6634f; stroke-opacity: 0.3; } path.shadow.selected { - stroke: #E96666; + stroke: #f6634f; stroke-opacity: 0.7; } @@ -191,31 +196,31 @@ path.area.stroke.selected { path.area.stroke.tag-natural, path.multipolygon.tag-natural { - stroke: #ADD6A5; + stroke: #b6e199; stroke-width:1; } path.area.fill.tag-natural, path.multipolygon.tag-natural { - fill: #ADD6A5; + fill: #b6e199; } path.area.stroke.tag-natural-water, path.multipolygon.tag-natural-water { - stroke: #6382FF; + stroke: #77d3de; } path.area.fill.tag-natural-water, path.multipolygon.tag-natural-water { - fill: #ADBEFF; + fill: #77d3de; } path.area.stroke.tag-building, path.multipolygon.tag-building { - stroke: #9E176A; + stroke: #e06e5f; stroke-width: 1; } path.area.fill.tag-building, path.multipolygon.tag-building { - fill: #ff6ec7; + fill: #e06e5f; } path.area.stroke.tag-landuse, @@ -228,7 +233,7 @@ path.multipolygon.tag-natural-wood, path.multipolygon.tag-natural-tree, path.multipolygon.tag-natural-grassland, path.multipolygon.tag-leisure-park { - stroke: #006B34; + stroke: #8cd05f; stroke-width: 1; } path.area.fill.tag-landuse, @@ -241,18 +246,18 @@ path.multipolygon.tag-natural-wood, path.multipolygon.tag-natural-tree, path.multipolygon.tag-natural-grassland, path.multipolygon.tag-leisure-park { - fill: #189E59; + fill: #8cd05f; fill-opacity: 0.2; } path.area.stroke.tag-amenity-parking, path.multipolygon.tag-amenity-parking { - stroke: #beb267; + stroke: #aaa; stroke-width: 1; } path.area.fill.tag-amenity-parking, path.multipolygon.tag-amenity-parking { - fill: #edecc0; + fill: #aaa; } path.multipolygon.tag-boundary { @@ -286,56 +291,57 @@ svg[data-zoom="16"] path.stroke.tag-highway { path.stroke.tag-highway-motorway, path.stroke.tag-highway-motorway_link, path.stroke.tag-construction-motorway { - stroke:#809bc0; + stroke:#58a9ed; } + path.casing.tag-highway-motorway, path.casing.tag-highway-motorway_link, path.casing.tag-construction-motorway { - stroke:#506077; + stroke:#2c5476; } path.stroke.tag-highway-trunk, path.stroke.tag-highway-trunk_link, path.stroke.tag-construction-trunk { - stroke:#97d397; + stroke:#8cd05f; } path.casing.tag-highway-trunk, path.casing.tag-highway-trunk_link, path.casing.tag-construction-trunk { - stroke:#477147; + stroke:#46682f; } path.stroke.tag-highway-primary, path.stroke.tag-highway-primary_link, path.stroke.tag-construction-primary { - stroke:#ec989a; + stroke:#e06d5f; } path.casing.tag-highway-primary, path.casing.tag-highway-primary_link, path.casing.tag-construction-primary { - stroke:#8d4346; + stroke:#70372f; } path.stroke.tag-highway-secondary, path.stroke.tag-highway-secondary_link, path.stroke.tag-construction-secondary { - stroke:#fecc8b; + stroke:#eab056; } path.casing.tag-highway-secondary, path.casing.tag-highway-secondary_link, path.casing.tag-construction-secondary { - stroke:#a37b48; + stroke:#75582b; } path.stroke.tag-highway-tertiary, path.stroke.tag-highway-tertiary_link, path.stroke.tag-construction-tertiary { - stroke:#ffffb3; + stroke:#ffff7e; } path.casing.tag-highway-tertiary, path.casing.tag-highway-tertiary_link, path.casing.tag-construction-tertiary { - stroke:#bbb; + stroke:#7f7f3f; } path.stroke.tag-highway-unclassified, @@ -372,7 +378,7 @@ path.stroke.tag-highway-pedestrian { shapeRendering: auto; } path.casing.tag-highway-pedestrian { - stroke:#84C382; + stroke:#8cd05f; stroke-width:6 !important; } @@ -445,17 +451,17 @@ svg[data-zoom="16"] path.casing.tag-highway-bridleway { } path.stroke.tag-highway-footway { - stroke: #996600; + stroke: #ae8681; } path.stroke.tag-highway-cycleway { - stroke: #69f; + stroke: #58a9ed; } path.stroke.tag-highway-bridleway { - stroke: green; + stroke: #e06d5f; } path.stroke.tag-highway-steps { - stroke: #ff6257; + stroke: #81d25c; stroke-width: 4; stroke-linecap: butt; stroke-dasharray: 3, 3; @@ -467,7 +473,7 @@ path.casing.tag-highway-steps { path.casing.tag-bridge-yes { stroke-width: 14; - stroke: #000; + stroke: #333; } path.stroke.tag-highway-construction, @@ -511,12 +517,16 @@ path.casing.tag-railway-subway { /* waterways */ +path.area.fill.tag-waterway { + fill: #77d3de; +} + path.stroke.tag-waterway { - stroke: #10539a; + stroke: #77d3de; stroke-width: 2; } path.casing.tag-waterway { - stroke: #6AA2FF; + stroke: #77d3de; stroke-width: 4; } @@ -535,11 +545,11 @@ svg[data-zoom="16"] path.casing.tag-waterway-river { } path.stroke.tag-waterway-ditch { - stroke: #10539a; + stroke: #6591ff; stroke-width: 1; } path.casing.tag-waterway-ditch { - stroke: #999692; + stroke: #6591ff; stroke-width: 3; } @@ -568,17 +578,22 @@ path.casing.tag-boundary { path.casing.tag-boundary-protected_area, path.casing.tag-boundary-national_park { - stroke: #4D9849; + stroke: #b0e298; } text { font-size:10px; pointer-events: none; + color: #222; + opacity: 1; } .oneway .textpath { pointer-events: none; + font-size: 7px; + baseline-shift: 2px; + opacity: .7; } text.tag-oneway { @@ -606,7 +621,7 @@ text.pathlabel, text.pointlabel { font-size: 12px; font-weight: bold; - fill: black; + fill: #333; text-anchor: middle; pointer-events: none; } @@ -632,7 +647,8 @@ text.pointlabel { text.point { - font-size: 9px; + font-size: 10px; + baseline-shift: 2px; } /* Cursors */ diff --git a/icons/tree.png b/icons/tree.png index 7575bd63b3fe7db6fb5747751fcf545792102bad..d88c945d4682230d400dae67eabaa572a96141ad 100644 GIT binary patch literal 158 zcmeAS@N?(olHy`uVBq!ia0vp^93afW1SGw4HSYi^wj^(N7l!{JxM1({$v}}{PZ!4! zj+x1S{{OdUHapM~I;F9(k=5z$?~=s(mzVPJ@SMA}v8$$vL$-RJ&aWS@zaEcY|J^3G zBk4*#+X~+T?S(??j6T||VX|4%w9bfAK+tVcEGxso(=xyAAM=$28qeVA>gTe~DWM4f DxBfYi literal 102 zcmeAS@N?(olHy`uVBq!ia0vp^%plCc1SD^IDZKzvoCO|{#S9GGLLkg|>2BR0prC}O wi(^Q|oaBhlC5u1Fge#n6SfbFusK>@|nT1bw>bJ?OfNB^#UHx3vIVCg!06AzGssI20 diff --git a/icons/unknown.png b/icons/unknown.png index 404602aa4de44786ef78c6551f51c3e23a3ec3da..03fff0c0e1eb35671712f853dd8ee43f0420828d 100644 GIT binary patch delta 292 zcmV+<0o(q>3(^9RBn<>}LP=Bz2nYy#2xN$nAs2rD8FWQhbW?9;ba!ELWdL_~cP?pe zYja~^aAhuUa%Y?FJQ@H10MAK8K~y-6rISqx!Y~kp--u`GLhvX(h9FEzs%1d%4sN|d z_ks&g;-Ywh;5i|2;R1lv0Fot8vKHek&!`f5tWfPcxkpTtflQ6m9vp|qAiGYaibJIDK z2>QOi0q7>?yTI52@BJG<3BbCZ3ZM*(EdT)LTuVf?v3l*CYxCaxWXya9AW< zB%&n3*T*V3KUXg?B|j-uuOhbqsG5Pnrosxy%uOvxRH(?!$t$+1uvG%9umZ9{!um=I zU?nBlwn`Dc0SeCfMX3s=dWL#NN_Jcd3JNwwDQQ+gE^bimK%T8qMoCG5mA-y?dAVM> zv0i>ry1t>MrKP@sk-m|UE>MMTab;dfVufyAu`tKo-FP)SbBnaEtPap}qq8Pro9uK;KZ$Kp$>0P@@gdk5<0FM2bZyT0xdlb3#l;|NOrh$L#n9CwYzfWFEP=ZWO&DEQ z1VY{p&2h+5P;FET_|%F_903oK!3=nis1-PUM7U(;rsjb|#n8+~AFBkCC&BX0`8oMT z!3BxQsdi?jrpCa~L>ETa0k$dwEs=+y_)3i1SltFNa%`2Fun>D}<9SD1g#C<>qU z)av}_{MqZ@_;)ON!5hJ|aR%QF)*CvWlcr`W@l*NrrP+B7@W4v%HFamrI&$L|i{Iz45v8$bT8BvmJYEg;$@?_^@=V5Pw#uY>3ai-4;iV@a2?qh#&dz>sguq|97Sr jmh8KA%Ta#k>hJu2F7G+w=fBYeRL*+3`njxgN@xNAqdD_9 diff --git a/img/source/sprite.svg b/img/source/sprite.svg index 29e87e2b8..64f682c3d 100644 --- a/img/source/sprite.svg +++ b/img/source/sprite.svg @@ -39,12 +39,12 @@ borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" - inkscape:zoom="1" - inkscape:cx="329.64693" - inkscape:cy="58.693021" + inkscape:zoom="0.5" + inkscape:cx="279.87773" + inkscape:cy="136.54467" inkscape:document-units="px" inkscape:current-layer="layer1" - showgrid="false" + showgrid="true" inkscape:window-width="1280" inkscape:window-height="700" inkscape:window-x="48" @@ -186,7 +186,7 @@ image/svg+xml - + @@ -195,11 +195,200 @@ inkscape:groupmode="layer" id="layer1" transform="translate(-25,-62.362183)" - style="display:inline"> + style="display:none"> + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + fix misalignment + + + + + RESET + style="display:inline;fill:#1a1a1a;fill-opacity:1" + transform="translate(505,-653.36218)"> + style="display:inline;color:#000000;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;overflow:visible;enable-background:accumulate" /> - - - - - - - - - - - - - + style="opacity:0.50000000000000000;color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /> + style="opacity:0.50000000000000000;fill:#000000;fill-opacity:1;display:inline"> + style="opacity:0.50000000000000000;fill:#000000;fill-opacity:1;display:inline"> @@ -778,9 +932,9 @@ inkscape:connector-curvature="0" id="path58971" d="m 35,44 c 0,1 0,4 0,4 0,0 0,0.5 -0.5,0.5 -0.5,0 -0.429283,-0.27516 -0.5,-0.5 -0.304688,-0.96875 -0.867187,-2.36459 -1,-3 -0.204595,-0.97885 -0.666667,-1 -1,-1 -1,0 -1,1 -1,1 l 1,4 0,3 C 32,52 31.5,51.5 30.5,50.5 29.945312,49.94531 29.257659,49.7508 28.8125,50.00781 28.377049,50.25922 28.150942,50.89541 28.5,51.5 28.853553,52.11237 32,56 32,56 c 1,1 2,1 4,1 2.666667,0 1,0 3,0 2,0 2.288488,-2.86546 3,-5 1,-3 1.5,-5 1.5,-5 0.113427,-0.42332 -0.04289,-0.846 -0.5,-1 -0.880461,-0.29662 -1.36006,0.35278 -1.5,1 -0.25,1.15625 -0.5,2 -0.5,2 -0.09375,0.31383 0.0013,0.5 -0.5,0.5 C 39.99086,49.5 40,49 40,49 c 0,0 0,-2.66667 0,-4 0,-1 -1,-1 -1,-1 0,0 -1,0 -1,1 0,1 0,1.66667 0,3 0,0 0.01305,0.5 -0.5,0.5 C 36.998673,48.5 37,48 37,48 c 0,0 0,-3 0,-4 0,-1 -1,-1 -1,-1 0,0 -1,0 -1,1 z" - style="opacity:0.5;color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;filter:url(#filter8013-4);enable-background:accumulate" /> + style="opacity:0.50000000000000000;color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;filter:url(#filter8013-4);enable-background:accumulate" /> + transform="translate(25,-3.0625001e-6)" /> - - - - - - - - - - - - - - - - fix misalignment - - - - - RESET diff --git a/js/id/svg/lines.js b/js/id/svg/lines.js index 83c533255..624237d93 100644 --- a/js/id/svg/lines.js +++ b/js/id/svg/lines.js @@ -1,6 +1,6 @@ iD.svg.Lines = function(projection) { - var arrowtext = '►\u3000\u3000', + var arrowtext = '►\u3000\u3000\u3000', alength; var highway_stack = { diff --git a/js/id/svg/midpoints.js b/js/id/svg/midpoints.js index 935f84719..c721f191c 100644 --- a/js/id/svg/midpoints.js +++ b/js/id/svg/midpoints.js @@ -38,7 +38,7 @@ iD.svg.Midpoints = function(projection) { .attr('class', 'midpoint'); group.append('circle') - .attr('r', 7) + .attr('r', 8) .attr('class', 'shadow'); group.append('circle') diff --git a/js/id/svg/vertices.js b/js/id/svg/vertices.js index ba28c968a..f7090d892 100644 --- a/js/id/svg/vertices.js +++ b/js/id/svg/vertices.js @@ -26,7 +26,7 @@ iD.svg.Vertices = function(projection) { .attr('class', 'shadow'); group.append('circle') - .attr('r', 6) + .attr('r', 4) .attr('class', 'stroke'); group.append('circle') From 4d0a42344db8009ed29bd360c75e53ac41381d7b Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Mon, 4 Feb 2013 19:47:25 -0500 Subject: [PATCH 168/415] Start dynamic layers work in branch --- data/imagery.json | 45 ++++++++++++++++++++--------- data/imagery_convert.js | 37 +++++++++++++++++++++++- index.html | 7 +++-- js/id/id.js | 5 +++- js/id/renderer/background_source.js | 29 +++++++------------ js/id/renderer/layers.js | 1 + js/id/ui/layerswitcher.js | 38 ++++++++---------------- 7 files changed, 100 insertions(+), 62 deletions(-) create mode 100644 js/id/renderer/layers.js diff --git a/data/imagery.json b/data/imagery.json index 2055c1cf5..44d62cb97 100644 --- a/data/imagery.json +++ b/data/imagery.json @@ -2,6 +2,12 @@ { "name": "Bing aerial imagery", "url": "http://ecn.t0.tiles.virtualearth.net/tiles/a{q}uadkey.jpeg?g=587&mkt=en-gb&n=z", + "description": "Satellite imagery.", + "scaleExtent": [ + 0, + 20 + ], + "default": "yes", "sourcetag": "Bing", "logo": "bing_maps.png", "logo_url": "http://www.bing.com/maps", @@ -10,6 +16,11 @@ { "name": "MapBox Satellite", "url": "http://{t}.tiles.mapbox.com/v3/openstreetmap.map-4wvf9l0l/{z}/{x}/{y}.png", + "description": "Satellite and aerial imagery", + "scaleExtent": [ + 0, + 16 + ], "subdomains": [ "a", "b", @@ -17,26 +28,19 @@ ] }, { - "name": "MapQuest Open Aerial", - "url": "http://oatile1.mqcdn.com/tiles/1.0.0/sat/{z}/{x}/{y}.jpg" - }, - { - "name": "OSM - Mapnik", + "name": "OpenStreetMap", "url": "http://{t}.tile.openstreetmap.org/{z}/{x}/{y}.png", + "description": "The default OpenStreetMap layer.", + "scaleExtent": [ + 0, + 18 + ], "subdomains": [ "a", "b", "c" ] }, - { - "name": "OSM - OpenCycleMap", - "url": "http://tile.opencyclemap.org/cycle/{z}/{x}/{y}.png" - }, - { - "name": "OSM - MapQuest", - "url": "http://otile1.mqcdn.com/tiles/1.0.0/osm/{z}/{x}/{y}.jpg" - }, { "name": "OSM - Tiger Edited Map", "url": "http://tiger-osm.mapquest.com/tiles/1.0.0/tiger/{z}/{x}/{y}.png", @@ -70,6 +74,11 @@ { "name": "OSM US TIGER 2012 Roads Overlay", "url": "http://{t}.tile.openstreetmap.us/tiger2012_roads_expanded/{z}/{x}/{y}.png", + "description": "Public domain road data from the US Government.", + "scaleExtent": [ + 0, + 17 + ], "subdomains": [ "a", "b", @@ -85,6 +94,11 @@ { "name": "OSM US TIGER 2012 Roads Overlay", "url": "http://{t}.tile.openstreetmap.us/tiger2012_roads_expanded/{z}/{x}/{y}.png", + "description": "Public domain road data from the US Government.", + "scaleExtent": [ + 0, + 17 + ], "subdomains": [ "a", "b", @@ -100,6 +114,11 @@ { "name": "OSM US TIGER 2012 Roads Overlay", "url": "http://{t}.tile.openstreetmap.us/tiger2012_roads_expanded/{z}/{x}/{y}.png", + "description": "Public domain road data from the US Government.", + "scaleExtent": [ + 0, + 17 + ], "subdomains": [ "a", "b", diff --git a/data/imagery_convert.js b/data/imagery_convert.js index 16cc23317..a7253aee4 100644 --- a/data/imagery_convert.js +++ b/data/imagery_convert.js @@ -5,6 +5,32 @@ $ = cheerio.load(fs.readFileSync('imagery.xml')); var imagery = []; +// CENSORSHIP! No, these are just layers that essentially duplicate other layers +// or which have no clear use case. +var censor = { + 'MapQuest Open Aerial': true, + 'OSM - OpenCycleMap': true, + 'OSM - MapQuest': true +}; + +var replace = { + 'OSM - Mapnik': 'OpenStreetMap' +}; + +var description = { + 'MapBox Satellite': 'Satellite and aerial imagery', + 'OpenStreetMap': 'The default OpenStreetMap layer.', + 'OSM US TIGER 2012 Roads Overlay': 'Public domain road data from the US Government.', + 'Bing aerial imagery': 'Satellite imagery.' +}; + +var scaleExtent = { + 'MapBox Satellite': [0, 16], + 'OpenStreetMap': [0, 18], + 'OSM US TIGER 2012 Roads Overlay': [0, 17], + 'Bing aerial imagery': [0, 20] +}; + $('set').each(function(i) { var elem = $(this); @@ -13,6 +39,14 @@ $('set').each(function(i) { url: $(this).find('url').first().text() }; + if (censor[im.name]) return; + + if (replace[im.name]) im.name = replace[im.name]; + + if (description[im.name]) im.description = description[im.name]; + + if (scaleExtent[im.name]) im.scaleExtent = scaleExtent[im.name]; + var subdomains = []; im.url = im.url @@ -34,7 +68,7 @@ $('set').each(function(i) { +elem.attr('maxlon')]; } - ['sourcetag', 'logo', 'logo_url', 'terms_url'].forEach(function(a) { + ['default', 'sourcetag', 'logo', 'logo_url', 'terms_url'].forEach(function(a) { if (elem.find(a).length) { im[a] = elem.find(a).first().text(); } @@ -43,3 +77,4 @@ $('set').each(function(i) { }); fs.writeFileSync('imagery.json', JSON.stringify(imagery, null, 4)); +fs.writeFileSync('imagery.js', 'iD.data.imagery = ' + JSON.stringify(imagery, null, 4) + ';'); diff --git a/index.html b/index.html index c03ac29ae..3be283a1f 100644 --- a/index.html +++ b/index.html @@ -32,12 +32,17 @@ + + + + + @@ -135,8 +140,6 @@ - -
+ diff --git a/js/id/actions/connect.js b/js/id/actions/connect.js new file mode 100644 index 000000000..88bf35932 --- /dev/null +++ b/js/id/actions/connect.js @@ -0,0 +1,54 @@ +// Connect the ways at the given nodes. +// +// The last node will survive. All other nodes will be replaced with +// the surviving node in parent ways, and then removed. +// +// Tags and relation memberships of of non-surviving nodes are merged +// to the survivor. +// +// This is the inverse of `iD.actions.Disconnect`. +// +// Reference: +// https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/MergeNodesAction.as +// https://github.com/openstreetmap/josm/blob/mirror/src/org/openstreetmap/josm/actions/MergeNodesAction.java +// +iD.actions.Connect = function(nodeIds) { + var action = function(graph) { + var survivor = graph.entity(_.last(nodeIds)); + + for (var i = 0; i < nodeIds.length - 1; i++) { + var node = graph.entity(nodeIds[i]), index; + + graph.parentWays(node).forEach(function (parent) { + while (true) { + index = parent.nodes.indexOf(node.id); + if (index < 0) + break; + parent = parent.updateNode(survivor.id, index); + } + graph = graph.replace(parent); + }); + + graph.parentRelations(node).forEach(function (parent) { + var memberA = parent.memberById(survivor.id), + memberB = parent.memberById(node.id); + if (!memberA) { + graph = graph.replace(parent.addMember({id: survivor.id, role: memberB.role, type: 'node'})); + } + }); + + survivor = survivor.mergeTags(node.tags); + graph = iD.actions.DeleteNode(node.id)(graph); + } + + graph = graph.replace(survivor); + + return graph; + }; + + action.enabled = function(graph) { + return nodeIds.length > 1; + }; + + return action; +}; diff --git a/js/id/actions/disconnect.js b/js/id/actions/disconnect.js index 8645c436b..415730e77 100644 --- a/js/id/actions/disconnect.js +++ b/js/id/actions/disconnect.js @@ -4,6 +4,8 @@ // Normally, this will be undefined and the way will automatically // be assigned a new ID. // +// This is the inverse of `iD.actions.Connect`. +// // Reference: // https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/UnjoinNodeAction.as // https://github.com/openstreetmap/josm/blob/mirror/src/org/openstreetmap/josm/actions/UnGlueAction.java diff --git a/js/id/behavior/drag_node.js b/js/id/behavior/drag_node.js index a0cb06e75..92318756c 100644 --- a/js/id/behavior/drag_node.js +++ b/js/id/behavior/drag_node.js @@ -17,39 +17,96 @@ iD.behavior.DragNode = function(context) { }, 50); } - function stopNudge(nudge) { + function stopNudge() { if (nudgeInterval) window.clearInterval(nudgeInterval); nudgeInterval = null; } - function annotation(entity) { + function moveAnnotation(entity) { return t('operations.move.annotation.' + entity.geometry(context.graph())); } - return iD.behavior.drag() - .delegate(".node") - .origin(function(entity) { - return context.projection(entity.loc); - }) - .on('start', function() { - context.perform( - iD.actions.Noop()); - }) - .on('move', function(entity) { - d3.event.sourceEvent.stopPropagation(); + function connectAnnotation(datum) { + return t('operations.connect.annotation.' + datum.geometry(context.graph())); + } - var nudge = edge(d3.event.point, context.map().size()); - if (nudge) startNudge(nudge); - else stopNudge(); + function origin(entity) { + return context.projection(entity.loc); + } + function start(entity) { + var activeIDs = _.pluck(context.graph().parentWays(entity), 'id'); + activeIDs.push(entity.id); + + context.surface() + .classed('behavior-drag-node', true) + .selectAll('.node, .way') + .filter(function (d) { return activeIDs.indexOf(d.id) >= 0; }) + .classed('active', true); + + context.perform( + iD.actions.Noop()); + } + + function datum() { + if (d3.event.sourceEvent.altKey) { + return {}; + } else { + return d3.event.sourceEvent.target.__data__ || {}; + } + } + + function move(entity) { + d3.event.sourceEvent.stopPropagation(); + + var nudge = edge(d3.event.point, context.map().size()); + if (nudge) startNudge(nudge); + else stopNudge(); + + var loc = context.map().mouseCoordinates(); + + var d = datum(); + if (d.type === 'node') { + loc = d.loc; + } else if (d.type === 'way') { + loc = iD.geo.chooseIndex(d, d3.mouse(context.surface().node()), context).loc; + } + + context.replace(iD.actions.MoveNode(entity.id, loc)); + } + + function end(entity) { + context.surface() + .classed('behavior-drag-node', false) + .selectAll('.active') + .classed('active', false); + + stopNudge(); + + var d = datum(); + if (d.type === 'way') { + var choice = iD.geo.chooseIndex(d, d3.mouse(context.surface().node()), context); context.replace( - iD.actions.MoveNode(entity.id, context.projection.invert(d3.event.point)), - annotation(entity)); - }) - .on('end', function(entity) { - stopNudge(); + iD.actions.MoveNode(entity.id, choice.loc), + iD.actions.AddVertex(d.id, entity.id, choice.index), + connectAnnotation(d)); + + } else if (d.type === 'node' && d.id !== entity.id) { + context.replace( + iD.actions.Connect([entity.id, d.id]), + connectAnnotation(d)); + + } else { context.replace( iD.actions.Noop(), - annotation(entity)); - }); + moveAnnotation(entity)); + } + } + + return iD.behavior.drag() + .delegate("g.node") + .origin(origin) + .on('start', start) + .on('move', move) + .on('end', end); }; diff --git a/js/id/svg/vertices.js b/js/id/svg/vertices.js index f7090d892..e9769a374 100644 --- a/js/id/svg/vertices.js +++ b/js/id/svg/vertices.js @@ -23,15 +23,15 @@ iD.svg.Vertices = function(projection) { group.append('circle') .attr('r', 10) - .attr('class', 'shadow'); + .attr('class', 'node vertex shadow'); group.append('circle') .attr('r', 4) - .attr('class', 'stroke'); + .attr('class', 'node vertex stroke'); group.append('circle') .attr('r', 3) - .attr('class', 'fill'); + .attr('class', 'node vertex fill'); groups.attr('transform', iD.svg.PointTransform(projection)) .call(iD.svg.TagClasses()) diff --git a/locale/en.js b/locale/en.js index 8f00590c6..516a43475 100644 --- a/locale/en.js +++ b/locale/en.js @@ -86,6 +86,14 @@ locale.en = { multiple: "Deleted {n} objects." } }, + connect: { + annotation: { + point: "Connected a way to a point.", + vertex: "Connected a way to another.", + line: "Connected a way to a line.", + area: "Connected a way to an area." + } + }, disconnect: { title: "Disconnect", description: "Disconnect these ways from each other.", diff --git a/test/index.html b/test/index.html index e6df6a963..5f7c19b5a 100644 --- a/test/index.html +++ b/test/index.html @@ -72,6 +72,7 @@ + @@ -151,6 +152,7 @@ + diff --git a/test/index_packaged.html b/test/index_packaged.html index ef91e2823..d21d48ff2 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -35,6 +35,7 @@ + diff --git a/test/spec/actions/connect.js b/test/spec/actions/connect.js new file mode 100644 index 000000000..3838d83c2 --- /dev/null +++ b/test/spec/actions/connect.js @@ -0,0 +1,110 @@ +describe("iD.actions.Connect", function() { + describe("#enabled", function () { + it("returns true for two or more nodes", function () { + expect(iD.actions.Connect(['a', 'b']).enabled()).to.be.true; + }); + + it("returns false for less than two nodes", function () { + expect(iD.actions.Connect(['a']).enabled()).to.be.false; + }); + }); + + it("removes all but the final node", function() { + var graph = iD.Graph({ + 'a': iD.Node({id: 'a'}), + 'b': iD.Node({id: 'b'}), + 'c': iD.Node({id: 'c'}) + }); + + graph = iD.actions.Connect(['a', 'b', 'c'])(graph); + + expect(graph.entity('a')).to.be.undefined; + expect(graph.entity('b')).to.be.undefined; + expect(graph.entity('c')).not.to.be.undefined; + }); + + it("replaces non-surviving nodes in parent ways", function() { + // a --- b --- c + // + // e + // | + // d + // + // Connect [e, b]. + // + // Expected result: + // + // a --- b --- c + // | + // d + // + var graph = iD.Graph({ + 'a': iD.Node({id: 'a'}), + 'b': iD.Node({id: 'b'}), + 'c': iD.Node({id: 'c'}), + 'd': iD.Node({id: 'd'}), + 'e': iD.Node({id: 'e'}), + '-': iD.Way({id: '-', nodes: ['a', 'b', 'c']}), + '|': iD.Way({id: '|', nodes: ['d', 'e']}) + }); + + graph = iD.actions.Connect(['e', 'b'])(graph); + + expect(graph.entity('-').nodes).to.eql(['a', 'b', 'c']); + expect(graph.entity('|').nodes).to.eql(['d', 'b']); + }); + + it("handles circular ways", function() { + // c -- a d === e + // | / + // | / + // | / + // b + // + // Connect [a, d]. + // + var graph = iD.Graph({ + 'a': iD.Node({id: 'a'}), + 'b': iD.Node({id: 'b'}), + 'c': iD.Node({id: 'c'}), + 'd': iD.Node({id: 'd'}), + 'e': iD.Node({id: 'e'}), + '-': iD.Way({id: '-', nodes: ['a', 'b', 'c', 'a']}), + '=': iD.Way({id: '=', nodes: ['d', 'e']}) + }); + + graph = iD.actions.Connect(['a', 'd'])(graph); + + expect(graph.entity('-').nodes).to.eql(['d', 'b', 'c', 'd']); + }); + + it("merges tags to the surviving node", function() { + var graph = iD.Graph({ + 'a': iD.Node({id: 'a', tags: {a: 'a'}}), + 'b': iD.Node({id: 'b', tags: {b: 'b'}}), + 'c': iD.Node({id: 'c', tags: {c: 'c'}}) + }); + + graph = iD.actions.Connect(['a', 'b', 'c'])(graph); + + expect(graph.entity('c').tags).to.eql({a: 'a', b: 'b', c: 'c'}); + }); + + it("merges memberships to the surviving node", function() { + var graph = iD.Graph({ + 'a': iD.Node({id: 'a'}), + 'b': iD.Node({id: 'b'}), + 'c': iD.Node({id: 'c'}), + 'd': iD.Node({id: 'c'}), + '-': iD.Way({id: '-', nodes: ['a', 'b']}), + '=': iD.Way({id: '=', nodes: ['c', 'd']}), + 'r1': iD.Relation({id: 'r1', members: [{id: 'b', role: 'r1', type: 'node'}]}), + 'r2': iD.Relation({id: 'r2', members: [{id: 'b', role: 'r1', type: 'node'}, {id: 'c', role: 'r2', type: 'node'}]}) + }); + + graph = iD.actions.Connect(['b', 'c'])(graph); + + expect(graph.entity('r1').members).to.eql([{id: 'c', role: 'r1', type: 'node'}]); + expect(graph.entity('r2').members).to.eql([{id: 'c', role: 'r2', type: 'node'}]); + }); +}); From 4fed3e5daca0bff016ca1861e33719ad61d426e8 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Mon, 4 Feb 2013 16:55:46 -0800 Subject: [PATCH 171/415] Fix rogue s/red/#f6634f/g --- css/map.css | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/css/map.css b/css/map.css index 66d1e4496..749aeb7b3 100644 --- a/css/map.css +++ b/css/map.css @@ -62,41 +62,41 @@ svg[data-zoom="17"] g.vertex .fill { transform:scale(0.7, 0.7); } -g.vertex.sha#f6634f .shadow { +g.vertex.shared .shadow { -webkit-transform:scale(1.2, 1.2); -moz-transform:scale(1.2, 1.2); transform:scale(1.2, 1.2); } -g.vertex.sha#f6634f .fill, -g.vertex.sha#f6634f .stroke { +g.vertex.shared .fill, +g.vertex.shared .stroke { -webkit-transform:scale(1.1, 1.1); -moz-transform:scale(1.1, 1.1); transform:scale(1.1, 1.1); } -svg[data-zoom="16"] g.vertex.sha#f6634f .shadow { +svg[data-zoom="16"] g.vertex.shared .shadow { -webkit-transform:scale(0.9, 0.9); -moz-transform:scale(0.9, 0.9); transform:scale(0.9, 0.9); } -svg[data-zoom="16"] g.vertex.sha#f6634f .fill, -svg[data-zoom="16"] g.vertex.sha#f6634f .stroke { +svg[data-zoom="16"] g.vertex.shared .fill, +svg[data-zoom="16"] g.vertex.shared .stroke { -webkit-transform:scale(0.8, 0.8); -moz-transform:scale(0.8, 0.8); transform:scale(0.8, 0.8); } -svg[data-zoom="17"] g.vertex.sha#f6634f .shadow { +svg[data-zoom="17"] g.vertex.shared .shadow { -webkit-transform:scale(1, 1); -moz-transform:scale(1, 1); transform:scale(1, 1); } -svg[data-zoom="17"] g.vertex.sha#f6634f .fill, -svg[data-zoom="17"] g.vertex.sha#f6634f .stroke { +svg[data-zoom="17"] g.vertex.shared .fill, +svg[data-zoom="17"] g.vertex.shared .stroke { -webkit-transform:scale(0.9, 0.9); -moz-transform:scale(0.9, 0.9); transform:scale(0.9, 0.9); } -g.vertex.sha#f6634f .fill { +g.vertex.shared .fill { fill:#aaa; } From 0bd864751ae1dd4756542d2869edd4ba4a05ee42 Mon Sep 17 00:00:00 2001 From: Saman Bemel-Benrud Date: Mon, 4 Feb 2013 19:58:28 -0500 Subject: [PATCH 172/415] new background icon. --- img/source/sprite.svg | 173 +++++------------------------------------- img/sprite.png | Bin 14084 -> 13761 bytes 2 files changed, 21 insertions(+), 152 deletions(-) diff --git a/img/source/sprite.svg b/img/source/sprite.svg index 64f682c3d..a8c29982b 100644 --- a/img/source/sprite.svg +++ b/img/source/sprite.svg @@ -7,7 +7,6 @@ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" - xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="420" @@ -39,12 +38,12 @@ borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" - inkscape:zoom="0.5" - inkscape:cx="279.87773" - inkscape:cy="136.54467" + inkscape:zoom="1" + inkscape:cx="304.46947" + inkscape:cy="116.03146" inkscape:document-units="px" - inkscape:current-layer="layer1" - showgrid="true" + inkscape:current-layer="layer12" + showgrid="false" inkscape:window-width="1280" inkscape:window-height="700" inkscape:window-x="48" @@ -254,137 +253,6 @@ id="g11041" style="fill:#ffffff;fill-opacity:1;display:inline" />
- - - - - - - - - - - - fix misalignment - - - - - RESET - -
- + + + +
diff --git a/img/sprite.png b/img/sprite.png index f14b74ac9f6a58c5728374dcb880a75fe7e9cb49..02aadf2a38265d2b23b22afe2961255769ef2a05 100644 GIT binary patch literal 13761 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%0Dt<^zknM6K!s$2` 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 E0T3gT6aWAK literal 14084 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 Date: Tue, 5 Feb 2013 00:41:07 -0500 Subject: [PATCH 173/415] Fix drawing, leaking globals --- js/id/modes/add_area.js | 2 +- js/id/modes/add_line.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/js/id/modes/add_area.js b/js/id/modes/add_area.js index ae83423cb..4cb98a3bc 100644 --- a/js/id/modes/add_area.js +++ b/js/id/modes/add_area.js @@ -10,7 +10,7 @@ iD.modes.AddArea = function(context) { var behavior = iD.behavior.AddWay(context) .on('start', start) .on('startFromWay', startFromWay) - .on('startFromNode', startFromNode) + .on('startFromNode', startFromNode), defaultTags = {area: 'yes'}; function start(loc) { diff --git a/js/id/modes/add_line.js b/js/id/modes/add_line.js index 3d45df2a0..76e31e83b 100644 --- a/js/id/modes/add_line.js +++ b/js/id/modes/add_line.js @@ -10,7 +10,7 @@ iD.modes.AddLine = function(context) { var behavior = iD.behavior.AddWay(context) .on('start', start) .on('startFromWay', startFromWay) - .on('startFromNode', startFromNode) + .on('startFromNode', startFromNode), defaultTags = {highway: 'residential'}; function start(loc) { From d3d08515969e846a2777415e15b2c9b48e76930d Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Tue, 5 Feb 2013 00:51:54 -0500 Subject: [PATCH 174/415] Fix svg/midpoint tests Proper solution waiting on 369 --- test/spec/svg/midpoints.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/spec/svg/midpoints.js b/test/spec/svg/midpoints.js index c34ac0350..e00f6f78b 100644 --- a/test/spec/svg/midpoints.js +++ b/test/spec/svg/midpoints.js @@ -14,6 +14,8 @@ describe("iD.svg.Midpoints", function () { line = iD.Way({nodes: [a.id, b.id]}), graph = iD.Graph([a, b, line]); + // If no vertices are drawn, no midpoints are drawn. This dependence needs to be removed + surface.call(iD.svg.Vertices(projection), graph, [a], filter); surface.call(iD.svg.Midpoints(projection), graph, [line], filter); expect(surface.select('.midpoint').datum().loc).to.eql([25, 0]); @@ -42,6 +44,8 @@ describe("iD.svg.Midpoints", function () { graph = iD.Graph([a, b, c, d, l1, l2, l3, l4]), ab = function (d) { return d.id === [a.id, b.id].sort().join("-"); }; + // If no vertices are drawn, no midpoints are drawn. This dependence needs to be removed + surface.call(iD.svg.Vertices(projection), graph, [a], filter); surface.call(iD.svg.Midpoints(projection), graph, [l1, l2, l3, l4], filter); expect(surface.selectAll('.midpoint').filter(ab).datum().ways).to.eql([ From 4cbdadfbbc6d25a26d17d5e6db4f207b6906bb21 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 5 Feb 2013 06:56:23 -0800 Subject: [PATCH 175/415] Fix undo of multipolygon deletion --- locale/en.js | 1 + 1 file changed, 1 insertion(+) diff --git a/locale/en.js b/locale/en.js index 516a43475..b92dc75ee 100644 --- a/locale/en.js +++ b/locale/en.js @@ -83,6 +83,7 @@ locale.en = { vertex: "Deleted a node from a way.", line: "Deleted a line.", area: "Deleted an area.", + relation: "Deleted a relation.", multiple: "Deleted {n} objects." } }, From c37adc617fc8873313e805fafe9e7ba59e323d12 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 5 Feb 2013 07:30:58 -0800 Subject: [PATCH 176/415] Fix build --- Makefile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 3b0f2d1b7..9fd5afb0e 100644 --- a/Makefile +++ b/Makefile @@ -50,7 +50,9 @@ all: \ js/id/validate.js \ js/id/end.js \ locale/locale.js \ - locale/en.js + locale/en.js \ + data/data.js \ + data/deprecated.js iD.js: Makefile @rm -f $@ From 3576a99eb5d4354160591359323165ea8255bde0 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Tue, 5 Feb 2013 11:00:13 -0500 Subject: [PATCH 177/415] Always have delete as first op --- js/id/modes/select.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/js/id/modes/select.js b/js/id/modes/select.js index ec6017ae3..126e01029 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -38,9 +38,10 @@ iD.modes.Select = function(context, selection, initial) { context.install(behavior); }); - var operations = d3.values(iD.operations) + var operations = _.without(d3.values(iD.operations), iD.operations.Delete) .map(function(o) { return o(selection, context); }) .filter(function(o) { return o.available(); }); + operations.unshift(iD.operations.Delete(selection, context)); operations.forEach(function(operation) { keybinding.on(operation.key, function() { From db7f42145e9ddbeaac936c872ee4ff45155100c7 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Tue, 5 Feb 2013 12:00:10 -0500 Subject: [PATCH 178/415] Continue removing any non-scoped selectors. Refs #595 Last hits will be combobox and layerswitcher. --- index.html | 8 +++--- js/id/modes/select.js | 2 +- js/id/oauth.js | 4 +-- js/id/ui.js | 2 +- js/id/ui/confirm.js | 4 +-- js/id/ui/flash.js | 4 +-- js/id/ui/geocoder.js | 10 +++++-- js/id/ui/inspector.js | 20 +++++++------- js/id/ui/loading.js | 9 ++++--- js/id/ui/modal.js | 9 ++++--- js/id/ui/save.js | 61 +++++++++++++++++++++++-------------------- 11 files changed, 73 insertions(+), 60 deletions(-) diff --git a/index.html b/index.html index b0a1cea53..afcb8e6be 100644 --- a/index.html +++ b/index.html @@ -9,8 +9,8 @@ - - + + @@ -140,7 +140,7 @@ -
- + + + + + @@ -132,8 +137,6 @@ - - - diff --git a/js/id/graph/relation.js b/js/id/graph/relation.js index a404d1981..f8540db1f 100644 --- a/js/id/graph/relation.js +++ b/js/id/graph/relation.js @@ -26,7 +26,7 @@ _.extend(iD.Relation.prototype, { }, geometry: function() { - return 'relation'; + return this.isMultipolygon() ? 'area' : 'relation'; }, // Return the first member with the given role. A copy of the member object diff --git a/js/id/graph/way.js b/js/id/graph/way.js index 1f7c46065..1830e61a4 100644 --- a/js/id/graph/way.js +++ b/js/id/graph/way.js @@ -49,6 +49,7 @@ _.extend(iD.Way.prototype, { isArea: function() { return this.tags.area === 'yes' || (this.isClosed() && + !_.isEmpty(this.tags) && this.tags.area !== 'no' && !this.tags.highway && !this.tags.barrier); diff --git a/js/id/modes/select.js b/js/id/modes/select.js index 3b0ad82dd..532faaca8 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -143,13 +143,22 @@ iD.modes.Select = function(context, selection, initial) { } } + function selected(entity) { + if (!entity) return false; + if (selection.indexOf(entity.id) >= 0) return true; + return d3.select(this).classed('stroke') && + _.any(context.graph().parentRelations(entity), function(parent) { + return selection.indexOf(parent.id) >= 0; + }); + } + d3.select(document) .call(keybinding); context.surface() .on('dblclick.select', dblclick) .selectAll("*") - .filter(function(d) { return d && selection.indexOf(d.id) >= 0; }) + .filter(selected) .classed('selected', true); radialMenu = iD.ui.RadialMenu(operations); diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index 19354d7f3..5074abd92 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -19,7 +19,6 @@ iD.Map = function(context) { vertices = iD.svg.Vertices(roundedProjection), lines = iD.svg.Lines(roundedProjection), areas = iD.svg.Areas(roundedProjection), - multipolygons = iD.svg.Multipolygons(roundedProjection), midpoints = iD.svg.Midpoints(roundedProjection), labels = iD.svg.Labels(roundedProjection), tail = d3.tail(), @@ -87,7 +86,6 @@ iD.Map = function(context) { .call(vertices, graph, all, filter) .call(lines, graph, all, filter) .call(areas, graph, all, filter) - .call(multipolygons, graph, all, filter) .call(midpoints, graph, all, filter) .call(labels, graph, all, filter, dimensions, !difference); } diff --git a/js/id/svg.js b/js/id/svg.js index aadb6b830..3b662b2e7 100644 --- a/js/id/svg.js +++ b/js/id/svg.js @@ -27,5 +27,17 @@ iD.svg = { return projection(n.loc); }).join('L')); }; + }, + + MultipolygonMemberTags: function (graph) { + return function (entity) { + var tags = entity.tags; + graph.parentRelations(entity).forEach(function (relation) { + if (relation.isMultipolygon()) { + tags = _.extend({}, relation.tags, tags); + } + }); + return tags; + } } }; diff --git a/js/id/svg/areas.js b/js/id/svg/areas.js index 991b5644c..cd9525d4d 100644 --- a/js/id/svg/areas.js +++ b/js/id/svg/areas.js @@ -1,38 +1,39 @@ iD.svg.Areas = function(projection) { return function drawAreas(surface, graph, entities, filter) { - var areas = []; + var path = d3.geo.path().projection(projection), + areas = []; for (var i = 0; i < entities.length; i++) { var entity = entities[i]; if (entity.geometry(graph) === 'area') { - var points = graph.childNodes(entity).map(function(n) { - return projection(n.loc); - }); - areas.push({ entity: entity, - area: entity.isDegenerate() ? 0 : Math.abs(d3.geom.polygon(points).area()) + area: Math.abs(path.area(entity.asGeoJSON(graph))) }); } } areas.sort(function(a, b) { return b.area - a.area; }); - var lineString = iD.svg.LineString(projection, graph); + function drawPaths(group, areas, filter, klass) { + var tagClasses = iD.svg.TagClasses(); + + if (klass === 'stroke') { + tagClasses.tags(iD.svg.MultipolygonMemberTags(graph)); + } - function drawPaths(group, areas, filter, classes) { var paths = group.selectAll('path.area') .filter(filter) .data(areas, iD.Entity.key); paths.enter() .append('path') - .attr('class', classes); + .attr('class', function (d) { return d.type + ' area ' + klass; }); paths .order() - .attr('d', lineString) - .call(iD.svg.TagClasses()) + .attr('d', function (entity) { return path(entity.asGeoJSON(graph)); }) + .call(tagClasses) .call(iD.svg.MemberClasses(graph)); paths.exit() @@ -43,9 +44,14 @@ iD.svg.Areas = function(projection) { areas = _.pluck(areas, 'entity'); + var strokes = areas.filter(function (area) { + return area.type === 'way'; + }); + var fill = surface.select('.layer-fill'), - stroke = surface.select('.layer-stroke'), - fills = drawPaths(fill, areas, filter, 'way area fill'), - strokes = drawPaths(stroke, areas, filter, 'way area stroke'); + stroke = surface.select('.layer-stroke'); + + drawPaths(fill, areas, filter, 'fill'); + drawPaths(stroke, strokes, filter, 'stroke'); }; }; diff --git a/js/id/svg/labels.js b/js/id/svg/labels.js index 9b2cbd1e9..31d2f03d5 100644 --- a/js/id/svg/labels.js +++ b/js/id/svg/labels.js @@ -335,9 +335,8 @@ iD.svg.Labels = function(projection) { } function getAreaLabel(entity, width, height) { - var nodes = _.pluck(graph.childNodes(entity), 'loc') - .map(iD.svg.RoundProjection(projection)), - centroid = d3.geom.polygon(nodes).centroid(), + var path = d3.geo.path().projection(projection), + centroid = path.centroid(entity.asGeoJSON(graph)), extent = entity.extent(graph), entitywidth = projection(extent[1])[0] - projection(extent[0])[0]; diff --git a/js/id/svg/lines.js b/js/id/svg/lines.js index a1151204b..2fd7b9152 100644 --- a/js/id/svg/lines.js +++ b/js/id/svg/lines.js @@ -34,19 +34,25 @@ iD.svg.Lines = function(projection) { } return function drawLines(surface, graph, entities, filter) { - function drawPaths(group, lines, filter, classes, lineString) { + function drawPaths(group, lines, filter, klass, lineString) { + var tagClasses = iD.svg.TagClasses(); + + if (klass === 'stroke') { + tagClasses.tags(iD.svg.MultipolygonMemberTags(graph)); + } + var paths = group.selectAll('path.line') .filter(filter) .data(lines, iD.Entity.key); paths.enter() .append('path') - .attr('class', classes); + .attr('class', 'way line ' + klass); paths .order() .attr('d', lineString) - .call(iD.svg.TagClasses()) + .call(tagClasses) .call(iD.svg.MemberClasses(graph)); paths.exit() @@ -65,8 +71,7 @@ iD.svg.Lines = function(projection) { container.remove(); } - var lines = [], - lineStrings = {}; + var lines = []; for (var i = 0; i < entities.length; i++) { var entity = entities[i]; @@ -84,9 +89,9 @@ iD.svg.Lines = function(projection) { stroke = surface.select('.layer-stroke'), defs = surface.select('defs'), text = surface.select('.layer-text'), - shadows = drawPaths(shadow, lines, filter, 'way line shadow', lineString), - casings = drawPaths(casing, lines, filter, 'way line casing', lineString), - strokes = drawPaths(stroke, lines, filter, 'way line stroke', lineString); + shadows = drawPaths(shadow, lines, filter, 'shadow', lineString), + casings = drawPaths(casing, lines, filter, 'casing', lineString), + strokes = drawPaths(stroke, lines, filter, 'stroke', lineString); // Determine the lengths of oneway paths var lengths = {}, diff --git a/js/id/svg/multipolygons.js b/js/id/svg/multipolygons.js deleted file mode 100644 index 092f65805..000000000 --- a/js/id/svg/multipolygons.js +++ /dev/null @@ -1,55 +0,0 @@ -iD.svg.Multipolygons = function(projection) { - return function(surface, graph, entities, filter) { - var multipolygons = []; - - for (var i = 0; i < entities.length; i++) { - var entity = entities[i]; - if (entity.geometry(graph) === 'relation' && entity.tags.type === 'multipolygon') { - multipolygons.push(entity); - } - } - - var lineStrings = {}; - - function lineString(entity) { - if (lineStrings[entity.id] !== undefined) { - return lineStrings[entity.id]; - } - - var multipolygon = entity.multipolygon(graph); - if (entity.members.length === 0 || !multipolygon) { - return (lineStrings[entity.id] = null); - } - - multipolygon = _.flatten(multipolygon, true); - return (lineStrings[entity.id] = - multipolygon.map(function (ring) { - return 'M' + ring.map(projection).join('L'); - }).join("")); - } - - function drawPaths(group, multipolygons, filter, classes) { - var paths = group.selectAll('path.multipolygon') - .filter(filter) - .data(multipolygons, iD.Entity.key); - - paths.enter() - .append('path') - .attr('class', classes); - - paths - .order() - .attr('d', lineString) - .call(iD.svg.TagClasses()) - .call(iD.svg.MemberClasses(graph)); - - paths.exit() - .remove(); - - return paths; - } - - var fill = surface.select('.layer-fill'), - paths = drawPaths(fill, multipolygons, filter, 'relation multipolygon'); - }; -}; diff --git a/js/id/svg/tag_classes.js b/js/id/svg/tag_classes.js index 5ce585add..dad388ffe 100644 --- a/js/id/svg/tag_classes.js +++ b/js/id/svg/tag_classes.js @@ -3,10 +3,11 @@ iD.svg.TagClasses = function() { 'highway', 'railway', 'waterway', 'power', 'motorway', 'amenity', 'natural', 'landuse', 'building', 'oneway', 'bridge', 'boundary', 'leisure', 'construction' - ]), tagClassRe = /^tag-/; + ]), tagClassRe = /^tag-/, + tags = function(entity) { return entity.tags; }; - return function tagClassesSelection(selection) { - selection.each(function tagClassesEach(d, i) { + var tagClasses = function(selection) { + selection.each(function tagClassesEach(entity) { var classes, value = this.className; if (value.baseVal !== undefined) value = value.baseVal; @@ -15,11 +16,10 @@ iD.svg.TagClasses = function() { return name.length && !tagClassRe.test(name); }).join(' '); - var tags = d.tags; - for (var k in tags) { + var t = tags(entity); + for (var k in t) { if (!keys[k]) continue; - classes += ' tag-' + k + ' ' + - 'tag-' + k + '-' + tags[k]; + classes += ' tag-' + k + ' ' + 'tag-' + k + '-' + t[k]; } classes = classes.trim(); @@ -29,4 +29,12 @@ iD.svg.TagClasses = function() { } }); }; + + tagClasses.tags = function(_) { + if (!arguments.length) return tags; + tags = _; + return tagClasses; + }; + + return tagClasses; }; diff --git a/test/index.html b/test/index.html index 5f7c19b5a..522f26092 100644 --- a/test/index.html +++ b/test/index.html @@ -47,7 +47,6 @@ - @@ -184,7 +183,6 @@ - diff --git a/test/index_packaged.html b/test/index_packaged.html index d21d48ff2..8dd73d666 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -66,7 +66,6 @@ - diff --git a/test/spec/graph/way.js b/test/spec/graph/way.js index 39393ab16..05864d5c3 100644 --- a/test/spec/graph/way.js +++ b/test/spec/graph/way.js @@ -95,8 +95,12 @@ describe('iD.Way', function() { expect(iD.Way({tags: { area: 'yes' }}).isArea()).to.equal(true); }); - it('returns true if the way is closed and has no tags', function() { - expect(iD.Way({nodes: ['n1', 'n1']}).isArea()).to.equal(true); + it('returns false if the way is closed and has no tags', function() { + expect(iD.Way({nodes: ['n1', 'n1']}).isArea()).to.equal(false); + }); + + it('returns true if the way is closed and has tags', function() { + expect(iD.Way({nodes: ['n1', 'n1'], tags: {a: 'b'}}).isArea()).to.equal(true); }); it('returns false if the way is closed and has tag area=no', function() { diff --git a/test/spec/svg/areas.js b/test/spec/svg/areas.js index 807a799f4..bbf8674ae 100644 --- a/test/spec/svg/areas.js +++ b/test/spec/svg/areas.js @@ -69,4 +69,46 @@ describe("iD.svg.Areas", function () { expect(surface.select('.area:nth-child(1)')).to.be.classed('tag-landuse-park'); expect(surface.select('.area:nth-child(2)')).to.be.classed('tag-building-yes'); }); + + it("renders fills for multipolygon areas", function () { + var a = iD.Node({loc: [1, 1]}), + b = iD.Node({loc: [2, 2]}), + c = iD.Node({loc: [3, 3]}), + w = iD.Way({nodes: [a.id, b.id, c.id, a.id]}), + r = iD.Relation({tags: {type: 'multipolygon'}, members: [{id: w.id, type: 'way'}]}), + graph = iD.Graph([a, b, c, w, r]), + areas = [w, r]; + + surface.call(iD.svg.Areas(projection), graph, areas, filter); + + expect(surface.select('.fill')).to.be.classed('relation'); + }); + + it("renders no strokes for multipolygon areas", function () { + var a = iD.Node({loc: [1, 1]}), + b = iD.Node({loc: [2, 2]}), + c = iD.Node({loc: [3, 3]}), + w = iD.Way({nodes: [a.id, b.id, c.id, a.id]}), + r = iD.Relation({tags: {type: 'multipolygon'}, members: [{id: w.id, type: 'way'}]}), + graph = iD.Graph([a, b, c, w, r]), + areas = [w, r]; + + surface.call(iD.svg.Areas(projection), graph, areas, filter); + + expect(surface.selectAll('.stroke')[0].length).to.equal(0); + }); + + it("adds stroke classes for the tags of the parent relation of multipolygon members", function() { + var a = iD.Node({loc: [1, 1]}), + b = iD.Node({loc: [2, 2]}), + c = iD.Node({loc: [3, 3]}), + w = iD.Way({tags: {area: 'yes'}, nodes: [a.id, b.id, c.id, a.id]}), + r = iD.Relation({members: [{id: w.id}], tags: {type: 'multipolygon', natural: 'wood'}}), + graph = iD.Graph([a, b, c, w, r]); + + surface.call(iD.svg.Areas(projection), graph, [w], filter); + + expect(surface.select('.stroke')).to.be.classed('tag-natural-wood'); + expect(surface.select('.fill')).not.to.be.classed('tag-natural-wood'); + }); }); diff --git a/test/spec/svg/lines.js b/test/spec/svg/lines.js index 61c3229a9..989022632 100644 --- a/test/spec/svg/lines.js +++ b/test/spec/svg/lines.js @@ -39,6 +39,16 @@ describe("iD.svg.Lines", function () { expect(surface.select('.line')).to.be.classed('member-type-route'); }); + it("adds stroke classes for the tags of the parent relation of multipolygon members", function() { + var line = iD.Way(), + relation = iD.Relation({members: [{id: line.id}], tags: {type: 'multipolygon', natural: 'wood'}}), + graph = iD.Graph([line, relation]); + + surface.call(iD.svg.Lines(projection), graph, [line], filter); + + expect(surface.select('.stroke')).to.be.classed('tag-natural-wood'); + }); + it("preserves non-line paths", function () { var line = iD.Way(), graph = iD.Graph([line]); diff --git a/test/spec/svg/multipolygons.js b/test/spec/svg/multipolygons.js deleted file mode 100644 index 67f44ebae..000000000 --- a/test/spec/svg/multipolygons.js +++ /dev/null @@ -1,43 +0,0 @@ -describe("iD.svg.Multipolygons", function () { - var surface, - projection = Object, - filter = d3.functor(true); - - beforeEach(function () { - surface = d3.select(document.createElementNS('http://www.w3.org/2000/svg', 'svg')) - .call(iD.svg.Surface()); - }); - - it("adds relation and multipolygon classes", function () { - var relation = iD.Relation({tags: {type: 'multipolygon'}}), - graph = iD.Graph([relation]); - - surface.call(iD.svg.Multipolygons(projection), graph, [relation], filter); - - expect(surface.select('path')).to.be.classed('relation'); - expect(surface.select('path')).to.be.classed('multipolygon'); - }); - - it("adds tag classes", function () { - var relation = iD.Relation({tags: {type: 'multipolygon', boundary: "administrative"}}), - graph = iD.Graph([relation]); - - surface.call(iD.svg.Multipolygons(projection), graph, [relation], filter); - - expect(surface.select('.relation')).to.be.classed('tag-boundary'); - expect(surface.select('.relation')).to.be.classed('tag-boundary-administrative'); - }); - - it("preserves non-multipolygon paths", function () { - var relation = iD.Relation({tags: {type: 'multipolygon'}}), - graph = iD.Graph([relation]); - - surface.select('.layer-fill') - .append('path') - .attr('class', 'other'); - - surface.call(iD.svg.Multipolygons(projection), graph, [relation], filter); - - expect(surface.selectAll('.other')[0].length).to.equal(1); - }); -}); diff --git a/test/spec/svg/tag_classes.js b/test/spec/svg/tag_classes.js index dd899e08e..048378815 100644 --- a/test/spec/svg/tag_classes.js +++ b/test/spec/svg/tag_classes.js @@ -19,6 +19,13 @@ describe("iD.svg.TagClasses", function () { expect(selection.attr('class')).to.equal('tag-highway tag-highway-primary'); }); + it('adds tags based on the result of the `tags` accessor', function() { + selection + .datum(iD.Entity()) + .call(iD.svg.TagClasses().tags(d3.functor({highway: 'primary'}))); + 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') From 14fc1d9c0d1fed338a5ea99a279fe74eb1f3bea8 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Tue, 5 Feb 2013 18:47:28 -0500 Subject: [PATCH 200/415] Fix flickering after redrawing active elems --- js/id/behavior/draw_way.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/js/id/behavior/draw_way.js b/js/id/behavior/draw_way.js index 6b648db0e..8c4c4a5cc 100644 --- a/js/id/behavior/draw_way.js +++ b/js/id/behavior/draw_way.js @@ -28,7 +28,13 @@ iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) { function move(datum) { var loc = context.map().mouseCoordinates(); - if (datum.type === 'node') { + if (datum.id === end.id || datum.id === segment.id) { + context.surface().selectAll('.way, .node') + .filter(function (d) { + return d.id === end.id || d.id === segment.id; + }) + .classed('active', true); + } else if (datum.type === 'node') { loc = datum.loc; } else if (datum.type === 'way') { loc = iD.geo.chooseIndex(datum, d3.mouse(context.surface().node()), context).loc; From 8f17628190b603f243c81e04fc2efb518be49800 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Tue, 5 Feb 2013 19:09:22 -0500 Subject: [PATCH 201/415] Support nudging while moving ways. Fixes #533 --- js/id/modes/move_way.js | 42 ++++++++++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/js/id/modes/move_way.js b/js/id/modes/move_way.js index 986eef5bd..e21dded78 100644 --- a/js/id/modes/move_way.js +++ b/js/id/modes/move_way.js @@ -6,7 +6,8 @@ iD.modes.MoveWay = function(context, wayId) { var keybinding = d3.keybinding('move-way'); mode.enter = function() { - var origin = point(), + var origin = context.map().mouseCoordinates(), + nudgeInterval, annotation = t('operations.move.annotation.' + context.geometry(wayId)); // If intiated via keyboard @@ -16,17 +17,44 @@ iD.modes.MoveWay = function(context, wayId) { iD.actions.Noop(), annotation); + function edge(point, size) { + var pad = [30, 100, 30, 100]; + if (point[0] > size[0] - pad[0]) return [-10, 0]; + else if (point[0] < pad[2]) return [10, 0]; + else if (point[1] > size[1] - pad[1]) return [0, -10]; + else if (point[1] < pad[3]) return [0, 10]; + return null; + } + + function startNudge(nudge) { + if (nudgeInterval) window.clearInterval(nudgeInterval); + nudgeInterval = window.setInterval(function() { + context.map().pan(nudge).redraw(); + }, 50); + } + + function stopNudge() { + if (nudgeInterval) window.clearInterval(nudgeInterval); + nudgeInterval = null; + } + function point() { - return d3.mouse(context.surface().node()); + return d3.mouse(context.map().surface.node()); } function move() { - var p = point(), - delta = origin ? - [p[0] - origin[0], p[1] - origin[1]] : - [0, 0]; + var p = point(); - origin = p; + var delta = origin ? + [p[0] - context.projection(origin)[0], + p[1] - context.projection(origin)[1]] : + [0, 0]; + + var nudge = edge(p, context.map().size()); + if (nudge) startNudge(nudge); + else stopNudge(); + + origin = context.map().mouseCoordinates(); context.replace( iD.actions.MoveWay(wayId, delta, context.projection), From 8bee68dfbd9300326216d8b979c481e5babd8195 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 5 Feb 2013 16:25:20 -0800 Subject: [PATCH 202/415] Fix includes --- test/rendering.html | 1 - 1 file changed, 1 deletion(-) diff --git a/test/rendering.html b/test/rendering.html index 196122b2c..ff624305a 100644 --- a/test/rendering.html +++ b/test/rendering.html @@ -21,7 +21,6 @@ - From 4dbd8f5efcde8643fd2b7974d7a64bd7a6b99fa4 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 5 Feb 2013 16:25:26 -0800 Subject: [PATCH 203/415] Fix #634 --- js/id/svg/points.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/id/svg/points.js b/js/id/svg/points.js index 471bc4b4d..a7fdb10fe 100644 --- a/js/id/svg/points.js +++ b/js/id/svg/points.js @@ -79,7 +79,7 @@ iD.svg.Points.imageIndex = [ }, { tags: { man_made: 'lighthouse' }, - icon: 'lighthouselevel_crossing' + icon: 'lighthouse' }, { tags: { natural: 'peak' }, From a4bf7c689fce0d63914b9ecf52740ca130c48f59 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Wed, 6 Feb 2013 10:49:58 -0500 Subject: [PATCH 204/415] Merge DragNode and DragMidpoint Adds shared behaviors such as snapping to DragMidpoint --- combobox.html | 1 - index.html | 1 - js/id/behavior/drag_midpoint.js | 29 ---------------------------- js/id/behavior/drag_node.js | 34 +++++++++++++++++++++++++++------ js/id/modes/browse.js | 3 +-- js/id/modes/select.js | 3 +-- test/index.html | 1 - 7 files changed, 30 insertions(+), 42 deletions(-) delete mode 100644 js/id/behavior/drag_midpoint.js diff --git a/combobox.html b/combobox.html index c739f4898..92949ecfa 100644 --- a/combobox.html +++ b/combobox.html @@ -90,7 +90,6 @@ - diff --git a/index.html b/index.html index a58aeb8f2..807e6a413 100644 --- a/index.html +++ b/index.html @@ -93,7 +93,6 @@ - diff --git a/js/id/behavior/drag_midpoint.js b/js/id/behavior/drag_midpoint.js deleted file mode 100644 index 4e39be2c6..000000000 --- a/js/id/behavior/drag_midpoint.js +++ /dev/null @@ -1,29 +0,0 @@ -iD.behavior.DragMidpoint = function(context) { - var behavior = iD.behavior.drag() - .delegate(".midpoint") - .origin(function(d) { - return context.projection(d.loc); - }) - .on('start', function(d) { - var node = iD.Node(); - - context.perform(iD.actions.AddMidpoint(d, node)); - - var vertex = context.surface().selectAll('.vertex') - .filter(function(data) { return data.id === node.id; }); - - behavior.target(vertex.node(), vertex.datum()); - }) - .on('move', function(d) { - d3.event.sourceEvent.stopPropagation(); - context.replace( - iD.actions.MoveNode(d.id, context.projection.invert(d3.event.point))); - }) - .on('end', function() { - context.replace( - iD.actions.Noop(), - t('operations.add.annotation.vertex')); - }); - - return behavior; -}; diff --git a/js/id/behavior/drag_node.js b/js/id/behavior/drag_node.js index 6df1fbf28..acba7919d 100644 --- a/js/id/behavior/drag_node.js +++ b/js/id/behavior/drag_node.js @@ -1,5 +1,6 @@ iD.behavior.DragNode = function(context) { - var nudgeInterval; + var nudgeInterval, + wasMidpoint; function edge(point, size) { var pad = [30, 100, 30, 100]; @@ -35,6 +36,23 @@ iD.behavior.DragNode = function(context) { } function start(entity) { + + wasMidpoint = entity.type === 'midpoint'; + if (wasMidpoint) { + var midpoint = entity; + entity = iD.Node(); + context.perform(iD.actions.AddMidpoint(midpoint, entity)); + + var vertex = context.surface() + .selectAll('.vertex') + .filter(function(d) { return d.id === entity.id; }); + behavior.target(vertex.node(), entity); + + } else { + context.perform( + iD.actions.Noop()); + } + var activeIDs = _.pluck(context.graph().parentWays(entity), 'id'); activeIDs.push(entity.id); @@ -43,9 +61,6 @@ iD.behavior.DragNode = function(context) { .selectAll('.node, .way') .filter(function (d) { return activeIDs.indexOf(d.id) >= 0; }) .classed('active', true); - - context.perform( - iD.actions.Noop()); } function datum() { @@ -96,6 +111,11 @@ iD.behavior.DragNode = function(context) { iD.actions.Connect([entity.id, d.id]), connectAnnotation(d)); + } else if (wasMidpoint) { + context.replace( + iD.actions.Noop(), + t('operations.add.annotation.vertex')); + } else { context.replace( iD.actions.Noop(), @@ -103,10 +123,12 @@ iD.behavior.DragNode = function(context) { } } - return iD.behavior.drag() - .delegate("g.node") + var behavior = iD.behavior.drag() + .delegate("g.node, g.midpoint") .origin(origin) .on('start', start) .on('move', move) .on('end', end); + + return behavior; }; diff --git a/js/id/modes/browse.js b/js/id/modes/browse.js index 33add1262..97f0d6935 100644 --- a/js/id/modes/browse.js +++ b/js/id/modes/browse.js @@ -10,8 +10,7 @@ iD.modes.Browse = function(context) { var behaviors = [ iD.behavior.Hover(), iD.behavior.Select(context), - iD.behavior.DragNode(context), - iD.behavior.DragMidpoint(context)]; + iD.behavior.DragNode(context)]; mode.enter = function() { behaviors.forEach(function(behavior) { diff --git a/js/id/modes/select.js b/js/id/modes/select.js index 532faaca8..0bcfa8c0b 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -9,8 +9,7 @@ iD.modes.Select = function(context, selection, initial) { behaviors = [ iD.behavior.Hover(), iD.behavior.Select(context), - iD.behavior.DragNode(context), - iD.behavior.DragMidpoint(context)], + iD.behavior.DragNode(context)], radialMenu; function changeTags(d, tags) { diff --git a/test/index.html b/test/index.html index 522f26092..f17829653 100644 --- a/test/index.html +++ b/test/index.html @@ -90,7 +90,6 @@ - From a21da4f15fef4b149d2ea88d5927093ef6ec4e98 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Wed, 6 Feb 2013 12:48:42 -0500 Subject: [PATCH 205/415] Saving graph to and reinstating from localStorage --- js/id/graph/graph.js | 45 +++++++++++++++++++++++++++++++++++++++++- js/id/graph/history.js | 45 +++++++++++++++++++++++++++++++++++++++++- js/id/id.js | 15 +++++++------- js/id/ui.js | 3 ++- js/id/ui/splash.js | 4 ++-- 5 files changed, 100 insertions(+), 12 deletions(-) diff --git a/js/id/graph/graph.js b/js/id/graph/graph.js index 2692aeba6..d9118045a 100644 --- a/js/id/graph/graph.js +++ b/js/id/graph/graph.js @@ -227,10 +227,53 @@ iD.Graph.prototype = { var items = []; for (var i in this.entities) { var entity = this.entities[i]; - if (entity && entity.intersects(extent, this)) { + if (entity && this.hasAllChildren(entity) && entity.intersects(extent, this)) { items.push(entity); } } return items; + }, + + hasAllChildren: function(entity) { + // we're only checking changed entities, since we assume fetched data + // must have all children present + if (this.entities.hasOwnProperty(entity.id)) { + if (entity.type === 'way') { + for (i = 0; i < entity.nodes.length; i++) { + if (!this.entities[entity.nodes[i]]) return false; + } + } else if (entity.type === 'relation') { + for (i = 0; i < entity.members.length; i++) { + if (!this.entities[entity.members[i].id]) return false; + } + } + } + return true; + }, + + // Obliterates any existing entities + load: function(entities) { + + var base = this.base(), + i, entity, prefix; + this.entities = Object.create(base.entities); + + for (i in entities) { + entity = entities[i]; + prefix = i[0]; + + if (prefix == 'n') { + this.entities[i] = new iD.Node(entity); + + } else if (prefix == 'w') { + this.entities[i] = new iD.Way(entity); + + } else if (prefix == 'r') { + this.entities[i] = new iD.Relation(entity); + } + this._updateCalculated(base.entities[i], this.entities[i]); + } + return this; } + }; diff --git a/js/id/graph/history.js b/js/id/graph/history.js index a0930203c..dba04ec7e 100644 --- a/js/id/graph/history.js +++ b/js/id/graph/history.js @@ -1,4 +1,4 @@ -iD.History = function() { +iD.History = function(context) { var stack, index, imagery_used = 'Bing', dispatch = d3.dispatch('change', 'undone', 'redone'); @@ -26,6 +26,10 @@ iD.History = function() { return difference; } + function getKey(n) { + return 'iD_' + window.location.origin + '_' + n; + } + var history = { graph: function() { return stack[index].graph; @@ -148,8 +152,47 @@ iD.History = function() { reset: function() { stack = [{graph: iD.Graph()}]; index = 0; + this.load(); dispatch.change(); + }, + + save: function() { + var json = JSON.stringify(stack.map(function(i) { + return _.extend(i, { + graph: i.graph.entities + }); + })); + + context.storage(getKey('history'), json); + context.storage(getKey('nextIDs'), JSON.stringify(iD.Entity.id.next)); + context.storage(getKey('index'), index); + context.storage(getKey('lock'), ''); + }, + + lock: function() { + if (context.storage(getKey('lock'))) return false; + context.storage(getKey('lock'), true); + return true; + }, + + load: function() { + if (!this.lock()) return; + + var json = context.storage(getKey('history')), + nextIDs = context.storage(getKey('nextIDs')), + index_ = context.storage(getKey('index')); + + if (!json) return; + if (nextIDs) iD.Entity.id.next = JSON.parse(nextIDs); + if (index_ !== null) index = parseInt(index_, 10); + + stack = JSON.parse(json).map(function(d) { + d.graph = iD.Graph().load(d.graph); + return d; + }); + } + }; history.reset(); diff --git a/js/id/id.js b/js/id/id.js index 56d3e481a..e20b3b111 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -1,18 +1,19 @@ window.iD = function () { var context = {}, - history = iD.History(), - storage = localStorage || {}, - dispatch = d3.dispatch('enter', 'exit'), - mode, - container, - ui = iD.ui(context), - map = iD.Map(context); + storage = localStorage || {}; context.storage = function(k, v) { if (arguments.length === 1) return storage[k]; else storage[k] = v; }; + var history = iD.History(context), + dispatch = d3.dispatch('enter', 'exit'), + mode, + container, + ui = iD.ui(context), + map = iD.Map(context); + // the connection requires .storage() to be available on calling. var connection = iD.Connection(context); diff --git a/js/id/ui.js b/js/id/ui.js index ace66703d..dbbff15c8 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -194,6 +194,7 @@ iD.ui = function(context) { history.on('change.editor', function() { window.onbeforeunload = history.hasChanges() ? function() { + history.save(); return 'You have unsaved changes.'; } : null; @@ -252,7 +253,7 @@ iD.ui = function(context) { context.enter(iD.modes.Browse(context)); if (!context.storage('sawSplash')) { - iD.ui.splash(); + iD.ui.splash(context.container()); context.storage('sawSplash', true); } }; diff --git a/js/id/ui/splash.js b/js/id/ui/splash.js index a4d2a915e..bfcbf3e05 100644 --- a/js/id/ui/splash.js +++ b/js/id/ui/splash.js @@ -1,5 +1,5 @@ -iD.ui.splash = function() { - var modal = iD.ui.modal(); +iD.ui.splash = function(selection) { + var modal = iD.ui.modal(selection); modal.select('.modal') .attr('class', 'modal-splash modal'); From 449c4d235da1f974f2b9c660baeb63a3c431c7b9 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Wed, 6 Feb 2013 14:03:31 -0500 Subject: [PATCH 206/415] Add option to restore or reset unsaved changes --- index.html | 1 + js/id/graph/history.js | 34 +++++++++++++++++++++++++++------- js/id/id.js | 1 + js/id/ui.js | 5 +++++ js/id/ui/restore.js | 34 ++++++++++++++++++++++++++++++++++ 5 files changed, 68 insertions(+), 7 deletions(-) create mode 100644 js/id/ui/restore.js diff --git a/index.html b/index.html index 807e6a413..18f1b275b 100644 --- a/index.html +++ b/index.html @@ -67,6 +67,7 @@ + diff --git a/js/id/graph/history.js b/js/id/graph/history.js index dba04ec7e..ac36d564f 100644 --- a/js/id/graph/history.js +++ b/js/id/graph/history.js @@ -1,7 +1,8 @@ iD.History = function(context) { var stack, index, imagery_used = 'Bing', - dispatch = d3.dispatch('change', 'undone', 'redone'); + dispatch = d3.dispatch('change', 'undone', 'redone'), + lock = false; function perform(actions) { actions = Array.prototype.slice.call(actions); @@ -152,11 +153,20 @@ iD.History = function(context) { reset: function() { stack = [{graph: iD.Graph()}]; index = 0; - this.load(); dispatch.change(); }, save: function() { + if (!lock) return; + context.storage(getKey('lock'), null); + + if (!stack.length) { + context.storage(getKey('history'), null); + context.storage(getKey('nextIDs'), null); + context.storage(getKey('index'), null); + return; + } + var json = JSON.stringify(stack.map(function(i) { return _.extend(i, { graph: i.graph.entities @@ -166,17 +176,22 @@ iD.History = function(context) { context.storage(getKey('history'), json); context.storage(getKey('nextIDs'), JSON.stringify(iD.Entity.id.next)); context.storage(getKey('index'), index); - context.storage(getKey('lock'), ''); }, lock: function() { if (context.storage(getKey('lock'))) return false; context.storage(getKey('lock'), true); - return true; + lock = true; + return lock; + }, + + restorableChanges: function() { + if (!this.lock()) return false; + return !!context.storage(getKey('history')); }, load: function() { - if (!this.lock()) return; + if (!lock) return; var json = context.storage(getKey('history')), nextIDs = context.storage(getKey('nextIDs')), @@ -186,10 +201,15 @@ iD.History = function(context) { if (nextIDs) iD.Entity.id.next = JSON.parse(nextIDs); if (index_ !== null) index = parseInt(index_, 10); - stack = JSON.parse(json).map(function(d) { - d.graph = iD.Graph().load(d.graph); + context.storage(getKey('history', null)); + context.storage(getKey('nextIDs', null)); + context.storage(getKey('index', null)); + + stack = JSON.parse(json).map(function(d, i) { + d.graph = iD.Graph(stack[0].graph).load(d.graph); return d; }); + dispatch.change(); } diff --git a/js/id/id.js b/js/id/id.js index e20b3b111..d58464ecb 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -4,6 +4,7 @@ window.iD = function () { context.storage = function(k, v) { if (arguments.length === 1) return storage[k]; + else if (v === null) delete storage[k]; else storage[k] = v; }; diff --git a/js/id/ui.js b/js/id/ui.js index dbbff15c8..c22ca6b83 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -256,5 +256,10 @@ iD.ui = function(context) { iD.ui.splash(context.container()); context.storage('sawSplash', true); } + + if (history.restorableChanges()) { + iD.ui.restore(context.container(), history); + } + }; }; diff --git a/js/id/ui/restore.js b/js/id/ui/restore.js new file mode 100644 index 000000000..3df275d89 --- /dev/null +++ b/js/id/ui/restore.js @@ -0,0 +1,34 @@ +iD.ui.restore = function(selection, history) { + var modal = iD.ui.modal(selection); + + modal.select('.modal') + .attr('class', 'modal-splash modal'); + + var introModal = modal.select('.content') + .append('div') + .attr('class', 'modal-section fillL') + .text('You have unsaved changes from a previous editing session. Do you wish to restore these changes?'); + + buttons = introModal + .append('div') + .attr('class', 'buttons cf') + .append('div') + .attr('class', 'button-wrap joined col4'); + + buttons.append('button') + .attr('class', 'save action button col6') + .text('Restore') + .on('click', function() { + history.load(); + modal.remove(); + }); + + buttons.append('button') + .attr('class', 'cancel button col6') + .text('Reset') + .on('click', function() { + modal.remove(); + }); + + return modal; +}; From 0d70e466de619185d6752a7f609663b513f9e502 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Wed, 6 Feb 2013 14:37:04 -0500 Subject: [PATCH 207/415] Add crazyegg, will remove at release --- index.html | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/index.html b/index.html index 18f1b275b..09d73b8f6 100644 --- a/index.html +++ b/index.html @@ -152,6 +152,8 @@ .call(id.ui()) }); + + + + + From 0acab34054131544587b486c35ee6ee5cee73ae7 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Wed, 6 Feb 2013 15:06:50 -0500 Subject: [PATCH 208/415] Draw click event triggered by click instead of up --- js/id/behavior/draw.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/js/id/behavior/draw.js b/js/id/behavior/draw.js index a4fb53a1a..b0da771a4 100644 --- a/js/id/behavior/draw.js +++ b/js/id/behavior/draw.js @@ -27,7 +27,7 @@ iD.behavior.Draw = function(context) { target.on('mousemove.draw', null); - d3.select(window).on('mouseup.draw', function() { + d3.select(window).on('click.draw', function() { target.on('mousemove.draw', mousemove); if (iD.geo.dist(pos, point()) < closeTolerance || (iD.geo.dist(pos, point()) < tolerance && @@ -35,7 +35,6 @@ iD.behavior.Draw = function(context) { click(); } }); - } function mousemove() { @@ -111,7 +110,7 @@ iD.behavior.Draw = function(context) { .on('mousedown.draw', null) .on('mousemove.draw', null); - d3.select(window).on('mouseup.draw', null); + d3.select(window).on('click.draw', null); d3.select(document) .call(keybinding.off) From a9632a2c7a05275315e25d3983110e9cf38cad5e Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Wed, 6 Feb 2013 15:09:40 -0500 Subject: [PATCH 209/415] Do not trigger radial on double click --- js/id/behavior/select.js | 72 +++++++++++++++++++++++++++++++--------- 1 file changed, 57 insertions(+), 15 deletions(-) diff --git a/js/id/behavior/select.js b/js/id/behavior/select.js index f7a388396..3d10db144 100644 --- a/js/id/behavior/select.js +++ b/js/id/behavior/select.js @@ -1,24 +1,66 @@ iD.behavior.Select = function(context) { - function click() { - var datum = d3.select(d3.event.target).datum(); - if (datum instanceof iD.Entity) { - if (d3.event.shiftKey) { - context.enter(iD.modes.Select(context, context.selection().concat([datum.id]))); - } else { - context.enter(iD.modes.Select(context, [datum.id])); - } - } else if (!d3.event.shiftKey) { - context.enter(iD.modes.Browse(context)); - } - } - var behavior = function(selection) { - selection.on('click.select', click); + + var timeout = null, + // the position of the first mousedown + pos = null; + + function click(event) { + d3.event = event; + var datum = d3.select(d3.event.target).datum(); + if (datum instanceof iD.Entity) { + if (d3.event.shiftKey) { + context.enter(iD.modes.Select(context, context.selection().concat([datum.id]))); + } else { + context.enter(iD.modes.Select(context, [datum.id])); + } + } else if (!d3.event.shiftKey) { + context.enter(iD.modes.Browse(context)); + } + } + + function mousedown() { + pos = d3.mouse(context.surface().node()); + selection + .on('mousemove.select', mousemove) + .on('touchmove.select', mousemove); + + // we've seen a mousedown within 400ms of this one, so ignore + // both because they will be a double click + if (timeout !== null) { + window.clearTimeout(timeout); + selection.on('mousemove.select', null); + timeout = null; + } else { + // queue the click handler to fire in 400ms if no other clicks + // are detected + timeout = window.setTimeout((function(event) { + return function() { + click(event); + timeout = null; + selection.on('mousemove.select', null); + }; + // save the event for the click handler + })(d3.event), 400); + } + } + + // allow mousemoves to cancel the click + function mousemove() { + if (iD.geo.dist(d3.mouse(context.surface().node()), pos) > 4) { + window.clearTimeout(timeout); + timeout = null; + } + } + + selection + .on('mousedown.select', mousedown) + .on('touchstart.select', mousedown); }; behavior.off = function(selection) { - selection.on('click.select', null); + selection.on('mousedown.select', null); }; return behavior; From 83224d0f87ec11a62903a0efba7896cf60a13702 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Wed, 6 Feb 2013 15:38:20 -0500 Subject: [PATCH 210/415] imagery_used includes full custom template --- js/id/renderer/background_source.js | 1 + js/id/ui/layerswitcher.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/js/id/renderer/background_source.js b/js/id/renderer/background_source.js index cfed23294..e740525c0 100644 --- a/js/id/renderer/background_source.js +++ b/js/id/renderer/background_source.js @@ -24,6 +24,7 @@ iD.BackgroundSource.template = function(template, subdomains, scaleExtent) { }; generator.scaleExtent = scaleExtent; + generator.template = template; return generator; }; diff --git a/js/id/ui/layerswitcher.js b/js/id/ui/layerswitcher.js index 9eb063517..a15cd33c3 100644 --- a/js/id/ui/layerswitcher.js +++ b/js/id/ui/layerswitcher.js @@ -125,7 +125,7 @@ iD.ui.layerswitcher = function(context) { var configured = d.source(); if (!configured) return; d.source = configured; - d.name = 'Custom (configured)'; + d.name = 'Custom (' + d.source.template + ')'; } context.background().source(d.source); context.history().imagery_used(d.name); From fbe3a41d570441f0cc32ae414c5f858db9d885f9 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Wed, 6 Feb 2013 15:47:16 -0500 Subject: [PATCH 211/415] Update tests for faux click events in more places. --- test/index.html | 2 +- test/spec/behavior/select.js | 26 ++++++++++++++++++-------- test/spec/modes/add_point.js | 4 ++-- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/test/index.html b/test/index.html index f17829653..3f2eba72e 100644 --- a/test/index.html +++ b/test/index.html @@ -137,7 +137,7 @@ iD.debug = true; mocha.setup({ ui: 'bdd', - globals: ['__onresize.tail-size'] + globals: ['__onresize.tail-size', '__onmousemove.zoom', '__onmouseup.zoom', '__onclick.draw'] }); var expect = chai.expect; diff --git a/test/spec/behavior/select.js b/test/spec/behavior/select.js index 42c568cca..e794cda8e 100644 --- a/test/spec/behavior/select.js +++ b/test/spec/behavior/select.js @@ -29,21 +29,31 @@ describe("iD.behavior.Select", function() { container.remove(); }); - specify("click on entity selects the entity", function() { - happen.click(context.surface().select('.' + a.id).node()); - expect(context.selection()).to.eql([a.id]); + specify("click on entity selects the entity", function(done) { + happen.mousedown(context.surface().select('.' + a.id).node()); + window.setTimeout(function() { + expect(context.selection()).to.eql([a.id]); + done(); + }, 600); }); - specify("click on empty space clears the selection", function() { + specify("click on empty space clears the selection", function(done) { context.enter(iD.modes.Select(context, [a.id])); happen.click(context.surface().node()); - expect(context.selection()).to.eql([]); + happen.mousedown(context.surface().node()); + window.setTimeout(function() { + expect(context.selection()).to.eql([]); + done(); + }, 600); }); - specify("shift-click on entity adds the entity to the selection", function() { + specify("shift-click on entity adds the entity to the selection", function(done) { context.enter(iD.modes.Select(context, [a.id])); - happen.click(context.surface().select('.' + b.id).node(), {shiftKey: true}); - expect(context.selection()).to.eql([a.id, b.id]); + happen.mousedown(context.surface().select('.' + b.id).node(), {shiftKey: true}); + window.setTimeout(function() { + expect(context.selection()).to.eql([a.id, b.id]); + done(); + }, 600); }); specify("shift-click on empty space leaves the selection unchanged", function() { diff --git a/test/spec/modes/add_point.js b/test/spec/modes/add_point.js index 62291f8ee..5807a3c0b 100644 --- a/test/spec/modes/add_point.js +++ b/test/spec/modes/add_point.js @@ -17,13 +17,13 @@ describe("iD.modes.AddPoint", function() { describe("clicking the map", function () { it("adds a node", function() { happen.mousedown(context.surface().node(), {}); - happen.mouseup(window, {}); + happen.click(window, {}); expect(context.changes().created).to.have.length(1); }); it("selects the node", function() { happen.mousedown(context.surface().node(), {}); - happen.mouseup(window, {}); + happen.click(window, {}); expect(context.mode().id).to.equal('select'); expect(context.mode().selection()).to.eql([context.changes().created[0].id]); }); From 3440f4254ddb468e6d4453bf2f9f7c9c839adac6 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Wed, 6 Feb 2013 16:22:39 -0500 Subject: [PATCH 212/415] Discard tags --- data/deprecated.js | 10 +--------- data/discarded.js | 10 ++++++++++ index.html | 2 ++ js/id/behavior/select.js | 2 +- js/id/graph/history.js | 10 +++++++--- 5 files changed, 21 insertions(+), 13 deletions(-) create mode 100644 data/discarded.js diff --git a/data/deprecated.js b/data/deprecated.js index 7cc6904ed..53457c9f3 100644 --- a/data/deprecated.js +++ b/data/deprecated.js @@ -108,13 +108,5 @@ iD.data.deprecated = [ shop: 'supermarket', organic: 'only' } - }, - // entirely discarded tags - { old: { 'tiger:upload_uuid': '*' } }, - { old: { 'tiger:tlid': '*' } }, - { old: { 'tiger:source': '*' } }, - { old: { 'tiger:separated': '*' } }, - { old: { 'geobase:datasetName': '*' } }, - { old: { 'geobase:uuid': '*' } }, - { old: { 'sub_sea:type': '*' } } + } ]; diff --git a/data/discarded.js b/data/discarded.js new file mode 100644 index 000000000..7a1e4580e --- /dev/null +++ b/data/discarded.js @@ -0,0 +1,10 @@ +// entirely discarded tags +iD.data.discarded = [ + 'tiger:upload_uuid', + 'tiger:tlid', + 'tiger:source', + 'tiger:separated', + 'geobase:datasetName', + 'geobase:uuid', + 'sub_sea:type' +]; diff --git a/index.html b/index.html index 09d73b8f6..a4a2ab5f5 100644 --- a/index.html +++ b/index.html @@ -90,6 +90,7 @@ + @@ -137,6 +138,7 @@ +
+ diff --git a/js/id/actions/merge.js b/js/id/actions/merge.js new file mode 100644 index 000000000..8ba2a2318 --- /dev/null +++ b/js/id/actions/merge.js @@ -0,0 +1,35 @@ +iD.actions.Merge = function(ids) { + function groupEntitiesByGeometry(graph) { + var entities = ids.map(function(id) { return graph.entity(id); }); + return _.extend({point: [], area: []}, _.groupBy(entities, function(entity) { return entity.geometry(graph); })); + } + + var action = function(graph) { + var geometries = groupEntitiesByGeometry(graph), + area = geometries['area'][0], + points = geometries['point']; + + points.forEach(function (point) { + area = area.mergeTags(point.tags); + + graph.parentRelations(point).forEach(function (parent) { + graph = graph.replace(parent.replaceMember(point, area)); + }); + + graph = graph.remove(point); + }); + + graph = graph.replace(area); + + return graph; + }; + + action.enabled = function(graph) { + var geometries = groupEntitiesByGeometry(graph); + return geometries['area'].length === 1 && + geometries['point'].length > 0 && + (geometries['area'].length + geometries['point'].length) === ids.length; + }; + + return action; +}; diff --git a/js/id/operations/merge.js b/js/id/operations/merge.js index b2073db52..6202f1b3f 100644 --- a/js/id/operations/merge.js +++ b/js/id/operations/merge.js @@ -1,18 +1,28 @@ iD.operations.Merge = function(selection, context) { - var action = iD.actions.Join(selection); + var join = iD.actions.Join(selection), + merge = iD.actions.Merge(selection); var operation = function() { var annotation = t('operations.merge.annotation', {n: selection.length}), - difference = context.perform(action, annotation); + action; + + if (join.enabled(context.graph())) { + action = join; + } else { + action = merge; + } + + var difference = context.perform(action, annotation); context.enter(iD.modes.Select(context, difference.extantIDs())); }; operation.available = function() { - return selection.length > 1; + return selection.length >= 2; }; operation.enabled = function() { - return action.enabled(context.graph()); + return join.enabled(context.graph()) || + merge.enabled(context.graph()); }; operation.id = "merge"; diff --git a/test/index.html b/test/index.html index 3f2eba72e..ecad5ca75 100644 --- a/test/index.html +++ b/test/index.html @@ -80,6 +80,7 @@ + @@ -157,6 +158,7 @@ + diff --git a/test/index_packaged.html b/test/index_packaged.html index 8dd73d666..f85ac1f0b 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -42,6 +42,7 @@ + diff --git a/test/spec/actions/merge.js b/test/spec/actions/merge.js new file mode 100644 index 000000000..5ea0b3dea --- /dev/null +++ b/test/spec/actions/merge.js @@ -0,0 +1,20 @@ +describe("iD.actions.Merge", function () { + it("merges multiple points to an area", function () { + var graph = iD.Graph({ + 'a': iD.Node({id: 'a', tags: {a: 'a'}}), + 'b': iD.Node({id: 'b', tags: {b: 'b'}}), + 'w': iD.Way({id: 'w', tags: {area: 'yes'}}), + 'r': iD.Relation({id: 'r', members: [{id: 'a', role: 'r', type: 'node'}]}) + }), + action = iD.actions.Merge(['a', 'b', 'w']); + + expect(action.enabled(graph)).to.be.true; + + graph = action(graph); + + expect(graph.entity('a')).to.be.undefined; + expect(graph.entity('b')).to.be.undefined; + expect(graph.entity('w').tags).to.eql({a: 'a', b: 'b', area: 'yes'}); + expect(graph.entity('r').members).to.eql([{id: 'w', role: 'r', type: 'way'}]); + }); +}); From 104b02eda110844c9b2c84359e2668367f30c0df Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Wed, 6 Feb 2013 14:04:19 -0800 Subject: [PATCH 218/415] Move common setup to spec_helpers.js --- test/index.html | 9 --------- test/index_packaged.html | 9 --------- test/spec/spec_helpers.js | 9 +++++++++ 3 files changed, 9 insertions(+), 18 deletions(-) diff --git a/test/index.html b/test/index.html index ecad5ca75..87d799279 100644 --- a/test/index.html +++ b/test/index.html @@ -134,15 +134,6 @@ - - diff --git a/test/index_packaged.html b/test/index_packaged.html index f85ac1f0b..778231edd 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -18,15 +18,6 @@ - - diff --git a/test/spec/spec_helpers.js b/test/spec/spec_helpers.js index b8ebdf307..7a62e76b9 100644 --- a/test/spec/spec_helpers.js +++ b/test/spec/spec_helpers.js @@ -1,3 +1,12 @@ +iD.debug = true; + +mocha.setup({ + ui: 'bdd', + globals: ['__onresize.tail-size', '__onmousemove.zoom', '__onmouseup.zoom', '__onclick.draw'] +}); + +var expect = chai.expect; + chai.use(function (chai, utils) { var flag = utils.flag; From e868c071ac329accc8cfa19d4db800186660d536 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Wed, 6 Feb 2013 17:16:00 -0500 Subject: [PATCH 219/415] Remove more unreliable or slow layers, select the right layer initially, fix null tooltips --- css/app.css | 6 +++ data/imagery.js | 77 ++++++--------------------------------- data/imagery.json | 77 ++++++--------------------------------- data/imagery_convert.js | 10 ++++- js/id/renderer/layers.js | 1 + js/id/ui/layerswitcher.js | 8 +++- 6 files changed, 44 insertions(+), 135 deletions(-) diff --git a/css/app.css b/css/app.css index a43133f3d..ef42bb28c 100644 --- a/css/app.css +++ b/css/app.css @@ -186,6 +186,8 @@ ul.toggle-list li a { border-top: 1px solid white; display:block; border-top: 1px solid rgba(0, 0, 0, .5); + text-wrap:no-wrap; + overflow:hidden; } ul.toggle-list li a:hover { background-color: #ececec;} @@ -687,6 +689,10 @@ a.selected:hover .toggle.icon { background-position: -40px -180px;} top:190px; } +.layerswitcher-control .map-overlay { + width:250px; +} + .nudge-container { margin-top: 10px; } diff --git a/data/imagery.js b/data/imagery.js index fb6f3f020..6c232b626 100644 --- a/data/imagery.js +++ b/data/imagery.js @@ -42,55 +42,8 @@ iD.data.imagery = [ ] }, { - "name": "OSM - Tiger Edited Map", - "template": "http://tiger-osm.mapquest.com/tiles/1.0.0/tiger/{z}/{x}/{y}.png", - "extent": [ - [ - -124.81, - 24.055 - ], - [ - -66.865, - 49.386 - ] - ] - }, - { - "name": "OSM - Tiger Edited Map", - "template": "http://tiger-osm.mapquest.com/tiles/1.0.0/tiger/{z}/{x}/{y}.png", - "extent": [ - [ - -179.754, - 50.858 - ], - [ - -129.899, - 71.463 - ] - ] - }, - { - "name": "OSM - Tiger Edited Map", - "template": "http://tiger-osm.mapquest.com/tiles/1.0.0/tiger/{z}/{x}/{y}.png", - "extent": [ - [ - -174.46, - 18.702 - ], - [ - -154.516, - 26.501 - ] - ] - }, - { - "name": "OSM US TIGER 2012 Roads Overlay", + "name": " TIGER 2012 Roads Overlay", "template": "http://{t}.tile.openstreetmap.us/tiger2012_roads_expanded/{z}/{x}/{y}.png", - "description": "Public domain road data from the US Government.", - "scaleExtent": [ - 0, - 17 - ], "subdomains": [ "a", "b", @@ -108,13 +61,8 @@ iD.data.imagery = [ ] }, { - "name": "OSM US TIGER 2012 Roads Overlay", + "name": " TIGER 2012 Roads Overlay", "template": "http://{t}.tile.openstreetmap.us/tiger2012_roads_expanded/{z}/{x}/{y}.png", - "description": "Public domain road data from the US Government.", - "scaleExtent": [ - 0, - 17 - ], "subdomains": [ "a", "b", @@ -132,13 +80,8 @@ iD.data.imagery = [ ] }, { - "name": "OSM US TIGER 2012 Roads Overlay", + "name": " TIGER 2012 Roads Overlay", "template": "http://{t}.tile.openstreetmap.us/tiger2012_roads_expanded/{z}/{x}/{y}.png", - "description": "Public domain road data from the US Government.", - "scaleExtent": [ - 0, - 17 - ], "subdomains": [ "a", "b", @@ -156,7 +99,7 @@ iD.data.imagery = [ ] }, { - "name": "OSM US USGS Topographic Maps", + "name": " USGS Topographic Maps", "template": "http://{t}.tile.openstreetmap.us/usgs_scanned_topos/{z}/{x}/{y}.png", "subdomains": [ "a", @@ -175,7 +118,7 @@ iD.data.imagery = [ ] }, { - "name": "OSM US USGS Topographic Maps", + "name": " USGS Topographic Maps", "template": "http://{t}.tile.openstreetmap.us/usgs_scanned_topos/{z}/{x}/{y}.png", "subdomains": [ "a", @@ -194,7 +137,7 @@ iD.data.imagery = [ ] }, { - "name": "OSM US USGS Topographic Maps", + "name": " USGS Topographic Maps", "template": "http://{t}.tile.openstreetmap.us/usgs_scanned_topos/{z}/{x}/{y}.png", "subdomains": [ "a", @@ -213,7 +156,7 @@ iD.data.imagery = [ ] }, { - "name": "OSM US USGS Large Scale Aerial Imagery", + "name": " USGS Large Scale Aerial Imagery", "template": "http://{t}.tile.openstreetmap.us/usgs_large_scale/{z}/{x}/{y}.jpg", "subdomains": [ "a", @@ -453,8 +396,9 @@ iD.data.imagery = [ "sourcetag": "Haiti streetnames" }, { - "name": "National Agriculture Imagery Program", + "name": "NAIP", "template": "http://cube.telascience.org/tilecache/tilecache.py/NAIP_ALL/{z}/{x}/{y}.png", + "description": "National Agriculture Imagery Program", "extent": [ [ -125.8, @@ -468,8 +412,9 @@ iD.data.imagery = [ "sourcetag": "NAIP" }, { - "name": "National Agriculture Imagery Program", + "name": "NAIP", "template": "http://cube.telascience.org/tilecache/tilecache.py/NAIP_ALL/{z}/{x}/{y}.png", + "description": "National Agriculture Imagery Program", "extent": [ [ -168.5, diff --git a/data/imagery.json b/data/imagery.json index e4497fc9d..ee9906d31 100644 --- a/data/imagery.json +++ b/data/imagery.json @@ -42,55 +42,8 @@ ] }, { - "name": "OSM - Tiger Edited Map", - "template": "http://tiger-osm.mapquest.com/tiles/1.0.0/tiger/{z}/{x}/{y}.png", - "extent": [ - [ - -124.81, - 24.055 - ], - [ - -66.865, - 49.386 - ] - ] - }, - { - "name": "OSM - Tiger Edited Map", - "template": "http://tiger-osm.mapquest.com/tiles/1.0.0/tiger/{z}/{x}/{y}.png", - "extent": [ - [ - -179.754, - 50.858 - ], - [ - -129.899, - 71.463 - ] - ] - }, - { - "name": "OSM - Tiger Edited Map", - "template": "http://tiger-osm.mapquest.com/tiles/1.0.0/tiger/{z}/{x}/{y}.png", - "extent": [ - [ - -174.46, - 18.702 - ], - [ - -154.516, - 26.501 - ] - ] - }, - { - "name": "OSM US TIGER 2012 Roads Overlay", + "name": " TIGER 2012 Roads Overlay", "template": "http://{t}.tile.openstreetmap.us/tiger2012_roads_expanded/{z}/{x}/{y}.png", - "description": "Public domain road data from the US Government.", - "scaleExtent": [ - 0, - 17 - ], "subdomains": [ "a", "b", @@ -108,13 +61,8 @@ ] }, { - "name": "OSM US TIGER 2012 Roads Overlay", + "name": " TIGER 2012 Roads Overlay", "template": "http://{t}.tile.openstreetmap.us/tiger2012_roads_expanded/{z}/{x}/{y}.png", - "description": "Public domain road data from the US Government.", - "scaleExtent": [ - 0, - 17 - ], "subdomains": [ "a", "b", @@ -132,13 +80,8 @@ ] }, { - "name": "OSM US TIGER 2012 Roads Overlay", + "name": " TIGER 2012 Roads Overlay", "template": "http://{t}.tile.openstreetmap.us/tiger2012_roads_expanded/{z}/{x}/{y}.png", - "description": "Public domain road data from the US Government.", - "scaleExtent": [ - 0, - 17 - ], "subdomains": [ "a", "b", @@ -156,7 +99,7 @@ ] }, { - "name": "OSM US USGS Topographic Maps", + "name": " USGS Topographic Maps", "template": "http://{t}.tile.openstreetmap.us/usgs_scanned_topos/{z}/{x}/{y}.png", "subdomains": [ "a", @@ -175,7 +118,7 @@ ] }, { - "name": "OSM US USGS Topographic Maps", + "name": " USGS Topographic Maps", "template": "http://{t}.tile.openstreetmap.us/usgs_scanned_topos/{z}/{x}/{y}.png", "subdomains": [ "a", @@ -194,7 +137,7 @@ ] }, { - "name": "OSM US USGS Topographic Maps", + "name": " USGS Topographic Maps", "template": "http://{t}.tile.openstreetmap.us/usgs_scanned_topos/{z}/{x}/{y}.png", "subdomains": [ "a", @@ -213,7 +156,7 @@ ] }, { - "name": "OSM US USGS Large Scale Aerial Imagery", + "name": " USGS Large Scale Aerial Imagery", "template": "http://{t}.tile.openstreetmap.us/usgs_large_scale/{z}/{x}/{y}.jpg", "subdomains": [ "a", @@ -453,8 +396,9 @@ "sourcetag": "Haiti streetnames" }, { - "name": "National Agriculture Imagery Program", + "name": "NAIP", "template": "http://cube.telascience.org/tilecache/tilecache.py/NAIP_ALL/{z}/{x}/{y}.png", + "description": "National Agriculture Imagery Program", "extent": [ [ -125.8, @@ -468,8 +412,9 @@ "sourcetag": "NAIP" }, { - "name": "National Agriculture Imagery Program", + "name": "NAIP", "template": "http://cube.telascience.org/tilecache/tilecache.py/NAIP_ALL/{z}/{x}/{y}.png", + "description": "National Agriculture Imagery Program", "extent": [ [ -168.5, diff --git a/data/imagery_convert.js b/data/imagery_convert.js index 5dc36c502..abc5cbef6 100644 --- a/data/imagery_convert.js +++ b/data/imagery_convert.js @@ -14,14 +14,16 @@ var censor = { }; var replace = { - 'OSM - Mapnik': 'OpenStreetMap' + 'OSM - Mapnik': 'OpenStreetMap', + 'National Agriculture Imagery Program': 'NAIP' }; var description = { 'MapBox Satellite': 'Satellite and aerial imagery', 'OpenStreetMap': 'The default OpenStreetMap layer.', 'OSM US TIGER 2012 Roads Overlay': 'Public domain road data from the US Government.', - 'Bing aerial imagery': 'Satellite imagery.' + 'Bing aerial imagery': 'Satellite imagery.', + 'NAIP': 'National Agriculture Imagery Program' }; var scaleExtent = { @@ -39,8 +41,12 @@ $('set').each(function(i) { template: $(this).find('url').first().text() }; + // no luck with mapquest servers currently... + if (im.template.match(/mapquest/g)) return; if (censor[im.name]) return; + im.name = im.name.replace('OSM US', ''); + if (replace[im.name]) im.name = replace[im.name]; if (description[im.name]) im.description = description[im.name]; diff --git a/js/id/renderer/layers.js b/js/id/renderer/layers.js index 773e8011e..966e16bb8 100644 --- a/js/id/renderer/layers.js +++ b/js/id/renderer/layers.js @@ -4,6 +4,7 @@ iD.layers.push((function() { function custom() { var template = window.prompt('Enter a tile template. Valid tokens are {z}, {x}, {y} for Z/X/Y scheme and {u} for quadtile scheme.'); if (!template) return null; + if (template.match(/google/g)) return null; return iD.BackgroundSource.template({ template: template, name: 'Custom (customized)' diff --git a/js/id/ui/layerswitcher.js b/js/id/ui/layerswitcher.js index 88ee45f49..f788322b1 100644 --- a/js/id/ui/layerswitcher.js +++ b/js/id/ui/layerswitcher.js @@ -122,10 +122,16 @@ iD.ui.layerswitcher = function(context) { .text(function(d) { return d.data.name; }) - .call(bootstrap.tooltip().placement('right')) + .each(function(d) { + // only set tooltips for layers with tooltips + if (d.data.description) { + d3.select(this).call(bootstrap.tooltip().placement('right')); + } + }) .on('click.set-source', clickSetSource) .insert('span') .attr('class','icon toggle'); + selectLayer(context.background().source()); } context.map().on('move.layerswitcher-update', _.debounce(update, 1000)); From f6e726bcd68dd2f0a43d14e407794eed0b213978 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Wed, 6 Feb 2013 14:31:11 -0800 Subject: [PATCH 220/415] Join should run Reverse where necessary (fixes #652) --- js/id/actions/join.js | 6 ++++-- test/spec/actions/join.js | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/js/id/actions/join.js b/js/id/actions/join.js index 7bba4783c..267b6c75a 100644 --- a/js/id/actions/join.js +++ b/js/id/actions/join.js @@ -24,7 +24,8 @@ iD.actions.Join = function(ids) { // a <-- b ==> c // Expected result: // a <-- b <-- c - nodes = b.nodes.slice().reverse().concat(a.nodes.slice(1)); + b = iD.actions.Reverse(idB)(graph).entity(idB); + nodes = b.nodes.slice().concat(a.nodes.slice(1)); } else if (a.first() === b.last()) { // a <-- b <== c @@ -42,7 +43,8 @@ iD.actions.Join = function(ids) { // a --> b <== c // Expected result: // a --> b --> c - nodes = a.nodes.concat(b.nodes.slice().reverse().slice(1)); + b = iD.actions.Reverse(idB)(graph).entity(idB); + nodes = a.nodes.concat(b.nodes.slice().slice(1)); } graph.parentRelations(b).forEach(function (parent) { diff --git a/test/spec/actions/join.js b/test/spec/actions/join.js index 11a95bc2d..6951013d3 100644 --- a/test/spec/actions/join.js +++ b/test/spec/actions/join.js @@ -112,13 +112,14 @@ describe("iD.actions.Join", function () { 'b': iD.Node({id: 'b'}), 'c': iD.Node({id: 'c'}), '-': iD.Way({id: '-', nodes: ['b', 'a']}), - '=': iD.Way({id: '=', nodes: ['b', 'c']}) + '=': iD.Way({id: '=', nodes: ['b', 'c'], tags: {'lanes:forward': 2}}) }); graph = iD.actions.Join(['-', '='])(graph); expect(graph.entity('-').nodes).to.eql(['c', 'b', 'a']); expect(graph.entity('=')).to.be.undefined; + expect(graph.entity('-').tags).to.eql({'lanes:backward': 2}); }); it("joins a --> b <== c", function () { @@ -130,13 +131,14 @@ describe("iD.actions.Join", function () { 'b': iD.Node({id: 'b'}), 'c': iD.Node({id: 'c'}), '-': iD.Way({id: '-', nodes: ['a', 'b']}), - '=': iD.Way({id: '=', nodes: ['c', 'b']}) + '=': iD.Way({id: '=', nodes: ['c', 'b'], tags: {'lanes:forward': 2}}) }); graph = iD.actions.Join(['-', '='])(graph); expect(graph.entity('-').nodes).to.eql(['a', 'b', 'c']); expect(graph.entity('=')).to.be.undefined; + expect(graph.entity('-').tags).to.eql({'lanes:backward': 2}); }); it("merges tags", function () { From 4b5dcd054e13667a3a456a75a722087791fcd6fc Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Wed, 6 Feb 2013 17:34:41 -0500 Subject: [PATCH 221/415] Fix sourcetag regression --- css/app.css | 1 - js/id/ui.js | 5 +++-- js/id/ui/layerswitcher.js | 24 ++++++++++++++++++------ 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/css/app.css b/css/app.css index ef42bb28c..65f98dfd6 100644 --- a/css/app.css +++ b/css/app.css @@ -93,7 +93,6 @@ a:hover { color:#597be7; } - textarea, input[type=text] { background-color: white; diff --git a/js/id/ui.js b/js/id/ui.js index ace66703d..747bccb70 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -161,8 +161,9 @@ iD.ui = function(context) { var imagery = linkList.append('li').attr('id', 'attribution'); imagery.append('span').text('imagery'); - imagery.append('a').attr('target', '_blank') - .attr('href', 'http://opengeodata.org/microsoft-imagery-details').text(' provided by bing'); + imagery + .append('span') + .attr('class', 'provided-by'); linkList.append('li').attr('class', 'source-switch').append('a').attr('href', '#') .text('dev') diff --git a/js/id/ui/layerswitcher.js b/js/id/ui/layerswitcher.js index f788322b1..b73ede9d3 100644 --- a/js/id/ui/layerswitcher.js +++ b/js/id/ui/layerswitcher.js @@ -38,7 +38,7 @@ iD.ui.layerswitcher = function(context) { selection.on('click.layerswitcher-inside', function() { return d3.event.stopPropagation(); }); - d3.select('body').on('click.layerswitcher-outside', hide); + context.container().on('click.layerswitcher-outside', hide); } var opa = content @@ -76,16 +76,28 @@ iD.ui.layerswitcher = function(context) { .style('opacity', String); // Make sure there is an active selection by default - d3.select('.opacity-options li:nth-child(2)').classed('selected', true); + opa.select('.opacity-options li:nth-child(2)').classed('selected', true); function selectLayer(d) { + content.selectAll('a.layer') .classed('selected', function(d) { return d === context.background().source(); }); - d3.select('#attribution a') - .attr('href', d.data.link) - .text('provided by ' + d.data.name); + + var provided_by = context.container().select('#attribution .provided-by') + .html(''); + + if (d.data.terms_url) { + provided_by.append('a') + .attr('href', (d.data.terms_url || '')) + .classed('disabled', !d.data.terms_url) + .text(' provided by ' + (d.data.sourcetag || d.data.name)); + } else { + provided_by + .text(' provided by ' + (d.data.sourcetag || d.data.name)); + } + } function clickSetSource(d) { @@ -96,7 +108,7 @@ iD.ui.layerswitcher = function(context) { d = configured; } context.background().source(d); - context.history().imagery_used(d.name); + context.history().imagery_used(d.data.sourcetag || d.data.name); context.redraw(); selectLayer(d); } From 2ca4387f741164d2b205d87b0751aef65579c210 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Wed, 6 Feb 2013 17:42:51 -0500 Subject: [PATCH 222/415] Fix snapping nodes to areas In the future it may be good to use shadow paths, but at this point there isn't really a reason to add thousands of additional paths. --- js/id/behavior/drag_node.js | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/js/id/behavior/drag_node.js b/js/id/behavior/drag_node.js index acba7919d..e3028953d 100644 --- a/js/id/behavior/drag_node.js +++ b/js/id/behavior/drag_node.js @@ -84,7 +84,11 @@ iD.behavior.DragNode = function(context) { if (d.type === 'node' && d.id !== entity.id) { loc = d.loc; } else if (d.type === 'way') { - loc = iD.geo.chooseIndex(d, d3.mouse(context.surface().node()), context).loc; + var point = d3.mouse(context.surface().node()), + index = iD.geo.chooseIndex(d, point, context); + if (iD.geo.dist(point, context.projection(index.loc)) < 10) { + loc = index.loc; + } } context.replace(iD.actions.MoveNode(entity.id, loc)); @@ -100,13 +104,18 @@ iD.behavior.DragNode = function(context) { var d = datum(); if (d.type === 'way') { - var choice = iD.geo.chooseIndex(d, d3.mouse(context.surface().node()), context); - context.replace( - iD.actions.MoveNode(entity.id, choice.loc), - iD.actions.AddVertex(d.id, entity.id, choice.index), - connectAnnotation(d)); + var point = d3.mouse(context.surface().node()), + choice = iD.geo.chooseIndex(d, point, context); + if (iD.geo.dist(point, context.projection(choice.loc)) < 10) { + context.replace( + iD.actions.MoveNode(entity.id, choice.loc), + iD.actions.AddVertex(d.id, entity.id, choice.index), + connectAnnotation(d)); + return; + } + } - } else if (d.type === 'node' && d.id !== entity.id) { + if (d.type === 'node' && d.id !== entity.id) { context.replace( iD.actions.Connect([entity.id, d.id]), connectAnnotation(d)); From ce53a9233a09dd506fdced44c2ec0b430901a25d Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Wed, 6 Feb 2013 18:11:50 -0500 Subject: [PATCH 223/415] Remove unscoped d3.select --- js/id/ui/save.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/id/ui/save.js b/js/id/ui/save.js index 905a43b11..38bba8518 100644 --- a/js/id/ui/save.js +++ b/js/id/ui/save.js @@ -30,7 +30,7 @@ iD.ui.save = function(context) { function click() { function commit(e) { - d3.select('.shaded').remove(); + context.container().select('.shaded').remove(); var l = iD.ui.loading(context.container(), t('uploading_changes'), true); connection.putChangeset(history.changes(), From 246481ad9262ea2a71316fa55ebd04cf24bc071b Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Wed, 6 Feb 2013 18:53:46 -0500 Subject: [PATCH 224/415] Switch back to mouseup, and block following click --- js/id/behavior/draw.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/js/id/behavior/draw.js b/js/id/behavior/draw.js index b0da771a4..6b1b7ff0a 100644 --- a/js/id/behavior/draw.js +++ b/js/id/behavior/draw.js @@ -27,13 +27,19 @@ iD.behavior.Draw = function(context) { target.on('mousemove.draw', null); - d3.select(window).on('click.draw', function() { + d3.select(window).on('mouseup.draw', function() { target.on('mousemove.draw', mousemove); if (iD.geo.dist(pos, point()) < closeTolerance || (iD.geo.dist(pos, point()) < tolerance && (+new Date() - time) < 500)) { click(); } + if (target.node() === d3.event.target) { + d3.select(window).on('click.draw', function() { + d3.select(window).on('click.draw', null); + d3.event.stopPropagation(); + }); + } }); } @@ -110,7 +116,7 @@ iD.behavior.Draw = function(context) { .on('mousedown.draw', null) .on('mousemove.draw', null); - d3.select(window).on('click.draw', null); + d3.select(window).on('mouseup.draw', null); d3.select(document) .call(keybinding.off) From 0af51a0ef6f542ea1b4443a3809bfb14a792e966 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Wed, 6 Feb 2013 15:58:13 -0800 Subject: [PATCH 225/415] Improvements to Split * Split a closed way at selected and antipode point (fixes #651) * Split an area into a multipolygon (fixes #572) --- js/id/actions/split.js | 69 +++++++++++++++++++++++++++----------- test/spec/actions/split.js | 60 +++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+), 19 deletions(-) diff --git a/js/id/actions/split.js b/js/id/actions/split.js index 24f2b4318..ea2100238 100644 --- a/js/id/actions/split.js +++ b/js/id/actions/split.js @@ -15,37 +15,55 @@ iD.actions.Split = function(nodeId, newWayId) { parents = graph.parentWays(node); return parents.filter(function (parent) { - return parent.first() !== nodeId && - parent.last() !== nodeId; + return parent.isClosed() || + (parent.first() !== nodeId && + parent.last() !== nodeId); }); } var action = function(graph) { - if (!action.enabled(graph)) - return graph; + var wayA = candidateWays(graph)[0], + wayB = iD.Way({id: newWayId, tags: wayA.tags}), + nodesA, + nodesB, + isArea = wayA.isArea(); - var way = candidateWays(graph)[0], - idx = _.indexOf(way.nodes, nodeId); + if (wayA.isClosed()) { + var nodes = wayA.nodes.slice(0, -1), + idxA = _.indexOf(nodes, nodeId), + idxB = idxA + Math.floor(nodes.length / 2); - // Create a 'b' way that contains all of the tags in the second - // half of this way - var newWay = iD.Way({id: newWayId, tags: way.tags, nodes: way.nodes.slice(idx)}); - graph = graph.replace(newWay); + if (idxB >= nodes.length) { + idxB %= nodes.length; + nodesA = nodes.slice(idxA).concat(nodes.slice(0, idxB + 1)); + nodesB = nodes.slice(idxB, idxA + 1); + } else { + nodesA = nodes.slice(idxA, idxB + 1); + nodesB = nodes.slice(idxB).concat(nodes.slice(0, idxA + 1)); + } + } else { + var idx = _.indexOf(wayA.nodes, nodeId); + nodesA = wayA.nodes.slice(0, idx + 1); + nodesB = wayA.nodes.slice(idx); + } - // Reduce the original way to only contain the first set of nodes - graph = graph.replace(way.update({nodes: way.nodes.slice(0, idx + 1)})); + wayA = wayA.update({nodes: nodesA}); + wayB = wayB.update({nodes: nodesB}); - graph.parentRelations(way).forEach(function(relation) { + graph = graph.replace(wayA); + graph = graph.replace(wayB); + + graph.parentRelations(wayA).forEach(function(relation) { if (relation.isRestriction()) { var via = relation.memberByRole('via'); - if (via && newWay.contains(via.id)) { - relation = relation.updateMember({id: newWay.id}, relation.memberById(way.id).index); + if (via && wayB.contains(via.id)) { + relation = relation.updateMember({id: wayB.id}, relation.memberById(wayA.id).index); graph = graph.replace(relation); } } else { - var role = relation.memberById(way.id).role, - last = newWay.last(), - i = relation.memberById(way.id).index, + var role = relation.memberById(wayA.id).role, + last = wayB.last(), + i = relation.memberById(wayA.id).index, j; for (j = 0; j < relation.members.length; j++) { @@ -55,11 +73,24 @@ iD.actions.Split = function(nodeId, newWayId) { } } - relation = relation.addMember({id: newWay.id, type: 'way', role: role}, i <= j ? i + 1 : i); + relation = relation.addMember({id: wayB.id, type: 'wayA', role: role}, i <= j ? i + 1 : i); graph = graph.replace(relation); } }); + if (isArea) { + var multipolygon = iD.Relation({ + tags: _.extend({}, wayA.tags, {type: 'multipolygon'}), + members: [ + {id: wayA.id, role: 'outer', type: 'way'}, + {id: wayB.id, role: 'outer', type: 'way'} + ]}); + + graph = graph.replace(multipolygon); + graph = graph.replace(wayA.update({tags: {}})); + graph = graph.replace(wayB.update({tags: {}})); + } + return graph; }; diff --git a/test/spec/actions/split.js b/test/spec/actions/split.js index 5ee68e67e..417525941 100644 --- a/test/spec/actions/split.js +++ b/test/spec/actions/split.js @@ -99,6 +99,66 @@ describe("iD.actions.Split", function () { expect(graph.entity('|').nodes).to.eql(['d', 'b']); }); + it("splits a closed way at the given point and its antipode", function () { + // Situation: + // a ---- b + // | | + // d ---- c + // + // Split at a. + // + // Expected result: + // a ---- b + // || | + // d ==== c + // + var graph = iD.Graph({ + 'a': iD.Node({id: 'a'}), + 'b': iD.Node({id: 'b'}), + 'c': iD.Node({id: 'c'}), + 'd': iD.Node({id: 'd'}), + '-': iD.Way({id: '-', nodes: ['a', 'b', 'c', 'd', 'a']}) + }); + + var g1 = iD.actions.Split('a', '=')(graph); + expect(g1.entity('-').nodes).to.eql(['a', 'b', 'c']); + expect(g1.entity('=').nodes).to.eql(['c', 'd', 'a']); + + var g2 = iD.actions.Split('b', '=')(graph); + expect(g2.entity('-').nodes).to.eql(['b', 'c', 'd']); + expect(g2.entity('=').nodes).to.eql(['d', 'a', 'b']); + + var g3 = iD.actions.Split('c', '=')(graph); + expect(g3.entity('-').nodes).to.eql(['c', 'd', 'a']); + expect(g3.entity('=').nodes).to.eql(['a', 'b', 'c']); + + var g4 = iD.actions.Split('d', '=')(graph); + expect(g4.entity('-').nodes).to.eql(['d', 'a', 'b']); + expect(g4.entity('=').nodes).to.eql(['b', 'c', 'd']); + }); + + it("splits an area by converting it to a multipolygon", function () { + var graph = iD.Graph({ + 'a': iD.Node({id: 'a'}), + 'b': iD.Node({id: 'b'}), + 'c': iD.Node({id: 'c'}), + 'd': iD.Node({id: 'd'}), + '-': iD.Way({id: '-', tags: {building: 'yes'}, nodes: ['a', 'b', 'c', 'd', 'a']}) + }); + + graph = iD.actions.Split('a', '=')(graph); + expect(graph.entity('-').tags).to.eql({}); + expect(graph.entity('=').tags).to.eql({}); + expect(graph.parentRelations(graph.entity('-'))).to.have.length(1); + + var relation = graph.parentRelations(graph.entity('-'))[0]; + expect(relation.tags).to.eql({type: 'multipolygon', building: 'yes'}); + expect(relation.members).to.eql([ + {id: '-', role: 'outer', type: 'way'}, + {id: '=', role: 'outer', type: 'way'} + ]); + }); + it("adds the new way to parent relations (no connections)", function () { // Situation: // a ---- b ---- c From 4f6637d58ba85a677eafa245b9cc8af559f9e6c9 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Wed, 6 Feb 2013 19:11:28 -0500 Subject: [PATCH 226/415] Fix add_point tests (switch back to mouseup) --- test/spec/modes/add_point.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/spec/modes/add_point.js b/test/spec/modes/add_point.js index 5807a3c0b..62291f8ee 100644 --- a/test/spec/modes/add_point.js +++ b/test/spec/modes/add_point.js @@ -17,13 +17,13 @@ describe("iD.modes.AddPoint", function() { describe("clicking the map", function () { it("adds a node", function() { happen.mousedown(context.surface().node(), {}); - happen.click(window, {}); + happen.mouseup(window, {}); expect(context.changes().created).to.have.length(1); }); it("selects the node", function() { happen.mousedown(context.surface().node(), {}); - happen.click(window, {}); + happen.mouseup(window, {}); expect(context.mode().id).to.equal('select'); expect(context.mode().selection()).to.eql([context.changes().created[0].id]); }); From ec152716fba81566d1a0e28647d3cd30955d2c6a Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Thu, 7 Feb 2013 01:16:51 -0500 Subject: [PATCH 227/415] Fix calls to ui.flash --- js/id/renderer/map.js | 2 +- js/id/ui/geocoder.js | 2 +- js/id/ui/inspector.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index 5074abd92..946b29b70 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -106,7 +106,7 @@ iD.Map = function(context) { } if (Math.log(d3.event.scale / Math.LN2 - 8) < minzoom + 1) { - iD.ui.flash() + iD.ui.flash(context.container()) .select('.content') .text('Cannot zoom out further in current mode.'); return map.zoom(16); diff --git a/js/id/ui/geocoder.js b/js/id/ui/geocoder.js index ffb735305..8883dc934 100644 --- a/js/id/ui/geocoder.js +++ b/js/id/ui/geocoder.js @@ -12,7 +12,7 @@ iD.ui.geocoder = function() { if (err) return hide(); hide(); if (!resp.length) { - return iD.ui.flash() + return iD.ui.flash(context.container()) .select('.content') .append('h3') .text('No location found for "' + searchVal + '"'); diff --git a/js/id/ui/inspector.js b/js/id/ui/inspector.js index bf6099823..35e03d231 100644 --- a/js/id/ui/inspector.js +++ b/js/id/ui/inspector.js @@ -163,7 +163,7 @@ iD.ui.inspector = function() { }) .call(iD.keyReference(context)); } else { - iD.ui.flash() + iD.ui.flash(context.container()) .select('.content') .append('h3') .text(t('inspector.no_documentation_key')); From cf96055781e08033e94e920c0466b1e385343c30 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Thu, 7 Feb 2013 14:08:07 -0500 Subject: [PATCH 228/415] Fix scale calculation in orthogonalize --- js/id/actions/orthogonalize.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/id/actions/orthogonalize.js b/js/id/actions/orthogonalize.js index 068db55bd..b57b8ad0a 100644 --- a/js/id/actions/orthogonalize.js +++ b/js/id/actions/orthogonalize.js @@ -62,7 +62,7 @@ iD.actions.Orthogonalize = function(wayId, projection) { p = subtractPoints(a, b), q = subtractPoints(c, b); - var scale = p.length + q.length; + var scale = iD.geo.dist(p, [0, 0]) + iD.geo.dist(q, [0, 0]); p = normalizePoint(p, 1.0); q = normalizePoint(q, 1.0); From 9a2bafac1be3e5e2b8cee00bca226545445e2efb Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Thu, 7 Feb 2013 15:30:44 -0500 Subject: [PATCH 229/415] Power through less square iterations --- js/id/actions/orthogonalize.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/js/id/actions/orthogonalize.js b/js/id/actions/orthogonalize.js index b57b8ad0a..09711364f 100644 --- a/js/id/actions/orthogonalize.js +++ b/js/id/actions/orthogonalize.js @@ -1,9 +1,14 @@ +/* + * Based on https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/potlatch2/tools/Quadrilateralise.as + */ + iD.actions.Orthogonalize = function(wayId, projection) { var action = function(graph) { var way = graph.entity(wayId), nodes = graph.childNodes(way), points = nodes.map(function(n) { return projection(n.loc); }), - quad_nodes = [], i, j; + quad_nodes = [], + best, i, j; var score = squareness(); for (i = 0; i < 1000; i++) { @@ -12,14 +17,15 @@ iD.actions.Orthogonalize = function(wayId, projection) { points[j] = addPoints(points[j],motions[j]); } var newScore = squareness(); - if (newScore > score) { - return graph; + if (newScore < score) { + best = _.clone(points); + score = newScore; } - score = newScore; if (score < 1.0e-8) { break; } } + points = best; for (i = 0; i < points.length - 1; i++) { quad_nodes.push(iD.Node({ loc: projection.invert(points[i]) })); From b9d80538b1fa413914e94cbf04a02f4d6f39b029 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Thu, 7 Feb 2013 15:41:14 -0500 Subject: [PATCH 230/415] jshinting --- js/id/actions/merge.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/js/id/actions/merge.js b/js/id/actions/merge.js index 8ba2a2318..5b515bcc5 100644 --- a/js/id/actions/merge.js +++ b/js/id/actions/merge.js @@ -6,8 +6,8 @@ iD.actions.Merge = function(ids) { var action = function(graph) { var geometries = groupEntitiesByGeometry(graph), - area = geometries['area'][0], - points = geometries['point']; + area = geometries.area[0], + points = geometries.point; points.forEach(function (point) { area = area.mergeTags(point.tags); @@ -26,9 +26,9 @@ iD.actions.Merge = function(ids) { action.enabled = function(graph) { var geometries = groupEntitiesByGeometry(graph); - return geometries['area'].length === 1 && - geometries['point'].length > 0 && - (geometries['area'].length + geometries['point'].length) === ids.length; + return geometries.area.length === 1 && + geometries.point.length > 0 && + (geometries.area.length + geometries.point.length) === ids.length; }; return action; From 02d4b8f1aaa3ad67eaa3843a6fc21652a2cf93a6 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Thu, 7 Feb 2013 15:48:32 -0500 Subject: [PATCH 231/415] Remove unnecessary code from orthogonalize --- js/id/actions/orthogonalize.js | 31 +------------------------------ 1 file changed, 1 insertion(+), 30 deletions(-) diff --git a/js/id/actions/orthogonalize.js b/js/id/actions/orthogonalize.js index 09711364f..ada281a95 100644 --- a/js/id/actions/orthogonalize.js +++ b/js/id/actions/orthogonalize.js @@ -28,36 +28,7 @@ iD.actions.Orthogonalize = function(wayId, projection) { points = best; for (i = 0; i < points.length - 1; i++) { - quad_nodes.push(iD.Node({ loc: projection.invert(points[i]) })); - } - - for (i = 0; i < nodes.length; i++) { - if (graph.parentWays(nodes[i]).length > 1) { - var closest, closest_dist = Infinity, dist; - for (j = 0; j < quad_nodes.length; j++) { - dist = iD.geo.dist(quad_nodes[j].loc, nodes[i].loc); - if (dist < closest_dist) { - closest_dist = dist; - closest = j; - } - } - quad_nodes.splice(closest, 1, nodes[i]); - } - } - - for (i = 0; i < quad_nodes.length; i++) { - graph = graph.replace(quad_nodes[i]); - } - - var ids = _.pluck(quad_nodes, 'id'), - difference = _.difference(_.uniq(way.nodes), ids); - - ids.push(ids[0]); - - graph = graph.replace(way.update({nodes: ids})); - - for (i = 0; i < difference.length; i++) { - graph = iD.actions.DeleteNode(difference[i])(graph); + graph = graph.replace(graph.entity(nodes[i].id).move(projection.invert(points[i]))); } return graph; From f0b761b97923cf4dc3100443ce03a64b24f66c83 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Thu, 7 Feb 2013 16:21:25 -0500 Subject: [PATCH 232/415] Lasso action --- css/app.css | 11 ++++--- index.html | 2 ++ js/id/behavior/lasso.js | 57 +++++++++++++++++++++++++++++++++++++ js/id/modes/browse.js | 1 + js/id/ui/lasso.js | 63 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 130 insertions(+), 4 deletions(-) create mode 100644 js/id/behavior/lasso.js create mode 100644 js/id/ui/lasso.js diff --git a/css/app.css b/css/app.css index 65f98dfd6..99904d953 100644 --- a/css/app.css +++ b/css/app.css @@ -1398,6 +1398,13 @@ a.success-action { border-radius: 4px; } +.lasso-box { + fill-opacity:0.2; + fill: #bde5aa; + stroke: #000; + stroke-width: 1; +} + /* Media Queries ------------------------------------------------------- */ @@ -1408,10 +1415,6 @@ a.success-action { .save .label, .apply .label, .cancel .label { display: block;} } - - - - div.combobox { width:155px; z-index: 9999; diff --git a/index.html b/index.html index 9f4c23757..31f06870a 100644 --- a/index.html +++ b/index.html @@ -75,6 +75,7 @@ + @@ -102,6 +103,7 @@ + diff --git a/js/id/behavior/lasso.js b/js/id/behavior/lasso.js new file mode 100644 index 000000000..08dda8cd3 --- /dev/null +++ b/js/id/behavior/lasso.js @@ -0,0 +1,57 @@ +iD.behavior.Lasso = function(context) { + + var behavior = function(selection) { + + var timeout = null, + // the position of the first mousedown + pos = null, + lasso; + + function mousedown() { + if (d3.event.shiftKey === true) { + + lasso = iD.ui.lasso().a(d3.mouse(context.surface().node())); + + context.surface().call(lasso); + + selection + .on('mousemove.lasso', mousemove) + .on('mouseup.lasso', mouseup); + + d3.event.stopPropagation(); + d3.event.preventDefault(); + + } + } + + function mousemove() { + lasso.b(d3.mouse(context.surface().node())); + } + + function mouseup() { + var extent = iD.geo.Extent( + context.projection.invert(lasso.a()), + context.projection.invert(lasso.b())); + + lasso.close(); + + var selected = context.graph().intersects(extent); + + if (selected.length) { + context.enter(iD.modes.Select(context, _.pluck(selected, 'id'))); + } + + selection + .on('mousemove.lasso', null); + } + + selection + .on('mousedown.select', mousedown); + }; + + behavior.off = function(selection) { + selection.on('mousedown.lasso', null); + }; + + return behavior; +}; diff --git a/js/id/modes/browse.js b/js/id/modes/browse.js index 97f0d6935..5df94d69d 100644 --- a/js/id/modes/browse.js +++ b/js/id/modes/browse.js @@ -10,6 +10,7 @@ iD.modes.Browse = function(context) { var behaviors = [ iD.behavior.Hover(), iD.behavior.Select(context), + iD.behavior.Lasso(context), iD.behavior.DragNode(context)]; mode.enter = function() { diff --git a/js/id/ui/lasso.js b/js/id/ui/lasso.js new file mode 100644 index 000000000..1ca079fda --- /dev/null +++ b/js/id/ui/lasso.js @@ -0,0 +1,63 @@ +iD.ui.lasso = function() { + + var center, box, + group, + a = [0, 0], + b = [0, 0]; + + function lasso(selection) { + + group = selection.append('g') + .attr('class', 'lasso') + .attr('opacity', 0); + + box = group.append('rect') + .attr('class', 'lasso-box'); + + group.transition() + .style('opacity', 1); + + } + + // top-left + function topLeft(d) { + return 'translate(' + + [Math.min(d[0][0], d[1][0]), Math.min(d[0][1], d[1][1])] + ')'; + } + + function width(d) { return Math.abs(d[0][0] - d[1][0]); } + function height(d) { return Math.abs(d[0][1] - d[1][1]); } + + function draw() { + if (box) { + box.data([[a, b]]) + .attr('transform', topLeft) + .attr('width', width) + .attr('height', height); + } + } + + lasso.a = function(_) { + if (!arguments.length) return a; + a = _; + draw(); + return lasso; + }; + + lasso.b = function(_) { + if (!arguments.length) return b; + b = _; + draw(); + return lasso; + }; + + lasso.close = function(selection) { + if (group) { + group.transition() + .attr('opacity', 0) + .remove(); + } + }; + + return lasso; +}; From 843926009de92595a0ac996927fd47aea3c549b6 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Thu, 7 Feb 2013 16:32:03 -0500 Subject: [PATCH 233/415] Normalize coordinates so that lasso can be dragged in any direction --- js/id/behavior/lasso.js | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/js/id/behavior/lasso.js b/js/id/behavior/lasso.js index 08dda8cd3..3986b5cf2 100644 --- a/js/id/behavior/lasso.js +++ b/js/id/behavior/lasso.js @@ -28,25 +28,32 @@ iD.behavior.Lasso = function(context) { lasso.b(d3.mouse(context.surface().node())); } + function normalize(a, b) { + return [ + [Math.min(a[0], b[0]), Math.min(a[1], b[1])], + [Math.max(a[0], b[0]), Math.max(a[1], b[1])]]; + } + function mouseup() { var extent = iD.geo.Extent( - context.projection.invert(lasso.a()), - context.projection.invert(lasso.b())); + normalize(context.projection.invert(lasso.a()), + context.projection.invert(lasso.b()))); lasso.close(); var selected = context.graph().intersects(extent); + selection + .on('mousemove.lasso', null) + .on('mouseup.lasso', null); + if (selected.length) { context.enter(iD.modes.Select(context, _.pluck(selected, 'id'))); } - - selection - .on('mousemove.lasso', null); } selection - .on('mousedown.select', mousedown); + .on('mousedown.lasso', mousedown); }; behavior.off = function(selection) { From edc424367344ebb5f0c21628929c02c660a494ce Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Thu, 7 Feb 2013 16:36:07 -0500 Subject: [PATCH 234/415] Add includes to test, fixes tests --- test/index.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/index.html b/test/index.html index 7b90f7c09..1e5975595 100644 --- a/test/index.html +++ b/test/index.html @@ -67,6 +67,7 @@ + @@ -101,6 +102,7 @@ + From 5cad057212a2a93d76b3f144ef8370c289577255 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 7 Feb 2013 13:36:19 -0800 Subject: [PATCH 235/415] Clear selection on esc (fixes #643) --- js/id/modes/select.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/js/id/modes/select.js b/js/id/modes/select.js index 0bcfa8c0b..95eb3e78a 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -42,6 +42,10 @@ iD.modes.Select = function(context, selection, initial) { .filter(function(o) { return o.available(); }); operations.unshift(iD.operations.Delete(selection, context)); + keybinding.on('⎋', function() { + context.enter(iD.modes.Browse(context)); + }); + operations.forEach(function(operation) { keybinding.on(operation.key, function() { if (operation.enabled()) { From e1bc78871b04aa5a5a6800f2204b808908c1331c Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 7 Feb 2013 13:46:06 -0800 Subject: [PATCH 236/415] Handle entities that are already deleted (fixes #672) --- js/id/actions/delete_multiple.js | 5 ++++- test/spec/actions/delete_multiple.js | 9 +++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/js/id/actions/delete_multiple.js b/js/id/actions/delete_multiple.js index 839c1d780..a33dba4de 100644 --- a/js/id/actions/delete_multiple.js +++ b/js/id/actions/delete_multiple.js @@ -7,7 +7,10 @@ iD.actions.DeleteMultiple = function(ids) { }; ids.forEach(function (id) { - graph = actions[graph.entity(id).type](id)(graph); + var entity = graph.entity(id); + if (entity) { // It may have been deleted aready. + graph = actions[entity.type](id)(graph); + } }); return graph; diff --git a/test/spec/actions/delete_multiple.js b/test/spec/actions/delete_multiple.js index 3a70adc38..85329b7a8 100644 --- a/test/spec/actions/delete_multiple.js +++ b/test/spec/actions/delete_multiple.js @@ -9,4 +9,13 @@ describe("iD.actions.DeleteMultiple", function () { expect(graph.entity(w.id)).to.be.undefined; expect(graph.entity(r.id)).to.be.undefined; }); + + it("deletes a way and one of its nodes", function () { + var n = iD.Node(), + w = iD.Way({nodes: [n.id]}), + action = iD.actions.DeleteMultiple([w.id, n.id]), + graph = action(iD.Graph([n, w])); + expect(graph.entity(w.id)).to.be.undefined; + expect(graph.entity(n.id)).to.be.undefined; + }); }); From 425138e21db39acfae9a2b978b775cdb30ed417a Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Thu, 7 Feb 2013 16:55:06 -0500 Subject: [PATCH 237/415] Remove discard-tags include --- index.html | 1 - 1 file changed, 1 deletion(-) diff --git a/index.html b/index.html index 8eca2fae7..37ff61404 100644 --- a/index.html +++ b/index.html @@ -98,7 +98,6 @@ - From 5e328435e6ad036a91e99196b95570e2d41a1bf2 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Thu, 7 Feb 2013 16:58:38 -0500 Subject: [PATCH 238/415] jshint --- js/id/actions/join.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/id/actions/join.js b/js/id/actions/join.js index 267b6c75a..feccfebaf 100644 --- a/js/id/actions/join.js +++ b/js/id/actions/join.js @@ -51,7 +51,7 @@ iD.actions.Join = function(ids) { graph = graph.replace(parent.replaceMember(b, a)); }); - graph = graph.replace(a.mergeTags(b.tags).update({nodes: nodes})); + graph = graph.replace(a.mergeTags(b.tags).update({ nodes: nodes })); graph = iD.actions.DeleteWay(idB)(graph); return graph; @@ -60,7 +60,7 @@ iD.actions.Join = function(ids) { action.enabled = function(graph) { var geometries = groupEntitiesByGeometry(graph); - if (ids.length !== 2 || ids.length !== geometries['line'].length) + if (ids.length !== 2 || ids.length !== geometries.line.length) return false; var a = graph.entity(idA), From 3626534a704132ba39d03b00cce276127cef5d3d Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Thu, 7 Feb 2013 17:10:31 -0500 Subject: [PATCH 239/415] Remove deprecate tags test --- test/index.html | 1 - test/spec/actions/deprecate_tags.js | 47 ----------------------------- 2 files changed, 48 deletions(-) delete mode 100644 test/spec/actions/deprecate_tags.js diff --git a/test/index.html b/test/index.html index 1e5975595..7f5d2a878 100644 --- a/test/index.html +++ b/test/index.html @@ -91,7 +91,6 @@ - diff --git a/test/spec/actions/deprecate_tags.js b/test/spec/actions/deprecate_tags.js deleted file mode 100644 index 61d1da4fb..000000000 --- a/test/spec/actions/deprecate_tags.js +++ /dev/null @@ -1,47 +0,0 @@ -describe('iD.actions.DeprecateTags', function () { - it('deprecates tags', function () { - var entity = iD.Entity({ tags: { barrier: 'wire_fence' } }), - graph = iD.actions.DeprecateTags(entity.id)(iD.Graph([entity])), - undeprecated = { - barrier: 'fence', - fence_type: 'chain' - }; - expect(graph.entity(entity.id).tags).to.eql(undeprecated); - }); - - it('does not overwrite explicit tags', function () { - var entity = iD.Entity({ tags: { barrier: 'wire_fence', fence_type: 'foo' } }), - graph = iD.actions.DeprecateTags(entity.id)(iD.Graph([entity])), - undeprecated = { - barrier: 'fence', - fence_type: 'foo' - }; - expect(graph.entity(entity.id).tags).to.eql(undeprecated); - }); - - it('leaves other tags alone', function () { - var entity = iD.Entity({ tags: { highway: 'ford', name: 'Foo' } }), - graph = iD.actions.DeprecateTags(entity.id)(iD.Graph([entity])), - undeprecated = { - ford: 'yes', - name: 'Foo' - }; - expect(graph.entity(entity.id).tags).to.eql(undeprecated); - }); - - it('wipes out tags that should be entirely removed', function () { - var entity = iD.Entity({ tags: { 'tiger:source': 'foo' } }), - graph = iD.actions.DeprecateTags(entity.id)(iD.Graph([entity])), - undeprecated = { }; - expect(graph.entity(entity.id).tags).to.eql(undeprecated); - }); - - it('replaces keys', function () { - var entity = iD.Entity({ tags: { power_rating: '1 billion volts' } }), - graph = iD.actions.DeprecateTags(entity.id)(iD.Graph([entity])), - undeprecated = { - 'generator:output': '1 billion volts' - }; - expect(graph.entity(entity.id).tags).to.eql(undeprecated); - }); -}); From 57493016b6ca60a737220bcfd5d1ca2662c8562b Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Thu, 7 Feb 2013 17:17:24 -0500 Subject: [PATCH 240/415] Remove ref to 404 test, do not discard tags from empty tags --- js/id/graph/history.js | 10 +++++++--- test/index.html | 1 - 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/js/id/graph/history.js b/js/id/graph/history.js index 04d52f6ab..5f648b434 100644 --- a/js/id/graph/history.js +++ b/js/id/graph/history.js @@ -134,9 +134,13 @@ iD.History = function(context) { var difference = history.difference(); function discardTags(entity) { - return entity.update({ - tags: _.omit(entity.tags, iD.data.discarded) - }); + if (_.isEmpty(entity.tags)) { + return entity; + } else { + return entity.update({ + tags: _.omit(entity.tags, iD.data.discarded) + }); + } } return { diff --git a/test/index.html b/test/index.html index 7f5d2a878..8fb7da652 100644 --- a/test/index.html +++ b/test/index.html @@ -159,7 +159,6 @@ - From ec065dca4c8e05c790d0a58a0dbc9d48df86742d Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Thu, 7 Feb 2013 17:18:44 -0500 Subject: [PATCH 241/415] Fix flashing, reset transform on mouseup --- js/id/renderer/map.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index 946b29b70..5bdeebce4 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -42,6 +42,9 @@ iD.Map = function(context) { d3.event.stopPropagation(); } }, true) + .on('mouseup.zoom', function() { + if (resetTransform) redraw(); + }) .attr('id', 'surface') .call(iD.svg.Surface()); From 887151c9febd31f91f776c72dde08238a0797000 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Thu, 7 Feb 2013 17:32:26 -0500 Subject: [PATCH 242/415] Make all non-dashed keys into keys --- locale/en.js | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/locale/en.js b/locale/en.js index b92dc75ee..6c4f8bc2b 100644 --- a/locale/en.js +++ b/locale/en.js @@ -140,13 +140,13 @@ locale.en = { deprecated_tags: "Deprecated tags: {tags}" }, - "save": "Save", - "save_help": "Save changes to OpenStreetMap, making them visible to other users", - "no_changes": "You don't have any changes to save.", - "save_error": "An error occurred while trying to save", - "uploading_changes": "Uploading changes to OpenStreetMap.", - "just_edited": "You Just Edited OpenStreetMap!", - "okay": "Okay", + save: "Save", + save_help: "Save changes to OpenStreetMap, making them visible to other users", + no_changes: "You don't have any changes to save.", + save_error: "An error occurred while trying to save", + uploading_changes: "Uploading changes to OpenStreetMap.", + just_edited: "You Just Edited OpenStreetMap!", + okay: "Okay", "zoom-in": "Zoom In", "zoom-out": "Zoom Out", @@ -154,7 +154,7 @@ locale.en = { nothing_to_undo: "Nothing to undo.", nothing_to_redo: "Nothing to redo.", - "browser_notice": "This editor is supported in Firefox, Chrome, Safari, Opera, and Internet Explorer 9 and above. Please upgrade your browser or use Potlatch 2 to edit the map.", + browser_notice: "This editor is supported in Firefox, Chrome, Safari, Opera, and Internet Explorer 9 and above. Please upgrade your browser or use Potlatch 2 to edit the map.", inspector: { no_documentation_combination: "This is no documentation available for this tag combination", @@ -162,20 +162,20 @@ locale.en = { new_tag: "New Tag" }, - "view_on_osm": "View on OSM", + view_on_osm: "View on OSM", - "zoom_in_edit": "zoom in to edit the map", + zoom_in_edit: "zoom in to edit the map", - "edit_tags": "Edit tags", + edit_tags: "Edit tags", geocoder: { "find_location": "Find A Location", "find_a_place": "find a place" }, - "description": "Description", + description: "Description", - "logout": "logout", + logout: "logout", layerswitcher: { title: "Background", From 28c541067bc74baed7bc0689540dba265e047863 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Thu, 7 Feb 2013 17:58:28 -0500 Subject: [PATCH 243/415] Fix #680, fix use of imagery scaleExtents --- js/id/renderer/background.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/id/renderer/background.js b/js/id/renderer/background.js index 1f0834ed1..badafa18d 100644 --- a/js/id/renderer/background.js +++ b/js/id/renderer/background.js @@ -63,7 +63,7 @@ iD.Background = function() { var sel = this, tiles = tile .scale(projection.scale()) - .scaleExtent(source.scaleExtent || [1, 17]) + .scaleExtent(source.data.scaleExtent || [1, 17]) .translate(projection.translate())(), requests = [], scaleExtent = tile.scaleExtent(), From ad5c1df2285510fc211c9015df1e9d968f656ffb Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Thu, 7 Feb 2013 17:59:31 -0500 Subject: [PATCH 244/415] Detect support as well as os and language --- js/id/id.js | 33 +++++++++++++++++++++++---------- js/id/ui.js | 2 +- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/js/id/id.js b/js/id/id.js index e83c442c4..e6396b491 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -102,16 +102,29 @@ window.iD = function () { iD.version = '0.0.0-alpha1'; -iD.supported = function() { - if (navigator.appName !== 'Microsoft Internet Explorer') { - return true; +iD.detect = function() { + var browser = {}; + + var ua = navigator.userAgent, + msie = new RegExp("MSIE ([0-9]{1,}[\\.0-9]{0,})"); + + if (msie.exec(ua) !== null) { + var rv = parseFloat(RegExp.$1); + browser.support = !(rv && rv < 9); } else { - var ua = navigator.userAgent; - var re = new RegExp("MSIE ([0-9]{1,}[\\.0-9]{0,})"); - if (re.exec(ua) !== null) { - rv = parseFloat( RegExp.$1 ); - } - if (rv && rv < 9) return false; - else return true; + browser.support = true; } + + browser.locale = navigator.language; + + function nav(x) { + return navigator.userAgent.indexOf(x) !== -1; + } + + if (nav('Win')) browser.os = 'win'; + else if (nav('Mac')) browser.os = 'mac'; + else if (nav('X11')) browser.os = 'linux'; + else if (nav('Linux')) browser.os = 'linux'; + + return browser; }; diff --git a/js/id/ui.js b/js/id/ui.js index 768d95813..37874b25f 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -6,7 +6,7 @@ iD.ui = function(context) { history = context.history(), map = context.map(); - if (!iD.supported()) { + if (!iD.detect().support) { container.html('This editor is supported in Firefox, Chrome, Safari, Opera, ' + 'and Internet Explorer 9 and above. Please upgrade your browser ' + 'or use Potlatch 2 to edit the map.') From 645dcebbfe242850a1d1b761b25481ce8e2ff692 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Thu, 7 Feb 2013 18:09:28 -0500 Subject: [PATCH 245/415] Use platform-specific mod keys, refs #676 --- js/id/id.js | 1 + js/id/ui.js | 10 ++++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/js/id/id.js b/js/id/id.js index e6396b491..f1d7d853d 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -125,6 +125,7 @@ iD.detect = function() { else if (nav('Mac')) browser.os = 'mac'; else if (nav('X11')) browser.os = 'linux'; else if (nav('Linux')) browser.os = 'linux'; + else browser.os = 'win'; return browser; }; diff --git a/js/id/ui.js b/js/id/ui.js index 37874b25f..f18e0ef0c 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -210,14 +210,20 @@ iD.ui = function(context) { } } + var mod = { + 'mac': '⌘', + 'win': 'Ctrl', + 'linux': 'Ctrl' + }[iD.detect().os]; + limiter.select('#undo') .classed('disabled', !undo) - .attr('data-original-title', hintprefix('⌘ + Z', undo || t('nothing_to_undo'))) + .attr('data-original-title', hintprefix(mod + ' + Z', undo || t('nothing_to_undo'))) .call(refreshTooltip); limiter.select('#redo') .classed('disabled', !redo) - .attr('data-original-title', hintprefix('⌘ + ⇧ + Z', redo || t('nothing_to_redo'))) + .attr('data-original-title', hintprefix(mod + ' + ⇧ + Z', redo || t('nothing_to_redo'))) .call(refreshTooltip); }); From 27e7df888d7bcaa67da6a589e79e26dc53a40887 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Thu, 7 Feb 2013 18:24:01 -0500 Subject: [PATCH 246/415] Support locales in taginfo. Fixes #678 --- js/id/ui/inspector.js | 109 ++++++++++++++++++++++++++---------------- 1 file changed, 68 insertions(+), 41 deletions(-) diff --git a/js/id/ui/inspector.js b/js/id/ui/inspector.js index 35e03d231..fd29e08a5 100644 --- a/js/id/ui/inspector.js +++ b/js/id/ui/inspector.js @@ -119,6 +119,72 @@ iD.ui.inspector = function() { removeBtn.append('span') .attr('class', 'icon delete'); + function findLocal(docs) { + var locale = iD.detect().locale.toLowerCase(), + localized; + + localized = _.find(docs, function(d) { + return d.lang.toLowerCase() === locale; + }); + if (localized) return localized; + + // try the non-regional version of a language, like + // 'en' if the language is 'en-US' + if (locale.indexOf('-') !== -1) { + var first = locale.split('-')[0]; + localized = _.find(docs, function(d) { + return d.lang.toLowerCase() === first; + }); + if (localized) return localized; + } + + // finally fall back to english + return _.find(docs, function(d) { + return d.lang.toLowerCase() === 'en'; + }); + } + + function keyValueReference(err, docs) { + var local; + if (!err && docs) { + local = findLocal(docs); + } + if (local) { + var types = []; + if (local.on_area) types.push('area'); + if (local.on_node) types.push('point'); + if (local.on_way) types.push('line'); + local.types = types; + iD.ui.modal(context.container()) + .select('.content') + .datum(local) + .call(iD.ui.tagReference); + } else { + iD.ui.flash(context.container()) + .select('.content') + .append('h3') + .text(t('inspector.no_documentation_combination')); + } + } + + function keyReference(err, values) { + if (!err && values.data.length) { + iD.ui.modal(context.container()) + .select('.content') + .datum({ + data: values.data, + title: 'Key:' + params.key, + geometry: params.geometry + }) + .call(iD.keyReference(context)); + } else { + iD.ui.flash(context.container()) + .select('.content') + .append('h3') + .text(t('inspector.no_documentation_key')); + } + } + var helpBtn = row.append('button') .attr('tabindex', -1) .attr('class', 'tag-help minor') @@ -127,48 +193,9 @@ iD.ui.inspector = function() { geometry: entity.geometry(context.graph()) }); if (d.key && d.value) { - taginfo.docs(params, function(err, docs) { - var en; - if (!err && docs) { - en = _.find(docs, function(d) { - return d.lang == 'en'; - }); - } - if (en) { - var types = []; - if (en.on_area) types.push('area'); - if (en.on_node) types.push('point'); - if (en.on_way) types.push('line'); - en.types = types; - iD.ui.modal(context.container()) - .select('.content') - .datum(en) - .call(iD.ui.tagReference); - } else { - iD.ui.flash(context.container()) - .select('.content') - .append('h3') - .text(t('inspector.no_documentation_combination')); - } - }); + taginfo.docs(params, keyValueReference); } else if (d.key) { - taginfo.values(params, function(err, values) { - if (!err && values.data.length) { - iD.ui.modal(context.container()) - .select('.content') - .datum({ - data: values.data, - title: 'Key:' + params.key, - geometry: params.geometry - }) - .call(iD.keyReference(context)); - } else { - iD.ui.flash(context.container()) - .select('.content') - .append('h3') - .text(t('inspector.no_documentation_key')); - } - }); + taginfo.values(params, keyReference); } }); From 01f2463ed5483adb7c6d751749ad718d64c75236 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Thu, 7 Feb 2013 18:31:05 -0500 Subject: [PATCH 247/415] Support other locales based on browser language --- index.html | 5 ++++- locale/locale.js | 10 ++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/index.html b/index.html index 37ff61404..4dc3b373f 100644 --- a/index.html +++ b/index.html @@ -147,7 +147,10 @@
+ diff --git a/test/index_packaged.html b/test/index_packaged.html index 778231edd..1102336d9 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -26,6 +26,7 @@ + diff --git a/test/spec/actions/circularize.js b/test/spec/actions/circularize.js new file mode 100644 index 000000000..8215c2253 --- /dev/null +++ b/test/spec/actions/circularize.js @@ -0,0 +1,61 @@ +describe("iD.actions.Circularize", function () { + var projection = d3.geo.mercator(); + + it("creates a circle of 12 nodes", function () { + var graph = iD.Graph({ + 'a': iD.Node({id: 'a', loc: [0, 0]}), + 'b': iD.Node({id: 'b', loc: [2, 0]}), + 'c': iD.Node({id: 'c', loc: [2, 2]}), + 'd': iD.Node({id: 'd', loc: [0, 2]}), + '-': iD.Way({id: '-', nodes: ['a', 'b', 'c', 'd', 'a']}) + }); + + graph = iD.actions.Circularize('-', projection)(graph); + + expect(graph.entity('-').nodes).to.have.length(13); + }); + + it("reuses existing nodes", function () { + var graph = iD.Graph({ + 'a': iD.Node({id: 'a', loc: [0, 0]}), + 'b': iD.Node({id: 'b', loc: [2, 0]}), + 'c': iD.Node({id: 'c', loc: [2, 2]}), + 'd': iD.Node({id: 'd', loc: [0, 2]}), + '-': iD.Way({id: '-', nodes: ['a', 'b', 'c', 'd', 'a']}) + }); + + graph = iD.actions.Circularize('-', projection)(graph); + + expect(graph.entity('-').nodes.slice(0, 4)).to.eql(['c', 'b', 'a', 'd']); + }); + + it("deletes unused nodes that are not members of other ways", function () { + var graph = iD.Graph({ + 'a': iD.Node({id: 'a', loc: [0, 0]}), + 'b': iD.Node({id: 'b', loc: [2, 0]}), + 'c': iD.Node({id: 'c', loc: [2, 2]}), + 'd': iD.Node({id: 'd', loc: [0, 2]}), + '-': iD.Way({id: '-', nodes: ['a', 'b', 'c', 'd', 'a']}) + }); + + graph = iD.actions.Circularize('-', projection, 3)(graph); + + expect(graph.entity('d')).to.be.undefined; + }); + + it("reconnects unused nodes that are members of other ways", function () { + var graph = iD.Graph({ + 'a': iD.Node({id: 'a', loc: [0, 0]}), + 'b': iD.Node({id: 'b', loc: [2, 0]}), + 'c': iD.Node({id: 'c', loc: [2, 2]}), + 'd': iD.Node({id: 'd', loc: [0, 2]}), + '-': iD.Way({id: '-', nodes: ['a', 'b', 'c', 'd', 'a']}), + '=': iD.Way({id: '=', nodes: ['d']}) + }); + + graph = iD.actions.Circularize('-', projection, 3)(graph); + + expect(graph.entity('d')).to.be.undefined; + expect(graph.entity('=').nodes).to.eql(['c']); + }); +}); From 123c700853d4a0ee10015010d9042398aa54eb32 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Fri, 8 Feb 2013 16:49:09 -0500 Subject: [PATCH 268/415] Fix midpoint dragging --- js/id/behavior/select.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/id/behavior/select.js b/js/id/behavior/select.js index 32e246e98..3f7961974 100644 --- a/js/id/behavior/select.js +++ b/js/id/behavior/select.js @@ -22,7 +22,7 @@ iD.behavior.Select = function(context) { function mousedown() { var datum = d3.event.target.__data__; - if (datum instanceof iD.Entity) { + if (datum instanceof iD.Entity || (datum && datum.type === 'midpoint')) { pos = [d3.event.x, d3.event.y]; selection .on('mousemove.select', mousemove) From ba47d3183dc9744314a39c0ddd822dacddb7c174 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Fri, 8 Feb 2013 17:46:37 -0500 Subject: [PATCH 269/415] Fix lasso and add it to select --- js/id/behavior/select.js | 8 +++++--- js/id/modes/select.js | 1 + test/spec/behavior/select.js | 1 + 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/js/id/behavior/select.js b/js/id/behavior/select.js index 3f7961974..9d30236bb 100644 --- a/js/id/behavior/select.js +++ b/js/id/behavior/select.js @@ -22,8 +22,8 @@ iD.behavior.Select = function(context) { function mousedown() { var datum = d3.event.target.__data__; + pos = [d3.event.x, d3.event.y]; if (datum instanceof iD.Entity || (datum && datum.type === 'midpoint')) { - pos = [d3.event.x, d3.event.y]; selection .on('mousemove.select', mousemove) .on('touchmove.select', mousemove); @@ -46,8 +46,6 @@ iD.behavior.Select = function(context) { // save the event for the click handler })(d3.event), 200); } - } else { - context.enter(iD.modes.Browse(context)); } } @@ -61,6 +59,10 @@ iD.behavior.Select = function(context) { function mouseup() { selection.on('mousemove.select', null); + if (d3.event.x === pos[0] && d3.event.y === pos[1] && + !(d3.event.target.__data__ instanceof iD.Entity)) { + context.enter(iD.modes.Browse(context)); + } } selection diff --git a/js/id/modes/select.js b/js/id/modes/select.js index 95eb3e78a..5732b58df 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -9,6 +9,7 @@ iD.modes.Select = function(context, selection, initial) { behaviors = [ iD.behavior.Hover(), iD.behavior.Select(context), + iD.behavior.Lasso(context), iD.behavior.DragNode(context)], radialMenu; diff --git a/test/spec/behavior/select.js b/test/spec/behavior/select.js index cfb7b95b1..5e9bf9ea3 100644 --- a/test/spec/behavior/select.js +++ b/test/spec/behavior/select.js @@ -42,6 +42,7 @@ describe("iD.behavior.Select", function() { context.enter(iD.modes.Select(context, [a.id])); happen.click(context.surface().node()); happen.mousedown(context.surface().node()); + happen.mouseup(context.surface().node()); window.setTimeout(function() { expect(context.selection()).to.eql([]); done(); From 5b646b75c322124f122a2508a246ceec9ac7944a Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Fri, 8 Feb 2013 17:53:43 -0500 Subject: [PATCH 270/415] Only lasso if mouse has moved --- js/id/behavior/lasso.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/js/id/behavior/lasso.js b/js/id/behavior/lasso.js index 3986b5cf2..e99b1bf14 100644 --- a/js/id/behavior/lasso.js +++ b/js/id/behavior/lasso.js @@ -10,6 +10,8 @@ iD.behavior.Lasso = function(context) { function mousedown() { if (d3.event.shiftKey === true) { + pos = [d3.event.x, d3.event.y]; + lasso = iD.ui.lasso().a(d3.mouse(context.surface().node())); context.surface().call(lasso); @@ -35,20 +37,23 @@ iD.behavior.Lasso = function(context) { } function mouseup() { + var extent = iD.geo.Extent( normalize(context.projection.invert(lasso.a()), context.projection.invert(lasso.b()))); lasso.close(); - var selected = context.graph().intersects(extent); - selection .on('mousemove.lasso', null) .on('mouseup.lasso', null); - if (selected.length) { - context.enter(iD.modes.Select(context, _.pluck(selected, 'id'))); + if (d3.event.x !== pos[0] || d3.event.y !== pos[1]) { + var selected = context.graph().intersects(extent); + + if (selected.length) { + context.enter(iD.modes.Select(context, _.pluck(selected, 'id'))); + } } } From 334cb9f93b660a204b826eb6d1b3f0244793dc84 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Fri, 8 Feb 2013 18:01:45 -0500 Subject: [PATCH 271/415] Fix error, pos could be null --- js/id/behavior/select.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/id/behavior/select.js b/js/id/behavior/select.js index 9d30236bb..4d0bef2de 100644 --- a/js/id/behavior/select.js +++ b/js/id/behavior/select.js @@ -59,7 +59,7 @@ iD.behavior.Select = function(context) { function mouseup() { selection.on('mousemove.select', null); - if (d3.event.x === pos[0] && d3.event.y === pos[1] && + if (pos && d3.event.x === pos[0] && d3.event.y === pos[1] && !(d3.event.target.__data__ instanceof iD.Entity)) { context.enter(iD.modes.Browse(context)); } From f4a6edb224edf0b43a70b524b59ccd11dfeeaaa7 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Fri, 8 Feb 2013 18:25:39 -0500 Subject: [PATCH 272/415] Localize labels --- js/id/svg/labels.js | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/js/id/svg/labels.js b/js/id/svg/labels.js index dd26072f8..507f4cbdd 100644 --- a/js/id/svg/labels.js +++ b/js/id/svg/labels.js @@ -82,7 +82,7 @@ iD.svg.Labels = function(projection) { 'startOffset': '50%', 'xlink:href': function(d, i) { return '#halo-' + d.id; } }) - .text(function(d, i) { return d.tags.name; }); + .text(function(d, i) { return name(d); }); texts.exit().remove(); @@ -119,14 +119,14 @@ iD.svg.Labels = function(projection) { 'x': function(d, i) { var x = labels[i].x - 2; if (labels[i].textAnchor === 'middle') { - x -= textWidth(d.tags.name, labels[i].height) / 2; + x -= textWidth(name(d), labels[i].height) / 2; } return x; }, 'y': function(d, i) { return labels[i].y - labels[i].height + 1 - 2; }, 'rx': 3, 'ry': 3, - 'width': function(d, i) { return textWidth(d.tags.name, labels[i].height) + 4; }, + 'width': function(d, i) { return textWidth(name(d), labels[i].height) + 4; }, 'height': function(d, i) { return labels[i].height + 4; }, 'fill': 'white' }); @@ -149,8 +149,8 @@ iD.svg.Labels = function(projection) { .attr('y', get(labels, 'y')) .attr('transform', get(labels, 'transform')) .style('text-anchor', get(labels, 'textAnchor')) - .text(function(d) { return d.tags.name; }) - .each(function(d, i) { textWidth(d.tags.name, labels[i].height, this); }); + .text(function(d) { return name(d); }) + .each(function(d, i) { textWidth(name(d), labels[i].height, this); }); texts.exit().remove(); return texts; @@ -233,8 +233,13 @@ iD.svg.Labels = function(projection) { .property('_opacity', 0); } + function name(d) { + return d.tags[lang] || d.tags.name; + } + var rtree = new RTree(), rectangles = {}, + lang = 'name:' + iD.detect().locale.toLowerCase().split('-')[0], mousePosition, cacheDimensions; return function drawLabels(surface, graph, entities, filter, dimensions, fullRedraw) { @@ -264,7 +269,7 @@ iD.svg.Labels = function(projection) { // Split entities into groups specified by label_stack for (i = 0; i < entities.length; i++) { entity = entities[i]; - if (!entity.tags.name) continue; + if (!name(entity)) continue; if (hidePoints && entity.geometry(graph) === 'point') continue; for (k = 0; k < label_stack.length; k ++) { if (entity.geometry(graph) === label_stack[k][0] && @@ -292,7 +297,7 @@ iD.svg.Labels = function(projection) { var font_size = font_sizes[k]; for (i = 0; i < labelable[k].length; i ++) { entity = labelable[k][i]; - var width = textWidth(entity.tags.name, font_size), + var width = textWidth(name(entity), font_size), p; if (entity.geometry(graph) === 'point') { p = getPointLabel(entity, width, font_size); From 4a9d41d3fbd7b854dfbb61d9d2e92a2923a561f1 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 8 Feb 2013 15:05:24 -0800 Subject: [PATCH 273/415] Start on architecture docs --- ARCHITECTURE.md | 203 +++++++++++++++++++++++++++++++++++++++++++++ img/modes.png | Bin 0 -> 13614 bytes img/operations.png | Bin 0 -> 99538 bytes 3 files changed, 203 insertions(+) create mode 100644 ARCHITECTURE.md create mode 100644 img/modes.png create mode 100644 img/operations.png diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 000000000..c0ff6b217 --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,203 @@ +## d3 + +[d3](http://d3js.org/) is the primary library used by iD. It is used for +rendering the map data as well as many sorts of general DOM manipulation tasks +for which jQuery would often be used. + +Notable features of d3 that are used by iD include +[d3.xhr](https://github.com/mbostock/d3/wiki/Requests#wiki-d3_xhr), which is +used to make the API requests to download data from openstreetmap.org and save +changes; +[d3.dispatch](https://github.com/mbostock/d3/wiki/Internals#wiki-d3_dispatch), +which provides a callback-based [Observer +pattern](http://en.wikipedia.org/wiki/Observer_pattern) between different +parts of iD; +[d3.geo.path](https://github.com/mbostock/d3/wiki/Geo-Paths#wiki-path), which +generates SVG paths for lines and areas; and +[d3.behavior.zoom](https://github.com/mbostock/d3/wiki/Zoom-Behavior#wiki-zoom), +which implements map panning and zooming. + +## Core + +The iD *core* implements the OSM data types, a graph of OSM object's +relationships to each other, and an undo/redo history for changes made during +editing. It aims to be generic enough to be used by other JavaScript-based +tools for OpenStreetMap. + +Briefly, the OSM data model includes three basic data types: nodes, ways, and +relations. A _node_ is a point type, having a single geographic coordinate. A +_way_ is an ordered list of nodes. And a _relation_ groups together nodes, +ways, and other relations to provide free-form higher-level structures. Each +of these three types has _tags_: an associative array of key-value pairs which +describe the object. + +In iD, these three types are implemented by `iD.Node`, `iD.Way` and +`iD.Relation`. These three classes inherit from a common base, `iD.Entity` +(the only use of classical inheritance in iD). Generically, we refer to a +node, way or relation as an _entity_. + +Every entity has an _ID_ either assigned by the OSM database, or, for an +entity that is newly created, constructed as a proxy consisting of a negative +numeral. IDs from the OSM database as treated as opaque strings; no +[assumptions](http://lists.openstreetmap.org/pipermail/dev/2013-February/026495.html) +are made of them other than that they can be compared for identity and do not +begin with a minus sign (and thus will not conflict with proxy IDs). In fact, +in the OSM database the three types of entities have separate ID spaces; a +node can have the same ID as a way, for instance. Because it is useful to +store heterogeneous entities in the same datastructure, iD ensures that every +entity has a fully-unique ID by prefixing each OSM ID with the first letter of +the entity type. For example, a way with OSM ID 123456 is represented as +'w123456' within iD. + +iD entities are *immutable*: once constructed, an `Entity` object cannot +change. Tags cannot be updated; nodes cannot be added or removed from ways, +and so on. Immutability makes it easier to reason about the behavior of an +entity: if your code has a reference to one, it is safe to store it and use it +later, knowing that it cannot have been changed outside of your control. It +also makes it possible to implement the entity graph (described below) as an +efficient [persistent data +structure](http://en.wikipedia.org/wiki/Persistent_data_structure). But +obviously, iD is an editor, and must allow entities to change somehow. The +solution is that all edits produce new copies of anything that changes. At the +entity level, this takes the form of methods such as `iD.Node#move`, which +returns a new node object that has the same ID and tags as the original, but a +different coordinate. More generically, `iD.Entity#update` returns a new +entity of the same type and ID as the original but with specified properties +such as `nodes`, `tags`, or `members` replaced. + +Entities are related to one another: ways have many nodes and relations have +many members. In order to render a map of a certain area, iD needs a +datastructure to hold all the entities in that area and traverse these +relationships. `iD.Graph` provides this functionality. The core of a graph is +a map between IDs and the associated entities; given an ID, the graph can give +you the entity. Like entities, a graph is immutable: adding, replacing, or +removing an entity produces a new graph, and the original is unchanged. +Because entities are immutable, the original and new graphs can share +references to entities that have not changed, keeping memory use to a minimum. +If you are familiar with how git works internally, this persistent data +structure approach is very similar. + +The final component of the core is comprised of `iD.History` and +`iD.Difference`, which track the changes made in an editing session and +provide undo/redo capabilities. Here, the immutable nature of the core types +really pays off: the history is a simple stack of graphs, each representing +the state of the data at a particular point in editing. The graph at the top +of the stack is the current state, off which all rendering is based. To undo +the last change, this graph is popped off the stack, and the map is +re-rendered based on the new top of the stack. Contrast this to a mutable +graph as used in JOSM and Potlatch: every command that changes the graph must +implement an equal and opposite undo command that restores the graph to the +previous state. + +## Actions + +In iD, an _action_ is a function that accepts a graph as input and returns a +modified graph as output. Actions typically need other inputs as well; for +example, `iD.actions.DeleteNode` also requires the ID of a node to delete. The +additional input is passed to the action's constructor: + +``` var action = iD.actions.DeleteNode('n123456'); // construct the action var +newGraph = action(oldGraph); // apply the action ``` + +iD provides actions for all the typical things an editor needs to do: add a +new entity, split a way in two, connect the vertices of two ways together, and +so on. In addition to performing the basic work needed to accomplish these +things, an action typically contains a significant amount of logic for keeping +the relationships between entities logical and consistent. For example, an +action as apparently simple as `DeleteNode`, in addition to removing the node +from the graph, needs to do two other things: remove the node from any ways in +which it is a member (which in turn requires deleting parent ways that are +left with just a single node), and removing it from any relations of which it +is a member. + +As you can imagine, implementing all these details requires an expert +knowledge of the OpenStreetMap data model. It is our hope that JavaScript +based tools for OpenStreetMap can reuse the implementations provided by iD in +other contexts, significantly reducing the work necessary to create a robust +tool. + +## Modes + +With _modes_, we shift gears from abstract data types and algorithms to the +parts of the architecture that implement the user interface for iD. Modes are +manifested in the interface by the four buttons at the top left: + +![Mode buttons](img/modes.png) + +The modality of existing OSM editors runs the gamut from Potlatch 2, which is +almost entirely modeless, to JOSM, which sports half a dozen modes out of the +box and has many more provided by plugins. iD seeks a middle ground: too few +modes can leave new users unsure where to start, while too many can be +overwhelming. + +iD's user-facing modes consist of a base "Browse" mode, in which you can move +around the map and select and edit entities, and three geometrically-oriented +drawing modes: Point, Line, and Area. In the code, these are broken down a +little bit more. There are separate modes for when an entity is selected +(`iD.modes.Select`) versus when nothing is selected (`iD.modes.Browse`), and +each of the geometric modes is split into one mode for starting to draw an +object and one mode for continuing an existing object (with the exception of +`iD.modes.AddPoint`, which is a single-step operation for obvious reasons). + +The code interface for each mode consists of a pair of methods: `enter` and +`exit`. In the `enter` method, a mode sets up all the behavior that should be +present when that mode is active. This typically means binding callbacks to +DOM events that will be triggered on map elements, installing keybindings, and +showing certain parts of the interface like the inspector in `Select` mode. +The `exit` mode does the opposite, removing the behavior installed by the +`enter` method. Together the two methods ensure that modes are self-contained +and exclusive: each mode knows exactly the behavior that is specific to that +mode, and exactly one mode's behavior is active at any time. + +## Behavior + +Certain behaviors are common to more than one mode. For example, iD indicates +interactive map elements by drawing a halo around them when you hover over +them, and this behavior is common to both the browse and draw modes. Instead +of duplicating the code to implement this behavior in all these modes, we +extract it to `iD.behavior.Hover`. + +_Behaviors_ take their inspiration from [d3's +behaviors](https://github.com/mbostock/d3/wiki/Behaviors). Like d3's `zoom` +and `drag`, each iD behavior is a function that takes as input a d3 selection +(assumed to consist of a single element) and installs the DOM event bindings +necessary to implement the behavior. The `Hover` behavior, for example, +installs bindings for the `mouseover` and `mouseout` events that add and +remove a `hover` class from map elements. + +Because certain behaviors are appropriate to some but not all modes, we need +the ability to remove a behavior when entering a mode where it is not +appropriate. (This is functionality [not yet +provided](https://github.com/mbostock/d3/issues/894) by d3's own behaviors.) +Each behavior implements an `off` function that "uninstalls" the behavior. +This is very similar to the `exit` method of a mode, and in fact many modes do +little else but uninstall behaviors in their `exit` methods. + +## Operations + +_Operations_ wrap actions, providing their user-interface: tooltips, key +bindings, and the logic that determines whether an action can be validly +performed given the current map state and selection. Each operation is +constructed with the list of IDs which are currently selected and a `context` +object which provides access to the history and other important parts of iD's +internal state. After being constructed, an operation can be queried as to +whether or not it should be made available (i.e., show up in the context menu) +and if so, if it should be enabled. + +![Operations menu](img/operations.png) + +We make a distinction between availability and enabled state for the sake of +learnability: most operations are available so long as an entity of the +appropriate type is selected. Even if it remains disabled for other reasons +(e.g. because you can't split a way on its start or end vertex), a new user +can still learn that "this is something I can do to this type of thing", and a +tooltip can provide an explanation of what that operation does and the +conditions under which it is enabled. + +To execute an operation, call it as a function, with no arguments. The typical +operation will perform the appropriate action, creating a new undo state in +the history, and then enter the appropriate mode. For example, +`iD.operations.Split` performs `iD.actions.Split`, then enters +`iD.modes.Select` with the resulting ways selected. + +## Rendering and other UI diff --git a/img/modes.png b/img/modes.png new file mode 100644 index 0000000000000000000000000000000000000000..6b5ec860348b7217977845221eb19091f842502a GIT binary patch literal 13614 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 z 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 Jc-G!6}%>;K8r6bs@AI$v)8c$V0A#N>E$Ajj% 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�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 LNkvXXu0mjfiQS)R literal 0 HcmV?d00001 From f1079a644ae56aabf55248be5e02398342e62834 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 8 Feb 2013 20:44:14 -0800 Subject: [PATCH 274/415] Fix relation role in Split (#694) --- js/id/actions/split.js | 2 +- test/spec/actions/split.js | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/js/id/actions/split.js b/js/id/actions/split.js index ea2100238..92f9296f7 100644 --- a/js/id/actions/split.js +++ b/js/id/actions/split.js @@ -73,7 +73,7 @@ iD.actions.Split = function(nodeId, newWayId) { } } - relation = relation.addMember({id: wayB.id, type: 'wayA', role: role}, i <= j ? i + 1 : i); + relation = relation.addMember({id: wayB.id, type: 'way', role: role}, i <= j ? i + 1 : i); graph = graph.replace(relation); } }); diff --git a/test/spec/actions/split.js b/test/spec/actions/split.js index 417525941..2e829fc93 100644 --- a/test/spec/actions/split.js +++ b/test/spec/actions/split.js @@ -175,12 +175,15 @@ describe("iD.actions.Split", function () { 'b': iD.Node({id: 'b'}), 'c': iD.Node({id: 'c'}), '-': iD.Way({id: '-', nodes: ['a', 'b', 'c']}), - 'r': iD.Relation({id: 'r', members: [{id: '-', type: 'way'}]}) + 'r': iD.Relation({id: 'r', members: [{id: '-', type: 'way', role: 'forward'}]}) }); graph = iD.actions.Split('b', '=')(graph); - expect(_.pluck(graph.entity('r').members, 'id')).to.eql(['-', '=']); + expect(graph.entity('r').members).to.eql([ + {id: '-', type: 'way', role: 'forward'}, + {id: '=', type: 'way', role: 'forward'} + ]); }); it("adds the new way to parent relations (forward order)", function () { From a60ed7f6e55c01b6a28803c00e5b43c131ddffc7 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Sat, 9 Feb 2013 00:04:29 -0500 Subject: [PATCH 275/415] Remove pointer-events for area being drawn --- js/id/behavior/draw_way.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/js/id/behavior/draw_way.js b/js/id/behavior/draw_way.js index 8c4c4a5cc..0a05c7e9d 100644 --- a/js/id/behavior/draw_way.js +++ b/js/id/behavior/draw_way.js @@ -47,6 +47,14 @@ iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) { context.enter(iD.modes.Browse(context)); } + function lineActives(d) { + return d.id === segment.id || d.id === start.id || d.id === end.id; + } + + function areaActives(d) { + return d.id === wayId || d.id === end.id; + } + var drawWay = function(surface) { draw.on('move', move) .on('click', drawWay.add) @@ -63,7 +71,7 @@ iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) { surface.call(draw) .selectAll('.way, .node') - .filter(function (d) { return d.id === segment.id || d.id === start.id || d.id === end.id; }) + .filter(isArea ? areaActives : lineActives) .classed('active', true); context.history() From 334e842926a522417b640d1ab372a90b397b718b Mon Sep 17 00:00:00 2001 From: Alex Barth Date: Sat, 9 Feb 2013 10:52:01 -0500 Subject: [PATCH 276/415] Title for installation instructions --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 8cf1daeeb..e185fbb4f 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,8 @@ * [Read up on Contributing and the code style of iD](CONTRIBUTING.md) * See [open issues in the issue tracker](https://github.com/systemed/iD/issues?state=open) if you're looking for something to do +## Installation + To run the current development version, fork this project and serve it locally. If you have Python handy, just `cd` into the project root directory and run From 8a8a29013ed77dff22e53c5dbeeee8ed7dbc5ec2 Mon Sep 17 00:00:00 2001 From: Ian B Date: Sat, 9 Feb 2013 15:22:02 +0100 Subject: [PATCH 277/415] Search results area Display multiple search results --- css/app.css | 15 ++++++++++++++ js/id/ui/geocoder.js | 47 +++++++++++++++++++++++++++++++++++++++----- 2 files changed, 57 insertions(+), 5 deletions(-) diff --git a/css/app.css b/css/app.css index 99904d953..35f7c3821 100644 --- a/css/app.css +++ b/css/app.css @@ -848,6 +848,21 @@ a.selected:hover .toggle.icon { background-position: -40px -180px;} margin: 4px; } +.geocode-control div { + top: 50px; + width: 340px; + margin: 4px; + padding: 5px; +} +.geocode-control div span { + display: inline-block; + border-bottom: 1px solid #333; +} + +.geocode-control div span:hover { + background-color: #333; +} + /* Geolocator */ .geolocate-control { diff --git a/js/id/ui/geocoder.js b/js/id/ui/geocoder.js index 8883dc934..52fcb1672 100644 --- a/js/id/ui/geocoder.js +++ b/js/id/ui/geocoder.js @@ -8,21 +8,54 @@ iD.ui.geocoder = function() { d3.event.preventDefault(); var searchVal = this.value; d3.json('http://nominatim.openstreetmap.org/search/' + - encodeURIComponent(searchVal) + '?limit=10&format=json', function(err, resp) { + encodeURIComponent(searchVal) + '?limit=10&format=json', function (err, resp) { if (err) return hide(); - hide(); if (!resp.length) { return iD.ui.flash(context.container()) .select('.content') .append('h3') .text('No location found for "' + searchVal + '"'); } - var bounds = resp[0].boundingbox; - map.extent(iD.geo.Extent([parseFloat(bounds[3]), parseFloat(bounds[0])], [parseFloat(bounds[2]), parseFloat(bounds[1])])); - if (map.zoom() > 19) map.zoom(19); + if(resp.length > 1) { + for (var i=0; i < resp.length; i++) { + var displayName, elementType, typeStr, span; + displayName = resp[i].display_name, + elementType = resp[i].type, + typeStr = elementType.charAt(0).toUpperCase() + elementType.slice(1) + ': ', + span = resultsList.append('span').text(typeStr); + if(displayName.length > 80) displayName = displayName.substr(0,80) + '...'; + span.append('a') + .attr('data-min-lon',resp[i].boundingbox[3]) + .attr('data-min-lat',resp[i].boundingbox[0]) + .attr('data-max-lon',resp[i].boundingbox[2]) + .attr('data-max-lat',resp[i].boundingbox[1]) + .text(displayName) + .on('click', clickResult); + } + resultsList.classed('hide',false); + } else { + var bounds = resp[0].boundingbox; + var extent = iD.geo.Extent([parseFloat(bounds[3]), parseFloat(bounds[0])], [parseFloat(bounds[2]), parseFloat(bounds[1])]); + applyBounds(extent); + } }); } + function clickResult() { + var result = d3.select(this); + var extent = iD.geo.Extent( + [parseFloat(result.attr('data-min-lon')), parseFloat(result.attr('data-min-lat'))], + [parseFloat(result.attr('data-max-lon')), parseFloat(result.attr('data-max-lat'))] + ); + applyBounds(extent); + } + + function applyBounds(extent) { + hide(); + map.extent(extent); + if (map.zoom() > 19) map.zoom(19); + } + function clickoutside(selection) { selection .on('click.geocoder-inside', function() { @@ -38,6 +71,7 @@ iD.ui.geocoder = function() { function setVisible(show) { button.classed('active', show); gcForm.classed('hide', !show); + if (!show) resultsList.classed('hide', !show); if (show) inputNode.node().focus(); else inputNode.node().blur(); } @@ -55,6 +89,9 @@ iD.ui.geocoder = function() { .attr({ type: 'text', placeholder: t('geocoder.find_a_place') }) .on('keydown', keydown); + var resultsList = selection.append('div') + .attr('class','content fillD map-overlay hide'); + selection.call(clickoutside); } From 78cdc3aec2a0d186f24bd8e8010f3a7fbf7f9197 Mon Sep 17 00:00:00 2001 From: Alex Barth Date: Sat, 9 Feb 2013 10:52:01 -0500 Subject: [PATCH 278/415] Title for installation instructions --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 8cf1daeeb..e185fbb4f 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,8 @@ * [Read up on Contributing and the code style of iD](CONTRIBUTING.md) * See [open issues in the issue tracker](https://github.com/systemed/iD/issues?state=open) if you're looking for something to do +## Installation + To run the current development version, fork this project and serve it locally. If you have Python handy, just `cd` into the project root directory and run From cc51fdc4beba0ddf3f59f9ea84f0fae96faabf37 Mon Sep 17 00:00:00 2001 From: Ian B Date: Sat, 9 Feb 2013 22:27:19 +0100 Subject: [PATCH 279/415] Refactored geocoder to use data joins --- js/id/ui/geocoder.js | 66 +++++++++++++++++++++----------------------- 1 file changed, 31 insertions(+), 35 deletions(-) diff --git a/js/id/ui/geocoder.js b/js/id/ui/geocoder.js index 52fcb1672..22c897653 100644 --- a/js/id/ui/geocoder.js +++ b/js/id/ui/geocoder.js @@ -9,44 +9,40 @@ iD.ui.geocoder = function() { var searchVal = this.value; d3.json('http://nominatim.openstreetmap.org/search/' + encodeURIComponent(searchVal) + '?limit=10&format=json', function (err, resp) { - if (err) return hide(); - if (!resp.length) { - return iD.ui.flash(context.container()) - .select('.content') - .append('h3') - .text('No location found for "' + searchVal + '"'); - } - if(resp.length > 1) { - for (var i=0; i < resp.length; i++) { - var displayName, elementType, typeStr, span; - displayName = resp[i].display_name, - elementType = resp[i].type, - typeStr = elementType.charAt(0).toUpperCase() + elementType.slice(1) + ': ', - span = resultsList.append('span').text(typeStr); - if(displayName.length > 80) displayName = displayName.substr(0,80) + '...'; - span.append('a') - .attr('data-min-lon',resp[i].boundingbox[3]) - .attr('data-min-lat',resp[i].boundingbox[0]) - .attr('data-max-lon',resp[i].boundingbox[2]) - .attr('data-max-lat',resp[i].boundingbox[1]) - .text(displayName) - .on('click', clickResult); + if (err) return hide(); + if (!resp.length) { + return iD.ui.flash(context.container()) + .select('.content') + .append('h3') + .text('No location found for "' + searchVal + '"'); } - resultsList.classed('hide',false); - } else { - var bounds = resp[0].boundingbox; - var extent = iD.geo.Extent([parseFloat(bounds[3]), parseFloat(bounds[0])], [parseFloat(bounds[2]), parseFloat(bounds[1])]); - applyBounds(extent); - } - }); + if(resp.length > 1) { + var spans = resultsList.selectAll('span') + .data(resp, function (d) { return d.place_id; }); + spans.enter() + .append('span') + .text(function(d) { + return d.type.charAt(0).toUpperCase() + d.type.slice(1) + ': '; + }) + .append('a') + .text(function(d) { + if(d.display_name > 80) return d.display_name.substr(0,80) + '...'; + return d.display_name; + }) + .on('click', clickResult); + spans.exit().remove(); + resultsList.classed('hide',false); + } else { + var bounds = resp[0].boundingbox; + var extent = iD.geo.Extent([parseFloat(bounds[3]), parseFloat(bounds[0])], [parseFloat(bounds[2]), parseFloat(bounds[1])]); + applyBounds(extent); + } + }); } - function clickResult() { - var result = d3.select(this); - var extent = iD.geo.Extent( - [parseFloat(result.attr('data-min-lon')), parseFloat(result.attr('data-min-lat'))], - [parseFloat(result.attr('data-max-lon')), parseFloat(result.attr('data-max-lat'))] - ); + function clickResult(data) { + var bounds = data.boundingbox; + var extent = iD.geo.Extent([parseFloat(bounds[3]), parseFloat(bounds[0])], [parseFloat(bounds[2]), parseFloat(bounds[1])]); applyBounds(extent); } From f56e27ad2a6d8a4dd8ba88b0c6796796071e5779 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Sat, 9 Feb 2013 17:21:28 -0500 Subject: [PATCH 280/415] Style tweaks and refactoring --- css/app.css | 4 ---- js/id/ui/geocoder.js | 33 +++++++++++++++++++-------------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/css/app.css b/css/app.css index 35f7c3821..355fd7262 100644 --- a/css/app.css +++ b/css/app.css @@ -859,10 +859,6 @@ a.selected:hover .toggle.icon { background-position: -40px -180px;} border-bottom: 1px solid #333; } -.geocode-control div span:hover { - background-color: #333; -} - /* Geolocator */ .geolocate-control { diff --git a/js/id/ui/geocoder.js b/js/id/ui/geocoder.js index 22c897653..c873e1c10 100644 --- a/js/id/ui/geocoder.js +++ b/js/id/ui/geocoder.js @@ -2,48 +2,53 @@ iD.ui.geocoder = function() { var map, context; + function resultExtent(bounds) { + return new iD.geo.Extent( + [parseFloat(bounds[3]), parseFloat(bounds[0])], + [parseFloat(bounds[2]), parseFloat(bounds[1])]); + } + function geocoder(selection) { function keydown() { if (d3.event.keyCode !== 13) return; d3.event.preventDefault(); var searchVal = this.value; d3.json('http://nominatim.openstreetmap.org/search/' + - encodeURIComponent(searchVal) + '?limit=10&format=json', function (err, resp) { + encodeURIComponent(searchVal) + '?limit=10&format=json', function(err, resp) { if (err) return hide(); if (!resp.length) { return iD.ui.flash(context.container()) .select('.content') .append('h3') .text('No location found for "' + searchVal + '"'); - } - if(resp.length > 1) { + } else if (resp.length > 1) { var spans = resultsList.selectAll('span') .data(resp, function (d) { return d.place_id; }); + spans.enter() .append('span') - .text(function(d) { + .text(function(d) { return d.type.charAt(0).toUpperCase() + d.type.slice(1) + ': '; }) .append('a') .text(function(d) { - if(d.display_name > 80) return d.display_name.substr(0,80) + '...'; - return d.display_name; + if (d.display_name > 80) { + return d.display_name.substr(0, 80) + '…'; + } else { + return d.display_name; + } }) .on('click', clickResult); spans.exit().remove(); - resultsList.classed('hide',false); + resultsList.classed('hide', false); } else { - var bounds = resp[0].boundingbox; - var extent = iD.geo.Extent([parseFloat(bounds[3]), parseFloat(bounds[0])], [parseFloat(bounds[2]), parseFloat(bounds[1])]); - applyBounds(extent); + applyBounds(resultExtent(resp[0].boundingbox)); } }); } - function clickResult(data) { - var bounds = data.boundingbox; - var extent = iD.geo.Extent([parseFloat(bounds[3]), parseFloat(bounds[0])], [parseFloat(bounds[2]), parseFloat(bounds[1])]); - applyBounds(extent); + function clickResult(d) { + applyBounds(resultExtent(d.boundingbox)); } function applyBounds(extent) { From d9b581653032a9d775a1b23c0bb03c8e26449965 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Sat, 9 Feb 2013 17:34:44 -0500 Subject: [PATCH 281/415] Store background setting in hash. Fixes #632 --- js/id/id.js | 17 +++++++++++++---- js/id/renderer/background.js | 13 +++++++++++++ 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/js/id/id.js b/js/id/id.js index 5e39e6b0e..544c66a12 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -92,10 +92,19 @@ window.iD = function () { return context; }; - context.background() - .source(_.find(iD.layers, function(l) { - return l.data.name === 'Bing aerial imagery'; - })); + var q = iD.util.stringQs(location.hash.substring(1)); + if (q.layer) { + context.background() + .source(_.find(iD.layers, function(l) { + return l.data.sourcetag === q.layer; + })); + } + if (!context.background()) { + context.background() + .source(_.find(iD.layers, function(l) { + return l.data.name === 'Bing aerial imagery'; + })); + } return d3.rebind(context, dispatch, 'on'); }; diff --git a/js/id/renderer/background.js b/js/id/renderer/background.js index be4cc1927..8a14dc0f3 100644 --- a/js/id/renderer/background.js +++ b/js/id/renderer/background.js @@ -152,9 +152,22 @@ iD.Background = function() { return background; }; + function setPermalink(source) { + var tag = source.data.sourcetag; + var q = iD.util.stringQs(location.hash.substring(1)); + if (tag) { + location.replace('#' + iD.util.qsString(_.assign(q, { + layer: tag + }), true)); + } else { + location.replace('#' + iD.util.qsString(_.omit(q, 'layer'), true)); + } + } + background.source = function(_) { if (!arguments.length) return source; source = _; + setPermalink(source); return background; }; From d584eb2f9d0a123c46b272ef10736db585a4f46c Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Sat, 9 Feb 2013 17:56:22 -0500 Subject: [PATCH 282/415] Pan, fix initial loads --- js/id/behavior/hash.js | 6 ++---- js/id/id.js | 13 ++++++++----- js/id/ui.js | 16 +++++++++++++++- 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/js/id/behavior/hash.js b/js/id/behavior/hash.js index ced648e45..62f7e51b2 100644 --- a/js/id/behavior/hash.js +++ b/js/id/behavior/hash.js @@ -66,11 +66,9 @@ iD.behavior.Hash = function(context) { if (location.hash) { var q = iD.util.stringQs(location.hash.substring(1)); - if (q.id) { - willselect(q.id); - } + if (q.id) willselect(q.id); hashchange(); - hash.hadHash = true; + if (q.map) hash.hadHash = true; } } diff --git a/js/id/id.js b/js/id/id.js index 544c66a12..665e0b0a1 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -92,14 +92,17 @@ window.iD = function () { return context; }; - var q = iD.util.stringQs(location.hash.substring(1)); + var q = iD.util.stringQs(location.hash.substring(1)), detected = false; if (q.layer) { context.background() - .source(_.find(iD.layers, function(l) { - return l.data.sourcetag === q.layer; - })); + .source(_.find(iD.layers, function(l) { + if (l.data.sourcetag === q.layer) { + return (detected = true); + } + })); } - if (!context.background()) { + + if (!detected) { context.background() .source(_.find(iD.layers, function(l) { return l.data.name === 'Bing aerial imagery'; diff --git a/js/id/ui.js b/js/id/ui.js index 359a39dcb..8681c9a50 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -232,12 +232,26 @@ iD.ui = function(context) { map.size(m.size()); }); + function pan(d) { + return function() { + map.pan(d); + map.redraw(); + }; + } + + // pan amount + var pa = 5; + var keybinding = d3.keybinding('main') .on('⌘+Z', function() { history.undo(); }) .on('⌃+Z', function() { history.undo(); }) .on('⌘+⇧+Z', function() { history.redo(); }) .on('⌃+⇧+Z', function() { history.redo(); }) - .on('⌫', function() { d3.event.preventDefault(); }); + .on('⌫', function() { d3.event.preventDefault(); }) + .on('←', pan([pa, 0])) + .on('↑', pan([0, pa])) + .on('→', pan([-pa, 0])) + .on('↓', pan([0, -pa])); modes.forEach(function(m) { keybinding.on(m.key, function() { if (map.editable()) context.enter(m); }); From 6faf7a27c4ddc82bfa0ac8c7d98263191b476c8a Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Sat, 9 Feb 2013 18:02:09 -0500 Subject: [PATCH 283/415] Support zooming with keyboard. Fixes #695 --- js/id/ui.js | 6 +++++- js/lib/d3.keybinding.js | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/js/id/ui.js b/js/id/ui.js index 8681c9a50..8dccb8352 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -251,7 +251,11 @@ iD.ui = function(context) { .on('←', pan([pa, 0])) .on('↑', pan([0, pa])) .on('→', pan([-pa, 0])) - .on('↓', pan([0, -pa])); + .on('↓', pan([0, -pa])) + .on('⇧+=', function() { map.zoomIn(); }) + .on('+', function() { map.zoomIn(); }) + .on('-', function() { map.zoomOut(); }) + .on('dash', function() { map.zoomOut(); }); modes.forEach(function(m) { keybinding.on(m.key, function() { if (map.editable()) context.enter(m); }); diff --git a/js/lib/d3.keybinding.js b/js/lib/d3.keybinding.js index e16ade725..b1106c976 100644 --- a/js/lib/d3.keybinding.js +++ b/js/lib/d3.keybinding.js @@ -151,7 +151,7 @@ d3.keybinding = function(namespace) { '=': 187, 'equals': 187, // Comma, or , ',': 188, comma: 188, - //'-': 189, //??? + 'dash': 189, //??? // Period, or ., or full-stop '.': 190, period: 190, 'full-stop': 190, // Slash, or /, or forward-slash From f15191af2ccfc26bef0e2a6148e51a8080366732 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Sat, 9 Feb 2013 18:06:35 -0500 Subject: [PATCH 284/415] Update tests --- test/spec/behavior/hash.js | 2 ++ test/spec/renderer/background.js | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/test/spec/behavior/hash.js b/test/spec/behavior/hash.js index 7f9200ae1..c93e42d21 100644 --- a/test/spec/behavior/hash.js +++ b/test/spec/behavior/hash.js @@ -51,6 +51,8 @@ describe("iD.behavior.Hash", function () { }); it("stores the current zoom and coordinates in location.hash on map move events", function () { + location.hash = ""; + hash(); context.map().center([38.9, -77.0]); diff --git a/test/spec/renderer/background.js b/test/spec/renderer/background.js index 4321e861e..cdfb3239d 100644 --- a/test/spec/renderer/background.js +++ b/test/spec/renderer/background.js @@ -22,8 +22,8 @@ describe('iD.Background', function() { }); it('#source', function() { - expect(c.source(iD.BackgroundSource.Bing)).to.equal(c); - expect(c.source()).to.equal(iD.BackgroundSource.Bing); + expect(c.source(iD.layers[0])).to.equal(c); + expect(c.source()).to.equal(iD.layers[0]); }); }); From 574e072598e98e60e14b55fa1407f05c0395564c Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Sat, 9 Feb 2013 18:08:03 -0500 Subject: [PATCH 285/415] Update license --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5c991f29b..26728933b 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "editor", "openstreetmap" ], - "license": "BSD", + "license": "WTFPL", "devDependencies": { "uglify-js": "~2.2.2", "mocha-phantomjs": "~1.1.1" From c406413b67ac2ea3a728682ac9e29fe034089305 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sat, 9 Feb 2013 15:09:02 -0800 Subject: [PATCH 286/415] Browse button should be active when moving (fixes #692) --- js/id/modes/move_way.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/js/id/modes/move_way.js b/js/id/modes/move_way.js index e21dded78..99a2ebb48 100644 --- a/js/id/modes/move_way.js +++ b/js/id/modes/move_way.js @@ -1,6 +1,7 @@ iD.modes.MoveWay = function(context, wayId) { var mode = { - id: 'move-way' + id: 'move-way', + button: 'browse' }; var keybinding = d3.keybinding('move-way'); From 71fc68ddfa712cddeb4dbac8017b989cfe567a09 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Sat, 9 Feb 2013 18:11:59 -0500 Subject: [PATCH 287/415] Go live by default. Fixes #653 --- index.html | 3 +-- js/id/ui.js | 7 +++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/index.html b/index.html index 4dc3b373f..e4b474f45 100644 --- a/index.html +++ b/index.html @@ -155,8 +155,7 @@ var id = iD(); id.connection() - .keys(keys) - .url('http://api06.dev.openstreetmap.org'); + .keys(keys); d3.select("#iD") .call(id.ui()) diff --git a/js/id/ui.js b/js/id/ui.js index 8dccb8352..81797b731 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -165,8 +165,11 @@ iD.ui = function(context) { .append('span') .attr('class', 'provided-by'); - linkList.append('li').attr('class', 'source-switch').append('a').attr('href', '#') - .text('dev') + linkList.append('li') + .attr('class', 'source-switch') + .append('a').attr('href', '#') + .text('live') + .classed('live', true) .on('click.editor', function() { d3.event.preventDefault(); if (d3.select(this).classed('live')) { From bd8847ba6a42bfadbba2090ca19878aafacf39d8 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Sat, 9 Feb 2013 18:18:30 -0500 Subject: [PATCH 288/415] Sub french and german translations --- index.html | 2 + locale/de.js | 187 +++++++++++++++++++++++++++++++++++++++++++++++++++ locale/fr.js | 187 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 376 insertions(+) create mode 100644 locale/de.js create mode 100644 locale/fr.js diff --git a/index.html b/index.html index e4b474f45..0701835f3 100644 --- a/index.html +++ b/index.html @@ -143,6 +143,8 @@ + + diff --git a/locale/de.js b/locale/de.js new file mode 100644 index 000000000..091ef9a07 --- /dev/null +++ b/locale/de.js @@ -0,0 +1,187 @@ +locale.de = { + modes: { + add_area: { + title: "Area", + description: "Add parks, buildings, lakes, or other areas to the map.", + tail: "Click on the map to start drawing an area, like a park, lake, or building.", + key: "A" + }, + add_line: { + title: "Line", + description: "Lines can be highways, streets, pedestrian paths, or even canals.", + tail: "Click on the map to start drawing an road, path, or route.", + key: "L" + }, + add_point: { + title: "Point", + description: "Restaurants, monuments, and postal boxes are points.", + tail: "Click on the map to add a point.", + key: "P" + }, + browse: { + title: "Browse", + description: "Pan and zoom the map.", + key: "B" + }, + draw_area: { + tail: "Click to add points to your area. Click the first point to finish the area." + }, + draw_line: { + tail: "Click to add more points to the line. Click on other lines to connect to them, and double-click to end the line." + } + }, + + operations: { + add: { + annotation: { + point: "Added a point.", + vertex: "Added a node to a way." + } + }, + start: { + annotation: { + line: "Started a line.", + area: "Started an area." + } + }, + 'continue': { + annotation: { + line: "Continued a line.", + area: "Continued an area." + } + }, + cancel_draw: { + annotation: "Cancelled drawing." + }, + change_tags: { + annotation: "Changed tags." + }, + circularize: { + title: "Circularize", + description: "Make this round.", + key: "O", + annotation: { + line: "Made a line circular.", + area: "Made an area circular." + } + }, + orthogonalize: { + title: "Orthogonalize", + description: "Square these corners.", + key: "Q", + annotation: { + line: "Squared the corners of a line.", + area: "Squared the corners of an area." + } + }, + 'delete': { + title: "Delete", + description: "Remove this from the map.", + key: "⌫", + annotation: { + point: "Deleted a point.", + vertex: "Deleted a node from a way.", + line: "Deleted a line.", + area: "Deleted an area.", + relation: "Deleted a relation.", + multiple: "Deleted {n} objects." + } + }, + connect: { + annotation: { + point: "Connected a way to a point.", + vertex: "Connected a way to another.", + line: "Connected a way to a line.", + area: "Connected a way to an area." + } + }, + disconnect: { + title: "Disconnect", + description: "Disconnect these ways from each other.", + key: "D", + annotation: "Disconnected ways." + }, + merge: { + title: "Merge", + description: "Merge these lines.", + key: "C", + annotation: "Merged {n} lines." + }, + move: { + title: "Move", + description: "Move this to a different location.", + key: "M", + annotation: { + point: "Moved a point.", + vertex: "Moved a node in a way.", + line: "Moved a line.", + area: "Moved an area." + } + }, + reverse: { + title: "Reverse", + description: "Make this line go in the opposite direction.", + key: "V", + annotation: "Reversed a line." + }, + split: { + title: "Split", + description: "Split this into two ways at this point.", + key: "X", + annotation: "Split a way." + } + }, + + validations: { + untagged_point: "Untagged point which is not part of a line or area", + untagged_line: "Untagged line", + untagged_area: "Untagged area", + tag_suggests_area: "The tag {tag} suggests line should be area, but it is not an area", + deprecated_tags: "Deprecated tags: {tags}" + }, + + save: "Save", + save_help: "Save changes to OpenStreetMap, making them visible to other users", + no_changes: "You don't have any changes to save.", + save_error: "An error occurred while trying to save", + uploading_changes: "Uploading changes to OpenStreetMap.", + just_edited: "You Just Edited OpenStreetMap!", + okay: "Okay", + + "zoom-in": "Zoom In", + "zoom-out": "Zoom Out", + + nothing_to_undo: "Nothing to undo.", + nothing_to_redo: "Nothing to redo.", + + browser_notice: "This editor is supported in Firefox, Chrome, Safari, Opera, and Internet Explorer 9 and above. Please upgrade your browser or use Potlatch 2 to edit the map.", + + inspector: { + no_documentation_combination: "This is no documentation available for this tag combination", + no_documentation_key: "This is no documentation available for this key", + new_tag: "New Tag" + }, + + view_on_osm: "View on OSM", + + zoom_in_edit: "zoom in to edit the map", + + edit_tags: "Edit tags", + + geocoder: { + "find_location": "Find A Location", + "find_a_place": "find a place" + }, + + description: "Description", + + logout: "logout", + + layerswitcher: { + title: "Background", + description: "Background Settings", + percent_brightness: "{opacity}% brightness", + fix_misalignment: "Fix misalignment", + reset: "reset" + } +}; diff --git a/locale/fr.js b/locale/fr.js new file mode 100644 index 000000000..6c4f8bc2b --- /dev/null +++ b/locale/fr.js @@ -0,0 +1,187 @@ +locale.en = { + modes: { + add_area: { + title: "Area", + description: "Add parks, buildings, lakes, or other areas to the map.", + tail: "Click on the map to start drawing an area, like a park, lake, or building.", + key: "A" + }, + add_line: { + title: "Line", + description: "Lines can be highways, streets, pedestrian paths, or even canals.", + tail: "Click on the map to start drawing an road, path, or route.", + key: "L" + }, + add_point: { + title: "Point", + description: "Restaurants, monuments, and postal boxes are points.", + tail: "Click on the map to add a point.", + key: "P" + }, + browse: { + title: "Browse", + description: "Pan and zoom the map.", + key: "B" + }, + draw_area: { + tail: "Click to add points to your area. Click the first point to finish the area." + }, + draw_line: { + tail: "Click to add more points to the line. Click on other lines to connect to them, and double-click to end the line." + } + }, + + operations: { + add: { + annotation: { + point: "Added a point.", + vertex: "Added a node to a way." + } + }, + start: { + annotation: { + line: "Started a line.", + area: "Started an area." + } + }, + 'continue': { + annotation: { + line: "Continued a line.", + area: "Continued an area." + } + }, + cancel_draw: { + annotation: "Cancelled drawing." + }, + change_tags: { + annotation: "Changed tags." + }, + circularize: { + title: "Circularize", + description: "Make this round.", + key: "O", + annotation: { + line: "Made a line circular.", + area: "Made an area circular." + } + }, + orthogonalize: { + title: "Orthogonalize", + description: "Square these corners.", + key: "Q", + annotation: { + line: "Squared the corners of a line.", + area: "Squared the corners of an area." + } + }, + 'delete': { + title: "Delete", + description: "Remove this from the map.", + key: "⌫", + annotation: { + point: "Deleted a point.", + vertex: "Deleted a node from a way.", + line: "Deleted a line.", + area: "Deleted an area.", + relation: "Deleted a relation.", + multiple: "Deleted {n} objects." + } + }, + connect: { + annotation: { + point: "Connected a way to a point.", + vertex: "Connected a way to another.", + line: "Connected a way to a line.", + area: "Connected a way to an area." + } + }, + disconnect: { + title: "Disconnect", + description: "Disconnect these ways from each other.", + key: "D", + annotation: "Disconnected ways." + }, + merge: { + title: "Merge", + description: "Merge these lines.", + key: "C", + annotation: "Merged {n} lines." + }, + move: { + title: "Move", + description: "Move this to a different location.", + key: "M", + annotation: { + point: "Moved a point.", + vertex: "Moved a node in a way.", + line: "Moved a line.", + area: "Moved an area." + } + }, + reverse: { + title: "Reverse", + description: "Make this line go in the opposite direction.", + key: "V", + annotation: "Reversed a line." + }, + split: { + title: "Split", + description: "Split this into two ways at this point.", + key: "X", + annotation: "Split a way." + } + }, + + validations: { + untagged_point: "Untagged point which is not part of a line or area", + untagged_line: "Untagged line", + untagged_area: "Untagged area", + tag_suggests_area: "The tag {tag} suggests line should be area, but it is not an area", + deprecated_tags: "Deprecated tags: {tags}" + }, + + save: "Save", + save_help: "Save changes to OpenStreetMap, making them visible to other users", + no_changes: "You don't have any changes to save.", + save_error: "An error occurred while trying to save", + uploading_changes: "Uploading changes to OpenStreetMap.", + just_edited: "You Just Edited OpenStreetMap!", + okay: "Okay", + + "zoom-in": "Zoom In", + "zoom-out": "Zoom Out", + + nothing_to_undo: "Nothing to undo.", + nothing_to_redo: "Nothing to redo.", + + browser_notice: "This editor is supported in Firefox, Chrome, Safari, Opera, and Internet Explorer 9 and above. Please upgrade your browser or use Potlatch 2 to edit the map.", + + inspector: { + no_documentation_combination: "This is no documentation available for this tag combination", + no_documentation_key: "This is no documentation available for this key", + new_tag: "New Tag" + }, + + view_on_osm: "View on OSM", + + zoom_in_edit: "zoom in to edit the map", + + edit_tags: "Edit tags", + + geocoder: { + "find_location": "Find A Location", + "find_a_place": "find a place" + }, + + description: "Description", + + logout: "logout", + + layerswitcher: { + title: "Background", + description: "Background Settings", + percent_brightness: "{opacity}% brightness", + fix_misalignment: "Fix misalignment", + reset: "reset" + } +}; From 843baa11c05f0fe29d07c43ef39cea03fe7e506f Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sat, 9 Feb 2013 15:11:19 -0800 Subject: [PATCH 289/415] graph -> core This matches how it's described in ARCHITECTURE.md. --- Makefile | 2 +- combobox.html | 12 +++++------ index.html | 14 ++++++------- js/id/{graph => core}/difference.js | 0 js/id/{graph => core}/entity.js | 0 js/id/{graph => core}/graph.js | 0 js/id/{graph => core}/history.js | 0 js/id/{graph => core}/node.js | 0 js/id/{graph => core}/relation.js | 0 js/id/{graph => core}/way.js | 0 test/index.html | 28 ++++++++++++------------- test/index_packaged.html | 14 ++++++------- test/rendering.html | 12 +++++------ test/spec/{graph => core}/difference.js | 0 test/spec/{graph => core}/entity.js | 0 test/spec/{graph => core}/graph.js | 0 test/spec/{graph => core}/history.js | 0 test/spec/{graph => core}/node.js | 0 test/spec/{graph => core}/relation.js | 0 test/spec/{graph => core}/way.js | 0 20 files changed, 41 insertions(+), 41 deletions(-) rename js/id/{graph => core}/difference.js (100%) rename js/id/{graph => core}/entity.js (100%) rename js/id/{graph => core}/graph.js (100%) rename js/id/{graph => core}/history.js (100%) rename js/id/{graph => core}/node.js (100%) rename js/id/{graph => core}/relation.js (100%) rename js/id/{graph => core}/way.js (100%) rename test/spec/{graph => core}/difference.js (100%) rename test/spec/{graph => core}/entity.js (100%) rename test/spec/{graph => core}/graph.js (100%) rename test/spec/{graph => core}/history.js (100%) rename test/spec/{graph => core}/node.js (100%) rename test/spec/{graph => core}/relation.js (100%) rename test/spec/{graph => core}/way.js (100%) diff --git a/Makefile b/Makefile index 30c179741..826337109 100644 --- a/Makefile +++ b/Makefile @@ -44,7 +44,7 @@ all: \ js/id/modes/*.js \ js/id/operations.js \ js/id/operations/*.js \ - js/id/graph/*.js \ + js/id/core/*.js \ js/id/renderer/*.js \ js/id/svg.js \ js/id/svg/*.js \ diff --git a/combobox.html b/combobox.html index 92949ecfa..63a2c9757 100644 --- a/combobox.html +++ b/combobox.html @@ -114,12 +114,12 @@ - - - - - - + + + + + + diff --git a/index.html b/index.html index 0701835f3..40a424899 100644 --- a/index.html +++ b/index.html @@ -130,13 +130,13 @@ - - - - - - - + + + + + + + diff --git a/js/id/graph/difference.js b/js/id/core/difference.js similarity index 100% rename from js/id/graph/difference.js rename to js/id/core/difference.js diff --git a/js/id/graph/entity.js b/js/id/core/entity.js similarity index 100% rename from js/id/graph/entity.js rename to js/id/core/entity.js diff --git a/js/id/graph/graph.js b/js/id/core/graph.js similarity index 100% rename from js/id/graph/graph.js rename to js/id/core/graph.js diff --git a/js/id/graph/history.js b/js/id/core/history.js similarity index 100% rename from js/id/graph/history.js rename to js/id/core/history.js diff --git a/js/id/graph/node.js b/js/id/core/node.js similarity index 100% rename from js/id/graph/node.js rename to js/id/core/node.js diff --git a/js/id/graph/relation.js b/js/id/core/relation.js similarity index 100% rename from js/id/graph/relation.js rename to js/id/core/relation.js diff --git a/js/id/graph/way.js b/js/id/core/way.js similarity index 100% rename from js/id/graph/way.js rename to js/id/core/way.js diff --git a/test/index.html b/test/index.html index ca610d714..52e13f03d 100644 --- a/test/index.html +++ b/test/index.html @@ -124,13 +124,13 @@ - - - - - - - + + + + + + + @@ -163,13 +163,13 @@ - - - - - - - + + + + + + + diff --git a/test/index_packaged.html b/test/index_packaged.html index 1102336d9..cd3451357 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -43,13 +43,13 @@ - - - - - - - + + + + + + + diff --git a/test/rendering.html b/test/rendering.html index ff624305a..cd75a848b 100644 --- a/test/rendering.html +++ b/test/rendering.html @@ -26,12 +26,12 @@ - - - - - - + + + + + +
diff --git a/test/spec/graph/difference.js b/test/spec/core/difference.js similarity index 100% rename from test/spec/graph/difference.js rename to test/spec/core/difference.js diff --git a/test/spec/graph/entity.js b/test/spec/core/entity.js similarity index 100% rename from test/spec/graph/entity.js rename to test/spec/core/entity.js diff --git a/test/spec/graph/graph.js b/test/spec/core/graph.js similarity index 100% rename from test/spec/graph/graph.js rename to test/spec/core/graph.js diff --git a/test/spec/graph/history.js b/test/spec/core/history.js similarity index 100% rename from test/spec/graph/history.js rename to test/spec/core/history.js diff --git a/test/spec/graph/node.js b/test/spec/core/node.js similarity index 100% rename from test/spec/graph/node.js rename to test/spec/core/node.js diff --git a/test/spec/graph/relation.js b/test/spec/core/relation.js similarity index 100% rename from test/spec/graph/relation.js rename to test/spec/core/relation.js diff --git a/test/spec/graph/way.js b/test/spec/core/way.js similarity index 100% rename from test/spec/graph/way.js rename to test/spec/core/way.js From 5fb4fc8521f11f179819d693cacbe8b5e6825a51 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Sat, 9 Feb 2013 18:23:07 -0500 Subject: [PATCH 290/415] Add locale readme --- locale/README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 locale/README.md diff --git a/locale/README.md b/locale/README.md new file mode 100644 index 000000000..1f97112ed --- /dev/null +++ b/locale/README.md @@ -0,0 +1,18 @@ +# Translations + +At this stage in its development, iD is using an extremely minimal, simple +system for translations. This directory contains languages according to +code (de: German, fr: French, etc). + +To contribute: + +If you're technically-minded, clone this repository and edit the necessary +file, and you can preview your changes in-place if your system language is +set. Check out [the contributing guide for submitting changes](https://github.com/systemed/iD/blob/master/CONTRIBUTING.md). + +If you aren't, you can still contribute! You'll still need a GitHub account, but +you can just browse to your language's file here, +click 'Edit', and edit each translated string. + +Contributions to translations are under the same liberal +license as iD itself, [wtfpl](http://www.wtfpl.net/). From cd470675e23d433b66faad8c9e435743f01ccd77 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Sat, 9 Feb 2013 18:27:18 -0500 Subject: [PATCH 291/415] Display name fix --- js/id/ui/geocoder.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/id/ui/geocoder.js b/js/id/ui/geocoder.js index c873e1c10..7f1a6ca4c 100644 --- a/js/id/ui/geocoder.js +++ b/js/id/ui/geocoder.js @@ -32,7 +32,7 @@ iD.ui.geocoder = function() { }) .append('a') .text(function(d) { - if (d.display_name > 80) { + if (d.display_name.length > 80) { return d.display_name.substr(0, 80) + '…'; } else { return d.display_name; From ccb5b81645ff2feb3b32e6ea116dd7c67d28c107 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sat, 9 Feb 2013 15:35:25 -0800 Subject: [PATCH 292/415] Clean up geocoder Pass context directly; fix indentation; i18n. --- js/id/ui.js | 2 +- js/id/ui/geocoder.js | 45 +++++++++++++------------------------------- locale/en.js | 5 +++-- 3 files changed, 17 insertions(+), 35 deletions(-) diff --git a/js/id/ui.js b/js/id/ui.js index 81797b731..3b13c2847 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -128,7 +128,7 @@ iD.ui = function(context) { } container.append('div').attr('class', 'geocode-control map-control') - .call(iD.ui.geocoder().map(map).context(context)); + .call(iD.ui.geocoder(context)); container.append('div').attr('class', 'map-control layerswitcher-control') .call(iD.ui.layerswitcher(context)); diff --git a/js/id/ui/geocoder.js b/js/id/ui/geocoder.js index 7f1a6ca4c..04f68f1ba 100644 --- a/js/id/ui/geocoder.js +++ b/js/id/ui/geocoder.js @@ -1,7 +1,4 @@ -iD.ui.geocoder = function() { - - var map, context; - +iD.ui.geocoder = function(context) { function resultExtent(bounds) { return new iD.geo.Extent( [parseFloat(bounds[3]), parseFloat(bounds[0])], @@ -20,7 +17,7 @@ iD.ui.geocoder = function() { return iD.ui.flash(context.container()) .select('.content') .append('h3') - .text('No location found for "' + searchVal + '"'); + .text(t('geocoder.no_results', {name: searchVal})); } else if (resp.length > 1) { var spans = resultsList.selectAll('span') .data(resp, function (d) { return d.place_id; }); @@ -53,19 +50,11 @@ iD.ui.geocoder = function() { function applyBounds(extent) { hide(); + var map = context.map(); map.extent(extent); if (map.zoom() > 19) map.zoom(19); } - function clickoutside(selection) { - selection - .on('click.geocoder-inside', function() { - return d3.event.stopPropagation(); - }); - context.container().on('click.geocoder-outside', hide); - } - - function show() { setVisible(true); } function hide() { setVisible(false); } function toggle() { setVisible(gcForm.classed('hide')); } @@ -79,34 +68,26 @@ iD.ui.geocoder = function() { var button = selection.append('button') .attr('tabindex', -1) - .attr('title', t('geocoder.find_location')) + .attr('title', t('geocoder.title')) .html('') .on('click', toggle); var gcForm = selection.append('form'); - var inputNode = gcForm.attr('class','content fillD map-overlay hide') + var inputNode = gcForm.attr('class', 'content fillD map-overlay hide') .append('input') - .attr({ type: 'text', placeholder: t('geocoder.find_a_place') }) - .on('keydown', keydown); + .attr({ type: 'text', placeholder: t('geocoder.placeholder') }) + .on('keydown', keydown); var resultsList = selection.append('div') - .attr('class','content fillD map-overlay hide'); + .attr('class', 'content fillD map-overlay hide'); - selection.call(clickoutside); + selection.on('click.geocoder-inside', function() { + return d3.event.stopPropagation(); + }); + + context.container().on('click.geocoder-outside', hide); } - geocoder.map = function(_) { - if (!arguments.length) return map; - map = _; - return geocoder; - }; - - geocoder.context = function(_) { - if (!arguments.length) return context; - context = _; - return geocoder; - }; - return geocoder; }; diff --git a/locale/en.js b/locale/en.js index 6c4f8bc2b..1f354013b 100644 --- a/locale/en.js +++ b/locale/en.js @@ -169,8 +169,9 @@ locale.en = { edit_tags: "Edit tags", geocoder: { - "find_location": "Find A Location", - "find_a_place": "find a place" + title: "Find A Place", + placeholder: "find a place", + no_results: "Couldn't locate a place named '{name}'" }, description: "Description", From 8c72ebc3cdef05bf8f527267053b879c84945dc6 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sat, 9 Feb 2013 15:49:24 -0800 Subject: [PATCH 293/415] Fix fr locale --- locale/fr.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locale/fr.js b/locale/fr.js index 6c4f8bc2b..9bd37e688 100644 --- a/locale/fr.js +++ b/locale/fr.js @@ -1,4 +1,4 @@ -locale.en = { +locale.fr = { modes: { add_area: { title: "Area", From b9fb37e1e4e2ae0d454026de7de043ec315c320c Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sat, 9 Feb 2013 15:59:59 -0800 Subject: [PATCH 294/415] i18n --- js/id/ui.js | 40 +++++++++++++++++++++++++--------------- locale/en.js | 5 +++++ 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/js/id/ui.js b/js/id/ui.js index 3b13c2847..57063a8b9 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -7,9 +7,7 @@ iD.ui = function(context) { map = context.map(); if (!iD.detect().support) { - container.html('This editor is supported in Firefox, Chrome, Safari, Opera, ' + - 'and Internet Explorer 9 and above. Please upgrade your browser ' + - 'or use Potlatch 2 to edit the map.') + container.text(t('browser_notice')) .style('text-align:center;font-style:italic;'); return; } @@ -110,7 +108,7 @@ iD.ui = function(context) { var zoom = container.append('div') .attr('class', 'zoombuttons map-control') .selectAll('button') - .data([['zoom-in', '+', map.zoomIn, 'Zoom In'], ['zoom-out', '-', map.zoomOut, 'Zoom Out']]) + .data([['zoom-in', '+', map.zoomIn, t('zoom-in')], ['zoom-out', '-', map.zoomOut, t('zoom-out')]]) .enter() .append('button') .attr('tabindex', -1) @@ -154,21 +152,33 @@ iD.ui = function(context) { var linkList = aboutList.append('ul') .attr('id','about') .attr('class','pad1 fillD about-block link-list'); - linkList.append('li').append('a').attr('target', '_blank') - .attr('href', 'http://github.com/systemed/iD').text(iD.version); - linkList.append('li').append('a').attr('target', '_blank') - .attr('href', 'http://github.com/systemed/iD/issues').text('report a bug'); - var imagery = linkList.append('li').attr('id', 'attribution'); - imagery.append('span').text('imagery'); + linkList.append('li') + .append('a') + .attr('target', '_blank') + .attr('href', 'http://github.com/systemed/iD') + .text(iD.version); + + linkList.append('li') + .append('a') + .attr('target', '_blank') + .attr('href', 'http://github.com/systemed/iD/issues') + .text(t('report_a_bug')); + + var imagery = linkList.append('li') + .attr('id', 'attribution'); + + imagery.append('span') + .text('imagery'); + imagery .append('span') - .attr('class', 'provided-by'); + .attr('class', 'provided-by'); linkList.append('li') .attr('class', 'source-switch') .append('a').attr('href', '#') - .text('live') + .text(t('live')) .classed('live', true) .on('click.editor', function() { d3.event.preventDefault(); @@ -176,12 +186,12 @@ iD.ui = function(context) { map.flush(); context.connection() .url('http://api06.dev.openstreetmap.org'); - d3.select(this).text('dev').classed('live', false); + d3.select(this).text(t('dev')).classed('live', false); } else { map.flush(); context.connection() .url('http://www.openstreetmap.org'); - d3.select(this).text('live').classed('live', true); + d3.select(this).text(t('live')).classed('live', true); } }); @@ -198,7 +208,7 @@ iD.ui = function(context) { window.onbeforeunload = function() { history.save(); - if (history.hasChanges()) return 'You have unsaved changes'; + if (history.hasChanges()) return t('unsaved_changes'); }; history.on('change.editor', function() { diff --git a/locale/en.js b/locale/en.js index 1f354013b..ff6e0b8f2 100644 --- a/locale/en.js +++ b/locale/en.js @@ -141,6 +141,7 @@ locale.en = { }, save: "Save", + unsaved_changes: "You have unsaved changes", save_help: "Save changes to OpenStreetMap, making them visible to other users", no_changes: "You don't have any changes to save.", save_error: "An error occurred while trying to save", @@ -178,6 +179,10 @@ locale.en = { logout: "logout", + live: "live", + dev: "dev", + report_a_bug: "report a bug", + layerswitcher: { title: "Background", description: "Background Settings", From 5feb9dea5da859624cd3414f2ac30a8be8ee86fa Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sat, 9 Feb 2013 16:52:08 -0800 Subject: [PATCH 295/415] Make contributors i18n-friendly --- js/id/ui.js | 18 ++++----------- js/id/ui/contributors.js | 47 ++++++++++++++++++++-------------------- locale/en.js | 5 +++++ 3 files changed, 33 insertions(+), 37 deletions(-) diff --git a/js/id/ui.js b/js/id/ui.js index 57063a8b9..6c0b24ac2 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -62,10 +62,7 @@ iD.ui = function(context) { .message(false) .on('zoom', function() { map.zoom(16); }); - map.on('move.editor', _.debounce(function() { - disableTooHigh(); - contributors.call(iD.ui.contributors(context)); - }, 500)); + map.on('move.editor', _.debounce(disableTooHigh, 500)); buttons.append('span') .attr('class', function(d) { @@ -195,16 +192,9 @@ iD.ui = function(context) { } }); - var contributors = linkList.append('li') - .attr('id', 'user-list'); - contributors.append('span') - .attr('class', 'icon nearby icon-pre-text'); - contributors.append('span') - .text('Viewing contributions by '); - contributors.append('span') - .attr('class', 'contributor-list'); - contributors.append('span') - .attr('class', 'contributor-count'); + linkList.append('li') + .attr('id', 'user-list') + .call(iD.ui.contributors(context)); window.onbeforeunload = function() { history.save(); diff --git a/js/id/ui/contributors.js b/js/id/ui/contributors.js index e029bfd04..743235edf 100644 --- a/js/id/ui/contributors.js +++ b/js/id/ui/contributors.js @@ -1,7 +1,5 @@ iD.ui.contributors = function(context) { - - function contributors(selection) { - + function update(selection) { var users = {}, limit = 3, entities = context.graph().intersects(context.map().extent()); @@ -13,28 +11,25 @@ iD.ui.contributors = function(context) { var u = Object.keys(users), subset = u.slice(0, limit); - var l = selection - .select('.contributor-list') - .selectAll('a.user-link') - .data(subset, function(d) { return d; }); + selection.html('') + .append('span') + .attr('class', 'icon nearby icon-pre-text'); + var userList = d3.select(document.createElement('span')); - l.enter().append('a') + userList.selectAll() + .data(subset) + .enter() + .append('a') .attr('class', 'user-link') .attr('href', function(d) { return context.connection().userUrl(d); }) .attr('target', '_blank') .text(String); - l.exit().remove(); - - selection - .select('.contributor-count') - .html(''); - if (u.length > limit) { - selection - .select('.contributor-count') - .append('a') + var count = d3.select(document.createElement('span')); + + count.append('a') .attr('target', '_blank') .attr('href', function() { var ext = context.map().extent(); @@ -42,11 +37,13 @@ iD.ui.contributors = function(context) { ext[0][0], ext[0][1], ext[1][0], ext[1][1]]; }) - .text(' and ' + (u.length - limit) + ' others'); + .text(u.length - limit); + + selection.append('span') + .html(t('contributors.truncated_list', {users: userList.html(), count: count.html()})); } else { - selection - .select('.contributor-count') - .html(''); + selection.append('span') + .html(t('contributors.list', {users: userList.html()})); } if (!u.length) { @@ -54,9 +51,13 @@ iD.ui.contributors = function(context) { } else if (selection.style('opacity') === '0') { selection.transition().style('opacity', 1); } - } - return contributors; + return function(selection) { + update(selection); + context.map().on('move.contributors', _.debounce(function() { + update(selection); + }, 500)); + }; }; diff --git a/locale/en.js b/locale/en.js index ff6e0b8f2..0bad1b751 100644 --- a/locale/en.js +++ b/locale/en.js @@ -189,5 +189,10 @@ locale.en = { percent_brightness: "{opacity}% brightness", fix_misalignment: "Fix misalignment", reset: "reset" + }, + + contributors: { + list: "Viewing contributions by {users}", + truncated_list: "Viewing contributions by {users} and {count} others" } }; From c56879b29eb5e5c963f91db3ec0c2e0997c8e45c Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sat, 9 Feb 2013 16:53:29 -0800 Subject: [PATCH 296/415] Update contributors on load (fixes #699) --- js/id/ui/contributors.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/js/id/ui/contributors.js b/js/id/ui/contributors.js index 743235edf..596c576ad 100644 --- a/js/id/ui/contributors.js +++ b/js/id/ui/contributors.js @@ -56,6 +56,10 @@ iD.ui.contributors = function(context) { return function(selection) { update(selection); + context.connection().on('load.contributors', function() { + update(selection); + }); + context.map().on('move.contributors', _.debounce(function() { update(selection); }, 500)); From 65ab71c3d0e67a8dffb7665b2097f702c54b3595 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sat, 9 Feb 2013 17:04:39 -0800 Subject: [PATCH 297/415] Extract iD.ui.SourceSwitch --- index.html | 1 + js/id/ui.js | 18 +----------------- js/id/ui/source_switch.js | 25 +++++++++++++++++++++++++ locale/en.js | 7 +++++-- test/index.html | 1 + 5 files changed, 33 insertions(+), 19 deletions(-) create mode 100644 js/id/ui/source_switch.js diff --git a/index.html b/index.html index 40a424899..adbc28ef4 100644 --- a/index.html +++ b/index.html @@ -77,6 +77,7 @@ + diff --git a/js/id/ui.js b/js/id/ui.js index 6c0b24ac2..529d8bca5 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -174,23 +174,7 @@ iD.ui = function(context) { linkList.append('li') .attr('class', 'source-switch') - .append('a').attr('href', '#') - .text(t('live')) - .classed('live', true) - .on('click.editor', function() { - d3.event.preventDefault(); - if (d3.select(this).classed('live')) { - map.flush(); - context.connection() - .url('http://api06.dev.openstreetmap.org'); - d3.select(this).text(t('dev')).classed('live', false); - } else { - map.flush(); - context.connection() - .url('http://www.openstreetmap.org'); - d3.select(this).text(t('live')).classed('live', true); - } - }); + .call(iD.ui.SourceSwitch(context)); linkList.append('li') .attr('id', 'user-list') diff --git a/js/id/ui/source_switch.js b/js/id/ui/source_switch.js new file mode 100644 index 000000000..1b1bb9530 --- /dev/null +++ b/js/id/ui/source_switch.js @@ -0,0 +1,25 @@ +iD.ui.SourceSwitch = function(context) { + function click() { + d3.event.preventDefault(); + + var live = d3.select(this).classed('live'); + + context.map() + .flush(); + + context.connection() + .url(live ? 'http://api06.dev.openstreetmap.org' : 'http://www.openstreetmap.org'); + + d3.select(this) + .text(live ? t('source_switch.dev') : t('source_switch.live')) + .classed('live', !live); + } + + return function(selection) { + selection.append('a') + .attr('href', '#') + .text(t('source_switch.live')) + .classed('live', true) + .on('click', click); + } +}; diff --git a/locale/en.js b/locale/en.js index 0bad1b751..a2233551b 100644 --- a/locale/en.js +++ b/locale/en.js @@ -179,8 +179,6 @@ locale.en = { logout: "logout", - live: "live", - dev: "dev", report_a_bug: "report a bug", layerswitcher: { @@ -194,5 +192,10 @@ locale.en = { contributors: { list: "Viewing contributions by {users}", truncated_list: "Viewing contributions by {users} and {count} others" + }, + + source_switch: { + live: "live", + dev: "dev" } }; diff --git a/test/index.html b/test/index.html index 52e13f03d..056a0aff8 100644 --- a/test/index.html +++ b/test/index.html @@ -71,6 +71,7 @@ + From be387170f70cce0bcd9185325802168ae64cb816 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Sun, 10 Feb 2013 00:04:24 -0500 Subject: [PATCH 298/415] start Latvian translation --- index.html | 1 + locale/lv.js | 198 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 199 insertions(+) create mode 100644 locale/lv.js diff --git a/index.html b/index.html index adbc28ef4..89ba50fe1 100644 --- a/index.html +++ b/index.html @@ -146,6 +146,7 @@ + diff --git a/locale/lv.js b/locale/lv.js new file mode 100644 index 000000000..8cd194d39 --- /dev/null +++ b/locale/lv.js @@ -0,0 +1,198 @@ +locale.lv = { + modes: { + add_area: { + title: "Apgabals", + description: "Pievieno parkus, ēkas, ezerus un citus apgabalus.", + tail: "Klikšķiniet uz kartes, lai sāktu zīmēt apgablalu, piemēram parku, ezeru, vai ēku.", + key: "A" + }, + add_line: { + title: "Līnija", + description: "Līnijas var būt ceļi, ielas, takas vai pat kanāli.", + tail: "Klikšķiniet uz kartes, lai sāktu zīmēt līniju, piemēram ceļu vai taku.", + key: "L" + }, + add_point: { + title: "Punkts", + description: "Kafejnīcas, pieminekļi, un veikali var būt punkti.", + tail: "Klikšķiniet uz kartes, lai pievienotu interešu punktu.", + key: "P" + }, + browse: { + title: "Pārlūkot", + description: "Pārlūko karti.", + key: "B" + }, + draw_area: { + tail: "Klikšķiniet, lai pievinotu mezglus apgabalam. Lai beigtu zīmēt apgabalu, klikšķiniet uz sākuma mezgla." + }, + draw_line: { + tail: "Klikšķiniet, lai pievienotu mezglus līnijai. Lai savienotu ar citām linijām, klikšķiniet uz tām. Dubultklikšķis nobeidz līniju." + } + }, + + operations: { + add: { + annotation: { + point: "Punkts pievienots.", + vertex: "Mezgls pievienots līnijai." + } + }, + start: { + annotation: { + line: "Līnija iesākta.", + area: "Apgabals iesākts." + } + }, + 'continue': { + annotation: { + line: "Linija turpināta.", + area: "Apgabals turpināts." + } + }, + cancel_draw: { + annotation: "Zīmēšana atcelta." + }, + change_tags: { + annotation: "Apzīmējumi mainīti." + }, + circularize: { + title: "Circularize", + description: "Make this round.", + key: "O", + annotation: { + line: "Made a line circular.", + area: "Made an area circular." + } + }, + orthogonalize: { + title: "Orthogonalize", + description: "Square these corners.", + key: "Q", + annotation: { + line: "Squared the corners of a line.", + area: "Squared the corners of an area." + } + }, + 'delete': { + title: "Dzēst", + description: "Noņemt no kartes.", + key: "⌫", + annotation: { + point: "Punkts dzēsts.", + vertex: "Mezgls dzests.", + line: "Līnija dzēsta.", + area: "Apgabals dzēsts.", + relation: "Relācija dzēsta.", + multiple: "{n} objekti dzēsti." + } + }, + connect: { + annotation: { + point: "Līnija savienota ar punktu.", + vertex: "Līnija savienota ar otru.", + line: "Līnija savienota ar līniju.", + area: "Līnija savienota ar apgabalu." + } + }, + disconnect: { + title: "Atvienot", + description: "Atvieno līnijas.", + key: "D", + annotation: "Līnijas atvienotas." + }, + merge: { + title: "Sapludināt", + description: "Sapludināt līnijas.", + key: "C", + annotation: "{n} līnijas sapludinātas." + }, + move: { + title: "Pārvietot", + description: "Pārvieto objektu.", + key: "M", + annotation: { + point: "Punkts pārvietots.", + vertex: "Mezgls pārvietots.", + line: "Līnija pārvietota.", + area: "Apgabals pārvietots." + } + }, + reverse: { + title: "Mainīt virzienu", + description: "Maini līnijas virzienu.", + key: "V", + annotation: "Līnijas virziens mainīts." + }, + split: { + title: "Sadalīt", + description: "Sadali līniju pie ši punkta.", + key: "X", + annotation: "Līnija sadalīta." + } + }, + + validations: { + untagged_point: "Neapzīmēts punkts", + untagged_line: "Neapzīmēta līnija", + untagged_area: "Neapzīmēts apgabals", + tag_suggests_area: "Apzīmējums {tag} parasti lietots apgabaliem, bet objekts nav apgabals", + deprecated_tags: "Novecojūši apzīmējumi: {tags}" + }, + + save: "Saglabāt", + unsaved_changes: "Jums ir nesaglabātas maiņas", + save_help: "Saglabā maiņas, padarot tās redzamas citiem", + no_changes: "Jums nav maiņas ko saglabāt", + save_error: "Kļūda. Nevarēja saglabāt maiņas", + uploading_changes: "Augšupielādē", + just_edited: "Jūs nupat rediģējāt OpenStreetMap", + okay: "Labi", + + "zoom-in": "Pietuvināt", + "zoom-out": "Attālināt", + + nothing_to_undo: "Nekas ko atcelt", + nothing_to_redo: "Nekas ko atatsaukt", + + browser_notice: "This editor is supported in Firefox, Chrome, Safari, Opera, and Internet Explorer 9 and above. Please upgrade your browser or use Potlatch 2 to edit the map.", + + inspector: { + no_documentation_combination: "This is no documentation available for this tag combination", + no_documentation_key: "This is no documentation available for this key", + new_tag: "Jauns apzīmejums" + }, + + view_on_osm: "Apskatīt OSM lapu", + + zoom_in_edit: "zoom in to edit the map", + + edit_tags: "Rediģēt apzīmējumus", + + geocoder: { + title: "Atrast vietu", + placeholder: "atrod vietu", + no_results: "Nevarēja atrast vietu vārdā '{name}'" + }, + + description: "Apraksts", + + logout: "logout", + + live: "live", + dev: "dev", + report_a_bug: "ziņot par kļūdu", + + layerswitcher: { + title: "Fons", + description: "Fona iestatījumi", + percent_brightness: "{opacity}% gaišums", + fix_misalignment: "Labot fona nolīdzināšanu", + reset: "Pārstatīt" + }, + + contributors: { + list: "{users} papildinājumi redzāmi", + truncated_list: "{users} un {count} citu papildinājumi redzāmi" + } +}; From 72a29a14f44294ebe045c8d04e4d9e1f9083f31a Mon Sep 17 00:00:00 2001 From: Neogeografen Date: Sun, 10 Feb 2013 00:14:16 -0800 Subject: [PATCH 299/415] Started danish language --- locale/da.js | 201 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 locale/da.js diff --git a/locale/da.js b/locale/da.js new file mode 100644 index 000000000..217f597f6 --- /dev/null +++ b/locale/da.js @@ -0,0 +1,201 @@ +locale.da = { + modes: { + add_area: { + title: "Område", + description: "Tilføj parker, bygninger, søer, eller andre områder til kortet.", + tail: "Klik på kortet for at indtegne et område fx en park, sø eller bygning.", + key: "A" + }, + add_line: { + title: "Linje", + description: "Linjer kan være veje, gader eller stier selv kanaler kan være linjer.", + tail: "Klik på koret for at indtegne en vej, sti eller rute.", + key: "L" + }, + add_point: { + title: "Punkt", + description: "Restauranter, mindesmærker og postkasser er punkter.", + tail: "Klik på kortet for at tilføje et punkt.", + key: "P" + }, + browse: { + title: "Browse", + description: "Træk rundt og zoom på kortet.", + key: "B" + }, + draw_area: { + tail: "Klik her for at tilføje punkter til dit område. Click the first point to finish the area." + }, + draw_line: { + tail: "Click to add more points to the line. Click on other lines to connect to them, and double-click to end the line." + } + }, + + operations: { + add: { + annotation: { + point: "Added a point.", + vertex: "Added a node to a way." + } + }, + start: { + annotation: { + line: "Started a line.", + area: "Started an area." + } + }, + 'continue': { + annotation: { + line: "Continued a line.", + area: "Continued an area." + } + }, + cancel_draw: { + annotation: "Cancelled drawing." + }, + change_tags: { + annotation: "Changed tags." + }, + circularize: { + title: "Circularize", + description: "Make this round.", + key: "O", + annotation: { + line: "Made a line circular.", + area: "Made an area circular." + } + }, + orthogonalize: { + title: "Orthogonalize", + description: "Square these corners.", + key: "Q", + annotation: { + line: "Squared the corners of a line.", + area: "Squared the corners of an area." + } + }, + 'delete': { + title: "Delete", + description: "Remove this from the map.", + key: "⌫", + annotation: { + point: "Deleted a point.", + vertex: "Deleted a node from a way.", + line: "Deleted a line.", + area: "Deleted an area.", + relation: "Deleted a relation.", + multiple: "Deleted {n} objects." + } + }, + connect: { + annotation: { + point: "Connected a way to a point.", + vertex: "Connected a way to another.", + line: "Connected a way to a line.", + area: "Connected a way to an area." + } + }, + disconnect: { + title: "Disconnect", + description: "Disconnect these ways from each other.", + key: "D", + annotation: "Disconnected ways." + }, + merge: { + title: "Merge", + description: "Merge these lines.", + key: "C", + annotation: "Merged {n} lines." + }, + move: { + title: "Move", + description: "Move this to a different location.", + key: "M", + annotation: { + point: "Moved a point.", + vertex: "Moved a node in a way.", + line: "Moved a line.", + area: "Moved an area." + } + }, + reverse: { + title: "Reverse", + description: "Make this line go in the opposite direction.", + key: "V", + annotation: "Reversed a line." + }, + split: { + title: "Split", + description: "Split this into two ways at this point.", + key: "X", + annotation: "Split a way." + } + }, + + validations: { + untagged_point: "Untagged point which is not part of a line or area", + untagged_line: "Untagged line", + untagged_area: "Untagged area", + tag_suggests_area: "The tag {tag} suggests line should be area, but it is not an area", + deprecated_tags: "Deprecated tags: {tags}" + }, + + save: "Save", + unsaved_changes: "You have unsaved changes", + save_help: "Save changes to OpenStreetMap, making them visible to other users", + no_changes: "You don't have any changes to save.", + save_error: "An error occurred while trying to save", + uploading_changes: "Uploading changes to OpenStreetMap.", + just_edited: "You Just Edited OpenStreetMap!", + okay: "Okay", + + "zoom-in": "Zoom ind", + "zoom-out": "Zoom ud", + + nothing_to_undo: "Nothing to undo.", + nothing_to_redo: "Nothing to redo.", + + browser_notice: "This editor is supported in Firefox, Chrome, Safari, Opera, and Internet Explorer 9 and above. Please upgrade your browser or use Potlatch 2 to edit the map.", + + inspector: { + no_documentation_combination: "This is no documentation available for this tag combination", + no_documentation_key: "This is no documentation available for this key", + new_tag: "Nyt Tag" + }, + + view_on_osm: "Vis på OSM", + + zoom_in_edit: "zoom ind for at rette kortet", + + edit_tags: "Ret tags", + + geocoder: { + title: "Find et sted", + placeholder: "find et sted", + no_results: "Kunne ikke finde '{name}'" + }, + + description: "Description", + + logout: "log ud", + + report_a_bug: "report a bug", + + layerswitcher: { + title: "Background", + description: "Background Settings", + percent_brightness: "{opacity}% brightness", + fix_misalignment: "Fix misalignment", + reset: "nulstill" + }, + + contributors: { + list: "Vis bidrag fra {users}", + truncated_list: "Vis bidrag fra {users} og {count} andre" + }, + + source_switch: { + live: "live", + dev: "dev" + } +}; From 0481cdaa1fd1bd33a8a59539b44d00058a7ce786 Mon Sep 17 00:00:00 2001 From: richlv Date: Sun, 10 Feb 2013 12:06:28 +0200 Subject: [PATCH 300/415] Update locale/lv.js minor typo & style fixes --- locale/lv.js | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/locale/lv.js b/locale/lv.js index 8cd194d39..2c28b400f 100644 --- a/locale/lv.js +++ b/locale/lv.js @@ -3,13 +3,13 @@ locale.lv = { add_area: { title: "Apgabals", description: "Pievieno parkus, ēkas, ezerus un citus apgabalus.", - tail: "Klikšķiniet uz kartes, lai sāktu zīmēt apgablalu, piemēram parku, ezeru, vai ēku.", + tail: "Klikšķiniet uz kartes, lai sāktu zīmēt apgabalu, piemēram parku, ezeru, vai ēku.", key: "A" }, add_line: { title: "Līnija", description: "Līnijas var būt ceļi, ielas, takas vai pat kanāli.", - tail: "Klikšķiniet uz kartes, lai sāktu zīmēt līniju, piemēram ceļu vai taku.", + tail: "Klikšķiniet uz kartes, lai sāktu zīmēt līniju, piemēram, ceļu vai taku.", key: "L" }, add_point: { @@ -120,13 +120,13 @@ locale.lv = { }, reverse: { title: "Mainīt virzienu", - description: "Maini līnijas virzienu.", + description: "Mainīt līnijas virzienu.", key: "V", annotation: "Līnijas virziens mainīts." }, split: { title: "Sadalīt", - description: "Sadali līniju pie ši punkta.", + description: "Sadalīt līniju pie šī punkta.", key: "X", annotation: "Līnija sadalīta." } @@ -136,14 +136,14 @@ locale.lv = { untagged_point: "Neapzīmēts punkts", untagged_line: "Neapzīmēta līnija", untagged_area: "Neapzīmēts apgabals", - tag_suggests_area: "Apzīmējums {tag} parasti lietots apgabaliem, bet objekts nav apgabals", - deprecated_tags: "Novecojūši apzīmējumi: {tags}" + tag_suggests_area: "Apzīmējums {tag} parasti tiek lietots apgabaliem, bet objekts nav apgabals", + deprecated_tags: "Novecojuši apzīmējumi: {tags}" }, save: "Saglabāt", - unsaved_changes: "Jums ir nesaglabātas maiņas", - save_help: "Saglabā maiņas, padarot tās redzamas citiem", - no_changes: "Jums nav maiņas ko saglabāt", + unsaved_changes: "Jums ir nesaglabātas izmaiņas", + save_help: "Saglabā izmaiņas, padarot tās redzamas citiem", + no_changes: "Jums nav izmaiņu, ko saglabāt", save_error: "Kļūda. Nevarēja saglabāt maiņas", uploading_changes: "Augšupielādē", just_edited: "Jūs nupat rediģējāt OpenStreetMap", @@ -152,8 +152,8 @@ locale.lv = { "zoom-in": "Pietuvināt", "zoom-out": "Attālināt", - nothing_to_undo: "Nekas ko atcelt", - nothing_to_redo: "Nekas ko atatsaukt", + nothing_to_undo: "Nav nekā, ko atcelt", + nothing_to_redo: "Nav nekā, ko atsaukt", browser_notice: "This editor is supported in Firefox, Chrome, Safari, Opera, and Internet Explorer 9 and above. Please upgrade your browser or use Potlatch 2 to edit the map.", @@ -171,8 +171,8 @@ locale.lv = { geocoder: { title: "Atrast vietu", - placeholder: "atrod vietu", - no_results: "Nevarēja atrast vietu vārdā '{name}'" + placeholder: "meklē vietu", + no_results: "Nevarēja atrast vietu '{name}'" }, description: "Apraksts", @@ -192,7 +192,7 @@ locale.lv = { }, contributors: { - list: "{users} papildinājumi redzāmi", - truncated_list: "{users} un {count} citu papildinājumi redzāmi" + list: "{users} papildinājumi redzami", + truncated_list: "{users} un {count} citu papildinājumi redzami" } }; From b639de3f20798cc9c7be363fd6e1cb8729856414 Mon Sep 17 00:00:00 2001 From: metalalp Date: Sun, 10 Feb 2013 07:12:29 -0800 Subject: [PATCH 301/415] Creating Turkish translation --- locale/tr.js | 201 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 locale/tr.js diff --git a/locale/tr.js b/locale/tr.js new file mode 100644 index 000000000..2b84a8b0d --- /dev/null +++ b/locale/tr.js @@ -0,0 +1,201 @@ +locale.tr = { + modes: { + add_area: { + title: "Area", + description: "Add parks, buildings, lakes, or other areas to the map.", + tail: "Click on the map to start drawing an area, like a park, lake, or building.", + key: "A" + }, + add_line: { + title: "Line", + description: "Lines can be highways, streets, pedestrian paths, or even canals.", + tail: "Click on the map to start drawing an road, path, or route.", + key: "L" + }, + add_point: { + title: "Point", + description: "Restaurants, monuments, and postal boxes are points.", + tail: "Click on the map to add a point.", + key: "P" + }, + browse: { + title: "Browse", + description: "Pan and zoom the map.", + key: "B" + }, + draw_area: { + tail: "Click to add points to your area. Click the first point to finish the area." + }, + draw_line: { + tail: "Click to add more points to the line. Click on other lines to connect to them, and double-click to end the line." + } + }, + + operations: { + add: { + annotation: { + point: "Added a point.", + vertex: "Added a node to a way." + } + }, + start: { + annotation: { + line: "Started a line.", + area: "Started an area." + } + }, + 'continue': { + annotation: { + line: "Continued a line.", + area: "Continued an area." + } + }, + cancel_draw: { + annotation: "Cancelled drawing." + }, + change_tags: { + annotation: "Changed tags." + }, + circularize: { + title: "Circularize", + description: "Make this round.", + key: "O", + annotation: { + line: "Made a line circular.", + area: "Made an area circular." + } + }, + orthogonalize: { + title: "Orthogonalize", + description: "Square these corners.", + key: "Q", + annotation: { + line: "Squared the corners of a line.", + area: "Squared the corners of an area." + } + }, + 'delete': { + title: "Delete", + description: "Remove this from the map.", + key: "⌫", + annotation: { + point: "Deleted a point.", + vertex: "Deleted a node from a way.", + line: "Deleted a line.", + area: "Deleted an area.", + relation: "Deleted a relation.", + multiple: "Deleted {n} objects." + } + }, + connect: { + annotation: { + point: "Connected a way to a point.", + vertex: "Connected a way to another.", + line: "Connected a way to a line.", + area: "Connected a way to an area." + } + }, + disconnect: { + title: "Disconnect", + description: "Disconnect these ways from each other.", + key: "D", + annotation: "Disconnected ways." + }, + merge: { + title: "Merge", + description: "Merge these lines.", + key: "C", + annotation: "Merged {n} lines." + }, + move: { + title: "Move", + description: "Move this to a different location.", + key: "M", + annotation: { + point: "Moved a point.", + vertex: "Moved a node in a way.", + line: "Moved a line.", + area: "Moved an area." + } + }, + reverse: { + title: "Reverse", + description: "Make this line go in the opposite direction.", + key: "V", + annotation: "Reversed a line." + }, + split: { + title: "Split", + description: "Split this into two ways at this point.", + key: "X", + annotation: "Split a way." + } + }, + + validations: { + untagged_point: "Untagged point which is not part of a line or area", + untagged_line: "Untagged line", + untagged_area: "Untagged area", + tag_suggests_area: "The tag {tag} suggests line should be area, but it is not an area", + deprecated_tags: "Deprecated tags: {tags}" + }, + + save: "Save", + unsaved_changes: "You have unsaved changes", + save_help: "Save changes to OpenStreetMap, making them visible to other users", + no_changes: "You don't have any changes to save.", + save_error: "An error occurred while trying to save", + uploading_changes: "Uploading changes to OpenStreetMap.", + just_edited: "You Just Edited OpenStreetMap!", + okay: "Okay", + + "zoom-in": "Zoom In", + "zoom-out": "Zoom Out", + + nothing_to_undo: "Nothing to undo.", + nothing_to_redo: "Nothing to redo.", + + browser_notice: "This editor is supported in Firefox, Chrome, Safari, Opera, and Internet Explorer 9 and above. Please upgrade your browser or use Potlatch 2 to edit the map.", + + inspector: { + no_documentation_combination: "This is no documentation available for this tag combination", + no_documentation_key: "This is no documentation available for this key", + new_tag: "New Tag" + }, + + view_on_osm: "View on OSM", + + zoom_in_edit: "zoom in to edit the map", + + edit_tags: "Edit tags", + + geocoder: { + title: "Find A Place", + placeholder: "find a place", + no_results: "Couldn't locate a place named '{name}'" + }, + + description: "Description", + + logout: "logout", + + report_a_bug: "report a bug", + + layerswitcher: { + title: "Background", + description: "Background Settings", + percent_brightness: "{opacity}% brightness", + fix_misalignment: "Fix misalignment", + reset: "reset" + }, + + contributors: { + list: "Viewing contributions by {users}", + truncated_list: "Viewing contributions by {users} and {count} others" + }, + + source_switch: { + live: "live", + dev: "dev" + } +}; From bed7ec109ebd4befd3574de6b6583b9ef4fe030f Mon Sep 17 00:00:00 2001 From: Christian Mayer Date: Sun, 10 Feb 2013 18:01:41 +0100 Subject: [PATCH 302/415] Initial translation to German --- locale/de.js | 180 +++++++++++++++++++++++++-------------------------- 1 file changed, 90 insertions(+), 90 deletions(-) diff --git a/locale/de.js b/locale/de.js index 091ef9a07..a2e3f6e7d 100644 --- a/locale/de.js +++ b/locale/de.js @@ -1,187 +1,187 @@ locale.de = { modes: { add_area: { - title: "Area", - description: "Add parks, buildings, lakes, or other areas to the map.", - tail: "Click on the map to start drawing an area, like a park, lake, or building.", + title: "Fläche", + description: "Füge Parks, Gebäude, Seen oder andere Flächen zur Karte hinzu.", + tail: "Klicke in die Karte um das Zeichnen einer Fläche wie einen Park, einen See oder Gebäude zu starten.", key: "A" }, add_line: { - title: "Line", - description: "Lines can be highways, streets, pedestrian paths, or even canals.", - tail: "Click on the map to start drawing an road, path, or route.", + title: "Linie", + description: "Linien können Autobahnen, Straßen, Fußwege oder sogar Kanäle sein.", + tail: "Klicke in die Karte um das Zeichnen einer Straße eines Pfades oder einer Route zu starten.", key: "L" }, add_point: { - title: "Point", - description: "Restaurants, monuments, and postal boxes are points.", - tail: "Click on the map to add a point.", + title: "Punkt", + description: "Restaurants, Denkmäler und Briefkästen sind Punkte", + tail: "Klicke in die Karte, um einen Punkt hinzuzufügen.", key: "P" }, browse: { - title: "Browse", - description: "Pan and zoom the map.", + title: "Navigation", + description: "Verschieben und Vergrößern/Verkleinern des Kartenausschnitts.", key: "B" }, draw_area: { - tail: "Click to add points to your area. Click the first point to finish the area." + tail: "Klicke, um Punkte zur Fläche hinzuzufügen. Klicke den ersten Punkt, um die Fläche abzuschließen." }, draw_line: { - tail: "Click to add more points to the line. Click on other lines to connect to them, and double-click to end the line." + tail: "Klicke, um mehr Punkte zur Linie hizuzufügen. Klicke auf eine andere Linie um die Linien zu verbinden, und klicke doppelt, um die Linie zu beenden." } }, operations: { add: { annotation: { - point: "Added a point.", - vertex: "Added a node to a way." + point: "Punkt hinzugefügt.", + vertex: "Stützpunkt einem Weg hinzugefügt." } }, start: { annotation: { - line: "Started a line.", - area: "Started an area." + line: "Linie begonnen.", + area: "Fläche begonnen." } }, 'continue': { annotation: { - line: "Continued a line.", - area: "Continued an area." + line: "Linie fortgesetzt.", + area: "Fläche fortgesetzt." } }, cancel_draw: { - annotation: "Cancelled drawing." + annotation: "Zeichnen abgebrochen." }, change_tags: { - annotation: "Changed tags." + annotation: "Tags verändert." }, circularize: { - title: "Circularize", - description: "Make this round.", + title: "Abrunden", + description: "Runde dies ab.", key: "O", annotation: { - line: "Made a line circular.", - area: "Made an area circular." + line: "Runde eine Linie ab.", + area: "Runde eine Fläche ab." } }, orthogonalize: { - title: "Orthogonalize", - description: "Square these corners.", + title: "Rechtwinkligkeit herstellen", + description: "Diese Ecken rechtwinklig ausrichten.", key: "Q", annotation: { - line: "Squared the corners of a line.", - area: "Squared the corners of an area." + line: "Die Ecken einer Linie rechtwinklig ausgerichtet.", + area: "Die Ecken einer Fläche rechtwinklig ausgerichtet." } }, 'delete': { - title: "Delete", - description: "Remove this from the map.", + title: "Löschen", + description: "Lösche dies aus der Karte.", key: "⌫", annotation: { - point: "Deleted a point.", - vertex: "Deleted a node from a way.", - line: "Deleted a line.", - area: "Deleted an area.", - relation: "Deleted a relation.", - multiple: "Deleted {n} objects." + point: "Punkt gelöscht.Deleted a point.", + vertex: "Stützpunkt aus einem Weg gelöscht.", + line: "Linie gelöscht.", + area: "Fläche gelöscht.", + relation: "Relation gelöscht.", + multiple: "{n} Objekte gelöscht." } }, connect: { annotation: { - point: "Connected a way to a point.", - vertex: "Connected a way to another.", - line: "Connected a way to a line.", - area: "Connected a way to an area." + point: "Weg mit einem Punkt verbunden.", + vertex: "Weg mit einem anderem Weg verbunden.", + line: "Weg mit einer Linie verbunden.", + area: "Weg mit einer Fläche verbunden." } }, disconnect: { - title: "Disconnect", - description: "Disconnect these ways from each other.", + title: "Trennen", + description: "Trenne diese Wege voneinander.", key: "D", - annotation: "Disconnected ways." + annotation: "Wege getrennt." }, merge: { - title: "Merge", - description: "Merge these lines.", + title: "Vereinigen", + description: "Vereinige diese Linien.", key: "C", - annotation: "Merged {n} lines." + annotation: "{n} Linien vereinigt." }, move: { - title: "Move", - description: "Move this to a different location.", + title: "Verschieben", + description: "Verschiebe dieses Objekt an einen anderen Ort.", key: "M", annotation: { - point: "Moved a point.", - vertex: "Moved a node in a way.", - line: "Moved a line.", - area: "Moved an area." + point: "Punkt verschoben.", + vertex: "Stützpunkt in einen Weg veschoben.", + line: "Linie verschoben.", + area: "Fläche verschoben." } }, reverse: { - title: "Reverse", - description: "Make this line go in the opposite direction.", + title: "Umkehren", + description: "Ändere die Richtung diese Linie.", key: "V", - annotation: "Reversed a line." + annotation: "Linienrichtung umgekehrt." }, split: { - title: "Split", - description: "Split this into two ways at this point.", + title: "Teilen", + description: "Teile dies in zwei Wege an diesem Punkt.", key: "X", - annotation: "Split a way." + annotation: "Weg geteilt." } }, validations: { - untagged_point: "Untagged point which is not part of a line or area", - untagged_line: "Untagged line", - untagged_area: "Untagged area", - tag_suggests_area: "The tag {tag} suggests line should be area, but it is not an area", - deprecated_tags: "Deprecated tags: {tags}" + untagged_point: "Punkt, der kein Teil einer Linie oder Fläche ist entmarkiert", + untagged_line: "Linie entmarkiert", + untagged_area: "Fläche entmarkiert", + tag_suggests_area: "Die Markierung {tag} suggeriert eine Fläche, ist aber keine Fläche", + deprecated_tags: "Abgelehnte Markierungen: {tags}" }, - save: "Save", - save_help: "Save changes to OpenStreetMap, making them visible to other users", - no_changes: "You don't have any changes to save.", - save_error: "An error occurred while trying to save", - uploading_changes: "Uploading changes to OpenStreetMap.", - just_edited: "You Just Edited OpenStreetMap!", + save: "Speichern", + save_help: "Speichere Änderungen zu OpenStreetMap, so dass sie für andere Nutzer sichtbar werden", + no_changes: "Sie haben keine Änderungen zum Speichern.", + save_error: "Es ist ein Fehler aufgetreten beim Versuch des Speicherns", + uploading_changes: "Lade Änderungen zu OpenStreetMap.", + just_edited: "Sie haben gerade OpenStreetMap editiert!", okay: "Okay", - "zoom-in": "Zoom In", - "zoom-out": "Zoom Out", + "zoom-in": "Hineinzoomen", + "zoom-out": "Herauszoomen", - nothing_to_undo: "Nothing to undo.", - nothing_to_redo: "Nothing to redo.", + nothing_to_undo: "Nichts zum Rückgängigmachen.", + nothing_to_redo: "Nichts zum Wiederherstellen.", - browser_notice: "This editor is supported in Firefox, Chrome, Safari, Opera, and Internet Explorer 9 and above. Please upgrade your browser or use Potlatch 2 to edit the map.", + browser_notice: "Dieser Editor wird in Firefox, Chrome, Safari, Opera, und Internet Explorer 9 und höher unterstzützt. Bitte aktualisieren Sie Ihren Browser oder nutzen Sie Potlatch 2, um die Karte zu modifizieren.", inspector: { - no_documentation_combination: "This is no documentation available for this tag combination", - no_documentation_key: "This is no documentation available for this key", - new_tag: "New Tag" + no_documentation_combination: "Es ist keine Dokumentation verfügbar für diese Markierungskombination.", + no_documentation_key: "Es ist keine Dokumentation verfügbar für dieses Schlüsselwort", + new_tag: "Neue Markierung" }, - view_on_osm: "View on OSM", + view_on_osm: "Bei OSM anschauen", - zoom_in_edit: "zoom in to edit the map", + zoom_in_edit: "Hineinzoomen, um die Karte zu editieren", - edit_tags: "Edit tags", + edit_tags: "Markierungen bearbeiten", geocoder: { - "find_location": "Find A Location", - "find_a_place": "find a place" + "find_location": "Finde einen Ort", + "find_a_place": "Finde einen Platz" }, - description: "Description", + description: "Beschreibung", - logout: "logout", + logout: "Abmelden", layerswitcher: { - title: "Background", - description: "Background Settings", - percent_brightness: "{opacity}% brightness", - fix_misalignment: "Fix misalignment", - reset: "reset" + title: "Hintergrund", + description: "Hintergrundeinstellungen", + percent_brightness: "{opacity}% Helligkeit", + fix_misalignment: "Fehlerhafte Ausrichtung reparieren", + reset: "Zurücksetzen" } }; From 6ce987f50fce7cb353ed64344bbe58b80b80d0c6 Mon Sep 17 00:00:00 2001 From: Christian Mayer Date: Sun, 10 Feb 2013 18:11:08 +0100 Subject: [PATCH 303/415] Corrected typos --- locale/de.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/locale/de.js b/locale/de.js index a2e3f6e7d..3c24f8f46 100644 --- a/locale/de.js +++ b/locale/de.js @@ -3,13 +3,13 @@ locale.de = { add_area: { title: "Fläche", description: "Füge Parks, Gebäude, Seen oder andere Flächen zur Karte hinzu.", - tail: "Klicke in die Karte um das Zeichnen einer Fläche wie einen Park, einen See oder Gebäude zu starten.", + tail: "Klicke in die Karte, um das Zeichnen einer Fläche wie einen Park, einen See oder Gebäude zu starten.", key: "A" }, add_line: { title: "Linie", description: "Linien können Autobahnen, Straßen, Fußwege oder sogar Kanäle sein.", - tail: "Klicke in die Karte um das Zeichnen einer Straße eines Pfades oder einer Route zu starten.", + tail: "Klicke in die Karte, um das Zeichnen einer Straße eines Pfades oder einer Route zu starten.", key: "L" }, add_point: { @@ -24,10 +24,10 @@ locale.de = { key: "B" }, draw_area: { - tail: "Klicke, um Punkte zur Fläche hinzuzufügen. Klicke den ersten Punkt, um die Fläche abzuschließen." + tail: "Klicke, um Punkte zur Fläche hinzuzufügen. Klicke auf den ersten Punkt, um die Fläche abzuschließen." }, draw_line: { - tail: "Klicke, um mehr Punkte zur Linie hizuzufügen. Klicke auf eine andere Linie um die Linien zu verbinden, und klicke doppelt, um die Linie zu beenden." + tail: "Klicke, um mehr Punkte zur Linie hizuzufügen. Klicke auf eine andere Linie um die Linien zu verbinden und klicke doppelt, um die Linie zu beenden." } }, @@ -79,11 +79,11 @@ locale.de = { description: "Lösche dies aus der Karte.", key: "⌫", annotation: { - point: "Punkt gelöscht.Deleted a point.", + point: "Punkt gelöscht.", vertex: "Stützpunkt aus einem Weg gelöscht.", line: "Linie gelöscht.", area: "Fläche gelöscht.", - relation: "Relation gelöscht.", + relation: "Verbindung gelöscht.", multiple: "{n} Objekte gelöscht." } }, @@ -120,7 +120,7 @@ locale.de = { }, reverse: { title: "Umkehren", - description: "Ändere die Richtung diese Linie.", + description: "Ändere die Richtung dieser Linie.", key: "V", annotation: "Linienrichtung umgekehrt." }, @@ -133,7 +133,7 @@ locale.de = { }, validations: { - untagged_point: "Punkt, der kein Teil einer Linie oder Fläche ist entmarkiert", + untagged_point: "Punkt, der kein Teil einer Linie oder Fläche ist, entmarkiert", untagged_line: "Linie entmarkiert", untagged_area: "Fläche entmarkiert", tag_suggests_area: "Die Markierung {tag} suggeriert eine Fläche, ist aber keine Fläche", From 00313ebc4696cc221ccc0a4ffbdae46370dd006d Mon Sep 17 00:00:00 2001 From: metalalp Date: Sun, 10 Feb 2013 21:40:03 +0200 Subject: [PATCH 304/415] Updated some Turkish translations. --- locale/tr.js | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/locale/tr.js b/locale/tr.js index 2b84a8b0d..1315eb05a 100644 --- a/locale/tr.js +++ b/locale/tr.js @@ -1,60 +1,60 @@ locale.tr = { modes: { add_area: { - title: "Area", - description: "Add parks, buildings, lakes, or other areas to the map.", - tail: "Click on the map to start drawing an area, like a park, lake, or building.", + title: "Alan", + description: "Park, bina, göl ve benzeri alanları haritaya ekle.", + tail: "Park, göl ya da bina gibi alanları çizmek için haritaya tıklayın.", key: "A" }, add_line: { - title: "Line", - description: "Lines can be highways, streets, pedestrian paths, or even canals.", - tail: "Click on the map to start drawing an road, path, or route.", + title: "Çizgi", + description: "Yollar, sokaklar, patikalar ya da kanallar çizgi ile çizilebilir.", + tail: "Yol, patika yada rota çizmek için haritaya tıklayın.", key: "L" }, add_point: { - title: "Point", - description: "Restaurants, monuments, and postal boxes are points.", - tail: "Click on the map to add a point.", + title: "Nokta", + description: "Restoranlar, anıtlar ya da posta kutuları nokta ile gösterilebilir.", + tail: "Nokta eklemek için haritaya tıklayın.", key: "P" }, browse: { - title: "Browse", - description: "Pan and zoom the map.", + title: "Tara", + description: "Harita üzerinde dolan ve yaklaş.", key: "B" }, draw_area: { - tail: "Click to add points to your area. Click the first point to finish the area." + tail: "Alanınıza nokta eklemek için tıklayınız. İlk noktaya tıklayarak alan çizimini bitirebilirsiniz." }, draw_line: { - tail: "Click to add more points to the line. Click on other lines to connect to them, and double-click to end the line." + tail: "Çizgiye daha fazla nokta eklemek için tıklayınız. Diğer çizgilerle bağlamak için üstlerine tıklyınız ve bitirmek için de son noktada çift tıklayınız." } }, operations: { add: { annotation: { - point: "Added a point.", - vertex: "Added a node to a way." + point: "Nokta eklendi.", + vertex: "Çizgiye bir nod eklendi." } }, start: { annotation: { - line: "Started a line.", - area: "Started an area." + line: "Çizgi çizimi başlatıldı.", + area: "Alan çizimi başlatıldı." } }, 'continue': { annotation: { - line: "Continued a line.", - area: "Continued an area." + line: "Çizgiye devam edildi.", + area: "Alana devam edildi." } }, cancel_draw: { - annotation: "Cancelled drawing." + annotation: "Çizim iptal edildi." }, change_tags: { - annotation: "Changed tags." + annotation: "Etiketler değiştirildi." }, circularize: { title: "Circularize", From 3e7b0b0d98f3518fc8475d78c9508c2b01ce4765 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sat, 9 Feb 2013 20:48:25 -0800 Subject: [PATCH 305/415] Optimize iD.Connection The big win here is using direct property accessors on node attributes rather than iteration. The rest is just micro-optimization. --- js/id/connection.js | 135 +++++++++++++++++++++------------------ test/bench/node-xml.html | 99 ++++++++++++++++++++++++++++ 2 files changed, 173 insertions(+), 61 deletions(-) create mode 100644 test/bench/node-xml.html diff --git a/js/id/connection.js b/js/id/connection.js index 5f5f17482..df9c318ca 100644 --- a/js/id/connection.js +++ b/js/id/connection.js @@ -7,7 +7,13 @@ iD.Connection = function(context) { keys, inflight = {}, loadedTiles = {}, - oauth = iD.OAuth(context).url(url); + oauth = iD.OAuth(context).url(url), + ndStr = 'nd', + tagStr = 'tag', + memberStr = 'member', + nodeStr = 'node', + wayStr = 'way', + relationStr = 'relation'; function changesetUrl(changesetId) { return url + '/browse/changeset/' + changesetId; @@ -34,92 +40,99 @@ iD.Connection = function(context) { } function getNodes(obj) { - var nelems = obj.getElementsByTagName('nd'), nodes = new Array(nelems.length); - for (var i = 0, l = nelems.length; i < l; i++) { - nodes[i] = 'n' + nelems[i].attributes.ref.nodeValue; + var elems = obj.getElementsByTagName(ndStr), + nodes = new Array(elems.length); + for (var i = 0, l = elems.length; i < l; i++) { + nodes[i] = 'n' + elems[i].attributes.ref.nodeValue; } return nodes; } function getTags(obj) { - var tags = {}, tagelems = obj.getElementsByTagName('tag'); - for (var i = 0, l = tagelems.length; i < l; i++) { - var item = tagelems[i]; - tags[item.attributes.k.nodeValue] = item.attributes.v.nodeValue; + var elems = obj.getElementsByTagName(tagStr), + tags = {}; + for (var i = 0, l = elems.length; i < l; i++) { + var attrs = elems[i].attributes; + tags[attrs.k.nodeValue] = attrs.v.nodeValue; } return tags; } function getMembers(obj) { - var elems = obj.getElementsByTagName('member'), + var elems = obj.getElementsByTagName(memberStr), members = new Array(elems.length); - for (var i = 0, l = elems.length; i < l; i++) { + var attrs = elems[i].attributes; members[i] = { - id: elems[i].attributes.type.nodeValue[0] + elems[i].attributes.ref.nodeValue, - type: elems[i].attributes.type.nodeValue, - role: elems[i].attributes.role.nodeValue + id: attrs.type.nodeValue[0] + attrs.ref.nodeValue, + type: attrs.type.nodeValue, + role: attrs.role.nodeValue }; } return members; } - function nodeData(obj) { - var o = { type: 'node', tags: getTags(obj) }; - for (var i = 0, l = obj.attributes.length; i < l; i++) { - o[obj.attributes[i].nodeName] = obj.attributes[i].nodeValue; - } - if (o.lon && o.lat) { - o.loc = [parseFloat(o.lon), parseFloat(o.lat)]; - delete o.lon; delete o.lat; - } - o.id = iD.Entity.id.fromOSM('node', o.id); - return new iD.Node(o); - } + var parsers = { + node: function nodeData(obj) { + var attrs = obj.attributes; + return new iD.Node({ + id: iD.Entity.id.fromOSM(nodeStr, attrs.id.nodeValue), + loc: [parseFloat(attrs.lon.nodeValue), parseFloat(attrs.lat.nodeValue)], + version: attrs.version.nodeValue, + changeset: attrs.changeset.nodeValue, + user: attrs.user.nodeValue, + uid: attrs.uid.nodeValue, + visible: attrs.visible.nodeValue, + timestamp: attrs.timestamp.nodeValue, + tags: getTags(obj) + }); + }, - function wayData(obj) { - var o = { type: 'way', nodes: getNodes(obj), - tags: getTags(obj) - }; - for (var i = 0, l = obj.attributes.length; i < l; i++) { - o[obj.attributes[i].nodeName] = obj.attributes[i].nodeValue; - } - o.id = iD.Entity.id.fromOSM('way', o.id); - return new iD.Way(o); - } + way: function wayData(obj) { + var attrs = obj.attributes; + return new iD.Way({ + id: iD.Entity.id.fromOSM(wayStr, attrs.id.nodeValue), + version: attrs.version.nodeValue, + changeset: attrs.changeset.nodeValue, + user: attrs.user.nodeValue, + uid: attrs.uid.nodeValue, + visible: attrs.visible.nodeValue, + timestamp: attrs.timestamp.nodeValue, + tags: getTags(obj), + nodes: getNodes(obj) + }); + }, - function relationData(obj) { - var o = { - type: 'relation', members: getMembers(obj), - tags: getTags(obj) - }; - for (var i = 0, l = obj.attributes.length; i < l; i++) { - o[obj.attributes[i].nodeName] = obj.attributes[i].nodeValue; + relation: function relationData(obj) { + var attrs = obj.attributes; + return new iD.Relation({ + id: iD.Entity.id.fromOSM(relationStr, attrs.id.nodeValue), + version: attrs.version.nodeValue, + changeset: attrs.changeset.nodeValue, + user: attrs.user.nodeValue, + uid: attrs.uid.nodeValue, + visible: attrs.visible.nodeValue, + timestamp: attrs.timestamp.nodeValue, + tags: getTags(obj), + members: getMembers(obj) + }); } - o.id = iD.Entity.id.fromOSM('relation', o.id); - return new iD.Relation(o); - } + }; function parse(dom) { if (!dom || !dom.childNodes) return new Error('Bad request'); - var root = dom.childNodes[0]; - var entities = {}; + + var root = dom.childNodes[0], + children = root.childNodes, + entities = {}; var i, o, l; - for (i = 0, l = root.childNodes.length; i < l; i++) { - switch(root.childNodes[i].nodeName) { - case 'node': - o = nodeData(root.childNodes[i]); - entities[o.id] = o; - break; - case 'way': - o = wayData(root.childNodes[i]); - entities[o.id] = o; - break; - case 'relation': - o = relationData(root.childNodes[i]); - entities[o.id] = o; - break; + for (i = 0, l = children.length; i < l; i++) { + var child = children[i], + parser = parsers[child.nodeName]; + if (parser) { + o = parser(child); + entities[o.id] = o; } } diff --git a/test/bench/node-xml.html b/test/bench/node-xml.html new file mode 100644 index 000000000..174738350 --- /dev/null +++ b/test/bench/node-xml.html @@ -0,0 +1,99 @@ + + + + Node XML + + + +
+
+
+

+        
+    
+    
+

From 3f47dbe65eff37780353b9775b6261b61f61248a Mon Sep 17 00:00:00 2001
From: Ansis Brammanis 
Date: Sun, 10 Feb 2013 15:36:03 -0500
Subject: [PATCH 306/415] Typo fix via @Richlv

---
 locale/en.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/locale/en.js b/locale/en.js
index a2233551b..1e45312de 100644
--- a/locale/en.js
+++ b/locale/en.js
@@ -158,8 +158,8 @@ locale.en = {
     browser_notice: "This editor is supported in Firefox, Chrome, Safari, Opera, and Internet Explorer 9 and above. Please upgrade your browser or use Potlatch 2 to edit the map.",
 
     inspector: {
-        no_documentation_combination:  "This is no documentation available for this tag combination",
-        no_documentation_key: "This is no documentation available for this key",
+        no_documentation_combination:  "There is no documentation available for this tag combination",
+        no_documentation_key: "There is no documentation available for this key",
         new_tag: "New Tag"
     },
 

From 14e1ad71476c4db1791d8166878acd408be47fe7 Mon Sep 17 00:00:00 2001
From: Ansis Brammanis 
Date: Sun, 10 Feb 2013 15:48:47 -0500
Subject: [PATCH 307/415] Add turkish to index.html

---
 index.html | 1 +
 1 file changed, 1 insertion(+)

diff --git a/index.html b/index.html
index 89ba50fe1..34906c477 100644
--- a/index.html
+++ b/index.html
@@ -147,6 +147,7 @@
         
         
         
+        
 
     
     

From a3fca51af140c261654b40686bd58fb233dcf397 Mon Sep 17 00:00:00 2001
From: Van De Casteele 
Date: Sun, 10 Feb 2013 17:19:46 -0430
Subject: [PATCH 308/415] Update locale/fr.js

French translation
---
 locale/fr.js | 168 +++++++++++++++++++++++++--------------------------
 1 file changed, 84 insertions(+), 84 deletions(-)

diff --git a/locale/fr.js b/locale/fr.js
index 9bd37e688..9b4017b31 100644
--- a/locale/fr.js
+++ b/locale/fr.js
@@ -1,185 +1,185 @@
 locale.fr = {
     modes: {
         add_area: {
-            title: "Area",
-            description: "Add parks, buildings, lakes, or other areas to the map.",
-            tail: "Click on the map to start drawing an area, like a park, lake, or building.",
+            title: "Polygone",
+            description: "Les polygones peuvent être des parcs, des batîments, des lacs ou tout autre objet surfacique.",
+            tail: "Cliquez sur la carte pour ajouter un polygone tel qu'un parc, un lac ou un bâtiment.",
             key: "A"
         },
         add_line: {
-            title: "Line",
-            description: "Lines can be highways, streets, pedestrian paths, or even canals.",
-            tail: "Click on the map to start drawing an road, path, or route.",
+            title: "Ligne",
+            description: "Les lignes peuvent être des autoroutes, des routes, des chemins ou encore des caneaux.",
+            tail: "Cliquez sur la carte pour ajouter une nouvelle ligne telle qu'une route ou un nouveau chemin.",
             key: "L"
         },
         add_point: {
             title: "Point",
-            description: "Restaurants, monuments, and postal boxes are points.",
-            tail: "Click on the map to add a point.",
+            description: "Les points peuvent être des restaurants, des monuments, ou encore des boites aux lettres.",
+            tail: "Cliquez sur la carte pour ajouter un point tel qu'un restaurant ou un monument.",
             key: "P"
         },
         browse: {
-            title: "Browse",
-            description: "Pan and zoom the map.",
+            title: "Navigation",
+            description: "Naviguer ou zoomer sur la carte.",
             key: "B"
         },
         draw_area: {
-            tail: "Click to add points to your area. Click the first point to finish the area."
+            tail: "Cliquez pour ajouter un point à la zone. Cliquez sur le dernier point pour fermer la zone."
         },
         draw_line: {
-            tail: "Click to add more points to the line. Click on other lines to connect to them, and double-click to end the line."
+            tail: "Cliquez pour ajouter un point à la ligne. Cliquez sur une autre ligne pour les connecter, puis faîtes un double-clique pour terminer la ligne."
         }
     },
 
     operations: {
         add: {
             annotation: {
-                point: "Added a point.",
-                vertex: "Added a node to a way."
+                point: "Ajouter un point.",
+                vertex: "Ajouter un noeud à une ligne."
             }
         },
         start: {
             annotation: {
-                line: "Started a line.",
-                area: "Started an area."
+                line: "Commencer une nouvelle ligne.",
+                area: "Commencer un polygone."
             }
         },
         'continue': {
             annotation: {
-                line: "Continued a line.",
-                area: "Continued an area."
+                line: "Continuer une ligne.",
+                area: "Continuer un polygone."
             }
         },
         cancel_draw: {
-            annotation: "Cancelled drawing."
+            annotation: "Annuler un ajout."
         },
         change_tags: {
-            annotation: "Changed tags."
+            annotation: "Modifier les tags."
         },
         circularize: {
             title: "Circularize",
-            description: "Make this round.",
+            description: "Créer un cercle.",
             key: "O",
             annotation: {
-                line: "Made a line circular.",
-                area: "Made an area circular."
+                line: "Créer un cercle linéaire.",
+                area: "Créer un cercle surfacique (disque)."
             }
         },
         orthogonalize: {
-            title: "Orthogonalize",
-            description: "Square these corners.",
+            title: "Orthogonaliser",
+            description: "Rendre une forme orthogonale.",
             key: "Q",
             annotation: {
-                line: "Squared the corners of a line.",
-                area: "Squared the corners of an area."
+                line: "Orthogonaliser une ligne orthogonale.",
+                area: "Orthogonaliser un polygone orthogonale."
             }
         },
         'delete': {
-            title: "Delete",
-            description: "Remove this from the map.",
+            title: "Supprimer",
+            description: "Supprime l'élément de la carte.",
             key: "⌫",
             annotation: {
-                point: "Deleted a point.",
-                vertex: "Deleted a node from a way.",
-                line: "Deleted a line.",
-                area: "Deleted an area.",
-                relation: "Deleted a relation.",
-                multiple: "Deleted {n} objects."
+                point: "Supprime un point.",
+                vertex: "Supprime le noeud d'une ligne.",
+                line: "Supprime une ligne.",
+                area: "Supprime un polygone.",
+                relation: "Supprime une relation.",
+                multiple: "Supprime {n} objets."
             }
         },
         connect: {
             annotation: {
-                point: "Connected a way to a point.",
-                vertex: "Connected a way to another.",
-                line: "Connected a way to a line.",
-                area: "Connected a way to an area."
+                point: "Joindre une ligne à un point.",
+                vertex: "Joindre les noeuds à une ligne.",
+                line: "Joindre les chemins ensemble.",
+                area: "Joindre une ligne à un polygone."
             }
         },
         disconnect: {
-            title: "Disconnect",
-            description: "Disconnect these ways from each other.",
+            title: "Séparer",
+            description: "Sépare les lignes l'une de l'autre.",
             key: "D",
-            annotation: "Disconnected ways."
+            annotation: "Sépare les lignes."
         },
         merge: {
-            title: "Merge",
-            description: "Merge these lines.",
+            title: "Fusionner",
+            description: "Fusionne les lignes.",
             key: "C",
-            annotation: "Merged {n} lines."
+            annotation: "Fusionne les {n} ligne."
         },
         move: {
-            title: "Move",
-            description: "Move this to a different location.",
+            title: "Déplacer",
+            description: "Déplace l'élément à un autre endroit.",
             key: "M",
             annotation: {
-                point: "Moved a point.",
-                vertex: "Moved a node in a way.",
-                line: "Moved a line.",
-                area: "Moved an area."
+                point: "Déplace un point.",
+                vertex: "Déplace le noeud d'une ligne.",
+                line: "Déplace une ligne.",
+                area: "Déplace un polygone."
             }
         },
         reverse: {
-            title: "Reverse",
-            description: "Make this line go in the opposite direction.",
+            title: "Inverser",
+            description: "Inverse le sens d'une ligne.",
             key: "V",
-            annotation: "Reversed a line."
+            annotation: "Inverse le sens d'une ligne."
         },
         split: {
-            title: "Split",
-            description: "Split this into two ways at this point.",
+            title: "Couper",
+            description: "Coupe une ligne en deux par rapport au point sélectionné.",
             key: "X",
-            annotation: "Split a way."
+            annotation: "Coupe une ligne."
         }
     },
 
     validations: {
-        untagged_point: "Untagged point which is not part of a line or area",
-        untagged_line: "Untagged line",
-        untagged_area: "Untagged area",
-        tag_suggests_area: "The tag {tag} suggests line should be area, but it is not an area",
-        deprecated_tags: "Deprecated tags: {tags}"
+        untagged_point: "Point sans aucun tag ne faisant partie ni d'une ligne, ni d'un polygone",
+        untagged_line: "Ligne sans aucun tag",
+        untagged_area: "Polygone sans aucun tag",
+        tag_suggests_area: "Ce tag {tag} suppose que cette ligne devrait être un polygone, or ce n'est pas le cas",
+        deprecated_tags: "Tags obsolètes : {tags}"
     },
 
-    save: "Save",
-    save_help: "Save changes to OpenStreetMap, making them visible to other users",
-    no_changes: "You don't have any changes to save.",
-    save_error: "An error occurred while trying to save",
-    uploading_changes: "Uploading changes to OpenStreetMap.",
-    just_edited: "You Just Edited OpenStreetMap!",
+    save: "Sauvegarder",
+    save_help: "Envoie des modifications au serveyr OpenStreetMap afin qu'elles soient visibles par les autres contributeurs.",
+    no_changes: "Vous n'avez aucune modification à enregistrer.",
+    save_error: "Une erreur est survenue lors de l'enregistrement des données",
+    uploading_changes: "Envoie des modifications vers OpenStreetMap.",
+    just_edited: "Vous venez de participer à OpenStreetMap!",
     okay: "Okay",
 
-    "zoom-in": "Zoom In",
-    "zoom-out": "Zoom Out",
+    "zoom-in": "Zoomer",
+    "zoom-out": "Dézoomer",
 
-    nothing_to_undo: "Nothing to undo.",
-    nothing_to_redo: "Nothing to redo.",
+    nothing_to_undo: "Rien à annuler.",
+    nothing_to_redo: "Rien à refaire.",
 
-    browser_notice: "This editor is supported in Firefox, Chrome, Safari, Opera, and Internet Explorer 9 and above. Please upgrade your browser or use Potlatch 2 to edit the map.",
+    browser_notice: "Les navigateurs supportés par cet éditeur sont : Firefox, Chrome, Safari, Opera et Internet Explorer (version 9 et supérieures). Pour éditer la carte, veuillez mettre à jour votre navigateur ou utiliser Potlatch 2.",
 
     inspector: {
-        no_documentation_combination:  "This is no documentation available for this tag combination",
-        no_documentation_key: "This is no documentation available for this key",
-        new_tag: "New Tag"
+        no_documentation_combination:  "Aucune documentation n'est disponible pour cette combinaison de tag,
+        no_documentation_key: "Aucune documentation n'est disponible pour cette clé",
+        new_tag: "Nouveau tag"
     },
 
-    view_on_osm: "View on OSM",
+    view_on_osm: "Consulter dans OSM",
 
-    zoom_in_edit: "zoom in to edit the map",
+    zoom_in_edit: "Zoomer pour modifier la carte
 
-    edit_tags: "Edit tags",
+    edit_tags: "Editer les tags
 
     geocoder: {
-        "find_location": "Find A Location",
-        "find_a_place": "find a place"
+        "find_location": "Trouver un emplacement",
+        "find_a_place": "Trouver un endroit"
     },
 
     description: "Description",
 
-    logout: "logout",
+    logout: "Déconnexion",
 
     layerswitcher: {
-        title: "Background",
-        description: "Background Settings",
+        title: "Fond de carte",
+        description: "Paramètres du fond de carte",
         percent_brightness: "{opacity}% brightness",
         fix_misalignment: "Fix misalignment",
         reset: "reset"

From 39e99aa9e1c67972b8344843f0ba0aefd746852d Mon Sep 17 00:00:00 2001
From: richlv 
Date: Sun, 10 Feb 2013 23:56:33 +0200
Subject: [PATCH 309/415] Update locale/lv.js

translate additional strings;
fix more typos
---
 locale/lv.js | 38 +++++++++++++++++++-------------------
 1 file changed, 19 insertions(+), 19 deletions(-)

diff --git a/locale/lv.js b/locale/lv.js
index 2c28b400f..fa32bef5c 100644
--- a/locale/lv.js
+++ b/locale/lv.js
@@ -3,7 +3,7 @@ locale.lv = {
         add_area: {
             title: "Apgabals",
             description: "Pievieno parkus, ēkas, ezerus un citus apgabalus.",
-            tail: "Klikšķiniet uz kartes, lai sāktu zīmēt apgabalu, piemēram parku, ezeru, vai ēku.",
+            tail: "Klikšķiniet uz kartes, lai sāktu zīmēt apgabalu, piemēram, parku, ezeru, vai ēku.",
             key: "A"
         },
         add_line: {
@@ -46,7 +46,7 @@ locale.lv = {
         },
         'continue': {
             annotation: {
-                line: "Linija turpināta.",
+                line: "Līnija turpināta.",
                 area: "Apgabals turpināts."
             }
         },
@@ -57,26 +57,26 @@ locale.lv = {
             annotation: "Apzīmējumi mainīti."
         },
         circularize: {
-            title: "Circularize",
-            description: "Make this round.",
+            title: "Pārveidot par apļveida",
+            description: "Pārveidot šo objektu par apļveida.",
             key: "O",
             annotation: {
-                line: "Made a line circular.",
-                area: "Made an area circular."
+                line: "Līnija pārveidota par apļveida.",
+                area: "Apgabals pārveidots par apļveida."
             }
         },
         orthogonalize: {
-            title: "Orthogonalize",
-            description: "Square these corners.",
+            title: "Ortogonalizēt",
+            description: "Pārveidot, lai visi leņķi būtu tasnleņķi.",
             key: "Q",
             annotation: {
-                line: "Squared the corners of a line.",
-                area: "Squared the corners of an area."
+                line: "Līnijas leņķi pārvedoti par taisnleņķiem.",
+                area: "Apgabala leņķi pārvedoti par taisnleņķiem."
             }
         },
         'delete': {
             title: "Dzēst",
-            description: "Noņemt no kartes.",
+            description: "Izdzēst no kartes.",
             key: "⌫",
             annotation: {
                 point: "Punkts dzēsts.",
@@ -90,7 +90,7 @@ locale.lv = {
         connect: {
             annotation: {
                 point: "Līnija savienota ar punktu.",
-                vertex: "Līnija savienota ar otru.",
+                vertex: "Līnija savienota ar citu.",
                 line: "Līnija savienota ar līniju.",
                 area: "Līnija savienota ar apgabalu."
             }
@@ -155,29 +155,29 @@ locale.lv = {
     nothing_to_undo: "Nav nekā, ko atcelt",
     nothing_to_redo: "Nav nekā, ko atsaukt",
 
-    browser_notice: "This editor is supported in Firefox, Chrome, Safari, Opera, and Internet Explorer 9 and above. Please upgrade your browser or use Potlatch 2 to edit the map.",
+    browser_notice: "Šis redaktors tiek atbalstīts ar Firefox, Chrome, Safari, Opera, un Internet Explorer 9 un jaunāku. Lūdzu, atjauniniet savu pārlūkprogrammu vai izmantojiet Potlatch 2 to kartes rediģēšanai,
 
     inspector: {
-        no_documentation_combination:  "This is no documentation available for this tag combination",
-        no_documentation_key: "This is no documentation available for this key",
-        new_tag: "Jauns apzīmejums"
+        no_documentation_combination: "Šai apzīmējumu kombinācijai nav piejama dokumetācija",
+        no_documentation_key: "There is no documentation available for this key",
+        new_tag: "Jauns apzīmējums"
     },
 
     view_on_osm: "Apskatīt OSM lapu",
 
-    zoom_in_edit: "zoom in to edit the map",
+    zoom_in_edit: "pietuviniet, lai rediģētu karti,
 
     edit_tags: "Rediģēt apzīmējumus",
 
     geocoder: {
         title: "Atrast vietu",
         placeholder: "meklē vietu",
-        no_results: "Nevarēja atrast vietu '{name}'"
+        no_results: "Nevar atrast vietu '{name}'"
     },
 
     description: "Apraksts",
 
-    logout: "logout",
+    logout: "atslēgties",
 
     live: "live",
     dev: "dev",

From 937fd97900213886360f3d3e22f52a4c44ba03a2 Mon Sep 17 00:00:00 2001
From: richlv 
Date: Sun, 10 Feb 2013 23:57:52 +0200
Subject: [PATCH 310/415] Update locale/lv.js

add missing doublequote
---
 locale/lv.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/locale/lv.js b/locale/lv.js
index fa32bef5c..2d45b1dd9 100644
--- a/locale/lv.js
+++ b/locale/lv.js
@@ -155,7 +155,7 @@ locale.lv = {
     nothing_to_undo: "Nav nekā, ko atcelt",
     nothing_to_redo: "Nav nekā, ko atsaukt",
 
-    browser_notice: "Šis redaktors tiek atbalstīts ar Firefox, Chrome, Safari, Opera, un Internet Explorer 9 un jaunāku. Lūdzu, atjauniniet savu pārlūkprogrammu vai izmantojiet Potlatch 2 to kartes rediģēšanai,
+    browser_notice: "Šis redaktors tiek atbalstīts ar Firefox, Chrome, Safari, Opera, un Internet Explorer 9 un jaunāku. Lūdzu, atjauniniet savu pārlūkprogrammu vai izmantojiet Potlatch 2 to kartes rediģēšanai",
 
     inspector: {
         no_documentation_combination: "Šai apzīmējumu kombinācijai nav piejama dokumetācija",

From f75111bd51a645ef8b792c6d38ac6a0382158847 Mon Sep 17 00:00:00 2001
From: richlv 
Date: Sun, 10 Feb 2013 23:58:32 +0200
Subject: [PATCH 311/415] Update locale/lv.js

add another missing doublequote
---
 locale/lv.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/locale/lv.js b/locale/lv.js
index 2d45b1dd9..b7ba5504f 100644
--- a/locale/lv.js
+++ b/locale/lv.js
@@ -165,7 +165,7 @@ locale.lv = {
 
     view_on_osm: "Apskatīt OSM lapu",
 
-    zoom_in_edit: "pietuviniet, lai rediģētu karti,
+    zoom_in_edit: "pietuviniet, lai rediģētu karti",
 
     edit_tags: "Rediģēt apzīmējumus",
 

From a162a92adcf252e3c033f399d70283f8c04f30ed Mon Sep 17 00:00:00 2001
From: richlv 
Date: Mon, 11 Feb 2013 00:19:43 +0200
Subject: [PATCH 312/415] Update locale/en.js

- as this apparently uses american english, change "Cancelled" -> "Canceled"
- remove extra space (yay)
---
 locale/en.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/locale/en.js b/locale/en.js
index 1e45312de..efd3c1d90 100644
--- a/locale/en.js
+++ b/locale/en.js
@@ -51,7 +51,7 @@ locale.en = {
             }
         },
         cancel_draw: {
-            annotation: "Cancelled drawing."
+            annotation: "Canceled drawing."
         },
         change_tags: {
             annotation: "Changed tags."
@@ -158,7 +158,7 @@ locale.en = {
     browser_notice: "This editor is supported in Firefox, Chrome, Safari, Opera, and Internet Explorer 9 and above. Please upgrade your browser or use Potlatch 2 to edit the map.",
 
     inspector: {
-        no_documentation_combination:  "There is no documentation available for this tag combination",
+        no_documentation_combination: "There is no documentation available for this tag combination",
         no_documentation_key: "There is no documentation available for this key",
         new_tag: "New Tag"
     },

From db820e2d294ce8216f3f4f69b1f03ca71c0c9194 Mon Sep 17 00:00:00 2001
From: Tom MacWright 
Date: Sun, 10 Feb 2013 18:51:48 -0500
Subject: [PATCH 313/415] Fix js in french translation

---
 locale/fr.js | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/locale/fr.js b/locale/fr.js
index 9b4017b31..904d5cb9e 100644
--- a/locale/fr.js
+++ b/locale/fr.js
@@ -157,16 +157,16 @@ locale.fr = {
     browser_notice: "Les navigateurs supportés par cet éditeur sont : Firefox, Chrome, Safari, Opera et Internet Explorer (version 9 et supérieures). Pour éditer la carte, veuillez mettre à jour votre navigateur ou utiliser Potlatch 2.",
 
     inspector: {
-        no_documentation_combination:  "Aucune documentation n'est disponible pour cette combinaison de tag,
+        no_documentation_combination:  "Aucune documentation n'est disponible pour cette combinaison de tag",
         no_documentation_key: "Aucune documentation n'est disponible pour cette clé",
         new_tag: "Nouveau tag"
     },
 
     view_on_osm: "Consulter dans OSM",
 
-    zoom_in_edit: "Zoomer pour modifier la carte
+    zoom_in_edit: "Zoomer pour modifier la carte",
 
-    edit_tags: "Editer les tags
+    edit_tags: "Editer les tags",
 
     geocoder: {
         "find_location": "Trouver un emplacement",

From e60fd8665863b02d8b0c5b9a342631ec98de8a71 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Iv=C3=A1n=20Perdomo?= 
Date: Mon, 11 Feb 2013 08:42:34 +0100
Subject: [PATCH 314/415] Add Spanish to available locales

---
 locale/es.js | 201 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 201 insertions(+)
 create mode 100644 locale/es.js

diff --git a/locale/es.js b/locale/es.js
new file mode 100644
index 000000000..c2296b745
--- /dev/null
+++ b/locale/es.js
@@ -0,0 +1,201 @@
+locale.es = {
+    modes: {
+        add_area: {
+            title: "Zona", //"Area",
+            description: "Agregar parques, edificios, lagos u otras zonas en el mapa", //"Add parks, buildings, lakes, or other areas to the map.",
+            tail: "Hacer click en el mapa para empezar a dibujar una zona como un parque, lago o edificio", //"Click on the map to start drawing an area, like a park, lake, or building.",
+            key: "Z", //"A"
+        },
+        add_line: {
+            title: "Línea", //"Line",
+            description: "Las líneas pueden ser autopistas, calles, pasos peatonales o canales.", //"Lines can be highways, streets, pedestrian paths, or even canals.",
+            tail: "Hace clic para dibujar en el mapa, una calle, camino o ruta.", //"Click on the map to start drawing an road, path, or route.",
+            key: "L"
+        },
+        add_point: {
+            title: "Punto", //"Point",
+            description: "Son puntos los restaurantes, monumentos y buzones", //"Restaurants, monuments, and postal boxes are points.",
+            tail: "Hacer clic para agregar un punto en el mapa", //"Click on the map to add a point.",
+            key: "P"
+        },
+        browse: {
+            title: "Navegar", //"Browse",
+            description: "Aumentar y navegar el mapa", //"Pan and zoom the map.",
+            key: "N" //"B"
+        },
+        draw_area: {
+            tail: "Hacer clic para agregar puntos en tu zona. Hacer hacer click en el primer punto para finalizar la zona." //"Click to add points to your area. Click the first point to finish the area."
+        },
+        draw_line: {
+            tail: "Hacer clic para agregar más puntos a la línea. Hacer clic en otras líneas para conectarlas, y doble clic para finalizar." //"Click to add more points to the line. Click on other lines to connect to them, and double-click to end the line."
+        }
+    },
+
+    operations: {
+        add: {
+            annotation: {
+                point: "Punto agregado", //"Added a point.",
+                vertex: "Nodo agregado a una ruta" //"Added a node to a way."
+            }
+        },
+        start: {
+            annotation: {
+                line: "Línea iniciada", //"Started a line.",
+                area: "Zona iniciada" //"Started an area."
+            }
+        },
+        'continue': {
+            annotation: {
+                line: "Línea continuada.", //"Continued a line.",
+                area: "Zona continuada." //"Continued an area."
+            }
+        },
+        cancel_draw: {
+            annotation: "Dibujo cancelado." //"Cancelled drawing."
+        },
+        change_tags: {
+            annotation: "Etiquetas cambiadas." //"Changed tags."
+        },
+        circularize: {
+            title: "Redondear", //"Circularize",
+            description: "Hacer esto redondo.", //"Make this round.",
+            key: "O",
+            annotation: {
+                line: "Redondear una línea.", //"Made a line circular.",
+                area: "Redondear una zona." //"Made an area circular."
+            }
+        },
+        orthogonalize: {
+            title: "Escuadrar", //"Orthogonalize",
+            description: "Escuadrar estas esquinas.", //"Square these corners.",
+            key: "E", //"Q",
+            annotation: {
+                line: "Esquinas de la línea escuadrados.", //"Squared the corners of a line.",
+                area: "Esquinas de la zona escuadrados." //"Squared the corners of an area."
+            }
+        },
+        'delete': {
+            title: "Eliminar", //"Delete",
+            description: "Eliminar esto del mapa.", //"Remove this from the map.",
+            key: "⌫",
+            annotation: {
+                point: "Punto eliminado.", //"Deleted a point.",
+                vertex: "Nodo elimnado de una ruta.", //"Deleted a node from a way.",
+                line: "Línea eliminada.", //"Deleted a line.",
+                area: "Zona eliminada.", //"Deleted an area.",
+                relation: "Relación eliminada.", //"Deleted a relation.",
+                multiple: "{n} objetos eliminados." //"Deleted {n} objects."
+            }
+        },
+        connect: {
+            annotation: {
+                point: "Punto conectado a una ruta.", //"Connected a way to a point.",
+                vertex: "Ruta conectada a otra.", //"Connected a way to another.",
+                line: "Ruta conectada a una línea.", //"Connected a way to a line.",
+                area: "Ruta conectada a una zona." //"Connected a way to an area."
+            }
+        },
+        disconnect: {
+            title: "Desconectar", //"Disconnect",
+            description: "Desconectar estas rutas.", //"Disconnect these ways from each other.",
+            key: "D",
+            annotation: "Rutas desconectadas." //"Disconnected ways."
+        },
+        merge: {
+            title: "Combinar", //"Merge",
+            description: "Combinar estas líneas.", //"Merge these lines.",
+            key: "C",
+            annotation: "{n} líneas combinadas" //"Merged {n} lines."
+        },
+        move: {
+            title: "Mover", //"Move",
+            description: "Mover esto a una ubicación diferente.", //"Move this to a different location.",
+            key: "M",
+            annotation: {
+                point: "Punto movido", //"Moved a point.",
+                vertex: "Nodo movido a una ruta", //"Moved a node in a way.",
+                line: "Línea movida", //"Moved a line.",
+                area: "Zona movida" //"Moved an area."
+            }
+        },
+        reverse: {
+            title: "Invertir", //"Reverse",
+            description: "Hacer que esta línea vaya en sentido inverso.", //"Make this line go in the opposite direction.",
+            key: "I", //"V",
+            annotation: "Línea invertida" //"Reversed a line."
+        },
+        split: {
+            title: "Dividir", //"Split",
+            description: "Dividir en dos rutas en éste punto.", //"Split this into two ways at this point.",
+            key: "D", //"X",
+            annotation: "Dividir una ruta." //"Split a way."
+        }
+    },
+
+    validations: {
+        untagged_point: "Punto sin etiquetar que no es parte de una línea ni zona.", //"Untagged point which is not part of a line or area",
+        untagged_line: "Línea sin etiquetar", //"Untagged line",
+        untagged_area: "Zona sin etiquetar", //"Untagged area",
+        tag_suggests_area: "La etiqueta {tag} sugiere que esta línea debería ser una zona, pero no lo es.", //"The tag {tag} suggests line should be area, but it is not an area",
+        deprecated_tags: "Etiquetas obsoletas: {tags}" //"Deprecated tags: {tags}"
+    },
+
+    save: "Guardar", //"Save",
+    unsaved_changes: "Tienes cambios sin guardar", //"You have unsaved changes",
+    save_help: "Guardar los cambios en OpenStreetMap haciéndolos visibles a otros usuarios", //"Save changes to OpenStreetMap, making them visible to other users",
+    no_changes: "No tienes cambios sin guardar", //"You don't have any changes to save.",
+    save_error: "Ha ocurrido un error tratando de guardar", //"An error occurred while trying to save",
+    uploading_changes: "Subiendo cambios a OpenStreetMap", //"Uploading changes to OpenStreetMap.",
+    just_edited: "Acabas de editar OpenStreetMap!", //"You Just Edited OpenStreetMap!",
+    okay: "OK", //"Okay",
+
+    "zoom-in": "Aumentar", // "Zoom In",
+    "zoom-out": "Alejar", //"Zoom Out",
+
+    nothing_to_undo: "Nada para deshacer", //"Nothing to undo.",
+    nothing_to_redo: "Nada para rehacer", //"Nothing to redo.",
+
+    browser_notice: "Este editor soporta Firefox, Chrome, Safari, Opera e Internet Explorer 9 o superior. Por favor actualiza tu navegador o utiliza Potlatch 2 para editar el mapa.", //"This editor is supported in Firefox, Chrome, Safari, Opera, and Internet Explorer 9 and above. Please upgrade your browser or use Potlatch 2 to edit the map.",
+
+    inspector: {
+        no_documentation_combination: "No hay documentación disponible para esta combinación de etiquetas", //"This is no documentation available for this tag combination",
+        no_documentation_key: "No hay documentación disponible para esta tecla", //"This is no documentation available for this key",
+        new_tag: "Nueve etiqueta" //"New Tag"
+    },
+
+    view_on_osm: "Ver en OSM", //"View on OSM",
+
+    zoom_in_edit: "acercar para editar el mapa", //"zoom in to edit the map",
+
+    edit_tags: "Editar etiquetas", //"Edit tags",
+
+    geocoder: {
+        title: "Encontrar un lugar", //"Find A Place",
+        placeholder: "encontrar un lugar", //"find a place",
+        no_results: "No se pudo encontrar el lugar llamado '{name}'" //"Couldn't locate a place named '{name}'"
+    },
+
+    description: "Descripción", //"Description",
+
+    logout: "cerrar sesión", //"logout",
+
+    report_a_bug: "reportar un error", //"report a bug",
+
+    layerswitcher: {
+        title: "Fondo", //"Background",
+        description: "Configuración de fondo", //"Background Settings",
+        percent_brightness: "{opacity}% brillo", //"{opacity}% brightness",
+        fix_misalignment: "Arreglar alineamiento", //"Fix misalignment",
+        reset: "reiniciar" //"reset"
+    },
+
+    contributors: {
+        list: "Viendo las contribuciones de usuarios {users}", //"Viewing contributions by {users}",
+        truncated_list: "Viendo las contribuciones de {users} y {count} más" //"Viewing contributions by {users} and {count} others"
+    },
+
+    source_switch: {
+        live: "en vivo", //"live",
+        dev: "dev"
+    }
+};

From 19ccada7847d91e4bdef69cdeed71a08c8f53022 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Iv=C3=A1n=20Perdomo?= 
Date: Mon, 11 Feb 2013 08:44:56 +0100
Subject: [PATCH 315/415] Removes extra comma

---
 locale/es.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/locale/es.js b/locale/es.js
index c2296b745..47fb7427d 100644
--- a/locale/es.js
+++ b/locale/es.js
@@ -4,7 +4,7 @@ locale.es = {
             title: "Zona", //"Area",
             description: "Agregar parques, edificios, lagos u otras zonas en el mapa", //"Add parks, buildings, lakes, or other areas to the map.",
             tail: "Hacer click en el mapa para empezar a dibujar una zona como un parque, lago o edificio", //"Click on the map to start drawing an area, like a park, lake, or building.",
-            key: "Z", //"A"
+            key: "Z" //"A"
         },
         add_line: {
             title: "Línea", //"Line",

From ae0de28c070a8a0cfc0c3d71793326854b7206bd Mon Sep 17 00:00:00 2001
From: Martin Raifer 
Date: Mon, 11 Feb 2013 11:58:57 +0100
Subject: [PATCH 316/415] Update locale/de.js

rephrased (the very uncommon) usage of "entmarkiert" to "ohne Attribute".
---
 locale/de.js | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/locale/de.js b/locale/de.js
index 3c24f8f46..a1ad84e9e 100644
--- a/locale/de.js
+++ b/locale/de.js
@@ -133,11 +133,11 @@ locale.de = {
     },
 
     validations: {
-        untagged_point: "Punkt, der kein Teil einer Linie oder Fläche ist, entmarkiert",
-        untagged_line: "Linie entmarkiert",
-        untagged_area: "Fläche entmarkiert",
-        tag_suggests_area: "Die Markierung {tag} suggeriert eine Fläche, ist aber keine Fläche",
-        deprecated_tags: "Abgelehnte Markierungen: {tags}"
+        untagged_point: "Punkt ohne Attribute, der kein Teil einer Linie oder Fläche ist",
+        untagged_line: "Linie ohne Attribute",
+        untagged_area: "Fläche ohne Attribute",
+        tag_suggests_area: "Das Attribut {tag} suggeriert eine Fläche, ist aber keine Fläche",
+        deprecated_tags: "Veralterte Attribute: {tags}"
     },
 
     save: "Speichern",

From c0a1eb9e1922e0abea3867bd10b90812cafa8249 Mon Sep 17 00:00:00 2001
From: Martin Raifer 
Date: Mon, 11 Feb 2013 12:19:09 +0100
Subject: [PATCH 317/415] Update locale/de.js

more "Markierung" -> "Attribut"
(see usage in the OSM wiki: http://wiki.openstreetmap.org/wiki/DE:Tagging)
---
 locale/de.js | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/locale/de.js b/locale/de.js
index a1ad84e9e..4ff4d3660 100644
--- a/locale/de.js
+++ b/locale/de.js
@@ -157,16 +157,16 @@ locale.de = {
     browser_notice: "Dieser Editor wird in Firefox, Chrome, Safari, Opera, und Internet Explorer 9 und höher unterstzützt. Bitte aktualisieren Sie Ihren Browser oder nutzen Sie Potlatch 2, um die Karte zu modifizieren.",
 
     inspector: {
-        no_documentation_combination:  "Es ist keine Dokumentation verfügbar für diese Markierungskombination.",
-        no_documentation_key: "Es ist keine Dokumentation verfügbar für dieses Schlüsselwort",
-        new_tag: "Neue Markierung"
+        no_documentation_combination:  "Für dieses Attribut ist keine Dokumentation verfügbar.",
+        no_documentation_key: "Für dises Schlüsselwort ist keine Dokumentation verfügbar",
+        new_tag: "Neues Attribut"
     },
 
     view_on_osm: "Bei OSM anschauen",
 
     zoom_in_edit: "Hineinzoomen, um die Karte zu editieren",
 
-    edit_tags: "Markierungen bearbeiten",
+    edit_tags: "Attribute bearbeiten",
 
     geocoder: {
         "find_location": "Finde einen Ort",

From 1bfdb0715ff12ffd92c097a20807b36e68a21fff Mon Sep 17 00:00:00 2001
From: Martin Raifer 
Date: Mon, 11 Feb 2013 12:21:47 +0100
Subject: [PATCH 318/415] Update locale/de.js

updated geocoder localization
---
 locale/de.js | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/locale/de.js b/locale/de.js
index 4ff4d3660..40a916bf7 100644
--- a/locale/de.js
+++ b/locale/de.js
@@ -169,8 +169,9 @@ locale.de = {
     edit_tags: "Attribute bearbeiten",
 
     geocoder: {
-        "find_location": "Finde einen Ort",
-        "find_a_place": "Finde einen Platz"
+        title: "Suche einen Ort",
+        placeholder: "suche einen Ort",
+        no_results: "Der Ort '{name}' konnte nicht gefunden werden"
     },
 
     description: "Beschreibung",

From ec8cd00a801eb9f376e861701728c3c3a6d44cc8 Mon Sep 17 00:00:00 2001
From: Martin Raifer 
Date: Mon, 11 Feb 2013 12:26:09 +0100
Subject: [PATCH 319/415] Update locale/de.js

Rephrased more unusual formulations.
---
 locale/de.js | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/locale/de.js b/locale/de.js
index 40a916bf7..0c7231f25 100644
--- a/locale/de.js
+++ b/locale/de.js
@@ -141,12 +141,12 @@ locale.de = {
     },
 
     save: "Speichern",
-    save_help: "Speichere Änderungen zu OpenStreetMap, so dass sie für andere Nutzer sichtbar werden",
+    save_help: "Speichere Änderungen auf OpenStreetMap, um diese für andere Nutzer sichtbar zu machen",
     no_changes: "Sie haben keine Änderungen zum Speichern.",
-    save_error: "Es ist ein Fehler aufgetreten beim Versuch des Speicherns",
-    uploading_changes: "Lade Änderungen zu OpenStreetMap.",
+    save_error: "Beim Speichern ist ein Fehler aufgetreten",
+    uploading_changes: "Änderungen werden zu OpenStreetMap hochgeladen.",
     just_edited: "Sie haben gerade OpenStreetMap editiert!",
-    okay: "Okay",
+    okay: "OK",
 
     "zoom-in": "Hineinzoomen",
     "zoom-out": "Herauszoomen",
@@ -154,7 +154,7 @@ locale.de = {
     nothing_to_undo: "Nichts zum Rückgängigmachen.",
     nothing_to_redo: "Nichts zum Wiederherstellen.",
 
-    browser_notice: "Dieser Editor wird in Firefox, Chrome, Safari, Opera, und Internet Explorer 9 und höher unterstzützt. Bitte aktualisieren Sie Ihren Browser oder nutzen Sie Potlatch 2, um die Karte zu modifizieren.",
+    browser_notice: "Dieser Editor wird von Firefox, Chrome, Safari, Opera, und Internet Explorer (Version 9 und höher) unterstzützt. Bitte aktualisieren Sie Ihren Browser oder nutzen Sie Potlatch 2, um die Karte zu modifizieren.",
 
     inspector: {
         no_documentation_combination:  "Für dieses Attribut ist keine Dokumentation verfügbar.",
@@ -162,9 +162,9 @@ locale.de = {
         new_tag: "Neues Attribut"
     },
 
-    view_on_osm: "Bei OSM anschauen",
+    view_on_osm: "Auf OSM anschauen",
 
-    zoom_in_edit: "Hineinzoomen, um die Karte zu editieren",
+    zoom_in_edit: "Hineinzoomen, um die Karte zu bearbeiten",
 
     edit_tags: "Attribute bearbeiten",
 

From 9b784cb76e7826f853383b38ae9b46013935f160 Mon Sep 17 00:00:00 2001
From: Tom MacWright 
Date: Mon, 11 Feb 2013 10:00:46 -0500
Subject: [PATCH 320/415] Include spanish translation in index

---
 index.html | 1 +
 1 file changed, 1 insertion(+)

diff --git a/index.html b/index.html
index 34906c477..5d43a890c 100644
--- a/index.html
+++ b/index.html
@@ -148,6 +148,7 @@
         
         
         
+        
 
     
     

From 38b327fefeb436ad06444c42abe3620c2f391a44 Mon Sep 17 00:00:00 2001
From: Tom MacWright 
Date: Mon, 11 Feb 2013 10:42:49 -0500
Subject: [PATCH 321/415] jshint fixes, fix global leak

---
 js/id/behavior/add_way.js | 2 +-
 js/id/core/history.js     | 2 +-
 js/id/svg.js              | 2 +-
 js/id/ui/source_switch.js | 2 +-
 4 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/js/id/behavior/add_way.js b/js/id/behavior/add_way.js
index 782d7f183..19f9437cd 100644
--- a/js/id/behavior/add_way.js
+++ b/js/id/behavior/add_way.js
@@ -1,5 +1,5 @@
 iD.behavior.AddWay = function(context) {
-    var event = d3.dispatch('start', 'startFromWay', 'startFromNode')
+    var event = d3.dispatch('start', 'startFromWay', 'startFromNode'),
         draw = iD.behavior.Draw(context);
 
     var addWay = function(surface) {
diff --git a/js/id/core/history.js b/js/id/core/history.js
index caefc45d2..1bedaeb60 100644
--- a/js/id/core/history.js
+++ b/js/id/core/history.js
@@ -187,7 +187,7 @@ iD.History = function(context) {
                     annotation: i.annotation,
                     imagery_used: i.imagery_used,
                     entities: i.graph.entities
-                }
+                };
             }));
 
             context.storage(getKey('history'), json);
diff --git a/js/id/svg.js b/js/id/svg.js
index bbd22fb78..dda91ec99 100644
--- a/js/id/svg.js
+++ b/js/id/svg.js
@@ -41,6 +41,6 @@ iD.svg = {
                 }
             });
             return tags;
-        }
+        };
     }
 };
diff --git a/js/id/ui/source_switch.js b/js/id/ui/source_switch.js
index 1b1bb9530..624e26af4 100644
--- a/js/id/ui/source_switch.js
+++ b/js/id/ui/source_switch.js
@@ -21,5 +21,5 @@ iD.ui.SourceSwitch = function(context) {
             .text(t('source_switch.live'))
             .classed('live', true)
             .on('click', click);
-    }
+    };
 };

From 97ab739f1be4a82e84b2e998804e6cb12f288592 Mon Sep 17 00:00:00 2001
From: Ansis Brammanis 
Date: Mon, 11 Feb 2013 11:00:45 -0500
Subject: [PATCH 322/415] Update bound data for all elements of points,
 vertices

---
 js/id/svg/points.js   | 1 +
 js/id/svg/vertices.js | 3 +--
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/js/id/svg/points.js b/js/id/svg/points.js
index a7fdb10fe..838faf2bb 100644
--- a/js/id/svg/points.js
+++ b/js/id/svg/points.js
@@ -52,6 +52,7 @@ iD.svg.Points = function(projection) {
         // sets the data (point entity) on the element
         groups.select('image')
             .attr('xlink:href', imageHref);
+        groups.select('.shadow, .stroke');
 
         groups.exit()
             .remove();
diff --git a/js/id/svg/vertices.js b/js/id/svg/vertices.js
index e9769a374..339c0752a 100644
--- a/js/id/svg/vertices.js
+++ b/js/id/svg/vertices.js
@@ -40,8 +40,7 @@ iD.svg.Vertices = function(projection) {
 
         // Selecting the following implicitly
         // sets the data (vertix entity) on the elements
-        groups.select('circle.fill');
-        groups.select('circle.stroke');
+        groups.select('circle.fill, circle.stroke, circle.shadow');
 
         groups.exit()
             .remove();

From d34863bfc29f7bafac3cf1540a33541830733cb6 Mon Sep 17 00:00:00 2001
From: Tom MacWright 
Date: Mon, 11 Feb 2013 11:02:58 -0500
Subject: [PATCH 323/415] Toggle ui elements gracefully. Refs #449

---
 index.html                |  1 +
 js/id/ui/geocoder.js      | 16 +++++++++++-----
 js/id/ui/inspector.js     |  4 +++-
 js/id/ui/layerswitcher.js | 10 +++++++---
 js/id/ui/toggle.js        | 15 +++++++++++++++
 5 files changed, 37 insertions(+), 9 deletions(-)
 create mode 100644 js/id/ui/toggle.js

diff --git a/index.html b/index.html
index 5d43a890c..08cd1c9db 100644
--- a/index.html
+++ b/index.html
@@ -78,6 +78,7 @@
         
         
         
+        
 
         
         
diff --git a/js/id/ui/geocoder.js b/js/id/ui/geocoder.js
index 04f68f1ba..0643c41d1 100644
--- a/js/id/ui/geocoder.js
+++ b/js/id/ui/geocoder.js
@@ -6,6 +6,9 @@ iD.ui.geocoder = function(context) {
     }
 
     function geocoder(selection) {
+
+        var shown = false;
+
         function keydown() {
             if (d3.event.keyCode !== 13) return;
             d3.event.preventDefault();
@@ -59,11 +62,14 @@ iD.ui.geocoder = function(context) {
         function toggle() { setVisible(gcForm.classed('hide')); }
 
         function setVisible(show) {
-            button.classed('active', show);
-            gcForm.classed('hide', !show);
-            if (!show) resultsList.classed('hide', !show);
-            if (show) inputNode.node().focus();
-            else inputNode.node().blur();
+            if (show !== shown) {
+                button.classed('active', show);
+                gcForm.call(iD.ui.toggle(show));
+                if (!show) resultsList.classed('hide', !show);
+                if (show) inputNode.node().focus();
+                else inputNode.node().blur();
+                shown = show;
+            }
         }
 
         var button = selection.append('button')
diff --git a/js/id/ui/inspector.js b/js/id/ui/inspector.js
index fd29e08a5..836f73cff 100644
--- a/js/id/ui/inspector.js
+++ b/js/id/ui/inspector.js
@@ -9,7 +9,7 @@ iD.ui.inspector = function() {
         var entity = selection.datum();
 
         var inspector = selection.append('div')
-            .attr('class','inspector content');
+            .attr('class','inspector content hide');
 
         inspector.append('div')
             .attr('class', 'head inspector-inner fillL')
@@ -42,6 +42,8 @@ iD.ui.inspector = function() {
         inspectorbody.append('div')
             .attr('class', 'inspector-buttons pad1 fillD')
             .call(drawButtons);
+
+        inspector.call(iD.ui.toggle(true));
     }
 
     function drawHead(selection) {
diff --git a/js/id/ui/layerswitcher.js b/js/id/ui/layerswitcher.js
index 2007b45fa..9ae4f3fbe 100644
--- a/js/id/ui/layerswitcher.js
+++ b/js/id/ui/layerswitcher.js
@@ -15,7 +15,8 @@ iD.ui.layerswitcher = function(context) {
     function layerswitcher(selection) {
 
         var content = selection
-            .append('div').attr('class', 'content fillD map-overlay hide');
+            .append('div').attr('class', 'content fillD map-overlay hide'),
+            shown = false;
 
         var button = selection
             .append('button')
@@ -30,8 +31,11 @@ iD.ui.layerswitcher = function(context) {
         function toggle() { setVisible(content.classed('hide')); }
 
         function setVisible(show) {
-            button.classed('active', show);
-            content.classed('hide', !show);
+            if (show !== shown) {
+                button.classed('active', show);
+                content.call(iD.ui.toggle(show));
+                shown = show;
+            }
         }
 
         function clickoutside(selection) {
diff --git a/js/id/ui/toggle.js b/js/id/ui/toggle.js
new file mode 100644
index 000000000..96c6af645
--- /dev/null
+++ b/js/id/ui/toggle.js
@@ -0,0 +1,15 @@
+// toggles the visibility of ui elements, using a combination of the
+// hide class, which sets display=none, and a d3 transition for opacity.
+// this will cause blinking when called repeatedly, so check that the
+// value actually changes between calls.
+iD.ui.toggle = function(show) {
+    return function(selection) {
+        selection.style('opacity', show ? 0 : 1)
+            .classed('hide', false)
+            .transition()
+            .style('opacity', show ? 1 : 0)
+            .each('end', function() {
+                d3.select(this).classed('hide', !show);
+            });
+    };
+};

From f1237a952059e2469a744da672a982a13837b70b Mon Sep 17 00:00:00 2001
From: Tom MacWright 
Date: Mon, 11 Feb 2013 11:10:44 -0500
Subject: [PATCH 324/415] Add toggle to tests

---
 test/index.html | 1 +
 1 file changed, 1 insertion(+)

diff --git a/test/index.html b/test/index.html
index 056a0aff8..fe81a4ec5 100644
--- a/test/index.html
+++ b/test/index.html
@@ -72,6 +72,7 @@
     
     
     
+    
 
     
     

From fe9cf436e589897635e0f13e62e61f4996655634 Mon Sep 17 00:00:00 2001
From: Ansis Brammanis 
Date: Mon, 11 Feb 2013 11:19:10 -0500
Subject: [PATCH 325/415] Update translation key

---
 locale/lv.js | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/locale/lv.js b/locale/lv.js
index b7ba5504f..f341f7fed 100644
--- a/locale/lv.js
+++ b/locale/lv.js
@@ -179,8 +179,6 @@ locale.lv = {
 
     logout: "atslēgties",
 
-    live: "live",
-    dev: "dev",
     report_a_bug: "ziņot par kļūdu",
 
     layerswitcher: {
@@ -194,5 +192,10 @@ locale.lv = {
     contributors: {
         list: "{users} papildinājumi redzami",
         truncated_list: "{users} un {count} citu papildinājumi redzami"
+    },
+
+    source_switch: {
+        live: "live",
+        dev: "dev"
     }
 };

From 0e871bb9574da0349d5c81cfe559f6d400a83ed8 Mon Sep 17 00:00:00 2001
From: Ansis Brammanis 
Date: Mon, 11 Feb 2013 11:53:26 -0500
Subject: [PATCH 326/415] Fix shift-click on points in ff

---
 css/map.css | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/css/map.css b/css/map.css
index 73da09b55..885c8195c 100644
--- a/css/map.css
+++ b/css/map.css
@@ -32,6 +32,10 @@ g.point circle {
     fill:#fff;
 }
 
+g.point image {
+  pointer-events: none;
+}
+
 g.point .shadow {
     fill: none;
     pointer-events: all;

From fc6cb352d855be62958a661b57974b83adfd9f4f Mon Sep 17 00:00:00 2001
From: Ansis Brammanis 
Date: Mon, 11 Feb 2013 12:12:18 -0500
Subject: [PATCH 327/415] use compatible mouse position properties

---
 js/id/behavior/lasso.js  | 4 ++--
 js/id/behavior/select.js | 6 +++---
 js/id/modes/select.js    | 4 ++--
 3 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/js/id/behavior/lasso.js b/js/id/behavior/lasso.js
index e99b1bf14..0a26f311d 100644
--- a/js/id/behavior/lasso.js
+++ b/js/id/behavior/lasso.js
@@ -10,7 +10,7 @@ iD.behavior.Lasso = function(context) {
         function mousedown() {
             if (d3.event.shiftKey === true) {
 
-                pos = [d3.event.x, d3.event.y];
+                pos = [d3.event.clientX, d3.event.clientY];
 
                 lasso = iD.ui.lasso().a(d3.mouse(context.surface().node()));
 
@@ -48,7 +48,7 @@ iD.behavior.Lasso = function(context) {
                 .on('mousemove.lasso', null)
                 .on('mouseup.lasso', null);
 
-            if (d3.event.x !== pos[0] || d3.event.y !== pos[1]) {
+            if (d3.event.clientX !== pos[0] || d3.event.clientY !== pos[1]) {
                 var selected = context.graph().intersects(extent);
 
                 if (selected.length) {
diff --git a/js/id/behavior/select.js b/js/id/behavior/select.js
index 4d0bef2de..7369af97c 100644
--- a/js/id/behavior/select.js
+++ b/js/id/behavior/select.js
@@ -22,7 +22,7 @@ iD.behavior.Select = function(context) {
 
         function mousedown() {
             var datum = d3.event.target.__data__;
-            pos = [d3.event.x, d3.event.y];
+            pos = [d3.event.clientX, d3.event.clientY];
             if (datum instanceof iD.Entity || (datum && datum.type === 'midpoint')) {
                 selection
                     .on('mousemove.select', mousemove)
@@ -51,7 +51,7 @@ iD.behavior.Select = function(context) {
 
         // allow mousemoves to cancel the click
         function mousemove() {
-            if (iD.geo.dist([d3.event.x, d3.event.y], pos) > 4) {
+            if (iD.geo.dist([d3.event.clientX, d3.event.clientY], pos) > 4) {
                 window.clearTimeout(timeout);
                 timeout = null;
             }
@@ -59,7 +59,7 @@ iD.behavior.Select = function(context) {
 
         function mouseup() {
             selection.on('mousemove.select', null);
-            if (pos && d3.event.x === pos[0] && d3.event.y === pos[1] &&
+            if (pos && d3.event.clientX === pos[0] && d3.event.clientY === pos[1] &&
                 !(d3.event.target.__data__ instanceof iD.Entity)) {
                 context.enter(iD.modes.Browse(context));
             }
diff --git a/js/id/modes/select.js b/js/id/modes/select.js
index 5732b58df..9b12c8134 100644
--- a/js/id/modes/select.js
+++ b/js/id/modes/select.js
@@ -76,10 +76,10 @@ iD.modes.Select = function(context, selection, initial) {
                 var inspector_size = context.container().select('.inspector-wrap').size(),
                     map_size = context.map().size(),
                     offset = 50,
-                    shift_left = d3.event.x - map_size[0] + inspector_size[0] + offset,
+                    shift_left = d3.event.clientX - map_size[0] + inspector_size[0] + offset,
                     center = (map_size[0] / 2) + shift_left + offset;
 
-                if (shift_left > 0 && inspector_size[1] > d3.event.y) {
+                if (shift_left > 0 && inspector_size[1] > d3.event.clientY) {
                     context.map().centerEase(context.projection.invert([center, map_size[1]/2]));
                 }
             }

From fe32ca9d03ca27ed0b7d5d22400a3b374470f700 Mon Sep 17 00:00:00 2001
From: Ansis Brammanis 
Date: Mon, 11 Feb 2013 12:36:21 -0500
Subject: [PATCH 328/415] Yep, detecting opera.

Goal can be achieved with two different svg styles,
but Opera only implements one, Firefox the other. Can't
apply both because Chrome implements both.
---
 css/map.css | 6 ++++++
 js/id/id.js | 2 ++
 js/id/ui.js | 2 ++
 3 files changed, 10 insertions(+)

diff --git a/css/map.css b/css/map.css
index 885c8195c..bea6edde3 100644
--- a/css/map.css
+++ b/css/map.css
@@ -648,7 +648,13 @@ text.pointlabel {
 }
 
 .pathlabel .textpath {
+  dominant-baseline: middle;
+}
+
+/* Opera doesn't support dominant-baseline */
+.opera .pathlabel .textpath {
   baseline-shift: -33%;
+  dominant-baseline: auto;
 }
 
 .pointlabel-halo,
diff --git a/js/id/id.js b/js/id/id.js
index 665e0b0a1..df986bc5f 100644
--- a/js/id/id.js
+++ b/js/id/id.js
@@ -127,6 +127,8 @@ iD.detect = function() {
         browser.support = true;
     }
 
+    browser.opera = ua.indexOf('Opera') >= 0;
+
     browser.locale = navigator.language;
 
     function nav(x) {
diff --git a/js/id/ui.js b/js/id/ui.js
index 529d8bca5..ecab88470 100644
--- a/js/id/ui.js
+++ b/js/id/ui.js
@@ -12,6 +12,8 @@ iD.ui = function(context) {
             return;
         }
 
+        if (iD.detect().opera) container.classed('opera', true);
+
         function hintprefix(x, y) {
             return '' + y + '' + '
' + x + '
'; } From d458a3707f2271bbc8ee69e37ac9233f2e052c86 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Mon, 11 Feb 2013 12:42:02 -0500 Subject: [PATCH 329/415] Add comments about opera detection --- css/map.css | 2 +- js/id/id.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/css/map.css b/css/map.css index bea6edde3..d8f8ab220 100644 --- a/css/map.css +++ b/css/map.css @@ -651,7 +651,7 @@ text.pointlabel { dominant-baseline: middle; } -/* Opera doesn't support dominant-baseline */ +/* Opera doesn't support dominant-baseline. See #715 */ .opera .pathlabel .textpath { baseline-shift: -33%; dominant-baseline: auto; diff --git a/js/id/id.js b/js/id/id.js index df986bc5f..a7d7a44ac 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -127,6 +127,7 @@ iD.detect = function() { browser.support = true; } + // Added due to incomplete svg style support. See #715 browser.opera = ua.indexOf('Opera') >= 0; browser.locale = navigator.language; From c77f23a809ee5cd14f73e5418e04b81f7d0d7fc4 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Mon, 11 Feb 2013 12:47:26 -0500 Subject: [PATCH 330/415] Added lint to locale, update a french and german with strings to translate --- locale/de.js | 17 ++++ locale/fr.js | 21 ++++- locale/lint.js | 237 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 273 insertions(+), 2 deletions(-) create mode 100644 locale/lint.js diff --git a/locale/de.js b/locale/de.js index 0c7231f25..6087c7c71 100644 --- a/locale/de.js +++ b/locale/de.js @@ -141,6 +141,8 @@ locale.de = { }, save: "Speichern", + // TODO + unsaved_changes: "You have unsaved changes", save_help: "Speichere Änderungen auf OpenStreetMap, um diese für andere Nutzer sichtbar zu machen", no_changes: "Sie haben keine Änderungen zum Speichern.", save_error: "Beim Speichern ist ein Fehler aufgetreten", @@ -176,13 +178,28 @@ locale.de = { description: "Beschreibung", + // TODO + report_a_bug: "report a bug", + logout: "Abmelden", + // TODO + contributors: { + list: "Viewing contributions by {users}", + truncated_list: "Viewing contributions by {users} and {count} others" + }, + layerswitcher: { title: "Hintergrund", description: "Hintergrundeinstellungen", percent_brightness: "{opacity}% Helligkeit", fix_misalignment: "Fehlerhafte Ausrichtung reparieren", reset: "Zurücksetzen" + }, + + // TODO + source_switch: { + live: "live", + dev: "dev" } }; diff --git a/locale/fr.js b/locale/fr.js index 904d5cb9e..eeb280beb 100644 --- a/locale/fr.js +++ b/locale/fr.js @@ -141,6 +141,8 @@ locale.fr = { }, save: "Sauvegarder", + // TODO + unsaved_changes: "You have unsaved changes", save_help: "Envoie des modifications au serveyr OpenStreetMap afin qu'elles soient visibles par les autres contributeurs.", no_changes: "Vous n'avez aucune modification à enregistrer.", save_error: "Une erreur est survenue lors de l'enregistrement des données", @@ -169,19 +171,34 @@ locale.fr = { edit_tags: "Editer les tags", geocoder: { - "find_location": "Trouver un emplacement", - "find_a_place": "Trouver un endroit" + title: "Trouver un emplacement", + placeholder: "Trouver un endroit", + // TODO + no_results: "Couldn't locate a place named '{name}'" }, description: "Description", logout: "Déconnexion", + // TODO + report_a_bug: "report a bug", + + contributors: { + list: "Viewing contributions by {users}", + truncated_list: "Viewing contributions by {users} and {count} others" + }, + layerswitcher: { title: "Fond de carte", description: "Paramètres du fond de carte", percent_brightness: "{opacity}% brightness", fix_misalignment: "Fix misalignment", reset: "reset" + }, + + source_switch: { + live: "live", + dev: "dev" } }; diff --git a/locale/lint.js b/locale/lint.js new file mode 100644 index 000000000..44c5139e7 --- /dev/null +++ b/locale/lint.js @@ -0,0 +1,237 @@ +/** + * Fragment used to represent a string fragment in the diff. + */ +var Fragment = function (string) { + this.content = string; + this.equiv = false; +}; + +/** + * Wrap in given tag or return the clean value. + */ +Fragment.prototype.toString = function (tag) { + if (this.equiv || !tag) { + return this.content; + } + else { + return '<' + tag + '>' + this.content + ''; + } +}; + +var moveToEnd = function (a, i, k) { + if (!a.equiv && (!k[i-1] || k[i-1].equiv)) { + // Find next item equiv item. + for (var j = i+1; k[j] && !k[j].equiv; j++); + if (k[j] && k[j].content === a.content) { + k[i] = k[j]; + k[j] = a; + } + } +}; + +var aggregate = function (a, i, k) { + if (!a.equiv && k[i+1] && !k[i+1].equiv) { + k[i+1].content = a.content + k[i+1].content; + delete k[i]; + } +}; + +var join = function (what, t) { + return what.map(function (a) { + if (a) return a.toString(t); + }).join(''); +}; + +var clone = function(source) { + if (typeof source === 'object' && source !== null) { + var target = Array.isArray(source) ? [] : {}; + for (var key in source) target[key] = clone(source[key]); + return target; + } + return source; +}; + +var WordDiff = { + nonWord: /(&.+?;|[\u0000-\u0040\u005B-\u0060\u007B-\u00A9\u00AB-\u00B4\u00B6-\u00B9\u00BB-\u00BF\u00D7\u00F7\u02C2-\u02C5\u02D2-\u02DF\u02E5-\u02EB\u02ED\u02EF-\u036F\u0375\u037E\u0384\u0385\u0387\u03F6\u0482-\u0489\u055A-\u055F\u0589\u058A\u0591-\u05C7\u05F3\u05F4\u0600-\u0603\u0606-\u061B\u061E\u061F\u064B-\u065E\u0660-\u066D\u0670\u06D4\u06D6-\u06E4\u06EA-\u06ED\u06F0-\u06F9\u06FD\u06FE\u0700-\u070D\u070F\u0711\u0730-\u074A\u07A6-\u07B0\u07C0-\u07C9\u07EB-\u07F3\u07F6-\u07F9\u0901-\u0903\u093C\u093E-\u094D\u0951-\u0954\u09E2\u0962-\u0970\u06E7-\u06E9\u0981-\u0983\u09BC\u09BE-\u09C4\u09C7\u09C8\u09CB-\u09CD\u09D7\u09E3\u09E6-\u09EF\u09F2-\u09FA\u0A01-\u0A03\u0A3C\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A66-\u0A71\u0A75\u0A81-\u0A83\u0ABC\u0ABE-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0AE2\u0AE3\u0AE6-\u0AEF\u0AF1\u0B01-\u0B03\u0B3C\u0B3E-\u0B44\u0B47\u0B48\u0B4B-\u0B4D\u0B56\u0B57\u0B62\u0B63\u0B66-\u0B70\u0B82\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD7\u0BE6-\u0BFA\u0C01-\u0C03\u0C3E-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C62\u0C63\u0C66-\u0C6F\u0C78-\u0C7F\u0C82\u0C83\u0CBC\u0CBE-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5\u0CD6\u0CE2\u0CE3\u0CE6-\u0CEF\u0CF1\u0CF2\u0D02\u0D03\u0D3E-\u0D44\u0D46-\u0D48\u0D4A-\u0D4D\u0D57\u0D62\u0D63\u0D66-\u0D75\u0D79\u0D82\u0D83\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DF2-\u0DF4\u0E31\u0E34-\u0E3A\u0E3F\u0E47-\u0E5B\u0EB1\u0EB4-\u0EB9\u0EBB\u0EBC\u0EC8-\u0ECD\u0ED0-\u0ED9\u0F01-\u0F3F\u0F71-\u0F87\u0F90-\u0F97\u0F99-\u0FBC\u0FBE-\u0FCC\u0FCE-\u0FD4\u102B-\u103E\u1040-\u104F\u1056-\u1059\u105E-\u1060\u1062-\u1064\u1067-\u106D\u1071-\u1074\u1082-\u108D\u108F-\u1099\u109E\u109F\u10FB\u135F-\u137C\u1390-\u1399\u166D\u166E\u1680\u169B\u169C\u16EB-\u16F0\u1712-\u1714\u1732-\u1736\u1752\u1753\u1772\u1773\u17B4-\u17D6\u17D8-\u17DB\u17DD\u17E0-\u17E9\u17F0-\u17F9\u1800-\u180E\u1810-\u1819\u18A9\u1920-\u192B\u1930-\u193B\u1940\u1944-\u194F\u19B0-\u19C0\u19C8\u19C9\u19D0-\u19D9\u19DE-\u19FF\u1A17-\u1A1B\u1A1E\u1A1F\u1B00-\u1B04\u1B34-\u1B44\u1B50-\u1B7C\u1B80-\u1B82\u1BA1-\u1BAA\u1BB0-\u1BB9\u1C24-\u1C37\u1C3B-\u1C49\u1C50-\u1C59\u1C7E\u1C7F\u1DC0-\u1DE6\u1DFE\u1DFF\u1FBD\u1FBF-\u1FC1\u1FCD-\u1FCF\u1FDD-\u1FDF\u1FED-\u1FEF\u1FFD\u1FFE\u2000-\u2064\u206A-\u2070\u2074-\u207E\u2080-\u208E\u20A0-\u20B5\u20D0-\u20F0\u2100\u2101\u2103-\u2106\u2108\u2109\u2114\u2116-\u2118\u211E-\u2123\u2125\u2127\u2129\u212E\u213A\u213B\u2140-\u2144\u214A-\u214D\u214F\u2153-\u2182\u2185-\u2188\u2190-\u23E7\u2400-\u2426\u2440-\u244A\u2460-\u269D\u26A0-\u26BC\u26C0-\u26C3\u2701-\u2704\u2706-\u2709\u270C-\u2727\u2729-\u274B\u274D\u274F-\u2752\u2756\u2758-\u275E\u2761-\u2794\u2798-\u27AF\u27B1-\u27BE\u27C0-\u27CA\u27CC\u27D0-\u2B4C\u2B50-\u2B54\u2CE5-\u2CEA\u2CF9-\u2CFF\u2DE0-\u2E2E\u2E30\u2E80-\u2E99\u2E9B-\u2EF3\u2F00-\u2FD5\u2FF0-\u2FFB\u3000-\u3004\u3007-\u3030\u3036-\u303A\u303D-\u303F\u3099-\u309C\u30A0\u30FB\u3190-\u319F\u31C0-\u31E3\u3200-\u321E\u3220-\u3243\u3250-\u32FE\u3300-\u33FF\u4DC0-\u4DFF\uA490-\uA4C6\uA60D-\uA60F\uA620-\uA629\uA66F-\uA673\uA67C-\uA67E\uA700-\uA716\uA720\uA721\uA789\uA78A\uA802\uA806\uA80B\uA823-\uA82B\uA874-\uA877\uA880\uA881\uA8B4-\uA8C4\uA8CE-\uA8D9\uA900-\uA909\uA926-\uA92F\uA947-\uA953\uA95F\uAA29-\uAA36\uAA43\uAA4C\uAA4D\uAA50-\uAA59\uAA5C-\uAA5F\uD800\uDB7F\uDB80\uDBFF\uDC00\uDFFF\uE000\uF8FF\uFB1E\uFB29\uFD3E\uFD3F\uFDFC\uFDFD\uFE00-\uFE19\uFE20-\uFE26\uFE30-\uFE52\uFE54-\uFE66\uFE68-\uFE6B\uFEFF\uFF01-\uFF20\uFF3B-\uFF40\uFF5B-\uFF65\uFFE0-\uFFE6\uFFE8-\uFFEE\uFFF9-\uFFFD])/, + + tokenize: function (args) { + // Split on non-word characters. + for (var type in args) { + args[type] = args[type].split(WordDiff.nonWord).filter(function (s) { + return s.length; + }); + } + + // Calculate the indexes and offsets for common suffixes and prefixes. + var i = -1, j = args.del.length, k = args.ins.length; + while (args.del[++i] === args.ins[i] && i <= j); + while (j >= i && k >= i && args.del[--j] === args.ins[--k]); + + args.prefix = args.del.slice(0, i).join(''); + args.suffix = args.del.slice(j + 1).join(''); + args.del = args.del.slice(i, ++j); + args.ins = args.ins.slice(i, ++k); + }, + + lcs: function (args) { + var matrix = []; + + for (var i = 0; i < args.del.length; i++) { + matrix[i] = []; + for (var j = 0; j < args.ins.length; j++) { + if (args.del[i] === args.ins[j]) { + matrix[i][j] = (matrix[i - 1] && matrix[i - 1][j - 1] || 0) + args.del[i].length; + } + else { + matrix[i][j] = Math.max(matrix[i][j - 1] || 0, matrix[i - 1] && matrix[i - 1][j] || 0); + } + } + } + + return matrix; + }, + + changeset: function (args, matrix) { + var result = {}; + + ['del', 'ins'].forEach(function (type) { + result[type] = args[type].map(function (a) { return new Fragment(a); }); + }); + + // Backtrack through the matrix. + for (var i = result.del.length - 1, j = result.ins.length - 1; i >= 0; i--, j--) { + if (j < 0 || result.del[i].content !== result.ins[j].content) { + if (j < 0 || (j > 0 && matrix[i - 1] && (matrix[i][j - 1] < matrix[i - 1][j]))) { + j++; + } + else { + i++; + } + } + else { + result.del[i] = result.ins[j]; + result.del[i].equiv = true; + } + } + + // Fill up gaps. + for (var i = 0; i < result.del.length; i++) { + if (result.del[i].equiv && result.del[i].content.length < 3) { + var j = result.ins.indexOf(result.del[i]); + if (result.del[i-1] && result.del[i+1] && result.ins[j-1] && result.ins[j+1] && !result.del[i-1].equiv && !result.del[i+1].equiv && !result.ins[j-1].equiv && !result.ins[j+1].equiv){ + result.del[i].equiv = false; + result.ins[j] = clone(result.del[i]); + } + } + } + + ['del', 'ins'].forEach(function (type) { + // Try to move changes to the end. + for (var i = 0; i < result[type].length; i++) + moveToEnd(result[type][i], i, result[type]); + + // Aggregate subsequent changes to minimize ins/del tags. + for (var i = 0; i < result[type].length; i++) + aggregate(result[type][i], i, result[type]); + }); + + return result; + }, + + htmlRender: function (args, result) { + var diff = { + del: args.prefix + join(result.del, 'del') + args.suffix, + ins: args.prefix + join(result.ins, 'ins') + args.suffix + }; + + return diff; + }, + + htmlDiff: function (del, ins) { + var args = { 'del': del, 'ins': ins }; + + WordDiff.tokenize(args); + var matrix = WordDiff.lcs(args); + var result = WordDiff.changeset(args, matrix); + return WordDiff.htmlRender(args, result); + }, + + render: function (args, result) { + var join = function (what, type) { + return what.map(function (a) { + if (!a) return; + if (a.equiv) return a.content; + if (type == 'del') return '\033[31;4m' + a.content + '\033[0m'; + if (type == 'ins') return '\033[32;4m' + a.content + '\033[0m'; + }).join(''); + }; + + return { + del: args.prefix + join(result.del, 'del') + args.suffix, + ins: args.prefix + join(result.ins, 'ins') + args.suffix + }; + }, + + diff: function(del, ins) { + var args = { 'del': del, 'ins': ins }; + + WordDiff.tokenize(args); + var matrix = WordDiff.lcs(args); + var result = WordDiff.changeset(args, matrix); + return WordDiff.render(args, result); + } +}; + + + + +var fs = require('fs'), _ = require('lodash'); + +function getKeys(lang, keys, prefix) { + keys = keys || []; + prefix = prefix || ''; + for (var i in lang) { + keys.push(prefix + i); + if (typeof lang[i] === 'object') { + getKeys(lang[i], keys, i + '.'); + } + } + return keys; +} + +var languages = ['de', 'en', 'es', 'fr', 'lv', 'tr']; +var langkeys = {}; + +eval(fs.readFileSync('./locale.js', 'utf8')); +for (var i = 0; i < languages.length; i++) { + eval(fs.readFileSync('./' + languages[i] + '.js', 'utf8')); + langkeys[languages[i]] = getKeys(locale[languages[i]]).sort(); +} + +// for (var i = 1; i < languages.length - 1; i++) { +// +// var changes = WordDiff.diff( +// langkeys[languages[i]].join(','), +// langkeys[languages[i + 1]].join(',')); +// +// +// console.warn('actual:' + '\n' + changes.del); +// console.warn('expected:' + '\n' + changes.ins); +// } + +var allkeys = []; +_.forEach(langkeys, function(l) { + allkeys = _.union(allkeys, l); +}); + +// console.warn('\n\n------------------------------------------'); +// console.warn('all keys ---------------------------------\n\n'); + +// console.log(allkeys.join(',')); + +_.forEach(langkeys, function(l, k) { + var missing = _.difference(allkeys, l); + if (missing.length) { + console.log('\n', k, 'is missing\n\n', _.difference(allkeys, l).join(',')); + } +}); From e8efb981506014cba4a9dac0a34a66859f0bfeae Mon Sep 17 00:00:00 2001 From: nyampire Date: Tue, 12 Feb 2013 02:53:52 +0900 Subject: [PATCH 331/415] Add Japanese Translation --- locale/ja.js | 201 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 locale/ja.js diff --git a/locale/ja.js b/locale/ja.js new file mode 100644 index 000000000..bb44b8739 --- /dev/null +++ b/locale/ja.js @@ -0,0 +1,201 @@ +locale.en = { + modes: { + add_area: { + title: "エリア", + description: "公園や建物、湖沼、をマップに追加します", + tail: "マップをクリックすると、公園や湖沼、建物などのエリアの描画が開始されます。", + key: "A" + }, + add_line: { + title: "ライン", + description: "ラインは車両用の道路や歩道、用水路を表すことができます", + tail: "マップをクリックすると、道路や歩道、流水経路の描画が始まります", + key: "L" + }, + add_point: { + title: "ポイント", + description: "レストランや記念碑、郵便ボックスはポイントで表現します", + tail: "マップをクリックするとポイントを追加できます", + key: "P" + }, + browse: { + title: "ブラウズ", + description: "マップを拡大縮小します", + key: "B" + }, + draw_area: { + tail: "クリックするとエリアにポイントを追加できます。起点となっているポイントをクリックするとエリアが作成されます" + }, + draw_line: { + tail: "クリックするとラインにポイントを追加できます。クリックすることで他のラインと接続することが可能です。ライン描画を終了するにはダブルクリックしてください" + } + }, + + operations: { + add: { + annotation: { + point: "ポイントを追加しました", + vertex: "ウェイにノードを追加しました" + } + }, + start: { + annotation: { + line: "ラインの描画を開始しました", + area: "エリアの描画を開始しました" + } + }, + 'continue': { + annotation: { + line: "ライン描画を継続中", + area: "エリア描画を継続中" + } + }, + cancel_draw: { + annotation: "描画をキャンセルしました" + }, + change_tags: { + annotation: "タグを変更しました" + }, + circularize: { + title: "円状に並べる", + description: "この地物を円状に配置します", + key: "O", + annotation: { + line: "ラインを円状にしました", + area: "エリアを円状にしました" + } + }, + orthogonalize: { + title: "角の直交化Orthogonalize", + description: "角を90度に配置します", + key: "Q", + annotation: { + line: "ラインの角を90度にしました", + area: "エリアの角を90度にしました" + } + }, + 'delete': { + title: "削除", + description: "この地物をマップから削除します", + key: "⌫", + annotation: { + point: "ポイント削除しました", + vertex: "ウェイ上のノードを削除しました", + line: "ライン削除しました", + area: "エリア削除しました", + relation: "リレーション削除しました", + multiple: "{n} 個のオブジェクトを削除しました" + } + }, + connect: { + annotation: { + point: "ウェイをポイントに接続しました", + vertex: "ウェイを他のウェイト接続しました", + line: "ウェイとラインを接続しました", + area: "ウェイとエリアを接続しました" + } + }, + disconnect: { + title: "接続解除", + description: "ウェイの接続を解除して切り離します", + key: "D", + annotation: "ウェイの接続を解除しました" + }, + merge: { + title: "結合", + description: "複数のラインを結合します", + key: "C", + annotation: "{n} 本のラインを結合しました" + }, + move: { + title: "移動", + description: "この地物を別の位置に移動させます", + key: "M", + annotation: { + point: "ポイントを移動しました", + vertex: "ウェイ上のノードを移動しました", + line: "ラインを移動しました", + area: "エリアを移動しました" + } + }, + reverse: { + title: "方向反転", + description: "ラインの向きを反転させます", + key: "V", + annotation: "ラインの向きを反転しました" + }, + split: { + title: "分割", + description: "このポイントを境目としてウェイを2つに分割します", + key: "X", + annotation: "ウェイを分割しました" + } + }, + + validations: { + untagged_point: "ポイントにタグが付与されておらず、ラインやエリアの一部でもありません", + untagged_line: "ラインにタグが付与されていません", + untagged_area: "エリアにタグが付与されていません", + tag_suggests_area: "ラインに {tag} タグが付与されています。エリアで描かれるべきです", + deprecated_tags: "タグの重複: {tags}" + }, + + save: "Save", + unsaved_changes: "変更が保存されていません", + save_help: "変更点をOpenStreetMapに保存し、他ユーザが確認できるようにします", + no_changes: "変更点がありません", + save_error: "データ保存中にエラーが発生しました", + uploading_changes: "変更点をOpenStreetMapへアップロードしています", + just_edited: "OpenStreetMap編集完了!", + okay: "OK", + + "zoom-in": "ズームイン", + "zoom-out": "ズームアウト", + + nothing_to_undo: "やり直す変更点がありません", + nothing_to_redo: "やり直した変更点がありません", + + browser_notice: "このエディタは Firefox, Chrome, Safari, Opera, および Internet Explorer 9 以上をサポートしています。ブラウザのバージョンを更新するか、Potlatch 2を使用して編集してください", + + inspector: { + no_documentation_combination: "このタグの組み合わせに関する説明文はありません", + no_documentation_key: "このキーに対する説明文はありません", + new_tag: "新規タグ" + }, + + view_on_osm: "OSMで確認", + + zoom_in_edit: "編集するにはさらに地図を拡大してください", + + edit_tags: "タグを編集", + + geocoder: { + title: "特定地点を検索", + placeholder: "地点を検索", + no_results: "'{name}' という名称の地点が見つかりません" + }, + + description: "説明", + + logout: "ログアウト", + + report_a_bug: "バグを報告", + + layerswitcher: { + title: "背景画像", + description: "背景画像の設定", + percent_brightness: "{opacity}% 輝度", + fix_misalignment: "値の調整", + reset: "設定リセット" + }, + + contributors: { + list: "{users} による編集履歴を確認", + truncated_list: "{users} とその他 {count} 人による編集履歴を表示" + }, + + source_switch: { + live: "本番サーバ", + dev: "開発サーバ" + } +}; From 16bbc88cdf68172e5aa4e4bb7088d7717371cb27 Mon Sep 17 00:00:00 2001 From: Martin Raifer Date: Mon, 11 Feb 2013 20:06:45 +0100 Subject: [PATCH 332/415] Update locale/de.js. Fixes #719 --- locale/de.js | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/locale/de.js b/locale/de.js index 6087c7c71..f74c1a3fc 100644 --- a/locale/de.js +++ b/locale/de.js @@ -141,10 +141,9 @@ locale.de = { }, save: "Speichern", - // TODO - unsaved_changes: "You have unsaved changes", + unsaved_changes: "Ungespeicherte Änderugen vorhanden", save_help: "Speichere Änderungen auf OpenStreetMap, um diese für andere Nutzer sichtbar zu machen", - no_changes: "Sie haben keine Änderungen zum Speichern.", + no_changes: "Keine Änderungen zum Speichern vorhanden.", save_error: "Beim Speichern ist ein Fehler aufgetreten", uploading_changes: "Änderungen werden zu OpenStreetMap hochgeladen.", just_edited: "Sie haben gerade OpenStreetMap editiert!", @@ -178,15 +177,13 @@ locale.de = { description: "Beschreibung", - // TODO - report_a_bug: "report a bug", + report_a_bug: "Programmfehler melden", logout: "Abmelden", - // TODO contributors: { - list: "Viewing contributions by {users}", - truncated_list: "Viewing contributions by {users} and {count} others" + list: "Diese Kartenansicht enthält Beiträge von:", + truncated_list: "Diese Kartenansicht enthält Beiträge von: {users} und {count} Anderen" }, layerswitcher: { @@ -197,7 +194,6 @@ locale.de = { reset: "Zurücksetzen" }, - // TODO source_switch: { live: "live", dev: "dev" From 66afcd992340216d15b8710b20dba080a7c14174 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Mon, 11 Feb 2013 11:07:29 -0800 Subject: [PATCH 333/415] Platform-specific keybindings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Specifying keybindings that use a command key (⌘ on Mac, Ctrl on Win/Linux) using the appropriate Mac binding, e.g. '⌘Z'. Use iD.ui.cmd to translate it to an appropriate key string for other platforms, e.g. 'Ctrl+Z'. --- css/app.css | 1 - index.html | 1 + js/id/ui.js | 18 +++++------------- js/id/ui/cmd.js | 22 ++++++++++++++++++++++ js/lib/d3.keybinding.js | 2 +- 5 files changed, 29 insertions(+), 15 deletions(-) create mode 100644 js/id/ui/cmd.js diff --git a/css/app.css b/css/app.css index 355fd7262..44d7d7dbf 100644 --- a/css/app.css +++ b/css/app.css @@ -1333,7 +1333,6 @@ a.success-action { color: #222; font-size: 10px; padding: 0px 7px; - text-transform: uppercase; font-weight: bold; display: inline-block; border-radius: 2px; diff --git a/index.html b/index.html index 08cd1c9db..8c084cf77 100644 --- a/index.html +++ b/index.html @@ -60,6 +60,7 @@ + diff --git a/js/id/ui.js b/js/id/ui.js index ecab88470..7214628b4 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -200,20 +200,14 @@ iD.ui = function(context) { } } - var mod = { - 'mac': '⌘', - 'win': 'Ctrl', - 'linux': 'Ctrl' - }[iD.detect().os]; - limiter.select('#undo') .classed('disabled', !undo) - .attr('data-original-title', hintprefix(mod + ' + Z', undo || t('nothing_to_undo'))) + .attr('data-original-title', hintprefix(iD.ui.cmd('⌘Z'), undo || t('nothing_to_undo'))) .call(refreshTooltip); limiter.select('#redo') .classed('disabled', !redo) - .attr('data-original-title', hintprefix(mod + ' + ⇧ + Z', redo || t('nothing_to_redo'))) + .attr('data-original-title', hintprefix(iD.ui.cmd('⌘⇧Z'), redo || t('nothing_to_redo'))) .call(refreshTooltip); }); @@ -232,16 +226,14 @@ iD.ui = function(context) { var pa = 5; var keybinding = d3.keybinding('main') - .on('⌘+Z', function() { history.undo(); }) - .on('⌃+Z', function() { history.undo(); }) - .on('⌘+⇧+Z', function() { history.redo(); }) - .on('⌃+⇧+Z', function() { history.redo(); }) + .on(iD.ui.cmd('⌘Z'), function() { history.undo(); }) + .on(iD.ui.cmd('⌘⇧Z'), function() { history.redo(); }) .on('⌫', function() { d3.event.preventDefault(); }) .on('←', pan([pa, 0])) .on('↑', pan([0, pa])) .on('→', pan([-pa, 0])) .on('↓', pan([0, -pa])) - .on('⇧+=', function() { map.zoomIn(); }) + .on('⇧=', function() { map.zoomIn(); }) .on('+', function() { map.zoomIn(); }) .on('-', function() { map.zoomOut(); }) .on('dash', function() { map.zoomOut(); }); diff --git a/js/id/ui/cmd.js b/js/id/ui/cmd.js new file mode 100644 index 000000000..9876da236 --- /dev/null +++ b/js/id/ui/cmd.js @@ -0,0 +1,22 @@ +// Translate a MacOS key command into the appropriate Windows/Linux equivalent. +// For example, ⌘Z -> Ctrl+Z +iD.ui.cmd = function(code) { + if (iD.detect().os === 'mac') + return code; + + var modifiers = { + '⌘': 'Ctrl', + '⇧': 'Shift', + '⌥': 'Alt' + }, keys = []; + + for (var i = 0; i < code.length; i++) { + if (code[i] in modifiers) { + keys.push(modifiers[code[i]]); + } else { + keys.push(code[i]); + } + } + + return keys.join('+'); +}; diff --git a/js/lib/d3.keybinding.js b/js/lib/d3.keybinding.js index b1106c976..6aa39fb39 100644 --- a/js/lib/d3.keybinding.js +++ b/js/lib/d3.keybinding.js @@ -63,7 +63,7 @@ d3.keybinding = function(namespace) { callback: callback }; - code = code.toLowerCase().match(/(?:(?:[^+])+|\+\+|^\+$)/g); + code = code.toLowerCase().match(/(?:(?:[^+⇧⌃⌥⌘])+|[⇧⌃⌥⌘]|\+\+|^\+$)/g); for (var i = 0; i < code.length; i++) { // Normalise matching errors From 1298fe195dc5be44855161f8d1106306e3ceb996 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Mon, 11 Feb 2013 11:15:23 -0800 Subject: [PATCH 334/415] Remove keybinding coupling from iD.behavior.drag --- js/id/behavior/drag.js | 10 ---------- js/id/behavior/drag_node.js | 26 ++++++++++++++++++++------ 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/js/id/behavior/drag.js b/js/id/behavior/drag.js index cd6f607bf..012e08d34 100644 --- a/js/id/behavior/drag.js +++ b/js/id/behavior/drag.js @@ -24,7 +24,6 @@ iD.behavior.drag = function() { origin = null, selector = '', filter = null, - keybinding = d3.keybinding('drag'), event_, target; event.of = function(thiz, argumentz) { @@ -137,9 +136,6 @@ iD.behavior.drag = function() { drag.off = function(selection) { selection.on("mousedown.drag" + selector, null) .on("touchstart.drag" + selector, null); - keybinding - .on('⌘+Z', null) - .on('⌃+Z', null); }; drag.delegate = function(_) { @@ -174,11 +170,5 @@ iD.behavior.drag = function() { return drag; }; - keybinding - .on('⌘+Z', drag.cancel) - .on('⌃+Z', drag.cancel); - - d3.select(document).call(keybinding); - return d3.rebind(drag, event, "on"); }; diff --git a/js/id/behavior/drag_node.js b/js/id/behavior/drag_node.js index e3028953d..3e652794d 100644 --- a/js/id/behavior/drag_node.js +++ b/js/id/behavior/drag_node.js @@ -36,6 +36,8 @@ iD.behavior.DragNode = function(context) { } function start(entity) { + context.history() + .on('undone.drag-node', cancel); wasMidpoint = entity.type === 'midpoint'; if (wasMidpoint) { @@ -95,12 +97,7 @@ iD.behavior.DragNode = function(context) { } function end(entity) { - context.surface() - .classed('behavior-drag-node', false) - .selectAll('.active') - .classed('active', false); - - stopNudge(); + off(); var d = datum(); if (d.type === 'way') { @@ -132,6 +129,23 @@ iD.behavior.DragNode = function(context) { } } + function off() { + context.history() + .on('undone.drag_node', null); + + context.surface() + .classed('behavior-drag-node', false) + .selectAll('.active') + .classed('active', false); + + stopNudge(); + } + + function cancel() { + off(); + behavior.cancel(); + } + var behavior = iD.behavior.drag() .delegate("g.node, g.midpoint") .origin(origin) From c97084555314725805187198c1bdd64626710d98 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Mon, 11 Feb 2013 14:52:30 -0500 Subject: [PATCH 335/415] Fix #717 --- css/app.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/css/app.css b/css/app.css index 44d7d7dbf..2d0b91bc4 100644 --- a/css/app.css +++ b/css/app.css @@ -903,6 +903,10 @@ img.tile { -o-transform-origin:0 0; } +#surface { + position: static; +} + #tile-g { opacity: 0.5; } From 7401bc431c9003a486555ce5f82fa8d4d6dc421b Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Mon, 11 Feb 2013 15:38:39 -0500 Subject: [PATCH 336/415] Fade lasso in and out --- js/id/ui/lasso.js | 15 ++++++--------- js/id/ui/toggle.js | 3 ++- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/js/id/ui/lasso.js b/js/id/ui/lasso.js index 338fa2f2d..511df2a7e 100644 --- a/js/id/ui/lasso.js +++ b/js/id/ui/lasso.js @@ -8,21 +8,18 @@ iD.ui.lasso = function() { function lasso(selection) { group = selection.append('g') - .attr('class', 'lasso') - .attr('opacity', 0); + .attr('class', 'lasso hide'); box = group.append('rect') .attr('class', 'lasso-box'); - group.transition() - .style('opacity', 1); + group.call(iD.ui.toggle(true)); } // top-left function topLeft(d) { - return 'translate(' + - [Math.min(d[0][0], d[1][0]), Math.min(d[0][1], d[1][1])].join(',') + ')'; + return 'translate(' + Math.min(d[0][0], d[1][0]) + ',' + Math.min(d[0][1], d[1][1]) + ')'; } function width(d) { return Math.abs(d[0][0] - d[1][0]); } @@ -53,9 +50,9 @@ iD.ui.lasso = function() { lasso.close = function(selection) { if (group) { - group.transition() - .attr('opacity', 0) - .remove(); + group.call(iD.ui.toggle(false, function() { + d3.select(this).remove(); + })); } }; diff --git a/js/id/ui/toggle.js b/js/id/ui/toggle.js index 96c6af645..3a7500023 100644 --- a/js/id/ui/toggle.js +++ b/js/id/ui/toggle.js @@ -2,7 +2,7 @@ // hide class, which sets display=none, and a d3 transition for opacity. // this will cause blinking when called repeatedly, so check that the // value actually changes between calls. -iD.ui.toggle = function(show) { +iD.ui.toggle = function(show, callback) { return function(selection) { selection.style('opacity', show ? 0 : 1) .classed('hide', false) @@ -10,6 +10,7 @@ iD.ui.toggle = function(show) { .style('opacity', show ? 1 : 0) .each('end', function() { d3.select(this).classed('hide', !show); + if (callback) callback.apply(this); }); }; }; From 4d976013f93ca4ab54d44afccf514c1df223fdd1 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Mon, 11 Feb 2013 12:44:04 -0800 Subject: [PATCH 337/415] Connect via drag for points (fixes #725) --- css/map.css | 4 ++++ js/id/behavior/drag_node.js | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/css/map.css b/css/map.css index d8f8ab220..43db827fe 100644 --- a/css/map.css +++ b/css/map.css @@ -52,6 +52,10 @@ g.point.selected .shadow { fill-opacity: 0.7; } +g.point.active, g.point.active * { + pointer-events: none; +} + /* vertices */ g.vertex .fill { diff --git a/js/id/behavior/drag_node.js b/js/id/behavior/drag_node.js index 3e652794d..1d1afcc2c 100644 --- a/js/id/behavior/drag_node.js +++ b/js/id/behavior/drag_node.js @@ -147,7 +147,7 @@ iD.behavior.DragNode = function(context) { } var behavior = iD.behavior.drag() - .delegate("g.node, g.midpoint") + .delegate("g.node, g.point, g.midpoint") .origin(origin) .on('start', start) .on('move', move) From b0bd041f2ff934066ba7bed02f0433a55c82d9a3 Mon Sep 17 00:00:00 2001 From: nyampire Date: Tue, 12 Feb 2013 06:00:07 +0900 Subject: [PATCH 338/415] Fix minor expression --- locale/ja.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/locale/ja.js b/locale/ja.js index bb44b8739..3f6455554 100644 --- a/locale/ja.js +++ b/locale/ja.js @@ -24,10 +24,10 @@ locale.en = { key: "B" }, draw_area: { - tail: "クリックするとエリアにポイントを追加できます。起点となっているポイントをクリックするとエリアが作成されます" + tail: "クリックするとエリア上にポイントを追加できます。起点となっているポイントをクリックするとエリアが作成されます" }, draw_line: { - tail: "クリックするとラインにポイントを追加できます。クリックすることで他のラインと接続することが可能です。ライン描画を終了するにはダブルクリックしてください" + tail: "クリックするとライン上にポイントを追加できます。クリックすることで他のラインと接続することが可能です。ライン描画を終了するにはダブルクリックしてください" } }, @@ -183,9 +183,9 @@ locale.en = { layerswitcher: { title: "背景画像", - description: "背景画像の設定", + description: "背景画像設定", percent_brightness: "{opacity}% 輝度", - fix_misalignment: "値の調整", + fix_misalignment: "背景画像を移動", reset: "設定リセット" }, From 8bd62b92372774bedf481340538a60d4b1e6a7f7 Mon Sep 17 00:00:00 2001 From: Alper Dincer Date: Tue, 12 Feb 2013 11:09:31 +0200 Subject: [PATCH 339/415] Finished translation to Turkish "Way" is translated to "Taraf", but there should be a better translation. I'm still looking for a better word and change them when I find it. --- locale/tr.js | 154 +++++++++++++++++++++++++-------------------------- 1 file changed, 77 insertions(+), 77 deletions(-) diff --git a/locale/tr.js b/locale/tr.js index 1315eb05a..904bccee6 100644 --- a/locale/tr.js +++ b/locale/tr.js @@ -57,145 +57,145 @@ locale.tr = { annotation: "Etiketler değiştirildi." }, circularize: { - title: "Circularize", - description: "Make this round.", + title: "Daireleştir", + description: "Yuvarlak hale getir", key: "O", annotation: { - line: "Made a line circular.", - area: "Made an area circular." + line: "Çizgiyi daireleştirin.", + area: "Alanı daireleştirin." } }, orthogonalize: { - title: "Orthogonalize", - description: "Square these corners.", + title: "Doğrultmak", + description: "Köşeleri doğrultun.", key: "Q", annotation: { - line: "Squared the corners of a line.", - area: "Squared the corners of an area." + line: "Çizginin köşeleri doğrultuldu.", + area: "Alanın köşeleri doğrultuldu." } }, 'delete': { - title: "Delete", - description: "Remove this from the map.", + title: "Sil", + description: "Haritan bunu sil.", key: "⌫", annotation: { - point: "Deleted a point.", - vertex: "Deleted a node from a way.", - line: "Deleted a line.", - area: "Deleted an area.", - relation: "Deleted a relation.", - multiple: "Deleted {n} objects." + point: "Bir nokta silindi.", + vertex: "Yoldan bir nod silindi.", + line: "Bir çizgi silindi.", + area: "Bir alan silindi.", + relation: "Bir ilişki silindi.", + multiple: "{n} adet obje silindi." } }, connect: { annotation: { - point: "Connected a way to a point.", - vertex: "Connected a way to another.", - line: "Connected a way to a line.", - area: "Connected a way to an area." + point: "Taraf bir noktaya bağlandı.", + vertex: "Bir taraf diğerine bağlandı.", + line: "Taraf bir çizgiye bağlandı.", + area: "Taraf bir alana bağlandı." } }, disconnect: { - title: "Disconnect", - description: "Disconnect these ways from each other.", + title: "Birbirinden Ayır", + description: "Her iki tarafı da ayır.", key: "D", - annotation: "Disconnected ways." + annotation: "Taraflar birbirinden ayrıldı." }, merge: { - title: "Merge", - description: "Merge these lines.", + title: "Birleştir", + description: "Bu çizgileri birleştir.", key: "C", - annotation: "Merged {n} lines." + annotation: "{n} adet çizgi birleştirildi." }, move: { - title: "Move", - description: "Move this to a different location.", + title: "Taşı", + description: "Bunu farklı bir konuma taşı.", key: "M", annotation: { - point: "Moved a point.", - vertex: "Moved a node in a way.", - line: "Moved a line.", - area: "Moved an area." + point: "Bir nokta taşındı.", + vertex: "Yoldan bir nokta taşındı.", + line: "Bir çizgi taşındı.", + area: "Bir alan taşındı." } }, reverse: { - title: "Reverse", - description: "Make this line go in the opposite direction.", + title: "Ters çevir", + description: "Bu çizgiyi ters yönde çevir.", key: "V", - annotation: "Reversed a line." + annotation: "Çizgi ters çevrildi." }, split: { - title: "Split", - description: "Split this into two ways at this point.", + title: "Ayır", + description: "Bu yolu bu noktadan ikiye ayır.", key: "X", - annotation: "Split a way." + annotation: "Yolu ayır." } }, validations: { - untagged_point: "Untagged point which is not part of a line or area", - untagged_line: "Untagged line", - untagged_area: "Untagged area", - tag_suggests_area: "The tag {tag} suggests line should be area, but it is not an area", - deprecated_tags: "Deprecated tags: {tags}" + untagged_point: "Herhangi bir çizgi ya da alana bağlantısı olmayan ve etiketlenmemiş bir nokta.", + untagged_line: "Etiketlenmemiş çizgi", + untagged_area: "Etiketlenmemiş alan", + tag_suggests_area: "{tag} etiketi buranın alan olmasını tavsiye ediyor ama alan değil.", + deprecated_tags: "Kullanımdan kaldırılmış etiket : {tags}" }, - save: "Save", - unsaved_changes: "You have unsaved changes", - save_help: "Save changes to OpenStreetMap, making them visible to other users", - no_changes: "You don't have any changes to save.", - save_error: "An error occurred while trying to save", - uploading_changes: "Uploading changes to OpenStreetMap.", - just_edited: "You Just Edited OpenStreetMap!", - okay: "Okay", + save: "Kaydet", + unsaved_changes: "Kaydedilmemiş değişiklikleriniz var", + save_help: "Diğer kullanıcıların yaptığınız değişiklikleri görmesi için OpenStreetMap'e kaydediniz", + no_changes: "Kaydedecek hiçbir değişikliğiniz yok", + save_error: "Kaydederken bir hata oluştu", + uploading_changes: "Değişiklikleriniz OpenStreetMap'e gönderiliyor.", + just_edited: "Şu an OpenStreetMap'de bir değişiklik yaptınız!", + okay: "Tamam", - "zoom-in": "Zoom In", - "zoom-out": "Zoom Out", + "zoom-in": "Yaklaş", + "zoom-out": "Uzaklaş", - nothing_to_undo: "Nothing to undo.", - nothing_to_redo: "Nothing to redo.", + nothing_to_undo: "Geri alınacak birşey yok.", + nothing_to_redo: "Tekrar yapılacak birşey yok.", - browser_notice: "This editor is supported in Firefox, Chrome, Safari, Opera, and Internet Explorer 9 and above. Please upgrade your browser or use Potlatch 2 to edit the map.", + browser_notice: "Bu editör sadece Firefox, Chrome, Safari, Opera ile Internet Explorer 9 ve üstü tarayıcılarda çalışmaktadır. Lütfen tarayınıcı güncelleyin ya da Potlatch 2'yi kullanarak haritada güncelleme yapınız.", inspector: { - no_documentation_combination: "This is no documentation available for this tag combination", - no_documentation_key: "This is no documentation available for this key", - new_tag: "New Tag" + no_documentation_combination: "Bu etiket kombinasyonu için dökümantasyon bulunmamaktadır.", + no_documentation_key: "Bu anahtar için dökümantasyon bulunmamaktadır.", + new_tag: "Yeni Etiket" }, - view_on_osm: "View on OSM", + view_on_osm: "OSM üstünde Gör", - zoom_in_edit: "zoom in to edit the map", + zoom_in_edit: "Güncelleme yapmak için haritada yakınlaşmalısınız", - edit_tags: "Edit tags", + edit_tags: "Etiketleri güncelle", geocoder: { - title: "Find A Place", - placeholder: "find a place", - no_results: "Couldn't locate a place named '{name}'" + title: "Bir Yer Bul", + placeholder: "bir yer bul", + no_results: "'{name}' ismindeki yer bulunamadı" }, - description: "Description", + description: "Açıklama", - logout: "logout", + logout: "Çıkış", - report_a_bug: "report a bug", + report_a_bug: "Hata rapor et", layerswitcher: { - title: "Background", - description: "Background Settings", - percent_brightness: "{opacity}% brightness", - fix_misalignment: "Fix misalignment", - reset: "reset" + title: "Arkaplan", + description: "Arkaplan Ayarları", + percent_brightness: "{opacity}% parlaklık", + fix_misalignment: "Yanlış hizalamayı düzelt", + reset: "Sıfırla" }, contributors: { - list: "Viewing contributions by {users}", - truncated_list: "Viewing contributions by {users} and {count} others" + list: "{users} tarafından yapılan katkılar görünmektedir", + truncated_list: "{users} ve diğer {count} tarafından yapılan katkılar görünmektedir" }, source_switch: { - live: "live", - dev: "dev" + live: "canlı", + dev: "geliştirme" } }; From 2062fe9a67846cf019fe4586bd756bd66ebbdfbc Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Tue, 12 Feb 2013 11:29:21 -0500 Subject: [PATCH 340/415] Don't end drag when parent removed --- js/id/behavior/drag.js | 11 ++++++++--- js/id/behavior/drag_node.js | 1 + 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/js/id/behavior/drag.js b/js/id/behavior/drag.js index 012e08d34..c1d877d1c 100644 --- a/js/id/behavior/drag.js +++ b/js/id/behavior/drag.js @@ -24,7 +24,7 @@ iD.behavior.drag = function() { origin = null, selector = '', filter = null, - event_, target; + event_, target, surface; event.of = function(thiz, argumentz) { return function(e1) { @@ -62,14 +62,13 @@ iD.behavior.drag = function() { if (touchId === null) d3_eventCancel(); function point() { - var p = target.parentNode; + var p = target.parentNode || surface; return touchId !== null ? d3.touches(p).filter(function(p) { return p.identifier === touchId; })[0] : d3.mouse(p); } function dragmove() { - if (!target.parentNode) return dragend(); var p = point(), dx = p[0] - origin_[0], @@ -170,5 +169,11 @@ iD.behavior.drag = function() { return drag; }; + drag.surface = function() { + if (!arguments.length) return surface; + surface = arguments[0]; + return drag; + }; + return d3.rebind(drag, event, "on"); }; diff --git a/js/id/behavior/drag_node.js b/js/id/behavior/drag_node.js index 1d1afcc2c..0cf732b4d 100644 --- a/js/id/behavior/drag_node.js +++ b/js/id/behavior/drag_node.js @@ -148,6 +148,7 @@ iD.behavior.DragNode = function(context) { var behavior = iD.behavior.drag() .delegate("g.node, g.point, g.midpoint") + .surface(context.surface().node()) .origin(origin) .on('start', start) .on('move', move) From 502f35869c7cf97ffd27337fc41485a156c537b1 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 10:40:41 -0800 Subject: [PATCH 341/415] Fall back to en strings (fixes #738) --- locale/locale.js | 11 ++++++++--- test/index.html | 1 + test/index_packaged.html | 1 + test/spec/lib/locale.js | 26 ++++++++++++++++++++++++++ 4 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 test/spec/lib/locale.js diff --git a/locale/locale.js b/locale/locale.js index c3dd5bcc4..8582df03e 100644 --- a/locale/locale.js +++ b/locale/locale.js @@ -6,9 +6,11 @@ locale.current = function(_) { return locale; }; -function t(s, o) { +function t(s, o, loc) { + loc = loc || locale._current; + var path = s.split(".").reverse(), - rep = locale[locale._current]; + rep = locale[loc]; while (rep !== undefined && path.length) rep = rep[path.pop()]; @@ -16,6 +18,9 @@ function t(s, o) { if (o) for (var k in o) rep = rep.replace('{' + k + '}', o[k]); return rep; } else { - if (console) console.error('key ' + s + ' not found'); + var missing = 'Missing translation: ' + s; + if (console) console.error(missing); + if (loc !== 'en') return t(s, o, 'en'); + return missing; } } diff --git a/test/index.html b/test/index.html index fe81a4ec5..3ba4e3bd8 100644 --- a/test/index.html +++ b/test/index.html @@ -144,6 +144,7 @@ + diff --git a/test/index_packaged.html b/test/index_packaged.html index cd3451357..874639094 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -22,6 +22,7 @@ + diff --git a/test/spec/lib/locale.js b/test/spec/lib/locale.js new file mode 100644 index 000000000..3228aa99b --- /dev/null +++ b/test/spec/lib/locale.js @@ -0,0 +1,26 @@ +describe("locale", function() { + var saved, error; + + beforeEach(function() { + saved = locale; + error = console.error; + console.error = function () {}; + locale = { _current: 'en', en: {test: 'test', foo: 'bar'}, __: {}} + }); + + afterEach(function() { + locale = saved; + console.error = error; + }); + + describe("t", function() { + it("defaults to locale._current", function() { + expect(t('test')).to.equal('test'); + }); + + it("falls back to en", function() { + locale._current = '__'; + expect(t('test')).to.equal('test'); + }); + }); +}); From cca9c30b32c40bec01ba8100ed6606dbf1ee8bbe Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 10:51:19 -0800 Subject: [PATCH 342/415] Fix midpoint hover (fixes #728) --- js/id/svg/midpoints.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/js/id/svg/midpoints.js b/js/id/svg/midpoints.js index 5403e4b45..174012cf3 100644 --- a/js/id/svg/midpoints.js +++ b/js/id/svg/midpoints.js @@ -51,7 +51,9 @@ iD.svg.Midpoints = function(projection) { groups.attr('transform', iD.svg.PointTransform(projection)); - groups.select('circle'); + // Propagate data bindings. + groups.select('circle.shadow'); + groups.select('circle.fill'); groups.exit() .remove(); From 6df64dbf37e3ebeb92f1e9a09e575157e5342d65 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 11:00:11 -0800 Subject: [PATCH 343/415] Switch to number keys 1-4 for modes (fixes #731) --- js/id/modes/add_area.js | 2 +- js/id/modes/add_line.js | 2 +- js/id/modes/add_point.js | 2 +- js/id/modes/browse.js | 2 +- locale/de.js | 12 ++++-------- locale/en.js | 12 ++++-------- locale/es.js | 12 ++++-------- locale/fr.js | 12 ++++-------- locale/ja.js | 12 ++++-------- locale/lv.js | 12 ++++-------- locale/tr.js | 12 ++++-------- 11 files changed, 32 insertions(+), 60 deletions(-) diff --git a/js/id/modes/add_area.js b/js/id/modes/add_area.js index 4cb98a3bc..1614a3997 100644 --- a/js/id/modes/add_area.js +++ b/js/id/modes/add_area.js @@ -4,7 +4,7 @@ iD.modes.AddArea = function(context) { button: 'area', title: t('modes.add_area.title'), description: t('modes.add_area.description'), - key: t('modes.add_area.key') + key: '4' }; var behavior = iD.behavior.AddWay(context) diff --git a/js/id/modes/add_line.js b/js/id/modes/add_line.js index 76e31e83b..ae1d03cef 100644 --- a/js/id/modes/add_line.js +++ b/js/id/modes/add_line.js @@ -4,7 +4,7 @@ iD.modes.AddLine = function(context) { button: 'line', title: t('modes.add_line.title'), description: t('modes.add_line.description'), - key: t('modes.add_line.key') + key: '3' }; var behavior = iD.behavior.AddWay(context) diff --git a/js/id/modes/add_point.js b/js/id/modes/add_point.js index 457ed87a4..47bdafae8 100644 --- a/js/id/modes/add_point.js +++ b/js/id/modes/add_point.js @@ -3,7 +3,7 @@ iD.modes.AddPoint = function(context) { id: 'add-point', title: t('modes.add_point.title'), description: t('modes.add_point.description'), - key: t('modes.add_point.key') + key: '2' }; var behavior = iD.behavior.Draw(context) diff --git a/js/id/modes/browse.js b/js/id/modes/browse.js index 5df94d69d..d26ea4a55 100644 --- a/js/id/modes/browse.js +++ b/js/id/modes/browse.js @@ -4,7 +4,7 @@ iD.modes.Browse = function(context) { id: 'browse', title: t('modes.browse.title'), description: t('modes.browse.description'), - key: t('modes.browse.key') + key: '1' }; var behaviors = [ diff --git a/locale/de.js b/locale/de.js index f74c1a3fc..543eaab2d 100644 --- a/locale/de.js +++ b/locale/de.js @@ -3,25 +3,21 @@ locale.de = { add_area: { title: "Fläche", description: "Füge Parks, Gebäude, Seen oder andere Flächen zur Karte hinzu.", - tail: "Klicke in die Karte, um das Zeichnen einer Fläche wie einen Park, einen See oder Gebäude zu starten.", - key: "A" + tail: "Klicke in die Karte, um das Zeichnen einer Fläche wie einen Park, einen See oder Gebäude zu starten." }, add_line: { title: "Linie", description: "Linien können Autobahnen, Straßen, Fußwege oder sogar Kanäle sein.", - tail: "Klicke in die Karte, um das Zeichnen einer Straße eines Pfades oder einer Route zu starten.", - key: "L" + tail: "Klicke in die Karte, um das Zeichnen einer Straße eines Pfades oder einer Route zu starten." }, add_point: { title: "Punkt", description: "Restaurants, Denkmäler und Briefkästen sind Punkte", - tail: "Klicke in die Karte, um einen Punkt hinzuzufügen.", - key: "P" + tail: "Klicke in die Karte, um einen Punkt hinzuzufügen." }, browse: { title: "Navigation", - description: "Verschieben und Vergrößern/Verkleinern des Kartenausschnitts.", - key: "B" + description: "Verschieben und Vergrößern/Verkleinern des Kartenausschnitts." }, draw_area: { tail: "Klicke, um Punkte zur Fläche hinzuzufügen. Klicke auf den ersten Punkt, um die Fläche abzuschließen." diff --git a/locale/en.js b/locale/en.js index efd3c1d90..007c8e5be 100644 --- a/locale/en.js +++ b/locale/en.js @@ -3,25 +3,21 @@ locale.en = { add_area: { title: "Area", description: "Add parks, buildings, lakes, or other areas to the map.", - tail: "Click on the map to start drawing an area, like a park, lake, or building.", - key: "A" + tail: "Click on the map to start drawing an area, like a park, lake, or building." }, add_line: { title: "Line", description: "Lines can be highways, streets, pedestrian paths, or even canals.", - tail: "Click on the map to start drawing an road, path, or route.", - key: "L" + tail: "Click on the map to start drawing an road, path, or route." }, add_point: { title: "Point", description: "Restaurants, monuments, and postal boxes are points.", - tail: "Click on the map to add a point.", - key: "P" + tail: "Click on the map to add a point." }, browse: { title: "Browse", - description: "Pan and zoom the map.", - key: "B" + description: "Pan and zoom the map." }, draw_area: { tail: "Click to add points to your area. Click the first point to finish the area." diff --git a/locale/es.js b/locale/es.js index 47fb7427d..4bb5f3959 100644 --- a/locale/es.js +++ b/locale/es.js @@ -3,25 +3,21 @@ locale.es = { add_area: { title: "Zona", //"Area", description: "Agregar parques, edificios, lagos u otras zonas en el mapa", //"Add parks, buildings, lakes, or other areas to the map.", - tail: "Hacer click en el mapa para empezar a dibujar una zona como un parque, lago o edificio", //"Click on the map to start drawing an area, like a park, lake, or building.", - key: "Z" //"A" + tail: "Hacer click en el mapa para empezar a dibujar una zona como un parque, lago o edificio" //"Click on the map to start drawing an area, like a park, lake, or building." }, add_line: { title: "Línea", //"Line", description: "Las líneas pueden ser autopistas, calles, pasos peatonales o canales.", //"Lines can be highways, streets, pedestrian paths, or even canals.", - tail: "Hace clic para dibujar en el mapa, una calle, camino o ruta.", //"Click on the map to start drawing an road, path, or route.", - key: "L" + tail: "Hace clic para dibujar en el mapa, una calle, camino o ruta." //"Click on the map to start drawing an road, path, or route.", }, add_point: { title: "Punto", //"Point", description: "Son puntos los restaurantes, monumentos y buzones", //"Restaurants, monuments, and postal boxes are points.", - tail: "Hacer clic para agregar un punto en el mapa", //"Click on the map to add a point.", - key: "P" + tail: "Hacer clic para agregar un punto en el mapa" //"Click on the map to add a point.", }, browse: { title: "Navegar", //"Browse", - description: "Aumentar y navegar el mapa", //"Pan and zoom the map.", - key: "N" //"B" + description: "Aumentar y navegar el mapa" //"Pan and zoom the map.", }, draw_area: { tail: "Hacer clic para agregar puntos en tu zona. Hacer hacer click en el primer punto para finalizar la zona." //"Click to add points to your area. Click the first point to finish the area." diff --git a/locale/fr.js b/locale/fr.js index eeb280beb..85a704271 100644 --- a/locale/fr.js +++ b/locale/fr.js @@ -3,25 +3,21 @@ locale.fr = { add_area: { title: "Polygone", description: "Les polygones peuvent être des parcs, des batîments, des lacs ou tout autre objet surfacique.", - tail: "Cliquez sur la carte pour ajouter un polygone tel qu'un parc, un lac ou un bâtiment.", - key: "A" + tail: "Cliquez sur la carte pour ajouter un polygone tel qu'un parc, un lac ou un bâtiment." }, add_line: { title: "Ligne", description: "Les lignes peuvent être des autoroutes, des routes, des chemins ou encore des caneaux.", - tail: "Cliquez sur la carte pour ajouter une nouvelle ligne telle qu'une route ou un nouveau chemin.", - key: "L" + tail: "Cliquez sur la carte pour ajouter une nouvelle ligne telle qu'une route ou un nouveau chemin." }, add_point: { title: "Point", description: "Les points peuvent être des restaurants, des monuments, ou encore des boites aux lettres.", - tail: "Cliquez sur la carte pour ajouter un point tel qu'un restaurant ou un monument.", - key: "P" + tail: "Cliquez sur la carte pour ajouter un point tel qu'un restaurant ou un monument." }, browse: { title: "Navigation", - description: "Naviguer ou zoomer sur la carte.", - key: "B" + description: "Naviguer ou zoomer sur la carte." }, draw_area: { tail: "Cliquez pour ajouter un point à la zone. Cliquez sur le dernier point pour fermer la zone." diff --git a/locale/ja.js b/locale/ja.js index 3f6455554..ade489c51 100644 --- a/locale/ja.js +++ b/locale/ja.js @@ -3,25 +3,21 @@ locale.en = { add_area: { title: "エリア", description: "公園や建物、湖沼、をマップに追加します", - tail: "マップをクリックすると、公園や湖沼、建物などのエリアの描画が開始されます。", - key: "A" + tail: "マップをクリックすると、公園や湖沼、建物などのエリアの描画が開始されます。" }, add_line: { title: "ライン", description: "ラインは車両用の道路や歩道、用水路を表すことができます", - tail: "マップをクリックすると、道路や歩道、流水経路の描画が始まります", - key: "L" + tail: "マップをクリックすると、道路や歩道、流水経路の描画が始まります" }, add_point: { title: "ポイント", description: "レストランや記念碑、郵便ボックスはポイントで表現します", - tail: "マップをクリックするとポイントを追加できます", - key: "P" + tail: "マップをクリックするとポイントを追加できます" }, browse: { title: "ブラウズ", - description: "マップを拡大縮小します", - key: "B" + description: "マップを拡大縮小します" }, draw_area: { tail: "クリックするとエリア上にポイントを追加できます。起点となっているポイントをクリックするとエリアが作成されます" diff --git a/locale/lv.js b/locale/lv.js index f341f7fed..1d55450f8 100644 --- a/locale/lv.js +++ b/locale/lv.js @@ -3,25 +3,21 @@ locale.lv = { add_area: { title: "Apgabals", description: "Pievieno parkus, ēkas, ezerus un citus apgabalus.", - tail: "Klikšķiniet uz kartes, lai sāktu zīmēt apgabalu, piemēram, parku, ezeru, vai ēku.", - key: "A" + tail: "Klikšķiniet uz kartes, lai sāktu zīmēt apgabalu, piemēram, parku, ezeru, vai ēku." }, add_line: { title: "Līnija", description: "Līnijas var būt ceļi, ielas, takas vai pat kanāli.", - tail: "Klikšķiniet uz kartes, lai sāktu zīmēt līniju, piemēram, ceļu vai taku.", - key: "L" + tail: "Klikšķiniet uz kartes, lai sāktu zīmēt līniju, piemēram, ceļu vai taku." }, add_point: { title: "Punkts", description: "Kafejnīcas, pieminekļi, un veikali var būt punkti.", - tail: "Klikšķiniet uz kartes, lai pievienotu interešu punktu.", - key: "P" + tail: "Klikšķiniet uz kartes, lai pievienotu interešu punktu." }, browse: { title: "Pārlūkot", - description: "Pārlūko karti.", - key: "B" + description: "Pārlūko karti." }, draw_area: { tail: "Klikšķiniet, lai pievinotu mezglus apgabalam. Lai beigtu zīmēt apgabalu, klikšķiniet uz sākuma mezgla." diff --git a/locale/tr.js b/locale/tr.js index 904bccee6..d14f08ecb 100644 --- a/locale/tr.js +++ b/locale/tr.js @@ -3,25 +3,21 @@ locale.tr = { add_area: { title: "Alan", description: "Park, bina, göl ve benzeri alanları haritaya ekle.", - tail: "Park, göl ya da bina gibi alanları çizmek için haritaya tıklayın.", - key: "A" + tail: "Park, göl ya da bina gibi alanları çizmek için haritaya tıklayın." }, add_line: { title: "Çizgi", description: "Yollar, sokaklar, patikalar ya da kanallar çizgi ile çizilebilir.", - tail: "Yol, patika yada rota çizmek için haritaya tıklayın.", - key: "L" + tail: "Yol, patika yada rota çizmek için haritaya tıklayın." }, add_point: { title: "Nokta", description: "Restoranlar, anıtlar ya da posta kutuları nokta ile gösterilebilir.", - tail: "Nokta eklemek için haritaya tıklayın.", - key: "P" + tail: "Nokta eklemek için haritaya tıklayın." }, browse: { title: "Tara", - description: "Harita üzerinde dolan ve yaklaş.", - key: "B" + description: "Harita üzerinde dolan ve yaklaş." }, draw_area: { tail: "Alanınıza nokta eklemek için tıklayınız. İlk noktaya tıklayarak alan çizimini bitirebilirsiniz." From 4d6778e1594eb83f3ff493cb58c551d04788ef75 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Tue, 12 Feb 2013 15:21:32 -0500 Subject: [PATCH 344/415] Update and fix ja translation --- index.html | 1 + locale/ja.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/index.html b/index.html index 8c084cf77..018d6d008 100644 --- a/index.html +++ b/index.html @@ -146,6 +146,7 @@ + diff --git a/locale/ja.js b/locale/ja.js index 3f6455554..39f80122e 100644 --- a/locale/ja.js +++ b/locale/ja.js @@ -1,4 +1,4 @@ -locale.en = { +locale.ja = { modes: { add_area: { title: "エリア", From 7554c7445dd997e3dfa3ef77980f0abfb73ec0c5 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Tue, 12 Feb 2013 15:34:26 -0500 Subject: [PATCH 345/415] Fix typo triggering too many redraws --- js/id/renderer/map.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index ef101dbc7..af828b49d 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -43,7 +43,7 @@ iD.Map = function(context) { } }, true) .on('mouseup.zoom', function() { - if (resetTransform) redraw(); + if (resetTransform()) redraw(); }) .attr('id', 'surface') .call(iD.svg.Surface()); From e92d991677b0b313478f7f10757e1081b8835b22 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 11:05:53 -0800 Subject: [PATCH 346/415] Remove dead code --- js/id/ui.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/js/id/ui.js b/js/id/ui.js index 7214628b4..9fec273cd 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -188,14 +188,11 @@ iD.ui = function(context) { }; history.on('change.editor', function() { - var undo = history.undoAnnotation(), redo = history.redoAnnotation(); function refreshTooltip(selection) { - if (selection.property('disabled')) { - selection.call(undo_tooltip.hide); - } else if (selection.property('tooltipVisible')) { + if (selection.property('tooltipVisible')) { selection.call(undo_tooltip.show); } } From 50f5a43efaa712941d49c95163a3ffec01854e6a Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 11:31:07 -0800 Subject: [PATCH 347/415] Avoid data-original-title hack --- js/id/ui.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/js/id/ui.js b/js/id/ui.js index 9fec273cd..60b241b7e 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -43,10 +43,12 @@ iD.ui = function(context) { .enter().append('button') .attr('tabindex', -1) .attr('class', function(mode) { return mode.title + ' add-button col3'; }) - .call(bootstrap.tooltip().placement('bottom').html(true)) - .attr('data-original-title', function(mode) { - return hintprefix(mode.key, mode.description); - }) + .call(bootstrap.tooltip() + .placement('bottom') + .html(true) + .title(function(mode) { + return hintprefix(mode.key, mode.description); + })) .on('click.editor', function(mode) { context.enter(mode); }); function disableTooHigh() { From 5761c31d298b6cf10669ce409d4f26196f44babf Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 11:45:32 -0800 Subject: [PATCH 348/415] Extract iD.ui.Modes --- index.html | 1 + js/id/ui.js | 73 ++++++----------------------------------------- js/id/ui/modes.js | 68 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 64 deletions(-) create mode 100644 js/id/ui/modes.js diff --git a/index.html b/index.html index 018d6d008..cdf6b38e4 100644 --- a/index.html +++ b/index.html @@ -67,6 +67,7 @@ + diff --git a/js/id/ui.js b/js/id/ui.js index 60b241b7e..564c07983 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -14,10 +14,6 @@ iD.ui = function(context) { if (iD.detect().opera) container.classed('opera', true); - function hintprefix(x, y) { - return '' + y + '' + '
' + x + '
'; - } - var m = container.append('div') .attr('id', 'map') .call(map); @@ -29,60 +25,9 @@ iD.ui = function(context) { var limiter = bar.append('div') .attr('class', 'limiter'); - var buttons_joined = limiter.append('div') - .attr('class', 'button-wrap joined col4'); - - var modes = [ - iD.modes.Browse(context), - iD.modes.AddPoint(context), - iD.modes.AddLine(context), - iD.modes.AddArea(context)]; - - var buttons = buttons_joined.selectAll('button.add-button') - .data(modes) - .enter().append('button') - .attr('tabindex', -1) - .attr('class', function(mode) { return mode.title + ' add-button col3'; }) - .call(bootstrap.tooltip() - .placement('bottom') - .html(true) - .title(function(mode) { - return hintprefix(mode.key, mode.description); - })) - .on('click.editor', function(mode) { context.enter(mode); }); - - function disableTooHigh() { - if (map.editable()) { - notice.message(false); - buttons.attr('disabled', null); - } else { - buttons.attr('disabled', 'disabled'); - notice.message(true); - context.enter(iD.modes.Browse(context)); - } - } - - var notice = iD.ui.notice(limiter) - .message(false) - .on('zoom', function() { map.zoom(16); }); - - map.on('move.editor', _.debounce(disableTooHigh, 500)); - - buttons.append('span') - .attr('class', function(d) { - return d.id + ' icon icon-pre-text'; - }); - - buttons.append('span').attr('class', 'label').text(function(mode) { return mode.title; }); - - context.on('enter.editor', function(entered) { - buttons.classed('active', function(mode) { return entered.button === mode.button; }); - container.classed("mode-" + entered.id, true); - }); - - context.on('exit.editor', function(exited) { - container.classed("mode-" + exited.id, false); - }); + limiter.append('div') + .attr('class', 'button-wrap joined col4') + .call(iD.ui.Modes(context), limiter); var undo_buttons = limiter.append('div') .attr('class', 'button-wrap joined col1'), @@ -201,12 +146,12 @@ iD.ui = function(context) { limiter.select('#undo') .classed('disabled', !undo) - .attr('data-original-title', hintprefix(iD.ui.cmd('⌘Z'), undo || t('nothing_to_undo'))) + .attr('data-original-title', iD.ui.tooltipHtml(undo || t('nothing_to_undo'), iD.ui.cmd('⌘Z'))) .call(refreshTooltip); limiter.select('#redo') .classed('disabled', !redo) - .attr('data-original-title', hintprefix(iD.ui.cmd('⌘⇧Z'), redo || t('nothing_to_redo'))) + .attr('data-original-title', iD.ui.tooltipHtml(redo || t('nothing_to_redo'), iD.ui.cmd('⌘⇧Z'))) .call(refreshTooltip); }); @@ -237,10 +182,6 @@ iD.ui = function(context) { .on('-', function() { map.zoomOut(); }) .on('dash', function() { map.zoomOut(); }); - modes.forEach(function(m) { - keybinding.on(m.key, function() { if (map.editable()) context.enter(m); }); - }); - d3.select(document) .call(keybinding); @@ -269,3 +210,7 @@ iD.ui = function(context) { }; }; + +iD.ui.tooltipHtml = function(text, key) { + return '' + text + '' + '
' + key + '
'; +}; diff --git a/js/id/ui/modes.js b/js/id/ui/modes.js new file mode 100644 index 000000000..a08b2b13b --- /dev/null +++ b/js/id/ui/modes.js @@ -0,0 +1,68 @@ +iD.ui.Modes = function(context) { + var modes = [ + iD.modes.Browse(context), + iD.modes.AddPoint(context), + iD.modes.AddLine(context), + iD.modes.AddArea(context)]; + + return function(selection, limiter) { + var buttons = selection.selectAll('button.add-button') + .data(modes); + + buttons.enter().append('button') + .attr('tabindex', -1) + .attr('class', function(mode) { return mode.title + ' add-button col3'; }) + .on('click.mode-buttons', function(mode) { context.enter(mode); }) + .call(bootstrap.tooltip() + .placement('bottom') + .html(true) + .title(function(mode) { + return iD.ui.tooltipHtml(mode.description, mode.key); + })); + + var notice = iD.ui.notice(limiter) + .message(false) + .on('zoom', function() { context.map().zoom(16); }); + + function disableTooHigh() { + if (context.map().editable()) { + notice.message(false); + buttons.attr('disabled', null); + } else { + buttons.attr('disabled', 'disabled'); + notice.message(true); + context.enter(iD.modes.Browse(context)); + } + } + + context.map() + .on('move.mode-buttons', _.debounce(disableTooHigh, 500)); + + buttons.append('span') + .attr('class', function(mode) { return mode.id + ' icon icon-pre-text'; }); + + buttons.append('span') + .attr('class', 'label') + .text(function(mode) { return mode.title; }); + + context.on('enter.editor', function(entered) { + buttons.classed('active', function(mode) { return entered.button === mode.button; }); + context.container() + .classed("mode-" + entered.id, true); + }); + + context.on('exit.editor', function(exited) { + context.container() + .classed("mode-" + exited.id, false); + }); + + var keybinding = d3.keybinding('mode-buttons'); + + modes.forEach(function(m) { + keybinding.on(m.key, function() { if (context.map().editable()) context.enter(m); }); + }); + + d3.select(document) + .call(keybinding); + } +}; From e73c19a4e8858b9be7d08b49fede70387b55baa9 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 11:55:38 -0800 Subject: [PATCH 349/415] Extract iD.ui.UndoRedo --- index.html | 1 + js/id/id.js | 2 +- js/id/ui.js | 43 +++------------------------------------ js/id/ui/undo_redo.js | 47 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 52 insertions(+), 41 deletions(-) create mode 100644 js/id/ui/undo_redo.js diff --git a/index.html b/index.html index cdf6b38e4..4ea9d9721 100644 --- a/index.html +++ b/index.html @@ -81,6 +81,7 @@ + diff --git a/js/id/id.js b/js/id/id.js index a7d7a44ac..f0747ad02 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -34,7 +34,7 @@ window.iD = function () { context.replace = history.replace; context.pop = history.pop; context.undo = history.undo; - context.redo = history.undo; + context.redo = history.redo; context.changes = history.changes; /* Graph */ diff --git a/js/id/ui.js b/js/id/ui.js index 564c07983..8515d3dfd 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -29,23 +29,9 @@ iD.ui = function(context) { .attr('class', 'button-wrap joined col4') .call(iD.ui.Modes(context), limiter); - var undo_buttons = limiter.append('div') - .attr('class', 'button-wrap joined col1'), - undo_tooltip = bootstrap.tooltip().placement('bottom').html(true); - - undo_buttons.append('button') - .attr({ id: 'undo', 'class': 'col6' }) - .classed('disabled', true) - .html("") - .on('click.editor', history.undo) - .call(undo_tooltip); - - undo_buttons.append('button') - .attr({ id: 'redo', 'class': 'col6' }) - .classed('disabled', true) - .html("") - .on('click.editor', history.redo) - .call(undo_tooltip); + limiter.append('div') + .attr('class', 'button-wrap joined col1') + .call(iD.ui.UndoRedo(context)); limiter.append('div').attr('class','button-wrap col1').append('button') .attr('class', 'save col12') @@ -134,27 +120,6 @@ iD.ui = function(context) { if (history.hasChanges()) return t('unsaved_changes'); }; - history.on('change.editor', function() { - var undo = history.undoAnnotation(), - redo = history.redoAnnotation(); - - function refreshTooltip(selection) { - if (selection.property('tooltipVisible')) { - selection.call(undo_tooltip.show); - } - } - - limiter.select('#undo') - .classed('disabled', !undo) - .attr('data-original-title', iD.ui.tooltipHtml(undo || t('nothing_to_undo'), iD.ui.cmd('⌘Z'))) - .call(refreshTooltip); - - limiter.select('#redo') - .classed('disabled', !redo) - .attr('data-original-title', iD.ui.tooltipHtml(redo || t('nothing_to_redo'), iD.ui.cmd('⌘⇧Z'))) - .call(refreshTooltip); - }); - d3.select(window).on('resize.editor', function() { map.size(m.size()); }); @@ -170,8 +135,6 @@ iD.ui = function(context) { var pa = 5; var keybinding = d3.keybinding('main') - .on(iD.ui.cmd('⌘Z'), function() { history.undo(); }) - .on(iD.ui.cmd('⌘⇧Z'), function() { history.redo(); }) .on('⌫', function() { d3.event.preventDefault(); }) .on('←', pan([pa, 0])) .on('↑', pan([0, pa])) diff --git a/js/id/ui/undo_redo.js b/js/id/ui/undo_redo.js new file mode 100644 index 000000000..f38066af4 --- /dev/null +++ b/js/id/ui/undo_redo.js @@ -0,0 +1,47 @@ +iD.ui.UndoRedo = function(context) { + return function(selection) { + var tooltip = bootstrap.tooltip() + .placement('bottom') + .html(true); + + var undoButton = selection.append('button') + .attr('class', 'col6 disabled') + .html('') + .on('click', context.undo) + .call(tooltip); + + var redoButton = selection.append('button') + .attr('class', 'col6 disabled') + .html('') + .on('click', context.redo) + .call(tooltip); + + var keybinding = d3.keybinding('undo') + .on(iD.ui.cmd('⌘Z'), context.undo) + .on(iD.ui.cmd('⌘⇧Z'), context.redo); + + d3.select(document) + .call(keybinding); + + context.history().on('change.editor', function() { + var undo = context.history().undoAnnotation(), + redo = context.history().redoAnnotation(); + + function refreshTooltip(selection) { + if (selection.property('tooltipVisible')) { + selection.call(tooltip.show); + } + } + + undoButton + .classed('disabled', !undo) + .attr('data-original-title', iD.ui.tooltipHtml(undo || t('nothing_to_undo'), iD.ui.cmd('⌘Z'))) + .call(refreshTooltip); + + redoButton + .classed('disabled', !redo) + .attr('data-original-title', iD.ui.tooltipHtml(redo || t('nothing_to_redo'), iD.ui.cmd('⌘⇧Z'))) + .call(refreshTooltip); + }); + } +}; From 50edf5870308c7d98a8795d8a5bf2719c7fa82f5 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 12:33:00 -0800 Subject: [PATCH 350/415] Clean up iD.ui.Save, give it a shortcut (fixes #739) --- js/id/ui.js | 6 +- js/id/ui/save.js | 169 +++++++++++++++++++++++++---------------------- 2 files changed, 93 insertions(+), 82 deletions(-) diff --git a/js/id/ui.js b/js/id/ui.js index 8515d3dfd..535c825eb 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -33,9 +33,9 @@ iD.ui = function(context) { .attr('class', 'button-wrap joined col1') .call(iD.ui.UndoRedo(context)); - limiter.append('div').attr('class','button-wrap col1').append('button') - .attr('class', 'save col12') - .call(iD.ui.save(context)); + limiter.append('div') + .attr('class', 'button-wrap col1') + .call(iD.ui.Save(context)); var zoom = container.append('div') .attr('class', 'zoombuttons map-control') diff --git a/js/id/ui/save.js b/js/id/ui/save.js index 38bba8518..e19097ec9 100644 --- a/js/id/ui/save.js +++ b/js/id/ui/save.js @@ -1,99 +1,110 @@ -iD.ui.save = function(context) { - return function (selection) { - var map = context.map(), - history = context.history(), - connection = context.connection(), - tooltip = bootstrap.tooltip() - .placement('bottom'); +iD.ui.Save = function(context) { + var map = context.map(), + history = context.history(), + connection = context.connection(), + key = iD.ui.cmd('⌘S'), + modal; - function success(e, changeset_id) { + function save() { + d3.event.preventDefault(); + + if (!history.hasChanges()) return; + + connection.authenticate(function (err) { var modal = iD.ui.modal(context.container()); + var changes = history.changes(); + changes.connection = connection; modal.select('.content') - .classed('success-modal', true) - .datum({ - id: changeset_id, - comment: e.comment - }) - .call(iD.ui.success(connection) - .on('cancel', function() { + .classed('commit-modal', true) + .datum(changes) + .call(iD.ui.commit(context) + .on('cancel', function () { modal.remove(); - })); - } + }) + .on('fix', clickFix) + .on('save', commit)); + }); + } - function clickFix(d) { - map.extent(d.entity.extent(context.graph())); - if (map.zoom() > 19) map.zoom(19); - context.enter(iD.modes.Select(context, [d.entity.id])); - modal.remove(); - } + function commit(e) { + context.container().select('.shaded') + .remove(); - function click() { + var loading = iD.ui.loading(context.container(), t('uploading_changes'), true); - function commit(e) { - context.container().select('.shaded').remove(); - var l = iD.ui.loading(context.container(), t('uploading_changes'), true); + connection.putChangeset( + history.changes(), + e.comment, + history.imagery_used(), + function(err, changeset_id) { + loading.remove(); + history.reset(); + map.flush().redraw(); + if (err) { + var desc = iD.ui.confirm() + .select('.description'); + desc.append('h2') + .text(t('save_error')); + desc.append('p').text(err.responseText); + } else { + success(e, changeset_id); + } + }); + } - connection.putChangeset(history.changes(), - e.comment, - history.imagery_used(), function(err, changeset_id) { - l.remove(); - history.reset(); - map.flush().redraw(); - if (err) { - var desc = iD.ui.confirm() - .select('.description'); - desc.append('h2') - .text(t('save_error')); - desc.append('p').text(err.responseText); - } else { - success(e, changeset_id); - } - }); - } + function success(e, changeset_id) { + modal = iD.ui.modal(context.container()); + modal.select('.content') + .classed('success-modal', true) + .datum({ + id: changeset_id, + comment: e.comment + }) + .call(iD.ui.success(connection) + .on('cancel', function() { + modal.remove(); + })); + } - if (history.hasChanges()) { - connection.authenticate(function(err) { - var modal = iD.ui.modal(context.container()); - var changes = history.changes(); - changes.connection = connection; - modal.select('.content') - .classed('commit-modal', true) - .datum(changes) - .call(iD.ui.commit(context) - .on('cancel', function() { - modal.remove(); - }) - .on('fix', clickFix) - .on('save', commit)); - }); - } else { - iD.ui.confirm().select('.description') - .append('h3').text(t('no_changes')); - } - } + function clickFix(d) { + map.extent(d.entity.extent(context.graph())); + if (map.zoom() > 19) map.zoom(19); + context.enter(iD.modes.Select(context, [d.entity.id])); + modal.remove(); + } - selection.html("" + t('save') + "") - .attr('title', t('save_help')) + return function (selection) { + var button = selection.append('button') + .attr('class', 'save col12 disabled') .attr('tabindex', -1) - .property('disabled', true) - .call(tooltip) - .on('click', click); + .on('click', save) + .call(bootstrap.tooltip() + .placement('bottom') + .html(true) + .title(iD.ui.tooltipHtml(t('save_help'), key))); - selection.append('span') + button.append('span') + .attr('class', 'label') + .text(t('save')); + + button.append('span') .attr('class', 'count'); - history.on('change.save-button', function() { + var keybinding = d3.keybinding('undo-redo') + .on(key, save); + + d3.select(document) + .call(keybinding); + + context.history().on('change.save', function() { var hasChanges = history.hasChanges(); - selection - .property('disabled', !hasChanges) - .classed('has-count', hasChanges) - .select('span.count') - .text(history.numChanges()); + button + .classed('disabled', !hasChanges) + .classed('has-count', hasChanges); - if (!hasChanges) { - selection.call(tooltip.hide); - } + button.select('span.count') + .text(history.numChanges()); }); }; }; From ceb8d5cf8168b173948c20a70f54d41ab35ed1b3 Mon Sep 17 00:00:00 2001 From: Van De Casteele Date: Tue, 12 Feb 2013 16:24:50 -0430 Subject: [PATCH 351/415] Update locale/fr.js Translate some updates. Do I have to translate also the words in brackets like {users} or {count} ? Also, does brightness relates to luminosity or opacity? --- locale/fr.js | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/locale/fr.js b/locale/fr.js index 85a704271..adf4a2d26 100644 --- a/locale/fr.js +++ b/locale/fr.js @@ -136,9 +136,8 @@ locale.fr = { deprecated_tags: "Tags obsolètes : {tags}" }, - save: "Sauvegarder", - // TODO - unsaved_changes: "You have unsaved changes", + save: "Sauvegarder", + unsaved_changes: "Vous avez des modifications non enregistrées", save_help: "Envoie des modifications au serveyr OpenStreetMap afin qu'elles soient visibles par les autres contributeurs.", no_changes: "Vous n'avez aucune modification à enregistrer.", save_error: "Une erreur est survenue lors de l'enregistrement des données", @@ -168,21 +167,19 @@ locale.fr = { geocoder: { title: "Trouver un emplacement", - placeholder: "Trouver un endroit", - // TODO - no_results: "Couldn't locate a place named '{name}'" + placeholder: "Trouver un endroit", + no_results: "Impossible de localiser l'endroit nommé '{name}'" }, - description: "Description", + description: "Déscription", logout: "Déconnexion", - // TODO - report_a_bug: "report a bug", + report_a_bug: "Signaler un bug", contributors: { - list: "Viewing contributions by {users}", - truncated_list: "Viewing contributions by {users} and {count} others" + list: "Consulter les contributions de {users}", + truncated_list: "Consulter les contributions de {users} et {count} les autres" }, layerswitcher: { From 293784fbe9e9b8a2cf29fb548827c308a1a62c27 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Tue, 12 Feb 2013 15:57:30 -0500 Subject: [PATCH 352/415] Note about translation and tokens --- locale/README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/locale/README.md b/locale/README.md index 1f97112ed..b1610b311 100644 --- a/locale/README.md +++ b/locale/README.md @@ -14,5 +14,23 @@ If you aren't, you can still contribute! You'll still need a GitHub account, but you can just browse to your language's file here, click 'Edit', and edit each translated string. +## Translating Strings + +Let's look at an example line from `en.js`: + +```javascript +no_results: "Couldn't locate a place named '{name}'" +``` + +The word in brackets, `{name}`, should **not** be translated into a new +language: it's replaced with a place name when iD presents the text. So +a French translation would look like + +```javascript +no_results: "Impossible de localiser l'endroit nommé '{name}'" +``` + +## License + Contributions to translations are under the same liberal license as iD itself, [wtfpl](http://www.wtfpl.net/). From d9d05f3e3a4abf45fc93f6c60c67a9bc2ae57ccd Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Tue, 12 Feb 2013 16:20:39 -0500 Subject: [PATCH 353/415] Test translations --- locale/lint.js | 237 --------------------------------------- test/index.html | 8 +- test/spec/translation.js | 35 ++++++ 3 files changed, 42 insertions(+), 238 deletions(-) delete mode 100644 locale/lint.js create mode 100644 test/spec/translation.js diff --git a/locale/lint.js b/locale/lint.js deleted file mode 100644 index 44c5139e7..000000000 --- a/locale/lint.js +++ /dev/null @@ -1,237 +0,0 @@ -/** - * Fragment used to represent a string fragment in the diff. - */ -var Fragment = function (string) { - this.content = string; - this.equiv = false; -}; - -/** - * Wrap in given tag or return the clean value. - */ -Fragment.prototype.toString = function (tag) { - if (this.equiv || !tag) { - return this.content; - } - else { - return '<' + tag + '>' + this.content + ''; - } -}; - -var moveToEnd = function (a, i, k) { - if (!a.equiv && (!k[i-1] || k[i-1].equiv)) { - // Find next item equiv item. - for (var j = i+1; k[j] && !k[j].equiv; j++); - if (k[j] && k[j].content === a.content) { - k[i] = k[j]; - k[j] = a; - } - } -}; - -var aggregate = function (a, i, k) { - if (!a.equiv && k[i+1] && !k[i+1].equiv) { - k[i+1].content = a.content + k[i+1].content; - delete k[i]; - } -}; - -var join = function (what, t) { - return what.map(function (a) { - if (a) return a.toString(t); - }).join(''); -}; - -var clone = function(source) { - if (typeof source === 'object' && source !== null) { - var target = Array.isArray(source) ? [] : {}; - for (var key in source) target[key] = clone(source[key]); - return target; - } - return source; -}; - -var WordDiff = { - nonWord: /(&.+?;|[\u0000-\u0040\u005B-\u0060\u007B-\u00A9\u00AB-\u00B4\u00B6-\u00B9\u00BB-\u00BF\u00D7\u00F7\u02C2-\u02C5\u02D2-\u02DF\u02E5-\u02EB\u02ED\u02EF-\u036F\u0375\u037E\u0384\u0385\u0387\u03F6\u0482-\u0489\u055A-\u055F\u0589\u058A\u0591-\u05C7\u05F3\u05F4\u0600-\u0603\u0606-\u061B\u061E\u061F\u064B-\u065E\u0660-\u066D\u0670\u06D4\u06D6-\u06E4\u06EA-\u06ED\u06F0-\u06F9\u06FD\u06FE\u0700-\u070D\u070F\u0711\u0730-\u074A\u07A6-\u07B0\u07C0-\u07C9\u07EB-\u07F3\u07F6-\u07F9\u0901-\u0903\u093C\u093E-\u094D\u0951-\u0954\u09E2\u0962-\u0970\u06E7-\u06E9\u0981-\u0983\u09BC\u09BE-\u09C4\u09C7\u09C8\u09CB-\u09CD\u09D7\u09E3\u09E6-\u09EF\u09F2-\u09FA\u0A01-\u0A03\u0A3C\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A66-\u0A71\u0A75\u0A81-\u0A83\u0ABC\u0ABE-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0AE2\u0AE3\u0AE6-\u0AEF\u0AF1\u0B01-\u0B03\u0B3C\u0B3E-\u0B44\u0B47\u0B48\u0B4B-\u0B4D\u0B56\u0B57\u0B62\u0B63\u0B66-\u0B70\u0B82\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD7\u0BE6-\u0BFA\u0C01-\u0C03\u0C3E-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C62\u0C63\u0C66-\u0C6F\u0C78-\u0C7F\u0C82\u0C83\u0CBC\u0CBE-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5\u0CD6\u0CE2\u0CE3\u0CE6-\u0CEF\u0CF1\u0CF2\u0D02\u0D03\u0D3E-\u0D44\u0D46-\u0D48\u0D4A-\u0D4D\u0D57\u0D62\u0D63\u0D66-\u0D75\u0D79\u0D82\u0D83\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DF2-\u0DF4\u0E31\u0E34-\u0E3A\u0E3F\u0E47-\u0E5B\u0EB1\u0EB4-\u0EB9\u0EBB\u0EBC\u0EC8-\u0ECD\u0ED0-\u0ED9\u0F01-\u0F3F\u0F71-\u0F87\u0F90-\u0F97\u0F99-\u0FBC\u0FBE-\u0FCC\u0FCE-\u0FD4\u102B-\u103E\u1040-\u104F\u1056-\u1059\u105E-\u1060\u1062-\u1064\u1067-\u106D\u1071-\u1074\u1082-\u108D\u108F-\u1099\u109E\u109F\u10FB\u135F-\u137C\u1390-\u1399\u166D\u166E\u1680\u169B\u169C\u16EB-\u16F0\u1712-\u1714\u1732-\u1736\u1752\u1753\u1772\u1773\u17B4-\u17D6\u17D8-\u17DB\u17DD\u17E0-\u17E9\u17F0-\u17F9\u1800-\u180E\u1810-\u1819\u18A9\u1920-\u192B\u1930-\u193B\u1940\u1944-\u194F\u19B0-\u19C0\u19C8\u19C9\u19D0-\u19D9\u19DE-\u19FF\u1A17-\u1A1B\u1A1E\u1A1F\u1B00-\u1B04\u1B34-\u1B44\u1B50-\u1B7C\u1B80-\u1B82\u1BA1-\u1BAA\u1BB0-\u1BB9\u1C24-\u1C37\u1C3B-\u1C49\u1C50-\u1C59\u1C7E\u1C7F\u1DC0-\u1DE6\u1DFE\u1DFF\u1FBD\u1FBF-\u1FC1\u1FCD-\u1FCF\u1FDD-\u1FDF\u1FED-\u1FEF\u1FFD\u1FFE\u2000-\u2064\u206A-\u2070\u2074-\u207E\u2080-\u208E\u20A0-\u20B5\u20D0-\u20F0\u2100\u2101\u2103-\u2106\u2108\u2109\u2114\u2116-\u2118\u211E-\u2123\u2125\u2127\u2129\u212E\u213A\u213B\u2140-\u2144\u214A-\u214D\u214F\u2153-\u2182\u2185-\u2188\u2190-\u23E7\u2400-\u2426\u2440-\u244A\u2460-\u269D\u26A0-\u26BC\u26C0-\u26C3\u2701-\u2704\u2706-\u2709\u270C-\u2727\u2729-\u274B\u274D\u274F-\u2752\u2756\u2758-\u275E\u2761-\u2794\u2798-\u27AF\u27B1-\u27BE\u27C0-\u27CA\u27CC\u27D0-\u2B4C\u2B50-\u2B54\u2CE5-\u2CEA\u2CF9-\u2CFF\u2DE0-\u2E2E\u2E30\u2E80-\u2E99\u2E9B-\u2EF3\u2F00-\u2FD5\u2FF0-\u2FFB\u3000-\u3004\u3007-\u3030\u3036-\u303A\u303D-\u303F\u3099-\u309C\u30A0\u30FB\u3190-\u319F\u31C0-\u31E3\u3200-\u321E\u3220-\u3243\u3250-\u32FE\u3300-\u33FF\u4DC0-\u4DFF\uA490-\uA4C6\uA60D-\uA60F\uA620-\uA629\uA66F-\uA673\uA67C-\uA67E\uA700-\uA716\uA720\uA721\uA789\uA78A\uA802\uA806\uA80B\uA823-\uA82B\uA874-\uA877\uA880\uA881\uA8B4-\uA8C4\uA8CE-\uA8D9\uA900-\uA909\uA926-\uA92F\uA947-\uA953\uA95F\uAA29-\uAA36\uAA43\uAA4C\uAA4D\uAA50-\uAA59\uAA5C-\uAA5F\uD800\uDB7F\uDB80\uDBFF\uDC00\uDFFF\uE000\uF8FF\uFB1E\uFB29\uFD3E\uFD3F\uFDFC\uFDFD\uFE00-\uFE19\uFE20-\uFE26\uFE30-\uFE52\uFE54-\uFE66\uFE68-\uFE6B\uFEFF\uFF01-\uFF20\uFF3B-\uFF40\uFF5B-\uFF65\uFFE0-\uFFE6\uFFE8-\uFFEE\uFFF9-\uFFFD])/, - - tokenize: function (args) { - // Split on non-word characters. - for (var type in args) { - args[type] = args[type].split(WordDiff.nonWord).filter(function (s) { - return s.length; - }); - } - - // Calculate the indexes and offsets for common suffixes and prefixes. - var i = -1, j = args.del.length, k = args.ins.length; - while (args.del[++i] === args.ins[i] && i <= j); - while (j >= i && k >= i && args.del[--j] === args.ins[--k]); - - args.prefix = args.del.slice(0, i).join(''); - args.suffix = args.del.slice(j + 1).join(''); - args.del = args.del.slice(i, ++j); - args.ins = args.ins.slice(i, ++k); - }, - - lcs: function (args) { - var matrix = []; - - for (var i = 0; i < args.del.length; i++) { - matrix[i] = []; - for (var j = 0; j < args.ins.length; j++) { - if (args.del[i] === args.ins[j]) { - matrix[i][j] = (matrix[i - 1] && matrix[i - 1][j - 1] || 0) + args.del[i].length; - } - else { - matrix[i][j] = Math.max(matrix[i][j - 1] || 0, matrix[i - 1] && matrix[i - 1][j] || 0); - } - } - } - - return matrix; - }, - - changeset: function (args, matrix) { - var result = {}; - - ['del', 'ins'].forEach(function (type) { - result[type] = args[type].map(function (a) { return new Fragment(a); }); - }); - - // Backtrack through the matrix. - for (var i = result.del.length - 1, j = result.ins.length - 1; i >= 0; i--, j--) { - if (j < 0 || result.del[i].content !== result.ins[j].content) { - if (j < 0 || (j > 0 && matrix[i - 1] && (matrix[i][j - 1] < matrix[i - 1][j]))) { - j++; - } - else { - i++; - } - } - else { - result.del[i] = result.ins[j]; - result.del[i].equiv = true; - } - } - - // Fill up gaps. - for (var i = 0; i < result.del.length; i++) { - if (result.del[i].equiv && result.del[i].content.length < 3) { - var j = result.ins.indexOf(result.del[i]); - if (result.del[i-1] && result.del[i+1] && result.ins[j-1] && result.ins[j+1] && !result.del[i-1].equiv && !result.del[i+1].equiv && !result.ins[j-1].equiv && !result.ins[j+1].equiv){ - result.del[i].equiv = false; - result.ins[j] = clone(result.del[i]); - } - } - } - - ['del', 'ins'].forEach(function (type) { - // Try to move changes to the end. - for (var i = 0; i < result[type].length; i++) - moveToEnd(result[type][i], i, result[type]); - - // Aggregate subsequent changes to minimize ins/del tags. - for (var i = 0; i < result[type].length; i++) - aggregate(result[type][i], i, result[type]); - }); - - return result; - }, - - htmlRender: function (args, result) { - var diff = { - del: args.prefix + join(result.del, 'del') + args.suffix, - ins: args.prefix + join(result.ins, 'ins') + args.suffix - }; - - return diff; - }, - - htmlDiff: function (del, ins) { - var args = { 'del': del, 'ins': ins }; - - WordDiff.tokenize(args); - var matrix = WordDiff.lcs(args); - var result = WordDiff.changeset(args, matrix); - return WordDiff.htmlRender(args, result); - }, - - render: function (args, result) { - var join = function (what, type) { - return what.map(function (a) { - if (!a) return; - if (a.equiv) return a.content; - if (type == 'del') return '\033[31;4m' + a.content + '\033[0m'; - if (type == 'ins') return '\033[32;4m' + a.content + '\033[0m'; - }).join(''); - }; - - return { - del: args.prefix + join(result.del, 'del') + args.suffix, - ins: args.prefix + join(result.ins, 'ins') + args.suffix - }; - }, - - diff: function(del, ins) { - var args = { 'del': del, 'ins': ins }; - - WordDiff.tokenize(args); - var matrix = WordDiff.lcs(args); - var result = WordDiff.changeset(args, matrix); - return WordDiff.render(args, result); - } -}; - - - - -var fs = require('fs'), _ = require('lodash'); - -function getKeys(lang, keys, prefix) { - keys = keys || []; - prefix = prefix || ''; - for (var i in lang) { - keys.push(prefix + i); - if (typeof lang[i] === 'object') { - getKeys(lang[i], keys, i + '.'); - } - } - return keys; -} - -var languages = ['de', 'en', 'es', 'fr', 'lv', 'tr']; -var langkeys = {}; - -eval(fs.readFileSync('./locale.js', 'utf8')); -for (var i = 0; i < languages.length; i++) { - eval(fs.readFileSync('./' + languages[i] + '.js', 'utf8')); - langkeys[languages[i]] = getKeys(locale[languages[i]]).sort(); -} - -// for (var i = 1; i < languages.length - 1; i++) { -// -// var changes = WordDiff.diff( -// langkeys[languages[i]].join(','), -// langkeys[languages[i + 1]].join(',')); -// -// -// console.warn('actual:' + '\n' + changes.del); -// console.warn('expected:' + '\n' + changes.ins); -// } - -var allkeys = []; -_.forEach(langkeys, function(l) { - allkeys = _.union(allkeys, l); -}); - -// console.warn('\n\n------------------------------------------'); -// console.warn('all keys ---------------------------------\n\n'); - -// console.log(allkeys.join(',')); - -_.forEach(langkeys, function(l, k) { - var missing = _.difference(allkeys, l); - if (missing.length) { - console.log('\n', k, 'is missing\n\n', _.difference(allkeys, l).join(',')); - } -}); diff --git a/test/index.html b/test/index.html index 3ba4e3bd8..d9640c687 100644 --- a/test/index.html +++ b/test/index.html @@ -138,7 +138,12 @@ - + + + + + + @@ -197,6 +202,7 @@ + diff --git a/test/spec/translation.js b/test/spec/translation.js new file mode 100644 index 000000000..963f59fe0 --- /dev/null +++ b/test/spec/translation.js @@ -0,0 +1,35 @@ +describe('translations', function() { + var languages = [], languageKeys = {}; + + function getKeys(lang, keys, prefix) { + keys = keys || []; + prefix = prefix || ''; + for (var i in lang) { + keys.push(prefix + i); + if (typeof lang[i] === 'object') { + getKeys(lang[i], keys, i + '.'); + } + } + return keys; + } + + describe('#translation-differences', function() { + + it('does not differ between languages', function() { + languages = _(locale).keys() + .without('current', '_current').value(); + + languageKeys = _.reduce(languages, function(mem, lang) { + mem[lang] = getKeys(locale[lang]); + return mem; + }, {}); + + var allkeys = _.flatten(_.values(languageKeys)); + + _.forEach(languageKeys, function(l, k) { + expect(_.difference(allkeys, l)).to.eql([]); + }); + }); + + }); +}); From 072f85e2b17cf9454ffcd9251e1a215d3fc3a43d Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 13:24:09 -0800 Subject: [PATCH 354/415] Redraw nodes that move off the screen (fixes #735) --- js/id/core/difference.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/js/id/core/difference.js b/js/id/core/difference.js index e2159271a..15ae88df3 100644 --- a/js/id/core/difference.js +++ b/js/id/core/difference.js @@ -89,7 +89,9 @@ iD.Difference = function (base, head) { b = change.base, entity = h || b; - if (extent && !entity.intersects(extent, h ? head : base)) + if (extent && + (!h || !h.intersects(extent, head)) && + (!b || !b.intersects(extent, base))) continue; result[id] = h; From 61d7ec45b5b64238f3a5e6a1334a3b1a7ff02000 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Tue, 12 Feb 2013 16:35:47 -0500 Subject: [PATCH 355/415] Banish function ( style --- js/id/actions/connect.js | 4 ++-- js/id/actions/delete_multiple.js | 2 +- js/id/actions/delete_way.js | 2 +- js/id/actions/join.js | 2 +- js/id/actions/merge.js | 4 ++-- js/id/actions/move_way.js | 4 ++-- js/id/actions/reverse.js | 4 ++-- js/id/actions/split.js | 2 +- js/id/behavior/drag_node.js | 2 +- js/id/behavior/draw_way.js | 2 +- js/id/behavior/hover.js | 4 ++-- js/id/connection.js | 6 +++--- js/id/core/difference.js | 6 +++--- js/id/core/graph.js | 4 ++-- js/id/core/relation.js | 14 +++++++------- js/id/core/way.js | 2 +- js/id/geo.js | 4 ++-- js/id/geo/extent.js | 6 +++--- js/id/renderer/map.js | 4 ++-- js/id/svg.js | 6 +++--- js/id/svg/areas.js | 6 +++--- js/id/svg/lines.js | 2 +- js/id/svg/member_classes.js | 2 +- js/id/svg/midpoints.js | 2 +- js/id/ui/geocoder.js | 2 +- js/id/ui/radial_menu.js | 8 ++++---- js/id/ui/save.js | 6 +++--- test/index_packaged.html | 1 + test/spec/actions/delete_way.js | 16 ++++++++-------- test/spec/actions/move_way.js | 6 +++--- 30 files changed, 68 insertions(+), 67 deletions(-) diff --git a/js/id/actions/connect.js b/js/id/actions/connect.js index 73c1af09a..3c69b8a0f 100644 --- a/js/id/actions/connect.js +++ b/js/id/actions/connect.js @@ -19,11 +19,11 @@ iD.actions.Connect = function(nodeIds) { for (var i = 0; i < nodeIds.length - 1; i++) { var node = graph.entity(nodeIds[i]), index; - graph.parentWays(node).forEach(function (parent) { + graph.parentWays(node).forEach(function(parent) { graph = graph.replace(parent.replaceNode(node.id, survivor.id)); }); - graph.parentRelations(node).forEach(function (parent) { + graph.parentRelations(node).forEach(function(parent) { graph = graph.replace(parent.replaceMember(node, survivor)); }); diff --git a/js/id/actions/delete_multiple.js b/js/id/actions/delete_multiple.js index a33dba4de..cd7c022a1 100644 --- a/js/id/actions/delete_multiple.js +++ b/js/id/actions/delete_multiple.js @@ -6,7 +6,7 @@ iD.actions.DeleteMultiple = function(ids) { relation: iD.actions.DeleteRelation }; - ids.forEach(function (id) { + ids.forEach(function(id) { var entity = graph.entity(id); if (entity) { // It may have been deleted aready. graph = actions[entity.type](id)(graph); diff --git a/js/id/actions/delete_way.js b/js/id/actions/delete_way.js index 211447228..95888470e 100644 --- a/js/id/actions/delete_way.js +++ b/js/id/actions/delete_way.js @@ -8,7 +8,7 @@ iD.actions.DeleteWay = function(wayId) { graph = graph.replace(parent.removeMember(wayId)); }); - way.nodes.forEach(function (nodeId) { + way.nodes.forEach(function(nodeId) { var node = graph.entity(nodeId); // Circular ways include nodes more than once, so they diff --git a/js/id/actions/join.js b/js/id/actions/join.js index feccfebaf..d3b353dc5 100644 --- a/js/id/actions/join.js +++ b/js/id/actions/join.js @@ -47,7 +47,7 @@ iD.actions.Join = function(ids) { nodes = a.nodes.concat(b.nodes.slice().slice(1)); } - graph.parentRelations(b).forEach(function (parent) { + graph.parentRelations(b).forEach(function(parent) { graph = graph.replace(parent.replaceMember(b, a)); }); diff --git a/js/id/actions/merge.js b/js/id/actions/merge.js index 5b515bcc5..4c4fb2abc 100644 --- a/js/id/actions/merge.js +++ b/js/id/actions/merge.js @@ -9,10 +9,10 @@ iD.actions.Merge = function(ids) { area = geometries.area[0], points = geometries.point; - points.forEach(function (point) { + points.forEach(function(point) { area = area.mergeTags(point.tags); - graph.parentRelations(point).forEach(function (parent) { + graph.parentRelations(point).forEach(function(parent) { graph = graph.replace(parent.replaceMember(point, area)); }); diff --git a/js/id/actions/move_way.js b/js/id/actions/move_way.js index 8301539c4..376e31d6e 100644 --- a/js/id/actions/move_way.js +++ b/js/id/actions/move_way.js @@ -1,9 +1,9 @@ iD.actions.MoveWay = function(wayId, delta, projection) { return function(graph) { - return graph.update(function (graph) { + return graph.update(function(graph) { var way = graph.entity(wayId); - _.uniq(way.nodes).forEach(function (id) { + _.uniq(way.nodes).forEach(function(id) { var node = graph.entity(id), start = projection(node.loc), end = projection.invert([start[0] + delta[0], start[1] + delta[1]]); diff --git a/js/id/actions/reverse.js b/js/id/actions/reverse.js index 5c8315467..f9696ceb1 100644 --- a/js/id/actions/reverse.js +++ b/js/id/actions/reverse.js @@ -62,8 +62,8 @@ iD.actions.Reverse = function(wayId) { tags[reverseKey(key)] = reverseValue(key, way.tags[key]); } - graph.parentRelations(way).forEach(function (relation) { - relation.members.forEach(function (member, index) { + graph.parentRelations(way).forEach(function(relation) { + relation.members.forEach(function(member, index) { if (member.id === way.id && (role = {forward: 'backward', backward: 'forward'}[member.role])) { relation = relation.updateMember({role: role}, index); graph = graph.replace(relation); diff --git a/js/id/actions/split.js b/js/id/actions/split.js index 92f9296f7..cf5d56f84 100644 --- a/js/id/actions/split.js +++ b/js/id/actions/split.js @@ -14,7 +14,7 @@ iD.actions.Split = function(nodeId, newWayId) { var node = graph.entity(nodeId), parents = graph.parentWays(node); - return parents.filter(function (parent) { + return parents.filter(function(parent) { return parent.isClosed() || (parent.first() !== nodeId && parent.last() !== nodeId); diff --git a/js/id/behavior/drag_node.js b/js/id/behavior/drag_node.js index 0cf732b4d..eff4c1174 100644 --- a/js/id/behavior/drag_node.js +++ b/js/id/behavior/drag_node.js @@ -61,7 +61,7 @@ iD.behavior.DragNode = function(context) { context.surface() .classed('behavior-drag-node', true) .selectAll('.node, .way') - .filter(function (d) { return activeIDs.indexOf(d.id) >= 0; }) + .filter(function(d) { return activeIDs.indexOf(d.id) >= 0; }) .classed('active', true); } diff --git a/js/id/behavior/draw_way.js b/js/id/behavior/draw_way.js index 0a05c7e9d..a9a34e37c 100644 --- a/js/id/behavior/draw_way.js +++ b/js/id/behavior/draw_way.js @@ -30,7 +30,7 @@ iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) { if (datum.id === end.id || datum.id === segment.id) { context.surface().selectAll('.way, .node') - .filter(function (d) { + .filter(function(d) { return d.id === end.id || d.id === segment.id; }) .classed('active', true); diff --git a/js/id/behavior/hover.js b/js/id/behavior/hover.js index b724f13b1..0f5786676 100644 --- a/js/id/behavior/hover.js +++ b/js/id/behavior/hover.js @@ -15,14 +15,14 @@ iD.behavior.Hover = function() { var datum = d3.event.target.__data__; if (datum) { selection.selectAll('*') - .filter(function (d) { return d === datum; }) + .filter(function(d) { return d === datum; }) .classed('hover', true); } } selection.on('mouseover.hover', mouseover); - selection.on('mouseout.hover', function () { + selection.on('mouseout.hover', function() { selection.selectAll('.hover') .classed('hover', false); }); diff --git a/js/id/connection.js b/js/id/connection.js index df9c318ca..ea90c5591 100644 --- a/js/id/connection.js +++ b/js/id/connection.js @@ -200,19 +200,19 @@ iD.Connection = function(context) { comment: comment, created_by: 'iD ' + iD.version })) - }, function (err, changeset_id) { + }, function(err, changeset_id) { if (err) return callback(err); oauth.xhr({ method: 'POST', path: '/api/0.6/changeset/' + changeset_id + '/upload', options: { header: { 'Content-Type': 'text/xml' } }, content: JXON.stringify(connection.osmChangeJXON(user.id, changeset_id, changes)) - }, function (err) { + }, function(err) { if (err) return callback(err); oauth.xhr({ method: 'PUT', path: '/api/0.6/changeset/' + changeset_id + '/close' - }, function (err) { + }, function(err) { callback(err, changeset_id); }); }); diff --git a/js/id/core/difference.js b/js/id/core/difference.js index e2159271a..0138844cb 100644 --- a/js/id/core/difference.js +++ b/js/id/core/difference.js @@ -6,7 +6,7 @@ of entities that will require a redraw, taking into account child and parent relationships. */ -iD.Difference = function (base, head) { +iD.Difference = function(base, head) { var changes = {}, length = 0; _.each(head.entities, function(h, id) { @@ -27,7 +27,7 @@ iD.Difference = function (base, head) { var difference = {}; - difference.length = function () { + difference.length = function() { return length; }; @@ -105,7 +105,7 @@ iD.Difference = function (base, head) { } diff = _.difference(nb, nh); - for (var i = 0; i < diff.length; i++) { + for (i = 0; i < diff.length; i++) { result[diff[i]] = head.entity(diff[i]); } } diff --git a/js/id/core/graph.js b/js/id/core/graph.js index eae11cc15..96dca44e7 100644 --- a/js/id/core/graph.js +++ b/js/id/core/graph.js @@ -192,14 +192,14 @@ iD.Graph.prototype = { if (this.entities[entity.id] === entity) return this; - return this.update(function () { + return this.update(function() { this._updateCalculated(this.entities[entity.id], entity); this.entities[entity.id] = entity; }); }, remove: function(entity) { - return this.update(function () { + return this.update(function() { this._updateCalculated(entity, undefined); this.entities[entity.id] = undefined; }); diff --git a/js/id/core/relation.js b/js/id/core/relation.js index 950f56230..778568917 100644 --- a/js/id/core/relation.js +++ b/js/id/core/relation.js @@ -14,7 +14,7 @@ _.extend(iD.Relation.prototype, { extent: function(resolver) { return resolver.transient(this, 'extent', function() { - return this.members.reduce(function (extent, member) { + return this.members.reduce(function(extent, member) { member = resolver.entity(member.id); if (member) { return extent.extend(member.extent(resolver)); @@ -156,8 +156,8 @@ _.extend(iD.Relation.prototype, { // multipolygon: function(resolver) { var members = this.members - .filter(function (m) { return m.type === 'way' && resolver.entity(m.id); }) - .map(function (m) { return { role: m.role || 'outer', id: m.id, nodes: resolver.childNodes(resolver.entity(m.id)) }; }); + .filter(function(m) { return m.type === 'way' && resolver.entity(m.id); }) + .map(function(m) { return { role: m.role || 'outer', id: m.id, nodes: resolver.childNodes(resolver.entity(m.id)) }; }); function join(ways) { var joined = [], current, first, last, i, how, what; @@ -202,7 +202,7 @@ _.extend(iD.Relation.prototype, { } } - return joined.map(function (nodes) { return _.pluck(nodes, 'loc'); }); + return joined.map(function(nodes) { return _.pluck(nodes, 'loc'); }); } function findOuter(inner) { @@ -221,9 +221,9 @@ _.extend(iD.Relation.prototype, { } } - var outers = join(members.filter(function (m) { return m.role === 'outer'; })), - inners = join(members.filter(function (m) { return m.role === 'inner'; })), - result = outers.map(function (o) { return [o]; }); + var outers = join(members.filter(function(m) { return m.role === 'outer'; })), + inners = join(members.filter(function(m) { return m.role === 'inner'; })), + result = outers.map(function(o) { return [o]; }); for (var i = 0; i < inners.length; i++) { var o = findOuter(inners[i]); diff --git a/js/id/core/way.js b/js/id/core/way.js index 5229040ab..e9fb7b71d 100644 --- a/js/id/core/way.js +++ b/js/id/core/way.js @@ -14,7 +14,7 @@ _.extend(iD.Way.prototype, { extent: function(resolver) { return resolver.transient(this, 'extent', function() { - return this.nodes.reduce(function (extent, id) { + return this.nodes.reduce(function(extent, id) { return extent.extend(resolver.entity(id).extent(resolver)); }, iD.geo.Extent()); }); diff --git a/js/id/geo.js b/js/id/geo.js index 61bd9273e..682980758 100644 --- a/js/id/geo.js +++ b/js/id/geo.js @@ -64,13 +64,13 @@ iD.geo.pointInPolygon = function(point, polygon) { }; iD.geo.polygonContainsPolygon = function(outer, inner) { - return _.every(inner, function (point) { + return _.every(inner, function(point) { return iD.geo.pointInPolygon(point, outer); }); }; iD.geo.polygonIntersectsPolygon = function(outer, inner) { - return _.some(inner, function (point) { + return _.some(inner, function(point) { return iD.geo.pointInPolygon(point, outer); }); }; diff --git a/js/id/geo/extent.js b/js/id/geo/extent.js index 4422b5e39..243ebad4f 100644 --- a/js/id/geo/extent.js +++ b/js/id/geo/extent.js @@ -14,7 +14,7 @@ iD.geo.Extent = function geoExtent(min, max) { iD.geo.Extent.prototype = [[], []]; _.extend(iD.geo.Extent.prototype, { - extend: function (obj) { + extend: function(obj) { if (!(obj instanceof iD.geo.Extent)) obj = new iD.geo.Extent(obj); return iD.geo.Extent([Math.min(obj[0][0], this[0][0]), Math.min(obj[0][1], this[0][1])], @@ -22,12 +22,12 @@ _.extend(iD.geo.Extent.prototype, { Math.max(obj[1][1], this[1][1])]); }, - center: function () { + center: function() { return [(this[0][0] + this[1][0]) / 2, (this[0][1] + this[1][1]) / 2]; }, - intersects: function (obj) { + intersects: function(obj) { if (!(obj instanceof iD.geo.Extent)) obj = new iD.geo.Extent(obj); return obj[0][0] <= this[1][0] && obj[0][1] <= this[1][1] && diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index af828b49d..1bea28a17 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -319,14 +319,14 @@ iD.Map = function(context) { } }; - map.flush = function () { + map.flush = function() { context.connection().flush(); context.history().reset(); return map; }; var usedTails = {}; - map.tail = function (_) { + map.tail = function(_) { if (!_ || usedTails[_] === undefined) { tail.text(_); usedTails[_] = true; diff --git a/js/id/svg.js b/js/id/svg.js index dda91ec99..36d1d060d 100644 --- a/js/id/svg.js +++ b/js/id/svg.js @@ -32,10 +32,10 @@ iD.svg = { }; }, - MultipolygonMemberTags: function (graph) { - return function (entity) { + MultipolygonMemberTags: function(graph) { + return function(entity) { var tags = entity.tags; - graph.parentRelations(entity).forEach(function (relation) { + graph.parentRelations(entity).forEach(function(relation) { if (relation.isMultipolygon()) { tags = _.extend({}, relation.tags, tags); } diff --git a/js/id/svg/areas.js b/js/id/svg/areas.js index cd9525d4d..46df5544c 100644 --- a/js/id/svg/areas.js +++ b/js/id/svg/areas.js @@ -28,11 +28,11 @@ iD.svg.Areas = function(projection) { paths.enter() .append('path') - .attr('class', function (d) { return d.type + ' area ' + klass; }); + .attr('class', function(d) { return d.type + ' area ' + klass; }); paths .order() - .attr('d', function (entity) { return path(entity.asGeoJSON(graph)); }) + .attr('d', function(entity) { return path(entity.asGeoJSON(graph)); }) .call(tagClasses) .call(iD.svg.MemberClasses(graph)); @@ -44,7 +44,7 @@ iD.svg.Areas = function(projection) { areas = _.pluck(areas, 'entity'); - var strokes = areas.filter(function (area) { + var strokes = areas.filter(function(area) { return area.type === 'way'; }); diff --git a/js/id/svg/lines.js b/js/id/svg/lines.js index 2fd7b9152..59fec46e1 100644 --- a/js/id/svg/lines.js +++ b/js/id/svg/lines.js @@ -95,7 +95,7 @@ iD.svg.Lines = function(projection) { // Determine the lengths of oneway paths var lengths = {}, - oneways = strokes.filter(function (d) { return d.isOneWay(); }).each(function(d) { + oneways = strokes.filter(function(d) { return d.isOneWay(); }).each(function(d) { lengths[d.id] = Math.floor(this.getTotalLength() / alength); }).data(); diff --git a/js/id/svg/member_classes.js b/js/id/svg/member_classes.js index d5745a466..713cc898e 100644 --- a/js/id/svg/member_classes.js +++ b/js/id/svg/member_classes.js @@ -17,7 +17,7 @@ iD.svg.MemberClasses = function(graph) { classes += ' member'; } - relations.forEach(function (relation) { + relations.forEach(function(relation) { classes += ' member-type-' + relation.tags.type; classes += ' member-role-' + relation.memberById(d.id).role; }); diff --git a/js/id/svg/midpoints.js b/js/id/svg/midpoints.js index 174012cf3..fc1a5bbc3 100644 --- a/js/id/svg/midpoints.js +++ b/js/id/svg/midpoints.js @@ -35,7 +35,7 @@ iD.svg.Midpoints = function(projection) { var groups = surface.select('.layer-hit').selectAll('g.midpoint') .filter(filter) - .data(_.values(midpoints), function (d) { return d.id; }); + .data(_.values(midpoints), function(d) { return d.id; }); var group = groups.enter() .insert('g', ':first-child') diff --git a/js/id/ui/geocoder.js b/js/id/ui/geocoder.js index 0643c41d1..7b79c2158 100644 --- a/js/id/ui/geocoder.js +++ b/js/id/ui/geocoder.js @@ -23,7 +23,7 @@ iD.ui.geocoder = function(context) { .text(t('geocoder.no_results', {name: searchVal})); } else if (resp.length > 1) { var spans = resultsList.selectAll('span') - .data(resp, function (d) { return d.place_id; }); + .data(resp, function(d) { return d.place_id; }); spans.enter() .append('span') diff --git a/js/id/ui/radial_menu.js b/js/id/ui/radial_menu.js index fe0d7ee86..106a48196 100644 --- a/js/id/ui/radial_menu.js +++ b/js/id/ui/radial_menu.js @@ -42,10 +42,10 @@ iD.ui.RadialMenu = function(operations) { }); button.append('circle') - .attr('class', function (d) { return 'radial-menu-item radial-menu-item-' + d.id; }) + .attr('class', function(d) { return 'radial-menu-item radial-menu-item-' + d.id; }) .attr('r', 15) - .attr('title', function (d) { return d.title; }) - .classed('disabled', function (d) { return !d.enabled(); }) + .attr('title', function(d) { return d.title; }) + .classed('disabled', function(d) { return !d.enabled(); }) .on('click', click) .on('mouseover', mouseover) .on('mouseout', mouseout); @@ -58,7 +58,7 @@ iD.ui.RadialMenu = function(operations) { .attr('y', -10); image.append('xhtml:span') - .attr('class', function (d) { return 'icon icon-operation icon-operation-' + d.id; }); + .attr('class', function(d) { return 'icon icon-operation icon-operation-' + d.id; }); var tooltip = menu.append('foreignObject') .style('display', 'none') diff --git a/js/id/ui/save.js b/js/id/ui/save.js index e19097ec9..b8f9d619a 100644 --- a/js/id/ui/save.js +++ b/js/id/ui/save.js @@ -10,7 +10,7 @@ iD.ui.Save = function(context) { if (!history.hasChanges()) return; - connection.authenticate(function (err) { + connection.authenticate(function(err) { var modal = iD.ui.modal(context.container()); var changes = history.changes(); changes.connection = connection; @@ -18,7 +18,7 @@ iD.ui.Save = function(context) { .classed('commit-modal', true) .datum(changes) .call(iD.ui.commit(context) - .on('cancel', function () { + .on('cancel', function() { modal.remove(); }) .on('fix', clickFix) @@ -73,7 +73,7 @@ iD.ui.Save = function(context) { modal.remove(); } - return function (selection) { + return function(selection) { var button = selection.append('button') .attr('class', 'save col12 disabled') .attr('tabindex', -1) diff --git a/test/index_packaged.html b/test/index_packaged.html index 874639094..c7d8ad7de 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -23,6 +23,7 @@ + diff --git a/test/spec/actions/delete_way.js b/test/spec/actions/delete_way.js index d550803fa..130f5f759 100644 --- a/test/spec/actions/delete_way.js +++ b/test/spec/actions/delete_way.js @@ -1,12 +1,12 @@ -describe("iD.actions.DeleteWay", function () { - it("removes the way from the graph", function () { +describe("iD.actions.DeleteWay", function() { + it("removes the way from the graph", function() { var way = iD.Way(), action = iD.actions.DeleteWay(way.id), graph = iD.Graph([way]).update(action); expect(graph.entity(way.id)).to.be.undefined; }); - it("removes a way from parent relations", function () { + it("removes a way from parent relations", function() { var way = iD.Way(), relation = iD.Relation({members: [{ id: way.id }]}), action = iD.actions.DeleteWay(way.id), @@ -14,7 +14,7 @@ describe("iD.actions.DeleteWay", function () { expect(_.pluck(graph.entity(relation.id).members, 'id')).not.to.contain(way.id); }); - it("deletes member nodes not referenced by another parent", function () { + it("deletes member nodes not referenced by another parent", function() { var node = iD.Node(), way = iD.Way({nodes: [node.id]}), action = iD.actions.DeleteWay(way.id), @@ -22,7 +22,7 @@ describe("iD.actions.DeleteWay", function () { expect(graph.entity(node.id)).to.be.undefined; }); - it("does not delete member nodes referenced by another parent", function () { + it("does not delete member nodes referenced by another parent", function() { var node = iD.Node(), way1 = iD.Way({nodes: [node.id]}), way2 = iD.Way({nodes: [node.id]}), @@ -31,7 +31,7 @@ describe("iD.actions.DeleteWay", function () { expect(graph.entity(node.id)).not.to.be.undefined; }); - it("deletes multiple member nodes", function () { + it("deletes multiple member nodes", function() { var a = iD.Node(), b = iD.Node(), way = iD.Way({nodes: [a.id, b.id]}), @@ -41,7 +41,7 @@ describe("iD.actions.DeleteWay", function () { expect(graph.entity(b.id)).to.be.undefined; }); - it("deletes a circular way's start/end node", function () { + it("deletes a circular way's start/end node", function() { var a = iD.Node(), b = iD.Node(), c = iD.Node(), @@ -53,7 +53,7 @@ describe("iD.actions.DeleteWay", function () { expect(graph.entity(c.id)).to.be.undefined; }); - it("does not delete member nodes with interesting tags", function () { + it("does not delete member nodes with interesting tags", function() { var node = iD.Node({tags: {highway: 'traffic_signals'}}), way = iD.Way({nodes: [node.id]}), action = iD.actions.DeleteWay(way.id), diff --git a/test/spec/actions/move_way.js b/test/spec/actions/move_way.js index 38f191a3c..24bc81593 100644 --- a/test/spec/actions/move_way.js +++ b/test/spec/actions/move_way.js @@ -1,5 +1,5 @@ -describe("iD.actions.MoveWay", function () { - it("moves all nodes in a way by the given amount", function () { +describe("iD.actions.MoveWay", function() { + it("moves all nodes in a way by the given amount", function() { var node1 = iD.Node({loc: [0, 0]}), node2 = iD.Node({loc: [5, 10]}), way = iD.Way({nodes: [node1.id, node2.id]}), @@ -14,7 +14,7 @@ describe("iD.actions.MoveWay", function () { expect(loc2[1]).to.be.closeTo( 7.866, 0.001); }); - it("moves repeated nodes only once", function () { + it("moves repeated nodes only once", function() { var node = iD.Node({loc: [0, 0]}), way = iD.Way({nodes: [node.id, node.id]}), delta = [2, 3], From b720a23897656dff6a9f5bf7450f2e800abab196 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Tue, 12 Feb 2013 16:37:13 -0500 Subject: [PATCH 356/415] Jshint fixes --- js/id/ui/modes.js | 2 +- js/id/ui/undo_redo.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/js/id/ui/modes.js b/js/id/ui/modes.js index a08b2b13b..b382ab295 100644 --- a/js/id/ui/modes.js +++ b/js/id/ui/modes.js @@ -64,5 +64,5 @@ iD.ui.Modes = function(context) { d3.select(document) .call(keybinding); - } + }; }; diff --git a/js/id/ui/undo_redo.js b/js/id/ui/undo_redo.js index f38066af4..9eef75364 100644 --- a/js/id/ui/undo_redo.js +++ b/js/id/ui/undo_redo.js @@ -43,5 +43,5 @@ iD.ui.UndoRedo = function(context) { .attr('data-original-title', iD.ui.tooltipHtml(redo || t('nothing_to_redo'), iD.ui.cmd('⌘⇧Z'))) .call(refreshTooltip); }); - } + }; }; From 4cbee01be18c50d1fb6742915e7de9677d2d521d Mon Sep 17 00:00:00 2001 From: Neogeografen Date: Tue, 12 Feb 2013 23:18:45 +0100 Subject: [PATCH 357/415] Update locale/da.js Working on more danish translation - not finish yet --- locale/da.js | 78 ++++++++++++++++++++++++++-------------------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/locale/da.js b/locale/da.js index 217f597f6..347a58962 100644 --- a/locale/da.js +++ b/locale/da.js @@ -34,27 +34,27 @@ locale.da = { operations: { add: { annotation: { - point: "Added a point.", - vertex: "Added a node to a way." + point: "Tilføjede et punkt.", + vertex: "Tilføjede en node til en vej." } }, start: { annotation: { - line: "Started a line.", - area: "Started an area." + line: "Startede en linje.", + area: "Startede et område." } }, - 'continue': { + 'Forsæt': { annotation: { - line: "Continued a line.", - area: "Continued an area." + line: "Forsatte en linje.", + area: "Forsatte et område." } }, cancel_draw: { - annotation: "Cancelled drawing." + annotation: "Annulleret indtegning." }, change_tags: { - annotation: "Changed tags." + annotation: "Ændret tags." }, circularize: { title: "Circularize", @@ -74,17 +74,17 @@ locale.da = { area: "Squared the corners of an area." } }, - 'delete': { - title: "Delete", - description: "Remove this from the map.", + 'slet': { + title: "Slet", + description: "Fjern dette fra kortet.", key: "⌫", annotation: { - point: "Deleted a point.", - vertex: "Deleted a node from a way.", - line: "Deleted a line.", - area: "Deleted an area.", - relation: "Deleted a relation.", - multiple: "Deleted {n} objects." + point: "Slettede et punkt.", + vertex: "Slettede en node fra en vej.", + line: "Slettede en linje.", + area: "Slettede et område.", + relation: "Sletede en relation.", + multiple: "Slettede {n} objekter." } }, connect: { @@ -108,14 +108,14 @@ locale.da = { annotation: "Merged {n} lines." }, move: { - title: "Move", - description: "Move this to a different location.", + title: "Flyt", + description: "Flyt dette til anden lokation.", key: "M", annotation: { - point: "Moved a point.", - vertex: "Moved a node in a way.", - line: "Moved a line.", - area: "Moved an area." + point: "Flyttede et punktMoved.", + vertex: "Flyttede en node i en vej.", + line: "Flyttede en linje.", + area: "Flyttede et område." } }, reverse: { @@ -125,10 +125,10 @@ locale.da = { annotation: "Reversed a line." }, split: { - title: "Split", - description: "Split this into two ways at this point.", + title: "Del op", + description: "Del op i to vej ved dette punkt.", key: "X", - annotation: "Split a way." + annotation: "Del op en vej." } }, @@ -140,17 +140,17 @@ locale.da = { deprecated_tags: "Deprecated tags: {tags}" }, - save: "Save", - unsaved_changes: "You have unsaved changes", - save_help: "Save changes to OpenStreetMap, making them visible to other users", - no_changes: "You don't have any changes to save.", - save_error: "An error occurred while trying to save", - uploading_changes: "Uploading changes to OpenStreetMap.", - just_edited: "You Just Edited OpenStreetMap!", - okay: "Okay", + save: "Gem", + unsaved_changes: "Du har ændringer der ikke er gemt endnu", + save_help: "Gem ændringer til OpenStreetMap gør dem synlige for andre brugere", + no_changes: "Du har ingen ændringer til at gemme endnu.", + save_error: "Der skete en fejl da du prøvede at gemme", + uploading_changes: "Gemmer nu ændringer til OpenStreetMap.", + just_edited: "Du har lige rettede i OpenStreetMap!", + okay: "Ok", - "zoom-in": "Zoom ind", - "zoom-out": "Zoom ud", + "zoom-ind": "Zoom ind", + "zoom-ud": "Zoom ud", nothing_to_undo: "Nothing to undo.", nothing_to_redo: "Nothing to redo.", @@ -158,8 +158,8 @@ locale.da = { browser_notice: "This editor is supported in Firefox, Chrome, Safari, Opera, and Internet Explorer 9 and above. Please upgrade your browser or use Potlatch 2 to edit the map.", inspector: { - no_documentation_combination: "This is no documentation available for this tag combination", - no_documentation_key: "This is no documentation available for this key", + no_documentation_combination: "Der er ingen dokumentation for denne tag kombination", + no_documentation_key: "Der er ingen dokumenation tilgængelig for denne nøgle", new_tag: "Nyt Tag" }, From 334f963f3cc57cb4f583072431960dc956f69ddd Mon Sep 17 00:00:00 2001 From: Saman Bemel-Benrud Date: Tue, 12 Feb 2013 17:31:16 -0500 Subject: [PATCH 358/415] fix change count, closes #638 --- css/app.css | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/css/app.css b/css/app.css index 2d0b91bc4..29f5b82b5 100644 --- a/css/app.css +++ b/css/app.css @@ -19,6 +19,7 @@ body { } .limiter { + position: relative; max-width: 1200px; } @@ -185,7 +186,8 @@ ul.toggle-list li a { border-top: 1px solid white; display:block; border-top: 1px solid rgba(0, 0, 0, .5); - text-wrap:no-wrap; + white-space:nowrap; + text-overflow:ellipsis; overflow:hidden; } ul.toggle-list li a:hover { background-color: #ececec;} @@ -352,8 +354,7 @@ button.save .count { button.save.has-count .count { display: block; position: absolute; - top: 0; - bottom: 0; + top: 5px; background: rgba(255, 255, 255, .5); color: #333; padding: 10px; From 3f19a293aae2929a15390d1cabbe6cff87bd01b5 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 14:04:39 -0800 Subject: [PATCH 359/415] Make selection less laggy --- js/id/behavior/select.js | 61 ++-------------------------------------- js/id/modes/select.js | 7 ++++- 2 files changed, 9 insertions(+), 59 deletions(-) diff --git a/js/id/behavior/select.js b/js/id/behavior/select.js index 7369af97c..c0b942520 100644 --- a/js/id/behavior/select.js +++ b/js/id/behavior/select.js @@ -1,13 +1,6 @@ iD.behavior.Select = function(context) { - var behavior = function(selection) { - - var timeout = null, - // the position of the first mousedown - pos = null; - - function click(event) { - d3.event = event; + function click() { var datum = d3.event.target.__data__; if (datum instanceof iD.Entity) { if (d3.event.shiftKey) { @@ -20,59 +13,11 @@ iD.behavior.Select = function(context) { } } - function mousedown() { - var datum = d3.event.target.__data__; - pos = [d3.event.clientX, d3.event.clientY]; - if (datum instanceof iD.Entity || (datum && datum.type === 'midpoint')) { - selection - .on('mousemove.select', mousemove) - .on('touchmove.select', mousemove); - - // we've seen a mousedown within 400ms of this one, so ignore - // both because they will be a double click - if (timeout !== null) { - window.clearTimeout(timeout); - selection.on('mousemove.select', null); - timeout = null; - } else { - // queue the click handler to fire in 400ms if no other clicks - // are detected - timeout = window.setTimeout((function(event) { - return function() { - click(event); - timeout = null; - selection.on('mousemove.select', null); - }; - // save the event for the click handler - })(d3.event), 200); - } - } - } - - // allow mousemoves to cancel the click - function mousemove() { - if (iD.geo.dist([d3.event.clientX, d3.event.clientY], pos) > 4) { - window.clearTimeout(timeout); - timeout = null; - } - } - - function mouseup() { - selection.on('mousemove.select', null); - if (pos && d3.event.clientX === pos[0] && d3.event.clientY === pos[1] && - !(d3.event.target.__data__ instanceof iD.Entity)) { - context.enter(iD.modes.Browse(context)); - } - } - - selection - .on('mousedown.select', mousedown) - .on('mouseup.select', mouseup) - .on('touchstart.select', mousedown); + selection.on('click.select', click); }; behavior.off = function(selection) { - selection.on('mousedown.select', null); + selection.on('click.select', null); }; return behavior; diff --git a/js/id/modes/select.js b/js/id/modes/select.js index 9b12c8134..db2b5f238 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -6,6 +6,7 @@ iD.modes.Select = function(context, selection, initial) { var inspector = iD.ui.inspector().initial(!!initial), keybinding = d3.keybinding('select'), + radialTime = null, behaviors = [ iD.behavior.Hover(), iD.behavior.Select(context), @@ -174,7 +175,9 @@ iD.modes.Select = function(context, selection, initial) { loc = entity.loc; } - context.surface().call(radialMenu, context.projection(loc)); + radialTime = window.setTimeout(function() { + context.surface().call(radialMenu, context.projection(loc)); + }, 300); } }; @@ -183,6 +186,8 @@ iD.modes.Select = function(context, selection, initial) { changeTags(singular(), inspector.tags()); } + if (radialTime) window.clearTimeout(radialTime); + context.container() .select('.inspector-wrap') .style('display', 'none') From 419aa088e33a865b87fb07cdb943528121072554 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 14:10:17 -0800 Subject: [PATCH 360/415] RadialMenu#center --- js/id/modes/select.js | 8 ++++---- js/id/ui/radial_menu.js | 11 +++++++++-- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/js/id/modes/select.js b/js/id/modes/select.js index db2b5f238..f1a35d225 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -169,14 +169,14 @@ iD.modes.Select = function(context, selection, initial) { radialMenu = iD.ui.RadialMenu(operations); if (d3.event && !initial) { - var loc = context.map().mouseCoordinates(); - if (entity && entity.type === 'node') { - loc = entity.loc; + radialMenu.center(context.projection(entity.loc)); + } else { + radialMenu.center(d3.mouse(context.surface().node())); } radialTime = window.setTimeout(function() { - context.surface().call(radialMenu, context.projection(loc)); + context.surface().call(radialMenu); }, 300); } }; diff --git a/js/id/ui/radial_menu.js b/js/id/ui/radial_menu.js index 106a48196..e718cfe51 100644 --- a/js/id/ui/radial_menu.js +++ b/js/id/ui/radial_menu.js @@ -1,7 +1,8 @@ iD.ui.RadialMenu = function(operations) { - var menu; + var menu, + center = [0, 0]; - var radialMenu = function(selection, center) { + var radialMenu = function(selection) { if (!operations.length) return; @@ -94,5 +95,11 @@ iD.ui.RadialMenu = function(operations) { } }; + radialMenu.center = function(_) { + if (!arguments.length) return center; + center = _; + return radialMenu; + }; + return radialMenu; }; From 0dbdd7c79766658353a2374933bbfd3a423f057b Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 14:20:07 -0800 Subject: [PATCH 361/415] Shift-click deselects a selected entity --- js/id/behavior/select.js | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/js/id/behavior/select.js b/js/id/behavior/select.js index c0b942520..f3d52a71c 100644 --- a/js/id/behavior/select.js +++ b/js/id/behavior/select.js @@ -2,14 +2,20 @@ iD.behavior.Select = function(context) { var behavior = function(selection) { function click() { var datum = d3.event.target.__data__; - if (datum instanceof iD.Entity) { - if (d3.event.shiftKey) { - context.enter(iD.modes.Select(context, context.selection().concat([datum.id]))); - } else { - context.enter(iD.modes.Select(context, [datum.id])); - } - } else if (!d3.event.shiftKey) { + if (!(datum instanceof iD.Entity) && !d3.event.shiftKey) { context.enter(iD.modes.Browse(context)); + + } else if (!d3.event.shiftKey) { + context.enter(iD.modes.Select(context, [datum.id])); + + } else if (context.selection().indexOf(datum.id) >= 0) { + var selection = _.without(context.selection(), datum.id); + context.enter(selection.length ? + iD.modes.Select(context, selection) : + iD.modes.Browse(context)); + + } else { + context.enter(iD.modes.Select(context, context.selection().concat([datum.id]))); } } From 89fe4bff09695b0113e4380fb3b85b6f39e73c29 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 14:28:58 -0800 Subject: [PATCH 362/415] Only add vertex when double-clicking the selected entity Previously double-clicking would add a vertex to any way, as long as anything was selected. --- js/id/behavior/select.js | 4 +++- js/id/modes/select.js | 19 +++++++++++-------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/js/id/behavior/select.js b/js/id/behavior/select.js index f3d52a71c..6b60b2da0 100644 --- a/js/id/behavior/select.js +++ b/js/id/behavior/select.js @@ -6,7 +6,9 @@ iD.behavior.Select = function(context) { context.enter(iD.modes.Browse(context)); } else if (!d3.event.shiftKey) { - context.enter(iD.modes.Select(context, [datum.id])); + // Avoid re-entering Select mode with same entity. + if (context.selection().length !== 1 || context.selection()[0] !== datum.id) + context.enter(iD.modes.Select(context, [datum.id])); } else if (context.selection().indexOf(datum.id) >= 0) { var selection = _.without(context.selection(), datum.id); diff --git a/js/id/modes/select.js b/js/id/modes/select.js index f1a35d225..c17a02ca1 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -6,7 +6,7 @@ iD.modes.Select = function(context, selection, initial) { var inspector = iD.ui.inspector().initial(!!initial), keybinding = d3.keybinding('select'), - radialTime = null, + timeout = null, behaviors = [ iD.behavior.Hover(), iD.behavior.Select(context), @@ -161,24 +161,27 @@ iD.modes.Select = function(context, selection, initial) { .call(keybinding); context.surface() - .on('dblclick.select', dblclick) .selectAll("*") .filter(selected) .classed('selected', true); radialMenu = iD.ui.RadialMenu(operations); + var showMenu = d3.event && !initial; - if (d3.event && !initial) { + if (showMenu) { if (entity && entity.type === 'node') { radialMenu.center(context.projection(entity.loc)); } else { radialMenu.center(d3.mouse(context.surface().node())); } - - radialTime = window.setTimeout(function() { - context.surface().call(radialMenu); - }, 300); } + + timeout = window.setTimeout(function() { + if (showMenu) context.surface().call(radialMenu); + + context.surface() + .on('dblclick.select', dblclick) + }, 300); }; mode.exit = function() { @@ -186,7 +189,7 @@ iD.modes.Select = function(context, selection, initial) { changeTags(singular(), inspector.tags()); } - if (radialTime) window.clearTimeout(radialTime); + if (timeout) window.clearTimeout(timeout); context.container() .select('.inspector-wrap') From 6bebb9197c9280103ff880d71b67e8df81f28631 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 14:58:54 -0800 Subject: [PATCH 363/415] 200ms --- js/id/modes/select.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/id/modes/select.js b/js/id/modes/select.js index c17a02ca1..ad1571d20 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -181,7 +181,7 @@ iD.modes.Select = function(context, selection, initial) { context.surface() .on('dblclick.select', dblclick) - }, 300); + }, 200); }; mode.exit = function() { From 3cce5b28dd2771240de3e5da26ba02e8651f3377 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 15:22:38 -0800 Subject: [PATCH 364/415] Fix, expand iD.behavior.Select tests --- js/id/behavior/select.js | 5 +++-- test/spec/behavior/select.js | 41 +++++++++++++++++++----------------- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/js/id/behavior/select.js b/js/id/behavior/select.js index 6b60b2da0..162535764 100644 --- a/js/id/behavior/select.js +++ b/js/id/behavior/select.js @@ -2,8 +2,9 @@ iD.behavior.Select = function(context) { var behavior = function(selection) { function click() { var datum = d3.event.target.__data__; - if (!(datum instanceof iD.Entity) && !d3.event.shiftKey) { - context.enter(iD.modes.Browse(context)); + if (!(datum instanceof iD.Entity)) { + if (!d3.event.shiftKey) + context.enter(iD.modes.Browse(context)); } else if (!d3.event.shiftKey) { // Avoid re-entering Select mode with same entity. diff --git a/test/spec/behavior/select.js b/test/spec/behavior/select.js index 5e9bf9ea3..ecbb7e7b4 100644 --- a/test/spec/behavior/select.js +++ b/test/spec/behavior/select.js @@ -20,6 +20,8 @@ describe("iD.behavior.Select", function() { .enter().append('circle') .attr('class', function(d) { return d.id; }); + context.enter(iD.modes.Browse(context)); + behavior = iD.behavior.Select(context); context.install(behavior); }); @@ -30,32 +32,33 @@ describe("iD.behavior.Select", function() { container.remove(); }); - specify("click on entity selects the entity", function(done) { - happen.mousedown(context.surface().select('.' + a.id).node()); - window.setTimeout(function() { - expect(context.selection()).to.eql([a.id]); - done(); - }, 600); + specify("click on entity selects the entity", function() { + happen.click(context.surface().select('.' + a.id).node()); + expect(context.selection()).to.eql([a.id]); }); - specify("click on empty space clears the selection", function(done) { + specify("click on empty space clears the selection", function() { context.enter(iD.modes.Select(context, [a.id])); happen.click(context.surface().node()); - happen.mousedown(context.surface().node()); - happen.mouseup(context.surface().node()); - window.setTimeout(function() { - expect(context.selection()).to.eql([]); - done(); - }, 600); + expect(context.mode().id).to.eql('browse'); }); - specify("shift-click on entity adds the entity to the selection", function(done) { + specify("shift-click on unselected entity adds it to the selection", function() { context.enter(iD.modes.Select(context, [a.id])); - happen.mousedown(context.surface().select('.' + b.id).node(), {shiftKey: true}); - window.setTimeout(function() { - expect(context.selection()).to.eql([a.id, b.id]); - done(); - }, 600); + happen.click(context.surface().select('.' + b.id).node(), {shiftKey: true}); + expect(context.selection()).to.eql([a.id, b.id]); + }); + + specify("shift-click on selected entity removes it from the selection", function() { + context.enter(iD.modes.Select(context, [a.id, b.id])); + happen.click(context.surface().select('.' + b.id).node(), {shiftKey: true}); + expect(context.selection()).to.eql([a.id]); + }); + + specify("shift-click on last selected entity clears the selection", function() { + context.enter(iD.modes.Select(context, [a.id])); + happen.click(context.surface().select('.' + a.id).node(), {shiftKey: true}); + expect(context.mode().id).to.eql('browse'); }); specify("shift-click on empty space leaves the selection unchanged", function() { From f071e9cf189e0e657fa7d302174d4f082d6bc6d1 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 15:28:14 -0800 Subject: [PATCH 365/415] Fix da translation --- locale/da.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/locale/da.js b/locale/da.js index 347a58962..342273223 100644 --- a/locale/da.js +++ b/locale/da.js @@ -44,7 +44,7 @@ locale.da = { area: "Startede et område." } }, - 'Forsæt': { + 'continue': { annotation: { line: "Forsatte en linje.", area: "Forsatte et område." @@ -74,7 +74,7 @@ locale.da = { area: "Squared the corners of an area." } }, - 'slet': { + 'delete': { title: "Slet", description: "Fjern dette fra kortet.", key: "⌫", @@ -149,8 +149,8 @@ locale.da = { just_edited: "Du har lige rettede i OpenStreetMap!", okay: "Ok", - "zoom-ind": "Zoom ind", - "zoom-ud": "Zoom ud", + "zoom-in": "Zoom ind", + "zoom-out": "Zoom ud", nothing_to_undo: "Nothing to undo.", nothing_to_redo: "Nothing to redo.", From dc2dbbe183dcb327a08488a9bd3854fe4e68e086 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 15:45:12 -0800 Subject: [PATCH 366/415] Extract iD.ui.Zoom, add tooltips with key hint --- index.html | 1 + js/id/id.js | 2 ++ js/id/ui.js | 21 +++------------------ js/id/ui/zoom.js | 40 ++++++++++++++++++++++++++++++++++++++++ locale/da.js | 8 +++++--- locale/de.js | 8 +++++--- locale/en.js | 8 +++++--- locale/es.js | 8 +++++--- locale/fr.js | 8 +++++--- locale/ja.js | 8 +++++--- locale/lv.js | 8 +++++--- locale/tr.js | 8 +++++--- 12 files changed, 86 insertions(+), 42 deletions(-) create mode 100644 js/id/ui/zoom.js diff --git a/index.html b/index.html index 4ea9d9721..009ee4133 100644 --- a/index.html +++ b/index.html @@ -82,6 +82,7 @@ + diff --git a/js/id/id.js b/js/id/id.js index f0747ad02..5db609593 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -85,6 +85,8 @@ window.iD = function () { context.projection = map.projection; context.tail = map.tail; context.redraw = map.redraw; + context.zoomIn = map.zoomIn; + context.zoomOut = map.zoomOut; context.container = function(_) { if (!arguments.length) return container; diff --git a/js/id/ui.js b/js/id/ui.js index 535c825eb..7ae2c9e28 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -37,20 +37,9 @@ iD.ui = function(context) { .attr('class', 'button-wrap col1') .call(iD.ui.Save(context)); - var zoom = container.append('div') + container.append('div') .attr('class', 'zoombuttons map-control') - .selectAll('button') - .data([['zoom-in', '+', map.zoomIn, t('zoom-in')], ['zoom-out', '-', map.zoomOut, t('zoom-out')]]) - .enter() - .append('button') - .attr('tabindex', -1) - .attr('class', function(d) { return d[0]; }) - .attr('title', function(d) { return d[3]; }) - .on('click.editor', function(d) { return d[2](); }) - .append('span') - .attr('class', function(d) { - return d[0] + ' icon'; - }); + .call(iD.ui.Zoom(context)); if (navigator.geolocation) { container.append('div') @@ -139,11 +128,7 @@ iD.ui = function(context) { .on('←', pan([pa, 0])) .on('↑', pan([0, pa])) .on('→', pan([-pa, 0])) - .on('↓', pan([0, -pa])) - .on('⇧=', function() { map.zoomIn(); }) - .on('+', function() { map.zoomIn(); }) - .on('-', function() { map.zoomOut(); }) - .on('dash', function() { map.zoomOut(); }); + .on('↓', pan([0, -pa])); d3.select(document) .call(keybinding); diff --git a/js/id/ui/zoom.js b/js/id/ui/zoom.js new file mode 100644 index 000000000..823bc120f --- /dev/null +++ b/js/id/ui/zoom.js @@ -0,0 +1,40 @@ +iD.ui.Zoom = function(context) { + var zooms = [{ + id: 'zoom-in', + title: t('zoom.in'), + action: context.zoomIn, + key: '+' + }, { + id: 'zoom-out', + title: t('zoom.out'), + action: context.zoomOut, + key: '-' + }]; + + return function(selection) { + var button = selection.selectAll('button') + .data(zooms) + .enter().append('button') + .attr('tabindex', -1) + .attr('class', function(d) { return d.id; }) + .on('click.editor', function(d) { d.action(); }) + .call(bootstrap.tooltip() + .placement('right') + .html(true) + .title(function(d) { + return iD.ui.tooltipHtml(d.title, d.key); + })); + + button.append('span') + .attr('class', function(d) { return d.id + ' icon'; }); + + var keybinding = d3.keybinding('zoom') + .on('+', function() { context.zoomIn(); }) + .on('-', function() { context.zoomOut(); }) + .on('⇧=', function() { context.zoomIn(); }) + .on('dash', function() { context.zoomOut(); }); + + d3.select(document) + .call(keybinding); + } +}; diff --git a/locale/da.js b/locale/da.js index 342273223..29e23c6f2 100644 --- a/locale/da.js +++ b/locale/da.js @@ -149,9 +149,6 @@ locale.da = { just_edited: "Du har lige rettede i OpenStreetMap!", okay: "Ok", - "zoom-in": "Zoom ind", - "zoom-out": "Zoom ud", - nothing_to_undo: "Nothing to undo.", nothing_to_redo: "Nothing to redo.", @@ -197,5 +194,10 @@ locale.da = { source_switch: { live: "live", dev: "dev" + }, + + zoom: { + in: "Zoom ind", + out: "Zoom ud" } }; diff --git a/locale/de.js b/locale/de.js index 543eaab2d..f1f3ef850 100644 --- a/locale/de.js +++ b/locale/de.js @@ -145,9 +145,6 @@ locale.de = { just_edited: "Sie haben gerade OpenStreetMap editiert!", okay: "OK", - "zoom-in": "Hineinzoomen", - "zoom-out": "Herauszoomen", - nothing_to_undo: "Nichts zum Rückgängigmachen.", nothing_to_redo: "Nichts zum Wiederherstellen.", @@ -193,5 +190,10 @@ locale.de = { source_switch: { live: "live", dev: "dev" + }, + + zoom: { + in: "Hineinzoomen", + out: "Herauszoomen" } }; diff --git a/locale/en.js b/locale/en.js index 007c8e5be..501f36a37 100644 --- a/locale/en.js +++ b/locale/en.js @@ -145,9 +145,6 @@ locale.en = { just_edited: "You Just Edited OpenStreetMap!", okay: "Okay", - "zoom-in": "Zoom In", - "zoom-out": "Zoom Out", - nothing_to_undo: "Nothing to undo.", nothing_to_redo: "Nothing to redo.", @@ -193,5 +190,10 @@ locale.en = { source_switch: { live: "live", dev: "dev" + }, + + zoom: { + in: "Zoom In", + out: "Zoom Out" } }; diff --git a/locale/es.js b/locale/es.js index 4bb5f3959..0468405b5 100644 --- a/locale/es.js +++ b/locale/es.js @@ -145,9 +145,6 @@ locale.es = { just_edited: "Acabas de editar OpenStreetMap!", //"You Just Edited OpenStreetMap!", okay: "OK", //"Okay", - "zoom-in": "Aumentar", // "Zoom In", - "zoom-out": "Alejar", //"Zoom Out", - nothing_to_undo: "Nada para deshacer", //"Nothing to undo.", nothing_to_redo: "Nada para rehacer", //"Nothing to redo.", @@ -193,5 +190,10 @@ locale.es = { source_switch: { live: "en vivo", //"live", dev: "dev" + }, + + zoom: { + in: "Aumentar", // "Zoom In", + out: "Alejar" //"Zoom Out", } }; diff --git a/locale/fr.js b/locale/fr.js index adf4a2d26..4530924a0 100644 --- a/locale/fr.js +++ b/locale/fr.js @@ -145,9 +145,6 @@ locale.fr = { just_edited: "Vous venez de participer à OpenStreetMap!", okay: "Okay", - "zoom-in": "Zoomer", - "zoom-out": "Dézoomer", - nothing_to_undo: "Rien à annuler.", nothing_to_redo: "Rien à refaire.", @@ -193,5 +190,10 @@ locale.fr = { source_switch: { live: "live", dev: "dev" + }, + + zoom: { + in: "Zoomer", + out: "Dézoomer" } }; diff --git a/locale/ja.js b/locale/ja.js index b002f54bf..ada737bd1 100644 --- a/locale/ja.js +++ b/locale/ja.js @@ -145,9 +145,6 @@ locale.ja = { just_edited: "OpenStreetMap編集完了!", okay: "OK", - "zoom-in": "ズームイン", - "zoom-out": "ズームアウト", - nothing_to_undo: "やり直す変更点がありません", nothing_to_redo: "やり直した変更点がありません", @@ -193,5 +190,10 @@ locale.ja = { source_switch: { live: "本番サーバ", dev: "開発サーバ" + }, + + zoom: { + in: "ズームイン", + out: "ズームアウト" } }; diff --git a/locale/lv.js b/locale/lv.js index 1d55450f8..fd8be7cfe 100644 --- a/locale/lv.js +++ b/locale/lv.js @@ -145,9 +145,6 @@ locale.lv = { just_edited: "Jūs nupat rediģējāt OpenStreetMap", okay: "Labi", - "zoom-in": "Pietuvināt", - "zoom-out": "Attālināt", - nothing_to_undo: "Nav nekā, ko atcelt", nothing_to_redo: "Nav nekā, ko atsaukt", @@ -193,5 +190,10 @@ locale.lv = { source_switch: { live: "live", dev: "dev" + }, + + zoom: { + in: "Pietuvināt", + out: "Attālināt" } }; diff --git a/locale/tr.js b/locale/tr.js index d14f08ecb..a9b3e4b47 100644 --- a/locale/tr.js +++ b/locale/tr.js @@ -145,9 +145,6 @@ locale.tr = { just_edited: "Şu an OpenStreetMap'de bir değişiklik yaptınız!", okay: "Tamam", - "zoom-in": "Yaklaş", - "zoom-out": "Uzaklaş", - nothing_to_undo: "Geri alınacak birşey yok.", nothing_to_redo: "Tekrar yapılacak birşey yok.", @@ -193,5 +190,10 @@ locale.tr = { source_switch: { live: "canlı", dev: "geliştirme" + }, + + zoom: { + in: "Yaklaş", + out: "Uzaklaş" } }; From b9860f222fdbfab04f77313db7e66673a5d209f1 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 15:52:17 -0800 Subject: [PATCH 367/415] Cleanup; consistent tooltips on remaining buttons --- css/app.css | 4 ++-- js/id/ui.js | 19 ++++++++++--------- js/id/ui/geocoder.js | 10 +++++++--- js/id/ui/geolocate.js | 25 ++++++++++++++----------- js/id/ui/layerswitcher.js | 9 ++++++--- 5 files changed, 39 insertions(+), 28 deletions(-) diff --git a/css/app.css b/css/app.css index 29f5b82b5..9f9e54c01 100644 --- a/css/app.css +++ b/css/app.css @@ -849,13 +849,13 @@ a.selected:hover .toggle.icon { background-position: -40px -180px;} margin: 4px; } -.geocode-control div { +.geocode-control div.content { top: 50px; width: 340px; margin: 4px; padding: 5px; } -.geocode-control div span { +.geocode-control div.content span { display: inline-block; border-bottom: 1px solid #333; } diff --git a/js/id/ui.js b/js/id/ui.js index 7ae2c9e28..e1daad3a1 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -38,20 +38,21 @@ iD.ui = function(context) { .call(iD.ui.Save(context)); container.append('div') - .attr('class', 'zoombuttons map-control') + .attr('class', 'map-control zoombuttons') .call(iD.ui.Zoom(context)); - if (navigator.geolocation) { - container.append('div') - .call(iD.ui.geolocate(map)); - } + container.append('div') + .attr('class', 'map-control geocode-control') + .call(iD.ui.Geocoder(context)); - container.append('div').attr('class', 'geocode-control map-control') - .call(iD.ui.geocoder(context)); - - container.append('div').attr('class', 'map-control layerswitcher-control') + container.append('div') + .attr('class', 'map-control layerswitcher-control') .call(iD.ui.layerswitcher(context)); + container.append('div') + .attr('class', 'map-control geolocate-control') + .call(iD.ui.Geolocate(map)); + container.append('div') .style('display', 'none') .attr('class', 'inspector-wrap fr col5'); diff --git a/js/id/ui/geocoder.js b/js/id/ui/geocoder.js index 7b79c2158..d0479e61d 100644 --- a/js/id/ui/geocoder.js +++ b/js/id/ui/geocoder.js @@ -1,4 +1,4 @@ -iD.ui.geocoder = function(context) { +iD.ui.Geocoder = function(context) { function resultExtent(bounds) { return new iD.geo.Extent( [parseFloat(bounds[3]), parseFloat(bounds[0])], @@ -75,8 +75,12 @@ iD.ui.geocoder = function(context) { var button = selection.append('button') .attr('tabindex', -1) .attr('title', t('geocoder.title')) - .html('') - .on('click', toggle); + .on('click', toggle) + .call(bootstrap.tooltip() + .placement('right')); + + button.append('span') + .attr('class', 'icon geocode'); var gcForm = selection.append('form'); diff --git a/js/id/ui/geolocate.js b/js/id/ui/geolocate.js index 14b060e8a..5356039dd 100644 --- a/js/id/ui/geolocate.js +++ b/js/id/ui/geolocate.js @@ -1,4 +1,8 @@ -iD.ui.geolocate = function(map) { +iD.ui.Geolocate = function(map) { + function click() { + navigator.geolocation.getCurrentPosition( + success, error); + } function success(position) { map.center([position.coords.longitude, position.coords.latitude]); @@ -7,17 +11,16 @@ iD.ui.geolocate = function(map) { function error() { } return function(selection) { - selection - .attr('class', 'geolocate-control map-control') - .append('button') + if (!navigator.geolocation) return; + + var button = selection.append('button') .attr('tabindex', -1) .attr('title', 'Show My Location') - .on('click', function() { - navigator.geolocation.getCurrentPosition( - success, error); - }) - .append('span') - .attr('class','icon geolocate'); - }; + .on('click', click) + .call(bootstrap.tooltip() + .placement('right')); + button.append('span') + .attr('class', 'icon geolocate'); + }; }; diff --git a/js/id/ui/layerswitcher.js b/js/id/ui/layerswitcher.js index 9ae4f3fbe..60fd674e0 100644 --- a/js/id/ui/layerswitcher.js +++ b/js/id/ui/layerswitcher.js @@ -23,10 +23,13 @@ iD.ui.layerswitcher = function(context) { .attr('tabindex', -1) .attr('class', 'fillD') .attr('title', t('layerswitcher.description')) - .html("") - .on('click.layerswitcher-toggle', toggle); + .on('click.layerswitcher-toggle', toggle) + .call(bootstrap.tooltip() + .placement('right')); + + button.append('span') + .attr('class', 'layers icon'); - function show() { setVisible(true); } function hide() { setVisible(false); } function toggle() { setVisible(content.classed('hide')); } From 8d0225e9389c9cf81e145676c7be5af9018ed541 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 16:11:22 -0800 Subject: [PATCH 368/415] i18n for geolocate --- js/id/ui/geolocate.js | 2 +- locale/en.js | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/js/id/ui/geolocate.js b/js/id/ui/geolocate.js index 5356039dd..d1d3f3772 100644 --- a/js/id/ui/geolocate.js +++ b/js/id/ui/geolocate.js @@ -15,7 +15,7 @@ iD.ui.Geolocate = function(map) { var button = selection.append('button') .attr('tabindex', -1) - .attr('title', 'Show My Location') + .attr('title', t('geolocate.title')) .on('click', click) .call(bootstrap.tooltip() .placement('right')); diff --git a/locale/en.js b/locale/en.js index 501f36a37..7581b5fcb 100644 --- a/locale/en.js +++ b/locale/en.js @@ -168,6 +168,10 @@ locale.en = { no_results: "Couldn't locate a place named '{name}'" }, + geolocate: { + title: "Show My Location" + }, + description: "Description", logout: "logout", From 1c5a894f1e37bb5ebbeb66eaf2938df678610d0b Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 16:16:52 -0800 Subject: [PATCH 369/415] Add to translation README --- locale/README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/locale/README.md b/locale/README.md index b1610b311..0feb1f631 100644 --- a/locale/README.md +++ b/locale/README.md @@ -22,6 +22,10 @@ Let's look at an example line from `en.js`: no_results: "Couldn't locate a place named '{name}'" ``` +`no_results` is the translation _key_, and should not be translated. +The text to the right of the colon, `"Couldn't locate a place named '{name}'"`, +is the string to be translated. + The word in brackets, `{name}`, should **not** be translated into a new language: it's replaced with a place name when iD presents the text. So a French translation would look like @@ -30,6 +34,15 @@ a French translation would look like no_results: "Impossible de localiser l'endroit nommé '{name}'" ``` +For technical reasons, a few translation keys are quoted. For example: + +``` +'delete': "Delete" +``` + +Only translate the value to the right of the colon, not the quoted key on +the left. + ## License Contributions to translations are under the same liberal From 7da3ef791f479721bb33f022194548aaa7c4f63b Mon Sep 17 00:00:00 2001 From: Saman Bemel-Benrud Date: Tue, 12 Feb 2013 19:17:00 -0500 Subject: [PATCH 370/415] style and layout refinements to layer switcher. --- css/app.css | 23 +++++++++-------------- js/id/ui/layerswitcher.js | 13 +++++++------ 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/css/app.css b/css/app.css index 29f5b82b5..3593628d7 100644 --- a/css/app.css +++ b/css/app.css @@ -225,7 +225,7 @@ ul.link-list li:last-child { .fillD { background:rgba(0,0,0,.8); - color: #a9a9a9; + color: #6C6C6C; } @@ -660,10 +660,11 @@ a.selected:hover .toggle.icon { background-position: -40px -180px;} } .map-overlay { - width: 150px; - position:absolute; - left:40px; - top:0; + right: 75%; + max-width: 260px; + min-width: 210px; + position: fixed; + left: 40px; display: block; border-radius: 4px; } @@ -689,10 +690,6 @@ a.selected:hover .toggle.icon { background-position: -40px -180px;} top:190px; } -.layerswitcher-control .map-overlay { - width:250px; -} - .nudge-container { margin-top: 10px; } @@ -702,10 +699,8 @@ a.selected:hover .toggle.icon { background-position: -40px -180px;} font-size:10px; padding:0 5px 3px 5px; background: white; - border:0; text-transform: uppercase; font-weight: bold; - } .layerswitcher-control .adjustments button:hover { @@ -742,9 +737,9 @@ a.selected:hover .toggle.icon { background-position: -40px -180px;} .layerswitcher-control .nudge { text-indent: -9999px; overflow: hidden; - width:20px; + width:16.6666%; border-radius: 0; - margin-right:1px; + border-right: 1px solid rgba(0, 0, 0, .5); position: relative; } @@ -787,7 +782,7 @@ a.selected:hover .toggle.icon { background-position: -40px -180px;} } .layerswitcher-control .reset { - width: 45px; + width: 33.3333%; border-radius: 0 4px 4px 0; } diff --git a/js/id/ui/layerswitcher.js b/js/id/ui/layerswitcher.js index 9ae4f3fbe..312605f54 100644 --- a/js/id/ui/layerswitcher.js +++ b/js/id/ui/layerswitcher.js @@ -133,7 +133,7 @@ iD.ui.layerswitcher = function(context) { return d.data.name; }); layerLinks.exit().remove(); - layerLinks.enter() + var LayerInner = layerLinks.enter() .append('li') .append('a') .attr('data-original-title', function(d) { @@ -141,18 +141,19 @@ iD.ui.layerswitcher = function(context) { }) .attr('href', '#') .attr('class', 'layer') - .text(function(d) { - return d.data.name; - }) .each(function(d) { // only set tooltips for layers with tooltips if (d.data.description) { d3.select(this).call(bootstrap.tooltip().placement('right')); } }) - .on('click.set-source', clickSetSource) - .insert('span') + .on('click.set-source', clickSetSource); + LayerInner.insert('span') .attr('class','icon toggle'); + LayerInner.insert('span').text(function(d) { + return d.data.name; + }); + selectLayer(context.background().source()); } From 5c9832e2ef5a28f89d9a364b5608fc9d9e16fc6e Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 16:18:55 -0800 Subject: [PATCH 371/415] Fix test --- test/spec/ui/geocoder.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/spec/ui/geocoder.js b/test/spec/ui/geocoder.js index 54a7e571c..91e107ee2 100644 --- a/test/spec/ui/geocoder.js +++ b/test/spec/ui/geocoder.js @@ -1,6 +1,6 @@ -describe("iD.ui.geocoder", function () { +describe("iD.ui.Geocoder", function () { it('can be instantiated', function () { - var geocoder = iD.ui.geocoder(); + var geocoder = iD.ui.Geocoder(); expect(geocoder).to.be.ok; }); }); From 50e01150a7379b91867747e3c3eb77c16f0137c8 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 16:25:39 -0800 Subject: [PATCH 372/415] Fix global leak --- js/id/ui/tag_reference.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/id/ui/tag_reference.js b/js/id/ui/tag_reference.js index cdb22b505..6b7b9fb9f 100644 --- a/js/id/ui/tag_reference.js +++ b/js/id/ui/tag_reference.js @@ -19,7 +19,7 @@ iD.ui.tagReference = function(selection) { header.append('span') .text(g('title')); - referenceBody = selection.append('div') + var referenceBody = selection.append('div') .attr('class','modal-section fillL2'); referenceBody From 1e60b0b7fa7c2057cc7f22478fe2e71f16aada3a Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 16:28:02 -0800 Subject: [PATCH 373/415] iD constructors are camel case --- js/id/behavior/lasso.js | 2 +- js/id/modes/select.js | 2 +- js/id/ui.js | 6 +++--- js/id/ui/commit.js | 2 +- js/id/ui/contributors.js | 2 +- js/id/ui/geocoder.js | 2 +- js/id/ui/inspector.js | 4 ++-- js/id/ui/lasso.js | 6 +++--- js/id/ui/layerswitcher.js | 4 ++-- js/id/ui/save.js | 4 ++-- js/id/ui/success.js | 2 +- js/id/ui/toggle.js | 2 +- js/id/ui/userpanel.js | 2 +- test/spec/ui/inspector.js | 4 ++-- 14 files changed, 22 insertions(+), 22 deletions(-) diff --git a/js/id/behavior/lasso.js b/js/id/behavior/lasso.js index 0a26f311d..6196cbf2e 100644 --- a/js/id/behavior/lasso.js +++ b/js/id/behavior/lasso.js @@ -12,7 +12,7 @@ iD.behavior.Lasso = function(context) { pos = [d3.event.clientX, d3.event.clientY]; - lasso = iD.ui.lasso().a(d3.mouse(context.surface().node())); + lasso = iD.ui.Lasso().a(d3.mouse(context.surface().node())); context.surface().call(lasso); diff --git a/js/id/modes/select.js b/js/id/modes/select.js index ad1571d20..ac6ad4943 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -4,7 +4,7 @@ iD.modes.Select = function(context, selection, initial) { button: 'browse' }; - var inspector = iD.ui.inspector().initial(!!initial), + var inspector = iD.ui.Inspector().initial(!!initial), keybinding = d3.keybinding('select'), timeout = null, behaviors = [ diff --git a/js/id/ui.js b/js/id/ui.js index e1daad3a1..48e5682bf 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -47,7 +47,7 @@ iD.ui = function(context) { container.append('div') .attr('class', 'map-control layerswitcher-control') - .call(iD.ui.layerswitcher(context)); + .call(iD.ui.LayerSwitcher(context)); container.append('div') .attr('class', 'map-control geolocate-control') @@ -103,7 +103,7 @@ iD.ui = function(context) { linkList.append('li') .attr('id', 'user-list') - .call(iD.ui.contributors(context)); + .call(iD.ui.Contributors(context)); window.onbeforeunload = function() { history.save(); @@ -142,7 +142,7 @@ iD.ui = function(context) { map.centerZoom([-77.02271, 38.90085], 20); } - userContainer.call(iD.ui.userpanel(connection) + userContainer.call(iD.ui.UserPanel(connection) .on('logout.editor', connection.logout) .on('login.editor', connection.authenticate)); diff --git a/js/id/ui/commit.js b/js/id/ui/commit.js index 8d724a732..6708ece49 100644 --- a/js/id/ui/commit.js +++ b/js/id/ui/commit.js @@ -1,4 +1,4 @@ -iD.ui.commit = function(context) { +iD.ui.Commit = function(context) { var event = d3.dispatch('cancel', 'save', 'fix'); function zipSame(d) { diff --git a/js/id/ui/contributors.js b/js/id/ui/contributors.js index 596c576ad..2389f266a 100644 --- a/js/id/ui/contributors.js +++ b/js/id/ui/contributors.js @@ -1,4 +1,4 @@ -iD.ui.contributors = function(context) { +iD.ui.Contributors = function(context) { function update(selection) { var users = {}, limit = 3, diff --git a/js/id/ui/geocoder.js b/js/id/ui/geocoder.js index d0479e61d..cc0ce205c 100644 --- a/js/id/ui/geocoder.js +++ b/js/id/ui/geocoder.js @@ -64,7 +64,7 @@ iD.ui.Geocoder = function(context) { function setVisible(show) { if (show !== shown) { button.classed('active', show); - gcForm.call(iD.ui.toggle(show)); + gcForm.call(iD.ui.Toggle(show)); if (!show) resultsList.classed('hide', !show); if (show) inputNode.node().focus(); else inputNode.node().blur(); diff --git a/js/id/ui/inspector.js b/js/id/ui/inspector.js index 836f73cff..173594101 100644 --- a/js/id/ui/inspector.js +++ b/js/id/ui/inspector.js @@ -1,4 +1,4 @@ -iD.ui.inspector = function() { +iD.ui.Inspector = function() { var event = d3.dispatch('changeTags', 'close'), taginfo = iD.taginfo(), initial = false, @@ -43,7 +43,7 @@ iD.ui.inspector = function() { .attr('class', 'inspector-buttons pad1 fillD') .call(drawButtons); - inspector.call(iD.ui.toggle(true)); + inspector.call(iD.ui.Toggle(true)); } function drawHead(selection) { diff --git a/js/id/ui/lasso.js b/js/id/ui/lasso.js index 511df2a7e..4644364c3 100644 --- a/js/id/ui/lasso.js +++ b/js/id/ui/lasso.js @@ -1,4 +1,4 @@ -iD.ui.lasso = function() { +iD.ui.Lasso = function() { var center, box, group, @@ -13,7 +13,7 @@ iD.ui.lasso = function() { box = group.append('rect') .attr('class', 'lasso-box'); - group.call(iD.ui.toggle(true)); + group.call(iD.ui.Toggle(true)); } @@ -50,7 +50,7 @@ iD.ui.lasso = function() { lasso.close = function(selection) { if (group) { - group.call(iD.ui.toggle(false, function() { + group.call(iD.ui.Toggle(false, function() { d3.select(this).remove(); })); } diff --git a/js/id/ui/layerswitcher.js b/js/id/ui/layerswitcher.js index 60fd674e0..6724715e0 100644 --- a/js/id/ui/layerswitcher.js +++ b/js/id/ui/layerswitcher.js @@ -1,4 +1,4 @@ -iD.ui.layerswitcher = function(context) { +iD.ui.LayerSwitcher = function(context) { var event = d3.dispatch('cancel', 'save'), opacities = [1, 0.5, 0]; @@ -36,7 +36,7 @@ iD.ui.layerswitcher = function(context) { function setVisible(show) { if (show !== shown) { button.classed('active', show); - content.call(iD.ui.toggle(show)); + content.call(iD.ui.Toggle(show)); shown = show; } } diff --git a/js/id/ui/save.js b/js/id/ui/save.js index b8f9d619a..b4eed1dd0 100644 --- a/js/id/ui/save.js +++ b/js/id/ui/save.js @@ -17,7 +17,7 @@ iD.ui.Save = function(context) { modal.select('.content') .classed('commit-modal', true) .datum(changes) - .call(iD.ui.commit(context) + .call(iD.ui.Commit(context) .on('cancel', function() { modal.remove(); }) @@ -60,7 +60,7 @@ iD.ui.Save = function(context) { id: changeset_id, comment: e.comment }) - .call(iD.ui.success(connection) + .call(iD.ui.Success(connection) .on('cancel', function() { modal.remove(); })); diff --git a/js/id/ui/success.js b/js/id/ui/success.js index fffa7b6ab..17a577fb3 100644 --- a/js/id/ui/success.js +++ b/js/id/ui/success.js @@ -1,4 +1,4 @@ -iD.ui.success = function(connection) { +iD.ui.Success = function(connection) { var event = d3.dispatch('cancel', 'save'); function success(selection) { diff --git a/js/id/ui/toggle.js b/js/id/ui/toggle.js index 3a7500023..c3c299905 100644 --- a/js/id/ui/toggle.js +++ b/js/id/ui/toggle.js @@ -2,7 +2,7 @@ // hide class, which sets display=none, and a d3 transition for opacity. // this will cause blinking when called repeatedly, so check that the // value actually changes between calls. -iD.ui.toggle = function(show, callback) { +iD.ui.Toggle = function(show, callback) { return function(selection) { selection.style('opacity', show ? 0 : 1) .classed('hide', false) diff --git a/js/id/ui/userpanel.js b/js/id/ui/userpanel.js index fda2fc463..3dab2a5bd 100644 --- a/js/id/ui/userpanel.js +++ b/js/id/ui/userpanel.js @@ -1,4 +1,4 @@ -iD.ui.userpanel = function(connection) { +iD.ui.UserPanel = function(connection) { var event = d3.dispatch('logout', 'login'); function user(selection) { diff --git a/test/spec/ui/inspector.js b/test/spec/ui/inspector.js index e92604a7a..17160ddb4 100644 --- a/test/spec/ui/inspector.js +++ b/test/spec/ui/inspector.js @@ -1,10 +1,10 @@ -describe("iD.ui.inspector", function () { +describe("iD.ui.Inspector", function () { var inspector, element, tags = {highway: 'residential'}, entity, graph, context; function render() { - inspector = iD.ui.inspector().context(context); + inspector = iD.ui.Inspector().context(context); element = d3.select('body') .append('div') .attr('id', 'inspector-wrap') From 902ae8026709fdb9467a87a8764b521c44584781 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 16:51:31 -0800 Subject: [PATCH 374/415] Include all locale files --- Makefile | 4 ++-- index.html | 11 ++++++----- {locale => js/lib}/locale.js | 0 3 files changed, 8 insertions(+), 7 deletions(-) rename {locale => js/lib}/locale.js (100%) diff --git a/Makefile b/Makefile index 826337109..bb6844399 100644 --- a/Makefile +++ b/Makefile @@ -52,8 +52,8 @@ all: \ js/id/ui/*.js \ js/id/validate.js \ js/id/end.js \ - locale/locale.js \ - locale/en.js + js/lib/locale.js \ + locale/*.js iD.js: Makefile @rm -f $@ diff --git a/index.html b/index.html index 009ee4133..e79c178e4 100644 --- a/index.html +++ b/index.html @@ -147,14 +147,15 @@ - - - - + + + + + + - diff --git a/locale/locale.js b/js/lib/locale.js similarity index 100% rename from locale/locale.js rename to js/lib/locale.js From 211431bdb87287808328490d57ae04f72eced161 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 16:58:44 -0800 Subject: [PATCH 375/415] Fix "Browse" button in non-English locales --- css/app.css | 2 +- js/id/ui/modes.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/css/app.css b/css/app.css index 9f9e54c01..765c58f1f 100644 --- a/css/app.css +++ b/css/app.css @@ -327,7 +327,7 @@ button.centered { border-radius:0 4px 4px 0; } -button.Browse .label { display: none;} +button.browse .label { display: none;} button.action { background: #7092ff; diff --git a/js/id/ui/modes.js b/js/id/ui/modes.js index b382ab295..d3db0074a 100644 --- a/js/id/ui/modes.js +++ b/js/id/ui/modes.js @@ -11,7 +11,7 @@ iD.ui.Modes = function(context) { buttons.enter().append('button') .attr('tabindex', -1) - .attr('class', function(mode) { return mode.title + ' add-button col3'; }) + .attr('class', function(mode) { return mode.id + ' add-button col3'; }) .on('click.mode-buttons', function(mode) { context.enter(mode); }) .call(bootstrap.tooltip() .placement('bottom') From 5ee554be0d0585152eac58b9726fbb3df065b6ba Mon Sep 17 00:00:00 2001 From: Saman Bemel-Benrud Date: Tue, 12 Feb 2013 20:19:23 -0500 Subject: [PATCH 376/415] styling geocoder. --- css/app.css | 48 ++++++++++++++++++++------------------------- js/id/ui/restore.js | 17 ++++++++-------- 2 files changed, 30 insertions(+), 35 deletions(-) diff --git a/css/app.css b/css/app.css index 3593628d7..f42fbaf26 100644 --- a/css/app.css +++ b/css/app.css @@ -23,7 +23,7 @@ body { max-width: 1200px; } -div, textarea, input, span, ul, li, ol, a, button { +div, textarea, input, form, span, ul, li, ol, a, button { -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; @@ -121,7 +121,7 @@ input[type=text]:focus { } input[type=text] { - padding:4px 10px; + padding:5px 10px; height:30px; resize: none; } @@ -182,10 +182,9 @@ ul li { list-style: none;} ul.toggle-list li a { font-weight: bold; color: #333; - padding: 10px; - border-top: 1px solid white; + padding: 5px 10px; display:block; - border-top: 1px solid rgba(0, 0, 0, .5); + border-top: 1px solid #ccc; white-space:nowrap; text-overflow:ellipsis; overflow:hidden; @@ -268,10 +267,6 @@ button:hover { background-color: #ececec; } -button.col3:hover { - background: #bde5aa; -} - button.active { cursor:url(../img/cursor-pointing.png) 6 1, auto; } @@ -282,7 +277,7 @@ button.disabled { } button.active:not([disabled]):not(.disabled) { - background: #6bc641; + background: #7092ff; } button.minor { @@ -656,7 +651,7 @@ a.selected:hover .toggle.icon { background-position: -40px -180px;} } .map-control > button.active:hover { - background: #6bc641; + background: #7092ff; } .map-overlay { @@ -816,8 +811,8 @@ a.selected:hover .toggle.icon { background-position: -40px -180px;} .layerswitcher-control li:hover .select-box, .layerswitcher-control li.selected .select-box { - border: 2px solid #6bc641; - background: rgba(107, 198, 65, .5); + border: 2px solid #7092ff; + background: rgba(89, 123, 231, .5); opacity: .5; } .layerswitcher-control li.selected:hover .select-box, @@ -834,25 +829,29 @@ a.selected:hover .toggle.icon { background-position: -40px -180px;} /* Geocoder */ -.geocode-control { +.geocode-control, .geocode-control form { top:150px; } +.geocode-control form { + padding: 4px; +} + .geocode-control input { - width: 140px; - border: 1px solid #ccc; - margin: 4px; + width: 100%; } .geocode-control div { - top: 50px; - width: 340px; - margin: 4px; - padding: 5px; + z-index: 100; + top: 190px; + max-height: 300px; + overflow-y: auto; } + .geocode-control div span { display: inline-block; border-bottom: 1px solid #333; + padding: 5px 10px; } /* Geolocator */ @@ -1146,11 +1145,6 @@ div.typeahead a:first-child { padding: 20px; } -.modal-section .buttons { - padding-top: 10px; - width: 100%; -} - .modal-section img.wiki-image { max-width: 100%; max-height: 300px; @@ -1214,7 +1208,7 @@ a.success-action { } .notice .zoom-to:hover { - background: #bde5aa; + background: #d8e1ff; } .notice .zoom-to .icon { diff --git a/js/id/ui/restore.js b/js/id/ui/restore.js index 3df275d89..aaadae950 100644 --- a/js/id/ui/restore.js +++ b/js/id/ui/restore.js @@ -4,16 +4,17 @@ iD.ui.restore = function(selection, history) { modal.select('.modal') .attr('class', 'modal-splash modal'); - var introModal = modal.select('.content') - .append('div') - .attr('class', 'modal-section fillL') - .text('You have unsaved changes from a previous editing session. Do you wish to restore these changes?'); + var introModal = modal.select('.content'); - buttons = introModal - .append('div') - .attr('class', 'buttons cf') + introModal.append('div') + .attr('class', 'modal-section fillL') + .append('h3').text('You have unsaved changes from a previous editing session. Do you wish to restore these changes?'); + var buttonWrap = introModal.append('div') + .attr('class', 'modal-section fillD cf col12'); + + buttons = buttonWrap .append('div') - .attr('class', 'button-wrap joined col4'); + .attr('class', 'button-wrap joined col6'); buttons.append('button') .attr('class', 'save action button col6') From c3a06c681a6c0534f024dceb45c275d871c72c65 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 17:21:52 -0800 Subject: [PATCH 377/415] Translate/cleanup iD.ui.Commit --- js/id/ui/commit.js | 96 ++++++++++++++++++++++++---------------------- locale/en.js | 12 ++++++ 2 files changed, 63 insertions(+), 45 deletions(-) diff --git a/js/id/ui/commit.js b/js/id/ui/commit.js index 6708ece49..da6fe5c02 100644 --- a/js/id/ui/commit.js +++ b/js/id/ui/commit.js @@ -31,47 +31,44 @@ iD.ui.Commit = function(context) { header = selection.append('div').attr('class', 'header modal-section fillL'), body = selection.append('div').attr('class', 'body'); - header.append('h2').text('Save Changes'); + header.append('h2') + .text(t('commit.title')); - // Comment Box - var comment_section = body.append('div').attr('class','modal-section fillD'); - var commentField = comment_section.append('textarea') + var commentSection = body.append('div') + .attr('class', 'modal-section fillD'); + + var commentField = commentSection.append('textarea') .attr('class', 'changeset-comment') - .attr('placeholder', 'Brief Description of your contributions') + .attr('placeholder', t('commit.description_placeholder')) .property('value', context.storage('comment') || ''); commentField.node().select(); - var commit_info = - comment_section - .append('p') - .attr('class','commit-info'); + var userLink = d3.select(document.createElement('div')); - commit_info.append('span').text('The changes you upload as '); - - var user_link = commit_info.append('a') - .attr('class','user-info') - .text(user.display_name) - .attr('href', connection.url() + '/user/' + user.display_name) - .attr('target', '_blank'); - - commit_info.append('span').text(' will be visible on all maps that use OpenStreetMap data:'); + userLink.append('a') + .attr('class','user-info') + .text(user.display_name) + .attr('href', connection.url() + '/user/' + user.display_name) + .attr('target', '_blank'); if (user.image_url) { - user_link - .append('img') - .attr('src', user.image_url) - .attr('class', 'icon icon-pre-text user-icon'); + userLink.append('img') + .attr('src', user.image_url) + .attr('class', 'icon icon-pre-text user-icon'); } - // Confirm / Cancel Buttons - var buttonwrap = comment_section.append('div') - .attr('class', 'buttons cf') - .append('div') - .attr('class', 'button-wrap joined col4'); + commentSection.append('p') + .attr('class', 'commit-info') + .html(t('commit.upload_explanation', {user: userLink.html()})); - var savebutton = buttonwrap - .append('button') + // Confirm / Cancel Buttons + var buttonWrap = commentSection.append('div') + .attr('class', 'buttons cf') + .append('div') + .attr('class', 'button-wrap joined col4'); + + var saveButton = buttonWrap.append('button') .attr('class', 'save action col6 button') .on('click.save', function() { var comment = commentField.node().value; @@ -80,62 +77,71 @@ iD.ui.Commit = function(context) { comment: comment }); }); - savebutton.append('span').attr('class','label').text('Save'); - var cancelbutton = buttonwrap.append('button') + saveButton.append('span') + .attr('class', 'label') + .text(t('commit.save')); + + var cancelButton = buttonWrap.append('button') .attr('class', 'cancel col6 button') .on('click.cancel', function() { event.cancel(); }); - cancelbutton.append('span').attr('class','label').text('Cancel'); + + cancelButton.append('span') + .attr('class', 'label') + .text(t('commit.cancel')); var warnings = body.selectAll('div.warning-section') .data(iD.validate(changes, context.graph())) .enter() - .append('div').attr('class', 'modal-section warning-section fillL'); + .append('div') + .attr('class', 'modal-section warning-section fillL'); warnings.append('h3') - .text('Warnings'); + .text(t('commit.warnings')); - var warning_li = warnings.append('ul') + var warningLi = warnings.append('ul') .attr('class', 'changeset-list') .selectAll('li') .data(function(d) { return d; }) .enter() .append('li'); - warning_li.append('button') + warningLi.append('button') .attr('class', 'minor') .on('click', event.fix) .append('span') .attr('class', 'icon warning'); - warning_li.append('strong').text(function(d) { + warningLi.append('strong').text(function(d) { return d.message; }); var section = body.selectAll('div.commit-section') .data(['modified', 'deleted', 'created'].filter(changesLength)) .enter() - .append('div').attr('class', 'commit-section modal-section fillL2'); + .append('div') + .attr('class', 'commit-section modal-section fillL2'); - section.append('h3').text(function(d) { - return d.charAt(0).toUpperCase() + d.slice(1); - }) + section.append('h3') + .text(function(d) { return t('commit.' + d); }) .append('small') .attr('class', 'count') .text(changesLength); var li = section.append('ul') - .attr('class','changeset-list') + .attr('class', 'changeset-list') .selectAll('li') .data(function(d) { return zipSame(changes[d]); }) .enter() .append('li'); - li.append('strong').text(function(d) { - return (d.count > 1) ? d.type + 's ' : d.type + ' '; - }); + li.append('strong') + .text(function(d) { + return (d.count > 1) ? d.type + 's ' : d.type + ' '; + }); + li.append('span') .text(function(d) { return d.name; }) .attr('title', function(d) { return d.tagText; }); diff --git a/locale/en.js b/locale/en.js index 7581b5fcb..3902fb20e 100644 --- a/locale/en.js +++ b/locale/en.js @@ -186,6 +186,18 @@ locale.en = { reset: "reset" }, + commit: { + title: "Save Changes", + description_placeholder: "Brief description of your contributions", + upload_explanation: "The changes you upload as {user} will be visible on all maps that use OpenStreetMap data.", + save: "Save", + cancel: "Cancel", + warnings: "Warnings", + modified: "Modified", + deleted: "Deleted", + created: "Created" + }, + contributors: { list: "Viewing contributions by {users}", truncated_list: "Viewing contributions by {users} and {count} others" From 6bbea0784abd6bd639092cf2be626a30438ab68c Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 17:31:59 -0800 Subject: [PATCH 378/415] Fix locale includes --- test/index.html | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/test/index.html b/test/index.html index d9640c687..dcea8e28c 100644 --- a/test/index.html +++ b/test/index.html @@ -136,14 +136,15 @@ - - - - + + + + + + - From 5f0bef89e1b4039e2f57809706289dd68874c72f Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 17:38:55 -0800 Subject: [PATCH 379/415] Sync translations --- locale/da.js | 28 ++++++++++++++++++++-------- locale/de.js | 16 ++++++++++++++++ locale/es.js | 16 ++++++++++++++++ locale/fr.js | 16 ++++++++++++++++ locale/ja.js | 16 ++++++++++++++++ locale/lv.js | 16 ++++++++++++++++ locale/tr.js | 16 ++++++++++++++++ test/spec/translation.js | 3 ++- 8 files changed, 118 insertions(+), 9 deletions(-) diff --git a/locale/da.js b/locale/da.js index 29e23c6f2..770b7681e 100644 --- a/locale/da.js +++ b/locale/da.js @@ -3,25 +3,21 @@ locale.da = { add_area: { title: "Område", description: "Tilføj parker, bygninger, søer, eller andre områder til kortet.", - tail: "Klik på kortet for at indtegne et område fx en park, sø eller bygning.", - key: "A" + tail: "Klik på kortet for at indtegne et område fx en park, sø eller bygning." }, add_line: { title: "Linje", description: "Linjer kan være veje, gader eller stier selv kanaler kan være linjer.", - tail: "Klik på koret for at indtegne en vej, sti eller rute.", - key: "L" + tail: "Klik på koret for at indtegne en vej, sti eller rute." }, add_point: { title: "Punkt", description: "Restauranter, mindesmærker og postkasser er punkter.", - tail: "Klik på kortet for at tilføje et punkt.", - key: "P" + tail: "Klik på kortet for at tilføje et punkt." }, browse: { title: "Browse", - description: "Træk rundt og zoom på kortet.", - key: "B" + description: "Træk rundt og zoom på kortet." }, draw_area: { tail: "Klik her for at tilføje punkter til dit område. Click the first point to finish the area." @@ -172,6 +168,10 @@ locale.da = { no_results: "Kunne ikke finde '{name}'" }, + geolocate: { + title: "Show My Location" + }, + description: "Description", logout: "log ud", @@ -186,6 +186,18 @@ locale.da = { reset: "nulstill" }, + commit: { + title: "Save Changes", + description_placeholder: "Brief description of your contributions", + upload_explanation: "The changes you upload as {user} will be visible on all maps that use OpenStreetMap data.", + save: "Save", + cancel: "Cancel", + warnings: "Warnings", + modified: "Modified", + deleted: "Deleted", + created: "Created" + }, + contributors: { list: "Vis bidrag fra {users}", truncated_list: "Vis bidrag fra {users} og {count} andre" diff --git a/locale/de.js b/locale/de.js index f1f3ef850..0a74f78f6 100644 --- a/locale/de.js +++ b/locale/de.js @@ -168,12 +168,28 @@ locale.de = { no_results: "Der Ort '{name}' konnte nicht gefunden werden" }, + geolocate: { + title: "Show My Location" + }, + description: "Beschreibung", report_a_bug: "Programmfehler melden", logout: "Abmelden", + commit: { + title: "Save Changes", + description_placeholder: "Brief description of your contributions", + upload_explanation: "The changes you upload as {user} will be visible on all maps that use OpenStreetMap data.", + save: "Save", + cancel: "Cancel", + warnings: "Warnings", + modified: "Modified", + deleted: "Deleted", + created: "Created" + }, + contributors: { list: "Diese Kartenansicht enthält Beiträge von:", truncated_list: "Diese Kartenansicht enthält Beiträge von: {users} und {count} Anderen" diff --git a/locale/es.js b/locale/es.js index 0468405b5..1de8aa06c 100644 --- a/locale/es.js +++ b/locale/es.js @@ -168,6 +168,10 @@ locale.es = { no_results: "No se pudo encontrar el lugar llamado '{name}'" //"Couldn't locate a place named '{name}'" }, + geolocate: { + title: "Show My Location" + }, + description: "Descripción", //"Description", logout: "cerrar sesión", //"logout", @@ -182,6 +186,18 @@ locale.es = { reset: "reiniciar" //"reset" }, + commit: { + title: "Save Changes", + description_placeholder: "Brief description of your contributions", + upload_explanation: "The changes you upload as {user} will be visible on all maps that use OpenStreetMap data.", + save: "Save", + cancel: "Cancel", + warnings: "Warnings", + modified: "Modified", + deleted: "Deleted", + created: "Created" + }, + contributors: { list: "Viendo las contribuciones de usuarios {users}", //"Viewing contributions by {users}", truncated_list: "Viendo las contribuciones de {users} y {count} más" //"Viewing contributions by {users} and {count} others" diff --git a/locale/fr.js b/locale/fr.js index 4530924a0..02a23b7db 100644 --- a/locale/fr.js +++ b/locale/fr.js @@ -168,6 +168,10 @@ locale.fr = { no_results: "Impossible de localiser l'endroit nommé '{name}'" }, + geolocate: { + title: "Show My Location" + }, + description: "Déscription", logout: "Déconnexion", @@ -179,6 +183,18 @@ locale.fr = { truncated_list: "Consulter les contributions de {users} et {count} les autres" }, + commit: { + title: "Save Changes", + description_placeholder: "Brief description of your contributions", + upload_explanation: "The changes you upload as {user} will be visible on all maps that use OpenStreetMap data.", + save: "Save", + cancel: "Cancel", + warnings: "Warnings", + modified: "Modified", + deleted: "Deleted", + created: "Created" + }, + layerswitcher: { title: "Fond de carte", description: "Paramètres du fond de carte", diff --git a/locale/ja.js b/locale/ja.js index ada737bd1..d08e0a976 100644 --- a/locale/ja.js +++ b/locale/ja.js @@ -168,6 +168,10 @@ locale.ja = { no_results: "'{name}' という名称の地点が見つかりません" }, + geolocate: { + title: "Show My Location" + }, + description: "説明", logout: "ログアウト", @@ -182,6 +186,18 @@ locale.ja = { reset: "設定リセット" }, + commit: { + title: "Save Changes", + description_placeholder: "Brief description of your contributions", + upload_explanation: "The changes you upload as {user} will be visible on all maps that use OpenStreetMap data.", + save: "Save", + cancel: "Cancel", + warnings: "Warnings", + modified: "Modified", + deleted: "Deleted", + created: "Created" + }, + contributors: { list: "{users} による編集履歴を確認", truncated_list: "{users} とその他 {count} 人による編集履歴を表示" diff --git a/locale/lv.js b/locale/lv.js index fd8be7cfe..9ca9fd440 100644 --- a/locale/lv.js +++ b/locale/lv.js @@ -168,6 +168,10 @@ locale.lv = { no_results: "Nevar atrast vietu '{name}'" }, + geolocate: { + title: "Show My Location" + }, + description: "Apraksts", logout: "atslēgties", @@ -182,6 +186,18 @@ locale.lv = { reset: "Pārstatīt" }, + commit: { + title: "Save Changes", + description_placeholder: "Brief description of your contributions", + upload_explanation: "The changes you upload as {user} will be visible on all maps that use OpenStreetMap data.", + save: "Save", + cancel: "Cancel", + warnings: "Warnings", + modified: "Modified", + deleted: "Deleted", + created: "Created" + }, + contributors: { list: "{users} papildinājumi redzami", truncated_list: "{users} un {count} citu papildinājumi redzami" diff --git a/locale/tr.js b/locale/tr.js index a9b3e4b47..3b0602658 100644 --- a/locale/tr.js +++ b/locale/tr.js @@ -168,6 +168,10 @@ locale.tr = { no_results: "'{name}' ismindeki yer bulunamadı" }, + geolocate: { + title: "Show My Location" + }, + description: "Açıklama", logout: "Çıkış", @@ -182,6 +186,18 @@ locale.tr = { reset: "Sıfırla" }, + commit: { + title: "Save Changes", + description_placeholder: "Brief description of your contributions", + upload_explanation: "The changes you upload as {user} will be visible on all maps that use OpenStreetMap data.", + save: "Save", + cancel: "Cancel", + warnings: "Warnings", + modified: "Modified", + deleted: "Deleted", + created: "Created" + }, + contributors: { list: "{users} tarafından yapılan katkılar görünmektedir", truncated_list: "{users} ve diğer {count} tarafından yapılan katkılar görünmektedir" diff --git a/test/spec/translation.js b/test/spec/translation.js index 963f59fe0..41f2e07df 100644 --- a/test/spec/translation.js +++ b/test/spec/translation.js @@ -27,7 +27,8 @@ describe('translations', function() { var allkeys = _.flatten(_.values(languageKeys)); _.forEach(languageKeys, function(l, k) { - expect(_.difference(allkeys, l)).to.eql([]); + var diff = _.difference(allkeys, l).join(", "); + expect(diff).to.equal(""); }); }); From d92d87ebf5b9516b969991f05f6bf4affd3615a3 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 19:55:09 -0800 Subject: [PATCH 380/415] Organize translations --- js/id/ui.js | 2 +- js/id/ui/inspector.js | 4 +- js/id/ui/save.js | 8 ++-- js/id/ui/tag_reference.js | 2 +- locale/da.js | 80 ++++++++++++++++---------------- locale/de.js | 82 ++++++++++++++++----------------- locale/en.js | 96 +++++++++++++++++++-------------------- locale/es.js | 96 +++++++++++++++++++-------------------- locale/fr.js | 90 ++++++++++++++++++------------------ locale/ja.js | 96 +++++++++++++++++++-------------------- locale/lv.js | 96 +++++++++++++++++++-------------------- locale/tr.js | 96 +++++++++++++++++++-------------------- 12 files changed, 366 insertions(+), 382 deletions(-) diff --git a/js/id/ui.js b/js/id/ui.js index 48e5682bf..4e6ec4c24 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -107,7 +107,7 @@ iD.ui = function(context) { window.onbeforeunload = function() { history.save(); - if (history.hasChanges()) return t('unsaved_changes'); + if (history.hasChanges()) return t('save.unsaved_changes'); }; d3.select(window).on('resize.editor', function() { diff --git a/js/id/ui/inspector.js b/js/id/ui/inspector.js index 173594101..1b1c78be6 100644 --- a/js/id/ui/inspector.js +++ b/js/id/ui/inspector.js @@ -22,7 +22,7 @@ iD.ui.Inspector = function() { .attr('class', 'inspector-inner tag-wrap fillL2'); inspectorwrap.append('h4') - .text(t('edit_tags')); + .text(t('inspector.edit_tags')); tagList = inspectorwrap.append('ul'); @@ -65,7 +65,7 @@ iD.ui.Inspector = function() { .attr('class', 'apply action') .on('click', apply); - inspectorButton.append('span').attr('class','label').text(t('okay')); + inspectorButton.append('span').attr('class','label').text(t('inspector.okay')); var minorButtons = selection.append('div').attr('class','minor-buttons fl'); diff --git a/js/id/ui/save.js b/js/id/ui/save.js index b4eed1dd0..8b0ef57ce 100644 --- a/js/id/ui/save.js +++ b/js/id/ui/save.js @@ -30,7 +30,7 @@ iD.ui.Save = function(context) { context.container().select('.shaded') .remove(); - var loading = iD.ui.loading(context.container(), t('uploading_changes'), true); + var loading = iD.ui.loading(context.container(), t('save.uploading'), true); connection.putChangeset( history.changes(), @@ -44,7 +44,7 @@ iD.ui.Save = function(context) { var desc = iD.ui.confirm() .select('.description'); desc.append('h2') - .text(t('save_error')); + .text(t('save.error')); desc.append('p').text(err.responseText); } else { success(e, changeset_id); @@ -81,11 +81,11 @@ iD.ui.Save = function(context) { .call(bootstrap.tooltip() .placement('bottom') .html(true) - .title(iD.ui.tooltipHtml(t('save_help'), key))); + .title(iD.ui.tooltipHtml(t('save.help'), key))); button.append('span') .attr('class', 'label') - .text(t('save')); + .text(t('save.title')); button.append('span') .attr('class', 'count'); diff --git a/js/id/ui/tag_reference.js b/js/id/ui/tag_reference.js index 6b7b9fb9f..dc583b932 100644 --- a/js/id/ui/tag_reference.js +++ b/js/id/ui/tag_reference.js @@ -24,7 +24,7 @@ iD.ui.tagReference = function(selection) { referenceBody .append('h5') - .text(t('description')); + .text(t('tag_reference.description')); if (selection.datum().image) { referenceBody diff --git a/locale/da.js b/locale/da.js index 770b7681e..17b75c340 100644 --- a/locale/da.js +++ b/locale/da.js @@ -128,54 +128,14 @@ locale.da = { } }, - validations: { - untagged_point: "Untagged point which is not part of a line or area", - untagged_line: "Untagged line", - untagged_area: "Untagged area", - tag_suggests_area: "The tag {tag} suggests line should be area, but it is not an area", - deprecated_tags: "Deprecated tags: {tags}" - }, - - save: "Gem", - unsaved_changes: "Du har ændringer der ikke er gemt endnu", - save_help: "Gem ændringer til OpenStreetMap gør dem synlige for andre brugere", - no_changes: "Du har ingen ændringer til at gemme endnu.", - save_error: "Der skete en fejl da du prøvede at gemme", - uploading_changes: "Gemmer nu ændringer til OpenStreetMap.", - just_edited: "Du har lige rettede i OpenStreetMap!", - okay: "Ok", - nothing_to_undo: "Nothing to undo.", nothing_to_redo: "Nothing to redo.", + just_edited: "Du har lige rettede i OpenStreetMap!", browser_notice: "This editor is supported in Firefox, Chrome, Safari, Opera, and Internet Explorer 9 and above. Please upgrade your browser or use Potlatch 2 to edit the map.", - - inspector: { - no_documentation_combination: "Der er ingen dokumentation for denne tag kombination", - no_documentation_key: "Der er ingen dokumenation tilgængelig for denne nøgle", - new_tag: "Nyt Tag" - }, - view_on_osm: "Vis på OSM", - zoom_in_edit: "zoom ind for at rette kortet", - - edit_tags: "Ret tags", - - geocoder: { - title: "Find et sted", - placeholder: "find et sted", - no_results: "Kunne ikke finde '{name}'" - }, - - geolocate: { - title: "Show My Location" - }, - - description: "Description", - logout: "log ud", - report_a_bug: "report a bug", layerswitcher: { @@ -203,13 +163,51 @@ locale.da = { truncated_list: "Vis bidrag fra {users} og {count} andre" }, + geocoder: { + title: "Find et sted", + placeholder: "find et sted", + no_results: "Kunne ikke finde '{name}'" + }, + + geolocate: { + title: "Show My Location" + }, + + inspector: { + no_documentation_combination: "Der er ingen dokumentation for denne tag kombination", + no_documentation_key: "Der er ingen dokumenation tilgængelig for denne nøgle", + new_tag: "Nyt Tag", + edit_tags: "Ret tags", + okay: "Ok" + }, + + save: { + title: "Gem", + help: "Gem ændringer til OpenStreetMap gør dem synlige for andre brugere", + error: "Der skete en fejl da du prøvede at gemme", + uploading: "Gemmer nu ændringer til OpenStreetMap.", + unsaved_changes: "Du har ændringer der ikke er gemt endnu", + }, + source_switch: { live: "live", dev: "dev" }, + tag_reference: { + description: "Description" + }, + zoom: { in: "Zoom ind", out: "Zoom ud" + }, + + validations: { + untagged_point: "Untagged point which is not part of a line or area", + untagged_line: "Untagged line", + untagged_area: "Untagged area", + tag_suggests_area: "The tag {tag} suggests line should be area, but it is not an area", + deprecated_tags: "Deprecated tags: {tags}" } }; diff --git a/locale/de.js b/locale/de.js index 0a74f78f6..60f4c6155 100644 --- a/locale/de.js +++ b/locale/de.js @@ -128,55 +128,15 @@ locale.de = { } }, - validations: { - untagged_point: "Punkt ohne Attribute, der kein Teil einer Linie oder Fläche ist", - untagged_line: "Linie ohne Attribute", - untagged_area: "Fläche ohne Attribute", - tag_suggests_area: "Das Attribut {tag} suggeriert eine Fläche, ist aber keine Fläche", - deprecated_tags: "Veralterte Attribute: {tags}" - }, - - save: "Speichern", - unsaved_changes: "Ungespeicherte Änderugen vorhanden", - save_help: "Speichere Änderungen auf OpenStreetMap, um diese für andere Nutzer sichtbar zu machen", - no_changes: "Keine Änderungen zum Speichern vorhanden.", - save_error: "Beim Speichern ist ein Fehler aufgetreten", - uploading_changes: "Änderungen werden zu OpenStreetMap hochgeladen.", - just_edited: "Sie haben gerade OpenStreetMap editiert!", - okay: "OK", - nothing_to_undo: "Nichts zum Rückgängigmachen.", nothing_to_redo: "Nichts zum Wiederherstellen.", + just_edited: "Sie haben gerade OpenStreetMap editiert!", browser_notice: "Dieser Editor wird von Firefox, Chrome, Safari, Opera, und Internet Explorer (Version 9 und höher) unterstzützt. Bitte aktualisieren Sie Ihren Browser oder nutzen Sie Potlatch 2, um die Karte zu modifizieren.", - - inspector: { - no_documentation_combination: "Für dieses Attribut ist keine Dokumentation verfügbar.", - no_documentation_key: "Für dises Schlüsselwort ist keine Dokumentation verfügbar", - new_tag: "Neues Attribut" - }, - view_on_osm: "Auf OSM anschauen", - zoom_in_edit: "Hineinzoomen, um die Karte zu bearbeiten", - - edit_tags: "Attribute bearbeiten", - - geocoder: { - title: "Suche einen Ort", - placeholder: "suche einen Ort", - no_results: "Der Ort '{name}' konnte nicht gefunden werden" - }, - - geolocate: { - title: "Show My Location" - }, - - description: "Beschreibung", - - report_a_bug: "Programmfehler melden", - logout: "Abmelden", + report_a_bug: "Programmfehler melden", commit: { title: "Save Changes", @@ -195,6 +155,24 @@ locale.de = { truncated_list: "Diese Kartenansicht enthält Beiträge von: {users} und {count} Anderen" }, + geocoder: { + title: "Suche einen Ort", + placeholder: "suche einen Ort", + no_results: "Der Ort '{name}' konnte nicht gefunden werden" + }, + + geolocate: { + title: "Show My Location" + }, + + inspector: { + no_documentation_combination: "Für dieses Attribut ist keine Dokumentation verfügbar.", + no_documentation_key: "Für dises Schlüsselwort ist keine Dokumentation verfügbar", + new_tag: "Neues Attribut", + edit_tags: "Attribute bearbeiten", + okay: "OK" + }, + layerswitcher: { title: "Hintergrund", description: "Hintergrundeinstellungen", @@ -203,11 +181,31 @@ locale.de = { reset: "Zurücksetzen" }, + save: { + title: "Speichern", + help: "Speichere Änderungen auf OpenStreetMap, um diese für andere Nutzer sichtbar zu machen", + error: "Beim Speichern ist ein Fehler aufgetreten", + uploading: "Änderungen werden zu OpenStreetMap hochgeladen.", + unsaved_changes: "Ungespeicherte Änderugen vorhanden", + }, + source_switch: { live: "live", dev: "dev" }, + tag_reference: { + description: "Beschreibung" + }, + + validations: { + untagged_point: "Punkt ohne Attribute, der kein Teil einer Linie oder Fläche ist", + untagged_line: "Linie ohne Attribute", + untagged_area: "Fläche ohne Attribute", + tag_suggests_area: "Das Attribut {tag} suggeriert eine Fläche, ist aber keine Fläche", + deprecated_tags: "Veralterte Attribute: {tags}" + }, + zoom: { in: "Hineinzoomen", out: "Herauszoomen" diff --git a/locale/en.js b/locale/en.js index 3902fb20e..9060f7b66 100644 --- a/locale/en.js +++ b/locale/en.js @@ -128,64 +128,16 @@ locale.en = { } }, - validations: { - untagged_point: "Untagged point which is not part of a line or area", - untagged_line: "Untagged line", - untagged_area: "Untagged area", - tag_suggests_area: "The tag {tag} suggests line should be area, but it is not an area", - deprecated_tags: "Deprecated tags: {tags}" - }, - - save: "Save", - unsaved_changes: "You have unsaved changes", - save_help: "Save changes to OpenStreetMap, making them visible to other users", - no_changes: "You don't have any changes to save.", - save_error: "An error occurred while trying to save", - uploading_changes: "Uploading changes to OpenStreetMap.", - just_edited: "You Just Edited OpenStreetMap!", - okay: "Okay", - nothing_to_undo: "Nothing to undo.", nothing_to_redo: "Nothing to redo.", + just_edited: "You Just Edited OpenStreetMap!", browser_notice: "This editor is supported in Firefox, Chrome, Safari, Opera, and Internet Explorer 9 and above. Please upgrade your browser or use Potlatch 2 to edit the map.", - - inspector: { - no_documentation_combination: "There is no documentation available for this tag combination", - no_documentation_key: "There is no documentation available for this key", - new_tag: "New Tag" - }, - view_on_osm: "View on OSM", - zoom_in_edit: "zoom in to edit the map", - - edit_tags: "Edit tags", - - geocoder: { - title: "Find A Place", - placeholder: "find a place", - no_results: "Couldn't locate a place named '{name}'" - }, - - geolocate: { - title: "Show My Location" - }, - - description: "Description", - logout: "logout", - report_a_bug: "report a bug", - layerswitcher: { - title: "Background", - description: "Background Settings", - percent_brightness: "{opacity}% brightness", - fix_misalignment: "Fix misalignment", - reset: "reset" - }, - commit: { title: "Save Changes", description_placeholder: "Brief description of your contributions", @@ -203,11 +155,57 @@ locale.en = { truncated_list: "Viewing contributions by {users} and {count} others" }, + geocoder: { + title: "Find A Place", + placeholder: "find a place", + no_results: "Couldn't locate a place named '{name}'" + }, + + geolocate: { + title: "Show My Location" + }, + + inspector: { + no_documentation_combination: "There is no documentation available for this tag combination", + no_documentation_key: "There is no documentation available for this key", + new_tag: "New Tag", + edit_tags: "Edit tags", + okay: "Okay" + }, + + layerswitcher: { + title: "Background", + description: "Background Settings", + percent_brightness: "{opacity}% brightness", + fix_misalignment: "Fix misalignment", + reset: "reset" + }, + + save: { + title: "Save", + help: "Save changes to OpenStreetMap, making them visible to other users", + error: "An error occurred while trying to save", + uploading: "Uploading changes to OpenStreetMap.", + unsaved_changes: "You have unsaved changes" + }, + source_switch: { live: "live", dev: "dev" }, + tag_reference: { + description: 'Description' + }, + + validations: { + untagged_point: "Untagged point which is not part of a line or area", + untagged_line: "Untagged line", + untagged_area: "Untagged area", + tag_suggests_area: "The tag {tag} suggests line should be area, but it is not an area", + deprecated_tags: "Deprecated tags: {tags}" + }, + zoom: { in: "Zoom In", out: "Zoom Out" diff --git a/locale/es.js b/locale/es.js index 1de8aa06c..c440429db 100644 --- a/locale/es.js +++ b/locale/es.js @@ -128,64 +128,16 @@ locale.es = { } }, - validations: { - untagged_point: "Punto sin etiquetar que no es parte de una línea ni zona.", //"Untagged point which is not part of a line or area", - untagged_line: "Línea sin etiquetar", //"Untagged line", - untagged_area: "Zona sin etiquetar", //"Untagged area", - tag_suggests_area: "La etiqueta {tag} sugiere que esta línea debería ser una zona, pero no lo es.", //"The tag {tag} suggests line should be area, but it is not an area", - deprecated_tags: "Etiquetas obsoletas: {tags}" //"Deprecated tags: {tags}" - }, - - save: "Guardar", //"Save", - unsaved_changes: "Tienes cambios sin guardar", //"You have unsaved changes", - save_help: "Guardar los cambios en OpenStreetMap haciéndolos visibles a otros usuarios", //"Save changes to OpenStreetMap, making them visible to other users", - no_changes: "No tienes cambios sin guardar", //"You don't have any changes to save.", - save_error: "Ha ocurrido un error tratando de guardar", //"An error occurred while trying to save", - uploading_changes: "Subiendo cambios a OpenStreetMap", //"Uploading changes to OpenStreetMap.", - just_edited: "Acabas de editar OpenStreetMap!", //"You Just Edited OpenStreetMap!", - okay: "OK", //"Okay", - nothing_to_undo: "Nada para deshacer", //"Nothing to undo.", nothing_to_redo: "Nada para rehacer", //"Nothing to redo.", + just_edited: "Acabas de editar OpenStreetMap!", //"You Just Edited OpenStreetMap!", browser_notice: "Este editor soporta Firefox, Chrome, Safari, Opera e Internet Explorer 9 o superior. Por favor actualiza tu navegador o utiliza Potlatch 2 para editar el mapa.", //"This editor is supported in Firefox, Chrome, Safari, Opera, and Internet Explorer 9 and above. Please upgrade your browser or use Potlatch 2 to edit the map.", - - inspector: { - no_documentation_combination: "No hay documentación disponible para esta combinación de etiquetas", //"This is no documentation available for this tag combination", - no_documentation_key: "No hay documentación disponible para esta tecla", //"This is no documentation available for this key", - new_tag: "Nueve etiqueta" //"New Tag" - }, - view_on_osm: "Ver en OSM", //"View on OSM", - zoom_in_edit: "acercar para editar el mapa", //"zoom in to edit the map", - - edit_tags: "Editar etiquetas", //"Edit tags", - - geocoder: { - title: "Encontrar un lugar", //"Find A Place", - placeholder: "encontrar un lugar", //"find a place", - no_results: "No se pudo encontrar el lugar llamado '{name}'" //"Couldn't locate a place named '{name}'" - }, - - geolocate: { - title: "Show My Location" - }, - - description: "Descripción", //"Description", - logout: "cerrar sesión", //"logout", - report_a_bug: "reportar un error", //"report a bug", - layerswitcher: { - title: "Fondo", //"Background", - description: "Configuración de fondo", //"Background Settings", - percent_brightness: "{opacity}% brillo", //"{opacity}% brightness", - fix_misalignment: "Arreglar alineamiento", //"Fix misalignment", - reset: "reiniciar" //"reset" - }, - commit: { title: "Save Changes", description_placeholder: "Brief description of your contributions", @@ -203,11 +155,57 @@ locale.es = { truncated_list: "Viendo las contribuciones de {users} y {count} más" //"Viewing contributions by {users} and {count} others" }, + geocoder: { + title: "Encontrar un lugar", //"Find A Place", + placeholder: "encontrar un lugar", //"find a place", + no_results: "No se pudo encontrar el lugar llamado '{name}'" //"Couldn't locate a place named '{name}'" + }, + + geolocate: { + title: "Show My Location" + }, + + inspector: { + no_documentation_combination: "No hay documentación disponible para esta combinación de etiquetas", //"This is no documentation available for this tag combination", + no_documentation_key: "No hay documentación disponible para esta tecla", //"This is no documentation available for this key", + new_tag: "Nueve etiqueta", //"New Tag" + edit_tags: "Editar etiquetas", //"Edit tags", + okay: "OK" //"Okay", + }, + + layerswitcher: { + title: "Fondo", //"Background", + description: "Configuración de fondo", //"Background Settings", + percent_brightness: "{opacity}% brillo", //"{opacity}% brightness", + fix_misalignment: "Arreglar alineamiento", //"Fix misalignment", + reset: "reiniciar" //"reset" + }, + + save: { + title: "Guardar", //"Save", + help: "Guardar los cambios en OpenStreetMap haciéndolos visibles a otros usuarios", //"Save changes to OpenStreetMap, making them visible to other users", + error: "Ha ocurrido un error tratando de guardar", //"An error occurred while trying to save", + uploading: "Subiendo cambios a OpenStreetMap", //"Uploading changes to OpenStreetMap.", + unsaved_changes: "Tienes cambios sin guardar" //"You have unsaved changes", + }, + source_switch: { live: "en vivo", //"live", dev: "dev" }, + tag_reference: { + description: "Descripción" //"Description", + }, + + validations: { + untagged_point: "Punto sin etiquetar que no es parte de una línea ni zona.", //"Untagged point which is not part of a line or area", + untagged_line: "Línea sin etiquetar", //"Untagged line", + untagged_area: "Zona sin etiquetar", //"Untagged area", + tag_suggests_area: "La etiqueta {tag} sugiere que esta línea debería ser una zona, pero no lo es.", //"The tag {tag} suggests line should be area, but it is not an area", + deprecated_tags: "Etiquetas obsoletas: {tags}" //"Deprecated tags: {tags}" + }, + zoom: { in: "Aumentar", // "Zoom In", out: "Alejar" //"Zoom Out", diff --git a/locale/fr.js b/locale/fr.js index 02a23b7db..d6c36189a 100644 --- a/locale/fr.js +++ b/locale/fr.js @@ -128,61 +128,16 @@ locale.fr = { } }, - validations: { - untagged_point: "Point sans aucun tag ne faisant partie ni d'une ligne, ni d'un polygone", - untagged_line: "Ligne sans aucun tag", - untagged_area: "Polygone sans aucun tag", - tag_suggests_area: "Ce tag {tag} suppose que cette ligne devrait être un polygone, or ce n'est pas le cas", - deprecated_tags: "Tags obsolètes : {tags}" - }, - - save: "Sauvegarder", - unsaved_changes: "Vous avez des modifications non enregistrées", - save_help: "Envoie des modifications au serveyr OpenStreetMap afin qu'elles soient visibles par les autres contributeurs.", - no_changes: "Vous n'avez aucune modification à enregistrer.", - save_error: "Une erreur est survenue lors de l'enregistrement des données", - uploading_changes: "Envoie des modifications vers OpenStreetMap.", - just_edited: "Vous venez de participer à OpenStreetMap!", - okay: "Okay", - nothing_to_undo: "Rien à annuler.", nothing_to_redo: "Rien à refaire.", + just_edited: "Vous venez de participer à OpenStreetMap!", browser_notice: "Les navigateurs supportés par cet éditeur sont : Firefox, Chrome, Safari, Opera et Internet Explorer (version 9 et supérieures). Pour éditer la carte, veuillez mettre à jour votre navigateur ou utiliser Potlatch 2.", - - inspector: { - no_documentation_combination: "Aucune documentation n'est disponible pour cette combinaison de tag", - no_documentation_key: "Aucune documentation n'est disponible pour cette clé", - new_tag: "Nouveau tag" - }, - view_on_osm: "Consulter dans OSM", - zoom_in_edit: "Zoomer pour modifier la carte", - - edit_tags: "Editer les tags", - - geocoder: { - title: "Trouver un emplacement", - placeholder: "Trouver un endroit", - no_results: "Impossible de localiser l'endroit nommé '{name}'" - }, - - geolocate: { - title: "Show My Location" - }, - - description: "Déscription", - logout: "Déconnexion", - report_a_bug: "Signaler un bug", - contributors: { - list: "Consulter les contributions de {users}", - truncated_list: "Consulter les contributions de {users} et {count} les autres" - }, - commit: { title: "Save Changes", description_placeholder: "Brief description of your contributions", @@ -195,6 +150,29 @@ locale.fr = { created: "Created" }, + contributors: { + list: "Consulter les contributions de {users}", + truncated_list: "Consulter les contributions de {users} et {count} les autres" + }, + + geocoder: { + title: "Trouver un emplacement", + placeholder: "Trouver un endroit", + no_results: "Impossible de localiser l'endroit nommé '{name}'" + }, + + geolocate: { + title: "Show My Location" + }, + + inspector: { + no_documentation_combination: "Aucune documentation n'est disponible pour cette combinaison de tag", + no_documentation_key: "Aucune documentation n'est disponible pour cette clé", + new_tag: "Nouveau tag", + edit_tags: "Editer les tags", + okay: "Okay" + }, + layerswitcher: { title: "Fond de carte", description: "Paramètres du fond de carte", @@ -203,11 +181,31 @@ locale.fr = { reset: "reset" }, + save: { + title: "Sauvegarder", + help: "Envoie des modifications au serveyr OpenStreetMap afin qu'elles soient visibles par les autres contributeurs.", + error: "Une erreur est survenue lors de l'enregistrement des données", + uploading: "Envoie des modifications vers OpenStreetMap.", + unsaved_changes: "Vous avez des modifications non enregistrées" + }, + source_switch: { live: "live", dev: "dev" }, + tag_reference: { + description: "Déscription" + }, + + validations: { + untagged_point: "Point sans aucun tag ne faisant partie ni d'une ligne, ni d'un polygone", + untagged_line: "Ligne sans aucun tag", + untagged_area: "Polygone sans aucun tag", + tag_suggests_area: "Ce tag {tag} suppose que cette ligne devrait être un polygone, or ce n'est pas le cas", + deprecated_tags: "Tags obsolètes : {tags}" + }, + zoom: { in: "Zoomer", out: "Dézoomer" diff --git a/locale/ja.js b/locale/ja.js index d08e0a976..61b76ace5 100644 --- a/locale/ja.js +++ b/locale/ja.js @@ -128,64 +128,16 @@ locale.ja = { } }, - validations: { - untagged_point: "ポイントにタグが付与されておらず、ラインやエリアの一部でもありません", - untagged_line: "ラインにタグが付与されていません", - untagged_area: "エリアにタグが付与されていません", - tag_suggests_area: "ラインに {tag} タグが付与されています。エリアで描かれるべきです", - deprecated_tags: "タグの重複: {tags}" - }, - - save: "Save", - unsaved_changes: "変更が保存されていません", - save_help: "変更点をOpenStreetMapに保存し、他ユーザが確認できるようにします", - no_changes: "変更点がありません", - save_error: "データ保存中にエラーが発生しました", - uploading_changes: "変更点をOpenStreetMapへアップロードしています", - just_edited: "OpenStreetMap編集完了!", - okay: "OK", - nothing_to_undo: "やり直す変更点がありません", nothing_to_redo: "やり直した変更点がありません", + just_edited: "OpenStreetMap編集完了!", browser_notice: "このエディタは Firefox, Chrome, Safari, Opera, および Internet Explorer 9 以上をサポートしています。ブラウザのバージョンを更新するか、Potlatch 2を使用して編集してください", - - inspector: { - no_documentation_combination: "このタグの組み合わせに関する説明文はありません", - no_documentation_key: "このキーに対する説明文はありません", - new_tag: "新規タグ" - }, - view_on_osm: "OSMで確認", - zoom_in_edit: "編集するにはさらに地図を拡大してください", - - edit_tags: "タグを編集", - - geocoder: { - title: "特定地点を検索", - placeholder: "地点を検索", - no_results: "'{name}' という名称の地点が見つかりません" - }, - - geolocate: { - title: "Show My Location" - }, - - description: "説明", - logout: "ログアウト", - report_a_bug: "バグを報告", - layerswitcher: { - title: "背景画像", - description: "背景画像設定", - percent_brightness: "{opacity}% 輝度", - fix_misalignment: "背景画像を移動", - reset: "設定リセット" - }, - commit: { title: "Save Changes", description_placeholder: "Brief description of your contributions", @@ -203,11 +155,57 @@ locale.ja = { truncated_list: "{users} とその他 {count} 人による編集履歴を表示" }, + geocoder: { + title: "特定地点を検索", + placeholder: "地点を検索", + no_results: "'{name}' という名称の地点が見つかりません" + }, + + geolocate: { + title: "Show My Location" + }, + + inspector: { + no_documentation_combination: "このタグの組み合わせに関する説明文はありません", + no_documentation_key: "このキーに対する説明文はありません", + new_tag: "新規タグ", + edit_tags: "タグを編集", + okay: "OK" + }, + + layerswitcher: { + title: "背景画像", + description: "背景画像設定", + percent_brightness: "{opacity}% 輝度", + fix_misalignment: "背景画像を移動", + reset: "設定リセット" + }, + + save: { + title: "Save", + help: "変更点をOpenStreetMapに保存し、他ユーザが確認できるようにします", + error: "データ保存中にエラーが発生しました", + uploading: "変更点をOpenStreetMapへアップロードしています", + unsaved_changes: "変更が保存されていません" + }, + source_switch: { live: "本番サーバ", dev: "開発サーバ" }, + tag_reference: { + description: "説明" + }, + + validations: { + untagged_point: "ポイントにタグが付与されておらず、ラインやエリアの一部でもありません", + untagged_line: "ラインにタグが付与されていません", + untagged_area: "エリアにタグが付与されていません", + tag_suggests_area: "ラインに {tag} タグが付与されています。エリアで描かれるべきです", + deprecated_tags: "タグの重複: {tags}" + }, + zoom: { in: "ズームイン", out: "ズームアウト" diff --git a/locale/lv.js b/locale/lv.js index 9ca9fd440..3a799c2a9 100644 --- a/locale/lv.js +++ b/locale/lv.js @@ -128,64 +128,16 @@ locale.lv = { } }, - validations: { - untagged_point: "Neapzīmēts punkts", - untagged_line: "Neapzīmēta līnija", - untagged_area: "Neapzīmēts apgabals", - tag_suggests_area: "Apzīmējums {tag} parasti tiek lietots apgabaliem, bet objekts nav apgabals", - deprecated_tags: "Novecojuši apzīmējumi: {tags}" - }, - - save: "Saglabāt", - unsaved_changes: "Jums ir nesaglabātas izmaiņas", - save_help: "Saglabā izmaiņas, padarot tās redzamas citiem", - no_changes: "Jums nav izmaiņu, ko saglabāt", - save_error: "Kļūda. Nevarēja saglabāt maiņas", - uploading_changes: "Augšupielādē", - just_edited: "Jūs nupat rediģējāt OpenStreetMap", - okay: "Labi", - nothing_to_undo: "Nav nekā, ko atcelt", nothing_to_redo: "Nav nekā, ko atsaukt", + just_edited: "Jūs nupat rediģējāt OpenStreetMap", browser_notice: "Šis redaktors tiek atbalstīts ar Firefox, Chrome, Safari, Opera, un Internet Explorer 9 un jaunāku. Lūdzu, atjauniniet savu pārlūkprogrammu vai izmantojiet Potlatch 2 to kartes rediģēšanai", - - inspector: { - no_documentation_combination: "Šai apzīmējumu kombinācijai nav piejama dokumetācija", - no_documentation_key: "There is no documentation available for this key", - new_tag: "Jauns apzīmējums" - }, - view_on_osm: "Apskatīt OSM lapu", - zoom_in_edit: "pietuviniet, lai rediģētu karti", - - edit_tags: "Rediģēt apzīmējumus", - - geocoder: { - title: "Atrast vietu", - placeholder: "meklē vietu", - no_results: "Nevar atrast vietu '{name}'" - }, - - geolocate: { - title: "Show My Location" - }, - - description: "Apraksts", - logout: "atslēgties", - report_a_bug: "ziņot par kļūdu", - layerswitcher: { - title: "Fons", - description: "Fona iestatījumi", - percent_brightness: "{opacity}% gaišums", - fix_misalignment: "Labot fona nolīdzināšanu", - reset: "Pārstatīt" - }, - commit: { title: "Save Changes", description_placeholder: "Brief description of your contributions", @@ -203,11 +155,57 @@ locale.lv = { truncated_list: "{users} un {count} citu papildinājumi redzami" }, + geocoder: { + title: "Atrast vietu", + placeholder: "meklē vietu", + no_results: "Nevar atrast vietu '{name}'" + }, + + geolocate: { + title: "Show My Location" + }, + + inspector: { + no_documentation_combination: "Šai apzīmējumu kombinācijai nav piejama dokumetācija", + no_documentation_key: "There is no documentation available for this key", + new_tag: "Jauns apzīmējums", + edit_tags: "Rediģēt apzīmējumus", + okay: "Labi" + }, + + layerswitcher: { + title: "Fons", + description: "Fona iestatījumi", + percent_brightness: "{opacity}% gaišums", + fix_misalignment: "Labot fona nolīdzināšanu", + reset: "Pārstatīt" + }, + + save: { + title: "Saglabāt", + help: "Saglabā izmaiņas, padarot tās redzamas citiem", + error: "Kļūda. Nevarēja saglabāt maiņas", + uploading: "Augšupielādē", + unsaved_changes: "Jums ir nesaglabātas izmaiņas" + }, + source_switch: { live: "live", dev: "dev" }, + tag_reference: { + description: "Apraksts" + }, + + validations: { + untagged_point: "Neapzīmēts punkts", + untagged_line: "Neapzīmēta līnija", + untagged_area: "Neapzīmēts apgabals", + tag_suggests_area: "Apzīmējums {tag} parasti tiek lietots apgabaliem, bet objekts nav apgabals", + deprecated_tags: "Novecojuši apzīmējumi: {tags}" + }, + zoom: { in: "Pietuvināt", out: "Attālināt" diff --git a/locale/tr.js b/locale/tr.js index 3b0602658..f212ca373 100644 --- a/locale/tr.js +++ b/locale/tr.js @@ -128,64 +128,16 @@ locale.tr = { } }, - validations: { - untagged_point: "Herhangi bir çizgi ya da alana bağlantısı olmayan ve etiketlenmemiş bir nokta.", - untagged_line: "Etiketlenmemiş çizgi", - untagged_area: "Etiketlenmemiş alan", - tag_suggests_area: "{tag} etiketi buranın alan olmasını tavsiye ediyor ama alan değil.", - deprecated_tags: "Kullanımdan kaldırılmış etiket : {tags}" - }, - - save: "Kaydet", - unsaved_changes: "Kaydedilmemiş değişiklikleriniz var", - save_help: "Diğer kullanıcıların yaptığınız değişiklikleri görmesi için OpenStreetMap'e kaydediniz", - no_changes: "Kaydedecek hiçbir değişikliğiniz yok", - save_error: "Kaydederken bir hata oluştu", - uploading_changes: "Değişiklikleriniz OpenStreetMap'e gönderiliyor.", - just_edited: "Şu an OpenStreetMap'de bir değişiklik yaptınız!", - okay: "Tamam", - nothing_to_undo: "Geri alınacak birşey yok.", nothing_to_redo: "Tekrar yapılacak birşey yok.", + just_edited: "Şu an OpenStreetMap'de bir değişiklik yaptınız!", browser_notice: "Bu editör sadece Firefox, Chrome, Safari, Opera ile Internet Explorer 9 ve üstü tarayıcılarda çalışmaktadır. Lütfen tarayınıcı güncelleyin ya da Potlatch 2'yi kullanarak haritada güncelleme yapınız.", - - inspector: { - no_documentation_combination: "Bu etiket kombinasyonu için dökümantasyon bulunmamaktadır.", - no_documentation_key: "Bu anahtar için dökümantasyon bulunmamaktadır.", - new_tag: "Yeni Etiket" - }, - view_on_osm: "OSM üstünde Gör", - zoom_in_edit: "Güncelleme yapmak için haritada yakınlaşmalısınız", - - edit_tags: "Etiketleri güncelle", - - geocoder: { - title: "Bir Yer Bul", - placeholder: "bir yer bul", - no_results: "'{name}' ismindeki yer bulunamadı" - }, - - geolocate: { - title: "Show My Location" - }, - - description: "Açıklama", - logout: "Çıkış", - report_a_bug: "Hata rapor et", - layerswitcher: { - title: "Arkaplan", - description: "Arkaplan Ayarları", - percent_brightness: "{opacity}% parlaklık", - fix_misalignment: "Yanlış hizalamayı düzelt", - reset: "Sıfırla" - }, - commit: { title: "Save Changes", description_placeholder: "Brief description of your contributions", @@ -203,11 +155,57 @@ locale.tr = { truncated_list: "{users} ve diğer {count} tarafından yapılan katkılar görünmektedir" }, + geocoder: { + title: "Bir Yer Bul", + placeholder: "bir yer bul", + no_results: "'{name}' ismindeki yer bulunamadı" + }, + + geolocate: { + title: "Show My Location" + }, + + inspector: { + no_documentation_combination: "Bu etiket kombinasyonu için dökümantasyon bulunmamaktadır.", + no_documentation_key: "Bu anahtar için dökümantasyon bulunmamaktadır.", + new_tag: "Yeni Etiket", + edit_tags: "Etiketleri güncelle", + okay: "Tamam" + }, + + layerswitcher: { + title: "Arkaplan", + description: "Arkaplan Ayarları", + percent_brightness: "{opacity}% parlaklık", + fix_misalignment: "Yanlış hizalamayı düzelt", + reset: "Sıfırla" + }, + + save: { + title: "Kaydet", + help: "Diğer kullanıcıların yaptığınız değişiklikleri görmesi için OpenStreetMap'e kaydediniz", + error: "Kaydederken bir hata oluştu", + uploading: "Değişiklikleriniz OpenStreetMap'e gönderiliyor.", + unsaved_changes: "Kaydedilmemiş değişiklikleriniz var" + }, + source_switch: { live: "canlı", dev: "geliştirme" }, + tag_reference: { + description: "Açıklama" + }, + + validations: { + untagged_point: "Herhangi bir çizgi ya da alana bağlantısı olmayan ve etiketlenmemiş bir nokta.", + untagged_line: "Etiketlenmemiş çizgi", + untagged_area: "Etiketlenmemiş alan", + tag_suggests_area: "{tag} etiketi buranın alan olmasını tavsiye ediyor ama alan değil.", + deprecated_tags: "Kullanımdan kaldırılmış etiket : {tags}" + }, + zoom: { in: "Yaklaş", out: "Uzaklaş" From 8882023e5b6029e335a3c32e9a51a14b75d9e6d1 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 19:59:32 -0800 Subject: [PATCH 381/415] tag_reference translations --- js/id/ui/tag_reference.js | 5 +++-- locale/da.js | 4 +++- locale/de.js | 4 +++- locale/en.js | 4 +++- locale/es.js | 4 +++- locale/fr.js | 4 +++- locale/ja.js | 4 +++- locale/lv.js | 4 +++- locale/tr.js | 4 +++- 9 files changed, 27 insertions(+), 10 deletions(-) diff --git a/js/id/ui/tag_reference.js b/js/id/ui/tag_reference.js index dc583b932..8a8ea2041 100644 --- a/js/id/ui/tag_reference.js +++ b/js/id/ui/tag_reference.js @@ -11,7 +11,7 @@ iD.ui.tagReference = function(selection) { .enter() .append('span') .attr('title', function(d) { - return 'used with ' + d; + return t('tag_reference.used_with', {type: d}); }) .attr('class', function(d) { return 'icon big icon-pre-text big-' + d; @@ -36,6 +36,7 @@ iD.ui.tagReference = function(selection) { referenceBody .append('p') .text(g('description')); + referenceBody .append('a') .attr('target', '_blank') @@ -43,7 +44,7 @@ iD.ui.tagReference = function(selection) { return 'http://wiki.openstreetmap.org/wiki/' + d.title; }) .text(function(d) { - return d.title + ' on wiki.osm.org'; + return t('tag_reference.on_wiki', {tag: d.title}); }); }); }; diff --git a/locale/da.js b/locale/da.js index 17b75c340..199cc2f5d 100644 --- a/locale/da.js +++ b/locale/da.js @@ -195,7 +195,9 @@ locale.da = { }, tag_reference: { - description: "Description" + description: "Description", + on_wiki: "{tag} on wiki.osm.org", + used_with: "used with {type}" }, zoom: { diff --git a/locale/de.js b/locale/de.js index 60f4c6155..71d94c9f3 100644 --- a/locale/de.js +++ b/locale/de.js @@ -195,7 +195,9 @@ locale.de = { }, tag_reference: { - description: "Beschreibung" + description: "Beschreibung", + on_wiki: "{tag} on wiki.osm.org", + used_with: "used with {type}" }, validations: { diff --git a/locale/en.js b/locale/en.js index 9060f7b66..7e3534f71 100644 --- a/locale/en.js +++ b/locale/en.js @@ -195,7 +195,9 @@ locale.en = { }, tag_reference: { - description: 'Description' + description: "Description", + on_wiki: "{tag} on wiki.osm.org", + used_with: "used with {type}" }, validations: { diff --git a/locale/es.js b/locale/es.js index c440429db..e82e4c465 100644 --- a/locale/es.js +++ b/locale/es.js @@ -195,7 +195,9 @@ locale.es = { }, tag_reference: { - description: "Descripción" //"Description", + description: "Descripción", + on_wiki: "{tag} on wiki.osm.org", + used_with: "used with {type}" }, validations: { diff --git a/locale/fr.js b/locale/fr.js index d6c36189a..da6e54cf3 100644 --- a/locale/fr.js +++ b/locale/fr.js @@ -195,7 +195,9 @@ locale.fr = { }, tag_reference: { - description: "Déscription" + description: "Déscription", + on_wiki: "{tag} on wiki.osm.org", + used_with: "used with {type}" }, validations: { diff --git a/locale/ja.js b/locale/ja.js index 61b76ace5..fa4297828 100644 --- a/locale/ja.js +++ b/locale/ja.js @@ -195,7 +195,9 @@ locale.ja = { }, tag_reference: { - description: "説明" + description: "説明", + on_wiki: "{tag} on wiki.osm.org", + used_with: "used with {type}" }, validations: { diff --git a/locale/lv.js b/locale/lv.js index 3a799c2a9..60f9136ff 100644 --- a/locale/lv.js +++ b/locale/lv.js @@ -195,7 +195,9 @@ locale.lv = { }, tag_reference: { - description: "Apraksts" + description: "Apraksts", + on_wiki: "{tag} on wiki.osm.org", + used_with: "used with {type}" }, validations: { diff --git a/locale/tr.js b/locale/tr.js index f212ca373..1274cf221 100644 --- a/locale/tr.js +++ b/locale/tr.js @@ -195,7 +195,9 @@ locale.tr = { }, tag_reference: { - description: "Açıklama" + description: "Açıklama", + on_wiki: "{tag} on wiki.osm.org", + used_with: "used with {type}" }, validations: { From 6d3d00e78d70deedac194361431818b3cd7165af Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 20:02:35 -0800 Subject: [PATCH 382/415] inspector translation --- js/id/ui/inspector.js | 37 ++++++++++++++++++++++--------------- locale/da.js | 3 ++- locale/de.js | 3 ++- locale/en.js | 3 ++- locale/es.js | 3 ++- locale/fr.js | 3 ++- locale/ja.js | 3 ++- locale/lv.js | 3 ++- locale/tr.js | 3 ++- 9 files changed, 38 insertions(+), 23 deletions(-) diff --git a/js/id/ui/inspector.js b/js/id/ui/inspector.js index 1b1c78be6..bc15cdbb4 100644 --- a/js/id/ui/inspector.js +++ b/js/id/ui/inspector.js @@ -27,15 +27,19 @@ iD.ui.Inspector = function() { tagList = inspectorwrap.append('ul'); var newTag = inspectorwrap.append('button') - .attr('class', 'add-tag'); + .attr('class', 'add-tag'); - newTag.on('click', function() { - addTag(); - focusNewKey(); - }); + newTag.on('click', function () { + addTag(); + focusNewKey(); + }); - newTag.append('span').attr('class', 'icon icon-pre-text plus'); - newTag.append('span').attr('class','label').text(t('inspector.new_tag')); + newTag.append('span') + .attr('class', 'icon icon-pre-text plus'); + + newTag.append('span') + .attr('class', 'label') + .text(t('inspector.new_tag')); drawTags(entity.tags); @@ -62,17 +66,20 @@ iD.ui.Inspector = function() { var entity = selection.datum(); var inspectorButton = selection.append('button') - .attr('class', 'apply action') - .on('click', apply); + .attr('class', 'apply action') + .on('click', apply); - inspectorButton.append('span').attr('class','label').text(t('inspector.okay')); + inspectorButton.append('span') + .attr('class','label') + .text(t('inspector.okay')); - var minorButtons = selection.append('div').attr('class','minor-buttons fl'); + var minorButtons = selection.append('div') + .attr('class','minor-buttons fl'); - minorButtons.append('a') - .attr('href', 'http://www.openstreetmap.org/browse/' + entity.type + '/' + entity.osmId()) - .attr('target', '_blank') - .text('View on OSM'); + minorButtons.append('a') + .attr('href', 'http://www.openstreetmap.org/browse/' + entity.type + '/' + entity.osmId()) + .attr('target', '_blank') + .text(t('inspector.view_on_osm')); } function drawTags(tags) { diff --git a/locale/da.js b/locale/da.js index 199cc2f5d..6f7e85d0b 100644 --- a/locale/da.js +++ b/locale/da.js @@ -178,7 +178,8 @@ locale.da = { no_documentation_key: "Der er ingen dokumenation tilgængelig for denne nøgle", new_tag: "Nyt Tag", edit_tags: "Ret tags", - okay: "Ok" + okay: "Ok", + view_on_osm: "View on OSM" }, save: { diff --git a/locale/de.js b/locale/de.js index 71d94c9f3..5291fb3aa 100644 --- a/locale/de.js +++ b/locale/de.js @@ -170,7 +170,8 @@ locale.de = { no_documentation_key: "Für dises Schlüsselwort ist keine Dokumentation verfügbar", new_tag: "Neues Attribut", edit_tags: "Attribute bearbeiten", - okay: "OK" + okay: "OK", + view_on_osm: "View on OSM" }, layerswitcher: { diff --git a/locale/en.js b/locale/en.js index 7e3534f71..015e4193c 100644 --- a/locale/en.js +++ b/locale/en.js @@ -170,7 +170,8 @@ locale.en = { no_documentation_key: "There is no documentation available for this key", new_tag: "New Tag", edit_tags: "Edit tags", - okay: "Okay" + okay: "Okay", + view_on_osm: "View on OSM" }, layerswitcher: { diff --git a/locale/es.js b/locale/es.js index e82e4c465..adc2150c9 100644 --- a/locale/es.js +++ b/locale/es.js @@ -170,7 +170,8 @@ locale.es = { no_documentation_key: "No hay documentación disponible para esta tecla", //"This is no documentation available for this key", new_tag: "Nueve etiqueta", //"New Tag" edit_tags: "Editar etiquetas", //"Edit tags", - okay: "OK" //"Okay", + okay: "OK", + view_on_osm: "View on OSM" }, layerswitcher: { diff --git a/locale/fr.js b/locale/fr.js index da6e54cf3..f0ddce52c 100644 --- a/locale/fr.js +++ b/locale/fr.js @@ -170,7 +170,8 @@ locale.fr = { no_documentation_key: "Aucune documentation n'est disponible pour cette clé", new_tag: "Nouveau tag", edit_tags: "Editer les tags", - okay: "Okay" + okay: "Okay", + view_on_osm: "View on OSM" }, layerswitcher: { diff --git a/locale/ja.js b/locale/ja.js index fa4297828..f2c1839a5 100644 --- a/locale/ja.js +++ b/locale/ja.js @@ -170,7 +170,8 @@ locale.ja = { no_documentation_key: "このキーに対する説明文はありません", new_tag: "新規タグ", edit_tags: "タグを編集", - okay: "OK" + okay: "OK", + view_on_osm: "View on OSM" }, layerswitcher: { diff --git a/locale/lv.js b/locale/lv.js index 60f9136ff..36d93c4aa 100644 --- a/locale/lv.js +++ b/locale/lv.js @@ -170,7 +170,8 @@ locale.lv = { no_documentation_key: "There is no documentation available for this key", new_tag: "Jauns apzīmējums", edit_tags: "Rediģēt apzīmējumus", - okay: "Labi" + okay: "Labi", + view_on_osm: "View on OSM" }, layerswitcher: { diff --git a/locale/tr.js b/locale/tr.js index 1274cf221..a26542333 100644 --- a/locale/tr.js +++ b/locale/tr.js @@ -170,7 +170,8 @@ locale.tr = { no_documentation_key: "Bu anahtar için dökümantasyon bulunmamaktadır.", new_tag: "Yeni Etiket", edit_tags: "Etiketleri güncelle", - okay: "Tamam" + okay: "Tamam", + view_on_osm: "View on OSM" }, layerswitcher: { From 0578d645bc1079a4a71dc8e47791bfc5d7b65ccc Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 20:05:51 -0800 Subject: [PATCH 383/415] restore translation --- js/id/ui/restore.js | 22 ++++++++++++---------- locale/da.js | 6 ++++++ locale/de.js | 6 ++++++ locale/en.js | 6 ++++++ locale/es.js | 6 ++++++ locale/fr.js | 6 ++++++ locale/ja.js | 6 ++++++ locale/lv.js | 6 ++++++ locale/tr.js | 6 ++++++ 9 files changed, 60 insertions(+), 10 deletions(-) diff --git a/js/id/ui/restore.js b/js/id/ui/restore.js index aaadae950..2fd8d9de9 100644 --- a/js/id/ui/restore.js +++ b/js/id/ui/restore.js @@ -6,19 +6,21 @@ iD.ui.restore = function(selection, history) { var introModal = modal.select('.content'); - introModal.append('div') - .attr('class', 'modal-section fillL') - .append('h3').text('You have unsaved changes from a previous editing session. Do you wish to restore these changes?'); - var buttonWrap = introModal.append('div') - .attr('class', 'modal-section fillD cf col12'); + introModal.append('div') + .attr('class', 'modal-section fillL') + .append('h3') + .text(t('restore.description')); - buttons = buttonWrap - .append('div') - .attr('class', 'button-wrap joined col6'); + var buttonWrap = introModal.append('div') + .attr('class', 'modal-section fillD cf col12'); + + var buttons = buttonWrap + .append('div') + .attr('class', 'button-wrap joined col6'); buttons.append('button') .attr('class', 'save action button col6') - .text('Restore') + .text(t('restore.restore')) .on('click', function() { history.load(); modal.remove(); @@ -26,7 +28,7 @@ iD.ui.restore = function(selection, history) { buttons.append('button') .attr('class', 'cancel button col6') - .text('Reset') + .text(t('restore.reset')) .on('click', function() { modal.remove(); }); diff --git a/locale/da.js b/locale/da.js index 6f7e85d0b..1e983c3de 100644 --- a/locale/da.js +++ b/locale/da.js @@ -182,6 +182,12 @@ locale.da = { view_on_osm: "View on OSM" }, + restore: { + description: "You have unsaved changes from a previous editing session. Do you wish to restore these changes?", + restore: "Restore", + reset: "Reset" + }, + save: { title: "Gem", help: "Gem ændringer til OpenStreetMap gør dem synlige for andre brugere", diff --git a/locale/de.js b/locale/de.js index 5291fb3aa..31f919a73 100644 --- a/locale/de.js +++ b/locale/de.js @@ -182,6 +182,12 @@ locale.de = { reset: "Zurücksetzen" }, + restore: { + description: "You have unsaved changes from a previous editing session. Do you wish to restore these changes?", + restore: "Restore", + reset: "Reset" + }, + save: { title: "Speichern", help: "Speichere Änderungen auf OpenStreetMap, um diese für andere Nutzer sichtbar zu machen", diff --git a/locale/en.js b/locale/en.js index 015e4193c..b5ea93202 100644 --- a/locale/en.js +++ b/locale/en.js @@ -182,6 +182,12 @@ locale.en = { reset: "reset" }, + restore: { + description: "You have unsaved changes from a previous editing session. Do you wish to restore these changes?", + restore: "Restore", + reset: "Reset" + }, + save: { title: "Save", help: "Save changes to OpenStreetMap, making them visible to other users", diff --git a/locale/es.js b/locale/es.js index adc2150c9..2e3eebcdd 100644 --- a/locale/es.js +++ b/locale/es.js @@ -182,6 +182,12 @@ locale.es = { reset: "reiniciar" //"reset" }, + restore: { + description: "You have unsaved changes from a previous editing session. Do you wish to restore these changes?", + restore: "Restore", + reset: "Reset" + }, + save: { title: "Guardar", //"Save", help: "Guardar los cambios en OpenStreetMap haciéndolos visibles a otros usuarios", //"Save changes to OpenStreetMap, making them visible to other users", diff --git a/locale/fr.js b/locale/fr.js index f0ddce52c..61ffcf675 100644 --- a/locale/fr.js +++ b/locale/fr.js @@ -182,6 +182,12 @@ locale.fr = { reset: "reset" }, + restore: { + description: "You have unsaved changes from a previous editing session. Do you wish to restore these changes?", + restore: "Restore", + reset: "Reset" + }, + save: { title: "Sauvegarder", help: "Envoie des modifications au serveyr OpenStreetMap afin qu'elles soient visibles par les autres contributeurs.", diff --git a/locale/ja.js b/locale/ja.js index f2c1839a5..0fd8c5229 100644 --- a/locale/ja.js +++ b/locale/ja.js @@ -182,6 +182,12 @@ locale.ja = { reset: "設定リセット" }, + restore: { + description: "You have unsaved changes from a previous editing session. Do you wish to restore these changes?", + restore: "Restore", + reset: "Reset" + }, + save: { title: "Save", help: "変更点をOpenStreetMapに保存し、他ユーザが確認できるようにします", diff --git a/locale/lv.js b/locale/lv.js index 36d93c4aa..58d05e4a4 100644 --- a/locale/lv.js +++ b/locale/lv.js @@ -182,6 +182,12 @@ locale.lv = { reset: "Pārstatīt" }, + restore: { + description: "You have unsaved changes from a previous editing session. Do you wish to restore these changes?", + restore: "Restore", + reset: "Reset" + }, + save: { title: "Saglabāt", help: "Saglabā izmaiņas, padarot tās redzamas citiem", diff --git a/locale/tr.js b/locale/tr.js index a26542333..566ee10d2 100644 --- a/locale/tr.js +++ b/locale/tr.js @@ -182,6 +182,12 @@ locale.tr = { reset: "Sıfırla" }, + restore: { + description: "You have unsaved changes from a previous editing session. Do you wish to restore these changes?", + restore: "Restore", + reset: "Reset" + }, + save: { title: "Kaydet", help: "Diğer kullanıcıların yaptığınız değişiklikleri görmesi için OpenStreetMap'e kaydediniz", From 02ed91096a1041f1c713057a154cab76367d1ef1 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 20:08:36 -0800 Subject: [PATCH 384/415] Follow ui pattern --- js/id/ui.js | 3 ++- js/id/ui/restore.js | 58 ++++++++++++++++++++++----------------------- 2 files changed, 31 insertions(+), 30 deletions(-) diff --git a/js/id/ui.js b/js/id/ui.js index 4e6ec4c24..628a46d4a 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -154,7 +154,8 @@ iD.ui = function(context) { } if (history.lock() && history.restorableChanges()) { - iD.ui.restore(context.container(), history); + context.container() + .call(iD.ui.Restore(context)) } }; diff --git a/js/id/ui/restore.js b/js/id/ui/restore.js index 2fd8d9de9..518bf1ddd 100644 --- a/js/id/ui/restore.js +++ b/js/id/ui/restore.js @@ -1,37 +1,37 @@ -iD.ui.restore = function(selection, history) { - var modal = iD.ui.modal(selection); +iD.ui.Restore = function(context) { + return function(selection) { + var modal = iD.ui.modal(selection); - modal.select('.modal') - .attr('class', 'modal-splash modal'); + modal.select('.modal') + .attr('class', 'modal-splash modal'); - var introModal = modal.select('.content'); + var introModal = modal.select('.content'); - introModal.append('div') - .attr('class', 'modal-section fillL') - .append('h3') - .text(t('restore.description')); + introModal.append('div') + .attr('class', 'modal-section fillL') + .append('h3') + .text(t('restore.description')); - var buttonWrap = introModal.append('div') - .attr('class', 'modal-section fillD cf col12'); + var buttonWrap = introModal.append('div') + .attr('class', 'modal-section fillD cf col12'); - var buttons = buttonWrap - .append('div') - .attr('class', 'button-wrap joined col6'); + var buttons = buttonWrap + .append('div') + .attr('class', 'button-wrap joined col6'); - buttons.append('button') - .attr('class', 'save action button col6') - .text(t('restore.restore')) - .on('click', function() { - history.load(); - modal.remove(); - }); + buttons.append('button') + .attr('class', 'save action button col6') + .text(t('restore.restore')) + .on('click', function() { + context.history().load(); + modal.remove(); + }); - buttons.append('button') - .attr('class', 'cancel button col6') - .text(t('restore.reset')) - .on('click', function() { - modal.remove(); - }); - - return modal; + buttons.append('button') + .attr('class', 'cancel button col6') + .text(t('restore.reset')) + .on('click', function() { + modal.remove(); + }); + } }; From 806de963dbbac723d5c20cf5368eb2b289064650 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 20:16:53 -0800 Subject: [PATCH 385/415] Cleanup, translate Splash --- js/id/ui.js | 6 ++---- js/id/ui/splash.js | 41 ++++++++++++++++++++++++++--------------- locale/da.js | 5 +++++ locale/de.js | 5 +++++ locale/en.js | 5 +++++ locale/es.js | 5 +++++ locale/fr.js | 5 +++++ locale/ja.js | 5 +++++ locale/lv.js | 5 +++++ locale/tr.js | 5 +++++ 10 files changed, 68 insertions(+), 19 deletions(-) diff --git a/js/id/ui.js b/js/id/ui.js index 628a46d4a..15fb55c09 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -148,10 +148,8 @@ iD.ui = function(context) { context.enter(iD.modes.Browse(context)); - if (!context.storage('sawSplash')) { - iD.ui.splash(context.container()); - context.storage('sawSplash', true); - } + context.container() + .call(iD.ui.Splash(context)); if (history.lock() && history.restorableChanges()) { context.container() diff --git a/js/id/ui/splash.js b/js/id/ui/splash.js index bfcbf3e05..c0e79261c 100644 --- a/js/id/ui/splash.js +++ b/js/id/ui/splash.js @@ -1,21 +1,32 @@ -iD.ui.splash = function(selection) { - var modal = iD.ui.modal(selection); +iD.ui.Splash = function(context) { + return function(selection) { + if (context.storage('sawSplash')) + return; - modal.select('.modal') - .attr('class', 'modal-splash modal'); + context.storage('sawSplash', true); - var introModal = modal.select('.content') - .append('div') - .attr('class', 'modal-section fillL'); + var modal = iD.ui.modal(selection); - introModal.append('div') - .attr('class','logo'); + modal.select('.modal') + .attr('class', 'modal-splash modal'); - introModal.append('div') - .html("

Welcome to the iD OpenStreetMap editor

" + - "This is development version 0.0.0-alpha1. " + - "For more information see ideditor.com" + - " and report bugs at github.com.systemed/iD.

"); + var introModal = modal.select('.content') + .append('div') + .attr('class', 'modal-section fillL'); - return modal; + introModal.append('div') + .attr('class', 'logo'); + + var div = introModal.append('div'); + + div.append("h2") + .text(t('splash.welcome')); + + div.append("p") + .html(t('splash.text', { + version: iD.version, + website: 'ideditor.com', + github: 'github.com' + })); + } }; diff --git a/locale/da.js b/locale/da.js index 1e983c3de..d42f95c61 100644 --- a/locale/da.js +++ b/locale/da.js @@ -196,6 +196,11 @@ locale.da = { unsaved_changes: "Du har ændringer der ikke er gemt endnu", }, + splash: { + welcome: "Welcome to the iD OpenStreetMap editor", + text: "This is development version {version}. For more information see {website} and report bugs at {github}." + }, + source_switch: { live: "live", dev: "dev" diff --git a/locale/de.js b/locale/de.js index 31f919a73..8629471be 100644 --- a/locale/de.js +++ b/locale/de.js @@ -196,6 +196,11 @@ locale.de = { unsaved_changes: "Ungespeicherte Änderugen vorhanden", }, + splash: { + welcome: "Welcome to the iD OpenStreetMap editor", + text: "This is development version {version}. For more information see {website} and report bugs at {github}." + }, + source_switch: { live: "live", dev: "dev" diff --git a/locale/en.js b/locale/en.js index b5ea93202..bd26f932b 100644 --- a/locale/en.js +++ b/locale/en.js @@ -196,6 +196,11 @@ locale.en = { unsaved_changes: "You have unsaved changes" }, + splash: { + welcome: "Welcome to the iD OpenStreetMap editor", + text: "This is development version {version}. For more information see {website} and report bugs at {github}." + }, + source_switch: { live: "live", dev: "dev" diff --git a/locale/es.js b/locale/es.js index 2e3eebcdd..2d47ecfe6 100644 --- a/locale/es.js +++ b/locale/es.js @@ -196,6 +196,11 @@ locale.es = { unsaved_changes: "Tienes cambios sin guardar" //"You have unsaved changes", }, + splash: { + welcome: "Welcome to the iD OpenStreetMap editor", + text: "This is development version {version}. For more information see {website} and report bugs at {github}." + }, + source_switch: { live: "en vivo", //"live", dev: "dev" diff --git a/locale/fr.js b/locale/fr.js index 61ffcf675..4716080cc 100644 --- a/locale/fr.js +++ b/locale/fr.js @@ -196,6 +196,11 @@ locale.fr = { unsaved_changes: "Vous avez des modifications non enregistrées" }, + splash: { + welcome: "Welcome to the iD OpenStreetMap editor", + text: "This is development version {version}. For more information see {website} and report bugs at {github}." + }, + source_switch: { live: "live", dev: "dev" diff --git a/locale/ja.js b/locale/ja.js index 0fd8c5229..3d4abec79 100644 --- a/locale/ja.js +++ b/locale/ja.js @@ -196,6 +196,11 @@ locale.ja = { unsaved_changes: "変更が保存されていません" }, + splash: { + welcome: "Welcome to the iD OpenStreetMap editor", + text: "This is development version {version}. For more information see {website} and report bugs at {github}." + }, + source_switch: { live: "本番サーバ", dev: "開発サーバ" diff --git a/locale/lv.js b/locale/lv.js index 58d05e4a4..9eb93f387 100644 --- a/locale/lv.js +++ b/locale/lv.js @@ -196,6 +196,11 @@ locale.lv = { unsaved_changes: "Jums ir nesaglabātas izmaiņas" }, + splash: { + welcome: "Welcome to the iD OpenStreetMap editor", + text: "This is development version {version}. For more information see {website} and report bugs at {github}." + }, + source_switch: { live: "live", dev: "dev" diff --git a/locale/tr.js b/locale/tr.js index 566ee10d2..402d822f1 100644 --- a/locale/tr.js +++ b/locale/tr.js @@ -196,6 +196,11 @@ locale.tr = { unsaved_changes: "Kaydedilmemiş değişiklikleriniz var" }, + splash: { + welcome: "Welcome to the iD OpenStreetMap editor", + text: "This is development version {version}. For more information see {website} and report bugs at {github}." + }, + source_switch: { live: "canlı", dev: "geliştirme" From 8928e757a413681e9dce321bb083c4cf53656227 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 20:19:36 -0800 Subject: [PATCH 386/415] Simplify --- js/id/ui.js | 9 ++------- js/id/ui/restore.js | 3 +++ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/js/id/ui.js b/js/id/ui.js index 15fb55c09..fe35ccc16 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -149,13 +149,8 @@ iD.ui = function(context) { context.enter(iD.modes.Browse(context)); context.container() - .call(iD.ui.Splash(context)); - - if (history.lock() && history.restorableChanges()) { - context.container() - .call(iD.ui.Restore(context)) - } - + .call(iD.ui.Splash(context)) + .call(iD.ui.Restore(context)); }; }; diff --git a/js/id/ui/restore.js b/js/id/ui/restore.js index 518bf1ddd..d15f80d96 100644 --- a/js/id/ui/restore.js +++ b/js/id/ui/restore.js @@ -1,5 +1,8 @@ iD.ui.Restore = function(context) { return function(selection) { + if (!context.history().lock() || !context.history().restorableChanges()) + return; + var modal = iD.ui.modal(selection); modal.select('.modal') From cfb6f7b1d4e6616b7a5013f126c6e2e47078a751 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 20:29:16 -0800 Subject: [PATCH 387/415] Cleanup --- js/id/ui.js | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/js/id/ui.js b/js/id/ui.js index fe35ccc16..3379ef4f1 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -63,17 +63,20 @@ iD.ui = function(context) { var userContainer = about.append('div') .attr('class', 'user-container'); - userContainer - .append('div') - .attr('class', 'hello'); + userContainer.append('div') + .attr('class', 'hello'); + + userContainer.call(iD.ui.UserPanel(connection) + .on('logout.editor', connection.logout) + .on('login.editor', connection.authenticate)); var aboutList = about.append('ul') - .attr('id','about') - .attr('class','link-list'); + .attr('id','about') + .attr('class', 'link-list'); var linkList = aboutList.append('ul') .attr('id','about') - .attr('class','pad1 fillD about-block link-list'); + .attr('class', 'pad1 fillD about-block link-list'); linkList.append('li') .append('a') @@ -142,10 +145,6 @@ iD.ui = function(context) { map.centerZoom([-77.02271, 38.90085], 20); } - userContainer.call(iD.ui.UserPanel(connection) - .on('logout.editor', connection.logout) - .on('login.editor', connection.authenticate)); - context.enter(iD.modes.Browse(context)); context.container() From f98cee40a7a0f34ef1b2a43e5c083fb2ca62d030 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 20:46:54 -0800 Subject: [PATCH 388/415] Extract iD.ui.Attribution --- index.html | 1 + js/id/ui.js | 12 +++--------- js/id/ui/attribution.js | 10 ++++++++++ 3 files changed, 14 insertions(+), 9 deletions(-) create mode 100644 js/id/ui/attribution.js diff --git a/index.html b/index.html index e79c178e4..6e8661145 100644 --- a/index.html +++ b/index.html @@ -57,6 +57,7 @@ + diff --git a/js/id/ui.js b/js/id/ui.js index 3379ef4f1..328d46b45 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -90,15 +90,9 @@ iD.ui = function(context) { .attr('href', 'http://github.com/systemed/iD/issues') .text(t('report_a_bug')); - var imagery = linkList.append('li') - .attr('id', 'attribution'); - - imagery.append('span') - .text('imagery'); - - imagery - .append('span') - .attr('class', 'provided-by'); + linkList.append('li') + .attr('id', 'attribution') + .call(iD.ui.Attribution(context)); linkList.append('li') .attr('class', 'source-switch') diff --git a/js/id/ui/attribution.js b/js/id/ui/attribution.js new file mode 100644 index 000000000..282833e7e --- /dev/null +++ b/js/id/ui/attribution.js @@ -0,0 +1,10 @@ +iD.ui.Attribution = function(context) { + return function(selection) { + selection.append('span') + .text('imagery'); + + selection + .append('span') + .attr('class', 'provided-by'); + } +}; From 6f6958ad98e618cfddd6b9cf6837c62b01d0fd27 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 20:50:17 -0800 Subject: [PATCH 389/415] Remove redundant ul, id -> class --- css/app.css | 2 +- js/id/ui.js | 12 ++++-------- js/id/ui/layerswitcher.js | 2 +- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/css/app.css b/css/app.css index 0e5651c9a..4cb56f910 100644 --- a/css/app.css +++ b/css/app.css @@ -930,7 +930,7 @@ img.tile { color:#fff; } -#user-list a:not(:last-child):after { +.user-list a:not(:last-child):after { content: ', '; } diff --git a/js/id/ui.js b/js/id/ui.js index 328d46b45..40e700801 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -70,12 +70,8 @@ iD.ui = function(context) { .on('logout.editor', connection.logout) .on('login.editor', connection.authenticate)); - var aboutList = about.append('ul') - .attr('id','about') - .attr('class', 'link-list'); - - var linkList = aboutList.append('ul') - .attr('id','about') + var linkList = about.append('ul') + .attr('id', 'about') .attr('class', 'pad1 fillD about-block link-list'); linkList.append('li') @@ -91,7 +87,7 @@ iD.ui = function(context) { .text(t('report_a_bug')); linkList.append('li') - .attr('id', 'attribution') + .attr('class', 'attribution') .call(iD.ui.Attribution(context)); linkList.append('li') @@ -99,7 +95,7 @@ iD.ui = function(context) { .call(iD.ui.SourceSwitch(context)); linkList.append('li') - .attr('id', 'user-list') + .attr('class', 'user-list') .call(iD.ui.Contributors(context)); window.onbeforeunload = function() { diff --git a/js/id/ui/layerswitcher.js b/js/id/ui/layerswitcher.js index f4d57a422..da800596f 100644 --- a/js/id/ui/layerswitcher.js +++ b/js/id/ui/layerswitcher.js @@ -92,7 +92,7 @@ iD.ui.LayerSwitcher = function(context) { return d === context.background().source(); }); - var provided_by = context.container().select('#attribution .provided-by') + var provided_by = context.container().select('.attribution .provided-by') .html(''); if (d.data.terms_url) { From 4bbed7e9437e52889bad30ea7d801a2cd5f55f6d Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 21:03:34 -0800 Subject: [PATCH 390/415] Cleanup, fix UserPanel --- js/id/connection.js | 2 +- js/id/ui.js | 12 ++---- js/id/ui/userpanel.js | 97 +++++++++++++++++++++---------------------- 3 files changed, 52 insertions(+), 59 deletions(-) diff --git a/js/id/connection.js b/js/id/connection.js index ea90c5591..f15aa80f8 100644 --- a/js/id/connection.js +++ b/js/id/connection.js @@ -228,7 +228,7 @@ iD.Connection = function(context) { if (img && img[0].getAttribute('href')) { image_url = img[0].getAttribute('href'); } - callback(connection.user({ + callback(undefined, connection.user({ display_name: u.attributes.display_name.nodeValue, image_url: image_url, id: u.attributes.id.nodeValue diff --git a/js/id/ui.js b/js/id/ui.js index 40e700801..115199edf 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -60,15 +60,9 @@ iD.ui = function(context) { var about = container.append('div') .attr('class','col12 about-block fillD pad1'); - var userContainer = about.append('div') - .attr('class', 'user-container'); - - userContainer.append('div') - .attr('class', 'hello'); - - userContainer.call(iD.ui.UserPanel(connection) - .on('logout.editor', connection.logout) - .on('login.editor', connection.authenticate)); + about.append('div') + .attr('class', 'user-container') + .call(iD.ui.UserPanel(context)); var linkList = about.append('ul') .attr('id', 'about') diff --git a/js/id/ui/userpanel.js b/js/id/ui/userpanel.js index 3dab2a5bd..f323706ae 100644 --- a/js/id/ui/userpanel.js +++ b/js/id/ui/userpanel.js @@ -1,54 +1,53 @@ -iD.ui.UserPanel = function(connection) { - var event = d3.dispatch('logout', 'login'); +iD.ui.UserPanel = function(context) { + var connection = context.connection(); - function user(selection) { - function update() { - if (connection.authenticated()) { - selection.style('display', 'block'); - connection.userDetails(function(err, user_details) { - - selection.html(''); - - if (err) return; - - // Link - var userLink = selection.append('a') - .attr('href', connection.url() + '/user/' + - user_details.display_name) - .attr('target', '_blank'); - - // Add thumbnail or dont - if (user_details.image_url) { - userLink.append('img') - .attr('class', 'icon icon-pre-text user-icon') - .attr('src', user_details.image_url); - } else { - userLink.append('span') - .attr('class','icon avatar icon-pre-text'); - } - - // Add user name - userLink.append('span') - .attr('class','label') - .text(user_details.display_name); - - selection - .append('a') - .attr('class', 'logout') - .attr('href', '#') - .text(t('logout')) - .on('click.logout', function() { - d3.event.preventDefault(); - event.logout(); - }); - }); - } else { - selection.html('').style('display', 'none'); - } + function update(selection) { + if (!connection.authenticated()) { + selection.html('') + .style('display', 'none'); + return; } - connection.on('auth', update); - update(); + + selection.style('display', 'block'); + + connection.userDetails(function(err, details) { + selection.html(''); + + if (err) return; + + // Link + var userLink = selection.append('a') + .attr('href', connection.url() + '/user/' + details.display_name) + .attr('target', '_blank'); + + // Add thumbnail or dont + if (details.image_url) { + userLink.append('img') + .attr('class', 'icon icon-pre-text user-icon') + .attr('src', details.image_url); + } else { + userLink.append('span') + .attr('class', 'icon avatar icon-pre-text'); + } + + // Add user name + userLink.append('span') + .attr('class', 'label') + .text(details.display_name); + + selection.append('a') + .attr('class', 'logout') + .attr('href', '#') + .text(t('logout')) + .on('click.logout', function() { + d3.event.preventDefault(); + connection.logout(); + }); + }); } - return d3.rebind(user, event, 'on'); + return function(selection) { + connection.on('auth', function() { update(selection); }); + update(selection); + }; }; From a25f2895d078b11f868c73f65ca79c9420bfcb84 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 21:04:59 -0800 Subject: [PATCH 391/415] Rename UserPanel -> Account --- combobox.html | 2 +- css/app.css | 4 ++-- index.html | 2 +- js/id/ui.js | 2 +- js/id/ui/{userpanel.js => account.js} | 2 +- test/index.html | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) rename js/id/ui/{userpanel.js => account.js} (97%) diff --git a/combobox.html b/combobox.html index 63a2c9757..383dbe325 100644 --- a/combobox.html +++ b/combobox.html @@ -61,7 +61,7 @@ - + diff --git a/css/app.css b/css/app.css index 4cb56f910..11e710970 100644 --- a/css/app.css +++ b/css/app.css @@ -936,11 +936,11 @@ img.tile { /* Account Information */ -.user-container { +.account { float: left; } -.user-container .logout { +.account .logout { margin-left:10px; border-left: 1px solid white; padding-left: 10px; diff --git a/index.html b/index.html index 6e8661145..61790eae0 100644 --- a/index.html +++ b/index.html @@ -66,7 +66,7 @@ - + diff --git a/js/id/ui.js b/js/id/ui.js index 115199edf..086cd59a4 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -61,7 +61,7 @@ iD.ui = function(context) { .attr('class','col12 about-block fillD pad1'); about.append('div') - .attr('class', 'user-container') + .attr('class', 'account') .call(iD.ui.UserPanel(context)); var linkList = about.append('ul') diff --git a/js/id/ui/userpanel.js b/js/id/ui/account.js similarity index 97% rename from js/id/ui/userpanel.js rename to js/id/ui/account.js index f323706ae..5e9fe7705 100644 --- a/js/id/ui/userpanel.js +++ b/js/id/ui/account.js @@ -1,4 +1,4 @@ -iD.ui.UserPanel = function(context) { +iD.ui.Account = function(context) { var connection = context.connection(); function update(selection) { diff --git a/test/index.html b/test/index.html index dcea8e28c..05482ab1f 100644 --- a/test/index.html +++ b/test/index.html @@ -63,7 +63,7 @@ - + From 9695a9cafaa275715a11cff443b38c7380e32bc1 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 21:11:16 -0800 Subject: [PATCH 392/415] Fix geocode tooltip --- css/app.css | 2 +- js/id/ui.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/css/app.css b/css/app.css index 11e710970..efcdd8b87 100644 --- a/css/app.css +++ b/css/app.css @@ -841,7 +841,7 @@ a.selected:hover .toggle.icon { background-position: -40px -180px;} width: 100%; } -.geocode-control div { +.geocode-control div.content { z-index: 100; top: 190px; max-height: 300px; diff --git a/js/id/ui.js b/js/id/ui.js index 086cd59a4..72712f861 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -62,7 +62,7 @@ iD.ui = function(context) { about.append('div') .attr('class', 'account') - .call(iD.ui.UserPanel(context)); + .call(iD.ui.Account(context)); var linkList = about.append('ul') .attr('id', 'about') From 5d11770e7e95f3d3c317fcbac6b668cf41a6ccf5 Mon Sep 17 00:00:00 2001 From: m0rix Date: Wed, 13 Feb 2013 11:33:34 +0100 Subject: [PATCH 393/415] Update locale/de.js --- locale/de.js | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/locale/de.js b/locale/de.js index 8629471be..7145dfe2b 100644 --- a/locale/de.js +++ b/locale/de.js @@ -139,15 +139,15 @@ locale.de = { report_a_bug: "Programmfehler melden", commit: { - title: "Save Changes", - description_placeholder: "Brief description of your contributions", - upload_explanation: "The changes you upload as {user} will be visible on all maps that use OpenStreetMap data.", - save: "Save", - cancel: "Cancel", - warnings: "Warnings", - modified: "Modified", - deleted: "Deleted", - created: "Created" + title: "Änderungen speichern", + description_placeholder: "Eine kurze Beschreibung deiner Beiträge", + upload_explanation: "Änderungen, die du als {user} hochlädst werden sichtbar auf allen Karte, die OpenStreetMap nutzen.", + save: "Speichern", + cancel: "Abbrechen", + warnings: "Warnungen", + modified: "Verändert", + deleted: "Gelöscht", + created: "Erstellt" }, contributors: { @@ -162,7 +162,7 @@ locale.de = { }, geolocate: { - title: "Show My Location" + title: "Zeige meine Position" }, inspector: { @@ -171,7 +171,7 @@ locale.de = { new_tag: "Neues Attribut", edit_tags: "Attribute bearbeiten", okay: "OK", - view_on_osm: "View on OSM" + view_on_osm: "auf OSM ansehen" }, layerswitcher: { @@ -183,9 +183,9 @@ locale.de = { }, restore: { - description: "You have unsaved changes from a previous editing session. Do you wish to restore these changes?", - restore: "Restore", - reset: "Reset" + description: "Es gibt ungespeicherte Änderungen aus einer vorherigen Sitzung. Möchtest du diese Änderungen wiederherstellen?", + restore: "Wiederherstellen", + reset: "Zurücksetzen" }, save: { @@ -197,8 +197,8 @@ locale.de = { }, splash: { - welcome: "Welcome to the iD OpenStreetMap editor", - text: "This is development version {version}. For more information see {website} and report bugs at {github}." + welcome: "Willkommen beim iD OpenStreetMap editor", + text: "Dies ist eine Entwicklungsversion {version}. Für weitere Informationen besuche {website} und melde Fehler unter {github}." }, source_switch: { @@ -208,8 +208,8 @@ locale.de = { tag_reference: { description: "Beschreibung", - on_wiki: "{tag} on wiki.osm.org", - used_with: "used with {type}" + on_wiki: "{tag} auf wiki.osm.org", + used_with: "benutzt mit {type}" }, validations: { From 7c10d37c1ebc9b3736a6cf1e7933f096274e53bc Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Wed, 13 Feb 2013 11:13:37 -0500 Subject: [PATCH 394/415] GA-based js error logging. Fixes #660 for the time being. --- index.html | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/index.html b/index.html index 4ea9d9721..2f3b5fb91 100644 --- a/index.html +++ b/index.html @@ -182,7 +182,18 @@ _gaq.push(['_trackPageview']); var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); + + // javascript errors + var lastev = ''; + window.onerror = function(message, file, lineNumber) { + var ev = ['_trackEvent', 'error', file + ':' + lineNumber, message + '']; + if (ev.join(',') !== lastev) { + _gaq.push(ev); + lastev = ev.join(','); + } + }; })(); + From f2d66285478385ea67e0449e8b2109b67b202aa9 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Wed, 13 Feb 2013 11:17:29 -0500 Subject: [PATCH 395/415] Don't break rebasing with loading --- js/id/core/history.js | 1 + 1 file changed, 1 insertion(+) diff --git a/js/id/core/history.js b/js/id/core/history.js index 1bedaeb60..4c6bbed6c 100644 --- a/js/id/core/history.js +++ b/js/id/core/history.js @@ -225,6 +225,7 @@ iD.History = function(context) { d.graph = iD.Graph(stack[0].graph).load(d.entities); return d; }); + stack[0].graph.inherited = false; dispatch.change(); } From a21b973a41fadc54a78ee51789105c359c137c37 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Wed, 13 Feb 2013 12:42:51 -0500 Subject: [PATCH 396/415] Fix saving deletions to storage, add tests --- js/id/core/graph.js | 4 ++- js/id/core/history.js | 9 +++++-- test/spec/core/history.js | 54 +++++++++++++++++++++++++++++++++++++-- 3 files changed, 62 insertions(+), 5 deletions(-) diff --git a/js/id/core/graph.js b/js/id/core/graph.js index 96dca44e7..a3fb8e823 100644 --- a/js/id/core/graph.js +++ b/js/id/core/graph.js @@ -265,7 +265,9 @@ iD.Graph.prototype = { entity = entities[i]; prefix = i[0]; - if (prefix == 'n') { + if (entity === 'undefined') { + this.entities[i] = undefined; + } else if (prefix == 'n') { this.entities[i] = new iD.Node(entity); } else if (prefix == 'w') { diff --git a/js/id/core/history.js b/js/id/core/history.js index 4c6bbed6c..47c93c145 100644 --- a/js/id/core/history.js +++ b/js/id/core/history.js @@ -188,7 +188,10 @@ iD.History = function(context) { imagery_used: i.imagery_used, entities: i.graph.entities }; - })); + }), function includeUndefined(key, value) { + if (typeof value === 'undefined') return 'undefined'; + return value; + }); context.storage(getKey('history'), json); context.storage(getKey('nextIDs'), JSON.stringify(iD.Entity.id.next)); @@ -228,7 +231,9 @@ iD.History = function(context) { stack[0].graph.inherited = false; dispatch.change(); - } + }, + + _getKey: getKey }; diff --git a/test/spec/core/history.js b/test/spec/core/history.js index 0ecbed5d3..0618f3ec3 100644 --- a/test/spec/core/history.js +++ b/test/spec/core/history.js @@ -1,10 +1,13 @@ describe("iD.History", function () { - var history, spy, + var context, history, spy, action = function() { return iD.Graph(); }; beforeEach(function () { - history = iD.History(); + context = iD(); + history = context.history(); spy = sinon.spy(); + // clear lock + context.storage(history._getKey('lock'), null); }); describe("#graph", function () { @@ -246,4 +249,51 @@ describe("iD.History", function () { expect(spy).to.have.been.called; }); }); + + describe("#lock", function() { + it("acquires lock if possible", function() { + expect(history.lock()).to.be.true; + expect(history.lock()).to.be.false; + }); + }); + + describe("#save", function() { + + it("doesn't do anything if it doesn't have the lock", function() { + var key = history._getKey('history'); + context.storage(key, null); + history.save(); + expect(context.storage(key)).to.be.undefined; + context.storage(key, 'something'); + expect(context.storage(key)).to.equal('something'); + history.save(); + context.storage(key, null); + }); + + it("saves to localStorage", function() { + var node = iD.Node({ id: 'n' }); + history.lock(); + history.perform(iD.actions.AddEntity(node)); + history.save(); + var saved = JSON.parse(context.storage(history._getKey('history'))); + expect(saved[1].entities.n.id).to.eql('n'); + }); + }); + + describe("#load", function() { + it("saves and loads a created and deleted entities", function() { + var node = iD.Node({ id: 'n' }), + node2 = iD.Node({ id: 'n2' }); + history.lock(); + history.perform(iD.actions.AddEntity(node)); + history.perform(iD.actions.AddEntity(node2)); + history.perform(iD.actions.DeleteNode('n2')); + history.save(); + history.reset(); + expect(history.graph().entity('n')).to.be.undefined + history.load(); + expect(history.graph().entity('n').id).to.equal('n'); + expect(history.graph().entity('n2')).to.be.undefined; + }); + }); }); From 95e6b840ee0970d4dc5e1914e8a807b96999ee0c Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Wed, 13 Feb 2013 13:14:57 -0500 Subject: [PATCH 397/415] Fix key reference --- js/id/services/taginfo.js | 2 +- js/id/ui/inspector.js | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/js/id/services/taginfo.js b/js/id/services/taginfo.js index fc4e72542..850de9efd 100644 --- a/js/id/services/taginfo.js +++ b/js/id/services/taginfo.js @@ -76,7 +76,7 @@ iD.taginfo = function() { page: 1 }, parameters)), function(err, d) { if (err) return callback(err); - callback(null, d.data.filter(popularValues()).map(valKeyDescription)); + callback(null, d.data.filter(popularValues()).map(valKeyDescription), parameters); }); }; diff --git a/js/id/ui/inspector.js b/js/id/ui/inspector.js index bc15cdbb4..7357646d1 100644 --- a/js/id/ui/inspector.js +++ b/js/id/ui/inspector.js @@ -176,16 +176,16 @@ iD.ui.Inspector = function() { } } - function keyReference(err, values) { - if (!err && values.data.length) { + function keyReference(err, values, params) { + if (!err && values.length) { iD.ui.modal(context.container()) .select('.content') .datum({ - data: values.data, + data: values, title: 'Key:' + params.key, geometry: params.geometry }) - .call(iD.keyReference(context)); + .call(iD.ui.keyReference); } else { iD.ui.flash(context.container()) .select('.content') From 65fbc808a1916e7c93b0ce06907ba333ce6619af Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 21:19:02 -0800 Subject: [PATCH 398/415] Map#pan redraws --- js/id/behavior/drag_node.js | 2 +- js/id/id.js | 1 + js/id/modes/move_way.js | 2 +- js/id/renderer/map.js | 2 +- js/id/ui.js | 6 ++---- 5 files changed, 6 insertions(+), 7 deletions(-) diff --git a/js/id/behavior/drag_node.js b/js/id/behavior/drag_node.js index eff4c1174..e06bec43e 100644 --- a/js/id/behavior/drag_node.js +++ b/js/id/behavior/drag_node.js @@ -14,7 +14,7 @@ iD.behavior.DragNode = function(context) { function startNudge(nudge) { if (nudgeInterval) window.clearInterval(nudgeInterval); nudgeInterval = window.setInterval(function() { - context.map().pan(nudge).redraw(); + context.pan(nudge); }, 50); } diff --git a/js/id/id.js b/js/id/id.js index 5db609593..5b4b1fb73 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -85,6 +85,7 @@ window.iD = function () { context.projection = map.projection; context.tail = map.tail; context.redraw = map.redraw; + context.pan = map.pan; context.zoomIn = map.zoomIn; context.zoomOut = map.zoomOut; diff --git a/js/id/modes/move_way.js b/js/id/modes/move_way.js index 99a2ebb48..cdd9fa792 100644 --- a/js/id/modes/move_way.js +++ b/js/id/modes/move_way.js @@ -30,7 +30,7 @@ iD.modes.MoveWay = function(context, wayId) { function startNudge(nudge) { if (nudgeInterval) window.clearInterval(nudgeInterval); nudgeInterval = window.setInterval(function() { - context.map().pan(nudge).redraw(); + context.pan(nudge); }, 50); } diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index 1bea28a17..29de5489b 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -241,7 +241,7 @@ iD.Map = function(context) { t[1] += d[1]; projection.translate(t); zoom.translate(projection.translate()); - return map; + return redraw(); }; map.size = function(_) { diff --git a/js/id/ui.js b/js/id/ui.js index 72712f861..6e7322848 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -2,8 +2,7 @@ iD.ui = function(context) { return function(container) { context.container(container); - var connection = context.connection(), - history = context.history(), + var history = context.history(), map = context.map(); if (!iD.detect().support) { @@ -103,8 +102,7 @@ iD.ui = function(context) { function pan(d) { return function() { - map.pan(d); - map.redraw(); + context.pan(d); }; } From 7fcf329ee876797f479980423ea917ae5bad0b99 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 21:21:02 -0800 Subject: [PATCH 399/415] Fix browser_notice style --- js/id/ui.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/js/id/ui.js b/js/id/ui.js index 6e7322848..3db4f6ece 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -6,8 +6,12 @@ iD.ui = function(context) { map = context.map(); if (!iD.detect().support) { - container.text(t('browser_notice')) - .style('text-align:center;font-style:italic;'); + container + .text(t('browser_notice')) + .style({ + 'text-align': 'center', + 'font-style': 'italic' + }); return; } From a53e604b5b3f8aa88916019ce13c4b24dab81fff Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Wed, 13 Feb 2013 11:57:32 -0800 Subject: [PATCH 400/415] Avoid unnecessary style recalculation --- js/id/renderer/map.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index 29de5489b..231a2e082 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -152,8 +152,12 @@ iD.Map = function(context) { if (resetTransform()) difference = undefined; - surface.attr('data-zoom', ~~map.zoom()); - tilegroup.call(background); + var zoom = String(~~map.zoom()); + if (surface.attr('data-zoom') !== zoom) + surface.attr('data-zoom', zoom); + + if (!difference) + tilegroup.call(background); if (map.editable()) { context.connection().loadTiles(projection, dimensions); From d50f7f13abe1bc286af54e27112687dd4a1cadb9 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Wed, 13 Feb 2013 12:03:41 -0800 Subject: [PATCH 401/415] Cache properties in fastMouse --- js/id/util.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/js/id/util.js b/js/id/util.js index bec8bc075..cfcf453a9 100644 --- a/js/id/util.js +++ b/js/id/util.js @@ -80,12 +80,14 @@ iD.util.getStyle = function(selector) { // 2. Does not cause style recalculation iD.util.fastMouse = function(container) { var rect = _.clone(container.getBoundingClientRect()), + rectLeft = rect.left, + rectTop = rect.top, clientLeft = +container.clientLeft, clientTop = +container.clientTop; return function(e) { return [ - e.clientX - rect.left - container.clientLeft, - e.clientY - rect.top - container.clientTop]; + e.clientX - rectLeft - clientLeft, + e.clientY - rectTop - clientTop]; }; }; From fd423f4db7a5558ee30d75c657c2c2ae0e08e1d7 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Wed, 13 Feb 2013 15:39:22 -0500 Subject: [PATCH 402/415] Don't drag when shiftKey --- js/id/behavior/drag_node.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/js/id/behavior/drag_node.js b/js/id/behavior/drag_node.js index e06bec43e..c16ede85f 100644 --- a/js/id/behavior/drag_node.js +++ b/js/id/behavior/drag_node.js @@ -1,6 +1,7 @@ iD.behavior.DragNode = function(context) { var nudgeInterval, - wasMidpoint; + wasMidpoint, + cancelled; function edge(point, size) { var pad = [30, 100, 30, 100]; @@ -36,6 +37,9 @@ iD.behavior.DragNode = function(context) { } function start(entity) { + cancelled = d3.event.sourceEvent.shiftKey; + if (cancelled) return behavior.cancel(); + context.history() .on('undone.drag-node', cancel); @@ -74,6 +78,7 @@ iD.behavior.DragNode = function(context) { } function move(entity) { + if (cancelled) return; d3.event.sourceEvent.stopPropagation(); var nudge = edge(d3.event.point, context.map().size()); @@ -97,6 +102,7 @@ iD.behavior.DragNode = function(context) { } function end(entity) { + if (cancelled) return; off(); var d = datum(); From 8538339b44fef934fae29d9cc2880f0c4f842cd3 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Wed, 13 Feb 2013 17:21:21 -0500 Subject: [PATCH 403/415] Fix zooming to validated features. Fixes #748 --- js/id/modes/select.js | 2 +- js/id/renderer/map.js | 28 ++++++++++++++++------------ js/id/ui/save.js | 6 +++--- 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/js/id/modes/select.js b/js/id/modes/select.js index ac6ad4943..d071d1484 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -180,7 +180,7 @@ iD.modes.Select = function(context, selection, initial) { if (showMenu) context.surface().call(radialMenu); context.surface() - .on('dblclick.select', dblclick) + .on('dblclick.select', dblclick); }, 200); }; diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index 231a2e082..26ec19ebe 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -308,21 +308,25 @@ iD.Map = function(context) { return new iD.geo.Extent(projection.invert([0, dimensions[1]]), projection.invert([dimensions[0], 0])); } else { - var extent = iD.geo.Extent(_), - tl = projection([extent[0][0], extent[1][1]]), - br = projection([extent[1][0], extent[0][1]]); - - // Calculate maximum zoom that fits extent - var hFactor = (br[0] - tl[0]) / dimensions[0], - vFactor = (br[1] - tl[1]) / dimensions[1], - hZoomDiff = Math.log(Math.abs(hFactor)) / Math.LN2, - vZoomDiff = Math.log(Math.abs(vFactor)) / Math.LN2, - newZoom = map.zoom() - Math.max(hZoomDiff, vZoomDiff); - - map.centerZoom(extent.center(), newZoom); + map.centerZoom(extent.center(), map.extentZoom(extent)); } }; + map.extentZoom = function(_) { + var extent = iD.geo.Extent(_), + tl = projection([extent[0][0], extent[1][1]]), + br = projection([extent[1][0], extent[0][1]]); + + // Calculate maximum zoom that fits extent + var hFactor = (br[0] - tl[0]) / dimensions[0], + vFactor = (br[1] - tl[1]) / dimensions[1], + hZoomDiff = Math.log(Math.abs(hFactor)) / Math.LN2, + vZoomDiff = Math.log(Math.abs(vFactor)) / Math.LN2, + newZoom = map.zoom() - Math.max(hZoomDiff, vZoomDiff); + + return newZoom; + }; + map.flush = function() { context.connection().flush(); context.history().reset(); diff --git a/js/id/ui/save.js b/js/id/ui/save.js index 8b0ef57ce..5e05183b2 100644 --- a/js/id/ui/save.js +++ b/js/id/ui/save.js @@ -11,7 +11,7 @@ iD.ui.Save = function(context) { if (!history.hasChanges()) return; connection.authenticate(function(err) { - var modal = iD.ui.modal(context.container()); + modal = iD.ui.modal(context.container()); var changes = history.changes(); changes.connection = connection; modal.select('.content') @@ -67,8 +67,8 @@ iD.ui.Save = function(context) { } function clickFix(d) { - map.extent(d.entity.extent(context.graph())); - if (map.zoom() > 19) map.zoom(19); + var extent = d.entity.extent(context.graph()); + map.centerZoom(extent.center(), Math.min(19, map.extentZoom(extent))); context.enter(iD.modes.Select(context, [d.entity.id])); modal.remove(); } From 3be61a65e67efe83f814a32096c2c8ec1b072fc6 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Wed, 13 Feb 2013 14:26:21 -0800 Subject: [PATCH 404/415] Manual redraw debouncing This avoids a duplicate full redraw at the end of a drag pan. The redraw triggered by mouseup.zoom now cancels the pending debounced redraw. Also, bump the debounce interval to 300ms, which helps avoid full redraws during scroll zoom, making it more responsive. --- js/id/renderer/map.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index 26ec19ebe..03562d3d8 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -146,6 +146,8 @@ iD.Map = function(context) { } function redraw(difference) { + clearTimeout(timeoutId); + // If we are in the middle of a zoom/pan, we can't do differenced redraws. // It would result in artifacts where differenced entities are redrawn with // one transform and unchanged entities with another. @@ -173,7 +175,11 @@ iD.Map = function(context) { return map; } - var queueRedraw = _.debounce(redraw, 200); + var timeoutId; + function queueRedraw() { + clearTimeout(timeoutId); + timeoutId = setTimeout(function() { redraw(); }, 300); + } function pointLocation(p) { var translate = projection.translate(), From e60c9ab16b1d6c7b4e7268a6e2e6ce8f5b4ac4d2 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Wed, 13 Feb 2013 17:31:23 -0500 Subject: [PATCH 405/415] Fix zoom limitation while drawing --- js/id/renderer/map.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index 03562d3d8..900609af4 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -112,7 +112,7 @@ iD.Map = function(context) { iD.ui.flash(context.container()) .select('.content') .text('Cannot zoom out further in current mode.'); - return map.zoom(16); + return setZoom(16, true); } projection @@ -214,8 +214,8 @@ iD.Map = function(context) { return map; }; - function setZoom(z) { - if (z === map.zoom()) + function setZoom(z, force) { + if (z === map.zoom() && !force) return false; var scale = 256 * Math.pow(2, z), center = pxCenter(), From 1050faab8f8b66e163551b02131eb040b679110d Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Wed, 13 Feb 2013 17:42:46 -0500 Subject: [PATCH 406/415] Only delete saved on hitting reset, fixes #744 --- js/id/core/history.js | 14 ++++++++------ js/id/ui/restore.js | 3 ++- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/js/id/core/history.js b/js/id/core/history.js index 47c93c145..bfd0d5a8c 100644 --- a/js/id/core/history.js +++ b/js/id/core/history.js @@ -175,12 +175,7 @@ iD.History = function(context) { if (!lock) return; context.storage(getKey('lock'), null); - if (stack.length <= 1) { - context.storage(getKey('history'), null); - context.storage(getKey('nextIDs'), null); - context.storage(getKey('index'), null); - return; - } + if (stack.length <= 1) return; var json = JSON.stringify(stack.map(function(i) { return { @@ -198,6 +193,13 @@ iD.History = function(context) { context.storage(getKey('index'), index); }, + clearSaved: function() { + if (!lock) return; + context.storage(getKey('history'), null); + context.storage(getKey('nextIDs'), null); + context.storage(getKey('index'), null); + }, + lock: function() { if (context.storage(getKey('lock'))) return false; context.storage(getKey('lock'), true); diff --git a/js/id/ui/restore.js b/js/id/ui/restore.js index d15f80d96..c6996ff23 100644 --- a/js/id/ui/restore.js +++ b/js/id/ui/restore.js @@ -34,7 +34,8 @@ iD.ui.Restore = function(context) { .attr('class', 'cancel button col6') .text(t('restore.reset')) .on('click', function() { + context.history().clearSaved(); modal.remove(); }); - } + }; }; From eb2a133ac64fb23c3cbefcb9baf91519085e4a35 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Wed, 13 Feb 2013 17:57:58 -0500 Subject: [PATCH 407/415] Hate multiline bracketless ifs --- js/id/renderer/map.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index 900609af4..21313ee39 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -151,15 +151,18 @@ iD.Map = function(context) { // If we are in the middle of a zoom/pan, we can't do differenced redraws. // It would result in artifacts where differenced entities are redrawn with // one transform and unchanged entities with another. - if (resetTransform()) + if (resetTransform()) { difference = undefined; + } var zoom = String(~~map.zoom()); - if (surface.attr('data-zoom') !== zoom) + if (surface.attr('data-zoom') !== zoom) { surface.attr('data-zoom', zoom); + } - if (!difference) + if (!difference) { tilegroup.call(background); + } if (map.editable()) { context.connection().loadTiles(projection, dimensions); From b2f1de654ce2eb2043f1a1cd9f047f309b30ea74 Mon Sep 17 00:00:00 2001 From: Luis GC Date: Thu, 14 Feb 2013 00:01:56 +0100 Subject: [PATCH 408/415] Completed Spanish translations --- locale/es.js | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/locale/es.js b/locale/es.js index 2d47ecfe6..739e3de22 100644 --- a/locale/es.js +++ b/locale/es.js @@ -54,7 +54,7 @@ locale.es = { }, circularize: { title: "Redondear", //"Circularize", - description: "Hacer esto redondo.", //"Make this round.", + description: "Redondear esto.", //"Make this round.", key: "O", annotation: { line: "Redondear una línea.", //"Made a line circular.", @@ -139,15 +139,15 @@ locale.es = { report_a_bug: "reportar un error", //"report a bug", commit: { - title: "Save Changes", - description_placeholder: "Brief description of your contributions", - upload_explanation: "The changes you upload as {user} will be visible on all maps that use OpenStreetMap data.", - save: "Save", - cancel: "Cancel", - warnings: "Warnings", - modified: "Modified", - deleted: "Deleted", - created: "Created" + title: "Guardar Cambios", // "Save Changes" + description_placeholder: "Breve descripción de tus contribuciones", //"Brief description of your contributions" + upload_explanation: "Los cambios que subes como {user} serán visibles en todos los mapas que usen datos de OpenStreetMap.", //"The changes you upload as {user} will be visible on all maps that use OpenStreetMap data." + save: "Guardar", //"Save" + cancel: "Cancelar", //"Cancel" + warnings: "Avisos", //"Warnings" + modified: "Modificado", //"Modified" + deleted: "Borrado", //"Deleted" + created: "Creado" //"Created" }, contributors: { @@ -162,7 +162,7 @@ locale.es = { }, geolocate: { - title: "Show My Location" + title: "Mostrar Mi Localización" //"Show My Location" }, inspector: { @@ -171,7 +171,7 @@ locale.es = { new_tag: "Nueve etiqueta", //"New Tag" edit_tags: "Editar etiquetas", //"Edit tags", okay: "OK", - view_on_osm: "View on OSM" + view_on_osm: "Ver en OSM" //"View on OSM" }, layerswitcher: { @@ -183,9 +183,9 @@ locale.es = { }, restore: { - description: "You have unsaved changes from a previous editing session. Do you wish to restore these changes?", - restore: "Restore", - reset: "Reset" + description: "Tienes cambios no guardados de una sesión de edición previa. ¿Quieres recuperar esos cambios?", //"You have unsaved changes from a previous editing session. Do you wish to restore these changes?" + restore: "Restaurar", //"Restore" + reset: "Restablecer" //"Reset" }, save: { @@ -197,8 +197,8 @@ locale.es = { }, splash: { - welcome: "Welcome to the iD OpenStreetMap editor", - text: "This is development version {version}. For more information see {website} and report bugs at {github}." + welcome: "Bienvenido al editor de OpenStreetMap iD", //"Welcome to the iD OpenStreetMap editor" + text: "Esta es la versión {version} de desarrollo. Para más información visita {website} y reporta cualquier error en {github}." //"This is development version {version}. For more information see {website} and report bugs at {github}." }, source_switch: { @@ -208,8 +208,8 @@ locale.es = { tag_reference: { description: "Descripción", - on_wiki: "{tag} on wiki.osm.org", - used_with: "used with {type}" + on_wiki: "{tag} en wiki.osm.org", //"{tag} on wiki.osm.org" + used_with: "usado con {type}" //"used with {type}" }, validations: { From b8c79b8b480461f9b2d345089a830ae278f73340 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Wed, 13 Feb 2013 18:12:38 -0500 Subject: [PATCH 409/415] Fix map.extent --- js/id/renderer/map.js | 1 + 1 file changed, 1 insertion(+) diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index 21313ee39..c3d5ec696 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -317,6 +317,7 @@ iD.Map = function(context) { return new iD.geo.Extent(projection.invert([0, dimensions[1]]), projection.invert([dimensions[0], 0])); } else { + var extent = iD.geo.Extent(_); map.centerZoom(extent.center(), map.extentZoom(extent)); } }; From 5e66307500d250c42c4ea0b98a1b32fc09afd339 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Wed, 13 Feb 2013 16:01:13 -0800 Subject: [PATCH 410/415] Fix incorrect parentWays after reloading a split way When recalculating parent ways/relations during rebase, a graph should not add modified or deleted entities as parents. Such entities will already be correctly marked as parents or not. Graph had the correct behavior for deleted entities, but not for modified entities. This had the effect that if you split a way that was partially off screen, and then panned so that the way was re-retrieved, Graph#rebase would mistakenly add back the original way as a parent of all the nodes that were split into the new section, making them appear as shared. Fixes #751. --- js/id/core/graph.js | 5 +++-- test/spec/core/graph.js | 42 ++++++++++++++++++++++++++++++++++++----- 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/js/id/core/graph.js b/js/id/core/graph.js index a3fb8e823..a595f0df6 100644 --- a/js/id/core/graph.js +++ b/js/id/core/graph.js @@ -93,6 +93,7 @@ iD.Graph.prototype = { rebase: function(entities) { var base = this.base(), i, k, child, id, keys; + // Merging of data only needed if graph is the base graph if (!this.inherited) { for (i in entities) { @@ -110,7 +111,7 @@ iD.Graph.prototype = { if (base.parentWays[child]) { for (k = 0; k < base.parentWays[child].length; k++) { id = base.parentWays[child][k]; - if (this.entity(id) && !_.contains(this._parentWays[child], id)) { + if (!this.entities.hasOwnProperty(id) && !_.contains(this._parentWays[child], id)) { this._parentWays[child].push(id); } } @@ -123,7 +124,7 @@ iD.Graph.prototype = { if (base.parentRels[child]) { for (k = 0; k < base.parentRels[child].length; k++) { id = base.parentRels[child][k]; - if (this.entity(id) && !_.contains(this._parentRels[child], id)) { + if (!this.entities.hasOwnProperty(id) && !_.contains(this._parentRels[child], id)) { this._parentRels[child].push(id); } } diff --git a/test/spec/core/graph.js b/test/spec/core/graph.js index a099d8b61..ef6741bb9 100644 --- a/test/spec/core/graph.js +++ b/test/spec/core/graph.js @@ -107,19 +107,36 @@ describe('iD.Graph', function() { w3 = iD.Way({id: 'w3', nodes: ['n']}), graph = iD.Graph([n, w1]), graph2 = graph.replace(w2); + graph.rebase({ 'w3': w3 }); graph2.rebase({ 'w3': w3 }); expect(graph2.parentWays(n)).to.eql([w1, w2, w3]); }); - it("avoids re-adding removed parentWays", function() { + it("avoids re-adding a modified way as a parent way", function() { + var n1 = iD.Node({id: 'n1'}), + n2 = iD.Node({id: 'n2'}), + w1 = iD.Way({id: 'w1', nodes: ['n1', 'n2']}), + w2 = w1.removeNode('n2'), + graph = iD.Graph([n1, n2, w1]), + graph2 = graph.replace(w2); + + graph.rebase({ 'w1': w1 }); + graph2.rebase({ 'w1': w1 }); + + expect(graph2.parentWays(n2)).to.eql([]); + }); + + it("avoids re-adding a deleted way as a parent way", function() { var n = iD.Node({id: 'n'}), w1 = iD.Way({id: 'w1', nodes: ['n']}), graph = iD.Graph([n, w1]), graph2 = graph.remove(w1); + graph.rebase({ 'w1': w1 }); graph2.rebase({ 'w1': w1 }); + expect(graph2.parentWays(n)).to.eql([]); }); @@ -135,14 +152,29 @@ describe('iD.Graph', function() { expect(graph._parentRels.hasOwnProperty('n')).to.be.false; }); - it("avoids re-adding removed parentRels", function() { + it("avoids re-adding a modified relation as a parent relation", function() { + var n = iD.Node({id: 'n'}), + r1 = iD.Relation({id: 'r1', members: [{id: 'n'}]}), + r2 = r1.removeMember('n'), + graph = iD.Graph([n, r1]), + graph2 = graph.replace(r2); + + graph.rebase({ 'r1': r1 }); + graph2.rebase({ 'r1': r1 }); + + expect(graph2.parentRelations(n)).to.eql([]); + }); + + it("avoids re-adding a deleted relation as a parent relation", function() { var n = iD.Node({id: 'n'}), r1 = iD.Relation({id: 'r1', members: [{id: 'n'}]}), graph = iD.Graph([n, r1]), graph2 = graph.remove(r1); - graph.rebase({ 'w1': r1 }); - graph2.rebase({ 'w1': r1 }); - expect(graph2.parentWays(n)).to.eql([]); + + graph.rebase({ 'r1': r1 }); + graph2.rebase({ 'r1': r1 }); + + expect(graph2.parentRelations(n)).to.eql([]); }); it("updates parentRels for nodes with modified parentWays", function () { From 372e009888654c1f8b622fae356763de970465df Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Wed, 13 Feb 2013 16:15:49 -0800 Subject: [PATCH 411/415] Assign id at top level So you can do id.graph(), etc. in the console for debugging. --- index.html | 4 ++-- index_packaged.html | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/index.html b/index.html index 341fc1ad1..6941ce008 100644 --- a/index.html +++ b/index.html @@ -165,9 +165,9 @@ .current('en') .current(iD.detect().locale); - d3.json('keys.json', function(err, keys) { - var id = iD(); + var id = iD(); + d3.json('keys.json', function(err, keys) { id.connection() .keys(keys); diff --git a/index_packaged.html b/index_packaged.html index a961a5202..7da953292 100644 --- a/index_packaged.html +++ b/index_packaged.html @@ -17,9 +17,10 @@
z16z17