-
Notifications
You must be signed in to change notification settings - Fork 263
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Drag and Drop (revisited) #4048
Drag and Drop (revisited) #4048
Conversation
Awesome! In my opinion this is a huge usability improvement. I will look at the code and test as soon as I can :) |
🤯 |
@jancborchardt please have a look at the design :) |
So, does anyone happen to have an opinion on this?
I'm eager to put time into optimizations, but would like to put that time into the "right" thing. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First of all, I really like your changes. Dragging works very well, feels nice and fits the design of nextcloud. The code looks clean overall but I commented on some minor things here and there.
The new global event bus is a great addition but might be out of scope of this PR. If this is blocking the review/merge we could move this to a follow up PR (but keep the uses in the dragging related code here). In my opinion we should definitely keep it (be it here or in another PR) as it increases our code quality. Currently, it is kind of hard to trace all the separate event buses scattered in our code.
When moving an envelope the thread is not updated. E.g. moving a collapsed envelope to another mailbox and then expanding this envelope in the thread will cause it to not load because the attached message/envelope does not exist anymore. I had a similar issue in my move modal code. You could use the new global event bus and update the thread after moving via this.fetchThread()
or navigate away when the thread root was moved. Take a look at my solution https://github.com/nextcloud/mail/pull/3915/files#diff-ee27750a392035af626e82a2c822204689adf614396d005d18281faf701da601 (especially at the onMove()
method)
The draggable ghost does not have a transparent background but this seems to be an issue with the proprietary nvidia linux drivers and compositing and is not related to your code.
EDIT: spelling
src/App.vue
Outdated
// TODO: move to mail.scss | ||
// The drag-ghost is appended to the body | ||
// and cannot be styled from within a | ||
// component with scoped CSS |
This comment was marked as outdated.
This comment was marked as outdated.
Sorry, something went wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤔 Where/How is mail.scss compiled though? It's not picked up by the default watcher process.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By Nextcloud server in the background. But we're trying to move away from this. Please try to put everything into a component scss so it's bundled with the app ✌️
39f8c89
to
1ed3169
Compare
I remember facing the same problem when I once tried to revive drag and drop in an early version of Mail with Vue. Now that we streamlined our data structures a bit we don't have to pass around objects anymore because messages can be identified by unique integer IDs (was a combination of account id, mailbox name and message UID before). So, can we pass the array of IDs? Is that easier than an object? If a string works we can also serialize the ID array to a json string. 🤔 |
It would be charming indeed to detach this thing from the store. My first approach was to attach a |
Okay, so this API is melting my brain. Apparently, you shouldn't be able to access the data attached to the dataTransfer object on That's a bummer, because I am trying to prevent the user from dropping the mail into the same folder it came from. People are recommending global variables in that case (so... the store in our case). 🤷♂️ FWIW – attaching a stringified JSON and parsing it in The other reason the current implementation is relying on the global store is for each Mailbox in the sidebar to detect wether it is a valid drop target or not (and to disable/fade it out in that case). Without using the store, I'd have to emit an event to inform the mailboxes about the origin of envelopes being dragged instead. Which is... I don't know. It's just shifting the load from the store over to events. I prefer the data-driven approach using the store tbh. What do you think? |
Yeah guess we have to use the store then. Or we just don't do the move (and show a hint to the user via a Toast) if source=destination.
The dynamic drop target is required for the move from the unified/priority inbox to their real mailboxes, right? Because otherwise the current mailbox that is open should never be a valid drop target. |
1165912
to
d7ecfb1
Compare
Alright, I refactored it into two directives
I still need to move the styles, but the most important change is the extraction into directives. Also: I am not using the store to temporarily persist the envelopes anymore. I am using local state within the directives in combination with the DataTransferObject: It works faily well like that! I also still need to look into @st3iny 's comment:
Oh, there is one addition in terms of usability: when starting the drag, not only is the list of folders expanded, I am now also expanding all folders and their subfolders, which helps alot I think. When dragging is done, the subfolders are collapsed again. I'm sure I forgot to mention something, but I am stoked about the fact that I managed to separate most of the functionality into these two directives. 🥳 Could you please have a look at that? |
15fbb4e
to
a326bb1
Compare
Okay, I'm thrilled to announce that I think I'm done. 🔥 The issue @st3iny mentioned (thanks for that by the way, I actually didn't notice that myself!) is resolved here: 173201d. I decided to check if the user is currently viewing a message and if that As for the styles, I moved all related styling to separate scss files within the directive's folder. To avoid potential styling collisions (since they're not scoped anymore), the classes and stylable attributes are now fairly specific. I also refactored and moved around some code. The Oh, I also reverted my global EventBus changes, which were still lingering in there... ;-) It would be great if you took a final look at the whole thing one more time, @st3iny and @ChristophWurst, before I bring it up to speed with master and squash everything. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Really great design-wise @brueckner, amazing work! :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rest of the code looks good. Still have to wrap my head around those directives and the other logic but it looks solid :)
src/directives/drag-and-drop/draggable-envelope/draggable-envelope.js
Outdated
Show resolved
Hide resolved
f2c36a0
to
404b0ad
Compare
Thanks for the review @ChristophWurst, I've implemented all of the remarks. I'll hit the "Ready for review" button to move it out of drafting phase. 🚀 |
Please rebase and squash this awesomeness into a single commit, then we can merge 🚀 |
eeb5adc
to
b5c1a8f
Compare
Ready to roll! 🚀 🥳 |
src/components/NavigationMailbox.vue
Outdated
if (this.mailbox.databaseId !== mailboxId) { | ||
return | ||
} | ||
this.$store.dispatch('syncEnvelopes', { mailboxId }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we don't need/want that, do we? The destination mailbox is always a different one than the current mailbox. And we don't care about keeping another mailbox updated (on the front-end).
@GretaD found out that this action can run into
mail/lib/Controller/MailboxesController.php
Line 171 in f37124c
return new JSONResponse([], Http::STATUS_PRECONDITION_REQUIRED); |
Hence I'd say we remove the explicit sync of the destination mailbox for now. When the user navigates there, the background sync will update the mailbox anyway. #3951 is a bit of a missing piece here, but again, off topic 😉
@brueckner does that make sense or am I missing something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmm, I see the point. However, before I implemented triggering the sync it was pretty irritating to me to move a mail to its new destination and not seeing it when going to that mailbox right after the move. Sometimes it would take quite some time until the background sync hit. Until then – from the perspective of an unaware user – the mail is just... gone. I don't know about you, but I would be pretty angry if a mail app (seemingly) made my mails disappear 👀 . Also: wouldn't the background-sync essentially do the same expensive operation as well, just... delayed? ;)
(Disclaimer: I didn't look at the background sync yet) Couldn't we reset the timer of a background sync based on a move-operation's triggered sync or something along those lines?
OR... yes! Hear me out... What if we dropped the sync-trigger and marked every mailbox that was the target of a recent move-operation instead (something like outdated
). If a user clicks on a mailbox that is marked outdated
, a sync is triggered immediately. That way, the user will be greeted by a loading spinner and glad to find that all their mails have been moved successfully. On the other hand, all outdated
mailboxes the user does not navigate to will not trigger a sync (until the regular background sync hits).
That way I can move a thousand mails to 20 different mailboxes while only ever triggering one sync once I navigate to one of the 20 outdated
mailboxes. In addition to that, the background sync will remove the outdated flag, thus preventing an unnecessary sync.
👯
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you describing #3951? 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ha! Hell yea! :)
So in terms of what's described in #3951 we could just disable the sync on any move-target-mailbox in the store without persisting that setting?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, wait. It would just have to be disabled for like 30 seconds, right? If the user has bg sync enabled, moves a mail into the folder and doesn't open the mailbox right away afterwards, the background sync should still to its' thing on regular schedule.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Btw we have the same problems/questions for drafts (they should show up if you switch mailbox) and also sent messages.
I'm all in for improving that. But can we keep those two things separate?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea sure – so should I just remove the sync-trigger? We'd just have to make sure to have a solution for it when this gets released, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We'd just have to make sure to have a solution for it when this gets released, right?
Absolutely. We can discuss this the next few days (maybe in a call) and then have a solid fix for the next release :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sound good to me! I'll remove the trigger :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed, rebased on master and squashed. ✅
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It works 👍 see comment
Signed-off-by: Johannes Brückner <johannes@dotplex.com>
b5c1a8f
to
6682c8e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Works really well 🚀 🙏 👍
Adds drag & drop support for moving emails to folders
First off, I know there has been a previous attempt (#2834) to implement this and it has been closed in favor of #3591. This PR doesn't aim to replace the newly implemented modal window to move mails, but rather to complement it. It in fact uses methods introduced in #3591 to actually move the mails.
This approach also makes use of the multi-selection feature, to enable dragging of single and multiple emails.
I have recorded a quick video to showcase the feature (too bad I cant attach it right here, Nextcloud to the rescue!):
https://cloud.dotplex.com/s/RHNGPL8xCzspCkr
Sneak peek:
Features
Code
This is dependency-free. I am not using any external libraries for this. I dug into the HTML5 drag and drop API. I've studied #2834 and the library that was used there and figured out that in order for this to work, event listeners would have to be attached in a more dynamic manner, since mails are loaded asynchronously. I was trying to make it work with vue-draggable, as well as some other libraries, but each of them would spawn a different set of challanges. So in the end I decided to do it "by hand", which has the huge benefit of being able to tailor the feature exactly to our needs.
It's a draft!
In order to get this to work, I had to do some low-level attaching of eventListeners and dom manipulations (to create the draggable-ghost). It's basically exactly what vue-draggable does, just with less overhead. I would love to wrap that up in two custom directives (one for the draggables, one for the drop-targets), but I've never done that before and ran into some issues. So, before pouring more time into this, I wanted to make sure that this feature would be accepted in the first place.
How does this work?
There are
dragstart
anddragend
eventListeners attached to an envelope inEnvelope.vue
. On the other hand, there aredragenter
,dragover
,dragleave
anddrop
eventListeners on each folder/mailbox inNavigationMailbox.vue
that matches the criteria for a valid drop-target.On
dragstart
, each envelope being dragged is added to the global $store and a drag-ghost is created. The HTML5 API to customize that is kinda wierd... you have to create an element to your liking and attach it to the DOM. The browser will then take a "screenshot" of that element, at which point you can remove the element from the DOM again. So in practice: you create the element, attach it to the dom, set it as a DragImage and remove it right away. 🤷♂️Once you drop the mail(s) on a valid drop target (mailbox), the mails to be moved are pulled from the $store and processed. It would be possible to make them disappear right away, but I went with a slight fade-out until they are actually moved. In theory, it would be possible to use the HTML5 DataTransfer object instead of the $store, but that API is pretty wierd as well. Good luck passing objects... :/
Improvements
App.vue
, as it needs to be global. Of course, that's not the right place. How/where ismail.scss
compiled? I'd like to add it there.Trojan Horse 🐴
This commit has nothing to do with the feature, but it neccessary for it to work. I need to pass an event from an envelope to a mailbox/folder. Apparently there is an event bus, but it is created in
MailboxThread.vue
and only passed down as a prop to its' subcomponents. Instead, I am proposing this pattern to make it available whereever neccessary. I'd submit this as a separate PR once out of draft-phase.Conclusion
I strongly believe that this is a must have for an email client. Every user will try to drag an email. We are just used to it from any other client out there. However, I am not married to the specifics of this implementation and am glad for any help with this!