mirror of
https://github.com/FoggedLens/iD.git
synced 2026-05-17 22:24:49 +02:00
Support the Relations editor for multiple selected features (close #7753)
This commit is contained in:
+10
-3
@@ -73,7 +73,9 @@ en:
|
||||
cancel_draw:
|
||||
annotation: Canceled drawing.
|
||||
change_role:
|
||||
annotation: Changed the role of a relation member.
|
||||
annotation:
|
||||
one: Changed the role of a relation member.
|
||||
other: "Changed the roles of {n} relation members."
|
||||
change_tags:
|
||||
annotation: Changed tags.
|
||||
copy:
|
||||
@@ -242,9 +244,13 @@ en:
|
||||
single: This feature can't be downgraded because it has a Wikidata tag.
|
||||
multiple: These features can't be downgraded because some have Wikidata tags.
|
||||
add_member:
|
||||
annotation: Added a member to a relation.
|
||||
annotation:
|
||||
one: Added a member to a relation.
|
||||
other: "Added {n} members to a relation."
|
||||
delete_member:
|
||||
annotation: Removed a member from a relation.
|
||||
annotation:
|
||||
one: Removed a member from a relation.
|
||||
other: "Removed {n} members from a relation."
|
||||
reorder_members:
|
||||
annotation: Reordered a relation's members.
|
||||
connect:
|
||||
@@ -683,6 +689,7 @@ en:
|
||||
new_relation: New relation...
|
||||
choose_relation: Choose a parent relation
|
||||
role: Role
|
||||
multiple_roles: Multiple Roles
|
||||
choose: Select feature type
|
||||
results:
|
||||
one: "{n} result for {search}"
|
||||
|
||||
Vendored
+13
-3
@@ -94,7 +94,10 @@
|
||||
"annotation": "Canceled drawing."
|
||||
},
|
||||
"change_role": {
|
||||
"annotation": "Changed the role of a relation member."
|
||||
"annotation": {
|
||||
"one": "Changed the role of a relation member.",
|
||||
"other": "Changed the roles of {n} relation members."
|
||||
}
|
||||
},
|
||||
"change_tags": {
|
||||
"annotation": "Changed tags."
|
||||
@@ -323,10 +326,16 @@
|
||||
}
|
||||
},
|
||||
"add_member": {
|
||||
"annotation": "Added a member to a relation."
|
||||
"annotation": {
|
||||
"one": "Added a member to a relation.",
|
||||
"other": "Added {n} members to a relation."
|
||||
}
|
||||
},
|
||||
"delete_member": {
|
||||
"annotation": "Removed a member from a relation."
|
||||
"annotation": {
|
||||
"one": "Removed a member from a relation.",
|
||||
"other": "Removed {n} members from a relation."
|
||||
}
|
||||
},
|
||||
"reorder_members": {
|
||||
"annotation": "Reordered a relation's members."
|
||||
@@ -882,6 +891,7 @@
|
||||
"new_relation": "New relation...",
|
||||
"choose_relation": "Choose a parent relation",
|
||||
"role": "Role",
|
||||
"multiple_roles": "Multiple Roles",
|
||||
"choose": "Select feature type",
|
||||
"results": {
|
||||
"one": "{n} result for {search}",
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { actionDeleteMember } from './delete_member';
|
||||
|
||||
|
||||
export function actionDeleteMembers(relationId, memberIndexes) {
|
||||
return function(graph) {
|
||||
// Remove the members in descending order so removals won't shift what members
|
||||
// are at the remaining indexes
|
||||
memberIndexes.sort((a, b) => b - a);
|
||||
for (var i in memberIndexes) {
|
||||
graph = actionDeleteMember(relationId, memberIndexes[i])(graph);
|
||||
}
|
||||
return graph;
|
||||
};
|
||||
}
|
||||
@@ -87,7 +87,9 @@ export function uiSectionRawMemberEditor(context) {
|
||||
var member = { id: d.id, type: d.type, role: newRole };
|
||||
context.perform(
|
||||
actionChangeMember(d.relation.id, member, d.index),
|
||||
t('operations.change_role.annotation')
|
||||
t('operations.change_role.annotation', {
|
||||
n: 1
|
||||
})
|
||||
);
|
||||
context.validator().validate();
|
||||
}
|
||||
@@ -101,7 +103,9 @@ export function uiSectionRawMemberEditor(context) {
|
||||
|
||||
context.perform(
|
||||
actionDeleteMember(d.relation.id, d.index),
|
||||
t('operations.delete_member.annotation')
|
||||
t('operations.delete_member.annotation', {
|
||||
n: 1
|
||||
})
|
||||
);
|
||||
|
||||
if (!context.hasEntity(d.relation.id)) {
|
||||
|
||||
@@ -8,7 +8,7 @@ import { t, localizer } from '../../core/localizer';
|
||||
import { actionAddEntity } from '../../actions/add_entity';
|
||||
import { actionAddMember } from '../../actions/add_member';
|
||||
import { actionChangeMember } from '../../actions/change_member';
|
||||
import { actionDeleteMember } from '../../actions/delete_member';
|
||||
import { actionDeleteMembers } from '../../actions/delete_members';
|
||||
|
||||
import { modeSelect } from '../../modes/select';
|
||||
import { osmEntity, osmRelation } from '../../osm';
|
||||
@@ -17,20 +17,18 @@ import { svgIcon } from '../../svg/icon';
|
||||
import { uiCombobox } from '../combobox';
|
||||
import { uiSection } from '../section';
|
||||
import { uiTooltip } from '../tooltip';
|
||||
import { utilArrayGroupBy, utilDisplayName, utilNoAuto, utilHighlightEntities, utilUniqueDomId } from '../../util';
|
||||
import { utilArrayGroupBy, utilArrayIntersection } from '../../util/array';
|
||||
import { utilDisplayName, utilNoAuto, utilHighlightEntities, utilUniqueDomId } from '../../util';
|
||||
|
||||
|
||||
export function uiSectionRawMembershipEditor(context) {
|
||||
|
||||
var section = uiSection('raw-membership-editor', context)
|
||||
.shouldDisplay(function() {
|
||||
return _entityIDs && _entityIDs.length === 1;
|
||||
return _entityIDs && _entityIDs.length;
|
||||
})
|
||||
.label(function() {
|
||||
var entity = context.hasEntity(_entityIDs[0]);
|
||||
if (!entity) return '';
|
||||
|
||||
var parents = context.graph().parentRelations(entity);
|
||||
var parents = getSharedParentRelations();
|
||||
var gt = parents.length > _maxMemberships ? '>' : '';
|
||||
var count = gt + parents.slice(0, _maxMemberships).length;
|
||||
return t('inspector.title_count', { title: t.html('inspector.relations'), count: count });
|
||||
@@ -52,6 +50,72 @@ export function uiSectionRawMembershipEditor(context) {
|
||||
var _showBlank;
|
||||
var _maxMemberships = 1000;
|
||||
|
||||
function getSharedParentRelations() {
|
||||
var parents = [];
|
||||
for (var i = 0; i < _entityIDs.length; i++) {
|
||||
var entity = context.graph().hasEntity(_entityIDs[i]);
|
||||
if (!entity) continue;
|
||||
|
||||
if (i === 0) {
|
||||
parents = context.graph().parentRelations(entity);
|
||||
} else {
|
||||
parents = utilArrayIntersection(parents, context.graph().parentRelations(entity));
|
||||
}
|
||||
if (!parents.length) break;
|
||||
}
|
||||
return parents;
|
||||
}
|
||||
|
||||
function getMemberships() {
|
||||
|
||||
var memberships = [];
|
||||
var relations = getSharedParentRelations().slice(0, _maxMemberships);
|
||||
|
||||
var isMultiselect = _entityIDs.length > 1;
|
||||
|
||||
var i, relation, membership, index, member, indexedMember;
|
||||
for (i = 0; i < relations.length; i++) {
|
||||
relation = relations[i];
|
||||
membership = {
|
||||
relation: relation,
|
||||
members: [],
|
||||
hash: osmEntity.key(relation)
|
||||
};
|
||||
for (index = 0; index < relation.members.length; index++) {
|
||||
member = relation.members[index];
|
||||
if (_entityIDs.indexOf(member.id) !== -1) {
|
||||
indexedMember = Object.assign({}, member, { index: index });
|
||||
membership.members.push(indexedMember);
|
||||
membership.hash += ',' + index.toString();
|
||||
|
||||
if (!isMultiselect) {
|
||||
// For single selections, list one entry per membership per relation.
|
||||
// For multiselections, list one entry per relation.
|
||||
|
||||
memberships.push(membership);
|
||||
membership = {
|
||||
relation: relation,
|
||||
members: [],
|
||||
hash: osmEntity.key(relation)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
if (membership.members.length) memberships.push(membership);
|
||||
}
|
||||
|
||||
memberships.forEach(function(membership) {
|
||||
membership.domId = utilUniqueDomId('membership-' + membership.relation.id);
|
||||
var roles = [];
|
||||
membership.members.forEach(function(member) {
|
||||
if (roles.indexOf(member.role) === -1) roles.push(member.role);
|
||||
});
|
||||
membership.role = roles.length === 1 ? roles[0] : roles;
|
||||
});
|
||||
|
||||
return memberships;
|
||||
}
|
||||
|
||||
function selectRelation(d3_event, d) {
|
||||
d3_event.preventDefault();
|
||||
|
||||
@@ -76,14 +140,28 @@ export function uiSectionRawMembershipEditor(context) {
|
||||
if (d === 0) return; // called on newrow (shouldn't happen)
|
||||
if (_inChange) return; // avoid accidental recursive call #5731
|
||||
|
||||
var oldRole = d.member.role;
|
||||
var newRole = context.cleanRelationRole(d3_select(this).property('value'));
|
||||
|
||||
if (oldRole !== newRole) {
|
||||
if (!newRole.trim() && typeof d.role !== 'string') return;
|
||||
|
||||
var membersToUpdate = d.members.filter(function(member) {
|
||||
return member.role !== newRole;
|
||||
});
|
||||
|
||||
if (membersToUpdate.length) {
|
||||
_inChange = true;
|
||||
context.perform(
|
||||
actionChangeMember(d.relation.id, Object.assign({}, d.member, { role: newRole }), d.index),
|
||||
t('operations.change_role.annotation')
|
||||
function actionChangeMemberRoles(graph) {
|
||||
membersToUpdate.forEach(function(member) {
|
||||
var newMember = Object.assign({}, member, { role: newRole });
|
||||
delete newMember.index;
|
||||
graph = actionChangeMember(d.relation.id, newMember, member.index)(graph);
|
||||
});
|
||||
return graph;
|
||||
},
|
||||
t('operations.change_role.annotation', {
|
||||
n: membersToUpdate.length
|
||||
})
|
||||
);
|
||||
context.validator().validate();
|
||||
}
|
||||
@@ -95,12 +173,22 @@ export function uiSectionRawMembershipEditor(context) {
|
||||
this.blur(); // avoid keeping focus on the button
|
||||
_showBlank = false;
|
||||
|
||||
var member = { id: _entityIDs[0], type: context.entity(_entityIDs[0]).type, role: role };
|
||||
function actionAddMembers(relationId, ids, role) {
|
||||
return function(graph) {
|
||||
for (var i in ids) {
|
||||
var member = { id: ids[i], type: graph.entity(ids[i]).type, role: role };
|
||||
graph = actionAddMember(relationId, member)(graph);
|
||||
}
|
||||
return graph;
|
||||
};
|
||||
}
|
||||
|
||||
if (d.relation) {
|
||||
context.perform(
|
||||
actionAddMember(d.relation.id, member),
|
||||
t('operations.add_member.annotation')
|
||||
actionAddMembers(d.relation.id, _entityIDs, role),
|
||||
t('operations.add_member.annotation', {
|
||||
n: _entityIDs.length
|
||||
})
|
||||
);
|
||||
context.validator().validate();
|
||||
|
||||
@@ -108,7 +196,7 @@ export function uiSectionRawMembershipEditor(context) {
|
||||
var relation = osmRelation();
|
||||
context.perform(
|
||||
actionAddEntity(relation),
|
||||
actionAddMember(relation.id, member),
|
||||
actionAddMembers(relation.id, _entityIDs, role),
|
||||
t('operations.add.annotation.relation')
|
||||
);
|
||||
// changing the mode also runs `validate`
|
||||
@@ -124,9 +212,15 @@ export function uiSectionRawMembershipEditor(context) {
|
||||
// remove the hover-highlight styling
|
||||
utilHighlightEntities([d.relation.id], false, context);
|
||||
|
||||
var indexes = d.members.map(function(member) {
|
||||
return member.index;
|
||||
});
|
||||
|
||||
context.perform(
|
||||
actionDeleteMember(d.relation.id, d.index),
|
||||
t('operations.delete_member.annotation')
|
||||
actionDeleteMembers(d.relation.id, indexes),
|
||||
t('operations.delete_member.annotation', {
|
||||
n: _entityIDs.length
|
||||
})
|
||||
);
|
||||
context.validator().validate();
|
||||
}
|
||||
@@ -197,25 +291,7 @@ export function uiSectionRawMembershipEditor(context) {
|
||||
|
||||
function renderDisclosureContent(selection) {
|
||||
|
||||
var entityID = _entityIDs[0];
|
||||
|
||||
var entity = context.entity(entityID);
|
||||
var parents = context.graph().parentRelations(entity);
|
||||
|
||||
var memberships = [];
|
||||
|
||||
parents.slice(0, _maxMemberships).forEach(function(relation) {
|
||||
relation.members.forEach(function(member, index) {
|
||||
if (member.id === entity.id) {
|
||||
memberships.push({
|
||||
relation: relation,
|
||||
member: member,
|
||||
index: index,
|
||||
domId: utilUniqueDomId(entityID + '-membership-' + relation.id + '-' + index)
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
var memberships = getMemberships();
|
||||
|
||||
var list = selection.selectAll('.member-list')
|
||||
.data([0]);
|
||||
@@ -228,7 +304,7 @@ export function uiSectionRawMembershipEditor(context) {
|
||||
|
||||
var items = list.selectAll('li.member-row-normal')
|
||||
.data(memberships, function(d) {
|
||||
return osmEntity.key(d.relation) + ',' + d.index;
|
||||
return d.hash;
|
||||
});
|
||||
|
||||
items.exit()
|
||||
@@ -299,9 +375,19 @@ export function uiSectionRawMembershipEditor(context) {
|
||||
return d.domId;
|
||||
})
|
||||
.property('type', 'text')
|
||||
.attr('placeholder', t('inspector.role'))
|
||||
.property('value', function(d) {
|
||||
return typeof d.role === 'string' ? d.role : '';
|
||||
})
|
||||
.attr('title', function(d) {
|
||||
return Array.isArray(d.role) ? d.role.filter(Boolean).join('\n') : d.role;
|
||||
})
|
||||
.attr('placeholder', function(d) {
|
||||
return Array.isArray(d.role) ? t('inspector.multiple_roles') : t('inspector.role');
|
||||
})
|
||||
.classed('mixed', function(d) {
|
||||
return Array.isArray(d.role);
|
||||
})
|
||||
.call(utilNoAuto)
|
||||
.property('value', function(d) { return d.member.role; })
|
||||
.on('blur', changeRole)
|
||||
.on('change', changeRole);
|
||||
|
||||
@@ -309,7 +395,6 @@ export function uiSectionRawMembershipEditor(context) {
|
||||
wrapEnter.each(bindTypeahead);
|
||||
}
|
||||
|
||||
|
||||
var newMembership = list.selectAll('.member-row-new')
|
||||
.data(_showBlank ? [0] : []);
|
||||
|
||||
@@ -450,7 +535,7 @@ export function uiSectionRawMembershipEditor(context) {
|
||||
taginfo.roles({
|
||||
debounce: true,
|
||||
rtype: rtype || '',
|
||||
geometry: context.graph().geometry(entityID),
|
||||
geometry: context.graph().geometry(_entityIDs[0]),
|
||||
query: role
|
||||
}, function(err, data) {
|
||||
if (!err) callback(sort(role, data));
|
||||
|
||||
@@ -65,7 +65,9 @@ export function validationMissingRole() {
|
||||
onClick: function(context) {
|
||||
context.perform(
|
||||
actionDeleteMember(this.issue.entityIds[0], this.issue.data.member.index),
|
||||
t('operations.delete_member.annotation')
|
||||
t('operations.delete_member.annotation', {
|
||||
n: 1
|
||||
})
|
||||
);
|
||||
}
|
||||
})
|
||||
@@ -93,7 +95,9 @@ export function validationMissingRole() {
|
||||
var member = { id: this.issue.entityIds[1], type: oldMember.type, role: role };
|
||||
context.perform(
|
||||
actionChangeMember(this.issue.entityIds[0], member, oldMember.index),
|
||||
t('operations.change_role.annotation')
|
||||
t('operations.change_role.annotation', {
|
||||
n: 1
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user