@tscircuit/fake-snippets 0.0.5 → 0.0.7

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 (40) hide show
  1. package/bun-tests/fake-snippets-api/routes/package_files/create.test.ts +375 -0
  2. package/bun-tests/fake-snippets-api/routes/package_files/download.test.ts +248 -0
  3. package/bun-tests/fake-snippets-api/routes/package_files/get.test.ts +220 -0
  4. package/bun-tests/fake-snippets-api/routes/package_files/list.test.ts +204 -0
  5. package/bun-tests/fake-snippets-api/routes/package_releases/get.test.ts +0 -1
  6. package/bun.lock +97 -73
  7. package/dist/bundle.js +602 -213
  8. package/fake-snippets-api/lib/db/db-client.ts +13 -0
  9. package/fake-snippets-api/lib/package_file/get-package-file-id-from-file-descriptor.ts +168 -0
  10. package/fake-snippets-api/lib/package_release/find-package-release-id.ts +122 -0
  11. package/fake-snippets-api/routes/api/package_files/create.ts +132 -0
  12. package/fake-snippets-api/routes/api/package_files/download.ts +70 -153
  13. package/fake-snippets-api/routes/api/package_files/get.ts +24 -5
  14. package/fake-snippets-api/routes/api/package_files/list.ts +16 -28
  15. package/fake-snippets-api/routes/api/package_releases/get.ts +21 -1
  16. package/index.html +113 -20
  17. package/package.json +9 -9
  18. package/playwright-tests/profile-page.spec.ts +59 -0
  19. package/playwright-tests/snapshots/profile-page.spec.ts-profile-page-before-delete.png +0 -0
  20. package/playwright-tests/snapshots/profile-page.spec.ts-profile-page-delete-dialog.png +0 -0
  21. package/playwright-tests/snapshots/profile-page.spec.ts-profile-page-dropdown-open.png +0 -0
  22. package/scripts/generate-image-sizes.ts +0 -1
  23. package/scripts/generate_bundle_stats.js +22 -6
  24. package/src/App.tsx +12 -1
  25. package/src/components/AiChatInterface.tsx +8 -0
  26. package/src/components/Analytics.tsx +1 -1
  27. package/src/components/CodeAndPreview.tsx +9 -3
  28. package/src/components/CreateNewSnippetWithAiHero.tsx +6 -2
  29. package/src/components/EditorNav.tsx +4 -0
  30. package/src/components/Footer.tsx +1 -1
  31. package/src/components/Header.tsx +7 -10
  32. package/src/components/HeaderLogin.tsx +1 -1
  33. package/src/components/PreviewContent.tsx +4 -1
  34. package/src/components/SnippetList.tsx +71 -0
  35. package/src/lib/templates/blinking-led-board-template.ts +2 -1
  36. package/src/pages/dashboard.tsx +19 -44
  37. package/src/pages/editor.tsx +1 -1
  38. package/src/pages/landing.tsx +8 -16
  39. package/src/pages/quickstart.tsx +9 -9
  40. package/src/pages/user-profile.tsx +50 -3
@@ -75,16 +75,15 @@ export default function Header() {
75
75
  <HeaderButton href="/dashboard">Dashboard</HeaderButton>
76
76
  </li>
77
77
  )}
78
- <li>
79
- <HeaderButton href="/newest">Newest</HeaderButton>
80
- </li>
81
78
  <li>
82
79
  <HeaderButton href="/quickstart" alsoHighlightForUrl="/editor">
83
80
  Editor
84
81
  </HeaderButton>
85
82
  </li>
86
83
  <li>
87
- <HeaderButton href="/ai">AI</HeaderButton>
84
+ <a href="https://chat.tscircuit.com">
85
+ <Button variant="ghost">AI</Button>
86
+ </a>
88
87
  </li>
89
88
  <li>
90
89
  <a href="https://docs.tscircuit.com">
@@ -144,11 +143,6 @@ export default function Header() {
144
143
  </HeaderButton>
145
144
  </li>
146
145
  )}
