@bbki.ng/site 5.8.2 → 5.8.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/package.json +2 -2
- package/src/app/app.tsx +4 -8
- package/src/app/components/BaseLayout.tsx +2 -3
- package/src/app/components/cover/index.tsx +17 -15
- package/src/app/hooks/use_plugin_entries.ts +17 -27
- package/src/core/components/SlotComp.tsx +4 -17
- package/src/core/hooks/index.ts +2 -2
- package/src/core/hooks/useMiddlewareTransData.ts +13 -82
- package/src/core/hooks/useSlotComp.ts +3 -26
- package/src/core/hooks/use_plugins.ts +4 -3
- package/src/core/plugin-system/pluginManager.ts +5 -2
- package/src/core/plugin-system/pluginManifestService.ts +2 -18
- package/src/core/plugin-system/pluginStore.ts +1 -0
- package/src/core/plugin-system/registry.ts +1 -106
- package/src/core/plugin-system/services/systemUIService.ts +31 -126
- package/src/core/shared-service/contract/IUIService.ts +15 -17
- package/src/core/shared-service/factory/createUIService.ts +121 -0
- package/src/core/shared-service/factory/createUIServiceReactKit.tsx +149 -0
- package/src/plugins/blog/components/BlogSlotCom.tsx +4 -17
- package/src/plugins/blog/hooks/useMiddlewareTransData.ts +11 -76
- package/src/plugins/blog/hooks/use_blog_slot_com.ts +2 -24
- package/src/plugins/blog/index.ts +6 -1
- package/src/plugins/blog/services/BlogUIService.ts +10 -103
- package/src/plugins/blog/services/IBlogUIService.ts +1 -16
- package/src/plugins/store/components/storeIcon.tsx +16 -0
- package/src/plugins/store/index.ts +3 -1
- package/src/types/slots.ts +0 -17
- package/tsconfig.json +1 -1
- package/src/utils/index.tsx +0 -20
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @bbki.ng/site
|
|
2
2
|
|
|
3
|
+
## 5.8.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 5efa382: move store entry to nav
|
|
8
|
+
- Updated dependencies [5efa382]
|
|
9
|
+
- @bbki.ng/ui@0.2.20
|
|
10
|
+
|
|
11
|
+
## 5.8.3
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- b74177a: refactor ui service
|
|
16
|
+
|
|
3
17
|
## 5.8.2
|
|
4
18
|
|
|
5
19
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bbki.ng/site",
|
|
3
|
-
"version": "5.8.
|
|
3
|
+
"version": "5.8.4",
|
|
4
4
|
"description": "code behind bbki.ng",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"react-router-dom": "6",
|
|
16
16
|
"sonner": "^2.0.7",
|
|
17
17
|
"swr": "^2.2.5",
|
|
18
|
-
"@bbki.ng/ui": "0.2.
|
|
18
|
+
"@bbki.ng/ui": "0.2.20"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
21
|
"@eslint/compat": "^1.0.0",
|
package/src/app/app.tsx
CHANGED
|
@@ -3,7 +3,6 @@ import { Route, Routes } from 'react-router-dom';
|
|
|
3
3
|
|
|
4
4
|
import { BotRedirect } from '#/app/components/bot';
|
|
5
5
|
import { BBContext } from '#/app/context/bbcontext';
|
|
6
|
-
import { Slot } from '#/core/components/SlotComp';
|
|
7
6
|
import { usePlugins } from '#/core/hooks/use_plugins';
|
|
8
7
|
import { Cover } from '#/app/components/cover';
|
|
9
8
|
import { SWR } from '#/app/swr';
|
|
@@ -20,13 +19,10 @@ const AppRoutes = () => {
|
|
|
20
19
|
<Route path="/" element={<BaseLayout />}>
|
|
21
20
|
<Route index element={<Cover />} />
|
|
22
21
|
<Route path="bot" element={<BotRedirect />} />
|
|
23
|
-
{pluginEntries?.map(route =>
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
element={<Slot name="route" data={route} />}
|
|
28
|
-
/>
|
|
29
|
-
))}
|
|
22
|
+
{pluginEntries?.map(route => {
|
|
23
|
+
const Com = route.pageComponent;
|
|
24
|
+
return <Route key={route.path} path={`${route.path}/*`} element={<Com data={route} />} />;
|
|
25
|
+
})}
|
|
30
26
|
<Route path="*" element={null} />
|
|
31
27
|
</Route>
|
|
32
28
|
</Routes>
|
|
@@ -14,9 +14,7 @@ export const BaseLayout = () => {
|
|
|
14
14
|
|
|
15
15
|
const nav = useNavigate();
|
|
16
16
|
|
|
17
|
-
const defaultLogo = (
|
|
18
|
-
<Logo className="mr-2 cursor-pointer hover:opacity-80" onClick={() => nav('/')} />
|
|
19
|
-
);
|
|
17
|
+
const defaultLogo = <Logo className="cursor-pointer hover:opacity-80" onClick={() => nav('/')} />;
|
|
20
18
|
|
|
21
19
|
return (
|
|
22
20
|
<>
|
|
@@ -31,6 +29,7 @@ export const BaseLayout = () => {
|
|
|
31
29
|
paddingTop: 'calc(var(--safe-top) + 4px)',
|
|
32
30
|
transition: 'all .2s ease-in-out',
|
|
33
31
|
}}
|
|
32
|
+
rightElement={<Slot name="nav-right" data={paths} />}
|
|
34
33
|
/>
|
|
35
34
|
}
|
|
36
35
|
main={
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import React
|
|
1
|
+
import React from 'react';
|
|
2
2
|
import { LinkProps, LinkListProps, LinkList } from '@bbki.ng/ui';
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { usePluginEntries } from '#/app/hooks/use_plugin_entries';
|
|
5
|
+
import { useMiddlewareTransformedData } from '#/core/hooks/useMiddlewareTransData';
|
|
5
6
|
|
|
6
7
|
const CenterLinkList = (props: LinkListProps) => {
|
|
7
8
|
return (
|
|
@@ -11,25 +12,26 @@ const CenterLinkList = (props: LinkListProps) => {
|
|
|
11
12
|
);
|
|
12
13
|
};
|
|
13
14
|
|
|
14
|
-
const baseEntries: Array<LinkProps> = [];
|
|
15
|
-
|
|
16
15
|
export const Cover = (_: { className?: string }) => {
|
|
17
|
-
const
|
|
16
|
+
const pluginEntries = usePluginEntries();
|
|
18
17
|
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
18
|
+
const entries: Array<LinkProps> = React.useMemo(() => {
|
|
19
|
+
return pluginEntries
|
|
20
|
+
.filter(entry => entry.label)
|
|
21
|
+
.map(entry => ({
|
|
22
|
+
to: entry.path,
|
|
23
|
+
children: entry.label,
|
|
24
|
+
})) as Array<LinkProps>;
|
|
25
|
+
}, [pluginEntries]);
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
const transformedEntries = useMiddlewareTransformedData(
|
|
28
|
+
'transformEntryLink',
|
|
29
|
+
entries
|
|
30
|
+
) as Array<LinkProps>;
|
|
29
31
|
|
|
30
32
|
return (
|
|
31
33
|
<>
|
|
32
|
-
<CenterLinkList className="select-none" links={
|
|
34
|
+
<CenterLinkList className="select-none" links={transformedEntries} />
|
|
33
35
|
</>
|
|
34
36
|
);
|
|
35
37
|
};
|
|
@@ -1,39 +1,29 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { type PathRouteProps } from 'react-router-dom';
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
3
2
|
|
|
4
|
-
import {
|
|
3
|
+
import { SystemUIService } from '#/core/plugin-system/services/systemUIService';
|
|
4
|
+
import { IPluginEntry } from '#/types/plugin';
|
|
5
5
|
|
|
6
6
|
const protectedRoutesSet = new Set<string>(['/bot', '/', 'default']);
|
|
7
7
|
|
|
8
|
-
type PluginRoute = Omit<PathRouteProps, 'element'>;
|
|
9
|
-
|
|
10
8
|
export const usePluginEntries = () => {
|
|
11
|
-
const [
|
|
12
|
-
|
|
13
|
-
Promise.resolve([])
|
|
9
|
+
const [entries, setEntries] = useState<Array<IPluginEntry>>(() =>
|
|
10
|
+
SystemUIService.getInstance().getPluginEntries()
|
|
14
11
|
);
|
|
15
12
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const { run } = useMiddlewareRunner<Array<PluginRoute>>({
|
|
21
|
-
hookPoint: 'extendedRoutes',
|
|
22
|
-
onMiddlewareChange,
|
|
23
|
-
});
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
const service = SystemUIService.getInstance();
|
|
15
|
+
setEntries(service.getPluginEntries());
|
|
24
16
|
|
|
25
|
-
|
|
17
|
+
const unsubscribe = service.subscribePluginEntryChange(() => {
|
|
18
|
+
setEntries(service.getPluginEntries());
|
|
19
|
+
});
|
|
26
20
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
21
|
+
return () => {
|
|
22
|
+
unsubscribe();
|
|
23
|
+
};
|
|
24
|
+
}, []);
|
|
30
25
|
|
|
31
|
-
const
|
|
32
|
-
if ('path' in route) {
|
|
33
|
-
return !protectedRoutesSet.has(route.path);
|
|
34
|
-
}
|
|
35
|
-
return false;
|
|
36
|
-
}) as Array<PluginRoute>;
|
|
26
|
+
const safeEntries = entries?.filter(entry => !protectedRoutesSet.has(entry.path));
|
|
37
27
|
|
|
38
|
-
return
|
|
28
|
+
return safeEntries ?? [];
|
|
39
29
|
};
|
|
@@ -1,27 +1,14 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { SystemUIService } from '#/core/plugin-system/services/systemUIService';
|
|
4
|
+
import { createSlotComponent } from '#/core/shared-service/factory/createUIServiceReactKit';
|
|
5
|
+
|
|
4
6
|
import { SystemSlotName } from '../shared-service/contract/IUIService';
|
|
5
7
|
|
|
6
8
|
export interface ISlotProps {
|
|
7
9
|
name: SystemSlotName;
|
|
8
10
|
data?: unknown;
|
|
9
|
-
|
|
10
11
|
placeholder?: React.ReactNode;
|
|
11
12
|
}
|
|
12
13
|
|
|
13
|
-
export const Slot
|
|
14
|
-
const components = useSlotComp(name);
|
|
15
|
-
|
|
16
|
-
if (components.length === 0) {
|
|
17
|
-
return <>{placeholder}</>;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
return (
|
|
21
|
-
<>
|
|
22
|
-
{components.map((Component, index) => (
|
|
23
|
-
<Component key={index} data={data} />
|
|
24
|
-
))}
|
|
25
|
-
</>
|
|
26
|
-
);
|
|
27
|
-
};
|
|
14
|
+
export const Slot = createSlotComponent<SystemSlotName>(SystemUIService.getInstance());
|
package/src/core/hooks/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { useSlotComp } from './useSlotComp';
|
|
2
2
|
export { useMiddlewareRunner } from './useMiddlewareTransData';
|
|
3
3
|
export type {
|
|
4
|
-
UseMiddlewareTransDataOptions,
|
|
5
|
-
UseMiddlewareTransDataResult,
|
|
4
|
+
UseMiddlewareRunnerOptions as UseMiddlewareTransDataOptions,
|
|
5
|
+
UseMiddlewareRunnerResult as UseMiddlewareTransDataResult,
|
|
6
6
|
} from './useMiddlewareTransData';
|
|
@@ -1,82 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
run: (inputData: T) => Promise<T>;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function useMiddlewareRunner<T>({
|
|
18
|
-
hookPoint,
|
|
19
|
-
onMiddlewareChange,
|
|
20
|
-
}: UseMiddlewareTransDataOptions): UseMiddlewareTransDataResult<T> {
|
|
21
|
-
const [loading, setLoading] = useState(false);
|
|
22
|
-
const [error, setError] = useState<Error | null>(null);
|
|
23
|
-
|
|
24
|
-
const run = useCallback(
|
|
25
|
-
async (inputData: T) => {
|
|
26
|
-
setLoading(true);
|
|
27
|
-
setError(null);
|
|
28
|
-
try {
|
|
29
|
-
const result = await SystemUIService.getInstance().runMiddlewares(hookPoint, inputData);
|
|
30
|
-
return result;
|
|
31
|
-
} catch (err) {
|
|
32
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
33
|
-
setError(error);
|
|
34
|
-
throw error;
|
|
35
|
-
} finally {
|
|
36
|
-
setLoading(false);
|
|
37
|
-
}
|
|
38
|
-
},
|
|
39
|
-
[hookPoint]
|
|
40
|
-
);
|
|
41
|
-
|
|
42
|
-
// 仅通知,不执行
|
|
43
|
-
useEffect(() => {
|
|
44
|
-
const unsubscribe = SystemUIService.getInstance().subscribeMiddlewareChange(hook => {
|
|
45
|
-
if (hook === hookPoint) {
|
|
46
|
-
onMiddlewareChange?.();
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
return () => {
|
|
51
|
-
unsubscribe();
|
|
52
|
-
};
|
|
53
|
-
}, [hookPoint, onMiddlewareChange]);
|
|
54
|
-
|
|
55
|
-
return { loading, error, run };
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export const useMiddlewareTransformedData = <T>(
|
|
59
|
-
hookPoint: SystemDataHookPoint,
|
|
60
|
-
defaultValue: T
|
|
61
|
-
) => {
|
|
62
|
-
const [result, setResult] = useState<T>(defaultValue);
|
|
63
|
-
|
|
64
|
-
const runRef = useRef<(input: T) => Promise<T>>(() => Promise.resolve(defaultValue));
|
|
65
|
-
|
|
66
|
-
const onMiddlewareChange = useCallback(() => {
|
|
67
|
-
runRef.current(defaultValue).then(setResult);
|
|
68
|
-
}, [defaultValue]);
|
|
69
|
-
|
|
70
|
-
const { run } = useMiddlewareRunner<T>({
|
|
71
|
-
hookPoint,
|
|
72
|
-
onMiddlewareChange,
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
runRef.current = run;
|
|
76
|
-
|
|
77
|
-
useEffect(() => {
|
|
78
|
-
run(defaultValue).then(setResult);
|
|
79
|
-
}, [defaultValue, run]);
|
|
80
|
-
|
|
81
|
-
return result;
|
|
82
|
-
};
|
|
1
|
+
import {
|
|
2
|
+
createMiddlewareRunnerHook,
|
|
3
|
+
createMiddlewareTransformedDataHook,
|
|
4
|
+
} from '#/core/shared-service/factory/createUIServiceReactKit';
|
|
5
|
+
import { SystemUIService } from '#/core/plugin-system/services/systemUIService';
|
|
6
|
+
|
|
7
|
+
export const useMiddlewareRunner = createMiddlewareRunnerHook(SystemUIService.getInstance());
|
|
8
|
+
export const useMiddlewareTransformedData =
|
|
9
|
+
createMiddlewareTransformedDataHook(useMiddlewareRunner);
|
|
10
|
+
export type {
|
|
11
|
+
UseMiddlewareRunnerOptions,
|
|
12
|
+
UseMiddlewareRunnerResult,
|
|
13
|
+
} from '#/core/shared-service/factory/createUIServiceReactKit';
|
|
@@ -1,27 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { createSlotHook } from '#/core/shared-service/factory/createUIServiceReactKit';
|
|
2
|
+
import { SystemUIService } from '#/core/plugin-system/services/systemUIService';
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import { SystemUIService } from '../plugin-system/services/systemUIService';
|
|
6
|
-
import { SystemSlotName } from '../shared-service/contract/IUIService';
|
|
7
|
-
|
|
8
|
-
export const useSlotComp = (slotName: SystemSlotName) => {
|
|
9
|
-
const [components, setComponents] = useState<React.ComponentType<IComPropsRegisteredToSlot>[]>(
|
|
10
|
-
() => SystemUIService.getInstance().getComponents(slotName)
|
|
11
|
-
);
|
|
12
|
-
|
|
13
|
-
useEffect(() => {
|
|
14
|
-
setComponents(SystemUIService.getInstance().getComponents(slotName));
|
|
15
|
-
|
|
16
|
-
const unsubscribe = SystemUIService.getInstance().subscribeSlotChange(() => {
|
|
17
|
-
const comps = SystemUIService.getInstance().getComponents(slotName);
|
|
18
|
-
setComponents(comps);
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
return () => {
|
|
22
|
-
unsubscribe();
|
|
23
|
-
};
|
|
24
|
-
}, [slotName]);
|
|
25
|
-
|
|
26
|
-
return components;
|
|
27
|
-
};
|
|
4
|
+
export const useSlotComp = createSlotHook(SystemUIService.getInstance());
|
|
@@ -16,14 +16,15 @@ const usePluginsLoading = () => {
|
|
|
16
16
|
}, [isLoading]);
|
|
17
17
|
|
|
18
18
|
useEffect(() => {
|
|
19
|
-
let
|
|
19
|
+
let unregisters = new Map<string, () => void>();
|
|
20
|
+
|
|
20
21
|
const unsubscribe = CoreService.getInstance().subscribePluginLoading(payload => {
|
|
21
|
-
|
|
22
|
+
unregisters.set(payload.id, setGlobalLoading(payload.id, payload.loading));
|
|
22
23
|
});
|
|
23
24
|
|
|
24
25
|
return () => {
|
|
25
26
|
unsubscribe();
|
|
26
|
-
unregister
|
|
27
|
+
unregisters.forEach(unregister => unregister());
|
|
27
28
|
};
|
|
28
29
|
}, [setGlobalLoading]);
|
|
29
30
|
};
|
|
@@ -78,8 +78,7 @@ class PluginManager {
|
|
|
78
78
|
|
|
79
79
|
const perm = p.getMeta().perm || 'guest';
|
|
80
80
|
if (perm === 'admin' && !AdminPluginIDSet.has(pluginId)) {
|
|
81
|
-
|
|
82
|
-
return;
|
|
81
|
+
throw new Error(`Plugin ${pluginId} requires admin permission, but it's not trusted.`);
|
|
83
82
|
}
|
|
84
83
|
|
|
85
84
|
const ctx = this.createHostContext(perm);
|
|
@@ -108,6 +107,10 @@ class PluginManager {
|
|
|
108
107
|
await plugin.onDisable();
|
|
109
108
|
}
|
|
110
109
|
|
|
110
|
+
if (plugin.onDestroy) {
|
|
111
|
+
plugin.onDestroy();
|
|
112
|
+
}
|
|
113
|
+
|
|
111
114
|
this.activePlugins.delete(pluginId);
|
|
112
115
|
|
|
113
116
|
SystemUIService.getInstance().unregisterAllByPluginId(plugin.id);
|
|
@@ -2,7 +2,7 @@ import { cfApiFetcher } from '#/app/utils';
|
|
|
2
2
|
import { FALLBACK_MANIFEST } from '#/core/plugin-system/manifest';
|
|
3
3
|
import { IPluginManifestEntry, PluginID } from '#/types/plugin';
|
|
4
4
|
|
|
5
|
-
const KnownPluginIDSet = new Set<string>(FALLBACK_MANIFEST.map(entry => entry.id));
|
|
5
|
+
// const KnownPluginIDSet = new Set<string>(FALLBACK_MANIFEST.map(entry => entry.id));
|
|
6
6
|
|
|
7
7
|
interface PluginsApiResponse {
|
|
8
8
|
status: string;
|
|
@@ -48,22 +48,6 @@ class PluginManifestService {
|
|
|
48
48
|
if (res.status === 'success' && Array.isArray(res.data)) {
|
|
49
49
|
const validated = res.data
|
|
50
50
|
.map((item): IPluginManifestEntry | null => {
|
|
51
|
-
if (!KnownPluginIDSet.has(item.id)) {
|
|
52
|
-
console.warn(`[PluginManifestService] Unknown plugin id from server: ${item.id}`);
|
|
53
|
-
return null;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
let dependencies: PluginID[] | undefined;
|
|
57
|
-
if (item.dependencies) {
|
|
58
|
-
try {
|
|
59
|
-
dependencies = item.dependencies.filter(p => KnownPluginIDSet.has(p));
|
|
60
|
-
} catch {
|
|
61
|
-
console.warn(
|
|
62
|
-
`[PluginManifestService] Failed to parse dependencies for plugin: ${item.id}`
|
|
63
|
-
);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
51
|
return {
|
|
68
52
|
id: item.id as PluginID,
|
|
69
53
|
name: item.name,
|
|
@@ -71,7 +55,7 @@ class PluginManifestService {
|
|
|
71
55
|
description: item.description,
|
|
72
56
|
perm: (item.perm as 'guest' | 'admin' | undefined) || 'guest',
|
|
73
57
|
icon: item.icon,
|
|
74
|
-
dependencies,
|
|
58
|
+
dependencies: item.dependencies || [],
|
|
75
59
|
};
|
|
76
60
|
})
|
|
77
61
|
.filter((item): item is IPluginManifestEntry => item !== null);
|
|
@@ -12,6 +12,7 @@ export class PluginStore {
|
|
|
12
12
|
this.parse();
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
// TODO: check circular dependency when install plugin, and also check if all dependencies are installed before install a plugin
|
|
15
16
|
private checkDep = (id: PluginID): boolean => {
|
|
16
17
|
const manifest = PluginManifestService.getInstance().getPlugin(id);
|
|
17
18
|
if (!manifest) {
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
|
|
3
|
-
import type {
|
|
4
|
-
|
|
5
|
-
import { createEventBus } from '../utils/eventBus';
|
|
3
|
+
import type { IComPropsRegisteredToSlot } from '#/types/slots';
|
|
6
4
|
|
|
7
5
|
export interface ISlotEntry {
|
|
8
6
|
id: string;
|
|
@@ -17,106 +15,3 @@ export interface IMiddlewareEntry<T> {
|
|
|
17
15
|
pluginId: string;
|
|
18
16
|
weight?: number;
|
|
19
17
|
}
|
|
20
|
-
|
|
21
|
-
type RegistryEvents = {
|
|
22
|
-
'components:changed': void;
|
|
23
|
-
'middleware:changed': HookPoint;
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
export class Registry {
|
|
27
|
-
private slots = new Map<SlotName, ISlotEntry[]>();
|
|
28
|
-
|
|
29
|
-
private bus = createEventBus<RegistryEvents>();
|
|
30
|
-
|
|
31
|
-
private middlewares = new Map<HookPoint, IMiddlewareEntry<unknown>[]>();
|
|
32
|
-
|
|
33
|
-
subscribeMiddleware(hookPoint: HookPoint, listener: () => void) {
|
|
34
|
-
return this.bus.on('middleware:changed', point => {
|
|
35
|
-
if (point === hookPoint) {
|
|
36
|
-
listener();
|
|
37
|
-
}
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
subscribe(listener: () => void) {
|
|
42
|
-
return this.bus.on('components:changed', listener);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
registerComponent(
|
|
46
|
-
slot: SlotName,
|
|
47
|
-
component: React.ComponentType<IComPropsRegisteredToSlot>,
|
|
48
|
-
pluginId: string,
|
|
49
|
-
weight = 0
|
|
50
|
-
) {
|
|
51
|
-
const existing = this.slots.get(slot) || [];
|
|
52
|
-
|
|
53
|
-
const newEntry: ISlotEntry = {
|
|
54
|
-
id: `${pluginId}-${component.name || 'comp'}`,
|
|
55
|
-
pluginId,
|
|
56
|
-
component,
|
|
57
|
-
weight,
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
// 插入并按权重排序(权重大的在前)
|
|
61
|
-
const newList = [...existing, newEntry].sort((a, b) => b.weight - a.weight);
|
|
62
|
-
this.slots.set(slot, newList);
|
|
63
|
-
|
|
64
|
-
this.bus.emit('components:changed', undefined);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
unregisterAllByPluginId(pluginId: string) {
|
|
68
|
-
// 1. 清理 UI 槽位
|
|
69
|
-
this.slots.forEach((entries, slotName) => {
|
|
70
|
-
const filtered = entries.filter(entry => entry.pluginId !== pluginId);
|
|
71
|
-
this.slots.set(slotName, filtered);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
// 2. 清理中间件
|
|
75
|
-
this.middlewares.forEach((entries, point) => {
|
|
76
|
-
const filtered = entries.filter(entry => entry.pluginId !== pluginId);
|
|
77
|
-
this.middlewares.set(point, filtered);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
console.log(`[Registry] All resources for plugin "${pluginId}" have been cleared.`);
|
|
81
|
-
|
|
82
|
-
this.bus.emit('components:changed', undefined);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
registerMiddleware<T>(
|
|
86
|
-
hookPoint: HookPoint,
|
|
87
|
-
fn: (data: T) => Promise<T> | T,
|
|
88
|
-
pluginId: string,
|
|
89
|
-
weight = 0
|
|
90
|
-
) {
|
|
91
|
-
const existing = this.middlewares.get(hookPoint) || [];
|
|
92
|
-
|
|
93
|
-
const newEntry: IMiddlewareEntry<T> = {
|
|
94
|
-
id: `${pluginId}-${fn.name || 'middleware'}`,
|
|
95
|
-
pluginId,
|
|
96
|
-
fn,
|
|
97
|
-
weight,
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
// 插入并按权重排序(权重大的在前)
|
|
101
|
-
const newList = [...existing, newEntry as IMiddlewareEntry<unknown>].sort(
|
|
102
|
-
(a, b) => (b.weight || 0) - (a.weight || 0)
|
|
103
|
-
);
|
|
104
|
-
|
|
105
|
-
this.middlewares.set(hookPoint, newList);
|
|
106
|
-
|
|
107
|
-
this.bus.emit('middleware:changed', hookPoint);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
getComponents(slotName: SlotName): React.ComponentType<IComPropsRegisteredToSlot>[] {
|
|
111
|
-
return (this.slots.get(slotName) || []).map(entry => entry.component);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
async runMiddleware<T>(point: HookPoint, data: T): Promise<T> {
|
|
115
|
-
const fns = (this.middlewares.get(point) || []).map(entry => entry.fn);
|
|
116
|
-
let result = data;
|
|
117
|
-
for (const fn of fns) {
|
|
118
|
-
result = (await fn(result)) as T;
|
|
119
|
-
}
|
|
120
|
-
return result;
|
|
121
|
-
}
|
|
122
|
-
}
|