@tscircuit/fake-snippets 0.0.108 → 0.0.109
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 +62 -19
- package/dist/bundle.js +3 -2
- 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 +1 -0
- package/package.json +7 -8
- package/src/App.tsx +0 -2
- package/src/components/DownloadButtonAndMenu.tsx +133 -35
- package/src/components/FileSidebar.tsx +31 -34
- package/src/components/Footer.tsx +0 -1
- package/src/components/HeaderLogin.tsx +1 -1
- package/src/components/HiddenFilesDropdown.tsx +0 -2
- package/src/components/PackageBuildsPage/PackageBuildDetailsPage.tsx +0 -2
- package/src/components/PackageBuildsPage/build-preview-content.tsx +34 -5
- package/src/components/PackageCard.tsx +0 -1
- package/src/components/ViewPackagePage/components/ShikiCodeViewer.tsx +20 -11
- package/src/components/ViewPackagePage/components/important-files-view.tsx +75 -59
- package/src/components/ViewPackagePage/components/main-content-header.tsx +4 -4
- package/src/components/ViewPackagePage/components/main-content-view-selector.tsx +0 -1
- package/src/components/ViewPackagePage/components/mobile-sidebar.tsx +0 -1
- package/src/components/ViewPackagePage/components/package-header.tsx +14 -17
- package/src/components/ViewPackagePage/components/preview-image-squares.tsx +0 -1
- package/src/components/ViewPackagePage/components/repo-page-content.tsx +21 -20
- package/src/components/ViewPackagePage/components/sidebar.tsx +0 -2
- package/src/components/ViewPackagePage/components/tab-views/files-view.tsx +18 -17
- package/src/components/ViewPackagePage/components/theme-toggle.tsx +0 -2
- package/src/components/ViewPackagePage/hooks/use-toast.tsx +0 -1
- package/src/components/package-port/CodeAndPreview.tsx +23 -40
- package/src/components/package-port/CodeEditor.tsx +24 -1
- package/src/components/package-port/CodeEditorHeader.tsx +5 -2
- package/src/components/preview/PackageReleasesDashboard.tsx +30 -25
- package/src/hooks/use-current-package-id.ts +5 -30
- package/src/hooks/use-current-package-info.ts +29 -5
- package/src/hooks/use-global-store.ts +1 -1
- package/src/hooks/useFileManagement.ts +153 -34
- package/src/hooks/useOptimizedPackageFilesLoader.ts +149 -0
- package/src/hooks/useUpdatePackageFilesMutation.ts +2 -0
- package/src/lib/download-fns/download-circuit-png.ts +11 -3
- package/src/lib/download-fns/download-gltf-from-circuit-json.ts +44 -0
- package/src/lib/utils/isComponentExported.ts +9 -0
- package/src/pages/authorize.tsx +0 -2
- package/src/pages/landing.tsx +0 -1
- package/src/pages/preview-release.tsx +14 -4
- package/src/pages/view-package.tsx +14 -13
- package/src/components/Footer2.tsx +0 -100
- package/src/components/ShippingInformationForm.tsx +0 -423
- package/src/components/StaticViewSnippetHeader.tsx +0 -70
- package/src/components/ViewPackagePage/components/file-explorer.tsx +0 -67
- package/src/components/ViewPackagePage/components/readme-view.tsx +0 -58
- package/src/components/ViewPackagePage/components/repo-header-button.tsx +0 -36
- package/src/components/ViewPackagePage/components/repo-header.tsx +0 -4
- package/src/components/ViewPackagePage/components/sidebar-contributors-section.tsx +0 -31
- package/src/components/ViewSnippetHeader.tsx +0 -181
- package/src/components/ui/input-otp.tsx +0 -69
- package/src/pages/settings.tsx +0 -25
|
@@ -43,7 +43,7 @@ export const HeaderLogin = () => {
|
|
|
43
43
|
</AvatarFallback>
|
|
44
44
|
</Avatar>
|
|
45
45
|
</DropdownMenuTrigger>
|
|
46
|
-
<DropdownMenuContent className="ml-1 md:ml-0 md:mr-1">
|
|
46
|
+
<DropdownMenuContent className="ml-1 mr-1 md:ml-0 md:mr-1">
|
|
47
47
|
<DropdownMenuItem asChild className="text-gray-500 text-xs" disabled>
|
|
48
48
|
<div>
|
|
49
49
|
AI Usage $
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { useCurrentPackageInfo } from "@/hooks/use-current-package-info"
|
|
2
2
|
import { useCurrentPackageRelease } from "@/hooks/use-current-package-release"
|
|
3
|
+
import { useState } from "react"
|
|
4
|
+
import { CircuitBoard } from "lucide-react"
|
|
3
5
|
|
|
4
6
|
export function BuildPreviewContent() {
|
|
5
7
|
const { packageRelease } = useCurrentPackageRelease({ refetchInterval: 2000 })
|
|
6
8
|
const { packageInfo } = useCurrentPackageInfo()
|
|
9
|
+
const [imageError, setImageError] = useState(false)
|
|
10
|
+
const [imageLoading, setImageLoading] = useState(true)
|
|
7
11
|
|
|
8
12
|
if (!packageRelease) {
|
|
9
13
|
return (
|
|
@@ -16,11 +20,36 @@ export function BuildPreviewContent() {
|
|
|
16
20
|
return (
|
|
17
21
|
<div className="flex items-center justify-center w-full h-full">
|
|
18
22
|
<div className="rounded overflow-hidden w-full max-w-full">
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
{imageError ? (
|
|
24
|
+
<div className="flex flex-col items-center justify-center bg-gray-50 border border-gray-300 rounded-lg p-8 sm:p-12 lg:p-16 min-h-[240px] sm:min-h-[300px] lg:min-h-[360px]">
|
|
25
|
+
<CircuitBoard className="w-12 h-12 sm:w-16 sm:h-16 lg:w-20 lg:h-20 text-gray-400 mb-4" />
|
|
26
|
+
<h3 className="text-lg sm:text-xl font-medium text-gray-600 mb-2">
|
|
27
|
+
Preview Not Available
|
|
28
|
+
</h3>
|
|
29
|
+
<p className="text-sm sm:text-base text-gray-500 text-center max-w-sm">
|
|
30
|
+
The build preview image could not be loaded. This may be because
|
|
31
|
+
the build is still processing or the image is not available.
|
|
32
|
+
</p>
|
|
33
|
+
</div>
|
|
34
|
+
) : (
|
|
35
|
+
<>
|
|
36
|
+
{imageLoading && (
|
|
37
|
+
<div className="flex items-center justify-center bg-gray-100 rounded-lg min-h-[240px] sm:min-h-[300px] lg:min-h-[360px]">
|
|
38
|
+
<div className="w-16 h-16 sm:w-20 sm:h-20 bg-gray-200 rounded animate-pulse"></div>
|
|
39
|
+
</div>
|
|
40
|
+
)}
|
|
41
|
+
<img
|
|
42
|
+
src={`https://api.tscircuit.com/packages/images/${packageInfo?.name}/pcb.png`}
|
|
43
|
+
alt="Package build preview"
|
|
44
|
+
className={`object-contain rounded w-full h-auto max-h-[240px] sm:max-h-[300px] lg:max-h-[360px] ${imageLoading ? "hidden" : "block"}`}
|
|
45
|
+
onLoad={() => setImageLoading(false)}
|
|
46
|
+
onError={() => {
|
|
47
|
+
setImageError(true)
|
|
48
|
+
setImageLoading(false)
|
|
49
|
+
}}
|
|
50
|
+
/>
|
|
51
|
+
</>
|
|
52
|
+
)}
|
|
24
53
|
</div>
|
|
25
54
|
</div>
|
|
26
55
|
)
|
|
@@ -19,7 +19,6 @@ import {
|
|
|
19
19
|
} from "@/components/ui/dropdown-menu"
|
|
20
20
|
import { SnippetType, SnippetTypeIcon } from "./SnippetTypeIcon"
|
|
21
21
|
import { timeAgo } from "@/lib/utils/timeAgo"
|
|
22
|
-
import { ImageWithFallback } from "./ImageWithFallback"
|
|
23
22
|
import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard"
|
|
24
23
|
|
|
25
24
|
export interface PackageCardProps {
|
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import { useShikiHighlighter } from "@/hooks/use-shiki-highlighter"
|
|
2
|
-
import {
|
|
3
|
-
import { useQuery } from "react-query"
|
|
2
|
+
import { useMemo } from "react"
|
|
4
3
|
import { Skeleton } from "@/components/ui/skeleton"
|
|
5
4
|
|
|
6
5
|
// Pre-randomized array to avoid flickering on re-renders
|
|
7
|
-
const SKELETON_WIDTHS = [
|
|
8
|
-
|
|
6
|
+
export const SKELETON_WIDTHS = [
|
|
7
|
+
"w-2/3",
|
|
8
|
+
"w-1/4",
|
|
9
|
+
"w-5/6",
|
|
10
|
+
"w-1/3",
|
|
11
|
+
"w-1/2",
|
|
12
|
+
"w-3/4",
|
|
13
|
+
]
|
|
14
|
+
const PLACEHOLDER_SHIKI_HTML = `<pre class="shiki vitesse-light" style="background-color:#ffffff;color:#393a34" tabindex="0"><code><span class="line"></span></code></pre>`
|
|
9
15
|
export const ShikiCodeViewer = ({
|
|
10
16
|
code,
|
|
11
17
|
filePath,
|
|
@@ -24,17 +30,20 @@ export const ShikiCodeViewer = ({
|
|
|
24
30
|
[filePath, code, highlighter],
|
|
25
31
|
)
|
|
26
32
|
|
|
27
|
-
if (
|
|
33
|
+
if (html && html?.trim() !== PLACEHOLDER_SHIKI_HTML) {
|
|
28
34
|
return (
|
|
29
|
-
<div
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
</div>
|
|
35
|
+
<div
|
|
36
|
+
className="text-sm shiki"
|
|
37
|
+
dangerouslySetInnerHTML={{ __html: html }}
|
|
38
|
+
/>
|
|
34
39
|
)
|
|
35
40
|
}
|
|
36
41
|
|
|
37
42
|
return (
|
|
38
|
-
<div className="text-sm
|
|
43
|
+
<div className="text-sm p-4">
|
|
44
|
+
{SKELETON_WIDTHS.map((w, i) => (
|
|
45
|
+
<Skeleton key={i} className={`h-4 mb-2 ${w}`} />
|
|
46
|
+
))}
|
|
47
|
+
</div>
|
|
39
48
|
)
|
|
40
49
|
}
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
1
|
import { useState, useEffect, useMemo, useCallback } from "react"
|
|
4
2
|
import {
|
|
5
3
|
Edit,
|
|
@@ -14,7 +12,7 @@ import {
|
|
|
14
12
|
import { Skeleton } from "@/components/ui/skeleton"
|
|
15
13
|
import { Button } from "@/components/ui/button"
|
|
16
14
|
import { usePackageFile } from "@/hooks/use-package-files"
|
|
17
|
-
import { ShikiCodeViewer } from "./ShikiCodeViewer"
|
|
15
|
+
import { ShikiCodeViewer, SKELETON_WIDTHS } from "./ShikiCodeViewer"
|
|
18
16
|
import MarkdownViewer from "./markdown-viewer"
|
|
19
17
|
import { useGlobalStore } from "@/hooks/use-global-store"
|
|
20
18
|
import { useCurrentPackageCircuitJson } from "../hooks/use-current-package-circuit-json"
|
|
@@ -24,12 +22,12 @@ interface PackageFile {
|
|
|
24
22
|
package_release_id: string
|
|
25
23
|
file_path: string
|
|
26
24
|
created_at: string
|
|
27
|
-
content_text?: string
|
|
25
|
+
content_text?: string | null
|
|
28
26
|
}
|
|
29
27
|
|
|
30
28
|
interface ImportantFilesViewProps {
|
|
31
29
|
importantFiles?: PackageFile[]
|
|
32
|
-
|
|
30
|
+
isFetched?: boolean
|
|
33
31
|
onEditClicked?: (file_path?: string | null) => void
|
|
34
32
|
packageAuthorOwner?: string | null
|
|
35
33
|
aiDescription?: string
|
|
@@ -56,7 +54,7 @@ export default function ImportantFilesView({
|
|
|
56
54
|
aiReviewText,
|
|
57
55
|
aiReviewRequested,
|
|
58
56
|
onRequestAiReview,
|
|
59
|
-
|
|
57
|
+
isFetched = false,
|
|
60
58
|
onEditClicked,
|
|
61
59
|
packageAuthorOwner,
|
|
62
60
|
onLicenseFileRequested,
|
|
@@ -130,19 +128,25 @@ export default function ImportantFilesView({
|
|
|
130
128
|
const availableTabs = useMemo((): TabInfo[] => {
|
|
131
129
|
const tabs: TabInfo[] = []
|
|
132
130
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
131
|
+
// Only show AI description tab if there's actual AI content
|
|
132
|
+
if (hasAiContent) {
|
|
133
|
+
tabs.push({
|
|
134
|
+
type: "ai",
|
|
135
|
+
filePath: null,
|
|
136
|
+
label: "Description",
|
|
137
|
+
icon: <SparklesIcon className="h-3.5 w-3.5 mr-1.5" />,
|
|
138
|
+
})
|
|
139
|
+
}
|
|
139
140
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
141
|
+
// Only show AI review tab if there's actual AI review content
|
|
142
|
+
if (hasAiReview || isOwner) {
|
|
143
|
+
tabs.push({
|
|
144
|
+
type: "ai-review",
|
|
145
|
+
filePath: null,
|
|
146
|
+
label: "AI Review",
|
|
147
|
+
icon: <SparklesIcon className="h-3.5 w-3.5 mr-1.5" />,
|
|
148
|
+
})
|
|
149
|
+
}
|
|
146
150
|
|
|
147
151
|
importantFiles.forEach((file) => {
|
|
148
152
|
tabs.push({
|
|
@@ -154,11 +158,11 @@ export default function ImportantFilesView({
|
|
|
154
158
|
})
|
|
155
159
|
|
|
156
160
|
return tabs
|
|
157
|
-
}, [hasAiContent, importantFiles, getFileName, getFileIcon])
|
|
161
|
+
}, [hasAiContent, hasAiReview, importantFiles, getFileName, getFileIcon])
|
|
158
162
|
|
|
159
163
|
// Find default tab with fallback logic
|
|
160
164
|
const getDefaultTab = useCallback((): TabInfo | null => {
|
|
161
|
-
if (
|
|
165
|
+
if (!isFetched || availableTabs.length === 0) return null
|
|
162
166
|
|
|
163
167
|
// Priority 1: README file
|
|
164
168
|
const readmeTab = availableTabs.find(
|
|
@@ -167,12 +171,14 @@ export default function ImportantFilesView({
|
|
|
167
171
|
)
|
|
168
172
|
if (readmeTab) return readmeTab
|
|
169
173
|
|
|
170
|
-
// Priority 2: AI content
|
|
171
|
-
const aiTab = availableTabs.find((tab) => tab.type === "ai")
|
|
174
|
+
// Priority 2: AI content (only if available)
|
|
175
|
+
const aiTab = availableTabs.find((tab) => tab.type === "ai" && hasAiContent)
|
|
172
176
|
if (aiTab) return aiTab
|
|
173
177
|
|
|
174
178
|
// Priority 3: AI review
|
|
175
|
-
const aiReviewTab = availableTabs.find(
|
|
179
|
+
const aiReviewTab = availableTabs.find(
|
|
180
|
+
(tab) => tab.type === "ai-review" && hasAiReview,
|
|
181
|
+
)
|
|
176
182
|
if (aiReviewTab) return aiReviewTab
|
|
177
183
|
|
|
178
184
|
// Priority 4: First file
|
|
@@ -180,24 +186,7 @@ export default function ImportantFilesView({
|
|
|
180
186
|
if (firstFileTab) return firstFileTab
|
|
181
187
|
|
|
182
188
|
return null
|
|
183
|
-
}, [
|
|
184
|
-
|
|
185
|
-
// Handle copy functionality
|
|
186
|
-
const handleCopy = useCallback(() => {
|
|
187
|
-
let textToCopy = ""
|
|
188
|
-
|
|
189
|
-
if (activeTab?.type === "ai-review" && aiReviewText) {
|
|
190
|
-
textToCopy = aiReviewText
|
|
191
|
-
} else if (activeTab?.type === "file" && activeFileContent) {
|
|
192
|
-
textToCopy = activeFileContent
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
if (textToCopy) {
|
|
196
|
-
navigator.clipboard.writeText(textToCopy)
|
|
197
|
-
setCopyState("copied")
|
|
198
|
-
setTimeout(() => setCopyState("copy"), 500)
|
|
199
|
-
}
|
|
200
|
-
}, [activeTab, aiReviewText])
|
|
189
|
+
}, [isFetched, availableTabs, isReadmeFile])
|
|
201
190
|
|
|
202
191
|
// Handle tab selection with validation
|
|
203
192
|
const selectTab = useCallback(
|
|
@@ -245,11 +234,11 @@ export default function ImportantFilesView({
|
|
|
245
234
|
|
|
246
235
|
// Set default tab when no tab is active
|
|
247
236
|
useEffect(() => {
|
|
248
|
-
if (activeTab === null &&
|
|
237
|
+
if (activeTab === null && isFetched) {
|
|
249
238
|
const defaultTab = getDefaultTab()
|
|
250
239
|
setActiveTab(defaultTab)
|
|
251
240
|
}
|
|
252
|
-
}, [activeTab,
|
|
241
|
+
}, [activeTab, isFetched, getDefaultTab])
|
|
253
242
|
|
|
254
243
|
// Validate active tab still exists (handles file deletion)
|
|
255
244
|
useEffect(() => {
|
|
@@ -273,18 +262,41 @@ export default function ImportantFilesView({
|
|
|
273
262
|
return importantFiles.find((file) => file.file_path === activeTab.filePath)
|
|
274
263
|
}, [activeTab, importantFiles])
|
|
275
264
|
|
|
276
|
-
const { data: activeFileFull } =
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
265
|
+
const { data: activeFileFull, isFetched: isActiveFileFetched } =
|
|
266
|
+
usePackageFile(
|
|
267
|
+
partialActiveFile
|
|
268
|
+
? {
|
|
269
|
+
file_path: partialActiveFile.file_path,
|
|
270
|
+
package_release_id: partialActiveFile.package_release_id,
|
|
271
|
+
}
|
|
272
|
+
: null,
|
|
273
|
+
{
|
|
274
|
+
keepPreviousData: true,
|
|
275
|
+
staleTime: Infinity,
|
|
276
|
+
refetchOnMount: false,
|
|
277
|
+
refetchOnWindowFocus: false,
|
|
278
|
+
refetchOnReconnect: false,
|
|
279
|
+
},
|
|
280
|
+
)
|
|
285
281
|
|
|
286
282
|
const activeFileContent = activeFileFull?.content_text || ""
|
|
287
283
|
|
|
284
|
+
const handleCopy = () => {
|
|
285
|
+
let textToCopy = ""
|
|
286
|
+
|
|
287
|
+
if (activeTab?.type === "ai-review" && aiReviewText) {
|
|
288
|
+
textToCopy = aiReviewText
|
|
289
|
+
} else if (activeTab?.type === "file" && activeFileContent) {
|
|
290
|
+
textToCopy = activeFileContent
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (textToCopy) {
|
|
294
|
+
navigator.clipboard.writeText(textToCopy)
|
|
295
|
+
setCopyState("copied")
|
|
296
|
+
setTimeout(() => setCopyState("copy"), 500)
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
288
300
|
// Render content based on active tab
|
|
289
301
|
const renderAiContent = useCallback(
|
|
290
302
|
() => (
|
|
@@ -369,20 +381,24 @@ export default function ImportantFilesView({
|
|
|
369
381
|
}, [aiReviewText, aiReviewRequested, isOwner, onRequestAiReview])
|
|
370
382
|
|
|
371
383
|
const renderFileContent = useCallback(() => {
|
|
372
|
-
if (!activeTab?.filePath || !activeFileContent) {
|
|
373
|
-
|
|
384
|
+
if (!isActiveFileFetched || !activeTab?.filePath || !activeFileContent) {
|
|
385
|
+
;<div className="text-sm p-4">
|
|
386
|
+
{SKELETON_WIDTHS.map((w, i) => (
|
|
387
|
+
<Skeleton key={i} className={`h-4 mb-2 ${w}`} />
|
|
388
|
+
))}
|
|
389
|
+
</div>
|
|
374
390
|
}
|
|
375
391
|
|
|
376
|
-
if (isMarkdownFile(activeTab
|
|
392
|
+
if (isMarkdownFile(String(activeTab?.filePath))) {
|
|
377
393
|
return <MarkdownViewer markdownContent={activeFileContent} />
|
|
378
394
|
}
|
|
379
395
|
|
|
380
|
-
if (isCodeFile(activeTab
|
|
396
|
+
if (isCodeFile(String(activeTab?.filePath))) {
|
|
381
397
|
return (
|
|
382
|
-
<div className="overflow-x-auto">
|
|
398
|
+
<div className="overflow-x-auto no-scrollbar">
|
|
383
399
|
<ShikiCodeViewer
|
|
384
400
|
code={activeFileContent}
|
|
385
|
-
filePath={activeTab
|
|
401
|
+
filePath={String(activeTab?.filePath)}
|
|
386
402
|
/>
|
|
387
403
|
</div>
|
|
388
404
|
)
|
|
@@ -422,7 +438,7 @@ export default function ImportantFilesView({
|
|
|
422
438
|
[activeTab],
|
|
423
439
|
)
|
|
424
440
|
|
|
425
|
-
if (
|
|
441
|
+
if (!isFetched) {
|
|
426
442
|
return (
|
|
427
443
|
<div className="mt-4 border border-gray-200 dark:border-[#30363d] rounded-md overflow-hidden">
|
|
428
444
|
<div className="flex items-center pl-2 pr-4 py-2 bg-gray-100 dark:bg-[#161b22] border-b border-gray-200 dark:border-[#30363d]">
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
1
|
import { useState } from "react"
|
|
4
2
|
import { Button } from "@/components/ui/button"
|
|
5
3
|
import {
|
|
@@ -72,7 +70,9 @@ export default function MainContentHeader({
|
|
|
72
70
|
}
|
|
73
71
|
}
|
|
74
72
|
|
|
75
|
-
const
|
|
73
|
+
const hasCircuitJson = packageFiles.some(
|
|
74
|
+
(file) => file.file_path === "dist/circuit.json",
|
|
75
|
+
)
|
|
76
76
|
|
|
77
77
|
return (
|
|
78
78
|
<div className="flex items-center justify-between mb-4">
|
|
@@ -86,7 +86,7 @@ export default function MainContentHeader({
|
|
|
86
86
|
unscopedName={packageInfo?.unscoped_name}
|
|
87
87
|
desiredImageType={activeView}
|
|
88
88
|
author={packageInfo?.owner_github_username ?? undefined}
|
|
89
|
-
|
|
89
|
+
hasCircuitJson={hasCircuitJson}
|
|
90
90
|
/>
|
|
91
91
|
|
|
92
92
|
{/* Code Dropdown */}
|
|
@@ -12,10 +12,7 @@ import { Lock, Globe } from "lucide-react"
|
|
|
12
12
|
import { GitFork, Package, Star } from "lucide-react"
|
|
13
13
|
|
|
14
14
|
import { useForkPackageMutation } from "@/hooks/use-fork-package-mutation"
|
|
15
|
-
import {
|
|
16
|
-
usePackageStarMutationByName,
|
|
17
|
-
usePackageStarsByName,
|
|
18
|
-
} from "@/hooks/use-package-stars"
|
|
15
|
+
import { usePackageStarMutationByName } from "@/hooks/use-package-stars"
|
|
19
16
|
import { useOrderDialog } from "@tscircuit/runframe"
|
|
20
17
|
import { useGlobalStore } from "@/hooks/use-global-store"
|
|
21
18
|
import { Package as PackageType } from "fake-snippets-api/lib/db/schema"
|
|
@@ -45,8 +42,7 @@ export default function PackageHeader({
|
|
|
45
42
|
isLoggedIn,
|
|
46
43
|
packageReleaseId: packageInfo?.latest_package_release_id ?? "",
|
|
47
44
|
})
|
|
48
|
-
|
|
49
|
-
usePackageStarsByName(packageInfo?.name ?? null)
|
|
45
|
+
|
|
50
46
|
const { addStar, removeStar } = usePackageStarMutationByName(
|
|
51
47
|
packageInfo?.name ?? "",
|
|
52
48
|
)
|
|
@@ -57,7 +53,7 @@ export default function PackageHeader({
|
|
|
57
53
|
const handleStarClick = async () => {
|
|
58
54
|
if (!packageInfo?.name || !isLoggedIn) return
|
|
59
55
|
|
|
60
|
-
if (
|
|
56
|
+
if (packageInfo?.is_starred) {
|
|
61
57
|
await removeStar.mutateAsync()
|
|
62
58
|
} else {
|
|
63
59
|
await addStar.mutateAsync()
|
|
@@ -69,8 +65,7 @@ export default function PackageHeader({
|
|
|
69
65
|
await forkPackage(packageInfo.package_id)
|
|
70
66
|
}
|
|
71
67
|
|
|
72
|
-
const isStarLoading =
|
|
73
|
-
isStarDataLoading || addStar.isLoading || removeStar.isLoading
|
|
68
|
+
const isStarLoading = addStar.isLoading || removeStar.isLoading
|
|
74
69
|
|
|
75
70
|
useEffect(() => {
|
|
76
71
|
window.TSCIRCUIT_REGISTRY_API_BASE_URL =
|
|
@@ -155,15 +150,15 @@ export default function PackageHeader({
|
|
|
155
150
|
>
|
|
156
151
|
<Star
|
|
157
152
|
className={`w-4 h-4 mr-2 ${
|
|
158
|
-
|
|
153
|
+
packageInfo?.is_starred
|
|
159
154
|
? "fill-yellow-500 text-yellow-500"
|
|
160
155
|
: ""
|
|
161
156
|
}`}
|
|
162
157
|
/>
|
|
163
|
-
{
|
|
164
|
-
{(
|
|
158
|
+
{packageInfo?.is_starred ? "Starred" : "Star"}
|
|
159
|
+
{(packageInfo?.star_count ?? 0) > 0 && (
|
|
165
160
|
<span className="ml-1.5 bg-gray-100 text-gray-700 rounded-full px-1.5 py-0.5 text-xs font-medium">
|
|
166
|
-
{
|
|
161
|
+
{packageInfo?.star_count}
|
|
167
162
|
</span>
|
|
168
163
|
)}
|
|
169
164
|
</Button>
|
|
@@ -229,13 +224,15 @@ export default function PackageHeader({
|
|
|
229
224
|
>
|
|
230
225
|
<Star
|
|
231
226
|
className={`w-4 h-4 mr-2 ${
|
|
232
|
-
|
|
227
|
+
packageInfo?.is_starred
|
|
228
|
+
? "fill-yellow-500 text-yellow-500"
|
|
229
|
+
: ""
|
|
233
230
|
}`}
|
|
234
231
|
/>
|
|
235
|
-
{
|
|
236
|
-
{(
|
|
232
|
+
{packageInfo?.is_starred ? "Starred" : "Star"}
|
|
233
|
+
{(packageInfo?.star_count ?? 0) > 0 && (
|
|
237
234
|
<span className="ml-1.5 bg-gray-100 text-gray-700 rounded-full px-1.5 py-0.5 text-xs font-medium">
|
|
238
|
-
{
|
|
235
|
+
{packageInfo?.star_count}
|
|
239
236
|
</span>
|
|
240
237
|
)}
|
|
241
238
|
</Button>
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
1
|
import { useState, useEffect, useMemo } from "react"
|
|
4
2
|
import MainContentHeader from "./main-content-header"
|
|
5
3
|
import Sidebar from "./sidebar"
|
|
@@ -21,20 +19,18 @@ import Footer from "@/components/Footer"
|
|
|
21
19
|
import PackageHeader from "./package-header"
|
|
22
20
|
import { useGlobalStore } from "@/hooks/use-global-store"
|
|
23
21
|
import { useLocation } from "wouter"
|
|
24
|
-
import {
|
|
25
|
-
|
|
22
|
+
import type {
|
|
23
|
+
Package,
|
|
24
|
+
PackageFile as ApiPackageFile,
|
|
25
|
+
} from "fake-snippets-api/lib/db/schema"
|
|
26
26
|
import { useRequestAiReviewMutation } from "@/hooks/use-request-ai-review-mutation"
|
|
27
27
|
import { useAiReview } from "@/hooks/use-ai-review"
|
|
28
28
|
import { useQueryClient } from "react-query"
|
|
29
29
|
import SidebarReleasesSection from "./sidebar-releases-section"
|
|
30
30
|
|
|
31
|
-
interface PackageFile {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
file_path: string
|
|
35
|
-
file_content: string
|
|
36
|
-
content_text?: string // Keep for backward compatibility
|
|
37
|
-
created_at: string // iso-8601
|
|
31
|
+
interface PackageFile extends ApiPackageFile {
|
|
32
|
+
file_content?: string
|
|
33
|
+
content_text?: string | null // Keep for backward compatibility
|
|
38
34
|
}
|
|
39
35
|
|
|
40
36
|
interface RepoPageContentProps {
|
|
@@ -44,10 +40,12 @@ interface RepoPageContentProps {
|
|
|
44
40
|
packageRelease?: import("fake-snippets-api/lib/db/schema").PackageRelease
|
|
45
41
|
onFileClicked?: (file: PackageFile) => void
|
|
46
42
|
onEditClicked?: () => void
|
|
43
|
+
arePackageFilesFetched?: boolean
|
|
47
44
|
}
|
|
48
45
|
|
|
49
46
|
export default function RepoPageContent({
|
|
50
47
|
packageFiles,
|
|
48
|
+
arePackageFilesFetched = false,
|
|
51
49
|
packageInfo,
|
|
52
50
|
packageRelease,
|
|
53
51
|
onFileClicked,
|
|
@@ -70,8 +68,12 @@ export default function RepoPageContent({
|
|
|
70
68
|
}
|
|
71
69
|
}, [aiReview?.ai_review_text, queryClient])
|
|
72
70
|
const session = useGlobalStore((s) => s.session)
|
|
73
|
-
|
|
74
|
-
|
|
71
|
+
|
|
72
|
+
// Check if circuit.json exists without downloading it
|
|
73
|
+
const circuitJsonExists = useMemo(() => {
|
|
74
|
+
return packageFiles?.some((file) => file.file_path === "dist/circuit.json")
|
|
75
|
+
}, [packageFiles])
|
|
76
|
+
|
|
75
77
|
const { mutate: requestAiReview, isLoading: isRequestingAiReview } =
|
|
76
78
|
useRequestAiReviewMutation({
|
|
77
79
|
onSuccess: (_packageRelease, aiReview) => {
|
|
@@ -91,13 +93,12 @@ export default function RepoPageContent({
|
|
|
91
93
|
|
|
92
94
|
// Handle initial view selection and hash-based view changes
|
|
93
95
|
useEffect(() => {
|
|
94
|
-
if (
|
|
95
|
-
if (!packageInfo) return
|
|
96
|
+
if (!packageInfo || !arePackageFilesFetched) return
|
|
96
97
|
const hash = window.location.hash.slice(1)
|
|
97
98
|
const validViews = ["files", "3d", "pcb", "schematic", "bom"]
|
|
98
99
|
const circuitDependentViews = ["3d", "pcb", "schematic", "bom"]
|
|
99
100
|
|
|
100
|
-
const availableViews =
|
|
101
|
+
const availableViews = circuitJsonExists
|
|
101
102
|
? validViews
|
|
102
103
|
: validViews.filter((view) => !circuitDependentViews.includes(view))
|
|
103
104
|
|
|
@@ -115,7 +116,7 @@ export default function RepoPageContent({
|
|
|
115
116
|
window.location.hash = "files"
|
|
116
117
|
}
|
|
117
118
|
}
|
|
118
|
-
}, [packageInfo?.default_view,
|
|
119
|
+
}, [packageInfo?.default_view, circuitJsonExists])
|
|
119
120
|
|
|
120
121
|
const importantFilePaths = packageFiles
|
|
121
122
|
?.filter((pf) => isPackageFileImportant(pf.file_path))
|
|
@@ -146,7 +147,7 @@ export default function RepoPageContent({
|
|
|
146
147
|
return (
|
|
147
148
|
<FilesView
|
|
148
149
|
packageFiles={packageFiles}
|
|
149
|
-
|
|
150
|
+
arePackageFilesFetched={arePackageFilesFetched}
|
|
150
151
|
onFileClicked={onFileClicked}
|
|
151
152
|
/>
|
|
152
153
|
)
|
|
@@ -162,7 +163,7 @@ export default function RepoPageContent({
|
|
|
162
163
|
return (
|
|
163
164
|
<FilesView
|
|
164
165
|
packageFiles={packageFiles}
|
|
165
|
-
|
|
166
|
+
arePackageFilesFetched={arePackageFilesFetched}
|
|
166
167
|
onFileClicked={onFileClicked}
|
|
167
168
|
/>
|
|
168
169
|
)
|
|
@@ -215,7 +216,7 @@ export default function RepoPageContent({
|
|
|
215
216
|
{/* Important Files View - Always shown */}
|
|
216
217
|
<ImportantFilesView
|
|
217
218
|
importantFiles={importantFiles}
|
|
218
|
-
|
|
219
|
+
isFetched={arePackageFilesFetched}
|
|
219
220
|
onEditClicked={onEditClicked}
|
|
220
221
|
packageAuthorOwner={packageInfo?.owner_github_username}
|
|
221
222
|
aiDescription={packageInfo?.ai_description ?? ""}
|