diff --git a/index.html b/index.html
index 122569faa..51ddec95d 100644
--- a/index.html
+++ b/index.html
@@ -53,7 +53,8 @@
-
+
+
diff --git a/js/id/actions/move.js b/js/id/actions/move_node.js
similarity index 64%
rename from js/id/actions/move.js
rename to js/id/actions/move_node.js
index 21870c8d4..9204fcb67 100644
--- a/js/id/actions/move.js
+++ b/js/id/actions/move_node.js
@@ -1,8 +1,8 @@
// https://github.com/openstreetmap/josm/blob/mirror/src/org/openstreetmap/josm/command/MoveCommand.java
// https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/MoveNodeAction.as
-iD.actions.Move = function(entityId, loc) {
+iD.actions.MoveNode = function(nodeId, loc) {
return function(graph) {
- var entity = graph.entity(entityId);
- return graph.replace(entity.update({loc: loc}));
+ var node = graph.entity(nodeId);
+ return graph.replace(node.update({loc: loc}));
};
};
diff --git a/js/id/actions/move_way.js b/js/id/actions/move_way.js
new file mode 100644
index 000000000..0ef14e4f3
--- /dev/null
+++ b/js/id/actions/move_way.js
@@ -0,0 +1,14 @@
+iD.actions.MoveWay = function(wayId, dxdy, projection) {
+ return function(graph) {
+ var way = graph.entity(wayId);
+
+ _.uniq(way.nodes).forEach(function(id) {
+ var node = graph.entity(id),
+ start = projection(node.loc),
+ end = projection.invert([start[0] + dxdy[0], start[1] + dxdy[1]]);
+ graph = iD.actions.MoveNode(id, end)(graph);
+ });
+
+ return graph;
+ };
+};
diff --git a/js/id/id.js b/js/id/id.js
index 071c0773d..8a65570e4 100644
--- a/js/id/id.js
+++ b/js/id/id.js
@@ -36,14 +36,36 @@ window.iD = function(container) {
.call(bootstrap.tooltip().placement('bottom'))
.on('click', function (mode) { controller.enter(mode); });
- map.on('move.disable-buttons', function() {
+ function disableTooHigh() {
if (map.zoom() < 16) {
buttons.attr('disabled', 'disabled');
controller.enter(iD.modes.Browse());
} else {
buttons.attr('disabled', null);
}
- });
+ }
+
+ var showUsers = _.debounce(function() {
+ var users = {},
+ entities = map.history().graph().entities;
+ for (var i in entities) {
+ users[entities[i].user] = true;
+ if (Object.keys(users).length > 10) break;
+ }
+ var u = Object.keys(users);
+ var l = d3.select('#user-list')
+ .selectAll('a.user-link').data(u);
+ l.enter().append('a')
+ .attr('class', 'user-link')
+ .attr('href', function(d) {
+ return 'http://api06.dev.openstreetmap.org/user/' + d;
+ })
+ .text(String);
+ l.exit().remove();
+ }, 1000);
+
+ map.on('move.disable-buttons', disableTooHigh)
+ .on('move.show-users', showUsers);
buttons.append('span')
.attr('class', function(d) {
@@ -143,12 +165,15 @@ window.iD = function(container) {
.attr('class', 'inspector-wrap fillL')
.style('display', 'none');
- this.append('div')
+ var about = this.append('div')
.attr('id', 'about')
.html("code " +
"report a bug" +
"
");
+ about.append('div')
+ .attr('id', 'user-list');
+
history.on('change.buttons', function() {
var undo = history.undoAnnotation(),
redo = history.redoAnnotation();
@@ -166,7 +191,7 @@ window.iD = function(container) {
map.size(m.size());
};
- var keybinding = d3.keybinding()
+ map.keybinding()
.on('a', function(evt, mods) {
controller.enter(iD.modes.AddArea());
})
@@ -180,8 +205,6 @@ window.iD = function(container) {
if (mods === '⇧⌘') history.redo();
if (mods === '⌘') history.undo();
});
- d3.select(document).call(keybinding);
- map.keybinding(keybinding);
var hash = iD.Hash().map(map);
diff --git a/js/id/modes/add_line.js b/js/id/modes/add_line.js
index bf5172b4a..baa4364bc 100644
--- a/js/id/modes/add_line.js
+++ b/js/id/modes/add_line.js
@@ -15,7 +15,7 @@ iD.modes.AddLine = function() {
map.dblclickEnable(false)
.hint('Click on the map to start drawing an road, path, or route.');
- map.surface.on('click.addroad', function() {
+ map.surface.on('click.addline', function() {
var datum = d3.select(d3.event.target).datum() || {},
way = iD.Way({ tags: { highway: 'residential' } }),
direction = 'forward';
@@ -62,7 +62,7 @@ iD.modes.AddLine = function() {
controller.enter(iD.modes.DrawLine(way.id, direction));
});
- map.keybinding().on('⎋.addroad', function() {
+ map.keybinding().on('⎋.addline', function() {
controller.exit();
});
};
@@ -70,8 +70,8 @@ iD.modes.AddLine = function() {
mode.exit = function() {
mode.map.dblclickEnable(true);
mode.map.hint(false);
- mode.map.surface.on('click.addroad', null);
- mode.map.keybinding().on('⎋.addroad', null);
+ mode.map.surface.on('click.addline', null);
+ mode.map.keybinding().on('⎋.addline', null);
};
return mode;
diff --git a/js/id/modes/drag_features.js b/js/id/modes/drag_features.js
index 7a38883ed..d20119080 100644
--- a/js/id/modes/drag_features.js
+++ b/js/id/modes/drag_features.js
@@ -4,6 +4,7 @@ iD.modes._dragFeatures = function(mode) {
var dragbehavior = d3.behavior.drag()
.origin(function(entity) {
var p = mode.map.projection(entity.loc);
+ d3.event.sourceEvent.stopPropagation();
return { x: p[0], y: p[1] };
})
.on('drag', function(entity) {
@@ -20,11 +21,11 @@ iD.modes._dragFeatures = function(mode) {
} else {
dragging = entity;
mode.history.perform(
- iD.actions.Move(dragging.id, loc));
+ iD.actions.MoveNode(dragging.id, loc));
}
}
- mode.history.replace(iD.actions.Move(dragging.id, loc));
+ mode.history.replace(iD.actions.MoveNode(dragging.id, loc));
})
.on('dragend', function (entity) {
if (!dragging) return;
diff --git a/js/id/modes/draw_area.js b/js/id/modes/draw_area.js
index ae5925ea0..c31544e5c 100644
--- a/js/id/modes/draw_area.js
+++ b/js/id/modes/draw_area.js
@@ -24,7 +24,7 @@ iD.modes.DrawArea = function(wayId) {
iD.actions.AddWayNode(way.id, node.id, -1));
function mousemove() {
- history.replace(iD.actions.Move(node.id, map.mouseCoordinates()));
+ history.replace(iD.actions.MoveNode(node.id, map.mouseCoordinates()));
}
function click() {
@@ -95,24 +95,32 @@ iD.modes.DrawArea = function(wayId) {
controller.enter(iD.modes.Browse());
}
- map.surface.on('mousemove.drawarea', mousemove);
- map.surface.on('click.drawarea', click);
- map.keybinding().on('⎋.drawarea', esc)
+ map.surface
+ .on('mousemove.drawarea', mousemove)
+ .on('click.drawarea', click);
+
+ map.keybinding()
+ .on('⎋.drawarea', esc)
.on('⌫.drawarea', backspace)
- .on('delete.drawarea', del)
+ .on('⌦.drawarea', del)
.on('↩.drawarea', ret);
};
mode.exit = function() {
- mode.map.hint(false);
- mode.map.fastEnable(true);
+ mode.map
+ .hint(false)
+ .fastEnable(true);
+
mode.map.surface
.on('mousemove.drawarea', null)
.on('click.drawarea', null);
- mode.map.keybinding().on('⎋.drawarea', null)
+
+ mode.map.keybinding()
+ .on('⎋.drawarea', null)
.on('⌫.drawarea', null)
- .on('delete.drawarea', null)
+ .on('⌦.drawarea', null)
.on('↩.drawarea', null);
+
window.setTimeout(function() {
mode.map.dblclickEnable(true);
}, 1000);
diff --git a/js/id/modes/draw_line.js b/js/id/modes/draw_line.js
index 09789c8f7..74c16095f 100644
--- a/js/id/modes/draw_line.js
+++ b/js/id/modes/draw_line.js
@@ -24,11 +24,11 @@ iD.modes.DrawLine = function(wayId, direction) {
iD.actions.AddNode(node),
iD.actions.AddWayNode(wayId, node.id, index));
- map.surface.on('mousemove.drawline', function() {
- history.replace(iD.actions.Move(node.id, map.mouseCoordinates()));
- });
+ function mousemove() {
+ history.replace(iD.actions.MoveNode(node.id, map.mouseCoordinates()));
+ }
- map.surface.on('click.drawline', function() {
+ function click() {
var datum = d3.select(d3.event.target).datum() || {};
if (datum.id === tailId) {
@@ -72,7 +72,7 @@ iD.modes.DrawLine = function(wayId, direction) {
controller.enter(iD.modes.DrawLine(wayId, direction));
}
- });
+ }
function esc() {
history.replace(
@@ -108,23 +108,32 @@ iD.modes.DrawLine = function(wayId, direction) {
controller.enter(iD.modes.Browse());
}
- map.keybinding().on('⎋.drawline', esc)
+ map.surface
+ .on('mousemove.drawline', mousemove)
+ .on('click.drawline', click);
+
+ map.keybinding()
+ .on('⎋.drawline', esc)
.on('⌫.drawline', backspace)
- .on('delete.drawline', del)
+ .on('⌦.drawline', del)
.on('↩.drawline', ret);
};
mode.exit = function() {
- mode.map.hint(false);
- mode.map.fastEnable(true);
+ mode.map
+ .hint(false)
+ .fastEnable(true);
mode.map.surface
.on('mousemove.drawline', null)
.on('click.drawline', null);
- mode.map.keybinding().on('⎋.drawline', null)
+
+ mode.map.keybinding()
+ .on('⎋.drawline', null)
.on('⌫.drawline', null)
- .on('delete.drawline', null)
+ .on('⌦.drawline', null)
.on('↩.drawline', null);
+
window.setTimeout(function() {
mode.map.dblclickEnable(true);
}, 1000);
diff --git a/js/id/modes/select.js b/js/id/modes/select.js
index daf5c9016..a94e54103 100644
--- a/js/id/modes/select.js
+++ b/js/id/modes/select.js
@@ -1,8 +1,11 @@
iD.modes.Select = function (entity) {
var mode = {
- button: ''
- },
- inspector = iD.Inspector(),
+ id: 'select',
+ button: 'browse',
+ entity: entity
+ };
+
+ var inspector = iD.Inspector(),
dragging, target;
var dragWay = d3.behavior.drag()
@@ -11,48 +14,45 @@ iD.modes.Select = function (entity) {
return { x: p[0], y: p[1] };
})
.on('drag', function(entity) {
- if (!mode.map.dragEnable()) return;
-
d3.event.sourceEvent.stopPropagation();
if (!dragging) {
- dragging = iD.util.trueObj([entity.id].concat(
- _.pluck(mode.history.graph().parentWays(entity.id), 'id')));
+ dragging = true;
mode.history.perform(iD.actions.Noop());
}
- _.uniq(_.pluck(entity.nodes, 'id'))
- .forEach(function(id) {
- var node = mode.history.graph().entity(id),
- start = mode.map.projection(node.loc),
- end = mode.map.projection.invert([
- start[0] + d3.event.dx,
- start[1] + d3.event.dy]);
- mode.history.replace(iD.actions.Move(id, end));
- });
+ mode.history.replace(iD.actions.MoveWay(entity.id, [d3.event.dx, d3.event.dy], mode.map.projection));
})
.on('dragend', function () {
- if (!mode.map.dragEnable() || !dragging) return;
+ if (!dragging) return;
dragging = undefined;
mode.map.redraw();
});
function remove() {
- switch (entity.type) {
- case 'way':
- mode.history.perform(iD.actions.DeleteWay(entity.id));
- break;
- case 'node':
- mode.history.perform(iD.actions.DeleteNode(entity.id));
+ if (entity.type === 'way') {
+ mode.history.perform(
+ iD.actions.DeleteWay(entity.id),
+ 'deleted a way');
+ } else if (entity.type === 'node') {
+ var parents = mode.history.graph().parentWays(entity.id),
+ operations = [iD.actions.DeleteNode(entity.id)];
+ parents.forEach(function(parent) {
+ if (_.uniq(parent.nodes).length === 1) operations.push(iD.actions.DeleteWay(parent.id));
+ });
+ mode.history.perform.apply(mode.history,
+ operations.concat(['deleted a node']));
}
mode.controller.exit();
}
mode.enter = function () {
- target = mode.map.surface.selectAll("*")
+ target = mode.map.surface.selectAll('*')
.filter(function (d) { return d === entity; });
+ iD.modes._dragFeatures(mode);
+
d3.select('.inspector-wrap')
.style('display', 'block')
.style('opacity', 1)
@@ -60,11 +60,23 @@ iD.modes.Select = function (entity) {
.call(inspector);
inspector.on('changeTags', function(d, tags) {
- mode.history.perform(iD.actions.ChangeEntityTags(d.id, tags));
+ mode.history.perform(
+ iD.actions.ChangeEntityTags(d.id, tags),
+ 'changed tags');
+
}).on('changeWayDirection', function(d) {
- mode.history.perform(iD.actions.ReverseWay(d));
+ mode.history.perform(
+ iD.actions.ReverseWay(d.id),
+ 'reversed a way');
+
+ }).on('splitWay', function(d) {
+ mode.history.perform(
+ iD.actions.SplitWay(d.id),
+ 'split a way on a node');
+
}).on('remove', function() {
remove();
+
}).on('close', function() {
mode.controller.exit();
});
@@ -73,7 +85,7 @@ iD.modes.Select = function (entity) {
target.call(dragWay);
}
- mode.map.surface.on("click.browse", function () {
+ mode.map.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));
@@ -100,6 +112,7 @@ iD.modes.Select = function (entity) {
}
mode.map.surface.on("click.browse", null);
+ mode.map.surface.on('mousedown.latedrag', null);
mode.map.keybinding().on('⌫.browse', null);
mode.map.selection(null);
diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js
index 7bb1a08db..93a9dec86 100644
--- a/js/id/renderer/map.js
+++ b/js/id/renderer/map.js
@@ -4,7 +4,7 @@ iD.Map = function() {
dispatch = d3.dispatch('move'),
selection = null, hover = null,
translateStart,
- keybinding,
+ keybinding = d3.keybinding(),
projection = d3.geo.mercator().scale(1024),
zoom = d3.behavior.zoom()
.translate(projection.translate())
@@ -12,7 +12,6 @@ iD.Map = function() {
.scaleExtent([1024, 256 * Math.pow(2, 24)])
.on('zoom', zoomPan),
dblclickEnabled = true,
- dragging = false,
fastEnabled = true,
notice,
background = iD.Background()
@@ -63,6 +62,8 @@ iD.Map = function() {
map.size(this.size());
map.surface = surface;
+
+ d3.select(document).call(keybinding);
}
function pxCenter() { return [dimensions[0] / 2, dimensions[1] / 2]; }
diff --git a/js/id/ui/inspector.js b/js/id/ui/inspector.js
index c5a8991c6..014ed09a3 100644
--- a/js/id/ui/inspector.js
+++ b/js/id/ui/inspector.js
@@ -48,103 +48,109 @@ iD.Inspector = function() {
var inspectorwrap = selection
.append('ul').attr('class', 'inspector-inner tag-wrap fillL2');
- inspectorwrap.append('h4').text('Edit tags')
+ inspectorwrap.append('h4').text('Edit tags');
inspectorwrap
.data(['tag', 'value', ''])
.enter();
function removeTag(d) {
- var tags = pad(grabtags());
- delete tags[d.key];
- draw(tags);
+ draw(grabtags().filter(function(t) { return t.key !== d.key; }));
}
function draw(data) {
- var tr = inspectorwrap.selectAll('li')
- .data(d3.entries(data), function(d) { return [d.key, d.value]; });
- tr.exit().remove();
- var row = tr.enter().append('li').attr('class','tag-row');
- var inputs = row.append('div').attr('class','input-wrap').selectAll('input')
- .data(function(d) { return [d, d]; });
- inputs.enter().append('input')
+
+ var li = inspectorwrap.selectAll('li')
+ .data(data, function(d) { return [d.key, d.value]; });
+
+ li.exit().remove();
+
+ var row = li.enter().append('li').attr('class','tag-row');
+ var inputs = row.append('div').attr('class','input-wrap');
+
+ function setValue(d, i) { d.value = this.value; }
+
+ function emptyTag(d) { return d.key === ''; }
+
+ function pushMore(d, i) {
+ if (d3.event.keyCode === 9) {
+ var tags = grabtags();
+ if (i == tags.length - 1 && !tags.filter(emptyTag).length) {
+ draw(tags.concat([{ key: '', value: '' }]));
+ }
+ }
+ }
+
+ function bindTypeahead(d, i) {
+ var selection = d3.select(this);
+ selection.call(d3.typeahead()
+ .data(function(selection, callback) {
+ taginfo.values(selection.datum().key, function(err, data) {
+ callback(data.data);
+ });
+ }));
+ }
+
+ inputs.append('input')
.property('type', 'text')
- .attr('class', function(d, i) {
- return i ? 'value' : 'key';
- })
- .property('value', function(d, i) { return d[i ? 'value' : 'key']; })
- .on('keyup.update', function(d, i) {
- d[i ? 'value' : 'key'] = this.value;
- update();
- })
- .each(function(d, i) {
- if (!i) return;
- var selection = d3.select(this);
- selection.call(d3.typeahead()
- .data(function(selection, callback) {
- update();
- taginfo.values(selection.datum().key, function(err, data) {
- callback(data.data);
- });
- }));
- });
- row.append('button')
- .html("")
+ .attr('class', 'key')
+ .property('value', function(d, i) { return d.key; })
+ .on('keyup.update', setValue);
+
+ inputs.append('input')
+ .property('type', 'text')
+ .attr('class', 'value')
+ .property('value', function(d, i) { return d.value; })
+ .on('keyup.update', setValue)
+ .on('keydown.push-more', pushMore)
+ .each(bindTypeahead);
+
+ removeBtn = row.append('button')
.attr('class','remove minor')
.on('click', removeTag);
- row.append('button').attr('class', 'tag-help minor').append('a')
- .html("")
+
+ removeBtn.append('span').attr('class', 'icon remove')
+
+ helpBtn = row.append('button').attr('class', 'tag-help minor').append('a')
.attr('target', '_blank')
.attr('tabindex', -1)
.attr('href', function(d) {
return 'http://taginfo.openstreetmap.org/keys/' + d.key;
});
- }
-
- // Remove any blank key-values
- function clean(x) {
- for (var i in x) {
- // undefined is cast to a string as an object key
- if (!i || i === 'undefined') delete x[i];
- }
- return x;
- }
-
- // Add a blank row for new tags
- function pad(x) {
- if (!x['']) x[''] = '';
- return x;
+ helpBtn.append('span').attr('class', 'icon inspect')
}
function grabtags() {
- var grabbed = {};
- function grab(d) { if (d.key !== undefined) grabbed[d.key] = d.value; }
- inspectorwrap.selectAll('input').each(grab);
+ var grabbed = [];
+ function grab(d) { grabbed.push(d); }
+ inspectorwrap.selectAll('li').each(grab);
return grabbed;
}
- // fill values and add blank field if necessary
- function update() {
- draw(pad(grabtags()));
+ function unentries(entries) {
+ return d3.nest()
+ .key(function(d) { return d.key; })
+ .rollup(function(v) { return v[0].value; })
+ .map(entries);
}
- var data = _.clone(entity.tags);
- draw(data);
- update();
+ draw(d3.entries(_.clone(entity.tags)));
selection.select('input').node().focus();
selection.append('div')
.attr('class', 'inspector-buttons').call(drawbuttons);
+ function apply(entity) {
+ event.changeTags(entity, unentries(grabtags()));
+ event.close(entity);
+ }
+
function drawbuttons(selection) {
selection.append('button')
.attr('class', 'apply wide action')
.html("Apply")
- .on('click', function(entity) {
- event.changeTags(entity, clean(grabtags()));
- event.close(entity);
- });
+ .on('click', apply);
selection.append('button')
.attr('class', 'delete wide action fr')
.html("Delete")
diff --git a/js/id/ui/layerswitcher.js b/js/id/ui/layerswitcher.js
index e5f1a8b32..f79ab169c 100644
--- a/js/id/ui/layerswitcher.js
+++ b/js/id/ui/layerswitcher.js
@@ -48,36 +48,36 @@ iD.layerswitcher = function(map) {
var opa = content
.append('div')
- .attr('class', 'opacity-options-wrapper fillL2')
+ .attr('class', 'opacity-options-wrapper fillL2');
- opa.append('h4').text('Layers')
+ opa.append('h4').text('Layers');
- opa.append('ul')
- .attr('class', 'opacity-options')
- .selectAll('div.opacity')
- .data(opacities)
- .enter()
- .append('li')
- .attr('data-original-title', function(d) {
- return (d * 100) + "% opacity";
- })
- .on('click.set-opacity', function(d) {
- d3.select('#tile-g')
- .transition()
- .style('opacity', d)
- .attr('data-opacity', d);
- d3.selectAll('.opacity-options li')
- .classed('selected', false);
- d3.select(this)
- .classed('selected', true);
- })
- .html("