@seamly/web-ui 19.0.0 → 19.1.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 (81) hide show
  1. package/package.json +1 -1
  2. package/src/javascripts/api/errors/seamly-base-error.js +10 -0
  3. package/src/javascripts/api/errors/seamly-configuration-error.js +4 -6
  4. package/src/javascripts/api/errors/seamly-general-error.js +4 -6
  5. package/src/javascripts/api/errors/seamly-offline-error.js +4 -6
  6. package/src/javascripts/api/errors/seamly-session-expired-error.js +4 -6
  7. package/src/javascripts/api/errors/seamly-unauthorized-error.js +4 -6
  8. package/src/javascripts/api/errors/seamly-unavailable-error.js +17 -0
  9. package/src/javascripts/api/index.js +10 -11
  10. package/src/javascripts/domains/app/actions.js +27 -17
  11. package/src/javascripts/domains/app/index.js +2 -1
  12. package/src/javascripts/domains/config/reducer.js +1 -0
  13. package/src/javascripts/domains/config/selectors.js +1 -1
  14. package/src/javascripts/domains/errors/index.js +32 -0
  15. package/src/javascripts/domains/i18n/reducer.js +6 -0
  16. package/src/javascripts/domains/interrupt/middleware.js +2 -0
  17. package/src/javascripts/domains/interrupt/reducer.js +10 -9
  18. package/src/javascripts/domains/store/index.js +6 -1
  19. package/src/javascripts/domains/store/state-reducer.js +10 -6
  20. package/src/javascripts/domains/visibility/actions.js +73 -0
  21. package/src/javascripts/domains/visibility/constants.js +8 -0
  22. package/src/javascripts/domains/visibility/hooks.js +24 -0
  23. package/src/javascripts/domains/visibility/index.js +8 -0
  24. package/src/javascripts/domains/visibility/reducer.js +19 -0
  25. package/src/javascripts/domains/visibility/selectors.js +9 -0
  26. package/src/javascripts/domains/visibility/utils.js +42 -0
  27. package/src/javascripts/index.js +3 -12
  28. package/src/javascripts/lib/engine/index.js +1 -0
  29. package/src/javascripts/lib/redux-helpers/index.js +1 -5
  30. package/src/javascripts/style-guide/components/app.js +1 -1
  31. package/src/javascripts/style-guide/components/static-core.js +5 -0
  32. package/src/javascripts/style-guide/states.js +48 -21
  33. package/src/javascripts/ui/components/conversation/conversation.js +2 -2
  34. package/src/javascripts/ui/components/conversation/event/hooks/use-text-rendering.js +1 -8
  35. package/src/javascripts/ui/components/conversation/event/text.js +19 -13
  36. package/src/javascripts/ui/components/core/seamly-core.js +3 -0
  37. package/src/javascripts/ui/components/core/seamly-event-subscriber.js +3 -3
  38. package/src/javascripts/ui/components/core/seamly-initializer.js +2 -6
  39. package/src/javascripts/ui/components/core/seamly-instance-functions-loader.js +2 -3
  40. package/src/javascripts/ui/components/core/seamly-new-notifications.js +2 -2
  41. package/src/javascripts/ui/components/core/seamly-read-state.js +2 -2
  42. package/src/javascripts/ui/components/entry/toggle-button.js +2 -2
  43. package/src/javascripts/ui/components/layout/agent-info.js +2 -2
  44. package/src/javascripts/ui/components/layout/app-frame.js +2 -3
  45. package/src/javascripts/ui/components/layout/chat-frame.js +2 -2
  46. package/src/javascripts/ui/components/layout/modal-wrapper.js +3 -6
  47. package/src/javascripts/ui/components/layout/view.js +3 -6
  48. package/src/javascripts/ui/hooks/seamly-hooks.js +0 -2
  49. package/src/javascripts/ui/hooks/use-seamly-chat.js +3 -5
  50. package/src/javascripts/ui/hooks/use-seamly-commands.js +7 -29
  51. package/src/javascripts/ui/hooks/use-seamly-idle-detach-countdown.js +2 -2
  52. package/src/javascripts/ui/utils/general-utils.js +0 -9
  53. package/src/javascripts/ui/utils/seamly-utils.js +0 -66
  54. package/build/dist/lib/components.js +0 -62
  55. package/build/dist/lib/components.min.js +0 -1
  56. package/build/dist/lib/config.js +0 -51
  57. package/build/dist/lib/config.min.js +0 -1
  58. package/build/dist/lib/contexts.js +0 -53
  59. package/build/dist/lib/contexts.min.js +0 -1
  60. package/build/dist/lib/hooks.js +0 -64
  61. package/build/dist/lib/hooks.min.js +0 -1
  62. package/build/dist/lib/index.debug.js +0 -3113
  63. package/build/dist/lib/index.debug.min.js +0 -2
  64. package/build/dist/lib/index.debug.min.js.LICENSE.txt +0 -1099
  65. package/build/dist/lib/index.js +0 -25907
  66. package/build/dist/lib/index.min.js +0 -2
  67. package/build/dist/lib/index.min.js.LICENSE.txt +0 -14
  68. package/build/dist/lib/standalone.js +0 -34806
  69. package/build/dist/lib/standalone.min.js +0 -2
  70. package/build/dist/lib/standalone.min.js.LICENSE.txt +0 -9
  71. package/build/dist/lib/storage.js +0 -275
  72. package/build/dist/lib/storage.min.js +0 -2
  73. package/build/dist/lib/storage.min.js.LICENSE.txt +0 -1
  74. package/build/dist/lib/style-guide.js +0 -8097
  75. package/build/dist/lib/style-guide.min.js +0 -1
  76. package/build/dist/lib/styles.css +0 -1
  77. package/build/dist/lib/styles.js +0 -1
  78. package/build/dist/lib/utils.js +0 -59
  79. package/build/dist/lib/utils.min.js +0 -1
  80. package/src/javascripts/ui/hooks/use-seamly-stored-visibility.js +0 -31
  81. package/src/javascripts/ui/hooks/use-seamly-visibility.js +0 -98
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seamly/web-ui",
3
- "version": "19.0.0",
3
+ "version": "19.1.0",
4
4
  "main": "build/dist/lib/index.js",
