@seamly/web-ui 20.7.0 → 20.8.0-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 (226) hide show
  1. package/build/dist/lib/hooks.js +1 -1
  2. package/build/dist/lib/hooks.min.js +1 -1
  3. package/build/dist/lib/index.debug.js +945 -790
  4. package/build/dist/lib/index.debug.min.js +1 -1
  5. package/build/dist/lib/index.debug.min.js.LICENSE.txt +187 -131
  6. package/build/dist/lib/index.js +24800 -19606
  7. package/build/dist/lib/index.min.js +1 -1
  8. package/build/dist/lib/index.min.js.LICENSE.txt +38 -4
  9. package/build/dist/lib/standalone.js +32920 -26742
  10. package/build/dist/lib/standalone.min.js +1 -1
  11. package/build/dist/lib/standalone.min.js.LICENSE.txt +39 -0
  12. package/build/dist/lib/storage.js +2 -2
  13. package/build/dist/lib/storage.min.js +1 -1
  14. package/build/dist/lib/style-guide.js +8780 -7907
  15. package/build/dist/lib/style-guide.min.js +2 -1
  16. package/build/dist/lib/style-guide.min.js.LICENSE.txt +38 -0
  17. package/build/dist/lib/styles.css +1 -1
  18. package/build/dist/lib/utils.js +1 -2
  19. package/build/dist/lib/utils.min.js +1 -1
  20. package/package.json +19 -9
  21. package/src/icons/avatar_agent-32.svg +7 -0
  22. package/src/icons/avatar_bot-32.svg +6 -1
  23. package/src/javascripts/api/index.js +1 -1
  24. package/src/javascripts/{config.js → config.ts} +3 -1
  25. package/src/javascripts/config.types.ts +96 -0
  26. package/src/javascripts/domains/app/actions.ts +83 -0
  27. package/src/javascripts/domains/app/app.types.ts +3 -0
  28. package/src/javascripts/domains/app/hooks.js +3 -5
  29. package/src/javascripts/domains/app/selectors.ts +6 -0
  30. package/src/javascripts/domains/app/slice.ts +30 -0
  31. package/src/javascripts/domains/config/actions.ts +45 -0
  32. package/src/javascripts/domains/config/hooks.ts +19 -0
  33. package/src/javascripts/domains/config/selectors.ts +24 -0
  34. package/src/javascripts/domains/config/slice.ts +113 -0
  35. package/src/javascripts/domains/errors/index.js +13 -9
  36. package/src/javascripts/domains/forms/context.ts +14 -0
  37. package/src/javascripts/domains/forms/forms.types.ts +24 -0
  38. package/src/javascripts/domains/forms/{hooks.js → hooks.ts} +23 -26
  39. package/src/javascripts/domains/forms/{provider.js → provider.tsx} +20 -14
  40. package/src/javascripts/domains/forms/{selectors.js → selectors.ts} +7 -8
  41. package/src/javascripts/domains/forms/slice.ts +84 -0
  42. package/src/javascripts/domains/forms/utils.ts +15 -0
  43. package/src/javascripts/domains/i18n/actions.ts +24 -0
  44. package/src/javascripts/domains/i18n/{hooks.js → hooks.ts} +2 -2
  45. package/src/javascripts/domains/i18n/i18n.types.ts +6 -0
  46. package/src/javascripts/domains/i18n/selectors.ts +16 -0
  47. package/src/javascripts/domains/i18n/{reducer.js → slice.ts} +40 -37
  48. package/src/javascripts/domains/interrupt/{hooks.js → hooks.ts} +2 -2
  49. package/src/javascripts/domains/interrupt/{middleware.js → middleware.ts} +11 -8
  50. package/src/javascripts/domains/interrupt/selectors.ts +6 -0
  51. package/src/javascripts/domains/interrupt/slice.ts +40 -0
  52. package/src/javascripts/domains/options/middleware.js +9 -6
  53. package/src/javascripts/domains/redux/redux.types.ts +11 -0
  54. package/src/javascripts/domains/store/index.ts +53 -0
  55. package/src/javascripts/domains/store/slice.ts +642 -0
  56. package/src/javascripts/domains/store/store.types.ts +146 -0
  57. package/src/javascripts/domains/translations/components/chat-status.js +2 -2
  58. package/src/javascripts/domains/translations/components/options-button.js +1 -1
  59. package/src/javascripts/domains/translations/components/options-dialog/form.js +5 -5
  60. package/src/javascripts/domains/translations/components/options-dialog/index.js +2 -2
  61. package/src/javascripts/domains/translations/hooks.ts +114 -0
  62. package/src/javascripts/domains/translations/middleware.js +29 -27
  63. package/src/javascripts/domains/translations/selectors.ts +12 -0
  64. package/src/javascripts/domains/translations/slice.ts +120 -0
  65. package/src/javascripts/domains/translations/translations.types.ts +19 -0
  66. package/src/javascripts/domains/visibility/{actions.js → actions.ts} +25 -19
  67. package/src/javascripts/domains/visibility/{hooks.js → hooks.ts} +13 -10
  68. package/src/javascripts/domains/visibility/{selectors.js → selectors.ts} +3 -6
  69. package/src/javascripts/domains/visibility/slice.ts +38 -0
  70. package/src/javascripts/domains/visibility/utils.js +0 -9
  71. package/src/javascripts/domains/visibility/visibility.types.ts +6 -0
  72. package/src/javascripts/index.ts +92 -0
  73. package/src/javascripts/lib/engine/index.js +15 -11
  74. package/src/javascripts/lib/external-api/initialize-api.js +1 -1
  75. package/src/javascripts/lib/id.js +5 -8
  76. package/src/javascripts/lib/mutex.js +3 -1
  77. package/src/javascripts/lib/store/providers/cookie-storage.js +1 -1
  78. package/src/javascripts/lib/store/providers/session-storage.js +1 -1
  79. package/src/javascripts/package/hooks.js +2 -2
  80. package/src/javascripts/package/utils.js +0 -1
  81. package/src/javascripts/schema.ts +1448 -0
  82. package/src/javascripts/style-guide/components/app.js +6 -6
  83. package/src/javascripts/style-guide/components/static-core.js +87 -65
  84. package/src/javascripts/style-guide/components/view.js +4 -4
  85. package/src/javascripts/style-guide/state-helpers/index.js +5 -5
  86. package/src/javascripts/style-guide/states.js +67 -7
  87. package/src/javascripts/style-guide.ts +5 -0
  88. package/src/javascripts/ui/components/app-options/index.js +2 -4
  89. package/src/javascripts/ui/components/conversation/component-filter.js +1 -1
  90. package/src/javascripts/ui/components/conversation/conversation.js +11 -7
  91. package/src/javascripts/ui/components/conversation/event/card-message.js +2 -2
  92. package/src/javascripts/ui/components/conversation/event/carousel-component/components/controls.js +1 -1
  93. package/src/javascripts/ui/components/conversation/event/carousel-message/components/slide.js +1 -1
  94. package/src/javascripts/ui/components/conversation/event/carousel-message/index.js +2 -2
  95. package/src/javascripts/ui/components/conversation/event/choice-prompt.js +3 -3
  96. package/src/javascripts/ui/components/conversation/event/conversation-suggestions.js +19 -15
  97. package/src/javascripts/ui/components/conversation/event/cta.js +2 -2
  98. package/src/javascripts/ui/components/conversation/event/divider/variants/default.js +1 -1
  99. package/src/javascripts/ui/components/conversation/event/divider/variants/new-translation.js +44 -5
  100. package/src/javascripts/ui/components/conversation/event/event-participant.js +2 -2
  101. package/src/javascripts/ui/components/conversation/event/hooks/use-formatted-date.js +2 -2
  102. package/src/javascripts/ui/components/conversation/event/image-lightbox.js +1 -1
  103. package/src/javascripts/ui/components/conversation/event/image.js +6 -8
  104. package/src/javascripts/ui/components/conversation/event/participant.js +2 -2
  105. package/src/javascripts/ui/components/conversation/event/splash.js +4 -4
  106. package/src/javascripts/ui/components/conversation/event/text.js +2 -2
  107. package/src/javascripts/ui/components/conversation/event/translation.js +3 -3
  108. package/src/javascripts/ui/components/conversation/event/upload.js +3 -3
  109. package/src/javascripts/ui/components/conversation/event/video.js +2 -2
  110. package/src/javascripts/ui/components/conversation/message-container.js +4 -26
  111. package/src/javascripts/ui/components/core/seamly-api-context.js +1 -1
  112. package/src/javascripts/ui/components/core/seamly-core.js +15 -14
  113. package/src/javascripts/ui/components/core/seamly-event-subscriber.js +98 -92
  114. package/src/javascripts/ui/components/core/seamly-file-upload.js +20 -24
  115. package/src/javascripts/ui/components/core/seamly-initializer.js +1 -1
  116. package/src/javascripts/ui/components/core/seamly-instance-functions-loader.js +5 -4
  117. package/src/javascripts/ui/components/core/seamly-new-notifications.js +2 -2
  118. package/src/javascripts/ui/components/core/seamly-read-state.js +10 -17
  119. package/src/javascripts/ui/components/entry/deprecated-toggle-button.js +3 -3
  120. package/src/javascripts/ui/components/entry/entry-container.js +4 -6
  121. package/src/javascripts/ui/components/entry/text-entry/hooks.js +3 -3
  122. package/src/javascripts/ui/components/entry/text-entry/index.js +3 -2
  123. package/src/javascripts/ui/components/entry/text-entry/text-entry-form.js +6 -10
  124. package/src/javascripts/ui/components/entry/upload/file-upload-form.js +2 -2
  125. package/src/javascripts/ui/components/entry/upload/index.js +10 -9
  126. package/src/javascripts/ui/components/entry/upload-toggle.js +2 -2
  127. package/src/javascripts/ui/components/faq/faq.js +9 -7
  128. package/src/javascripts/ui/components/form-controls/file-input.js +1 -1
  129. package/src/javascripts/ui/components/form-controls/form.js +1 -1
  130. package/src/javascripts/ui/components/form-controls/input.js +1 -1
  131. package/src/javascripts/ui/components/form-controls/select.js +1 -1
  132. package/src/javascripts/ui/components/layout/agent-info.js +4 -4
  133. package/src/javascripts/ui/components/layout/chat-frame.js +3 -3
  134. package/src/javascripts/ui/components/layout/chat.js +11 -12
  135. package/src/javascripts/ui/components/layout/deprecated-app-frame.js +10 -9
  136. package/src/javascripts/ui/components/layout/header.js +1 -1
  137. package/src/javascripts/ui/components/layout/interrupt.js +23 -24
  138. package/src/javascripts/ui/components/layout/pre-chat-messages.js +11 -11
  139. package/src/javascripts/ui/components/layout/privacy-disclaimer.js +2 -2
  140. package/src/javascripts/ui/components/options/options-button.js +14 -10
  141. package/src/javascripts/ui/components/options/transcript/index.js +2 -2
  142. package/src/javascripts/ui/components/options/transcript/transcript-form.js +1 -1
  143. package/src/javascripts/ui/components/suggestions/index.js +14 -10
  144. package/src/javascripts/ui/components/view/deprecated-view.js +19 -16
  145. package/src/javascripts/ui/components/view/index.js +12 -12
  146. package/src/javascripts/ui/components/view/inline-view.js +2 -2
  147. package/src/javascripts/ui/components/view/window-view/collapse-button.js +3 -3
  148. package/src/javascripts/ui/components/view/window-view/index.js +13 -13
  149. package/src/javascripts/ui/components/view/window-view/window-open-button.js +13 -13
  150. package/src/javascripts/ui/components/warnings/idle-detach-warning.js +1 -1
  151. package/src/javascripts/ui/components/warnings/resume-conversation-prompt.js +1 -1
  152. package/src/javascripts/ui/components/widgets/lightbox.js +2 -2
  153. package/src/javascripts/ui/components/widgets/upload-progress.js +1 -1
  154. package/src/javascripts/ui/hooks/component-helper-hooks.js +1 -1
  155. package/src/javascripts/ui/hooks/file-upload-hooks.js +4 -6
  156. package/src/javascripts/ui/hooks/focus-helper-hooks.js +14 -12
  157. package/src/javascripts/ui/hooks/live-region-hooks.js +2 -0
  158. package/src/javascripts/ui/hooks/seamly-api-hooks.js +8 -3
  159. package/src/javascripts/ui/hooks/seamly-entry-hooks.js +28 -25
  160. package/src/javascripts/ui/hooks/seamly-hooks.js +25 -25
  161. package/src/javascripts/ui/hooks/seamly-option-hooks.js +17 -19
  162. package/src/javascripts/ui/hooks/seamly-state-hooks.js +20 -13
  163. package/src/javascripts/ui/hooks/use-seamly-chat.js +15 -25
  164. package/src/javascripts/ui/hooks/use-seamly-commands.js +46 -46
  165. package/src/javascripts/ui/hooks/use-seamly-idle-detach-countdown.js +22 -24
  166. package/src/javascripts/ui/hooks/use-seamly-resume-conversation-prompt.js +8 -9
  167. package/src/javascripts/ui/hooks/use-single-file-upload.js +4 -6
  168. package/src/javascripts/ui/hooks/utility-hooks.js +4 -4
  169. package/src/javascripts/ui/utils/form-utils.js +0 -145
  170. package/src/javascripts/ui/utils/general-utils.js +3 -4
  171. package/src/javascripts/ui/utils/seamly-utils.ts +73 -0
  172. package/src/stylesheets/5-components/_message-carousel.scss +10 -8
  173. package/webpack/config.common.js +16 -0
  174. package/webpack/config.dev.js +1 -0
  175. package/webpack/config.package.js +26 -5
  176. package/webpack/defaults.js +7 -2
  177. package/webpack/parts/babel-loader-browser-plugins.js +1 -0
  178. package/webpack/parts/dev-server.js +4 -3
  179. package/CHANGELOG.md +0 -791
  180. package/src/javascripts/domains/app/actions.js +0 -112
  181. package/src/javascripts/domains/app/index.js +0 -7
  182. package/src/javascripts/domains/app/reducer.js +0 -16
  183. package/src/javascripts/domains/app/selectors.js +0 -8
  184. package/src/javascripts/domains/app/utils.js +0 -4
  185. package/src/javascripts/domains/config/actions.js +0 -7
  186. package/src/javascripts/domains/config/hooks.js +0 -23
  187. package/src/javascripts/domains/config/index.js +0 -7
  188. package/src/javascripts/domains/config/reducer.js +0 -79
  189. package/src/javascripts/domains/config/selectors.js +0 -23
  190. package/src/javascripts/domains/config/utils.js +0 -4
  191. package/src/javascripts/domains/forms/actions.js +0 -21
  192. package/src/javascripts/domains/forms/context.js +0 -6
  193. package/src/javascripts/domains/forms/index.js +0 -8
  194. package/src/javascripts/domains/forms/reducer.js +0 -84
  195. package/src/javascripts/domains/forms/utils.js +0 -20
  196. package/src/javascripts/domains/i18n/actions.js +0 -20
  197. package/src/javascripts/domains/i18n/index.js +0 -7
  198. package/src/javascripts/domains/i18n/selectors.js +0 -15
  199. package/src/javascripts/domains/i18n/utils.js +0 -4
  200. package/src/javascripts/domains/interrupt/actions.js +0 -4
  201. package/src/javascripts/domains/interrupt/index.js +0 -9
  202. package/src/javascripts/domains/interrupt/reducer.js +0 -22
  203. package/src/javascripts/domains/interrupt/selectors.js +0 -6
  204. package/src/javascripts/domains/interrupt/utils.js +0 -4
  205. package/src/javascripts/domains/options/index.js +0 -1
  206. package/src/javascripts/domains/redux/context.js +0 -6
  207. package/src/javascripts/domains/redux/create-redux-store.js +0 -21
  208. package/src/javascripts/domains/redux/hooks.js +0 -80
  209. package/src/javascripts/domains/redux/index.js +0 -19
  210. package/src/javascripts/domains/redux/provider.js +0 -5
  211. package/src/javascripts/domains/redux/utils.js +0 -12
  212. package/src/javascripts/domains/store/index.js +0 -46
  213. package/src/javascripts/domains/store/state-reducer.js +0 -56
  214. package/src/javascripts/domains/translations/actions.js +0 -11
  215. package/src/javascripts/domains/translations/hooks.js +0 -103
  216. package/src/javascripts/domains/translations/index.js +0 -10
  217. package/src/javascripts/domains/translations/reducer.js +0 -69
  218. package/src/javascripts/domains/translations/selectors.js +0 -16
  219. package/src/javascripts/domains/translations/utils.js +0 -4
  220. package/src/javascripts/domains/visibility/index.js +0 -8
  221. package/src/javascripts/domains/visibility/reducer.js +0 -24
  222. package/src/javascripts/index.js +0 -153
  223. package/src/javascripts/lib/redux-helpers/index.js +0 -99
  224. package/src/javascripts/style-guide.js +0 -5
  225. package/src/javascripts/ui/hooks/use-seamly-dispatch.js +0 -3
  226. package/src/javascripts/ui/utils/seamly-utils.js +0 -832
