codekingpro/portable-devtools
114k
1import React, { useEffect, useMemo, useRef, useState } from 'react';2import {Tree} from './tree';3import * as pgadminUtils from '../utils';4 5import { Directory } from 'react-aspen';6import { ManageTreeNodes } from './tree_nodes';7import { FileTreeX, TreeModelX } from '../components/PgTree';8import ContextMenu from '../components/ContextMenu';9import { generateNodeUrl } from '../../../browser/static/js/node_ajax';10import { copyToClipboard } from '../clipboard';11import { usePgAdmin } from '../BrowserComponent';12 13function postTreeReady(b) {14 const draggableTypes = [15 'collation domain domain_constraints fts_configuration fts_dictionary fts_parser fts_template synonym table partition type sequence package view mview foreign_table edbvar',16 'schema column database cast event_trigger extension language foreign_data_wrapper foreign_server user_mapping compound_trigger index index_constraint primary_key unique_constraint check_constraint exclusion_constraint foreign_key rule',17 'trigger trigger_function',18 'edbfunc function edbproc procedure',19 'coll-column'20 ];21 const getQualifiedName = (data, item)=>{22 if(draggableTypes[0].includes(data._type)) {23 return pgadminUtils.fully_qualify(b, data, item);24 } else if(draggableTypes[1].includes(data._type)) {25 return pgadminUtils.quote_ident(data._label);26 } else if(draggableTypes[3].includes(data._type)) {27 let newData = {...data};28 let parsedFunc = pgadminUtils.parseFuncParams(newData._label);29 newData._label = parsedFunc.func_name;30 return pgadminUtils.fully_qualify(b, newData, item);31 } else {32 return data._label;33 }34 };35 36 b.tree.registerDraggableType({37 [draggableTypes[0]] : (data, item, treeNodeInfo)=>{38 let text = getQualifiedName(data, item);39 return {40 text: text,41 objUrl: generateNodeUrl.call(b.Nodes[data._type], treeNodeInfo, 'properties', data, true),42 nodeType: data._type,43 cur: {44 from: text.length,45 to: text.length,46 },47 };48 },49 [draggableTypes[1]] : (data)=>{50 return getQualifiedName(data);51 },52 [draggableTypes[2]] : (data)=>{53 return getQualifiedName(data);54 },55 [draggableTypes[3]] : (data, item)=>{56 let parsedFunc = pgadminUtils.parseFuncParams(data._label),57 dropVal = getQualifiedName(data, item),58 curPos = {from: 0, to: 0};59 60 if(parsedFunc.params.length > 0) {61 dropVal = dropVal + '(';62 curPos.from = dropVal.length;63 dropVal = dropVal + parsedFunc.params[0][0];64 curPos.to = dropVal.length;65 66 for(let i=1; i<parsedFunc.params.length; i++) {67 dropVal = dropVal + ', ' + parsedFunc.params[i][0];68 }69 70 dropVal = dropVal + ')';71 } else {72 dropVal = dropVal + '()';73 curPos.from = curPos.to = dropVal.length + 1;74 }75 76 return {77 text: dropVal,78 cur: curPos,79 };80 },81 [draggableTypes[4]] : (_data, item)=>{82 return item?.children?.map((ci)=>{83 return getQualifiedName(ci._metadata.data);84 }).join(', ') ?? '';85 }86 });87 88 b.tree.onNodeCopy((data, item)=>{89 copyToClipboard(getQualifiedName(data, item));90 });91}92 93export default function ObjectExplorer() {94 const [contextPos, setContextPos] = React.useState(null);95 const pgAdmin = usePgAdmin();96 const contextMenuItems = pgAdmin.Browser.BrowserContextMenu;97 const [treeModelLoaded, setTreeModelLoaded] = useState(false);98 const treeModelXRef = useRef();99 const MOUNT_POINT = '/browser';100 const mtree = useMemo(()=>new ManageTreeNodes(), []);101 102 useEffect(()=>{103 // Init Tree with the Tree Parent node '/browser'104 mtree.init(MOUNT_POINT);105 106 const host = {107 pathStyle: 'unix',108 getItems: async (path) => {109 return mtree.readNode(path);110 },111 sortComparator: (a, b) => {112 // No nee to sort columns113 if (a._metadata && a._metadata.data._type == 'column') return 0;114 // Sort alphabetically115 if (a.constructor === b.constructor) {116 return pgAdmin.natural_sort(a.fileName, b.fileName);117 }118 let retval = 0;119 if (a.constructor === Directory) {120 retval = -1;121 } else if (b.constructor === Directory) {122 retval = 1;123 }124 return retval;125 },126 };127 128 treeModelXRef.current = new TreeModelX(host, MOUNT_POINT);129 130 treeModelXRef.current.root.ensureLoaded().then(()=>{131 setTreeModelLoaded(true);132 });133 }, []);134 135 const itemHandle = function onReady(handler) {136 pgAdmin.Browser.tree = new Tree(handler, mtree, pgAdmin.Browser);137 postTreeReady(pgAdmin.Browser);138 };139 // Create Node140 const create = async (parentPath, _data) => {141 try {142 const _node_path = parentPath + '/' + _data.id;143 return mtree.addNode(parentPath, _node_path, _data);144 } catch (error) {145 return null; // or throw error as you see fit146 }147 };148 149 // Remove Node150 const remove = async (path, _removeOnlyChild) => {151 try {152 await mtree.removeNode(path, _removeOnlyChild);153 return true;154 } catch (error) {155 return false; // or throw error as you see fit156 }157 };158 159 // Update Node160 const update = async (path, data) => {161 try {162 await mtree.updateNode(path, data);163 return true;164 } catch (error) {165 return false; // or throw error as you see fit166 }167 };168 169 const onContextMenu = React.useCallback(async (ev, item)=>{170 ev.preventDefault();171 if(item) {172 await pgAdmin.Browser.tree.select(item);173 setContextPos({x: ev.clientX, y: ev.clientY});174 }175 }, []);176 177 if(!treeModelLoaded) {178 return <span>Loading...</span>;179 }180 181 return (182 <>183 <FileTreeX184 model={treeModelXRef.current}185 onReady={itemHandle}186 create={create} update={update} remove={remove}187 height={'100%'} disableCache={true} onContextMenu={onContextMenu}188 onScroll={()=>{189 contextPos && setContextPos(null);190 }}191 />192 <ContextMenu position={contextPos} onClose={()=>setContextPos(null)}193 menuItems={contextMenuItems} label="Object Context Menu" />194 </>195 );196}197 