@tscircuit/fake-snippets 0.0.44 → 0.0.45
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 +43 -11
- package/dist/bundle.js +405 -335
- package/fake-snippets-api/routes/api/_fake/received_quotes.ts +66 -0
- package/fake-snippets-api/routes/api/package_releases/update.ts +25 -18
- package/package.json +2 -2
- package/src/components/CodeAndPreview.tsx +0 -1
- package/src/components/CodeEditor.tsx +0 -1
- package/src/components/CodeEditorHeader.tsx +0 -25
- package/src/components/EditorNav.tsx +10 -8
- package/src/components/ErrorOutline.tsx +35 -0
- package/src/components/FileSidebar.tsx +46 -16
- package/src/components/NotFound.tsx +37 -0
- package/src/components/PreviewContent.tsx +0 -6
- package/src/components/TrendingSnippetCarousel.tsx +1 -1
- package/src/components/ViewPackagePage/components/package-header.tsx +24 -3
- package/src/components/ViewPackagePage/utils/is-hidden-file.ts +0 -1
- package/src/components/dialogs/package-visibility-settings-dialog.tsx +10 -1
- package/src/components/dialogs/view-ts-files-dialog.tsx +0 -6
- package/src/components/package-port/CodeAndPreview.tsx +10 -8
- package/src/components/package-port/CodeEditor.tsx +26 -38
- package/src/components/package-port/CodeEditorHeader.tsx +117 -39
- package/src/components/package-port/EditorNav.tsx +10 -8
- package/src/components/ui/tree-view.tsx +5 -1
- package/src/{prettier.ts → lib/types.ts} +3 -1
- package/src/lib/utils/findTargetFile.ts +62 -0
- package/src/lib/utils/load-prettier.ts +3 -0
- package/src/pages/404.tsx +2 -33
- package/src/pages/package-editor.tsx +14 -3
- package/src/pages/user-profile.tsx +66 -27
- package/src/components/FootprintDialog.tsx +0 -339
- package/src/components/ParametersEditor.tsx +0 -140
- package/src/lib/utils/parseFootprintParams.ts +0 -52
|
@@ -26,19 +26,15 @@ import { EditorView } from "codemirror"
|
|
|
26
26
|
import { useEffect, useMemo, useRef, useState } from "react"
|
|
27
27
|
import ts from "typescript"
|
|
28
28
|
import CodeEditorHeader from "@/components/package-port/CodeEditorHeader"
|
|
29
|
-
// import { copilotPlugin, Language } from "@valtown/codemirror-codeium"
|
|
30
29
|
import { useCodeCompletionApi } from "@/hooks/use-code-completion-ai-api"
|
|
31
30
|
import FileSidebar from "../FileSidebar"
|
|
32
|
-
|
|
31
|
+
import { findTargetFile } from "@/lib/utils/findTargetFile"
|
|
32
|
+
import type { PackageFile } from "./CodeAndPreview"
|
|
33
33
|
const defaultImports = `
|
|
34
34
|
import React from "@types/react/jsx-runtime"
|
|
35
35
|
import { Circuit, createUseComponent } from "@tscircuit/core"
|
|
36
36
|
import type { CommonLayoutProps } from "@tscircuit/props"
|
|
37
37
|
`
|
|
38
|
-
export interface FileContent {
|
|
39
|
-
path: string
|
|
40
|
-
content: string
|
|
41
|
-
}
|
|
42
38
|
|
|
43
39
|
export const CodeEditor = ({
|
|
44
40
|
onCodeChange,
|
|
@@ -48,12 +44,14 @@ export const CodeEditor = ({
|
|
|
48
44
|
isStreaming = false,
|
|
49
45
|
showImportAndFormatButtons = true,
|
|
50
46
|
onFileContentChanged,
|
|
47
|
+
pkgFilesLoaded,
|
|
51
48
|
}: {
|
|
52
49
|
onCodeChange: (code: string, filename?: string) => void
|
|
53
50
|
onDtsChange?: (dts: string) => void
|
|
54
|
-
files:
|
|
51
|
+
files: PackageFile[]
|
|
55
52
|
readOnly?: boolean
|
|
56
53
|
isStreaming?: boolean
|
|
54
|
+
pkgFilesLoaded?: boolean
|
|
57
55
|
showImportAndFormatButtons?: boolean
|
|
58
56
|
onFileContentChanged?: (path: string, content: string) => void
|
|
59
57
|
}) => {
|
|
@@ -62,7 +60,6 @@ export const CodeEditor = ({
|
|
|
62
60
|
const ataRef = useRef<ReturnType<typeof setupTypeAcquisition> | null>(null)
|
|
63
61
|
const apiUrl = useSnippetsBaseApiUrl()
|
|
64
62
|
const codeCompletionApi = useCodeCompletionApi()
|
|
65
|
-
|
|
66
63
|
const [cursorPosition, setCursorPosition] = useState<number | null>(null)
|
|
67
64
|
const [code, setCode] = useState(files[0]?.content || "")
|
|
68
65
|
const [currentFile, setCurrentFile] = useState<string>("")
|
|
@@ -70,35 +67,17 @@ export const CodeEditor = ({
|
|
|
70
67
|
// Get URL search params for file_path
|
|
71
68
|
const urlParams = new URLSearchParams(window.location.search)
|
|
72
69
|
const filePathFromUrl = urlParams.get("file_path")
|
|
73
|
-
|
|
74
70
|
// Set current file on component mount
|
|
75
71
|
useEffect(() => {
|
|
76
|
-
if (files.length
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
// Priority 2: Use index.tsx if it exists in files
|
|
85
|
-
else if (files.some((file) => file.path === "index.tsx")) {
|
|
86
|
-
setCurrentFile("index.tsx")
|
|
87
|
-
}
|
|
88
|
-
// Priority 3: Use the first file with .tsx extension
|
|
89
|
-
else {
|
|
90
|
-
const tsxFile = files.find((file) => file.path.endsWith(".tsx"))
|
|
91
|
-
if (tsxFile) {
|
|
92
|
-
setCurrentFile(tsxFile.path)
|
|
93
|
-
}
|
|
94
|
-
// Fallback: Use the first file in the array
|
|
95
|
-
else if (files[0]) {
|
|
96
|
-
setCurrentFile(files[0].path)
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
return
|
|
72
|
+
if (files.length === 0 || !pkgFilesLoaded || currentFile) return
|
|
73
|
+
|
|
74
|
+
const targetFile = findTargetFile(files, filePathFromUrl)
|
|
75
|
+
|
|
76
|
+
if (targetFile) {
|
|
77
|
+
setCurrentFile(targetFile.path)
|
|
78
|
+
setCode(targetFile.content)
|
|
100
79
|
}
|
|
101
|
-
}, [
|
|
80
|
+
}, [filePathFromUrl, pkgFilesLoaded])
|
|
102
81
|
|
|
103
82
|
const fileMap = useMemo(() => {
|
|
104
83
|
const map: Record<string, string> = {}
|
|
@@ -448,6 +427,12 @@ export const CodeEditor = ({
|
|
|
448
427
|
|
|
449
428
|
const handleFileChange = (path: string) => {
|
|
450
429
|
setCurrentFile(path)
|
|
430
|
+
try {
|
|
431
|
+
// Set url query to file path
|
|
432
|
+
const urlParams = new URLSearchParams(window.location.search)
|
|
433
|
+
urlParams.set("file_path", path)
|
|
434
|
+
window.history.replaceState(null, "", `?${urlParams.toString()}`)
|
|
435
|
+
} catch {}
|
|
451
436
|
}
|
|
452
437
|
|
|
453
438
|
const updateFileContent = (path: string, newContent: string) => {
|
|
@@ -478,7 +463,7 @@ export const CodeEditor = ({
|
|
|
478
463
|
}
|
|
479
464
|
const [sidebarOpen, setSidebarOpen] = useState(false)
|
|
480
465
|
return (
|
|
481
|
-
<div className="flex h-full">
|
|
466
|
+
<div className="flex h-full w-full">
|
|
482
467
|
<FileSidebar
|
|
483
468
|
files={Object.fromEntries(files.map((f) => [f.path, f.content]))}
|
|
484
469
|
currentFile={currentFile}
|
|
@@ -487,7 +472,7 @@ export const CodeEditor = ({
|
|
|
487
472
|
}
|
|
488
473
|
onFileSelect={handleFileChange}
|
|
489
474
|
/>
|
|
490
|
-
<div className="flex flex-col flex-1">
|
|
475
|
+
<div className="flex flex-col flex-1 w-full min-w-0">
|
|
491
476
|
{showImportAndFormatButtons && (
|
|
492
477
|
<CodeEditorHeader
|
|
493
478
|
fileSidebarState={
|
|
@@ -500,10 +485,13 @@ export const CodeEditor = ({
|
|
|
500
485
|
updateFileContent={(...args) => {
|
|
501
486
|
return updateFileContent(...args)
|
|
502
487
|
}}
|
|
503
|
-
|
|
488
|
+
handleFileChange={handleFileChange}
|
|
504
489
|
/>
|
|
505
490
|
)}
|
|
506
|
-
<div
|
|
491
|
+
<div
|
|
492
|
+
ref={editorRef}
|
|
493
|
+
className="flex-1 overflow-auto [&_.cm-editor]:h-full"
|
|
494
|
+
/>
|
|
507
495
|
</div>
|
|
508
496
|
</div>
|
|
509
497
|
)
|
|
@@ -3,7 +3,6 @@ import { Button } from "@/components/ui/button"
|
|
|
3
3
|
import { handleManualEditsImport } from "@/lib/handleManualEditsImport"
|
|
4
4
|
import { useImportSnippetDialog } from "@/components/dialogs/import-snippet-dialog"
|
|
5
5
|
import { useToast } from "@/hooks/use-toast"
|
|
6
|
-
import { FootprintDialog } from "@/components/FootprintDialog"
|
|
7
6
|
import {
|
|
8
7
|
DropdownMenu,
|
|
9
8
|
DropdownMenuContent,
|
|
@@ -12,36 +11,70 @@ import {
|
|
|
12
11
|
} from "@/components/ui/dropdown-menu"
|
|
13
12
|
import { AlertTriangle, PanelRightClose } from "lucide-react"
|
|
14
13
|
import { checkIfManualEditsImported } from "@/lib/utils/checkIfManualEditsImported"
|
|
15
|
-
|
|
14
|
+
import {
|
|
15
|
+
Select,
|
|
16
|
+
SelectContent,
|
|
17
|
+
SelectItem,
|
|
18
|
+
SelectTrigger,
|
|
19
|
+
SelectValue,
|
|
20
|
+
} from "../ui/select"
|
|
21
|
+
import { isHiddenFile } from "../ViewPackagePage/utils/is-hidden-file"
|
|
16
22
|
export type FileName = string
|
|
17
23
|
|
|
18
24
|
interface CodeEditorHeaderProps {
|
|
19
25
|
currentFile: FileName
|
|
20
26
|
files: Record<FileName, string>
|
|
21
27
|
updateFileContent: (filename: FileName, content: string) => void
|
|
22
|
-
cursorPosition: number | null
|
|
23
28
|
fileSidebarState: ReturnType<typeof useState<boolean>>
|
|
29
|
+
handleFileChange: (filename: FileName) => void
|
|
24
30
|
}
|
|
25
31
|
|
|
26
32
|
export const CodeEditorHeader: React.FC<CodeEditorHeaderProps> = ({
|
|
27
33
|
currentFile,
|
|
28
34
|
files,
|
|
29
35
|
updateFileContent,
|
|
30
|
-
cursorPosition,
|
|
31
36
|
fileSidebarState,
|
|
37
|
+
handleFileChange,
|
|
32
38
|
}) => {
|
|
33
39
|
const { Dialog: ImportSnippetDialog, openDialog: openImportDialog } =
|
|
34
40
|
useImportSnippetDialog()
|
|
35
|
-
const [footprintDialogOpen, setFootprintDialogOpen] = useState(false)
|
|
36
41
|
const { toast } = useToast()
|
|
37
42
|
const [sidebarOpen, setSidebarOpen] = fileSidebarState
|
|
43
|
+
|
|
38
44
|
const handleFormatFile = useCallback(() => {
|
|
39
45
|
if (!window.prettier || !window.prettierPlugins) return
|
|
40
|
-
|
|
41
46
|
try {
|
|
42
47
|
const currentContent = files[currentFile]
|
|
48
|
+
let fileExtension = currentFile.split(".").pop()?.toLowerCase()
|
|
49
|
+
if (currentContent.trim().length === 0) {
|
|
50
|
+
toast({
|
|
51
|
+
title: "Empty file",
|
|
52
|
+
description: "Cannot format an empty file.",
|
|
53
|
+
})
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
if (!fileExtension) {
|
|
57
|
+
toast({
|
|
58
|
+
title: "Cannot determine file type",
|
|
59
|
+
description: "Unable to format file without an extension.",
|
|
60
|
+
})
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (["readme"].includes(currentFile.toLowerCase())) {
|
|
65
|
+
fileExtension = "md"
|
|
66
|
+
}
|
|
43
67
|
|
|
44
|
-
if (currentFile.
|
|
68
|
+
if (fileExtension === currentFile.toLowerCase()) {
|
|
69
|
+
toast({
|
|
70
|
+
title: "Cannot determine file type",
|
|
71
|
+
description: "Unable to format file without an extension.",
|
|
72
|
+
})
|
|
73
|
+
return
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Handle JSON formatting separately
|
|
77
|
+
if (fileExtension === "json") {
|
|
45
78
|
try {
|
|
46
79
|
const jsonObj = JSON.parse(currentContent)
|
|
47
80
|
const formattedJson = JSON.stringify(jsonObj, null, 2)
|
|
@@ -52,28 +85,47 @@ export const CodeEditorHeader: React.FC<CodeEditorHeaderProps> = ({
|
|
|
52
85
|
description: "Failed to format JSON: invalid syntax.",
|
|
53
86
|
variant: "destructive",
|
|
54
87
|
})
|
|
55
|
-
return
|
|
56
88
|
}
|
|
57
89
|
return
|
|
58
90
|
}
|
|
59
91
|
|
|
92
|
+
const parserMap: Record<string, string> = {
|
|
93
|
+
js: "babel",
|
|
94
|
+
jsx: "babel",
|
|
95
|
+
ts: "typescript",
|
|
96
|
+
tsx: "typescript",
|
|
97
|
+
md: "markdown",
|
|
98
|
+
markdown: "markdown",
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const parser = parserMap[fileExtension] || "typescript"
|
|
60
102
|
const formattedCode = window.prettier.format(currentContent, {
|
|
61
103
|
semi: false,
|
|
62
|
-
parser:
|
|
104
|
+
parser: parser,
|
|
63
105
|
plugins: window.prettierPlugins,
|
|
64
106
|
})
|
|
65
107
|
|
|
66
108
|
updateFileContent(currentFile, formattedCode)
|
|
67
109
|
} catch (error) {
|
|
68
110
|
console.error("Formatting error:", error)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
111
|
+
if (
|
|
112
|
+
error instanceof Error &&
|
|
113
|
+
error.message.includes("No parser could be inferred")
|
|
114
|
+
) {
|
|
115
|
+
toast({
|
|
116
|
+
title: "Unsupported File Type",
|
|
117
|
+
description: `Formatting not supported for .${currentFile.split(".").pop()?.toLowerCase()} files. Tried default parser.`,
|
|
118
|
+
})
|
|
119
|
+
} else {
|
|
120
|
+
toast({
|
|
121
|
+
title: "Formatting error",
|
|
122
|
+
description:
|
|
123
|
+
error instanceof Error
|
|
124
|
+
? error.message
|
|
125
|
+
: "Failed to format the code. Please check for syntax errors.",
|
|
126
|
+
variant: "destructive",
|
|
127
|
+
})
|
|
128
|
+
}
|
|
77
129
|
}
|
|
78
130
|
}, [currentFile, files, toast, updateFileContent])
|
|
79
131
|
|
|
@@ -81,13 +133,59 @@ export const CodeEditorHeader: React.FC<CodeEditorHeaderProps> = ({
|
|
|
81
133
|
<>
|
|
82
134
|
<div className="flex items-center gap-2 px-2 border-b border-gray-200">
|
|
83
135
|
<button
|
|
84
|
-
className={`text-
|
|
136
|
+
className={`text-gray-400 scale-90 transition-opacity duration-200 ${
|
|
137
|
+
sidebarOpen ? "opacity-0 pointer-events-none" : "opacity-100"
|
|
138
|
+
}`}
|
|
85
139
|
onClick={() => setSidebarOpen(true)}
|
|
86
140
|
>
|
|
87
141
|
<PanelRightClose />
|
|
88
142
|
</button>
|
|
143
|
+
<div>
|
|
144
|
+
<Select value={currentFile} onValueChange={handleFileChange}>
|
|
145
|
+
<SelectTrigger className="h-7 px-3 bg-white">
|
|
146
|
+
<SelectValue placeholder="Select file" />
|
|
147
|
+
</SelectTrigger>
|
|
148
|
+
<SelectContent>
|
|
149
|
+
{Object.keys(files)
|
|
150
|
+
.filter(
|
|
151
|
+
(filename) =>
|
|
152
|
+
!isHiddenFile(
|
|
153
|
+
filename.startsWith("/") ? filename.slice(1) : filename,
|
|
154
|
+
),
|
|
155
|
+
)
|
|
156
|
+
.map((filename) => (
|
|
157
|
+
<SelectItem className="py-1" key={filename} value={filename}>
|
|
158
|
+
<span
|
|
159
|
+
className={`text-xs pr-1 block truncate ${
|
|
160
|
+
sidebarOpen ? "max-w-[5rem]" : "max-w-[10rem]"
|
|
161
|
+
}`}
|
|
162
|
+
>
|
|
163
|
+
{filename}
|
|
164
|
+
</span>
|
|
165
|
+
</SelectItem>
|
|
166
|
+
))}
|
|
167
|
+
{currentFile &&
|
|
168
|
+
Object.keys(files).includes(currentFile) &&
|
|
169
|
+
isHiddenFile(
|
|
170
|
+
currentFile.startsWith("/")
|
|
171
|
+
? currentFile.slice(1)
|
|
172
|
+
: currentFile,
|
|
173
|
+
) && (
|
|
174
|
+
<SelectItem className="py-1" value={currentFile}>
|
|
175
|
+
<span
|
|
176
|
+
className={`text-xs pr-1 block truncate ${
|
|
177
|
+
sidebarOpen ? "max-w-[5rem]" : "max-w-[10rem]"
|
|
178
|
+
}`}
|
|
179
|
+
>
|
|
180
|
+
{currentFile}
|
|
181
|
+
</span>
|
|
182
|
+
</SelectItem>
|
|
183
|
+
)}
|
|
184
|
+
</SelectContent>
|
|
185
|
+
</Select>
|
|
186
|
+
</div>
|
|
89
187
|
|
|
90
|
-
<div className="flex items-center gap-2 px-2 py-1 ml-auto">
|
|
188
|
+
<div className="flex items-center overflow-x-hidden gap-2 px-2 py-1 ml-auto">
|
|
91
189
|
{checkIfManualEditsImported(files) && (
|
|
92
190
|
<DropdownMenu>
|
|
93
191
|
<DropdownMenuTrigger asChild>
|
|
@@ -112,18 +210,6 @@ export const CodeEditorHeader: React.FC<CodeEditorHeaderProps> = ({
|
|
|
112
210
|
</DropdownMenuContent>
|
|
113
211
|
</DropdownMenu>
|
|
114
212
|
)}
|
|
115
|
-
<DropdownMenu>
|
|
116
|
-
<DropdownMenuTrigger asChild>
|
|
117
|
-
<Button size="sm" variant="ghost">
|
|
118
|
-
Insert
|
|
119
|
-
</Button>
|
|
120
|
-
</DropdownMenuTrigger>
|
|
121
|
-
<DropdownMenuContent>
|
|
122
|
-
<DropdownMenuItem onClick={() => setFootprintDialogOpen(true)}>
|
|
123
|
-
Chip
|
|
124
|
-
</DropdownMenuItem>
|
|
125
|
-
</DropdownMenuContent>
|
|
126
|
-
</DropdownMenu>
|
|
127
213
|
<Button size="sm" variant="ghost" onClick={() => openImportDialog()}>
|
|
128
214
|
Import
|
|
129
215
|
</Button>
|
|
@@ -137,14 +223,6 @@ export const CodeEditorHeader: React.FC<CodeEditorHeaderProps> = ({
|
|
|
137
223
|
updateFileContent(currentFile, newContent)
|
|
138
224
|
}}
|
|
139
225
|
/>
|
|
140
|
-
<FootprintDialog
|
|
141
|
-
currentFile={currentFile as `${string}.${string}`}
|
|
142
|
-
open={footprintDialogOpen}
|
|
143
|
-
onOpenChange={setFootprintDialogOpen}
|
|
144
|
-
updateFileContent={updateFileContent}
|
|
145
|
-
files={files}
|
|
146
|
-
cursorPosition={cursorPosition}
|
|
147
|
-
/>
|
|
148
226
|
</div>
|
|
149
227
|
</>
|
|
150
228
|
)
|
|
@@ -218,14 +218,16 @@ export default function EditorNav({
|
|
|
218
218
|
{pkg.star_count}
|
|
219
219
|
</span>
|
|
220
220
|
)}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
221
|
+
{pkg.owner_github_username === session?.github_username && (
|
|
222
|
+
<Button
|
|
223
|
+
variant="ghost"
|
|
224
|
+
size="icon"
|
|
225
|
+
className="h-6 w-6 ml-2"
|
|
226
|
+
onClick={() => openRenameDialog()}
|
|
227
|
+
>
|
|
228
|
+
<Pencil className="h-3 w-3 text-gray-700" />
|
|
229
|
+
</Button>
|
|
230
|
+
)}
|
|
229
231
|
{isPrivate && (
|
|
230
232
|
<div className="relative group">
|
|
231
233
|
<LockClosedIcon className="h-3 w-3 text-gray-700" />
|
|
@@ -9,7 +9,7 @@ const treeVariants = cva(
|
|
|
9
9
|
)
|
|
10
10
|
|
|
11
11
|
const selectedTreeVariants = cva(
|
|
12
|
-
"before:opacity-100 before:bg-slate-100/70 text-accent-foreground
|
|
12
|
+
"before:opacity-100 before:bg-slate-100/70 text-accent-foreground dark:before:bg-slate-800/70",
|
|
13
13
|
)
|
|
14
14
|
|
|
15
15
|
const dragOverVariants = cva(
|
|
@@ -58,6 +58,10 @@ const TreeView = React.forwardRef<HTMLDivElement, TreeProps>(
|
|
|
58
58
|
string | undefined
|
|
59
59
|
>(initialSelectedItemId)
|
|
60
60
|
|
|
61
|
+
React.useEffect(() => {
|
|
62
|
+
setSelectedItemId(initialSelectedItemId)
|
|
63
|
+
}, [initialSelectedItemId])
|
|
64
|
+
|
|
61
65
|
const [draggedItem, setDraggedItem] = React.useState<TreeDataItem | null>(
|
|
62
66
|
null,
|
|
63
67
|
)
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
// Prettier is injected into the global scope inside index.html
|
|
2
1
|
declare global {
|
|
3
2
|
interface Window {
|
|
3
|
+
TSCIRCUIT_REGISTRY_API_BASE_URL: string
|
|
4
|
+
TSCIRCUIT_3D_OBJECT_REF: any
|
|
5
|
+
__DEBUG_CODE_EDITOR_FS_MAP: Map<string, string>
|
|
4
6
|
prettier: {
|
|
5
7
|
format: (code: string, options: any) => string
|
|
6
8
|
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { PackageFile } from "@/components/package-port/CodeAndPreview"
|
|
2
|
+
|
|
3
|
+
export const findMainEntrypointFileFromTscircuitConfig = (
|
|
4
|
+
files: PackageFile[],
|
|
5
|
+
): PackageFile | null => {
|
|
6
|
+
const configFile = files.find((file) => file.path === "tscircuit.config.json")
|
|
7
|
+
|
|
8
|
+
if (configFile) {
|
|
9
|
+
try {
|
|
10
|
+
const config = JSON.parse(configFile.content)
|
|
11
|
+
|
|
12
|
+
if (config && typeof config.mainEntrypoint === "string") {
|
|
13
|
+
const mainComponentPath = config.mainEntrypoint
|
|
14
|
+
|
|
15
|
+
const normalizedPath = mainComponentPath.startsWith("./")
|
|
16
|
+
? mainComponentPath.substring(2)
|
|
17
|
+
: mainComponentPath
|
|
18
|
+
|
|
19
|
+
return files.find((file) => file.path === normalizedPath) ?? null
|
|
20
|
+
}
|
|
21
|
+
} catch {}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return null
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const findTargetFile = (
|
|
28
|
+
files: PackageFile[],
|
|
29
|
+
filePathFromUrl: string | null,
|
|
30
|
+
): PackageFile | null => {
|
|
31
|
+
if (files.length === 0) {
|
|
32
|
+
return null
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let targetFile: PackageFile | null = null
|
|
36
|
+
|
|
37
|
+
if (filePathFromUrl) {
|
|
38
|
+
targetFile = files.find((file) => file.path === filePathFromUrl) ?? null
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!targetFile) {
|
|
42
|
+
targetFile = findMainEntrypointFileFromTscircuitConfig(files)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!targetFile) {
|
|
46
|
+
targetFile = files.find((file) => file.path === "index.tsx") ?? null
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (!targetFile) {
|
|
50
|
+
targetFile = files.find((file) => file.path.endsWith(".tsx")) ?? null
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!targetFile) {
|
|
54
|
+
targetFile = files.find((file) => file.path === "index.ts") ?? null
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (!targetFile && files[0]) {
|
|
58
|
+
targetFile = files[0]
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return targetFile
|
|
62
|
+
}
|
package/src/pages/404.tsx
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import React from "react"
|
|
2
1
|
import { Helmet } from "react-helmet"
|
|
3
2
|
import { Header2 } from "@/components/Header2"
|
|
4
3
|
import Footer from "@/components/Footer"
|
|
5
|
-
import {
|
|
6
|
-
import { PrefetchPageLink } from "@/components/PrefetchPageLink"
|
|
4
|
+
import { NotFound } from "@/components/NotFound"
|
|
7
5
|
|
|
8
6
|
export function NotFoundPage({
|
|
9
7
|
heading = "Page Not Found",
|
|
@@ -18,36 +16,7 @@ export function NotFoundPage({
|
|
|
18
16
|
/>
|
|
19
17
|
</Helmet>
|
|
20
18
|
<Header2 />
|
|
21
|
-
<
|
|
22
|
-
<div className="container px-4 md:px-6 py-12 flex flex-col items-center text-center max-w-3xl">
|
|
23
|
-
<div className="mb-8 flex flex-col items-center justify-center">
|
|
24
|
-
<div className="mb-2">
|
|
25
|
-
<span className="text-3xl font-bold text-white bg-blue-500 px-4 py-2 rounded-md shadow-md inline-block">
|
|
26
|
-
404
|
|
27
|
-
</span>
|
|
28
|
-
</div>
|
|
29
|
-
</div>
|
|
30
|
-
<h1 className="text-4xl font-extrabold tracking-tight lg:text-5xl mb-4">
|
|
31
|
-
{heading}
|
|
32
|
-
</h1>
|
|
33
|
-
<p className="text-xl text-muted-foreground mb-8">
|
|
34
|
-
The page you're looking for doesn't exist or has been moved to
|
|
35
|
-
another address.
|
|
36
|
-
</p>
|
|
37
|
-
<div className="flex flex-col sm:flex-row gap-4">
|
|
38
|
-
<PrefetchPageLink href="/">
|
|
39
|
-
<Button size="lg" className="bg-blue-500 hover:bg-blue-600">
|
|
40
|
-
Return Home
|
|
41
|
-
</Button>
|
|
42
|
-
</PrefetchPageLink>
|
|
43
|
-
<PrefetchPageLink href="/search">
|
|
44
|
-
<Button size="lg" variant="outline">
|
|
45
|
-
Search Packages
|
|
46
|
-
</Button>
|
|
47
|
-
</PrefetchPageLink>
|
|
48
|
-
</div>
|
|
49
|
-
</div>
|
|
50
|
-
</main>
|
|
19
|
+
<NotFound heading={heading} />
|
|
51
20
|
<Footer />
|
|
52
21
|
</div>
|
|
53
22
|
)
|
|
@@ -4,10 +4,15 @@ import Header from "@/components/Header"
|
|
|
4
4
|
import { usePackage } from "@/hooks/use-package"
|
|
5
5
|
import { Helmet } from "react-helmet-async"
|
|
6
6
|
import { useCurrentPackageId } from "@/hooks/use-current-package-id"
|
|
7
|
+
import { NotFound } from "@/components/NotFound"
|
|
8
|
+
import { ErrorOutline } from "@/components/ErrorOutline"
|
|
7
9
|
|
|
8
10
|
export const EditorPage = () => {
|
|
9
11
|
const { packageId } = useCurrentPackageId()
|
|
10
12
|
const { data: pkg, isLoading, error } = usePackage(packageId)
|
|
13
|
+
const uuid4RegExp = new RegExp(
|
|
14
|
+
/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$/,
|
|
15
|
+
)
|
|
11
16
|
return (
|
|
12
17
|
<div className="overflow-x-hidden">
|
|
13
18
|
<Helmet>
|
|
@@ -32,10 +37,16 @@ export const EditorPage = () => {
|
|
|
32
37
|
</Helmet>
|
|
33
38
|
<Header />
|
|
34
39
|
{!error && <CodeAndPreview pkg={pkg} />}
|
|
35
|
-
{error &&
|
|
40
|
+
{error &&
|
|
41
|
+
(error.status === 404 || !uuid4RegExp.test(packageId ?? "")) && (
|
|
42
|
+
<NotFound heading="Package not found" />
|
|
43
|
+
)}
|
|
36
44
|
{error && error.status !== 404 && (
|
|
37
|
-
<div className="
|
|
38
|
-
|
|
45
|
+
<div className="min-h-screen grid place-items-center">
|
|
46
|
+
<ErrorOutline
|
|
47
|
+
error={error}
|
|
48
|
+
description={"There was an error loading the editor page"}
|
|
49
|
+
/>
|
|
39
50
|
</div>
|
|
40
51
|
)}
|
|
41
52
|
<Footer />
|