@seamly/web-ui 20.8.1 → 21.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (208) hide show
  1. package/build/dist/lib/deprecated-view.js +1 -1
  2. package/build/dist/lib/index.debug.js +585 -584
  3. package/build/dist/lib/index.debug.min.js +1 -1
  4. package/build/dist/lib/index.debug.min.js.LICENSE.txt +110 -110
  5. package/build/dist/lib/index.js +20269 -26441
  6. package/build/dist/lib/index.min.js +1 -1
  7. package/build/dist/lib/index.min.js.LICENSE.txt +6 -1
  8. package/build/dist/lib/standalone.js +27728 -34583
  9. package/build/dist/lib/standalone.min.js +1 -1
  10. package/build/dist/lib/standalone.min.js.LICENSE.txt +1 -1
  11. package/build/dist/lib/storage.js +6 -15
  12. package/build/dist/lib/style-guide.js +9660 -8970
  13. package/build/dist/lib/style-guide.min.js +1 -1
  14. package/build/dist/lib/styles-default-implementation.js +1 -1
  15. package/build/dist/lib/styles.js +1 -1
  16. package/build/dist/lib/utils.js +85 -3
  17. package/build/dist/lib/utils.min.js +1 -1
  18. package/package.json +54 -52
  19. package/src/icons/icon_check-16.svg +14 -0
  20. package/src/icons/icon_check-32.svg +14 -0
  21. package/src/javascripts/api/conversation-connector.ts +149 -0
  22. package/src/javascripts/api/errors/seamly-base-error.js +19 -0
  23. package/src/javascripts/api/errors/seamly-unavailable-error.js +5 -7
  24. package/src/javascripts/api/{index.js → index.ts} +163 -116
  25. package/src/javascripts/config.types.ts +5 -4
  26. package/src/javascripts/domains/app/actions.ts +47 -46
  27. package/src/javascripts/domains/app/hooks.js +1 -1
  28. package/src/javascripts/domains/config/actions.ts +2 -8
  29. package/src/javascripts/domains/config/hooks.ts +1 -1
  30. package/src/javascripts/domains/config/selectors.ts +6 -6
  31. package/src/javascripts/domains/config/slice.ts +3 -3
  32. package/src/javascripts/domains/errors/index.ts +66 -0
  33. package/src/javascripts/domains/forms/context.ts +1 -1
  34. package/src/javascripts/domains/forms/forms.types.ts +3 -3
  35. package/src/javascripts/domains/forms/hooks.ts +10 -10
  36. package/src/javascripts/domains/forms/provider.tsx +9 -9
  37. package/src/javascripts/domains/i18n/actions.ts +11 -5
  38. package/src/javascripts/domains/i18n/hooks.ts +11 -8
  39. package/src/javascripts/domains/i18n/selectors.ts +10 -4
  40. package/src/javascripts/domains/i18n/slice.ts +0 -1
  41. package/src/javascripts/domains/interrupt/hooks.ts +1 -1
  42. package/src/javascripts/domains/interrupt/middleware.ts +1 -1
  43. package/src/javascripts/domains/store/index.ts +1 -1
  44. package/src/javascripts/domains/store/selectors.ts +16 -0
  45. package/src/javascripts/domains/store/slice.ts +47 -41
  46. package/src/javascripts/domains/store/store.types.ts +38 -10
  47. package/src/javascripts/domains/translations/components/{options-button.js → options-button.tsx} +30 -20
  48. package/src/javascripts/domains/translations/components/options-dialog/index.tsx +33 -0
  49. package/src/javascripts/domains/translations/components/options-dialog/translation-option.tsx +37 -0
  50. package/src/javascripts/domains/translations/components/options-dialog/translation-options.tsx +85 -0
  51. package/src/javascripts/domains/translations/components/translation-status.tsx +15 -0
  52. package/src/javascripts/domains/translations/hooks.ts +77 -11
  53. package/src/javascripts/domains/translations/slice.ts +20 -9
  54. package/src/javascripts/domains/translations/translations.types.ts +4 -2
  55. package/src/javascripts/domains/visibility/actions.ts +6 -10
  56. package/src/javascripts/domains/visibility/hooks.ts +33 -14
  57. package/src/javascripts/domains/visibility/selectors.ts +3 -2
  58. package/src/javascripts/domains/visibility/slice.ts +2 -6
  59. package/src/javascripts/index.ts +19 -21
  60. package/src/javascripts/lib/engine/{index.js → index.tsx} +25 -7
  61. package/src/javascripts/lib/url-helpers.ts +112 -0
  62. package/src/javascripts/package/utils.js +5 -2
  63. package/src/javascripts/schema.ts +28 -0
  64. package/src/javascripts/style-guide/components/app.js +16 -12
  65. package/src/javascripts/style-guide/components/links.js +6 -6
  66. package/src/javascripts/style-guide/components/static-core.js +6 -3
  67. package/src/javascripts/style-guide/components/view.js +1 -1
  68. package/src/javascripts/style-guide/states.js +129 -31
  69. package/src/javascripts/style-guide/style-guide-engine.js +1 -1
  70. package/src/javascripts/ui/components/app-options/index.js +25 -6
  71. package/src/javascripts/ui/components/chat-app.js +1 -1
  72. package/src/javascripts/ui/components/chat-status/chat-status-action.tsx +30 -0
  73. package/src/javascripts/ui/components/chat-status/index.tsx +61 -0
  74. package/src/javascripts/ui/components/conversation/component-filter.js +9 -9
  75. package/src/javascripts/ui/components/conversation/{conversation.js → conversation.tsx} +32 -41
  76. package/src/javascripts/ui/components/conversation/event/card-component.js +2 -2
  77. package/src/javascripts/ui/components/conversation/event/card-message.js +1 -1
  78. package/src/javascripts/ui/components/conversation/event/carousel-component/components/controls.js +2 -2
  79. package/src/javascripts/ui/components/conversation/event/carousel-component/index.js +4 -4
  80. package/src/javascripts/ui/components/conversation/event/carousel-message/components/slide.js +2 -2
  81. package/src/javascripts/ui/components/conversation/event/carousel-message/index.js +1 -1
  82. package/src/javascripts/ui/components/conversation/event/chat-scroll/chat-scroll-context.ts +12 -0
  83. package/src/javascripts/ui/components/conversation/event/chat-scroll/chat-scroll-provider.tsx +46 -0
  84. package/src/javascripts/ui/components/conversation/event/chat-scroll/unread-messages-button.tsx +27 -0
  85. package/src/javascripts/ui/components/conversation/event/choice-prompt.js +12 -8
  86. package/src/javascripts/ui/components/conversation/event/conversation-suggestions.js +6 -6
  87. package/src/javascripts/ui/components/conversation/event/cta.js +2 -2
  88. package/src/javascripts/ui/components/conversation/event/divider/index.js +0 -1
  89. package/src/javascripts/ui/components/conversation/event/divider/variants/default.js +1 -1
  90. package/src/javascripts/ui/components/conversation/event/divider/variants/new-translation.js +17 -22
  91. package/src/javascripts/ui/components/conversation/event/divider/variants/time-indicator.js +2 -2
  92. package/src/javascripts/ui/components/conversation/event/event-participant.js +1 -1
  93. package/src/javascripts/ui/components/conversation/event/event.tsx +66 -0
  94. package/src/javascripts/ui/components/conversation/event/hooks/use-event-link-click-handler.js +1 -1
  95. package/src/javascripts/ui/components/conversation/event/hooks/use-formatted-date.js +1 -1
  96. package/src/javascripts/ui/components/conversation/event/image-lightbox.js +1 -1
  97. package/src/javascripts/ui/components/conversation/event/image.js +2 -2
  98. package/src/javascripts/ui/components/conversation/event/splash.js +1 -1
  99. package/src/javascripts/ui/components/conversation/event/translation.js +1 -1
  100. package/src/javascripts/ui/components/conversation/event/upload.js +2 -2
  101. package/src/javascripts/ui/components/conversation/event/video.js +2 -2
  102. package/src/javascripts/ui/components/conversation/event-divider.js +1 -1
  103. package/src/javascripts/ui/components/conversation/message-container.js +1 -1
  104. package/src/javascripts/ui/components/conversation/use-chat-scroll.ts +108 -0
  105. package/src/javascripts/ui/components/core/{seamly-activity-monitor.js → seamly-activity-monitor.tsx} +12 -5
  106. package/src/javascripts/ui/components/core/seamly-api-context.ts +7 -0
  107. package/src/javascripts/ui/components/core/seamly-chat.tsx +8 -0
  108. package/src/javascripts/ui/components/core/{seamly-core.js → seamly-core.tsx} +27 -14
  109. package/src/javascripts/ui/components/core/seamly-event-subscriber.ts +340 -0
  110. package/src/javascripts/ui/components/core/seamly-file-upload.js +2 -2
  111. package/src/javascripts/ui/components/core/seamly-idle-detach-counter.js +1 -1
  112. package/src/javascripts/ui/components/core/seamly-instance-functions-loader.js +24 -11
  113. package/src/javascripts/ui/components/core/seamly-live-region.js +4 -4
  114. package/src/javascripts/ui/components/core/seamly-new-notifications.js +3 -3
  115. package/src/javascripts/ui/components/core/seamly-read-state.js +2 -33
  116. package/src/javascripts/ui/components/entry/deprecated-toggle-button.js +4 -4
  117. package/src/javascripts/ui/components/entry/entry-container.js +8 -8
  118. package/src/javascripts/ui/components/entry/text-entry/hooks.js +3 -3
  119. package/src/javascripts/ui/components/entry/text-entry/index.js +3 -3
  120. package/src/javascripts/ui/components/entry/text-entry/text-entry-form.js +4 -4
  121. package/src/javascripts/ui/components/entry/upload/file-upload-form.js +3 -3
  122. package/src/javascripts/ui/components/entry/upload/index.js +5 -5
  123. package/src/javascripts/ui/components/entry/upload-toggle.js +6 -6
  124. package/src/javascripts/ui/components/faq/faq.js +14 -14
  125. package/src/javascripts/ui/components/form-controls/error.js +2 -2
  126. package/src/javascripts/ui/components/form-controls/file-input.js +3 -3
  127. package/src/javascripts/ui/components/layout/agent-info.js +3 -3
  128. package/src/javascripts/ui/components/layout/chat-frame.js +20 -12
  129. package/src/javascripts/ui/components/layout/chat.js +5 -5
  130. package/src/javascripts/ui/components/layout/deprecated-app-frame.js +6 -6
  131. package/src/javascripts/ui/components/layout/deprecated-chat-frame.js +34 -0
  132. package/src/javascripts/ui/components/layout/header.js +2 -2
  133. package/src/javascripts/ui/components/layout/icon.js +11 -9
  134. package/src/javascripts/ui/components/layout/interrupt.js +7 -5
  135. package/src/javascripts/ui/components/layout/pre-chat-messages.js +1 -1
  136. package/src/javascripts/ui/components/layout/privacy-disclaimer.js +2 -2
  137. package/src/javascripts/ui/components/options/options-button.js +5 -5
  138. package/src/javascripts/ui/components/options/{options-frame.js → options-frame.tsx} +52 -18
  139. package/src/javascripts/ui/components/options/transcript/index.js +9 -10
  140. package/src/javascripts/ui/components/options/transcript/transcript-form.js +2 -2
  141. package/src/javascripts/ui/components/suggestions/index.js +8 -8
  142. package/src/javascripts/ui/components/suggestions/suggestions-item.js +1 -1
  143. package/src/javascripts/{domains/translations/components/chat-status.js → ui/components/translation-chat-status/index.tsx} +13 -14
  144. package/src/javascripts/ui/components/translation-proposal/index.tsx +36 -0
  145. package/src/javascripts/ui/components/view/app-view.js +2 -7
  146. package/src/javascripts/ui/components/view/deprecated-view.js +8 -10
  147. package/src/javascripts/ui/components/view/index.js +6 -6
  148. package/src/javascripts/ui/components/view/inline-view.js +4 -8
  149. package/src/javascripts/ui/components/view/window-view/collapse-button.js +2 -2
  150. package/src/javascripts/ui/components/view/window-view/index.js +11 -17
  151. package/src/javascripts/ui/components/view/window-view/window-open-button.js +6 -6
  152. package/src/javascripts/ui/components/warnings/idle-detach-warning.js +3 -3
  153. package/src/javascripts/ui/components/warnings/prompt.js +1 -1
  154. package/src/javascripts/ui/components/warnings/resume-conversation-prompt.js +4 -4
  155. package/src/javascripts/ui/components/widgets/in-out-transition.js +20 -18
  156. package/src/javascripts/ui/components/widgets/lightbox.js +3 -3
  157. package/src/javascripts/ui/components/widgets/modal.js +2 -2
  158. package/src/javascripts/ui/components/widgets/upload-progress.js +2 -2
  159. package/src/javascripts/ui/hooks/file-upload-hooks.js +1 -1
  160. package/src/javascripts/ui/hooks/focus-helper-hooks.js +1 -1
  161. package/src/javascripts/ui/hooks/seamly-entry-hooks.js +6 -6
  162. package/src/javascripts/ui/hooks/seamly-hooks.js +11 -10
  163. package/src/javascripts/ui/hooks/seamly-option-hooks.js +6 -6
  164. package/src/javascripts/ui/hooks/{seamly-state-hooks.js → seamly-state-hooks.ts} +9 -6
  165. package/src/javascripts/ui/hooks/use-click-outside.ts +29 -0
  166. package/src/javascripts/ui/hooks/use-event-component-mapping.js +11 -10
  167. package/src/javascripts/ui/hooks/use-interval.js +1 -1
  168. package/src/javascripts/ui/hooks/use-seamly-actions.ts +29 -29
  169. package/src/javascripts/ui/hooks/use-seamly-chat.js +13 -23
  170. package/src/javascripts/ui/hooks/use-seamly-commands.js +20 -15
  171. package/src/javascripts/ui/hooks/use-seamly-idle-detach-countdown.js +8 -8
  172. package/src/javascripts/ui/hooks/use-seamly-resume-conversation-prompt.js +2 -2
  173. package/src/javascripts/ui/hooks/use-single-file-upload.js +1 -1
  174. package/src/javascripts/ui/hooks/utility-hooks.js +1 -1
  175. package/src/javascripts/ui/utils/general-utils.js +0 -23
  176. package/src/javascripts/ui/utils/seamly-utils.ts +10 -1
  177. package/src/javascripts/ui/utils/seamly-utils.types.ts +9 -0
  178. package/src/stylesheets/1-settings/_config.scss +1 -1
  179. package/src/stylesheets/3-chat/_chat.scss +23 -5
  180. package/src/stylesheets/5-components/_chat-status.scss +72 -16
  181. package/src/stylesheets/5-components/_conversation.scss +35 -1
  182. package/src/stylesheets/5-components/_disclaimer.scss +0 -5
  183. package/src/stylesheets/5-components/_options.scss +16 -2
  184. package/src/stylesheets/5-components/_translation-options.scss +39 -0
  185. package/src/stylesheets/6-default-implementation/_scrollbar.scss +1 -1
  186. package/src/stylesheets/7-deprecated/3-app/_app.scss +19 -4
  187. package/src/stylesheets/7-deprecated/5-components/_chat-status.scss +5 -0
  188. package/src/stylesheets/7-deprecated/5-components/_options.scss +1 -0
  189. package/src/stylesheets/7-deprecated/5-components/_translation-options.scss +39 -0
  190. package/src/stylesheets/deprecated-view.scss +1 -0
  191. package/src/stylesheets/styles.scss +1 -0
  192. package/webpack/config.common.js +4 -4
  193. package/webpack/config.package.js +10 -16
  194. package/webpack/config.site.js +4 -1
  195. package/webpack/config.test.js +2 -1
  196. package/build/dist/lib/deprecated-view.css +0 -1
  197. package/build/dist/lib/styles-default-implementation.css +0 -1
  198. package/build/dist/lib/styles.css +0 -1
  199. package/src/.DS_Store +0 -0
  200. package/src/javascripts/api/event-producer.js +0 -20
  201. package/src/javascripts/api/producer.js +0 -136
  202. package/src/javascripts/domains/errors/index.js +0 -37
  203. package/src/javascripts/domains/translations/components/options-dialog/form.js +0 -70
  204. package/src/javascripts/domains/translations/components/options-dialog/index.js +0 -87
  205. package/src/javascripts/ui/components/chat-status/index.js +0 -38
  206. package/src/javascripts/ui/components/conversation/event/event.js +0 -36
  207. package/src/javascripts/ui/components/core/seamly-api-context.js +0 -5
  208. package/src/javascripts/ui/components/core/seamly-event-subscriber.js +0 -279
