Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 81 additions & 2 deletions css/80_app.css
Original file line number Diff line number Diff line change
Expand Up @@ -2885,9 +2885,88 @@ img.tag-reference-wiki-image {
margin: 0;
padding-bottom: 10px;
}

.member-list .member-row label {
border-bottom: none;
}

.member-list .member-row div.form-field-input-wrap {
border: 1px solid var(--border-color);
border-top: none;
border-right: none;
}
.member-list .member-row div.form-field-input-wrap input {
border-top: 1px solid var(--border-color);
flex: 1 1 auto;
min-width: 80px;
}
.member-list .member-row div.form-field-input-wrap .combobox-caret,
.member-list .member-row div.form-field-input-wrap button {
flex: 0 0 auto;
}
.section-raw-member-editor .member-list .member-row div.form-field-input-wrap input {
border-bottom: none;
border-radius: 0;
}
.section-raw-membership-editor .member-list .member-row div.form-field-input-wrap input {
border-bottom: none;
border-left: none; /* todo: rtl layout */
border-bottom-right-radius: 0; /* todo: rtl layout */
}
.member-list .member-row div.form-field-input-wrap button {
border-bottom: none;
border-top: 1px solid var(--border-color);
}

.member-list .grab-icon svg.icon {
padding-top: 4px;
height: 20px;
fill: var(--text-color);
opacity: .5;
}

.member-list .member-row.member-connects {
padding-bottom: 0;
margin-bottom: -1px;
}
.member-list .member-row.member-connects-prev label {
border-radius: 0;
}
.member-list .member-row.member-connects-next div.form-field-input-wrap {
border-radius: 0;
}
.member-list .member-row.member-connects-next button {
border-radius: 0;
}

.members-download button,
.members-download button.loading {
width: 32px;
background: none;
margin-right: -5px;
}
.members-download button .icon {
fill: var(--text-color);
opacity: .5;
}


/* only the raw-member-editor is reorederable, not the raw-membership-editor */
.section-raw-member-editor .member-row .label-text { cursor: grab; }
.section-raw-member-editor .member-row .label-text:active { cursor: grabbing; }
.section-raw-member-editor .member-row label,
.section-raw-member-editor .member-row div.form-field-input-wrap {
cursor: grab;
}
.section-raw-member-editor .member-row:active label,
.section-raw-member-editor .member-row:active div.form-field-input-wrap {
cursor: grabbing;
}
.section-raw-member-editor .member-row:hover label,
.section-raw-member-editor .member-row:hover div.form-field-input-wrap {
background: var(--bg-color-3);
}
.section-raw-member-editor .member-row:hover .grab-icon svg.icon {
opacity: .8;
}

.section-raw-member-editor .member-row .member-entity-name,
.section-raw-membership-editor .member-row .member-entity-name {
Expand Down
18 changes: 16 additions & 2 deletions data/core.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,12 @@ en:
relation: This can't be disconnected because it connects members of a relation.
merge:
title: Merge
description: Merge these features.
description:
join: Join these lines.
merge_polygon: Combine the selected areas.
merge_members: Add these features as members to the selected relation.
merge_nodes: Merge these points.
merge: Merge these features.
key: C
annotation:
one: "Merged a feature."
Expand All @@ -328,6 +333,12 @@ en:
conflicting_relations: These features can't be merged because they belong to conflicting relations.
paths_intersect: These features can't be merged because the resulting path would intersect itself.
too_many_vertices: These features can't be merged because the resulting path would have too many points.
members_remove:
title: Remove Members
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove Members doesn’t look like an operation that undoes what Merge does. How about Unmerge? Or maybe make it look like a version of the Detach operation.

description: Remove one or more selected members from the selected relation.
annotation:
one: "Remove a member from the relation."
other: "Merged {n} members from the relation."
move:
title: Move
description:
Expand Down Expand Up @@ -715,7 +726,10 @@ en:
feature_type: Feature Type
fields: Fields
tags: Tags
members: Members
members:
title: Members
grab: Drag and drop to reorder members
download_all: Download all members
relations: Relations
features: Features
title_count: "{title} ({count})"
Expand Down
2 changes: 2 additions & 0 deletions modules/actions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ export { actionDiscardTags } from './discard_tags';
export { actionDisconnect } from './disconnect';
export { actionExtract } from './extract';
export { actionJoin } from './join';
export { actionMembersRemove } from './members_remove';
export { actionMerge } from './merge';
export { actionMergeNodes } from './merge_nodes';
export { actionMergePolygon } from './merge_polygon';
export { actionMergeMembers } from './merge_members';
export { actionMergeRemoteChanges } from './merge_remote_changes';
export { actionMove } from './move';
export { actionMoveMember } from './move_member';
Expand Down
2 changes: 2 additions & 0 deletions modules/actions/join.js
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,8 @@ export function actionJoin(ids) {
isFinite(tagsB[key]));
}

