chore: Function Render of State Inspector (#38893)

This commit is contained in:
Hetu Nandu 2025-01-31 10:49:09 +05:30 committed by GitHub
parent 632443c013
commit 0f8e41fc7c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 262 additions and 221 deletions

View File

@ -1,38 +0,0 @@
import type { MouseEventHandler } from "react";
import AnalyticsUtil from "ee/utils/AnalyticsUtil";
export const objectCollapseAnalytics: MouseEventHandler = (ev) => {
/*
* Analytics events to be logged whenever user clicks on
* react json viewer's controls to expand or collapse object/array
*/
const targetNode = ev.target as HTMLElement;
if (
// collapse/expand icon click, object key click
targetNode.parentElement?.parentElement?.parentElement?.firstElementChild?.classList.contains(
"icon-container",
) ||
// : click
targetNode.parentElement?.parentElement?.firstElementChild?.classList.contains(
"icon-container",
) ||
// { click
targetNode.parentElement?.firstElementChild?.classList.contains(
"icon-container",
) ||
// ellipsis click
targetNode.classList.contains("node-ellipsis") ||
// collapse/expand icon - svg path click
targetNode.parentElement?.parentElement?.classList.contains(
"collapsed-icon",
) ||
targetNode.parentElement?.parentElement?.classList.contains("expanded-icon")
) {
AnalyticsUtil.logEvent("PEEK_OVERLAY_COLLAPSE_EXPAND_CLICK");
}
};
export const textSelectAnalytics = () => {
AnalyticsUtil.logEvent("PEEK_OVERLAY_VALUE_COPIED");
};

View File

@ -1,17 +1,21 @@
import type { MutableRefObject } from "react"; import React, {
import { useState } from "react"; type MutableRefObject,
import React, { useEffect, useRef } from "react"; useCallback,
import ReactJson from "react-json-view"; useMemo,
import { JsonWrapper, reactJsonProps } from "./JsonWrapper"; useRef,
} from "react";
import { useEventCallback } from "usehooks-ts";
import { componentWillAppendToBody } from "react-append-to-body"; import { componentWillAppendToBody } from "react-append-to-body";
import _, { debounce } from "lodash"; import { debounce } from "lodash";
import { zIndexLayers } from "constants/CanvasEditorConstants"; import { zIndexLayers } from "constants/CanvasEditorConstants";
import { objectCollapseAnalytics, textSelectAnalytics } from "./Analytics";
import { Divider } from "@appsmith/ads";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import { getConfigTree, getDataTree } from "selectors/dataTreeSelectors"; import { getConfigTree, getDataTree } from "selectors/dataTreeSelectors";
import { filterInternalProperties } from "utils/FilterInternalProperties"; import { filterInternalProperties } from "utils/FilterInternalProperties";
import { getJSCollections } from "ee/selectors/entitiesSelector"; import { getJSCollections } from "ee/selectors/entitiesSelector";
import * as Styled from "./styles";
import { CONTAINER_MAX_HEIGHT_PX, PEEK_OVERLAY_DELAY } from "./constants";
import { getDataTypeHeader, getPropertyData } from "./utils";
import { JSONViewer, Size } from "../../JSONViewer";
export interface PeekOverlayStateProps { export interface PeekOverlayStateProps {
objectName: string; objectName: string;
@ -31,155 +35,83 @@ export const PeekOverlayPopUp = componentWillAppendToBody(
PeekOverlayPopUpContent, PeekOverlayPopUpContent,
); );
export const PEEK_OVERLAY_DELAY = 200;
const getPropertyData = (src: unknown, propertyPath: string[]) => {
return propertyPath.length > 0 ? _.get(src, propertyPath) : src;
};
const getDataTypeHeader = (data: unknown) => {
const dataType = typeof data;
if (dataType === "object") {
if (Array.isArray(data)) return "array";
if (data === null) return "null";
}
return dataType;
};
export function PeekOverlayPopUpContent( export function PeekOverlayPopUpContent(
props: PeekOverlayStateProps & { props: PeekOverlayStateProps & {
hidePeekOverlay: () => void; hidePeekOverlay: () => void;
}, },
) { ) {
const CONTAINER_MAX_HEIGHT_PX = 252; const { hidePeekOverlay, objectName, position, propertyPath } = props;
const dataWrapperRef: MutableRefObject<HTMLDivElement | null> = useRef(null); const dataWrapperRef: MutableRefObject<HTMLDivElement | null> = useRef(null);
const dataTree = useSelector(getDataTree); const dataTree = useSelector(getDataTree);
const configTree = useSelector(getConfigTree); const configTree = useSelector(getConfigTree);
const jsActions = useSelector(getJSCollections); const jsActions = useSelector(getJSCollections);
const filteredData = filterInternalProperties( const filteredData = filterInternalProperties(
props.objectName, objectName,
dataTree[props.objectName], dataTree[objectName],
jsActions, jsActions,
dataTree, dataTree,
configTree, configTree,
); );
// Because getPropertyData can return a function const [jsData, dataType] = useMemo(
// And we don't want to execute it. // Because getPropertyData can return a function
const [jsData] = useState(() => // And we don't want to execute it.
getPropertyData(filteredData, props.propertyPath), () => {
const jsData = getPropertyData(filteredData, propertyPath);
const dataType = getDataTypeHeader(jsData);
return [jsData, dataType];
},
[filteredData, propertyPath],
); );
const [dataType] = useState(getDataTypeHeader(jsData)); const debouncedHide = debounce(hidePeekOverlay, PEEK_OVERLAY_DELAY);
useEffect(() => { const getPositionValues = useCallback(() => {
const wheelCallback = () => { const positionValues: { $left: string; $bottom?: string; $top?: string } = {
props.hidePeekOverlay(); // Always have a minimum of 8px from the left
$left: Math.max(position.right - 300, 8) + "px",
}; };
window.addEventListener("wheel", wheelCallback); // if the peek overlay is going to be more than the container height, then show it from the bottom
if (position.top >= CONTAINER_MAX_HEIGHT_PX) {
positionValues.$bottom = `calc(100vh - ${position.top}px)`;
} else {
positionValues.$top = `${position.bottom}px`;
}
return () => { return positionValues;
window.removeEventListener("wheel", wheelCallback); }, [position]);
};
}, []);
useEffect(() => { const onWheel = useEventCallback((ev: React.WheelEvent) => {
if (!dataWrapperRef.current) return; ev.stopPropagation();
hidePeekOverlay();
dataWrapperRef.current.addEventListener("copy", textSelectAnalytics); });
return () =>
dataWrapperRef.current?.removeEventListener("copy", textSelectAnalytics);
}, [dataWrapperRef, dataWrapperRef.current]);
const debouncedHide = debounce(
() => props.hidePeekOverlay(),
PEEK_OVERLAY_DELAY,
);
const getLeftPosition = (position: DOMRect) => {
let left = position.right - 300;
if (left < 0) left = 8;
return left;
};
return ( return (
<div <Styled.PeekOverlayContainer
className={`absolute ${zIndexLayers.PEEK_OVERLAY}`} className={`absolute ${zIndexLayers.PEEK_OVERLAY}`}
id="t--peek-overlay-container" id="t--peek-overlay-container"
onMouseEnter={() => debouncedHide.cancel()} onMouseEnter={debouncedHide.cancel}
onMouseLeave={() => debouncedHide()} onMouseLeave={debouncedHide}
onWheel={(ev) => ev.stopPropagation()} onWheel={onWheel}
style={{ {...getPositionValues()}
minHeight: "46px",
maxHeight: `${CONTAINER_MAX_HEIGHT_PX}px`,
width: "300px",
backgroundColor: "var(--ads-v2-color-bg)",
boxShadow: "0px 0px 10px #0000001A", // color used from designs
borderRadius: "var(--ads-v2-border-radius)",
left: `${getLeftPosition(props.position)}px`,
...(props.position.top >= CONTAINER_MAX_HEIGHT_PX
? {
bottom: `calc(100vh - ${props.position.top}px)`,
}
: {
top: `${props.position.bottom}px`,
}),
}}
> >
<div <Styled.DataType className="first-letter:uppercase">
className="first-letter:uppercase"
style={{
height: "24px",
color: "var(--appsmith-color-black-700)",
padding: "4px 0px 4px 12px",
fontSize: "10px",
}}
>
{dataType} {dataType}
</div> </Styled.DataType>
<Divider style={{ display: "block" }} /> <Styled.BlockDivider />
<div <Styled.PeekOverlayData id="t--peek-overlay-data" ref={dataWrapperRef}>
id="t--peek-overlay-data"
ref={dataWrapperRef}
style={{
minHeight: "20px",
padding: "2px 0px 2px 12px",
fontSize: "10px",
}}
>
{(dataType === "object" || dataType === "array") && jsData !== null && ( {(dataType === "object" || dataType === "array") && jsData !== null && (
<JsonWrapper <Styled.JsonWrapper className="as-mask">
className="as-mask" <JSONViewer size={Size.SMALL} src={jsData} />
onClick={objectCollapseAnalytics} </Styled.JsonWrapper>
style={{
minHeight: "20px",
maxHeight: "225px",
overflowY: "auto",
}}
>
<ReactJson src={jsData} {...reactJsonProps} />
</JsonWrapper>
)} )}
{/* TODO: Fix this the next time the file is edited */} {dataType === "function" && <div>{jsData.toString()}</div>}
{/* eslint-disable-next-line @typescript-eslint/no-explicit-any */} {dataType === "boolean" && <div>{jsData.toString()}</div>}
{dataType === "function" && <div>{(jsData as any).toString()}</div>} {dataType === "string" && <div>{jsData.toString()}</div>}
{/* TODO: Fix this the next time the file is edited */} {dataType === "number" && <div>{jsData.toString()}</div>}
{/* eslint-disable-next-line @typescript-eslint/no-explicit-any */}
{dataType === "boolean" && <div>{(jsData as any).toString()}</div>}
{/* TODO: Fix this the next time the file is edited */}
{/* eslint-disable-next-line @typescript-eslint/no-explicit-any */}
{dataType === "string" && <div>{(jsData as any).toString()}</div>}
{/* TODO: Fix this the next time the file is edited */}
{/* eslint-disable-next-line @typescript-eslint/no-explicit-any */}
{dataType === "number" && <div>{(jsData as any).toString()}</div>}
{((dataType !== "object" && {((dataType !== "object" &&
dataType !== "function" && dataType !== "function" &&
dataType !== "boolean" && dataType !== "boolean" &&
@ -188,14 +120,12 @@ export function PeekOverlayPopUpContent(
dataType !== "number") || dataType !== "number") ||
jsData === null) && ( jsData === null) && (
<div> <div>
{/* TODO: Fix this the next time the file is edited */} {jsData?.toString() ?? jsData ?? jsData === undefined
{/* eslint-disable-next-line @typescript-eslint/no-explicit-any */}
{(jsData as any)?.toString() ?? jsData ?? jsData === undefined
? "undefined" ? "undefined"
: "null"} : "null"}
</div> </div>
)} )}
</div> </Styled.PeekOverlayData>
</div> </Styled.PeekOverlayContainer>
); );
} }

View File

@ -0,0 +1,3 @@
export const CONTAINER_MAX_HEIGHT_PX = 252;
export const PEEK_OVERLAY_DELAY = 200;

View File

@ -0,0 +1,43 @@
import styled from "styled-components";
import { Divider } from "@appsmith/ads";
export const PeekOverlayContainer = styled.div<{
$left: string;
$top?: string;
$bottom?: string;
}>`
min-height: 46px;
max-height: 252px;
width: 300px;
background-color: var(--ads-v2-color-bg);
box-shadow: 0 0 10px #0000001a; // color used from designs
border-radius: var(--ads-v2-border-radius);
left: ${({ $left }) => $left};
top: ${({ $top }) => $top};
bottom: ${({ $bottom }) => $bottom};
`;
export const DataType = styled.div`
height: 24px;
color: var(--appsmith-color-black-700);
padding: var(--ads-v2-spaces-2) 0 var(--ads-v2-spaces-2)
var(--ads-v2-spaces-4);
font-size: 10px;
`;
export const BlockDivider = styled(Divider)`
display: block;
`;
export const PeekOverlayData = styled.div`
min-height: 20px;
padding: var(--ads-v2-spaces-1) 0 var(--ads-v2-spaces-1)
var(--ads-v2-spaces-4);
font-size: 10px;
`;
export const JsonWrapper = styled.div`
min-height: 20px;
max-height: 225px;
overflow-y: auto;
`;

View File

@ -0,0 +1,16 @@
import { get } from "lodash";
export const getPropertyData = (src: unknown, propertyPath: string[]) => {
return propertyPath.length > 0 ? get(src, propertyPath) : src;
};
export const getDataTypeHeader = (data: unknown) => {
const dataType = typeof data;
if (dataType === "object") {
if (Array.isArray(data)) return "array";
if (data === null) return "null";
}
return dataType;
};

View File

@ -128,10 +128,7 @@ import { getEntitiesForNavigation } from "selectors/navigationSelectors";
import history, { NavigationMethod } from "utils/history"; import history, { NavigationMethod } from "utils/history";
import { CursorPositionOrigin } from "ee/reducers/uiReducers/editorContextReducer"; import { CursorPositionOrigin } from "ee/reducers/uiReducers/editorContextReducer";
import type { PeekOverlayStateProps } from "./PeekOverlayPopup/PeekOverlayPopup"; import type { PeekOverlayStateProps } from "./PeekOverlayPopup/PeekOverlayPopup";
import { import { PeekOverlayPopUp } from "./PeekOverlayPopup/PeekOverlayPopup";
PeekOverlayPopUp,
PEEK_OVERLAY_DELAY,
} from "./PeekOverlayPopup/PeekOverlayPopup";
import ConfigTreeActions from "utils/configTree"; import ConfigTreeActions from "utils/configTree";
import { import {
getSaveAndAutoIndentKey, getSaveAndAutoIndentKey,
@ -164,6 +161,7 @@ import CodeMirrorTernService from "utils/autocomplete/CodemirrorTernService";
import { getEachEntityInformation } from "ee/utils/autocomplete/EntityDefinitions"; import { getEachEntityInformation } from "ee/utils/autocomplete/EntityDefinitions";
import { getCurrentPageId } from "selectors/editorSelectors"; import { getCurrentPageId } from "selectors/editorSelectors";
import { executeCommandAction } from "actions/pluginActionActions"; import { executeCommandAction } from "actions/pluginActionActions";
import { PEEK_OVERLAY_DELAY } from "./PeekOverlayPopup/constants";
type ReduxStateProps = ReturnType<typeof mapStateToProps>; type ReduxStateProps = ReturnType<typeof mapStateToProps>;
type ReduxDispatchProps = ReturnType<typeof mapDispatchToProps>; type ReduxDispatchProps = ReturnType<typeof mapDispatchToProps>;
@ -202,6 +200,7 @@ export interface EditorStyleProps {
popperZIndex?: Indices; popperZIndex?: Indices;
blockCompletions?: Array<BlockCompletion>; blockCompletions?: Array<BlockCompletion>;
} }
/** /**
* line => Line to which the gutter is added * line => Line to which the gutter is added
* *

View File

@ -4,6 +4,8 @@ import "@testing-library/jest-dom";
import { StateInspector } from "./StateInspector"; import { StateInspector } from "./StateInspector";
import { useStateInspectorItems } from "./hooks"; import { useStateInspectorItems } from "./hooks";
import { filterEntityGroupsBySearchTerm } from "IDE/utils"; import { filterEntityGroupsBySearchTerm } from "IDE/utils";
import { lightTheme } from "selectors/themeSelectors";
import { ThemeProvider } from "styled-components";
jest.mock("./hooks"); jest.mock("./hooks");
jest.mock("IDE/utils"); jest.mock("IDE/utils");
@ -31,7 +33,11 @@ describe("StateInspector", () => {
], ],
{ key: "value1" }, { key: "value1" },
]); ]);
render(<StateInspector />); render(
<ThemeProvider theme={lightTheme}>
<StateInspector />
</ThemeProvider>,
);
const searchInput = screen.getByPlaceholderText("Search entities"); const searchInput = screen.getByPlaceholderText("Search entities");
fireEvent.change(searchInput, { target: { value: "Group 1" } }); fireEvent.change(searchInput, { target: { value: "Group 1" } });
@ -53,7 +59,11 @@ describe("StateInspector", () => {
], ],
{ key: "value1" }, { key: "value1" },
]); ]);
render(<StateInspector />); render(
<ThemeProvider theme={lightTheme}>
<StateInspector />
</ThemeProvider>,
);
fireEvent.click(screen.getByText("Item 2")); fireEvent.click(screen.getByText("Item 2"));
expect(mockOnClick).toHaveBeenCalled(); expect(mockOnClick).toHaveBeenCalled();
@ -71,7 +81,11 @@ describe("StateInspector", () => {
], ],
{ key: "Value1" }, { key: "Value1" },
]); ]);
render(<StateInspector />); render(
<ThemeProvider theme={lightTheme}>
<StateInspector />
</ThemeProvider>,
);
expect( expect(
screen.getByTestId("t--selected-entity-details").textContent, screen.getByTestId("t--selected-entity-details").textContent,
@ -84,7 +98,11 @@ describe("StateInspector", () => {
it("does not render selected item details when no item is selected", () => { it("does not render selected item details when no item is selected", () => {
mockedUseStateInspectorItems.mockReturnValue([null, [], null]); mockedUseStateInspectorItems.mockReturnValue([null, [], null]);
render(<StateInspector />); render(
<ThemeProvider theme={lightTheme}>
<StateInspector />
</ThemeProvider>,
);
expect(screen.queryByText("Item 1")).not.toBeInTheDocument(); expect(screen.queryByText("Item 1")).not.toBeInTheDocument();
}); });
@ -101,12 +119,20 @@ describe("StateInspector", () => {
{ key: "value1" }, { key: "value1" },
]); ]);
render(<StateInspector />); render(
<ThemeProvider theme={lightTheme}>
<StateInspector />
</ThemeProvider>,
);
expect(screen.getByText("Group 1")).toBeInTheDocument(); expect(screen.getByText("Group 1")).toBeInTheDocument();
expect(screen.getByText("Group 2")).toBeInTheDocument(); expect(screen.getByText("Group 2")).toBeInTheDocument();
}); });
it("renders no items when search term does not match any group", () => { it("renders no items when search term does not match any group", () => {
render(<StateInspector />); render(
<ThemeProvider theme={lightTheme}>
<StateInspector />
</ThemeProvider>,
);
const searchInput = screen.getByPlaceholderText("Search entities"); const searchInput = screen.getByPlaceholderText("Search entities");
fireEvent.change(searchInput, { target: { value: "Nonexistent Group" } }); fireEvent.change(searchInput, { target: { value: "Nonexistent Group" } });
@ -116,7 +142,11 @@ describe("StateInspector", () => {
it("renders no items when items list is empty", () => { it("renders no items when items list is empty", () => {
mockedUseStateInspectorItems.mockReturnValue([null, [], null]); mockedUseStateInspectorItems.mockReturnValue([null, [], null]);
render(<StateInspector />); render(
<ThemeProvider theme={lightTheme}>
<StateInspector />
</ThemeProvider>,
);
expect(screen.queryByText("Group 1")).not.toBeInTheDocument(); expect(screen.queryByText("Group 1")).not.toBeInTheDocument();
expect(screen.queryByText("Group 2")).not.toBeInTheDocument(); expect(screen.queryByText("Group 2")).not.toBeInTheDocument();
}); });
@ -130,7 +160,11 @@ describe("StateInspector", () => {
], ],
{}, {},
]); ]);
render(<StateInspector />); render(
<ThemeProvider theme={lightTheme}>
<StateInspector />
</ThemeProvider>,
);
expect( expect(
screen.getByTestId("t--selected-entity-details").textContent, screen.getByTestId("t--selected-entity-details").textContent,

View File

@ -1,29 +1,21 @@
import React, { useState } from "react"; import React, { useState } from "react";
import ReactJson from "react-json-view";
import { import {
EntityGroupsList, EntityGroupsList,
Flex, Flex,
type FlexProps,
type ListItemProps, type ListItemProps,
SearchInput, SearchInput,
Text, Text,
} from "@appsmith/ads"; } from "@appsmith/ads";
import { JSONViewer, Size } from "components/editorComponents/JSONViewer";
import { filterEntityGroupsBySearchTerm } from "IDE/utils"; import { filterEntityGroupsBySearchTerm } from "IDE/utils";
import { useStateInspectorItems } from "./hooks"; import { useStateInspectorItems } from "./hooks";
import * as Styled from "./styles"; import * as Styled from "./styles";
export const reactJsonProps = { const GroupListPadding = {
name: null, pl: "spaces-3",
enableClipboard: false, pr: "spaces-3",
displayDataTypes: false, } as FlexProps;
displayArrayKey: true,
quotesOnKeys: false,
style: {
fontSize: "12px",
},
collapsed: 1,
indentWidth: 2,
collapseStringsAfterLength: 30,
};
export const StateInspector = () => { export const StateInspector = () => {
const [selectedItem, items, selectedItemCode] = useStateInspectorItems(); const [selectedItem, items, selectedItemCode] = useStateInspectorItems();
@ -51,10 +43,7 @@ export const StateInspector = () => {
/> />
</Flex> </Flex>
<EntityGroupsList <EntityGroupsList
flexProps={{ flexProps={GroupListPadding}
pl: "spaces-3",
pr: "spaces-3",
}}
groups={filteredItemGroups.map((item) => { groups={filteredItemGroups.map((item) => {
return { return {
groupTitle: item.group, groupTitle: item.group,
@ -81,8 +70,8 @@ export const StateInspector = () => {
{selectedItem.icon} {selectedItem.icon}
<Text kind="body-m">{selectedItem.title}</Text> <Text kind="body-m">{selectedItem.title}</Text>
</Styled.SelectedItem> </Styled.SelectedItem>
<Flex overflowY="auto" px="spaces-3"> <Flex className="as-mask" overflowY="auto" px="spaces-3">
<ReactJson src={selectedItemCode} {...reactJsonProps} /> <JSONViewer size={Size.MEDIUM} src={selectedItemCode} />
</Flex> </Flex>
</Flex> </Flex>
) : null} ) : null}

View File

@ -0,0 +1,16 @@
import React from "react";
import type { JSONViewerProps } from "./types";
import ReactJson from "react-json-view";
import * as Styled from "./styles";
import { FontSize, IconSize, reactJsonProps } from "./constants";
export function JSONViewer(props: JSONViewerProps) {
const fontSize = FontSize[props.size];
const iconSize = IconSize[props.size];
return (
<Styled.Container $fontSize={fontSize} $iconSize={iconSize}>
<ReactJson src={props.src} {...reactJsonProps} />
</Styled.Container>
);
}

View File

@ -0,0 +1,22 @@
import { Size } from "./types";
export const FontSize = {
[Size.SMALL]: "10px",
[Size.MEDIUM]: "12px",
};
export const IconSize = {
[Size.SMALL]: "8px",
[Size.MEDIUM]: "10px",
};
export const reactJsonProps = {
name: null,
enableClipboard: false,
displayDataTypes: false,
displayArrayKey: true,
quotesOnKeys: false,
collapsed: 1,
indentWidth: 2,
collapseStringsAfterLength: 30,
};

View File

@ -0,0 +1,3 @@
export { JSONViewer } from "./JSONViewer";
export { Size } from "./types";
export type { JSONViewerProps } from "./types";

View File

@ -1,33 +1,24 @@
import styled from "styled-components"; import styled, { css } from "styled-components";
export const reactJsonProps = { const ReactJSONViewerOverrider = css<{ $fontSize: string; $iconSize: string }>`
name: null, font-size: ${({ $fontSize }) => $fontSize} !important;
enableClipboard: false,
displayDataTypes: false,
displayArrayKey: true,
quotesOnKeys: false,
style: {
fontSize: "10px",
},
collapsed: 1,
indentWidth: 2,
collapseStringsAfterLength: 30,
};
export const JsonWrapper = styled.div`
// all ellipsis font size // all ellipsis font size
.node-ellipsis, .node-ellipsis,
.function-collapsed span:nth-child(2), .function-collapsed span:nth-child(2),
.string-value span { .string-value span {
font-size: 10px !important; font-size: ${({ $fontSize }) => $fontSize} !important;
} }
// disable and hide first object collapser // disable and hide first object collapse icon
.pretty-json-container .pretty-json-container
> .object-content:first-of-type > .object-content:first-of-type
> .object-key-val:first-of-type > .object-key-val:first-of-type
> span { > span {
pointer-events: none !important; pointer-events: none !important;
.icon-container { .icon-container {
display: none !important; display: none !important;
} }
@ -38,28 +29,42 @@ export const JsonWrapper = styled.div`
} }
// collapse icon color change and alignment // collapse icon color change and alignment
.icon-container { .icon-container {
width: 10px !important; width: ${({ $iconSize }) => $iconSize} !important;
height: 8px !important; height: ${({ $iconSize }) => $iconSize} !important;
.expanded-icon {
svg {
vertical-align: middle !important;
padding-left: 0px !important;
width: 0.8em !important;
}
}
svg { svg {
color: var(--appsmith-color-black-600) !important; color: var(--appsmith-color-black-600) !important;
} }
} }
// font-sizes and alignments // font-sizes and alignments
.pushed-content.object-container { .pushed-content.object-container {
.object-content { .object-content {
padding-left: 4px !important; padding-left: 4px !important;
.variable-row { .variable-row {
padding-top: 0 !important; padding-top: 0 !important;
padding-bottom: 0 !important; padding-bottom: 0 !important;
border-left: 0 !important; border-left: 0 !important;
.variable-value div { .variable-value div {
font-size: 10px !important; font-size: ${({ $fontSize }) => $fontSize} !important;
padding-top: 0 !important; padding-top: 0 !important;
padding-bottom: 0 !important; padding-bottom: 0 !important;
} }
} }
.object-key-val { .object-key-val {
padding-top: 0 !important; padding-top: 0 !important;
padding-bottom: 0 !important; padding-bottom: 0 !important;
@ -70,25 +75,35 @@ export const JsonWrapper = styled.div`
} }
// disabling function collapse and neutral styling // disabling function collapse and neutral styling
.rjv-function-container { .rjv-function-container {
pointer-events: none; pointer-events: none;
font-weight: normal !important; font-weight: normal !important;
> span:first-child:before { > span:first-child:before {
// In prod build, for some reason react-json-viewer // In prod build, for some reason react-json-viewer
// misses adding this opening braces for function // misses adding this opening braces for function
content: "("; content: "(";
} }
.function-collapsed { .function-collapsed {
font-weight: normal !important; font-weight: normal !important;
span:nth-child(1) { span:nth-child(1) {
display: none; // hiding extra braces display: none; // hiding extra braces
} }
span:nth-child(2) { span:nth-child(2) {
color: #393939 !important; color: #393939 !important;
} }
} }
} }
div:has(.rjv-function-container) { div:has(.rjv-function-container) {
cursor: default !important; cursor: default !important;
} }
`; `;
export const Container = styled.div<{ $fontSize: string; $iconSize: string }>`
${ReactJSONViewerOverrider}
`;

View File

@ -0,0 +1,9 @@
export enum Size {
SMALL = "small",
MEDIUM = "medium",
}
export interface JSONViewerProps {
src: unknown;
size: Size;
}