1
1
import { Textarea } from "@/components/ui/textarea.tsx" ;
2
- import React , { useEffect , useState } from "react" ;
2
+ import React , { useMemo , useState } from "react" ;
3
3
import classNames from "classnames" ;
4
4
import { bytes } from "@typeberry/block" ;
5
5
import { logger } from "@/utils/loggerService" ;
6
+ import { useAppSelector } from "@/store/hooks.ts" ;
7
+ import { selectIsProgramInvalid } from "@/store/debugger/debuggerSlice.ts" ;
8
+
9
+ const parseArrayLikeString = ( input : string ) : ( number | string ) [ ] => {
10
+ // Remove the brackets and split the string by commas
11
+ const items = input
12
+ . replace ( / ^ \[ | \] $ / g, "" )
13
+ . split ( "," )
14
+ . map ( ( item ) => item . trim ( ) ) ;
15
+
16
+ // Process each item
17
+ return items . map ( ( item ) => {
18
+ if ( / ^ (?: 0 x ) ? [ 0 - 9 a - f A - F ] + $ / i. test ( item ) ) {
19
+ return parseInt ( item , 16 ) ;
20
+ } else if ( ! isNaN ( Number ( item ) ) ) {
21
+ return Number ( item ) ;
22
+ } else {
23
+ return item ;
24
+ }
25
+ } ) ;
26
+ } ;
6
27
7
28
export const ProgramTextLoader = ( {
8
29
program,
@@ -11,45 +32,83 @@ export const ProgramTextLoader = ({
11
32
program ?: number [ ] ;
12
33
setProgram : ( val ?: number [ ] , error ?: string ) => void ;
13
34
} ) => {
14
- const [ programInput , setProgramInput ] = useState ( program ?. length ? JSON . stringify ( program ) : "" ) ;
15
- useEffect ( ( ) => {
16
- setProgramInput ( program ?. length ? JSON . stringify ( program ) : "" ) ;
35
+ const defaultProgram = useMemo ( ( ) => {
36
+ return program ;
17
37
} , [ program ] ) ;
18
38
39
+ const [ programInput , setProgramInput ] = useState ( defaultProgram ?. length ? JSON . stringify ( defaultProgram ) : "" ) ;
40
+ const [ programError , setProgramError ] = useState ( "" ) ;
41
+ const isProgramInvalid = useAppSelector ( selectIsProgramInvalid ) ;
42
+
19
43
const handleOnChange = ( e : React . ChangeEvent < HTMLTextAreaElement > ) => {
20
44
const newInput = e . target . value . trim ( ) ;
21
45
setProgramInput ( newInput ) ;
22
46
23
47
if ( ! newInput . startsWith ( "[" ) ) {
24
48
try {
25
49
const parsedBlob = bytes . BytesBlob . parseBlob ( newInput ) ;
26
- setProgram ( Array . prototype . slice . call ( parsedBlob . raw ) ) ;
27
- } catch ( error ) {
28
- logger . error ( "Wrong binary file" , { error, hideToast : true } ) ;
29
- setProgram ( undefined , "Wrong binary file" ) ;
50
+
51
+ const parsedBlobArray = Array . prototype . slice . call ( parsedBlob . raw ) ;
52
+
53
+ if ( parsedBlobArray . length ) {
54
+ setProgram ( parsedBlobArray ) ;
55
+ }
56
+
57
+ setProgramError ( "" ) ;
58
+ } catch ( error : unknown ) {
59
+ logger . error ( "Wrong binary program" , { error, hideToast : true } ) ;
60
+
61
+ setProgram ( undefined , "Wrong binary program" ) ;
62
+
63
+ if ( error instanceof Error ) {
64
+ if ( error ?. message ) {
65
+ setProgramError ( error . message ) ;
66
+ }
67
+ }
30
68
}
31
69
} else {
32
70
try {
33
- JSON . parse ( newInput ) ;
34
- setProgram ( JSON . parse ( newInput ) ) ;
71
+ // Make sure that hex strings are parsed as strings for JSON.parse validation
72
+ const parseTest = newInput . replace ( / 0 x ( [ a - f A - F 0 - 9 ] + ) / g, '"0x$1"' ) ;
73
+ // Parse it just to check if it's a valid JSON
74
+ JSON . parse ( parseTest ) ;
75
+
76
+ const parsedJson = parseArrayLikeString ( newInput ) ;
77
+ const programArray = parsedJson . filter ( ( item ) => typeof item === "number" ) as number [ ] ;
78
+
79
+ if ( programArray . length ) {
80
+ setProgram ( programArray ) ;
81
+ }
82
+ setProgramError ( "" ) ;
35
83
} catch ( error ) {
36
84
logger . error ( "Wrong JSON" , { error, hideToast : true } ) ;
85
+
37
86
setProgram ( undefined , "Wrong JSON" ) ;
87
+
88
+ if ( error ) {
89
+ setProgramError ( error . toString ( ) ) ;
90
+ }
38
91
}
39
92
}
40
93
} ;
41
94
42
95
return (
43
96
< div className = "h-full" >
44
- < div className = { classNames ( "h-full flex-auto flex gap-1 flex-col border-2 rounded-md " ) } >
97
+ < div className = { classNames ( "h-full flex-auto flex gap-1 flex-col" ) } >
98
+ < p className = "pb-2 mb-1" >
99
+ < small > Edit program code bytes</ small >
100
+ </ p >
45
101
< Textarea
46
102
autoFocus
47
- className = { classNames ( "w-full flex-auto font-mono border-0 text-base" ) }
103
+ className = { classNames ( "w-full flex-auto font-mono text-base border-2 rounded-md" , {
104
+ "focus-visible:ring-3 focus-visible:outline-none active:outline-none border-red-500" : isProgramInvalid ,
105
+ } ) }
48
106
id = "program"
49
107
placeholder = "Paste the program as an array of numbers or hex string"
50
108
value = { programInput }
51
109
onChange = { handleOnChange }
52
110
/>
111
+ { isProgramInvalid && < span className = "text-red-500" > { programError || "Program is not valid" } </ span > }
53
112
</ div >
54
113
</ div >
55
114
) ;
0 commit comments