feat(app): Workspace-Umbau Schritt 8 — Live-Reveal + Layout-Persistenz
- Progressive Reveal: ChatScreen spiegelt bei project_changed den Projekt-Typ sofort in projectFocus (kind_changed nach set_project_kind) → Editor/Desktop- Kacheln erscheinen live, nicht erst beim Reconnect. - useWorkspaceLayout: merkt pro Projekt die zuletzt fokussierte Kachel (AsyncStorage aria_workspace_layout) und stellt sie beim Zurueckkehren in ein Code-Projekt wieder her. tsc clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -987,6 +987,11 @@ const ChatScreen: React.FC = () => {
|
||||
if (p.id && p.name) {
|
||||
setProjectNameById(prev => ({ ...prev, [p.id]: p.name }));
|
||||
}
|
||||
// Projekt-Typ sofort spiegeln → Editor/Desktop-Kacheln erscheinen live
|
||||
// (z.B. nach set_project_kind), nicht erst beim naechsten Reconnect.
|
||||
if (p.id && (p.kind === 'code' || p.kind === 'chat')) {
|
||||
projectFocus.setKind(p.id, p.kind);
|
||||
}
|
||||
if (action === 'entered' || action === 'created') {
|
||||
if (p.id) setFocusedProjectId(p.id);
|
||||
} else if (action === 'exited') {
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
boundsOf, Camera, focusCamera, overviewCamera, TileId, TILE_RECTS, toLocal,
|
||||
} from './layout';
|
||||
import Tile from './Tile';
|
||||
import { useWorkspaceLayout } from './useWorkspaceLayout';
|
||||
import ChatTile from './tiles/ChatTile';
|
||||
import CodeEditorTile from './tiles/CodeEditorTile';
|
||||
import VncTile from './tiles/VncTile';
|
||||
@@ -49,6 +50,7 @@ const WorkspaceCanvas: React.FC<Props> = ({ projectId, visibleTiles, subtitles }
|
||||
const worldW = bounds.w;
|
||||
const worldH = bounds.h;
|
||||
const single = visibleTiles.length <= 1;
|
||||
const { loaded: layoutLoaded, getFocus, saveFocus } = useWorkspaceLayout(projectId);
|
||||
|
||||
// Kamera (shared values fuer 60fps auf dem UI-Thread).
|
||||
const scale = useSharedValue(1);
|
||||
@@ -69,6 +71,22 @@ const WorkspaceCanvas: React.FC<Props> = ({ projectId, visibleTiles, subtitles }
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [visibleKey]);
|
||||
|
||||
// Zuletzt fokussierte Kachel pro Projekt wiederherstellen (nur Mehr-Kachel-
|
||||
// Projekte; laeuft NACH dem single-Effekt oben, gewinnt also fuer Code-Projekte).
|
||||
useEffect(() => {
|
||||
if (!layoutLoaded || single) return;
|
||||
const stored = getFocus();
|
||||
if (stored === undefined) return;
|
||||
if (stored === null || visibleTiles.includes(stored)) setFocusedTileId(stored);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [projectId, layoutLoaded, visibleKey]);
|
||||
|
||||
// Fokus-Wahl persistieren.
|
||||
useEffect(() => {
|
||||
if (layoutLoaded) saveFocus(focusedTileId);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [focusedTileId, layoutLoaded]);
|
||||
|
||||
// Kamera auf das Ziel fahren (Fokus-Rect oder Uebersicht).
|
||||
useEffect(() => {
|
||||
const cam: Camera = focusedTileId
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* useWorkspaceLayout — merkt sich pro Projekt die zuletzt fokussierte Kachel,
|
||||
* damit man beim Zurueckkehren in ein Code-Projekt wieder dort landet (Editor/
|
||||
* Desktop) statt immer im Chat. Persistiert nach AsyncStorage (Muster wie
|
||||
* aria_project_drafts).
|
||||
*/
|
||||
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { TileId } from './layout';
|
||||
|
||||
const KEY = 'aria_workspace_layout';
|
||||
|
||||
interface Entry { focus: TileId | null }
|
||||
type LayoutMap = Record<string, Entry>;
|
||||
|
||||
const keyOf = (projectId: string) => projectId || '__main__';
|
||||
|
||||
export function useWorkspaceLayout(projectId: string) {
|
||||
const mapRef = useRef<LayoutMap>({});
|
||||
const [loaded, setLoaded] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
AsyncStorage.getItem(KEY).then((v) => {
|
||||
if (v) { try { mapRef.current = JSON.parse(v) || {}; } catch { /* ignore */ } }
|
||||
setLoaded(true);
|
||||
}).catch(() => setLoaded(true));
|
||||
}, []);
|
||||
|
||||
const getFocus = useCallback((): TileId | null | undefined => {
|
||||
return mapRef.current[keyOf(projectId)]?.focus;
|
||||
}, [projectId]);
|
||||
|
||||
const saveFocus = useCallback((focus: TileId | null) => {
|
||||
mapRef.current = { ...mapRef.current, [keyOf(projectId)]: { focus } };
|
||||
AsyncStorage.setItem(KEY, JSON.stringify(mapRef.current)).catch(() => {});
|
||||
}, [projectId]);
|
||||
|
||||
return { loaded, getFocus, saveFocus };
|
||||
}
|
||||
Reference in New Issue
Block a user