Skip to content
Snippets Groups Projects
json_viewer.view.tsx 5.18 KiB
Newer Older
  • Learn to ignore specific revisions
  • matthiasf's avatar
    matthiasf committed
    import { faAlignRight, faPenToSquare, faTrashCan } from "@fortawesome/free-solid-svg-icons"
    
    import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
    
    import { insertMarkTags } from "@helper/text"
    
    import DOMPurify from 'dompurify'
    
    matthiasf's avatar
    matthiasf committed
    import React, { Suspense, useMemo, useRef } from "react"
    import { Form, Table } from "react-bootstrap"
    
    matthiasf's avatar
    matthiasf committed
    import { useTranslation } from "react-i18next"
    
    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) => {
    
    matthiasf's avatar
    matthiasf committed
        const { t } = useTranslation('common');
    
    matthiasf's avatar
    matthiasf committed
        const htmlContainer = useRef(null);
        const search = useRef<HTMLInputElement>(null);
    
    matthiasf's avatar
    matthiasf committed
    
    
    matthiasf's avatar
    matthiasf committed
        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
    
    matthiasf's avatar
    matthiasf committed
                if (searchTerm !== "") {
    
                    const foundPaths = parameterizedJson.current.filter(_path => _path === path)
    
    matthiasf's avatar
    matthiasf committed
    
    
                    //collapsed = !collapsed ? !!foundPaths.length : collapsed
                    collapsed = !!foundPaths.length
    
    matthiasf's avatar
    matthiasf committed
                }
    
    
                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' : ''}>&gt;</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}`}>
    
    matthiasf's avatar
    matthiasf committed
                        <tr
                            className={"list-item-td " + key + " " + nested + " " + (isObject ? 'object' : '')}
                            data-copy-value={readableValue}
    
                            onClick={() => { isObject ? collapse(key, nested, child) : null }}
    
    matthiasf's avatar
    matthiasf committed
                        >
    
                            <td style={{ marginLeft: tabs + 'em' }} className={"d-flex align-items-center "}>{icon}<span>&ensp;<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>
                </>
            )
    
    matthiasf's avatar
    matthiasf committed
        }, [json, collapseable, searchTerm])
    
    
        const searchHTML = (): React.ReactElement => {
    
    matthiasf's avatar
    matthiasf committed
            return (
    
                <Form.Group controlId='json_viewer.search' className='p-0 '>
    
                    <Form.Control type="text" placeholder={t('device.search.placeholder')} ref={search} />
                </Form.Group>
    
    matthiasf's avatar
    matthiasf committed
            )
        }
    
    matthiasf's avatar
    matthiasf committed
            <div ref={htmlContainer}>
    
                {options?.searchEnabled && searchHTML()}
    
                {hierarchyHTML}
            </div>
        )
    }