Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
tree.js627 linesDownload Raw Back to tree
1//////////////////////////////////////////////////////////////////////////2//3// pgAdmin 4 - PostgreSQL Tools4//5// Copyright (C) 2013 - 2024, The pgAdmin Development Team6// This software is released under the PostgreSQL Licence7//8//////////////////////////////////////////////////////////////////////////9 10import _ from 'lodash';11import pgAdmin from 'sources/pgadmin';12 13import { FileType } from 'react-aspen';14import { TreeNode } from './tree_nodes';15 16function manageTreeEvents(event, eventName, item) {17  let d = item ? item._metadata.data : [];18  let node_metadata = item ? item._metadata : {};19  let node;20  let obj = pgAdmin.Browser;21 22  // Events for preferences tree.23  if (node_metadata.parent?.includes('/preferences') && obj.ptree.tree.type == 'preferences') {24    try {25      obj.Events.trigger(26        'preferences:tree:' + eventName, event, item, d27      );28    } catch (e) {29      console.warn(e.stack || e);30      return false;31    }32  } else if(eventName == 'hovered') {33    /* Raise tree events for the nodes */34    try {35      obj.Events.trigger(36        'pgadmin-browser:tree:' + eventName, item, d, node37      );38    } catch (e) {39      console.warn(e.stack || e);40      return false;41    }42  } else if (d && obj.Nodes[d._type]) {43    // Events for browser tree.44    node = obj.Nodes[d._type];45 46    // If the Browser tree is not initialised yet47    if (obj.tree === null) return;48 49    if (eventName == 'dragstart') {50      obj.tree.handleDraggable(event, item);51    }52    if (eventName == 'added' || eventName == 'beforeopen' || eventName == 'loaded') {53      obj.tree.addNewNode(item.getMetadata('data').id, item.getMetadata('data'), item, item.parent.path);54    }55    if(eventName == 'copied') {56      obj.tree.copyHandler?.(item.getMetadata('data'), item);57    }58    if (_.isObject(node.callbacks) &&59      eventName in node.callbacks &&60      typeof node.callbacks[eventName] == 'function') {61      node.callbacks[eventName].apply(node, [item, d, obj, [], eventName]);62    }63 64    /* Raise tree events for the nodes */65    try {66      obj.Events.trigger(67        'pgadmin-browser:tree:' + eventName, item, d, node68      );69    } catch (e) {70      console.warn(e.stack || e);71      return false;72    }73  }74  return true;75}76 77 78export class Tree {79  constructor(tree, manageTree, pgBrowser, type) {80    this.tree = tree;81    this.tree.type = type || 'browser';82    this.tree.onTreeEvents(manageTreeEvents);83 84    this.rootNode = manageTree.tempTree;85    this.Nodes = pgBrowser ? pgBrowser.Nodes : pgAdmin.Browser.Nodes;86 87    this.draggableTypes = {};88  }89 90  async refresh(item) {91    //  Set _children to null as empty array not reload the children nodes on refresh.92    if(item.children?.length == 0) {93      item._children = null;94    }95    await this.tree.refresh(item);96  }97 98  async add(item, data) {99    await this.tree.create(item.parent, data.itemData);100  }101 102  async before(item, data) {103    return Promise.resolve(await this.tree.create(item.parent, data));104  }105 106  async update(item, data) {107    await this.tree.update(item, data);108  }109 110  async remove(item) {111    await this.tree.remove(item);112  }113 114  async append(item, data) {115    return Promise.resolve(await this.tree.create(item, data));116  }117 118  async destroy() {119    const model = this.tree.getModel();120    this.rootNode.children = [];121    if (model.root) {122      model.root.isExpanded = false;123      return Promise.resolve(await model.root.hardReloadChildren());124    }125  }126 127  next(item) {128    if (item) {129      let parent = this.parent(item);130      if (parent && parent.children.length > 0) {131        let idx = parent.children.indexOf(item);132        if (idx !== -1 && parent.children.length !== idx + 1) {133          return parent.children[idx + 1];134        }135      }136    }137    return null;138  }139 140  prev(item) {141    if (item) {142      let parent = this.parent(item);143      if (parent && parent.children.length > 0) {144        let idx = parent.children.indexOf(item);145        if (idx !== -1 && idx !== 0) {146          return parent.children[idx - 1];147        }148      }149    }150    return null;151  }152 153  async open(item) {154    if (this.isOpen(item)) { return true; }155    await this.tree.toggleDirectory(item);156  }157 158  async ensureLoaded(item) {159    await item.ensureLoaded();160  }161 162  async ensureVisible(item, align='auto') {163    await this.tree.ensureVisible(item, align);164  }165 166  async openPath(item) {167    parent = item.parent;168    await this.tree.openDirectory(parent);169  }170 171  async close(item) {172    await this.tree.closeDir(item);173  }174 175  async toggle(item) {176    await this.tree.toggleDirectory(item);177  }178 179  async select(item, ensureVisible = false, align = 'auto') {180    await this.tree.setActiveFile(item, ensureVisible, align);181  }182 183  async selectNode(item, ensureVisible = false, align = 'auto') {184    this.tree.setActiveFile(item, ensureVisible, align);185  }186 187  async unload(item) {188    await this.tree.unload(item);189  }190 191  async addIcon(item, icon) {192    if (item?.getMetadata('data') !== undefined) {193      item.getMetadata('data').icon = icon.icon;194    }195    await this.tree.addIcon(item, icon);196  }197 198  removeIcon() {199    // TBD200  }201 202  setLeaf() {203    // TBD204  }205  async setLabel(item, label) {206    if (item) {207      await this.tree.setLabel(item, label);208    }209  }210 211  async setInode(item) {212    if (item._children) item._children = null;213    await this.tree.closeDirectory(item);214  }215 216  async setId(item, data) {217    if (item) {218      item.getMetadata('data').id = data.id;219    }220  }221 222  async deselect(item) {223    await this.tree.deSelectActiveFile(item);224  }225 226  wasInit() {227    // TBD228    return true;229  }230 231  wasLoad(item) {232    if (item?.type === FileType.Directory) {233      return item.isExpanded && item.children != null && item.children.length > 0;234    }235    return true;236  }237 238  parent(item) {239    return item.parent;240  }241 242  first(item) {243    const model = this.tree.getModel();244    if ((item === undefined || item === null) && model.root.children !== null) {245      return model.root.children[0];246    }247 248    if (item?.branchSize > 0) {249      return item.children[0];250    }251 252    return null;253  }254 255  children(item) {256    const model = this.tree.getModel();257    if (item) {258      return (item.children !== null ? item.children : []);259    }260    return model.root.children;261  }262 263  itemFrom(domElem) {264    return this.tree.getItemFromDOM(domElem);265  }266 267  DOMFrom(item) {268    return this.tree.getDOMFromItem(item);269  }270 271  addCssClass(item, cssClass) {272    this.tree.addCssClass(item, cssClass);273  }274 275  path(item) {276    if (item) return item.path;277  }278 279  pathId(item) {280    if (item) {281      let pathIds = item.path.split('/');282      pathIds.splice(0, 1);283      return pathIds;284    }285    return [];286  }287 288  itemFromDOM(domElem) {289    return this.tree.getItemFromDOM(domElem[0]);290  }291 292  siblings(item) {293    if (this.hasParent(item)) {294      let _siblings = this.parent(item).children.filter((_item) => _item.path !== item.path);295      if (typeof (_siblings) !== 'object') return [_siblings];296      else return _siblings;297    }298    return [];299  }300 301  hasParent(item) {302    return item?.parent;303  }304 305  isOpen(item) {306    if (item.type === FileType.Directory) {307      return item.isExpanded;308    }309    return false;310  }311 312  isClosed(item) {313    if (item.type === FileType.Directory) {314      return !item.isExpanded;315    }316    return false;317  }318 319  itemData(item) {320    return (item?.getMetadata('data') !== undefined) ? item?._metadata.data : [];321  }322 323  getData(item) {324    return (item?.getMetadata('data') !== undefined) ? item?._metadata.data : [];325  }326 327  isRootNode(item) {328    const model = this.tree.getModel();329    return item === model.root;330  }331 332  isInode(item) {333    const children = this.children(item);334    if (children === null || children === undefined) return false;335    return children.length > 0;336  }337 338  selected() {339    return this.tree.getActiveFile();340  }341 342  resizeTree() {343    this.tree.resize();344  }345 346  findNodeWithToggle(path) {347    let tree = this;348 349    if (path == null || !Array.isArray(path)) {350      return Promise.reject(new Error(null));351    }352    const basepath = '/browser/' + path.slice(0, path.length-1).join('/') + '/';353    path = '/browser/' + path.join('/');354 355    let onCorrectPath = function (matchPath) {356      return (matchPath !== undefined && path !== undefined357        && (basepath.startsWith(`${matchPath}/`) || path === matchPath));358    };359 360    return (function findInNode(currentNode) {361      return new Promise((resolve, reject) => {362        if (path === null || path === undefined || path.length === 0) {363          resolve(null);364        }365        /* No point in checking the children if366         * the path for currentNode itself is not matching367         */368        if (currentNode.path !== undefined && !onCorrectPath(currentNode.path)) {369          reject(new Error(null));370        } else if (currentNode.path === path) {371          resolve(currentNode);372        } else {373          tree.open(currentNode)374            .then(() => {375              let children = currentNode.children;376              for (let i = 0, length = children.length; i < length; i++) {377                let childNode = children[i];378                if (onCorrectPath(childNode.path)) {379                  resolve(findInNode(childNode));380                  return;381                }382              }383              reject(new Error(null));384            })385            .catch(() => {386              reject(new Error(null));387            });388        }389      });390    })(tree.tree.getModel().root);391  }392 393  getNodeDisplayPath(item, separator='/', skip_coll=false) {394    let retStack = [];395    let currItem = item;396    while(currItem?.fileName) {397      const data = currItem._metadata?.data;398      if(data._type.startsWith('coll-') && skip_coll) {399        /* Skip collection */400      } else {401        retStack.push(data._label);402      }403      currItem = currItem.parent;404    }405    retStack = retStack.reverse();406    if(!separator) return retStack;407    return retStack.join(separator);408  }409 410  findNodeByDomElement(domElement) {411    const path = domElement?.path;412    if (!path?.[0]) {413      return undefined;414    }415 416    return this.findNode(path);417  }418 419  addNewNode(id, data, item, parentPath) {420    let parent;421    parent = this.findNode(parentPath);422    return this.createOrUpdateNode(id, data, parent, item);423  }424 425  findNode(path) {426    if (path === null || path === undefined || path.length === 0 || path == '/browser') {427      return this.rootNode;428    }429    return findInTree(this.rootNode, path);430  }431 432  createOrUpdateNode(id, data, parent, domNode) {433    let oldNodePath = id;434    if (parent?.path != '/browser') {435      oldNodePath = parent.path + '/' + id;436    }437    const oldNode = this.findNode(oldNodePath);438    if (oldNode !== null) {439      oldNode.data = data;440      oldNode.domNode = domNode;441      return oldNode;442    }443 444    const node = new TreeNode(id, data, domNode, parent);445    if (parent === this.rootNode) {446      node.parentNode = null;447    }448 449    if (parent !== null && parent !== undefined)450      parent.children.push(node);451    return node;452  }453 454  async updateAndReselectNode(item, data) {455    await this.update(item, data);456    await this.deselect(item);457    await this.select(item);458  }459 460  translateTreeNodeIdFromReactTree(treeNode) {461    let currentTreeNode = treeNode;462    let path = [];463    while (currentTreeNode !== null && currentTreeNode !== undefined) {464      if (currentTreeNode.path !== '/browser') path.unshift(currentTreeNode.path);465      if (this.hasParent(currentTreeNode)) {466        currentTreeNode = this.parent(currentTreeNode);467      } else {468        break;469      }470    }471    return path;472  }473 474  getTreeNodeHierarchy(identifier) {475    let idx = 0;476    let node_cnt = 0;477    let result = {};478    if (identifier === undefined) return;479    let item = TreeNode.prototype.isPrototypeOf(identifier) ? identifier : this.findNode(identifier.path);480    if (item === undefined) return;481    do {482      const currentNodeData = item.getData();483      if (currentNodeData._type in this.Nodes && this.Nodes[currentNodeData._type].hasId) {484        const nodeType = mapType(currentNodeData._type, node_cnt);485        if (result[nodeType] === undefined) {486          result[nodeType] = _.extend({}, currentNodeData, {487            'priority': idx,488          });489          idx -= 1;490        }491      }492      node_cnt += 1;493      item = item.hasParent() ? item.parent() : null;494    } while (item);495 496    return result;497  }498 499  /*500   *501   * The dropDetailsFunc should return an object of sample502   * {text: 'xyz', cur: {from:0, to:0} where text is the drop text and503   * cur is selection range of text after dropping. If returned as504   * string, by default cursor will be set to the end of text505   */506  registerDraggableType(typeOrTypeDict, dropDetailsFunc = null) {507    if (typeof typeOrTypeDict == 'object') {508      Object.keys(typeOrTypeDict).forEach((type) => {509        this.registerDraggableType(type, typeOrTypeDict[type]);510      });511    } else if (dropDetailsFunc != null) {512      typeOrTypeDict.replace(/ +/, ' ').split(' ').forEach((type) => {513        this.draggableTypes[type] = dropDetailsFunc;514      });515    }516  }517 518  getDraggable(type) {519    if (this.draggableTypes[type]) {520      return this.draggableTypes[type];521    } else {522      return null;523    }524  }525 526  handleDraggable(e, item) {527    let data = item.getMetadata('data');528    let dropDetailsFunc = this.getDraggable(data._type);529 530    if (dropDetailsFunc != null) {531      let dropDetails = dropDetailsFunc(data, item, this.getTreeNodeHierarchy(item));532 533      if (typeof dropDetails == 'string') {534        dropDetails = {535          text: dropDetails,536          cur: {537            from: dropDetails.length,538            to: dropDetails.length,539          },540        };541      } else if (!dropDetails.cur) {542        dropDetails = {543          ...dropDetails,544          cur: {545            from: dropDetails.text.length,546            to: dropDetails.text.length,547          },548        };549      }550 551      e.dataTransfer.setData('text', JSON.stringify(dropDetails));552      /* Required by Firefox */553      if (e.dataTransfer.dropEffect) {554        e.dataTransfer.dropEffect = 'move';555      }556 557      /* setDragImage is not supported in IE. We leave it to558      * its default look and feel559      */560      const dropText = _.escape(dropDetails.text);561      if(!dropText) {562        e.preventDefault();563      }564      if (e.dataTransfer.setDragImage) {565        const dragItem = document.createElement('div');566        dragItem.classList.add('drag-tree-node');567        dragItem.innerHTML = `<span>${dropText}</span>`;568 569        document.querySelector('body .drag-tree-node')?.remove();570        document.body.appendChild(dragItem);571 572        e.dataTransfer.setDragImage(dragItem, 0, 0);573      }574    }575    else {576      e.preventDefault();577    }578  }579 580  onNodeCopy(copyCallback) {581    this.copyHandler = copyCallback;582  }583}584 585function mapType(type, idx) {586  return (type === 'partition' && idx > 0) ? 'table' : type;587}588 589 590 591/**592 * Given an initial node and a path, it will navigate through593 * the new tree to find the node that matches the path594 */595export function findInTree(rootNode, path) {596  if (path === null) {597    return rootNode;598  }599  return (function findInNode(currentNode) {600 601    /* No point in checking the children if602     * the path for currentNode itself is not matching603     */604    if (currentNode.path !== undefined && path !== undefined605      && !path.startsWith(currentNode.path)) {606      return null;607    }608 609    for (let i = 0, length = currentNode.children.length; i < length; i++) {610      const calculatedNode = findInNode(currentNode.children[i]);611      if (calculatedNode !== null) {612        return calculatedNode;613      }614    }615 616    if (currentNode.path === path) {617      return currentNode;618    } else {619      return null;620    }621  })(rootNode);622}623 624const isValidTreeNodeData = (data) => (!_.isEmpty(data));625 626export { isValidTreeNodeData };627 
codekingpro/portable-devtools · Team Ai