@seamly/web-ui 20.3.0 → 20.5.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 (48) hide show
  1. package/build/dist/lib/index.debug.js +61 -94
  2. package/build/dist/lib/index.debug.min.js +1 -1
  3. package/build/dist/lib/index.debug.min.js.LICENSE.txt +4 -16
  4. package/build/dist/lib/index.js +467 -3965
  5. package/build/dist/lib/index.min.js +1 -1
  6. package/build/dist/lib/index.min.js.LICENSE.txt +0 -5
  7. package/build/dist/lib/standalone.js +432 -3865
  8. package/build/dist/lib/standalone.min.js +1 -1
  9. package/build/dist/lib/style-guide.js +267 -191
  10. package/build/dist/lib/style-guide.min.js +1 -1
  11. package/build/dist/lib/styles.css +1 -1
  12. package/package.json +2 -4
  13. package/src/.DS_Store +0 -0
  14. package/src/javascripts/api/index.js +17 -1
  15. package/src/javascripts/domains/config/reducer.js +2 -0
  16. package/src/javascripts/domains/forms/provider.js +14 -6
  17. package/src/javascripts/domains/store/state-reducer.js +1 -0
  18. package/src/javascripts/domains/visibility/actions.js +2 -0
  19. package/src/javascripts/domains/visibility/hooks.js +60 -1
  20. package/src/javascripts/domains/visibility/reducer.js +5 -0
  21. package/src/javascripts/domains/visibility/selectors.js +5 -0
  22. package/src/javascripts/domains/visibility/utils.js +5 -1
  23. package/src/javascripts/index.js +1 -0
  24. package/src/javascripts/style-guide/components/app.js +2 -0
  25. package/src/javascripts/style-guide/states.js +55 -50
  26. package/src/javascripts/ui/components/conversation/conversation.js +9 -10
  27. package/src/javascripts/ui/components/conversation/event/card-component.js +1 -2
  28. package/src/javascripts/ui/components/conversation/event/conversation-suggestions.js +19 -9
  29. package/src/javascripts/ui/components/conversation/event/cta.js +1 -2
  30. package/src/javascripts/ui/components/conversation/event/image.js +11 -3
  31. package/src/javascripts/ui/components/conversation/event/participant.js +2 -11
  32. package/src/javascripts/ui/components/conversation/event/splash.js +1 -3
  33. package/src/javascripts/ui/components/conversation/event/text.js +9 -9
  34. package/src/javascripts/ui/components/entry/text-entry/text-entry-form.js +6 -0
  35. package/src/javascripts/ui/components/layout/chat.js +52 -48
  36. package/src/javascripts/ui/components/suggestions/suggestions-list.js +12 -14
  37. package/src/javascripts/ui/components/view/deprecated-view.js +16 -11
  38. package/src/javascripts/ui/components/view/index.js +2 -2
  39. package/src/javascripts/ui/components/view/inline-view.js +13 -8
  40. package/src/javascripts/ui/hooks/seamly-entry-hooks.js +3 -2
  41. package/src/javascripts/ui/hooks/seamly-state-hooks.js +7 -3
  42. package/src/javascripts/ui/hooks/use-seamly-chat.js +41 -29
  43. package/src/javascripts/ui/hooks/use-seamly-commands.js +16 -4
  44. package/src/javascripts/ui/utils/seamly-utils.js +24 -7
  45. package/src/stylesheets/5-components/_message-count.scss +5 -2
  46. package/CHANGELOG.md +0 -729
  47. package/src/javascripts/lib/parse-body.js +0 -10
  48. package/src/javascripts/ui/components/conversation/event/hooks/use-text-rendering.js +0 -35
@@ -1,3 +1,4 @@
1
+ import { forwardRef } from 'preact/compat'
1
2
  import { className } from '../../../lib/css'
