@dxos/app-framework 0.3.3-main.0c081e6
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/.eslintrc.js +9 -0
- package/.storybook/main.cjs +36 -0
- package/.storybook/preview-head.html +3 -0
- package/.storybook/preview.cjs +45 -0
- package/LICENSE +8 -0
- package/README.md +29 -0
- package/README.yml +5 -0
- package/dist/lib/browser/index.mjs +453 -0
- package/dist/lib/browser/index.mjs.map +7 -0
- package/dist/lib/browser/meta.json +1 -0
- package/dist/lib/node/index.cjs +522 -0
- package/dist/lib/node/index.cjs.map +7 -0
- package/dist/lib/node/meta.json +1 -0
- package/dist/types/src/App.d.ts +19 -0
- package/dist/types/src/App.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +3 -0
- package/dist/types/src/index.d.ts.map +1 -0
- package/dist/types/src/plugins/IntentPlugin/IntentContext.d.ts +8 -0
- package/dist/types/src/plugins/IntentPlugin/IntentContext.d.ts.map +1 -0
- package/dist/types/src/plugins/IntentPlugin/IntentPlugin.d.ts +19 -0
- package/dist/types/src/plugins/IntentPlugin/IntentPlugin.d.ts.map +1 -0
- package/dist/types/src/plugins/IntentPlugin/index.d.ts +4 -0
- package/dist/types/src/plugins/IntentPlugin/index.d.ts.map +1 -0
- package/dist/types/src/plugins/IntentPlugin/intent.d.ts +28 -0
- package/dist/types/src/plugins/IntentPlugin/intent.d.ts.map +1 -0
- package/dist/types/src/plugins/PluginHost/PluginContext.d.ts +24 -0
- package/dist/types/src/plugins/PluginHost/PluginContext.d.ts.map +1 -0
- package/dist/types/src/plugins/PluginHost/PluginHost.d.ts +20 -0
- package/dist/types/src/plugins/PluginHost/PluginHost.d.ts.map +1 -0
- package/dist/types/src/plugins/PluginHost/index.d.ts +4 -0
- package/dist/types/src/plugins/PluginHost/index.d.ts.map +1 -0
- package/dist/types/src/plugins/PluginHost/plugin.d.ts +66 -0
- package/dist/types/src/plugins/PluginHost/plugin.d.ts.map +1 -0
- package/dist/types/src/plugins/PluginHost/plugin.test.d.ts +35 -0
- package/dist/types/src/plugins/PluginHost/plugin.test.d.ts.map +1 -0
- package/dist/types/src/plugins/SurfacePlugin/ErrorBoundary.d.ts +30 -0
- package/dist/types/src/plugins/SurfacePlugin/ErrorBoundary.d.ts.map +1 -0
- package/dist/types/src/plugins/SurfacePlugin/Surface.d.ts +45 -0
- package/dist/types/src/plugins/SurfacePlugin/Surface.d.ts.map +1 -0
- package/dist/types/src/plugins/SurfacePlugin/SurfacePlugin.d.ts +20 -0
- package/dist/types/src/plugins/SurfacePlugin/SurfacePlugin.d.ts.map +1 -0
- package/dist/types/src/plugins/SurfacePlugin/SurfaceRootContext.d.ts +13 -0
- package/dist/types/src/plugins/SurfacePlugin/SurfaceRootContext.d.ts.map +1 -0
- package/dist/types/src/plugins/SurfacePlugin/index.d.ts +5 -0
- package/dist/types/src/plugins/SurfacePlugin/index.d.ts.map +1 -0
- package/dist/types/src/plugins/common/graph.d.ts +26 -0
- package/dist/types/src/plugins/common/graph.d.ts.map +1 -0
- package/dist/types/src/plugins/common/index.d.ts +4 -0
- package/dist/types/src/plugins/common/index.d.ts.map +1 -0
- package/dist/types/src/plugins/common/layout.d.ts +89 -0
- package/dist/types/src/plugins/common/layout.d.ts.map +1 -0
- package/dist/types/src/plugins/common/translations.d.ts +22 -0
- package/dist/types/src/plugins/common/translations.d.ts.map +1 -0
- package/dist/types/src/plugins/helpers.d.ts +22 -0
- package/dist/types/src/plugins/helpers.d.ts.map +1 -0
- package/dist/types/src/plugins/index.d.ts +6 -0
- package/dist/types/src/plugins/index.d.ts.map +1 -0
- package/package.json +34 -0
- package/project.json +78 -0
- package/src/App.tsx +43 -0
- package/src/index.ts +7 -0
- package/src/plugins/IntentPlugin/IntentContext.tsx +17 -0
- package/src/plugins/IntentPlugin/IntentPlugin.tsx +69 -0
- package/src/plugins/IntentPlugin/index.ts +7 -0
- package/src/plugins/IntentPlugin/intent.ts +34 -0
- package/src/plugins/PluginHost/PluginContext.tsx +45 -0
- package/src/plugins/PluginHost/PluginHost.tsx +200 -0
- package/src/plugins/PluginHost/index.ts +7 -0
- package/src/plugins/PluginHost/plugin.test.ts +159 -0
- package/src/plugins/PluginHost/plugin.ts +82 -0
- package/src/plugins/SurfacePlugin/ErrorBoundary.tsx +45 -0
- package/src/plugins/SurfacePlugin/Surface.tsx +116 -0
- package/src/plugins/SurfacePlugin/SurfacePlugin.tsx +53 -0
- package/src/plugins/SurfacePlugin/SurfaceRootContext.tsx +22 -0
- package/src/plugins/SurfacePlugin/index.ts +8 -0
- package/src/plugins/common/graph.ts +33 -0
- package/src/plugins/common/index.ts +7 -0
- package/src/plugins/common/layout.ts +102 -0
- package/src/plugins/common/translations.ts +34 -0
- package/src/plugins/helpers.ts +43 -0
- package/src/plugins/index.ts +10 -0
- package/tsconfig.json +43 -0
- package/tsconfig.node.json +11 -0
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dxos/app-framework",
|
|
3
|
+
"version": "0.3.3-main.0c081e6",
|
|
4
|
+
"description": "A framework for building applications from composible plugins.",
|
|
5
|
+
"homepage": "https://dxos.org",
|
|
6
|
+
"bugs": "https://github.com/dxos/dxos/issues",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"author": "DXOS.org",
|
|
9
|
+
"main": "dist/lib/node/index.cjs",
|
|
10
|
+
"browser": {
|
|
11
|
+
"./dist/lib/node/index.cjs": "./dist/lib/browser/index.mjs"
|
|
12
|
+
},
|
|
13
|
+
"types": "dist/types/src/index.d.ts",
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@preact/signals-react": "^1.3.6",
|
|
16
|
+
"deepsignal": "1.4.0-shallow.0",
|
|
17
|
+
"zod": "^3.19.1",
|
|
18
|
+
"@dxos/app-graph": "0.3.3-main.0c081e6",
|
|
19
|
+
"@dxos/async": "0.3.3-main.0c081e6",
|
|
20
|
+
"@dxos/debug": "0.3.3-main.0c081e6",
|
|
21
|
+
"@dxos/invariant": "0.3.3-main.0c081e6",
|
|
22
|
+
"@dxos/log": "0.3.3-main.0c081e6"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@types/react": "^18.0.21",
|
|
26
|
+
"react": "^18.2.0"
|
|
27
|
+
},
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"react": "*"
|
|
30
|
+
},
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public"
|
|
33
|
+
}
|
|
34
|
+
}
|
package/project.json
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"sourceRoot": "packages/sdk/app-framework/src",
|
|
3
|
+
"projectType": "application",
|
|
4
|
+
"targets": {
|
|
5
|
+
"build": {
|
|
6
|
+
"executor": "@nx/js:tsc",
|
|
7
|
+
"options": {
|
|
8
|
+
"main": "packages/sdk/app-framework/src/index.ts",
|
|
9
|
+
"outputPath": "packages/sdk/app-framework/dist/types",
|
|
10
|
+
"tsConfig": "packages/sdk/app-framework/tsconfig.json"
|
|
11
|
+
},
|
|
12
|
+
"outputs": [
|
|
13
|
+
"{options.outputPath}"
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
"compile": {
|
|
17
|
+
"executor": "@dxos/esbuild:build",
|
|
18
|
+
"options": {
|
|
19
|
+
"entryPoints": [
|
|
20
|
+
"packages/sdk/app-framework/src/index.ts"
|
|
21
|
+
],
|
|
22
|
+
"outputPath": "packages/sdk/app-framework/dist/lib"
|
|
23
|
+
},
|
|
24
|
+
"outputs": [
|
|
25
|
+
"{options.outputPath}"
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
"lint": {
|
|
29
|
+
"executor": "@nx/linter:eslint",
|
|
30
|
+
"options": {
|
|
31
|
+
"format": "unix",
|
|
32
|
+
"lintFilePatterns": [
|
|
33
|
+
"packages/sdk/app-framework/**/*.{ts,tsx,js,jsx}"
|
|
34
|
+
],
|
|
35
|
+
"quiet": true
|
|
36
|
+
},
|
|
37
|
+
"outputs": [
|
|
38
|
+
"{options.outputFile}"
|
|
39
|
+
]
|
|
40
|
+
},
|
|
41
|
+
"storybook": {
|
|
42
|
+
"configurations": {
|
|
43
|
+
"ci": {
|
|
44
|
+
"quiet": true
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"executor": "@nx/storybook:storybook",
|
|
48
|
+
"options": {
|
|
49
|
+
"configDir": "packages/sdk/app-framework/.storybook"
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"test": {
|
|
53
|
+
"executor": "@dxos/test:run",
|
|
54
|
+
"options": {
|
|
55
|
+
"ciEnvironments": [
|
|
56
|
+
"nodejs"
|
|
57
|
+
],
|
|
58
|
+
"coveragePath": "coverage/packages/sdk/app-framework",
|
|
59
|
+
"outputPath": "tmp/mocha/packages/sdk/app-framework",
|
|
60
|
+
"resultsPath": "test-results/packages/sdk/app-framework",
|
|
61
|
+
"testPatterns": [
|
|
62
|
+
"packages/sdk/app-framework/src/**/*.test.{ts,js}"
|
|
63
|
+
],
|
|
64
|
+
"watchPatterns": [
|
|
65
|
+
"packages/sdk/app-framework/src/**/*"
|
|
66
|
+
]
|
|
67
|
+
},
|
|
68
|
+
"outputs": [
|
|
69
|
+
"{options.coveragePath}",
|
|
70
|
+
"{options.outputPath}",
|
|
71
|
+
"{options.resultsPath}"
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
"implicitDependencies": [
|
|
76
|
+
"test"
|
|
77
|
+
]
|
|
78
|
+
}
|
package/src/App.tsx
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React from 'react';
|
|
6
|
+
|
|
7
|
+
import { invariant } from '@dxos/invariant';
|
|
8
|
+
|
|
9
|
+
import { type BootstrapPluginsParams, PluginHost, SurfacePlugin, IntentPlugin } from './plugins';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Expected usage is for this to be the entrypoint of the application.
|
|
13
|
+
* Initializes plugins and renders the root components.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* const fallback = <div>Initializing Plugins...</div>;
|
|
17
|
+
* const App = createApp({ plugins, fallback });
|
|
18
|
+
* createRoot(document.getElementById('root')!).render(
|
|
19
|
+
* <StrictMode>
|
|
20
|
+
* <App />
|
|
21
|
+
* </StrictMode>,
|
|
22
|
+
* );
|
|
23
|
+
*
|
|
24
|
+
* @param params.plugins List of plugin definitions to initialize.
|
|
25
|
+
* @param params.fallback Fallback component to render while plugins are initializing.
|
|
26
|
+
*/
|
|
27
|
+
export const createApp = ({ plugins, fallback }: BootstrapPluginsParams) => {
|
|
28
|
+
const host = PluginHost({
|
|
29
|
+
plugins: [SurfacePlugin(), IntentPlugin(), ...plugins],
|
|
30
|
+
fallback,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
invariant(host.provides?.context);
|
|
34
|
+
invariant(host.provides?.root);
|
|
35
|
+
const Context = host.provides.context;
|
|
36
|
+
const Root = host.provides.root;
|
|
37
|
+
|
|
38
|
+
return () => (
|
|
39
|
+
<Context>
|
|
40
|
+
<Root />
|
|
41
|
+
</Context>
|
|
42
|
+
);
|
|
43
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Context, createContext, useContext, type Provider } from 'react';
|
|
6
|
+
|
|
7
|
+
import { type DispatchIntent } from './intent';
|
|
8
|
+
|
|
9
|
+
export type IntentContext = {
|
|
10
|
+
dispatch: DispatchIntent;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const IntentContext: Context<IntentContext> = createContext<IntentContext>({ dispatch: async () => {} });
|
|
14
|
+
|
|
15
|
+
export const useIntent = () => useContext(IntentContext);
|
|
16
|
+
|
|
17
|
+
export const IntentProvider: Provider<IntentContext> = IntentContext.Provider;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { deepSignal } from 'deepsignal/react';
|
|
6
|
+
import React from 'react';
|
|
7
|
+
|
|
8
|
+
import { type IntentContext, IntentProvider } from './IntentContext';
|
|
9
|
+
import { type Intent } from './intent';
|
|
10
|
+
import { type PluginDefinition, type Plugin } from '../PluginHost';
|
|
11
|
+
import { filterPlugins, findPlugin } from '../helpers';
|
|
12
|
+
|
|
13
|
+
export type IntentResolverProvides = {
|
|
14
|
+
intent: {
|
|
15
|
+
resolver: (intent: Intent, plugins: Plugin[]) => any;
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type IntentPluginProvides = {
|
|
20
|
+
intent: IntentContext;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const parseIntentPlugin = (plugin: Plugin) =>
|
|
24
|
+
(plugin.provides as any).intent?.dispatch ? (plugin as Plugin<IntentPluginProvides>) : undefined;
|
|
25
|
+
|
|
26
|
+
export const parseIntentResolverPlugin = (plugin: Plugin) =>
|
|
27
|
+
(plugin.provides as any).intent?.resolver ? (plugin as Plugin<IntentResolverProvides>) : undefined;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Allows plugins to register intent handlers and routes sent intents to the appropriate plugin.
|
|
31
|
+
* Inspired by https://developer.android.com/reference/android/content/Intent.
|
|
32
|
+
*/
|
|
33
|
+
export const IntentPlugin = (): PluginDefinition<IntentPluginProvides> => {
|
|
34
|
+
const state = deepSignal<IntentContext>({ dispatch: async () => {} });
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
meta: {
|
|
38
|
+
id: 'dxos.org/plugin/intent',
|
|
39
|
+
},
|
|
40
|
+
ready: async (plugins) => {
|
|
41
|
+
// Dispatch intent to associated plugin.
|
|
42
|
+
const dispatch = (intent: Intent) => {
|
|
43
|
+
if (intent.plugin) {
|
|
44
|
+
const plugin = findPlugin<IntentResolverProvides>(plugins, intent.plugin);
|
|
45
|
+
return plugin?.provides.intent.resolver(intent, plugins);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Return resolved value from first plugin that handles the intent.
|
|
49
|
+
return filterPlugins(plugins, parseIntentResolverPlugin).reduce((acc, plugin) => {
|
|
50
|
+
return acc ?? plugin.provides.intent.resolver(intent, plugins);
|
|
51
|
+
}, undefined);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// Sequentially dispatch array of invents.
|
|
55
|
+
state.dispatch = async (intentOrArray) => {
|
|
56
|
+
let result: any = null;
|
|
57
|
+
for (const intent of Array.isArray(intentOrArray) ? intentOrArray : [intentOrArray]) {
|
|
58
|
+
const data = intent.data ? { ...result, ...intent.data } : result;
|
|
59
|
+
result = await dispatch({ ...intent, data });
|
|
60
|
+
}
|
|
61
|
+
return result;
|
|
62
|
+
};
|
|
63
|
+
},
|
|
64
|
+
provides: {
|
|
65
|
+
intent: state,
|
|
66
|
+
context: ({ children }) => <IntentProvider value={state}>{children}</IntentProvider>,
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* An intent is an abstract description of an operation to be performed.
|
|
7
|
+
* Intents allow actions to be performed across plugins.
|
|
8
|
+
*/
|
|
9
|
+
export type Intent = {
|
|
10
|
+
/**
|
|
11
|
+
* Plugin ID.
|
|
12
|
+
* If specified, the intent will be sent explicitly to the plugin.
|
|
13
|
+
* Otherwise, the intent will be sent to all plugins, in order and the first to resolve a non-null value will be used.
|
|
14
|
+
*/
|
|
15
|
+
plugin?: string;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The action to perform.
|
|
19
|
+
*/
|
|
20
|
+
action: string;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Any data needed to perform the desired action.
|
|
24
|
+
*/
|
|
25
|
+
data?: any;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Trigger one or more intents to be sent.
|
|
30
|
+
* If multiple intents are specified, the result of each will be merged with the data to the next.
|
|
31
|
+
*
|
|
32
|
+
* @returns The result of the last intent.
|
|
33
|
+
*/
|
|
34
|
+
export type DispatchIntent = (intent: Intent | Intent[]) => Promise<any>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Context, type Provider, createContext, useContext } from 'react';
|
|
6
|
+
|
|
7
|
+
import { type Plugin, type PluginDefinition } from './plugin';
|
|
8
|
+
import { findPlugin, resolvePlugin } from '../helpers';
|
|
9
|
+
|
|
10
|
+
export type PluginContext = {
|
|
11
|
+
ready: boolean;
|
|
12
|
+
initializing: PluginDefinition[];
|
|
13
|
+
loading: PluginDefinition[];
|
|
14
|
+
plugins: Plugin[];
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const PluginContext: Context<PluginContext> = createContext<PluginContext>({
|
|
18
|
+
ready: false,
|
|
19
|
+
initializing: [],
|
|
20
|
+
loading: [],
|
|
21
|
+
plugins: [],
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Get all plugins.
|
|
26
|
+
*/
|
|
27
|
+
export const usePlugins = (): PluginContext => useContext(PluginContext);
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Get a plugin by ID.
|
|
31
|
+
*/
|
|
32
|
+
export const usePlugin = <T,>(id: string): Plugin<T> | undefined => {
|
|
33
|
+
const { plugins } = usePlugins();
|
|
34
|
+
return findPlugin<T>(plugins, id);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Resolve a plugin by predicate.
|
|
39
|
+
*/
|
|
40
|
+
export const useResolvePlugin = <T,>(predicate: (plugin: Plugin) => Plugin<T> | undefined): Plugin<T> | undefined => {
|
|
41
|
+
const { plugins } = usePlugins();
|
|
42
|
+
return resolvePlugin(plugins, predicate);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const PluginProvider: Provider<PluginContext> = PluginContext.Provider;
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { deepSignal, shallow } from 'deepsignal/react';
|
|
6
|
+
import React, { useEffect, type FC, type PropsWithChildren, type ReactNode } from 'react';
|
|
7
|
+
|
|
8
|
+
import { log } from '@dxos/log';
|
|
9
|
+
|
|
10
|
+
import { type PluginContext, PluginProvider } from './PluginContext';
|
|
11
|
+
import { type Plugin, type PluginDefinition, type PluginProvides } from './plugin';
|
|
12
|
+
|
|
13
|
+
export type BootstrapPluginsParams = {
|
|
14
|
+
plugins: PluginDefinition[];
|
|
15
|
+
fallback?: ReactNode | FC<Omit<PluginContext, 'ready'>>;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type PluginHostProvides = {
|
|
19
|
+
plugins: PluginContext;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const parsePluginHost = (plugin: Plugin) =>
|
|
23
|
+
(plugin.provides as PluginHostProvides).plugins ? (plugin as Plugin<PluginHostProvides>) : undefined;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Bootstraps an application by initializing plugins and rendering root components.
|
|
27
|
+
*/
|
|
28
|
+
export const PluginHost = ({
|
|
29
|
+
plugins: definitions,
|
|
30
|
+
fallback,
|
|
31
|
+
}: BootstrapPluginsParams): PluginDefinition<PluginHostProvides> => {
|
|
32
|
+
const state = deepSignal<PluginContext>({ ready: false, initializing: [], loading: [], plugins: [] });
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
meta: {
|
|
36
|
+
id: 'dxos.org/plugin/host',
|
|
37
|
+
},
|
|
38
|
+
provides: {
|
|
39
|
+
plugins: state,
|
|
40
|
+
context: ({ children }) => <PluginProvider value={state}>{children}</PluginProvider>,
|
|
41
|
+
root: () => {
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
log('initializing plugins', { definitions });
|
|
44
|
+
const timeout = setTimeout(async () => {
|
|
45
|
+
state.initializing = shallow(definitions);
|
|
46
|
+
const plugins = await Promise.all(
|
|
47
|
+
definitions.map(async (definition) => {
|
|
48
|
+
const plugin = await initializePlugin(definition).catch((err) => {
|
|
49
|
+
console.error('Failed to initialize plugin:', definition.meta.id, err);
|
|
50
|
+
return undefined;
|
|
51
|
+
});
|
|
52
|
+
state.initializing = shallow(state.initializing.filter((plugin) => plugin !== definition));
|
|
53
|
+
return plugin;
|
|
54
|
+
}),
|
|
55
|
+
).then((plugins) => plugins.filter((plugin): plugin is Plugin => Boolean(plugin)));
|
|
56
|
+
|
|
57
|
+
log('plugins initialized', { plugins });
|
|
58
|
+
state.loading = shallow(definitions);
|
|
59
|
+
await Promise.all(
|
|
60
|
+
definitions.map(async (pluginDefinition) => {
|
|
61
|
+
await pluginDefinition.ready?.(plugins);
|
|
62
|
+
state.loading = shallow(state.loading.filter((plugin) => plugin !== pluginDefinition));
|
|
63
|
+
}),
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
log('plugins ready', { plugins });
|
|
67
|
+
state.plugins = shallow(plugins);
|
|
68
|
+
state.ready = true;
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
return () => {
|
|
72
|
+
clearTimeout(timeout);
|
|
73
|
+
state.ready = false;
|
|
74
|
+
void Promise.all(definitions.map((definition) => definition.unload?.()));
|
|
75
|
+
};
|
|
76
|
+
}, []);
|
|
77
|
+
|
|
78
|
+
if (!state.ready) {
|
|
79
|
+
if (typeof fallback === 'function') {
|
|
80
|
+
const FallbackComponent = fallback;
|
|
81
|
+
return (
|
|
82
|
+
<FallbackComponent initializing={state.initializing} loading={state.loading} plugins={state.plugins} />
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
return <>{fallback ?? null}</>;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const ComposedContext = composeContext(state.plugins);
|
|
89
|
+
|
|
90
|
+
return <ComposedContext>{rootComponents(state.plugins)}</ComposedContext>;
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Resolve a `PluginDefinition` into a fully initialized `Plugin`.
|
|
98
|
+
*/
|
|
99
|
+
export const initializePlugin = async <T, U>(pluginDefinition: PluginDefinition<T, U>): Promise<Plugin<T & U>> => {
|
|
100
|
+
const provides = await pluginDefinition.initialize?.();
|
|
101
|
+
return {
|
|
102
|
+
...pluginDefinition,
|
|
103
|
+
provides: {
|
|
104
|
+
...pluginDefinition.provides,
|
|
105
|
+
...provides,
|
|
106
|
+
} as PluginProvides<T & U>,
|
|
107
|
+
};
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const rootComponents = (plugins: Plugin[]) => {
|
|
111
|
+
return plugins
|
|
112
|
+
.map((plugin) => {
|
|
113
|
+
const Component = plugin.provides.root;
|
|
114
|
+
if (Component) {
|
|
115
|
+
return <Component key={plugin.meta.id} />;
|
|
116
|
+
} else {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
})
|
|
120
|
+
.filter((node): node is JSX.Element => Boolean(node));
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const composeContext = (plugins: Plugin[]) => {
|
|
124
|
+
return compose(plugins.map((p) => p.provides.context!).filter(Boolean));
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const compose = (contexts: FC<PropsWithChildren>[]) => {
|
|
128
|
+
return [...contexts].reduce((Acc, Next) => ({ children }) => (
|
|
129
|
+
<Acc>
|
|
130
|
+
<Next>{children}</Next>
|
|
131
|
+
</Acc>
|
|
132
|
+
));
|
|
133
|
+
};
|
|
134
|
+
// export const PluginHost = ({ plugins: definitions, fallback }: BootstrapPluginsParams): PluginDefinition => {
|
|
135
|
+
// // const state = deepSignal<PluginContext>({ ready: false, initializing: [], loading: [], plugins: [] });
|
|
136
|
+
|
|
137
|
+
// return {
|
|
138
|
+
// meta: {
|
|
139
|
+
// id: 'dxos.org/plugin/host',
|
|
140
|
+
// },
|
|
141
|
+
// provides: {
|
|
142
|
+
// // plugins: state,
|
|
143
|
+
// // context: ({ children }) => <PluginProvider value={state}>{children}</PluginProvider>,
|
|
144
|
+
// root: () => {
|
|
145
|
+
// const [plugins, setPlugins] = useState<Plugin[]>();
|
|
146
|
+
// const [initializing, setInitializing] = useState<PluginDefinition[]>([]);
|
|
147
|
+
// const [loading, setLoading] = useState<PluginDefinition[]>([]);
|
|
148
|
+
// useEffect(() => {
|
|
149
|
+
// log('initializing plugins', { definitions });
|
|
150
|
+
// const timeout = setTimeout(async () => {
|
|
151
|
+
// setInitializing(definitions);
|
|
152
|
+
// const plugins = await Promise.all(
|
|
153
|
+
// definitions.map(async (definition) => {
|
|
154
|
+
// const plugin = await initializePlugin(definition).catch((err) => {
|
|
155
|
+
// console.error('Failed to initialize plugin:', definition.meta.id, err);
|
|
156
|
+
// return undefined;
|
|
157
|
+
// });
|
|
158
|
+
// setInitializing((initializing) => initializing.filter((plugin) => plugin !== definition));
|
|
159
|
+
// return plugin;
|
|
160
|
+
// }),
|
|
161
|
+
// ).then((plugins) => plugins.filter((plugin): plugin is Plugin => Boolean(plugin)));
|
|
162
|
+
|
|
163
|
+
// log('plugins initialized', { plugins });
|
|
164
|
+
// setLoading(definitions);
|
|
165
|
+
// await Promise.all(
|
|
166
|
+
// definitions.map(async (pluginDefinition) => {
|
|
167
|
+
// await pluginDefinition.ready?.(plugins);
|
|
168
|
+
// setLoading((loading) => loading.filter((plugin) => plugin !== pluginDefinition));
|
|
169
|
+
// }),
|
|
170
|
+
// );
|
|
171
|
+
|
|
172
|
+
// log('plugins ready', { plugins });
|
|
173
|
+
// setPlugins(plugins);
|
|
174
|
+
// });
|
|
175
|
+
|
|
176
|
+
// return () => {
|
|
177
|
+
// clearTimeout(timeout);
|
|
178
|
+
// void Promise.all(definitions.map((definition) => definition.unload?.()));
|
|
179
|
+
// };
|
|
180
|
+
// }, []);
|
|
181
|
+
|
|
182
|
+
// if (!plugins) {
|
|
183
|
+
// if (typeof fallback === 'function') {
|
|
184
|
+
// const FallbackComponent = fallback;
|
|
185
|
+
// return <FallbackComponent initializing={initializing} loading={loading} />;
|
|
186
|
+
// }
|
|
187
|
+
// return <>{fallback ?? null}</>;
|
|
188
|
+
// }
|
|
189
|
+
|
|
190
|
+
// const ComposedContext = composeContext(plugins);
|
|
191
|
+
|
|
192
|
+
// return (
|
|
193
|
+
// <PluginProvider value={{ plugins, initializing, loading, ready: true }}>
|
|
194
|
+
// <ComposedContext>{rootComponents(plugins)}</ComposedContext>
|
|
195
|
+
// </PluginProvider>
|
|
196
|
+
// );
|
|
197
|
+
// },
|
|
198
|
+
// },
|
|
199
|
+
// };
|
|
200
|
+
// };
|