@tscircuit/fake-snippets 0.0.67 → 0.0.69
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-tests/fake-snippets-api/routes/packages/update.test.ts +104 -0
- package/bun.lock +26 -83
- package/dist/bundle.js +17 -5
- package/dist/index.d.ts +5 -0
- package/dist/index.js +2 -1
- package/dist/schema.d.ts +8 -0
- package/dist/schema.js +2 -1
- package/fake-snippets-api/lib/db/schema.ts +4 -0
- package/fake-snippets-api/routes/api/packages/create.ts +1 -0
- package/fake-snippets-api/routes/api/packages/update.ts +11 -2
- package/package.json +3 -4
- package/src/App.tsx +0 -4
- package/src/components/CmdKMenu.tsx +19 -19
- package/src/components/FAQ.tsx +3 -1
- package/src/components/FileSidebar.tsx +50 -1
- package/src/components/Header2.tsx +20 -9
- package/src/components/JLCPCBImportDialog.tsx +13 -16
- package/src/components/ViewPackagePage/components/important-files-view.tsx +1 -1
- package/src/components/ViewPackagePage/components/mobile-sidebar.tsx +1 -0
- package/src/components/ViewPackagePage/components/package-header.tsx +22 -12
- package/src/components/ViewPackagePage/components/repo-page-content.tsx +23 -7
- package/src/components/ViewPackagePage/components/sidebar-about-section.tsx +1 -0
- package/src/components/dialogs/confirm-delete-package-dialog.tsx +8 -0
- package/src/components/dialogs/edit-package-details-dialog.tsx +177 -139
- package/src/components/package-port/CodeAndPreview.tsx +40 -19
- package/src/components/package-port/CodeEditor.tsx +8 -27
- package/src/components/package-port/EditorNav.tsx +1 -11
- package/src/hooks/use-package-details-form.ts +15 -1
- package/src/hooks/useFileManagement.ts +59 -0
- package/src/index.css +13 -0
- package/src/lib/utils/isValidFileName.ts +5 -0
- package/src/pages/dashboard.tsx +1 -0
- package/src/pages/quickstart.tsx +5 -5
- package/src/pages/search.tsx +1 -1
- package/src/pages/user-profile.tsx +1 -0
- package/src/components/OrderPreviewContent.tsx +0 -61
- package/src/components/ViewSnippetSidebar.tsx +0 -162
- package/src/components/dialogs/create-order-dialog.tsx +0 -146
- package/src/pages/preview.tsx +0 -44
- package/src/pages/view-order.tsx +0 -111
|
@@ -19,6 +19,7 @@ import PackageHeader from "./package-header"
|
|
|
19
19
|
import { useGlobalStore } from "@/hooks/use-global-store"
|
|
20
20
|
import { useLocation } from "wouter"
|
|
21
21
|
import { Package } from "fake-snippets-api/lib/db/schema"
|
|
22
|
+
import { useCurrentPackageCircuitJson } from "../hooks/use-current-package-circuit-json"
|
|
22
23
|
|
|
23
24
|
interface PackageFile {
|
|
24
25
|
package_file_id: string
|
|
@@ -43,22 +44,37 @@ export default function RepoPageContent({
|
|
|
43
44
|
onFileClicked,
|
|
44
45
|
onEditClicked,
|
|
45
46
|
}: RepoPageContentProps) {
|
|
46
|
-
const [location, setLocation] = useLocation()
|
|
47
47
|
const [activeView, setActiveView] = useState<string>("files")
|
|
48
48
|
const session = useGlobalStore((s) => s.session)
|
|
49
|
+
const { circuitJson, isLoading: isCircuitJsonLoading } =
|
|
50
|
+
useCurrentPackageCircuitJson()
|
|
49
51
|
|
|
50
|
-
// Handle hash-based view
|
|
52
|
+
// Handle initial view selection and hash-based view changes
|
|
51
53
|
useEffect(() => {
|
|
52
|
-
|
|
54
|
+
if (isCircuitJsonLoading) return
|
|
53
55
|
const hash = window.location.hash.slice(1)
|
|
54
|
-
// Valid views
|
|
55
56
|
const validViews = ["files", "3d", "pcb", "schematic", "bom"]
|
|
57
|
+
const circuitDependentViews = ["3d", "pcb", "schematic", "bom"]
|
|
56
58
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
+
const availableViews = circuitJson
|
|
60
|
+
? validViews
|
|
61
|
+
: validViews.filter((view) => !circuitDependentViews.includes(view))
|
|
62
|
+
|
|
63
|
+
if (hash && availableViews.includes(hash)) {
|
|
59
64
|
setActiveView(hash)
|
|
65
|
+
} else if (
|
|
66
|
+
packageInfo?.default_view &&
|
|
67
|
+
availableViews.includes(packageInfo.default_view)
|
|
68
|
+
) {
|
|
69
|
+
setActiveView(packageInfo.default_view)
|
|
70
|
+
window.location.hash = packageInfo.default_view
|
|
71
|
+
} else {
|
|
72
|
+
setActiveView("files")
|
|
73
|
+
if (!hash || !availableViews.includes(hash)) {
|
|
74
|
+
window.location.hash = "files"
|
|
75
|
+
}
|
|
60
76
|
}
|
|
61
|
-
}, [])
|
|
77
|
+
}, [packageInfo?.default_view, circuitJson, isCircuitJsonLoading])
|
|
62
78
|
|
|
63
79
|
const importantFilePaths = packageFiles
|
|
64
80
|
?.filter((pf) => isPackageFileImportant(pf.file_path))
|
|
@@ -2,24 +2,32 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle } from "../ui/dialog"
|
|
|
2
2
|
import { Button } from "../ui/button"
|
|
3
3
|
import { createUseDialog } from "./create-use-dialog"
|
|
4
4
|
import { useDeletePackage } from "@/hooks/use-delete-package"
|
|
5
|
+
import { useQueryClient } from "react-query"
|
|
5
6
|
|
|
6
7
|
export const ConfirmDeletePackageDialog = ({
|
|
7
8
|
open,
|
|
8
9
|
onOpenChange,
|
|
9
10
|
packageId,
|
|
10
11
|
packageName,
|
|
12
|
+
packageOwner,
|
|
11
13
|
refetchUserPackages,
|
|
12
14
|
}: {
|
|
13
15
|
open: boolean
|
|
14
16
|
onOpenChange: (open: boolean) => void
|
|
15
17
|
packageId: string
|
|
16
18
|
packageName: string
|
|
19
|
+
packageOwner: string
|
|
17
20
|
refetchUserPackages?: () => void
|
|
18
21
|
}) => {
|
|
22
|
+
const queryClient = useQueryClient()
|
|
23
|
+
|
|
19
24
|
const { mutate: deletePackage, isLoading } = useDeletePackage({
|
|
20
25
|
onSuccess: () => {
|
|
21
26
|
onOpenChange(false)
|
|
22
27
|
refetchUserPackages?.()
|
|
28
|
+
queryClient.invalidateQueries({
|
|
29
|
+
queryKey: ["userPackages", packageOwner],
|
|
30
|
+
})
|
|
23
31
|
},
|
|
24
32
|
})
|
|
25
33
|
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
Dialog,
|
|
17
17
|
DialogContent,
|
|
18
18
|
DialogDescription,
|
|
19
|
+
DialogFooter,
|
|
19
20
|
DialogHeader,
|
|
20
21
|
DialogTitle,
|
|
21
22
|
} from "../ui/dialog"
|
|
@@ -34,6 +35,7 @@ interface EditPackageDetailsDialogProps {
|
|
|
34
35
|
currentDescription: string
|
|
35
36
|
currentWebsite: string
|
|
36
37
|
currentLicense?: string | null
|
|
38
|
+
currentDefaultView?: string
|
|
37
39
|
isPrivate?: boolean
|
|
38
40
|
packageName: string
|
|
39
41
|
packageReleaseId: string | null
|
|
@@ -42,6 +44,7 @@ interface EditPackageDetailsDialogProps {
|
|
|
42
44
|
newDescription: string,
|
|
43
45
|
newWebsite: string,
|
|
44
46
|
newLicense: string | null,
|
|
47
|
+
newDefaultView: string,
|
|
45
48
|
) => void
|
|
46
49
|
}
|
|
47
50
|
|
|
@@ -52,6 +55,7 @@ export const EditPackageDetailsDialog = ({
|
|
|
52
55
|
currentDescription,
|
|
53
56
|
currentWebsite,
|
|
54
57
|
currentLicense,
|
|
58
|
+
currentDefaultView = "files",
|
|
55
59
|
isPrivate = false,
|
|
56
60
|
packageName,
|
|
57
61
|
packageReleaseId,
|
|
@@ -67,12 +71,14 @@ export const EditPackageDetailsDialog = ({
|
|
|
67
71
|
setFormData,
|
|
68
72
|
websiteError,
|
|
69
73
|
hasLicenseChanged,
|
|
74
|
+
hasDefaultViewChanged,
|
|
70
75
|
hasChanges,
|
|
71
76
|
isFormValid,
|
|
72
77
|
} = usePackageDetailsForm({
|
|
73
78
|
initialDescription: currentDescription,
|
|
74
79
|
initialWebsite: currentWebsite,
|
|
75
80
|
initialLicense: currentLicense || null,
|
|
81
|
+
initialDefaultView: currentDefaultView,
|
|
76
82
|
isDialogOpen: open,
|
|
77
83
|
initialVisibility: isPrivate ? "private" : "public",
|
|
78
84
|
})
|
|
@@ -107,6 +113,7 @@ export const EditPackageDetailsDialog = ({
|
|
|
107
113
|
description: formData.description,
|
|
108
114
|
website: formData.website,
|
|
109
115
|
is_private: formData.visibility == "private",
|
|
116
|
+
default_view: formData.defaultView,
|
|
110
117
|
})
|
|
111
118
|
const privacyUpdateResponse = await axios.post("/snippets/update", {
|
|
112
119
|
snippet_id: packageId,
|
|
@@ -148,6 +155,7 @@ export const EditPackageDetailsDialog = ({
|
|
|
148
155
|
website: formData.website,
|
|
149
156
|
license: formData.license,
|
|
150
157
|
visibility: formData.visibility,
|
|
158
|
+
defaultView: formData.defaultView,
|
|
151
159
|
}
|
|
152
160
|
},
|
|
153
161
|
onMutate: async () => {
|
|
@@ -159,11 +167,12 @@ export const EditPackageDetailsDialog = ({
|
|
|
159
167
|
website: formData.website,
|
|
160
168
|
license: formData.license,
|
|
161
169
|
is_private: formData.visibility == "private",
|
|
170
|
+
default_view: formData.defaultView,
|
|
162
171
|
}))
|
|
163
172
|
return { previous }
|
|
164
173
|
},
|
|
165
174
|
onSuccess: (data) => {
|
|
166
|
-
onUpdate?.(data.description, data.website, data.license)
|
|
175
|
+
onUpdate?.(data.description, data.website, data.license, data.defaultView)
|
|
167
176
|
onOpenChange(false)
|
|
168
177
|
qc.invalidateQueries([
|
|
169
178
|
"packageFile",
|
|
@@ -217,151 +226,180 @@ export const EditPackageDetailsDialog = ({
|
|
|
217
226
|
</DialogContent>
|
|
218
227
|
</Dialog>
|
|
219
228
|
<Dialog open={open !== showConfirmDelete} onOpenChange={onOpenChange}>
|
|
220
|
-
<DialogContent className="sm:max-w-[500px] lg:h-[
|
|
221
|
-
<
|
|
222
|
-
<
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
229
|
+
<DialogContent className="sm:max-w-[500px] lg:h-[85vh] sm:h-[90vh] overflow-y-auto no-scrollbar w-[95vw] h-[80vh] p-6 gap-6 rounded-2xl shadow-lg">
|
|
230
|
+
<div className="flex flex-col gap-10">
|
|
231
|
+
<DialogHeader>
|
|
232
|
+
<DialogTitle>Edit Package Details</DialogTitle>
|
|
233
|
+
<DialogDescription>
|
|
234
|
+
Update your package's description, website, visibility, or
|
|
235
|
+
delete it.
|
|
236
|
+
</DialogDescription>
|
|
237
|
+
</DialogHeader>
|
|
238
|
+
<div className="">
|
|
239
|
+
<div className="grid gap-2">
|
|
240
|
+
<div className="space-y-1">
|
|
241
|
+
<Label htmlFor="website">Website</Label>
|
|
242
|
+
<Input
|
|
243
|
+
id="website"
|
|
244
|
+
value={formData.website}
|
|
245
|
+
onChange={(e) =>
|
|
246
|
+
setFormData((prev) => ({
|
|
247
|
+
...prev,
|
|
248
|
+
website: e.target.value,
|
|
249
|
+
}))
|
|
250
|
+
}
|
|
251
|
+
placeholder="https://example.com"
|
|
252
|
+
disabled={updatePackageDetailsMutation.isLoading}
|
|
253
|
+
className="w-full"
|
|
254
|
+
aria-invalid={!!websiteError}
|
|
255
|
+
/>
|
|
256
|
+
{websiteError && (
|
|
257
|
+
<p className="text-sm text-red-500">{websiteError}</p>
|
|
258
|
+
)}
|
|
259
|
+
</div>
|
|
260
|
+
<div className="space-y-1">
|
|
261
|
+
<Label htmlFor="visibility">Visibility</Label>
|
|
262
|
+
<Select
|
|
263
|
+
value={formData.visibility}
|
|
264
|
+
onValueChange={(val) => {
|
|
265
|
+
setFormData((prev) => ({
|
|
266
|
+
...prev,
|
|
267
|
+
visibility: val,
|
|
268
|
+
}))
|
|
269
|
+
}}
|
|
270
|
+
disabled={updatePackageDetailsMutation.isLoading}
|
|
271
|
+
>
|
|
272
|
+
<SelectTrigger className="w-full">
|
|
273
|
+
<SelectValue placeholder="Select visibility" />
|
|
274
|
+
</SelectTrigger>
|
|
275
|
+
<SelectContent className="!z-[999]">
|
|
276
|
+
<SelectItem value="public">public</SelectItem>
|
|
277
|
+
<SelectItem value="private">private</SelectItem>
|
|
278
|
+
</SelectContent>
|
|
279
|
+
</Select>
|
|
280
|
+
</div>
|
|
281
|
+
<div className="space-y-1">
|
|
282
|
+
<Label htmlFor="description">Description</Label>
|
|
283
|
+
<Textarea
|
|
284
|
+
id="description"
|
|
285
|
+
value={formData.description}
|
|
286
|
+
onChange={(e) =>
|
|
287
|
+
setFormData((prev) => ({
|
|
288
|
+
...prev,
|
|
289
|
+
description: e.target.value,
|
|
290
|
+
}))
|
|
291
|
+
}
|
|
292
|
+
placeholder="Enter package description"
|
|
293
|
+
disabled={updatePackageDetailsMutation.isLoading}
|
|
294
|
+
className="w-full min-h-[80px] resize-none"
|
|
295
|
+
/>
|
|
296
|
+
</div>
|
|
297
|
+
<div className="space-y-1">
|
|
298
|
+
<Label htmlFor="license">License</Label>
|
|
299
|
+
<Select
|
|
300
|
+
value={formData.license || "unset"}
|
|
301
|
+
onValueChange={(value) =>
|
|
302
|
+
setFormData((prev) => ({
|
|
303
|
+
...prev,
|
|
304
|
+
license: value === "unset" ? null : value,
|
|
305
|
+
}))
|
|
306
|
+
}
|
|
307
|
+
disabled={updatePackageDetailsMutation.isLoading}
|
|
308
|
+
>
|
|
309
|
+
<SelectTrigger className="w-full">
|
|
310
|
+
<SelectValue placeholder="Select a license" />
|
|
311
|
+
</SelectTrigger>
|
|
312
|
+
<SelectContent className="!z-[999]">
|
|
313
|
+
<SelectItem value="MIT">MIT</SelectItem>
|
|
314
|
+
<SelectItem value="Apache-2.0">Apache-2.0</SelectItem>
|
|
315
|
+
<SelectItem value="BSD-3-Clause">BSD-3-Clause</SelectItem>
|
|
316
|
+
<SelectItem value="GPL-3.0">GPL-3.0</SelectItem>
|
|
317
|
+
<SelectItem value="unset">Unset</SelectItem>
|
|
318
|
+
</SelectContent>
|
|
319
|
+
</Select>
|
|
320
|
+
</div>
|
|
321
|
+
<div className="space-y-1">
|
|
322
|
+
<Label htmlFor="defaultView">Default View</Label>
|
|
323
|
+
<Select
|
|
324
|
+
value={formData.defaultView}
|
|
325
|
+
onValueChange={(value) =>
|
|
326
|
+
setFormData((prev) => ({
|
|
327
|
+
...prev,
|
|
328
|
+
defaultView: value,
|
|
329
|
+
}))
|
|
330
|
+
}
|
|
331
|
+
disabled={updatePackageDetailsMutation.isLoading}
|
|
332
|
+
>
|
|
333
|
+
<SelectTrigger className="w-full">
|
|
334
|
+
<SelectValue placeholder="Select default view" />
|
|
335
|
+
</SelectTrigger>
|
|
336
|
+
<SelectContent className="!z-[999]">
|
|
337
|
+
<SelectItem value="files">Files</SelectItem>
|
|
338
|
+
<SelectItem value="3d">3D</SelectItem>
|
|
339
|
+
<SelectItem value="pcb">PCB</SelectItem>
|
|
340
|
+
<SelectItem value="schematic">Schematic</SelectItem>
|
|
341
|
+
</SelectContent>
|
|
342
|
+
</Select>
|
|
343
|
+
</div>
|
|
344
|
+
</div>
|
|
228
345
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
<Input
|
|
233
|
-
id="website"
|
|
234
|
-
value={formData.website}
|
|
235
|
-
onChange={(e) =>
|
|
236
|
-
setFormData((prev) => ({
|
|
237
|
-
...prev,
|
|
238
|
-
website: e.target.value,
|
|
239
|
-
}))
|
|
240
|
-
}
|
|
241
|
-
placeholder="https://example.com"
|
|
242
|
-
disabled={updatePackageDetailsMutation.isLoading}
|
|
243
|
-
className="w-full"
|
|
244
|
-
aria-invalid={!!websiteError}
|
|
245
|
-
/>
|
|
246
|
-
{websiteError && (
|
|
247
|
-
<p className="text-sm text-red-500">{websiteError}</p>
|
|
248
|
-
)}
|
|
249
|
-
</div>
|
|
250
|
-
<div className="space-y-1">
|
|
251
|
-
<Label htmlFor="visibility">Visibility</Label>
|
|
252
|
-
<Select
|
|
253
|
-
value={formData.visibility}
|
|
254
|
-
onValueChange={(val) => {
|
|
255
|
-
setFormData((prev) => ({
|
|
256
|
-
...prev,
|
|
257
|
-
visibility: val,
|
|
258
|
-
}))
|
|
259
|
-
}}
|
|
260
|
-
disabled={updatePackageDetailsMutation.isLoading}
|
|
346
|
+
<details
|
|
347
|
+
className="mt-2 rounded-md"
|
|
348
|
+
onToggle={(e) => setDangerOpen(e.currentTarget.open)}
|
|
261
349
|
>
|
|
262
|
-
<
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
350
|
+
<summary className="cursor-pointer p-2 font-medium text-sm text-black list-none flex justify-between items-center">
|
|
351
|
+
Danger Zone
|
|
352
|
+
<ChevronDown
|
|
353
|
+
className={`w-4 h-4 mr-1 transition-transform ${dangerOpen ? "rotate-180" : ""}`}
|
|
354
|
+
/>
|
|
355
|
+
</summary>
|
|
356
|
+
<div className="p-2 pr-2">
|
|
357
|
+
<div className="flex justify-between items-center">
|
|
358
|
+
<div>
|
|
359
|
+
<p className="text-sm text-muted-foreground">
|
|
360
|
+
Once deleted, it cannot be recovered.
|
|
361
|
+
</p>
|
|
362
|
+
</div>
|
|
363
|
+
<Button
|
|
364
|
+
variant="destructive"
|
|
365
|
+
size="default"
|
|
366
|
+
onClick={() => setShowConfirmDelete(true)}
|
|
367
|
+
disabled={deleting}
|
|
368
|
+
className="shrink-0 lg:w-[115px] w-[70px]"
|
|
369
|
+
>
|
|
370
|
+
{deleting ? "Deleting..." : "Delete"}
|
|
371
|
+
</Button>
|
|
372
|
+
</div>
|
|
373
|
+
</div>
|
|
374
|
+
</details>
|
|
270
375
|
</div>
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
...prev,
|
|
279
|
-
description: e.target.value,
|
|
280
|
-
}))
|
|
281
|
-
}
|
|
282
|
-
placeholder="Enter package description"
|
|
376
|
+
</div>
|
|
377
|
+
|
|
378
|
+
<DialogFooter className="mt-auto">
|
|
379
|
+
<div className="lg:px-2 flex flex-col sm:flex-row justify-end gap-2">
|
|
380
|
+
<Button
|
|
381
|
+
variant="outline"
|
|
382
|
+
onClick={() => onOpenChange(false)}
|
|
283
383
|
disabled={updatePackageDetailsMutation.isLoading}
|
|
284
|
-
className="w-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
<
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
license: value === "unset" ? null : value,
|
|
295
|
-
}))
|
|
384
|
+
className="sm:w-auto w-full"
|
|
385
|
+
>
|
|
386
|
+
Cancel
|
|
387
|
+
</Button>
|
|
388
|
+
<Button
|
|
389
|
+
onClick={() => updatePackageDetailsMutation.mutate()}
|
|
390
|
+
disabled={
|
|
391
|
+
updatePackageDetailsMutation.isLoading ||
|
|
392
|
+
!hasChanges ||
|
|
393
|
+
!isFormValid
|
|
296
394
|
}
|
|
297
|
-
|
|
395
|
+
className="sm:w-auto lg:w-[115px]"
|
|
298
396
|
>
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
<SelectItem value="MIT">MIT</SelectItem>
|
|
304
|
-
<SelectItem value="Apache-2.0">Apache-2.0</SelectItem>
|
|
305
|
-
<SelectItem value="BSD-3-Clause">BSD-3-Clause</SelectItem>
|
|
306
|
-
<SelectItem value="GPL-3.0">GPL-3.0</SelectItem>
|
|
307
|
-
<SelectItem value="unset">Unset</SelectItem>
|
|
308
|
-
</SelectContent>
|
|
309
|
-
</Select>
|
|
397
|
+
{updatePackageDetailsMutation.isLoading
|
|
398
|
+
? "Updating..."
|
|
399
|
+
: "Save Changes"}
|
|
400
|
+
</Button>
|
|
310
401
|
</div>
|
|
311
|
-
</
|
|
312
|
-
<details
|
|
313
|
-
className="mt-2 rounded-md"
|
|
314
|
-
onToggle={(e) => setDangerOpen(e.currentTarget.open)}
|
|
315
|
-
>
|
|
316
|
-
<summary className="cursor-pointer p-2 font-medium text-sm text-black list-none flex justify-between items-center">
|
|
317
|
-
Danger Zone
|
|
318
|
-
<ChevronDown
|
|
319
|
-
className={`w-4 h-4 mr-1 transition-transform ${dangerOpen ? "rotate-180" : ""}`}
|
|
320
|
-
/>
|
|
321
|
-
</summary>
|
|
322
|
-
<div className="p-2 pr-2">
|
|
323
|
-
<div className="flex justify-between items-center">
|
|
324
|
-
<div>
|
|
325
|
-
<p className="text-sm text-muted-foreground">
|
|
326
|
-
Once deleted, it cannot be recovered.
|
|
327
|
-
</p>
|
|
328
|
-
</div>
|
|
329
|
-
<Button
|
|
330
|
-
variant="destructive"
|
|
331
|
-
size="default"
|
|
332
|
-
onClick={() => setShowConfirmDelete(true)}
|
|
333
|
-
disabled={deleting}
|
|
334
|
-
className="shrink-0 lg:w-[115px] w-[70px]"
|
|
335
|
-
>
|
|
336
|
-
{deleting ? "Deleting..." : "Delete"}
|
|
337
|
-
</Button>
|
|
338
|
-
</div>
|
|
339
|
-
</div>
|
|
340
|
-
</details>
|
|
341
|
-
|
|
342
|
-
<div className=" lg:px-2 flex flex-col sm:flex-row justify-end gap-3">
|
|
343
|
-
<Button
|
|
344
|
-
variant="outline"
|
|
345
|
-
onClick={() => onOpenChange(false)}
|
|
346
|
-
disabled={updatePackageDetailsMutation.isLoading}
|
|
347
|
-
className="sm:w-auto w-full"
|
|
348
|
-
>
|
|
349
|
-
Cancel
|
|
350
|
-
</Button>
|
|
351
|
-
<Button
|
|
352
|
-
onClick={() => updatePackageDetailsMutation.mutate()}
|
|
353
|
-
disabled={
|
|
354
|
-
updatePackageDetailsMutation.isLoading ||
|
|
355
|
-
!hasChanges ||
|
|
356
|
-
!isFormValid
|
|
357
|
-
}
|
|
358
|
-
className="sm:w-auto lg:w-[115px]"
|
|
359
|
-
>
|
|
360
|
-
{updatePackageDetailsMutation.isLoading
|
|
361
|
-
? "Updating..."
|
|
362
|
-
: "Save Changes"}
|
|
363
|
-
</Button>
|
|
364
|
-
</div>
|
|
402
|
+
</DialogFooter>
|
|
365
403
|
</DialogContent>
|
|
366
404
|
</Dialog>
|
|
367
405
|
</div>
|
|
@@ -22,6 +22,8 @@ import { usePackageFilesLoader } from "@/hooks/usePackageFilesLoader"
|
|
|
22
22
|
import { findTargetFile } from "@/lib/utils/findTargetFile"
|
|
23
23
|
import { toastManualEditConflicts } from "@/lib/utils/toastManualEditConflicts"
|
|
24
24
|
import { ManualEditEvent } from "@tscircuit/props"
|
|
25
|
+
import { isValidFileName } from "@/lib/utils/isValidFileName"
|
|
26
|
+
import { useFileManagement } from "@/hooks/useFileManagement"
|
|
25
27
|
|
|
26
28
|
interface Props {
|
|
27
29
|
pkg?: Package
|
|
@@ -32,12 +34,19 @@ export interface PackageFile {
|
|
|
32
34
|
content: string
|
|
33
35
|
}
|
|
34
36
|
|
|
35
|
-
interface
|
|
37
|
+
export interface CreateFileProps {
|
|
38
|
+
newFileName: string
|
|
39
|
+
setErrorMessage: (message: string) => void
|
|
40
|
+
onFileSelect: (fileName: string) => void
|
|
41
|
+
setNewFileName: (fileName: string) => void
|
|
42
|
+
setIsCreatingFile: (isCreatingFile: boolean) => void
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface CodeAndPreviewState {
|
|
36
46
|
pkgFilesWithContent: PackageFile[]
|
|
37
47
|
initialFilesLoad: PackageFile[]
|
|
38
48
|
showPreview: boolean
|
|
39
49
|
fullScreen: boolean
|
|
40
|
-
dts: string
|
|
41
50
|
lastSavedAt: number
|
|
42
51
|
circuitJson: null | any
|
|
43
52
|
isPrivate: boolean
|
|
@@ -92,7 +101,6 @@ export function CodeAndPreview({ pkg }: Props) {
|
|
|
92
101
|
initialFilesLoad: [],
|
|
93
102
|
showPreview: true,
|
|
94
103
|
fullScreen: false,
|
|
95
|
-
dts: "",
|
|
96
104
|
lastSavedAt: Date.now(),
|
|
97
105
|
circuitJson: null,
|
|
98
106
|
isPrivate: false,
|
|
@@ -148,7 +156,6 @@ export function CodeAndPreview({ pkg }: Props) {
|
|
|
148
156
|
|
|
149
157
|
if (loadedFiles && !isLoadingFiles) {
|
|
150
158
|
const processedResults = [...loadedFiles]
|
|
151
|
-
|
|
152
159
|
setState((prev) => ({
|
|
153
160
|
...prev,
|
|
154
161
|
pkgFilesWithContent: processedResults,
|
|
@@ -159,13 +166,7 @@ export function CodeAndPreview({ pkg }: Props) {
|
|
|
159
166
|
defaultCode,
|
|
160
167
|
}))
|
|
161
168
|
}
|
|
162
|
-
}, [
|
|
163
|
-
isLoadingFiles,
|
|
164
|
-
pkg,
|
|
165
|
-
pkgFiles.data,
|
|
166
|
-
state.pkgFilesWithContent.length,
|
|
167
|
-
defaultCode,
|
|
168
|
-
])
|
|
169
|
+
}, [isLoadingFiles, pkg, pkgFiles.data, defaultCode])
|
|
169
170
|
|
|
170
171
|
const createPackageMutation = useCreatePackageMutation()
|
|
171
172
|
const { mutate: createRelease } = useCreatePackageReleaseMutation({
|
|
@@ -250,10 +251,21 @@ export function CodeAndPreview({ pkg }: Props) {
|
|
|
250
251
|
setState((prev) => ({ ...prev, lastSavedAt: Date.now() }))
|
|
251
252
|
|
|
252
253
|
if (pkg) {
|
|
253
|
-
updatePackageFilesMutation.mutate(
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
254
|
+
updatePackageFilesMutation.mutate(
|
|
255
|
+
{
|
|
256
|
+
package_name_with_version: `${pkg.name}@latest`,
|
|
257
|
+
...pkg,
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
onSuccess: () => {
|
|
261
|
+
setState((prev) => ({
|
|
262
|
+
...prev,
|
|
263
|
+
initialFilesLoad: [...prev.pkgFilesWithContent],
|
|
264
|
+
}))
|
|
265
|
+
pkgFiles.refetch()
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
)
|
|
257
269
|
}
|
|
258
270
|
}
|
|
259
271
|
|
|
@@ -284,10 +296,17 @@ export function CodeAndPreview({ pkg }: Props) {
|
|
|
284
296
|
state.pkgFilesWithContent,
|
|
285
297
|
])
|
|
286
298
|
const mainComponentPath = useMemo(() => {
|
|
287
|
-
|
|
299
|
+
const isReactComponentExported =
|
|
300
|
+
/export function\s+\w+/.test(currentFileCode) ||
|
|
301
|
+
/export const\s+\w+\s*=/.test(currentFileCode) ||
|
|
302
|
+
/export default\s+\w+/.test(currentFileCode) ||
|
|
303
|
+
/export default\s+function\s*(\w*)\s*\(/.test(currentFileCode) ||
|
|
304
|
+
/export default\s*\(\s*\)\s*=>/.test(currentFileCode)
|
|
305
|
+
|
|
306
|
+
return (state.currentFile?.endsWith(".tsx") ||
|
|
307
|
+
state.currentFile?.endsWith(".ts")) &&
|
|
288
308
|
!!state.pkgFilesWithContent.some((x) => x.path == state.currentFile) &&
|
|
289
|
-
|
|
290
|
-
currentFileCode.match(/export const (\w+) ?=/))
|
|
309
|
+
isReactComponentExported
|
|
291
310
|
? state.currentFile
|
|
292
311
|
: state.defaultComponentFile
|
|
293
312
|
}, [state.currentFile, state.pkgFilesWithContent, currentFileCode])
|
|
@@ -327,6 +346,8 @@ export function CodeAndPreview({ pkg }: Props) {
|
|
|
327
346
|
})
|
|
328
347
|
}
|
|
329
348
|
|
|
349
|
+
const { handleCreateFile } = useFileManagement(state, setState)
|
|
350
|
+
|
|
330
351
|
if ((!pkg && urlParams.package_id) || pkgFiles.isLoading || isLoadingFiles) {
|
|
331
352
|
return (
|
|
332
353
|
<div className="flex items-center justify-center h-64">
|
|
@@ -363,6 +384,7 @@ export function CodeAndPreview({ pkg }: Props) {
|
|
|
363
384
|
)}
|
|
364
385
|
>
|
|
365
386
|
<CodeEditor
|
|
387
|
+
handleCreateFile={handleCreateFile}
|
|
366
388
|
currentFile={state.currentFile}
|
|
367
389
|
setCurrentFile={(file) =>
|
|
368
390
|
setState((prev) => ({ ...prev, currentFile: file }))
|
|
@@ -379,7 +401,6 @@ export function CodeAndPreview({ pkg }: Props) {
|
|
|
379
401
|
),
|
|
380
402
|
}))
|
|
381
403
|
}}
|
|
382
|
-
onDtsChange={(dts) => setState((prev) => ({ ...prev, dts }))}
|
|
383
404
|
pkgFilesLoaded={state.pkgFilesLoaded}
|
|
384
405
|
/>
|
|
385
406
|
</div>
|