@tscircuit/fake-snippets 0.0.43 → 0.0.45
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bun.lock +378 -26
- package/dist/bundle.js +411 -335
- package/fake-snippets-api/routes/api/_fake/received_quotes.ts +66 -0
- package/fake-snippets-api/routes/api/package_files/create_or_update.ts +9 -0
- package/fake-snippets-api/routes/api/package_releases/update.ts +25 -18
- package/fake-snippets-api/routes/api/packages/update.ts +1 -0
- package/package.json +6 -6
- package/src/App.tsx +8 -0
- package/src/components/CodeAndPreview.tsx +0 -1
- package/src/components/CodeEditor.tsx +5 -4
- package/src/components/CodeEditorHeader.tsx +1 -26
- package/src/components/DownloadButtonAndMenu.tsx +3 -2
- package/src/components/EditorNav.tsx +13 -11
- package/src/components/ErrorOutline.tsx +35 -0
- package/src/components/FileSidebar.tsx +114 -0
- package/src/components/NotFound.tsx +37 -0
- package/src/components/PreviewContent.tsx +0 -6
- package/src/components/TrendingSnippetCarousel.tsx +1 -1
- package/src/components/ViewPackagePage/components/package-header.tsx +24 -3
- package/src/components/ViewPackagePage/utils/is-hidden-file.ts +0 -1
- package/src/components/dialogs/package-visibility-settings-dialog.tsx +10 -1
- package/src/components/dialogs/rename-package-dialog.tsx +81 -0
- package/src/components/dialogs/update-package-description-dialog.tsx +96 -0
- package/src/components/dialogs/view-ts-files-dialog.tsx +0 -6
- package/src/components/package-port/CodeAndPreview.tsx +419 -0
- package/src/components/package-port/CodeEditor.tsx +498 -0
- package/src/components/package-port/CodeEditorHeader.tsx +231 -0
- package/src/components/package-port/EditorNav.tsx +520 -0
- package/src/components/ui/tree-view.tsx +494 -0
- package/src/hooks/use-package.ts +23 -0
- package/src/hooks/useForkPackageMutation.ts +49 -0
- package/src/hooks/usePackageFilesLoader.ts +56 -0
- package/src/hooks/useUpdatePackageFilesMutation.ts +86 -0
- package/src/hooks/useUpdatePackageMutation.ts +63 -0
- package/src/{prettier.ts → lib/types.ts} +3 -1
- package/src/lib/utils/checkIfManualEditsImported.ts +1 -1
- package/src/lib/utils/findTargetFile.ts +62 -0
- package/src/lib/utils/load-prettier.ts +3 -0
- package/src/pages/404.tsx +2 -33
- package/src/pages/package-editor.tsx +55 -0
- package/src/pages/user-profile.tsx +66 -27
- package/src/components/FootprintDialog.tsx +0 -339
- package/src/components/ParametersEditor.tsx +0 -140
- package/src/lib/utils/parseFootprintParams.ts +0 -52
|
@@ -1,339 +0,0 @@
|
|
|
1
|
-
import { Input } from "./ui/input"
|
|
2
|
-
import { useEffect, useMemo, useState } from "react"
|
|
3
|
-
import { parseFootprintParams } from "../lib/utils/parseFootprintParams"
|
|
4
|
-
import ParametersEditor from "./ParametersEditor"
|
|
5
|
-
import { convertCircuitJsonToPcbSvg } from "circuit-to-svg"
|
|
6
|
-
import { fp, getFootprintNamesByType } from "@tscircuit/footprinter"
|
|
7
|
-
import { useToast } from "../hooks/use-toast"
|
|
8
|
-
import { Button } from "./ui/button"
|
|
9
|
-
import { FileName } from "./CodeEditorHeader"
|
|
10
|
-
import {
|
|
11
|
-
Dialog,
|
|
12
|
-
DialogContent,
|
|
13
|
-
DialogDescription,
|
|
14
|
-
DialogHeader,
|
|
15
|
-
DialogTitle,
|
|
16
|
-
} from "./ui/dialog"
|
|
17
|
-
import { Copy, Check } from "lucide-react"
|
|
18
|
-
import { Combobox } from "./ui/combobox"
|
|
19
|
-
|
|
20
|
-
interface FootprintDialogProps {
|
|
21
|
-
currentFile: FileName
|
|
22
|
-
open: boolean
|
|
23
|
-
onOpenChange: (open: boolean) => void
|
|
24
|
-
updateFileContent: (filename: FileName, content: string) => void
|
|
25
|
-
files: Record<string, string>
|
|
26
|
-
cursorPosition?: number | null
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const PARAM_NAMES: any = {
|
|
30
|
-
p: "Pitch",
|
|
31
|
-
w: "Width",
|
|
32
|
-
num_pins: "Number of Pins",
|
|
33
|
-
pl: "Pad Length",
|
|
34
|
-
pw: "Pad Width",
|
|
35
|
-
id: "Inner Diameter",
|
|
36
|
-
od: "Outer Diameter",
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export const FootprintDialog = ({
|
|
40
|
-
currentFile,
|
|
41
|
-
open,
|
|
42
|
-
onOpenChange,
|
|
43
|
-
updateFileContent,
|
|
44
|
-
files,
|
|
45
|
-
cursorPosition,
|
|
46
|
-
}: FootprintDialogProps) => {
|
|
47
|
-
const [footprintString, setFootprintString] = useState("")
|
|
48
|
-
const [footprintName, setFootprintName] = useState("")
|
|
49
|
-
const [previewSvg, setPreviewSvg] = useState<string | null>(null)
|
|
50
|
-
const [chipName, setChipName] = useState("")
|
|
51
|
-
const [footprintNameError, setFootprintNameError] = useState(false)
|
|
52
|
-
const [copied, setCopied] = useState(false)
|
|
53
|
-
const [error, setError] = useState<string | null>(null)
|
|
54
|
-
const { toast } = useToast()
|
|
55
|
-
|
|
56
|
-
const { normalFootprintNames } = getFootprintNamesByType()
|
|
57
|
-
|
|
58
|
-
useEffect(() => {
|
|
59
|
-
if (copied) {
|
|
60
|
-
const timeout = setTimeout(() => {
|
|
61
|
-
setCopied(false)
|
|
62
|
-
}, 1000)
|
|
63
|
-
return () => clearTimeout(timeout)
|
|
64
|
-
}
|
|
65
|
-
}, [copied])
|
|
66
|
-
|
|
67
|
-
const params: any = useMemo(() => {
|
|
68
|
-
try {
|
|
69
|
-
return fp.string(footprintString).json()
|
|
70
|
-
} catch (error) {
|
|
71
|
-
return null
|
|
72
|
-
}
|
|
73
|
-
}, [footprintName, footprintString])
|
|
74
|
-
|
|
75
|
-
const updateFootprintString = (baseName: string, currentParams: any) => {
|
|
76
|
-
try {
|
|
77
|
-
const parsedParams = parseFootprintParams(currentParams)
|
|
78
|
-
|
|
79
|
-
if (parsedParams.missing && Array.isArray(parsedParams.missing)) {
|
|
80
|
-
parsedParams.missing =
|
|
81
|
-
parsedParams.missing.length > 0
|
|
82
|
-
? `missing(${parsedParams.missing.join(",")})`
|
|
83
|
-
: "missing()"
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (
|
|
87
|
-
parsedParams.grid === "0x0" ||
|
|
88
|
-
parsedParams.grid === "0x" ||
|
|
89
|
-
/^(\d+x0|0x\d+)$/.test(parsedParams.grid as string)
|
|
90
|
-
) {
|
|
91
|
-
delete parsedParams.grid
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const paramsString = Object.entries(parsedParams)
|
|
95
|
-
.filter(([key]) => key !== "fn" && key !== "num_pins")
|
|
96
|
-
.map(([key, val]) => {
|
|
97
|
-
if (typeof val === "boolean") return val ? key : ""
|
|
98
|
-
if (key === "missing") return val
|
|
99
|
-
return `${key}${val}`
|
|
100
|
-
})
|
|
101
|
-
.filter((item) => item !== "")
|
|
102
|
-
.join("_")
|
|
103
|
-
|
|
104
|
-
const newFootprintString = paramsString
|
|
105
|
-
? `${baseName}_${paramsString}`
|
|
106
|
-
: baseName
|
|
107
|
-
setFootprintString(newFootprintString)
|
|
108
|
-
handleFootprintPreview(newFootprintString)
|
|
109
|
-
} catch (error) {
|
|
110
|
-
console.error("Error updating footprint string:", error)
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const updateParam = (
|
|
115
|
-
paramName: string,
|
|
116
|
-
value: string | number | boolean | string[],
|
|
117
|
-
) => {
|
|
118
|
-
try {
|
|
119
|
-
let currentParams = parseFootprintParams({ ...params })
|
|
120
|
-
if (paramName === "num_pins") {
|
|
121
|
-
if (Number(value) < 1) value = 1
|
|
122
|
-
if (Number(value) > 4000) value = 4000
|
|
123
|
-
const baseNameWithoutNumber = footprintName.replace(/\d+$/, "")
|
|
124
|
-
const newName = `${baseNameWithoutNumber}${value}`
|
|
125
|
-
updateFootprintString(newName, currentParams)
|
|
126
|
-
return
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
currentParams[paramName] = value
|
|
130
|
-
if (currentParams.missing && Array.isArray(currentParams.missing)) {
|
|
131
|
-
currentParams.missing =
|
|
132
|
-
currentParams.missing.length > 0
|
|
133
|
-
? `missing(${currentParams.missing.join(",")})`
|
|
134
|
-
: "missing()"
|
|
135
|
-
}
|
|
136
|
-
currentParams = parseFootprintParams(currentParams)
|
|
137
|
-
const pinMatch = footprintString.match(/\d+(?=(_|$))/)
|
|
138
|
-
const pinNumber = pinMatch ? pinMatch[0] : ""
|
|
139
|
-
const baseNameWithoutNumber = footprintName.replace(/\d+$/, "")
|
|
140
|
-
const nameWithNumber = pinNumber
|
|
141
|
-
? `${baseNameWithoutNumber}${pinNumber}`
|
|
142
|
-
: footprintName
|
|
143
|
-
|
|
144
|
-
updateFootprintString(nameWithNumber, currentParams)
|
|
145
|
-
} catch (error) {
|
|
146
|
-
console.error("Error updating parameter:", error)
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
const handleFootprintPreview = async (str: string) => {
|
|
151
|
-
try {
|
|
152
|
-
const circuitJson = fp.string(str).circuitJson()
|
|
153
|
-
const svg = convertCircuitJsonToPcbSvg(circuitJson)
|
|
154
|
-
setFootprintNameError(false)
|
|
155
|
-
setPreviewSvg(svg)
|
|
156
|
-
setError(null)
|
|
157
|
-
} catch (error) {
|
|
158
|
-
setFootprintNameError(true)
|
|
159
|
-
setPreviewSvg(null)
|
|
160
|
-
setError(
|
|
161
|
-
error instanceof Error
|
|
162
|
-
? error.message
|
|
163
|
-
: "Invalid footprint configuration",
|
|
164
|
-
)
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
const handleInsertFootprint = () => {
|
|
169
|
-
try {
|
|
170
|
-
const tsxCode = `\n
|
|
171
|
-
<chip
|
|
172
|
-
name="${chipName}"
|
|
173
|
-
footprint="${footprintString}"
|
|
174
|
-
/>\n`
|
|
175
|
-
const currentContent = files[currentFile]
|
|
176
|
-
|
|
177
|
-
if (cursorPosition !== undefined && cursorPosition !== null) {
|
|
178
|
-
const newContent =
|
|
179
|
-
currentContent.slice(0, cursorPosition) +
|
|
180
|
-
tsxCode +
|
|
181
|
-
currentContent.slice(cursorPosition)
|
|
182
|
-
updateFileContent(currentFile, newContent)
|
|
183
|
-
} else {
|
|
184
|
-
// No cursor position, look for </board> tag
|
|
185
|
-
const boardClosingTagIndex = currentContent.lastIndexOf("</board>")
|
|
186
|
-
|
|
187
|
-
if (boardClosingTagIndex !== -1) {
|
|
188
|
-
// Insert before the closing board tag
|
|
189
|
-
const newContent =
|
|
190
|
-
currentContent.slice(0, boardClosingTagIndex) +
|
|
191
|
-
tsxCode +
|
|
192
|
-
currentContent.slice(boardClosingTagIndex)
|
|
193
|
-
updateFileContent(currentFile, newContent)
|
|
194
|
-
} else {
|
|
195
|
-
const lastParenIndex = currentContent.lastIndexOf(")")
|
|
196
|
-
|
|
197
|
-
if (lastParenIndex !== -1) {
|
|
198
|
-
const newContent =
|
|
199
|
-
currentContent.slice(0, lastParenIndex) +
|
|
200
|
-
tsxCode +
|
|
201
|
-
currentContent.slice(lastParenIndex)
|
|
202
|
-
updateFileContent(currentFile, newContent)
|
|
203
|
-
} else {
|
|
204
|
-
// If no closing parenthesis found, append to end of file
|
|
205
|
-
updateFileContent(currentFile, currentContent + tsxCode)
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
setChipName("")
|
|
211
|
-
} catch (error) {
|
|
212
|
-
console.error("Error inserting footprint:", error)
|
|
213
|
-
toast({
|
|
214
|
-
title: "Error",
|
|
215
|
-
description: "Failed to insert footprint",
|
|
216
|
-
variant: "destructive",
|
|
217
|
-
})
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
const handleCopyToClipboard = async () => {
|
|
222
|
-
try {
|
|
223
|
-
await navigator.clipboard.writeText(footprintString)
|
|
224
|
-
setCopied(true)
|
|
225
|
-
} catch (err) {
|
|
226
|
-
console.error("Failed to copy to clipboard:", err)
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
return (
|
|
231
|
-
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
232
|
-
<DialogContent className="max-w-[1160px] h-full flex flex-col overflow-x-scroll">
|
|
233
|
-
<DialogHeader>
|
|
234
|
-
<DialogTitle>Insert Chip</DialogTitle>
|
|
235
|
-
<DialogDescription>
|
|
236
|
-
Choose a footprint type and configure its parameters. The footprint
|
|
237
|
-
will be inserted at your cursor position.
|
|
238
|
-
</DialogDescription>
|
|
239
|
-
</DialogHeader>
|
|
240
|
-
<div className="w-fit h-fit flex gap-4 pt-4">
|
|
241
|
-
<div className="space-y-4 min-w-[280px]">
|
|
242
|
-
<div>
|
|
243
|
-
<label className="text-sm font-medium">Chip Name</label>
|
|
244
|
-
<Input
|
|
245
|
-
value={chipName}
|
|
246
|
-
onChange={(e) => setChipName(e.target.value)}
|
|
247
|
-
placeholder="Enter chip name (e.g., U1)..."
|
|
248
|
-
className="mt-1"
|
|
249
|
-
/>
|
|
250
|
-
</div>
|
|
251
|
-
<div>
|
|
252
|
-
<label className="text-sm font-medium">Footprint Name</label>
|
|
253
|
-
<Combobox
|
|
254
|
-
value={footprintName}
|
|
255
|
-
onChange={(value) => {
|
|
256
|
-
setFootprintName(value)
|
|
257
|
-
try {
|
|
258
|
-
let newParams = fp.string(value).json()
|
|
259
|
-
updateFootprintString(value, newParams)
|
|
260
|
-
} catch (error) {
|
|
261
|
-
console.error("Error updating footprint string:", error)
|
|
262
|
-
setFootprintString(value)
|
|
263
|
-
handleFootprintPreview(value)
|
|
264
|
-
}
|
|
265
|
-
}}
|
|
266
|
-
options={normalFootprintNames}
|
|
267
|
-
placeholder="Select footprint..."
|
|
268
|
-
searchPlaceholder="Search footprints..."
|
|
269
|
-
emptyText="No footprints found."
|
|
270
|
-
className="mt-1"
|
|
271
|
-
/>
|
|
272
|
-
</div>
|
|
273
|
-
<div>
|
|
274
|
-
<label className="text-sm font-medium">Footprint String</label>
|
|
275
|
-
<div className="flex items-center justify-center mt-1 gap-1">
|
|
276
|
-
<Input
|
|
277
|
-
readOnly
|
|
278
|
-
value={footprintString}
|
|
279
|
-
onChange={(e) => {
|
|
280
|
-
setFootprintString(e.target.value)
|
|
281
|
-
handleFootprintPreview(e.target.value)
|
|
282
|
-
}}
|
|
283
|
-
placeholder="Complete footprint string..."
|
|
284
|
-
className={`bg-gray-50 text-gray-500 ${footprintNameError && "bg-red-50 border-red-200"}`}
|
|
285
|
-
/>
|
|
286
|
-
<Button
|
|
287
|
-
size="icon"
|
|
288
|
-
variant="outline"
|
|
289
|
-
onClick={handleCopyToClipboard}
|
|
290
|
-
className={`shrink-0 ${copied && "text-green-500 border-green-500"}`}
|
|
291
|
-
title="Copy to clipboard"
|
|
292
|
-
>
|
|
293
|
-
{copied ? (
|
|
294
|
-
<Check className="h-4 w-4" />
|
|
295
|
-
) : (
|
|
296
|
-
<Copy className="h-4 w-4" />
|
|
297
|
-
)}
|
|
298
|
-
</Button>
|
|
299
|
-
</div>
|
|
300
|
-
</div>
|
|
301
|
-
{params && (
|
|
302
|
-
<ParametersEditor
|
|
303
|
-
params={params}
|
|
304
|
-
updateParam={updateParam}
|
|
305
|
-
paramNames={PARAM_NAMES}
|
|
306
|
-
/>
|
|
307
|
-
)}
|
|
308
|
-
<Button
|
|
309
|
-
onClick={() => {
|
|
310
|
-
handleInsertFootprint()
|
|
311
|
-
onOpenChange(false)
|
|
312
|
-
}}
|
|
313
|
-
disabled={!footprintString || !chipName}
|
|
314
|
-
className="w-full"
|
|
315
|
-
>
|
|
316
|
-
Insert Footprint
|
|
317
|
-
</Button>
|
|
318
|
-
</div>
|
|
319
|
-
<div className="flex flex-col">
|
|
320
|
-
<div className="rounded-xl overflow-hidden w-[800px] h-[600px]">
|
|
321
|
-
{previewSvg && (
|
|
322
|
-
<div
|
|
323
|
-
dangerouslySetInnerHTML={{
|
|
324
|
-
__html: previewSvg,
|
|
325
|
-
}}
|
|
326
|
-
/>
|
|
327
|
-
)}
|
|
328
|
-
</div>
|
|
329
|
-
{error && (
|
|
330
|
-
<div className="mt-2 p-2 text-sm text-red-600 bg-red-50 border border-red-200 rounded">
|
|
331
|
-
{error}
|
|
332
|
-
</div>
|
|
333
|
-
)}
|
|
334
|
-
</div>
|
|
335
|
-
</div>
|
|
336
|
-
</DialogContent>
|
|
337
|
-
</Dialog>
|
|
338
|
-
)
|
|
339
|
-
}
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
import { Input } from "./ui/input"
|
|
2
|
-
|
|
3
|
-
interface ParametersEditorProps {
|
|
4
|
-
params: Record<string, any>
|
|
5
|
-
updateParam: (
|
|
6
|
-
key: string,
|
|
7
|
-
value: string | number | boolean | string[],
|
|
8
|
-
) => void
|
|
9
|
-
paramNames: Record<string, string>
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const ParametersEditor = ({
|
|
13
|
-
params,
|
|
14
|
-
updateParam,
|
|
15
|
-
paramNames,
|
|
16
|
-
}: ParametersEditorProps) => {
|
|
17
|
-
const renderStringInput = (key: string, value: string) => {
|
|
18
|
-
if (key === "grid") {
|
|
19
|
-
let rows = "",
|
|
20
|
-
cols = ""
|
|
21
|
-
if (typeof value === "string") {
|
|
22
|
-
;[rows = "", cols = ""] = value.split("x").map(String)
|
|
23
|
-
} else if (typeof value === "number") {
|
|
24
|
-
rows = cols = String(value)
|
|
25
|
-
} else if (value && typeof value === "object") {
|
|
26
|
-
const grid = value as { x: number; y: number }
|
|
27
|
-
rows = String(grid.x || "")
|
|
28
|
-
cols = String(grid.y || "")
|
|
29
|
-
}
|
|
30
|
-
return (
|
|
31
|
-
<div className="flex gap-2 flex-1">
|
|
32
|
-
<Input
|
|
33
|
-
type="number"
|
|
34
|
-
value={rows || ""}
|
|
35
|
-
onChange={(e) => {
|
|
36
|
-
const newRows = e.target.value || "0"
|
|
37
|
-
const newCols = cols || "0"
|
|
38
|
-
updateParam(key, `${newRows}x${newCols}`)
|
|
39
|
-
}}
|
|
40
|
-
placeholder="Rows"
|
|
41
|
-
className="flex-1"
|
|
42
|
-
/>
|
|
43
|
-
<span className="flex items-center">×</span>
|
|
44
|
-
<Input
|
|
45
|
-
type="number"
|
|
46
|
-
value={cols || ""}
|
|
47
|
-
onChange={(e) => {
|
|
48
|
-
const newRows = rows || "0"
|
|
49
|
-
const newCols = e.target.value || "0"
|
|
50
|
-
updateParam(key, `${newRows}x${newCols}`)
|
|
51
|
-
}}
|
|
52
|
-
placeholder="Cols"
|
|
53
|
-
className="flex-1"
|
|
54
|
-
/>
|
|
55
|
-
</div>
|
|
56
|
-
)
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (key === "missing") {
|
|
60
|
-
const missingArray = Array.isArray(value) ? value : []
|
|
61
|
-
return (
|
|
62
|
-
<div className="flex flex-wrap gap-2 items-center">
|
|
63
|
-
{missingArray.map((item, index) => (
|
|
64
|
-
<Input
|
|
65
|
-
key={index}
|
|
66
|
-
type="number"
|
|
67
|
-
value={item}
|
|
68
|
-
onChange={(e) => {
|
|
69
|
-
if (!e.target.value) {
|
|
70
|
-
const newArray = missingArray.filter((_, i) => i !== index)
|
|
71
|
-
updateParam(key, newArray)
|
|
72
|
-
return
|
|
73
|
-
}
|
|
74
|
-
const newArray = [...missingArray]
|
|
75
|
-
newArray[index] = e.target.value
|
|
76
|
-
updateParam(key, newArray)
|
|
77
|
-
}}
|
|
78
|
-
className="w-16 h-8 text-center p-1"
|
|
79
|
-
/>
|
|
80
|
-
))}
|
|
81
|
-
<button
|
|
82
|
-
onClick={() => {
|
|
83
|
-
updateParam(key, [...missingArray, "0"])
|
|
84
|
-
}}
|
|
85
|
-
className="w-8 h-8 flex items-center justify-center rounded border border-gray-200 hover:bg-gray-50"
|
|
86
|
-
>
|
|
87
|
-
+
|
|
88
|
-
</button>
|
|
89
|
-
</div>
|
|
90
|
-
)
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return (
|
|
94
|
-
<Input
|
|
95
|
-
type="text"
|
|
96
|
-
value={value}
|
|
97
|
-
onChange={(e) => updateParam(key, e.target.value)}
|
|
98
|
-
className="flex-1"
|
|
99
|
-
/>
|
|
100
|
-
)
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
return (
|
|
104
|
-
<div className="space-y-2">
|
|
105
|
-
<label className="text-sm font-medium">Parameters</label>
|
|
106
|
-
{Object.entries(params)
|
|
107
|
-
.filter(
|
|
108
|
-
([key]) => key !== "fn" && (key !== "num_pins" || !key.match(/\d/)),
|
|
109
|
-
)
|
|
110
|
-
.map(([key, value]) => {
|
|
111
|
-
return (
|
|
112
|
-
<div key={key} className="flex gap-2 items-center">
|
|
113
|
-
<label className="text-sm">
|
|
114
|
-
{paramNames[key] ? `${paramNames[key]} (${key})` : key}:
|
|
115
|
-
</label>
|
|
116
|
-
{typeof value === "boolean" ? (
|
|
117
|
-
<input
|
|
118
|
-
type="checkbox"
|
|
119
|
-
checked={value}
|
|
120
|
-
onChange={(e) => updateParam(key, e.target.checked)}
|
|
121
|
-
className="h-4 w-4"
|
|
122
|
-
/>
|
|
123
|
-
) : typeof value === "number" ? (
|
|
124
|
-
<Input
|
|
125
|
-
type="number"
|
|
126
|
-
value={value}
|
|
127
|
-
onChange={(e) => updateParam(key, e.target.value)}
|
|
128
|
-
className="flex-1"
|
|
129
|
-
/>
|
|
130
|
-
) : (
|
|
131
|
-
renderStringInput(key, value)
|
|
132
|
-
)}
|
|
133
|
-
</div>
|
|
134
|
-
)
|
|
135
|
-
})}
|
|
136
|
-
</div>
|
|
137
|
-
)
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
export default ParametersEditor
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
export interface FootprintParams {
|
|
2
|
-
[key: string]: string | number | boolean | string[]
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Parses and normalizes footprint parameters
|
|
7
|
-
* @param params Raw parameters from footprint
|
|
8
|
-
* @returns Normalized parameters with proper formatting
|
|
9
|
-
*/
|
|
10
|
-
export function parseFootprintParams(params: FootprintParams): FootprintParams {
|
|
11
|
-
if (params.grid) {
|
|
12
|
-
const grid = params.grid
|
|
13
|
-
if (typeof grid === "object" && grid !== null) {
|
|
14
|
-
const { x, y } = grid as any
|
|
15
|
-
params.grid = `${x}x${y}`
|
|
16
|
-
} else if (typeof grid === "string") {
|
|
17
|
-
const gridMatch = grid.match(/^(\d+)(?:x(\d+)?)?$/)
|
|
18
|
-
if (gridMatch) {
|
|
19
|
-
const [, x, y = x] = gridMatch
|
|
20
|
-
params.grid = `${x}x${y}`
|
|
21
|
-
}
|
|
22
|
-
} else if (typeof grid === "number") {
|
|
23
|
-
params.grid = `${grid}x${grid}`
|
|
24
|
-
}
|
|
25
|
-
delete params.grid3x3
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
if ("missing" in params && typeof params.missing === "string") {
|
|
29
|
-
const value = params.missing
|
|
30
|
-
if (value === "") {
|
|
31
|
-
params.missing = []
|
|
32
|
-
} else if (!Array.isArray(value)) {
|
|
33
|
-
if (value.startsWith("missing(") && value.endsWith(")")) {
|
|
34
|
-
const pinsStr = value.slice(8, -1)
|
|
35
|
-
params.missing = pinsStr ? pinsStr.split(",").map((p) => p.trim()) : []
|
|
36
|
-
} else {
|
|
37
|
-
params.missing = value
|
|
38
|
-
.split(",")
|
|
39
|
-
.map((p) => p.trim())
|
|
40
|
-
.filter(Boolean)
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
Object.entries(params).forEach(([key, value]) => {
|
|
46
|
-
if (typeof value === "string" && !isNaN(Number(value)) && key !== "grid") {
|
|
47
|
-
params[key] = Number(Number(value).toFixed(2))
|
|
48
|
-
}
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
return params
|
|
52
|
-
}
|