From 7aee3cf01764a272e7b0a09045ff674575b15035 Mon Sep 17 00:00:00 2001 From: Odin Thomas Rochmann Date: Fri, 28 Jul 2023 13:54:06 +0200 Subject: [PATCH] Feat(modules-event-event): create hook for observing events (#1076) * fix(module-event): expose event stream expose event stream from `IEventModuleProvider` * feat(react-module-event): create hook for observing events * docs: update event module docs --- .changeset/dry-parents-design.md | 5 + .changeset/light-cows-play.md | 22 ++++ packages/modules/event/src/provider.ts | 6 + packages/react/modules/event/src/index.ts | 1 + .../react/modules/event/src/useEventStream.ts | 30 +++++ .../event/src/useModulesEventProvider.ts | 1 + vue-press/src/.vuepress/sidebar.ts | 17 ++- vue-press/src/modules/event/README.md | 70 +---------- vue-press/src/modules/event/react.md | 118 ++++++++++++++++++ 9 files changed, 201 insertions(+), 69 deletions(-) create mode 100644 .changeset/dry-parents-design.md create mode 100644 .changeset/light-cows-play.md create mode 100644 packages/react/modules/event/src/useEventStream.ts create mode 100644 vue-press/src/modules/event/react.md diff --git a/.changeset/dry-parents-design.md b/.changeset/dry-parents-design.md new file mode 100644 index 000000000..f981bcfb0 --- /dev/null +++ b/.changeset/dry-parents-design.md @@ -0,0 +1,5 @@ +--- +'@equinor/fusion-framework-module-event': patch +--- + +expose event stream from `IEventModuleProvider` diff --git a/.changeset/light-cows-play.md b/.changeset/light-cows-play.md new file mode 100644 index 000000000..eda2353c9 --- /dev/null +++ b/.changeset/light-cows-play.md @@ -0,0 +1,22 @@ +--- +'@equinor/fusion-framework-react-module-event': minor +--- + +Add hook for observing event streams + +```ts +/* observe stream of events */ +const someEvent$ = useEventStream( + 'some_event', + useCallback( + (event$) => event$.pipe( + // only some events + filter(e => e.detail.foo === dep.foo), + // mutate data + map(e => e.detail) + ) + ), [dep] +); +/* use state of stream */ +const someEvent = useObservableState(someEvent$); +``` \ No newline at end of file diff --git a/packages/modules/event/src/provider.ts b/packages/modules/event/src/provider.ts index 61fccb6fc..ece69542c 100644 --- a/packages/modules/event/src/provider.ts +++ b/packages/modules/event/src/provider.ts @@ -12,6 +12,8 @@ import { } from './event'; export interface IEventModuleProvider { + readonly event$: Observable; + /** subscribe to a known mapped event @see {@link FrameworkEventMap} */ addEventListener( type: TKey, @@ -69,6 +71,10 @@ export class EventModuleProvider private __dispatcher: FrameworkEventDispatcher; + get event$(): Observable { + return this.__event$.asObservable(); + } + get closed() { return this.__event$.closed; } diff --git a/packages/react/modules/event/src/index.ts b/packages/react/modules/event/src/index.ts index 075301e21..6d0220279 100644 --- a/packages/react/modules/event/src/index.ts +++ b/packages/react/modules/event/src/index.ts @@ -5,3 +5,4 @@ export { EventConsumer, EventProvider } from './eventContext'; export { useEventProvider } from './useEventProvider'; export { useModulesEventProvider } from './useModulesEventProvider'; export { useEventHandler } from './useEventHandler'; +export { useEventStream, type EventStream } from './useEventStream'; diff --git a/packages/react/modules/event/src/useEventStream.ts b/packages/react/modules/event/src/useEventStream.ts new file mode 100644 index 000000000..e2e622d1a --- /dev/null +++ b/packages/react/modules/event/src/useEventStream.ts @@ -0,0 +1,30 @@ +import { useMemo } from 'react'; + +import { Observable, OperatorFunction } from 'rxjs'; + +import { FrameworkEventMap, filterEvent } from '@equinor/fusion-framework-module-event'; +import { useEventProvider } from 'EventProvider'; + +export type EventStream = + Observable; + +/** + * Hook for observing events + * + * @param key name of the event to filter out + * @param operator [optional] {@link OperatorFunction} for transforming the stream __must be memorized!__ + */ +export const useEventStream = < + TKey extends keyof FrameworkEventMap = keyof FrameworkEventMap, + TData = FrameworkEventMap[TKey] +>( + key: TKey, + operator?: OperatorFunction +): Observable => { + const provider = useEventProvider(); + return useMemo(() => { + return provider.event$.pipe(filterEvent(key), operator ?? ((x) => x as Observable)); + }, [provider, key, operator]); +}; + +export default useEventStream; diff --git a/packages/react/modules/event/src/useModulesEventProvider.ts b/packages/react/modules/event/src/useModulesEventProvider.ts index bfaf9a0b2..54de89133 100644 --- a/packages/react/modules/event/src/useModulesEventProvider.ts +++ b/packages/react/modules/event/src/useModulesEventProvider.ts @@ -8,6 +8,7 @@ import { /** * Hook for using the event module from the closes ModuleProvider + * @see {@link useModule} */ export const useModulesEventProvider = (): IEventModuleProvider | undefined => useModule(eventModuleKey); diff --git a/vue-press/src/.vuepress/sidebar.ts b/vue-press/src/.vuepress/sidebar.ts index 4079248b4..c812d5563 100644 --- a/vue-press/src/.vuepress/sidebar.ts +++ b/vue-press/src/.vuepress/sidebar.ts @@ -41,7 +41,22 @@ export default sidebar({ link: 'context/', }, 'app/', - 'event/', + { + text: 'Event', + prefix: 'event/', + link: 'event/', + children: [ + { + text: 'module', + link: 'README.md', + }, + { + text: 'React', + link: 'react.md', + }, + ] + + }, 'navigation/', 'bookmark/', 'ag-grid/', diff --git a/vue-press/src/modules/event/README.md b/vue-press/src/modules/event/README.md index eb80180f8..621d62193 100644 --- a/vue-press/src/modules/event/README.md +++ b/vue-press/src/modules/event/README.md @@ -1,12 +1,12 @@ --- -title: Events +title: Event Module category: Module tag: - event - core --- - + ## Concept @@ -164,69 +164,3 @@ config.event.onBubble = (e) => { ref.event.dispatch(e); } ``` - -## React - - - -### EventProvider - -example app: -```tsx -import { EventProvider } = from '@equinor/fusion-framework-react-module-event'; -import { useFramework } = from '@equinor/fusion-framework-react-app/framework'; -const Content = () => { - const framework = useFramework().modules.event; - return ( - - - - ); -}; -``` -```tsx -const InnerContent = () => { - const eventProvider = useEventProvider(); - useEventHandler('some_event', useCallback((event) => { - console.log('FRAMEWORK_EVENT', event.detail); - }, [eventProvider])); - - const appEventProvider = useEventModuleProvider(); - useEventHandler('some_event', useCallback((event) => { - console.log('APP_EVENT', event.detail); - }, [appEventProvider])); -} -``` - -### Hooks - -#### useEventProvider - -use `IEventModuleProvider` from current context see [EventProvider](#EventProvider) -```ts -import { useEventHandler } from '@equinor/fusion-framework-react-module-event'; -``` - -#### useEventModuleProvider - -use `IEventModuleProvider` from closes module provider - -```ts -import { useEventModuleProvider } from '@equinor/fusion-framework-react-module-event'; -``` - - -#### useEventHandler -```ts -import { useEventHandler } from '@equinor/fusion-framework-react-module-event'; - -const MyHook = () => { - useEventHandler( - 'onContextChange', - /** note that callback must be memorized */ - useCallback((e) => { - console.log(e.detail); - }, [deps]); - ); -} -``` diff --git a/vue-press/src/modules/event/react.md b/vue-press/src/modules/event/react.md new file mode 100644 index 000000000..1b632a425 --- /dev/null +++ b/vue-press/src/modules/event/react.md @@ -0,0 +1,118 @@ +--- +title: Event Module - React +category: Module +tag: + - react + - event +--- + + + +```ts +import { useEventProvider } from '@equinor/fusion-framework-react-module-event'; + +/* fetch the `ÌEventModuleProvider` from the closes module provder */ +const eventProvider = useEventProvider(); +``` + +## EventProvider + +if needed, the resolving of which `ÌEventModuleProvider` the `useEventProvider` will provide can be altered by using +the `EventProvider` component + +**example app:** +```tsx +import { EventProvider, EventConsumer } = from '@equinor/fusion-framework-react-module-event'; +import { useFramework } = from '@equinor/fusion-framework-react-app/framework'; +const Content = () => { + const framework = useFramework().modules.event; + return ( + + + + + ); +}; +``` +```tsx +import { useEventHandler } = from '@equinor/fusion-framework-react-module-event'; + +const eventHandler = (event: FrameworkEventMap['some_event']) => { + console.log(event.detail); +}; + +const EventLogger = () => useEventHandler('some_event', eventHandler); +``` + +```tsx +import { EventConsumer } = from '@equinor/fusion-framework-react-module-event'; + +const InlineEventConsumer = () => ( + + { + (provider) => provider.dispatch( + 'some_event'), + { detail: { foo: 'bar' } } + } + +) +`````` + +## Hooks + +### useEventProvider + +use `IEventModuleProvider` from current context see [EventProvider](#EventProvider) +```ts +import { useEventProvider } from '@equinor/fusion-framework-react-module-event'; +``` + +### useEventModuleProvider + +use `IEventModuleProvider` from closes module provider + +```ts +import { useEventModuleProvider } from '@equinor/fusion-framework-react-module-event'; +``` + + +### useEventHandler +```ts +import { useEventHandler } from '@equinor/fusion-framework-react-module-event'; + +useEventHandler( + 'onContextChange', + /** note that callback must be memorized */ + useCallback((e) => { + console.log(e.detail); + }, [deps]); +); +``` + +### useEventStream + +```ts +import { useEventStream, EventStream } from '@equinor/fusion-framework-react-module-event'; + +/* simple usage */ +const { value: someEvent } = useObservableState(useEventStream('some_event')); + +/* observe stream of events */ +const someEvent$ = useEventStream( + 'some_event', + /* note that callback must be memorized */ + useCallback( + /* note react.useCallback cannot resolve source input */ + (event$: EventStream<'some_event'>) => event$.pipe( + /* only some events */ + filter(e => e.detail.foo === dep.foo), + /* mutate data */ + map(e => e.detail) + ), + [dep] + ) +); +/* use state of stream */ +const { value: foo } = useObservableState(someEvent$); + +```