@tscircuit/fake-snippets 0.0.65 → 0.0.67
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/CONTRIBUTING.md +2 -2
- package/README.md +2 -2
- package/bun-tests/fake-snippets-api/fixtures/get-circuit-json.ts +5 -143
- package/bun-tests/fake-snippets-api/fixtures/get-test-server.ts +1 -4
- package/bun-tests/fake-snippets-api/fixtures/start-server.ts +7 -3
- package/bun-tests/fake-snippets-api/routes/order_quotes/create.test.ts +20 -56
- package/bun-tests/fake-snippets-api/routes/package_files/create_or_update.test.ts +2 -2
- package/bun-tests/fake-snippets-api/routes/package_releases/update.test.ts +1 -1
- package/bun-tests/fake-snippets-api/routes/packages/images.test.ts +1 -16
- package/bun.lock +42 -31
- package/dist/bundle.js +34 -41
- package/fake-snippets-api/routes/api/order_quotes/create.ts +30 -37
- package/fake-snippets-api/routes/api/order_quotes/get.ts +5 -8
- package/fake-snippets-api/routes/api/packages/images/[owner_github_username]/[unscoped_name]/[view_format].ts +3 -3
- package/package.json +7 -5
- package/src/App.tsx +0 -7
- package/src/ContextProviders.tsx +2 -0
- package/src/components/DownloadButtonAndMenu.tsx +1 -4
- package/src/components/ErrorTabContent.tsx +1 -1
- package/src/components/Footer.tsx +5 -2
- package/src/components/HeaderLogin.tsx +37 -54
- package/src/components/ImageWithFallback.tsx +37 -0
- package/src/components/JLCPCBImportDialog.tsx +43 -24
- package/src/components/PackageCard.tsx +12 -3
- package/src/components/{SnippetLink.tsx → PackageLink.tsx} +8 -16
- package/src/components/PackageSearchResults.tsx +87 -0
- package/src/components/PackagesList.tsx +3 -3
- package/src/components/PageSearchComponent.tsx +9 -9
- package/src/components/ShippingInformationForm.tsx +1 -1
- package/src/components/TrendingPackagesCarousel.tsx +43 -23
- package/src/components/ViewPackagePage/components/ShikiCodeViewer.tsx +5 -28
- package/src/components/ViewPackagePage/components/main-content-header.tsx +10 -22
- package/src/components/ViewPackagePage/components/mobile-sidebar.tsx +25 -14
- package/src/components/ViewPackagePage/components/package-header.tsx +9 -4
- package/src/components/ViewPackagePage/components/preview-image-squares.tsx +6 -1
- package/src/components/ViewPackagePage/components/repo-page-content.tsx +4 -4
- package/src/components/ViewPackagePage/components/sidebar.tsx +2 -14
- package/src/components/ViewSnippetSidebar.tsx +1 -1
- package/src/components/package-port/CodeEditor.tsx +13 -10
- package/src/components/package-port/CodeEditorHeader.tsx +1 -1
- package/src/components/package-port/EditorNav.tsx +2 -2
- package/src/hooks/use-get-fsmap-hash-for-package.ts +19 -0
- package/src/hooks/use-global-store.ts +1 -0
- package/src/hooks/use-preview-images.ts +20 -4
- package/src/hooks/use-shiki-highlighter.ts +13 -6
- package/src/hooks/use-toast.tsx +1 -1
- package/src/lib/download-fns/download-gltf.ts +3 -10
- package/src/lib/handleManualEditsImport.tsx +1 -1
- package/src/lib/types.ts +4 -2
- package/src/pages/dashboard.tsx +3 -4
- package/src/pages/editor.tsx +20 -14
- package/src/pages/latest.tsx +25 -26
- package/src/pages/package-editor.tsx +14 -2
- package/src/pages/search.tsx +120 -19
- package/src/pages/trending.tsx +14 -59
- package/src/pages/user-profile.tsx +13 -8
- package/bun-tests/fake-snippets-api/routes/snippets/add_star.test.ts +0 -84
- package/bun-tests/fake-snippets-api/routes/snippets/create.test.ts +0 -53
- package/bun-tests/fake-snippets-api/routes/snippets/delete.test.ts +0 -82
- package/bun-tests/fake-snippets-api/routes/snippets/download.test.ts +0 -90
- package/bun-tests/fake-snippets-api/routes/snippets/generate_from_jlcpcb.test.ts +0 -16
- package/bun-tests/fake-snippets-api/routes/snippets/get.test.ts +0 -163
- package/bun-tests/fake-snippets-api/routes/snippets/get_image.test.ts +0 -117
- package/bun-tests/fake-snippets-api/routes/snippets/images.test.ts +0 -114
- package/bun-tests/fake-snippets-api/routes/snippets/list.test.ts +0 -169
- package/bun-tests/fake-snippets-api/routes/snippets/list_newest.test.ts +0 -50
- package/bun-tests/fake-snippets-api/routes/snippets/list_trending.test.ts +0 -72
- package/bun-tests/fake-snippets-api/routes/snippets/remove_star.test.ts +0 -80
- package/bun-tests/fake-snippets-api/routes/snippets/search.test.ts +0 -75
- package/bun-tests/fake-snippets-api/routes/snippets/star-count.test.ts +0 -51
- package/bun-tests/fake-snippets-api/routes/snippets/update.test.ts +0 -175
- package/src/components/AiChatInterface.tsx +0 -229
- package/src/components/CodeAndPreview.tsx +0 -289
- package/src/components/CodeEditor.tsx +0 -539
- package/src/components/CodeEditorHeader.tsx +0 -135
- package/src/components/EditorNav.tsx +0 -502
- package/src/components/PreviewContent.tsx +0 -372
- package/src/components/SnippetCard.tsx +0 -159
- package/src/components/SnippetList.tsx +0 -71
- package/src/hooks/use-compiled-tsx.ts +0 -37
- package/src/hooks/use-run-tsx/construct-circuit.tsx +0 -62
- package/src/hooks/use-run-tsx/index.tsx +0 -256
- package/src/hooks/use-save-snippet.ts +0 -66
- package/src/hooks/use-typecheck.ts +0 -54
- package/src/lib/utils/getSyntaxError.ts +0 -13
- package/src/pages/ai.tsx +0 -92
- package/src/pages/view-snippet.tsx +0 -166
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { usePackageFiles } from "@/hooks/use-package-files"
|
|
2
|
+
import md5 from "md5"
|
|
3
|
+
|
|
4
|
+
export const useGetFsMapHashForPackage = (packageReleaseId: string) => {
|
|
5
|
+
const { data: pkgFilesList } = usePackageFiles(packageReleaseId)
|
|
6
|
+
|
|
7
|
+
if (!pkgFilesList) {
|
|
8
|
+
console.error(
|
|
9
|
+
`No package files found for package release ${packageReleaseId}`,
|
|
10
|
+
)
|
|
11
|
+
return null
|
|
12
|
+
}
|
|
13
|
+
const fsMap = new Map<string, string>()
|
|
14
|
+
for (const file of pkgFilesList) {
|
|
15
|
+
fsMap.set(file.file_path, file.content_text ?? "")
|
|
16
|
+
}
|
|
17
|
+
const fsMapHash = md5(JSON.stringify(fsMap))
|
|
18
|
+
return `md5-${fsMapHash}`
|
|
19
|
+
}
|
|
@@ -2,9 +2,13 @@ import { useState } from "react"
|
|
|
2
2
|
|
|
3
3
|
interface UsePreviewImagesProps {
|
|
4
4
|
packageName?: string
|
|
5
|
+
fsMapHash?: string
|
|
5
6
|
}
|
|
6
7
|
|
|
7
|
-
export function usePreviewImages({
|
|
8
|
+
export function usePreviewImages({
|
|
9
|
+
packageName,
|
|
10
|
+
fsMapHash,
|
|
11
|
+
}: UsePreviewImagesProps) {
|
|
8
12
|
const [imageStatus, setImageStatus] = useState<
|
|
9
13
|
Record<string, "loading" | "loaded" | "error">
|
|
10
14
|
>({
|
|
@@ -18,21 +22,33 @@ export function usePreviewImages({ packageName }: UsePreviewImagesProps) {
|
|
|
18
22
|
id: "3d",
|
|
19
23
|
label: "3D View",
|
|
20
24
|
imageUrl: packageName
|
|
21
|
-
? `https://registry-api.tscircuit.com/
|
|
25
|
+
? `https://registry-api.tscircuit.com/packages/images/${packageName}/3d.png?${new URLSearchParams(
|
|
26
|
+
{
|
|
27
|
+
fs_sha: fsMapHash ?? "",
|
|
28
|
+
},
|
|
29
|
+
).toString()}`
|
|
22
30
|
: undefined,
|
|
23
31
|
},
|
|
24
32
|
{
|
|
25
33
|
id: "pcb",
|
|
26
34
|
label: "PCB View",
|
|
27
35
|
imageUrl: packageName
|
|
28
|
-
? `https://registry-api.tscircuit.com/
|
|
36
|
+
? `https://registry-api.tscircuit.com/packages/images/${packageName}/pcb.png?${new URLSearchParams(
|
|
37
|
+
{
|
|
38
|
+
fs_sha: fsMapHash ?? "",
|
|
39
|
+
},
|
|
40
|
+
).toString()}`
|
|
29
41
|
: undefined,
|
|
30
42
|
},
|
|
31
43
|
{
|
|
32
44
|
id: "schematic",
|
|
33
45
|
label: "Schematic View",
|
|
34
46
|
imageUrl: packageName
|
|
35
|
-
? `https://registry-api.tscircuit.com/
|
|
47
|
+
? `https://registry-api.tscircuit.com/packages/images/${packageName}/schematic.png?${new URLSearchParams(
|
|
48
|
+
{
|
|
49
|
+
fs_sha: fsMapHash ?? "",
|
|
50
|
+
},
|
|
51
|
+
).toString()}`
|
|
36
52
|
: undefined,
|
|
37
53
|
},
|
|
38
54
|
]
|
|
@@ -1,18 +1,25 @@
|
|
|
1
1
|
import { useState, useEffect } from "react"
|
|
2
|
-
import {
|
|
2
|
+
import { createHighlighterCore, type HighlighterCore } from "shiki/core"
|
|
3
|
+
import { createOnigurumaEngine } from "shiki/engine/oniguruma"
|
|
3
4
|
|
|
4
|
-
let cachedHighlighter:
|
|
5
|
+
let cachedHighlighter: HighlighterCore | null = null
|
|
5
6
|
|
|
6
7
|
export const useShikiHighlighter = () => {
|
|
7
|
-
const [highlighter, setHighlighter] = useState<
|
|
8
|
+
const [highlighter, setHighlighter] = useState<HighlighterCore | null>(null)
|
|
8
9
|
const [isLoading, setIsLoading] = useState(true)
|
|
9
10
|
|
|
10
11
|
useEffect(() => {
|
|
11
12
|
const fetchHighlighter = async () => {
|
|
12
13
|
if (!cachedHighlighter) {
|
|
13
|
-
cachedHighlighter = await
|
|
14
|
-
themes: [
|
|
15
|
-
|
|
14
|
+
cachedHighlighter = await createHighlighterCore({
|
|
15
|
+
themes: [
|
|
16
|
+
import("@shikijs/themes/github-dark"),
|
|
17
|
+
import("@shikijs/themes/github-light"),
|
|
18
|
+
import("@shikijs/themes/vitesse-light"),
|
|
19
|
+
],
|
|
20
|
+
langs: [import("@shikijs/langs/tsx")],
|
|
21
|
+
// `shiki/wasm` contains the wasm binary inlined as base64 string.
|
|
22
|
+
engine: createOnigurumaEngine(import("shiki/wasm")),
|
|
16
23
|
})
|
|
17
24
|
}
|
|
18
25
|
setHighlighter(cachedHighlighter)
|
package/src/hooks/use-toast.tsx
CHANGED
|
@@ -197,7 +197,7 @@ export function useNotImplementedToast() {
|
|
|
197
197
|
The {feature} feature is not implemented yet. Help us out!{" "}
|
|
198
198
|
<a
|
|
199
199
|
className="text-blue-500 hover:underline font-semibold"
|
|
200
|
-
href="https://github.com/tscircuit/
|
|
200
|
+
href="https://github.com/tscircuit/tscircuit.com"
|
|
201
201
|
>
|
|
202
202
|
Check out our Github
|
|
203
203
|
</a>
|
|
@@ -1,19 +1,12 @@
|
|
|
1
|
-
import { AnyCircuitElement } from "circuit-json"
|
|
2
|
-
import { convertCircuitJsonToAssemblySvg } from "circuit-to-svg"
|
|
3
1
|
import { GLTFExporter, type GLTFExporterOptions } from "three-stdlib"
|
|
4
2
|
import { saveAs } from "file-saver"
|
|
5
3
|
import * as THREE from "three"
|
|
6
4
|
|
|
7
|
-
export const downloadGltf = async (
|
|
8
|
-
|
|
9
|
-
fileName: string,
|
|
10
|
-
) => {
|
|
11
|
-
const threeJsObject = window.TSCIRCUIT_3D_OBJECT_REF
|
|
12
|
-
?.current as THREE.Object3D
|
|
13
|
-
|
|
5
|
+
export const downloadGltf = async (fileName: string) => {
|
|
6
|
+
const threeJsObject = window.TSCIRCUIT_3D_OBJECT_REF as THREE.Object3D
|
|
14
7
|
if (!threeJsObject) {
|
|
15
8
|
throw new Error(
|
|
16
|
-
"To download the 3D model, please open the 3D view first and run the
|
|
9
|
+
"To download the 3D model, please open the 3D view first and run the package",
|
|
17
10
|
)
|
|
18
11
|
}
|
|
19
12
|
|
package/src/lib/types.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { Object3D, Object3DEventMap } from "three"
|
|
2
|
+
|
|
1
3
|
declare global {
|
|
2
4
|
interface Window {
|
|
3
5
|
TSCIRCUIT_REGISTRY_API_BASE_URL: string
|
|
4
|
-
TSCIRCUIT_REGISTRY_TOKEN: string
|
|
6
|
+
TSCIRCUIT_REGISTRY_TOKEN: string | null
|
|
5
7
|
TSCIRCUIT_STRIPE_CHECKOUT_BASE_URL: string
|
|
6
|
-
TSCIRCUIT_3D_OBJECT_REF:
|
|
8
|
+
TSCIRCUIT_3D_OBJECT_REF: Object3D<Object3DEventMap> | undefined
|
|
7
9
|
__DEBUG_CODE_EDITOR_FS_MAP: Map<string, string>
|
|
8
10
|
prettier: {
|
|
9
11
|
format: (code: string, options: any) => string
|
package/src/pages/dashboard.tsx
CHANGED
|
@@ -9,8 +9,7 @@ import { Edit2, KeyRound } from "lucide-react"
|
|
|
9
9
|
import { Button } from "@/components/ui/button"
|
|
10
10
|
import { useGlobalStore } from "@/hooks/use-global-store"
|
|
11
11
|
import { PrefetchPageLink } from "@/components/PrefetchPageLink"
|
|
12
|
-
import {
|
|
13
|
-
import { SnippetList } from "@/components/SnippetList"
|
|
12
|
+
import { PackagesList } from "@/components/PackagesList"
|
|
14
13
|
import { Helmet } from "react-helmet-async"
|
|
15
14
|
import { useSignIn } from "@/hooks/use-sign-in"
|
|
16
15
|
import { useSnippetsBaseApiUrl } from "@/hooks/use-snippets-base-api-url"
|
|
@@ -175,14 +174,14 @@ export const DashboardPage = () => {
|
|
|
175
174
|
)}
|
|
176
175
|
</div>
|
|
177
176
|
<div className="md:w-1/4">
|
|
178
|
-
<
|
|
177
|
+
<PackagesList
|
|
179
178
|
title="Trending Packages"
|
|
180
179
|
packages={trendingPackages}
|
|
181
180
|
showAll={showAllTrending}
|
|
182
181
|
onToggleShowAll={() => setShowAllTrending(!showAllTrending)}
|
|
183
182
|
/>
|
|
184
183
|
<div className="mt-8">
|
|
185
|
-
<
|
|
184
|
+
<PackagesList
|
|
186
185
|
title="Latest Packages"
|
|
187
186
|
packages={latestPackages}
|
|
188
187
|
showAll={showAllLatest}
|
package/src/pages/editor.tsx
CHANGED
|
@@ -1,45 +1,51 @@
|
|
|
1
|
-
import { CodeAndPreview } from "@/components/CodeAndPreview"
|
|
1
|
+
import { CodeAndPreview } from "@/components/package-port/CodeAndPreview"
|
|
2
2
|
import Footer from "@/components/Footer"
|
|
3
3
|
import Header from "@/components/Header"
|
|
4
|
-
import { useCurrentSnippetId } from "@/hooks/use-current-snippet-id"
|
|
5
|
-
import { useSnippet } from "@/hooks/use-snippet"
|
|
6
4
|
import { Helmet } from "react-helmet-async"
|
|
5
|
+
import { useCurrentPackageId } from "@/hooks/use-current-package-id"
|
|
6
|
+
import { usePackage } from "@/hooks/use-package"
|
|
7
|
+
import { useGetFsMapHashForPackage } from "@/hooks/use-get-fsmap-hash-for-package"
|
|
7
8
|
|
|
8
9
|
export const EditorPage = () => {
|
|
9
|
-
const {
|
|
10
|
-
const { data:
|
|
10
|
+
const { packageId } = useCurrentPackageId()
|
|
11
|
+
const { data: pkg, isLoading, error } = usePackage(packageId)
|
|
12
|
+
const fsMapHash = useGetFsMapHashForPackage(
|
|
13
|
+
pkg?.latest_package_release_id ?? "",
|
|
14
|
+
)
|
|
11
15
|
|
|
12
16
|
return (
|
|
13
17
|
<div className="overflow-x-hidden">
|
|
14
18
|
<Helmet>
|
|
15
19
|
<title>
|
|
16
|
-
{
|
|
17
|
-
? `${snippet.unscoped_name} - tscircuit`
|
|
18
|
-
: "tscircuit editor"}
|
|
20
|
+
{pkg ? `${pkg.unscoped_name} - tscircuit` : "tscircuit editor"}
|
|
19
21
|
</title>
|
|
20
|
-
{
|
|
22
|
+
{pkg && (
|
|
21
23
|
<>
|
|
22
24
|
<meta
|
|
23
25
|
property="og:title"
|
|
24
|
-
content={`${
|
|
26
|
+
content={`${pkg.unscoped_name} - tscircuit`}
|
|
25
27
|
/>
|
|
26
28
|
<meta
|
|
27
29
|
property="og:image"
|
|
28
|
-
content={`https://registry-api.tscircuit.com/
|
|
30
|
+
content={`https://registry-api.tscircuit.com/packages/images/${pkg.owner_github_username}/${pkg.unscoped_name}/pcb.png?${new URLSearchParams(
|
|
31
|
+
{
|
|
32
|
+
fs_sha: fsMapHash ?? "",
|
|
33
|
+
},
|
|
34
|
+
).toString()}`}
|
|
29
35
|
/>
|
|
30
36
|
<meta name="twitter:card" content="summary_large_image" />
|
|
31
37
|
<meta
|
|
32
38
|
name="twitter:image"
|
|
33
|
-
content={`https://registry-api.tscircuit.com/
|
|
39
|
+
content={`https://registry-api.tscircuit.com/packages/images/${pkg.owner_github_username}/${pkg.unscoped_name}/pcb.png`}
|
|
34
40
|
/>
|
|
35
41
|
</>
|
|
36
42
|
)}
|
|
37
43
|
</Helmet>
|
|
38
44
|
<Header />
|
|
39
|
-
{!error && <CodeAndPreview
|
|
45
|
+
{!error && <CodeAndPreview pkg={pkg} />}
|
|
40
46
|
{error && error.status === 404 && (
|
|
41
47
|
<div className="w-full h-[calc(100vh-20rem)] text-xl text-center flex justify-center items-center">
|
|
42
|
-
|
|
48
|
+
Package not found
|
|
43
49
|
</div>
|
|
44
50
|
)}
|
|
45
51
|
{error && error.status !== 404 && (
|
package/src/pages/latest.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { useState } from "react"
|
|
2
2
|
import { useQuery } from "react-query"
|
|
3
3
|
import { useAxios } from "@/hooks/use-axios"
|
|
4
|
-
import {
|
|
4
|
+
import { Package } from "fake-snippets-api/lib/db/schema"
|
|
5
5
|
import Header from "@/components/Header"
|
|
6
6
|
import Footer from "@/components/Footer"
|
|
7
7
|
import {
|
|
@@ -23,8 +23,8 @@ import {
|
|
|
23
23
|
SelectTrigger,
|
|
24
24
|
SelectValue,
|
|
25
25
|
} from "@/components/ui/select"
|
|
26
|
-
import { SnippetCard } from "@/components/SnippetCard"
|
|
27
26
|
import { PackageCardSkeleton } from "@/components/PackageCardSkeleton"
|
|
27
|
+
import { PackageCard } from "@/components/PackageCard"
|
|
28
28
|
|
|
29
29
|
const LatestPage: React.FC = () => {
|
|
30
30
|
const axios = useAxios()
|
|
@@ -33,30 +33,30 @@ const LatestPage: React.FC = () => {
|
|
|
33
33
|
const [category, setCategory] = useState("all")
|
|
34
34
|
|
|
35
35
|
const {
|
|
36
|
-
data:
|
|
36
|
+
data: packages,
|
|
37
37
|
isLoading,
|
|
38
38
|
error,
|
|
39
|
-
} = useQuery<
|
|
40
|
-
["
|
|
39
|
+
} = useQuery<Package[]>(
|
|
40
|
+
["latestPackages", category],
|
|
41
41
|
async () => {
|
|
42
42
|
const params = category !== "all" ? { tag: category } : {}
|
|
43
|
-
const response = await axios.get("/
|
|
44
|
-
return response.data.
|
|
43
|
+
const response = await axios.get("/packages/list_latest", { params })
|
|
44
|
+
return response.data.packages
|
|
45
45
|
},
|
|
46
46
|
{
|
|
47
47
|
keepPreviousData: true,
|
|
48
48
|
},
|
|
49
49
|
)
|
|
50
50
|
|
|
51
|
-
const
|
|
51
|
+
const filteredPackages = packages?.filter((pkg) => {
|
|
52
52
|
if (!searchQuery) return true
|
|
53
53
|
|
|
54
54
|
const query = searchQuery.toLowerCase().trim()
|
|
55
55
|
|
|
56
56
|
const searchableFields = [
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
(
|
|
57
|
+
pkg.unscoped_name.toLowerCase(),
|
|
58
|
+
pkg.owner_github_username?.toLowerCase() ?? "",
|
|
59
|
+
(pkg.description || "").toLowerCase(),
|
|
60
60
|
]
|
|
61
61
|
|
|
62
62
|
return searchableFields.some((field) => {
|
|
@@ -73,7 +73,7 @@ const LatestPage: React.FC = () => {
|
|
|
73
73
|
<div className="flex items-center gap-2 mb-3">
|
|
74
74
|
<Calendar className="w-6 h-6 text-blue-500" />
|
|
75
75
|
<h1 className="text-4xl font-bold text-gray-900">
|
|
76
|
-
Latest
|
|
76
|
+
Latest Packages
|
|
77
77
|
</h1>
|
|
78
78
|
</div>
|
|
79
79
|
<p className="text-lg text-gray-600 mb-4">
|
|
@@ -99,7 +99,7 @@ const LatestPage: React.FC = () => {
|
|
|
99
99
|
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4" />
|
|
100
100
|
<Input
|
|
101
101
|
type="text"
|
|
102
|
-
placeholder="Search latest
|
|
102
|
+
placeholder="Search latest packages..."
|
|
103
103
|
value={searchQuery}
|
|
104
104
|
onChange={(e) => setSearchQuery(e.target.value)}
|
|
105
105
|
className="pl-10"
|
|
@@ -154,40 +154,39 @@ const LatestPage: React.FC = () => {
|
|
|
154
154
|
</div>
|
|
155
155
|
<div>
|
|
156
156
|
<h3 className="text-lg font-semibold mb-2">
|
|
157
|
-
Error Loading
|
|
157
|
+
Error Loading Packages
|
|
158
158
|
</h3>
|
|
159
159
|
<p className="text-red-600">
|
|
160
|
-
We couldn't load the latest
|
|
160
|
+
We couldn't load the latest packages. Please try again later.
|
|
161
161
|
</p>
|
|
162
162
|
</div>
|
|
163
163
|
</div>
|
|
164
164
|
</div>
|
|
165
|
-
) :
|
|
165
|
+
) : filteredPackages?.length === 0 ? (
|
|
166
166
|
<div className="text-center py-12 px-4">
|
|
167
167
|
<div className="bg-slate-50 inline-flex rounded-full p-4 mb-4">
|
|
168
168
|
<Search className="w-8 h-8 text-slate-400" />
|
|
169
169
|
</div>
|
|
170
170
|
<h3 className="text-xl font-medium text-slate-900 mb-2">
|
|
171
|
-
No Matching
|
|
171
|
+
No Matching Packages
|
|
172
172
|
</h3>
|
|
173
173
|
<p className="text-slate-500 max-w-md mx-auto mb-6">
|
|
174
174
|
{searchQuery
|
|
175
|
-
? `No
|
|
175
|
+
? `No packages match your search for "${searchQuery}".`
|
|
176
176
|
: category !== "all"
|
|
177
|
-
? `No ${category}
|
|
178
|
-
: "There are no new
|
|
177
|
+
? `No ${category} packages found in the latest list.`
|
|
178
|
+
: "There are no new packages at the moment."}
|
|
179
179
|
</p>
|
|
180
180
|
</div>
|
|
181
181
|
) : (
|
|
182
182
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
183
|
-
{
|
|
183
|
+
{filteredPackages
|
|
184
184
|
?.sort((a, b) => b.created_at.localeCompare(a.created_at))
|
|
185
|
-
?.map((
|
|
186
|
-
<
|
|
187
|
-
key={
|
|
188
|
-
|
|
185
|
+
?.map((pkg) => (
|
|
186
|
+
<PackageCard
|
|
187
|
+
key={pkg.package_id}
|
|
188
|
+
pkg={pkg}
|
|
189
189
|
baseUrl={apiBaseUrl}
|
|
190
|
-
showOwner={true}
|
|
191
190
|
/>
|
|
192
191
|
))}
|
|
193
192
|
</div>
|
|
@@ -6,10 +6,14 @@ import { Helmet } from "react-helmet-async"
|
|
|
6
6
|
import { useCurrentPackageId } from "@/hooks/use-current-package-id"
|
|
7
7
|
import { NotFound } from "@/components/NotFound"
|
|
8
8
|
import { ErrorOutline } from "@/components/ErrorOutline"
|
|
9
|
+
import { useGetFsMapHashForPackage } from "@/hooks/use-get-fsmap-hash-for-package"
|
|
9
10
|
|
|
10
11
|
export const EditorPage = () => {
|
|
11
12
|
const { packageId } = useCurrentPackageId()
|
|
12
13
|
const { data: pkg, isLoading, error } = usePackage(packageId)
|
|
14
|
+
const fsMapHash = useGetFsMapHashForPackage(
|
|
15
|
+
pkg?.latest_package_release_id ?? "",
|
|
16
|
+
)
|
|
13
17
|
const uuid4RegExp = new RegExp(
|
|
14
18
|
/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$/,
|
|
15
19
|
)
|
|
@@ -25,12 +29,20 @@ export const EditorPage = () => {
|
|
|
25
29
|
/>
|
|
26
30
|
<meta
|
|
27
31
|
property="og:image"
|
|
28
|
-
content={`https://registry-api.tscircuit.com/
|
|
32
|
+
content={`https://registry-api.tscircuit.com/packages/images/${pkg.owner_github_username}/${pkg.unscoped_name}/pcb.png?${new URLSearchParams(
|
|
33
|
+
{
|
|
34
|
+
fs_sha: fsMapHash ?? "",
|
|
35
|
+
},
|
|
36
|
+
).toString()}`}
|
|
29
37
|
/>
|
|
30
38
|
<meta name="twitter:card" content="summary_large_image" />
|
|
31
39
|
<meta
|
|
32
40
|
name="twitter:image"
|
|
33
|
-
content={`https://registry-api.tscircuit.com/
|
|
41
|
+
content={`https://registry-api.tscircuit.com/packages/images/${pkg.owner_github_username}/${pkg.unscoped_name}/pcb.png?${new URLSearchParams(
|
|
42
|
+
{
|
|
43
|
+
fs_sha: fsMapHash ?? "",
|
|
44
|
+
},
|
|
45
|
+
).toString()}`}
|
|
34
46
|
/>
|
|
35
47
|
</>
|
|
36
48
|
)}
|
package/src/pages/search.tsx
CHANGED
|
@@ -1,13 +1,92 @@
|
|
|
1
|
+
import React, { useState, useEffect } from "react"
|
|
2
|
+
import { useQuery } from "react-query"
|
|
3
|
+
import { useAxios } from "@/hooks/use-axios"
|
|
4
|
+
import { useSearchParams } from "wouter"
|
|
1
5
|
import Header from "@/components/Header"
|
|
2
6
|
import Footer from "@/components/Footer"
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
7
|
+
import { Input } from "@/components/ui/input"
|
|
8
|
+
import { Search } from "lucide-react"
|
|
9
|
+
import PackageSearchResults from "@/components/PackageSearchResults"
|
|
10
|
+
import { useSnippetsBaseApiUrl } from "@/hooks/use-snippets-base-api-url"
|
|
11
|
+
import {
|
|
12
|
+
Select,
|
|
13
|
+
SelectContent,
|
|
14
|
+
SelectItem,
|
|
15
|
+
SelectTrigger,
|
|
16
|
+
SelectValue,
|
|
17
|
+
} from "@/components/ui/select"
|
|
18
|
+
import { Package } from "fake-snippets-api/lib/db/schema"
|
|
8
19
|
|
|
9
20
|
export const SearchPage = () => {
|
|
10
|
-
const
|
|
21
|
+
const axios = useAxios()
|
|
22
|
+
const apiBaseUrl = useSnippetsBaseApiUrl()
|
|
23
|
+
const [searchParams, setSearchParams] = useSearchParams()
|
|
24
|
+
|
|
25
|
+
const [searchQuery, setSearchQuery] = useState(searchParams.get("q") || "")
|
|
26
|
+
const [category, setCategory] = useState(
|
|
27
|
+
searchParams.get("category") || "all",
|
|
28
|
+
)
|
|
29
|
+
const [sortBy, setSortBy] = useState(searchParams.get("sort") || "stars")
|
|
30
|
+
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
const params = new URLSearchParams()
|
|
33
|
+
if (searchQuery) params.set("q", searchQuery)
|
|
34
|
+
if (category !== "all") params.set("category", category)
|
|
35
|
+
if (sortBy !== "stars") params.set("sort", sortBy)
|
|
36
|
+
setSearchParams(params)
|
|
37
|
+
}, [searchQuery, category, sortBy, setSearchParams])
|
|
38
|
+
|
|
39
|
+
const {
|
|
40
|
+
data: packages,
|
|
41
|
+
isLoading,
|
|
42
|
+
error,
|
|
43
|
+
} = useQuery(
|
|
44
|
+
["packageSearch", searchQuery, category],
|
|
45
|
+
async () => {
|
|
46
|
+
const params = new URLSearchParams()
|
|
47
|
+
if (searchQuery) params.append("q", searchQuery)
|
|
48
|
+
if (category !== "all") params.append("category", category)
|
|
49
|
+
|
|
50
|
+
const response = await axios.post(`/packages/search`, {
|
|
51
|
+
query: searchQuery,
|
|
52
|
+
})
|
|
53
|
+
return response.data.packages
|
|
54
|
+
},
|
|
55
|
+
{ enabled: Boolean(searchQuery), keepPreviousData: true },
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
const filteredPackages = packages
|
|
59
|
+
?.filter((pkg: Package) => {
|
|
60
|
+
if (!searchQuery) return true
|
|
61
|
+
|
|
62
|
+
const query = searchQuery.toLowerCase().trim()
|
|
63
|
+
const searchableFields = [
|
|
64
|
+
pkg.unscoped_name.toLowerCase(),
|
|
65
|
+
(pkg.owner_github_username || "").toLowerCase(),
|
|
66
|
+
(pkg.description || "").toLowerCase(),
|
|
67
|
+
pkg.description?.toLowerCase(),
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
return searchableFields.some((field) => {
|
|
71
|
+
const queryWords = query.split(/\s+/).filter((word) => word.length > 0)
|
|
72
|
+
if (!field) return false
|
|
73
|
+
return queryWords.every((word) => field.includes(word))
|
|
74
|
+
})
|
|
75
|
+
})
|
|
76
|
+
?.sort((a: Package, b: Package) => {
|
|
77
|
+
if (sortBy === "stars") {
|
|
78
|
+
return (b.star_count || 0) - (a.star_count || 0)
|
|
79
|
+
} else if (sortBy === "newest") {
|
|
80
|
+
return (
|
|
81
|
+
new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime()
|
|
82
|
+
)
|
|
83
|
+
} else if (sortBy === "oldest") {
|
|
84
|
+
return (
|
|
85
|
+
new Date(a.updated_at).getTime() - new Date(b.updated_at).getTime()
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
return 0
|
|
89
|
+
})
|
|
11
90
|
|
|
12
91
|
return (
|
|
13
92
|
<div className="min-h-screen flex flex-col">
|
|
@@ -17,26 +96,46 @@ export const SearchPage = () => {
|
|
|
17
96
|
<div className="max-w-8xl mx-auto">
|
|
18
97
|
<div className="mb-6">
|
|
19
98
|
<div className="flex items-center gap-2 mb-3">
|
|
20
|
-
<Search className="w-6 h-6 text-blue-500" />
|
|
21
99
|
<h1 className="text-3xl font-bold text-gray-900">
|
|
22
100
|
Search tscircuit Packages
|
|
23
101
|
</h1>
|
|
24
102
|
</div>
|
|
25
|
-
<div className="flex flex-
|
|
26
|
-
<
|
|
27
|
-
<
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
103
|
+
<div className="flex flex-col sm:flex-row gap-4 mb-4">
|
|
104
|
+
<div className="relative flex-grow">
|
|
105
|
+
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4" />
|
|
106
|
+
<Input
|
|
107
|
+
type="search"
|
|
108
|
+
placeholder="Search packages..."
|
|
109
|
+
className="pl-10"
|
|
110
|
+
value={searchQuery}
|
|
111
|
+
onChange={(e) => setSearchQuery(e.target.value)}
|
|
112
|
+
aria-label="Search packages"
|
|
113
|
+
role="searchbox"
|
|
114
|
+
/>
|
|
115
|
+
</div>
|
|
116
|
+
<Select value={sortBy} onValueChange={setSortBy}>
|
|
117
|
+
<SelectTrigger className="w-[140px]">
|
|
118
|
+
<SelectValue placeholder="Sort By" />
|
|
119
|
+
</SelectTrigger>
|
|
120
|
+
<SelectContent>
|
|
121
|
+
<SelectItem value="stars">Most Starred</SelectItem>
|
|
122
|
+
<SelectItem value="newest">Newest</SelectItem>
|
|
123
|
+
<SelectItem value="oldest">Oldest</SelectItem>
|
|
124
|
+
</SelectContent>
|
|
125
|
+
</Select>
|
|
35
126
|
</div>
|
|
36
127
|
</div>
|
|
37
128
|
|
|
38
|
-
<
|
|
39
|
-
|
|
129
|
+
<PackageSearchResults
|
|
130
|
+
isLoading={isLoading}
|
|
131
|
+
error={error}
|
|
132
|
+
filteredPackages={filteredPackages}
|
|
133
|
+
apiBaseUrl={apiBaseUrl}
|
|
134
|
+
emptyStateMessage={
|
|
135
|
+
searchQuery
|
|
136
|
+
? `No packages match your search for "${searchQuery}".`
|
|
137
|
+
: "Please enter a search query to find packages."
|
|
138
|
+
}
|
|
40
139
|
/>
|
|
41
140
|
</div>
|
|
42
141
|
</div>
|
|
@@ -45,3 +144,5 @@ export const SearchPage = () => {
|
|
|
45
144
|
</div>
|
|
46
145
|
)
|
|
47
146
|
}
|
|
147
|
+
|
|
148
|
+
export default SearchPage
|