@@ -3,108 +3,21 @@ import clsx from 'clsx'
33import { css , useStyles } from '../styles/use-styles'
44import { CopiedCopier , Copier , ErrorCopier } from './icons'
55
6- export function JsonTree ( props : { value : any ; copyable ?: boolean } ) {
7- return < JsonValue isRoot value = { props . value } copyable = { props . copyable } />
6+ export function JsonTree ( props : { value : any ; copyable ?: boolean , defaultExpansionDepth ?: number } ) {
7+ return < JsonValue isRoot value = { props . value } copyable = { props . copyable } depth = { 0 } defaultExpansionDepth = { props . defaultExpansionDepth ?? 1 } />
88}
9- type CopyState = 'NoCopy' | 'SuccessCopy' | 'ErrorCopy'
10-
11- const CopyButton = ( props : { value : unknown } ) => {
12- const styles = useStyles ( )
13- const [ copyState , setCopyState ] = createSignal < CopyState > ( 'NoCopy' )
149
15- return (
16- < button
17- class = { styles ( ) . tree . actionButton }
18- title = "Copy object to clipboard"
19- aria-label = { `${
20- copyState ( ) === 'NoCopy'
21- ? 'Copy object to clipboard'
22- : copyState ( ) === 'SuccessCopy'
23- ? 'Object copied to clipboard'
24- : 'Error copying object to clipboard'
25- } `}
26- onClick = {
27- copyState ( ) === 'NoCopy'
28- ? ( ) => {
29- navigator . clipboard
30- . writeText ( JSON . stringify ( props . value , null , 2 ) )
31- . then (
32- ( ) => {
33- setCopyState ( 'SuccessCopy' )
34- setTimeout ( ( ) => {
35- setCopyState ( 'NoCopy' )
36- } , 1500 )
37- } ,
38- ( err ) => {
39- console . error ( 'Failed to copy: ' , err )
40- setCopyState ( 'ErrorCopy' )
41- setTimeout ( ( ) => {
42- setCopyState ( 'NoCopy' )
43- } , 1500 )
44- } ,
45- )
46- }
47- : undefined
48- }
49- >
50- < Switch >
51- < Match when = { copyState ( ) === 'NoCopy' } >
52- < Copier />
53- </ Match >
54- < Match when = { copyState ( ) === 'SuccessCopy' } >
55- < CopiedCopier theme = { 'dark' } />
56- </ Match >
57- < Match when = { copyState ( ) === 'ErrorCopy' } >
58- < ErrorCopier />
59- </ Match >
60- </ Switch >
61- </ button >
62- )
63- }
64-
65- const Expander = ( props : { expanded : boolean } ) => {
66- const styles = useStyles ( )
67- return (
68- < span
69- class = { clsx (
70- styles ( ) . tree . expander ,
71- css `
72- transform : rotate (${ props . expanded ? 90 : 0 } deg);
73- ` ,
74- props . expanded &&
75- css `
76- & svg {
77- top : -1px ;
78- }
79- ` ,
80- ) }
81- >
82- < svg
83- width = "16"
84- height = "16"
85- viewBox = "0 0 16 16"
86- fill = "none"
87- xmlns = "http://www.w3.org/2000/svg"
88- >
89- < path
90- d = "M6 12L10 8L6 4"
91- stroke-width = "2"
92- stroke-linecap = "round"
93- stroke-linejoin = "round"
94- />
95- </ svg >
96- </ span >
97- )
98- }
9910
10011function JsonValue ( props : {
10112 value : any
10213 keyName ?: string
10314 isRoot ?: boolean
10415 isLastKey ?: boolean
10516 copyable ?: boolean
17+ defaultExpansionDepth : number
18+ depth : number
10619} ) {
107- const { value, keyName, isRoot = false , isLastKey, copyable } = props
20+ const { value, keyName, isRoot = false , isLastKey, copyable, defaultExpansionDepth , depth } = props
10821 const styles = useStyles ( )
10922
11023 return (
@@ -137,12 +50,12 @@ function JsonValue(props: {
13750 }
13851 if ( Array . isArray ( value ) ) {
13952 return (
140- < ArrayValue copyable = { copyable } keyName = { keyName } value = { value } />
53+ < ArrayValue defaultExpansionDepth = { defaultExpansionDepth } depth = { depth } copyable = { copyable } keyName = { keyName } value = { value } />
14154 )
14255 }
14356 if ( typeof value === 'object' ) {
14457 return (
145- < ObjectValue copyable = { copyable } keyName = { keyName } value = { value } />
58+ < ObjectValue defaultExpansionDepth = { defaultExpansionDepth } depth = { depth } copyable = { copyable } keyName = { keyName } value = { value } />
14659 )
14760 }
14861 return < span />
@@ -161,16 +74,32 @@ const ArrayValue = ({
16174 value,
16275 keyName,
16376 copyable,
77+ defaultExpansionDepth,
78+ depth
16479} : {
16580 value : Array < any >
16681 copyable ?: boolean
16782 keyName ?: string
83+ defaultExpansionDepth : number
84+ depth : number
16885} ) => {
16986 const styles = useStyles ( )
170- const [ expanded , setExpanded ] = createSignal ( true )
87+ const [ expanded , setExpanded ] = createSignal ( depth <= defaultExpansionDepth )
88+
89+ if ( value . length === 0 ) {
90+ return ( < span class = { styles ( ) . tree . expanderContainer } >
91+ { keyName && < span
92+
93+ class = { clsx ( styles ( ) . tree . valueKey , styles ( ) . tree . collapsible ) }
94+ >
95+ "{ keyName } ":{ ' ' }
96+ </ span > }
97+ < span class = { styles ( ) . tree . valueBraces } > []</ span >
98+ </ span > )
99+ }
171100 return (
172101 < span class = { styles ( ) . tree . expanderContainer } >
173- < Expander expanded = { expanded ( ) } />
102+ < Expander onClick = { ( ) => setExpanded ( ! expanded ( ) ) } expanded = { expanded ( ) } />
174103 { keyName && (
175104 < span
176105 onclick = { ( e ) => {
@@ -195,6 +124,8 @@ const ArrayValue = ({
195124 copyable = { copyable }
196125 value = { item }
197126 isLastKey = { isLastKey }
127+ defaultExpansionDepth = { defaultExpansionDepth }
128+ depth = { depth + 1 }
198129 />
199130 )
200131 } }
@@ -222,19 +153,31 @@ const ObjectValue = ({
222153 value,
223154 keyName,
224155 copyable,
156+ defaultExpansionDepth,
157+ depth
225158} : {
226159 value : Record < string , any >
227160 keyName ?: string
228161 copyable ?: boolean
162+ defaultExpansionDepth : number
163+ depth : number
229164} ) => {
230165 const styles = useStyles ( )
231- const [ expanded , setExpanded ] = createSignal ( true )
166+ const [ expanded , setExpanded ] = createSignal ( depth <= defaultExpansionDepth )
232167 const keys = Object . keys ( value )
233168 const lastKeyName = keys [ keys . length - 1 ]
234169
170+ if ( keys . length === 0 ) {
171+ return ( < span class = { styles ( ) . tree . expanderContainer } >
172+ { keyName && < span class = { clsx ( styles ( ) . tree . valueKey , styles ( ) . tree . collapsible ) } >
173+ "{ keyName } ":{ ' ' }
174+ </ span > }
175+ < span class = { styles ( ) . tree . valueBraces } > { "{}" } </ span >
176+ </ span > )
177+ }
235178 return (
236179 < span class = { styles ( ) . tree . expanderContainer } >
237- { keyName && < Expander expanded = { expanded ( ) } /> }
180+ { keyName && < Expander onClick = { ( ) => setExpanded ( ! expanded ( ) ) } expanded = { expanded ( ) } /> }
238181 { keyName && (
239182 < span
240183 onClick = { ( e ) => {
@@ -259,6 +202,8 @@ const ObjectValue = ({
259202 keyName = { k }
260203 isLastKey = { lastKeyName === k }
261204 copyable = { copyable }
205+ defaultExpansionDepth = { defaultExpansionDepth }
206+ depth = { depth + 1 }
262207 />
263208 </ >
264209 ) }
@@ -281,3 +226,94 @@ const ObjectValue = ({
281226 </ span >
282227 )
283228}
229+
230+ type CopyState = 'NoCopy' | 'SuccessCopy' | 'ErrorCopy'
231+
232+ const CopyButton = ( props : { value : unknown } ) => {
233+ const styles = useStyles ( )
234+ const [ copyState , setCopyState ] = createSignal < CopyState > ( 'NoCopy' )
235+
236+ return (
237+ < button
238+ class = { styles ( ) . tree . actionButton }
239+ title = "Copy object to clipboard"
240+ aria-label = { `${ copyState ( ) === 'NoCopy'
241+ ? 'Copy object to clipboard'
242+ : copyState ( ) === 'SuccessCopy'
243+ ? 'Object copied to clipboard'
244+ : 'Error copying object to clipboard'
245+ } `}
246+ onClick = {
247+ copyState ( ) === 'NoCopy'
248+ ? ( ) => {
249+ navigator . clipboard
250+ . writeText ( JSON . stringify ( props . value , null , 2 ) )
251+ . then (
252+ ( ) => {
253+ setCopyState ( 'SuccessCopy' )
254+ setTimeout ( ( ) => {
255+ setCopyState ( 'NoCopy' )
256+ } , 1500 )
257+ } ,
258+ ( err ) => {
259+ console . error ( 'Failed to copy: ' , err )
260+ setCopyState ( 'ErrorCopy' )
261+ setTimeout ( ( ) => {
262+ setCopyState ( 'NoCopy' )
263+ } , 1500 )
264+ } ,
265+ )
266+ }
267+ : undefined
268+ }
269+ >
270+ < Switch >
271+ < Match when = { copyState ( ) === 'NoCopy' } >
272+ < Copier />
273+ </ Match >
274+ < Match when = { copyState ( ) === 'SuccessCopy' } >
275+ < CopiedCopier theme = { 'dark' } />
276+ </ Match >
277+ < Match when = { copyState ( ) === 'ErrorCopy' } >
278+ < ErrorCopier />
279+ </ Match >
280+ </ Switch >
281+ </ button >
282+ )
283+ }
284+
285+ const Expander = ( props : { expanded : boolean , onClick : ( ) => void } ) => {
286+ const styles = useStyles ( )
287+ return (
288+ < span
289+ onClick = { props . onClick }
290+ class = { clsx (
291+ styles ( ) . tree . expander ,
292+ css `
293+ transform : rotate (${ props . expanded ? 90 : 0 } deg);
294+ ` ,
295+ props . expanded &&
296+ css `
297+ & svg {
298+ top : -1px ;
299+ }
300+ ` ,
301+ ) }
302+ >
303+ < svg
304+ width = "16"
305+ height = "16"
306+ viewBox = "0 0 16 16"
307+ fill = "none"
308+ xmlns = "http://www.w3.org/2000/svg"
309+ >
310+ < path
311+ d = "M6 12L10 8L6 4"
312+ stroke-width = "2"
313+ stroke-linecap = "round"
314+ stroke-linejoin = "round"
315+ />
316+ </ svg >
317+ </ span >
318+ )
319+ }
0 commit comments