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