Files
radixOS/public/javascript/os.js
2026-05-01 22:37:21 +02:00

1117 lines
30 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 taskbar = document.getElementById('taskbar');
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}`;
let MAX_PADDING = { left: 4, top: 4, right: 4, bottom: 40 };
startBtn.addEventListener('click', (evt) => {
evt.stopPropagation(); // verhindert sofortiges Schließen
startBtn.classList.toggle('active');
startMenu.classList.toggle('hidden');
});
function updateStartMenuPosition() {
const height = taskbar.offsetHeight;
startMenu.style.bottom = (height + 12) + 'px';
}
const observer = new ResizeObserver(entries => {
for (let entry of entries) {
const height = entry.contentRect.height;
MAX_PADDING = { left: 4, top: 4, right: 4, bottom: height + 8 };
windowsContainer.querySelectorAll('[data-winid]').forEach(win => {
if(win.dataset.state === 'maximized') {
applyMaximized(win) ;
}
});
}
});
observer.observe(taskbar);
window.addEventListener('resize', updateStartMenuPosition);
window.addEventListener('load', updateStartMenuPosition);
// 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) {
startBtn.classList.remove('active');
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}<br>${
meta.context.menu.label != viewLabel ? `[${viewLabel}]` : '&nbsp;'
}`;
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)
}