@tscircuit/fake-snippets 0.0.43 → 0.0.45

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 (44) hide show
  1. package/bun.lock +378 -26
  2. package/dist/bundle.js +411 -335
  3. package/fake-snippets-api/routes/api/_fake/received_quotes.ts +66 -0
  4. package/fake-snippets-api/routes/api/package_files/create_or_update.ts +9 -0
  5. package/fake-snippets-api/routes/api/package_releases/update.ts +25 -18
  6. package/fake-snippets-api/routes/api/packages/update.ts +1 -0
  7. package/package.json +6 -6
  8. package/src/App.tsx +8 -0
  9. package/src/components/CodeAndPreview.tsx +0 -1
  10. package/src/components/CodeEditor.tsx +5 -4
  11. package/src/components/CodeEditorHeader.tsx +1 -26
  12. package/src/components/DownloadButtonAndMenu.tsx +3 -2
  13. package/src/components/EditorNav.tsx +13 -11
  14. package/src/components/ErrorOutline.tsx +35 -0
  15. package/src/components/FileSidebar.tsx +114 -0
  16. package/src/components/NotFound.tsx +37 -0
  17. package/src/components/PreviewContent.tsx +0 -6
  18. package/src/components/TrendingSnippetCarousel.tsx +1 -1
  19. package/src/components/ViewPackagePage/components/package-header.tsx +24 -3
  20. package/src/components/ViewPackagePage/utils/is-hidden-file.ts +0 -1
  21. package/src/components/dialogs/package-visibility-settings-dialog.tsx +10 -1
  22. package/src/components/dialogs/rename-package-dialog.tsx +81 -0
  23. package/src/components/dialogs/update-package-description-dialog.tsx +96 -0
  24. package/src/components/dialogs/view-ts-files-dialog.tsx +0 -6
  25. package/src/components/package-port/CodeAndPreview.tsx +419 -0
  26. package/src/components/package-port/CodeEditor.tsx +498 -0
  27. package/src/components/package-port/CodeEditorHeader.tsx +231 -0
  28. package/src/components/package-port/EditorNav.tsx +520 -0
  29. package/src/components/ui/tree-view.tsx +494 -0
  30. package/src/hooks/use-package.ts +23 -0
  31. package/src/hooks/useForkPackageMutation.ts +49 -0
  32. package/src/hooks/usePackageFilesLoader.ts +56 -0
  33. package/src/hooks/useUpdatePackageFilesMutation.ts +86 -0
  34. package/src/hooks/useUpdatePackageMutation.ts +63 -0
  35. package/src/{prettier.ts → lib/types.ts} +3 -1
  36. package/src/lib/utils/checkIfManualEditsImported.ts +1 -1
  37. package/src/lib/utils/findTargetFile.ts +62 -0
  38. package/src/lib/utils/load-prettier.ts +3 -0
  39. package/src/pages/404.tsx +2 -33
  40. package/src/pages/package-editor.tsx +55 -0
  41. package/src/pages/user-profile.tsx +66 -27
  42. package/src/components/FootprintDialog.tsx +0 -339
  43. package/src/components/ParametersEditor.tsx +0 -140
  44. package/src/lib/utils/parseFootprintParams.ts +0 -52
