@tscircuit/fake-snippets 0.0.107 → 0.0.109

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 (80) hide show
  1. package/api/generated-index.js +82 -22
  2. package/biome.json +7 -1
  3. package/bun-tests/fake-snippets-api/routes/package_builds/get.test.ts +0 -15
  4. package/bun-tests/fake-snippets-api/routes/package_builds/list.test.ts +0 -12
  5. package/bun.lock +62 -19
  6. package/dist/bundle.js +25 -24
  7. package/dist/index.d.ts +26 -15
  8. package/dist/index.js +19 -18
  9. package/dist/schema.d.ts +32 -24
  10. package/dist/schema.js +7 -6
  11. package/fake-snippets-api/lib/db/db-client.ts +10 -1
  12. package/fake-snippets-api/lib/db/schema.ts +4 -3
  13. package/fake-snippets-api/lib/db/seed.ts +6 -9
  14. package/fake-snippets-api/lib/public-mapping/public-map-package-build.ts +0 -3
  15. package/fake-snippets-api/lib/public-mapping/public-map-package-release.ts +3 -0
  16. package/package.json +7 -8
  17. package/src/App.tsx +12 -11
  18. package/src/components/DownloadButtonAndMenu.tsx +133 -35
  19. package/src/components/FileSidebar.tsx +45 -193
  20. package/src/components/Footer.tsx +0 -1
  21. package/src/components/HeaderLogin.tsx +1 -1
  22. package/src/components/HiddenFilesDropdown.tsx +0 -2
  23. package/src/components/PackageBreadcrumb.tsx +1 -1
  24. package/src/components/PackageBuildsPage/PackageBuildDetailsPage.tsx +0 -2
  25. package/src/components/PackageBuildsPage/build-preview-content.tsx +34 -5
  26. package/src/components/PackageCard.tsx +0 -1
  27. package/src/components/ViewPackagePage/components/ShikiCodeViewer.tsx +20 -11
  28. package/src/components/ViewPackagePage/components/important-files-view.tsx +75 -59
  29. package/src/components/ViewPackagePage/components/main-content-header.tsx +4 -4
  30. package/src/components/ViewPackagePage/components/main-content-view-selector.tsx +0 -1
  31. package/src/components/ViewPackagePage/components/mobile-sidebar.tsx +1 -2
  32. package/src/components/ViewPackagePage/components/package-header.tsx +14 -17
  33. package/src/components/ViewPackagePage/components/preview-image-squares.tsx +0 -1
  34. package/src/components/ViewPackagePage/components/repo-page-content.tsx +21 -20
  35. package/src/components/ViewPackagePage/components/sidebar-about-section.tsx +18 -2
  36. package/src/components/ViewPackagePage/components/sidebar-releases-section.tsx +1 -1
  37. package/src/components/ViewPackagePage/components/sidebar.tsx +0 -2
  38. package/src/components/ViewPackagePage/components/tab-views/files-view.tsx +18 -17
  39. package/src/components/ViewPackagePage/components/theme-toggle.tsx +0 -2
  40. package/src/components/ViewPackagePage/hooks/use-toast.tsx +0 -1
  41. package/src/components/package-port/CodeAndPreview.tsx +23 -40
  42. package/src/components/package-port/CodeEditor.tsx +24 -1
  43. package/src/components/package-port/CodeEditorHeader.tsx +5 -2
  44. package/src/components/preview/BuildsList.tsx +20 -9
  45. package/src/components/preview/ConnectedPackagesList.tsx +73 -60
  46. package/src/components/preview/ConnectedRepoOverview.tsx +160 -154
  47. package/src/components/preview/PackageReleasesDashboard.tsx +41 -30
  48. package/src/components/preview/index.tsx +16 -153
  49. package/src/hooks/use-current-package-id.ts +5 -30
  50. package/src/hooks/use-current-package-info.ts +29 -5
  51. package/src/hooks/use-global-store.ts +1 -1
  52. package/src/hooks/useFileManagement.ts +153 -34
  53. package/src/hooks/useOptimizedPackageFilesLoader.ts +149 -0
  54. package/src/hooks/useUpdatePackageFilesMutation.ts +2 -0
  55. package/src/index.css +24 -0
  56. package/src/lib/download-fns/download-circuit-png.ts +11 -3
  57. package/src/lib/download-fns/download-gltf-from-circuit-json.ts +44 -0
  58. package/src/lib/utils/isComponentExported.ts +9 -0
  59. package/src/lib/utils/transformFilesToTreeData.tsx +195 -0
  60. package/src/pages/404.tsx +3 -5
  61. package/src/pages/authorize.tsx +0 -2
  62. package/src/pages/landing.tsx +0 -1
  63. package/src/pages/preview-release.tsx +279 -0
  64. package/src/pages/release-builds.tsx +0 -8
  65. package/src/pages/release-detail.tsx +17 -15
  66. package/src/pages/releases.tsx +5 -1
  67. package/src/pages/view-package.tsx +14 -13
  68. package/src/components/Footer2.tsx +0 -100
  69. package/src/components/ShippingInformationForm.tsx +0 -423
  70. package/src/components/StaticViewSnippetHeader.tsx +0 -70
  71. package/src/components/ViewPackagePage/components/file-explorer.tsx +0 -67
  72. package/src/components/ViewPackagePage/components/readme-view.tsx +0 -58
  73. package/src/components/ViewPackagePage/components/repo-header-button.tsx +0 -36
  74. package/src/components/ViewPackagePage/components/repo-header.tsx +0 -4
  75. package/src/components/ViewPackagePage/components/sidebar-contributors-section.tsx +0 -31
  76. package/src/components/ViewSnippetHeader.tsx +0 -181
  77. package/src/components/ui/input-otp.tsx +0 -69
  78. package/src/hooks/use-snippets-base-api-url.ts +0 -3
  79. package/src/pages/preview-build.tsx +0 -380
  80. package/src/pages/settings.tsx +0 -25
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tscircuit/fake-snippets",
3
- "version": "0.0.107",
3
+ "version": "0.0.109",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -75,6 +75,7 @@
75
75
  "@radix-ui/react-toggle": "^1.1.0",
