feat: UI Overhaul
This commit is contained in:
105
extension/modules/ui/overlays/garden.js
Normal file
105
extension/modules/ui/overlays/garden.js
Normal file
@@ -0,0 +1,105 @@
|
||||
import { WindowManager } from './window_manager.js';
|
||||
|
||||
export const GardenOverlay = {
|
||||
open() {
|
||||
const { win, content } = WindowManager.create('window-garden', '🌻 Garden', { x: 240, y: 20, width: 600, height: 320 });
|
||||
// Render happens via update()
|
||||
this.update(content);
|
||||
},
|
||||
|
||||
update(container) {
|
||||
if (!container) container = document.getElementById('window-garden-content');
|
||||
if (!container) return;
|
||||
|
||||
const MB = window.MagicBot;
|
||||
if (!MB || !MB.state || !MB.state.garden || !MB.state.garden.tileObjects) {
|
||||
container.innerHTML = '<div style="color:red">No Garden Data</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
|
||||
// Re-render completely for simplicity (virtual DOM would be better but vanilla JS...)
|
||||
// To avoid flicker, we could diff, but for now just clear and rebuild.
|
||||
container.innerHTML = '';
|
||||
|
||||
// Harvest All Button
|
||||
const btnHarvestAll = document.createElement('button');
|
||||
btnHarvestAll.textContent = "Harvest All Ready";
|
||||
btnHarvestAll.style.cssText = "width: 100%; margin-bottom: 10px; padding: 8px; background: #66bb6a; color: white; border: none; border-radius: 4px; font-weight: bold; cursor: pointer;";
|
||||
btnHarvestAll.onclick = () => {
|
||||
let count = 0;
|
||||
for (let i = 0; i < 200; i++) {
|
||||
const tile = MB.state.garden.tileObjects[i.toString()];
|
||||
const slots = tile?.slots || [];
|
||||
slots.forEach((s, idx) => {
|
||||
if (Date.now() >= s.endTime) {
|
||||
MB.sendMsg({ type: 'HarvestCrop', slot: i, slotsIndex: idx, scopePath: ["Room", "Quinoa"] });
|
||||
count++;
|
||||
}
|
||||
});
|
||||
}
|
||||
btnHarvestAll.textContent = `Harvested ${count}!`;
|
||||
setTimeout(() => btnHarvestAll.textContent = "Harvest All Ready", 1000);
|
||||
};
|
||||
container.appendChild(btnHarvestAll);
|
||||
|
||||
const gridContainer = document.createElement('div');
|
||||
gridContainer.style.cssText = "display: flex; gap: 10px; justify-content: center;";
|
||||
|
||||
const leftGrid = document.createElement('div');
|
||||
leftGrid.style.cssText = "display: grid; grid-template-columns: repeat(10, 1fr); gap: 2px;";
|
||||
const rightGrid = document.createElement('div');
|
||||
rightGrid.style.cssText = "display: grid; grid-template-columns: repeat(10, 1fr); gap: 2px;";
|
||||
|
||||
gridContainer.appendChild(leftGrid);
|
||||
gridContainer.appendChild(rightGrid);
|
||||
container.appendChild(gridContainer);
|
||||
|
||||
const getProgressColor = (p) => {
|
||||
const hue = Math.floor(Math.max(0, Math.min(1, p)) * 120);
|
||||
return `hsl(${hue}, 70%, 50%)`;
|
||||
};
|
||||
|
||||
for (let i = 0; i < 200; i++) {
|
||||
const tile = MB.state.garden.tileObjects[i.toString()];
|
||||
const slots = tile?.slots || [];
|
||||
const crop = slots[0];
|
||||
|
||||
const cell = document.createElement('div');
|
||||
cell.style.cssText = "width: 25px; height: 25px; border: 1px solid #333; border-radius: 3px; background: rgba(255,255,255,0.02); position: relative; cursor: pointer;";
|
||||
|
||||
if (crop) {
|
||||
const totalTime = crop.endTime - crop.startTime;
|
||||
const progress = totalTime > 0 ? (Math.max(0, now - crop.startTime) / totalTime) : 1;
|
||||
cell.style.background = getProgressColor(progress);
|
||||
|
||||
let anyReady = false;
|
||||
let tooltip = `Slot: ${i}\nSpecies: ${tile.species}`;
|
||||
slots.forEach((s, idx) => {
|
||||
const isReady = now >= s.endTime;
|
||||
if (isReady) anyReady = true;
|
||||
const left = Math.max(0, Math.ceil((s.endTime - now) / 1000));
|
||||
tooltip += `\n#${idx}: ${isReady ? 'READY' : left + 's'}`;
|
||||
if (s.mutations?.length) tooltip += ` [${s.mutations.join(',')}]`;
|
||||
});
|
||||
|
||||
cell.style.border = `1px solid ${anyReady ? '#fff' : '#555'}`;
|
||||
cell.title = tooltip;
|
||||
cell.innerHTML = `<span style="font-size: 10px; color: rgba(0,0,0,0.5); font-weight: bold; position: absolute; top: 1px; left: 2px;">${tile.species[0]}</span>`;
|
||||
|
||||
cell.onclick = () => {
|
||||
slots.forEach((s, idx) => {
|
||||
if (Date.now() >= s.endTime) {
|
||||
MB.sendMsg({ type: 'HarvestCrop', slot: i, slotsIndex: idx, scopePath: ["Room", "Quinoa"] });
|
||||
}
|
||||
});
|
||||
cell.style.opacity = '0.5';
|
||||
};
|
||||
}
|
||||
|
||||
if (i % 20 < 10) leftGrid.appendChild(cell);
|
||||
else rightGrid.appendChild(cell);
|
||||
}
|
||||
}
|
||||
};
|
||||
54
extension/modules/ui/overlays/inventory.js
Normal file
54
extension/modules/ui/overlays/inventory.js
Normal file
@@ -0,0 +1,54 @@
|
||||
import { WindowManager } from './window_manager.js';
|
||||
|
||||
export const InventoryOverlay = {
|
||||
open() {
|
||||
const { win, content } = WindowManager.create('window-inventory', '🎒 Inventory', { x: 560, y: 20, width: 220, height: 300 });
|
||||
this.update(content);
|
||||
},
|
||||
|
||||
update(container) {
|
||||
if (!container) container = document.getElementById('window-inventory-content');
|
||||
if (!container) return;
|
||||
|
||||
const MB = window.MagicBot;
|
||||
if (!MB || !MB.state || !MB.state.inventory || !MB.state.inventory.items) {
|
||||
container.innerHTML = '<div style="color:red">No Data</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = '';
|
||||
const grid = document.createElement('div');
|
||||
grid.style.cssText = "display: grid; grid-template-columns: 1fr; gap: 5px; font-size: 11px;";
|
||||
|
||||
MB.state.inventory.items.forEach(item => {
|
||||
let name = item.itemType;
|
||||
let species = item.species || item.parameters?.species || Object.values(item.parameters?.speciesIds || {})[0];
|
||||
if (species) name = species + " " + item.itemType;
|
||||
|
||||
const div = document.createElement('div');
|
||||
div.style.cssText = "background: rgba(255,255,255,0.05); padding: 4px; border-radius: 4px; display: flex; justify-content: space-between; cursor: pointer;";
|
||||
div.innerHTML = `<span>${name}</span><span style="color:#aaa; font-weight:bold;">x${item.quantity || item.count || 1}</span>`;
|
||||
|
||||
// Plant logic on click
|
||||
div.onclick = () => {
|
||||
if (item.itemType === 'Seed' && species) {
|
||||
// Find empty slot
|
||||
for (let i = 0; i < 200; i++) {
|
||||
if (!MB.state.garden.tileObjects[i.toString()]?.slots?.length) {
|
||||
MB.sendMsg({ type: "PlantSeed", slot: i, species: species, scopePath: ["Room", "Quinoa"] });
|
||||
div.style.background = 'rgba(100,255,100,0.2)';
|
||||
setTimeout(() => div.style.background = 'rgba(255,255,255,0.05)', 200);
|
||||
return;
|
||||
}
|
||||
}
|
||||
alert("Garden Full!");
|
||||
}
|
||||
};
|
||||
|
||||
grid.appendChild(div);
|
||||
});
|
||||
|
||||
if (MB.state.inventory.items.length === 0) grid.innerHTML = '<div style="color:#666; text-align:center;">Empty</div>';
|
||||
container.appendChild(grid);
|
||||
}
|
||||
};
|
||||
117
extension/modules/ui/overlays/model_overlay.js
Normal file
117
extension/modules/ui/overlays/model_overlay.js
Normal file
@@ -0,0 +1,117 @@
|
||||
import { WindowManager } from './window_manager.js';
|
||||
import { Styles, createElement } from '../ui_styles.js';
|
||||
|
||||
export const ModelOverlay = {
|
||||
interval: null,
|
||||
|
||||
open() {
|
||||
const { win, content } = WindowManager.create('window-model', '🧠 Model Visualization', { x: 260, y: 340, width: 300, height: 400 });
|
||||
this.update(content);
|
||||
|
||||
if (this.interval) clearInterval(this.interval);
|
||||
this.interval = setInterval(() => this.update(content), 1000); // 1s refresh
|
||||
|
||||
// Cleanup on close (hacky but works for now if window maps to ID)
|
||||
const closeBtn = win.querySelector('button'); // THe X button
|
||||
if (closeBtn) {
|
||||
const oldClick = closeBtn.onclick;
|
||||
closeBtn.onclick = () => {
|
||||
if (this.interval) clearInterval(this.interval);
|
||||
if (oldClick) oldClick();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
update(container) {
|
||||
if (!container) container = document.getElementById('window-model-content');
|
||||
if (!container) {
|
||||
if (this.interval) clearInterval(this.interval);
|
||||
return;
|
||||
}
|
||||
|
||||
const MB = window.MagicBot;
|
||||
if (!MB || !MB.state || !MB.state.inventory) {
|
||||
container.innerHTML = '<div style="color:red">No State Data</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = '';
|
||||
const list = createElement('div', 'display: flex; flex-direction: column; gap: 4px; font-size: 10px;');
|
||||
|
||||
// Header Info
|
||||
const header = createElement('div', 'padding: 5px; background: rgba(0,0,0,0.2); margin-bottom: 5px; border-radius: 4px;');
|
||||
const rainProb = MB.weatherTracker ? (MB.weatherTracker.probabilities.rain * 100).toFixed(0) : '?';
|
||||
const frostProb = MB.weatherTracker ? (MB.weatherTracker.probabilities.frost * 100).toFixed(0) : '?';
|
||||
header.innerHTML = `
|
||||
<div><b>Weather Probabilities:</b> Rain: ${rainProb}% | Frost: ${frostProb}%</div>
|
||||
<div><b>Smart Harvest:</b> ${MB.automation && MB.automation.smartHarvest ? '<span style="color:#bada55">ON</span>' : '<span style="color:#ff5252">OFF</span>'}</div>
|
||||
`;
|
||||
list.appendChild(header);
|
||||
|
||||
const plots = MB.state.inventory.items.filter(i => i.itemType === 'Plot');
|
||||
if (plots.length === 0) {
|
||||
container.innerHTML += '<div style="padding:10px">No Plots Found</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
plots.forEach(plot => {
|
||||
if (!plot.properties) return;
|
||||
const crop = plot.properties.crop;
|
||||
if (!crop) return; // Empty plot
|
||||
|
||||
const row = createElement('div', 'background: rgba(255,255,255,0.05); padding: 5px; border-radius: 4px; display: flex; flex-direction: column; gap: 2px;');
|
||||
|
||||
// Basic Crop Info
|
||||
const name = crop.name;
|
||||
const stage = crop.stage;
|
||||
const totalStages = crop.stages;
|
||||
const isReady = stage >= totalStages;
|
||||
|
||||
// Value Calculation (Simple approximation if actual vals not available)
|
||||
const sellPrice = crop.sellPrice || 0;
|
||||
const currentVal = isReady ? sellPrice : 0;
|
||||
|
||||
// Decision Logic Inspection
|
||||
// We can't easily "spy" on the exact decision function result without modifying it to store state,
|
||||
// but we can replicate the visual indicators.
|
||||
|
||||
let statusColor = '#aaa';
|
||||
let statusText = 'Growing';
|
||||
|
||||
if (isReady) {
|
||||
// Check mutation potential
|
||||
const nextWeather = MB.weatherTracker ? MB.weatherTracker.nextEvent : 'Unknown';
|
||||
const hasMutation = crop.mutations && crop.mutations.length > 0;
|
||||
|
||||
if (hasMutation) {
|
||||
statusText = 'Ready (Mutated!)';
|
||||
statusColor = '#bada55'; // Green
|
||||
} else {
|
||||
// This is where we'd ideally show the "Wait vs Harvest" logic
|
||||
// For now, let's show the current value and a theoretical max if waiting
|
||||
statusText = 'Ready';
|
||||
statusColor = '#fff';
|
||||
}
|
||||
}
|
||||
|
||||
row.innerHTML = `
|
||||
<div style="font-weight:bold; display:flex; justify-content:space-between;">
|
||||
<span style="color:${Styles.colors.primary}">${name}</span>
|
||||
<span style="color:${statusColor}">${statusText}</span>
|
||||
</div>
|
||||
<div style="display:flex; justify-content:space-between; color:#888;">
|
||||
<span>Stage: ${stage}/${totalStages}</span>
|
||||
<span>Value: ${currentVal}</span>
|
||||
</div>
|
||||
<!--
|
||||
<div style="font-size:9px; color:#666;">
|
||||
EV: ??? | Boundary: ???
|
||||
</div>
|
||||
-->
|
||||
`;
|
||||
list.appendChild(row);
|
||||
});
|
||||
|
||||
container.appendChild(list);
|
||||
}
|
||||
};
|
||||
86
extension/modules/ui/overlays/pets.js
Normal file
86
extension/modules/ui/overlays/pets.js
Normal file
@@ -0,0 +1,86 @@
|
||||
import { WindowManager } from './window_manager.js';
|
||||
|
||||
export const PetsOverlay = {
|
||||
open() {
|
||||
// Position next to others
|
||||
const { win, content } = WindowManager.create('window-pets', '🐾 My Pets', { x: 20, y: 340, width: 220, height: 250 });
|
||||
this.update(content);
|
||||
},
|
||||
|
||||
update(container) {
|
||||
if (!container) container = document.getElementById('window-pets-content');
|
||||
if (!container) return;
|
||||
|
||||
const MB = window.MagicBot;
|
||||
if (!MB || !MB.state || !MB.state.inventory || !MB.state.inventory.items) {
|
||||
container.innerHTML = '<div style="color:red">No Data</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = '';
|
||||
const list = document.createElement('div');
|
||||
list.style.cssText = "display: flex; flex-direction: column; gap: 8px; font-size: 11px;";
|
||||
|
||||
const pets = MB.state.inventory.items.filter(i => i.itemType === 'Pet');
|
||||
|
||||
if (pets.length === 0) {
|
||||
container.innerHTML = '<div style="color:#aaa; text-align:center; padding: 10px;">No Pets Found</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
pets.forEach(p => {
|
||||
const row = document.createElement('div');
|
||||
row.style.cssText = `background: rgba(255,255,255,0.05); padding: 8px; border-radius: 4px; display: flex; flex-direction: column; gap: 2px;`;
|
||||
|
||||
// Name/Species
|
||||
const name = p.name || p.petSpecies;
|
||||
const species = p.petSpecies;
|
||||
const isAutoFeed = MB.automation && MB.automation.pets && MB.automation.pets[p.id] && MB.automation.pets[p.id].autoFeed;
|
||||
|
||||
const diets = {
|
||||
'Bee': ['Strawberry', 'Blueberry', 'Tulip', 'Daffodil', 'Lily'],
|
||||
'Goat': ['Pumpkin', 'Coconut', 'Cactus', 'Pepper']
|
||||
};
|
||||
const diet = diets[species] ? diets[species].join(', ') : 'Unknown Diet';
|
||||
|
||||
row.innerHTML = `
|
||||
<div style="font-weight:bold; color: #bada55; display: flex; justify-content: space-between; align-items:center;">
|
||||
<span>${name}</span>
|
||||
<label style="font-size:9px; color:#eee; display:flex; align-items:center;">
|
||||
<input type="checkbox" class="chk-pet-feed" data-id="${p.id}" ${isAutoFeed ? 'checked' : ''}> A-Feed
|
||||
</label>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: space-between; font-size: 9px; color: #666;">
|
||||
<span>${species}</span>
|
||||
<span>XP: ${p.xp}</span>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: space-between;">
|
||||
<span>Hunger: ${Math.floor(p.hunger || 0)}</span>
|
||||
</div>
|
||||
<div style="color: #aaa; font-size: 9px; margin-top: 4px;">
|
||||
Diet: ${diet}
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Event listener for checkbox (needs to be attached after innerHTML reflow)
|
||||
// We'll trust the parent re-render or attach globally for now to avoid complexity in this simple render loop.
|
||||
// Actually, let's attach immediately after this loop finishes or use a delegate.
|
||||
|
||||
list.appendChild(row);
|
||||
});
|
||||
|
||||
// Delegate listener
|
||||
list.onchange = (e) => {
|
||||
if (e.target.classList.contains('chk-pet-feed')) {
|
||||
const id = e.target.getAttribute('data-id');
|
||||
if (!MB.automation.pets) MB.automation.pets = {};
|
||||
if (!MB.automation.pets[id]) MB.automation.pets[id] = {};
|
||||
MB.automation.pets[id].autoFeed = e.target.checked;
|
||||
console.log(`[MagicBot] Pet ${id} Auto-Feed: ${e.target.checked}`);
|
||||
}
|
||||
};
|
||||
|
||||
container.appendChild(list);
|
||||
}
|
||||
};
|
||||
```
|
||||
34
extension/modules/ui/overlays/players.js
Normal file
34
extension/modules/ui/overlays/players.js
Normal file
@@ -0,0 +1,34 @@
|
||||
import { WindowManager } from './window_manager.js';
|
||||
|
||||
export const PlayersOverlay = {
|
||||
open() {
|
||||
const { win, content } = WindowManager.create('window-players', '👥 Players', { x: 20, y: 120, width: 200, height: 200 });
|
||||
this.update(content);
|
||||
},
|
||||
|
||||
update(container) {
|
||||
if (!container) container = document.getElementById('window-players-content');
|
||||
if (!container) return;
|
||||
|
||||
const MB = window.MagicBot;
|
||||
if (!MB || !MB.state || !MB.state.players) {
|
||||
container.innerHTML = '<div style="color:red">No Data</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = '';
|
||||
const list = document.createElement('div');
|
||||
list.style.cssText = "display: flex; flex-direction: column; gap: 4px; font-size: 11px;";
|
||||
|
||||
MB.state.players.forEach(p => {
|
||||
const isSelf = p.id === MB.state.playerId;
|
||||
const color = p.isConnected ? '#66bb6a' : '#666';
|
||||
const row = document.createElement('div');
|
||||
row.style.cssText = `background: rgba(255,255,255,${isSelf ? '0.1' : '0.05'}); padding: 4px; border-radius: 4px; display: flex; align-items: center; gap: 8px;`;
|
||||
row.innerHTML = `<div style="width:8px; height:8px; border-radius:50%; background:${color};"></div><span style="color:${isSelf ? '#448aff' : '#eee'}; font-weight:${isSelf ? 'bold' : 'normal'};">${p.name}</span>`;
|
||||
list.appendChild(row);
|
||||
});
|
||||
|
||||
container.appendChild(list);
|
||||
}
|
||||
};
|
||||
50
extension/modules/ui/overlays/shop.js
Normal file
50
extension/modules/ui/overlays/shop.js
Normal file
@@ -0,0 +1,50 @@
|
||||
import { WindowManager } from './window_manager.js';
|
||||
|
||||
export const ShopOverlay = {
|
||||
open() {
|
||||
const { win, content } = WindowManager.create('window-shop', '🏪 Shop', { x: 790, y: 20, width: 250, height: 350 });
|
||||
this.update(content);
|
||||
},
|
||||
|
||||
update(container) {
|
||||
if (!container) container = document.getElementById('window-shop-content');
|
||||
if (!container) return;
|
||||
|
||||
const MB = window.MagicBot;
|
||||
if (!MB || !MB.state || !MB.state.shops?.seed) {
|
||||
container.innerHTML = '<div style="color:red">No Shop Data</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = '';
|
||||
const grid = document.createElement('div');
|
||||
grid.style.cssText = "display: grid; grid-template-columns: repeat(2, 1fr); gap: 5px; font-size: 11px;";
|
||||
|
||||
MB.state.shops.seed.inventory.forEach(item => {
|
||||
const purchased = MB.state.shopPurchases?.seed?.purchases?.[item.species] || 0;
|
||||
const stock = Math.max(0, item.initialStock - purchased);
|
||||
const stockColor = stock > 0 ? '#66bb6a' : '#ff5252';
|
||||
const auto = MB.automation?.autoBuyItems?.has(item.species);
|
||||
|
||||
const div = document.createElement('div');
|
||||
div.style.cssText = `background: rgba(255,255,255,0.05); padding: 5px; border-radius: 4px; border-left: 2px solid ${stockColor}; border-right: 2px solid ${auto ? '#ffd700' : 'transparent'}; cursor: pointer;`;
|
||||
|
||||
div.innerHTML = `<div style="font-weight:bold;">${item.species}</div><div style="color:#888;">${stock}/${item.initialStock}</div>`;
|
||||
|
||||
div.oncontextmenu = (e) => {
|
||||
e.preventDefault();
|
||||
if (auto) MB.automation.autoBuyItems.delete(item.species);
|
||||
else MB.automation.autoBuyItems.add(item.species);
|
||||
this.update(container); // Refresh immediately to show border change
|
||||
};
|
||||
|
||||
div.onclick = () => {
|
||||
if (stock > 0) MB.sendMsg({ scopePath: ["Room", "Quinoa"], type: "PurchaseSeed", species: item.species });
|
||||
};
|
||||
|
||||
grid.appendChild(div);
|
||||
});
|
||||
|
||||
container.appendChild(grid);
|
||||
}
|
||||
};
|
||||
112
extension/modules/ui/overlays/window_manager.js
Normal file
112
extension/modules/ui/overlays/window_manager.js
Normal file
@@ -0,0 +1,112 @@
|
||||
export const WindowManager = (function () {
|
||||
let zIndexCounter = 9999990;
|
||||
|
||||
function makeDraggable(el, header) {
|
||||
let isDragging = false, offX = 0, offY = 0;
|
||||
|
||||
header.addEventListener('mousedown', (e) => {
|
||||
isDragging = true;
|
||||
offX = e.clientX - el.offsetLeft;
|
||||
offY = e.clientY - el.offsetTop;
|
||||
|
||||
// Bring to front
|
||||
zIndexCounter++;
|
||||
el.style.zIndex = zIndexCounter;
|
||||
});
|
||||
|
||||
window.addEventListener('mousemove', (e) => {
|
||||
if (isDragging) {
|
||||
el.style.left = (e.clientX - offX) + 'px';
|
||||
el.style.top = (e.clientY - offY) + 'px';
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('mouseup', () => isDragging = false);
|
||||
}
|
||||
|
||||
function createWindow(id, title, props = {}) {
|
||||
let win = document.getElementById(id);
|
||||
if (win) {
|
||||
// Just bring to front if exists
|
||||
zIndexCounter++;
|
||||
win.style.zIndex = zIndexCounter;
|
||||
return { win, content: document.getElementById(id + '-content') };
|
||||
}
|
||||
|
||||
const x = props.x || 100;
|
||||
const y = props.y || 100;
|
||||
const w = props.width || 300;
|
||||
const h = props.height || 200;
|
||||
|
||||
win = document.createElement('div');
|
||||
win.id = id;
|
||||
win.style.cssText = `
|
||||
position: fixed;
|
||||
top: ${y}px;
|
||||
left: ${x}px;
|
||||
width: ${w}px;
|
||||
height: ${h}px;
|
||||
background: rgba(10, 10, 20, 0.95);
|
||||
border: 1px solid #444;
|
||||
border-radius: 8px;
|
||||
z-index: ${++zIndexCounter};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-shadow: 0 4px 20px rgba(0,0,0,0.6);
|
||||
font-family: sans-serif;
|
||||
color: #eee;
|
||||
backdrop-filter: blur(5px);
|
||||
resize: both;
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
const header = document.createElement('div');
|
||||
header.id = id + '-header';
|
||||
header.style.cssText = `
|
||||
padding: 8px;
|
||||
background: rgba(255,255,255,0.05);
|
||||
border-bottom: 1px solid #444;
|
||||
cursor: move;
|
||||
font-weight: bold;
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const titleSpan = document.createElement('span');
|
||||
titleSpan.textContent = title;
|
||||
|
||||
const closeBtn = document.createElement('span');
|
||||
closeBtn.textContent = '✕';
|
||||
closeBtn.style.cssText = 'cursor: pointer; color: #ff5252; font-size: 14px;';
|
||||
closeBtn.onclick = () => win.remove();
|
||||
|
||||
header.appendChild(titleSpan);
|
||||
header.appendChild(closeBtn);
|
||||
|
||||
const content = document.createElement('div');
|
||||
content.id = id + '-content';
|
||||
content.style.cssText = 'flex: 1; overflow-y: auto; padding: 10px;';
|
||||
|
||||
win.appendChild(header);
|
||||
win.appendChild(content);
|
||||
document.body.appendChild(win);
|
||||
|
||||
makeDraggable(win, header);
|
||||
|
||||
// Allow clicking anywhere on window to bring to front
|
||||
win.addEventListener('mousedown', () => {
|
||||
if (win.style.zIndex != zIndexCounter) {
|
||||
zIndexCounter++;
|
||||
win.style.zIndex = zIndexCounter;
|
||||
}
|
||||
});
|
||||
|
||||
return { win, content };
|
||||
}
|
||||
|
||||
return {
|
||||
create: createWindow
|
||||
};
|
||||
})();
|
||||
Reference in New Issue
Block a user