import { useCallback, useContext, useEffect, useRef, useState } from 'react' import { JsonViewContext } from './json-view' import { isObject, customAdd, customCopy, customDelete, editableAdd, editableDelete, isCollapsed, objectSize, ifDisplay } from '../utils' import { ReactComponent as AngleDownSVG } from '../svgs/angle-down.svg' import CopyButton from './copy-button' import NameValue from './name-value' import { ReactComponent as DeleteSVG } from '../svgs/trash.svg' import { ReactComponent as AddSVG } from '../svgs/add-square.svg' import { ReactComponent as DoneSVG } from '../svgs/done.svg' import { ReactComponent as CancelSVG } from '../svgs/cancel.svg' import type { CustomizeOptions } from '../types' import LargeArray from './large-array' interface Props { node: Record | Array depth: number indexOrName?: number | string deleteHandle?: (_: string | number) => void customOptions?: CustomizeOptions } export default function ObjectNode({ node, depth, indexOrName, deleteHandle: _deleteSelf, customOptions }: Props) { const { collapsed, enableClipboard, ignoreLargeArray, collapseObjectsAfterLength, editable, onDelete, src, onAdd, onEdit, onChange, forceUpdate, displaySize } = useContext(JsonViewContext) if (!ignoreLargeArray && Array.isArray(node) && node.length > 100) { return } const isPlainObject = isObject(node) const [fold, setFold] = useState(isCollapsed(node, depth, indexOrName, collapsed, collapseObjectsAfterLength, customOptions)) useEffect(() => { setFold(isCollapsed(node, depth, indexOrName, collapsed, collapseObjectsAfterLength, customOptions)) }, [collapsed, collapseObjectsAfterLength]) // Edit property const editHandle = useCallback( (indexOrName: number | string, newValue: any, oldValue: any) => { if (Array.isArray(node)) { node[+indexOrName] = newValue } else if (node) { node[indexOrName] = newValue } if (onEdit) onEdit({ newValue, oldValue, depth, src, indexOrName: indexOrName, parentType: isPlainObject ? 'object' : 'array' }) if (onChange) onChange({ type: 'edit', depth, src, indexOrName: indexOrName, parentType: isPlainObject ? 'object' : 'array' }) forceUpdate() }, [node, onEdit, onChange, forceUpdate] ) // Delete property const deleteHandle = (indexOrName: number | string) => { if (Array.isArray(node)) { node.splice(+indexOrName, 1) } else if (node) { delete node[indexOrName] } forceUpdate() } // Delete self const [deleting, setDeleting] = useState(false) const deleteSelf = () => { setDeleting(false) if (_deleteSelf) _deleteSelf(indexOrName!) if (onDelete) onDelete({ value: node, depth, src, indexOrName: indexOrName!, parentType: isPlainObject ? 'object' : 'array' }) if (onChange) onChange({ type: 'delete', depth, src, indexOrName: indexOrName!, parentType: isPlainObject ? 'object' : 'array' }) } // Add const [adding, setAdding] = useState(false) const inputRef = useRef(null) const add = () => { if (isPlainObject) { const inputName = inputRef.current?.value if (inputName) { ;(node as Record)[inputName] = null if (inputRef.current) inputRef.current.value = '' setAdding(false) if (onAdd) onAdd({ indexOrName: inputName, depth, src, parentType: 'object' }) if (onChange) onChange({ type: 'add', indexOrName: inputName, depth, src, parentType: 'object' }) } } else if (Array.isArray(node)) { const arr = node as unknown as any[] arr.push(null) if (onAdd) onAdd({ indexOrName: arr.length - 1, depth, src, parentType: 'array' }) if (onChange) onChange({ type: 'add', indexOrName: arr.length - 1, depth, src, parentType: 'array' }) } forceUpdate() } const handleAddKeyDown = (event: React.KeyboardEvent) => { if (event.key === 'Enter') { event.preventDefault() add() } else if (event.key === 'Escape') { cancel() } } const isEditing = deleting || adding const cancel = () => { setDeleting(false) setAdding(false) } const Icons = ( <> {!fold && !isEditing && ( setFold(true)} className='jv-size-chevron'> {ifDisplay(displaySize, depth, fold) && {objectSize(node)} Items} )} {adding && isPlainObject && } {isEditing && } {isEditing && } {!fold && !isEditing && enableClipboard && customCopy(customOptions) && } {!fold && !isEditing && editableAdd(editable) && customAdd(customOptions) && ( { if (isPlainObject) { setAdding(true) setTimeout(() => inputRef.current?.focus()) } else { add() } }} /> )} {!fold && !isEditing && editableDelete(editable) && customDelete(customOptions) && _deleteSelf && ( setDeleting(true)} /> )} ) if (Array.isArray(node)) { return ( <> {'['} {Icons} {!fold ? (
{node.map((n, i) => ( ))}
) : ( )} {']'} {fold && ifDisplay(displaySize, depth, fold) && ( setFold(false)} className='jv-size'> {objectSize(node)} Items )} ) } else if (isPlainObject) { return ( <> {'{'} {Icons} {!fold ? (
{Object.entries(node).map(([name, value]) => ( ))}
) : ( )} {'}'} {fold && ifDisplay(displaySize, depth, fold) && ( setFold(false)} className='jv-size'> {objectSize(node)} Items )} ) } return null }