From a82e6f485603ff86a7315161be1e4824bc75368c Mon Sep 17 00:00:00 2001 From: erik Date: Thu, 26 Feb 2026 09:26:06 +0000 Subject: [PATCH] Extract createWindow helper to deduplicate window setup code Refactor showStatsWindow, showInventoryWindow, and showChatWindow to use a shared createWindow() helper that handles existence checks, z-index management, header/close button creation, and makeDraggable setup. Each function now only contains its unique content creation logic. Added .window-content CSS class to style.css, style-ac.css, and the christmas theme to maintain flex layout through the new wrapper div. Co-Authored-By: Claude Opus 4.6 --- static/script.js | 296 ++++++++++++++++++++++---------------------- static/style-ac.css | 8 ++ static/style.css | 8 ++ 3 files changed, 164 insertions(+), 148 deletions(-) diff --git a/static/script.js b/static/script.js index 419881d1..a7a1dc80 100644 --- a/static/script.js +++ b/static/script.js @@ -735,37 +735,77 @@ function debounce(fn, ms) { }; } -// Show or create a stats window for a character -function showStatsWindow(name) { - debugLog('📊 showStatsWindow called for:', name); - if (statsWindows[name]) { - const existing = statsWindows[name]; - debugLog('📊 Existing stats window found, showing it:', existing); - // Always show the window (no toggle) - existing.style.display = 'flex'; - // Bring to front when opening +/** + * Create or show a draggable window. Returns { win, content, isNew }. + * If window already exists, brings it to front and returns isNew: false. + */ +function createWindow(id, title, className, options = {}) { + const { onClose } = options; + + // Check if window already exists - bring to front + const existing = document.getElementById(id); + if (existing) { + existing.style.display = 'flex'; + if (!window.__chatZ) window.__chatZ = 10000; + window.__chatZ += 1; + existing.style.zIndex = window.__chatZ; + return { win: existing, content: existing.querySelector('.window-content'), isNew: false }; + } + + // Create new window if (!window.__chatZ) window.__chatZ = 10000; window.__chatZ += 1; - existing.style.zIndex = window.__chatZ; - debugLog('📊 Stats window shown with zIndex:', window.__chatZ); + + const win = document.createElement('div'); + win.id = id; + win.className = className; + win.style.display = 'flex'; + win.style.zIndex = window.__chatZ; + + const header = document.createElement('div'); + header.className = 'chat-header'; + + const titleSpan = document.createElement('span'); + titleSpan.textContent = title; + header.appendChild(titleSpan); + + const closeBtn = document.createElement('button'); + closeBtn.className = 'chat-close-btn'; + closeBtn.textContent = '\u00D7'; + closeBtn.addEventListener('click', () => { + win.style.display = 'none'; + if (onClose) onClose(); + }); + header.appendChild(closeBtn); + + const content = document.createElement('div'); + content.className = 'window-content'; + + win.appendChild(header); + win.appendChild(content); + document.body.appendChild(win); + makeDraggable(win, header); + + return { win, content, isNew: true }; +} + +// Show or create a stats window for a character +function showStatsWindow(name) { + debugLog('showStatsWindow called for:', name); + const windowId = `statsWindow-${name}`; + + const { win, content, isNew } = createWindow( + windowId, `Stats: ${name}`, 'stats-window' + ); + + if (!isNew) { + debugLog('Existing stats window found, showing it'); return; } - debugLog('📊 Creating new stats window for:', name); - const win = document.createElement('div'); - win.className = 'stats-window'; + win.dataset.character = name; - // Header (reuses chat-header styling) - const header = document.createElement('div'); - header.className = 'chat-header'; - const title = document.createElement('span'); - title.textContent = `Stats: ${name}`; - const closeBtn = document.createElement('button'); - closeBtn.className = 'chat-close-btn'; - closeBtn.textContent = '×'; - closeBtn.addEventListener('click', () => { win.style.display = 'none'; }); - header.appendChild(title); - header.appendChild(closeBtn); - win.appendChild(header); + statsWindows[name] = win; + // Time period controls const controls = document.createElement('div'); controls.className = 'stats-controls'; @@ -775,6 +815,12 @@ function showStatsWindow(name) { { label: '24H', value: 'now-24h' }, { label: '7D', value: 'now-7d' } ]; + + // Stats content container (iframes grid) + const statsContent = document.createElement('div'); + statsContent.className = 'chat-messages'; + statsContent.textContent = 'Loading stats...'; + timeRanges.forEach(range => { const btn = document.createElement('button'); btn.className = 'time-range-btn'; @@ -783,25 +829,17 @@ function showStatsWindow(name) { btn.addEventListener('click', () => { controls.querySelectorAll('.time-range-btn').forEach(b => b.classList.remove('active')); btn.classList.add('active'); - updateStatsTimeRange(content, name, range.value); + updateStatsTimeRange(statsContent, name, range.value); }); controls.appendChild(btn); }); - win.appendChild(controls); - - // Content container - const content = document.createElement('div'); - content.className = 'chat-messages'; - content.textContent = 'Loading stats...'; - win.appendChild(content); - debugLog('📊 Appending stats window to DOM:', win); - document.body.appendChild(win); - statsWindows[name] = win; - debugLog('📊 Stats window added to DOM, total children:', document.body.children.length); + + content.appendChild(controls); + content.appendChild(statsContent); + + debugLog('Stats window created for:', name); // Load initial stats with default 24h range - updateStatsTimeRange(content, name, 'now-24h'); - // Enable dragging using the global drag system - makeDraggable(win, header); + updateStatsTimeRange(statsContent, name, 'now-24h'); } function updateStatsTimeRange(content, name, timeRange) { @@ -832,47 +870,33 @@ function updateStatsTimeRange(content, name, timeRange) { // Show or create an inventory window for a character function showInventoryWindow(name) { - debugLog('🎒 showInventoryWindow called for:', name); - if (inventoryWindows[name]) { - const existing = inventoryWindows[name]; - debugLog('🎒 Existing inventory window found, showing it:', existing); - // Always show the window (no toggle) - existing.style.display = 'flex'; - // Bring to front when opening - if (!window.__chatZ) window.__chatZ = 10000; - window.__chatZ += 1; - existing.style.zIndex = window.__chatZ; - debugLog('🎒 Inventory window shown with zIndex:', window.__chatZ); + debugLog('showInventoryWindow called for:', name); + const windowId = `inventoryWindow-${name}`; + + const { win, content, isNew } = createWindow( + windowId, `Inventory: ${name}`, 'inventory-window' + ); + + if (!isNew) { + debugLog('Existing inventory window found, showing it'); return; } - debugLog('🎒 Creating new inventory window for:', name); - const win = document.createElement('div'); - win.className = 'inventory-window'; + win.dataset.character = name; - // Header (reuses chat-header styling) - const header = document.createElement('div'); - header.className = 'chat-header'; - const title = document.createElement('span'); - title.textContent = `Inventory: ${name}`; - const closeBtn = document.createElement('button'); - closeBtn.className = 'chat-close-btn'; - closeBtn.textContent = '×'; - closeBtn.addEventListener('click', () => { win.style.display = 'none'; }); - header.appendChild(title); - header.appendChild(closeBtn); - win.appendChild(header); + inventoryWindows[name] = win; + // Loading message const loading = document.createElement('div'); loading.className = 'inventory-loading'; loading.textContent = 'Loading inventory...'; - win.appendChild(loading); - - // Content container - const content = document.createElement('div'); - content.className = 'inventory-content'; - content.style.display = 'none'; - win.appendChild(content); - + content.appendChild(loading); + + // Inventory content container + const invContent = document.createElement('div'); + invContent.className = 'inventory-content'; + invContent.style.display = 'none'; + content.appendChild(invContent); + // Fetch inventory data from main app (which will proxy to inventory service) fetch(`${API_BASE}/inventory/${encodeURIComponent(name)}?limit=1000`) .then(response => { @@ -881,38 +905,38 @@ function showInventoryWindow(name) { }) .then(data => { loading.style.display = 'none'; - content.style.display = 'block'; - + invContent.style.display = 'block'; + // Create inventory grid const grid = document.createElement('div'); grid.className = 'inventory-grid'; - + // Render each item data.items.forEach(item => { - + const slot = document.createElement('div'); slot.className = 'inventory-slot'; - + // Create layered icon container const iconContainer = document.createElement('div'); iconContainer.className = 'item-icon-composite'; - + // Get base icon ID with portal.dat offset const baseIconId = (item.icon + 0x06000000).toString(16).toUpperCase().padStart(8, '0'); - + // Check for overlay and underlay from enhanced format or legacy format let overlayIconId = null; let underlayIconId = null; - + // Enhanced format (inventory service) - check for proper icon overlay/underlay properties if (item.icon_overlay_id && item.icon_overlay_id > 0) { overlayIconId = (item.icon_overlay_id + 0x06000000).toString(16).toUpperCase().padStart(8, '0'); } - + if (item.icon_underlay_id && item.icon_underlay_id > 0) { underlayIconId = (item.icon_underlay_id + 0x06000000).toString(16).toUpperCase().padStart(8, '0'); } - + // Fallback: Enhanced format (inventory service) - check spells object for decal info if (!overlayIconId && !underlayIconId && item.spells && typeof item.spells === 'object') { // Icon overlay (using the actual property names from the data) @@ -920,8 +944,8 @@ function showInventoryWindow(name) { if (item.spells.spell_decal_218103838 && item.spells.spell_decal_218103838 > 100) { overlayIconId = (item.spells.spell_decal_218103838 + 0x06000000).toString(16).toUpperCase().padStart(8, '0'); } - - // Icon underlay + + // Icon underlay if (item.spells.spell_decal_218103848 && item.spells.spell_decal_218103848 > 100) { underlayIconId = (item.spells.spell_decal_218103848 + 0x06000000).toString(16).toUpperCase().padStart(8, '0'); } @@ -929,13 +953,13 @@ function showInventoryWindow(name) { // Legacy format - parse item_data try { const itemData = typeof item.item_data === 'string' ? JSON.parse(item.item_data) : item.item_data; - + if (itemData.IntValues) { // Icon overlay (ID 218103849) - only use valid icon IDs if (itemData.IntValues['218103849'] && itemData.IntValues['218103849'] > 100) { overlayIconId = (itemData.IntValues['218103849'] + 0x06000000).toString(16).toUpperCase().padStart(8, '0'); } - + // Icon underlay (ID 218103850) - only use valid icon IDs if (itemData.IntValues['218103850'] && itemData.IntValues['218103850'] > 100) { underlayIconId = (itemData.IntValues['218103850'] + 0x06000000).toString(16).toUpperCase().padStart(8, '0'); @@ -945,7 +969,7 @@ function showInventoryWindow(name) { console.warn('Failed to parse item data for', item.name); } } - + // Create underlay (bottom layer) if (underlayIconId) { const underlayImg = document.createElement('img'); @@ -955,7 +979,7 @@ function showInventoryWindow(name) { underlayImg.onerror = function() { this.style.display = 'none'; }; iconContainer.appendChild(underlayImg); } - + // Create base icon (middle layer) const baseImg = document.createElement('img'); baseImg.className = 'icon-base'; @@ -966,7 +990,7 @@ function showInventoryWindow(name) { this.src = '/icons/06000133.png'; }; iconContainer.appendChild(baseImg); - + // Create overlay (top layer) if (overlayIconId) { const overlayImg = document.createElement('img'); @@ -976,74 +1000,68 @@ function showInventoryWindow(name) { overlayImg.onerror = function() { this.style.display = 'none'; }; iconContainer.appendChild(overlayImg); } - + // Create tooltip data slot.dataset.name = item.name || 'Unknown Item'; slot.dataset.value = item.value || 0; slot.dataset.burden = item.burden || 0; - + // Store enhanced data for tooltips // All data now comes from inventory service (no more local fallback) if (item.max_damage !== undefined || item.object_class_name !== undefined || item.spells !== undefined) { // Inventory service provides clean, structured data with translations // Only include properties that actually exist on the item const enhancedData = {}; - + // Check all possible enhanced properties from inventory service const possibleProps = [ 'max_damage', 'armor_level', 'damage_bonus', 'attack_bonus', 'wield_level', 'skill_level', 'lore_requirement', 'equip_skill', 'equip_skill_name', - 'material', 'material_name', 'material_id', 'imbue', 'item_set', 'tinks', - 'workmanship', 'workmanship_text', 'damage_rating', 'crit_rating', + 'material', 'material_name', 'material_id', 'imbue', 'item_set', 'tinks', + 'workmanship', 'workmanship_text', 'damage_rating', 'crit_rating', 'heal_boost_rating', 'has_id_data', 'object_class_name', 'spells', 'enhanced_properties', 'damage_range', 'damage_type', 'min_damage', 'speed_text', 'speed_value', 'mana_display', 'spellcraft', 'current_mana', 'max_mana', - 'melee_defense_bonus', 'magic_defense_bonus', 'missile_defense_bonus', + 'melee_defense_bonus', 'magic_defense_bonus', 'missile_defense_bonus', 'elemental_damage_vs_monsters', 'mana_conversion_bonus', 'icon_overlay_id', 'icon_underlay_id' ]; - + // Only add properties that exist and have meaningful values possibleProps.forEach(prop => { if (item.hasOwnProperty(prop) && item[prop] !== undefined && item[prop] !== null) { enhancedData[prop] = item[prop]; } }); - + slot.dataset.enhancedData = JSON.stringify(enhancedData); } else { // No enhanced data available slot.dataset.enhancedData = JSON.stringify({}); } - + // Add tooltip on hover slot.addEventListener('mouseenter', e => showInventoryTooltip(e, slot)); slot.addEventListener('mousemove', e => showInventoryTooltip(e, slot)); slot.addEventListener('mouseleave', hideInventoryTooltip); - + slot.appendChild(iconContainer); grid.appendChild(slot); }); - - content.appendChild(grid); - + + invContent.appendChild(grid); + // Add item count const count = document.createElement('div'); count.className = 'inventory-count'; count.textContent = `${data.item_count} items`; - content.appendChild(count); + invContent.appendChild(count); }) .catch(err => { loading.textContent = `Failed to load inventory: ${err.message}`; console.error('Inventory fetch failed:', err); }); - - debugLog('🎒 Appending inventory window to DOM:', win); - document.body.appendChild(win); - inventoryWindows[name] = win; - debugLog('🎒 Inventory window added to DOM, total children:', document.body.children.length); - - // Enable dragging using the global drag system - makeDraggable(win, header); + + debugLog('Inventory window created for:', name); } // Inventory tooltip functions @@ -1812,39 +1830,26 @@ function initWebSocket() { // Display or create a chat window for a character function showChatWindow(name) { - debugLog('💬 showChatWindow called for:', name); - if (chatWindows[name]) { - const existing = chatWindows[name]; - debugLog('💬 Existing chat window found, showing it:', existing); - // Always show the window (no toggle) - existing.style.display = 'flex'; - // Bring to front when opening - if (!window.__chatZ) window.__chatZ = 10000; - window.__chatZ += 1; - existing.style.zIndex = window.__chatZ; - debugLog('💬 Chat window shown with zIndex:', window.__chatZ); + debugLog('showChatWindow called for:', name); + const windowId = `chatWindow-${name}`; + + const { win, content, isNew } = createWindow( + windowId, `Chat: ${name}`, 'chat-window' + ); + + if (!isNew) { + debugLog('Existing chat window found, showing it'); return; } - debugLog('💬 Creating new chat window for:', name); - const win = document.createElement('div'); - win.className = 'chat-window'; + win.dataset.character = name; - // Header - const header = document.createElement('div'); - header.className = 'chat-header'; - const title = document.createElement('span'); - title.textContent = `Chat: ${name}`; - const closeBtn = document.createElement('button'); - closeBtn.className = 'chat-close-btn'; - closeBtn.textContent = '×'; - closeBtn.addEventListener('click', () => { win.style.display = 'none'; }); - header.appendChild(title); - header.appendChild(closeBtn); - win.appendChild(header); + chatWindows[name] = win; + // Messages container const msgs = document.createElement('div'); msgs.className = 'chat-messages'; - win.appendChild(msgs); + content.appendChild(msgs); + // Input form const form = document.createElement('form'); form.className = 'chat-form'; @@ -1861,14 +1866,9 @@ function showChatWindow(name) { socket.send(JSON.stringify({ player_name: name, command: text })); input.value = ''; }); - win.appendChild(form); - debugLog('💬 Appending chat window to DOM:', win); - document.body.appendChild(win); - chatWindows[name] = win; - debugLog('💬 Chat window added to DOM, total children:', document.body.children.length); + content.appendChild(form); - // Enable dragging using the global drag system - makeDraggable(win, header); + debugLog('Chat window created for:', name); } // Append a chat message to the correct window diff --git a/static/style-ac.css b/static/style-ac.css index 7d30d4cf..a25e1680 100644 --- a/static/style-ac.css +++ b/static/style-ac.css @@ -363,6 +363,14 @@ body { border-radius: 2px; } +.window-content { + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; + min-height: 0; +} + .chat-header { display: flex; justify-content: space-between; diff --git a/static/style.css b/static/style.css index d1b8c82f..bb903112 100644 --- a/static/style.css +++ b/static/style.css @@ -540,6 +540,14 @@ body { z-index: 10000; } +.window-content { + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; + min-height: 0; +} + .chat-header { display: flex; justify-content: space-between;