////////////////////////////////////////////////////////////////////////// // // pgAdmin 4 - PostgreSQL Tools // // Copyright (C) 2013 - 2020, The pgAdmin Development Team // This software is released under the PostgreSQL Licence // ////////////////////////////////////////////////////////////////////////// import {isValidData} from 'sources/utils'; import $ from 'jquery'; import Alertify from 'pgadmin.alertifyjs'; export class TreeNode { constructor(id, data, domNode, parent) { this.id = id; this.data = data; this.setParent(parent); this.children = []; this.domNode = domNode; } hasParent() { return this.parentNode !== null && this.parentNode !== undefined; } parent() { return this.parentNode; } setParent(parent) { this.parentNode = parent; this.path = this.id; if (parent !== null && parent !== undefined && parent.path !== undefined) { this.path = parent.path + '.' + this.id; } } getData() { if (this.data === undefined) { return undefined; } else if (this.data === null) { return null; } return Object.assign({}, this.data); } getHtmlIdentifier() { return this.domNode; } reload(tree) { this.unload(tree); tree.aciTreeApi.setInode(this.domNode); tree.aciTreeApi.deselect(this.domNode); setTimeout(() => { tree.selectNode(this.domNode); }, 0); } unload(tree) { this.children = []; tree.aciTreeApi.unload(this.domNode); } /* * Find the ancestor with matches this condition */ ancestorNode(condition) { let node = this; while (node.hasParent()) { node = node.parent(); if (condition(node)) { return node; } } return null; } /** * Given a condition returns true if the current node * or any of the parent nodes condition result is true */ anyFamilyMember(condition) { if(condition(this)) { return true; } return this.ancestorNode(condition) !== null; } anyParent(condition) { return this.ancestorNode(condition) !== null; } } export class Tree { constructor() { this.rootNode = new TreeNode(undefined, {}); this.aciTreeApi = undefined; this.draggableTypes = {}; } /* * * The dropDetailsFunc should return an object of sample * {text: 'xyz', cur: {from:0, to:0} where text is the drop text and * cur is selection range of text after dropping. If returned as * string, by default cursor will be set to the end of text */ registerDraggableType(typeOrTypeDict, dropDetailsFunc=null) { if(typeof typeOrTypeDict == 'object') { Object.keys(typeOrTypeDict).forEach((type)=>{ this.registerDraggableType(type, typeOrTypeDict[type]); }); } else { if(dropDetailsFunc != null) { typeOrTypeDict.replace(/ +/, ' ').split(' ').forEach((type)=>{ this.draggableTypes[type] = dropDetailsFunc; }); } } } getDraggable(type) { if(this.draggableTypes[type]) { return this.draggableTypes[type]; } else { return null; } } prepareDraggable(data, item) { let dropDetailsFunc = this.getDraggable(data._type); if(dropDetailsFunc != null) { /* addEventListener is used here because import jquery.drag.event * overrides the dragstart event set using element.on('dragstart') * This will avoid conflict. */ item.find('.aciTreeItem') .attr('draggable', true)[0] .addEventListener('dragstart', (e)=> { let dropDetails = dropDetailsFunc(data, item); if(typeof dropDetails == 'string') { dropDetails = { text:dropDetails, cur:{ from:dropDetails.length, to: dropDetails.length, }, }; } else { if(!dropDetails.cur) { dropDetails = { ...dropDetails, cur:{ from:dropDetails.text.length, to: dropDetails.text.length, }, }; } } e.dataTransfer.setData('text', JSON.stringify(dropDetails)); /* Required by Firefox */ if(e.dataTransfer.dropEffect) { e.dataTransfer.dropEffect = 'move'; } /* setDragImage is not supported in IE. We leave it to * its default look and feel */ if(e.dataTransfer.setDragImage) { let dragItem = $(`