diff --git a/src/components/DataTypes/Object.tsx b/src/components/DataTypes/Object.tsx index df8e9cce..f7a05e6c 100644 --- a/src/components/DataTypes/Object.tsx +++ b/src/components/DataTypes/Object.tsx @@ -1,5 +1,5 @@ import { Box } from '@mui/material' -import React, { useMemo } from 'react' +import React, { useMemo, useState } from 'react' import { useTextColor } from '../../hooks/useColor' import { useIsCycleReference } from '../../hooks/useIsCycleReference' @@ -7,6 +7,7 @@ import { useJsonViewerStore } from '../../stores/JsonViewerStore' import type { DataItemProps } from '../../type' import { DataKeyPair } from '../DataKeyPair' import { CircularArrowsIcon } from '../icons/CircularArrowsIcon' +import { DataBox } from '../mui/DataBox' const objectLb = '{' const arrayLb = '[' @@ -85,20 +86,44 @@ export const ObjectType: React.FC> = (props) => { const groupArraysAfterLength = useJsonViewerStore( store => store.groupArraysAfterLength) const isTrap = useIsCycleReference(props.path, props.value) + const [displayLength, setDisplayLength] = useState( + useJsonViewerStore(store => store.maxDisplayLength) + ) const elements = useMemo(() => { if (!props.inspect) { return null } - if (Array.isArray(props.value)) { - if (props.value.length <= groupArraysAfterLength) { - return props.value.map((value, index) => { + const value: unknown[] | object = props.value + if (Array.isArray(value)) { + // unknown[] + if (value.length <= groupArraysAfterLength) { + const elements = value.slice(0, displayLength).map((value, index) => { const path = [...props.path, index] return ( ) }) + if (value.length > displayLength) { + const rest = value.length - displayLength + elements.push( + setDisplayLength(length => length * 2)} + > + hidden {rest} items... + + ) + } + return elements } - const value = props.value.reduce((array, value, index) => { + const elements = value.reduce((array, value, index) => { const target = Math.floor(index / groupArraysAfterLength) if (array[target]) { array[target].push(value) @@ -108,21 +133,43 @@ export const ObjectType: React.FC> = (props) => { return array }, []) - return value.map((list, index) => { + return elements.map((list, index) => { const path = [...props.path] return ( - + ) }) } else { - return Object.entries(props.value).map(([key, value]) => { + // object + const entries = Object.entries(value) + const elements = entries.slice(0, displayLength).map(([key, value]) => { const path = [...props.path, key] return ( ) }) + if (entries.length > displayLength) { + const rest = entries.length - displayLength + elements.push( + setDisplayLength(length => length * 2)} + > + hidden {rest} items... + + ) + } + return elements } - }, [props.inspect, props.value, props.path, groupArraysAfterLength]) + }, [props.inspect, props.value, props.path, groupArraysAfterLength, displayLength, keyColor]) return ( = (props) => { }, [props.value, setIfNotUndefined]) useEffect(() => { // setIfNotUndefined('indentWidth', props.indentWidth) - setIfNotUndefined('defaultInspectDepth', props.defaultInspectDepth) setIfNotUndefined('onChange', props.onChange) setIfNotUndefined('groupArraysAfterLength', props.groupArraysAfterLength) setIfNotUndefined('keyRenderer', props.keyRenderer) + setIfNotUndefined('maxDisplayLength', props.maxDisplayLength) }, [ api, - props.defaultInspectDepth, props.groupArraysAfterLength, props.keyRenderer, props.onChange, props.value, - setIfNotUndefined]) + props.maxDisplayLength, + setIfNotUndefined + ]) useEffect(() => { if (props.theme === 'light') { @@ -79,7 +75,8 @@ const JsonViewerInner: React.FC = (props) => { style={props.style} sx={{ fontFamily: 'monospace', - userSelect: 'none' + userSelect: 'none', + contentVisibility: 'auto' }} onMouseLeave={ useCallback(() => { @@ -116,21 +113,12 @@ export const JsonViewer = function JsonViewer (props: JsonViewerProps { registerType(type) }) - // Always last one, fallback for all data like 'object' - registerType( - { - is: (value): value is object => typeof value === 'object', - Component: ObjectType, - PreComponent: PreObjectType, - PostComponent: PostObjectType - } - ) onceRef.current = false } const mixedProps = { ...props, theme: themeType } return ( - + createJsonViewerStore(props)}> diff --git a/src/stores/JsonViewerStore.ts b/src/stores/JsonViewerStore.ts index 6769777d..200f6dc2 100644 --- a/src/stores/JsonViewerStore.ts +++ b/src/stores/JsonViewerStore.ts @@ -3,7 +3,7 @@ import create from 'zustand' import createContext from 'zustand/context' import { combine } from 'zustand/middleware' -import type { JsonViewerOnChange, Path } from '..' +import type { JsonViewerOnChange, JsonViewerProps, Path } from '..' import type { ColorNamespace } from '../theme/base16' import { lightColorNamespace } from '../theme/base16' import type { JsonViewerKeyRenderer } from '../type' @@ -11,15 +11,16 @@ import type { JsonViewerKeyRenderer } from '../type' const DefaultKeyRenderer: JsonViewerKeyRenderer = () => null DefaultKeyRenderer.when = () => false -export type JsonViewerState = { +export type JsonViewerState = { inspectCache: Record hoverPath: { path: Path; nestedIndex?: number } | null groupArraysAfterLength: number + maxDisplayLength: number defaultInspectDepth: number colorNamespace: ColorNamespace expanded: string[] rootName: string - value: unknown + value: T onChange: JsonViewerOnChange keyRenderer: JsonViewerKeyRenderer } @@ -31,20 +32,21 @@ export type JsonViewerActions = { setHover: (path: Path | null, nestedIndex?: number) => void } -export const createJsonViewerStore = () => +export const createJsonViewerStore = (props: JsonViewerProps) => create( - combine( + combine, JsonViewerActions>( { inspectCache: {}, hoverPath: null, - groupArraysAfterLength: 100, + groupArraysAfterLength: props.groupArraysAfterLength ?? 100, + maxDisplayLength: props.maxDisplayLength ?? 30, rootName: 'root', - defaultInspectDepth: 10, + defaultInspectDepth: 5, colorNamespace: lightColorNamespace, expanded: ['data-viewer-root'], - value: {}, - onChange: () => {}, - keyRenderer: DefaultKeyRenderer + value: props.value, + onChange: props.onChange ?? (() => {}), + keyRenderer: props.keyRenderer ?? DefaultKeyRenderer }, (set, get) => ({ getInspectCache: (path, nestedIndex) => { diff --git a/src/stores/typeRegistry.tsx b/src/stores/typeRegistry.tsx index 2af0a5db..4c5b6f4a 100644 --- a/src/stores/typeRegistry.tsx +++ b/src/stores/typeRegistry.tsx @@ -4,9 +4,15 @@ import React, { useMemo } from 'react' import { createEasyType } from '../components/DataTypes/createEasyType' import { - FunctionType, PostFunctionType, + FunctionType, + PostFunctionType, PreFunctionType } from '../components/DataTypes/Function' +import { + ObjectType, + PostObjectType, + PreObjectType +} from '../components/DataTypes/Object' import type { DataType } from '../type' import { useJsonViewerStore } from './JsonViewerStore' @@ -16,6 +22,13 @@ export function registerType (type: DataType) { typeRegistry.push(type) } +const objectType: DataType = { + is: (value): value is object => typeof value === 'object', + Component: ObjectType, + PreComponent: PreObjectType, + PostComponent: PostObjectType +} + export function matchTypeComponents (value: Value): DataType { let potential: DataType | undefined for (const T of typeRegistry) { @@ -27,10 +40,14 @@ export function matchTypeComponents (value: Value): DataType { } } } - if (potential === undefined) { - throw new DevelopmentError('this is not possible') + if (potential === undefined && typeof value === 'object') { + return objectType as unknown as DataType + } else { + if (potential === undefined) { + throw new DevelopmentError('this is not possible') + } + return potential } - return potential } export function useTypeComponents (value: unknown) { diff --git a/src/type.ts b/src/type.ts index 95a3fd57..334d865e 100644 --- a/src/type.ts +++ b/src/type.ts @@ -53,9 +53,17 @@ export type JsonViewerProps = { /** * Inspect depth by default. * Do not set the number too large, otherwise there will have performance issue - * @default 50 + * @default 5 */ defaultInspectDepth?: number + /** + * Hide items after reaching the count. + * Array and object will be affected. + * + * If `Object.keys(value).length` is large also, this will cause a performance issue. + * @default 30 + */ + maxDisplayLength?: number /** * When an integer value is assigned, arrays will be displayed in groups by count of the value. * Groups are displayed with bracket notation and can be expanded and collapsed by clicking on the brackets.