190 lines
6.3 KiB
JavaScript
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);
|
|
}
|