fix: sync context panel iframe themes
Keeps embedded sessions aligned with parent theme Prevents iframe reloads on theme changes Supports theme hotkeys from focused iframes
This commit is contained in:
@@ -26,6 +26,7 @@ import { getRuntimeApiBaseUrl } from '@/lib/runtime-switch';
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
import { OpenChamberLogo } from "@/components/ui/OpenChamberLogo";
|
||||
import { invokeDesktopCommand } from '@/lib/desktopNative';
|
||||
import { getOrCreateEmbeddedSessionChatURL, type EmbeddedSessionChatURLCacheEntry } from './contextPanelEmbeddedChat';
|
||||
import {
|
||||
type PreviewElementMetadata,
|
||||
isPreviewElementMetadata,
|
||||
@@ -401,29 +402,6 @@ const runIframeScript = async <T,>(iframe: HTMLIFrameElement, script: string): P
|
||||
};
|
||||
|
||||
|
||||
const buildEmbeddedSessionChatURL = (sessionID: string, directory: string | null, readOnly: boolean): string => {
|
||||
if (typeof window === 'undefined') {
|
||||
return '';
|
||||
}
|
||||
|
||||
const url = new URL(window.location.pathname, window.location.origin);
|
||||
url.searchParams.set('ocPanel', 'session-chat');
|
||||
url.searchParams.set('sessionId', sessionID);
|
||||
if (readOnly) {
|
||||
url.searchParams.set('readOnly', '1');
|
||||
} else {
|
||||
url.searchParams.delete('readOnly');
|
||||
}
|
||||
if (directory && directory.trim().length > 0) {
|
||||
url.searchParams.set('directory', directory);
|
||||
} else {
|
||||
url.searchParams.delete('directory');
|
||||
}
|
||||
|
||||
url.hash = '';
|
||||
return url.toString();
|
||||
};
|
||||
|
||||
const truncateTabLabel = (value: string, maxChars: number): string => {
|
||||
if (value.length <= maxChars) {
|
||||
return value;
|
||||
@@ -2004,7 +1982,7 @@ export const ContextPanel: React.FC = () => {
|
||||
const reorderContextPanelTabs = useUIStore((state) => state.reorderContextPanelTabs);
|
||||
const setSelectedFilePath = useFilesViewTabsStore((state) => state.setSelectedPath);
|
||||
const openContextPreview = useUIStore((state) => state.openContextPreview);
|
||||
const { themeMode, lightThemeId, darkThemeId, currentTheme } = useThemeSystem();
|
||||
const { themeMode, setThemeMode, lightThemeId, darkThemeId, currentTheme } = useThemeSystem();
|
||||
|
||||
const tabs = React.useMemo(() => panelState?.tabs ?? [], [panelState?.tabs]);
|
||||
const activeTab = tabs.find((tab) => tab.id === panelState?.activeTabId) ?? tabs[tabs.length - 1] ?? null;
|
||||
@@ -2020,6 +1998,7 @@ export const ContextPanel: React.FC = () => {
|
||||
const activeResizePointerIDRef = React.useRef<number | null>(null);
|
||||
const panelRef = React.useRef<HTMLElement | null>(null);
|
||||
const chatFrameRefs = React.useRef<Map<string, HTMLIFrameElement>>(new Map());
|
||||
const chatFrameSrcByTabIDRef = React.useRef<Map<string, EmbeddedSessionChatURLCacheEntry>>(new Map());
|
||||
const wasOpenRef = React.useRef(false);
|
||||
const previousIsOpenRef = React.useRef(isOpen);
|
||||
const suppressWidthTransitionFrameRef = React.useRef<number | null>(null);
|
||||
@@ -2179,6 +2158,24 @@ export const ContextPanel: React.FC = () => {
|
||||
|
||||
const activeChatTabID = activeTab?.mode === 'chat' ? activeTab.id : null;
|
||||
|
||||
const getEmbeddedChatSrc = React.useCallback((tabID: string, sessionID: string, readOnly: boolean): string => {
|
||||
return getOrCreateEmbeddedSessionChatURL(chatFrameSrcByTabIDRef.current, tabID, sessionID, directoryKey || null, readOnly, {
|
||||
mode: themeMode,
|
||||
lightThemeId,
|
||||
darkThemeId,
|
||||
currentTheme,
|
||||
});
|
||||
}, [currentTheme, darkThemeId, directoryKey, lightThemeId, themeMode]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const liveTabIDs = new Set(tabs.map((tab) => tab.id));
|
||||
for (const tabID of chatFrameSrcByTabIDRef.current.keys()) {
|
||||
if (!liveTabIDs.has(tabID)) {
|
||||
chatFrameSrcByTabIDRef.current.delete(tabID);
|
||||
}
|
||||
}
|
||||
}, [tabs]);
|
||||
|
||||
const handleDiffScopeChange = React.useCallback((nextScope: 'working' | 'staged') => {
|
||||
if (!directoryKey || activeTab?.mode !== 'diff') {
|
||||
return;
|
||||
@@ -2267,6 +2264,37 @@ export const ContextPanel: React.FC = () => {
|
||||
}
|
||||
}, [activeChatTabID]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (typeof window === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
const handleMessage = (event: MessageEvent) => {
|
||||
if (event.origin !== window.location.origin) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isKnownChatFrame = Array.from(chatFrameRefs.current.values())
|
||||
.some((frame) => frame.contentWindow === event.source);
|
||||
if (!isKnownChatFrame) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = event.data as { type?: unknown };
|
||||
if (data?.type !== 'openchamber:cycle-theme-request') {
|
||||
return;
|
||||
}
|
||||
|
||||
const modes: Array<'light' | 'dark' | 'system'> = ['light', 'dark', 'system'];
|
||||
const currentIndex = modes.indexOf(themeMode);
|
||||
const nextIndex = (currentIndex + 1) % modes.length;
|
||||
setThemeMode(modes[nextIndex]);
|
||||
};
|
||||
|
||||
window.addEventListener('message', handleMessage);
|
||||
return () => window.removeEventListener('message', handleMessage);
|
||||
}, [setThemeMode, themeMode]);
|
||||
|
||||
React.useLayoutEffect(() => {
|
||||
const hasAnyChatTab = tabs.some((tab) => tab.mode === 'chat');
|
||||
if (!hasAnyChatTab) {
|
||||
@@ -2447,7 +2475,7 @@ export const ContextPanel: React.FC = () => {
|
||||
return null;
|
||||
}
|
||||
|
||||
const src = buildEmbeddedSessionChatURL(sessionID, directoryKey || null, tab.readOnly);
|
||||
const src = getEmbeddedChatSrc(tab.id, sessionID, tab.readOnly);
|
||||
if (!src) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user