Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

DnD Ordered TagPanel #1653

Merged
merged 39 commits into from
Dec 14, 2017
Merged

DnD Ordered TagPanel #1653

merged 39 commits into from
Dec 14, 2017

Conversation

lukebarnard1
Copy link
Contributor

Add ability to reorder the TagPanel items.

This works by having a TagOrderStore that stores the tags shown in the TagPanel in an order that the user controls via DnD. The full list of tags is stored in the im.vector.web.tag_ordering account event. By default, the ordering is alphabetical.

@lukebarnard1
Copy link
Contributor Author

This has sprouted a riot-web PR - element-hq/element-web#5790

Becuase the tests rely on being able to inspect the state of MatrixChat
React DnD specifies functions with upper-case first letters
dbkr
dbkr previously requested changes Dec 6, 2017
tag: monitor.getItem().tag,
targetTag: props.groupProfile.groupId,
// Note: we indicate that the tag should be after the target when
// it's being dragged over the top half of the target.
Copy link
Member

Choose a reason for hiding this comment

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

bottom half, no?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually no, it really is being dragged over the top half. I just found this snappier.

Copy link
Member

Choose a reason for hiding this comment

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

oh, so it is - so after == above?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nope, after == below. It feels snappier when you move a tile over the top half of another tile and the target is replaced with the one you're dragging, i.e. it's placed after the target.

// Initialise the state such that if account data is unset, default to the existing ordering
case 'all_tags':
this._setState({
allTags: payload.tags.sort(), // Sort lexically
Copy link
Member

Choose a reason for hiding this comment

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

I don't think this will do the right thing when you join a new group: the new group will be added to allTags but won't propagate to orderedTags which is what the TagPanel reads, so they new group won't show up? Somewhere we'll have to work out what changed in the new set of tags and make the equivalent change to the orderedTags, preserving the order.

I assume the idea here is that the TagOrderStore knows nothing about groups, but it doesn't seem great that it's the responsibility of a component to give the store the set of all tags, ie. that component has to be mounted once (and only once?) in the view for the store to work. I'd guess the pattern I'd expect here would be to have another store or something for the complete set of tags, then the TagOrderStore consumes that and exposes the same info but ordered.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

it's the responsibility of a component to give the store the set of all tags

I could move the fetch to get the list of groups to the TagOrderStore. The issue is that this would be perfect in an action creator, something that we haven't done before. Perhaps we should go and make a bunch of action creators and formalises all of our existing dispatches by wrapping them in functions (which is standard in other apps AFAICT).

have another store or something for the complete set of tags, then the TagOrderStore consumes that

Stores really should not communicate. all_tags is already being consumed by another store, and I believe the general pattern is to duplicate state as required such that the state of the store really only depends on its own previous state and the actions it receives.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think this will do the right thing when you join a new group

This is true. If only our store could listen for the group membership update...

Copy link
Member

Choose a reason for hiding this comment

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

Having TagOrderStore fetch the list of groups is fine by me

@@ -344,3 +346,5 @@ export default React.createClass({
);
},
});

export default DragDropContext(HTML5Backend)(LoggedInView);
Copy link
Member

Choose a reason for hiding this comment

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

I know little about react-dnd but I assume this still allows us to have other drag-n-drop contexts within it (ie. is it going to break the RoomTile DND?)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The point of moving it to the root LoggedInView is such that we can actually do DnD wherever we please. The instructions said to wrap the entire app, using two contexts (on e.g. TagPanel and LeftPanel) causes bugs.

Copy link
Member

Choose a reason for hiding this comment

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

ok 👍

@dbkr dbkr assigned lukebarnard1 and unassigned dbkr Dec 6, 2017
@lukebarnard1 lukebarnard1 assigned dbkr and unassigned lukebarnard1 Dec 7, 2017
@dbkr dbkr assigned lukebarnard1 and unassigned dbkr Dec 7, 2017
These can be used to dispatch actions immediately, or after some asynchronous
work has been done. Also, create GroupActions.fetchJoinedGroups as an example.

The concept of async action creators can be used in the following cases:
 - stores or views that do async work, dispatching based on the results
 - actions that have complicated payloads, would make more sense as functions
   with documentation that dispatch created actions.
@lukebarnard1
Copy link
Contributor Author

lukebarnard1 commented Dec 7, 2017

We agreed that I shall:

  • Move the group fetching logic to an action creator that will dispatch all_tags at some point. (Or probably joined_groups tbh).
  • Move the getAccountData in TagOrderStore to an action creator that is invoked whenever we get an account data event.
  • Move commit_tags to an action creator too

This introduces a generic way to register certain events emitted by
the js-sdk as those that should be propagated through as dispatched
actions.

This allows the store to treat the js-sdk as the "Server" in the
Flux data flow model. It also allows for stores to not be aware
specifically of the matrix client if they are only reading from it.
@lukebarnard1
Copy link
Contributor Author

I'm a little uncertain as to whether commitTagOrdering and fetchJoinedGroups actually belong in the separate actions directory. They feel like they could just go in the same file as the views that use them (TagPanel, in this case). wdyt?

