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 <noreply@anthropic.com>
This commit is contained in:
erik 2026-02-26 09:26:06 +00:00
parent a0698753c5
commit a82e6f4856
3 changed files with 164 additions and 148 deletions

View file

@ -735,37 +735,77 @@ function debounce(fn, ms) {
}; };
} }
// Show or create a stats window for a character /**
function showStatsWindow(name) { * Create or show a draggable window. Returns { win, content, isNew }.
debugLog('📊 showStatsWindow called for:', name); * If window already exists, brings it to front and returns isNew: false.
if (statsWindows[name]) { */
const existing = statsWindows[name]; function createWindow(id, title, className, options = {}) {
debugLog('📊 Existing stats window found, showing it:', existing); const { onClose } = options;
// Always show the window (no toggle)
existing.style.display = 'flex'; // Check if window already exists - bring to front
// Bring to front when opening 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; if (!window.__chatZ) window.__chatZ = 10000;
window.__chatZ += 1; 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; return;
} }
debugLog('📊 Creating new stats window for:', name);
const win = document.createElement('div');
win.className = 'stats-window';
win.dataset.character = name; win.dataset.character = name;
// Header (reuses chat-header styling) statsWindows[name] = win;
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);
// Time period controls // Time period controls
const controls = document.createElement('div'); const controls = document.createElement('div');
controls.className = 'stats-controls'; controls.className = 'stats-controls';
@ -775,6 +815,12 @@ function showStatsWindow(name) {
{ label: '24H', value: 'now-24h' }, { label: '24H', value: 'now-24h' },
{ label: '7D', value: 'now-7d' } { 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 => { timeRanges.forEach(range => {
const btn = document.createElement('button'); const btn = document.createElement('button');
btn.className = 'time-range-btn'; btn.className = 'time-range-btn';
@ -783,25 +829,17 @@ function showStatsWindow(name) {
btn.addEventListener('click', () => { btn.addEventListener('click', () => {
controls.querySelectorAll('.time-range-btn').forEach(b => b.classList.remove('active')); controls.querySelectorAll('.time-range-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active'); btn.classList.add('active');
updateStatsTimeRange(content, name, range.value); updateStatsTimeRange(statsContent, name, range.value);
}); });
controls.appendChild(btn); controls.appendChild(btn);
}); });
win.appendChild(controls);
// Content container content.appendChild(controls);
const content = document.createElement('div'); content.appendChild(statsContent);
content.className = 'chat-messages';
content.textContent = 'Loading stats...'; debugLog('Stats window created for:', name);
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);
// Load initial stats with default 24h range // Load initial stats with default 24h range
updateStatsTimeRange(content, name, 'now-24h'); updateStatsTimeRange(statsContent, name, 'now-24h');
// Enable dragging using the global drag system
makeDraggable(win, header);
} }
function updateStatsTimeRange(content, name, timeRange) { function updateStatsTimeRange(content, name, timeRange) {
@ -832,46 +870,32 @@ function updateStatsTimeRange(content, name, timeRange) {
// Show or create an inventory window for a character // Show or create an inventory window for a character
function showInventoryWindow(name) { function showInventoryWindow(name) {
debugLog('🎒 showInventoryWindow called for:', name); debugLog('showInventoryWindow called for:', name);
if (inventoryWindows[name]) { const windowId = `inventoryWindow-${name}`;
const existing = inventoryWindows[name];
debugLog('🎒 Existing inventory window found, showing it:', existing); const { win, content, isNew } = createWindow(
// Always show the window (no toggle) windowId, `Inventory: ${name}`, 'inventory-window'
existing.style.display = 'flex'; );
// Bring to front when opening
if (!window.__chatZ) window.__chatZ = 10000; if (!isNew) {
window.__chatZ += 1; debugLog('Existing inventory window found, showing it');
existing.style.zIndex = window.__chatZ;
debugLog('🎒 Inventory window shown with zIndex:', window.__chatZ);
return; return;
} }
debugLog('🎒 Creating new inventory window for:', name);
const win = document.createElement('div');
win.className = 'inventory-window';
win.dataset.character = name; win.dataset.character = name;
// Header (reuses chat-header styling) inventoryWindows[name] = win;
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);
// Loading message // Loading message
const loading = document.createElement('div'); const loading = document.createElement('div');
loading.className = 'inventory-loading'; loading.className = 'inventory-loading';
loading.textContent = 'Loading inventory...'; loading.textContent = 'Loading inventory...';
win.appendChild(loading); content.appendChild(loading);
// Content container // Inventory content container
const content = document.createElement('div'); const invContent = document.createElement('div');
content.className = 'inventory-content'; invContent.className = 'inventory-content';
content.style.display = 'none'; invContent.style.display = 'none';
win.appendChild(content); content.appendChild(invContent);
// Fetch inventory data from main app (which will proxy to inventory service) // Fetch inventory data from main app (which will proxy to inventory service)
fetch(`${API_BASE}/inventory/${encodeURIComponent(name)}?limit=1000`) fetch(`${API_BASE}/inventory/${encodeURIComponent(name)}?limit=1000`)
@ -881,7 +905,7 @@ function showInventoryWindow(name) {
}) })
.then(data => { .then(data => {
loading.style.display = 'none'; loading.style.display = 'none';
content.style.display = 'block'; invContent.style.display = 'block';
// Create inventory grid // Create inventory grid
const grid = document.createElement('div'); const grid = document.createElement('div');
@ -1024,26 +1048,20 @@ function showInventoryWindow(name) {
grid.appendChild(slot); grid.appendChild(slot);
}); });
content.appendChild(grid); invContent.appendChild(grid);
// Add item count // Add item count
const count = document.createElement('div'); const count = document.createElement('div');
count.className = 'inventory-count'; count.className = 'inventory-count';
count.textContent = `${data.item_count} items`; count.textContent = `${data.item_count} items`;
content.appendChild(count); invContent.appendChild(count);
}) })
.catch(err => { .catch(err => {
loading.textContent = `Failed to load inventory: ${err.message}`; loading.textContent = `Failed to load inventory: ${err.message}`;
console.error('Inventory fetch failed:', err); console.error('Inventory fetch failed:', err);
}); });
debugLog('🎒 Appending inventory window to DOM:', win); debugLog('Inventory window created for:', name);
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);
} }
// Inventory tooltip functions // Inventory tooltip functions
@ -1812,39 +1830,26 @@ function initWebSocket() {
// Display or create a chat window for a character // Display or create a chat window for a character
function showChatWindow(name) { function showChatWindow(name) {
debugLog('💬 showChatWindow called for:', name); debugLog('showChatWindow called for:', name);
if (chatWindows[name]) { const windowId = `chatWindow-${name}`;
const existing = chatWindows[name];
debugLog('💬 Existing chat window found, showing it:', existing); const { win, content, isNew } = createWindow(
// Always show the window (no toggle) windowId, `Chat: ${name}`, 'chat-window'
existing.style.display = 'flex'; );
// Bring to front when opening
if (!window.__chatZ) window.__chatZ = 10000; if (!isNew) {
window.__chatZ += 1; debugLog('Existing chat window found, showing it');
existing.style.zIndex = window.__chatZ;
debugLog('💬 Chat window shown with zIndex:', window.__chatZ);
return; return;
} }
debugLog('💬 Creating new chat window for:', name);
const win = document.createElement('div');
win.className = 'chat-window';
win.dataset.character = name; win.dataset.character = name;
// Header chatWindows[name] = win;
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);
// Messages container // Messages container
const msgs = document.createElement('div'); const msgs = document.createElement('div');
msgs.className = 'chat-messages'; msgs.className = 'chat-messages';
win.appendChild(msgs); content.appendChild(msgs);
// Input form // Input form
const form = document.createElement('form'); const form = document.createElement('form');
form.className = 'chat-form'; form.className = 'chat-form';
@ -1861,14 +1866,9 @@ function showChatWindow(name) {
socket.send(JSON.stringify({ player_name: name, command: text })); socket.send(JSON.stringify({ player_name: name, command: text }));
input.value = ''; input.value = '';
}); });
win.appendChild(form); content.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);
// Enable dragging using the global drag system debugLog('Chat window created for:', name);
makeDraggable(win, header);
} }
// Append a chat message to the correct window // Append a chat message to the correct window

View file

@ -363,6 +363,14 @@ body {
border-radius: 2px; border-radius: 2px;
} }
.window-content {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
min-height: 0;
}
.chat-header { .chat-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;

View file

@ -540,6 +540,14 @@ body {
z-index: 10000; z-index: 10000;
} }
.window-content {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
min-height: 0;
}
.chat-header { .chat-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;