@@ -0,0 +1,520 @@
1
+ import { Button } from "@/components/ui/button"
2
+ import { GitFork, Star } from "lucide-react"
3
+ import {
4
+ DropdownMenu,
5
+ DropdownMenuContent,
6
+ DropdownMenuItem,
7
+ DropdownMenuSub,
8
+ DropdownMenuSubContent,
9
+ DropdownMenuSubTrigger,
10
+ DropdownMenuTrigger,
11
+ } from "@/components/ui/dropdown-menu"
12
+ import { useGlobalStore } from "@/hooks/use-global-store"
13
+ import { encodeTextToUrlHash } from "@/lib/encodeTextToUrlHash"
14
+ import { cn } from "@/lib/utils"
15
+ import { OpenInNewWindowIcon, LockClosedIcon } from "@radix-ui/react-icons"
16
+ import { AnyCircuitElement } from "circuit-json"
17
+ import { Package } from "fake-snippets-api/lib/db/schema"
18
+ import {
19
+ ChevronDown,
20
+ CodeIcon,
21
+ Download,
22
+ Edit2,
23
+ Eye,
24
+ EyeIcon,
25
+ File,
26
+ FilePenLine,
27
+ MoreVertical,
28
+ Package as PackageIcon,
29
+ Pencil,
30
+ Save,
31
+ Share,
32
+ Sidebar,
33
+ Sparkles,
34
+ Trash2,
35
+ } from "lucide-react"
36
+ import { useEffect, useState } from "react"
37
+ import { useQueryClient } from "react-query"
38
+ import { Link, useLocation } from "wouter"
39
+ import { useAxios } from "@/hooks/use-axios"
40
+ import { useToast } from "@/hooks/use-toast"
41
+ import { useConfirmDeletePackageDialog } from "@/components/dialogs/confirm-delete-package-dialog"
42
+ import { useCreateOrderDialog } from "@/components/dialogs/create-order-dialog"
43
+ import { useFilesDialog } from "@/components/dialogs/files-dialog"
44
+ import { useViewTsFilesDialog } from "@/components/dialogs/view-ts-files-dialog"
45
+ import { DownloadButtonAndMenu } from "@/components/DownloadButtonAndMenu"
46
+ import { TypeBadge } from "@/components/TypeBadge"
47
+ import { useForkPackageMutation } from "@/hooks/useForkPackageMutation"
48
+ import tscircuitCorePkg from "@tscircuit/core/package.json"
49
+ import { useRenamePackageDialog } from "../dialogs/rename-package-dialog"
50
+ import { useUpdatePackageDescriptionDialog } from "../dialogs/update-package-description-dialog"
51
+
52
+ export default function EditorNav({
53
+ circuitJson,
54
+ pkg,
55
+ code,
56
+ hasUnsavedChanges,
57
+ onTogglePreview,
58
+ previewOpen,
59
+ onSave,
60
+ packageType,
61
+ isSaving,
62
+ canSave,
63
+ manualEditsFileContent,
64
+ }: {
65
+ pkg?: Package | null
66
+ circuitJson?: AnyCircuitElement[] | null
67
+ code: string
68
+ packageType?: string
69
+ hasUnsavedChanges: boolean
70
+ previewOpen: boolean
71
+ onTogglePreview: () => void
72
+ isSaving: boolean
73
+ onSave: () => void
74
+ canSave: boolean
75
+ manualEditsFileContent: string
76
+ }) {
77
+ const [, navigate] = useLocation()
78
+ const isLoggedIn = useGlobalStore((s) => Boolean(s.session))
79
+ const session = useGlobalStore((s) => s.session)
80
+ const { Dialog: RenameDialog, openDialog: openRenameDialog } =
81
+ useRenamePackageDialog()
82
+ const {
83
+ Dialog: UpdateDescriptionDialog,
84
+ openDialog: openupdateDescriptionDialog,
85
+ } = useUpdatePackageDescriptionDialog()
86
+ const { Dialog: DeleteDialog, openDialog: openDeleteDialog } =
87
+ useConfirmDeletePackageDialog()
88
+ const { Dialog: CreateOrderDialog, openDialog: openCreateOrderDialog } =
89
+ useCreateOrderDialog()
90
+ const { Dialog: FilesDialog, openDialog: openFilesDialog } = useFilesDialog()
91
+ const { Dialog: ViewTsFilesDialog, openDialog: openViewTsFilesDialog } =
92
+ useViewTsFilesDialog()
93
+
94
+ const [isChangingType, setIsChangingType] = useState(false)
95
+ const [currentType, setCurrentType] = useState(
96
+ packageType ?? pkg?.snippet_type,
97
+ )
98
+ const [isPrivate, setIsPrivate] = useState(pkg?.is_private ?? false)
99
+ const axios = useAxios()
100
+ const { toast } = useToast()
101
+ const qc = useQueryClient()
102
+
103
+ const { mutate: forkSnippet, isLoading: isForking } = useForkPackageMutation({
104
+ pkg: pkg!,
105
+ currentCode: code,
106
+ onSuccess: (forkedPackage) => {
107
+ navigate("/p/editor?package_id=" + forkedPackage.package_id)
108
+ setTimeout(() => {
109
+ window.location.reload() //reload the page
110
+ }, 2000)
111
+ },
112
+ })
113
+
114
+ // Update currentType when snippet or packageType changes
115
+ useEffect(() => {
116
+ setCurrentType(packageType ?? pkg?.snippet_type)
117
+ }, [packageType, pkg?.snippet_type])
118
+
119
+ const handleTypeChange = async (newType: string) => {
120
+ if (!pkg || newType === currentType) return
121
+
122
+ try {
123
+ setIsChangingType(true)
124
+
125
+ const response = await axios.post("/packages/update", {
126
+ package_id: pkg.package_id,
127
+ snippet_type: newType,
128
+ })
129
+
130
+ if (response.status === 200) {
131
+ setCurrentType(newType)
132
+ toast({
133
+ title: "Snippet type changed",
134
+ description: `Successfully changed type to "${newType}"`,
135
+ })
136
+
137
+ // Invalidate queries to refetch data
138
+ await Promise.all([
139
+ qc.invalidateQueries({ queryKey: ["packages"] }),
140
+ qc.invalidateQueries({ queryKey: ["packages", pkg.package_id] }),
141
+ ])
142
+
143
+ // Reload the page to ensure all components reflect the new type
144
+ // window.location.reload()
145
+ } else {
146
+ throw new Error("Failed to update snippet type")
147
+ }
148
+ } catch (error: any) {
149
+ console.error("Error changing snippet type:", error)
150
+ toast({
151
+ title: "Error",
152
+ description:
153
+ error.response?.data?.error?.message ||
154
+ "Failed to change the snippet type. Please try again.",
155
+ variant: "destructive",
156
+ })
157
+ // Reset to previous type on error
158
+ setCurrentType(pkg.snippet_type)
159
+ } finally {
160
+ setIsChangingType(false)
161
+ }
162
+ }
163
+
164
+ const updatePackageVisibilityToPrivate = async (isPrivate: boolean) => {
165
+ if (!pkg) return
166
+
167
+ const response = await axios.post("/packages/update", {
168
+ package_id: pkg.package_id,
169
+ is_private: isPrivate,
170
+ })
171
+
172
+ if (response.status === 200) {
173
+ setIsPrivate(isPrivate)
174
+ toast({
175
+ title: "Package visibility changed",
176
+ description: `Successfully changed visibility to ${
177
+ isPrivate ? "private" : "public"
178
+ }`,
179
+ })
180
+ } else {
181
+ setIsPrivate(pkg.is_private ?? false)
182
+ toast({
183
+ title: "Error",
184
+ description: "Failed to update package visibility",
185
+ variant: "destructive",
186
+ })
187
+ throw new Error("Failed to update package visibility")
188
+ }
189
+ }
190
+
191
+ const canSavePackage =
192
+ !pkg || pkg.owner_github_username === session?.github_username
193
+
194
+ const hasManualEditsChangedFromDefault = manualEditsFileContent !== "{}"
195
+
196
+ return (
197
+ <nav className="lg:flex w-screen items-center justify-between px-2 py-3 border-b border-gray-200 bg-white text-sm border-t">
198
+ <div className="lg:flex items-center my-2 ">
199
+ <div className="flex items-center space-x-1">
200
+ {pkg && (
201
+ <>
202
+ <Link
203
+ className="text-blue-500 font-semibold hover:underline"
204
+ href={`/${pkg.owner_github_username}`}
205
+ >
206
+ {pkg.owner_github_username}
207
+ </Link>
208
+ <span className="px-0.5 text-gray-500">/</span>
209
+ <Link
210
+ className="text-blue-500 font-semibold hover:underline"
211
+ href={`/${pkg.name}`}
212
+ >
213
+ {pkg.unscoped_name}
214
+ </Link>
215
+ {pkg.star_count !== undefined && (
216
+ <span className="ml-2 text-gray-500 text-xs flex items-center">
217
+ <Star className="w-3 h-3 mr-1" />
218
+ {pkg.star_count}
219
+ </span>
220
+ )}
221
+ {pkg.owner_github_username === session?.github_username && (
222
+ <Button
223
+ variant="ghost"
224
+ size="icon"
225
+ className="h-6 w-6 ml-2"
226
+ onClick={() => openRenameDialog()}
227
+ >
228
+ <Pencil className="h-3 w-3 text-gray-700" />
229
+ </Button>
230
+ )}
231
+ {isPrivate && (
232
+ <div className="relative group">
233
+ <LockClosedIcon className="h-3 w-3 text-gray-700" />
234
+ <span className="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-1 hidden group-hover:block bg-black text-white text-xs rounded py-1 px-2">
235
+ private
236
+ </span>
237
+ </div>
238
+ )}
239
+ <Link href={`/${pkg.name}`}>
240
+ <Button variant="ghost" size="icon" className="h-6 w-6">
241
+ <OpenInNewWindowIcon className="h-3 w-3 text-gray-700" />
242
+ </Button>
243
+ </Link>
244
+ </>
245
+ )}
246
+ </div>
247
+ <div className="flex items-center space-x-1">
248
+ {!isLoggedIn && (
249
+ <div className="bg-orange-100 text-orange-700 py-1 px-2 text-xs opacity-70">
250
+ Not logged in, can't save
251
+ </div>
252
+ )}
253
+ <Button
254
+ variant="outline"
255
+ size="sm"
256
+ className={"ml-1 h-6 px-2 text-xs save-button"}
257
+ disabled={
258
+ !isLoggedIn ||
259
+ (!canSavePackage && hasManualEditsChangedFromDefault)
260
+ }
261
+ onClick={canSavePackage ? onSave : () => forkSnippet()}
262
+ >
263
+ {canSavePackage ? (
264
+ <>
265
+ <Save className="mr-1 h-3 w-3" />
266
+ Save
267
+ </>
268
+ ) : (
269
+ <>
270
+ <GitFork className="mr-1 h-3 w-3" />
271
+ Fork
272
+ </>
273
+ )}
274
+ </Button>
275
+ {isSaving && (
276
+ <div className="animate-fadeIn bg-blue-100 text-blue-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
277
+ <svg
278
+ className="animate-spin h-3 w-3 mr-2 text-blue-600"
279
+ xmlns="http://www.w3.org/2000/svg"
280
+ fill="none"
281
+ viewBox="0 0 24 24"
282
+ >
283
+ <circle
284
+ className="opacity-25"
285
+ cx="12"
286
+ cy="12"
287
+ r="10"
288
+ stroke="currentColor"
289
+ strokeWidth="4"
290
+ ></circle>
291
+ <path
292
+ className="opacity-75"
293
+ fill="currentColor"
294
+ d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
295
+ ></path>
296
+ </svg>
297
+ Saving...
298
+ </div>
299
+ )}
300
+ {hasUnsavedChanges && !isSaving && isLoggedIn && (
301
+ <div className="animate-fadeIn bg-yellow-100 text-yellow-800 text-xs font-medium px-2.5 py-0.5 rounded">
302
+ {pkg ? "unsaved changes" : "unsaved"}
303
+ </div>
304
+ )}
305
+ </div>
306
+ </div>
307
+ <div className="flex items-center justify-end -space-x-1">
308
+ <div className="flex mx-2 items-center space-x-1">
309
+ {pkg && <TypeBadge type={`${packageType ?? pkg.snippet_type}`} />}
310
+ <Button
311
+ variant="ghost"
312
+ size="sm"
313
+ disabled={hasUnsavedChanges || isSaving || !pkg}
314
+ onClick={() => navigate(`/ai?snippet_id=${pkg!.package_id}`)}
315
+ >
316
+ <Sparkles className="mr-1 h-3 w-3" />
317
+ Edit with AI
318
+ </Button>
319
+ <DownloadButtonAndMenu
320
+ snippetUnscopedName={pkg?.unscoped_name}
321
+ circuitJson={circuitJson}
322
+ className="hidden md:flex"
323
+ />
324
+ <Button
325
+ variant="ghost"
326
+ size="sm"
327
+ className="hidden md:flex px-2 text-xs"
328
+ onClick={() => {
329
+ const url = encodeTextToUrlHash(code, packageType)
330
+ navigator.clipboard.writeText(url)
331
+ alert("URL copied to clipboard!")
332
+ }}
333
+ >
334
+ <Share className="mr-1 h-3 w-3" />
335
+ Copy URL
336
+ </Button>
337
+ {/* <Button
338
+ variant="ghost"
339
+ size="sm"
340
+ className="hidden md:flex px-2 text-xs"
341
+ >
342
+ <Eye className="mr-1 h-3 w-3" />
343
+ Public
344
+ </Button> */}
345
+ {pkg && (
346
+ <DropdownMenu>
347
+ <DropdownMenuTrigger asChild>
348
+ <Button variant="ghost" size="icon" className="hidden md:flex">
349
+ <MoreVertical className="h-3 w-3" />
350
+ </Button>
351
+ </DropdownMenuTrigger>
352
+ <DropdownMenuContent>
353
+ <DropdownMenuItem
354
+ className="text-xs"
355
+ onClick={() => openCreateOrderDialog()}
356
+ >
357
+ <PackageIcon className="mr-2 h-3 w-3" />
358
+ Submit Order
359
+ </DropdownMenuItem>
360
+ <DropdownMenuItem
361
+ className="text-xs"
362
+ onClick={() => openFilesDialog()}
363
+ >
364
+ <File className="mr-2 h-3 w-3" />
365
+ View Files
366
+ </DropdownMenuItem>
367
+ <DropdownMenuItem
368
+ className="text-xs"
369
+ onClick={() => openupdateDescriptionDialog()}
370
+ >
371
+ <FilePenLine className="mr-2 h-3 w-3" />
372
+ Edit Description
373
+ </DropdownMenuItem>
374
+ <DropdownMenuItem
375
+ className="text-xs"
376
+ onClick={() => openViewTsFilesDialog()}
377
+ >
378
+ <File className="mr-2 h-3 w-3" />
379
+ [Debug] View TS Files
380
+ </DropdownMenuItem>
381
+ <DropdownMenuSub>
382
+ <DropdownMenuSubTrigger
383
+ className="text-xs"
384
+ disabled={isChangingType || hasUnsavedChanges}
385
+ >
386
+ <Edit2 className="mr-2 h-3 w-3" />
387
+ {isChangingType ? "Changing..." : "Change Type"}
388
+ </DropdownMenuSubTrigger>
389
+ <DropdownMenuSubContent>
390
+ <DropdownMenuItem
391
+ className="text-xs"
392
+ disabled={currentType === "board" || isChangingType}
393
+ onClick={() => handleTypeChange("board")}
394
+ >
395
+ Board {currentType === "board" && "✓"}
396
+ </DropdownMenuItem>
397
+ <DropdownMenuItem
398
+ className="text-xs"
399
+ disabled={currentType === "package" || isChangingType}
400
+ onClick={() => handleTypeChange("package")}
401
+ >
402
+ Module {currentType === "package" && "✓"}
403
+ </DropdownMenuItem>
404
+ </DropdownMenuSubContent>
405
+ </DropdownMenuSub>
406
+ <DropdownMenuSub>
407
+ <DropdownMenuSubTrigger className="text-xs">
408
+ <Edit2 className="mr-2 h-3 w-3" />
409
+ Change Package Visibility
410
+ </DropdownMenuSubTrigger>
411
+ <DropdownMenuSubContent>
412
+ <DropdownMenuItem
413
+ className="text-xs"
414
+ disabled={isPrivate}
415
+ onClick={() => updatePackageVisibilityToPrivate(true)}
416
+ >
417
+ Private {isPrivate && "✓"}
418
+ </DropdownMenuItem>
419
+ <DropdownMenuItem
420
+ className="text-xs"
421
+ disabled={!isPrivate}
422
+ onClick={() => updatePackageVisibilityToPrivate(false)}
423
+ >
424
+ Public {!isPrivate && "✓"}
425
+ </DropdownMenuItem>
426
+ </DropdownMenuSubContent>
427
+ </DropdownMenuSub>
428
+ <DropdownMenuItem
429
+ className="text-xs text-red-600"
430
+ onClick={() => openDeleteDialog()}
431
+ >
432
+ <Trash2 className="mr-2 h-3 w-3" />
433
+ Delete Snippet
434
+ </DropdownMenuItem>
435
+ <DropdownMenuItem className="text-xs text-gray-500" disabled>
436
+ @tscircuit/core@{tscircuitCorePkg.version}
437
+ </DropdownMenuItem>
438
+ </DropdownMenuContent>
439
+ </DropdownMenu>
440
+ )}
441
+ <Button
442
+ variant="ghost"
443
+ size="icon"
444
+ className={cn(
445
+ "hidden md:flex",
446
+ !previewOpen
447
+ ? "bg-blue-600 text-white hover:bg-blue-700 hover:text-white"
448
+ : "",
449
+ )}
450
+ onClick={() => onTogglePreview()}
451
+ >
452
+ {previewOpen ? (
453
+ <Sidebar className="h-3 w-3" />
454
+ ) : (
455
+ <EyeIcon className="h-3 w-3" />
456
+ )}
457
+ </Button>
458
+ </div>
459
+ <div className="flex items-center ">
460
+ <DropdownMenu>
461
+ <DropdownMenuTrigger asChild>
462
+ <div className="md:hidden rounded-full p-1 hover:bg-gray-100 cursor-pointer">
463
+ <Button className="md:hidden" variant="secondary" size="sm">
464
+ <ChevronDown className="h-4 w-4" />
465
+ </Button>
466
+ </div>
467
+ </DropdownMenuTrigger>
468
+ <DropdownMenuContent>
469
+ <DropdownMenuItem className="text-xs">
470
+ <Download className="mr-1 h-3 w-3" />
471
+ Download
472
+ </DropdownMenuItem>
473
+ <DropdownMenuItem className="text-xs">
474
+ <Share className="mr-1 h-3 w-3" />
475
+ Copy URL
476
+ </DropdownMenuItem>
477
+ <DropdownMenuItem className="text-xs">
478
+ <Eye className="mr-1 h-3 w-3" />
479
+ Public
480
+ </DropdownMenuItem>
481
+ </DropdownMenuContent>
482
+ </DropdownMenu>
483
+ <Button
484
+ variant="ghost"
485
+ size="sm"
486
+ className="md:hidden"
487
+ onClick={() => onTogglePreview()}
488
+ >
489
+ {previewOpen ? (
490
+ <div className="flex items-center">
491
+ <CodeIcon className="h-3 w-3 mr-1" />
492
+ Show Code
493
+ </div>
494
+ ) : (
495
+ <div className="flex items-center">
496
+ <EyeIcon className="h-3 w-3 mr-1" />
497
+ Show Preview
498
+ </div>
499
+ )}
500
+ </Button>
501
+ </div>
502
+ </div>
503
+ <UpdateDescriptionDialog
504
+ packageId={pkg?.package_id ?? ""}
505
+ currentDescription={pkg?.description ?? ""}
506
+ />
507
+ <RenameDialog
508
+ packageId={pkg?.package_id ?? ""}
509
+ currentName={pkg?.unscoped_name ?? ""}
510
+ />
511
+ <DeleteDialog
512
+ packageId={pkg?.package_id ?? ""}
513
+ packageName={pkg?.unscoped_name ?? ""}
514
+ />
515
+ <CreateOrderDialog />
516
+ <FilesDialog snippetId={pkg?.package_id ?? ""} />
517
+ <ViewTsFilesDialog />
518
+ </nav>
519
+ )
520
+ }