Files
ARIA-AGENT/android/src/workspace/WorkspaceDeck.tsx
T
duffyduckandClaude Opus 4.8 5890c17ec0 feat(app): Dateien-Panel im Dock (zwischen Chat und Code)
Neues 📁-Panel listet ALLE Projektdateien (/shared/projects/<id>/) — auch
erzeugte Bilder, nicht nur Code; dieselben, die in der Projektliste als 📄
gezaehlt werden. Bild antippen → Vollbild-Vorschau; Textdatei → Text-Vorschau.
- Brain: /projects/<id>/file?binary=1 → Base64 + MIME (fuer Bilder, max 8 MB).
- App: brainApi.readProjectFileBinary; layout 'files'-Tile; Dock-Reihenfolge
  Chat · Dateien · Code · Desktop; FilesTile mit Bild-/Text-Modal.

py/tsc clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 00:09:34 +02:00

93 lines
3.1 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 FilesTile from './tiles/FilesTile';
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 'files': return <FilesTile projectId={projectId} focused={active === 'files'} />;
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;