mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-11-30 20:04:30 +01:00
5e3c67a5cf
classes are now uppercase and in their own files. lowercase classes are deprecated. Interfaces are now actual interfaces that should be implemented instead of creating and returning an ai Object every time
302 lines
11 KiB
TypeScript
302 lines
11 KiB
TypeScript
/**
|
|
* EGroupware egw_action framework - egw action framework
|
|
*
|
|
* @link https://www.egroupware.org
|
|
* @author Andreas Stöckel <as@stylite.de>
|
|
* @copyright 2011 by Andreas Stöckel
|
|
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
|
* @package egw_action
|
|
*/
|
|
|
|
import {EgwActionImplementation} from "./EgwActionImplementation";
|
|
import {EGW_AI_DRAG_ENTER, EGW_AI_DRAG_OUT, EGW_AI_DRAG_OVER, EGW_AO_EXEC_THIS} from "./egw_action_constants";
|
|
import {egw_getObjectManager} from "./egw_action";
|
|
import {getPopupImplementation} from "./EgwPopupActionImplementation";
|
|
|
|
export class EgwDropActionImplementation implements EgwActionImplementation {
|
|
type: string = "drop";
|
|
//keeps track of current drop element where dragged item's entered.
|
|
// it's necessary for dragenter/dragleave issue correction.
|
|
private currentDropEl = null
|
|
|
|
|
|
registerAction: (_actionObjectInterface: any, _triggerCallback: Function, _context: object) => boolean = (_aoi, _callback, _context)=> {
|
|
const node = _aoi.getDOMNode() && _aoi.getDOMNode()[0] ? _aoi.getDOMNode()[0] : _aoi.getDOMNode();
|
|
const self:EgwDropActionImplementation = this;
|
|
if (node) {
|
|
node.classList.add('et2dropzone');
|
|
const dragover = (event)=> {
|
|
if (event.preventDefault) {
|
|
event.preventDefault();
|
|
}
|
|
if (!this.getTheDraggedDOM()) return;
|
|
|
|
const data = {
|
|
event: event,
|
|
ui: this.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
|
|
// 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 == self.currentDropEl) return;
|
|
|
|
self.currentDropEl = event.currentTarget;
|
|
event.dataTransfer.dropEffect = 'link';
|
|
|
|
const data = {
|
|
event: event,
|
|
ui: self.getTheDraggedData()
|
|
};
|
|
|
|
_aoi.triggerEvent(EGW_AI_DRAG_ENTER, data);
|
|
|
|
// cleanup drop hover class from all other DOMs if there's still anything left
|
|
Array.from(document.getElementsByClassName('et2dropzone drop-hover')).forEach(_i => {
|
|
_i.classList.remove('drop-hover')
|
|
})
|
|
|
|
this.classList.add('drop-hover');
|
|
|
|
// 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');
|
|
|
|
const helper = self.getHelperDOM();
|
|
let ui = self.getTheDraggedData();
|
|
ui.position = {top: event.clientY, left: event.clientX};
|
|
ui.offset = {top: event.offsetY, left: event.offsetX};
|
|
|
|
|
|
let data = JSON.parse(event.dataTransfer.getData('application/json'));
|
|
|
|
if (!self.isAccepted(data, _context, _callback,undefined) || 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)
|
|
});
|
|
|
|
//links is an Object of DropActions bound to their names
|
|
const links = _callback.call(_context, "links", self, EGW_AO_EXEC_THIS);
|
|
|
|
// Disable all links which only accept types which are not
|
|
// inside ddTypes
|
|
for (const k in links) {
|
|
const accepted = links[k].actionObj.acceptedTypes;
|
|
|
|
let enabled = false;
|
|
for (let i = 0; i < data.ddTypes.length; i++) {
|
|
if (accepted.indexOf(data.ddTypes[i]) != -1) {
|
|
enabled = true;
|
|
break;
|
|
}
|
|
}
|
|
// Check for allowing multiple selected
|
|
if (!links[k].actionObj.allowOnMultiple && selected.length > 1) {
|
|
enabled = false;
|
|
}
|
|
if (!enabled) {
|
|
links[k].enabled = false;
|
|
links[k].visible = !links[k].actionObj.hideOnDisabled;
|
|
}
|
|
}
|
|
|
|
// Check whether there is only one link
|
|
let cnt = 0;
|
|
let lnk = null;
|
|
for (const k in links) {
|
|
if (links[k].enabled && links[k].visible) {
|
|
lnk = links[k];
|
|
cnt += 1 + links[k].actionObj.children.length;
|
|
|
|
// Add ui, so you know what happened where
|
|
lnk.actionObj.ui = ui;
|
|
|
|
}
|
|
}
|
|
|
|
if (cnt == 1) {
|
|
window.setTimeout(function () {
|
|
lnk.actionObj.execute(selected, _context);
|
|
}, 0);
|
|
}
|
|
|
|
if (cnt > 1) {
|
|
// More than one drop action link is associated
|
|
// to the drop event - show those as a popup menu
|
|
// and let the user decide which one to use.
|
|
// This is possible as the popup and the popup action
|
|
// object and the drop action object share same
|
|
// set of properties.
|
|
const popup = getPopupImplementation();
|
|
const pos = popup._getPageXY(event);
|
|
|
|
// Don't add paste actions, this is a drop
|
|
popup.auto_paste = false;
|
|
|
|
window.setTimeout(function () {
|
|
popup.executeImplementation(pos, selected, links,
|
|
_context);
|
|
// Reset, popup is reused
|
|
popup.auto_paste = true;
|
|
}, 0); // Timeout is needed to have it working in IE
|
|
}
|
|
// 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, ui: self.getTheDraggedData()});
|
|
|
|
// clean up the helper dom
|
|
if (helper) helper.remove();
|
|
self.getTheDraggedDOM().classList.remove('drag--moving');
|
|
};
|
|
|
|
const dragleave = function (event) {
|
|
event.stopImmediatePropagation();
|
|
|
|
// don't trigger dragleave if we are leaving the drag element
|
|
// 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 == self.currentDropEl) return;
|
|
|
|
const data = {
|
|
event: event,
|
|
ui: self.getTheDraggedData()
|
|
};
|
|
|
|
_aoi.triggerEvent(EGW_AI_DRAG_OUT, data);
|
|
|
|
this.classList.remove('drop-hover');
|
|
|
|
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;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
unregisterAction: (_actionObjectInterface: any) => boolean = function (_aoi) {
|
|
const node = _aoi.getDOMNode();
|
|
|
|
if (node) {
|
|
node.classList.remove('et2dropzone');
|
|
}
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* Builds the context menu and shows it at the given position/DOM-Node.
|
|
*
|
|
* @param {string} _context
|
|
* @param {array} _selected
|
|
* @param {object} _links
|
|
*/
|
|
executeImplementation: (_context: any, _selected: any, _links: any) => any = function (_context, _selected, _links) {
|
|
if (_context == "links") {
|
|
return _links;
|
|
}
|
|
};
|
|
|
|
|
|
isTheDraggedDOM = function (_dom) {
|
|
return _dom.classList.contains('drag--moving');
|
|
}
|
|
|
|
getTheDraggedDOM = function () {
|
|
return document.querySelector('.drag--moving');
|
|
}
|
|
|
|
getHelperDOM = function () {
|
|
return document.querySelector('.et2_egw_action_ddHelper');
|
|
}
|
|
|
|
getTheDraggedData = ()=> {
|
|
// @ts-ignore // in our case dataset will be present
|
|
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,
|
|
position: undefined,
|
|
offset: undefined
|
|
|
|
}
|
|
}
|
|
|
|
// check if given draggable is accepted for drop
|
|
isAccepted = (_data, _context, _callback, _node)=> {
|
|
if (_node && !_node.classList.contains('et2dropzone')) return false;
|
|
if (typeof _data.ddTypes != "undefined") {
|
|
const accepted = this._fetchAccepted(
|
|
_callback.call(_context, "links", this, EGW_AO_EXEC_THIS));
|
|
|
|
// Check whether all drag types of the selected objects
|
|
// are accepted
|
|
const ddTypes = _data.ddTypes;
|
|
|
|
for (let i = 0; i < ddTypes.length; i++) {
|
|
if (accepted.indexOf(ddTypes[i]) != -1) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
|
|
|
|
private _fetchAccepted = (_links) =>{
|
|
// Accumulate the accepted types
|
|
const accepted = [];
|
|
for (let k in _links) {
|
|
for (let i = 0; i < _links[k].actionObj.acceptedTypes.length; i++) {
|
|
const type = _links[k].actionObj.acceptedTypes[i];
|
|
|
|
if (accepted.indexOf(type) == -1) {
|
|
accepted.push(type);
|
|
}
|
|
}
|
|
}
|
|
|
|
return accepted;
|
|
};
|
|
|
|
}
|
|
|
|
export class egwDropActionImplementation extends EgwDropActionImplementation {
|
|
} |