improved html5 d&d

This commit is contained in:
djmaze 2020-09-21 09:29:00 +02:00
parent 4e449b9c66
commit ea2e0c09d8
6 changed files with 38 additions and 56 deletions

View file

@ -1,7 +1,8 @@
const ko = window.ko, const ko = window.ko,
$ = jQuery, $ = jQuery,
validTransfer = data => ('move' === data.dropEffect || 'copy' === data.dropEffect) // effectAllowed rlContentType = 'application/x-rainloop-action',
&& data.getData('text/x-rainloop-json'); validTransfer = data => ['move','copy'].includes(data.dropEffect) // effectAllowed
&& data.getData('application/x-rainloop-messages');
ko.bindingHandlers.editor = { ko.bindingHandlers.editor = {
init: (element, fValueAccessor) => { init: (element, fValueAccessor) => {
@ -44,14 +45,11 @@ ko.bindingHandlers.emailsTags = {
inputDelimiters = [',', ';', '\n']; inputDelimiters = [',', ';', '\n'];
$el.inputosaurus({ $el.inputosaurus({
parseOnBlur: true,
allowDragAndDrop: true,
focusCallback: value => { focusCallback: value => {
if (fValue && fValue.focused) { if (fValue && fValue.focused) {
fValue.focused(!!value); fValue.focused(!!value);
} }
}, },
inputDelimiters: inputDelimiters,
autoCompleteSource: fAllBindings.autoCompleteSource || null, autoCompleteSource: fAllBindings.autoCompleteSource || null,
splitHook: value => { splitHook: value => {
const v = value.trim(); const v = value.trim();
@ -91,7 +89,7 @@ ko.bindingHandlers.emailsTags = {
}; };
// Start dragging selected messages // Start dragging selected messages
ko.bindingHandlers.draggable = { ko.bindingHandlers.dragmessages = {
init: (element, fValueAccessor) => { init: (element, fValueAccessor) => {
if (!rl.settings.app('mobile')) { if (!rl.settings.app('mobile')) {
element.addEventListener("dragstart", e => fValueAccessor()(e)); element.addEventListener("dragstart", e => fValueAccessor()(e));
@ -100,16 +98,27 @@ ko.bindingHandlers.draggable = {
}; };
// Drop selected messages on folder // Drop selected messages on folder
ko.bindingHandlers.droppable = { const dragTimer = {
id: 0,
stop: () => clearTimeout(this.id),
start: fn => this.id = setTimeout(fn, 500)
};
ko.bindingHandlers.dropmessages = {
init: (element, fValueAccessor) => { init: (element, fValueAccessor) => {
if (!rl.settings.app('mobile')) { if (!rl.settings.app('mobile')) {
const FolderList = fValueAccessor(), const folder = fValueAccessor(),
folder = ko.dataFor(element), // folder = ko.dataFor(element),
fnHover = e => { fnHover = e => {
if (validTransfer(e.dataTransfer)) { if (validTransfer(e.dataTransfer)) {
dragTimer.stop();
e.preventDefault(); e.preventDefault();
element.classList.add('droppableHover'); element.classList.add('droppableHover');
FolderList.messagesDropOver(folder); if (folder && folder.collapsed()) {
dragTimer.start(() => {
folder.collapsed(false);
rl.app.setExpandedFolder(folder.fullNameHash, true);
}, 500);
}
} }
}; };
element.addEventListener("dragenter", fnHover); element.addEventListener("dragenter", fnHover);
@ -117,14 +126,16 @@ ko.bindingHandlers.droppable = {
element.addEventListener("dragleave", e => { element.addEventListener("dragleave", e => {
e.preventDefault(); e.preventDefault();
element.classList.remove('droppableHover'); element.classList.remove('droppableHover');
FolderList.messagesDropOut(); dragTimer.stop();
}); });
element.addEventListener("drop", e => { element.addEventListener("drop", e => {
const data = JSON.parse(validTransfer(e.dataTransfer)); const data = JSON.parse(validTransfer(e.dataTransfer));
if (data) { if (data) {
data.copy = data.copy && event.ctrlKey; data.copy = data.copy && event.ctrlKey;
e.preventDefault(); e.preventDefault();
FolderList.messagesDrop(folder, data); if (folder && data && data.folder && Array.isArray(data.uids)) {
rl.app.moveMessagesToFolder(data.folder, data.uids, folder.fullNameRaw, data.copy);
}
} }
}); });
} }
@ -132,13 +143,13 @@ ko.bindingHandlers.droppable = {
}; };
let sortableElement = null, let sortableElement = null,
isOrderData = data => 'move' === data.dropEffect && 'orderable' === data.getData('text/x-rainloop-action'); isSortableData = data => 'move' === data.dropEffect && 'sortable' === data.getData(rlContentType);
ko.bindingHandlers.sortableItem = { ko.bindingHandlers.sortableItem = {
init: (element, fValueAccessor/*, allBindingsAccessor, data, context*/) => { init: (element, fValueAccessor) => {
const options = ko.utils.unwrapObservable(fValueAccessor()) || {}, let options = ko.utils.unwrapObservable(fValueAccessor()) || {},
parent = element.parentNode, parent = element.parentNode,
fnHover = e => { fnHover = e => {
if (isOrderData(e.dataTransfer)) { if (isSortableData(e.dataTransfer)) {
e.preventDefault(); e.preventDefault();
let node = (e.target.closest ? e.target : e.target.parentNode).closest('[draggable]'); let node = (e.target.closest ? e.target : e.target.parentNode).closest('[draggable]');
if (node && node !== sortableElement && parent.contains(node)) { if (node && node !== sortableElement && parent.contains(node)) {
@ -155,29 +166,27 @@ ko.bindingHandlers.sortableItem = {
}; };
element.addEventListener("dragstart", e => { element.addEventListener("dragstart", e => {
sortableElement = element; sortableElement = element;
e.dataTransfer.setData('text/x-rainloop-action', 'orderable'); e.dataTransfer.setData(rlContentType, 'sortable');
e.dataTransfer.setDragImage(element, 0, 0); e.dataTransfer.setDragImage(element, 0, 0);
e.dataTransfer.effectAllowed = 'move'; e.dataTransfer.effectAllowed = 'move';
element.style.opacity = 0.25; element.style.opacity = 0.25;
}); });
element.addEventListener("dragend", e => { element.addEventListener("dragend", e => {
element.style.opacity = null; element.style.opacity = null;
if ('move' != e.dataTransfer.dropEffect) { if (isSortableData(e.dataTransfer)) {
const index = options.list.indexOf(ko.dataFor(element)), let row = parent.rows[options.list.indexOf(ko.dataFor(element))];
row = parent.rows[index];
if (row != sortableElement) { if (row != sortableElement) {
row.before(sortableElement); row.before(sortableElement);
} }
} }
}); });
if (!parent.orderable) { if (!parent.sortable) {
parent.orderable = true; parent.sortable = true;
parent.addEventListener("dragenter", fnHover); parent.addEventListener("dragenter", fnHover);
parent.addEventListener("dragover", fnHover); parent.addEventListener("dragover", fnHover);
// parent.addEventListener("dragleave", e => e.preventDefault());
parent.addEventListener("drop", e => { parent.addEventListener("drop", e => {
if (isOrderData(e.dataTransfer)) { if (isSortableData(e.dataTransfer)) {
e.dataTransfer.dropEffect = 'move'; e.dataTransfer.clearData();
let data = ko.dataFor(sortableElement), let data = ko.dataFor(sortableElement),
from = options.list.indexOf(data), from = options.list.indexOf(data),
to = [...parent.children].indexOf(sortableElement); to = [...parent.children].indexOf(sortableElement);

View file

@ -39,8 +39,6 @@ class FolderListMailBoxUserView extends AbstractViewNext {
this.leftPanelDisabled = leftPanelDisabled; this.leftPanelDisabled = leftPanelDisabled;
this.iDropOverTimer = 0;
this.allowComposer = !!Settings.capa(Capa.Composer); this.allowComposer = !!Settings.capa(Capa.Composer);
this.allowContacts = !!AppStore.contactsIsAllowed(); this.allowContacts = !!AppStore.contactsIsAllowed();
this.allowFolders = !!Settings.capa(Capa.Folders); this.allowFolders = !!Settings.capa(Capa.Folders);
@ -185,20 +183,6 @@ class FolderListMailBoxUserView extends AbstractViewNext {
}); });
} }
messagesDropOver(folder) {
clearTimeout(this.iDropOverTimer);
if (folder && folder.collapsed()) {
this.iDropOverTimer = setTimeout(() => {
folder.collapsed(false);
rl.app.setExpandedFolder(folder.fullNameHash, true);
}, 500);
}
}
messagesDropOut() {
clearTimeout(this.iDropOverTimer);
}
scrollToFocused() { scrollToFocused() {
const scrollable = this.oContentScrollable; const scrollable = this.oContentScrollable;
if (scrollable) { if (scrollable) {
@ -216,17 +200,6 @@ class FolderListMailBoxUserView extends AbstractViewNext {
} }
} }
/**
* @param {FolderModel} toFolder
* @param {Array} data
* @returns {void}
*/
messagesDrop(toFolder, data) {
if (toFolder && data && data.folder && Array.isArray(data.uids)) {
rl.app.moveMessagesToFolder(data.folder, data.uids, toFolder.fullNameRaw, data.copy);
}
}
composeClick() { composeClick() {
if (Settings.capa(Capa.Composer)) { if (Settings.capa(Capa.Composer)) {
showScreenPopup(require('View/Popup/Compose')); showScreenPopup(require('View/Popup/Compose'));

View file

@ -499,7 +499,7 @@ class MessageListMailBoxUserView extends AbstractViewNext {
dragImage.style.right = 'auto'; dragImage.style.right = 'auto';
let action = event.ctrlKey ? 'copy' : 'move'; let action = event.ctrlKey ? 'copy' : 'move';
event.dataTransfer.setData('text/x-rainloop-json', JSON.stringify({ event.dataTransfer.setData('application/x-rainloop-messages', JSON.stringify({
copy: event.ctrlKey, copy: event.ctrlKey,
folder: FolderStore.currentFolderFullNameRaw(), folder: FolderStore.currentFolderFullNameRaw(),
uids: uids uids: uids

View file

@ -1,5 +1,5 @@
<div class="e-item" data-bind="visible: visible, css: { 'i-am-inbox-wrapper': isInbox }"> <div class="e-item" data-bind="visible: visible, css: { 'i-am-inbox-wrapper': isInbox }">
<a class="e-link" data-bind="droppable: $root, <a class="e-link" data-bind="dropmessages: $data,
css: { 'i-am-inbox': isInbox, 'selected': selected() && !isSystemFolder(), 'selectable': selectableForFolderList, 'hidden' : hidden, 'print-count': hasUnreadMessages, 'unread-sub': hasSubScribedUnreadMessagesSubfolders, 'system': isSystemFolder, 'anim-action-class': actionBlink }"> css: { 'i-am-inbox': isInbox, 'selected': selected() && !isSystemFolder(), 'selectable': selectableForFolderList, 'hidden' : hidden, 'print-count': hasUnreadMessages, 'unread-sub': hasSubScribedUnreadMessagesSubfolders, 'system': isSystemFolder, 'anim-action-class': actionBlink }">
<span class="badge pull-right count" data-bind="text: printableUnreadCount"></span> <span class="badge pull-right count" data-bind="text: printableUnreadCount"></span>
<i data-bind="css: collapsedCss()"></i> <i data-bind="css: collapsedCss()"></i>

View file

@ -1,5 +1,5 @@
<div class="e-item b-folder-system-item"> <div class="e-item b-folder-system-item">
<a class="e-link" data-bind="droppable: $root, css: { 'selected': selected, 'selectable': selectable, 'print-count': 0 < printableUnreadCount().length, 'anim-action-class': actionBlink, 'is-inbox': isInbox }"> <a class="e-link" data-bind="dropmessages: $data, css: { 'selected': selected, 'selectable': selectable, 'print-count': 0 < printableUnreadCount().length, 'anim-action-class': actionBlink, 'is-inbox': isInbox }">
<span class="badge pull-right count" data-bind="text: printableUnreadCount"></span> <span class="badge pull-right count" data-bind="text: printableUnreadCount"></span>
&nbsp; &nbsp;
<span class="focused-poiner"></span> <span class="focused-poiner"></span>

View file

@ -198,7 +198,7 @@
<div class="listEmptySearchList" data-bind="visible: !dragOver() && 0 === messageList().length && !messageListCompleteLoadingThrottle() && '' === messageListError() && '' !== messageListSearch()"> <div class="listEmptySearchList" data-bind="visible: !dragOver() && 0 === messageList().length && !messageListCompleteLoadingThrottle() && '' === messageListError() && '' !== messageListSearch()">
<span class="i18n" data-i18n="MESSAGE_LIST/EMPTY_SEARCH_LIST"></span> <span class="i18n" data-i18n="MESSAGE_LIST/EMPTY_SEARCH_LIST"></span>
</div> </div>
<div draggable="true" data-bind="draggable: dragStart"> <div draggable="true" data-bind="dragmessages: dragStart">
<div class="messageListPlace" data-bind="template: { name: messageListItemTemplate(), foreach: messageList }"></div> <div class="messageListPlace" data-bind="template: { name: messageListItemTemplate(), foreach: messageList }"></div>
</div> </div>
<div id="messagesDragImage"><span class="text"></span>&nbsp;<i class="icon-mail icon-white"></i></div> <div id="messagesDragImage"><span class="text"></span>&nbsp;<i class="icon-mail icon-white"></i></div>