/** * CatalogPanel — left sidebar for browsing furniture catalog. * * Shows categories, search, and item cards. Clicking an item * adds it to the center of the selected room via DesignState. */ export class CatalogPanel { constructor(container, { renderer, state, interaction }) { this.container = container; this.renderer = renderer; this.state = state; this.interaction = interaction; this.selectedCategory = 'all'; this.searchQuery = ''; this.selectedRoomId = null; this._build(); this._bindEvents(); } // ---- Build DOM ---- _build() { this.container.innerHTML = ''; this.container.className = 'catalog-panel'; // Search const searchWrap = document.createElement('div'); searchWrap.className = 'catalog-search'; this._searchInput = document.createElement('input'); this._searchInput.type = 'text'; this._searchInput.placeholder = 'Search furniture...'; this._searchInput.className = 'catalog-search-input'; searchWrap.appendChild(this._searchInput); this.container.appendChild(searchWrap); // Categories this._categoryBar = document.createElement('div'); this._categoryBar.className = 'catalog-categories'; this.container.appendChild(this._categoryBar); // Items list this._itemList = document.createElement('div'); this._itemList.className = 'catalog-items'; this.container.appendChild(this._itemList); this._renderCategories(); this._renderItems(); } _renderCategories() { const catalog = this.renderer.catalogData; if (!catalog) return; this._categoryBar.innerHTML = ''; const categories = ['all', ...catalog.categories]; const LABELS = { all: 'All', seating: 'Seating', tables: 'Tables', storage: 'Storage', beds: 'Beds', bathroom: 'Bath', kitchen: 'Kitchen', office: 'Office', lighting: 'Lighting', decor: 'Decor' }; for (const cat of categories) { const btn = document.createElement('button'); btn.className = 'catalog-cat-btn' + (cat === this.selectedCategory ? ' active' : ''); btn.textContent = LABELS[cat] || cat; btn.dataset.category = cat; btn.addEventListener('click', () => { this.selectedCategory = cat; this._renderCategories(); this._renderItems(); }); this._categoryBar.appendChild(btn); } } _renderItems() { const catalog = this.renderer.catalogData; if (!catalog) { this._itemList.innerHTML = '
No catalog loaded
'; return; } let items = catalog.items; // Filter by category if (this.selectedCategory !== 'all') { items = items.filter(it => it.category === this.selectedCategory); } // Filter by search if (this.searchQuery) { const q = this.searchQuery.toLowerCase(); items = items.filter(it => it.name.toLowerCase().includes(q) || it.id.toLowerCase().includes(q) || it.category.toLowerCase().includes(q) ); } this._itemList.innerHTML = ''; if (items.length === 0) { this._itemList.innerHTML = '
No items found
'; return; } for (const item of items) { const card = this._createItemCard(item); this._itemList.appendChild(card); } } _createItemCard(item) { const card = document.createElement('div'); card.className = 'catalog-item'; card.dataset.catalogId = item.id; // Color swatch from first part const color = item.mesh?.parts?.[0]?.color || '#888'; const dims = item.dimensions; const dimStr = `${dims.width}×${dims.depth}×${dims.height}m`; card.innerHTML = `
` + `
` + `
${item.name}
` + `
${dimStr}
` + `
` + ``; card.querySelector('.catalog-item-add').addEventListener('click', (e) => { e.stopPropagation(); this._placeItem(item); }); card.addEventListener('click', () => { this._placeItem(item); }); return card; } // ---- Place item ---- _placeItem(catalogItem) { // Determine target room const roomId = this._getTargetRoom(catalogItem); if (!roomId) return; // Get room center for initial placement const floor = this.renderer.houseData.floors[this.renderer.currentFloor]; const room = floor?.rooms.find(r => r.id === roomId); if (!room) return; // Place at room center (local coords) const cx = room.dimensions.width / 2; const cz = room.dimensions.length / 2; const placement = { catalogId: catalogItem.id, position: { x: cx, z: cz }, rotation: 0, wallMounted: false }; // InteractionManager handles the re-render via furniture-add event this.state.addFurniture(roomId, placement); } _getTargetRoom(catalogItem) { // Use currently selected room from interaction manager or sidebar if (this.selectedRoomId) { return this.selectedRoomId; } // If interaction has a room selected (via furniture selection) if (this.interaction.selectedRoomId) { return this.interaction.selectedRoomId; } // Try to find a matching room on the current floor const rooms = this.renderer.getRooms(); if (rooms.length === 0) return null; // If catalog item has room hints, try to match if (catalogItem.rooms && catalogItem.rooms.length > 0) { for (const room of rooms) { // Room ids contain the room type (eg "eg-wohnzimmer") for (const hint of catalogItem.rooms) { if (room.id.includes(hint)) return room.id; } } } // Fallback: first room on the floor return rooms[0].id; } // ---- Events ---- _bindEvents() { this._searchInput.addEventListener('input', () => { this.searchQuery = this._searchInput.value.trim(); this._renderItems(); }); // Track room selection from main sidebar this.renderer.container.addEventListener('roomclick', (e) => { this.selectedRoomId = e.detail.roomId; }); } /** Called externally when a room is selected in the main sidebar. */ setSelectedRoom(roomId) { this.selectedRoomId = roomId; } /** Refresh the item list (e.g., after floor change). */ refresh() { this._renderItems(); } }