2
3
  import {
3
4
  useSeamlyAppContainerClassNames,
@@ -9,54 +10,57 @@ import { useI18n } from '../../../domains/i18n'
9
10
  import { useVisibility, visibilityStates } from '../../../domains/visibility'
10
11
  import Suggestions from '../suggestions'
11
12
 
12
- const Chat = ({ children, className: givenClassName = '' }) => {
13
- const { isOpen, isVisible, setVisibility } = useVisibility()
14
- const { namespace, layoutMode } = useConfig()
15
- const { isInline } = useSeamlyLayoutMode()
16
- const appContainerClassNames = useSeamlyAppContainerClassNames()
17
- const userResponded = useUserHasResponded()
18
- const { t } = useI18n()
19
-
20
- const defaultClassNames = [
21
- 'chat',
22
- `chat--layout-${layoutMode}`,
23
- `namespace--${namespace}`,
24
- ]
25
-
26
- const classNames = [
27
- ...defaultClassNames,
28
- ...appContainerClassNames,
29
- givenClassName,
30
- ]
31
-
32
- if (!isOpen && layoutMode !== 'app') {
33
- classNames.push('chat--collapsed')
34
- }
35
-
36
- if (userResponded) {
37
- classNames.push('chat--user-responded')
38
- }
39
-
40
- const onKeyDownHandler = (e) => {
41
- if ((e.code && e.code === 'Escape') || e.keyCode === 27)
42
- if (!isInline && isOpen) {
43
- setVisibility(visibilityStates.minimized)
44
- }
45
- }
46
-
47
- return (
48
- isVisible && (
49
- <section
50
- className={className(classNames)}
51
- onKeyDown={onKeyDownHandler}
52
- tabIndex="-1"
53
- aria-label={t('chat.srLabel')}
54
- >
55
- <div className={className('chat-wrapper')}>{children}</div>
56
- {layoutMode === 'inline' && isOpen && <Suggestions isAside={true} />}
57
- </section>
13
+ const Chat = forwardRef(
14
+ ({ children, className: givenClassName = '' }, forwardedRef) => {
15
+ const { isOpen, isVisible, setVisibility } = useVisibility()
16
+ const { namespace, layoutMode } = useConfig()
17
+ const { isInline } = useSeamlyLayoutMode()
18
+ const appContainerClassNames = useSeamlyAppContainerClassNames()
19
+ const userResponded = useUserHasResponded()
20
+ const { t } = useI18n()
21
+
22
+ const defaultClassNames = [
23
+ 'chat',
24
+ `chat--layout-${layoutMode}`,
25
+ `namespace--${namespace}`,
26
+ ]
27
+
28
+ const classNames = [
29
+ ...defaultClassNames,
30
+ ...appContainerClassNames,
31
+ givenClassName,
32
+ ]
33
+
34
+ if (!isOpen && layoutMode !== 'app') {
35
+ classNames.push('chat--collapsed')
36
+ }
37
+
38
+ if (userResponded) {
39
+ classNames.push('chat--user-responded')
40
+ }
41
+
42
+ const onKeyDownHandler = (e) => {
43
+ if ((e.code && e.code === 'Escape') || e.keyCode === 27)
44
+ if (!isInline && isOpen) {
45
+ setVisibility(visibilityStates.minimized)
46
+ }
47
+ }
48
+
49
+ return (
50
+ isVisible && (
51
+ <section
52
+ className={className(classNames)}
53
+ onKeyDown={onKeyDownHandler}
54
+ tabIndex="-1"
55
+ ref={forwardedRef}
56
+ aria-label={t('chat.srLabel')}
57
+ >
58
+ <div className={className('chat-wrapper')}>{children}</div>
59
+ {layoutMode === 'inline' && isOpen && <Suggestions isAside={true} />}
60
+ </section>
61
+ )
58
62
  )
59
- )
60
- }
63
+ },
64
+ )
61
65
 
62
66
  export default Chat
@@ -6,19 +6,17 @@ const SuggestionsList = ({
6
6
  suggestions = [],
7
7
  onClickSuggestion,
8
8
  hasIcon = true,
9
- }) => {
10
- return (
11
- <ul className={className('suggestions__list', givenClassName)}>
12
- {suggestions.map((suggestion) => (
13
- <SuggestionsItem
14
- hasIcon={hasIcon}
15
- key={suggestion.id}
16
- onClick={onClickSuggestion}
17
- {...suggestion}
18
- />
19
- ))}
20
- </ul>
21
- )
22
- }
9
+ }) => (
10
+ <ul className={className('suggestions__list', givenClassName)}>
11
+ {suggestions.map((suggestion) => (
12
+ <SuggestionsItem
13
+ hasIcon={hasIcon}
14
+ key={suggestion.id}
15
+ onClick={onClickSuggestion}
16
+ {...suggestion}
17
+ />
18
+ ))}
19
+ </ul>
20
+ )
23
21
 
