Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sample: Password input activity #1569

Merged
merged 19 commits into from
Jan 18, 2019
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ script:
- npm run coveralls

# Upload image snapshots to build artifact curator
- zip image-snapshots __tests__/__image_snapshots__/**/*
- zip -r image-snapshots __tests__/__image_snapshots__/
- curl -T image-snapshots.zip https://webchat-curator.azurewebsites.net/upload

before_deploy:
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- `component`: Fix outgoing typing indicators are not sent and acknowledged properly, in PR [#1286](https://github.com/Microsoft/BotFramework-WebChat/pull/1286)
- Fix [#1402](https://github.com/Microsoft/BotFramework-WebChat/issues/1402). Add `messageBack` support, by [@corinagum](https://github.com/corinagum) in PR [#1581](https://github.com/Microsoft/BotFramework-WebChat/pull/1581)
- Fix [#1539], outgoing typing indicators are not sent and acknowledged properly, in PR [#1541](https://github.com/Microsoft/BotFramework-WebChat/pull/1541)
- `component`: Fix [#1547](https://github.com/Microsoft/BotFramework-WebChat/issues/1547). Fixed unhandled activity type should be forwarded to custom middleware, by [@compulim](https://github.com/compulim) in PR [#1569](https://github.com/Microsoft/BotFramework-WebChat/pull/1569)

### Removed
- `botAvatarImage` and `userAvatarImage` props, as they are moved inside `styleOptions`, in PR [#1486](https://github.com/Microsoft/BotFramework-WebChat/pull/1486)
Expand All @@ -62,6 +63,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- `core`: [Incoming activity to JavaScript event](https://microsoft.github.io/BotFramework-WebChat/15.b.incoming-activity-event/), in [#1567](https://github.com/Microsoft/BotFramework-WebChat/pull/1567)
- `core`: [Send welcome event](https://microsoft.github.io/BotFramework-WebChat/15.b.backchannel-send-welcome-event/), in PR [#1286](https://github.com/Microsoft/BotFramework-WebChat/pull/1286)
- `core`: [Send typing indicator](https://microsoft.github.io/BotFramework-WebChat/07.b.customization-send-typing-indicator), in [#1541](https://github.com/Microsoft/BotFramework-WebChat/pull/1541)
- `component`: [Password input activity](https://microsoft.github.io/BotFramework-WebChat/10.b.customization-password-input/), in [#1569](https://github.com/Microsoft/BotFramework-WebChat/pull/1569)

## [4.2.0] - 2018-12-11
### Added
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,8 @@ npm run prepublishOnly
| [`07.b.customization-send-typing-indicator`](https://github.com/Microsoft/BotFramework-WebChat/tree/master/samples/07.customization-send-typing-indicator) | Demonstrates how to send typing activity when the user start typing on the send box. | [Demo](https://microsoft.github.io/BotFramework-WebChat/07.b.customization-send-typing-indicator) |
| [`08.customization-user-highlighting`](https://github.com/Microsoft/BotFramework-WebChat/tree/master/samples/08.customization-user-highlighting) | Demonstrates how to customize the styling of activities based whether the message is from the user or the bot. | [Demo](https://microsoft.github.io/BotFramework-WebChat/08.customization-user-highlighting) |
| [`09.customization-reaction-buttons`](https://github.com/Microsoft/BotFramework-WebChat/tree/master/samples/09.customization-reaction-buttons/) | Introduces the ability to create custom components for Web Chat that are unique to your bot's needs. This tutorial demonstrates the ability to add reaction emoji such as :thumbsup: and :thumbsdown: to conversational activities. | [Demo](https://microsoft.github.io/BotFramework-WebChat/09.customization-reaction-buttons) |
| [`10.customization-card-components`](https://github.com/Microsoft/BotFramework-WebChat/tree/master/samples/10.customization-card-components) | Demonstrates how to create custom activity card attachments, in this case GitHub repository cards. | [Demo](https://microsoft.github.io/BotFramework-WebChat/10.customization-card-components) |
| [`10.a.customization-card-components`](https://github.com/Microsoft/BotFramework-WebChat/tree/master/samples/10.a.customization-card-components) | Demonstrates how to create custom activity card attachments, in this case GitHub repository cards. | [Demo](https://microsoft.github.io/BotFramework-WebChat/10.a.customization-card-components) |
| [`10.b.customization-password-input`](https://github.com/Microsoft/BotFramework-WebChat/tree/master/samples/10.b.customization-password-input) | Demonstrates how to create custom activity for password input. | [Demo](https://microsoft.github.io/BotFramework-WebChat/10.b.customization-password-input) |
| [`11.customization-redux-actions`](https://github.com/Microsoft/BotFramework-WebChat/tree/master/samples/11.customization-redux-actions) | Advanced tutorial: Demonstrates how to incorporate redux middleware into your Web Chat app by sending redux actions through the bot. This example demonstrates manual styling based on activities between bot and user. | [Demo](https://microsoft.github.io/BotFramework-WebChat/11.customization-redux-actions) |
| [`12.customization-minimizable-web-chat`](https://github.com/Microsoft/BotFramework-WebChat/tree/master/samples/12.customization-minimizable-web-chat) | Advanced tutorial: Demonstrates how to add the Web Chat interface to your website as a minimizable show/hide chat box. | [Demo](https://microsoft.github.io/BotFramework-WebChat/12.customization-minimizable-web-chat) |
| [`13.customization-speech-ui`](https://github.com/Microsoft/BotFramework-WebChat/tree/master/samples/13.customization-speech-ui) | Advanced tutorial: Demonstrates how to fully customize key components of your bot, in this case speech, which entirely replaces the text-based transcript UI and instead shows a simple speech button with the bot's response. | [Demo](https://microsoft.github.io/BotFramework-WebChat/13.customization-speech-ui) |
Expand Down
5 changes: 3 additions & 2 deletions __tests__/constants.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"imageSnapshotOptions": {
"failureThreshold": 10,
"failureThresholdType": "pixel"
"customDiffConfig": {
"threshold": 0.15
compulim marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
9 changes: 6 additions & 3 deletions packages/component/src/Activity/CarouselFilmStrip.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,8 @@ const ConnectedCarouselFilmStrip = connectCarouselFilmStrip(
language,
className,
filmContext,
showTimestamp,
styleSet,
timestampClassName
}) => {
const fromUser = activity.from.role === 'user';
const ariaLabel = localize('Bot said something', language, avatarInitials, activity.text, activity.timestamp)
Expand Down Expand Up @@ -168,8 +168,11 @@ const ConnectedCarouselFilmStrip = connectCarouselFilmStrip(
)
) ?
<SendStatus activity={ activity } />
: showTimestamp &&
<Timestamp activity={ activity } />
:
<Timestamp
activity={ activity }
className={ timestampClassName }
/>
}
</div>
</div>
Expand Down
4 changes: 2 additions & 2 deletions packages/component/src/Activity/CarouselLayout.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ const ROOT_CSS = css({

export default connectToWebChat(
({ language, styleSet }) => ({ language, styleSet })
)(({ activity, children, language, showTimestamp, styleSet }) => {
)(({ activity, children, language, styleSet, timestampClassName }) => {
const filmStyleSet = createBasicStyleSet();

return (
<Composer>
<FilmContext.Consumer>
{ ({ scrollBarWidth }) =>
<div className={ classNames(ROOT_CSS + '', filmStyleSet.carousel + '') }>
<CarouselFilmStrip activity={ activity } showTimestamp={ showTimestamp }>
<CarouselFilmStrip activity={ activity } timestampClassName={ timestampClassName }>
{ children }
</CarouselFilmStrip>
{ scrollBarWidth !== '100%' &&
Expand Down
29 changes: 13 additions & 16 deletions packages/component/src/Activity/StackedLayout.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,8 @@ export default connectStackedLayout(
avatarInitials,
children,
language,
showTimestamp,
styleSet
styleSet,
timestampClassName
}) => {
const fromUser = activity.from.role === 'user';
const { state } = activity.channelData || {};
Expand Down Expand Up @@ -161,20 +161,17 @@ export default connectStackedLayout(
</div>
)
}
{
(showSendStatus || showTimestamp) &&
<div
aria-hidden={ true }
className="row"
>
{ showSendStatus ?
<SendStatus activity={ activity } className="timestamp" />
:
<Timestamp activity={ activity } className="timestamp" />
}
<div className="filler" />
</div>
}
<div
aria-hidden={ true }
className="row"
>
{ showSendStatus ?
<SendStatus activity={ activity } className="timestamp" />
:
<Timestamp activity={ activity } className={ classNames('timestamp', timestampClassName) } />
}
<div className="filler" />
</div>
</div>
<div className="filler" />
</div>
Expand Down
7 changes: 6 additions & 1 deletion packages/component/src/Activity/Timestamp.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import classNames from 'classnames';
import React from 'react';

import connectToWebChat from '../connectToWebChat';
Expand All @@ -8,9 +9,13 @@ export default connectToWebChat(
)(
({
activity: { timestamp },
className,
styleSet
}) =>
<span className={ styleSet.timestamp }>
<span className={ classNames(
styleSet.timestamp + '',
(className || '') + ''
) }>
<TimeAgo value={ timestamp } />
</span>
)
101 changes: 50 additions & 51 deletions packages/component/src/BasicTranscript.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,50 +24,28 @@ const FILLER_CSS = css({
});

const LIST_CSS = css({
listStyleType: 'none'
});
listStyleType: 'none',

function shouldShowActivity(activity) {
if (activity) {
if (activity.type === 'message') {
const { attachments = [], text } = activity;

if (
// Do not show postback
!(activity.channelData && activity.channelData.postBack)
// Do not show messageBack if displayText is undefined
&& !(activity.channelData && activity.channelData.messageBack && !activity.channelData.messageBack.displayText)
// Do not show empty bubbles (no text and attachments, and not "typing")
&& (text || attachments.length)
) {
return true;
}
} else if (activity.type === 'typing') {
return true;
}
'& > li.hide-timestamp .transcript-timestamp': {
display: 'none'
}
});

return false;
}

function shouldShowTimestamp(activity, nextActivity, groupTimestamp) {
function sameTimestampGroup(activityX, activityY, groupTimestamp) {
if (groupTimestamp === false) {
return false;
} else {
return true;
} else if (activityX && activityY) {
groupTimestamp = typeof groupTimestamp === 'number' ? groupTimestamp : 5 * 60 * 1000;

if (activity.type !== 'message') {
// Hide timestamp for typing
return false;
} else if (nextActivity && activity.from.role === nextActivity.from.role) {
const time = new Date(activity.timestamp).getTime();
const nextTime = new Date(nextActivity.timestamp).getTime();
if (activityX.from.role === activityY.from.role) {
const timeX = new Date(activityX.timestamp).getTime();
const timeY = new Date(activityY.timestamp).getTime();

return (nextTime - time) > groupTimestamp;
} else {
return true;
return Math.abs(timeX - timeY) <= groupTimestamp;
}
}

return false;
}

const BasicTranscript = ({
Expand All @@ -80,7 +58,26 @@ const BasicTranscript = ({
webSpeechPonyfill
}) => {
const { speechSynthesis, SpeechSynthesisUtterance } = webSpeechPonyfill || {};
const visibleActivities = activities.filter(shouldShowActivity);

// We use 2-pass approach for rendering activities, for show/hide timestamp grouping.
// Until the activity pass thru middleware, we never know if it is going to show up.
// After we know which activities will show up, we can compute which activity will show timestamp.
compulim marked this conversation as resolved.
Show resolved Hide resolved
// If the activity does not render, we will also not speaking it out (if text-to-speech is enabled).
compulim marked this conversation as resolved.
Show resolved Hide resolved
const activityElements = activities.reduce((activityElements, activity) => {
const element = activityRenderer({
activity,
timestampClassName: 'transcript-timestamp'
})(
({ attachment }) => attachmentRenderer({ activity, attachment })
);

element && activityElements.push({
activity,
element
});

return activityElements;
}, []);

return (
<div
Expand All @@ -102,23 +99,25 @@ const BasicTranscript = ({
role="list"
>
{
visibleActivities.map((activity, index) => {
const showTimestamp = shouldShowTimestamp(activity, visibleActivities[index + 1], groupTimestamp);

return (
<li
className={ styleSet.activity }
key={ (activity.channelData && activity.channelData.clientActivityID) || activity.id || index }
role="listitem"
>
{ activityRenderer({ activity, showTimestamp })(({ attachment }) => attachmentRenderer({ activity, attachment })) }
activityElements.map(({ activity, element }, index) =>
<li
className={ classNames(
styleSet.activity + '',
{
// TODO: [P2] We should use core/definitions/speakingActivity for this predicate instead
activity.channelData && activity.channelData.speak && <SpeakActivity activity={ activity } />
// Hide timestamp if same timestamp group with the next activity
'hide-timestamp': sameTimestampGroup(activity, (activityElements[index + 1] || {}).activity, groupTimestamp)
}
</li>
);
})
) }
key={ (activity.channelData && activity.channelData.clientActivityID) || activity.id || index }
role="listitem"
>
{ element }
{
// TODO: [P2] We should use core/definitions/speakingActivity for this predicate instead
activity.channelData && activity.channelData.speak && <SpeakActivity activity={ activity } />
}
</li>
)
}
</ul>
</SayComposer>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,44 @@ import StackedLayout from '../../Activity/StackedLayout';
const RETURN_FALSE = () => false;

export default function () {
return () => next => ({ activity, showTimestamp }) => {
return () => next => ({ activity, timestampClassName }) => {
// TODO: [P4] Can we simplify these if-statement to something more readable?

if (activity.type === 'typing' && activity.from.role === 'user') {
const { type } = activity;

// Filter out activities that should not be visible
if (type === 'event') {
return RETURN_FALSE;
} else if (type === 'message') {
const { attachments = [], text } = activity;

if (
// Do not show postback
(activity.channelData && activity.channelData.postBack)
// Do not show messageBack if displayText is undefined
|| (activity.channelData && activity.channelData.messageBack && !activity.channelData.messageBack.displayText)
// Do not show empty bubbles (no text and attachments, and not "typing")
|| !(text || attachments.length)
) {
return RETURN_FALSE;
}
} else if (type === 'typing' && activity.from.role === 'user') {
// Do not show typing by oneself
return RETURN_FALSE;
}

if (activity.type === 'message' || activity.type === 'typing') {
if (type === 'message' || type === 'typing') {
if (
activity.type === 'message'
type === 'message'
&& (activity.attachments || []).length > 1
&& activity.attachmentLayout === 'carousel'
) {
return children => <CarouselLayout activity={ activity } showTimestamp={ showTimestamp }>{ children }</CarouselLayout>;
return children => <CarouselLayout activity={ activity } timestampClassName={ timestampClassName }>{ children }</CarouselLayout>;
} else {
return children => <StackedLayout activity={ activity } showTimestamp={ showTimestamp }>{ children }</StackedLayout>;
return children => <StackedLayout activity={ activity } timestampClassName={ timestampClassName }>{ children }</StackedLayout>;
}
} else {
return next({ activity, showTimestamp });
return next({ activity, timestampClassName });
}
};
}
2 changes: 2 additions & 0 deletions packages/playground/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,8 @@ export default class extends React.Component {
<option value="false">Don't show timestamp</option>
<option value="0">Don't group</option>
<option value="1000">1 second</option>
<option value="2000">2 seconds</option>
<option value="5000">5 seconds</option>
<option value="10000">10 seconds</option>
<option value="60000">One minute</option>
<option value="300000">5 minutes</option>
Expand Down
8 changes: 5 additions & 3 deletions packages/playground/src/createDevModeActivityMiddleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,10 @@ const ConnectedDevModeDecorator = connectToWebChat(

export default function () {
return () => next => card => {
return (children =>
<ConnectedDevModeDecorator card={ card }>{ next(card)(children) }</ConnectedDevModeDecorator>
);
return (children => {
const content = next(card)(children);

return !!content && <ConnectedDevModeDecorator card={ card }>{ content }</ConnectedDevModeDecorator>;
});
};
}
10 changes: 10 additions & 0 deletions samples/10.a.customization-card-components/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Sample

A simple web page with a maximized Web Chat and hosted using React.

# How to run

- Run `npx serve`
- Browse to [http://localhost:5000/](http://localhost:5000/)

# Things to try out
17 changes: 17 additions & 0 deletions samples/10.b.customization-password-input/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Sample - Password input activity

Show a custom activity that accepts password input and submit as a postback activity.

# Test out the hosted sample

- [Try out MockBot](https://microsoft.github.io/BotFramework-WebChat/10.b.customization-password-input)

# Things to try out

- Type `sample:password-input` to get the password input box

# Further reading

## Full list of Web Chat hosted samples

View the list of available samples by clicking [here](https://github.com/Microsoft/BotFramework-WebChat/tree/master/samples)
Loading