@tscircuit/fake-snippets 0.0.68 → 0.0.70

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.
Files changed (30) hide show
  1. package/bun-tests/fake-snippets-api/routes/packages/update.test.ts +104 -0
  2. package/bun.lock +15 -25
  3. package/dist/bundle.js +25 -5
  4. package/dist/index.d.ts +5 -0
  5. package/dist/index.js +2 -1
  6. package/dist/schema.d.ts +8 -0
  7. package/dist/schema.js +2 -1
  8. package/fake-snippets-api/lib/db/schema.ts +4 -0
  9. package/fake-snippets-api/routes/api/packages/create.ts +1 -0
  10. package/fake-snippets-api/routes/api/packages/get.ts +12 -0
  11. package/fake-snippets-api/routes/api/packages/update.ts +11 -2
  12. package/package.json +3 -3
  13. package/src/components/CircuitJsonImportDialog.tsx +113 -52
  14. package/src/components/DownloadButtonAndMenu.tsx +1 -1
  15. package/src/components/HeaderLogin.tsx +1 -1
  16. package/src/components/ViewPackagePage/components/important-files-view.tsx +1 -1
  17. package/src/components/ViewPackagePage/components/mobile-sidebar.tsx +1 -0
  18. package/src/components/ViewPackagePage/components/package-header.tsx +21 -11
  19. package/src/components/ViewPackagePage/components/repo-page-content.tsx +24 -7
  20. package/src/components/ViewPackagePage/components/sidebar-about-section.tsx +1 -0
  21. package/src/components/dialogs/edit-package-details-dialog.tsx +33 -2
  22. package/src/components/package-port/CodeAndPreview.tsx +1 -1
  23. package/src/components/package-port/CodeEditorHeader.tsx +12 -4
  24. package/src/components/package-port/EditorNav.tsx +33 -8
  25. package/src/hooks/use-create-package-mutation.ts +0 -2
  26. package/src/hooks/use-package-details-form.ts +15 -1
  27. package/src/index.css +13 -0
  28. package/src/pages/package-editor.tsx +2 -1
  29. package/src/pages/quickstart.tsx +1 -1
  30. package/src/pages/view-package.tsx +13 -11
