Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d46d8e531 | ||
|
|
b839b059d0 | ||
|
|
9950cdea87 | ||
|
|
7329257830 |
+12
-1
@@ -10,7 +10,18 @@ Alle Änderungen am Projekt. Format: [Keep a Changelog](https://keepachangelog.c
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## [0.2.2.0] — 2026-07-17 — Desktop-Workspace: zoombarer Canvas, Live-Code-Editor, QEMU/VNC
|
## [0.2.1.9] — 2026-07-17 — Kompakt ↔ Cockpit: Umschalter für den Kachel-Desktop
|
||||||
|
|
||||||
|
### Hinzugefügt
|
||||||
|
|
||||||
|
- **Ansichts-Umschalter im Header** („⧉ Kompakt" / „⧉ Cockpit"): Die App startet in **Kompakt** — der klassische Vollbild-Chat, **exakt wie vor dem Umbau** (Default, Mama-tauglich). Ein Tap auf den Button oben rechts schaltet auf **Cockpit** — den zoom-/verschiebbaren Kachel-Desktop. Persistiert über Neustart (`aria_view_mode`).
|
||||||
|
- **Warum:** Nach dem 0.2.1.8-Deploy sah die App „unverändert" aus — korrekt, denn im normalen Chat gibt es nur eine Kachel (= Vollbild-Chat). Der Umschalter macht den Cockpit-Modus jetzt **explizit sichtbar/steuerbar**, statt nur bei Code-Projekten aufzutauchen.
|
||||||
|
- Im Cockpit ist die **Übersicht jetzt immer erreichbar** (auch im Hauptchat): „⤢ Übersicht" sitzt **im Header links** (kollidiert nicht mehr mit dem Abbrechen-Button der „ARIA denkt"-Leiste), dazu 2-Finger-Pinch/Pan und Hardware-Back.
|
||||||
|
- Im Cockpit werden **immer alle vier Kacheln** gezeigt (Chat/Editor/Desktop/Vorschau) — Editor/Desktop/Vorschau als Platzhalter mit Status-Untertitel („kein Code-Projekt" / „kein Desktop"), bis ARIA ein Code-Projekt startet bzw. eine VM läuft. Vorher wirkten sie „verschwunden", weil sie erst bei Code-Projekten auftauchten.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.2.1.8] — 2026-07-17 — Desktop-Workspace: zoombarer Canvas, Live-Code-Editor, QEMU/VNC
|
||||||
|
|
||||||
### Hinzugefügt
|
### Hinzugefügt
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
|
|||||||
|
|
||||||
import WorkspaceScreen from './src/workspace/WorkspaceScreen';
|
import WorkspaceScreen from './src/workspace/WorkspaceScreen';
|
||||||
import SettingsScreen from './src/screens/SettingsScreen';
|
import SettingsScreen from './src/screens/SettingsScreen';
|
||||||
|
import ViewModeToggle from './src/components/ViewModeToggle';
|
||||||
|
import CockpitOverviewButton from './src/components/CockpitOverviewButton';
|
||||||
import rvs from './src/services/rvs';
|
import rvs from './src/services/rvs';
|
||||||
import { initLogger, installGlobalCrashReporter } from './src/services/logger';
|
import { initLogger, installGlobalCrashReporter } from './src/services/logger';
|
||||||
import { acquireBackgroundAudio } from './src/services/backgroundAudio';
|
import { acquireBackgroundAudio } from './src/services/backgroundAudio';
|
||||||
@@ -170,6 +172,8 @@ const App: React.FC = () => {
|
|||||||
options={{
|
options={{
|
||||||
title: 'ARIA Chat',
|
title: 'ARIA Chat',
|
||||||
headerTitle: 'ARIA Cockpit',
|
headerTitle: 'ARIA Cockpit',
|
||||||
|
headerLeft: () => <CockpitOverviewButton />,
|
||||||
|
headerRight: () => <ViewModeToggle />,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Tab.Screen
|
<Tab.Screen
|
||||||
|
|||||||
@@ -79,8 +79,8 @@ android {
|
|||||||
applicationId "com.ariacockpit"
|
applicationId "com.ariacockpit"
|
||||||
minSdkVersion rootProject.ext.minSdkVersion
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
versionCode 20108
|
versionCode 20200
|
||||||
versionName "0.2.1.8"
|
versionName "0.2.2.0"
|
||||||
// Fallback fuer Libraries mit Product Flavors
|
// Fallback fuer Libraries mit Product Flavors
|
||||||
missingDimensionStrategy 'react-native-camera', 'general'
|
missingDimensionStrategy 'react-native-camera', 'general'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "aria-cockpit",
|
"name": "aria-cockpit",
|
||||||
"version": "0.2.1.8",
|
"version": "0.2.2.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"android": "react-native run-android",
|
"android": "react-native run-android",
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
/**
|
||||||
|
* CockpitOverviewButton — Header-Button (links) für „⤢ Übersicht".
|
||||||
|
*
|
||||||
|
* Nur sichtbar im Cockpit-Modus, wenn gerade eine Kachel fokussiert ist. Sitzt
|
||||||
|
* im Navigations-Header, kollidiert also mit nichts im Chat (früher hing er über
|
||||||
|
* dem Abbrechen-Button der „ARIA denkt"-Leiste).
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { StyleSheet, Text, TouchableOpacity } from 'react-native';
|
||||||
|
import cockpitNav from '../services/cockpitNav';
|
||||||
|
import viewMode, { ViewModeValue } from '../services/viewMode';
|
||||||
|
|
||||||
|
const CockpitOverviewButton: React.FC = () => {
|
||||||
|
const [mode, setMode] = useState<ViewModeValue>(viewMode.get());
|
||||||
|
const [focused, setFocused] = useState<boolean>(cockpitNav.isFocused());
|
||||||
|
|
||||||
|
useEffect(() => viewMode.subscribe(setMode), []);
|
||||||
|
useEffect(() => cockpitNav.subscribe(setFocused), []);
|
||||||
|
|
||||||
|
if (mode !== 'cockpit' || !focused) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={() => cockpitNav.requestOverview()}
|
||||||
|
style={styles.pill}
|
||||||
|
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
|
||||||
|
activeOpacity={0.75}
|
||||||
|
>
|
||||||
|
<Text style={styles.text}>⤢ Übersicht</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
pill: {
|
||||||
|
marginLeft: 12,
|
||||||
|
paddingHorizontal: 12,
|
||||||
|
paddingVertical: 6,
|
||||||
|
borderRadius: 16,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: '#0096FF',
|
||||||
|
backgroundColor: '#0A1F33',
|
||||||
|
},
|
||||||
|
text: { color: '#0096FF', fontSize: 13, fontWeight: '700' },
|
||||||
|
});
|
||||||
|
|
||||||
|
export default CockpitOverviewButton;
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
/**
|
||||||
|
* ViewModeToggle — kleiner Header-Button zum Umschalten zwischen Kompakt-
|
||||||
|
* Ansicht (klassischer Chat) und Cockpit (Kachel-Desktop).
|
||||||
|
*
|
||||||
|
* Sitzt rechts im Navigations-Header ("ARIA Cockpit"), kollidiert also mit
|
||||||
|
* nichts in der Chat-Ansicht. Zeigt das Ziel des naechsten Taps.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { StyleSheet, Text, TouchableOpacity } from 'react-native';
|
||||||
|
import viewMode, { ViewModeValue } from '../services/viewMode';
|
||||||
|
|
||||||
|
const ViewModeToggle: React.FC = () => {
|
||||||
|
const [mode, setMode] = useState<ViewModeValue>(viewMode.get());
|
||||||
|
useEffect(() => viewMode.subscribe(setMode), []);
|
||||||
|
|
||||||
|
const isCockpit = mode === 'cockpit';
|
||||||
|
return (
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={() => viewMode.toggle()}
|
||||||
|
style={[styles.pill, isCockpit && styles.pillActive]}
|
||||||
|
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
|
||||||
|
activeOpacity={0.75}
|
||||||
|
>
|
||||||
|
<Text style={[styles.text, isCockpit && styles.textActive]}>
|
||||||
|
{isCockpit ? '⧉ Cockpit' : '⧉ Kompakt'}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
pill: {
|
||||||
|
marginRight: 12,
|
||||||
|
paddingHorizontal: 12,
|
||||||
|
paddingVertical: 6,
|
||||||
|
borderRadius: 16,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: '#1E1E2E',
|
||||||
|
backgroundColor: '#12122A',
|
||||||
|
},
|
||||||
|
pillActive: { borderColor: '#0096FF', backgroundColor: '#0A1F33' },
|
||||||
|
text: { color: '#9090B0', fontSize: 13, fontWeight: '700' },
|
||||||
|
textActive: { color: '#0096FF' },
|
||||||
|
});
|
||||||
|
|
||||||
|
export default ViewModeToggle;
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
* cockpitNav — kleine Brücke zwischen WorkspaceCanvas (Fokus-Zustand) und dem
|
||||||
|
* Navigations-Header, damit der „⤢ Übersicht"-Button im Header sitzen kann
|
||||||
|
* (kollidiert dort mit nichts im Chat) statt im überfüllten Chat-Inhalt.
|
||||||
|
*
|
||||||
|
* Der Canvas meldet, ob gerade eine Kachel fokussiert ist, und registriert eine
|
||||||
|
* Aktion zum Zurück-zur-Übersicht. Der Header abonniert den Zustand und ruft die
|
||||||
|
* Aktion. Muster wie services/rvs.ts (Singleton).
|
||||||
|
*/
|
||||||
|
|
||||||
|
type Sub = (focused: boolean) => void;
|
||||||
|
|
||||||
|
class CockpitNav {
|
||||||
|
private focused = false;
|
||||||
|
private overviewHandler: (() => void) | null = null;
|
||||||
|
private subs: Sub[] = [];
|
||||||
|
|
||||||
|
isFocused(): boolean { return this.focused; }
|
||||||
|
|
||||||
|
setFocused(f: boolean): void {
|
||||||
|
if (this.focused === f) return;
|
||||||
|
this.focused = f;
|
||||||
|
this.subs.forEach((cb) => cb(f));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Canvas registriert seine „zur Übersicht"-Aktion (null beim Unmount). */
|
||||||
|
registerOverview(fn: (() => void) | null): void {
|
||||||
|
this.overviewHandler = fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
requestOverview(): void {
|
||||||
|
this.overviewHandler?.();
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribe(cb: Sub): () => void {
|
||||||
|
this.subs.push(cb);
|
||||||
|
cb(this.focused);
|
||||||
|
return () => { this.subs = this.subs.filter((s) => s !== cb); };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const cockpitNav = new CockpitNav();
|
||||||
|
export default cockpitNav;
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
/**
|
||||||
|
* viewMode — App-Ansicht: 'compact' (klassischer Vollbild-Chat wie vor 0.2.2.0)
|
||||||
|
* oder 'cockpit' (zoombarer Kachel-Desktop).
|
||||||
|
*
|
||||||
|
* Default 'compact' → fuer normale Nutzung aendert sich nichts (Mama-tauglich).
|
||||||
|
* Umschaltbar ueber den Header-Button; persistiert in AsyncStorage. Muster wie
|
||||||
|
* services/rvs.ts (Singleton mit Listener-Liste).
|
||||||
|
*/
|
||||||
|
|
||||||
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||||
|
|
||||||
|
export type ViewModeValue = 'compact' | 'cockpit';
|
||||||
|
|
||||||
|
const KEY = 'aria_view_mode';
|
||||||
|
type Sub = (mode: ViewModeValue) => void;
|
||||||
|
|
||||||
|
class ViewMode {
|
||||||
|
private mode: ViewModeValue = 'compact';
|
||||||
|
private subs: Sub[] = [];
|
||||||
|
private loaded = false;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
AsyncStorage.getItem(KEY).then((v) => {
|
||||||
|
if (v === 'cockpit' || v === 'compact') this.mode = v;
|
||||||
|
this.loaded = true;
|
||||||
|
this.emit();
|
||||||
|
}).catch(() => { this.loaded = true; });
|
||||||
|
}
|
||||||
|
|
||||||
|
get(): ViewModeValue { return this.mode; }
|
||||||
|
isLoaded(): boolean { return this.loaded; }
|
||||||
|
|
||||||
|
set(mode: ViewModeValue): void {
|
||||||
|
if (this.mode === mode) return;
|
||||||
|
this.mode = mode;
|
||||||
|
AsyncStorage.setItem(KEY, mode).catch(() => {});
|
||||||
|
this.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle(): void {
|
||||||
|
this.set(this.mode === 'compact' ? 'cockpit' : 'compact');
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribe(cb: Sub): () => void {
|
||||||
|
this.subs.push(cb);
|
||||||
|
cb(this.mode);
|
||||||
|
return () => { this.subs = this.subs.filter((s) => s !== cb); };
|
||||||
|
}
|
||||||
|
|
||||||
|
private emit(): void {
|
||||||
|
const m = this.mode;
|
||||||
|
this.subs.forEach((cb) => cb(m));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const viewMode = new ViewMode();
|
||||||
|
export default viewMode;
|
||||||
@@ -17,13 +17,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useEffect, useMemo, useState } from 'react';
|
import React, { useEffect, useMemo, useState } from 'react';
|
||||||
import { BackHandler, StyleSheet, Text, TouchableOpacity, useWindowDimensions, View } from 'react-native';
|
import { BackHandler, StyleSheet, Text, useWindowDimensions, View } from 'react-native';
|
||||||
import Animated, { useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated';
|
import Animated, { useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated';
|
||||||
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
|
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
boundsOf, Camera, focusCamera, overviewCamera, TileId, TILE_RECTS, toLocal,
|
boundsOf, Camera, focusCamera, overviewCamera, TileId, TILE_RECTS, toLocal,
|
||||||
} from './layout';
|
} from './layout';
|
||||||
|
import cockpitNav from '../services/cockpitNav';
|
||||||
import Tile from './Tile';
|
import Tile from './Tile';
|
||||||
import { useWorkspaceLayout } from './useWorkspaceLayout';
|
import { useWorkspaceLayout } from './useWorkspaceLayout';
|
||||||
import ChatTile from './tiles/ChatTile';
|
import ChatTile from './tiles/ChatTile';
|
||||||
@@ -49,7 +50,6 @@ const WorkspaceCanvas: React.FC<Props> = ({ projectId, visibleTiles, subtitles }
|
|||||||
const bounds = useMemo(() => boundsOf(visibleTiles), [visibleKey]);
|
const bounds = useMemo(() => boundsOf(visibleTiles), [visibleKey]);
|
||||||
const worldW = bounds.w;
|
const worldW = bounds.w;
|
||||||
const worldH = bounds.h;
|
const worldH = bounds.h;
|
||||||
const single = visibleTiles.length <= 1;
|
|
||||||
const { loaded: layoutLoaded, getFocus, saveFocus } = useWorkspaceLayout(projectId);
|
const { loaded: layoutLoaded, getFocus, saveFocus } = useWorkspaceLayout(projectId);
|
||||||
|
|
||||||
// Kamera (shared values fuer 60fps auf dem UI-Thread).
|
// Kamera (shared values fuer 60fps auf dem UI-Thread).
|
||||||
@@ -60,21 +60,17 @@ const WorkspaceCanvas: React.FC<Props> = ({ projectId, visibleTiles, subtitles }
|
|||||||
const savedTx = useSharedValue(0);
|
const savedTx = useSharedValue(0);
|
||||||
const savedTy = useSharedValue(0);
|
const savedTy = useSharedValue(0);
|
||||||
|
|
||||||
// Bei nur einer Kachel: immer fokussiert. Verschwindet die fokussierte
|
// Verschwindet die fokussierte Kachel aus der Sichtbarkeit → auf Chat zurueck.
|
||||||
// Kachel aus der Sichtbarkeit, auf Chat zurueckfallen.
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (single) {
|
if (focusedTileId && !visibleTiles.includes(focusedTileId)) {
|
||||||
setFocusedTileId(visibleTiles[0] || 'chat');
|
|
||||||
} else if (focusedTileId && !visibleTiles.includes(focusedTileId)) {
|
|
||||||
setFocusedTileId('chat');
|
setFocusedTileId('chat');
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [visibleKey]);
|
}, [visibleKey]);
|
||||||
|
|
||||||
// Zuletzt fokussierte Kachel pro Projekt wiederherstellen (nur Mehr-Kachel-
|
// Zuletzt fokussierte Kachel pro Projekt wiederherstellen.
|
||||||
// Projekte; laeuft NACH dem single-Effekt oben, gewinnt also fuer Code-Projekte).
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!layoutLoaded || single) return;
|
if (!layoutLoaded) return;
|
||||||
const stored = getFocus();
|
const stored = getFocus();
|
||||||
if (stored === undefined) return;
|
if (stored === undefined) return;
|
||||||
if (stored === null || visibleTiles.includes(stored)) setFocusedTileId(stored);
|
if (stored === null || visibleTiles.includes(stored)) setFocusedTileId(stored);
|
||||||
@@ -101,10 +97,18 @@ const WorkspaceCanvas: React.FC<Props> = ({ projectId, visibleTiles, subtitles }
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [focusedTileId, visibleKey, vw, vh]);
|
}, [focusedTileId, visibleKey, vw, vh]);
|
||||||
|
|
||||||
// Hardware-Back: im Fokus (und mehr als eine Kachel) → zurueck zur Uebersicht.
|
// Fokus-Zustand + „zur Uebersicht"-Aktion an den Header (cockpitNav) melden,
|
||||||
|
// damit der Uebersicht-Button dort sitzt (kollidiert nicht mit dem Chat).
|
||||||
|
useEffect(() => {
|
||||||
|
cockpitNav.setFocused(focusedTileId != null);
|
||||||
|
cockpitNav.registerOverview(() => setFocusedTileId(null));
|
||||||
|
return () => { cockpitNav.registerOverview(null); cockpitNav.setFocused(false); };
|
||||||
|
}, [focusedTileId]);
|
||||||
|
|
||||||
|
// Hardware-Back: im Fokus → zurueck zur Uebersicht.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onBack = () => {
|
const onBack = () => {
|
||||||
if (focusedTileId && !single) {
|
if (focusedTileId) {
|
||||||
setFocusedTileId(null);
|
setFocusedTileId(null);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -112,10 +116,10 @@ const WorkspaceCanvas: React.FC<Props> = ({ projectId, visibleTiles, subtitles }
|
|||||||
};
|
};
|
||||||
const sub = BackHandler.addEventListener('hardwareBackPress', onBack);
|
const sub = BackHandler.addEventListener('hardwareBackPress', onBack);
|
||||||
return () => sub.remove();
|
return () => sub.remove();
|
||||||
}, [focusedTileId, single]);
|
}, [focusedTileId]);
|
||||||
|
|
||||||
// Gesten nur in der Uebersicht (Fokus-Modus: Touches fallen an die Kachel).
|
// Gesten nur in der Uebersicht (Fokus-Modus: Touches fallen an die Kachel).
|
||||||
const gesturesEnabled = !focusedTileId && !single;
|
const gesturesEnabled = !focusedTileId;
|
||||||
const canvasGesture = useMemo(() => {
|
const canvasGesture = useMemo(() => {
|
||||||
const pinch = Gesture.Pinch()
|
const pinch = Gesture.Pinch()
|
||||||
.enabled(gesturesEnabled)
|
.enabled(gesturesEnabled)
|
||||||
@@ -193,12 +197,8 @@ const WorkspaceCanvas: React.FC<Props> = ({ projectId, visibleTiles, subtitles }
|
|||||||
))}
|
))}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Steuerung */}
|
{/* Steuerung — der „⤢ Übersicht"-Button sitzt im Header (cockpitNav),
|
||||||
{focusedTileId && !single && (
|
nicht hier im Chat-Inhalt. */}
|
||||||
<TouchableOpacity style={styles.overviewBtn} onPress={() => setFocusedTileId(null)} activeOpacity={0.8}>
|
|
||||||
<Text style={styles.overviewBtnText}>⤢ Übersicht</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
)}
|
|
||||||
{!focusedTileId && (
|
{!focusedTileId && (
|
||||||
<View style={styles.hintBar} pointerEvents="none">
|
<View style={styles.hintBar} pointerEvents="none">
|
||||||
<Text style={styles.hintText}>Kachel antippen zum Öffnen · 2 Finger: zoomen & schieben</Text>
|
<Text style={styles.hintText}>Kachel antippen zum Öffnen · 2 Finger: zoomen & schieben</Text>
|
||||||
@@ -211,18 +211,6 @@ const WorkspaceCanvas: React.FC<Props> = ({ projectId, visibleTiles, subtitles }
|
|||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
root: { flex: 1, backgroundColor: '#0D0D1A' },
|
root: { flex: 1, backgroundColor: '#0D0D1A' },
|
||||||
world: { position: 'absolute', left: 0, top: 0 },
|
world: { position: 'absolute', left: 0, top: 0 },
|
||||||
overviewBtn: {
|
|
||||||
position: 'absolute',
|
|
||||||
top: 10,
|
|
||||||
right: 12,
|
|
||||||
backgroundColor: 'rgba(18,18,42,0.92)',
|
|
||||||
borderColor: '#1E1E2E',
|
|
||||||
borderWidth: 1,
|
|
||||||
borderRadius: 18,
|
|
||||||
paddingHorizontal: 14,
|
|
||||||
paddingVertical: 8,
|
|
||||||
},
|
|
||||||
overviewBtnText: { color: '#0096FF', fontSize: 14, fontWeight: '700' },
|
|
||||||
hintBar: {
|
hintBar: {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
bottom: 16,
|
bottom: 16,
|
||||||
|
|||||||
@@ -14,14 +14,18 @@ import React, { useEffect, useMemo, useState } from 'react';
|
|||||||
import projectFocus, { FocusSnapshot } from '../services/projectFocus';
|
import projectFocus, { FocusSnapshot } from '../services/projectFocus';
|
||||||
import codeFile from '../services/codeFile';
|
import codeFile from '../services/codeFile';
|
||||||
import desktop from '../services/desktop';
|
import desktop from '../services/desktop';
|
||||||
import { TileId, visibleTilesFor } from './layout';
|
import viewMode, { ViewModeValue } from '../services/viewMode';
|
||||||
|
import ChatScreen from '../screens/ChatScreen';
|
||||||
|
import { TileId, TILE_ORDER } from './layout';
|
||||||
import WorkspaceCanvas from './WorkspaceCanvas';
|
import WorkspaceCanvas from './WorkspaceCanvas';
|
||||||
|
|
||||||
const WorkspaceScreen: React.FC = () => {
|
const WorkspaceScreen: React.FC = () => {
|
||||||
|
const [mode, setMode] = useState<ViewModeValue>(viewMode.get());
|
||||||
const [focus, setFocus] = useState<FocusSnapshot>(projectFocus.get());
|
const [focus, setFocus] = useState<FocusSnapshot>(projectFocus.get());
|
||||||
const [hasCode, setHasCode] = useState(false);
|
const [hasCode, setHasCode] = useState(false);
|
||||||
const [hasDesktop, setHasDesktop] = useState(false);
|
const [hasDesktop, setHasDesktop] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => viewMode.subscribe(setMode), []);
|
||||||
useEffect(() => projectFocus.subscribe(setFocus), []);
|
useEffect(() => projectFocus.subscribe(setFocus), []);
|
||||||
|
|
||||||
const pid = focus.focusedProjectId;
|
const pid = focus.focusedProjectId;
|
||||||
@@ -43,16 +47,28 @@ const WorkspaceScreen: React.FC = () => {
|
|||||||
return unsub;
|
return unsub;
|
||||||
}, [pid, kind]);
|
}, [pid, kind]);
|
||||||
|
|
||||||
const visibleTiles: TileId[] = useMemo(
|
// Im Cockpit immer alle Kacheln zeigen (Editor/Desktop/Vorschau als
|
||||||
() => visibleTilesFor({ kind, hasCode, hasDesktop }),
|
// Platzhalter, bis ein Code-Projekt/VM aktiv ist) — sonst „fehlen" sie
|
||||||
[kind, hasCode, hasDesktop],
|
// gefuehlt. isCode blendet nur die Untertitel-Info um.
|
||||||
);
|
const visibleTiles: TileId[] = TILE_ORDER;
|
||||||
|
const isCode = kind === 'code' || hasCode || hasDesktop;
|
||||||
|
|
||||||
const subtitles = useMemo(
|
const subtitles = useMemo(() => {
|
||||||
() => ({ chat: pid ? projectFocus.getProjectName(pid) : 'Hauptchat' } as Partial<Record<TileId, string>>),
|
const fileCount = codeFile.getFiles(pid).length;
|
||||||
[pid, focus.projectNameById],
|
return {
|
||||||
);
|
chat: pid ? projectFocus.getProjectName(pid) : 'Hauptchat',
|
||||||
|
editor: isCode ? `${fileCount} Datei${fileCount === 1 ? '' : 'en'}` : 'kein Code-Projekt',
|
||||||
|
vnc: hasDesktop ? 'verbunden' : 'kein Desktop',
|
||||||
|
preview: undefined,
|
||||||
|
} as Partial<Record<TileId, string>>;
|
||||||
|
}, [pid, focus.projectNameById, isCode, hasCode, hasDesktop]);
|
||||||
|
|
||||||
|
// Kompakt-Ansicht: klassischer Vollbild-Chat, exakt wie vor dem Umbau.
|
||||||
|
if (mode === 'compact') {
|
||||||
|
return <ChatScreen />;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cockpit: zoombarer Kachel-Desktop.
|
||||||
return <WorkspaceCanvas projectId={pid} visibleTiles={visibleTiles} subtitles={subtitles} />;
|
return <WorkspaceCanvas projectId={pid} visibleTiles={visibleTiles} subtitles={subtitles} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user