@tscircuit/fake-snippets 0.0.108 → 0.0.110

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 (203) hide show
  1. package/.github/workflows/bun-formatcheck.yml +2 -2
  2. package/.github/workflows/bun-pver-release.yml +3 -3
  3. package/.github/workflows/bun-test.yml +1 -1
  4. package/.github/workflows/bun-typecheck.yml +2 -2
  5. package/.github/workflows/update-snapshots.yml +1 -1
  6. package/README.md +4 -0
  7. package/api/generated-index.js +37 -3
  8. package/biome.json +2 -1
  9. package/bun-tests/fake-snippets-api/fixtures/get-test-server.ts +31 -3
  10. package/bun-tests/fake-snippets-api/fixtures/preload.ts +18 -0
  11. package/bun-tests/fake-snippets-api/routes/orgs/add_member.test.ts +26 -0
  12. package/bun-tests/fake-snippets-api/routes/orgs/create.test.ts +37 -0
  13. package/bun-tests/fake-snippets-api/routes/orgs/get.test.ts +52 -0
  14. package/bun-tests/fake-snippets-api/routes/orgs/list.test.ts +17 -0
  15. package/bun-tests/fake-snippets-api/routes/orgs/list_members.test.ts +23 -0
  16. package/bun-tests/fake-snippets-api/routes/orgs/remove_member.test.ts +81 -0
  17. package/bun-tests/fake-snippets-api/routes/orgs/update.test.ts +99 -0
  18. package/bun-tests/fake-snippets-api/routes/package_builds/get.test.ts +1 -1
  19. package/bun-tests/fake-snippets-api/routes/package_files/create.test.ts +15 -13
  20. package/bun-tests/fake-snippets-api/routes/package_files/create_or_update.test.ts +26 -24
  21. package/bun-tests/fake-snippets-api/routes/package_files/delete.test.ts +9 -9
  22. package/bun-tests/fake-snippets-api/routes/package_files/download.test.ts +4 -4
  23. package/bun-tests/fake-snippets-api/routes/package_files/get.test.ts +38 -28
  24. package/bun-tests/fake-snippets-api/routes/package_files/list.test.ts +23 -15
  25. package/bun-tests/fake-snippets-api/routes/package_releases/create.test.ts +33 -0
  26. package/bun-tests/fake-snippets-api/routes/package_releases/get.test.ts +4 -4
  27. package/bun-tests/fake-snippets-api/routes/package_releases/get_image_generation_fields.test.ts +38 -0
  28. package/bun-tests/fake-snippets-api/routes/packages/create.test.ts +19 -0
  29. package/bun-tests/fake-snippets-api/routes/packages/fork.test.ts +3 -4
  30. package/bun-tests/fake-snippets-api/routes/packages/get.test.ts +30 -0
  31. package/bun-tests/fake-snippets-api/routes/packages/images.test.ts +4 -2
  32. package/bun-tests/fake-snippets-api/routes/packages/list-1.test.ts +34 -0
  33. package/bun.lock +389 -450
  34. package/bunfig.toml +2 -1
  35. package/dist/bundle.js +1255 -625
  36. package/dist/index.d.ts +296 -4
  37. package/dist/index.js +325 -24
  38. package/dist/schema.d.ts +282 -1
  39. package/dist/schema.js +54 -2
  40. package/fake-snippets-api/lib/db/autoload-dev-packages.ts +31 -20
  41. package/fake-snippets-api/lib/db/db-client.ts +214 -3
  42. package/fake-snippets-api/lib/db/schema.ts +62 -0
  43. package/fake-snippets-api/lib/db/seed.ts +100 -0
  44. package/fake-snippets-api/lib/middleware/with-session-auth.ts +1 -1
  45. package/fake-snippets-api/lib/package_file/get-package-file-id-from-file-descriptor.ts +2 -2
  46. package/fake-snippets-api/lib/public-mapping/public-map-org.ts +32 -0
  47. package/fake-snippets-api/lib/public-mapping/public-map-package-build.ts +10 -0
  48. package/fake-snippets-api/lib/public-mapping/public-map-package-release.ts +17 -0
  49. package/fake-snippets-api/routes/api/orgs/add_member.ts +52 -0
  50. package/fake-snippets-api/routes/api/orgs/create.ts +46 -0
  51. package/fake-snippets-api/routes/api/orgs/get.ts +39 -0
  52. package/fake-snippets-api/routes/api/orgs/list.ts +31 -0
  53. package/fake-snippets-api/routes/api/orgs/list_members.ts +67 -0
  54. package/fake-snippets-api/routes/api/orgs/remove_member.ts +46 -0
  55. package/fake-snippets-api/routes/api/orgs/update.ts +93 -0
  56. package/fake-snippets-api/routes/api/package_files/get.ts +3 -6
  57. package/fake-snippets-api/routes/api/package_files/list.ts +7 -4
  58. package/fake-snippets-api/routes/api/packages/create.ts +54 -10
  59. package/fake-snippets-api/routes/api/packages/get.ts +23 -0
  60. package/fake-snippets-api/routes/api/packages/images/[owner_github_username]/[unscoped_name]/[view_format].ts +13 -11
  61. package/fake-snippets-api/routes/api/packages/list.ts +29 -2
  62. package/fake-snippets-api/routes/api/packages/update_ai_description.ts +37 -0
  63. package/package.json +27 -24
  64. package/renovate.json +1 -1
  65. package/scripts/generate-sitemap.ts +1 -1
  66. package/src/App.tsx +29 -10
  67. package/src/ContextProviders.tsx +25 -2
  68. package/src/components/CircuitJsonImportDialog.tsx +1 -1
  69. package/src/components/CmdKMenu.tsx +281 -247
  70. package/src/components/DownloadButtonAndMenu.tsx +133 -36
  71. package/src/components/FileSidebar.tsx +41 -50
  72. package/src/components/Footer.tsx +8 -10
  73. package/src/components/Header.tsx +19 -32
  74. package/src/components/Header2.tsx +16 -32
  75. package/src/components/HeaderDropdown.tsx +13 -8
  76. package/src/components/HeaderLogin.tsx +44 -16
  77. package/src/components/HiddenFilesDropdown.tsx +0 -2
  78. package/src/components/NotFound.tsx +5 -5
  79. package/src/components/PackageBreadcrumb.tsx +6 -12
  80. package/src/components/PackageCard.tsx +0 -1
  81. package/src/components/PackageSearchResults.tsx +1 -1
  82. package/src/components/PrefetchPageLink.tsx +7 -1
  83. package/src/components/ProfileRouter.tsx +32 -0
  84. package/src/components/SearchComponent.tsx +12 -8
  85. package/src/components/UserCard.tsx +80 -0
  86. package/src/components/ViewPackagePage/components/ShikiCodeViewer.tsx +20 -11
  87. package/src/components/ViewPackagePage/components/build-status.tsx +1 -1
  88. package/src/components/ViewPackagePage/components/important-files-view.tsx +174 -87
  89. package/src/components/ViewPackagePage/components/main-content-header.tsx +8 -4
  90. package/src/components/ViewPackagePage/components/main-content-view-selector.tsx +1 -2
  91. package/src/components/ViewPackagePage/components/mobile-sidebar.tsx +54 -20
  92. package/src/components/ViewPackagePage/components/package-header.tsx +26 -37
  93. package/src/components/ViewPackagePage/components/preview-image-squares.tsx +11 -19
  94. package/src/components/ViewPackagePage/components/repo-page-content.tsx +33 -25
  95. package/src/components/ViewPackagePage/components/sidebar-about-section.tsx +16 -10
  96. package/src/components/ViewPackagePage/components/sidebar-releases-section.tsx +11 -11
  97. package/src/components/ViewPackagePage/components/sidebar.tsx +0 -2
  98. package/src/components/ViewPackagePage/components/tab-views/files-view.tsx +18 -17
  99. package/src/components/ViewPackagePage/components/tab-views/pcb-view.tsx +1 -2
  100. package/src/components/ViewPackagePage/components/tab-views/schematic-view.tsx +2 -1
  101. package/src/components/ViewPackagePage/components/theme-toggle.tsx +0 -2
  102. package/src/components/ViewPackagePage/hooks/use-toast.tsx +0 -1
  103. package/src/components/dialogs/GitHubRepositorySelector.tsx +56 -49
  104. package/src/components/dialogs/edit-package-details-dialog.tsx +5 -6
  105. package/src/components/dialogs/import-component-dialog.tsx +16 -9
  106. package/src/components/dialogs/import-package-dialog.tsx +3 -2
  107. package/src/components/dialogs/new-package-save-prompt-dialog.tsx +190 -0
  108. package/src/components/organization/OrganizationCard.tsx +204 -0
  109. package/src/components/organization/OrganizationCardSkeleton.tsx +55 -0
  110. package/src/components/organization/OrganizationHeader.tsx +154 -0
  111. package/src/components/organization/OrganizationMembers.tsx +146 -0
  112. package/src/components/package-port/CodeAndPreview.tsx +32 -46
  113. package/src/components/package-port/CodeEditor.tsx +28 -31
  114. package/src/components/package-port/CodeEditorHeader.tsx +128 -63
  115. package/src/components/package-port/EditorNav.tsx +32 -49
  116. package/src/components/preview/ConnectedPackagesList.tsx +8 -8
  117. package/src/components/preview/ConnectedRepoOverview.tsx +102 -2
  118. package/src/components/preview/PackageReleasesDashboard.tsx +53 -36
  119. package/src/components/ui/tree-view.tsx +6 -3
  120. package/src/hooks/use-add-org-member-mutation.ts +51 -0
  121. package/src/hooks/use-create-org-mutation.ts +38 -0
  122. package/src/hooks/use-create-package-mutation.ts +3 -0
  123. package/src/hooks/use-current-package-id.ts +5 -30
  124. package/src/hooks/use-current-package-info.ts +29 -5
  125. package/src/hooks/use-current-package-release.ts +4 -3
  126. package/src/hooks/use-download-zip.ts +2 -2
  127. package/src/hooks/use-global-store.ts +6 -4
  128. package/src/hooks/use-jlcpcb-component-import.tsx +164 -0
  129. package/src/hooks/use-list-org-members.ts +27 -0
  130. package/src/hooks/use-list-user-orgs.ts +25 -0
  131. package/src/hooks/use-org-by-github-handle.ts +26 -0
  132. package/src/hooks/use-org.ts +24 -0
  133. package/src/hooks/use-organization.ts +42 -0
  134. package/src/hooks/use-package-as-snippet.ts +4 -2
  135. package/src/hooks/use-package-builds.ts +6 -2
  136. package/src/hooks/use-package-files.ts +5 -3
  137. package/src/hooks/use-package-release-by-id-or-version.ts +29 -20
  138. package/src/hooks/use-package-release-images.ts +105 -0
  139. package/src/hooks/use-package-release.ts +2 -2
  140. package/src/hooks/use-package-stars.ts +80 -4
  141. package/src/hooks/use-preview-images.ts +6 -3
  142. package/src/hooks/use-remove-org-member-mutation.ts +32 -0
  143. package/src/hooks/use-update-ai-description-mutation.ts +42 -0
  144. package/src/hooks/use-update-org-mutation.ts +41 -0
  145. package/src/hooks/use-warn-user-on-page-change.ts +71 -4
  146. package/src/hooks/useFileManagement.ts +183 -35
  147. package/src/hooks/useOptimizedPackageFilesLoader.ts +136 -0
  148. package/src/hooks/usePackageFilesLoader.ts +2 -2
  149. package/src/hooks/useUpdatePackageFilesMutation.ts +15 -1
  150. package/src/lib/download-fns/download-circuit-png.ts +11 -3
  151. package/src/lib/download-fns/download-gltf-from-circuit-json.ts +44 -0
  152. package/src/lib/download-fns/download-kicad-files.ts +12 -11
  153. package/src/lib/normalize-svg-for-tile.ts +50 -0
  154. package/src/lib/posthog.ts +11 -9
  155. package/src/lib/react-query-api-failure-tracking.ts +148 -0
  156. package/src/lib/sentry.ts +14 -0
  157. package/src/lib/templates/blank-circuit-board-template.ts +0 -4
  158. package/src/lib/ts-lib-cache.ts +122 -7
  159. package/src/lib/utils/checkIfManualEditsImported.ts +4 -4
  160. package/src/lib/utils/findTargetFile.ts +45 -10
  161. package/src/lib/utils/isComponentExported.ts +10 -0
  162. package/src/main.tsx +2 -1
  163. package/src/pages/authorize.tsx +0 -2
  164. package/src/pages/create-organization.tsx +168 -0
  165. package/src/pages/dashboard.tsx +38 -6
  166. package/src/pages/datasheet.tsx +1 -1
  167. package/src/pages/datasheets.tsx +3 -3
  168. package/src/pages/editor.tsx +4 -6
  169. package/src/pages/landing.tsx +6 -7
  170. package/src/pages/latest.tsx +3 -0
  171. package/src/pages/organization-profile.tsx +199 -0
  172. package/src/pages/organization-settings.tsx +566 -0
  173. package/src/pages/package-editor.tsx +21 -21
  174. package/src/pages/preview-release.tsx +76 -136
  175. package/src/pages/quickstart.tsx +159 -123
  176. package/src/pages/release-detail.tsx +119 -31
  177. package/src/pages/search.tsx +192 -57
  178. package/src/pages/settings-redirect.tsx +44 -0
  179. package/src/pages/trending.tsx +29 -20
  180. package/src/pages/user-profile.tsx +58 -7
  181. package/src/pages/view-package.tsx +21 -26
  182. package/vite.config.ts +9 -0
  183. package/fake-snippets-api/routes/api/autocomplete/create_autocomplete.ts +0 -133
  184. package/src/components/Footer2.tsx +0 -100
  185. package/src/components/JLCPCBImportDialog.tsx +0 -280
  186. package/src/components/PackageBuildsPage/LogContent.tsx +0 -72
  187. package/src/components/PackageBuildsPage/PackageBuildDetailsPage.tsx +0 -115
  188. package/src/components/PackageBuildsPage/build-preview-content.tsx +0 -27
  189. package/src/components/PackageBuildsPage/collapsible-section.tsx +0 -63
  190. package/src/components/PackageBuildsPage/package-build-details-panel.tsx +0 -166
  191. package/src/components/PackageBuildsPage/package-build-header.tsx +0 -79
  192. package/src/components/PageSearchComponent.tsx +0 -148
  193. package/src/components/ShippingInformationForm.tsx +0 -423
  194. package/src/components/StaticViewSnippetHeader.tsx +0 -70
  195. package/src/components/ViewPackagePage/components/file-explorer.tsx +0 -67
  196. package/src/components/ViewPackagePage/components/readme-view.tsx +0 -58
  197. package/src/components/ViewPackagePage/components/repo-header-button.tsx +0 -36
  198. package/src/components/ViewPackagePage/components/repo-header.tsx +0 -4
  199. package/src/components/ViewPackagePage/components/sidebar-contributors-section.tsx +0 -31
  200. package/src/components/ViewSnippetHeader.tsx +0 -181
  201. package/src/components/ui/input-otp.tsx +0 -69
  202. package/src/pages/package-builds.tsx +0 -33
  203. package/src/pages/settings.tsx +0 -25
