1
- import { ISimulationBlock , ISimulationTransaction } from '@/contexts/SimContext/types' ;
1
+ import { ISimulationBlock , ISimulationEndorsementBlock , ISimulationInputBlock , ISimulationTransaction } from '@/contexts/SimContext/types' ;
2
2
import cx from "classnames" ;
3
- import { CSSProperties , FC , MouseEvent , PropsWithChildren , useMemo , useState } from "react" ;
3
+ import { CSSProperties , FC , MouseEvent , PropsWithChildren , ReactElement , useMemo , useState } from "react" ;
4
4
5
5
import { printBytes } from '@/utils' ;
6
6
import classes from "./styles.module.css" ;
@@ -12,21 +12,34 @@ export interface IBlockContentsProps {
12
12
interface IBoxProps extends PropsWithChildren {
13
13
className ?: string ;
14
14
style ?: CSSProperties ;
15
- selected ?: boolean ;
16
- select ?: ( e : MouseEvent ) => void ;
15
+ hovered ?: boolean ;
16
+ onHover ?: ( e : MouseEvent ) => void ;
17
+ onClick ?: ( e : MouseEvent ) => void ;
17
18
}
18
19
19
- const Box : FC < IBoxProps > = ( { selected , select , children, className, style } ) => {
20
- const color = selected ? "border-black" : "border-gray-400" ;
20
+ const Box : FC < IBoxProps > = ( { hovered , onHover , onClick , children, className, style } ) => {
21
+ const color = hovered ? "border-black" : "border-gray-400" ;
21
22
return (
22
- < span className = { cx ( "border-2 border-solid min-h-24 max-h-48 w-48 flex flex-col items-center justify-center text-center" , color , { 'cursor-pointer' : ! ! select } , { [ classes . selected ] : selected } , className ) } style = { style }
23
- onMouseEnter = { select }
24
- onMouseMove = { select } >
23
+ < span className = { cx ( "border-2 border-solid min-h-24 max-h-48 w-48 flex flex-col items-center justify-center text-center" , color , { 'cursor-pointer' : ! ! onHover } , { [ classes . hovered ] : hovered } , className ) } style = { style }
24
+ onMouseEnter = { onHover }
25
+ onMouseMove = { onHover }
26
+ onClick = { onClick } >
25
27
{ children }
26
28
</ span >
27
29
)
28
30
}
29
31
32
+ interface ITabButtonProps {
33
+ name : string ;
34
+ active : boolean ;
35
+ onClick : ( ) => void ;
36
+ }
37
+
38
+ const TabButton : FC < ITabButtonProps > = ( { name, active, onClick } ) => {
39
+ const color = active ? "bg-[blue]" : "bg-gray-400" ;
40
+ return < button className = { `${ color } text-white rounded-md px-4 py-2` } onClick = { onClick } > { name } </ button >
41
+ }
42
+
30
43
interface ITXStats {
31
44
name : string ;
32
45
slot : number ;
@@ -64,11 +77,92 @@ const Stats: FC<IStatsProps> = ({ name, slot, txs, breakdown, position: [left, t
64
77
)
65
78
}
66
79
67
- interface SelectState {
80
+ interface HoverState {
68
81
key : string ,
69
82
position : [ number , number ]
70
83
}
71
84
85
+ enum ContentsView {
86
+ Input ,
87
+ Endorser ,
88
+ }
89
+
90
+ interface IBlockProps {
91
+ block : ISimulationBlock ,
92
+ hovered : boolean ,
93
+ onHover : ( e : MouseEvent ) => void ;
94
+ }
95
+
96
+ const Block : FC < IBlockProps > = ( { block, hovered, onHover } ) => {
97
+ return < div className = { classes . block } >
98
+ < Box hovered = { hovered } onHover = { onHover } >
99
+ Block
100
+ < span className = 'text-sm' > Slot { block . slot } , { block . txs . length } TX</ span >
101
+ </ Box >
102
+ </ div >
103
+ }
104
+
105
+ interface IInputBlocksProps {
106
+ ibs : ISimulationInputBlock [ ] ,
107
+ block : ISimulationBlock ,
108
+ hovered : HoverState | null ,
109
+ onHover : ( box : string | null ) => ( e : MouseEvent ) => void ;
110
+ }
111
+
112
+ const InputBlocksList : FC < IInputBlocksProps > = ( { ibs, hovered, block, onHover } ) => {
113
+ return < div className = { cx ( "border-2 p-[25px] border-black max-w-[80vw] overflow-scroll" ) } >
114
+ < div className = "flex gap-2" >
115
+ { ibs . map ( ib => {
116
+ const isHovered = hovered ?. key === ib . id ;
117
+ const proportion = Math . min ( 100 , ib . txs . length * 1000 / block . txs . length ) ;
118
+ return (
119
+ < Box key = { ib . id } style = { { backgroundColor : `color-mix(in hsl, white, #82ca9d ${ proportion } %)` } } hovered = { isHovered } onHover = { onHover ( ib . id ) } className = { classes . input } >
120
+ { isHovered ? 'Input Block' : 'IB' }
121
+ < span className = "text-sm" > Slot { ib . slot } , { ib . txs . length } TX</ span >
122
+ </ Box >
123
+ ) ;
124
+ } ) }
125
+ </ div >
126
+ </ div >
127
+ } ;
128
+
129
+ interface IEndorserBlockProps {
130
+ eb : ISimulationEndorsementBlock ,
131
+ hovered : boolean ,
132
+ onHover : ( e : MouseEvent ) => void ;
133
+ onClick ?: ( e : MouseEvent ) => void ;
134
+ }
135
+
136
+ const EndorserBlock : FC < IEndorserBlockProps > = ( { eb, hovered, onHover, onClick } ) => {
137
+ return < div className = { classes . endorser } >
138
+ < Box hovered = { hovered } onHover = { onHover } onClick = { onClick } >
139
+ Endorser Block
140
+ < span className = 'text-sm' > Slot { eb . slot } </ span >
141
+ { eb . ibs . length ? < span className = 'text-sm' > References { eb . ibs . length } IB(s)</ span > : null }
142
+ { eb . ebs . length ? < span className = 'text-sm' > References { eb . ebs . length } EB(s)</ span > : null }
143
+ </ Box >
144
+ </ div >
145
+ } ;
146
+
147
+ interface IEndorserBlocksProps {
148
+ ebs : ISimulationEndorsementBlock [ ] ,
149
+ hovered : HoverState | null ,
150
+ onHover : ( box : string | null ) => ( e : MouseEvent ) => void ;
151
+ onClick : ( eb : ISimulationEndorsementBlock ) => ( e : MouseEvent ) => void ;
152
+ }
153
+
154
+ const EndorserBlocksList : FC < IEndorserBlocksProps > = ( { ebs, hovered, onHover, onClick } ) => {
155
+ return < div className = { cx ( "border-2 p-[25px] border-black max-w-[80vw] overflow-scroll" ) } >
156
+ < div className = "flex gap-2" >
157
+ { ebs . map ( eb => {
158
+ return (
159
+ < EndorserBlock key = { eb . id } eb = { eb } hovered = { hovered ?. key == eb . id } onHover = { onHover ( eb . id ) } onClick = { onClick ( eb ) } />
160
+ ) ;
161
+ } ) }
162
+ </ div >
163
+ </ div >
164
+ } ;
165
+
72
166
export const BlockContents : FC < IBlockContentsProps > = ( { block } ) => {
73
167
const stats = useMemo ( ( ) => {
74
168
const result : Map < string , ITXStats > = new Map ( ) ;
@@ -85,80 +179,94 @@ export const BlockContents: FC<IBlockContentsProps> = ({ block }) => {
85
179
breakdown,
86
180
} ) ;
87
181
if ( block . cert ) {
88
- result . set ( "eb" , {
89
- name : "Endorsement Block" ,
90
- slot : block . cert . eb . slot ,
91
- txs : null ,
92
- breakdown : [ [ "Size" , block . cert . eb . bytes ] ] ,
93
- } ) ;
94
- for ( const ib of block . cert . eb . ibs ) {
95
- result . set ( ib . id , {
96
- name : "Input Block" ,
97
- slot : ib . slot ,
98
- txs : ib . txs ,
99
- breakdown : [
100
- [ "Header size" , ib . headerBytes ] ,
101
- ] ,
182
+ const ebs = [ block . cert . eb ] ;
183
+ while ( ebs . length ) {
184
+ const eb = ebs . pop ( ) ! ;
185
+ result . set ( eb . id , {
186
+ name : "Endorser Block" ,
187
+ slot : eb . slot ,
188
+ txs : null ,
189
+ breakdown : [ [ "Size" , eb . bytes ] ] ,
102
190
} ) ;
191
+ for ( const ib of eb . ibs ) {
192
+ result . set ( ib . id , {
193
+ name : "Input Block" ,
194
+ slot : ib . slot ,
195
+ txs : ib . txs ,
196
+ breakdown : [
197
+ [ "Header size" , ib . headerBytes ] ,
198
+ ] ,
199
+ } ) ;
200
+ }
201
+ for ( const nestedEb of eb . ebs ) {
202
+ ebs . push ( nestedEb ) ;
203
+ }
103
204
}
104
205
}
105
206
return result ;
106
207
} , [ block ] ) ;
107
208
108
- const [ selected , setSelected ] = useState < SelectState | null > ( null ) ;
109
- const selectBox = ( box : string | null ) => ( e : MouseEvent ) => {
209
+ const [ hovered , setHovered ] = useState < HoverState | null > ( null ) ;
210
+ const [ viewing , setViewing ] = useState ( ContentsView . Input ) ;
211
+ const [ ebTrail , setEbTrail ] = useState ( block . cert ?. eb ? [ block . cert . eb ] : [ ] ) ;
212
+
213
+ const onHover = ( box : string | null ) => ( e : MouseEvent ) => {
110
214
e . stopPropagation ( ) ;
111
215
if ( box ) {
112
- setSelected ( {
216
+ setHovered ( {
113
217
key : box ,
114
218
position : [ e . pageX , e . pageY ] ,
115
219
} ) ;
116
220
} else {
117
- setSelected ( null ) ;
221
+ setHovered ( null ) ;
118
222
}
119
223
} ;
120
224
121
- const eb = block . cert ?. eb ;
122
- const ibs = block . cert ?. eb ?. ibs ?? [ ] ;
225
+ const viewParentEb = ( ) => {
226
+ setEbTrail ( trail => trail . slice ( 1 ) ) ;
227
+ }
228
+ const viewChildEb = ( eb : ISimulationEndorsementBlock ) => ( ) => {
229
+ setEbTrail ( trail => [ eb , ...trail ] ) ;
230
+ }
231
+
232
+ const eb : ISimulationEndorsementBlock | undefined = ebTrail [ 0 ] ;
233
+ const ibs = eb ?. ibs ?? [ ] ;
234
+ const ebs = eb ?. ebs ?? [ ] ;
235
+
236
+ const parent = ebTrail [ 1 ] ;
237
+ const topRow = parent ? (
238
+ < EndorserBlock eb = { parent } hovered = { hovered ?. key == parent . id } onHover = { onHover ( parent . id ) } onClick = { viewParentEb } />
239
+ ) : (
240
+ < Block block = { block } hovered = { hovered ?. key == "block" } onHover = { onHover ( "block" ) } />
241
+ )
242
+
243
+ let bottomRow : ReactElement | null = null ;
244
+ if ( viewing == ContentsView . Input && ibs . length ) {
245
+ bottomRow = < InputBlocksList ibs = { ibs } block = { block } hovered = { hovered } onHover = { onHover } />
246
+ }
247
+ if ( viewing == ContentsView . Endorser && ebs . length ) {
248
+ bottomRow = < EndorserBlocksList ebs = { ebs } hovered = { hovered } onHover = { onHover } onClick = { viewChildEb } />
249
+ }
123
250
124
251
return (
125
252
< >
126
- { selected && < Stats { ...stats . get ( selected . key ) ! } position = { selected . position } onMouseMove = { selectBox ( selected . key ) } /> }
253
+ { hovered && < Stats { ...stats . get ( hovered . key ) ! } position = { hovered . position } onMouseMove = { onHover ( hovered . key ) } /> }
127
254
128
- < div className = 'flex flex-col w-full h-3/5 items-center' onMouseMove = { selectBox ( null ) } >
255
+ < div className = 'flex flex-col w-full h-3/5 items-center' onMouseMove = { onHover ( null ) } >
129
256
< h2 className = 'font-bold text-xl' > Block Transactions</ h2 >
130
257
< div className = "flex flex-col w-full h-full items-center justify-center" >
131
- < div className = { classes . block } >
132
- < Box selected = { selected ?. key === "block" } select = { selectBox ( "block" ) } >
133
- Block
134
- < span className = 'text-sm' > Slot { block . slot } , { block . txs . length } TX</ span >
135
- </ Box >
136
- </ div >
258
+ { topRow }
137
259
{ eb && ( < span className = "flex flex-none w-[2px] h-[50px] bg-black" /> ) }
138
260
{ eb && (
139
- < div className = { classes . endorser } >
140
- < Box selected = { selected ?. key === "eb" } select = { selectBox ( "eb" ) } >
141
- Endorsement Block
142
- < span className = 'text-sm' > Slot { eb . slot } </ span >
143
- </ Box >
261
+ < div className = 'flex gap-4 items-center' >
262
+ < TabButton name = "View IB(s)" active = { viewing == ContentsView . Input } onClick = { ( ) => setViewing ( ContentsView . Input ) } />
263
+ < EndorserBlock eb = { eb } hovered = { hovered ?. key == eb . id } onHover = { onHover ( eb . id ) } />
264
+ < TabButton name = "View EB(s)" active = { viewing == ContentsView . Endorser } onClick = { ( ) => setViewing ( ContentsView . Endorser ) } />
144
265
</ div >
145
266
) }
146
- { ibs . length ? < >
147
- < span className = "flex flex-none w-[2px] h-[25px] bg-black" />
148
- < div className = { cx ( "border-2 p-[25px] border-black max-w-[80vw] overflow-scroll" ) } >
149
- < div className = "flex gap-2" >
150
- { ibs . map ( ib => {
151
- const isSelected = selected ?. key === ib . id ;
152
- const proportion = Math . min ( 100 , ib . txs . length * 1000 / block . txs . length ) ;
153
- return (
154
- < Box key = { ib . id } style = { { backgroundColor : `color-mix(in hsl, white, #82ca9d ${ proportion } %)` } } selected = { isSelected } select = { selectBox ( ib . id ) } className = { classes . input } >
155
- { isSelected ? 'Input Block' : 'IB' }
156
- < span className = "text-sm" > Slot { ib . slot } , { ib . txs . length } TX</ span >
157
- </ Box >
158
- ) ;
159
- } ) }
160
- </ div >
161
- </ div >
267
+ { bottomRow ? < >
268
+ < span className = "flex flex-none w-[2px] h-[50px] bg-black" />
269
+ { bottomRow }
162
270
</ > : null }
163
271
</ div >
164
272
</ div >
0 commit comments