@tscircuit/fake-snippets 0.0.69 → 0.0.70
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bundle.js +9 -1
- package/fake-snippets-api/routes/api/packages/get.ts +12 -0
- package/package.json +1 -1
- package/src/components/CircuitJsonImportDialog.tsx +113 -52
- package/src/components/DownloadButtonAndMenu.tsx +1 -1
- package/src/components/HeaderLogin.tsx +1 -1
- package/src/components/ViewPackagePage/components/important-files-view.tsx +1 -1
- package/src/components/ViewPackagePage/components/repo-page-content.tsx +1 -0
- package/src/components/package-port/CodeAndPreview.tsx +1 -1
- package/src/components/package-port/CodeEditorHeader.tsx +12 -4
- package/src/components/package-port/EditorNav.tsx +33 -8
- package/src/hooks/use-create-package-mutation.ts +0 -2
- package/src/pages/package-editor.tsx +2 -1
- package/src/pages/quickstart.tsx +1 -1
- package/src/pages/view-package.tsx +13 -11
|
@@ -34,6 +34,18 @@ export default withRouteSpec({
|
|
|
34
34
|
})
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
if (
|
|
38
|
+
foundPackage.is_private &&
|
|
39
|
+
auth?.github_username !== foundPackage.owner_github_username
|
|
40
|
+
) {
|
|
41
|
+
return ctx.error(404, {
|
|
42
|
+
error_code: "package_not_found",
|
|
43
|
+
message: `Package not found (searched using ${JSON.stringify(
|
|
44
|
+
req.commonParams,
|
|
45
|
+
)})`,
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
|
|
37
49
|
return ctx.json({
|
|
38
50
|
ok: true,
|
|
39
51
|
package: {
|
package/package.json
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
DialogHeader,
|
|
6
6
|
DialogTitle,
|
|
7
7
|
DialogFooter,
|
|
8
|
+
DialogDescription,
|
|
8
9
|
} from "@/components/ui/dialog"
|
|
9
10
|
import { Button } from "@/components/ui/button"
|
|
10
11
|
import { Textarea } from "@/components/ui/textarea"
|
|
@@ -13,6 +14,10 @@ import { useToast } from "@/hooks/use-toast"
|
|
|
13
14
|
import { useLocation } from "wouter"
|
|
14
15
|
import { useGlobalStore } from "@/hooks/use-global-store"
|
|
15
16
|
import { convertCircuitJsonToTscircuit } from "circuit-json-to-tscircuit"
|
|
17
|
+
import { useCreatePackageMutation } from "@/hooks/use-create-package-mutation"
|
|
18
|
+
import { generateRandomPackageName } from "./package-port/CodeAndPreview"
|
|
19
|
+
import { useCreatePackageReleaseMutation } from "@/hooks/use-create-package-release-mutation"
|
|
20
|
+
import { useCreatePackageFilesMutation } from "@/hooks/use-create-package-files-mutation"
|
|
16
21
|
|
|
17
22
|
interface CircuitJsonImportDialogProps {
|
|
18
23
|
open: boolean
|
|
@@ -36,11 +41,29 @@ export function CircuitJsonImportDialog({
|
|
|
36
41
|
const [file, setFile] = useState<File | null>(null)
|
|
37
42
|
const [isLoading, setIsLoading] = useState(false)
|
|
38
43
|
const [error, setError] = useState<string | null>(null)
|
|
39
|
-
const
|
|
44
|
+
const loggedInUser = useGlobalStore((s) => s.session)
|
|
40
45
|
const { toast } = useToast()
|
|
46
|
+
const axios = useAxios()
|
|
41
47
|
const [, navigate] = useLocation()
|
|
42
48
|
const isLoggedIn = useGlobalStore((s) => Boolean(s.session))
|
|
43
|
-
const
|
|
49
|
+
const createPackageMutation = useCreatePackageMutation()
|
|
50
|
+
const { mutate: createRelease } = useCreatePackageReleaseMutation({
|
|
51
|
+
onSuccess: () => {
|
|
52
|
+
toast({
|
|
53
|
+
title: "Package released",
|
|
54
|
+
description: "Your package has been released successfully.",
|
|
55
|
+
})
|
|
56
|
+
},
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
const createPackageFilesMutation = useCreatePackageFilesMutation({
|
|
60
|
+
onSuccess: () => {
|
|
61
|
+
toast({
|
|
62
|
+
title: "Package files created",
|
|
63
|
+
description: "Your package files have been created successfully.",
|
|
64
|
+
})
|
|
65
|
+
},
|
|
66
|
+
})
|
|
44
67
|
|
|
45
68
|
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
46
69
|
const selectedFile = e.target.files?.[0]
|
|
@@ -54,80 +77,117 @@ export function CircuitJsonImportDialog({
|
|
|
54
77
|
const handleImport = async () => {
|
|
55
78
|
let importedCircuitJson
|
|
56
79
|
|
|
57
|
-
|
|
80
|
+
const parseJson = async (jsonString: string) => {
|
|
58
81
|
try {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
setError("Error reading JSON file. Please ensure it is valid.")
|
|
63
|
-
return
|
|
82
|
+
return JSON.parse(jsonString)
|
|
83
|
+
} catch {
|
|
84
|
+
throw new Error("Invalid JSON format.")
|
|
64
85
|
}
|
|
65
|
-
} else if (isValidJSON(circuitJson)) {
|
|
66
|
-
setIsLoading(true)
|
|
67
|
-
setError(null)
|
|
68
|
-
importedCircuitJson = JSON.parse(circuitJson)
|
|
69
|
-
} else {
|
|
70
|
-
toast({
|
|
71
|
-
title: "Invalid Input",
|
|
72
|
-
description: "Please provide a valid JSON content or file.",
|
|
73
|
-
variant: "destructive",
|
|
74
|
-
})
|
|
75
|
-
return
|
|
76
86
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
tscircuit = convertCircuitJsonToTscircuit(importedCircuitJson as any, {
|
|
80
|
-
componentName: "circuit",
|
|
81
|
-
})
|
|
82
|
-
console.info(tscircuit)
|
|
83
|
-
} catch {
|
|
87
|
+
|
|
88
|
+
const handleError = (message: string) => {
|
|
84
89
|
toast({
|
|
85
90
|
title: "Import Failed",
|
|
86
|
-
description:
|
|
91
|
+
description: message,
|
|
87
92
|
variant: "destructive",
|
|
88
93
|
})
|
|
89
94
|
setIsLoading(false)
|
|
90
|
-
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const handleSuccess = (message: string) => {
|
|
98
|
+
toast({
|
|
99
|
+
title: "Success",
|
|
100
|
+
description: message,
|
|
101
|
+
})
|
|
91
102
|
}
|
|
92
103
|
|
|
93
104
|
try {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if (message) {
|
|
104
|
-
setError(message)
|
|
105
|
-
setIsLoading(false)
|
|
105
|
+
if (file) {
|
|
106
|
+
const fileText = await file.text()
|
|
107
|
+
importedCircuitJson = await parseJson(fileText)
|
|
108
|
+
} else if (isValidJSON(circuitJson)) {
|
|
109
|
+
setIsLoading(true)
|
|
110
|
+
setError(null)
|
|
111
|
+
importedCircuitJson = await parseJson(circuitJson)
|
|
112
|
+
} else {
|
|
113
|
+
handleError("Please provide a valid JSON content or file.")
|
|
106
114
|
return
|
|
107
115
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
116
|
+
|
|
117
|
+
const tscircuitComponentContent = convertCircuitJsonToTscircuit(
|
|
118
|
+
importedCircuitJson as any,
|
|
119
|
+
{
|
|
120
|
+
componentName: "circuit",
|
|
121
|
+
},
|
|
122
|
+
)
|
|
123
|
+
console.info(tscircuitComponentContent)
|
|
124
|
+
|
|
125
|
+
await createPackageMutation.mutateAsync(
|
|
126
|
+
{
|
|
127
|
+
name: `${loggedInUser?.github_username}/${generateRandomPackageName()}`,
|
|
128
|
+
description: "Imported from Circuit JSON",
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
onSuccess: (newPackage) => {
|
|
132
|
+
handleSuccess("Package has been created successfully.")
|
|
133
|
+
createRelease(
|
|
134
|
+
{
|
|
135
|
+
package_name_with_version: `${newPackage.name}@latest`,
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
onSuccess: (release) => {
|
|
139
|
+
createPackageFilesMutation
|
|
140
|
+
.mutateAsync({
|
|
141
|
+
file_path: "index.tsx",
|
|
142
|
+
content_text: tscircuitComponentContent,
|
|
143
|
+
package_release_id: release.package_release_id,
|
|
144
|
+
})
|
|
145
|
+
.then(() => {
|
|
146
|
+
navigate(`/editor?package_id=${newPackage.package_id}`)
|
|
147
|
+
})
|
|
148
|
+
},
|
|
149
|
+
onError: (error) => {
|
|
150
|
+
setError(error)
|
|
151
|
+
handleError("Failed to create package release.")
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
)
|
|
155
|
+
},
|
|
156
|
+
onError: (error) => {
|
|
157
|
+
setError(error)
|
|
158
|
+
handleError("Failed to create package.")
|
|
159
|
+
},
|
|
160
|
+
onSettled: () => {
|
|
161
|
+
setIsLoading(false)
|
|
162
|
+
onOpenChange(false)
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
)
|
|
114
166
|
} catch (error) {
|
|
115
167
|
console.error("Error importing Circuit Json:", error)
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
variant: "destructive",
|
|
120
|
-
})
|
|
168
|
+
handleError(
|
|
169
|
+
"The Circuit JSON appears to be invalid or malformed. Please check the format and try again.",
|
|
170
|
+
)
|
|
121
171
|
} finally {
|
|
122
172
|
setIsLoading(false)
|
|
123
173
|
}
|
|
124
174
|
}
|
|
125
175
|
|
|
176
|
+
const handleKeyDown = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
|
177
|
+
if (event.ctrlKey && event.key === "Enter") {
|
|
178
|
+
handleImport()
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
126
182
|
return (
|
|
127
183
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
128
184
|
<DialogContent>
|
|
129
185
|
<DialogHeader>
|
|
130
186
|
<DialogTitle>Import Circuit JSON</DialogTitle>
|
|
187
|
+
<DialogDescription>
|
|
188
|
+
Use this dialog to import a Circuit JSON file or paste the JSON
|
|
189
|
+
content directly.
|
|
190
|
+
</DialogDescription>
|
|
131
191
|
</DialogHeader>
|
|
132
192
|
<div className="pb-4">
|
|
133
193
|
<Textarea
|
|
@@ -135,6 +195,7 @@ export function CircuitJsonImportDialog({
|
|
|
135
195
|
placeholder="Paste the Circuit JSON."
|
|
136
196
|
value={circuitJson}
|
|
137
197
|
onChange={(e) => setcircuitJson(e.target.value)}
|
|
198
|
+
onKeyDown={handleKeyDown}
|
|
138
199
|
disabled={!!file}
|
|
139
200
|
/>
|
|
140
201
|
<div className="mt-4 flex flex-col gap-2">
|
|
@@ -150,7 +211,7 @@ export function CircuitJsonImportDialog({
|
|
|
150
211
|
type="file"
|
|
151
212
|
accept="application/json"
|
|
152
213
|
onChange={handleFileChange}
|
|
153
|
-
className="hidden"
|
|
214
|
+
className="hidden"
|
|
154
215
|
/>
|
|
155
216
|
<label
|
|
156
217
|
htmlFor="file-input"
|
|
@@ -67,7 +67,7 @@ export const HeaderLogin = () => {
|
|
|
67
67
|
</a>
|
|
68
68
|
</DropdownMenuItem>
|
|
69
69
|
<DropdownMenuItem asChild onClick={() => setSession(null)}>
|
|
70
|
-
<a href="/
|
|
70
|
+
<a href="/" className="cursor-pointer">
|
|
71
71
|
Sign out
|
|
72
72
|
</a>
|
|
73
73
|
</DropdownMenuItem>
|
|
@@ -168,7 +168,7 @@ export default function ImportantFilesView({
|
|
|
168
168
|
return (
|
|
169
169
|
<div className="mt-4 border border-gray-200 dark:border-[#30363d] rounded-md overflow-hidden">
|
|
170
170
|
<div className="flex items-center pl-2 pr-4 py-2 bg-gray-100 dark:bg-[#161b22] border-b border-gray-200 dark:border-[#30363d]">
|
|
171
|
-
<div className="flex items-center space-x-2">
|
|
171
|
+
<div className="flex items-center space-x-2 overflow-x-auto no-scrollbar">
|
|
172
172
|
{/* AI Description Tab */}
|
|
173
173
|
{hasAiContent && (
|
|
174
174
|
<button
|
|
@@ -136,16 +136,24 @@ export const CodeEditorHeader: React.FC<CodeEditorHeaderProps> = ({
|
|
|
136
136
|
<>
|
|
137
137
|
<div className="flex items-center gap-2 px-2 border-b border-gray-200">
|
|
138
138
|
<button
|
|
139
|
-
className={`text-gray-400 scale-90 transition-opacity duration-
|
|
140
|
-
sidebarOpen
|
|
139
|
+
className={`text-gray-400 scale-90 p-0 transition-[width,opacity] duration-300 ease-in-out overflow-hidden ${
|
|
140
|
+
sidebarOpen
|
|
141
|
+
? "w-0 pointer-events-none opacity-0"
|
|
142
|
+
: "w-6 opacity-100"
|
|
141
143
|
}`}
|
|
142
144
|
onClick={() => setSidebarOpen(true)}
|
|
143
145
|
>
|
|
144
|
-
<
|
|
146
|
+
<div className="w-6 h-6 flex items-center justify-center">
|
|
147
|
+
<PanelRightClose />
|
|
148
|
+
</div>
|
|
145
149
|
</button>
|
|
146
150
|
<div>
|
|
147
151
|
<Select value={currentFile} onValueChange={handleFileChange}>
|
|
148
|
-
<SelectTrigger
|
|
152
|
+
<SelectTrigger
|
|
153
|
+
className={`h-7 px-3 bg-white select-none transition-[margin] duration-300 ease-in-out ${
|
|
154
|
+
sidebarOpen ? "-ml-2" : "-ml-1"
|
|
155
|
+
}`}
|
|
156
|
+
>
|
|
149
157
|
<SelectValue placeholder="Select file" />
|
|
150
158
|
</SelectTrigger>
|
|
151
159
|
<SelectContent>
|
|
@@ -324,7 +324,7 @@ export default function EditorNav({
|
|
|
324
324
|
<DownloadButtonAndMenu
|
|
325
325
|
snippetUnscopedName={pkg?.unscoped_name}
|
|
326
326
|
circuitJson={circuitJson}
|
|
327
|
-
className="
|
|
327
|
+
className="flex"
|
|
328
328
|
/>
|
|
329
329
|
<Button
|
|
330
330
|
variant="ghost"
|
|
@@ -464,17 +464,42 @@ export default function EditorNav({
|
|
|
464
464
|
</div>
|
|
465
465
|
</DropdownMenuTrigger>
|
|
466
466
|
<DropdownMenuContent>
|
|
467
|
-
<DropdownMenuItem
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
467
|
+
<DropdownMenuItem
|
|
468
|
+
className="text-xs"
|
|
469
|
+
onClick={() => {
|
|
470
|
+
if (window) {
|
|
471
|
+
navigator.clipboard.writeText(
|
|
472
|
+
new URL(window.location.href).origin + "/" + pkg?.name,
|
|
473
|
+
)
|
|
474
|
+
toast({
|
|
475
|
+
title: "URL copied!",
|
|
476
|
+
description: "The URL has been copied to your clipboard.",
|
|
477
|
+
})
|
|
478
|
+
}
|
|
479
|
+
}}
|
|
480
|
+
>
|
|
472
481
|
<Share className="mr-1 h-3 w-3" />
|
|
473
482
|
Copy URL
|
|
474
483
|
</DropdownMenuItem>
|
|
475
|
-
<DropdownMenuItem
|
|
484
|
+
<DropdownMenuItem
|
|
485
|
+
className="text-xs"
|
|
486
|
+
onClick={() => {
|
|
487
|
+
if (
|
|
488
|
+
pkg &&
|
|
489
|
+
session?.github_username === pkg.owner_github_username
|
|
490
|
+
) {
|
|
491
|
+
updatePackageVisibilityToPrivate(!isPrivate)
|
|
492
|
+
}
|
|
493
|
+
}}
|
|
494
|
+
>
|
|
476
495
|
<Eye className="mr-1 h-3 w-3" />
|
|
477
|
-
|
|
496
|
+
{session?.github_username === pkg?.owner_github_username
|
|
497
|
+
? isPrivate
|
|
498
|
+
? "Make Public"
|
|
499
|
+
: "Make Private"
|
|
500
|
+
: isPrivate
|
|
501
|
+
? "Private Package"
|
|
502
|
+
: "Public Package"}
|
|
478
503
|
</DropdownMenuItem>
|
|
479
504
|
</DropdownMenuContent>
|
|
480
505
|
</DropdownMenu>
|
|
@@ -7,8 +7,6 @@ import { useUrlParams } from "./use-url-params"
|
|
|
7
7
|
export const useCreatePackageMutation = ({
|
|
8
8
|
onSuccess,
|
|
9
9
|
}: { onSuccess?: (pkg: Package) => void } = {}) => {
|
|
10
|
-
const urlParams = useUrlParams()
|
|
11
|
-
const templateName = urlParams.template
|
|
12
10
|
const axios = useAxios()
|
|
13
11
|
const session = useGlobalStore((s) => s.session)
|
|
14
12
|
|
|
@@ -10,13 +10,14 @@ import { useGetFsMapHashForPackage } from "@/hooks/use-get-fsmap-hash-for-packag
|
|
|
10
10
|
|
|
11
11
|
export const EditorPage = () => {
|
|
12
12
|
const { packageId } = useCurrentPackageId()
|
|
13
|
-
const { data: pkg,
|
|
13
|
+
const { data: pkg, error } = usePackage(packageId)
|
|
14
14
|
const fsMapHash = useGetFsMapHashForPackage(
|
|
15
15
|
pkg?.latest_package_release_id ?? "",
|
|
16
16
|
)
|
|
17
17
|
const uuid4RegExp = new RegExp(
|
|
18
18
|
/^[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}$/,
|
|
19
19
|
)
|
|
20
|
+
|
|
20
21
|
return (
|
|
21
22
|
<div className="overflow-x-hidden">
|
|
22
23
|
<Helmet>
|
package/src/pages/quickstart.tsx
CHANGED
|
@@ -166,7 +166,7 @@ export const QuickstartPage = () => {
|
|
|
166
166
|
<CardHeader className="p-4 pb-0">
|
|
167
167
|
<CardTitle className="text-lg flex items-center justify-between">
|
|
168
168
|
Circuit Json
|
|
169
|
-
<TypeBadge type="
|
|
169
|
+
<TypeBadge type="package" className="ml-2" />
|
|
170
170
|
</CardTitle>
|
|
171
171
|
</CardHeader>
|
|
172
172
|
<CardContent className="p-4 mt-auto">
|
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
import RepoPageContent from "@/components/ViewPackagePage/components/repo-page-content"
|
|
2
|
-
import { useCurrentPackageInfo } from "@/hooks/use-current-package-info"
|
|
3
2
|
import { usePackageFiles } from "@/hooks/use-package-files"
|
|
4
3
|
import { usePackageRelease } from "@/hooks/use-package-release"
|
|
5
4
|
import { useLocation, useParams } from "wouter"
|
|
6
5
|
import { Helmet } from "react-helmet-async"
|
|
7
6
|
import { useEffect, useState } from "react"
|
|
8
7
|
import NotFoundPage from "./404"
|
|
8
|
+
import { useCurrentPackageId } from "@/hooks/use-current-package-id"
|
|
9
|
+
import { usePackage } from "@/hooks/use-package"
|
|
9
10
|
|
|
10
11
|
export const ViewPackagePage = () => {
|
|
11
|
-
const {
|
|
12
|
+
const {
|
|
13
|
+
packageId,
|
|
14
|
+
error: packageIdError,
|
|
15
|
+
isLoading: isLoadingPackageId,
|
|
16
|
+
} = useCurrentPackageId()
|
|
17
|
+
const { data: packageInfo } = usePackage(packageId)
|
|
12
18
|
const { author, packageName } = useParams()
|
|
13
19
|
const [, setLocation] = useLocation()
|
|
14
|
-
const [isNotFound, setIsNotFound] = useState(false)
|
|
15
|
-
|
|
16
20
|
const {
|
|
17
21
|
data: packageRelease,
|
|
18
22
|
error: packageReleaseError,
|
|
@@ -25,14 +29,12 @@ export const ViewPackagePage = () => {
|
|
|
25
29
|
const { data: packageFiles } = usePackageFiles(
|
|
26
30
|
packageRelease?.package_release_id,
|
|
27
31
|
)
|
|
28
|
-
useEffect(() => {
|
|
29
|
-
if (isLoadingPackageRelease) return
|
|
30
|
-
if (packageReleaseError?.status == 404) {
|
|
31
|
-
setIsNotFound(true)
|
|
32
|
-
}
|
|
33
|
-
}, [isLoadingPackageRelease, packageReleaseError])
|
|
34
32
|
|
|
35
|
-
if (
|
|
33
|
+
if (!isLoadingPackageId && packageIdError) {
|
|
34
|
+
return <NotFoundPage heading="Package Not Found" />
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!isLoadingPackageRelease && packageReleaseError?.status == 404) {
|
|
36
38
|
return <NotFoundPage heading="Package Not Found" />
|
|
37
39
|
}
|
|
38
40
|
|