76
76
  "@radix-ui/react-toggle-group": "^1.1.0",
77
77
  "@radix-ui/react-tooltip": "^1.1.2",
78
+ "@resvg/resvg-wasm": "^2.6.2",
78
79
  "@tailwindcss/typography": "^0.5.16",
79
80
  "@tscircuit/3d-viewer": "^0.0.303",
80
81
  "@tscircuit/assembly-viewer": "^0.0.1",
@@ -84,8 +85,9 @@
84
85
  "@tscircuit/mm": "^0.0.8",
85
86
  "@tscircuit/pcb-viewer": "^1.11.194",
86
87
  "@tscircuit/prompt-benchmarks": "^0.0.28",
87
- "@tscircuit/runframe": "0.0.764",
88
+ "@tscircuit/runframe": "^0.0.781",
88
89
  "@tscircuit/schematic-viewer": "^2.0.21",
90
+ "@tscircuit/simple-3d-svg": "^0.0.41",
89
91
  "@types/babel__standalone": "^7.1.7",
90
92
  "@types/bun": "^1.1.10",
91
93
  "@types/country-list": "^2.1.4",
@@ -93,7 +95,6 @@
93
95
  "@types/md5": "^2.3.5",
94
96
  "@types/ms": "^0.7.34",
95
97
  "@types/node": "^22.13.0",
96
- "@types/prismjs": "^1.26.4",
97
98
  "@types/react": "^18.3.9",
98
99
  "@types/react-dom": "^18.3.0",
99
100
  "@types/react-helmet": "^6.1.11",
@@ -104,11 +105,12 @@
104
105
  "@vercel/analytics": "^1.4.1",
105
106
  "@vitejs/plugin-react": "^4.3.1",
106
107
  "autoprefixer": "^10.4.20",
107
- "change-case": "^5.4.4",
108
108
  "circuit-json-to-bom-csv": "^0.0.7",
109
109
  "circuit-json-to-gerber": "^0.0.29",
110
+ "circuit-json-to-gltf": "^0.0.4",
110
111
  "circuit-json-to-pnp-csv": "^0.0.7",
111
112
  "circuit-json-to-readable-netlist": "^0.0.13",
