@dxos/app-framework 0.3.11-main.d7d4c52 → 0.3.11-main.da09c3c
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/dist/lib/browser/index.mjs +171 -139
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node/index.cjs +159 -129
- package/dist/lib/node/index.cjs.map +3 -3
- package/dist/lib/node/meta.json +1 -1
- package/dist/types/src/plugins/PluginHost/PluginHost.d.ts +4 -2
- package/dist/types/src/plugins/PluginHost/PluginHost.d.ts.map +1 -1
- package/dist/types/src/plugins/PluginHost/plugin.d.ts.map +1 -1
- package/package.json +7 -7
- package/project.json +3 -1
- package/src/plugins/PluginHost/PluginHost.tsx +92 -57
- package/src/plugins/PluginHost/plugin.ts +1 -0
|
@@ -3,20 +3,22 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { shallow } from 'deepsignal/react';
|
|
6
|
-
import React, { useEffect, type FC, type PropsWithChildren, type ReactNode } from 'react';
|
|
6
|
+
import React, { useEffect, type FC, type PropsWithChildren, type ReactNode, useState } from 'react';
|
|
7
7
|
|
|
8
8
|
import { LocalStorageStore } from '@dxos/local-storage';
|
|
9
9
|
import { log } from '@dxos/log';
|
|
10
10
|
|
|
11
11
|
import { type PluginContext, PluginProvider } from './PluginContext';
|
|
12
12
|
import { type Plugin, type PluginDefinition, type PluginProvides } from './plugin';
|
|
13
|
+
import { ErrorBoundary } from '../SurfacePlugin';
|
|
13
14
|
|
|
14
15
|
export type BootstrapPluginsParams = {
|
|
15
16
|
order: PluginDefinition['meta'][];
|
|
16
17
|
plugins: Record<string, () => Promise<PluginDefinition>>;
|
|
17
18
|
core?: string[];
|
|
18
19
|
defaults?: string[];
|
|
19
|
-
fallback?:
|
|
20
|
+
fallback?: ErrorBoundary['props']['fallback'];
|
|
21
|
+
placeholder?: ReactNode;
|
|
20
22
|
};
|
|
21
23
|
|
|
22
24
|
export type PluginHostProvides = {
|
|
@@ -36,7 +38,8 @@ export const PluginHost = ({
|
|
|
36
38
|
plugins: definitions,
|
|
37
39
|
core = [],
|
|
38
40
|
defaults = [],
|
|
39
|
-
fallback =
|
|
41
|
+
fallback = DefaultFallback,
|
|
42
|
+
placeholder = null,
|
|
40
43
|
}: BootstrapPluginsParams): PluginDefinition<PluginHostProvides> => {
|
|
41
44
|
const state = new LocalStorageStore<PluginContext>(PLUGIN_HOST, {
|
|
42
45
|
ready: false,
|
|
@@ -63,65 +66,97 @@ export const PluginHost = ({
|
|
|
63
66
|
plugins: state.values,
|
|
64
67
|
context: ({ children }) => <PluginProvider value={state.values}>{children}</PluginProvider>,
|
|
65
68
|
root: () => {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const indexB = order.findIndex(({ id }) => id === b);
|
|
72
|
-
return indexA - indexB;
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
const enabled = await Promise.all(
|
|
76
|
-
enabledIds
|
|
77
|
-
.map((id) => definitions[id])
|
|
78
|
-
// If local storage indicates a plugin is enabled, but it is not available, ignore it.
|
|
79
|
-
.filter((definition): definition is () => Promise<PluginDefinition> => Boolean(definition))
|
|
80
|
-
.map((definition) => definition()),
|
|
81
|
-
);
|
|
82
|
-
|
|
83
|
-
const plugins = await Promise.all(
|
|
84
|
-
enabled.map(async (definition) => {
|
|
85
|
-
const plugin = await initializePlugin(definition).catch((err) => {
|
|
86
|
-
console.error('Failed to initialize plugin:', definition.meta.id, err);
|
|
87
|
-
return undefined;
|
|
88
|
-
});
|
|
89
|
-
return plugin;
|
|
90
|
-
}),
|
|
91
|
-
).then((plugins) => plugins.filter((plugin): plugin is Plugin => Boolean(plugin)));
|
|
92
|
-
log('plugins initialized', { plugins });
|
|
93
|
-
|
|
94
|
-
await Promise.all(
|
|
95
|
-
enabled.map(async (pluginDefinition) => {
|
|
96
|
-
await pluginDefinition.ready?.(plugins);
|
|
97
|
-
}),
|
|
98
|
-
);
|
|
99
|
-
log('plugins ready', { plugins });
|
|
100
|
-
|
|
101
|
-
state.values.plugins = shallow(plugins);
|
|
102
|
-
state.values.ready = true;
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
return () => {
|
|
106
|
-
clearTimeout(timeout);
|
|
107
|
-
state.values.ready = false;
|
|
108
|
-
// TODO(wittjosiah): Does this ever need to be called prior to having dynamic plugins?
|
|
109
|
-
// void Promise.all(enabled.map((definition) => definition.unload?.()));
|
|
110
|
-
};
|
|
111
|
-
}, []);
|
|
112
|
-
|
|
113
|
-
if (!state.values.ready) {
|
|
114
|
-
return <>{fallback}</>;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const ComposedContext = composeContext(state.values.plugins);
|
|
118
|
-
|
|
119
|
-
return <ComposedContext>{rootComponents(state.values.plugins)}</ComposedContext>;
|
|
69
|
+
return (
|
|
70
|
+
<ErrorBoundary fallback={fallback}>
|
|
71
|
+
<Root order={order} core={core} definitions={definitions} state={state.values} placeholder={placeholder} />
|
|
72
|
+
</ErrorBoundary>
|
|
73
|
+
);
|
|
120
74
|
},
|
|
121
75
|
},
|
|
122
76
|
};
|
|
123
77
|
};
|
|
124
78
|
|
|
79
|
+
const DefaultFallback = ({ error }: { error: Error }) => {
|
|
80
|
+
return (
|
|
81
|
+
<div style={{ padding: '1rem' }}>
|
|
82
|
+
{/* TODO(wittjosiah): Link to docs for replacing default. */}
|
|
83
|
+
<h1 style={{ fontSize: '1.2rem', fontWeight: 700, margin: '0.5rem 0' }}>{error.message}</h1>
|
|
84
|
+
<pre>{error.stack}</pre>
|
|
85
|
+
</div>
|
|
86
|
+
);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
type RootProps = {
|
|
90
|
+
order: PluginDefinition['meta'][];
|
|
91
|
+
state: PluginContext;
|
|
92
|
+
definitions: Record<string, () => Promise<PluginDefinition>>;
|
|
93
|
+
core: string[];
|
|
94
|
+
placeholder: ReactNode;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const Root = ({ order, core: corePluginIds, definitions, state, placeholder }: RootProps) => {
|
|
98
|
+
const [error, setError] = useState<unknown>();
|
|
99
|
+
|
|
100
|
+
useEffect(() => {
|
|
101
|
+
log('initializing plugins', { enabled: state.enabled });
|
|
102
|
+
const timeout = setTimeout(async () => {
|
|
103
|
+
try {
|
|
104
|
+
const enabledIds = [...corePluginIds, ...state.enabled].sort((a, b) => {
|
|
105
|
+
const indexA = order.findIndex(({ id }) => id === a);
|
|
106
|
+
const indexB = order.findIndex(({ id }) => id === b);
|
|
107
|
+
return indexA - indexB;
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const enabled = await Promise.all(
|
|
111
|
+
enabledIds
|
|
112
|
+
.map((id) => definitions[id])
|
|
113
|
+
// If local storage indicates a plugin is enabled, but it is not available, ignore it.
|
|
114
|
+
.filter((definition): definition is () => Promise<PluginDefinition> => Boolean(definition))
|
|
115
|
+
.map((definition) => definition()),
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
const plugins = await Promise.all(
|
|
119
|
+
enabled.map(async (definition) => {
|
|
120
|
+
const plugin = await initializePlugin(definition).catch((err) => {
|
|
121
|
+
console.error('Failed to initialize plugin:', definition.meta.id, err);
|
|
122
|
+
return undefined;
|
|
123
|
+
});
|
|
124
|
+
return plugin;
|
|
125
|
+
}),
|
|
126
|
+
).then((plugins) => plugins.filter((plugin): plugin is Plugin => Boolean(plugin)));
|
|
127
|
+
log('plugins initialized', { plugins });
|
|
128
|
+
|
|
129
|
+
await Promise.all(enabled.map((pluginDefinition) => pluginDefinition.ready?.(plugins)));
|
|
130
|
+
log('plugins ready', { plugins });
|
|
131
|
+
|
|
132
|
+
state.plugins = shallow(plugins);
|
|
133
|
+
state.ready = true;
|
|
134
|
+
} catch (err) {
|
|
135
|
+
setError(err);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
return () => {
|
|
140
|
+
clearTimeout(timeout);
|
|
141
|
+
state.ready = false;
|
|
142
|
+
// TODO(wittjosiah): Does this ever need to be called prior to having dynamic plugins?
|
|
143
|
+
// void Promise.all(enabled.map((definition) => definition.unload?.()));
|
|
144
|
+
};
|
|
145
|
+
}, []);
|
|
146
|
+
|
|
147
|
+
if (error) {
|
|
148
|
+
throw error;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (!state.ready) {
|
|
152
|
+
return <>{placeholder}</>;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const ComposedContext = composeContext(state.plugins);
|
|
156
|
+
|
|
157
|
+
return <ComposedContext>{rootComponents(state.plugins)}</ComposedContext>;
|
|
158
|
+
};
|
|
159
|
+
|
|
125
160
|
/**
|
|
126
161
|
* Resolve a `PluginDefinition` into a fully initialized `Plugin`.
|
|
127
162
|
*/
|
|
@@ -99,6 +99,7 @@ export type PluginDefinition<TProvides = {}, TInitializeProvides = {}> = Omit<Pl
|
|
|
99
99
|
*
|
|
100
100
|
* @param plugins All plugins which successfully initialized.
|
|
101
101
|
*/
|
|
102
|
+
// TODO(wittjosiah): Rename `ready` to a verb?
|
|
102
103
|
ready?: (plugins: Plugin[]) => Promise<void>;
|
|
103
104
|
|
|
104
105
|
/**
|