diff --git a/examples/basic/pages/index.tsx b/examples/basic/pages/index.tsx index 772fc823..df23b88d 100644 --- a/examples/basic/pages/index.tsx +++ b/examples/basic/pages/index.tsx @@ -15,6 +15,10 @@ const example = { string: 'this is a string', integer: 42, array: [19, 19, 810, 'test', NaN], + nestedArray: [ + [1, 2], + [3, 4] + ], float: 114.514, undefined, object: { @@ -30,6 +34,7 @@ const example = { const IndexPage: React.FC = () => { const [indent, setIndent] = useState(2) + const [groupArraysAfterLength, setGroupArraysAfterLength] = useState(100) const [src, setSrc] = useState(() => example) useEffect(() => { const loop = () => { @@ -57,13 +62,30 @@ const IndexPage: React.FC = () => { } } /> + { + const groupArraysAfterLength = parseInt(event.target.value) + if (groupArraysAfterLength > -1 && groupArraysAfterLength < 500) { + setGroupArraysAfterLength(groupArraysAfterLength) + } + } + } + /> ( (path, oldValue, newValue) => { - setSrc(src => applyValue(src, path, newValue)) + setSrc(src => { + const newSrc = applyValue(src, path, newValue) + console.log(newSrc, newSrc === src) + return src + }) }, [] ) } diff --git a/src/components/DataKeyPair.tsx b/src/components/DataKeyPair.tsx index 9adaabe8..b86b22fe 100644 --- a/src/components/DataKeyPair.tsx +++ b/src/components/DataKeyPair.tsx @@ -17,7 +17,8 @@ import { DataBox } from './mui/DataBox' export type DataKeyPairProps = { value: unknown - path: string[] + nested?: boolean + path: (string | number)[] } const IconBox = styled(props => )` @@ -42,17 +43,18 @@ export const DataKeyPair: React.FC = (props) => { const keyColor = useTextColor() const numberKeyColor = useJsonViewerStore( store => store.colorNamespace.base0C) - const { Component, PreComponent, PostComponent, Editor } = useTypeComponents(value) + const { Component, PreComponent, PostComponent, Editor } = useTypeComponents( + value) const rootName = useJsonViewerStore(store => store.rootName) const isRoot = useJsonViewerStore(store => store.value) === value const isNumberKey = Number.isInteger(Number(key)) const displayKey = isRoot ? rootName : key - const downstreamProps: DataItemProps = { + const downstreamProps: DataItemProps = useMemo(() => ({ path, inspect, setInspect, value - } + }), [inspect, path, value]) const actionIcons = useMemo(() => { if (editing) { return ( @@ -151,12 +153,19 @@ export const DataKeyPair: React.FC = (props) => { }, []) } > - {isNumberKey - ? {displayKey} - : <>"{displayKey}" + { + !props.nested && ( + isNumberKey + ? {displayKey} + : <>"{displayKey}" + ) + } + { + !props.nested && ( + : + ) } - : {PreComponent && } {(isHover && expandable && inspect) && actionIcons} diff --git a/src/components/DataTypes/Array.tsx b/src/components/DataTypes/Array.tsx new file mode 100644 index 00000000..190a77f4 --- /dev/null +++ b/src/components/DataTypes/Array.tsx @@ -0,0 +1,113 @@ +import { Box } from '@mui/material' +import React, { useMemo } from 'react' + +import { useTextColor } from '../../hooks/useColor' +import { useJsonViewerStore } from '../../stores/JsonViewerStore' +import type { DataItemProps } from '../../type' +import { DataKeyPair } from '../DataKeyPair' + +const arrayLb = '[' +const arrayRb = ']' + +export const PreArrayType: React.FC> = (props) => { + const metadataColor = useJsonViewerStore(store => store.colorNamespace.base04) + const sizeOfValue = useMemo( + () => props.inspect ? `${Object.keys(props.value).length} Items` : '', + [props.inspect, props.value] + ) + return ( + + {arrayLb} + + {sizeOfValue} + + + ) +} + +export const PostArrayType: React.FC> = (props) => { + const metadataColor = useJsonViewerStore(store => store.colorNamespace.base04) + const sizeOfValue = useMemo( + () => !props.inspect ? `${Object.keys(props.value).length} Items` : '', + [props.inspect, props.value] + ) + return ( + + {arrayRb} + + {sizeOfValue} + + + ) +} + +export const ArrayType: React.FC> = (props) => { + const keyColor = useTextColor() + const groupArraysAfterLength = useJsonViewerStore(store => store.groupArraysAfterLength) + const elements = useMemo(() => { + if (props.value.length <= groupArraysAfterLength) { + return props.value.map((value, index) => { + const path = [...props.path, index] + return ( + + ) + }) + } + const value = props.value.reduce((array, value, index) => { + const target = Math.floor(index / groupArraysAfterLength) + if (array[target]) { + array[target].push(value) + } else { + array[target] = [value] + } + return array + }, []) + console.log('value', value) + + return value.map((list, index) => { + const path = [...props.path] + return ( + + ) + }) + }, [props.path, props.value, groupArraysAfterLength]) + return ( + + { + props.inspect + ? elements + : ( + + ... + + ) + } + + ) +} diff --git a/src/components/DataTypes/Object.tsx b/src/components/DataTypes/Object.tsx index 213458c2..a2b1905a 100644 --- a/src/components/DataTypes/Object.tsx +++ b/src/components/DataTypes/Object.tsx @@ -6,11 +6,14 @@ import { useJsonViewerStore } from '../../stores/JsonViewerStore' import type { DataItemProps } from '../../type' import { DataKeyPair } from '../DataKeyPair' -const lb = '{' -const rb = '}' +const objectLb = '{' +const arrayLb = '[' +const objectRb = '}' +const arrayRb = ']' export const PreObjectType: React.FC> = (props) => { const metadataColor = useJsonViewerStore(store => store.colorNamespace.base04) + const isArray = useMemo(() => Array.isArray(props.value), [props.value]) const sizeOfValue = useMemo( () => props.inspect ? `${Object.keys(props.value).length} Items` : '', [props.inspect, props.value] @@ -22,7 +25,7 @@ export const PreObjectType: React.FC> = (props) => { letterSpacing: 0.5 }} > - {lb} + {isArray ? arrayLb : objectLb} > = (props) => { export const PostObjectType: React.FC> = (props) => { const metadataColor = useJsonViewerStore(store => store.colorNamespace.base04) + const isArray = useMemo(() => Array.isArray(props.value), [props.value]) const sizeOfValue = useMemo( () => !props.inspect ? `${Object.keys(props.value).length} Items` : '', [props.inspect, props.value] ) return ( - {rb} + {isArray ? arrayRb : objectRb} > = (props) => { export const ObjectType: React.FC> = (props) => { const keyColor = useTextColor() + const elements = useMemo(() => ( + Object.entries(props.value).map(([key, value]) => { + const path = [...props.path, key] + return ( + + ) + }) + ), [props.path, props.value]) return ( > = (props) => { > { props.inspect - ? ( - Object.entries(props.value).map(([key, value]) => { - const path = [...props.path, key] - return ( - - ) - }) - ) + ? elements : ( ... diff --git a/src/index.tsx b/src/index.tsx index 15af2ae8..d792cfd4 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -17,7 +17,7 @@ import { applyValue } from './utils' export { applyValue } -export type JsonViewerOnChange = (path: string[], oldValue: U, newValue: U) => void +export type JsonViewerOnChange = (path: (string | number)[], oldValue: U, newValue: U) => void const JsonViewerInner: React.FC = (props) => { const api = useJsonViewerStoreApi() @@ -26,9 +26,10 @@ const JsonViewerInner: React.FC = (props) => { value: props.value, indentWidth: props.indentWidth, defaultCollapsed: props.defaultCollapsed, - onChange: props.onChange + onChange: props.onChange, + groupArraysAfterLength: props.groupArraysAfterLength })) - }, [api, props.defaultCollapsed, props.indentWidth, props.onChange, props.value]) + }, [api, props.defaultCollapsed, props.groupArraysAfterLength, props.indentWidth, props.onChange, props.value]) const value = useJsonViewerStore(store => store.value) const setHover = useJsonViewerStore(store => store.setHover) diff --git a/src/stores/JsonViewerStore.ts b/src/stores/JsonViewerStore.ts index 1a0f237c..f2f53aa3 100644 --- a/src/stores/JsonViewerStore.ts +++ b/src/stores/JsonViewerStore.ts @@ -62,7 +62,8 @@ export const darkNamespace: ColorNamespace = { } export type JsonViewerState = { - hoverPath: string[] | null + hoverPath: (string | number)[] | null + groupArraysAfterLength: number defaultCollapsed: number | boolean colorNamespace: ColorNamespace expanded: string[] @@ -72,7 +73,7 @@ export type JsonViewerState = { } export type JsonViewerActions = { - setHover: (path: string[] | null) => void + setHover: (path: (string | number)[] | null) => void } // todo @@ -81,6 +82,7 @@ export const createJsonViewerStore = () => combine( { hoverPath: null, + groupArraysAfterLength: 100, rootName: 'root', defaultCollapsed: false, colorNamespace: defaultColorNamespace, diff --git a/src/stores/typeRegistry.tsx b/src/stores/typeRegistry.tsx index 59809527..6a30b68f 100644 --- a/src/stores/typeRegistry.tsx +++ b/src/stores/typeRegistry.tsx @@ -2,6 +2,11 @@ import { Box } from '@mui/material' import { DevelopmentError } from '@textea/dev-kit/utils' import React, { useMemo } from 'react' +import { + ArrayType, + PostArrayType, + PreArrayType +} from '../components/DataTypes/Array' import { createEasyType } from '../components/DataTypes/createEasyType' import { FunctionType, PostFunctionType, @@ -200,6 +205,15 @@ registerType( } ) +registerType( + { + is: (value): value is unknown[] => Array.isArray(value), + Component: ArrayType, + PreComponent: PreArrayType, + PostComponent: PostArrayType + } +) + // fallback for all data like 'object' registerType( { diff --git a/src/type.ts b/src/type.ts index f31035f2..5b1fce25 100644 --- a/src/type.ts +++ b/src/type.ts @@ -5,7 +5,7 @@ export interface DataItemProps { inspect: boolean setInspect: Dispatch> value: ValueType - path: string[] + path: (string | number)[] } export type EditorProps = { @@ -36,13 +36,19 @@ export type JsonViewerProps = { * @param oldValue * @param newValue */ - onChange?: (path: string[], oldValue: U, newValue: U) => void + onChange?: (path: (string | number)[], oldValue: U, newValue: U) => void /** * collapsed depth, true for all collapsed, false for all expanded. * number for depth that default expanded. * @default false */ defaultCollapsed?: boolean | 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. + * @default 100 + */ + groupArraysAfterLength?: number className?: string style?: React.CSSProperties } diff --git a/src/utils/index.ts b/src/utils/index.ts index 5cb649f0..6182b8b2 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,11 +1,10 @@ export const applyValue = (obj: any, path: string[], value: any) => { - let arr - let key - if (!obj || typeof obj !== 'object') { - obj = {} + if (typeof obj !== 'object' || obj === null) { + return value } + const arr: string[] = [...path] + let key if (path.length > 0) { - arr = path key = arr[0] if (arr.length > 1) { arr.shift()