try { const isMobile = ( window.matchMedia("(pointer: coarse)").matches || window.innerWidth <= 768 || /Android|iPhone|iPad|iPod/i.test(navigator.userAgent) ); let topZ = 100; const maximizeIcon = '▢'; const restoreIcon = '🗗'; const startBtn = document.getElementById('start-btn'); const startMenu = document.getElementById('start-menu'); const windowsContainer = document.getElementById('windows'); const taskbarWindows = document.getElementById('taskbar-windows'); const ctx = new ContextMenu(); const windowCleanup = new Map(); const username = getCookie('sAMAccountName'); const LS_KEY = (key) => `${username}:${key}`; const MAX_PADDING = { left: 4, top: 4, right: 4, bottom: (56 - 4) }; startBtn.addEventListener('click', (evt) => { evt.stopPropagation(); // verhindert sofortiges Schließen startMenu.classList.toggle('hidden'); }); // Launch app when clicking start menu item document.addEventListener('click', async (e) => { const target = e.target.closest('.start-item'); const clickedInsideMenu = startMenu.contains(e.target); const clickedButton = startBtn.contains(e.target); if(!clickedInsideMenu && !clickedButton) { startMenu.classList.add('hidden'); return; } if (!target) return; const name = target.dataset.appname; const view = target.dataset.appview; const id = `win-${name}.${view}`; const active = target.dataset.active; if(active !== "true") return // 👉 WICHTIG: erst prüfen ob Fenster existiert const focused = focusWindowById(id); if (focused) { startMenu.classList.add('hidden'); return; } openApp({ name, view, viewLabel: target.dataset.viewlabel }); startMenu.classList.add('hidden'); }); /* // serverside route app.post('/children/Demo/second_frame', async (req, res) => { await renderView(app, `${metadata.name}/views/chldren/%name_of_file%`, { ...metadata, tutorial: false }, res) }); //clientside trigger e.g.: onClick: () => openView({ name: 'Demo', view: 'second_frame', viewLabel: 'Second Frame', content: 'Hello world, I\'m the second frame', defaultSize: { width: '900px', height: '300px' } }) */ window.openView = async (payload) => { const { name, view, viewLabel } = payload; const id = `win-${name}.${view}`; const exists = document.querySelector(`[data-winid="${id}"]`); let resume = null; if (exists) { resume = { left: exists.style.left, top: exists.style.top }; exists.remove(); } const res = await fetch(`/children/${name}/${view}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); if (!res.ok) { console.warn('Child partial nicht gefunden', `${name}.${view}`); return; } const html = await res.text(); const wrapper = document.createElement('div'); wrapper.innerHTML = html; const win = wrapper.firstElementChild; win.style.left = resume?.left || (50 + Math.random()*100) + 'px'; win.style.top = resume?.top || (50 + Math.random()*60) + 'px'; const defaultW = payload.defaultSize?.width || 800; const defaultH = payload.defaultSize?.height || 600; win.style.width = payload.size?.width || defaultW; win.style.height = payload.size?.height || defaultH; win.dataset.winid = id; win.dataset.appname = name; win.dataset.appview = view; win.dataset.viewLabel = viewLabel; win.dataset.type = 'view'; windowsContainer.appendChild(win); bringToFront(win); reloadJS(win); wireWindowControls(win); } async function openApp(payload) { const { name, view, viewLabel } = payload; const saved = JSON.parse(localStorage.getItem(LS_KEY('openWindows')) || '[]') // 🔁 prüfen ob schon offen const id = `win-${name}.${view}`; const exists = document.querySelector(`[data-winid="${id}"]`); if (exists) { bringToFront(exists); startMenu.classList.add('hidden'); return; } // 📡 Server holen const resMeta = await fetch('/api/open_app', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); if (!resMeta.ok) { console.warn('open_app failed'); return; } const meta = await resMeta.json(); const res = await fetch(`/window/${name}/${view}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); if (!res.ok) { console.warn('Window partial nicht gefunden', `${name}.${view}`); return; } const html = await res.text(); const wrapper = document.createElement('div'); wrapper.innerHTML = html; const win = wrapper.firstElementChild; // 📍 Position win.style.left = meta.location?.left || (50 + Math.random()*100) + 'px'; win.style.top = meta.location?.top || (50 + Math.random()*60) + 'px'; // 📐 Größe const defaultW = meta.context.defaultSize?.width || 800; const defaultH = meta.context.defaultSize?.height || 600; win.style.width = meta.size?.width || defaultW; win.style.height = meta.size?.height || defaultH; // 🆔 IDs setzen win.dataset.winid = id; win.dataset.appname = name; win.dataset.appview = view; win.dataset.viewLabel = viewLabel; win.dataset.type = 'app'; windowsContainer.appendChild(win); if (isMobile) { const maximizeBtn = win.querySelector('.maximize'); if (maximizeBtn) maximizeBtn.style.display = 'none'; } if(!saved.length) { payload.zIndex = topZ; } if (payload.state !== 'minimized') { bringToFront(win); } if (payload.state !== 'minimized') requestAnimationFrame(() => saveOpenWindows()); reloadJS(win); // 🧷 Taskbar Button const btn = document.createElement('div'); btn.className = 'taskbar-item focus'; btn.dataset.winid = id; btn.innerHTML = `${meta.context.menu.label}
${ meta.context.menu.label != viewLabel ? `[${viewLabel}]` : ' ' }`; btn.oncontextmenu = (evt) => { evt.preventDefault(); if(ctx != null) { ctx.setItems([ { label: "Schließen", onClick: () => { btn.remove(); saveOpenWindows() handleWindowAction({ id: win.dataset.winid, action: 'close' }); } } ]); ctx.show(btn, { position: 'top' }); } } resetFocus(id); taskbarWindows.appendChild(btn); wireWindowControls(win, btn); restoreWindow(win, meta, btn); } function reloadJS(win) { const runtimeId = win.dataset.runtimeId; win.querySelectorAll('script').forEach(oldScript => { const script = document.createElement('script'); if (!oldScript.src) { script.textContent = `(function(runtimeId){${oldScript.textContent}})("${runtimeId}");`; } win.appendChild(script); oldScript.remove(); }); } function handleWindowAction(payload) { const win = document.querySelector(`[data-winid="${payload.id}"]`); if (!win) return; const tb = document.querySelector(`.taskbar-item[data-winid="${payload.id}"]`); if (tb) resetFocus(payload.id); if (payload.action === 'minimize') { win.dataset.state = 'minimized'; win.style.display = 'none'; tb?.classList.add('minimized'); } if (payload.action === 'restore') { win.style.display = 'flex'; win.dataset.state = 'normal'; bringToFront(win); tb?.classList.remove('minimized'); } if (payload.action === 'close') { win.remove(); tb?.remove(); } saveOpenWindows(); } // mainSocket.on('window_action', payload => { // // simple forwarding; client-specific behaviour can be implemented // const win = document.querySelector(`[data-winid="${payload.id}"]`); // if (!win) return; // const tb = document.querySelector(`.taskbar-item[data-winid="${payload.id}"]`); // if(tb) { // resetFocus(payload.id); // } // if (payload.action === 'minimize') { // win.dataset.state = 'minimized'; // win.style.display = 'none'; // if (tb) { // tb.classList.add('minimized'); // } // saveOpenWindows(); // <-- HIER // } // if (payload.action === 'restore') { // win.style.display = 'flex'; // win.dataset.state = 'normal'; // bringToFront(); // if (tb) { // tb.classList.remove('minimized'); // tb.classList.remove('focus'); // } // saveOpenWindows(); // <-- HIER // } // if (payload.action === 'close') { // win.remove(); // if (tb) tb.remove(); // saveOpenWindows(); // <-- HIER // } // }); // taskbar click toggles minimize // taskbarWindows.addEventListener('click', (e) => { // const btn = e.target.closest('.taskbar-item'); // if (!btn) return; // focusWindowById(btn.dataset.winid); // }); taskbarWindows.addEventListener('click', (e) => { const btn = e.target.closest('.taskbar-item'); if (!btn) return; const id = btn.dataset.winid; const win = document.querySelector(`[data-winid="${id}"]`); if (!win) return; const isHidden = win.style.display === 'none'; const isFocused = btn.classList.contains('focus'); // 🔵 1. minimiert → restore + fokus if (isHidden) { win.style.display = 'flex'; win.dataset.state = 'normal'; bringToFront(win); btn.classList.add('focus'); btn.classList.remove('minimized'); saveOpenWindows(); return; } // 🟢 2. sichtbar + aktiv → MINIMIEREN if (isFocused) { win.style.display = 'none'; win.dataset.state = 'minimized'; btn.classList.remove('focus'); btn.classList.add('minimized'); saveOpenWindows(); return; } // 🟡 3. sichtbar aber nicht aktiv → FOKUS bringToFront(win); resetFocus(id); btn.classList.add('focus'); btn.classList.remove('minimized'); }); function applyMaximized(win) { win.style.left = MAX_PADDING.left + 'px'; win.style.top = MAX_PADDING.top + 'px'; win.style.width = `calc(100% - ${MAX_PADDING.left + (MAX_PADDING.right * 1.5)}px)`; win.style.height = `calc(100% - ${MAX_PADDING.top + MAX_PADDING.bottom}px)`; win.classList.add('max'); win.dataset.state = 'maximized'; } function focusWindowById(id) { const win = document.querySelector(`[data-winid="${id}"]`); if (!win) return false; const tb = document.querySelector(`.taskbar-item[data-winid="${id}"]`); // 1. Falls minimiert → wieder anzeigen if (win.style.display === 'none') { win.style.display = 'flex'; win.dataset.state = 'normal'; } // 2. Fokus setzen bringToFront(win); if (tb) { resetFocus(id); tb.classList.add('focus'); tb.classList.remove('minimized'); } return true; } function cacheWindowGeometry(win) { const rect = win.getBoundingClientRect(); win.dataset.lastGeometry = JSON.stringify({ left: `${rect.left}px`, top: `${rect.top}px`, width: `${rect.width}px`, height: `${rect.height}px` }); } function storeNormalGeometry(win) { // ❌ NICHT speichern, wenn maximiert oder gesnappt if (win.classList.contains('max')) return; if (win.dataset.snapped) return; const r = win.getBoundingClientRect(); win.dataset.normalGeometry = JSON.stringify({ left: `${r.left}px`, top: `${r.top}px`, width: `${r.width}px`, height: `${r.height}px`, }); } function makeResizable(win) { let resizing = false; let dir, startX, startY, startW, startH, startL, startT; const minW = 300; const minH = 200; win.querySelectorAll('.window-resize-handle').forEach(handle => { handle.addEventListener('mousedown', startResize); handle.addEventListener('touchstart', startResize, { passive: false }); }); function startResize(e) { e.preventDefault(); if (win.classList.contains('max')) return; resizing = true; dir = e.target.dataset.dir; const r = win.getBoundingClientRect(); startX = e.clientX || e.touches[0].clientX; startY = e.clientY || e.touches[0].clientY; startW = r.width; startH = r.height; startL = r.left; startT = r.top; document.addEventListener('mousemove', resize); document.addEventListener('mouseup', stopResize); document.addEventListener('touchmove', resize, { passive: false }); document.addEventListener('touchend', stopResize); } function resize(e) { if (!resizing) return; const x = e.clientX || e.touches[0].clientX; const y = e.clientY || e.touches[0].clientY; const dx = x - startX; const dy = y - startY; if (dir.includes('e')) { win.style.width = Math.max(minW, startW + dx) + 'px'; } if (dir.includes('s')) { win.style.height = Math.max(minH, startH + dy) + 'px'; } if (dir.includes('w')) { const w = Math.max(minW, startW - dx); win.style.width = w + 'px'; win.style.left = startL + (startW - w) + 'px'; } if (dir.includes('n')) { const h = Math.max(minH, startH - dy); win.style.height = h + 'px'; win.style.top = startT + (startH - h) + 'px'; } } function stopResize() { resizing = false; document.removeEventListener('mousemove', resize); document.removeEventListener('mouseup', stopResize); document.removeEventListener('touchmove', resize); document.removeEventListener('touchend', stopResize); storeNormalGeometry(win); // <-- HIER (neu) saveOpenWindows(); } } function addResizeHandles(win) { const dirs = ['n','s','e','w','ne','nw','se','sw']; dirs.forEach(dir => { const h = document.createElement('div'); h.classList.add( 'window-resize-handle', `window-resize-${dir}` ); h.dataset.dir = dir; win.appendChild(h); }); } // Simple drag (titlebar) function wireWindowControls(win, taskbarBtn = null) { const minimize = win.querySelector('.minimize'); const maximize = win.querySelector('.maximize'); const close = win.querySelector('.close'); const titlebar = win.querySelector('.window-titlebar'); function isControl(el) { return el.closest('.minimize, .maximize, .close, .window-resize-handle'); } addResizeHandles(win); makeResizable(win); // 🟦 Minimieren if(minimize != null) { minimize.addEventListener('click', () => { cacheWindowGeometry(win); // 🔥 WICHTIG win.dataset.state = 'minimized'; win.style.display = 'none'; if (taskbarBtn) { taskbarBtn.classList.add('minimized'); taskbarBtn.classList.remove('focus'); } saveOpenWindows(); }); } win.addEventListener('mousedown', (e) => { if (isControl(e.target)) return; if (win.style.display !== 'none') { bringToFront(win); } }); win.addEventListener('touchstart', () => { if (win.style.display !== 'none') bringToFront(win); }); // 🟩 Maximieren / Wiederherstellen if(maximize != null) { maximize.addEventListener('click', (evt) => { if(taskbarBtn != null) { taskbarBtn.classList.add('focus'); maximize.innerHTML = win.dataset.state === 'normal' ? restoreIcon : maximizeIcon; resetFocus(win.dataset.winid); } toggleMaximize(); saveOpenWindows() }); } // 🟥 Schließen if(close != null) { close.addEventListener('click', () => { destroyWindow(win); if (taskbarBtn) taskbarBtn.remove(); saveOpenWindows(); handleWindowAction({ id: win.dataset.winid, action: 'close' }); }); } // 🟨 Doppelklick auf Titelleiste → Maximieren / Wiederherstellen let lastTap = 0; if (taskbarBtn !== null) { titlebar.addEventListener('dblclick', (evt) => { toggleMaximize(); titlebar.querySelector('.maximize').innerHTML = win.dataset.state === 'maximized' ? restoreIcon : maximizeIcon; saveOpenWindows() }); } titlebar.addEventListener('touchend', (ev) => { const current = Date.now(); const delta = current - lastTap; if (delta < 300 && delta > 0) { // Double tap → maximize / restore titlebar.querySelector('.maximize').innerHTML = win.dataset.state === 'maximized' ? restoreIcon : maximizeIcon; toggleMaximize(); } lastTap = current; }); function destroyWindow(win) { try { const runtimeId = win.dataset.runtimeId; // Cleanup ausführen if(windowCleanup !== null) { if (windowCleanup.has(runtimeId)) { windowCleanup.get(runtimeId).forEach(fn => { try { fn(); } catch(e) { console.warn(e); } }); windowCleanup.delete(runtimeId); } } // DOM entfernen win.remove() } catch (err) { alert(err.message) } } // function applyWindowState(win, payload) { // const state = payload.state || 'normal'; // if (state === 'minimized') { // win.style.display = 'none'; // win.dataset.state = 'minimized'; // } // if (state === 'maximized') { // win.dataset.state = 'maximized'; // win.classList.add('max'); // win.style.left = '8px'; // win.style.top = '8px'; // win.style.width = 'calc(100% - 16px)'; // win.style.height = 'calc(100% - 60px)'; // } // if (state === 'normal') { // win.dataset.state = 'normal'; // } // } function toggleMaximize() { if (!win.classList.contains('max')) { // 🔥 VOR dem Maximieren speichern storeNormalGeometry(win); applyMaximized(win); } else { const prev = JSON.parse(win.dataset.normalGeometry || '{}'); win.style.left = prev.left || '50px'; win.style.top = prev.top || '50px'; win.style.width = prev.width || '800px'; win.style.height = prev.height || '600px'; win.classList.remove('max'); win.dataset.state = 'normal'; // 🔥 WICHTIG: Snap/Drag Reset win.dataset.snapped = ''; } requestAnimationFrame(() => saveOpenWindows()); } // 🧲 Snap-to-Side + Drag Handling makeDraggableWithSnap(win); } function makeDraggableWithSnap(win) { if (win.style.display === 'none') return; const title = win.querySelector('.window-titlebar'); let isDown = false; let startX, startY, startLeft, startTop; let snapped = false; const snapThreshold = 30; // Pixel vom Rand zum Einrasten title.addEventListener('touchstart', (ev) => { if (ev.target.closest('.minimize, .maximize, .close')) return; isDown = true; const t = ev.touches[0]; startX = t.clientX; startY = t.clientY; startLeft = parseInt(win.style.left || 0); startTop = parseInt(win.style.top || 0); document.body.classList.add('dragging'); }, { passive: true }); document.addEventListener('touchmove', (ev) => { if (!isDown) return; const t = ev.touches[0]; const dx = t.clientX - startX; const dy = t.clientY - startY; win.style.left = startLeft + dx + 'px'; win.style.top = startTop + dy + 'px'; // Snap const screenW = window.innerWidth; const screenH = window.innerHeight; if (t.clientX <= snapThreshold) { applySnap(win, 'left'); } else if (t.clientX >= screenW - snapThreshold) { applySnap(win, 'right'); } else if (t.clientY <= snapThreshold) { applySnap(win, 'top'); } }, { passive: true }); document.addEventListener('touchend', () => { isDown = false; document.body.classList.remove('dragging'); saveOpenWindows() }); title.addEventListener('mousedown', (ev) => { if (ev.target.closest('.minimize, .maximize, .close')) return; isDown = true; startX = ev.clientX; startY = ev.clientY; const wasMax = win.classList.contains('max'); let restored = false; const onMove = (moveEv) => { if (!wasMax || restored) return; const dy = moveEv.clientY - startY; // 👉 erst wenn 12px NACH UNTEN gezogen if (dy > 12) { restored = true; // Größe wiederherstellen const prev = JSON.parse(win.dataset.normalGeometry || '{}'); const width = parseFloat(prev.width) || 800; const height = parseFloat(prev.height) || 600; let left = moveEv.clientX - width / 2; let top = moveEv.clientY - 10; left = Math.max(0, Math.min(left, window.innerWidth - width)); top = Math.max(0, Math.min(top, window.innerHeight - height)); win.style.left = left + 'px'; win.style.top = top + 'px'; win.style.width = width + 'px'; win.style.height = height + 'px'; win.classList.remove('max'); win.dataset.state = 'normal'; win.dataset.snapped = ''; // wichtig: neue drag basis setzen startX = moveEv.clientX; startY = moveEv.clientY; const rect = win.getBoundingClientRect(); startLeft = rect.left; startTop = rect.top; document.removeEventListener('mousemove', onMove); } }; const onUp = () => { document.removeEventListener('mousemove', onMove); document.removeEventListener('mouseup', onUp); isDown = false; document.body.classList.remove('dragging'); }; document.addEventListener('mousemove', onMove); document.addEventListener('mouseup', onUp); // 👉 normal drag only if NOT maximized if (!wasMax) { const rect = win.getBoundingClientRect(); startLeft = rect.left; startTop = rect.top; } document.body.classList.add('dragging'); }); document.addEventListener('mousemove', (ev) => { if (!isDown) return; const dx = ev.clientX - startX; const dy = ev.clientY - startY; win.style.left = startLeft + dx + 'px'; win.style.top = startTop + dy + 'px'; // Snap to edges const screenW = window.innerWidth; const screenH = window.innerHeight; if (ev.clientX <= snapThreshold) { // Snap left applySnap(win, 'left'); snapped = 'left'; } else if (ev.clientX >= screenW - snapThreshold) { // Snap right applySnap(win, 'right'); snapped = 'right'; } else if (ev.clientY <= snapThreshold) { // Snap top (maximize) applySnap(win, 'top'); win.querySelector('.maximize').innerHTML = '🗗'; snapped = 'top'; } else if (snapped && ev.clientX > snapThreshold * 2 && ev.clientX < screenW - snapThreshold * 2) { // Wegziehen vom Rand → Restore restoreFromSnap(win); snapped = false; } }); document.addEventListener('mouseup', () => { isDown = false; document.body.classList.remove('dragging'); saveOpenWindows() }); function applySnap(win, side) { if(win.dataset.type == 'view') return; if (!win.dataset.prevstyle) { win.dataset.prevstyle = JSON.stringify({ left: win.style.left, top: win.style.top, width: `${win.getBoundingClientRect().width}px`, height: `${win.getBoundingClientRect().height}px`, }); } win.classList.add('max'); if (side === 'left') { win.style.left = '0'; win.style.top = '8px'; win.style.width = '50%'; win.style.height = `calc(100% - 60px)`; } else if (side === 'right') { win.style.left = '50%'; win.style.top = '8px'; win.style.width = '50%'; win.style.height = `calc(100% - 60px)`; } else if (side === 'top') { applyMaximized(win); } win.dataset.state = side === 'top' ? 'maximized' : 'normal'; saveOpenWindows(); // <-- HIER (SEHR wichtig!) } function restoreFromSnap(win, pointerX, pointerY) { // 1️⃣ Alte Normalgröße wiederherstellen const prev = JSON.parse(win.dataset.normalGeometry || '{}'); const width = parseFloat(prev.width) || 800; const height = parseFloat(prev.height) || 600; // 2️⃣ Maus in die Fenster-Mitte setzen let left = pointerX - width / 2; let top = pointerY - height / 2; // 3️⃣ Fenster darf nicht aus dem Bildschirm rutschen const screenW = window.innerWidth; const screenH = window.innerHeight; left = Math.max(0, Math.min(left, screenW - width)); top = Math.max(0, Math.min(top, screenH - height)); // 4️⃣ Fenster anwenden win.style.left = left + 'px'; win.style.top = top + 'px'; win.style.width = width + 'px'; win.style.height = height + 'px'; win.classList.remove('max'); win.querySelector('.maximize').innerHTML = maximizeIcon; win.dataset.state = 'normal'; win.dataset.snapped = ''; } } function bringToFront(win) { // Wenn Fenster minimiert → nix machen if (win.style.display === 'none') return; const tb = document.querySelector(`.taskbar-item[data-winid="${win.dataset.winid}"]`); if(tb) { tb.classList.add('focus'); resetFocus(win.dataset.winid); saveOpenWindows(); } const currentZ = parseInt(win.style.zIndex || 0); if (currentZ < topZ) { topZ += 1; win.style.zIndex = topZ; } saveOpenWindows(); } function resetFocus(exceptID) { Array.from(document.querySelectorAll('.taskbar-item')).filter(btn => btn.dataset.winid != exceptID).forEach(btn => { btn.classList.remove('focus') }) } function restoreWindow(win, payload, taskbarBtn = null) { const state = payload.state || 'normal'; if (payload.location) { win.style.left = payload.location.left; win.style.top = payload.location.top; } if (payload.size) { win.style.width = payload.size.width; win.style.height = payload.size.height; } // zIndex wiederherstellen if (payload.zIndex) { win.style.zIndex = payload.zIndex; topZ = Math.max(topZ, payload.zIndex); // topZ anpassen, damit bringToFront funktioniert } win.dataset.state = state; if (state === 'minimized') { win.style.display = 'none'; if (taskbarBtn) { taskbarBtn.classList.add('minimized'); taskbarBtn.classList.remove('focus'); } } else { win.style.display = 'flex'; win.querySelector('.maximize').innerHTML = win.dataset.state === 'maximized' ? restoreIcon : maximizeIcon; } if (state === 'maximized') { win.classList.add('max'); } return; } function saveOpenWindows() { let previous = ((v)=>Array.isArray(v)?v:[])(JSON.parse(localStorage.getItem(LS_KEY('openWindows')) || '[]')); const openWindows = []; document.querySelectorAll('[data-winid]').forEach(win => { if (win.dataset.type !== 'app') return; if (!win.dataset.appname || !win.dataset.appview) return; let state = win.dataset.state || 'normal'; let geometry; if (win.classList.contains('max') && state === 'normal') { state = 'maximized'; } const prev = previous.length == 0 ? {} : previous.find(w => w.name === win.dataset.appname && w.view === win.dataset.appview ); if (win.dataset.state === 'minimized' && prev?.location && prev?.size) { // 🟡 Minimiert → alte gespeicherte Geometrie benutzen geometry = { left: prev.location.left, top: prev.location.top, width: prev.size.width, height: prev.size.height }; } else { // 🟢 Sichtbar → echte Größe messen const rect = win.getBoundingClientRect(); geometry = { left: win.style.left, top: win.style.top, width: win.style.width, height: win.style.height }; } // letzte gültige Geometrie merken win.dataset.lastGeometry = JSON.stringify(geometry); //alert(win.dataset.appname + ': ' + state) const entry = { name: win.dataset.appname, view: win.dataset.appview, viewLabel: win.dataset.viewLabel, state: state, zIndex: parseInt(win.style.zIndex || 0) }; if (win.dataset.state !== 'minimized') { entry.location = { left: geometry.left, top: geometry.top }; entry.size = { width: geometry.width, height: geometry.height }; } else if (prev?.location && prev?.size) { // minimiert → alte geometry trotzdem behalten entry.location = prev.location; entry.size = prev.size; } // alert(previous.find(w => w.name === entry.name && w.view === entry.view)); if(previous.find(w => w.name === entry.name && w.view === entry.view)) { // 🔁 In previous ersetzen previous[previous.findIndex(w => w.name === entry.name && w.view === entry.view)] = entry; } else { // ➕ Neu hinzufügen previous.push(entry); } openWindows.push(entry); }); localStorage.setItem(LS_KEY('openWindows'), JSON.stringify(openWindows)); } window.addEventListener('DOMContentLoaded', () => { let saved = JSON.parse(localStorage.getItem(LS_KEY('openWindows')) || '[]') if (saved.length > 0) { saved.sort((a,b) => (Number(a.zIndex) || 0) - (Number(b.zIndex) || 0)); for (const payload of saved) { // mainSocket.emit('open_app', payload); openApp(payload); } } if (savedFontFamily) { setFontFamily(savedFontFamily); } else { setFontFamily('Arial'); } if (savedFontSize) { setFontSize(savedFontSize); } else { setFontSize(18); } if (savedTheme) { loadServerStyles(savedTheme); } else { loadServerStyles('dark'); } }); } catch( err ) { alert(err) }