tscircuit/ti

ti tscircuit library

Version
1.0.198
License
unset
Stars
0

system-block-ui/src/rendering/render-schematic-sheets.ts

import type { AnyCircuitElement, SchematicSheet } from "circuit-json";
import { convertCircuitJsonToSchematicSvg } from "circuit-to-svg";

type CircuitToSvgOptions = NonNullable<
  Parameters<typeof convertCircuitJsonToSchematicSvg>[1]
>;

/** Options shared by every rendered sheet. Sheet selection is handled here. */
export type SchematicSvgOptions = Omit<
  CircuitToSvgOptions,
  "schematicSheetId" | "schematicSheetIndex"
>;

export interface EvaluatedSchematicSheet {
  /** Circuit JSON's stable schematic_sheet_id. */
  id: string;
  /** Internal sheet name used by generated component placement. */
  name: string;
  /** Human-readable display name, falling back to the internal name. */
  title: string;
  /** Sheet order from Circuit JSON (or source order for legacy metadata). */
  sheetIndex: number;
  svg: string;
}

type RuntimeSchematicSheet = SchematicSheet & {
  // @tscircuit/core emits this field, though the pinned circuit-json public
  // SchematicSheet interface has not yet added it.
  display_name?: unknown;
};

interface IndexedSheet {
  sourceOrder: number;
  sheet: RuntimeSchematicSheet;
  sheetIndex: number;
}

const getNonEmptyString = (value: unknown): string | undefined => {
  if (typeof value !== "string") return undefined;
  return value.trim().length > 0 ? value : undefined;
};

const getIndexedSheets = (
  circuitJson: readonly AnyCircuitElement[],
): IndexedSheet[] =>
  circuitJson
    .filter(
      (element): element is RuntimeSchematicSheet =>
        element.type === "schematic_sheet",
    )
    .map((sheet, sourceOrder) => ({
      sourceOrder,
      sheet,
      sheetIndex: Number.isFinite(sheet.sheet_index)
        ? (sheet.sheet_index as number)
        : sourceOrder,
    }))
    .sort(
      (a, b) => a.sheetIndex - b.sheetIndex || a.sourceOrder - b.sourceOrder,
    );

/**
 * Renders every Circuit JSON schematic sheet as an independent SVG, ordered by
 * sheetIndex. Designs created before schematic_sheet metadata receive one
 * backwards-compatible synthetic page.
 */
export function renderSchematicSheets(
  circuitJson: AnyCircuitElement[],
  options: SchematicSvgOptions = {},
): EvaluatedSchematicSheet[] {
  const indexedSheets = getIndexedSheets(circuitJson);

  if (indexedSheets.length === 0) {
    return [
      {
        id: "default",
        name: "Schematic",
        title: "Schematic",
        sheetIndex: 0,
        svg: convertCircuitJsonToSchematicSvg(circuitJson, options),
      },
    ];
  }

  return indexedSheets.map(({ sheet, sheetIndex }, orderedIndex) => {
    const fallbackName = `Sheet ${orderedIndex + 1}`;
    const name = getNonEmptyString(sheet.name) ?? fallbackName;
    const title = getNonEmptyString(sheet.display_name) ?? name;

    return {
      id: sheet.schematic_sheet_id,
      name,
      title,
      sheetIndex,
      svg: convertCircuitJsonToSchematicSvg(circuitJson, {
        ...options,
        schematicSheetId: sheet.schematic_sheet_id,
        schematicSheetIndex: undefined,
      }),
    };
  });
}