mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-03-16 11:27:24 +00:00
Feat: initial plugin system (Frontend only)
This commit is contained in:
105
src/components/plugins/PluginTabContent.tsx
Normal file
105
src/components/plugins/PluginTabContent.tsx
Normal file
@@ -0,0 +1,105 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user