Skip to content

Commit

Permalink
feat: support props.maxDisplayLength (#30)
Browse files Browse the repository at this point in the history
  • Loading branch information
himself65 committed Sep 19, 2022
1 parent e12ffc2 commit 498efe2
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 43 deletions.
65 changes: 56 additions & 9 deletions src/components/DataTypes/Object.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
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'
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 = '['
Expand Down Expand Up @@ -85,20 +86,44 @@ export const ObjectType: React.FC<DataItemProps<object>> = (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 (
<DataKeyPair key={index} path={path} value={value}/>
)
})
if (value.length > displayLength) {
const rest = value.length - displayLength
elements.push(
<DataBox
sx={{
cursor: 'pointer',
lineHeight: 1.5,
color: keyColor,
letterSpacing: 0.5,
opacity: 0.8
}}
key='last'
onClick={() => setDisplayLength(length => length * 2)}
>
hidden {rest} items...
</DataBox>
)
}
return elements
}
const value = props.value.reduce<unknown[][]>((array, value, index) => {
const elements = value.reduce<unknown[][]>((array, value, index) => {
const target = Math.floor(index / groupArraysAfterLength)
if (array[target]) {
array[target].push(value)
Expand All @@ -108,21 +133,43 @@ export const ObjectType: React.FC<DataItemProps<object>> = (props) => {
return array
}, [])

return value.map((list, index) => {
return elements.map((list, index) => {
const path = [...props.path]
return (
<DataKeyPair key={index} path={path} value={list} nestedIndex={index}/>
<DataKeyPair key={index} path={path} value={list}
nestedIndex={index}/>
)
})
} 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 (
<DataKeyPair key={key} path={path} value={value}/>
)
})
if (entries.length > displayLength) {
const rest = entries.length - displayLength
elements.push(
<DataBox
sx={{
cursor: 'pointer',
lineHeight: 1.5,
color: keyColor,
letterSpacing: 0.5,
opacity: 0.8
}}
key='last'
onClick={() => setDisplayLength(length => length * 2)}
>
hidden {rest} items...
</DataBox>
)
}
return elements
}
}, [props.inspect, props.value, props.path, groupArraysAfterLength])
}, [props.inspect, props.value, props.path, groupArraysAfterLength, displayLength, keyColor])
return (
<Box
className='data-object'
Expand Down
26 changes: 7 additions & 19 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,6 @@ import type React from 'react'
import { useCallback, useEffect, useMemo, useRef } from 'react'

import { DataKeyPair } from './components/DataKeyPair'
import {
ObjectType,
PostObjectType,
PreObjectType
} from './components/DataTypes/Object'
import { useThemeDetector } from './hooks/useThemeDetector'
import {
createJsonViewerStore,
Expand Down Expand Up @@ -46,18 +41,19 @@ const JsonViewerInner: React.FC<JsonViewerProps> = (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') {
Expand All @@ -79,7 +75,8 @@ const JsonViewerInner: React.FC<JsonViewerProps> = (props) => {
style={props.style}
sx={{
fontFamily: 'monospace',
userSelect: 'none'
userSelect: 'none',
contentVisibility: 'auto'
}}
onMouseLeave={
useCallback(() => {
Expand Down Expand Up @@ -116,21 +113,12 @@ export const JsonViewer = function JsonViewer<Value> (props: JsonViewerProps<Val
props.valueTypes?.forEach(type => {
registerType(type)
})
// Always last one, fallback for all data like 'object'
registerType<object>(
{
is: (value): value is object => typeof value === 'object',
Component: ObjectType,
PreComponent: PreObjectType,
PostComponent: PostObjectType
}
)
onceRef.current = false
}
const mixedProps = { ...props, theme: themeType }
return (
<ThemeProvider theme={theme}>
<JsonViewerProvider createStore={createJsonViewerStore}>
<JsonViewerProvider createStore={() => createJsonViewerStore(props)}>
<JsonViewerInner {...mixedProps}/>
</JsonViewerProvider>
</ThemeProvider>
Expand Down
22 changes: 12 additions & 10 deletions src/stores/JsonViewerStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,24 @@ 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'

const DefaultKeyRenderer: JsonViewerKeyRenderer = () => null
DefaultKeyRenderer.when = () => false

export type JsonViewerState = {
export type JsonViewerState<T = unknown> = {
inspectCache: Record<string, boolean>
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
}
Expand All @@ -31,20 +32,21 @@ export type JsonViewerActions = {
setHover: (path: Path | null, nestedIndex?: number) => void
}

export const createJsonViewerStore = () =>
export const createJsonViewerStore = <T = unknown>(props: JsonViewerProps<T>) =>
create(
combine<JsonViewerState, JsonViewerActions>(
combine<JsonViewerState<T>, 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) => {
Expand Down
25 changes: 21 additions & 4 deletions src/stores/typeRegistry.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand All @@ -16,6 +22,13 @@ export function registerType<Value> (type: DataType<Value>) {
typeRegistry.push(type)
}

const objectType: DataType<object> = {
is: (value): value is object => typeof value === 'object',
Component: ObjectType,
PreComponent: PreObjectType,
PostComponent: PostObjectType
}

export function matchTypeComponents<Value> (value: Value): DataType<Value> {
let potential: DataType<Value> | undefined
for (const T of typeRegistry) {
Expand All @@ -27,10 +40,14 @@ export function matchTypeComponents<Value> (value: Value): DataType<Value> {
}
}
}
if (potential === undefined) {
throw new DevelopmentError('this is not possible')
if (potential === undefined && typeof value === 'object') {
return objectType as unknown as DataType<Value>
} else {
if (potential === undefined) {
throw new DevelopmentError('this is not possible')
}
return potential
}
return potential
}

export function useTypeComponents (value: unknown) {
Expand Down
10 changes: 9 additions & 1 deletion src/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,17 @@ export type JsonViewerProps<T = unknown> = {
/**
* 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.
Expand Down

0 comments on commit 498efe2

Please sign in to comment.