Files
iD/js/id/modes/select.js
John Firebaugh 5eb0644242 Improve multipolygon rendering
Multipolygon relations report their geometry as 'area' and are rendered
as such. However, they do not render a stroke. The stroke rendering
will come from the individual lines, which are given the tag
classes of their parent relations, allowing them to have a stroke
style matching the style of simple areas with the same tags.

Untagged circular ways are no longer considered areas. This prevents
an untagged inner way of a multipolygon from rendering as an area and
is consistent with how P2 and JOSM treat them.

In the CSS, it's no longer necessary to deal with multipolygons
explicitly in selectors. But keep in mind that area boundaries can
now be rendered either as lines or as area strokes. In most cases
the selector should be `path.stroke.tag-_____`, i.e. an explicit
`.area` or `.line` classes should not be included.

Finally, the parent ways of selected multipolygons are given the 'selected'
class.
2013-02-05 15:20:11 -08:00

212 lines
6.9 KiB
JavaScript

iD.modes.Select = function(context, selection, initial) {
var mode = {
id: 'select',
button: 'browse'
};
var inspector = iD.ui.inspector().initial(!!initial),
keybinding = d3.keybinding('select'),
behaviors = [
iD.behavior.Hover(),
iD.behavior.Select(context),
iD.behavior.DragNode(context),
iD.behavior.DragMidpoint(context)],
radialMenu;
function changeTags(d, tags) {
if (!_.isEqual(singular().tags, tags)) {
context.perform(
iD.actions.ChangeTags(d.id, tags),
t('operations.change_tags.annotation'));
}
}
function singular() {
if (selection.length === 1) {
return context.entity(selection[0]);
}
}
mode.selection = function() {
return selection;
};
mode.enter = function() {
var entity = singular();
behaviors.forEach(function(behavior) {
context.install(behavior);
});
var operations = _.without(d3.values(iD.operations), iD.operations.Delete)
.map(function(o) { return o(selection, context); })
.filter(function(o) { return o.available(); });
operations.unshift(iD.operations.Delete(selection, context));
operations.forEach(function(operation) {
keybinding.on(operation.key, function() {
if (operation.enabled()) {
operation();
}
});
});
var q = iD.util.stringQs(location.hash.substring(1));
location.replace('#' + iD.util.qsString(_.assign(q, {
id: selection.join(',')
}), true));
if (entity) {
inspector.context(context);
context.container()
.select('.inspector-wrap')
.style('display', 'block')
.style('opacity', 1)
.datum(entity)
.call(inspector);
if (d3.event) {
// Pan the map if the clicked feature intersects with the position
// of the inspector
var inspector_size = context.container().select('.inspector-wrap').size(),
map_size = context.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) {
context.map().centerEase(context.projection.invert([center, map_size[1]/2]));
}
}
inspector
.on('changeTags', changeTags)
.on('close', function() { context.enter(iD.modes.Browse(context)); });
}
context.history().on('change.select', function() {
context.surface().call(radialMenu.close);
if (_.any(selection, function(id) { return !context.entity(id); })) {
// Exit mode if selected entity gets undone
context.enter(iD.modes.Browse(context));
} else if (entity) {
var newEntity = context.entity(selection[0]);
if (!_.isEqual(entity.tags, newEntity.tags)) {
inspector.tags(newEntity.tags);
}
}
});
context.map().on('move.select', function() {
context.surface().call(radialMenu.close);
});
function dblclick() {
var target = d3.select(d3.event.target),
datum = target.datum();
if (datum instanceof iD.Way && !target.classed('fill')) {
var choice = iD.geo.chooseIndex(datum,
d3.mouse(context.surface().node()), context),
node = iD.Node({ loc: choice.loc });
var prev = datum.nodes[choice.index - 1],
next = datum.nodes[choice.index],
prevParents = context.graph().parentWays({ id: prev }),
ways = [];
for (var i = 0; i < prevParents.length; i++) {
var p = prevParents[i];
for (var k = 0; k < p.nodes.length; k++) {
if (p.nodes[k] === prev) {
if (p.nodes[k-1] === next) {
ways.push({ id: p.id, index: k});
break;
} else if (p.nodes[k+1] === next) {
ways.push({ id: p.id, index: k+1});
break;
}
}
}
}
context.perform(iD.actions.AddEntity(node),
iD.actions.AddMidpoint({ ways: ways, loc: node.loc }, node),
t('operations.add.annotation.vertex'));
d3.event.preventDefault();
d3.event.stopPropagation();
}
}
function selected(entity) {
if (!entity) return false;
if (selection.indexOf(entity.id) >= 0) return true;
return d3.select(this).classed('stroke') &&
_.any(context.graph().parentRelations(entity), function(parent) {
return selection.indexOf(parent.id) >= 0;
});
}
d3.select(document)
.call(keybinding);
context.surface()
.on('dblclick.select', dblclick)
.selectAll("*")
.filter(selected)
.classed('selected', true);
radialMenu = iD.ui.RadialMenu(operations);
if (d3.event && !initial) {
var loc = context.map().mouseCoordinates();
if (entity && entity.type === 'node') {
loc = entity.loc;
}
context.surface().call(radialMenu, context.projection(loc));
}
};
mode.exit = function() {
if (singular()) {
changeTags(singular(), inspector.tags());
}
context.container()
.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) {
context.uninstall(behavior);
});
var q = iD.util.stringQs(location.hash.substring(1));
location.replace('#' + iD.util.qsString(_.omit(q, 'id'), true));
keybinding.off();
context.history()
.on('change.select', null);
context.surface()
.call(radialMenu.close)
.on('dblclick.select', null)
.selectAll(".selected")
.classed('selected', false);
};
return mode;
};