1
1
import { useState , useCallback } from "react"
2
2
import { PCBViewer } from "@tscircuit/pcb-viewer"
3
- import { parseDsnToCircuitJson } from "dsn-converter"
3
+ import {
4
+ convertDsnSessionToCircuitJson ,
5
+ DsnPcb ,
6
+ DsnSession ,
7
+ parseDsnToCircuitJson ,
8
+ parseDsnToDsnJson ,
9
+ } from "dsn-converter"
4
10
5
11
function App ( ) {
6
12
const [ circuitJson , setCircuitJson ] = useState < any > ( null )
13
+ const [ inDsnSessionMode , setInDsnSessionMode ] = useState ( false )
14
+ const [ sessionFile , setSessionFile ] = useState < string | null > ( null )
15
+ const [ dsnPcbFile , setDsnPcbFile ] = useState < string | null > ( null )
16
+
17
+ const isSessionFileUploaded = Boolean ( sessionFile )
18
+ const isDsnPcbFileUploaded = Boolean ( dsnPcbFile )
19
+
20
+ const processDsnUploadOrPaste = ( content : string ) => {
21
+ if ( ! inDsnSessionMode ) {
22
+ try {
23
+ const json = parseDsnToCircuitJson ( content )
24
+ setCircuitJson ( json )
25
+ } catch ( err ) {
26
+ console . log ( err )
27
+ console . error ( "Failed to parse DSN content:" , err )
28
+ alert ( "Failed to parse DSN content. Please check the format." )
29
+ }
30
+ return
31
+ }
32
+
33
+ let dsnPcbContent = dsnPcbFile
34
+ let sessionContent = sessionFile
35
+
36
+ if ( content . trim ( ) . startsWith ( "(pcb" ) ) {
37
+ dsnPcbContent = content
38
+ setDsnPcbFile ( content )
39
+ }
40
+
41
+ if ( content . trim ( ) . startsWith ( "(session" ) ) {
42
+ sessionContent = content
43
+ setSessionFile ( content )
44
+ }
45
+
46
+ if ( sessionContent && dsnPcbContent ) {
47
+ const dsnPcb = parseDsnToDsnJson ( dsnPcbContent ) as DsnPcb
48
+ const dsnSession = parseDsnToDsnJson ( sessionContent ) as DsnSession
49
+ try {
50
+ setCircuitJson ( convertDsnSessionToCircuitJson ( dsnPcb , dsnSession ) )
51
+ } catch ( err ) {
52
+ console . log ( err )
53
+ console . error ( "Failed to convert DSN session to circuit JSON:" , err )
54
+ alert ( "Failed to convert DSN session to circuit JSON." )
55
+ }
56
+ }
57
+ }
7
58
8
59
const handleDrop = useCallback ( ( e : React . DragEvent ) => {
9
60
e . preventDefault ( )
@@ -12,13 +63,7 @@ function App() {
12
63
const reader = new FileReader ( )
13
64
reader . onload = async ( e ) => {
14
65
const dsnContent = e . target ?. result as string
15
- try {
16
- const json = parseDsnToCircuitJson ( dsnContent )
17
- setCircuitJson ( json )
18
- } catch ( err ) {
19
- console . error ( "Failed to parse DSN file:" , err )
20
- alert ( "Failed to parse DSN file. Please check the format." )
21
- }
66
+ processDsnUploadOrPaste ( dsnContent )
22
67
}
23
68
reader . readAsText ( file )
24
69
}
@@ -27,13 +72,7 @@ function App() {
27
72
const handlePaste = useCallback ( ( e : React . ClipboardEvent ) => {
28
73
const dsnContent = e . clipboardData . getData ( "text" )
29
74
if ( dsnContent ) {
30
- try {
31
- const json = parseDsnToCircuitJson ( dsnContent )
32
- setCircuitJson ( json )
33
- } catch ( err ) {
34
- console . error ( "Failed to parse DSN content:" , err )
35
- alert ( "Failed to parse DSN content. Please check the format." )
36
- }
75
+ processDsnUploadOrPaste ( dsnContent )
37
76
}
38
77
} , [ ] )
39
78
@@ -54,33 +93,67 @@ function App() {
54
93
< h1 className = "text-3xl font-bold mb-4" >
55
94
Specctra DSN File Viewer
56
95
</ h1 >
57
- < p className = "text-gray-400 mb-2" > Drag and drop a DSN file here</ p >
58
- < p className = "text-gray-400" >
59
- or paste DSN content with Ctrl/CMD+V
60
- </ p >
61
- { /* biome-ignore lint/a11y/useKeyWithClickEvents: <explanation> */ }
62
- < div
63
- className = "underline cursor-pointer"
64
- onClick = { ( ) => {
65
- fetch ( "/exampledsn.dsn" )
66
- . then ( ( response ) => response . text ( ) )
67
- . then ( ( dsnContent ) => {
68
- try {
69
- const json = parseDsnToCircuitJson ( dsnContent )
70
- setCircuitJson ( json )
71
- } catch ( err ) {
72
- console . error ( "Failed to parse example DSN:" , err )
73
- alert ( "Failed to parse example DSN file." )
74
- }
75
- } )
76
- . catch ( ( err ) => {
77
- console . error ( "Failed to load example DSN:" , err )
78
- alert ( "Failed to load example DSN file." )
79
- } )
80
- } }
81
- >
82
- open example
83
- </ div >
96
+ { ! inDsnSessionMode && (
97
+ < >
98
+ < p className = "text-gray-400 mb-2" >
99
+ Drag and drop a DSN file here
100
+ </ p >
101
+ < p className = "text-gray-400" >
102
+ or paste DSN content with Ctrl/CMD+V
103
+ </ p >
104
+ </ >
105
+ ) }
106
+ { inDsnSessionMode && (
107
+ < >
108
+ < p className = "text-gray-400 mb-2" >
109
+ Drag and drop or paste a DSN session and original (DSN PCB)
110
+ file here
111
+ </ p >
112
+ < p className = "text-gray-400" >
113
+ You must upload both files to see the results.
114
+ </ p >
115
+ < p className = "text-gray-400" >
116
+ { isSessionFileUploaded ? "✅" : "❌" } Session file uploaded
117
+ </ p >
118
+ < p className = "text-gray-400" >
119
+ { isDsnPcbFileUploaded ? "✅" : "❌" } DSN PCB file uploaded
120
+ </ p >
121
+ </ >
122
+ ) }
123
+ { ! inDsnSessionMode && (
124
+ < div className = "flex gap-4 justify-center" >
125
+ < div
126
+ className = "underline cursor-pointer"
127
+ onClick = { ( ) => {
128
+ fetch ( "/exampledsn.dsn" )
129
+ . then ( ( response ) => response . text ( ) )
130
+ . then ( ( dsnContent ) => {
131
+ try {
132
+ const json = parseDsnToCircuitJson ( dsnContent )
133
+ setCircuitJson ( json )
134
+ } catch ( err ) {
135
+ console . error ( "Failed to parse example DSN:" , err )
136
+ alert ( "Failed to parse example DSN file." )
137
+ }
138
+ } )
139
+ . catch ( ( err ) => {
140
+ console . error ( "Failed to load example DSN:" , err )
141
+ alert ( "Failed to load example DSN file." )
142
+ } )
143
+ } }
144
+ >
145
+ open example
146
+ </ div >
147
+ < div
148
+ className = "underline cursor-pointer"
149
+ onClick = { ( ) => {
150
+ setInDsnSessionMode ( true )
151
+ } }
152
+ >
153
+ upload session
154
+ </ div >
155
+ </ div >
156
+ ) }
84
157
</ div >
85
158
< div className = "text-gray-400 text-sm mt-16" >
86
159
Unofficial Specctra DSN Parser/Viewer created by{ " " }
0 commit comments