113
+ "circuit-json-to-simple-3d": "^0.0.6",
112
114
  "circuit-json-to-spice": "^0.0.6",
113
115
  "circuit-json-to-tscircuit": "^0.0.4",
114
116
  "circuit-to-svg": "^0.0.167",
@@ -117,8 +119,6 @@
117
119
  "cmdk": "^1.0.4",
118
120
  "codemirror": "^6.0.1",
119
121
  "codemirror-copilot": "^0.0.7",
120
- "country-list": "^2.3.0",
121
- "date-fns": "^4.1.0",
122
122
  "dotenv": "^16.5.0",
123
123
  "dsn-converter": "^0.0.60",
124
124
  "easyeda": "^0.0.203",
@@ -131,7 +131,6 @@
131
131
  "he": "^1.2.0",
132
132
  "idb-keyval": "^6.2.2",
133
133
  "immer": "^10.1.1",
134
- "input-otp": "^1.2.4",
135
134
  "javascript-time-ago": "^2.5.11",
136
135
  "jose": "^5.9.3",
137
136
  "jscad-electronics": "^0.0.25",
@@ -147,7 +146,6 @@
147
146
  "openai": "^5.6.0",
148
147
  "postcss": "^8.4.47",
149
148
  "posthog-js": "^1.203.2",
150
- "prismjs": "^1.29.0",
151
149
  "prompts": "^2.4.2",
152
150
  "react": "^18.3.1",
153
151
  "react-cookie-consent": "^9.0.0",
@@ -172,6 +170,7 @@
172
170
  "sitemap": "^8.0.0",
173
171
  "sonner": "^1.5.0",
174
172
  "states-us": "^1.1.1",
173
+ "svgo": "^4.0.0",
175
174
  "tailwind-merge": "^2.5.2",
176
175
  "tailwindcss": "^3.4.13",
177
176
  "tailwindcss-animate": "^1.0.7",
package/src/App.tsx CHANGED
@@ -64,7 +64,6 @@ const MyOrdersPage = lazyImport(() => import("@/pages/my-orders"))
64
64
  const LatestPage = lazyImport(() => import("@/pages/latest"))
65
65
  const QuickstartPage = lazyImport(() => import("@/pages/quickstart"))
66
66
  const SearchPage = lazyImport(() => import("@/pages/search"))
67
- const SettingsPage = lazyImport(() => import("@/pages/settings"))
68
67
  const UserProfilePage = lazyImport(() => import("@/pages/user-profile"))
69
68
  const DevLoginPage = lazyImport(() => import("@/pages/dev-login"))
70
69
  const ViewPackagePage = lazyImport(() => import("@/pages/view-package"))
@@ -82,7 +81,7 @@ const PackageEditorPage = lazyImport(async () => {
82
81
  const ReleasesPage = lazyImport(() => import("@/pages/releases"))
83
82
  const ReleaseDetailPage = lazyImport(() => import("@/pages/release-detail"))
84
83
  const ReleaseBuildsPage = lazyImport(() => import("@/pages/release-builds"))
85
- const PreviewBuildPage = lazyImport(() => import("@/pages/preview-build"))
84
+ const ReleasePreviewPage = lazyImport(() => import("@/pages/preview-release"))
86
85
 
87
86
  class ErrorBoundary extends React.Component<
88
87
  { children: React.ReactNode },
@@ -109,7 +108,7 @@ class ErrorBoundary extends React.Component<
109
108
  )
110
109
  ) {
111
110
  const loadedAt = window.__APP_LOADED_AT || Date.now()
112
- if (Date.now() - loadedAt >= 10_000) {
111
+ if (Date.now() - loadedAt >= 180_000) {
113
112
  this.performReload()
114
113
  }
115
114
  }
@@ -144,6 +143,7 @@ class ErrorBoundary extends React.Component<
144
143
  this.setState({ reloading: true })
145
144
  this.reloadTimeout = window.setTimeout(() => {
146
145
  if (window?.location.href.includes("localhost:")) return
146
+ if (window?.location.href.includes("127.0.0.1:")) return
147
147
  window.location.reload()
148
148
  }, 500)
149
149
  }
