diff --git a/css/app.css b/css/app.css
index 7227e6d82..b520dd1a0 100644
--- a/css/app.css
+++ b/css/app.css
@@ -1102,6 +1102,28 @@ div.typeahead a:first-child {
border-radius: 4px;
}
+.arc-menu-item {
+ fill: white;
+ stroke: black;
+ stroke-width: 1;
+ cursor:url(../img/cursor-pointer.png) 6 1, auto;
+}
+
+.arc-menu-item:hover {
+ fill: #bde5aa;
+}
+
+.arc-menu-item:active {
+ fill: #6bc641;
+}
+
+.arc-menu-item.disabled {
+ cursor: auto;
+ pointer-events: none;
+ fill: rgba(255,255,255,.5);
+}
+
+
/* Media Queries
------------------------------------------------------- */
diff --git a/index.html b/index.html
index 69f83f76b..7d98b97ea 100644
--- a/index.html
+++ b/index.html
@@ -52,6 +52,7 @@
+
diff --git a/js/id/modes/select.js b/js/id/modes/select.js
index d80d6fffd..d44d3d889 100644
--- a/js/id/modes/select.js
+++ b/js/id/modes/select.js
@@ -7,7 +7,8 @@ iD.modes.Select = function(entity, initial) {
var inspector = iD.ui.inspector().initial(!!initial),
keybinding = d3.keybinding('select'),
- behaviors;
+ behaviors,
+ arcMenu;
function remove() {
if (entity.type === 'way') {
@@ -32,7 +33,8 @@ iD.modes.Select = function(entity, initial) {
}
mode.enter = function() {
- var surface = mode.map.surface;
+ var surface = mode.map.surface,
+ history = mode.history;
behaviors = [
iD.behavior.Hover(),
@@ -71,32 +73,12 @@ iD.modes.Select = function(entity, initial) {
inspector
.on('changeTags', changeTags)
- .on('reverseWay', function(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('unjoin', function(d) {
- mode.history.perform(
- iD.actions.UnjoinNode(d.id),
- 'unjoined ways');
-
- }).on('remove', function() {
- remove();
-
- }).on('close', function() {
- mode.controller.exit();
- });
+ .on('close', function() { mode.controller.exit(); });
// Exit mode if selected entity gets undone
- mode.history.on('change.entity-undone', function() {
+ history.on('change.entity-undone', function() {
var old = entity;
- entity = mode.history.graph().entity(entity.id);
+ entity = history.graph().entity(entity.id);
if (!entity) {
mode.controller.enter(iD.modes.Browse());
} else if(!_.isEqual(entity.tags, old.tags)) {
@@ -121,7 +103,7 @@ iD.modes.Select = function(entity, initial) {
d3.mouse(mode.map.surface.node()), mode.map),
node = iD.Node({ loc: choice.loc });
- mode.history.perform(
+ history.perform(
iD.actions.AddNode(node),
iD.actions.AddWayNode(datum.id, node.id, choice.index),
'added a point to a road');
@@ -144,10 +126,17 @@ iD.modes.Select = function(entity, initial) {
return d && entity && d.id === entity.id;
})
.classed('selected', true);
+
+ arcMenu = iD.ui.ArcMenu(entity, history);
+
+ if (d3.event) {
+ surface.call(arcMenu, d3.mouse(surface.node()));
+ }
};
mode.exit = function () {
- var surface = mode.map.surface;
+ var surface = mode.map.surface,
+ history = mode.history;
if (entity) {
changeTags(entity, inspector.tags());
@@ -173,10 +162,12 @@ iD.modes.Select = function(entity, initial) {
surface.on('click.select', null)
.on('dblclick.select', null);
- mode.history.on('change.entity-undone', null);
+ history.on('change.entity-undone', null);
surface.selectAll(".selected")
.classed('selected', false);
+
+ surface.call(arcMenu.close);
};
return mode;
diff --git a/js/id/ui/arc_menu.js b/js/id/ui/arc_menu.js
new file mode 100644
index 000000000..09b0ec1d5
--- /dev/null
+++ b/js/id/ui/arc_menu.js
@@ -0,0 +1,97 @@
+iD.ui.ArcMenu = function(entity, history) {
+ var arcMenu = function(selection, center) {
+ var π = Math.PI;
+ var operations;
+
+ if (entity.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 (entity.geometry() === 'point') {
+ operations = [
+ {
+ id: 'delete',
+ text: 'Delete',
+ description: 'deleted a point',
+ action: iD.actions.DeleteNode(entity.id)
+ }
+ ];
+ } else if (entity.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)
+ }
+ ];
+ } else if (entity.geometry() === 'area') {
+ operations = [
+ {
+ id: 'delete',
+ text: 'Delete',
+ description: 'deleted an area',
+ action: iD.actions.DeleteWay(entity.id)
+ }
+ ];
+ }
+
+ var arc = d3.svg.arc()
+ .outerRadius(70)
+ .innerRadius(30)
+ .startAngle(function (d, i) { return 2 * Math.PI / operations.length * i; })
+ .endAngle(function (d, i) { return 2 * Math.PI / operations.length * (i + 1); });
+
+ var arcs = selection.selectAll('.arc-menu')
+ .data(operations)
+ .enter().append('g')
+ .attr('class', 'arc-menu')
+ .attr('transform', "translate(" + center + ")")
+ .attr('opacity', 0);
+
+ arcs.transition()
+ .attr('opacity', 0.8);
+
+ arcs.append('path')
+ .attr('class', function (d) { return 'arc-menu-item arc-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); });
+
+ 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; });
+ };
+
+ arcMenu.close = function(selection) {
+ selection.selectAll('.arc-menu')
+ .remove();
+ };
+
+ return arcMenu;
+};
diff --git a/js/id/ui/inspector.js b/js/id/ui/inspector.js
index 22fac522d..b784e5fb3 100644
--- a/js/id/ui/inspector.js
+++ b/js/id/ui/inspector.js
@@ -1,6 +1,5 @@
iD.ui.inspector = function() {
- var event = d3.dispatch('changeTags', 'reverseWay',
- 'update', 'remove', 'close', 'splitWay', 'unjoin'),
+ var event = d3.dispatch('changeTags', 'close'),
taginfo = iD.taginfo(),
initial = false,
tagList;
@@ -69,39 +68,12 @@ iD.ui.inspector = function() {
inspectorButton1.append('span').attr('class','icon icon-pre-text apply');
inspectorButton1.append('span').attr('class','label').text('Okay');
- var inspectorButton2 = inspectorButtonWrap.append('button')
- .attr('class', 'delete col6 action')
- .on('click', function(entity) { event.remove(entity); });
-
- inspectorButton2.append('span').attr('class','icon icon-pre-text delete');
- inspectorButton2.append('span').attr('class','label').text('Delete');
-
var minorButtons = selection.append('div').attr('class','minor-buttons fl');
minorButtons.append('a')
.attr('href', 'http://www.openstreetmap.org/browse/' + entity.type + '/' + entity.osmId())
.attr('target', '_blank')
.text('View on OSM');
-
- if (entity.type === 'way') {
- minorButtons.append('a')
- .attr('href', '#')
- .text('Reverse Direction')
- .on('click', function() { event.reverseWay(entity); });
- }
-
- if (entity.geometry() === 'vertex') {
- minorButtons.append('a')
- .attr('href', '#')
- .text('Split Way')
- .on('click', function() { event.splitWay(entity); });
-
- minorButtons.append('a')
- .attr('href', '#')
- .text('Unjoin')
- .on('click', function() { event.unjoin(entity); });
- }
-
}
function drawTags(tags) {
diff --git a/test/index.html b/test/index.html
index e7e6701a6..aea53c1e9 100644
--- a/test/index.html
+++ b/test/index.html
@@ -54,6 +54,7 @@
+