@seamly/web-ui 20.8.1 → 21.0.1-beta.1

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 (225) hide show
  1. package/build/dist/lib/components.js +14855 -20
  2. package/build/dist/lib/components.min.js +2 -1
  3. package/build/dist/lib/components.min.js.LICENSE.txt +48 -0
  4. package/build/dist/lib/config.js +9 -3
  5. package/build/dist/lib/config.min.js +1 -1
  6. package/build/dist/lib/contexts.js +14 -5
  7. package/build/dist/lib/contexts.min.js +1 -1
  8. package/build/dist/lib/deprecated-view.js +1 -1
  9. package/build/dist/lib/hooks.js +8446 -20
  10. package/build/dist/lib/hooks.min.js +2 -1
  11. package/build/dist/lib/hooks.min.js.LICENSE.txt +38 -0
  12. package/build/dist/lib/index.debug.js +585 -584
  13. package/build/dist/lib/index.debug.min.js +1 -1
  14. package/build/dist/lib/index.debug.min.js.LICENSE.txt +110 -110
  15. package/build/dist/lib/index.js +20279 -26454
  16. package/build/dist/lib/index.min.js +1 -1
  17. package/build/dist/lib/index.min.js.LICENSE.txt +6 -1
  18. package/build/dist/lib/standalone.js +27823 -34681
  19. package/build/dist/lib/standalone.min.js +1 -1
  20. package/build/dist/lib/standalone.min.js.LICENSE.txt +1 -1
  21. package/build/dist/lib/storage.js +6 -15
  22. package/build/dist/lib/style-guide.js +23181 -7921
  23. package/build/dist/lib/style-guide.min.js +1 -1
  24. package/build/dist/lib/style-guide.min.js.LICENSE.txt +10 -0
  25. package/build/dist/lib/styles-default-implementation.js +1 -1
  26. package/build/dist/lib/styles.js +1 -1
  27. package/build/dist/lib/utils.js +22149 -17
  28. package/build/dist/lib/utils.min.js +2 -1
  29. package/build/dist/lib/utils.min.js.LICENSE.txt +48 -0
  30. package/package.json +54 -52
  31. package/src/icons/icon_check-16.svg +14 -0
  32. package/src/icons/icon_check-32.svg +14 -0
  33. package/src/javascripts/api/conversation-connector.ts +149 -0
  34. package/src/javascripts/api/errors/seamly-base-error.js +19 -0
  35. package/src/javascripts/api/errors/seamly-unavailable-error.js +5 -7
  36. package/src/javascripts/api/{index.js → index.ts} +163 -116
  37. package/src/javascripts/config.types.ts +5 -4
  38. package/src/javascripts/domains/app/actions.ts +47 -46
  39. package/src/javascripts/domains/app/hooks.js +1 -1
  40. package/src/javascripts/domains/config/actions.ts +2 -8
  41. package/src/javascripts/domains/config/hooks.ts +1 -1
  42. package/src/javascripts/domains/config/selectors.ts +6 -6
  43. package/src/javascripts/domains/config/slice.ts +3 -3
  44. package/src/javascripts/domains/errors/index.ts +66 -0
  45. package/src/javascripts/domains/forms/context.ts +1 -1
  46. package/src/javascripts/domains/forms/forms.types.ts +3 -3
  47. package/src/javascripts/domains/forms/hooks.ts +10 -10
  48. package/src/javascripts/domains/forms/provider.tsx +9 -9
  49. package/src/javascripts/domains/i18n/actions.ts +11 -5
  50. package/src/javascripts/domains/i18n/hooks.ts +11 -8
  51. package/src/javascripts/domains/i18n/selectors.ts +10 -4
  52. package/src/javascripts/domains/i18n/slice.ts +0 -1
  53. package/src/javascripts/domains/interrupt/hooks.ts +1 -1
  54. package/src/javascripts/domains/interrupt/middleware.ts +1 -1
  55. package/src/javascripts/domains/store/index.ts +1 -1
  56. package/src/javascripts/domains/store/selectors.ts +16 -0
  57. package/src/javascripts/domains/store/slice.ts +47 -41
  58. package/src/javascripts/domains/store/store.types.ts +38 -10
  59. package/src/javascripts/domains/translations/components/{options-button.js → options-button.tsx} +30 -20
  60. package/src/javascripts/domains/translations/components/options-dialog/index.tsx +33 -0
  61. package/src/javascripts/domains/translations/components/options-dialog/translation-option.tsx +42 -0
  62. package/src/javascripts/domains/translations/components/options-dialog/translation-options.tsx +81 -0
  63. package/src/javascripts/domains/translations/components/translation-status.tsx +15 -0
  64. package/src/javascripts/domains/translations/hooks.ts +77 -11
  65. package/src/javascripts/domains/translations/slice.ts +20 -9
  66. package/src/javascripts/domains/translations/translations.types.ts +4 -2
  67. package/src/javascripts/domains/visibility/actions.ts +6 -10
  68. package/src/javascripts/domains/visibility/hooks.ts +33 -14
  69. package/src/javascripts/domains/visibility/selectors.ts +3 -2
  70. package/src/javascripts/domains/visibility/slice.ts +2 -6
  71. package/src/javascripts/index.ts +18 -22
  72. package/src/javascripts/lib/engine/{index.js → index.tsx} +25 -7
  73. package/src/javascripts/lib/url-helpers.ts +112 -0
  74. package/src/javascripts/package/components.js +15 -1
  75. package/src/javascripts/package/config.js +1 -1
  76. package/src/javascripts/package/contexts.js +5 -3
  77. package/src/javascripts/package/hooks.js +19 -1
  78. package/src/javascripts/package/utils.js +13 -3
  79. package/src/javascripts/schema.ts +28 -0
  80. package/src/javascripts/style-guide/components/app.js +16 -12
  81. package/src/javascripts/style-guide/components/links.js +6 -6
  82. package/src/javascripts/style-guide/components/static-core.js +14 -9
  83. package/src/javascripts/style-guide/components/view.js +2 -2
  84. package/src/javascripts/style-guide/states.js +132 -36
  85. package/src/javascripts/style-guide/style-guide-engine.js +2 -1
  86. package/src/javascripts/style-guide/style-guide-external-api.js +1 -1
  87. package/src/javascripts/ui/components/app-options/index.js +25 -6
  88. package/src/javascripts/ui/components/chat-app.js +1 -1
  89. package/src/javascripts/ui/components/chat-status/chat-status-action.tsx +30 -0
  90. package/src/javascripts/ui/components/chat-status/index.tsx +61 -0
  91. package/src/javascripts/ui/components/conversation/component-filter.js +9 -9
  92. package/src/javascripts/ui/components/conversation/{conversation.js → conversation.tsx} +32 -41
  93. package/src/javascripts/ui/components/conversation/event/card-component.js +2 -2
  94. package/src/javascripts/ui/components/conversation/event/card-message.js +1 -1
  95. package/src/javascripts/ui/components/conversation/event/carousel-component/components/controls.js +2 -2
  96. package/src/javascripts/ui/components/conversation/event/carousel-component/index.js +4 -4
  97. package/src/javascripts/ui/components/conversation/event/carousel-message/components/slide.js +2 -2
  98. package/src/javascripts/ui/components/conversation/event/carousel-message/index.js +1 -1
  99. package/src/javascripts/ui/components/conversation/event/chat-scroll/chat-scroll-context.ts +12 -0
  100. package/src/javascripts/ui/components/conversation/event/chat-scroll/chat-scroll-provider.tsx +46 -0
  101. package/src/javascripts/ui/components/conversation/event/chat-scroll/unread-messages-button.tsx +30 -0
  102. package/src/javascripts/ui/components/conversation/event/choice-prompt.js +12 -8
  103. package/src/javascripts/ui/components/conversation/event/conversation-suggestions.js +6 -6
  104. package/src/javascripts/ui/components/conversation/event/cta.js +2 -2
  105. package/src/javascripts/ui/components/conversation/event/divider/index.js +0 -1
  106. package/src/javascripts/ui/components/conversation/event/divider/variants/default.js +1 -1
  107. package/src/javascripts/ui/components/conversation/event/divider/variants/new-translation.js +17 -22
  108. package/src/javascripts/ui/components/conversation/event/divider/variants/time-indicator.js +2 -2
  109. package/src/javascripts/ui/components/conversation/event/event-participant.js +1 -1
  110. package/src/javascripts/ui/components/conversation/event/event.tsx +66 -0
  111. package/src/javascripts/ui/components/conversation/event/hooks/use-event-link-click-handler.js +1 -1
  112. package/src/javascripts/ui/components/conversation/event/hooks/use-formatted-date.js +1 -1
  113. package/src/javascripts/ui/components/conversation/event/image-lightbox.js +1 -1
  114. package/src/javascripts/ui/components/conversation/event/image.js +2 -2
  115. package/src/javascripts/ui/components/conversation/event/splash.js +1 -1
  116. package/src/javascripts/ui/components/conversation/event/translation.js +1 -1
  117. package/src/javascripts/ui/components/conversation/event/upload.js +2 -2
  118. package/src/javascripts/ui/components/conversation/event/video.js +2 -2
  119. package/src/javascripts/ui/components/conversation/event-divider.js +1 -1
  120. package/src/javascripts/ui/components/conversation/message-container.js +1 -1
  121. package/src/javascripts/ui/components/conversation/use-chat-scroll.ts +108 -0
  122. package/src/javascripts/ui/components/core/{seamly-activity-monitor.js → seamly-activity-monitor.tsx} +12 -5
  123. package/src/javascripts/ui/components/core/seamly-api-context.ts +7 -0
  124. package/src/javascripts/ui/components/core/seamly-chat.tsx +8 -0
  125. package/src/javascripts/ui/components/core/{seamly-core.js → seamly-core.tsx} +27 -14
  126. package/src/javascripts/ui/components/core/seamly-event-subscriber.ts +340 -0
  127. package/src/javascripts/ui/components/core/seamly-file-upload.js +2 -2
  128. package/src/javascripts/ui/components/core/seamly-idle-detach-counter.js +1 -1
  129. package/src/javascripts/ui/components/core/seamly-instance-functions-loader.js +24 -11
  130. package/src/javascripts/ui/components/core/seamly-live-region.js +4 -4
  131. package/src/javascripts/ui/components/core/seamly-new-notifications.js +3 -3
  132. package/src/javascripts/ui/components/core/seamly-read-state.js +2 -33
  133. package/src/javascripts/ui/components/entry/deprecated-toggle-button.js +4 -4
  134. package/src/javascripts/ui/components/entry/entry-container.js +8 -8
  135. package/src/javascripts/ui/components/entry/text-entry/hooks.js +3 -3
  136. package/src/javascripts/ui/components/entry/text-entry/index.js +3 -3
  137. package/src/javascripts/ui/components/entry/text-entry/text-entry-form.js +4 -4
  138. package/src/javascripts/ui/components/entry/upload/file-upload-form.js +3 -3
  139. package/src/javascripts/ui/components/entry/upload/index.js +5 -5
  140. package/src/javascripts/ui/components/entry/upload-toggle.js +6 -6
  141. package/src/javascripts/ui/components/faq/faq.js +14 -14
  142. package/src/javascripts/ui/components/form-controls/error.js +2 -2
  143. package/src/javascripts/ui/components/form-controls/file-input.js +3 -3
  144. package/src/javascripts/ui/components/layout/agent-info.js +3 -3
  145. package/src/javascripts/ui/components/layout/chat-frame.js +20 -12
  146. package/src/javascripts/ui/components/layout/chat.js +5 -5
  147. package/src/javascripts/ui/components/layout/deprecated-app-frame.js +6 -6
  148. package/src/javascripts/ui/components/layout/deprecated-chat-frame.js +34 -0
  149. package/src/javascripts/ui/components/layout/header.js +2 -2
  150. package/src/javascripts/ui/components/layout/icon.js +11 -9
  151. package/src/javascripts/ui/components/layout/interrupt.js +7 -5
  152. package/src/javascripts/ui/components/layout/pre-chat-messages.js +1 -1
  153. package/src/javascripts/ui/components/layout/privacy-disclaimer.js +2 -2
  154. package/src/javascripts/ui/components/options/options-button.js +5 -5
  155. package/src/javascripts/ui/components/options/{options-frame.js → options-frame.tsx} +52 -18
  156. package/src/javascripts/ui/components/options/transcript/index.js +9 -10
  157. package/src/javascripts/ui/components/options/transcript/transcript-form.js +2 -2
  158. package/src/javascripts/ui/components/suggestions/index.js +8 -8
  159. package/src/javascripts/ui/components/suggestions/suggestions-item.js +1 -1
  160. package/src/javascripts/{domains/translations/components/chat-status.js → ui/components/translation-chat-status/index.tsx} +13 -14
  161. package/src/javascripts/ui/components/translation-proposal/index.tsx +36 -0
  162. package/src/javascripts/ui/components/view/app-view.js +2 -7
  163. package/src/javascripts/ui/components/view/deprecated-view.js +8 -10
  164. package/src/javascripts/ui/components/view/index.js +6 -6
  165. package/src/javascripts/ui/components/view/inline-view.js +4 -8
  166. package/src/javascripts/ui/components/view/window-view/collapse-button.js +2 -2
  167. package/src/javascripts/ui/components/view/window-view/index.js +11 -17
  168. package/src/javascripts/ui/components/view/window-view/window-open-button.js +6 -6
  169. package/src/javascripts/ui/components/warnings/idle-detach-warning.js +3 -3
  170. package/src/javascripts/ui/components/warnings/prompt.js +1 -1
  171. package/src/javascripts/ui/components/warnings/resume-conversation-prompt.js +4 -4
  172. package/src/javascripts/ui/components/widgets/in-out-transition.js +20 -18
  173. package/src/javascripts/ui/components/widgets/lightbox.js +3 -3
  174. package/src/javascripts/ui/components/widgets/modal.js +2 -2
  175. package/src/javascripts/ui/components/widgets/upload-progress.js +2 -2
  176. package/src/javascripts/ui/hooks/file-upload-hooks.js +1 -1
  177. package/src/javascripts/ui/hooks/focus-helper-hooks.js +1 -1
  178. package/src/javascripts/ui/hooks/seamly-entry-hooks.js +6 -6
  179. package/src/javascripts/ui/hooks/seamly-hooks.js +11 -10
  180. package/src/javascripts/ui/hooks/seamly-option-hooks.js +6 -6
  181. package/src/javascripts/ui/hooks/{seamly-state-hooks.js → seamly-state-hooks.ts} +9 -6
  182. package/src/javascripts/ui/hooks/use-click-outside.ts +29 -0
  183. package/src/javascripts/ui/hooks/use-event-component-mapping.js +11 -10
  184. package/src/javascripts/ui/hooks/use-interval.js +1 -1
  185. package/src/javascripts/ui/hooks/use-seamly-actions.ts +29 -29
  186. package/src/javascripts/ui/hooks/use-seamly-chat.js +14 -24
  187. package/src/javascripts/ui/hooks/use-seamly-commands.js +20 -15
  188. package/src/javascripts/ui/hooks/use-seamly-idle-detach-countdown.js +8 -8
  189. package/src/javascripts/ui/hooks/use-seamly-resume-conversation-prompt.js +2 -2
  190. package/src/javascripts/ui/hooks/use-single-file-upload.js +1 -1
  191. package/src/javascripts/ui/hooks/utility-hooks.js +1 -1
  192. package/src/javascripts/ui/utils/general-utils.js +0 -23
  193. package/src/javascripts/ui/utils/seamly-utils.ts +10 -1
  194. package/src/javascripts/ui/utils/seamly-utils.types.ts +9 -0
  195. package/src/stylesheets/1-settings/_config.scss +1 -1
  196. package/src/stylesheets/3-chat/_chat.scss +24 -9
  197. package/src/stylesheets/5-components/_chat-status.scss +72 -16
  198. package/src/stylesheets/5-components/_conversation.scss +35 -1
  199. package/src/stylesheets/5-components/_disclaimer.scss +0 -5
  200. package/src/stylesheets/5-components/_options.scss +16 -6
  201. package/src/stylesheets/5-components/_translation-options.scss +51 -0
  202. package/src/stylesheets/6-default-implementation/_scrollbar.scss +1 -1
  203. package/src/stylesheets/7-deprecated/3-app/_app.scss +19 -4
  204. package/src/stylesheets/7-deprecated/5-components/_chat-status.scss +5 -0
  205. package/src/stylesheets/7-deprecated/5-components/_options.scss +1 -4
  206. package/src/stylesheets/7-deprecated/5-components/_translation-options.scss +49 -0
  207. package/src/stylesheets/deprecated-view.scss +1 -0
  208. package/src/stylesheets/styles.scss +1 -0
  209. package/webpack/config.common.js +4 -4
  210. package/webpack/config.package.js +10 -16
  211. package/webpack/config.site.js +4 -1
  212. package/webpack/config.test.js +2 -1
  213. package/build/dist/lib/deprecated-view.css +0 -1
  214. package/build/dist/lib/styles-default-implementation.css +0 -1
  215. package/build/dist/lib/styles.css +0 -1
  216. package/src/.DS_Store +0 -0
  217. package/src/javascripts/api/event-producer.js +0 -20
  218. package/src/javascripts/api/producer.js +0 -136
  219. package/src/javascripts/domains/errors/index.js +0 -37
  220. package/src/javascripts/domains/translations/components/options-dialog/form.js +0 -70
  221. package/src/javascripts/domains/translations/components/options-dialog/index.js +0 -87
  222. package/src/javascripts/ui/components/chat-status/index.js +0 -38
  223. package/src/javascripts/ui/components/conversation/event/event.js +0 -36
  224. package/src/javascripts/ui/components/core/seamly-api-context.js +0 -5
  225. package/src/javascripts/ui/components/core/seamly-event-subscriber.js +0 -279
