@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/bun.lock +73 -97
- package/dist/bundle.js +4 -3
- package/dist/index.js +2 -1
- package/fake-snippets-api/lib/db/autoload-dev-packages.ts +1 -0
- package/fake-snippets-api/routes/api/packages/list_trending.ts +1 -1
- package/package.json +4 -8
- package/src/ContextProviders.tsx +0 -2
- package/src/components/PageSearchComponent.tsx +2 -2
- package/src/components/SearchComponent.tsx +2 -2
- package/src/components/TrendingPackagesCarousel.tsx +2 -2
- package/src/components/dialogs/import-component-dialog.tsx +25 -0
- package/src/components/package-port/CodeEditor.tsx +103 -21
- package/src/components/package-port/CodeEditorHeader.tsx +67 -10
- package/src/components/package-port/EditorNav.tsx +56 -50
- package/src/components/package-port/GlobalFindReplace.tsx +2 -2
- package/src/hooks/use-axios.ts +2 -2
- package/src/hooks/use-packages-base-api-url.ts +3 -0
- package/src/hooks/use-request-ai-review-mutation.ts +1 -1
- package/src/hooks/use-sign-in.ts +2 -2
- package/src/hooks/use-toast.tsx +1 -0
- package/src/hooks/useFileManagement.ts +8 -3
- package/src/main.tsx +0 -3
- package/src/pages/dashboard.tsx +2 -2
- package/src/pages/dev-login.tsx +2 -2
- package/src/pages/latest.tsx +2 -2
- package/src/pages/search.tsx +2 -2
- package/src/pages/trending.tsx +2 -2
- package/src/pages/user-profile.tsx +2 -2
- package/vite.config.ts +0 -19
- package/src/build-watcher.ts +0 -52
- package/src/global.d.ts +0 -3
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 =
|
|
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.
|
|
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.
|
|
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.
|
|
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",
|
package/src/ContextProviders.tsx
CHANGED
|
@@ -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 {
|
|
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 =
|
|
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 {
|
|
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 =
|
|
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 {
|
|
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 =
|
|
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 {
|
|
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 =
|
|
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
|
-
|
|
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(
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
|
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:
|
|
53
|
-
|
|
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
|
-
<
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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
|
-
|
|
389
|
-
|
|
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
|
-
|
|
429
|
-
onClick={() => updatePackageVisibilityToPrivate(false)}
|
|
385
|
+
onClick={() => openupdateDescriptionDialog()}
|
|
430
386
|
>
|
|
431
|
-
|
|
387
|
+
<FilePenLine className="mr-2 h-3 w-3" />
|
|
388
|
+
Edit Description
|
|
432
389
|
</DropdownMenuItem>
|
|
433
|
-
|
|
434
|
-
|
|
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()}
|