@tscircuit/fake-snippets 0.0.71 → 0.0.73

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 (43) hide show
  1. package/bun-tests/fake-snippets-api/routes/package_files/create_or_update.test.ts +26 -0
  2. package/bun.lock +58 -58
  3. package/dist/bundle.js +56 -5
  4. package/dist/index.d.ts +15 -2
  5. package/dist/index.js +47 -1
  6. package/dist/schema.d.ts +8 -0
  7. package/dist/schema.js +1 -0
  8. package/fake-snippets-api/lib/db/db-client.ts +47 -0
  9. package/fake-snippets-api/lib/db/schema.ts +1 -0
  10. package/fake-snippets-api/lib/package_file/generate-fs-sha.ts +20 -0
  11. package/fake-snippets-api/lib/public-mapping/public-map-package.ts +1 -0
  12. package/fake-snippets-api/routes/api/package_files/create.ts +3 -0
  13. package/fake-snippets-api/routes/api/package_files/create_or_update.ts +9 -3
  14. package/fake-snippets-api/routes/api/package_files/delete.ts +3 -0
  15. package/fake-snippets-api/routes/api/packages/create.ts +1 -0
  16. package/package.json +11 -11
  17. package/src/App.tsx +5 -0
  18. package/src/components/FileSidebar.tsx +111 -37
  19. package/src/components/JLCPCBImportDialog.tsx +1 -1
  20. package/src/components/PackageBuildsPage/DeploymentDetailsPage.tsx +56 -0
  21. package/src/components/PackageBuildsPage/build-preview-content.tsx +11 -0
  22. package/src/components/PackageBuildsPage/collapsible-section.tsx +70 -0
  23. package/src/components/PackageBuildsPage/deployment-details-panel.tsx +84 -0
  24. package/src/components/PackageBuildsPage/deployment-header.tsx +75 -0
  25. package/src/components/PackageCard.tsx +1 -10
  26. package/src/components/TrendingPackagesCarousel.tsx +5 -16
  27. package/src/components/ViewPackagePage/components/mobile-sidebar.tsx +1 -1
  28. package/src/components/ViewPackagePage/components/preview-image-squares.tsx +2 -6
  29. package/src/components/package-port/CodeAndPreview.tsx +78 -267
  30. package/src/components/package-port/CodeEditor.tsx +30 -19
  31. package/src/components/package-port/CodeEditorHeader.tsx +7 -6
  32. package/src/components/package-port/EditorNav.tsx +17 -13
  33. package/src/components/ui/tree-view.tsx +3 -3
  34. package/src/hooks/use-current-package-id.ts +2 -8
  35. package/src/hooks/use-preview-images.ts +3 -15
  36. package/src/hooks/useFileManagement.ts +257 -38
  37. package/src/hooks/usePackageFilesLoader.ts +2 -2
  38. package/src/hooks/useUpdatePackageFilesMutation.ts +50 -24
  39. package/src/lib/utils/checkIfManualEditsImported.ts +9 -0
  40. package/src/pages/editor.tsx +2 -10
  41. package/src/pages/package-builds.tsx +33 -0
  42. package/src/pages/package-editor.tsx +2 -14
  43. package/src/hooks/use-get-fsmap-hash-for-package.ts +0 -19
@@ -1,20 +1,33 @@
1
1
  import React, { useState } from "react"
2
2
  import { cn } from "@/lib/utils"
3
- import { File, Folder, PanelRightOpen, Plus } from "lucide-react"
3
+ import { File, Folder, MoreVertical, PanelRightOpen, Plus } from "lucide-react"
4
4
  import { TreeView, TreeDataItem } from "@/components/ui/tree-view"
5
5
  import { isHiddenFile } from "./ViewPackagePage/utils/is-hidden-file"
6
6
  import { Input } from "@/components/ui/input"
7
- import { CreateFileProps } from "./package-port/CodeAndPreview"
8
-
7
+ import {
8
+ DropdownMenu,
9
+ DropdownMenuContent,
10
+ DropdownMenuGroup,
11
+ DropdownMenuItem,
12
+ DropdownMenuTrigger,
13
+ } from "./ui/dropdown-menu"
14
+ import type {
15
+ ICreateFileProps,
16
+ ICreateFileResult,
17
+ IDeleteFileProps,
18
+ IDeleteFileResult,
19
+ } from "@/hooks/useFileManagement"
20
+ import { useToast } from "@/hooks/use-toast"
9
21
  type FileName = string
10
22
 
11
23
  interface FileSidebarProps {
12
24
  files: Record<FileName, string>
13
- currentFile: FileName
25
+ currentFile: FileName | null
14
26
  onFileSelect: (filename: FileName) => void
15
27
  className?: string
16
28
  fileSidebarState: ReturnType<typeof useState<boolean>>
17
- handleCreateFile: (props: CreateFileProps) => void
29
+ handleCreateFile: (props: ICreateFileProps) => ICreateFileResult
30
+ handleDeleteFile: (props: IDeleteFileProps) => IDeleteFileResult
18
31
  }
19
32
 
20
33
  const FileSidebar: React.FC<FileSidebarProps> = ({
@@ -24,11 +37,13 @@ const FileSidebar: React.FC<FileSidebarProps> = ({
24
37
  className,
25
38
  fileSidebarState,
26
39
  handleCreateFile,
40
+ handleDeleteFile,
27
41
  }) => {
28
42
  const [sidebarOpen, setSidebarOpen] = fileSidebarState
29
43
  const [newFileName, setNewFileName] = useState("")
30
44
  const [isCreatingFile, setIsCreatingFile] = useState(false)
31
45
  const [errorMessage, setErrorMessage] = useState("")
46
+ const { toast } = useToast()
32
47
 
33
48
  const transformFilesToTreeData = (
34
49
  files: Record<FileName, string>,
@@ -38,38 +53,85 @@ const FileSidebar: React.FC<FileSidebarProps> = ({
38
53
  }
39
54
  const root: Record<string, TreeNode> = {}
40
55
 
41
- Object.keys(files).forEach((path) => {
42
- const startsWithSlash = path.startsWith("/")
43
- const parts = (startsWithSlash ? path.slice(1) : path).trim().split("/")
44
- let current = root
56
+ Object.keys(files).forEach((filePath) => {
57
+ const hasLeadingSlash = filePath.startsWith("/")
58
+ const pathSegments = (hasLeadingSlash ? filePath.slice(1) : filePath)
59
+ .trim()
60
+ .split("/")
61
+ let currentNode: Record<string, TreeNode> = root
45
62
 
46
- parts.forEach((part, index) => {
47
- const isFile = index === parts.length - 1
48
- const parentPath = parts.slice(0, index).join("/")
49
- const currentPath = parentPath ? `${parentPath}/${part}` : part
50
- const evaluatedFilePath = startsWithSlash
51
- ? `/${currentPath}`
52
- : currentPath
63
+ pathSegments.forEach((segment, segmentIndex) => {
64
+ const isLeafNode = segmentIndex === pathSegments.length - 1
65
+ const ancestorPath = pathSegments.slice(0, segmentIndex).join("/")
66
+ const relativePath = ancestorPath
67
+ ? `${ancestorPath}/${segment}`
68
+ : segment
69
+ const absolutePath = hasLeadingSlash ? `/${relativePath}` : relativePath
70
+ const itemId = absolutePath
53
71
  if (
54
- !current[part] &&
55
- (!isHiddenFile(currentPath) ||
72
+ !currentNode[segment] &&
73
+ (!isHiddenFile(relativePath) ||
56
74
  isHiddenFile(
57
- currentFile.startsWith("/") ? currentFile.slice(1) : currentFile,
75
+ currentFile?.startsWith("/")
76
+ ? currentFile.slice(1)
77
+ : currentFile || "",
58
78
  ))
59
79
  ) {
60
- current[part] = {
61
- id: currentPath,
62
- name: isFile ? part : part,
63
- icon: isFile ? File : Folder,
64
- onClick: isFile ? () => onFileSelect(evaluatedFilePath) : undefined,
65
- draggable: isFile,
66
- droppable: !isFile,
67
- children: isFile ? undefined : {},
80
+ currentNode[segment] = {
81
+ id: itemId,
82
+ name: isLeafNode ? segment : segment,
83
+ icon: isLeafNode ? File : Folder,
84
+ onClick: isLeafNode ? () => onFileSelect(absolutePath) : undefined,
85
+ draggable: false,
86
+ droppable: !isLeafNode,
87
+ children: isLeafNode ? undefined : {},
88
+ actions: (
89
+ <>
90
+ <DropdownMenu key={itemId}>
91
+ <DropdownMenuTrigger asChild>
92
+ <MoreVertical className="w-4 h-4 text-gray-500 hover:text-gray-700" />
93
+ </DropdownMenuTrigger>
94
+ <DropdownMenuContent
95
+ className="w-48 bg-white shadow-lg rounded-md border-4 z-[100] border-white"
96
+ style={{
97
+ position: "absolute",
98
+ top: "100%",
99
+ left: "0",
100
+ marginTop: "0.5rem",
101
+ width: "8rem",
102
+ padding: "0.01rem",
103
+ }}
104
+ >
105
+ <DropdownMenuGroup>
106
+ <DropdownMenuItem
107
+ onClick={() => {
108
+ const { fileDeleted } = handleDeleteFile({
109
+ filename: relativePath,
110
+ onError: (error) => {
111
+ toast({
112
+ title: `Error deleting file ${relativePath}`,
113
+ description: error.message,
114
+ })
115
+ },
116
+ })
117
+ if (fileDeleted) {
118
+ setErrorMessage("")
119
+ }
120
+ }}
121
+ className="flex items-center px-4 py-1 text-sm text-gray-700 hover:bg-gray-100 cursor-pointer"
122
+ >
123
+ Delete
124
+ </DropdownMenuItem>
125
+ </DropdownMenuGroup>
126
+ </DropdownMenuContent>
127
+ </DropdownMenu>
128
+ </>
129
+ ),
68
130
  }
69
131
  }
70
132
 
71
- if (!isFile && current[part].children) {
72
- current = current[part].children
133
+ if (!isLeafNode && currentNode[segment].children) {
134
+ currentNode = currentNode[segment].children
73
135
  }
74
136
  })
75
137
  })
@@ -90,15 +152,26 @@ const FileSidebar: React.FC<FileSidebarProps> = ({
90
152
  }
91
153
 
92
154
  const treeData = transformFilesToTreeData(files)
93
-
155
+ // console.log("treeData", files)
94
156
  const handleCreateFileInline = () => {
95
- handleCreateFile({
157
+ const { newFileCreated } = handleCreateFile({
96
158
  newFileName,
97
- setErrorMessage,
98
- onFileSelect,
99
- setNewFileName,
100
- setIsCreatingFile,
159
+ onError: (error) => {
160
+ setErrorMessage(error.message)
161
+ },
101
162
  })
163
+ if (newFileCreated) {
164
+ setIsCreatingFile(false)
165
+ setNewFileName("")
166
+ setErrorMessage("")
167
+ }
168
+ }
169
+
170
+ const toggleSidebar = () => {
171
+ setSidebarOpen(!sidebarOpen)
172
+ setErrorMessage("")
173
+ setIsCreatingFile(false)
174
+ setNewFileName("")
102
175
  }
103
176
 
104
177
  return (
@@ -110,7 +183,7 @@ const FileSidebar: React.FC<FileSidebarProps> = ({
110
183
  )}
111
184
  >
112
185
  <button
113
- onClick={() => setSidebarOpen(!sidebarOpen)}
186
+ onClick={toggleSidebar}
114
187
  className={`z-[99] mt-2 ml-2 text-gray-400 scale-90 transition-opacity duration-200 ${
115
188
  !sidebarOpen ? "opacity-0 pointer-events-none" : "opacity-100"
116
189
  }`}
@@ -129,6 +202,7 @@ const FileSidebar: React.FC<FileSidebarProps> = ({
129
202
  <Input
130
203
  autoFocus
131
204
  value={newFileName}
205
+ spellCheck={false}
132
206
  onChange={(e) => setNewFileName(e.target.value)}
133
207
  onBlur={handleCreateFileInline}
134
208
  onKeyDown={(e) => {
@@ -149,7 +223,7 @@ const FileSidebar: React.FC<FileSidebarProps> = ({
149
223
  )}
150
224
  <TreeView
151
225
  data={treeData}
152
- initialSelectedItemId={currentFile}
226
+ initialSelectedItemId={currentFile || ""}
153
227
  onSelectChange={(item) => {
154
228
  if (item?.onClick) {
155
229
  item.onClick()
@@ -50,7 +50,7 @@ export function JLCPCBImportDialog({
50
50
  setHasBeenImportedToAccountAlready(false)
51
51
 
52
52
  try {
53
- const apiUrl = `/snippets/get?owner_name=${session?.github_username}&unscoped_name=${partNumber}`
53
+ const apiUrl = `/packages/get?name=${session?.github_username}/${partNumber}`
54
54
 
55
55
  const existingPackageRes = await axios.get(apiUrl, {
56
56
  validateStatus: (status) => true,
@@ -0,0 +1,56 @@
1
+ "use client"
2
+
3
+ import { useState } from "react"
4
+ import { BuildPreviewContent } from "./build-preview-content"
5
+ import { DeploymentDetailsPanel } from "./deployment-details-panel"
6
+ import { DeploymentHeader } from "./deployment-header"
7
+ import { CollapsibleSection } from "./collapsible-section"
8
+
9
+ export const DeploymentDetailsPage = () => {
10
+ const [openSections, setOpenSections] = useState<Record<string, boolean>>({})
11
+
12
+ const toggleSection = (section: string) => {
13
+ setOpenSections((prev) => ({
14
+ ...prev,
15
+ [section]: !prev[section],
16
+ }))
17
+ }
18
+
19
+ return (
20
+ <div className="min-h-screen bg-gray-50 text-gray-900">
21
+ <DeploymentHeader />
22
+
23
+ <div className="px-6 py-6 container mx-auto">
24
+ {/* Main Content */}
25
+ <div className="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8 items-start">
26
+ {/* Preview Section */}
27
+ <div className="lg:col-span-2">
28
+ <div className="bg-white border border-gray-200 rounded-lg p-4 flex items-center justify-center max-h-[420px]">
29
+ <BuildPreviewContent />
30
+ </div>
31
+ </div>
32
+
33
+ {/* Details Panel */}
34
+ <DeploymentDetailsPanel />
35
+ </div>
36
+
37
+ {/* Collapsible Sections */}
38
+ <div className="space-y-4 mb-8">
39
+ <CollapsibleSection
40
+ title="Transpilation Logs"
41
+ duration="1m 15s"
42
+ isOpen={openSections.summary}
43
+ onToggle={() => toggleSection("summary")}
44
+ />
45
+
46
+ <CollapsibleSection
47
+ title="Circuit JSON Build Logs"
48
+ duration="2m 29s"
49
+ isOpen={openSections.logs}
50
+ onToggle={() => toggleSection("logs")}
51
+ />
52
+ </div>
53
+ </div>
54
+ </div>
55
+ )
56
+ }
@@ -0,0 +1,11 @@
1
+ export function BuildPreviewContent() {
2
+ return (
3
+ <div className="flex items-center justify-center">
4
+ <img
5
+ src="https://placehold.co/600x400/000/31343C"
6
+ alt="Deployment preview"
7
+ className="object-contain rounded p-2 max-h-[400px]"
8
+ />
9
+ </div>
10
+ )
11
+ }
@@ -0,0 +1,70 @@
1
+ import type React from "react"
2
+ import { ChevronRight, CheckCircle2 } from "lucide-react"
3
+ import { Badge } from "@/components/ui/badge"
4
+ import {
5
+ Collapsible,
6
+ CollapsibleContent,
7
+ CollapsibleTrigger,
8
+ } from "@/components/ui/collapsible"
9
+
10
+ interface CollapsibleSectionProps {
11
+ title: string
12
+ duration?: string
13
+ isOpen: boolean
14
+ onToggle: () => void
15
+ badges?: Array<{
16
+ text: string
17
+ icon?: React.ReactNode
18
+ variant?: "default" | "secondary"
19
+ className?: string
20
+ }>
21
+ children?: React.ReactNode
22
+ }
23
+
24
+ export function CollapsibleSection({
25
+ title,
26
+ duration,
27
+ isOpen,
28
+ onToggle,
29
+ badges = [],
30
+ children,
31
+ }: CollapsibleSectionProps) {
32
+ return (
33
+ <Collapsible open={isOpen} onOpenChange={onToggle}>
34
+ <CollapsibleTrigger asChild>
35
+ <div className="flex items-center justify-between p-4 bg-white border border-gray-200 rounded-lg cursor-pointer hover:bg-gray-100">
36
+ <div className="flex items-center gap-2">
37
+ <ChevronRight
38
+ className={`w-4 h-4 transition-transform ${isOpen ? "rotate-90" : ""}`}
39
+ />
40
+ <span className="font-medium">{title}</span>
41
+ </div>
42
+ <div className="flex items-center gap-2">
43
+ {badges.map((badge, index) => (
44
+ <Badge
45
+ key={index}
46
+ variant={badge.variant || "secondary"}
47
+ className={
48
+ badge.className ||
49
+ "bg-gray-200 text-gray-700 flex items-center gap-1"
50
+ }
51
+ >
52
+ {badge.icon}
53
+ {badge.text}
54
+ </Badge>
55
+ ))}
56
+ {duration && (
57
+ <span className="text-sm text-gray-600">{duration}</span>
58
+ )}
59
+ <CheckCircle2 className="w-4 h-4 text-green-500" />
60
+ </div>
61
+ </div>
62
+ </CollapsibleTrigger>
63
+ <CollapsibleContent>
64
+ <div className="p-4 bg-white border-x border-b border-gray-200 rounded-b-lg">
65
+ {children || `${title} details would go here...`}
66
+ </div>
67
+ </CollapsibleContent>
68
+ </Collapsible>
69
+ )
70
+ }
@@ -0,0 +1,84 @@
1
+ import { Globe, GitBranch, GitCommit, Clock } from "lucide-react"
2
+ import { Badge } from "@/components/ui/badge"
3
+
4
+ export function DeploymentDetailsPanel() {
5
+ return (
6
+ <div className="space-y-6 bg-white p-4 border border-gray-200 rounded-lg">
7
+ {/* Created */}
8
+ <div>
9
+ <h3 className="text-sm font-medium text-gray-600 mb-2">Created</h3>
10
+ <div className="flex items-center gap-2">
11
+ <div className="w-6 h-6 bg-orange-500 rounded-full flex items-center justify-center text-xs font-bold">
12
+ I
13
+ </div>
14
+ <span className="text-sm">imrishabh18</span>
15
+ <span className="text-sm text-gray-500">48m ago</span>
16
+ </div>
17
+ </div>
18
+
19
+ {/* Status */}
20
+ <div>
21
+ <h3 className="text-sm font-medium text-gray-600 mb-2">Status</h3>
22
+ <div className="flex items-center gap-2">
23
+ <div className="w-2 h-2 bg-green-500 rounded-full"></div>
24
+ <span className="text-sm">Ready</span>
25
+ <Badge
26
+ variant="secondary"
27
+ className="bg-gray-200 text-gray-700 text-xs"
28
+ >
29
+ Latest
30
+ </Badge>
31
+ </div>
32
+ </div>
33
+
34
+ {/* Time to Ready */}
35
+ <div>
36
+ <h3 className="text-sm font-medium text-gray-600 mb-2">
37
+ Time to Ready
38
+ </h3>
39
+ <div className="flex items-center gap-2">
40
+ <Clock className="w-4 h-4 text-gray-500" />
41
+ <span className="text-sm">1m 3s</span>
42
+ <span className="text-sm text-gray-500">47m ago</span>
43
+ </div>
44
+ </div>
45
+
46
+ {/* Version */}
47
+ <div>
48
+ <h3 className="text-sm font-medium text-gray-600 mb-2">Version</h3>
49
+ <div className="flex items-center gap-2">
50
+ <Globe className="w-4 h-4 text-gray-500" />
51
+ <span className="text-sm">v1.0.3</span>
52
+ <Badge variant="default" className="bg-blue-600 text-white text-xs">
53
+ Current
54
+ </Badge>
55
+ </div>
56
+ </div>
57
+
58
+ {/* Outputs */}
59
+ <div>
60
+ <h3 className="text-sm font-medium text-gray-600 mb-2">Outputs</h3>
61
+ <div>
62
+ <span className="text-sm text-gray-400">None</span>
63
+ </div>
64
+ </div>
65
+
66
+ {/* Source */}
67
+ <div>
68
+ <h3 className="text-sm font-medium text-gray-600 mb-2">Source</h3>
69
+ <div className="space-y-2">
70
+ <div className="flex items-center gap-2">
71
+ <GitBranch className="w-4 h-4 text-gray-500" />
72
+ <span className="text-sm">main</span>
73
+ </div>
74
+ <div className="flex items-center gap-2">
75
+ <GitCommit className="w-4 h-4 text-gray-500" />
76
+ <span className="text-sm text-gray-500">
77
+ edfdc67 support empty file creation (#356)
78
+ </span>
79
+ </div>
80
+ </div>
81
+ </div>
82
+ </div>
83
+ )
84
+ }
@@ -0,0 +1,75 @@
1
+ import { ChevronDown, Download, Github, Link } from "lucide-react"
2
+ import { Button } from "@/components/ui/button"
3
+ import {
4
+ DropdownMenu,
5
+ DropdownMenuContent,
6
+ DropdownMenuItem,
7
+ DropdownMenuTrigger,
8
+ } from "@/components/ui/dropdown-menu"
9
+ import { useParams } from "wouter"
10
+
11
+ export function DeploymentHeader() {
12
+ const { author, packageName } = useParams()
13
+
14
+ return (
15
+ <div className="border-b border-gray-200 bg-white px-6 py-4">
16
+ <div className="flex items-center justify-between container mx-auto">
17
+ <h1 className="text-2xl font-semibold">
18
+ Package Build
19
+ <a
20
+ className="ml-2 bg-gray-100 px-2 py-1 rounded font-mono text-blue-600"
21
+ href={`/${author}/${packageName}`}
22
+ >
23
+ {author}/{packageName}
24
+ </a>
25
+ </h1>
26
+ <div className="flex items-center gap-3">
27
+ <Button
28
+ variant="outline"
29
+ size="sm"
30
+ className="border-gray-300 bg-white hover:bg-gray-50 text-xs sm:text-sm"
31
+ asChild
32
+ >
33
+ <a
34
+ href="https://github.com/tscircuit/tscircuit.com/issues/new"
35
+ target="_blank"
36
+ rel="noopener noreferrer"
37
+ >
38
+ <Github className="w-3 h-3 sm:w-4 sm:h-4 mr-1 sm:mr-2" />
39
+ Report Issue
40
+ </a>
41
+ </Button>
42
+ <DropdownMenu>
43
+ <DropdownMenuTrigger asChild>
44
+ <Button
45
+ size="sm"
46
+ className="bg-gray-900 text-white hover:bg-gray-800"
47
+ >
48
+ <Download className="w-4 h-4 mr-2" />
49
+ Download
50
+ <ChevronDown className="w-4 h-4 ml-2" />
51
+ </Button>
52
+ </DropdownMenuTrigger>
53
+ <DropdownMenuContent
54
+ align="end"
55
+ className="bg-white border-gray-200"
56
+ >
57
+ <DropdownMenuItem className="text-gray-900 hover:bg-gray-100">
58
+ Circuit JSON
59
+ </DropdownMenuItem>
60
+ <DropdownMenuItem className="text-gray-900 hover:bg-gray-100">
61
+ PCB SVG
62
+ </DropdownMenuItem>
63
+ <DropdownMenuItem className="text-gray-900 hover:bg-gray-100">
64
+ Schematic SVG
65
+ </DropdownMenuItem>
66
+ <DropdownMenuItem className="text-gray-900 hover:bg-gray-100">
67
+ 3D Model (stl)
68
+ </DropdownMenuItem>
69
+ </DropdownMenuContent>
70
+ </DropdownMenu>
71
+ </div>
72
+ </div>
73
+ </div>
74
+ )
75
+ }
@@ -12,7 +12,6 @@ import {
12
12
  } from "@/components/ui/dropdown-menu"
13
13
  import { SnippetType, SnippetTypeIcon } from "./SnippetTypeIcon"
14
14
  import { timeAgo } from "@/lib/utils/timeAgo"
15
- import { useGetFsMapHashForPackage } from "@/hooks/use-get-fsmap-hash-for-package"
16
15
  import { ImageWithFallback } from "./ImageWithFallback"
17
16
 
18
17
  export interface PackageCardProps {
@@ -57,10 +56,6 @@ export const PackageCard: React.FC<PackageCardProps> = ({
57
56
  }
58
57
  }
59
58
 
60
- const fsMapHash = useGetFsMapHashForPackage(
61
- pkg.latest_package_release_id ?? "",
62
- )
63
-
64
59
  const cardContent = (
65
60
  <div
66
61
  className={`border p-4 rounded-md hover:shadow-md transition-shadow flex flex-col gap-4 ${className}`}
@@ -70,11 +65,7 @@ export const PackageCard: React.FC<PackageCardProps> = ({
70
65
  className={`${imageSize} flex-shrink-0 rounded-md overflow-hidden`}
71
66
  >
72
67
  <ImageWithFallback
73
- src={`${baseUrl}/packages/images/${pkg.owner_github_username}/${pkg.unscoped_name}/pcb.svg?${new URLSearchParams(
74
- {
75
- fs_sha: fsMapHash ?? "",
76
- },
77
- ).toString()}`}
68
+ src={`${baseUrl}/packages/images/${pkg.owner_github_username}/${pkg.unscoped_name}/pcb.svg?fs_sha=${pkg.latest_package_release_fs_sha}`}
78
69
  alt={`${pkg.unscoped_name} PCB image`}
79
70
  className={`object-cover h-full w-full ${imageTransform}`}
80
71
  />
@@ -5,16 +5,11 @@ import { Link } from "wouter"
5
5
  import { Package } from "fake-snippets-api/lib/db/schema"
6
6
  import { useRef, useState } from "react"
7
7
  import { useSnippetsBaseApiUrl } from "@/hooks/use-snippets-base-api-url"
8
- import { useGetFsMapHashForPackage } from "@/hooks/use-get-fsmap-hash-for-package"
9
8
 
10
9
  const CarouselItem = ({
11
10
  pkg,
12
11
  apiBaseUrl,
13
12
  }: { pkg: Package; apiBaseUrl: string }) => {
14
- const fsMapHash = useGetFsMapHashForPackage(
15
- pkg.latest_package_release_id ?? "",
16
- )
17
-
18
13
  return (
19
14
  <Link href={`/${pkg.owner_github_username}/${pkg.unscoped_name}`}>
20
15
  <div className="flex-shrink-0 w-[200px] bg-white p-3 py-2 rounded-lg shadow-sm border border-gray-200 hover:border-gray-300 transition-colors">
@@ -22,17 +17,11 @@ const CarouselItem = ({
22
17
  {pkg.owner_github_username}/{pkg.unscoped_name}
23
18
  </div>
24
19
  <div className="mb-2 h-24 w-full bg-black rounded overflow-hidden">
25
- {fsMapHash && (
26
- <img
27
- src={`${apiBaseUrl}/packages/images/${pkg.owner_github_username}/${pkg.unscoped_name}/pcb.svg?${new URLSearchParams(
28
- {
29
- fs_sha: fsMapHash,
30
- },
31
- ).toString()}`}
32
- alt="PCB preview"
33
- className="w-full h-full object-contain p-2 scale-[3] rotate-45 hover:scale-[3.5] transition-transform"
34
- />
35
- )}
20
+ <img
21
+ src={`${apiBaseUrl}/packages/images/${pkg.owner_github_username}/${pkg.unscoped_name}/pcb.svg?fs_map=${pkg.latest_package_release_fs_sha}`}
22
+ alt="PCB preview"
23
+ className="w-full h-full object-contain p-2 scale-[3] rotate-45 hover:scale-[3.5] transition-transform"
24
+ />
36
25
  </div>
37
26
  <div className="flex items-center text-xs text-gray-500">
38
27
  <StarFilledIcon className="w-3 h-3 mr-1" />
@@ -72,7 +72,7 @@ const MobileSidebar = ({
72
72
 
73
73
  const { availableViews } = usePreviewImages({
74
74
  packageName: packageInfo?.name,
75
- fsMapHash: packageInfo?.latest_package_release_id ?? "",
75
+ fsMapHash: packageInfo?.latest_package_release_fs_sha ?? "",
76
76
  })
77
77
 
78
78
  const handleViewClick = useCallback(
@@ -1,11 +1,10 @@
1
1
  "use client"
2
2
  import { Skeleton } from "@/components/ui/skeleton"
3
- import { useGetFsMapHashForPackage } from "@/hooks/use-get-fsmap-hash-for-package"
4
3
  import { usePreviewImages } from "@/hooks/use-preview-images"
5
4
  import type { Package } from "fake-snippets-api/lib/db/schema"
6
5
 
7
6
  interface ViewPlaceholdersProps {
8
- packageInfo?: Pick<Package, "name" | "latest_package_release_id">
7
+ packageInfo?: Pick<Package, "name" | "latest_package_release_fs_sha">
9
8
  onViewChange?: (view: "3d" | "pcb" | "schematic") => void
10
9
  }
11
10
 
@@ -13,12 +12,9 @@ export default function PreviewImageSquares({
13
12
  packageInfo,
14
13
  onViewChange,
15
14
  }: ViewPlaceholdersProps) {
16
- const fsMapHash = useGetFsMapHashForPackage(
17
- packageInfo?.latest_package_release_id ?? "",
18
- )
19
15
  const { availableViews } = usePreviewImages({
20
16
  packageName: packageInfo?.name,
21
- fsMapHash: fsMapHash ?? "",
17
+ fsMapHash: packageInfo?.latest_package_release_fs_sha ?? "",
22
18
  })
23
19
 
24
20
  const handleViewClick = (viewId: string) => {