feat: implement auto-pan feature for board visibility after project load and normalize pin names for component connections

pull/10/head
David Montero Crespo 2026-03-08 16:30:49 -03:00
parent 66b4fa6030
commit 1206a34e64
3 changed files with 76 additions and 8 deletions

View File

@ -439,6 +439,37 @@ export const SimulatorCanvas = () => {
return () => timers.forEach(t => clearTimeout(t));
}, [components, recalculateAllWirePositions]);
// Auto-pan to keep the board visible after a project import/load.
// We track the previous component count and only re-center when the count
// jumps (indicating the user loaded a new circuit, not just added one part).
const prevComponentCountRef = useRef(-1);
useEffect(() => {
const prev = prevComponentCountRef.current;
const curr = components.length;
prevComponentCountRef.current = curr;
// Only re-center when the component list transitions from empty/different
// project to a populated one (i.e., a load/import event).
const isLoad = curr > 0 && (prev <= 0 || Math.abs(curr - prev) > 2);
if (!isLoad) return;
const timer = setTimeout(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const rect = canvas.getBoundingClientRect();
const currentZoom = zoomRef.current;
const newPan = {
x: rect.width / 4 - boardPosition.x * currentZoom,
y: rect.height / 4 - boardPosition.y * currentZoom,
};
panRef.current = newPan;
setPan(newPan);
}, 150);
return () => clearTimeout(timer);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [components.length]);
// Render component using dynamic renderer
const renderComponent = (component: any) => {
const metadata = registry.getById(component.metadataId);

View File

@ -497,6 +497,11 @@ export const useSimulatorStore = create<SimulatorState>((set, get) => {
);
if (startPos) {
updated.start = { ...wire.start, x: startPos.x, y: startPos.y };
} else {
// Pin name not found in element's pinInfo (e.g. board type mismatch).
// Fall back to the component/board position so the wire renders near
// its endpoint rather than at the canvas origin (0,0).
updated.start = { ...wire.start, x: startX, y: startY };
}
// Resolve end component position
@ -512,6 +517,8 @@ export const useSimulatorStore = create<SimulatorState>((set, get) => {
);
if (endPos) {
updated.end = { ...wire.end, x: endPos.x, y: endPos.y };
} else {
updated.end = { ...wire.end, x: endX, y: endY };
}
// Auto-generate control points for wires that have none

View File

@ -74,6 +74,22 @@ const BOARD_TO_WOKWI_ID: Record<string, string> = {
'raspberry-pi-pico': 'pico',
};
// ── Pin name aliases ─────────────────────────────────────────────────────────
// Maps Wokwi connection "signal" pin names to wokwi-element physical pin names.
// Wokwi boards (e.g. board-ssd1306) use different naming than the bare elements.
const COMPONENT_PIN_ALIASES: Record<string, Record<string, string>> = {
'ssd1306': {
'SDA': 'DATA',
'SCL': 'CLK',
'VCC': 'VIN',
},
};
function normalizePinName(metadataId: string, pinName: string): string {
return COMPONENT_PIN_ALIASES[metadataId]?.[pinName] ?? pinName;
}
// ── Color helpers ─────────────────────────────────────────────────────────────
const COLOR_NAME_TO_HEX: Record<string, string> = {
@ -205,20 +221,27 @@ export async function importFromWokwiZip(file: File): Promise<ImportResult> {
};
const velxioBoardId = VELXIO_BOARD_ID[boardType] ?? 'arduino-uno';
// Board position from diagram (use directly as Velxio board position)
// Board position from diagram. Apply a minimum offset so the board is never
// crammed against the canvas top-left corner (Wokwi diagrams often use 0,0).
const MIN_OFFSET = 50;
const rawBoardX = boardPart?.left ?? MIN_OFFSET;
const rawBoardY = boardPart?.top ?? MIN_OFFSET;
const offsetX = rawBoardX < MIN_OFFSET ? MIN_OFFSET - rawBoardX : 0;
const offsetY = rawBoardY < MIN_OFFSET ? MIN_OFFSET - rawBoardY : 0;
const boardPosition = {
x: boardPart?.left ?? 50,
y: boardPart?.top ?? 50,
x: rawBoardX + offsetX,
y: rawBoardY + offsetY,
};
// Convert non-board parts to Velxio components (use Wokwi coords directly)
// Convert non-board parts to Velxio components.
// Apply the same offset so components keep their relative position to the board.
const components: VelxioComponent[] = diagram.parts
.filter((p) => !WOKWI_TYPE_TO_BOARD[p.type])
.map((p) => ({
id: p.id,
metadataId: wokwiTypeToMetadataId(p.type),
x: p.left,
y: p.top,
x: p.left + offsetX,
y: p.top + offsetY,
properties: { ...p.attrs },
}));
@ -236,10 +259,17 @@ export async function importFromWokwiZip(file: File): Promise<ImportResult> {
const startId = startCompRaw === boardId ? velxioBoardId : startCompRaw;
const endId = endCompRaw === boardId ? velxioBoardId : endCompRaw;
// Normalize pin names: Wokwi uses signal names (SDA, SCL, VCC) while
// wokwi-elements use physical/board pin names (DATA, CLK, VIN).
const startMetadataId = components.find((c) => c.id === startId)?.metadataId ?? '';
const endMetadataId = components.find((c) => c.id === endId)?.metadataId ?? '';
const normalizedStartPin = normalizePinName(startMetadataId, startPin);
const normalizedEndPin = normalizePinName(endMetadataId, endPin);
return {
id: `wire-${i}-${Date.now()}`,
start: { componentId: startId, pinName: startPin, x: 0, y: 0 },
end: { componentId: endId, pinName: endPin, x: 0, y: 0 },
start: { componentId: startId, pinName: normalizedStartPin, x: 0, y: 0 },
end: { componentId: endId, pinName: normalizedEndPin, x: 0, y: 0 },
controlPoints: [],
color: colorToHex(color),
signalType: 'digital' as const,