@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,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
@@ -1,44 +1,63 @@
1
+ import type { VisibilityOptions } from 'config.types'
2
+ import { RefObject, createRef } from 'preact'
3
+ import { useCallback, useEffect, useState } from 'preact/hooks'
4
+ import { useDispatch, useSelector } from 'react-redux'
1
5
  import { useConfig } from 'domains/config/hooks'
2
6
  import { useAppDispatch } from 'domains/store'
3
7
  import { setVisibility } from 'domains/visibility/actions'
4
8
  import { setShowInlineView } from 'domains/visibility/slice'
5
- import { VisibilityState } from 'domains/visibility/visibility.types'
6
- import { useCallback, useEffect, useRef, useState } from 'preact/hooks'
7
- import { useDispatch, useSelector } from 'react-redux'
8
9
  import { visibilityStates } from './constants'
9
- import * as Selectors from './selectors'
10
+ import { selectShowInlineView, selectVisibility } from './selectors'
10
11
 
11
12
  export const useVisibility = () => {
12
13
  const dispatch = useAppDispatch()
13
- const visible = useSelector(Selectors.selectVisibility)
14
+ const visible = useSelector(selectVisibility)
14
15
  const isVisible = visible ? visible !== visibilityStates.hidden : false
15
16
  const isOpen = visible === visibilityStates.open
16
17
  const isMinimized = visible === visibilityStates.minimized
17
18
 
18
19
  const dispatchVisibility = useCallback(
19
- (visibility: VisibilityState) => dispatch(setVisibility(visibility)),
20
+ (visibility: VisibilityOptions) => dispatch(setVisibility(visibility)),
20
21
  [dispatch],
21
22
  )
22
23
 
24
+ const openChat = () => {
25
+ dispatchVisibility(visibilityStates.open as VisibilityOptions)
26
+ }
27
+
28
+ const closeChat = () => {
29
+ dispatchVisibility(visibilityStates.minimized as VisibilityOptions)
30
+ }
31
+
23
32
  return {
24
33
  isVisible,
25
34
  isOpen,
26
35
  isMinimized,
27
36
  visible,
28
37
  setVisibility: dispatchVisibility,
38
+ openChat,
39
+ closeChat,
29
40
  }
30
41
  }
31
42
 
43
+ type UseIntersectOptions = {
44
+ /** Stops observing when the root element is visible. */
45
+ freezeOnceVisible?: boolean
46
+ /** Determines if useIntersect is enabled. */
47
+ enabled?: boolean
48
+ /** The node ref to apply the intersection to */
49
+ containerRef?: RefObject<any>
50
+ }
51
+
32
52
  /**
33
53
  * Custom hook which enables initializing of IntersectionObserver on any node ref.
34
- * @param {object} options Hook options.
35
- * @param {boolean=} options.freezeOnceVisible Stops observing when the root element is visible.
36
- * @param {boolean=} options.enabled Determines if useIntersect is enabled.
37
54
  */
38
- const useIntersect = ({ freezeOnceVisible = false, enabled = true }) => {
55
+ export const useIntersect = ({
56
+ freezeOnceVisible = false,
57
+ enabled = true,
58
+ containerRef = createRef(),
59
+ }: UseIntersectOptions) => {
39
60
  const [entry, setEntry] = useState(null)
40
- const containerRef = useRef(null)
41
-
42
61
  const isVisible = !!entry?.isIntersecting || !enabled
43
62
  const frozen = isVisible && freezeOnceVisible
44
63
 
@@ -63,7 +82,7 @@ const useIntersect = ({ freezeOnceVisible = false, enabled = true }) => {
63
82
  observer.observe(node)
64
83
 
65
84
  return () => observer.disconnect()
66
- }, [enabled, frozen])
85
+ }, [containerRef, enabled, frozen])
67
86
 
68
87
  return { isVisible, containerRef }
69
88
  }
