Skip to content

Commit 5671929

Browse files
committed
Initial commit of polyMiddleware
1 parent 718d543 commit 5671929

27 files changed

Lines changed: 886 additions & 166 deletions

package-lock.json

Lines changed: 76 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"./packages/test/web-server",
2323
"./packages/core",
2424
"./packages/react-valibot",
25+
"./packages/middleware",
2526
"./packages/redux-store",
2627
"./packages/styles",
2728
"./packages/support/cldr-data-downloader",
@@ -86,6 +87,7 @@
8687
"start:core": "cd packages && cd core && npm start",
8788
"start:directlinespeech": "cd packages && cd directlinespeech && npm start",
8889
"start:fluent-theme": "cd packages && cd fluent-theme && npm start",
90+
"start:middleware": "cd packages && cd middleware && npm start",
8991
"start:react-valibot": "cd packages && cd react-valibot && npm start",
9092
"start:redux-store": "cd packages && cd redux-store && npm start",
9193
"start:server": "serve -p 5000",

packages/api/src/middleware/private/templateMiddleware.ts renamed to packages/api/src/middleware/private/templateMiddleware.tsx

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { warnOnce } from 'botframework-webchat-core';
2+
import React, { memo, type ReactNode } from 'react';
23
import { createChainOfResponsibility, type ComponentMiddleware } from 'react-chain-of-responsibility';
34
import { array, function_, safeParse, type InferOutput } from 'valibot';
45

@@ -58,15 +59,30 @@ function templateMiddleware<Request, Props extends {}>(name: string) {
5859
return EMPTY_ARRAY;
5960
};
6061

61-
const { Provider, Proxy } = createChainOfResponsibility<Request, Props>();
62+
const { Provider, Proxy } = createChainOfResponsibility<Request, Props, string>();
63+
64+
type TemplatedProviderProps = {
65+
readonly children?: ReactNode | undefined;
66+
readonly middleware: readonly Middleware[];
67+
};
68+
69+
// eslint-disable-next-line prefer-arrow-callback
70+
const TemplatedProvider = memo(function TemplatedProvider({ children, middleware }: TemplatedProviderProps) {
71+
return (
72+
<Provider init={name} middleware={middleware}>
73+
{children}
74+
</Provider>
75+
);
76+
});
77+
78+
TemplatedProvider.displayName = `${name}Provider`;
6279

63-
Provider.displayName = `${name}Provider`;
6480
Proxy.displayName = `${name}Proxy`;
6581

