Add house customization UI with create/edit houses and room management

Adds HouseEditor panel to the right sidebar with:
- Edit house name and description
- Adjust building footprint dimensions
- Add/remove floors with ceiling height control
- Add/remove rooms with name, type, and dimensions
- Edit selected room: name, type, dimensions, position, flooring
- Save house as JSON template
- Create new empty house from scratch
This commit is contained in:
m
2026-02-07 16:31:55 +01:00
parent 4ca495209d
commit bc94d41f2b
2 changed files with 858 additions and 1 deletions

681
src/house-editor.js Normal file
View File

@@ -0,0 +1,681 @@
/**
* HouseEditor — UI panel for house customization.
*
* Provides controls to create/edit houses, add/remove rooms,
* adjust dimensions, manage floors, and save as templates.
*/
export class HouseEditor {
constructor(container, { renderer, onHouseChanged }) {
this.container = container;
this.renderer = renderer;
this.onHouseChanged = onHouseChanged || (() => {});
this._editing = false;
this._selectedRoomId = null;
this.render();
}
get houseData() {
return this.renderer.houseData;
}
render() {
this.container.innerHTML = '';
if (!this.houseData) {
this.container.innerHTML = '<p style="color:#999;font-size:12px;">No house loaded</p>';
return;
}
// Toggle button
const toggleBtn = document.createElement('button');
toggleBtn.className = 'he-toggle-btn';
toggleBtn.textContent = this._editing ? 'Close Editor' : 'Edit House';
toggleBtn.addEventListener('click', () => {
this._editing = !this._editing;
this.render();
});
this.container.appendChild(toggleBtn);
if (!this._editing) return;
// House metadata section
this._renderMetadataSection();
// Building section
this._renderBuildingSection();
// Floor management
this._renderFloorSection();
// Room list with editing
this._renderRoomSection();
// Room editor (if a room is selected)
if (this._selectedRoomId) {
this._renderRoomEditor();
}
// Save as template
this._renderSaveSection();
}
setSelectedRoom(roomId) {
this._selectedRoomId = roomId;
if (this._editing) {
this.render();
}
}
// ---- Metadata Section ----
_renderMetadataSection() {
const section = this._createSection('House Info');
const nameRow = this._createFieldRow('Name');
const nameInput = document.createElement('input');
nameInput.className = 'he-input';
nameInput.value = this.houseData.name || '';
nameInput.addEventListener('change', () => {
this.houseData.name = nameInput.value;
this.onHouseChanged('name');
});
nameRow.appendChild(nameInput);
section.appendChild(nameRow);
const descRow = this._createFieldRow('Description');
const descInput = document.createElement('input');
descInput.className = 'he-input';
descInput.value = this.houseData.description || '';
descInput.addEventListener('change', () => {
this.houseData.description = descInput.value;
this.onHouseChanged('description');
});
descRow.appendChild(descInput);
section.appendChild(descRow);
this.container.appendChild(section);
}
// ---- Building Section ----
_renderBuildingSection() {
const section = this._createSection('Building');
const building = this.houseData.building || {};
const footprint = building.footprint || {};
const widthRow = this._createFieldRow('Width (m)');
const widthInput = this._createNumberInput(footprint.width || 12, 4, 30, 0.5, (val) => {
if (!this.houseData.building) this.houseData.building = {};
if (!this.houseData.building.footprint) this.houseData.building.footprint = {};
this.houseData.building.footprint.width = val;
this.onHouseChanged('building');
});
widthRow.appendChild(widthInput);
section.appendChild(widthRow);
const depthRow = this._createFieldRow('Depth (m)');
const depthInput = this._createNumberInput(footprint.depth || 10, 4, 30, 0.5, (val) => {
if (!this.houseData.building) this.houseData.building = {};
if (!this.houseData.building.footprint) this.houseData.building.footprint = {};
this.houseData.building.footprint.depth = val;
this.onHouseChanged('building');
});
depthRow.appendChild(depthInput);
section.appendChild(depthRow);
this.container.appendChild(section);
}
// ---- Floor Section ----
_renderFloorSection() {
const section = this._createSection('Floors');
const floors = this.houseData.floors || [];
for (let i = 0; i < floors.length; i++) {
const floor = floors[i];
const row = document.createElement('div');
row.className = 'he-floor-row';
const label = document.createElement('span');
label.className = 'he-floor-label';
label.textContent = `${floor.name} (${floor.rooms.length} rooms)`;
row.appendChild(label);
// Ceiling height
const heightInput = this._createNumberInput(floor.ceilingHeight || 2.5, 2.2, 4.0, 0.1, (val) => {
floor.ceilingHeight = val;
this._rebuildFloor();
});
heightInput.title = 'Ceiling height';
heightInput.style.width = '55px';
row.appendChild(heightInput);
// Remove floor button (only if more than 1 floor)
if (floors.length > 1) {
const removeBtn = document.createElement('button');
removeBtn.className = 'he-icon-btn he-icon-btn-danger';
removeBtn.textContent = '\u00d7';
removeBtn.title = 'Remove floor';
removeBtn.addEventListener('click', () => this._removeFloor(i));
row.appendChild(removeBtn);
}
section.appendChild(row);
}
const addBtn = document.createElement('button');
addBtn.className = 'he-add-btn';
addBtn.textContent = '+ Add Floor';
addBtn.addEventListener('click', () => this._addFloor());
section.appendChild(addBtn);
this.container.appendChild(section);
}
// ---- Room Section ----
_renderRoomSection() {
const section = this._createSection('Rooms');
const floor = this.houseData.floors[this.renderer.currentFloor];
if (!floor) return;
for (const room of floor.rooms) {
const row = document.createElement('div');
row.className = 'he-room-row' + (this._selectedRoomId === room.id ? ' active' : '');
const info = document.createElement('span');
info.className = 'he-room-info';
info.textContent = `${room.name} (${room.dimensions.width}\u00d7${room.dimensions.length}m)`;
info.addEventListener('click', () => {
this._selectedRoomId = room.id;
this.renderer.focusRoom(room.id);
this.render();
});
row.appendChild(info);
const removeBtn = document.createElement('button');
removeBtn.className = 'he-icon-btn he-icon-btn-danger';
removeBtn.textContent = '\u00d7';
removeBtn.title = 'Remove room';
removeBtn.addEventListener('click', () => this._removeRoom(room.id));
row.appendChild(removeBtn);
section.appendChild(row);
}
const addBtn = document.createElement('button');
addBtn.className = 'he-add-btn';
addBtn.textContent = '+ Add Room';
addBtn.addEventListener('click', () => this._showAddRoomForm(section, addBtn));
section.appendChild(addBtn);
this.container.appendChild(section);
}
// ---- Room Editor ----
_renderRoomEditor() {
const floor = this.houseData.floors[this.renderer.currentFloor];
if (!floor) return;
const room = floor.rooms.find(r => r.id === this._selectedRoomId);
if (!room) return;
const section = this._createSection(`Edit: ${room.name}`);
// Name
const nameRow = this._createFieldRow('Name');
const nameInput = document.createElement('input');
nameInput.className = 'he-input';
nameInput.value = room.name;
nameInput.addEventListener('change', () => {
room.name = nameInput.value;
this._rebuildFloor();
});
nameRow.appendChild(nameInput);
section.appendChild(nameRow);
// English name
const nameEnRow = this._createFieldRow('Name (EN)');
const nameEnInput = document.createElement('input');
nameEnInput.className = 'he-input';
nameEnInput.value = room.nameEN || '';
nameEnInput.addEventListener('change', () => {
room.nameEN = nameEnInput.value;
this._rebuildFloor();
});
nameEnRow.appendChild(nameEnInput);
section.appendChild(nameEnRow);
// Type
const typeRow = this._createFieldRow('Type');
const typeSelect = document.createElement('select');
typeSelect.className = 'he-input';
const roomTypes = [
'living', 'kitchen', 'dining', 'bedroom', 'bathroom',
'hallway', 'office', 'utility', 'storage', 'garage'
];
for (const t of roomTypes) {
const opt = document.createElement('option');
opt.value = t;
opt.textContent = t.charAt(0).toUpperCase() + t.slice(1);
if (room.type === t) opt.selected = true;
typeSelect.appendChild(opt);
}
typeSelect.addEventListener('change', () => {
room.type = typeSelect.value;
});
typeRow.appendChild(typeSelect);
section.appendChild(typeRow);
// Dimensions
const dimsLabel = document.createElement('div');
dimsLabel.className = 'he-field-label';
dimsLabel.textContent = 'Dimensions';
section.appendChild(dimsLabel);
const dimsRow = document.createElement('div');
dimsRow.className = 'he-dims-row';
const wLabel = document.createElement('label');
wLabel.className = 'he-dim-label';
wLabel.textContent = 'W';
dimsRow.appendChild(wLabel);
const wInput = this._createNumberInput(room.dimensions.width, 1, 15, 0.25, (val) => {
room.dimensions.width = val;
this._rebuildFloor();
});
dimsRow.appendChild(wInput);
const lLabel = document.createElement('label');
lLabel.className = 'he-dim-label';
lLabel.textContent = 'L';
dimsRow.appendChild(lLabel);
const lInput = this._createNumberInput(room.dimensions.length, 1, 15, 0.25, (val) => {
room.dimensions.length = val;
this._rebuildFloor();
});
dimsRow.appendChild(lInput);
section.appendChild(dimsRow);
// Position
const posLabel = document.createElement('div');
posLabel.className = 'he-field-label';
posLabel.textContent = 'Position';
section.appendChild(posLabel);
const posRow = document.createElement('div');
posRow.className = 'he-dims-row';
const xLabel = document.createElement('label');
xLabel.className = 'he-dim-label';
xLabel.textContent = 'X';
posRow.appendChild(xLabel);
const xInput = this._createNumberInput(room.position.x, 0, 30, 0.25, (val) => {
room.position.x = val;
this._rebuildFloor();
});
posRow.appendChild(xInput);
const yLabel = document.createElement('label');
yLabel.className = 'he-dim-label';
yLabel.textContent = 'Y';
posRow.appendChild(yLabel);
const yInput = this._createNumberInput(room.position.y, 0, 30, 0.25, (val) => {
room.position.y = val;
this._rebuildFloor();
});
posRow.appendChild(yInput);
section.appendChild(posRow);
// Flooring
const flooringRow = this._createFieldRow('Flooring');
const flooringSelect = document.createElement('select');
flooringSelect.className = 'he-input';
for (const f of ['hardwood', 'tile']) {
const opt = document.createElement('option');
opt.value = f;
opt.textContent = f.charAt(0).toUpperCase() + f.slice(1);
if (room.flooring === f) opt.selected = true;
flooringSelect.appendChild(opt);
}
flooringSelect.addEventListener('change', () => {
room.flooring = flooringSelect.value;
this._rebuildFloor();
});
flooringRow.appendChild(flooringSelect);
section.appendChild(flooringRow);
this.container.appendChild(section);
}
// ---- Save Section ----
_renderSaveSection() {
const section = this._createSection('Template');
const saveBtn = document.createElement('button');
saveBtn.className = 'he-save-btn';
saveBtn.textContent = 'Save as House Template';
saveBtn.addEventListener('click', () => this._saveAsTemplate());
section.appendChild(saveBtn);
const newBtn = document.createElement('button');
newBtn.className = 'he-add-btn';
newBtn.style.marginTop = '6px';
newBtn.textContent = 'New Empty House';
newBtn.addEventListener('click', () => this._createNewHouse());
section.appendChild(newBtn);
this.container.appendChild(section);
}
// ---- Actions ----
_addFloor() {
const floors = this.houseData.floors;
const level = floors.length;
const id = `floor-${level}`;
floors.push({
id,
name: `Floor ${level}`,
nameEN: `Floor ${level}`,
level,
ceilingHeight: 2.5,
rooms: []
});
this.onHouseChanged('floors');
this.render();
}
_removeFloor(index) {
this.houseData.floors.splice(index, 1);
// Re-index levels
this.houseData.floors.forEach((f, i) => { f.level = i; });
// If current floor was removed, switch to last available
if (this.renderer.currentFloor >= this.houseData.floors.length) {
this.renderer.showFloor(this.houseData.floors.length - 1);
} else {
this._rebuildFloor();
}
this.onHouseChanged('floors');
this.render();
}
_showAddRoomForm(section, addBtn) {
// Replace button with form
addBtn.style.display = 'none';
const form = document.createElement('div');
form.className = 'he-add-room-form';
const nameInput = document.createElement('input');
nameInput.className = 'he-input';
nameInput.placeholder = 'Room name';
form.appendChild(nameInput);
const typeSelect = document.createElement('select');
typeSelect.className = 'he-input';
const roomTypes = [
'living', 'kitchen', 'dining', 'bedroom', 'bathroom',
'hallway', 'office', 'utility', 'storage', 'garage'
];
for (const t of roomTypes) {
const opt = document.createElement('option');
opt.value = t;
opt.textContent = t.charAt(0).toUpperCase() + t.slice(1);
typeSelect.appendChild(opt);
}
form.appendChild(typeSelect);
const dimsRow = document.createElement('div');
dimsRow.className = 'he-dims-row';
const wInput = document.createElement('input');
wInput.className = 'he-input';
wInput.type = 'number';
wInput.value = '4';
wInput.min = '1';
wInput.max = '15';
wInput.step = '0.25';
wInput.placeholder = 'Width';
wInput.style.flex = '1';
dimsRow.appendChild(wInput);
const xSpan = document.createElement('span');
xSpan.textContent = '\u00d7';
xSpan.style.padding = '0 4px';
xSpan.style.color = '#999';
dimsRow.appendChild(xSpan);
const lInput = document.createElement('input');
lInput.className = 'he-input';
lInput.type = 'number';
lInput.value = '3';
lInput.min = '1';
lInput.max = '15';
lInput.step = '0.25';
lInput.placeholder = 'Length';
lInput.style.flex = '1';
dimsRow.appendChild(lInput);
form.appendChild(dimsRow);
const actions = document.createElement('div');
actions.className = 'he-form-actions';
const cancelBtn = document.createElement('button');
cancelBtn.className = 'he-cancel-btn';
cancelBtn.textContent = 'Cancel';
cancelBtn.addEventListener('click', () => {
form.remove();
addBtn.style.display = '';
});
actions.appendChild(cancelBtn);
const submitBtn = document.createElement('button');
submitBtn.className = 'he-submit-btn';
submitBtn.textContent = 'Add';
submitBtn.addEventListener('click', () => {
const name = nameInput.value.trim();
if (!name) { nameInput.focus(); return; }
this._addRoom({
name,
type: typeSelect.value,
width: parseFloat(wInput.value) || 4,
length: parseFloat(lInput.value) || 3
});
});
actions.appendChild(submitBtn);
form.appendChild(actions);
section.appendChild(form);
nameInput.focus();
}
_addRoom({ name, type, width, length }) {
const floor = this.houseData.floors[this.renderer.currentFloor];
if (!floor) return;
// Auto-position: find rightmost edge of existing rooms
let maxX = 0;
for (const r of floor.rooms) {
const edge = r.position.x + r.dimensions.width;
if (edge > maxX) maxX = edge;
}
const id = `${floor.id}-${name.toLowerCase().replace(/[^a-z0-9]+/g, '-')}-${Date.now().toString(36).slice(-4)}`;
const room = {
id,
name,
nameEN: name,
type,
position: { x: maxX + 0.24, y: 0 },
dimensions: { width, length },
flooring: (type === 'bathroom' || type === 'kitchen' || type === 'utility') ? 'tile' : 'hardwood',
walls: {
south: { type: 'exterior' },
north: { type: 'exterior' },
west: { type: 'interior' },
east: { type: 'exterior' }
}
};
floor.rooms.push(room);
this._rebuildFloor();
this._selectedRoomId = room.id;
this.onHouseChanged('rooms');
this.render();
}
_removeRoom(roomId) {
const floor = this.houseData.floors[this.renderer.currentFloor];
if (!floor) return;
const idx = floor.rooms.findIndex(r => r.id === roomId);
if (idx === -1) return;
floor.rooms.splice(idx, 1);
if (this._selectedRoomId === roomId) {
this._selectedRoomId = null;
}
this._rebuildFloor();
this.onHouseChanged('rooms');
this.render();
}
_saveAsTemplate() {
const data = structuredClone(this.houseData);
data.savedAt = new Date().toISOString();
const json = JSON.stringify(data, null, 2);
const blob = new Blob([json], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${(data.name || 'house').replace(/\s+/g, '-').toLowerCase()}.json`;
a.click();
URL.revokeObjectURL(url);
}
_createNewHouse() {
const newHouse = {
name: 'New House',
description: '',
units: 'meters',
building: {
footprint: { width: 10, depth: 8 },
wallThickness: 0.24,
roofType: 'gable'
},
floors: [
{
id: 'floor-0',
name: 'Ground Floor',
nameEN: 'Ground Floor',
level: 0,
ceilingHeight: 2.6,
rooms: [
{
id: 'floor-0-hallway',
name: 'Hallway',
nameEN: 'Hallway',
type: 'hallway',
position: { x: 3.5, y: 0 },
dimensions: { width: 2.0, length: 8.0 },
flooring: 'tile',
walls: {
south: { type: 'exterior', doors: [{ id: 'entry', type: 'entry', position: 0.3, width: 1.1, height: 2.2, connectsTo: 'exterior' }] },
north: { type: 'exterior' },
west: { type: 'interior' },
east: { type: 'interior' }
}
},
{
id: 'floor-0-living',
name: 'Living Room',
nameEN: 'Living Room',
type: 'living',
position: { x: 0, y: 3.0 },
dimensions: { width: 3.5, length: 5.0 },
flooring: 'hardwood',
walls: {
south: { type: 'interior' },
north: { type: 'exterior', windows: [{ id: 'lr-w1', type: 'casement', position: 0.5, width: 1.4, height: 1.4, sillHeight: 0.6 }] },
west: { type: 'exterior', windows: [{ id: 'lr-w2', type: 'casement', position: 1.5, width: 1.2, height: 1.4, sillHeight: 0.6 }] },
east: { type: 'interior' }
}
},
{
id: 'floor-0-kitchen',
name: 'Kitchen',
nameEN: 'Kitchen',
type: 'kitchen',
position: { x: 0, y: 0 },
dimensions: { width: 3.5, length: 3.0 },
flooring: 'tile',
walls: {
south: { type: 'exterior', windows: [{ id: 'k-w1', type: 'casement', position: 1.0, width: 1.2, height: 1.2, sillHeight: 0.9 }] },
north: { type: 'interior' },
west: { type: 'exterior' },
east: { type: 'interior' }
}
}
]
}
]
};
this.renderer.houseData = newHouse;
this.renderer.showFloor(0);
this._selectedRoomId = null;
this.onHouseChanged('new');
this.render();
}
_rebuildFloor() {
this.renderer.showFloor(this.renderer.currentFloor);
this.onHouseChanged('rebuild');
}
// ---- UI Helpers ----
_createSection(title) {
const section = document.createElement('div');
section.className = 'he-section';
const h = document.createElement('div');
h.className = 'he-section-title';
h.textContent = title;
section.appendChild(h);
return section;
}
_createFieldRow(label) {
const row = document.createElement('div');
row.className = 'he-field-row';
const lbl = document.createElement('label');
lbl.className = 'he-field-label';
lbl.textContent = label;
row.appendChild(lbl);
return row;
}
_createNumberInput(value, min, max, step, onChange) {
const input = document.createElement('input');
input.className = 'he-input he-num-input';
input.type = 'number';
input.value = value;
input.min = min;
input.max = max;
input.step = step;
input.addEventListener('change', () => {
const val = parseFloat(input.value);
if (!isNaN(val) && val >= min && val <= max) {
onChange(val);
}
});
return input;
}
}

View File

@@ -14,7 +14,7 @@
position: fixed;
top: 0;
right: 0;
width: 280px;
width: 300px;
height: 100vh;
background: rgba(255, 255, 255, 0.95);
border-left: 1px solid #ddd;
@@ -404,6 +404,169 @@
}
.export-btn:hover { background: #f0f0f0; }
.export-btn:active { background: #e0e0e0; }
/* House Editor */
#house-editor {
margin-top: 16px;
padding-top: 12px;
border-top: 1px solid #ddd;
}
.he-toggle-btn {
display: block;
width: 100%;
padding: 7px;
border: 1px solid #4a90d9;
border-radius: 4px;
background: #fff;
color: #4a90d9;
cursor: pointer;
font-size: 12px;
font-weight: 500;
}
.he-toggle-btn:hover { background: #e8f0fe; }
.he-section {
margin-top: 10px;
padding-top: 8px;
border-top: 1px solid #eee;
}
.he-section-title {
font-size: 11px;
font-weight: 600;
color: #666;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 6px;
}
.he-field-row {
margin-bottom: 5px;
}
.he-field-label {
display: block;
font-size: 11px;
color: #888;
margin-bottom: 2px;
}
.he-input {
width: 100%;
padding: 4px 7px;
border: 1px solid #ccc;
border-radius: 3px;
font-size: 12px;
outline: none;
}
.he-input:focus { border-color: #4a90d9; }
.he-num-input { width: 65px; }
.he-dims-row {
display: flex;
align-items: center;
gap: 4px;
margin-bottom: 6px;
}
.he-dim-label {
font-size: 11px;
color: #888;
min-width: 12px;
}
.he-dims-row .he-input { flex: 1; min-width: 0; }
.he-floor-row {
display: flex;
align-items: center;
gap: 4px;
padding: 4px 0;
}
.he-floor-label {
flex: 1;
font-size: 12px;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.he-room-row {
display: flex;
align-items: center;
padding: 4px 6px;
margin: 1px 0;
border-radius: 3px;
cursor: pointer;
}
.he-room-row:hover { background: #f0f4fa; }
.he-room-row.active { background: #e0eaf5; }
.he-room-info {
flex: 1;
font-size: 12px;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.he-icon-btn {
width: 20px;
height: 20px;
border: none;
border-radius: 3px;
background: transparent;
cursor: pointer;
font-size: 14px;
line-height: 1;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.he-icon-btn-danger { color: #c44; }
.he-icon-btn-danger:hover { background: #fdd; }
.he-add-btn {
display: block;
width: 100%;
padding: 5px;
margin-top: 4px;
border: 1px dashed #aaa;
border-radius: 3px;
background: transparent;
color: #666;
cursor: pointer;
font-size: 11px;
}
.he-add-btn:hover { background: #f5f5f5; border-color: #4a90d9; color: #4a90d9; }
.he-add-room-form {
margin-top: 6px;
padding: 8px;
background: #f9f9f9;
border: 1px solid #ddd;
border-radius: 4px;
}
.he-add-room-form .he-input { margin-bottom: 5px; }
.he-form-actions {
display: flex;
gap: 6px;
margin-top: 6px;
}
.he-cancel-btn, .he-submit-btn {
flex: 1;
padding: 5px;
border: 1px solid #ccc;
border-radius: 3px;
font-size: 11px;
cursor: pointer;
}
.he-cancel-btn { background: #fff; }
.he-cancel-btn:hover { background: #f0f0f0; }
.he-submit-btn { background: #4a90d9; color: #fff; border-color: #4a90d9; }
.he-submit-btn:hover { background: #3a7bc8; }
.he-save-btn {
display: block;
width: 100%;
padding: 7px;
border: 1px solid #4a90d9;
border-radius: 4px;
background: #4a90d9;
color: #fff;
cursor: pointer;
font-size: 12px;
font-weight: 500;
}
.he-save-btn:hover { background: #3a7bc8; }
</style>
</head>
<body>
@@ -425,6 +588,7 @@
<button class="export-btn" id="btn-load">Load JSON</button>
<button class="export-btn" id="btn-screenshot">Screenshot</button>
</div>
<div id="house-editor"></div>
</div>
<div id="info">Click a room to select it. Click furniture to edit. Scroll to zoom, drag to orbit.</div>
@@ -444,6 +608,7 @@
import { ThemeManager } from './themes.js';
import { ExportManager } from './export.js';
import { CatalogPanel } from './catalog.js';
import { HouseEditor } from './house-editor.js';
const viewer = document.getElementById('viewer');
const houseRenderer = new HouseRenderer(viewer);
@@ -454,6 +619,7 @@
let themeManager = null;
let exportManager = null;
let catalogPanel = null;
let houseEditor = null;
houseRenderer.loadHouse('../data/sample-house.json').then(async (house) => {
document.getElementById('house-name').textContent = house.name;
@@ -485,6 +651,14 @@
state: designState,
interaction
});
houseEditor = new HouseEditor(document.getElementById('house-editor'), {
renderer: houseRenderer,
onHouseChanged: (what) => {
document.getElementById('house-name').textContent = houseRenderer.houseData.name;
buildFloorButtons();
buildRoomList();
}
});
buildFloorButtons();
buildRoomList();
buildThemeButtons();
@@ -508,6 +682,7 @@
buildRoomList();
selectedRoom = null;
if (catalogPanel) catalogPanel.setSelectedRoom(null);
if (houseEditor) houseEditor.setSelectedRoom(null);
});
container.appendChild(btn);
}
@@ -533,6 +708,7 @@
el.classList.toggle('active', el.dataset.roomId === roomId);
});
if (catalogPanel) catalogPanel.setSelectedRoom(roomId);
if (houseEditor) houseEditor.setSelectedRoom(roomId);
const room = houseRenderer.getRooms().find(r => r.id === roomId);
if (room) {
document.getElementById('info').textContent = `${room.name} (${room.nameEN}) — ${room.area}`;