adasd
This commit is contained in:
@@ -181,6 +181,25 @@ h1 {
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.widget-card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.widget-badge {
|
||||
display: inline-grid;
|
||||
place-items: center;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border-radius: 12px;
|
||||
background: rgba(15, 23, 42, 0.58);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.window-layer {
|
||||
position: absolute;
|
||||
inset: 24px 24px 92px 24px;
|
||||
@@ -272,6 +291,70 @@ h1 {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.start-menu {
|
||||
position: absolute;
|
||||
left: 24px;
|
||||
bottom: 102px;
|
||||
z-index: 90;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(240px, 280px) minmax(280px, 360px);
|
||||
gap: 16px;
|
||||
width: min(720px, calc(100% - 48px));
|
||||
padding: 18px;
|
||||
border-radius: 24px;
|
||||
background: rgba(15, 23, 42, 0.82);
|
||||
border: 1px solid var(--shell-border);
|
||||
box-shadow: var(--shadow);
|
||||
backdrop-filter: blur(24px);
|
||||
}
|
||||
|
||||
.start-menu-column,
|
||||
.start-menu-block {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.start-menu-entry {
|
||||
display: grid;
|
||||
grid-template-columns: 44px minmax(0, 1fr) auto;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
padding: 12px 14px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: 18px;
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
color: inherit;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.start-menu-entry strong,
|
||||
.start-menu-entry span {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.start-menu-entry small {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.start-menu-entry-badge,
|
||||
.start-menu-entry-toggle {
|
||||
display: inline-grid;
|
||||
place-items: center;
|
||||
min-width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 12px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.start-menu-entry-toggle.is-active {
|
||||
background: var(--accent);
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.taskbar-apps {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
@@ -314,6 +397,15 @@ h1 {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.start-menu {
|
||||
position: fixed;
|
||||
left: 16px;
|
||||
right: 16px;
|
||||
bottom: 88px;
|
||||
width: auto;
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.window-layer {
|
||||
position: static;
|
||||
inset: auto;
|
||||
|
||||
@@ -3,10 +3,17 @@ const payloadNode = document.getElementById('desktop-payload');
|
||||
if (payloadNode) {
|
||||
const payload = JSON.parse(payloadNode.textContent);
|
||||
const iconsRoot = document.getElementById('desktop-icons');
|
||||
const widgetZoneRoot = document.getElementById('widget-zone');
|
||||
const windowsRoot = document.getElementById('window-layer');
|
||||
const taskbarRoot = document.getElementById('taskbar-apps');
|
||||
const startButton = document.getElementById('start-button');
|
||||
const startMenu = document.getElementById('start-menu');
|
||||
const startMenuApps = document.getElementById('start-menu-apps');
|
||||
const startMenuWidgets = document.getElementById('start-menu-widgets');
|
||||
const startMenuActions = document.getElementById('start-menu-actions');
|
||||
const clockNode = document.getElementById('clock');
|
||||
const windows = new Map();
|
||||
const activeWidgets = new Set(payload.widgets.active_ids);
|
||||
let zIndex = 20;
|
||||
|
||||
const renderClock = () => {
|
||||
@@ -36,6 +43,15 @@ if (payloadNode) {
|
||||
record.node.classList.add('is-focused');
|
||||
};
|
||||
|
||||
const closeStartMenu = () => {
|
||||
if (!startMenu) {
|
||||
return;
|
||||
}
|
||||
|
||||
startMenu.hidden = true;
|
||||
startButton?.setAttribute('aria-expanded', 'false');
|
||||
};
|
||||
|
||||
const syncTaskbar = () => {
|
||||
taskbarRoot.innerHTML = '';
|
||||
|
||||
@@ -152,6 +168,153 @@ if (payloadNode) {
|
||||
syncTaskbar();
|
||||
};
|
||||
|
||||
const openApp = (app) => {
|
||||
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 || '',
|
||||
});
|
||||
};
|
||||
|
||||
const renderWidgets = () => {
|
||||
widgetZoneRoot.innerHTML = '';
|
||||
|
||||
payload.widgets.registry
|
||||
.filter((widget) => activeWidgets.has(widget.widget_id))
|
||||
.forEach((widget) => {
|
||||
const card = document.createElement('article');
|
||||
card.className = 'widget-card';
|
||||
card.innerHTML = `
|
||||
<div class="widget-card-header">
|
||||
<div>
|
||||
<h3>${widget.title}</h3>
|
||||
<p>${widget.summary || ''}</p>
|
||||
</div>
|
||||
<span class="widget-badge">${widget.icon || 'WG'}</span>
|
||||
</div>
|
||||
<p>${widget.content || ''}</p>
|
||||
`;
|
||||
widgetZoneRoot.appendChild(card);
|
||||
});
|
||||
};
|
||||
|
||||
const renderMenuWidgets = () => {
|
||||
startMenuWidgets.innerHTML = '';
|
||||
|
||||
payload.widgets.registry.forEach((widget) => {
|
||||
const button = document.createElement('button');
|
||||
button.type = 'button';
|
||||
button.className = 'start-menu-entry';
|
||||
button.innerHTML = `
|
||||
<span class="start-menu-entry-badge">${widget.icon || 'WG'}</span>
|
||||
<span>
|
||||
<strong>${widget.title}</strong>
|
||||
<small>${widget.summary || ''}</small>
|
||||
</span>
|
||||
<span class="start-menu-entry-toggle ${activeWidgets.has(widget.widget_id) ? 'is-active' : ''}">
|
||||
${activeWidgets.has(widget.widget_id) ? 'On' : 'Off'}
|
||||
</span>
|
||||
`;
|
||||
button.addEventListener('click', () => {
|
||||
if (activeWidgets.has(widget.widget_id)) {
|
||||
activeWidgets.delete(widget.widget_id);
|
||||
} else {
|
||||
activeWidgets.add(widget.widget_id);
|
||||
}
|
||||
|
||||
renderWidgets();
|
||||
renderMenuWidgets();
|
||||
});
|
||||
startMenuWidgets.appendChild(button);
|
||||
});
|
||||
};
|
||||
|
||||
const runQuickAction = (actionId) => {
|
||||
if (actionId === 'open-public-dashboard') {
|
||||
const publicDashboard = payload.apps.find((app) => app.app_id === 'public-dashboard');
|
||||
if (publicDashboard) {
|
||||
openApp(publicDashboard);
|
||||
}
|
||||
closeStartMenu();
|
||||
return;
|
||||
}
|
||||
|
||||
if (actionId === 'toggle-all-widgets') {
|
||||
const shouldShowAll = activeWidgets.size !== payload.widgets.registry.length;
|
||||
|
||||
activeWidgets.clear();
|
||||
if (shouldShowAll) {
|
||||
payload.widgets.registry.forEach((widget) => activeWidgets.add(widget.widget_id));
|
||||
}
|
||||
|
||||
renderWidgets();
|
||||
renderMenuWidgets();
|
||||
return;
|
||||
}
|
||||
|
||||
if (actionId === 'cycle-skin') {
|
||||
const skins = payload.meta.available_skins;
|
||||
const currentIndex = skins.indexOf(payload.meta.active_skin);
|
||||
const nextSkin = skins[(currentIndex + 1) % skins.length];
|
||||
window.location.search = `?skin=${encodeURIComponent(nextSkin)}`;
|
||||
}
|
||||
};
|
||||
|
||||
const renderStartMenu = () => {
|
||||
startMenuApps.innerHTML = '';
|
||||
startMenuActions.innerHTML = '';
|
||||
|
||||
payload.apps.forEach((app) => {
|
||||
const button = document.createElement('button');
|
||||
button.type = 'button';
|
||||
button.className = 'start-menu-entry';
|
||||
button.innerHTML = `
|
||||
<span class="start-menu-entry-badge">${app.icon}</span>
|
||||
<span>
|
||||
<strong>${app.title}</strong>
|
||||
<small>${app.summary || ''}</small>
|
||||
</span>
|
||||
<span class="start-menu-entry-toggle">Open</span>
|
||||
`;
|
||||
button.addEventListener('click', () => {
|
||||
openApp(app);
|
||||
closeStartMenu();
|
||||
});
|
||||
startMenuApps.appendChild(button);
|
||||
});
|
||||
|
||||
payload.menu.quick_actions.forEach((action) => {
|
||||
const button = document.createElement('button');
|
||||
button.type = 'button';
|
||||
button.className = 'start-menu-entry';
|
||||
button.innerHTML = `
|
||||
<span class="start-menu-entry-badge">QA</span>
|
||||
<span>
|
||||
<strong>${action.label}</strong>
|
||||
<small>${action.description}</small>
|
||||
</span>
|
||||
<span class="start-menu-entry-toggle">Run</span>
|
||||
`;
|
||||
button.addEventListener('click', () => runQuickAction(action.action_id));
|
||||
startMenuActions.appendChild(button);
|
||||
});
|
||||
|
||||
renderMenuWidgets();
|
||||
};
|
||||
|
||||
payload.apps.forEach((app) => {
|
||||
const icon = document.createElement('button');
|
||||
icon.type = 'button';
|
||||
@@ -160,29 +323,46 @@ if (payloadNode) {
|
||||
<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 || '',
|
||||
});
|
||||
});
|
||||
icon.addEventListener('click', () => openApp(app));
|
||||
iconsRoot.appendChild(icon);
|
||||
});
|
||||
|
||||
startButton?.setAttribute('aria-expanded', 'false');
|
||||
startButton?.addEventListener('click', () => {
|
||||
if (!startMenu) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isOpen = !startMenu.hidden;
|
||||
startMenu.hidden = isOpen;
|
||||
startButton.setAttribute('aria-expanded', String(!isOpen));
|
||||
});
|
||||
|
||||
document.addEventListener('click', (event) => {
|
||||
if (!startMenu || startMenu.hidden) {
|
||||
return;
|
||||
}
|
||||
|
||||
const target = event.target;
|
||||
if (!(target instanceof Node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (startMenu.contains(target) || startButton?.contains(target)) {
|
||||
return;
|
||||
}
|
||||
|
||||
closeStartMenu();
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', (event) => {
|
||||
if (event.key === 'Escape') {
|
||||
closeStartMenu();
|
||||
}
|
||||
});
|
||||
|
||||
renderWidgets();
|
||||
renderStartMenu();
|
||||
payload.windows.forEach(createWindow);
|
||||
renderClock();
|
||||
window.setInterval(renderClock, 1000);
|
||||
|
||||
Reference in New Issue
Block a user