Separating actions from views is mostly separation of concerns (we don't want our views doing async requests) but it's also nice to be able to use them from anywhere (without refactoring them later).

@lukebarnard1 lukebarnard1 assigned richvdh and unassigned lukebarnard1 Dec 13, 2017
@@ -18,6 +18,14 @@ import { asyncAction } from './actionCreators';

const GroupActions = {};

/**
* Create a GroupActions.fetchJoinedGroups action that represents an
* asyncronous request to fetch the groups to which a user is joined.
Copy link
Member

Choose a reason for hiding this comment

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

asynchronous

@@ -18,6 +18,14 @@ import { asyncAction } from './actionCreators';

const GroupActions = {};

/**
* Create a GroupActions.fetchJoinedGroups action that represents an
Copy link
Member

Choose a reason for hiding this comment

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

not aiui.

It returns an action creator: ie, a function which, when called, will start such a request and create such a (set of) actions.

@@ -19,8 +19,7 @@ import dis from '../dispatcher';
// TODO: migrate from sync_state to MatrixActions.sync so that more js-sdk events
// become dispatches in the same place.
/**
* An action creator that will map a `sync` event to a MatrixActions.sync action,
* each parameter mapping to a key-value in the action.
* Create a MatrixActions.sync action that represents a MatrixClient `sync` event.
Copy link
Member

Choose a reason for hiding this comment

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

yup, we should still document the members of the action object imho.

* An action creator that will map an account data matrix event to a
* MatrixActions.accountData action.
* Create a MatrixActions.accountData action that represents a MatrixClient `accountData`
* matrix event.
Copy link
Member

Choose a reason for hiding this comment

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

ditto

Copy link
Member

Choose a reason for hiding this comment

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

(it would help my brain if you could give some examples of what event_type is likely to be, here)

*
* @param {MatrixClient} matrixClient the matrix client with which to
* register a listener.
* @param {MatrixClient} matrixClient the matrix client.
Copy link
Member

Choose a reason for hiding this comment

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

why do we pass this in, given it's ignored?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Basically so that we can handle both action creators the same in _addMatrixClientListener

start(matrixClient) {
this._addMatrixClientListener(matrixClient, 'sync', createSyncAction);
this._addMatrixClientListener(matrixClient, 'accountData', createAccountDataAction);
},

/**
* Start listening to events emitted by matrixClient, dispatch an action created by the
Copy link
Member

Choose a reason for hiding this comment

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

is there a word missing here?

* actionCreator function.
* @param {MatrixClient} matrixClient a MatrixClient to register a listener with.
* @param {string} eventName the event to listen to on MatrixClient.
* @param {function} actionCreator a function that should return an action to dispatch
Copy link
Member

Choose a reason for hiding this comment

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

could you also specify that the function will be passed the MatrixClient event?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm assuming you meant MatrixClient itself

Copy link
Member

Choose a reason for hiding this comment

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

both, but yes.

@@ -36,7 +36,15 @@ const TagPanel = React.createClass({

getInitialState() {
return {
orderedGroupTagProfiles: [],
// A list of group profiles for group tags
Copy link
Member

Choose a reason for hiding this comment

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

This doesn't mean a great deal to me. Probably merits a longer explanation.

@richvdh richvdh assigned lukebarnard1 and unassigned richvdh Dec 13, 2017
@richvdh richvdh dismissed dbkr’s stale review December 13, 2017 14:48

think luke has addressed dave's concerns

@lukebarnard1 lukebarnard1 assigned richvdh and unassigned lukebarnard1 Dec 13, 2017
// A list of group profiles for group tags
// A list of group profiles for tags that are group IDs. The intention in future
// is to allow arbitrary tags to be selected in the TagPanel, not just groups.
// For now, it suffices to maintain a list of ordered group profiles.
Copy link
Member

Choose a reason for hiding this comment

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

👍

const GroupActions = {};

/**
* Create a GroupActions.fetchJoinedGroups action that represents an
Copy link
Member

Choose a reason for hiding this comment

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

this is still incorrect

const TagOrderActions = {};

/**
* Create a TagOrderActions.commitTagOrdering action that represents an
Copy link
Member

Choose a reason for hiding this comment

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

as with GroupActions.fetchJoinedGroups, I don't think this is correct.

@richvdh richvdh assigned lukebarnard1 and unassigned richvdh Dec 13, 2017
@lukebarnard1 lukebarnard1 assigned richvdh and unassigned lukebarnard1 Dec 13, 2017
@lukebarnard1
Copy link
Contributor Author

lukebarnard1 commented Dec 13, 2017

Note: element-hq/element-web#5790 needs to merged in parallel this

Copy link
Member

@richvdh richvdh left a comment

Choose a reason for hiding this comment

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

lgtm!

@richvdh richvdh assigned lukebarnard1 and unassigned richvdh Dec 14, 2017
@lukebarnard1 lukebarnard1 merged commit 9975941 into develop Dec 14, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants