@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,25 +1,24 @@
1
- import { createSlice, PayloadAction } from '@reduxjs/toolkit'
1
+ import { PayloadAction, createSlice, isAnyOf } from '@reduxjs/toolkit'
2
+ import { ConversationHistoryResponse } from 'api'
3
+ import { getTimeFromSeconds } from 'ui/utils/general-utils'
4
+ import {
5
+ entryTypes,
6
+ eventTypes,
7
+ featureKeys,
8
+ payloadTypes,
9
+ readStates,
10
+ } from 'ui/utils/seamly-utils'
2
11
  import { initializeApp, resetApp } from 'domains/app/actions'
3
12
  import { initializeConfig } from 'domains/config/actions'
4
- import {
13
+ import type {
14
+ AckEvent,
5
15
  ChannelEvent,
6
16
  EntryMeta,
7
- HistoryEvents,
8
- HistoryResponse,
9
17
  MessageParticipant,
10
- ParticipantInfo,
11
18
  ServiceInfo,
12
19
  StoreState,
13
20
  } from 'domains/store/store.types'
14
21
  import { randomId } from 'lib/id'
15
- import { getTimeFromSeconds } from 'ui/utils/general-utils'
16
- import {
17
- entryTypes,
18
- eventTypes,
19
- featureKeys,
20
- payloadTypes,
21
- readStates,
22
- } from 'ui/utils/seamly-utils'
23
22
 
24
23
  export const isUnreadMessage = ({ type, payload }: ChannelEvent) =>
25
24
  (type === eventTypes.message && !payload.fromClient) ||
@@ -36,7 +35,7 @@ export const orderHistory = (events: ChannelEvent[]) => {
36
35
 
37
36
  export const mergeHistory = (
38
37
  stateEvents: ChannelEvent[],
39
- historyEvents: HistoryEvents,
38
+ historyEvents: ConversationHistoryResponse['events'],
40
39
  ) => {
41
40
  const newStateEvents = stateEvents.filter(
42
41
  (stateEvent) =>
@@ -102,10 +101,10 @@ const participantReducer = (participantInfo, action) => {
102
101
 
103
102
  const calculateNewEntryMeta = (
104
103
  entryMeta: EntryMeta,
105
- channelEvent: ChannelEvent,
104
+ channelEvent?: ChannelEvent,
106
105
  ) => {
107
106
  const entry =
108
- channelEvent.type === 'message' ? channelEvent?.payload.entry : {}
107
+ channelEvent?.type === 'message' ? channelEvent?.payload.entry : {}
109
108
 
110
109
  const { blockAutoEntrySwitch } = entryMeta
111
110
 
@@ -133,6 +132,7 @@ const calculateNewEntryMeta = (
133
132
 
134
133
  export const initialStoreState: StoreState = {
135
134
  events: [],
135
+ isLastEventFromClient: false,
136
136
  initialState: {
137
137
  userResponded: false,
138
138
  },
@@ -243,15 +243,19 @@ export const storeSlice = createSlice({
243
243
 
244
244
  if (incrementUnread) {
245
245
  state.unreadEvents += 1
246
- action.payload.payload.messageStatus = payload.fromClient
247
- ? readStates.read
248
- : readStates.received
246
+ if (eventType !== 'service_data') {
247
+ action.payload.payload.messageStatus = payload.fromClient
248
+ ? readStates.read
249
+ : readStates.received
250
+ }
249
251
  }
250
252
 
251
253
  action.payload.payload.key = randomId()
252
254
  state.events.push(action.payload)
255
+
256
+ state.isLastEventFromClient = payload.fromClient
253
257
  },
254
- ackEvent: (state, { payload }) => {
258
+ ackEvent: (state, { payload: { payload } }: PayloadAction<AckEvent>) => {
255
259
  // If any ACKs are sent without transactionID the conversation crashes.
256
260
  // Ensure that this edge case is handled gracefully.
257
261
  if (!payload.transactionId) {
@@ -260,22 +264,17 @@ export const storeSlice = createSlice({
260
264
  }
261
265
 
262
266
  const matchedEvent = state.events.find(
263
- (m) => m.payload.transactionId === payload.transactionId,
267
+ (m) =>
268
+ m.payload.transactionId === payload.transactionId &&
269
+ (!payload.type || m.type === payload.type),
264
270
  )
265
271
 
266
272
  const { id, occurredAt } = payload
267
273
  if (matchedEvent) {
268
- state.events = orderHistory(
269
- //@ts-ignore
270
- state.events.map((m) =>
271
- m.payload.id === matchedEvent.payload.id
272
- ? {
273
- ...m,
274
- payload: { ...m.payload, id, occurredAt },
275
- }
276
- : m,
277
- ),
278
- )
274
+ matchedEvent.payload.id = id
275
+ matchedEvent.payload.occurredAt = occurredAt
276
+
277
+ state.events = orderHistory(state.events)
279
278
  }
280
279
  },
281
280
  clearEvents: (state) => {
@@ -283,15 +282,16 @@ export const storeSlice = createSlice({
283
282
  state.loadedImageEventIds = []
284
283
  state.events = []
285
284
  },
286
- setEventsRead: (state, { payload }) => {
285
+ setEventsRead: (state, { payload }: PayloadAction<string[]>) => {
287
286
  state.unreadEvents = 0
288
287
  state.events.forEach((event) => {
289
288
  if (payload.indexOf(event.payload.id) !== -1) {
290
289
  event.payload = {
291
290
  ...event.payload,
292
- ...(event.payload.messageStatus === readStates.received && {
293
- messageStatus: readStates.read,
294
- }),
291
+ ...(event.type !== 'service_data' &&
292
+ event.payload.messageStatus === readStates.received && {
293
+ messageStatus: readStates.read,
294
+ }),
295
295
  }
296
296
  }
297
297
  return event
@@ -311,7 +311,7 @@ export const storeSlice = createSlice({
311
311
  serviceData,
312
312
  resumeConversationPrompt,
313
313
  },
314
- }: PayloadAction<HistoryResponse>,
314
+ }: PayloadAction<ConversationHistoryResponse>,
315
315
  ) => {
316
316
  const events = mergeHistory(state.events, history)
317
317
 
@@ -341,14 +341,14 @@ export const storeSlice = createSlice({
341
341
  }
342
342
 
343
343
  const { entry } = activeServiceSettings
344
- const { upload } = entry.options
344
+ const upload = entry?.options?.upload
345
345
 
346
346
  const historyNewEntryMeta = calculateNewEntryMeta(
347
347
  {
348
348
  ...state.entryMeta,
349
349
  ...entry,
350
- active: entry.default || payloadTypes.text,
351
- options: { ...(entry && entry.options ? entry.options : {}) },
350
+ active: entry?.default || payloadTypes.text,
351
+ options: { ...(entry?.options ? entry.options : {}) },
352
352
  },
353
353
  events[events.length - 1],
354
354
  )
@@ -366,7 +366,7 @@ export const storeSlice = createSlice({
366
366
  newFeatures = {
367
367
  ...newFeatures,
368
368
  uploads: {
369
- enabled: !!(upload && upload.enabled),
369
+ enabled: !!(upload && upload?.enabled),
370
370
  enabledFromEntry: entryType === entryTypes.upload,
371
371
  },
372
372
  }
@@ -601,6 +601,12 @@ export const storeSlice = createSlice({
601
601
  if (!payload.initialState) return
602
602
  state.initialState = payload.initialState
603
603
  })
604
+ .addMatcher(
605
+ isAnyOf(initIdleDetachCountdown, initResumeConversationPrompt),
606
+ (state) => {
607
+ state.isLastEventFromClient = false
608
+ },
609
+ )
604
610
  },
605
611
  })
606
612
 
@@ -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,37 @@
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
+ onChange: () => void
9
+ id: string
10
+ }
11
+
12
+ const TranslationOption: FC<TranslationOptionProps> = ({
13
+ label,
14
+ checked,
15
+ onChange,
16
+ id,
17
+ }) => (
18
+ <li className={className('translation-options--item')}>
19
+ <label>
20
+ <input
21
+ id={id}
22
+ type="checkbox"
23
+ value={label}
24
+ checked={checked}
25
+ className={className('visually-hidden')}
26
+ onChange={onChange}
27
+ name="language"
28
+ />
29
+ <span className={className('translation-options--icon-container')}>
30
+ {checked ? <Icon alt={label} name="check" size="16" /> : null}
31
+ </span>
32
+ {label}
33
+ </label>
34
+ </li>
35
+ )
36
+
37
+ export default TranslationOption
@@ -0,0 +1,85 @@
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 originalLabel = isOriginal
64
+ ? ` (${t('translations.settings.original')})`
65
+ : ''
66
+ const label = `${language.nativeName}${originalLabel}`
67
+
68
+ const checked =
69
+ currentLocale === language.locale || (!currentLocale && isOriginal)
70
+
71
+ return (
72
+ <TranslationOption
73
+ key={language.locale}
74
+ id={language.locale}
75
+ label={label}
76
+ checked={checked}
77
+ onChange={handleChange(language)}
78
+ />
79
+ )
80
+ })}
81
+ </ul>
82
+ )
83
+ }
84
+
85
+ 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
+ }