@@ -1,18 +1,32 @@
1
1
  import type { Config } from 'config.types'
2
+ import { components } from 'schema'
3
+ import { entryTypes } from 'ui/utils/seamly-utils'
2
4
  import type { AppState } from 'domains/app/app.types'
3
5
  import type { FormState } from 'domains/forms/forms.types'
4
6
  import type { I18nState } from 'domains/i18n/i18n.types'
5
7
  import type { TranslationState } from 'domains/translations/translations.types'
6
8
  import type { VisibilityState } from 'domains/visibility/visibility.types'
7
- import { components } from 'schema'
8
- import { entryTypes } from 'ui/utils/seamly-utils'
9
9
 
10
- export type MessageInfo = components['schemas']['MessageInfo'] & {
10
+ export type AckEvent = {
11
+ type: 'ack'
12
+ payload: {
13
+ id: string
14
+ occurredAt: number
15
+ transactionId?: string
16
+ type?: string
17
+ }
18
+ }
19
+
20
+ type DefaultEventProps = {
21
+ timeIndicator?: number
11
22
  key?: string
12
23
  }
24
+
25
+ export type MessageInfo = components['schemas']['MessageInfo'] &
26
+ DefaultEventProps
13
27
  export interface InfoEvent {
14
28
  type: 'info'
15
- payload: MessageInfo
29
+ payload: MessageInfo & DefaultEventProps
16
30
  }
17
31
 
18
32
  export type MessageMessage = components['schemas']['MessageMessage'] & {
@@ -20,7 +34,7 @@ export type MessageMessage = components['schemas']['MessageMessage'] & {
20
34
  }
21
35
  export interface MessageEvent {
22
36
  type: 'message'
23
- payload: MessageMessage
37
+ payload: MessageMessage & DefaultEventProps
24
38
  }
25
39
 
26
40
  export type MessageParticipant = components['schemas']['MessageParticipant'] & {
@@ -28,10 +42,19 @@ export type MessageParticipant = components['schemas']['MessageParticipant'] & {
28
42
  }
29
43
  export interface ParticipantEvent {
30
44
  type: 'participant'
31
- payload: MessageParticipant
45
+ payload: MessageParticipant & DefaultEventProps
32
46
  }
33
47
 
34
- export type ChannelEvent = MessageEvent | ParticipantEvent | InfoEvent
48
+ export interface ServiceDataEvent {
49
+ type: 'service_data'
50
+ payload: components['schemas']['ServiceDataMessage'] & DefaultEventProps
51
+ }
52
+
53
+ export type ChannelEvent =
54
+ | MessageEvent
55
+ | ParticipantEvent
56
+ | InfoEvent
57
+ | ServiceDataEvent
35
58
 
36
59
  type History = components['schemas']['ConversationHistoryResponse']['history']
37
60
  export type HistoryResponse = {
@@ -41,18 +64,22 @@ export type HistoryResponse = {
41
64
  activeServiceSettings: History['activeServiceSettings']
42
65
  serviceData: History['serviceData']
43
66
  resumeConversationPrompt: History['ui']['resumeConversationPrompt']
67
+ translationProposal: History['ui']['translationProposal']
44
68
  translation: History['translation']
45
69
  }
46
70
 
47
71
  export type HistoryEvents = HistoryResponse['events']
48
72
 
73
+ export type EntryMetaOptions =
74
+ History['activeServiceSettings']['entry']['options']
75
+
49
76
  export type EntryMeta = {
50
77
  default: typeof entryTypes.text
51
78
  active: typeof entryTypes.text
52
79
  userSelected: null
53
80
  blockAutoEntrySwitch: boolean
54
- options: Record<string, unknown>
55
- optionsOverride: Record<string, unknown>
81
+ options: EntryMetaOptions
82
+ optionsOverride: EntryMetaOptions
56
83
  }
57
84
 
58
85
  export type ParticipantInfo = {
@@ -75,6 +102,7 @@ export type ServiceInfo = {
75
102
 
76
103
  export interface StoreState {
77
104
  events: ChannelEvent[]
105
+ isLastEventFromClient: boolean
78
106
  initialState: Partial<
79
107
  Omit<
80
108
  StoreState,
@@ -109,7 +137,7 @@ export interface StoreState {
109
137
  skiplinkTargetId: string
110
138
  optionsButtonId: string
111
139
  headerCollapseButtonId: string
112
- serviceData: Record<string, unknown>
140
+ serviceData: Record<string, components['schemas']['ServiceDataMessage']>
113
141
  options: {
114
142
  features: {
115
143
  uploads?: {
@@ -1,23 +1,33 @@
1
+ import { ComponentChildren } from 'preact'
1
2
  import { useRef, useState } from 'preact/hooks'
2
- import { className } from 'lib/css'
3
- import { focusElement, getKey, keyNames } from 'ui/utils/general-utils'
4
- import Icon from 'ui/components/layout/icon'
5
- import { useGeneratedId } from 'ui/hooks/seamly-hooks'
3
+ import type { FramePosition } from 'ui/components/options/options-frame'
6
4
  import InOutTransition, {
7
5
  transitionStartStates,
8
6
  } from 'ui/components/widgets/in-out-transition'
9
- import { useLocaleNativeName } from 'domains/translations/hooks'
10
- import { useI18n } from 'domains/i18n/hooks'
7
+ import { useGeneratedId } from 'ui/hooks/seamly-hooks'
8
+ import { focusElement, getKey, keyNames } from 'ui/utils/general-utils'
9
+ import { className } from 'lib/css'
11
10
  import TranslationsOptionsDialog from './options-dialog'
12
11
 
13
- export default function TranslationsOptionButton() {
14
- const { t, locale } = useI18n()
12
+ type TranslationsOptionsButtonProps = {
13
+ children: ComponentChildren
14
+ position: FramePosition
15
+ classNames?: string[]
16
+ }
17
+
18
+ export default function TranslationsOptionsButton({
19
+ children,
20
+ position = {
21
+ horizontal: 'left',
22
+ vertical: 'top',
23
+ },
24
+ classNames,
25
+ }: TranslationsOptionsButtonProps) {
15
26
  const [menuIsOpen, setMenuIsOpen] = useState(false)
16
27
  const toggleButton = useRef(null)
17
28
  const toggleButtonId = useGeneratedId()
18
- const localeNativeName = useLocaleNativeName(locale)
19
29
 
20
- const onMainKeyDownHandler = (e) => {
30
+ const onMainKeyDownHandler = (e: KeyboardEvent) => {
21
31
  if (!menuIsOpen) {
22
32
  return
23
33
  }
@@ -35,7 +45,7 @@ export default function TranslationsOptionButton() {
35
45
  const handleToggleClick = () => {
36
46
  setMenuIsOpen((o) => !o)
37
47
  }
38
- const handleToggleKeyDown = (e) => {
48
+ const handleToggleKeyDown = (e: KeyboardEvent) => {
39
49
  if (getKey(e) === keyNames.ArrowDown) {
40
50
  setMenuIsOpen(true)
41
51
  e.preventDefault()
@@ -47,19 +57,25 @@ export default function TranslationsOptionButton() {
47
57
  onKeyDown={onMainKeyDownHandler}
48
58
  >
49
59
  <InOutTransition
60
+ onOutTransitionComplete={() => undefined}
61
+ onInTransitionComplete={() => undefined}
62
+ timeout={0}
50
63
  transitionStartState={transitionStartStates.notRendered}
51
64
  isActive={menuIsOpen}
52
65
  >
53
66
  <div className={className('options__dialog')} role="dialog">
54
- <TranslationsOptionsDialog onClose={handleDialogClose} />
67
+ <TranslationsOptionsDialog
68
+ onClose={handleDialogClose}
69
+ position={position}
70
+ />
55
71
  </div>
56
72
  </InOutTransition>
57
73
  <button
58
74
  type="button"
59
75
  className={className([
60
76
  'button',
61
- 'button--secondary',
62
77
  'chat__options__button',
78
+ ...classNames,
63
79
  ])}
64
80
  id={toggleButtonId}
65
81
  onClick={handleToggleClick}
@@ -68,13 +84,7 @@ export default function TranslationsOptionButton() {
68
84
  aria-haspopup="dialog"
69
85
  aria-expanded={menuIsOpen.toString()}
70
86
  >
71
- <Icon name="newTranslation" size="16" />
72
- <span className={className('button__text')}>
73
- {t('translations.settings.openButtonText', {
74
- hasLanguage: !!localeNativeName,
75
- language: localeNativeName,
76
- })}
77
- </span>
87
+ {children}
78
88
  </button>
79
89
  </div>
80
90
  )
@@ -0,0 +1,33 @@
1
+ import type { FramePosition } from 'ui/components/options/options-frame'
2
+ import OptionsFrame from 'ui/components/options/options-frame'
3
+ import { useGeneratedId } from 'ui/hooks/seamly-hooks'
4
+ import { useI18n } from 'domains/i18n/hooks'
5
+ import TranslationOptions from 'domains/translations/components/options-dialog/translation-options'
6
+
7
+ type TranslationsOptionsDialogProps = {
8
+ onClose: () => void
9
+ position: FramePosition
10
+ }
11
+
12
+ function TranslationsOptionsDialog({
13
+ onClose,
14
+ position,
15
+ }: TranslationsOptionsDialogProps) {
16
+ const { t } = useI18n()
17
+ const descriptionId = useGeneratedId()
18
+ return (
19
+ <OptionsFrame
20
+ onCancel={onClose}
21
+ headingText={t('translations.menu.title')}
22
+ cancelButtonText={t('translations.settings.cancelButtonText')}
23
+ description={t('translations.menu.description')}
24
+ descriptionId={descriptionId}
25
+ position={position}
26
+ disableButtonFocusing
27
+ >
28
+ <TranslationOptions describedById={descriptionId} onChange={onClose} />
29
+ </OptionsFrame>
30
+ )
31
+ }
32
+
33
+ export default TranslationsOptionsDialog
@@ -0,0 +1,42 @@
1
+ import { FC } from 'preact/compat'
2
+ import Icon from 'ui/components/layout/icon'
3
+ import { className } from 'lib/css'
4
+
5
+ type TranslationOptionProps = {
6
+ label: string
7
+ checked: boolean
8
+ description?: string
9
+ onChange: () => void
10
+ id: string
11
+ }
12
+
13
+ const TranslationOption: FC<TranslationOptionProps> = ({
14
+ label,
15
+ checked,
16
+ description,
17
+ onChange,
18
+ id,
19
+ }) => (
20
+ <li
21
+ className={className('translation-options__item', {
22
+ 'translation-options__item--checked': checked,
23
+ })}
24
+ >
25
+ <input
26
+ id={id}
27
+ type="radio"
28
+ value={label}
29
+ checked={checked}
30
+ className={className('visually-hidden')}
31
+ onChange={onChange}
32
+ name="language"
33
+ />
34
+
35
+ <label htmlFor={id}>
36
+ <Icon alt="" name="check" size="16" />
37
+ {label} {description && <span>({description})</span>}
38
+ </label>
39
+ </li>
40
+ )
41
+
42
+ export default TranslationOption
@@ -0,0 +1,81 @@
1
+ import { FC } from 'preact/compat'
2
+ import { useMemo } from 'preact/hooks'
3
+ import { useConfig } from 'domains/config/hooks'
4
+ import { useI18n } from 'domains/i18n/hooks'
5
+ import TranslationOption from 'domains/translations/components/options-dialog/translation-option'
6
+ import {
7
+ useTranslations,
8
+ useTranslationsContainer,
9
+ } from 'domains/translations/hooks'
10
+ import type { Language } from 'domains/translations/translations.types'
11
+ import { className } from 'lib/css'
12
+
13
+ type TranslationOptionsProps = {
14
+ onChange: () => void
15
+ describedById?: string
16
+ }
17
+
18
+ const TranslationOptions: FC<TranslationOptionsProps> = ({
19
+ onChange,
20
+ describedById,
21
+ }) => {
22
+ const {
23
+ context: { locale: defaultLocale },
24
+ } = useConfig()
25
+
26
+ const { t } = useI18n()
27
+ const { focusContainer } = useTranslationsContainer()
28
+ const { languages, currentLocale, enableTranslations, disableTranslations } =
29
+ useTranslations()
30
+
31
+ const handleChange =
32
+ ({ locale }: Language) =>
33
+ () => {
34
+ if (locale === currentLocale || defaultLocale === locale) {
35
+ disableTranslations()
36
+ } else {
37
+ enableTranslations(locale)
38
+ }
39
+
40
+ onChange()
41
+ focusContainer()
42
+ }
43
+
44
+ const sortedLanguages = useMemo(() => {
45
+ return [...languages].sort((a, b) => {
46
+ if (a.locale === defaultLocale) return -1
47
+ if (b.locale === defaultLocale) return 1
48
+
49
+ return a.nativeName.localeCompare(b.nativeName, undefined, {
50
+ sensitivity: 'base',
51
+ })
52
+ })
53
+ }, [languages, defaultLocale])
54
+
55
+ return (
56
+ <ul
57
+ aria-describedby={describedById}
58
+ className={className('translation-options')}
59
+ >
60
+ {sortedLanguages.map((language, idx) => {
61
+ const isOriginal = idx === 0
62
+
63
+ const checked =
64
+ currentLocale === language.locale || (!currentLocale && isOriginal)
65
+
66
+ return (
67
+ <TranslationOption
68
+ key={language.locale}
69
+ id={language.locale}
70
+ label={language.nativeName}
71
+ checked={checked}
72
+ description={isOriginal && t('translations.settings.original')}
73
+ onChange={handleChange(language)}
74
+ />
75
+ )
76
+ })}
77
+ </ul>
78
+ )
79
+ }
80
+
81
+ export default TranslationOptions
@@ -0,0 +1,15 @@
1
+ import TranslationChatStatus from 'ui/components/translation-chat-status'
2
+ import TranslationProposal from 'ui/components/translation-proposal'
3
+ import { useInterrupt } from 'domains/interrupt/hooks'
4
+ import { useTranslations } from 'domains/translations/hooks'
5
+
6
+ export default function TranslationStatus() {
7
+ const { hasInterrupt } = useInterrupt()
8
+
9
+ const { isActive } = useTranslations()
10
+ if (hasInterrupt) {
11
+ return null
12
+ }
13
+
14
+ return !isActive ? <TranslationProposal /> : <TranslationChatStatus />
15
+ }
@@ -1,33 +1,47 @@
1
- import { RootState } from 'domains/store'
1
+ import { useCallback, useMemo } from 'preact/hooks'
2
+ import { useDispatch, useSelector } from 'react-redux'
3
+ import {
4
+ useElementFocusingById,
5
+ useSeamlyCommands,
6
+ } from 'ui/hooks/seamly-hooks'
7
+ import {
8
+ TRANSLATION_PROPOSAL,
9
+ actionTypes,
10
+ sourceTypes,
11
+ } from 'ui/utils/seamly-utils'
12
+ import { ValueOf } from 'ui/utils/seamly-utils.types'
13
+ import { useI18n } from 'domains/i18n/hooks'
14
+ import type { RootState } from 'domains/store'
2
15
  import {
3
16
  ChannelEvent,
4
17
  InfoEvent,
5
18
  MessageEvent,
6
19
  ParticipantEvent,
20
+ ServiceDataEvent,
7
21
  } from 'domains/store/store.types'
8
22
  import { selectIsTranslated } from 'domains/translations/selectors'
9
23
  import {
10
24
  disableTranslation,
25
+ disableTranslationProposalPrompt,
11
26
  enableTranslation,
12
27
  } from 'domains/translations/slice'
13
- import { useCallback, useMemo } from 'preact/hooks'
14
- import { useDispatch, useSelector } from 'react-redux'
15
- import {
16
- useElementFocusingById,
17
- useSeamlyCommands,
18
- } from 'ui/hooks/seamly-hooks'
19
- import { actionTypes } from 'ui/utils/seamly-utils'
28
+ import type { Language } from 'domains/translations/translations.types'
29
+ import { useVisibility } from 'domains/visibility/hooks'
20
30
 
21
31
  export function useTranslations() {
22
32
  const { sendAction } = useSeamlyCommands()
23
33
  const dispatch = useDispatch()
24
34
  const enableTranslations = useCallback(
25
- (locale) => {
35
+ (
36
+ locale: Language['locale'],
37
+ source: ValueOf<typeof sourceTypes> = sourceTypes.translationChoice,
38
+ ) => {
26
39
  sendAction({
27
40
  type: actionTypes.setTranslation,
28
- body: { enabled: true, locale },
41
+ body: { enabled: true, locale, source },
29
42
  })
30
43
  dispatch(enableTranslation(locale))
44
+ dispatch(disableTranslationProposalPrompt())
31
45
  },
32
46
  [sendAction, dispatch],
33
47
  )
@@ -37,7 +51,7 @@ export function useTranslations() {
37
51
  }, [sendAction, dispatch])
38
52
 
39
53
  const { languages, isActive, isAvailable, currentLocale } = useSelector(
40
- ({ translations }) => translations,
54
+ ({ translations }: RootState) => translations,
41
55
  )
42
56
 
43
57
  return {
@@ -54,11 +68,13 @@ type EventDataBody =
54
68
  | ParticipantEvent['payload']['participant']['introduction']
55
69
  | MessageEvent['payload']['body']
56
70
  | InfoEvent['payload']['body']
71
+ | ServiceDataEvent['payload']['body']
57
72
 
58
73
  type TranslatedEventDataBody =
59
74
  | ParticipantEvent['payload']['participant']['translatedIntroduction']
60
75
  | MessageEvent['payload']['translatedBody']
61
76
  | InfoEvent['payload']['translatedBody']
77
+ | ServiceDataEvent['payload']['translatedBody']
62
78
 
63
79
  export function useTranslatedEventData(channelEvent: ChannelEvent): {
64
80
  body: EventDataBody
@@ -112,3 +128,53 @@ export function useLocaleNativeName(locale: string) {
112
128
  [locale, languages],
113
129
  )
114
130
  }
131
+
132
+ export const useTranslationProposal = () => {
133
+ const { isActive, languages } = useTranslations()
134
+
135
+ const dispatch = useDispatch()
136
+ const { sendAction } = useSeamlyCommands()
137
+
138
+ const { translationProposal } = useSelector(
139
+ (state: RootState) => state.translations,
140
+ )
141
+ const { enableTranslations } = useTranslations()
142
+ const { isOpen } = useVisibility()
143
+ const { locale } = useI18n()
144
+ const proposedLocale = translationProposal?.proposedLocale
145
+ const proposedLocaleNativeName = useLocaleNativeName(proposedLocale)
146
+
147
+ const showProposal = useMemo(
148
+ () =>
149
+ translationProposal !== null &&
150
+ !isActive &&
151
+ isOpen &&
152
+ locale !== proposedLocale &&
153
+ languages.some((language) => language.locale === proposedLocale),
154
+ [translationProposal, isActive, isOpen, locale, proposedLocale, languages],
155
+ )
156
+
157
+ const dismissTranslationProposal = () => {
158
+ sendAction({
159
+ type: actionTypes.dismiss,
160
+ body: { type: TRANSLATION_PROPOSAL },
161
+ })
162
+ dispatch(disableTranslationProposalPrompt())
163
+ }
164
+
165
+ const activateTranslationProposal = () => {
166
+ enableTranslations(
167
+ translationProposal?.proposedLocale,
168
+ sourceTypes.translationProposal,
169
+ )
170
+ }
171
+
172
+ return {
173
+ activateTranslationProposal,
174
+ dismissTranslationProposal,
175
+ proposedLocale,
176
+ proposedLocaleNativeName,
177
+ showProposal,
178
+ translationProposal,
179
+ }
180
+ }
@@ -1,12 +1,8 @@
1
- import {
2
- TranslationEvent,
3
- TranslationState,
4
- } from 'domains/translations/translations.types'
5
- import { createSlice, nanoid, PayloadAction } from '@reduxjs/toolkit'
6
- import { initializeApp } from 'domains/app/actions'
1
+ import { PayloadAction, createSlice, nanoid } from '@reduxjs/toolkit'
7
2
  import { initializeConfig } from 'domains/config/actions'
8
- import { addEvent, orderHistory, setHistory } from 'domains/store/slice'
9
- import { ChannelEvent } from 'domains/store/store.types'
3
+ import { addEvent, setHistory } from 'domains/store/slice'
4
+ import type { ChannelEvent, HistoryResponse } from 'domains/store/store.types'
5
+ import type { TranslationState } from 'domains/translations/translations.types'
10
6
 
11
7
  export const translationsInitialState: TranslationState = {
12
8
  isActive: false,
@@ -15,6 +11,7 @@ export const translationsInitialState: TranslationState = {
15
11
  languages: [],
16
12
  containerId: nanoid(),
17
13
  translatedEventGroups: {},
14
+ translationProposal: null,
18
15
  }
19
16
 
20
17
  const getLastGroupId = (events: ChannelEvent[], id: string) => {
@@ -42,7 +39,7 @@ const getLastGroupId = (events: ChannelEvent[], id: string) => {
42
39
  const [[groupId, eventIds]] = Object.entries(eventGroup)
43
40
 
44
41
  const lastGroupId = events
45
- //@ts-ignore
42
+ // @ts-ignore
46
43
  .filter((event) => event.payload?.type === 'divider')
47
44
  .map((event) => event.payload.id)
48
45
  .at(-1)
@@ -84,6 +81,15 @@ export const translationSlice = createSlice({
84
81
  state.lastGroupId = lastGroupId
85
82
  state.translatedEventGroups[groupId] = eventIds
86
83
  },
84
+ disableTranslationProposalPrompt: (state) => {
85
+ state.translationProposal = null
86
+ },
87
+ setTranslationProposalPrompt: (
88
+ state,
89
+ { payload }: PayloadAction<HistoryResponse['translationProposal']>,
90
+ ) => {
91
+ state.translationProposal = payload
92
+ },
87
93
  },
88
94
  extraReducers: (builder) => {
89
95
  builder
@@ -94,6 +100,9 @@ export const translationSlice = createSlice({
94
100
  state.isAvailable = feature.enabled === true
95
101
  state.languages = feature.languages
96
102
  })
103
+ .addCase(setHistory, (state, { payload }) => {
104
+ state.translationProposal = payload.translationProposal
105
+ })
97
106
  .addCase(addEvent, (state, { payload }) => {
98
107
  if (state.translatedEventGroups[state.lastGroupId]) {
99
108
  state.translatedEventGroups[state.lastGroupId].push(
@@ -109,6 +118,8 @@ export const {
109
118
  disableTranslation,
110
119
  enableEventsTranslation,
111
120
  disableEventsTranslation,
121
+ setTranslationProposalPrompt,
122
+ disableTranslationProposalPrompt,
112
123
  } = translationSlice.actions
113
124
 
114
125
  export default translationSlice.reducer
@@ -1,5 +1,7 @@
1
- import { operations } from 'schema'
1
+ import type { components, operations } from 'schema'
2
+ import { HistoryResponse } from 'domains/store/store.types'
2
3
 
4
+ export type Language = components['schemas']['Translation']
3
5
  export type Languages =
4
6
  operations['getAccountConfig']['responses']['201']['content']['application/json']['config']['features']['translation']['languages']
5
7
 
@@ -7,7 +9,6 @@ export type TranslationEvent = {
7
9
  id: string
8
10
  items: string[]
9
11
  }
10
-
11
12
  export interface TranslationState {
12
13
  isActive: boolean
13
14
  currentLocale?: string
@@ -16,4 +17,5 @@ export interface TranslationState {
16
17
  containerId: string
17
18
  translatedEventGroups: Record<string, string[]>
18
19
  lastGroupId?: string
20
+ translationProposal?: HistoryResponse['translationProposal'] | null
19
21
  }
@@ -1,12 +1,10 @@
1
1
  import { createAsyncThunk } from '@reduxjs/toolkit'
2
- import { VisibilityOptions } from 'config.types'
2
+ import type { VisibilityOptions } from 'config.types'
3
+ import { selectState } from 'ui/hooks/seamly-state-hooks'
3
4
  import { selectUserHasResponded } from 'domains/app/selectors'
4
5
  import * as ConfigSelectors from 'domains/config/selectors'
5
- import { ThunkAPI } from 'domains/redux/redux.types'
6
- import { setFromStorage } from 'domains/visibility/slice'
6
+ import type { ThunkAPI } from 'domains/redux/redux.types'
7
7
  import { calculateVisibility } from 'domains/visibility/utils'
8
- import { VisibilityState } from 'domains/visibility/visibility.types'
9
- import { selectState } from 'ui/hooks/seamly-state-hooks'
10
8
  import { StoreKey, visibilityStates } from './constants'
11
9
  import * as Selectors from './selectors'
12
10
 
@@ -17,7 +15,7 @@ const validVisibilityStates = [
17
15
  ]
18
16
  export const setVisibility = createAsyncThunk<
19
17
  VisibilityOptions,
20
- VisibilityState,
18
+ VisibilityOptions,
21
19
  ThunkAPI
22
20
  >(
23
21
  'setVisibility',
@@ -70,10 +68,8 @@ export const initializeVisibility = createAsyncThunk<unknown, void, ThunkAPI>(
70
68
  // initialize stored visibility
71
69
  const { layoutMode } = ConfigSelectors.selectConfig(getState())
72
70
 
73
- const storedVisibility = api.store.get(StoreKey)?.[layoutMode]
74
- if (storedVisibility) {
75
- dispatch(setFromStorage(storedVisibility))
76
- }
71
+ const storedVisibility =
72
+ api.store.get(StoreKey)?.[layoutMode] || visibilityStates.initialize
77
73
 
78
74
  dispatch(setVisibility(storedVisibility))
79
75
  return storedVisibility