Skip to content
Snippets Groups Projects
json_viewer.view.tsx 5.67 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 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
    }
    
    export const JsonViewer = ({ json }: 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 breadcrumbHTML = useMemo(() => {
            return (
                <nav aria-label="breadcrumb">
                    <ol className="breadcrumb">
                        {breadcrumbs.map(breadcrumb => (
                            <li key={breadcrumb} className="breadcrumb-item"><a href="#">{breadcrumb}</a></li>
                        ))}
                    </ol>
                </nav>
            )
        }, [breadcrumbs])
    
    
        const insertMarkTags = (text: string, search: string): string => {
            const start = text.indexOf(search)
            const end = start + search.length
    
            return DOMPurify.sanitize(text.substring(0, start)) + "<span class='highlight'>" + DOMPurify.sanitize(search) + "</span>" + DOMPurify.sanitize(text.substring(end, text.length))
        }
    
        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>
    
    matthiasf's avatar
    matthiasf committed
                            <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="list-group-tr">
                    <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 = () => {
            return (
                <>
                    <Form.Group controlId='device.search' className='p-0 mx-1 pt-2'>
                        <Form.Control type="text" placeholder={t('device.search.placeholder')} ref={search} />
                    </Form.Group>
                </>
            )
        }
    
    matthiasf's avatar
    matthiasf committed
            <div ref={htmlContainer}>
    
    matthiasf's avatar
    matthiasf committed
                {searchHTML()}
    
                {breadcrumbHTML}
                {hierarchyHTML}
            </div>
        )
    }