@tscircuit/fake-snippets 0.0.65 → 0.0.67
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/CONTRIBUTING.md +2 -2
- package/README.md +2 -2
- package/bun-tests/fake-snippets-api/fixtures/get-circuit-json.ts +5 -143
- package/bun-tests/fake-snippets-api/fixtures/get-test-server.ts +1 -4
- package/bun-tests/fake-snippets-api/fixtures/start-server.ts +7 -3
- package/bun-tests/fake-snippets-api/routes/order_quotes/create.test.ts +20 -56
- package/bun-tests/fake-snippets-api/routes/package_files/create_or_update.test.ts +2 -2
- package/bun-tests/fake-snippets-api/routes/package_releases/update.test.ts +1 -1
- package/bun-tests/fake-snippets-api/routes/packages/images.test.ts +1 -16
- package/bun.lock +42 -31
- package/dist/bundle.js +34 -41
- package/fake-snippets-api/routes/api/order_quotes/create.ts +30 -37
- package/fake-snippets-api/routes/api/order_quotes/get.ts +5 -8
- package/fake-snippets-api/routes/api/packages/images/[owner_github_username]/[unscoped_name]/[view_format].ts +3 -3
- package/package.json +7 -5
- package/src/App.tsx +0 -7
- package/src/ContextProviders.tsx +2 -0
- package/src/components/DownloadButtonAndMenu.tsx +1 -4
- package/src/components/ErrorTabContent.tsx +1 -1
- package/src/components/Footer.tsx +5 -2
- package/src/components/HeaderLogin.tsx +37 -54
- package/src/components/ImageWithFallback.tsx +37 -0
- package/src/components/JLCPCBImportDialog.tsx +43 -24
- package/src/components/PackageCard.tsx +12 -3
- package/src/components/{SnippetLink.tsx → PackageLink.tsx} +8 -16
- package/src/components/PackageSearchResults.tsx +87 -0
- package/src/components/PackagesList.tsx +3 -3
- package/src/components/PageSearchComponent.tsx +9 -9
- package/src/components/ShippingInformationForm.tsx +1 -1
- package/src/components/TrendingPackagesCarousel.tsx +43 -23
- package/src/components/ViewPackagePage/components/ShikiCodeViewer.tsx +5 -28
- package/src/components/ViewPackagePage/components/main-content-header.tsx +10 -22
- package/src/components/ViewPackagePage/components/mobile-sidebar.tsx +25 -14
- package/src/components/ViewPackagePage/components/package-header.tsx +9 -4
- package/src/components/ViewPackagePage/components/preview-image-squares.tsx +6 -1
- package/src/components/ViewPackagePage/components/repo-page-content.tsx +4 -4
- package/src/components/ViewPackagePage/components/sidebar.tsx +2 -14
- package/src/components/ViewSnippetSidebar.tsx +1 -1
- package/src/components/package-port/CodeEditor.tsx +13 -10
- package/src/components/package-port/CodeEditorHeader.tsx +1 -1
- package/src/components/package-port/EditorNav.tsx +2 -2
- package/src/hooks/use-get-fsmap-hash-for-package.ts +19 -0
- package/src/hooks/use-global-store.ts +1 -0
- package/src/hooks/use-preview-images.ts +20 -4
- package/src/hooks/use-shiki-highlighter.ts +13 -6
- package/src/hooks/use-toast.tsx +1 -1
- package/src/lib/download-fns/download-gltf.ts +3 -10
- package/src/lib/handleManualEditsImport.tsx +1 -1
- package/src/lib/types.ts +4 -2
- package/src/pages/dashboard.tsx +3 -4
- package/src/pages/editor.tsx +20 -14
- package/src/pages/latest.tsx +25 -26
- package/src/pages/package-editor.tsx +14 -2
- package/src/pages/search.tsx +120 -19
- package/src/pages/trending.tsx +14 -59
- package/src/pages/user-profile.tsx +13 -8
- package/bun-tests/fake-snippets-api/routes/snippets/add_star.test.ts +0 -84
- package/bun-tests/fake-snippets-api/routes/snippets/create.test.ts +0 -53
- package/bun-tests/fake-snippets-api/routes/snippets/delete.test.ts +0 -82
- package/bun-tests/fake-snippets-api/routes/snippets/download.test.ts +0 -90
- package/bun-tests/fake-snippets-api/routes/snippets/generate_from_jlcpcb.test.ts +0 -16
- package/bun-tests/fake-snippets-api/routes/snippets/get.test.ts +0 -163
- package/bun-tests/fake-snippets-api/routes/snippets/get_image.test.ts +0 -117
- package/bun-tests/fake-snippets-api/routes/snippets/images.test.ts +0 -114
- package/bun-tests/fake-snippets-api/routes/snippets/list.test.ts +0 -169
- package/bun-tests/fake-snippets-api/routes/snippets/list_newest.test.ts +0 -50
- package/bun-tests/fake-snippets-api/routes/snippets/list_trending.test.ts +0 -72
- package/bun-tests/fake-snippets-api/routes/snippets/remove_star.test.ts +0 -80
- package/bun-tests/fake-snippets-api/routes/snippets/search.test.ts +0 -75
- package/bun-tests/fake-snippets-api/routes/snippets/star-count.test.ts +0 -51
- package/bun-tests/fake-snippets-api/routes/snippets/update.test.ts +0 -175
- package/src/components/AiChatInterface.tsx +0 -229
- package/src/components/CodeAndPreview.tsx +0 -289
- package/src/components/CodeEditor.tsx +0 -539
- package/src/components/CodeEditorHeader.tsx +0 -135
- package/src/components/EditorNav.tsx +0 -502
- package/src/components/PreviewContent.tsx +0 -372
- package/src/components/SnippetCard.tsx +0 -159
- package/src/components/SnippetList.tsx +0 -71
- package/src/hooks/use-compiled-tsx.ts +0 -37
- package/src/hooks/use-run-tsx/construct-circuit.tsx +0 -62
- package/src/hooks/use-run-tsx/index.tsx +0 -256
- package/src/hooks/use-save-snippet.ts +0 -66
- package/src/hooks/use-typecheck.ts +0 -54
- package/src/lib/utils/getSyntaxError.ts +0 -13
- package/src/pages/ai.tsx +0 -92
- package/src/pages/view-snippet.tsx +0 -166
|
@@ -1,372 +0,0 @@
|
|
|
1
|
-
import { CodeEditor } from "@/components/CodeEditor"
|
|
2
|
-
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
|
3
|
-
import { cn } from "@/lib/utils"
|
|
4
|
-
import { applyPcbEditEvents } from "@/lib/utils/pcbManualEditEventHandler"
|
|
5
|
-
import { CadViewer } from "@tscircuit/3d-viewer"
|
|
6
|
-
// import { PCBViewer } from "@tscircuit/pcb-viewer"
|
|
7
|
-
// import { SchematicViewer } from "@tscircuit/schematic-viewer"
|
|
8
|
-
import { useEffect, useRef, useState } from "react"
|
|
9
|
-
import { ErrorFallback } from "./ErrorFallback"
|
|
10
|
-
import { ErrorBoundary } from "react-error-boundary"
|
|
11
|
-
import { ErrorTabContent } from "./ErrorTabContent"
|
|
12
|
-
import PreviewEmptyState from "./PreviewEmptyState"
|
|
13
|
-
import { RunButton } from "./RunButton"
|
|
14
|
-
import { CircuitJsonTableViewer } from "./TableViewer/CircuitJsonTableViewer"
|
|
15
|
-
import { CircuitToSvgWithMouseControl } from "./CircuitToSvgWithMouseControl"
|
|
16
|
-
import { BomTable } from "./BomTable"
|
|
17
|
-
import {
|
|
18
|
-
CheckIcon,
|
|
19
|
-
EllipsisIcon,
|
|
20
|
-
EllipsisVerticalIcon,
|
|
21
|
-
FullscreenIcon,
|
|
22
|
-
MinimizeIcon,
|
|
23
|
-
} from "lucide-react"
|
|
24
|
-
import {
|
|
25
|
-
DropdownMenu,
|
|
26
|
-
DropdownMenuContent,
|
|
27
|
-
DropdownMenuItem,
|
|
28
|
-
DropdownMenuTrigger,
|
|
29
|
-
} from "./ui/dropdown-menu"
|
|
30
|
-
import { Button } from "./ui/button"
|
|
31
|
-
import { PcbViewerWithContainerHeight } from "./PcbViewerWithContainerHeight"
|
|
32
|
-
import type { EditEvent } from "@tscircuit/manual-edit-events"
|
|
33
|
-
|
|
34
|
-
export interface PreviewContentProps {
|
|
35
|
-
code: string
|
|
36
|
-
readOnly?: boolean
|
|
37
|
-
triggerRunTsx: () => void
|
|
38
|
-
tsxRunTriggerCount: number
|
|
39
|
-
errorMessage: string | null
|
|
40
|
-
circuitJson: any
|
|
41
|
-
circuitJsonKey?: string
|
|
42
|
-
className?: string
|
|
43
|
-
showCodeTab?: boolean
|
|
44
|
-
showJsonTab?: boolean
|
|
45
|
-
showImportAndFormatButtons?: boolean
|
|
46
|
-
headerClassName?: string
|
|
47
|
-
leftHeaderContent?: React.ReactNode
|
|
48
|
-
isRunningCode?: boolean
|
|
49
|
-
isStreaming?: boolean
|
|
50
|
-
onToggleFullScreen?: () => void
|
|
51
|
-
isFullScreen?: boolean
|
|
52
|
-
onCodeChange?: (code: string) => void
|
|
53
|
-
onDtsChange?: (dts: string) => void
|
|
54
|
-
manualEditsFileContent?: string
|
|
55
|
-
onManualEditsFileContentChange?: (newmanualEditsFileContent: string) => void
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export const PreviewContent = ({
|
|
59
|
-
code,
|
|
60
|
-
triggerRunTsx,
|
|
61
|
-
tsxRunTriggerCount,
|
|
62
|
-
errorMessage,
|
|
63
|
-
circuitJsonKey = "",
|
|
64
|
-
circuitJson,
|
|
65
|
-
showCodeTab = false,
|
|
66
|
-
showJsonTab = true,
|
|
67
|
-
showImportAndFormatButtons = true,
|
|
68
|
-
className,
|
|
69
|
-
headerClassName,
|
|
70
|
-
leftHeaderContent,
|
|
71
|
-
readOnly,
|
|
72
|
-
isStreaming,
|
|
73
|
-
onToggleFullScreen,
|
|
74
|
-
isFullScreen,
|
|
75
|
-
isRunningCode,
|
|
76
|
-
onCodeChange,
|
|
77
|
-
onDtsChange,
|
|
78
|
-
manualEditsFileContent,
|
|
79
|
-
onManualEditsFileContentChange,
|
|
80
|
-
}: PreviewContentProps) => {
|
|
81
|
-
const [activeTab, setActiveTab] = useState(showCodeTab ? "code" : "pcb")
|
|
82
|
-
const [lastRunHash, setLastRunHash] = useState("")
|
|
83
|
-
const threeJsObjectRef = useRef<any>(null)
|
|
84
|
-
|
|
85
|
-
useEffect(() => {
|
|
86
|
-
window.TSCIRCUIT_3D_OBJECT_REF = threeJsObjectRef
|
|
87
|
-
}, [])
|
|
88
|
-
|
|
89
|
-
const currentCodeHash = code + "\n" + manualEditsFileContent
|
|
90
|
-
const hasCodeChangedSinceLastRun = lastRunHash !== currentCodeHash
|
|
91
|
-
|
|
92
|
-
useEffect(() => {
|
|
93
|
-
if (tsxRunTriggerCount === 0) return
|
|
94
|
-
setLastRunHash(currentCodeHash)
|
|
95
|
-
}, [tsxRunTriggerCount])
|
|
96
|
-
|
|
97
|
-
useEffect(() => {
|
|
98
|
-
if (errorMessage) {
|
|
99
|
-
setActiveTab("error")
|
|
100
|
-
}
|
|
101
|
-
}, [errorMessage])
|
|
102
|
-
|
|
103
|
-
useEffect(() => {
|
|
104
|
-
if (activeTab === "code" && circuitJson && !errorMessage) {
|
|
105
|
-
setActiveTab("pcb")
|
|
106
|
-
}
|
|
107
|
-
}, [circuitJson])
|
|
108
|
-
|
|
109
|
-
return (
|
|
110
|
-
<div className={cn("flex flex-col relative", className)}>
|
|
111
|
-
<div className="md:sticky md:top-2">
|
|
112
|
-
<Tabs
|
|
113
|
-
value={activeTab}
|
|
114
|
-
onValueChange={setActiveTab}
|
|
115
|
-
className="flex-grow flex flex-col"
|
|
116
|
-
>
|
|
117
|
-
<div className={cn("flex items-center gap-2", headerClassName)}>
|
|
118
|
-
{leftHeaderContent}
|
|
119
|
-
{leftHeaderContent && <div className="flex-grow" />}
|
|
120
|
-
<RunButton
|
|
121
|
-
onClick={() => triggerRunTsx()}
|
|
122
|
-
disabled={!hasCodeChangedSinceLastRun && tsxRunTriggerCount !== 0}
|
|
123
|
-
isRunningCode={isRunningCode}
|
|
124
|
-
/>
|
|
125
|
-
{!leftHeaderContent && <div className="flex-grow" />}
|
|
126
|
-
<TabsList>
|
|
127
|
-
{showCodeTab && <TabsTrigger value="code">Code</TabsTrigger>}
|
|
128
|
-
<TabsTrigger value="pcb" className="whitespace-nowrap">
|
|
129
|
-
{circuitJson && (
|
|
130
|
-
<span
|
|
131
|
-
className={cn(
|
|
132
|
-
"inline-flex items-center justify-center w-2 h-2 mr-1 text-xs font-bold text-white rounded-full",
|
|
133
|
-
!hasCodeChangedSinceLastRun
|
|
134
|
-
? "bg-blue-500"
|
|
135
|
-
: "bg-gray-500",
|
|
136
|
-
)}
|
|
137
|
-
/>
|
|
138
|
-
)}
|
|
139
|
-
PCB
|
|
140
|
-
</TabsTrigger>
|
|
141
|
-
<TabsTrigger value="schematic" className="whitespace-nowrap">
|
|
142
|
-
{circuitJson && (
|
|
143
|
-
<span
|
|
144
|
-
className={cn(
|
|
145
|
-
"inline-flex items-center justify-center w-2 h-2 mr-1 text-xs font-bold text-white rounded-full",
|
|
146
|
-
!hasCodeChangedSinceLastRun
|
|
147
|
-
? "bg-blue-500"
|
|
148
|
-
: "bg-gray-500",
|
|
149
|
-
)}
|
|
150
|
-
/>
|
|
151
|
-
)}
|
|
152
|
-
Schematic
|
|
153
|
-
</TabsTrigger>
|
|
154
|
-
<TabsTrigger value="cad">
|
|
155
|
-
{circuitJson && (
|
|
156
|
-
<span
|
|
157
|
-
className={cn(
|
|
158
|
-
"inline-flex items-center justify-center w-2 h-2 mr-1 text-xs font-bold text-white rounded-full",
|
|
159
|
-
!hasCodeChangedSinceLastRun
|
|
160
|
-
? "bg-blue-500"
|
|
161
|
-
: "bg-gray-500",
|
|
162
|
-
)}
|
|
163
|
-
/>
|
|
164
|
-
)}
|
|
165
|
-
3D
|
|
166
|
-
</TabsTrigger>
|
|
167
|
-
<DropdownMenu>
|
|
168
|
-
<DropdownMenuTrigger asChild>
|
|
169
|
-
<div className="whitespace-nowrap p-2 mr-1 cursor-pointer relative">
|
|
170
|
-
<EllipsisIcon className="w-4 h-4" />
|
|
171
|
-
{errorMessage && (
|
|
172
|
-
<span className="inline-flex absolute top-[6px] right-[4px] items-center justify-center w-1 h-1 ml-2 text-[8px] font-bold text-white bg-red-500 rounded-full" />
|
|
173
|
-
)}
|
|
174
|
-
</div>
|
|
175
|
-
</DropdownMenuTrigger>
|
|
176
|
-
<DropdownMenuContent className="*:text-xs">
|
|
177
|
-
<DropdownMenuItem
|
|
178
|
-
onSelect={() => setActiveTab("error")}
|
|
179
|
-
className="flex"
|
|
180
|
-
>
|
|
181
|
-
<CheckIcon
|
|
182
|
-
className={cn(
|
|
183
|
-
"w-3 h-3 mr-2",
|
|
184
|
-
activeTab !== "error" && "invisible",
|
|
185
|
-
)}
|
|
186
|
-
/>
|
|
187
|
-
<div className="flex-grow">Errors</div>
|
|
188
|
-
{errorMessage && (
|
|
189
|
-
<span className="inline-flex items-center justify-center w-3 h-3 ml-2 text-[8px] font-bold text-white bg-red-500 rounded-full">
|
|
190
|
-
1
|
|
191
|
-
</span>
|
|
192
|
-
)}
|
|
193
|
-
</DropdownMenuItem>
|
|
194
|
-
<DropdownMenuItem onSelect={() => setActiveTab("bom")}>
|
|
195
|
-
<CheckIcon
|
|
196
|
-
className={cn(
|
|
197
|
-
"w-3 h-3 mr-2",
|
|
198
|
-
activeTab !== "bom" && "invisible",
|
|
199
|
-
)}
|
|
200
|
-
/>
|
|
201
|
-
Bill of Materials
|
|
202
|
-
</DropdownMenuItem>
|
|
203
|
-
<DropdownMenuItem
|
|
204
|
-
onSelect={() => setActiveTab("circuitjson")}
|
|
205
|
-
>
|
|
206
|
-
<CheckIcon
|
|
207
|
-
className={cn(
|
|
208
|
-
"w-3 h-3 mr-2",
|
|
209
|
-
activeTab !== "circuitjson" && "invisible",
|
|
210
|
-
)}
|
|
211
|
-
/>
|
|
212
|
-
JSON
|
|
213
|
-
</DropdownMenuItem>
|
|
214
|
-
</DropdownMenuContent>
|
|
215
|
-
</DropdownMenu>
|
|
216
|
-
</TabsList>
|
|
217
|
-
{onToggleFullScreen && (
|
|
218
|
-
<Button onClick={onToggleFullScreen} variant="ghost">
|
|
219
|
-
{isFullScreen ? (
|
|
220
|
-
<MinimizeIcon size={16} />
|
|
221
|
-
) : (
|
|
222
|
-
<FullscreenIcon size={16} />
|
|
223
|
-
)}
|
|
224
|
-
</Button>
|
|
225
|
-
)}
|
|
226
|
-
</div>
|
|
227
|
-
{showCodeTab && (
|
|
228
|
-
<TabsContent value="code" className="flex-grow overflow-hidden">
|
|
229
|
-
<div className="h-full">
|
|
230
|
-
<CodeEditor
|
|
231
|
-
initialCode={code}
|
|
232
|
-
manualEditsFileContent={manualEditsFileContent ?? ""}
|
|
233
|
-
isStreaming={isStreaming}
|
|
234
|
-
onCodeChange={onCodeChange!}
|
|
235
|
-
onDtsChange={onDtsChange!}
|
|
236
|
-
readOnly={readOnly}
|
|
237
|
-
showImportAndFormatButtons={showImportAndFormatButtons}
|
|
238
|
-
/>
|
|
239
|
-
</div>
|
|
240
|
-
</TabsContent>
|
|
241
|
-
)}
|
|
242
|
-
|
|
243
|
-
<TabsContent value="pcb">
|
|
244
|
-
<div
|
|
245
|
-
className={cn(
|
|
246
|
-
"mt-4 overflow-hidden",
|
|
247
|
-
isFullScreen ? "h-[calc(100vh-96px)]" : "h-[620px]",
|
|
248
|
-
)}
|
|
249
|
-
>
|
|
250
|
-
<ErrorBoundary fallback={<div>Error loading PCB viewer</div>}>
|
|
251
|
-
{circuitJson ? (
|
|
252
|
-
<PcbViewerWithContainerHeight
|
|
253
|
-
key={circuitJsonKey}
|
|
254
|
-
circuitJson={circuitJson}
|
|
255
|
-
containerClassName={cn(
|
|
256
|
-
"h-full w-full",
|
|
257
|
-
isFullScreen
|
|
258
|
-
? "min-h-[calc(100vh-240px)]"
|
|
259
|
-
: "min-h-[620px]",
|
|
260
|
-
)}
|
|
261
|
-
onEditEventsChanged={(editEvents) => {
|
|
262
|
-
if (editEvents.some((editEvent) => editEvent.in_progress))
|
|
263
|
-
return
|
|
264
|
-
// Update state with new edit events
|
|
265
|
-
const newManualEditsFileContent = applyPcbEditEvents({
|
|
266
|
-
editEvents: editEvents as EditEvent[],
|
|
267
|
-
circuitJson,
|
|
268
|
-
manualEditsFileContent,
|
|
269
|
-
})
|
|
270
|
-
onManualEditsFileContentChange?.(
|
|
271
|
-
JSON.stringify(newManualEditsFileContent, null, 2),
|
|
272
|
-
)
|
|
273
|
-
}}
|
|
274
|
-
/>
|
|
275
|
-
) : (
|
|
276
|
-
<PreviewEmptyState triggerRunTsx={triggerRunTsx} />
|
|
277
|
-
)}
|
|
278
|
-
</ErrorBoundary>
|
|
279
|
-
</div>
|
|
280
|
-
</TabsContent>
|
|
281
|
-
|
|
282
|
-
<TabsContent value="schematic">
|
|
283
|
-
<div
|
|
284
|
-
className={cn(
|
|
285
|
-
"mt-4 overflow-auto",
|
|
286
|
-
isFullScreen ? "h-[calc(100vh-96px)]" : "h-[620px]",
|
|
287
|
-
)}
|
|
288
|
-
>
|
|
289
|
-
<ErrorBoundary fallback={<div>Error loading Schematic</div>}>
|
|
290
|
-
{circuitJson ? (
|
|
291
|
-
<CircuitToSvgWithMouseControl
|
|
292
|
-
key={tsxRunTriggerCount}
|
|
293
|
-
circuitJson={circuitJson}
|
|
294
|
-
/>
|
|
295
|
-
// Waiting for Schematic Viewer to stablize
|
|
296
|
-
// <Schematic
|
|
297
|
-
// style={{ height: "500px" }}
|
|
298
|
-
// key={tsxRunTriggerCount}
|
|
299
|
-
// soup={circuitJson}
|
|
300
|
-
// />
|
|
301
|
-
) : (
|
|
302
|
-
<PreviewEmptyState triggerRunTsx={triggerRunTsx} />
|
|
303
|
-
)}
|
|
304
|
-
</ErrorBoundary>
|
|
305
|
-
</div>
|
|
306
|
-
</TabsContent>
|
|
307
|
-
|
|
308
|
-
<TabsContent value="cad">
|
|
309
|
-
<div
|
|
310
|
-
className={cn(
|
|
311
|
-
"mt-4 overflow-auto",
|
|
312
|
-
isFullScreen ? "h-[calc(100vh-96px)]" : "h-[620px]",
|
|
313
|
-
)}
|
|
314
|
-
>
|
|
315
|
-
<ErrorBoundary FallbackComponent={ErrorFallback}>
|
|
316
|
-
{circuitJson ? (
|
|
317
|
-
<CadViewer
|
|
318
|
-
circuitJson={circuitJson as any}
|
|
319
|
-
ref={threeJsObjectRef}
|
|
320
|
-
/>
|
|
321
|
-
) : (
|
|
322
|
-
<PreviewEmptyState triggerRunTsx={triggerRunTsx} />
|
|
323
|
-
)}
|
|
324
|
-
</ErrorBoundary>
|
|
325
|
-
</div>
|
|
326
|
-
</TabsContent>
|
|
327
|
-
|
|
328
|
-
<TabsContent value="bom">
|
|
329
|
-
<div
|
|
330
|
-
className={cn(
|
|
331
|
-
"mt-4 overflow-auto",
|
|
332
|
-
isFullScreen ? "h-[calc(100vh-96px)]" : "h-[620px]",
|
|
333
|
-
)}
|
|
334
|
-
>
|
|
335
|
-
<ErrorBoundary fallback={<div>Error loading BOM</div>}>
|
|
336
|
-
{circuitJson ? (
|
|
337
|
-
<BomTable circuitJson={circuitJson} />
|
|
338
|
-
) : (
|
|
339
|
-
<PreviewEmptyState triggerRunTsx={triggerRunTsx} />
|
|
340
|
-
)}
|
|
341
|
-
</ErrorBoundary>
|
|
342
|
-
</div>
|
|
343
|
-
</TabsContent>
|
|
344
|
-
|
|
345
|
-
<TabsContent value="circuitjson">
|
|
346
|
-
<div
|
|
347
|
-
className={cn(
|
|
348
|
-
"mt-4 overflow-auto",
|
|
349
|
-
isFullScreen ? "h-[calc(100vh-96px)]" : "h-[620px]",
|
|
350
|
-
)}
|
|
351
|
-
>
|
|
352
|
-
<ErrorBoundary fallback={<div>Error loading JSON viewer</div>}>
|
|
353
|
-
{circuitJson ? (
|
|
354
|
-
<CircuitJsonTableViewer elements={circuitJson as any} />
|
|
355
|
-
) : (
|
|
356
|
-
<PreviewEmptyState triggerRunTsx={triggerRunTsx} />
|
|
357
|
-
)}
|
|
358
|
-
</ErrorBoundary>
|
|
359
|
-
</div>
|
|
360
|
-
</TabsContent>
|
|
361
|
-
<TabsContent value="error">
|
|
362
|
-
{circuitJson || errorMessage ? (
|
|
363
|
-
<ErrorTabContent code={code} errorMessage={errorMessage} />
|
|
364
|
-
) : (
|
|
365
|
-
<PreviewEmptyState triggerRunTsx={triggerRunTsx} />
|
|
366
|
-
)}
|
|
367
|
-
</TabsContent>
|
|
368
|
-
</Tabs>
|
|
369
|
-
</div>
|
|
370
|
-
</div>
|
|
371
|
-
)
|
|
372
|
-
}
|
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
import React from "react"
|
|
2
|
-
import { Link } from "wouter"
|
|
3
|
-
import { Snippet } from "fake-snippets-api/lib/db/schema"
|
|
4
|
-
import { GitHubLogoIcon, StarIcon, LockClosedIcon } from "@radix-ui/react-icons"
|
|
5
|
-
import { GlobeIcon, MoreVertical, PencilIcon, Trash2 } from "lucide-react"
|
|
6
|
-
import { Button } from "@/components/ui/button"
|
|
7
|
-
import {
|
|
8
|
-
DropdownMenu,
|
|
9
|
-
DropdownMenuContent,
|
|
10
|
-
DropdownMenuItem,
|
|
11
|
-
DropdownMenuTrigger,
|
|
12
|
-
} from "@/components/ui/dropdown-menu"
|
|
13
|
-
import { OptimizedImage } from "./OptimizedImage"
|
|
14
|
-
import { SnippetTypeIcon } from "./SnippetTypeIcon"
|
|
15
|
-
import { timeAgo } from "@/lib/utils/timeAgo"
|
|
16
|
-
|
|
17
|
-
export interface SnippetCardProps {
|
|
18
|
-
/** The snippet data to display */
|
|
19
|
-
snippet: Snippet
|
|
20
|
-
/** Base URL for snippet images */
|
|
21
|
-
baseUrl: string
|
|
22
|
-
/** Whether to show the owner name (useful in starred views) */
|
|
23
|
-
showOwner?: boolean
|
|
24
|
-
/** Whether this is the current user's snippet (enables edit/delete options) */
|
|
25
|
-
isCurrentUserSnippet?: boolean
|
|
26
|
-
/** Callback when delete is clicked */
|
|
27
|
-
onDeleteClick?: (e: React.MouseEvent, snippet: Snippet) => void
|
|
28
|
-
/** Custom class name for the card container */
|
|
29
|
-
className?: string
|
|
30
|
-
/** Custom image size (default is h-16 w-16) */
|
|
31
|
-
imageSize?: string
|
|
32
|
-
/** Custom image transform style */
|
|
33
|
-
imageTransform?: string
|
|
34
|
-
/** Whether to render the card with a link to the snippet page */
|
|
35
|
-
withLink?: boolean
|
|
36
|
-
/** Custom render function for actions */
|
|
37
|
-
renderActions?: (snippet: Snippet) => React.ReactNode
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export const SnippetCard: React.FC<SnippetCardProps> = ({
|
|
41
|
-
snippet,
|
|
42
|
-
baseUrl,
|
|
43
|
-
showOwner = false,
|
|
44
|
-
isCurrentUserSnippet = false,
|
|
45
|
-
onDeleteClick,
|
|
46
|
-
className = "",
|
|
47
|
-
imageSize = "h-16 w-16",
|
|
48
|
-
imageTransform = "transition-transform duration-300 -rotate-45 hover:rotate-0 hover:scale-110 scale-150",
|
|
49
|
-
withLink = true,
|
|
50
|
-
renderActions,
|
|
51
|
-
}) => {
|
|
52
|
-
const handleDeleteClick = (e: React.MouseEvent) => {
|
|
53
|
-
e.preventDefault() // Prevent navigation
|
|
54
|
-
if (onDeleteClick) {
|
|
55
|
-
onDeleteClick(e, snippet)
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const cardContent = (
|
|
60
|
-
<div
|
|
61
|
-
className={`border p-4 rounded-md hover:shadow-md transition-shadow flex flex-col gap-4 ${className}`}
|
|
62
|
-
>
|
|
63
|
-
<div className="flex items-start gap-4">
|
|
64
|
-
<div
|
|
65
|
-
className={`${imageSize} flex-shrink-0 rounded-md overflow-hidden`}
|
|
66
|
-
>
|
|
67
|
-
<OptimizedImage
|
|
68
|
-
src={`${baseUrl}/snippets/images/${snippet.owner_name}/${snippet.unscoped_name}/pcb.svg`}
|
|
69
|
-
alt={`${snippet.owner_name}'s profile`}
|
|
70
|
-
className={`object-cover h-full w-full ${imageTransform}`}
|
|
71
|
-
/>
|
|
72
|
-
</div>
|
|
73
|
-
<div className="flex-1 min-w-0">
|
|
74
|
-
<div className="flex justify-between items-start mb-[2px] -mt-[3px]">
|
|
75
|
-
<h2 className="text-md font-semibold truncate pr-[30px]">
|
|
76
|
-
{showOwner && (
|
|
77
|
-
<>
|
|
78
|
-
<span className="text-gray-700 text-md">
|
|
79
|
-
{snippet.owner_name}
|
|
80
|
-
</span>
|
|
81
|
-
<span className="mx-1">/</span>
|
|
82
|
-
</>
|
|
83
|
-
)}
|
|
84
|
-
<span className="text-gray-900">{snippet.unscoped_name}</span>
|
|
85
|
-
</h2>
|
|
86
|
-
<div className="flex items-center gap-2">
|
|
87
|
-
<SnippetTypeIcon
|
|
88
|
-
type={snippet.snippet_type}
|
|
89
|
-
className="pt-[2.5px]"
|
|
90
|
-
/>
|
|
91
|
-
<div className="flex items-center gap-1 text-gray-600">
|
|
92
|
-
<StarIcon className="w-4 h-4 pt-[2.5px]" />
|
|
93
|
-
<span className="text-[16px]">{snippet.star_count || 0}</span>
|
|
94
|
-
</div>
|
|
95
|
-
{isCurrentUserSnippet && onDeleteClick && (
|
|
96
|
-
<DropdownMenu>
|
|
97
|
-
<DropdownMenuTrigger asChild>
|
|
98
|
-
<Button
|
|
99
|
-
variant="ghost"
|
|
100
|
-
size="icon"
|
|
101
|
-
className="h-[1.5rem] w-[1.5rem]"
|
|
102
|
-
>
|
|
103
|
-
<MoreVertical className="h-4 w-4" />
|
|
104
|
-
</Button>
|
|
105
|
-
</DropdownMenuTrigger>
|
|
106
|
-
<DropdownMenuContent>
|
|
107
|
-
<DropdownMenuItem
|
|
108
|
-
className="text-xs text-red-600"
|
|
109
|
-
onClick={handleDeleteClick}
|
|
110
|
-
>
|
|
111
|
-
<Trash2 className="mr-2 h-3 w-3" />
|
|
112
|
-
Delete Snippet
|
|
113
|
-
</DropdownMenuItem>
|
|
114
|
-
</DropdownMenuContent>
|
|
115
|
-
</DropdownMenu>
|
|
116
|
-
)}
|
|
117
|
-
{renderActions && renderActions(snippet)}
|
|
118
|
-
</div>
|
|
119
|
-
</div>
|
|
120
|
-
<p
|
|
121
|
-
className={`${!snippet.description && "h-[1.25rem]"} text-sm text-gray-500 mb-1 truncate max-w-xs`}
|
|
122
|
-
>
|
|
123
|
-
{snippet.description ? snippet.description : " "}
|
|
124
|
-
</p>
|
|
125
|
-
<div className={`flex items-center gap-4`}>
|
|
126
|
-
{snippet.is_private ? (
|
|
127
|
-
<div className="flex items-center text-xs gap-1 text-gray-500">
|
|
128
|
-
<LockClosedIcon height={12} width={12} />
|
|
129
|
-
<span>Private</span>
|
|
130
|
-
</div>
|
|
131
|
-
) : (
|
|
132
|
-
<div className="flex items-center text-xs gap-1 text-gray-500">
|
|
133
|
-
<GlobeIcon height={12} width={12} />
|
|
134
|
-
<span>Public</span>
|
|
135
|
-
</div>
|
|
136
|
-
)}
|
|
137
|
-
<div className="flex items-center text-xs gap-1 text-gray-500">
|
|
138
|
-
<PencilIcon height={12} width={12} />
|
|
139
|
-
<span>{timeAgo(new Date(snippet.updated_at))}</span>
|
|
140
|
-
</div>
|
|
141
|
-
</div>
|
|
142
|
-
</div>
|
|
143
|
-
</div>
|
|
144
|
-
</div>
|
|
145
|
-
)
|
|
146
|
-
|
|
147
|
-
if (withLink) {
|
|
148
|
-
return (
|
|
149
|
-
<Link
|
|
150
|
-
key={snippet.snippet_id}
|
|
151
|
-
href={`/${snippet.owner_name}/${snippet.unscoped_name}`}
|
|
152
|
-
>
|
|
153
|
-
{cardContent}
|
|
154
|
-
</Link>
|
|
155
|
-
)
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
return cardContent
|
|
159
|
-
}
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { Button } from "@/components/ui/button"
|
|
2
|
-
import { ChevronDown, ChevronUp, Star } from "lucide-react"
|
|
3
|
-
import { Link } from "wouter"
|
|
4
|
-
import { Snippet } from "fake-snippets-api/lib/db/schema"
|
|
5
|
-
|
|
6
|
-
interface SnippetListProps {
|
|
7
|
-
title: string
|
|
8
|
-
snippets?: Snippet[]
|
|
9
|
-
showAll?: boolean
|
|
10
|
-
onToggleShowAll?: () => void
|
|
11
|
-
maxItems?: number
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export const SnippetList = ({
|
|
15
|
-
title,
|
|
16
|
-
snippets = [],
|
|
17
|
-
showAll = false,
|
|
18
|
-
onToggleShowAll,
|
|
19
|
-
maxItems = 5,
|
|
20
|
-
}: SnippetListProps) => {
|
|
21
|
-
const displayedSnippets = showAll ? snippets : snippets.slice(0, maxItems)
|
|
22
|
-
|
|
23
|
-
return (
|
|
24
|
-
<div>
|
|
25
|
-
<div className="flex items-center justify-between">
|
|
26
|
-
<h2 className="text-sm font-bold text-gray-700">{title}</h2>
|
|
27
|
-
{snippets.length > maxItems && onToggleShowAll && (
|
|
28
|
-
<Button
|
|
29
|
-
variant="ghost"
|
|
30
|
-
size="sm"
|
|
31
|
-
onClick={onToggleShowAll}
|
|
32
|
-
className="text-blue-600 hover:text-blue-700 hover:bg-transparent"
|
|
33
|
-
>
|
|
34
|
-
{showAll ? (
|
|
35
|
-
<>
|
|
36
|
-
Show less <ChevronUp className="w-3 h-3 ml-1" />
|
|
37
|
-
</>
|
|
38
|
-
) : (
|
|
39
|
-
<>
|
|
40
|
-
Show more <ChevronDown className="w-3 h-3 ml-1" />
|
|
41
|
-
</>
|
|
42
|
-
)}
|
|
43
|
-
</Button>
|
|
44
|
-
)}
|
|
45
|
-
</div>
|
|
46
|
-
<div className="border-b border-gray-200" />
|
|
47
|
-
{snippets && (
|
|
48
|
-
<ul className="space-y-1 mt-2">
|
|
49
|
-
{displayedSnippets.map((snippet) => (
|
|
50
|
-
<li key={snippet.snippet_id}>
|
|
51
|
-
<div className="flex items-center">
|
|
52
|
-
<Link
|
|
53
|
-
href={`/${snippet.owner_name}/${snippet.unscoped_name}`}
|
|
54
|
-
className="text-blue-600 hover:underline text-sm"
|
|
55
|
-
>
|
|
56
|
-
{snippet.owner_name}/{snippet.unscoped_name}
|
|
57
|
-
</Link>
|
|
58
|
-
{snippet.star_count > 0 && (
|
|
59
|
-
<span className="ml-2 text-gray-500 text-xs flex items-center">
|
|
60
|
-
<Star className="w-3 h-3 mr-1" />
|
|
61
|
-
{snippet.star_count}
|
|
62
|
-
</span>
|
|
63
|
-
)}
|
|
64
|
-
</div>
|
|
65
|
-
</li>
|
|
66
|
-
))}
|
|
67
|
-
</ul>
|
|
68
|
-
)}
|
|
69
|
-
</div>
|
|
70
|
-
)
|
|
71
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { useMemo } from "react"
|
|
2
|
-
import * as Babel from "@babel/standalone"
|
|
3
|
-
|
|
4
|
-
export const safeCompileTsx = (
|
|
5
|
-
code: string,
|
|
6
|
-
):
|
|
7
|
-
| { success: true; compiledTsx: string; error?: undefined }
|
|
8
|
-
| { success: false; error: Error; compiledTsx?: undefined } => {
|
|
9
|
-
try {
|
|
10
|
-
return {
|
|
11
|
-
success: true,
|
|
12
|
-
compiledTsx:
|
|
13
|
-
Babel.transform(code, {
|
|
14
|
-
presets: ["react", "typescript"],
|
|
15
|
-
plugins: ["transform-modules-commonjs"],
|
|
16
|
-
filename: "virtual.tsx",
|
|
17
|
-
}).code || "",
|
|
18
|
-
}
|
|
19
|
-
} catch (error: any) {
|
|
20
|
-
return { success: false, error }
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export const useCompiledTsx = (
|
|
25
|
-
code?: string,
|
|
26
|
-
{ isStreaming = false }: { isStreaming?: boolean } = {},
|
|
27
|
-
) => {
|
|
28
|
-
return useMemo(() => {
|
|
29
|
-
if (!code) return ""
|
|
30
|
-
if (isStreaming) return ""
|
|
31
|
-
const result = safeCompileTsx(code)
|
|
32
|
-
if (result.success) {
|
|
33
|
-
return result.compiledTsx
|
|
34
|
-
}
|
|
35
|
-
return `Error: ${result.error.message}`
|
|
36
|
-
}, [code, isStreaming])
|
|
37
|
-
}
|