@tscircuit/fake-snippets 0.0.66 → 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/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 +0 -11
- package/bun.lock +15 -17
- package/dist/bundle.js +32 -39
- 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/package.json +4 -3
- package/src/App.tsx +0 -7
- package/src/ContextProviders.tsx +2 -0
- package/src/components/DownloadButtonAndMenu.tsx +1 -4
- 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 +2 -2
- 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/ViewPackagePage/components/ShikiCodeViewer.tsx +5 -28
- package/src/components/ViewPackagePage/components/main-content-header.tsx +8 -8
- package/src/components/ViewPackagePage/components/mobile-sidebar.tsx +24 -14
- package/src/components/ViewPackagePage/components/package-header.tsx +6 -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-global-store.ts +1 -0
- package/src/hooks/use-shiki-highlighter.ts +13 -6
- 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/search.tsx +120 -19
- package/src/pages/trending.tsx +14 -58
- 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
|
@@ -4,29 +4,10 @@ import { z } from "zod"
|
|
|
4
4
|
export default withRouteSpec({
|
|
5
5
|
methods: ["POST"],
|
|
6
6
|
auth: "session",
|
|
7
|
-
jsonBody: z
|
|
8
|
-
.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
vendor_name: z.string(),
|
|
12
|
-
})
|
|
13
|
-
.superRefine((data, ctx) => {
|
|
14
|
-
if (data.circuit_json && data.package_release_id) {
|
|
15
|
-
ctx.addIssue({
|
|
16
|
-
code: z.ZodIssueCode.custom,
|
|
17
|
-
message:
|
|
18
|
-
"You must provide either circuit_json or package_release_id, but not both.",
|
|
19
|
-
})
|
|
20
|
-
}
|
|
21
|
-
if (!data.circuit_json && !data.package_release_id) {
|
|
22
|
-
ctx.addIssue({
|
|
23
|
-
code: z.ZodIssueCode.custom,
|
|
24
|
-
message:
|
|
25
|
-
"You must provide either circuit_json or package_release_id.",
|
|
26
|
-
})
|
|
27
|
-
}
|
|
28
|
-
}),
|
|
29
|
-
|
|
7
|
+
jsonBody: z.object({
|
|
8
|
+
package_release_id: z.string(),
|
|
9
|
+
vendor_name: z.literal("jlcpcb"),
|
|
10
|
+
}),
|
|
30
11
|
jsonResponse: z.object({
|
|
31
12
|
order_quote_id: z.string().optional(),
|
|
32
13
|
error: z
|
|
@@ -37,22 +18,34 @@ export default withRouteSpec({
|
|
|
37
18
|
.optional(),
|
|
38
19
|
}),
|
|
39
20
|
})(async (req, ctx) => {
|
|
40
|
-
const { package_release_id, vendor_name
|
|
21
|
+
const { package_release_id, vendor_name } = req.jsonBody
|
|
22
|
+
|
|
23
|
+
// check package release exists
|
|
24
|
+
const packageRelease = ctx.db.getPackageReleaseById(package_release_id)
|
|
25
|
+
if (!packageRelease) {
|
|
26
|
+
return ctx.json(
|
|
27
|
+
{
|
|
28
|
+
error: {
|
|
29
|
+
error_code: "package_release_not_found",
|
|
30
|
+
message: "Package release not found",
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
{ status: 404 },
|
|
34
|
+
)
|
|
35
|
+
}
|
|
41
36
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
{
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
message: "Package release not found",
|
|
51
|
-
},
|
|
37
|
+
const packageReleaseFiles =
|
|
38
|
+
ctx.db.getPackageFilesByReleaseId(package_release_id)
|
|
39
|
+
if (packageReleaseFiles.length === 0) {
|
|
40
|
+
return ctx.json(
|
|
41
|
+
{
|
|
42
|
+
error: {
|
|
43
|
+
error_code: "package_release_files_not_found",
|
|
44
|
+
message: "Package release files not found",
|
|
52
45
|
},
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
46
|
+
},
|
|
47
|
+
{ status: 404 },
|
|
48
|
+
)
|
|
56
49
|
}
|
|
57
50
|
|
|
58
51
|
const orderQuoteId = ctx.db.addOrderQuote({
|
|
@@ -9,8 +9,7 @@ export default withRouteSpec({
|
|
|
9
9
|
order_quote_id: z.string(),
|
|
10
10
|
}),
|
|
11
11
|
jsonResponse: z.object({
|
|
12
|
-
order_quote: orderQuoteSchema
|
|
13
|
-
error: z.string().optional(),
|
|
12
|
+
order_quote: orderQuoteSchema,
|
|
14
13
|
}),
|
|
15
14
|
})(async (req, ctx) => {
|
|
16
15
|
const { order_quote_id } = req.commonParams
|
|
@@ -18,12 +17,10 @@ export default withRouteSpec({
|
|
|
18
17
|
const orderQuote = ctx.db.getOrderQuoteById(order_quote_id)
|
|
19
18
|
|
|
20
19
|
if (!orderQuote) {
|
|
21
|
-
return ctx.
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
{ status: 404 },
|
|
26
|
-
)
|
|
20
|
+
return ctx.error(404, {
|
|
21
|
+
error_code: "order_quote_not_found",
|
|
22
|
+
message: "Order quote not found",
|
|
23
|
+
})
|
|
27
24
|
}
|
|
28
25
|
|
|
29
26
|
return ctx.json({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tscircuit/fake-snippets",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.67",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"preview": "vite preview",
|
|
25
25
|
"format": "biome format --write .",
|
|
26
26
|
"lint": "biome format .",
|
|
27
|
+
"build:vite:analyze": "VITE_BUNDLE_ANALYZE=true vite build",
|
|
27
28
|
"build:fake-api:tsup": "tsup-node ./fake-snippets-api/lib/index.ts --format esm --dts",
|
|
28
29
|
"build:fake-api:bundle": "winterspec bundle -o dist/bundle.js",
|
|
29
30
|
"build:fake-api": "bun run build:fake-api:tsup && bun run build:fake-api:bundle && bun run build:fake-api:schema",
|
|
@@ -70,7 +71,7 @@
|
|
|
70
71
|
"@radix-ui/react-toggle-group": "^1.1.0",
|
|
71
72
|
"@radix-ui/react-tooltip": "^1.1.2",
|
|
72
73
|
"@tscircuit/3d-viewer": "^0.0.142",
|
|
73
|
-
"@tscircuit/eval": "^0.0.
|
|
74
|
+
"@tscircuit/eval": "^0.0.198",
|
|
74
75
|
"@tscircuit/footprinter": "^0.0.124",
|
|
75
76
|
"@tscircuit/layout": "^0.0.29",
|
|
76
77
|
"@tscircuit/math-utils": "^0.0.10",
|
|
@@ -147,7 +148,7 @@
|
|
|
147
148
|
"@tailwindcss/typography": "^0.5.16",
|
|
148
149
|
"@tscircuit/core": "^0.0.384",
|
|
149
150
|
"@tscircuit/prompt-benchmarks": "^0.0.28",
|
|
150
|
-
"@tscircuit/runframe": "^0.0.
|
|
151
|
+
"@tscircuit/runframe": "^0.0.461",
|
|
151
152
|
"@types/babel__standalone": "^7.1.7",
|
|
152
153
|
"@types/bun": "^1.1.10",
|
|
153
154
|
"@types/country-list": "^2.1.4",
|
package/src/App.tsx
CHANGED
|
@@ -49,7 +49,6 @@ const lazyImport = (importFn: () => Promise<any>) =>
|
|
|
49
49
|
}
|
|
50
50
|
})
|
|
51
51
|
|
|
52
|
-
const AiPage = lazyImport(() => import("@/pages/ai"))
|
|
53
52
|
const AuthenticatePage = lazyImport(() => import("@/pages/authorize"))
|
|
54
53
|
const DashboardPage = lazyImport(() => import("@/pages/dashboard"))
|
|
55
54
|
const EditorPage = lazyImport(async () => {
|
|
@@ -68,7 +67,6 @@ const SearchPage = lazyImport(() => import("@/pages/search"))
|
|
|
68
67
|
const SettingsPage = lazyImport(() => import("@/pages/settings"))
|
|
69
68
|
const UserProfilePage = lazyImport(() => import("@/pages/user-profile"))
|
|
70
69
|
const ViewOrderPage = lazyImport(() => import("@/pages/view-order"))
|
|
71
|
-
const ViewSnippetPage = lazyImport(() => import("@/pages/view-snippet"))
|
|
72
70
|
const DevLoginPage = lazyImport(() => import("@/pages/dev-login"))
|
|
73
71
|
const BetaPage = lazyImport(() => import("@/pages/beta"))
|
|
74
72
|
const ViewPackagePage = lazyImport(() => import("@/pages/view-package"))
|
|
@@ -119,7 +117,6 @@ function App() {
|
|
|
119
117
|
<Route path="/legacy-editor" component={EditorPage} />
|
|
120
118
|
<Route path="/quickstart" component={QuickstartPage} />
|
|
121
119
|
<Route path="/dashboard" component={DashboardPage} />
|
|
122
|
-
<Route path="/ai" component={AiPage} />
|
|
123
120
|
<Route path="/latest" component={LatestPage} />
|
|
124
121
|
<Route path="/settings" component={SettingsPage} />
|
|
125
122
|
<Route path="/search" component={SearchPage} />
|
|
@@ -131,10 +128,6 @@ function App() {
|
|
|
131
128
|
<Route path="/dev-login" component={DevLoginPage} />
|
|
132
129
|
<Route path="/:username" component={UserProfilePage} />
|
|
133
130
|
<Route path="/:author/:packageName" component={ViewPackagePage} />
|
|
134
|
-
<Route
|
|
135
|
-
path="/snippets/:author/:snippetName"
|
|
136
|
-
component={ViewSnippetPage}
|
|
137
|
-
/>
|
|
138
131
|
<Route component={lazyImport(() => import("@/pages/404"))} />
|
|
139
132
|
</Switch>
|
|
140
133
|
</Suspense>
|
package/src/ContextProviders.tsx
CHANGED
|
@@ -3,6 +3,7 @@ import { HelmetProvider } from "react-helmet-async"
|
|
|
3
3
|
import { useEffect } from "react"
|
|
4
4
|
import { useGlobalStore } from "./hooks/use-global-store"
|
|
5
5
|
import { posthog } from "./lib/posthog"
|
|
6
|
+
import { Toaster } from "react-hot-toast"
|
|
6
7
|
|
|
7
8
|
const staffGithubUsernames = [
|
|
8
9
|
"imrishabh18",
|
|
@@ -61,6 +62,7 @@ export const ContextProviders = ({ children }: any) => {
|
|
|
61
62
|
<HelmetProvider>
|
|
62
63
|
<PostHogIdentifier />
|
|
63
64
|
{children}
|
|
65
|
+
<Toaster />
|
|
64
66
|
</HelmetProvider>
|
|
65
67
|
</QueryClientProvider>
|
|
66
68
|
)
|
|
@@ -83,10 +83,7 @@ export function DownloadButtonAndMenu({
|
|
|
83
83
|
className="text-xs"
|
|
84
84
|
onClick={async () => {
|
|
85
85
|
try {
|
|
86
|
-
await downloadGltf(
|
|
87
|
-
circuitJson,
|
|
88
|
-
snippetUnscopedName || "circuit",
|
|
89
|
-
)
|
|
86
|
+
await downloadGltf(snippetUnscopedName || "circuit")
|
|
90
87
|
} catch (error: any) {
|
|
91
88
|
toast({
|
|
92
89
|
title: "Error Downloading 3D Model",
|
|
@@ -57,10 +57,13 @@ export default function Footer() {
|
|
|
57
57
|
<h3 className="font-semibold uppercase">Explore</h3>
|
|
58
58
|
<footer className="flex flex-col space-y-2">
|
|
59
59
|
<PrefetchPageLink href="/latest" className="hover:underline">
|
|
60
|
-
Latest
|
|
60
|
+
Latest Packages
|
|
61
61
|
</PrefetchPageLink>
|
|
62
62
|
<PrefetchPageLink href="/trending" className="hover:underline">
|
|
63
|
-
Trending
|
|
63
|
+
Trending Packages
|
|
64
|
+
</PrefetchPageLink>
|
|
65
|
+
<PrefetchPageLink href="/search" className="hover:underline">
|
|
66
|
+
Search Packages
|
|
64
67
|
</PrefetchPageLink>
|
|
65
68
|
<a href="https://docs.tscircuit.com" className="hover:underline">
|
|
66
69
|
Docs
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React
|
|
1
|
+
import React from "react"
|
|
2
2
|
import { Button } from "@/components/ui/button"
|
|
3
3
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
|
|
4
4
|
import {
|
|
@@ -7,39 +7,19 @@ import {
|
|
|
7
7
|
DropdownMenuItem,
|
|
8
8
|
DropdownMenuTrigger,
|
|
9
9
|
} from "@/components/ui/dropdown-menu"
|
|
10
|
-
import { Link, useLocation, useRouter } from "wouter"
|
|
11
10
|
import { User } from "lucide-react"
|
|
12
|
-
import { useSnippetsBaseApiUrl } from "@/hooks/use-snippets-base-api-url"
|
|
13
11
|
import { useGlobalStore } from "@/hooks/use-global-store"
|
|
14
12
|
import { useAccountBalance } from "@/hooks/use-account-balance"
|
|
15
|
-
import { useIsUsingFakeApi } from "@/hooks/use-is-using-fake-api"
|
|
16
13
|
import { useSignIn } from "@/hooks/use-sign-in"
|
|
17
14
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
export const HeaderLogin: React.FC<HeaderLoginProps> = () => {
|
|
21
|
-
const [, setLocation] = useLocation()
|
|
15
|
+
export const HeaderLogin = () => {
|
|
22
16
|
const session = useGlobalStore((s) => s.session)
|
|
23
17
|
const isLoggedIn = Boolean(session)
|
|
24
18
|
const setSession = useGlobalStore((s) => s.setSession)
|
|
25
|
-
const snippetsBaseApiUrl = useSnippetsBaseApiUrl()
|
|
26
|
-
const isUsingFakeApi = useIsUsingFakeApi()
|
|
27
19
|
const signIn = useSignIn()
|
|
28
20
|
const { data: accountBalance } = useAccountBalance()
|
|
29
21
|
|
|
30
22
|
if (!isLoggedIn) {
|
|
31
|
-
const handleLogin = () => {
|
|
32
|
-
if (isUsingFakeApi) {
|
|
33
|
-
setSession({
|
|
34
|
-
account_id: "account-1234",
|
|
35
|
-
github_username: "testuser",
|
|
36
|
-
token: "1234",
|
|
37
|
-
session_id: "session-1234",
|
|
38
|
-
})
|
|
39
|
-
} else {
|
|
40
|
-
signIn()
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
23
|
return (
|
|
44
24
|
<div className="flex items-center md:space-x-2 justify-end">
|
|
45
25
|
<Button onClick={() => signIn()} variant="ghost">
|
|
@@ -51,44 +31,47 @@ export const HeaderLogin: React.FC<HeaderLoginProps> = () => {
|
|
|
51
31
|
}
|
|
52
32
|
|
|
53
33
|
return (
|
|
54
|
-
<
|
|
55
|
-
<
|
|
56
|
-
<
|
|
57
|
-
<
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
<
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
<
|
|
68
|
-
<
|
|
34
|
+
<DropdownMenu>
|
|
35
|
+
<DropdownMenuTrigger asChild>
|
|
36
|
+
<Avatar className="w-8 h-8 login-avatar">
|
|
37
|
+
<AvatarImage
|
|
38
|
+
src={`https://github.com/${session?.github_username}.png`}
|
|
39
|
+
alt={`${session?.github_username}'s profile picture`}
|
|
40
|
+
/>
|
|
41
|
+
<AvatarFallback aria-label="User avatar fallback">
|
|
42
|
+
<User size={16} aria-hidden="true" />
|
|
43
|
+
</AvatarFallback>
|
|
44
|
+
</Avatar>
|
|
45
|
+
</DropdownMenuTrigger>
|
|
46
|
+
<DropdownMenuContent>
|
|
47
|
+
<DropdownMenuItem asChild className="text-gray-500 text-xs" disabled>
|
|
48
|
+
<div>
|
|
69
49
|
AI Usage $
|
|
70
50
|
{accountBalance?.monthly_ai_budget_used_usd.toFixed(2) ?? "0.00"} /
|
|
71
51
|
$5.00
|
|
72
|
-
</
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
>
|
|
52
|
+
</div>
|
|
53
|
+
</DropdownMenuItem>
|
|
54
|
+
<DropdownMenuItem asChild>
|
|
55
|
+
<a href={`/${session?.github_username}`} className="cursor-pointer">
|
|
76
56
|
My Profile
|
|
77
|
-
</
|
|
78
|
-
|
|
57
|
+
</a>
|
|
58
|
+
</DropdownMenuItem>
|
|
59
|
+
<DropdownMenuItem asChild>
|
|
60
|
+
<a href="/dashboard" className="cursor-pointer">
|
|
79
61
|
Dashboard
|
|
80
|
-
</
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
<DropdownMenuItem onClick={() => setLocation("/settings")}>
|
|
62
|
+
</a>
|
|
63
|
+
</DropdownMenuItem>
|
|
64
|
+
<DropdownMenuItem asChild>
|
|
65
|
+
<a href="/settings" className="cursor-pointer">
|
|
85
66
|
Settings
|
|
86
|
-
</
|
|
87
|
-
|
|
67
|
+
</a>
|
|
68
|
+
</DropdownMenuItem>
|
|
69
|
+
<DropdownMenuItem asChild onClick={() => setSession(null)}>
|
|
70
|
+
<a href="/sign-out" className="cursor-pointer">
|
|
88
71
|
Sign out
|
|
89
|
-
</
|
|
90
|
-
</
|
|
91
|
-
</
|
|
92
|
-
</
|
|
72
|
+
</a>
|
|
73
|
+
</DropdownMenuItem>
|
|
74
|
+
</DropdownMenuContent>
|
|
75
|
+
</DropdownMenu>
|
|
93
76
|
)
|
|
94
77
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { useState } from "react"
|
|
2
|
+
|
|
3
|
+
interface ImageWithFallbackProps
|
|
4
|
+
extends React.ImgHTMLAttributes<HTMLImageElement> {
|
|
5
|
+
src: string
|
|
6
|
+
alt: string
|
|
7
|
+
className?: string
|
|
8
|
+
fallbackSrc?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function ImageWithFallback({
|
|
12
|
+
src,
|
|
13
|
+
alt,
|
|
14
|
+
className = "",
|
|
15
|
+
fallbackSrc = "/assets/fallback-image.svg",
|
|
16
|
+
...props
|
|
17
|
+
}: ImageWithFallbackProps) {
|
|
18
|
+
const [loading, setLoading] = useState(true)
|
|
19
|
+
const [currentSrc, setCurrentSrc] = useState(src)
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<img
|
|
23
|
+
src={currentSrc}
|
|
24
|
+
alt={alt}
|
|
25
|
+
className={`object-contain h-full w-full ${
|
|
26
|
+
loading ? "animate-pulse bg-gray-200" : ""
|
|
27
|
+
} ${className}`}
|
|
28
|
+
onLoad={() => setLoading(false)}
|
|
29
|
+
onError={() => {
|
|
30
|
+
console.error("PCB image failed to load:", src)
|
|
31
|
+
setCurrentSrc(fallbackSrc)
|
|
32
|
+
setLoading(false)
|
|
33
|
+
}}
|
|
34
|
+
{...props}
|
|
35
|
+
/>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
@@ -27,6 +27,9 @@ export function JLCPCBImportDialog({
|
|
|
27
27
|
const [partNumber, setPartNumber] = useState("")
|
|
28
28
|
const [isLoading, setIsLoading] = useState(false)
|
|
29
29
|
const [error, setError] = useState<string | null>(null)
|
|
30
|
+
const [alreadyImportedPackageId, setAlreadyImportedPackageId] = useState<
|
|
31
|
+
string | null
|
|
32
|
+
>(null)
|
|
30
33
|
const axios = useAxios()
|
|
31
34
|
const { toast } = useToast()
|
|
32
35
|
const [, navigate] = useLocation()
|
|
@@ -45,29 +48,20 @@ export function JLCPCBImportDialog({
|
|
|
45
48
|
|
|
46
49
|
setIsLoading(true)
|
|
47
50
|
setError(null)
|
|
51
|
+
setAlreadyImportedPackageId(null)
|
|
52
|
+
|
|
48
53
|
try {
|
|
49
|
-
|
|
50
|
-
const existingSnippetRes = await axios.get(
|
|
54
|
+
const existingPackageRes = await axios.get(
|
|
51
55
|
`/snippets/get?owner_name=${session?.github_username}&unscoped_name=${partNumber}`,
|
|
52
56
|
{
|
|
53
57
|
validateStatus: (status) => true,
|
|
54
58
|
},
|
|
55
59
|
)
|
|
56
60
|
|
|
57
|
-
if (
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
<div>
|
|
62
|
-
<PrefetchPageLink
|
|
63
|
-
className="text-blue-500 hover:underline"
|
|
64
|
-
href={`/editor?package_id=${existingSnippetRes.data.snippet.snippet_id}`}
|
|
65
|
-
>
|
|
66
|
-
View {partNumber}
|
|
67
|
-
</PrefetchPageLink>
|
|
68
|
-
</div>
|
|
69
|
-
),
|
|
70
|
-
})
|
|
61
|
+
if (existingPackageRes.status !== 404) {
|
|
62
|
+
const packageId = existingPackageRes.data.snippet.snippet_id
|
|
63
|
+
setAlreadyImportedPackageId(packageId)
|
|
64
|
+
setIsLoading(false)
|
|
71
65
|
return
|
|
72
66
|
}
|
|
73
67
|
|
|
@@ -76,16 +70,20 @@ export function JLCPCBImportDialog({
|
|
|
76
70
|
jlcpcb_part_number: partNumber,
|
|
77
71
|
})
|
|
78
72
|
.catch((e) => e)
|
|
73
|
+
|
|
79
74
|
const { snippet, error } = response.data
|
|
75
|
+
|
|
80
76
|
if (error) {
|
|
81
77
|
setError(error.message)
|
|
82
78
|
setIsLoading(false)
|
|
83
79
|
return
|
|
84
80
|
}
|
|
81
|
+
|
|
85
82
|
toast({
|
|
86
83
|
title: "Import Successful",
|
|
87
84
|
description: "JLCPCB component has been imported successfully.",
|
|
88
85
|
})
|
|
86
|
+
|
|
89
87
|
onOpenChange(false)
|
|
90
88
|
navigate(`/editor?package_id=${snippet.snippet_id}`)
|
|
91
89
|
} catch (error) {
|
|
@@ -121,10 +119,17 @@ export function JLCPCBImportDialog({
|
|
|
121
119
|
className="mt-3"
|
|
122
120
|
placeholder="Enter JLCPCB part number (e.g., C46749)"
|
|
123
121
|
value={partNumber}
|
|
124
|
-
onChange={(e) =>
|
|
122
|
+
onChange={(e) => {
|
|
123
|
+
setPartNumber(e.target.value)
|
|
124
|
+
setError(null)
|
|
125
|
+
setAlreadyImportedPackageId(null)
|
|
126
|
+
}}
|
|
125
127
|
/>
|
|
126
|
-
{error &&
|
|
127
|
-
|
|
128
|
+
{error && !alreadyImportedPackageId && (
|
|
129
|
+
<p className="bg-red-100 p-2 mt-2 pre-wrap">{error}</p>
|
|
130
|
+
)}
|
|
131
|
+
|
|
132
|
+
{error && !alreadyImportedPackageId && (
|
|
128
133
|
<div className="flex justify-end mt-2">
|
|
129
134
|
<Button
|
|
130
135
|
variant="default"
|
|
@@ -132,21 +137,35 @@ export function JLCPCBImportDialog({
|
|
|
132
137
|
const issueTitle = `[${partNumber}] Failed to import from JLCPCB`
|
|
133
138
|
const issueBody = `I tried to import the part number ${partNumber} from JLCPCB, but it failed. Here's the error I got:\n\`\`\`\n${error}\n\`\`\`\n\nCould be an issue in \`fetchEasyEDAComponent\` or \`convertRawEasyEdaToTs\``
|
|
134
139
|
const issueLabels = "snippets,good first issue"
|
|
135
|
-
const url = `https://github.com/tscircuit/easyeda-converter/issues/new?title=${encodeURIComponent(
|
|
136
|
-
|
|
137
|
-
|
|
140
|
+
const url = `https://github.com/tscircuit/easyeda-converter/issues/new?title=${encodeURIComponent(
|
|
141
|
+
issueTitle,
|
|
142
|
+
)}&body=${encodeURIComponent(
|
|
143
|
+
issueBody,
|
|
144
|
+
)}&labels=${encodeURIComponent(issueLabels)}`
|
|
138
145
|
window.open(url, "_blank")
|
|
139
146
|
}}
|
|
140
147
|
>
|
|
141
|
-
File Issue on
|
|
148
|
+
File Issue on GitHub (prefilled)
|
|
142
149
|
</Button>
|
|
143
150
|
</div>
|
|
144
151
|
)}
|
|
152
|
+
|
|
153
|
+
{alreadyImportedPackageId && (
|
|
154
|
+
<p className="p-2 mt-2 pre-wrap text-md text-green-600">
|
|
155
|
+
This part number has already been imported to your profile.{" "}
|
|
156
|
+
<PrefetchPageLink
|
|
157
|
+
className="text-blue-500 hover:underline"
|
|
158
|
+
href={`/${session?.github_username}/${partNumber}`}
|
|
159
|
+
>
|
|
160
|
+
View it here
|
|
161
|
+
</PrefetchPageLink>
|
|
162
|
+
</p>
|
|
163
|
+
)}
|
|
145
164
|
</div>
|
|
146
165
|
<DialogFooter>
|
|
147
166
|
<Button onClick={handleImport} disabled={isLoading || !isLoggedIn}>
|
|
148
167
|
{!isLoggedIn
|
|
149
|
-
? "
|
|
168
|
+
? "You must be logged in to import from JLCPCB"
|
|
150
169
|
: isLoading
|
|
151
170
|
? "Importing..."
|
|
152
171
|
: "Import"}
|
|
@@ -10,10 +10,10 @@ import {
|
|
|
10
10
|
DropdownMenuItem,
|
|
11
11
|
DropdownMenuTrigger,
|
|
12
12
|
} from "@/components/ui/dropdown-menu"
|
|
13
|
-
import { OptimizedImage } from "./OptimizedImage"
|
|
14
13
|
import { SnippetType, SnippetTypeIcon } from "./SnippetTypeIcon"
|
|
15
14
|
import { timeAgo } from "@/lib/utils/timeAgo"
|
|
16
15
|
import { useGetFsMapHashForPackage } from "@/hooks/use-get-fsmap-hash-for-package"
|
|
16
|
+
import { ImageWithFallback } from "./ImageWithFallback"
|
|
17
17
|
|
|
18
18
|
export interface PackageCardProps {
|
|
19
19
|
/** The package data to display */
|
|
@@ -69,7 +69,7 @@ export const PackageCard: React.FC<PackageCardProps> = ({
|
|
|
69
69
|
<div
|
|
70
70
|
className={`${imageSize} flex-shrink-0 rounded-md overflow-hidden`}
|
|
71
71
|
>
|
|
72
|
-
<
|
|
72
|
+
<ImageWithFallback
|
|
73
73
|
src={`${baseUrl}/packages/images/${pkg.owner_github_username}/${pkg.unscoped_name}/pcb.svg?${new URLSearchParams(
|
|
74
74
|
{
|
|
75
75
|
fs_sha: fsMapHash ?? "",
|
|
@@ -1,35 +1,27 @@
|
|
|
1
1
|
import { Link } from "wouter"
|
|
2
2
|
import { Star } from "lucide-react"
|
|
3
|
+
import { Package } from "fake-snippets-api/lib/db/schema"
|
|
3
4
|
|
|
4
|
-
export const
|
|
5
|
-
snippet,
|
|
6
|
-
}: {
|
|
7
|
-
snippet: {
|
|
8
|
-
owner_name: string
|
|
9
|
-
name: string
|
|
10
|
-
unscoped_name: string
|
|
11
|
-
star_count?: number
|
|
12
|
-
}
|
|
13
|
-
}) => {
|
|
5
|
+
export const PackageLink = (pkg: Package) => {
|
|
14
6
|
return (
|
|
15
7
|
<>
|
|
16
8
|
<Link
|
|
17
9
|
className="text-blue-500 font-semibold hover:underline"
|
|
18
|
-
href={`/${
|
|
10
|
+
href={`/${pkg.owner_github_username}`}
|
|
19
11
|
>
|
|
20
|
-
{
|
|
12
|
+
{pkg.owner_github_username}
|
|
21
13
|
</Link>
|
|
22
14
|
<span className="px-0.5 text-gray-500">/</span>
|
|
23
15
|
<Link
|
|
24
16
|
className="text-blue-500 font-semibold hover:underline"
|
|
25
|
-
href={`/${
|
|
17
|
+
href={`/${pkg.unscoped_name}`}
|
|
26
18
|
>
|
|
27
|
-
{
|
|
19
|
+
{pkg.unscoped_name}
|
|
28
20
|
</Link>
|
|
29
|
-
{
|
|
21
|
+
{pkg.star_count !== undefined && (
|
|
30
22
|
<span className="ml-2 text-gray-500 text-xs flex items-center">
|
|
31
23
|
<Star className="w-3 h-3 mr-1" />
|
|
32
|
-
{
|
|
24
|
+
{pkg.star_count}
|
|
33
25
|
</span>
|
|
34
26
|
)}
|
|
35
27
|
</>
|