@@ -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,51 @@ 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.get("/package_files/get", {
69
+ params: { package_id: packageId, file_path: "dist/circuit.json" },
70
+ })
71
+ const content = data?.package_file?.content_text
72
+ if (!content) throw new Error("Circuit JSON not found")
73
+ const parsed = JSON.parse(content)
74
+ setFetchedCircuitJson(parsed)
75
+ return parsed
76
+ } catch (error: any) {
77
+ toast({
78
+ title: "Failed to fetch Circuit JSON",
79
+ description: error?.message || error?.toString?.() || "Unknown error",
80
+ variant: "destructive",
81
+ })
82
+ throw error
83
+ } finally {
84
+ setIsFetchingCircuitJson(false)
85
+ }
86
+ }
46
87
 
47
- if (!circuitJson) {
88
+ if (!canDownload) {
48
89
  return (
49
90
  <div className={className}>
50
91
  <Button
@@ -66,7 +107,7 @@ export function DownloadButtonAndMenu({
66
107
  <DropdownMenuTrigger asChild>
67
108
  <Button
68
109
  size="sm"
69
- className="bg-white shadow-none text-gray-900 hover:bg-gray-100 border border-gray-300 px-1 pl-2"
110
+ className="bg-white shadow-none text-gray-900 hover:bg-gray-100 border border-gray-300 px-1 pl-2 select-none"
70
111
  >
71
112
  <Download className="w-4 h-4 mr-2" />
72
113
  Download
@@ -76,11 +117,9 @@ export function DownloadButtonAndMenu({
76
117
  <DropdownMenuContent className="!z-[101]">
77
118
  <DropdownMenuItem
78
119
  className="text-xs"
79
- onSelect={() => {
80
- downloadCircuitJson(
81
- circuitJson,
82
- unscopedName || "circuit" + ".json",
83
- )
120
+ onSelect={async () => {
121
+ const cj = await getCircuitJson()
122
+ downloadCircuitJson(cj, unscopedName || "circuit" + ".json")
84
123
  }}
85
124
  >
86
125
  <Download className="mr-1 h-3 w-3" />
@@ -93,7 +132,12 @@ export function DownloadButtonAndMenu({
93
132
  className="text-xs"
94
133
  onClick={async () => {
95
134
  try {
96
- await downloadGltf(unscopedName || "circuit")
135
+ const cj = await getCircuitJson()
136
+ await downloadGltfFromCircuitJson(
137
+ cj,
138
+ unscopedName || "circuit",
139
+ { format: "glb", boardTextureResolution: 2048 },
140
+ )
97
141
  } catch (error: any) {
98
142
  toast({
99
143
  title: "Error Downloading 3D Model",
@@ -112,8 +156,34 @@ export function DownloadButtonAndMenu({
112
156
  <DropdownMenuItem
113
157
  className="text-xs"
114
158
  onClick={async () => {
159
+ try {
160
+ const cj = await getCircuitJson()
161
+ await downloadGltfFromCircuitJson(
162
+ cj,
163
+ unscopedName || "circuit",
164
+ { format: "gltf", boardTextureResolution: 2048 },
165
+ )
166
+ } catch (error: any) {
167
+ toast({
168
+ title: "Error Downloading 3D Model (GLTF)",
169
+ description: error.toString(),
170
+ variant: "destructive",
171
+ })
172
+ }
173
+ }}
174
+ >
175
+ <CubeIcon className="mr-1 h-3 w-3" />
176
+ <span className="flex-grow mr-6">3D Model</span>
177
+ <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">
178
+ gltf
179
+ </span>
180
+ </DropdownMenuItem>
181
+ <DropdownMenuItem
182
+ className="text-xs"
183
+ onClick={async () => {
184
+ const cj = await getCircuitJson()
115
185
  await downloadFabricationFiles({
116
- circuitJson,
186
+ circuitJson: cj,
117
187
  snippetUnscopedName: unscopedName || "snippet",
118
188
  }).catch((error) => {
119
189
  console.error(error)
@@ -144,8 +214,9 @@ export function DownloadButtonAndMenu({
144
214
  </DropdownMenuItem>
145
215
  <DropdownMenuItem
146
216
  className="text-xs"
147
- onSelect={() => {
148
- downloadKicadFiles(circuitJson, unscopedName || "kicad_project")
217
+ onSelect={async () => {
218
+ const cj = await getCircuitJson()
219
+ downloadKicadFiles(cj, unscopedName || "kicad_project")
149
220
  }}
150
221
  >
151
222
  <Download className="mr-1 h-3 w-3" />
@@ -157,8 +228,9 @@ export function DownloadButtonAndMenu({
157
228
 
158
229
  <DropdownMenuItem
159
230
  className="text-xs"
160
- onSelect={() => {
161
- downloadSchematicSvg(circuitJson, unscopedName || "circuit")
231
+ onSelect={async () => {
232
+ const cj = await getCircuitJson()
233
+ downloadSchematicSvg(cj, unscopedName || "circuit")
162
234
  }}
163
235
  >
164
236
  <Download className="mr-1 h-3 w-3" />
@@ -169,8 +241,9 @@ export function DownloadButtonAndMenu({
169
241
  </DropdownMenuItem>
170
242
  <DropdownMenuItem
171
243
  className="text-xs"
172
- onSelect={() => {
173
- downloadAssemblySvg(circuitJson, unscopedName || "circuit")
244
+ onSelect={async () => {
245
+ const cj = await getCircuitJson()
246
+ downloadAssemblySvg(cj, unscopedName || "circuit")
174
247
  }}
175
248
  >
176
249
  <Download className="mr-1 h-3 w-3" />
@@ -181,7 +254,9 @@ export function DownloadButtonAndMenu({
181
254
  </DropdownMenuItem>
182
255
  <DropdownMenuItem
183
256
  className="text-xs"
184
- onSelect={() => {
257
+ onSelect={async () => {
258
+ const cj = await getCircuitJson()
259
+ setFetchedCircuitJson(cj)
185
260
  openPcbDownloadDialog()
186
261
  }}
187
262
  >
@@ -193,8 +268,9 @@ export function DownloadButtonAndMenu({
193
268
  </DropdownMenuItem>
194
269
  <DropdownMenuItem
195
270
  className="text-xs"
196
- onSelect={() => {
197
- downloadDsnFile(circuitJson, unscopedName || "circuit")
271
+ onSelect={async () => {
272
+ const cj = await getCircuitJson()
273
+ downloadDsnFile(cj, unscopedName || "circuit")
198
274
  }}
199
275
  >
200
276
  <Download className="mr-1 h-3 w-3" />
@@ -205,8 +281,9 @@ export function DownloadButtonAndMenu({
205
281
  </DropdownMenuItem>
206
282
  <DropdownMenuItem
207
283
  className="text-xs"
208
- onClick={() => {
209
- downloadReadableNetlist(circuitJson, unscopedName || "circuit")
284
+ onClick={async () => {
285
+ const cj = await getCircuitJson()
286
+ downloadReadableNetlist(cj, unscopedName || "circuit")
210
287
  }}
211
288
  >
212
289
  <Download className="mr-1 h-3 w-3" />
@@ -217,8 +294,9 @@ export function DownloadButtonAndMenu({
217
294
  </DropdownMenuItem>
218
295
  <DropdownMenuItem
219
296
  className="text-xs"
220
- onSelect={() => {
221
- downloadSpiceFile(circuitJson, unscopedName || "circuit")
297
+ onSelect={async () => {
298
+ const cj = await getCircuitJson()
299
+ downloadSpiceFile(cj, unscopedName || "circuit")
222
300
  }}
223
301
  >
224
302
  <Download className="mr-1 h-3 w-3" />
@@ -229,8 +307,9 @@ export function DownloadButtonAndMenu({
229
307
  </DropdownMenuItem>
230
308
  <DropdownMenuItem
231
309
  className="text-xs"
232
- onSelect={() => {
233
- downloadSimpleRouteJson(circuitJson, unscopedName || "circuit")
310
+ onSelect={async () => {
311
+ const cj = await getCircuitJson()
312
+ downloadSimpleRouteJson(cj, unscopedName || "circuit")
234
313
  }}
235
314
  >
236
315
  <Download className="mr-1 h-3 w-3" />
@@ -243,7 +322,7 @@ export function DownloadButtonAndMenu({
243
322
  {!offerMultipleImageFormats && (
244
323
  <DropdownMenuItem
245
324
  className="text-xs"
246
- onClick={() => {
325
+ onClick={async () => {
247
326
  const desiredImageFormat = [
248
327
  "pcb",
249
328
  "schematic",
@@ -252,8 +331,9 @@ export function DownloadButtonAndMenu({
252
331
  ].includes(desiredImageType)
253
332
  ? desiredImageType
254
333
  : "pcb"
334
+ const cj = await getCircuitJson()
255
335
  downloadPngImage({
256
- circuitJson,
336
+ circuitJson: cj,
257
337
  unscopedName,
258
338
  author,
259
339
  format: desiredImageFormat as ImageFormat,
@@ -271,9 +351,9 @@ export function DownloadButtonAndMenu({
271
351
  <>
272
352
  <DropdownMenuItem
273
353
  className="text-xs"
274
- onClick={() =>
354
+ onClick={async () =>
275
355
  downloadPngImage({
276
- circuitJson,
356
+ circuitJson: await getCircuitJson(),
277
357
  unscopedName,
278
358
  author,
279
359
  format: "schematic",
@@ -288,9 +368,9 @@ export function DownloadButtonAndMenu({
288
368
  </DropdownMenuItem>
289
369
  <DropdownMenuItem
290
370
  className="text-xs"
291
- onClick={() =>
371
+ onClick={async () =>
292
372
  downloadPngImage({
293
- circuitJson,
373
+ circuitJson: await getCircuitJson(),
294
374
  unscopedName,
295
375
  author,
296
376
  format: "pcb",
@@ -305,9 +385,9 @@ export function DownloadButtonAndMenu({
305
385
  </DropdownMenuItem>
306
386
  <DropdownMenuItem
307
387
  className="text-xs"
308
- onClick={() =>
388
+ onClick={async () =>
309
389
  downloadPngImage({
310
- circuitJson,
390
+ circuitJson: await getCircuitJson(),
311
391
  unscopedName,
312
392
  author,
313
393
  format: "assembly",
@@ -320,12 +400,29 @@ export function DownloadButtonAndMenu({
320
400
  png
321
401
  </span>
322
402
  </DropdownMenuItem>
403
+ <DropdownMenuItem
404
+ className="text-xs"
405
+ onClick={async () =>
406
+ downloadPngImage({
407
+ circuitJson: await getCircuitJson(),
408
+ unscopedName,
409
+ author,
410
+ format: "3d",
411
+ })
412
+ }
413
+ >
414
+ <Download className="mr-1 h-3 w-3" />
415
+ <span className="flex-grow mr-6">3D PNG</span>
416
+ <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">
417
+ png
418
+ </span>
419
+ </DropdownMenuItem>
323
420
  </>
324
421
  )}
325
422
  </DropdownMenuContent>
326
423
  </DropdownMenu>
327
424
  <PcbDownloadDialog
328
- circuitJson={circuitJson}
425
+ circuitJson={fetchedCircuitJson || circuitJson || []}
329
426
  fileName={unscopedName || "circuit"}
330
427
  />
331
428
  </div>
@@ -1,25 +1,9 @@
1
- import React, { useState } from "react"
1
+ import React, { useMemo, useState } from "react"
2
2
  import { cn } from "@/lib/utils"
3
- import {
4
- File,
5
- Folder,
6
- MoreVertical,
7
- PanelRightOpen,
8
- Plus,
9
- Trash2,
10
- Pencil,
11
- } from "lucide-react"
12
- import { TreeView, TreeDataItem } from "@/components/ui/tree-view"
13
- import { isHiddenFile } from "./ViewPackagePage/utils/is-hidden-file"
3
+ import { PanelRightOpen, Plus, Loader2 } from "lucide-react"
4
+ import { TreeView } from "@/components/ui/tree-view"
14
5
  import { Input } from "@/components/ui/input"
15
6
  import { transformFilesToTreeData } from "@/lib/utils/transformFilesToTreeData"
16
- import {
17
- DropdownMenu,
18
- DropdownMenuContent,
19
- DropdownMenuGroup,
20
- DropdownMenuItem,
21
- DropdownMenuTrigger,
22
- } from "./ui/dropdown-menu"
23
7
  import type {
24
8
  ICreateFileProps,
25
9
  ICreateFileResult,
@@ -28,8 +12,6 @@ import type {
28
12
  IRenameFileProps,
29
13
  IRenameFileResult,
30
14
  } from "@/hooks/useFileManagement"
31
- import { useToast } from "@/hooks/use-toast"
32
- import { useGlobalStore } from "@/hooks/use-global-store"
33
15
  import type { Package } from "fake-snippets-api/lib/db/schema"
34
16
  type FileName = string
35
17
 
@@ -45,6 +27,8 @@ interface FileSidebarProps {
45
27
  isCreatingFile: boolean
46
28
  setIsCreatingFile: React.Dispatch<React.SetStateAction<boolean>>
47
29
  pkg?: Package
30
+ isLoadingFiles?: boolean
31
+ loadingProgress?: string | null
48
32
  }
49
33
 
50
34
  const FileSidebar: React.FC<FileSidebarProps> = ({
@@ -58,6 +42,9 @@ const FileSidebar: React.FC<FileSidebarProps> = ({
58
42
  handleRenameFile,
59
43
  isCreatingFile,
60
44
  setIsCreatingFile,
45
+ pkg,
46
+ isLoadingFiles = true,
47
+ loadingProgress = null,
61
48
  }) => {
62
49
  const [sidebarOpen, setSidebarOpen] = fileSidebarState
63
50
  const [newFileName, setNewFileName] = useState("")
@@ -66,11 +53,8 @@ const FileSidebar: React.FC<FileSidebarProps> = ({
66
53
  const [selectedFolderForCreation, setSelectedFolderForCreation] = useState<
67
54
  string | null
68
55
  >(null)
69
- const [selectedItemId, setSelectedItemId] = React.useState<string>(
70
- currentFile || "",
71
- )
72
- const { toast } = useToast()
73
- const canModifyFiles = true
56
+ const selectedItemId = useMemo(() => currentFile || "", [currentFile])
57
+ const canModifyFiles = Boolean(pkg) && !isLoadingFiles
74
58
 
75
59
  const onFolderSelect = (folderPath: string) => {
76
60
  setSelectedFolderForCreation(folderPath)
@@ -152,7 +136,6 @@ const FileSidebar: React.FC<FileSidebarProps> = ({
152
136
  setNewFileName("")
153
137
  setErrorMessage("")
154
138
  onFileSelect(finalFileName)
155
- setSelectedItemId(finalFileName)
156
139
  setSelectedFolderForCreation(null)
157
140
  }
158
141
  }
@@ -184,19 +167,31 @@ const FileSidebar: React.FC<FileSidebarProps> = ({
184
167
  className,
185
168
  )}
186
169
  >
187
- <button
188
- onClick={toggleSidebar}
189
- className={`z-[99] mt-2 ml-2 text-gray-400 scale-90 transition-opacity duration-200 ${!sidebarOpen ? "opacity-0 pointer-events-none" : "opacity-100"}`}
190
- >
191
- <PanelRightOpen />
192
- </button>
193
- <button
194
- onClick={() => setIsCreatingFile(true)}
195
- className="absolute top-2 right-2 text-gray-400 hover:text-gray-600"
196
- aria-label="Create new file"
197
- >
198
- <Plus className="w-5 h-5" />
199
- </button>
170
+ <div className="flex items-center justify-between px-2 py-2">
171
+ <button
172
+ onClick={toggleSidebar}
173
+ className={`text-gray-400 scale-90 transition-opacity duration-200 ${!sidebarOpen ? "opacity-0 pointer-events-none" : "opacity-100"}`}
174
+ >
175
+ <PanelRightOpen />
176
+ </button>
177
+ <div className="flex items-center gap-2">
178
+ {isLoadingFiles && (
179
+ <div className="flex items-center gap-1">
180
+ <Loader2 className="w-3 h-3 animate-spin text-gray-400" />
181
+ {loadingProgress && (
182
+ <span className="text-xs text-gray-400">{loadingProgress}</span>
183
+ )}
184
+ </div>
185
+ )}
186
+ <button
187
+ onClick={() => setIsCreatingFile(true)}
188
+ className="text-gray-400 hover:text-gray-600"
189
+ aria-label="Create new file"
190
+ >
191
+ <Plus className="w-5 h-5" />
192
+ </button>
193
+ </div>
194
+ </div>
200
195
  {isCreatingFile && (
201
196
  <div className="p-2">
202
197
  <Input
@@ -253,18 +248,14 @@ const FileSidebar: React.FC<FileSidebarProps> = ({
253
248
  </div>
254
249
  </div>
255
250
  )}
256
- <div
257
- onClick={(e) => {
258
- if (e.target === e.currentTarget) {
259
- setSelectedFolderForCreation(null)
260
- setSelectedItemId("")
261
- }
262
- }}
263
- className="flex-1 border-2 h-full"
264
- >
251
+ <div className="flex-1 border-t h-full">
265
252
  <TreeView
266
253
  data={treeData}
267
- setSelectedItemId={(value) => setSelectedItemId(value || "")}
254
+ setSelectedItemId={(value) => {
255
+ if (value && files[value]) {
256
+ onFileSelect(value)
257
+ }
258
+ }}
268
259
  selectedItemId={selectedItemId}
269
260
  onSelectChange={(item) => {
270
261
  if (item?.onClick) {
@@ -1,7 +1,6 @@
1
1
  import { useGlobalStore } from "@/hooks/use-global-store"
2
2
  import { CircuitBoard } from "lucide-react"
3
3
  import { Link } from "wouter"
4
- import { PrefetchPageLink } from "./PrefetchPageLink"
5
4
 
6
5
  export default function Footer() {
7
6
  const session = useGlobalStore((s) => s.session)
@@ -31,17 +30,16 @@ export default function Footer() {
31
30
  href: `/${session?.github_username}`,
32
31
  hidden: !isLoggedIn,
33
32
  },
34
- { name: "Settings", href: "/settings" },
35
33
  ]
36
34
  .filter((item) => !item.hidden)
37
35
  .map((item) => (
38
- <PrefetchPageLink
36
+ <Link
39
37
  key={item.name}
40
38
  href={item.href}
41
39
  className="hover:underline"
42
40
  >
43
41
  {item.name}
44
- </PrefetchPageLink>
42
+ </Link>
45
43
  ))}
46
44
  <a
47
45
  href="https://chat.tscircuit.com"
@@ -56,15 +54,15 @@ export default function Footer() {
56
54
  <div className="space-y-4">
57
55
  <h3 className="font-semibold uppercase">Explore</h3>
58
56
  <footer className="flex flex-col space-y-2">
59
- <PrefetchPageLink href="/latest" className="hover:underline">
57
+ <Link href="/latest" className="hover:underline">
60
58
  Latest Packages
61
- </PrefetchPageLink>
62
- <PrefetchPageLink href="/trending" className="hover:underline">
59
+ </Link>
60
+ <Link href="/trending" className="hover:underline">
63
61
  Trending Packages
64
- </PrefetchPageLink>
65
- <PrefetchPageLink href="/search" className="hover:underline">
62
+ </Link>
63
+ <Link href="/search" className="hover:underline">
66
64
  Search Packages
67
- </PrefetchPageLink>
65
+ </Link>
68
66
  <a href="https://docs.tscircuit.com" className="hover:underline">
69
67
  Docs
70
68
  </a>
@@ -6,7 +6,7 @@ import { GitHubLogoIcon, DiscordLogoIcon } from "@radix-ui/react-icons"
6
6
  import { Menu, X } from "lucide-react"
7
7
  import React, { useEffect, useState } from "react"
8
8
  import { useLocation } from "wouter"
9
- import { PrefetchPageLink } from "./PrefetchPageLink"
9
+ import { Link } from "wouter"
10
10
  import CmdKMenu from "./CmdKMenu"
11
11
  import HeaderDropdown from "./HeaderDropdown"
12
12
  import SearchComponent from "./SearchComponent"
@@ -27,23 +27,23 @@ const HeaderButton = ({
27
27
 
28
28
  if (location === href || location === alsoHighlightForUrl) {
29
29
  return (
30
- <PrefetchPageLink className={cn("header-button", className)} href={href}>
30
+ <Link className={cn("header-button", className)} href={href}>
31
31
  <Button
32
32
  variant="ghost"
33
33
  className={`border-b-2 rounded-none border-blue-600 header-button ${className}`}
34
34
  >
35
35
  {children}
36
36
  </Button>
37
- </PrefetchPageLink>
37
+ </Link>
38
38
  )
39
39
  }
40
40
 
41
41
  return (
42
- <PrefetchPageLink className={cn("header-button", className)} href={href}>
42
+ <Link className={cn("header-button", className)} href={href}>
43
43
  <Button className={className} variant="ghost">
44
44
  {children}
45
45
  </Button>
46
- </PrefetchPageLink>
46
+ </Link>
47
47
  )
48
48
  }
49
49
 
@@ -59,14 +59,14 @@ export default function Header() {
59
59
  return (
60
60
  <header className="px-4 py-3">
61
61
  <div className="flex items-center">
62
- <PrefetchPageLink
62
+ <Link
63
63
  href="/"
64
- className="text-lg font-semibold whitespace-nowrap"
64
+ className="text-lg font-semibold whitespace-nowrap select-none"
65
65
  >
66
66
  <span className="bg-blue-500 px-2 py-1 rounded-md text-white">
67
67
  tscircuit
68
68
  </span>
69
- </PrefetchPageLink>
69
+ </Link>
70
70
  <div className="hidden md:flex items-center space-x-4">
71
71
  <nav>
72
72
  <ul className="flex items-center gap-2 ml-2">
@@ -80,9 +80,6 @@ export default function Header() {
80
80
  Editor
81
81
  </HeaderButton>
82
82
  </li>
83
- <li>
84
- <HeaderButton href="/datasheets">Datasheets</HeaderButton>
85
- </li>
86
83
  <li>
87
84
  <a href="https://chat.tscircuit.com">
88
85
  <Button variant="ghost">AI</Button>
@@ -93,28 +90,26 @@ export default function Header() {
93
90
  <Button variant="ghost">Docs</Button>
94
91
  </a>
95
92
  </li>
96
- <li>
97
- <a
98
- href="https://tscircuit.com/join"
99
- target="_blank"
100
- className="mr-2"
101
- >
102
- <Button variant="ghost">
103
- <DiscordLogoIcon className="text-gray-400 hover:text-gray-600 transition-colors w-4 h-4" />
104
- </Button>
105
- </a>
106
- </li>
107
93
  </ul>
108
94
  </nav>
109
95
  </div>
110
96
  <div className="flex-grow"></div>
97
+ <a
98
+ href="https://tscircuit.com/join"
99
+ target="_blank"
100
+ className="lg:mr-2"
101
+ >
102
+ <Button variant="ghost">
103
+ <DiscordLogoIcon className="text-gray-400 hidden lg:block hover:text-gray-600 transition-colors w-4 h-4" />
104
+ </Button>
105
+ </a>
111
106
  <a
112
107
  href="https://github.com/tscircuit/tscircuit"
113
108
  target="_blank"
114
- className="mr-4"
109
+ className="lg:mr-4"
115
110
  aria-label="View TSCircuit on GitHub"
116
111
  >
117
- <GitHubLogoIcon className="text-gray-400 hover:text-gray-600 transition-colors" />
112
+ <GitHubLogoIcon className="hidden lg:block text-gray-400 hover:text-gray-600 transition-colors" />
118
113
  </a>
119
114
  {/* <a href="https://tscircuit.com/join" target="_blank" className="mr-2">
120
115
  <DiscordLogoIcon className="text-gray-400 hover:text-gray-600 transition-colors" />
@@ -155,14 +150,6 @@ export default function Header() {
155
150
  Editor
156
151
  </HeaderButton>
157
152
  </li>
158
- <li>
159
- <HeaderButton
160
- className="w-full justify-start"
161
- href="/datasheets"
162
- >
163
- Datasheets
164
- </HeaderButton>
165
- </li>
166
153
  <li>
167
154
  <HeaderButton
168
155
  className="w-full justify-start"