From 0781b70f382b06706dcceb8ed1537069451b7a10 Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Fri, 15 Mar 2019 12:22:24 -0400 Subject: [PATCH] Add drag-and-drop reordering of relation members (close #2283) --- css/80_app.css | 18 +++++++-- data/core.yaml | 2 + dist/locales/en.json | 3 ++ modules/actions/index.js | 1 + modules/actions/move_member.js | 5 +++ modules/osm/relation.js | 6 +++ modules/ui/raw_member_editor.js | 67 ++++++++++++++++++++++++++++++++- 7 files changed, 98 insertions(+), 4 deletions(-) create mode 100644 modules/actions/move_member.js diff --git a/css/80_app.css b/css/80_app.css index 1c9f5c65d..bbfa4601c 100644 --- a/css/80_app.css +++ b/css/80_app.css @@ -2522,9 +2522,12 @@ img.tag-reference-wiki-image { /* Raw Member / Membership Editor ------------------------------------------------------- */ -.raw-member-editor .member-list li:first-child, -.raw-membership-editor .member-list li:first-child { - padding-top: 10px; +.raw-member-editor .member-list li, +.raw-membership-editor .member-list li { + position: relative; + border-radius: 4px; + margin-top: 10px; + margin-bottom: 10px; } .raw-member-editor .member-row .member-entity-name, .raw-membership-editor .member-row .member-entity-name { @@ -2560,6 +2563,15 @@ img.tag-reference-wiki-image { border: 0; } +.raw-member-editor .member-row.dragging { + opacity: 0.75; + z-index: 3000; + -webkit-box-shadow: 0px 0px 5px 0px rgba(0,0,0,0.3); + -moz-box-shadow: 0px 0px 5px 0px rgba(0,0,0,0.3); + box-shadow: 0px 0px 5px 0px rgba(0,0,0,0.3); +} +.raw-member-editor .member-row.dragging + /* preserve extra space at bottom of inspector to allow for dropdown options - #5280 */ .raw-membership-editor.inspector-inner { margin-bottom: 150px; diff --git a/data/core.yaml b/data/core.yaml index 4f0f78fcf..72f415511 100644 --- a/data/core.yaml +++ b/data/core.yaml @@ -142,6 +142,8 @@ en: annotation: Added a member to a relation. delete_member: annotation: Removed a member from a relation. + reorder_members: + annotation: Reordered a relation's members. connect: annotation: from_vertex: diff --git a/dist/locales/en.json b/dist/locales/en.json index d5e750a95..81597210e 100644 --- a/dist/locales/en.json +++ b/dist/locales/en.json @@ -186,6 +186,9 @@ "delete_member": { "annotation": "Removed a member from a relation." }, + "reorder_members": { + "annotation": "Reordered a relation's members." + }, "connect": { "annotation": { "from_vertex": { diff --git a/modules/actions/index.js b/modules/actions/index.js index 6eac6bfc6..daf3e1a0e 100644 --- a/modules/actions/index.js +++ b/modules/actions/index.js @@ -21,6 +21,7 @@ export { actionMergeNodes } from './merge_nodes'; export { actionMergePolygon } from './merge_polygon'; export { actionMergeRemoteChanges } from './merge_remote_changes'; export { actionMove } from './move'; +export { actionMoveMember } from './move_member'; export { actionMoveNode } from './move_node'; export { actionNoop } from './noop'; export { actionOrthogonalize } from './orthogonalize'; diff --git a/modules/actions/move_member.js b/modules/actions/move_member.js new file mode 100644 index 000000000..a79daee21 --- /dev/null +++ b/modules/actions/move_member.js @@ -0,0 +1,5 @@ +export function actionMoveMember(relationId, fromIndex, toIndex) { + return function(graph) { + return graph.replace(graph.entity(relationId).moveMember(fromIndex, toIndex)); + }; +} diff --git a/modules/osm/relation.js b/modules/osm/relation.js index 1ba283df4..c642b0740 100644 --- a/modules/osm/relation.js +++ b/modules/osm/relation.js @@ -167,6 +167,12 @@ _extend(osmRelation.prototype, { return this.update({members: members}); }, + moveMember: function(fromIndex, toIndex) { + var members = this.members.slice(); + members.splice(toIndex, 0, members.splice(fromIndex, 1)[0]); + return this.update({members: members}); + }, + // Wherever a member appears with id `needle.id`, replace it with a member // with id `replacement.id`, type `replacement.type`, and the original role, diff --git a/modules/ui/raw_member_editor.js b/modules/ui/raw_member_editor.js index e60d5bbca..69474753f 100644 --- a/modules/ui/raw_member_editor.js +++ b/modules/ui/raw_member_editor.js @@ -1,10 +1,11 @@ +import { drag as d3_drag } from 'd3-drag'; import { event as d3_event, select as d3_select } from 'd3-selection'; import { t } from '../util/locale'; -import { actionChangeMember, actionDeleteMember } from '../actions'; +import { actionChangeMember, actionDeleteMember, actionMoveMember } from '../actions'; import { modeBrowse, modeSelect } from '../modes'; import { osmEntity } from '../osm'; import { svgIcon } from '../svg'; @@ -229,6 +230,70 @@ export function uiRawMemberEditor(context) { wrapEnter.each(bindTypeahead); } + var dragOrigin, targetIndex; + + itemsEnter.call(d3_drag() + .on('start', function() { + dragOrigin = { + x: d3_event.x, + y: d3_event.y + }; + targetIndex = null; + }) + .on('drag', function(d, index) { + var x = d3_event.x - dragOrigin.x, + y = d3_event.y - dragOrigin.y; + + if (!d3_select(this).classed('dragging') && + // don't display drag until dragging beyond a distance threshold + Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)) <= 5) return; + + d3_select(this) + .classed('dragging', true); + + targetIndex = null; + + selection.selectAll('li.member-row') + .style('transform', function(d2, index2) { + var node = d3_select(this).node(); + if (index === index2) { + return 'translate(' + x + 'px, ' + y + 'px)'; + } else if (index2 > index && d3_event.y > node.offsetTop - node.offsetHeight) { + if (targetIndex === null || index2 > targetIndex) { + targetIndex = index2; + } + return 'translateY(-100%)'; + } else if (index2 < index && d3_event.y < node.offsetTop) { + if (targetIndex === null || index2 < targetIndex) { + targetIndex = index2; + } + return 'translateY(100%)'; + } + return null; + }); + }) + .on('end', function(d, index) { + + if (!d3_select(this).classed('dragging')) { + return; + } + + d3_select(this) + .classed('dragging', false); + + selection.selectAll('li.member-row') + .style('transform', null); + + if (targetIndex !== null) { + // dragged to a new position, reorder + context.perform( + actionMoveMember(d.relation.id, index, targetIndex), + t('operations.reorder_members.annotation') + ); + } + }) + ); + // update items = items