@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.
Files changed (44) hide show
  1. package/bun.lock +378 -26
  2. package/dist/bundle.js +411 -335
  3. package/fake-snippets-api/routes/api/_fake/received_quotes.ts +66 -0
  4. package/fake-snippets-api/routes/api/package_files/create_or_update.ts +9 -0
  5. package/fake-snippets-api/routes/api/package_releases/update.ts +25 -18
  6. package/fake-snippets-api/routes/api/packages/update.ts +1 -0
  7. package/package.json +6 -6
  8. package/src/App.tsx +8 -0
  9. package/src/components/CodeAndPreview.tsx +0 -1
  10. package/src/components/CodeEditor.tsx +5 -4
  11. package/src/components/CodeEditorHeader.tsx +1 -26
  12. package/src/components/DownloadButtonAndMenu.tsx +3 -2
  13. package/src/components/EditorNav.tsx +13 -11
  14. package/src/components/ErrorOutline.tsx +35 -0
  15. package/src/components/FileSidebar.tsx +114 -0
  16. package/src/components/NotFound.tsx +37 -0
  17. package/src/components/PreviewContent.tsx +0 -6
  18. package/src/components/TrendingSnippetCarousel.tsx +1 -1
  19. package/src/components/ViewPackagePage/components/package-header.tsx +24 -3
  20. package/src/components/ViewPackagePage/utils/is-hidden-file.ts +0 -1
  21. package/src/components/dialogs/package-visibility-settings-dialog.tsx +10 -1
  22. package/src/components/dialogs/rename-package-dialog.tsx +81 -0
  23. package/src/components/dialogs/update-package-description-dialog.tsx +96 -0
  24. package/src/components/dialogs/view-ts-files-dialog.tsx +0 -6
  25. package/src/components/package-port/CodeAndPreview.tsx +419 -0
  26. package/src/components/package-port/CodeEditor.tsx +498 -0
  27. package/src/components/package-port/CodeEditorHeader.tsx +231 -0
  28. package/src/components/package-port/EditorNav.tsx +520 -0
  29. package/src/components/ui/tree-view.tsx +494 -0
  30. package/src/hooks/use-package.ts +23 -0
  31. package/src/hooks/useForkPackageMutation.ts +49 -0
  32. package/src/hooks/usePackageFilesLoader.ts +56 -0
  33. package/src/hooks/useUpdatePackageFilesMutation.ts +86 -0
  34. package/src/hooks/useUpdatePackageMutation.ts +63 -0
  35. package/src/{prettier.ts → lib/types.ts} +3 -1
  36. package/src/lib/utils/checkIfManualEditsImported.ts +1 -1
  37. package/src/lib/utils/findTargetFile.ts +62 -0
  38. package/src/lib/utils/load-prettier.ts +3 -0
  39. package/src/pages/404.tsx +2 -33
  40. package/src/pages/package-editor.tsx +55 -0
  41. package/src/pages/user-profile.tsx +66 -27
  42. package/src/components/FootprintDialog.tsx +0 -339
  43. package/src/components/ParametersEditor.tsx +0 -140
  44. 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
- }