From db4f0cb0bffc091df477899f00eded9c265bedc7 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Wed, 14 Feb 2018 16:40:24 +0000 Subject: [PATCH 1/6] Handle adding previously removed tags --- src/actions/TagOrderActions.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/actions/TagOrderActions.js b/src/actions/TagOrderActions.js index 3504adb09b..38bada2228 100644 --- a/src/actions/TagOrderActions.js +++ b/src/actions/TagOrderActions.js @@ -35,6 +35,7 @@ const TagOrderActions = {}; TagOrderActions.moveTag = function(matrixClient, tag, destinationIx) { // Only commit tags if the state is ready, i.e. not null let tags = TagOrderStore.getOrderedTags(); + let removedTags = TagOrderStore.getRemovedTagsAccountData(); if (!tags) { return; } @@ -42,17 +43,19 @@ TagOrderActions.moveTag = function(matrixClient, tag, destinationIx) { tags = tags.filter((t) => t !== tag); tags = [...tags.slice(0, destinationIx), tag, ...tags.slice(destinationIx)]; + removedTags = removedTags.filter((t) => t !== tag); + const storeId = TagOrderStore.getStoreId(); return asyncAction('TagOrderActions.moveTag', () => { Analytics.trackEvent('TagOrderActions', 'commitTagOrdering'); return matrixClient.setAccountData( 'im.vector.web.tag_ordering', - {tags, _storeId: storeId}, + {tags, removedTags, _storeId: storeId}, ); }, () => { // For an optimistic update - return {tags}; + return {tags, removedTags}; }); }; From b626420eb93e4f25333cd5c98758d7d3cc586265 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Wed, 14 Feb 2018 16:40:58 +0000 Subject: [PATCH 2/6] Move DND context to LoggedInView so that we can drag things from any part of the logged in app to another. (Specifically GroupView and TagPanel). --- src/components/structures/LoggedInView.js | 70 +++++++++++++++++++---- 1 file changed, 60 insertions(+), 10 deletions(-) diff --git a/src/components/structures/LoggedInView.js b/src/components/structures/LoggedInView.js index d7fe699156..f6bbfd247b 100644 --- a/src/components/structures/LoggedInView.js +++ b/src/components/structures/LoggedInView.js @@ -19,6 +19,7 @@ limitations under the License. import * as Matrix from 'matrix-js-sdk'; import React from 'react'; import PropTypes from 'prop-types'; +import { DragDropContext } from 'react-beautiful-dnd'; import { KeyCode, isOnlyCtrlOrCmdKeyEvent } from '../../Keyboard'; import Notifier from '../../Notifier'; @@ -30,6 +31,9 @@ import sessionStore from '../../stores/SessionStore'; import MatrixClientPeg from '../../MatrixClientPeg'; import SettingsStore from "../../settings/SettingsStore"; +import TagOrderActions from '../../actions/TagOrderActions'; +import RoomListActions from '../../actions/RoomListActions'; + /** * This is what our MatrixChat shows when we are logged in. The precise view is * determined by the page_type property. @@ -207,6 +211,50 @@ const LoggedInView = React.createClass({ } }, + _onDragEnd: function(result) { + // Dragged to an invalid destination, not onto a droppable + if (!result.destination) { + return; + } + + const dest = result.destination.droppableId; + + if (dest === 'tag-panel-droppable') { + // Could be "GroupTile +groupId:domain" + const draggableId = result.draggableId.split(' ').pop(); + + // Dispatch synchronously so that the TagPanel receives an + // optimistic update from TagOrderStore before the previous + // state is shown. + dis.dispatch(TagOrderActions.moveTag( + this._matrixClient, + draggableId, + result.destination.index, + ), true); + } else if (dest.startsWith('room-sub-list-droppable_')) { + this._onRoomTileEndDrag(result); + } + }, + + _onRoomTileEndDrag: function(result) { + let newTag = result.destination.droppableId.split('_')[1]; + let prevTag = result.source.droppableId.split('_')[1]; + if (newTag === 'undefined') newTag = undefined; + if (prevTag === 'undefined') prevTag = undefined; + + const roomId = result.draggableId.split('_')[1]; + + const oldIndex = result.source.index; + const newIndex = result.destination.index; + + dis.dispatch(RoomListActions.tagRoom( + this._matrixClient, + this._matrixClient.getRoom(roomId), + prevTag, newTag, + oldIndex, newIndex, + ), true); + }, + render: function() { const LeftPanel = sdk.getComponent('structures.LeftPanel'); const RightPanel = sdk.getComponent('structures.RightPanel'); @@ -328,16 +376,18 @@ const LoggedInView = React.createClass({ return (
{ topBar } -
- -
- { page_element } -
- { right_panel } -
+ +
+ +
+ { page_element } +
+ { right_panel } +
+
); }, From 74c8a74e7d2106057b9c1e368914146c8d2ad8d3 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Wed, 14 Feb 2018 16:43:01 +0000 Subject: [PATCH 3/6] Add Droppable to GroupView to contain the GroupTiles as Draggables --- src/components/structures/MyGroups.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/components/structures/MyGroups.js b/src/components/structures/MyGroups.js index 22157beaca..4c9229a2ea 100644 --- a/src/components/structures/MyGroups.js +++ b/src/components/structures/MyGroups.js @@ -17,6 +17,7 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; import GeminiScrollbar from 'react-gemini-scrollbar'; +import { Droppable } from 'react-beautiful-dnd'; import sdk from '../../index'; import { _t } from '../../languageHandler'; import dis from '../../dispatcher'; @@ -74,7 +75,13 @@ export default withMatrixClient(React.createClass({ contentHeader = groupNodes.length > 0 ?

{ _t('Your Communities') }

:
; content = groupNodes.length > 0 ? - { groupNodes } + + { (provided, snapshot) => ( +
+ { groupNodes } +
+ ) } +
:
{ _t( From 3850b552a5bddb753d21da3634d30914ed1f520f Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Wed, 14 Feb 2018 16:46:06 +0000 Subject: [PATCH 4/6] Make GroupTile avatar draggable --- src/components/views/groups/GroupTile.js | 32 +++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/src/components/views/groups/GroupTile.js b/src/components/views/groups/GroupTile.js index ce426a9b78..70947afa65 100644 --- a/src/components/views/groups/GroupTile.js +++ b/src/components/views/groups/GroupTile.js @@ -17,10 +17,12 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; import {MatrixClient} from 'matrix-js-sdk'; +import { Draggable } from 'react-beautiful-dnd'; import sdk from '../../../index'; import dis from '../../../dispatcher'; import FlairStore from '../../../stores/FlairStore'; + const GroupTile = React.createClass({ displayName: 'GroupTile', @@ -78,9 +80,33 @@ const GroupTile = React.createClass({ profile.avatarUrl, avatarHeight, avatarHeight, "crop", ) : null; return -
- -
+ + { (provided, snapshot) => ( +
+
+
+ +
+
+ { /* Instead of a blank placeholder, use a copy of the avatar itself. */ } + { provided.placeholder ? +
+ +
: +
+ } +
+ ) } +
{ name }
{ descElement } From 389d96bc46047bf2791b2fc6bf8200d68f7673ed Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Wed, 14 Feb 2018 16:47:29 +0000 Subject: [PATCH 5/6] Use optimistic removedTagsAccountData state in TagOrderStore when receiving TagOrderActions.moveTag.pending, which now exposes this state. --- src/stores/TagOrderStore.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/stores/TagOrderStore.js b/src/stores/TagOrderStore.js index 78e4a6e95d..eef078d8da 100644 --- a/src/stores/TagOrderStore.js +++ b/src/stores/TagOrderStore.js @@ -89,6 +89,7 @@ class TagOrderStore extends Store { // Optimistic update of a moved tag this._setState({ orderedTags: payload.request.tags, + removedTagsAccountData: payload.request.removedTags, }); break; } From 3948ee8ca14d5f00f2a0163a692569f303be9811 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Wed, 14 Feb 2018 17:53:54 +0000 Subject: [PATCH 6/6] Give each GroupTile avatar its own droppable so that they can be dragged and dropped without interacting with each other, as they would do if GroupView contained one droppable to contain them all. --- src/components/structures/MyGroups.js | 13 ++---- src/components/views/groups/GroupTile.js | 56 +++++++++++++----------- 2 files changed, 35 insertions(+), 34 deletions(-) diff --git a/src/components/structures/MyGroups.js b/src/components/structures/MyGroups.js index 4c9229a2ea..116607fb08 100644 --- a/src/components/structures/MyGroups.js +++ b/src/components/structures/MyGroups.js @@ -17,7 +17,6 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; import GeminiScrollbar from 'react-gemini-scrollbar'; -import { Droppable } from 'react-beautiful-dnd'; import sdk from '../../index'; import { _t } from '../../languageHandler'; import dis from '../../dispatcher'; @@ -74,14 +73,10 @@ export default withMatrixClient(React.createClass({ }); contentHeader = groupNodes.length > 0 ?

{ _t('Your Communities') }

:
; content = groupNodes.length > 0 ? - - - { (provided, snapshot) => ( -
- { groupNodes } -
- ) } -
+ +
+ { groupNodes } +
:
{ _t( diff --git a/src/components/views/groups/GroupTile.js b/src/components/views/groups/GroupTile.js index 70947afa65..f1dbb75988 100644 --- a/src/components/views/groups/GroupTile.js +++ b/src/components/views/groups/GroupTile.js @@ -17,7 +17,7 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; import {MatrixClient} from 'matrix-js-sdk'; -import { Draggable } from 'react-beautiful-dnd'; +import { Draggable, Droppable } from 'react-beautiful-dnd'; import sdk from '../../../index'; import dis from '../../../dispatcher'; import FlairStore from '../../../stores/FlairStore'; @@ -80,33 +80,39 @@ const GroupTile = React.createClass({ profile.avatarUrl, avatarHeight, avatarHeight, "crop", ) : null; return - - { (provided, snapshot) => ( -
-
+ { (droppableProvided, droppableSnapshot) => ( +
+ -
- -
-
- { /* Instead of a blank placeholder, use a copy of the avatar itself. */ } - { provided.placeholder ? -
- -
: -
- } + { (provided, snapshot) => ( +
+
+
+ +
+
+ { /* Instead of a blank placeholder, use a copy of the avatar itself. */ } + { provided.placeholder ? +
+ +
: +
+ } +
+ ) } +
) } - +
{ name }
{ descElement }