147
- <li>
148
- <HeaderButton className="w-full justify-start" href="/newest">
149
- Newest
150
- </HeaderButton>
151
- </li>
152
146
  <li>
153
147
  <HeaderButton
154
148
  className="w-full justify-start"
@@ -159,7 +153,10 @@ export default function Header() {
159
153
  </HeaderButton>
160
154
  </li>
161
155
  <li>
162
- <HeaderButton className="w-full justify-start" href="/ai">
156
+ <HeaderButton
157
+ className="w-full justify-start"
158
+ href="https://chat.tscircuit.com"
159
+ >
163
160
  AI
164
161
  </HeaderButton>
165
162
  </li>
@@ -41,7 +41,7 @@ export const HeaderLogin: React.FC<HeaderLoginProps> = () => {
41
41
  }
42
42
  }
43
43
  return (
44
- <div className="flex items-center space-x-2 justify-end">
44
+ <div className="flex items-center md:space-x-2 justify-end">
45
45
  <Button onClick={() => signIn()} variant="ghost">
46
46
  Log in
47
47
  </Button>
@@ -320,7 +320,10 @@ export const PreviewContent = ({
320
320
  >
321
321
  <ErrorBoundary FallbackComponent={ErrorFallback}>
322
322
  {circuitJson ? (
323
- <CadViewer soup={circuitJson as any} ref={threeJsObjectRef} />
323
+ <CadViewer
324
+ circuitJson={circuitJson as any}
325
+ ref={threeJsObjectRef}
326
+ />
324
327
  ) : (
325
328
  <PreviewEmptyState triggerRunTsx={triggerRunTsx} />
326
329
  )}
@@ -0,0 +1,71 @@
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
+ }
@@ -3,11 +3,12 @@ export const blinkingLedBoardTemplate = {
3
3
  code: `
4
4
  import { useUsbC } from "@tsci/seveibar.smd-usb-c"
5
5
  import { useRedLed } from "@tsci/seveibar.red-led"
6
- import { A555Timer } from "@tsci/seveibar.a555timer"
6
+ import { useNE555P } from "@tsci/seveibar.a555timer"
7
7
 
8
8
  export const MyBlinkingLedCircuit = () => {
9
9
  const USBC = useUsbC("USBC")
10
10
  const Led = useRedLed("LED")
11
+ const A555Timer = useNE555P("B1")
11
12
 
12
13
  return (
13
14
  <board width="30mm" height="30mm" schAutoLayoutEnabled>
@@ -1,4 +1,4 @@
1
- import React from "react"
1
+ import React, { useState } from "react"
2
2
  import { useQuery } from "react-query"
3
3
  import { useAxios } from "@/hooks/use-axios"
4
4
  import Header from "@/components/Header"
@@ -6,15 +6,18 @@ import Footer from "@/components/Footer"
6
6
  import { Snippet } from "fake-snippets-api/lib/db/schema"
7
7
  import { Link } from "wouter"
8
8
  import { CreateNewSnippetWithAiHero } from "@/components/CreateNewSnippetWithAiHero"
9
- import { Edit2, Star } from "lucide-react"
9
+ import { Edit2, Star, ChevronDown, ChevronUp } from "lucide-react"
10
10
  import { Button } from "@/components/ui/button"
11
11
  import { useGlobalStore } from "@/hooks/use-global-store"
12
12
  import { PrefetchPageLink } from "@/components/PrefetchPageLink"
13
+ import { SnippetList } from "@/components/SnippetList"
13
14
 
14
15
  export const DashboardPage = () => {
15
16
  const axios = useAxios()
16
17
 
17
18
  const currentUser = useGlobalStore((s) => s.session?.github_username)
19
+ const [showAllTrending, setShowAllTrending] = useState(false)
20
+ const [showAllNewest, setShowAllNewest] = useState(false)
18
21
 
19
22
  const {
20
23
  data: mySnippets,
@@ -114,48 +117,20 @@ export const DashboardPage = () => {
114
117
  )}
115
118
  </div>
116
119
  <div className="md:w-1/4">
117
- <h2 className="text-sm font-bold mb-2 text-gray-700 border-b border-gray-200">
118
- Trending Snippets
119
- </h2>
120
- {trendingSnippets && (
121
- <ul className="space-y-1">
122
- {trendingSnippets.map((snippet) => (
123
- <li key={snippet.snippet_id}>
124
- <div className="flex items-center">
125
- <Link
126
- href={`/${snippet.owner_name}/${snippet.unscoped_name}`}
127
- className="text-blue-600 hover:underline text-sm"
128
- >
129
- {snippet.owner_name}/{snippet.unscoped_name}
130
- </Link>
131
- {snippet.star_count > 0 && (
132
- <span className="ml-2 text-gray-500 text-xs flex items-center">
133
- <Star className="w-3 h-3 mr-1" />
134
- {snippet.star_count}
135
- </span>
136
- )}
137
- </div>
138
- </li>
139
- ))}
140
- </ul>
141
- )}
142
- <h2 className="text-sm font-bold mt-8 mb-2 text-gray-700 border-b border-gray-200">
143
- Newest Snippets
144
- </h2>
145
- {newestSnippets && (
146
- <ul className="space-y-1">
147
- {newestSnippets.map((snippet) => (
148
- <li key={snippet.snippet_id}>
149
- <Link
150
- href={`/${snippet.name}`}
151
- className="text-blue-600 hover:underline text-sm"
152
- >
153
- {snippet.name}
154
- </Link>
155
- </li>
156
- ))}
157
- </ul>
158
- )}
120
+ <SnippetList
121
+ title="Trending Snippets"
122
+ snippets={trendingSnippets}
123
+ showAll={showAllTrending}
124
+ onToggleShowAll={() => setShowAllTrending(!showAllTrending)}
125
+ />
126
+ <div className="mt-8">
127
+ <SnippetList
128
+ title="Newest Snippets"
129
+ snippets={newestSnippets}
130
+ showAll={showAllNewest}
131
+ onToggleShowAll={() => setShowAllNewest(!showAllNewest)}
132
+ />
133
+ </div>
159
134
  </div>
160
135
  </div>
161
136
  </div>
@@ -9,7 +9,7 @@ export const EditorPage = () => {
9
9
  const { data: snippet, isLoading, error } = useSnippet(snippetId)
10
10
 
11
11
  return (
12
- <div>
12
+ <div className="overflow-x-hidden">
13
13
  <Header />
14
14
  {!error && <CodeAndPreview snippet={snippet} />}
15
15
  {error && error.status === 404 && (
@@ -50,8 +50,8 @@ export function LandingPage() {
50
50
  <Badge variant="secondary" className="w-fit">
51
51
  Open-Source, MIT Licensed
52
52
  </Badge>
53
- <h1 className="text-3xl font-bold tracking-tighter sm:text-5xl xl:text-6xl/none">
54
- The New Foundation for Electronic Design
53
+ <h1 className="text-3xl font-bold tracking-tight sm:text-5xl xl:text-6xl/none">
54
+ AI codes electronics with tscircuit
55
55
  </h1>
56
56
  <p className="max-w-[600px] text-muted-foreground md:text-xl">
57
57
  Build electronics with code, AI, and drag'n'drop tools.
@@ -60,20 +60,12 @@ export function LandingPage() {
60
60
  and more.
61
61
  </p>
62
62
  </div>
63
- <div className="flex flex-col items-center gap-2 min-[400px]:flex-row">
64
- <Button
65
- onClick={() => {
66
- if (!isLoggedIn) {
67
- signIn()
68
- } else {
69
- navigate("/dashboard")
70
- }
71
- }}
72
- size="lg"
73
- aria-label="Get started with TSCircuit"
74
- >
75
- Get Started
76
- </Button>
63
+ <div className="flex flex-col items-center gap-2 min-[500px]:flex-row">
64
+ <a href="https://docs.tscircuit.com">
65
+ <Button size="lg" aria-label="Get started with TSCircuit">
66
+ Get Started
67
+ </Button>
68
+ </a>
77
69
  <PrefetchPageLink href="/quickstart">
78
70
  <Button
79
71
  size="lg"
@@ -65,13 +65,13 @@ export const QuickstartPage = () => {
65
65
  key={snippet.snippet_id}
66
66
  href={`/editor?snippet_id=${snippet.snippet_id}`}
67
67
  >
68
- <Card className="hover:shadow-md transition-shadow rounded-md">
68
+ <Card className="hover:shadow-md transition-shadow rounded-md flex flex-col h-full">
69
69
  <CardHeader className="pb-0 p-4">
70
70
  <CardTitle className="text-md">
71
71
  {snippet.unscoped_name}
72
72
  </CardTitle>
73
73
  </CardHeader>
74
- <CardContent className="p-4 pt-0">
74
+ <CardContent className="p-4 pt-0 mt-auto">
75
75
  <p className="text-sm text-gray-500">
76
76
  Last edited:{" "}
77
77
  {new Date(snippet.updated_at).toLocaleDateString()}
@@ -118,7 +118,7 @@ export const QuickstartPage = () => {
118
118
 
119
119
  <div className="mt-12">
120
120
  <h2 className="text-xl font-semibold mb-4">Import as Snippet</h2>
121
- <div className="grid grid-cols-1 md:grid-cols-4 gap-4">
121
+ <div className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-4 gap-4">
122
122
  {[
123
123
  { name: "KiCad Footprint", type: "footprint" },
124
124
  { name: "KiCad Project", type: "board" },
@@ -126,7 +126,7 @@ export const QuickstartPage = () => {
126
126
  ].map((template, index) => (
127
127
  <Card
128
128
  key={index}
129
- className="hover:shadow-md transition-shadow rounded-md opacity-50"
129
+ className="hover:shadow-md transition-shadow rounded-md opacity-50 flex flex-col"
130
130
  >
131
131
  <CardHeader className="p-4 pb-0">
132
132
  <CardTitle className="text-lg flex items-center justify-between">
@@ -134,7 +134,7 @@ export const QuickstartPage = () => {
134
134
  <TypeBadge type={template.type as any} className="ml-2" />
135
135
  </CardTitle>
136
136
  </CardHeader>
137
- <CardContent className="p-4">
137
+ <CardContent className="p-4 mt-auto">
138
138
  <Button
139
139
  className="w-full"
140
140
  onClick={() => {
@@ -146,14 +146,14 @@ export const QuickstartPage = () => {
146
146
  </CardContent>
147
147
  </Card>
148
148
  ))}
149
- <Card className="hover:shadow-md transition-shadow rounded-md">
149
+ <Card className="hover:shadow-md transition-shadow rounded-md flex flex-col">
150
150
  <CardHeader className="p-4 pb-0">
151
151
  <CardTitle className="text-lg flex items-center justify-between">
152
152
  JLCPCB Component
153
153
  <TypeBadge type="package" className="ml-2" />
154
154
  </CardTitle>
155
155
  </CardHeader>
156
- <CardContent className="p-4">
156
+ <CardContent className="p-4 mt-auto">
157
157
  <Button
158
158
  className="w-full"
159
159
  onClick={() => setIsJLCPCBDialogOpen(true)}
@@ -162,14 +162,14 @@ export const QuickstartPage = () => {
162
162
  </Button>
163
163
  </CardContent>
164
164
  </Card>
165
- <Card className="hover:shadow-md transition-shadow rounded-md">
165
+ <Card className="hover:shadow-md transition-shadow rounded-md flex flex-col">
166
166
  <CardHeader className="p-4 pb-0">
167
167
  <CardTitle className="text-lg flex items-center justify-between">
168
168
  Circuit Json
169
169
  <TypeBadge type="module" className="ml-2" />
170
170
  </CardTitle>
171
171
  </CardHeader>
172
- <CardContent className="p-4">
172
+ <CardContent className="p-4 mt-auto">
173
173
  <Button
174
174
  className="w-full"
175
175
  onClick={() => setIsCircuitJsonImportDialogOpen(true)}
@@ -10,6 +10,14 @@ import { Button } from "@/components/ui/button"
10
10
  import { GitHubLogoIcon, StarIcon } from "@radix-ui/react-icons"
11
11
  import { Input } from "@/components/ui/input"
12
12
  import { useGlobalStore } from "@/hooks/use-global-store"
13
+ import { MoreVertical, Trash2 } from "lucide-react"
14
+ import { useConfirmDeleteSnippetDialog } from "@/components/dialogs/confirm-delete-snippet-dialog"
15
+ import {
16
+ DropdownMenu,
17
+ DropdownMenuContent,
18
+ DropdownMenuItem,
19
+ DropdownMenuTrigger,
20
+ } from "@/components/ui/dropdown-menu"
13
21
 
14
22
  export const UserProfilePage = () => {
15
23
  const { username } = useParams()
@@ -17,6 +25,9 @@ export const UserProfilePage = () => {
17
25
  const [searchQuery, setSearchQuery] = useState("")
18
26
  const session = useGlobalStore((s) => s.session)
19
27
  const isCurrentUserProfile = username === session?.github_username
28
+ const { Dialog: DeleteDialog, openDialog: openDeleteDialog } =
29
+ useConfirmDeleteSnippetDialog()
30
+ const [snippetToDelete, setSnippetToDelete] = useState<Snippet | null>(null)
20
31
 
21
32
  const { data: userSnippets, isLoading } = useQuery<Snippet[]>(
22
33
  ["userSnippets", username],
@@ -32,6 +43,12 @@ export const UserProfilePage = () => {
32
43
  snippet.unscoped_name.toLowerCase().includes(searchQuery.toLowerCase()),
33
44
  )
34
45
 
46
+ const handleDeleteClick = (e: React.MouseEvent, snippet: Snippet) => {
47
+ e.preventDefault() // Prevent navigation
48
+ setSnippetToDelete(snippet)
49
+ openDeleteDialog()
50
+ }
51
+
35
52
  return (
36
53
  <div>
37
54
  <Header />
@@ -76,9 +93,33 @@ export const UserProfilePage = () => {
76
93
  <h3 className="text-md font-semibold">
77
94
  {snippet.unscoped_name}
78
95
  </h3>
79
- <div className="flex items-center text-gray-600">
80
- <StarIcon className="w-4 h-4 mr-1" />
81
- <span>{snippet.star_count || 0}</span>
96
+ <div className="flex items-center gap-2">
97
+ <div className="flex items-center text-gray-600">
98
+ <StarIcon className="w-4 h-4 mr-1" />
99
+ <span>{snippet.star_count || 0}</span>
100
+ </div>
101
+ {isCurrentUserProfile && (
102
+ <DropdownMenu>
103
+ <DropdownMenuTrigger asChild>
104
+ <Button
105
+ variant="ghost"
106
+ size="icon"
107
+ className="h-8 w-8"
108
+ >
109
+ <MoreVertical className="h-4 w-4" />
110
+ </Button>
111
+ </DropdownMenuTrigger>
112
+ <DropdownMenuContent>
113
+ <DropdownMenuItem
114
+ className="text-xs text-red-600"
115
+ onClick={(e) => handleDeleteClick(e, snippet)}
116
+ >
117
+ <Trash2 className="mr-2 h-3 w-3" />
118
+ Delete Snippet
119
+ </DropdownMenuItem>
120
+ </DropdownMenuContent>
121
+ </DropdownMenu>
122
+ )}
82
123
  </div>
83
124
  </div>
84
125
  <p className="text-sm text-gray-500">
@@ -91,6 +132,12 @@ export const UserProfilePage = () => {
91
132
  </div>
92
133
  )}
93
134
  </div>
135
+ {snippetToDelete && (
136
+ <DeleteDialog
137
+ snippetId={snippetToDelete.snippet_id}
138
+ snippetName={snippetToDelete.unscoped_name}
139
+ />
140
+ )}
94
141
  <Footer />
95
142
  </div>
96
143
  )