Skip to content

Commit

Permalink
Add useAvatarForBot and useAvatarForUser (#2544)
Browse files Browse the repository at this point in the history
  • Loading branch information
compulim authored Nov 14, 2019
1 parent db2a1bb commit d9c09a4
Show file tree
Hide file tree
Showing 9 changed files with 122 additions and 28 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- PR [#2542](https://github.com/microsoft/BotFramework-WebChat/pull/2542): `useLanguage`, `useLocalize`, `useLocalizeDate`
- PR [#2543](https://github.com/microsoft/BotFramework-WebChat/pull/2543): `useAdaptiveCardsHostConfig`, `useAdaptiveCardsPackage`, `useRenderMarkdownAsHTML`
- 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)
- PR [#2544](https://github.com/microsoft/BotFramework-WebChat/pull/2544): `useAvatarForBot`, `useAvatarForUser`

### Fixed

Expand Down
32 changes: 32 additions & 0 deletions __tests__/hooks/useAvatarForBot.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { timeouts } from '../constants.json';

// selenium-webdriver API doc:
// https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebDriver.html

jest.setTimeout(timeouts.test);

test('getter should return image and initial of avatar for bot', async () => {
const { pageObjects } = await setupWebDriver({
props: {
styleOptions: {
botAvatarImage: 'about:blank#bot-icon',
botAvatarInitials: 'WC'
}
}
});

const [{ image, initials }] = await pageObjects.runHook('useAvatarForBot');

expect({ image, initials }).toMatchInlineSnapshot(`
Object {
"image": "about:blank#bot-icon",
"initials": "WC",
}
`);
});

test('setter should throw exception', async () => {
const { pageObjects } = await setupWebDriver();

await expect(pageObjects.runHook('useAvatarForBot', [], result => result[1]())).rejects.toThrow();
});
32 changes: 32 additions & 0 deletions __tests__/hooks/useAvatarForUser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { timeouts } from '../constants.json';

// selenium-webdriver API doc:
// https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebDriver.html

jest.setTimeout(timeouts.test);

test('getter should return image and initial of avatar for user', async () => {
const { pageObjects } = await setupWebDriver({
props: {
styleOptions: {
userAvatarImage: 'about:blank#user-icon',
userAvatarInitials: 'WW'
}
}
});

const [{ image, initials }] = await pageObjects.runHook('useAvatarForUser');

expect({ image, initials }).toMatchInlineSnapshot(`
Object {
"image": "about:blank#user-icon",
"initials": "WW",
}
`);
});

test('setter should throw exception', async () => {
const { pageObjects } = await setupWebDriver();

await expect(pageObjects.runHook('useAvatarForUser', [], result => result[1]())).rejects.toThrow();
});
20 changes: 11 additions & 9 deletions packages/component/src/Activity/Avatar.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import React from 'react';

import connectToWebChat from '../connectToWebChat';
import CroppedImage from '../Utils/CroppedImage';
import useAvatarForBot from '../hooks/useAvatarForBot';
import useAvatarForUser from '../hooks/useAvatarForUser';
import useStyleSet from '../hooks/useStyleSet';

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

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

const { image, initials } = fromUser ? userAvatar : botAvatar;

return (
!!(avatarImage || avatarInitials) && (
!!(image || initials) && (
<div
aria-hidden={ariaHidden}
className={classNames(avatarStyleSet + '', { 'from-user': fromUser }, className + '')}
>
{avatarInitials}
{!!avatarImage && <CroppedImage alt="" className="image" height="100%" src={avatarImage} width="100%" />}
{initials}
{!!image && <CroppedImage alt="" className="image" height="100%" src={image} width="100%" />}
</div>
)
);
};

Avatar.defaultProps = {
'aria-hidden': false,
avatarImage: '',
avatarInitials: '',
className: '',
fromUser: false
};

Avatar.propTypes = {
'aria-hidden': PropTypes.bool,
avatarImage: PropTypes.string,
avatarInitials: PropTypes.string,
className: PropTypes.string,
fromUser: PropTypes.bool
};

export default connectAvatar()(Avatar);
export default Avatar;

export { connectAvatar };
17 changes: 7 additions & 10 deletions packages/component/src/Activity/CarouselFilmStrip.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import ScreenReaderText from '../ScreenReaderText';
import SendStatus from './SendStatus';
import textFormatToContentType from '../Utils/textFormatToContentType';
import Timestamp from './Timestamp';
import useAvatarForBot from '../hooks/useAvatarForBot';
import useAvatarForUser from '../hooks/useAvatarForUser';
import useLocalize from '../hooks/useLocalize';
import useStyleOptions from '../hooks/useStyleOptions';
import useStyleSet from '../hooks/useStyleSet';
Expand Down Expand Up @@ -88,13 +90,14 @@ const connectCarouselFilmStrip = (...selectors) =>

const WebChatCarouselFilmStrip = ({
activity,
avatarInitials,
children,
className,
itemContainerRef,
scrollableRef,
timestampClassName
}) => {
const [{ initials: botInitials }] = useAvatarForBot();
const [{ initials: userInitials }] = useAvatarForUser();
const [{ bubbleNubSize, bubbleFromUserNubSize }] = useStyleOptions();
const [{ carouselFilmStrip: carouselFilmStripStyleSet }] = useStyleSet();

Expand All @@ -112,13 +115,13 @@ const WebChatCarouselFilmStrip = ({
const fromUser = role === 'user';
const activityDisplayText = messageBackDisplayText || text;
const indented = fromUser ? bubbleFromUserNubSize : bubbleNubSize;

const initials = fromUser ? userInitials : botInitials;
const roleLabel = fromUser ? userRoleLabel : botRoleLabel;

return (
<div
className={classNames(ROOT_CSS + '', carouselFilmStripStyleSet + '', className + '', {
webchat__carousel_indented_content: avatarInitials && !indented
webchat__carousel_indented_content: initials && !indented
})}
ref={scrollableRef}
>
Expand Down Expand Up @@ -162,7 +165,6 @@ const WebChatCarouselFilmStrip = ({
};

WebChatCarouselFilmStrip.defaultProps = {
avatarInitials: '',
children: undefined,
className: '',
timestampClassName: ''
Expand All @@ -184,22 +186,17 @@ WebChatCarouselFilmStrip.propTypes = {
textFormat: PropTypes.string,
timestamp: PropTypes.string
}).isRequired,
avatarInitials: PropTypes.string,
children: PropTypes.any,
className: PropTypes.string,
itemContainerRef: PropTypes.any.isRequired,
scrollableRef: PropTypes.any.isRequired,
timestampClassName: PropTypes.string
};

const ConnectedCarouselFilmStrip = connectCarouselFilmStrip(({ avatarInitials }) => ({
avatarInitials
}))(WebChatCarouselFilmStrip);

const CarouselFilmStrip = props => (
<FilmContext.Consumer>
{({ itemContainerRef, scrollableRef }) => (
<ConnectedCarouselFilmStrip itemContainerRef={itemContainerRef} scrollableRef={scrollableRef} {...props} />
<WebChatCarouselFilmStrip itemContainerRef={itemContainerRef} scrollableRef={scrollableRef} {...props} />
)}
</FilmContext.Consumer>
);
Expand Down
20 changes: 11 additions & 9 deletions packages/component/src/Activity/StackedLayout.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import ScreenReaderText from '../ScreenReaderText';
import SendStatus from './SendStatus';
import textFormatToContentType from '../Utils/textFormatToContentType';
import Timestamp from './Timestamp';
import useAvatarForBot from '../hooks/useAvatarForBot';
import useAvatarForUser from '../hooks/useAvatarForUser';
import useLocalize from '../hooks/useLocalize';
import useStyleOptions from '../hooks/useStyleOptions';
import useStyleSet from '../hooks/useStyleSet';
Expand Down Expand Up @@ -84,7 +86,9 @@ const connectStackedLayout = (...selectors) =>
...selectors
);

const StackedLayout = ({ activity, avatarInitials, children, timestampClassName }) => {
const StackedLayout = ({ activity, children, timestampClassName }) => {
const [{ initials: botInitials }] = useAvatarForBot();
const [{ initials: userInitials }] = useAvatarForUser();
const [{ botAvatarInitials, bubbleNubSize, bubbleFromUserNubSize, userAvatarInitials }] = useStyleOptions();
const [{ stackedLayout: stackedLayoutStyleSet }] = useStyleSet();

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

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

const roleLabel = fromUser ? botRoleLabel : userRoleLabel;

const botAriaLabel = useLocalize('Bot said something', avatarInitials, plainText);
const userAriaLabel = useLocalize('User said something', avatarInitials, plainText);
const botAriaLabel = useLocalize('Bot said something', initials, plainText);
const userAriaLabel = useLocalize('User said something', initials, plainText);

const ariaLabel = fromUser ? userAriaLabel : botAriaLabel;

Expand All @@ -120,10 +125,10 @@ const StackedLayout = ({ activity, avatarInitials, children, timestampClassName
'from-user': fromUser,
webchat__stacked_extra_left_indent: fromUser && !botAvatarInitials && bubbleNubSize,
webchat__stacked_extra_right_indent: !fromUser && !userAvatarInitials && bubbleFromUserNubSize,
webchat__stacked_indented_content: avatarInitials && !indented
webchat__stacked_indented_content: initials && !indented
})}
>
{!avatarInitials && !!(fromUser ? bubbleFromUserNubSize : bubbleNubSize) && <div className="avatar" />}
{!initials && !!(fromUser ? bubbleFromUserNubSize : bubbleNubSize) && <div className="avatar" />}
<Avatar aria-hidden={true} className="avatar" fromUser={fromUser} />
<div className="content">
{!!activityDisplayText && (
Expand Down Expand Up @@ -189,13 +194,10 @@ StackedLayout.propTypes = {
timestamp: PropTypes.string,
type: PropTypes.string.isRequired
}).isRequired,
avatarInitials: PropTypes.string.isRequired,
children: PropTypes.any,
timestampClassName: PropTypes.string
};

export default connectStackedLayout(({ avatarInitials }) => ({
avatarInitials
}))(StackedLayout);
export default StackedLayout;

export { connectStackedLayout };
4 changes: 4 additions & 0 deletions packages/component/src/hooks/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import useActivities from './useActivities';
import useAvatarForBot from './useAvatarForBot';
import useAvatarForUser from './useAvatarForUser';
import useLanguage from './useLanguage';
import useLocalize from './useLocalize';
import useLocalizeDate from './useLocalizeDate';
Expand All @@ -11,6 +13,8 @@ import { useSendBoxDictationStarted } from '../BasicSendBox';

export {
useActivities,
useAvatarForBot,
useAvatarForUser,
useLanguage,
useLocalize,
useLocalizeDate,
Expand Down
12 changes: 12 additions & 0 deletions packages/component/src/hooks/useAvatarForBot.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import useStyleOptions from './useStyleOptions';

export default function useAvatarForBot() {
const [{ botAvatarImage: image, botAvatarInitials: initials }] = useStyleOptions();

return [
{
image,
initials
}
];
}
12 changes: 12 additions & 0 deletions packages/component/src/hooks/useAvatarForUser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import useStyleOptions from './useStyleOptions';

export default function useAvatarForUser() {
const [{ userAvatarImage: image, userAvatarInitials: initials }] = useStyleOptions();

return [
{
image,
initials
}
];
}

0 comments on commit d9c09a4

Please sign in to comment.