tscircuit/dataset-srj12-bus-routing

The code defines electronic circuit diagrams featuring PCB layouts with USB-C, HDMI, and general-purpose chips, connecting their pins with traces, component footprints, and annotations for hardware design.

Version
1.0.6
License
unset
Stars
0

fixtures/sample.fixture.jsx

import { AutoroutingPipelineSolver } from "@tscircuit/capacity-autorouter";
import { getSvgFromGraphicsObject } from "graphics-debug";
import { useEffect, useMemo, useState } from "react";
import sample001 from "../circuits/sample001/sample001.circuit.simple-route.json";
import sample002 from "../circuits/sample002/sample002.circuit.simple-route.json";
import sample003 from "../circuits/sample003/sample003.circuit.simple-route.json";
import sample004 from "../circuits/sample004/sample004.circuit.simple-route.json";
import sample005 from "../circuits/sample005/sample005.circuit.simple-route.json";
import sample006 from "../circuits/sample006/sample006.circuit.simple-route.json";
import sample007 from "../circuits/sample007/sample007.circuit.simple-route.json";
import sample008 from "../circuits/sample008/sample008.circuit.simple-route.json";
import sample009 from "../circuits/sample009/sample009.circuit.simple-route.json";
import sample010 from "../circuits/sample010/sample010.circuit.simple-route.json";

const samples = [
	{ id: "sample001", data: sample001 },
	{ id: "sample002", data: sample002 },
	{ id: "sample003", data: sample003 },
	{ id: "sample004", data: sample004 },
	{ id: "sample005", data: sample005 },
	{ id: "sample006", data: sample006 },
	{ id: "sample007", data: sample007 },
	{ id: "sample008", data: sample008 },
	{ id: "sample009", data: sample009 },
	{ id: "sample010", data: sample010 },
];

const defaultSampleId = samples[0].id;

function getUrlWindow() {
	if (typeof window === "undefined") return null;

	try {
		if (
			window.parent !== window &&
			window.parent.location.origin === window.location.origin
		) {
			return window.parent;
		}
	} catch {
		// Fall back to the fixture window if the parent is not same-origin.
	}

	return window;
}

function getSampleIdFromUrl() {
	const urlWindow = getUrlWindow();
	if (!urlWindow) return defaultSampleId;

	const requestedId = new URL(urlWindow.location.href).searchParams.get(
		"sample",
	);
	return samples.some(({ id }) => id === requestedId)
		? requestedId
		: defaultSampleId;
}

function writeSampleIdToUrl(sampleId, mode) {
	const urlWindow = getUrlWindow();
	if (!urlWindow) return;

	const url = new URL(urlWindow.location.href);
	if (url.searchParams.get("sample") === sampleId) return;

	url.searchParams.set("sample", sampleId);
	urlWindow.history[mode]({}, "", url);
}

