feat(app): Workbench-Dock statt Zoom-Landkarte + bedienbare VNC-Konsole (0.2.2.0)

Cockpit neu gedacht — das Pinch-Zoom-Landkarten-Konzept war auf 5 Zoll fummelig:
- WorkspaceDeck + WorkspaceDock: Taskleiste unten (Chat · Code · Desktop),
  Ein-Tap-Panelwechsel im Daumenbereich, animierter Indikator, Aktivitaets-
  Badges (Code blau / Desktop gruen), Safe-Area, blendet bei offener Tastatur
  aus. Panels bildschirmfuellend + immer gemountet (kein Remount).
- noVNC bedienbar: novncHtml bekommt Tastatur-Bridge (verstecktes Input-Feld →
  rfb.sendKey inkl. Enter/Backspace/Pfeile), Strg-Alt-Entf, Fit↔1:1. VncTile
  zeigt eine Steuerungs-Leiste (⌨ / Strg+Alt+Entf / ⤢).
- Entfernt: WorkspaceCanvas, Tile, PreviewTile, CockpitOverviewButton,
  cockpitNav (Pinch-Canvas + Uebersichts-Button obsolet). layout.ts auf
  Panel-Metadaten geschlankt. Header nur noch mit Kompakt/Cockpit-Umschalter.

tsc clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-17 21:44:32 +02:00
co-authored by Claude Opus 4.8
parent 7d46d8e531
commit 0110526622
14 changed files with 317 additions and 551 deletions
+90
View File
@@ -0,0 +1,90 @@
/**
* 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 VncTile from './tiles/VncTile';
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 <VncTile 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;