From ef6cf29ce118c1086085644458b855bbe292b545 Mon Sep 17 00:00:00 2001 From: Hadi Nategh Date: Fri, 20 May 2022 16:02:20 +0200 Subject: [PATCH] WIP DND: - fix drag-hover not working smoothly - fix drop zone events being triggered by other draggable items (none et2action draggable) --- api/js/egw_action/egw_action.js | 2 +- api/js/egw_action/egw_action_dragdrop.js | 152 +++++++++++++++++----- api/js/etemplate/et2_dataview_view_aoi.ts | 4 +- api/js/framework/fw_ui.js | 5 +- 4 files changed, 124 insertions(+), 39 deletions(-) diff --git a/api/js/egw_action/egw_action.js b/api/js/egw_action/egw_action.js index 531e1ae2e3..4a5a550d29 100644 --- a/api/js/egw_action/egw_action.js +++ b/api/js/egw_action/egw_action.js @@ -617,7 +617,7 @@ egwAction.prototype.execute = function(_senders, _target) if(!this._check_confirm_mass_selections(_senders, _target)) { - this._check_confirm(_senders, _target); + return this._check_confirm(_senders, _target); } }; diff --git a/api/js/egw_action/egw_action_dragdrop.js b/api/js/egw_action/egw_action_dragdrop.js index dc2f0dd194..ffbbda9476 100644 --- a/api/js/egw_action/egw_action_dragdrop.js +++ b/api/js/egw_action/egw_action_dragdrop.js @@ -214,8 +214,9 @@ export function egwDragActionImplementation() } node.setAttribute('draggable', true); - node.addEventListener('dragstart', function(event) { - if(action) { + const dragstart = function(event) { + event.stopImmediatePropagation(); + if (action) { if (_context.isSelection(event)) return; // Get all selected @@ -245,11 +246,15 @@ export function egwDragActionImplementation() } } } + event.dataTransfer.effectAllowed = 'copy'; + if (event.dataTransfer.types.length == 0) { // No file data? Abort: drag does nothing event.preventDefault(); return; } + } else { + event.dataTransfer.effectAllowed = 'linkMove'; } // The helper function is called before the start function // is evoked. Call the given callback function. The callback @@ -267,19 +272,30 @@ export function egwDragActionImplementation() }) }; - if (!ai.helper) - { + if (!ai.helper) { ai.helper = ai.defaultDDHelper(ai.selected); } // Add a basic class to the helper in order to standardize the background layout ai.helper[0].classList.add('et2_egw_action_ddHelper', 'ui-draggable-dragging'); document.body.append(ai.helper[0]); this.classList.add('drag--moving'); + event.dataTransfer.setData('application/json', JSON.stringify(data)) - event.dataTransfer.effectAllowed = 'move'; event.dataTransfer.setDragImage(ai.helper[0], 12, 12); - } , false); + + this.setAttribute('data-egwActionObjID', JSON.stringify(data.selected)); + }; + + const dragend = function(event){ + const helper = document.querySelector('.et2_egw_action_ddHelper'); + if (helper) helper.remove(); + }; + + // Drag Event listeners + node.addEventListener('dragstart', dragstart , false); + node.addEventListener('dragend', dragend, false); + return true; } @@ -425,53 +441,73 @@ export function egwDropActionImplementation() { var ai = new egwActionImplementation(); + //keeps track of current drop element where dragged item's entered. + // it's necessary for dragenter/dragleave issue correction. + var currentDropEl = null; + ai.type = "drop"; ai.doRegisterAction = function(_aoi, _callback, _context) { var node = _aoi.getDOMNode()[0] ? _aoi.getDOMNode()[0] : _aoi.getDOMNode(); var self = this; - if (node) { node.classList.add('et2dropzone'); - node.addEventListener('dragover', function (event) { + const dragover = function (event) { if (event.preventDefault) { event.preventDefault(); } - event.dataTransfer.dropEffect = 'move'; - _aoi.triggerEvent(EGW_AI_DRAG_OVER, {event: event});//TODO: check this event - return false; - }, false); + if (!self.getTheDraggedDOM()) return ; - node.addEventListener('dragenter', function (event) { - // stop the event from being fired for its children - event.stopPropagation(); + const data = { + event: event, + ui: self.getTheDraggedData() + }; + _aoi.triggerEvent(EGW_AI_DRAG_OVER, data); + return true; + + }; + + const dragenter = function (event) { + event.stopImmediatePropagation(); // don't trigger dragenter if we are entering the drag element - if (event.dataTransfer.getData('application/json') != '' || self.isTheDraggedDOM(this)) return; + // don't go further if the dragged element is no there (happens when a none et2 dragged element is being dragged) + if (!self.getTheDraggedDOM() || self.isTheDraggedDOM(this) || this == currentDropEl) return; + + currentDropEl = event.currentTarget; + event.dataTransfer.dropEffect = 'link'; this.classList.add('drop-hover'); - }, false); - node.addEventListener('drop', function (event) { + // stop the event from being fired for its children event.preventDefault(); + return false; + }; + + const drop = function (event) { + event.preventDefault(); + // don't go further if the dragged element is no there (happens when a none et2 dragged element is being dragged) + if (!self.getTheDraggedDOM()) return ; // remove the hover class this.classList.remove('drop-hover'); - // clean up the helper dom - const helper = document.querySelector('.et2_egw_action_ddHelper'); - const ui = { - position: {top: event.clientY, left: event.clientX}, - offset: {top: event.offsetY, left: event.offsetX} - } + const helper = self.getHelperDOM(); + let ui = self.getTheDraggedData(); + ui.position = {top: event.clientY, left: event.clientX}; + ui.offset = {top: event.offsetY, left: event.offsetX}; - if (helper) helper.remove(); let data = JSON.parse(event.dataTransfer.getData('application/json')); - if (!self.isAccepted(data, _context, _callback) || self.isTheDraggedDOM(this)) return; + if (!self.isAccepted(data, _context, _callback) || self.isTheDraggedDOM(this)) + { + // clean up the helper dom + if (helper) helper.remove(); + return; + } let selected = data.selected.map((item) => { return egw_getObjectManager(item.id, false) @@ -545,20 +581,41 @@ export function egwDropActionImplementation() // Set cursor back to auto. Seems FF can't handle cursor reversion jQuery('body').css({cursor: 'auto'}); - _aoi.triggerEvent(EGW_AI_DRAG_OUT, {event: event});//TODO: check this event - }, false); + _aoi.triggerEvent(EGW_AI_DRAG_OUT, {event: event, ui: self.getTheDraggedData()}); - node.addEventListener('dragleave', function (event) { - // stop the event from being fired for its children - event.stopPropagation(); + // clean up the helper dom + if (helper) helper.remove(); + + }; + + const dragleave = function (event) { + event.stopImmediatePropagation(); // don't trigger dragleave if we are leaving the drag element - if (event.dataTransfer.getData('application/json') != '') return; + // don't go further if the dragged element is no there (happens when a none et2 dragged element is being dragged) + if (!self.getTheDraggedDOM() || self.isTheDraggedDOM(this) || this == currentDropEl) return; - _aoi.triggerEvent(EGW_AI_DRAG_OUT, {event: event});//TODO: check this event + const data = { + event: event, + ui: self.getTheDraggedData() + }; + + _aoi.triggerEvent(EGW_AI_DRAG_OUT, data); this.classList.remove('drop-hover'); - }, false); + + event.preventDefault(); + return false; + }; + + // DND Event listeners + node.addEventListener('dragover', dragover, false); + + node.addEventListener('dragenter', dragenter, false); + + node.addEventListener('drop', drop, false); + + node.addEventListener('dragleave', dragleave, false); return true; } @@ -570,6 +627,33 @@ export function egwDropActionImplementation() return _dom.classList.contains('drag--moving'); } + ai.getTheDraggedDOM = function () + { + return document.querySelector('.drag--moving'); + } + + ai.getHelperDOM = function () + { + return document.querySelector('.et2_egw_action_ddHelper'); + } + + ai.getTheDraggedData = function() + { + let data = this.getTheDraggedDOM().dataset.egwactionobjid; + let selected = []; + if (data) + { + data = JSON.parse(data); + selected = data.map((item)=>{return egw_getObjectManager(item.id, false)}); + } + return { + draggable: this.getTheDraggedDOM(), + helper: this.getHelperDOM(), + selected: selected + + } + } + // check if given draggable is accepted for drop ai.isAccepted = function(_data, _context, _callback, _node) { diff --git a/api/js/etemplate/et2_dataview_view_aoi.ts b/api/js/etemplate/et2_dataview_view_aoi.ts index f6e0fde487..877800f588 100644 --- a/api/js/etemplate/et2_dataview_view_aoi.ts +++ b/api/js/etemplate/et2_dataview_view_aoi.ts @@ -114,7 +114,7 @@ export function et2_dataview_rowAOI(_node) }; if (egwIsMobile()) { - jQuery(_node).swipe({ + /*jQuery(_node).swipe({ allowPageScroll: "vertical", longTapThreshold: 10, swipe: function (event, direction, distance) @@ -131,7 +131,7 @@ export function et2_dataview_rowAOI(_node) return; } - }); + });*/ } else { jQuery(_node).click(selectHandler); } diff --git a/api/js/framework/fw_ui.js b/api/js/framework/fw_ui.js index 3665a3211d..da5bbad5db 100644 --- a/api/js/framework/fw_ui.js +++ b/api/js/framework/fw_ui.js @@ -19,7 +19,7 @@ import "../jquery/jquery.noconflict.js"; import "../jquery/mousewheel/mousewheel.js"; import '../jsapi/egw_inheritance.js'; import {EGW_KEY_ENTER, EGW_KEY_SPACE} from '../egw_action/egw_action_constants.js'; - +import interact from "@interactjs/interactjs"; /** * ui siemenu entry class * Basic sidebar menu implementation @@ -359,7 +359,8 @@ window.egw_fw_ui_tab = function(_parent, _contHeaderDiv, _contDiv, _icon, _callb // If dragging something over the tab, activate that app var tab = this.headerDiv; this.headerDiv.addEventListener('dragenter', (event) => { - tab._callbackObject.call(tab); + event.stopPropagation(); + tab._callbackObject.call(tab); });