Skip to content

Commit c1bd6b1

Browse files
authored
feat: template decorator (#5510)
* feat: template decorator * Fix tests * Changelog
1 parent 40736b1 commit c1bd6b1

File tree

13 files changed

+344
-266
lines changed

13 files changed

+344
-266
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ Notes: web developers are advised to use [`~` (tilde range)](https://github.com/
3535
- `useGroupActivities` hook is being deprecated in favor of the `useGroupActivitiesByName` hook. The hook will be removed on or after 2027-05-04
3636
- `useSuggestedActions()` hook is being deprecated in favor of the `useSuggestedActionsHooks().useSuggestedActions()` hook. The hook will be removed on or after 2027-05-30
3737
- The following middleware should be created using their respective factory function:
38-
- `activityBorderDecoratorMiddleware`, related to PR [#5504](https://github.com/microsoft/BotFramework-WebChat/pull/5504)
38+
- `activityBorderDecoratorMiddleware`, related to PR [#5504](https://github.com/microsoft/BotFramework-WebChat/pull/5504), [#5510](https://github.com/microsoft/BotFramework-WebChat/pull/5510)
3939
- `activityGroupingDecoratorMiddleware`, related to PR [#5504](https://github.com/microsoft/BotFramework-WebChat/pull/5504)
4040
- `sendBoxMiddleware`, related to PR [#5504](https://github.com/microsoft/BotFramework-WebChat/pull/5504)
4141
- `sendBoxToolbarMiddleware`, related to PR [#5504](https://github.com/microsoft/BotFramework-WebChat/pull/5504)
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>

__tests__/html2/livestream/layout.html

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
.flair--ongoing {
2020
outline-color: green;
21+
border-radius: var(--webchat-borderRadiusXLarge);
2122
}
2223

2324
.loader {
@@ -39,28 +40,28 @@
3940
}
4041
} = window; // Imports in UMD fashion.
4142

42-
function Flair({ children }) {
43-
return <div className="flair">{children}</div>;
43+
function Flair({ request, Next, ...props }) {
44+
return <div className={request.livestreamingState === 'completing' ? 'flair' : ''}>
45+
<Next {...props} />
46+
</div>;
4447
}
4548

46-
function FlairOngoing({ children }) {
47-
return <div className="flair flair--ongoing">{children}</div>;
49+
function FlairOngoing({ request, Next, ...props }) {
50+
return <div className={request.livestreamingState === 'ongoing' ? 'flair flair--ongoing' : ''}>
51+
<Next {...props} />
52+
</div>;
4853
}
4954

50-
function Loader({ children }) {
51-
return <div className="loader">{children}</div>;
55+
function Loader({ request, Next, ...props }) {
56+
return <div className={request.livestreamingState === 'preparing' ? 'loader' : ''}>
57+
<Next {...props} showLoader={false} />
58+
</div>;
5259
}
5360

5461
const decoratorMiddleware = [
55-
createActivityBorderMiddleware(
56-
next => request => (request.livestreamingState === 'completing' ? Flair : next(request))
57-
),
58-
createActivityBorderMiddleware(
59-
next => request => (request.livestreamingState === 'preparing' ? Loader : next(request))
60-
),
61-
createActivityBorderMiddleware(
62-
next => request => (request.livestreamingState === 'ongoing' ? FlairOngoing : next(request))
63-
)
62+
createActivityBorderMiddleware(Flair),
63+
createActivityBorderMiddleware(FlairOngoing),
64+
createActivityBorderMiddleware(Loader)
6465
];
6566

6667
const { directLine, store } = testHelpers.createDirectLineEmulator();

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);

0 commit comments

Comments
 (0)