@tscircuit/fake-snippets 0.0.83 → 0.0.84
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/README.md +5 -2
- package/bun-tests/fake-snippets-api/routes/ai_reviews/create.test.ts +12 -0
- package/bun-tests/fake-snippets-api/routes/ai_reviews/get.test.ts +16 -0
- package/bun-tests/fake-snippets-api/routes/ai_reviews/list.test.ts +14 -0
- package/bun-tests/fake-snippets-api/routes/ai_reviews/process_review.test.ts +16 -0
- package/bun.lock +26 -37
- package/dist/bundle.js +557 -422
- package/dist/index.d.ts +62 -10
- package/dist/index.js +45 -1
- package/dist/schema.d.ts +82 -13
- package/dist/schema.js +12 -1
- package/fake-snippets-api/lib/db/db-client.ts +40 -0
- package/fake-snippets-api/lib/db/schema.ts +12 -0
- package/fake-snippets-api/routes/api/_fake/ai_reviews/process_review.ts +31 -0
- package/fake-snippets-api/routes/api/ai_reviews/create.ts +22 -0
- package/fake-snippets-api/routes/api/ai_reviews/get.ts +24 -0
- package/fake-snippets-api/routes/api/ai_reviews/list.ts +14 -0
- package/package.json +4 -3
- package/src/ContextProviders.tsx +1 -1
- package/src/components/Header2.tsx +8 -18
- package/src/components/SearchComponent.tsx +46 -8
- package/src/components/ViewSnippetHeader.tsx +9 -6
- package/src/components/dialogs/edit-package-details-dialog.tsx +5 -10
- package/src/hooks/use-fork-package-mutation.ts +4 -3
- package/src/hooks/use-sign-in.ts +10 -8
- package/src/hooks/useFileManagement.ts +74 -12
- package/src/hooks/useForkPackageMutation.ts +2 -1
- package/src/hooks/useForkSnippetMutation.ts +2 -1
- package/src/pages/authorize.tsx +164 -8
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec"
|
|
2
|
+
import { z } from "zod"
|
|
3
|
+
import { aiReviewSchema } from "fake-snippets-api/lib/db/schema"
|
|
4
|
+
|
|
5
|
+
export default withRouteSpec({
|
|
6
|
+
methods: ["POST"],
|
|
7
|
+
auth: "session",
|
|
8
|
+
jsonResponse: z.object({
|
|
9
|
+
ai_review: aiReviewSchema,
|
|
10
|
+
}),
|
|
11
|
+
})(async (req, ctx) => {
|
|
12
|
+
const ai_review = ctx.db.addAiReview({
|
|
13
|
+
ai_review_text: null,
|
|
14
|
+
start_processing_at: null,
|
|
15
|
+
finished_processing_at: null,
|
|
16
|
+
processing_error: null,
|
|
17
|
+
created_at: new Date().toISOString(),
|
|
18
|
+
display_status: "pending",
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
return ctx.json({ ai_review })
|
|
22
|
+
})
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec"
|
|
2
|
+
import { z } from "zod"
|
|
3
|
+
import { aiReviewSchema } from "fake-snippets-api/lib/db/schema"
|
|
4
|
+
|
|
5
|
+
export default withRouteSpec({
|
|
6
|
+
methods: ["GET"],
|
|
7
|
+
auth: "session",
|
|
8
|
+
queryParams: z.object({
|
|
9
|
+
ai_review_id: z.string(),
|
|
10
|
+
}),
|
|
11
|
+
jsonResponse: z.object({
|
|
12
|
+
ai_review: aiReviewSchema,
|
|
13
|
+
}),
|
|
14
|
+
})(async (req, ctx) => {
|
|
15
|
+
const { ai_review_id } = req.query
|
|
16
|
+
const ai_review = ctx.db.getAiReviewById(ai_review_id)
|
|
17
|
+
if (!ai_review) {
|
|
18
|
+
return ctx.error(404, {
|
|
19
|
+
error_code: "ai_review_not_found",
|
|
20
|
+
message: "AI review not found",
|
|
21
|
+
})
|
|
22
|
+
}
|
|
23
|
+
return ctx.json({ ai_review })
|
|
24
|
+
})
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec"
|
|
2
|
+
import { z } from "zod"
|
|
3
|
+
import { aiReviewSchema } from "fake-snippets-api/lib/db/schema"
|
|
4
|
+
|
|
5
|
+
export default withRouteSpec({
|
|
6
|
+
methods: ["GET"],
|
|
7
|
+
auth: "session",
|
|
8
|
+
jsonResponse: z.object({
|
|
9
|
+
ai_reviews: z.array(aiReviewSchema),
|
|
10
|
+
}),
|
|
11
|
+
})(async (req, ctx) => {
|
|
12
|
+
const ai_reviews = ctx.db.listAiReviews()
|
|
13
|
+
return ctx.json({ ai_reviews })
|
|
14
|
+
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tscircuit/fake-snippets",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.84",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
"circuit-json-to-bom-csv": "^0.0.6",
|
|
87
87
|
"circuit-json-to-gerber": "^0.0.21",
|
|
88
88
|
"circuit-json-to-pnp-csv": "^0.0.6",
|
|
89
|
-
"circuit-json-to-readable-netlist": "^0.0.
|
|
89
|
+
"circuit-json-to-readable-netlist": "^0.0.13",
|
|
90
90
|
"circuit-json-to-tscircuit": "^0.0.4",
|
|
91
91
|
"class-variance-authority": "^0.7.1",
|
|
92
92
|
"clsx": "^2.1.1",
|
|
@@ -145,8 +145,9 @@
|
|
|
145
145
|
"@playwright/test": "^1.48.0",
|
|
146
146
|
"@tailwindcss/typography": "^0.5.16",
|
|
147
147
|
"@tscircuit/core": "^0.0.433",
|
|
148
|
+
"@tscircuit/eval": "^0.0.227",
|
|
148
149
|
"@tscircuit/prompt-benchmarks": "^0.0.28",
|
|
149
|
-
"@tscircuit/runframe": "^0.0.
|
|
150
|
+
"@tscircuit/runframe": "^0.0.578",
|
|
150
151
|
"@types/babel__standalone": "^7.1.7",
|
|
151
152
|
"@types/bun": "^1.1.10",
|
|
152
153
|
"@types/country-list": "^2.1.4",
|
package/src/ContextProviders.tsx
CHANGED
|
@@ -15,18 +15,15 @@ const SearchButtonComponent = () => {
|
|
|
15
15
|
return (
|
|
16
16
|
<div className="relative">
|
|
17
17
|
{isExpanded ? (
|
|
18
|
-
<div className="flex items-center gap-2">
|
|
19
|
-
<div className="
|
|
20
|
-
<SearchComponent
|
|
18
|
+
<div className="flex items-center gap-2 ml-8">
|
|
19
|
+
<div className="absolute -top-4 right-3 bg-white">
|
|
20
|
+
<SearchComponent
|
|
21
|
+
autofocus
|
|
22
|
+
closeOnClick={() => {
|
|
23
|
+
setIsExpanded(false)
|
|
24
|
+
}}
|
|
25
|
+
/>
|
|
21
26
|
</div>
|
|
22
|
-
{/* <Button
|
|
23
|
-
variant="ghost"
|
|
24
|
-
size="icon"
|
|
25
|
-
onClick={() => setIsExpanded(false)}
|
|
26
|
-
className="h-8 w-8"
|
|
27
|
-
>
|
|
28
|
-
<X className="h-4 w-4" />
|
|
29
|
-
</Button> */}
|
|
30
27
|
</div>
|
|
31
28
|
) : (
|
|
32
29
|
<>
|
|
@@ -58,13 +55,6 @@ export const Header2 = () => {
|
|
|
58
55
|
const isLoggedIn = useGlobalStore((state) => Boolean(state.session))
|
|
59
56
|
return (
|
|
60
57
|
<>
|
|
61
|
-
{/* <div className="absolute left-0 top-0 z-[9999999]">
|
|
62
|
-
<div className="hidden xl:block">xl</div>
|
|
63
|
-
<div className="hidden lg:block xl:hidden">lg</div>
|
|
64
|
-
<div className="hidden md:block lg:hidden">md</div>
|
|
65
|
-
<div className="hidden sm:block md:hidden">sm</div>
|
|
66
|
-
<div className="hidden xs:block sm:hidden">xs</div>
|
|
67
|
-
</div> */}
|
|
68
58
|
<header className="sticky top-0 z-50 w-full border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
|
69
59
|
<div className="container mx-auto flex h-16 items-center justify-between px-2 md:px-6">
|
|
70
60
|
<div className="flex items-center gap-2">
|
|
@@ -6,10 +6,12 @@ import { useQuery } from "react-query"
|
|
|
6
6
|
import { Alert } from "./ui/alert"
|
|
7
7
|
import { useSnippetsBaseApiUrl } from "@/hooks/use-snippets-base-api-url"
|
|
8
8
|
import { PrefetchPageLink } from "./PrefetchPageLink"
|
|
9
|
+
import { CircuitBoard } from "lucide-react"
|
|
9
10
|
|
|
10
11
|
interface SearchComponentProps {
|
|
11
12
|
onResultsFetched?: (results: any[]) => void
|
|
12
13
|
autofocus?: boolean
|
|
14
|
+
closeOnClick?: () => void
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
const LinkWithNewTabHandling = ({
|
|
@@ -45,13 +47,14 @@ const LinkWithNewTabHandling = ({
|
|
|
45
47
|
const SearchComponent: React.FC<SearchComponentProps> = ({
|
|
46
48
|
onResultsFetched,
|
|
47
49
|
autofocus = false,
|
|
50
|
+
closeOnClick,
|
|
48
51
|
}) => {
|
|
49
52
|
const [searchQuery, setSearchQuery] = useState("")
|
|
50
53
|
const [showResults, setShowResults] = useState(false)
|
|
51
54
|
const axios = useAxios()
|
|
52
55
|
const resultsRef = useRef<HTMLDivElement>(null)
|
|
53
56
|
const inputRef = useRef<HTMLInputElement>(null)
|
|
54
|
-
const [location] = useLocation()
|
|
57
|
+
const [location, setLocation] = useLocation()
|
|
55
58
|
const snippetsBaseApiUrl = useSnippetsBaseApiUrl()
|
|
56
59
|
|
|
57
60
|
const { data: searchResults, isLoading } = useQuery(
|
|
@@ -71,7 +74,9 @@ const SearchComponent: React.FC<SearchComponentProps> = ({
|
|
|
71
74
|
|
|
72
75
|
const handleSearch = (e: React.FormEvent) => {
|
|
73
76
|
e.preventDefault()
|
|
74
|
-
|
|
77
|
+
if (searchQuery.trim()) {
|
|
78
|
+
setLocation(`/search?q=${encodeURIComponent(searchQuery.trim())}`)
|
|
79
|
+
}
|
|
75
80
|
}
|
|
76
81
|
|
|
77
82
|
// Focus input on mount
|
|
@@ -91,18 +96,31 @@ const SearchComponent: React.FC<SearchComponentProps> = ({
|
|
|
91
96
|
}
|
|
92
97
|
}
|
|
93
98
|
|
|
99
|
+
const handleEscapeKey = (event: KeyboardEvent) => {
|
|
100
|
+
if (event.key === "Escape") {
|
|
101
|
+
setShowResults(false)
|
|
102
|
+
if (closeOnClick) {
|
|
103
|
+
closeOnClick()
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
94
108
|
document.addEventListener("mousedown", handleClickOutside)
|
|
109
|
+
document.addEventListener("keydown", handleEscapeKey)
|
|
95
110
|
return () => {
|
|
96
111
|
document.removeEventListener("mousedown", handleClickOutside)
|
|
112
|
+
document.removeEventListener("keydown", handleEscapeKey)
|
|
97
113
|
}
|
|
98
|
-
}, [])
|
|
114
|
+
}, [closeOnClick])
|
|
99
115
|
|
|
100
116
|
const shouldOpenInNewTab = location === "/editor" || location === "/ai"
|
|
101
117
|
const shouldOpenInEditor = location === "/editor" || location === "/ai"
|
|
102
118
|
|
|
103
119
|
return (
|
|
104
|
-
<form onSubmit={handleSearch} className="relative">
|
|
120
|
+
<form onSubmit={handleSearch} autoComplete="off" className="relative w-44">
|
|
105
121
|
<Input
|
|
122
|
+
autoComplete="off"
|
|
123
|
+
spellCheck={false}
|
|
106
124
|
ref={inputRef}
|
|
107
125
|
type="search"
|
|
108
126
|
placeholder="Search"
|
|
@@ -112,12 +130,20 @@ const SearchComponent: React.FC<SearchComponentProps> = ({
|
|
|
112
130
|
setSearchQuery(e.target.value)
|
|
113
131
|
setShowResults(!!e.target.value)
|
|
114
132
|
}}
|
|
133
|
+
onKeyDown={(e) => {
|
|
134
|
+
if (e.key === "Backspace" && !searchQuery && closeOnClick) {
|
|
135
|
+
closeOnClick()
|
|
136
|
+
}
|
|
137
|
+
}}
|
|
115
138
|
aria-label="Search packages"
|
|
116
139
|
role="searchbox"
|
|
117
140
|
/>
|
|
118
141
|
{isLoading && (
|
|
119
|
-
<div className="absolute top-full left-0 right-0 mt-
|
|
120
|
-
<
|
|
142
|
+
<div className="absolute top-full w-lg left-0 right-0 mt-1 bg-white shadow-lg rounded-lg border w-80 grid place-items-center py-4 z-10 p-3">
|
|
143
|
+
<div className="flex items-center space-x-2">
|
|
144
|
+
<div className="w-4 h-4 border-2 border-blue-500 border-t-transparent rounded-full animate-spin"></div>
|
|
145
|
+
<span className="text-gray-600 text-sm">Searching...</span>
|
|
146
|
+
</div>
|
|
121
147
|
</div>
|
|
122
148
|
)}
|
|
123
149
|
|
|
@@ -139,12 +165,24 @@ const SearchComponent: React.FC<SearchComponentProps> = ({
|
|
|
139
165
|
shouldOpenInNewTab={shouldOpenInNewTab}
|
|
140
166
|
className="flex"
|
|
141
167
|
>
|
|
142
|
-
<div className="w-12 h-12 overflow-hidden mr-2 flex-shrink-0 rounded-sm">
|
|
168
|
+
<div className="w-12 h-12 overflow-hidden mr-2 flex-shrink-0 rounded-sm bg-gray-50 border flex items-center justify-center">
|
|
143
169
|
<img
|
|
144
170
|
src={`${snippetsBaseApiUrl}/snippets/images/${pkg.name}/pcb.svg`}
|
|
145
171
|
alt={`PCB preview for ${pkg.name}`}
|
|
146
172
|
className="w-12 h-12 object-contain p-1 scale-[4] rotate-45"
|
|
173
|
+
onError={(e) => {
|
|
174
|
+
e.currentTarget.style.display = "none"
|
|
175
|
+
e.currentTarget.nextElementSibling?.classList.remove(
|
|
176
|
+
"hidden",
|
|
177
|
+
)
|
|
178
|
+
e.currentTarget.nextElementSibling?.classList.add(
|
|
179
|
+
"flex",
|
|
180
|
+
)
|
|
181
|
+
}}
|
|
147
182
|
/>
|
|
183
|
+
<div className="w-12 h-12 hidden items-center justify-center">
|
|
184
|
+
<CircuitBoard className="w-6 h-6 text-gray-300" />
|
|
185
|
+
</div>
|
|
148
186
|
</div>
|
|
149
187
|
<div className="flex-grow">
|
|
150
188
|
<div className="font-medium text-blue-600 break-words text-xs">
|
|
@@ -161,7 +199,7 @@ const SearchComponent: React.FC<SearchComponentProps> = ({
|
|
|
161
199
|
))}
|
|
162
200
|
</ul>
|
|
163
201
|
) : (
|
|
164
|
-
<Alert variant="default" className="p-4">
|
|
202
|
+
<Alert variant="default" className="p-4 text-center">
|
|
165
203
|
No results found for "{searchQuery}"
|
|
166
204
|
</Alert>
|
|
167
205
|
)}
|
|
@@ -63,18 +63,21 @@ export default function ViewSnippetHeader() {
|
|
|
63
63
|
onSuccess?.(forkedSnippet)
|
|
64
64
|
},
|
|
65
65
|
onError: (error: any) => {
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
const message =
|
|
67
|
+
error?.data?.error?.message ||
|
|
68
|
+
error.message ||
|
|
69
|
+
"Failed to fork snippet. Please try again."
|
|
70
|
+
if (message.includes("already forked")) {
|
|
68
71
|
toast({
|
|
69
72
|
title: "Snippet already exists",
|
|
70
|
-
description:
|
|
71
|
-
variant: "destructive",
|
|
73
|
+
description: message,
|
|
74
|
+
variant: "destructive",
|
|
72
75
|
})
|
|
73
76
|
} else {
|
|
74
77
|
toast({
|
|
75
78
|
title: "Error",
|
|
76
|
-
description:
|
|
77
|
-
variant: "destructive",
|
|
79
|
+
description: message,
|
|
80
|
+
variant: "destructive",
|
|
78
81
|
})
|
|
79
82
|
}
|
|
80
83
|
console.error("Error forking snippet:", error)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { useState } from "react"
|
|
2
2
|
import {
|
|
3
3
|
Select,
|
|
4
4
|
SelectContent,
|
|
@@ -110,12 +110,12 @@ export const EditPackageDetailsDialog = ({
|
|
|
110
110
|
|
|
111
111
|
const response = await axios.post("/packages/update", {
|
|
112
112
|
package_id: packageId,
|
|
113
|
-
description: formData.description,
|
|
114
|
-
website: formData.website,
|
|
113
|
+
description: formData.description.trim(),
|
|
114
|
+
website: formData.website.trim(),
|
|
115
115
|
is_private: formData.visibility == "private",
|
|
116
116
|
default_view: formData.defaultView,
|
|
117
117
|
...(formData.unscopedPackageName !== unscopedPackageName && {
|
|
118
|
-
name: formData.unscopedPackageName,
|
|
118
|
+
name: formData.unscopedPackageName.trim(),
|
|
119
119
|
}),
|
|
120
120
|
})
|
|
121
121
|
if (response.status !== 200)
|
|
@@ -148,12 +148,7 @@ export const EditPackageDetailsDialog = ({
|
|
|
148
148
|
})
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
|
-
|
|
152
|
-
"formData.unscopedPackageName",
|
|
153
|
-
formData.unscopedPackageName,
|
|
154
|
-
"unscopedPackageName",
|
|
155
|
-
unscopedPackageName,
|
|
156
|
-
)
|
|
151
|
+
|
|
157
152
|
if (formData.unscopedPackageName !== unscopedPackageName) {
|
|
158
153
|
// Use router for client-side navigation
|
|
159
154
|
window.history.replaceState(
|
|
@@ -42,16 +42,17 @@ export const useForkPackageMutation = ({
|
|
|
42
42
|
onSuccess?.(result)
|
|
43
43
|
},
|
|
44
44
|
onError: (error: any) => {
|
|
45
|
-
|
|
45
|
+
const message = error?.data?.error?.message
|
|
46
|
+
if (error?.data?.error_code === "cannot_fork_own_package") {
|
|
46
47
|
toast({
|
|
47
48
|
title: "Cannot Fork Package",
|
|
48
|
-
description: "You cannot fork your own package.",
|
|
49
|
+
description: message || "You cannot fork your own package.",
|
|
49
50
|
})
|
|
50
51
|
return
|
|
51
52
|
}
|
|
52
53
|
toast({
|
|
53
54
|
title: "Error",
|
|
54
|
-
description: "Failed to fork package. Please try again.",
|
|
55
|
+
description: message || "Failed to fork package. Please try again.",
|
|
55
56
|
variant: "destructive",
|
|
56
57
|
})
|
|
57
58
|
},
|
package/src/hooks/use-sign-in.ts
CHANGED
|
@@ -7,16 +7,18 @@ export const useSignIn = () => {
|
|
|
7
7
|
const isUsingFakeApi = useIsUsingFakeApi()
|
|
8
8
|
const setSession = useGlobalStore((s) => s.setSession)
|
|
9
9
|
return () => {
|
|
10
|
+
const currentUrl = window.location.href.replace("127.0.0.1", "localhost")
|
|
11
|
+
const nextUrl = `${window.location.origin.replace("127.0.0.1", "localhost")}/authorize?redirect=${encodeURIComponent(currentUrl)}`
|
|
10
12
|
if (!isUsingFakeApi) {
|
|
11
|
-
|
|
12
|
-
window.location.href = `${snippetsBaseApiUrl}/internal/oauth/github/authorize?next=${nextUrl}/authorize`
|
|
13
|
+
window.location.href = `${snippetsBaseApiUrl}/internal/oauth/github/authorize?next=${encodeURIComponent(nextUrl)}`
|
|
13
14
|
} else {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
window.location.href = nextUrl
|
|
16
|
+
// setSession({
|
|
17
|
+
// account_id: "account-1234",
|
|
18
|
+
// github_username: "testuser",
|
|
19
|
+
// token: "1234",
|
|
20
|
+
// session_id: "session-1234",
|
|
21
|
+
// })
|
|
20
22
|
}
|
|
21
23
|
}
|
|
22
24
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect, useMemo, useState, useCallback } from "react"
|
|
1
|
+
import { useEffect, useMemo, useState, useCallback, useRef } from "react"
|
|
2
2
|
import { isValidFileName } from "@/lib/utils/isValidFileName"
|
|
3
3
|
import {
|
|
4
4
|
DEFAULT_CODE,
|
|
@@ -6,11 +6,7 @@ import {
|
|
|
6
6
|
PackageFile,
|
|
7
7
|
} from "../components/package-port/CodeAndPreview"
|
|
8
8
|
import { Package } from "fake-snippets-api/lib/db/schema"
|
|
9
|
-
import {
|
|
10
|
-
usePackageFile,
|
|
11
|
-
usePackageFileById,
|
|
12
|
-
usePackageFiles,
|
|
13
|
-
} from "./use-package-files"
|
|
9
|
+
import { usePackageFiles } from "./use-package-files"
|
|
14
10
|
import { decodeUrlHashToText } from "@/lib/decodeUrlHashToText"
|
|
15
11
|
import { usePackageFilesLoader } from "./usePackageFilesLoader"
|
|
16
12
|
import { useGlobalStore } from "./use-global-store"
|
|
@@ -19,6 +15,7 @@ import { useUpdatePackageFilesMutation } from "./useUpdatePackageFilesMutation"
|
|
|
19
15
|
import { useCreatePackageReleaseMutation } from "./use-create-package-release-mutation"
|
|
20
16
|
import { useCreatePackageMutation } from "./use-create-package-mutation"
|
|
21
17
|
import { findTargetFile } from "@/lib/utils/findTargetFile"
|
|
18
|
+
import { createSnippetUrl } from "@tscircuit/create-snippet-url"
|
|
22
19
|
|
|
23
20
|
export interface ICreateFileProps {
|
|
24
21
|
newFileName: string
|
|
@@ -55,17 +52,19 @@ export function useFileManagement({
|
|
|
55
52
|
const isLoggedIn = useGlobalStore((s) => Boolean(s.session))
|
|
56
53
|
const loggedInUser = useGlobalStore((s) => s.session)
|
|
57
54
|
const { toast } = useToast()
|
|
55
|
+
const debounceTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
|
58
56
|
const {
|
|
59
57
|
data: packageFilesWithContent,
|
|
60
58
|
isLoading: isLoadingPackageFilesWithContent,
|
|
61
59
|
} = usePackageFilesLoader(currentPackage)
|
|
62
60
|
const { data: packageFilesMeta, isLoading: isLoadingPackageFiles } =
|
|
63
61
|
usePackageFiles(currentPackage?.latest_package_release_id)
|
|
64
|
-
|
|
65
62
|
const initialCodeContent = useMemo(() => {
|
|
66
63
|
return (
|
|
67
|
-
|
|
68
|
-
|
|
64
|
+
(!!decodeUrlHashToText(window.location.toString()) &&
|
|
65
|
+
decodeUrlHashToText(window.location.toString()) !== ""
|
|
66
|
+
? decodeUrlHashToText(window.location.toString())
|
|
67
|
+
: templateCode) || DEFAULT_CODE
|
|
69
68
|
)
|
|
70
69
|
}, [templateCode, currentPackage])
|
|
71
70
|
const manualEditsFileContent = useMemo(() => {
|
|
@@ -193,7 +192,8 @@ export function useFileManagement({
|
|
|
193
192
|
{ path: newFileName, content: "" },
|
|
194
193
|
]
|
|
195
194
|
setLocalFiles(updatedFiles)
|
|
196
|
-
|
|
195
|
+
// immediately select the newly created file
|
|
196
|
+
setCurrentFile(newFileName)
|
|
197
197
|
return {
|
|
198
198
|
newFileCreated: true,
|
|
199
199
|
}
|
|
@@ -233,14 +233,76 @@ export function useFileManagement({
|
|
|
233
233
|
})
|
|
234
234
|
}
|
|
235
235
|
|
|
236
|
+
const saveToUrl = useCallback(
|
|
237
|
+
(files: PackageFile[]) => {
|
|
238
|
+
if (isLoggedIn || !files.length) return
|
|
239
|
+
|
|
240
|
+
if (debounceTimeoutRef.current) {
|
|
241
|
+
clearTimeout(debounceTimeoutRef.current)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
debounceTimeoutRef.current = setTimeout(() => {
|
|
245
|
+
try {
|
|
246
|
+
const mainFile =
|
|
247
|
+
files.find((f) => f.path === currentFile) ||
|
|
248
|
+
files.find((f) => f.path === "index.tsx") ||
|
|
249
|
+
files[0]
|
|
250
|
+
|
|
251
|
+
if (mainFile.content.length > 50000) return
|
|
252
|
+
|
|
253
|
+
const snippetUrl = createSnippetUrl(mainFile.content)
|
|
254
|
+
if (typeof snippetUrl !== "string") return
|
|
255
|
+
|
|
256
|
+
const currentUrl = new URL(window.location.href)
|
|
257
|
+
const urlParts = snippetUrl.split("#")
|
|
258
|
+
|
|
259
|
+
if (urlParts.length > 1 && urlParts[1]) {
|
|
260
|
+
const newHash = urlParts[1]
|
|
261
|
+
if (newHash.length > 8000) return
|
|
262
|
+
|
|
263
|
+
currentUrl.hash = newHash
|
|
264
|
+
const finalUrl = currentUrl.toString()
|
|
265
|
+
|
|
266
|
+
if (finalUrl.length <= 32000) {
|
|
267
|
+
window.history.replaceState(null, "", finalUrl)
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
} catch (error) {
|
|
271
|
+
console.warn("Failed to save code to URL:", error)
|
|
272
|
+
}
|
|
273
|
+
}, 1000)
|
|
274
|
+
},
|
|
275
|
+
[isLoggedIn, currentFile],
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
useEffect(() => {
|
|
279
|
+
if (!isLoggedIn && localFiles.length > 0) {
|
|
280
|
+
saveToUrl(localFiles)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return () => {
|
|
284
|
+
if (debounceTimeoutRef.current) {
|
|
285
|
+
clearTimeout(debounceTimeoutRef.current)
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}, [localFiles, saveToUrl, isLoggedIn])
|
|
289
|
+
|
|
236
290
|
const saveFiles = () => {
|
|
237
291
|
if (!isLoggedIn) {
|
|
292
|
+
// For non-logged-in users, trigger immediate URL save
|
|
293
|
+
if (debounceTimeoutRef.current) {
|
|
294
|
+
clearTimeout(debounceTimeoutRef.current)
|
|
295
|
+
}
|
|
296
|
+
saveToUrl(localFiles)
|
|
297
|
+
|
|
238
298
|
toast({
|
|
239
|
-
title: "
|
|
240
|
-
description:
|
|
299
|
+
title: "Code Saved to URL",
|
|
300
|
+
description:
|
|
301
|
+
"Your code has been saved to the URL. Bookmark this page to access your code later.",
|
|
241
302
|
})
|
|
242
303
|
return
|
|
243
304
|
}
|
|
305
|
+
|
|
244
306
|
if (!currentPackage) {
|
|
245
307
|
openNewPackageSaveDialog()
|
|
246
308
|
return
|
|
@@ -38,9 +38,10 @@ export const useForkPackageMutation = ({
|
|
|
38
38
|
},
|
|
39
39
|
onError: (error: any) => {
|
|
40
40
|
console.error("Error forking package:", error)
|
|
41
|
+
const message = error?.data?.error?.message
|
|
41
42
|
toast({
|
|
42
43
|
title: "Error",
|
|
43
|
-
description: "Failed to fork package. Please try again.",
|
|
44
|
+
description: message || "Failed to fork package. Please try again.",
|
|
44
45
|
variant: "destructive",
|
|
45
46
|
})
|
|
46
47
|
},
|
|
@@ -41,9 +41,10 @@ export const useForkSnippetMutation = ({
|
|
|
41
41
|
},
|
|
42
42
|
onError: (error: any) => {
|
|
43
43
|
console.error("Error forking snippet:", error)
|
|
44
|
+
const message = error?.data?.error?.message
|
|
44
45
|
toast({
|
|
45
46
|
title: "Error",
|
|
46
|
-
description: "Failed to fork snippet. Please try again.",
|
|
47
|
+
description: message || "Failed to fork snippet. Please try again.",
|
|
47
48
|
variant: "destructive",
|
|
48
49
|
})
|
|
49
50
|
},
|