Skip to content
Snippets Groups Projects
Commit 543cd2e4 authored by matthiasf's avatar matthiasf
Browse files

wip_search_complete

parent 3b38b5c0
Branches
No related tags found
2 merge requests!1162Draft: Ui integration,!1128UI: Implement yang model view
...@@ -16,12 +16,6 @@ import { router } from './routes' ...@@ -16,12 +16,6 @@ import { router } from './routes'
import './shared/icons/icons' import './shared/icons/icons'
import { persistor, store } from './stores' import { persistor, store } from './stores'
const installToastify = () => {
return (
<ToastContainer />
)
};
ReactDOM.createRoot(document.getElementById("root")).render( ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode> <React.StrictMode>
<Provider store={store}> <Provider store={store}>
...@@ -29,7 +23,7 @@ ReactDOM.createRoot(document.getElementById("root")).render( ...@@ -29,7 +23,7 @@ ReactDOM.createRoot(document.getElementById("root")).render(
<I18nextProvider i18n={i18next}> <I18nextProvider i18n={i18next}>
<UtilsProvider> <UtilsProvider>
<MenuProvider> <MenuProvider>
{installToastify()} <ToastContainer />
<RouterProvider router={router} /> <RouterProvider router={router} />
</MenuProvider> </MenuProvider>
</UtilsProvider> </UtilsProvider>
......
...@@ -34,6 +34,10 @@ ...@@ -34,6 +34,10 @@
padding-top: 0 !important; padding-top: 0 !important;
padding-right: 5px !important; padding-right: 5px !important;
} }
& > .text-element {
max-width: 100px;
}
} }
.list-item-td.object { .list-item-td.object {
......
import { faAlignRight, faCopy, faPenToSquare, faTrashCan } from "@fortawesome/free-solid-svg-icons" import { faAlignRight, faPenToSquare, faTrashCan } from "@fortawesome/free-solid-svg-icons"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import { useMenu } from "@provider/menu/menu.provider" import React, { Suspense, useMemo, useRef } from "react"
import { useUtils } from "@provider/utils.provider" import { Form, Table } from "react-bootstrap"
import React, { Suspense, useEffect, useMemo, useRef } from "react"
import { Table } from "react-bootstrap"
import { useTranslation } from "react-i18next" import { useTranslation } from "react-i18next"
import { useJsonViewer } from "../viewmodel/json_viewer.viewmodel" import { useJsonViewer } from "../viewmodel/json_viewer.viewmodel"
import './json_viewer.scss' import './json_viewer.scss'
...@@ -13,38 +11,11 @@ type JsonViewerProbs = { ...@@ -13,38 +11,11 @@ type JsonViewerProbs = {
} }
export const JsonViewer = ({ json }: JsonViewerProbs) => { export const JsonViewer = ({ json }: JsonViewerProbs) => {
const { getSubset, breadcrumbs, isCollapsed, collapseable, collapse } = useJsonViewer();
const { subscribe } = useMenu();
const htmlContainer = useRef(null);
const { t } = useTranslation('common'); const { t } = useTranslation('common');
const { toClipboard } = useUtils(); const htmlContainer = useRef(null);
const search = useRef<HTMLInputElement>(null);
useEffect(() => { const { getSubset, breadcrumbs, isCollapsed, collapseable, collapse, parameterizedJson, searchTerm } = useJsonViewer({ json, search, container: htmlContainer });
if (htmlContainer.current) {
const subscription = subscribe({
target: htmlContainer.current,
actions: [
{
key: t('json_viewer.copy'),
icon: faCopy,
action: (clickedElement) => {
let parent = clickedElement;
while (parent && parent.tagName !== 'TR') {
parent = parent.parentNode;
}
const copyValue = parent.dataset.copyValue
toClipboard(copyValue)
}
}
]
})
return () => {
subscription.unsubscribe();
}
}
}, [])
const breadcrumbHTML = useMemo(() => { const breadcrumbHTML = useMemo(() => {
return ( return (
...@@ -59,8 +30,17 @@ export const JsonViewer = ({ json }: JsonViewerProbs) => { ...@@ -59,8 +30,17 @@ export const JsonViewer = ({ json }: JsonViewerProbs) => {
}, [breadcrumbs]) }, [breadcrumbs])
const renderInner = (innerJson: JSON, nested: number = 0): JSX.Element => { const renderInner = (innerJson: JSON, nested: number = 0, path: string = "/network-instance/0/"): JSX.Element => {
return Object.entries(innerJson).map(([key, value]): JSX.Element => { return Object.entries(innerJson).map(([key, value]): JSX.Element => {
if (searchTerm !== "") {
path += key + "/"
const is = parameterizedJson.current.filter(_path => _path === path)[0]
if (!is) {
return (<></>)
}
}
const isObject = value instanceof Object; const isObject = value instanceof Object;
const readableValue = isObject ? '' : value; const readableValue = isObject ? '' : value;
...@@ -69,7 +49,6 @@ export const JsonViewer = ({ json }: JsonViewerProbs) => { ...@@ -69,7 +49,6 @@ export const JsonViewer = ({ json }: JsonViewerProbs) => {
const icon = isObject ? const icon = isObject ?
<span className={collapsed ? 'fa-rotate-90' : ''}>&gt;</span> : <FontAwesomeIcon className="icon fa-rotate-180" icon={faAlignRight} size="xs" /> <span className={collapsed ? 'fa-rotate-90' : ''}>&gt;</span> : <FontAwesomeIcon className="icon fa-rotate-180" icon={faAlignRight} size="xs" />
// determine the margin-left: n indent // determine the margin-left: n indent
let tabs = 0.0; let tabs = 0.0;
for (let i = 0; i < nested; i++) { for (let i = 0; i < nested; i++) {
...@@ -78,9 +57,13 @@ export const JsonViewer = ({ json }: JsonViewerProbs) => { ...@@ -78,9 +57,13 @@ export const JsonViewer = ({ json }: JsonViewerProbs) => {
return ( return (
<React.Fragment key={`${nested}-${key}`}> <React.Fragment key={`${nested}-${key}`}>
<tr className={"list-item-td " + key + " " + nested + " " + (isObject ? 'object' : '')} data-copy-value={readableValue} onClick={() => { isObject ? collapse(key, nested, value) : null }} > <tr
className={"list-item-td " + key + " " + nested + " " + (isObject ? 'object' : '')}
data-copy-value={readableValue}
onClick={() => { isObject ? collapse(key, nested, value) : null }}
>
<td style={{ marginLeft: tabs + 'em' }} className={"d-flex align-items-center "}>{icon}<span>&ensp;{key}</span></td> <td style={{ marginLeft: tabs + 'em' }} className={"d-flex align-items-center "}>{icon}<span>&ensp;{key}</span></td>
<td>{readableValue}</td> <td className="text-element text-truncate">{readableValue}</td>
<td className="text-end"> <td className="text-end">
<div className="d-flex icons justify-content-end align-items-center"> <div className="d-flex icons justify-content-end align-items-center">
<FontAwesomeIcon icon={faPenToSquare} size="sm" /> <FontAwesomeIcon icon={faPenToSquare} size="sm" />
...@@ -88,13 +71,12 @@ export const JsonViewer = ({ json }: JsonViewerProbs) => { ...@@ -88,13 +71,12 @@ export const JsonViewer = ({ json }: JsonViewerProbs) => {
</div> </div>
</td> </td>
</tr > </tr >
{isObject && collapsed ? renderInner(value, nested + 1) : ''} {isObject && collapsed ? renderInner(value, nested + 1, path) : ''}
</React.Fragment > </React.Fragment >
) )
}) })
} }
const renderJson = (json: JSON): JSX.Element => { const renderJson = (json: JSON): JSX.Element => {
return ( return (
<Table className="list-group-tr"> <Table className="list-group-tr">
...@@ -117,10 +99,21 @@ export const JsonViewer = ({ json }: JsonViewerProbs) => { ...@@ -117,10 +99,21 @@ export const JsonViewer = ({ json }: JsonViewerProbs) => {
</Suspense> </Suspense>
</> </>
) )
}, [json, collapseable]) }, [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>
</>
)
}
return ( return (
<div ref={htmlContainer}> <div ref={htmlContainer}>
{searchHTML()}
{breadcrumbHTML} {breadcrumbHTML}
{hierarchyHTML} {hierarchyHTML}
</div> </div>
......
import { faCopy } from "@fortawesome/free-solid-svg-icons";
import { useAppDispatch, useAppSelector } from "@hooks"; import { useAppDispatch, useAppSelector } from "@hooks";
import { useMenu } from "@provider/menu/menu.provider";
import { useUtils } from "@provider/utils.provider";
import React, { MutableRefObject, useEffect, useMemo, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { compareIdentifier, toggleCollapse } from "../reducer/json_viewer.reducer"; import { compareIdentifier, toggleCollapse } from "../reducer/json_viewer.reducer";
export enum CollapseValues { export enum CollapseValues {
...@@ -7,10 +12,25 @@ export enum CollapseValues { ...@@ -7,10 +12,25 @@ export enum CollapseValues {
TRUE TRUE
} }
interface JsonViewerViewModelType {
json: JSON,
search: React.RefObject<HTMLInputElement>,
container: React.RefObject<HTMLElement>
}
export const MARKED = "?marked"
export const useJsonViewer = () => { export const useJsonViewer = ({ json, search, container }: JsonViewerViewModelType) => {
const { breadcrumbs, collapseContainer } = useAppSelector(state => state.json_viwer) const { breadcrumbs, collapseContainer } = useAppSelector(state => state.json_viwer)
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const [searchTerm, setSearchTerm] = useState('');
const { toClipboard } = useUtils();
const { t } = useTranslation('common');
const { subscribe } = useMenu();
// Map that contains a filtered key list with all keys that are found by the searchTerm
// The number represents
const parameterizedJsonMap = useRef<Array<string>>([]);
const getSubset = (json: JSON) => { const getSubset = (json: JSON) => {
...@@ -42,14 +62,94 @@ export const useJsonViewer = () => { ...@@ -42,14 +62,94 @@ export const useJsonViewer = () => {
if (keys.length === 1) { if (keys.length === 1) {
collapse(keys[0], nested + 1, json[keys[0]], CollapseValues.TRUE) collapse(keys[0], nested + 1, json[keys[0]], CollapseValues.TRUE)
} }
}
const handleSearchInput = () => {
setSearchTerm(search.current!.value)
}
const registerMenuOptions = () => {
if (container.current) {
const subscription = subscribe({
target: container.current,
actions: [
{
key: t('json_viewer.copy'),
icon: faCopy,
action: (clickedElement) => {
let parent = clickedElement;
while (parent && parent.tagName !== 'TR') {
parent = parent.parentNode;
}
const copyValue = parent.dataset.copyValue
toClipboard(copyValue)
}
}
]
})
return () => {
subscription.unsubscribe();
}
}
}
const innerSearch = (json: Object, searchValue: string, path: string = "/"): boolean => {
let found = false;
path = JSON.parse(JSON.stringify(path))
for (const [key, childJson] of Object.entries(json)) {
path += key + "/"
if (!(childJson instanceof Object)) {
const marked = key.includes(searchValue) || childJson.includes(searchValue);
if (marked) {
parameterizedJsonMap.current.push(path)
found = true
}
continue
}
const marked = innerSearch(childJson, searchValue, JSON.parse(JSON.stringify(path)));
if (marked) {
found = true
parameterizedJsonMap.current.push(path)
}
}
return found
} }
const parameterizedJson = useMemo<MutableRefObject<Array<string>>>(() => {
parameterizedJsonMap.current = []
if (searchTerm === "") {
return json
}
innerSearch(json, searchTerm);
return parameterizedJsonMap
}, [searchTerm])
useEffect(() => {
registerMenuOptions();
if (search.current) {
search.current.addEventListener('input', handleSearchInput)
}
return () => {
search.current!.removeEventListener('input', handleSearchInput)
}
}, [])
return { return {
getSubset, getSubset,
breadcrumbs, breadcrumbs,
collapseable: collapseContainer, collapseable: collapseContainer,
isCollapsed, isCollapsed,
collapse collapse,
searchTerm,
parameterizedJson
} }
} }
\ No newline at end of file
export const stringToHash = (text: string): number => {
let hash = 0;
if (text.length === 0) return hash;
for (const char of text) {
hash ^= char.charCodeAt(0);
}
return hash;
}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment