@tscircuit/fake-snippets 0.0.88 → 0.0.90
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/api/generated-index.js +96 -14
- package/bun-tests/fake-snippets-api/routes/proxy.test.ts +42 -0
- package/bun.lock +196 -215
- package/dist/bundle.js +596 -370
- package/fake-snippets-api/routes/api/autocomplete/create_autocomplete.ts +134 -0
- package/fake-snippets-api/routes/api/proxy.ts +128 -0
- package/package.json +59 -48
- package/renovate.json +2 -1
- package/src/App.tsx +67 -3
- package/src/ContextProviders.tsx +2 -0
- package/src/build-watcher.ts +52 -0
- package/src/components/CircuitJsonImportDialog.tsx +1 -1
- package/src/components/CmdKMenu.tsx +533 -197
- package/src/components/DownloadButtonAndMenu.tsx +104 -26
- package/src/components/FileSidebar.tsx +11 -1
- package/src/components/Header2.tsx +7 -2
- package/src/components/PackageBuildsPage/LogContent.tsx +25 -22
- package/src/components/PackageBuildsPage/PackageBuildDetailsPage.tsx +6 -6
- package/src/components/PackageBuildsPage/build-preview-content.tsx +5 -5
- package/src/components/PackageBuildsPage/package-build-details-panel.tsx +15 -13
- package/src/components/PackageBuildsPage/package-build-header.tsx +19 -28
- package/src/components/PackageCard.tsx +66 -16
- package/src/components/SearchComponent.tsx +2 -2
- package/src/components/SuspenseRunFrame.tsx +14 -2
- package/src/components/ViewPackagePage/components/important-files-view.tsx +90 -17
- package/src/components/ViewPackagePage/components/main-content-header.tsx +26 -2
- package/src/components/ViewPackagePage/components/mobile-sidebar.tsx +2 -2
- package/src/components/ViewPackagePage/components/repo-page-content.tsx +35 -30
- package/src/components/ViewPackagePage/components/sidebar-about-section.tsx +2 -2
- package/src/components/ViewPackagePage/components/sidebar-releases-section.tsx +20 -12
- package/src/components/ViewPackagePage/components/tab-views/files-view.tsx +0 -7
- package/src/components/ViewPackagePage/utils/fuzz-search.ts +121 -0
- package/src/components/ViewPackagePage/utils/is-hidden-file.ts +4 -0
- package/src/components/dialogs/confirm-delete-package-dialog.tsx +1 -1
- package/src/components/dialogs/confirm-discard-changes-dialog.tsx +73 -0
- package/src/components/dialogs/edit-package-details-dialog.tsx +2 -2
- package/src/components/dialogs/view-ts-files-dialog.tsx +478 -42
- package/src/components/package-port/CodeAndPreview.tsx +17 -16
- package/src/components/package-port/CodeEditor.tsx +138 -17
- package/src/components/package-port/CodeEditorHeader.tsx +44 -4
- package/src/components/package-port/EditorNav.tsx +42 -29
- package/src/components/package-port/GlobalFindReplace.tsx +681 -0
- package/src/components/package-port/QuickOpen.tsx +241 -0
- package/src/components/ui/dialog.tsx +1 -1
- package/src/components/ui/tree-view.tsx +1 -1
- package/src/global.d.ts +3 -0
- package/src/hooks/use-ai-review.ts +31 -0
- package/src/hooks/use-code-completion-ai-api.ts +3 -3
- package/src/hooks/use-current-package-release.ts +5 -1
- package/src/hooks/use-delete-package.ts +6 -2
- package/src/hooks/use-download-zip.ts +50 -0
- package/src/hooks/use-hotkey.ts +116 -0
- package/src/hooks/use-package-by-package-id.ts +1 -0
- package/src/hooks/use-package-by-package-name.ts +1 -0
- package/src/hooks/use-package-files.ts +3 -0
- package/src/hooks/use-package-release.ts +5 -1
- package/src/hooks/use-package.ts +1 -0
- package/src/hooks/use-request-ai-review-mutation.ts +14 -6
- package/src/hooks/use-snippet.ts +1 -0
- package/src/hooks/useFileManagement.ts +28 -10
- package/src/hooks/usePackageFilesLoader.ts +3 -1
- package/src/index.css +11 -0
- package/src/lib/decodeUrlHashToFsMap.ts +17 -0
- package/src/lib/download-fns/download-circuit-png.ts +88 -0
- package/src/lib/download-fns/download-png-utils.ts +31 -0
- package/src/lib/encodeFsMapToUrlHash.ts +13 -0
- package/src/lib/populate-query-cache-with-ssr-data.ts +7 -0
- package/src/lib/ts-lib-cache.ts +47 -0
- package/src/lib/types.ts +2 -0
- package/src/lib/utils/findTargetFile.ts +1 -1
- package/src/lib/utils/package-utils.ts +10 -0
- package/src/main.tsx +7 -0
- package/src/pages/dashboard.tsx +18 -5
- package/src/pages/view-package.tsx +15 -7
- package/src/types/package.ts +4 -0
- package/vite.config.ts +100 -1
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { useEffect, useMemo, useState, useCallback, useRef } from "react"
|
|
2
2
|
import { isValidFileName } from "@/lib/utils/isValidFileName"
|
|
3
|
+
import { PackageFile } from "@/types/package"
|
|
3
4
|
import {
|
|
4
5
|
DEFAULT_CODE,
|
|
5
6
|
generateRandomPackageName,
|
|
6
|
-
|
|
7
|
-
} from "../components/package-port/CodeAndPreview"
|
|
7
|
+
} from "@/lib/utils/package-utils"
|
|
8
8
|
import { Package } from "fake-snippets-api/lib/db/schema"
|
|
9
9
|
import { usePackageFiles } from "./use-package-files"
|
|
10
10
|
import { decodeUrlHashToText } from "@/lib/decodeUrlHashToText"
|
|
11
|
+
import { decodeUrlHashToFsMap } from "@/lib/decodeUrlHashToFsMap"
|
|
11
12
|
import { usePackageFilesLoader } from "./usePackageFilesLoader"
|
|
12
13
|
import { useGlobalStore } from "./use-global-store"
|
|
13
14
|
import { useToast } from "@/components/ViewPackagePage/hooks/use-toast"
|
|
@@ -15,7 +16,7 @@ import { useUpdatePackageFilesMutation } from "./useUpdatePackageFilesMutation"
|
|
|
15
16
|
import { useCreatePackageReleaseMutation } from "./use-create-package-release-mutation"
|
|
16
17
|
import { useCreatePackageMutation } from "./use-create-package-mutation"
|
|
17
18
|
import { findTargetFile } from "@/lib/utils/findTargetFile"
|
|
18
|
-
import {
|
|
19
|
+
import { encodeFsMapToUrlHash } from "@/lib/encodeFsMapToUrlHash"
|
|
19
20
|
|
|
20
21
|
export interface ICreateFileProps {
|
|
21
22
|
newFileName: string
|
|
@@ -117,6 +118,22 @@ export function useFileManagement({
|
|
|
117
118
|
|
|
118
119
|
useEffect(() => {
|
|
119
120
|
if (!currentPackage || isLoadingPackageFilesWithContent) {
|
|
121
|
+
const decodedFsMap = decodeUrlHashToFsMap(window.location.toString())
|
|
122
|
+
|
|
123
|
+
if (decodedFsMap && Object.keys(decodedFsMap).length > 0) {
|
|
124
|
+
const filesFromUrl = Object.entries(decodedFsMap).map(
|
|
125
|
+
([path, content]) => ({
|
|
126
|
+
path,
|
|
127
|
+
content: String(content),
|
|
128
|
+
}),
|
|
129
|
+
)
|
|
130
|
+
const targetFile = findTargetFile(filesFromUrl, fileChoosen)
|
|
131
|
+
setLocalFiles(filesFromUrl)
|
|
132
|
+
setInitialFiles([])
|
|
133
|
+
setCurrentFile(targetFile?.path || filesFromUrl[0]?.path || null)
|
|
134
|
+
return
|
|
135
|
+
}
|
|
136
|
+
|
|
120
137
|
setLocalFiles([
|
|
121
138
|
{
|
|
122
139
|
path: "index.tsx",
|
|
@@ -243,14 +260,15 @@ export function useFileManagement({
|
|
|
243
260
|
|
|
244
261
|
debounceTimeoutRef.current = setTimeout(() => {
|
|
245
262
|
try {
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
263
|
+
const map = files.reduce(
|
|
264
|
+
(acc, f) => {
|
|
265
|
+
acc[f.path] = f.content || ""
|
|
266
|
+
return acc
|
|
267
|
+
},
|
|
268
|
+
{} as Record<string, string>,
|
|
269
|
+
)
|
|
252
270
|
|
|
253
|
-
const snippetUrl =
|
|
271
|
+
const snippetUrl = encodeFsMapToUrlHash(map)
|
|
254
272
|
if (typeof snippetUrl !== "string") return
|
|
255
273
|
|
|
256
274
|
const currentUrl = new URL(window.location.href)
|
|
@@ -36,7 +36,9 @@ export function usePackageFilesLoader(pkg?: Package) {
|
|
|
36
36
|
const content = response.data.package_file?.content_text
|
|
37
37
|
return { path: file.file_path, content: content ?? "" }
|
|
38
38
|
},
|
|
39
|
-
|
|
39
|
+
refetchOnWindowFocus: false,
|
|
40
|
+
refetchOnMount: false,
|
|
41
|
+
cacheTime: 0,
|
|
40
42
|
})) ?? [],
|
|
41
43
|
)
|
|
42
44
|
|
package/src/index.css
CHANGED
|
@@ -67,3 +67,14 @@
|
|
|
67
67
|
padding: 12px !important;
|
|
68
68
|
margin: 0 !important;
|
|
69
69
|
}
|
|
70
|
+
|
|
71
|
+
.markdown-content {
|
|
72
|
+
padding-top: 0.5rem;
|
|
73
|
+
max-width: none !important;
|
|
74
|
+
width: 100% !important;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.markdown-content .prose {
|
|
78
|
+
max-width: none !important;
|
|
79
|
+
width: 100% !important;
|
|
80
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { gunzipSync, strFromU8 } from "fflate"
|
|
2
|
+
import { base64ToBytes } from "./base64ToBytes"
|
|
3
|
+
|
|
4
|
+
export function decodeUrlHashToFsMap(
|
|
5
|
+
url: string,
|
|
6
|
+
): Record<string, string> | null {
|
|
7
|
+
const base64Data = url.split("#data:application/gzip;base64,")[1]
|
|
8
|
+
if (!base64Data) return null
|
|
9
|
+
try {
|
|
10
|
+
const compressedData = base64ToBytes(base64Data)
|
|
11
|
+
const decompressedData = gunzipSync(compressedData)
|
|
12
|
+
const text = strFromU8(decompressedData)
|
|
13
|
+
return JSON.parse(text)
|
|
14
|
+
} catch {
|
|
15
|
+
return null
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { AnyCircuitElement } from "circuit-json"
|
|
2
|
+
import {
|
|
3
|
+
convertCircuitJsonToAssemblySvg,
|
|
4
|
+
convertCircuitJsonToPcbSvg,
|
|
5
|
+
convertCircuitJsonToSchematicSvg,
|
|
6
|
+
} from "circuit-to-svg"
|
|
7
|
+
import { saveAs } from "file-saver"
|
|
8
|
+
|
|
9
|
+
export type ImageFormat = "schematic" | "pcb" | "assembly"
|
|
10
|
+
|
|
11
|
+
interface DownloadCircuitPngOptions {
|
|
12
|
+
format: ImageFormat
|
|
13
|
+
width?: number
|
|
14
|
+
height?: number
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const convertSvgToPng = async (svgString: string): Promise<Blob> => {
|
|
18
|
+
return new Promise((resolve, reject) => {
|
|
19
|
+
const canvas = document.createElement("canvas")
|
|
20
|
+
const ctx = canvas.getContext("2d")
|
|
21
|
+
if (!ctx) {
|
|
22
|
+
reject(new Error("Failed to get canvas context"))
|
|
23
|
+
return
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const img = new Image()
|
|
27
|
+
img.onload = () => {
|
|
28
|
+
canvas.width = img.width || 800
|
|
29
|
+
canvas.height = img.height || 600
|
|
30
|
+
ctx.fillStyle = "white"
|
|
31
|
+
ctx.fillRect(0, 0, canvas.width, canvas.height)
|
|
32
|
+
ctx.drawImage(img, 0, 0)
|
|
33
|
+
|
|
34
|
+
canvas.toBlob((blob) => {
|
|
35
|
+
if (blob) {
|
|
36
|
+
resolve(blob)
|
|
37
|
+
} else {
|
|
38
|
+
reject(new Error("Failed to convert canvas to blob"))
|
|
39
|
+
}
|
|
40
|
+
}, "image/png")
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
img.onerror = () => {
|
|
44
|
+
reject(new Error("Failed to load SVG image"))
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const svgBlob = new Blob([svgString], { type: "image/svg+xml" })
|
|
48
|
+
const url = URL.createObjectURL(svgBlob)
|
|
49
|
+
img.src = url
|
|
50
|
+
|
|
51
|
+
setTimeout(() => URL.revokeObjectURL(url), 10000)
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const downloadCircuitPng = async (
|
|
56
|
+
circuitJson: AnyCircuitElement[],
|
|
57
|
+
fileName: string,
|
|
58
|
+
options: DownloadCircuitPngOptions = { format: "pcb" },
|
|
59
|
+
) => {
|
|
60
|
+
try {
|
|
61
|
+
let blob: Blob
|
|
62
|
+
let svg: string
|
|
63
|
+
|
|
64
|
+
const svgOptions: any = {}
|
|
65
|
+
if (options.width) svgOptions.width = options.width
|
|
66
|
+
if (options.height) svgOptions.height = options.height
|
|
67
|
+
|
|
68
|
+
switch (options.format) {
|
|
69
|
+
case "schematic":
|
|
70
|
+
svg = convertCircuitJsonToSchematicSvg(circuitJson, svgOptions)
|
|
71
|
+
break
|
|
72
|
+
case "pcb":
|
|
73
|
+
svg = convertCircuitJsonToPcbSvg(circuitJson, svgOptions)
|
|
74
|
+
break
|
|
75
|
+
case "assembly":
|
|
76
|
+
svg = convertCircuitJsonToAssemblySvg(circuitJson, svgOptions)
|
|
77
|
+
break
|
|
78
|
+
default:
|
|
79
|
+
throw new Error(`Unsupported format: ${options.format}`)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
blob = await convertSvgToPng(svg)
|
|
83
|
+
const downloadFileName = `${fileName}_${options.format}.png`
|
|
84
|
+
saveAs(blob, downloadFileName)
|
|
85
|
+
} catch (error) {
|
|
86
|
+
throw new Error(`Failed to download ${options.format} PNG: ${error}`)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { AnyCircuitElement } from "circuit-json"
|
|
2
|
+
import { downloadCircuitPng, ImageFormat } from "./download-circuit-png"
|
|
3
|
+
import { toast } from "@/hooks/use-toast"
|
|
4
|
+
|
|
5
|
+
interface DownloadPngOptions {
|
|
6
|
+
circuitJson?: AnyCircuitElement[] | null
|
|
7
|
+
unscopedName?: string
|
|
8
|
+
author?: string
|
|
9
|
+
format: ImageFormat
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const downloadPngImage = async ({
|
|
13
|
+
circuitJson,
|
|
14
|
+
unscopedName,
|
|
15
|
+
format,
|
|
16
|
+
}: DownloadPngOptions) => {
|
|
17
|
+
try {
|
|
18
|
+
if (!circuitJson) {
|
|
19
|
+
throw new Error("Circuit JSON not available")
|
|
20
|
+
}
|
|
21
|
+
await downloadCircuitPng(circuitJson, unscopedName || "circuit", {
|
|
22
|
+
format,
|
|
23
|
+
})
|
|
24
|
+
} catch (error: any) {
|
|
25
|
+
toast({
|
|
26
|
+
title: `Error Downloading ${format.charAt(0).toUpperCase() + format.slice(1)} PNG`,
|
|
27
|
+
description: error.toString(),
|
|
28
|
+
variant: "destructive",
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { gzipSync, strToU8 } from "fflate"
|
|
2
|
+
import { bytesToBase64 } from "./bytesToBase64"
|
|
3
|
+
|
|
4
|
+
export function encodeFsMapToUrlHash(
|
|
5
|
+
fsMap: Record<string, string>,
|
|
6
|
+
snippet_type?: string,
|
|
7
|
+
): string {
|
|
8
|
+
const text = JSON.stringify(fsMap)
|
|
9
|
+
const compressedData = gzipSync(strToU8(text))
|
|
10
|
+
const base64Data = bytesToBase64(compressedData)
|
|
11
|
+
const typeParam = snippet_type ? `&snippet_type=${snippet_type}` : ""
|
|
12
|
+
return `${window.location.origin}/editor?${typeParam}#data:application/gzip;base64,${base64Data}`
|
|
13
|
+
}
|
|
@@ -14,7 +14,9 @@ export function populateQueryCacheWithSSRData(queryClient: QueryClient) {
|
|
|
14
14
|
|
|
15
15
|
if (!ssrPackage || !ssrPackageRelease) return
|
|
16
16
|
|
|
17
|
+
// Cache lookups by package name and id
|
|
17
18
|
queryClient.setQueryData(["package", ssrPackage.name], ssrPackage)
|
|
19
|
+
queryClient.setQueryData(["package", ssrPackage.package_id], ssrPackage)
|
|
18
20
|
queryClient.setQueryData(["packages", ssrPackage.package_id], ssrPackage)
|
|
19
21
|
|
|
20
22
|
queryClient.setQueryData(
|
|
@@ -29,6 +31,11 @@ export function populateQueryCacheWithSSRData(queryClient: QueryClient) {
|
|
|
29
31
|
ssrPackageRelease,
|
|
30
32
|
)
|
|
31
33
|
|
|
34
|
+
queryClient.setQueryData(
|
|
35
|
+
["packageRelease", { is_latest: true, package_id: ssrPackage.package_id }],
|
|
36
|
+
ssrPackageRelease,
|
|
37
|
+
)
|
|
38
|
+
|
|
32
39
|
queryClient.setQueryData(
|
|
33
40
|
[
|
|
34
41
|
"packageRelease",
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createDefaultMapFromCDN,
|
|
3
|
+
knownLibFilesForCompilerOptions,
|
|
4
|
+
} from "@typescript/vfs"
|
|
5
|
+
import { get, set } from "idb-keyval"
|
|
6
|
+
import { compressToUTF16, decompressFromUTF16 } from "lz-string"
|
|
7
|
+
import ts from "typescript"
|
|
8
|
+
|
|
9
|
+
const TS_LIB_VERSION = "5.6.3"
|
|
10
|
+
const CACHE_PREFIX = `ts-lib-${TS_LIB_VERSION}-`
|
|
11
|
+
|
|
12
|
+
export async function loadDefaultLibMap(): Promise<Map<string, string>> {
|
|
13
|
+
const fsMap = new Map<string, string>()
|
|
14
|
+
const libs = knownLibFilesForCompilerOptions(
|
|
15
|
+
{ target: ts.ScriptTarget.ES2022 },
|
|
16
|
+
ts,
|
|
17
|
+
)
|
|
18
|
+
const missing: string[] = []
|
|
19
|
+
|
|
20
|
+
for (const lib of libs) {
|
|
21
|
+
const cached = await get(CACHE_PREFIX + lib)
|
|
22
|
+
if (cached) {
|
|
23
|
+
fsMap.set("/" + lib, decompressFromUTF16(cached as string))
|
|
24
|
+
} else {
|
|
25
|
+
missing.push(lib)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (missing.length > 0) {
|
|
30
|
+
const fetched = await createDefaultMapFromCDN(
|
|
31
|
+
{ target: ts.ScriptTarget.ES2022 },
|
|
32
|
+
TS_LIB_VERSION,
|
|
33
|
+
true,
|
|
34
|
+
ts,
|
|
35
|
+
{ compressToUTF16, decompressFromUTF16 } as any,
|
|
36
|
+
)
|
|
37
|
+
for (const [filename, content] of fetched) {
|
|
38
|
+
fsMap.set(filename, content)
|
|
39
|
+
await set(
|
|
40
|
+
CACHE_PREFIX + filename.replace(/^\//, ""),
|
|
41
|
+
compressToUTF16(content),
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return fsMap
|
|
47
|
+
}
|
package/src/lib/types.ts
CHANGED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const DEFAULT_CODE = `
|
|
2
|
+
export default () => (
|
|
3
|
+
<board width="10mm" height="10mm">
|
|
4
|
+
{/* write your code here! */}
|
|
5
|
+
</board>
|
|
6
|
+
)
|
|
7
|
+
`.trim()
|
|
8
|
+
|
|
9
|
+
export const generateRandomPackageName = () =>
|
|
10
|
+
`untitled-package-${Math.floor(Math.random() * 900) + 100}`
|
package/src/main.tsx
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import { StrictMode } from "react"
|
|
2
2
|
import { createRoot } from "react-dom/client"
|
|
3
3
|
import App from "./App.tsx"
|
|
4
|
+
|
|
5
|
+
if (typeof window !== "undefined" && !window.__APP_LOADED_AT) {
|
|
6
|
+
window.__APP_LOADED_AT = Date.now()
|
|
7
|
+
}
|
|
4
8
|
import "./index.css"
|
|
9
|
+
import { setupBuildWatcher } from "./build-watcher"
|
|
10
|
+
|
|
11
|
+
setupBuildWatcher()
|
|
5
12
|
|
|
6
13
|
createRoot(document.getElementById("root")!).render(
|
|
7
14
|
<StrictMode>
|
package/src/pages/dashboard.tsx
CHANGED
|
@@ -41,14 +41,17 @@ export const DashboardPage = () => {
|
|
|
41
41
|
const response = await axios.post(`/packages/list`, {
|
|
42
42
|
owner_github_username: currentUser,
|
|
43
43
|
})
|
|
44
|
-
return response.data.packages
|
|
45
|
-
(a: any, b: any) =>
|
|
46
|
-
new Date(b.updated_at || b.created_at).getTime() -
|
|
47
|
-
new Date(a.updated_at || a.created_at).getTime(),
|
|
48
|
-
)
|
|
44
|
+
return response.data.packages
|
|
49
45
|
},
|
|
50
46
|
{
|
|
51
47
|
enabled: isLoggedIn,
|
|
48
|
+
select: (data: Package[]) => {
|
|
49
|
+
return [...data].sort(
|
|
50
|
+
(a: Package, b: Package) =>
|
|
51
|
+
new Date(b.updated_at || b.created_at).getTime() -
|
|
52
|
+
new Date(a.updated_at || a.created_at).getTime(),
|
|
53
|
+
)
|
|
54
|
+
},
|
|
52
55
|
},
|
|
53
56
|
)
|
|
54
57
|
|
|
@@ -58,6 +61,11 @@ export const DashboardPage = () => {
|
|
|
58
61
|
const response = await axios.get("/packages/list_trending")
|
|
59
62
|
return response.data.packages
|
|
60
63
|
},
|
|
64
|
+
{
|
|
65
|
+
refetchOnWindowFocus: false,
|
|
66
|
+
refetchOnMount: false,
|
|
67
|
+
refetchOnReconnect: false,
|
|
68
|
+
},
|
|
61
69
|
)
|
|
62
70
|
|
|
63
71
|
const { data: latestPackages } = useQuery<Package[]>(
|
|
@@ -66,6 +74,11 @@ export const DashboardPage = () => {
|
|
|
66
74
|
const response = await axios.get("/packages/list_latest")
|
|
67
75
|
return response.data.packages
|
|
68
76
|
},
|
|
77
|
+
{
|
|
78
|
+
refetchOnWindowFocus: false,
|
|
79
|
+
refetchOnMount: false,
|
|
80
|
+
refetchOnReconnect: false,
|
|
81
|
+
},
|
|
69
82
|
)
|
|
70
83
|
|
|
71
84
|
const baseUrl = useSnippetsBaseApiUrl()
|
|
@@ -21,11 +21,17 @@ export const ViewPackagePage = () => {
|
|
|
21
21
|
data: packageRelease,
|
|
22
22
|
error: packageReleaseError,
|
|
23
23
|
isLoading: isLoadingPackageRelease,
|
|
24
|
-
} = usePackageRelease(
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
} = usePackageRelease(
|
|
25
|
+
{
|
|
26
|
+
is_latest: true,
|
|
27
|
+
package_name: `${author}/${packageName}`,
|
|
28
|
+
include_ai_review: true,
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
refetchInterval: (data) =>
|
|
32
|
+
data?.ai_review_requested && !data.ai_review_text ? 2000 : false,
|
|
33
|
+
},
|
|
34
|
+
)
|
|
29
35
|
|
|
30
36
|
const { data: packageFiles } = usePackageFiles(
|
|
31
37
|
packageRelease?.package_release_id,
|
|
@@ -46,15 +52,17 @@ export const ViewPackagePage = () => {
|
|
|
46
52
|
</Helmet>
|
|
47
53
|
<RepoPageContent
|
|
48
54
|
packageFiles={packageFiles as any}
|
|
49
|
-
packageInfo={packageInfo
|
|
50
|
-
packageRelease={packageRelease
|
|
55
|
+
packageInfo={packageInfo}
|
|
56
|
+
packageRelease={packageRelease}
|
|
51
57
|
importantFilePaths={["README.md", "LICENSE", "package.json"]}
|
|
52
58
|
onFileClicked={(file) => {
|
|
59
|
+
if (!packageInfo?.package_id) return
|
|
53
60
|
setLocation(
|
|
54
61
|
`/editor?package_id=${packageInfo?.package_id}&file_path=${file.file_path}`,
|
|
55
62
|
)
|
|
56
63
|
}}
|
|
57
64
|
onEditClicked={(file_path?: string) => {
|
|
65
|
+
if (!packageInfo?.package_id) return
|
|
58
66
|
setLocation(
|
|
59
67
|
`/editor?package_id=${packageInfo?.package_id}${
|
|
60
68
|
file_path ? `&file_path=${file_path}` : ""
|
package/vite.config.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { createDatabase } from "./fake-snippets-api/lib/db/db-client"
|
|
2
2
|
import { defineConfig, Plugin, UserConfig } from "vite"
|
|
3
3
|
import type { PluginOption } from "vite"
|
|
4
|
-
import path from "path"
|
|
4
|
+
import path, { extname } from "path"
|
|
5
|
+
import { readFileSync } from "fs"
|
|
6
|
+
import { execSync } from "child_process"
|
|
5
7
|
import react from "@vitejs/plugin-react"
|
|
6
8
|
import { ViteImageOptimizer } from "vite-plugin-image-optimizer"
|
|
7
9
|
import { getNodeHandler } from "winterspec/adapters/node"
|
|
8
10
|
import vercel from "vite-plugin-vercel"
|
|
11
|
+
import type { IncomingMessage, ServerResponse } from "http"
|
|
9
12
|
|
|
10
13
|
// @ts-ignore
|
|
11
14
|
import winterspecBundle from "./dist/bundle.js"
|
|
@@ -39,6 +42,100 @@ function apiFakePlugin(): Plugin {
|
|
|
39
42
|
}
|
|
40
43
|
}
|
|
41
44
|
|
|
45
|
+
function vercelSsrDevPlugin(): Plugin {
|
|
46
|
+
return {
|
|
47
|
+
name: "vercel-ssr-dev",
|
|
48
|
+
apply: "serve",
|
|
49
|
+
async configureServer(server) {
|
|
50
|
+
const port = server.config.server.port || 5173
|
|
51
|
+
process.env.TSC_DEV_SSR = "true"
|
|
52
|
+
process.env.TSC_BASE_URL = `http://127.0.0.1:${port}`
|
|
53
|
+
process.env.TSC_REGISTRY_API = `http://127.0.0.1:${port}/api`
|
|
54
|
+
|
|
55
|
+
const { default: ssrHandler, setHtmlContent } = await import(
|
|
56
|
+
"./api/generated-index.js"
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
server.middlewares.use(async (req, res, next) => {
|
|
60
|
+
const url = req.url?.split("?")[0] || ""
|
|
61
|
+
const accept = req.headers.accept || ""
|
|
62
|
+
|
|
63
|
+
if (url === "/" || url === "/landing.html") {
|
|
64
|
+
return next()
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (url.startsWith("/api/")) {
|
|
68
|
+
return next()
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!accept.includes("text/html")) {
|
|
72
|
+
return next()
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (extname(url)) {
|
|
76
|
+
return next()
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const patchedRes = res as ServerResponse & {
|
|
80
|
+
status?: (code: number) => ServerResponse
|
|
81
|
+
send?: (body: any) => void
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (!patchedRes.status) {
|
|
85
|
+
patchedRes.status = function (code: number) {
|
|
86
|
+
patchedRes.statusCode = code
|
|
87
|
+
return patchedRes
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (!patchedRes.send) {
|
|
92
|
+
patchedRes.send = function (body: any) {
|
|
93
|
+
if (Buffer.isBuffer(body) || typeof body === "string") {
|
|
94
|
+
patchedRes.end(body)
|
|
95
|
+
} else {
|
|
96
|
+
patchedRes.end(JSON.stringify(body))
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
let rawHtml = readFileSync(
|
|
103
|
+
path.resolve(__dirname, "index.html"),
|
|
104
|
+
"utf-8",
|
|
105
|
+
)
|
|
106
|
+
rawHtml = rawHtml.replace(
|
|
107
|
+
/src="\.\/src\/main\.tsx"/,
|
|
108
|
+
'src="/src/main.tsx"',
|
|
109
|
+
)
|
|
110
|
+
const transformed = await server.transformIndexHtml(url, rawHtml)
|
|
111
|
+
setHtmlContent(transformed)
|
|
112
|
+
await ssrHandler(req as IncomingMessage, patchedRes)
|
|
113
|
+
} catch (err) {
|
|
114
|
+
console.error("SSR handler error", err)
|
|
115
|
+
next()
|
|
116
|
+
}
|
|
117
|
+
})
|
|
118
|
+
},
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function buildIdPlugin(): Plugin {
|
|
123
|
+
return {
|
|
124
|
+
name: "build-id",
|
|
125
|
+
transformIndexHtml(html) {
|
|
126
|
+
try {
|
|
127
|
+
const hash = execSync("git rev-parse --short HEAD").toString().trim()
|
|
128
|
+
return html.replace(
|
|
129
|
+
/<\/head>/i,
|
|
130
|
+
` <meta name="tscircuit-build" content="${hash}"></head>`,
|
|
131
|
+
)
|
|
132
|
+
} catch {
|
|
133
|
+
return html
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
42
139
|
export default defineConfig(async (): Promise<UserConfig> => {
|
|
43
140
|
let proxyConfig: Record<string, any> | undefined
|
|
44
141
|
|
|
@@ -71,6 +168,8 @@ export default defineConfig(async (): Promise<UserConfig> => {
|
|
|
71
168
|
effort: 6,
|
|
72
169
|
},
|
|
73
170
|
}),
|
|
171
|
+
vercelSsrDevPlugin(),
|
|
172
|
+
buildIdPlugin(),
|
|
74
173
|
]
|
|
75
174
|
|
|
76
175
|
if (process.env.VITE_BUNDLE_ANALYZE === "true" || 1) {
|