From b9490fe8e6516d2369535b65b50ddde0a05de55b Mon Sep 17 00:00:00 2001 From: eyhn Date: Thu, 23 Mar 2023 11:33:25 +0800 Subject: [PATCH] feat: highlight changed --- docs/pages/full/index.tsx | 11 ++++ src/components/DataKeyPair.tsx | 84 ++++++++++++++++++++++------- src/components/DataTypes/Object.tsx | 13 ++++- src/index.tsx | 10 +++- src/stores/JsonViewerStore.ts | 4 ++ src/type.ts | 8 +++ 6 files changed, 109 insertions(+), 21 deletions(-) diff --git a/docs/pages/full/index.tsx b/docs/pages/full/index.tsx index 4e53004b..ea1c0702 100644 --- a/docs/pages/full/index.tsx +++ b/docs/pages/full/index.tsx @@ -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 => ({ @@ -166,6 +167,15 @@ const IndexPage: FC = () => { )} label='Editable' /> + setHighlightUpdates(event.target.checked)} + /> + )} + label='Highlight Updates' + /> { = (props) => ( ) export const DataKeyPair: FC = (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(() => { @@ -73,6 +74,7 @@ export const DataKeyPair: FC = (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) @@ -82,6 +84,49 @@ export const DataKeyPair: FC = (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() + 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 ( @@ -166,8 +211,9 @@ export const DataKeyPair: FC = (props) => { path, inspect, setInspect, - value - }), [inspect, path, setInspect, value]) + value, + prevValue + }), [inspect, path, setInspect, value, prevValue]) return ( = (props) => { ) : null } - { - (isRoot - ? rootName !== false - ? (quotesOnKeys ? <>"{rootName}" : <>{rootName}) - : null - : KeyRenderer.when(downstreamProps) - ? - : nestedIndex === undefined && ( - isNumberKey - ? {key} - : quotesOnKeys ? <>"{key}" : <>{key} - ) - ) - } + + { + (isRoot + ? rootName !== false + ? (quotesOnKeys ? <>"{rootName}" : <>{rootName}) + : null + : KeyRenderer.when(downstreamProps) + ? + : nestedIndex === undefined && ( + isNumberKey + ? {key} + : quotesOnKeys ? <>"{key}" : <>{key} + ) + ) + } + { ( isRoot diff --git a/src/components/DataTypes/Object.tsx b/src/components/DataTypes/Object.tsx index fecbc17b..95ea1fbd 100644 --- a/src/components/DataTypes/Object.tsx +++ b/src/components/DataTypes/Object.tsx @@ -136,6 +136,7 @@ export const ObjectType: FC> = (props) => { key={key} path={path} value={value} + prevValue={props.prevValue instanceof Map ? props.prevValue.get(k) : undefined} editable={false} /> ) @@ -167,7 +168,12 @@ export const ObjectType: FC> = (props) => { const elements = value.slice(0, displayLength).map((value, index) => { const path = [...props.path, index] return ( - + ) }) if (value.length > displayLength) { @@ -193,6 +199,7 @@ export const ObjectType: FC> = (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] @@ -202,6 +209,7 @@ export const ObjectType: FC> = (props) => { path={path} value={list} nestedIndex={index} + prevValue={prevElements?.[index]} /> ) }) @@ -216,7 +224,7 @@ export const ObjectType: FC> = (props) => { const elements = entries.slice(0, displayLength).map(([key, value]) => { const path = [...props.path, key] return ( - + ) }) if (entries.length > displayLength) { @@ -242,6 +250,7 @@ export const ObjectType: FC> = (props) => { }, [ props.inspect, props.value, + props.prevValue, props.path, groupArraysAfterLength, displayLength, diff --git a/src/index.tsx b/src/index.tsx index 45f976c2..10760d89 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -47,7 +47,12 @@ function useSetIfNotUndefinedEffect ( */ const JsonViewerInner: FC = (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) @@ -55,6 +60,7 @@ const JsonViewerInner: FC = (props) => { 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) @@ -97,6 +103,7 @@ const JsonViewerInner: FC = (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 ( @@ -114,6 +121,7 @@ const JsonViewerInner: FC = (props) => { > [], [])} /> diff --git a/src/stores/JsonViewerStore.ts b/src/stores/JsonViewerStore.ts index a2d9cb9e..52b2fe1d 100644 --- a/src/stores/JsonViewerStore.ts +++ b/src/stores/JsonViewerStore.ts @@ -23,6 +23,7 @@ export type JsonViewerState = { indentWidth: number groupArraysAfterLength: number enableClipboard: boolean + highlightUpdates: boolean maxDisplayLength: number defaultInspectDepth: number collapseStringsAfterLength: number @@ -32,6 +33,7 @@ export type JsonViewerState = { editable: boolean | ((path: Path, currentValue: U) => boolean) displayDataTypes: boolean rootName: false | string + prevValue: T | undefined value: T onChange: JsonViewerOnChange onCopy: JsonViewerOnCopy | undefined @@ -49,6 +51,7 @@ export const createJsonViewerStore = (props: JsonViewerProps) = return create()((set, get) => ({ // provided by user enableClipboard: props.enableClipboard ?? true, + highlightUpdates: props.highlightUpdates ?? false, indentWidth: props.indentWidth ?? 3, groupArraysAfterLength: props.groupArraysAfterLength ?? 100, collapseStringsAfterLength: @@ -71,6 +74,7 @@ export const createJsonViewerStore = (props: JsonViewerProps) = hoverPath: null, colorspace: lightColorspace, value: props.value, + prevValue: undefined, displayObjectSize: props.displayObjectSize ?? true, getInspectCache: (path, nestedIndex) => { diff --git a/src/type.ts b/src/type.ts index a9453211..87a79918 100644 --- a/src/type.ts +++ b/src/type.ts @@ -36,6 +36,7 @@ export interface DataItemProps { inspect: boolean setInspect: Dispatch> value: ValueType + prevValue: ValueType | undefined path: Path } @@ -168,4 +169,11 @@ export type JsonViewerProps = { * @default true */ displayObjectSize?: boolean + + /** + * Whether to highlight updates. + * + * @default false + */ + highlightUpdates?: boolean }