@tscircuit/fake-snippets 0.0.71 → 0.0.73
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/package_files/create_or_update.test.ts +26 -0
- package/bun.lock +58 -58
- package/dist/bundle.js +56 -5
- package/dist/index.d.ts +15 -2
- package/dist/index.js +47 -1
- package/dist/schema.d.ts +8 -0
- package/dist/schema.js +1 -0
- package/fake-snippets-api/lib/db/db-client.ts +47 -0
- package/fake-snippets-api/lib/db/schema.ts +1 -0
- package/fake-snippets-api/lib/package_file/generate-fs-sha.ts +20 -0
- package/fake-snippets-api/lib/public-mapping/public-map-package.ts +1 -0
- package/fake-snippets-api/routes/api/package_files/create.ts +3 -0
- package/fake-snippets-api/routes/api/package_files/create_or_update.ts +9 -3
- package/fake-snippets-api/routes/api/package_files/delete.ts +3 -0
- package/fake-snippets-api/routes/api/packages/create.ts +1 -0
- package/package.json +11 -11
- package/src/App.tsx +5 -0
- package/src/components/FileSidebar.tsx +111 -37
- package/src/components/JLCPCBImportDialog.tsx +1 -1
- package/src/components/PackageBuildsPage/DeploymentDetailsPage.tsx +56 -0
- package/src/components/PackageBuildsPage/build-preview-content.tsx +11 -0
- package/src/components/PackageBuildsPage/collapsible-section.tsx +70 -0
- package/src/components/PackageBuildsPage/deployment-details-panel.tsx +84 -0
- package/src/components/PackageBuildsPage/deployment-header.tsx +75 -0
- package/src/components/PackageCard.tsx +1 -10
- package/src/components/TrendingPackagesCarousel.tsx +5 -16
- package/src/components/ViewPackagePage/components/mobile-sidebar.tsx +1 -1
- package/src/components/ViewPackagePage/components/preview-image-squares.tsx +2 -6
- package/src/components/package-port/CodeAndPreview.tsx +78 -267
- package/src/components/package-port/CodeEditor.tsx +30 -19
- package/src/components/package-port/CodeEditorHeader.tsx +7 -6
- package/src/components/package-port/EditorNav.tsx +17 -13
- package/src/components/ui/tree-view.tsx +3 -3
- package/src/hooks/use-current-package-id.ts +2 -8
- package/src/hooks/use-preview-images.ts +3 -15
- package/src/hooks/useFileManagement.ts +257 -38
- package/src/hooks/usePackageFilesLoader.ts +2 -2
- package/src/hooks/useUpdatePackageFilesMutation.ts +50 -24
- package/src/lib/utils/checkIfManualEditsImported.ts +9 -0
- package/src/pages/editor.tsx +2 -10
- package/src/pages/package-builds.tsx +33 -0
- package/src/pages/package-editor.tsx +2 -14
- package/src/hooks/use-get-fsmap-hash-for-package.ts +0 -19
|
@@ -207,19 +207,23 @@ export default function EditorNav({
|
|
|
207
207
|
<div className="flex items-center space-x-1">
|
|
208
208
|
{pkg && (
|
|
209
209
|
<>
|
|
210
|
-
<
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
className="text-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
210
|
+
<div className="flex items-center space-x-1 overflow-hidden">
|
|
211
|
+
<Link
|
|
212
|
+
className="text-blue-500 font-semibold hover:underline truncate"
|
|
213
|
+
href={`/${pkg.owner_github_username}`}
|
|
214
|
+
title={pkg.owner_github_username || ""}
|
|
215
|
+
>
|
|
216
|
+
{pkg.owner_github_username}
|
|
217
|
+
</Link>
|
|
218
|
+
<span className="px-0.5 text-gray-500">/</span>
|
|
219
|
+
<Link
|
|
220
|
+
className="text-blue-500 font-semibold hover:underline truncate"
|
|
221
|
+
href={`/${pkg.name}`}
|
|
222
|
+
title={pkg.unscoped_name}
|
|
223
|
+
>
|
|
224
|
+
{pkg.unscoped_name}
|
|
225
|
+
</Link>
|
|
226
|
+
</div>
|
|
223
227
|
{pkg.star_count !== undefined && (
|
|
224
228
|
<span className="ml-2 text-gray-500 text-xs flex items-center">
|
|
225
229
|
<Star className="w-3 h-3 mr-1" />
|
|
@@ -404,9 +404,9 @@ const TreeLeaf = React.forwardRef<
|
|
|
404
404
|
default={defaultLeafIcon}
|
|
405
405
|
/>
|
|
406
406
|
<span className="flex-grow text-sm truncate">{item.name}</span>
|
|
407
|
-
<
|
|
408
|
-
{item.actions}
|
|
409
|
-
</
|
|
407
|
+
<div onClick={(e) => e.stopPropagation()}>
|
|
408
|
+
<TreeActions isSelected={true}>{item.actions}</TreeActions>
|
|
409
|
+
</div>
|
|
410
410
|
</div>
|
|
411
411
|
)
|
|
412
412
|
},
|
|
@@ -31,17 +31,11 @@ export const useCurrentPackageId = (): {
|
|
|
31
31
|
error: errorPackageByName,
|
|
32
32
|
} = usePackageByName(packageName)
|
|
33
33
|
|
|
34
|
-
const {
|
|
35
|
-
data: packageById,
|
|
36
|
-
isLoading: isLoadingPackageById,
|
|
37
|
-
error: errorPackageById,
|
|
38
|
-
} = usePackageById(packageIdFromUrl)
|
|
39
|
-
|
|
40
34
|
const packageId = packageIdFromUrl ?? packageByName?.package_id ?? null
|
|
41
35
|
|
|
42
36
|
return {
|
|
43
37
|
packageId,
|
|
44
|
-
isLoading: isLoadingPackageByName
|
|
45
|
-
error: errorPackageByName
|
|
38
|
+
isLoading: isLoadingPackageByName,
|
|
39
|
+
error: errorPackageByName,
|
|
46
40
|
}
|
|
47
41
|
}
|
|
@@ -22,33 +22,21 @@ export function usePreviewImages({
|
|
|
22
22
|
id: "3d",
|
|
23
23
|
label: "3D View",
|
|
24
24
|
imageUrl: packageName
|
|
25
|
-
? `https://registry-api.tscircuit.com/packages/images/${packageName}/3d.png
|
|
26
|
-
{
|
|
27
|
-
fs_sha: fsMapHash ?? "",
|
|
28
|
-
},
|
|
29
|
-
).toString()}`
|
|
25
|
+
? `https://registry-api.tscircuit.com/packages/images/${packageName}/3d.png?fs_sha=${fsMapHash}`
|
|
30
26
|
: undefined,
|
|
31
27
|
},
|
|
32
28
|
{
|
|
33
29
|
id: "pcb",
|
|
34
30
|
label: "PCB View",
|
|
35
31
|
imageUrl: packageName
|
|
36
|
-
? `https://registry-api.tscircuit.com/packages/images/${packageName}/pcb.png
|
|
37
|
-
{
|
|
38
|
-
fs_sha: fsMapHash ?? "",
|
|
39
|
-
},
|
|
40
|
-
).toString()}`
|
|
32
|
+
? `https://registry-api.tscircuit.com/packages/images/${packageName}/pcb.png?fs_sha=${fsMapHash}`
|
|
41
33
|
: undefined,
|
|
42
34
|
},
|
|
43
35
|
{
|
|
44
36
|
id: "schematic",
|
|
45
37
|
label: "Schematic View",
|
|
46
38
|
imageUrl: packageName
|
|
47
|
-
? `https://registry-api.tscircuit.com/packages/images/${packageName}/schematic.png
|
|
48
|
-
{
|
|
49
|
-
fs_sha: fsMapHash ?? "",
|
|
50
|
-
},
|
|
51
|
-
).toString()}`
|
|
39
|
+
? `https://registry-api.tscircuit.com/packages/images/${packageName}/schematic.png?fs_sha=${fsMapHash}`
|
|
52
40
|
: undefined,
|
|
53
41
|
},
|
|
54
42
|
]
|
|
@@ -1,59 +1,278 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useEffect, useMemo, useState, useCallback } from "react"
|
|
2
2
|
import { isValidFileName } from "@/lib/utils/isValidFileName"
|
|
3
3
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
DEFAULT_CODE,
|
|
5
|
+
generateRandomPackageName,
|
|
6
|
+
PackageFile,
|
|
6
7
|
} from "../components/package-port/CodeAndPreview"
|
|
8
|
+
import { Package } from "fake-snippets-api/lib/db/schema"
|
|
9
|
+
import {
|
|
10
|
+
usePackageFile,
|
|
11
|
+
usePackageFileById,
|
|
12
|
+
usePackageFiles,
|
|
13
|
+
} from "./use-package-files"
|
|
14
|
+
import { decodeUrlHashToText } from "@/lib/decodeUrlHashToText"
|
|
15
|
+
import { usePackageFilesLoader } from "./usePackageFilesLoader"
|
|
16
|
+
import { useGlobalStore } from "./use-global-store"
|
|
17
|
+
import { useToast } from "@/components/ViewPackagePage/hooks/use-toast"
|
|
18
|
+
import { useUpdatePackageFilesMutation } from "./useUpdatePackageFilesMutation"
|
|
19
|
+
import { useCreatePackageReleaseMutation } from "./use-create-package-release-mutation"
|
|
20
|
+
import { useCreatePackageMutation } from "./use-create-package-mutation"
|
|
21
|
+
import { findTargetFile } from "@/lib/utils/findTargetFile"
|
|
22
|
+
|
|
23
|
+
export interface ICreateFileProps {
|
|
24
|
+
newFileName: string
|
|
25
|
+
onError: (error: Error) => void
|
|
26
|
+
}
|
|
27
|
+
export interface ICreateFileResult {
|
|
28
|
+
newFileCreated: boolean
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface IDeleteFileResult {
|
|
32
|
+
fileDeleted: boolean
|
|
33
|
+
}
|
|
34
|
+
export interface IDeleteFileProps {
|
|
35
|
+
filename: string
|
|
36
|
+
onError: (error: Error) => void
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function useFileManagement({
|
|
40
|
+
templateCode,
|
|
41
|
+
currentPackage,
|
|
42
|
+
fileChoosen,
|
|
43
|
+
openNewPackageSaveDialog,
|
|
44
|
+
updateLastUpdated,
|
|
45
|
+
}: {
|
|
46
|
+
templateCode?: string
|
|
47
|
+
currentPackage?: Package
|
|
48
|
+
fileChoosen: string | null
|
|
49
|
+
openNewPackageSaveDialog: () => void
|
|
50
|
+
updateLastUpdated: () => void
|
|
51
|
+
}) {
|
|
52
|
+
const [localFiles, setLocalFiles] = useState<PackageFile[]>([])
|
|
53
|
+
const [initialFiles, setInitialFiles] = useState<PackageFile[]>([])
|
|
54
|
+
const [currentFile, setCurrentFile] = useState<string | null>(null)
|
|
55
|
+
const isLoggedIn = useGlobalStore((s) => Boolean(s.session))
|
|
56
|
+
const loggedInUser = useGlobalStore((s) => s.session)
|
|
57
|
+
const { toast } = useToast()
|
|
58
|
+
const {
|
|
59
|
+
data: packageFilesWithContent,
|
|
60
|
+
isLoading: isLoadingPackageFilesWithContent,
|
|
61
|
+
} = usePackageFilesLoader(currentPackage)
|
|
62
|
+
const { data: packageFilesMeta, isLoading: isLoadingPackageFiles } =
|
|
63
|
+
usePackageFiles(currentPackage?.latest_package_release_id)
|
|
7
64
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
)
|
|
12
|
-
|
|
65
|
+
const initialCodeContent = useMemo(() => {
|
|
66
|
+
return (
|
|
67
|
+
templateCode ??
|
|
68
|
+
decodeUrlHashToText(window.location.toString()) ??
|
|
69
|
+
DEFAULT_CODE
|
|
70
|
+
)
|
|
71
|
+
}, [templateCode, currentPackage])
|
|
72
|
+
const manualEditsFileContent = useMemo(() => {
|
|
73
|
+
return (
|
|
74
|
+
localFiles?.find((file) => file.path === "manual-edits.json")?.content ||
|
|
75
|
+
"{}"
|
|
76
|
+
)
|
|
77
|
+
}, [localFiles])
|
|
78
|
+
|
|
79
|
+
const updatePackageFilesMutation = useUpdatePackageFilesMutation({
|
|
80
|
+
currentPackage,
|
|
81
|
+
localFiles,
|
|
82
|
+
initialFiles,
|
|
83
|
+
packageFilesMeta: packageFilesMeta || [],
|
|
84
|
+
})
|
|
85
|
+
const { mutate: createRelease, isLoading: isCreatingRelease } =
|
|
86
|
+
useCreatePackageReleaseMutation({
|
|
87
|
+
onSuccess: () => {
|
|
88
|
+
toast({
|
|
89
|
+
title: "Package released",
|
|
90
|
+
description: "Your package has been released successfully.",
|
|
91
|
+
})
|
|
92
|
+
},
|
|
93
|
+
})
|
|
94
|
+
const createPackageMutation = useCreatePackageMutation()
|
|
95
|
+
|
|
96
|
+
useEffect(() => {
|
|
97
|
+
if (!currentPackage || isLoadingPackageFilesWithContent) {
|
|
98
|
+
setLocalFiles([
|
|
99
|
+
{
|
|
100
|
+
path: "index.tsx",
|
|
101
|
+
content: initialCodeContent || "",
|
|
102
|
+
},
|
|
103
|
+
])
|
|
104
|
+
setInitialFiles([])
|
|
105
|
+
setCurrentFile("index.tsx")
|
|
106
|
+
return
|
|
107
|
+
} else {
|
|
108
|
+
const targetFile = findTargetFile(
|
|
109
|
+
packageFilesWithContent || [],
|
|
110
|
+
fileChoosen,
|
|
111
|
+
)
|
|
112
|
+
setLocalFiles(packageFilesWithContent || [])
|
|
113
|
+
setInitialFiles(packageFilesWithContent || [])
|
|
114
|
+
setCurrentFile(targetFile?.path || null)
|
|
115
|
+
}
|
|
116
|
+
}, [currentPackage, isLoadingPackageFilesWithContent])
|
|
117
|
+
|
|
118
|
+
const isLoading = useMemo(() => {
|
|
119
|
+
return (
|
|
120
|
+
isLoadingPackageFilesWithContent || isLoadingPackageFiles || !localFiles
|
|
121
|
+
)
|
|
122
|
+
}, [isLoadingPackageFilesWithContent, localFiles, isLoadingPackageFiles])
|
|
123
|
+
|
|
124
|
+
const fsMap = useMemo(() => {
|
|
125
|
+
const map = localFiles.reduce(
|
|
126
|
+
(acc, file) => {
|
|
127
|
+
acc[file.path] = file.content || ""
|
|
128
|
+
return acc
|
|
129
|
+
},
|
|
130
|
+
{} as Record<string, string>,
|
|
131
|
+
)
|
|
132
|
+
return map
|
|
133
|
+
}, [localFiles, manualEditsFileContent])
|
|
134
|
+
|
|
135
|
+
const onFileSelect = (fileName: string) => {
|
|
136
|
+
if (localFiles.some((file) => file.path === fileName)) {
|
|
137
|
+
setCurrentFile(fileName)
|
|
138
|
+
} else {
|
|
139
|
+
setCurrentFile(null)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const createFile = ({
|
|
13
144
|
newFileName,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
setNewFileName,
|
|
17
|
-
setIsCreatingFile,
|
|
18
|
-
}: CreateFileProps) => {
|
|
145
|
+
onError,
|
|
146
|
+
}: ICreateFileProps): ICreateFileResult => {
|
|
19
147
|
newFileName = newFileName.trim()
|
|
20
148
|
if (!newFileName) {
|
|
21
|
-
|
|
22
|
-
return
|
|
149
|
+
onError(new Error("File name cannot be empty"))
|
|
150
|
+
return {
|
|
151
|
+
newFileCreated: false,
|
|
152
|
+
}
|
|
23
153
|
}
|
|
24
154
|
if (!isValidFileName(newFileName)) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
155
|
+
onError(new Error("Invalid file name"))
|
|
156
|
+
return {
|
|
157
|
+
newFileCreated: false,
|
|
158
|
+
}
|
|
29
159
|
}
|
|
30
|
-
setErrorMessage("")
|
|
31
|
-
|
|
32
|
-
const fileExists = state.pkgFilesWithContent.some(
|
|
33
|
-
(file) => file.path === newFileName,
|
|
34
|
-
)
|
|
35
160
|
|
|
161
|
+
const fileExists = localFiles?.some((file) => file.path === newFileName)
|
|
36
162
|
if (fileExists) {
|
|
37
|
-
|
|
38
|
-
return
|
|
163
|
+
onError(new Error("File already exists"))
|
|
164
|
+
return {
|
|
165
|
+
newFileCreated: false,
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const updatedFiles = [
|
|
169
|
+
...(localFiles || []),
|
|
170
|
+
{ path: newFileName, content: "" },
|
|
171
|
+
]
|
|
172
|
+
setLocalFiles(updatedFiles)
|
|
173
|
+
onFileSelect(newFileName)
|
|
174
|
+
return {
|
|
175
|
+
newFileCreated: true,
|
|
39
176
|
}
|
|
177
|
+
}
|
|
40
178
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
179
|
+
const deleteFile = ({
|
|
180
|
+
filename,
|
|
181
|
+
onError,
|
|
182
|
+
}: IDeleteFileProps): IDeleteFileResult => {
|
|
183
|
+
const fileExists = localFiles?.some((file) => file.path === filename)
|
|
184
|
+
if (!fileExists) {
|
|
185
|
+
onError(new Error("File does not exist"))
|
|
46
186
|
return {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
187
|
+
fileDeleted: false,
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
const updatedFiles = localFiles.filter((file) => file.path !== filename)
|
|
191
|
+
setLocalFiles(updatedFiles)
|
|
192
|
+
onFileSelect(updatedFiles[0]?.path || "")
|
|
193
|
+
return {
|
|
194
|
+
fileDeleted: true,
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const savePackage = async (isPrivate: boolean) => {
|
|
199
|
+
if (!isLoggedIn) {
|
|
200
|
+
toast({
|
|
201
|
+
title: "Not Logged In",
|
|
202
|
+
description: "You must be logged in to save your package.",
|
|
203
|
+
})
|
|
204
|
+
return
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const newPackage = await createPackageMutation.mutateAsync({
|
|
208
|
+
name: `${loggedInUser?.github_username}/${generateRandomPackageName()}`,
|
|
209
|
+
is_private: isPrivate,
|
|
50
210
|
})
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
211
|
+
|
|
212
|
+
if (newPackage) {
|
|
213
|
+
createRelease(
|
|
214
|
+
{
|
|
215
|
+
package_name_with_version: `${newPackage.name}@latest`,
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
onSuccess: () => {
|
|
219
|
+
updatePackageFilesMutation.mutate({
|
|
220
|
+
package_name_with_version: `${newPackage.name}@latest`,
|
|
221
|
+
...newPackage,
|
|
222
|
+
})
|
|
223
|
+
updateLastUpdated()
|
|
224
|
+
setInitialFiles([...localFiles])
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
)
|
|
228
|
+
}
|
|
229
|
+
updateLastUpdated()
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const saveFiles = () => {
|
|
233
|
+
if (!isLoggedIn) {
|
|
234
|
+
toast({
|
|
235
|
+
title: "Not Logged In",
|
|
236
|
+
description: "You must be logged in to save your package.",
|
|
237
|
+
})
|
|
238
|
+
return
|
|
239
|
+
}
|
|
240
|
+
if (!currentPackage) {
|
|
241
|
+
openNewPackageSaveDialog()
|
|
242
|
+
return
|
|
243
|
+
}
|
|
244
|
+
updatePackageFilesMutation.mutate({
|
|
245
|
+
package_name_with_version: `${currentPackage.name}@latest`,
|
|
246
|
+
...currentPackage,
|
|
247
|
+
})
|
|
248
|
+
updateLastUpdated()
|
|
249
|
+
setInitialFiles([...localFiles])
|
|
54
250
|
}
|
|
55
251
|
|
|
252
|
+
const isSaving = useMemo(() => {
|
|
253
|
+
return (
|
|
254
|
+
updatePackageFilesMutation.isLoading ||
|
|
255
|
+
createPackageMutation.isLoading ||
|
|
256
|
+
isCreatingRelease
|
|
257
|
+
)
|
|
258
|
+
}, [
|
|
259
|
+
updatePackageFilesMutation.isLoading,
|
|
260
|
+
createPackageMutation.isLoading,
|
|
261
|
+
isCreatingRelease,
|
|
262
|
+
])
|
|
263
|
+
|
|
56
264
|
return {
|
|
57
|
-
|
|
265
|
+
fsMap,
|
|
266
|
+
createFile,
|
|
267
|
+
deleteFile,
|
|
268
|
+
saveFiles,
|
|
269
|
+
localFiles,
|
|
270
|
+
initialFiles,
|
|
271
|
+
currentFile,
|
|
272
|
+
setLocalFiles,
|
|
273
|
+
onFileSelect,
|
|
274
|
+
isLoading,
|
|
275
|
+
isSaving,
|
|
276
|
+
savePackage,
|
|
58
277
|
}
|
|
59
278
|
}
|
|
@@ -33,8 +33,8 @@ export function usePackageFilesLoader(pkg?: Package) {
|
|
|
33
33
|
const response = await axios.post(`/package_files/get`, {
|
|
34
34
|
package_file_id: file.package_file_id,
|
|
35
35
|
})
|
|
36
|
-
const content = response.data.package_file?.content_text
|
|
37
|
-
return
|
|
36
|
+
const content = response.data.package_file?.content_text
|
|
37
|
+
return { path: file.file_path, content: content ?? "" }
|
|
38
38
|
},
|
|
39
39
|
staleTime: 2,
|
|
40
40
|
})) ?? [],
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { useMutation } from "react-query"
|
|
2
2
|
import type { Package } from "fake-snippets-api/lib/db/schema"
|
|
3
|
+
import { useAxios } from "./use-axios"
|
|
4
|
+
import { useToast } from "@/components/ViewPackagePage/hooks/use-toast"
|
|
3
5
|
|
|
4
6
|
interface PackageFile {
|
|
5
7
|
path: string
|
|
@@ -7,47 +9,49 @@ interface PackageFile {
|
|
|
7
9
|
}
|
|
8
10
|
|
|
9
11
|
interface UseUpdatePackageFilesMutationProps {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
currentPackage: Package | undefined
|
|
13
|
+
localFiles: PackageFile[]
|
|
14
|
+
initialFiles: PackageFile[]
|
|
15
|
+
packageFilesMeta: {
|
|
16
|
+
created_at: string
|
|
17
|
+
file_path: string
|
|
18
|
+
package_file_id: string
|
|
19
|
+
package_release_id: string
|
|
20
|
+
}[]
|
|
16
21
|
}
|
|
17
22
|
|
|
18
23
|
export function useUpdatePackageFilesMutation({
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
axios,
|
|
24
|
-
toast,
|
|
24
|
+
currentPackage,
|
|
25
|
+
localFiles,
|
|
26
|
+
initialFiles,
|
|
27
|
+
packageFilesMeta,
|
|
25
28
|
}: UseUpdatePackageFilesMutationProps) {
|
|
29
|
+
const axios = useAxios()
|
|
30
|
+
const { toast } = useToast()
|
|
26
31
|
return useMutation({
|
|
27
32
|
mutationFn: async (
|
|
28
|
-
|
|
33
|
+
newPackage: Pick<Package, "package_id" | "name"> & {
|
|
29
34
|
package_name_with_version: string
|
|
30
35
|
},
|
|
31
36
|
) => {
|
|
32
|
-
if (
|
|
33
|
-
|
|
37
|
+
if (currentPackage) {
|
|
38
|
+
newPackage = { ...currentPackage, ...newPackage }
|
|
34
39
|
}
|
|
35
|
-
if (!
|
|
40
|
+
if (!newPackage) throw new Error("No package to update")
|
|
36
41
|
|
|
37
42
|
let updatedFilesCount = 0
|
|
38
43
|
|
|
39
|
-
for (const file of
|
|
40
|
-
const initialFile =
|
|
41
|
-
if (file.content
|
|
44
|
+
for (const file of localFiles) {
|
|
45
|
+
const initialFile = initialFiles.find((x) => x.path === file.path)
|
|
46
|
+
if (file.content !== initialFile?.content) {
|
|
42
47
|
const updatePkgFilePayload = {
|
|
43
48
|
package_file_id:
|
|
44
|
-
|
|
49
|
+
packageFilesMeta.find((x) => x.file_path === file.path)
|
|
45
50
|
?.package_file_id ?? null,
|
|
46
51
|
content_text: file.content,
|
|
47
52
|
file_path: file.path,
|
|
48
|
-
package_name_with_version: `${
|
|
53
|
+
package_name_with_version: `${newPackage.name}`,
|
|
49
54
|
}
|
|
50
|
-
|
|
51
55
|
const response = await axios.post(
|
|
52
56
|
"/package_files/create_or_update",
|
|
53
57
|
updatePkgFilePayload,
|
|
@@ -58,10 +62,33 @@ export function useUpdatePackageFilesMutation({
|
|
|
58
62
|
}
|
|
59
63
|
}
|
|
60
64
|
}
|
|
65
|
+
|
|
66
|
+
for (const initialFile of initialFiles) {
|
|
67
|
+
const fileStillExists = localFiles.some(
|
|
68
|
+
(x) => x.path === initialFile.path,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
if (!fileStillExists) {
|
|
72
|
+
const fileToDelete = packageFilesMeta.find(
|
|
73
|
+
(x) => x.file_path === initialFile.path,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
if (fileToDelete?.package_file_id) {
|
|
77
|
+
const response = await axios.post("/package_files/delete", {
|
|
78
|
+
package_name_with_version: `${newPackage.name}`,
|
|
79
|
+
file_path: initialFile.path,
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
if (response.status === 200) {
|
|
83
|
+
updatedFilesCount++
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
61
88
|
return updatedFilesCount
|
|
62
89
|
},
|
|
63
90
|
onSuccess: (updatedFilesCount) => {
|
|
64
|
-
if (updatedFilesCount) {
|
|
91
|
+
if (updatedFilesCount > 0) {
|
|
65
92
|
toast({
|
|
66
93
|
title: `Package's ${updatedFilesCount} files saved`,
|
|
67
94
|
description: "Your changes have been saved successfully.",
|
|
@@ -69,7 +96,6 @@ export function useUpdatePackageFilesMutation({
|
|
|
69
96
|
}
|
|
70
97
|
},
|
|
71
98
|
onError: (error: any) => {
|
|
72
|
-
console.error("Error updating pkg files:", error)
|
|
73
99
|
toast({
|
|
74
100
|
title: "Error",
|
|
75
101
|
description:
|
|
@@ -1,8 +1,17 @@
|
|
|
1
|
+
import { findTargetFile } from "./findTargetFile"
|
|
2
|
+
|
|
1
3
|
export const checkIfManualEditsImported = (
|
|
2
4
|
files: Record<string, string>,
|
|
3
5
|
file: string = "index.tsx",
|
|
4
6
|
) => {
|
|
5
7
|
if (!files[file]) return false
|
|
8
|
+
const targetFile = findTargetFile(
|
|
9
|
+
Object.keys(files).map((f) => ({ path: f, content: files[f] })),
|
|
10
|
+
null,
|
|
11
|
+
)
|
|
12
|
+
if (targetFile && file !== targetFile.path) {
|
|
13
|
+
return false
|
|
14
|
+
}
|
|
6
15
|
if (!file.endsWith(".tsx") && !file.endsWith(".ts")) return false
|
|
7
16
|
const importRegex =
|
|
8
17
|
/import\s+(?:\*\s+as\s+)?([a-zA-Z_$][\w$]*)\s+from\s+["'](?:\.\/)?manual-edits\.json["'];?/
|
package/src/pages/editor.tsx
CHANGED
|
@@ -4,14 +4,10 @@ import Header from "@/components/Header"
|
|
|
4
4
|
import { Helmet } from "react-helmet-async"
|
|
5
5
|
import { useCurrentPackageId } from "@/hooks/use-current-package-id"
|
|
6
6
|
import { usePackage } from "@/hooks/use-package"
|
|
7
|
-
import { useGetFsMapHashForPackage } from "@/hooks/use-get-fsmap-hash-for-package"
|
|
8
7
|
|
|
9
8
|
export const EditorPage = () => {
|
|
10
9
|
const { packageId } = useCurrentPackageId()
|
|
11
10
|
const { data: pkg, isLoading, error } = usePackage(packageId)
|
|
12
|
-
const fsMapHash = useGetFsMapHashForPackage(
|
|
13
|
-
pkg?.latest_package_release_id ?? "",
|
|
14
|
-
)
|
|
15
11
|
|
|
16
12
|
return (
|
|
17
13
|
<div className="overflow-x-hidden">
|
|
@@ -27,16 +23,12 @@ export const EditorPage = () => {
|
|
|
27
23
|
/>
|
|
28
24
|
<meta
|
|
29
25
|
property="og:image"
|
|
30
|
-
content={`https://registry-api.tscircuit.com/packages/images/${pkg.owner_github_username}/${pkg.unscoped_name}/pcb.png
|
|
31
|
-
{
|
|
32
|
-
fs_sha: fsMapHash ?? "",
|
|
33
|
-
},
|
|
34
|
-
).toString()}`}
|
|
26
|
+
content={`https://registry-api.tscircuit.com/packages/images/${pkg.owner_github_username}/${pkg.unscoped_name}/pcb.png?fs_sha=${pkg.latest_package_release_fs_sha}`}
|
|
35
27
|
/>
|
|
36
28
|
<meta name="twitter:card" content="summary_large_image" />
|
|
37
29
|
<meta
|
|
38
30
|
name="twitter:image"
|
|
39
|
-
content={`https://registry-api.tscircuit.com/packages/images/${pkg.owner_github_username}/${pkg.unscoped_name}/pcb.png`}
|
|
31
|
+
content={`https://registry-api.tscircuit.com/packages/images/${pkg.owner_github_username}/${pkg.unscoped_name}/pcb.png?fs_sha=${pkg.latest_package_release_fs_sha}`}
|
|
40
32
|
/>
|
|
41
33
|
</>
|
|
42
34
|
)}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { CodeAndPreview } from "@/components/package-port/CodeAndPreview"
|
|
2
|
+
import Footer from "@/components/Footer"
|
|
3
|
+
import Header from "@/components/Header"
|
|
4
|
+
import { usePackage } from "@/hooks/use-package"
|
|
5
|
+
import { Helmet } from "react-helmet-async"
|
|
6
|
+
import { useCurrentPackageId } from "@/hooks/use-current-package-id"
|
|
7
|
+
import { NotFound } from "@/components/NotFound"
|
|
8
|
+
import { ErrorOutline } from "@/components/ErrorOutline"
|
|
9
|
+
import { DeploymentDetailsPage } from "@/components/PackageBuildsPage/DeploymentDetailsPage"
|
|
10
|
+
|
|
11
|
+
export const EditorPage = () => {
|
|
12
|
+
const { packageId } = useCurrentPackageId()
|
|
13
|
+
const { data: pkg, error } = usePackage(packageId)
|
|
14
|
+
const uuid4RegExp = new RegExp(
|
|
15
|
+
/^[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}$/,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<div className="overflow-x-hidden">
|
|
20
|
+
<Helmet>
|
|
21
|
+
<title>{pkg ? `${pkg.name} Package Builds` : "Package Builds"}</title>
|
|
22
|
+
{pkg && (
|
|
23
|
+
<>
|
|
24
|
+
<meta property="og:title" content={`${pkg.name} Package Builds`} />
|
|
25
|
+
</>
|
|
26
|
+
)}
|
|
27
|
+
</Helmet>
|
|
28
|
+
<Header />
|
|
29
|
+
<DeploymentDetailsPage />
|
|
30
|
+
<Footer />
|
|
31
|
+
</div>
|
|
32
|
+
)
|
|
33
|
+
}
|