WEFT_OS/infra/shell/system-ui.html

396 lines
12 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

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.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>WEFT Desktop</title>
<style>
:root {
color-scheme: dark;
--surface-bg: rgba(255, 255, 255, 0.07);
--surface-border: rgba(255, 255, 255, 0.12);
--taskbar-height: 48px;
--text-primary: rgba(255, 255, 255, 0.92);
--text-secondary: rgba(255, 255, 255, 0.55);
--accent: #5b8af5;
}
*, *::before, *::after {
box-sizing: border-box;
}
body {
margin: 0;
overflow: hidden;
background: #0f1117;
font-family: system-ui, -apple-system, sans-serif;
color: var(--text-primary);
height: 100dvh;
width: 100dvw;
}
weft-desktop {
display: grid;
grid-template-rows: 1fr var(--taskbar-height);
height: 100dvh;
width: 100dvw;
contain: layout;
}
weft-wallpaper {
display: block;
grid-row: 1;
background: linear-gradient(
145deg,
#0f1117 0%,
#161b2e 40%,
#0d2040 70%,
#0a1a30 100%
);
}
weft-taskbar {
display: flex;
align-items: center;
grid-row: 2;
height: var(--taskbar-height);
padding: 0 12px;
gap: 8px;
background: var(--surface-bg);
border-top: 1px solid var(--surface-border);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
}
weft-taskbar-launcher {
display: flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
border-radius: 8px;
cursor: pointer;
background: transparent;
transition: background 0.15s;
}
weft-taskbar-launcher:hover {
background: var(--surface-border);
}
weft-taskbar-clock {
margin-left: auto;
font-size: 13px;
color: var(--text-secondary);
font-variant-numeric: tabular-nums;
}
weft-launcher {
display: none;
position: fixed;
inset: 0;
bottom: var(--taskbar-height);
background: rgba(10, 12, 20, 0.85);
backdrop-filter: blur(32px);
-webkit-backdrop-filter: blur(32px);
z-index: 100;
padding: 24px;
overflow-y: auto;
}
weft-launcher:not([hidden]) {
display: block;
}
weft-app-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
gap: 16px;
max-width: 640px;
margin: 0 auto;
}
weft-app-icon {
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
padding: 12px 8px;
border-radius: 12px;
cursor: pointer;
color: var(--text-primary);
font-size: 12px;
text-align: center;
word-break: break-word;
transition: background 0.15s;
}
weft-app-icon:hover {
background: var(--surface-border);
}
weft-app-icon svg {
flex-shrink: 0;
}
weft-taskbar-app {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 12px;
color: var(--text-secondary);
padding: 0 4px 0 6px;
cursor: pointer;
border-radius: 6px;
transition: background 0.15s;
}
weft-taskbar-app:hover {
background: var(--surface-border);
}
weft-taskbar-app .task-close {
opacity: 0;
font-size: 11px;
line-height: 1;
padding: 1px 3px;
border-radius: 3px;
transition: opacity 0.1s, background 0.1s;
}
weft-taskbar-app:hover .task-close {
opacity: 1;
}
weft-taskbar-app .task-close:hover {
background: rgba(255,80,80,0.3);
}
weft-notification-center {
position: fixed;
top: 8px;
right: 8px;
width: 360px;
display: flex;
flex-direction: column;
gap: 8px;
z-index: 200;
pointer-events: none;
}
weft-window {
display: none;
}
</style>
</head>
<body>
<weft-desktop>
<weft-wallpaper></weft-wallpaper>
<weft-taskbar>
<weft-taskbar-launcher id="launcher-btn" title="Apps">
<svg width="18" height="18" viewBox="0 0 18 18" fill="none"
xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<rect x="1" y="1" width="6" height="6" rx="1.5"
fill="rgba(255,255,255,0.7)" />
<rect x="11" y="1" width="6" height="6" rx="1.5"
fill="rgba(255,255,255,0.7)" />
<rect x="1" y="11" width="6" height="6" rx="1.5"
fill="rgba(255,255,255,0.7)" />
<rect x="11" y="11" width="6" height="6" rx="1.5"
fill="rgba(255,255,255,0.7)" />
</svg>
</weft-taskbar-launcher>
<weft-taskbar-clock id="clock"></weft-taskbar-clock>
</weft-taskbar>
</weft-desktop>
<weft-launcher hidden id="launcher">
<weft-app-grid id="app-grid"></weft-app-grid>
</weft-launcher>
<weft-notification-center id="notifications"></weft-notification-center>
<script>
(function () {
var clockEl = document.getElementById('clock');
function updateClock() {
clockEl.textContent = new Date().toLocaleTimeString([], {
hour: '2-digit',
minute: '2-digit',
});
}
updateClock();
setInterval(updateClock, 10000);
document.getElementById('launcher-btn').addEventListener('click', function () {
var launcher = document.getElementById('launcher');
if (launcher.hasAttribute('hidden')) {
launcher.removeAttribute('hidden');
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'QUERY_INSTALLED_APPS' }));
}
} else {
launcher.setAttribute('hidden', '');
}
});
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape') {
document.getElementById('launcher').setAttribute('hidden', '');
}
});
document.getElementById('launcher').addEventListener('click', function (e) {
if (e.target === this) {
this.setAttribute('hidden', '');
}
});
var APPD_WS_PORT = window.WEFT_APPD_WS_PORT || 7410;
var ws = null;
var wsReconnectDelay = 1000;
function appdConnect() {
try {
ws = new WebSocket('ws://127.0.0.1:' + APPD_WS_PORT + '/appd');
} catch (e) {
scheduleReconnect();
return;
}
ws.addEventListener('open', function () {
wsReconnectDelay = 1000;
document.querySelectorAll('weft-taskbar-app').forEach(function (el) { el.remove(); });
ws.send(JSON.stringify({ type: 'QUERY_RUNNING' }));
ws.send(JSON.stringify({ type: 'QUERY_INSTALLED_APPS' }));
});
ws.addEventListener('message', function (ev) {
var msg;
try { msg = JSON.parse(ev.data); } catch (e) { return; }
handleAppdMessage(msg);
});
ws.addEventListener('close', function () {
ws = null;
scheduleReconnect();
});
ws.addEventListener('error', function () {
ws = null;
});
}
function scheduleReconnect() {
setTimeout(appdConnect, wsReconnectDelay);
wsReconnectDelay = Math.min(wsReconnectDelay * 2, 16000);
}
function handleAppdMessage(msg) {
if (msg.type === 'INSTALLED_APPS') {
renderLauncher(msg.apps || []);
} else if (msg.type === 'APP_READY') {
var label = msg.app_id ? msg.app_id.split('.').pop() : String(msg.session_id);
showNotification(label + ' is ready');
} else if (msg.type === 'APP_STATE') {
if (msg.state === 'stopped') {
removeTaskbarEntry(msg.session_id);
}
} else if (msg.type === 'RUNNING_APPS') {
msg.sessions.forEach(function (s) {
ensureTaskbarEntry(s.session_id, s.app_id);
});
} else if (msg.type === 'LAUNCH_ACK') {
ensureTaskbarEntry(msg.session_id, msg.app_id || null);
} else if (msg.type === 'ERROR') {
console.warn('appd error', msg.code, msg.message);
}
}
function renderLauncher(apps) {
var grid = document.getElementById('app-grid');
grid.innerHTML = '';
if (apps.length === 0) {
var empty = document.createElement('p');
empty.textContent = 'No apps installed';
empty.style.cssText = 'color:var(--text-secondary);font-size:13px;';
grid.appendChild(empty);
return;
}
apps.forEach(function (app) {
var icon = document.createElement('weft-app-icon');
icon.dataset.appId = app.app_id;
icon.title = app.app_id;
icon.innerHTML =
'<svg width="40" height="40" viewBox="0 0 40 40" fill="none"' +
' xmlns="http://www.w3.org/2000/svg" aria-hidden="true">' +
'<rect width="40" height="40" rx="10" fill="rgba(91,138,245,0.25)"/>' +
'<text x="20" y="27" text-anchor="middle" font-size="18" fill="rgba(255,255,255,0.8)">' +
(app.name ? app.name.charAt(0).toUpperCase() : '?') +
'</text></svg>' +
'<span>' + (app.name || app.app_id) + '</span>';
icon.addEventListener('click', function () {
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'LAUNCH_APP', app_id: app.app_id, surface_id: 0 }));
}
document.getElementById('launcher').setAttribute('hidden', '');
});
grid.appendChild(icon);
});
}
function ensureTaskbarEntry(sessionId, appId) {
var id = 'task-' + sessionId;
if (document.getElementById(id)) { return; }
var label = appId ? appId.split('.').pop() : String(sessionId);
var el = document.createElement('weft-taskbar-app');
el.id = id;
el.dataset.sessionId = sessionId;
if (appId) { el.dataset.appId = appId; }
el.title = appId ? appId + ' (session ' + sessionId + ')' : 'Session ' + sessionId;
var span = document.createElement('span');
span.textContent = '● ' + label;
var close = document.createElement('span');
close.className = 'task-close';
close.textContent = '×';
close.title = 'Terminate';
close.addEventListener('click', function (e) {
e.stopPropagation();
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'TERMINATE_APP', session_id: sessionId }));
}
});
el.appendChild(span);
el.appendChild(close);
var taskbar = document.querySelector('weft-taskbar');
var clock = document.getElementById('clock');
taskbar.insertBefore(el, clock);
}
function removeTaskbarEntry(sessionId) {
var el = document.getElementById('task-' + sessionId);
if (el) { el.remove(); }
}
function showNotification(text) {
var center = document.getElementById('notifications');
var note = document.createElement('div');
note.textContent = text;
note.style.cssText = [
'background:var(--surface-bg)',
'border:1px solid var(--surface-border)',
'border-radius:8px',
'padding:10px 14px',
'font-size:13px',
'color:var(--text-primary)',
'pointer-events:auto',
'backdrop-filter:blur(20px)',
].join(';');
center.appendChild(note);
setTimeout(function () { note.remove(); }, 4000);
}
appdConnect();
}());
</script>
</body>
</html>