Skip to content

Commit c4af18f

Browse files
committed
Add useAvatarForBot and useAvatarForUser
1 parent db2a1bb commit c4af18f

9 files changed

Lines changed: 122 additions & 28 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
4242
- PR [#2542](https://github.com/microsoft/BotFramework-WebChat/pull/2542): `useLanguage`, `useLocalize`, `useLocalizeDate`
4343
- PR [#2543](https://github.com/microsoft/BotFramework-WebChat/pull/2543): `useAdaptiveCardsHostConfig`, `useAdaptiveCardsPackage`, `useRenderMarkdownAsHTML`
4444
- Bring your own Adaptive Cards package by specifying `adaptiveCardsPackage` prop, by [@compulim](https://github.com/compulim) in PR [#2543](https://github.com/microsoft/BotFramework-WebChat/pull/2543)
45+
- PR [#2544](https://github.com/microsoft/BotFramework-WebChat/pull/2544): `useAvatarForBot`, `useAvatarForUser`
4546

4647
### Fixed
4748

__tests__/hooks/useAvatarForBot.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { timeouts } from '../constants.json';
2+
3+
// selenium-webdriver API doc:
4+
// https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebDriver.html
5+
6+
jest.setTimeout(timeouts.test);
7+
8+
test('getter should return image and initial of avatar for bot', async () => {
9+
const { pageObjects } = await setupWebDriver({
10+
props: {
11+
styleOptions: {
12+
botAvatarImage: 'about:blank#bot-icon',
13+
botAvatarInitials: 'WC'
14+
}
15+
}
16+
});
17+
18+
const [{ image, initials }] = await pageObjects.runHook('useAvatarForBot');
19+
20+
expect({ image, initials }).toMatchInlineSnapshot(`
21+
Object {
22+
"image": "about:blank#bot-icon",
23+
"initials": "WC",
24+
}
25+
`);
26+
});
27+
28+
test('setter should throw exception', async () => {
29+
const { pageObjects } = await setupWebDriver();
30+
31+
await expect(pageObjects.runHook('useAvatarForBot', [], result => result[1]())).rejects.toThrow();
32+
});
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { timeouts } from '../constants.json';
2+
3+
// selenium-webdriver API doc:
4+
// https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebDriver.html
5+
6+
jest.setTimeout(timeouts.test);
7+
8+
test('getter should return image and initial of avatar for user', async () => {
9+
const { pageObjects } = await setupWebDriver({
10+
props: {
11+
styleOptions: {
12+
userAvatarImage: 'about:blank#user-icon',
13+
userAvatarInitials: 'WW'
14+
}
15+
}
16+
});
17+
18+
const [{ image, initials }] = await pageObjects.runHook('useAvatarForUser');
19+
20+
expect({ image, initials }).toMatchInlineSnapshot(`
21+
Object {
22+
"image": "about:blank#user-icon",
23+
"initials": "WW",
24+
}
25+
`);
26+
});
27+
28+
test('setter should throw exception', async () => {
29+
const { pageObjects } = await setupWebDriver();
30+
31+
await expect(pageObjects.runHook('useAvatarForUser', [], result => result[1]())).rejects.toThrow();
32+
});

packages/component/src/Activity/Avatar.js

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import React from 'react';
44

55
import connectToWebChat from '../connectToWebChat';
66
import CroppedImage from '../Utils/CroppedImage';
7+
import useAvatarForBot from '../hooks/useAvatarForBot';
8+
import useAvatarForUser from '../hooks/useAvatarForUser';
79
import useStyleSet from '../hooks/useStyleSet';
810

911
const connectAvatar = (...selectors) =>
@@ -25,38 +27,38 @@ const connectAvatar = (...selectors) =>
2527
// TODO: [P2] Consider memoizing "style={ backgroundImage }" in our upstreamers
2628
// We have 2 different upstreamers <CarouselFilmStrip> and <StackedLayout>
2729

28-
const Avatar = ({ 'aria-hidden': ariaHidden, avatarImage, avatarInitials, className, fromUser }) => {
30+
const Avatar = ({ 'aria-hidden': ariaHidden, className, fromUser }) => {
31+
const [botAvatar] = useAvatarForBot();
32+
const [userAvatar] = useAvatarForUser();
2933
const [{ avatar: avatarStyleSet }] = useStyleSet();
3034

35+
const { image, initials } = fromUser ? userAvatar : botAvatar;
36+
3137
return (
32-
!!(avatarImage || avatarInitials) && (
38+
!!(image || initials) && (
3339
<div
3440
aria-hidden={ariaHidden}
3541
className={classNames(avatarStyleSet + '', { 'from-user': fromUser }, className + '')}
3642
>
37-
{avatarInitials}
38-
{!!avatarImage && <CroppedImage alt="" className="image" height="100%" src={avatarImage} width="100%" />}
43+
{initials}
44+
{!!image && <CroppedImage alt="" className="image" height="100%" src={image} width="100%" />}
3945
</div>
4046
)
4147
);
4248
};
4349

4450
Avatar.defaultProps = {
4551
'aria-hidden': false,
46-
avatarImage: '',
47-
avatarInitials: '',
4852
className: '',
4953
fromUser: false
5054
};
5155

5256
Avatar.propTypes = {
5357
'aria-hidden': PropTypes.bool,
54-
avatarImage: PropTypes.string,
55-
avatarInitials: PropTypes.string,
5658
className: PropTypes.string,
5759
fromUser: PropTypes.bool
5860
};
5961

60-
export default connectAvatar()(Avatar);
62+
export default Avatar;
6163

6264
export { connectAvatar };

packages/component/src/Activity/CarouselFilmStrip.js

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import ScreenReaderText from '../ScreenReaderText';
1515
import SendStatus from './SendStatus';
1616
import textFormatToContentType from '../Utils/textFormatToContentType';
1717
import Timestamp from './Timestamp';
18+
import useAvatarForBot from '../hooks/useAvatarForBot';
19+
import useAvatarForUser from '../hooks/useAvatarForUser';
1820
import useLocalize from '../hooks/useLocalize';
1921
import useStyleOptions from '../hooks/useStyleOptions';
2022
import useStyleSet from '../hooks/useStyleSet';
@@ -88,13 +90,14 @@ const connectCarouselFilmStrip = (...selectors) =>
8890

8991
const WebChatCarouselFilmStrip = ({
9092
activity,
91-
avatarInitials,
9293
children,
9394
className,
9495
itemContainerRef,
9596
scrollableRef,
9697
timestampClassName
9798
}) => {
99+
const [{ initials: botInitials }] = useAvatarForBot();
100+
const [{ initials: userInitials }] = useAvatarForUser();
98101
const [{ bubbleNubSize, bubbleFromUserNubSize }] = useStyleOptions();
99102
const [{ carouselFilmStrip: carouselFilmStripStyleSet }] = useStyleSet();
100103

@@ -112,13 +115,13 @@ const WebChatCarouselFilmStrip = ({
112115
const fromUser = role === 'user';
113116
const activityDisplayText = messageBackDisplayText || text;
114117
const indented = fromUser ? bubbleFromUserNubSize : bubbleNubSize;
115-
118+
const initials = fromUser ? userInitials : botInitials;
116119
const roleLabel = fromUser ? userRoleLabel : botRoleLabel;
117120

118121
return (
119122
<div
120123
className={classNames(ROOT_CSS + '', carouselFilmStripStyleSet + '', className + '', {
121-
webchat__carousel_indented_content: avatarInitials && !indented
124+
webchat__carousel_indented_content: initials && !indented
122125
})}
123126
ref={scrollableRef}
124127
>
@@ -162,7 +165,6 @@ const WebChatCarouselFilmStrip = ({
162165
};
163166

164167
WebChatCarouselFilmStrip.defaultProps = {
165-
avatarInitials: '',
166168
children: undefined,
167169
className: '',
168170
timestampClassName: ''
@@ -184,22 +186,17 @@ WebChatCarouselFilmStrip.propTypes = {
184186
textFormat: PropTypes.string,
185187
timestamp: PropTypes.string
186188
}).isRequired,
187-
avatarInitials: PropTypes.string,
188189
children: PropTypes.any,
189190
className: PropTypes.string,
190191
itemContainerRef: PropTypes.any.isRequired,
191192
scrollableRef: PropTypes.any.isRequired,
192193
timestampClassName: PropTypes.string
193194
};
194195

195-
const ConnectedCarouselFilmStrip = connectCarouselFilmStrip(({ avatarInitials }) => ({
196-
avatarInitials
197-
}))(WebChatCarouselFilmStrip);
198-
199196
const CarouselFilmStrip = props => (
200197
<FilmContext.Consumer>
201198
{({ itemContainerRef, scrollableRef }) => (
202-
<ConnectedCarouselFilmStrip itemContainerRef={itemContainerRef} scrollableRef={scrollableRef} {...props} />
199+
<WebChatCarouselFilmStrip itemContainerRef={itemContainerRef} scrollableRef={scrollableRef} {...props} />
203200
)}
204201
</FilmContext.Consumer>
205202
);

packages/component/src/Activity/StackedLayout.js

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import ScreenReaderText from '../ScreenReaderText';
1616
import SendStatus from './SendStatus';
1717
import textFormatToContentType from '../Utils/textFormatToContentType';
1818
import Timestamp from './Timestamp';
19+
import useAvatarForBot from '../hooks/useAvatarForBot';
20+
import useAvatarForUser from '../hooks/useAvatarForUser';
1921
import useLocalize from '../hooks/useLocalize';
2022
import useStyleOptions from '../hooks/useStyleOptions';
2123
import useStyleSet from '../hooks/useStyleSet';
@@ -84,7 +86,9 @@ const connectStackedLayout = (...selectors) =>
8486
...selectors
8587
);
8688

87-
const StackedLayout = ({ activity, avatarInitials, children, timestampClassName }) => {
89+
const StackedLayout = ({ activity, children, timestampClassName }) => {
90+
const [{ initials: botInitials }] = useAvatarForBot();
91+
const [{ initials: userInitials }] = useAvatarForUser();
8892
const [{ botAvatarInitials, bubbleNubSize, bubbleFromUserNubSize, userAvatarInitials }] = useStyleOptions();
8993
const [{ stackedLayout: stackedLayoutStyleSet }] = useStyleSet();
9094

@@ -98,6 +102,7 @@ const StackedLayout = ({ activity, avatarInitials, children, timestampClassName
98102

99103
const activityDisplayText = messageBackDisplayText || text;
100104
const fromUser = role === 'user';
105+
const initials = fromUser ? userInitials : botInitials;
101106
const showSendStatus = state === SENDING || state === SEND_FAILED;
102107
const plainText = remark()
103108
.use(stripMarkdown)
@@ -109,8 +114,8 @@ const StackedLayout = ({ activity, avatarInitials, children, timestampClassName
109114

110115
const roleLabel = fromUser ? botRoleLabel : userRoleLabel;
111116

112-
const botAriaLabel = useLocalize('Bot said something', avatarInitials, plainText);
113-
const userAriaLabel = useLocalize('User said something', avatarInitials, plainText);
117+
const botAriaLabel = useLocalize('Bot said something', initials, plainText);
118+
const userAriaLabel = useLocalize('User said something', initials, plainText);
114119

115120
const ariaLabel = fromUser ? userAriaLabel : botAriaLabel;
116121

@@ -120,10 +125,10 @@ const StackedLayout = ({ activity, avatarInitials, children, timestampClassName
120125
'from-user': fromUser,
121126
webchat__stacked_extra_left_indent: fromUser && !botAvatarInitials && bubbleNubSize,
122127
webchat__stacked_extra_right_indent: !fromUser && !userAvatarInitials && bubbleFromUserNubSize,
123-
webchat__stacked_indented_content: avatarInitials && !indented
128+
webchat__stacked_indented_content: initials && !indented
124129
})}
125130
>
126-
{!avatarInitials && !!(fromUser ? bubbleFromUserNubSize : bubbleNubSize) && <div className="avatar" />}
131+
{!initials && !!(fromUser ? bubbleFromUserNubSize : bubbleNubSize) && <div className="avatar" />}
127132
<Avatar aria-hidden={true} className="avatar" fromUser={fromUser} />
128133
<div className="content">
129134
{!!activityDisplayText && (
@@ -189,13 +194,10 @@ StackedLayout.propTypes = {
189194
timestamp: PropTypes.string,
190195
type: PropTypes.string.isRequired
191196
}).isRequired,
192-
avatarInitials: PropTypes.string.isRequired,
193197
children: PropTypes.any,
194198
timestampClassName: PropTypes.string
195199
};
196200

197-
export default connectStackedLayout(({ avatarInitials }) => ({
198-
avatarInitials
199-
}))(StackedLayout);
201+
export default StackedLayout;
200202

201203
export { connectStackedLayout };

packages/component/src/hooks/index.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import useActivities from './useActivities';
2+
import useAvatarForBot from './useAvatarForBot';
3+
import useAvatarForUser from './useAvatarForUser';
24
import useLanguage from './useLanguage';
35
import useLocalize from './useLocalize';
46
import useLocalizeDate from './useLocalizeDate';
@@ -11,6 +13,8 @@ import { useSendBoxDictationStarted } from '../BasicSendBox';
1113

1214
export {
1315
useActivities,
16+
useAvatarForBot,
17+
useAvatarForUser,
1418
useLanguage,
1519
useLocalize,
1620
useLocalizeDate,
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import useStyleOptions from './useStyleOptions';
2+
3+
export default function useAvatarForBot() {
4+
const [{ botAvatarImage: image, botAvatarInitials: initials }] = useStyleOptions();
5+
6+
return [
7+
{
8+
image,
9+
initials
10+
}
11+
];
12+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import useStyleOptions from './useStyleOptions';
2+
3+
export default function useAvatarForUser() {
4+
const [{ userAvatarImage: image, userAvatarInitials: initials }] = useStyleOptions();
5+
6+
return [
7+
{
8+
image,
9+
initials
10+
}
11+
];
12+
}

0 commit comments

Comments
 (0)