Inspector revamp fixes

This commit is contained in:
Shaurya Sharma 2025-05-06 03:35:22 +05:30
parent e6ee4e5bd8
commit f1820e9620
9 changed files with 241 additions and 112 deletions

View file

@ -43,7 +43,7 @@ const Row = ({ label, value, level = 1, absolutePath }) => {
(isExpanded ? (
<SolidIcon
name="TriangleUpCenter"
size={12}
size={14}
color="#1F99ED"
style={{ marginRight: '4px' }}
className="json-viewer-expand-icon"

View file

@ -29,6 +29,7 @@
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.json-viewer-label-container {

View file

@ -11,6 +11,8 @@ import { isEmpty } from 'lodash';
const JSONTreeViewerV2 = ({ data = {}, iconsList = [], darkMode, searchablePaths = new Set() }) => {
const searchValue = useStore((state) => state.inspectorSearchValue, shallow);
const getComponentIdFromName = useStore((state) => state.getComponentIdFromName, shallow);
const getComponentDefinition = useStore((state) => state.getComponentDefinition, shallow);
const getResolvedValue = useStore((state) => state.getResolvedValue, shallow);
const setSearchValue = useStore((state) => state.setInspectorSearchValue, shallow);
const [selectedNodePath, setSelectedNodePath] = React.useState(null);
@ -76,7 +78,27 @@ const JSONTreeViewerV2 = ({ data = {}, iconsList = [], darkMode, searchablePaths
setSelectedNodePath(null);
};
const selectedData = selectedNodePath ? getResolvedValue(`{{${selectedNodePath}}}`) : {};
const selectedData = (() => {
if (selectedNodePath?.startsWith('components.')) {
// Split the selectedNode path using . and grab the second element if it exists
const pathArray = selectedNodePath.split('.');
const componentName = pathArray?.[1];
const componentId = getComponentIdFromName(componentName);
const component = getComponentDefinition(componentId);
const parent = component?.component?.parent;
if (parent) {
const parentComponent = getComponentDefinition(parent);
const parentType = parentComponent?.component?.component;
if (parentType === 'Form') {
return {
id: componentId,
};
}
}
}
return selectedNodePath ? getResolvedValue(`{{${selectedNodePath}}}`) : {};
})();
const expandedIds = [...Array.from(pathSet), ...selectedNodes];
const filteredIds = useMemo(() => {
@ -86,7 +108,20 @@ const JSONTreeViewerV2 = ({ data = {}, iconsList = [], darkMode, searchablePaths
const { path } = metadata || {};
return expandedIdsSet.has(path);
});
return filtered.map((item) => item.id);
return filtered
.map((item) => item.id)
.filter((path) => {
const pathArray = path.split('.');
// One by one combine and check if the path is in expandedIds or not
for (let i = pathArray.length - 1; i > 0; i--) {
const parentPath = pathArray.slice(0, i).join('.');
if (!expandedIdsSet.has(parentPath)) {
return false;
}
}
return true;
});
}, [flattendedData, expandedIds]);
return (
@ -105,35 +140,36 @@ const JSONTreeViewerV2 = ({ data = {}, iconsList = [], darkMode, searchablePaths
width={300}
/>
</div>
<div className="json-tree-view">
<TreeView
data={flattendedData}
className="basic"
aria-label="basic example tree"
defaultExpandedIds={selectedNodes}
expandedIds={filteredIds}
key={key}
nodeRenderer={(props) => {
const { element } = props;
const { metadata } = element || {};
const { path } = metadata || {};
const data = {
nodeName: element.name,
selectedNodePath: path,
};
<TreeView
data={flattendedData}
className="basic"
aria-label="basic example tree"
defaultExpandedIds={selectedNodes}
expandedIds={filteredIds}
key={key}
nodeRenderer={(props) => {
const { element } = props;
const { metadata } = element || {};
const { path } = metadata || {};
const data = {
nodeName: element.name,
selectedNodePath: path,
};
return (
<Node
{...props}
darkMode={darkMode}
setSelectedNodePath={setSelectedNodePath}
searchValue={searchValue}
iconsList={iconsList}
data={data}
/>
);
}}
/>
return (
<Node
{...props}
darkMode={darkMode}
setSelectedNodePath={setSelectedNodePath}
searchValue={searchValue}
iconsList={iconsList}
data={data}
/>
);
}}
/>
</div>
</div>
) : (
<JSONViewer data={selectedData} darkMode={darkMode} path={selectedNodePath} backFn={backFn} />

View file

@ -7,8 +7,8 @@ import JSONTreeViewerV2 from './JSONTreeViewerV2';
import _ from 'lodash';
import { ButtonSolid } from '@/_ui/AppButton/AppButton';
import useIconList from './useIconList';
import { formatInspectorComponentData, formatInspectorDataMisc, formatInspectorQueryData } from './utils';
import { Button as ButtonComponent } from '@/components/ui/Button/Button';
import { formatInspectorDataMisc, formatInspectorQueryData } from './utils';
const LeftSidebarInspector = ({ darkMode, pinned, setPinned }) => {
const exposedComponentsVariables = useStore((state) => state.getAllExposedValues().components, shallow);
@ -18,6 +18,7 @@ const LeftSidebarInspector = ({ darkMode, pinned, setPinned }) => {
const exposedPageVariables = useStore((state) => state.getAllExposedValues().page || {}, shallow);
const exposedGlobalVariables = useStore((state) => state.getAllExposedValues().globals || {}, shallow);
const componentIdNameMapping = useStore((state) => state.getComponentIdNameMapping(), shallow);
const formatInspectorComponentData = useStore((state) => state.formatInspectorComponentData, shallow);
const queryNameIdMapping = useStore((state) => state.getQueryNameIdMapping(), shallow);
const searchablePaths = useRef(new Set(['queries', 'components', 'globals', 'variables', 'page', 'constants']));
@ -109,18 +110,14 @@ const LeftSidebarInspector = ({ darkMode, pinned, setPinned }) => {
<HeaderSection darkMode={darkMode}>
<HeaderSection.PanelHeader title="State inspector">
<div className="d-flex justify-content-end">
<ButtonSolid
title={`${pinned ? 'Unpin' : 'Pin'}`}
<ButtonComponent
iconOnly
leadingIcon={pinned ? 'unpin' : 'pin'}
onClick={() => setPinned(!pinned)}
darkMode={darkMode}
styles={{ width: '28px', padding: 0 }}
data-cy={`left-sidebar-inspector`}
variant="ghostBlack"
className="left-sidebar-header-btn"
leftIcon={pinned ? 'unpin' : 'pin'}
iconWidth="14"
fill={`var(--slate12)`}
></ButtonSolid>
variant="ghost"
fill="var(--icon-strong,#6A727C)"
size="medium"
/>
</div>
</HeaderSection.PanelHeader>
</HeaderSection>

View file

@ -9,6 +9,7 @@ import OverflowTooltip from '@/_components/OverflowTooltip';
import { HiddenOptions } from './HiddenOptions';
import useCallbackActions from './useCallbackActions';
import useStore from '@/AppBuilder/_stores/store';
import { Button as ButtonComponent } from '@/components/ui/Button/Button';
import { shallow } from 'zustand/shallow';
const renderNodeIcons = (node, iconsList, darkMode) => {
@ -56,21 +57,26 @@ export const Node = (props) => {
const setSelectedNodes = useStore((state) => state.setSelectedNodes, shallow);
const callbackActions = useCallbackActions() || [];
const nodeIcon = renderNodeIcons(element.name, iconsList, darkMode);
const getResolvedValue = useStore((state) => state.getResolvedValue, shallow);
const metadata = element.metadata || {};
const { type } = metadata;
const { type, path } = metadata;
const nodeSpecificActions = callbackActions.filter((action) => [type].includes(action.for));
const onExpand = (node) => {
const { element } = node || {};
const { metadata } = element || {};
const { path } = metadata || {};
setSelectedNodes(path);
};
const onSelect = (node) => {
const { isBranch, element } = node || {};
const { metadata } = element || {};
const { path } = metadata || {};
if (!isBranch) {
const { path, type } = metadata || {};
if (type) {
setSelectedNodePath(path);
} else {
setSelectedNodePath(null);
}
setSelectedNodes(path);
};
const nodeSpecificFilteredActions =
nodeSpecificActions?.[0]?.actions?.filter((action) => {
return action.enableInspectorTreeView;
@ -84,27 +90,34 @@ export const Node = (props) => {
return (
// <div {...getNodeProps({ onClick: handleExpand })}>
<div
onClick={() => onSelect(props)}
style={{
marginLeft: 22 * (level - 1),
marginLeft: level > 1 ? 12 : 0,
// paddingLeft: '16px',
opacity: isDisabled ? 0.5 : 1,
height: level === 1 ? '28px' : '32px',
display: 'flex',
alignItems: 'center',
color: level === 1 ? 'var(--text-placeholder, #6A727C)' : 'var(--text-default, #1B1F24)',
cursor: isBranch || level === 1 ? 'pointer' : 'default',
// borderLeft: level > 1 ? '1px solid var(--slate6, #D7DBDF)' : 'none',
}}
>
{(isBranch || level === 1) && (
{!['queries', 'globals', 'variables'].includes(type) && (
<div className="node-expansion-icon">
{isExpanded ? (
<SolidIcon name="TriangleDownCenter" width="16" height="16" />
) : (
<SolidIcon name="TriangleUpCenter" width="16" height="16" />
{(isBranch || level === 1 || path === 'page.variables') && (
<ButtonComponent
iconOnly
leadingIcon={isExpanded ? 'TriangleDownCenter' : 'rightarrrow'}
onClick={() => onExpand(props)}
variant="ghost"
fill="var(--icon-default,#ACB2B9)"
size="small"
/>
)}
</div>
)}
<div
onClick={() => onSelect(props)}
className={cx('node-content', {
'node-content-hoverable': level !== 1,
'node-content-active': actionClicked,
@ -112,7 +125,7 @@ export const Node = (props) => {
>
{nodeIcon && <div className="node-icon">{nodeIcon}</div>}
<div className="node-label">
<OverflowTooltip whiteSpace="normal" placement="top" style={{ height: '100%', width: '190px' }}>
<OverflowTooltip whiteSpace="normal" placement="top" style={{ height: '100%', width: '80%' }}>
<Highlighter
highlightClassName="node-highlight"
searchWords={[searchValue]}

View file

@ -138,6 +138,8 @@ export const TreeViewHeader = (props) => {
className="copy-menu-options-icon json-viewer-options-btn"
style={{
outline: 'none',
border: 'none',
boxShadow: 'none',
}}
>
<SolidIcon data-cy={'menu-icon'} name="morevertical" width="18" fill={'#6A727C'} />

View file

@ -19,7 +19,7 @@ export const formatInspectorDataMisc = (obj, type, searchablePaths = new Set())
name,
children: reduceData(value, currentPath, level + 1),
metadata: {
type: 'misc',
type: type,
path: currentPath,
...((path === 'page.variables' ? level === 2 : level === 1) && {
data: typeof value === 'object' ? JSON.stringify(value) : value,
@ -33,49 +33,6 @@ export const formatInspectorDataMisc = (obj, type, searchablePaths = new Set())
return reduceData(data, type);
};
export const formatInspectorComponentData = (
componentIdNameMapping,
exposedComponentsVariables,
searchablePaths = new Set()
) => {
const data = Object.entries(componentIdNameMapping)
.map(([key, name]) => ({
key,
name: name || key,
value: exposedComponentsVariables[key] ?? { id: key },
}))
.sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: 'base' }));
const reduceData = (obj, path = 'components', level = 1) => {
let data = obj;
if (!obj || typeof obj !== 'object' || level > 1) return [];
else if (!Array.isArray(obj)) {
data = Object.entries(obj);
}
return data
.filter((item) => item.name)
.reduce((acc, { key, name, value }) => {
const currentPath = path + `.${name}`;
searchablePaths.add(currentPath);
return [
...acc,
{
id: currentPath,
name,
children: reduceData(value, currentPath, level + 1),
metadata: {
type: 'components',
path: currentPath,
...(level === 1 && { data: typeof value === 'object' ? JSON.stringify(value) : value }),
},
},
];
}, []);
};
return reduceData(data);
};
export const formatInspectorQueryData = (queryNameIdMapping, exposedQueries, searchablePaths = new Set()) => {
const reverseMapping = Object.fromEntries(Object.entries(queryNameIdMapping).map(([name, id]) => [id, name]));
const _sortedQueries = Object.entries(exposedQueries)

View file

@ -31,4 +31,103 @@ export const createInspectorSlice = (set, get) => ({
setInspectorSearchResults: (results) => {
set({ inspectorSearchResults: results });
},
getAllComponentChildrenById: (id) => {
const { getComponentDefinition, getResolvedComponent } = get();
const component = getComponentDefinition(id);
const componentType = component?.component?.component;
switch (componentType) {
case 'Container':
case 'Form':
case 'ModalV2':
return [
...get().getContainerChildrenMapping(id),
...get().getContainerChildrenMapping(`${id}-header`),
...get().getContainerChildrenMapping(`${id}-footer`),
];
case 'Tabs': {
const tabs = getResolvedComponent(id)?.properties?.tabs;
const children = Array.isArray(tabs) ? tabs : [];
const res = children
?.map((tab) => {
const tabId = `${id}-${tab.id}`;
return get().getContainerChildrenMapping(tabId);
})
.reduce((acc, curr) => {
return [...acc, ...curr];
}, []);
return res;
}
default:
return get().getContainerChildrenMapping(id);
}
},
formatInspectorComponentData: (
componentIdNameMapping,
exposedComponentsVariables,
searchablePaths = new Set(),
moduleId = 'canvas'
) => {
const { getComponentDefinition, getAllComponentChildrenById } = get();
const data = Object.entries(componentIdNameMapping)
.filter(([key]) => {
const component = getComponentDefinition(key, moduleId);
return !component?.component?.parent;
})
.map(([key, name]) => {
const component = getComponentDefinition(key, moduleId);
let parentComponentType = null;
if (component?.component?.parent) {
const parentComponent = getComponentDefinition(component.component.parent, moduleId);
parentComponentType = parentComponent?.component?.component;
}
return {
key,
name: name || key,
parentType: parentComponentType,
};
})
.sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: 'base' }));
const reduceData = (obj, path = 'components', level = 1) => {
let data = obj;
if (!obj || typeof obj !== 'object') return [];
return data
.filter((item) => item.name)
.reduce((acc, { key, name, parentType }) => {
const currentPath = `components.${name}`;
searchablePaths.add(currentPath);
const children = getAllComponentChildrenById(key).map((childKey) => {
const childComponent = getComponentDefinition(childKey);
let parentComponentType = null;
if (childComponent?.component?.parent) {
const parentComponent = getComponentDefinition(childComponent.component.parent);
parentComponentType = parentComponent?.component?.component;
}
return {
key: childKey,
name: childComponent?.component?.name,
parentType: parentComponentType,
};
});
return [
...acc,
{
id: currentPath,
name,
children: reduceData(children, currentPath, level + 1),
metadata: {
type: 'components',
path: currentPath,
parentType: parentType,
},
},
];
}, []);
};
return reduceData(data);
},
});

View file

@ -18877,12 +18877,24 @@ section.ai-message-prompt-input-wrapper {
}
}
.json-tree-view {
ul {
margin-left:16px !important;
border-left: 1px solid var(--slate6, #D7DBDF);
}
ul[role="tree"] {
border-left: none !important;
}
}
.basic.tree {
list-style: none;
margin: 0;
padding: 20px;
padding: 0px;
padding-right:20px;
padding-top: 0px;
}
.basic .tree-node,
@ -18919,6 +18931,7 @@ section.ai-message-prompt-input-wrapper {
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.node-icon {
@ -18932,6 +18945,8 @@ section.ai-message-prompt-input-wrapper {
display: flex;
align-items: center;
font-size:12px;
flex:1;
min-width: 0px;
}
@ -19016,16 +19031,24 @@ section.ai-message-prompt-input-wrapper {
}
// .json-viewer-options-btn {
// display: flex;
// align-items: center;
// margin-left: auto;
// &:hover {
// cursor: pointer;
// background-color:var(--interactive-overlays-fill-hover);
// border-radius: 4px;
// }
// }
.json-viewer-options-btn {
display: flex;
align-items: center;
margin-left: auto;
&:hover {
cursor: pointer;
background-color:var(--interactive-overlays-fill-hover);
border-radius: 4px;
}
align-items: center;
}
@ -19052,6 +19075,7 @@ section.ai-message-prompt-input-wrapper {
width: 20px;
border-radius: 4px;
margin-right:4px;
flex-shrink: 0;
&:hover {
background-color: var(--button-outline-hover);