Files
desktop/public/assets/desktop/desktop.js
Lars Gebhardt-Kusche 888981c782
Some checks failed
Deploy / deploy-staging (push) Failing after 6s
Deploy / deploy-production (push) Has been skipped
New desktop
2026-06-06 03:20:50 +02:00

190 lines
6.3 KiB
JavaScript

const payloadNode = document.getElementById('desktop-payload');
if (payloadNode) {
const payload = JSON.parse(payloadNode.textContent);
const iconsRoot = document.getElementById('desktop-icons');
const windowsRoot = document.getElementById('window-layer');
const taskbarRoot = document.getElementById('taskbar-apps');
const clockNode = document.getElementById('clock');
const windows = new Map();
let zIndex = 20;
const renderClock = () => {
if (!clockNode) {
return;
}
clockNode.textContent = new Intl.DateTimeFormat('de-DE', {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
}).format(new Date());
};
const focusWindow = (windowId) => {
windows.forEach((record) => {
record.node.classList.remove('is-focused');
});
const record = windows.get(windowId);
if (!record) {
return;
}
zIndex += 1;
record.node.style.zIndex = String(zIndex);
record.node.classList.add('is-focused');
};
const syncTaskbar = () => {
taskbarRoot.innerHTML = '';
windows.forEach((record) => {
const button = document.createElement('button');
button.type = 'button';
button.className = 'taskbar-app-button';
button.textContent = record.title;
button.addEventListener('click', () => {
record.node.classList.toggle('is-minimized');
focusWindow(record.id);
});
taskbarRoot.appendChild(button);
});
};
const wireDragging = (record) => {
const header = record.node.querySelector('.window-header');
if (!header || window.matchMedia('(max-width: 1100px)').matches) {
return;
}
let startX = 0;
let startY = 0;
let originX = 0;
let originY = 0;
let dragging = false;
header.addEventListener('pointerdown', (event) => {
dragging = true;
startX = event.clientX;
startY = event.clientY;
originX = parseInt(record.node.style.left, 10);
originY = parseInt(record.node.style.top, 10);
header.setPointerCapture(event.pointerId);
focusWindow(record.id);
});
header.addEventListener('pointermove', (event) => {
if (!dragging) {
return;
}
record.node.style.left = `${originX + event.clientX - startX}px`;
record.node.style.top = `${originY + event.clientY - startY}px`;
});
header.addEventListener('pointerup', () => {
dragging = false;
});
};
const createWindow = (definition) => {
const node = document.createElement('article');
node.className = 'window';
node.style.left = `${definition.x}px`;
node.style.top = `${definition.y}px`;
node.style.width = `${definition.width}px`;
node.style.height = `${definition.height}px`;
node.innerHTML = `
<header class="window-header">
<strong>${definition.title}</strong>
<div class="window-actions">
<button class="window-action" data-action="minimize" type="button" aria-label="Minimieren"></button>
<button class="window-action" data-action="maximize" type="button" aria-label="Maximieren"></button>
<button class="window-action" data-action="close" type="button" aria-label="Schliessen"></button>
</div>
</header>
<div class="window-content">
<p>${definition.content}</p>
<p><strong>Route:</strong> ${definition.app_id}</p>
</div>
`;
const record = {
id: definition.window_id,
title: definition.title,
node,
definition,
};
node.addEventListener('mousedown', () => focusWindow(record.id));
node.querySelectorAll('.window-action').forEach((action) => {
action.addEventListener('click', () => {
const actionName = action.dataset.action;
if (actionName === 'close') {
windows.delete(record.id);
node.remove();
syncTaskbar();
return;
}
if (actionName === 'minimize') {
node.classList.toggle('is-minimized');
syncTaskbar();
return;
}
if (actionName === 'maximize') {
node.style.left = '24px';
node.style.top = '24px';
node.style.width = 'calc(100% - 48px)';
node.style.height = 'calc(100% - 48px)';
focusWindow(record.id);
}
});
});
windows.set(record.id, record);
windowsRoot.appendChild(node);
wireDragging(record);
focusWindow(record.id);
syncTaskbar();
};
payload.apps.forEach((app) => {
const icon = document.createElement('button');
icon.type = 'button';
icon.className = 'desktop-icon';
icon.innerHTML = `
<span class="desktop-icon-badge">${app.icon}</span>
<span class="desktop-icon-label">${app.title}</span>
`;
icon.addEventListener('click', () => {
const existing = Array.from(windows.values()).find((record) => record.definition.app_id === app.app_id);
if (existing) {
existing.node.classList.remove('is-minimized');
focusWindow(existing.id);
return;
}
createWindow({
window_id: `window-${app.app_id}-${Date.now()}`,
app_id: app.app_id,
title: app.title,
x: 64 + Math.round(Math.random() * 80),
y: 70 + Math.round(Math.random() * 60),
width: app.default_width || 640,
height: app.default_height || 420,
content: app.summary || '',
});
});
iconsRoot.appendChild(icon);
});
payload.windows.forEach(createWindow);
renderClock();
window.setInterval(renderClock, 1000);
}