feat: Enhance board positioning by integrating boardPosition state in export/import functions and updating component dragging logic

pull/10/head
David Montero Crespo 2026-03-07 19:29:23 -03:00
parent 5f2c176648
commit f2b275c03d
4 changed files with 85 additions and 37 deletions

View File

@ -97,9 +97,9 @@ export const EditorToolbar = ({ consoleOpen, setConsoleOpen, compileLogs: _compi
const handleExport = async () => {
try {
const { components, wires } = useSimulatorStore.getState();
const { components, wires, boardPosition } = useSimulatorStore.getState();
const projectName = files.find((f) => f.name.endsWith('.ino'))?.name.replace('.ino', '') || 'velxio-project';
await exportToWokwiZip(files, components, wires, boardType, projectName);
await exportToWokwiZip(files, components, wires, boardType, projectName, boardPosition);
} catch (err) {
setMessage({ type: 'error', text: 'Export failed.' });
}
@ -113,9 +113,10 @@ export const EditorToolbar = ({ consoleOpen, setConsoleOpen, compileLogs: _compi
try {
const result = await importFromWokwiZip(file);
const { loadFiles } = useEditorStore.getState();
const { setComponents, setWires, setBoardType, stopSimulation } = useSimulatorStore.getState();
const { setComponents, setWires, setBoardType, setBoardPosition, stopSimulation } = useSimulatorStore.getState();
stopSimulation();
if (result.boardType) setBoardType(result.boardType);
setBoardPosition(result.boardPosition);
setComponents(result.components);
setWires(result.wires);
if (result.files.length > 0) loadFiles(result.files);

View File

@ -1,4 +1,4 @@
import { useSimulatorStore, ARDUINO_POSITION, BOARD_LABELS } from '../../store/useSimulatorStore';
import { useSimulatorStore, BOARD_LABELS } from '../../store/useSimulatorStore';
import type { BoardType } from '../../store/useSimulatorStore';
import React, { useEffect, useState, useRef, useCallback } from 'react';
import { ArduinoUno } from '../components-wokwi/ArduinoUno';
@ -19,6 +19,8 @@ export const SimulatorCanvas = () => {
const {
boardType,
setBoardType,
boardPosition,
setBoardPosition,
components,
running,
pinManager,
@ -289,11 +291,18 @@ export const SimulatorCanvas = () => {
// Handle component dragging
if (draggedComponentId) {
const world = toWorld(e.clientX, e.clientY);
if (draggedComponentId === '__board__') {
setBoardPosition({
x: Math.max(0, world.x - dragOffset.x),
y: Math.max(0, world.y - dragOffset.y),
});
} else {
updateComponent(draggedComponentId, {
x: Math.max(0, world.x - dragOffset.x),
y: Math.max(0, world.y - dragOffset.y),
} as any);
}
}
// Handle wire creation preview
if (wireInProgress) {
@ -317,7 +326,7 @@ export const SimulatorCanvas = () => {
Math.pow(e.clientY - clickStartPos.y, 2)
);
if (posDiff < 5 && timeDiff < 300) {
if (posDiff < 5 && timeDiff < 300 && draggedComponentId !== '__board__') {
const component = components.find((c) => c.id === draggedComponentId);
if (component) {
setPropertyDialogComponentId(draggedComponentId);
@ -574,23 +583,47 @@ export const SimulatorCanvas = () => {
{/* Board visual — switches based on selected board type */}
{boardType === 'arduino-uno' ? (
<ArduinoUno
x={ARDUINO_POSITION.x}
y={ARDUINO_POSITION.y}
x={boardPosition.x}
y={boardPosition.y}
led13={Boolean(components.find((c) => c.id === 'led-builtin')?.properties.state)}
/>
) : (
<NanoRP2040
x={ARDUINO_POSITION.x}
y={ARDUINO_POSITION.y}
x={boardPosition.x}
y={boardPosition.y}
ledBuiltIn={Boolean(components.find((c) => c.id === 'led-builtin')?.properties.state)}
/>
)}
{/* Board interaction overlay for dragging */}
{!running && (
<div
style={{
position: 'absolute',
left: boardPosition.x,
top: boardPosition.y,
width: boardType === 'arduino-uno' ? 360 : 280,
height: boardType === 'arduino-uno' ? 250 : 180,
cursor: 'move',
zIndex: 1,
}}
onMouseDown={(e) => {
e.stopPropagation();
const world = toWorld(e.clientX, e.clientY);
setDraggedComponentId('__board__');
setDragOffset({
x: world.x - boardPosition.x,
y: world.y - boardPosition.y,
});
}}
/>
)}
{/* Board pin overlay */}
<PinOverlay
componentId={boardType === 'arduino-uno' ? 'arduino-uno' : 'nano-rp2040'}
componentX={ARDUINO_POSITION.x}
componentY={ARDUINO_POSITION.y}
componentX={boardPosition.x}
componentY={boardPosition.y}
onPinClick={handlePinClick}
showPins={true}
wrapperOffsetX={0}

View File

@ -19,8 +19,10 @@ export const BOARD_LABELS: Record<BoardType, string> = {
'raspberry-pi-pico': 'Raspberry Pi Pico',
};
// Fixed position for the Arduino board (not in components array)
export const ARDUINO_POSITION = { x: 50, y: 50 };
// Default position for the Arduino board
export const DEFAULT_BOARD_POSITION = { x: 50, y: 50 };
// Keep legacy export alias for any remaining references
export const ARDUINO_POSITION = DEFAULT_BOARD_POSITION;
interface Component {
id: string;
@ -35,6 +37,10 @@ interface SimulatorState {
boardType: BoardType;
setBoardType: (type: BoardType) => void;
// Board position (mutable — allows dragging)
boardPosition: { x: number; y: number };
setBoardPosition: (pos: { x: number; y: number }) => void;
// Simulation state
simulator: AVRSimulator | RP2040Simulator | null;
pinManager: PinManager;
@ -102,6 +108,7 @@ export const useSimulatorStore = create<SimulatorState>((set, get) => {
return {
boardType: 'arduino-uno' as BoardType,
boardPosition: { ...DEFAULT_BOARD_POSITION },
simulator: null,
pinManager,
running: false,
@ -168,6 +175,10 @@ export const useSimulatorStore = create<SimulatorState>((set, get) => {
serialBaudRate: 0,
serialMonitorOpen: false,
setBoardPosition: (pos) => {
set({ boardPosition: pos });
},
setBoardType: (type: BoardType) => {
const { running } = get();
if (running) {
@ -426,9 +437,10 @@ export const useSimulatorStore = create<SimulatorState>((set, get) => {
updateWirePositions: (componentId) => {
set((state) => {
const component = state.components.find((c) => c.id === componentId);
// For fixed components like Arduino, use ARDUINO_POSITION
const compX = component ? component.x : ARDUINO_POSITION.x;
const compY = component ? component.y : ARDUINO_POSITION.y;
// For the board, use boardPosition from state
const bp = state.boardPosition;
const compX = component ? component.x : bp.x;
const compY = component ? component.y : bp.y;
const updatedWires = state.wires.map((wire) => {
const updated = { ...wire };
@ -471,8 +483,9 @@ export const useSimulatorStore = create<SimulatorState>((set, get) => {
const updatedWires = state.wires.map((wire) => {
const updated = { ...wire };
const startComp = state.components.find((c) => c.id === wire.start.componentId);
const startX = startComp ? startComp.x : ARDUINO_POSITION.x;
const startY = startComp ? startComp.y : ARDUINO_POSITION.y;
const bp = state.boardPosition;
const startX = startComp ? startComp.x : bp.x;
const startY = startComp ? startComp.y : bp.y;
const startPos = calculatePinPosition(
wire.start.componentId,
@ -486,8 +499,8 @@ export const useSimulatorStore = create<SimulatorState>((set, get) => {
// Resolve end component position
const endComp = state.components.find((c) => c.id === wire.end.componentId);
const endX = endComp ? endComp.x : ARDUINO_POSITION.x;
const endY = endComp ? endComp.y : ARDUINO_POSITION.y;
const endX = endComp ? endComp.x : bp.x;
const endY = endComp ? endComp.y : bp.y;
const endPos = calculatePinPosition(
wire.end.componentId,

View File

@ -14,7 +14,6 @@
import JSZip from 'jszip';
import type { Wire } from '../types/wire';
import { ARDUINO_POSITION } from '../store/useSimulatorStore';
// ── Type definitions ──────────────────────────────────────────────────────────
@ -45,6 +44,7 @@ export interface VelxioComponent {
export interface ImportResult {
boardType: 'arduino-uno' | 'raspberry-pi-pico';
boardPosition: { x: number; y: number };
components: VelxioComponent[];
wires: Wire[];
files: Array<{ name: string; content: string }>;
@ -118,6 +118,7 @@ export async function exportToWokwiZip(
wires: Wire[],
boardType: string,
projectName: string,
boardPosition: { x: number; y: number } = { x: 50, y: 50 },
): Promise<void> {
const zip = new JSZip();
@ -125,14 +126,14 @@ export async function exportToWokwiZip(
const boardId = BOARD_TO_WOKWI_ID[boardType] ?? 'uno';
// Build parts — board first, then user components
// Subtract ARDUINO_POSITION to convert from Velxio coords to Wokwi-relative coords
// Subtract boardPosition so coords are relative to the board
const parts: WokwiPart[] = [
{ type: boardWokwiType, id: boardId, top: 0, left: 0, attrs: {} },
...components.map((c) => ({
type: metadataIdToWokwiType(c.metadataId),
id: c.id,
top: Math.round(c.y - ARDUINO_POSITION.y),
left: Math.round(c.x - ARDUINO_POSITION.x),
top: Math.round(c.y - boardPosition.y),
left: Math.round(c.x - boardPosition.x),
attrs: c.properties as Record<string, unknown>,
})),
];
@ -192,20 +193,20 @@ export async function importFromWokwiZip(file: File): Promise<ImportResult> {
const boardType = boardPart ? WOKWI_TYPE_TO_BOARD[boardPart.type] : 'arduino-uno';
const boardId = boardPart?.id ?? 'uno';
// Calculate offset: Wokwi board position → Velxio ARDUINO_POSITION
const wokwiBoardX = boardPart?.left ?? 0;
const wokwiBoardY = boardPart?.top ?? 0;
const offsetX = ARDUINO_POSITION.x - wokwiBoardX;
const offsetY = ARDUINO_POSITION.y - wokwiBoardY;
// Board position from diagram (use directly as Velxio board position)
const boardPosition = {
x: boardPart?.left ?? 50,
y: boardPart?.top ?? 50,
};
// Convert non-board parts to Velxio components (apply offset)
// Convert non-board parts to Velxio components (use Wokwi coords directly)
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 + offsetX,
y: p.top + offsetY,
x: p.left,
y: p.top,
properties: { ...p.attrs },
}));
@ -257,5 +258,5 @@ export async function importFromWokwiZip(file: File): Promise<ImportResult> {
return a.name.localeCompare(b.name);
});
return { boardType, components, wires, files };
return { boardType, boardPosition, components, wires, files };
}