@@ -71,7 +90,7 @@ const useIntersect = ({ freezeOnceVisible = false, enabled = true }) => {
71
90
  export const useShowInlineView = () => {
72
91
  const dispatch = useDispatch()
73
92
  const { connectWhenInView } = useConfig()
74
- const showInlineView = useSelector(Selectors.selectShowInlineView)
93
+ const showInlineView = useSelector(selectShowInlineView)
75
94
 
76
95
  const { containerRef, isVisible } = useIntersect({
77
96
  enabled: connectWhenInView,
@@ -1,11 +1,12 @@
1
1
  import { createSelector } from '@reduxjs/toolkit'
2
+ import type { RootState } from 'domains/store'
2
3
 
3
4
  export const selectVisibility = createSelector(
4
- ({ visibility }) => visibility,
5
+ ({ visibility }: RootState) => visibility,
5
6
  (state) => state.visibility,
6
7
  )
7
8
 
8
9
  export const selectShowInlineView = createSelector(
9
- ({ visibility }) => visibility,
10
+ ({ visibility }: RootState) => visibility,
10
11
  (state) => state.showInlineView,
11
12
  )
@@ -1,5 +1,5 @@
1
- import { createSlice, PayloadAction } from '@reduxjs/toolkit'
2
- import { VisibilityOptions } from 'config.types'
1
+ import { PayloadAction, createSlice } from '@reduxjs/toolkit'
2
+ import type { VisibilityOptions } from 'config.types'
3
3
  import { initializeConfig } from 'domains/config/actions'
4
4
  import { setVisibility } from 'domains/visibility/actions'
5
5
  import { visibilityStates } from 'domains/visibility/constants'
@@ -14,9 +14,6 @@ export const visibilitySlice = createSlice({
14
14
  name: 'visibility',
15
15
  initialState,
16
16
  reducers: {
17
- setFromStorage: (state, action) => {
18
- state.visibility = action.payload
19
- },
20
17
  setShowInlineView: (state) => {
21
18
  state.showInlineView = true
22
19
  },
@@ -40,7 +37,6 @@ export const visibilitySlice = createSlice({
40
37
  },
41
38
  })
42
39
 
43
- export const { setFromStorage } = visibilitySlice.actions
44
40
  export const { setShowInlineView } = visibilitySlice.actions
45
41
 
46
42
  export default visibilitySlice.reducer
@@ -5,14 +5,6 @@ import initializeExternalApi from './lib/external-api/initialize-api'
5
5
  export default initializeExternalApi
6
6
 
7
7
  // Used by: StyleGuide
8
- export { API } from './api'
9
-
10
- // Used by: StyleGuide
11
- export { Provider as SeamlyStoreProvider } from 'react-redux'
12
- // Used by: StyleGuide
13
- export { default as SeamlyGeneralError } from './api/errors/seamly-general-error'
14
- // Used by: StyleGuide
15
- export { default as SeamlyOfflineError } from './api/errors/seamly-offline-error'
16
8
  // Used by: Client
17
9
  export { useConfig as useSeamlyConfig } from 'domains/config/hooks'
18
10
  // Used by: Client
@@ -23,18 +15,12 @@ export {
23
15
  useTranslations,
24
16
  useTranslationsContainer,
25
17
  } from 'domains/translations/hooks'
18
+ export { visibilityStates } from 'domains/visibility/constants'
26
19
  // Used by: Client
27
20
  export { useVisibility as useSeamlyVisibility } from 'domains/visibility/hooks'
28
- export { visibilityStates } from 'domains/visibility/constants'
29
21
  export { calculateVisibility } from 'domains/visibility/utils'
30
- // Used by: Client
31
- export { className } from './lib/css'
32
22
  // Used by: StyleGuide
33
- export { default as Engine } from './lib/engine'
34
- // Used by: StyleGuide
35
- export { default as ExternalApi } from './lib/external-api'
36
- // Used by: StyleGuide
37
- export { randomId } from './lib/id'
23
+ export { Provider as SeamlyStoreProvider } from 'react-redux'
38
24
  // Used by: StyleGuide
39
25
  export { default as ComponentFilter } from 'ui/components/conversation/component-filter'
40
26
  // Used by: Client
@@ -74,6 +60,23 @@ export { default as Interrupt } from 'ui/components/layout/interrupt'
74
60
  export { default as View } from 'ui/components/view'
75
61
  // Used by: Client
76
62
  export { default as DeprecatedView } from 'ui/components/view/deprecated-view'
63
+ export { useSeamlyActions } from 'ui/hooks/use-seamly-actions'
64
+ // Used by: StyleGuide
65
+ // Used by: Client
66
+ export { eventTypes } from 'ui/utils/seamly-utils'
67
+ export { API } from './api'
68
+ // Used by: StyleGuide
69
+ export { default as SeamlyGeneralError } from './api/errors/seamly-general-error'
70
+ // Used by: StyleGuide
71
+ export { default as SeamlyOfflineError } from './api/errors/seamly-offline-error'
72
+ // Used by: Client
73
+ export { className } from './lib/css'
74
+ // Used by: StyleGuide
75
+ export { default as Engine } from './lib/engine'
76
+ // Used by: StyleGuide
77
+ export { default as ExternalApi } from './lib/external-api'
78
+ // Used by: StyleGuide
79
+ export { randomId } from './lib/id'
77
80
  // Used by: Client
78
81
  export {
79
82
  useEvents,
@@ -85,8 +88,3 @@ export {
85
88
  useSeamlyMessageContainerClassNames,
86
89
  useSeamlyOptions,
87
90
  } from './ui/hooks/seamly-hooks'
88
- // Used by: StyleGuide
89
- export { getUrlParams, getUrlSearchString } from './ui/utils/general-utils'
90
- // Used by: Client
91
- export { eventTypes } from './ui/utils/seamly-utils'
92
- export { useSeamlyActions } from 'ui/hooks/use-seamly-actions'
@@ -1,19 +1,33 @@
1
1
  import { API } from 'api'
2
+ import type { Config } from 'config.types'
3
+ import Events, { Events as EventsType } from 'minivents'
4
+ import { render } from 'preact'
5
+ import ChatApp from 'ui/components/chat-app'
6
+ import SeamlyCore from 'ui/components/core/seamly-core'
2
7
  import { initializeApp } from 'domains/app/actions'
3
-
4
8
  import { initializeConfig } from 'domains/config/actions'
5
9
  import { setConfig } from 'domains/config/slice'
6
10
  import { setLocale } from 'domains/i18n/actions'
7
-
8
11
  import { createStore } from 'domains/store'
9
12
  import { initializeVisibility } from 'domains/visibility/actions'
10
- import Events from 'minivents'
11
- import { render } from 'preact'
12
- import ChatApp from 'ui/components/chat-app'
13
- import SeamlyCore from 'ui/components/core/seamly-core'
13
+ import type ExternalApi from 'lib/external-api'
14
14
 
15
15
  export default class Engine {
16
- constructor(config, externalApi) {
16
+ config: Config
17
+
18
+ namespace: string
19
+
20
+ parentElement: HTMLElement
21
+
22
+ externalApi: ExternalApi
23
+
24
+ functions: {}
25
+
26
+ eventBus: EventsType
27
+
28
+ api: API
29
+
30
+ constructor(config: Config, externalApi: ExternalApi) {
17
31
  const { namespace = '', parentElement, showFaq, ...restConfig } = config
18
32
 
19
33
  this.config = {
@@ -32,6 +46,8 @@ export default class Engine {
32
46
  context: config.context,
33
47
  })
34
48
 
49
+ // Following are ignored because the types of minivents do not match the docs.
50
+ // @ts-ignore
35
51
  this.eventBus = new Events()
36
52
  this.functions = {}
37
53
  this.registerFunctions({
@@ -39,9 +55,11 @@ export default class Engine {
39
55
  off: this.eventBus.off,
40
56
  })
41
57
 
58
+ // @ts-ignore
42
59
  this.eventBus.on('function.register', (functionName, fn) =>
43
60
  this.registerFunction(functionName, fn),
44
61
  )
62
+ // @ts-ignore
45
63
  this.eventBus.on('function.unregister', (functionName, fn) =>
46
64
  this.unregisterFunction(functionName, fn),
47
65
  )
@@ -0,0 +1,112 @@
1
+ export const getUrlSearchParams = () => {
2
+ const params = new URLSearchParams(window.location.search)
3
+
4
+ return Array.from(params.entries()).reduce(
5
+ (acc, [key, val]) => ({ ...acc, [key]: val }),
6
+ {},
7
+ )
8
+ }
9
+
10
+ // Return search parameters as a string
11
+ export const getUrlSearchString = (params: Record<string, string>) =>
12
+ new URLSearchParams(params).toString()
13
+
14
+ // Return search parameters found in URL or sessionStorage
15
+ export const getSearchParamsByKeys = <T extends string>(
16
+ ...keys: T[]
17
+ ): Record<T, string> => {
18
+ const url = new URL(window.location.href)
19
+
20
+ const values = keys.reduce((acc, key) => {
21
+ const searchParam = url.searchParams.get(key)
22
+ const param = searchParam || sessionStorage.getItem(key)
23
+ if (param) {
24
+ acc[key] = param
25
+ }
26
+ return acc
27
+ }, {} as Record<T, string>)
28
+
29
+ return values
30
+ }
31
+
32
+ // Adds a search parameter to the url
33
+ export const setSearchParam = (key: string, value: string): void => {
34
+ const url = new URL(window.location.href)
35
+ const { searchParams } = url
36
+
37
+ searchParams.set(key, value)
38
+
39
+ url.search = searchParams.toString()
40
+ window.location.href = url.toString()
41
+ }
42
+
43
+ // Replace search parameters with those found in URL search parameters or sessionStorage
44
+ export const replaceSearchParams = <T extends string>(
45
+ ...keys: T[]
46
+ ): Record<T, string> => {
47
+ const url = new URL(window.location.href)
48
+
49
+ const params = keys.reduce<URLSearchParams>((acc, key) => {
50
+ const paramValue = url.searchParams.get(key)
51
+
52
+ const value = paramValue || sessionStorage.getItem(key)
53
+
54
+ // If the query parameter is present, add it to the sessionStorage
55
+ if (paramValue) {
56
+ sessionStorage.setItem(key, paramValue)
57
+ }
58
+
59
+ // Bail if there is nothing to set
60
+ if (!value) return acc
61
+
62
+ acc.set(key, value)
63
+ return acc
64
+ }, new URLSearchParams())
65
+
66
+ // To keep other params in place, we merge them with the current URL parameters
67
+ const combinedParams = new URLSearchParams({
68
+ ...Object.fromEntries(url.searchParams),
69
+ ...Object.fromEntries(params),
70
+ })
71
+
72
+ if (combinedParams.toString()) {
73
+ window.history.replaceState(null, null, `?${combinedParams}`)
74
+ }
75
+
76
+ return getSearchParamsByKeys(...params.keys())
77
+ }
78
+
79
+ // Sets up click handlers for elements with data-attribute `data-reset-search-params`.
80
+ // Clicking these will remove all given keys from the sessionStorage and remove the search parameters from the url
81
+
82
+ // If you want to reset both 'apiKey' and 'locale' when the event is executed, you would use it as followed
83
+ // import { initResetSearchParams } from 'lib/url-helpers'
84
+
85
+ // Setup the event handlers for the search parameters you want to be reset after execution.
86
+ // initResetSearchParams('apiKey', 'locale')
87
+
88
+ // Somewhere in the HTML add an element with the 'data-reset-search-params' attribute.
89
+ // <a href="#" data-reset-search-params>Reset account and source locale</a>
90
+ export const initResetSearchParams = <T extends string>(...keys: T[]): void => {
91
+ const resetLink = document.querySelector('[data-reset-search-params]')
92
+
93
+ if (!resetLink) {
94
+ return
95
+ }
96
+
97
+ resetLink.addEventListener('click', (e) => {
98
+ e.preventDefault()
99
+ const url = new URL(window.location.href)
100
+
101
+ keys.forEach((key) => {
102
+ // Clear current session for each key
103
+ sessionStorage.removeItem(key)
104
+
105
+ // Delete key from url
106
+ url.searchParams.delete(key)
107
+ })
108
+
109
+ // Reload page
110
+ window.location.href = url.toString()
111
+ })
112
+ }
@@ -4,7 +4,10 @@ export {
4
4
  Engine,
5
5
  eventTypes,
6
6
  ExternalApi,
7
- getUrlParams,
8
- getUrlSearchString,
9
7
  randomId,
10
8
  } from '@seamly/web-ui'
9
+ export {
10
+ getSearchParamsByKeys,
11
+ initResetSearchParams,
12
+ replaceSearchParams,
13
+ } from 'lib/url-helpers'