Skip to content

Commit

Permalink
Merge branch 'master' into sa-fix
Browse files Browse the repository at this point in the history
  • Loading branch information
corinagum authored Dec 12, 2019
2 parents 941a7db + c61576d commit 2499967
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 28 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Moved `<SayComposer>` from `<BasicTranscript>` to `<Composer>`
- Moved WebSpeechPonyfill patching code from `<BasicTranscript>` to `<Composer>`
- Fixes [#2699](https://github.com/microsoft/BotFramework-WebChat/issues/2699). Disable speech recognition and synthesis when using Direct Line Speech under IE11, in PR [#2649](https://github.com/microsoft/BotFramework-WebChat/pull/2649)
- Fixes [#2710](https://github.com/microsoft/BotFramework-WebChat/issues/2710). Suggested actions container should persist for AT, by [@corinagum](https://github.com/corinagum) in PR [#2710](https://github.com/microsoft/BotFramework-WebChat/pull/2710)
- Fixes [#2709](https://github.com/microsoft/BotFramework-WebChat/issues/2709). Reduce wasted render of activities by memoizing partial result of `<BasicTranscript>`, in PR [#2710](https://github.com/microsoft/BotFramework-WebChat/pull/2710)
- Fixes [#2710](https://github.com/microsoft/BotFramework-WebChat/issues/2710). Suggested actions container should persist for AT, by [@corinagum](https://github.com/corinagum) in PR [#2710](https://github.com/microsoft/BotFramework-WebChat/pull/2710

### Changed

Expand Down
60 changes: 33 additions & 27 deletions packages/component/src/BasicTranscript.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { css } from 'glamor';
import { Panel as ScrollToBottomPanel } from 'react-scroll-to-bottom';
import classNames from 'classnames';
import PropTypes from 'prop-types';
import React, { useMemo } from 'react';
import React, { useCallback, useMemo } from 'react';

import ScrollToEndButton from './Activity/ScrollToEndButton';
import SpeakActivity from './Activity/Speak';
Expand All @@ -12,6 +12,7 @@ import useRenderActivity from './hooks/useRenderActivity';
import useRenderAttachment from './hooks/useRenderAttachment';
import useStyleOptions from './hooks/useStyleOptions';
import useStyleSet from './hooks/useStyleSet';
import useMemoArrayMap from './hooks/internal/useMemoArrayMap';

const ROOT_CSS = css({
overflow: 'hidden',
Expand Down Expand Up @@ -62,45 +63,50 @@ const BasicTranscript = ({ className }) => {
const [groupTimestamp] = useGroupTimestamp();
const renderAttachment = useRenderAttachment();
const renderActivity = useRenderActivity(renderAttachment);
const renderActivityElement = useCallback(
activity => {
const element = renderActivity({
activity,
timestampClassName: 'transcript-timestamp'
});

return element && { activity, element };
},
[renderActivity]
);

// 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 timestamps.
// If the activity does not render, it will not be spoken if text-to-speech is enabled.
const activityElements = useMemo(
() =>
activities.reduce((activityElements, activity) => {
const element = renderActivity({
activity,
timestampClassName: 'transcript-timestamp'
});

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

return activityElements;
}, []),
[activities, renderActivity]
);
const activityElements = useMemoArrayMap(activities, renderActivityElement);

// TODO: [P2] We can also use useMemoArrayMap() for this function.
// useMemoArrayMap(array, mapper) will need to be modified to useMemoArrayMap(array, mapper, getDeps).
// This is because the deps for every item is not itself anymore. It will include activityElements[index + 1].
const trimmedActivityElements = activityElements.filter(activityElement => activityElement);
const activityElementsWithMetadata = useMemo(
() =>
activityElements.map((activityElement, index) => {
const { activity } = activityElement;
const { activity: nextActivity } = activityElements[index + 1] || {};
trimmedActivityElements
.filter(activityElement => activityElement)
.map((activityElement, index) => {
const { activity } = activityElement;
const { activity: nextActivity } = trimmedActivityElements[index + 1] || {};

return {
...activityElement,
return {
...activityElement,

key: (activity.channelData && activity.channelData.clientActivityID) || activity.id || index,
key: (activity.channelData && activity.channelData.clientActivityID) || activity.id || index,

// TODO: [P2] We should use core/definitions/speakingActivity for this predicate instead
shouldSpeak: activity.channelData && activity.channelData.speak,
// TODO: [P2] We should use core/definitions/speakingActivity for this predicate instead
shouldSpeak: activity.channelData && activity.channelData.speak,

// Hide timestamp if same timestamp group with the next activity
timestampVisible: !sameTimestampGroup(activity, nextActivity, groupTimestamp)
};
}),
[activityElements, groupTimestamp]
// Hide timestamp if same timestamp group with the next activity
timestampVisible: !sameTimestampGroup(activity, nextActivity, groupTimestamp)
};
}),
[groupTimestamp, trimmedActivityElements]
);

return (
Expand Down
26 changes: 26 additions & 0 deletions packages/component/src/hooks/internal/useMemoArrayMap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { useMemo, useRef } from 'react';

export default function useMemoArrayMap(array, mapper) {
const prevMapperRef = useRef();
const sameMapper = Object.is(mapper, prevMapperRef.current);

const prevMapperCallsRef = useRef([]);
const { current: prevMapperCalls = [] } = sameMapper ? prevMapperCallsRef : {};
const nextMapperCalls = [];

return useMemo(() => {
const mapped = array.map((value, index) => {
const prevResult = prevMapperCalls.find(({ value: targetValue }) => targetValue === value);
const { result = mapper.call(array, value, index) } = prevResult || {};

nextMapperCalls.push({ result, value });

return result;
});

prevMapperCallsRef.current = nextMapperCalls;
prevMapperRef.current = mapper;

return mapped;
}, [array, mapper]);
}

0 comments on commit 2499967

Please sign in to comment.