From c8bee263e9c7c57e441798c7daf9a884f1a357aa Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Mon, 21 Jan 2013 20:32:17 -0500 Subject: [PATCH 01/14] Split ways at T junctions (fixes #453) --- js/id/actions/split_way.js | 21 +++++++++--- test/spec/actions/split_way.js | 62 ++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 4 deletions(-) diff --git a/js/id/actions/split_way.js b/js/id/actions/split_way.js index de399df80..d716880cf 100644 --- a/js/id/actions/split_way.js +++ b/js/id/actions/split_way.js @@ -8,14 +8,21 @@ // https://github.com/systemed/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/SplitWayAction.as // iD.actions.SplitWay = function(nodeId, newWayId) { - return function(graph) { + function candidateWays(graph) { var node = graph.entity(nodeId), parents = graph.parentWays(node); - // splitting ways at intersections TODO - if (parents.length !== 1) return graph; + return parents.filter(function (parent) { + return parent.first() !== nodeId && + parent.last() !== nodeId; + }) + } - var way = parents[0], + var action = function(graph) { + if (!action.permitted(graph)) + return graph; + + var way = candidateWays(graph)[0], idx = _.indexOf(way.nodes, nodeId); // Create a 'b' way that contains all of the tags in the second @@ -58,4 +65,10 @@ iD.actions.SplitWay = function(nodeId, newWayId) { return graph; }; + + action.permitted = function(graph) { + return candidateWays(graph).length === 1; + }; + + return action; }; diff --git a/test/spec/actions/split_way.js b/test/spec/actions/split_way.js index e83d5ee1a..09bacb82a 100644 --- a/test/spec/actions/split_way.js +++ b/test/spec/actions/split_way.js @@ -1,4 +1,37 @@ describe("iD.actions.SplitWay", function () { + describe("#permitted", function () { + it("returns true for a non-end node of a single way", 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', 'c']}) + }); + + expect(iD.actions.SplitWay('b').permitted(graph)).to.be.true; + }); + + it("returns false for the first node of a single way", function () { + var graph = iD.Graph({ + 'a': iD.Node({id: 'a'}), + 'b': iD.Node({id: 'b'}), + '-': iD.Way({id: '-', nodes: ['a', 'b']}) + }); + + expect(iD.actions.SplitWay('a').permitted(graph)).to.be.false; + }); + + it("returns false for the last node of a single way", function () { + var graph = iD.Graph({ + 'a': iD.Node({id: 'a'}), + 'b': iD.Node({id: 'b'}), + '-': iD.Way({id: '-', nodes: ['a', 'b']}) + }); + + expect(iD.actions.SplitWay('b').permitted(graph)).to.be.false; + }); + }); + it("creates a new way with the appropriate nodes", function () { // Situation: // a ---- b ---- c @@ -37,6 +70,35 @@ describe("iD.actions.SplitWay", function () { expect(graph.entity('=').tags).to.equal(tags); }); + it("splits a way at a T-junction", function () { + // Situation: + // a ---- b ---- c + // | + // d + // + // Split at 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'}), + '-': iD.Way({id: '-', nodes: ['a', 'b', 'c']}), + '|': iD.Way({id: '|', nodes: ['d', 'b']}) + }); + + graph = iD.actions.SplitWay('b', '=')(graph); + + expect(graph.entity('-').nodes).to.eql(['a', 'b']); + expect(graph.entity('=').nodes).to.eql(['b', 'c']); + expect(graph.entity('|').nodes).to.eql(['d', 'b']); + }); + it("adds the new way to parent relations (no connections)", function () { // Situation: // a ---- b ---- c From c05f3ea7f4d34b600a88aed55316ba46daf08ecb Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Mon, 21 Jan 2013 20:32:56 -0500 Subject: [PATCH 02/14] Don't fill multipolygon boundaries (fixes #426) --- css/map.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/css/map.css b/css/map.css index 7985d78a4..80916685d 100644 --- a/css/map.css +++ b/css/map.css @@ -224,6 +224,10 @@ path.multipolygon.tag-amenity-parking { fill: #edecc0; } +path.multipolygon.tag-boundary { + fill: none; +} + /* highways */ path.shadow.tag-highway { From b3765a4467f9236d4bd051a59d3992ca447379ae Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Tue, 22 Jan 2013 09:35:34 -0500 Subject: [PATCH 03/14] Microbranching jshint stuff. --- js/id/modes/select.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/js/id/modes/select.js b/js/id/modes/select.js index 8d59fbe43..1de8ab0f7 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -138,7 +138,9 @@ iD.modes.Select = function (entity) { mode.exit = function () { var surface = mode.map.surface; - entity && changeTags(entity, inspector.tags()); + if (entity) { + changeTags(entity, inspector.tags()); + } d3.select('.inspector-wrap') .style('display', 'none') .html(''); From 7cf234365e85dbdf1f690ebc683e5abf0cf1bd5b Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Tue, 22 Jan 2013 09:41:29 -0500 Subject: [PATCH 04/14] Non-overwriting hash, add hash in select mode. Needs code for dealing with hash. --- js/id/modes/select.js | 8 ++++++++ js/id/renderer/hash.js | 13 ++++++++----- js/id/util.js | 5 +++-- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/js/id/modes/select.js b/js/id/modes/select.js index 1de8ab0f7..505aff07a 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -47,6 +47,11 @@ iD.modes.Select = function (entity) { behavior(surface); }); + var q = iD.util.stringQs(location.hash.substring(1)); + location.hash = '#' + iD.util.qsString(_.assign(q, { + id: entity.id + }), true); + d3.select('.inspector-wrap') .style('display', 'block') .style('opacity', 1) @@ -149,6 +154,9 @@ iD.modes.Select = function (entity) { behavior.off(surface); }); + var q = iD.util.stringQs(location.hash.substring(1)); + location.hash = '#' + iD.util.qsString(_.omit(q, 'id'), true); + surface.on("click.select", null); mode.map.keybinding().on('⌫.select', null); mode.history.on('change.entity-undone', null); diff --git a/js/id/renderer/hash.js b/js/id/renderer/hash.js index bb454811b..652e64f20 100644 --- a/js/id/renderer/hash.js +++ b/js/id/renderer/hash.js @@ -1,6 +1,6 @@ iD.Hash = function() { var hash = { hadHash: false }, - s0, // cached location.hash + s0 = null, // cached location.hash lat = 90 - 1e-8, // allowable latitude range map; @@ -20,9 +20,12 @@ iD.Hash = function() { var center = map.center(), zoom = map.zoom(), precision = Math.max(0, Math.ceil(Math.log(zoom) / Math.LN2)); - return '#?map=' + zoom.toFixed(2) + - '/' + center[1].toFixed(precision) + - '/' + center[0].toFixed(precision); + var q = iD.util.stringQs(location.hash.substring(1)); + return '#' + iD.util.qsString(_.assign(q, { + map: zoom.toFixed(2) + + '/' + center[1].toFixed(precision) + + '/' + center[0].toFixed(precision) + }), true); }; var move = _.throttle(function() { @@ -32,7 +35,7 @@ iD.Hash = function() { function hashchange() { if (location.hash === s0) return; // ignore spurious hashchange events - if (parser(map, (s0 = location.hash).substring(2))) { + if (parser(map, (s0 = location.hash).substring(1))) { move(); // replace bogus hash } } diff --git a/js/id/util.js b/js/id/util.js index 79b65c524..b3ae537ec 100644 --- a/js/id/util.js +++ b/js/id/util.js @@ -30,9 +30,10 @@ iD.util.stringQs = function(str) { }, {}); }; -iD.util.qsString = function(obj) { +iD.util.qsString = function(obj, noencode) { return Object.keys(obj).sort().map(function(key) { - return encodeURIComponent(key) + '=' + encodeURIComponent(obj[key]); + return encodeURIComponent(key) + '=' + ( + noencode ? obj[key] : encodeURIComponent(obj[key])); }).join('&'); }; From 16ba8af4998da766dc5eb28aea7bded4b50e355e Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Tue, 22 Jan 2013 09:48:30 -0500 Subject: [PATCH 05/14] Fix tests for non-question hash --- test/spec/renderer/hash.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/spec/renderer/hash.js b/test/spec/renderer/hash.js index 44d3c64a2..9de281453 100644 --- a/test/spec/renderer/hash.js +++ b/test/spec/renderer/hash.js @@ -24,13 +24,13 @@ describe("hash", function () { }); it("sets hadHash if location.hash is present", function () { - location.hash = "?map=20.00/38.87952/-77.02405"; + 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"; + 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); @@ -67,7 +67,7 @@ describe("hash", function () { }); sinon.spy(map, 'centerZoom'); - location.hash = "#?map=20.00/38.87952/-77.02405"; + location.hash = "#map=20.00/38.87952/-77.02405"; }); }); @@ -75,7 +75,7 @@ describe("hash", function () { it("stores the current zoom and coordinates in location.hash", function () { sinon.stub(map, 'on').yields(); hash.map(map); - expect(location.hash).to.equal("#?map=0.00/0/0"); + expect(location.hash).to.equal("#map=0.00/0/0"); }); }); }); From 2e14a6b4deacaf8f649c0be3b1880354371125aa Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 22 Jan 2013 09:49:38 -0500 Subject: [PATCH 06/14] =?UTF-8?q?Unbind=20=E2=8C=98-Z=20when=20exiting=20D?= =?UTF-8?q?rawLine=20(fixes=20#455)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- js/id/modes/draw_line.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/js/id/modes/draw_line.js b/js/id/modes/draw_line.js index 72a85ae41..4be2e5527 100644 --- a/js/id/modes/draw_line.js +++ b/js/id/modes/draw_line.js @@ -142,8 +142,6 @@ iD.modes.DrawLine = function(wayId, direction) { }); d3.select('#undo').on('click.drawline', undo); - - }; mode.exit = function() { @@ -161,10 +159,11 @@ iD.modes.DrawLine = function(wayId, direction) { .on('click.drawline', null); mode.map.keybinding() - .on('⎋.drawline', null) .on('⌫.drawline', null) .on('⌦.drawline', null) - .on('↩.drawline', null); + .on('⎋.drawline', null) + .on('↩.drawline', null) + .on('z.drawline', null); d3.select('#undo').on('click.drawline', null); From 3d90801e63b0bbf756f1287971917b0fc2ed2ec3 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Tue, 22 Jan 2013 10:22:00 -0500 Subject: [PATCH 07/14] Add willselect to hash, works with non-line features. --- js/id/id.js | 2 +- js/id/modes/select.js | 24 +++++++++++++----------- js/id/renderer/hash.js | 31 +++++++++++++++++++++++++++++++ js/id/renderer/map.js | 42 +++++++++++++++++++++--------------------- 4 files changed, 66 insertions(+), 33 deletions(-) diff --git a/js/id/id.js b/js/id/id.js index 4d3948107..3d9004bfb 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -202,7 +202,7 @@ window.iD = function(container) { if (mods === '⌘' || mods === '⌃') history.undo(); }); - var hash = iD.Hash().map(map); + var hash = iD.Hash().controller(controller).map(map); if (!hash.hadHash) { map.centerZoom([-77.02271, 38.90085], 20); diff --git a/js/id/modes/select.js b/js/id/modes/select.js index 505aff07a..3b9667de3 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -1,4 +1,4 @@ -iD.modes.Select = function (entity) { +iD.modes.Select = function(entity) { var mode = { id: 'select', button: 'browse', @@ -34,7 +34,7 @@ iD.modes.Select = function (entity) { } } - mode.enter = function () { + mode.enter = function() { var surface = mode.map.surface; behaviors = [ @@ -58,16 +58,18 @@ iD.modes.Select = function (entity) { .datum(entity) .call(inspector); - // 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 (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])); + if (shift_left > 0 && inspector_size[1] > d3.event.y) { + mode.map.centerEase(mode.map.projection.invert([center, map_size[1]/2])); + } } inspector diff --git a/js/id/renderer/hash.js b/js/id/renderer/hash.js index 652e64f20..81da12d90 100644 --- a/js/id/renderer/hash.js +++ b/js/id/renderer/hash.js @@ -2,6 +2,7 @@ iD.Hash = function() { var hash = { hadHash: false }, s0 = null, // cached location.hash lat = 90 - 1e-8, // allowable latitude range + controller, map; var parser = function(map, s) { @@ -40,6 +41,32 @@ iD.Hash = function() { } } + // the hash can declare that the map should select a feature, but it can + // do so before any features are loaded. thus wait for the feature to + // be loaded and then select + function willselect(id) { + map.on('drawn.after-draw-select', function() { + var entity = map.history().graph().entity(id); + if (entity === undefined) return; + else selectoff(); + controller.enter(iD.modes.Select(entity)); + map.on('drawn.after-draw-select', null); + }); + controller.on('enter', function() { + if (controller.mode.id !== 'browse') selectoff(); + }); + } + + function selectoff() { + map.on('drawn.after-draw-select', null); + } + + hash.controller = function(_) { + if (!arguments.length) return controller; + controller = _; + return hash; + }; + hash.map = function(x) { if (!arguments.length) return map; if (map) { @@ -51,6 +78,10 @@ iD.Hash = function() { map.on("move", 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; } diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index 3f55f7271..c2de92855 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -1,7 +1,7 @@ iD.Map = function() { var connection, history, dimensions = [], - dispatch = d3.dispatch('move'), + dispatch = d3.dispatch('move', 'drawn'), translateStart, keybinding = d3.keybinding(), projection = d3.geo.mercator().scale(1024), @@ -65,22 +65,22 @@ iD.Map = function() { extent = map.extent(), graph = history.graph(); + function addParents(parents) { + for (var i = 0; i < parents.length; i++) { + var parent = parents[i]; + if (only[parent.id] === undefined) { + only[parent.id] = graph.fetch(parent.id); + addParents(graph.parentRelations(parent)); + } + } + } + if (!difference) { all = graph.intersects(extent); filter = d3.functor(true); } else { var only = {}; - function addParents(parents) { - for (var i = 0; i < parents.length; i++) { - var parent = parents[i]; - if (only[parent.id] === undefined) { - only[parent.id] = graph.fetch(parent.id); - addParents(graph.parentRelations(parent)); - } - } - } - for (var j = 0; j < difference.length; j++) { var id = difference[j], entity = graph.fetch(id); @@ -101,16 +101,16 @@ iD.Map = function() { if (all.length > 100000) { editOff(); - return; + } else { + surface + .call(points, graph, all, filter) + .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); } - - surface - .call(points, graph, all, filter) - .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); + dispatch.drawn(map); } function editOff() { @@ -302,7 +302,7 @@ iD.Map = function() { map.connection = function(_) { if (!arguments.length) return connection; connection = _; - connection.on('load', connectionLoad); + connection.on('load.tile', connectionLoad); return map; }; From 38c02509eadd6e9ff8fa62c48e0e9450d375068d Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Tue, 22 Jan 2013 10:39:10 -0500 Subject: [PATCH 08/14] Do not tolerate modifiers for action shortcuts. Tolerates id-equality for selecting features. --- js/id/id.js | 3 +++ js/id/modes/select.js | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/js/id/id.js b/js/id/id.js index 3d9004bfb..31ff26836 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -186,15 +186,18 @@ window.iD = function(container) { map.keybinding() .on('a', function(evt, mods) { + if (mods) return; controller.enter(iD.modes.AddArea()); }) .on('⌫.prevent_navigation', function(evt, mods) { evt.preventDefault(); }) .on('p', function(evt, mods) { + if (mods) return; controller.enter(iD.modes.AddPoint()); }) .on('l', function(evt, mods) { + if (mods) return; controller.enter(iD.modes.AddLine()); }) .on('z', function(evt, mods) { diff --git a/js/id/modes/select.js b/js/id/modes/select.js index 3b9667de3..9397b1e98 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -138,7 +138,9 @@ iD.modes.Select = function(entity) { }); surface.selectAll("*") - .filter(function (d) { return d === entity; }) + .filter(function (d) { + return d && entity && d.id === entity.id; + }) .classed('selected', true); }; From d214c651f274b977238007c76ee0ad8299644a6d Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Tue, 22 Jan 2013 10:42:16 -0500 Subject: [PATCH 09/14] Fix tests for hash updates --- test/spec/renderer/hash.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/test/spec/renderer/hash.js b/test/spec/renderer/hash.js index 9de281453..1304602f1 100644 --- a/test/spec/renderer/hash.js +++ b/test/spec/renderer/hash.js @@ -1,5 +1,5 @@ describe("hash", function () { - var hash, map; + var hash, map, controller; beforeEach(function () { hash = iD.Hash(); @@ -7,8 +7,12 @@ describe("hash", function () { on: function () { return map; }, off: 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] } + center: function () { return arguments.length ? map : [0, 0]; }, + centerZoom: function () { return arguments.length ? map : [0, 0]; } + }; + controller = { + on: function () { return controller; }, + off: function () { return controller; } }; }); @@ -19,7 +23,7 @@ describe("hash", function () { describe("#map()", function () { it("gets and sets map", function () { - expect(hash.map(map)).to.equal(hash); + expect(hash.controller(controller).map(map)).to.equal(hash); expect(hash.map()).to.equal(map); }); From 55346c4d3310e264df1dee5865b966626804dc18 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Tue, 22 Jan 2013 11:19:09 -0500 Subject: [PATCH 10/14] Work around blur issue for #439 --- NOTES.md | 7 +++++++ js/id/modes/select.js | 5 +++++ js/id/ui/inspector.js | 11 +++++++---- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/NOTES.md b/NOTES.md index c2076a1d7..abd5738c2 100644 --- a/NOTES.md +++ b/NOTES.md @@ -293,6 +293,13 @@ webkit doesn't let querySelectorAll select camelcase elements: * https://bugs.webkit.org/show_bug.cgi?id=83438 * https://github.com/mbostock/d3/issues/925 +Firefox does not fire a `blur` event when an element is removed from the DOM, +unlike WebKit browsers. + +Firefox does not support [the focusout event](https://bugzilla.mozilla.org/show_bug.cgi?id=687787). + +Opera does not support `pointer-events` on HTML elements, only SVG elements. + ## Transients The graph supports `transient`, which is storage for non-versioned mutable diff --git a/js/id/modes/select.js b/js/id/modes/select.js index 9397b1e98..add2e3750 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -150,10 +150,15 @@ iD.modes.Select = function(entity) { if (entity) { changeTags(entity, inspector.tags()); } + d3.select('.inspector-wrap') .style('display', 'none') .html(''); + // Firefox incorrectly implements blur, so typeahead elements + // are not correctly removed. Remove any stragglers manually. + d3.selectAll('div.typeahead').remove(); + behaviors.forEach(function(behavior) { behavior.off(surface); }); diff --git a/js/id/ui/inspector.js b/js/id/ui/inspector.js index 850fe84c0..aa3ad3add 100644 --- a/js/id/ui/inspector.js +++ b/js/id/ui/inspector.js @@ -149,9 +149,12 @@ iD.ui.inspector = function() { }); if (d.key && d.value) { taginfo.docs(params, function(err, docs) { - var en = _.find(docs, function(d) { - return d.lang == 'en'; - }); + 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'); @@ -170,7 +173,7 @@ iD.ui.inspector = function() { }); } else if (d.key) { taginfo.values(params, function(err, values) { - if (values.data.length) { + if (!err && values.data.length) { iD.ui.modal() .select('.content') .datum({ From 0d86cd89903b1e056f58af23b4b45b2bf6d9f3d2 Mon Sep 17 00:00:00 2001 From: Saman Bemel-Benrud Date: Tue, 22 Jan 2013 11:48:36 -0500 Subject: [PATCH 11/14] remove ui hidiing. --- css/app.css | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/css/app.css b/css/app.css index f57611633..312f958a8 100644 --- a/css/app.css +++ b/css/app.css @@ -481,21 +481,6 @@ a.selected:hover .toggle.icon { background-position: -40px -180px;} right:0; height:60px; border-radius: 0; - -webkit-transition: opacity .25s, z-index 0 0s; - -moz-transition: opacity .25s, z-index 0 0s; - transition: opacity .25s, z-index 0 0s; -} - -.mode-add-point #bar, -.mode-add-line #bar, -.mode-draw-line #bar, -.mode-draw-area #bar, -.mode-add-area #bar { - opacity:0; - z-index: -9999; - -webkit-transition: opacity .25s, z-index 0 .5s; - -moz-transition: opacity .25s, z-index 0 .5s; - transition: opacity .25s, z-index 0 .5s; } /* Inspector */ @@ -595,7 +580,7 @@ a.selected:hover .toggle.icon { background-position: -40px -180px;} .tag-row button.tag-help { right: -30px; } -.inspector-buttons { +.inspector-buttons { border-radius: 0 0 0 10px; height: 60px; } @@ -943,7 +928,7 @@ div.typeahead a:first-child { border-radius: 0 0 4px 4px; } -.modal-section .buttons { +.modal-section .buttons { padding-top: 10px; width: 100%; } From 748b597c84ce43da1ecdb2b6abe6f356fdd0e065 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Tue, 22 Jan 2013 12:23:51 -0500 Subject: [PATCH 12/14] Fast zooming based on transforms. * Replaces the mouseup-tranformStart method with a redraw debouced by 1/5s --- css/app.css | 8 +++++++- js/id/renderer/map.js | 45 ++++++++++++++++++++++--------------------- 2 files changed, 30 insertions(+), 23 deletions(-) diff --git a/css/app.css b/css/app.css index f57611633..e69d7e521 100644 --- a/css/app.css +++ b/css/app.css @@ -783,9 +783,14 @@ img.tile { -o-transform-origin:0 0; } -#tile-g { +#surface, #tile-g { position:absolute; top:0; + transform-origin:0 0; + -ms-transform-origin:0 0; + -webkit-transform-origin:0 0; + -moz-transform-origin:0 0; + -o-transform-origin:0 0; } /* About Section @@ -993,6 +998,7 @@ div.typeahead a:first-child { height:38px; padding:10px 20px; background:#fff; + color:#000; font-weight: normal; line-height: 21px; border-radius:5px; diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index c2de92855..0a77aedc6 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -2,7 +2,6 @@ iD.Map = function() { var connection, history, dimensions = [], dispatch = d3.dispatch('move', 'drawn'), - translateStart, keybinding = d3.keybinding(), projection = d3.geo.mercator().scale(1024), roundedProjection = iD.svg.RoundProjection(projection), @@ -32,19 +31,15 @@ iD.Map = function() { var supersurface = selection.append('div') .style('position', 'absolute') - .on('mousedown.drag', function() { - translateStart = projection.translate(); - }) .call(zoom); surface = supersurface.append('svg') - .on('mouseup.reset-transform', resetTransform) - .on('touchend.reset-transform', resetTransform) .on('mousedown.zoom', function() { if (d3.event.button == 2) { d3.event.stopPropagation(); } }, true) + .attr('id', 'surface') .call(iD.svg.Surface()); @@ -136,32 +131,35 @@ iD.Map = function() { .text('Cannot zoom out further in current mode.'); return map.zoom(16); } - var fast = (d3.event.scale === projection.scale() && fastEnabled); + projection .translate(d3.event.translate) .scale(d3.event.scale); - if (fast && translateStart) { - var a = d3.event.translate, - b = translateStart, - translate = 'translate(' + ~~(a[0] - b[0]) + 'px,' + - ~~(a[1] - b[1]) + 'px)'; - tilegroup.style(transformProp, translate); - surface.style(transformProp, translate); - } else { - redraw(); - translateStart = null; - } + + var ascale = d3.event.scale; + var bscale = transformStart[0]; + var scale = (ascale / bscale); + + var tX = Math.round((d3.event.translate[0] / scale) - (transformStart[1][0])); + var tY = Math.round((d3.event.translate[1] / scale) - (transformStart[1][1])); + + var transform = + 'scale(' + scale + ')' + + 'translate(' + tX + 'px,' + tY + 'px) '; + + tilegroup.style(transformProp, transform); + surface.style(transformProp, transform); + redraw(); } function resetTransform() { if (!surface.style(transformProp)) return; - translateStart = null; surface.style(transformProp, ''); tilegroup.style(transformProp, ''); - redraw(); } - var redraw = _.throttle(function(difference) { + var redraw = _.debounce(function(difference) { + resetTransform(); dispatch.move(map); surface.attr('data-zoom', ~~map.zoom()); tilegroup.call(background); @@ -171,8 +169,11 @@ iD.Map = function() { } else { editOff(); } + transformStart = [ + projection.scale(), + projection.translate().slice()]; return map; - }, 10); + }, 200); function pointLocation(p) { var translate = projection.translate(), From 64445e50ac82ef4ce44da30f98058cb1bfb88de7 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Tue, 22 Jan 2013 14:17:27 -0500 Subject: [PATCH 13/14] Slow and fast redraw --- js/id/renderer/map.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index 0a77aedc6..b228ea765 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -149,7 +149,7 @@ iD.Map = function() { tilegroup.style(transformProp, transform); surface.style(transformProp, transform); - redraw(); + queueRedraw(); } function resetTransform() { @@ -158,7 +158,7 @@ iD.Map = function() { tilegroup.style(transformProp, ''); } - var redraw = _.debounce(function(difference) { + function redraw(difference) { resetTransform(); dispatch.move(map); surface.attr('data-zoom', ~~map.zoom()); @@ -173,7 +173,9 @@ iD.Map = function() { projection.scale(), projection.translate().slice()]; return map; - }, 200); + } + + var queueRedraw = _.debounce(redraw, 200); function pointLocation(p) { var translate = projection.translate(), From 9a3d545f17119b706c6f3d794384dd94b3c99da1 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Tue, 22 Jan 2013 14:42:21 -0500 Subject: [PATCH 14/14] Do not focus text fields after initial placement --- js/id/modes/add_point.js | 2 +- js/id/modes/draw_area.js | 4 ++-- js/id/modes/draw_line.js | 9 +++++---- js/id/modes/select.js | 4 ++-- js/id/ui/inspector.js | 11 +++++++++-- 5 files changed, 19 insertions(+), 11 deletions(-) diff --git a/js/id/modes/add_point.js b/js/id/modes/add_point.js index 21d6343e0..82ad283c2 100644 --- a/js/id/modes/add_point.js +++ b/js/id/modes/add_point.js @@ -19,7 +19,7 @@ iD.modes.AddPoint = function() { iD.actions.AddNode(node), 'added a point'); - controller.enter(iD.modes.Select(node)); + controller.enter(iD.modes.Select(node, true)); }); map.keybinding().on('⎋.addpoint', function() { diff --git a/js/id/modes/draw_area.js b/js/id/modes/draw_area.js index 196a4de53..2f1fa6304 100644 --- a/js/id/modes/draw_area.js +++ b/js/id/modes/draw_area.js @@ -43,7 +43,7 @@ iD.modes.DrawArea = function(wayId) { if (datum.id === tailId || datum.id === headId) { if (way.nodes.length > 3) { history.undo(); - controller.enter(iD.modes.Select(way)); + controller.enter(iD.modes.Select(way, true)); } else { // Areas with less than 3 nodes gets deleted history.replace(iD.actions.DeleteWay(way.id)); @@ -94,7 +94,7 @@ iD.modes.DrawArea = function(wayId) { function ret() { d3.event.preventDefault(); history.replace(iD.actions.DeleteNode(node.id)); - controller.enter(iD.modes.Select(way)); + controller.enter(iD.modes.Select(way, true)); } surface diff --git a/js/id/modes/draw_line.js b/js/id/modes/draw_line.js index 4be2e5527..8442fbf80 100644 --- a/js/id/modes/draw_line.js +++ b/js/id/modes/draw_line.js @@ -48,7 +48,7 @@ iD.modes.DrawLine = function(wayId, direction) { iD.actions.AddWayNode(wayId, tailId, index), 'added to a line'); - controller.enter(iD.modes.Select(way)); + controller.enter(iD.modes.Select(way, true)); } else { history.replace(iD.actions.DeleteWay(way.id)); @@ -59,7 +59,7 @@ iD.modes.DrawLine = function(wayId, direction) { // finish the way history.undo(); - controller.enter(iD.modes.Select(way)); + controller.enter(iD.modes.Select(way, true)); } else if (datum.type === 'node' && datum.id !== node.id) { // connect the way to an existing node @@ -71,13 +71,14 @@ iD.modes.DrawLine = function(wayId, direction) { controller.enter(iD.modes.DrawLine(wayId, direction)); } else if (datum.type === 'way' || datum.midpoint) { + var choice; // connect the way to an existing way if (datum.midpoint) { // if clicked on midpoint datum.id = datum.way; choice = datum; } else { - var choice = iD.util.geo.chooseIndex(datum, d3.mouse(surface.node()), map); + choice = iD.util.geo.chooseIndex(datum, d3.mouse(surface.node()), map); } history.replace( @@ -120,7 +121,7 @@ iD.modes.DrawLine = function(wayId, direction) { function ret() { d3.event.preventDefault(); history.replace(iD.actions.DeleteNode(node.id)); - controller.enter(iD.modes.Select(way)); + controller.enter(iD.modes.Select(way, true)); } function undo() { diff --git a/js/id/modes/select.js b/js/id/modes/select.js index add2e3750..883acacb1 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -1,11 +1,11 @@ -iD.modes.Select = function(entity) { +iD.modes.Select = function(entity, initial) { var mode = { id: 'select', button: 'browse', entity: entity }; - var inspector = iD.ui.inspector(), + var inspector = iD.ui.inspector().initial(!!initial), behaviors; function remove() { diff --git a/js/id/ui/inspector.js b/js/id/ui/inspector.js index aa3ad3add..7837ba21f 100644 --- a/js/id/ui/inspector.js +++ b/js/id/ui/inspector.js @@ -2,6 +2,7 @@ iD.ui.inspector = function() { var event = d3.dispatch('changeTags', 'changeWayDirection', 'update', 'remove', 'close', 'splitWay'), taginfo = iD.taginfo(), + initial = false, tagList; function inspector(selection) { @@ -194,7 +195,8 @@ iD.ui.inspector = function() { helpBtn.append('span') .attr('class', 'icon inspect'); - if (tags.length === 1 && tags[0].key === '' && tags[0].value === '') { + if (initial && tags.length === 1 && + tags[0].key === '' && tags[0].value === '') { focusNewKey(); } @@ -271,7 +273,7 @@ iD.ui.inspector = function() { event.close(entity); } - inspector.tags = function (tags) { + inspector.tags = function(tags) { if (!arguments.length) { tags = {}; tagList.selectAll('li').each(function() { @@ -286,5 +288,10 @@ iD.ui.inspector = function() { } }; + inspector.initial = function(_) { + initial = _; + return inspector; + }; + return d3.rebind(inspector, event, 'on'); };