export default function SampleFixture() {
	const [selectedSampleId, setSelectedSampleId] = useState(getSampleIdFromUrl);
	const selectedIndex = samples.findIndex(({ id }) => id === selectedSampleId);
	const selectedSample = samples[selectedIndex] ?? samples[0];

	useEffect(() => {
		writeSampleIdToUrl(selectedSample.id, "replaceState");

		const urlWindow = getUrlWindow();
		if (!urlWindow) return;

		const handlePopState = () => setSelectedSampleId(getSampleIdFromUrl());
		urlWindow.addEventListener("popstate", handlePopState);
		return () => urlWindow.removeEventListener("popstate", handlePopState);
	}, [selectedSample.id]);

	const svg = useMemo(() => {
		const boundsWidth =
			selectedSample.data.bounds.maxX - selectedSample.data.bounds.minX;
		const boundsHeight =
			selectedSample.data.bounds.maxY - selectedSample.data.bounds.minY;
		const svgHeight = 1000;
		const svgWidth = Math.round(svgHeight * (boundsWidth / boundsHeight));
		const solver = new AutoroutingPipelineSolver(
			structuredClone(selectedSample.data),
		);
		return getSvgFromGraphicsObject(solver.visualize(), {
			backgroundColor: "#ffffff",
			svgWidth,
			svgHeight,
		});
	}, [selectedSample.data]);

	const selectSample = (sampleId) => {
		if (sampleId === selectedSample.id) return;
		setSelectedSampleId(sampleId);
		writeSampleIdToUrl(sampleId, "pushState");
	};

	const selectSampleNumber = (sampleNumber) => {
		if (
			!Number.isInteger(sampleNumber) ||
			sampleNumber < 1 ||
			sampleNumber > samples.length
		) {
			return;
		}

		selectSample(samples[sampleNumber - 1].id);
	};

	return (
		<main className="srj12-browser">
			<style>{`
				html, body, #root {
					width: 100%;
					height: 100%;
					margin: 0;
				}

				* {
					box-sizing: border-box;
				}

				.srj12-browser {
					display: flex;
					flex-direction: column;
					gap: 14px;
					width: 100%;
					height: 100vh;
					min-height: 420px;
					padding: clamp(12px, 2.5vw, 24px);
					overflow: hidden;
					background: #f3f5f7;
					color: #17202a;
					font-family: Inter, ui-sans-serif, system-ui, sans-serif;
				}

				.srj12-header {
					display: flex;
					flex: 0 0 auto;
					flex-wrap: wrap;
					align-items: end;
					justify-content: space-between;
					gap: 12px 20px;
				}

				.srj12-heading {
					margin: 0;
					font-size: clamp(20px, 3vw, 30px);
					line-height: 1.1;
				}

				.srj12-summary {
					margin: 5px 0 0;
					color: #59636e;
					font-size: 14px;
				}

				.srj12-controls {
					display: flex;
					align-items: end;
					gap: 8px;
				}

				.srj12-input-label {
					display: grid;
					gap: 4px;
					color: #59636e;
					font-size: 12px;
					font-weight: 650;
					letter-spacing: 0.04em;
					text-transform: uppercase;
				}

				.srj12-input,
				.srj12-button {
					height: 38px;
					border: 1px solid #c7cdd4;
					border-radius: 8px;
					background: #ffffff;
					color: #17202a;
					font: inherit;
				}

				.srj12-input {
					width: 76px;
					padding: 0 8px;
					font-variant-numeric: tabular-nums;
				}

				.srj12-button {
					width: 38px;
					padding: 0;
					font-size: 20px;
					line-height: 1;
					cursor: pointer;
				}

				.srj12-button:hover:not(:disabled),
				.srj12-input:hover {
					border-color: #74808c;
				}

				.srj12-button:focus-visible,
				.srj12-input:focus-visible {
					outline: 3px solid rgba(37, 99, 235, 0.25);
					outline-offset: 1px;
				}

				.srj12-button:disabled {
					cursor: default;
					opacity: 0.35;
				}

				.srj12-canvas {
					flex: 1 1 auto;
					min-width: 0;
					min-height: 0;
					overflow: hidden;
					border: 1px solid #d9dee3;
					border-radius: 12px;
					background: #ffffff;
					box-shadow: 0 3px 14px rgba(23, 32, 42, 0.07);
				}

				.srj12-svg,
				.srj12-svg > svg {
					display: block;
					width: 100%;
					height: 100%;
					max-width: none;
				}

				@media (max-width: 560px) {
					.srj12-header,
					.srj12-controls {
						align-items: stretch;
					}

					.srj12-controls {
						width: 100%;
					}

					.srj12-input-label {
						flex: 1;
					}

					.srj12-input {
						width: 100%;
						min-width: 0;
					}
				}
			`}</style>

			<header className="srj12-header">
				<div>
					<h1 className="srj12-heading">SRJ12 · Bus routing</h1>
					<p className="srj12-summary" aria-live="polite">
						{selectedSample.id} · {selectedSample.data.obstacles.length}{" "}
						obstacles
						{" · "}
						{selectedSample.data.connections.length} connections
					</p>
				</div>

				<div className="srj12-controls">
					<button
						className="srj12-button"
						type="button"
						disabled={selectedIndex === 0}
						onClick={() => selectSample(samples[selectedIndex - 1].id)}
						aria-label="Previous sample"
					>
						‹
					</button>

					<label className="srj12-input-label">
						Sample
						<input
							className="srj12-input"
							type="number"
							min="1"
							max={samples.length}
							step="1"
							inputMode="numeric"
							value={selectedIndex + 1}
							onChange={(event) =>
								selectSampleNumber(event.target.valueAsNumber)
							}
						/>
					</label>

					<button
						className="srj12-button"
						type="button"
						disabled={selectedIndex === samples.length - 1}
						onClick={() => selectSample(samples[selectedIndex + 1].id)}
						aria-label="Next sample"
					>
						›
					</button>
				</div>
			</header>

			<div
				className="srj12-canvas"
				role="img"
				aria-label={`${selectedSample.id} bus-routing visualization`}
			>
				{/* biome-ignore lint/security/noDangerouslySetInnerHtml: This SVG is generated locally from checked-in SRJ data. */}
				<div dangerouslySetInnerHTML={{ __html: svg }} className="srj12-svg" />
			</div>
		</main>
	);
}