@tscircuit/fake-snippets 0.0.109 → 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 +349 -453
- package/bunfig.toml +2 -1
- package/dist/bundle.js +1253 -624
- package/dist/index.d.ts +291 -4
- package/dist/index.js +323 -23
- package/dist/schema.d.ts +274 -1
- package/dist/schema.js +52 -1
- 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 +61 -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 +24 -20
- package/renovate.json +1 -1
- package/scripts/generate-sitemap.ts +1 -1
- package/src/App.tsx +29 -8
- 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 +3 -4
- package/src/components/FileSidebar.tsx +11 -17
- package/src/components/Footer.tsx +8 -9
- 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 +43 -15
- package/src/components/NotFound.tsx +5 -5
- package/src/components/PackageBreadcrumb.tsx +6 -12
- 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/build-status.tsx +1 -1
- package/src/components/ViewPackagePage/components/important-files-view.tsx +105 -34
- package/src/components/ViewPackagePage/components/main-content-header.tsx +10 -6
- package/src/components/ViewPackagePage/components/main-content-view-selector.tsx +1 -1
- package/src/components/ViewPackagePage/components/mobile-sidebar.tsx +54 -19
- package/src/components/ViewPackagePage/components/package-header.tsx +25 -33
- package/src/components/ViewPackagePage/components/preview-image-squares.tsx +11 -18
- package/src/components/ViewPackagePage/components/repo-page-content.tsx +12 -5
- 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/tab-views/pcb-view.tsx +1 -2
- package/src/components/ViewPackagePage/components/tab-views/schematic-view.tsx +2 -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 +15 -12
- package/src/components/package-port/CodeEditor.tsx +4 -30
- package/src/components/package-port/CodeEditorHeader.tsx +123 -61
- 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 +23 -11
- 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-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 +51 -22
- package/src/hooks/useOptimizedPackageFilesLoader.ts +11 -24
- package/src/hooks/usePackageFilesLoader.ts +2 -2
- package/src/hooks/useUpdatePackageFilesMutation.ts +13 -1
- package/src/lib/download-fns/download-gltf-from-circuit-json.ts +1 -1
- 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 +2 -1
- package/src/main.tsx +2 -1
- 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 -6
- 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 +75 -145
- 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 +7 -13
- package/vite.config.ts +9 -0
- package/fake-snippets-api/routes/api/autocomplete/create_autocomplete.ts +0 -133
- package/src/components/JLCPCBImportDialog.tsx +0 -280
- package/src/components/PackageBuildsPage/LogContent.tsx +0 -72
- package/src/components/PackageBuildsPage/PackageBuildDetailsPage.tsx +0 -113
- package/src/components/PackageBuildsPage/build-preview-content.tsx +0 -56
- 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/pages/package-builds.tsx +0 -33
|
@@ -5,7 +5,7 @@ import React, { useEffect, useRef, useState } from "react"
|
|
|
5
5
|
import { useQuery } from "react-query"
|
|
6
6
|
import { Alert } from "./ui/alert"
|
|
7
7
|
import { useApiBaseUrl } from "@/hooks/use-packages-base-api-url"
|
|
8
|
-
import {
|
|
8
|
+
import { Link } from "wouter"
|
|
9
9
|
import { CircuitBoard } from "lucide-react"
|
|
10
10
|
import { cn } from "@/lib/utils"
|
|
11
11
|
|
|
@@ -42,9 +42,9 @@ const LinkWithNewTabHandling = ({
|
|
|
42
42
|
)
|
|
43
43
|
}
|
|
44
44
|
return (
|
|
45
|
-
<
|
|
45
|
+
<Link onClick={onClick} className={className} href={href}>
|
|
46
46
|
{children}
|
|
47
|
-
</
|
|
47
|
+
</Link>
|
|
48
48
|
)
|
|
49
49
|
}
|
|
50
50
|
|
|
@@ -60,7 +60,7 @@ const SearchComponent: React.FC<SearchComponentProps> = ({
|
|
|
60
60
|
const resultsRef = useRef<HTMLDivElement>(null)
|
|
61
61
|
const inputRef = useRef<HTMLInputElement>(null)
|
|
62
62
|
const [location, setLocation] = useLocation()
|
|
63
|
-
const
|
|
63
|
+
const apiBaseUrl = useApiBaseUrl()
|
|
64
64
|
|
|
65
65
|
const { data: searchResults, isLoading } = useQuery(
|
|
66
66
|
["packageSearch", searchQuery],
|
|
@@ -137,10 +137,13 @@ const SearchComponent: React.FC<SearchComponentProps> = ({
|
|
|
137
137
|
<Input
|
|
138
138
|
autoComplete="off"
|
|
139
139
|
spellCheck={false}
|
|
140
|
+
autoCorrect="off"
|
|
141
|
+
autoCapitalize="off"
|
|
140
142
|
ref={inputRef}
|
|
141
143
|
type="search"
|
|
144
|
+
aria-autocomplete="none"
|
|
142
145
|
placeholder="Search"
|
|
143
|
-
className="pl-4 focus:border-blue-500 placeholder-gray-400 text-sm"
|
|
146
|
+
className="pl-4 focus:border-blue-500 placeholder-gray-400 text-sm select-none"
|
|
144
147
|
value={searchQuery}
|
|
145
148
|
onChange={(e) => {
|
|
146
149
|
setSearchQuery(e.target.value)
|
|
@@ -195,10 +198,10 @@ const SearchComponent: React.FC<SearchComponentProps> = ({
|
|
|
195
198
|
{showResults && searchResults && (
|
|
196
199
|
<div
|
|
197
200
|
ref={resultsRef}
|
|
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"
|
|
201
|
+
className="absolute top-full md:left-0 right-0 no-scrollbar mt-2 bg-white shadow-lg rounded-md z-50 w-80 max-h-screen overflow-y-auto overflow-x-visible"
|
|
199
202
|
>
|
|
200
203
|
{searchResults.length > 0 ? (
|
|
201
|
-
<ul className="divide-y divide-gray-200">
|
|
204
|
+
<ul className="divide-y divide-gray-200 no-scrollbar">
|
|
202
205
|
{searchResults.map((pkg: any, index: number) => (
|
|
203
206
|
<li
|
|
204
207
|
key={pkg.package_id}
|
|
@@ -222,8 +225,9 @@ const SearchComponent: React.FC<SearchComponentProps> = ({
|
|
|
222
225
|
>
|
|
223
226
|
<div className="w-12 h-12 overflow-hidden mr-2 flex-shrink-0 rounded-sm bg-gray-50 border flex items-center justify-center">
|
|
224
227
|
<img
|
|
225
|
-
src={`${
|
|
228
|
+
src={`${apiBaseUrl}/packages/images/${pkg.name}/pcb.svg`}
|
|
226
229
|
alt={`PCB preview for ${pkg.name}`}
|
|
230
|
+
draggable={false}
|
|
227
231
|
className="w-12 h-12 object-contain p-1 scale-[4] rotate-45"
|
|
228
232
|
onError={(e) => {
|
|
229
233
|
e.currentTarget.style.display = "none"
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import { Link } from "wouter"
|
|
3
|
+
import { Account } from "fake-snippets-api/lib/db/schema"
|
|
4
|
+
import { User } from "lucide-react"
|
|
5
|
+
|
|
6
|
+
export interface UserCardProps {
|
|
7
|
+
/** The account data to display */
|
|
8
|
+
account: Account | Omit<Account, "account_id">
|
|
9
|
+
/** Whether to render the card with a link to the user profile page */
|
|
10
|
+
withLink?: boolean
|
|
11
|
+
/** Custom class name for the card container */
|
|
12
|
+
className?: string
|
|
13
|
+
/** Custom onClick handler */
|
|
14
|
+
onClick?: (account: Account | Omit<Account, "account_id">) => void
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const UserCard: React.FC<UserCardProps> = ({
|
|
18
|
+
account,
|
|
19
|
+
withLink = true,
|
|
20
|
+
className = "",
|
|
21
|
+
onClick,
|
|
22
|
+
}) => {
|
|
23
|
+
const handleClick = () => {
|
|
24
|
+
if (onClick) {
|
|
25
|
+
onClick(account)
|
|
26
|
+
} else if (!withLink) {
|
|
27
|
+
window.location.href = `/${account.github_username}`
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const cardContent = (
|
|
32
|
+
<div
|
|
33
|
+
className={`border p-4 rounded-md hover:shadow-md transition-shadow flex flex-col gap-4 cursor-pointer ${className}`}
|
|
34
|
+
onClick={!withLink ? handleClick : undefined}
|
|
35
|
+
>
|
|
36
|
+
<div className="flex items-start gap-4">
|
|
37
|
+
<div className="w-16 h-16 flex-shrink-0 rounded-md overflow-hidden bg-gray-50 border flex items-center justify-center">
|
|
38
|
+
<img
|
|
39
|
+
src={`https://github.com/${account.github_username}.png`}
|
|
40
|
+
alt={`${account.github_username} avatar`}
|
|
41
|
+
className="object-cover h-full w-full transition-transform duration-300 hover:scale-110"
|
|
42
|
+
onError={(e) => {
|
|
43
|
+
const target = e.target as HTMLImageElement
|
|
44
|
+
target.style.display = "none"
|
|
45
|
+
target.nextElementSibling?.classList.remove("hidden")
|
|
46
|
+
target.nextElementSibling?.classList.add("flex")
|
|
47
|
+
}}
|
|
48
|
+
/>
|
|
49
|
+
<div className="hidden items-center justify-center h-full w-full">
|
|
50
|
+
<User className="w-6 h-6 text-gray-300" />
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
<div className="flex-1 min-w-0 flex flex-col justify-center my-auto">
|
|
54
|
+
<div className="flex justify-between items-start">
|
|
55
|
+
<h2 className="text-md font-semibold truncate pr-[30px]">
|
|
56
|
+
<span className="text-gray-900">{account.github_username}</span>
|
|
57
|
+
</h2>
|
|
58
|
+
</div>
|
|
59
|
+
<p className="text-sm text-gray-500 truncate max-w-xs">
|
|
60
|
+
@{account.github_username}
|
|
61
|
+
</p>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
if (withLink) {
|
|
68
|
+
return (
|
|
69
|
+
<Link
|
|
70
|
+
key={account.github_username}
|
|
71
|
+
href={`/${account.github_username}`}
|
|
72
|
+
onClick={onClick ? () => onClick(account) : undefined}
|
|
73
|
+
>
|
|
74
|
+
{cardContent}
|
|
75
|
+
</Link>
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return cardContent
|
|
80
|
+
}
|
|
@@ -14,7 +14,7 @@ export interface BuildStatusProps {
|
|
|
14
14
|
|
|
15
15
|
export const BuildStatus = ({ step, packageReleaseId }: BuildStatusProps) => {
|
|
16
16
|
const { author, packageName } = useParams()
|
|
17
|
-
const href = `/${author}/${packageName}/
|
|
17
|
+
const href = `/${author}/${packageName}/releases/${packageReleaseId}`
|
|
18
18
|
|
|
19
19
|
return (
|
|
20
20
|
<Link href={href} className="flex items-center gap-2">
|
|
@@ -16,6 +16,8 @@ import { ShikiCodeViewer, SKELETON_WIDTHS } from "./ShikiCodeViewer"
|
|
|
16
16
|
import MarkdownViewer from "./markdown-viewer"
|
|
17
17
|
import { useGlobalStore } from "@/hooks/use-global-store"
|
|
18
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"
|
|
19
21
|
|
|
20
22
|
interface PackageFile {
|
|
21
23
|
package_file_id: string
|
|
@@ -29,13 +31,12 @@ interface ImportantFilesViewProps {
|
|
|
29
31
|
importantFiles?: PackageFile[]
|
|
30
32
|
isFetched?: boolean
|
|
31
33
|
onEditClicked?: (file_path?: string | null) => void
|
|
32
|
-
packageAuthorOwner?: string | null
|
|
33
|
-
aiDescription?: string
|
|
34
|
-
aiUsageInstructions?: string
|
|
35
34
|
aiReviewText?: string | null
|
|
36
35
|
aiReviewRequested?: boolean
|
|
37
36
|
onRequestAiReview?: () => void
|
|
37
|
+
onRequestAiDescriptionUpdate?: () => void
|
|
38
38
|
onLicenseFileRequested?: boolean
|
|
39
|
+
pkg?: Package
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
type TabType = "ai" | "ai-review" | "file"
|
|
@@ -49,30 +50,44 @@ interface TabInfo {
|
|
|
49
50
|
|
|
50
51
|
export default function ImportantFilesView({
|
|
51
52
|
importantFiles = [],
|
|
52
|
-
aiDescription,
|
|
53
|
-
aiUsageInstructions,
|
|
54
53
|
aiReviewText,
|
|
55
54
|
aiReviewRequested,
|
|
56
55
|
onRequestAiReview,
|
|
56
|
+
onRequestAiDescriptionUpdate,
|
|
57
57
|
isFetched = false,
|
|
58
58
|
onEditClicked,
|
|
59
|
-
packageAuthorOwner,
|
|
60
59
|
onLicenseFileRequested,
|
|
60
|
+
pkg,
|
|
61
61
|
}: ImportantFilesViewProps) {
|
|
62
62
|
const [activeTab, setActiveTab] = useState<TabInfo | null>(null)
|
|
63
63
|
const [copyState, setCopyState] = useState<"copy" | "copied">("copy")
|
|
64
64
|
const { session: user } = useGlobalStore()
|
|
65
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
|
+
|
|
66
74
|
// Memoized computed values
|
|
67
75
|
const hasAiContent = useMemo(
|
|
68
|
-
() => Boolean(
|
|
69
|
-
[
|
|
76
|
+
() => Boolean(pkg?.ai_description || pkg?.ai_usage_instructions),
|
|
77
|
+
[pkg?.ai_description, pkg?.ai_usage_instructions],
|
|
70
78
|
)
|
|
71
79
|
const hasAiReview = useMemo(() => Boolean(aiReviewText), [aiReviewText])
|
|
72
80
|
const isOwner = useMemo(
|
|
73
|
-
() => user?.github_username ===
|
|
74
|
-
[user?.github_username,
|
|
81
|
+
() => user?.github_username === pkg?.owner_github_username,
|
|
82
|
+
[user?.github_username, pkg?.owner_github_username],
|
|
75
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])
|
|
76
91
|
|
|
77
92
|
// File type utilities
|
|
78
93
|
const isLicenseFile = useCallback((filePath: string) => {
|
|
@@ -139,7 +154,7 @@ export default function ImportantFilesView({
|
|
|
139
154
|
}
|
|
140
155
|
|
|
141
156
|
// Only show AI review tab if there's actual AI review content
|
|
142
|
-
if (hasAiReview ||
|
|
157
|
+
if (hasAiReview || canManagePackage) {
|
|
143
158
|
tabs.push({
|
|
144
159
|
type: "ai-review",
|
|
145
160
|
filePath: null,
|
|
@@ -286,6 +301,16 @@ export default function ImportantFilesView({
|
|
|
286
301
|
|
|
287
302
|
if (activeTab?.type === "ai-review" && aiReviewText) {
|
|
288
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")
|
|
289
314
|
} else if (activeTab?.type === "file" && activeFileContent) {
|
|
290
315
|
textToCopy = activeFileContent
|
|
291
316
|
}
|
|
@@ -298,25 +323,49 @@ export default function ImportantFilesView({
|
|
|
298
323
|
}
|
|
299
324
|
|
|
300
325
|
// Render content based on active tab
|
|
301
|
-
const renderAiContent = useCallback(
|
|
302
|
-
()
|
|
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 (
|
|
303
348
|
<div className="markdown-content">
|
|
304
|
-
{
|
|
349
|
+
{pkg?.ai_description && (
|
|
305
350
|
<div className="mb-6">
|
|
306
351
|
<h3 className="font-semibold text-lg mb-2">Description</h3>
|
|
307
|
-
<MarkdownViewer markdownContent={
|
|
352
|
+
<MarkdownViewer markdownContent={pkg?.ai_description} />
|
|
308
353
|
</div>
|
|
309
354
|
)}
|
|
310
|
-
{
|
|
355
|
+
{pkg?.ai_usage_instructions && (
|
|
311
356
|
<div>
|
|
312
357
|
<h3 className="font-semibold text-lg mb-2">Instructions</h3>
|
|
313
|
-
<MarkdownViewer markdownContent={
|
|
358
|
+
<MarkdownViewer markdownContent={pkg?.ai_usage_instructions} />
|
|
314
359
|
</div>
|
|
315
360
|
)}
|
|
316
361
|
</div>
|
|
317
|
-
)
|
|
318
|
-
|
|
319
|
-
|
|
362
|
+
)
|
|
363
|
+
}, [
|
|
364
|
+
pkg?.ai_description,
|
|
365
|
+
pkg?.ai_usage_instructions,
|
|
366
|
+
canManagePackage,
|
|
367
|
+
onRequestAiDescriptionUpdate,
|
|
368
|
+
])
|
|
320
369
|
|
|
321
370
|
const renderAiReviewContent = useCallback(() => {
|
|
322
371
|
if (!aiReviewText && !aiReviewRequested) {
|
|
@@ -334,7 +383,7 @@ export default function ImportantFilesView({
|
|
|
334
383
|
from our AI assistant.
|
|
335
384
|
</p>
|
|
336
385
|
</div>
|
|
337
|
-
{!
|
|
386
|
+
{!canManagePackage ? (
|
|
338
387
|
<p className="text-sm text-gray-500">
|
|
339
388
|
Only the package owner can generate an AI review
|
|
340
389
|
</p>
|
|
@@ -378,15 +427,17 @@ export default function ImportantFilesView({
|
|
|
378
427
|
}
|
|
379
428
|
|
|
380
429
|
return <MarkdownViewer markdownContent={aiReviewText || ""} />
|
|
381
|
-
}, [aiReviewText, aiReviewRequested,
|
|
430
|
+
}, [aiReviewText, aiReviewRequested, canManagePackage, onRequestAiReview])
|
|
382
431
|
|
|
383
432
|
const renderFileContent = useCallback(() => {
|
|
384
433
|
if (!isActiveFileFetched || !activeTab?.filePath || !activeFileContent) {
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
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
|
+
)
|
|
390
441
|
}
|
|
391
442
|
|
|
392
443
|
if (isMarkdownFile(String(activeTab?.filePath))) {
|
|
@@ -405,7 +456,13 @@ export default function ImportantFilesView({
|
|
|
405
456
|
}
|
|
406
457
|
|
|
407
458
|
return <pre className="whitespace-pre-wrap">{activeFileContent}</pre>
|
|
408
|
-
}, [
|
|
459
|
+
}, [
|
|
460
|
+
activeTab,
|
|
461
|
+
activeFileContent,
|
|
462
|
+
isActiveFileFetched,
|
|
463
|
+
isMarkdownFile,
|
|
464
|
+
isCodeFile,
|
|
465
|
+
])
|
|
409
466
|
|
|
410
467
|
const renderTabContent = useCallback(() => {
|
|
411
468
|
if (!activeTab) return null
|
|
@@ -500,7 +557,9 @@ export default function ImportantFilesView({
|
|
|
500
557
|
</div>
|
|
501
558
|
<div className="ml-auto flex items-center">
|
|
502
559
|
{((activeTab?.type === "file" && activeFileContent) ||
|
|
503
|
-
(activeTab?.type === "ai-review" && aiReviewText)
|
|
560
|
+
(activeTab?.type === "ai-review" && aiReviewText) ||
|
|
561
|
+
(activeTab?.type === "ai" &&
|
|
562
|
+
(pkg?.ai_description || pkg?.ai_usage_instructions))) && (
|
|
504
563
|
<button
|
|
505
564
|
className="hover:bg-gray-200 dark:hover:bg-[#30363d] p-1 rounded-md transition-all duration-300"
|
|
506
565
|
onClick={handleCopy}
|
|
@@ -513,17 +572,29 @@ export default function ImportantFilesView({
|
|
|
513
572
|
<span className="sr-only">Copy</span>
|
|
514
573
|
</button>
|
|
515
574
|
)}
|
|
516
|
-
{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 && (
|
|
517
588
|
<button
|
|
518
589
|
className="hover:bg-gray-200 dark:hover:bg-[#30363d] p-1 rounded-md ml-1"
|
|
519
|
-
onClick={
|
|
520
|
-
title="
|
|
590
|
+
onClick={onRequestAiDescriptionUpdate}
|
|
591
|
+
title="Regenerate AI Description"
|
|
521
592
|
>
|
|
522
593
|
<RefreshCcwIcon className="h-4 w-4" />
|
|
523
|
-
<span className="sr-only">
|
|
594
|
+
<span className="sr-only">Regenerate AI Description</span>
|
|
524
595
|
</button>
|
|
525
596
|
)}
|
|
526
|
-
{activeTab?.type === "file" && (
|
|
597
|
+
{activeTab?.type === "file" && canManagePackage && (
|
|
527
598
|
<button
|
|
528
599
|
className="hover:bg-gray-200 dark:hover:bg-[#30363d] p-1 rounded-md"
|
|
529
600
|
onClick={() => onEditClicked?.(activeTab.filePath)}
|
|
@@ -26,6 +26,7 @@ import { useLocation } from "wouter"
|
|
|
26
26
|
import { Package, PackageFile } from "fake-snippets-api/lib/db/schema"
|
|
27
27
|
import { usePackageFiles } from "@/hooks/use-package-files"
|
|
28
28
|
import { useDownloadZip } from "@/hooks/use-download-zip"
|
|
29
|
+
import { useToast } from "@/hooks/use-toast"
|
|
29
30
|
interface MainContentHeaderProps {
|
|
30
31
|
packageFiles: PackageFile[]
|
|
31
32
|
activeView: string
|
|
@@ -63,16 +64,19 @@ export default function MainContentHeader({
|
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
const { downloadZip } = useDownloadZip()
|
|
67
|
+
const { toastLibrary } = useToast()
|
|
66
68
|
|
|
67
69
|
const handleDownloadZip = () => {
|
|
68
70
|
if (packageInfo && packageFiles) {
|
|
69
|
-
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
|
+
})
|
|
70
76
|
}
|
|
71
77
|
}
|
|
72
78
|
|
|
73
|
-
const
|
|
74
|
-
(file) => file.file_path === "dist/circuit.json",
|
|
75
|
-
)
|
|
79
|
+
const { circuitJson } = useCurrentPackageCircuitJson()
|
|
76
80
|
|
|
77
81
|
return (
|
|
78
82
|
<div className="flex items-center justify-between mb-4">
|
|
@@ -86,7 +90,7 @@ export default function MainContentHeader({
|
|
|
86
90
|
unscopedName={packageInfo?.unscoped_name}
|
|
87
91
|
desiredImageType={activeView}
|
|
88
92
|
author={packageInfo?.owner_github_username ?? undefined}
|
|
89
|
-
|
|
93
|
+
circuitJson={circuitJson}
|
|
90
94
|
/>
|
|
91
95
|
|
|
92
96
|
{/* Code Dropdown */}
|
|
@@ -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}`}
|
|
@@ -130,7 +130,7 @@ export default function MainContentViewSelector({
|
|
|
130
130
|
</svg>
|
|
131
131
|
</Button>
|
|
132
132
|
</DropdownMenuTrigger>
|
|
133
|
-
<DropdownMenuContent align="start">
|
|
133
|
+
<DropdownMenuContent align="start" className="z-[101]">
|
|
134
134
|
<TooltipProvider>
|
|
135
135
|
{views.map((view) => {
|
|
136
136
|
const isDisabled = !circuitJson && view.requiresCircuitJson
|
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
import { GitFork, Star, Tag, Settings, LinkIcon } from "lucide-react"
|
|
2
2
|
import { Badge } from "@/components/ui/badge"
|
|
3
3
|
import { Skeleton } from "@/components/ui/skeleton"
|
|
4
|
+
import { usePackageReleaseImages } from "@/hooks/use-package-release-images"
|
|
4
5
|
import { usePreviewImages } from "@/hooks/use-preview-images"
|
|
5
6
|
import { useGlobalStore } from "@/hooks/use-global-store"
|
|
6
7
|
import { Button } from "@/components/ui/button"
|
|
7
8
|
import { useEditPackageDetailsDialog } from "@/components/dialogs/edit-package-details-dialog"
|
|
8
9
|
import React, { useState, useEffect, useMemo, useCallback } from "react"
|
|
10
|
+
import {
|
|
11
|
+
normalizeSvgForSquareTile,
|
|
12
|
+
svgToDataUrl,
|
|
13
|
+
} from "@/lib/normalize-svg-for-tile"
|
|
9
14
|
import { useCurrentPackageInfo } from "@/hooks/use-current-package-info"
|
|
10
|
-
import {
|
|
15
|
+
import { usePackageFileById, usePackageFiles } from "@/hooks/use-package-files"
|
|
11
16
|
import { getLicenseFromLicenseContent } from "@/lib/getLicenseFromLicenseContent"
|
|
12
17
|
|
|
13
18
|
interface MobileSidebarProps {
|
|
@@ -20,19 +25,25 @@ const MobileSidebar = ({
|
|
|
20
25
|
onViewChange,
|
|
21
26
|
}: MobileSidebarProps) => {
|
|
22
27
|
const { packageInfo, refetch: refetchPackageInfo } = useCurrentPackageInfo()
|
|
23
|
-
const { data:
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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)
|
|
27
38
|
const currentLicense = useMemo(() => {
|
|
28
39
|
if (packageInfo?.latest_license) {
|
|
29
40
|
return packageInfo?.latest_license
|
|
30
41
|
}
|
|
31
42
|
if (licenseFileMeta?.content_text) {
|
|
32
|
-
return getLicenseFromLicenseContent(licenseFileMeta
|
|
43
|
+
return getLicenseFromLicenseContent(licenseFileMeta.content_text)
|
|
33
44
|
}
|
|
34
45
|
return undefined
|
|
35
|
-
}, [licenseFileMeta
|
|
46
|
+
}, [licenseFileMeta, packageInfo?.latest_license])
|
|
36
47
|
const topics = useMemo(
|
|
37
48
|
() => (packageInfo?.is_package ? ["Package"] : ["Board"]),
|
|
38
49
|
[packageInfo?.is_package],
|
|
@@ -69,11 +80,21 @@ const MobileSidebar = ({
|
|
|
69
80
|
[refetchPackageInfo],
|
|
70
81
|
)
|
|
71
82
|
|
|
72
|
-
const { availableViews } =
|
|
83
|
+
const { availableViews: imageViews } = usePackageReleaseImages({
|
|
84
|
+
packageReleaseId: packageInfo?.latest_package_release_id,
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
const { availableViews: pngViews } = usePreviewImages({
|
|
73
88
|
packageName: packageInfo?.name,
|
|
74
89
|
fsMapHash: packageInfo?.latest_package_release_fs_sha ?? "",
|
|
75
90
|
})
|
|
76
91
|
|
|
92
|
+
const viewsToRender =
|
|
93
|
+
imageViews.length === 0 ||
|
|
94
|
+
imageViews.every((v) => !v.isLoading && !v.imageUrl)
|
|
95
|
+
? (pngViews as any)
|
|
96
|
+
: imageViews
|
|
97
|
+
|
|
77
98
|
const handleViewClick = useCallback(
|
|
78
99
|
(viewId: string) => {
|
|
79
100
|
onViewChange?.(viewId as "3d" | "pcb" | "schematic")
|
|
@@ -185,11 +206,14 @@ const MobileSidebar = ({
|
|
|
185
206
|
</div>
|
|
186
207
|
|
|
187
208
|
<div className="grid grid-cols-3 gap-2">
|
|
188
|
-
{
|
|
209
|
+
{viewsToRender.map((view: any) => (
|
|
189
210
|
<PreviewButton
|
|
190
211
|
key={view.id}
|
|
191
212
|
view={view.label}
|
|
192
213
|
onClick={() => handleViewClick(view.id)}
|
|
214
|
+
backgroundClass={view.backgroundClass}
|
|
215
|
+
svg={view.svg}
|
|
216
|
+
isLoading={view.isLoading}
|
|
193
217
|
imageUrl={view.imageUrl}
|
|
194
218
|
status={view.status}
|
|
195
219
|
onLoad={view.onLoad}
|
|
@@ -225,6 +249,9 @@ export default React.memo(MobileSidebar)
|
|
|
225
249
|
function PreviewButton({
|
|
226
250
|
view,
|
|
227
251
|
onClick,
|
|
252
|
+
backgroundClass,
|
|
253
|
+
svg,
|
|
254
|
+
isLoading,
|
|
228
255
|
imageUrl,
|
|
229
256
|
status,
|
|
230
257
|
onLoad,
|
|
@@ -232,30 +259,38 @@ function PreviewButton({
|
|
|
232
259
|
}: {
|
|
233
260
|
view: string
|
|
234
261
|
onClick: () => void
|
|
262
|
+
backgroundClass?: string
|
|
263
|
+
svg?: string | null
|
|
264
|
+
isLoading?: boolean
|
|
235
265
|
imageUrl?: string
|
|
236
|
-
status
|
|
237
|
-
onLoad
|
|
238
|
-
onError
|
|
266
|
+
status?: "loading" | "loaded" | "error"
|
|
267
|
+
onLoad?: () => void
|
|
268
|
+
onError?: () => void
|
|
239
269
|
}) {
|
|
240
|
-
if (
|
|
270
|
+
if (!svg && !isLoading && !imageUrl) {
|
|
241
271
|
return null
|
|
242
272
|
}
|
|
243
273
|
|
|
244
274
|
return (
|
|
245
275
|
<button
|
|
246
276
|
onClick={onClick}
|
|
247
|
-
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`}
|
|
248
278
|
>
|
|
249
|
-
{status === "loading" && (
|
|
279
|
+
{(isLoading || status === "loading") && (
|
|
250
280
|
<Skeleton className="w-full h-full rounded-lg" />
|
|
251
281
|
)}
|
|
252
|
-
{
|
|
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 && (
|
|
253
290
|
<img
|
|
254
291
|
src={imageUrl}
|
|
255
292
|
alt={view}
|
|
256
|
-
className=
|
|
257
|
-
status === "loaded" ? "block" : "hidden"
|
|
258
|
-
}`}
|
|
293
|
+
className="w-full h-full object-cover rounded-lg"
|
|
259
294
|
onLoad={onLoad}
|
|
260
295
|
onError={onError}
|
|
261
296
|
/>
|