@tscircuit/fake-snippets 0.0.108 → 0.0.110
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/.github/workflows/bun-formatcheck.yml +2 -2
- package/.github/workflows/bun-pver-release.yml +3 -3
- package/.github/workflows/bun-test.yml +1 -1
- package/.github/workflows/bun-typecheck.yml +2 -2
- package/.github/workflows/update-snapshots.yml +1 -1
- package/README.md +4 -0
- package/api/generated-index.js +37 -3
- package/biome.json +2 -1
- package/bun-tests/fake-snippets-api/fixtures/get-test-server.ts +31 -3
- package/bun-tests/fake-snippets-api/fixtures/preload.ts +18 -0
- package/bun-tests/fake-snippets-api/routes/orgs/add_member.test.ts +26 -0
- package/bun-tests/fake-snippets-api/routes/orgs/create.test.ts +37 -0
- package/bun-tests/fake-snippets-api/routes/orgs/get.test.ts +52 -0
- package/bun-tests/fake-snippets-api/routes/orgs/list.test.ts +17 -0
- package/bun-tests/fake-snippets-api/routes/orgs/list_members.test.ts +23 -0
- package/bun-tests/fake-snippets-api/routes/orgs/remove_member.test.ts +81 -0
- package/bun-tests/fake-snippets-api/routes/orgs/update.test.ts +99 -0
- package/bun-tests/fake-snippets-api/routes/package_builds/get.test.ts +1 -1
- package/bun-tests/fake-snippets-api/routes/package_files/create.test.ts +15 -13
- package/bun-tests/fake-snippets-api/routes/package_files/create_or_update.test.ts +26 -24
- package/bun-tests/fake-snippets-api/routes/package_files/delete.test.ts +9 -9
- package/bun-tests/fake-snippets-api/routes/package_files/download.test.ts +4 -4
- package/bun-tests/fake-snippets-api/routes/package_files/get.test.ts +38 -28
- package/bun-tests/fake-snippets-api/routes/package_files/list.test.ts +23 -15
- package/bun-tests/fake-snippets-api/routes/package_releases/create.test.ts +33 -0
- package/bun-tests/fake-snippets-api/routes/package_releases/get.test.ts +4 -4
- package/bun-tests/fake-snippets-api/routes/package_releases/get_image_generation_fields.test.ts +38 -0
- package/bun-tests/fake-snippets-api/routes/packages/create.test.ts +19 -0
- package/bun-tests/fake-snippets-api/routes/packages/fork.test.ts +3 -4
- package/bun-tests/fake-snippets-api/routes/packages/get.test.ts +30 -0
- package/bun-tests/fake-snippets-api/routes/packages/images.test.ts +4 -2
- package/bun-tests/fake-snippets-api/routes/packages/list-1.test.ts +34 -0
- package/bun.lock +389 -450
- package/bunfig.toml +2 -1
- package/dist/bundle.js +1255 -625
- package/dist/index.d.ts +296 -4
- package/dist/index.js +325 -24
- package/dist/schema.d.ts +282 -1
- package/dist/schema.js +54 -2
- package/fake-snippets-api/lib/db/autoload-dev-packages.ts +31 -20
- package/fake-snippets-api/lib/db/db-client.ts +214 -3
- package/fake-snippets-api/lib/db/schema.ts +62 -0
- package/fake-snippets-api/lib/db/seed.ts +100 -0
- package/fake-snippets-api/lib/middleware/with-session-auth.ts +1 -1
- package/fake-snippets-api/lib/package_file/get-package-file-id-from-file-descriptor.ts +2 -2
- package/fake-snippets-api/lib/public-mapping/public-map-org.ts +32 -0
- package/fake-snippets-api/lib/public-mapping/public-map-package-build.ts +10 -0
- package/fake-snippets-api/lib/public-mapping/public-map-package-release.ts +17 -0
- package/fake-snippets-api/routes/api/orgs/add_member.ts +52 -0
- package/fake-snippets-api/routes/api/orgs/create.ts +46 -0
- package/fake-snippets-api/routes/api/orgs/get.ts +39 -0
- package/fake-snippets-api/routes/api/orgs/list.ts +31 -0
- package/fake-snippets-api/routes/api/orgs/list_members.ts +67 -0
- package/fake-snippets-api/routes/api/orgs/remove_member.ts +46 -0
- package/fake-snippets-api/routes/api/orgs/update.ts +93 -0
- package/fake-snippets-api/routes/api/package_files/get.ts +3 -6
- package/fake-snippets-api/routes/api/package_files/list.ts +7 -4
- package/fake-snippets-api/routes/api/packages/create.ts +54 -10
- package/fake-snippets-api/routes/api/packages/get.ts +23 -0
- package/fake-snippets-api/routes/api/packages/images/[owner_github_username]/[unscoped_name]/[view_format].ts +13 -11
- package/fake-snippets-api/routes/api/packages/list.ts +29 -2
- package/fake-snippets-api/routes/api/packages/update_ai_description.ts +37 -0
- package/package.json +27 -24
- package/renovate.json +1 -1
- package/scripts/generate-sitemap.ts +1 -1
- package/src/App.tsx +29 -10
- package/src/ContextProviders.tsx +25 -2
- package/src/components/CircuitJsonImportDialog.tsx +1 -1
- package/src/components/CmdKMenu.tsx +281 -247
- package/src/components/DownloadButtonAndMenu.tsx +133 -36
- package/src/components/FileSidebar.tsx +41 -50
- package/src/components/Footer.tsx +8 -10
- package/src/components/Header.tsx +19 -32
- package/src/components/Header2.tsx +16 -32
- package/src/components/HeaderDropdown.tsx +13 -8
- package/src/components/HeaderLogin.tsx +44 -16
- package/src/components/HiddenFilesDropdown.tsx +0 -2
- package/src/components/NotFound.tsx +5 -5
- package/src/components/PackageBreadcrumb.tsx +6 -12
- package/src/components/PackageCard.tsx +0 -1
- package/src/components/PackageSearchResults.tsx +1 -1
- package/src/components/PrefetchPageLink.tsx +7 -1
- package/src/components/ProfileRouter.tsx +32 -0
- package/src/components/SearchComponent.tsx +12 -8
- package/src/components/UserCard.tsx +80 -0
- package/src/components/ViewPackagePage/components/ShikiCodeViewer.tsx +20 -11
- package/src/components/ViewPackagePage/components/build-status.tsx +1 -1
- package/src/components/ViewPackagePage/components/important-files-view.tsx +174 -87
- package/src/components/ViewPackagePage/components/main-content-header.tsx +8 -4
- package/src/components/ViewPackagePage/components/main-content-view-selector.tsx +1 -2
- package/src/components/ViewPackagePage/components/mobile-sidebar.tsx +54 -20
- package/src/components/ViewPackagePage/components/package-header.tsx +26 -37
- package/src/components/ViewPackagePage/components/preview-image-squares.tsx +11 -19
- package/src/components/ViewPackagePage/components/repo-page-content.tsx +33 -25
- package/src/components/ViewPackagePage/components/sidebar-about-section.tsx +16 -10
- package/src/components/ViewPackagePage/components/sidebar-releases-section.tsx +11 -11
- 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/tab-views/pcb-view.tsx +1 -2
- package/src/components/ViewPackagePage/components/tab-views/schematic-view.tsx +2 -1
- package/src/components/ViewPackagePage/components/theme-toggle.tsx +0 -2
- package/src/components/ViewPackagePage/hooks/use-toast.tsx +0 -1
- package/src/components/dialogs/GitHubRepositorySelector.tsx +56 -49
- package/src/components/dialogs/edit-package-details-dialog.tsx +5 -6
- package/src/components/dialogs/import-component-dialog.tsx +16 -9
- package/src/components/dialogs/import-package-dialog.tsx +3 -2
- package/src/components/dialogs/new-package-save-prompt-dialog.tsx +190 -0
- package/src/components/organization/OrganizationCard.tsx +204 -0
- package/src/components/organization/OrganizationCardSkeleton.tsx +55 -0
- package/src/components/organization/OrganizationHeader.tsx +154 -0
- package/src/components/organization/OrganizationMembers.tsx +146 -0
- package/src/components/package-port/CodeAndPreview.tsx +32 -46
- package/src/components/package-port/CodeEditor.tsx +28 -31
- package/src/components/package-port/CodeEditorHeader.tsx +128 -63
- package/src/components/package-port/EditorNav.tsx +32 -49
- package/src/components/preview/ConnectedPackagesList.tsx +8 -8
- package/src/components/preview/ConnectedRepoOverview.tsx +102 -2
- package/src/components/preview/PackageReleasesDashboard.tsx +53 -36
- package/src/components/ui/tree-view.tsx +6 -3
- package/src/hooks/use-add-org-member-mutation.ts +51 -0
- package/src/hooks/use-create-org-mutation.ts +38 -0
- package/src/hooks/use-create-package-mutation.ts +3 -0
- package/src/hooks/use-current-package-id.ts +5 -30
- package/src/hooks/use-current-package-info.ts +29 -5
- package/src/hooks/use-current-package-release.ts +4 -3
- package/src/hooks/use-download-zip.ts +2 -2
- package/src/hooks/use-global-store.ts +6 -4
- package/src/hooks/use-jlcpcb-component-import.tsx +164 -0
- package/src/hooks/use-list-org-members.ts +27 -0
- package/src/hooks/use-list-user-orgs.ts +25 -0
- package/src/hooks/use-org-by-github-handle.ts +26 -0
- package/src/hooks/use-org.ts +24 -0
- package/src/hooks/use-organization.ts +42 -0
- package/src/hooks/use-package-as-snippet.ts +4 -2
- package/src/hooks/use-package-builds.ts +6 -2
- package/src/hooks/use-package-files.ts +5 -3
- package/src/hooks/use-package-release-by-id-or-version.ts +29 -20
- package/src/hooks/use-package-release-images.ts +105 -0
- package/src/hooks/use-package-release.ts +2 -2
- package/src/hooks/use-package-stars.ts +80 -4
- package/src/hooks/use-preview-images.ts +6 -3
- package/src/hooks/use-remove-org-member-mutation.ts +32 -0
- package/src/hooks/use-update-ai-description-mutation.ts +42 -0
- package/src/hooks/use-update-org-mutation.ts +41 -0
- package/src/hooks/use-warn-user-on-page-change.ts +71 -4
- package/src/hooks/useFileManagement.ts +183 -35
- package/src/hooks/useOptimizedPackageFilesLoader.ts +136 -0
- package/src/hooks/usePackageFilesLoader.ts +2 -2
- package/src/hooks/useUpdatePackageFilesMutation.ts +15 -1
- 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/download-fns/download-kicad-files.ts +12 -11
- package/src/lib/normalize-svg-for-tile.ts +50 -0
- package/src/lib/posthog.ts +11 -9
- package/src/lib/react-query-api-failure-tracking.ts +148 -0
- package/src/lib/sentry.ts +14 -0
- package/src/lib/templates/blank-circuit-board-template.ts +0 -4
- package/src/lib/ts-lib-cache.ts +122 -7
- package/src/lib/utils/checkIfManualEditsImported.ts +4 -4
- package/src/lib/utils/findTargetFile.ts +45 -10
- package/src/lib/utils/isComponentExported.ts +10 -0
- package/src/main.tsx +2 -1
- package/src/pages/authorize.tsx +0 -2
- package/src/pages/create-organization.tsx +168 -0
- package/src/pages/dashboard.tsx +38 -6
- package/src/pages/datasheet.tsx +1 -1
- package/src/pages/datasheets.tsx +3 -3
- package/src/pages/editor.tsx +4 -6
- package/src/pages/landing.tsx +6 -7
- package/src/pages/latest.tsx +3 -0
- package/src/pages/organization-profile.tsx +199 -0
- package/src/pages/organization-settings.tsx +566 -0
- package/src/pages/package-editor.tsx +21 -21
- package/src/pages/preview-release.tsx +76 -136
- package/src/pages/quickstart.tsx +159 -123
- package/src/pages/release-detail.tsx +119 -31
- package/src/pages/search.tsx +192 -57
- package/src/pages/settings-redirect.tsx +44 -0
- package/src/pages/trending.tsx +29 -20
- package/src/pages/user-profile.tsx +58 -7
- package/src/pages/view-package.tsx +21 -26
- package/vite.config.ts +9 -0
- package/fake-snippets-api/routes/api/autocomplete/create_autocomplete.ts +0 -133
- package/src/components/Footer2.tsx +0 -100
- package/src/components/JLCPCBImportDialog.tsx +0 -280
- package/src/components/PackageBuildsPage/LogContent.tsx +0 -72
- package/src/components/PackageBuildsPage/PackageBuildDetailsPage.tsx +0 -115
- package/src/components/PackageBuildsPage/build-preview-content.tsx +0 -27
- package/src/components/PackageBuildsPage/collapsible-section.tsx +0 -63
- package/src/components/PackageBuildsPage/package-build-details-panel.tsx +0 -166
- package/src/components/PackageBuildsPage/package-build-header.tsx +0 -79
- package/src/components/PageSearchComponent.tsx +0 -148
- 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/package-builds.tsx +0 -33
- package/src/pages/settings.tsx +0 -25
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
1
|
import { useState, useEffect, useMemo, useCallback } from "react"
|
|
4
2
|
import {
|
|
5
3
|
Edit,
|
|
@@ -14,30 +12,31 @@ 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"
|
|
19
|
+
import { useOrganization } from "@/hooks/use-organization"
|
|
20
|
+
import { Package } from "fake-snippets-api/lib/db/schema"
|
|
21
21
|
|
|
22
22
|
interface PackageFile {
|
|
23
23
|
package_file_id: string
|
|
24
24
|
package_release_id: string
|
|
25
25
|
file_path: string
|
|
26
26
|
created_at: string
|
|
27
|
-
content_text?: string
|
|
27
|
+
content_text?: string | null
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
interface ImportantFilesViewProps {
|
|
31
31
|
importantFiles?: PackageFile[]
|
|
32
|
-
|
|
32
|
+
isFetched?: boolean
|
|
33
33
|
onEditClicked?: (file_path?: string | null) => void
|
|
34
|
-
packageAuthorOwner?: string | null
|
|
35
|
-
aiDescription?: string
|
|
36
|
-
aiUsageInstructions?: string
|
|
37
34
|
aiReviewText?: string | null
|
|
38
35
|
aiReviewRequested?: boolean
|
|
39
36
|
onRequestAiReview?: () => void
|
|
37
|
+
onRequestAiDescriptionUpdate?: () => void
|
|
40
38
|
onLicenseFileRequested?: boolean
|
|
39
|
+
pkg?: Package
|
|
41
40
|
}
|
|
42
41
|
|
|
43
42
|
type TabType = "ai" | "ai-review" | "file"
|
|
@@ -51,30 +50,44 @@ interface TabInfo {
|
|
|
51
50
|
|
|
52
51
|
export default function ImportantFilesView({
|
|
53
52
|
importantFiles = [],
|
|
54
|
-
aiDescription,
|
|
55
|
-
aiUsageInstructions,
|
|
56
53
|
aiReviewText,
|
|
57
54
|
aiReviewRequested,
|
|
58
55
|
onRequestAiReview,
|
|
59
|
-
|
|
56
|
+
onRequestAiDescriptionUpdate,
|
|
57
|
+
isFetched = false,
|
|
60
58
|
onEditClicked,
|
|
61
|
-
packageAuthorOwner,
|
|
62
59
|
onLicenseFileRequested,
|
|
60
|
+
pkg,
|
|
63
61
|
}: ImportantFilesViewProps) {
|
|
64
62
|
const [activeTab, setActiveTab] = useState<TabInfo | null>(null)
|
|
65
63
|
const [copyState, setCopyState] = useState<"copy" | "copied">("copy")
|
|
66
64
|
const { session: user } = useGlobalStore()
|
|
67
65
|
|
|
66
|
+
const { organization } = useOrganization(
|
|
67
|
+
pkg?.owner_org_id
|
|
68
|
+
? { orgId: String(pkg.owner_org_id) }
|
|
69
|
+
: pkg?.owner_github_username
|
|
70
|
+
? { github_handle: pkg.owner_github_username }
|
|
71
|
+
: {},
|
|
72
|
+
)
|
|
73
|
+
|
|
68
74
|
// Memoized computed values
|
|
69
75
|
const hasAiContent = useMemo(
|
|
70
|
-
() => Boolean(
|
|
71
|
-
[
|
|
76
|
+
() => Boolean(pkg?.ai_description || pkg?.ai_usage_instructions),
|
|
77
|
+
[pkg?.ai_description, pkg?.ai_usage_instructions],
|
|
72
78
|
)
|
|
73
79
|
const hasAiReview = useMemo(() => Boolean(aiReviewText), [aiReviewText])
|
|
74
80
|
const isOwner = useMemo(
|
|
75
|
-
() => user?.github_username ===
|
|
76
|
-
[user?.github_username,
|
|
81
|
+
() => user?.github_username === pkg?.owner_github_username,
|
|
82
|
+
[user?.github_username, pkg?.owner_github_username],
|
|
77
83
|
)
|
|
84
|
+
const canManagePackage = useMemo(() => {
|
|
85
|
+
if (isOwner) return isOwner
|
|
86
|
+
if (organization) {
|
|
87
|
+
return organization.user_permissions?.can_manage_package
|
|
88
|
+
}
|
|
89
|
+
return false
|
|
90
|
+
}, [isOwner, organization])
|
|
78
91
|
|
|
79
92
|
// File type utilities
|
|
80
93
|
const isLicenseFile = useCallback((filePath: string) => {
|
|
@@ -130,19 +143,25 @@ export default function ImportantFilesView({
|
|
|
130
143
|
const availableTabs = useMemo((): TabInfo[] => {
|
|
131
144
|
const tabs: TabInfo[] = []
|
|
132
145
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
146
|
+
// Only show AI description tab if there's actual AI content
|
|
147
|
+
if (hasAiContent) {
|
|
148
|
+
tabs.push({
|
|
149
|
+
type: "ai",
|
|
150
|
+
filePath: null,
|
|
151
|
+
label: "Description",
|
|
152
|
+
icon: <SparklesIcon className="h-3.5 w-3.5 mr-1.5" />,
|
|
153
|
+
})
|
|
154
|
+
}
|
|
139
155
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
156
|
+
// Only show AI review tab if there's actual AI review content
|
|
157
|
+
if (hasAiReview || canManagePackage) {
|
|
158
|
+
tabs.push({
|
|
159
|
+
type: "ai-review",
|
|
160
|
+
filePath: null,
|
|
161
|
+
label: "AI Review",
|
|
162
|
+
icon: <SparklesIcon className="h-3.5 w-3.5 mr-1.5" />,
|
|
163
|
+
})
|
|
164
|
+
}
|
|
146
165
|
|
|
147
166
|
importantFiles.forEach((file) => {
|
|
148
167
|
tabs.push({
|
|
@@ -154,11 +173,11 @@ export default function ImportantFilesView({
|
|
|
154
173
|
})
|
|
155
174
|
|
|
156
175
|
return tabs
|
|
157
|
-
}, [hasAiContent, importantFiles, getFileName, getFileIcon])
|
|
176
|
+
}, [hasAiContent, hasAiReview, importantFiles, getFileName, getFileIcon])
|
|
158
177
|
|
|
159
178
|
// Find default tab with fallback logic
|
|
160
179
|
const getDefaultTab = useCallback((): TabInfo | null => {
|
|
161
|
-
if (
|
|
180
|
+
if (!isFetched || availableTabs.length === 0) return null
|
|
162
181
|
|
|
163
182
|
// Priority 1: README file
|
|
164
183
|
const readmeTab = availableTabs.find(
|
|
@@ -167,12 +186,14 @@ export default function ImportantFilesView({
|
|
|
167
186
|
)
|
|
168
187
|
if (readmeTab) return readmeTab
|
|
169
188
|
|
|
170
|
-
// Priority 2: AI content
|
|
171
|
-
const aiTab = availableTabs.find((tab) => tab.type === "ai")
|
|
189
|
+
// Priority 2: AI content (only if available)
|
|
190
|
+
const aiTab = availableTabs.find((tab) => tab.type === "ai" && hasAiContent)
|
|
172
191
|
if (aiTab) return aiTab
|
|
173
192
|
|
|
174
193
|
// Priority 3: AI review
|
|
175
|
-
const aiReviewTab = availableTabs.find(
|
|
194
|
+
const aiReviewTab = availableTabs.find(
|
|
195
|
+
(tab) => tab.type === "ai-review" && hasAiReview,
|
|
196
|
+
)
|
|
176
197
|
if (aiReviewTab) return aiReviewTab
|
|
177
198
|
|
|
178
199
|
// Priority 4: First file
|
|
@@ -180,24 +201,7 @@ export default function ImportantFilesView({
|
|
|
180
201
|
if (firstFileTab) return firstFileTab
|
|
181
202
|
|
|
182
203
|
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])
|
|
204
|
+
}, [isFetched, availableTabs, isReadmeFile])
|
|
201
205
|
|
|
202
206
|
// Handle tab selection with validation
|
|
203
207
|
const selectTab = useCallback(
|
|
@@ -245,11 +249,11 @@ export default function ImportantFilesView({
|
|
|
245
249
|
|
|
246
250
|
// Set default tab when no tab is active
|
|
247
251
|
useEffect(() => {
|
|
248
|
-
if (activeTab === null &&
|
|
252
|
+
if (activeTab === null && isFetched) {
|
|
249
253
|
const defaultTab = getDefaultTab()
|
|
250
254
|
setActiveTab(defaultTab)
|
|
251
255
|
}
|
|
252
|
-
}, [activeTab,
|
|
256
|
+
}, [activeTab, isFetched, getDefaultTab])
|
|
253
257
|
|
|
254
258
|
// Validate active tab still exists (handles file deletion)
|
|
255
259
|
useEffect(() => {
|
|
@@ -273,38 +277,95 @@ export default function ImportantFilesView({
|
|
|
273
277
|
return importantFiles.find((file) => file.file_path === activeTab.filePath)
|
|
274
278
|
}, [activeTab, importantFiles])
|
|
275
279
|
|
|
276
|
-
const { data: activeFileFull } =
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
280
|
+
const { data: activeFileFull, isFetched: isActiveFileFetched } =
|
|
281
|
+
usePackageFile(
|
|
282
|
+
partialActiveFile
|
|
283
|
+
? {
|
|
284
|
+
file_path: partialActiveFile.file_path,
|
|
285
|
+
package_release_id: partialActiveFile.package_release_id,
|
|
286
|
+
}
|
|
287
|
+
: null,
|
|
288
|
+
{
|
|
289
|
+
keepPreviousData: true,
|
|
290
|
+
staleTime: Infinity,
|
|
291
|
+
refetchOnMount: false,
|
|
292
|
+
refetchOnWindowFocus: false,
|
|
293
|
+
refetchOnReconnect: false,
|
|
294
|
+
},
|
|
295
|
+
)
|
|
285
296
|
|
|
286
297
|
const activeFileContent = activeFileFull?.content_text || ""
|
|
287
298
|
|
|
299
|
+
const handleCopy = () => {
|
|
300
|
+
let textToCopy = ""
|
|
301
|
+
|
|
302
|
+
if (activeTab?.type === "ai-review" && aiReviewText) {
|
|
303
|
+
textToCopy = aiReviewText
|
|
304
|
+
} else if (
|
|
305
|
+
activeTab?.type === "ai" &&
|
|
306
|
+
(pkg?.ai_description || pkg?.ai_usage_instructions)
|
|
307
|
+
) {
|
|
308
|
+
const parts = []
|
|
309
|
+
if (pkg?.ai_description)
|
|
310
|
+
parts.push(`# Description\n\n${pkg?.ai_description}`)
|
|
311
|
+
if (pkg?.ai_usage_instructions)
|
|
312
|
+
parts.push(`# Instructions\n\n${pkg?.ai_usage_instructions}`)
|
|
313
|
+
textToCopy = parts.join("\n\n")
|
|
314
|
+
} else if (activeTab?.type === "file" && activeFileContent) {
|
|
315
|
+
textToCopy = activeFileContent
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (textToCopy) {
|
|
319
|
+
navigator.clipboard.writeText(textToCopy)
|
|
320
|
+
setCopyState("copied")
|
|
321
|
+
setTimeout(() => setCopyState("copy"), 500)
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
288
325
|
// Render content based on active tab
|
|
289
|
-
const renderAiContent = useCallback(
|
|
290
|
-
()
|
|
326
|
+
const renderAiContent = useCallback(() => {
|
|
327
|
+
if (!pkg?.ai_description && !pkg?.ai_usage_instructions) {
|
|
328
|
+
return (
|
|
329
|
+
<div className="flex flex-col items-center justify-center py-8 px-4">
|
|
330
|
+
<div className="text-center space-y-4 max-w-md">
|
|
331
|
+
<div className="flex justify-center">
|
|
332
|
+
<div className="w-12 h-12 bg-gray-200 rounded-full flex items-center justify-center">
|
|
333
|
+
<Loader2 className="h-6 w-6 text-gray-600 animate-spin" />
|
|
334
|
+
</div>
|
|
335
|
+
</div>
|
|
336
|
+
<div className="space-y-2">
|
|
337
|
+
<p className="text-sm text-gray-600">
|
|
338
|
+
Our AI is generating a description for your package. This
|
|
339
|
+
usually takes a few minutes. Please check back shortly.
|
|
340
|
+
</p>
|
|
341
|
+
</div>
|
|
342
|
+
</div>
|
|
343
|
+
</div>
|
|
344
|
+
)
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return (
|
|
291
348
|
<div className="markdown-content">
|
|
292
|
-
{
|
|
349
|
+
{pkg?.ai_description && (
|
|
293
350
|
<div className="mb-6">
|
|
294
351
|
<h3 className="font-semibold text-lg mb-2">Description</h3>
|
|
295
|
-
<MarkdownViewer markdownContent={
|
|
352
|
+
<MarkdownViewer markdownContent={pkg?.ai_description} />
|
|
296
353
|
</div>
|
|
297
354
|
)}
|
|
298
|
-
{
|
|
355
|
+
{pkg?.ai_usage_instructions && (
|
|
299
356
|
<div>
|
|
300
357
|
<h3 className="font-semibold text-lg mb-2">Instructions</h3>
|
|
301
|
-
<MarkdownViewer markdownContent={
|
|
358
|
+
<MarkdownViewer markdownContent={pkg?.ai_usage_instructions} />
|
|
302
359
|
</div>
|
|
303
360
|
)}
|
|
304
361
|
</div>
|
|
305
|
-
)
|
|
306
|
-
|
|
307
|
-
|
|
362
|
+
)
|
|
363
|
+
}, [
|
|
364
|
+
pkg?.ai_description,
|
|
365
|
+
pkg?.ai_usage_instructions,
|
|
366
|
+
canManagePackage,
|
|
367
|
+
onRequestAiDescriptionUpdate,
|
|
368
|
+
])
|
|
308
369
|
|
|
309
370
|
const renderAiReviewContent = useCallback(() => {
|
|
310
371
|
if (!aiReviewText && !aiReviewRequested) {
|
|
@@ -322,7 +383,7 @@ export default function ImportantFilesView({
|
|
|
322
383
|
from our AI assistant.
|
|
323
384
|
</p>
|
|
324
385
|
</div>
|
|
325
|
-
{!
|
|
386
|
+
{!canManagePackage ? (
|
|
326
387
|
<p className="text-sm text-gray-500">
|
|
327
388
|
Only the package owner can generate an AI review
|
|
328
389
|
</p>
|
|
@@ -366,30 +427,42 @@ export default function ImportantFilesView({
|
|
|
366
427
|
}
|
|
367
428
|
|
|
368
429
|
return <MarkdownViewer markdownContent={aiReviewText || ""} />
|
|
369
|
-
}, [aiReviewText, aiReviewRequested,
|
|
430
|
+
}, [aiReviewText, aiReviewRequested, canManagePackage, onRequestAiReview])
|
|
370
431
|
|
|
371
432
|
const renderFileContent = useCallback(() => {
|
|
372
|
-
if (!activeTab?.filePath || !activeFileContent) {
|
|
373
|
-
return
|
|
433
|
+
if (!isActiveFileFetched || !activeTab?.filePath || !activeFileContent) {
|
|
434
|
+
return (
|
|
435
|
+
<div className="text-sm p-4">
|
|
436
|
+
{SKELETON_WIDTHS.map((w, i) => (
|
|
437
|
+
<Skeleton key={i} className={`h-4 mb-2 ${w}`} />
|
|
438
|
+
))}
|
|
439
|
+
</div>
|
|
440
|
+
)
|
|
374
441
|
}
|
|
375
442
|
|
|
376
|
-
if (isMarkdownFile(activeTab
|
|
443
|
+
if (isMarkdownFile(String(activeTab?.filePath))) {
|
|
377
444
|
return <MarkdownViewer markdownContent={activeFileContent} />
|
|
378
445
|
}
|
|
379
446
|
|
|
380
|
-
if (isCodeFile(activeTab
|
|
447
|
+
if (isCodeFile(String(activeTab?.filePath))) {
|
|
381
448
|
return (
|
|
382
|
-
<div className="overflow-x-auto">
|
|
449
|
+
<div className="overflow-x-auto no-scrollbar">
|
|
383
450
|
<ShikiCodeViewer
|
|
384
451
|
code={activeFileContent}
|
|
385
|
-
filePath={activeTab
|
|
452
|
+
filePath={String(activeTab?.filePath)}
|
|
386
453
|
/>
|
|
387
454
|
</div>
|
|
388
455
|
)
|
|
389
456
|
}
|
|
390
457
|
|
|
391
458
|
return <pre className="whitespace-pre-wrap">{activeFileContent}</pre>
|
|
392
|
-
}, [
|
|
459
|
+
}, [
|
|
460
|
+
activeTab,
|
|
461
|
+
activeFileContent,
|
|
462
|
+
isActiveFileFetched,
|
|
463
|
+
isMarkdownFile,
|
|
464
|
+
isCodeFile,
|
|
465
|
+
])
|
|
393
466
|
|
|
394
467
|
const renderTabContent = useCallback(() => {
|
|
395
468
|
if (!activeTab) return null
|
|
@@ -422,7 +495,7 @@ export default function ImportantFilesView({
|
|
|
422
495
|
[activeTab],
|
|
423
496
|
)
|
|
424
497
|
|
|
425
|
-
if (
|
|
498
|
+
if (!isFetched) {
|
|
426
499
|
return (
|
|
427
500
|
<div className="mt-4 border border-gray-200 dark:border-[#30363d] rounded-md overflow-hidden">
|
|
428
501
|
<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]">
|
|
@@ -484,7 +557,9 @@ export default function ImportantFilesView({
|
|
|
484
557
|
</div>
|
|
485
558
|
<div className="ml-auto flex items-center">
|
|
486
559
|
{((activeTab?.type === "file" && activeFileContent) ||
|
|
487
|
-
(activeTab?.type === "ai-review" && aiReviewText)
|
|
560
|
+
(activeTab?.type === "ai-review" && aiReviewText) ||
|
|
561
|
+
(activeTab?.type === "ai" &&
|
|
562
|
+
(pkg?.ai_description || pkg?.ai_usage_instructions))) && (
|
|
488
563
|
<button
|
|
489
564
|
className="hover:bg-gray-200 dark:hover:bg-[#30363d] p-1 rounded-md transition-all duration-300"
|
|
490
565
|
onClick={handleCopy}
|
|
@@ -497,17 +572,29 @@ export default function ImportantFilesView({
|
|
|
497
572
|
<span className="sr-only">Copy</span>
|
|
498
573
|
</button>
|
|
499
574
|
)}
|
|
500
|
-
{activeTab?.type === "ai-review" &&
|
|
575
|
+
{activeTab?.type === "ai-review" &&
|
|
576
|
+
aiReviewText &&
|
|
577
|
+
canManagePackage && (
|
|
578
|
+
<button
|
|
579
|
+
className="hover:bg-gray-200 dark:hover:bg-[#30363d] p-1 rounded-md ml-1"
|
|
580
|
+
onClick={onRequestAiReview}
|
|
581
|
+
title="Re-request AI Review"
|
|
582
|
+
>
|
|
583
|
+
<RefreshCcwIcon className="h-4 w-4" />
|
|
584
|
+
<span className="sr-only">Re-request AI Review</span>
|
|
585
|
+
</button>
|
|
586
|
+
)}
|
|
587
|
+
{activeTab?.type === "ai" && hasAiContent && canManagePackage && (
|
|
501
588
|
<button
|
|
502
589
|
className="hover:bg-gray-200 dark:hover:bg-[#30363d] p-1 rounded-md ml-1"
|
|
503
|
-
onClick={
|
|
504
|
-
title="
|
|
590
|
+
onClick={onRequestAiDescriptionUpdate}
|
|
591
|
+
title="Regenerate AI Description"
|
|
505
592
|
>
|
|
506
593
|
<RefreshCcwIcon className="h-4 w-4" />
|
|
507
|
-
<span className="sr-only">
|
|
594
|
+
<span className="sr-only">Regenerate AI Description</span>
|
|
508
595
|
</button>
|
|
509
596
|
)}
|
|
510
|
-
{activeTab?.type === "file" && (
|
|
597
|
+
{activeTab?.type === "file" && canManagePackage && (
|
|
511
598
|
<button
|
|
512
599
|
className="hover:bg-gray-200 dark:hover:bg-[#30363d] p-1 rounded-md"
|
|
513
600
|
onClick={() => onEditClicked?.(activeTab.filePath)}
|
|
@@ -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 {
|
|
@@ -28,6 +26,7 @@ import { useLocation } from "wouter"
|
|
|
28
26
|
import { Package, PackageFile } from "fake-snippets-api/lib/db/schema"
|
|
29
27
|
import { usePackageFiles } from "@/hooks/use-package-files"
|
|
30
28
|
import { useDownloadZip } from "@/hooks/use-download-zip"
|
|
29
|
+
import { useToast } from "@/hooks/use-toast"
|
|
31
30
|
interface MainContentHeaderProps {
|
|
32
31
|
packageFiles: PackageFile[]
|
|
33
32
|
activeView: string
|
|
@@ -65,10 +64,15 @@ export default function MainContentHeader({
|
|
|
65
64
|
}
|
|
66
65
|
|
|
67
66
|
const { downloadZip } = useDownloadZip()
|
|
67
|
+
const { toastLibrary } = useToast()
|
|
68
68
|
|
|
69
69
|
const handleDownloadZip = () => {
|
|
70
70
|
if (packageInfo && packageFiles) {
|
|
71
|
-
downloadZip(packageInfo, packageFiles)
|
|
71
|
+
toastLibrary.promise(downloadZip(packageInfo, packageFiles), {
|
|
72
|
+
loading: "Downloading ZIP...",
|
|
73
|
+
success: "ZIP downloaded successfully!",
|
|
74
|
+
error: "Failed to download ZIP",
|
|
75
|
+
})
|
|
72
76
|
}
|
|
73
77
|
}
|
|
74
78
|
|
|
@@ -101,7 +105,7 @@ export default function MainContentHeader({
|
|
|
101
105
|
<ChevronDown className="h-4 w-4 ml-0.5" />
|
|
102
106
|
</Button>
|
|
103
107
|
</DropdownMenuTrigger>
|
|
104
|
-
<DropdownMenuContent align="end" className="w-72">
|
|
108
|
+
<DropdownMenuContent align="end" className="w-72 relative z-[101]">
|
|
105
109
|
<DropdownMenuItem disabled={!Boolean(packageInfo)} asChild>
|
|
106
110
|
<a
|
|
107
111
|
href={`/editor?package_id=${packageInfo?.package_id}`}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use client"
|
|
2
1
|
import React from "react"
|
|
3
2
|
import {
|
|
4
3
|
Code,
|
|
@@ -131,7 +130,7 @@ export default function MainContentViewSelector({
|
|
|
131
130
|
</svg>
|
|
132
131
|
</Button>
|
|
133
132
|
</DropdownMenuTrigger>
|
|
134
|
-
<DropdownMenuContent align="start">
|
|
133
|
+
<DropdownMenuContent align="start" className="z-[101]">
|
|
135
134
|
<TooltipProvider>
|
|
136
135
|
{views.map((view) => {
|
|
137
136
|
const isDisabled = !circuitJson && view.requiresCircuitJson
|
|
@@ -1,14 +1,18 @@
|
|
|
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"
|
|
4
|
+
import { usePackageReleaseImages } from "@/hooks/use-package-release-images"
|
|
5
5
|
import { usePreviewImages } from "@/hooks/use-preview-images"
|
|
6
6
|
import { useGlobalStore } from "@/hooks/use-global-store"
|
|
7
7
|
import { Button } from "@/components/ui/button"
|
|
8
8
|
import { useEditPackageDetailsDialog } from "@/components/dialogs/edit-package-details-dialog"
|
|
9
9
|
import React, { useState, useEffect, useMemo, useCallback } from "react"
|
|
10
|
+
import {
|
|
11
|
+
normalizeSvgForSquareTile,
|
|
12
|
+
svgToDataUrl,
|
|
13
|
+
} from "@/lib/normalize-svg-for-tile"
|
|
10
14
|
import { useCurrentPackageInfo } from "@/hooks/use-current-package-info"
|
|
11
|
-
import {
|
|
15
|
+
import { usePackageFileById, usePackageFiles } from "@/hooks/use-package-files"
|
|
12
16
|
import { getLicenseFromLicenseContent } from "@/lib/getLicenseFromLicenseContent"
|
|
13
17
|
|
|
14
18
|
interface MobileSidebarProps {
|
|
@@ -21,19 +25,25 @@ const MobileSidebar = ({
|
|
|
21
25
|
onViewChange,
|
|
22
26
|
}: MobileSidebarProps) => {
|
|
23
27
|
const { packageInfo, refetch: refetchPackageInfo } = useCurrentPackageInfo()
|
|
24
|
-
const { data:
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
const { data: releaseFiles } = usePackageFiles(
|
|
29
|
+
packageInfo?.latest_package_release_id,
|
|
30
|
+
)
|
|
31
|
+
const licenseFileId = useMemo(() => {
|
|
32
|
+
return (
|
|
33
|
+
releaseFiles?.find((f) => f.file_path === "LICENSE")?.package_file_id ||
|
|
34
|
+
null
|
|
35
|
+
)
|
|
36
|
+
}, [releaseFiles])
|
|
37
|
+
const { data: licenseFileMeta } = usePackageFileById(licenseFileId)
|
|
28
38
|
const currentLicense = useMemo(() => {
|
|
29
39
|
if (packageInfo?.latest_license) {
|
|
30
40
|
return packageInfo?.latest_license
|
|
31
41
|
}
|
|
32
42
|
if (licenseFileMeta?.content_text) {
|
|
33
|
-
return getLicenseFromLicenseContent(licenseFileMeta
|
|
43
|
+
return getLicenseFromLicenseContent(licenseFileMeta.content_text)
|
|
34
44
|
}
|
|
35
45
|
return undefined
|
|
36
|
-
}, [licenseFileMeta
|
|
46
|
+
}, [licenseFileMeta, packageInfo?.latest_license])
|
|
37
47
|
const topics = useMemo(
|
|
38
48
|
() => (packageInfo?.is_package ? ["Package"] : ["Board"]),
|
|
39
49
|
[packageInfo?.is_package],
|
|
@@ -70,11 +80,21 @@ const MobileSidebar = ({
|
|
|
70
80
|
[refetchPackageInfo],
|
|
71
81
|
)
|
|
72
82
|
|
|
73
|
-
const { availableViews } =
|
|
83
|
+
const { availableViews: imageViews } = usePackageReleaseImages({
|
|
84
|
+
packageReleaseId: packageInfo?.latest_package_release_id,
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
const { availableViews: pngViews } = usePreviewImages({
|
|
74
88
|
packageName: packageInfo?.name,
|
|
75
89
|
fsMapHash: packageInfo?.latest_package_release_fs_sha ?? "",
|
|
76
90
|
})
|
|
77
91
|
|
|
92
|
+
const viewsToRender =
|
|
93
|
+
imageViews.length === 0 ||
|
|
94
|
+
imageViews.every((v) => !v.isLoading && !v.imageUrl)
|
|
95
|
+
? (pngViews as any)
|
|
96
|
+
: imageViews
|
|
97
|
+
|
|
78
98
|
const handleViewClick = useCallback(
|
|
79
99
|
(viewId: string) => {
|
|
80
100
|
onViewChange?.(viewId as "3d" | "pcb" | "schematic")
|
|
@@ -186,11 +206,14 @@ const MobileSidebar = ({
|
|
|
186
206
|
</div>
|
|
187
207
|
|
|
188
208
|
<div className="grid grid-cols-3 gap-2">
|
|
189
|
-
{
|
|
209
|
+
{viewsToRender.map((view: any) => (
|
|
190
210
|
<PreviewButton
|
|
191
211
|
key={view.id}
|
|
192
212
|
view={view.label}
|
|
193
213
|
onClick={() => handleViewClick(view.id)}
|
|
214
|
+
backgroundClass={view.backgroundClass}
|
|
215
|
+
svg={view.svg}
|
|
216
|
+
isLoading={view.isLoading}
|
|
194
217
|
imageUrl={view.imageUrl}
|
|
195
218
|
status={view.status}
|
|
196
219
|
onLoad={view.onLoad}
|
|
@@ -226,6 +249,9 @@ export default React.memo(MobileSidebar)
|
|
|
226
249
|
function PreviewButton({
|
|
227
250
|
view,
|
|
228
251
|
onClick,
|
|
252
|
+
backgroundClass,
|
|
253
|
+
svg,
|
|
254
|
+
isLoading,
|
|
229
255
|
imageUrl,
|
|
230
256
|
status,
|
|
231
257
|
onLoad,
|
|
@@ -233,30 +259,38 @@ function PreviewButton({
|
|
|
233
259
|
}: {
|
|
234
260
|
view: string
|
|
235
261
|
onClick: () => void
|
|
262
|
+
backgroundClass?: string
|
|
263
|
+
svg?: string | null
|
|
264
|
+
isLoading?: boolean
|
|
236
265
|
imageUrl?: string
|
|
237
|
-
status
|
|
238
|
-
onLoad
|
|
239
|
-
onError
|
|
266
|
+
status?: "loading" | "loaded" | "error"
|
|
267
|
+
onLoad?: () => void
|
|
268
|
+
onError?: () => void
|
|
240
269
|
}) {
|
|
241
|
-
if (
|
|
270
|
+
if (!svg && !isLoading && !imageUrl) {
|
|
242
271
|
return null
|
|
243
272
|
}
|
|
244
273
|
|
|
245
274
|
return (
|
|
246
275
|
<button
|
|
247
276
|
onClick={onClick}
|
|
248
|
-
className=
|
|
277
|
+
className={`aspect-square ${backgroundClass ?? "bg-gray-100"} rounded-lg border border-gray-200 dark:border-[#30363d] flex items-center justify-center transition-colors mt-4 overflow-hidden`}
|
|
249
278
|
>
|
|
250
|
-
{status === "loading" && (
|
|
279
|
+
{(isLoading || status === "loading") && (
|
|
251
280
|
<Skeleton className="w-full h-full rounded-lg" />
|
|
252
281
|
)}
|
|
253
|
-
{
|
|
282
|
+
{!isLoading && !status && svg && (
|
|
283
|
+
<img
|
|
284
|
+
src={svgToDataUrl(normalizeSvgForSquareTile(svg))}
|
|
285
|
+
alt={view}
|
|
286
|
+
className="w-full h-full object-contain"
|
|
287
|
+
/>
|
|
288
|
+
)}
|
|
289
|
+
{imageUrl && !isLoading && (
|
|
254
290
|
<img
|
|
255
291
|
src={imageUrl}
|
|
256
292
|
alt={view}
|
|
257
|
-
className=
|
|
258
|
-
status === "loaded" ? "block" : "hidden"
|
|
259
|
-
}`}
|
|
293
|
+
className="w-full h-full object-cover rounded-lg"
|
|
260
294
|
onLoad={onLoad}
|
|
261
295
|
onError={onError}
|
|
262
296
|
/>
|