@tscircuit/fake-snippets 0.0.87 → 0.0.89
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 +96 -14
- package/bun-tests/fake-snippets-api/routes/proxy.test.ts +42 -0
- package/bun.lock +187 -206
- package/dist/bundle.js +207 -101
- package/fake-snippets-api/routes/api/package_releases/create.ts +1 -1
- package/fake-snippets-api/routes/api/proxy.ts +128 -0
- package/package.json +57 -50
- package/renovate.json +2 -1
- package/src/App.tsx +22 -3
- package/src/ContextProviders.tsx +2 -0
- package/src/build-watcher.ts +52 -0
- package/src/components/CmdKMenu.tsx +533 -197
- package/src/components/DownloadButtonAndMenu.tsx +104 -26
- package/src/components/FileSidebar.tsx +11 -1
- package/src/components/Header.tsx +5 -1
- package/src/components/Header2.tsx +7 -2
- package/src/components/HeaderLogin.tsx +1 -1
- package/src/components/PackageBuildsPage/LogContent.tsx +25 -22
- package/src/components/PackageBuildsPage/PackageBuildDetailsPage.tsx +6 -6
- package/src/components/PackageBuildsPage/build-preview-content.tsx +5 -5
- package/src/components/PackageBuildsPage/package-build-details-panel.tsx +15 -13
- package/src/components/PackageBuildsPage/package-build-header.tsx +17 -16
- package/src/components/PackageCard.tsx +66 -16
- package/src/components/PrefetchPageLink.tsx +66 -15
- package/src/components/SearchComponent.tsx +2 -2
- package/src/components/SuspenseRunFrame.tsx +14 -2
- package/src/components/ViewPackagePage/components/important-files-view.tsx +97 -22
- package/src/components/ViewPackagePage/components/main-content-header.tsx +27 -3
- package/src/components/ViewPackagePage/components/mobile-sidebar.tsx +2 -2
- package/src/components/ViewPackagePage/components/repo-page-content.tsx +49 -34
- package/src/components/ViewPackagePage/components/sidebar-about-section.tsx +2 -2
- package/src/components/ViewPackagePage/components/sidebar-releases-section.tsx +20 -12
- package/src/components/ViewPackagePage/components/tab-views/files-view.tsx +0 -7
- package/src/components/ViewPackagePage/utils/fuzz-search.ts +121 -0
- package/src/components/ViewPackagePage/utils/is-hidden-file.ts +4 -0
- package/src/components/ViewPackagePage/utils/is-package-file-important.ts +18 -5
- package/src/components/dialogs/confirm-delete-package-dialog.tsx +1 -1
- package/src/components/dialogs/confirm-discard-changes-dialog.tsx +73 -0
- package/src/components/dialogs/edit-package-details-dialog.tsx +2 -2
- package/src/components/dialogs/view-ts-files-dialog.tsx +478 -42
- package/src/components/package-port/CodeAndPreview.tsx +16 -0
- package/src/components/package-port/CodeEditor.tsx +113 -11
- package/src/components/package-port/CodeEditorHeader.tsx +39 -4
- package/src/components/package-port/EditorNav.tsx +41 -15
- package/src/components/package-port/GlobalFindReplace.tsx +681 -0
- package/src/components/package-port/QuickOpen.tsx +241 -0
- package/src/components/ui/dialog.tsx +1 -1
- package/src/components/ui/tree-view.tsx +1 -1
- package/src/global.d.ts +3 -0
- package/src/hooks/use-ai-review.ts +31 -0
- package/src/hooks/use-current-package-release.ts +5 -1
- package/src/hooks/use-download-zip.ts +50 -0
- package/src/hooks/use-hotkey.ts +116 -0
- package/src/hooks/use-package-by-package-id.ts +1 -0
- package/src/hooks/use-package-by-package-name.ts +1 -0
- package/src/hooks/use-package-files.ts +3 -0
- package/src/hooks/use-package-release.ts +5 -1
- package/src/hooks/use-package.ts +1 -0
- package/src/hooks/use-request-ai-review-mutation.ts +14 -6
- package/src/hooks/use-snippet.ts +1 -0
- package/src/hooks/useFileManagement.ts +26 -8
- package/src/hooks/usePackageFilesLoader.ts +3 -1
- package/src/index.css +11 -0
- package/src/lib/decodeUrlHashToFsMap.ts +17 -0
- package/src/lib/download-fns/download-circuit-png.ts +88 -0
- package/src/lib/download-fns/download-png-utils.ts +31 -0
- package/src/lib/encodeFsMapToUrlHash.ts +13 -0
- package/src/lib/populate-query-cache-with-ssr-data.ts +39 -38
- package/src/lib/ts-lib-cache.ts +47 -0
- package/src/lib/types.ts +2 -0
- package/src/main.tsx +7 -0
- package/src/pages/dashboard.tsx +8 -5
- package/src/pages/user-profile.tsx +1 -1
- package/src/pages/view-package.tsx +15 -7
- package/vite.config.ts +100 -1
|
@@ -2,7 +2,13 @@ import React from "react"
|
|
|
2
2
|
import { Link } from "wouter"
|
|
3
3
|
import { Package } from "fake-snippets-api/lib/db/schema"
|
|
4
4
|
import { StarIcon, LockClosedIcon } from "@radix-ui/react-icons"
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
GlobeIcon,
|
|
7
|
+
MoreVertical,
|
|
8
|
+
PencilIcon,
|
|
9
|
+
Share2,
|
|
10
|
+
Trash2,
|
|
11
|
+
} from "lucide-react"
|
|
6
12
|
import { Button } from "@/components/ui/button"
|
|
7
13
|
import {
|
|
8
14
|
DropdownMenu,
|
|
@@ -13,6 +19,7 @@ import {
|
|
|
13
19
|
import { SnippetType, SnippetTypeIcon } from "./SnippetTypeIcon"
|
|
14
20
|
import { timeAgo } from "@/lib/utils/timeAgo"
|
|
15
21
|
import { ImageWithFallback } from "./ImageWithFallback"
|
|
22
|
+
import { useToast } from "@/hooks/use-toast"
|
|
16
23
|
|
|
17
24
|
export interface PackageCardProps {
|
|
18
25
|
/** The package data to display */
|
|
@@ -49,6 +56,7 @@ export const PackageCard: React.FC<PackageCardProps> = ({
|
|
|
49
56
|
withLink = true,
|
|
50
57
|
renderActions,
|
|
51
58
|
}) => {
|
|
59
|
+
const { toast } = useToast()
|
|
52
60
|
const handleDeleteClick = (e: React.MouseEvent) => {
|
|
53
61
|
e.preventDefault() // Prevent navigation
|
|
54
62
|
if (onDeleteClick) {
|
|
@@ -56,6 +64,41 @@ export const PackageCard: React.FC<PackageCardProps> = ({
|
|
|
56
64
|
}
|
|
57
65
|
}
|
|
58
66
|
|
|
67
|
+
const handleShareClick = async (e: React.MouseEvent) => {
|
|
68
|
+
e.preventDefault()
|
|
69
|
+
|
|
70
|
+
const shareUrl = `${window.location.origin}/${pkg.owner_github_username}/${pkg.unscoped_name}`
|
|
71
|
+
const shareText =
|
|
72
|
+
`Explore this tscircuit package: ${pkg.unscoped_name} by ${pkg.owner_github_username}${pkg.description ? ` - ${pkg.description}` : ""}`.trim()
|
|
73
|
+
if (navigator.share) {
|
|
74
|
+
await navigator
|
|
75
|
+
.share({
|
|
76
|
+
title: shareText,
|
|
77
|
+
text: shareText,
|
|
78
|
+
url: shareUrl,
|
|
79
|
+
})
|
|
80
|
+
.catch(() => fallbackShare(shareText, shareUrl))
|
|
81
|
+
} else {
|
|
82
|
+
fallbackShare(shareText, shareUrl)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const fallbackShare = (text: string, url: string) => {
|
|
87
|
+
const shareContent = `${text}\n${url}`
|
|
88
|
+
navigator.clipboard
|
|
89
|
+
.writeText(shareContent)
|
|
90
|
+
.then(() => {
|
|
91
|
+
toast({
|
|
92
|
+
title: "Share content copied to clipboard",
|
|
93
|
+
})
|
|
94
|
+
})
|
|
95
|
+
.catch(() => {
|
|
96
|
+
toast({
|
|
97
|
+
title: "Unable to share or copy to clipboard",
|
|
98
|
+
})
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
|
|
59
102
|
const cardContent = (
|
|
60
103
|
<div
|
|
61
104
|
className={`border p-4 rounded-md hover:shadow-md transition-shadow flex flex-col gap-4 ${className}`}
|
|
@@ -92,18 +135,25 @@ export const PackageCard: React.FC<PackageCardProps> = ({
|
|
|
92
135
|
<StarIcon className="w-4 h-4 pt-[2.5px]" />
|
|
93
136
|
<span className="text-[16px]">{pkg.star_count || 0}</span>
|
|
94
137
|
</div>
|
|
95
|
-
|
|
96
|
-
<
|
|
97
|
-
<
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
<
|
|
138
|
+
<DropdownMenu>
|
|
139
|
+
<DropdownMenuTrigger asChild>
|
|
140
|
+
<Button
|
|
141
|
+
variant="ghost"
|
|
142
|
+
size="icon"
|
|
143
|
+
className="h-[1.5rem] w-[1.5rem]"
|
|
144
|
+
>
|
|
145
|
+
<MoreVertical className="h-4 w-4" />
|
|
146
|
+
</Button>
|
|
147
|
+
</DropdownMenuTrigger>
|
|
148
|
+
<DropdownMenuContent>
|
|
149
|
+
<DropdownMenuItem
|
|
150
|
+
className="text-xs cursor-pointer"
|
|
151
|
+
onClick={handleShareClick}
|
|
152
|
+
>
|
|
153
|
+
<Share2 className="mr-2 h-3 w-3" />
|
|
154
|
+
Share Package
|
|
155
|
+
</DropdownMenuItem>{" "}
|
|
156
|
+
{isCurrentUserPackage && onDeleteClick && (
|
|
107
157
|
<DropdownMenuItem
|
|
108
158
|
className="text-xs text-red-600"
|
|
109
159
|
onClick={handleDeleteClick}
|
|
@@ -111,9 +161,9 @@ export const PackageCard: React.FC<PackageCardProps> = ({
|
|
|
111
161
|
<Trash2 className="mr-2 h-3 w-3" />
|
|
112
162
|
Delete Package
|
|
113
163
|
</DropdownMenuItem>
|
|
114
|
-
|
|
115
|
-
</
|
|
116
|
-
|
|
164
|
+
)}
|
|
165
|
+
</DropdownMenuContent>
|
|
166
|
+
</DropdownMenu>
|
|
117
167
|
{renderActions && renderActions(pkg)}
|
|
118
168
|
</div>
|
|
119
169
|
</div>
|
|
@@ -1,18 +1,34 @@
|
|
|
1
1
|
import { Link } from "wouter"
|
|
2
2
|
import { useEffect } from "react"
|
|
3
3
|
import { useInView } from "react-intersection-observer"
|
|
4
|
+
import { useQueryClient } from "react-query"
|
|
5
|
+
import { useAxios } from "@/hooks/use-axios"
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
7
|
+
// Whitelist of known pages that can be safely prefetched
|
|
8
|
+
const PREFETCHABLE_PAGES = new Set([
|
|
9
|
+
"landing",
|
|
10
|
+
"editor",
|
|
11
|
+
"search",
|
|
12
|
+
"trending",
|
|
13
|
+
"dashboard",
|
|
14
|
+
"latest",
|
|
15
|
+
"settings",
|
|
16
|
+
"quickstart",
|
|
17
|
+
])
|
|
18
|
+
|
|
19
|
+
// Helper to check if a path is a package path (e.g. /username/package-name)
|
|
20
|
+
const isPackagePath = (path: string) => {
|
|
21
|
+
const parts = path.split("/").filter(Boolean)
|
|
22
|
+
return parts.length === 2
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Helper to check if a path is a user profile path
|
|
26
|
+
const isUserProfilePath = (path: string) => {
|
|
27
|
+
const parts = path.split("/").filter(Boolean)
|
|
28
|
+
return parts.length === 1 && !PREFETCHABLE_PAGES.has(parts[0])
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const PrefetchPageLink = ({
|
|
16
32
|
href,
|
|
17
33
|
children,
|
|
18
34
|
className,
|
|
@@ -27,13 +43,48 @@ const PrefetchPageLink = ({
|
|
|
27
43
|
triggerOnce: true,
|
|
28
44
|
threshold: 0,
|
|
29
45
|
})
|
|
46
|
+
const queryClient = useQueryClient()
|
|
47
|
+
const axios = useAxios()
|
|
30
48
|
|
|
31
49
|
useEffect(() => {
|
|
32
|
-
if (inView)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
50
|
+
if (!inView) return
|
|
51
|
+
|
|
52
|
+
const path = href === "/" ? "landing" : href.slice(1)
|
|
53
|
+
if (!path) return
|
|
54
|
+
|
|
55
|
+
// Handle user profile paths
|
|
56
|
+
if (isUserProfilePath(path)) {
|
|
57
|
+
const username = path.split("/")[0]
|
|
58
|
+
// Prefetch user profile data
|
|
59
|
+
queryClient.prefetchQuery(["account", username], async () => {
|
|
60
|
+
const { data } = await axios.post("/accounts/get", {
|
|
61
|
+
github_username: username,
|
|
62
|
+
})
|
|
63
|
+
return data.account
|
|
64
|
+
})
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Handle package paths
|
|
69
|
+
if (isPackagePath(path)) {
|
|
70
|
+
const [owner, name] = path.split("/")
|
|
71
|
+
const packageName = name.split("#")[0]
|
|
72
|
+
// Prefetch package data
|
|
73
|
+
queryClient.prefetchQuery(
|
|
74
|
+
["package", `${owner}/${packageName}`],
|
|
75
|
+
async () => {
|
|
76
|
+
const { data } = await axios.get("/packages/get", {
|
|
77
|
+
params: { name: `${owner}/${packageName}` },
|
|
78
|
+
})
|
|
79
|
+
return data.package
|
|
80
|
+
},
|
|
81
|
+
)
|
|
82
|
+
return
|
|
83
|
+
}
|
|
36
84
|
|
|
85
|
+
// Handle regular pages
|
|
86
|
+
const pageName = path.split("?")[0]
|
|
87
|
+
if (PREFETCHABLE_PAGES.has(pageName)) {
|
|
37
88
|
import(`@/pages/${pageName}.tsx`).catch((error) => {
|
|
38
89
|
console.error(`Failed to prefetch page module ${pageName}:`, error)
|
|
39
90
|
})
|
|
@@ -184,7 +184,7 @@ const SearchComponent: React.FC<SearchComponentProps> = ({
|
|
|
184
184
|
role="searchbox"
|
|
185
185
|
/>
|
|
186
186
|
{isLoading && (
|
|
187
|
-
<div className="absolute top-full w-lg left-0 right-0 mt-1 bg-white shadow-lg rounded-lg border w-80 grid place-items-center py-4 z-
|
|
187
|
+
<div className="absolute top-full w-lg left-0 right-0 mt-1 bg-white shadow-lg rounded-lg border w-80 grid place-items-center py-4 z-50 p-3">
|
|
188
188
|
<div className="flex items-center space-x-2">
|
|
189
189
|
<div className="w-4 h-4 border-2 border-blue-500 border-t-transparent rounded-full animate-spin"></div>
|
|
190
190
|
<span className="text-gray-600 text-sm">Searching...</span>
|
|
@@ -195,7 +195,7 @@ const SearchComponent: React.FC<SearchComponentProps> = ({
|
|
|
195
195
|
{showResults && searchResults && (
|
|
196
196
|
<div
|
|
197
197
|
ref={resultsRef}
|
|
198
|
-
className="absolute top-full md:left-0 right-0 mt-2 bg-white shadow-lg rounded-md z-
|
|
198
|
+
className="absolute top-full md:left-0 right-0 mt-2 bg-white shadow-lg rounded-md z-50 w-80 max-h-screen overflow-y-auto overflow-x-visible"
|
|
199
199
|
>
|
|
200
200
|
{searchResults.length > 0 ? (
|
|
201
201
|
<ul className="divide-y divide-gray-200">
|
|
@@ -9,8 +9,20 @@ export const SuspenseRunFrame = (
|
|
|
9
9
|
props: React.ComponentProps<typeof RunFrame>,
|
|
10
10
|
) => {
|
|
11
11
|
return (
|
|
12
|
-
<Suspense
|
|
13
|
-
|
|
12
|
+
<Suspense
|
|
13
|
+
fallback={
|
|
14
|
+
<div className="flex justify-center items-center h-full">
|
|
15
|
+
<div className="w-48">
|
|
16
|
+
<div className="loading">
|
|
17
|
+
<div className="loading-bar"></div>
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
}
|
|
22
|
+
>
|
|
23
|
+
<div className="h-[98vh]">
|
|
24
|
+
<RunFrame {...props} />
|
|
25
|
+
</div>
|
|
14
26
|
</Suspense>
|
|
15
27
|
)
|
|
16
28
|
}
|
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
3
|
import { useState, useEffect } from "react"
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
Edit,
|
|
6
|
+
FileText,
|
|
7
|
+
Code,
|
|
8
|
+
Copy,
|
|
9
|
+
CopyCheck,
|
|
10
|
+
Loader2,
|
|
11
|
+
RefreshCcwIcon,
|
|
12
|
+
} from "lucide-react"
|
|
5
13
|
import { Skeleton } from "@/components/ui/skeleton"
|
|
14
|
+
import { Button } from "@/components/ui/button"
|
|
6
15
|
import { usePackageFile, usePackageFileByPath } from "@/hooks/use-package-files"
|
|
7
16
|
import { ShikiCodeViewer } from "./ShikiCodeViewer"
|
|
8
17
|
import { SparklesIcon } from "lucide-react"
|
|
9
18
|
import MarkdownViewer from "./markdown-viewer"
|
|
19
|
+
import { useGlobalStore } from "@/hooks/use-global-store"
|
|
10
20
|
|
|
11
21
|
interface PackageFile {
|
|
12
22
|
package_file_id: string
|
|
@@ -20,7 +30,7 @@ interface ImportantFilesViewProps {
|
|
|
20
30
|
importantFiles?: PackageFile[]
|
|
21
31
|
isLoading?: boolean
|
|
22
32
|
onEditClicked?: (file_path?: string | null) => void
|
|
23
|
-
|
|
33
|
+
packageAuthorOwner?: string | null
|
|
24
34
|
aiDescription?: string
|
|
25
35
|
aiUsageInstructions?: string
|
|
26
36
|
aiReviewText?: string | null
|
|
@@ -37,12 +47,20 @@ export default function ImportantFilesView({
|
|
|
37
47
|
onRequestAiReview,
|
|
38
48
|
isLoading = false,
|
|
39
49
|
onEditClicked,
|
|
50
|
+
packageAuthorOwner,
|
|
40
51
|
}: ImportantFilesViewProps) {
|
|
41
52
|
const [activeFilePath, setActiveFilePath] = useState<string | null>(null)
|
|
42
53
|
const [activeTab, setActiveTab] = useState<string | null>(null)
|
|
43
54
|
const [copyState, setCopyState] = useState<"copy" | "copied">("copy")
|
|
55
|
+
const { session: user } = useGlobalStore()
|
|
44
56
|
|
|
45
57
|
const handleCopy = () => {
|
|
58
|
+
if (activeTab === "ai-review" && aiReviewText) {
|
|
59
|
+
navigator.clipboard.writeText(aiReviewText || "")
|
|
60
|
+
setCopyState("copied")
|
|
61
|
+
setTimeout(() => setCopyState("copy"), 500)
|
|
62
|
+
return
|
|
63
|
+
}
|
|
46
64
|
navigator.clipboard.writeText(activeFileContent)
|
|
47
65
|
setCopyState("copied")
|
|
48
66
|
setTimeout(() => setCopyState("copy"), 500)
|
|
@@ -131,20 +149,59 @@ export default function ImportantFilesView({
|
|
|
131
149
|
}
|
|
132
150
|
|
|
133
151
|
const renderAiReviewContent = () => {
|
|
152
|
+
const isOwner = user?.github_username === packageAuthorOwner
|
|
153
|
+
|
|
134
154
|
if (!aiReviewText && !aiReviewRequested) {
|
|
135
155
|
return (
|
|
136
|
-
<
|
|
137
|
-
className="text-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
156
|
+
<div className="flex flex-col items-center justify-center py-8 px-4">
|
|
157
|
+
<div className="text-center space-y-4 max-w-md">
|
|
158
|
+
<div className="flex justify-center">
|
|
159
|
+
<div className="w-12 h-12 bg-blue-100 rounded-full flex items-center justify-center">
|
|
160
|
+
<SparklesIcon className="h-6 w-6 text-blue-600" />
|
|
161
|
+
</div>
|
|
162
|
+
</div>
|
|
163
|
+
<div className="space-y-2">
|
|
164
|
+
<p className="text-sm text-gray-600">
|
|
165
|
+
Get detailed feedback and suggestions for improving your package
|
|
166
|
+
from our AI assistant.
|
|
167
|
+
</p>
|
|
168
|
+
</div>
|
|
169
|
+
{isOwner ? (
|
|
170
|
+
<Button
|
|
171
|
+
onClick={onRequestAiReview}
|
|
172
|
+
size="sm"
|
|
173
|
+
className="bg-blue-600 hover:bg-blue-700 text-white shadow-sm transition-all duration-200 hover:shadow-md"
|
|
174
|
+
>
|
|
175
|
+
<SparklesIcon className="h-4 w-4 mr-2" />
|
|
176
|
+
Request AI Review
|
|
177
|
+
</Button>
|
|
178
|
+
) : (
|
|
179
|
+
<p className="text-sm text-gray-500">
|
|
180
|
+
Only the package owner can generate an AI review
|
|
181
|
+
</p>
|
|
182
|
+
)}
|
|
183
|
+
</div>
|
|
184
|
+
</div>
|
|
142
185
|
)
|
|
143
186
|
}
|
|
144
187
|
|
|
145
188
|
if (!aiReviewText && aiReviewRequested) {
|
|
146
189
|
return (
|
|
147
|
-
<
|
|
190
|
+
<div className="flex flex-col items-center justify-center py-8 px-4">
|
|
191
|
+
<div className="text-center space-y-4 max-w-md">
|
|
192
|
+
<div className="flex justify-center">
|
|
193
|
+
<div className="w-12 h-12 bg-gray-200 rounded-full flex items-center justify-center">
|
|
194
|
+
<Loader2 className="h-6 w-6 text-gray-600 animate-spin" />
|
|
195
|
+
</div>
|
|
196
|
+
</div>
|
|
197
|
+
<div className="space-y-2">
|
|
198
|
+
<p className="text-sm text-gray-600">
|
|
199
|
+
Our AI is analyzing your package. This usually takes a few
|
|
200
|
+
minutes. Please check back shortly.
|
|
201
|
+
</p>
|
|
202
|
+
</div>
|
|
203
|
+
</div>
|
|
204
|
+
</div>
|
|
148
205
|
)
|
|
149
206
|
}
|
|
150
207
|
|
|
@@ -162,6 +219,7 @@ export default function ImportantFilesView({
|
|
|
162
219
|
package_release_id: partialActiveFile.package_release_id,
|
|
163
220
|
}
|
|
164
221
|
: null,
|
|
222
|
+
{ keepPreviousData: true },
|
|
165
223
|
)
|
|
166
224
|
const activeFileContent = activeFileFull?.content_text || ""
|
|
167
225
|
|
|
@@ -210,14 +268,16 @@ export default function ImportantFilesView({
|
|
|
210
268
|
)
|
|
211
269
|
}
|
|
212
270
|
|
|
271
|
+
const isOwner = user?.github_username === packageAuthorOwner
|
|
272
|
+
|
|
213
273
|
return (
|
|
214
274
|
<div className="mt-4 border border-gray-200 dark:border-[#30363d] rounded-md overflow-hidden">
|
|
215
275
|
<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]">
|
|
216
|
-
<div className="flex items-center space-x-2 overflow-x-auto no-scrollbar">
|
|
276
|
+
<div className="flex items-center space-x-2 overflow-x-auto no-scrollbar flex-1 min-w-0">
|
|
217
277
|
{/* AI Description Tab */}
|
|
218
278
|
{hasAiContent && (
|
|
219
279
|
<button
|
|
220
|
-
className={`flex items-center px-3 py-1.5 rounded-md text-xs ${
|
|
280
|
+
className={`flex items-center px-3 py-1.5 rounded-md text-xs flex-shrink-0 whitespace-nowrap ${
|
|
221
281
|
activeTab === "ai"
|
|
222
282
|
? "bg-gray-200 dark:bg-[#30363d] font-medium"
|
|
223
283
|
: "text-gray-500 dark:text-[#8b949e] hover:bg-gray-200 dark:hover:bg-[#30363d]"
|
|
@@ -234,7 +294,7 @@ export default function ImportantFilesView({
|
|
|
234
294
|
|
|
235
295
|
{/* AI Review Tab */}
|
|
236
296
|
<button
|
|
237
|
-
className={`flex items-center px-3 py-1.5 rounded-md text-xs ${
|
|
297
|
+
className={`flex items-center px-3 py-1.5 rounded-md text-xs flex-shrink-0 whitespace-nowrap ${
|
|
238
298
|
activeTab === "ai-review"
|
|
239
299
|
? "bg-gray-200 dark:bg-[#30363d] font-medium"
|
|
240
300
|
: "text-gray-500 dark:text-[#8b949e] hover:bg-gray-200 dark:hover:bg-[#30363d]"
|
|
@@ -252,7 +312,7 @@ export default function ImportantFilesView({
|
|
|
252
312
|
{importantFiles.map((file) => (
|
|
253
313
|
<button
|
|
254
314
|
key={file.package_file_id}
|
|
255
|
-
className={`flex items-center px-3 py-1.5 rounded-md text-xs ${
|
|
315
|
+
className={`flex items-center px-3 py-1.5 rounded-md text-xs flex-shrink-0 whitespace-nowrap ${
|
|
256
316
|
activeTab === "file" && activeFilePath === file.file_path
|
|
257
317
|
? "bg-gray-200 dark:bg-[#30363d] font-medium"
|
|
258
318
|
: "text-gray-500 dark:text-[#8b949e] hover:bg-gray-200 dark:hover:bg-[#30363d]"
|
|
@@ -268,7 +328,8 @@ export default function ImportantFilesView({
|
|
|
268
328
|
))}
|
|
269
329
|
</div>
|
|
270
330
|
<div className="ml-auto flex items-center">
|
|
271
|
-
{
|
|
331
|
+
{((activeTab === "file" && activeFileContent) ||
|
|
332
|
+
(activeTab === "ai-review" && aiReviewText)) && (
|
|
272
333
|
<button
|
|
273
334
|
className="hover:bg-gray-200 dark:hover:bg-[#30363d] p-1 rounded-md transition-all duration-300"
|
|
274
335
|
onClick={handleCopy}
|
|
@@ -281,13 +342,25 @@ export default function ImportantFilesView({
|
|
|
281
342
|
<span className="sr-only">Copy</span>
|
|
282
343
|
</button>
|
|
283
344
|
)}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
345
|
+
{activeTab === "ai-review" && aiReviewText && isOwner && (
|
|
346
|
+
<button
|
|
347
|
+
className="hover:bg-gray-200 dark:hover:bg-[#30363d] p-1 rounded-md ml-1"
|
|
348
|
+
onClick={onRequestAiReview}
|
|
349
|
+
title="Re-request AI Review"
|
|
350
|
+
>
|
|
351
|
+
<RefreshCcwIcon className="h-4 w-4" />
|
|
352
|
+
<span className="sr-only">Re-request AI Review</span>
|
|
353
|
+
</button>
|
|
354
|
+
)}
|
|
355
|
+
{activeTab === "file" && (
|
|
356
|
+
<button
|
|
357
|
+
className="hover:bg-gray-200 dark:hover:bg-[#30363d] p-1 rounded-md"
|
|
358
|
+
onClick={() => onEditClicked?.(activeFilePath)}
|
|
359
|
+
>
|
|
360
|
+
<Edit className="h-4 w-4" />
|
|
361
|
+
<span className="sr-only">Edit</span>
|
|
362
|
+
</button>
|
|
363
|
+
)}
|
|
291
364
|
</div>
|
|
292
365
|
</div>
|
|
293
366
|
<div className="p-4 bg-white dark:bg-[#0d1117]">
|
|
@@ -295,7 +368,9 @@ export default function ImportantFilesView({
|
|
|
295
368
|
renderAiContent()
|
|
296
369
|
) : activeTab === "ai-review" ? (
|
|
297
370
|
renderAiReviewContent()
|
|
298
|
-
) : activeFilePath &&
|
|
371
|
+
) : activeFilePath &&
|
|
372
|
+
(activeFilePath.endsWith(".md") ||
|
|
373
|
+
activeFilePath?.toLowerCase().endsWith("readme")) ? (
|
|
299
374
|
<MarkdownViewer markdownContent={activeFileContent} />
|
|
300
375
|
) : activeFilePath &&
|
|
301
376
|
(activeFilePath.endsWith(".js") ||
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
Pencil,
|
|
13
13
|
GitForkIcon,
|
|
14
14
|
DownloadIcon,
|
|
15
|
+
Package2,
|
|
15
16
|
} from "lucide-react"
|
|
16
17
|
import MainContentViewSelector from "./main-content-view-selector"
|
|
17
18
|
import {
|
|
@@ -24,8 +25,11 @@ import {
|
|
|
24
25
|
import { DownloadButtonAndMenu } from "@/components/DownloadButtonAndMenu"
|
|
25
26
|
import { useCurrentPackageCircuitJson } from "../hooks/use-current-package-circuit-json"
|
|
26
27
|
import { useLocation } from "wouter"
|
|
27
|
-
import { Package } from "fake-snippets-api/lib/db/schema"
|
|
28
|
+
import { Package, PackageFile } from "fake-snippets-api/lib/db/schema"
|
|
29
|
+
import { usePackageFiles } from "@/hooks/use-package-files"
|
|
30
|
+
import { useDownloadZip } from "@/hooks/use-download-zip"
|
|
28
31
|
interface MainContentHeaderProps {
|
|
32
|
+
packageFiles: PackageFile[]
|
|
29
33
|
activeView: string
|
|
30
34
|
onViewChange: (view: string) => void
|
|
31
35
|
onExportClicked?: (exportType: string) => void
|
|
@@ -33,6 +37,7 @@ interface MainContentHeaderProps {
|
|
|
33
37
|
}
|
|
34
38
|
|
|
35
39
|
export default function MainContentHeader({
|
|
40
|
+
packageFiles,
|
|
36
41
|
activeView,
|
|
37
42
|
onViewChange,
|
|
38
43
|
packageInfo,
|
|
@@ -59,6 +64,14 @@ export default function MainContentHeader({
|
|
|
59
64
|
setTimeout(() => setCopyCloneState("copy"), 2000)
|
|
60
65
|
}
|
|
61
66
|
|
|
67
|
+
const { downloadZip } = useDownloadZip()
|
|
68
|
+
|
|
69
|
+
const handleDownloadZip = () => {
|
|
70
|
+
if (packageInfo && packageFiles) {
|
|
71
|
+
downloadZip(packageInfo, packageFiles)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
62
75
|
const { circuitJson } = useCurrentPackageCircuitJson()
|
|
63
76
|
|
|
64
77
|
return (
|
|
@@ -70,7 +83,9 @@ export default function MainContentHeader({
|
|
|
70
83
|
|
|
71
84
|
<div className="flex space-x-2">
|
|
72
85
|
<DownloadButtonAndMenu
|
|
73
|
-
|
|
86
|
+
unscopedName={packageInfo?.unscoped_name}
|
|
87
|
+
desiredImageType={activeView}
|
|
88
|
+
author={packageInfo?.owner_github_username ?? undefined}
|
|
74
89
|
circuitJson={circuitJson}
|
|
75
90
|
/>
|
|
76
91
|
|
|
@@ -87,7 +102,7 @@ export default function MainContentHeader({
|
|
|
87
102
|
</Button>
|
|
88
103
|
</DropdownMenuTrigger>
|
|
89
104
|
<DropdownMenuContent align="end" className="w-72">
|
|
90
|
-
<DropdownMenuItem asChild>
|
|
105
|
+
<DropdownMenuItem disabled={!Boolean(packageInfo)} asChild>
|
|
91
106
|
<a
|
|
92
107
|
href={`/editor?package_id=${packageInfo?.package_id}`}
|
|
93
108
|
className="cursor-pointer p-2 py-4"
|
|
@@ -96,6 +111,15 @@ export default function MainContentHeader({
|
|
|
96
111
|
Edit Online
|
|
97
112
|
</a>
|
|
98
113
|
</DropdownMenuItem>
|
|
114
|
+
|
|
115
|
+
<DropdownMenuItem
|
|
116
|
+
disabled={!Boolean(packageInfo)}
|
|
117
|
+
onClick={handleDownloadZip}
|
|
118
|
+
className="cursor-pointer p-2 py-4"
|
|
119
|
+
>
|
|
120
|
+
<Package2 className="h-4 w-4 mx-3" />
|
|
121
|
+
Download ZIP
|
|
122
|
+
</DropdownMenuItem>
|
|
99
123
|
<DropdownMenuSeparator />
|
|
100
124
|
|
|
101
125
|
{/* Install Option */}
|
|
@@ -41,8 +41,8 @@ const MobileSidebar = ({
|
|
|
41
41
|
const isLoggedIn = useGlobalStore((s) => Boolean(s.session))
|
|
42
42
|
const isOwner =
|
|
43
43
|
isLoggedIn &&
|
|
44
|
-
packageInfo?.
|
|
45
|
-
useGlobalStore((s) => s.session?.
|
|
44
|
+
packageInfo?.owner_github_username ===
|
|
45
|
+
useGlobalStore((s) => s.session?.github_username)
|
|
46
46
|
|
|
47
47
|
const {
|
|
48
48
|
Dialog: EditPackageDetailsDialog,
|