@tscircuit/fake-snippets 0.0.96 → 0.0.98

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.
@@ -1,20 +1,26 @@
1
1
  import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec"
2
- import { packageReleaseSchema } from "fake-snippets-api/lib/db/schema"
3
2
  import { z } from "zod"
3
+ import Debug from "debug"
4
+
5
+ const debug = Debug("fake-snippets-api:rebuild-package")
4
6
 
5
7
  export default withRouteSpec({
6
8
  methods: ["POST"],
7
9
  auth: "session",
8
10
  jsonBody: z.object({
9
11
  package_release_id: z.string(),
12
+ rebuild_transpilation: z.boolean().default(true),
13
+ rebuild_circuit_json: z.boolean().default(true),
10
14
  }),
11
15
  jsonResponse: z.object({
12
16
  ok: z.boolean(),
13
- package_release: packageReleaseSchema,
14
17
  }),
15
18
  })(async (req, ctx) => {
16
- const { package_release_id } = req.jsonBody
19
+ const { package_release_id, rebuild_transpilation, rebuild_circuit_json } =
20
+ req.jsonBody
17
21
 
22
+ debug("jsonBody", req.jsonBody)
23
+ // Get the package release info for logging
18
24
  const release = ctx.db.getPackageReleaseById(package_release_id)
19
25
 
20
26
  if (!release) {
@@ -24,9 +30,37 @@ export default withRouteSpec({
24
30
  })
25
31
  }
26
32
 
27
- // In a real API this would trigger a rebuild. Here we simply return the release.
33
+ const packageInfo = ctx.db.getPackageById(release.package_id)
34
+
35
+ if (!packageInfo) {
36
+ return ctx.error(404, {
37
+ error_code: "package_not_found",
38
+ message: "Package not found",
39
+ })
40
+ }
41
+
42
+ if (packageInfo.owner_org_id !== ctx.auth.personal_org_id) {
43
+ return ctx.error(403, {
44
+ error_code: "forbidden",
45
+ message: "You do not have permission to rebuild this package release",
46
+ })
47
+ }
48
+
49
+ debug(
50
+ `Rebuilding package release ${package_release_id} for ${packageInfo.name}@${release.version}`,
51
+ )
52
+
53
+ // Trigger transpilation if requested
54
+ if (rebuild_transpilation) {
55
+ debug("Resetting transpilation state")
56
+ }
57
+
58
+ // Set circuit JSON build state to pending for worker pickup if requested
59
+ if (rebuild_circuit_json) {
60
+ debug("Resetting circuit JSON build state")
61
+ }
62
+
28
63
  return ctx.json({
29
64
  ok: true,
30
- package_release: release,
31
65
  })
32
66
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tscircuit/fake-snippets",
3
- "version": "0.0.96",
3
+ "version": "0.0.98",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -109,7 +109,7 @@
109
109
  "circuit-json-to-pnp-csv": "^0.0.7",
110
110
  "circuit-json-to-readable-netlist": "^0.0.13",
111
111
  "circuit-json-to-tscircuit": "^0.0.4",
112
- "circuit-to-svg": "^0.0.163",
112
+ "circuit-to-svg": "^0.0.167",
113
113
  "class-variance-authority": "^0.7.1",
114
114
  "clsx": "^2.1.1",
115
115
  "cmdk": "^1.0.4",
@@ -162,7 +162,7 @@
162
162
  "redaxios": "^0.5.1",
163
163
  "remark-gfm": "^4.0.1",
164
164
  "rollup-plugin-visualizer": "^5.12.0",
165
- "schematic-symbols": "^0.0.155",
165
+ "schematic-symbols": "^0.0.171",
166
166
  "sharp": "^0.33.5",
167
167
  "shiki": "^3.2.1",
168
168
  "sitemap": "^8.0.0",
@@ -174,7 +174,7 @@
174
174
  "terser": "^5.27.0",
175
175
  "three": "^0.177.0",
176
176
  "three-stdlib": "^2.36.0",
177
- "tscircuit": "^0.0.522",
177
+ "tscircuit": "^0.0.538",
178
178
  "tsup": "^8.5.0",
179
179
  "typescript": "^5.6.3",
180
180
  "use-async-memo": "^1.2.5",
package/src/App.tsx CHANGED
@@ -122,6 +122,7 @@ class ErrorBoundary extends React.Component<
122
122
  cleanup = () => {
123
123
  if (this.visibilityHandler) {
124
124
  document.removeEventListener("visibilitychange", this.visibilityHandler)
125
+ window.removeEventListener("focus", this.visibilityHandler)
125
126
  this.visibilityHandler = undefined
126
127
  }
127
128
  if (this.reloadTimeout) {
@@ -144,12 +145,20 @@ class ErrorBoundary extends React.Component<
144
145
  this.cleanup() // Clean up any existing handlers
145
146
 
146
147
  this.visibilityHandler = () => {
147
- if (!document.hidden && this.state.hasError && !this.state.reloading) {
148
+ if (
149
+ document.visibilityState === "visible" &&
150
+ this.state.hasError &&
151
+ !this.state.reloading
152
+ ) {
148
153
  this.performReload()
149
154
  }
150
155
  }
151
156
 
152
157
  document.addEventListener("visibilitychange", this.visibilityHandler)
158
+ window.addEventListener("focus", this.visibilityHandler)
159
+
160
+ // In case the tab is already visible when the error occurs
161
+ this.visibilityHandler()
153
162
  }
154
163
 
155
164
  render() {
@@ -49,11 +49,11 @@ export function DownloadButtonAndMenu({
49
49
  <Button
50
50
  disabled
51
51
  size="sm"
52
- className="h-9 bg-muted text-muted-foreground border border-input cursor-not-allowed"
52
+ className="shadow-none bg-muted text-muted-foreground border border-input cursor-not-allowed"
53
53
  >
54
- <Download className="h-4 w-4 mr-1.5" />
54
+ <Download className="h-4 w-4 mr-2" />
55
55
  Download
56
- <ChevronDown className="h-4 w-4 ml-0.5" />
56
+ <ChevronDown className="h-4 w-4 ml-1" />
57
57
  </Button>
58
58
  </div>
59
59
  )
@@ -65,11 +65,11 @@ export function DownloadButtonAndMenu({
65
65
  <DropdownMenuTrigger asChild>
66
66
  <Button
67
67
  size="sm"
68
- className="bg-transparent shadow-inner hover:bg-gray-100 text-gray-700 outline-none"
68
+ className="bg-white shadow-none text-gray-900 hover:bg-gray-100 border border-gray-300 px-1 pl-2"
69
69
  >
70
- <Download className="h-4 w-4 mr-1.5" />
70
+ <Download className="w-4 h-4 mr-2" />
71
71
  Download
72
- <ChevronDown className="h-4 w-4 ml-0.5" />
72
+ <ChevronDown className="w-4 h-4 ml-1" />
73
73
  </Button>
74
74
  </DropdownMenuTrigger>
75
75
  <DropdownMenuContent className="!z-[101]">
@@ -20,7 +20,7 @@ import {
20
20
  import { SnippetType, SnippetTypeIcon } from "./SnippetTypeIcon"
21
21
  import { timeAgo } from "@/lib/utils/timeAgo"
22
22
  import { ImageWithFallback } from "./ImageWithFallback"
23
- import { useToast } from "@/hooks/use-toast"
23
+ import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard"
24
24
 
25
25
  export interface PackageCardProps {
26
26
  /** The package data to display */
@@ -57,7 +57,6 @@ export const PackageCard: React.FC<PackageCardProps> = ({
57
57
  withLink = true,
58
58
  renderActions,
59
59
  }) => {
60
- const { toast } = useToast()
61
60
  const handleDeleteClick = (e: React.MouseEvent) => {
62
61
  e.preventDefault() // Prevent navigation
63
62
  if (onDeleteClick) {
@@ -65,39 +64,12 @@ export const PackageCard: React.FC<PackageCardProps> = ({
65
64
  }
66
65
  }
67
66
 
68
- const handleShareClick = async (e: React.MouseEvent) => {
69
- e.preventDefault()
67
+ const { copyToClipboard } = useCopyToClipboard()
70
68
 
69
+ const handleShareClick = (e: React.MouseEvent) => {
70
+ e.preventDefault()
71
71
  const shareUrl = `${window.location.origin}/${pkg.owner_github_username}/${pkg.unscoped_name}`
72
- const shareText =
73
- `Explore this tscircuit package: ${pkg.unscoped_name} by ${pkg.owner_github_username}${pkg.description ? ` - ${pkg.description}` : ""}`.trim()
74
- if (navigator.share) {
75
- await navigator
76
- .share({
77
- title: shareText,
78
- text: shareText,
79
- url: shareUrl,
80
- })
81
- .catch(() => fallbackShare(shareText, shareUrl))
82
- } else {
83
- fallbackShare(shareText, shareUrl)
84
- }
85
- }
86
-
87
- const fallbackShare = (text: string, url: string) => {
88
- const shareContent = `${text}\n${url}`
89
- navigator.clipboard
90
- .writeText(shareContent)
91
- .then(() => {
92
- toast({
93
- title: "Share content copied to clipboard",
94
- })
95
- })
96
- .catch(() => {
97
- toast({
98
- title: "Unable to share or copy to clipboard",
99
- })
100
- })
72
+ copyToClipboard(shareUrl)
101
73
  }
102
74
 
103
75
  const availableImages = ["pcb", "schematic", "assembly", "3d"]
@@ -1,11 +1,18 @@
1
1
  import { useShikiHighlighter } from "@/hooks/use-shiki-highlighter"
2
2
  import { useEffect, useMemo } from "react"
3
3
  import { useQuery } from "react-query"
4
+ import { Skeleton } from "@/components/ui/skeleton"
5
+
6
+ // Pre-randomized array to avoid flickering on re-renders
7
+ const SKELETON_WIDTHS = ["w-2/3", "w-1/4", "w-5/6", "w-1/3", "w-1/2", "w-3/4"]
4
8
 
5
9
  export const ShikiCodeViewer = ({
6
10
  code,
7
11
  filePath,
8
- }: { code: string; filePath: string }) => {
12
+ }: {
13
+ code: string
14
+ filePath: string
15
+ }) => {
9
16
  const { highlighter } = useShikiHighlighter()
10
17
 
11
18
  const html = useMemo(
@@ -18,7 +25,13 @@ export const ShikiCodeViewer = ({
18
25
  )
19
26
 
20
27
  if (!html) {
21
- return <div>Loading...</div>
28
+ return (
29
+ <div className="text-sm p-4">
30
+ {SKELETON_WIDTHS.map((w, i) => (
31
+ <Skeleton key={i} className={`h-4 mb-2 ${w}`} />
32
+ ))}
33
+ </div>
34
+ )
22
35
  }
23
36
 
24
37
  return (
@@ -94,7 +94,7 @@ export default function MainContentHeader({
94
94
  <DropdownMenuTrigger asChild>
95
95
  <Button
96
96
  size="sm"
97
- className="bg-green-600 shadow-none hover:bg-green-700 dark:bg-[#238636] dark:hover:bg-[#2ea043] text-white"
97
+ className="bg-green-600 hover:bg-green-700 dark:bg-[#238636] dark:hover:bg-[#2ea043] text-white"
98
98
  >
99
99
  <CodeIcon className="h-4 w-4 mr-1.5" />
100
100
  Code
@@ -1,5 +1,6 @@
1
1
  import { CadViewer } from "@tscircuit/runframe"
2
2
  import { useCurrentPackageCircuitJson } from "../../hooks/use-current-package-circuit-json"
3
+ import { Suspense } from "react"
3
4
 
4
5
  export default function ThreeDView() {
5
6
  const { circuitJson, isLoading, error } = useCurrentPackageCircuitJson()
@@ -24,7 +25,19 @@ export default function ThreeDView() {
24
25
 
25
26
  return (
26
27
  <div className="h-[620px]">
27
- <CadViewer clickToInteractEnabled circuitJson={circuitJson} />
28
+ <Suspense
29
+ fallback={
30
+ <div className="flex justify-center items-center h-full">
31
+ <div className="w-48">
32
+ <div className="loading">
33
+ <div className="loading-bar"></div>
34
+ </div>
35
+ </div>
36
+ </div>
37
+ }
38
+ >
39
+ <CadViewer clickToInteractEnabled circuitJson={circuitJson} />
40
+ </Suspense>
28
41
  </div>
29
42
  )
30
43
  }
@@ -1,4 +1,10 @@
1
- import { Dialog, DialogContent, DialogHeader, DialogTitle } from "../ui/dialog"
1
+ import {
2
+ Dialog,
3
+ DialogContent,
4
+ DialogHeader,
5
+ DialogTitle,
6
+ DialogDescription,
7
+ } from "../ui/dialog"
2
8
  import { Button } from "../ui/button"
3
9
  import { createUseDialog } from "./create-use-dialog"
4
10
  import { useDeletePackage } from "@/hooks/use-delete-package"
@@ -33,13 +39,20 @@ export const ConfirmDeletePackageDialog = ({
33
39
 
34
40
  return (
35
41
  <Dialog open={open} onOpenChange={onOpenChange}>
36
- <DialogContent className="w-[90vw]">
42
+ <DialogContent className="w-[90vw] p-6 rounded-2xl shadow-lg">
37
43
  <DialogHeader>
38
- <DialogTitle>Confirm Delete Package</DialogTitle>
44
+ <DialogTitle className="text-left">
45
+ Confirm Delete Package
46
+ </DialogTitle>
47
+ <DialogDescription className="text-left">
48
+ Are you sure you want to delete the package{" "}
49
+ <span className="font-bold">{packageName}</span>?
50
+ </DialogDescription>
39
51
  </DialogHeader>
40
- <p>Are you sure you want to delete the package "{packageName}"?</p>
41
- <p>This action cannot be undone.</p>
42
- <div className="flex justify-end space-x-2 mt-4">
52
+ <p className="text-red-600 font-medium">
53
+ This action cannot be undone.
54
+ </p>
55
+ <div className="flex justify-end gap-4 mt-6">
43
56
  <Button variant="outline" onClick={() => onOpenChange(false)}>
44
57
  Cancel
45
58
  </Button>
@@ -15,7 +15,10 @@ import { getImportsFromCode } from "@tscircuit/prompt-benchmarks/code-runner-uti
15
15
  import type { ATABootstrapConfig } from "@typescript/ata"
16
16
  import { setupTypeAcquisition } from "@typescript/ata"
17
17
  import { linter } from "@codemirror/lint"
18
- import { TSCI_PACKAGE_PATTERN } from "@/lib/constants"
18
+ import {
19
+ TSCI_PACKAGE_PATTERN,
20
+ LOCAL_FILE_IMPORT_PATTERN,
21
+ } from "@/lib/constants"
19
22
  import {
20
23
  createSystem,
21
24
  createVirtualTypeScriptEnvironment,
@@ -29,7 +32,6 @@ import tsModule from "typescript"
29
32
  import CodeEditorHeader, {
30
33
  FileName,
31
34
  } from "@/components/package-port/CodeEditorHeader"
32
- import { useCodeCompletionApi } from "@/hooks/use-code-completion-ai-api"
33
35
  import FileSidebar from "../FileSidebar"
34
36
  import { findTargetFile } from "@/lib/utils/findTargetFile"
35
37
  import type { PackageFile } from "@/types/package"
@@ -44,6 +46,7 @@ import {
44
46
  } from "@/hooks/useFileManagement"
45
47
  import { isHiddenFile } from "../ViewPackagePage/utils/is-hidden-file"
46
48
  import { inlineCopilot } from "codemirror-copilot"
49
+ import { resolveRelativePath } from "@/lib/utils/resolveRelativePath"
47
50
 
48
51
  const defaultImports = `
49
52
  import React from "@types/react/jsx-runtime"
@@ -417,11 +420,13 @@ export const CodeEditor = ({
417
420
  const lineStart = line.from
418
421
  const lineEnd = line.to
419
422
  const lineText = view.state.sliceDoc(lineStart, lineEnd)
420
- const matches = Array.from(
423
+
424
+ // Check for TSCI package imports
425
+ const packageMatches = Array.from(
421
426
  lineText.matchAll(TSCI_PACKAGE_PATTERN),
422
427
  )
423
428
 
424
- for (const match of matches) {
429
+ for (const match of packageMatches) {
425
430
  if (match.index !== undefined) {
426
431
  const start = lineStart + match.index
427
432
  const end = start + match[0].length
@@ -484,10 +489,12 @@ export const CodeEditor = ({
484
489
  const lineStart = line.from
485
490
  const lineEnd = line.to
486
491
  const lineText = view.state.sliceDoc(lineStart, lineEnd)
487
- const matches = Array.from(
492
+
493
+ // Check for TSCI package imports first
494
+ const packageMatches = Array.from(
488
495
  lineText.matchAll(TSCI_PACKAGE_PATTERN),
489
496
  )
490
- for (const match of matches) {
497
+ for (const match of packageMatches) {
491
498
  if (match.index !== undefined) {
492
499
  const start = lineStart + match.index
493
500
  const end = start + match[0].length
@@ -502,6 +509,42 @@ export const CodeEditor = ({
502
509
  }
503
510
  }
504
511
  }
512
+
513
+ // Check for local file imports
514
+ const localFileMatches = Array.from(
515
+ lineText.matchAll(LOCAL_FILE_IMPORT_PATTERN),
516
+ )
517
+ for (const match of localFileMatches) {
518
+ if (match.index !== undefined) {
519
+ const start = lineStart + match.index
520
+ const end = start + match[0].length
521
+ if (pos >= start && pos <= end) {
522
+ const relativePath = match[0]
523
+ const resolvedPath = resolveRelativePath(
524
+ relativePath,
525
+ currentFile || "",
526
+ )
527
+
528
+ // Add common extensions if not present
529
+ let targetPath = resolvedPath
530
+ if (!targetPath.includes(".")) {
531
+ const extensions = [".tsx", ".ts", ".js", ".jsx"]
532
+ for (const ext of extensions) {
533
+ if (fileMap[`${targetPath}${ext}`]) {
534
+ targetPath = `${targetPath}${ext}`
535
+ break
536
+ }
537
+ }
538
+ }
539
+
540
+ if (fileMap[targetPath]) {
541
+ onFileSelect(targetPath)
542
+ return true
543
+ }
544
+ return !!fileMap[targetPath]
545
+ }
546
+ }
547
+ }
505
548
  return false
506
549
  },
507
550
  keydown: (event) => {
@@ -529,6 +572,12 @@ export const CodeEditor = ({
529
572
  overflow: "auto",
530
573
  zIndex: "9999",
531
574
  },
575
+ ".cm-import:hover": {
576
+ textDecoration: "underline",
577
+ textDecorationColor: "#aa1111",
578
+ textUnderlineOffset: "1px",
579
+ filter: "brightness(0.7)",
580
+ },
532
581
  }),
533
582
  EditorView.decorations.of((view) => {
534
583
  const decorations = []
@@ -536,14 +585,32 @@ export const CodeEditor = ({
536
585
  for (let pos = from; pos < to; ) {
537
586
  const line = view.state.doc.lineAt(pos)
538
587
  const lineText = line.text
539
- const matches = lineText.matchAll(TSCI_PACKAGE_PATTERN)
540
- for (const match of matches) {
588
+
589
+ // Add decorations for TSCI package imports
590
+ const packageMatches = lineText.matchAll(TSCI_PACKAGE_PATTERN)
591
+ for (const match of packageMatches) {
592
+ if (match.index !== undefined) {
593
+ const start = line.from + match.index
594
+ const end = start + match[0].length
595
+ decorations.push(
596
+ Decoration.mark({
597
+ class: "cm-import cursor-pointer",
598
+ }).range(start, end),
599
+ )
600
+ }
601
+ }
602
+
603
+ // Add decorations for local file imports
604
+ const localFileMatches = lineText.matchAll(
605
+ LOCAL_FILE_IMPORT_PATTERN,
606
+ )
607
+ for (const match of localFileMatches) {
541
608
  if (match.index !== undefined) {
542
609
  const start = line.from + match.index
543
610
  const end = start + match[0].length
544
611
  decorations.push(
545
612
  Decoration.mark({
546
- class: "cm-underline cursor-pointer",
613
+ class: "cm-import cursor-pointer",
547
614
  }).range(start, end),
548
615
  )
549
616
  }
@@ -29,6 +29,7 @@ import { convertRawEasyToTsx, fetchEasyEDAComponent } from "easyeda/browser"
29
29
  import { ComponentSearchResult } from "@tscircuit/runframe/runner"
30
30
  import { usePackagesBaseApiUrl } from "@/hooks/use-packages-base-api-url"
31
31
  import { ICreateFileProps, ICreateFileResult } from "@/hooks/useFileManagement"
32
+ import { useGlobalStore } from "@/hooks/use-global-store"
32
33
 
33
34
  export type FileName = string
34
35
 
@@ -61,6 +62,7 @@ export const CodeEditorHeader: React.FC<CodeEditorHeaderProps> = ({
61
62
  const [sidebarOpen, setSidebarOpen] = fileSidebarState
62
63
  const API_BASE = usePackagesBaseApiUrl()
63
64
  const [aiAutocompleteEnabled, setAiAutocompleteEnabled] = aiAutocompleteState
65
+ const session = useGlobalStore((s) => s.session)
64
66
 
65
67
  const handleFormatFile = useCallback(() => {
66
68
  if (!window.prettier || !window.prettierPlugins) return
@@ -157,18 +159,21 @@ export const CodeEditorHeader: React.FC<CodeEditorHeaderProps> = ({
157
159
  updateFileContent(currentFile, newContent)
158
160
  }
159
161
  if (component.source == "jlcpcb") {
162
+ if (!session?.token) {
163
+ throw new Error("You need to be logged in to import jlcpcb component")
164
+ }
160
165
  const jlcpcbComponent = await fetchEasyEDAComponent("C1", {
161
166
  fetch: ((url, options: any) => {
162
167
  return fetch(`${API_BASE}/proxy`, {
163
- ...options,
168
+ body: options.body,
169
+ method: options.method,
164
170
  headers: {
165
- ...options?.headers,
171
+ authority: options.headers.authority,
172
+ Authorization: `Bearer ${session?.token}`,
166
173
  "X-Target-Url": url.toString(),
167
- "X-Sender-Origin": options?.headers?.origin ?? "",
168
- "X-Sender-Host": options?.headers?.host ?? "https://easyeda.com",
169
- "X-Sender-Referer": options?.headers?.referer ?? "",
170
- "X-Sender-User-Agent": options?.headers?.userAgent ?? "",
171
- "X-Sender-Cookie": options?.headers?.cookie ?? "",
174
+ "X-Sender-Host": options.headers.origin,
175
+ "X-Sender-Origin": options.headers.origin,
176
+ "content-type": options.headers["content-type"],
172
177
  },
173
178
  })
174
179
  }) as typeof fetch,
@@ -42,9 +42,9 @@ export const useCreatePackageReleaseMutation = ({
42
42
  resolvedPkgName = pkgName
43
43
  }
44
44
 
45
- // Default version to 1.0.0 when it contains no digits
45
+ // Default version to 0.0.1 when it contains no digits
46
46
  if (!resolvedVersion || !/[0-9]/.test(resolvedVersion)) {
47
- resolvedVersion = "1.0.0"
47
+ resolvedVersion = "0.0.1"
48
48
  }
49
49
 
50
50
  const normalizedPackageNameWithVersion =
@@ -30,8 +30,8 @@ export const useRebuildPackageReleaseMutation = ({
30
30
  toast({
31
31
  title: "Error",
32
32
  description:
33
- error?.response?.data?.message ||
34
- error?.data?.message ||
33
+ error?.response?.data?.error?.message ||
34
+ error?.data?.error?.message ||
35
35
  "Failed to rebuild package.",
36
36
  variant: "destructive",
37
37
  })
@@ -10,3 +10,14 @@
10
10
  */
11
11
  export const TSCI_PACKAGE_PATTERN =
12
12
  /@tsci\/[a-zA-Z][a-zA-Z0-9]*(?:--?[a-zA-Z0-9]+)*(?:\.[a-zA-Z][a-zA-Z0-9_]*(?:--?[a-zA-Z0-9_]+)*)*/g
13
+
14
+ /**
15
+ * Regular expression pattern for matching local file imports
16
+ * Rules:
17
+ * - Must start with ./ or ../
18
+ * - Can contain letters, numbers, dots, dashes, underscores, and forward slashes
19
+ * - Can optionally end with file extensions like .ts, .tsx, .js, .jsx, .json
20
+ * - Captures the full relative path
21
+ */
22
+ export const LOCAL_FILE_IMPORT_PATTERN =
23
+ /(?:\.\.?\/[a-zA-Z0-9._\-\/]*(?:\.(?:ts|tsx|js|jsx|json))?)/g
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Resolves a relative file path to an absolute path based on the current file
3
+ */
4
+ export const resolveRelativePath = (
5
+ relativePath: string,
6
+ currentFilePath: string,
7
+ ): string => {
8
+ if (!currentFilePath) return relativePath
9
+
10
+ const currentDir = currentFilePath.includes("/")
11
+ ? currentFilePath.substring(0, currentFilePath.lastIndexOf("/"))
12
+ : ""
13
+
14
+ if (relativePath.startsWith("./")) {
15
+ return currentDir
16
+ ? `${currentDir}/${relativePath.slice(2)}`
17
+ : relativePath.slice(2)
18
+ }
19
+
20
+ if (relativePath.startsWith("../")) {
21
+ const parts = currentDir.split("/").filter((p) => p !== "")
22
+ const relativeParts = relativePath.split("/").filter((p) => p !== "")
23
+
24
+ let upCount = 0
25
+ for (const part of relativeParts) {
26
+ if (part === "..") {
27
+ upCount++
28
+ } else {
29
+ break
30
+ }
31
+ }
32
+
33
+ const resultParts = parts.slice(0, Math.max(0, parts.length - upCount))
34
+ const remainingParts = relativeParts.slice(upCount)
35
+
36
+ return [...resultParts, ...remainingParts].join("/")
37
+ }
38
+
39
+ return relativePath
40
+ }
@@ -1,11 +1,12 @@
1
1
  import React, { useState } from "react"
2
2
  import { useQuery } from "react-query"
3
3
  import { useAxios } from "@/hooks/use-axios"
4
+ import { useCreateDatasheet } from "@/hooks/use-create-datasheet"
4
5
  import Header from "@/components/Header"
5
6
  import Footer from "@/components/Footer"
6
7
  import { Input } from "@/components/ui/input"
7
8
  import { Search } from "lucide-react"
8
- import { Link } from "wouter"
9
+ import { Link, useLocation } from "wouter"
9
10
 
10
11
  interface DatasheetSummary {
11
12
  datasheet_id: string
@@ -14,6 +15,10 @@ interface DatasheetSummary {
14
15
 
15
16
  export const DatasheetsPage: React.FC = () => {
16
17
  const axios = useAxios()
18
+ const [, navigate] = useLocation()
19
+ const createDatasheet = useCreateDatasheet({
20
+ onSuccess: (datasheet) => navigate(`/datasheets/${datasheet.chip_name}`),
21
+ })
17
22
  const [searchQuery, setSearchQuery] = useState("")
18
23
 
19
24
  const {
@@ -119,6 +124,19 @@ export const DatasheetsPage: React.FC = () => {
119
124
  ? `No datasheets match your search for "${searchQuery}".`
120
125
  : "There are no popular datasheets at the moment."}
121
126
  </p>
127
+ {searchQuery && (
128
+ <button
129
+ className="mt-2 px-4 py-2 bg-blue-500 text-white rounded"
130
+ onClick={() =>
131
+ createDatasheet.mutate({ chip_name: searchQuery })
132
+ }
133
+ disabled={createDatasheet.isLoading}
134
+ >
135
+ {createDatasheet.isLoading
136
+ ? "Creating..."
137
+ : `Create Datasheet for ${searchQuery}`}
138
+ </button>
139
+ )}
122
140
  </div>
123
141
  )}
124
142
  </main>