@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
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { useQuery } from "react-query"
|
|
2
|
+
import { useAxios } from "@/hooks/use-axios"
|
|
3
|
+
import type { PublicOrgSchema } from "fake-snippets-api/lib/db/schema"
|
|
4
|
+
|
|
5
|
+
export const useOrganization = ({
|
|
6
|
+
orgId,
|
|
7
|
+
orgName,
|
|
8
|
+
github_handle,
|
|
9
|
+
}: { orgId?: string; orgName?: string; github_handle?: string }) => {
|
|
10
|
+
const axios = useAxios()
|
|
11
|
+
|
|
12
|
+
const orgQuery = useQuery<PublicOrgSchema, Error & { status: number }>(
|
|
13
|
+
["orgs", "get", orgId || orgName || github_handle],
|
|
14
|
+
async () => {
|
|
15
|
+
if (!orgId && !orgName && !github_handle) {
|
|
16
|
+
throw new Error("Organization ID, name, or GitHub handle is required")
|
|
17
|
+
}
|
|
18
|
+
const params = orgId
|
|
19
|
+
? { org_id: orgId }
|
|
20
|
+
: orgName
|
|
21
|
+
? { org_name: orgName }
|
|
22
|
+
: { github_handle }
|
|
23
|
+
const { data } = await axios.get("/orgs/get", { params })
|
|
24
|
+
return data.org
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
enabled: Boolean(orgId || orgName),
|
|
28
|
+
retry: false,
|
|
29
|
+
refetchOnWindowFocus: false,
|
|
30
|
+
keepPreviousData: true,
|
|
31
|
+
},
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
organization: orgQuery.data,
|
|
36
|
+
membersCount: orgQuery.data?.member_count || 0,
|
|
37
|
+
packagesCount: orgQuery.data?.package_count || 0,
|
|
38
|
+
isLoading: orgQuery.isLoading,
|
|
39
|
+
isError: orgQuery.isError,
|
|
40
|
+
error: orgQuery.error,
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -32,8 +32,10 @@ export const usePackageAsSnippet = (packageId: string | null) => {
|
|
|
32
32
|
if (!packageQuery.data?.latest_package_release_id) {
|
|
33
33
|
throw new Error("No latest release ID available")
|
|
34
34
|
}
|
|
35
|
-
const { data } = await axios.
|
|
36
|
-
|
|
35
|
+
const { data } = await axios.get("/package_files/list", {
|
|
36
|
+
params: {
|
|
37
|
+
package_release_id: packageQuery.data.latest_package_release_id,
|
|
38
|
+
},
|
|
37
39
|
})
|
|
38
40
|
return data.package_files
|
|
39
41
|
},
|
|
@@ -56,11 +56,14 @@ export const usePackageBuildsByReleaseId = (releaseId?: string | null) => {
|
|
|
56
56
|
return usePackageBuilds(releaseId ? { package_release_id: releaseId } : null)
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
export const usePackageBuild = (
|
|
59
|
+
export const usePackageBuild = (
|
|
60
|
+
packageBuildId: string | null,
|
|
61
|
+
options?: { include_logs?: boolean },
|
|
62
|
+
) => {
|
|
60
63
|
const axios = useAxios()
|
|
61
64
|
|
|
62
65
|
return useQuery<PackageBuild, Error & { status: number }>(
|
|
63
|
-
["packageBuild", packageBuildId],
|
|
66
|
+
["packageBuild", packageBuildId, options?.include_logs],
|
|
64
67
|
async () => {
|
|
65
68
|
if (!packageBuildId) {
|
|
66
69
|
throw new Error("package_build_id is required")
|
|
@@ -69,6 +72,7 @@ export const usePackageBuild = (packageBuildId: string | null) => {
|
|
|
69
72
|
const { data } = await axios.get("/package_builds/get", {
|
|
70
73
|
params: {
|
|
71
74
|
package_build_id: packageBuildId,
|
|
75
|
+
include_logs: options?.include_logs,
|
|
72
76
|
},
|
|
73
77
|
})
|
|
74
78
|
|
|
@@ -36,7 +36,9 @@ export const usePackageFile = (
|
|
|
36
36
|
async () => {
|
|
37
37
|
if (!query) return
|
|
38
38
|
|
|
39
|
-
const { data } = await axios.
|
|
39
|
+
const { data } = await axios.get("/package_files/get", {
|
|
40
|
+
params: query,
|
|
41
|
+
})
|
|
40
42
|
|
|
41
43
|
if (!data.package_file) {
|
|
42
44
|
throw new Error("Package file not found")
|
|
@@ -98,8 +100,8 @@ export const usePackageFiles = (packageReleaseId?: string | null) => {
|
|
|
98
100
|
if (!packageReleaseId) return []
|
|
99
101
|
|
|
100
102
|
try {
|
|
101
|
-
const { data } = await axios.
|
|
102
|
-
package_release_id: packageReleaseId,
|
|
103
|
+
const { data } = await axios.get("/package_files/list", {
|
|
104
|
+
params: { package_release_id: packageReleaseId },
|
|
103
105
|
})
|
|
104
106
|
|
|
105
107
|
if (!data.package_files) {
|
|
@@ -1,36 +1,45 @@
|
|
|
1
|
-
import {
|
|
2
|
-
usePackageReleaseById,
|
|
3
|
-
usePackageReleaseByNameAndVersion,
|
|
4
|
-
} from "./use-package-release"
|
|
1
|
+
import { usePackageRelease } from "./use-package-release"
|
|
5
2
|
import { isUuid } from "@/lib/utils/isUuid"
|
|
6
3
|
|
|
7
4
|
export const usePackageReleaseByIdOrVersion = (
|
|
8
5
|
releaseIdOrVersion: string | null,
|
|
9
6
|
packageName?: string | null,
|
|
7
|
+
options?: {
|
|
8
|
+
include_logs?: boolean
|
|
9
|
+
include_ai_review?: boolean
|
|
10
|
+
refetchInterval?:
|
|
11
|
+
| number
|
|
12
|
+
| false
|
|
13
|
+
| ((
|
|
14
|
+
data:
|
|
15
|
+
| import("fake-snippets-api/lib/db/schema").PackageRelease
|
|
16
|
+
| undefined,
|
|
17
|
+
) => number | false)
|
|
18
|
+
},
|
|
10
19
|
) => {
|
|
11
20
|
const isReleaseIdUuid = releaseIdOrVersion
|
|
12
21
|
? isUuid(releaseIdOrVersion)
|
|
13
22
|
: false
|
|
14
23
|
|
|
15
|
-
|
|
16
|
-
const releaseByIdQuery = usePackageReleaseById(
|
|
17
|
-
isReleaseIdUuid ? releaseIdOrVersion : null,
|
|
18
|
-
)
|
|
24
|
+
let query: Parameters<typeof usePackageRelease>[0] = null
|
|
19
25
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
? `${packageName}@${releaseIdOrVersion}`
|
|
26
|
+
if (isReleaseIdUuid) {
|
|
27
|
+
query = releaseIdOrVersion
|
|
28
|
+
? { package_release_id: releaseIdOrVersion }
|
|
24
29
|
: null
|
|
30
|
+
} else if (packageName && releaseIdOrVersion) {
|
|
31
|
+
query = {
|
|
32
|
+
package_name_with_version: `${packageName}@${releaseIdOrVersion}`,
|
|
33
|
+
}
|
|
34
|
+
}
|
|
25
35
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
36
|
+
if (query && options?.include_logs !== undefined) {
|
|
37
|
+
query.include_logs = options.include_logs
|
|
38
|
+
}
|
|
29
39
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
return releaseByIdQuery
|
|
33
|
-
} else {
|
|
34
|
-
return releaseByVersionQuery
|
|
40
|
+
if (query && options?.include_ai_review !== undefined) {
|
|
41
|
+
query.include_ai_review = options.include_ai_review
|
|
35
42
|
}
|
|
43
|
+
|
|
44
|
+
return usePackageRelease(query, { refetchInterval: options?.refetchInterval })
|
|
36
45
|
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { useMemo, useCallback } from "react"
|
|
2
|
+
import { useQueries } from "react-query"
|
|
3
|
+
import { useAxios } from "./use-axios"
|
|
4
|
+
import { useParams } from "wouter"
|
|
5
|
+
import { useApiBaseUrl } from "./use-packages-base-api-url"
|
|
6
|
+
|
|
7
|
+
interface UsePackageReleaseImagesProps {
|
|
8
|
+
packageReleaseId?: string | null
|
|
9
|
+
availableFilePaths?: string[] | undefined
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface ViewConfig {
|
|
13
|
+
id: string
|
|
14
|
+
label: string
|
|
15
|
+
backgroundClass: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const VIEWS: ViewConfig[] = [
|
|
19
|
+
{
|
|
20
|
+
id: "3d",
|
|
21
|
+
label: "3D",
|
|
22
|
+
backgroundClass: "bg-gray-100",
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: "pcb",
|
|
26
|
+
label: "PCB",
|
|
27
|
+
backgroundClass: "bg-black",
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: "schematic",
|
|
31
|
+
label: "Schematic",
|
|
32
|
+
backgroundClass: "bg-[#F5F1ED]",
|
|
33
|
+
},
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
export function usePackageReleaseImages({
|
|
37
|
+
packageReleaseId,
|
|
38
|
+
}: UsePackageReleaseImagesProps) {
|
|
39
|
+
const apiurl = useApiBaseUrl()
|
|
40
|
+
const axios = useAxios()
|
|
41
|
+
const { author, packageName } = useParams()
|
|
42
|
+
|
|
43
|
+
const createQueryFn = useCallback(
|
|
44
|
+
(viewId: string) => async () => {
|
|
45
|
+
if (!packageReleaseId || !author || !packageName) return null
|
|
46
|
+
|
|
47
|
+
const imageUrl = `${apiurl}/packages/images/${author}/${packageName}/${viewId}.png?package_release_id=${packageReleaseId}`
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const response = await axios.head(imageUrl)
|
|
51
|
+
return response.status === 200 ? imageUrl : null
|
|
52
|
+
} catch {
|
|
53
|
+
return null
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
[packageReleaseId, author, packageName, apiurl, axios],
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
const queryConfigs = useMemo(
|
|
60
|
+
() =>
|
|
61
|
+
VIEWS.map((view) => ({
|
|
62
|
+
queryKey: [
|
|
63
|
+
"packageReleaseImage",
|
|
64
|
+
packageReleaseId,
|
|
65
|
+
view.id,
|
|
66
|
+
author,
|
|
67
|
+
packageName,
|
|
68
|
+
],
|
|
69
|
+
queryFn: createQueryFn(view.id),
|
|
70
|
+
enabled: Boolean(packageReleaseId && author && packageName),
|
|
71
|
+
retry: false,
|
|
72
|
+
refetchOnWindowFocus: false,
|
|
73
|
+
refetchOnMount: false,
|
|
74
|
+
refetchOnReconnect: false,
|
|
75
|
+
staleTime: Infinity,
|
|
76
|
+
cacheTime: Infinity,
|
|
77
|
+
})),
|
|
78
|
+
[packageReleaseId, author, packageName, createQueryFn],
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
const queries = useQueries(queryConfigs)
|
|
82
|
+
|
|
83
|
+
const availableViews = useMemo(() => {
|
|
84
|
+
const result = []
|
|
85
|
+
|
|
86
|
+
for (let i = 0; i < VIEWS.length; i++) {
|
|
87
|
+
const view = VIEWS[i]
|
|
88
|
+
const query = queries[i]
|
|
89
|
+
|
|
90
|
+
if (query.isLoading || query.data) {
|
|
91
|
+
result.push({
|
|
92
|
+
id: view.id,
|
|
93
|
+
label: view.label,
|
|
94
|
+
imageUrl: query.data || "",
|
|
95
|
+
isLoading: query.isLoading,
|
|
96
|
+
backgroundClass: view.backgroundClass,
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return result
|
|
102
|
+
}, [queries])
|
|
103
|
+
|
|
104
|
+
return { availableViews }
|
|
105
|
+
}
|
|
@@ -40,8 +40,8 @@ export const usePackageRelease = (
|
|
|
40
40
|
|
|
41
41
|
const { data } = await axios.post("/package_releases/get", query, {
|
|
42
42
|
params: {
|
|
43
|
-
include_logs: query.include_logs,
|
|
44
|
-
include_ai_review: query.include_ai_review,
|
|
43
|
+
include_logs: Boolean(query.include_logs),
|
|
44
|
+
include_ai_review: Boolean(query.include_ai_review),
|
|
45
45
|
},
|
|
46
46
|
})
|
|
47
47
|
|
|
@@ -38,25 +38,77 @@ export const usePackageStarMutation = (query: PackageStarQuery) => {
|
|
|
38
38
|
const axios = useAxios()
|
|
39
39
|
const queryClient = useQueryClient()
|
|
40
40
|
|
|
41
|
-
const addStar = useMutation
|
|
41
|
+
const addStar = useMutation<
|
|
42
|
+
any,
|
|
43
|
+
Error,
|
|
44
|
+
void,
|
|
45
|
+
{ previousStars?: PackageStarResponse }
|
|
46
|
+
>(
|
|
42
47
|
async () => {
|
|
43
48
|
const { data } = await axios.post("/packages/add_star", query)
|
|
44
49
|
return data
|
|
45
50
|
},
|
|
46
51
|
{
|
|
47
|
-
|
|
52
|
+
onMutate: async () => {
|
|
53
|
+
await queryClient.cancelQueries(["packageStars", query])
|
|
54
|
+
const previousStars = queryClient.getQueryData<PackageStarResponse>([
|
|
55
|
+
"packageStars",
|
|
56
|
+
query,
|
|
57
|
+
])
|
|
58
|
+
const optimistic: PackageStarResponse = {
|
|
59
|
+
is_starred: true,
|
|
60
|
+
star_count: (previousStars?.star_count ?? 0) + 1,
|
|
61
|
+
}
|
|
62
|
+
queryClient.setQueryData(["packageStars", query], optimistic)
|
|
63
|
+
return { previousStars }
|
|
64
|
+
},
|
|
65
|
+
onError: (_error, _vars, context) => {
|
|
66
|
+
if (context?.previousStars) {
|
|
67
|
+
queryClient.setQueryData(
|
|
68
|
+
["packageStars", query],
|
|
69
|
+
context.previousStars,
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
onSettled: () => {
|
|
48
74
|
queryClient.invalidateQueries(["packageStars", query])
|
|
49
75
|
},
|
|
50
76
|
},
|
|
51
77
|
)
|
|
52
78
|
|
|
53
|
-
const removeStar = useMutation
|
|
79
|
+
const removeStar = useMutation<
|
|
80
|
+
any,
|
|
81
|
+
Error,
|
|
82
|
+
void,
|
|
83
|
+
{ previousStars?: PackageStarResponse }
|
|
84
|
+
>(
|
|
54
85
|
async () => {
|
|
55
86
|
const { data } = await axios.post("/packages/remove_star", query)
|
|
56
87
|
return data
|
|
57
88
|
},
|
|
58
89
|
{
|
|
59
|
-
|
|
90
|
+
onMutate: async () => {
|
|
91
|
+
await queryClient.cancelQueries(["packageStars", query])
|
|
92
|
+
const previousStars = queryClient.getQueryData<PackageStarResponse>([
|
|
93
|
+
"packageStars",
|
|
94
|
+
query,
|
|
95
|
+
])
|
|
96
|
+
const optimistic: PackageStarResponse = {
|
|
97
|
+
is_starred: false,
|
|
98
|
+
star_count: Math.max(0, (previousStars?.star_count ?? 1) - 1),
|
|
99
|
+
}
|
|
100
|
+
queryClient.setQueryData(["packageStars", query], optimistic)
|
|
101
|
+
return { previousStars }
|
|
102
|
+
},
|
|
103
|
+
onError: (_error, _vars, context) => {
|
|
104
|
+
if (context?.previousStars) {
|
|
105
|
+
queryClient.setQueryData(
|
|
106
|
+
["packageStars", query],
|
|
107
|
+
context.previousStars,
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
onSettled: () => {
|
|
60
112
|
queryClient.invalidateQueries(["packageStars", query])
|
|
61
113
|
},
|
|
62
114
|
},
|
|
@@ -84,3 +136,27 @@ export const usePackageStarMutationById = (packageId: string) => {
|
|
|
84
136
|
export const usePackageStarMutationByName = (packageName: string) => {
|
|
85
137
|
return usePackageStarMutation({ name: packageName })
|
|
86
138
|
}
|
|
139
|
+
|
|
140
|
+
// High-level hook that exposes current star state and a single toggle action
|
|
141
|
+
export const usePackageStarring = (query: PackageStarQuery | null) => {
|
|
142
|
+
const starsQuery = usePackageStars(query)
|
|
143
|
+
const mutations = usePackageStarMutation(query ?? { name: "" })
|
|
144
|
+
|
|
145
|
+
const toggleStar = async () => {
|
|
146
|
+
if (!query) return
|
|
147
|
+
const currentlyStarred = starsQuery.data?.is_starred ?? false
|
|
148
|
+
if (currentlyStarred) await mutations.removeStar.mutateAsync()
|
|
149
|
+
else await mutations.addStar.mutateAsync()
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
isStarred: starsQuery.data?.is_starred ?? false,
|
|
154
|
+
starCount: starsQuery.data?.star_count ?? 0,
|
|
155
|
+
isPending: mutations.addStar.isLoading || mutations.removeStar.isLoading,
|
|
156
|
+
toggleStar,
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export const usePackageStarringByName = (packageName: string | null) => {
|
|
161
|
+
return usePackageStarring(packageName ? { name: packageName } : null)
|
|
162
|
+
}
|
|
@@ -21,22 +21,25 @@ export function usePreviewImages({
|
|
|
21
21
|
{
|
|
22
22
|
id: "3d",
|
|
23
23
|
label: "3D View",
|
|
24
|
+
backgroundClass: "bg-gray-100",
|
|
24
25
|
imageUrl: packageName
|
|
25
|
-
? `https://
|
|
26
|
+
? `https://api.tscircuit.com/packages/images/${packageName}/3d.png?fs_sha=${fsMapHash}`
|
|
26
27
|
: undefined,
|
|
27
28
|
},
|
|
28
29
|
{
|
|
29
30
|
id: "pcb",
|
|
30
31
|
label: "PCB View",
|
|
32
|
+
backgroundClass: "bg-black",
|
|
31
33
|
imageUrl: packageName
|
|
32
|
-
? `https://
|
|
34
|
+
? `https://api.tscircuit.com/packages/images/${packageName}/pcb.png?fs_sha=${fsMapHash}`
|
|
33
35
|
: undefined,
|
|
34
36
|
},
|
|
35
37
|
{
|
|
36
38
|
id: "schematic",
|
|
37
39
|
label: "Schematic View",
|
|
40
|
+
backgroundClass: "bg-[#F5F1ED]",
|
|
38
41
|
imageUrl: packageName
|
|
39
|
-
? `https://
|
|
42
|
+
? `https://api.tscircuit.com/packages/images/${packageName}/schematic.png?fs_sha=${fsMapHash}`
|
|
40
43
|
: undefined,
|
|
41
44
|
},
|
|
42
45
|
]
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { useMutation, useQueryClient } from "react-query"
|
|
2
|
+
import { useAxios } from "@/hooks/use-axios"
|
|
3
|
+
import { useGlobalStore } from "@/hooks/use-global-store"
|
|
4
|
+
|
|
5
|
+
export const useRemoveOrgMemberMutation = ({
|
|
6
|
+
onSuccess,
|
|
7
|
+
}: { onSuccess?: () => void } = {}) => {
|
|
8
|
+
const axios = useAxios()
|
|
9
|
+
const session = useGlobalStore((s) => s.session)
|
|
10
|
+
const queryClient = useQueryClient()
|
|
11
|
+
|
|
12
|
+
return useMutation(
|
|
13
|
+
["removeOrgMember"],
|
|
14
|
+
async ({ orgId, accountId }: { orgId: string; accountId: string }) => {
|
|
15
|
+
if (!session) throw new Error("No session")
|
|
16
|
+
|
|
17
|
+
await axios.post("/orgs/remove_member", {
|
|
18
|
+
org_id: orgId,
|
|
19
|
+
account_id: accountId,
|
|
20
|
+
})
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
onSuccess: () => {
|
|
24
|
+
queryClient.invalidateQueries(["orgs", "members"])
|
|
25
|
+
onSuccess?.()
|
|
26
|
+
},
|
|
27
|
+
onError: (error: any) => {
|
|
28
|
+
console.error("Error removing organization member:", error)
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
)
|
|
32
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { useMutation, useQueryClient } from "react-query"
|
|
2
|
+
import { useAxios } from "./use-axios"
|
|
3
|
+
import { useToast } from "./use-toast"
|
|
4
|
+
|
|
5
|
+
export const useUpdateAiDescriptionMutation = ({
|
|
6
|
+
onSuccess,
|
|
7
|
+
}: {
|
|
8
|
+
onSuccess?: () => void
|
|
9
|
+
} = {}) => {
|
|
10
|
+
const axios = useAxios()
|
|
11
|
+
const { toast } = useToast()
|
|
12
|
+
const queryClient = useQueryClient()
|
|
13
|
+
|
|
14
|
+
return useMutation(
|
|
15
|
+
async ({ package_id }: { package_id: string }) => {
|
|
16
|
+
const { data } = await axios.post("/packages/update_ai_description", {
|
|
17
|
+
package_id,
|
|
18
|
+
})
|
|
19
|
+
return data
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
onSuccess: () => {
|
|
23
|
+
toast({
|
|
24
|
+
title: "AI description update requested",
|
|
25
|
+
description: "The AI description will be regenerated shortly.",
|
|
26
|
+
})
|
|
27
|
+
queryClient.invalidateQueries(["package"])
|
|
28
|
+
onSuccess?.()
|
|
29
|
+
},
|
|
30
|
+
onError: (error: any) => {
|
|
31
|
+
toast({
|
|
32
|
+
title: "Error",
|
|
33
|
+
description:
|
|
34
|
+
error?.response?.data?.message ||
|
|
35
|
+
error?.data?.message ||
|
|
36
|
+
"Failed to request AI description update.",
|
|
37
|
+
variant: "destructive",
|
|
38
|
+
})
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
)
|
|
42
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { useMutation, useQueryClient } from "react-query"
|
|
2
|
+
import { useAxios } from "@/hooks/use-axios"
|
|
3
|
+
import { useGlobalStore } from "@/hooks/use-global-store"
|
|
4
|
+
import type { PublicOrgSchema } from "fake-snippets-api/lib/db/schema"
|
|
5
|
+
|
|
6
|
+
export const useUpdateOrgMutation = ({
|
|
7
|
+
onSuccess,
|
|
8
|
+
}: { onSuccess?: (org: PublicOrgSchema) => void } = {}) => {
|
|
9
|
+
const axios = useAxios()
|
|
10
|
+
const session = useGlobalStore((s) => s.session)
|
|
11
|
+
const queryClient = useQueryClient()
|
|
12
|
+
|
|
13
|
+
return useMutation(
|
|
14
|
+
["updateOrg"],
|
|
15
|
+
async ({ orgId, name }: { orgId: string; name?: string }) => {
|
|
16
|
+
if (!session) throw new Error("No session")
|
|
17
|
+
|
|
18
|
+
const {
|
|
19
|
+
data: { org: updatedOrg },
|
|
20
|
+
} = await axios.post("/orgs/update", {
|
|
21
|
+
org_id: orgId,
|
|
22
|
+
name,
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
if (!updatedOrg) {
|
|
26
|
+
throw new Error("Failed to update organization")
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return updatedOrg
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
onSuccess: (org: PublicOrgSchema) => {
|
|
33
|
+
queryClient.invalidateQueries(["orgs"])
|
|
34
|
+
onSuccess?.(org)
|
|
35
|
+
},
|
|
36
|
+
onError: (error: any) => {
|
|
37
|
+
console.error("Error updating organization:", error)
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
)
|
|
41
|
+
}
|
|
@@ -1,23 +1,90 @@
|
|
|
1
|
-
import { useEffect } from "react"
|
|
1
|
+
import { useEffect, useRef } from "react"
|
|
2
|
+
|
|
2
3
|
export default function useWarnUserOnPageChange({
|
|
3
4
|
hasUnsavedChanges,
|
|
5
|
+
isPackageThere,
|
|
4
6
|
}: {
|
|
5
7
|
hasUnsavedChanges: boolean
|
|
8
|
+
isPackageThere: Boolean
|
|
6
9
|
}) {
|
|
10
|
+
const originalTitleRef = useRef<string>("")
|
|
11
|
+
|
|
7
12
|
useEffect(() => {
|
|
13
|
+
if (!hasUnsavedChanges || !originalTitleRef.current) {
|
|
14
|
+
originalTitleRef.current = document.title.replace(/^⚠️\s*/, "")
|
|
15
|
+
}
|
|
16
|
+
|
|
8
17
|
const handleBeforeUnload = (event: BeforeUnloadEvent) => {
|
|
9
18
|
if (hasUnsavedChanges) {
|
|
19
|
+
const message =
|
|
20
|
+
"You have unsaved changes. Are you sure you want to leave?"
|
|
10
21
|
event.preventDefault()
|
|
11
|
-
event.returnValue =
|
|
22
|
+
event.returnValue = message
|
|
23
|
+
return message
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const handleVisibilityChange = () => {
|
|
28
|
+
if (document.hidden && hasUnsavedChanges) {
|
|
29
|
+
if (!document.title.startsWith("⚠️")) {
|
|
30
|
+
document.title = "⚠️ " + originalTitleRef.current
|
|
31
|
+
}
|
|
32
|
+
} else if (!document.hidden) {
|
|
33
|
+
document.title = originalTitleRef.current
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const handlePopState = (event: PopStateEvent) => {
|
|
38
|
+
if (hasUnsavedChanges && isPackageThere) {
|
|
39
|
+
const shouldLeave = window.confirm(
|
|
40
|
+
"You have unsaved changes. Are you sure you want to leave this page?",
|
|
41
|
+
)
|
|
42
|
+
if (!shouldLeave) {
|
|
43
|
+
window.history.pushState(null, "", window.location.href)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const handleLinkClick = (event: MouseEvent) => {
|
|
49
|
+
if (!hasUnsavedChanges) return
|
|
50
|
+
|
|
51
|
+
const target = event.target as HTMLElement
|
|
52
|
+
const link = target.closest("a[href]") as HTMLAnchorElement
|
|
53
|
+
|
|
54
|
+
if (link && link.href) {
|
|
55
|
+
try {
|
|
56
|
+
const linkUrl = new URL(link.href)
|
|
57
|
+
const currentUrl = new URL(window.location.href)
|
|
58
|
+
|
|
59
|
+
if (linkUrl.origin === currentUrl.origin) {
|
|
60
|
+
event.preventDefault()
|
|
61
|
+
event.stopPropagation()
|
|
62
|
+
|
|
63
|
+
const shouldLeave = window.confirm(
|
|
64
|
+
"You have unsaved changes. Are you sure you want to leave this page?",
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
if (shouldLeave) {
|
|
68
|
+
window.location.href = link.href
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.warn("Failed to parse link URL:", error)
|
|
73
|
+
}
|
|
12
74
|
}
|
|
13
75
|
}
|
|
14
76
|
|
|
15
|
-
// Attach event listeners
|
|
16
77
|
window.addEventListener("beforeunload", handleBeforeUnload)
|
|
78
|
+
document.addEventListener("visibilitychange", handleVisibilityChange)
|
|
79
|
+
window.addEventListener("popstate", handlePopState)
|
|
80
|
+
document.addEventListener("click", handleLinkClick, true)
|
|
17
81
|
|
|
18
|
-
// Cleanup event listeners on component unmount
|
|
19
82
|
return () => {
|
|
20
83
|
window.removeEventListener("beforeunload", handleBeforeUnload)
|
|
84
|
+
document.removeEventListener("visibilitychange", handleVisibilityChange)
|
|
85
|
+
window.removeEventListener("popstate", handlePopState)
|
|
86
|
+
document.removeEventListener("click", handleLinkClick, true)
|
|
87
|
+
document.title = originalTitleRef.current
|
|
21
88
|
}
|
|
22
89
|
}, [hasUnsavedChanges])
|
|
23
90
|
}
|