From de5b773ddf21911e941392e4459d71f58e16fa15 Mon Sep 17 00:00:00 2001 From: Sashank Aryal <66688606+sashankaryal@users.noreply.github.com> Date: Wed, 17 Apr 2024 10:35:06 -0500 Subject: [PATCH] Refactor json viewer (#4278) * remove unused jsonview file * remove dep on searchable-json-view and bring in new json viewer * add new json viewer with highlighting * handle esc to clear search term and close json view * linting --------- Co-authored-by: Benjamin Kane --- app/packages/components/package.json | 1 + .../src/components/JSONPanel/JSONPanel.tsx | 72 ++++--- .../src/components/JSONPanel/highlight.tsx | 124 ++++++++++++ .../src/components/JSONPanel/json.module.css | 2 +- .../src/components/JSONPanel/panel.module.css | 2 +- app/packages/core/package.json | 1 - .../plugins/SchemaIO/components/JSONView.tsx | 55 ------ .../src/plugins/SchemaIO/components/index.ts | 1 - app/yarn.lock | 182 ++---------------- 9 files changed, 196 insertions(+), 244 deletions(-) create mode 100644 app/packages/components/src/components/JSONPanel/highlight.tsx delete mode 100644 app/packages/core/src/plugins/SchemaIO/components/JSONView.tsx diff --git a/app/packages/components/package.json b/app/packages/components/package.json index 115a84a2c3..d7fac13c9d 100644 --- a/app/packages/components/package.json +++ b/app/packages/components/package.json @@ -42,6 +42,7 @@ "@mui/icons-material": "^5.10.2", "@mui/material": "^5.9.0", "@react-spring/web": "^9.7.3", + "@textea/json-viewer": "^3.4.1", "classnames": "^2.3.1", "framer-motion": "^6.2.7", "path-to-regexp": "^6.2.0", diff --git a/app/packages/components/src/components/JSONPanel/JSONPanel.tsx b/app/packages/components/src/components/JSONPanel/JSONPanel.tsx index c922b9461e..7ed878f486 100644 --- a/app/packages/components/src/components/JSONPanel/JSONPanel.tsx +++ b/app/packages/components/src/components/JSONPanel/JSONPanel.tsx @@ -1,25 +1,25 @@ /** * Copyright 2017-2024, Voxel51, Inc. */ -import { useState } from "react"; -import { Copy as CopyIcon, Close as CloseIcon } from "@fiftyone/components"; +import { Close as CloseIcon, Copy as CopyIcon } from "@fiftyone/components"; import CloseRoundedIcon from "@mui/icons-material/CloseRounded"; +import { useColorScheme } from "@mui/material"; +import { JsonViewer } from "@textea/json-viewer"; +import React, { useEffect, useMemo, useState } from "react"; +import { KeyRendererWrapper, getValueRenderersForSearch } from "./highlight"; +import { + lookerCloseJSON, + lookerCopyJSON, + lookerJSONPanel, +} from "./json.module.css"; import { lookerPanel, lookerPanelContainer, lookerPanelVerticalContainer, + searchCloseIcon, searchContainer, searchInput, - copyBtnClass, - searchCloseIcon, } from "./panel.module.css"; -import { - lookerCopyJSON, - lookerCloseJSON, - lookerJSONPanel, -} from "./json.module.css"; -import ReactJson from "searchable-react-json-view"; -import { useColorScheme } from "@mui/material"; export default function JSONPanel({ containerRef, onClose, onCopy, json }) { const parsed = JSON.parse(json); @@ -27,6 +27,34 @@ export default function JSONPanel({ containerRef, onClose, onCopy, json }) { const isDarkMode = mode === "dark"; const [searchTerm, setSearchTerm] = useState(""); + useEffect(() => { + const handleEsc = (e: KeyboardEvent) => { + if (e.key === "Escape") { + if (searchTerm) { + setSearchTerm(""); + return; + } + + onClose(); + } + }; + + window.addEventListener("keydown", handleEsc); + return () => { + window.removeEventListener("keydown", handleEsc); + }; + }, [searchTerm, onClose]); + + const keyRenderer = useMemo( + () => KeyRendererWrapper(searchTerm), + [searchTerm] + ); + + const valuesRenderer = useMemo( + () => getValueRenderersForSearch(searchTerm), + [searchTerm] + ); + return (
)}
- } - customCopiedIcon={ - - } /> )} diff --git a/app/packages/components/src/components/JSONPanel/highlight.tsx b/app/packages/components/src/components/JSONPanel/highlight.tsx new file mode 100644 index 0000000000..03036fbd49 --- /dev/null +++ b/app/packages/components/src/components/JSONPanel/highlight.tsx @@ -0,0 +1,124 @@ +import { DataItemProps, defineEasyType } from "@textea/json-viewer"; +import React, { useMemo } from "react"; + +const HighlightedText = ({ + text, + searchTerm, +}: { + text: string | number; + searchTerm: string; +}) => { + const parts = useMemo( + () => String(text ?? "").split(searchTerm), + [text, searchTerm] + ); + + if (!String(text).includes(searchTerm)) { + return <>{text}; + } + + return ( + <> + {parts.map((part, index) => ( + + {part} + {index !== parts.length - 1 && ( + + {searchTerm} + + )} + + ))} + + ); +}; + +export const KeyRenderer = (props: DataItemProps & { searchTerm: string }) => { + const { searchTerm, path } = props; + const leafPath = String(path.at(-1)); + + if (leafPath?.includes(searchTerm)) { + return ; + } + + return <>{leafPath}; +}; + +export const KeyRendererWrapper = (searchTerm: string) => { + const wrapper = (props: DataItemProps) => { + return ; + }; + + // show all keys (required in runtime by json-viewer) + wrapper.when = () => true; + return wrapper; +}; + +const getHighlightedComponentString = (searchTerm: string) => + defineEasyType({ + is: (value) => { + if (searchTerm?.length === 0 || typeof value !== "string") { + return false; + } + + return String(value).includes(searchTerm); + }, + type: "string", + colorKey: "base09", + Renderer: (props) => { + const value = props.value as string; + return ; + }, + }); + +const isInt = (n: number) => n % 1 === 0; + +const getHighlightedComponentFloat = (searchTerm: string) => + defineEasyType({ + is: (value) => { + if ( + searchTerm?.length === 0 || + typeof value !== "number" || + isInt(value) || + isNaN(value) + ) { + return false; + } + + return String(value).includes(searchTerm); + }, + type: "float", + colorKey: "base0B", + Renderer: (props) => { + const value = props.value as number; + return ; + }, + }); + +const getHighlightedComponentInt = (searchTerm: string) => + defineEasyType({ + is: (value) => { + if ( + searchTerm?.length === 0 || + typeof value !== "number" || + isNaN(value) || + !isInt(value) + ) { + return false; + } + + return String(value).includes(searchTerm); + }, + type: "int", + colorKey: "base0F", + Renderer: (props) => { + const value = props.value as number; + return ; + }, + }); + +export const getValueRenderersForSearch = (searchTerm: string) => [ + getHighlightedComponentString(searchTerm), + getHighlightedComponentFloat(searchTerm), + getHighlightedComponentInt(searchTerm), +]; diff --git a/app/packages/components/src/components/JSONPanel/json.module.css b/app/packages/components/src/components/JSONPanel/json.module.css index 9e4ecc8032..7f984f4937 100644 --- a/app/packages/components/src/components/JSONPanel/json.module.css +++ b/app/packages/components/src/components/JSONPanel/json.module.css @@ -1,5 +1,5 @@ /** - * Copyright 2017-2022, Voxel51, Inc. + * Copyright 2017-2024, Voxel51, Inc. */ .lookerJSONPanel { diff --git a/app/packages/components/src/components/JSONPanel/panel.module.css b/app/packages/components/src/components/JSONPanel/panel.module.css index 74a2dd5b10..46ab5da6fc 100644 --- a/app/packages/components/src/components/JSONPanel/panel.module.css +++ b/app/packages/components/src/components/JSONPanel/panel.module.css @@ -1,5 +1,5 @@ /** - * Copyright 2017-2022, Voxel51, Inc. + * Copyright 2017-2024, Voxel51, Inc. */ .lookerPanel { diff --git a/app/packages/core/package.json b/app/packages/core/package.json index 153044c80c..0177ae7b2c 100644 --- a/app/packages/core/package.json +++ b/app/packages/core/package.json @@ -46,7 +46,6 @@ "recoil-relay": "^0.1.0", "remark-gfm": "^3.0.1", "resize-observer-polyfill": "^1.5.1", - "searchable-react-json-view": "^0.0.8", "styled-components": "^6.1.8", "uuid": "^8.3.2", "xstate": "^4.14.0" diff --git a/app/packages/core/src/plugins/SchemaIO/components/JSONView.tsx b/app/packages/core/src/plugins/SchemaIO/components/JSONView.tsx deleted file mode 100644 index f548e7ce86..0000000000 --- a/app/packages/core/src/plugins/SchemaIO/components/JSONView.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { Copy as CopyIcon } from "@fiftyone/components"; -import { scrollbarStyles } from "@fiftyone/utilities"; -import { Box, SvgIconProps, useColorScheme } from "@mui/material"; -import React from "react"; -import ReactJSON from "searchable-react-json-view"; -import styled from "styled-components"; -import { HeaderView } from "."; -import { getComponentProps } from "../utils"; - -export default function JSONView(props) { - const { data } = props; - const { mode } = useColorScheme(); - const isDarkMode = mode === "dark"; - - return ( - - - - } - customCopiedIcon={ - theme.palette.primary.plainColor, - }} - /> - } - {...getComponentProps(props, "json")} - /> - - - ); -} - -const ReactJSONContainer = styled.div` - .react-json-view { - ${scrollbarStyles} - } -`; - -const copyIconStyles: SvgIconProps["sx"] = { - color: (theme) => theme.palette.text.tertiary, - fontSize: "1.25rem", - position: "absolute", -}; diff --git a/app/packages/core/src/plugins/SchemaIO/components/index.ts b/app/packages/core/src/plugins/SchemaIO/components/index.ts index 8aa9b12859..91dbcd1801 100644 --- a/app/packages/core/src/plugins/SchemaIO/components/index.ts +++ b/app/packages/core/src/plugins/SchemaIO/components/index.ts @@ -21,7 +21,6 @@ export { default as HelpTooltip } from "./HelpTooltip"; export { default as HiddenView } from "./HiddenView"; export { default as ImageView } from "./ImageView"; export { default as InferredView } from "./InferredView"; -export { default as JSONView } from "./JSONView"; export { default as KeyValueView } from "./KeyValueView"; export { default as LabelValueView } from "./LabelValueView"; export { default as LinkView } from "./LinkView"; diff --git a/app/yarn.lock b/app/yarn.lock index 4171529eb0..2da104089b 100644 --- a/app/yarn.lock +++ b/app/yarn.lock @@ -2390,6 +2390,7 @@ __metadata: "@mui/icons-material": ^5.10.2 "@mui/material": ^5.9.0 "@react-spring/web": ^9.7.3 + "@textea/json-viewer": ^3.4.1 "@types/react-input-autosize": ^2.2.1 "@types/react-syntax-highlighter": ^15.5.6 classnames: ^2.3.1 @@ -2466,7 +2467,6 @@ __metadata: remark-gfm: ^3.0.1 resize-observer-polyfill: ^1.5.1 rollup-plugin-polyfill-node: ^0.6.2 - searchable-react-json-view: ^0.0.8 styled-components: ^6.1.8 typescript: ^4.7.4 typescript-plugin-css-modules: ^5.0.2 @@ -4712,6 +4712,23 @@ __metadata: languageName: node linkType: hard +"@textea/json-viewer@npm:^3.4.1": + version: 3.4.1 + resolution: "@textea/json-viewer@npm:3.4.1" + dependencies: + clsx: ^2.1.0 + copy-to-clipboard: ^3.3.3 + zustand: ^4.5.2 + peerDependencies: + "@emotion/react": ^11 + "@emotion/styled": ^11 + "@mui/material": ^5 + react: ^17 || ^18 + react-dom: ^17 || ^18 + checksum: e70f34e7fd139e0ddfc7977ad3f791b535965cd915f529e2438d9b29968a0875d63200bb3c1c3bb09d5af6388ccea375c90990691f484ef631160aea242f6756 + languageName: node + linkType: hard + "@tootallnate/once@npm:2": version: 2.0.0 resolution: "@tootallnate/once@npm:2.0.0" @@ -6659,13 +6676,6 @@ __metadata: languageName: node linkType: hard -"base16@npm:^1.0.0": - version: 1.0.0 - resolution: "base16@npm:1.0.0" - checksum: 0cd449a2db0f0f957e4b6b57e33bc43c9e20d4f1dd744065db94b5da35e8e71fa4dc4bc7a901e59a84d5f8b6936e3c520e2471787f667fc155fb0f50d8540f5d - languageName: node - linkType: hard - "base64-arraybuffer@npm:^0.2.0": version: 0.2.0 resolution: "base64-arraybuffer@npm:0.2.0" @@ -7573,7 +7583,7 @@ __metadata: languageName: node linkType: hard -"copy-to-clipboard@npm:^3.3.1": +"copy-to-clipboard@npm:^3.3.1, copy-to-clipboard@npm:^3.3.3": version: 3.3.3 resolution: "copy-to-clipboard@npm:3.3.3" dependencies: @@ -7591,13 +7601,6 @@ __metadata: languageName: node linkType: hard -"core-js@npm:^1.0.0": - version: 1.2.7 - resolution: "core-js@npm:1.2.7" - checksum: 0b76371bfa98708351cde580f9287e2360d2209920e738ae950ae74ad08639a2e063541020bf666c28778956fc356ed9fe56d962129c88a87a6a4a0612526c75 - languageName: node - linkType: hard - "core-util-is@npm:1.0.2": version: 1.0.2 resolution: "core-util-is@npm:1.0.2" @@ -8682,7 +8685,7 @@ __metadata: languageName: node linkType: hard -"encoding@npm:^0.1.11, encoding@npm:^0.1.13": +"encoding@npm:^0.1.13": version: 0.1.13 resolution: "encoding@npm:0.1.13" dependencies: @@ -9825,15 +9828,6 @@ __metadata: languageName: node linkType: hard -"fbemitter@npm:^2.0.0": - version: 2.1.1 - resolution: "fbemitter@npm:2.1.1" - dependencies: - fbjs: ^0.8.4 - checksum: 28b3024456e320275d759d594f0b2099964f5c6289c768d856481e795db557c425bae9845ff382922eb32ec331da77fc3f6be3832a7e9fcdfa0f4fb1fc7f703c - languageName: node - linkType: hard - "fbjs-css-vars@npm:^1.0.0": version: 1.0.2 resolution: "fbjs-css-vars@npm:1.0.2" @@ -9841,21 +9835,6 @@ __metadata: languageName: node linkType: hard -"fbjs@npm:^0.8.0, fbjs@npm:^0.8.4": - version: 0.8.18 - resolution: "fbjs@npm:0.8.18" - dependencies: - core-js: ^1.0.0 - isomorphic-fetch: ^2.1.1 - loose-envify: ^1.0.0 - object-assign: ^4.1.0 - promise: ^7.1.1 - setimmediate: ^1.0.5 - ua-parser-js: ^0.7.30 - checksum: 668731b946a765908c9cbe51d5160f973abb78004b3d122587c3e930e3e1ddcc0ce2b17f2a8637dc9d733e149aa580f8d3035a35cc2d3bc78b78f1b19aab90e2 - languageName: node - linkType: hard - "fbjs@npm:^3.0.2": version: 3.0.5 resolution: "fbjs@npm:3.0.5" @@ -10020,18 +9999,6 @@ __metadata: languageName: node linkType: hard -"flux@npm:^3.1.3": - version: 3.1.3 - resolution: "flux@npm:3.1.3" - dependencies: - fbemitter: ^2.0.0 - fbjs: ^0.8.0 - peerDependencies: - react: ^15.0.2 || ^16.0.0-beta || ^16.0.0 - checksum: 8aa8b0d80cbdd6c751f52876573f934282a784c2382b32518fffac94f462d02a8c85d6c83ed266df6db4ebea58442ba9d8d7e5127797df6476944b1ab531560a - languageName: node - linkType: hard - "font-atlas@npm:^2.1.0": version: 2.1.0 resolution: "font-atlas@npm:2.1.0" @@ -11616,13 +11583,6 @@ __metadata: languageName: node linkType: hard -"is-stream@npm:^1.0.1": - version: 1.1.0 - resolution: "is-stream@npm:1.1.0" - checksum: 063c6bec9d5647aa6d42108d4c59723d2bd4ae42135a2d4db6eadbd49b7ea05b750fd69d279e5c7c45cf9da753ad2c00d8978be354d65aa9f6bb434969c6a2ae - languageName: node - linkType: hard - "is-stream@npm:^2.0.0": version: 2.0.1 resolution: "is-stream@npm:2.0.1" @@ -11776,16 +11736,6 @@ __metadata: languageName: node linkType: hard -"isomorphic-fetch@npm:^2.1.1": - version: 2.2.1 - resolution: "isomorphic-fetch@npm:2.2.1" - dependencies: - node-fetch: ^1.0.1 - whatwg-fetch: ">=0.10.0" - checksum: bb5daa7c3785d6742f4379a81e55b549a469503f7c9bf9411b48592e86632cf5e8fe8ea878dba185c0f33eb7c510c23abdeb55aebfdf5d3c70f031ced68c5424 - languageName: node - linkType: hard - "istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.0": version: 3.2.2 resolution: "istanbul-lib-coverage@npm:3.2.2" @@ -12873,13 +12823,6 @@ __metadata: languageName: node linkType: hard -"lodash.curry@npm:^4.0.1": - version: 4.1.1 - resolution: "lodash.curry@npm:4.1.1" - checksum: 9192b70fe7df4d1ff780c0260bee271afa9168c93fe4fa24bc861900240531b59781b5fdaadf4644fea8f4fbcd96f0700539ab294b579ffc1022c6c15dcc462a - languageName: node - linkType: hard - "lodash.debounce@npm:^4.0.0, lodash.debounce@npm:^4.0.8": version: 4.0.8 resolution: "lodash.debounce@npm:4.0.8" @@ -12887,13 +12830,6 @@ __metadata: languageName: node linkType: hard -"lodash.flow@npm:^3.3.0": - version: 3.5.0 - resolution: "lodash.flow@npm:3.5.0" - checksum: a9a62ad344e3c5a1f42bc121da20f64dd855aaafecee24b1db640f29b88bd165d81c37ff7e380a7191de6f70b26f5918abcebbee8396624f78f3618a0b18634c - languageName: node - linkType: hard - "lodash.isequal@npm:^4.5.0": version: 4.5.0 resolution: "lodash.isequal@npm:4.5.0" @@ -14139,16 +14075,6 @@ __metadata: languageName: node linkType: hard -"node-fetch@npm:^1.0.1": - version: 1.7.3 - resolution: "node-fetch@npm:1.7.3" - dependencies: - encoding: ^0.1.11 - is-stream: ^1.0.1 - checksum: 3bb0528c05d541316ebe52770d71ee25a6dce334df4231fd55df41a644143e07f068637488c18a5b0c43f05041dbd3346752f9e19b50df50569a802484544d5b - languageName: node - linkType: hard - "node-fetch@npm:^2.6.12": version: 2.7.0 resolution: "node-fetch@npm:2.7.0" @@ -15310,13 +15236,6 @@ __metadata: languageName: node linkType: hard -"pure-color@npm:^1.2.0": - version: 1.3.0 - resolution: "pure-color@npm:1.3.0" - checksum: 646d8bed6e6eab89affdd5e2c11f607a85b631a7fb03c061dfa658eb4dc4806881a15feed2ac5fd8c0bad8c00c632c640d5b1cb8b9a972e6e947393a1329371b - languageName: node - linkType: hard - "querystringify@npm:^2.1.1": version: 2.2.0 resolution: "querystringify@npm:2.2.0" @@ -15389,18 +15308,6 @@ __metadata: languageName: node linkType: hard -"react-base16-styling@npm:^0.6.0": - version: 0.6.0 - resolution: "react-base16-styling@npm:0.6.0" - dependencies: - base16: ^1.0.0 - lodash.curry: ^4.0.1 - lodash.flow: ^3.3.0 - pure-color: ^1.2.0 - checksum: 00a12dddafc8a9025cca933b0dcb65fca41c81fa176d1fc3a6a9d0242127042e2c0a604f4c724a3254dd2c6aeb5ef55095522ff22f5462e419641c1341a658e4 - languageName: node - linkType: hard - "react-color@npm:^2.19.3": version: 2.19.3 resolution: "react-color@npm:2.19.3" @@ -15559,13 +15466,6 @@ __metadata: languageName: node linkType: hard -"react-lifecycles-compat@npm:^3.0.4": - version: 3.0.4 - resolution: "react-lifecycles-compat@npm:3.0.4" - checksum: a904b0fc0a8eeb15a148c9feb7bc17cec7ef96e71188280061fc340043fd6d8ee3ff233381f0e8f95c1cf926210b2c4a31f38182c8f35ac55057e453d6df204f - languageName: node - linkType: hard - "react-map-gl@npm:^7.0.18": version: 7.1.7 resolution: "react-map-gl@npm:7.1.7" @@ -15709,17 +15609,6 @@ __metadata: languageName: node linkType: hard -"react-textarea-autosize@npm:^6.1.0": - version: 6.1.0 - resolution: "react-textarea-autosize@npm:6.1.0" - dependencies: - prop-types: ^15.6.0 - peerDependencies: - react: ">=0.14.0 <17.0.0" - checksum: 931721953f6a61baca6889ff9a41741a77d15f6514ad6326466f2ad11a89327e0edbbf82330b90c202d92b6d939707ac7ff8261cf7280022b3067fc2b5b187c2 - languageName: node - linkType: hard - "react-transition-group@npm:^4.4.0, react-transition-group@npm:^4.4.5": version: 4.4.5 resolution: "react-transition-group@npm:4.4.5" @@ -16653,21 +16542,6 @@ __metadata: languageName: node linkType: hard -"searchable-react-json-view@npm:^0.0.8": - version: 0.0.8 - resolution: "searchable-react-json-view@npm:0.0.8" - dependencies: - flux: ^3.1.3 - react-base16-styling: ^0.6.0 - react-lifecycles-compat: ^3.0.4 - react-textarea-autosize: ^6.1.0 - peerDependencies: - react: ^16.0.0 || ^15.5.4 - react-dom: ^16.0.0 || ^15.5.4 - checksum: 42b374db16e9916ae3ead259145653d27762c92f0187277fe8a9b5ef2f54532f0e7d4e54708c92166e0a0ba2bcd58256e623e6074b4ba5b415b2a5c433036f01 - languageName: node - linkType: hard - "semver-compare@npm:^1.0.0": version: 1.0.0 resolution: "semver-compare@npm:1.0.0" @@ -18306,13 +18180,6 @@ __metadata: languageName: node linkType: hard -"ua-parser-js@npm:^0.7.30": - version: 0.7.37 - resolution: "ua-parser-js@npm:0.7.37" - checksum: 9e91a66171aa16c74680cfac84af6ed7ecdeb508ff7c90a55222f56c63172da2d98d2478763e9469c940415fe29c45a56ae51fec1c19a498e7a3b293f7b3b874 - languageName: node - linkType: hard - "ua-parser-js@npm:^1.0.35": version: 1.0.37 resolution: "ua-parser-js@npm:1.0.37" @@ -19118,13 +18985,6 @@ __metadata: languageName: node linkType: hard -"whatwg-fetch@npm:>=0.10.0": - version: 3.6.20 - resolution: "whatwg-fetch@npm:3.6.20" - checksum: c58851ea2c4efe5c2235f13450f426824cf0253c1d45da28f45900290ae602a20aff2ab43346f16ec58917d5562e159cd691efa368354b2e82918c2146a519c5 - languageName: node - linkType: hard - "whatwg-mimetype@npm:^3.0.0": version: 3.0.0 resolution: "whatwg-mimetype@npm:3.0.0" @@ -19453,7 +19313,7 @@ __metadata: languageName: node linkType: hard -"zustand@npm:^4.3.2": +"zustand@npm:^4.3.2, zustand@npm:^4.5.2": version: 4.5.2 resolution: "zustand@npm:4.5.2" dependencies: