@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.
- package/bun-tests/fake-snippets-api/routes/package_files/create_or_update.test.ts +26 -0
- package/bun.lock +58 -58
- package/dist/bundle.js +56 -5
- package/dist/index.d.ts +15 -2
- package/dist/index.js +47 -1
- package/dist/schema.d.ts +8 -0
- package/dist/schema.js +1 -0
- package/fake-snippets-api/lib/db/db-client.ts +47 -0
- package/fake-snippets-api/lib/db/schema.ts +1 -0
- package/fake-snippets-api/lib/package_file/generate-fs-sha.ts +20 -0
- package/fake-snippets-api/lib/public-mapping/public-map-package.ts +1 -0
- package/fake-snippets-api/routes/api/package_files/create.ts +3 -0
- package/fake-snippets-api/routes/api/package_files/create_or_update.ts +9 -3
- package/fake-snippets-api/routes/api/package_files/delete.ts +3 -0
- package/fake-snippets-api/routes/api/packages/create.ts +1 -0
- package/package.json +11 -11
- package/src/App.tsx +5 -0
- package/src/components/FileSidebar.tsx +111 -37
- package/src/components/JLCPCBImportDialog.tsx +1 -1
- package/src/components/PackageBuildsPage/DeploymentDetailsPage.tsx +56 -0
- package/src/components/PackageBuildsPage/build-preview-content.tsx +11 -0
- package/src/components/PackageBuildsPage/collapsible-section.tsx +70 -0
- package/src/components/PackageBuildsPage/deployment-details-panel.tsx +84 -0
- package/src/components/PackageBuildsPage/deployment-header.tsx +75 -0
- package/src/components/PackageCard.tsx +1 -10
- package/src/components/TrendingPackagesCarousel.tsx +5 -16
- package/src/components/ViewPackagePage/components/mobile-sidebar.tsx +1 -1
- package/src/components/ViewPackagePage/components/preview-image-squares.tsx +2 -6
- package/src/components/package-port/CodeAndPreview.tsx +78 -267
- package/src/components/package-port/CodeEditor.tsx +30 -19
- package/src/components/package-port/CodeEditorHeader.tsx +7 -6
- package/src/components/package-port/EditorNav.tsx +17 -13
- package/src/components/ui/tree-view.tsx +3 -3
- package/src/hooks/use-current-package-id.ts +2 -8
- package/src/hooks/use-preview-images.ts +3 -15
- package/src/hooks/useFileManagement.ts +257 -38
- package/src/hooks/usePackageFilesLoader.ts +2 -2
- package/src/hooks/useUpdatePackageFilesMutation.ts +50 -24
- package/src/lib/utils/checkIfManualEditsImported.ts +9 -0
- package/src/pages/editor.tsx +2 -10
- package/src/pages/package-builds.tsx +33 -0
- package/src/pages/package-editor.tsx +2 -14
- 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 {
|
|
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:
|
|
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((
|
|
42
|
-
const
|
|
43
|
-
const
|
|
44
|
-
|
|
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
|
-
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
!
|
|
55
|
-
(!isHiddenFile(
|
|
72
|
+
!currentNode[segment] &&
|
|
73
|
+
(!isHiddenFile(relativePath) ||
|
|
56
74
|
isHiddenFile(
|
|
57
|
-
currentFile
|
|
75
|
+
currentFile?.startsWith("/")
|
|
76
|
+
? currentFile.slice(1)
|
|
77
|
+
: currentFile || "",
|
|
58
78
|
))
|
|
59
79
|
) {
|
|
60
|
-
|
|
61
|
-
id:
|
|
62
|
-
name:
|
|
63
|
-
icon:
|
|
64
|
-
onClick:
|
|
65
|
-
draggable:
|
|
66
|
-
droppable: !
|
|
67
|
-
children:
|
|
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 (!
|
|
72
|
-
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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={
|
|
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 = `/
|
|
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
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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?.
|
|
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" | "
|
|
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:
|
|
17
|
+
fsMapHash: packageInfo?.latest_package_release_fs_sha ?? "",
|
|
22
18
|
})
|
|
23
19
|
|
|
24
20
|
const handleViewClick = (viewId: string) => {
|