mirror of https://github.com/msgbyte/tailchat
feat: 插件机制与第一个内置插件webpanel
parent
1e7e8318f1
commit
cd125e9c9c
@ -0,0 +1,13 @@
|
||||
/**
|
||||
* 构建一组注册列表的方式
|
||||
* 用于从其他地方统一获取数据
|
||||
*/
|
||||
export function buildRegList<T>(): [T[], (item: T) => void] {
|
||||
const list: T[] = [];
|
||||
|
||||
const reg = (item: T) => {
|
||||
list.push(item);
|
||||
};
|
||||
|
||||
return [list, reg];
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
/**
|
||||
* 构建一组注册Mapping的方式
|
||||
* 用于从其他地方统一获取数据
|
||||
*/
|
||||
export function buildRegMap<T>(): [
|
||||
Record<string, T>,
|
||||
(name: string, item: T) => void
|
||||
] {
|
||||
const mapping: Record<string, T> = {};
|
||||
|
||||
const reg = (name: string, item: T) => {
|
||||
if (mapping[name]) {
|
||||
console.warn('[buildRegMap] 重复注册:', name);
|
||||
}
|
||||
|
||||
mapping[name] = item;
|
||||
};
|
||||
|
||||
return [mapping, reg];
|
||||
}
|
@ -1 +0,0 @@
|
||||
console.log('Hello World!');
|
@ -0,0 +1,24 @@
|
||||
import React from 'react';
|
||||
import { regGroupPanel, useCurrentGroupPanelInfo } from '@capital/common';
|
||||
|
||||
const PLUGIN_NAME = 'com.msgbyte.webpanel';
|
||||
|
||||
const GroupWebPanelRender = () => {
|
||||
const groupPanelInfo = useCurrentGroupPanelInfo();
|
||||
|
||||
if (!groupPanelInfo) {
|
||||
return <div>加载失败, 面板信息不存在</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<iframe className="w-full h-full bg-white" src={groupPanelInfo.meta?.url} />
|
||||
);
|
||||
};
|
||||
|
||||
regGroupPanel({
|
||||
name: `${PLUGIN_NAME}/grouppanel`,
|
||||
label: '网页面板',
|
||||
provider: PLUGIN_NAME,
|
||||
extraFormMeta: [{ type: 'text', name: 'url', label: '网址' }],
|
||||
render: GroupWebPanelRender,
|
||||
});
|
@ -1,6 +1,11 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"baseUrl": "./src"
|
||||
"baseUrl": "./src",
|
||||
"esModuleInterop": true,
|
||||
"jsx": "react",
|
||||
"paths": {
|
||||
"@capital/*": ["../../../src/plugin/*"],
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
import { findPluginPanelInfoByName } from '@/utils/plugin-helper';
|
||||
import { Alert } from 'antd';
|
||||
import React, { useMemo } from 'react';
|
||||
import { isValidStr, useGroupPanel } from 'tailchat-shared';
|
||||
|
||||
interface GroupPluginPanelProps {
|
||||
groupId: string;
|
||||
panelId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 插件群组面板
|
||||
*/
|
||||
export const GroupPluginPanel: React.FC<GroupPluginPanelProps> = React.memo(
|
||||
(props) => {
|
||||
const panelInfo = useGroupPanel(props.groupId, props.panelId);
|
||||
|
||||
if (!panelInfo) {
|
||||
return (
|
||||
<Alert className="w-full text-center" message="无法获取面板信息" />
|
||||
);
|
||||
}
|
||||
|
||||
if (typeof panelInfo.provider !== 'string') {
|
||||
return (
|
||||
<Alert className="w-full text-center" message="未找到插件的提供者" />
|
||||
);
|
||||
}
|
||||
|
||||
// 从已安装插件注册的群组面板中查找对应群组的面板配置
|
||||
const pluginPanelInfo = useMemo(() => {
|
||||
if (!isValidStr(panelInfo.pluginPanelName)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return findPluginPanelInfoByName(panelInfo.pluginPanelName);
|
||||
}, [panelInfo.name]);
|
||||
|
||||
if (!pluginPanelInfo) {
|
||||
// TODO: 如果没有安装, 引导用户安装插件
|
||||
return (
|
||||
<Alert
|
||||
className="w-full text-center"
|
||||
message={`该面板由插件提供, 插件未安装: ${panelInfo.provider}`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const Component = pluginPanelInfo.render;
|
||||
|
||||
if (!Component) {
|
||||
return null;
|
||||
}
|
||||
return <Component />;
|
||||
}
|
||||
);
|
||||
GroupPluginPanel.displayName = 'GroupPluginPanel';
|
@ -0,0 +1,10 @@
|
||||
/**
|
||||
* 同步加载的代码
|
||||
* 用于导出常见的模块依赖
|
||||
*/
|
||||
|
||||
export * from './reg';
|
||||
export {
|
||||
useGroupPanelParams,
|
||||
useCurrentGroupPanelInfo,
|
||||
} from '@/routes/Main/Content/Group/utils';
|
@ -0,0 +1,34 @@
|
||||
import { buildRegList, FastFormFieldMeta } from 'tailchat-shared';
|
||||
|
||||
/**
|
||||
* 注册群组面板
|
||||
*/
|
||||
export interface PluginGroupPanel {
|
||||
/**
|
||||
* 面板唯一标识
|
||||
* @example com.msgbyte.webpanel/grouppanel
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* 面板显示名
|
||||
*/
|
||||
label: string;
|
||||
|
||||
/**
|
||||
* 插件提供者, 用于引导没有安装插件的用户安装插件
|
||||
*/
|
||||
provider: string;
|
||||
|
||||
/**
|
||||
* 额外的表单数据, 用于创建面板时使用
|
||||
*/
|
||||
extraFormMeta: FastFormFieldMeta[];
|
||||
|
||||
/**
|
||||
* 该面板如何渲染
|
||||
*/
|
||||
render: React.ComponentType;
|
||||
}
|
||||
export const [pluginGroupPanel, regGroupPanel] =
|
||||
buildRegList<PluginGroupPanel>();
|
@ -0,0 +1,26 @@
|
||||
import { initMiniStar, regDependency, regSharedModule } from 'mini-star';
|
||||
|
||||
/**
|
||||
* 初始化插件
|
||||
*/
|
||||
export function initPlugins(): Promise<void> {
|
||||
registerDependencies();
|
||||
registerModules();
|
||||
|
||||
return initMiniStar({
|
||||
plugins: [
|
||||
{
|
||||
name: 'com.msgbyte.webpanel',
|
||||
url: '/plugins/com.msgbyte.webpanel/index.js',
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
function registerDependencies() {
|
||||
regDependency('react', () => import('react'));
|
||||
}
|
||||
|
||||
function registerModules() {
|
||||
regSharedModule('@capital/common', () => import('./common/index'));
|
||||
}
|
@ -1,25 +1,30 @@
|
||||
import { GroupPluginPanel } from '@/components/Panel/group/PluginPanel';
|
||||
import { TextPanel } from '@/components/Panel/group/TextPanel';
|
||||
import { Alert } from 'antd';
|
||||
import React from 'react';
|
||||
import { useParams } from 'react-router';
|
||||
import { GroupPanelType, useGroupPanel } from 'tailchat-shared';
|
||||
import { GroupPanelType, t, useGroupPanel } from 'tailchat-shared';
|
||||
import { useGroupPanelParams } from './utils';
|
||||
|
||||
export const GroupPanelRender: React.FC = React.memo(() => {
|
||||
const { groupId, panelId } = useParams<{
|
||||
groupId: string;
|
||||
panelId: string;
|
||||
}>();
|
||||
|
||||
const { groupId, panelId } = useGroupPanelParams();
|
||||
const panelInfo = useGroupPanel(groupId, panelId);
|
||||
|
||||
if (panelInfo === undefined) {
|
||||
if (panelInfo === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (panelInfo.type === GroupPanelType.TEXT) {
|
||||
return <TextPanel groupId={groupId} panelId={panelInfo.id} />;
|
||||
} else if (panelInfo.type === GroupPanelType.PLUGIN) {
|
||||
return <GroupPluginPanel groupId={groupId} panelId={panelInfo.id} />;
|
||||
}
|
||||
|
||||
return <Alert message="未知的面板类型" />;
|
||||
return (
|
||||
<Alert
|
||||
className="w-full text-center"
|
||||
type="error"
|
||||
message={t('未知的面板类型')}
|
||||
/>
|
||||
);
|
||||
});
|
||||
GroupPanelRender.displayName = 'GroupPanelRender';
|
||||
|
@ -0,0 +1,16 @@
|
||||
import React, { useContext } from 'react';
|
||||
|
||||
interface GroupPanelContextValue {
|
||||
groupId: string;
|
||||
panelId: string;
|
||||
}
|
||||
export const GroupPanelContext =
|
||||
React.createContext<GroupPanelContextValue | null>(null);
|
||||
GroupPanelContext.displayName = 'GroupPanelContext';
|
||||
|
||||
/**
|
||||
* 获取群组面板的上下文
|
||||
*/
|
||||
export function useGroupPanelContext(): GroupPanelContextValue | null {
|
||||
return useContext(GroupPanelContext);
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
import { useParams } from 'react-router';
|
||||
import { GroupPanel, useGroupPanel } from 'tailchat-shared';
|
||||
|
||||
/**
|
||||
* 获取群组面板的参数
|
||||
*/
|
||||
export function useGroupPanelParams(): {
|
||||
groupId: string;
|
||||
panelId: string;
|
||||
} {
|
||||
const { groupId, panelId } = useParams<{
|
||||
groupId: string;
|
||||
panelId: string;
|
||||
}>();
|
||||
|
||||
return { groupId, panelId };
|
||||
}
|
||||
|
||||
export function useCurrentGroupPanelInfo(): GroupPanel | null {
|
||||
const { groupId, panelId } = useGroupPanelParams();
|
||||
const panelInfo = useGroupPanel(groupId, panelId);
|
||||
|
||||
return panelInfo;
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
import { pluginGroupPanel, PluginGroupPanel } from '@/plugin/common';
|
||||
|
||||
/**
|
||||
* 查找注册插件提供的群组面板的信息
|
||||
* @param pluginPanelName 插件面板名
|
||||
*/
|
||||
export function findPluginPanelInfoByName(
|
||||
pluginPanelName: string
|
||||
): PluginGroupPanel | undefined {
|
||||
return pluginGroupPanel.find((p) => p.name === pluginPanelName);
|
||||
}
|
Loading…
Reference in New Issue