diff --git a/data/core.yaml b/data/core.yaml index 5b544040b..820e0fc0d 100644 --- a/data/core.yaml +++ b/data/core.yaml @@ -229,6 +229,11 @@ en: annotation: create: Added a turn restriction delete: Deleted a turn restriction + detachNode: + title: Detach + key: D + description: Detach this node from these lines/areas. + annotation: Detached a node from owning lines/areas. restriction: controls: distance: Distance diff --git a/dist/locales/en.json b/dist/locales/en.json index 3412491a4..8abaa9db5 100644 --- a/dist/locales/en.json +++ b/dist/locales/en.json @@ -297,6 +297,12 @@ "create": "Added a turn restriction", "delete": "Deleted a turn restriction" } + }, + "detachNode": { + "title": "Detach", + "key": "D", + "description": "Detach this node from these lines/areas.", + "annotation": "Detached a node from owning lines/areas." } }, "restriction": { diff --git a/modules/actions/detach_node.js b/modules/actions/detach_node.js new file mode 100644 index 000000000..d83fd46ed --- /dev/null +++ b/modules/actions/detach_node.js @@ -0,0 +1,27 @@ +import { osmNode } from '../osm'; + +export function actionDetachNode(nodeId) { + return function (graph) { + // Get the point in question + var node = graph.entity(nodeId); + // Get all of the ways it's currently attached to + var parentWays = graph.parentWays(node); + // Create a new node to replace the one we will detach + var replacementNode = osmNode({ loc: node.loc }); + // We need to process each way in turn, updating the graph as we go + return parentWays + .reduce(function (accGraph, parentWay) { + // Make a note of where in the way our target node is inside this way + var originalIndex = parentWay.nodes.indexOf(nodeId); + // Swap out the target node for the replacement + var updatedWay = parentWay + .removeNode(nodeId) // Remove our target node from the parent way + .addNode(replacementNode.id, originalIndex); // Add in the replacement node in its place + // Update the graph with the updated way + return accGraph.replace(updatedWay); + }, + // Seed the reduction with the input graph, updated to include the replacementNode so + // that is accessible to the ways when we add it in to them + graph.replace(replacementNode)); + }; +} diff --git a/modules/actions/index.js b/modules/actions/index.js index 330690db2..4f15456d8 100644 --- a/modules/actions/index.js +++ b/modules/actions/index.js @@ -33,3 +33,4 @@ export { actionSplit } from './split'; export { actionStraighten } from './straighten'; export { actionUnrestrictTurn } from './unrestrict_turn'; export { actionReflect } from './reflect.js'; +export { actionDetachNode } from './detach_node'; \ No newline at end of file diff --git a/modules/operations/detach_node.js b/modules/operations/detach_node.js new file mode 100644 index 000000000..28ca9b600 --- /dev/null +++ b/modules/operations/detach_node.js @@ -0,0 +1,50 @@ +import { actionDetachNode } from '../actions/index'; +import { behaviorOperation } from '../behavior/index'; +import { modeMove } from '../modes/index'; +import { t } from '../util/locale'; + +export function operationDetachNode(selectedIDs, context) { + var selectedNode = selectedIDs[0]; + var operation = function () { + context.perform(actionDetachNode(selectedNode)); + context.enter(modeMove(context, [selectedNode], context.graph)); + }; + var hasTags = function (entity) { + return Object.keys(entity.tags).length > 0; + }; + operation.available = function () { + // Check multiple items aren't selected + if (selectedIDs.length !== 1) { + return false; + } + // Get the entity itself + var graph = context.graph(); + var entity = graph.hasEntity(selectedNode); + if (!entity) { + // This probably isn't possible + return false; + } + // Confirm entity is a node with tags + if (entity.type === 'node' && hasTags(entity)) { + // Confirm that the node is owned by at least 1 parent way + var parentWays = graph.parentWays(entity); + return parentWays && parentWays.length > 0; + } + // Not appropriate + return false; + }; + operation.disabled = function () { + return false; + }; + operation.tooltip = function () { + return t('operations.detachNode.description'); + }; + operation.annotation = function () { + return t('operations.detachNode.annotation'); + }; + operation.id = 'detachNode'; + operation.keys = [t('operations.detachNode.key')]; + operation.title = t('operations.detachNode.title'); + operation.behavior = behaviorOperation(context).which(operation); + return operation; +} diff --git a/modules/operations/index.js b/modules/operations/index.js index 1ad24cd97..8339d8205 100644 --- a/modules/operations/index.js +++ b/modules/operations/index.js @@ -10,3 +10,4 @@ export { operationReverse } from './reverse'; export { operationRotate } from './rotate'; export { operationSplit } from './split'; export { operationStraighten } from './straighten'; +export { operationDetachNode } from './detach_node'; diff --git a/svg/iD-sprite/operations/operation-detachNode.svg b/svg/iD-sprite/operations/operation-detachNode.svg new file mode 100644 index 000000000..fea5ba873 --- /dev/null +++ b/svg/iD-sprite/operations/operation-detachNode.svg @@ -0,0 +1,61 @@ + + diff --git a/test/index.html b/test/index.html index 188b9084c..73fd6cc59 100644 --- a/test/index.html +++ b/test/index.html @@ -1,5 +1,6 @@ +