Inspector revamped

This commit is contained in:
Shaurya Sharma 2025-05-02 02:42:21 +05:30
parent 10f946130f
commit 58f640b1ed
15 changed files with 313 additions and 73 deletions

View file

@ -0,0 +1,11 @@
import React from 'react';
const ArrayNode = ({ value }) => {
return (
<div className="json-viewer-node-value" style={{ color: '#1F99ED' }}>
{`[${value.length}]`}
</div>
);
};
export default ArrayNode;

View file

@ -0,0 +1,12 @@
import React from 'react';
import OverflowTooltip from '@/_components/OverflowTooltip';
const BooleanNode = ({ value }) => {
return (
<div className="json-viewer-node-value" style={{ color: '#9467BD' }}>
<OverflowTooltip style={{ width: '100%' }}>{value.toString()}</OverflowTooltip>
</div>
);
};
export default BooleanNode;

View file

@ -0,0 +1,12 @@
import React from 'react';
import OverflowTooltip from '@/_components/OverflowTooltip';
const FunctionNode = () => {
return (
<div className="json-viewer-node-value" style={{ color: '#4368E3' }}>
<OverflowTooltip style={{ width: '100%' }}>function</OverflowTooltip>
</div>
);
};
export default FunctionNode;

View file

@ -0,0 +1,12 @@
import React from 'react';
import OverflowTooltip from '@/_components/OverflowTooltip';
const NullNode = ({ value }) => {
return (
<div className="json-viewer-node-value" style={{ color: '#ca3973' }}>
<OverflowTooltip style={{ width: '100%' }}>{value === null ? 'null' : 'undefined'}</OverflowTooltip>
</div>
);
};
export default NullNode;

View file

@ -0,0 +1,12 @@
import React from 'react';
import OverflowTooltip from '@/_components/OverflowTooltip';
const NumberNode = ({ value }) => {
return (
<div className="json-viewer-node-value" style={{ color: '#2CA02C' }}>
<OverflowTooltip style={{ width: '100%' }}>{value}</OverflowTooltip>
</div>
);
};
export default NumberNode;

View file

@ -0,0 +1,11 @@
import React from 'react';
const ObjectNode = ({ value }) => {
return (
<div className="json-viewer-node-value" style={{ color: '#FF7F0E' }}>
{`{${Object.keys(value).length}}`}
</div>
);
};
export default ObjectNode;

View file

@ -0,0 +1,123 @@
import React, { useState } from 'react';
import StringNode from './StringNode';
import FunctionNode from './FunctionNode';
import NumberNode from './NumberNode';
import BooleanNode from './BooleanNode';
import NullNode from './NullNode';
import ArrayNode from './ArrayNode';
import ObjectNode from './ObjectNode';
import SolidIcon from '@/_ui/Icon/SolidIcons';
import { ToolTip } from '@/_components/ToolTip';
import { DefaultCopyIcon } from '../../DefaultCopyIcon';
import { copyToClipboard } from '../../utils';
const Row = ({ label, value, level = 1, absolutePath }) => {
const [isExpanded, setIsExpanded] = useState(false);
const Node = () => {
if (typeof value === 'string') {
return <StringNode value={value} />;
} else if (typeof value === 'undefined' || value === null) {
return <NullNode value={value} />;
} else if (typeof value === 'number') {
return <NumberNode value={value} />;
} else if (typeof value === 'boolean') {
return <BooleanNode value={value} />;
} else if (Array.isArray(value)) {
return <ArrayNode value={value} />;
} else if (typeof value === 'object') {
return <ObjectNode value={value} />;
} else if (typeof value === 'function') {
return <FunctionNode />;
}
};
const isObject = typeof value === 'object' && !Array.isArray(value) && value !== null;
const isArray = Array.isArray(value);
return (
<div>
<div className="json-viewer-row-container" style={{ marginLeft: level > 1 ? '8px' : '0px' }}>
<div className="json-viewer-row" onClick={() => setIsExpanded((prev) => !prev)}>
<div className="json-viewer-expand-icon">
{(isArray || isObject) &&
(isExpanded ? (
<SolidIcon
name="TriangleUpCenter"
size={12}
color="#1F99ED"
style={{ marginRight: '4px' }}
className="json-viewer-expand-icon"
/>
) : (
<SolidIcon
name="rightarrrow"
size={12}
color="#1F99ED"
style={{ marginRight: '4px' }}
className="json-viewer-expand-icon"
/>
))}
</div>
<div className="json-viewer-label-container">
<span>{label}</span>
</div>
<div className="json-viewer-value-container">
<Node />
</div>
<div className="json-viewer-actions-container">
<ToolTip message={'Copy to clipboard'}>
<span
onClick={() => {
copyToClipboard(absolutePath);
}}
className="copy-to-clipboard json-viewer-action-icon"
>
<DefaultCopyIcon height={12} width={12} />
</span>
</ToolTip>
<ToolTip message={'Copy value'}>
<span
onClick={() => {
copyToClipboard(value);
}}
className="json-viewer-action-icon"
>
<SolidIcon width="12" height="12" name="copy" />
</span>
</ToolTip>
</div>
</div>
</div>
{isExpanded && isObject && (
<div
className="json-viewer-children"
style={{ marginLeft: `${20 * level}px`, borderLeft: '1px solid var(--border-weak)' }}
>
{Object.entries(value).map(([key, val]) => (
<Row key={key} label={key} value={val} level={level + 1} absolutePath={`${absolutePath}.${key}`} />
))}
</div>
)}
{isExpanded && isArray && (
<div
className="json-viewer-children"
style={{ marginLeft: `${20 * level}px`, borderLeft: '1px solid var(--border-weak)' }}
>
{value.map((item, index) => {
return (
<Row
key={index}
label={`${index}`}
value={item}
level={level + 1}
absolutePath={`${absolutePath}.${index}`}
/>
);
})}
</div>
)}
</div>
);
};
export default Row;