5
5
  "module": "",
6
6
  "exports": {
@@ -0,0 +1,10 @@
1
+ export default class SeamlyBaseError extends Error {
2
+ constructor(originalError, ...params) {
3
+ super(...params)
4
+
5
+ if (Error.captureStackTrace) {
6
+ Error.captureStackTrace(this, Object.getPrototypeOf(this))
7
+ }
8
+ this.originalError = originalError
9
+ }
10
+ }
@@ -1,10 +1,8 @@
1
- export default class SeamlyConfigurationError extends Error {
2
- constructor(params) {
3
- super(params)
1
+ import SeamlyBaseError from './seamly-base-error'
4
2
 
5
- if (Error.captureStackTrace) {
6
- Error.captureStackTrace(this, SeamlyConfigurationError)
7
- }
3
+ export default class SeamlyConfigurationError extends SeamlyBaseError {
4
+ constructor(originalError, ...params) {
5
+ super(originalError, ...params)
8
6
 
9
7
  this.name = 'SeamlyConfigurationError'
10
8
  this.langKey = 'errors.configError'
@@ -1,10 +1,8 @@
1
- export default class SeamlyGeneralError extends Error {
2
- constructor(params) {
3
- super(params)
1
+ import SeamlyBaseError from './seamly-base-error'
4
2
 
5
- if (Error.captureStackTrace) {
6
- Error.captureStackTrace(this, SeamlyGeneralError)
7
- }
3
+ export default class SeamlyGeneralError extends SeamlyBaseError {
4
+ constructor(originalError, ...params) {
5
+ super(originalError, ...params)
8
6
 
9
7
  this.name = 'SeamlyGeneralError'
10
8
  this.langKey = 'errors.general'
@@ -1,10 +1,8 @@
1
- export default class SeamlyOfflineError extends Error {
2
- constructor(params) {
3
- super(params)
1
+ import SeamlyBaseError from './seamly-base-error'
4
2
 
5
- if (Error.captureStackTrace) {
6
- Error.captureStackTrace(this, SeamlyOfflineError)
7
- }
3
+ export default class SeamlyOfflineError extends SeamlyBaseError {
4
+ constructor(originalError, ...params) {
5
+ super(originalError, ...params)
8
6
 
9
7
  this.name = 'SeamlyOfflineError'
10
8
  this.langKey = 'errors.seamlyOffline'
@@ -1,10 +1,8 @@
1
- export default class SeamlySessionExpiredError extends Error {
2
- constructor(params) {
3
- super(params)
1
+ import SeamlyBaseError from './seamly-base-error'
4
2
 
5
- if (Error.captureStackTrace) {
6
- Error.captureStackTrace(this, SeamlySessionExpiredError)
7
- }
3
+ export default class SeamlySessionExpiredError extends SeamlyBaseError {
4
+ constructor(originalError, ...params) {
5
+ super(originalError, ...params)
8
6
 
9
7
  this.name = 'SeamlySessionExpiredError'
10
8
  this.action = 'reset'
@@ -1,10 +1,8 @@
1
- export default class SeamlyUnauthorizedError extends Error {
2
- constructor(params) {
3
- super(params)
1
+ import SeamlyBaseError from './seamly-base-error'
4
2
 
5
- if (Error.captureStackTrace) {
6
- Error.captureStackTrace(this, SeamlyUnauthorizedError)
7
- }
3
+ export default class SeamlyUnauthorizedError extends SeamlyBaseError {
4
+ constructor(originalError, ...params) {
5
+ super(originalError, ...params)
8
6
 
9
7
  this.name = 'SeamlyUnauthorizedError'
10
8
  this.langKey = 'errors.general'
@@ -0,0 +1,17 @@
1
+ /**
2
+ * This error is used to alert the user that there's a problem with the connection
3
+ * when initialising the application because of a connection issue on either the server
4
+ * or the client side.
5
+ */
6
+ export default class SeamlyUnavailableError extends Error {
7
+ constructor(params) {
8
+ super(params)
9
+
10
+ if (Error.captureStackTrace) {
11
+ Error.captureStackTrace(this, SeamlyUnavailableError)
12
+ }
13
+
14
+ this.name = 'SeamlyUnavailableError'
15
+ this.langKey = 'errors.seamlyUnavailable'
16
+ }
17
+ }
@@ -195,7 +195,6 @@ export class API {
195
195
  // cookies in CORS requests.
196
196
  .withCredentials()
197
197
  .send({
198
- authorizationRequired: true,
199
198
  externalId: this.externalId || undefined,
200
199
  })
201
200
 
@@ -211,7 +210,7 @@ export class API {
211
210
  return initialState
212
211
  } catch (error) {
213
212
  if (error.status >= 500) {
214
- throw new SeamlyGeneralError()
213
+ throw new SeamlyGeneralError(error)
215
214
  }
216
215
  throw error
217
216
  }
@@ -260,13 +259,13 @@ export class API {
260
259
  }
261
260
  } catch (error) {
262
261
  if (error.status === 401) {
263
- throw new SeamlyUnauthorizedError()
262
+ throw new SeamlyUnauthorizedError(error)
264
263
  }
265
264
  if (error.status === 404) {
266
- throw new SeamlySessionExpiredError()
265
+ throw new SeamlySessionExpiredError(error)
267
266
  }
268
267
  if (error.status >= 500) {
269
- throw new SeamlyGeneralError()
268
+ throw new SeamlyGeneralError(error)
270
269
  }
271
270
  throw error
272
271
  }
@@ -353,10 +352,10 @@ export class API {
353
352
  })
354
353
  .catch((error) => {
355
354
  if (error.status === 404) {
356
- throw new SeamlyConfigurationError()
355
+ throw new SeamlyConfigurationError(error)
357
356
  }
358
357
  if (error.status >= 500) {
359
- throw new SeamlyGeneralError()
358
+ throw new SeamlyGeneralError(error)
360
359
  }
361
360
  throw error
362
361
  })
@@ -373,13 +372,13 @@ export class API {
373
372
  })
374
373
  .catch((error) => {
375
374
  if (error.status === 401) {
376
- throw new SeamlyUnauthorizedError()
375
+ throw new SeamlyUnauthorizedError(error)
377
376
  }
378
377
  if (error.status === 404) {
379
- throw new SeamlySessionExpiredError()
378
+ throw new SeamlySessionExpiredError(error)
380
379
  }
381
380
  if (error.status >= 500) {
382
- throw new SeamlyGeneralError()
381
+ throw new SeamlyGeneralError(error)
383
382
  }
384
383
  throw error
385
384
  })
@@ -402,7 +401,7 @@ export class API {
402
401
  return body.translations
403
402
  } catch (error) {
404
403
  if (error.status >= 500) {
405
- throw new SeamlyGeneralError()
404
+ throw new SeamlyGeneralError(error)
406
405
  }
407
406
  throw error
408
407
  }
@@ -1,8 +1,10 @@
1
1
  import { seamlyActions } from '../../ui/utils/seamly-utils'
2
2
  import { Actions as ConfigActions } from '../config'
3
- import { Actions as InterruptActions } from '../interrupt'
4
3
  import { Actions as I18nActions } from '../i18n'
4
+ import * as VisibilityActions from '../visibility/actions'
5
5
  import { createAction, createThunk } from './utils'
6
+ import SeamlyUnavailableError from '../../api/errors/seamly-unavailable-error'
7
+ import SeamlySessionExpiredError from '../../api/errors/seamly-session-expired-error'
6
8
 
7
9
  export const setHasResponded = createAction(
8
10
  'setHasResponded',
@@ -12,31 +14,39 @@ export const setHasResponded = createAction(
12
14
  export const initialize = createThunk(
13
15
  'initialize',
14
16
  async (config, { dispatch, extra: { api } }) => {
15
- try {
16
- dispatch(ConfigActions.initialize(config))
17
+ dispatch(ConfigActions.initialize(config))
18
+ let locale = config?.context?.locale
17
19
 
20
+ try {
18
21
  const { features, defaultLocale } = await api.getConfig()
19
22
  dispatch({ type: seamlyActions.SET_FEATURES, features })
20
-
21
- let locale = config?.context?.locale || defaultLocale
23
+ locale = locale || defaultLocale
22
24
  dispatch(I18nActions.setInitialLocale(locale))
23
- try {
24
- if (api.hasConversation()) {
25
- const initialState = await api.getConversationIntitialState()
26
- dispatch({ type: seamlyActions.SET_INITIAL_STATE, initialState })
25
+ } catch (e) {
26
+ throw new SeamlyUnavailableError()
27
+ }
27
28
 
28
- locale = initialState.translation?.locale || locale
29
+ try {
30
+ if (api.hasConversation()) {
31
+ const initialState = await api.getConversationIntitialState()
32
+ dispatch({ type: seamlyActions.SET_INITIAL_STATE, initialState })
33
+
34
+ locale = initialState.translation?.locale || locale
29
35
 
30
- if ('userResponded' in initialState) {
31
- dispatch(setHasResponded(initialState.userResponded))
32
- }
36
+ if ('userResponded' in initialState) {
37
+ dispatch(setHasResponded(initialState.userResponded))
33
38
  }
34
- } catch (error) {
35
- dispatch(InterruptActions.set(error))
36
39
  }
40
+ } catch (e) {
41
+ if (e instanceof SeamlySessionExpiredError) {
42
+ throw e
43
+ }
44
+ throw new SeamlyUnavailableError()
45
+ } finally {
37
46
  await dispatch(I18nActions.setLocale(locale))
38
- } catch (error) {
39
- dispatch(InterruptActions.set(error))
47
+ dispatch(VisibilityActions.initialize())
40
48
  }
41
49
  },
42
50
  )
51
+
52
+ export const reset = createAction('reset', () => {})
@@ -1,6 +1,7 @@
1
1
  import * as Actions from './actions'
2
+ import * as Selectors from './selectors'
2
3
 
3
4
  export * from './hooks'
4
5
  export { default as Reducer } from './reducer'
5
6
 
6
- export { Actions }
7
+ export { Actions, Selectors }
@@ -27,6 +27,7 @@ const configKeys = [
27
27
  'messages',
28
28
  'visible',
29
29
  'visibilityCallback',
30
+ 'errorCallback',
30
31
  ]
31
32
  const updateState = (state, { config }) => {
32
33
  const { messages, ...partialConfig } = pick(config, configKeys)
@@ -1,5 +1,5 @@
1
1
  import { createSelector } from 'reselect'
2
- import { visibilityStates } from '../../ui/utils/seamly-utils'
2
+ import { visibilityStates } from '../visibility/constants'
3
3
  import { selectState } from './utils'
4
4
 
5
5
  export const selectConfig = createSelector(selectState, (config) => {
@@ -0,0 +1,32 @@
1
+ import { createDomain } from '../../lib/redux-helpers'
2
+ import { Selectors as ConfigSelectors } from '../config'
3
+
4
+ const { createAction } = createDomain('errors')
5
+
6
+ export const catchError = createAction('catch-error', (error) => ({ error }))
7
+
8
+ export function createMiddleware({ api: seamlyApi }) {
9
+ return ({ getState }) => {
10
+ const handleError = (error) => {
11
+ const { errorCallback, namespace, api, layoutMode } =
12
+ ConfigSelectors.selectConfig(getState())
13
+ errorCallback?.(error, {
14
+ namespace,
15
+ api,
16
+ layoutMode,
17
+ conversationUrl: seamlyApi.getConversationUrl(),
18
+ })
19
+ }
20
+ return (next) => (action) => {
21
+ try {
22
+ if (action.error) {
23
+ handleError(action.error)
24
+ }
25
+ return next(action)
26
+ } catch (error) {
27
+ handleError(error)
28
+ throw error
29
+ }
30
+ }
31
+ }
32
+ }
@@ -18,6 +18,12 @@ const defaultState = {
18
18
  'errors.seamlyOffline.srText':
19
19
  'The chat has connection issues. There might be a problem with your or our network connection. The chat session should resume as soon as the connection is available again.',
20
20
  'errors.seamlyOffline.title': 'Connection issues',
21
+ 'errors.seamlyUnavailable.buttonText': 'Try again',
22
+ 'errors.seamlyUnavailable.message':
23
+ 'The server could not be reached. Try again in a little while.',
24
+ 'errors.seamlyUnavailable.srText':
25
+ 'The chat server could not be reached. Try again in a little while.',
26
+ 'errors.seamlyUnavailable.title': 'Server unavailable',
21
27
  },
22
28
  isLoading: false,
23
29
  initialLocale: undefined,
@@ -3,6 +3,7 @@ import SeamlyConfigurationError from '../../api/errors/seamly-configuration-erro
3
3
  import SeamlySessionExpiredError from '../../api/errors/seamly-session-expired-error'
4
4
  import SeamlyOfflineError from '../../api/errors/seamly-offline-error'
5
5
  import SeamlyUnauthorizedError from '../../api/errors/seamly-unauthorized-error'
6
+ import SeamlyUnavailableError from '../../api/errors/seamly-unavailable-error'
6
7
 
7
8
  const handledErrorTypes = [
8
9
  SeamlyGeneralError,
@@ -10,6 +11,7 @@ const handledErrorTypes = [
10
11
  SeamlySessionExpiredError,
11
12
  SeamlyOfflineError,
12
13
  SeamlyUnauthorizedError,
14
+ SeamlyUnavailableError,
13
15
  ]
14
16
 
15
17
  export default function createMiddleware({ api }) {
@@ -1,21 +1,22 @@
1
1
  import { createReducer } from './utils'
2
2
  import * as Actions from './actions'
3
+ import { Actions as AppActions } from '../app'
3
4
 
4
5
  const initialState = {
5
6
  error: undefined,
6
7
  }
7
8
 
9
+ const handleError = (state, { error }) => ({
10
+ ...state,
11
+ error,
12
+ })
13
+
8
14
  export default createReducer(
9
15
  {
10
- [Actions.set]: (state, { error }) => {
11
- return {
12
- ...state,
13
- error,
14
- }
15
- },
16
- [Actions.clear]: () => {
17
- return initialState
18
- },
16
+ [Actions.set]: handleError,
17
+ [AppActions.initialize.rejected]: handleError,
18
+ [Actions.clear]: () => initialState,
19
+ [AppActions.reset]: () => initialState,
19
20
  },
20
21
  initialState,
21
22
  )
@@ -11,14 +11,16 @@ import {
11
11
  createMiddleware as createI18nMiddleware,
12
12
  } from '../translations'
13
13
  import { Reducer as i18nReducer } from '../i18n'
14
+ import { Reducer as visibilityReducer } from '../visibility'
14
15
  import {
15
16
  Reducer as interruptReducer,
16
17
  createMiddleware as createInterruptMiddleware,
17
18
  } from '../interrupt'
18
19
  import { createMiddleware as createOptionsMiddleware } from '../options'
20
+ import { createMiddleware as createErrorsMiddleware } from '../errors'
19
21
  import stateReducer from './state-reducer'
20
22
 
21
- export function createStore({ initialState, api } = {}) {
23
+ export function createStore({ initialState, api, eventBus } = {}) {
22
24
  const store = createReduxStore({
23
25
  reducers: {
24
26
  state: stateReducer,
@@ -28,11 +30,14 @@ export function createStore({ initialState, api } = {}) {
28
30
  [String(translationsReducer)]: translationsReducer,
29
31
  [String(i18nReducer)]: i18nReducer,
30
32
  [String(interruptReducer)]: interruptReducer,
33
+ [String(visibilityReducer)]: visibilityReducer,
31
34
  },
32
35
  initialState,
33
36
  middlewares: [
37
+ createErrorsMiddleware({ api }),
34
38
  thunkMiddleware.withExtraArgument({
35
39
  api,
40
+ eventBus,
36
41
  }),
37
42
  createConfigMiddleware(),
38
43
  createInterruptMiddleware({ api }),
@@ -1,11 +1,8 @@
1
1
  // Legacy state reducer. Do not add new features here but extract/create new reducers as needed
2
2
 
3
3
  import { randomId } from '../../lib/id'
4
- import {
5
- entryTypes,
6
- seamlyStateReducer,
7
- visibilityStates,
8
- } from '../../ui/utils/seamly-utils'
4
+ import { entryTypes, seamlyStateReducer } from '../../ui/utils/seamly-utils'
5
+ import { Actions as AppActions } from '../app'
9
6
 
10
7
  const initialState = {
11
8
  events: [],
@@ -14,7 +11,6 @@ const initialState = {
14
11
  isLoading: false,
15
12
  idleDetachCountdown: { hasCountdown: false, isActive: false },
16
13
  resumeConversationPrompt: false,
17
- visible: visibilityStates.hidden,
18
14
  serviceInfo: {
19
15
  activeServiceSessionId: '',
20
16
  },
@@ -52,5 +48,13 @@ const initialState = {
52
48
  }
53
49
 
54
50
  export default function stateReducer(state = initialState, action) {
51
+ if (action.type === String(AppActions.reset)) {
52
+ const { visible } = state
53
+ return {
54
+ ...initialState,
55
+ visible,
56
+ }
57
+ }
58
+
55
59
  return seamlyStateReducer(state, action)
56
60
  }
@@ -0,0 +1,73 @@
1
+ import { Selectors as ConfigSelectors } from '../config'
2
+ import * as AppSelectors from '../app/selectors'
3
+
4
+ import { visibilityStates, StoreKey } from './constants'
5
+ import { createAction, createThunk, calculateVisibility } from './utils'
6
+ import * as Selectors from './selectors'
7
+ import { selectState } from '../../ui/hooks/seamly-state-hooks'
8
+
9
+ export const setFromStorage = createAction('setFromStorage', (visibility) => ({
10
+ visibility,
11
+ }))
12
+
13
+ const validVisibilityStates = [
14
+ visibilityStates.open,
15
+ visibilityStates.minimized,
16
+ visibilityStates.hidden,
17
+ ]
18
+ export const setVisibility = createThunk(
19
+ 'set',
20
+ (requestedVisibility, { getState, extra: { api, eventBus } }) => {
21
+ const state = getState()
22
+ const previousVisibility = Selectors.selectVisibility(state)
23
+ const hasResponded = AppSelectors.selectUserHasResponded(state)
24
+ const hasConversation = api.hasConversation()
25
+ const config = ConfigSelectors.selectConfig(state)
26
+ const { visibilityCallback = calculateVisibility, layoutMode } = config
27
+ const { unreadEvents: unreadMessageCount } = selectState(state)
28
+
29
+ const calculatedVisibility = visibilityCallback({
30
+ hasConversation,
31
+ hasResponded,
32
+ previousVisibility,
33
+ requestedVisibility,
34
+ config,
35
+ })
36
+ if (!validVisibilityStates.includes(calculatedVisibility)) {
37
+ console.error(
38
+ 'The visibilityCallback function should return "open", "minimized" or "hidden".',
39
+ )
40
+ return undefined
41
+ }
42
+ if (previousVisibility === calculatedVisibility) {
43
+ return undefined
44
+ }
45
+ // Store the user-requested visibility in order to reinitialize after refresh
46
+ api.store.set(StoreKey, {
47
+ ...(api.store.get(StoreKey) || {}),
48
+ [layoutMode]: requestedVisibility,
49
+ })
50
+ if (requestedVisibility) {
51
+ eventBus.emit('ui.visible', requestedVisibility, {
52
+ visibility: requestedVisibility,
53
+ hasConversation,
54
+ hasResponded,
55
+ unreadMessageCount,
56
+ })
57
+ }
58
+ return calculatedVisibility
59
+ },
60
+ )
61
+
62
+ export const initialize = createThunk(
63
+ 'initialize',
64
+ async (locale, { dispatch, getState, extra: { api } }) => {
65
+ // initialize stored visibility
66
+ const { layoutMode } = ConfigSelectors.selectConfig(getState())
67
+ const storedVisibility = api.store.get(StoreKey)?.[layoutMode]
68
+ if (storedVisibility) {
69
+ dispatch(setFromStorage(storedVisibility))
70
+ }
71
+ dispatch(setVisibility(visibilityStates.initialize))
72
+ },
73
+ )
@@ -0,0 +1,8 @@
1
+ export const StoreKey = 'visibility'
2
+
3
+ export const visibilityStates = {
4
+ hidden: 'hidden',
5
+ minimized: 'minimized',
6
+ open: 'open',
7
+ initialize: null,
8
+ }
@@ -0,0 +1,24 @@
1
+ import { useCallback } from 'preact/hooks'
2
+ import { useConfig } from '../config'
3
+ import { useSelector, useStoreDispatch } from '../redux'
4
+ import * as Actions from './actions'
5
+ import * as Selectors from './selectors'
6
+ import { visibilityStates } from './constants'
7
+
8
+ export const useVisibility = () => {
9
+ const dispatch = useStoreDispatch()
10
+ const visible = useSelector(Selectors.selectVisibility)
11
+ const isVisible = visible ? visible !== visibilityStates.hidden : false
12
+ const { layoutMode } = useConfig()
13
+ const isOpen =
14
+ visible && layoutMode
15
+ ? visible === visibilityStates.open ||
16
+ (layoutMode === 'inline' && visible !== visibilityStates.hidden)
17
+ : false
18
+ const setVisibility = useCallback(
19
+ (visibility) => dispatch(Actions.setVisibility(visibility)),
20
+ [dispatch],
21
+ )
22
+
23
+ return { isVisible, isOpen, visible, setVisibility }
24
+ }
@@ -0,0 +1,8 @@
1
+ import * as Actions from './actions'
2
+ import * as Selectors from './selectors'
3
+
4
+ export * from './hooks'
5
+ export { visibilityStates } from './constants'
6
+ export { default as Reducer } from './reducer'
7
+ export { calculateVisibility } from './utils'
8
+ export { Actions, Selectors }
@@ -0,0 +1,19 @@
1
+ import { createReducer } from './utils'
2
+ import * as Actions from './actions'
3
+ import { visibilityStates } from './constants'
4
+
5
+ const initialState = {
6
+ visibility: visibilityStates.initialize,
7
+ }
8
+
9
+ export default createReducer(
10
+ {
11
+ [Actions.setFromStorage]: (state, { visibility }) => ({
12
+ ...state,
13
+ visibility,
14
+ }),
15
+ [Actions.setVisibility.fulfilled]: (state, { payload: visibility }) =>
16
+ visibility ? { ...state, visibility } : state,
17
+ },
18
+ initialState,
19
+ )
@@ -0,0 +1,9 @@
1
+ import { createSelector } from 'reselect'
2
+ import { selectState } from './utils'
3
+
4
+ export const selectVisibility = createSelector(
5
+ selectState,
6
+ (state) => state.visibility,
7
+ )
8
+
9
+ export { selectState }
@@ -0,0 +1,42 @@
1
+ import { createDomain } from '../../lib/redux-helpers'
2
+ import { visibilityStates } from './constants'
3
+
4
+ export const {
5
+ createAction,
6
+ createActions,
7
+ createThunk,
8
+ createReducer,
9
+ selectState,
10
+ } = createDomain('visibility')
11
+
12
+ export const calculateVisibility = ({
13
+ hasResponded,
14
+ previousVisibility,
15
+ requestedVisibility,
16
+ config,
17
+ }) => {
18
+ const { defaults, layoutMode, hideOnNoUserResponse } = config
19
+
20
+ const { visible: defaultVisibility } = defaults || {}
21
+
22
+ // Requesting open should override the responded check.
23
+ if (
24
+ layoutMode === 'window' &&
25
+ hideOnNoUserResponse &&
26
+ requestedVisibility !== visibilityStates.open
27
+ ) {
28
+ return hasResponded
29
+ ? requestedVisibility || previousVisibility || visibilityStates.open
30
+ : visibilityStates.hidden
31
+ }
32
+
33
+ const baseVisibility =
34
+ layoutMode === 'inline' ? visibilityStates.open : visibilityStates.minimized
35
+
36
+ return (
37
+ requestedVisibility ||
38
+ previousVisibility ||
39
+ defaultVisibility ||
40
+ baseVisibility
41
+ )
42
+ }