feat(Plugin): Front-end plugins support `type`, and all plugins of the same type can be automatically rendered by `type`.

This commit is contained in:
haitaoo 2023-06-09 11:44:10 +08:00
parent 92cd3d7b40
commit 10637527f5
8 changed files with 76 additions and 25 deletions

View File

@ -1,35 +1,52 @@
import { FC, ReactNode } from 'react';
import { FC, ReactNode, memo } from 'react';
import builtin from '@/plugins/builtin';
import * as plugins from '@/plugins';
import { Plugin } from '@/utils/pluginKit';
import { Plugin, PluginType } from '@/utils/pluginKit';
/**
* NotePlease set at least either of the `slug_name` and `type` attributes, otherwise no plugins will be rendered.
*
* @field slug_name: The `slug_name` of the plugin needs to be rendered.
* If this property is set, `PluginRender` will use it first (regardless of whether `type` is set)
* to find the corresponding plugin and render it.
* @field type: Used to formulate the rendering of all plugins of this type.
* (if the `slug_name` attribute is set, it will be ignored)
* @field prop: Any attribute you want to configure, e.g. `className`
*/
interface Props {
slug_name: string;
slug_name?: string;
type?: PluginType;
children?: ReactNode;
[prop: string]: any;
}
const findPluginBySlugName: (l, n) => Plugin | null = (source, slug_name) => {
let ret: Plugin | null = null;
const findPlugin: (s, k: 'slug_name' | 'type', v) => Plugin[] = (
source,
k,
v,
) => {
const ret: Plugin[] = [];
if (source) {
Object.keys(source).forEach((k) => {
const p = source[k];
if (p && p.info && p.info.slug_name === slug_name && p.component) {
ret = p;
Object.keys(source).forEach((i) => {
const p = source[i];
if (p && p.component && p.info && p.info[k] === v) {
ret.push(p);
}
});
}
return ret;
};
const Index: FC<Props> = ({ slug_name, children, ...props }) => {
const bp = findPluginBySlugName(builtin, slug_name);
const vp = findPluginBySlugName(plugins, slug_name);
const plugin = bp || vp;
const Index: FC<Props> = ({ slug_name, type, children, ...props }) => {
const fk = slug_name ? 'slug_name' : 'type';
const fv = fk === 'slug_name' ? slug_name : type;
const bp = findPlugin(builtin, fk, fv);
const vp = findPlugin(plugins, fk, fv);
const pluginSlice = [...bp, ...vp];
if (!plugin) {
if (!pluginSlice.length) {
return null;
}
/**
@ -37,9 +54,19 @@ const Index: FC<Props> = ({ slug_name, children, ...props }) => {
* ps: Logic such as version compatibility determination can be placed here
*/
const PluginComponent = plugin.component;
// @ts-ignore
return <PluginComponent {...props}>{children}</PluginComponent>;
return (
<>
{pluginSlice.map((ps) => {
const PluginFC = ps.component;
return (
// @ts-ignore
<PluginFC key={ps.info.slug_name} {...props}>
{children}
</PluginFC>
);
})}
</>
);
};
export default Index;
export default memo(Index);

View File

@ -175,15 +175,12 @@ const Index: React.FC = () => {
return (
<Container style={{ paddingTop: '4rem', paddingBottom: '5rem' }}>
<WelcomeTitle />
<Col className="mx-auto" md={6} lg={4} xl={3}>
<PluginRender slug_name="ui_plugin_demo" className="mb-5" />
</Col>
{step === 1 ? (
<Col className="mx-auto" md={6} lg={4} xl={3}>
{ucAgentInfo ? (
<PluginRender slug_name="uc_login" className="mb-5" />
) : (
<PluginRender slug_name="connector" className="mb-5" />
<PluginRender type="Connector" className="mb-5" />
)}
{canOriginalLogin ? (
<>

View File

@ -26,7 +26,7 @@ const Index: React.FC = () => {
{showForm ? (
<Col className="mx-auto" md={6} lg={4} xl={3}>
<PluginRender slug_name="connector" className="mb-5" />
<PluginRender type="Connector" className="mb-5" />
<SignUpForm callback={onStep} />
</Col>
) : (

View File

@ -0,0 +1,20 @@
package demo
import "github.com/answerdev/answer/plugin"
type DemoPlugin struct {
}
func init() {
plugin.Register(&DemoPlugin{})
}
func (d DemoPlugin) Info() plugin.Info {
return plugin.Info{
Name: plugin.MakeTranslator("i18n.demo.name"),
SlugName: "demo_plugin",
Description: plugin.MakeTranslator("i18n.demo.description"),
Author: "answerdev",
Version: "0.0.1",
}
}

View File

@ -13,6 +13,7 @@ import './i18n';
const pluginInfo: PluginInfo = {
slug_name: info.slug_name,
type: info.type,
};
interface Props {
className?: string;

View File

@ -1,4 +1,5 @@
slug_name: connector
type: Connector
version: 0.0.1
link: https://github.com/answerdev/plugins/tree/main/connector/
author: Answer.dev

View File

@ -1,3 +1,3 @@
export default null;
export { default as Demo } from './Demo';
// export { default as Demo } from './Demo';

View File

@ -7,14 +7,19 @@ import i18next from 'i18next';
* It may be used for feature upgrades or version compatibility processing.
*
* @field slug_name: Unique identity string for the plugin, usually configured in `info.yaml`
* @field type: The type of plugin is defined and a single type of plugin can have multiple implementations.
* For example, a plugin of type `Connector` can have a `google` implementation and a `github` implementation.
* `PluginRender` automatically renders the plug-in types already included in `PluginType`.
* @field name: Plugin name, optionally configurable. Usually read from the `i18n` file
* @field description: Plugin description, optionally configurable. Usually read from the `i18n` file
*/
const I18N_NS = 'plugin';
export type PluginType = 'Connector';
export interface PluginInfo {
slug_name: string;
type?: PluginType;
name?: string;
description?: string;
}