@@ -1,15 +1,13 @@
1
- import { useI18n } from 'domains/i18n/hooks'
2
- import { useVisibility } from 'domains/visibility/hooks'
3
- import { className } from 'lib/css'
4
- import { useLayoutEffect, useRef } from 'preact/hooks'
5
1
  import PrivacyDisclaimer from 'ui/components/layout/privacy-disclaimer'
6
- import { timeout } from 'ui/hooks/focus-helper-hooks'
7
2
  import {
8
3
  useSeamlyIsLoading,
9
4
  useSkiplink,
10
5
  useSkiplinkTargetFocusing,
11
6
  } from 'ui/hooks/seamly-hooks'
12
- import { useEvents, useLoadedImageEventIds } from 'ui/hooks/seamly-state-hooks'
7
+ import { useEvents } from 'ui/hooks/seamly-state-hooks'
8
+ import { useI18n } from 'domains/i18n/hooks'
9
+ import { useVisibility } from 'domains/visibility/hooks'
10
+ import { className } from 'lib/css'
13
11
  import ComponentFilter from './component-filter'
14
12
  import Event from './event/event'
15
13
  import Loader from './loader'
@@ -18,49 +16,41 @@ const Events = () => {
18
16
  const events = useEvents()
19
17
 
20
18
  let prevParticipant = null
21
- return events.map((event) => {
22
- const { type, payload } = event
23
- const { participant, fromClient } = payload
24
- let participantChanged = false
25
- if (type !== 'participant') {
26
- const currentParticipant = fromClient
27
- ? 'seamly-client-participant'
28
- : participant
29
- if (event.type !== 'info' && prevParticipant !== currentParticipant) {
30
- participantChanged = true
31
- }
32
- prevParticipant = currentParticipant
33
- }
19
+ return (
20
+ <>
21
+ {events.map((event) => {
22
+ const { type, payload } = event
23
+
24
+ let participantChanged = false
25
+ if (type !== 'participant' && type !== 'service_data') {
26
+ const { participant, fromClient } = payload
27
+ const currentParticipant = fromClient
28
+ ? 'seamly-client-participant'
29
+ : participant
30
+ if (event.type !== 'info' && prevParticipant !== currentParticipant) {
31
+ participantChanged = true
32
+ }
33
+ prevParticipant = currentParticipant
34
+ }
34
35
 
35
- return (
36
- <Event
37
- key={event.payload.key || event.payload.id}
38
- event={event}
39
- newParticipant={participantChanged}
40
- />
41
- )
42
- })
36
+ return (
37
+ <Event
38
+ key={event.payload.key || event.payload.id}
39
+ event={event}
40
+ newParticipant={participantChanged}
41
+ />
42
+ )
43
+ })}
44
+ </>
45
+ )
43
46
  }
