@@ -3,97 +3,19 @@ import clsx from 'clsx'
3
3
import { css , useStyles } from '../styles/use-styles'
4
4
import { CopiedCopier , Copier , ErrorCopier } from './icons'
5
5
6
- export function JsonTree ( props : { value : any ; copyable ?: boolean } ) {
7
- return < JsonValue isRoot value = { props . value } copyable = { props . copyable } />
8
- }
9
- type CopyState = 'NoCopy' | 'SuccessCopy' | 'ErrorCopy'
10
-
11
- const CopyButton = ( props : { value : unknown } ) => {
12
- const styles = useStyles ( )
13
- const [ copyState , setCopyState ] = createSignal < CopyState > ( 'NoCopy' )
14
-
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 ( )
6
+ export function JsonTree ( props : {
7
+ value : any
8
+ copyable ?: boolean
9
+ defaultExpansionDepth ?: number
10
+ } ) {
67
11
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 >
12
+ < JsonValue
13
+ isRoot
14
+ value = { props . value }
15
+ copyable = { props . copyable }
16
+ depth = { 0 }
17
+ defaultExpansionDepth = { props . defaultExpansionDepth ?? 1 }
18
+ />
97
19
)
98
20
}
99
21
@@ -103,8 +25,18 @@ function JsonValue(props: {
103
25
isRoot ?: boolean
104
26
isLastKey ?: boolean
105
27
copyable ?: boolean
28
+ defaultExpansionDepth : number
29
+ depth : number
106
30
} ) {
107
- const { value, keyName, isRoot = false , isLastKey, copyable } = props
31
+ const {
32
+ value,
33
+ keyName,
34
+ isRoot = false ,
35
+ isLastKey,
36
+ copyable,
37
+ defaultExpansionDepth,
38
+ depth,
39
+ } = props
108
40
const styles = useStyles ( )
109
41
110
42
return (
@@ -137,12 +69,24 @@ function JsonValue(props: {
137
69
}
138
70
if ( Array . isArray ( value ) ) {
139
71
return (
140
- < ArrayValue copyable = { copyable } keyName = { keyName } value = { value } />
72
+ < ArrayValue
73
+ defaultExpansionDepth = { defaultExpansionDepth }
74
+ depth = { depth }
75
+ copyable = { copyable }
76
+ keyName = { keyName }
77
+ value = { value }
78
+ />
141
79
)
142
80
}
143
81
if ( typeof value === 'object' ) {
144
82
return (
145
- < ObjectValue copyable = { copyable } keyName = { keyName } value = { value } />
83
+ < ObjectValue
84
+ defaultExpansionDepth = { defaultExpansionDepth }
85
+ depth = { depth }
86
+ copyable = { copyable }
87
+ keyName = { keyName }
88
+ value = { value }
89
+ />
146
90
)
147
91
}
148
92
return < span />
@@ -161,16 +105,36 @@ const ArrayValue = ({
161
105
value,
162
106
keyName,
163
107
copyable,
108
+ defaultExpansionDepth,
109
+ depth,
164
110
} : {
165
111
value : Array < any >
166
112
copyable ?: boolean
167
113
keyName ?: string
114
+ defaultExpansionDepth : number
115
+ depth : number
168
116
} ) => {
169
117
const styles = useStyles ( )
170
- const [ expanded , setExpanded ] = createSignal ( true )
118
+ const [ expanded , setExpanded ] = createSignal ( depth <= defaultExpansionDepth )
119
+
120
+ if ( value . length === 0 ) {
121
+ return (
122
+ < span class = { styles ( ) . tree . expanderContainer } >
123
+ { keyName && (
124
+ < span class = { clsx ( styles ( ) . tree . valueKey , styles ( ) . tree . collapsible ) } >
125
+ "{ keyName } ":{ ' ' }
126
+ </ span >
127
+ ) }
128
+ < span class = { styles ( ) . tree . valueBraces } > []</ span >
129
+ </ span >
130
+ )
131
+ }
171
132
return (
172
133
< span class = { styles ( ) . tree . expanderContainer } >
173
- < Expander expanded = { expanded ( ) } />
134
+ < Expander
135
+ onClick = { ( ) => setExpanded ( ! expanded ( ) ) }
136
+ expanded = { expanded ( ) }
137
+ />
174
138
{ keyName && (
175
139
< span
176
140
onclick = { ( e ) => {
@@ -195,6 +159,8 @@ const ArrayValue = ({
195
159
copyable = { copyable }
196
160
value = { item }
197
161
isLastKey = { isLastKey }
162
+ defaultExpansionDepth = { defaultExpansionDepth }
163
+ depth = { depth + 1 }
198
164
/>
199
165
)
200
166
} }
@@ -222,19 +188,40 @@ const ObjectValue = ({
222
188
value,
223
189
keyName,
224
190
copyable,
191
+ defaultExpansionDepth,
192
+ depth,
225
193
} : {
226
194
value : Record < string , any >
227
195
keyName ?: string
228
196
copyable ?: boolean
197
+ defaultExpansionDepth : number
198
+ depth : number
229
199
} ) => {
230
200
const styles = useStyles ( )
231
- const [ expanded , setExpanded ] = createSignal ( true )
201
+ const [ expanded , setExpanded ] = createSignal ( depth <= defaultExpansionDepth )
232
202
const keys = Object . keys ( value )
233
203
const lastKeyName = keys [ keys . length - 1 ]
234
204
205
+ if ( keys . length === 0 ) {
206
+ return (
207
+ < span class = { styles ( ) . tree . expanderContainer } >
208
+ { keyName && (
209
+ < span class = { clsx ( styles ( ) . tree . valueKey , styles ( ) . tree . collapsible ) } >
210
+ "{ keyName } ":{ ' ' }
211
+ </ span >
212
+ ) }
213
+ < span class = { styles ( ) . tree . valueBraces } > { '{}' } </ span >
214
+ </ span >
215
+ )
216
+ }
235
217
return (
236
218
< span class = { styles ( ) . tree . expanderContainer } >
237
- { keyName && < Expander expanded = { expanded ( ) } /> }
219
+ { keyName && (
220
+ < Expander
221
+ onClick = { ( ) => setExpanded ( ! expanded ( ) ) }
222
+ expanded = { expanded ( ) }
223
+ />
224
+ ) }
238
225
{ keyName && (
239
226
< span
240
227
onClick = { ( e ) => {
@@ -259,6 +246,8 @@ const ObjectValue = ({
259
246
keyName = { k }
260
247
isLastKey = { lastKeyName === k }
261
248
copyable = { copyable }
249
+ defaultExpansionDepth = { defaultExpansionDepth }
250
+ depth = { depth + 1 }
262
251
/>
263
252
</ >
264
253
) }
@@ -281,3 +270,95 @@ const ObjectValue = ({
281
270
</ span >
282
271
)
283
272
}
273
+
274
+ type CopyState = 'NoCopy' | 'SuccessCopy' | 'ErrorCopy'
275
+
276
+ const CopyButton = ( props : { value : unknown } ) => {
277
+ const styles = useStyles ( )
278
+ const [ copyState , setCopyState ] = createSignal < CopyState > ( 'NoCopy' )
279
+
280
+ return (
281
+ < button
282
+ class = { styles ( ) . tree . actionButton }
283
+ title = "Copy object to clipboard"
284
+ aria-label = { `${
285
+ copyState ( ) === 'NoCopy'
286
+ ? 'Copy object to clipboard'
287
+ : copyState ( ) === 'SuccessCopy'
288
+ ? 'Object copied to clipboard'
289
+ : 'Error copying object to clipboard'
290
+ } `}
291
+ onClick = {
292
+ copyState ( ) === 'NoCopy'
293
+ ? ( ) => {
294
+ navigator . clipboard
295
+ . writeText ( JSON . stringify ( props . value , null , 2 ) )
296
+ . then (
297
+ ( ) => {
298
+ setCopyState ( 'SuccessCopy' )
299
+ setTimeout ( ( ) => {
300
+ setCopyState ( 'NoCopy' )
301
+ } , 1500 )
302
+ } ,
303
+ ( err ) => {
304
+ console . error ( 'Failed to copy: ' , err )
305
+ setCopyState ( 'ErrorCopy' )
306
+ setTimeout ( ( ) => {
307
+ setCopyState ( 'NoCopy' )
308
+ } , 1500 )
309
+ } ,
310
+ )
311
+ }
312
+ : undefined
313
+ }
314
+ >
315
+ < Switch >
316
+ < Match when = { copyState ( ) === 'NoCopy' } >
317
+ < Copier />
318
+ </ Match >
319
+ < Match when = { copyState ( ) === 'SuccessCopy' } >
320
+ < CopiedCopier theme = { 'dark' } />
321
+ </ Match >
322
+ < Match when = { copyState ( ) === 'ErrorCopy' } >
323
+ < ErrorCopier />
324
+ </ Match >
325
+ </ Switch >
326
+ </ button >
327
+ )
328
+ }
329
+
330
+ const Expander = ( props : { expanded : boolean ; onClick : ( ) => void } ) => {
331
+ const styles = useStyles ( )
332
+ return (
333
+ < span
334
+ onClick = { props . onClick }
335
+ class = { clsx (
336
+ styles ( ) . tree . expander ,
337
+ css `
338
+ transform : rotate (${ props . expanded ? 90 : 0 } deg);
339
+ ` ,
340
+ props . expanded &&
341
+ css `
342
+ & svg {
343
+ top : -1px ;
344
+ }
345
+ ` ,
346
+ ) }
347
+ >
348
+ < svg
349
+ width = "16"
350
+ height = "16"
351
+ viewBox = "0 0 16 16"
352
+ fill = "none"
353
+ xmlns = "http://www.w3.org/2000/svg"
354
+ >
355
+ < path
356
+ d = "M6 12L10 8L6 4"
357
+ stroke-width = "2"
358
+ stroke-linecap = "round"
359
+ stroke-linejoin = "round"
360
+ />
361
+ </ svg >
362
+ </ span >
363
+ )
364
+ }
0 commit comments