diff --git a/js/components.js b/js/components.js index 143545d..e801efd 100644 --- a/js/components.js +++ b/js/components.js @@ -208,7 +208,7 @@ export function importComponent(data) { } /** - * Enter component editor mode + * Enter component editor mode (new component) */ export function enterComponentEditor() { // Save current main circuit @@ -223,6 +223,7 @@ export function enterComponentEditor() { state.connections = []; state.nextId = 1; state.componentEditorActive = true; + state.editingComponentId = null; // new component, not editing existing state.placingGate = null; state.connecting = null; @@ -235,6 +236,42 @@ export function enterComponentEditor() { if (resizeCallback) resizeCallback(); } +/** + * Enter component editor to edit an existing component's blueprint. + * Loads the component's internal circuit for modification. + */ +export function editComponentBlueprint(gate) { + if (!gate.component) return; + const comp = gate.component; + + // Save current main circuit + state.savedMainCircuit = { + gates: JSON.parse(JSON.stringify(state.gates)), + connections: JSON.parse(JSON.stringify(state.connections)), + nextId: state.nextId + }; + + // Load the component's internal circuit into the canvas + state.gates = JSON.parse(JSON.stringify(comp.gates)); + state.connections = JSON.parse(JSON.stringify(comp.connections)); + // Set nextId to max existing id + 1 so new gates don't collide + state.nextId = state.gates.reduce((max, g) => Math.max(max, g.id), 0) + 1; + state.componentEditorActive = true; + state.editingComponentId = comp.id; // track which component we're editing + state.placingGate = null; + state.connecting = null; + + // Show editor overlay + const overlay = document.getElementById('component-editor-overlay'); + overlay.style.display = 'flex'; + document.getElementById('component-editor-title').textContent = `Editing Component: ${comp.name}`; + + console.log(`[component] editing blueprint of "${comp.name}" (${comp.inputCount} in, ${comp.outputCount} out)`); + + // Resize canvas to account for editor bar + if (resizeCallback) resizeCallback(); +} + /** * Exit component editor mode */ @@ -242,9 +279,26 @@ export function exitComponentEditor(name, shouldSave) { const overlay = document.getElementById('component-editor-overlay'); overlay.style.display = 'none'; + const editingId = state.editingComponentId; + if (shouldSave && name) { - // Save the component - saveComponentFromCircuit(name); + // Save the component (works for both new and edited) + const result = saveComponentFromCircuit(name); + + // If editing an existing component, update all placed instances in the main circuit + if (editingId && result.success && state.savedMainCircuit) { + const updatedComp = state.customComponents[result.component.id]; + if (updatedComp) { + for (const gate of state.savedMainCircuit.gates) { + if (gate.component && gate.component.id === editingId) { + gate.component = updatedComp; + // Clear persisted internal state so it re-initializes from updated blueprint + delete gate._internalGates; + console.log(`[component] updated instance #${gate.id} with new blueprint`); + } + } + } + } } // Restore main circuit @@ -256,6 +310,7 @@ export function exitComponentEditor(name, shouldSave) { } state.componentEditorActive = false; + state.editingComponentId = null; state.placingGate = null; // Update component buttons to show newly saved component diff --git a/js/events.js b/js/events.js index 117a0b2..cf1e540 100644 --- a/js/events.js +++ b/js/events.js @@ -8,7 +8,7 @@ import { resize, screenToWorld } from './renderer.js'; import { puzzleMode, currentLevel, showLevelPanel } from './puzzleUI.js'; import { getLevel } from './levels.js'; import { saveState, loadState, exportAsFile, importFromFile } from './saveLoad.js'; -import { enterComponentEditor, exitComponentEditor, updateComponentButtons, setResizeCallback } from './components.js'; +import { enterComponentEditor, editComponentBlueprint, exitComponentEditor, updateComponentButtons, setResizeCallback } from './components.js'; import { getExampleList, loadExample } from './examples.js'; const PAN_SPEED = 40; @@ -147,11 +147,20 @@ export function initEvents() { dragStartPos = null; }); - // Double-click to rename INPUT/OUTPUT/CLOCK gates + // Double-click to rename INPUT/OUTPUT/CLOCK gates, or edit component blueprint canvas.addEventListener('dblclick', e => { const world = screenToWorld(e.offsetX, e.offsetY); const gate = findGateAt(world.x, world.y); - if (gate && (gate.type === 'INPUT' || gate.type === 'OUTPUT' || gate.type === 'CLOCK')) { + if (!gate) return; + + // Double-click on component gate → edit its blueprint + if (gate.type.startsWith('COMPONENT:') && gate.component) { + editComponentBlueprint(gate); + return; + } + + // Double-click on I/O gates → rename + if (gate.type === 'INPUT' || gate.type === 'OUTPUT' || gate.type === 'CLOCK') { const current = gate.label || ''; const label = prompt(`Label for ${gate.type}#${gate.id}:`, current); if (label !== null) { @@ -415,7 +424,11 @@ export function initEvents() { }); document.getElementById('component-editor-save').addEventListener('click', () => { - const name = prompt('Component name:', 'MyComponent'); + // If editing existing, pre-fill with current name + const existingName = state.editingComponentId + ? (state.customComponents[state.editingComponentId]?.name || 'MyComponent') + : 'MyComponent'; + const name = prompt('Component name:', existingName); if (name && name.trim()) { exitComponentEditor(name.trim(), true); } diff --git a/js/state.js b/js/state.js index 6134f66..c2ce050 100644 --- a/js/state.js +++ b/js/state.js @@ -41,5 +41,6 @@ export const state = { // Component Editor componentEditorActive: false, savedMainCircuit: null, // { gates, connections, nextId } saved before entering editor - componentEditorName: '' + componentEditorName: '', + editingComponentId: null // ID of component being edited (null = new component) };