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

Add datasheet URL support for snippets #380

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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
42 changes: 42 additions & 0 deletions bun-tests/fake-snippets-api/routes/snippets/update.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,45 @@ test("update snippet with null compiled_js", async () => {
const updatedSnippet = db.snippets[0]
expect(updatedSnippet.compiled_js).toBeNull()
})

test("update snippet datasheet URL", async () => {
const { axios, db } = await getTestServer()

// Add a test snippet
const snippet = {
unscoped_name: "TestSnippet",
owner_name: "testuser",
code: "Original Content",
created_at: "2023-01-01T00:00:00Z",
updated_at: "2023-01-01T00:00:00Z",
name: "testuser/TestSnippet",
snippet_type: "package",
description: "Original Description",
datasheet_url: null
}
db.addSnippet(snippet as any)

const addedSnippet = db.snippets[0]

// Update the datasheet URL
const newUrl = "https://example.com/datasheet.pdf"
const response = await axios.post(
"/api/snippets/update",
{
snippet_id: addedSnippet.snippet_id,
datasheet_url: newUrl,
},
{
headers: {
Authorization: "Bearer 1234",
},
},
)

expect(response.status).toBe(200)
expect(response.data.snippet.datasheet_url).toBe(newUrl)

// Verify the snippet was updated in the database
const updatedSnippet = db.snippets[0]
expect(updatedSnippet.datasheet_url).toBe(newUrl)
})
1 change: 1 addition & 0 deletions fake-snippets-api/lib/db/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const snippetSchema = z.object({
description: z.string().optional(),
version: z.string().default("0.0.1"),
star_count: z.number().default(0),
datasheet_url: z.string().url().optional().nullable(),
})
export type Snippet = z.infer<typeof snippetSchema>

