mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-03-15 19:07:23 +00:00
106 lines
3.3 KiB
TypeScript
106 lines
3.3 KiB
TypeScript
import { useEffect, useRef } from 'react';
|
|
import { useTheme } from '../../contexts/ThemeContext';
|
|
import type { Project, ProjectSession } from '../../types/app';
|
|
|
|
type PluginTabContentProps = {
|
|
pluginName: string;
|
|
selectedProject: Project | null;
|
|
selectedSession: ProjectSession | null;
|
|
};
|
|
|
|
export default function PluginTabContent({
|
|
pluginName,
|
|
selectedProject,
|
|
selectedSession,
|
|
}: PluginTabContentProps) {
|
|
const iframeRef = useRef<HTMLIFrameElement>(null);
|
|
const { isDarkMode } = useTheme();
|
|
|
|
const iframeSrc = `/api/plugins/${encodeURIComponent(pluginName)}/assets/index.html`;
|
|
|
|
// Send context to iframe when it loads or when context changes.
|
|
// Use '*' as targetOrigin because the sandbox (without allow-same-origin) gives the
|
|
// iframe an opaque origin that cannot be matched with a specific origin string.
|
|
useEffect(() => {
|
|
const iframe = iframeRef.current;
|
|
if (!iframe) return;
|
|
|
|
const sendContext = () => {
|
|
iframe.contentWindow?.postMessage(
|
|
{
|
|
type: 'ccui:context',
|
|
theme: isDarkMode ? 'dark' : 'light',
|
|
project: selectedProject
|
|
? { name: selectedProject.name, path: selectedProject.fullPath || selectedProject.path }
|
|
: null,
|
|
session: selectedSession
|
|
? { id: selectedSession.id, title: selectedSession.title }
|
|
: null,
|
|
},
|
|
'*',
|
|
);
|
|
};
|
|
|
|
iframe.addEventListener('load', sendContext);
|
|
|
|
// Also send when context changes (iframe already loaded)
|
|
if (iframe.contentWindow) {
|
|
sendContext();
|
|
}
|
|
|
|
return () => {
|
|
iframe.removeEventListener('load', sendContext);
|
|
};
|
|
}, [isDarkMode, selectedProject, selectedSession]);
|
|
|
|
// Listen for messages from plugin iframe.
|
|
// We verify by event.source rather than event.origin because the sandboxed iframe
|
|
// (without allow-same-origin) has an opaque origin that shows up as "null".
|
|
useEffect(() => {
|
|
const handleMessage = (event: MessageEvent) => {
|
|
// Only accept messages originating from our plugin iframe
|
|
if (event.source !== iframeRef.current?.contentWindow) return;
|
|
if (!event.data || typeof event.data !== 'object') return;
|
|
|
|
const { type } = event.data;
|
|
|
|
switch (type) {
|
|
case 'ccui:request-context': {
|
|
// Plugin is requesting current context
|
|
iframeRef.current?.contentWindow?.postMessage(
|
|
{
|
|
type: 'ccui:context',
|
|
theme: isDarkMode ? 'dark' : 'light',
|
|
project: selectedProject
|
|
? { name: selectedProject.name, path: selectedProject.fullPath || selectedProject.path }
|
|
: null,
|
|
session: selectedSession
|
|
? { id: selectedSession.id, title: selectedSession.title }
|
|
: null,
|
|
},
|
|
'*',
|
|
);
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
};
|
|
|
|
window.addEventListener('message', handleMessage);
|
|
return () => window.removeEventListener('message', handleMessage);
|
|
}, [isDarkMode, selectedProject, selectedSession]);
|
|
|
|
return (
|
|
<div className="h-full w-full overflow-hidden">
|
|
<iframe
|
|
ref={iframeRef}
|
|
src={iframeSrc}
|
|
title={`Plugin: ${pluginName}`}
|
|
className="w-full h-full border-0"
|
|
sandbox="allow-scripts allow-forms allow-popups"
|
|
/>
|
|
</div>
|
|
);
|
|
}
|