@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.
- package/api/generated-index.js +8 -0
- package/bun.lock +36 -32
- package/dist/bundle.js +32 -7
- package/fake-snippets-api/routes/api/package_releases/rebuild.ts +39 -5
- package/package.json +4 -4
- package/src/App.tsx +10 -1
- package/src/components/DownloadButtonAndMenu.tsx +6 -6
- package/src/components/PackageCard.tsx +5 -33
- package/src/components/ViewPackagePage/components/ShikiCodeViewer.tsx +15 -2
- package/src/components/ViewPackagePage/components/main-content-header.tsx +1 -1
- package/src/components/ViewPackagePage/components/tab-views/3d-view.tsx +14 -1
- package/src/components/dialogs/confirm-delete-package-dialog.tsx +19 -6
- package/src/components/package-port/CodeEditor.tsx +76 -9
- package/src/components/package-port/CodeEditorHeader.tsx +12 -7
- package/src/hooks/use-create-package-release-mutation.ts +2 -2
- package/src/hooks/use-rebuild-package-release-mutation.ts +2 -2
- package/src/lib/constants.ts +11 -0
- package/src/lib/utils/resolveRelativePath.ts +40 -0
- package/src/pages/datasheets.tsx +19 -1
|
@@ -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 } =
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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 (
|
|
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="
|
|
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-
|
|
54
|
+
<Download className="h-4 w-4 mr-2" />
|
|
55
55
|
Download
|
|
56
|
-
<ChevronDown className="h-4 w-4 ml-
|
|
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-
|
|
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="
|
|
70
|
+
<Download className="w-4 h-4 mr-2" />
|
|
71
71
|
Download
|
|
72
|
-
<ChevronDown className="
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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
|
-
}: {
|
|
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
|
|
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
|
|
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
|
-
<
|
|
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 {
|
|
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
|
|
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
|
|
41
|
-
|
|
42
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
540
|
-
|
|
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-
|
|
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
|
-
|
|
168
|
+
body: options.body,
|
|
169
|
+
method: options.method,
|
|
164
170
|
headers: {
|
|
165
|
-
|
|
171
|
+
authority: options.headers.authority,
|
|
172
|
+
Authorization: `Bearer ${session?.token}`,
|
|
166
173
|
"X-Target-Url": url.toString(),
|
|
167
|
-
"X-Sender-
|
|
168
|
-
"X-Sender-
|
|
169
|
-
"
|
|
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
|
|
45
|
+
// Default version to 0.0.1 when it contains no digits
|
|
46
46
|
if (!resolvedVersion || !/[0-9]/.test(resolvedVersion)) {
|
|
47
|
-
resolvedVersion = "
|
|
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
|
})
|
package/src/lib/constants.ts
CHANGED
|
@@ -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
|
+
}
|
package/src/pages/datasheets.tsx
CHANGED
|
@@ -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>
|