fix(ui): crowded pin headers escaped the inspector - draw those labels vertically

An SSD1306 has 8 pins along a 240px header. Horizontal labels need ~36px
each = 288px, so they overflowed the art box and rendered outside the dialog,
over the header and the canvas. Top/bottom labels are now vertical text - what
printed pinout diagrams do - which needs only the side spacing, and the gutter
grows to fit. The preview also clips, so nothing can escape again.
This commit is contained in:
David Montero Crespo 2026-08-03 22:04:43 +02:00
parent d4a42a4e75
commit b02aab9a47
4 changed files with 43 additions and 6 deletions

View File

@ -127,6 +127,27 @@ describe('layoutInspectorPins — small parts', () => {
}); });
}); });
describe('layoutInspectorPins — a crowded header fits its edge', () => {
it('packs 8 top pins inside the art width (they are drawn as vertical text)', () => {
// The SSD1306's 8-pin header measured 240px wide in the dialog. With
// horizontal labels this demanded ~288px and the labels escaped the
// dialog; vertical labels use the side spacing instead.
const pins: InspectorPinInput[] = Array.from({ length: 8 }, (_, i) => ({
name: ['DATA', 'CLK', 'DC', 'RST', 'CS', '3V3', 'VIN', 'GND'][i],
x: 14 + i * 13,
y: 2,
}));
const r = layoutInspectorPins(pins, { width: 128, height: 119 });
for (const p of r.pins) expect(p.edge).toBe('top');
const xs = r.pins.map((p) => p.labelX).sort((a, b) => a - b);
expect(xs[0]).toBeGreaterThanOrEqual(0);
expect(xs[xs.length - 1]).toBeLessThanOrEqual(r.artWidth);
for (let i = 1; i < xs.length; i++) {
expect(xs[i] - xs[i - 1]).toBeGreaterThanOrEqual(16 - 1e-6);
}
});
});
describe('layoutInspectorPins — interior pins', () => { describe('layoutInspectorPins — interior pins', () => {
it('detects a pin far from every edge and anchors its label on the dot', () => { it('detects a pin far from every edge and anchors its label on the dot', () => {
const r = layoutInspectorPins( const r = layoutInspectorPins(

View File

@ -147,6 +147,9 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
/* Labels are laid out inside the reserved gutters; clip anything that still
overshoots rather than letting it escape over the header or the canvas. */
overflow: hidden;
} }
.pid-preview-art { .pid-preview-art {
@ -248,8 +251,16 @@
transform: translate(-100%, -50%); transform: translate(-100%, -50%);
} }
/* Top/bottom labels are VERTICAL, like a printed pinout diagram: a header row
packs pins every ~12px, where horizontal text would need ~36px and spill
out of the dialog (an 8-pin SSD1306 header is 240px wide). */
.pid-pin-label--top,
.pid-pin-label--bottom {
writing-mode: vertical-rl;
}
.pid-pin-label--top { .pid-pin-label--top {
transform: translate(-50%, -100%); transform: translate(-50%, -100%) rotate(180deg);
} }
.pid-pin-label--bottom { .pid-pin-label--bottom {

1
frontend/src/pro Symbolic link
View File

@ -0,0 +1 @@
/home/dave/velxio-prod/pro/frontend/src/pro

View File

@ -69,10 +69,14 @@ export interface PinLayoutOptions {
/** Horizontal gutter for left/right label columns (room for "GND2" etc.). */ /** Horizontal gutter for left/right label columns (room for "GND2" etc.). */
const SIDE_GUTTER = 72; const SIDE_GUTTER = 72;
/** Vertical gutter for top/bottom labels (one text line + leader). */ /**
const CAP_GUTTER = 28; * Vertical gutter for top/bottom labels. Those are drawn as VERTICAL text —
/** Top/bottom labels are horizontal text, so they need more room than 16px. */ * the way printed pinout diagrams do it — because horizontal ones do not fit:
const CAP_MIN_SPACING = 36; * an SSD1306 has 8 pins along a 240px edge, and at the ~36px a horizontal
* "DATA" needs they demand 288px and spill out of the dialog. Vertical text
* needs only the same spacing as a side column, and the gutter grows instead.
*/
const CAP_GUTTER = 52;
/** Small parts (a resistor is 107x11) are upscaled, but only this far. */ /** Small parts (a resistor is 107x11) are upscaled, but only this far. */
const MAX_UPSCALE = 2; const MAX_UPSCALE = 2;
@ -197,7 +201,7 @@ export function layoutInspectorPins(
const group = byEdge(edge); const group = byEdge(edge);
const { out, nudged } = stackLabels( const { out, nudged } = stackLabels(
group.map(([c]) => c.x), group.map(([c]) => c.x),
Math.max(minSpacing, CAP_MIN_SPACING), minSpacing,
W, W,
); );
group.forEach(([c, i], k) => { group.forEach(([c, i], k) => {