@@ -0,0 +1,146 @@
1
+ import type { Config } from 'config.types'
2
+ import type { AppState } from 'domains/app/app.types'
3
+ import type { FormState } from 'domains/forms/forms.types'
4
+ import type { I18nState } from 'domains/i18n/i18n.types'
5
+ import type { TranslationState } from 'domains/translations/translations.types'
6
+ import type { VisibilityState } from 'domains/visibility/visibility.types'
7
+ import { components } from 'schema'
8
+ import { entryTypes } from 'ui/utils/seamly-utils'
9
+
10
+ export type MessageInfo = components['schemas']['MessageInfo'] & {
11
+ key?: string
12
+ }
13
+ export interface InfoEvent {
14
+ type: 'info'
15
+ payload: MessageInfo
16
+ }
17
+
18
+ export type MessageMessage = components['schemas']['MessageMessage'] & {
19
+ key?: string
20
+ }
21
+ export interface MessageEvent {
22
+ type: 'message'
23
+ payload: MessageMessage
24
+ }
25
+
26
+ export type MessageParticipant = components['schemas']['MessageParticipant'] & {
27
+ key?: string
28
+ }
29
+ export interface ParticipantEvent {
30
+ type: 'participant'
31
+ payload: MessageParticipant
32
+ }
33
+
34
+ export type ChannelEvent = MessageEvent | ParticipantEvent | InfoEvent
35
+
36
+ type History = components['schemas']['ConversationHistoryResponse']['history']
37
+ export type HistoryResponse = {
38
+ events: ChannelEvent[]
39
+ participants: History['participants']
40
+ activeServiceSessionId: History['activeServiceSessionId']
41
+ activeServiceSettings: History['activeServiceSettings']
42
+ serviceData: History['serviceData']
43
+ resumeConversationPrompt: History['ui']['resumeConversationPrompt']
44
+ translation: History['translation']
45
+ }
46
+
47
+ export type HistoryEvents = HistoryResponse['events']
48
+
49
+ export type EntryMeta = {
50
+ default: typeof entryTypes.text
51
+ active: typeof entryTypes.text
52
+ userSelected: null
53
+ blockAutoEntrySwitch: boolean
54
+ options: Record<string, unknown>
55
+ optionsOverride: Record<string, unknown>
56
+ }
57
+
58
+ export type ParticipantInfo = {
59
+ participants: Record<string, MessageParticipant['participant']>
60
+ currentAgent: string
61
+ }
62
+ export interface CurrentUpload {
63
+ id: string
64
+ progress: number
65
+ name: string
66
+ uploading: boolean
67
+ uploadHandle: null | { abort: () => void }
68
+ error: string
69
+ complete: boolean
70
+ }
71
+
72
+ export type ServiceInfo = {
73
+ activeServiceSessionId: string
74
+ }
75
+
76
+ export interface StoreState {
77
+ events: ChannelEvent[]
78
+ initialState: Partial<
79
+ Omit<
80
+ StoreState,
81
+ 'initialState' | 'userHasResponded' | 'seamlyContainerElement'
82
+ >
83
+ > & {
84
+ userResponded: boolean
85
+ }
86
+ unreadEvents: number
87
+ userHasResponded: boolean
88
+ loadedImageEventIds: string[]
89
+ isLoading: boolean
90
+ idleDetachCountdown: {
91
+ hasCountdown: boolean
92
+ isActive: boolean
93
+ remaining?: number
94
+ wasStopped?: boolean
95
+ count?: number
96
+ timer?: {
97
+ minutes: number
98
+ seconds: number
99
+ }
100
+ }
101
+ resumeConversationPrompt: boolean
102
+ serviceInfo: ServiceInfo
103
+ participantInfo: ParticipantInfo
104
+ headerTitles: {
105
+ title: null
106
+ subTitle: string
107
+ }
108
+ historyLoaded: boolean
109
+ skiplinkTargetId: string
110
+ optionsButtonId: string
111
+ headerCollapseButtonId: string
112
+ serviceData: Record<string, unknown>
113
+ options: {
114
+ features: {
115
+ uploads?: {
116
+ enabledFromEntry: boolean
117
+ enabled: boolean
118
+ }
119
+ }
120
+ panelActive: boolean
121
+ optionActive: string
122
+ userSelectedOptions: Record<string, unknown>
123
+ }
124
+ showFileUpload: boolean
125
+ currentUploads: CurrentUpload[]
126
+ entryMeta: EntryMeta
127
+ seamlyContainerElement: null | HTMLElement
128
+ }
129
+
130
+ export type CreateStore = {
131
+ initialState: Partial<StoreState>
132
+ api: Record<string, unknown>
133
+ eventBus: Record<string, unknown>
134
+ config: Config
135
+ }
136
+
137
+ export interface ReduxStore {
138
+ state: StoreState
139
+ app: AppState
140
+ config: Config
141
+ forms: FormState
142
+ translations: TranslationState
143
+ i18n: I18nState
144
+ interrupt: {}
145
+ visibility: VisibilityState
146
+ }
@@ -1,13 +1,13 @@
1
1
  import { useCallback } from 'preact/hooks'
