import PropTypes from 'prop-types' import { Tree, Typography, Space, Tag } from 'antd' import { useState, useMemo, useCallback } from 'react' import XMarkIcon from '../../Icons/XMarkIcon' import QuestionCircleIcon from '../../Icons/QuestionCircleIcon' import JsonStringIcon from '../../Icons/JsonStringIcon' import JsonArrayIcon from '../../Icons/JsonArrayIcon' import JsonObjectIcon from '../../Icons/JsonObjectIcon' import JsonBoolIcon from '../../Icons/JsonBoolIcon' import JsonNumberIcon from '../../Icons/JsonNumberIcon' import CopyButton from './CopyButton' const { Text } = Typography const DataTree = ({ data, showLine = true, showValueCopy = true, showKeyCopy = false, defaultExpandAll = false, onNodeSelect, style = {} }) => { const [expandedKeys, setExpandedKeys] = useState([]) const [selectedKeys, setSelectedKeys] = useState([]) // Function to get data type and format value const getDataTypeInfo = (value) => { if (value === null) return { type: 'null', color: 'default', icon: } if (value === undefined) return { type: 'undefined', color: 'default', icon: } if (typeof value === 'boolean') return { type: 'boolean', color: 'blue', icon: } if (typeof value === 'number') return { type: 'number', color: 'green', icon: } if (typeof value === 'string') return { type: 'string', color: 'orange', icon: } if (Array.isArray(value)) return { type: 'array', color: 'purple', icon: } if (typeof value === 'object') return { type: 'object', color: 'cyan', icon: } return { type: 'unknown', color: 'default', icon: } } // Function to format value for display const formatValue = useCallback((value) => { if (value === null) return 'null' if (value === undefined) return 'undefined' if (typeof value === 'boolean') return value.toString() if (typeof value === 'number') return value.toString() if (typeof value === 'string') { // Truncate long strings return value.length > 50 ? `${value.substring(0, 50)}...` : value } if (Array.isArray(value)) return `Array (${value.length} items)` if (typeof value === 'object') { const keys = Object.keys(value) return `Object (${keys.length} properties)` } return String(value) }, []) // Function to get raw value for copying const getCopyValue = useCallback((value) => { if (value === null) return 'null' if (value === undefined) return 'undefined' if (typeof value === 'boolean') return value.toString() if (typeof value === 'number') return value.toString() if (typeof value === 'string') return value if (Array.isArray(value)) return JSON.stringify(value, null, 2) if (typeof value === 'object') return JSON.stringify(value, null, 2) return String(value) }, []) // Recursive function to convert JSON to tree data const convertToTreeData = useCallback( (obj, key = 'root', path = '') => { const currentPath = path ? `${path}.${key}` : key const dataInfo = getDataTypeInfo(obj) const node = { title: ( {dataInfo.icon} {key} {showKeyCopy && ( )} ({dataInfo.type}) {dataInfo.type !== 'object' && dataInfo.type !== 'array' && ( <> {formatValue(obj)} {showValueCopy && ( )} )} {(dataInfo.type === 'object' || dataInfo.type === 'array') && showValueCopy && ( )} ), key: currentPath, value: obj, path: currentPath } // Add children for objects and arrays if (typeof obj === 'object' && obj !== null) { if (Array.isArray(obj)) { node.children = obj.map((item, index) => convertToTreeData(item, `[${index}]`, currentPath) ) } else { node.children = Object.entries(obj).map(([childKey, childValue]) => convertToTreeData(childValue, childKey, currentPath) ) } } return node }, [showValueCopy, getCopyValue, formatValue, showKeyCopy] ) // Convert data to tree structure const treeData = useMemo(() => { if (!data) return [] if (typeof data === 'object' && data !== null) { if (Array.isArray(data)) { return [convertToTreeData(data, 'root')] } else { return [convertToTreeData(data, 'root')] } } else { // Handle primitive values const dataInfo = getDataTypeInfo(data) return [ { title: ( {dataInfo.icon} Value ({dataInfo.type}) {formatValue(data)} {showValueCopy && ( )} ), key: 'root', value: data, path: 'root' } ] } }, [data, showValueCopy, convertToTreeData, getCopyValue, formatValue]) // Handle node selection const handleSelect = (selectedKeys, { selected, selectedNodes }) => { setSelectedKeys(selectedKeys) if (onNodeSelect && selected && selectedNodes.length > 0) { const node = selectedNodes[0] onNodeSelect({ key: node.key, value: node.value, path: node.path }) } } // Handle expand/collapse const handleExpand = (keys) => { setExpandedKeys(keys) } // Auto-expand all if requested const getExpandedKeys = () => { if (defaultExpandAll) { return treeData.length > 0 ? getAllKeys(treeData[0]) : [] } return expandedKeys } // Helper function to get all keys for auto-expand const getAllKeys = (node) => { let keys = [node.key] if (node.children) { node.children.forEach((child) => { keys = keys.concat(getAllKeys(child)) }) } return keys } return ( ) } DataTree.propTypes = { data: PropTypes.any.isRequired, showLine: PropTypes.bool, showValueCopy: PropTypes.bool, showKeyCopy: PropTypes.bool, defaultExpandAll: PropTypes.bool, onNodeSelect: PropTypes.func, style: PropTypes.object } export default DataTree