Tooltips for disabled operations (fixes #573)

This commit is contained in:
John Firebaugh
2013-03-29 12:17:39 -07:00
parent 52dd7bf869
commit b1daf85cfd
27 changed files with 198 additions and 122 deletions
+13 -3
View File
@@ -39,11 +39,14 @@ en:
annotation: Changed tags.
circularize:
title: Circularize
description: Make this round.
description:
line: Make this line circular.
area: Make this area circular.
key: O
annotation:
line: Made a line circular.
area: Made an area circular.
not_closed: This can't be made circular because it's not a loop.
orthogonalize:
title: Orthogonalize
description: Square these corners.
@@ -51,6 +54,7 @@ en:
annotation:
line: Squared the corners of a line.
area: Squared the corners of an area.
not_closed: This can't be made square because it's not a loop.
delete:
title: Delete
description: Remove this from the map.
@@ -69,14 +73,17 @@ en:
area: Connected a way to an area.
disconnect:
title: Disconnect
description: Disconnect these ways from each other.
description: Disconnect these lines/areas from each other.
key: D
annotation: Disconnected ways.
annotation: Disconnected lines/areas.
not_connected: There aren't enough lines/areas here to disconnect.
merge:
title: Merge
description: Merge these lines.
key: C
annotation: "Merged {n} lines."
not_eligible: These features can't be merged.
not_adjacent: These lines can't be merged because they aren't connected.
move:
title: Move
description: Move this to a different location.
@@ -87,6 +94,7 @@ en:
line: Moved a line.
area: Moved an area.
multiple: Moved multiple objects.
incomplete_relation: This feature can't be moved because it hasn't been fully downloaded.
rotate:
title: Rotate
description: Rotate this object around its centre point.
@@ -104,6 +112,8 @@ en:
description: Split this into two ways at this point.
key: X
annotation: Split a way.
not_eligible: Lines can't be split at their beginning or end.
multiple_ways: There are too many lines here to split.
nothing_to_undo: Nothing to undo.
nothing_to_redo: Nothing to redo.
just_edited: "You just edited OpenStreetMap!"
+19 -8
View File
@@ -53,12 +53,16 @@ locale.en = {
},
"circularize": {
"title": "Circularize",
"description": "Make this round.",
"description": {
"line": "Make this line circular.",
"area": "Make this area circular."
},
"key": "O",
"annotation": {
"line": "Made a line circular.",
"area": "Made an area circular."
}
},
"not_closed": "This can't be made circular because it's not a loop."
},
"orthogonalize": {
"title": "Orthogonalize",
@@ -67,7 +71,8 @@ locale.en = {
"annotation": {
"line": "Squared the corners of a line.",
"area": "Squared the corners of an area."
}
},
"not_closed": "This can't be made square because it's not a loop."
},
"delete": {
"title": "Delete",
@@ -91,15 +96,18 @@ locale.en = {
},
"disconnect": {
"title": "Disconnect",
"description": "Disconnect these ways from each other.",
"description": "Disconnect these lines/areas from each other.",
"key": "D",
"annotation": "Disconnected ways."
"annotation": "Disconnected lines/areas.",
"not_connected": "There aren't enough lines/areas here to disconnect."
},
"merge": {
"title": "Merge",
"description": "Merge these lines.",
"key": "C",
"annotation": "Merged {n} lines."
"annotation": "Merged {n} lines.",
"not_eligible": "These features can't be merged.",
"not_adjacent": "These lines can't be merged because they aren't connected."
},
"move": {
"title": "Move",
@@ -111,7 +119,8 @@ locale.en = {
"line": "Moved a line.",
"area": "Moved an area.",
"multiple": "Moved multiple objects."
}
},
"incomplete_relation": "This feature can't be moved because it hasn't been fully downloaded."
},
"rotate": {
"title": "Rotate",
@@ -132,7 +141,9 @@ locale.en = {
"title": "Split",
"description": "Split this into two ways at this point.",
"key": "X",
"annotation": "Split a way."
"annotation": "Split a way.",
"not_eligible": "Lines can't be split at their beginning or end.",
"multiple_ways": "There are too many lines here to split."
}
},
"nothing_to_undo": "Nothing to undo.",
+3 -2
View File
@@ -58,8 +58,9 @@ iD.actions.Circularize = function(wayId, projection, count) {
return graph;
};
action.enabled = function(graph) {
return graph.entity(wayId).isClosed();
action.disabled = function(graph) {
if (!graph.entity(wayId).isClosed())
return 'not_closed';
};
return action;
+1 -7
View File
@@ -13,7 +13,7 @@
// https://github.com/openstreetmap/josm/blob/mirror/src/org/openstreetmap/josm/actions/MergeNodesAction.java
//
iD.actions.Connect = function(nodeIds) {
var action = function(graph) {
return function(graph) {
var survivor = graph.entity(_.last(nodeIds));
for (var i = 0; i < nodeIds.length - 1; i++) {
@@ -37,10 +37,4 @@ iD.actions.Connect = function(nodeIds) {
return graph;
};
action.enabled = function() {
return nodeIds.length > 1;
};
return action;
};
+7 -8
View File
@@ -12,9 +12,6 @@
//
iD.actions.Disconnect = function(nodeId, newNodeId) {
var action = function(graph) {
if (!action.enabled(graph))
return graph;
var node = graph.entity(nodeId);
graph.parentWays(node).forEach(function(parent, i) {
@@ -40,12 +37,14 @@ iD.actions.Disconnect = function(nodeId, newNodeId) {
return graph;
};
action.enabled = function(graph) {
action.disabled = function(graph) {
var parentWays = graph.parentWays(graph.entity(nodeId));
return parentWays.length >= 2 ||
(parentWays.length == 1 && parentWays[0].nodes.filter(function(d) {
return d === nodeId;
}).length >= 2);
if (parentWays.length >= 2)
return;
if (parentWays.length === 0)
return 'not_connected';
if (parentWays[0].nodes.filter(function(d) { return d === nodeId; }).length < 2)
return 'not_connected';
};
return action;
+7 -6
View File
@@ -57,19 +57,20 @@ iD.actions.Join = function(ids) {
return graph;
};
action.enabled = function(graph) {
action.disabled = function(graph) {
var geometries = groupEntitiesByGeometry(graph);
if (ids.length !== 2 || ids.length !== geometries.line.length)
return false;
return 'not_eligible';
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();
if (a.first() !== b.first() &&
a.first() !== b.last() &&
a.last() !== b.first() &&
a.last() !== b.last())
return 'not_adjacent';
};
return action;
+5 -4
View File
@@ -25,11 +25,12 @@ iD.actions.Merge = function(ids) {
return graph;
};
action.enabled = function(graph) {
action.disabled = function(graph) {
var geometries = groupEntitiesByGeometry(graph);
return geometries.point.length > 0 &&
(geometries.area.length + geometries.line.length) === 1 &&
geometries.relation.length === 0;
if (geometries.point.length === 0 ||
(geometries.area.length + geometries.line.length) !== 1 ||
geometries.relation.length !== 0)
return 'not_eligible';
};
return action;
+7 -4
View File
@@ -29,11 +29,14 @@ iD.actions.Move = function(ids, delta, projection) {
return graph;
};
action.enabled = function(graph) {
return _.every(ids, function(id) {
action.disabled = function(graph) {
function incompleteRelation(id) {
var entity = graph.entity(id);
return entity.type !== 'relation' || entity.isComplete(graph);
});
return entity.type === 'relation' && !entity.isComplete(graph);
}
if (_.any(ids, incompleteRelation))
return 'incomplete_relation';
};
return action;
+3 -2
View File
@@ -127,8 +127,9 @@ iD.actions.Orthogonalize = function(wayId, projection) {
}
};
action.enabled = function(graph) {
return graph.entity(wayId).isClosed();
action.disabled = function(graph) {
if (!graph.entity(wayId).isClosed())
return 'not_closed';
};
return action;
+6 -2
View File
@@ -94,8 +94,12 @@ iD.actions.Split = function(nodeId, newWayId) {
return graph;
};
action.enabled = function(graph) {
return candidateWays(graph).length === 1;
action.disabled = function(graph) {
var candidates = candidateWays(graph);
if (candidates.length === 0)
return 'not_eligible';
if (candidates.length > 1)
return 'multiple_ways';
};
return action;
+1 -1
View File
@@ -65,7 +65,7 @@ iD.modes.Select = function(context, selection, initial) {
operations.forEach(function(operation) {
operation.keys.forEach(function(key) {
keybinding.on(key, function() {
if (operation.enabled()) {
if (!operation.disabled()) {
operation();
}
});
+11 -4
View File
@@ -1,9 +1,10 @@
iD.operations.Circularize = function(selection, context) {
var entityId = selection[0],
geometry = context.geometry(entityId),
action = iD.actions.Circularize(entityId, context.projection);
var operation = function() {
var annotation = t('operations.circularize.annotation.' + context.geometry(entityId));
var annotation = t('operations.circularize.annotation.' + geometry);
context.perform(action, annotation);
};
@@ -12,14 +13,20 @@ iD.operations.Circularize = function(selection, context) {
context.entity(entityId).type === 'way';
};
operation.enabled = function() {
return action.enabled(context.graph());
operation.disabled = function() {
return action.disabled(context.graph());
};
operation.tooltip = function() {
var disable = operation.disabled();
return disable ?
t('operations.circularize.' + disable) :
t('operations.circularize.description.' + geometry);
};
operation.id = "circularize";
operation.keys = [t('operations.circularize.key')];
operation.title = t('operations.circularize.title');
operation.description = t('operations.circularize.description');
return operation;
};
+6 -3
View File
@@ -19,14 +19,17 @@ iD.operations.Delete = function(selection, context) {
return true;
};
operation.enabled = function() {
return true;
operation.disabled = function() {
return false;
};
operation.tooltip = function() {
return t('operations.delete.description');
};
operation.id = "delete";
operation.keys = [iD.ui.cmd('⌫'), iD.ui.cmd('⌦')];
operation.title = t('operations.delete.title');
operation.description = t('operations.delete.description');
return operation;
};
+9 -3
View File
@@ -11,14 +11,20 @@ iD.operations.Disconnect = function(selection, context) {
context.geometry(entityId) === 'vertex';
};
operation.enabled = function() {
return action.enabled(context.graph());
operation.disabled = function() {
return action.disabled(context.graph());
};
operation.tooltip = function() {
var disable = operation.disabled();
return disable ?
t('operations.disconnect.' + disable) :
t('operations.disconnect.description');
};
operation.id = "disconnect";
operation.keys = [t('operations.disconnect.key')];
operation.title = t('operations.disconnect.title');
operation.description = t('operations.disconnect.description');
return operation;
};
+14 -5
View File
@@ -6,7 +6,7 @@ iD.operations.Merge = function(selection, context) {
var annotation = t('operations.merge.annotation', {n: selection.length}),
action;
if (join.enabled(context.graph())) {
if (!join.disabled(context.graph())) {
action = join;
} else {
action = merge;
@@ -20,15 +20,24 @@ iD.operations.Merge = function(selection, context) {
return selection.length >= 2;
};
operation.enabled = function() {
return join.enabled(context.graph()) ||
merge.enabled(context.graph());
operation.disabled = function() {
return join.disabled(context.graph()) &&
merge.disabled(context.graph());
};
operation.tooltip = function() {
var j = join.disabled(context.graph()),
m = merge.disabled(context.graph());
if (j && m)
return t('operations.merge.' + j);
return t('operations.merge.description');
};
operation.id = "merge";
operation.keys = [t('operations.merge.key')];
operation.title = t('operations.merge.title');
operation.description = t('operations.merge.description');
return operation;
};
+9 -3
View File
@@ -8,15 +8,21 @@ iD.operations.Move = function(selection, context) {
context.entity(selection[0]).type !== 'node';
};
operation.enabled = function() {
operation.disabled = function() {
return iD.actions.Move(selection)
.enabled(context.graph());
.disabled(context.graph());
};
operation.tooltip = function() {
var disable = operation.disabled();
return disable ?
t('operations.move.' + disable) :
t('operations.move.description');
};
operation.id = "move";
operation.keys = [t('operations.move.key')];
operation.title = t('operations.move.title');
operation.description = t('operations.move.description');
return operation;
};
+9 -2
View File
@@ -13,8 +13,15 @@ iD.operations.Orthogonalize = function(selection, context) {
_.uniq(context.entity(entityId).nodes).length > 2;
};
operation.enabled = function() {
return action.enabled(context.graph());
operation.disabled = function() {
return action.disabled(context.graph());
};
operation.tooltip = function() {
var disable = operation.disabled();
return disable ?
t('operations.orthogonalize.' + disable) :
t('operations.orthogonalize.description');
};
operation.id = "orthogonalize";
+6 -3
View File
@@ -12,14 +12,17 @@ iD.operations.Reverse = function(selection, context) {
context.geometry(entityId) === 'line';
};
operation.enabled = function() {
return true;
operation.disabled = function() {
return false;
};
operation.tooltip = function() {
return t('operations.reverse.description');
};
operation.id = "reverse";
operation.keys = [t('operations.reverse.key')];
operation.title = t('operations.reverse.title');
operation.description = t('operations.reverse.description');
return operation;
};
+6 -3
View File
@@ -11,14 +11,17 @@ iD.operations.Rotate = function(selection, context) {
context.entity(entityId).geometry() === 'area';
};
operation.enabled = function() {
return true;
operation.disabled = function() {
return false;
};
operation.tooltip = function() {
return t('operations.rotate.description');
};
operation.id = "rotate";
operation.keys = [t('operations.rotate.key')];
operation.title = t('operations.rotate.title');
operation.description = t('operations.rotate.description');
return operation;
};
+9 -3
View File
@@ -13,14 +13,20 @@ iD.operations.Split = function(selection, context) {
context.geometry(entityId) === 'vertex';
};
operation.enabled = function() {
return action.enabled(context.graph());
operation.disabled = function() {
return action.disabled(context.graph());
};
operation.tooltip = function() {
var disable = operation.disabled();
return disable ?
t('operations.split.' + disable) :
t('operations.split.description');
};
operation.id = "split";
operation.keys = [t('operations.split.key')];
operation.title = t('operations.split.title');
operation.description = t('operations.split.description');
return operation;
};
+5 -2
View File
@@ -11,6 +11,8 @@ iD.ui.RadialMenu = function(operations) {
function click(operation) {
d3.event.stopPropagation();
if (operation.disabled())
return;
operation();
radialMenu.close();
}
@@ -49,7 +51,8 @@ iD.ui.RadialMenu = function(operations) {
button.append('circle')
.attr('class', function(d) { return 'radial-menu-item radial-menu-item-' + d.id; })
.attr('r', 15)
.classed('disabled', function(d) { return !d.enabled(); })
.classed('disabled', function(d) { return d.disabled(); })
.style('pointer-events', 'all')
.on('click', click)
.on('mouseover', mouseover)
.on('mouseout', mouseout);
@@ -72,7 +75,7 @@ iD.ui.RadialMenu = function(operations) {
.style('left', (r + 25) * Math.sin(angle) + dx + center[0] + 'px')
.style('top', (r + 25) * Math.cos(angle) + dy + center[1]+ 'px')
.style('display', 'block')
.html(iD.ui.tooltipHtml(d.description, d.keys[0]));
.html(iD.ui.tooltipHtml(d.tooltip(), d.keys[0]));
}
function mouseout() {
-10
View File
@@ -1,14 +1,4 @@
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'}),
+7 -7
View File
@@ -1,12 +1,12 @@
describe("iD.actions.Disconnect", function () {
describe("#enabled", function () {
it("returns false for a node shared by less than two ways", function () {
describe("#disabled", function () {
it("returns 'not_connected' for a node shared by less than two ways", function () {
var graph = iD.Graph({'a': iD.Node()});
expect(iD.actions.Disconnect('a').enabled(graph)).to.equal(false);
expect(iD.actions.Disconnect('a').disabled(graph)).to.equal('not_connected');
});
it("returns true for a node appearing twice in the same way", function () {
it("returns falsy for a node appearing twice in the same way", function () {
// a ---- b
// | |
// d ---- c
@@ -17,10 +17,10 @@ describe("iD.actions.Disconnect", function () {
'd': iD.Node({id: 'd'}),
'w': iD.Way({id: 'w', nodes: ['a', 'b', 'c', 'd', 'a']})
});
expect(iD.actions.Disconnect('a').enabled(graph)).to.equal(true);
expect(iD.actions.Disconnect('a').disabled(graph)).not.to.be.ok;
});
it("returns true for a node shared by two or more ways", function () {
it("returns falsy for a node shared by two or more ways", function () {
// a ---- b ---- c
// |
// d
@@ -33,7 +33,7 @@ describe("iD.actions.Disconnect", function () {
'|': iD.Way({id: '|', nodes: ['d', 'b']})
});
expect(iD.actions.Disconnect('b').enabled(graph)).to.equal(true);
expect(iD.actions.Disconnect('b').disabled(graph)).not.to.be.ok;
});
});
+19 -11
View File
@@ -1,6 +1,6 @@
describe("iD.actions.Join", function () {
describe("#enabled", function () {
it("returns true for ways that share an end/start node", function () {
describe("#disabled", function () {
it("returns falsy for ways that share an end/start node", function () {
// a --> b ==> c
var graph = iD.Graph({
'a': iD.Node({id: 'a'}),
@@ -10,10 +10,10 @@ describe("iD.actions.Join", function () {
'=': iD.Way({id: '=', nodes: ['b', 'c']})
});
expect(iD.actions.Join(['-', '=']).enabled(graph)).to.be.true;
expect(iD.actions.Join(['-', '=']).disabled(graph)).not.to.be.ok;
});
it("returns true for ways that share a start/end node", function () {
it("returns falsy for ways that share a start/end node", function () {
// a <-- b <== c
var graph = iD.Graph({
'a': iD.Node({id: 'a'}),
@@ -23,10 +23,10 @@ describe("iD.actions.Join", function () {
'=': iD.Way({id: '=', nodes: ['c', 'b']})
});
expect(iD.actions.Join(['-', '=']).enabled(graph)).to.be.true;
expect(iD.actions.Join(['-', '=']).disabled(graph)).not.to.be.ok;
});
it("returns true for ways that share a start/start node", function () {
it("returns falsy for ways that share a start/start node", function () {
// a <-- b ==> c
var graph = iD.Graph({
'a': iD.Node({id: 'a'}),
@@ -36,10 +36,10 @@ describe("iD.actions.Join", function () {
'=': iD.Way({id: '=', nodes: ['b', 'c']})
});
expect(iD.actions.Join(['-', '=']).enabled(graph)).to.be.true;
expect(iD.actions.Join(['-', '=']).disabled(graph)).not.to.be.ok;
});
it("returns true for ways that share an end/end node", function () {
it("returns falsy for ways that share an end/end node", function () {
// a --> b <== c
var graph = iD.Graph({
'a': iD.Node({id: 'a'}),
@@ -49,10 +49,18 @@ describe("iD.actions.Join", function () {
'=': iD.Way({id: '=', nodes: ['c', 'b']})
});
expect(iD.actions.Join(['-', '=']).enabled(graph)).to.be.true;
expect(iD.actions.Join(['-', '=']).disabled(graph)).not.to.be.ok;
});
it("returns false for ways that don't share the necessary nodes", function () {
it("returns 'not_eligible' for non-line geometries", function () {
var graph = iD.Graph({
'a': iD.Node({id: 'a'})
});
expect(iD.actions.Join(['a']).disabled(graph)).to.equal('not_eligible');
});
it("returns 'not_adjacent' for ways that don't share the necessary nodes", function () {
// a -- b -- c
// |
// d
@@ -65,7 +73,7 @@ describe("iD.actions.Join", function () {
'=': iD.Way({id: '=', nodes: ['b', 'd']})
});
expect(iD.actions.Join(['-', '=']).enabled(graph)).to.be.false;
expect(iD.actions.Join(['-', '=']).disabled(graph)).to.equal('not_adjacent');
});
});
+2 -2
View File
@@ -8,7 +8,7 @@ describe("iD.actions.Merge", function () {
}),
action = iD.actions.Merge(['a', 'b', 'w']);
expect(action.enabled(graph)).to.be.true;
expect(action.disabled(graph)).not.to.be.ok;
graph = action(graph);
@@ -27,7 +27,7 @@ describe("iD.actions.Merge", function () {
}),
action = iD.actions.Merge(['a', 'b', 'w']);
expect(action.enabled(graph)).to.be.true;
expect(action.disabled(graph)).not.to.be.ok;
graph = action(graph);
+7 -7
View File
@@ -1,25 +1,25 @@
describe("iD.actions.Move", function() {
describe("#enabled", function() {
it("returns true by default", function() {
describe("#disabled", function() {
it("returns falsy by default", function() {
var node = iD.Node({loc: [0, 0]}),
action = iD.actions.Move([node.id], [0, 0], d3.geo.mercator()),
graph = iD.Graph([node]);
expect(action.enabled(graph)).to.be.true;
expect(action.disabled(graph)).not.to.be.ok;
});
it("returns false for an incomplete relation", function() {
it("returns 'incomplete_relation' for an incomplete relation", function() {
var relation = iD.Relation({members: [{id: 1}]}),
action = iD.actions.Move([relation.id], [0, 0], d3.geo.mercator()),
graph = iD.Graph([relation]);
expect(action.enabled(graph)).to.be.false;
expect(action.disabled(graph)).to.equal('incomplete_relation');
});
it("returns true for a complete relation", function() {
it("returns falsy for a complete relation", function() {
var node = iD.Node({loc: [0, 0]}),
relation = iD.Relation({members: [{id: node.id}]}),
action = iD.actions.Move([relation.id], [0, 0], d3.geo.mercator()),
graph = iD.Graph([node, relation]);
expect(action.enabled(graph)).to.be.true;
expect(action.disabled(graph)).not.to.be.ok;
});
});
+7 -7
View File
@@ -1,6 +1,6 @@
describe("iD.actions.Split", function () {
describe("#enabled", function () {
it("returns true for a non-end node of a single way", function () {
describe("#disabled", function () {
it("returns falsy for a non-end node of a single way", function () {
var graph = iD.Graph({
'a': iD.Node({id: 'a'}),
'b': iD.Node({id: 'b'}),
@@ -8,27 +8,27 @@ describe("iD.actions.Split", function () {
'-': iD.Way({id: '-', nodes: ['a', 'b', 'c']})
});
expect(iD.actions.Split('b').enabled(graph)).to.be.true;
expect(iD.actions.Split('b').disabled(graph)).not.to.be.ok;
});
it("returns false for the first node of a single way", function () {
it("returns 'not_eligible' 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.Split('a').enabled(graph)).to.be.false;
expect(iD.actions.Split('a').disabled(graph)).to.equal('not_eligible');
});
it("returns false for the last node of a single way", function () {
it("returns 'not_eligible' 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.Split('b').enabled(graph)).to.be.false;
expect(iD.actions.Split('b').disabled(graph)).to.equal('not_eligible');
});
});