Merge remote-tracking branch 'systemed/master' into labels

Conflicts:
	js/id/renderer/map.js
This commit is contained in:
Ansis Brammanis
2013-01-22 14:55:15 -05:00
15 changed files with 264 additions and 113 deletions
+7
View File
@@ -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
+9 -18
View File
@@ -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;
}
@@ -783,9 +768,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
@@ -943,7 +933,7 @@ div.typeahead a:first-child {
border-radius: 0 0 4px 4px;
}
.modal-section .buttons {
.modal-section .buttons {
padding-top: 10px;
width: 100%;
}
@@ -993,6 +983,7 @@ div.typeahead a:first-child {
height:38px;
padding:10px 20px;
background:#fff;
color:#000;
font-weight: normal;
line-height: 21px;
border-radius:5px;
+4
View File
@@ -224,6 +224,10 @@ path.multipolygon.tag-amenity-parking {
fill: #edecc0;
}
path.multipolygon.tag-boundary {
fill: none;
}
/* highways */
path.shadow.tag-highway {
+17 -4
View File
@@ -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;
};
+4 -1
View File
@@ -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) {
@@ -202,7 +205,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);
+1 -1
View File
@@ -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() {
+2 -2
View File
@@ -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
+8 -8
View File
@@ -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() {
@@ -142,8 +143,6 @@ iD.modes.DrawLine = function(wayId, direction) {
});
d3.select('#undo').on('click.drawline', undo);
};
mode.exit = function() {
@@ -161,10 +160,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);
+33 -14
View File
@@ -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() {
@@ -34,7 +34,7 @@ iD.modes.Select = function (entity) {
}
}
mode.enter = function () {
mode.enter = function() {
var surface = mode.map.surface;
behaviors = [
@@ -47,22 +47,29 @@ 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)
.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
@@ -131,22 +138,34 @@ 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);
};
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('');
// 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);
});
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);
+39 -5
View File
@@ -1,7 +1,8 @@
iD.Hash = function() {
var hash = { hadHash: false },
s0, // cached location.hash
s0 = null, // cached location.hash
lat = 90 - 1e-8, // allowable latitude range
controller,
map;
var parser = function(map, s) {
@@ -20,9 +21,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,11 +36,37 @@ 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
}
}
// 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) {
@@ -48,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;
}
+47 -44
View File
@@ -1,8 +1,7 @@
iD.Map = function() {
var connection, history,
dimensions = [],
dispatch = d3.dispatch('move'),
translateStart,
dispatch = d3.dispatch('move', 'drawn'),
keybinding = d3.keybinding(),
projection = d3.geo.mercator().scale(1024),
roundedProjection = iD.svg.RoundProjection(projection),
@@ -33,19 +32,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());
@@ -66,22 +61,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);
@@ -102,17 +97,17 @@ 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)
.call(labels, 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)
.call(labels, graph, all, filter, projection);
dispatch.drawn(map);
}
function editOff() {
@@ -138,32 +133,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);
queueRedraw();
}
function resetTransform() {
if (!surface.style(transformProp)) return;
translateStart = null;
surface.style(transformProp, '');
tilegroup.style(transformProp, '');
redraw();
}
var redraw = _.throttle(function(difference) {
function redraw(difference) {
resetTransform();
dispatch.move(map);
surface.attr('data-zoom', ~~map.zoom());
tilegroup.call(background);
@@ -173,8 +171,13 @@ iD.Map = function() {
} else {
editOff();
}
transformStart = [
projection.scale(),
projection.translate().slice()];
return map;
}, 10);
}
var queueRedraw = _.debounce(redraw, 200);
function pointLocation(p) {
var translate = projection.translate(),
@@ -304,7 +307,7 @@ iD.Map = function() {
map.connection = function(_) {
if (!arguments.length) return connection;
connection = _;
connection.on('load', connectionLoad);
connection.on('load.tile', connectionLoad);
return map;
};
+16 -6
View File
@@ -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) {
@@ -149,9 +150,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 +174,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({
@@ -191,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();
}
@@ -268,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() {
@@ -283,5 +288,10 @@ iD.ui.inspector = function() {
}
};
inspector.initial = function(_) {
initial = _;
return inspector;
};
return d3.rebind(inspector, event, 'on');
};
+3 -2
View File
@@ -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('&');
};
+62
View File
@@ -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
+12 -8
View File
@@ -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,18 +23,18 @@ 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);
});
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 +71,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 +79,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");
});
});
});