2
2
  import ChatStatus from 'ui/components/chat-status'
3
- import { useI18n } from 'domains/i18n'
3
+ import { useI18n } from 'domains/i18n/hooks'
4
4
  import { useSkiplinkTargetFocusing } from 'ui/hooks/seamly-hooks'
5
5
  import {
6
6
  useTranslationsContainer,
7
7
  useTranslations,
8
8
  useLocaleNativeName,
9
9
  } from 'domains/translations/hooks'
10
- import { useInterrupt } from 'domains/interrupt'
10
+ import { useInterrupt } from 'domains/interrupt/hooks'
11
11
 
12
12
  export default function TranslationsChatStatus() {
13
13
  const { t } = useI18n()
@@ -7,7 +7,7 @@ import InOutTransition, {
7
7
  transitionStartStates,
8
8
  } from 'ui/components/widgets/in-out-transition'
9
9
  import { useLocaleNativeName } from 'domains/translations/hooks'
10
- import { useI18n } from 'domains/i18n'
10
+ import { useI18n } from 'domains/i18n/hooks'
11
11
  import TranslationsOptionsDialog from './options-dialog'
12
12
 
13
13
  export default function TranslationsOptionButton() {
@@ -1,11 +1,11 @@
1
- import { useMemo } from 'preact/hooks'
2
- import Form from 'ui/components/form-controls/form'
3
- import { className } from 'lib/css'
4
- import { useI18n } from 'domains/i18n'
1
+ import { useI18n } from 'domains/i18n/hooks'
5
2
  import {
6
- useTranslations,
7
3
  useLocaleNativeName,
4
+ useTranslations,
8
5
  } from 'domains/translations/hooks'
6
+ import { className } from 'lib/css'
7
+ import { useMemo } from 'preact/hooks'
8
+ import Form from 'ui/components/form-controls/form'
9
9
  import Select from 'ui/components/form-controls/select'
10
10
 
11
11
  function TranslationsOptionsDialogForm({ controlName, descriptionId }) {
@@ -1,6 +1,6 @@
1
1
  import { useCallback, useMemo, useRef, useState } from 'preact/hooks'
2
2
  import OptionsFrame from 'ui/components/options/options-frame'
3
- import { FormProvider } from 'domains/forms'
3
+ import FormProvider from 'domains/forms/provider'
4
4
  import { useGeneratedId } from 'ui/hooks/seamly-hooks'
5
5
  import { getValidator } from 'ui/utils/form-utils'
6
6
  import { isNotEmptyString } from 'ui/utils/validations'
@@ -8,7 +8,7 @@ import {
8
8
  useTranslations,
9
9
  useTranslationsContainer,
10
10
  } from 'domains/translations/hooks'
11
- import { useI18n } from 'domains/i18n'
11
+ import { useI18n } from 'domains/i18n/hooks'
12
12
  import TranslationsOptionsDialogForm from './form'
13
13
 
14
14
  export const formName = 'translation-settings'
@@ -0,0 +1,114 @@
1
+ import { RootState } from 'domains/store'
2
+ import {
3
+ ChannelEvent,
4
+ InfoEvent,
5
+ MessageEvent,
6
+ ParticipantEvent,
7
+ } from 'domains/store/store.types'
8
+ import { selectIsTranslated } from 'domains/translations/selectors'
9
+ import {
10
+ disableTranslation,
11
+ enableTranslation,
12
+ } 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'
20
+
21
+ export function useTranslations() {
22
+ const { sendAction } = useSeamlyCommands()
23
+ const dispatch = useDispatch()
24
+ const enableTranslations = useCallback(
25
+ (locale) => {
26
+ sendAction({
27
+ type: actionTypes.setTranslation,
28
+ body: { enabled: true, locale },
29
+ })
30
+ dispatch(enableTranslation(locale))
31
+ },
32
+ [sendAction, dispatch],
33
+ )
34
+ const disableTranslations = useCallback(() => {
35
+ sendAction({ type: actionTypes.setTranslation, body: { enabled: false } })
36
+ dispatch(disableTranslation())
37
+ }, [sendAction, dispatch])
38
+
39
+ const { languages, isActive, isAvailable, currentLocale } = useSelector(
40
+ ({ translations }) => translations,
41
+ )
42
+
43
+ return {
44
+ languages,
45
+ isActive,
46
+ isAvailable,
47
+ currentLocale,
48
+ enableTranslations,
49
+ disableTranslations,
50
+ }
51
+ }
52
+
53
+ type EventDataBody =
54
+ | ParticipantEvent['payload']['participant']['introduction']
55
+ | MessageEvent['payload']['body']
56
+ | InfoEvent['payload']['body']
57
+
58
+ type TranslatedEventDataBody =
59
+ | ParticipantEvent['payload']['participant']['translatedIntroduction']
60
+ | MessageEvent['payload']['translatedBody']
61
+ | InfoEvent['payload']['translatedBody']
62
+
63
+ export function useTranslatedEventData(channelEvent: ChannelEvent): {
64
+ body: EventDataBody
65
+ hasTranslation: boolean
66
+ isTranslated: boolean
67
+ locale: string
68
+ } {
69
+ const getTranslations = (): {
70
+ body: EventDataBody
71
+ translatedBody: TranslatedEventDataBody
72
+ } => {
73
+ if (!channelEvent.payload)
74
+ return { body: undefined, translatedBody: undefined }
75
+ if (channelEvent.type === 'participant') {
76
+ return {
77
+ body: channelEvent.payload?.participant.introduction,
78
+ translatedBody:
79
+ channelEvent.payload?.participant?.translatedIntroduction,
80
+ }
81
+ }
82
+
83
+ return {
84
+ body: channelEvent.payload.body,
85
+ translatedBody: channelEvent.payload.translatedBody,
86
+ }
87
+ }
88
+
89
+ const { translatedBody, body } = getTranslations()
90
+ const hasTranslation = !!translatedBody
91
+ const isTranslated = useSelector(selectIsTranslated(channelEvent))
92
+
93
+ return {
94
+ body: hasTranslation && isTranslated ? translatedBody?.data : body,
95
+ hasTranslation,
96
+ isTranslated: isTranslated && hasTranslation,
97
+ locale: translatedBody?.locale,
98
+ }
99
+ }
100
+
101
+ export function useTranslationsContainer() {
102
+ const id = useSelector(({ translations }) => translations.containerId)
103
+ const focusContainer = useElementFocusingById(id)
104
+ return { id, focusContainer }
105
+ }
106
+
107
+ export function useLocaleNativeName(locale: string) {
108
+ const { languages } = useTranslations()
109
+
110
+ return useMemo(
111
+ () => languages?.find((lang) => lang.locale === locale)?.nativeName,
112
+ [locale, languages],
113
+ )
114
+ }
@@ -1,44 +1,46 @@
1
+ import { initializeConfig } from 'domains/config/actions'
2
+ import { setLocale } from 'domains/i18n/actions'
3
+ import { selectInitialLocale } from 'domains/i18n/selectors'
4
+ import { addEvent, setHistory, setInitialState } from 'domains/store/slice'
1
5
  import {
2
- Actions as I18nActions,
3
- Selectors as I18nSelectors,
4
- } from 'domains/i18n'
5
- import { seamlyActions } from 'ui/utils/seamly-utils'
6
- import * as Actions from './actions'
6
+ disableTranslation,
7
+ enableTranslation,
8
+ } from 'domains/translations/slice'
7
9
 
8
- export default function createMiddleware({ dispatch, getState }) {
10
+ export default function createI18nMiddleware({ dispatch, getState }) {
9
11
  return (next) => {
10
12
  return (action) => {
11
13
  const result = next(action)
12
-
14
+ const { payload } = action
13
15
  switch (action.type) {
14
- case String(seamlyActions.SET_HISTORY):
15
- if (action.history?.translation?.enabled) {
16
- dispatch(Actions.enable(action.history.translation.locale))
17
- dispatch(I18nActions.setLocale(action.history.translation.locale))
16
+ case setHistory.type:
17
+ if (payload?.translation?.enabled) {
18
+ dispatch(enableTranslation(payload.translation.locale))
19
+ dispatch(setLocale(payload.translation.locale))
20
+ }
21
+ break
22
+ case initializeConfig.fulfilled.type:
23
+ if (payload.locale) {
24
+ dispatch(setLocale(payload.locale))
18
25
  }
19
26
  break
20
- case String(seamlyActions.SET_INITIAL_STATE):
21
- if (action.initialState?.translation?.enabled) {
22
- dispatch(Actions.enable(action.initialState.translation.locale))
23
- dispatch(I18nActions.setLocale(action.locale))
27
+ case setInitialState.type:
28
+ if (payload?.translation?.enabled) {
29
+ dispatch(enableTranslation(payload.translation.locale))
24
30
  }
25
31
  break
26
- case String(seamlyActions.ADD_EVENT):
32
+ case addEvent.type:
27
33
  if (
28
- action.event.type === 'info' &&
29
- action.event?.payload?.body?.subtype === 'new_translation' &&
30
- action.event.payload.body.translationEnabled
34
+ payload.type === 'info' &&
35
+ payload?.payload?.body?.subtype === 'new_translation' &&
36
+ payload.payload.body.translationEnabled
31
37
  ) {
32
- dispatch(
33
- I18nActions.setLocale(
34
- action.event.payload.body.translationLocale,
35
- ),
36
- )
38
+ dispatch(setLocale(payload.payload.body.translationLocale))
37
39
  }
38
40
  break
39
- case String(Actions.disable):
40
- const initialLocale = I18nSelectors.selectInitialLocale(getState())
41
- dispatch(I18nActions.setLocale(initialLocale))
41
+ case disableTranslation.type:
42
+ const initialLocale = selectInitialLocale(getState())
43
+ dispatch(setLocale(initialLocale))
42
44
  break
43
45
  }
44
46
  return result
@@ -0,0 +1,12 @@
1
+ import { createSelector } from '@reduxjs/toolkit'
2
+ import { RootState } from 'domains/store'
3
+ import { ChannelEvent } from 'domains/store/store.types'
4
+
5
+ export const selectIsTranslated = (channelEvent: ChannelEvent) =>
6
+ createSelector(
7
+ (store: RootState) => store.translations.translatedEventGroups,
8
+ (translatedEventGroups: Record<string, string[]>) =>
9
+ Object.values(translatedEventGroups).every(
10
+ (value) => !value.includes(channelEvent?.payload?.id),
11
+ ),
12
+ )
@@ -0,0 +1,120 @@
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'
7
+ import { initializeConfig } from 'domains/config/actions'
8
+ import { addEvent, orderHistory, setHistory } from 'domains/store/slice'
9
+ import { ChannelEvent } from 'domains/store/store.types'
10
+
11
+ export const translationsInitialState: TranslationState = {
12
+ isActive: false,
13
+ currentLocale: undefined,
14
+ isAvailable: false,
15
+ languages: [],
16
+ containerId: nanoid(),
17
+ translatedEventGroups: {},
18
+ }
19
+
20
+ const getLastGroupId = (events: ChannelEvent[], id: string) => {
21
+ const eventGroup = [...events].reduce<Record<string, string[]>>(
22
+ (acc, { payload }, _index, arr) => {
23
+ if (acc[id]) {
24
+ // Splice to break early (make the reducer think we are done)
25
+ // This is needed to avoid events of other groups from being added to the array.
26
+ // @ts-ignore
27
+ if (payload?.type === 'divider' && payload?.body?.translationEnabled) {
28
+ arr.splice(0)
29
+ return acc
30
+ }
31
+
32
+ acc[id].push(payload.id)
33
+ }
34
+
35
+ if (payload.id === id) acc[id] = []
36
+
37
+ return acc
38
+ },
39
+ {},
40
+ )
41
+
42
+ const [[groupId, eventIds]] = Object.entries(eventGroup)
43
+
44
+ const lastGroupId = events
45
+ //@ts-ignore
46
+ .filter((event) => event.payload?.type === 'divider')
47
+ .map((event) => event.payload.id)
48
+ .at(-1)
49
+
50
+ return { lastGroupId, groupId, eventIds }
51
+ }
52
+
53
+ export const translationSlice = createSlice({
54
+ name: 'translation',
55
+ initialState: translationsInitialState,
56
+ reducers: {
57
+ enableTranslation: (state, { payload }) => {
58
+ state.isActive = true
59
+ state.currentLocale = payload
60
+ },
61
+ disableTranslation: (state) => {
62
+ state.isActive = false
63
+ state.currentLocale = undefined
64
+ },
65
+ enableEventsTranslation: (
66
+ state,
67
+ {
68
+ payload: { events, id },
69
+ }: PayloadAction<{ events: ChannelEvent[]; id: string }>,
70
+ ) => {
71
+ delete state.translatedEventGroups[id]
72
+
73
+ const { lastGroupId } = getLastGroupId(events, id)
74
+ state.lastGroupId = lastGroupId
75
+ },
76
+ disableEventsTranslation: (
77
+ state,
78
+ {
79
+ payload: { events, id },
80
+ }: PayloadAction<{ events: ChannelEvent[]; id: string }>,
81
+ ) => {
82
+ const { lastGroupId, groupId, eventIds } = getLastGroupId(events, id)
83
+
84
+ state.lastGroupId = lastGroupId
85
+ state.translatedEventGroups[groupId] = eventIds
86
+ },
87
+ },
88
+ extraReducers: (builder) => {
89
+ builder
90
+ .addCase(initializeApp.fulfilled, (state, { payload }) => {
91
+ if (!payload.config?.context?.translationLocale) return
92
+
93
+ state.isActive = true
94
+ state.currentLocale = payload.locale
95
+ })
96
+ .addCase(initializeConfig.fulfilled, (state, { payload }) => {
97
+ const feature = payload?.features?.translation
98
+ if (!feature) return
99
+
100
+ state.isAvailable = feature.enabled === true
101
+ state.languages = feature.languages
102
+ })
103
+ .addCase(addEvent, (state, { payload }) => {
104
+ if (state.translatedEventGroups[state.lastGroupId]) {
105
+ state.translatedEventGroups[state.lastGroupId].push(
106
+ payload.payload.id,
107
+ )
108
+ }
109
+ })
110
+ },
111
+ })
112
+
113
+ export const {
114
+ enableTranslation,
115
+ disableTranslation,
116
+ enableEventsTranslation,
117
+ disableEventsTranslation,
118
+ } = translationSlice.actions
119
+
120
+ export default translationSlice.reducer
@@ -0,0 +1,19 @@
1
+ import { operations } from 'schema'
2
+
3
+ export type Languages =
4
+ operations['getAccountConfig']['responses']['201']['content']['application/json']['config']['features']['translation']['languages']
5
+
6
+ export type TranslationEvent = {
7
+ id: string
8
+ items: string[]
9
+ }
10
+
11
+ export interface TranslationState {
12
+ isActive: boolean
13
+ currentLocale?: string
14
+ isAvailable: boolean
15
+ languages: Languages
16
+ containerId: string
17
+ translatedEventGroups: Record<string, string[]>
18
+ lastGroupId?: string
19
+ }
@@ -1,36 +1,38 @@
1
+ import { createAsyncThunk } from '@reduxjs/toolkit'
2
+ import { VisibilityOptions } from 'config.types'
3
+ import { selectUserHasResponded } from 'domains/app/selectors'
1
4
  import * as ConfigSelectors from 'domains/config/selectors'
2
- import * as AppSelectors from 'domains/app/selectors'
3
-
5
+ import { ThunkAPI } from 'domains/redux/redux.types'
6
+ import { setFromStorage } from 'domains/visibility/slice'
7
+ import { calculateVisibility } from 'domains/visibility/utils'
8
+ import { VisibilityState } from 'domains/visibility/visibility.types'
4
9
  import { selectState } from 'ui/hooks/seamly-state-hooks'
5
- import { visibilityStates, StoreKey } from './constants'
6
- import { createAction, createThunk, calculateVisibility } from './utils'
10
+ import { StoreKey, visibilityStates } from './constants'
7
11
  import * as Selectors from './selectors'
8
12
 
9
- export const setFromStorage = createAction('setFromStorage', (visibility) => ({
10
- visibility,
11
- }))
12
-
13
- export const setShowInlineView = createAction('setShowInlineView')
14
-
15
13
  const validVisibilityStates = [
16
14
  visibilityStates.open,
17
15
  visibilityStates.minimized,
18
16
  visibilityStates.hidden,
19
17
  ]
20
- export const setVisibility = createThunk(
21
- 'set',
18
+ export const setVisibility = createAsyncThunk<
19
+ VisibilityOptions,
20
+ VisibilityState,
21
+ ThunkAPI
22
+ >(
23
+ 'setVisibility',
22
24
  (requestedVisibility, { getState, extra: { api, eventBus } }) => {
23
25
  const state = getState()
24
26
  const previousVisibility = Selectors.selectVisibility(state)
25
- const hasResponded = AppSelectors.selectUserHasResponded(state)
27
+ const userHasResponded = selectUserHasResponded(state)
26
28
  const hasConversation = api.hasConversation()
27
29
  const config = ConfigSelectors.selectConfig(state)
30
+
28
31
  const { visibilityCallback = calculateVisibility, layoutMode } = config
29
32
  const { unreadEvents: unreadMessageCount } = selectState(state)
30
33
 
31
34
  const calculatedVisibility = visibilityCallback({
32
- hasConversation,
33
- hasResponded,
35
+ hasResponded: userHasResponded,
34
36
  previousVisibility,
35
37
  requestedVisibility,
36
38
  config,
@@ -53,23 +55,27 @@ export const setVisibility = createThunk(
53
55
  eventBus.emit('ui.visible', requestedVisibility, {
54
56
  visibility: requestedVisibility,
55
57
  hasConversation,
56
- hasResponded,
58
+ hasResponded: userHasResponded,
57
59
  unreadMessageCount,
58
60
  })
59
61
  }
62
+
60
63
  return calculatedVisibility
61
64
  },
62
65
  )
63
66
 
64
- export const initialize = createThunk(
67
+ export const initializeVisibility = createAsyncThunk<unknown, void, ThunkAPI>(
65
68
  'initialize',
66
- async (locale, { dispatch, getState, extra: { api } }) => {
69
+ async (_, { dispatch, getState, extra: { api } }) => {
67
70
  // initialize stored visibility
68
71
  const { layoutMode } = ConfigSelectors.selectConfig(getState())
72
+
69
73
  const storedVisibility = api.store.get(StoreKey)?.[layoutMode]
70
74
  if (storedVisibility) {
71
75
  dispatch(setFromStorage(storedVisibility))
72
76
  }
73
- dispatch(setVisibility(visibilityStates.initialize))
77
+
78
+ dispatch(setVisibility(storedVisibility))
79
+ return storedVisibility
74
80
  },
75
81
  )