mirror of
https://github.com/ToolJet/ToolJet
synced 2026-05-23 00:48:25 +00:00
Left sidebar changes added
This commit is contained in:
parent
2ec0506c8f
commit
9c949eda00
16 changed files with 109 additions and 42 deletions
|
|
@ -238,7 +238,7 @@ export const BaseLeftSidebar = ({
|
|||
toggleLeftSidebar(false);
|
||||
}}
|
||||
open={isSidebarOpen}
|
||||
popoverContentClassName={`p-0 sidebar-h-100-popover ${selectedSidebarItem}`}
|
||||
popoverContentClassName={`p-0 left-sidebar-scrollbar sidebar-h-100-popover ${selectedSidebarItem}`}
|
||||
side="right"
|
||||
popoverContent={renderPopoverContent()}
|
||||
popoverContentHeight={popoverContentHeight}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import React from 'react';
|
||||
import OverflowTooltip from '@/_components/OverflowTooltip';
|
||||
|
||||
const ArrayNode = ({ value }) => {
|
||||
return (
|
||||
<div className="json-viewer-node-value" style={{ color: '#1F99ED' }}>
|
||||
{`[${value.length}]`}
|
||||
<OverflowTooltip maxLetters={32} style={{ width: '100%' }}>{`[${value.length}]`}</OverflowTooltip>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@ 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>
|
||||
<OverflowTooltip maxLetters={32} style={{ width: '100%' }}>
|
||||
{value.toString()}
|
||||
</OverflowTooltip>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@ import OverflowTooltip from '@/_components/OverflowTooltip';
|
|||
const FunctionNode = () => {
|
||||
return (
|
||||
<div className="json-viewer-node-value" style={{ color: '#4368E3' }}>
|
||||
<OverflowTooltip style={{ width: '100%' }}>function</OverflowTooltip>
|
||||
<OverflowTooltip maxLetters={32} style={{ width: '100%' }}>
|
||||
function
|
||||
</OverflowTooltip>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@ import OverflowTooltip from '@/_components/OverflowTooltip';
|
|||
const NumberNode = ({ value }) => {
|
||||
return (
|
||||
<div className="json-viewer-node-value" style={{ color: '#2CA02C' }}>
|
||||
<OverflowTooltip style={{ width: '100%' }}>{value}</OverflowTooltip>
|
||||
<OverflowTooltip maxLetters={32} style={{ width: '100%' }}>
|
||||
{value}
|
||||
</OverflowTooltip>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import React from 'react';
|
||||
|
||||
import OverflowTooltip from '@/_components/OverflowTooltip';
|
||||
const ObjectNode = ({ value }) => {
|
||||
return (
|
||||
<div className="json-viewer-node-value" style={{ color: '#FF7F0E' }}>
|
||||
{`{${Object.keys(value).length}}`}
|
||||
<OverflowTooltip maxLetters={32} style={{ width: '100%' }}>{`{${Object.keys(value).length}}`}</OverflowTooltip>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import NullNode from './NullNode';
|
|||
import ArrayNode from './ArrayNode';
|
||||
import ObjectNode from './ObjectNode';
|
||||
import SolidIcon from '@/_ui/Icon/SolidIcons';
|
||||
import OverflowTooltip from '@/_components/OverflowTooltip';
|
||||
import { ToolTip } from '@/_components/ToolTip';
|
||||
import { DefaultCopyIcon } from '../../DefaultCopyIcon';
|
||||
import { copyToClipboard } from '../../utils';
|
||||
|
|
@ -35,8 +36,8 @@ const Row = ({ label, value, level = 1, absolutePath }) => {
|
|||
const isArray = Array.isArray(value);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="json-viewer-row-container" style={{ marginLeft: level > 1 ? '8px' : '0px' }}>
|
||||
<div style={{ marginLeft: `${level === 1 ? '0px' : '22px'}` }}>
|
||||
<div className="json-viewer-row-container">
|
||||
<div className="json-viewer-row" onClick={() => setIsExpanded((prev) => !prev)}>
|
||||
<div className="json-viewer-expand-icon">
|
||||
{(isArray || isObject) &&
|
||||
|
|
@ -59,13 +60,13 @@ const Row = ({ label, value, level = 1, absolutePath }) => {
|
|||
))}
|
||||
</div>
|
||||
<div className="json-viewer-label-container">
|
||||
<span>{label}</span>
|
||||
<OverflowTooltip style={{ width: '100%' }}>{label}</OverflowTooltip>
|
||||
</div>
|
||||
<div className="json-viewer-value-container">
|
||||
<Node />
|
||||
</div>
|
||||
<div className="json-viewer-actions-container">
|
||||
<ToolTip message={'Copy to clipboard'}>
|
||||
<ToolTip message={'Copy path'}>
|
||||
<span
|
||||
onClick={() => {
|
||||
copyToClipboard(absolutePath);
|
||||
|
|
@ -89,20 +90,14 @@ const Row = ({ label, value, level = 1, absolutePath }) => {
|
|||
</div>
|
||||
</div>
|
||||
{isExpanded && isObject && (
|
||||
<div
|
||||
className="json-viewer-children"
|
||||
style={{ marginLeft: `${20 * level}px`, borderLeft: '1px solid var(--border-weak)' }}
|
||||
>
|
||||
<div className="json-viewer-children" style={{ marginLeft: '5px', 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)' }}
|
||||
>
|
||||
<div className="json-viewer-children" style={{ marginLeft: '5px', borderLeft: '1px solid var(--border-weak)' }}>
|
||||
{value.map((item, index) => {
|
||||
return (
|
||||
<Row
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import OverflowTooltip from '@/_components/OverflowTooltip';
|
|||
const StringNode = ({ value }) => {
|
||||
return (
|
||||
<div className="json-viewer-node-value" style={{ color: '#2CA02C' }}>
|
||||
<OverflowTooltip>{`"${value}"`}</OverflowTooltip>
|
||||
<OverflowTooltip maxLetters={32}>{`"${value}"`}</OverflowTooltip>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,9 +4,20 @@
|
|||
font-family: "IBM Plex Sans";
|
||||
font-size: 12px;
|
||||
color: var(--text-default, #1B1F24);
|
||||
|
||||
overflow-x: auto;
|
||||
min-width: 0;
|
||||
|
||||
// Hide scrollbar for Chrome, Safari and Opera
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// Hide scrollbar for IE, Edge and Firefox
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
|
||||
.json-viewer-row-container {
|
||||
min-width: max-content;
|
||||
&:hover {
|
||||
background-color: var(--interactive-overlays-fill-hover);
|
||||
|
||||
|
|
@ -22,6 +33,7 @@
|
|||
height: 20px;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
|
||||
.json-viewer-expand-icon {
|
||||
width: 12px;
|
||||
|
|
@ -33,9 +45,8 @@
|
|||
}
|
||||
|
||||
.json-viewer-label-container {
|
||||
margin-left: 8px;
|
||||
margin-right: 4px;
|
||||
flex-shrink: 0; /* don’t shrink */
|
||||
flex-shrink: 0; /* don't shrink */
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
|
|
@ -54,6 +65,8 @@
|
|||
width:40px;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
position: absolute;
|
||||
right:20px;
|
||||
|
||||
.json-viewer-action-icon {
|
||||
display: flex;
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ export const HiddenOptions = (props) => {
|
|||
const [showMenu, setShowMenu] = useState(false);
|
||||
const closeMenu = () => {
|
||||
setShowMenu(false);
|
||||
setActionClicked(false);
|
||||
};
|
||||
|
||||
const copyPath = () => {
|
||||
|
|
@ -39,6 +38,11 @@ export const HiddenOptions = (props) => {
|
|||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
// This is to ensure that the actionClicked state is updated when the menu is shown or deleted on the next render to avoid misplacing the Popover
|
||||
useEffect(() => {
|
||||
setTimeout(() => setActionClicked(showMenu), 0);
|
||||
}, [showMenu]);
|
||||
|
||||
const renderOptions = () => {
|
||||
return nodeSpecificFilteredActions?.map((actionOption, index) => {
|
||||
const { name, icon, src, iconName, dispatchAction, width = 12, height = 12 } = actionOption;
|
||||
|
|
@ -69,7 +73,7 @@ export const HiddenOptions = (props) => {
|
|||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
className="d-flex position-absolute"
|
||||
className={cx('d-flex position-absolute', { 'show-menu': showMenu })}
|
||||
>
|
||||
{renderOptions()}
|
||||
<OverlayTrigger
|
||||
|
|
@ -78,7 +82,7 @@ export const HiddenOptions = (props) => {
|
|||
rootClose={false}
|
||||
show={showMenu}
|
||||
overlay={
|
||||
<Popover className={cx('copy-menu-options', { 'dark-theme': darkMode })}>
|
||||
<Popover className={cx('copy-menu-options', { 'dark-theme': darkMode })} onClick={(e) => e.stopPropagation()}>
|
||||
<Popover.Body bsPrefix="popover-body">
|
||||
<div className="menu-options mb-0">
|
||||
<div
|
||||
|
|
@ -112,7 +116,6 @@ export const HiddenOptions = (props) => {
|
|||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
setShowMenu((prev) => !prev);
|
||||
setActionClicked((prev) => !prev);
|
||||
}}
|
||||
className="node-action-icon"
|
||||
style={{
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import useStore from '@/AppBuilder/_stores/store';
|
|||
import { shallow } from 'zustand/shallow';
|
||||
import Fuse from 'fuse.js';
|
||||
import JSONViewer from './JSONViewer';
|
||||
import { SearchBox } from '@/_components';
|
||||
import { Node } from './Node';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import InputComponent from '@/components/ui/Input/Index';
|
||||
|
|
@ -16,12 +15,21 @@ const JSONTreeViewerV2 = ({ data = {}, iconsList = [], darkMode, searchablePaths
|
|||
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);
|
||||
const selectedNodePath = useStore((state) => state.selectedNodePath, shallow);
|
||||
const setSelectedNodePath = useStore((state) => state.setSelectedNodePath, shallow);
|
||||
|
||||
const selectedNodes = useStore((state) => state.selectedNodes, shallow);
|
||||
|
||||
function fuzzySearch(query, searchablePaths) {
|
||||
const list = Array.from(searchablePaths);
|
||||
const fuse = new Fuse(list, { threshold: 0.3 });
|
||||
const fuse = new Fuse(list, {
|
||||
threshold: 0.2,
|
||||
minMatchCharLength: 2,
|
||||
includeScore: true,
|
||||
distance: 1000,
|
||||
tokenize: true,
|
||||
matchAllTokens: true,
|
||||
});
|
||||
return fuse.search(query).map((result) => result.item);
|
||||
}
|
||||
|
||||
|
|
@ -106,8 +114,8 @@ const JSONTreeViewerV2 = ({ data = {}, iconsList = [], darkMode, searchablePaths
|
|||
const expandedIdsSet = new Set(expandedIds);
|
||||
const filtered = flattendedData.filter((item) => {
|
||||
const { metadata } = item || {};
|
||||
const { path } = metadata || {};
|
||||
return expandedIdsSet.has(path);
|
||||
const { actualPath, path } = metadata || {};
|
||||
return expandedIdsSet.has(actualPath || path);
|
||||
});
|
||||
|
||||
return filtered
|
||||
|
|
|
|||
|
|
@ -65,8 +65,8 @@ export const Node = (props) => {
|
|||
const onExpand = (node) => {
|
||||
const { element } = node || {};
|
||||
const { metadata } = element || {};
|
||||
const { path } = metadata || {};
|
||||
setSelectedNodes(path);
|
||||
const { path, actualPath } = metadata || {};
|
||||
setSelectedNodes(actualPath || path);
|
||||
};
|
||||
|
||||
const onSelect = (node) => {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ const initialState = {
|
|||
searchedNodes: new Set(),
|
||||
inspectorSearchValue: '',
|
||||
inspectorSearchResults: new Set(),
|
||||
selectedNodePath: null,
|
||||
};
|
||||
|
||||
export const createInspectorSlice = (set, get) => ({
|
||||
|
|
@ -31,6 +32,9 @@ export const createInspectorSlice = (set, get) => ({
|
|||
setInspectorSearchResults: (results) => {
|
||||
set({ inspectorSearchResults: results });
|
||||
},
|
||||
setSelectedNodePath: (path) => {
|
||||
set({ selectedNodePath: path });
|
||||
},
|
||||
getAllComponentChildrenById: (id) => {
|
||||
const { getComponentDefinition, getResolvedComponent } = get();
|
||||
const component = getComponentDefinition(id);
|
||||
|
|
@ -97,7 +101,8 @@ export const createInspectorSlice = (set, get) => ({
|
|||
.filter((item) => item.name)
|
||||
.reduce((acc, { key, name, parentType }) => {
|
||||
const currentPath = `components.${name}`;
|
||||
searchablePaths.add(currentPath);
|
||||
const actualPath = `${path}.${name}`;
|
||||
searchablePaths.add(actualPath);
|
||||
const children = getAllComponentChildrenById(key).map((childKey) => {
|
||||
const childComponent = getComponentDefinition(childKey);
|
||||
let parentComponentType = null;
|
||||
|
|
@ -115,13 +120,14 @@ export const createInspectorSlice = (set, get) => ({
|
|||
return [
|
||||
...acc,
|
||||
{
|
||||
id: currentPath,
|
||||
id: actualPath,
|
||||
name,
|
||||
children: reduceData(children, currentPath, level + 1),
|
||||
children: reduceData(children, actualPath, level + 1),
|
||||
metadata: {
|
||||
type: 'components',
|
||||
path: currentPath,
|
||||
parentType: parentType,
|
||||
actualPath,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -30,8 +30,15 @@ export const createLeftSideBarSlice = (set, get) => ({
|
|||
),
|
||||
setPathToBeInspected: (pathToBeInspected) => set(() => ({ pathToBeInspected }), false, 'setPathToBeInspected'),
|
||||
setComponentToInspect: (componentToInspect) => {
|
||||
const { setPathToBeInspected, setSelectedSidebarItem, toggleLeftSidebar, selectedSidebarItem } = get();
|
||||
setPathToBeInspected(['components', componentToInspect]);
|
||||
const {
|
||||
setPathToBeInspected,
|
||||
setSelectedSidebarItem,
|
||||
toggleLeftSidebar,
|
||||
selectedSidebarItem,
|
||||
setSelectedNodePath,
|
||||
} = get();
|
||||
// setPathToBeInspected(['components', componentToInspect]);
|
||||
setSelectedNodePath(`components.${componentToInspect}`);
|
||||
if (selectedSidebarItem !== 'inspect') {
|
||||
setSelectedSidebarItem('inspect');
|
||||
toggleLeftSidebar(true);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,14 @@
|
|||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { ToolTip } from '@/_components';
|
||||
|
||||
export default function OverflowTooltip({ children, className, whiteSpace = 'nowrap', placement = 'bottom', ...rest }) {
|
||||
export default function OverflowTooltip({
|
||||
children,
|
||||
className,
|
||||
whiteSpace = 'nowrap',
|
||||
placement = 'bottom',
|
||||
maxLetters,
|
||||
...rest
|
||||
}) {
|
||||
const [isOverflowed, setIsOverflow] = useState(false);
|
||||
const textElementRef = useRef();
|
||||
|
||||
|
|
@ -12,6 +19,11 @@ export default function OverflowTooltip({ children, className, whiteSpace = 'now
|
|||
);
|
||||
}, [children]);
|
||||
|
||||
const displayText =
|
||||
maxLetters && typeof children === 'string' && children.length > maxLetters
|
||||
? `${children.substring(0, maxLetters)}...`
|
||||
: children;
|
||||
|
||||
return (
|
||||
<ToolTip
|
||||
className={className}
|
||||
|
|
@ -32,7 +44,7 @@ export default function OverflowTooltip({ children, className, whiteSpace = 'now
|
|||
...rest.style,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
{displayText}
|
||||
</div>
|
||||
</ToolTip>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -859,4 +859,20 @@
|
|||
height: calc(100% - 48px);
|
||||
min-height: 300px;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
.left-sidebar-scrollbar {
|
||||
&::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: var(--interactive-default) !important;
|
||||
border-radius: 3px;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue