@tscircuit/runframe 0.0.105 → 0.0.107

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/dist/preview.d.ts CHANGED
@@ -18,7 +18,7 @@ interface RenderLog {
18
18
  phaseTimings?: Record<string, number>;
19
19
  }
20
20
 
21
- type TabId = "code" | "pcb" | "schematic" | "assembly" | "cad" | "bom" | "circuitjson" | "error" | "render_log";
21
+ type TabId = "code" | "pcb" | "schematic" | "assembly" | "cad" | "bom" | "circuit_json" | "errors" | "render_log";
22
22
  interface PreviewContentProps {
23
23
  code?: string;
24
24
  readOnly?: boolean;
package/dist/preview.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  CircuitJsonPreview
3
- } from "./chunk-7F2R7RXD.js";
3
+ } from "./chunk-WUXAA7XM.js";
4
4
  export {
5
5
  CircuitJsonPreview
6
6
  };
package/dist/runner.d.ts CHANGED
@@ -16,6 +16,10 @@ interface Props {
16
16
  * The entry point file that will be executed first
17
17
  */
18
18
  entrypoint: string;
19
+ /**
20
+ * Whether to show a run button that controls when code executes
21
+ */
22
+ showRunButton?: boolean;
19
23
  /**
20
24
  * An optional left-side header, you can put a save button, a run button, or
21
25
  * a title here.
package/dist/runner.js CHANGED
@@ -1,11 +1,12 @@
1
1
  import {
2
2
  CircuitJsonPreview
3
- } from "./chunk-7F2R7RXD.js";
3
+ } from "./chunk-WUXAA7XM.js";
4
4
 
5
5
  // lib/components/RunFrame.tsx
6
6
  import { createCircuitWebWorker } from "@tscircuit/eval-webworker";
7
- import { useEffect, useRef, useState } from "react";
7
+ import { useEffect, useReducer, useRef, useState } from "react";
8
8
  import Debug2 from "debug";
9
+ import { Loader2, Play } from "lucide-react";
9
10
  import evalWebWorkerBlobUrl from "@tscircuit/eval-webworker/blob-url";
10
11
 
11
12
  // lib/components/RunFrameWithApi/store.ts
@@ -223,7 +224,7 @@ var getPhaseTimingsFromRenderEvents = (renderEvents) => {
223
224
  };
224
225
 
225
226
  // lib/components/RunFrame.tsx
226
- import { jsx } from "react/jsx-runtime";
227
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
227
228
  var numRenderPhases = 26;
228
229
  var debug3 = Debug2("run-frame:RunFrame");
229
230
  var RunFrame = (props) => {
@@ -232,6 +233,12 @@ var RunFrame = (props) => {
232
233
  s.setCircuitJson
233
234
  ]);
234
235
  const [error, setError] = useState(null);
236
+ const [runCountTrigger, incRunCountTrigger] = useReducer(
237
+ (acc, s) => acc + 1,
238
+ 0
239
+ );
240
+ const lastRunCountTriggerRef = useRef(0);
241
+ const [isRunning, setIsRunning] = useState(false);
235
242
  const [renderLog, setRenderLog] = useState(null);
236
243
  const [activeTab, setActiveTab] = useState(
237
244
  props.defaultActiveTab ?? "pcb"
@@ -247,19 +254,24 @@ var RunFrame = (props) => {
247
254
  const lastEntrypointRef = useRef(null);
248
255
  useEffect(() => {
249
256
  if (!fsMap) return;
257
+ const wasTriggeredByRunButton = props.showRunButton && runCountTrigger !== lastRunCountTriggerRef.current;
250
258
  if (lastFsMapRef.current && circuitJson) {
251
259
  const changes = getChangesBetweenFsMaps(lastFsMapRef.current, fsMap);
252
260
  if (Object.keys(changes).length > 0) {
253
- debug3("render triggered by changes:", changes);
261
+ debug3("code changes detected");
254
262
  } else if (lastEntrypointRef.current !== props.entrypoint) {
255
263
  debug3("render triggered by entrypoint change");
256
- } else {
264
+ } else if (!wasTriggeredByRunButton) {
257
265
  debug3("render triggered without changes to fsMap, skipping");
258
266
  return;
259
267
  }
260
268
  }
269
+ if (props.showRunButton && runCountTrigger === lastRunCountTriggerRef.current) {
270
+ return;
271
+ }
261
272
  lastFsMapRef.current = fsMap;
262
273
  lastEntrypointRef.current = props.entrypoint;
274
+ setIsRunning(true);
263
275
  async function runWorker() {
264
276
  debug3("running render worker");
265
277
  setError(null);
@@ -322,6 +334,7 @@ var RunFrame = (props) => {
322
334
  debug3("error getting initial circuit json", e);
323
335
  props.onError?.(e);
324
336
  setError({ error: e.message, stack: e.stack });
337
+ setIsRunning(false);
325
338
  return null;
326
339
  });
327
340
  if (!circuitJson2) return;
@@ -342,14 +355,33 @@ var RunFrame = (props) => {
342
355
  }
343
356
  renderLog2.progress = 1;
344
357
  setRenderLog({ ...renderLog2 });
358
+ setIsRunning(false);
345
359
  }
346
360
  runWorker();
347
- }, [props.fsMap, props.entrypoint]);
361
+ }, [props.fsMap, props.entrypoint, runCountTrigger]);
348
362
  return /* @__PURE__ */ jsx(
349
363
  CircuitJsonPreview,
350
364
  {
351
365
  defaultActiveTab: props.defaultActiveTab,
352
- leftHeaderContent: props.leftHeaderContent,
366
+ leftHeaderContent: /* @__PURE__ */ jsxs(Fragment, { children: [
367
+ props.showRunButton && /* @__PURE__ */ jsxs(
368
+ "button",
369
+ {
370
+ type: "button",
371
+ onClick: () => {
372
+ incRunCountTrigger(1);
373
+ },
374
+ className: "rf-flex rf-items-center rf-gap-2 rf-px-4 rf-py-2 rf-bg-blue-600 rf-text-white rf-rounded-md rf-mr-2 disabled:rf-opacity-50",
375
+ disabled: isRunning,
376
+ children: [
377
+ "Run",
378
+ " ",
379
+ isRunning ? /* @__PURE__ */ jsx(Loader2, { className: "rf-w-3 rf-h-3 rf-animate-spin" }) : /* @__PURE__ */ jsx(Play, { className: "rf-w-3 rf-h-3" })
380
+ ]
381
+ }
382
+ ),
383
+ props.leftHeaderContent
384
+ ] }),
353
385
  onActiveTabChange: setActiveTab,
354
386
  circuitJson,
355
387
  renderLog,
@@ -422,7 +454,7 @@ var useEditEventController = () => {
422
454
 
423
455
  // lib/components/RunFrameWithApi/RunFrameWithApi.tsx
424
456
  import Debug3 from "debug";
425
- import { jsx as jsx2, jsxs } from "react/jsx-runtime";
457
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
426
458
  var debug5 = Debug3("run-frame:RunFrameWithApi");
427
459
  var guessEntrypoint = (files) => files.find((file) => file.includes("entrypoint.")) ?? files.find((file) => file.includes("index.")) ?? files.find((file) => file.includes("main.")) ?? files.find((file) => file.endsWith(".tsx"));
428
460
  var RunFrameWithApi = (props) => {
@@ -463,7 +495,7 @@ var RunFrameWithApi = (props) => {
463
495
  });
464
496
  }, [entrypoint]);
465
497
  if (!fsMap.get(entrypoint)) {
466
- return /* @__PURE__ */ jsxs("div", { children: [
498
+ return /* @__PURE__ */ jsxs2("div", { children: [
467
499
  "No entrypoint found for Run Frame! Found files:",
468
500
  " ",
469
501
  Array.from(fsMap.keys()).join(",")
@@ -504,7 +536,7 @@ import { useEffect as useEffect5, useMemo as useMemo3, useState as useState5 } f
504
536
 
505
537
  // lib/components/RunFrameForCli/SelectSnippetDialog.tsx
506
538
  import { useState as useState4 } from "react";
507
- import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
539
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
508
540
  var SelectSnippetDialog = ({
509
541
  snippetNames,
510
542
  onSelect,
@@ -527,20 +559,20 @@ var SelectSnippetDialog = ({
527
559
  }
528
560
  }
529
561
  };
530
- return /* @__PURE__ */ jsx3("div", { className: "fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-[100]", children: /* @__PURE__ */ jsxs2("div", { className: "bg-white rounded-lg p-6 w-96", children: [
531
- /* @__PURE__ */ jsx3("h2", { className: "text-lg font-semibold mb-4", children: "Select Snippet" }),
562
+ return /* @__PURE__ */ jsx3("div", { className: "rf-fixed rf-inset-0 rf-bg-black rf-bg-opacity-50 rf-flex rf-items-center rf-justify-center rf-z-[100]", children: /* @__PURE__ */ jsxs3("div", { className: "rf-bg-white rf-rounded-lg rf-p-6 rf-w-96", children: [
563
+ /* @__PURE__ */ jsx3("h2", { className: "rf-text-lg rf-font-semibold rf-mb-4", children: "Select Snippet" }),
532
564
  /* @__PURE__ */ jsx3(
533
565
  "input",
534
566
  {
535
567
  type: "text",
536
- className: "w-full px-4 py-2 border rounded mb-4",
568
+ className: "rf-w-full rf-px-4 rf-py-2 rf-border rf-rounded rf-mb-4",
537
569
  placeholder: "Search snippets or new snippet name...",
538
570
  value: searchInput,
539
571
  onChange: (e) => setSearchInput(e.target.value),
540
572
  onKeyDown: handleKeyDown
541
573
  }
542
574
  ),
543
- /* @__PURE__ */ jsxs2("div", { className: "h-60 overflow-y-auto", children: [
575
+ /* @__PURE__ */ jsxs3("div", { className: "rf-h-60 rf-overflow-y-auto", children: [
544
576
  filteredSnippets.map((name) => /* @__PURE__ */ jsx3(
545
577
  "button",
546
578
  {
@@ -551,7 +583,7 @@ var SelectSnippetDialog = ({
551
583
  },
552
584
  name
553
585
  )),
554
- showCreateNew && /* @__PURE__ */ jsxs2(
586
+ showCreateNew && /* @__PURE__ */ jsxs3(
555
587
  "button",
556
588
  {
557
589
  type: "button",
@@ -565,12 +597,12 @@ var SelectSnippetDialog = ({
565
597
  }
566
598
  )
567
599
  ] }),
568
- /* @__PURE__ */ jsxs2("div", { className: "mt-4 flex justify-end gap-2", children: [
600
+ /* @__PURE__ */ jsxs3("div", { className: "rf-mt-4 rf-flex rf-justify-end rf-gap-2", children: [
569
601
  /* @__PURE__ */ jsx3(
570
602
  "button",
571
603
  {
572
604
  type: "button",
573
- className: "px-4 py-2 text-gray-600 hover:text-gray-800",
605
+ className: "rf-px-4 rf-py-2 rf-text-gray-600 rf-hover:text-gray-800",
574
606
  onClick: onCancel,
575
607
  children: "Cancel"
576
608
  }
@@ -579,7 +611,7 @@ var SelectSnippetDialog = ({
579
611
  "button",
580
612
  {
581
613
  type: "button",
582
- className: "px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 disabled:opacity-50",
614
+ className: "rf-px-4 rf-py-2 rf-bg-blue-500 rf-text-white rf-rounded rf-hover:bg-blue-600 rf-disabled:opacity-50",
583
615
  onClick: () => selectedName && onSelect(selectedName),
584
616
  disabled: !selectedName,
585
617
  children: selectedName === searchInput ? `Create "${selectedName}"` : `Save to ${selectedName}`
@@ -606,7 +638,7 @@ var useEventHandler = (callback) => {
606
638
 
607
639
  // lib/components/RunFrameForCli/SaveToSnippetsButton.tsx
608
640
  import { CheckCircle2 } from "lucide-react";
609
- import { Fragment, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
641
+ import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
610
642
  var SaveToSnippetsButton = () => {
611
643
  const [snippetName, setSnippetName] = useState5(null);
612
644
  const [hasUnsavedChanges, setHasUnsavedChanges] = useState5(false);
@@ -659,8 +691,8 @@ var SaveToSnippetsButton = () => {
659
691
  setRequestToSaveSentAt(null);
660
692
  }
661
693
  }, [recentEvents, isSaving]);
662
- return /* @__PURE__ */ jsxs3(Fragment, { children: [
663
- /* @__PURE__ */ jsxs3(
694
+ return /* @__PURE__ */ jsxs4(Fragment2, { children: [
695
+ /* @__PURE__ */ jsxs4(
664
696
  "button",
665
697
  {
666
698
  type: "button",
@@ -676,7 +708,7 @@ var SaveToSnippetsButton = () => {
676
708
  className: `px-3 flex items-center text-sm py-1.5 rounded-md ${isSaving ? "cursor-not-allowed" : "bg-white border border-gray-300 font-medium text-gray-900 shadow-sm"}`,
677
709
  children: [
678
710
  isSaving ? "Saving..." : "Save to Snippets",
679
- isSaving || hasNeverBeenSaved ? null : /* @__PURE__ */ jsx4("span", { className: "ml-1.5 flex items-center", children: hasUnsavedChanges ? /* @__PURE__ */ jsx4("div", { className: "text-xs bg-orange-400 text-white p-0.5 px-1.5 rounded", children: "unsaved changes" }) : /* @__PURE__ */ jsx4(CheckCircle2, { className: "w-4 h-4 text-green-500" }) })
711
+ isSaving || hasNeverBeenSaved ? null : /* @__PURE__ */ jsx4("span", { className: "rf-ml-1.5 rf-flex rf-items-center", children: hasUnsavedChanges ? /* @__PURE__ */ jsx4("div", { className: "rf-text-xs rf-bg-orange-400 rf-text-white rf-p-0.5 rf-px-1.5 rf-rounded", children: "unsaved changes" }) : /* @__PURE__ */ jsx4(CheckCircle2, { className: "rf-w-4 rf-h-4 rf-text-green-500" }) })
680
712
  ]
681
713
  }
682
714
  ),