Newer
Older
import { faCopy } from "@fortawesome/free-solid-svg-icons";
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";
export enum CollapseValues {
TOGGLE,
FALSE,
TRUE
}
interface JsonViewerViewModelType {
json: JSON,
search: React.RefObject<HTMLInputElement>,
container: React.RefObject<HTMLElement>
}
export const useJsonViewer = ({ json, search, container }: JsonViewerViewModelType) => {
const { breadcrumbs, collapseContainer } = useAppSelector(state => state.json_viwer)
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 subset = breadcrumbs.reduce((nested, key) => nested?.[key], json);
let inner = subset;
const keys: Array<string> = [];
while (Object.keys(inner).length === 1) {
const key: string = Object.keys(inner)[0];
inner = inner[key];
keys.push(key);
}
//dispatch(setBreadcrumbs([...breadcrumbs, ...keys]))
return inner
}
const isCollapsed = (key: string, nested: number): boolean => {
const item = collapseContainer
.filter(({ identifier, collapsed }) => compareIdentifier(identifier, { key, nested }) && collapsed);
return !!item.length;
}
const collapse = (key: string, nested: number, json: object, collapseState: CollapseValues = CollapseValues.TOGGLE) => {
const identifier = { key, nested };
dispatch(toggleCollapse({ identifier, collapse: collapseState }))
const keys = Object.keys(json)
if (keys.length === 1) {
collapse(keys[0], nested + 1, json[keys[0]], CollapseValues.TRUE)
}
}
const handleSearchInput = () => {
setSearchTerm(search.current!.value)
}
const registerMenuOptions = () => {
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, parentKey: string = "", path: string = "/"): boolean => {
path += parentKey + (parentKey === "" ? "" : "/")
for (const [key, childJson] of Object.entries(json)) {
if (!(childJson instanceof Object)) {
const marked = key.includes(searchValue) || childJson.includes(searchValue);
if (marked) {
parameterizedJsonMap.current.push(path + key)
if (key.includes(searchValue)) {
parameterizedJsonMap.current.push(path + key)
found = true
}
const marked = innerSearch(childJson, searchValue, key, path);
// if found in some child leaf save the parent
if (marked) {
found = true
parameterizedJsonMap.current.push(path)
}
}
const parameterizedJson = useMemo<MutableRefObject<string[]>>(() => {
parameterizedJsonMap.current = []
if (searchTerm === "") {
return json
}
innerSearch(json, searchTerm);
return parameterizedJsonMap
if (!container.current || !subscribe) {
return
}
const unsubscribe = registerMenuOptions()
return () => {
unsubscribe()
}
}, [subscribe])
useEffect(() => {
if (search.current) {
search.current.addEventListener('input', handleSearchInput)
}
return () => {
if (search.current) {
search.current.removeEventListener('input', handleSearchInput)
}
return {
getSubset,
breadcrumbs,
collapseable: collapseContainer,
isCollapsed,