6682
return {
6783
createMiddleware,
6884
extractMiddleware,
69-
Provider,
85+
Provider: TemplatedProvider,
7086
Proxy,
7187
'~types': undefined as {
7288
middleware: Middleware;

packages/component/src/Composer.tsx

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ import {
1212
} from 'botframework-webchat-api';
1313
import { DecoratorComposer, type DecoratorMiddleware } from 'botframework-webchat-api/decorator';
1414
import { singleToArray } from 'botframework-webchat-core';
15+
import {
16+
createActivityPolyMiddlewareFromLegacy,
17+
PolyMiddlewareComposer,
18+
type PolyMiddleware
19+
} from 'botframework-webchat-middleware';
1520
import classNames from 'classnames';
1621
import MarkdownIt from 'markdown-it';
1722
import PropTypes from 'prop-types';
@@ -33,6 +38,7 @@ import WebChatUIContext from './hooks/internal/WebChatUIContext';
3338
import { FocusSendBoxScope } from './hooks/sendBoxFocus';
3439
import { ScrollRelativeTranscriptScope } from './hooks/transcriptScrollRelative';
3540
import createDefaultActivityMiddleware from './Middleware/Activity/createCoreMiddleware';
41+
import LegacyActivityBridge from './Middleware/Activity/private/LegacyActivityBridge';
3642
import createDefaultActivityStatusMiddleware from './Middleware/ActivityStatus/createCoreMiddleware';
3743
import createDefaultAttachmentForScreenReaderMiddleware from './Middleware/AttachmentForScreenReader/createCoreMiddleware';
3844
import createDefaultAvatarMiddleware from './Middleware/Avatar/createCoreMiddleware';
@@ -441,6 +447,15 @@ const InternalComposer = ({
441447
[sendBoxToolbarMiddlewareFromProps, theme.sendBoxToolbarMiddleware]
442448
);
443449

450+
const polyMiddlewareArray = useMemo<readonly PolyMiddleware[]>(
451+
() =>
452+
Object.freeze([
453+
// TODO: Add <FallbackComponent>.
454+
createActivityPolyMiddlewareFromLegacy(LegacyActivityBridge, () => undefined, ...patchedActivityMiddleware)
455+
]),
456+
[patchedActivityMiddleware]
457+
);
458+
444459
return (
445460
<APIComposer
446461
activityMiddleware={patchedActivityMiddleware}
@@ -464,22 +479,24 @@ const InternalComposer = ({
464479
<StyleToEmotionObjectComposer nonce={nonce}>
465480
<HTMLContentTransformComposer middleware={htmlContentTransformMiddleware}>
466481
<ReducedMotionComposer>
467-
<BuiltInDecorator>
468-
<DecoratorComposer middleware={decoratorMiddleware}>
469-
<ComposerCore
470-
extraStyleSet={extraStyleSet}
471-
nonce={nonce}
472-
renderMarkdown={renderMarkdown}
473-
styleSet={styleSet}
474-
styles={theme.styles}
475-
suggestedActionsAccessKey={suggestedActionsAccessKey}
476-
webSpeechPonyfillFactory={webSpeechPonyfillFactory}
477-
>
478-
{children}
479-
{onTelemetry && <UITracker />}
480-
</ComposerCore>
481-
</DecoratorComposer>
482-
</BuiltInDecorator>
482+
<PolyMiddlewareComposer middleware={polyMiddlewareArray}>
483+
<BuiltInDecorator>
484+
<DecoratorComposer middleware={decoratorMiddleware}>
485+
<ComposerCore
486+
extraStyleSet={extraStyleSet}
487+
nonce={nonce}
488+
renderMarkdown={renderMarkdown}
489+
styleSet={styleSet}
490+
styles={theme.styles}
491+
suggestedActionsAccessKey={suggestedActionsAccessKey}
492+
webSpeechPonyfillFactory={webSpeechPonyfillFactory}
493+
>
494+
{children}
495+
{onTelemetry && <UITracker />}
496+
</ComposerCore>
497+
</DecoratorComposer>
498+
</BuiltInDecorator>
499+
</PolyMiddlewareComposer>
483500
</ReducedMotionComposer>
484501
</HTMLContentTransformComposer>
485502
</StyleToEmotionObjectComposer>

packages/component/src/Transcript/TranscriptActivity.tsx renamed to packages/component/src/Middleware/Activity/private/LegacyActivityBridge.tsx

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,27 @@
1-
import { hooks, type ActivityComponentFactory } from 'botframework-webchat-api';
2-
import { type WebChatActivity } from 'botframework-webchat-core';
1+
import { hooks } from 'botframework-webchat-api';
2+
import { bridgeComponentPropsSchema, type BridgeComponentProps } from 'botframework-webchat-middleware';
3+
import { validateProps } from 'botframework-webchat-react-valibot';
34
import React, { memo, useCallback, useMemo } from 'react';
45

5-
import useFirstActivityInSenderGroup from '../Middleware/ActivityGrouping/ui/SenderGrouping/useFirstActivity';
6-
import useLastActivityInSenderGroup from '../Middleware/ActivityGrouping/ui/SenderGrouping/useLastActivity';
7-
import useFirstActivityInStatusGroup from '../Middleware/ActivityGrouping/ui/StatusGrouping/useFirstActivity';
8-
import useLastActivityInStatusGroup from '../Middleware/ActivityGrouping/ui/StatusGrouping/useLastActivity';
9-
import useActivityElementMapRef from '../providers/ChatHistoryDOM/useActivityElementRef';
10-
import isZeroOrPositive from '../Utils/isZeroOrPositive';
11-
import ActivityRow from './ActivityRow';
6+
import useActivityElementMapRef from '../../../providers/ChatHistoryDOM/useActivityElementRef';
7+
import ActivityRow from '../../../Transcript/ActivityRow';
8+
import isZeroOrPositive from '../../../Utils/isZeroOrPositive';
9+
import useFirstActivityInSenderGroup from '../../ActivityGrouping/ui/SenderGrouping/useFirstActivity';
10+
import useLastActivityInSenderGroup from '../../ActivityGrouping/ui/SenderGrouping/useLastActivity';
11+
import useFirstActivityInStatusGroup from '../../ActivityGrouping/ui/StatusGrouping/useFirstActivity';
12+
import useLastActivityInStatusGroup from '../../ActivityGrouping/ui/StatusGrouping/useLastActivity';
1213

13-
const { useCreateActivityStatusRenderer, useCreateAvatarRenderer, useGetKeyByActivity, useStyleOptions } = hooks;
14+
const {
15+
useCreateActivityStatusRenderer,
16+
useCreateAvatarRenderer,
17+
useGetKeyByActivity,
18+
useRenderAttachment,
19+
useStyleOptions
20+
} = hooks;
1421

15-
type TranscriptActivityProps = Readonly<{
16-
activity: WebChatActivity;
17-
renderActivity: Exclude<ReturnType<ActivityComponentFactory>, false>;
18-
}>;
22+
function LegacyActivityBridge(props: BridgeComponentProps) {
23+
const { activity, render } = validateProps(bridgeComponentPropsSchema, props);
1924

20-
const TranscriptActivity = ({ activity, renderActivity }: TranscriptActivityProps) => {
2125
const [{ bubbleFromUserNubOffset, bubbleNubOffset, groupTimestamp, showAvatarInGroup }] = useStyleOptions();
2226
const [firstActivityInSenderGroup] = useFirstActivityInSenderGroup();
2327
const [firstActivityInStatusGroup] = useFirstActivityInStatusGroup();
@@ -26,9 +30,9 @@ const TranscriptActivity = ({ activity, renderActivity }: TranscriptActivityProp
2630
const activityElementMapRef = useActivityElementMapRef();
2731
const createActivityStatusRenderer = useCreateActivityStatusRenderer();
2832
const getKeyByActivity = useGetKeyByActivity();
33+
const renderAttachment = useRenderAttachment();
2934
const renderAvatar = useCreateAvatarRenderer();
3035

31-
const activityKey: string = useMemo(() => getKeyByActivity(activity), [activity, getKeyByActivity]);
3236
const hideAllTimestamps = groupTimestamp === false;
3337
const isFirstInSenderGroup =
3438
firstActivityInSenderGroup === activity || typeof firstActivityInSenderGroup === 'undefined';
@@ -53,6 +57,7 @@ const TranscriptActivity = ({ activity, renderActivity }: TranscriptActivityProp
5357
[activity, createActivityStatusRenderer]
5458
);
5559

60+
const activityKey: string = useMemo(() => getKeyByActivity(activity), [activity, getKeyByActivity]);
5661
const activityCallbackRef = useCallback(
5762
(activityElement: HTMLElement) => {
5863
activityElement
@@ -83,23 +88,22 @@ const TranscriptActivity = ({ activity, renderActivity }: TranscriptActivityProp
8388
showCallout = true;
8489
}
8590

86-
const children = useMemo(
91+
const node = useMemo(
8792
() =>
88-
renderActivity({
93+
render(renderAttachment, {
8994
hideTimestamp,
9095
renderActivityStatus,
9196
renderAvatar: renderAvatarForSenderGroup,
9297
showCallout
9398
}),
94-
[hideTimestamp, renderActivity, renderActivityStatus, renderAvatarForSenderGroup, showCallout]
99+
[render, hideTimestamp, renderActivityStatus, renderAttachment, renderAvatarForSenderGroup, showCallout]
95100
);
96101

97102
return (
98103
<ActivityRow activity={activity} ref={activityCallbackRef}>
99-
{children}
104+
{node}
100105
</ActivityRow>
101106
);
102-
};
107+
}
103108

104-
export default memo(TranscriptActivity);
105-
export { type TranscriptActivityProps };
109+
export default memo(LegacyActivityBridge);
Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,40 @@
11
import { hooks } from 'botframework-webchat-api';
22
import { type WebChatActivity } from 'botframework-webchat-core';
3+
import { validateProps } from 'botframework-webchat-react-valibot';
34
import React, { Fragment, memo } from 'react';
4-
import useGetRenderActivityCallback from '../../../providers/RenderingActivities/useGetRenderActivityCallback';
5-
import TranscriptActivity from '../../../Transcript/TranscriptActivity';
5+
import { array, custom, object, pipe, readonly, safeParse, type InferInput } from 'valibot';
6+
7+
import useActivityRendererMap from '../../../providers/RenderingActivities/useActivityRendererMap';
68

79
const { useGetKeyByActivity } = hooks;
810

9-
type RenderActivityGroupingProps = Readonly<{
10-
activities: readonly WebChatActivity[];
11-
}>;
11+
const renderActivityGroupingPropsSchema = pipe(
12+
object({
13+
activities: pipe(array(custom<WebChatActivity>(value => safeParse(object({}), value).success)), readonly())
14+
}),
15+
readonly()
16+
);
17+
18+
type RenderActivityGroupingProps = Readonly<InferInput<typeof renderActivityGroupingPropsSchema>>;
1219

13-
const RenderActivityGrouping = ({ activities }: RenderActivityGroupingProps) => {
20+
const RenderActivityGrouping = (props: RenderActivityGroupingProps) => {
21+
const { activities } = validateProps(renderActivityGroupingPropsSchema, props);
22+
23+
const [activityRendererMap] = useActivityRendererMap();
1424
const getKeyByActivity = useGetKeyByActivity();
15-
const getRenderActivityCallback = useGetRenderActivityCallback();
1625

1726
return (
1827
<Fragment>
19-
{activities.map(activity => (
20-
<TranscriptActivity
21-
activity={activity}
22-
key={getKeyByActivity(activity)}
23-
renderActivity={getRenderActivityCallback(activity)}
24-
/>
25-
))}
28+
{activities.map(activity => {
29+
const children = activityRendererMap.get(activity)?.({});
30+
31+
return children && <Fragment key={getKeyByActivity(activity)}>{children}</Fragment>;
32+
})}
2633
</Fragment>
2734
);
2835
};
2936

3037
RenderActivityGrouping.displayName = 'RenderActivityGrouping';
3138

3239
export default memo(RenderActivityGrouping);
33-
export { type RenderActivityGroupingProps };
40+
export { renderActivityGroupingPropsSchema, type RenderActivityGroupingProps };

0 commit comments

Comments
 (0)