@dxos/app-framework 0.8.4-main.b97322e → 0.8.4-main.dedc0f3
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/.swc/plugins/linux_x86_64_19.0.0/727453fb3a62f7f1d952a41e051ca8a6f88cadc45cee43c6a4d1aa45f9b75665.wasmer-v7 +0 -0
- package/.swc/plugins/{v7_linux_x86_64_13.0.0/fce1bdb8e20a094e4af08bad09cc81497ed0e2e7c51223b07d371063cca18429 → linux_x86_64_19.0.0/fce1bdb8e20a094e4af08bad09cc81497ed0e2e7c51223b07d371063cca18429.wasmer-v7} +0 -0
- package/dist/lib/browser/{app-graph-builder-LYF7EKNN.mjs → app-graph-builder-AFFC6VB2.mjs} +3 -3
- package/dist/lib/browser/app-graph-builder-AFFC6VB2.mjs.map +7 -0
- package/dist/lib/browser/{chunk-FMN65HSW.mjs → chunk-OZY7HV2A.mjs} +376 -252
- package/dist/lib/browser/chunk-OZY7HV2A.mjs.map +7 -0
- package/dist/lib/browser/{chunk-FO2PH7M3.mjs → chunk-T6M7JB7M.mjs} +186 -130
- package/dist/lib/browser/chunk-T6M7JB7M.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +11 -5
- package/dist/lib/browser/index.mjs.map +2 -2
- package/dist/lib/browser/{intent-dispatcher-LSYQZSEB.mjs → intent-dispatcher-QG7UPGQX.mjs} +2 -2
- package/dist/lib/browser/{intent-resolver-ZTNOSO3A.mjs → intent-resolver-4S4PSTM5.mjs} +2 -2
- package/dist/lib/browser/intent-resolver-4S4PSTM5.mjs.map +7 -0
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/{store-KML2R4IE.mjs → store-6E33KLGK.mjs} +2 -2
- package/dist/lib/browser/{store-KML2R4IE.mjs.map → store-6E33KLGK.mjs.map} +1 -1
- package/dist/lib/browser/testing/index.mjs +5 -7
- package/dist/lib/browser/testing/index.mjs.map +3 -3
- package/dist/lib/browser/worker.mjs +7 -1
- package/dist/lib/node-esm/{app-graph-builder-SAOWGJDK.mjs → app-graph-builder-S4OAULX5.mjs} +3 -3
- package/dist/lib/node-esm/app-graph-builder-S4OAULX5.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-ZEZ4FVEU.mjs → chunk-F63ZRXMK.mjs} +376 -252
- package/dist/lib/node-esm/chunk-F63ZRXMK.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-73HGSHKE.mjs → chunk-HJFU7QOR.mjs} +186 -130
- package/dist/lib/node-esm/chunk-HJFU7QOR.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +11 -5
- package/dist/lib/node-esm/index.mjs.map +2 -2
- package/dist/lib/node-esm/{intent-dispatcher-6CYNGPSW.mjs → intent-dispatcher-NXBGPJOX.mjs} +2 -2
- package/dist/lib/node-esm/{intent-resolver-W7Z7WFFM.mjs → intent-resolver-2ZKXI5ET.mjs} +2 -2
- package/dist/lib/node-esm/intent-resolver-2ZKXI5ET.mjs.map +7 -0
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/{store-QEXGXLWZ.mjs → store-QQUTQHHT.mjs} +2 -2
- package/dist/lib/node-esm/{store-QEXGXLWZ.mjs.map → store-QQUTQHHT.mjs.map} +1 -1
- package/dist/lib/node-esm/testing/index.mjs +5 -7
- package/dist/lib/node-esm/testing/index.mjs.map +3 -3
- package/dist/lib/node-esm/worker.mjs +7 -1
- package/dist/types/src/common/capabilities.d.ts +75 -8
- package/dist/types/src/common/capabilities.d.ts.map +1 -1
- package/dist/types/src/common/collaboration.d.ts +9 -8
- package/dist/types/src/common/collaboration.d.ts.map +1 -1
- package/dist/types/src/common/events.d.ts.map +1 -1
- package/dist/types/src/common/index.d.ts +1 -1
- package/dist/types/src/common/index.d.ts.map +1 -1
- package/dist/types/src/common/surface.d.ts +1 -1
- package/dist/types/src/common/surface.d.ts.map +1 -1
- package/dist/types/src/components/App.d.ts +10 -0
- package/dist/types/src/components/App.d.ts.map +1 -0
- package/dist/types/src/components/App.stories.d.ts +15 -0
- package/dist/types/src/components/App.stories.d.ts.map +1 -0
- package/dist/types/src/components/DefaultFallback.d.ts +8 -0
- package/dist/types/src/components/DefaultFallback.d.ts.map +1 -0
- package/dist/types/src/components/index.d.ts +2 -0
- package/dist/types/src/components/index.d.ts.map +1 -0
- package/dist/types/src/{App.d.ts → components/useApp.d.ts} +7 -6
- package/dist/types/src/components/useApp.d.ts.map +1 -0
- package/dist/types/src/components/useLoading.d.ts +19 -0
- package/dist/types/src/components/useLoading.d.ts.map +1 -0
- package/dist/types/src/core/capabilities.d.ts +4 -1
- package/dist/types/src/core/capabilities.d.ts.map +1 -1
- package/dist/types/src/core/manager.d.ts +6 -2
- package/dist/types/src/core/manager.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -1
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/playground/debug/Debug.d.ts +1 -1
- package/dist/types/src/playground/generator/Main.d.ts +1 -1
- package/dist/types/src/playground/generator/Toolbar.d.ts +1 -1
- package/dist/types/src/playground/generator/Toolbar.d.ts.map +1 -1
- package/dist/types/src/playground/generator/generator.d.ts.map +1 -1
- package/dist/types/src/playground/layout/Layout.d.ts +2 -2
- package/dist/types/src/playground/logger/Toolbar.d.ts +1 -1
- package/dist/types/src/playground/logger/Toolbar.d.ts.map +1 -1
- package/dist/types/src/playground/logger/plugin.d.ts.map +1 -1
- package/dist/types/src/playground/playground.stories.d.ts +5 -3
- package/dist/types/src/playground/playground.stories.d.ts.map +1 -1
- package/dist/types/src/plugin-intent/IntentPlugin.d.ts.map +1 -1
- package/dist/types/src/plugin-intent/index.d.ts +1 -0
- package/dist/types/src/plugin-intent/index.d.ts.map +1 -1
- package/dist/types/src/plugin-intent/intent-dispatcher.d.ts +3 -3
- package/dist/types/src/plugin-intent/intent-dispatcher.d.ts.map +1 -1
- package/dist/types/src/plugin-settings/SettingsPlugin.d.ts.map +1 -1
- package/dist/types/src/plugin-settings/app-graph-builder.d.ts +1 -1
- package/dist/types/src/plugin-settings/app-graph-builder.d.ts.map +1 -1
- package/dist/types/src/plugin-settings/intent-resolver.d.ts +1 -1
- package/dist/types/src/plugin-settings/intent-resolver.d.ts.map +1 -1
- package/dist/types/src/plugin-settings/store.d.ts +1 -1
- package/dist/types/src/plugin-settings/store.d.ts.map +1 -1
- package/dist/types/src/react/ErrorBoundary.d.ts +13 -14
- package/dist/types/src/react/ErrorBoundary.d.ts.map +1 -1
- package/dist/types/src/react/IntentContext.d.ts.map +1 -1
- package/dist/types/src/react/Surface.d.ts.map +1 -1
- package/dist/types/src/react/Surface.stories.d.ts +6 -4
- package/dist/types/src/react/Surface.stories.d.ts.map +1 -1
- package/dist/types/src/react/common.d.ts.map +1 -1
- package/dist/types/src/react/useCapabilities.d.ts.map +1 -1
- package/dist/types/src/testing/withPluginManager.d.ts +4 -2
- package/dist/types/src/testing/withPluginManager.d.ts.map +1 -1
- package/dist/types/src/testing/withPluginManager.stories.d.ts +9 -3
- package/dist/types/src/testing/withPluginManager.stories.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +28 -24
- package/src/common/capabilities.ts +91 -10
- package/src/common/collaboration.ts +5 -8
- package/src/common/events.ts +3 -1
- package/src/common/index.ts +1 -1
- package/src/common/surface.ts +1 -1
- package/src/components/App.stories.tsx +35 -0
- package/src/components/App.tsx +59 -0
- package/src/components/DefaultFallback.tsx +26 -0
- package/src/components/index.ts +5 -0
- package/src/{App.tsx → components/useApp.tsx} +20 -130
- package/src/components/useLoading.tsx +70 -0
- package/src/core/capabilities.test.ts +1 -1
- package/src/core/capabilities.ts +11 -6
- package/src/core/manager.test.ts +4 -3
- package/src/core/manager.ts +132 -54
- package/src/helpers.test.ts +1 -1
- package/src/index.ts +1 -1
- package/src/playground/debug/Debug.tsx +1 -1
- package/src/playground/generator/Toolbar.tsx +2 -1
- package/src/playground/generator/generator.ts +2 -2
- package/src/playground/layout/plugin.ts +1 -1
- package/src/playground/logger/Toolbar.tsx +2 -1
- package/src/playground/logger/plugin.ts +3 -2
- package/src/playground/playground.stories.tsx +15 -10
- package/src/plugin-intent/IntentPlugin.ts +2 -1
- package/src/plugin-intent/index.ts +1 -0
- package/src/plugin-intent/intent-dispatcher.test.ts +1 -1
- package/src/plugin-intent/intent-dispatcher.ts +10 -8
- package/src/plugin-settings/SettingsPlugin.ts +3 -2
- package/src/plugin-settings/app-graph-builder.ts +4 -3
- package/src/plugin-settings/intent-resolver.ts +3 -2
- package/src/plugin-settings/store.ts +1 -1
- package/src/react/ErrorBoundary.tsx +24 -15
- package/src/react/IntentContext.tsx +3 -2
- package/src/react/Surface.stories.tsx +21 -13
- package/src/react/Surface.tsx +4 -3
- package/src/react/common.ts +2 -1
- package/src/react/useCapabilities.ts +2 -1
- package/src/testing/withPluginManager.stories.tsx +9 -5
- package/src/testing/withPluginManager.tsx +13 -13
- package/tsconfig.json +1 -8
- package/.swc/plugins/v7_linux_x86_64_13.0.0/f45bdff002284d9e8f9ef3f0be909de12da36c049cbcf261ac78fc00abb09a2d +0 -0
- package/dist/lib/browser/app-graph-builder-LYF7EKNN.mjs.map +0 -7
- package/dist/lib/browser/chunk-FMN65HSW.mjs.map +0 -7
- package/dist/lib/browser/chunk-FO2PH7M3.mjs.map +0 -7
- package/dist/lib/browser/intent-resolver-ZTNOSO3A.mjs.map +0 -7
- package/dist/lib/node-esm/app-graph-builder-SAOWGJDK.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-73HGSHKE.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-ZEZ4FVEU.mjs.map +0 -7
- package/dist/lib/node-esm/intent-resolver-W7Z7WFFM.mjs.map +0 -7
- package/dist/types/src/App.d.ts.map +0 -1
- /package/dist/lib/browser/{intent-dispatcher-LSYQZSEB.mjs.map → intent-dispatcher-QG7UPGQX.mjs.map} +0 -0
- /package/dist/lib/node-esm/{intent-dispatcher-6CYNGPSW.mjs.map → intent-dispatcher-NXBGPJOX.mjs.map} +0 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { useEffect, useState } from 'react';
|
|
6
|
+
|
|
7
|
+
import { type AppProps } from './App';
|
|
8
|
+
|
|
9
|
+
export enum LoadingState {
|
|
10
|
+
Loading = 0,
|
|
11
|
+
FadeIn = 1,
|
|
12
|
+
FadeOut = 2,
|
|
13
|
+
Done = 3,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* To avoid "flashing" the placeholder, we wait a period of time before starting the loading animation.
|
|
18
|
+
* If loading completes during this time the placehoder is not shown, otherwise is it displayed for a minimum period of time.
|
|
19
|
+
*
|
|
20
|
+
* States:
|
|
21
|
+
* 0: Loading - Wait for a period of time before starting the loading animation.
|
|
22
|
+
* 1: Fade-in - Display a loading animation.
|
|
23
|
+
* 2: Fade-out - Fade out the loading animation.
|
|
24
|
+
* 3: Done - Remove the placeholder.
|
|
25
|
+
*/
|
|
26
|
+
export const useLoading = (state: AppProps['state'], debounce = 0) => {
|
|
27
|
+
const [stage, setStage] = useState<LoadingState>(LoadingState.Loading);
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
if (!debounce) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const i = setInterval(() => {
|
|
34
|
+
setStage((stage) => {
|
|
35
|
+
switch (stage) {
|
|
36
|
+
case LoadingState.Loading: {
|
|
37
|
+
if (!state.ready) {
|
|
38
|
+
return LoadingState.FadeIn;
|
|
39
|
+
} else {
|
|
40
|
+
clearInterval(i);
|
|
41
|
+
return LoadingState.Done;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
case LoadingState.FadeIn: {
|
|
46
|
+
if (state.ready) {
|
|
47
|
+
return LoadingState.FadeOut;
|
|
48
|
+
}
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
case LoadingState.FadeOut: {
|
|
53
|
+
clearInterval(i);
|
|
54
|
+
return LoadingState.Done;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return stage;
|
|
59
|
+
});
|
|
60
|
+
}, debounce);
|
|
61
|
+
|
|
62
|
+
return () => clearInterval(i);
|
|
63
|
+
}, [debounce]);
|
|
64
|
+
|
|
65
|
+
if (!debounce) {
|
|
66
|
+
return state.ready ? LoadingState.Done : LoadingState.Loading;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return stage;
|
|
70
|
+
};
|
|
@@ -6,7 +6,7 @@ import { Registry } from '@effect-rx/rx-react';
|
|
|
6
6
|
import { Effect } from 'effect';
|
|
7
7
|
import { describe, expect, it, onTestFinished } from 'vitest';
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import { PluginContext, defineCapability } from './capabilities';
|
|
10
10
|
|
|
11
11
|
const defaultOptions = {
|
|
12
12
|
activate: () => Effect.succeed(false),
|
package/src/core/capabilities.ts
CHANGED
|
@@ -22,6 +22,10 @@ export type InterfaceDef<T> = {
|
|
|
22
22
|
identifier: string;
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
+
export namespace InterfaceDef {
|
|
26
|
+
export type Implementation<I extends InterfaceDef<any>> = I extends InterfaceDef<infer T> ? T : never;
|
|
27
|
+
}
|
|
28
|
+
|
|
25
29
|
/**
|
|
26
30
|
* Helper to define the interface of a capability.
|
|
27
31
|
*/
|
|
@@ -69,16 +73,17 @@ class CapabilityImpl<T> {
|
|
|
69
73
|
/**
|
|
70
74
|
* Helper to define the implementation of a capability.
|
|
71
75
|
*/
|
|
72
|
-
export const contributes = <
|
|
73
|
-
interfaceDef:
|
|
74
|
-
implementation: Capability<
|
|
75
|
-
deactivate?: Capability<
|
|
76
|
-
): Capability<
|
|
77
|
-
return { interface: interfaceDef, implementation, deactivate } satisfies Capability<
|
|
76
|
+
export const contributes = <I extends InterfaceDef<any>>(
|
|
77
|
+
interfaceDef: I,
|
|
78
|
+
implementation: Capability<InterfaceDef.Implementation<I>>['implementation'],
|
|
79
|
+
deactivate?: Capability<InterfaceDef.Implementation<I>>['deactivate'],
|
|
80
|
+
): Capability<I> => {
|
|
81
|
+
return { interface: interfaceDef, implementation, deactivate } satisfies Capability<I>;
|
|
78
82
|
};
|
|
79
83
|
|
|
80
84
|
type LoadCapability<T, U> = () => Promise<{ default: (props: T) => MaybePromise<Capability<U>> }>;
|
|
81
85
|
type LoadCapabilities<T> = () => Promise<{ default: (props: T) => MaybePromise<AnyCapability[]> }>;
|
|
86
|
+
|
|
82
87
|
// TODO(wittjosiah): Not having the array be `any` causes type errors when using the lazy capability.
|
|
83
88
|
type LazyCapability<T, U> = (props?: T) => Promise<() => Promise<Capability<U> | AnyCapability[]>>;
|
|
84
89
|
|
package/src/core/manager.test.ts
CHANGED
|
@@ -12,11 +12,12 @@ import { registerSignalsRuntime } from '@dxos/echo-signals';
|
|
|
12
12
|
import { invariant } from '@dxos/invariant';
|
|
13
13
|
import { live } from '@dxos/live-object';
|
|
14
14
|
|
|
15
|
-
import {
|
|
15
|
+
import { Events } from '../common';
|
|
16
|
+
|
|
17
|
+
import { type PluginContext, contributes, defineCapability } from './capabilities';
|
|
16
18
|
import { allOf, defineEvent, oneOf } from './events';
|
|
17
19
|
import { PluginManager } from './manager';
|
|
18
|
-
import {
|
|
19
|
-
import { Events } from '../common';
|
|
20
|
+
import { type Plugin, defineModule, definePlugin } from './plugin';
|
|
20
21
|
|
|
21
22
|
registerSignalsRuntime();
|
|
22
23
|
|
package/src/core/manager.ts
CHANGED
|
@@ -4,16 +4,16 @@
|
|
|
4
4
|
|
|
5
5
|
import { Registry } from '@effect-rx/rx-react';
|
|
6
6
|
import { untracked } from '@preact/signals-core';
|
|
7
|
-
import { Array
|
|
7
|
+
import { Array, Duration, Effect, Fiber, HashSet, Match, Ref, pipe } from 'effect';
|
|
8
8
|
|
|
9
9
|
import { Event } from '@dxos/async';
|
|
10
|
-
import {
|
|
10
|
+
import { type Live, live } from '@dxos/live-object';
|
|
11
11
|
import { log } from '@dxos/log';
|
|
12
12
|
import { type MaybePromise } from '@dxos/util';
|
|
13
13
|
|
|
14
14
|
import { type AnyCapability, PluginContext } from './capabilities';
|
|
15
15
|
import { type ActivationEvent, eventKey, getEvents, isAllOf } from './events';
|
|
16
|
-
import { type
|
|
16
|
+
import { type Plugin, type PluginModule } from './plugin';
|
|
17
17
|
|
|
18
18
|
// TODO(wittjosiah): Factor out?
|
|
19
19
|
const isPromise = (value: unknown): value is Promise<unknown> => {
|
|
@@ -52,6 +52,9 @@ export class PluginManager {
|
|
|
52
52
|
private readonly _state: Live<PluginManagerState>;
|
|
53
53
|
private readonly _pluginLoader: PluginManagerOptions['pluginLoader'];
|
|
54
54
|
private readonly _capabilities = new Map<string, AnyCapability[]>();
|
|
55
|
+
private readonly _moduleMemoMap = new Map<PluginModule['id'], Promise<AnyCapability[]>>();
|
|
56
|
+
private readonly _activatingEvents = Effect.runSync(Ref.make<string[]>([]));
|
|
57
|
+
private readonly _activatingModules = Effect.runSync(Ref.make<string[]>([]));
|
|
55
58
|
|
|
56
59
|
constructor({
|
|
57
60
|
pluginLoader,
|
|
@@ -74,8 +77,8 @@ export class PluginManager {
|
|
|
74
77
|
enabled,
|
|
75
78
|
modules: [],
|
|
76
79
|
active: [],
|
|
77
|
-
pendingReset: [],
|
|
78
80
|
eventsFired: [],
|
|
81
|
+
pendingReset: [],
|
|
79
82
|
});
|
|
80
83
|
plugins.forEach((plugin) => this._addPlugin(plugin));
|
|
81
84
|
core.forEach((id) => this.enable(id));
|
|
@@ -268,6 +271,7 @@ export class PluginManager {
|
|
|
268
271
|
private _addPlugin(plugin: Plugin): void {
|
|
269
272
|
untracked(() => {
|
|
270
273
|
log('add plugin', { id: plugin.meta.id });
|
|
274
|
+
// TODO(wittjosiah): Find a way to add a warning for duplicate plugins that doesn't cause log spam.
|
|
271
275
|
if (!this._state.plugins.includes(plugin)) {
|
|
272
276
|
this._state.plugins.push(plugin);
|
|
273
277
|
}
|
|
@@ -287,6 +291,7 @@ export class PluginManager {
|
|
|
287
291
|
private _addModule(module: PluginModule): void {
|
|
288
292
|
untracked(() => {
|
|
289
293
|
log('add module', { id: module.id });
|
|
294
|
+
// TODO(wittjosiah): Find a way to add a warning for duplicate modules that doesn't cause log spam.
|
|
290
295
|
if (!this._state.modules.includes(module)) {
|
|
291
296
|
this._state.modules.push(module);
|
|
292
297
|
}
|
|
@@ -329,7 +334,7 @@ export class PluginManager {
|
|
|
329
334
|
.map(eventKey)
|
|
330
335
|
.filter((key) => this._state.eventsFired.includes(key));
|
|
331
336
|
|
|
332
|
-
const pendingReset = Array.
|
|
337
|
+
const pendingReset = Array.fromIterable(new Set(activationEvents)).filter(
|
|
333
338
|
(event) => !this._state.pendingReset.includes(event),
|
|
334
339
|
);
|
|
335
340
|
if (pendingReset.length > 0) {
|
|
@@ -343,24 +348,42 @@ export class PluginManager {
|
|
|
343
348
|
* @internal
|
|
344
349
|
*/
|
|
345
350
|
// TODO(wittjosiah): Improve error typing.
|
|
346
|
-
_activate(
|
|
351
|
+
_activate(
|
|
352
|
+
event: ActivationEvent | string,
|
|
353
|
+
params?: { before?: string; after?: string },
|
|
354
|
+
): Effect.Effect<boolean, Error> {
|
|
347
355
|
return Effect.gen(this, function* () {
|
|
348
356
|
const key = typeof event === 'string' ? event : eventKey(event);
|
|
349
|
-
log('activating', { key });
|
|
357
|
+
log('activating', { key, ...params });
|
|
358
|
+
yield* Ref.update(this._activatingEvents, (activating) => Array.append(activating, key));
|
|
350
359
|
const pendingIndex = this._state.pendingReset.findIndex((event) => event === key);
|
|
351
360
|
if (pendingIndex !== -1) {
|
|
352
361
|
this._state.pendingReset.splice(pendingIndex, 1);
|
|
353
362
|
}
|
|
354
363
|
|
|
364
|
+
const activatingEvents = yield* this._activatingEvents;
|
|
365
|
+
const activatingModules = yield* this._activatingModules;
|
|
355
366
|
const modules = this._getInactiveModulesByEvent(key).filter((module) => {
|
|
356
367
|
const allOf = isAllOf(module.activatesOn);
|
|
357
368
|
if (!allOf) {
|
|
358
369
|
return true;
|
|
359
370
|
}
|
|
360
371
|
|
|
372
|
+
// Check to see if all of the events in the `allOf` have been fired.
|
|
373
|
+
// An event can be considered "fired" if it is in the `eventsFired` list or if it is currently being activated.
|
|
361
374
|
const events = module.activatesOn.events.filter((event) => eventKey(event) !== key);
|
|
362
|
-
return
|
|
375
|
+
return (
|
|
376
|
+
events.every(
|
|
377
|
+
(event) => this._state.eventsFired.includes(eventKey(event)) || activatingEvents.includes(eventKey(event)),
|
|
378
|
+
) && !activatingModules.includes(module.id)
|
|
379
|
+
);
|
|
363
380
|
});
|
|
381
|
+
yield* Ref.update(this._activatingModules, (activating) =>
|
|
382
|
+
Array.appendAll(
|
|
383
|
+
activating,
|
|
384
|
+
modules.map((module) => module.id),
|
|
385
|
+
),
|
|
386
|
+
);
|
|
364
387
|
if (modules.length === 0) {
|
|
365
388
|
log('no modules to activate', { key });
|
|
366
389
|
if (!this._state.eventsFired.includes(key)) {
|
|
@@ -372,31 +395,53 @@ export class PluginManager {
|
|
|
372
395
|
log('activating modules', { key, modules: modules.map((module) => module.id) });
|
|
373
396
|
this.activation.emit({ event: key, state: 'activating' });
|
|
374
397
|
|
|
398
|
+
// Fire activatesBefore events.
|
|
399
|
+
yield* pipe(
|
|
400
|
+
modules,
|
|
401
|
+
Array.flatMap((module) => module.activatesBefore ?? []),
|
|
402
|
+
HashSet.fromIterable,
|
|
403
|
+
HashSet.toValues,
|
|
404
|
+
Array.filter((event) => !activatingEvents.includes(eventKey(event))),
|
|
405
|
+
Array.map((event) => this._activate(event, { before: key })),
|
|
406
|
+
Effect.allWith({ concurrency: 'unbounded' }),
|
|
407
|
+
);
|
|
408
|
+
|
|
375
409
|
// Concurrently triggers loading of lazy capabilities.
|
|
376
|
-
const getCapabilities = yield*
|
|
377
|
-
modules
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
})
|
|
382
|
-
|
|
383
|
-
|
|
410
|
+
const getCapabilities = yield* pipe(
|
|
411
|
+
modules,
|
|
412
|
+
Array.map((mod) => this._loadModule(mod)),
|
|
413
|
+
Effect.allWith({ concurrency: 'unbounded' }),
|
|
414
|
+
Effect.catchAll((error) => {
|
|
415
|
+
this.activation.emit({ event: key, state: 'error', error });
|
|
416
|
+
return Effect.fail(error);
|
|
417
|
+
}),
|
|
384
418
|
);
|
|
385
419
|
|
|
386
|
-
|
|
420
|
+
// Contribute the capabilities from the activated modules.
|
|
421
|
+
yield* pipe(
|
|
387
422
|
modules,
|
|
388
|
-
|
|
389
|
-
|
|
423
|
+
Array.zip(getCapabilities),
|
|
424
|
+
Array.map(([module, capabilities]) => this._contributeCapabilities(module, capabilities)),
|
|
390
425
|
// TODO(wittjosiah): This currently can't be run in parallel.
|
|
391
426
|
// Running this with concurrency causes races with `allOf` activation events.
|
|
392
427
|
Effect.all,
|
|
393
|
-
Effect.either,
|
|
394
428
|
);
|
|
395
429
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
430
|
+
// Fire activatesAfter events.
|
|
431
|
+
yield* pipe(
|
|
432
|
+
modules,
|
|
433
|
+
Array.flatMap((module) => module.activatesAfter ?? []),
|
|
434
|
+
HashSet.fromIterable,
|
|
435
|
+
HashSet.toValues,
|
|
436
|
+
Array.filter((event) => !activatingEvents.includes(eventKey(event))),
|
|
437
|
+
Array.map((event) => this._activate(event, { after: key })),
|
|
438
|
+
Effect.allWith({ concurrency: 'unbounded' }),
|
|
439
|
+
);
|
|
440
|
+
|
|
441
|
+
yield* Ref.update(this._activatingEvents, (activating) => Array.filter(activating, (event) => event !== key));
|
|
442
|
+
yield* Ref.update(this._activatingModules, (activating) =>
|
|
443
|
+
Array.filter(activating, (module) => !modules.map((module) => module.id).includes(module)),
|
|
444
|
+
);
|
|
400
445
|
|
|
401
446
|
if (!this._state.eventsFired.includes(key)) {
|
|
402
447
|
this._state.eventsFired.push(key);
|
|
@@ -409,43 +454,60 @@ export class PluginManager {
|
|
|
409
454
|
});
|
|
410
455
|
}
|
|
411
456
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
457
|
+
// Memoized with _moduleMemoMap
|
|
458
|
+
private _loadModule = (mod: PluginModule): Effect.Effect<AnyCapability[], Error> =>
|
|
459
|
+
Effect.tryPromise({
|
|
460
|
+
try: async () => {
|
|
461
|
+
const entry = this._moduleMemoMap.get(mod.id);
|
|
462
|
+
if (entry) {
|
|
463
|
+
return entry;
|
|
464
|
+
}
|
|
420
465
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
466
|
+
const promise = (async () => {
|
|
467
|
+
const start = performance.now();
|
|
468
|
+
let failed = false;
|
|
469
|
+
try {
|
|
470
|
+
log('loading module', { module: mod.id });
|
|
471
|
+
// TODO(wittjosiah): Support activation with an effect.
|
|
472
|
+
let activationResult = await mod.activate(this.context);
|
|
473
|
+
if (typeof activationResult === 'function') {
|
|
474
|
+
activationResult = await activationResult();
|
|
475
|
+
}
|
|
476
|
+
return Array.isArray(activationResult) ? activationResult : [activationResult];
|
|
477
|
+
} catch (error) {
|
|
478
|
+
failed = true;
|
|
479
|
+
throw error;
|
|
480
|
+
} finally {
|
|
481
|
+
performance.measure('activate-module', {
|
|
482
|
+
start,
|
|
483
|
+
end: performance.now(),
|
|
484
|
+
detail: {
|
|
485
|
+
module: mod.id,
|
|
486
|
+
},
|
|
487
|
+
});
|
|
488
|
+
log('loaded module', { module: mod.id, elapsed: performance.now() - start, failed });
|
|
489
|
+
}
|
|
490
|
+
})();
|
|
491
|
+
this._moduleMemoMap.set(mod.id, promise);
|
|
492
|
+
return promise;
|
|
493
|
+
},
|
|
494
|
+
catch: (error) => error as Error,
|
|
495
|
+
}).pipe(
|
|
496
|
+
Effect.withSpan('PluginManager._loadModule'),
|
|
497
|
+
together(
|
|
498
|
+
Effect.sleep(Duration.seconds(10)).pipe(
|
|
499
|
+
Effect.andThen(Effect.sync(() => log.warn(`module is taking a long time to activate`, { module: mod.id }))),
|
|
432
500
|
),
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
);
|
|
501
|
+
),
|
|
502
|
+
);
|
|
503
|
+
|
|
504
|
+
private _contributeCapabilities(module: PluginModule, capabilities: AnyCapability[]): Effect.Effect<void, Error> {
|
|
505
|
+
return Effect.gen(this, function* () {
|
|
439
506
|
capabilities.forEach((capability) => {
|
|
440
507
|
this.context.contributeCapability({ module: module.id, ...capability });
|
|
441
508
|
});
|
|
442
509
|
this._state.active.push(module.id);
|
|
443
510
|
this._capabilities.set(module.id, capabilities);
|
|
444
|
-
log('activated module', { module: module.id });
|
|
445
|
-
|
|
446
|
-
yield* Effect.all(module.activatesAfter?.map((event) => this._activate(event)) ?? [], {
|
|
447
|
-
concurrency: 'unbounded',
|
|
448
|
-
});
|
|
449
511
|
});
|
|
450
512
|
}
|
|
451
513
|
|
|
@@ -469,6 +531,7 @@ export class PluginManager {
|
|
|
469
531
|
return Effect.gen(this, function* () {
|
|
470
532
|
const id = module.id;
|
|
471
533
|
log('deactivating', { id });
|
|
534
|
+
this._moduleMemoMap.delete(id);
|
|
472
535
|
|
|
473
536
|
const capabilities = this._capabilities.get(id);
|
|
474
537
|
if (capabilities) {
|
|
@@ -517,3 +580,18 @@ export class PluginManager {
|
|
|
517
580
|
});
|
|
518
581
|
}
|
|
519
582
|
}
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* Runs an effect concurrently with another effect.
|
|
586
|
+
* If the first effect completes, the second effect is interrupted.
|
|
587
|
+
*/
|
|
588
|
+
// TODO(dmaretskyi): Effect.race > Effect.asVoid
|
|
589
|
+
const together =
|
|
590
|
+
<R1>(togetherEffect: Effect.Effect<void, never, R1>) =>
|
|
591
|
+
<A, E, R2>(effect: Effect.Effect<A, E, R2>): Effect.Effect<A, E, R1 | R2> =>
|
|
592
|
+
Effect.gen(function* () {
|
|
593
|
+
const togetherFiber = yield* Effect.fork(togetherEffect);
|
|
594
|
+
const result = yield* effect;
|
|
595
|
+
yield* Fiber.interrupt(togetherFiber);
|
|
596
|
+
return result;
|
|
597
|
+
});
|
package/src/helpers.test.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -22,7 +22,7 @@ export const Debug = () => {
|
|
|
22
22
|
};
|
|
23
23
|
|
|
24
24
|
return (
|
|
25
|
-
<SyntaxHighlighter language='json' classNames='
|
|
25
|
+
<SyntaxHighlighter language='json' classNames='text-xs opacity-75 rounded'>
|
|
26
26
|
{JSON.stringify(object, undefined, 2)}
|
|
27
27
|
</SyntaxHighlighter>
|
|
28
28
|
);
|
|
@@ -6,12 +6,13 @@ import React, { useCallback } from 'react';
|
|
|
6
6
|
|
|
7
7
|
import { Button } from '@dxos/react-ui';
|
|
8
8
|
|
|
9
|
-
import { createGeneratorIntent, createPluginId, Number } from './generator';
|
|
10
9
|
import { Capabilities } from '../../common';
|
|
11
10
|
import { contributes } from '../../core';
|
|
12
11
|
import { createIntent } from '../../plugin-intent';
|
|
13
12
|
import { useCapabilities, useIntentDispatcher, usePluginManager } from '../../react';
|
|
14
13
|
|
|
14
|
+
import { Number, createGeneratorIntent, createPluginId } from './generator';
|
|
15
|
+
|
|
15
16
|
export const Toolbar = () => {
|
|
16
17
|
const manager = usePluginManager();
|
|
17
18
|
const { dispatchPromise: dispatch } = useIntentDispatcher();
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
import { Schema } from 'effect';
|
|
6
6
|
|
|
7
7
|
import { Capabilities, Events } from '../../common';
|
|
8
|
-
import { contributes,
|
|
9
|
-
import {
|
|
8
|
+
import { contributes, defineCapability, defineEvent, defineModule, definePlugin } from '../../core';
|
|
9
|
+
import { type IntentSchema, createResolver } from '../../plugin-intent';
|
|
10
10
|
|
|
11
11
|
export const Number = defineCapability<number>('dxos.org/test/generator/number');
|
|
12
12
|
|
|
@@ -6,12 +6,13 @@ import React, { useCallback } from 'react';
|
|
|
6
6
|
|
|
7
7
|
import { Button } from '@dxos/react-ui';
|
|
8
8
|
|
|
9
|
-
import { Log } from './schema';
|
|
10
9
|
import { Capabilities, createSurface } from '../../common';
|
|
11
10
|
import { contributes } from '../../core';
|
|
12
11
|
import { createIntent } from '../../plugin-intent';
|
|
13
12
|
import { useIntentDispatcher } from '../../react';
|
|
14
13
|
|
|
14
|
+
import { Log } from './schema';
|
|
15
|
+
|
|
15
16
|
export const Logger = () => {
|
|
16
17
|
const { dispatchPromise } = useIntentDispatcher();
|
|
17
18
|
const handleClick = useCallback(() => dispatchPromise(createIntent(Log, { message: 'Hello, world!' })), []);
|
|
@@ -4,11 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
import { log } from '@dxos/log';
|
|
6
6
|
|
|
7
|
-
import { Log } from './schema';
|
|
8
7
|
import { Capabilities, Events } from '../../common';
|
|
9
|
-
import { contributes, defineModule,
|
|
8
|
+
import { contributes, defineModule, definePlugin, lazy } from '../../core';
|
|
10
9
|
import { createResolver } from '../../plugin-intent';
|
|
11
10
|
|
|
11
|
+
import { Log } from './schema';
|
|
12
|
+
|
|
12
13
|
const Toolbar = lazy(() => import('./Toolbar'));
|
|
13
14
|
|
|
14
15
|
export const LoggerPlugin = () =>
|
|
@@ -4,16 +4,18 @@
|
|
|
4
4
|
|
|
5
5
|
import '@dxos-theme';
|
|
6
6
|
|
|
7
|
+
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
7
8
|
import React from 'react';
|
|
8
9
|
|
|
9
10
|
import { withLayout, withTheme } from '@dxos/storybook-utils';
|
|
10
11
|
|
|
12
|
+
import { useApp } from '../components';
|
|
13
|
+
import { IntentPlugin } from '../plugin-intent';
|
|
14
|
+
|
|
11
15
|
import { DebugPlugin } from './debug';
|
|
12
|
-
import {
|
|
16
|
+
import { GeneratorPlugin, createNumberPlugin } from './generator';
|
|
13
17
|
import { LayoutPlugin } from './layout';
|
|
14
18
|
import { LoggerPlugin } from './logger';
|
|
15
|
-
import { useApp } from '../App';
|
|
16
|
-
import { IntentPlugin } from '../plugin-intent';
|
|
17
19
|
|
|
18
20
|
const plugins = [IntentPlugin(), LayoutPlugin(), DebugPlugin(), LoggerPlugin(), GeneratorPlugin()];
|
|
19
21
|
|
|
@@ -21,22 +23,25 @@ const Placeholder = () => {
|
|
|
21
23
|
return <div>Loading...</div>;
|
|
22
24
|
};
|
|
23
25
|
|
|
24
|
-
const
|
|
26
|
+
const DefaultStory = () => {
|
|
25
27
|
const App = useApp({
|
|
26
28
|
pluginLoader: (id) => createNumberPlugin(id),
|
|
27
29
|
plugins,
|
|
28
30
|
core: plugins.map((plugin) => plugin.meta.id),
|
|
29
|
-
// Having a non-empty placeholder makes it clear if it's taking a while to load.
|
|
30
31
|
placeholder: Placeholder,
|
|
31
32
|
});
|
|
32
33
|
|
|
33
34
|
return <App />;
|
|
34
35
|
};
|
|
35
36
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
export default {
|
|
37
|
+
const meta = {
|
|
39
38
|
title: 'sdk/app-framework/playground',
|
|
40
|
-
render:
|
|
39
|
+
render: DefaultStory,
|
|
41
40
|
decorators: [withTheme, withLayout()],
|
|
42
|
-
}
|
|
41
|
+
} satisfies Meta<typeof DefaultStory>;
|
|
42
|
+
|
|
43
|
+
export default meta;
|
|
44
|
+
|
|
45
|
+
type Story = StoryObj<typeof meta>;
|
|
46
|
+
|
|
47
|
+
export const Playground: Story = {};
|
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { INTENT_PLUGIN } from './actions';
|
|
6
5
|
import { Events } from '../common';
|
|
7
6
|
import { defineModule, definePlugin, lazy } from '../core';
|
|
8
7
|
|
|
8
|
+
import { INTENT_PLUGIN } from './actions';
|
|
9
|
+
|
|
9
10
|
export const IntentPlugin = () =>
|
|
10
11
|
definePlugin({ id: INTENT_PLUGIN, name: 'Intent' }, [
|
|
11
12
|
defineModule({
|