action.id = 'join';


return action;
}
59 changes: 59 additions & 0 deletions modules/actions/members_remove.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { actionDeleteMembers } from './delete_members';


// `actionMembersRemove` removes selected members from a single relation
//
// * there must be only one relation in the selection
// * all other selected entities are members of the relation
// * the operation removes all occurrences of the features from
// being a member of the relation

export function actionMembersRemove(entityIDs) {

// export function actionDeleteMembers(relationId, memberIndexes) {

var action = function(graph) {
let relation;
for (const entityID of entityIDs) {
var entity = graph.entity(entityID);
if (entity.type === 'relation') {
relation = entity;
}
}
let memberIndices = [];
for (let i = 0; i < relation.members.length; i++) {
if (entityIDs.indexOf(relation.members[i].id) > 0) {
memberIndices.push(i);
}
}

graph = actionDeleteMembers(relation.id, memberIndices)(graph);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a feature is a member of the relation multiple times, this operation completely removes all its memberships from the relation. This makes it less useful for dealing with incorrect redundant memberships, though it would make more sense if the operation were called something like Detach.


// only keep relation in new selection (see operation/merge.js)
entityIDs.splice(0, entityIDs.indexOf(relation.id));
entityIDs.splice(1, entityIDs.length - 1);

return graph;
};


action.disabled = function(graph) {
let relation;
for (const entityID of entityIDs) {
var entity = graph.entity(entityID);
if (entity.type === 'relation') {
if (relation !== undefined) return 'not_eligible';
relation = entity;
}
}
if (relation === undefined) {
return 'not_eligible';
}
if (entityIDs.some(entityID => entityID !== relation.id &&
!relation.members.find(member => member.id === entityID))) {
return 'not_eligible';
}
};

return action;
}
2 changes: 2 additions & 0 deletions modules/actions/merge.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ export function actionMerge(ids) {
}
};

action.id = 'merge';


return action;
}
53 changes: 53 additions & 0 deletions modules/actions/merge_members.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { actionAddMember } from './add_member';


// `actionMergeMembers` adds new members to a single relation
//
// * there must be only one relation in the selection
// * all other selected entities are added as new members to the relation
// * sorting is done "automagically" when applicable (e.g. connecting to
// existing members of a route), otherwise they will be appended at the
// end of the members list
// * members are added using an empty role

export function actionMergeMembers(entityIDs) {

var action = function(graph) {
let relationID;
let newMembers = [];
for (const entityID of entityIDs) {
var entity = graph.entity(entityID);
if (entity.type === 'relation') {
relationID = entityID;
} else {
newMembers.push({
id: entity.id,
type: entity.type,
role: ''
});
}
}

for (const member of newMembers) {
graph = actionAddMember(relationID, member)(graph);
}

// only keep relation in new selection (see operation/merge.js)
entityIDs.splice(0, entityIDs.indexOf(relationID));
entityIDs.splice(1, entityIDs.length - 1);

return graph;
};


action.disabled = function(graph) {
const relationCount = entityIDs.filter(entityID =>
graph.entity(entityID).type === 'relation')
.length;
if (relationCount !== 1) return 'not_eligible';
Comment on lines +44 to +47
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Merge action remains enabled even when the Remove Members action is enabled, so the context menu starts out with ➖ followed by ➕. Do we need to have both actions enabled at the same time? Users shouldn’t need to add redundant memberships very often, but when they do have to, they can still add it the old-fashioned way with the way selected.

};

action.id = 'merge_members';

return action;
}
2 changes: 2 additions & 0 deletions modules/actions/merge_nodes.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,5 +58,7 @@ export function actionMergeNodes(nodeIDs, loc) {
return actionConnect(nodeIDs).disabled(graph);
};

