dadssad
This commit is contained in:
@@ -503,6 +503,13 @@ h1 {
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.window-content--desktop-debug {
|
||||
padding: 0;
|
||||
background:
|
||||
linear-gradient(180deg, rgba(241, 245, 249, 0.98), rgba(226, 232, 240, 0.98)),
|
||||
radial-gradient(circle at top right, rgba(34, 197, 94, 0.12), transparent 36%);
|
||||
}
|
||||
|
||||
.window-module-host {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
@@ -518,6 +525,94 @@ h1 {
|
||||
color: #991b1b;
|
||||
}
|
||||
|
||||
.desktop-debug-window {
|
||||
display: grid;
|
||||
grid-template-rows: auto minmax(0, 1fr);
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.desktop-debug-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
padding: 18px;
|
||||
border-bottom: 1px solid rgba(148, 163, 184, 0.22);
|
||||
background: rgba(255, 255, 255, 0.72);
|
||||
}
|
||||
|
||||
.desktop-debug-copy {
|
||||
margin: 0;
|
||||
color: #475569;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.desktop-debug-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.desktop-debug-action {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.desktop-debug-log {
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
padding: 18px;
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
background: rgba(15, 23, 42, 0.04);
|
||||
}
|
||||
|
||||
.desktop-debug-entry,
|
||||
.desktop-debug-empty {
|
||||
padding: 14px 16px;
|
||||
border-radius: 14px;
|
||||
background: rgba(255, 255, 255, 0.92);
|
||||
border: 1px solid rgba(148, 163, 184, 0.2);
|
||||
box-shadow: 0 8px 24px rgba(15, 23, 42, 0.06);
|
||||
}
|
||||
|
||||
.desktop-debug-entry-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin-bottom: 6px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.desktop-debug-entry-meta,
|
||||
.desktop-debug-entry-url,
|
||||
.desktop-debug-entry-message {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
color: #475569;
|
||||
}
|
||||
|
||||
.desktop-debug-entry-url {
|
||||
color: #0f172a;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.desktop-debug-entry-message {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.desktop-debug-entry-context {
|
||||
margin: 10px 0 0;
|
||||
padding: 12px;
|
||||
border-radius: 12px;
|
||||
background: #0f172a;
|
||||
color: #e2e8f0;
|
||||
overflow: auto;
|
||||
font-size: 12px;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.window-app-shell {
|
||||
height: 100%;
|
||||
color: #0f172a;
|
||||
@@ -1238,6 +1333,11 @@ h1 {
|
||||
background: rgba(255, 255, 255, 0.16);
|
||||
}
|
||||
|
||||
.tray-pill-debug.is-active {
|
||||
background: linear-gradient(135deg, rgba(34, 197, 94, 0.34), rgba(59, 130, 246, 0.28));
|
||||
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.18);
|
||||
}
|
||||
|
||||
.tray-menu {
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
@@ -1364,4 +1464,9 @@ h1 {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.desktop-debug-toolbar {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ if (payloadNode) {
|
||||
const accountMenu = document.getElementById('tray-account-menu');
|
||||
const clockNode = document.getElementById('clock');
|
||||
const clockTopNode = document.getElementById('clock-top');
|
||||
const debugToggleNode = document.getElementById('debug-toggle');
|
||||
const debugToggleTopNode = document.getElementById('debug-toggle-top');
|
||||
const windows = new Map();
|
||||
const activeWidgets = new Set(payload.widgets.active_ids);
|
||||
const activeSkin = payload.meta.active_skin;
|
||||
@@ -29,6 +31,8 @@ if (payloadNode) {
|
||||
const storageScope = payload.desktop.storage_scope || 'guest';
|
||||
const storageKey = `desktop.kusche.berlin.window-state.${storageScope}`;
|
||||
const iconStorageKey = `desktop.kusche.berlin.icon-state.${storageScope}.${activeSkin}`;
|
||||
const debugStorageKey = `desktop.kusche.berlin.debug-state.${storageScope}`;
|
||||
const isAdmin = payload.session?.is_admin === true;
|
||||
let zIndex = 20;
|
||||
let topDesktopInset = 0;
|
||||
let sideDesktopInset = 0;
|
||||
@@ -38,6 +42,92 @@ if (payloadNode) {
|
||||
const windowControlsMode = document.body.dataset.windowControls || 'windows';
|
||||
let wallpaperImagePromise = null;
|
||||
|
||||
const loadDebugState = () => {
|
||||
try {
|
||||
const raw = window.localStorage.getItem(debugStorageKey);
|
||||
const parsed = raw ? JSON.parse(raw) : null;
|
||||
|
||||
return {
|
||||
open: parsed?.open === true,
|
||||
};
|
||||
} catch {
|
||||
return { open: false };
|
||||
}
|
||||
};
|
||||
|
||||
const debugState = loadDebugState();
|
||||
|
||||
const saveDebugState = () => {
|
||||
try {
|
||||
window.localStorage.setItem(debugStorageKey, JSON.stringify({
|
||||
open: debugState.open === true,
|
||||
}));
|
||||
} catch {
|
||||
// Ignore storage failures for now.
|
||||
}
|
||||
};
|
||||
|
||||
const debugBus = window.__desktopDebugBus || {
|
||||
enabled: isAdmin && debugState.open === true,
|
||||
sequence: 0,
|
||||
entries: [],
|
||||
listener: null,
|
||||
maxEntries: 400,
|
||||
emit(entry) {
|
||||
if (!this.enabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
this.sequence += 1;
|
||||
const payloadEntry = {
|
||||
id: this.sequence,
|
||||
time: new Date().toISOString(),
|
||||
...entry,
|
||||
};
|
||||
|
||||
this.entries.push(payloadEntry);
|
||||
if (this.entries.length > this.maxEntries) {
|
||||
this.entries.shift();
|
||||
}
|
||||
|
||||
if (typeof this.listener === 'function') {
|
||||
this.listener(payloadEntry, this.entries.slice());
|
||||
}
|
||||
|
||||
return payloadEntry;
|
||||
},
|
||||
subscribe(listener) {
|
||||
this.listener = typeof listener === 'function' ? listener : null;
|
||||
if (typeof this.listener === 'function') {
|
||||
this.listener(null, this.entries.slice());
|
||||
}
|
||||
},
|
||||
setEnabled(nextEnabled) {
|
||||
this.enabled = nextEnabled === true;
|
||||
if (!this.enabled) {
|
||||
this.entries = [];
|
||||
} else {
|
||||
this.emit({
|
||||
type: 'debug:enabled',
|
||||
source: 'desktop',
|
||||
message: 'Desktop-Debug aktiviert.',
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof this.listener === 'function') {
|
||||
this.listener(null, this.entries.slice());
|
||||
}
|
||||
},
|
||||
clear() {
|
||||
this.entries = [];
|
||||
if (typeof this.listener === 'function') {
|
||||
this.listener(null, this.entries.slice());
|
||||
}
|
||||
},
|
||||
};
|
||||
window.__desktopDebugBus = debugBus;
|
||||
window.__nexusDebugBus = debugBus;
|
||||
|
||||
const escapeHtml = (value) => String(value)
|
||||
.replaceAll('&', '&')
|
||||
.replaceAll('<', '<')
|
||||
@@ -45,6 +135,89 @@ if (payloadNode) {
|
||||
.replaceAll('"', '"')
|
||||
.replaceAll("'", ''');
|
||||
|
||||
const emitDebug = (entry) => debugBus.emit(entry);
|
||||
|
||||
const updateDebugToggleUi = () => {
|
||||
[debugToggleNode, debugToggleTopNode].forEach((node) => {
|
||||
if (!(node instanceof HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
node.setAttribute('aria-expanded', String(debugState.open === true));
|
||||
node.classList.toggle('is-active', debugState.open === true);
|
||||
node.textContent = debugState.open === true ? 'Debug an' : 'Debug';
|
||||
});
|
||||
};
|
||||
|
||||
const ensureFetchDebugHook = () => {
|
||||
if (window.__desktopDebugFetchHookInstalled === true || typeof window.fetch !== 'function') {
|
||||
return;
|
||||
}
|
||||
|
||||
const originalFetch = window.fetch.bind(window);
|
||||
window.fetch = async (input, init) => {
|
||||
const method = String(init?.method || 'GET').toUpperCase();
|
||||
const url = typeof input === 'string' ? input : (input instanceof Request ? input.url : String(input));
|
||||
const startedAt = performance.now();
|
||||
const requestId = emitDebug({
|
||||
type: 'fetch:start',
|
||||
source: 'desktop',
|
||||
method,
|
||||
url,
|
||||
})?.id ?? null;
|
||||
|
||||
try {
|
||||
const response = await originalFetch(input, init);
|
||||
emitDebug({
|
||||
type: 'fetch:end',
|
||||
source: 'desktop',
|
||||
request_id: requestId,
|
||||
method,
|
||||
url,
|
||||
status: response.status,
|
||||
ok: response.ok,
|
||||
duration_ms: Math.round((performance.now() - startedAt) * 100) / 100,
|
||||
});
|
||||
return response;
|
||||
} catch (error) {
|
||||
emitDebug({
|
||||
type: 'fetch:error',
|
||||
source: 'desktop',
|
||||
request_id: requestId,
|
||||
method,
|
||||
url,
|
||||
duration_ms: Math.round((performance.now() - startedAt) * 100) / 100,
|
||||
message: error instanceof Error ? error.message : 'Fetch fehlgeschlagen',
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
window.__desktopDebugFetchHookInstalled = true;
|
||||
};
|
||||
|
||||
ensureFetchDebugHook();
|
||||
|
||||
window.addEventListener('error', (event) => {
|
||||
emitDebug({
|
||||
type: 'window:error',
|
||||
source: 'desktop',
|
||||
message: event.message || 'Unbekannter Fehler',
|
||||
file: event.filename || null,
|
||||
line: event.lineno || null,
|
||||
column: event.colno || null,
|
||||
});
|
||||
});
|
||||
|
||||
window.addEventListener('unhandledrejection', (event) => {
|
||||
const reason = event.reason;
|
||||
emitDebug({
|
||||
type: 'window:unhandledrejection',
|
||||
source: 'desktop',
|
||||
message: reason instanceof Error ? reason.message : String(reason || 'Promise wurde verworfen'),
|
||||
});
|
||||
});
|
||||
|
||||
const buildAppIconMarkup = (app, className = '') => {
|
||||
const safeClassName = className ? ` ${className}` : '';
|
||||
|
||||
@@ -292,6 +465,22 @@ if (payloadNode) {
|
||||
};
|
||||
|
||||
const buildWindowBody = (definition) => {
|
||||
if (definition.content_mode === 'desktop-debug') {
|
||||
return `
|
||||
<div class="window-content window-content--desktop-debug">
|
||||
<div class="desktop-debug-window" data-debug-window="1">
|
||||
<div class="desktop-debug-toolbar">
|
||||
<p class="desktop-debug-copy">Live-Debug fuer Desktop und Native-Module. Tracking laeuft nur, solange dieses Fenster offen ist.</p>
|
||||
<div class="desktop-debug-actions">
|
||||
<button class="window-app-button desktop-debug-action" type="button" data-debug-action="clear">Log leeren</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="desktop-debug-log" data-debug-log></div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
if (definition.content_mode === 'native-module' && definition.app_id === 'mining-checker') {
|
||||
return `
|
||||
<div class="window-content window-content--embedded window-content--module">
|
||||
@@ -660,6 +849,132 @@ if (payloadNode) {
|
||||
setAccountMenuOpen(false);
|
||||
};
|
||||
|
||||
const renderDebugEntries = (container, entries) => {
|
||||
if (!(container instanceof HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Array.isArray(entries) || entries.length === 0) {
|
||||
container.innerHTML = '<div class="desktop-debug-empty">Noch keine Debug-Eintraege.</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = entries.slice().reverse().map((entry) => {
|
||||
const meta = [];
|
||||
if (entry.source) {
|
||||
meta.push(String(entry.source));
|
||||
}
|
||||
if (entry.method) {
|
||||
meta.push(String(entry.method));
|
||||
}
|
||||
if (entry.status !== undefined && entry.status !== null) {
|
||||
meta.push(`HTTP ${entry.status}`);
|
||||
}
|
||||
if (entry.duration_ms !== undefined && entry.duration_ms !== null) {
|
||||
meta.push(`${entry.duration_ms} ms`);
|
||||
}
|
||||
|
||||
const reservedKeys = new Set([
|
||||
'id', 'time', 'type', 'source', 'message', 'method', 'url', 'status',
|
||||
'ok', 'duration_ms', 'request_id', 'server_time', 'file', 'line', 'column',
|
||||
]);
|
||||
const context = Object.fromEntries(
|
||||
Object.entries(entry).filter(([key]) => !reservedKeys.has(key))
|
||||
);
|
||||
const contextText = Object.keys(context).length > 0
|
||||
? escapeHtml(JSON.stringify(context, null, 2))
|
||||
: '';
|
||||
|
||||
return `
|
||||
<article class="desktop-debug-entry">
|
||||
<div class="desktop-debug-entry-head">
|
||||
<strong>${escapeHtml(entry.type || 'debug')}</strong>
|
||||
<span>${escapeHtml(new Date(entry.time || Date.now()).toLocaleTimeString('de-DE'))}</span>
|
||||
</div>
|
||||
${meta.length ? `<p class="desktop-debug-entry-meta">${escapeHtml(meta.join(' · '))}</p>` : ''}
|
||||
${entry.url ? `<p class="desktop-debug-entry-url">${escapeHtml(String(entry.url))}</p>` : ''}
|
||||
${entry.message ? `<p class="desktop-debug-entry-message">${escapeHtml(String(entry.message))}</p>` : ''}
|
||||
${contextText ? `<pre class="desktop-debug-entry-context">${contextText}</pre>` : ''}
|
||||
</article>
|
||||
`;
|
||||
}).join('');
|
||||
container.scrollTop = 0;
|
||||
};
|
||||
|
||||
const initializeDesktopDebugWindow = (record) => {
|
||||
const host = record.node.querySelector('[data-debug-window="1"]');
|
||||
const logNode = record.node.querySelector('[data-debug-log]');
|
||||
const clearButton = record.node.querySelector('[data-debug-action="clear"]');
|
||||
|
||||
if (!(host instanceof HTMLElement) || !(logNode instanceof HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
renderDebugEntries(logNode, debugBus.entries);
|
||||
debugBus.subscribe((_entry, entries) => {
|
||||
renderDebugEntries(logNode, entries);
|
||||
});
|
||||
|
||||
if (clearButton instanceof HTMLButtonElement) {
|
||||
clearButton.addEventListener('click', () => {
|
||||
debugBus.clear();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const findDebugWindow = () => Array.from(windows.values()).find((record) => record.definition.app_id === 'desktop-debug') || null;
|
||||
|
||||
const closeDebugWindow = () => {
|
||||
const existing = findDebugWindow();
|
||||
if (existing) {
|
||||
closeWindow(existing.id);
|
||||
} else {
|
||||
debugState.open = false;
|
||||
saveDebugState();
|
||||
debugBus.setEnabled(false);
|
||||
updateDebugToggleUi();
|
||||
}
|
||||
};
|
||||
|
||||
const openDebugWindow = () => {
|
||||
if (!isAdmin) {
|
||||
return;
|
||||
}
|
||||
|
||||
const existing = findDebugWindow();
|
||||
debugState.open = true;
|
||||
saveDebugState();
|
||||
debugBus.setEnabled(true);
|
||||
updateDebugToggleUi();
|
||||
|
||||
if (existing) {
|
||||
focusWindow(existing.id);
|
||||
return;
|
||||
}
|
||||
|
||||
createWindow({
|
||||
window_id: `window-desktop-debug-${Date.now()}`,
|
||||
app_id: 'desktop-debug',
|
||||
title: 'Desktop Debug',
|
||||
x: 120,
|
||||
y: 88,
|
||||
width: 720,
|
||||
height: 520,
|
||||
content: '',
|
||||
entry_route: '',
|
||||
content_mode: 'desktop-debug',
|
||||
});
|
||||
};
|
||||
|
||||
const toggleDebugWindow = () => {
|
||||
if (debugState.open === true) {
|
||||
closeDebugWindow();
|
||||
return;
|
||||
}
|
||||
|
||||
openDebugWindow();
|
||||
};
|
||||
|
||||
const applyWindowLayout = (record) => {
|
||||
const { x, y, width, height } = record.layout;
|
||||
record.node.style.left = `${x}px`;
|
||||
@@ -736,6 +1051,13 @@ if (payloadNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (record.definition.app_id === 'desktop-debug') {
|
||||
debugState.open = false;
|
||||
saveDebugState();
|
||||
debugBus.setEnabled(false);
|
||||
updateDebugToggleUi();
|
||||
}
|
||||
|
||||
windows.delete(windowId);
|
||||
record.node.remove();
|
||||
syncTaskbar();
|
||||
@@ -1052,6 +1374,10 @@ if (payloadNode) {
|
||||
}
|
||||
}
|
||||
|
||||
if (definition.content_mode === 'desktop-debug') {
|
||||
initializeDesktopDebugWindow(record);
|
||||
}
|
||||
|
||||
node.querySelectorAll('.window-action').forEach((action) => {
|
||||
action.addEventListener('click', (event) => {
|
||||
event.stopPropagation();
|
||||
@@ -1328,6 +1654,14 @@ if (payloadNode) {
|
||||
setAccountMenuOpen(accountMenu.hidden);
|
||||
});
|
||||
|
||||
debugToggleNode?.addEventListener('click', () => {
|
||||
toggleDebugWindow();
|
||||
});
|
||||
|
||||
debugToggleTopNode?.addEventListener('click', () => {
|
||||
toggleDebugWindow();
|
||||
});
|
||||
|
||||
accountMenu?.addEventListener('click', (event) => {
|
||||
const target = event.target instanceof Element ? event.target.closest('[data-account-action]') : null;
|
||||
|
||||
@@ -1426,9 +1760,13 @@ if (payloadNode) {
|
||||
|
||||
renderWidgets();
|
||||
renderStartMenu();
|
||||
updateDebugToggleUi();
|
||||
setStartMenuOpen(false);
|
||||
measureDesktopBounds();
|
||||
applyStoredIconLayout();
|
||||
if (isAdmin && debugState.open === true) {
|
||||
openDebugWindow();
|
||||
}
|
||||
payload.windows.forEach(createWindow);
|
||||
renderClock();
|
||||
window.setInterval(renderClock, 1000);
|
||||
|
||||
Reference in New Issue
Block a user