@tscircuit/fake-snippets 0.0.4 → 0.0.6
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_releases/create.test.ts +131 -0
- package/bun-tests/fake-snippets-api/routes/package_releases/get.test.ts +89 -0
- package/bun-tests/fake-snippets-api/routes/package_releases/list.test.ts +157 -0
- package/bun-tests/fake-snippets-api/routes/package_releases/update.test.ts +189 -0
- package/bun.lock +16 -2
- package/dist/bundle.js +421 -132
- package/fake-snippets-api/lib/db/db-client.ts +30 -0
- package/fake-snippets-api/lib/db/schema.ts +2 -0
- package/fake-snippets-api/lib/public-mapping/public-map-package-release.ts +10 -0
- package/fake-snippets-api/routes/api/package_releases/create.ts +83 -0
- package/fake-snippets-api/routes/api/package_releases/get.ts +54 -0
- package/fake-snippets-api/routes/api/package_releases/list.ts +77 -0
- package/fake-snippets-api/routes/api/package_releases/update.ts +96 -0
- package/index.html +103 -21
- package/package.json +3 -1
- package/playwright-tests/circuit-json-import.spec.ts +133 -0
- package/playwright-tests/exampleCircuitJson.ts +498 -0
- package/src/App.tsx +12 -1
- package/src/components/CircuitJsonImportDialog.tsx +186 -0
- package/src/pages/quickstart.tsx +24 -0
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
packageReleaseSchema,
|
|
18
18
|
packageSchema,
|
|
19
19
|
Package,
|
|
20
|
+
PackageRelease,
|
|
20
21
|
} from "./schema.ts"
|
|
21
22
|
import { combine } from "zustand/middleware"
|
|
22
23
|
import { seed as seedFn } from "./seed"
|
|
@@ -387,4 +388,33 @@ const initializer = combine(databaseSchema.parse({}), (set, get) => ({
|
|
|
387
388
|
...pkg,
|
|
388
389
|
}
|
|
389
390
|
},
|
|
391
|
+
getPackageReleaseById: (
|
|
392
|
+
package_release_id: string,
|
|
393
|
+
): PackageRelease | undefined => {
|
|
394
|
+
const state = get()
|
|
395
|
+
return state.packageReleases.find(
|
|
396
|
+
(pr) => pr.package_release_id === package_release_id,
|
|
397
|
+
)
|
|
398
|
+
},
|
|
399
|
+
addPackageRelease: (
|
|
400
|
+
packageRelease: Omit<PackageRelease, "package_release_id">,
|
|
401
|
+
): PackageRelease => {
|
|
402
|
+
const newPackageRelease = {
|
|
403
|
+
package_release_id: `package_release_${Date.now()}`,
|
|
404
|
+
...packageRelease,
|
|
405
|
+
}
|
|
406
|
+
set((state) => ({
|
|
407
|
+
packageReleases: [...state.packageReleases, newPackageRelease],
|
|
408
|
+
}))
|
|
409
|
+
return newPackageRelease
|
|
410
|
+
},
|
|
411
|
+
updatePackageRelease: (packageRelease: PackageRelease): void => {
|
|
412
|
+
set((state) => ({
|
|
413
|
+
packageReleases: state.packageReleases.map((pr) =>
|
|
414
|
+
pr.package_release_id === packageRelease.package_release_id
|
|
415
|
+
? packageRelease
|
|
416
|
+
: pr,
|
|
417
|
+
),
|
|
418
|
+
}))
|
|
419
|
+
},
|
|
390
420
|
}))
|
|
@@ -107,6 +107,8 @@ export const packageReleaseSchema = z.object({
|
|
|
107
107
|
is_locked: z.boolean(),
|
|
108
108
|
is_latest: z.boolean(),
|
|
109
109
|
created_at: z.string().datetime(),
|
|
110
|
+
commit_sha: z.string().nullable().optional(),
|
|
111
|
+
license: z.string().nullable().optional(),
|
|
110
112
|
})
|
|
111
113
|
export type PackageRelease = z.infer<typeof packageReleaseSchema>
|
|
112
114
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as ZT from "fake-snippets-api/lib/db/schema"
|
|
2
|
+
|
|
3
|
+
export const publicMapPackageRelease = (
|
|
4
|
+
internal_package_release: ZT.PackageRelease,
|
|
5
|
+
): ZT.PackageRelease => {
|
|
6
|
+
return {
|
|
7
|
+
...internal_package_release,
|
|
8
|
+
created_at: internal_package_release.created_at,
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { packageReleaseSchema } from "fake-snippets-api/lib/db/schema"
|
|
2
|
+
import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec"
|
|
3
|
+
import { publicMapPackageRelease } from "fake-snippets-api/lib/public-mapping/public-map-package-release"
|
|
4
|
+
import { z } from "zod"
|
|
5
|
+
|
|
6
|
+
export default withRouteSpec({
|
|
7
|
+
methods: ["POST"],
|
|
8
|
+
auth: "none",
|
|
9
|
+
jsonBody: z.object({
|
|
10
|
+
package_id: z.string().optional(),
|
|
11
|
+
version: z.string().optional(),
|
|
12
|
+
is_latest: z.boolean().optional(),
|
|
13
|
+
commit_sha: z.string().optional(),
|
|
14
|
+
package_name_with_version: z.string().optional(),
|
|
15
|
+
}),
|
|
16
|
+
jsonResponse: z.object({
|
|
17
|
+
ok: z.boolean(),
|
|
18
|
+
package_release: packageReleaseSchema,
|
|
19
|
+
}),
|
|
20
|
+
})(async (req, ctx) => {
|
|
21
|
+
let {
|
|
22
|
+
package_id,
|
|
23
|
+
is_latest = true,
|
|
24
|
+
version,
|
|
25
|
+
commit_sha,
|
|
26
|
+
package_name_with_version,
|
|
27
|
+
} = req.jsonBody
|
|
28
|
+
|
|
29
|
+
if (package_name_with_version && !version && !package_id) {
|
|
30
|
+
const [packageName, parsedVersion] = package_name_with_version.split("@")
|
|
31
|
+
const pkg = ctx.db.packages.find((p) => p.name === packageName)
|
|
32
|
+
|
|
33
|
+
if (!pkg) {
|
|
34
|
+
return ctx.error(404, {
|
|
35
|
+
error_code: "package_not_found",
|
|
36
|
+
message: `Package not found: ${packageName}`,
|
|
37
|
+
})
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
package_id = pkg.package_id
|
|
41
|
+
version = parsedVersion
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!package_id || !version) {
|
|
45
|
+
return ctx.error(400, {
|
|
46
|
+
error_code: "missing_options",
|
|
47
|
+
message: "package_id and version are required",
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Check if version already exists
|
|
52
|
+
const existingRelease = ctx.db.packageReleases.find(
|
|
53
|
+
(pr) => pr.package_id === package_id && pr.version === version,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
if (existingRelease) {
|
|
57
|
+
return ctx.error(400, {
|
|
58
|
+
error_code: "version_already_exists",
|
|
59
|
+
message: `Version ${version} already exists for this package`,
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Update previous latest if needed
|
|
64
|
+
if (is_latest) {
|
|
65
|
+
ctx.db.packageReleases
|
|
66
|
+
.filter((pr) => pr.package_id === package_id && pr.is_latest)
|
|
67
|
+
.forEach((pr) => (pr.is_latest = false))
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const newPackageRelease = ctx.db.addPackageRelease({
|
|
71
|
+
package_id,
|
|
72
|
+
is_latest,
|
|
73
|
+
version,
|
|
74
|
+
is_locked: false,
|
|
75
|
+
created_at: new Date().toISOString(),
|
|
76
|
+
commit_sha: commit_sha ?? null,
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
return ctx.json({
|
|
80
|
+
ok: true,
|
|
81
|
+
package_release: publicMapPackageRelease(newPackageRelease),
|
|
82
|
+
})
|
|
83
|
+
})
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import * as zt from "fake-snippets-api/lib/db/schema"
|
|
2
|
+
import { publicMapPackageRelease } from "fake-snippets-api/lib/public-mapping/public-map-package-release"
|
|
3
|
+
import { withRouteSpec } from "fake-snippets-api/lib/with-winter-spec"
|
|
4
|
+
import { z } from "zod"
|
|
5
|
+
|
|
6
|
+
export default withRouteSpec({
|
|
7
|
+
methods: ["POST"],
|
|
8
|
+
auth: "none",
|
|
9
|
+
jsonBody: z.object({
|
|
10
|
+
package_release_id: z.string().optional(),
|
|
11
|
+
package_name_with_version: z.string().optional(),
|
|
12
|
+
}),
|
|
13
|
+
jsonResponse: z.object({
|
|
14
|
+
ok: z.boolean(),
|
|
15
|
+
package_release: zt.packageReleaseSchema,
|
|
16
|
+
}),
|
|
17
|
+
})(async (req, ctx) => {
|
|
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
|
+
}
|
|
39
|
+
|
|
40
|
+
const foundRelease =
|
|
41
|
+
package_release_id && ctx.db.getPackageReleaseById(package_release_id)
|
|
42
|
+
|
|
43
|
+
if (!foundRelease) {
|
|
44
|
+
return ctx.error(404, {
|
|
45
|
+
error_code: "package_release_not_found",
|
|
46
|
+
message: "Package release not found",
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return ctx.json({
|
|
51
|
+
ok: true,
|
|
52
|
+
package_release: publicMapPackageRelease(foundRelease),
|
|
53
|
+
})
|
|
54
|
+
})
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { packageReleaseSchema } from "fake-snippets-api/lib/db/schema"
|
|
2
|
+
import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec"
|
|
3
|
+
import { publicMapPackageRelease } from "fake-snippets-api/lib/public-mapping/public-map-package-release"
|
|
4
|
+
import { z } from "zod"
|
|
5
|
+
|
|
6
|
+
export default withRouteSpec({
|
|
7
|
+
methods: ["POST"],
|
|
8
|
+
auth: "none",
|
|
9
|
+
jsonBody: z
|
|
10
|
+
.object({
|
|
11
|
+
package_id: z.string().optional(),
|
|
12
|
+
package_name: z.string().optional(),
|
|
13
|
+
is_latest: z.boolean().optional(),
|
|
14
|
+
version: z.string().optional(),
|
|
15
|
+
commit_sha: z.string().optional(),
|
|
16
|
+
})
|
|
17
|
+
.refine(({ package_id, package_name }) => {
|
|
18
|
+
if (package_id && package_name) {
|
|
19
|
+
return false
|
|
20
|
+
}
|
|
21
|
+
return true
|
|
22
|
+
}, "package_id and package_name are mutually exclusive")
|
|
23
|
+
.refine(({ package_id, package_name }) => {
|
|
24
|
+
if (!package_id && !package_name) {
|
|
25
|
+
return false
|
|
26
|
+
}
|
|
27
|
+
return true
|
|
28
|
+
}, "package_id or package_name is required"),
|
|
29
|
+
jsonResponse: z.object({
|
|
30
|
+
ok: z.boolean(),
|
|
31
|
+
package_releases: z.array(packageReleaseSchema),
|
|
32
|
+
}),
|
|
33
|
+
})(async (req, ctx) => {
|
|
34
|
+
const { package_id, package_name, is_latest, version, commit_sha } =
|
|
35
|
+
req.jsonBody
|
|
36
|
+
|
|
37
|
+
if (!package_id && !package_name && !is_latest && !version && !commit_sha) {
|
|
38
|
+
return ctx.error(400, {
|
|
39
|
+
error_code: "invalid_query",
|
|
40
|
+
message:
|
|
41
|
+
"At least one of package_id, package_name, is_latest, version or commit_sha is required",
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let releases = ctx.db.packageReleases
|
|
46
|
+
|
|
47
|
+
// Apply filters
|
|
48
|
+
if (package_id) {
|
|
49
|
+
releases = releases.filter((pr) => pr.package_id === package_id)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (package_name) {
|
|
53
|
+
const pkg = ctx.db.packages.find((p) => p.name === package_name)
|
|
54
|
+
if (pkg) {
|
|
55
|
+
releases = releases.filter((pr) => pr.package_id === pkg.package_id)
|
|
56
|
+
} else {
|
|
57
|
+
releases = []
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (is_latest !== undefined) {
|
|
62
|
+
releases = releases.filter((pr) => pr.is_latest === is_latest)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (version) {
|
|
66
|
+
releases = releases.filter((pr) => pr.version === version)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (commit_sha) {
|
|
70
|
+
releases = releases.filter((pr) => pr.commit_sha === commit_sha)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return ctx.json({
|
|
74
|
+
ok: true,
|
|
75
|
+
package_releases: releases.map((pr) => publicMapPackageRelease(pr)),
|
|
76
|
+
})
|
|
77
|
+
})
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec"
|
|
2
|
+
import { z } from "zod"
|
|
3
|
+
|
|
4
|
+
export default withRouteSpec({
|
|
5
|
+
methods: ["POST"],
|
|
6
|
+
auth: "none",
|
|
7
|
+
jsonBody: z.object({
|
|
8
|
+
package_release_id: z.string().optional(),
|
|
9
|
+
package_name_with_version: z.string().optional(),
|
|
10
|
+
is_locked: z.boolean().optional(),
|
|
11
|
+
is_latest: z.boolean().optional(),
|
|
12
|
+
license: z.string().optional(),
|
|
13
|
+
}),
|
|
14
|
+
jsonResponse: z.object({
|
|
15
|
+
ok: z.boolean(),
|
|
16
|
+
}),
|
|
17
|
+
})(async (req, ctx) => {
|
|
18
|
+
const {
|
|
19
|
+
package_release_id,
|
|
20
|
+
package_name_with_version,
|
|
21
|
+
is_locked,
|
|
22
|
+
is_latest,
|
|
23
|
+
license,
|
|
24
|
+
} = req.jsonBody
|
|
25
|
+
let releaseId = package_release_id
|
|
26
|
+
|
|
27
|
+
// Handle package_name_with_version lookup
|
|
28
|
+
if (!releaseId && package_name_with_version) {
|
|
29
|
+
const [packageName, version] = package_name_with_version.split("@")
|
|
30
|
+
const pkg = ctx.db.packages.find((p) => p.name === packageName)
|
|
31
|
+
if (pkg) {
|
|
32
|
+
const release = ctx.db.packageReleases.find(
|
|
33
|
+
(pr) => pr.package_id === pkg.package_id && pr.version === version,
|
|
34
|
+
)
|
|
35
|
+
if (release) {
|
|
36
|
+
releaseId = release.package_release_id
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!releaseId) {
|
|
42
|
+
return ctx.error(404, {
|
|
43
|
+
error_code: "package_release_not_found",
|
|
44
|
+
message: "Package release not found",
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const delta = { is_locked, is_latest, license }
|
|
49
|
+
if (
|
|
50
|
+
Object.keys(delta).filter(
|
|
51
|
+
(k) => delta[k as keyof typeof delta] !== undefined,
|
|
52
|
+
).length === 0
|
|
53
|
+
) {
|
|
54
|
+
return ctx.error(400, {
|
|
55
|
+
error_code: "no_fields_provided",
|
|
56
|
+
message: "No fields provided to update",
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const release = ctx.db.packageReleases.find(
|
|
61
|
+
(pr) => pr.package_release_id === releaseId,
|
|
62
|
+
)
|
|
63
|
+
if (!release) {
|
|
64
|
+
return ctx.error(404, {
|
|
65
|
+
error_code: "package_release_not_found",
|
|
66
|
+
message: "Package release not found",
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Handle is_latest updates
|
|
71
|
+
if (is_latest !== undefined && is_latest) {
|
|
72
|
+
ctx.db.packageReleases
|
|
73
|
+
.filter(
|
|
74
|
+
(pr) =>
|
|
75
|
+
pr.package_id === release.package_id &&
|
|
76
|
+
pr.package_release_id !== releaseId &&
|
|
77
|
+
pr.is_latest,
|
|
78
|
+
)
|
|
79
|
+
.forEach((pr) => {
|
|
80
|
+
pr.is_latest = false
|
|
81
|
+
})
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Update the release
|
|
85
|
+
Object.assign(release, {
|
|
86
|
+
...(is_locked !== undefined && { is_locked }),
|
|
87
|
+
...(is_latest !== undefined && { is_latest }),
|
|
88
|
+
...(license !== undefined && { license }),
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
ctx.db.updatePackageRelease(release)
|
|
92
|
+
|
|
93
|
+
return ctx.json({
|
|
94
|
+
ok: true,
|
|
95
|
+
})
|
|
96
|
+
})
|
package/index.html
CHANGED
|
@@ -1,23 +1,105 @@
|
|
|
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
|
-
|
|
23
|
-
|
|
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
|
+
</head>
|
|
88
|
+
|
|
89
|
+
<body>
|
|
90
|
+
<div class="loaderanimation">
|
|
91
|
+
<div id="loader" class="loading-overlay">
|
|
92
|
+
<div class="loading-container">
|
|
93
|
+
<div class="loading">
|
|
94
|
+
<div class="loading-bar"></div>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
<div id="root" class="loaderanimation" style="visibility: hidden;"></div> <!-- Initially hidden -->
|
|
101
|
+
|
|
102
|
+
<script type="module" src="./src/main.tsx"></script>
|
|
103
|
+
</body>
|
|
104
|
+
|
|
105
|
+
</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.6",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -80,6 +80,7 @@
|
|
|
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-tscircuit": "^0.0.4",
|
|
83
84
|
"circuit-json-to-readable-netlist": "^0.0.7",
|
|
84
85
|
"class-variance-authority": "^0.7.0",
|
|
85
86
|
"clsx": "^2.1.1",
|
|
@@ -135,6 +136,7 @@
|
|
|
135
136
|
"@types/babel__standalone": "^7.1.7",
|
|
136
137
|
"@types/bun": "^1.1.10",
|
|
137
138
|
"@types/country-list": "^2.1.4",
|
|
139
|
+
"@types/node": "^22.13.0",
|
|
138
140
|
"@types/prismjs": "^1.26.4",
|
|
139
141
|
"@types/react": "^18.3.9",
|
|
140
142
|
"@types/react-dom": "^18.3.0",
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { test, expect } from "@playwright/test"
|
|
2
|
+
import { exampleCircuitJson } from "./exampleCircuitJson"
|
|
3
|
+
|
|
4
|
+
async function loginToSite(page) {
|
|
5
|
+
const loginButton = page.getByRole("button", { name: "Log in" })
|
|
6
|
+
if (await loginButton.isVisible()) {
|
|
7
|
+
await loginButton.click()
|
|
8
|
+
await page.waitForLoadState("networkidle")
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
test.beforeEach(async ({ page }) => {
|
|
13
|
+
await page.goto("http://127.0.0.1:5177/quickstart")
|
|
14
|
+
await page.waitForTimeout(3000)
|
|
15
|
+
await loginToSite(page).catch(() => {})
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
test("should open and close the Circuit Json Import Dialog", async ({
|
|
19
|
+
page,
|
|
20
|
+
}) => {
|
|
21
|
+
const importButton = page.locator('button:has-text("Import Circuit JSON")')
|
|
22
|
+
await importButton.click()
|
|
23
|
+
|
|
24
|
+
const dialog = page.getByRole("dialog")
|
|
25
|
+
await expect(dialog).toBeVisible()
|
|
26
|
+
|
|
27
|
+
const closeButton = dialog.getByRole("button", { name: "Close" })
|
|
28
|
+
await closeButton.click()
|
|
29
|
+
|
|
30
|
+
await expect(dialog).not.toBeVisible()
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
test("should handle valid Circuit JSON input", async ({ page }) => {
|
|
34
|
+
const importButton = page.getByRole("button", { name: "Import Circuit JSON" })
|
|
35
|
+
await importButton.click()
|
|
36
|
+
const textarea = page.locator(
|
|
37
|
+
'textarea[placeholder="Paste the Circuit JSON."]',
|
|
38
|
+
)
|
|
39
|
+
await textarea.fill(JSON.stringify(exampleCircuitJson))
|
|
40
|
+
|
|
41
|
+
const importDialogButton = page.getByRole("button", { name: "Import" })
|
|
42
|
+
await importDialogButton.click()
|
|
43
|
+
|
|
44
|
+
const successToast = page.locator(
|
|
45
|
+
'div.text-sm.font-semibold:has-text("Import Successful")',
|
|
46
|
+
)
|
|
47
|
+
await successToast.waitFor({ state: "visible", timeout: 5000 })
|
|
48
|
+
await expect(successToast).toBeVisible()
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
test("should handle valid Circuit JSON file upload", async ({ page }) => {
|
|
52
|
+
const importButton = page.locator('button:has-text("Import Circuit JSON")')
|
|
53
|
+
await importButton.click()
|
|
54
|
+
|
|
55
|
+
const fileInput = page.locator('input[type="file"]')
|
|
56
|
+
|
|
57
|
+
await fileInput.setInputFiles({
|
|
58
|
+
name: "circuit.json",
|
|
59
|
+
mimeType: "application/json",
|
|
60
|
+
// @ts-expect-error didnt add node types to tsconfig
|
|
61
|
+
buffer: Buffer.from(JSON.stringify(exampleCircuitJson)),
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
const importDialogButton = page.getByRole("button", { name: "Import" })
|
|
65
|
+
await importDialogButton.click()
|
|
66
|
+
const successToast = page.locator(
|
|
67
|
+
'div.text-sm.font-semibold:has-text("Import Successful")',
|
|
68
|
+
)
|
|
69
|
+
await successToast.waitFor({ state: "visible", timeout: 5000 })
|
|
70
|
+
await expect(successToast).toBeVisible()
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
test("should handle invalid Circuit JSON input", async ({ page }) => {
|
|
74
|
+
const importButton = page.locator('button:has-text("Import Circuit JSON")')
|
|
75
|
+
await importButton.click()
|
|
76
|
+
|
|
77
|
+
const textarea = page.locator(
|
|
78
|
+
'textarea[placeholder="Paste the Circuit JSON."]',
|
|
79
|
+
)
|
|
80
|
+
await textarea.fill("invalid json content")
|
|
81
|
+
|
|
82
|
+
const importDialogButton = page.getByRole("button", { name: "Import" })
|
|
83
|
+
await importDialogButton.click()
|
|
84
|
+
|
|
85
|
+
const errorToast = page.locator(
|
|
86
|
+
'div.text-sm.font-semibold:has-text("Invalid Input")',
|
|
87
|
+
)
|
|
88
|
+
await errorToast.waitFor({ state: "visible", timeout: 5000 })
|
|
89
|
+
await expect(errorToast).toBeVisible()
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
test("should handle invalid Circuit JSON file upload", async ({ page }) => {
|
|
93
|
+
const importButton = page.locator('button:has-text("Import Circuit JSON")')
|
|
94
|
+
await importButton.click()
|
|
95
|
+
|
|
96
|
+
const fileInput = page.locator('input[type="file"]')
|
|
97
|
+
await fileInput.setInputFiles({
|
|
98
|
+
name: "circuit.json",
|
|
99
|
+
mimeType: "application/json",
|
|
100
|
+
// @ts-expect-error didnt add node types to tsconfig
|
|
101
|
+
buffer: Buffer.from(JSON.stringify({})),
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
const importDialogButton = page.getByRole("button", { name: "Import" })
|
|
105
|
+
await importDialogButton.click()
|
|
106
|
+
|
|
107
|
+
const errorToast = page.locator(
|
|
108
|
+
'div.text-sm.font-semibold:has-text("Import Failed")',
|
|
109
|
+
)
|
|
110
|
+
await errorToast.waitFor({ state: "visible", timeout: 5000 })
|
|
111
|
+
await expect(errorToast).toBeVisible()
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
test("should handle non-JSON file upload", async ({ page }) => {
|
|
115
|
+
const importButton = page.locator('button:has-text("Import Circuit JSON")')
|
|
116
|
+
await importButton.click()
|
|
117
|
+
|
|
118
|
+
const fileInput = page.locator('input[type="file"]')
|
|
119
|
+
await fileInput.setInputFiles({
|
|
120
|
+
name: "circuit.txt",
|
|
121
|
+
mimeType: "application/text",
|
|
122
|
+
// @ts-expect-error didnt add node types to tsconfig
|
|
123
|
+
buffer: Buffer.from(""),
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
const importDialogButton = page.getByRole("button", { name: "Import" })
|
|
127
|
+
await importDialogButton.click()
|
|
128
|
+
|
|
129
|
+
const errorToast = page.locator(
|
|
130
|
+
'div.pb-4 > p:has-text("Please select a valid JSON file.")',
|
|
131
|
+
)
|
|
132
|
+
await expect(errorToast).toBeVisible()
|
|
133
|
+
})
|