@tscircuit/fake-snippets 0.0.107 → 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/api/generated-index.js +82 -22
- package/biome.json +7 -1
- package/bun-tests/fake-snippets-api/routes/package_builds/get.test.ts +0 -15
- package/bun-tests/fake-snippets-api/routes/package_builds/list.test.ts +0 -12
- package/bun.lock +62 -19
- package/dist/bundle.js +25 -24
- package/dist/index.d.ts +26 -15
- package/dist/index.js +19 -18
- package/dist/schema.d.ts +32 -24
- package/dist/schema.js +7 -6
- package/fake-snippets-api/lib/db/db-client.ts +10 -1
- package/fake-snippets-api/lib/db/schema.ts +4 -3
- package/fake-snippets-api/lib/db/seed.ts +6 -9
- package/fake-snippets-api/lib/public-mapping/public-map-package-build.ts +0 -3
- package/fake-snippets-api/lib/public-mapping/public-map-package-release.ts +3 -0
- package/package.json +7 -8
- package/src/App.tsx +12 -11
- package/src/components/DownloadButtonAndMenu.tsx +133 -35
- package/src/components/FileSidebar.tsx +45 -193
- package/src/components/Footer.tsx +0 -1
- package/src/components/HeaderLogin.tsx +1 -1
- package/src/components/HiddenFilesDropdown.tsx +0 -2
- package/src/components/PackageBreadcrumb.tsx +1 -1
- 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 +1 -2
- 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-about-section.tsx +18 -2
- package/src/components/ViewPackagePage/components/sidebar-releases-section.tsx +1 -1
- 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/BuildsList.tsx +20 -9
- package/src/components/preview/ConnectedPackagesList.tsx +73 -60
- package/src/components/preview/ConnectedRepoOverview.tsx +160 -154
- package/src/components/preview/PackageReleasesDashboard.tsx +41 -30
- package/src/components/preview/index.tsx +16 -153
- 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/index.css +24 -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/lib/utils/transformFilesToTreeData.tsx +195 -0
- package/src/pages/404.tsx +3 -5
- package/src/pages/authorize.tsx +0 -2
- package/src/pages/landing.tsx +0 -1
- package/src/pages/preview-release.tsx +279 -0
- package/src/pages/release-builds.tsx +0 -8
- package/src/pages/release-detail.tsx +17 -15
- package/src/pages/releases.tsx +5 -1
- 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/hooks/use-snippets-base-api-url.ts +0 -3
- package/src/pages/preview-build.tsx +0 -380
- package/src/pages/settings.tsx +0 -25
|
@@ -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 */}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use client"
|
|
2
1
|
import { GitFork, Star, Tag, Settings, LinkIcon } from "lucide-react"
|
|
3
2
|
import { Badge } from "@/components/ui/badge"
|
|
4
3
|
import { Skeleton } from "@/components/ui/skeleton"
|
|
@@ -112,7 +111,7 @@ const MobileSidebar = ({
|
|
|
112
111
|
{localDescription ||
|
|
113
112
|
packageInfo?.description ||
|
|
114
113
|
packageInfo?.ai_description ||
|
|
115
|
-
"
|
|
114
|
+
""}
|
|
116
115
|
</p>
|
|
117
116
|
{isOwner && (
|
|
118
117
|
<Button
|
|
@@ -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 ?? ""}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Badge } from "@/components/ui/badge"
|
|
2
|
-
import { GitFork, Star, Settings, LinkIcon, Github } from "lucide-react"
|
|
2
|
+
import { GitFork, Star, Settings, LinkIcon, Github, Plus } from "lucide-react"
|
|
3
3
|
import { Skeleton } from "@/components/ui/skeleton"
|
|
4
4
|
import { useCurrentPackageInfo } from "@/hooks/use-current-package-info"
|
|
5
5
|
import { usePackageReleaseById } from "@/hooks/use-package-release"
|
|
@@ -164,7 +164,7 @@ export default function SidebarAboutSection({
|
|
|
164
164
|
<GitFork className="h-4 w-4 mr-2 text-gray-500 dark:text-[#8b949e]" />
|
|
165
165
|
<span>{(packageInfo as any)?.fork_count ?? "0"} forks</span>
|
|
166
166
|
</div>
|
|
167
|
-
{packageInfo?.github_repo_full_name
|
|
167
|
+
{packageInfo?.github_repo_full_name ? (
|
|
168
168
|
<a
|
|
169
169
|
target="_blank"
|
|
170
170
|
href={`https://github.com/${packageInfo.github_repo_full_name}`}
|
|
@@ -173,6 +173,22 @@ export default function SidebarAboutSection({
|
|
|
173
173
|
<Github className="h-4 w-4 mr-2 text-gray-500 dark:text-[#8b949e]" />
|
|
174
174
|
<span>{packageInfo?.github_repo_full_name.split("/")[1]}</span>
|
|
175
175
|
</a>
|
|
176
|
+
) : (
|
|
177
|
+
<>
|
|
178
|
+
{isOwner && (
|
|
179
|
+
<div
|
|
180
|
+
className="flex items-center hover:underline hover:underline-offset-2 cursor-pointer hover:decoration-gray-500"
|
|
181
|
+
onClick={openEditPackageDetailsDialog}
|
|
182
|
+
title="Connect GitHub"
|
|
183
|
+
>
|
|
184
|
+
<div className="relative mr-2">
|
|
185
|
+
<Github className="h-4 w-4 text-gray-500 dark:text-[#8b949e]" />
|
|
186
|
+
<Plus className="h-2 w-2 absolute -bottom-0.5 -right-0.5 text-gray-500 dark:text-[#8b949e] bg-white dark:bg-[#0d1117] rounded-full" />
|
|
187
|
+
</div>
|
|
188
|
+
<span>Connect GitHub</span>
|
|
189
|
+
</div>
|
|
190
|
+
)}
|
|
191
|
+
</>
|
|
176
192
|
)}
|
|
177
193
|
</div>
|
|
178
194
|
</div>
|
|
@@ -110,7 +110,7 @@ export default function SidebarReleasesSection() {
|
|
|
110
110
|
))}
|
|
111
111
|
{latestBuild && (
|
|
112
112
|
<PrefetchPageLink
|
|
113
|
-
href={
|
|
113
|
+
href={`/${packageInfo?.name}/releases`}
|
|
114
114
|
className="flex items-center gap-2 text-sm text-gray-500 dark:text-[#8b949e]"
|
|
115
115
|
>
|
|
116
116
|
<StatusIcon status={status} />
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
1
|
import { Skeleton } from "@/components/ui/skeleton"
|
|
4
2
|
import { FileText, Folder } from "lucide-react"
|
|
5
3
|
import { useMemo, useState } from "react"
|
|
6
4
|
import { isHiddenFile } from "../../utils/is-hidden-file"
|
|
7
5
|
import { isWithinDirectory } from "../../utils/is-within-directory"
|
|
8
6
|
import HiddenFilesDropdown from "@/components/HiddenFilesDropdown"
|
|
7
|
+
import type { PackageFile as ApiPackageFile } from "fake-snippets-api/lib/db/schema"
|
|
9
8
|
|
|
10
9
|
interface Directory {
|
|
11
10
|
type: "directory"
|
|
@@ -21,24 +20,20 @@ interface File {
|
|
|
21
20
|
created_at: string
|
|
22
21
|
}
|
|
23
22
|
|
|
24
|
-
interface PackageFile {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
file_path: string
|
|
28
|
-
file_content: string
|
|
29
|
-
content_text?: string // Keep for backward compatibility
|
|
30
|
-
created_at: string // iso-8601
|
|
23
|
+
interface PackageFile extends ApiPackageFile {
|
|
24
|
+
file_content?: string
|
|
25
|
+
content_text?: string | null // Keep for backward compatibility
|
|
31
26
|
}
|
|
32
27
|
|
|
33
28
|
interface FilesViewProps {
|
|
34
29
|
packageFiles?: PackageFile[]
|
|
35
|
-
isLoading?: boolean
|
|
36
30
|
onFileClicked?: (file: PackageFile) => void
|
|
31
|
+
arePackageFilesFetched?: boolean
|
|
37
32
|
}
|
|
38
33
|
|
|
39
34
|
export default function FilesView({
|
|
40
35
|
packageFiles = [],
|
|
41
|
-
|
|
36
|
+
arePackageFilesFetched = false,
|
|
42
37
|
onFileClicked,
|
|
43
38
|
}: FilesViewProps) {
|
|
44
39
|
const [activeDir, setActiveDir] = useState("")
|
|
@@ -106,12 +101,19 @@ export default function FilesView({
|
|
|
106
101
|
}, [packageFiles, showHiddenFiles])
|
|
107
102
|
// Format date for display
|
|
108
103
|
const formatDate = (dateString: string) => {
|
|
109
|
-
const
|
|
104
|
+
const parsedDate = new Date(dateString)
|
|
105
|
+
if (Number.isNaN(parsedDate.getTime())) return ""
|
|
106
|
+
|
|
110
107
|
const now = new Date()
|
|
111
|
-
const
|
|
112
|
-
const
|
|
108
|
+
const diffMs = now.getTime() - parsedDate.getTime()
|
|
109
|
+
const oneDayMs = 1000 * 60 * 60 * 24
|
|
110
|
+
|
|
111
|
+
// Treat future dates as today
|
|
112
|
+
if (diffMs <= 0) return "today"
|
|
113
113
|
|
|
114
|
-
if (
|
|
114
|
+
if (diffMs < oneDayMs) return "today"
|
|
115
|
+
|
|
116
|
+
const diffDays = Math.floor(diffMs / oneDayMs)
|
|
115
117
|
if (diffDays === 1) return "yesterday"
|
|
116
118
|
if (diffDays < 7) return `${diffDays} days ago`
|
|
117
119
|
if (diffDays < 30) return `${Math.floor(diffDays / 7)} weeks ago`
|
|
@@ -160,7 +162,7 @@ export default function FilesView({
|
|
|
160
162
|
|
|
161
163
|
const toggleHiddenFiles = () => setShowHiddenFiles((prev) => !prev)
|
|
162
164
|
|
|
163
|
-
if (
|
|
165
|
+
if (!arePackageFilesFetched) {
|
|
164
166
|
return (
|
|
165
167
|
<div className="mb-4 border border-gray-200 dark:border-[#30363d] rounded-md overflow-hidden">
|
|
166
168
|
<div className="flex items-center px-4 py-2 md:py-3 bg-gray-100 dark:bg-[#161b22] border-b border-gray-200 dark:border-[#30363d]">
|
|
@@ -225,7 +227,6 @@ export default function FilesView({
|
|
|
225
227
|
</div>
|
|
226
228
|
</div>
|
|
227
229
|
</div>
|
|
228
|
-
|
|
229
230
|
{/* Files and Directories */}
|
|
230
231
|
<div className="bg-white dark:bg-[#0d1117]">
|
|
231
232
|
{items.length === 0 && !activeDir ? (
|
|
@@ -7,7 +7,6 @@ import useWarnUserOnPageChange from "@/hooks/use-warn-user-on-page-change"
|
|
|
7
7
|
import { getSnippetTemplate } from "@/lib/get-snippet-template"
|
|
8
8
|
import { cn } from "@/lib/utils"
|
|
9
9
|
import type { Package } from "fake-snippets-api/lib/db/schema"
|
|
10
|
-
import { Loader2 } from "lucide-react"
|
|
11
10
|
import { useMemo, useState } from "react"
|
|
12
11
|
import EditorNav from "@/components/package-port/EditorNav"
|
|
13
12
|
import { SuspenseRunFrame } from "../SuspenseRunFrame"
|
|
@@ -15,8 +14,7 @@ import { applyEditEventsToManualEditsFile } from "@tscircuit/core"
|
|
|
15
14
|
import { toastManualEditConflicts } from "@/lib/utils/toastManualEditConflicts"
|
|
16
15
|
import { ManualEditEvent } from "@tscircuit/props"
|
|
17
16
|
import { useFileManagement } from "@/hooks/useFileManagement"
|
|
18
|
-
import {
|
|
19
|
-
import { useGlobalStore } from "@/hooks/use-global-store"
|
|
17
|
+
import { isHiddenFile } from "../ViewPackagePage/utils/is-hidden-file"
|
|
20
18
|
|
|
21
19
|
interface Props {
|
|
22
20
|
pkg?: Package
|
|
@@ -39,13 +37,13 @@ export interface CodeAndPreviewState {
|
|
|
39
37
|
|
|
40
38
|
export function CodeAndPreview({ pkg, projectUrl }: Props) {
|
|
41
39
|
const { toast } = useToast()
|
|
42
|
-
const session = useGlobalStore((s) => s.session)
|
|
43
40
|
const urlParams = useUrlParams()
|
|
44
41
|
|
|
45
42
|
const templateFromUrl = useMemo(
|
|
46
43
|
() => (urlParams.template ? getSnippetTemplate(urlParams.template) : null),
|
|
47
44
|
[urlParams.template],
|
|
48
45
|
)
|
|
46
|
+
|
|
49
47
|
const [state, setState] = useState<CodeAndPreviewState>({
|
|
50
48
|
showPreview: true,
|
|
51
49
|
fullScreen: false,
|
|
@@ -69,20 +67,26 @@ export function CodeAndPreview({ pkg, projectUrl }: Props) {
|
|
|
69
67
|
isSaving,
|
|
70
68
|
currentFile,
|
|
71
69
|
fsMap,
|
|
70
|
+
priorityFileFetched,
|
|
72
71
|
isLoading,
|
|
73
72
|
createFile,
|
|
73
|
+
mainComponentPath,
|
|
74
74
|
deleteFile,
|
|
75
|
+
isFullyLoaded,
|
|
75
76
|
onFileSelect,
|
|
77
|
+
totalFilesCount,
|
|
76
78
|
saveFiles,
|
|
77
79
|
setLocalFiles,
|
|
80
|
+
loadedFilesCount,
|
|
78
81
|
localFiles,
|
|
82
|
+
currentFileCode,
|
|
79
83
|
initialFiles,
|
|
80
84
|
renameFile,
|
|
81
85
|
packageFilesMeta,
|
|
82
86
|
} = useFileManagement({
|
|
83
87
|
templateCode: templateFromUrl?.code,
|
|
84
88
|
currentPackage: pkg,
|
|
85
|
-
|
|
89
|
+
urlParams,
|
|
86
90
|
openNewPackageSaveDialog,
|
|
87
91
|
updateLastUpdated: () => {
|
|
88
92
|
setState((prev) => ({ ...prev, lastSavedAt: Date.now() }))
|
|
@@ -94,6 +98,7 @@ export function CodeAndPreview({ pkg, projectUrl }: Props) {
|
|
|
94
98
|
(!isSaving &&
|
|
95
99
|
Date.now() - state.lastSavedAt > 1000 &&
|
|
96
100
|
localFiles.some((file) => {
|
|
101
|
+
if (isHiddenFile(file.path)) return false
|
|
97
102
|
const initialFile = initialFiles.find((x) => x.path === file.path)
|
|
98
103
|
return initialFile?.content !== file.content
|
|
99
104
|
})) ||
|
|
@@ -103,29 +108,6 @@ export function CodeAndPreview({ pkg, projectUrl }: Props) {
|
|
|
103
108
|
|
|
104
109
|
useWarnUserOnPageChange({ hasUnsavedChanges })
|
|
105
110
|
|
|
106
|
-
const currentFileCode = useMemo(
|
|
107
|
-
() =>
|
|
108
|
-
localFiles.find((x) => x.path === currentFile)?.content ??
|
|
109
|
-
state.defaultComponentFile ??
|
|
110
|
-
DEFAULT_CODE,
|
|
111
|
-
[localFiles, currentFile],
|
|
112
|
-
)
|
|
113
|
-
|
|
114
|
-
const mainComponentPath = useMemo(() => {
|
|
115
|
-
const isReactComponentExported =
|
|
116
|
-
/export function\s+\w+/.test(currentFileCode) ||
|
|
117
|
-
/export const\s+\w+\s*=/.test(currentFileCode) ||
|
|
118
|
-
/export default\s+\w+/.test(currentFileCode) ||
|
|
119
|
-
/export default\s+function\s*(\w*)\s*\(/.test(currentFileCode) ||
|
|
120
|
-
/export default\s*\(\s*\)\s*=>/.test(currentFileCode)
|
|
121
|
-
|
|
122
|
-
return (currentFile?.endsWith(".tsx") || currentFile?.endsWith(".ts")) &&
|
|
123
|
-
!!localFiles.some((x) => x.path == currentFile) &&
|
|
124
|
-
isReactComponentExported
|
|
125
|
-
? currentFile
|
|
126
|
-
: state.defaultComponentFile
|
|
127
|
-
}, [currentFile, localFiles, currentFileCode])
|
|
128
|
-
|
|
129
111
|
const handleEditEvent = (event: ManualEditEvent) => {
|
|
130
112
|
const parsedManualEdits = JSON.parse(
|
|
131
113
|
localFiles.find((x) => x.path === "manual-edits.json")?.content || "{}",
|
|
@@ -171,17 +153,10 @@ export function CodeAndPreview({ pkg, projectUrl }: Props) {
|
|
|
171
153
|
})
|
|
172
154
|
}
|
|
173
155
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
<div className="text-lg text-gray-500 mb-4">Loading</div>
|
|
179
|
-
<Loader2 className="w-16 h-16 animate-spin text-gray-400" />
|
|
180
|
-
</div>
|
|
181
|
-
</div>
|
|
182
|
-
)
|
|
183
|
-
}
|
|
184
|
-
|
|
156
|
+
const finalfsMap = useMemo(
|
|
157
|
+
() => (Object.keys(fsMap).length > 0 ? fsMap : {}),
|
|
158
|
+
[fsMap],
|
|
159
|
+
)
|
|
185
160
|
return (
|
|
186
161
|
<div className="flex flex-col min-h-[50vh]">
|
|
187
162
|
<EditorNav
|
|
@@ -213,8 +188,14 @@ export function CodeAndPreview({ pkg, projectUrl }: Props) {
|
|
|
213
188
|
<CodeEditor
|
|
214
189
|
isSaving={isSaving}
|
|
215
190
|
handleCreateFile={createFile}
|
|
191
|
+
totalFilesCount={totalFilesCount}
|
|
192
|
+
loadedFilesCount={loadedFilesCount}
|
|
193
|
+
isFullyLoaded={isFullyLoaded}
|
|
216
194
|
handleDeleteFile={deleteFile}
|
|
217
195
|
handleRenameFile={renameFile}
|
|
196
|
+
isPriorityFileFetched={
|
|
197
|
+
!priorityFileFetched && Boolean(urlParams.package_id)
|
|
198
|
+
}
|
|
218
199
|
pkg={pkg}
|
|
219
200
|
currentFile={currentFile}
|
|
220
201
|
onFileSelect={onFileSelect}
|
|
@@ -255,7 +236,7 @@ export function CodeAndPreview({ pkg, projectUrl }: Props) {
|
|
|
255
236
|
onEditEvent={(event) => {
|
|
256
237
|
handleEditEvent(event)
|
|
257
238
|
}}
|
|
258
|
-
fsMap={
|
|
239
|
+
fsMap={finalfsMap}
|
|
259
240
|
projectUrl={projectUrl}
|
|
260
241
|
/>
|
|
261
242
|
</div>
|
|
@@ -265,3 +246,5 @@ export function CodeAndPreview({ pkg, projectUrl }: Props) {
|
|
|
265
246
|
</div>
|
|
266
247
|
)
|
|
267
248
|
}
|
|
249
|
+
|
|
250
|
+
export default CodeAndPreview
|
|
@@ -50,6 +50,7 @@ import {
|
|
|
50
50
|
import { isHiddenFile } from "../ViewPackagePage/utils/is-hidden-file"
|
|
51
51
|
import { inlineCopilot } from "codemirror-copilot"
|
|
52
52
|
import { useViewTsFilesDialog } from "@/components/dialogs/view-ts-files-dialog"
|
|
53
|
+
import { Loader2 } from "lucide-react"
|
|
53
54
|
|
|
54
55
|
const defaultImports = `
|
|
55
56
|
import React from "@types/react/jsx-runtime"
|
|
@@ -59,6 +60,7 @@ import type { CommonLayoutProps } from "@tscircuit/props"
|
|
|
59
60
|
|
|
60
61
|
export const CodeEditor = ({
|
|
61
62
|
onCodeChange,
|
|
63
|
+
isPriorityFileFetched,
|
|
62
64
|
readOnly = false,
|
|
63
65
|
files = [],
|
|
64
66
|
isSaving = false,
|
|
@@ -72,9 +74,13 @@ export const CodeEditor = ({
|
|
|
72
74
|
handleCreateFile,
|
|
73
75
|
handleDeleteFile,
|
|
74
76
|
pkg,
|
|
77
|
+
isFullyLoaded = false,
|
|
78
|
+
totalFilesCount = 0,
|
|
79
|
+
loadedFilesCount = 0,
|
|
75
80
|
}: {
|
|
76
81
|
onCodeChange: (code: string, filename?: string) => void
|
|
77
82
|
files: PackageFile[]
|
|
83
|
+
isPriorityFileFetched: boolean
|
|
78
84
|
isSaving?: boolean
|
|
79
85
|
handleCreateFile: (props: ICreateFileProps) => ICreateFileResult
|
|
80
86
|
handleDeleteFile: (props: IDeleteFileProps) => IDeleteFileResult
|
|
@@ -87,6 +93,9 @@ export const CodeEditor = ({
|
|
|
87
93
|
onFileContentChanged?: (path: string, content: string) => void
|
|
88
94
|
currentFile: string | null
|
|
89
95
|
onFileSelect: (path: string, lineNumber?: number) => void
|
|
96
|
+
isFullyLoaded?: boolean
|
|
97
|
+
totalFilesCount?: number
|
|
98
|
+
loadedFilesCount?: number
|
|
90
99
|
}) => {
|
|
91
100
|
const editorRef = useRef<HTMLDivElement>(null)
|
|
92
101
|
const viewRef = useRef<EditorView | null>(null)
|
|
@@ -120,10 +129,13 @@ export const CodeEditor = ({
|
|
|
120
129
|
const [sidebarOpen, setSidebarOpen] = useState(false)
|
|
121
130
|
const [isCreatingFile, setIsCreatingFile] = useState(false)
|
|
122
131
|
|
|
123
|
-
// Set current file on component mount
|
|
132
|
+
// Set current file on component mount - only when explicitly requested via URL
|
|
124
133
|
useEffect(() => {
|
|
125
134
|
if (files.length === 0 || !pkgFilesLoaded || currentFile) return
|
|
126
135
|
|
|
136
|
+
// Only run this if there's an explicit file_path in URL - don't auto-select files
|
|
137
|
+
if (!filePathFromUrl) return
|
|
138
|
+
|
|
127
139
|
const targetFile = findTargetFile(files, filePathFromUrl)
|
|
128
140
|
if (targetFile) {
|
|
129
141
|
const lineNumber = lineNumberFromUrl
|
|
@@ -825,10 +837,15 @@ export const CodeEditor = ({
|
|
|
825
837
|
isCreatingFile={isCreatingFile}
|
|
826
838
|
setIsCreatingFile={setIsCreatingFile}
|
|
827
839
|
pkg={pkg}
|
|
840
|
+
isLoadingFiles={!isFullyLoaded}
|
|
841
|
+
loadingProgress={
|
|
842
|
+
totalFilesCount > 0 ? `${loadedFilesCount}/${totalFilesCount}` : null
|
|
843
|
+
}
|
|
828
844
|
/>
|
|
829
845
|
<div className="flex flex-col flex-1 w-full min-w-0 h-full">
|
|
830
846
|
{showImportAndFormatButtons && (
|
|
831
847
|
<CodeEditorHeader
|
|
848
|
+
isLoadingFiles={!isFullyLoaded}
|
|
832
849
|
entrypointFileName={entryPointFileName}
|
|
833
850
|
appendNewFile={(path: string, content: string) => {
|
|
834
851
|
onFileContentChanged?.(path, content)
|
|
@@ -854,7 +871,13 @@ export const CodeEditor = ({
|
|
|
854
871
|
className={
|
|
855
872
|
"flex-1 overflow-auto [&_.cm-editor]:h-full [&_.cm-scroller]:!h-full"
|
|
856
873
|
}
|
|
874
|
+
style={{ display: isPriorityFileFetched ? "none" : "block" }}
|
|
857
875
|
/>
|
|
876
|
+
{isPriorityFileFetched && (
|
|
877
|
+
<div className="grid place-items-center h-full">
|
|
878
|
+
<Loader2 className="w-16 h-16 animate-spin text-gray-400" />
|
|
879
|
+
</div>
|
|
880
|
+
)}
|
|
858
881
|
</div>
|
|
859
882
|
{showQuickOpen && (
|
|
860
883
|
<QuickOpen
|
|
@@ -41,6 +41,7 @@ interface CodeEditorHeaderProps {
|
|
|
41
41
|
handleFileChange: (filename: FileName) => void
|
|
42
42
|
entrypointFileName?: string
|
|
43
43
|
appendNewFile: (path: string, content: string) => void
|
|
44
|
+
isLoadingFiles: boolean
|
|
44
45
|
createFile: (props: ICreateFileProps) => ICreateFileResult
|
|
45
46
|
aiAutocompleteState: [boolean, React.Dispatch<React.SetStateAction<boolean>>]
|
|
46
47
|
}
|
|
@@ -49,8 +50,8 @@ export const CodeEditorHeader: React.FC<CodeEditorHeaderProps> = ({
|
|
|
49
50
|
currentFile,
|
|
50
51
|
files,
|
|
51
52
|
updateFileContent,
|
|
52
|
-
appendNewFile,
|
|
53
53
|
fileSidebarState,
|
|
54
|
+
isLoadingFiles = true,
|
|
54
55
|
handleFileChange,
|
|
55
56
|
entrypointFileName = "index.tsx",
|
|
56
57
|
createFile,
|
|
@@ -230,7 +231,9 @@ export const CodeEditorHeader: React.FC<CodeEditorHeaderProps> = ({
|
|
|
230
231
|
(filename) => !isHiddenFile(filename),
|
|
231
232
|
).length > 0
|
|
232
233
|
? "Select file"
|
|
233
|
-
:
|
|
234
|
+
: isLoadingFiles
|
|
235
|
+
? "Loading files..."
|
|
236
|
+
: "No files"
|
|
234
237
|
}
|
|
235
238
|
/>
|
|
236
239
|
</SelectTrigger>
|