diff --git a/CHANGELOG.md b/CHANGELOG.md index f3a0bd994d..ebd3f5f39f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -90,6 +90,7 @@ Notes: web developers are advised to use [`~` (tilde range)](https://github.com/ - Moved to `micromark` for rendering Markdown, instead of `markdown-it`, in PR [#5330](https://github.com/microsoft/BotFramework-WebChat/pull/5330), by [@compulim](https://github.com/compulim) - Improved view code dialog UI in Fluent theme with better styling and accessibility, in PR [#5337](https://github.com/microsoft/BotFramework-WebChat/pull/5337), by [@OEvgeny](https://github.com/OEvgeny) - Switched math block syntax from `$$` to Tex-style `\[ \]` and `\( \)` delimiters with improved rendering and error handling, in PR [#5353](https://github.com/microsoft/BotFramework-WebChat/pull/5353), by [@OEvgeny](https://github.com/OEvgeny) +- Improved avatar display and grouping behavior by fixing rendering issues and activity sender identification, in PR [#5346](https://github.com/microsoft/BotFramework-WebChat/pull/5346), by [@OEvgeny](https://github.com/OEvgeny) ### Fixed diff --git a/__tests__/__image_snapshots__/html/transcript-navigation-focus-activity-save-focus-js-transcript-navigation-should-save-last-focused-activity-5-snap.png b/__tests__/__image_snapshots__/html/transcript-navigation-focus-activity-save-focus-js-transcript-navigation-should-save-last-focused-activity-5-snap.png index 475f62b550..e6a86b782f 100644 Binary files a/__tests__/__image_snapshots__/html/transcript-navigation-focus-activity-save-focus-js-transcript-navigation-should-save-last-focused-activity-5-snap.png and b/__tests__/__image_snapshots__/html/transcript-navigation-focus-activity-save-focus-js-transcript-navigation-should-save-last-focused-activity-5-snap.png differ diff --git a/__tests__/html/autoScroll.withPostBack.activity.html b/__tests__/html/autoScroll.withPostBack.activity.html index bddf54762f..b19d5a1ec0 100644 --- a/__tests__/html/autoScroll.withPostBack.activity.html +++ b/__tests__/html/autoScroll.withPostBack.activity.html @@ -101,7 +101,6 @@ } ], from: { - id: 'bot', role: 'bot' }, id: '1', diff --git a/__tests__/html/autoScroll.withPostBack.page.html b/__tests__/html/autoScroll.withPostBack.page.html index cf2eb3497d..4abbacac6c 100644 --- a/__tests__/html/autoScroll.withPostBack.page.html +++ b/__tests__/html/autoScroll.withPostBack.page.html @@ -101,7 +101,6 @@ } ], from: { - id: 'bot', role: 'bot' }, id: '1', diff --git a/__tests__/html/typing/activityOrder.html b/__tests__/html/typing/activityOrder.html index 4ce388253c..4acc0986a7 100644 --- a/__tests__/html/typing/activityOrder.html +++ b/__tests__/html/typing/activityOrder.html @@ -51,6 +51,7 @@ // SETUP: Bot sent a message. await directLine.emulateIncomingActivity({ id: 'a-00001', + from: { id: 'u-00001', name: 'Bot', role: 'bot' }, text: 'Adipisicing cupidatat eu Lorem anim ut aute magna occaecat id cillum.', type: 'message' }); @@ -91,6 +92,7 @@ // WHEN: Bot send another message. await directLine.emulateIncomingActivity({ id: 'a-00003', + from: { id: 'u-00001', name: 'Bot', role: 'bot' }, text: 'Amet consequat enim incididunt excepteur aliquip magna duis et tempor.', type: 'message' }); diff --git a/__tests__/html/typing/chunk.html b/__tests__/html/typing/chunk.html index ebc4f40936..66f967ccc7 100644 --- a/__tests__/html/typing/chunk.html +++ b/__tests__/html/typing/chunk.html @@ -51,6 +51,7 @@ // SETUP: Bot sent a message. await directLine.emulateIncomingActivity({ id: 'a-00001', + from: { id: 'u-00001', name: 'Bot', role: 'bot' }, text: 'Adipisicing cupidatat eu Lorem anim ut aute magna occaecat id cillum.', type: 'message' }); diff --git a/__tests__/html/typing/informative.html b/__tests__/html/typing/informative.html index b2f4cdc8de..351f13ca42 100644 --- a/__tests__/html/typing/informative.html +++ b/__tests__/html/typing/informative.html @@ -33,9 +33,14 @@ await pageConditions.uiConnected(); - await directLine.emulateIncomingActivity( - 'Adipisicing cupidatat eu Lorem anim ut aute magna occaecat id cillum.' - ); + await directLine.emulateIncomingActivity({ + from: { + id: 'u-00001', + name: 'Bot', + role: 'bot' + }, + text: 'Adipisicing cupidatat eu Lorem anim ut aute magna occaecat id cillum.' + }); const firstTypingActivityId = 't-00001'; diff --git a/__tests__/html/typing/outOfOrder.html b/__tests__/html/typing/outOfOrder.html index dc7a8ccb29..e0e474d022 100644 --- a/__tests__/html/typing/outOfOrder.html +++ b/__tests__/html/typing/outOfOrder.html @@ -55,6 +55,7 @@ // SETUP: Bot sent a message. await directLine.emulateIncomingActivity({ id: 'a-00001', + from: { id: 'u-00001', name: 'Bot', role: 'bot' }, text: 'Adipisicing cupidatat eu Lorem anim ut aute magna occaecat id cillum.', timestamp: 0, type: 'message' diff --git a/__tests__/html/typing/outOfOrder.sequenceNumber.html b/__tests__/html/typing/outOfOrder.sequenceNumber.html index 9dce5cbb52..a8c78e225d 100644 --- a/__tests__/html/typing/outOfOrder.sequenceNumber.html +++ b/__tests__/html/typing/outOfOrder.sequenceNumber.html @@ -55,6 +55,7 @@ // SETUP: Bot sent a message. await directLine.emulateIncomingActivity({ id: 'a-00001', + from: { id: 'u-00001', name: 'Bot', role: 'bot' }, text: 'Adipisicing cupidatat eu Lorem anim ut aute magna occaecat id cillum.', timestamp: 0, type: 'message' diff --git a/__tests__/html/typing/simultaneous.html b/__tests__/html/typing/simultaneous.html index 6377f11ece..91ebf62764 100644 --- a/__tests__/html/typing/simultaneous.html +++ b/__tests__/html/typing/simultaneous.html @@ -60,7 +60,7 @@ // WHEN: Bot is typing a message. await directLine.emulateIncomingActivity({ channelData: { streamSequence: 1, streamType: 'streaming' }, - from: { id: 'u-00001', name: 'Bot', role: 'bot' }, + from: { name: 'Bot', role: 'bot' }, id: 't-00001', text: 'A quick', type: 'typing' @@ -89,7 +89,7 @@ // WHEN: Bot is typing another message. await directLine.emulateIncomingActivity({ channelData: { streamSequence: 1, streamType: 'streaming' }, - from: { id: 'u-00001', name: 'Bot', role: 'bot' }, + from: { name: 'Bot', role: 'bot' }, id: 't-10001', text: 'Falsches Üben', type: 'typing' @@ -120,7 +120,7 @@ // WHEN: Bot continue typing the message. await directLine.emulateIncomingActivity({ channelData: { streamSequence: 2, streamId: 't-00001', streamType: 'streaming' }, - from: { id: 'u-00001', name: 'Bot', role: 'bot' }, + from: { name: 'Bot', role: 'bot' }, id: 't-00002', text: 'A quick brown fox', type: 'typing' @@ -149,7 +149,7 @@ // WHEN: Bot continue typing the third message. await directLine.emulateIncomingActivity({ channelData: { streamSequence: 2, streamId: 't-10001', streamType: 'streaming' }, - from: { id: 'u-00001', name: 'Bot', role: 'bot' }, + from: { name: 'Bot', role: 'bot' }, id: 't-10002', text: 'Falsches Üben von Xylophonmusik', type: 'typing' @@ -178,7 +178,7 @@ // WHEN: Bot finished typing the second message. await directLine.emulateIncomingActivity({ channelData: { streamSequence: 3, streamId: 't-00001', streamType: 'streaming' }, - from: { id: 'u-00001', name: 'Bot', role: 'bot' }, + from: { name: 'Bot', role: 'bot' }, id: 'a-00002', text: 'A quick brown fox jumped over the lazy dogs.', type: 'message' @@ -210,7 +210,7 @@ // WHEN: Bot finished typing the third message. await directLine.emulateIncomingActivity({ channelData: { streamSequence: 3, streamId: 't-10001', streamType: 'streaming' }, - from: { id: 'u-00001', name: 'Bot', role: 'bot' }, + from: { name: 'Bot', role: 'bot' }, id: 'a-00003', text: 'Falsches Üben von Xylophonmusik quält jeden größeren Zwerg.', type: 'message' diff --git a/__tests__/html2/activity/avatar.aspect.html b/__tests__/html2/activity/avatar.aspect.html new file mode 100644 index 0000000000..25bb73e577 --- /dev/null +++ b/__tests__/html2/activity/avatar.aspect.html @@ -0,0 +1,211 @@ + + + + + + + + + + + + + +
+ + + diff --git a/__tests__/html2/activity/avatar.aspect.html.snap-1.png b/__tests__/html2/activity/avatar.aspect.html.snap-1.png new file mode 100644 index 0000000000..f11de2b809 Binary files /dev/null and b/__tests__/html2/activity/avatar.aspect.html.snap-1.png differ diff --git a/__tests__/html2/activity/avatar.image.html b/__tests__/html2/activity/avatar.image.html new file mode 100644 index 0000000000..590f192bea --- /dev/null +++ b/__tests__/html2/activity/avatar.image.html @@ -0,0 +1,203 @@ + + + + + + + + + + + + + +
+ + + diff --git a/__tests__/html2/activity/avatar.image.html.snap-1.png b/__tests__/html2/activity/avatar.image.html.snap-1.png new file mode 100644 index 0000000000..afcde7e312 Binary files /dev/null and b/__tests__/html2/activity/avatar.image.html.snap-1.png differ diff --git a/__tests__/html2/activity/grouping.multipleAvatars.html b/__tests__/html2/activity/grouping.multipleAvatars.html new file mode 100644 index 0000000000..88081ffa36 --- /dev/null +++ b/__tests__/html2/activity/grouping.multipleAvatars.html @@ -0,0 +1,240 @@ + + + + + + + + + + + + + + +
+ + + diff --git a/__tests__/html2/activity/grouping.multipleAvatars.html.snap-1.png b/__tests__/html2/activity/grouping.multipleAvatars.html.snap-1.png new file mode 100644 index 0000000000..1849c6a21d Binary files /dev/null and b/__tests__/html2/activity/grouping.multipleAvatars.html.snap-1.png differ diff --git a/packages/api/src/hooks/middleware/createDefaultGroupActivitiesMiddleware.ts b/packages/api/src/hooks/middleware/createDefaultGroupActivitiesMiddleware.ts index 23d81d3162..85f163b76e 100644 --- a/packages/api/src/hooks/middleware/createDefaultGroupActivitiesMiddleware.ts +++ b/packages/api/src/hooks/middleware/createDefaultGroupActivitiesMiddleware.ts @@ -8,7 +8,7 @@ function bin(items: T[], grouping: (last: T, current: T) => boolean): T[][] { const bins: T[][] = []; let lastItem: T; - items.forEach(item => { + for (const item of items) { if (lastItem && grouping(lastItem, item)) { lastBin.push(item); } else { @@ -17,7 +17,7 @@ function bin(items: T[], grouping: (last: T, current: T) => boolean): T[][] { } lastItem = item; - }); + } return bins; } @@ -25,38 +25,43 @@ function bin(items: T[], grouping: (last: T, current: T) => boolean): T[][] { function sending(activity: WebChatActivity): SendStatus | undefined { if (activity.from.role === 'user') { const { - channelData: { state, 'webchat:send-status': sendStatus } + channelData: { 'webchat:send-status': sendStatus } } = activity; - // `channelData.state` is being deprecated in favor of `channelData['webchat:send-status']`. - // Please refer to #4362 for details. Remove on or after 2024-07-31. - return sendStatus || (state === 'sent' ? 'sent' : 'sending'); + return sendStatus; } } -function shouldGroupTimestamp( - activityX: WebChatActivity, - activityY: WebChatActivity, - groupTimestamp: boolean | number, - { Date }: GlobalScopePonyfill -): boolean { - if (groupTimestamp === false) { - // Hide timestamp for all activities. - return true; - } else if (activityX && activityY) { - if (sending(activityX) !== sending(activityY)) { - return false; - } +function createShouldGroupTimestamp(groupTimestamp: boolean | number, { Date }: GlobalScopePonyfill) { + return (activityX: WebChatActivity, activityY: WebChatActivity): boolean => { + if (groupTimestamp === false) { + // Hide timestamp for all activities. + return true; + } else if (activityX && activityY) { + if (sending(activityX) !== sending(activityY)) { + return false; + } - groupTimestamp = typeof groupTimestamp === 'number' ? groupTimestamp : Infinity; + groupTimestamp = typeof groupTimestamp === 'number' ? groupTimestamp : Infinity; - const timeX = new Date(activityX.timestamp).getTime(); - const timeY = new Date(activityY.timestamp).getTime(); + const timeX = new Date(activityX.timestamp).getTime(); + const timeY = new Date(activityY.timestamp).getTime(); - return Math.abs(timeX - timeY) <= groupTimestamp; - } + return Math.abs(timeX - timeY) <= groupTimestamp; + } + + return false; + }; +} - return false; +function shouldGroupSender(x: WebChatActivity, y: WebChatActivity): boolean { + const { + from: { role: roleX, id: idX } + } = x; + const { + from: { role: roleY, id: idY } + } = y; + return roleX === roleY && idX === idY; } export default function createDefaultGroupActivitiesMiddleware({ @@ -69,7 +74,7 @@ export default function createDefaultGroupActivitiesMiddleware({ return () => () => ({ activities }) => ({ - sender: bin(activities, (x, y) => x.from.role === y.from.role), - status: bin(activities, (x, y) => shouldGroupTimestamp(x, y, groupTimestamp, ponyfill)) + sender: bin(activities, shouldGroupSender), + status: bin(activities, createShouldGroupTimestamp(groupTimestamp, ponyfill)) }); } diff --git a/packages/component/src/Avatar/ImageAvatar.tsx b/packages/component/src/Avatar/ImageAvatar.tsx index 83a440fbbd..89578baa7c 100644 --- a/packages/component/src/Avatar/ImageAvatar.tsx +++ b/packages/component/src/Avatar/ImageAvatar.tsx @@ -2,7 +2,6 @@ import { hooks } from 'botframework-webchat-api'; import classNames from 'classnames'; import React, { memo } from 'react'; -import FixedWidthImage from '../Utils/FixedWidthImage'; import useStyleSet from '../hooks/useStyleSet'; import { useStyleToEmotionObject } from '../hooks/internal/styleToEmotionObject'; @@ -10,7 +9,8 @@ const { useAvatarForBot, useAvatarForUser } = hooks; const ROOT_STYLE = { '& .webchat__imageAvatar__image': { - width: '100%' + width: '100%', + height: 'auto' } }; @@ -25,11 +25,7 @@ const ImageAvatar = memo(({ fromUser }: Readonly<{ fromUser: boolean }>) => { return ( !!avatarImage && (
- +
) ); diff --git a/packages/component/src/Middleware/Avatar/createCoreMiddleware.tsx b/packages/component/src/Middleware/Avatar/createCoreMiddleware.tsx index 1f015eb364..07863132f0 100644 --- a/packages/component/src/Middleware/Avatar/createCoreMiddleware.tsx +++ b/packages/component/src/Middleware/Avatar/createCoreMiddleware.tsx @@ -9,7 +9,7 @@ import useStyleSet from '../../hooks/useStyleSet'; import { useStyleToEmotionObject } from '../../hooks/internal/styleToEmotionObject'; const ROOT_STYLE = { - overflow: 'hidden', + overflow: ['hidden', 'clip'], position: 'relative', '> *': { diff --git a/packages/component/src/Styles/StyleSet/ImageAvatar.ts b/packages/component/src/Styles/StyleSet/ImageAvatar.ts index 3dfc919a1f..59735b438a 100644 --- a/packages/component/src/Styles/StyleSet/ImageAvatar.ts +++ b/packages/component/src/Styles/StyleSet/ImageAvatar.ts @@ -2,8 +2,11 @@ import CSSTokens from '../CSSTokens'; export default function createImageAvatarStyle() { return { + alignItems: 'center', + display: 'flex', height: CSSTokens.SizeAvatar, - overflow: 'hidden', + justifyContent: 'center', + overflow: ['hidden', 'clip'], width: CSSTokens.SizeAvatar }; }