package/dist/index.d.ts CHANGED
@@ -508,6 +508,7 @@ declare const packageSchema: z.ZodObject<{
508
508
  ai_description: z.ZodNullable<z.ZodString>;
509
509
  latest_license: z.ZodOptional<z.ZodNullable<z.ZodString>>;
510
510
  ai_usage_instructions: z.ZodNullable<z.ZodString>;
511
+ default_view: z.ZodOptional<z.ZodDefault<z.ZodEnum<["files", "3d", "pcb", "schematic"]>>>;
511
512
  }, "strip", z.ZodTypeAny, {
512
513
  name: string;
513
514
  unscoped_name: string;
@@ -536,6 +537,7 @@ declare const packageSchema: z.ZodObject<{
536
537
  ai_usage_instructions: string | null;
537
538
  snippet_type?: "board" | "package" | "model" | "footprint" | undefined;
538
539
  latest_license?: string | null | undefined;
540
+ default_view?: "files" | "3d" | "pcb" | "schematic" | undefined;
539
541
  }, {
540
542
  name: string;
541
543
  unscoped_name: string;
@@ -564,6 +566,7 @@ declare const packageSchema: z.ZodObject<{
564
566
  is_source_from_github?: boolean | undefined;
565
567
  website?: string | null | undefined;
566
568
  latest_license?: string | null | undefined;
569
+ default_view?: "files" | "3d" | "pcb" | "schematic" | undefined;
567
570
  }>;
568
571
  type Package = z.infer<typeof packageSchema>;
569
572
  declare const jlcpcbOrderStateSchema: z.ZodObject<{
@@ -778,6 +781,7 @@ declare const createDatabase: ({ seed }?: {
778
781
  ai_usage_instructions: string | null;
779
782
  snippet_type?: "board" | "package" | "model" | "footprint" | undefined;
780
783
  latest_license?: string | null | undefined;
784
+ default_view?: "files" | "3d" | "pcb" | "schematic" | undefined;
781
785
  }[];
782
786
  orders: {
783
787
  error: z.objectOutputType<{
@@ -1068,6 +1072,7 @@ declare const createDatabase: ({ seed }?: {
1068
1072
  ai_usage_instructions: string | null;
1069
1073
  snippet_type?: "board" | "package" | "model" | "footprint" | undefined;
1070
1074
  latest_license?: string | null | undefined;
1075
+ default_view?: "files" | "3d" | "pcb" | "schematic" | undefined;
1071
1076
  }[];
1072
1077
  orders: {
1073
1078
  error: z.objectOutputType<{
package/dist/index.js CHANGED
@@ -187,7 +187,8 @@ var packageSchema = z.object({
187
187
  star_count: z.number().default(0),
188
188
  ai_description: z.string().nullable(),
189
189
  latest_license: z.string().nullable().optional(),
190
- ai_usage_instructions: z.string().nullable()
190
+ ai_usage_instructions: z.string().nullable(),
191
+ default_view: z.enum(["files", "3d", "pcb", "schematic"]).default("files").optional()
191
192
  });
192
193
  var jlcpcbOrderStateSchema = z.object({
193
194
  jlcpcb_order_state_id: z.string(),
package/dist/schema.d.ts CHANGED
@@ -633,6 +633,7 @@ declare const packageSchema: z.ZodObject<{
633
633
  ai_description: z.ZodNullable<z.ZodString>;
634
634
  latest_license: z.ZodOptional<z.ZodNullable<z.ZodString>>;
635
635
  ai_usage_instructions: z.ZodNullable<z.ZodString>;
636
+ default_view: z.ZodOptional<z.ZodDefault<z.ZodEnum<["files", "3d", "pcb", "schematic"]>>>;
636
637
  }, "strip", z.ZodTypeAny, {
637
638
  name: string;
638
639
  unscoped_name: string;
@@ -661,6 +662,7 @@ declare const packageSchema: z.ZodObject<{
661
662
  ai_usage_instructions: string | null;
662
663
  snippet_type?: "board" | "package" | "model" | "footprint" | undefined;
663
664
  latest_license?: string | null | undefined;
665
+ default_view?: "files" | "3d" | "pcb" | "schematic" | undefined;
664
666
  }, {
665
667
  name: string;
666
668
  unscoped_name: string;
@@ -689,6 +691,7 @@ declare const packageSchema: z.ZodObject<{
689
691
  is_source_from_github?: boolean | undefined;
690
692
  website?: string | null | undefined;
691
693
  latest_license?: string | null | undefined;
694
+ default_view?: "files" | "3d" | "pcb" | "schematic" | undefined;
692
695
  }>;
693
696
  type Package = z.infer<typeof packageSchema>;
694
697
  declare const jlcpcbOrderStateSchema: z.ZodObject<{
@@ -1068,6 +1071,7 @@ declare const databaseSchema: z.ZodObject<{
1068
1071
  ai_description: z.ZodNullable<z.ZodString>;
1069
1072
  latest_license: z.ZodOptional<z.ZodNullable<z.ZodString>>;
1070
1073
  ai_usage_instructions: z.ZodNullable<z.ZodString>;
1074
+ default_view: z.ZodOptional<z.ZodDefault<z.ZodEnum<["files", "3d", "pcb", "schematic"]>>>;
1071
1075
  }, "strip", z.ZodTypeAny, {
1072
1076
  name: string;
1073
1077
  unscoped_name: string;
@@ -1096,6 +1100,7 @@ declare const databaseSchema: z.ZodObject<{
1096
1100
  ai_usage_instructions: string | null;
1097
1101
  snippet_type?: "board" | "package" | "model" | "footprint" | undefined;
1098
1102
  latest_license?: string | null | undefined;
1103
+ default_view?: "files" | "3d" | "pcb" | "schematic" | undefined;
1099
1104
  }, {
1100
1105
  name: string;
1101
1106
  unscoped_name: string;
@@ -1124,6 +1129,7 @@ declare const databaseSchema: z.ZodObject<{
1124
1129
  is_source_from_github?: boolean | undefined;
1125
1130
  website?: string | null | undefined;
1126
1131
  latest_license?: string | null | undefined;
1132
+ default_view?: "files" | "3d" | "pcb" | "schematic" | undefined;
1127
1133
  }>, "many">>;
1128
1134
  orders: z.ZodDefault<z.ZodArray<z.ZodObject<{
1129
1135
  order_id: z.ZodString;
@@ -1572,6 +1578,7 @@ declare const databaseSchema: z.ZodObject<{
1572
1578
  ai_usage_instructions: string | null;
1573
1579
  snippet_type?: "board" | "package" | "model" | "footprint" | undefined;
1574
1580
  latest_license?: string | null | undefined;
1581
+ default_view?: "files" | "3d" | "pcb" | "schematic" | undefined;
1575
1582
  }[];
1576
1583
  orders: {
1577
1584
  error: z.objectOutputType<{
@@ -1788,6 +1795,7 @@ declare const databaseSchema: z.ZodObject<{
1788
1795
  is_source_from_github?: boolean | undefined;
1789
1796
  website?: string | null | undefined;
1790
1797
  latest_license?: string | null | undefined;
1798
+ default_view?: "files" | "3d" | "pcb" | "schematic" | undefined;
1791
1799
  }[] | undefined;
1792
1800
  orders?: {
1793
1801
  error: z.objectInputType<{
package/dist/schema.js CHANGED
@@ -182,7 +182,8 @@ var packageSchema = z.object({
182
182
  star_count: z.number().default(0),
183
183
  ai_description: z.string().nullable(),
184
184
  latest_license: z.string().nullable().optional(),
185
- ai_usage_instructions: z.string().nullable()
185
+ ai_usage_instructions: z.string().nullable(),
186
+ default_view: z.enum(["files", "3d", "pcb", "schematic"]).default("files").optional()
186
187
  });
187
188
  var jlcpcbOrderStateSchema = z.object({
188
189
  jlcpcb_order_state_id: z.string(),
@@ -219,6 +219,10 @@ export const packageSchema = z.object({
219
219
  ai_description: z.string().nullable(),
220
220
  latest_license: z.string().nullable().optional(),
221
221
  ai_usage_instructions: z.string().nullable(),
222
+ default_view: z
223
+ .enum(["files", "3d", "pcb", "schematic"])
224
+ .default("files")
225
+ .optional(),
222
226
  })
223
227
  export type Package = z.infer<typeof packageSchema>
224
228
 
@@ -55,6 +55,7 @@ export default withRouteSpec({
55
55
  is_public: is_private === true ? false : true,
56
56
  is_unlisted: is_private === true ? true : (is_unlisted ?? false),
57
57
  ai_usage_instructions: "placeholder ai usage instructions",
58
+ default_view: "files",
58
59
  })
59
60
 
60
61
  if (!newPackage) {
@@ -34,6 +34,18 @@ export default withRouteSpec({
34
34
  })
35
35
  }
36
36
 
37
+ if (
38
+ foundPackage.is_private &&
39
+ auth?.github_username !== foundPackage.owner_github_username
40
+ ) {
41
+ return ctx.error(404, {
42
+ error_code: "package_not_found",
43
+ message: `Package not found (searched using ${JSON.stringify(
44
+ req.commonParams,
45
+ )})`,
46
+ })
47
+ }
48
+
37
49
  return ctx.json({
38
50
  ok: true,
39
51
  package: {
@@ -20,6 +20,7 @@ export default withRouteSpec({
20
20
  website: z.string().optional(),
21
21
  is_private: z.boolean().optional(),
22
22
  is_unlisted: z.boolean().optional(),
23
+ default_view: z.enum(["files", "3d", "pcb", "schematic"]).optional(),
23
24
  })
24
25
  .transform((data) => ({
25
26
  ...data,
@@ -30,8 +31,15 @@ export default withRouteSpec({
30
31
  package: packageSchema,
31
32
  }),
32
33
  })(async (req, ctx) => {
33
- const { package_id, name, description, website, is_private, is_unlisted } =
34
- req.jsonBody
34
+ const {
35
+ package_id,
36
+ name,
37
+ description,
38
+ website,
39
+ is_private,
40
+ is_unlisted,
41
+ default_view,
42
+ } = req.jsonBody
35
43
 
36
44
  const packageIndex = ctx.db.packages.findIndex(
37
45
  (p) => p.package_id === package_id,
@@ -77,6 +85,7 @@ export default withRouteSpec({
77
85
  is_public:
78
86
  is_private !== undefined ? !is_private : existingPackage.is_public,
79
87
  is_unlisted: is_unlisted ?? existingPackage.is_unlisted,
88
+ default_view: default_view ?? existingPackage.default_view,
80
89
  updated_at: new Date().toISOString(),
81
90
  })
82
91
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tscircuit/fake-snippets",
3
- "version": "0.0.68",
3
+ "version": "0.0.70",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -75,7 +75,7 @@
75
75
  "@tscircuit/layout": "^0.0.29",
76
76
  "@tscircuit/math-utils": "^0.0.10",
77
77
  "@tscircuit/mm": "^0.0.8",
78
- "@tscircuit/props": "^0.0.143",
78
+ "@tscircuit/props": "^0.0.186",
79
79
  "@types/file-saver": "^2.0.7",
80
80
  "@types/ms": "^0.7.34",
81
81
  "@typescript/ata": "^0.9.7",
@@ -147,7 +147,7 @@
147
147
  "@tailwindcss/typography": "^0.5.16",
148
148
  "@tscircuit/core": "^0.0.384",
149
149
  "@tscircuit/prompt-benchmarks": "^0.0.28",
150
- "@tscircuit/runframe": "^0.0.485",
150
+ "@tscircuit/runframe": "^0.0.494",
151
151
  "@types/babel__standalone": "^7.1.7",
152
152
  "@types/bun": "^1.1.10",
153
153
  "@types/country-list": "^2.1.4",
@@ -5,6 +5,7 @@ import {
5
5
  DialogHeader,
6
6
  DialogTitle,
7
7
  DialogFooter,
8
+ DialogDescription,
8
9
  } from "@/components/ui/dialog"
9
10
  import { Button } from "@/components/ui/button"
10
11
  import { Textarea } from "@/components/ui/textarea"
@@ -13,6 +14,10 @@ import { useToast } from "@/hooks/use-toast"
13
14
  import { useLocation } from "wouter"
14
15
  import { useGlobalStore } from "@/hooks/use-global-store"
15
16
  import { convertCircuitJsonToTscircuit } from "circuit-json-to-tscircuit"
17
+ import { useCreatePackageMutation } from "@/hooks/use-create-package-mutation"
18
+ import { generateRandomPackageName } from "./package-port/CodeAndPreview"
19
+ import { useCreatePackageReleaseMutation } from "@/hooks/use-create-package-release-mutation"
20
+ import { useCreatePackageFilesMutation } from "@/hooks/use-create-package-files-mutation"
16
21
 
17
22
  interface CircuitJsonImportDialogProps {
18
23
  open: boolean
@@ -36,11 +41,29 @@ export function CircuitJsonImportDialog({
36
41
  const [file, setFile] = useState<File | null>(null)
37
42
  const [isLoading, setIsLoading] = useState(false)
38
43
  const [error, setError] = useState<string | null>(null)
39
- const axios = useAxios()
44
+ const loggedInUser = useGlobalStore((s) => s.session)
40
45
  const { toast } = useToast()
46
+ const axios = useAxios()
41
47
  const [, navigate] = useLocation()
42
48
  const isLoggedIn = useGlobalStore((s) => Boolean(s.session))
43
- const session = useGlobalStore((s) => s.session)
49
+ const createPackageMutation = useCreatePackageMutation()
50
+ const { mutate: createRelease } = useCreatePackageReleaseMutation({
51
+ onSuccess: () => {
52
+ toast({
53
+ title: "Package released",
54
+ description: "Your package has been released successfully.",
55
+ })
56
+ },
57
+ })
58
+
59
+ const createPackageFilesMutation = useCreatePackageFilesMutation({
60
+ onSuccess: () => {
61
+ toast({
62
+ title: "Package files created",
63
+ description: "Your package files have been created successfully.",
64
+ })
65
+ },
66
+ })
44
67
 
45
68
  const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
46
69
  const selectedFile = e.target.files?.[0]
@@ -54,80 +77,117 @@ export function CircuitJsonImportDialog({
54
77
  const handleImport = async () => {
55
78
  let importedCircuitJson
56
79
 
57
- if (file) {
80
+ const parseJson = async (jsonString: string) => {
58
81
  try {
59
- const fileText = await file.text()
60
- importedCircuitJson = JSON.parse(fileText)
61
- } catch (err) {
62
- setError("Error reading JSON file. Please ensure it is valid.")
63
- return
82
+ return JSON.parse(jsonString)
83
+ } catch {
84
+ throw new Error("Invalid JSON format.")
64
85
  }
65
- } else if (isValidJSON(circuitJson)) {
66
- setIsLoading(true)
67
- setError(null)
68
- importedCircuitJson = JSON.parse(circuitJson)
69
- } else {
70
- toast({
71
- title: "Invalid Input",
72
- description: "Please provide a valid JSON content or file.",
73
- variant: "destructive",
74
- })
75
- return
76
86
  }
77
- let tscircuit
78
- try {
79
- tscircuit = convertCircuitJsonToTscircuit(importedCircuitJson as any, {
80
- componentName: "circuit",
81
- })
82
- console.info(tscircuit)
83
- } catch {
87
+
88
+ const handleError = (message: string) => {
84
89
  toast({
85
90
  title: "Import Failed",
86
- description: "Invalid Circuit JSON was provided.",
91
+ description: message,
87
92
  variant: "destructive",
88
93
  })
89
94
  setIsLoading(false)
90
- return
95
+ }
96
+
97
+ const handleSuccess = (message: string) => {
98
+ toast({
99
+ title: "Success",
100
+ description: message,
101
+ })
91
102
  }
92
103
 
93
104
  try {
94
- const newSnippetData = {
95
- snippet_type: importedCircuitJson.type ?? "board",
96
- circuit_json: importedCircuitJson,
97
- code: tscircuit,
98
- }
99
- const response = await axios
100
- .post("/snippets/create", newSnippetData)
101
- .catch((e) => e)
102
- const { snippet, message } = response.data
103
- if (message) {
104
- setError(message)
105
- setIsLoading(false)
105
+ if (file) {
106
+ const fileText = await file.text()
107
+ importedCircuitJson = await parseJson(fileText)
108
+ } else if (isValidJSON(circuitJson)) {
109
+ setIsLoading(true)
110
+ setError(null)
111
+ importedCircuitJson = await parseJson(circuitJson)
112
+ } else {
113
+ handleError("Please provide a valid JSON content or file.")
106
114
  return
107
115
  }
108
- toast({
109
- title: "Import Successful",
110
- description: "Circuit Json has been imported successfully.",
111
- })
112
- onOpenChange(false)
113
- navigate(`/editor?snippet_id=${snippet.snippet_id}`)
116
+
117
+ const tscircuitComponentContent = convertCircuitJsonToTscircuit(
118
+ importedCircuitJson as any,
119
+ {
120
+ componentName: "circuit",
121
+ },
122
+ )
123
+ console.info(tscircuitComponentContent)
124
+
125
+ await createPackageMutation.mutateAsync(
126
+ {
127
+ name: `${loggedInUser?.github_username}/${generateRandomPackageName()}`,
128
+ description: "Imported from Circuit JSON",
129
+ },
130
+ {
131
+ onSuccess: (newPackage) => {
132
+ handleSuccess("Package has been created successfully.")
133
+ createRelease(
134
+ {
135
+ package_name_with_version: `${newPackage.name}@latest`,
136
+ },
137
+ {
138
+ onSuccess: (release) => {
139
+ createPackageFilesMutation
140
+ .mutateAsync({
141
+ file_path: "index.tsx",
142
+ content_text: tscircuitComponentContent,
143
+ package_release_id: release.package_release_id,
144
+ })
145
+ .then(() => {
146
+ navigate(`/editor?package_id=${newPackage.package_id}`)
147
+ })
148
+ },
149
+ onError: (error) => {
150
+ setError(error)
151
+ handleError("Failed to create package release.")
152
+ },
153
+ },
154
+ )
155
+ },
156
+ onError: (error) => {
157
+ setError(error)
158
+ handleError("Failed to create package.")
159
+ },
160
+ onSettled: () => {
161
+ setIsLoading(false)
162
+ onOpenChange(false)
163
+ },
164
+ },
165
+ )
114
166
  } catch (error) {
115
167
  console.error("Error importing Circuit Json:", error)
116
- toast({
117
- title: "Import Failed",
118
- description: "Failed to import the Circuit Json. Please try again.",
119
- variant: "destructive",
120
- })
168
+ handleError(
169
+ "The Circuit JSON appears to be invalid or malformed. Please check the format and try again.",
170
+ )
121
171
  } finally {
122
172
  setIsLoading(false)
123
173
  }
124
174
  }
125
175
 
176
+ const handleKeyDown = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
177
+ if (event.ctrlKey && event.key === "Enter") {
178
+ handleImport()
179
+ }
180
+ }
181
+
126
182
  return (
127
183
  <Dialog open={open} onOpenChange={onOpenChange}>
128
184
  <DialogContent>
129
185
  <DialogHeader>
130
186
  <DialogTitle>Import Circuit JSON</DialogTitle>
187
+ <DialogDescription>
188
+ Use this dialog to import a Circuit JSON file or paste the JSON
189
+ content directly.
190
+ </DialogDescription>
131
191
  </DialogHeader>
132
192
  <div className="pb-4">
133
193
  <Textarea
@@ -135,6 +195,7 @@ export function CircuitJsonImportDialog({
135
195
  placeholder="Paste the Circuit JSON."
136
196
  value={circuitJson}
137
197
  onChange={(e) => setcircuitJson(e.target.value)}
198
+ onKeyDown={handleKeyDown}
138
199
  disabled={!!file}
139
200
  />
140
201
  <div className="mt-4 flex flex-col gap-2">
@@ -150,7 +211,7 @@ export function CircuitJsonImportDialog({
150
211
  type="file"
151
212
  accept="application/json"
152
213
  onChange={handleFileChange}
153
- className="hidden" // Hide the default file input
214
+ className="hidden"
154
215
  />
155
216
  <label
156
217
  htmlFor="file-input"
@@ -34,7 +34,7 @@ export function DownloadButtonAndMenu({
34
34
 
35
35
  if (!circuitJson) {
36
36
  return (
37
- <div>
37
+ <div className={className}>
38
38
  <Button
39
39
  disabled
40
40
  variant="ghost"
@@ -67,7 +67,7 @@ export const HeaderLogin = () => {
67
67
  </a>
68
68
  </DropdownMenuItem>
69
69
  <DropdownMenuItem asChild onClick={() => setSession(null)}>
70
- <a href="/sign-out" className="cursor-pointer">
70
+ <a href="/" className="cursor-pointer">
71
71
  Sign out
72
72
  </a>
73
73
  </DropdownMenuItem>
@@ -168,7 +168,7 @@ export default function ImportantFilesView({
168
168
  return (
169
169
  <div className="mt-4 border border-gray-200 dark:border-[#30363d] rounded-md overflow-hidden">
170
170
  <div className="flex items-center pl-2 pr-4 py-2 bg-gray-100 dark:bg-[#161b22] border-b border-gray-200 dark:border-[#30363d]">
171
- <div className="flex items-center space-x-2">
171
+ <div className="flex items-center space-x-2 overflow-x-auto no-scrollbar">
172
172
  {/* AI Description Tab */}
173
173
  {hasAiContent && (
174
174
  <button
@@ -211,6 +211,7 @@ const MobileSidebar = ({
211
211
  packageAuthor={packageInfo.owner_github_username}
212
212
  onUpdate={handlePackageUpdate}
213
213
  packageName={packageInfo.name}
214
+ currentDefaultView={packageInfo.default_view}
214
215
  />
215
216
  )}
216
217
  </div>
@@ -1,7 +1,5 @@
1
- import React, { useEffect } from "react"
1
+ import { useEffect } from "react"
2
2
  import { Link } from "wouter"
3
-
4
- import { TypeBadge } from "@/components/TypeBadge"
5
3
  import { Button } from "@/components/ui/button"
6
4
  import { Skeleton } from "@/components/ui/skeleton"
7
5
  import {
@@ -10,7 +8,7 @@ import {
10
8
  TooltipProvider,
11
9
  TooltipTrigger,
12
10
  } from "@/components/ui/tooltip"
13
- import { LockClosedIcon } from "@radix-ui/react-icons"
11
+ import { Lock, Globe } from "lucide-react"
14
12
  import { GitFork, Package, Star } from "lucide-react"
15
13
 
16
14
  import { useForkPackageMutation } from "@/hooks/use-fork-package-mutation"
@@ -106,13 +104,25 @@ export default function PackageHeader({
106
104
  {packageName}
107
105
  </Link>
108
106
  </h1>
109
- {packageInfo?.name && <TypeBadge type="package" />}
110
- {isPrivate && (
111
- <div className="relative group pl-2">
112
- <LockClosedIcon className="h-4 w-4 text-gray-700" />
113
- <span className="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-1 hidden group-hover:block bg-black text-white text-xs rounded py-1 px-2">
114
- private
115
- </span>
107
+ {packageInfo?.name && (
108
+ <div
109
+ className={`select-none inline-flex items-center px-2 py-1 rounded text-xs font-medium ${
110
+ isPrivate
111
+ ? "bg-gray-100 text-gray-700 border border-gray-200"
112
+ : "bg-blue-50 text-blue-700 border border-blue-200"
113
+ }`}
114
+ >
115
+ {isPrivate ? (
116
+ <>
117
+ <Lock className="w-3 h-3 mr-1 flex-shrink-0" />
118
+ <span className="leading-none">Private</span>
119
+ </>
120
+ ) : (
121
+ <>
122
+ <Globe className="w-3 h-3 mr-1 flex-shrink-0" />
123
+ <span className="leading-none">Public</span>
124
+ </>
125
+ )}
116
126
  </div>
117
127
  )}
118
128
  </>
@@ -19,6 +19,7 @@ import PackageHeader from "./package-header"
19
19
  import { useGlobalStore } from "@/hooks/use-global-store"
20
20
  import { useLocation } from "wouter"
21
21
  import { Package } from "fake-snippets-api/lib/db/schema"
22
+ import { useCurrentPackageCircuitJson } from "../hooks/use-current-package-circuit-json"
22
23
 
23
24
  interface PackageFile {
24
25
  package_file_id: string
@@ -43,22 +44,37 @@ export default function RepoPageContent({
43
44
  onFileClicked,
44
45
  onEditClicked,
45
46
  }: RepoPageContentProps) {
46
- const [location, setLocation] = useLocation()
47
47
  const [activeView, setActiveView] = useState<string>("files")
48
48
  const session = useGlobalStore((s) => s.session)
49
+ const { circuitJson, isLoading: isCircuitJsonLoading } =
50
+ useCurrentPackageCircuitJson()
49
51
 
50
- // Handle hash-based view selection
52
+ // Handle initial view selection and hash-based view changes
51
53
  useEffect(() => {
52
- // Get the hash without the # character
54
+ if (isCircuitJsonLoading) return
53
55
  const hash = window.location.hash.slice(1)
54
- // Valid views
55
56
  const validViews = ["files", "3d", "pcb", "schematic", "bom"]
57
+ const circuitDependentViews = ["3d", "pcb", "schematic", "bom"]
56
58
 
57
- // If hash is a valid view, set it as active
58
- if (validViews.includes(hash)) {
59
+ const availableViews = circuitJson
60
+ ? validViews
61
+ : validViews.filter((view) => !circuitDependentViews.includes(view))
62
+
63
+ if (hash && availableViews.includes(hash)) {
59
64
  setActiveView(hash)
65
+ } else if (
66
+ packageInfo?.default_view &&
67
+ availableViews.includes(packageInfo.default_view)
68
+ ) {
69
+ setActiveView(packageInfo.default_view)
70
+ window.location.hash = packageInfo.default_view
71
+ } else {
72
+ setActiveView("files")
73
+ if (!hash || !availableViews.includes(hash)) {
74
+ window.location.hash = "files"
75
+ }
60
76
  }
61
- }, [])
77
+ }, [packageInfo?.default_view, circuitJson, isCircuitJsonLoading])
62
78
 
63
79
  const importantFilePaths = packageFiles
64
80
  ?.filter((pf) => isPackageFileImportant(pf.file_path))
@@ -138,6 +154,7 @@ export default function RepoPageContent({
138
154
  <Header />
139
155
  <PackageHeader
140
156
  packageInfo={packageInfo}
157
+ isPrivate={packageInfo?.is_private ?? false}
141
158
  isCurrentUserAuthor={
142
159
  packageInfo?.creator_account_id === session?.github_username
143
160
  }
@@ -172,6 +172,7 @@ export default function SidebarAboutSection() {
172
172
  packageAuthor={packageInfo.owner_github_username}
173
173
  onUpdate={handlePackageUpdate}
174
174
  packageName={packageInfo.name}
175
+ currentDefaultView={packageInfo.default_view}
175
176
  />
176
177
  )}
177
178
  </>