Skip to content

Commit 12365da

Browse files
committed
feat: template decorator
1 parent 40736b1 commit 12365da

11 files changed

Lines changed: 327 additions & 250 deletions

File tree

Lines changed: 130 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -1,138 +1,141 @@
11
<!doctype html>
22
<html lang="en-US">
3-
<head>
4-
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
5-
<script crossorigin="anonymous" src="https://unpkg.com/@babel/standalone@7.8.7/babel.min.js"></script>
6-
<script crossorigin="anonymous" src="https://unpkg.com/react@16.8.6/umd/react.production.min.js"></script>
7-
<script crossorigin="anonymous" src="https://unpkg.com/react-dom@16.8.6/umd/react-dom.production.min.js"></script>
8-
<script crossorigin="anonymous" src="/test-harness.js"></script>
9-
<script crossorigin="anonymous" src="/test-page-object.js"></script>
10-
<script crossorigin="anonymous" src="/__dist__/webchat-es5.js"></script>
11-
<script crossorigin="anonymous" src="/__dist__/botframework-webchat-fluent-theme.production.min.js"></script>
12-
<style>
13-
.flair {
14-
border-radius: inherit;
15-
border: solid 2px red;
3+
4+
<head>
5+
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
6+
<script crossorigin="anonymous" src="https://unpkg.com/@babel/standalone@7.8.7/babel.min.js"></script>
7+
<script crossorigin="anonymous" src="https://unpkg.com/react@16.8.6/umd/react.production.min.js"></script>
8+
<script crossorigin="anonymous" src="https://unpkg.com/react-dom@16.8.6/umd/react-dom.production.min.js"></script>
9+
<script crossorigin="anonymous" src="/test-harness.js"></script>
10+
<script crossorigin="anonymous" src="/test-page-object.js"></script>
11+
<script crossorigin="anonymous" src="/__dist__/webchat-es5.js"></script>
12+
<script crossorigin="anonymous" src="/__dist__/botframework-webchat-fluent-theme.production.min.js"></script>
13+
<style>
14+
.flair {
15+
border-radius: inherit;
16+
border: solid 2px red;
17+
}
18+
19+
.loader {
20+
border-bottom: solid 4px blue;
21+
}
22+
</style>
23+
</head>
24+
25+
<body>
26+
<main id="webchat"></main>
27+
<script type="text/babel">
28+
run(async function () {
29+
const {
30+
React,
31+
ReactDOM: { render },
32+
WebChat: {
33+
decorator: { createActivityBorderMiddleware, DecoratorComposer },
34+
FluentThemeProvider,
35+
ReactWebChat
36+
}
37+
} = window; // Imports in UMD fashion.
38+
39+
function Flair({ request, Next, ...props }) {
40+
return <div className={request.livestreamingState === 'completing' ? 'flair' : ''}>
41+
<Next {...props} showFlair={false} />
42+
</div>;
1643
}
1744

18-
.loader {
19-
border-bottom: solid 4px blue;
45+
function Loader({ request, Next, ...props }) {
46+
return <div className={request.livestreamingState === 'preparing' ? 'loader' : ''}>
47+
<Next {...props} showLoader={false} />
48+
</div>;
2049
}
21-
</style>
22-
</head>
23-
<body>
24-
<main id="webchat"></main>
25-
<script type="text/babel">
26-
run(async function () {
27-
const {
28-
React,
29-
ReactDOM: { render },
30-
WebChat: {
31-
decorator: { createActivityBorderMiddleware, DecoratorComposer },
32-
FluentThemeProvider,
33-
ReactWebChat
34-
}
35-
} = window; // Imports in UMD fashion.
36-
37-
function Flair({ children }) {
38-
return <div className="flair">{children}</div>;
39-
}
4050

41-
function Loader({ children }) {
42-
return <div className="loader">{children}</div>;
43-
}
51+
const decoratorMiddleware = [
52+
createActivityBorderMiddleware(Flair),
53+
createActivityBorderMiddleware(Loader)
54+
];
4455

45-
const decoratorMiddleware = [
46-
createActivityBorderMiddleware(
47-
next => request => (request.livestreamingState === 'completing' ? Flair : next(request))
48-
),
49-
createActivityBorderMiddleware(
50-
next => request => (request.livestreamingState === 'preparing' ? Loader : next(request))
51-
)
52-
];
53-
54-
const { directLine, store } = testHelpers.createDirectLineEmulator();
55-
56-
const App = () => (
57-
<ReactWebChat
58-
directLine={directLine}
59-
store={store}
60-
styleOptions={{
61-
bubbleBorderRadius: 10,
62-
typingAnimationBackgroundImage: `url('data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAAUACgDASIAAhEBAxEB/8QAGgABAQACAwAAAAAAAAAAAAAAAAYCBwMFCP/EACsQAAECBQIEBQUAAAAAAAAAAAECAwAEBQYRBxITIjFBMlFhccFScoGh8f/EABQBAQAAAAAAAAAAAAAAAAAAAAD/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwD0lctx023JVD9UeKOIcNoSNylkdcCMbauSmXHLOPUx8r4ZAcQtO1SM9Mj5iO1gtWo1syc7S2zMKYSptbIPNgnII8/5HBpRZ9RpaKjNVVCpUzLPAQ1nmA7qPl6fmAondRrcaqhkVTiiQrYXgglsH7vnpHc3DcNNoEimaqT4Q2s4bCRuUs+gEaLd05uNFVMmiS3o3YEwFDhlP1Z7e3WLzUuzahUKHRk0zM07TmeApvOFLGEjcM9+Xp6wFnbN0Uu5GnF0x4qW1je2tO1Sc9Djy9oRD6QWlU6PPzVSqjRlgtksttKPMcqBKiO3h/cIDacIQgEIQgEIQgP/2Q==')`
63-
}}
64-
/>
65-
);
66-
67-
render(
68-
<FluentThemeProvider>
69-
<DecoratorComposer middleware={decoratorMiddleware}>
70-
<App />
71-
</DecoratorComposer>
72-
</FluentThemeProvider>,
73-
document.getElementById('webchat')
74-
);
75-
76-
await pageConditions.uiConnected();
77-
78-
await directLine.emulateIncomingActivity({
79-
channelData: { streamSequence: 1, streamType: 'informative' },
80-
from: {
81-
id: 'u-00001',
82-
name: 'Bot',
83-
role: 'bot'
84-
},
85-
id: 't-00001',
86-
text: 'Working on it...',
87-
type: 'typing'
88-
});
89-
90-
await pageConditions.numActivitiesShown(1);
91-
await host.snapshot();
92-
93-
const attachments = [
94-
{
95-
content: {
96-
type: 'AdaptiveCard',
97-
$schema: 'http://adaptivecards.io/schemas/adaptive-card.json',
98-
version: '1.5',
99-
actions: [
100-
{ type: 'Action.Submit', title: 'Button 1' },
101-
{
102-
type: 'Action.ShowCard',
103-
title: 'Show card',
104-
card: {
105-
type: 'AdaptiveCard',
106-
$schema: 'http://adaptivecards.io/schemas/adaptive-card.json',
107-
version: '1.5',
108-
actions: [
109-
{ type: 'Action.Submit', title: 'Button 2' },
110-
{ type: 'Action.Submit', title: 'Button 3' }
111-
]
112-
}
56+
const { directLine, store } = testHelpers.createDirectLineEmulator();
57+
58+
const App = () => (
59+
<ReactWebChat
60+
directLine={directLine}
61+
store={store}
62+
styleOptions={{
63+
bubbleBorderRadius: 10,
64+
typingAnimationBackgroundImage: `url('data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAAUACgDASIAAhEBAxEB/8QAGgABAQACAwAAAAAAAAAAAAAAAAYCBwMFCP/EACsQAAECBQIEBQUAAAAAAAAAAAECAwAEBQYRBxITIjFBMlFhccFScoGh8f/EABQBAQAAAAAAAAAAAAAAAAAAAAD/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwD0lctx023JVD9UeKOIcNoSNylkdcCMbauSmXHLOPUx8r4ZAcQtO1SM9Mj5iO1gtWo1syc7S2zMKYSptbIPNgnII8/5HBpRZ9RpaKjNVVCpUzLPAQ1nmA7qPl6fmAondRrcaqhkVTiiQrYXgglsH7vnpHc3DcNNoEimaqT4Q2s4bCRuUs+gEaLd05uNFVMmiS3o3YEwFDhlP1Z7e3WLzUuzahUKHRk0zM07TmeApvOFLGEjcM9+Xp6wFnbN0Uu5GnF0x4qW1je2tO1Sc9Djy9oRD6QWlU6PPzVSqjRlgtksttKPMcqBKiO3h/cIDacIQgEIQgEIQgP/2Q==')`
65+
}}
66+
/>
67+
);
68+
69+
render(
70+
<FluentThemeProvider>
71+
<DecoratorComposer middleware={decoratorMiddleware}>
72+
<App />
73+
</DecoratorComposer>
74+
</FluentThemeProvider>,
75+
document.getElementById('webchat')
76+
);
77+
78+
await pageConditions.uiConnected();
79+
80+
await directLine.emulateIncomingActivity({
81+
channelData: { streamSequence: 1, streamType: 'informative' },
82+
from: {
83+
id: 'u-00001',
84+
name: 'Bot',
85+
role: 'bot'
86+
},
87+
id: 't-00001',
88+
text: 'Working on it...',
89+
type: 'typing'
90+
});
91+
92+
await pageConditions.numActivitiesShown(1);
93+
await host.snapshot();
94+
95+
const attachments = [
96+
{
97+
content: {
98+
type: 'AdaptiveCard',
99+
$schema: 'http://adaptivecards.io/schemas/adaptive-card.json',
100+
version: '1.5',
101+
actions: [
102+
{ type: 'Action.Submit', title: 'Button 1' },
103+
{
104+
type: 'Action.ShowCard',
105+
title: 'Show card',
106+
card: {
107+
type: 'AdaptiveCard',
108+
$schema: 'http://adaptivecards.io/schemas/adaptive-card.json',
109+
version: '1.5',
110+
actions: [
111+
{ type: 'Action.Submit', title: 'Button 2' },
112+
{ type: 'Action.Submit', title: 'Button 3' }
113+
]
113114
}
114-
]
115-
},
116-
contentType: 'application/vnd.microsoft.card.adaptive'
117-
}
118-
];
119-
await directLine.emulateIncomingActivity({
120-
attachments,
121-
channelData: { streamId: 't-00001', streamType: 'final' },
122-
from: {
123-
id: 'u-00001',
124-
name: 'Bot',
125-
role: 'bot'
115+
}
116+
]
126117
},
127-
id: 'm-00001',
128-
text: 'Work completed!'
129-
});
118+
contentType: 'application/vnd.microsoft.card.adaptive'
119+
}
120+
];
121+
await directLine.emulateIncomingActivity({
122+
attachments,
123+
channelData: { streamId: 't-00001', streamType: 'final' },
124+
from: {
125+
id: 'u-00001',
126+
name: 'Bot',
127+
role: 'bot'
128+
},
129+
id: 'm-00001',
130+
text: 'Work completed!'
131+
});
130132

131-
await pageConditions.numActivitiesShown(1);
133+
await pageConditions.numActivitiesShown(1);
132134

133-
// THEN: Should render the activity.
134-
await host.snapshot();
135-
});
136-
</script>
137-
</body>
138-
</html>
135+
// THEN: Should render the activity.
136+
await host.snapshot();
137+
});
138+
</script>
139+
</body>
140+
141+
</html>

packages/api/src/decorator/ActivityBorder/ActivityBorderDecorator.tsx

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { getActivityLivestreamingMetadata, type WebChatActivity } from 'botframework-webchat-core';
22
import React, { memo, useMemo, type ReactNode } from 'react';
33

4-
import PassthroughFallback from '../private/PassthroughFallback';
54
import {
65
ActivityBorderDecoratorMiddlewareProxy,
76
createActivityBorderMiddleware,
@@ -40,15 +39,7 @@ function ActivityBorderDecorator({ activity, children }: ActivityBorderDecorator
4039
};
4140
}, [activity]);
4241

43-
const requestValue = useMemo(() => Object.freeze({ request }), [request]);
44-
45-
return (
46-
<ActivityBorderDecoratorRequestContext.Provider value={requestValue}>
47-
<ActivityBorderDecoratorMiddlewareProxy fallbackComponent={PassthroughFallback} request={request}>
48-
{children}
49-
</ActivityBorderDecoratorMiddlewareProxy>
50-
</ActivityBorderDecoratorRequestContext.Provider>
51-
);
42+
return <ActivityBorderDecoratorMiddlewareProxy request={request}>{children}</ActivityBorderDecoratorMiddlewareProxy>;
5243
}
5344

5445
export default memo(ActivityBorderDecorator);

packages/api/src/decorator/ActivityBorder/private/ActivityBorderDecoratorMiddleware.ts

Lines changed: 16 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,33 @@
11
import { type ReactNode } from 'react';
2-
import templateMiddleware, {
2+
import templateDecorator, {
3+
InferRequest,
34
type InferMiddleware,
4-
type InferProps,
5-
type InferRequest
6-
} from '../../../middleware/private/templateMiddleware';
5+
type InferProps
6+
} from '../../private/templateDecorator';
7+
import ActivityBorderDecoratorRequestContext, {
8+
type ActivityBorderDecoratorRequest
9+
} from './ActivityBorderDecoratorRequestContext';
710

8-
type Request = Readonly<{
9-
/**
10-
* Decorate the activity as it participate in a livestreaming session.
11-
*
12-
* - `"completing"` - decorate as the livestreaming is completing
13-
* - `"ongoing"` - decorate as the livestreaming is ongoing
14-
* - `"preparing"` - decorate as the livestreaming is being prepared
15-
* - `undefined` - not participated in a livestreaming session
16-
*/
17-
livestreamingState: 'completing' | 'ongoing' | 'preparing' | undefined;
18-
19-
/**
20-
* Gets the role of the sender for the activity.
21-
*
22-
* - `"bot"` - the sender is a bot or other users
23-
* - `"channel"` - the sender is the channel service
24-
* - `"user"` - the sender is the current user
25-
* - `undefined` - the sender is unknown
26-
*/
27-
from: 'bot' | 'channel' | `user` | undefined;
11+
type Props = Readonly<{
12+
children?: ReactNode | undefined;
13+
showFlair?: boolean | undefined;
14+
showLoader?: boolean | undefined;
2815
}>;
2916

30-
type Props = Readonly<{ children?: ReactNode | undefined }>;
31-
32-
const template = templateMiddleware<Request, Props>('activity border');
17+
const template = templateDecorator<Props, ActivityBorderDecoratorRequest>(
18+
'activity border',
19+
ActivityBorderDecoratorRequestContext
20+
);
3321

34-
const {
35-
createMiddleware: createActivityBorderMiddleware,
36-
extractMiddleware: extractActivityBorderDecoratorMiddleware,
37-
Provider: ActivityBorderDecoratorMiddlewareProvider,
38-
Proxy: ActivityBorderDecoratorMiddlewareProxy
39-
} = template;
22+
const { createMiddleware: createActivityBorderMiddleware, Proxy: ActivityBorderDecoratorMiddlewareProxy } = template;
4023

4124
type ActivityBorderDecoratorMiddleware = InferMiddleware<typeof template>;
4225
type ActivityBorderDecoratorMiddlewareProps = InferProps<typeof template>;
4326
type ActivityBorderDecoratorMiddlewareRequest = InferRequest<typeof template>;
4427

4528
export {
46-
ActivityBorderDecoratorMiddlewareProvider,
4729
ActivityBorderDecoratorMiddlewareProxy,
4830
createActivityBorderMiddleware,
49-
extractActivityBorderDecoratorMiddleware,
5031
type ActivityBorderDecoratorMiddleware,
5132
type ActivityBorderDecoratorMiddlewareProps,
5233
type ActivityBorderDecoratorMiddlewareRequest

0 commit comments

Comments
 (0)