View file

@ -0,0 +1,12 @@
import React from 'react';
import OverflowTooltip from '@/_components/OverflowTooltip';
const StringNode = ({ value }) => {
return (
<div className="json-viewer-node-value" style={{ color: '#2CA02C' }}>
<OverflowTooltip>{`"${value}"`}</OverflowTooltip>
</div>
);
};
export default StringNode;

View file

@ -0,0 +1,17 @@
import React from 'react';
import Row from './Components/Row';
import './styles.scss';
const CustomJSONViewer = ({ data, absolutePath }) => {
let modifiedData = data;
if (typeof data !== 'object') modifiedData = { '': data };
return (
<div className="custom-json-viewer">
{Object.entries(modifiedData).map(([key, value], index) => {
return <Row key={index} label={key} value={value} absolutePath={`${absolutePath}.${key}`} />;
})}
</div>
);
};
export default CustomJSONViewer;

View file

@ -0,0 +1,74 @@
.custom-json-viewer {
margin-left: 16px;
margin-right: 16px;
font-family: "IBM Plex Sans";
font-size: 12px;
color: var(--text-default, #1B1F24);
.json-viewer-row-container {
&:hover {
background-color: var(--interactive-overlays-fill-hover);
.json-viewer-actions-container {
display: flex;
}
}
}
.json-viewer-row {
width: 100%;
display: flex;
height: 20px;
align-items: center;
overflow: hidden;
.json-viewer-expand-icon {
width: 12px;
height: 12px;
display: flex;
align-items: center;
justify-content: center;
}
.json-viewer-label-container {
margin-left: 8px;
margin-right: 4px;
flex-shrink: 0; /* dont shrink */
white-space: nowrap;
}
.json-viewer-value-container {
flex: 1; /* take available space */
min-width: 0; /* allow shrinkage */
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.json-viewer-actions-container {
display:none;
margin-left: auto;
margin-right: 4px;
height: 12px;
width:40px;
align-items: center;
flex-shrink: 0;
.json-viewer-action-icon {
display: flex;
align-items: center;
justify-content: center;
height: 20px;
width: 20px;
&:hover {
cursor: pointer;
background-color: var(--button-outline-hover);
border-radius: 4px;
}
}
}
}
}

View file

@ -1,9 +1,9 @@
import React from 'react';
export const DefaultCopyIcon = () => (
export const DefaultCopyIcon = ({ height = 12, width = 12 }) => (
<svg
width="13"
height="13"
width={width}
height={height}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"

View file

@ -1,4 +1,4 @@
import React, { useMemo, useState } from 'react';
import React, { useMemo } from 'react';
import TreeView, { flattenTree } from 'react-accessible-treeview';
import useStore from '@/AppBuilder/_stores/store';
import { shallow } from 'zustand/shallow';
@ -11,7 +11,6 @@ import { isEmpty } from 'lodash';
const JSONTreeViewerV2 = ({ data = {}, iconsList = [], darkMode, searchablePaths = new Set() }) => {
const searchValue = useStore((state) => state.inspectorSearchValue, shallow);
// const getSelectedNodes = useStore((state) => state.getSelectedNodes, shallow);
const getResolvedValue = useStore((state) => state.getResolvedValue, shallow);
const setSearchValue = useStore((state) => state.setInspectorSearchValue, shallow);
const [selectedNodePath, setSelectedNodePath] = React.useState(null);
@ -38,6 +37,7 @@ const JSONTreeViewerV2 = ({ data = {}, iconsList = [], darkMode, searchablePaths
return [new Set(result), expandedIdSet];
}, [searchValue, JSON.stringify(searchablePaths)]);
// Do not remove this code, once we have the data in the correct format, we can use this function to filter the data
// const recursiveFn = (obj) => {
// if (!obj || typeof obj !== 'object') return [];
// let isCompletelyExposed = false;
@ -89,10 +89,9 @@ const JSONTreeViewerV2 = ({ data = {}, iconsList = [], darkMode, searchablePaths
return filtered.map((item) => item.id);
}, [flattendedData, expandedIds]);
console.log('selectedData', selectedData);
return (
<>
{!selectedNodePath || isEmpty(selectedData) ? (
{!selectedNodePath || (typeof selectedData == 'object' && isEmpty(selectedData)) ? (
<div>
<div style={{ display: 'flex', justifyContent: 'center', marginBottom: '12px' }}>
<SearchBox

View file

@ -1,18 +1,11 @@
import React, { useEffect, useState } from 'react';
import { JSONTree } from 'react-json-tree';
import useStore from '@/AppBuilder/_stores/store';
import { shallow } from 'zustand/shallow';
import ArrowLeft from '@/_ui/Icon/bulkIcons/Arrowleft';
import CheveronRight from '@/_ui/Icon/bulkIcons/CheveronRight';
import { getTheme } from './utils';
import SolidIcon from '@/_ui/Icon/SolidIcons';
import React from 'react';
import { TreeViewHeader } from './TreeViewHeader';
import useCallbackActions from './useCallbackActions';
import OverflowTooltip from '@/_components/OverflowTooltip';
import CustomJSONViewer from './CustomJSONViewer/CustomJSONViewer';
export const JSONViewer = (props) => {
const { data, path, darkMode, backFn } = props;
const [theme, setTheme] = useState(() => getTheme(darkMode));
const callbackActions = useCallbackActions() || [];
const type = path.startsWith('components') ? 'components' : path.startsWith('queries') ? 'queries' : 'actions';
const nodeSpecificActions = callbackActions.filter((action) => [type].includes(action.for))?.[0]?.actions;
@ -23,10 +16,6 @@ export const JSONViewer = (props) => {
const generalActions = callbackActions.filter((action) => action.for === 'all')?.[0]?.actions || [];
useEffect(() => {
setTheme(() => getTheme(darkMode));
}, [darkMode]);
return (
<div className="json-viewer">
<TreeViewHeader
@ -38,30 +27,7 @@ export const JSONViewer = (props) => {
data={optionsData}
type={type}
/>
<JSONTree
theme={theme}
data={data}
invertTheme={!darkMode}
collectionLimit={100}
hideRoot={true}
labelRenderer={(keyPath) => {
const key = keyPath[0];
if (!key && key != 0) return '';
return key;
}}
valueRenderer={(raw, value) => {
if (typeof value === 'function') {
return (
<span className="json-viewer-node-value" style={{ color: '#4368E3' }}>
function
</span>
);
}
return <span className="json-viewer-node-value">{raw}</span>;
}}
/>
<CustomJSONViewer absolutePath={path} data={data} darkMode={darkMode} />
</div>
);
};

View file

@ -1,7 +1,7 @@
import { toast } from 'react-hot-toast';
import useStore from '@/AppBuilder/_stores/store';
import { shallow } from 'zustand/shallow';
// import { runQuery } from '@/AppBuilder/_utils/queryPanel';
import { copyToClipboard } from './utils';
const useCallbackActions = () => {
const deleteComponents = useStore((state) => state.deleteComponents, shallow);
@ -40,12 +40,6 @@ const useCallbackActions = () => {
setSelectedQuery(id);
};
const copyToClipboard = (data) => {
const stringified = JSON.stringify(data, null, 2).replace(/\\/g, '');
navigator.clipboard.writeText(stringified);
return toast.success('Copied to the clipboard', { position: 'top-center' });
};
const handleAutoScrollToComponent = (data) => {
const componentId = getComponentIdFromName(data.nodeName);
const { isAccessible, computedComponentId, isOnCanvas } = getComponentIdToAutoScroll(componentId);

View file

@ -1,3 +1,5 @@
import { toast } from 'react-hot-toast';
export const formatInspectorDataMisc = (obj, type, searchablePaths = new Set()) => {
if (typeof obj !== 'object' || obj === null) return [];
const data = Object.entries(obj).sort((a, b) => a[0].localeCompare(b[0], undefined, { sensitivity: 'base' }));
@ -125,25 +127,8 @@ export const extractComponentName = (path) => {
}
};
export const getTheme = (darkMode) => {
return {
scheme: 'custom',
author: 'chris kempson (http://chriskempson.com)',
base00: 'transparent',
base01: '#303030',
base02: '#505050',
base03: '#b0b0b0',
base04: '#d0d0d0',
base05: '#1B1F24',
base06: '#f5f5f5',
base07: '#ffffff',
base08: '#fb0120',
base09: '#9467BD',
base0A: '#fda331',
base0B: '#2CA02C',
base0C: '#76c7b7',
base0D: '#e4e0db',
base0E: '#d381c3',
base0F: '#be643c',
};
export const copyToClipboard = (data) => {
const stringified = JSON.stringify(data, null, 2).replace(/\\/g, '');
navigator.clipboard.writeText(stringified);
return toast.success('Copied to the clipboard', { position: 'top-center' });
};