Skip to content

Commit 7fcdd2b

Browse files
committed
Add AvatarPolymiddlewareProxy
1 parent 4b0bfe9 commit 7fcdd2b

5 files changed

Lines changed: 190 additions & 2 deletions

File tree

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
<!doctype html>
2+
<html lang="en-US">
3+
<head>
4+
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
5+
</head>
6+
<body>
7+
<main id="webchat"></main>
8+
<script type="importmap">
9+
{
10+
"imports": {
11+
"botframework-webchat": "/__dist__/packages/bundle/static/botframework-webchat.js",
12+
"botframework-webchat/component": "/__dist__/packages/bundle/static/botframework-webchat/component.js",
13+
"botframework-webchat/middleware": "/__dist__/packages/bundle/static/botframework-webchat/middleware.js",
14+
"react": "/__dist__/packages/bundle/static/react.js",
15+
"react-dom": "/__dist__/packages/bundle/static/react-dom.js",
16+
"react-dom/client": "/__dist__/packages/bundle/static/react-dom/client.js"
17+
}
18+
}
19+
</script>
20+
<script type="module">
21+
import '/test-harness.mjs';
22+
import '/test-page-object.mjs';
23+
24+
import { createStoreWithOptions, renderWebChat, testIds } from 'botframework-webchat';
25+
import { Composer } from 'botframework-webchat/component';
26+
import {
27+
AvatarPolymiddlewareProxy,
28+
avatarComponent,
29+
createAvatarPolymiddleware
30+
} from 'botframework-webchat/middleware';
31+
import { createElement } from 'react';
32+
import { createRoot } from 'react-dom/client';
33+
34+
const {
35+
testHelpers: { createDirectLineEmulator }
36+
} = window;
37+
38+
testHelpers.hideKnownError();
39+
40+
// TODO: Should find ways to eliminate this line.
41+
window.WebChat = { createStoreWithOptions, testIds };
42+
43+
run(async function () {
44+
const { directLine, store } = createDirectLineEmulator();
45+
46+
const MiddlewareAvatar = ({ activity, children, fromUser }) =>
47+
createElement(
48+
'div',
49+
{
50+
style: {
51+
borderRadius: 40,
52+
height: 40,
53+
outlineColor: 'green',
54+
outlineOffset: 2,
55+
outlineStyle: fromUser ? 'dotted' : 'dashed',
56+
outlineWidth: 2,
57+
overflow: 'hidden',
58+
width: 40
59+
}
60+
},
61+
children
62+
);
63+
64+
const PolymiddlewareAvatar = ({ activity, children }) =>
65+
createElement(
66+
'div',
67+
{
68+
style: {
69+
borderRadius: 32,
70+
height: 32,
71+
margin: 4,
72+
outlineColor: 'red',
73+
outlineOffset: 2,
74+
outlineStyle: activity.from?.role === 'user' ? 'dotted' : 'dashed',
75+
outlineWidth: 2,
76+
overflow: 'hidden',
77+
width: 32
78+
}
79+
},
80+
children
81+
);
82+
83+
createRoot(document.getElementById('webchat')).render(
84+
createElement(
85+
Composer,
86+
{
87+
avatarMiddleware: [
88+
() => next => request => {
89+
const children = next(request);
90+
91+
return (...args) =>
92+
createElement(
93+
MiddlewareAvatar,
94+
{ activity: request.activity, fromUser: request.fromUser },
95+
children(...args)
96+
);
97+
}
98+
],
99+
directLine,
100+
polymiddleware: Object.freeze([
101+
createAvatarPolymiddleware(next => request => {
102+
const children = next(request)?.render();
103+
104+
return avatarComponent(PolymiddlewareAvatar, { activity: request.activity, children });
105+
})
106+
]),
107+
store,
108+
styleOptions: {
109+
botAvatarImage: '/assets/bot-avatar.jpg',
110+
botAvatarInitials: 'WC',
111+
userAvatarImage: '/assets/user-avatar.jpg',
112+
userAvatarInitials: 'WW'
113+
}
114+
},
115+
createElement(
116+
'div',
117+
{
118+
style: {
119+
margin: 4
120+
}
121+
},
122+
createElement(AvatarPolymiddlewareProxy, {
123+
activity: {
124+
from: { role: 'bot' },
125+
id: 'a-00001',
126+
text: 'Hello, World!',
127+
type: 'messagenp'
128+
}
129+
})
130+
)
131+
)
132+
);
133+
134+
await host.snapshot('local');
135+
});
136+
</script>
137+
</body>
138+
</html>
6.94 KB
Loading

