feat: double-click component gates to edit their blueprint
Opens the component editor with the internal circuit loaded for modification. On save, updates the component definition and all existing instances in the main circuit. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -208,7 +208,7 @@ export function importComponent(data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enter component editor mode
|
* Enter component editor mode (new component)
|
||||||
*/
|
*/
|
||||||
export function enterComponentEditor() {
|
export function enterComponentEditor() {
|
||||||
// Save current main circuit
|
// Save current main circuit
|
||||||
@@ -223,6 +223,7 @@ export function enterComponentEditor() {
|
|||||||
state.connections = [];
|
state.connections = [];
|
||||||
state.nextId = 1;
|
state.nextId = 1;
|
||||||
state.componentEditorActive = true;
|
state.componentEditorActive = true;
|
||||||
|
state.editingComponentId = null; // new component, not editing existing
|
||||||
state.placingGate = null;
|
state.placingGate = null;
|
||||||
state.connecting = null;
|
state.connecting = null;
|
||||||
|
|
||||||
@@ -235,6 +236,42 @@ export function enterComponentEditor() {
|
|||||||
if (resizeCallback) resizeCallback();
|
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
|
* Exit component editor mode
|
||||||
*/
|
*/
|
||||||
@@ -242,9 +279,26 @@ export function exitComponentEditor(name, shouldSave) {
|
|||||||
const overlay = document.getElementById('component-editor-overlay');
|
const overlay = document.getElementById('component-editor-overlay');
|
||||||
overlay.style.display = 'none';
|
overlay.style.display = 'none';
|
||||||
|
|
||||||
|
const editingId = state.editingComponentId;
|
||||||
|
|
||||||
if (shouldSave && name) {
|
if (shouldSave && name) {
|
||||||
// Save the component
|
// Save the component (works for both new and edited)
|
||||||
saveComponentFromCircuit(name);
|
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
|
// Restore main circuit
|
||||||
@@ -256,6 +310,7 @@ export function exitComponentEditor(name, shouldSave) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
state.componentEditorActive = false;
|
state.componentEditorActive = false;
|
||||||
|
state.editingComponentId = null;
|
||||||
state.placingGate = null;
|
state.placingGate = null;
|
||||||
|
|
||||||
// Update component buttons to show newly saved component
|
// Update component buttons to show newly saved component
|
||||||
|
|||||||
21
js/events.js
21
js/events.js
@@ -8,7 +8,7 @@ import { resize, screenToWorld } from './renderer.js';
|
|||||||
import { puzzleMode, currentLevel, showLevelPanel } from './puzzleUI.js';
|
import { puzzleMode, currentLevel, showLevelPanel } from './puzzleUI.js';
|
||||||
import { getLevel } from './levels.js';
|
import { getLevel } from './levels.js';
|
||||||
import { saveState, loadState, exportAsFile, importFromFile } from './saveLoad.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';
|
import { getExampleList, loadExample } from './examples.js';
|
||||||
|
|
||||||
const PAN_SPEED = 40;
|
const PAN_SPEED = 40;
|
||||||
@@ -147,11 +147,20 @@ export function initEvents() {
|
|||||||
dragStartPos = null;
|
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 => {
|
canvas.addEventListener('dblclick', e => {
|
||||||
const world = screenToWorld(e.offsetX, e.offsetY);
|
const world = screenToWorld(e.offsetX, e.offsetY);
|
||||||
const gate = findGateAt(world.x, world.y);
|
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 current = gate.label || '';
|
||||||
const label = prompt(`Label for ${gate.type}#${gate.id}:`, current);
|
const label = prompt(`Label for ${gate.type}#${gate.id}:`, current);
|
||||||
if (label !== null) {
|
if (label !== null) {
|
||||||
@@ -415,7 +424,11 @@ export function initEvents() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById('component-editor-save').addEventListener('click', () => {
|
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()) {
|
if (name && name.trim()) {
|
||||||
exitComponentEditor(name.trim(), true);
|
exitComponentEditor(name.trim(), true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,5 +41,6 @@ export const state = {
|
|||||||
// Component Editor
|
// Component Editor
|
||||||
componentEditorActive: false,
|
componentEditorActive: false,
|
||||||
savedMainCircuit: null, // { gates, connections, nextId } saved before entering editor
|
savedMainCircuit: null, // { gates, connections, nextId } saved before entering editor
|
||||||
componentEditorName: ''
|
componentEditorName: '',
|
||||||
|
editingComponentId: null // ID of component being edited (null = new component)
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user