@@ -152,10 +152,12 @@ class ErrorBoundary extends React.Component<
152
152
  this.cleanup() // Clean up any existing handlers
153
153
 
154
154
  this.visibilityHandler = () => {
155
+ const loadedAt = window.__APP_LOADED_AT || Date.now()
155
156
  if (
156
157
  document.visibilityState === "visible" &&
157
158
  this.state.hasError &&
158
- !this.state.reloading
159
+ !this.state.reloading &&
160
+ Date.now() - loadedAt >= 180_000
159
161
  ) {
160
162
  this.performReload()
161
163
  }
@@ -249,7 +251,6 @@ function App() {
249
251
  <Route path="/quickstart" component={QuickstartPage} />
250
252
  <Route path="/dashboard" component={DashboardPage} />
251
253
  <Route path="/latest" component={LatestPage} />
252
- <Route path="/settings" component={SettingsPage} />
253
254
  <Route path="/search" component={SearchPage} />
254
255
  <Route path="/trending" component={TrendingPage} />
255
256
  <Route path="/datasheets" component={DatasheetsPage} />
@@ -259,13 +260,17 @@ function App() {
259
260
  <Route path="/dev-login" component={DevLoginPage} />
260
261
  <Route path="/:username" component={UserProfilePage} />
261
262
  <Route
262
- path="/:author/:packageName/release/:releaseId/builds"
263
+ path="/:author/:packageName/releases/:releaseId/builds"
263
264
  component={ReleaseBuildsPage}
264
265
  />
265
266
  <Route
266
- path="/:author/:packageName/release/:releaseId"
267
+ path="/:author/:packageName/releases/:releaseId"
267
268
  component={ReleaseDetailPage}
268
269
  />
270
+ <Route
271
+ path="/:author/:packageName/releases/:packageReleaseId/preview"
272
+ component={ReleasePreviewPage}
273
+ />
269
274
  <Route
270
275
  path="/:author/:packageName/releases/:packageReleaseId"
271
276
  component={ReleaseDetailPage}
@@ -274,10 +279,6 @@ function App() {
274
279
  path="/:author/:packageName/releases"
275
280
  component={ReleasesPage}
276
281
  />
277
- <Route
278
- path="/build/:buildId/preview"
279
- component={PreviewBuildPage}
280
- />
281
282
  <Route path="/:author/:packageName" component={ViewPackagePage} />
282
283
  <Route
283
284
  path="/:author/:packageName/builds"
@@ -18,18 +18,22 @@ import { usePcbDownloadDialog } from "@/components/dialogs/pcb-download-dialog"
18
18
  import { downloadKicadFiles } from "@/lib/download-fns/download-kicad-files"
19
19
  import { AnyCircuitElement } from "circuit-json"
20
20
  import { ChevronDown, Download, Hammer } from "lucide-react"
21
- import { downloadGltf } from "@/lib/download-fns/download-gltf"
21
+ import { downloadGltfFromCircuitJson } from "@/lib/download-fns/download-gltf-from-circuit-json"
22
22
  import { downloadPngImage } from "@/lib/download-fns/download-png-utils"
23
23
  import { ImageFormat } from "@/lib/download-fns/download-circuit-png"
24
24
  import { CubeIcon } from "@radix-ui/react-icons"
25
+ import { useState } from "react"
26
+ import { useAxios } from "@/hooks/use-axios"
27
+ import { useCurrentPackageId } from "@/hooks/use-current-package-id"
25
28
 
26
29
  interface DownloadButtonAndMenuProps {
27
30
  className?: string
28
31
  unscopedName?: string
29
32
  author?: string
30
- circuitJson?: AnyCircuitElement[] | null
33
+ hasCircuitJson?: boolean
31
34
  desiredImageType?: string
32
35
  offerMultipleImageFormats?: boolean
36
+ circuitJson?: AnyCircuitElement[] | null
33
37
  }
34
38
 
35
39
  export function DownloadButtonAndMenu({
@@ -37,14 +41,52 @@ export function DownloadButtonAndMenu({
37
41
  unscopedName,
38
42
  author,
39
43
  desiredImageType = "pcb",
40
- circuitJson,
44
+ hasCircuitJson,
41
45
  offerMultipleImageFormats = false,
46
+ circuitJson,
42
47
  }: DownloadButtonAndMenuProps) {
43
48
  const notImplemented = useNotImplementedToast()
44
49
  const { Dialog: PcbDownloadDialog, openDialog: openPcbDownloadDialog } =
45
50
  usePcbDownloadDialog()
51
+ const axios = useAxios()
52
+ const { packageId } = useCurrentPackageId()
53
+ const [fetchedCircuitJson, setFetchedCircuitJson] = useState<
54
+ AnyCircuitElement[] | null
55
+ >(null)
56
+ const [isFetchingCircuitJson, setIsFetchingCircuitJson] = useState(false)
57
+
58
+ const canDownload = Boolean(
59
+ hasCircuitJson || (circuitJson && circuitJson.length),
60
+ )
61
+
62
+ const getCircuitJson = async (): Promise<AnyCircuitElement[]> => {
63
+ if (circuitJson && circuitJson.length) return circuitJson
64
+ if (fetchedCircuitJson && fetchedCircuitJson.length)
65
+ return fetchedCircuitJson
66
+ setIsFetchingCircuitJson(true)
67
+ try {
68
+ const { data } = await axios.post("/package_files/get", {
69
+ package_id: packageId,
70
+ file_path: "dist/circuit.json",
71
+ })
72
+ const content = data?.package_file?.content_text
73
+ if (!content) throw new Error("Circuit JSON not found")
74
+ const parsed = JSON.parse(content)
75
+ setFetchedCircuitJson(parsed)
76
+ return parsed
77
+ } catch (error: any) {
78
+ toast({
79
+ title: "Failed to fetch Circuit JSON",
80
+ description: error?.message || error?.toString?.() || "Unknown error",
81
+ variant: "destructive",
82
+ })
83
+ throw error
84
+ } finally {
85
+ setIsFetchingCircuitJson(false)
86
+ }
87
+ }
46
88
 
47
- if (!circuitJson) {
89
+ if (!canDownload) {
48
90
  return (
49
91
  <div className={className}>
50
92
  <Button
@@ -76,11 +118,9 @@ export function DownloadButtonAndMenu({
76
118
  <DropdownMenuContent className="!z-[101]">
77
119
  <DropdownMenuItem
78
120
  className="text-xs"
79
- onSelect={() => {
80
- downloadCircuitJson(
81
- circuitJson,
82
- unscopedName || "circuit" + ".json",
83
- )
121
+ onSelect={async () => {
122
+ const cj = await getCircuitJson()
123
+ downloadCircuitJson(cj, unscopedName || "circuit" + ".json")
84
124
  }}
85
125
  >
86
126
  <Download className="mr-1 h-3 w-3" />
@@ -93,7 +133,12 @@ export function DownloadButtonAndMenu({
93
133
  className="text-xs"
94
134
  onClick={async () => {
95
135
  try {
96
- await downloadGltf(unscopedName || "circuit")
136
+ const cj = await getCircuitJson()
137
+ await downloadGltfFromCircuitJson(
138
+ cj,
139
+ unscopedName || "circuit",
140
+ { format: "glb", boardTextureResolution: 2048 },
141
+ )
97
142
  } catch (error: any) {
98
143
  toast({
99
144
  title: "Error Downloading 3D Model",
@@ -112,8 +157,34 @@ export function DownloadButtonAndMenu({
112
157
  <DropdownMenuItem
113
158
  className="text-xs"
114
159
  onClick={async () => {
160
+ try {
161
+ const cj = await getCircuitJson()
162
+ await downloadGltfFromCircuitJson(
163
+ cj,
164
+ unscopedName || "circuit",
165
+ { format: "gltf", boardTextureResolution: 2048 },
166
+ )
167
+ } catch (error: any) {
168
+ toast({
169
+ title: "Error Downloading 3D Model (GLTF)",
170
+ description: error.toString(),
171
+ variant: "destructive",
172
+ })
173
+ }
174
+ }}
175
+ >
176
+ <CubeIcon className="mr-1 h-3 w-3" />
177
+ <span className="flex-grow mr-6">3D Model</span>
178
+ <span className="text-[0.6rem] bg-green-500 opacity-80 text-white font-mono rounded-md px-1 text-center py-0.5 mr-1">
179
+ gltf
180
+ </span>
181
+ </DropdownMenuItem>
182
+ <DropdownMenuItem
183
+ className="text-xs"
184
+ onClick={async () => {
185
+ const cj = await getCircuitJson()
115
186
  await downloadFabricationFiles({
116
- circuitJson,
187
+ circuitJson: cj,
117
188
  snippetUnscopedName: unscopedName || "snippet",
118
189
  }).catch((error) => {
119
190
  console.error(error)
@@ -144,8 +215,9 @@ export function DownloadButtonAndMenu({
144
215
  </DropdownMenuItem>
145
216
  <DropdownMenuItem
146
217
  className="text-xs"
147
- onSelect={() => {
148
- downloadKicadFiles(circuitJson, unscopedName || "kicad_project")
218
+ onSelect={async () => {
219
+ const cj = await getCircuitJson()
220
+ downloadKicadFiles(cj, unscopedName || "kicad_project")
149
221
  }}
150
222
  >
151
223
  <Download className="mr-1 h-3 w-3" />
@@ -157,8 +229,9 @@ export function DownloadButtonAndMenu({
157
229
 
158
230
  <DropdownMenuItem
159
231
  className="text-xs"
160
- onSelect={() => {
161
- downloadSchematicSvg(circuitJson, unscopedName || "circuit")
232
+ onSelect={async () => {
233
+ const cj = await getCircuitJson()
234
+ downloadSchematicSvg(cj, unscopedName || "circuit")
162
235
  }}
163
236
  >
164
237
  <Download className="mr-1 h-3 w-3" />
@@ -169,8 +242,9 @@ export function DownloadButtonAndMenu({
169
242
  </DropdownMenuItem>
170
243
  <DropdownMenuItem
171
244
  className="text-xs"
172
- onSelect={() => {
173
- downloadAssemblySvg(circuitJson, unscopedName || "circuit")
245
+ onSelect={async () => {
246
+ const cj = await getCircuitJson()
247
+ downloadAssemblySvg(cj, unscopedName || "circuit")
174
248
  }}
175
249
  >
176
250
  <Download className="mr-1 h-3 w-3" />
@@ -181,7 +255,9 @@ export function DownloadButtonAndMenu({
181
255
  </DropdownMenuItem>
182
256
  <DropdownMenuItem
183
257
  className="text-xs"
184
- onSelect={() => {
258
+ onSelect={async () => {
259
+ const cj = await getCircuitJson()
260
+ setFetchedCircuitJson(cj)
185
261
  openPcbDownloadDialog()
186
262
  }}
187
263
  >
@@ -193,8 +269,9 @@ export function DownloadButtonAndMenu({
193
269
  </DropdownMenuItem>
194
270
  <DropdownMenuItem
195
271
  className="text-xs"
196
- onSelect={() => {
197
- downloadDsnFile(circuitJson, unscopedName || "circuit")
272
+ onSelect={async () => {
273
+ const cj = await getCircuitJson()
274
+ downloadDsnFile(cj, unscopedName || "circuit")
198
275
  }}
199
276
  >
200
277
  <Download className="mr-1 h-3 w-3" />
@@ -205,8 +282,9 @@ export function DownloadButtonAndMenu({
205
282
  </DropdownMenuItem>
206
283
  <DropdownMenuItem
207
284
  className="text-xs"
208
- onClick={() => {
209
- downloadReadableNetlist(circuitJson, unscopedName || "circuit")
285
+ onClick={async () => {
286
+ const cj = await getCircuitJson()
287
+ downloadReadableNetlist(cj, unscopedName || "circuit")
210
288
  }}
211
289
  >
212
290
  <Download className="mr-1 h-3 w-3" />
@@ -217,8 +295,9 @@ export function DownloadButtonAndMenu({
217
295
  </DropdownMenuItem>
218
296
  <DropdownMenuItem
219
297
  className="text-xs"
220
- onSelect={() => {
221
- downloadSpiceFile(circuitJson, unscopedName || "circuit")
298
+ onSelect={async () => {
299
+ const cj = await getCircuitJson()
300
+ downloadSpiceFile(cj, unscopedName || "circuit")
222
301
  }}
223
302
  >
224
303
  <Download className="mr-1 h-3 w-3" />
@@ -229,8 +308,9 @@ export function DownloadButtonAndMenu({
229
308
  </DropdownMenuItem>
230
309
  <DropdownMenuItem
231
310
  className="text-xs"
232
- onSelect={() => {
233
- downloadSimpleRouteJson(circuitJson, unscopedName || "circuit")
311
+ onSelect={async () => {
312
+ const cj = await getCircuitJson()
313
+ downloadSimpleRouteJson(cj, unscopedName || "circuit")
234
314
  }}
235
315
  >
236
316
  <Download className="mr-1 h-3 w-3" />
@@ -243,7 +323,7 @@ export function DownloadButtonAndMenu({
243
323
  {!offerMultipleImageFormats && (
244
324
  <DropdownMenuItem
245
325
  className="text-xs"
246
- onClick={() => {
326
+ onClick={async () => {
247
327
  const desiredImageFormat = [
248
328
  "pcb",
249
329
  "schematic",
@@ -252,8 +332,9 @@ export function DownloadButtonAndMenu({
252
332
  ].includes(desiredImageType)
253
333
  ? desiredImageType
254
334
  : "pcb"
335
+ const cj = await getCircuitJson()
255
336
  downloadPngImage({
256
- circuitJson,
337
+ circuitJson: cj,
257
338
  unscopedName,
258
339
  author,
259
340
  format: desiredImageFormat as ImageFormat,
@@ -271,9 +352,9 @@ export function DownloadButtonAndMenu({
271
352
  <>
272
353
  <DropdownMenuItem
273
354
  className="text-xs"
274
- onClick={() =>
355
+ onClick={async () =>
275
356
  downloadPngImage({
276
- circuitJson,
357
+ circuitJson: await getCircuitJson(),
277
358
  unscopedName,
278
359
  author,
279
360
  format: "schematic",
@@ -288,9 +369,9 @@ export function DownloadButtonAndMenu({
288
369
  </DropdownMenuItem>
289
370
  <DropdownMenuItem
290
371
  className="text-xs"
291
- onClick={() =>
372
+ onClick={async () =>
292
373
  downloadPngImage({
293
- circuitJson,
374
+ circuitJson: await getCircuitJson(),
294
375
  unscopedName,
295
376
  author,
296
377
  format: "pcb",
@@ -305,9 +386,9 @@ export function DownloadButtonAndMenu({
305
386
  </DropdownMenuItem>
306
387
  <DropdownMenuItem
307
388
  className="text-xs"
308
- onClick={() =>
389
+ onClick={async () =>
309
390
  downloadPngImage({
310
- circuitJson,
391
+ circuitJson: await getCircuitJson(),
311
392
  unscopedName,
312
393
  author,
313
394
  format: "assembly",
@@ -320,12 +401,29 @@ export function DownloadButtonAndMenu({
320
401
  png
321
402
  </span>
322
403
  </DropdownMenuItem>
404
+ <DropdownMenuItem
405
+ className="text-xs"
406
+ onClick={async () =>
407
+ downloadPngImage({
408
+ circuitJson: await getCircuitJson(),
409
+ unscopedName,
410
+ author,
411
+ format: "3d",
412
+ })
413
+ }
414
+ >
415
+ <Download className="mr-1 h-3 w-3" />
416
+ <span className="flex-grow mr-6">3D PNG</span>
417
+ <span className="text-[0.6rem] opacity-80 bg-teal-600 text-white font-mono rounded-md px-1 text-center py-0.5 mr-1">
418
+ png
419
+ </span>
420
+ </DropdownMenuItem>
323
421
  </>
324
422
  )}
325
423
  </DropdownMenuContent>
326
424
  </DropdownMenu>
327
425
  <PcbDownloadDialog
328
- circuitJson={circuitJson}
426
+ circuitJson={fetchedCircuitJson || circuitJson || []}
329
427
  fileName={unscopedName || "circuit"}
330
428
  />
331
429
  </div>