chore: Function Render of State Inspector (#38893)
This commit is contained in:
parent
632443c013
commit
0f8e41fc7c
|
|
@ -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");
|
||||
};
|
||||
|
|
@ -1,17 +1,21 @@
|
|||
import type { MutableRefObject } from "react";
|
||||
import { useState } from "react";
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import ReactJson from "react-json-view";
|
||||
import { JsonWrapper, reactJsonProps } from "./JsonWrapper";
|
||||
import React, {
|
||||
type MutableRefObject,
|
||||
useCallback,
|
||||
useMemo,
|
||||
useRef,
|
||||
} from "react";
|
||||
import { useEventCallback } from "usehooks-ts";
|
||||
import { componentWillAppendToBody } from "react-append-to-body";
|
||||
import _, { debounce } from "lodash";
|
||||
import { debounce } from "lodash";
|
||||
import { zIndexLayers } from "constants/CanvasEditorConstants";
|
||||
import { objectCollapseAnalytics, textSelectAnalytics } from "./Analytics";
|
||||
import { Divider } from "@appsmith/ads";
|
||||
import { useSelector } from "react-redux";
|
||||
import { getConfigTree, getDataTree } from "selectors/dataTreeSelectors";
|
||||
import { filterInternalProperties } from "utils/FilterInternalProperties";
|
||||
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 {
|
||||
objectName: string;
|
||||
|
|
@ -31,155 +35,83 @@ export const PeekOverlayPopUp = componentWillAppendToBody(
|
|||
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(
|
||||
props: PeekOverlayStateProps & {
|
||||
hidePeekOverlay: () => void;
|
||||
},
|
||||
) {
|
||||
const CONTAINER_MAX_HEIGHT_PX = 252;
|
||||
const { hidePeekOverlay, objectName, position, propertyPath } = props;
|
||||
const dataWrapperRef: MutableRefObject<HTMLDivElement | null> = useRef(null);
|
||||
const dataTree = useSelector(getDataTree);
|
||||
const configTree = useSelector(getConfigTree);
|
||||
const jsActions = useSelector(getJSCollections);
|
||||
|
||||
const filteredData = filterInternalProperties(
|
||||
props.objectName,
|
||||
dataTree[props.objectName],
|
||||
objectName,
|
||||
dataTree[objectName],
|
||||
jsActions,
|
||||
dataTree,
|
||||
configTree,
|
||||
);
|
||||
|
||||
// Because getPropertyData can return a function
|
||||
// And we don't want to execute it.
|
||||
const [jsData] = useState(() =>
|
||||
getPropertyData(filteredData, props.propertyPath),
|
||||
const [jsData, dataType] = useMemo(
|
||||
// Because getPropertyData can return a function
|
||||
// And we don't want to execute it.
|
||||
() => {
|
||||
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 wheelCallback = () => {
|
||||
props.hidePeekOverlay();
|
||||
const getPositionValues = useCallback(() => {
|
||||
const positionValues: { $left: string; $bottom?: string; $top?: string } = {
|
||||
// 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 () => {
|
||||
window.removeEventListener("wheel", wheelCallback);
|
||||
};
|
||||
}, []);
|
||||
return positionValues;
|
||||
}, [position]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!dataWrapperRef.current) return;
|
||||
|
||||
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;
|
||||
};
|
||||
const onWheel = useEventCallback((ev: React.WheelEvent) => {
|
||||
ev.stopPropagation();
|
||||
hidePeekOverlay();
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
<Styled.PeekOverlayContainer
|
||||
className={`absolute ${zIndexLayers.PEEK_OVERLAY}`}
|
||||
id="t--peek-overlay-container"
|
||||
onMouseEnter={() => debouncedHide.cancel()}
|
||||
onMouseLeave={() => debouncedHide()}
|
||||
onWheel={(ev) => ev.stopPropagation()}
|
||||
style={{
|
||||
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`,
|
||||
}),
|
||||
}}
|
||||
onMouseEnter={debouncedHide.cancel}
|
||||
onMouseLeave={debouncedHide}
|
||||
onWheel={onWheel}
|
||||
{...getPositionValues()}
|
||||
>
|
||||
<div
|
||||
className="first-letter:uppercase"
|
||||
style={{
|
||||
height: "24px",
|
||||
color: "var(--appsmith-color-black-700)",
|
||||
padding: "4px 0px 4px 12px",
|
||||
fontSize: "10px",
|
||||
}}
|
||||
>
|
||||
<Styled.DataType className="first-letter:uppercase">
|
||||
{dataType}
|
||||
</div>
|
||||
<Divider style={{ display: "block" }} />
|
||||
<div
|
||||
id="t--peek-overlay-data"
|
||||
ref={dataWrapperRef}
|
||||
style={{
|
||||
minHeight: "20px",
|
||||
padding: "2px 0px 2px 12px",
|
||||
fontSize: "10px",
|
||||
}}
|
||||
>
|
||||
</Styled.DataType>
|
||||
<Styled.BlockDivider />
|
||||
<Styled.PeekOverlayData id="t--peek-overlay-data" ref={dataWrapperRef}>
|
||||
{(dataType === "object" || dataType === "array") && jsData !== null && (
|
||||
<JsonWrapper
|
||||
className="as-mask"
|
||||
onClick={objectCollapseAnalytics}
|
||||
style={{
|
||||
minHeight: "20px",
|
||||
maxHeight: "225px",
|
||||
overflowY: "auto",
|
||||
}}
|
||||
>
|
||||
<ReactJson src={jsData} {...reactJsonProps} />
|
||||
</JsonWrapper>
|
||||
<Styled.JsonWrapper className="as-mask">
|
||||
<JSONViewer size={Size.SMALL} src={jsData} />
|
||||
</Styled.JsonWrapper>
|
||||
)}
|
||||
{/* TODO: Fix this the next time the file is edited */}
|
||||
{/* eslint-disable-next-line @typescript-eslint/no-explicit-any */}
|
||||
{dataType === "function" && <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 === "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 === "function" && <div>{jsData.toString()}</div>}
|
||||
{dataType === "boolean" && <div>{jsData.toString()}</div>}
|
||||
{dataType === "string" && <div>{jsData.toString()}</div>}
|
||||
{dataType === "number" && <div>{jsData.toString()}</div>}
|
||||
{((dataType !== "object" &&
|
||||
dataType !== "function" &&
|
||||
dataType !== "boolean" &&
|
||||
|
|
@ -188,14 +120,12 @@ export function PeekOverlayPopUpContent(
|
|||
dataType !== "number") ||
|
||||
jsData === null) && (
|
||||
<div>
|
||||
{/* TODO: Fix this the next time the file is edited */}
|
||||
{/* eslint-disable-next-line @typescript-eslint/no-explicit-any */}
|
||||
{(jsData as any)?.toString() ?? jsData ?? jsData === undefined
|
||||
{jsData?.toString() ?? jsData ?? jsData === undefined
|
||||
? "undefined"
|
||||
: "null"}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Styled.PeekOverlayData>
|
||||
</Styled.PeekOverlayContainer>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
export const CONTAINER_MAX_HEIGHT_PX = 252;
|
||||
|
||||
export const PEEK_OVERLAY_DELAY = 200;
|
||||
|
|
@ -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;
|
||||
`;
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -128,10 +128,7 @@ import { getEntitiesForNavigation } from "selectors/navigationSelectors";
|
|||
import history, { NavigationMethod } from "utils/history";
|
||||
import { CursorPositionOrigin } from "ee/reducers/uiReducers/editorContextReducer";
|
||||
import type { PeekOverlayStateProps } from "./PeekOverlayPopup/PeekOverlayPopup";
|
||||
import {
|
||||
PeekOverlayPopUp,
|
||||
PEEK_OVERLAY_DELAY,
|
||||
} from "./PeekOverlayPopup/PeekOverlayPopup";
|
||||
import { PeekOverlayPopUp } from "./PeekOverlayPopup/PeekOverlayPopup";
|
||||
import ConfigTreeActions from "utils/configTree";
|
||||
import {
|
||||
getSaveAndAutoIndentKey,
|
||||
|
|
@ -164,6 +161,7 @@ import CodeMirrorTernService from "utils/autocomplete/CodemirrorTernService";
|
|||
import { getEachEntityInformation } from "ee/utils/autocomplete/EntityDefinitions";
|
||||
import { getCurrentPageId } from "selectors/editorSelectors";
|
||||
import { executeCommandAction } from "actions/pluginActionActions";
|
||||
import { PEEK_OVERLAY_DELAY } from "./PeekOverlayPopup/constants";
|
||||
|
||||
type ReduxStateProps = ReturnType<typeof mapStateToProps>;
|
||||
type ReduxDispatchProps = ReturnType<typeof mapDispatchToProps>;
|
||||
|
|
@ -202,6 +200,7 @@ export interface EditorStyleProps {
|
|||
popperZIndex?: Indices;
|
||||
blockCompletions?: Array<BlockCompletion>;
|
||||
}
|
||||
|
||||
/**
|
||||
* line => Line to which the gutter is added
|
||||
*
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import "@testing-library/jest-dom";
|
|||
import { StateInspector } from "./StateInspector";
|
||||
import { useStateInspectorItems } from "./hooks";
|
||||
import { filterEntityGroupsBySearchTerm } from "IDE/utils";
|
||||
import { lightTheme } from "selectors/themeSelectors";
|
||||
import { ThemeProvider } from "styled-components";
|
||||
|
||||
jest.mock("./hooks");
|
||||
jest.mock("IDE/utils");
|
||||
|
|
@ -31,7 +33,11 @@ describe("StateInspector", () => {
|
|||
],
|
||||
{ key: "value1" },
|
||||
]);
|
||||
render(<StateInspector />);
|
||||
render(
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<StateInspector />
|
||||
</ThemeProvider>,
|
||||
);
|
||||
const searchInput = screen.getByPlaceholderText("Search entities");
|
||||
|
||||
fireEvent.change(searchInput, { target: { value: "Group 1" } });
|
||||
|
|
@ -53,7 +59,11 @@ describe("StateInspector", () => {
|
|||
],
|
||||
{ key: "value1" },
|
||||
]);
|
||||
render(<StateInspector />);
|
||||
render(
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<StateInspector />
|
||||
</ThemeProvider>,
|
||||
);
|
||||
fireEvent.click(screen.getByText("Item 2"));
|
||||
|
||||
expect(mockOnClick).toHaveBeenCalled();
|
||||
|
|
@ -71,7 +81,11 @@ describe("StateInspector", () => {
|
|||
],
|
||||
{ key: "Value1" },
|
||||
]);
|
||||
render(<StateInspector />);
|
||||
render(
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<StateInspector />
|
||||
</ThemeProvider>,
|
||||
);
|
||||
|
||||
expect(
|
||||
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", () => {
|
||||
mockedUseStateInspectorItems.mockReturnValue([null, [], null]);
|
||||
render(<StateInspector />);
|
||||
render(
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<StateInspector />
|
||||
</ThemeProvider>,
|
||||
);
|
||||
expect(screen.queryByText("Item 1")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
|
|
@ -101,12 +119,20 @@ describe("StateInspector", () => {
|
|||
{ key: "value1" },
|
||||
]);
|
||||
|
||||
render(<StateInspector />);
|
||||
render(
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<StateInspector />
|
||||
</ThemeProvider>,
|
||||
);
|
||||
expect(screen.getByText("Group 1")).toBeInTheDocument();
|
||||
expect(screen.getByText("Group 2")).toBeInTheDocument();
|
||||
});
|
||||
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");
|
||||
|
||||
fireEvent.change(searchInput, { target: { value: "Nonexistent Group" } });
|
||||
|
|
@ -116,7 +142,11 @@ describe("StateInspector", () => {
|
|||
|
||||
it("renders no items when items list is empty", () => {
|
||||
mockedUseStateInspectorItems.mockReturnValue([null, [], null]);
|
||||
render(<StateInspector />);
|
||||
render(
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<StateInspector />
|
||||
</ThemeProvider>,
|
||||
);
|
||||
expect(screen.queryByText("Group 1")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("Group 2")).not.toBeInTheDocument();
|
||||
});
|
||||
|
|
@ -130,7 +160,11 @@ describe("StateInspector", () => {
|
|||
],
|
||||
{},
|
||||
]);
|
||||
render(<StateInspector />);
|
||||
render(
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<StateInspector />
|
||||
</ThemeProvider>,
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.getByTestId("t--selected-entity-details").textContent,
|
||||
|
|
|
|||
|
|
@ -1,29 +1,21 @@
|
|||
import React, { useState } from "react";
|
||||
import ReactJson from "react-json-view";
|
||||
import {
|
||||
EntityGroupsList,
|
||||
Flex,
|
||||
type FlexProps,
|
||||
type ListItemProps,
|
||||
SearchInput,
|
||||
Text,
|
||||
} from "@appsmith/ads";
|
||||
import { JSONViewer, Size } from "components/editorComponents/JSONViewer";
|
||||
import { filterEntityGroupsBySearchTerm } from "IDE/utils";
|
||||
import { useStateInspectorItems } from "./hooks";
|
||||
import * as Styled from "./styles";
|
||||
|
||||
export const reactJsonProps = {
|
||||
name: null,
|
||||
enableClipboard: false,
|
||||
displayDataTypes: false,
|
||||
displayArrayKey: true,
|
||||
quotesOnKeys: false,
|
||||
style: {
|
||||
fontSize: "12px",
|
||||
},
|
||||
collapsed: 1,
|
||||
indentWidth: 2,
|
||||
collapseStringsAfterLength: 30,
|
||||
};
|
||||
const GroupListPadding = {
|
||||
pl: "spaces-3",
|
||||
pr: "spaces-3",
|
||||
} as FlexProps;
|
||||
|
||||
export const StateInspector = () => {
|
||||
const [selectedItem, items, selectedItemCode] = useStateInspectorItems();
|
||||
|
|
@ -51,10 +43,7 @@ export const StateInspector = () => {
|
|||
/>
|
||||
</Flex>
|
||||
<EntityGroupsList
|
||||
flexProps={{
|
||||
pl: "spaces-3",
|
||||
pr: "spaces-3",
|
||||
}}
|
||||
flexProps={GroupListPadding}
|
||||
groups={filteredItemGroups.map((item) => {
|
||||
return {
|
||||
groupTitle: item.group,
|
||||
|
|
@ -81,8 +70,8 @@ export const StateInspector = () => {
|
|||
{selectedItem.icon}
|
||||
<Text kind="body-m">{selectedItem.title}</Text>
|
||||
</Styled.SelectedItem>
|
||||
<Flex overflowY="auto" px="spaces-3">
|
||||
<ReactJson src={selectedItemCode} {...reactJsonProps} />
|
||||
<Flex className="as-mask" overflowY="auto" px="spaces-3">
|
||||
<JSONViewer size={Size.MEDIUM} src={selectedItemCode} />
|
||||
</Flex>
|
||||
</Flex>
|
||||
) : null}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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,
|
||||
};
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
export { JSONViewer } from "./JSONViewer";
|
||||
export { Size } from "./types";
|
||||
export type { JSONViewerProps } from "./types";
|
||||
|
|
@ -1,33 +1,24 @@
|
|||
import styled from "styled-components";
|
||||
import styled, { css } from "styled-components";
|
||||
|
||||
export const reactJsonProps = {
|
||||
name: null,
|
||||
enableClipboard: false,
|
||||
displayDataTypes: false,
|
||||
displayArrayKey: true,
|
||||
quotesOnKeys: false,
|
||||
style: {
|
||||
fontSize: "10px",
|
||||
},
|
||||
collapsed: 1,
|
||||
indentWidth: 2,
|
||||
collapseStringsAfterLength: 30,
|
||||
};
|
||||
const ReactJSONViewerOverrider = css<{ $fontSize: string; $iconSize: string }>`
|
||||
font-size: ${({ $fontSize }) => $fontSize} !important;
|
||||
|
||||
export const JsonWrapper = styled.div`
|
||||
// all ellipsis font size
|
||||
|
||||
.node-ellipsis,
|
||||
.function-collapsed span:nth-child(2),
|
||||
.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
|
||||
> .object-content:first-of-type
|
||||
> .object-key-val:first-of-type
|
||||
> span {
|
||||
pointer-events: none !important;
|
||||
|
||||
.icon-container {
|
||||
display: none !important;
|
||||
}
|
||||
|
|
@ -38,28 +29,42 @@ export const JsonWrapper = styled.div`
|
|||
}
|
||||
|
||||
// collapse icon color change and alignment
|
||||
|
||||
.icon-container {
|
||||
width: 10px !important;
|
||||
height: 8px !important;
|
||||
width: ${({ $iconSize }) => $iconSize} !important;
|
||||
height: ${({ $iconSize }) => $iconSize} !important;
|
||||
|
||||
.expanded-icon {
|
||||
svg {
|
||||
vertical-align: middle !important;
|
||||
padding-left: 0px !important;
|
||||
width: 0.8em !important;
|
||||
}
|
||||
}
|
||||
|
||||
svg {
|
||||
color: var(--appsmith-color-black-600) !important;
|
||||
}
|
||||
}
|
||||
|
||||
// font-sizes and alignments
|
||||
|
||||
.pushed-content.object-container {
|
||||
.object-content {
|
||||
padding-left: 4px !important;
|
||||
|
||||
.variable-row {
|
||||
padding-top: 0 !important;
|
||||
padding-bottom: 0 !important;
|
||||
border-left: 0 !important;
|
||||
|
||||
.variable-value div {
|
||||
font-size: 10px !important;
|
||||
font-size: ${({ $fontSize }) => $fontSize} !important;
|
||||
padding-top: 0 !important;
|
||||
padding-bottom: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.object-key-val {
|
||||
padding-top: 0 !important;
|
||||
padding-bottom: 0 !important;
|
||||
|
|
@ -70,25 +75,35 @@ export const JsonWrapper = styled.div`
|
|||
}
|
||||
|
||||
// disabling function collapse and neutral styling
|
||||
|
||||
.rjv-function-container {
|
||||
pointer-events: none;
|
||||
font-weight: normal !important;
|
||||
|
||||
> span:first-child:before {
|
||||
// In prod build, for some reason react-json-viewer
|
||||
// misses adding this opening braces for function
|
||||
content: "(";
|
||||
}
|
||||
|
||||
.function-collapsed {
|
||||
font-weight: normal !important;
|
||||
|
||||
span:nth-child(1) {
|
||||
display: none; // hiding extra braces
|
||||
}
|
||||
|
||||
span:nth-child(2) {
|
||||
color: #393939 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div:has(.rjv-function-container) {
|
||||
cursor: default !important;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Container = styled.div<{ $fontSize: string; $iconSize: string }>`
|
||||
${ReactJSONViewerOverrider}
|
||||
`;
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
export enum Size {
|
||||
SMALL = "small",
|
||||
MEDIUM = "medium",
|
||||
}
|
||||
|
||||
export interface JSONViewerProps {
|
||||
src: unknown;
|
||||
size: Size;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user