Skip to content
Snippets Groups Projects
json_viewer.viewmodel.tsx 5.12 KiB
Newer Older
  • Learn to ignore specific revisions
  • matthiasf's avatar
    matthiasf committed
    import { faCopy } from "@fortawesome/free-solid-svg-icons";
    
    import { useAppDispatch, useAppSelector } from "@hooks";
    
    matthiasf's avatar
    matthiasf committed
    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
    }
    
    
    matthiasf's avatar
    matthiasf committed
    interface JsonViewerViewModelType {
        json: JSON,
        search: React.RefObject<HTMLInputElement>,
        container: React.RefObject<HTMLElement>
    }
    
    export const MARKED = "?marked"
    
    matthiasf's avatar
    matthiasf committed
    export const useJsonViewer = ({ json, search, container }: JsonViewerViewModelType) => {
    
        const { breadcrumbs, collapseContainer } = useAppSelector(state => state.json_viwer)
        const dispatch = useAppDispatch();
    
    matthiasf's avatar
    matthiasf committed
        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)
            }
    
    matthiasf's avatar
    matthiasf committed
        }
    
        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, parentKey: string = "", path: string = "/"): boolean => {
    
    matthiasf's avatar
    matthiasf committed
            let found = false;
    
            path += parentKey + (parentKey === "" ? "" : "/")
    
    matthiasf's avatar
    matthiasf committed
    
            for (const [key, childJson] of Object.entries(json)) {
    
                // leaf
    
    matthiasf's avatar
    matthiasf committed
                if (!(childJson instanceof Object)) {
                    const marked = key.includes(searchValue) || childJson.includes(searchValue);
                    if (marked) {
    
                        parameterizedJsonMap.current.push(path + key)
    
    matthiasf's avatar
    matthiasf committed
                        found = true
                    }
    
                    continue
                }
    
    
                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
    
    matthiasf's avatar
    matthiasf committed
                if (marked) {
                    found = true
                    parameterizedJsonMap.current.push(path)
                }
            }
    
    matthiasf's avatar
    matthiasf committed
            return found
    
    matthiasf's avatar
    matthiasf committed
        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 () => {
    
                if (search.current) {
                    search.current.removeEventListener('input', handleSearchInput)
                }
    
    matthiasf's avatar
    matthiasf committed
            }
        }, [])
    
    
        return {
            getSubset,
            breadcrumbs,
            collapseable: collapseContainer,
            isCollapsed,
    
    matthiasf's avatar
    matthiasf committed
            collapse,
            searchTerm,
            parameterizedJson