Merge branch 'master' of github.com:systemed/iD

This commit is contained in:
Saman Bemel-Benrud
2013-01-29 13:24:16 -05:00
24 changed files with 389 additions and 209 deletions

View File

@@ -38,6 +38,8 @@ all: \
js/id/behavior/*.js \
js/id/modes.js \
js/id/modes/*.js \
js/id/operations.js \
js/id/operations/*.js \
js/id/controller/*.js \
js/id/graph/*.js \
js/id/renderer/*.js \

View File

@@ -16,7 +16,7 @@ g.point .shadow {
transition: transform 100ms linear;
-moz-transition: fill 100ms linear;
}
g.point.hover .shadow {
.behavior-hover g.point.hover .shadow {
fill: #E96666;
fill-opacity: 0.3;
}
@@ -106,7 +106,7 @@ g.vertex .shadow {
transition: transform 100ms linear;
-moz-transition: fill 100ms linear;
}
g.vertex.hover .shadow {
.behavior-hover g.vertex.hover .shadow {
fill: #E96666;
fill-opacity: 0.3;
}
@@ -120,7 +120,7 @@ g.vertex.selected .shadow {
g.midpoint .fill {
fill:#aaa;
}
g.midpoint .fill.hover {
.behavior-hover g.midpoint .fill.hover {
fill:#fff;
stroke:#000;
}
@@ -133,7 +133,7 @@ g.midpoint .shadow {
transition: transform 100ms linear;
-moz-transition: fill 100ms linear;
}
g.midpoint .shadow.hover {
.behavior-hover g.midpoint .shadow.hover {
fill:#E96666;
fill-opacity: 0.3;
}
@@ -161,7 +161,7 @@ path.shadow {
-webkit-transition: stroke 100ms linear;
}
path.shadow.hover {
.behavior-hover path.shadow.hover {
stroke: #E96666;
stroke-opacity: 0.3;
}
@@ -691,17 +691,17 @@ text.point.tag-amenity {
cursor:url(../img/cursor-draw.png) 9 9, auto;
}
.mode-draw-line .way,
.mode-draw-area .way,
.mode-add-line .way,
.mode-add-area .way {
.mode-draw-line .behavior-hover .way,
.mode-draw-area .behavior-hover .way,
.mode-add-line .behavior-hover .way,
.mode-add-area .behavior-hover .way {
cursor:url(../img/cursor-draw-connect-line.png) 9 9, auto;
}
.mode-draw-line .vertex,
.mode-draw-area .vertex,
.mode-add-line .vertex,
.mode-add-area .vertex {
.mode-draw-line .behavior-hover .vertex,
.mode-draw-area .behavior-hover .vertex,
.mode-add-line .behavior-hover .vertex,
.mode-add-area .behavior-hover .vertex {
cursor:url(../img/cursor-draw-connect-vertex.png) 9 9, auto;
}

View File

@@ -92,7 +92,6 @@
<script src='js/id/behavior/drag.js'></script>
<script src='js/id/behavior/drag_midpoint.js'></script>
<script src='js/id/behavior/drag_node.js'></script>
<script src='js/id/behavior/drag_way.js'></script>
<script src='js/id/behavior/draw.js'></script>
<script src='js/id/behavior/draw_way.js'></script>
<script src='js/id/behavior/hover.js'></script>
@@ -104,8 +103,17 @@
<script src='js/id/modes/browse.js'></script>
<script src='js/id/modes/draw_area.js'></script>
<script src='js/id/modes/draw_line.js'></script>
<script src='js/id/modes/move_way.js'></script>
<script src='js/id/modes/select.js'></script>
<script src='js/id/operations.js'></script>
<script src='js/id/operations/circular.js'></script>
<script src='js/id/operations/delete.js'></script>
<script src='js/id/operations/move.js'></script>
<script src='js/id/operations/reverse.js'></script>
<script src='js/id/operations/split.js'></script>
<script src='js/id/operations/unjoin.js'></script>
<script src='js/id/controller/controller.js'></script>
<script src='js/id/graph/entity.js'></script>

View File

@@ -51,7 +51,7 @@ iD.actions.Circular = function(wayId, map) {
};
action.enabled = function(graph) {
return true;
return graph.entity(wayId).isClosed();
};
return action;

View File

@@ -1,6 +1,6 @@
// https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/DeleteNodeAction.as
iD.actions.DeleteNode = function(nodeId) {
var action = function(graph) {
return function(graph) {
var node = graph.entity(nodeId);
graph.parentWays(node)
@@ -20,10 +20,4 @@ iD.actions.DeleteNode = function(nodeId) {
return graph.remove(node);
};
action.enabled = function(graph) {
return true;
};
return action;
};

View File

@@ -1,6 +1,6 @@
// https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/DeleteWayAction.as
iD.actions.DeleteWay = function(wayId) {
var action = function(graph) {
return function(graph) {
var way = graph.entity(wayId);
graph.parentRelations(way)
@@ -27,10 +27,4 @@ iD.actions.DeleteWay = function(wayId) {
return graph.remove(way);
};
action.enabled = function(graph) {
return true;
};
return action;
};

View File

@@ -53,7 +53,7 @@ iD.actions.ReverseWay = function(wayId) {
}
}
var action = function(graph) {
return function(graph) {
var way = graph.entity(wayId),
nodes = way.nodes.slice().reverse(),
tags = {}, key, role;
@@ -73,10 +73,4 @@ iD.actions.ReverseWay = function(wayId) {
return graph.replace(way.update({nodes: nodes, tags: tags}));
};
action.enabled = function(graph) {
return true;
};
return action;
};

View File

@@ -3,11 +3,9 @@ iD.behavior.AddWay = function(mode) {
history = mode.history,
controller = mode.controller,
event = d3.dispatch('startFromNode', 'startFromWay', 'start'),
hover, draw;
function add() {
var datum = d3.select(d3.event.target).datum() || {};
draw;
function add(datum) {
if (datum.type === 'node') {
event.startFromNode(datum);
} else if (datum.type === 'way') {
@@ -26,8 +24,7 @@ iD.behavior.AddWay = function(mode) {
.minzoom(16)
.dblclickEnable(false);
surface.call(hover)
.call(draw);
surface.call(draw);
};
addWay.off = function(surface) {
@@ -39,16 +36,13 @@ iD.behavior.AddWay = function(mode) {
map.dblclickEnable(true);
}, 1000);
surface.call(hover.off)
.call(draw.off);
surface.call(draw.off);
};
addWay.cancel = function() {
controller.exit();
};
hover = iD.behavior.Hover();
draw = iD.behavior.Draw()
.on('add', add)
.on('cancel', addWay.cancel)

View File

@@ -1,28 +0,0 @@
iD.behavior.DragWay = function(mode) {
var history = mode.history,
projection = mode.map.projection;
return iD.behavior.drag()
.delegate('.casing, .stroke, .area')
.filter(function(d) {
return d && d.id === mode.entity.id;
})
.origin(function(entity) {
return projection(history.graph().entity(entity.nodes[0]).loc);
})
.on('start', function() {
history.perform(
iD.actions.Noop());
})
.on('move', function(entity) {
d3.event.sourceEvent.stopPropagation();
history.replace(
iD.actions.MoveWay(entity.id, d3.event.delta, projection),
'moved a way');
})
.on('end', function() {
history.replace(
iD.actions.Noop(),
'moved a way');
});
};

View File

@@ -1,45 +1,64 @@
iD.behavior.Draw = function () {
var event = d3.dispatch('move', 'add', 'undo', 'cancel', 'finish'),
keybinding = d3.keybinding('draw'),
down;
down, surface, hover;
function datum() {
if (d3.event.altKey) {
return {};
} else {
return d3.event.target.__data__ || {};
}
}
function mousedown() {
down = true;
}
function mouseup() {
down = false;
}
function mousemove() {
if (!down) {
event.move(datum());
}
}
function click() {
event.add(datum());
}
function keydown() {
if (d3.event.keyCode === d3.keybinding.modifierCodes.alt) {
surface.call(hover.off);
}
}
function keyup() {
if (d3.event.keyCode === d3.keybinding.modifierCodes.alt) {
surface.call(hover);
}
}
function backspace() {
d3.event.preventDefault();
event.undo();
}
function del() {
d3.event.preventDefault();
event.cancel();
}
function ret() {
d3.event.preventDefault();
event.finish();
}
function draw(selection) {
function mousemove() {
if (!down) event.move();
}
function click() {
event.add();
}
function mousedown() {
down = true;
}
function mouseup() {
down = false;
}
function backspace() {
d3.event.preventDefault();
event.undo();
}
function del() {
d3.event.preventDefault();
event.cancel();
}
function ret() {
d3.event.preventDefault();
event.finish();
}
selection
.on('mousedown.draw', mousedown)
.on('mouseup.draw', mouseup)
.on('mousemove.draw', mousemove)
.on('click.draw', click);
surface = selection;
hover = iD.behavior.Hover();
keybinding
.on('⌫', backspace)
@@ -47,18 +66,33 @@ iD.behavior.Draw = function () {
.on('⎋', ret)
.on('↩', ret);
selection
.on('mousedown.draw', mousedown)
.on('mouseup.draw', mouseup)
.on('mousemove.draw', mousemove)
.on('click.draw', click)
.call(hover);
d3.select(document)
.call(keybinding);
.call(keybinding)
.on('keydown.draw', keydown)
.on('keyup.draw', keyup);
return draw;
}
draw.off = function(selection) {
selection
.on('mousedown.draw', null)
.on('mouseup.draw', null)
.on('mousemove.draw', null)
.on('click.draw', null);
.on('click.draw', null)
.call(hover.off);
keybinding.off();
d3.select(document)
.call(keybinding.off)
.on('keydown.draw', null)
.on('keyup.draw', null);
};
return d3.rebind(draw, event, 'on');

View File

@@ -5,7 +5,7 @@ iD.behavior.DrawWay = function(wayId, headId, tailId, index, mode, baseGraph) {
event = d3.dispatch('add', 'addHead', 'addTail', 'addNode', 'addWay'),
way = mode.history.graph().entity(wayId),
finished = false,
hover, draw;
draw;
var node = iD.Node({loc: map.mouseCoordinates()}),
nodeId = node.id;
@@ -14,13 +14,19 @@ iD.behavior.DrawWay = function(wayId, headId, tailId, index, mode, baseGraph) {
iD.actions.AddNode(node),
iD.actions.AddWayNode(wayId, node.id, index));
function move() {
history.replace(iD.actions.MoveNode(nodeId, map.mouseCoordinates()));
function move(datum) {
var loc = map.mouseCoordinates();
if (datum.type === 'node' || datum.midpoint) {
loc = datum.loc;
} else if (datum.type === 'way') {
loc = iD.geo.chooseIndex(datum, d3.mouse(map.surface.node()), map).loc;
}
history.replace(iD.actions.MoveNode(nodeId, loc));
}
function add() {
var datum = d3.select(d3.event.target).datum() || {};
function add(datum) {
if (datum.id === headId) {
event.addHead(datum);
} else if (datum.id === tailId) {
@@ -47,8 +53,7 @@ iD.behavior.DrawWay = function(wayId, headId, tailId, index, mode, baseGraph) {
.minzoom(16)
.dblclickEnable(false);
surface.call(hover)
.call(draw)
surface.call(draw)
.selectAll('.way, .node')
.filter(function (d) { return d.id === wayId || d.id === nodeId; })
.classed('active', true);
@@ -68,8 +73,7 @@ iD.behavior.DrawWay = function(wayId, headId, tailId, index, mode, baseGraph) {
map.dblclickEnable(true);
}, 1000);
surface.call(hover.off)
.call(draw.off)
surface.call(draw.off)
.selectAll('.way, .node')
.classed('active', false);
@@ -145,8 +149,6 @@ iD.behavior.DrawWay = function(wayId, headId, tailId, index, mode, baseGraph) {
controller.enter(iD.modes.Browse());
};
hover = iD.behavior.Hover();
draw = iD.behavior.Draw()
.on('move', move)
.on('add', add)

View File

@@ -9,6 +9,8 @@
*/
iD.behavior.Hover = function () {
var hover = function(selection) {
selection.classed('behavior-hover', true);
selection.on('mouseover.hover', function () {
var datum = d3.event.target.__data__;
if (datum) {
@@ -25,7 +27,8 @@ iD.behavior.Hover = function () {
};
hover.off = function(selection) {
selection.on('mouseover.hover', null)
selection.classed('behavior-hover', false)
.on('mouseover.hover', null)
.on('mouseout.hover', null);
};

69
js/id/modes/move_way.js Normal file
View File

@@ -0,0 +1,69 @@
iD.modes.MoveWay = function(wayId) {
var mode = {
id: 'move-way'
};
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;
var way = graph.entity(wayId),
origin = d3.mouse(selection.node());
history.perform(
iD.actions.Noop(),
'moved a way');
function move() {
var p = d3.mouse(selection.node()),
delta = [p[0] - origin[0],
p[1] - origin[1]];
origin = p;
history.replace(
iD.actions.MoveWay(wayId, delta, projection),
'moved a way');
}
function finish() {
d3.event.stopPropagation();
controller.enter(iD.modes.Select(way, true));
}
function cancel() {
history.pop();
controller.enter(iD.modes.Select(way, true));
}
selection
.on('mousemove.move-way', move)
.on('click.move-way', finish);
keybinding
.on('⎋', cancel)
.on('↩', finish);
d3.select(document)
.call(keybinding);
};
mode.exit = function() {
var map = mode.map,
selection = map.surface;
selection
.on('mousemove.move-way', null)
.on('click.move-way', null);
keybinding.off();
};
return mode;
};

View File

@@ -43,7 +43,6 @@ iD.modes.Select = function(entity, initial) {
behaviors = [
iD.behavior.Hover(),
iD.behavior.DragNode(mode),
iD.behavior.DragWay(mode),
iD.behavior.DragMidpoint(mode)];
behaviors.forEach(function(behavior) {
@@ -138,7 +137,7 @@ iD.modes.Select = function(entity, initial) {
})
.classed('selected', true);
radialMenu = iD.ui.RadialMenu(entity, history, mode.map);
radialMenu = iD.ui.RadialMenu(entity, mode);
if (d3.event && !initial) {
var loc = map.mouseCoordinates();

1
js/id/operations.js Normal file
View File

@@ -0,0 +1 @@
iD.operations = {}

View File

@@ -0,0 +1,34 @@
iD.operations.Circular = function(entityId, mode) {
var action = iD.actions.Circular(entityId, mode.map);
var operation = function(history) {
var graph = history.graph(),
entity = graph.entity(entityId),
geometry = entity.geometry(graph);
if (geometry === 'line') {
history.perform(
action,
'made a line circular');
} else if (geometry === 'area') {
history.perform(
action,
'made an area circular');
}
};
operation.available = function(graph) {
var entity = graph.entity(entityId);
return entity.geometry(graph) === 'area' || entity.geometry(graph) === 'line';
};
operation.enabled = function(graph) {
return action.enabled(graph);
};
operation.id = "circular";
operation.title = "Circular";
return operation;
};

View File

@@ -0,0 +1,42 @@
iD.operations.Delete = function(entityId) {
var operation = function(history) {
var graph = history.graph(),
entity = graph.entity(entityId),
geometry = 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');
}
};
operation.available = function(graph) {
var entity = graph.entity(entityId);
return _.contains(['vertex', 'point', 'line', 'area'], entity.geometry(graph));
};
operation.enabled = function() {
return true;
};
operation.id = "delete";
operation.title = "Delete";
return operation;
};

18
js/id/operations/move.js Normal file
View File

@@ -0,0 +1,18 @@
iD.operations.Move = function(entityId, mode) {
var operation = function() {
mode.controller.enter(iD.modes.MoveWay(entityId));
};
operation.available = function(graph) {
return graph.entity(entityId).type === 'way';
};
operation.enabled = function() {
return true;
};
operation.id = "move";
operation.title = "Move";
return operation;
};

View File

@@ -0,0 +1,21 @@
iD.operations.Reverse = function(entityId) {
var operation = function(history) {
history.perform(
iD.actions.ReverseWay(entityId),
'reversed a line');
};
operation.available = function(graph) {
var entity = graph.entity(entityId);
return entity.geometry(graph) === 'line';
};
operation.enabled = function() {
return true;
};
operation.id = "reverse";
operation.title = "Reverse";
return operation;
};

21
js/id/operations/split.js Normal file
View File

@@ -0,0 +1,21 @@
iD.operations.Split = function(entityId) {
var action = iD.actions.SplitWay(entityId);
var operation = function(history) {
history.perform(action, 'split a way');
};
operation.available = function(graph) {
var entity = graph.entity(entityId);
return entity.geometry(graph) === 'vertex';
};
operation.enabled = function(graph) {
return action.enabled(graph);
};
operation.id = "split";
operation.title = "Split";
return operation;
};

View File

@@ -0,0 +1,21 @@
iD.operations.Unjoin = function(entityId) {
var action = iD.actions.UnjoinNode(entityId);
var operation = function(history) {
history.perform(action, 'unjoined lines');
};
operation.available = function(graph) {
var entity = graph.entity(entityId);
return entity.geometry(graph) === 'vertex';
};
operation.enabled = function(graph) {
return action.enabled(graph);
};
operation.id = "unjoin";
operation.title = "Unjoin";
return operation;
};

View File

@@ -1,82 +1,16 @@
iD.ui.RadialMenu = function(entity, history, map) {
iD.ui.RadialMenu = function(entity, mode) {
var arcs;
var radialMenu = function(selection, center) {
var operations,
var history = mode.map.history(),
graph = history.graph(),
geometry = entity.geometry(graph);
operations = d3.values(iD.operations)
.map(function (o) { return o(entity.id, mode); })
.filter(function (o) { return o.available(graph); });
if (geometry === 'vertex') {
operations = [
{
id: 'delete',
text: 'Delete',
description: 'deleted a node',
action: iD.actions.DeleteNode(entity.id)
},
{
id: 'split',
text: 'Split Way',
description: 'split a way',
action: iD.actions.SplitWay(entity.id)
},
{
id: 'unjoin',
text: 'Unjoin',
description: 'unjoined lines',
action: iD.actions.UnjoinNode(entity.id)
}
];
} else if (geometry === 'point') {
operations = [
{
id: 'delete',
text: 'Delete',
description: 'deleted a point',
action: iD.actions.DeleteNode(entity.id)
}
];
} else if (geometry === 'line') {
operations = [
{
id: 'delete',
text: 'Delete',
description: 'deleted a line',
action: iD.actions.DeleteWay(entity.id)
},
{
id: 'reverse',
text: 'Reverse',
description: 'reversed a way',
action: iD.actions.ReverseWay(entity.id)
}
];
if (entity.isClosed()) {
operations.push({
id: 'circlar',
text: 'Circular',
description: 'made way circular',
action: iD.actions.Circular(entity.id, map)
});
}
} else if (geometry === 'area') {
operations = [
{
id: 'delete',
text: 'Delete',
description: 'deleted an area',
action: iD.actions.DeleteWay(entity.id)
},
{
id: 'circlar',
text: 'Circular',
description: 'made area circular',
action: iD.actions.Circular(entity.id, map)
}
];
} else {
// Relation, not implemented yet.
return;
function click(operation) {
d3.event.stopPropagation();
operation(history);
}
var arc = d3.svg.arc()
@@ -98,14 +32,14 @@ iD.ui.RadialMenu = function(entity, history, map) {
arcs.append('path')
.attr('class', function (d) { return 'radial-menu-item radial-menu-item-' + d.id; })
.attr('d', arc)
.classed('disabled', function (d) { return !d.action.enabled(history.graph()); })
.on('click', function (d) { history.perform(d.action, d.description); });
.classed('disabled', function (d) { return !d.enabled(graph); })
.on('click', click);
arcs.append('text')
.attr("transform", function(d, i) { return "translate(" + arc.centroid(d, i) + ")"; })
.attr("dy", ".35em")
.style("text-anchor", "middle")
.text(function(d) { return d.text; });
.text(function(d) { return d.title; });
};
radialMenu.close = function(selection) {

View File

@@ -86,7 +86,6 @@
<script src='../js/id/behavior/drag.js'></script>
<script src='../js/id/behavior/drag_midpoint.js'></script>
<script src='../js/id/behavior/drag_node.js'></script>
<script src='../js/id/behavior/drag_way.js'></script>
<script src='../js/id/behavior/draw.js'></script>
<script src='../js/id/behavior/draw_way.js'></script>
<script src='../js/id/behavior/hover.js'></script>
@@ -98,8 +97,17 @@
<script src='../js/id/modes/browse.js'></script>
<script src='../js/id/modes/draw_area.js'></script>
<script src='../js/id/modes/draw_line.js'></script>
<script src='../js/id/modes/move_way.js'></script>
<script src='../js/id/modes/select.js'></script>
<script src='../js/id/operations.js'></script>
<script src='../js/id/operations/circular.js'></script>
<script src='../js/id/operations/delete.js'></script>
<script src='../js/id/operations/move.js'></script>
<script src='../js/id/operations/reverse.js'></script>
<script src='../js/id/operations/split.js'></script>
<script src='../js/id/operations/unjoin.js'></script>
<script src='../js/id/controller/controller.js'></script>
<script src='../js/id/graph/entity.js'></script>

View File

@@ -9,8 +9,23 @@ describe("iD.behavior.Hover", function() {
container.remove();
});
describe("#on", function () {
it("adds the .behavior-hover class to the selection", function () {
container.call(iD.behavior.Hover());
expect(container).to.be.classed('behavior-hover')
});
});
describe("#off", function () {
it("removes the .behavior-hover class from the selection", function () {
container.classed('behavior-hover', true);
container.call(iD.behavior.Hover().off);
expect(container).not.to.be.classed('behavior-hover')
});
});
describe("mouseover", function () {
it("adds the 'hover' class to all elements to which the same datum is bound", function () {
it("adds the .hover class to all elements to which the same datum is bound", function () {
container.selectAll('span')
.data(['a', 'b', 'a', 'b'])
.enter().append('span').attr('class', Object);
@@ -24,7 +39,7 @@ describe("iD.behavior.Hover", function() {
});
describe("mouseout", function () {
it("removes the 'hover' class from all elements", function () {
it("removes the .hover class from all elements", function () {
container.append('span').attr('class', 'hover');
container.call(iD.behavior.Hover());