adsdad
This commit is contained in:
@@ -105,8 +105,10 @@ Aus [README.md](/home/lars/Schreibtisch/Projekte/desktop.kusche.berlin/docs/READ
|
||||
- `partials/desktop/` enthaelt Shell-Templates
|
||||
- `custom/apps/` bleibt Zielort fuer installierbare Fach-Apps
|
||||
- `system/apps/` bleibt Zielort fuer System-Tools
|
||||
- `system/shell/` bleibt Zielort fuer globale Shell-Werkzeuge und shell-nahe Desktop-Helfer
|
||||
- `temp/nexus-module-import/` ist Rohbasis fuer importierte Nexus-Module
|
||||
- Keycloak bleibt das Auth-System
|
||||
- das globale Desktop-Debug ist als Shell-Werkzeug unter `system/shell/desktop-debug/` definiert
|
||||
|
||||
### Module
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ Zentraler Dokumentationsindex fuer das Projekt.
|
||||
- `partials/desktop/` Shell-Template
|
||||
- `custom/apps/` Zielort fuer installierbare Fach-Apps
|
||||
- `system/apps/` Zielort fuer nicht installierbare System-Tools
|
||||
- `system/shell/` Zielort fuer globale Shell-Werkzeuge und shell-nahe Desktop-Helfer
|
||||
- `temp/nexus-module-import/` Rohbasis fuer importierte Nexus-Module
|
||||
|
||||
## Fachliche Hinweise
|
||||
@@ -37,6 +38,7 @@ Zentraler Dokumentationsindex fuer das Projekt.
|
||||
- API und Desktop laufen auf demselben Host. Zielpfad ist `(staging.)desktop.kusche.berlin/api/v1/...`.
|
||||
- der Theme-Handoff ist in [keycloak-theme-handoff.md](/home/lars/Schreibtisch/Projekte/desktop.kusche.berlin/docs/keycloak-theme-handoff.md) beschrieben.
|
||||
- das globale Admin-Debugging ist Teil der Desktop-Shell und wird nicht pro App als eigene Grundfunktion dupliziert
|
||||
- die Definition globaler Shell-Werkzeuge liegt unter `system/shell/`; aktuelles Beispiel ist `system/shell/desktop-debug/`
|
||||
|
||||
## Dokumentationsregel
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ Die Ordnerstruktur soll sich so nah wie sinnvoll an der bestehenden Nexus-Strukt
|
||||
- `/config/` -> Umgebungs-Configs
|
||||
- `/custom/apps/<app>/` -> installierbare Fach-Apps
|
||||
- `/system/apps/<app>/` -> System-Tools und interne App-Quellen
|
||||
- `/system/shell/<tool>/` -> globale Shell-Werkzeuge und shell-nahe Desktop-Helfer
|
||||
- `/partials/structure/` -> globale Layout-Bausteine
|
||||
- `/partials/landingpages/` -> Seiten oder Views
|
||||
- `/tools/` -> Worker, CLI, Hilfstools
|
||||
@@ -54,6 +55,7 @@ Das bedeutet:
|
||||
- Module dürfen nicht ungeordnet in den Desktop-Core verschoben werden
|
||||
- gemeinsame Desktop-Mechaniken liegen nicht in Modulen, sondern im globalen Kern
|
||||
- gemeinsame Debug-Grundlogik liegt ebenfalls nicht in Modulen, sondern im globalen Desktop-Core
|
||||
- globale Shell-Werkzeuge sollen als eigene Einheiten unter `/system/shell/` abgelegt werden
|
||||
|
||||
## Trennung
|
||||
|
||||
|
||||
@@ -58,9 +58,11 @@ Verbindlich ist:
|
||||
|
||||
- `custom/apps/<app>/` bleibt Ort fuer installierbare Fach-Apps
|
||||
- `system/apps/<app>/` bleibt Ort fuer System-Tools und nicht installierbare App-Quellen
|
||||
- `system/shell/<tool>/` bleibt Ort fuer globale Shell-Werkzeuge und shell-nahe Desktop-Helfer
|
||||
- globale Desktop-Mechaniken liegen im gemeinsamen Kern
|
||||
- globale Modul-Helfer duerfen im gemeinsamen Kern liegen, Fachlogik aber nicht
|
||||
- globale Debug-Steuerung fuer Admins liegt im Desktop-Core; Aktivierung erfolgt ueber das gemeinsame Debug-Widget neben der Uhr
|
||||
- die Definition globaler Debug- und Shell-Werkzeuge soll in `system/shell/` liegen und nicht lose in `public/` oder verstreut im JS
|
||||
- API und Desktop teilen sich im aktuellen Zielbild denselben Host; offizielle Basis ist `/api/v1/...`
|
||||
- App, API und Widget einer Fachfunktion muessen dieselben Sperr- und Force-Regeln fuer externe Abrufe nutzen
|
||||
- die Trennung `core` / `system_tool` / `module` ist in Metadaten, Startmenue und Benutzer-Setup konsistent zu halten
|
||||
|
||||
@@ -7,6 +7,8 @@ $activeSkin = $desktopPayload['meta']['active_skin'];
|
||||
$baseSkinProfile = $desktopPayload['meta']['base_skin_profile'];
|
||||
$skinProfile = $desktopPayload['meta']['skin_profile'];
|
||||
$wallpaper = $desktopPayload['desktop']['wallpaper'];
|
||||
$debugShellApp = is_array($desktopPayload['shell']['debug_app'] ?? null) ? $desktopPayload['shell']['debug_app'] : null;
|
||||
$debugShellLabel = trim((string) ($debugShellApp['label'] ?? 'Debug')) ?: 'Debug';
|
||||
|
||||
$assetVersion = static function (string $publicPath): string {
|
||||
$filesystemPath = dirname(__DIR__, 2) . '/public' . $publicPath;
|
||||
@@ -88,7 +90,7 @@ $renderStartIcon = static function (array $profile): string {
|
||||
</div>
|
||||
<div class="system-bar-right">
|
||||
<?php if (($desktopPayload['session']['is_admin'] ?? false) === true): ?>
|
||||
<button class="tray-pill tray-pill-button tray-pill-debug" id="debug-toggle-top" type="button" aria-expanded="false">Debug</button>
|
||||
<button class="tray-pill tray-pill-button tray-pill-debug" id="debug-toggle-top" type="button" aria-expanded="false"><?= htmlspecialchars($debugShellLabel, ENT_QUOTES) ?></button>
|
||||
<?php endif; ?>
|
||||
<time id="clock-top"></time>
|
||||
</div>
|
||||
@@ -166,7 +168,7 @@ $renderStartIcon = static function (array $profile): string {
|
||||
><?= htmlspecialchars($trayItem['label'], ENT_QUOTES) ?></button>
|
||||
<?php endforeach; ?>
|
||||
<?php if (($desktopPayload['session']['is_admin'] ?? false) === true): ?>
|
||||
<button class="tray-pill tray-pill-button tray-pill-debug" id="debug-toggle" type="button" aria-expanded="false">Debug</button>
|
||||
<button class="tray-pill tray-pill-button tray-pill-debug" id="debug-toggle" type="button" aria-expanded="false"><?= htmlspecialchars($debugShellLabel, ENT_QUOTES) ?></button>
|
||||
<?php endif; ?>
|
||||
<time id="clock"></time>
|
||||
</div>
|
||||
|
||||
@@ -49,6 +49,13 @@ if (payloadNode) {
|
||||
const activeSkin = payload.meta.active_skin;
|
||||
const skinProfile = payload.meta.skin_profile || {};
|
||||
const settingsApi = payload.desktop.settings_api || '';
|
||||
const shellApps = Array.isArray(payload.shell?.apps) ? payload.shell.apps : [];
|
||||
const debugShellApp = (payload.shell && typeof payload.shell.debug_app === 'object' && payload.shell.debug_app)
|
||||
|| shellApps.find((app) => app && app.app_id === 'desktop-debug')
|
||||
|| null;
|
||||
const debugShellAppId = typeof debugShellApp?.app_id === 'string' && debugShellApp.app_id !== ''
|
||||
? debugShellApp.app_id
|
||||
: 'desktop-debug';
|
||||
let currentUserPreferences = payload.desktop.user_preferences || {};
|
||||
const maximizeOverSystemBar = Boolean(skinProfile.maximize_over_system_bar);
|
||||
const storageScope = payload.desktop.storage_scope || 'guest';
|
||||
@@ -168,7 +175,9 @@ if (payloadNode) {
|
||||
|
||||
node.setAttribute('aria-expanded', String(debugState.open === true));
|
||||
node.classList.toggle('is-active', debugState.open === true);
|
||||
node.textContent = debugState.open === true ? 'Debug an' : 'Debug';
|
||||
node.textContent = debugState.open === true
|
||||
? `${String(debugShellApp?.label || 'Debug')} an`
|
||||
: String(debugShellApp?.label || 'Debug');
|
||||
});
|
||||
};
|
||||
|
||||
@@ -571,13 +580,15 @@ if (payloadNode) {
|
||||
|
||||
const buildWindowBody = (definition) => {
|
||||
if (definition.content_mode === 'desktop-debug') {
|
||||
const debugCopy = escapeHtml(definition.ui?.copy || 'Live-Debug fuer Desktop und Native-Module. Tracking laeuft nur, solange dieses Fenster offen ist.');
|
||||
const clearLabel = escapeHtml(definition.ui?.clear_label || 'Log leeren');
|
||||
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>
|
||||
<p class="desktop-debug-copy">${debugCopy}</p>
|
||||
<div class="desktop-debug-actions">
|
||||
<button class="window-app-button desktop-debug-action" type="button" data-debug-action="clear">Log leeren</button>
|
||||
<button class="window-app-button desktop-debug-action" type="button" data-debug-action="clear">${clearLabel}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="desktop-debug-log" data-debug-log></div>
|
||||
@@ -1020,7 +1031,7 @@ if (payloadNode) {
|
||||
}
|
||||
};
|
||||
|
||||
const findDebugWindow = () => Array.from(windows.values()).find((record) => record.definition.app_id === 'desktop-debug') || null;
|
||||
const findDebugWindow = () => Array.from(windows.values()).find((record) => record.definition.app_id === debugShellAppId) || null;
|
||||
|
||||
const closeDebugWindow = () => {
|
||||
const existing = findDebugWindow();
|
||||
@@ -1050,17 +1061,19 @@ if (payloadNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
const windowDefaults = debugShellApp && typeof debugShellApp.window === 'object' ? debugShellApp.window : {};
|
||||
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',
|
||||
window_id: `window-${debugShellAppId}-${Date.now()}`,
|
||||
app_id: debugShellAppId,
|
||||
title: String(debugShellApp?.title || 'Desktop Debug'),
|
||||
x: Number(windowDefaults.x ?? 120),
|
||||
y: Number(windowDefaults.y ?? 88),
|
||||
width: Number(windowDefaults.width ?? 720),
|
||||
height: Number(windowDefaults.height ?? 520),
|
||||
content: String(debugShellApp?.content || ''),
|
||||
entry_route: String(debugShellApp?.entry_route || ''),
|
||||
content_mode: String(debugShellApp?.content_mode || 'desktop-debug'),
|
||||
ui: debugShellApp && typeof debugShellApp.ui === 'object' ? debugShellApp.ui : {},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1153,7 +1166,7 @@ if (payloadNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (record.definition.app_id === 'desktop-debug') {
|
||||
if (record.definition.app_id === debugShellAppId) {
|
||||
debugState.open = false;
|
||||
saveDebugState();
|
||||
debugBus.setEnabled(false);
|
||||
|
||||
@@ -8,6 +8,7 @@ use Desktop\AppRegistry;
|
||||
use Desktop\AppIconResolver;
|
||||
use Desktop\AppVisibility;
|
||||
use Desktop\DesktopState;
|
||||
use Desktop\ShellAppRegistry;
|
||||
use Desktop\SkinResolver;
|
||||
use Desktop\WidgetRegistry;
|
||||
use Desktop\WindowManager;
|
||||
@@ -30,6 +31,7 @@ final class App
|
||||
$moduleRegistry = new ModuleRegistry(AppPaths::customAppsRoot($this->projectRoot));
|
||||
$registry = new AppRegistry($this->projectRoot . '/config/apps.php', $moduleRegistry->desktopApps());
|
||||
$widgetRegistry = new WidgetRegistry($this->projectRoot . '/config/widgets.php', $moduleRegistry->widgets());
|
||||
$shellRegistry = new ShellAppRegistry(AppPaths::systemShellRoot($this->projectRoot));
|
||||
$skins = SkinResolver::all();
|
||||
$isAuthenticated = isset($_SESSION['desktop_auth']) && is_array($_SESSION['desktop_auth']);
|
||||
$authUser = $isAuthenticated && is_array($_SESSION['desktop_auth']['user'] ?? null)
|
||||
@@ -113,6 +115,10 @@ final class App
|
||||
'active_ids' => $activeWidgetIds,
|
||||
],
|
||||
'tray' => $trayItems,
|
||||
'shell' => [
|
||||
'apps' => $shellRegistry->all(),
|
||||
'debug_app' => $shellRegistry->find('desktop-debug'),
|
||||
],
|
||||
'session' => [
|
||||
'display_name' => $displayName,
|
||||
'authenticated' => $isAuthenticated,
|
||||
|
||||
@@ -25,4 +25,14 @@ final class AppPaths
|
||||
{
|
||||
return self::systemAppsRoot($projectRoot) . '/' . trim($appName, '/');
|
||||
}
|
||||
|
||||
public static function systemShellRoot(string $projectRoot): string
|
||||
{
|
||||
return rtrim($projectRoot, '/') . '/system/shell';
|
||||
}
|
||||
|
||||
public static function systemShellPath(string $projectRoot, string $shellName): string
|
||||
{
|
||||
return self::systemShellRoot($projectRoot) . '/' . trim($shellName, '/');
|
||||
}
|
||||
}
|
||||
|
||||
82
src/Desktop/ShellAppRegistry.php
Normal file
82
src/Desktop/ShellAppRegistry.php
Normal file
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Desktop;
|
||||
|
||||
final class ShellAppRegistry
|
||||
{
|
||||
/**
|
||||
* @var array<int, array<string, mixed>>|null
|
||||
*/
|
||||
private ?array $apps = null;
|
||||
|
||||
public function __construct(
|
||||
private readonly string $shellRoot,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
public function all(): array
|
||||
{
|
||||
if ($this->apps !== null) {
|
||||
return $this->apps;
|
||||
}
|
||||
|
||||
if (!is_dir($this->shellRoot)) {
|
||||
return $this->apps = [];
|
||||
}
|
||||
|
||||
$apps = [];
|
||||
foreach (glob($this->shellRoot . '/*/definition.php') ?: [] as $definitionPath) {
|
||||
$app = require $definitionPath;
|
||||
|
||||
if (!is_array($app)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$appId = trim((string) ($app['app_id'] ?? ''));
|
||||
if ($appId === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$apps[] = [
|
||||
'app_id' => $appId,
|
||||
'title' => (string) ($app['title'] ?? $appId),
|
||||
'label' => (string) ($app['label'] ?? $appId),
|
||||
'content_mode' => (string) ($app['content_mode'] ?? 'shell'),
|
||||
'admin_only' => (bool) ($app['admin_only'] ?? false),
|
||||
'entry_route' => (string) ($app['entry_route'] ?? ''),
|
||||
'content' => (string) ($app['content'] ?? ''),
|
||||
'window' => is_array($app['window'] ?? null) ? $app['window'] : [],
|
||||
'ui' => is_array($app['ui'] ?? null) ? $app['ui'] : [],
|
||||
];
|
||||
}
|
||||
|
||||
usort(
|
||||
$apps,
|
||||
static fn (array $left, array $right): int => strcmp(
|
||||
(string) ($left['title'] ?? $left['app_id'] ?? ''),
|
||||
(string) ($right['title'] ?? $right['app_id'] ?? '')
|
||||
)
|
||||
);
|
||||
|
||||
return $this->apps = array_values($apps);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>|null
|
||||
*/
|
||||
public function find(string $appId): ?array
|
||||
{
|
||||
foreach ($this->all() as $app) {
|
||||
if ((string) ($app['app_id'] ?? '') === $appId) {
|
||||
return $app;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -20,3 +20,8 @@ Aktuelle Beispiele:
|
||||
- `user-management`
|
||||
- `user-self-management`
|
||||
- `cron-management`
|
||||
|
||||
Verwandte, aber getrennte Struktur:
|
||||
|
||||
- globale Shell-Werkzeuge und shell-nahe Desktop-Helfer liegen unter `system/shell/`
|
||||
- Beispiel: `system/shell/desktop-debug/`
|
||||
|
||||
24
system/shell/README.md
Normal file
24
system/shell/README.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# System Shell
|
||||
|
||||
Shell-nahe Werkzeuge und Shell-Skripte liegen in diesem Projekt unter `system/shell/<tool>/`.
|
||||
|
||||
Ziel der Struktur:
|
||||
|
||||
- globale Desktop-Werkzeuge klar von `system/apps/` und `custom/apps/` trennen
|
||||
- Shell-spezifische Helfer als eigenstaendige Einheiten sichtbar machen
|
||||
- Metadaten fuer den Desktop zentral ueber `definition.php` ablegen
|
||||
|
||||
Typische Bestandteile eines Shell-Tools:
|
||||
|
||||
- `definition.php` fuer Titel, Kennung, Content-Mode und Fenster-Defaults
|
||||
- optionale Zusatzdateien wie `README.md`, Shell-Skripte oder Hilfsdateien
|
||||
|
||||
Aktuelles Beispiel:
|
||||
|
||||
- `desktop-debug`
|
||||
|
||||
Regel:
|
||||
|
||||
- `system/shell/` ist fuer globale Shell-Werkzeuge reserviert
|
||||
- installierbare Fach-Apps bleiben unter `custom/apps/`
|
||||
- nicht installierbare Oberflaechen-Apps bleiben unter `system/apps/`
|
||||
21
system/shell/desktop-debug/definition.php
Normal file
21
system/shell/desktop-debug/definition.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
'app_id' => 'desktop-debug',
|
||||
'title' => 'Desktop Debug',
|
||||
'label' => 'Debug',
|
||||
'content_mode' => 'desktop-debug',
|
||||
'admin_only' => true,
|
||||
'window' => [
|
||||
'x' => 120,
|
||||
'y' => 88,
|
||||
'width' => 720,
|
||||
'height' => 520,
|
||||
],
|
||||
'ui' => [
|
||||
'copy' => 'Live-Debug fuer Desktop und Native-Module. Tracking laeuft nur, solange dieses Fenster offen ist.',
|
||||
'clear_label' => 'Log leeren',
|
||||
],
|
||||
];
|
||||
Reference in New Issue
Block a user