Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JLCPCB Import #32

Merged
merged 3 commits into from
Oct 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified bun.lockb
Binary file not shown.
49 changes: 49 additions & 0 deletions fake-snippets-api/routes/api/snippets/generate_from_jlcpcb.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec"
import { z } from "zod"
import { snippetSchema } from "fake-snippets-api/lib/db/schema"
import { fetchEasyEDAComponent, convertRawEasyEdaToTs } from "easyeda"

export default withRouteSpec({
methods: ["POST"],
auth: "session",
jsonBody: z.object({
jlcpcb_part_number: z.string(),
}),
jsonResponse: z.object({
snippet: snippetSchema,
}),
})(async (req, ctx) => {
const { jlcpcb_part_number } = req.jsonBody

try {
// Fetch the EasyEDA component data
const rawEasyJson = await fetchEasyEDAComponent(jlcpcb_part_number)

// Convert to TypeScript React component
const tsxComponent = await convertRawEasyEdaToTs(rawEasyJson)

// Create a new snippet
const newSnippet = {
snippet_id: `snippet_${ctx.db.idCounter + 1}`,
name: `${ctx.auth.github_username}/${jlcpcb_part_number}`,
unscoped_name: jlcpcb_part_number,
owner_name: ctx.auth.github_username,
code: tsxComponent,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
snippet_type: "package",
description: `Generated from JLCPCB part number ${jlcpcb_part_number}`,
}

ctx.db.addSnippet(newSnippet as any)

return ctx.json({
snippet: newSnippet as any,
})
} catch (error: any) {
return ctx.error(500, {
error_code: "jlcpcb_generation_failed",
message: `Failed to generate snippet from JLCPCB part: ${error.message}`,
})
}
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { getTestServer } from "fake-snippets-api/tests/fixtures/get-test-server"
import { test, expect } from "bun:test"

test.skip("generate snippet from JLCPCB part number", async () => {
const { axios } = await getTestServer()

const response = await axios.post(
"/api/snippets/generate_from_jlcpcb",
{
jlcpcb_part_number: "C46749",
},
{
headers: {
Authorization: "Bearer 1234",
},
},
)

expect(response.status).toBe(200)
expect(response.data.snippet).toBeDefined()
expect(response.data.snippet.unscoped_name).toBe("C46749")
expect(response.data.snippet.owner_name).toBe("testuser")
expect(response.data.snippet.snippet_type).toBe("package")
expect(response.data.snippet.code).toContain("export const NE555P")
})
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"clsx": "^2.1.1",
"cmdk": "1.0.0",
"date-fns": "^4.1.0",
"easyeda": "^0.0.32",
"embla-carousel-react": "^8.3.0",
"fflate": "^0.8.2",
"immer": "^10.1.1",
Expand Down
9 changes: 8 additions & 1 deletion src/components/HeaderDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { ChevronDown } from "lucide-react"
import { ChevronDown, Zap } from "lucide-react"
import { Link } from "wouter"

export default function HeaderDropdown() {
const blankTemplates = [
Expand All @@ -31,6 +32,12 @@ export default function HeaderDropdown() {
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-fit">
<DropdownMenuItem asChild>
<Link href="/quickstart" className="flex items-center cursor-pointer">
<Zap className="mr-2 h-3 w-3" />
Quickstart Templates
</Link>
</DropdownMenuItem>
{blankTemplates.map((template, index) => (
<DropdownMenuItem key={index} asChild>
<a
Expand Down
122 changes: 122 additions & 0 deletions src/components/JLCPCBImportDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import React, { useState } from "react"
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogFooter,
} from "@/components/ui/dialog"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { useAxios } from "@/hooks/use-axios"
import { useToast } from "@/hooks/use-toast"
import { useLocation } from "wouter"

interface JLCPCBImportDialogProps {
open: boolean
onOpenChange: (open: boolean) => void
}

export function JLCPCBImportDialog({
open,
onOpenChange,
}: JLCPCBImportDialogProps) {
const [partNumber, setPartNumber] = useState("")
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const axios = useAxios()
const { toast } = useToast()
const [, navigate] = useLocation()

const handleImport = async () => {
if (!partNumber.startsWith("C")) {
toast({
title: "Invalid Part Number",
description: "JLCPCB part numbers should start with 'C'.",
variant: "destructive",
})
return
}

setIsLoading(true)
setError(null)
try {
const response = await axios
.post("/snippets/generate_from_jlcpcb", {
jlcpcb_part_number: partNumber,
})
.catch((e) => e)
const { snippet, error } = response.data
if (error) {
setError(error.message)
setIsLoading(false)
return
}
toast({
title: "Import Successful",
description: "JLCPCB component has been imported successfully.",
})
onOpenChange(false)
navigate(`/editor?snippet_id=${snippet.snippet_id}`)
} catch (error) {
console.error("Error importing JLCPCB component:", error)
toast({
title: "Import Failed",
description: "Failed to import the JLCPCB component. Please try again.",
variant: "destructive",
})
} finally {
setIsLoading(false)
}
}

return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent>
<DialogHeader>
<DialogTitle>Import from JLCPCB</DialogTitle>
</DialogHeader>
<div className="py-4">
<a
href="https://yaqwsx.github.io/jlcparts/#/"
target="_blank"
rel="noopener noreferrer"
className="text-blue-500 hover:underline opacity-80"
>
JLCPCB Part Search
</a>
<Input
className="mt-3"
placeholder="Enter JLCPCB part number (e.g., C46749)"
value={partNumber}
onChange={(e) => setPartNumber(e.target.value)}
/>
{error && <p className="bg-red-100 p-2 mt-2 pre-wrap">{error}</p>}
{error && (
<div className="flex justify-end mt-2">
<Button
variant="default"
onClick={() => {
const issueTitle = `[${partNumber}] Failed to import from JLCPCB`
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\`\`\``
const issueLabels = "snippets,good first issue"
const url = `https://github.com/tscircuit/easyeda-converter/issues/new?title=${encodeURIComponent(issueTitle)}&body=${encodeURIComponent(issueBody)}&labels=${encodeURIComponent(issueLabels)}`

// Open the issue in a new tab
window.open(url, "_blank")
}}
>
File Issue on Github (prefilled)
</Button>
</div>
)}
</div>
<DialogFooter>
<Button onClick={handleImport} disabled={isLoading}>
{isLoading ? "Importing..." : "Import"}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
}
1 change: 0 additions & 1 deletion src/hooks/use-run-tsx.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ export const useRunTsx = (
} else if (type === "model") {
const jscadGeoms: any[] = []
const { createJSCADRoot } = createJSCADRenderer(jscadPlanner as any)
console.log({ jscadPlanner })
const jscadRoot = createJSCADRoot(jscadGeoms)
jscadRoot.render(<UserElm />)
circuit.add(
Expand Down
20 changes: 20 additions & 0 deletions src/hooks/use-toast.ts → src/hooks/use-toast.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -186,3 +186,23 @@ function useToast() {
}

export { useToast, toast }

export function useNotImplementedToast() {
const { toast } = useToast()
return (feature: string) => {
toast({
title: "Not Implemented",
description: (
<div>
The {feature} feature is not implemented yet. Help us out!{" "}
<a
className="text-blue-500 hover:underline font-semibold"
href="https://github.com/tscircuit/snippets"
>
Check out our Github
</a>
</div>
),
})
}
}
38 changes: 35 additions & 3 deletions src/pages/quickstart.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from "react"
import React, { useState } from "react"
import { useQuery } from "react-query"
import { useAxios } from "@/hooks/use-axios"
import Header from "@/components/Header"
Expand All @@ -8,9 +8,13 @@ import { Link } from "wouter"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { TypeBadge } from "@/components/TypeBadge"
import { JLCPCBImportDialog } from "@/components/JLCPCBImportDialog"
import { useNotImplementedToast } from "@/hooks/use-toast"

export const QuickstartPage = () => {
const axios = useAxios()
const [isJLCPCBDialogOpen, setIsJLCPCBDialogOpen] = useState(false)
const toastNotImplemented = useNotImplementedToast()
const { data: mySnippets, isLoading } = useQuery<Snippet[]>(
"userSnippets",
async () => {
Expand Down Expand Up @@ -96,7 +100,7 @@ export const QuickstartPage = () => {
].map((template, index) => (
<Card
key={index}
className="hover:shadow-md transition-shadow rounded-md"
className="hover:shadow-md transition-shadow rounded-md opacity-50"
>
<CardHeader className="p-4 pb-0">
<CardTitle className="text-lg flex items-center justify-between">
Expand All @@ -105,13 +109,41 @@ export const QuickstartPage = () => {
</CardTitle>
</CardHeader>
<CardContent className="p-4">
<Button className="w-full">Import {template.name}</Button>
<Button
className="w-full"
onClick={() => {
toastNotImplemented("Kicad Imports")
}}
>
Import {template.name}
</Button>
</CardContent>
</Card>
))}
<Card className="hover:shadow-md transition-shadow rounded-md">
<CardHeader className="p-4 pb-0">
<CardTitle className="text-lg flex items-center justify-between">
JLCPCB Component
<TypeBadge type="package" className="ml-2" />
</CardTitle>
</CardHeader>
<CardContent className="p-4">
<Button
className="w-full"
onClick={() => setIsJLCPCBDialogOpen(true)}
>
Import JLCPCB Component
</Button>
</CardContent>
</Card>
</div>
</div>

<JLCPCBImportDialog
open={isJLCPCBDialogOpen}
onOpenChange={setIsJLCPCBDialogOpen}
/>

<div>
<h2 className="text-xl font-semibold mb-4 mt-12">
Start from a Template
Expand Down
Loading