Skip to content

Commit

Permalink
feat: highlight changed
Browse files Browse the repository at this point in the history
  • Loading branch information
EYHN authored and pionxzh committed Mar 23, 2023
1 parent 1e5169a commit b9490fe
Show file tree
Hide file tree
Showing 6 changed files with 109 additions and 21 deletions.
11 changes: 11 additions & 0 deletions docs/pages/full/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ const IndexPage: FC = () => {
const [displayDataTypes, setDisplayDataTypes] = useState(true)
const [displayObjectSize, setDisplayObjectSize] = useState(true)
const [editable, setEditable] = useState(true)
const [highlightUpdates, setHighlightUpdates] = useState(true)
useEffect(() => {
const loop = () => {
setSrc(src => ({
Expand Down Expand Up @@ -166,6 +167,15 @@ const IndexPage: FC = () => {
)}
label='Editable'
/>
<FormControlLabel
control={(
<Switch
checked={highlightUpdates}
onChange={event => setHighlightUpdates(event.target.checked)}
/>
)}
label='Highlight Updates'
/>
<FormControlLabel
control={(
<Switch
Expand Down Expand Up @@ -239,6 +249,7 @@ const IndexPage: FC = () => {
<JsonViewer
value={src}
editable={editable}
highlightUpdates={highlightUpdates}
indentWidth={indent}
theme={theme}
displayDataTypes={displayDataTypes}
Expand Down
84 changes: 66 additions & 18 deletions src/components/DataKeyPair.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Box } from '@mui/material'
import type { ComponentProps, FC, MouseEvent } from 'react'
import { useCallback, useMemo, useState } from 'react'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'

import { useTextColor } from '../hooks/useColor'
import { useClipboard } from '../hooks/useCopyToClipboard'
Expand All @@ -21,6 +21,7 @@ import { DataBox } from './mui/DataBox'

export type DataKeyPairProps = {
value: unknown
prevValue?: unknown
nestedIndex?: number
editable?: boolean
path: (string | number)[]
Expand All @@ -41,7 +42,7 @@ const IconBox: FC<IconBoxProps> = (props) => (
)

export const DataKeyPair: FC<DataKeyPairProps> = (props) => {
const { value, path, nestedIndex } = props
const { value, prevValue, path, nestedIndex } = props
const propsEditable = props.editable ?? undefined
const storeEditable = useJsonViewerStore(store => store.editable)
const editable = useMemo(() => {
Expand Down Expand Up @@ -73,6 +74,7 @@ export const DataKeyPair: FC<DataKeyPairProps> = (props) => {
const onChange = useJsonViewerStore(store => store.onChange)
const keyColor = useTextColor()
const numberKeyColor = useJsonViewerStore(store => store.colorspace.base0C)
const highlightColor = useJsonViewerStore(store => store.colorspace.base0F)
const { Component, PreComponent, PostComponent, Editor } = useTypeComponents(value, path)
const quotesOnKeys = useJsonViewerStore(store => store.quotesOnKeys)
const rootName = useJsonViewerStore(store => store.rootName)
Expand All @@ -82,6 +84,49 @@ export const DataKeyPair: FC<DataKeyPairProps> = (props) => {
const enableClipboard = useJsonViewerStore(store => store.enableClipboard)
const { copy, copied } = useClipboard()

const highlightUpdates = useJsonViewerStore(store => store.highlightUpdates)
const isHighlight = useMemo(() => {
if (!highlightUpdates || prevValue === undefined) return false

// highlight if value type changed
if (typeof value !== typeof prevValue) {
return true
}

// highlight if isArray changed
if (Array.isArray(value) !== Array.isArray(prevValue)) {
return true
}

// not highlight object/function
// deep compare they will be slow
if (typeof value === 'object' || typeof value === 'function') {
return false
}

// highlight if not equal
if (value !== prevValue) {
return true
}

return false
}, [highlightUpdates, prevValue, value])
const highlightContainer = useRef<HTMLElement>()
useEffect(() => {
if (highlightContainer.current && isHighlight && 'animate' in highlightContainer.current) {
highlightContainer.current.animate(
[
{ backgroundColor: highlightColor },
{ backgroundColor: '' }
],
{
duration: 1000,
easing: 'ease-in'
}
)
}
}, [highlightColor, isHighlight, prevValue, value])

const actionIcons = useMemo(() => {
if (editing) {
return (
Expand Down Expand Up @@ -166,8 +211,9 @@ export const DataKeyPair: FC<DataKeyPairProps> = (props) => {
path,
inspect,
setInspect,
value
}), [inspect, path, setInspect, value])
value,
prevValue
}), [inspect, path, setInspect, value, prevValue])
return (
<Box
className='data-key-pair'
Expand Down Expand Up @@ -220,20 +266,22 @@ export const DataKeyPair: FC<DataKeyPairProps> = (props) => {
)
: null
}
{
(isRoot
? rootName !== false
? (quotesOnKeys ? <>&quot;{rootName}&quot;</> : <>{rootName}</>)
: null
: KeyRenderer.when(downstreamProps)
? <KeyRenderer {...downstreamProps} />
: nestedIndex === undefined && (
isNumberKey
? <Box component='span' style={{ color: numberKeyColor }}>{key}</Box>
: quotesOnKeys ? <>&quot;{key}&quot;</> : <>{key}</>
)
)
}
<Box ref={highlightContainer} component='span'>
{
(isRoot
? rootName !== false
? (quotesOnKeys ? <>&quot;{rootName}&quot;</> : <>{rootName}</>)
: null
: KeyRenderer.when(downstreamProps)
? <KeyRenderer {...downstreamProps} />
: nestedIndex === undefined && (
isNumberKey
? <Box component='span' style={{ color: numberKeyColor }}>{key}</Box>
: quotesOnKeys ? <>&quot;{key}&quot;</> : <>{key}</>
)
)
}
</Box>
{
(
isRoot
Expand Down
13 changes: 11 additions & 2 deletions src/components/DataTypes/Object.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ export const ObjectType: FC<DataItemProps<object>> = (props) => {
key={key}
path={path}
value={value}
prevValue={props.prevValue instanceof Map ? props.prevValue.get(k) : undefined}
editable={false}
/>
)
Expand Down Expand Up @@ -167,7 +168,12 @@ export const ObjectType: FC<DataItemProps<object>> = (props) => {
const elements = value.slice(0, displayLength).map((value, index) => {
const path = [...props.path, index]
return (
<DataKeyPair key={index} path={path} value={value} />
<DataKeyPair
key={index}
path={path}
value={value}
prevValue={Array.isArray(props.prevValue) ? props.prevValue[index] : undefined}
/>
)
})
if (value.length > displayLength) {
Expand All @@ -193,6 +199,7 @@ export const ObjectType: FC<DataItemProps<object>> = (props) => {
}

const elements: unknown[][] = segmentArray(value, groupArraysAfterLength)
const prevElements = Array.isArray(props.prevValue) ? segmentArray(props.prevValue, groupArraysAfterLength) : undefined

return elements.map((list, index) => {
const path = [...props.path]
Expand All @@ -202,6 +209,7 @@ export const ObjectType: FC<DataItemProps<object>> = (props) => {
path={path}
value={list}
nestedIndex={index}
prevValue={prevElements?.[index]}
/>
)
})
Expand All @@ -216,7 +224,7 @@ export const ObjectType: FC<DataItemProps<object>> = (props) => {
const elements = entries.slice(0, displayLength).map(([key, value]) => {
const path = [...props.path, key]
return (
<DataKeyPair key={key} path={path} value={value} />
<DataKeyPair key={key} path={path} value={value} prevValue={(props.prevValue as any)?.[key]} />
)
})
if (entries.length > displayLength) {
Expand All @@ -242,6 +250,7 @@ export const ObjectType: FC<DataItemProps<object>> = (props) => {
}, [
props.inspect,
props.value,
props.prevValue,
props.path,
groupArraysAfterLength,
displayLength,
Expand Down
10 changes: 9 additions & 1 deletion src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,20 @@ function useSetIfNotUndefinedEffect<Key extends keyof JsonViewerProps> (
*/
const JsonViewerInner: FC<JsonViewerProps> = (props) => {
const { setState } = useContext(JsonViewerStoreContext)
useSetIfNotUndefinedEffect('value', props.value)
useEffect(() => {
setState((state) => ({
prevValue: state.value,
value: props.value
}))
}, [props.value, setState])
useSetIfNotUndefinedEffect('editable', props.editable)
useSetIfNotUndefinedEffect('indentWidth', props.indentWidth)
useSetIfNotUndefinedEffect('onChange', props.onChange)
useSetIfNotUndefinedEffect('groupArraysAfterLength', props.groupArraysAfterLength)
useSetIfNotUndefinedEffect('keyRenderer', props.keyRenderer)
useSetIfNotUndefinedEffect('maxDisplayLength', props.maxDisplayLength)
useSetIfNotUndefinedEffect('enableClipboard', props.enableClipboard)
useSetIfNotUndefinedEffect('highlightUpdates', props.highlightUpdates)
useSetIfNotUndefinedEffect('rootName', props.rootName)
useSetIfNotUndefinedEffect('displayDataTypes', props.displayDataTypes)
useSetIfNotUndefinedEffect('displayObjectSize', props.displayObjectSize)
Expand Down Expand Up @@ -97,6 +103,7 @@ const JsonViewerInner: FC<JsonViewerProps> = (props) => {
}, [props.valueTypes, predefinedTypes, registerTypes])

const value = useJsonViewerStore(store => store.value)
const prevValue = useJsonViewerStore(store => store.prevValue)
const setHover = useJsonViewerStore(store => store.setHover)
const onMouseLeave = useCallback(() => setHover(null), [setHover])
return (
Expand All @@ -114,6 +121,7 @@ const JsonViewerInner: FC<JsonViewerProps> = (props) => {
>
<DataKeyPair
value={value}
prevValue={prevValue}
path={useMemo(() => [], [])}
/>
</Paper>
Expand Down
4 changes: 4 additions & 0 deletions src/stores/JsonViewerStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export type JsonViewerState<T = unknown> = {
indentWidth: number
groupArraysAfterLength: number
enableClipboard: boolean
highlightUpdates: boolean
maxDisplayLength: number
defaultInspectDepth: number
collapseStringsAfterLength: number
Expand All @@ -32,6 +33,7 @@ export type JsonViewerState<T = unknown> = {
editable: boolean | (<U>(path: Path, currentValue: U) => boolean)
displayDataTypes: boolean
rootName: false | string
prevValue: T | undefined
value: T
onChange: JsonViewerOnChange
onCopy: JsonViewerOnCopy | undefined
Expand All @@ -49,6 +51,7 @@ export const createJsonViewerStore = <T = unknown> (props: JsonViewerProps<T>) =
return create<JsonViewerState>()((set, get) => ({
// provided by user
enableClipboard: props.enableClipboard ?? true,
highlightUpdates: props.highlightUpdates ?? false,
indentWidth: props.indentWidth ?? 3,
groupArraysAfterLength: props.groupArraysAfterLength ?? 100,
collapseStringsAfterLength:
Expand All @@ -71,6 +74,7 @@ export const createJsonViewerStore = <T = unknown> (props: JsonViewerProps<T>) =
hoverPath: null,
colorspace: lightColorspace,
value: props.value,
prevValue: undefined,
displayObjectSize: props.displayObjectSize ?? true,

getInspectCache: (path, nestedIndex) => {
Expand Down
8 changes: 8 additions & 0 deletions src/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export interface DataItemProps<ValueType = unknown> {
inspect: boolean
setInspect: Dispatch<SetStateAction<boolean>>
value: ValueType
prevValue: ValueType | undefined
path: Path
}

Expand Down Expand Up @@ -168,4 +169,11 @@ export type JsonViewerProps<T = unknown> = {
* @default true
*/
displayObjectSize?: boolean

/**
* Whether to highlight updates.
*
* @default false
*/
highlightUpdates?: boolean
}

0 comments on commit b9490fe

Please sign in to comment.