@tscircuit/fake-snippets 0.0.109 → 0.0.111
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 +32 -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 +151 -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 +361 -453
- package/bunfig.toml +2 -1
- package/dist/bundle.js +1313 -639
- package/dist/index.d.ts +313 -6
- package/dist/index.js +328 -24
- package/dist/schema.d.ts +290 -1
- package/dist/schema.js +54 -1
- package/fake-snippets-api/lib/db/autoload-dev-packages.ts +31 -20
- package/fake-snippets-api/lib/db/db-client.ts +219 -4
- package/fake-snippets-api/lib/db/schema.ts +63 -1
- package/fake-snippets-api/lib/db/seed.ts +100 -0
- package/fake-snippets-api/lib/middleware/with-session-auth.ts +60 -8
- 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 +33 -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 +48 -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 +60 -0
- package/fake-snippets-api/routes/api/orgs/remove_member.ts +46 -0
- package/fake-snippets-api/routes/api/orgs/update.ts +118 -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 +57 -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 +25 -19
- package/renovate.json +1 -1
- package/scripts/generate-sitemap.ts +1 -1
- package/src/App.tsx +27 -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 +17 -5
- 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/SentryNotFoundReporter.tsx +44 -0
- 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 +206 -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-hydration.ts +30 -0
- 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 +22 -11
- package/src/lib/download-fns/download-step.ts +12 -0
- 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 +169 -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 +569 -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 +197 -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/user-settings.tsx +161 -0
- package/src/pages/view-package.tsx +30 -16
- 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
|
@@ -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
|
}
|
|
@@ -73,11 +73,7 @@ export function useFileManagement({
|
|
|
73
73
|
totalFilesCount,
|
|
74
74
|
loadedFilesCount,
|
|
75
75
|
isPriorityFileFetched,
|
|
76
|
-
} = useOptimizedPackageFilesLoader(
|
|
77
|
-
currentPackage,
|
|
78
|
-
fileChosen,
|
|
79
|
-
urlParams.package_id,
|
|
80
|
-
)
|
|
76
|
+
} = useOptimizedPackageFilesLoader(currentPackage, fileChosen)
|
|
81
77
|
|
|
82
78
|
const { data: packageFilesMeta, isLoading: isLoadingPackageFiles } =
|
|
83
79
|
usePackageFiles(currentPackage?.latest_package_release_id)
|
|
@@ -148,7 +144,10 @@ export function useFileManagement({
|
|
|
148
144
|
content: String(content),
|
|
149
145
|
}),
|
|
150
146
|
)
|
|
151
|
-
const targetFile = findTargetFile(
|
|
147
|
+
const targetFile = findTargetFile({
|
|
148
|
+
files: filesFromUrl,
|
|
149
|
+
filePathFromUrl: fileChosen,
|
|
150
|
+
})
|
|
152
151
|
setLocalFiles(filesFromUrl)
|
|
153
152
|
setInitialFiles([])
|
|
154
153
|
setCurrentFile(targetFile?.path || filesFromUrl[0]?.path || null)
|
|
@@ -208,7 +207,10 @@ export function useFileManagement({
|
|
|
208
207
|
if (fileChosen) {
|
|
209
208
|
const targetFile =
|
|
210
209
|
packageFilesWithContent.find((f) => f.path === fileChosen) ||
|
|
211
|
-
findTargetFile(
|
|
210
|
+
findTargetFile({
|
|
211
|
+
files: packageFilesWithContent,
|
|
212
|
+
filePathFromUrl: fileChosen,
|
|
213
|
+
})
|
|
212
214
|
if (targetFile) {
|
|
213
215
|
setCurrentFile((prevCurrentFile) => {
|
|
214
216
|
return targetFile.path !== prevCurrentFile
|
|
@@ -221,10 +223,10 @@ export function useFileManagement({
|
|
|
221
223
|
if (!prevCurrentFile) {
|
|
222
224
|
// Wait for priority file to load before making selection to avoid flicker
|
|
223
225
|
// Only select if we have a good candidate (tsx/ts file) or priority file isn't loading
|
|
224
|
-
const targetFile = findTargetFile(
|
|
225
|
-
packageFilesWithContent,
|
|
226
|
-
fileChosen,
|
|
227
|
-
)
|
|
226
|
+
const targetFile = findTargetFile({
|
|
227
|
+
files: packageFilesWithContent,
|
|
228
|
+
filePathFromUrl: fileChosen,
|
|
229
|
+
})
|
|
228
230
|
// Only consider it a "good enough" candidate if it's index.tsx
|
|
229
231
|
// Otherwise, wait for the actual priority file (index.tsx) to load
|
|
230
232
|
const isTopPriorityFile =
|
|
@@ -250,8 +252,22 @@ export function useFileManagement({
|
|
|
250
252
|
])
|
|
251
253
|
|
|
252
254
|
const isLoading = useMemo(() => {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
+
const waitingForPriorityFile =
|
|
256
|
+
Boolean(urlParams.package_id) && !isPriorityFileFetched
|
|
257
|
+
|
|
258
|
+
const hasPackageWithFilesButNoneLoaded =
|
|
259
|
+
Boolean(urlParams.package_id) &&
|
|
260
|
+
isPriorityFileFetched &&
|
|
261
|
+
totalFilesCount > 0 &&
|
|
262
|
+
localFiles.length === 0
|
|
263
|
+
|
|
264
|
+
return waitingForPriorityFile || hasPackageWithFilesButNoneLoaded
|
|
265
|
+
}, [
|
|
266
|
+
isPriorityFileFetched,
|
|
267
|
+
urlParams.package_id,
|
|
268
|
+
totalFilesCount,
|
|
269
|
+
localFiles.length,
|
|
270
|
+
])
|
|
255
271
|
|
|
256
272
|
const isFullyLoaded = useMemo(() => {
|
|
257
273
|
return !isLoadingPackageFilesWithContent && !isLoadingPackageFiles
|
|
@@ -409,7 +425,15 @@ export function useFileManagement({
|
|
|
409
425
|
}
|
|
410
426
|
}
|
|
411
427
|
|
|
412
|
-
const savePackage = async (
|
|
428
|
+
const savePackage = async ({
|
|
429
|
+
name,
|
|
430
|
+
isPrivate,
|
|
431
|
+
orgId,
|
|
432
|
+
}: {
|
|
433
|
+
name?: string
|
|
434
|
+
isPrivate: boolean
|
|
435
|
+
orgId: string
|
|
436
|
+
}) => {
|
|
413
437
|
if (!isLoggedIn) {
|
|
414
438
|
toast({
|
|
415
439
|
title: "Not Logged In",
|
|
@@ -420,6 +444,8 @@ export function useFileManagement({
|
|
|
420
444
|
|
|
421
445
|
await createPackageMutation.mutateAsync({
|
|
422
446
|
is_private: isPrivate,
|
|
447
|
+
org_id: orgId,
|
|
448
|
+
...(name ? { name } : {}),
|
|
423
449
|
})
|
|
424
450
|
}
|
|
425
451
|
|
|
@@ -523,6 +549,15 @@ export function useFileManagement({
|
|
|
523
549
|
[localFiles, currentFile],
|
|
524
550
|
)
|
|
525
551
|
const mainComponentPath = useMemo(() => {
|
|
552
|
+
const targetFile = findTargetFile({
|
|
553
|
+
files: localFiles,
|
|
554
|
+
filePathFromUrl: fileChosen,
|
|
555
|
+
fallbackToAnyFile: false,
|
|
556
|
+
})?.path
|
|
557
|
+
if (targetFile && !fileChosen && !currentFile) {
|
|
558
|
+
return targetFile
|
|
559
|
+
}
|
|
560
|
+
|
|
526
561
|
const isComponentExportedInCurrentFile =
|
|
527
562
|
isComponentExported(currentFileCode)
|
|
528
563
|
|
|
@@ -531,16 +566,10 @@ export function useFileManagement({
|
|
|
531
566
|
!!localFiles.some((x) => x.path === currentFile) &&
|
|
532
567
|
isComponentExportedInCurrentFile
|
|
533
568
|
? currentFile
|
|
534
|
-
:
|
|
535
|
-
const isComponentExportedInFile = isComponentExported(x.content)
|
|
536
|
-
return (
|
|
537
|
-
x.path.endsWith(".tsx") ||
|
|
538
|
-
(x.path.endsWith(".ts") && isComponentExportedInFile)
|
|
539
|
-
)
|
|
540
|
-
})?.path || localFiles[0]?.path
|
|
569
|
+
: targetFile
|
|
541
570
|
|
|
542
571
|
return selectedComponent
|
|
543
|
-
}, [currentFile, localFiles, currentFileCode
|
|
572
|
+
}, [currentFile, localFiles, currentFileCode])
|
|
544
573
|
|
|
545
574
|
const priorityFileFetched = useMemo(() => {
|
|
546
575
|
return urlParams.package_id && isPriorityFileFetched
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { useQuery, useQueries } from "react-query"
|
|
2
2
|
import { useAxios } from "@/hooks/use-axios"
|
|
3
3
|
import { usePackageFiles } from "@/hooks/use-package-files"
|
|
4
|
-
import { usePackageFileById } from "@/hooks/use-package-files"
|
|
5
4
|
import type { Package } from "fake-snippets-api/lib/db/schema"
|
|
6
5
|
import { useState, useMemo } from "react"
|
|
7
|
-
import { findTargetFile } from "@/lib/utils/findTargetFile"
|
|
8
6
|
|
|
9
7
|
export interface PackageFile {
|
|
10
8
|
path: string
|
|
@@ -22,25 +20,13 @@ export interface OptimizedLoadingState {
|
|
|
22
20
|
export function useOptimizedPackageFilesLoader(
|
|
23
21
|
pkg?: Package,
|
|
24
22
|
priorityFilePath?: string | null,
|
|
25
|
-
packageId?: string | null,
|
|
26
23
|
) {
|
|
27
24
|
const axios = useAxios()
|
|
28
25
|
const [loadedFiles, setLoadedFiles] = useState<Map<string, PackageFile>>(
|
|
29
26
|
new Map(),
|
|
30
27
|
)
|
|
31
28
|
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
// Filter out dist/ files to avoid downloading build artifacts
|
|
35
|
-
const pkgFiles = useMemo(
|
|
36
|
-
() => ({
|
|
37
|
-
...pkgFilesRaw,
|
|
38
|
-
data: pkgFilesRaw.data?.filter(
|
|
39
|
-
(file) => !file.file_path.startsWith("dist/"),
|
|
40
|
-
),
|
|
41
|
-
}),
|
|
42
|
-
[pkgFilesRaw.data, pkgFilesRaw.isLoading, pkgFilesRaw.error],
|
|
43
|
-
)
|
|
29
|
+
const pkgFiles = usePackageFiles(pkg?.latest_package_release_id)
|
|
44
30
|
|
|
45
31
|
const targetFilePath = useMemo(() => {
|
|
46
32
|
if (!pkgFiles.data) return priorityFilePath
|
|
@@ -76,8 +62,8 @@ export function useOptimizedPackageFilesLoader(
|
|
|
76
62
|
queryFn: async () => {
|
|
77
63
|
if (!priorityFileData) return null
|
|
78
64
|
|
|
79
|
-
const response = await axios.
|
|
80
|
-
package_file_id: priorityFileData.package_file_id,
|
|
65
|
+
const response = await axios.get(`/package_files/get`, {
|
|
66
|
+
params: { package_file_id: priorityFileData.package_file_id },
|
|
81
67
|
})
|
|
82
68
|
const content = response.data.package_file?.content_text
|
|
83
69
|
const file = { path: priorityFileData.file_path, content: content ?? "" }
|
|
@@ -92,8 +78,8 @@ export function useOptimizedPackageFilesLoader(
|
|
|
92
78
|
},
|
|
93
79
|
enabled: !!priorityFileData,
|
|
94
80
|
refetchOnWindowFocus: false,
|
|
95
|
-
refetchOnMount:
|
|
96
|
-
staleTime:
|
|
81
|
+
refetchOnMount: true,
|
|
82
|
+
staleTime: 0,
|
|
97
83
|
cacheTime: Infinity,
|
|
98
84
|
})
|
|
99
85
|
|
|
@@ -103,8 +89,8 @@ export function useOptimizedPackageFilesLoader(
|
|
|
103
89
|
?.map((file) => ({
|
|
104
90
|
queryKey: ["packageFile", file.package_file_id],
|
|
105
91
|
queryFn: async () => {
|
|
106
|
-
const response = await axios.
|
|
107
|
-
package_file_id: file.package_file_id,
|
|
92
|
+
const response = await axios.get(`/package_files/get`, {
|
|
93
|
+
params: { package_file_id: file.package_file_id },
|
|
108
94
|
})
|
|
109
95
|
const content = response.data.package_file?.content_text
|
|
110
96
|
const fileData = { path: file.file_path, content: content ?? "" }
|
|
@@ -118,14 +104,15 @@ export function useOptimizedPackageFilesLoader(
|
|
|
118
104
|
return fileData
|
|
119
105
|
},
|
|
120
106
|
refetchOnWindowFocus: false,
|
|
121
|
-
refetchOnMount:
|
|
122
|
-
staleTime:
|
|
107
|
+
refetchOnMount: true,
|
|
108
|
+
staleTime: 0,
|
|
123
109
|
cacheTime: Infinity,
|
|
124
110
|
})) ?? [],
|
|
125
111
|
)
|
|
126
112
|
|
|
127
113
|
const allFiles = useMemo(() => {
|
|
128
|
-
|
|
114
|
+
const files = Array.from(loadedFiles.values())
|
|
115
|
+
return files
|
|
129
116
|
}, [loadedFiles])
|
|
130
117
|
|
|
131
118
|
const areAllFilesLoading =
|
|
@@ -30,8 +30,8 @@ export function usePackageFilesLoader(pkg?: Package) {
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
const response = await axios.
|
|
34
|
-
package_file_id: file.package_file_id,
|
|
33
|
+
const response = await axios.get(`/package_files/get`, {
|
|
34
|
+
params: { package_file_id: file.package_file_id },
|
|
35
35
|
})
|
|
36
36
|
const content = response.data.package_file?.content_text
|
|
37
37
|
return { path: file.file_path, content: content ?? "" }
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useMutation } from "react-query"
|
|
1
|
+
import { useMutation, useQueryClient } from "react-query"
|
|
2
2
|
import type { Package } from "fake-snippets-api/lib/db/schema"
|
|
3
3
|
import { useAxios } from "./use-axios"
|
|
4
4
|
import { useToast } from "@/components/ViewPackagePage/hooks/use-toast"
|
|
@@ -29,6 +29,7 @@ export function useUpdatePackageFilesMutation({
|
|
|
29
29
|
}: UseUpdatePackageFilesMutationProps) {
|
|
30
30
|
const axios = useAxios()
|
|
31
31
|
const { toast } = useToast()
|
|
32
|
+
const queryClient = useQueryClient()
|
|
32
33
|
return useMutation({
|
|
33
34
|
mutationFn: async (
|
|
34
35
|
newPackage: Pick<Package, "package_id" | "name"> & {
|
|
@@ -95,6 +96,17 @@ export function useUpdatePackageFilesMutation({
|
|
|
95
96
|
title: `Package's ${updatedFilesCount} files saved`,
|
|
96
97
|
description: "Your changes have been saved successfully.",
|
|
97
98
|
})
|
|
99
|
+
queryClient.invalidateQueries({
|
|
100
|
+
predicate: (q) => {
|
|
101
|
+
const key = q.queryKey as any
|
|
102
|
+
return (
|
|
103
|
+
Array.isArray(key) &&
|
|
104
|
+
(key[0] === "packageFiles" ||
|
|
105
|
+
key[0] === "packageFile" ||
|
|
106
|
+
key[0] === "priorityPackageFile")
|
|
107
|
+
)
|
|
108
|
+
},
|
|
109
|
+
})
|
|
98
110
|
}
|
|
99
111
|
},
|
|
100
112
|
onError: (error: any) => {
|
|
@@ -27,7 +27,7 @@ export const downloadGltfFromCircuitJson = async (
|
|
|
27
27
|
typeof (result as any).buffer === "object" &&
|
|
28
28
|
(result as any).byteLength !== undefined
|
|
29
29
|
) {
|
|
30
|
-
const view = result as
|
|
30
|
+
const view = result as ArrayBuffer
|
|
31
31
|
blob = new Blob([view], { type: "model/gltf-binary" })
|
|
32
32
|
extension = options?.format
|
|
33
33
|
? options.format === "glb"
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { saveAs } from "file-saver"
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import {
|
|
3
|
+
CircuitJsonToKicadPcbConverter,
|
|
4
|
+
CircuitJsonToKicadSchConverter,
|
|
5
|
+
CircuitJsonToKicadProConverter,
|
|
6
|
+
} from "circuit-json-to-kicad"
|
|
4
7
|
import { AnyCircuitElement } from "circuit-json"
|
|
5
8
|
import JSZip from "jszip"
|
|
6
9
|
|
|
@@ -8,18 +11,26 @@ export const downloadKicadFiles = (
|
|
|
8
11
|
circuitJson: AnyCircuitElement[],
|
|
9
12
|
fileName: string,
|
|
10
13
|
) => {
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
? JSON.stringify(kicadPcbString)
|
|
15
|
-
: kicadPcbString
|
|
14
|
+
const pcbConverter = new CircuitJsonToKicadPcbConverter(circuitJson)
|
|
15
|
+
pcbConverter.runUntilFinished()
|
|
16
|
+
const kicadPcbContent = pcbConverter.getOutputString()
|
|
16
17
|
|
|
17
|
-
const
|
|
18
|
-
|
|
18
|
+
const schConverter = new CircuitJsonToKicadSchConverter(circuitJson)
|
|
19
|
+
schConverter.runUntilFinished()
|
|
20
|
+
const kicadSchContent = schConverter.getOutputString()
|
|
21
|
+
|
|
22
|
+
const proConverter = new CircuitJsonToKicadProConverter(circuitJson, {
|
|
23
|
+
projectName: fileName,
|
|
24
|
+
schematicFilename: `${fileName}.kicad_sch`,
|
|
25
|
+
pcbFilename: `${fileName}.kicad_pcb`,
|
|
26
|
+
})
|
|
27
|
+
proConverter.runUntilFinished()
|
|
28
|
+
const kicadProContent = proConverter.getOutputString()
|
|
19
29
|
|
|
20
30
|
const zip = new JSZip()
|
|
21
|
-
zip.file(`${fileName}.kicad_pcb`,
|
|
22
|
-
zip.file(`${fileName}.
|
|
31
|
+
zip.file(`${fileName}.kicad_pcb`, kicadPcbContent)
|
|
32
|
+
zip.file(`${fileName}.kicad_sch`, kicadSchContent)
|
|
33
|
+
zip.file(`${fileName}.kicad_pro`, kicadProContent)
|
|
23
34
|
|
|
24
35
|
zip.generateAsync({ type: "blob" }).then((content) => {
|
|
25
36
|
saveAs(content, `${fileName}_kicad.zip`)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { AnyCircuitElement } from "circuit-json"
|
|
2
|
+
import { saveAs } from "file-saver"
|
|
3
|
+
import { circuitJsonToStep } from "circuit-json-to-step"
|
|
4
|
+
|
|
5
|
+
export const downloadStepFile = async (
|
|
6
|
+
circuitJson: AnyCircuitElement[],
|
|
7
|
+
fileName: string,
|
|
8
|
+
) => {
|
|
9
|
+
const content = await circuitJsonToStep(circuitJson)
|
|
10
|
+
const blob = new Blob([content], { type: "text/plain" })
|
|
11
|
+
saveAs(blob, fileName + ".step")
|
|
12
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export function normalizeSvgForSquareTile(svg: string): string {
|
|
2
|
+
try {
|
|
3
|
+
const openTagMatch = svg.match(/<svg[^>]*>/i)
|
|
4
|
+
if (!openTagMatch) return svg
|
|
5
|
+
|
|
6
|
+
const openTag = openTagMatch[0]
|
|
7
|
+
|
|
8
|
+
const hasViewBox = /viewBox="[^"]+"/i.test(openTag)
|
|
9
|
+
const widthMatch = openTag.match(/\swidth="([0-9.]+)"/i)
|
|
10
|
+
const heightMatch = openTag.match(/\sheight="([0-9.]+)"/i)
|
|
11
|
+
|
|
12
|
+
let newOpenTag = openTag
|
|
13
|
+
|
|
14
|
+
// Remove explicit width/height so CSS can control sizing
|
|
15
|
+
newOpenTag = newOpenTag.replace(/\swidth="[^"]*"/i, "")
|
|
16
|
+
newOpenTag = newOpenTag.replace(/\sheight="[^"]*"/i, "")
|
|
17
|
+
|
|
18
|
+
// Ensure viewBox is present for proper scaling
|
|
19
|
+
if (!hasViewBox && widthMatch && heightMatch) {
|
|
20
|
+
const w = widthMatch[1]
|
|
21
|
+
const h = heightMatch[1]
|
|
22
|
+
newOpenTag = newOpenTag.replace(/<svg/i, `<svg viewBox="0 0 ${w} ${h}"`)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Force preserveAspectRatio to fit within square without distortion
|
|
26
|
+
if (/preserveAspectRatio="[^"]+"/i.test(newOpenTag)) {
|
|
27
|
+
newOpenTag = newOpenTag.replace(
|
|
28
|
+
/preserveAspectRatio="[^"]+"/i,
|
|
29
|
+
'preserveAspectRatio="xMidYMid meet"',
|
|
30
|
+
)
|
|
31
|
+
} else {
|
|
32
|
+
newOpenTag = newOpenTag.replace(
|
|
33
|
+
/<svg(\s|>)/i,
|
|
34
|
+
(_m, p1) => `<svg preserveAspectRatio="xMidYMid meet"${p1}`,
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return svg.replace(openTag, newOpenTag)
|
|
39
|
+
} catch {
|
|
40
|
+
return svg
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function svgToDataUrl(svg: string): string {
|
|
45
|
+
try {
|
|
46
|
+
return `data:image/svg+xml,${encodeURIComponent(svg)}`
|
|
47
|
+
} catch {
|
|
48
|
+
return `data:image/svg+xml,${svg}`
|
|
49
|
+
}
|
|
50
|
+
}
|
package/src/lib/posthog.ts
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import posthog from "posthog-js"
|
|
2
2
|
|
|
3
|
-
if (
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
)
|
|
7
|
-
|
|
8
|
-
posthog.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
3
|
+
if (typeof window !== "undefined") {
|
|
4
|
+
if (
|
|
5
|
+
!window.location.hostname.includes("localhost") &&
|
|
6
|
+
!window.location.hostname.includes("127.0.0.1")
|
|
7
|
+
) {
|
|
8
|
+
if (!posthog.__loaded) {
|
|
9
|
+
posthog.init("phc_htd8AQjSfVEsFCLQMAiUooG4Q0DKBCjqYuQglc9V3Wo", {
|
|
10
|
+
api_host: "https://postpig.tscircuit.com",
|
|
11
|
+
person_profiles: "always",
|
|
12
|
+
})
|
|
13
|
+
}
|
|
12
14
|
}
|
|
13
15
|
}
|
|
14
16
|
|