Newer
Older
import { faAlignRight, faPenToSquare, faTrashCan } from "@fortawesome/free-solid-svg-icons"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import { insertMarkTags } from "@helper/text"
import React, { Suspense, useMemo, useRef } from "react"
import { Form, Table } from "react-bootstrap"
import { useJsonViewer } from "../viewmodel/json_viewer.viewmodel"
import './json_viewer.scss'
type JsonViewerProbs = {
json: JSON,
options?: {
searchEnabled?: boolean
editable?: boolean
}
export const JsonViewer = ({ json, options = { searchEnabled: true, editable: true } }: JsonViewerProbs) => {
const htmlContainer = useRef(null);
const search = useRef<HTMLInputElement>(null);
const { getSubset, breadcrumbs, isCollapsed, collapseable, collapse, parameterizedJson, searchTerm } = useJsonViewer({ json, search, container: htmlContainer });
const renderInner = (innerJson: JSON, nested: number = 0, parentKey: string = "", path: string = "/network-instance/0/"): JSX.Element => {
path += parentKey + (parentKey === "" ? "" : "/")
return Object.entries(innerJson).map(([key, child]): JSX.Element => {
let collapsed = isCollapsed(key, nested);
// display only keys and values that matches
const foundPaths = parameterizedJson.current.filter(_path => _path === path)
//collapsed = !collapsed ? !!foundPaths.length : collapsed
collapsed = !!foundPaths.length
const isObject = child instanceof Object;
let readableValue: string = isObject ? '' : DOMPurify.sanitize(child);
if (searchTerm !== "" && readableValue.includes(searchTerm)) {
readableValue = insertMarkTags(readableValue, searchTerm)
}
const icon = isObject ?
<span className={collapsed ? 'fa-rotate-90' : ''}>></span> : <FontAwesomeIcon className="icon fa-rotate-180" icon={faAlignRight} size="xs" />
// determine the margin-left: n indent
let tabs = 0.0;
for (let i = 0; i < nested; i++) {
tabs += 0.4;
}
let concatenatedKey = key
let innerChild = child
while (innerChild.length === 1) {
const innerKey = Object.keys(innerChild)[0]
concatenatedKey += '/' + innerKey
innerChild = innerChild[innerKey]
}
concatenatedKey = DOMPurify.sanitize(concatenatedKey)
if (searchTerm !== "" && concatenatedKey.includes(searchTerm)) {
concatenatedKey = insertMarkTags(concatenatedKey, searchTerm)
}
return (
<React.Fragment key={`${nested}-${key}`}>
<tr
className={"list-item-td " + key + " " + nested + " " + (isObject ? 'object' : '')}
data-copy-value={readableValue}
onClick={() => { isObject ? collapse(key, nested, child) : null }}
<td style={{ marginLeft: tabs + 'em' }} className={"d-flex align-items-center "}>{icon}<span> <span dangerouslySetInnerHTML={{ __html: concatenatedKey }} /></span></td>
<td className="text-element text-truncate" dangerouslySetInnerHTML={{ __html: readableValue }}></td>
{options?.editable &&
<td className="text-end">
<div className="d-flex icons justify-content-end align-items-center">
<FontAwesomeIcon icon={faPenToSquare} size="sm" />
<FontAwesomeIcon icon={faTrashCan} size="sm" />
</div>
</td>
}
{isObject && collapsed && renderInner(innerChild, nested + 1, concatenatedKey, path)}
</React.Fragment >
)
})
}
const renderJson = (json: JSON): JSX.Element => {
return (
<Table className="m-0 p-0 list-unstyled">
<tbody>
{
renderInner(json)
}
</tbody>
</Table >
)
}
const hierarchyHTML = useMemo(() => {
const subset = getSubset(json);
return (
<>
<Suspense fallback={<div>loading...</div>}>
{renderJson(subset)}
</Suspense>
</>
)
const searchHTML = (): React.ReactElement => {
<Form.Group controlId='json_viewer.search' className='p-0 '>
<Form.Control type="text" placeholder={t('device.search.placeholder')} ref={search} />
</Form.Group>
{options?.searchEnabled && searchHTML()}