44
47
 
45
48
  const Conversation = () => {
46
49
  const { t } = useI18n()
47
- const chatBodyContainer = useRef(null)
48
- const events = useEvents()
49
50
  const isLoading = useSeamlyIsLoading()
50
51
  const { isOpen } = useVisibility()
51
52
  const skiplinkTargetId = useSkiplink()
52
53
  const focusSkiplinkTarget = useSkiplinkTargetFocusing()
53
- const loadedImageEventIds = useLoadedImageEventIds()
54
-
55
- useLayoutEffect(() => {
56
- const containerElement = chatBodyContainer.current
57
- if (containerElement) {
58
- requestAnimationFrame(async () => {
59
- await timeout(30) // Wait for next frame tick
60
- containerElement.scrollTop = containerElement.scrollHeight
61
- })
62
- }
63
- }, [events, isLoading, isOpen, loadedImageEventIds])
64
54
 
65
55
  const onClickHandler = (e) => {
66
56
  e.preventDefault()
@@ -78,7 +68,8 @@ const Conversation = () => {
78
68
  {t('skiplinkText')}
79
69
  </a>
80
70
  )}
81
- <div className={className('chat__body')} ref={chatBodyContainer}>
71
+
72
+ <div className={className('chat__body')}>
82
73
  <div className={className('conversation__container')}>
83
74
  <PrivacyDisclaimer />
84
75
  <ol className={className('conversation')}>
@@ -1,7 +1,7 @@
1
1
  import { useCallback, useEffect, useMemo, useRef } from 'preact/hooks'
2
- import { className } from 'lib/css'
3
2
  import { useGeneratedId, useSeamlyCommands } from 'ui/hooks/seamly-hooks'
4
- import { cardTypes, actionTypes } from 'ui/utils/seamly-utils'
3
+ import { actionTypes, cardTypes } from 'ui/utils/seamly-utils'
4
+ import { className } from 'lib/css'
5
5
 
6
6
  const CardComponent = ({
7
7
  id,
@@ -1,5 +1,5 @@
1
- import { useGeneratedId } from 'ui/hooks/seamly-hooks'
2
1
  import MessageContainer from 'ui/components/conversation/message-container'
2
+ import { useGeneratedId } from 'ui/hooks/seamly-hooks'
3
3
  import { useTranslatedEventData } from 'domains/translations/hooks'
4
4
  import CardComponent from './card-component'
5
5
 
@@ -1,6 +1,6 @@
1
- import { className } from 'lib/css'
2
- import { useI18n } from 'domains/i18n/hooks'
3
1
  import Icon from 'ui/components/layout/icon'
2
+ import { useI18n } from 'domains/i18n/hooks'
3
+ import { className } from 'lib/css'
4
4
 
5
5
  export default function CarouselControls({
6
6
  items,
@@ -1,10 +1,10 @@
1
1
  import { createRef } from 'preact'
2
- import { useState, useEffect, useRef, useMemo } from 'preact/hooks'
3
- import { className } from 'lib/css'
4
- import { useGeneratedId } from 'ui/hooks/utility-hooks'
2
+ import { useEffect, useMemo, useRef, useState } from 'preact/hooks'
5
3
  import CarouselMessageSlide from 'ui/components/conversation/event/carousel-message/components/slide'
6
- import CarouselPagination from './components/pagination'
4
+ import { useGeneratedId } from 'ui/hooks/utility-hooks'
5
+ import { className } from 'lib/css'
7
6
  import CarouselControls from './components/controls'
7
+ import CarouselPagination from './components/pagination'
8
8
 
9
9
  const defaultGetItemKey = (item, idx, prefix) => `${prefix}${idx}`
10
10
  const defaultGetItemLabel = (item) => item.label
@@ -1,6 +1,6 @@
1
- import { className } from 'lib/css'
2
- import { useI18n } from 'domains/i18n/hooks'
3
1
  import CardComponent from 'ui/components/conversation/event/card-component'
2
+ import { useI18n } from 'domains/i18n/hooks'
3
+ import { className } from 'lib/css'
4
4
 
5
5
  export default function CarouselMessageSlide({
6
6
  item: slide,
@@ -1,5 +1,5 @@
1
- import MessageContainer from 'ui/components/conversation/message-container'
2
1
  import CarouselComponent from 'ui/components/conversation/event/carousel-component'
2
+ import MessageContainer from 'ui/components/conversation/message-container'
3
3
  import { useTranslatedEventData } from 'domains/translations/hooks'
4
4
  import CarouselMessageSlide from './components/slide'
5
5
 
@@ -0,0 +1,12 @@
1
+ import { RefObject, createContext } from 'preact'
2
+ import { MutableRef } from 'preact/hooks'
3
+
4
+ const ChatScrollContext = createContext<{
5
+ eventRefs: Record<string, RefObject<HTMLElement>>
6
+ unreadIds: string[]
7
+ scrollToRef: () => void
8
+ scrollToBottom: () => void
9
+ containerRef: MutableRef<HTMLDivElement>
10
+ }>(null)
11
+
12
+ export default ChatScrollContext
@@ -0,0 +1,46 @@
1
+ import { RefObject, createRef } from 'preact'
2
+ import { useMemo } from 'preact/hooks'
3
+ import ChatScrollContext from 'ui/components/conversation/event/chat-scroll/chat-scroll-context'
4
+ import UnreadMessagesButton from 'ui/components/conversation/event/chat-scroll/unread-messages-button'
5
+ import useChatScroll from 'ui/components/conversation/use-chat-scroll'
6
+ import { useEvents } from 'ui/hooks/seamly-hooks'
7
+ import { className } from 'lib/css'
8
+
9
+ const ChatScrollProvider = ({ children }) => {
10
+ const events = useEvents()
11
+ const eventRefs = useMemo(
12
+ () =>
13
+ events.reduce<Record<string, RefObject<HTMLElement>>>((acc, value) => {
14
+ acc[value.payload.id] = createRef()
15
+ return acc
16
+ }, {}),
17
+ [events],
18
+ )
19
+
20
+ const { scrollToRef, scrollToBottom, containerRef, unreadIds } =
21
+ useChatScroll(eventRefs)
22
+
23
+ return (
24
+ <ChatScrollContext.Provider
25
+ value={{
26
+ eventRefs,
27
+ unreadIds,
28
+ scrollToRef,
29
+ scrollToBottom,
30
+ containerRef,
31
+ }}
32
+ >
33
+ <div className={className('chat__container')}>
34
+ <div
35
+ className={className('chat__container__scroll-area')}
36
+ ref={containerRef}
37
+ >
38
+ {children}
39
+ </div>
40
+ <UnreadMessagesButton />
41
+ </div>
42
+ </ChatScrollContext.Provider>
43
+ )
44
+ }
45
+
46
+ export default ChatScrollProvider
@@ -0,0 +1,27 @@
1
+ import { useContext } from 'preact/hooks'
2
+ import ChatScrollContext from 'ui/components/conversation/event/chat-scroll/chat-scroll-context'
3
+ import Icon from 'ui/components/layout/icon'
4
+ import InOutTransition from 'ui/components/widgets/in-out-transition'
5
+ import { useI18n } from 'domains/i18n/hooks'
6
+ import { className } from 'lib/css'
7
+
8
+ const UnreadMessagesButton = () => {
9
+ const { scrollToRef, unreadIds } = useContext(ChatScrollContext)
10
+ const { t } = useI18n()
11
+ return (
12
+ <InOutTransition isActive={!!unreadIds.length}>
13
+ <div className={className('unread-messages')}>
14
+ <button
15
+ type="button"
16
+ className={className('button', 'button--primary')}
17
+ onClick={scrollToRef}
18
+ >
19
+ {t('message.unreadMessagesCount', { unreadCount: unreadIds.length })}
20
+ <Icon name="chevronDown" size="32" alt="" />
21
+ </button>
22
+ </div>
23
+ </InOutTransition>
24
+ )
25
+ }
26
+
27
+ export default UnreadMessagesButton
@@ -1,17 +1,18 @@
1
1
  import { toChildArray } from 'preact'
2
- import { useEffect, useState, useMemo } from 'preact/hooks'
2
+ import { useEffect, useMemo, useState } from 'preact/hooks'
3
+ import MessageContainer from 'ui/components/conversation/message-container'
4
+ import Icon from 'ui/components/layout/icon'
3
5
  import {
6
+ useGeneratedId,
7
+ useLastMessageEventId,
4
8
  useSeamlyCommands,
5
9
  useSeamlyServiceInfo,
6
- useLastMessageEventId,
7
- useGeneratedId,
8
10
  } from 'ui/hooks/seamly-hooks'
9
- import { className } from 'lib/css'
10
11
  import { actionTypes } from 'ui/utils/seamly-utils'
11
- import Icon from 'ui/components/layout/icon'
12
12
  import { useI18n } from 'domains/i18n/hooks'
13
13
  import { useTranslatedEventData } from 'domains/translations/hooks'
14
- import MessageContainer from 'ui/components/conversation/message-container'
14
+ import { className } from 'lib/css'
15
+ import { randomId } from 'lib/id'
15
16
 
16
17
  export const useChoicePrompt = (event) => {
17
18
  const { payload } = event
@@ -46,10 +47,12 @@ export const useChoicePrompt = (event) => {
46
47
  }, [payload, lastEventId])
47
48
 
48
49
  const onChoiceClickHandler = (choice) => {
50
+ const transactionId = randomId()
51
+
49
52
  if (chooseAgain) {
50
- addDivider('new_topic')
53
+ addDivider('new_topic', transactionId)
51
54
  }
52
- addMessageBubble(choice.text)
55
+ addMessageBubble(choice.text, transactionId)
53
56
  sendAction({
54
57
  type: actionTypes.pickChoice,
55
58
  originMessage: payload.id,
@@ -58,6 +61,7 @@ export const useChoicePrompt = (event) => {
58
61
  text: choice.text,
59
62
  chooseAgain,
60
63
  },
64
+ transactionId,
61
65
  })
62
66
  setShowOptions(false)
63
67
  }
@@ -1,15 +1,15 @@
1
- import { useUserHasResponded } from 'domains/app/hooks'
2
- import { setHasResponded } from 'domains/app/slice'
3
- import { useConfig } from 'domains/config/hooks'
4
- import { useI18n } from 'domains/i18n/hooks'
5
- import { useTranslatedEventData } from 'domains/translations/hooks'
6
- import { className } from 'lib/css'
7
1
  import { useCallback, useMemo, useState } from 'preact/hooks'
8
2
  import { useDispatch } from 'react-redux'
9
3
  import MessageContainer from 'ui/components/conversation/message-container'
10
4
  import SuggestionsList from 'ui/components/suggestions/suggestions-list'
11
5
  import { useEvents, useSeamlyCommands } from 'ui/hooks/seamly-hooks'
12
6
  import { actionTypes } from 'ui/utils/seamly-utils'
7
+ import { useUserHasResponded } from 'domains/app/hooks'
8
+ import { setHasResponded } from 'domains/app/slice'
9
+ import { useConfig } from 'domains/config/hooks'
10
+ import { useI18n } from 'domains/i18n/hooks'
11
+ import { useTranslatedEventData } from 'domains/translations/hooks'
12
+ import { className } from 'lib/css'
13
13
 
14
14
  export const useSuggestions = (event) => {
15
15
  const { payload } = event
@@ -1,9 +1,9 @@
1
1
  import { useCallback } from 'preact/hooks'
2
- import { className } from 'lib/css'
2
+ import MessageContainer from 'ui/components/conversation/message-container'
3
3
  import { useGeneratedId, useSeamlyCommands } from 'ui/hooks/seamly-hooks'
4
4
  import { actionTypes } from 'ui/utils/seamly-utils'
5
- import MessageContainer from 'ui/components/conversation/message-container'
6
5
  import { useTranslatedEventData } from 'domains/translations/hooks'
6
+ import { className } from 'lib/css'
7
7
  import useEventLinkClickHandler from './hooks/use-event-link-click-handler'
8
8
 
9
9
  const Cta = ({ event }) => {
@@ -1,5 +1,4 @@
1
1
  import { dividerKeys } from 'ui/utils/seamly-utils'
2
-
3
2
  import DefaultDivider from './variants/default'
4
3
  import NewTranslationDivider from './variants/new-translation'
5
4
 
@@ -1,6 +1,6 @@
1
+ import EventDivider from 'ui/components/conversation/event-divider'
1
2
  import { dividerKeys } from 'ui/utils/seamly-utils'
2
3
  import { useI18n } from 'domains/i18n/hooks'
3
- import EventDivider from 'ui/components/conversation/event-divider'
4
4
 
5
5
  const dividerTypes = {
6
6
  [dividerKeys.new_topic]: 'newtopic',
@@ -1,27 +1,29 @@
1
- import { useI18n } from 'domains/i18n/hooks'
2
- import EventDivider from 'ui/components/conversation/event-divider'
3
- import { className } from 'lib/css'
4
- import {
5
- useTranslations,
6
- useLocaleNativeName,
7
- } from 'domains/translations/hooks'
1
+ import { useCallback } from 'preact/hooks'
8
2
  import { useDispatch, useSelector } from 'react-redux'
3
+ import EventDivider from 'ui/components/conversation/event-divider'
4
+ import { useEvents } from 'ui/hooks/seamly-state-hooks'
5
+ import { useI18n } from 'domains/i18n/hooks'
6
+ import { useTranslations } from 'domains/translations/hooks'
9
7
  import {
10
- enableEventsTranslation,
11
8
  disableEventsTranslation,
9
+ enableEventsTranslation,
12
10
  } from 'domains/translations/slice'
13
- import { useCallback } from 'preact/hooks'
14
- import { useEvents } from 'ui/hooks/seamly-hooks'
11
+ import { className } from 'lib/css'
15
12
 
16
13
  const NewTranslationDivider = ({ event }) => {
17
14
  const { t } = useI18n()
18
15
  const events = useEvents()
19
16
  const {
20
- body: { translationEnabled, translationLocale, text },
17
+ body: {
18
+ restartButtonText,
19
+ translationEnabled,
20
+ translationLocale,
21
+ text,
22
+ title,
23
+ },
21
24
  id,
22
25
  } = event.payload
23
26
  const { enableTranslations } = useTranslations()
24
- const localeNativeName = useLocaleNativeName(translationLocale)
25
27
  const translatedEventGroups = useSelector(
26
28
  (state) => state.translations.translatedEventGroups,
27
29
  )
@@ -41,17 +43,10 @@ const NewTranslationDivider = ({ event }) => {
41
43
  }
42
44
  return (
43
45
  <EventDivider iconName="newTranslation" dividerType="newtranslation">
44
- <p className={className('divider__title')}>
45
- {t(
46
- translationEnabled
47
- ? 'translations.divider.startText'
48
- : 'translations.divider.stopText',
49
- { language: localeNativeName },
50
- )}
51
- </p>
46
+ <p className={className('divider__title')}>{title}</p>
47
+ {text && <p>{text}</p>}
52
48
  {translationEnabled ? (
53
49
  <>
54
- <p>{text}</p>
55
50
  <button
56
51
  className={className('button', 'button--secondary')}
57
52
  onClick={toggleTranslations}
@@ -69,7 +64,7 @@ const NewTranslationDivider = ({ event }) => {
69
64
  data-testid="restartTranslationButton"
70
65
  onClick={handleRestartButtonclick}
71
66
  >
72
- {t('translations.divider.restartButtonText')}
67
+ {restartButtonText}
73
68
  </button>
74
69
  )}
75
70
  </EventDivider>
@@ -1,9 +1,9 @@
1
1
  import { useEffect } from 'preact/hooks'
2
2
  import EventDivider from 'ui/components/conversation/event-divider'
3
- import { className } from 'lib/css'
4
- import { microsecondsToMilliseconds } from 'ui/utils/general-utils'
5
3
  import { useFormattedDate } from 'ui/components/conversation/event/hooks/use-formatted-date'
6
4
  import { useForceUpdate } from 'ui/hooks/utility-hooks'
5
+ import { microsecondsToMilliseconds } from 'ui/utils/general-utils'
6
+ import { className } from 'lib/css'
7
7
 
8
8
  const TimeIndicator = ({ event }) => {
9
9
  const forceUpdate = useForceUpdate()
@@ -1,7 +1,7 @@
1
1
  import { useSeamlyParticipant } from 'ui/hooks/seamly-hooks'
2
- import { className } from 'lib/css'
3
2
  import { useConfig } from 'domains/config/hooks'
4
3
  import { useI18n } from 'domains/i18n/hooks'
4
+ import { className } from 'lib/css'
5
5
 
6
6
  const EventParticipant = ({ eventPayload }) => {
7
7
  const { t } = useI18n()
@@ -0,0 +1,66 @@
1
+ import { FunctionComponent, useContext, useEffect } from 'preact/compat'
2
+ import { useDispatch } from 'react-redux'
3
+ import ChatScrollContext from 'ui/components/conversation/event/chat-scroll/chat-scroll-context'
4
+ import TimeIndicator from 'ui/components/conversation/event/divider/variants/time-indicator'
5
+ import { useSeamlyCommands } from 'ui/hooks/seamly-hooks'
6
+ import useEventComponentMapping from 'ui/hooks/use-event-component-mapping'
7
+ import { actionTypes } from 'ui/utils/seamly-utils'
8
+ import { setEventsRead } from 'domains/store/slice'
9
+ import { useIntersect } from 'domains/visibility/hooks'
10
+ import { className } from 'lib/css'
11
+
12
+ const Event: FunctionComponent<{
13
+ event
14
+ newParticipant: boolean
15
+ }> = ({ event, newParticipant }) => {
16
+ const { eventRefs, unreadIds } = useContext(ChatScrollContext)
17
+ const { sendAction } = useSeamlyCommands()
18
+ const ref = eventRefs[event.payload.id]
19
+ const { containerRef, isVisible } = useIntersect({
20
+ containerRef: ref,
21
+ freezeOnceVisible: true,
22
+ })
23
+ const dispatch = useDispatch()
24
+ const [Component, SubComponent] = useEventComponentMapping(event)
25
+
26
+ useEffect(() => {
27
+ if (isVisible && unreadIds?.includes(event.payload.id)) {
28
+ const readIds = unreadIds.reduce<string[]>(
29
+ (arr, id) => (!arr.includes(event.payload.id) ? arr.concat(id) : arr),
30
+ [],
31
+ )
32
+
33
+ dispatch(setEventsRead(readIds))
34
+ sendAction({ type: actionTypes.read, events: readIds })
35
+ }
36
+ }, [dispatch, event.payload.id, isVisible, sendAction, unreadIds])
37
+
38
+ if (!Component) {
39
+ return null
40
+ }
41
+
42
+ const classNames = ['conversation__item']
43
+
44
+ if (event.type === 'info') {
45
+ classNames.push('conversation__item--source-info')
46
+ } else if (event.payload.fromClient) {
47
+ classNames.push('conversation__item--source-user')
48
+ } else {
49
+ classNames.push('conversation__item--source-agent')
50
+ }
51
+
52
+ if (newParticipant) {
53
+ classNames.push('conversation__item--new-participant')
54
+ }
55
+
56
+ return (
57
+ <li className={className(classNames)} ref={containerRef}>
58
+ {event.timeIndicator && <TimeIndicator event={event} />}
59
+ <Component event={event}>
60
+ <SubComponent event={event} />
61
+ </Component>
62
+ </li>
63
+ )
64
+ }
65
+
66
+ export default Event
@@ -1,5 +1,5 @@
1
- import { actionTypes } from 'ui/utils/seamly-utils'
2
1
  import { useSeamlyCommands } from 'ui/hooks/seamly-hooks'
2
+ import { actionTypes } from 'ui/utils/seamly-utils'
3
3
 
4
4
  const useEventLinkClickHandler = (eventId) => {
5
5
  const { sendAction } = useSeamlyCommands()
@@ -1,6 +1,6 @@
1
- import { useI18n } from 'domains/i18n/hooks'
2
1
  import { getRelativeDate } from 'ui/utils/general-utils'
3
2
  import { useConfig } from 'domains/config/hooks'
3
+ import { useI18n } from 'domains/i18n/hooks'
4
4
 
5
5
  const dateFormatOptions = { month: 'long', day: 'numeric', year: 'numeric' }
6
6
  const timeFormatOptions = { hour: 'numeric', minute: 'numeric' }
@@ -1,8 +1,8 @@
1
1
  import { useState } from 'preact/hooks'
2
+ import Icon from 'ui/components/layout/icon'
2
3
  import Lightbox from 'ui/components/widgets/lightbox'
3
4
  import { useI18n } from 'domains/i18n/hooks'
4
5
  import { className } from 'lib/css'
5
- import Icon from 'ui/components/layout/icon'
6
6
 
7
7
  const ImageLightbox = ({ description, url }) => {
8
8
  const { t } = useI18n()
@@ -1,8 +1,8 @@
1
- import { setLoadedImageEventIds } from 'domains/store/slice'
2
- import { useTranslatedEventData } from 'domains/translations/hooks'
3
1
  import { useState } from 'preact/hooks'
4
2
  import { useDispatch } from 'react-redux'
5
3
  import MessageContainer from 'ui/components/conversation/message-container'
4
+ import { setLoadedImageEventIds } from 'domains/store/slice'
5
+ import { useTranslatedEventData } from 'domains/translations/hooks'
6
6
  import ImageLightbox from './image-lightbox'
7
7
 
8
8
  const Image = ({ event, descriptorId, ...props }) => {
@@ -1,6 +1,6 @@
1
- import { useTranslatedEventData } from 'domains/translations/hooks'
2
1
  import useEventLinkClickHandler from 'ui/components/conversation/event/hooks/use-event-link-click-handler'
3
2
  import MessageContainer from 'ui/components/conversation/message-container'
3
+ import { useTranslatedEventData } from 'domains/translations/hooks'
4
4
 
5
5
  const Splash = ({ event, ...props }) => {
6
6
  const { payload } = event
@@ -1,5 +1,5 @@
1
- import { useI18n } from 'domains/i18n/hooks'
2
1
  import MessageContainer from 'ui/components/conversation/message-container'
2
+ import { useI18n } from 'domains/i18n/hooks'
3
3
  import { useTranslatedEventData } from 'domains/translations/hooks'
4
4
 
5
5
  const Translation = ({ event, ...props }) => {
@@ -1,9 +1,9 @@
1
1
  import { useMemo } from 'preact/hooks'
2
- import { className } from 'lib/css'
2
+ import MessageContainer from 'ui/components/conversation/message-container'
3
3
  import Icon from 'ui/components/layout/icon'
4
4
  import { useI18n } from 'domains/i18n/hooks'
5
- import MessageContainer from 'ui/components/conversation/message-container'
6
5
  import { useTranslatedEventData } from 'domains/translations/hooks'
6
+ import { className } from 'lib/css'
7
7
 
8
8
  const UploadContent = ({ children, url, target }) =>
9
9
  url ? (
@@ -1,7 +1,7 @@
1
- import { className } from 'lib/css'
1
+ import MessageContainer from 'ui/components/conversation/message-container'
2
2
  import { useSeamlyMessageContainerClassNames } from 'ui/hooks/seamly-hooks'
3
3
  import { useTranslatedEventData } from 'domains/translations/hooks'
4
- import MessageContainer from 'ui/components/conversation/message-container'
4
+ import { className } from 'lib/css'
5
5
 
6
6
  const Video = ({ event, descriptorId, ...props }) => {
7
7
  const { body } = useTranslatedEventData(event)
@@ -1,5 +1,5 @@
1
- import { className } from 'lib/css'
2
1
  import Icon from 'ui/components/layout/icon'
2
+ import { className } from 'lib/css'
3
3
 
4
4
  export default function EventDivider({
5
5
  children,
@@ -1,6 +1,6 @@
1
- import { className } from 'lib/css'
2
1
  import { useSeamlyMessageContainerClassNames } from 'ui/hooks/component-helper-hooks'
3
2
  import { useTranslatedEventData } from 'domains/translations/hooks'
3
+ import { className } from 'lib/css'
4
4
  import EventParticipant from './event/event-participant'
5
5
 
6
6
  function MessageContainer({