@tscircuit/fake-snippets 0.0.90 → 0.0.92

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/dist/index.js CHANGED
@@ -387,7 +387,8 @@ var loadPackageWithDependencies = async (db, owner, name, loadedPackages = /* @_
387
387
  ...pkg,
388
388
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
389
389
  updated_at: (/* @__PURE__ */ new Date()).toISOString(),
390
- latest_package_release_id: release.package_release_id
390
+ latest_package_release_id: release.package_release_id,
391
+ star_count: Math.floor(Math.random() * 11)
391
392
  });
392
393
  db.addPackageRelease({
393
394
  ...release,
@@ -118,6 +118,7 @@ const loadPackageWithDependencies = async (
118
118
  created_at: new Date().toISOString(),
119
119
  updated_at: new Date().toISOString(),
120
120
  latest_package_release_id: release.package_release_id,
121
+ star_count: Math.floor(Math.random() * 11),
121
122
  })
122
123
 
123
124
  db.addPackageRelease({
@@ -24,7 +24,7 @@ export default withRouteSpec({
24
24
  })
25
25
 
26
26
  // Filter out packages with no stars and sort by star count
27
- const trendingPackages = packagesWithStars
27
+ const trendingPackages = ctx.db.packages
28
28
  .filter((p) => p.star_count > 0)
29
29
  .sort((a, b) => b.star_count - a.star_count)
30
30
  .slice(0, 50)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tscircuit/fake-snippets",
3
- "version": "0.0.90",
3
+ "version": "0.0.92",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -77,16 +77,12 @@
77
77
  "@tailwindcss/typography": "^0.5.16",
78
78
  "@tscircuit/3d-viewer": "^0.0.279",
79
79
  "@tscircuit/assembly-viewer": "^0.0.1",
80
- "@tscircuit/core": "^0.0.536",
81
80
  "@tscircuit/create-snippet-url": "^0.0.8",
82
81
  "@tscircuit/eval": "^0.0.244",
83
- "@tscircuit/footprinter": "^0.0.186",
84
82
  "@tscircuit/layout": "^0.0.29",
85
- "@tscircuit/math-utils": "^0.0.10",
86
83
  "@tscircuit/mm": "^0.0.8",
87
84
  "@tscircuit/pcb-viewer": "^1.11.194",
88
85
  "@tscircuit/prompt-benchmarks": "^0.0.28",
89
- "@tscircuit/props": "^0.0.246",
90
86
  "@tscircuit/runframe": "^0.0.669",
91
87
  "@tscircuit/schematic-viewer": "^2.0.21",
92
88
  "@types/babel__standalone": "^7.1.7",
@@ -108,7 +104,6 @@
108
104
  "@vitejs/plugin-react": "^4.3.1",
109
105
  "autoprefixer": "^10.4.20",
110
106
  "change-case": "^5.4.4",
111
- "circuit-json": "^0.0.190",
112
107
  "circuit-json-to-bom-csv": "^0.0.7",
113
108
  "circuit-json-to-gerber": "^0.0.25",
114
109
  "circuit-json-to-pnp-csv": "^0.0.7",
@@ -124,7 +119,7 @@
124
119
  "date-fns": "^4.1.0",
125
120
  "dotenv": "^16.5.0",
126
121
  "dsn-converter": "^0.0.60",
127
- "easyeda": "^0.0.195",
122
+ "easyeda": "^0.0.203",
128
123
  "embla-carousel-react": "^8.3.0",
129
124
  "extract-codefence": "^0.0.4",
130
125
  "fflate": "^0.8.2",
@@ -139,7 +134,7 @@
139
134
  "jose": "^5.9.3",
140
135
  "jscad-electronics": "^0.0.25",
141
136
  "jszip": "^3.10.1",
142
- "kicad-converter": "^0.0.16",
137
+ "kicad-converter": "^0.0.17",
143
138
  "ky": "^1.7.5",
144
139
  "lucide-react": "^0.488.0",
145
140
  "lz-string": "^1.5.0",
@@ -179,6 +174,7 @@
179
174
  "terser": "^5.27.0",
180
175
  "three": "^0.177.0",
181
176
  "three-stdlib": "^2.36.0",
177
+ "tscircuit": "^0.0.522",
182
178
  "tsup": "^8.5.0",
183
179
  "typescript": "^5.6.3",
184
180
  "use-async-memo": "^1.2.5",
@@ -4,7 +4,6 @@ import { useEffect } from "react"
4
4
  import { useGlobalStore } from "./hooks/use-global-store"
5
5
  import { posthog } from "./lib/posthog"
6
6
  import { Toaster } from "react-hot-toast"
7
- import { Toaster as SonnerToaster } from "@/components/ui/sonner"
8
7
  import { populateQueryCacheWithSSRData } from "./lib/populate-query-cache-with-ssr-data"
9
8
 
10
9
  const staffGithubUsernames = [
@@ -66,7 +65,6 @@ export const ContextProviders = ({ children }: any) => {
66
65
  <PostHogIdentifier />
67
66
  {children}
68
67
  <Toaster position="bottom-right" />
69
- <SonnerToaster position="bottom-left" />
70
68
  </HelmetProvider>
71
69
  </QueryClientProvider>
72
70
  )
@@ -3,7 +3,7 @@ import { useAxios } from "@/hooks/use-axios"
3
3
  import { useLocation } from "wouter"
4
4
  import React, { useState } from "react"
5
5
  import { useQuery } from "react-query"
6
- import { useSnippetsBaseApiUrl } from "@/hooks/use-snippets-base-api-url"
6
+ import { usePackagesBaseApiUrl } from "@/hooks/use-packages-base-api-url"
7
7
  import { Search } from "lucide-react"
8
8
  import { Button } from "./ui/button"
9
9
  import { PackageCardSkeleton } from "./PackageCardSkeleton"
@@ -20,7 +20,7 @@ const PageSearchComponent: React.FC<PageSearchComponentProps> = ({
20
20
  }) => {
21
21
  const [location, setLocation] = useLocation()
22
22
  const axios = useAxios()
23
- const snippetsBaseApiUrl = useSnippetsBaseApiUrl()
23
+ const snippetsBaseApiUrl = usePackagesBaseApiUrl()
24
24
 
25
25
  // Initialize search query directly from URL
26
26
  const [searchQuery, setSearchQuery] = useState(
@@ -4,7 +4,7 @@ import { useLocation } from "wouter"
4
4
  import React, { useEffect, useRef, useState } from "react"
5
5
  import { useQuery } from "react-query"
6
6
  import { Alert } from "./ui/alert"
7
- import { useSnippetsBaseApiUrl } from "@/hooks/use-snippets-base-api-url"
7
+ import { usePackagesBaseApiUrl } from "@/hooks/use-packages-base-api-url"
8
8
  import { PrefetchPageLink } from "./PrefetchPageLink"
9
9
  import { CircuitBoard } from "lucide-react"
10
10
  import { cn } from "@/lib/utils"
@@ -60,7 +60,7 @@ const SearchComponent: React.FC<SearchComponentProps> = ({
60
60
  const resultsRef = useRef<HTMLDivElement>(null)
61
61
  const inputRef = useRef<HTMLInputElement>(null)
62
62
  const [location, setLocation] = useLocation()
63
- const snippetsBaseApiUrl = useSnippetsBaseApiUrl()
63
+ const snippetsBaseApiUrl = usePackagesBaseApiUrl()
64
64
 
65
65
  const { data: searchResults, isLoading } = useQuery(
66
66
  ["packageSearch", searchQuery],
@@ -4,7 +4,7 @@ import { StarFilledIcon } from "@radix-ui/react-icons"
4
4
  import { Link } from "wouter"
5
5
  import { Package } from "fake-snippets-api/lib/db/schema"
6
6
  import { useRef, useState } from "react"
7
- import { useSnippetsBaseApiUrl } from "@/hooks/use-snippets-base-api-url"
7
+ import { usePackagesBaseApiUrl } from "@/hooks/use-packages-base-api-url"
8
8
 
9
9
  const CarouselItem = ({
10
10
  pkg,
@@ -36,7 +36,7 @@ export const TrendingPackagesCarousel = () => {
36
36
  const axios = useAxios()
37
37
  const scrollRef = useRef<HTMLDivElement>(null)
38
38
  const [isHovered, setIsHovered] = useState(false)
39
- const apiBaseUrl = useSnippetsBaseApiUrl()
39
+ const apiBaseUrl = usePackagesBaseApiUrl()
40
40
 
41
41
  const { data: trendingPackages } = useQuery<Package[]>(
42
42
  "trendingPackages",
@@ -0,0 +1,25 @@
1
+ import { createUseDialog } from "./create-use-dialog"
2
+ import {
3
+ ComponentSearchResult,
4
+ ImportComponentDialog as RunframeImportComponentDialog,
5
+ } from "@tscircuit/runframe/runner"
6
+
7
+ export const ImportComponentDialog = ({
8
+ open,
9
+ onOpenChange,
10
+ onComponentSelected,
11
+ }: {
12
+ open: boolean
13
+ onOpenChange: (open: boolean) => any
14
+ onComponentSelected: (pkg: ComponentSearchResult) => any
15
+ }) => {
16
+ return (
17
+ <RunframeImportComponentDialog
18
+ isOpen={open}
19
+ onClose={() => onOpenChange(false)}
20
+ onImport={(data) => onComponentSelected(data)}
21
+ />
22
+ )
23
+ }
24
+
25
+ export const useImportComponentDialog = createUseDialog(ImportComponentDialog)
@@ -1,4 +1,4 @@
1
- import { useSnippetsBaseApiUrl } from "@/hooks/use-snippets-base-api-url"
1
+ import { usePackagesBaseApiUrl } from "@/hooks/use-packages-base-api-url"
2
2
  import { useHotkeyCombo } from "@/hooks/use-hotkey"
3
3
  import { basicSetup } from "@/lib/codemirror/basic-setup"
4
4
  import {
@@ -76,25 +76,27 @@ export const CodeEditor = ({
76
76
  showImportAndFormatButtons?: boolean
77
77
  onFileContentChanged?: (path: string, content: string) => void
78
78
  currentFile: string | null
79
- onFileSelect: (path: string) => void
79
+ onFileSelect: (path: string, lineNumber?: number) => void
80
80
  }) => {
81
81
  const editorRef = useRef<HTMLDivElement>(null)
82
82
  const viewRef = useRef<EditorView | null>(null)
83
83
  const ataRef = useRef<ReturnType<typeof setupTypeAcquisition> | null>(null)
84
84
  const lastReceivedTsFileTimeRef = useRef<number>(0)
85
- const apiUrl = useSnippetsBaseApiUrl()
86
- const codeCompletionApi = useCodeCompletionApi()
85
+ const apiUrl = usePackagesBaseApiUrl()
87
86
  const [cursorPosition, setCursorPosition] = useState<number | null>(null)
88
87
  const [code, setCode] = useState(files[0]?.content || "")
89
88
  const [fontSize, setFontSize] = useState(14)
90
89
  const [showQuickOpen, setShowQuickOpen] = useState(false)
91
90
  const [showGlobalFindReplace, setShowGlobalFindReplace] = useState(false)
91
+ const [highlightedLine, setHighlightedLine] = useState<number | null>(null)
92
+ const highlightTimeoutRef = useRef<number | null>(null)
92
93
 
93
94
  const { highlighter } = useShikiHighlighter()
94
95
 
95
96
  // Get URL search params for file_path
96
97
  const urlParams = new URLSearchParams(window.location.search)
97
98
  const filePathFromUrl = urlParams.get("file_path")
99
+ const lineNumberFromUrl = urlParams.get("line")
98
100
  const [aiAutocompleteEnabled, setAiAutocompleteEnabled] = useState(false)
99
101
 
100
102
  const entryPointFileName = useMemo(() => {
@@ -109,10 +111,13 @@ export const CodeEditor = ({
109
111
 
110
112
  const targetFile = findTargetFile(files, filePathFromUrl)
111
113
  if (targetFile) {
112
- handleFileChange(targetFile.path)
114
+ const lineNumber = lineNumberFromUrl
115
+ ? parseInt(lineNumberFromUrl, 10)
116
+ : undefined
117
+ handleFileChange(targetFile.path, lineNumber)
113
118
  setCode(targetFile.content)
114
119
  }
115
- }, [filePathFromUrl, pkgFilesLoaded])
120
+ }, [filePathFromUrl, lineNumberFromUrl, pkgFilesLoaded])
116
121
 
117
122
  const fileMap = useMemo(() => {
118
123
  const map: Record<string, string> = {}
@@ -309,6 +314,14 @@ export const CodeEditor = ({
309
314
  ".cm-content": {
310
315
  fontSize: `${fontSize}px`,
311
316
  },
317
+ ".cm-line-highlight": {
318
+ backgroundColor: "#dbeafe !important",
319
+ animation: "lineHighlightFade 3s ease-in-out forwards",
320
+ },
321
+ "@keyframes lineHighlightFade": {
322
+ "0%": { backgroundColor: "#93c5fd" },
323
+ "100%": { backgroundColor: "transparent" },
324
+ },
312
325
  }),
313
326
  EditorView.domEventHandlers({
314
327
  wheel: (event) => {
@@ -325,21 +338,39 @@ export const CodeEditor = ({
325
338
  return false
326
339
  },
327
340
  }),
341
+ EditorView.decorations.of((view) => {
342
+ const decorations = []
343
+ if (highlightedLine) {
344
+ const doc = view.state.doc
345
+ if (highlightedLine >= 1 && highlightedLine <= doc.lines) {
346
+ const line = doc.line(highlightedLine)
347
+ decorations.push(
348
+ Decoration.line({
349
+ class: "cm-line-highlight",
350
+ }).range(line.from),
351
+ )
352
+ }
353
+ }
354
+ return Decoration.set(decorations)
355
+ }),
328
356
  ]
329
357
  if (aiAutocompleteEnabled) {
330
358
  baseExtensions.push(
331
359
  inlineCopilot(async (prefix, suffix) => {
332
- const res = await fetch("/api/autocomplete/create_autocomplete", {
333
- method: "POST",
334
- headers: {
335
- "Content-Type": "application/json",
360
+ const res = await fetch(
361
+ `${apiUrl}/autocomplete/create_autocomplete`,
362
+ {
363
+ method: "POST",
364
+ headers: {
365
+ "Content-Type": "application/json",
366
+ },
367
+ body: JSON.stringify({
368
+ prefix,
369
+ suffix,
370
+ language: "typescript",
371
+ }),
336
372
  },
337
- body: JSON.stringify({
338
- prefix,
339
- suffix,
340
- language: "typescript",
341
- }),
342
- })
373
+ )
343
374
 
344
375
  const { prediction } = await res.json()
345
376
  return prediction
@@ -543,6 +574,11 @@ export const CodeEditor = ({
543
574
 
544
575
  return () => {
545
576
  view.destroy()
577
+ // Clean up any pending highlight timeout
578
+ if (highlightTimeoutRef.current) {
579
+ window.clearTimeout(highlightTimeoutRef.current)
580
+ highlightTimeoutRef.current = null
581
+ }
546
582
  }
547
583
  }, [
548
584
  !isStreaming,
@@ -552,6 +588,7 @@ export const CodeEditor = ({
552
588
  isSaving,
553
589
  fontSize,
554
590
  aiAutocompleteEnabled,
591
+ highlightedLine,
555
592
  ])
556
593
 
557
594
  const updateCurrentEditorContent = (newContent: string) => {
@@ -571,6 +608,35 @@ export const CodeEditor = ({
571
608
  }
572
609
  }
573
610
 
611
+ const navigateToLine = (lineNumber: number) => {
612
+ if (!viewRef.current) return
613
+
614
+ const view = viewRef.current
615
+ const doc = view.state.doc
616
+
617
+ if (lineNumber < 1 || lineNumber > doc.lines) return
618
+
619
+ if (highlightTimeoutRef.current) {
620
+ window.clearTimeout(highlightTimeoutRef.current)
621
+ highlightTimeoutRef.current = null
622
+ }
623
+
624
+ const line = doc.line(lineNumber)
625
+ const pos = line.from
626
+
627
+ view.dispatch({
628
+ selection: { anchor: pos, head: pos },
629
+ effects: EditorView.scrollIntoView(pos, { y: "center" }),
630
+ })
631
+
632
+ setHighlightedLine(lineNumber)
633
+
634
+ highlightTimeoutRef.current = window.setTimeout(() => {
635
+ setHighlightedLine(null)
636
+ highlightTimeoutRef.current = null
637
+ }, 3000)
638
+ }
639
+
574
640
  const updateEditorToMatchCurrentFile = () => {
575
641
  const currentContent = fileMap[currentFile || ""] || ""
576
642
  updateCurrentEditorContent(currentContent)
@@ -587,14 +653,26 @@ export const CodeEditor = ({
587
653
  }
588
654
  }, [codeImports])
589
655
 
590
- const handleFileChange = (path: string) => {
591
- onFileSelect(path)
656
+ const handleFileChange = (path: string, lineNumber?: number) => {
657
+ onFileSelect(path, lineNumber)
592
658
  try {
593
- // Set url query to file path
659
+ // Set url query to file path and line number
594
660
  const urlParams = new URLSearchParams(window.location.search)
595
661
  urlParams.set("file_path", path)
662
+ if (lineNumber) {
663
+ urlParams.set("line", lineNumber.toString())
664
+ } else {
665
+ urlParams.delete("line")
666
+ }
596
667
  window.history.replaceState(null, "", `?${urlParams.toString()}`)
597
668
  } catch {}
669
+
670
+ // Navigate to line after a short delay to ensure editor is ready
671
+ if (lineNumber) {
672
+ setTimeout(() => {
673
+ navigateToLine(lineNumber)
674
+ }, 100)
675
+ }
598
676
  }
599
677
 
600
678
  const updateFileContent = (path: FileName | null, newContent: string) => {
@@ -653,7 +731,7 @@ export const CodeEditor = ({
653
731
  fileSidebarState={
654
732
  [sidebarOpen, setSidebarOpen] as ReturnType<typeof useState<boolean>>
655
733
  }
656
- onFileSelect={handleFileChange}
734
+ onFileSelect={(path) => handleFileChange(path)}
657
735
  handleCreateFile={handleCreateFile}
658
736
  handleDeleteFile={handleDeleteFile}
659
737
  />
@@ -661,6 +739,10 @@ export const CodeEditor = ({
661
739
  {showImportAndFormatButtons && (
662
740
  <CodeEditorHeader
663
741
  entrypointFileName={entryPointFileName}
742
+ appendNewFile={(path: string, content: string) => {
743
+ onFileContentChanged?.(path, content)
744
+ }}
745
+ createFile={handleCreateFile}
664
746
  fileSidebarState={
665
747
  [sidebarOpen, setSidebarOpen] as ReturnType<
666
748
  typeof useState<boolean>
@@ -687,7 +769,7 @@ export const CodeEditor = ({
687
769
  <QuickOpen
688
770
  files={files.filter((f) => !isHiddenFile(f.path))}
689
771
  currentFile={currentFile}
690
- onFileSelect={handleFileChange}
772
+ onFileSelect={(path) => handleFileChange(path)}
691
773
  onClose={() => setShowQuickOpen(false)}
692
774
  />
693
775
  )}
@@ -1,7 +1,7 @@
1
1
  import React, { useState, useCallback } from "react"
2
2
  import { Button } from "@/components/ui/button"
3
3
  import { handleManualEditsImportWithSupportForMultipleFiles } from "@/lib/handleManualEditsImportWithSupportForMultipleFiles"
4
- import { useImportPackageDialog } from "@/components/dialogs/import-package-dialog"
4
+ import { useImportComponentDialog } from "@/components/dialogs/import-component-dialog"
5
5
  import { useToast } from "@/hooks/use-toast"
6
6
  import {
7
7
  DropdownMenu,
@@ -19,14 +19,16 @@ import {
19
19
  SelectValue,
20
20
  } from "../ui/select"
21
21
  import { isHiddenFile } from "../ViewPackagePage/utils/is-hidden-file"
22
- import { Package } from "fake-snippets-api/lib/db/schema"
23
22
  import {
24
23
  Tooltip,
25
24
  TooltipContent,
26
25
  TooltipProvider,
27
26
  TooltipTrigger,
28
27
  } from "@/components/ui/tooltip"
29
- import ai from "fake-snippets-api/routes/api/ai"
28
+ import { convertRawEasyToTsx, fetchEasyEDAComponent } from "easyeda/browser"
29
+ import { ComponentSearchResult } from "@tscircuit/runframe/runner"
30
+ import { usePackagesBaseApiUrl } from "@/hooks/use-packages-base-api-url"
31
+ import { ICreateFileProps, ICreateFileResult } from "@/hooks/useFileManagement"
30
32
 
31
33
  export type FileName = string
32
34
 
@@ -37,6 +39,8 @@ interface CodeEditorHeaderProps {
37
39
  fileSidebarState: ReturnType<typeof useState<boolean>>
38
40
  handleFileChange: (filename: FileName) => void
39
41
  entrypointFileName?: string
42
+ appendNewFile: (path: string, content: string) => void
43
+ createFile: (props: ICreateFileProps) => ICreateFileResult
40
44
  aiAutocompleteState: [boolean, React.Dispatch<React.SetStateAction<boolean>>]
41
45
  }
42
46
 
@@ -44,15 +48,18 @@ export const CodeEditorHeader: React.FC<CodeEditorHeaderProps> = ({
44
48
  currentFile,
45
49
  files,
46
50
  updateFileContent,
51
+ appendNewFile,
47
52
  fileSidebarState,
48
53
  handleFileChange,
49
54
  entrypointFileName = "index.tsx",
55
+ createFile,
50
56
  aiAutocompleteState,
51
57
  }) => {
52
- const { Dialog: ImportPackageDialog, openDialog: openImportDialog } =
53
- useImportPackageDialog()
54
- const { toast } = useToast()
58
+ const { Dialog: ImportComponentDialog, openDialog: openImportDialog } =
59
+ useImportComponentDialog()
60
+ const { toast, toastLibrary } = useToast()
55
61
  const [sidebarOpen, setSidebarOpen] = fileSidebarState
62
+ const API_BASE = usePackagesBaseApiUrl()
56
63
  const [aiAutocompleteEnabled, setAiAutocompleteEnabled] = aiAutocompleteState
57
64
 
58
65
  const handleFormatFile = useCallback(() => {
@@ -144,6 +151,49 @@ export const CodeEditorHeader: React.FC<CodeEditorHeaderProps> = ({
144
151
  }
145
152
  }, [currentFile, files, toast, updateFileContent])
146
153
 
154
+ const handleComponentImport = async (component: ComponentSearchResult) => {
155
+ if (component.source == "tscircuit.com") {
156
+ const newContent = `import {} from "@tsci/${component.owner}.${component.name}"\n${files[currentFile || ""]}`
157
+ updateFileContent(currentFile, newContent)
158
+ }
159
+ if (component.source == "jlcpcb") {
160
+ const jlcpcbComponent = await fetchEasyEDAComponent("C1", {
161
+ fetch: ((url, options: any) => {
162
+ return fetch(`${API_BASE}/proxy`, {
163
+ ...options,
164
+ headers: {
165
+ ...options?.headers,
166
+ "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 ?? "",
172
+ },
173
+ })
174
+ }) as typeof fetch,
175
+ })
176
+ const tsxComponent = await convertRawEasyToTsx(jlcpcbComponent)
177
+ let componentName = component.name.replace(/ /g, "-")
178
+ if (files[`${componentName}.tsx`] || files[`./${componentName}.tsx`]) {
179
+ componentName = `${componentName}-1`
180
+ }
181
+ const createFileResult = createFile({
182
+ newFileName: `${componentName}.tsx`,
183
+ content: tsxComponent,
184
+ onError: (error) => {
185
+ throw error
186
+ },
187
+ openFile: false,
188
+ })
189
+ if (!createFileResult.newFileCreated) {
190
+ throw new Error("Failed to create file")
191
+ }
192
+ const newContent = `import ${componentName.replace(/-/g, "")} from "./${componentName}.tsx"\n${files[currentFile || ""]}`
193
+ updateFileContent(currentFile, newContent)
194
+ }
195
+ }
196
+
147
197
  return (
148
198
  <>
149
199
  <div className="flex items-center gap-2 px-2 border-b border-gray-200">
@@ -275,10 +325,17 @@ export const CodeEditorHeader: React.FC<CodeEditorHeaderProps> = ({
275
325
  Format
276
326
  </Button>
277
327
  </div>
278
- <ImportPackageDialog
279
- onPackageSelected={(pkg: Package) => {
280
- const newContent = `import {} from "@tsci/${pkg.owner_github_username}.${pkg.unscoped_name}"\n${files[currentFile || ""]}`
281
- updateFileContent(currentFile, newContent)
328
+ <ImportComponentDialog
329
+ onComponentSelected={async (component) => {
330
+ toastLibrary.promise(handleComponentImport(component), {
331
+ loading: "Importing component...",
332
+ success: <p>Component imported successfully!</p>,
333
+ error: (error) => (
334
+ <p>
335
+ Error importing component: {error.message || String(error)}
336
+ </p>
337
+ ),
338
+ })
282
339
  }}
283
340
  />
284
341
  </div>
@@ -371,13 +371,6 @@ export default function EditorNav({
371
371
  </Button>
372
372
  </DropdownMenuTrigger>
373
373
  <DropdownMenuContent>
374
- <DropdownMenuItem
375
- className="text-xs"
376
- onClick={() => openupdateDescriptionDialog()}
377
- >
378
- <FilePenLine className="mr-2 h-3 w-3" />
379
- Edit Description
380
- </DropdownMenuItem>
381
374
  <DropdownMenuItem
382
375
  className="text-xs"
383
376
  onClick={() => openViewTsFilesDialog()}
@@ -385,53 +378,66 @@ export default function EditorNav({
385
378
  <File className="mr-2 h-3 w-3" />
386
379
  View Files
387
380
  </DropdownMenuItem>
388
- <DropdownMenuSub>
389
- <DropdownMenuSubTrigger
390
- className="text-xs"
391
- disabled={isChangingType || hasUnsavedChanges}
392
- >
393
- <Edit2 className="mr-2 h-3 w-3" />
394
- {isChangingType ? "Changing..." : "Change Type"}
395
- </DropdownMenuSubTrigger>
396
- <DropdownMenuSubContent>
397
- <DropdownMenuItem
398
- className="text-xs"
399
- disabled={currentType === "board" || isChangingType}
400
- onClick={() => handleTypeChange("board")}
401
- >
402
- Board {currentType === "board" && "✓"}
403
- </DropdownMenuItem>
404
- <DropdownMenuItem
405
- className="text-xs"
406
- disabled={currentType === "package" || isChangingType}
407
- onClick={() => handleTypeChange("package")}
408
- >
409
- Module {currentType === "package" && "✓"}
410
- </DropdownMenuItem>
411
- </DropdownMenuSubContent>
412
- </DropdownMenuSub>
413
- <DropdownMenuSub>
414
- <DropdownMenuSubTrigger className="text-xs">
415
- <Edit2 className="mr-2 h-3 w-3" />
416
- Change Package Visibility
417
- </DropdownMenuSubTrigger>
418
- <DropdownMenuSubContent>
419
- <DropdownMenuItem
420
- className="text-xs"
421
- disabled={isPrivate}
422
- onClick={() => updatePackageVisibilityToPrivate(true)}
423
- >
424
- Private {isPrivate && "✓"}
425
- </DropdownMenuItem>
381
+ {session?.github_username === pkg.owner_github_username && (
382
+ <>
426
383
  <DropdownMenuItem
427
384
  className="text-xs"
428
- disabled={!isPrivate}
429
- onClick={() => updatePackageVisibilityToPrivate(false)}
385
+ onClick={() => openupdateDescriptionDialog()}
430
386
  >
431
- Public {!isPrivate && "✓"}
387
+ <FilePenLine className="mr-2 h-3 w-3" />
388
+ Edit Description
432
389
  </DropdownMenuItem>
433
- </DropdownMenuSubContent>
434
- </DropdownMenuSub>
390
+ <DropdownMenuSub>
391
+ <DropdownMenuSubTrigger
392
+ className="text-xs"
393
+ disabled={isChangingType || hasUnsavedChanges}
394
+ >
395
+ <Edit2 className="mr-2 h-3 w-3" />
396
+ {isChangingType ? "Changing..." : "Change Type"}
397
+ </DropdownMenuSubTrigger>
398
+ <DropdownMenuSubContent>
399
+ <DropdownMenuItem
400
+ className="text-xs"
401
+ disabled={currentType === "board" || isChangingType}
402
+ onClick={() => handleTypeChange("board")}
403
+ >
404
+ Board {currentType === "board" && "✓"}
405
+ </DropdownMenuItem>
406
+ <DropdownMenuItem
407
+ className="text-xs"
408
+ disabled={currentType === "package" || isChangingType}
409
+ onClick={() => handleTypeChange("package")}
410
+ >
411
+ Module {currentType === "package" && "✓"}
412
+ </DropdownMenuItem>
413
+ </DropdownMenuSubContent>
414
+ </DropdownMenuSub>
415
+ <DropdownMenuSub>
416
+ <DropdownMenuSubTrigger className="text-xs">
417
+ <Edit2 className="mr-2 h-3 w-3" />
418
+ Change Package Visibility
419
+ </DropdownMenuSubTrigger>
420
+ <DropdownMenuSubContent>
421
+ <DropdownMenuItem
422
+ className="text-xs"
423
+ disabled={isPrivate}
424
+ onClick={() => updatePackageVisibilityToPrivate(true)}
425
+ >
426
+ Private {isPrivate && "✓"}
427
+ </DropdownMenuItem>
428
+ <DropdownMenuItem
429
+ className="text-xs"
430
+ disabled={!isPrivate}
431
+ onClick={() =>
432
+ updatePackageVisibilityToPrivate(false)
433
+ }
434
+ >
435
+ Public {!isPrivate && "✓"}
436
+ </DropdownMenuItem>
437
+ </DropdownMenuSubContent>
438
+ </DropdownMenuSub>
439
+ </>
440
+ )}
435
441
  <DropdownMenuItem
436
442
  className="text-xs text-red-600"
437
443
  onClick={() => openDeleteDialog()}