24
22
  export default SuggestionsList
@@ -1,3 +1,4 @@
1
+ import { useVisibility, useShowInlineView } from 'domains/visibility'
1
2
  import DeprecatedAppFrame from '../layout/deprecated-app-frame'
2
3
  import ChatFrame from '../layout/chat-frame'
3
4
  import AgentInfo from '../layout/agent-info'
@@ -6,25 +7,29 @@ import Conversation from '../conversation/conversation'
6
7
  import EntryContainer from '../entry/entry-container'
7
8
  import Interrupt from '../layout/interrupt'
8
9
  import { useSeamlyChat } from '../../hooks/seamly-hooks'
9
- import { useVisibility } from '../../../domains/visibility'
10
10
  import DeprecatedToggleButton from '../entry/deprecated-toggle-button'
11
11
 
12
12
  const DeprecatedView = () => {
13
13
  const { isVisible } = useVisibility()
14
14
  const { openChat, closeChat } = useSeamlyChat()
15
+ const { showInlineView, containerRef } = useShowInlineView()
15
16
 
16
17
  return (
17
18
  isVisible && (
18
- <DeprecatedAppFrame>
19
- <DeprecatedToggleButton onOpenChat={openChat} />
20
- <Header onCloseChat={closeChat}>
21
- <AgentInfo />
22
- </Header>
23
- <ChatFrame interruptComponent={Interrupt}>
24
- <Conversation />
25
- <EntryContainer />
26
- </ChatFrame>
27
- </DeprecatedAppFrame>
19
+ <div ref={containerRef}>
20
+ {showInlineView && (
21
+ <DeprecatedAppFrame>
22
+ <DeprecatedToggleButton onOpenChat={openChat} />
23
+ <Header onCloseChat={closeChat}>
24
+ <AgentInfo />
25
+ </Header>
26
+ <ChatFrame interruptComponent={Interrupt}>
27
+ <Conversation />
28
+ <EntryContainer />
29
+ </ChatFrame>
30
+ </DeprecatedAppFrame>
31
+ )}
32
+ </div>
28
33
  )
29
34
  )
30
35
  }
@@ -16,7 +16,7 @@ const ViewComponentsMap = {
16
16
  window: WindowView,
17
17
  }
18
18
 
