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

Adds AdaptiveCardsComposer and AdaptiveCardsContext for composability #2648

Merged
merged 8 commits into from
Dec 2, 2019
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- 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)
- Fixes [#2597](https://github.com/microsoft/BotFramework-WebChat/issues/2597). Modify `watch` script to `start` and add `tableflip` script for throwing `node_modules`, by [@corinagum](https://github.com/corinagum) in PR [#2598](https://github.com/microsoft/BotFramework-WebChat/pull/2598)
- Adds Arabic Language Support, by [@midineo](https://github.com/midineo), in PR [#2593](https://github.com/microsoft/BotFramework-WebChat/pull/2593)
- Adds `AdaptiveCardsComposer` and `AdaptiveCardsContext` for composability for Adaptive Cards, by [@compulim](https://github.com/compulim), in PR [#2648](https://github.com/microsoft/BotFramework-WebChat/pull/2648)

### Fixed

Expand Down
41 changes: 41 additions & 0 deletions packages/bundle/src/FullComposer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import PropTypes from 'prop-types';
import React from 'react';

import AdaptiveCardsComposer from './adaptiveCards/AdaptiveCardsComposer';
import { Components } from 'botframework-webchat-component';

import useComposerProps from './useComposerProps';

const { Composer } = Components;

const FullComposer = props => {
const { adaptiveCardsHostConfig, adaptiveCardsPackage, children, ...otherProps } = props;
const composerProps = useComposerProps(props);

return (
<AdaptiveCardsComposer
adaptiveCardsHostConfig={adaptiveCardsHostConfig}
adaptiveCardsPackage={adaptiveCardsPackage}
>
<Composer {...otherProps} {...composerProps}>
{children}
</Composer>
</AdaptiveCardsComposer>
);
};

FullComposer.defaultProps = {
...Composer.defaultProps,
adaptiveCardsHostConfig: undefined,
adaptiveCardsPackage: undefined,
children: undefined
};

FullComposer.propTypes = {
...Composer.propTypes,
adaptiveCardsHostConfig: PropTypes.any,
adaptiveCardsPackage: PropTypes.any,
children: PropTypes.any
};

export default FullComposer;
82 changes: 18 additions & 64 deletions packages/bundle/src/FullReactWebChat.js
Original file line number Diff line number Diff line change
@@ -1,93 +1,47 @@
import * as defaultAdaptiveCardsPackage from 'adaptivecards';
import BasicWebChat, { concatMiddleware } from 'botframework-webchat-component';
import BasicWebChat from 'botframework-webchat-component';
import PropTypes from 'prop-types';
import React, { useEffect, useMemo } from 'react';
import React, { useEffect } from 'react';

import AdaptiveCardsContext from './adaptiveCards/AdaptiveCardsContext';
import createAdaptiveCardsAttachmentMiddleware from './adaptiveCards/createAdaptiveCardMiddleware';
import createDefaultAdaptiveCardHostConfig from './adaptiveCards/Styles/adaptiveCardHostConfig';
import createStyleSet from './adaptiveCards/Styles/createStyleSetWithAdaptiveCards';
import defaultRenderMarkdown from './renderMarkdown';
import AdaptiveCardsComposer from './adaptiveCards/AdaptiveCardsComposer';
import useComposerProps from './useComposerProps';

// Add additional props to <WebChat>, so it support additional features
const FullReactWebChat = ({
adaptiveCardHostConfig,
adaptiveCardsHostConfig,
adaptiveCardsPackage,
attachmentMiddleware,
renderMarkdown,
styleOptions,
styleSet,
...otherProps
}) => {
const FullReactWebChat = props => {
const { adaptiveCardHostConfig, adaptiveCardsHostConfig, adaptiveCardsPackage, ...otherProps } = props;

useEffect(() => {
adaptiveCardHostConfig &&
console.warn(
'Web Chat: "adaptiveCardHostConfig" is deprecated. Please use "adaptiveCardsHostConfig" instead. "adaptiveCardHostConfig" will be removed on or after 2022-01-01.'
);
}, [adaptiveCardHostConfig]);

const patchedStyleSet = useMemo(() => styleSet || createStyleSet(styleOptions), [styleOptions, styleSet]);
const { options: patchedStyleOptions } = patchedStyleSet;

const patchedAdaptiveCardsHostConfig = useMemo(
() => adaptiveCardsHostConfig || adaptiveCardHostConfig || createDefaultAdaptiveCardHostConfig(patchedStyleOptions),
[adaptiveCardHostConfig, adaptiveCardsHostConfig, patchedStyleOptions]
);

const patchedAdaptiveCardsPackage = useMemo(() => adaptiveCardsPackage || defaultAdaptiveCardsPackage, [
adaptiveCardsPackage
]);

const patchedRenderMarkdown = useMemo(
() => renderMarkdown || (markdown => defaultRenderMarkdown(markdown, patchedStyleOptions)),
[renderMarkdown, patchedStyleOptions]
);

const patchedAttachmentMiddleware = useMemo(
() => concatMiddleware(attachmentMiddleware, createAdaptiveCardsAttachmentMiddleware()),
[attachmentMiddleware]
);

const adaptiveCardsContext = useMemo(
() => ({
adaptiveCardsPackage: patchedAdaptiveCardsPackage,
hostConfig: patchedAdaptiveCardsHostConfig
}),
[patchedAdaptiveCardsHostConfig, patchedAdaptiveCardsPackage]
);
const composerProps = useComposerProps(props);

return (
<AdaptiveCardsContext.Provider value={adaptiveCardsContext}>
<BasicWebChat
attachmentMiddleware={patchedAttachmentMiddleware}
renderMarkdown={patchedRenderMarkdown}
styleOptions={styleOptions}
styleSet={patchedStyleSet}
{...otherProps}
/>
</AdaptiveCardsContext.Provider>
<AdaptiveCardsComposer
adaptiveCardsHostConfig={adaptiveCardHostConfig || adaptiveCardsHostConfig}
adaptiveCardsPackage={adaptiveCardsPackage}
>
<BasicWebChat {...otherProps} {...composerProps} />
</AdaptiveCardsComposer>
);
};

FullReactWebChat.defaultProps = {
...BasicWebChat.defaultProps,
adaptiveCardHostConfig: undefined,
adaptiveCardsHostConfig: undefined,
adaptiveCardsPackage: undefined,
attachmentMiddleware: undefined,
renderMarkdown: undefined,
styleOptions: undefined,
styleSet: undefined
renderMarkdown: undefined
};

FullReactWebChat.propTypes = {
...BasicWebChat.propTypes,
adaptiveCardHostConfig: PropTypes.any,
adaptiveCardsHostConfig: PropTypes.any,
adaptiveCardsPackage: PropTypes.any,
attachmentMiddleware: PropTypes.func,
renderMarkdown: PropTypes.func,
styleOptions: PropTypes.any,
styleSet: PropTypes.any
renderMarkdown: PropTypes.func
};

export default FullReactWebChat;
35 changes: 35 additions & 0 deletions packages/bundle/src/adaptiveCards/AdaptiveCardsComposer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import * as defaultAdaptiveCardsPackage from 'adaptivecards';
import PropTypes from 'prop-types';
import React, { useMemo } from 'react';

import AdaptiveCardsContext from './AdaptiveCardsContext';

const AdaptiveCardsComposer = ({ adaptiveCardsHostConfig, adaptiveCardsPackage, children }) => {
const patchedAdaptiveCardsPackage = useMemo(() => adaptiveCardsPackage || defaultAdaptiveCardsPackage, [
adaptiveCardsPackage
]);

const adaptiveCardsContext = useMemo(
() => ({
adaptiveCardsPackage: patchedAdaptiveCardsPackage,
hostConfigFromProps: adaptiveCardsHostConfig
}),
[adaptiveCardsHostConfig, patchedAdaptiveCardsPackage]
);

return <AdaptiveCardsContext.Provider value={adaptiveCardsContext}>{children}</AdaptiveCardsContext.Provider>;
};

AdaptiveCardsComposer.defaultProps = {
adaptiveCardsHostConfig: undefined,
adaptiveCardsPackage: undefined,
children: undefined
};

AdaptiveCardsComposer.propTypes = {
adaptiveCardsHostConfig: PropTypes.any,
adaptiveCardsPackage: PropTypes.any,
children: PropTypes.any
};

export default AdaptiveCardsComposer;
5 changes: 1 addition & 4 deletions packages/bundle/src/adaptiveCards/AdaptiveCardsContext.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import { createContext } from 'react';

const AdaptiveCardHostConfigContext = createContext({
adaptiveCardsPackage: undefined,
hostConfig: undefined
});
const AdaptiveCardHostConfigContext = createContext();

export default AdaptiveCardHostConfigContext;
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,6 @@ HeroCardAttachment.propTypes = {
content: PropTypes.shape({
tap: PropTypes.any
}).isRequired
}).isRequired,
styleSet: PropTypes.shape({
options: PropTypes.any.isRequired
}).isRequired
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { createStyleSet as createBasicStyleSet, defaultStyleOptions } from 'botframework-webchat-component';
import { defaultStyleOptions } from 'botframework-webchat-component';

import createAdaptiveCardRendererStyle from './StyleSet/AdaptiveCardRenderer';
import createAnimationCardAttachmentStyle from './StyleSet/AnimationCardAttachment';
import createAudioCardAttachmentStyle from './StyleSet/AudioCardAttachment';
Expand All @@ -7,15 +8,10 @@ import createAudioCardAttachmentStyle from './StyleSet/AudioCardAttachment';
// "styleSet" is actually CSS stylesheet and it is based on the DOM tree.
// DOM tree may change from time to time, thus, maintaining "styleSet" becomes a constant effort.

export default function createStyleSet(options) {
export default function createAdaptiveCardsStyleSet(options) {
options = { ...defaultStyleOptions, ...options };

const basicStyleSet = createBasicStyleSet(options);

// Keep this list flat (no nested style) and serializable (no functions)

return {
...basicStyleSet,
adaptiveCardRenderer: createAdaptiveCardRendererStyle(options),
animationCardAttachment: createAnimationCardAttachmentStyle(options),
audioCardAttachment: createAudioCardAttachmentStyle(options)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import createStyleSet from './createStyleSetWithAdaptiveCards';
import createStyleSet from './createAdaptiveCardsStyleSet';

describe('createStyleSetWithAdaptiveCards', () => {
describe('createAdaptiveCardsStyleSet', () => {
it('should contain Adaptive Card styles in createStyleSet', () => {
const { adaptiveCardRenderer, animationCardAttachment, audioCardAttachment } = createStyleSet();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ import SignInCardAttachment from './Attachment/SignInCardAttachment';
import ThumbnailCardAttachment from './Attachment/ThumbnailCardAttachment';
import VideoCardAttachment from './Attachment/VideoCardAttachment';

// TODO: [P4] Rename this file or the whole middleware, it looks either too simple or too comprehensive now
export default function createAdaptiveCardMiddleware() {
export default function createAdaptiveCardsAttachmentMiddleware() {
return () => next => {
function AdaptiveCardMiddleware({ activity, attachment }) {
return attachment.contentType === 'application/vnd.microsoft.card.hero' ? (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { useContext } from 'react';

import AdaptiveCardsContext from '../../AdaptiveCardsContext';

export default function useAdaptiveCardsContext() {
const context = useContext(AdaptiveCardsContext);

if (!context) {
throw new Error('This hook can only be used on component that is decendants of <ComposerWithAdaptiveCards>');
}

return context;
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
import { useContext } from 'react';
import { hooks } from 'botframework-webchat-component';
import { useMemo } from 'react';

import AdaptiveCardsContext from '../AdaptiveCardsContext';
import createDefaultAdaptiveCardHostConfig from '../Styles/adaptiveCardHostConfig';
import useAdaptiveCardsContext from './internal/useAdaptiveCardsContext';

const { useStyleOptions } = hooks;

export default function useAdaptiveCardsHostConfig() {
const { hostConfig } = useContext(AdaptiveCardsContext);
const { hostConfigFromProps } = useAdaptiveCardsContext();
const [styleOptions] = useStyleOptions();

const patchedHostConfig = useMemo(() => hostConfigFromProps || createDefaultAdaptiveCardHostConfig(styleOptions), [
hostConfigFromProps,
styleOptions
]);

return [hostConfig];
return [patchedHostConfig];
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { useContext } from 'react';

import AdaptiveCardsContext from '../AdaptiveCardsContext';
import useAdaptiveCardsContext from './internal/useAdaptiveCardsContext';

export default function useAdaptiveCardsPackage() {
const { adaptiveCardsPackage } = useContext(AdaptiveCardsContext);
const { adaptiveCardsPackage } = useAdaptiveCardsContext();

return [adaptiveCardsPackage];
}
13 changes: 13 additions & 0 deletions packages/bundle/src/createFullStyleSet.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { createStyleSet } from 'botframework-webchat-component';
import createAdaptiveCardsStyleSet from './adaptiveCards/Styles/createAdaptiveCardsStyleSet';

// TODO: [P4] We should add a notice for people who want to use "styleSet" instead of "styleOptions".
compulim marked this conversation as resolved.
Show resolved Hide resolved
// "styleSet" is actually CSS stylesheet and it is based on the DOM tree.
// DOM tree may change from time to time, thus, maintaining "styleSet" becomes a constant effort.

export default function createFullStyleSet(options) {
return {
...createStyleSet(options),
...createAdaptiveCardsStyleSet(options)
};
}
14 changes: 12 additions & 2 deletions packages/bundle/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@

export * from './index-minimal';

import { hooks, version } from './index-minimal';
import { Components as MinimalComponents, hooks, version } from './index-minimal';
import addVersion from './addVersion';
import coreRenderWebChat from './renderWebChat';
import createAdaptiveCardsAttachmentMiddleware from './adaptiveCards/createAdaptiveCardsAttachmentMiddleware';
import createCognitiveServicesBingSpeechPonyfillFactory from './createCognitiveServicesBingSpeechPonyfillFactory';
import createCognitiveServicesSpeechServicesPonyfillFactory from './createCognitiveServicesSpeechServicesPonyfillFactory';
import createStyleSet from './adaptiveCards/Styles/createStyleSetWithAdaptiveCards';
import createStyleSet from './createFullStyleSet';
import defaultCreateDirectLine from './createDirectLine';
import FullComposer from './FullComposer';
import ReactWebChat from './FullReactWebChat';
import renderMarkdown from './renderMarkdown';
import useAdaptiveCardsHostConfig from './adaptiveCards/hooks/useAdaptiveCardsHostConfig';
Expand All @@ -31,9 +33,16 @@ const patchedHooks = {
useAdaptiveCardsPackage
};

const Components = {
...MinimalComponents,
Composer: FullComposer
};

export default ReactWebChat;

export {
Components,
createAdaptiveCardsAttachmentMiddleware,
createCognitiveServicesBingSpeechPonyfillFactory,
createCognitiveServicesSpeechServicesPonyfillFactory,
createStyleSet,
Expand All @@ -44,6 +53,7 @@ export {

window['WebChat'] = {
...window['WebChat'],
createAdaptiveCardsAttachmentMiddleware,
createCognitiveServicesBingSpeechPonyfillFactory,
createCognitiveServicesSpeechServicesPonyfillFactory,
createDirectLine,
Expand Down
27 changes: 27 additions & 0 deletions packages/bundle/src/useComposerProps.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { useMemo } from 'react';

import { concatMiddleware, defaultStyleOptions } from 'botframework-webchat-component';
import createAdaptiveCardsAttachmentMiddleware from './adaptiveCards/createAdaptiveCardsAttachmentMiddleware';
import createAdaptiveCardsStyleSet from './adaptiveCards/Styles/createAdaptiveCardsStyleSet';
import defaultRenderMarkdown from './renderMarkdown';

export default function useComposerProps({ attachmentMiddleware, renderMarkdown, styleOptions, styleSet }) {
const patchedAttachmentMiddleware = useMemo(
() => concatMiddleware(attachmentMiddleware, createAdaptiveCardsAttachmentMiddleware()),
[attachmentMiddleware]
);

const patchedStyleOptions = useMemo(() => ({ ...defaultStyleOptions, ...styleOptions }), [styleOptions]);

// When styleSet is not specified, the styleOptions will be used to create Adaptive Cards styleSet and merged into useStyleSet.
const extraStyleSet = useMemo(() => (styleSet ? undefined : createAdaptiveCardsStyleSet(patchedStyleOptions)), [
patchedStyleOptions,
styleSet
]);

return {
attachmentMiddleware: patchedAttachmentMiddleware,
extraStyleSet,
renderMarkdown: renderMarkdown || (text => defaultRenderMarkdown(text, patchedStyleOptions))
};
}
Loading