@tscircuit/fake-snippets 0.0.81 → 0.0.83
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_releases/create.test.ts +3 -3
- package/dist/bundle.js +41 -13
- package/dist/index.d.ts +24 -4
- package/dist/index.js +5 -1
- package/dist/schema.d.ts +37 -5
- package/dist/schema.js +5 -1
- package/fake-snippets-api/lib/db/schema.ts +5 -1
- package/fake-snippets-api/lib/public-mapping/public-map-package-release.ts +14 -1
- package/fake-snippets-api/routes/api/package_releases/get.ts +11 -3
- package/fake-snippets-api/routes/api/package_releases/list.ts +8 -1
- package/fake-snippets-api/routes/api/packages/generate_from_jlcpcb.ts +3 -3
- package/package.json +1 -1
- package/src/App.tsx +0 -2
- package/src/components/JLCPCBImportDialog.tsx +164 -62
- package/src/components/PackageBuildsPage/LogContent.tsx +12 -5
- package/src/components/PackageBuildsPage/PackageBuildDetailsPage.tsx +8 -7
- package/src/components/PackageBuildsPage/build-preview-content.tsx +1 -1
- package/src/components/PackageBuildsPage/collapsible-section.tsx +14 -46
- package/src/components/PackageBuildsPage/package-build-details-panel.tsx +28 -10
- package/src/components/PackageBuildsPage/package-build-header.tsx +16 -4
- package/src/components/ViewPackagePage/components/build-status.tsx +24 -85
- package/src/components/ViewPackagePage/components/important-files-view.tsx +8 -1
- package/src/components/ViewPackagePage/components/sidebar-releases-section.tsx +28 -5
- package/src/components/ViewPackagePage/hooks/use-toast.tsx +70 -0
- package/src/components/dialogs/{import-snippet-dialog.tsx → import-package-dialog.tsx} +25 -24
- package/src/components/package-port/CodeEditor.tsx +9 -1
- package/src/components/package-port/CodeEditorHeader.tsx +7 -6
- package/src/components/ui/toaster.tsx +1 -33
- package/src/hooks/use-current-package-release.ts +14 -3
- package/src/hooks/use-now.ts +12 -0
- package/src/hooks/use-package-release.ts +17 -15
- package/src/hooks/use-toast.tsx +50 -169
- package/src/pages/dashboard.tsx +3 -1
- package/src/pages/user-profile.tsx +9 -2
- package/src/pages/view-package.tsx +1 -0
- package/.github/workflows/formatbot.yml +0 -63
- package/src/components/ViewPackagePage/hooks/use-toast.ts +0 -191
|
@@ -20,43 +20,102 @@ interface JLCPCBImportDialogProps {
|
|
|
20
20
|
onOpenChange: (open: boolean) => void
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
23
|
+
interface ImportState {
|
|
24
|
+
isLoading: boolean
|
|
25
|
+
error: string | null
|
|
26
|
+
existingComponent: {
|
|
27
|
+
partNumber: string
|
|
28
|
+
username: string
|
|
29
|
+
} | null
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface JLCPCBResponse {
|
|
33
|
+
ok: boolean
|
|
34
|
+
package: {
|
|
35
|
+
package_id: string
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface APIError {
|
|
40
|
+
status: number
|
|
41
|
+
data?: {
|
|
42
|
+
message?: string
|
|
43
|
+
existing_part_number?: string
|
|
44
|
+
part_number?: string
|
|
45
|
+
error?: {
|
|
46
|
+
message?: string
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const extractErrorMessage = (error: APIError): string => {
|
|
52
|
+
return (
|
|
53
|
+
error?.data?.message ||
|
|
54
|
+
error?.data?.error?.message ||
|
|
55
|
+
"An unexpected error occurred"
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const extractExistingPartNumber = (
|
|
60
|
+
error: APIError,
|
|
61
|
+
fallback: string,
|
|
62
|
+
): string => {
|
|
63
|
+
return error?.data?.message || error?.data?.error?.message || fallback
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const useJLCPCBImport = () => {
|
|
67
|
+
const [state, setState] = useState<ImportState>({
|
|
68
|
+
isLoading: false,
|
|
69
|
+
error: null,
|
|
70
|
+
existingComponent: null,
|
|
71
|
+
})
|
|
72
|
+
|
|
32
73
|
const axios = useAxios()
|
|
33
74
|
const { toast } = useToast()
|
|
34
75
|
const [, navigate] = useLocation()
|
|
35
|
-
const isLoggedIn = useGlobalStore((s) => Boolean(s.session))
|
|
36
76
|
const session = useGlobalStore((s) => s.session)
|
|
37
77
|
|
|
38
|
-
const
|
|
39
|
-
|
|
78
|
+
const resetState = () => {
|
|
79
|
+
setState({
|
|
80
|
+
isLoading: false,
|
|
81
|
+
error: null,
|
|
82
|
+
existingComponent: null,
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const importComponent = async (partNumber: string) => {
|
|
87
|
+
if (!partNumber.startsWith("C") || partNumber.length < 2) {
|
|
40
88
|
toast({
|
|
41
89
|
title: "Invalid Part Number",
|
|
42
|
-
description:
|
|
90
|
+
description:
|
|
91
|
+
"JLCPCB part numbers should start with 'C' and be at least 2 characters long.",
|
|
43
92
|
variant: "destructive",
|
|
44
93
|
})
|
|
45
|
-
return
|
|
94
|
+
return { success: false }
|
|
46
95
|
}
|
|
47
96
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
97
|
+
setState((prev) => ({
|
|
98
|
+
...prev,
|
|
99
|
+
isLoading: true,
|
|
100
|
+
error: null,
|
|
101
|
+
existingComponent: null,
|
|
102
|
+
}))
|
|
51
103
|
|
|
52
104
|
try {
|
|
53
|
-
const response = await axios.post(
|
|
54
|
-
|
|
55
|
-
|
|
105
|
+
const response = await axios.post<JLCPCBResponse>(
|
|
106
|
+
"/packages/generate_from_jlcpcb",
|
|
107
|
+
{
|
|
108
|
+
jlcpcb_part_number: partNumber,
|
|
109
|
+
},
|
|
110
|
+
)
|
|
111
|
+
|
|
56
112
|
if (!response.data.ok) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
113
|
+
setState((prev) => ({
|
|
114
|
+
...prev,
|
|
115
|
+
isLoading: false,
|
|
116
|
+
error: "Failed to generate package from JLCPCB part",
|
|
117
|
+
}))
|
|
118
|
+
return { success: false }
|
|
60
119
|
}
|
|
61
120
|
|
|
62
121
|
const { package: generatedPackage } = response.data
|
|
@@ -66,27 +125,84 @@ export function JLCPCBImportDialog({
|
|
|
66
125
|
description: "JLCPCB component has been imported successfully.",
|
|
67
126
|
})
|
|
68
127
|
|
|
69
|
-
onOpenChange(false)
|
|
70
128
|
navigate(`/editor?package_id=${generatedPackage.package_id}`)
|
|
129
|
+
return { success: true }
|
|
71
130
|
} catch (error: any) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
131
|
+
const apiError = error as APIError
|
|
132
|
+
|
|
133
|
+
if (apiError.status === 404) {
|
|
134
|
+
setState((prev) => ({
|
|
135
|
+
...prev,
|
|
136
|
+
isLoading: false,
|
|
137
|
+
error: `Component with JLCPCB part number ${partNumber} not found`,
|
|
138
|
+
}))
|
|
139
|
+
} else if (apiError.status === 409) {
|
|
140
|
+
const existingPartNumber = extractExistingPartNumber(
|
|
141
|
+
apiError,
|
|
142
|
+
partNumber,
|
|
143
|
+
)
|
|
144
|
+
setState((prev) => ({
|
|
145
|
+
...prev,
|
|
146
|
+
isLoading: false,
|
|
147
|
+
existingComponent: {
|
|
148
|
+
partNumber: existingPartNumber,
|
|
149
|
+
username: session?.github_username || "",
|
|
150
|
+
},
|
|
151
|
+
}))
|
|
76
152
|
} else {
|
|
77
|
-
|
|
153
|
+
const errorMessage = extractErrorMessage(apiError)
|
|
154
|
+
setState((prev) => ({ ...prev, isLoading: false, error: errorMessage }))
|
|
155
|
+
|
|
78
156
|
toast({
|
|
79
157
|
title: "Import Failed",
|
|
80
|
-
description:
|
|
81
|
-
"Failed to import the JLCPCB component. Please try again.",
|
|
158
|
+
description: errorMessage,
|
|
82
159
|
variant: "destructive",
|
|
83
160
|
})
|
|
84
161
|
}
|
|
85
|
-
|
|
86
|
-
|
|
162
|
+
|
|
163
|
+
return { success: false }
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
...state,
|
|
169
|
+
importComponent,
|
|
170
|
+
resetState,
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function JLCPCBImportDialog({
|
|
175
|
+
open,
|
|
176
|
+
onOpenChange,
|
|
177
|
+
}: JLCPCBImportDialogProps) {
|
|
178
|
+
const [partNumber, setPartNumber] = useState("")
|
|
179
|
+
const isLoggedIn = useGlobalStore((s) => Boolean(s.session))
|
|
180
|
+
|
|
181
|
+
const { isLoading, error, existingComponent, importComponent, resetState } =
|
|
182
|
+
useJLCPCBImport()
|
|
183
|
+
|
|
184
|
+
const handleImport = async () => {
|
|
185
|
+
const result = await importComponent(partNumber)
|
|
186
|
+
if (result.success) {
|
|
187
|
+
onOpenChange(false)
|
|
87
188
|
}
|
|
88
189
|
}
|
|
89
190
|
|
|
191
|
+
const handleInputChange = (value: string) => {
|
|
192
|
+
setPartNumber(value)
|
|
193
|
+
resetState()
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const createGitHubIssue = () => {
|
|
197
|
+
const issueTitle = `[${partNumber}] Failed to import from JLCPCB`
|
|
198
|
+
const issueBody = `I tried to import the part number ${partNumber} from JLCPCB, but it failed. Here's the error I got:\n\`\`\`\n${error}\n\`\`\`\n\nCould be an issue in \`fetchEasyEDAComponent\` or \`convertRawEasyEdaToTs\``
|
|
199
|
+
const issueLabels = "snippets,good first issue"
|
|
200
|
+
const url = `https://github.com/tscircuit/easyeda-converter/issues/new?title=${encodeURIComponent(
|
|
201
|
+
issueTitle,
|
|
202
|
+
)}&body=${encodeURIComponent(issueBody)}&labels=${encodeURIComponent(issueLabels)}`
|
|
203
|
+
window.open(url, "_blank")
|
|
204
|
+
}
|
|
205
|
+
|
|
90
206
|
return (
|
|
91
207
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
92
208
|
<DialogContent>
|
|
@@ -96,6 +212,7 @@ export function JLCPCBImportDialog({
|
|
|
96
212
|
Enter the JLCPCB part number to import the component.
|
|
97
213
|
</DialogDescription>
|
|
98
214
|
</DialogHeader>
|
|
215
|
+
|
|
99
216
|
<div className="py-4 text-center">
|
|
100
217
|
<a
|
|
101
218
|
href="https://yaqwsx.github.io/jlcparts/#/"
|
|
@@ -105,16 +222,13 @@ export function JLCPCBImportDialog({
|
|
|
105
222
|
>
|
|
106
223
|
JLCPCB Part Search
|
|
107
224
|
</a>
|
|
225
|
+
|
|
108
226
|
<Input
|
|
109
227
|
className="mt-3"
|
|
110
228
|
placeholder="Enter JLCPCB part number (e.g., C46749)"
|
|
111
229
|
value={partNumber}
|
|
112
230
|
disabled={isLoading}
|
|
113
|
-
onChange={(e) =>
|
|
114
|
-
setPartNumber(e.target.value)
|
|
115
|
-
setError(null)
|
|
116
|
-
setHasBeenImportedToAccountAlready(false)
|
|
117
|
-
}}
|
|
231
|
+
onChange={(e) => handleInputChange(e.target.value)}
|
|
118
232
|
onKeyDown={(e) => {
|
|
119
233
|
if (
|
|
120
234
|
e.key === "Enter" &&
|
|
@@ -126,43 +240,31 @@ export function JLCPCBImportDialog({
|
|
|
126
240
|
}
|
|
127
241
|
}}
|
|
128
242
|
/>
|
|
129
|
-
{error && !hasBeenImportedToAccountAlready && (
|
|
130
|
-
<p className="bg-red-100 p-2 mt-2 pre-wrap">{error}</p>
|
|
131
|
-
)}
|
|
132
243
|
|
|
133
|
-
{error && !
|
|
134
|
-
|
|
135
|
-
<
|
|
136
|
-
|
|
137
|
-
onClick={
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
issueTitle,
|
|
143
|
-
)}&body=${encodeURIComponent(
|
|
144
|
-
issueBody,
|
|
145
|
-
)}&labels=${encodeURIComponent(issueLabels)}`
|
|
146
|
-
window.open(url, "_blank")
|
|
147
|
-
}}
|
|
148
|
-
>
|
|
149
|
-
File Issue on GitHub (prefilled)
|
|
150
|
-
</Button>
|
|
151
|
-
</div>
|
|
244
|
+
{error && !existingComponent && (
|
|
245
|
+
<>
|
|
246
|
+
<p className="bg-red-100 p-2 mt-2 pre-wrap">{error}</p>
|
|
247
|
+
<div className="flex justify-end mt-2">
|
|
248
|
+
<Button variant="default" onClick={createGitHubIssue}>
|
|
249
|
+
File Issue on GitHub (prefilled)
|
|
250
|
+
</Button>
|
|
251
|
+
</div>
|
|
252
|
+
</>
|
|
152
253
|
)}
|
|
153
254
|
|
|
154
|
-
{
|
|
255
|
+
{existingComponent && (
|
|
155
256
|
<p className="p-2 mt-2 pre-wrap text-md text-green-600">
|
|
156
257
|
This part number has already been imported to your profile.{" "}
|
|
157
258
|
<PrefetchPageLink
|
|
158
259
|
className="text-blue-500 hover:underline"
|
|
159
|
-
href={`/${
|
|
260
|
+
href={`/${existingComponent.username}/${existingComponent.partNumber}`}
|
|
160
261
|
>
|
|
161
262
|
View it here
|
|
162
263
|
</PrefetchPageLink>
|
|
163
264
|
</p>
|
|
164
265
|
)}
|
|
165
266
|
</div>
|
|
267
|
+
|
|
166
268
|
<DialogFooter>
|
|
167
269
|
<Button onClick={handleImport} disabled={isLoading || !isLoggedIn}>
|
|
168
270
|
{!isLoggedIn
|
|
@@ -23,26 +23,33 @@ export const LogContent = ({
|
|
|
23
23
|
error?: ErrorObject | string | null
|
|
24
24
|
}) => {
|
|
25
25
|
return (
|
|
26
|
-
<div className="
|
|
26
|
+
<div className="font-mono text-xs space-y-1 min-w-0">
|
|
27
27
|
{logs.map(
|
|
28
28
|
(log, i) =>
|
|
29
29
|
log.timestamp &&
|
|
30
30
|
log.message && (
|
|
31
31
|
<div
|
|
32
32
|
key={i}
|
|
33
|
-
className={
|
|
33
|
+
className={`break-words whitespace-pre-wrap ${
|
|
34
34
|
log.type === "error"
|
|
35
35
|
? "text-red-600"
|
|
36
36
|
: log.type === "success"
|
|
37
37
|
? "text-green-600"
|
|
38
38
|
: "text-gray-600"
|
|
39
|
-
}
|
|
39
|
+
}`}
|
|
40
40
|
>
|
|
41
|
-
|
|
41
|
+
<span className="text-gray-500 whitespace-nowrap">
|
|
42
|
+
{new Date(log.timestamp).toLocaleTimeString()}
|
|
43
|
+
</span>{" "}
|
|
44
|
+
<span className="break-all">{log.message}</span>
|
|
42
45
|
</div>
|
|
43
46
|
),
|
|
44
47
|
)}
|
|
45
|
-
{error &&
|
|
48
|
+
{error && (
|
|
49
|
+
<div className="text-red-600 break-words whitespace-pre-wrap">
|
|
50
|
+
{getErrorText(error)}
|
|
51
|
+
</div>
|
|
52
|
+
)}
|
|
46
53
|
</div>
|
|
47
54
|
)
|
|
48
55
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
|
+
import { useCurrentPackageRelease } from "@/hooks/use-current-package-release"
|
|
4
|
+
import { PackageRelease } from "fake-snippets-api/lib/db/schema"
|
|
3
5
|
import { useState } from "react"
|
|
6
|
+
import { LogContent } from "./LogContent"
|
|
4
7
|
import { BuildPreviewContent } from "./build-preview-content"
|
|
8
|
+
import { CollapsibleSection } from "./collapsible-section"
|
|
5
9
|
import { PackageBuildDetailsPanel } from "./package-build-details-panel"
|
|
6
10
|
import { PackageBuildHeader } from "./package-build-header"
|
|
7
|
-
import { CollapsibleSection } from "./collapsible-section"
|
|
8
|
-
import { useCurrentPackageRelease } from "@/hooks/use-current-package-release"
|
|
9
|
-
import { LogContent } from "./LogContent"
|
|
10
|
-
import { PackageRelease } from "fake-snippets-api/lib/db/schema"
|
|
11
11
|
|
|
12
12
|
function computeDuration(
|
|
13
13
|
startedAt: string | null | undefined,
|
|
@@ -18,7 +18,10 @@ function computeDuration(
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
export const PackageBuildDetailsPage = () => {
|
|
21
|
-
const { packageRelease } = useCurrentPackageRelease({
|
|
21
|
+
const { packageRelease } = useCurrentPackageRelease({
|
|
22
|
+
include_logs: true,
|
|
23
|
+
refetchInterval: 2000,
|
|
24
|
+
})
|
|
22
25
|
const [openSections, setOpenSections] = useState<Record<string, boolean>>({})
|
|
23
26
|
|
|
24
27
|
const {
|
|
@@ -73,7 +76,6 @@ export const PackageBuildDetailsPage = () => {
|
|
|
73
76
|
transpilation_completed_at,
|
|
74
77
|
)}
|
|
75
78
|
displayStatus={transpilation_display_status}
|
|
76
|
-
error={transpilation_error}
|
|
77
79
|
isOpen={openSections.summary}
|
|
78
80
|
onToggle={() => toggleSection("summary")}
|
|
79
81
|
>
|
|
@@ -94,7 +96,6 @@ export const PackageBuildDetailsPage = () => {
|
|
|
94
96
|
circuit_json_build_completed_at,
|
|
95
97
|
)}
|
|
96
98
|
displayStatus={circuit_json_build_display_status}
|
|
97
|
-
error={circuit_json_build_error}
|
|
98
99
|
isOpen={openSections.logs}
|
|
99
100
|
onToggle={() => toggleSection("logs")}
|
|
100
101
|
>
|
|
@@ -2,7 +2,7 @@ import { useCurrentPackageInfo } from "@/hooks/use-current-package-info"
|
|
|
2
2
|
import { useCurrentPackageRelease } from "@/hooks/use-current-package-release"
|
|
3
3
|
|
|
4
4
|
export function BuildPreviewContent() {
|
|
5
|
-
const { packageRelease } = useCurrentPackageRelease()
|
|
5
|
+
const { packageRelease } = useCurrentPackageRelease({ refetchInterval: 2000 })
|
|
6
6
|
const { packageInfo } = useCurrentPackageInfo()
|
|
7
7
|
|
|
8
8
|
if (!packageRelease) {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type React from "react"
|
|
2
|
-
import { ChevronRight
|
|
3
|
-
import { Badge } from "@/components/ui/badge"
|
|
2
|
+
import { ChevronRight } from "lucide-react"
|
|
4
3
|
import {
|
|
5
4
|
Collapsible,
|
|
6
5
|
CollapsibleContent,
|
|
@@ -8,16 +7,9 @@ import {
|
|
|
8
7
|
} from "@/components/ui/collapsible"
|
|
9
8
|
import { getColorForDisplayStatus } from "./getColorForDisplayStatus"
|
|
10
9
|
import { PackageRelease } from "fake-snippets-api/lib/db/schema"
|
|
11
|
-
import { ErrorObjectOrString
|
|
10
|
+
import { ErrorObjectOrString } from "./ErrorObject"
|
|
12
11
|
import { capitalCase } from "./capitalCase"
|
|
13
12
|
|
|
14
|
-
type BadgeInfo = {
|
|
15
|
-
text: string
|
|
16
|
-
variant?: "default" | "secondary" | "destructive"
|
|
17
|
-
className?: string
|
|
18
|
-
icon?: React.ReactNode
|
|
19
|
-
}
|
|
20
|
-
|
|
21
13
|
interface CollapsibleSectionProps {
|
|
22
14
|
title: string
|
|
23
15
|
duration?: string
|
|
@@ -25,69 +17,45 @@ interface CollapsibleSectionProps {
|
|
|
25
17
|
displayStatus?: PackageRelease["display_status"]
|
|
26
18
|
isOpen: boolean
|
|
27
19
|
onToggle: () => void
|
|
28
|
-
badges?: Array<BadgeInfo>
|
|
29
20
|
children?: React.ReactNode
|
|
30
21
|
}
|
|
31
22
|
|
|
32
23
|
export function CollapsibleSection({
|
|
33
24
|
title,
|
|
34
25
|
duration,
|
|
35
|
-
error,
|
|
36
26
|
displayStatus,
|
|
37
27
|
isOpen,
|
|
38
28
|
onToggle,
|
|
39
|
-
badges = [],
|
|
40
29
|
children,
|
|
41
30
|
}: CollapsibleSectionProps) {
|
|
42
31
|
return (
|
|
43
32
|
<Collapsible open={isOpen} onOpenChange={onToggle}>
|
|
44
33
|
<CollapsibleTrigger asChild>
|
|
45
|
-
<div className="flex items-center justify-between p-4 bg-white border border-gray-200 rounded-lg cursor-pointer hover:bg-gray-100">
|
|
46
|
-
<div className="flex items-center gap-2">
|
|
34
|
+
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2 p-4 bg-white border border-gray-200 rounded-lg cursor-pointer hover:bg-gray-100">
|
|
35
|
+
<div className="flex items-center gap-2 min-w-0">
|
|
47
36
|
<ChevronRight
|
|
48
|
-
className={`w-4 h-4 transition-transform ${isOpen ? "rotate-90" : ""}`}
|
|
37
|
+
className={`w-4 h-4 flex-shrink-0 transition-transform ${isOpen ? "rotate-90" : ""}`}
|
|
49
38
|
/>
|
|
50
|
-
<span className="font-medium">{title}</span>
|
|
39
|
+
<span className="font-medium truncate">{title}</span>
|
|
51
40
|
</div>
|
|
52
|
-
<div className="flex items-center gap-2">
|
|
53
|
-
{[
|
|
54
|
-
...badges,
|
|
55
|
-
...(error
|
|
56
|
-
? [
|
|
57
|
-
{
|
|
58
|
-
text: getErrorText(error),
|
|
59
|
-
variant: "destructive",
|
|
60
|
-
} as BadgeInfo,
|
|
61
|
-
]
|
|
62
|
-
: []),
|
|
63
|
-
].map((badge, index) => (
|
|
64
|
-
<Badge
|
|
65
|
-
key={index}
|
|
66
|
-
variant={badge.variant || "secondary"}
|
|
67
|
-
className={
|
|
68
|
-
badge.className ||
|
|
69
|
-
"bg-gray-200 text-gray-700 flex items-center gap-1"
|
|
70
|
-
}
|
|
71
|
-
>
|
|
72
|
-
{badge.icon}
|
|
73
|
-
{badge.text}
|
|
74
|
-
</Badge>
|
|
75
|
-
))}
|
|
41
|
+
<div className="flex items-center gap-2 flex-shrink-0 ml-6 sm:ml-0">
|
|
76
42
|
{duration && (
|
|
77
|
-
<span className="text-sm text-gray-600">
|
|
43
|
+
<span className="text-sm text-gray-600 whitespace-nowrap">
|
|
44
|
+
{duration}
|
|
45
|
+
</span>
|
|
78
46
|
)}
|
|
79
47
|
<div
|
|
80
|
-
className={`w-2 h-2 rounded-lg ${getColorForDisplayStatus(displayStatus)}`}
|
|
48
|
+
className={`w-2 h-2 rounded-lg flex-shrink-0 ${getColorForDisplayStatus(displayStatus)}`}
|
|
81
49
|
/>
|
|
82
|
-
<div className="text-gray-600 text-xs font-medium">
|
|
50
|
+
<div className="text-gray-600 text-xs font-medium whitespace-nowrap">
|
|
83
51
|
{capitalCase(displayStatus) || "???"}
|
|
84
52
|
</div>
|
|
85
53
|
</div>
|
|
86
54
|
</div>
|
|
87
55
|
</CollapsibleTrigger>
|
|
88
56
|
<CollapsibleContent>
|
|
89
|
-
<div className="
|
|
90
|
-
{children}
|
|
57
|
+
<div className="bg-white border-x border-b border-gray-200 rounded-b-lg overflow-hidden">
|
|
58
|
+
<div className="p-4 overflow-x-auto max-w-full">{children}</div>
|
|
91
59
|
</div>
|
|
92
60
|
</CollapsibleContent>
|
|
93
61
|
</Collapsible>
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { Globe, GitBranch, GitCommit, Clock } from "lucide-react"
|
|
2
1
|
import { useCurrentPackageRelease } from "@/hooks/use-current-package-release"
|
|
3
|
-
import {
|
|
2
|
+
import { useNow } from "@/hooks/use-now"
|
|
4
3
|
import { timeAgo } from "@/lib/utils/timeAgo"
|
|
5
4
|
import { PackageRelease } from "fake-snippets-api/lib/db/schema"
|
|
5
|
+
import { Clock, GitBranch, GitCommit, Globe } from "lucide-react"
|
|
6
|
+
import { useParams } from "wouter"
|
|
6
7
|
|
|
7
8
|
const capitalCase = (str: string) => {
|
|
8
9
|
return str.charAt(0).toUpperCase() + str.slice(1)
|
|
@@ -24,8 +25,9 @@ function getColorFromDisplayStatus(
|
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
export function PackageBuildDetailsPanel() {
|
|
27
|
-
const { packageRelease } = useCurrentPackageRelease()
|
|
28
|
+
const { packageRelease } = useCurrentPackageRelease({ refetchInterval: 2000 })
|
|
28
29
|
const { author } = useParams() // TODO use packageRelease.author_account_id when it's added by backed
|
|
30
|
+
const now = useNow(1000)
|
|
29
31
|
|
|
30
32
|
if (!packageRelease) {
|
|
31
33
|
// TODO show skeleton instead
|
|
@@ -55,6 +57,24 @@ export function PackageBuildDetailsPanel() {
|
|
|
55
57
|
commit_sha,
|
|
56
58
|
} = packageRelease
|
|
57
59
|
|
|
60
|
+
const buildStartedAt = (() => {
|
|
61
|
+
if (transpilation_started_at && circuit_json_build_started_at) {
|
|
62
|
+
return new Date(transpilation_started_at) <
|
|
63
|
+
new Date(circuit_json_build_started_at)
|
|
64
|
+
? transpilation_started_at
|
|
65
|
+
: circuit_json_build_started_at
|
|
66
|
+
}
|
|
67
|
+
return transpilation_started_at || circuit_json_build_started_at || null
|
|
68
|
+
})()
|
|
69
|
+
|
|
70
|
+
const buildCompletedAt =
|
|
71
|
+
circuit_json_build_completed_at || transpilation_completed_at || null
|
|
72
|
+
|
|
73
|
+
const elapsedMs = buildStartedAt
|
|
74
|
+
? (buildCompletedAt ? new Date(buildCompletedAt).getTime() : now) -
|
|
75
|
+
new Date(buildStartedAt).getTime()
|
|
76
|
+
: null
|
|
77
|
+
|
|
58
78
|
return (
|
|
59
79
|
<div className="space-y-6 bg-white p-4 border border-gray-200 rounded-lg">
|
|
60
80
|
{/* Created */}
|
|
@@ -92,15 +112,13 @@ export function PackageBuildDetailsPanel() {
|
|
|
92
112
|
<h3 className="text-sm font-medium text-gray-600 mb-2">Build Time</h3>
|
|
93
113
|
<div className="flex items-center gap-2">
|
|
94
114
|
<Clock className="w-4 h-4 text-gray-500" />
|
|
95
|
-
{
|
|
96
|
-
<span className="text-sm">
|
|
97
|
-
{total_build_duration_ms
|
|
98
|
-
? `${Math.floor(total_build_duration_ms / 1000)}s`
|
|
99
|
-
: ""}
|
|
100
|
-
</span>
|
|
115
|
+
{elapsedMs !== null && (
|
|
116
|
+
<span className="text-sm">{Math.floor(elapsedMs / 1000)}s</span>
|
|
101
117
|
)}
|
|
102
118
|
<span className="text-sm text-gray-500">
|
|
103
|
-
{
|
|
119
|
+
{buildStartedAt
|
|
120
|
+
? `Started ${timeAgo(buildStartedAt)}`
|
|
121
|
+
: "waiting..."}
|
|
104
122
|
</span>
|
|
105
123
|
</div>
|
|
106
124
|
</div>
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import { Github, RefreshCw } from "lucide-react"
|
|
2
1
|
import { Button } from "@/components/ui/button"
|
|
3
|
-
import { useParams } from "wouter"
|
|
4
|
-
import { DownloadButtonAndMenu } from "../DownloadButtonAndMenu"
|
|
5
2
|
import { useCurrentPackageRelease } from "@/hooks/use-current-package-release"
|
|
6
3
|
import { useRebuildPackageReleaseMutation } from "@/hooks/use-rebuild-package-release-mutation"
|
|
4
|
+
import { Github, RefreshCw, RotateCcw } from "lucide-react"
|
|
5
|
+
import { useParams } from "wouter"
|
|
6
|
+
import { DownloadButtonAndMenu } from "../DownloadButtonAndMenu"
|
|
7
7
|
|
|
8
8
|
export function PackageBuildHeader() {
|
|
9
9
|
const { author, packageName } = useParams()
|
|
10
|
-
const { packageRelease } = useCurrentPackageRelease(
|
|
10
|
+
const { packageRelease, refetch, isFetching } = useCurrentPackageRelease({
|
|
11
|
+
include_logs: true,
|
|
12
|
+
})
|
|
11
13
|
const { mutate: rebuildPackage, isLoading } =
|
|
12
14
|
useRebuildPackageReleaseMutation()
|
|
13
15
|
|
|
@@ -54,6 +56,16 @@ export function PackageBuildHeader() {
|
|
|
54
56
|
<RefreshCw className="w-3 h-3 sm:w-4 sm:h-4 mr-1 sm:mr-2" />
|
|
55
57
|
{isLoading ? "Rebuilding..." : "Rebuild"}
|
|
56
58
|
</Button>
|
|
59
|
+
<Button
|
|
60
|
+
variant="outline"
|
|
61
|
+
size="icon"
|
|
62
|
+
aria-label="Reload logs"
|
|
63
|
+
className="border-gray-300 bg-white hover:bg-gray-50"
|
|
64
|
+
onClick={() => refetch()}
|
|
65
|
+
disabled={isFetching}
|
|
66
|
+
>
|
|
67
|
+
<RotateCcw className="w-3 h-3 sm:w-4 sm:h-4" />
|
|
68
|
+
</Button>
|
|
57
69
|
<DownloadButtonAndMenu
|
|
58
70
|
snippetUnscopedName={`${author}/${packageName}`}
|
|
59
71
|
/>
|