packages/api-middleware/src/avatarPolymiddleware.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ const {
2626
useBuildRenderCallback: useBuildRenderAvatarCallback
2727
} = templatePolymiddleware<
2828
{
29+
// TODO: The `styleOptions` is only for legacy middleware.
2930
readonly [__INTERNAL_DO_NOT_USE__avatarPolymiddlewareRequestStyleOptionsSymbol]: any;
3031
readonly activity: WebChatActivity;
3132
},

packages/api/src/boot/middleware.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,21 @@ export {
1818

1919
export {
2020
avatarComponent,
21-
AvatarPolymiddlewareProxy,
2221
createAvatarPolymiddleware,
2322
useBuildRenderAvatarCallback,
2423
type AvatarPolymiddleware,
2524
type AvatarPolymiddlewareHandler,
2625
type AvatarPolymiddlewareHandlerResult,
2726
type AvatarPolymiddlewareProps,
28-
type AvatarPolymiddlewareProxyProps,
2927
type AvatarPolymiddlewareRenderer,
3028
type AvatarPolymiddlewareRequest
3129
} from '@msinternal/botframework-webchat-api-middleware';
3230

31+
export {
32+
default as AvatarPolymiddlewareProxy,
33+
AvatarPolymiddlewareProxyProps
34+
} from '../middleware/AvatarPolymiddlewareProxy';
35+
3336
export {
3437
createErrorBoxPolymiddleware,
3538
errorBoxComponent,
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// We need to patch <AvatarPolymiddlewareProxy> from `api-middleware`.
2+
//
3+
// - Props of <AvatarPolymiddlewareProxy> should not need `styleOptions`
4+
// - We should call `useStyleOptions()` to get style options
5+
// - However, `api-middleware` is before `api`, so it do not have access to `useStyleOptions`
6+
//
7+
// Until we have `api-style-options`, we have to patch <AvatarPolymiddlewareProxy> inside `api`.
8+
9+
import {
10+
__INTERNAL_DO_NOT_USE__avatarPolymiddlewareRequestStyleOptionsSymbol,
11+
AvatarPolymiddlewareProxy as RawAvatarPolymiddlewareProxy
12+
} from '@msinternal/botframework-webchat-api-middleware';
13+
import { validateProps } from '@msinternal/botframework-webchat-react-valibot';
14+
import type { WebChatActivity } from 'botframework-webchat-core';
15+
import React, { memo, useMemo } from 'react';
16+
import { custom, object, pipe, readonly, safeParse, type InferInput } from 'valibot';
17+
import { useStyleOptions } from '../hooks';
18+
19+
const avatarPolymiddlewareProxyPropsSchema = pipe(
20+
object({
21+
activity: custom<Readonly<WebChatActivity>>(value => safeParse(object({}), value).success)
22+
}),
23+
readonly()
24+
);
25+
26+
type AvatarPolymiddlewareProxyProps = Readonly<InferInput<typeof avatarPolymiddlewareProxyPropsSchema>>;
27+
28+
const AvatarPolymiddlewareProxy = memo((props: AvatarPolymiddlewareProxyProps) => {
29+
const { activity } = validateProps(avatarPolymiddlewareProxyPropsSchema, props);
30+
31+
const [styleOptions] = useStyleOptions();
32+
33+
const rawProps = useMemo(
34+
() => ({
35+
[__INTERNAL_DO_NOT_USE__avatarPolymiddlewareRequestStyleOptionsSymbol]: styleOptions
36+
}),
37+
[styleOptions]
38+
);
39+
40+
return <RawAvatarPolymiddlewareProxy activity={activity} {...rawProps} />;
41+
});
42+
43+
AvatarPolymiddlewareProxy.displayName = 'AvatarPolymiddlewareProxy';
44+
45+
export default AvatarPolymiddlewareProxy;
46+
export { avatarPolymiddlewareProxyPropsSchema, type AvatarPolymiddlewareProxyProps };

0 commit comments

Comments
 (0)