@tscircuit/fake-snippets 0.0.44 → 0.0.46
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/.github/workflows/bun-pver-release.yml +1 -0
- package/bun.lock +43 -11
- package/dist/bundle.js +405 -335
- package/dist/schema.d.ts +1845 -0
- package/dist/schema.js +251 -0
- 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 +8 -3
- 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 +24 -9
- 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
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Button } from "@/components/ui/button"
|
|
2
|
+
import { PrefetchPageLink } from "@/components/PrefetchPageLink"
|
|
3
|
+
|
|
4
|
+
export function NotFound({ heading = "Page not found" }: { heading?: string }) {
|
|
5
|
+
return (
|
|
6
|
+
<section className="flex-1 flex items-center justify-center min-h-[90vh]">
|
|
7
|
+
<div className="container px-4 md:px-6 py-12 flex flex-col items-center text-center max-w-3xl">
|
|
8
|
+
<div className="mb-8 flex flex-col items-center justify-center">
|
|
9
|
+
<div className="mb-2">
|
|
10
|
+
<span className="text-3xl font-bold text-white bg-blue-500 px-4 py-2 rounded-md shadow-md inline-block">
|
|
11
|
+
404
|
|
12
|
+
</span>
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
<h1 className="text-4xl font-extrabold tracking-tight lg:text-5xl mb-4">
|
|
16
|
+
{heading}
|
|
17
|
+
</h1>
|
|
18
|
+
<p className="text-xl text-muted-foreground mb-8">
|
|
19
|
+
The page you're looking for doesn't exist or has been moved to another
|
|
20
|
+
address.
|
|
21
|
+
</p>
|
|
22
|
+
<div className="flex flex-col sm:flex-row gap-4">
|
|
23
|
+
<PrefetchPageLink href="/">
|
|
24
|
+
<Button size="lg" className="bg-blue-500 hover:bg-blue-600">
|
|
25
|
+
Return Home
|
|
26
|
+
</Button>
|
|
27
|
+
</PrefetchPageLink>
|
|
28
|
+
<PrefetchPageLink href="/search">
|
|
29
|
+
<Button size="lg" variant="outline">
|
|
30
|
+
Search Packages
|
|
31
|
+
</Button>
|
|
32
|
+
</PrefetchPageLink>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
</section>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
@@ -55,12 +55,6 @@ export interface PreviewContentProps {
|
|
|
55
55
|
onManualEditsFileContentChange?: (newmanualEditsFileContent: string) => void
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
declare global {
|
|
59
|
-
interface Window {
|
|
60
|
-
TSCIRCUIT_3D_OBJECT_REF: any
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
58
|
export const PreviewContent = ({
|
|
65
59
|
code,
|
|
66
60
|
triggerRunTsx,
|
|
@@ -32,7 +32,7 @@ export const TrendingSnippetCarousel = () => {
|
|
|
32
32
|
{trendingSnippets?.length ? (
|
|
33
33
|
<>
|
|
34
34
|
<div className="container mx-auto px-4">
|
|
35
|
-
<h2 className="text-2xl font-semibold mb-6">Trending
|
|
35
|
+
<h2 className="text-2xl font-semibold mb-6">Trending Packages</h2>
|
|
36
36
|
</div>
|
|
37
37
|
<div
|
|
38
38
|
className="flex gap-6 overflow-x-hidden relative"
|
|
@@ -8,8 +8,9 @@ import {
|
|
|
8
8
|
} from "@/hooks/use-package-stars"
|
|
9
9
|
import { LockClosedIcon } from "@radix-ui/react-icons"
|
|
10
10
|
import { GitFork, Star } from "lucide-react"
|
|
11
|
-
import { useToast } from "@/hooks/use-toast"
|
|
12
11
|
import { Link } from "wouter"
|
|
12
|
+
import { useOrderDialog } from "@tscircuit/runframe"
|
|
13
|
+
import { useEffect } from "react"
|
|
13
14
|
|
|
14
15
|
interface PackageInfo {
|
|
15
16
|
name: string
|
|
@@ -37,8 +38,7 @@ export default function PackageHeader({
|
|
|
37
38
|
const author = packageInfo?.owner_github_username
|
|
38
39
|
const packageName = packageInfo?.unscoped_name
|
|
39
40
|
|
|
40
|
-
const {
|
|
41
|
-
|
|
41
|
+
const { OrderDialog, isOpen, open, close, stage, setStage } = useOrderDialog()
|
|
42
42
|
const { data: starData, isLoading: isStarDataLoading } =
|
|
43
43
|
usePackageStarsByName(packageInfo?.name ?? null)
|
|
44
44
|
const { addStar, removeStar } = usePackageStarMutationByName(
|
|
@@ -66,6 +66,12 @@ export default function PackageHeader({
|
|
|
66
66
|
const isStarLoading =
|
|
67
67
|
isStarDataLoading || addStar.isLoading || removeStar.isLoading
|
|
68
68
|
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
window.TSCIRCUIT_REGISTRY_API_BASE_URL =
|
|
71
|
+
import.meta.env.VITE_TSCIRCUIT_REGISTRY_API_URL ??
|
|
72
|
+
`${window.location.origin}/api`
|
|
73
|
+
}, [])
|
|
74
|
+
|
|
69
75
|
return (
|
|
70
76
|
<header className="bg-white border-b border-gray-200 py-4">
|
|
71
77
|
<div className="max-w-[1200px] mx-auto px-4">
|
|
@@ -103,6 +109,15 @@ export default function PackageHeader({
|
|
|
103
109
|
)}
|
|
104
110
|
</div>
|
|
105
111
|
<div className="items-center space-x-2 hidden md:flex">
|
|
112
|
+
{/* WIP: add order button */}
|
|
113
|
+
{/* <Button
|
|
114
|
+
size="sm"
|
|
115
|
+
variant="default"
|
|
116
|
+
className="bg-blue-600 hover:bg-blue-700"
|
|
117
|
+
onClick={open}
|
|
118
|
+
>
|
|
119
|
+
Place Order
|
|
120
|
+
</Button> */}
|
|
106
121
|
<Button
|
|
107
122
|
variant="outline"
|
|
108
123
|
size="sm"
|
|
@@ -165,6 +180,12 @@ export default function PackageHeader({
|
|
|
165
180
|
</div>
|
|
166
181
|
</div>
|
|
167
182
|
</div>
|
|
183
|
+
<OrderDialog
|
|
184
|
+
isOpen={isOpen}
|
|
185
|
+
onClose={close}
|
|
186
|
+
stage={stage}
|
|
187
|
+
setStage={setStage}
|
|
188
|
+
/>
|
|
168
189
|
</header>
|
|
169
190
|
)
|
|
170
191
|
}
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { useState } from "react"
|
|
2
2
|
import { Button } from "../ui/button"
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
Dialog,
|
|
5
|
+
DialogContent,
|
|
6
|
+
DialogHeader,
|
|
7
|
+
DialogTitle,
|
|
8
|
+
DialogDescription,
|
|
9
|
+
} from "../ui/dialog"
|
|
4
10
|
import { Label } from "../ui/label"
|
|
5
11
|
import { RadioGroup, RadioGroupItem } from "../ui/radio-group"
|
|
6
12
|
import { createUseDialog } from "./create-use-dialog"
|
|
@@ -23,6 +29,9 @@ export const PackageVisibilitySettingsDialog = ({
|
|
|
23
29
|
<DialogContent>
|
|
24
30
|
<DialogHeader>
|
|
25
31
|
<DialogTitle>Package Privacy Settings</DialogTitle>
|
|
32
|
+
<DialogDescription>
|
|
33
|
+
Control whether your package is publicly visible or private.
|
|
34
|
+
</DialogDescription>
|
|
26
35
|
</DialogHeader>
|
|
27
36
|
<div className="py-1">
|
|
28
37
|
<RadioGroup
|
|
@@ -13,12 +13,6 @@ interface ViewTsFilesDialogProps {
|
|
|
13
13
|
onOpenChange: (open: boolean) => void
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
declare global {
|
|
17
|
-
interface Window {
|
|
18
|
-
__DEBUG_CODE_EDITOR_FS_MAP: Map<string, string>
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
16
|
export const ViewTsFilesDialog: React.FC<ViewTsFilesDialogProps> = ({
|
|
23
17
|
open,
|
|
24
18
|
onOpenChange,
|
|
@@ -9,7 +9,6 @@ import { decodeUrlHashToText } from "@/lib/decodeUrlHashToText"
|
|
|
9
9
|
import { getSnippetTemplate } from "@/lib/get-snippet-template"
|
|
10
10
|
import { cn } from "@/lib/utils"
|
|
11
11
|
import { parseJsonOrNull } from "@/lib/utils/parseJsonOrNull"
|
|
12
|
-
import "@/prettier"
|
|
13
12
|
import type { Package } from "fake-snippets-api/lib/db/schema"
|
|
14
13
|
import { Loader2 } from "lucide-react"
|
|
15
14
|
import { useEffect, useMemo, useState } from "react"
|
|
@@ -23,12 +22,13 @@ import { useCreatePackageReleaseMutation } from "@/hooks/use-create-package-rele
|
|
|
23
22
|
import { useUpdatePackageFilesMutation } from "@/hooks/useUpdatePackageFilesMutation"
|
|
24
23
|
import { useUpdatePackageMutation } from "@/hooks/useUpdatePackageMutation"
|
|
25
24
|
import { usePackageFilesLoader } from "@/hooks/usePackageFilesLoader"
|
|
25
|
+
import { findTargetFile } from "@/lib/utils/findTargetFile"
|
|
26
26
|
|
|
27
27
|
interface Props {
|
|
28
28
|
pkg?: Package
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
interface PackageFile {
|
|
31
|
+
export interface PackageFile {
|
|
32
32
|
path: string
|
|
33
33
|
content: string
|
|
34
34
|
}
|
|
@@ -94,6 +94,8 @@ export function CodeAndPreview({ pkg }: Props) {
|
|
|
94
94
|
const [isPrivate, setIsPrivate] = useState(false)
|
|
95
95
|
|
|
96
96
|
const entryPointCode = useMemo(() => {
|
|
97
|
+
const entryPointFile = findTargetFile(pkgFilesWithContent, null)
|
|
98
|
+
if (entryPointFile && entryPointFile.content) return entryPointFile.content
|
|
97
99
|
return (
|
|
98
100
|
pkgFilesWithContent.find((x) => x.path === "index.tsx")?.content ??
|
|
99
101
|
defaultCode
|
|
@@ -147,6 +149,10 @@ export function CodeAndPreview({ pkg }: Props) {
|
|
|
147
149
|
const { data: loadedFiles, isLoading: isLoadingFiles } =
|
|
148
150
|
usePackageFilesLoader(pkg)
|
|
149
151
|
|
|
152
|
+
const [pkgFilesLoaded, setPkgFilesLoaded] = useState<boolean>(
|
|
153
|
+
urlParams.package_id ? false : true,
|
|
154
|
+
)
|
|
155
|
+
|
|
150
156
|
useEffect(() => {
|
|
151
157
|
if (!pkgFiles.data?.length) {
|
|
152
158
|
if (pkg && pkgFilesWithContent.length === 0) {
|
|
@@ -171,6 +177,7 @@ export function CodeAndPreview({ pkg }: Props) {
|
|
|
171
177
|
}
|
|
172
178
|
|
|
173
179
|
setPkgFilesWithContent(processedResults)
|
|
180
|
+
setPkgFilesLoaded(true)
|
|
174
181
|
setInitialFilesLoad(processedResults)
|
|
175
182
|
setLastRunCode(
|
|
176
183
|
processedResults.find((x) => x.path === "index.tsx")?.content ??
|
|
@@ -185,12 +192,6 @@ export function CodeAndPreview({ pkg }: Props) {
|
|
|
185
192
|
defaultCode,
|
|
186
193
|
])
|
|
187
194
|
|
|
188
|
-
// useEffect(() => {
|
|
189
|
-
// if (pkg && pkgFiles.data) {
|
|
190
|
-
// loadPkgFiles()
|
|
191
|
-
// }
|
|
192
|
-
// }, [pkg, pkgFiles.data])
|
|
193
|
-
|
|
194
195
|
const createPackageMutation = useCreatePackageMutation()
|
|
195
196
|
|
|
196
197
|
const { mutate: createRelease, isLoading: isCreatingRelease } =
|
|
@@ -303,11 +304,24 @@ export function CodeAndPreview({ pkg }: Props) {
|
|
|
303
304
|
: `${importStatement}\ncircuit.add(\n <board>\n <Snippet name="U1" />\n </board>\n)`
|
|
304
305
|
|
|
305
306
|
return {
|
|
307
|
+
...pkgFilesWithContent.reduce(
|
|
308
|
+
(acc, file) => {
|
|
309
|
+
acc[file.path] = file.content
|
|
310
|
+
return acc
|
|
311
|
+
},
|
|
312
|
+
{} as Record<string, string>,
|
|
313
|
+
),
|
|
306
314
|
"index.tsx": entryPointCode ?? "// No Default Code Found",
|
|
307
315
|
"manual-edits.json": manualEditsFileContent ?? "{}",
|
|
308
316
|
"main.tsx": entrypointContent.trim(),
|
|
309
317
|
}
|
|
310
|
-
}, [
|
|
318
|
+
}, [
|
|
319
|
+
manualEditsFileContent,
|
|
320
|
+
entryPointCode,
|
|
321
|
+
code,
|
|
322
|
+
packageType,
|
|
323
|
+
pkgFilesWithContent,
|
|
324
|
+
])
|
|
311
325
|
|
|
312
326
|
if ((!pkg && urlParams.package_id) || pkgFiles.isLoading) {
|
|
313
327
|
return (
|
|
@@ -355,6 +369,7 @@ export function CodeAndPreview({ pkg }: Props) {
|
|
|
355
369
|
)
|
|
356
370
|
}}
|
|
357
371
|
onDtsChange={setDts}
|
|
372
|
+
pkgFilesLoaded={pkgFilesLoaded}
|
|
358
373
|
/>
|
|
359
374
|
</div>
|
|
360
375
|
{showPreview && (
|
|
@@ -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" />
|