@dxos/app-framework 0.6.13 → 0.6.14-main.2b6a0f3
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/{chunk-YYV26N3W.mjs → chunk-SB4XRTGZ.mjs} +4 -5
- package/dist/lib/browser/chunk-SB4XRTGZ.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +193 -169
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/{plugin-K3KCPCTJ.mjs → plugin-Y4KRQJE4.mjs} +4 -3
- package/dist/lib/browser/plugin-Y4KRQJE4.mjs.map +7 -0
- package/dist/lib/node/{chunk-P5GRB4XF.cjs → chunk-PJGE52CN.cjs} +7 -8
- package/dist/lib/node/chunk-PJGE52CN.cjs.map +7 -0
- package/dist/lib/node/index.cjs +207 -188
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/{plugin-RUJ5PEXB.cjs → plugin-6TEDSCXW.cjs} +10 -9
- package/dist/lib/node/plugin-6TEDSCXW.cjs.map +7 -0
- package/dist/lib/node-esm/chunk-IY7HCP4K.mjs +22 -0
- package/dist/lib/node-esm/chunk-IY7HCP4K.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-MBHRXQTR.mjs +27 -0
- package/dist/lib/node-esm/chunk-MBHRXQTR.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-P2TQLXZR.mjs +54 -0
- package/dist/lib/node-esm/chunk-P2TQLXZR.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +701 -0
- package/dist/lib/node-esm/index.mjs.map +7 -0
- package/dist/lib/node-esm/meta.json +1 -0
- package/dist/lib/node-esm/plugin-5AAUGDB3.mjs +168 -0
- package/dist/lib/node-esm/plugin-5AAUGDB3.mjs.map +7 -0
- package/dist/lib/node-esm/plugin-J5IRJLM6.mjs +41 -0
- package/dist/lib/node-esm/plugin-J5IRJLM6.mjs.map +7 -0
- package/dist/types/src/App.d.ts +4 -4
- package/dist/types/src/App.d.ts.map +1 -1
- package/dist/types/src/plugins/PluginHost/PluginContainer.d.ts +14 -0
- package/dist/types/src/plugins/PluginHost/PluginContainer.d.ts.map +1 -0
- package/dist/types/src/plugins/PluginHost/PluginContext.d.ts +4 -4
- package/dist/types/src/plugins/PluginHost/PluginContext.d.ts.map +1 -1
- package/dist/types/src/plugins/PluginHost/PluginHost.d.ts +4 -8
- package/dist/types/src/plugins/PluginHost/PluginHost.d.ts.map +1 -1
- package/dist/types/src/plugins/PluginHost/plugin.d.ts +37 -83
- package/dist/types/src/plugins/PluginHost/plugin.d.ts.map +1 -1
- package/dist/types/src/plugins/PluginHost/plugin.test.d.ts.map +1 -1
- package/dist/types/src/plugins/SurfacePlugin/Surface.d.ts +2 -2
- package/dist/types/src/plugins/SurfacePlugin/Surface.d.ts.map +1 -1
- package/dist/types/src/plugins/SurfacePlugin/SurfaceRootContext.d.ts +15 -2
- package/dist/types/src/plugins/SurfacePlugin/SurfaceRootContext.d.ts.map +1 -1
- package/dist/types/src/plugins/SurfacePlugin/plugin.d.ts +1 -1
- package/dist/types/src/plugins/SurfacePlugin/plugin.d.ts.map +1 -1
- package/dist/types/src/plugins/common/navigation.d.ts +1 -12
- package/dist/types/src/plugins/common/navigation.d.ts.map +1 -1
- package/package.json +15 -11
- package/project.json +3 -8
- package/src/App.tsx +10 -9
- package/src/plugins/PluginHost/PluginContainer.tsx +120 -0
- package/src/plugins/PluginHost/PluginContext.tsx +8 -14
- package/src/plugins/PluginHost/PluginHost.tsx +18 -121
- package/src/plugins/PluginHost/plugin.test.ts +1 -2
- package/src/plugins/PluginHost/plugin.ts +45 -52
- package/src/plugins/SurfacePlugin/Surface.tsx +30 -10
- package/src/plugins/SurfacePlugin/SurfaceRootContext.tsx +21 -4
- package/src/plugins/SurfacePlugin/plugin.tsx +2 -2
- package/src/plugins/common/navigation.ts +4 -12
- package/tsconfig.json +1 -29
- package/vitest.config.ts +9 -0
- package/dist/lib/browser/chunk-YYV26N3W.mjs.map +0 -7
- package/dist/lib/browser/plugin-K3KCPCTJ.mjs.map +0 -7
- package/dist/lib/node/chunk-P5GRB4XF.cjs.map +0 -7
- package/dist/lib/node/plugin-RUJ5PEXB.cjs.map +0 -7
|
@@ -2,19 +2,20 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import React, { type
|
|
5
|
+
import React, { type ReactNode } from 'react';
|
|
6
6
|
|
|
7
7
|
import { LocalStorageStore } from '@dxos/local-storage';
|
|
8
|
-
import { log } from '@dxos/log';
|
|
9
8
|
|
|
9
|
+
import { PluginContainer } from './PluginContainer';
|
|
10
10
|
import { type PluginContext, PluginProvider } from './PluginContext';
|
|
11
|
-
import { type Plugin, type PluginDefinition, type
|
|
11
|
+
import { type Plugin, type PluginDefinition, type PluginMeta } from './plugin';
|
|
12
12
|
import { ErrorBoundary } from '../SurfacePlugin';
|
|
13
13
|
|
|
14
14
|
export type BootstrapPluginsParams = {
|
|
15
|
-
order: PluginDefinition['meta'][];
|
|
16
15
|
plugins: Record<string, () => Promise<PluginDefinition>>;
|
|
17
|
-
|
|
16
|
+
// Ordered list of plugins.
|
|
17
|
+
meta: PluginMeta[];
|
|
18
|
+
core: string[];
|
|
18
19
|
defaults?: string[];
|
|
19
20
|
fallback?: ErrorBoundary['props']['fallback'];
|
|
20
21
|
placeholder?: ReactNode;
|
|
@@ -33,9 +34,9 @@ const PLUGIN_HOST = 'dxos.org/plugin/host';
|
|
|
33
34
|
* Bootstraps an application by initializing plugins and rendering root components.
|
|
34
35
|
*/
|
|
35
36
|
export const PluginHost = ({
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
core
|
|
37
|
+
plugins,
|
|
38
|
+
meta,
|
|
39
|
+
core,
|
|
39
40
|
defaults = [],
|
|
40
41
|
fallback = DefaultFallback,
|
|
41
42
|
placeholder = null,
|
|
@@ -45,7 +46,7 @@ export const PluginHost = ({
|
|
|
45
46
|
core,
|
|
46
47
|
enabled: [...defaults],
|
|
47
48
|
plugins: [],
|
|
48
|
-
available:
|
|
49
|
+
available: meta.filter(({ id }) => !core.includes(id)),
|
|
49
50
|
setPlugin: (id: string, enabled: boolean) => {
|
|
50
51
|
if (enabled) {
|
|
51
52
|
state.values.enabled.push(id);
|
|
@@ -56,6 +57,7 @@ export const PluginHost = ({
|
|
|
56
57
|
},
|
|
57
58
|
});
|
|
58
59
|
|
|
60
|
+
// Register and load values.
|
|
59
61
|
state.prop({ key: 'enabled', type: LocalStorageStore.json<string[]>() });
|
|
60
62
|
|
|
61
63
|
return {
|
|
@@ -65,11 +67,13 @@ export const PluginHost = ({
|
|
|
65
67
|
},
|
|
66
68
|
provides: {
|
|
67
69
|
plugins: state.values,
|
|
68
|
-
context: ({ children }) =>
|
|
70
|
+
context: ({ children }) => {
|
|
71
|
+
return <PluginProvider value={state.values}>{children}</PluginProvider>;
|
|
72
|
+
},
|
|
69
73
|
root: () => {
|
|
70
74
|
return (
|
|
71
75
|
<ErrorBoundary fallback={fallback}>
|
|
72
|
-
<
|
|
76
|
+
<PluginContainer plugins={plugins} core={core} state={state.values} placeholder={placeholder} />
|
|
73
77
|
</ErrorBoundary>
|
|
74
78
|
);
|
|
75
79
|
},
|
|
@@ -77,6 +81,9 @@ export const PluginHost = ({
|
|
|
77
81
|
};
|
|
78
82
|
};
|
|
79
83
|
|
|
84
|
+
/**
|
|
85
|
+
* Fallback does not use tailwind or theme.
|
|
86
|
+
*/
|
|
80
87
|
const DefaultFallback = ({ error }: { error: Error }) => {
|
|
81
88
|
return (
|
|
82
89
|
<div style={{ padding: '1rem' }}>
|
|
@@ -86,113 +93,3 @@ const DefaultFallback = ({ error }: { error: Error }) => {
|
|
|
86
93
|
</div>
|
|
87
94
|
);
|
|
88
95
|
};
|
|
89
|
-
|
|
90
|
-
type RootProps = {
|
|
91
|
-
order: PluginDefinition['meta'][];
|
|
92
|
-
state: PluginContext;
|
|
93
|
-
definitions: Record<string, () => Promise<PluginDefinition>>;
|
|
94
|
-
core: string[];
|
|
95
|
-
placeholder: ReactNode;
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
const Root = ({ order, core: corePluginIds, definitions, state, placeholder }: RootProps) => {
|
|
99
|
-
const [error, setError] = useState<unknown>();
|
|
100
|
-
|
|
101
|
-
useEffect(() => {
|
|
102
|
-
log('initializing plugins', { enabled: state.enabled });
|
|
103
|
-
const timeout = setTimeout(async () => {
|
|
104
|
-
try {
|
|
105
|
-
const enabledIds = [...corePluginIds, ...state.enabled].sort((a, b) => {
|
|
106
|
-
const indexA = order.findIndex(({ id }) => id === a);
|
|
107
|
-
const indexB = order.findIndex(({ id }) => id === b);
|
|
108
|
-
return indexA - indexB;
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
const enabled = await Promise.all(
|
|
112
|
-
enabledIds
|
|
113
|
-
.map((id) => definitions[id])
|
|
114
|
-
// If local storage indicates a plugin is enabled, but it is not available, ignore it.
|
|
115
|
-
.filter((definition): definition is () => Promise<PluginDefinition> => Boolean(definition))
|
|
116
|
-
.map((definition) => definition()),
|
|
117
|
-
);
|
|
118
|
-
|
|
119
|
-
const plugins = await Promise.all(
|
|
120
|
-
enabled.map(async (definition) => {
|
|
121
|
-
const plugin = await initializePlugin(definition).catch((err) => {
|
|
122
|
-
log.error('Failed to initialize plugin:', { id: definition.meta.id, err });
|
|
123
|
-
return undefined;
|
|
124
|
-
});
|
|
125
|
-
return plugin;
|
|
126
|
-
}),
|
|
127
|
-
).then((plugins) => plugins.filter((plugin): plugin is Plugin => Boolean(plugin)));
|
|
128
|
-
log('plugins initialized', { plugins });
|
|
129
|
-
|
|
130
|
-
await Promise.all(enabled.map((pluginDefinition) => pluginDefinition.ready?.(plugins)));
|
|
131
|
-
log('plugins ready', { plugins });
|
|
132
|
-
|
|
133
|
-
state.plugins = plugins;
|
|
134
|
-
state.ready = true;
|
|
135
|
-
} catch (err) {
|
|
136
|
-
setError(err);
|
|
137
|
-
}
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
return () => {
|
|
141
|
-
clearTimeout(timeout);
|
|
142
|
-
state.ready = false;
|
|
143
|
-
// TODO(wittjosiah): Does this ever need to be called prior to having dynamic plugins?
|
|
144
|
-
// void Promise.all(enabled.map((definition) => definition.unload?.()));
|
|
145
|
-
};
|
|
146
|
-
}, []);
|
|
147
|
-
|
|
148
|
-
if (error) {
|
|
149
|
-
throw error;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
if (!state.ready) {
|
|
153
|
-
return <>{placeholder}</>;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const ComposedContext = composeContext(state.plugins);
|
|
157
|
-
|
|
158
|
-
return <ComposedContext>{rootComponents(state.plugins)}</ComposedContext>;
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Resolve a `PluginDefinition` into a fully initialized `Plugin`.
|
|
163
|
-
*/
|
|
164
|
-
export const initializePlugin = async <T, U>(pluginDefinition: PluginDefinition<T, U>): Promise<Plugin<T & U>> => {
|
|
165
|
-
const provides = await pluginDefinition.initialize?.();
|
|
166
|
-
return {
|
|
167
|
-
...pluginDefinition,
|
|
168
|
-
provides: {
|
|
169
|
-
...pluginDefinition.provides,
|
|
170
|
-
...provides,
|
|
171
|
-
} as PluginProvides<T & U>,
|
|
172
|
-
};
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
const rootComponents = (plugins: Plugin[]) => {
|
|
176
|
-
return plugins
|
|
177
|
-
.map((plugin) => {
|
|
178
|
-
const Component = plugin.provides.root;
|
|
179
|
-
if (Component) {
|
|
180
|
-
return <Component key={plugin.meta.id} />;
|
|
181
|
-
} else {
|
|
182
|
-
return null;
|
|
183
|
-
}
|
|
184
|
-
})
|
|
185
|
-
.filter((node): node is JSX.Element => Boolean(node));
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
const composeContext = (plugins: Plugin[]) => {
|
|
189
|
-
return compose(plugins.map((p) => p.provides.context!).filter(Boolean));
|
|
190
|
-
};
|
|
191
|
-
|
|
192
|
-
const compose = (contexts: FC<PropsWithChildren>[]) => {
|
|
193
|
-
return [...contexts].reduce((Acc, Next) => ({ children }) => (
|
|
194
|
-
<Acc>
|
|
195
|
-
<Next>{children}</Next>
|
|
196
|
-
</Acc>
|
|
197
|
-
));
|
|
198
|
-
};
|
|
@@ -2,11 +2,10 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { expect } from 'chai';
|
|
6
5
|
import { type FC } from 'react';
|
|
6
|
+
import { describe, expect, test } from 'vitest';
|
|
7
7
|
|
|
8
8
|
import { log } from '@dxos/log';
|
|
9
|
-
import { describe, test } from '@dxos/test';
|
|
10
9
|
|
|
11
10
|
import { type Plugin } from './plugin';
|
|
12
11
|
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import type { IconProps } from '@phosphor-icons/react';
|
|
6
5
|
import type { FC, PropsWithChildren } from 'react';
|
|
7
6
|
|
|
8
7
|
/**
|
|
@@ -21,60 +20,56 @@ export type PluginProvides<TProvides> = TProvides & {
|
|
|
21
20
|
root?: FC<PropsWithChildren>;
|
|
22
21
|
};
|
|
23
22
|
|
|
23
|
+
export type PluginMeta = {
|
|
24
|
+
/**
|
|
25
|
+
* Globally unique ID.
|
|
26
|
+
*
|
|
27
|
+
* Expected to be in the form of a valid URL.
|
|
28
|
+
*
|
|
29
|
+
* @example dxos.org/plugin/example
|
|
30
|
+
*/
|
|
31
|
+
id: string;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Short ID for use in URLs.
|
|
35
|
+
*
|
|
36
|
+
* NOTE: This is especially experimental and likely to change.
|
|
37
|
+
*/
|
|
38
|
+
// TODO(wittjosiah): How should these be managed?
|
|
39
|
+
shortId?: string;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Human-readable name.
|
|
43
|
+
*/
|
|
44
|
+
name?: string;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Short description of plugin functionality.
|
|
48
|
+
*/
|
|
49
|
+
description?: string;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* URL of home page.
|
|
53
|
+
*/
|
|
54
|
+
homePage?: string;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Tags to help categorize the plugin.
|
|
58
|
+
*/
|
|
59
|
+
tags?: string[];
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* A grep-able symbol string which can be resolved to an icon asset by @ch-ui/icons, via @ch-ui/vite-plugin-icons.
|
|
63
|
+
*/
|
|
64
|
+
icon?: string;
|
|
65
|
+
};
|
|
66
|
+
|
|
24
67
|
/**
|
|
25
68
|
* A unit of containment of modular functionality that can be provided to an application.
|
|
26
69
|
* Plugins provide things like components, state, actions, etc. to the application.
|
|
27
70
|
*/
|
|
28
71
|
export type Plugin<TProvides = {}> = {
|
|
29
|
-
meta:
|
|
30
|
-
/**
|
|
31
|
-
* Globally unique ID.
|
|
32
|
-
*
|
|
33
|
-
* Expected to be in the form of a valid URL.
|
|
34
|
-
*
|
|
35
|
-
* @example dxos.org/plugin/example
|
|
36
|
-
*/
|
|
37
|
-
id: string;
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Short ID for use in URLs.
|
|
41
|
-
*
|
|
42
|
-
* NOTE: This is especially experimental and likely to change.
|
|
43
|
-
*/
|
|
44
|
-
// TODO(wittjosiah): How should these be managed?
|
|
45
|
-
shortId?: string;
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Human-readable name.
|
|
49
|
-
*/
|
|
50
|
-
name?: string;
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Short description of plugin functionality.
|
|
54
|
-
*/
|
|
55
|
-
description?: string;
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* URL of home page.
|
|
59
|
-
*/
|
|
60
|
-
homePage?: string;
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Tags to help categorize the plugin.
|
|
64
|
-
*/
|
|
65
|
-
tags?: string[];
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Component to render icon for the plugin when displayed in a list.
|
|
69
|
-
* @deprecated
|
|
70
|
-
*/
|
|
71
|
-
iconComponent?: FC<IconProps>;
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* A grep-able symbol string which can be resolved to an icon asset by @ch-ui/icons, via @ch-ui/vite-plugin-icons.
|
|
75
|
-
*/
|
|
76
|
-
iconSymbol?: string;
|
|
77
|
-
};
|
|
72
|
+
meta: PluginMeta;
|
|
78
73
|
|
|
79
74
|
/**
|
|
80
75
|
* Capabilities provided by the plugin.
|
|
@@ -114,8 +109,6 @@ export type PluginDefinition<TProvides = {}, TInitializeProvides = {}> = Omit<Pl
|
|
|
114
109
|
unload?: () => Promise<void>;
|
|
115
110
|
};
|
|
116
111
|
|
|
117
|
-
export const pluginMeta = (meta: Plugin['meta']) => meta;
|
|
118
|
-
|
|
119
112
|
type LazyPlugin<T> = () => Promise<{ default: (props: T) => PluginDefinition }>;
|
|
120
113
|
|
|
121
114
|
export namespace Plugin {
|
|
@@ -3,15 +3,19 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import React, {
|
|
6
|
-
forwardRef,
|
|
7
|
-
type ReactNode,
|
|
8
6
|
Fragment,
|
|
9
7
|
type ForwardedRef,
|
|
10
8
|
type PropsWithChildren,
|
|
11
|
-
|
|
9
|
+
type ReactNode,
|
|
12
10
|
Suspense,
|
|
11
|
+
createContext,
|
|
12
|
+
forwardRef,
|
|
13
|
+
isValidElement,
|
|
14
|
+
memo,
|
|
15
|
+
useContext,
|
|
16
|
+
useEffect,
|
|
17
|
+
useState,
|
|
13
18
|
} from 'react';
|
|
14
|
-
import { createContext, useContext } from 'react';
|
|
15
19
|
|
|
16
20
|
import { raise } from '@dxos/debug';
|
|
17
21
|
|
|
@@ -81,9 +85,23 @@ export type SurfaceProps = PropsWithChildren<{
|
|
|
81
85
|
/**
|
|
82
86
|
* A surface is a named region of the screen that can be populated by plugins.
|
|
83
87
|
*/
|
|
84
|
-
export const Surface =
|
|
85
|
-
({ role, name = role, fallback, placeholder, ...rest }, forwardedRef) => {
|
|
88
|
+
export const Surface = memo(
|
|
89
|
+
forwardRef<HTMLElement, SurfaceProps>(({ role, name = role, fallback, placeholder, ...rest }, forwardedRef) => {
|
|
86
90
|
const props = { role, name, fallback, ...rest };
|
|
91
|
+
const { debugInfo } = useSurfaceRoot();
|
|
92
|
+
|
|
93
|
+
// Track debug info.
|
|
94
|
+
const [id] = useState<string>(Math.random().toString(36).slice(2));
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
debugInfo?.set(id, { id, created: Date.now(), name, role, renderCount: 0 });
|
|
97
|
+
return () => {
|
|
98
|
+
debugInfo?.delete(id);
|
|
99
|
+
};
|
|
100
|
+
}, [id]);
|
|
101
|
+
if (debugInfo?.get(id)) {
|
|
102
|
+
debugInfo.get(id)!.renderCount++;
|
|
103
|
+
}
|
|
104
|
+
|
|
87
105
|
const context = useContext(SurfaceContext);
|
|
88
106
|
const data = props.data ?? ((name && context?.surfaces?.[name]?.data) || {});
|
|
89
107
|
|
|
@@ -97,10 +115,10 @@ export const Surface = forwardRef<HTMLElement, SurfaceProps>(
|
|
|
97
115
|
) : (
|
|
98
116
|
suspense
|
|
99
117
|
);
|
|
100
|
-
},
|
|
118
|
+
}),
|
|
101
119
|
);
|
|
102
120
|
|
|
103
|
-
const SurfaceContext = createContext<SurfaceProps |
|
|
121
|
+
const SurfaceContext = createContext<SurfaceProps | undefined>(undefined);
|
|
104
122
|
|
|
105
123
|
export const useSurface = (): SurfaceProps =>
|
|
106
124
|
useContext(SurfaceContext) ?? raise(new Error('Surface context not found'));
|
|
@@ -120,10 +138,13 @@ const SurfaceResolver = forwardRef<HTMLElement, SurfaceProps>((props, forwardedR
|
|
|
120
138
|
return <SurfaceContext.Provider value={currentContext}>{nodes}</SurfaceContext.Provider>;
|
|
121
139
|
});
|
|
122
140
|
|
|
141
|
+
/**
|
|
142
|
+
* Resolve surface nodes from across all component.
|
|
143
|
+
*/
|
|
123
144
|
const resolveNodes = (
|
|
124
145
|
components: Record<string, SurfaceComponent>,
|
|
125
146
|
props: SurfaceProps,
|
|
126
|
-
context: SurfaceProps |
|
|
147
|
+
context: SurfaceProps | undefined,
|
|
127
148
|
forwardedRef: ForwardedRef<HTMLElement>,
|
|
128
149
|
): ReactNode[] => {
|
|
129
150
|
const data = {
|
|
@@ -144,7 +165,6 @@ const resolveNodes = (
|
|
|
144
165
|
.sort(([, a], [, b]) => {
|
|
145
166
|
const aDisposition = a.disposition ?? 'default';
|
|
146
167
|
const bDisposition = b.disposition ?? 'default';
|
|
147
|
-
|
|
148
168
|
if (aDisposition === bDisposition) {
|
|
149
169
|
return 0;
|
|
150
170
|
} else if (aDisposition === 'hoist' || bDisposition === 'fallback') {
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { createContext, useContext, type
|
|
5
|
+
import { createContext, useContext, type JSX, type ForwardedRef } from 'react';
|
|
6
|
+
|
|
7
|
+
import { raise } from '@dxos/debug';
|
|
6
8
|
|
|
7
9
|
import { type SurfaceProps } from './Surface';
|
|
8
10
|
|
|
@@ -18,6 +20,16 @@ type SurfaceComponentProps = WithRequiredProperty<SurfaceProps, 'data'>;
|
|
|
18
20
|
*/
|
|
19
21
|
export type SurfaceDisposition = 'hoist' | 'fallback';
|
|
20
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Surface debug info.
|
|
25
|
+
* NOTE: Short-term measure to track perf issues.
|
|
26
|
+
*/
|
|
27
|
+
export type DebugInfo = {
|
|
28
|
+
id: string;
|
|
29
|
+
created: number;
|
|
30
|
+
renderCount: number;
|
|
31
|
+
} & Pick<SurfaceProps, 'role' | 'name'>;
|
|
32
|
+
|
|
21
33
|
export type SurfaceResult = {
|
|
22
34
|
node: JSX.Element;
|
|
23
35
|
disposition?: SurfaceDisposition;
|
|
@@ -35,10 +47,15 @@ export type SurfaceComponent = (
|
|
|
35
47
|
|
|
36
48
|
export type SurfaceRootContext = {
|
|
37
49
|
components: Record<string, SurfaceComponent>;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Debug info.
|
|
53
|
+
*/
|
|
54
|
+
debugInfo?: Map<string, DebugInfo>;
|
|
38
55
|
};
|
|
39
56
|
|
|
40
|
-
const SurfaceRootContext
|
|
57
|
+
const SurfaceRootContext = createContext<SurfaceRootContext | undefined>(undefined);
|
|
41
58
|
|
|
42
|
-
export const useSurfaceRoot = () => useContext(SurfaceRootContext);
|
|
59
|
+
export const useSurfaceRoot = () => useContext(SurfaceRootContext) ?? raise(new Error('Missing SurfaceRootContext'));
|
|
43
60
|
|
|
44
|
-
export const SurfaceProvider
|
|
61
|
+
export const SurfaceProvider = SurfaceRootContext.Provider;
|
|
@@ -9,14 +9,14 @@ import { create } from '@dxos/echo-schema';
|
|
|
9
9
|
import { SurfaceProvider, type SurfaceRootContext } from './SurfaceRootContext';
|
|
10
10
|
import SurfaceMeta from './meta';
|
|
11
11
|
import { parseSurfacePlugin, type SurfacePluginProvides } from './provides';
|
|
12
|
-
import type
|
|
12
|
+
import { type PluginDefinition } from '../PluginHost';
|
|
13
13
|
import { filterPlugins } from '../helpers';
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Provides a registry of surface components.
|
|
17
17
|
*/
|
|
18
18
|
const SurfacePlugin = (): PluginDefinition<SurfacePluginProvides> => {
|
|
19
|
-
const state = create<SurfaceRootContext>({ components: {} });
|
|
19
|
+
const state = create<SurfaceRootContext>({ components: {}, debugInfo: new Map() });
|
|
20
20
|
|
|
21
21
|
return {
|
|
22
22
|
meta: SurfaceMeta,
|
|
@@ -2,10 +2,9 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import { Schema as S } from '@effect/schema';
|
|
5
6
|
import { z } from 'zod';
|
|
6
7
|
|
|
7
|
-
import { S } from '@dxos/echo-schema';
|
|
8
|
-
|
|
9
8
|
import type { IntentData } from '../IntentPlugin';
|
|
10
9
|
import type { Plugin } from '../PluginHost';
|
|
11
10
|
|
|
@@ -19,7 +18,6 @@ export const SLUG_COLLECTION_INDICATOR = '';
|
|
|
19
18
|
const LayoutEntrySchema = S.mutable(S.Struct({ id: S.String, path: S.optional(S.String) }));
|
|
20
19
|
export type LayoutEntry = S.Schema.Type<typeof LayoutEntrySchema>;
|
|
21
20
|
|
|
22
|
-
// TODO(Zan): Consider making solo it's own part. It's not really a function of the 'main' part?
|
|
23
21
|
// TODO(Zan): Consider renaming the 'main' part to 'deck' part now that we are throwing out the old layout plugin.
|
|
24
22
|
// TODO(Zan): Extend to all strings?
|
|
25
23
|
const LayoutPartSchema = S.Union(
|
|
@@ -31,7 +29,9 @@ const LayoutPartSchema = S.Union(
|
|
|
31
29
|
);
|
|
32
30
|
export type LayoutPart = S.Schema.Type<typeof LayoutPartSchema>;
|
|
33
31
|
|
|
34
|
-
const LayoutPartsSchema = S.partial(
|
|
32
|
+
const LayoutPartsSchema = S.partial(
|
|
33
|
+
S.mutable(S.Record({ key: LayoutPartSchema, value: S.mutable(S.Array(LayoutEntrySchema)) })),
|
|
34
|
+
);
|
|
35
35
|
export type LayoutParts = S.Schema.Type<typeof LayoutPartsSchema>;
|
|
36
36
|
|
|
37
37
|
const LayoutCoordinateSchema = S.mutable(S.Struct({ part: LayoutPartSchema, entryId: S.String }));
|
|
@@ -52,14 +52,6 @@ export type ActiveParts = z.infer<typeof ActiveParts>;
|
|
|
52
52
|
// TODO(burdon): Where should this go?
|
|
53
53
|
export type LayoutContainerProps<T> = T & { role?: string; coordinate?: LayoutCoordinate };
|
|
54
54
|
|
|
55
|
-
/**
|
|
56
|
-
* Basic state provided by a navigation plugin.
|
|
57
|
-
*/
|
|
58
|
-
export const Attention = z.object({
|
|
59
|
-
attended: z.set(z.string()).optional().describe('Ids of items which have focus.'),
|
|
60
|
-
});
|
|
61
|
-
export type Attention = z.infer<typeof Attention>;
|
|
62
|
-
|
|
63
55
|
/**
|
|
64
56
|
* Provides for a plugin that can manage the app navigation.
|
|
65
57
|
*/
|
package/tsconfig.json
CHANGED
|
@@ -16,35 +16,7 @@
|
|
|
16
16
|
"include": [
|
|
17
17
|
"src"
|
|
18
18
|
],
|
|
19
|
-
"references": [
|
|
20
|
-
{
|
|
21
|
-
"path": "../../common/async"
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
"path": "../../common/debug"
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
"path": "../../common/invariant"
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
"path": "../../common/local-storage"
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
"path": "../../common/log"
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
"path": "../../common/util"
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
"path": "../../core/echo/echo-schema"
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
"path": "../app-graph"
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
"path": "../client-protocol"
|
|
46
|
-
}
|
|
47
|
-
],
|
|
19
|
+
"references": [],
|
|
48
20
|
"ts-node": {
|
|
49
21
|
"compilerOptions": {
|
|
50
22
|
"module": "CommonJS"
|
package/vitest.config.ts
ADDED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../src/plugins/SurfacePlugin/provides.ts", "../../../src/plugins/SurfacePlugin/SurfaceRootContext.tsx", "../../../src/plugins/SurfacePlugin/meta.ts"],
|
|
4
|
-
"sourcesContent": ["//\n// Copyright 2023 DXOS.org\n//\n\nimport { type SurfaceComponent, type SurfaceRootContext } from './SurfaceRootContext';\nimport { type Plugin } from '../PluginHost';\n\nexport type SurfaceProvides = {\n surface: {\n /**\n * Used by the `Surface` resolver to find a component to render.\n */\n component: SurfaceComponent;\n };\n};\n\nexport type SurfacePluginProvides = {\n surface: SurfaceRootContext;\n};\n\nexport const parseRootSurfacePlugin = (plugin?: Plugin) =>\n (plugin?.provides as any)?.surface?.components ? (plugin as Plugin<SurfacePluginProvides>) : undefined;\n\nexport const parseSurfacePlugin = (plugin?: Plugin) =>\n (plugin?.provides as any)?.surface?.component ? (plugin as Plugin<SurfaceProvides>) : undefined;\n", "//\n// Copyright 2023 DXOS.org\n//\n\nimport { createContext, useContext, type Context, type JSX, type Provider, type ForwardedRef } from 'react';\n\nimport { type SurfaceProps } from './Surface';\n\n// TODO(wittjosiah): Factor out.\ntype WithRequiredProperty<Type, Key extends keyof Type> = Type & {\n [Property in Key]-?: Type[Property];\n};\n\ntype SurfaceComponentProps = WithRequiredProperty<SurfaceProps, 'data'>;\n\n/**\n * Determines the priority of the surface when multiple components are resolved.\n */\nexport type SurfaceDisposition = 'hoist' | 'fallback';\n\nexport type SurfaceResult = {\n node: JSX.Element;\n disposition?: SurfaceDisposition;\n};\n\n/**\n * Function which resolves a Surface.\n *\n * If a null value is returned, the rendering is deferred to other plugins.\n */\nexport type SurfaceComponent = (\n props: SurfaceComponentProps,\n forwardedRef: ForwardedRef<HTMLElement>,\n) => JSX.Element | SurfaceResult | null;\n\nexport type SurfaceRootContext = {\n components: Record<string, SurfaceComponent>;\n};\n\nconst SurfaceRootContext: Context<SurfaceRootContext> = createContext<SurfaceRootContext>({ components: {} });\n\nexport const useSurfaceRoot = () => useContext(SurfaceRootContext);\n\nexport const SurfaceProvider: Provider<SurfaceRootContext> = SurfaceRootContext.Provider;\n", "//\n// Copyright 2023 DXOS.org\n//\n\nconst SurfaceMeta = {\n id: 'dxos.org/plugin/surface',\n};\n\nexport default SurfaceMeta;\n"],
|
|
5
|
-
"mappings": ";AAoBO,IAAMA,yBAAyB,CAACC,WACpCA,QAAQC,UAAkBC,SAASC,aAAcH,SAA2CI;AAExF,IAAMC,qBAAqB,CAACL,WAChCA,QAAQC,UAAkBC,SAASI,YAAaN,SAAqCI;;;ACpBxF,SAASG,eAAeC,kBAA4E;AAmCpG,IAAMC,qBAAkDC,8BAAkC;EAAEC,YAAY,CAAC;AAAE,CAAA;AAEpG,IAAMC,iBAAiB,MAAMC,WAAWJ,kBAAAA;AAExC,IAAMK,kBAAgDL,mBAAmBM;;;ACvChF,IAAMC,cAAc;EAClBC,IAAI;AACN;AAEA,IAAA,eAAeD;",
|
|
6
|
-
"names": ["parseRootSurfacePlugin", "plugin", "provides", "surface", "components", "undefined", "parseSurfacePlugin", "component", "createContext", "useContext", "SurfaceRootContext", "createContext", "components", "useSurfaceRoot", "useContext", "SurfaceProvider", "Provider", "SurfaceMeta", "id"]
|
|
7
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../src/plugins/SurfacePlugin/plugin.tsx"],
|
|
4
|
-
"sourcesContent": ["//\n// Copyright 2023 DXOS.org\n//\n\nimport React from 'react';\n\nimport { create } from '@dxos/echo-schema';\n\nimport { SurfaceProvider, type SurfaceRootContext } from './SurfaceRootContext';\nimport SurfaceMeta from './meta';\nimport { parseSurfacePlugin, type SurfacePluginProvides } from './provides';\nimport type { PluginDefinition } from '../PluginHost';\nimport { filterPlugins } from '../helpers';\n\n/**\n * Provides a registry of surface components.\n */\nconst SurfacePlugin = (): PluginDefinition<SurfacePluginProvides> => {\n const state = create<SurfaceRootContext>({ components: {} });\n\n return {\n meta: SurfaceMeta,\n ready: async (plugins) => {\n state.components = filterPlugins(plugins, parseSurfacePlugin).reduce((acc, plugin) => {\n return { ...acc, [plugin.meta.id]: plugin.provides.surface.component };\n }, {});\n },\n provides: {\n surface: state,\n context: ({ children }) => <SurfaceProvider value={state}>{children}</SurfaceProvider>,\n },\n };\n};\n\nexport default SurfacePlugin;\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;AAIA,OAAOA,WAAW;AAElB,SAASC,cAAc;AAWvB,IAAMC,gBAAgB,MAAA;AACpB,QAAMC,QAAQC,OAA2B;IAAEC,YAAY,CAAC;EAAE,CAAA;AAE1D,SAAO;IACLC,MAAMC;IACNC,OAAO,OAAOC,YAAAA;AACZN,YAAME,aAAaK,cAAcD,SAASE,kBAAAA,EAAoBC,OAAO,CAACC,KAAKC,WAAAA;AACzE,eAAO;UAAE,GAAGD;UAAK,CAACC,OAAOR,KAAKS,EAAE,GAAGD,OAAOE,SAASC,QAAQC;QAAU;MACvE,GAAG,CAAC,CAAA;IACN;IACAF,UAAU;MACRC,SAASd;MACTgB,SAAS,CAAC,EAAEC,SAAQ,MAAO,sBAAA,cAACC,iBAAAA;QAAgBC,OAAOnB;SAAQiB,QAAAA;IAC7D;EACF;AACF;AAEA,IAAA,iBAAelB;",
|
|
6
|
-
"names": ["React", "create", "SurfacePlugin", "state", "create", "components", "meta", "SurfaceMeta", "ready", "plugins", "filterPlugins", "parseSurfacePlugin", "reduce", "acc", "plugin", "id", "provides", "surface", "component", "context", "children", "SurfaceProvider", "value"]
|
|
7
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../src/plugins/SurfacePlugin/provides.ts", "../../../src/plugins/SurfacePlugin/SurfaceRootContext.tsx", "../../../src/plugins/SurfacePlugin/meta.ts"],
|
|
4
|
-
"sourcesContent": ["//\n// Copyright 2023 DXOS.org\n//\n\nimport { type SurfaceComponent, type SurfaceRootContext } from './SurfaceRootContext';\nimport { type Plugin } from '../PluginHost';\n\nexport type SurfaceProvides = {\n surface: {\n /**\n * Used by the `Surface` resolver to find a component to render.\n */\n component: SurfaceComponent;\n };\n};\n\nexport type SurfacePluginProvides = {\n surface: SurfaceRootContext;\n};\n\nexport const parseRootSurfacePlugin = (plugin?: Plugin) =>\n (plugin?.provides as any)?.surface?.components ? (plugin as Plugin<SurfacePluginProvides>) : undefined;\n\nexport const parseSurfacePlugin = (plugin?: Plugin) =>\n (plugin?.provides as any)?.surface?.component ? (plugin as Plugin<SurfaceProvides>) : undefined;\n", "//\n// Copyright 2023 DXOS.org\n//\n\nimport { createContext, useContext, type Context, type JSX, type Provider, type ForwardedRef } from 'react';\n\nimport { type SurfaceProps } from './Surface';\n\n// TODO(wittjosiah): Factor out.\ntype WithRequiredProperty<Type, Key extends keyof Type> = Type & {\n [Property in Key]-?: Type[Property];\n};\n\ntype SurfaceComponentProps = WithRequiredProperty<SurfaceProps, 'data'>;\n\n/**\n * Determines the priority of the surface when multiple components are resolved.\n */\nexport type SurfaceDisposition = 'hoist' | 'fallback';\n\nexport type SurfaceResult = {\n node: JSX.Element;\n disposition?: SurfaceDisposition;\n};\n\n/**\n * Function which resolves a Surface.\n *\n * If a null value is returned, the rendering is deferred to other plugins.\n */\nexport type SurfaceComponent = (\n props: SurfaceComponentProps,\n forwardedRef: ForwardedRef<HTMLElement>,\n) => JSX.Element | SurfaceResult | null;\n\nexport type SurfaceRootContext = {\n components: Record<string, SurfaceComponent>;\n};\n\nconst SurfaceRootContext: Context<SurfaceRootContext> = createContext<SurfaceRootContext>({ components: {} });\n\nexport const useSurfaceRoot = () => useContext(SurfaceRootContext);\n\nexport const SurfaceProvider: Provider<SurfaceRootContext> = SurfaceRootContext.Provider;\n", "//\n// Copyright 2023 DXOS.org\n//\n\nconst SurfaceMeta = {\n id: 'dxos.org/plugin/surface',\n};\n\nexport default SurfaceMeta;\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;ACIA,mBAAoG;ADgB7F,IAAMA,yBAAyB,CAACC,WACpCA,QAAQC,UAAkBC,SAASC,aAAcH,SAA2CI;AAExF,IAAMC,qBAAqB,CAACL,WAChCA,QAAQC,UAAkBC,SAASI,YAAaN,SAAqCI;ACexF,IAAMG,qBAAkDC,gDAAkC;EAAEL,YAAY,CAAC;AAAE,CAAA;AAEpG,IAAMM,iBAAiB,UAAMC,yBAAWH,kBAAAA;AAExC,IAAMI,kBAAgDJ,mBAAmBK;ACvChF,IAAMC,cAAc;EAClBC,IAAI;AACN;AAEA,IAAA,eAAeD;",
|
|
6
|
-
"names": ["parseRootSurfacePlugin", "plugin", "provides", "surface", "components", "undefined", "parseSurfacePlugin", "component", "SurfaceRootContext", "createContext", "useSurfaceRoot", "useContext", "SurfaceProvider", "Provider", "SurfaceMeta", "id"]
|
|
7
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../src/plugins/SurfacePlugin/plugin.tsx"],
|
|
4
|
-
"sourcesContent": ["//\n// Copyright 2023 DXOS.org\n//\n\nimport React from 'react';\n\nimport { create } from '@dxos/echo-schema';\n\nimport { SurfaceProvider, type SurfaceRootContext } from './SurfaceRootContext';\nimport SurfaceMeta from './meta';\nimport { parseSurfacePlugin, type SurfacePluginProvides } from './provides';\nimport type { PluginDefinition } from '../PluginHost';\nimport { filterPlugins } from '../helpers';\n\n/**\n * Provides a registry of surface components.\n */\nconst SurfacePlugin = (): PluginDefinition<SurfacePluginProvides> => {\n const state = create<SurfaceRootContext>({ components: {} });\n\n return {\n meta: SurfaceMeta,\n ready: async (plugins) => {\n state.components = filterPlugins(plugins, parseSurfacePlugin).reduce((acc, plugin) => {\n return { ...acc, [plugin.meta.id]: plugin.provides.surface.component };\n }, {});\n },\n provides: {\n surface: state,\n context: ({ children }) => <SurfaceProvider value={state}>{children}</SurfaceProvider>,\n },\n };\n};\n\nexport default SurfacePlugin;\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAIA,mBAAkB;AAElB,yBAAuB;AAWvB,IAAMA,gBAAgB,MAAA;AACpB,QAAMC,YAAQC,2BAA2B;IAAEC,YAAY,CAAC;EAAE,CAAA;AAE1D,SAAO;IACLC,MAAMC;IACNC,OAAO,OAAOC,YAAAA;AACZN,YAAME,iBAAaK,qCAAcD,SAASE,wCAAAA,EAAoBC,OAAO,CAACC,KAAKC,WAAAA;AACzE,eAAO;UAAE,GAAGD;UAAK,CAACC,OAAOR,KAAKS,EAAE,GAAGD,OAAOE,SAASC,QAAQC;QAAU;MACvE,GAAG,CAAC,CAAA;IACN;IACAF,UAAU;MACRC,SAASd;MACTgB,SAAS,CAAC,EAAEC,SAAQ,MAAO,6BAAAC,QAAA,cAACC,uCAAAA;QAAgBC,OAAOpB;SAAQiB,QAAAA;IAC7D;EACF;AACF;AAEA,IAAA,iBAAelB;",
|
|
6
|
-
"names": ["SurfacePlugin", "state", "create", "components", "meta", "SurfaceMeta", "ready", "plugins", "filterPlugins", "parseSurfacePlugin", "reduce", "acc", "plugin", "id", "provides", "surface", "component", "context", "children", "React", "SurfaceProvider", "value"]
|
|
7
|
-
}
|