action.id = 'merge_nodes';

return action;
}
2 changes: 2 additions & 0 deletions modules/actions/merge_polygon.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@ export function actionMergePolygon(ids, newRelationId) {
}
};

action.id = 'merge_polygon';


return action;
}
1 change: 1 addition & 0 deletions modules/operations/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export { operationDelete } from './delete';
export { operationDisconnect } from './disconnect';
export { operationDowngrade } from './downgrade';
export { operationExtract } from './extract';
export { operationMembersRemove } from './members_remove';
export { operationMerge } from './merge';
export { operationMove } from './move';
export { operationOrthogonalize } from './orthogonalize';
Expand Down
52 changes: 52 additions & 0 deletions modules/operations/members_remove.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { t } from '../core/localizer';

import { actionMembersRemove } from '../actions/members_remove';

import { behaviorOperation } from '../behavior/operation';
import { modeSelect } from '../modes/select';

export function operationMembersRemove(context, selectedIDs) {

var _action = actionMembersRemove(selectedIDs);

var operation = function() {

if (operation.disabled()) return;

context.perform(_action, operation.annotation());

context.validator().validate();

var resultIDs = selectedIDs.filter(context.hasEntity);
if (resultIDs.length > 1) {
var interestingIDs = resultIDs.filter(function(id) {
return context.entity(id).hasInterestingTags();
});
if (interestingIDs.length) resultIDs = interestingIDs;
}
context.enter(modeSelect(context, resultIDs));
};

operation.available = function() {
return !_action.disabled(context.graph());
};

operation.disabled = function() {
return _action.disabled(context.graph());
};

operation.tooltip = function() {
return t.append('operations.members_remove.description');
};

operation.annotation = function() {
return t('operations.members_remove.annotation', { n: selectedIDs.length });
};

operation.id = 'members_remove';
operation.keys = [];
operation.title = t.append('operations.members_remove.title');
operation.behavior = behaviorOperation(context).which(operation);

return operation;
}
14 changes: 9 additions & 5 deletions modules/operations/merge.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { actionJoin } from '../actions/join';
import { actionMerge } from '../actions/merge';
import { actionMergeNodes } from '../actions/merge_nodes';
import { actionMergePolygon } from '../actions/merge_polygon';
import { actionMergeMembers } from '../actions/merge_members';

import { behaviorOperation } from '../behavior/operation';
import { modeSelect } from '../modes/select';
Expand All @@ -15,18 +16,21 @@ export function operationMerge(context, selectedIDs) {

function getAction() {
// prefer a non-disabled action first
var join = actionJoin(selectedIDs);
const join = actionJoin(selectedIDs);
if (!join.disabled(context.graph())) return join;

var merge = actionMerge(selectedIDs);
const merge = actionMerge(selectedIDs);
if (!merge.disabled(context.graph())) return merge;

var mergePolygon = actionMergePolygon(selectedIDs);
const mergePolygon = actionMergePolygon(selectedIDs);
if (!mergePolygon.disabled(context.graph())) return mergePolygon;

var mergeNodes = actionMergeNodes(selectedIDs);
const mergeNodes = actionMergeNodes(selectedIDs);
if (!mergeNodes.disabled(context.graph())) return mergeNodes;

const mergeWithRelation = actionMergeMembers(selectedIDs);
if (!mergeWithRelation.disabled(context.graph())) return mergeWithRelation;

// otherwise prefer an action with an interesting disabled reason
if (join.disabled(context.graph()) !== 'not_eligible') return join;
if (merge.disabled(context.graph()) !== 'not_eligible') return merge;
Expand Down Expand Up @@ -83,7 +87,7 @@ export function operationMerge(context, selectedIDs) {
}
return t.append('operations.merge.' + disabled);
}
return t.append('operations.merge.description');
return t.append(`operations.merge.description.${_action.id}`);
};

operation.annotation = function() {
Expand Down
Loading
Loading