@tscircuit/fake-snippets 0.0.98 → 0.0.100
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.lock +304 -947
- package/dist/bundle.js +11 -5
- package/fake-snippets-api/routes/api/autocomplete/create_autocomplete.ts +0 -1
- package/fake-snippets-api/routes/api/packages/create.ts +14 -3
- package/package.json +7 -4
- package/src/App.tsx +58 -2
- package/src/components/CircuitJsonImportDialog.tsx +10 -5
- package/src/components/DownloadButtonAndMenu.tsx +13 -0
- package/src/components/FileSidebar.tsx +83 -10
- package/src/components/PackageBuildsPage/LogContent.tsx +19 -7
- package/src/components/ViewPackagePage/components/important-files-view.tsx +294 -167
- package/src/components/ViewPackagePage/components/main-content-header.tsx +2 -2
- package/src/components/ViewPackagePage/components/repo-page-content.tsx +9 -0
- package/src/components/ViewPackagePage/components/sidebar-about-section.tsx +10 -3
- package/src/components/ViewPackagePage/components/sidebar.tsx +3 -1
- package/src/components/dialogs/edit-package-details-dialog.tsx +3 -2
- package/src/components/package-port/CodeAndPreview.tsx +6 -1
- package/src/components/package-port/CodeEditor.tsx +21 -3
- package/src/components/package-port/CodeEditorHeader.tsx +12 -7
- package/src/components/ui/tree-view.tsx +51 -2
- package/src/hooks/use-create-package-mutation.ts +1 -1
- package/src/hooks/useFileManagement.ts +71 -6
- package/src/lib/download-fns/download-spice-file.ts +13 -0
- package/src/lib/utils/package-utils.ts +0 -3
- package/src/pages/dashboard.tsx +1 -1
- package/src/pages/datasheet.tsx +157 -67
- package/src/pages/datasheets.tsx +2 -2
- package/src/pages/latest.tsx +2 -2
- package/src/pages/search.tsx +1 -1
- package/src/pages/trending.tsx +2 -2
- package/vite.config.ts +1 -0
|
@@ -13,7 +13,8 @@ export default withRouteSpec({
|
|
|
13
13
|
/^[@a-zA-Z0-9-_\/]+$/,
|
|
14
14
|
"Package name can only contain letters, numbers, hyphens, underscores, and forward slashes",
|
|
15
15
|
)
|
|
16
|
-
.transform((name) => name.replace(/^@/, ""))
|
|
16
|
+
.transform((name) => name.replace(/^@/, ""))
|
|
17
|
+
.optional(),
|
|
17
18
|
description: z.string().optional(),
|
|
18
19
|
is_private: z.boolean().optional().default(false),
|
|
19
20
|
is_unlisted: z.boolean().optional().default(false),
|
|
@@ -33,10 +34,20 @@ export default withRouteSpec({
|
|
|
33
34
|
})
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
|
|
37
|
+
let unscoped_name = name?.includes("/") ? name?.split("/")[1] : name
|
|
38
|
+
if (!unscoped_name) {
|
|
39
|
+
const state = ctx.db.getState()
|
|
40
|
+
const count = state.packages.filter(
|
|
41
|
+
(pkg) => pkg.creator_account_id === ctx.auth.account_id,
|
|
42
|
+
).length
|
|
43
|
+
|
|
44
|
+
unscoped_name = `untitled-package-${count}`
|
|
45
|
+
}
|
|
37
46
|
|
|
38
47
|
const newPackage = ctx.db.addPackage({
|
|
39
|
-
name
|
|
48
|
+
name: name?.includes("/")
|
|
49
|
+
? name
|
|
50
|
+
: `${ctx.auth.github_username}/${String(unscoped_name)}`,
|
|
40
51
|
description: description ?? null,
|
|
41
52
|
creator_account_id: ctx.auth.account_id,
|
|
42
53
|
owner_org_id: ctx.auth.personal_org_id,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tscircuit/fake-snippets",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.100",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
"@radix-ui/react-toggle-group": "^1.1.0",
|
|
76
76
|
"@radix-ui/react-tooltip": "^1.1.2",
|
|
77
77
|
"@tailwindcss/typography": "^0.5.16",
|
|
78
|
-
"@tscircuit/3d-viewer": "^0.0.
|
|
78
|
+
"@tscircuit/3d-viewer": "^0.0.303",
|
|
79
79
|
"@tscircuit/assembly-viewer": "^0.0.1",
|
|
80
80
|
"@tscircuit/create-snippet-url": "^0.0.8",
|
|
81
81
|
"@tscircuit/eval": "^0.0.244",
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
"@tscircuit/mm": "^0.0.8",
|
|
84
84
|
"@tscircuit/pcb-viewer": "^1.11.194",
|
|
85
85
|
"@tscircuit/prompt-benchmarks": "^0.0.28",
|
|
86
|
-
"@tscircuit/runframe": "
|
|
86
|
+
"@tscircuit/runframe": "0.0.725",
|
|
87
87
|
"@tscircuit/schematic-viewer": "^2.0.21",
|
|
88
88
|
"@types/babel__standalone": "^7.1.7",
|
|
89
89
|
"@types/bun": "^1.1.10",
|
|
@@ -105,9 +105,10 @@
|
|
|
105
105
|
"autoprefixer": "^10.4.20",
|
|
106
106
|
"change-case": "^5.4.4",
|
|
107
107
|
"circuit-json-to-bom-csv": "^0.0.7",
|
|
108
|
-
"circuit-json-to-gerber": "^0.0.
|
|
108
|
+
"circuit-json-to-gerber": "^0.0.29",
|
|
109
109
|
"circuit-json-to-pnp-csv": "^0.0.7",
|
|
110
110
|
"circuit-json-to-readable-netlist": "^0.0.13",
|
|
111
|
+
"circuit-json-to-spice": "^0.0.6",
|
|
111
112
|
"circuit-json-to-tscircuit": "^0.0.4",
|
|
112
113
|
"circuit-to-svg": "^0.0.167",
|
|
113
114
|
"class-variance-authority": "^0.7.1",
|
|
@@ -138,6 +139,7 @@
|
|
|
138
139
|
"ky": "^1.7.5",
|
|
139
140
|
"lucide-react": "^0.488.0",
|
|
140
141
|
"lz-string": "^1.5.0",
|
|
142
|
+
"marked": "^16.1.1",
|
|
141
143
|
"md5": "^2.3.0",
|
|
142
144
|
"ms": "^2.1.3",
|
|
143
145
|
"next-themes": "^0.3.0",
|
|
@@ -150,6 +152,7 @@
|
|
|
150
152
|
"react-cookie-consent": "^9.0.0",
|
|
151
153
|
"react-day-picker": "8.10.1",
|
|
152
154
|
"react-dom": "^18.3.1",
|
|
155
|
+
"react-error-boundary": "^6.0.0",
|
|
153
156
|
"react-helmet": "^6.1.0",
|
|
154
157
|
"react-helmet-async": "^2.0.5",
|
|
155
158
|
"react-hook-form": "^7.53.0",
|
package/src/App.tsx
CHANGED
|
@@ -3,6 +3,8 @@ import { Route, Switch } from "wouter"
|
|
|
3
3
|
import "./components/CmdKMenu"
|
|
4
4
|
import { ContextProviders } from "./ContextProviders"
|
|
5
5
|
import React from "react"
|
|
6
|
+
import { ReloadIcon } from "@radix-ui/react-icons"
|
|
7
|
+
import { Loader2 } from "lucide-react"
|
|
6
8
|
|
|
7
9
|
const FullPageLoader = () => (
|
|
8
10
|
<div className="fixed inset-0 flex items-center justify-center bg-white z-50">
|
|
@@ -163,10 +165,64 @@ class ErrorBoundary extends React.Component<
|
|
|
163
165
|
|
|
164
166
|
render() {
|
|
165
167
|
if (this.state.reloading) {
|
|
166
|
-
return
|
|
168
|
+
return (
|
|
169
|
+
<div className="min-h-screen flex items-center justify-center bg-gray-50 dark:bg-gray-900 px-4">
|
|
170
|
+
<div className="max-w-md w-full text-center">
|
|
171
|
+
<div className="mb-6">
|
|
172
|
+
<div className="inline-flex items-center justify-center w-16 h-16 bg-blue-100 dark:bg-blue-900 rounded-full mb-4">
|
|
173
|
+
<Loader2 className="w-8 h-8 text-blue-600 dark:text-blue-400 animate-spin" />
|
|
174
|
+
</div>
|
|
175
|
+
<h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-2">
|
|
176
|
+
Reloading Page
|
|
177
|
+
</h2>
|
|
178
|
+
<p className="text-gray-600 dark:text-gray-400">
|
|
179
|
+
We encountered an issue and are refreshing the page for you.
|
|
180
|
+
</p>
|
|
181
|
+
</div>
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
184
|
+
)
|
|
167
185
|
}
|
|
168
186
|
if (this.state.hasError) {
|
|
169
|
-
return
|
|
187
|
+
return (
|
|
188
|
+
<div className="min-h-screen flex items-center justify-center bg-gray-50 dark:bg-gray-900 px-4">
|
|
189
|
+
<div className="max-w-lg w-full text-center">
|
|
190
|
+
<div className="mb-8">
|
|
191
|
+
<div className="inline-flex items-center justify-center w-20 h-20 bg-red-100 dark:bg-red-900 rounded-full mb-6">
|
|
192
|
+
<svg
|
|
193
|
+
className="w-10 h-10 text-red-600 dark:text-red-400"
|
|
194
|
+
fill="none"
|
|
195
|
+
stroke="currentColor"
|
|
196
|
+
viewBox="0 0 24 24"
|
|
197
|
+
>
|
|
198
|
+
<path
|
|
199
|
+
strokeLinecap="round"
|
|
200
|
+
strokeLinejoin="round"
|
|
201
|
+
strokeWidth={2}
|
|
202
|
+
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"
|
|
203
|
+
/>
|
|
204
|
+
</svg>
|
|
205
|
+
</div>
|
|
206
|
+
<h1 className="text-2xl font-bold text-gray-900 dark:text-white mb-3">
|
|
207
|
+
Oops! Something went wrong
|
|
208
|
+
</h1>
|
|
209
|
+
<p className="text-gray-600 dark:text-gray-400 mb-6">
|
|
210
|
+
We're experiencing technical difficulties. The page will
|
|
211
|
+
automatically reload when you return to this tab.
|
|
212
|
+
</p>
|
|
213
|
+
</div>
|
|
214
|
+
<div className="space-y-3">
|
|
215
|
+
<button
|
|
216
|
+
onClick={this.performReload}
|
|
217
|
+
className="w-full sm:w-auto inline-flex items-center justify-center px-6 py-2 border border-transparent text-base font-medium rounded-lg text-white bg-red-600 hover:bg-red-700 dark:bg-red-500 dark:hover:bg-red-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 transition-colors duration-200"
|
|
218
|
+
>
|
|
219
|
+
<ReloadIcon className="w-4 h-4 mr-2" />
|
|
220
|
+
Reload Now
|
|
221
|
+
</button>
|
|
222
|
+
</div>
|
|
223
|
+
</div>
|
|
224
|
+
</div>
|
|
225
|
+
)
|
|
170
226
|
}
|
|
171
227
|
return this.props.children
|
|
172
228
|
}
|
|
@@ -15,7 +15,6 @@ import { useLocation } from "wouter"
|
|
|
15
15
|
import { useGlobalStore } from "@/hooks/use-global-store"
|
|
16
16
|
import { convertCircuitJsonToTscircuit } from "circuit-json-to-tscircuit"
|
|
17
17
|
import { useCreatePackageMutation } from "@/hooks/use-create-package-mutation"
|
|
18
|
-
import { generateRandomPackageName } from "@/lib/utils/package-utils"
|
|
19
18
|
import { useCreatePackageReleaseMutation } from "@/hooks/use-create-package-release-mutation"
|
|
20
19
|
import { useCreatePackageFilesMutation } from "@/hooks/use-create-package-files-mutation"
|
|
21
20
|
|
|
@@ -67,10 +66,17 @@ export function CircuitJsonImportDialog({
|
|
|
67
66
|
|
|
68
67
|
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
69
68
|
const selectedFile = e.target.files?.[0]
|
|
70
|
-
if (selectedFile
|
|
71
|
-
|
|
69
|
+
if (selectedFile) {
|
|
70
|
+
try {
|
|
71
|
+
const fileText = await selectedFile.text()
|
|
72
|
+
JSON.parse(fileText)
|
|
73
|
+
setFile(selectedFile)
|
|
74
|
+
setError(null)
|
|
75
|
+
} catch (e) {
|
|
76
|
+
setError("Please select a valid JSON file that can be parsed.")
|
|
77
|
+
}
|
|
72
78
|
} else {
|
|
73
|
-
setError("Please select a
|
|
79
|
+
setError("Please select a file.")
|
|
74
80
|
}
|
|
75
81
|
}
|
|
76
82
|
|
|
@@ -124,7 +130,6 @@ export function CircuitJsonImportDialog({
|
|
|
124
130
|
|
|
125
131
|
await createPackageMutation.mutateAsync(
|
|
126
132
|
{
|
|
127
|
-
name: `${loggedInUser?.github_username}/${generateRandomPackageName()}`,
|
|
128
133
|
description: "Imported from Circuit JSON",
|
|
129
134
|
},
|
|
130
135
|
{
|
|
@@ -12,6 +12,7 @@ import { downloadDsnFile } from "@/lib/download-fns/download-dsn-file-fn"
|
|
|
12
12
|
import { downloadFabricationFiles } from "@/lib/download-fns/download-fabrication-files"
|
|
13
13
|
import { downloadSchematicSvg } from "@/lib/download-fns/download-schematic-svg"
|
|
14
14
|
import { downloadReadableNetlist } from "@/lib/download-fns/download-readable-netlist"
|
|
15
|
+
import { downloadSpiceFile } from "@/lib/download-fns/download-spice-file"
|
|
15
16
|
import { downloadAssemblySvg } from "@/lib/download-fns/download-assembly-svg"
|
|
16
17
|
import { usePcbDownloadDialog } from "@/components/dialogs/pcb-download-dialog"
|
|
17
18
|
import { downloadKicadFiles } from "@/lib/download-fns/download-kicad-files"
|
|
@@ -214,6 +215,18 @@ export function DownloadButtonAndMenu({
|
|
|
214
215
|
txt
|
|
215
216
|
</span>
|
|
216
217
|
</DropdownMenuItem>
|
|
218
|
+
<DropdownMenuItem
|
|
219
|
+
className="text-xs"
|
|
220
|
+
onSelect={() => {
|
|
221
|
+
downloadSpiceFile(circuitJson, unscopedName || "circuit")
|
|
222
|
+
}}
|
|
223
|
+
>
|
|
224
|
+
<Download className="mr-1 h-3 w-3" />
|
|
225
|
+
<span className="flex-grow mr-6">SPICE Netlist</span>
|
|
226
|
+
<span className="text-[0.6rem] opacity-80 bg-blue-500 text-white font-mono rounded-md px-1 text-center py-0.5 mr-1">
|
|
227
|
+
spice
|
|
228
|
+
</span>
|
|
229
|
+
</DropdownMenuItem>
|
|
217
230
|
<DropdownMenuItem
|
|
218
231
|
className="text-xs"
|
|
219
232
|
onSelect={() => {
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import React, { useState } from "react"
|
|
2
2
|
import { cn } from "@/lib/utils"
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
File,
|
|
5
|
+
Folder,
|
|
6
|
+
MoreVertical,
|
|
7
|
+
PanelRightOpen,
|
|
8
|
+
Plus,
|
|
9
|
+
Trash2,
|
|
10
|
+
Pencil,
|
|
11
|
+
} from "lucide-react"
|
|
4
12
|
import { TreeView, TreeDataItem } from "@/components/ui/tree-view"
|
|
5
13
|
import { isHiddenFile } from "./ViewPackagePage/utils/is-hidden-file"
|
|
6
14
|
import { Input } from "@/components/ui/input"
|
|
@@ -16,8 +24,12 @@ import type {
|
|
|
16
24
|
ICreateFileResult,
|
|
17
25
|
IDeleteFileProps,
|
|
18
26
|
IDeleteFileResult,
|
|
27
|
+
IRenameFileProps,
|
|
28
|
+
IRenameFileResult,
|
|
19
29
|
} from "@/hooks/useFileManagement"
|
|
20
30
|
import { useToast } from "@/hooks/use-toast"
|
|
31
|
+
import { useGlobalStore } from "@/hooks/use-global-store"
|
|
32
|
+
import type { Package } from "fake-snippets-api/lib/db/schema"
|
|
21
33
|
type FileName = string
|
|
22
34
|
|
|
23
35
|
interface FileSidebarProps {
|
|
@@ -28,6 +40,10 @@ interface FileSidebarProps {
|
|
|
28
40
|
fileSidebarState: ReturnType<typeof useState<boolean>>
|
|
29
41
|
handleCreateFile: (props: ICreateFileProps) => ICreateFileResult
|
|
30
42
|
handleDeleteFile: (props: IDeleteFileProps) => IDeleteFileResult
|
|
43
|
+
handleRenameFile: (props: IRenameFileProps) => IRenameFileResult
|
|
44
|
+
isCreatingFile: boolean
|
|
45
|
+
setIsCreatingFile: React.Dispatch<React.SetStateAction<boolean>>
|
|
46
|
+
pkg?: Package
|
|
31
47
|
}
|
|
32
48
|
|
|
33
49
|
const FileSidebar: React.FC<FileSidebarProps> = ({
|
|
@@ -38,12 +54,18 @@ const FileSidebar: React.FC<FileSidebarProps> = ({
|
|
|
38
54
|
fileSidebarState,
|
|
39
55
|
handleCreateFile,
|
|
40
56
|
handleDeleteFile,
|
|
57
|
+
handleRenameFile,
|
|
58
|
+
isCreatingFile,
|
|
59
|
+
setIsCreatingFile,
|
|
60
|
+
pkg,
|
|
41
61
|
}) => {
|
|
42
62
|
const [sidebarOpen, setSidebarOpen] = fileSidebarState
|
|
43
63
|
const [newFileName, setNewFileName] = useState("")
|
|
44
|
-
const [isCreatingFile, setIsCreatingFile] = useState(false)
|
|
45
64
|
const [errorMessage, setErrorMessage] = useState("")
|
|
65
|
+
const [renamingFile, setRenamingFile] = useState<string | null>(null)
|
|
46
66
|
const { toast } = useToast()
|
|
67
|
+
const session = useGlobalStore((s) => s.session)
|
|
68
|
+
const canModifyFiles = true
|
|
47
69
|
|
|
48
70
|
const transformFilesToTreeData = (
|
|
49
71
|
files: Record<FileName, string>,
|
|
@@ -79,20 +101,59 @@ const FileSidebar: React.FC<FileSidebarProps> = ({
|
|
|
79
101
|
) {
|
|
80
102
|
currentNode[segment] = {
|
|
81
103
|
id: itemId,
|
|
82
|
-
name:
|
|
104
|
+
name: segment,
|
|
105
|
+
isRenaming: renamingFile === itemId,
|
|
106
|
+
onRename: (newFilename: string) => {
|
|
107
|
+
// Preserve the folder structure when renaming
|
|
108
|
+
const oldPath = itemId
|
|
109
|
+
const pathParts = oldPath.split("/").filter((part) => part !== "") // Remove empty segments
|
|
110
|
+
let newPath: string
|
|
111
|
+
|
|
112
|
+
if (pathParts.length > 1) {
|
|
113
|
+
// File is in a folder, preserve the folder structure
|
|
114
|
+
const folderPath = pathParts.slice(0, -1).join("/")
|
|
115
|
+
newPath = folderPath + "/" + newFilename
|
|
116
|
+
} else {
|
|
117
|
+
// File is in root, just use the new filename
|
|
118
|
+
newPath = newFilename
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Preserve leading slash if original path had one
|
|
122
|
+
if (oldPath.startsWith("/") && !newPath.startsWith("/")) {
|
|
123
|
+
newPath = "/" + newPath
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const { fileRenamed } = handleRenameFile({
|
|
127
|
+
oldFilename: itemId,
|
|
128
|
+
newFilename: newPath,
|
|
129
|
+
onError: (error) => {
|
|
130
|
+
toast({
|
|
131
|
+
title: `Error renaming file`,
|
|
132
|
+
description: error.message,
|
|
133
|
+
variant: "destructive",
|
|
134
|
+
})
|
|
135
|
+
},
|
|
136
|
+
})
|
|
137
|
+
if (fileRenamed) {
|
|
138
|
+
setRenamingFile(null)
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
onCancelRename: () => {
|
|
142
|
+
setRenamingFile(null)
|
|
143
|
+
},
|
|
83
144
|
icon: isLeafNode ? File : Folder,
|
|
84
145
|
onClick: isLeafNode ? () => onFileSelect(absolutePath) : undefined,
|
|
85
146
|
draggable: false,
|
|
86
147
|
droppable: !isLeafNode,
|
|
87
148
|
children: isLeafNode ? undefined : {},
|
|
88
|
-
actions: (
|
|
149
|
+
actions: canModifyFiles ? (
|
|
89
150
|
<>
|
|
90
151
|
<DropdownMenu key={itemId}>
|
|
91
152
|
<DropdownMenuTrigger asChild>
|
|
92
153
|
<MoreVertical className="w-4 h-4 text-gray-500 hover:text-gray-700" />
|
|
93
154
|
</DropdownMenuTrigger>
|
|
94
155
|
<DropdownMenuContent
|
|
95
|
-
className="w-
|
|
156
|
+
className="w-fit bg-white shadow-lg rounded-md border-4 z-[100] border-white"
|
|
96
157
|
style={{
|
|
97
158
|
position: "absolute",
|
|
98
159
|
top: "100%",
|
|
@@ -103,13 +164,24 @@ const FileSidebar: React.FC<FileSidebarProps> = ({
|
|
|
103
164
|
}}
|
|
104
165
|
>
|
|
105
166
|
<DropdownMenuGroup>
|
|
167
|
+
{isLeafNode && (
|
|
168
|
+
<DropdownMenuItem
|
|
169
|
+
onClick={() => {
|
|
170
|
+
setRenamingFile(itemId)
|
|
171
|
+
}}
|
|
172
|
+
className="flex items-center px-3 py-1 text-xs text-black hover:bg-gray-100 cursor-pointer"
|
|
173
|
+
>
|
|
174
|
+
<Pencil className="mr-2 h-3 w-3" />
|
|
175
|
+
Rename
|
|
176
|
+
</DropdownMenuItem>
|
|
177
|
+
)}
|
|
106
178
|
<DropdownMenuItem
|
|
107
179
|
onClick={() => {
|
|
108
180
|
const { fileDeleted } = handleDeleteFile({
|
|
109
|
-
filename:
|
|
181
|
+
filename: itemId,
|
|
110
182
|
onError: (error) => {
|
|
111
183
|
toast({
|
|
112
|
-
title: `Error deleting file ${
|
|
184
|
+
title: `Error deleting file ${itemId}`,
|
|
113
185
|
description: error.message,
|
|
114
186
|
})
|
|
115
187
|
},
|
|
@@ -118,15 +190,16 @@ const FileSidebar: React.FC<FileSidebarProps> = ({
|
|
|
118
190
|
setErrorMessage("")
|
|
119
191
|
}
|
|
120
192
|
}}
|
|
121
|
-
className="flex items-center px-
|
|
193
|
+
className="flex items-center px-3 py-1 text-xs text-red-600 hover:bg-gray-100 cursor-pointer"
|
|
122
194
|
>
|
|
195
|
+
<Trash2 className="mr-2 h-3 w-3" />
|
|
123
196
|
Delete
|
|
124
197
|
</DropdownMenuItem>
|
|
125
198
|
</DropdownMenuGroup>
|
|
126
199
|
</DropdownMenuContent>
|
|
127
200
|
</DropdownMenu>
|
|
128
201
|
</>
|
|
129
|
-
),
|
|
202
|
+
) : undefined,
|
|
130
203
|
}
|
|
131
204
|
}
|
|
132
205
|
|
|
@@ -152,7 +225,7 @@ const FileSidebar: React.FC<FileSidebarProps> = ({
|
|
|
152
225
|
}
|
|
153
226
|
|
|
154
227
|
const treeData = transformFilesToTreeData(files)
|
|
155
|
-
|
|
228
|
+
|
|
156
229
|
const handleCreateFileInline = () => {
|
|
157
230
|
const { newFileCreated } = handleCreateFile({
|
|
158
231
|
newFileName,
|
|
@@ -19,34 +19,46 @@ export const LogContent = ({
|
|
|
19
19
|
type?: "info" | "success" | "error"
|
|
20
20
|
msg?: string
|
|
21
21
|
message?: string
|
|
22
|
-
timestamp?: string
|
|
22
|
+
timestamp?: string | number
|
|
23
|
+
[key: string]: unknown
|
|
23
24
|
}>
|
|
24
25
|
error?: ErrorObject | string | null
|
|
25
26
|
}) => {
|
|
26
27
|
return (
|
|
27
28
|
<div className="font-mono text-xs space-y-1 min-w-0">
|
|
28
29
|
{logs.map((log, i) => {
|
|
29
|
-
const
|
|
30
|
+
const { type, msg, message, timestamp, ...rest } = log
|
|
31
|
+
const text = msg ?? message
|
|
30
32
|
if (!text) return null
|
|
31
33
|
|
|
32
34
|
return (
|
|
33
35
|
<div
|
|
34
36
|
key={i}
|
|
35
37
|
className={`break-words whitespace-pre-wrap ${
|
|
36
|
-
|
|
38
|
+
type === "error"
|
|
37
39
|
? "text-red-600"
|
|
38
|
-
:
|
|
40
|
+
: type === "success"
|
|
39
41
|
? "text-green-600"
|
|
40
42
|
: "text-gray-600"
|
|
41
43
|
}`}
|
|
42
44
|
>
|
|
43
|
-
{
|
|
45
|
+
{timestamp !== undefined && (
|
|
44
46
|
<span className="text-gray-500 whitespace-nowrap">
|
|
45
|
-
{new Date(
|
|
47
|
+
{new Date(Number(timestamp)).toLocaleTimeString()}
|
|
46
48
|
</span>
|
|
47
49
|
)}
|
|
48
|
-
{
|
|
50
|
+
{timestamp !== undefined && " "}
|
|
49
51
|
<span className="break-all">{text}</span>
|
|
52
|
+
{Object.keys(rest).filter((k) => k !== "package_release_id")
|
|
53
|
+
.length > 0 && (
|
|
54
|
+
<span className="text-gray-500">
|
|
55
|
+
{" "}
|
|
56
|
+
{Object.entries(rest)
|
|
57
|
+
.filter(([key]) => key !== "package_release_id")
|
|
58
|
+
.map(([key, value]) => `${key}: ${String(value)}`)
|
|
59
|
+
.join(" ")}
|
|
60
|
+
</span>
|
|
61
|
+
)}
|
|
50
62
|
</div>
|
|
51
63
|
)
|
|
52
64
|
})}
|