19
- const View = () => {
19
+ const View = ({ children }) => {
20
20
  const [, setSeamlyContainerElement] = useSeamlyContainerElement()
21
21
  const { namespace, layoutMode, zIndex } = useConfig()
22
22
  const { isOpen, isVisible } = useVisibility()
@@ -74,7 +74,7 @@ const View = () => {
74
74
  style={{ zIndex }}
75
75
  ref={containerElementRef}
76
76
  >
77
- <ViewComponent />
77
+ {children || <ViewComponent />}
78
78
  </div>
79
79
  )
80
80
  )
@@ -1,20 +1,22 @@
1
- import { className } from '../../../lib/css'
1
+ import { className } from 'lib/css'
2
+ import { useVisibility, useShowInlineView } from 'domains/visibility'
3
+ import { useInterrupt } from 'domains/interrupt'
2
4
  import Chat from '../layout/chat'
3
5
  import Interrupt from '../layout/interrupt'
4
6
  import Conversation from '../conversation/conversation'
5
7
  import EntryContainer from '../entry/entry-container'
6
8
  import ChatFrame from '../layout/chat-frame'
7
9
  import useSeamlyChat from '../../hooks/use-seamly-chat'
8
- import { useVisibility } from '../../../domains/visibility'
9
10
  import PreChatMessages from '../layout/pre-chat-messages'
10
11
  import Suggestions from '../suggestions'
11
12
  import InOutTransition, {
12
13
  transitionStartStates,
13
14
  } from '../widgets/in-out-transition'
14
- import { useInterrupt } from '../../../domains/interrupt'
15
15
 
16
16
  const InlineView = () => {
17
17
  useSeamlyChat()
18
+ const { showInlineView, containerRef } = useShowInlineView()
19
+
18
20
  const { isOpen } = useVisibility()
19
21
  const { hasInterrupt, meta } = useInterrupt()
20
22
 
@@ -29,6 +31,7 @@ const InlineView = () => {
29
31
  transitionStartState={transitionStartStates.rendered}
30
32
  >
31
33
  <div
34
+ ref={containerRef}
32
35
  className={className(
33
36
  'unstarted-wrapper',
34
37
  'unstarted-wrapper--inline',
@@ -42,11 +45,13 @@ const InlineView = () => {
42
45
  isActive={isOpen}
43
46
  transitionStartState={transitionStartStates.rendered}
44
47
  >
45
- <Chat>
46
- <ChatFrame interruptComponent={Interrupt}>
47
- {isOpen && <Conversation />}
48
- <EntryContainer />
49
- </ChatFrame>
48
+ <Chat ref={containerRef}>
49
+ {showInlineView && (
50
+ <ChatFrame interruptComponent={Interrupt}>
51
+ {isOpen && <Conversation />}
52
+ <EntryContainer />
53
+ </ChatFrame>
54
+ )}
50
55
  </Chat>
51
56
  </InOutTransition>
52
57
  </>
@@ -80,12 +80,13 @@ export const useSeamlyEntry = () => {
80
80
  active,
81
81
  userSelected,
82
82
  options: entryOptions,
83
+ optionsOverride: entryOptionsOverride,
83
84
  } = useSeamlyStateContext().entryMeta
84
85
  const dispatch = useSeamlyDispatchContext()
85
86
 
86
87
  const activeEntry = userSelected || active || defaultEntry
87
-
88
- const activeEntryOptions = entryOptions[activeEntry] || {}
88
+ const activeEntryOptions =
89
+ entryOptionsOverride[activeEntry] || entryOptions[activeEntry] || {}
89
90
 
90
91
  const setBlockAutoEntrySwitch = useCallback(
91
92
  (value) => {
@@ -71,6 +71,9 @@ export const useSeamlyHeaderData = () => useSeamlyStateContext().headerTitles
71
71
 
72
72
  export const useSeamlyUnreadCount = () => useSeamlyStateContext().unreadEvents
73
73
 
74
+ export const useLoadedImageEventIds = () =>
75
+ useSeamlyStateContext().loadedImageEventIds
76
+
74
77
  export const useSkiplink = () => useSeamlyStateContext().skiplinkTargetId
75
78
 
76
79
  export const useSeamlyParticipant = (participantId) =>
@@ -100,14 +103,15 @@ export const useEntryTextLimit = () => {
100
103
  const {
101
104
  entryMeta: {
102
105
  options: { text },
106
+ optionsOverride: { text: overrideText },
103
107
  },
104
108
  } = useSeamlyStateContext()
105
109
 
106
- const { limit } = text || {}
110
+ const { limit } = overrideText || text || { limit: null }
107
111
 
108
112
  return {
109
- hasLimit: limit != null,
110
- limit: limit != null ? limit : null,
113
+ hasLimit: limit !== null,
114
+ limit: limit !== null ? limit : null,
111
115
  }
112
116
  }
113
117
 
@@ -1,29 +1,31 @@
1
- import { useEffect, useRef } from 'preact/hooks'
1
+ import { useCallback, useEffect, useRef } from 'preact/hooks'
2
2
  import { useI18n } from 'domains/i18n'
3
3
  import { seamlyActions } from 'ui/utils/seamly-utils'
4
4
  import { useVisibility, visibilityStates } from 'domains/visibility'
5
5
  import useSeamlyDispatchContext from './use-seamly-dispatch'
6
- import { useEvents } from './seamly-state-hooks'
6
+ import { useEvents, useSeamlyLayoutMode } from './seamly-state-hooks'
7
7
  import useSeamlyCommands from './use-seamly-commands'
8
8
  import { useSeamlyHasConversation } from './seamly-api-hooks'
9
9
  import { useLiveRegion } from './live-region-hooks'
10
- import { useConfig } from '../../domains/config'
10
+ import { useSelector } from '../../domains/redux/hooks'
11
+ import { selectShowInlineView } from '../../domains/visibility/selectors'
11
12
 
12
13
  const { SET_IS_LOADING } = seamlyActions
13
14
 
14
15
  const useSeamlyChat = () => {
15
16
  const { t } = useI18n()
16
- const { layoutMode } = useConfig()
17
+ const { isInline, isWindow } = useSeamlyLayoutMode()
17
18
  const { isOpen, isVisible, setVisibility } = useVisibility()
19
+ const showInlineView = useSelector(selectShowInlineView)
18
20
  const dispatch = useSeamlyDispatchContext()
19
21
  const events = useEvents()
20
22
  const spinnerTimeout = useRef(null)
21
- const { start, connect, apiConfigReady } = useSeamlyCommands()
23
+ const { start, connect, apiConfigReady, apiConnected } = useSeamlyCommands()
22
24
  const hasConversation = useSeamlyHasConversation()
23
25
  const prevIsOpen = useRef(null)
24
26
  const prevIsVisible = useRef(null)
27
+ const startCalled = useRef(false)
25
28
  const { sendAssertive } = useLiveRegion()
26
- const connectCalled = useRef(false)
27
29
 
28
30
  const hasEvents = events.length > 0
29
31
 
@@ -76,28 +78,41 @@ const useSeamlyChat = () => {
76
78
  }, [hasEvents, dispatch])
77
79
 
78
80
  useEffect(() => {
79
- // This is needed to reset the ref to allow connect to happen again.
81
+ // This is needed to reset the ref to allow connect and start to happen again.
80
82
  // Mostly due to Interrupt situations and a reset being called.
81
- if (!hasConversation || !apiConfigReady) {
82
- connectCalled.current = false
83
+ if (!apiConfigReady || !apiConnected) {
84
+ startCalled.current = false
83
85
  }
84
- }, [hasConversation, apiConfigReady])
86
+ }, [apiConfigReady, apiConnected])
85
87
 
86
- useEffect(() => {
87
- // We don't connect minimised or hidden window interfaces unless
88
- // they had been connected before.
89
- // We also keep track of whether connect was called before to avoid
90
- // multiple in-flight connection processes.
88
+ const connectAndStart = useCallback(async () => {
89
+ // We don't connect if we are already connected to the api to avoid multiple in-flight connection processes.
90
+ if (!apiConnected) {
91
+ await connect()
92
+ }
93
+
94
+ // We only start a conversation when the chat interface is either 'open' or if using the inline view if it's 'open' or 'minimized'.
95
+ if (isOpen || (isVisible && isInline)) {
96
+ start()
97
+ startCalled.current = true
98
+ }
99
+ }, [apiConnected, connect, isInline, isOpen, isVisible, start])
91
100
 
101
+ useEffect(() => {
102
+ // We dont't connect or start when the apiConfig is not ready yet.
103
+ // We also keep track of whether start has been called to avoid multiple in-flight connection processes.
104
+ // We check if the window view is not open and no conversation is started yet.
105
+ // Lastly we check if the inline view is not scrolled in to view.
92
106
  if (
93
- (layoutMode === 'window' && !isOpen && !hasConversation) ||
94
- connectCalled.current ||
95
- !apiConfigReady
107
+ !apiConfigReady ||
108
+ startCalled.current ||
109
+ (isWindow && !isOpen && !hasConversation) ||
110
+ (isInline && !showInlineView)
96
111
  ) {
97
112
  return
98
113
  }
99
114
 
100
- if (hasConversation) {
115
+ if (hasConversation && isOpen) {
101
116
  // We deactivate the extra startup loading spinner when a conversation is available
102
117
  // We also stop setting the loading indicator in the first place to avoid a flash.
103
118
  clearTimeout(spinnerTimeout.current)
@@ -106,19 +121,16 @@ const useSeamlyChat = () => {
106
121
  isLoading: false,
107
122
  })
108
123
  }
109
- connect().then(() => {
110
- start()
111
- })
112
-
113
- connectCalled.current = true
124
+ connectAndStart()
114
125
  }, [
115
- isOpen,
116
- hasConversation,
117
126
  apiConfigReady,
118
- start,
119
- connect,
127
+ connectAndStart,
120
128
  dispatch,
121
- layoutMode,
129
+ hasConversation,
130
+ isInline,
131
+ isOpen,
132
+ isWindow,
133
+ showInlineView,
122
134
  ])
123
135
 
124
136
  const openChat = () => {
@@ -7,7 +7,7 @@ import { Actions as InterruptActions } from 'domains/interrupt'
7
7
  import { useConfig } from 'domains/config'
8
8
  import * as AppActions from 'domains/app/actions'
9
9
  import { useUserHasResponded } from 'domains/app/hooks'
10
- import { useVisibility } from 'domains/visibility'
10
+ import { useVisibility, visibilityStates } from 'domains/visibility'
11
11
  import { useStableCallback } from './utility-hooks'
12
12
  import useSeamlyDispatchContext from './use-seamly-dispatch'
13
13
  import { useSeamlyUnreadCount } from './seamly-state-hooks'
@@ -26,7 +26,7 @@ const useSeamlyCommands = () => {
26
26
 
27
27
  const hasResponded = useUserHasResponded()
28
28
  const hasConversation = useSeamlyHasConversation()
29
- const { visible: visibility } = useVisibility()
29
+ const { visible: visibility, setVisibility } = useVisibility()
30
30
  const unreadMessageCount = useSeamlyUnreadCount()
31
31
 
32
32
  const emitEvent = useCallback(
@@ -44,6 +44,7 @@ const useSeamlyCommands = () => {
44
44
  hasResponded,
45
45
  unreadMessageCount,
46
46
  })
47
+
47
48
  api.send('start')
48
49
  emitEvent('ui.start', {
49
50
  visibility,
@@ -107,7 +108,13 @@ const useSeamlyCommands = () => {
107
108
  emitEvent('message', message)
108
109
  dispatch({
109
110
  type: ADD_EVENT,
110
- event: { type: 'message', payload: message },
111
+ event: {
112
+ type: 'message',
113
+ payload: {
114
+ ...message,
115
+ optimisticallyInjected: true,
116
+ },
117
+ },
111
118
  })
112
119
  },
113
120
  [api, dispatch, emitEvent, getTextMessageBase],
@@ -213,12 +220,16 @@ const useSeamlyCommands = () => {
213
220
  .then((initialState) => {
214
221
  if (initialState) {
215
222
  dispatch({ type: SET_INITIAL_STATE, initialState })
223
+ if (initialState.userResponded) {
224
+ dispatch(AppActions.setHasResponded(initialState.userResponded))
225
+ setVisibility(visibilityStates.open)
226
+ }
216
227
  }
217
228
  })
218
229
  .catch((error) => {
219
230
  dispatch(InterruptActions.set(error))
220
231
  })
221
- }, [api, dispatch])
232
+ }, [api, dispatch, setVisibility])
222
233
 
223
234
  return {
224
235
  connect,
@@ -232,6 +243,7 @@ const useSeamlyCommands = () => {
232
243
  addMessageBubble,
233
244
  addUploadBubble,
234
245
  addDivider,
246
+ apiConnected: api.connected,
235
247
  apiConfigReady: api.configReady,
236
248
  }
237
249
  }
@@ -73,6 +73,7 @@ export const seamlyActions = {
73
73
  CLEAR_EVENTS: 'CLEAR_EVENTS',
74
74
  SET_HISTORY: 'SET_HISTORY',
75
75
  SET_EVENTS_READ: 'SET_EVENTS_READ',
76
+ SET_LOADED_IMAGE_EVENT_IDS: 'SET_LOADED_IMAGE_EVENT_IDS',
76
77
  ACK_EVENT: 'ACK_EVENT',
77
78
  SET_IS_LOADING: 'SET_IS_LOADING',
78
79
  CLEAR_PARTICIPANTS: 'CLEAR_PARTICIPANTS',
@@ -121,6 +122,7 @@ const {
121
122
  CLEAR_EVENTS,
122
123
  SET_HISTORY,
123
124
  SET_EVENTS_READ,
125
+ SET_LOADED_IMAGE_EVENT_IDS,
124
126
  ACK_EVENT,
125
127
  SET_IS_LOADING,
126
128
  CLEAR_PARTICIPANTS,
@@ -171,13 +173,18 @@ const orderHistory = (events) => {
171
173
  }
172
174
 
173
175
  export const mergeHistory = (stateEvents, historyEvents) => {
176
+ const newStateEvents = stateEvents.filter(
177
+ (stateEvent) =>
178
+ // Deduplicate the event streams, giving events in historyEvents
179
+ // precedence so the server is able to push changes to events.
180
+ !historyEvents.some(
181
+ (historyEvent) => historyEvent.payload.id === stateEvent.payload.id,
182
+ ),
183
+ )
184
+
174
185
  const newHistoryEvents = historyEvents
175
186
  .filter(
176
187
  (historyEvent) =>
177
- // Deduplicate the event streams
178
- !stateEvents.find(
179
- (stateEvent) => stateEvent.payload.id === historyEvent.payload.id,
180
- ) &&
181
188
  // Remove all non displayable participant messages
182
189
  !(
183
190
  historyEvent.type === 'participant' &&
@@ -190,7 +197,7 @@ export const mergeHistory = (stateEvents, historyEvents) => {
190
197
  // the normal merging logic there is no added effect.
191
198
  .reverse()
192
199
 
193
- return orderHistory([...newHistoryEvents, ...stateEvents])
200
+ return orderHistory([...newHistoryEvents, ...newStateEvents])
194
201
  }
195
202
 
196
203
  const participantReducer = (state, action) => {
@@ -384,7 +391,7 @@ export const seamlyStateReducer = (state, action) => {
384
391
  : state
385
392
 
386
393
  case CLEAR_EVENTS:
387
- return { ...state, unreadEvents: 0, events: [] }
394
+ return { ...state, unreadEvents: 0, loadedImageEventIds: [], events: [] }
388
395
  case SET_EVENTS_READ:
389
396
  return {
390
397
  ...state,
@@ -404,6 +411,11 @@ export const seamlyStateReducer = (state, action) => {
404
411
  return event
405
412
  }),
406
413
  }
414
+ case SET_LOADED_IMAGE_EVENT_IDS:
415
+ return {
416
+ ...state,
417
+ loadedImageEventIds: [...state.loadedImageEventIds, action.eventId],
418
+ }
407
419
  case SET_HISTORY:
408
420
  const {
409
421
  events: history,
@@ -606,7 +618,12 @@ export const seamlyStateReducer = (state, action) => {
606
618
  headerTitles: headerTitlesReducer(state.headerTitles, action),
607
619
  }
608
620
  case SET_INITIAL_STATE:
609
- return { ...state, initialState: action.initialState }
621
+ const { initialState } = action
622
+ return {
623
+ ...state,
624
+ initialState,
625
+ unreadEvents: initialState.unreadMessageCount,
626
+ }
610
627
  case SET_SERVICE_DATA_ITEM:
611
628
  return {
612
629
  ...state,
@@ -10,8 +10,10 @@
10
10
  height: $messagecountsize;
11
11
  transform: scale(1);
12
12
  transform-origin: 50% 50%;
13
- transition: transform 0.3s 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
13
+ transition: transform 0.3s 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275),
14
+ opacity $transition;
14
15
  border-radius: 50%;
16
+ opacity: 1;
15
17
  background-color: $negative;
16
18
  color: $white;
17
19
  font-size: $fontsize-small;
@@ -20,6 +22,7 @@
20
22
 
21
23
  &:empty {
22
24
  transform: scale(0);
23
- transition: transform $transition;
25
+ transition: transform $transition, opacity $transition;
26
+ opacity: 0;
24
27
  }
25
28
  }