@dxos/app-framework 0.8.4-staging.60fe92afc8 → 0.9.1-main.c7dcc2e112
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/{capability-BOPAKKWG.mjs → capability-Z4MRP3CP.mjs} +9 -9
- package/dist/lib/browser/{chunk-CO3XLNUX.mjs → chunk-37PG7XJC.mjs} +18 -18
- package/dist/lib/browser/chunk-37PG7XJC.mjs.map +7 -0
- package/dist/lib/browser/{chunk-66IXTIVK.mjs → chunk-3NQLTQCP.mjs} +6 -7
- package/dist/lib/browser/chunk-3NQLTQCP.mjs.map +7 -0
- package/dist/lib/browser/{chunk-KZUDO43J.mjs → chunk-3UTDRCKX.mjs} +31 -23
- package/dist/lib/browser/chunk-3UTDRCKX.mjs.map +7 -0
- package/dist/lib/browser/chunk-53HJHMS5.mjs +7 -0
- package/dist/lib/browser/chunk-53HJHMS5.mjs.map +7 -0
- package/dist/lib/browser/{chunk-IW44C7UL.mjs → chunk-CMINFVA5.mjs} +1 -1
- package/dist/lib/browser/chunk-CMINFVA5.mjs.map +7 -0
- package/dist/lib/browser/{chunk-Y66ELD4U.mjs → chunk-DFVSOBV2.mjs} +17 -7
- package/dist/lib/browser/chunk-DFVSOBV2.mjs.map +7 -0
- package/dist/lib/browser/{chunk-DC3WRPBV.mjs → chunk-S2ZBYQPI.mjs} +45 -12
- package/dist/lib/browser/chunk-S2ZBYQPI.mjs.map +7 -0
- package/dist/lib/browser/{chunk-HFLKXMT7.mjs → chunk-SWECIICH.mjs} +57 -41
- package/dist/lib/browser/chunk-SWECIICH.mjs.map +7 -0
- package/dist/lib/browser/{chunk-5GY3YOEL.mjs → chunk-VF37YTXJ.mjs} +5 -5
- package/dist/lib/browser/chunk-VF37YTXJ.mjs.map +7 -0
- package/dist/lib/browser/{chunk-XHS5KDNX.mjs → chunk-W4BHZING.mjs} +25 -25
- package/dist/lib/browser/chunk-W4BHZING.mjs.map +7 -0
- package/dist/lib/browser/{chunk-FO3IYSLV.mjs → chunk-YUVQIOTB.mjs} +29 -37
- package/dist/lib/browser/chunk-YUVQIOTB.mjs.map +7 -0
- package/dist/lib/browser/cli/index.mjs +9 -9
- package/dist/lib/browser/cli/index.mjs.map +2 -2
- package/dist/lib/browser/common/activation-events.mjs +7 -7
- package/dist/lib/browser/common/capabilities.mjs +7 -7
- package/dist/lib/browser/config/index.mjs +8 -0
- package/dist/lib/browser/config/index.mjs.map +7 -0
- package/dist/lib/browser/core/activation-event.mjs +1 -1
- package/dist/lib/browser/core/capability.mjs +1 -1
- package/dist/lib/browser/core/plugin-manager.mjs +4 -4
- package/dist/lib/browser/core/plugin.mjs +12 -2
- package/dist/lib/browser/core/url-loader.mjs +3 -3
- package/dist/lib/browser/index.mjs +14 -10
- package/dist/lib/browser/index.mjs.map +1 -1
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/{process-manager-capability-GXNUO7AR.mjs → process-manager-capability-RDROCCQD.mjs} +9 -9
- package/dist/lib/browser/testing/index.mjs +15 -15
- package/dist/lib/browser/testing/index.mjs.map +3 -3
- package/dist/lib/browser/testing/react.mjs +11 -11
- package/dist/lib/browser/testing/react.mjs.map +3 -3
- package/dist/lib/browser/ui/index.mjs +16 -10
- package/dist/lib/node-esm/{capability-KZWRTX7R.mjs → capability-34Z6NPVR.mjs} +9 -9
- package/dist/lib/node-esm/chunk-2UPUZXGU.mjs +9 -0
- package/dist/lib/node-esm/chunk-2UPUZXGU.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-FSVGKTF6.mjs → chunk-A4O42ZO3.mjs} +17 -7
- package/dist/lib/node-esm/chunk-A4O42ZO3.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-CTKEZHKF.mjs → chunk-AIDZMJ3U.mjs} +1 -1
- package/dist/lib/node-esm/chunk-AIDZMJ3U.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-XOCUANHO.mjs → chunk-BMW6P6QA.mjs} +6 -7
- package/dist/lib/node-esm/chunk-BMW6P6QA.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-A5CHGAZE.mjs → chunk-H7T7XXLS.mjs} +57 -41
- package/dist/lib/node-esm/chunk-H7T7XXLS.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-NULBHQYS.mjs → chunk-LUK3FCTH.mjs} +18 -18
- package/dist/lib/node-esm/chunk-LUK3FCTH.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-XGBUKRCK.mjs → chunk-TEQSMP2O.mjs} +31 -23
- package/dist/lib/node-esm/chunk-TEQSMP2O.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-KFZEB6BV.mjs → chunk-TLKYZKPW.mjs} +5 -5
- package/dist/lib/node-esm/chunk-TLKYZKPW.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-WK7OIQKI.mjs → chunk-VCPGX7WN.mjs} +29 -37
- package/dist/lib/node-esm/chunk-VCPGX7WN.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-XIGPWXCQ.mjs → chunk-WERVO5EK.mjs} +45 -12
- package/dist/lib/node-esm/chunk-WERVO5EK.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-SX5NVETP.mjs → chunk-ZRTG4TSS.mjs} +25 -25
- package/dist/lib/node-esm/chunk-ZRTG4TSS.mjs.map +7 -0
- package/dist/lib/node-esm/cli/index.mjs +9 -9
- package/dist/lib/node-esm/cli/index.mjs.map +2 -2
- package/dist/lib/node-esm/common/activation-events.mjs +7 -7
- package/dist/lib/node-esm/common/capabilities.mjs +7 -7
- package/dist/lib/node-esm/config/index.mjs +9 -0
- package/dist/lib/node-esm/config/index.mjs.map +7 -0
- package/dist/lib/node-esm/core/activation-event.mjs +1 -1
- package/dist/lib/node-esm/core/capability.mjs +1 -1
- package/dist/lib/node-esm/core/plugin-manager.mjs +4 -4
- package/dist/lib/node-esm/core/plugin.mjs +12 -2
- package/dist/lib/node-esm/core/url-loader.mjs +3 -3
- package/dist/lib/node-esm/index.mjs +14 -10
- package/dist/lib/node-esm/index.mjs.map +1 -1
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/{process-manager-capability-TX5TA6YN.mjs → process-manager-capability-6OCXSKUW.mjs} +9 -9
- package/dist/lib/node-esm/testing/index.mjs +15 -15
- package/dist/lib/node-esm/testing/index.mjs.map +3 -3
- package/dist/lib/node-esm/testing/react.mjs +11 -11
- package/dist/lib/node-esm/testing/react.mjs.map +3 -3
- package/dist/lib/node-esm/ui/index.mjs +16 -10
- package/dist/plugin/node-esm/index.mjs +406 -71
- package/dist/plugin/node-esm/index.mjs.map +4 -4
- package/dist/plugin/node-esm/meta.json +1 -1
- package/dist/types/src/cli/cli.d.ts +1 -1
- package/dist/types/src/common/activation-events.d.ts.map +1 -1
- package/dist/types/src/common/capabilities.d.ts.map +1 -1
- package/dist/types/src/config/index.d.ts +2 -0
- package/dist/types/src/config/index.d.ts.map +1 -0
- package/dist/types/src/core/activation-event.d.ts +9 -6
- package/dist/types/src/core/activation-event.d.ts.map +1 -1
- package/dist/types/src/core/capability.d.ts +6 -2
- package/dist/types/src/core/capability.d.ts.map +1 -1
- package/dist/types/src/core/edge-registry-plugin-provider.d.ts +6 -11
- package/dist/types/src/core/edge-registry-plugin-provider.d.ts.map +1 -1
- package/dist/types/src/core/plugin-manager.d.ts.map +1 -1
- package/dist/types/src/core/plugin-manifest.d.ts +25 -7
- package/dist/types/src/core/plugin-manifest.d.ts.map +1 -1
- package/dist/types/src/core/plugin.d.ts +81 -91
- package/dist/types/src/core/plugin.d.ts.map +1 -1
- package/dist/types/src/core/registry.d.ts +17 -57
- package/dist/types/src/core/registry.d.ts.map +1 -1
- package/dist/types/src/core/url-loader.d.ts +2 -2
- package/dist/types/src/core/url-loader.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/plugin-process-manager/meta.d.ts +28 -2
- package/dist/types/src/plugin-process-manager/meta.d.ts.map +1 -1
- package/dist/types/src/testing/harness.d.ts +1 -1
- package/dist/types/src/testing/harness.d.ts.map +1 -1
- package/dist/types/src/testing/react.d.ts +6 -5
- package/dist/types/src/testing/react.d.ts.map +1 -1
- package/dist/types/src/testing/withPluginManager.d.ts.map +1 -1
- package/dist/types/src/testing/withPluginManager.stories.d.ts.map +1 -1
- package/dist/types/src/ui/components/PluginManager/PluginManagerProvider.d.ts +6 -0
- package/dist/types/src/ui/components/PluginManager/PluginManagerProvider.d.ts.map +1 -1
- package/dist/types/src/ui/components/Surface/SurfaceComponent.d.ts +6 -10
- package/dist/types/src/ui/components/Surface/SurfaceComponent.d.ts.map +1 -1
- package/dist/types/src/ui/components/Surface/SurfaceComponent.stories.d.ts.map +1 -1
- package/dist/types/src/ui/components/Surface/context.d.ts +6 -2
- package/dist/types/src/ui/components/Surface/context.d.ts.map +1 -1
- package/dist/types/src/ui/components/Surface/index.d.ts +10 -3
- package/dist/types/src/ui/components/Surface/index.d.ts.map +1 -1
- package/dist/types/src/ui/components/Surface/types.d.ts +50 -70
- package/dist/types/src/ui/components/Surface/types.d.ts.map +1 -1
- package/dist/types/src/ui/hooks/useApp.d.ts +1 -1
- package/dist/types/src/ui/hooks/useCapabilities.d.ts +11 -1
- package/dist/types/src/ui/hooks/useCapabilities.d.ts.map +1 -1
- package/dist/types/src/vite-plugin/composer/index.d.ts +11 -4
- package/dist/types/src/vite-plugin/composer/index.d.ts.map +1 -1
- package/dist/types/src/vite-plugin/index.d.ts +1 -0
- package/dist/types/src/vite-plugin/index.d.ts.map +1 -1
- package/dist/types/src/vite-plugin/load.d.ts +16 -0
- package/dist/types/src/vite-plugin/load.d.ts.map +1 -0
- package/dist/types/src/vite-plugin/manifest.d.ts +16 -4
- package/dist/types/src/vite-plugin/manifest.d.ts.map +1 -1
- package/dist/types/src/vite-plugin/packages.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/docs/registry-spec.md +331 -0
- package/moon.yml +1 -0
- package/package.json +32 -27
- package/src/cli/cli.ts +2 -2
- package/src/common/activation-events.ts +3 -3
- package/src/common/capabilities.ts +16 -16
- package/src/config/index.ts +5 -0
- package/src/core/activation-event.ts +15 -8
- package/src/core/capability.ts +15 -4
- package/src/core/edge-registry-plugin-provider.ts +41 -42
- package/src/core/plugin-manager.test.ts +133 -137
- package/src/core/plugin-manager.ts +25 -19
- package/src/core/plugin-manifest.test.ts +5 -5
- package/src/core/plugin-manifest.ts +13 -10
- package/src/core/plugin.ts +58 -114
- package/src/core/registry.ts +20 -60
- package/src/core/url-loader.test.ts +2 -2
- package/src/core/url-loader.ts +23 -19
- package/src/index.ts +1 -0
- package/src/testing/harness.ts +5 -3
- package/src/testing/react.test.tsx +4 -2
- package/src/testing/react.tsx +9 -8
- package/src/testing/service.ts +2 -2
- package/src/testing/withPluginManager.stories.tsx +4 -2
- package/src/testing/withPluginManager.tsx +3 -1
- package/src/ui/components/PluginManager/PluginManagerProvider.ts +7 -0
- package/src/ui/components/Surface/SurfaceComponent.stories.tsx +11 -14
- package/src/ui/components/Surface/SurfaceComponent.tsx +84 -74
- package/src/ui/components/Surface/context.ts +6 -3
- package/src/ui/components/Surface/index.ts +12 -1
- package/src/ui/components/Surface/types.test.ts +39 -49
- package/src/ui/components/Surface/types.ts +95 -60
- package/src/ui/hooks/useApp.test.tsx +1 -1
- package/src/ui/hooks/useApp.tsx +2 -2
- package/src/ui/hooks/useCapabilities.ts +24 -2
- package/src/vite-plugin/composer/index.ts +134 -61
- package/src/vite-plugin/index.ts +1 -0
- package/src/vite-plugin/load.ts +57 -0
- package/src/vite-plugin/manifest.test.ts +3 -3
- package/src/vite-plugin/manifest.ts +21 -10
- package/src/vite-plugin/packages.ts +0 -1
- package/dist/lib/browser/chunk-5GY3YOEL.mjs.map +0 -7
- package/dist/lib/browser/chunk-66IXTIVK.mjs.map +0 -7
- package/dist/lib/browser/chunk-CO3XLNUX.mjs.map +0 -7
- package/dist/lib/browser/chunk-DC3WRPBV.mjs.map +0 -7
- package/dist/lib/browser/chunk-FO3IYSLV.mjs.map +0 -7
- package/dist/lib/browser/chunk-HFLKXMT7.mjs.map +0 -7
- package/dist/lib/browser/chunk-IW44C7UL.mjs.map +0 -7
- package/dist/lib/browser/chunk-KZUDO43J.mjs.map +0 -7
- package/dist/lib/browser/chunk-XHS5KDNX.mjs.map +0 -7
- package/dist/lib/browser/chunk-Y66ELD4U.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-A5CHGAZE.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-CTKEZHKF.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-FSVGKTF6.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-KFZEB6BV.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-NULBHQYS.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-SX5NVETP.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-WK7OIQKI.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-XGBUKRCK.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-XIGPWXCQ.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-XOCUANHO.mjs.map +0 -7
- /package/dist/lib/browser/{capability-BOPAKKWG.mjs.map → capability-Z4MRP3CP.mjs.map} +0 -0
- /package/dist/lib/browser/{process-manager-capability-GXNUO7AR.mjs.map → process-manager-capability-RDROCCQD.mjs.map} +0 -0
- /package/dist/lib/node-esm/{capability-KZWRTX7R.mjs.map → capability-34Z6NPVR.mjs.map} +0 -0
- /package/dist/lib/node-esm/{process-manager-capability-TX5TA6YN.mjs.map → process-manager-capability-6OCXSKUW.mjs.map} +0 -0
package/src/core/url-loader.ts
CHANGED
|
@@ -59,7 +59,7 @@ export type PreloadOptions = Options & {
|
|
|
59
59
|
*
|
|
60
60
|
* `url` is the URL of the plugin manifest (`plugin.json`), not the entry module.
|
|
61
61
|
*/
|
|
62
|
-
export type
|
|
62
|
+
export type RemotePluginView = {
|
|
63
63
|
id: string;
|
|
64
64
|
url: string;
|
|
65
65
|
/** Installed version string, e.g. `v1.0.0`. Populated after install from a community catalog entry. */
|
|
@@ -71,22 +71,26 @@ const defaultStorage = (): Storage => ({
|
|
|
71
71
|
set: (key, value) => localStorage.setItem(key, value),
|
|
72
72
|
});
|
|
73
73
|
|
|
74
|
-
const getPersistedRemotePlugins = (storage: Storage, key: string):
|
|
74
|
+
const getPersistedRemotePlugins = (storage: Storage, key: string): RemotePluginView[] => {
|
|
75
75
|
try {
|
|
76
76
|
const parsed: unknown = JSON.parse(storage.get(key) ?? '[]');
|
|
77
77
|
if (!Array.isArray(parsed)) {
|
|
78
78
|
return [];
|
|
79
79
|
}
|
|
80
80
|
return parsed.filter(
|
|
81
|
-
(entry): entry is
|
|
82
|
-
typeof entry === 'object' &&
|
|
81
|
+
(entry): entry is RemotePluginView =>
|
|
82
|
+
typeof entry === 'object' &&
|
|
83
|
+
entry !== null &&
|
|
84
|
+
typeof entry.id === 'string' &&
|
|
85
|
+
typeof entry.url === 'string' &&
|
|
86
|
+
(entry.version === undefined || typeof entry.version === 'string'),
|
|
83
87
|
);
|
|
84
88
|
} catch {
|
|
85
89
|
return [];
|
|
86
90
|
}
|
|
87
91
|
};
|
|
88
92
|
|
|
89
|
-
const persistRemotePlugin = (storage: Storage, key: string, entry:
|
|
93
|
+
const persistRemotePlugin = (storage: Storage, key: string, entry: RemotePluginView): void => {
|
|
90
94
|
try {
|
|
91
95
|
const entries = getPersistedRemotePlugins(storage, key).filter((existing) => existing.id !== entry.id);
|
|
92
96
|
entries.push(entry);
|
|
@@ -132,7 +136,7 @@ export const isLocalUrl = (locator: string): boolean => {
|
|
|
132
136
|
* Useful for UI code that needs to know which loaded plugins were installed from a URL
|
|
133
137
|
* (e.g. to surface a tag on remote or localhost-hosted plugins).
|
|
134
138
|
*/
|
|
135
|
-
export const getRemoteEntries = (options: Options = {}): readonly
|
|
139
|
+
export const getRemoteEntries = (options: Options = {}): readonly RemotePluginView[] => {
|
|
136
140
|
const storage = options.storage ?? defaultStorage();
|
|
137
141
|
const key = options.key ?? DEFAULT_KEY;
|
|
138
142
|
return getPersistedRemotePlugins(storage, key);
|
|
@@ -196,14 +200,14 @@ const loadStylesheets = (
|
|
|
196
200
|
}
|
|
197
201
|
const cssUrls = manifest.assetUrls.filter((url) => url.endsWith('.css'));
|
|
198
202
|
for (const url of cssUrls) {
|
|
199
|
-
const resolved = yield* cache.resolve(manifest.
|
|
200
|
-
if (document.querySelector(`link[data-dxos-plugin-id="${manifest.
|
|
203
|
+
const resolved = yield* cache.resolve(manifest.key, url);
|
|
204
|
+
if (document.querySelector(`link[data-dxos-plugin-id="${manifest.key}"][href="${resolved}"]`)) {
|
|
201
205
|
continue;
|
|
202
206
|
}
|
|
203
207
|
const link = document.createElement('link');
|
|
204
208
|
link.rel = 'stylesheet';
|
|
205
209
|
link.href = resolved;
|
|
206
|
-
link.dataset.dxosPluginId = manifest.
|
|
210
|
+
link.dataset.dxosPluginId = manifest.key;
|
|
207
211
|
document.head.appendChild(link);
|
|
208
212
|
}
|
|
209
213
|
});
|
|
@@ -233,30 +237,30 @@ const loadFromManifest = (
|
|
|
233
237
|
// plugin's host is offline, dropping the plugin from the runtime.
|
|
234
238
|
const cachedUrls =
|
|
235
239
|
manifest.assetUrls.indexOf(manifestUrl) === -1 ? [manifestUrl, ...manifest.assetUrls] : manifest.assetUrls;
|
|
236
|
-
yield* cache.cache(manifest.
|
|
240
|
+
yield* cache.cache(manifest.key, cachedUrls).pipe(wrapCacheError);
|
|
237
241
|
}
|
|
238
242
|
const entryUrl = manifest.dev
|
|
239
243
|
? manifest.entryUrl
|
|
240
|
-
: yield* cache.resolve(manifest.
|
|
244
|
+
: yield* cache.resolve(manifest.key, manifest.entryUrl).pipe(wrapCacheError);
|
|
241
245
|
const mod = yield* Effect.tryPromise({
|
|
242
246
|
try: () => import(/* @vite-ignore */ entryUrl),
|
|
243
247
|
catch: (cause) =>
|
|
244
248
|
new RemotePluginLoadError({ context: { locator: manifestUrl, reason: 'import-failed' }, cause }),
|
|
245
249
|
});
|
|
246
250
|
const plugin = normalizePluginExport(mod);
|
|
247
|
-
if (!plugin.meta.
|
|
251
|
+
if (!plugin.meta.profile.key || !plugin.meta.profile.name) {
|
|
248
252
|
return yield* Effect.fail(
|
|
249
253
|
new RemotePluginLoadError({ context: { locator: manifestUrl, reason: 'meta-missing' } }),
|
|
250
254
|
);
|
|
251
255
|
}
|
|
252
|
-
if (plugin.meta.
|
|
256
|
+
if (plugin.meta.profile.key !== manifest.key) {
|
|
253
257
|
return yield* Effect.fail(
|
|
254
258
|
new RemotePluginLoadError({
|
|
255
259
|
context: {
|
|
256
260
|
locator: manifestUrl,
|
|
257
261
|
reason: 'meta-mismatch',
|
|
258
|
-
metaId: plugin.meta.
|
|
259
|
-
manifestId: manifest.
|
|
262
|
+
metaId: plugin.meta.profile.key,
|
|
263
|
+
manifestId: manifest.key,
|
|
260
264
|
},
|
|
261
265
|
}),
|
|
262
266
|
);
|
|
@@ -337,7 +341,7 @@ export const make = (builtinPlugins: Plugin.Plugin[], options: Options = {}) =>
|
|
|
337
341
|
|
|
338
342
|
return (locator: string): Effect.Effect<{ plugin: Plugin.Plugin; dev?: boolean }, RemotePluginLoadError> =>
|
|
339
343
|
Effect.gen(function* () {
|
|
340
|
-
const builtin = builtinPlugins.find((plugin) => plugin.meta.
|
|
344
|
+
const builtin = builtinPlugins.find((plugin) => plugin.meta.profile.key === locator);
|
|
341
345
|
if (builtin) {
|
|
342
346
|
return { plugin: builtin };
|
|
343
347
|
}
|
|
@@ -351,13 +355,13 @@ export const make = (builtinPlugins: Plugin.Plugin[], options: Options = {}) =>
|
|
|
351
355
|
// overlap (dev plugin takes the id slot for the session, original is
|
|
352
356
|
// restored on uninstall or page reload).
|
|
353
357
|
if (!manifest.dev) {
|
|
354
|
-
const duplicate = builtinPlugins.find((existing) => existing.meta.
|
|
358
|
+
const duplicate = builtinPlugins.find((existing) => existing.meta.profile.key === plugin.meta.profile.key);
|
|
355
359
|
if (duplicate) {
|
|
356
360
|
return yield* Effect.fail(
|
|
357
|
-
new RemotePluginLoadError({ context: { locator, reason: 'duplicate-id', id: plugin.meta.
|
|
361
|
+
new RemotePluginLoadError({ context: { locator, reason: 'duplicate-id', id: plugin.meta.profile.key } }),
|
|
358
362
|
);
|
|
359
363
|
}
|
|
360
|
-
persistRemotePlugin(storage, key, { id: plugin.meta.
|
|
364
|
+
persistRemotePlugin(storage, key, { id: plugin.meta.profile.key, url: locator });
|
|
361
365
|
}
|
|
362
366
|
return { plugin, dev: manifest.dev };
|
|
363
367
|
});
|
package/src/index.ts
CHANGED
package/src/testing/harness.ts
CHANGED
|
@@ -17,7 +17,7 @@ import { ActivationEvent, type Capability, type CapabilityManager, type Plugin,
|
|
|
17
17
|
|
|
18
18
|
export type TestAppOptions = {
|
|
19
19
|
/**
|
|
20
|
-
* Plugins to register. Plugins whose `meta.tags` includes `'system'` are treated as core
|
|
20
|
+
* Plugins to register. Plugins whose `meta.profile.tags` includes `'system'` are treated as core
|
|
21
21
|
* (force-enabled). For test convenience, plugins without a `'system'` tag are enabled by
|
|
22
22
|
* default unless `enabled` is provided.
|
|
23
23
|
*/
|
|
@@ -97,7 +97,9 @@ const DEFAULT_TIMEOUT_MS = 5_000;
|
|
|
97
97
|
export const createTestApp = async (opts: TestAppOptions): Promise<TestHarness> => {
|
|
98
98
|
const {
|
|
99
99
|
plugins,
|
|
100
|
-
enabled = plugins
|
|
100
|
+
enabled = plugins
|
|
101
|
+
.filter(({ meta }) => !meta.profile.tags?.includes('system'))
|
|
102
|
+
.map((plugin) => plugin.meta.profile.key),
|
|
101
103
|
setupEvents = [],
|
|
102
104
|
autoStart = true,
|
|
103
105
|
registerFrameworkCapabilities = true,
|
|
@@ -105,7 +107,7 @@ export const createTestApp = async (opts: TestAppOptions): Promise<TestHarness>
|
|
|
105
107
|
|
|
106
108
|
const pluginLoader = (id: string) =>
|
|
107
109
|
Effect.sync(() => {
|
|
108
|
-
const plugin = plugins.find((plugin) => plugin.meta.
|
|
110
|
+
const plugin = plugins.find((plugin) => plugin.meta.profile.key === id);
|
|
109
111
|
invariant(plugin, `Plugin not found: ${id}`);
|
|
110
112
|
return { plugin };
|
|
111
113
|
});
|
|
@@ -16,6 +16,8 @@ import { render, renderSurface } from './react';
|
|
|
16
16
|
|
|
17
17
|
const testMeta = Plugin.makeMeta({ key: DXN.make('org.dxos.plugin.test.reactHarness'), name: 'ReactHarnessTest' });
|
|
18
18
|
|
|
19
|
+
const GreetingRole = Surface.makeType<{ message: string }>('org.dxos.test.role.greeting');
|
|
20
|
+
|
|
19
21
|
const TestPlugin = Plugin.define(testMeta).pipe(
|
|
20
22
|
Plugin.addModule({
|
|
21
23
|
id: 'surfaces',
|
|
@@ -25,7 +27,7 @@ const TestPlugin = Plugin.define(testMeta).pipe(
|
|
|
25
27
|
Capability.contributes(Capabilities.ReactSurface, [
|
|
26
28
|
Surface.create<{ message: string }>({
|
|
27
29
|
id: 'greeting',
|
|
28
|
-
|
|
30
|
+
filter: Surface.makeFilter(GreetingRole),
|
|
29
31
|
component: ({ data }) => <span data-testid='greeting'>hello {data.message}</span>,
|
|
30
32
|
}),
|
|
31
33
|
]),
|
|
@@ -43,7 +45,7 @@ describe('testing/react', () => {
|
|
|
43
45
|
|
|
44
46
|
test('renderSurface mounts a surface registered by a plugin', async ({ expect }) => {
|
|
45
47
|
await using harness = await createTestApp({ plugins: [TestPlugin()] });
|
|
46
|
-
const view = renderSurface(harness, {
|
|
48
|
+
const view = renderSurface(harness, { type: GreetingRole, data: { message: 'plugins' } });
|
|
47
49
|
const node = await view.findByTestId('greeting');
|
|
48
50
|
expect(node.textContent).toBe('hello plugins');
|
|
49
51
|
});
|
package/src/testing/react.tsx
CHANGED
|
@@ -13,6 +13,7 @@ import { PluginManagerContext } from '../context';
|
|
|
13
13
|
import { topologicalSort } from '../helpers';
|
|
14
14
|
import { PluginManagerProvider } from '../ui/components/PluginManager/PluginManagerProvider';
|
|
15
15
|
import { SurfaceComponent } from '../ui/components/Surface/SurfaceComponent';
|
|
16
|
+
import { type RoleToken } from '../ui/components/Surface/types';
|
|
16
17
|
import { type TestHarness } from './harness';
|
|
17
18
|
|
|
18
19
|
export type HarnessRenderOptions = Omit<RenderOptions, 'wrapper'> & {
|
|
@@ -34,26 +35,26 @@ export const render = (harness: TestHarness, ui: ReactNode, options?: HarnessRen
|
|
|
34
35
|
return rtlRender(<>{ui}</>, { ...rest, wrapper: Wrapper });
|
|
35
36
|
};
|
|
36
37
|
|
|
37
|
-
export type RenderSurfaceProps = {
|
|
38
|
-
|
|
39
|
-
data?:
|
|
38
|
+
export type RenderSurfaceProps<TToken extends RoleToken<any>> = {
|
|
39
|
+
type: TToken;
|
|
40
|
+
data?: TToken extends RoleToken<infer D> ? D : never;
|
|
40
41
|
limit?: number;
|
|
41
42
|
fallback?: FC<{ error: Error; data?: any }>;
|
|
42
43
|
placeholder?: ReactNode;
|
|
43
44
|
};
|
|
44
45
|
|
|
45
46
|
/**
|
|
46
|
-
* Renders a `Surface` with the given role/data inside the harness provider tree.
|
|
47
|
+
* Renders a `Surface` with the given role token/data inside the harness provider tree.
|
|
47
48
|
*/
|
|
48
|
-
export const renderSurface = (
|
|
49
|
+
export const renderSurface = <TToken extends RoleToken<any>>(
|
|
49
50
|
harness: TestHarness,
|
|
50
|
-
props: RenderSurfaceProps
|
|
51
|
+
props: RenderSurfaceProps<TToken>,
|
|
51
52
|
options?: HarnessRenderOptions,
|
|
52
53
|
): RenderResult => {
|
|
53
|
-
const {
|
|
54
|
+
const { type, data, limit, fallback, placeholder } = props;
|
|
54
55
|
return render(
|
|
55
56
|
harness,
|
|
56
|
-
<SurfaceComponent
|
|
57
|
+
<SurfaceComponent type={type} data={data as any} limit={limit} fallback={fallback} placeholder={placeholder} />,
|
|
57
58
|
options,
|
|
58
59
|
);
|
|
59
60
|
};
|
package/src/testing/service.ts
CHANGED
|
@@ -22,7 +22,7 @@ export const fromPlugins = (plugins: Plugin.Plugin[]) =>
|
|
|
22
22
|
|
|
23
23
|
const pluginLoader = (id: string) =>
|
|
24
24
|
Effect.sync(() => {
|
|
25
|
-
const plugin = plugins.find((plugin) => plugin.meta.
|
|
25
|
+
const plugin = plugins.find((plugin) => plugin.meta.profile.key === id);
|
|
26
26
|
invariant(plugin, `Plugin not found: ${id}`);
|
|
27
27
|
return { plugin };
|
|
28
28
|
});
|
|
@@ -30,7 +30,7 @@ export const fromPlugins = (plugins: Plugin.Plugin[]) =>
|
|
|
30
30
|
const manager = PluginManager.make({
|
|
31
31
|
pluginLoader,
|
|
32
32
|
plugins,
|
|
33
|
-
enabled: plugins.map((plugin) => plugin.meta.
|
|
33
|
+
enabled: plugins.map((plugin) => plugin.meta.profile.key),
|
|
34
34
|
});
|
|
35
35
|
|
|
36
36
|
manager.capabilities.contribute({
|
|
@@ -12,12 +12,14 @@ import { Capability } from '../core';
|
|
|
12
12
|
import { Surface } from '../ui';
|
|
13
13
|
import { withPluginManager } from './withPluginManager';
|
|
14
14
|
|
|
15
|
+
const MainRole = Surface.makeType<Record<string, never>>('org.dxos.test.role.main');
|
|
16
|
+
|
|
15
17
|
const DefaultStory = () => {
|
|
16
18
|
console.log('Render');
|
|
17
19
|
return (
|
|
18
20
|
<div>
|
|
19
21
|
<div>Hello</div>
|
|
20
|
-
<Surface.Surface
|
|
22
|
+
<Surface.Surface type={MainRole} />
|
|
21
23
|
</div>
|
|
22
24
|
);
|
|
23
25
|
};
|
|
@@ -33,7 +35,7 @@ const meta = {
|
|
|
33
35
|
Capabilities.ReactSurface,
|
|
34
36
|
Surface.create({
|
|
35
37
|
id: 'test',
|
|
36
|
-
|
|
38
|
+
filter: Surface.makeFilter(MainRole),
|
|
37
39
|
component: ({ role }) => <span>{JSON.stringify({ role })}</span>,
|
|
38
40
|
}),
|
|
39
41
|
),
|
|
@@ -26,7 +26,9 @@ export const setupPluginManager = ({
|
|
|
26
26
|
}: UseAppOptions & Pick<WithPluginManagerOptions, 'capabilities'> = {}) => {
|
|
27
27
|
// Auto-enable every non-system plugin so stories don't have to spell out
|
|
28
28
|
// enablement. System-tagged plugins are force-enabled by the manager.
|
|
29
|
-
const enabled = plugins
|
|
29
|
+
const enabled = plugins
|
|
30
|
+
.filter(({ meta }) => !meta.profile.tags?.includes('system'))
|
|
31
|
+
.map(({ meta }) => meta.profile.key);
|
|
30
32
|
const pluginManager = PluginManager.make({
|
|
31
33
|
pluginLoader: () => raise(new Error('Not implemented')),
|
|
32
34
|
plugins: [StoryPlugin, ...plugins],
|
|
@@ -16,6 +16,13 @@ const PluginManagerContext = createContext<PluginManager.PluginManager | undefin
|
|
|
16
16
|
export const usePluginManager = (): PluginManager.PluginManager =>
|
|
17
17
|
useContext(PluginManagerContext) ?? raise(new Error('Missing PluginManagerContext'));
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Get the plugin manager if present, or `undefined` when rendered outside a {@link PluginManagerProvider}
|
|
21
|
+
* (e.g. a standalone component story). Lets components treat plugin capabilities as a progressive
|
|
22
|
+
* enhancement rather than a hard requirement.
|
|
23
|
+
*/
|
|
24
|
+
export const useOptionalPluginManager = (): PluginManager.PluginManager | undefined => useContext(PluginManagerContext);
|
|
25
|
+
|
|
19
26
|
/**
|
|
20
27
|
* Context provider for a plugin manager.
|
|
21
28
|
*/
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
6
|
-
import React, {
|
|
6
|
+
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
7
7
|
|
|
8
8
|
import { random } from '@dxos/random';
|
|
9
9
|
import { List, ListItem, Panel, Toolbar } from '@dxos/react-ui';
|
|
@@ -14,23 +14,22 @@ import { Capabilities } from '../../../common';
|
|
|
14
14
|
import { withPluginManager } from '../../../testing';
|
|
15
15
|
import { usePluginManager } from '../PluginManager';
|
|
16
16
|
import { SurfaceComponent, useSurfaces } from './SurfaceComponent';
|
|
17
|
-
import { create } from './types';
|
|
17
|
+
import { create, makeFilter, makeType } from './types';
|
|
18
|
+
|
|
19
|
+
const ItemRole = makeType<{ id: string }>('org.dxos.test.role.item');
|
|
18
20
|
|
|
19
21
|
type TestComponentProps = {
|
|
20
22
|
id: string;
|
|
21
23
|
styles: ColorStyles;
|
|
22
24
|
};
|
|
23
25
|
|
|
24
|
-
const TestComponent =
|
|
26
|
+
const TestComponent = ({ styles, id }: TestComponentProps) => {
|
|
25
27
|
return (
|
|
26
|
-
<div
|
|
27
|
-
className={mx('flex justify-center items-center border rounded-sm', styles.surface, styles.border)}
|
|
28
|
-
ref={forwardedRef}
|
|
29
|
-
>
|
|
28
|
+
<div className={mx('flex justify-center items-center border rounded-sm', styles.surface, styles.border)}>
|
|
30
29
|
<span className={mx('dx-tag font-mono text-lg', styles.text)}>{id}</span>
|
|
31
30
|
</div>
|
|
32
31
|
);
|
|
33
|
-
}
|
|
32
|
+
};
|
|
34
33
|
|
|
35
34
|
const DefaultStory = () => {
|
|
36
35
|
const manager = usePluginManager();
|
|
@@ -46,9 +45,8 @@ const DefaultStory = () => {
|
|
|
46
45
|
interface: Capabilities.ReactSurface,
|
|
47
46
|
implementation: create({
|
|
48
47
|
id,
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
component: ({ ref }) => <TestComponent id={id} styles={styles} ref={ref} />,
|
|
48
|
+
filter: makeFilter(ItemRole, (data) => data.id === id),
|
|
49
|
+
component: () => <TestComponent id={id} styles={styles} />,
|
|
52
50
|
}),
|
|
53
51
|
});
|
|
54
52
|
|
|
@@ -65,8 +63,7 @@ const DefaultStory = () => {
|
|
|
65
63
|
interface: Capabilities.ReactSurface,
|
|
66
64
|
implementation: create({
|
|
67
65
|
id: 'error',
|
|
68
|
-
|
|
69
|
-
filter: (data): data is any => (data as any)?.id === 'error',
|
|
66
|
+
filter: makeFilter(ItemRole, (data) => data.id === 'error'),
|
|
70
67
|
component: () => {
|
|
71
68
|
const [count, setCount] = useState(3);
|
|
72
69
|
useEffect(() => {
|
|
@@ -113,7 +110,7 @@ const DefaultStory = () => {
|
|
|
113
110
|
</Toolbar.Root>
|
|
114
111
|
</Panel.Toolbar>
|
|
115
112
|
<Panel.Content className='grid grid-cols-2 h-full gap-4 overflow-hidden'>
|
|
116
|
-
<SurfaceComponent
|
|
113
|
+
<SurfaceComponent type={ItemRole} data={selected ? { id: selected } : undefined} limit={1} ref={ref} />
|
|
117
114
|
<div className='overflow-y-auto h-full'>
|
|
118
115
|
<List>
|
|
119
116
|
{surfaces.map((surface) => (
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { useAtomValue } from '@effect-atom/atom-react';
|
|
6
6
|
import React, {
|
|
7
|
+
type FC,
|
|
7
8
|
Fragment,
|
|
8
9
|
type NamedExoticComponent,
|
|
9
10
|
Profiler,
|
|
@@ -44,78 +45,95 @@ const DEFAULT_PLACEHOLDER = <Fragment />;
|
|
|
44
45
|
* Wrapper component for rendering Web Component surfaces.
|
|
45
46
|
* Handles creation, prop setting, and cleanup of Web Components.
|
|
46
47
|
*/
|
|
48
|
+
type WebComponentWrapperProps = {
|
|
49
|
+
id?: string;
|
|
50
|
+
role: string;
|
|
51
|
+
data?: Record<string, any>;
|
|
52
|
+
limit?: number;
|
|
53
|
+
definition: WebComponentDefinition;
|
|
54
|
+
[key: string]: any;
|
|
55
|
+
};
|
|
56
|
+
|
|
47
57
|
const WebComponentWrapper = memo(
|
|
48
|
-
forwardRef<HTMLElement,
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
58
|
+
forwardRef<HTMLElement, WebComponentWrapperProps>(({ id, role, data, limit, definition, ...rest }, forwardedRef) => {
|
|
59
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
60
|
+
const elementRef = useRef<HTMLElement | null>(null);
|
|
61
|
+
const propsRef = useRef({ id, role, data, limit, ...rest });
|
|
62
|
+
|
|
63
|
+
// Update props ref on every render
|
|
64
|
+
propsRef.current = { id, role, data, limit, ...rest };
|
|
65
|
+
|
|
66
|
+
// Create element only once
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
if (!containerRef.current || elementRef.current) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
62
71
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
72
|
+
// Create the Web Component
|
|
73
|
+
const element = document.createElement(definition.tagName);
|
|
74
|
+
elementRef.current = element;
|
|
66
75
|
|
|
67
|
-
|
|
68
|
-
|
|
76
|
+
// Set initial properties on the Web Component
|
|
77
|
+
Object.assign(element, propsRef.current);
|
|
69
78
|
|
|
70
|
-
|
|
71
|
-
|
|
79
|
+
// Append to container
|
|
80
|
+
containerRef.current.appendChild(element);
|
|
72
81
|
|
|
73
|
-
|
|
82
|
+
// Setup ref forwarding if provided
|
|
83
|
+
if (typeof forwardedRef === 'function') {
|
|
84
|
+
forwardedRef(element);
|
|
85
|
+
} else if (forwardedRef) {
|
|
86
|
+
forwardedRef.current = element;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Cleanup on unmount to prevent memory leaks
|
|
90
|
+
return () => {
|
|
91
|
+
if (elementRef.current && containerRef.current?.contains(elementRef.current)) {
|
|
92
|
+
containerRef.current.removeChild(elementRef.current);
|
|
93
|
+
}
|
|
74
94
|
if (typeof forwardedRef === 'function') {
|
|
75
|
-
forwardedRef(
|
|
95
|
+
forwardedRef(null);
|
|
76
96
|
} else if (forwardedRef) {
|
|
77
|
-
forwardedRef.current =
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Cleanup on unmount to prevent memory leaks
|
|
81
|
-
return () => {
|
|
82
|
-
if (elementRef.current && containerRef.current?.contains(elementRef.current)) {
|
|
83
|
-
containerRef.current.removeChild(elementRef.current);
|
|
84
|
-
}
|
|
85
|
-
if (typeof forwardedRef === 'function') {
|
|
86
|
-
forwardedRef(null);
|
|
87
|
-
} else if (forwardedRef) {
|
|
88
|
-
forwardedRef.current = null;
|
|
89
|
-
}
|
|
90
|
-
elementRef.current = null;
|
|
91
|
-
};
|
|
92
|
-
}, [definition.tagName, forwardedRef]);
|
|
93
|
-
|
|
94
|
-
// Update props on existing element without recreating it
|
|
95
|
-
// This runs on every render to ensure all props (including those in `rest`) are kept up to date
|
|
96
|
-
useEffect(() => {
|
|
97
|
-
const element = elementRef.current;
|
|
98
|
-
if (!element) {
|
|
99
|
-
return;
|
|
97
|
+
forwardedRef.current = null;
|
|
100
98
|
}
|
|
99
|
+
elementRef.current = null;
|
|
100
|
+
};
|
|
101
|
+
}, [definition.tagName, forwardedRef]);
|
|
102
|
+
|
|
103
|
+
// Update props on existing element without recreating it
|
|
104
|
+
// This runs on every render to ensure all props (including those in `rest`) are kept up to date
|
|
105
|
+
useEffect(() => {
|
|
106
|
+
const element = elementRef.current;
|
|
107
|
+
if (!element) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
101
110
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
111
|
+
// Update properties on the existing Web Component
|
|
112
|
+
Object.assign(element, propsRef.current);
|
|
113
|
+
});
|
|
105
114
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
),
|
|
115
|
+
return <div ref={containerRef} />;
|
|
116
|
+
}),
|
|
109
117
|
);
|
|
110
118
|
|
|
111
119
|
WebComponentWrapper.displayName = 'WebComponentWrapper';
|
|
112
120
|
|
|
121
|
+
type SurfaceContextProviderProps = {
|
|
122
|
+
id: string;
|
|
123
|
+
role: string;
|
|
124
|
+
data?: Record<string, any>;
|
|
125
|
+
limit?: number;
|
|
126
|
+
fallback?: FC<{ error: Error; data?: any }>;
|
|
127
|
+
definition: Definition;
|
|
128
|
+
[key: string]: any;
|
|
129
|
+
};
|
|
130
|
+
|
|
113
131
|
/**
|
|
114
132
|
* Wrapper component that provides context for a surface.
|
|
115
133
|
*/
|
|
116
134
|
// TODO(burdon): Allow DebugPlugin to provide different fallback using react-ui ErrorFallback.
|
|
117
135
|
const SurfaceContextProvider = memo(
|
|
118
|
-
forwardRef<HTMLElement,
|
|
136
|
+
forwardRef<HTMLElement, SurfaceContextProviderProps>(
|
|
119
137
|
({ id, role, data, limit, fallback = ErrorFallback, definition, ...rest }, forwardedRef) => {
|
|
120
138
|
const contextValue = useMemo(() => ({ id, role, data }), [id, role, data]);
|
|
121
139
|
const onProfilerRender = useSurfaceProfilerCallback();
|
|
@@ -182,11 +200,13 @@ SurfaceContextProvider.displayName = 'SurfaceContextProvider';
|
|
|
182
200
|
|
|
183
201
|
/**
|
|
184
202
|
* A surface is a named region of the screen that can be populated by plugins.
|
|
203
|
+
* The `type` prop is a {@link RoleToken} that defines which region and its
|
|
204
|
+
* associated data contract.
|
|
185
205
|
*/
|
|
186
206
|
// TODO(burdon): Remove `ref` since relying on this would be error prone.
|
|
187
207
|
export const SurfaceComponent = memo(
|
|
188
|
-
forwardRef<HTMLElement,
|
|
189
|
-
({ id: _id,
|
|
208
|
+
forwardRef<HTMLElement, TypedProps<RoleToken<any>>>(
|
|
209
|
+
({ id: _id, type, data: dataProp, limit, placeholder = DEFAULT_PLACEHOLDER, ...rest }, forwardedRef) => {
|
|
190
210
|
const data = useDefaultValue(dataProp, () => ({}));
|
|
191
211
|
// TODO(wittjosiah): This will make all surfaces depend on a single signal.
|
|
192
212
|
// This isn't ideal because it means that any change to the data will cause all surfaces to re-render.
|
|
@@ -195,10 +215,10 @@ export const SurfaceComponent = memo(
|
|
|
195
215
|
// In the future, it would be nice to be able to bucket the surface contributions by role.
|
|
196
216
|
const surfaces = useSurfaces();
|
|
197
217
|
|
|
198
|
-
const effectiveRole =
|
|
218
|
+
const effectiveRole = type?.role;
|
|
199
219
|
if (effectiveRole == null) {
|
|
200
220
|
if (DEBUG) {
|
|
201
|
-
log.warn('Surface
|
|
221
|
+
log.warn('Surface is missing required `type` prop', { id: _id });
|
|
202
222
|
}
|
|
203
223
|
return null;
|
|
204
224
|
}
|
|
@@ -230,12 +250,12 @@ export const SurfaceComponent = memo(
|
|
|
230
250
|
},
|
|
231
251
|
),
|
|
232
252
|
) as (<TToken extends RoleToken<any>>(props: TypedProps<TToken> & RefAttributes<HTMLElement>) => React.ReactNode) &
|
|
233
|
-
NamedExoticComponent<
|
|
253
|
+
NamedExoticComponent<TypedProps<RoleToken<any>> & RefAttributes<HTMLElement>>;
|
|
234
254
|
|
|
235
255
|
SurfaceComponent.displayName = 'Surface';
|
|
236
256
|
|
|
237
257
|
// TODO(burdon): Make user facing, with telemetry.
|
|
238
|
-
const ErrorFallback = ({ error }:
|
|
258
|
+
const ErrorFallback = ({ error }: { error: Error }) => {
|
|
239
259
|
const { message } = error instanceof Error ? error : { message: String(error) };
|
|
240
260
|
return (
|
|
241
261
|
<div role='alert' data-testid='error-boundary-fallback'>
|
|
@@ -273,31 +293,21 @@ export const useSurfaces = () => {
|
|
|
273
293
|
/**
|
|
274
294
|
* @returns `true` if there is a contributed surface which matches the specified role & data, `false` otherwise.
|
|
275
295
|
*
|
|
276
|
-
*
|
|
277
|
-
*
|
|
278
|
-
* declared contract (e.g. `AppSurface.Section` requires `attendableId`).
|
|
279
|
-
* - Legacy: pass a string `role` and `data` is untyped.
|
|
296
|
+
* Typed: pass a `type` role token and `data` is constrained to the token's
|
|
297
|
+
* declared contract (e.g. `AppSurface.Section` requires `attendableId`).
|
|
280
298
|
*/
|
|
281
299
|
export function isSurfaceAvailable<TToken extends RoleToken<any>>(
|
|
282
300
|
capabilityManager: CapabilityManager.CapabilityManager,
|
|
283
|
-
args: { type: TToken; data?: TokenData<TToken
|
|
284
|
-
): boolean;
|
|
285
|
-
export function isSurfaceAvailable(
|
|
286
|
-
capabilityManager: CapabilityManager.CapabilityManager,
|
|
287
|
-
args: Pick<Props, 'role' | 'data'> & { type?: undefined },
|
|
288
|
-
): boolean;
|
|
289
|
-
export function isSurfaceAvailable(
|
|
290
|
-
capabilityManager: CapabilityManager.CapabilityManager,
|
|
291
|
-
{ role, type, data }: { role?: string; type?: RoleToken<any>; data?: unknown },
|
|
301
|
+
args: { type: TToken; data?: TokenData<TToken> },
|
|
292
302
|
): boolean {
|
|
293
|
-
const effectiveRole =
|
|
303
|
+
const effectiveRole = args.type?.role;
|
|
294
304
|
if (effectiveRole == null) {
|
|
295
305
|
return false;
|
|
296
306
|
}
|
|
297
307
|
const surfaces = capabilityManager.getAll(Capabilities.ReactSurface);
|
|
298
308
|
const candidates = findCandidates(surfaces.flat(), {
|
|
299
309
|
role: effectiveRole,
|
|
300
|
-
data: data as Props['data'],
|
|
310
|
+
data: args.data as Props['data'],
|
|
301
311
|
});
|
|
302
312
|
return candidates.length > 0;
|
|
303
313
|
}
|
|
@@ -4,9 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
import { type Context, createContext } from 'react';
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
export type SurfaceContext = {
|
|
8
|
+
id?: string;
|
|
9
|
+
/** The resolved role NSID (e.g. `org.dxos.role.article`). */
|
|
10
|
+
role: string;
|
|
11
|
+
data?: Record<string, any>;
|
|
12
|
+
};
|
|
10
13
|
|
|
11
14
|
// TODO(burdon): Use @radix-ui/react-context
|
|
12
15
|
export const SurfaceContext: Context<SurfaceContext | undefined> = createContext<SurfaceContext | undefined>(undefined);
|