diff --git a/assets/icons/jsonarrayicon.svg b/assets/icons/jsonarrayicon.svg new file mode 100644 index 0000000..1866bc2 --- /dev/null +++ b/assets/icons/jsonarrayicon.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/assets/icons/jsonboolicon.svg b/assets/icons/jsonboolicon.svg new file mode 100644 index 0000000..5b639b1 --- /dev/null +++ b/assets/icons/jsonboolicon.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/jsonnumbericon.svg b/assets/icons/jsonnumbericon.svg new file mode 100644 index 0000000..c76184e --- /dev/null +++ b/assets/icons/jsonnumbericon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/icons/jsonobjecticon.svg b/assets/icons/jsonobjecticon.svg new file mode 100644 index 0000000..0563426 --- /dev/null +++ b/assets/icons/jsonobjecticon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/icons/jsonstringicon.svg b/assets/icons/jsonstringicon.svg new file mode 100644 index 0000000..eeeccd1 --- /dev/null +++ b/assets/icons/jsonstringicon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/design_files/jsonarrayicon.afdesign b/design_files/jsonarrayicon.afdesign new file mode 100644 index 0000000..6297f80 Binary files /dev/null and b/design_files/jsonarrayicon.afdesign differ diff --git a/design_files/jsonboolicon.afdesign b/design_files/jsonboolicon.afdesign new file mode 100644 index 0000000..6d77c0e Binary files /dev/null and b/design_files/jsonboolicon.afdesign differ diff --git a/design_files/jsonnumbericon.afdesign b/design_files/jsonnumbericon.afdesign new file mode 100644 index 0000000..6da729d Binary files /dev/null and b/design_files/jsonnumbericon.afdesign differ diff --git a/design_files/jsonobjecticon.afdesign b/design_files/jsonobjecticon.afdesign new file mode 100644 index 0000000..f64067e Binary files /dev/null and b/design_files/jsonobjecticon.afdesign differ diff --git a/design_files/jsonstringicon.afdesign b/design_files/jsonstringicon.afdesign new file mode 100644 index 0000000..cc0d809 Binary files /dev/null and b/design_files/jsonstringicon.afdesign differ diff --git a/src/components/Dashboard/common/DataTree.jsx b/src/components/Dashboard/common/DataTree.jsx new file mode 100644 index 0000000..aa47749 --- /dev/null +++ b/src/components/Dashboard/common/DataTree.jsx @@ -0,0 +1,238 @@ +import PropTypes from 'prop-types' +import { Tree, Typography, Space, Tag } from 'antd' +import { useState, useMemo } 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 = (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 = (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 = (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 + } + + // 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]) + + // 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