Skip to content
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

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion src/components/Envelope.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
<template>
<router-link class="app-content-list-item" :class="{seen: data.flags.seen, draft, selected: selected}" :to="link">
<router-link
v-draggable-envelope="{
accountId: data.accountId ? data.accountId : mailbox.accountId,
mailboxId: data.mailboxId,
envelopeId: data.databaseId,
draggableLabel: `${data.subject} (${data.from[0].label})`,
selectedEnvelopes,
}"
class="app-content-list-item"
:class="{seen: data.flags.seen, draft, selected: selected}"
:to="link"
:data-envelope-id="data.databaseId">
<div
v-if="mailbox.isUnified"
class="mail-message-account-color"
Expand Down Expand Up @@ -133,6 +144,7 @@ import { matchError } from '../errors/match'
import NoTrashMailboxConfiguredError
from '../errors/NoTrashMailboxConfiguredError'
import logger from '../logger'
import { DraggableEnvelopeDirective } from '../directives/drag-and-drop/draggable-envelope'
brueckner marked this conversation as resolved.
Show resolved Hide resolved

export default {
name: 'Envelope',
Expand All @@ -144,6 +156,9 @@ export default {
Moment,
MoveModal,
},
directives: {
draggableEnvelope: DraggableEnvelopeDirective,
brueckner marked this conversation as resolved.
Show resolved Hide resolved
},
props: {
data: {
type: Object,
Expand All @@ -161,6 +176,11 @@ export default {
type: Boolean,
default: false,
},
selectedEnvelopes: {
type: Array,
required: false,
default: () => [],
},
},
data() {
return {
Expand Down
15 changes: 15 additions & 0 deletions src/components/EnvelopeList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@
:mailbox="mailbox"
:selected="selection.includes(env.databaseId)"
:select-mode="selectMode"
:selected-envelopes="selectedEnvelopes"
@delete="$emit('delete', env.databaseId)"
@update:selected="onEnvelopeSelectToggle(env, index, ...$event)"
@select-multiple="onEnvelopeSelectMultiple(env, index)" />
Expand Down Expand Up @@ -125,6 +126,7 @@ import { matchError } from '../errors/match'
import NoTrashMailboxConfiguredError
from '../errors/NoTrashMailboxConfiguredError'
import { differenceWith } from 'ramda'
import dragEventBus from '../directives/drag-and-drop/util/dragEventBus'

export default {
name: 'EnvelopeList',
Expand Down Expand Up @@ -202,7 +204,20 @@ export default {
})
},
},
mounted() {
dragEventBus.$on('envelopesDropped', this.unselectAll)
},
beforeDestroy() {
dragEventBus.$off('envelopesDropped', this.unselectAll)
},
brueckner marked this conversation as resolved.
Show resolved Hide resolved
methods: {
isEnvelopeSelected(idx) {
if (this.selection.length === 0) {
return false
}

return this.selection.includes(idx)
},
markSelectedSeenOrUnseen() {
const seen = !this.areAllSelectedRead
this.selectedEnvelopes.forEach((envelope) => {
Expand Down
71 changes: 70 additions & 1 deletion src/components/NavigationMailbox.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@
v-if="visible"
:id="genId(mailbox)"
:key="genId(mailbox)"
v-droppable-mailbox="{
mailboxId: mailbox.databaseId,
accountId: mailbox.accountId,
isValidDropTarget,
}"
:allow-collapse="true"
:menu-open.sync="menuOpen"
:force-menu="true"
Expand Down Expand Up @@ -142,6 +147,8 @@ import logger from '../logger'
import { translatePlural as n } from '@nextcloud/l10n'
import { translate as translateMailboxName } from '../i18n/MailboxTranslator'
import { showInfo } from '@nextcloud/dialogs'
import { DroppableMailboxDirective as droppableMailbox } from '../directives/drag-and-drop/droppable-mailbox'
import dragEventBus from '../directives/drag-and-drop/util/dragEventBus'

export default {
name: 'NavigationMailbox',
Expand All @@ -154,6 +161,9 @@ export default {
ActionInput,
MoveMailboxModal,
},
directives: {
droppableMailbox,
},
props: {
account: {
type: Object,
Expand Down Expand Up @@ -189,7 +199,6 @@ export default {
renameInput: false,
mailboxName: this.mailbox.displayName,
showMoveModal: false,

}
},
computed: {
Expand Down Expand Up @@ -262,6 +271,34 @@ export default {
isSubscribed() {
return this.mailbox.attributes && this.mailbox.attributes.includes('\\subscribed')
},
isDroppableSpecialMailbox() {
if (this.filter === 'starred') {
return false
}
return ![
this.account.draftsMailboxId,
this.account.sentMailboxId,
].includes(this.mailbox.databaseId)
},
isActive() {
return this.$route.params.mailboxId === this.mailbox.databaseId
},
isValidDropTarget() {
if (this.isActive) {
return false
}
return this.isDroppableSpecialMailbox || (!this.mailbox.specialRole && !this.account.isUnified)
},
},
mounted() {
dragEventBus.$on('dragStart', this.onDragStart)
dragEventBus.$on('dragEnd', this.onDragEnd)
dragEventBus.$on('envelopesMoved', this.onEnvelopesMoved)
},
beforeDestroy() {
dragEventBus.$off('dragStart', this.onDragStart)
dragEventBus.$off('dragEnd', this.onDragEnd)
dragEventBus.$off('envelopesMoved', this.onEnvelopesMoved)
},
brueckner marked this conversation as resolved.
Show resolved Hide resolved
methods: {
/**
Expand Down Expand Up @@ -444,6 +481,38 @@ export default {
onCloseMoveModal() {
this.showMoveModal = false
},
onDragStart({ accountId }) {
if (accountId !== this.mailbox.accountId) {
return
}
this.$store.commit('expandAccount', accountId)
this.showSubMailboxes = true
},
onDragEnd({ accountId }) {
if (accountId !== this.mailbox.accountId) {
return
}
this.showSubMailboxes = false
},
onEnvelopesMoved({ mailboxId, movedEnvelopes }) {
if (this.mailbox.databaseId !== mailboxId) {
return
}
const openedMessageHasBeenMoved = movedEnvelopes.find((movedEnvelope) => {
return movedEnvelope.envelopeId === this.$route.params.threadId
})
// navigate to the mailbox root
// if the currently displayed message has been moved
if (this.$route.name === 'message' && openedMessageHasBeenMoved) {
this.$router.push({
name: 'mailbox',
params: {
mailboxId: this.$route.params.mailboxId,
filter: this.$route.params?.filter,
},
})
}
},
},
}
</script>
1 change: 0 additions & 1 deletion src/components/SignatureSettings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ export default {
return {
loading: false,
bus: new Vue(),
signature: '',
}
},
created() {
Expand Down
104 changes: 104 additions & 0 deletions src/directives/drag-and-drop/draggable-envelope/draggable-envelope.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import dragEventBus from '../util/dragEventBus'
import defer from 'lodash/defer'

export class DraggableEnvelope {

constructor(el, componentInstance, options) {
this.el = el
this.options = options
this.registerListeners.bind(this)(el)
this.setInitialAttributes()
}

setInitialAttributes() {
this.el.setAttribute('draggable', 'true')
this.el.classList.add('draggable-envelope')
}

update(el, instance) {
this.options = instance.options
}

registerListeners(el) {
el.addEventListener('dragstart', this.onDragStart.bind(this))
el.addEventListener('dragend', this.onDragEnd.bind(this))
}

removeListeners(el) {
el.removeEventListener('dragstart', this.onDragStart)
el.removeEventListener('dragend', this.onDragEnd)
}

onDragStart(event) {
const { accountId, mailboxId, selectedEnvelopes } = this.options

event.dataTransfer.clearData()
event.dataTransfer.effectAllowed = 'move'

const envelopes = []
if (selectedEnvelopes.length) {
// handle dragged selection mode items
selectedEnvelopes.forEach((envelope, index) => {
envelopes.push({
accountId,
mailboxId,
envelopeId: envelope.databaseId,
draggableLabel: `${envelope.subject} (${envelope.from[0].label})`,
})
})
} else {
// handle single dragged item
const { envelopeId, draggableLabel } = this.options
envelopes.push({ accountId, mailboxId, envelopeId, draggableLabel })
}

event.dataTransfer.setData('text/plain', JSON.stringify(envelopes))
this.attachGhost({ event, envelopes })

dragEventBus.$emit('dragStart', {
accountId,
mailboxId,
itemCount: envelopes.length,
})
}

onDragEnd(event) {
dragEventBus.$emit('dragEnd', { accountId: this.options.accountId })
}

attachGhost({ event, envelopes }) {
const baseClass = 'draggable-envelope-ghost'
const dragNode = document.createElement('div')
dragNode.classList.add(baseClass)

const counterNode = document.createElement('span')
counterNode.classList.add(`${baseClass}--counter`)
const textCountNode = document.createTextNode(envelopes.length)
counterNode.appendChild(textCountNode)
dragNode.appendChild(counterNode)

const labelWrapperNode = document.createElement('div')
labelWrapperNode.classList.add(`${baseClass}--label-wrapper`)

envelopes.forEach(envelope => {
const labelNode = document.createElement('div')
labelNode.classList.add(`${baseClass}--label-wrapper--label`)
const textLabelNode = document.createTextNode(envelope.draggableLabel)
labelNode.appendChild(textLabelNode)
labelWrapperNode.appendChild(labelNode)
})

dragNode.appendChild(labelWrapperNode)
document.body.appendChild(dragNode)

event.dataTransfer.setDragImage(dragNode, 0, 15)

// the item can be removed immediately, because the
// browser will take a "screenshot" of the dragImage
// upon initialization to be used while dragging
defer(() => {
document.body.removeChild(dragNode)
})
}

}
24 changes: 24 additions & 0 deletions src/directives/drag-and-drop/draggable-envelope/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { DraggableEnvelope } from './draggable-envelope'

let instances = []

export const DraggableEnvelopeDirective = {
bind(el, binding, vnode) {
const instance = new DraggableEnvelope(el, vnode.context, binding.value)
instances.push(instance)
},
componentUpdated(el, binding) {
const options = binding.value
setTimeout(() => {
instances.forEach(instance => {
instance.options.selectedEnvelopes = options.selectedEnvelopes
instance.update(el, instance)
})
})
},
unbind(el) {
instances = instances.filter((instance) => instance.el !== el)
},
}

export default DraggableEnvelope
Loading