- Brain: project_vms.py (Registry /shared/config/project_vms.json pro Projekt) + Endpoints GET/POST/DELETE /projects/<id>/vms + boot/stop (via SSH aria-wohnung aria-vm auf dem Host; Status aus `aria-vm list`). - ARIA-Tools vm_register/vm_list (aufs Request-Projekt) + Seed-Regel: nach dem VM-Bau registrieren, damit sie in Stefans Desktop-Panel auftaucht. - App: brainApi VM-Methoden + ProjectVm-Typ. Neues DesktopTile — pro Projekt die VM-Liste (leer bis registriert), Start/Stop/Verbinden; Verbinden oeffnet noVNC (VncTile mit dem VNC-Port der VM). Deck rendert DesktopTile statt VncTile. py/tsc clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
91 lines
3.0 KiB
TypeScript
91 lines
3.0 KiB
TypeScript
/**
|
|
* WorkspaceDeck — die Workbench: Vollbild-Panels + Taskleisten-Dock unten.
|
|
*
|
|
* Statt einer Zoom-Landkarte: jedes Panel ist bildschirmfuellend und Handy-
|
|
* optimiert, das Dock wechselt per Daumen-Tap sofort. Alle Panels sind IMMER
|
|
* gemountet (nur das aktive ist via display sichtbar) → kein Remount, Chat
|
|
* behaelt RVS/Audio/Queue, WebViews behalten ihren Zustand.
|
|
*
|
|
* Bei offener Tastatur blendet das Dock aus (mehr Platz zum Tippen).
|
|
*/
|
|
|
|
import React, { useEffect, useState } from 'react';
|
|
import { Keyboard, StyleSheet, View } from 'react-native';
|
|
import { TileId } from './layout';
|
|
import { useWorkspaceLayout } from './useWorkspaceLayout';
|
|
import WorkspaceDock from './WorkspaceDock';
|
|
import ChatTile from './tiles/ChatTile';
|
|
import CodeEditorTile from './tiles/CodeEditorTile';
|
|
import DesktopTile from './tiles/DesktopTile';
|
|
|
|
interface Props {
|
|
projectId: string;
|
|
panels: TileId[];
|
|
badges?: Partial<Record<TileId, string>>;
|
|
}
|
|
|
|
const WorkspaceDeck: React.FC<Props> = ({ projectId, panels, badges }) => {
|
|
const [active, setActive] = useState<TileId>('chat');
|
|
const [kbVisible, setKbVisible] = useState(false);
|
|
const { loaded, getFocus, saveFocus } = useWorkspaceLayout(projectId);
|
|
|
|
// Aktives Panel pro Projekt wiederherstellen.
|
|
useEffect(() => {
|
|
if (!loaded) return;
|
|
const stored = getFocus();
|
|
if (stored && panels.includes(stored)) setActive(stored);
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [projectId, loaded]);
|
|
|
|
// Falls das aktive Panel wegfaellt → erstes nehmen.
|
|
useEffect(() => {
|
|
if (!panels.includes(active)) setActive(panels[0] || 'chat');
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [panels.join(',')]);
|
|
|
|
useEffect(() => {
|
|
if (loaded) saveFocus(active);
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [active, loaded]);
|
|
|
|
useEffect(() => {
|
|
const s1 = Keyboard.addListener('keyboardDidShow', () => setKbVisible(true));
|
|
const s2 = Keyboard.addListener('keyboardDidHide', () => setKbVisible(false));
|
|
return () => { s1.remove(); s2.remove(); };
|
|
}, []);
|
|
|
|
const render = (id: TileId) => {
|
|
switch (id) {
|
|
case 'chat': return <ChatTile />;
|
|
case 'editor': return <CodeEditorTile projectId={projectId} />;
|
|
case 'vnc': return <DesktopTile projectId={projectId} focused={active === 'vnc'} />;
|
|
default: return null;
|
|
}
|
|
};
|
|
|
|
return (
|
|
<View style={styles.root}>
|
|
<View style={styles.stack}>
|
|
{panels.map((id) => (
|
|
<View
|
|
key={id}
|
|
style={[StyleSheet.absoluteFill, { display: active === id ? 'flex' : 'none' }]}
|
|
>
|
|
{render(id)}
|
|
</View>
|
|
))}
|
|
</View>
|
|
{!kbVisible && (
|
|
<WorkspaceDock panels={panels} active={active} badges={badges} onSelect={setActive} />
|
|
)}
|
|
</View>
|
|
);
|
|
};
|
|
|
|
const styles = StyleSheet.create({
|
|
root: { flex: 1, backgroundColor: '#0D0D1A' },
|
|
stack: { flex: 1, position: 'relative' },
|
|
});
|
|
|
|
export default WorkspaceDeck;
|