@tscircuit/fake-snippets 0.0.5 → 0.0.7
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/bun-tests/fake-snippets-api/routes/package_files/create.test.ts +375 -0
- package/bun-tests/fake-snippets-api/routes/package_files/download.test.ts +248 -0
- package/bun-tests/fake-snippets-api/routes/package_files/get.test.ts +220 -0
- package/bun-tests/fake-snippets-api/routes/package_files/list.test.ts +204 -0
- package/bun-tests/fake-snippets-api/routes/package_releases/get.test.ts +0 -1
- package/bun.lock +97 -73
- package/dist/bundle.js +602 -213
- package/fake-snippets-api/lib/db/db-client.ts +13 -0
- package/fake-snippets-api/lib/package_file/get-package-file-id-from-file-descriptor.ts +168 -0
- package/fake-snippets-api/lib/package_release/find-package-release-id.ts +122 -0
- package/fake-snippets-api/routes/api/package_files/create.ts +132 -0
- package/fake-snippets-api/routes/api/package_files/download.ts +70 -153
- package/fake-snippets-api/routes/api/package_files/get.ts +24 -5
- package/fake-snippets-api/routes/api/package_files/list.ts +16 -28
- package/fake-snippets-api/routes/api/package_releases/get.ts +21 -1
- package/index.html +113 -20
- package/package.json +9 -9
- package/playwright-tests/profile-page.spec.ts +59 -0
- package/playwright-tests/snapshots/profile-page.spec.ts-profile-page-before-delete.png +0 -0
- package/playwright-tests/snapshots/profile-page.spec.ts-profile-page-delete-dialog.png +0 -0
- package/playwright-tests/snapshots/profile-page.spec.ts-profile-page-dropdown-open.png +0 -0
- package/scripts/generate-image-sizes.ts +0 -1
- package/scripts/generate_bundle_stats.js +22 -6
- package/src/App.tsx +12 -1
- package/src/components/AiChatInterface.tsx +8 -0
- package/src/components/Analytics.tsx +1 -1
- package/src/components/CodeAndPreview.tsx +9 -3
- package/src/components/CreateNewSnippetWithAiHero.tsx +6 -2
- package/src/components/EditorNav.tsx +4 -0
- package/src/components/Footer.tsx +1 -1
- package/src/components/Header.tsx +7 -10
- package/src/components/HeaderLogin.tsx +1 -1
- package/src/components/PreviewContent.tsx +4 -1
- package/src/components/SnippetList.tsx +71 -0
- package/src/lib/templates/blinking-led-board-template.ts +2 -1
- package/src/pages/dashboard.tsx +19 -44
- package/src/pages/editor.tsx +1 -1
- package/src/pages/landing.tsx +8 -16
- package/src/pages/quickstart.tsx +9 -9
- package/src/pages/user-profile.tsx +50 -3
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
import { withRouteSpec } from "fake-snippets-api/lib/with-winter-spec"
|
|
2
2
|
import { z } from "zod"
|
|
3
3
|
import * as ZT from "fake-snippets-api/lib/db/schema"
|
|
4
|
-
import {
|
|
4
|
+
import { getPackageFileIdFromFileDescriptor } from "fake-snippets-api/lib/package_file/get-package-file-id-from-file-descriptor"
|
|
5
5
|
|
|
6
6
|
const routeSpec = {
|
|
7
7
|
methods: ["POST"],
|
|
8
8
|
auth: "none",
|
|
9
9
|
jsonBody: z
|
|
10
10
|
.object({
|
|
11
|
-
package_file_id: z.string()
|
|
11
|
+
package_file_id: z.string(),
|
|
12
12
|
})
|
|
13
13
|
.or(
|
|
14
14
|
z.object({
|
|
15
|
-
package_release_id: z.string()
|
|
15
|
+
package_release_id: z.string(),
|
|
16
16
|
file_path: z.string(),
|
|
17
17
|
}),
|
|
18
18
|
)
|
|
19
19
|
.or(
|
|
20
20
|
z.object({
|
|
21
|
-
package_id: z.string()
|
|
21
|
+
package_id: z.string(),
|
|
22
22
|
version: z.string().optional(),
|
|
23
23
|
file_path: z.string(),
|
|
24
24
|
}),
|
|
@@ -45,8 +45,27 @@ const routeSpec = {
|
|
|
45
45
|
} as const
|
|
46
46
|
|
|
47
47
|
export default withRouteSpec(routeSpec)(async (req, ctx) => {
|
|
48
|
+
const packageFileId = await getPackageFileIdFromFileDescriptor(
|
|
49
|
+
req.jsonBody,
|
|
50
|
+
ctx,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
const packageFile = ctx.db.packageFiles.find(
|
|
54
|
+
(pf: ZT.PackageFile) => pf.package_file_id === packageFileId,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
if (!packageFile) {
|
|
58
|
+
return ctx.error(404, {
|
|
59
|
+
error_code: "package_file_not_found",
|
|
60
|
+
message: "Package file not found",
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
|
|
48
64
|
return ctx.json({
|
|
49
65
|
ok: true,
|
|
50
|
-
package_file:
|
|
66
|
+
package_file: {
|
|
67
|
+
...packageFile,
|
|
68
|
+
created_at: packageFile.created_at.toString(),
|
|
69
|
+
},
|
|
51
70
|
})
|
|
52
71
|
})
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { withRouteSpec } from "fake-snippets-api/lib/with-winter-spec"
|
|
2
2
|
import { z } from "zod"
|
|
3
3
|
import * as ZT from "fake-snippets-api/lib/db/schema"
|
|
4
|
+
import { findPackageReleaseId } from "fake-snippets-api/lib/package_release/find-package-release-id"
|
|
4
5
|
|
|
5
6
|
const routeSpec = {
|
|
6
7
|
methods: ["POST"],
|
|
7
8
|
auth: "none",
|
|
8
9
|
jsonBody: z
|
|
9
10
|
.object({
|
|
10
|
-
package_release_id: z.string()
|
|
11
|
+
package_release_id: z.string(),
|
|
11
12
|
})
|
|
12
13
|
.or(
|
|
13
14
|
z.object({
|
|
@@ -27,34 +28,21 @@ const routeSpec = {
|
|
|
27
28
|
} as const
|
|
28
29
|
|
|
29
30
|
export default withRouteSpec(routeSpec)(async (req, ctx) => {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
// "file_path",
|
|
44
|
-
// "created_at",
|
|
45
|
-
// ])
|
|
46
|
-
// .where("package_release_id", "=", package_release_id)
|
|
47
|
-
// .where("file_path", "not like", ".tscircuit-internal/%")
|
|
48
|
-
// .execute()
|
|
49
|
-
// return ctx.json({
|
|
50
|
-
// ok: true,
|
|
51
|
-
// package_files: package_files.map((pf) => ({
|
|
52
|
-
// ...pf,
|
|
53
|
-
// created_at: pf.created_at.toISOString(),
|
|
54
|
-
// })),
|
|
55
|
-
// })
|
|
31
|
+
const packageReleaseId = await findPackageReleaseId(req.jsonBody, ctx)
|
|
32
|
+
|
|
33
|
+
if (!packageReleaseId) {
|
|
34
|
+
return ctx.error(404, {
|
|
35
|
+
error_code: "package_release_not_found",
|
|
36
|
+
message: "Package release not found",
|
|
37
|
+
})
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const packageFiles = ctx.db.packageFiles.filter(
|
|
41
|
+
(file) => file.package_release_id === packageReleaseId,
|
|
42
|
+
)
|
|
43
|
+
|
|
56
44
|
return ctx.json({
|
|
57
45
|
ok: true,
|
|
58
|
-
package_files:
|
|
46
|
+
package_files: packageFiles,
|
|
59
47
|
})
|
|
60
48
|
})
|
|
@@ -15,7 +15,27 @@ export default withRouteSpec({
|
|
|
15
15
|
package_release: zt.packageReleaseSchema,
|
|
16
16
|
}),
|
|
17
17
|
})(async (req, ctx) => {
|
|
18
|
-
const { package_release_id } = req.jsonBody
|
|
18
|
+
const { package_release_id, package_name_with_version } = req.jsonBody
|
|
19
|
+
|
|
20
|
+
if (package_name_with_version && !package_release_id) {
|
|
21
|
+
const [packageName, parsedVersion] = package_name_with_version.split("@")
|
|
22
|
+
const pkg = ctx.db.packages.find((x) => x.name === packageName)
|
|
23
|
+
const pkgRelease = ctx.db.packageReleases.find((x) => {
|
|
24
|
+
return x.version == parsedVersion && x.package_id == pkg?.package_id
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
if (!pkgRelease) {
|
|
28
|
+
return ctx.error(404, {
|
|
29
|
+
error_code: "package_release_not_found",
|
|
30
|
+
message: "Package release not found",
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return ctx.json({
|
|
35
|
+
ok: true,
|
|
36
|
+
package_release: publicMapPackageRelease(pkgRelease),
|
|
37
|
+
})
|
|
38
|
+
}
|
|
19
39
|
|
|
20
40
|
const foundRelease =
|
|
21
41
|
package_release_id && ctx.db.getPackageReleaseById(package_release_id)
|
package/index.html
CHANGED
|
@@ -1,23 +1,116 @@
|
|
|
1
1
|
<!doctype html>
|
|
2
2
|
<html lang="en">
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
<
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8" />
|
|
6
|
+
<meta name="robots" content="index, follow, NOODP" />
|
|
7
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
8
|
+
<link rel="icon" href="/favicon.ico" type="image/x-icon" sizes="16x16" />
|
|
9
|
+
<title>tscircuit - Code Electronics with React</title>
|
|
10
|
+
<meta name="description"
|
|
11
|
+
content="tscircuit is an open-source electronics design tool that lets you create circuits using React components. Design schematics, generate PCB layouts, export and manufacture PCBs online!" />
|
|
12
|
+
<meta name="keywords"
|
|
13
|
+
content="electronic design, PCB design, schematic capture, React components, circuit design, electronics CAD, open source EDA" />
|
|
14
|
+
<meta property="og:title" content="tscircuit - Design Electronics with React Components" />
|
|
15
|
+
<meta property="og:description"
|
|
16
|
+
content="Create electronic circuits using React components. Design schematics, generate PCB layouts, and manufacture custom PCBs with this free open-source tool." />
|
|
17
|
+
<meta property="og:type" content="website" />
|
|
18
|
+
<meta name="twitter:card" content="summary_large_image" />
|
|
19
|
+
<meta name="twitter:title" content="tscircuit - Design Electronics with React Components" />
|
|
20
|
+
<meta name="twitter:description"
|
|
21
|
+
content="Create electronic circuits using React components. Free open-source electronics design tool." />
|
|
22
|
+
<link rel="canonical" href="https://tscircuit.com" />
|
|
23
|
+
|
|
24
|
+
<style>
|
|
25
|
+
/* Loader Styles */
|
|
26
|
+
.loading-overlay {
|
|
27
|
+
position: fixed;
|
|
28
|
+
top: 0;
|
|
29
|
+
left: 0;
|
|
30
|
+
width: 100%;
|
|
31
|
+
height: 100%;
|
|
32
|
+
background: rgba(255, 255, 255, 0.7);
|
|
33
|
+
display: flex;
|
|
34
|
+
justify-content: center;
|
|
35
|
+
align-items: center;
|
|
36
|
+
z-index: 9999;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.loading-container {
|
|
40
|
+
display: flex;
|
|
41
|
+
flex-direction: column;
|
|
42
|
+
align-items: center;
|
|
43
|
+
gap: 1em;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.loading {
|
|
47
|
+
background-color: lightgrey;
|
|
48
|
+
height: 2px;
|
|
49
|
+
overflow: hidden;
|
|
50
|
+
position: relative;
|
|
51
|
+
width: 12em;
|
|
52
|
+
border-radius: 2px;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.loading-bar {
|
|
56
|
+
animation: side2side 2s ease-in-out infinite;
|
|
57
|
+
background-color: dodgerblue;
|
|
58
|
+
height: 100%;
|
|
59
|
+
position: absolute;
|
|
60
|
+
width: 45%;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@keyframes side2side {
|
|
64
|
+
0%, 100% {
|
|
65
|
+
transform: translateX(-50%);
|
|
66
|
+
}
|
|
67
|
+
50% {
|
|
68
|
+
transform: translateX(150%);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
</style>
|
|
72
|
+
|
|
73
|
+
<script>
|
|
74
|
+
window.onload = function() {
|
|
75
|
+
const loader = document.getElementById("loader");
|
|
76
|
+
const root = document.getElementById("root");
|
|
77
|
+
|
|
78
|
+
// Hide the loader and show the root when the page has fully loaded
|
|
79
|
+
loader.style.transition = "opacity 0.3s ease";
|
|
80
|
+
loader.style.opacity = "0"; // Fade out the loader
|
|
81
|
+
setTimeout(function() {
|
|
82
|
+
loader.style.display = "none"; // Hide loader completely
|
|
83
|
+
root.style.visibility = "visible"; // Show the root content
|
|
84
|
+
}, 300); // Match the opacity transition duration
|
|
85
|
+
};
|
|
86
|
+
</script>
|
|
87
|
+
|
|
88
|
+
<!-- Google tag (gtag.js) -->
|
|
89
|
+
<script async src="https://www.googletagmanager.com/gtag/js?id=AW-16607843883">
|
|
90
|
+
</script>
|
|
91
|
+
<script>
|
|
92
|
+
window.dataLayer = window.dataLayer || [];
|
|
93
|
+
function gtag(){dataLayer.push(arguments);}
|
|
94
|
+
gtag('js', new Date());
|
|
95
|
+
|
|
96
|
+
gtag('config', 'AW-16607843883');
|
|
97
|
+
</script>
|
|
98
|
+
</head>
|
|
99
|
+
|
|
100
|
+
<body>
|
|
101
|
+
<div class="loaderanimation">
|
|
102
|
+
<div id="loader" class="loading-overlay">
|
|
103
|
+
<div class="loading-container">
|
|
104
|
+
<div class="loading">
|
|
105
|
+
<div class="loading-bar"></div>
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
<div id="root" class="loaderanimation" style="visibility: hidden;"></div> <!-- Initially hidden -->
|
|
112
|
+
|
|
113
|
+
<script type="module" src="./src/main.tsx"></script>
|
|
114
|
+
</body>
|
|
115
|
+
|
|
23
116
|
</html>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tscircuit/fake-snippets",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -61,13 +61,13 @@
|
|
|
61
61
|
"@radix-ui/react-toggle": "^1.1.0",
|
|
62
62
|
"@radix-ui/react-toggle-group": "^1.1.0",
|
|
63
63
|
"@radix-ui/react-tooltip": "^1.1.2",
|
|
64
|
-
"@tscircuit/3d-viewer": "^0.0.
|
|
65
|
-
"@tscircuit/footprinter": "^0.0.
|
|
64
|
+
"@tscircuit/3d-viewer": "^0.0.142",
|
|
65
|
+
"@tscircuit/footprinter": "^0.0.124",
|
|
66
66
|
"@tscircuit/layout": "^0.0.29",
|
|
67
67
|
"@tscircuit/math-utils": "^0.0.10",
|
|
68
68
|
"@tscircuit/mm": "^0.0.8",
|
|
69
69
|
"@tscircuit/pcb-viewer": "^1.11.12",
|
|
70
|
-
"@tscircuit/props": "^0.0.
|
|
70
|
+
"@tscircuit/props": "^0.0.143",
|
|
71
71
|
"@tscircuit/schematic-viewer": "^1.4.3",
|
|
72
72
|
"@types/file-saver": "^2.0.7",
|
|
73
73
|
"@types/ms": "^0.7.34",
|
|
@@ -76,12 +76,12 @@
|
|
|
76
76
|
"@valtown/codemirror-ts": "^2.2.0",
|
|
77
77
|
"@vercel/analytics": "^1.4.1",
|
|
78
78
|
"change-case": "^5.4.4",
|
|
79
|
-
"circuit-json": "^0.0.
|
|
79
|
+
"circuit-json": "^0.0.136",
|
|
80
80
|
"circuit-json-to-bom-csv": "^0.0.6",
|
|
81
81
|
"circuit-json-to-gerber": "^0.0.16",
|
|
82
82
|
"circuit-json-to-pnp-csv": "^0.0.6",
|
|
83
|
+
"circuit-json-to-readable-netlist": "^0.0.8",
|
|
83
84
|
"circuit-json-to-tscircuit": "^0.0.4",
|
|
84
|
-
"circuit-json-to-readable-netlist": "^0.0.7",
|
|
85
85
|
"class-variance-authority": "^0.7.0",
|
|
86
86
|
"clsx": "^2.1.1",
|
|
87
87
|
"cmdk": "^1.0.4",
|
|
@@ -97,7 +97,7 @@
|
|
|
97
97
|
"immer": "^10.1.1",
|
|
98
98
|
"input-otp": "^1.2.4",
|
|
99
99
|
"jose": "^5.9.3",
|
|
100
|
-
"jscad-electronics": "^0.0.
|
|
100
|
+
"jscad-electronics": "^0.0.25",
|
|
101
101
|
"jszip": "^3.10.1",
|
|
102
102
|
"kicad-converter": "^0.0.16",
|
|
103
103
|
"lucide-react": "^0.445.0",
|
|
@@ -130,9 +130,9 @@
|
|
|
130
130
|
"@babel/standalone": "^7.26.2",
|
|
131
131
|
"@biomejs/biome": "^1.9.2",
|
|
132
132
|
"@playwright/test": "^1.48.0",
|
|
133
|
-
"@tscircuit/core": "^0.0.
|
|
133
|
+
"@tscircuit/core": "^0.0.315",
|
|
134
134
|
"@tscircuit/prompt-benchmarks": "^0.0.28",
|
|
135
|
-
"@tscircuit/runframe": "^0.0.
|
|
135
|
+
"@tscircuit/runframe": "^0.0.193",
|
|
136
136
|
"@types/babel__standalone": "^7.1.7",
|
|
137
137
|
"@types/bun": "^1.1.10",
|
|
138
138
|
"@types/country-list": "^2.1.4",
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { test, expect } from "@playwright/test"
|
|
2
|
+
|
|
3
|
+
test("test delete functionality in profile", async ({ page }) => {
|
|
4
|
+
// Go to profile page
|
|
5
|
+
await page.goto("http://localhost:5177/testuser")
|
|
6
|
+
|
|
7
|
+
// Login
|
|
8
|
+
await page.getByRole("button", { name: "Log in" }).click()
|
|
9
|
+
|
|
10
|
+
// Wait for snippets grid to load
|
|
11
|
+
await page.waitForSelector(".grid")
|
|
12
|
+
await page.waitForLoadState("networkidle")
|
|
13
|
+
|
|
14
|
+
// Verify initial snippet exists
|
|
15
|
+
const snippetTitle = page.locator(".text-md.font-semibold").first()
|
|
16
|
+
const snippetName = await snippetTitle.textContent()
|
|
17
|
+
expect(await snippetTitle.isVisible()).toBe(true)
|
|
18
|
+
|
|
19
|
+
// Take screenshot of initial state
|
|
20
|
+
await expect(page).toHaveScreenshot("profile-page-before-delete.png")
|
|
21
|
+
|
|
22
|
+
// Open dropdown menu
|
|
23
|
+
await page.locator(".lucide-ellipsis-vertical").first().click()
|
|
24
|
+
await page.waitForTimeout(1000)
|
|
25
|
+
|
|
26
|
+
// Take screenshot with dropdown open
|
|
27
|
+
await expect(page).toHaveScreenshot("profile-page-dropdown-open.png")
|
|
28
|
+
|
|
29
|
+
// Click delete option
|
|
30
|
+
await page.getByRole("menuitem", { name: "Delete Snippet" }).click()
|
|
31
|
+
|
|
32
|
+
// Wait for and verify confirmation dialog
|
|
33
|
+
const dialog = page.locator('[role="dialog"]')
|
|
34
|
+
await expect(dialog).toBeVisible()
|
|
35
|
+
const dialogText = await dialog.textContent()
|
|
36
|
+
expect(dialogText).toContain(
|
|
37
|
+
`Are you sure you want to delete the snippet "${snippetName}"?`,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
// Take screenshot of delete dialog
|
|
41
|
+
await expect(page).toHaveScreenshot("profile-page-delete-dialog.png")
|
|
42
|
+
|
|
43
|
+
// Confirm delete
|
|
44
|
+
await page.getByRole("button", { name: "Delete" }).click()
|
|
45
|
+
|
|
46
|
+
// Verify success toast appears
|
|
47
|
+
await page.waitForSelector('div:has-text("Successfully deleted")', {
|
|
48
|
+
state: "visible",
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
// Wait for page to update
|
|
52
|
+
await page.waitForLoadState("networkidle")
|
|
53
|
+
|
|
54
|
+
// Verify snippet is removed
|
|
55
|
+
const remainingSnippets = await page
|
|
56
|
+
.locator(`.text-md.font-semibold:has-text("${snippetName}")`)
|
|
57
|
+
.count()
|
|
58
|
+
expect(remainingSnippets).toBe(0)
|
|
59
|
+
})
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -82,11 +82,22 @@ function compareSizes(prData, mainData, dependencies) {
|
|
|
82
82
|
const mainSize = mainStats.depStats[dep]?.size || 0
|
|
83
83
|
const diff = prSize - mainSize
|
|
84
84
|
|
|
85
|
+
let percentChange = "N/A"
|
|
86
|
+
if (prSize === 0 && mainSize > 0) {
|
|
87
|
+
percentChange = "Removed"
|
|
88
|
+
} else if (mainSize !== 0) {
|
|
89
|
+
percentChange = (diff / mainSize) * 100
|
|
90
|
+
} else if (prSize > 0 && mainSize === 0) {
|
|
91
|
+
percentChange = "Added"
|
|
92
|
+
} else {
|
|
93
|
+
percentChange = 0
|
|
94
|
+
}
|
|
95
|
+
|
|
85
96
|
diffStats[dep] = {
|
|
86
97
|
before: mainSize,
|
|
87
98
|
after: prSize,
|
|
88
99
|
diff,
|
|
89
|
-
percentChange
|
|
100
|
+
percentChange,
|
|
90
101
|
}
|
|
91
102
|
})
|
|
92
103
|
|
|
@@ -95,7 +106,10 @@ function compareSizes(prData, mainData, dependencies) {
|
|
|
95
106
|
totalAfter: prStats.totalSize,
|
|
96
107
|
totalDiff: prStats.totalSize - mainStats.totalSize,
|
|
97
108
|
totalPercentChange:
|
|
98
|
-
|
|
109
|
+
mainStats.totalSize !== 0
|
|
110
|
+
? ((prStats.totalSize - mainStats.totalSize) / mainStats.totalSize) *
|
|
111
|
+
100
|
|
112
|
+
: 0,
|
|
99
113
|
diffStats,
|
|
100
114
|
}
|
|
101
115
|
}
|
|
@@ -107,7 +121,7 @@ function generateDiffMarkdown(prData, mainData, dependencies) {
|
|
|
107
121
|
markdown += `## Total Bundle Size\n\n`
|
|
108
122
|
markdown += `- Before: **${formatBytes(comparison.totalBefore)}**\n`
|
|
109
123
|
markdown += `- After: **${formatBytes(comparison.totalAfter)}**\n`
|
|
110
|
-
markdown += `- Change: ${totalDiffSymbol} **${formatBytes(Math.abs(comparison.totalDiff))}** (${comparison.totalPercentChange.toFixed(2)}%)\n\n`
|
|
124
|
+
markdown += `- Change: ${totalDiffSymbol} **${formatBytes(Math.abs(comparison.totalDiff))}** (${isNaN(comparison.totalPercentChange) ? "N/A" : comparison.totalPercentChange.toFixed(2)}%)\n\n`
|
|
111
125
|
|
|
112
126
|
markdown += `## Diff\n\n`
|
|
113
127
|
|
|
@@ -117,7 +131,10 @@ function generateDiffMarkdown(prData, mainData, dependencies) {
|
|
|
117
131
|
|
|
118
132
|
const significantChanges = sortedDiffs.filter(
|
|
119
133
|
([, stats]) =>
|
|
120
|
-
|
|
134
|
+
(typeof stats.percentChange === "number" &&
|
|
135
|
+
(Math.abs(stats.percentChange) > 1 || Math.abs(stats.diff) > 1024)) ||
|
|
136
|
+
stats.percentChange === "Added" ||
|
|
137
|
+
stats.percentChange === "Removed",
|
|
121
138
|
)
|
|
122
139
|
|
|
123
140
|
if (significantChanges.length > 0) {
|
|
@@ -127,8 +144,7 @@ function generateDiffMarkdown(prData, mainData, dependencies) {
|
|
|
127
144
|
for (const [name, stats] of significantChanges) {
|
|
128
145
|
const version = dependencies[name]
|
|
129
146
|
const symbol = stats.diff > 0 ? "📈" : "📉"
|
|
130
|
-
markdown += `| ${name}@${version} | ${formatBytes(stats.before)} | ${formatBytes(stats.after)} | ${symbol} ${formatBytes(Math.abs(stats.diff))} |
|
|
131
|
-
${stats.percentChange.toFixed(2)}% |\n`
|
|
147
|
+
markdown += `| ${name}@${version} | ${formatBytes(stats.before)} | ${formatBytes(stats.after)} | ${symbol} ${formatBytes(Math.abs(stats.diff))} | ${typeof stats.percentChange === "number" ? stats.percentChange.toFixed(2) + "%" : stats.percentChange} |\n`
|
|
132
148
|
}
|
|
133
149
|
} else {
|
|
134
150
|
markdown += "No significant changes in bundle size.\n"
|
package/src/App.tsx
CHANGED
|
@@ -5,9 +5,20 @@ import "./components/CmdKMenu"
|
|
|
5
5
|
import { ContextProviders } from "./ContextProviders"
|
|
6
6
|
import React from "react"
|
|
7
7
|
|
|
8
|
+
const FullPageLoader = () => (
|
|
9
|
+
<div className="fixed inset-0 flex items-center justify-center bg-white z-50">
|
|
10
|
+
<div className="w-48">
|
|
11
|
+
<div className="loading">
|
|
12
|
+
<div className="loading-bar"></div>
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
)
|
|
17
|
+
|
|
8
18
|
const lazyImport = (importFn: () => Promise<any>) =>
|
|
9
19
|
lazy<ComponentType<any>>(async () => {
|
|
10
20
|
try {
|
|
21
|
+
await new Promise((resolve) => setTimeout(resolve, 500))
|
|
11
22
|
const module = await importFn()
|
|
12
23
|
|
|
13
24
|
if (module.default) {
|
|
@@ -85,7 +96,7 @@ function App() {
|
|
|
85
96
|
return (
|
|
86
97
|
<ContextProviders>
|
|
87
98
|
<ErrorBoundary>
|
|
88
|
-
<Suspense fallback={<
|
|
99
|
+
<Suspense fallback={<FullPageLoader />}>
|
|
89
100
|
<Switch>
|
|
90
101
|
<Route path="/" component={LandingPage} />
|
|
91
102
|
<Route path="/editor" component={EditorPage} />
|
|
@@ -165,6 +165,14 @@ export default function AIChatInterface({
|
|
|
165
165
|
{messages.length === 0 && isLoggedIn && (
|
|
166
166
|
<div className="text-gray-500 text-xl text-center pt-[30vh] flex flex-col items-center">
|
|
167
167
|
<div>Submit a prompt to {snippet ? "edit!" : "get started!"}</div>
|
|
168
|
+
<div className="mt-2">
|
|
169
|
+
This is our legacy AI chat interface. For a better experience,
|
|
170
|
+
please use{" "}
|
|
171
|
+
<PrefetchPageLink href="https://chat.tscircuit.com">
|
|
172
|
+
chat.tscircuit.com
|
|
173
|
+
</PrefetchPageLink>
|
|
174
|
+
.
|
|
175
|
+
</div>
|
|
168
176
|
<div className="text-6xl mt-4">↓</div>
|
|
169
177
|
</div>
|
|
170
178
|
)}
|
|
@@ -3,7 +3,7 @@ import posthog from "posthog-js"
|
|
|
3
3
|
import CookieConsent from "react-cookie-consent"
|
|
4
4
|
|
|
5
5
|
posthog.init("phc_htd8AQjSfVEsFCLQMAiUooG4Q0DKBCjqYuQglc9V3Wo", {
|
|
6
|
-
api_host: "https://
|
|
6
|
+
api_host: "https://postpig.tscircuit.com",
|
|
7
7
|
person_profiles: "always",
|
|
8
8
|
})
|
|
9
9
|
|
|
@@ -166,6 +166,15 @@ export function CodeAndPreview({ snippet }: Props) {
|
|
|
166
166
|
const [lastSavedAt, setLastSavedAt] = useState(Date.now())
|
|
167
167
|
|
|
168
168
|
const handleSave = async () => {
|
|
169
|
+
if (hasUnrunChanges) {
|
|
170
|
+
toast({
|
|
171
|
+
title: "Warning",
|
|
172
|
+
description: "You must run the snippet before saving your changes.",
|
|
173
|
+
variant: "destructive",
|
|
174
|
+
})
|
|
175
|
+
return
|
|
176
|
+
}
|
|
177
|
+
|
|
169
178
|
setLastSavedAt(Date.now())
|
|
170
179
|
if (snippet) {
|
|
171
180
|
updateSnippetMutation.mutate()
|
|
@@ -197,8 +206,6 @@ export function CodeAndPreview({ snippet }: Props) {
|
|
|
197
206
|
...(code.match(/export const (\w+) ?=/)?.slice(1) ?? []),
|
|
198
207
|
]
|
|
199
208
|
|
|
200
|
-
console.log(possibleExportNames)
|
|
201
|
-
|
|
202
209
|
const exportName = possibleExportNames[0]
|
|
203
210
|
|
|
204
211
|
let entrypointContent: string
|
|
@@ -224,7 +231,6 @@ export function CodeAndPreview({ snippet }: Props) {
|
|
|
224
231
|
"main.tsx": entrypointContent,
|
|
225
232
|
}
|
|
226
233
|
}, [code, manualEditsFileContent])
|
|
227
|
-
console.log(fsMap)
|
|
228
234
|
|
|
229
235
|
if (!snippet && (urlParams.snippet_id || urlParams.should_create_snippet)) {
|
|
230
236
|
return (
|
|
@@ -12,12 +12,16 @@ export function CreateNewSnippetWithAiHero() {
|
|
|
12
12
|
const handleSubmit = (e: React.FormEvent) => {
|
|
13
13
|
e.preventDefault()
|
|
14
14
|
if (inputValue.trim()) {
|
|
15
|
-
navigate(
|
|
15
|
+
navigate(
|
|
16
|
+
`https://chat.tscircuit.com?initial_prompt=${encodeURIComponent(inputValue)}`,
|
|
17
|
+
)
|
|
16
18
|
}
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
const handleQuickPrompt = (prompt: string) => {
|
|
20
|
-
navigate(
|
|
22
|
+
navigate(
|
|
23
|
+
`https://chat.tscircuit.com?initial_prompt=${encodeURIComponent(prompt)}`,
|
|
24
|
+
)
|
|
21
25
|
}
|
|
22
26
|
|
|
23
27
|
return (
|
|
@@ -48,6 +48,7 @@ import { SnippetLink } from "./SnippetLink"
|
|
|
48
48
|
import { TypeBadge } from "./TypeBadge"
|
|
49
49
|
import { useUpdateDescriptionDialog } from "./dialogs/edit-description-dialog"
|
|
50
50
|
import { useForkSnippetMutation } from "@/hooks/useForkSnippetMutation"
|
|
51
|
+
import tscircuitCorePkg from "@tscircuit/core/package.json"
|
|
51
52
|
|
|
52
53
|
export default function EditorNav({
|
|
53
54
|
circuitJson,
|
|
@@ -367,6 +368,9 @@ export default function EditorNav({
|
|
|
367
368
|
<Trash2 className="mr-2 h-3 w-3" />
|
|
368
369
|
Delete Snippet
|
|
369
370
|
</DropdownMenuItem>
|
|
371
|
+
<DropdownMenuItem className="text-xs text-gray-500" disabled>
|
|
372
|
+
@tscircuit/core@{tscircuitCorePkg.version}
|
|
373
|
+
</DropdownMenuItem>
|
|
370
374
|
</DropdownMenuContent>
|
|
371
375
|
</DropdownMenu>
|
|
372
376
|
<Button
|
|
@@ -26,7 +26,7 @@ export default function Footer() {
|
|
|
26
26
|
{ name: "Home", href: "/" },
|
|
27
27
|
{ name: "Dashboard", href: "/dashboard" },
|
|
28
28
|
{ name: "Editor", href: "/editor" },
|
|
29
|
-
{ name: "Create with AI", href: "
|
|
29
|
+
{ name: "Create with AI", href: "https://chat.tscircuit.com" },
|
|
30
30
|
{
|
|
31
31
|
name: "My Profile",
|
|
32
32
|
href: `/${session?.github_username}`,
|