Expand Down
3 changes: 3 additions & 0 deletions fake-snippets-api/routes/api/snippets/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export default withRouteSpec({
circuit_json: z.array(z.record(z.any())).optional().nullable(),
manual_edits_json_content: z.string().optional().nullable(),
snippet_type: z.enum(["board", "package", "model", "footprint"]).optional(),
datasheet_url: z.string().url().optional().nullable(),
}),
jsonResponse: z.object({
ok: z.boolean(),
Expand All @@ -31,6 +32,7 @@ export default withRouteSpec({
circuit_json,
snippet_type,
manual_edits_json_content,
datasheet_url,
} = req.jsonBody

const snippetIndex = ctx.db.snippets.findIndex(
Expand Down Expand Up @@ -69,6 +71,7 @@ export default withRouteSpec({
circuit_json:
circuit_json !== undefined ? circuit_json : snippet.circuit_json,
snippet_type: snippet_type ?? snippet.snippet_type,
datasheet_url: datasheet_url !== undefined ? datasheet_url : snippet.datasheet_url,
updated_at: new Date().toISOString(),
})

Expand Down
55 changes: 49 additions & 6 deletions src/components/ViewSnippetSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard"
import { useCurrentSnippet } from "@/hooks/use-current-snippet"
import { useToast } from "@/hooks/use-toast"
import { cn } from "@/lib/utils"
import { AtSign, Bot, Clock, Code, File, GitFork, Package } from "lucide-react"
import { AtSign, Bot, Clock, Code, File, GitFork, Link2, Package } from "lucide-react"
import { Link } from "wouter"
import { useFilesDialog } from "./dialogs/files-dialog"
import { useEditDatasheetUrlDialog } from "./dialogs/edit-datasheet-url-dialog"
import { Button } from "./ui/button"
import { useState } from "react"

export default function ViewSnippetSidebar({
className,
Expand All @@ -14,7 +17,9 @@ export default function ViewSnippetSidebar({
const { snippet } = useCurrentSnippet()
const { toast } = useToast()
const { Dialog: FilesDialog, openDialog: openFilesDialog } = useFilesDialog()
const { Dialog: DatasheetUrlDialog, openDialog: openDatasheetUrlDialog } = useEditDatasheetUrlDialog()
const { copyToClipboard } = useCopyToClipboard()
const [datasheetUrlDialogProps, setDatasheetUrlDialogProps] = useState<{ snippetId: string; currentUrl: string | null } | null>(null)

return (
<div
Expand All @@ -38,10 +43,48 @@ export default function ViewSnippetSidebar({
badge: "AI",
href: `/ai?snippet_id=${snippet?.snippet_id}`,
},
// {
// icon: <GitHubLogoIcon className="w-5 h-5" />,
// label: "Github",
// },
{
icon: <Link2 className="w-5 h-5" />,
label: "Datasheet",
content: snippet?.datasheet_url ? (
<div className="flex items-center gap-2 mt-2">
<a
href={snippet.datasheet_url}
target="_blank"
rel="noopener noreferrer"
className="text-blue-500 hover:underline text-sm truncate max-w-[150px]"
>
View Datasheet
</a>
<Button
variant="ghost"
size="sm"
onClick={() => {
if (snippet) {
setDatasheetUrlDialogProps({ snippetId: snippet.snippet_id, currentUrl: snippet.datasheet_url ?? null })
openDatasheetUrlDialog()
}
}}
>
Edit
</Button>
</div>
) : (
<Button
variant="ghost"
size="sm"
onClick={() => {
if (snippet) {
setDatasheetUrlDialogProps({ snippetId: snippet.snippet_id, currentUrl: null })
openDatasheetUrlDialog()
}
}}
>
Add Datasheet URL
</Button>
),
badge: snippet?.datasheet_url ? "URL" : undefined,
},
{
icon: <GitFork className="w-5 h-5" />,
label: "Forks",
Expand Down Expand Up @@ -71,7 +114,6 @@ export default function ViewSnippetSidebar({
}
},
},
// { icon: <Settings className="w-5 h-5" />, label: "Settings" },
].map((item, index) => (
<li key={index}>
<Link
Expand Down Expand Up @@ -157,6 +199,7 @@ export default function ViewSnippetSidebar({
</div>
</div>
{snippet && <FilesDialog snippetId={snippet.snippet_id} />}
{snippet && datasheetUrlDialogProps && <DatasheetUrlDialog {...datasheetUrlDialogProps} />}
</div>
)
}
82 changes: 82 additions & 0 deletions src/components/dialogs/edit-datasheet-url-dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import React from "react"
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "../ui/dialog"
import { Input } from "../ui/input"
import { Button } from "../ui/button"
import { useState } from "react"
import { useMutation, useQueryClient } from "react-query"
import { createUseDialog } from "./create-use-dialog"
import { useAxios } from "@/hooks/use-axios"
import { useToast } from "@/hooks/use-toast"

type EditDatasheetUrlDialogProps = {
open: boolean
onOpenChange: (open: boolean) => void
snippetId: string
currentUrl: string | null
}

export const EditDatasheetUrlDialog = ({
open,
onOpenChange,
snippetId,
currentUrl,
}: EditDatasheetUrlDialogProps) => {
const [url, setUrl] = useState(currentUrl ?? "")
const axios = useAxios()
const { toast } = useToast()
const qc = useQueryClient()

const updateDatasheetUrlMutation = useMutation({
mutationFn: async () => {
const response = await axios.post("/snippets/update", {
snippet_id: snippetId,
datasheet_url: url || null,
})
if (response.status !== 200) {
throw new Error("Failed to update datasheet URL")
}
return response.data
},
onSuccess: () => {
onOpenChange(false)
toast({
title: "Datasheet URL updated",
description: url ? "Successfully updated datasheet URL" : "Removed datasheet URL",
})
qc.invalidateQueries({ queryKey: ["snippets", snippetId] })
},
onError: (error) => {
console.error("Error updating datasheet URL:", error)
toast({
title: "Error",
description: "Failed to update the datasheet URL. Please try again.",
variant: "destructive",
})
},
})

return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent>
<DialogHeader>
<DialogTitle>Edit Datasheet URL</DialogTitle>
</DialogHeader>
<Input
value={url}
onChange={(e) => setUrl(e.target.value)}
placeholder="Enter datasheet URL"
type="url"
disabled={updateDatasheetUrlMutation.isLoading}
/>
<Button
disabled={updateDatasheetUrlMutation.isLoading}
onClick={() => updateDatasheetUrlMutation.mutate()}
>
{updateDatasheetUrlMutation.isLoading ? "Updating..." : "Update"}
</Button>
</DialogContent>
</Dialog>
)
}

export const useEditDatasheetUrlDialog = createUseDialog(EditDatasheetUrlDialog)
Loading