@tscircuit/cli 0.0.64 → 0.0.66

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.lockb CHANGED
Binary file
@@ -2,9 +2,16 @@ import { z } from "zod"
2
2
 
3
3
  export const export_parameters = z.object({
4
4
  should_export_gerber_zip: z.boolean().default(false),
5
+ should_export_pnp_csv: z.boolean().default(false),
6
+ should_export_bom_csv: z.boolean().default(false),
5
7
  gerbers_zip_file_name: z
6
8
  .string()
7
9
  .nullable()
8
10
  .optional()
9
11
  .default("gerbers.zip"),
12
+ pnp_csv_file_name: z.string().nullable().optional().default("pnp.csv"),
13
+ bom_csv_file_name: z.string().nullable().optional().default("bom.csv"),
10
14
  })
15
+
16
+ export type ExportParametersInput = z.input<typeof export_parameters>
17
+ export type ExportParameters = z.infer<typeof export_parameters>
@@ -17,3 +17,5 @@ export const export_request = z.object({
17
17
  )
18
18
  .optional(),
19
19
  })
20
+
21
+ export type ExportRequest = z.infer<typeof export_request>
Binary file
@@ -18,6 +18,7 @@ import { useGlobalStore } from "./hooks/use-global-store"
18
18
  import packageJson from "../package.json"
19
19
  import cliPackageJson from "../../package.json"
20
20
  import { useGerberExportDialog } from "./components/dialogs/gerber-export-dialog"
21
+ import { useGenericExportDialog } from "./components/dialogs/generic-export-dialog"
21
22
 
22
23
  export const HeaderMenu = () => {
23
24
  const [viewMode, setViewMode] = useGlobalStore((s) => [
@@ -30,6 +31,24 @@ export const HeaderMenu = () => {
30
31
  ])
31
32
  const [inDebugMode, setInDebugMode] = useState(false)
32
33
  const gerberExportDialog = useGerberExportDialog()
34
+ const pnpExportDialog = useGenericExportDialog({
35
+ dialogTitle: "Export Pick'n'Place",
36
+ dialogDescription:
37
+ "Export the Pick'n'Place CSV for this example export. You can upload this to an assembler (PCBA) to tell their machines where to place each component.",
38
+ exportFileName: "pnp.csv",
39
+ exportParameters: {
40
+ should_export_pnp_csv: true,
41
+ },
42
+ })
43
+ const bomExportDialog = useGenericExportDialog({
44
+ dialogTitle: "Export Bill of Materials",
45
+ dialogDescription:
46
+ "Export the Bill of Materials CSV for this example export. You can upload this to an assembler (PCBA) so they know how to source each component.",
47
+ exportFileName: "bom.csv",
48
+ exportParameters: {
49
+ should_export_bom_csv: true,
50
+ },
51
+ })
33
52
 
34
53
  return (
35
54
  <>
@@ -53,11 +72,10 @@ export const HeaderMenu = () => {
53
72
  <MenubarItem onSelect={() => gerberExportDialog.openDialog()}>
54
73
  Gerbers
55
74
  </MenubarItem>
56
- <MenubarItem
57
- onSelect={() => {
58
- toast.error("Not yet implemented!")
59
- }}
60
- >
75
+ <MenubarItem onSelect={() => pnpExportDialog.openDialog()}>
76
+ Pick'n'Place CSV
77
+ </MenubarItem>
78
+ <MenubarItem onSelect={() => bomExportDialog.openDialog()}>
61
79
  Bill of Materials
62
80
  </MenubarItem>
63
81
  <MenubarItem
@@ -234,7 +252,9 @@ export const HeaderMenu = () => {
234
252
  </MenubarContent>
235
253
  </MenubarMenu>
236
254
  </Menubar>
237
- {<gerberExportDialog.Component />}
255
+ <gerberExportDialog.Component />
256
+ <pnpExportDialog.Component />
257
+ <bomExportDialog.Component />
238
258
  </>
239
259
  )
240
260
  }
@@ -0,0 +1,188 @@
1
+ import { useMemo, useState } from "react"
2
+ import { Dialog } from "@headlessui/react"
3
+ import { useGlobalStore } from "src/hooks/use-global-store"
4
+ import { useActiveDevPackageExampleLite } from "src/hooks/use-active-dev-package-example-lite"
5
+ import { Button } from "../ui/button"
6
+ import axios from "axios"
7
+ import {
8
+ ExportParameters,
9
+ ExportParametersInput,
10
+ export_parameters,
11
+ } from "@server/lib/zod/export_parameters"
12
+
13
+ export const useGenericExportDialog = ({
14
+ exportFileName,
15
+ dialogTitle,
16
+ dialogDescription,
17
+ exportParameters,
18
+ }: {
19
+ exportFileName: string
20
+ dialogTitle: string
21
+ dialogDescription: string
22
+ exportParameters: ExportParametersInput
23
+ }) => {
24
+ const [open, setIsOpen] = useState(false)
25
+ const activeDevExamplePackage = useActiveDevPackageExampleLite()
26
+ const [isExporting, setIsExporting] = useState(false)
27
+ const [exportError, setExportError] = useState<string | null>(null)
28
+
29
+ return useMemo(() => {
30
+ const openDialog = () => {
31
+ setIsOpen(true)
32
+ }
33
+ const closeDialog = () => {
34
+ setIsOpen(false)
35
+ setIsExporting(false)
36
+ }
37
+ if (!activeDevExamplePackage)
38
+ return {
39
+ openDialog,
40
+ closeDialog,
41
+ Component: () => null,
42
+ }
43
+ const { file_path: inputFilePath, export_name: exampleExportName } =
44
+ activeDevExamplePackage
45
+ const outputName = `${
46
+ inputFilePath.split("/").pop()?.split(".")[0]
47
+ }-${exampleExportName}-${exportFileName}`
48
+
49
+ return {
50
+ openDialog,
51
+ closeDialog,
52
+ Component: () => (
53
+ <GenericExportDialog
54
+ dialogTitle={dialogTitle}
55
+ dialogDescription={dialogDescription}
56
+ open={open}
57
+ isExporting={isExporting}
58
+ filePath={inputFilePath}
59
+ tsCircuitExportName={exampleExportName}
60
+ outputName={outputName}
61
+ exportError={exportError}
62
+ onClickExport={async () => {
63
+ setExportError(null)
64
+ setIsExporting(true)
65
+ try {
66
+ if (exportParameters.should_export_pnp_csv) {
67
+ exportParameters.pnp_csv_file_name ??= exportFileName
68
+ }
69
+ if (exportParameters.should_export_bom_csv) {
70
+ exportParameters.bom_csv_file_name ??= exportFileName
71
+ }
72
+
73
+ let export_request = await axios
74
+ .post("/api/export_requests/create", {
75
+ example_file_path: activeDevExamplePackage.file_path,
76
+ export_name: activeDevExamplePackage.export_name,
77
+ export_parameters: exportParameters,
78
+ })
79
+ .then((r) => r.data.export_request)
80
+ const pollExportRequest = async () => {
81
+ while (!export_request.is_complete) {
82
+ try {
83
+ export_request = await axios
84
+ .post("/api/export_requests/get", {
85
+ export_request_id: export_request.export_request_id,
86
+ })
87
+ .then((r) => r.data.export_request)
88
+ } catch (e: any) {
89
+ console.error(e)
90
+ setExportError(
91
+ `${e.toString()}\n\n${e.response?.data?.error?.message}`
92
+ )
93
+ setIsExporting(false)
94
+ return
95
+ }
96
+ await new Promise((resolve) => setTimeout(resolve, 100))
97
+ }
98
+ setIsExporting(false)
99
+ }
100
+ await pollExportRequest()
101
+ // open /api/export_files/download?export_file_id=... in new tab
102
+ const export_file_id =
103
+ export_request.file_summary[0].export_file_id
104
+ window.open(
105
+ `/api/export_files/download?export_file_id=${export_file_id}`,
106
+ "_blank"
107
+ )
108
+ setIsExporting(false)
109
+ } catch (e: any) {
110
+ console.error(e)
111
+ setExportError(
112
+ `${e.toString()}\n\n${e.response?.data?.error?.message}`
113
+ )
114
+ setIsExporting(false)
115
+ }
116
+ }}
117
+ onClose={() => closeDialog()}
118
+ />
119
+ ),
120
+ }
121
+ }, [open, isExporting, exportError])
122
+ }
123
+
124
+ export const GenericExportDialog = ({
125
+ open,
126
+ dialogTitle,
127
+ dialogDescription,
128
+ isExporting,
129
+ filePath,
130
+ tsCircuitExportName,
131
+ outputName,
132
+ exportError,
133
+ onClose,
134
+ onClickExport,
135
+ }: {
136
+ open: boolean
137
+ dialogTitle: string
138
+ dialogDescription: string
139
+ filePath: string
140
+ tsCircuitExportName: string
141
+ isExporting: boolean
142
+ outputName: string
143
+ exportError?: string | null
144
+ onClose: (value: boolean) => void
145
+ onClickExport: () => any
146
+ }) => {
147
+ return (
148
+ <Dialog open={open} onClose={onClose} className="relative z-50">
149
+ <div className="fixed inset-0 bg-black/30" aria-hidden="true" />
150
+ <div className="fixed inset-0 flex w-screen items-center justify-center p-4">
151
+ <Dialog.Panel className="w-full max-w-lg rounded bg-white p-6 border border-[rgba(0,0,0,0.3)]">
152
+ <Dialog.Title className="text-lg font-semibold tracking-tight text-black">
153
+ {dialogTitle}
154
+ </Dialog.Title>
155
+ <Dialog.Description className="text-sm font-medium text-muted-foreground">
156
+ {dialogDescription}
157
+ </Dialog.Description>
158
+ {exportError && (
159
+ <div className="text-red-700 bg-red-100 border-red-200 border whitespace-pre-wrap font-mono text-xs p-4 mt-8">
160
+ {exportError}
161
+ </div>
162
+ )}
163
+ <div className="text-xs font-medium grid grid-cols-4 font-mono mt-8 gap-4">
164
+ <div className="text-muted-foreground font-sans">Status</div>
165
+ <div className="col-span-3 text-muted-foreground font-sans">
166
+ {isExporting ? "Exporting" : "Not Started"}
167
+ </div>
168
+ <div className="text-muted-foreground font-sans">File Path</div>
169
+ <div className="col-span-3">{filePath.split("/examples/")[1]}</div>
170
+ <div className="text-muted-foreground font-sans">Export Name</div>
171
+ <div className="col-span-3">{tsCircuitExportName}</div>
172
+ <div className="text-muted-foreground font-sans">Output Name</div>
173
+ <div className="col-span-3">{outputName}</div>
174
+ </div>
175
+ <div className="flex mt-8 justify-end">
176
+ <Button
177
+ variant={isExporting ? "ghost" : "default"}
178
+ disabled={isExporting}
179
+ onClick={onClickExport}
180
+ >
181
+ {isExporting ? `Exporting...` : `Export`}
182
+ </Button>
183
+ </div>
184
+ </Dialog.Panel>
185
+ </div>
186
+ </Dialog>
187
+ )
188
+ }
@@ -5,6 +5,9 @@
5
5
  "lib": ["ES2020", "DOM", "DOM.Iterable"],
6
6
  "module": "ESNext",
7
7
  "skipLibCheck": true,
8
+ "paths": {
9
+ "@server/*": ["../dev-server-api/src/*"]
10
+ },
8
11
 
9
12
  "baseUrl": ".",
10
13