@seamly/web-ui 21.0.7 → 22.0.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.
- package/build/dist/lib/components.js +9354 -7909
- package/build/dist/lib/components.js.map +1 -0
- package/build/dist/lib/components.min.js +2 -1
- package/build/dist/lib/components.min.js.LICENSE.txt +2 -2
- package/build/dist/lib/components.min.js.map +1 -0
- package/build/dist/lib/config.js +2 -1
- package/build/dist/lib/config.js.map +1 -0
- package/build/dist/lib/config.min.js +2 -1
- package/build/dist/lib/config.min.js.map +1 -0
- package/build/dist/lib/contexts.js +2 -1
- package/build/dist/lib/contexts.js.map +1 -0
- package/build/dist/lib/contexts.min.js +2 -1
- package/build/dist/lib/contexts.min.js.map +1 -0
- package/build/dist/lib/deprecated-view.css +1 -1
- package/build/dist/lib/deprecated-view.js +1 -1
- package/build/dist/lib/hooks.js +7006 -5903
- package/build/dist/lib/hooks.js.map +1 -0
- package/build/dist/lib/hooks.min.js +2 -1
- package/build/dist/lib/hooks.min.js.map +1 -0
- package/build/dist/lib/index.debug.js +965 -384
- package/build/dist/lib/index.debug.js.map +1 -0
- package/build/dist/lib/index.debug.min.js +2 -1
- package/build/dist/lib/index.debug.min.js.LICENSE.txt +336 -108
- package/build/dist/lib/index.debug.min.js.map +1 -0
- package/build/dist/lib/index.js +2991 -5664
- package/build/dist/lib/index.js.map +1 -0
- package/build/dist/lib/index.min.js +2 -1
- package/build/dist/lib/index.min.js.LICENSE.txt +2 -2
- package/build/dist/lib/index.min.js.map +1 -0
- package/build/dist/lib/sounds/beep.mp3 +0 -0
- package/build/dist/lib/standalone.js +9461 -12461
- package/build/dist/lib/standalone.js.map +1 -0
- package/build/dist/lib/standalone.min.js +2 -1
- package/build/dist/lib/standalone.min.js.LICENSE.txt +1 -1
- package/build/dist/lib/standalone.min.js.map +1 -0
- package/build/dist/lib/storage.js +2 -1
- package/build/dist/lib/storage.js.map +1 -0
- package/build/dist/lib/storage.min.js +2 -1
- package/build/dist/lib/storage.min.js.map +1 -0
- package/build/dist/lib/style-guide.js +1831 -6023
- package/build/dist/lib/style-guide.js.map +1 -0
- package/build/dist/lib/style-guide.min.js +2 -1
- package/build/dist/lib/style-guide.min.js.LICENSE.txt +2 -2
- package/build/dist/lib/style-guide.min.js.map +1 -0
- package/build/dist/lib/styles-default-implementation.css +1 -1
- package/build/dist/lib/styles-default-implementation.js +1 -1
- package/build/dist/lib/styles.css +1 -1
- package/build/dist/lib/styles.js +1 -1
- package/build/dist/lib/utils.js +11598 -14588
- package/build/dist/lib/utils.js.map +1 -0
- package/build/dist/lib/utils.min.js +2 -1
- package/build/dist/lib/utils.min.js.LICENSE.txt +1 -6
- package/build/dist/lib/utils.min.js.map +1 -0
- package/package.json +58 -48
- package/src/javascripts/api/conversation-connector.ts +2 -0
- package/src/javascripts/api/errors/seamly-api-error.ts +15 -0
- package/src/javascripts/api/index.ts +168 -94
- package/src/javascripts/config.ts +1 -1
- package/src/javascripts/config.types.ts +18 -11
- package/src/javascripts/domains/config/selectors.ts +1 -1
- package/src/javascripts/domains/config/slice.ts +12 -0
- package/src/javascripts/domains/forms/forms.types.ts +1 -0
- package/src/javascripts/domains/forms/hooks.ts +10 -2
- package/src/javascripts/domains/i18n/slice.ts +2 -0
- package/src/javascripts/domains/interrupt/hooks.ts +15 -7
- package/src/javascripts/domains/interrupt/middleware.ts +7 -14
- package/src/javascripts/domains/interrupt/selectors.ts +4 -0
- package/src/javascripts/domains/interrupt/slice.ts +2 -2
- package/src/javascripts/domains/store/selectors.ts +23 -10
- package/src/javascripts/domains/store/slice.ts +63 -11
- package/src/javascripts/domains/store/store.types.ts +39 -1
- package/src/javascripts/domains/translations/components/options-button.tsx +1 -4
- package/src/javascripts/domains/translations/components/translation-status.tsx +4 -3
- package/src/javascripts/domains/translations/hooks.ts +11 -4
- package/src/javascripts/domains/translations/slice.ts +2 -0
- package/src/javascripts/index.ts +2 -0
- package/src/javascripts/lib/url-helpers.ts +24 -0
- package/src/javascripts/schema.ts +10 -0
- package/src/javascripts/style-guide/states.js +65 -0
- package/src/javascripts/ui/components/app-options/index.js +4 -3
- package/src/javascripts/ui/components/conversation/conversation.tsx +2 -0
- package/src/javascripts/ui/components/conversation/event/chat-scroll/chat-scroll-provider.tsx +2 -0
- package/src/javascripts/ui/components/conversation/event/choice-prompt.js +1 -1
- package/src/javascripts/ui/components/conversation/event/text.js +1 -1
- package/src/javascripts/ui/components/conversation/event/upload.js +50 -9
- package/src/javascripts/ui/components/conversation/use-chat-scroll.ts +3 -2
- package/src/javascripts/ui/components/core/seamly-event-subscriber.ts +16 -14
- package/src/javascripts/ui/components/core/seamly-file-upload.tsx +156 -0
- package/src/javascripts/ui/components/core/seamly-instance-functions-loader.js +5 -5
- package/src/javascripts/ui/components/entry/abort-transaction-button/abort-transaction-button.tsx +45 -0
- package/src/javascripts/ui/components/entry/deprecated-toggle-button.js +4 -3
- package/src/javascripts/ui/components/entry/text-entry/hooks.ts +108 -0
- package/src/javascripts/ui/components/entry/text-entry/index.js +7 -4
- package/src/javascripts/ui/components/entry/text-entry/{text-entry-form.js → text-entry-form.tsx} +8 -22
- package/src/javascripts/ui/components/faq/faq.js +5 -4
- package/src/javascripts/ui/components/form-controls/{input.js → input.tsx} +13 -2
- package/src/javascripts/ui/components/form-controls/{wrapper.js → wrapper.tsx} +8 -4
- package/src/javascripts/ui/components/layout/agent-info.js +4 -3
- package/src/javascripts/ui/components/layout/chat-frame.js +7 -8
- package/src/javascripts/ui/components/layout/deprecated-chat-frame.js +7 -8
- package/src/javascripts/ui/components/layout/interrupt.js +6 -15
- package/src/javascripts/ui/components/layout/pre-chat-messages.js +4 -3
- package/src/javascripts/ui/components/suggestions/index.js +5 -4
- package/src/javascripts/ui/components/translation-chat-status/index.tsx +4 -3
- package/src/javascripts/ui/components/view/app-view.js +1 -2
- package/src/javascripts/ui/components/view/deprecated-view.js +1 -2
- package/src/javascripts/ui/components/view/{index.js → index.tsx} +53 -4
- package/src/javascripts/ui/components/view/inline-view.js +1 -11
- package/src/javascripts/ui/components/view/window-view/{index.js → index.tsx} +15 -11
- package/src/javascripts/ui/components/view/window-view/window-open-button.js +4 -3
- package/src/javascripts/ui/components/widgets/{in-out-transition.js → in-out-transition.tsx} +67 -28
- package/src/javascripts/ui/hooks/sounds/beep.mp3 +0 -0
- package/src/javascripts/ui/hooks/use-click-outside.ts +5 -3
- package/src/javascripts/ui/hooks/use-notifications.ts +114 -0
- package/src/javascripts/ui/hooks/{use-seamly-chat.js → use-seamly-chat.ts} +5 -1
- package/src/javascripts/ui/hooks/use-session-expired-command.ts +17 -0
- package/src/javascripts/ui/hooks/use-timeout.ts +20 -0
- package/src/stylesheets/3-chat/_chat.scss +3 -5
- package/src/stylesheets/4-base/_formelements.scss +0 -36
- package/src/stylesheets/5-components/_abort-transaction.scss +10 -0
- package/src/stylesheets/5-components/_buttons.scss +18 -3
- package/src/stylesheets/5-components/_character-limit.scss +2 -2
- package/src/stylesheets/5-components/_chat-status.scss +26 -37
- package/src/stylesheets/5-components/_choice-prompt.scss +9 -10
- package/src/stylesheets/5-components/_conversation.scss +9 -62
- package/src/stylesheets/5-components/_disclaimer.scss +11 -3
- package/src/stylesheets/5-components/_error.scss +3 -2
- package/src/stylesheets/5-components/_idle.scss +3 -8
- package/src/stylesheets/5-components/_input.scss +34 -13
- package/src/stylesheets/5-components/_interrupt.scss +3 -10
- package/src/stylesheets/5-components/_loader.scss +1 -2
- package/src/stylesheets/5-components/_message-author.scss +2 -4
- package/src/stylesheets/5-components/_message-body.scss +33 -10
- package/src/stylesheets/5-components/_message-card.scss +2 -10
- package/src/stylesheets/5-components/_message-carousel.scss +4 -4
- package/src/stylesheets/5-components/_message-cta.scss +0 -6
- package/src/stylesheets/5-components/_message.scss +1 -0
- package/src/stylesheets/5-components/_modal.scss +2 -5
- package/src/stylesheets/5-components/_options.scss +17 -22
- package/src/stylesheets/5-components/_pre-chat-messages.scss +3 -1
- package/src/stylesheets/5-components/_prompt.scss +3 -7
- package/src/stylesheets/5-components/_skip-link.scss +2 -1
- package/src/stylesheets/5-components/_suggestions.scss +2 -2
- package/src/stylesheets/5-components/_translation-options.scss +5 -2
- package/src/stylesheets/5-components/_unread-messages.scss +33 -0
- package/src/stylesheets/5-components/_upload.scss +20 -27
- package/src/stylesheets/6-default-implementation/_hover.scss +14 -17
- package/src/stylesheets/7-deprecated/1-settings/_config.scss +17 -0
- package/src/stylesheets/7-deprecated/3-app/_app.scss +2 -1
- package/src/stylesheets/7-deprecated/5-components/_card.scss +1 -0
- package/src/stylesheets/7-deprecated/5-components/_chat-status.scss +66 -20
- package/src/stylesheets/7-deprecated/5-components/_conversation.scss +1 -4
- package/src/stylesheets/7-deprecated/5-components/_input.scss +6 -1
- package/src/stylesheets/7-deprecated/5-components/_interrupt.scss +1 -4
- package/src/stylesheets/7-deprecated/5-components/_message.scss +49 -12
- package/src/stylesheets/7-deprecated/5-components/_translation-options.scss +30 -37
- package/src/stylesheets/7-deprecated/5-components/_unread-messages.scss +38 -0
- package/src/stylesheets/deprecated-view.scss +1 -0
- package/src/stylesheets/styles.scss +2 -0
- package/webpack/config.common.js +6 -1
- package/webpack/config.package.js +18 -0
- package/webpack/defaults.js +1 -1
- package/src/javascripts/ui/components/core/seamly-file-upload.js +0 -86
- package/src/javascripts/ui/components/entry/text-entry/hooks.js +0 -46
|
@@ -55,6 +55,7 @@ const baseState = {
|
|
|
55
55
|
optionsOverride: {},
|
|
56
56
|
},
|
|
57
57
|
currentUploads: [],
|
|
58
|
+
processingFileUploads: [],
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
const avatar =
|
|
@@ -701,12 +702,28 @@ const fileDownloadAgentMessage = {
|
|
|
701
702
|
},
|
|
702
703
|
}
|
|
703
704
|
|
|
705
|
+
const fileUploadAgentMessage = {
|
|
706
|
+
type: 'message',
|
|
707
|
+
payload: {
|
|
708
|
+
...fileDownloadPayload,
|
|
709
|
+
fromClient: false,
|
|
710
|
+
participant: 'agent',
|
|
711
|
+
id: randomId(),
|
|
712
|
+
},
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
sessionStorage.setItem(
|
|
716
|
+
`image-${fileDownloadAgentMessage.payload.id}`,
|
|
717
|
+
fileDownloadAgentMessage.payload.body.url,
|
|
718
|
+
)
|
|
719
|
+
|
|
704
720
|
const deletedFileDownloadAgentMessage = {
|
|
705
721
|
...fileDownloadAgentMessage,
|
|
706
722
|
payload: {
|
|
707
723
|
...fileDownloadAgentMessage.payload,
|
|
708
724
|
body: {
|
|
709
725
|
...fileDownloadAgentMessage.payload.body,
|
|
726
|
+
url: undefined,
|
|
710
727
|
},
|
|
711
728
|
id: randomId(),
|
|
712
729
|
},
|
|
@@ -722,6 +739,11 @@ const fileDownloadUserMessage = {
|
|
|
722
739
|
},
|
|
723
740
|
}
|
|
724
741
|
|
|
742
|
+
sessionStorage.setItem(
|
|
743
|
+
`image-${fileDownloadUserMessage.payload.id}`,
|
|
744
|
+
fileDownloadUserMessage.payload.body.url,
|
|
745
|
+
)
|
|
746
|
+
|
|
725
747
|
const emptyUrlFileDownloadUserMessage = {
|
|
726
748
|
...fileDownloadUserMessage,
|
|
727
749
|
payload: {
|
|
@@ -1143,6 +1165,7 @@ const standardState = {
|
|
|
1143
1165
|
userMessageWithLinks,
|
|
1144
1166
|
newTopicDivider,
|
|
1145
1167
|
imageMessage,
|
|
1168
|
+
fileUploadAgentMessage,
|
|
1146
1169
|
fileDownloadAgentMessage,
|
|
1147
1170
|
deletedFileDownloadAgentMessage,
|
|
1148
1171
|
userMessageLong,
|
|
@@ -1219,7 +1242,9 @@ const standardState = {
|
|
|
1219
1242
|
imageMessage,
|
|
1220
1243
|
videoMessage,
|
|
1221
1244
|
imageMessageWithLightbox,
|
|
1245
|
+
fileUploadAgentMessage,
|
|
1222
1246
|
fileDownloadAgentMessage,
|
|
1247
|
+
fileDownloadUserMessage,
|
|
1223
1248
|
],
|
|
1224
1249
|
},
|
|
1225
1250
|
systemMessages: {
|
|
@@ -1495,6 +1520,46 @@ const standardState = {
|
|
|
1495
1520
|
optionsOverride: {},
|
|
1496
1521
|
},
|
|
1497
1522
|
},
|
|
1523
|
+
inputLabel: {
|
|
1524
|
+
category: categoryKeys.features,
|
|
1525
|
+
headingText: 'Input label',
|
|
1526
|
+
...baseState,
|
|
1527
|
+
entryMeta: {
|
|
1528
|
+
default: 'text',
|
|
1529
|
+
active: 'text',
|
|
1530
|
+
options: {
|
|
1531
|
+
text: { limit: null },
|
|
1532
|
+
},
|
|
1533
|
+
optionsOverride: {
|
|
1534
|
+
text: {
|
|
1535
|
+
label: 'What is your name?',
|
|
1536
|
+
limit: null,
|
|
1537
|
+
placeholder: 'Please enter your name',
|
|
1538
|
+
},
|
|
1539
|
+
},
|
|
1540
|
+
},
|
|
1541
|
+
},
|
|
1542
|
+
abortTransactional: {
|
|
1543
|
+
category: categoryKeys.features,
|
|
1544
|
+
headingText: 'Abort transaction',
|
|
1545
|
+
...baseState,
|
|
1546
|
+
events: [shortTextMessage, userMessage],
|
|
1547
|
+
entryMeta: {
|
|
1548
|
+
default: 'text',
|
|
1549
|
+
active: 'text',
|
|
1550
|
+
options: {
|
|
1551
|
+
text: { limit: null },
|
|
1552
|
+
},
|
|
1553
|
+
optionsOverride: {},
|
|
1554
|
+
actions: {
|
|
1555
|
+
abortTransaction: {
|
|
1556
|
+
label: 'Ask for another question',
|
|
1557
|
+
topicFallbackMessage: 'Oops...',
|
|
1558
|
+
topicName: 'abort_transactional',
|
|
1559
|
+
},
|
|
1560
|
+
},
|
|
1561
|
+
},
|
|
1562
|
+
},
|
|
1498
1563
|
fileUploadToggle: {
|
|
1499
1564
|
category: categoryKeys.uploads,
|
|
1500
1565
|
headingText: 'File upload toggle button',
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { useSelector } from 'react-redux'
|
|
1
2
|
import Icon from 'ui/components/layout/icon'
|
|
2
3
|
import OptionsButton from 'ui/components/options/options-button'
|
|
3
4
|
import { useSeamlyOptions } from 'ui/hooks/seamly-hooks'
|
|
4
5
|
import { useI18n } from 'domains/i18n/hooks'
|
|
5
|
-
import {
|
|
6
|
+
import { selectHasError } from 'domains/interrupt/selectors'
|
|
6
7
|
import TranslationsOptionsButton from 'domains/translations/components/options-button'
|
|
7
8
|
import {
|
|
8
9
|
useLocaleNativeName,
|
|
@@ -13,14 +14,14 @@ import { className } from 'lib/css'
|
|
|
13
14
|
export default function AppOptions() {
|
|
14
15
|
const { menuOptions, allowOptionSelection } = useSeamlyOptions()
|
|
15
16
|
const { isAvailable: isTranslationsAvailable } = useTranslations()
|
|
16
|
-
const
|
|
17
|
+
const hasError = useSelector(selectHasError)
|
|
17
18
|
const { t, locale } = useI18n()
|
|
18
19
|
const localeNativeName = useLocaleNativeName(locale)
|
|
19
20
|
|
|
20
21
|
if (
|
|
21
22
|
(!isTranslationsAvailable &&
|
|
22
23
|
(!allowOptionSelection || !menuOptions.length)) ||
|
|
23
|
-
|
|
24
|
+
hasError
|
|
24
25
|
) {
|
|
25
26
|
return null
|
|
26
27
|
}
|
|
@@ -8,6 +8,7 @@ import { useEvents } from 'ui/hooks/seamly-state-hooks'
|
|
|
8
8
|
import { useI18n } from 'domains/i18n/hooks'
|
|
9
9
|
import { useVisibility } from 'domains/visibility/hooks'
|
|
10
10
|
import { className } from 'lib/css'
|
|
11
|
+
import AbortTransactionButton from '../entry/abort-transaction-button/abort-transaction-button'
|
|
11
12
|
import ComponentFilter from './component-filter'
|
|
12
13
|
import Event from './event/event'
|
|
13
14
|
import Loader from './loader'
|
|
@@ -77,6 +78,7 @@ const Conversation = () => {
|
|
|
77
78
|
<Events />
|
|
78
79
|
</ComponentFilter>
|
|
79
80
|
{isLoading && <Loader />}
|
|
81
|
+
<AbortTransactionButton />
|
|
80
82
|
</ol>
|
|
81
83
|
</div>
|
|
82
84
|
</div>
|
package/src/javascripts/ui/components/conversation/event/chat-scroll/chat-scroll-provider.tsx
CHANGED
|
@@ -11,6 +11,8 @@ const ChatScrollProvider = ({ children }) => {
|
|
|
11
11
|
const eventRefs = useMemo(
|
|
12
12
|
() =>
|
|
13
13
|
events.reduce<Record<string, RefObject<HTMLElement>>>((acc, value) => {
|
|
14
|
+
if (!value.payload.id) return acc
|
|
15
|
+
|
|
14
16
|
acc[value.payload.id] = createRef()
|
|
15
17
|
return acc
|
|
16
18
|
}, {}),
|
|
@@ -31,7 +31,7 @@ export const useChoicePrompt = (event) => {
|
|
|
31
31
|
body: event.payload.body?.prompt,
|
|
32
32
|
translatedBody: event.payload.translatedBody && {
|
|
33
33
|
...event.payload.translatedBody,
|
|
34
|
-
data: event.payload.translatedBody.
|
|
34
|
+
data: event.payload.translatedBody.prompt,
|
|
35
35
|
},
|
|
36
36
|
},
|
|
37
37
|
}
|
|
@@ -1,16 +1,53 @@
|
|
|
1
1
|
import { useMemo } from 'preact/hooks'
|
|
2
|
+
import { useSelector } from 'react-redux'
|
|
2
3
|
import MessageContainer from 'ui/components/conversation/message-container'
|
|
3
4
|
import Icon from 'ui/components/layout/icon'
|
|
4
5
|
import { useI18n } from 'domains/i18n/hooks'
|
|
5
6
|
import { useTranslatedEventData } from 'domains/translations/hooks'
|
|
6
7
|
import { className } from 'lib/css'
|
|
7
8
|
|
|
9
|
+
const PROCESSING_IMAGE = 'PROCESSING_IMAGE'
|
|
10
|
+
|
|
11
|
+
const useImageFromStorage = (currentFileId) => {
|
|
12
|
+
const { processingFileUploads } = useSelector(({ state }) => state)
|
|
13
|
+
|
|
14
|
+
return useMemo(() => {
|
|
15
|
+
const isProcessingImg = processingFileUploads.some(
|
|
16
|
+
(fileId) => fileId === currentFileId,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
if (isProcessingImg) return PROCESSING_IMAGE
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
return sessionStorage.getItem(`image-${currentFileId}`)
|
|
23
|
+
} catch (error) {
|
|
24
|
+
return undefined
|
|
25
|
+
}
|
|
26
|
+
}, [currentFileId, processingFileUploads])
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const UploadedImage = ({ img, filename }) => {
|
|
30
|
+
const { t } = useI18n()
|
|
31
|
+
const srText = t('fileUpload.srFileUploadedText', { fileName: filename })
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<>
|
|
35
|
+
<span className={className(['download', 'download--preview'])}>
|
|
36
|
+
<img src={img} alt={srText} />
|
|
37
|
+
<span aria-hidden="true" className={className('file-download')}>
|
|
38
|
+
{filename}
|
|
39
|
+
</span>
|
|
40
|
+
</span>
|
|
41
|
+
</>
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
8
45
|
const UploadContent = ({ children, url, target }) =>
|
|
9
46
|
url ? (
|
|
10
47
|
<a
|
|
11
48
|
href={url}
|
|
12
49
|
download
|
|
13
|
-
target={target
|
|
50
|
+
target={target}
|
|
14
51
|
className={className(['download', 'download-link'])}
|
|
15
52
|
>
|
|
16
53
|
{children}
|
|
@@ -22,8 +59,9 @@ const UploadContent = ({ children, url, target }) =>
|
|
|
22
59
|
const Upload = ({ event, ...props }) => {
|
|
23
60
|
const { t } = useI18n()
|
|
24
61
|
const { body } = useTranslatedEventData(event)
|
|
25
|
-
const { fromClient } = event.payload
|
|
62
|
+
const { fromClient, id } = event.payload
|
|
26
63
|
const { filename, url } = body
|
|
64
|
+
const img = useImageFromStorage(id)
|
|
27
65
|
|
|
28
66
|
const srText = useMemo(
|
|
29
67
|
() =>
|
|
@@ -35,13 +73,16 @@ const Upload = ({ event, ...props }) => {
|
|
|
35
73
|
|
|
36
74
|
return (
|
|
37
75
|
<MessageContainer event={event} type="upload" {...props}>
|
|
38
|
-
|
|
39
|
-
<
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
76
|
+
{img && img !== PROCESSING_IMAGE ? (
|
|
77
|
+
<UploadedImage img={img} filename={filename} />
|
|
78
|
+
) : (
|
|
79
|
+
<UploadContent url={url} target={!fromClient ? '_blank' : undefined}>
|
|
80
|
+
<Icon name="download" size="16" alt={srText} />
|
|
81
|
+
<span aria-hidden="true" className={className('file-download')}>
|
|
82
|
+
{filename}
|
|
83
|
+
</span>
|
|
84
|
+
</UploadContent>
|
|
85
|
+
)}
|
|
45
86
|
</MessageContainer>
|
|
46
87
|
)
|
|
47
88
|
}
|
|
@@ -37,8 +37,8 @@ const useChatScroll = (
|
|
|
37
37
|
const isLoading = useSeamlyIsLoading()
|
|
38
38
|
const { isOpen } = useVisibility()
|
|
39
39
|
const loadedImageEventIds = useLoadedImageEventIds()
|
|
40
|
-
const isLastEventFromClient = useSelector(
|
|
41
|
-
(state: RootState) => state
|
|
40
|
+
const { processingFileUploads, isLastEventFromClient } = useSelector(
|
|
41
|
+
({ state }: RootState) => state,
|
|
42
42
|
)
|
|
43
43
|
|
|
44
44
|
useEffect(() => {
|
|
@@ -84,6 +84,7 @@ const useChatScroll = (
|
|
|
84
84
|
isLoading,
|
|
85
85
|
isOpen,
|
|
86
86
|
loadedImageEventIds,
|
|
87
|
+
processingFileUploads,
|
|
87
88
|
scrollToBottom,
|
|
88
89
|
])
|
|
89
90
|
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
setHistory,
|
|
25
25
|
setIsLoading,
|
|
26
26
|
setParticipant,
|
|
27
|
+
setProactiveMessages,
|
|
27
28
|
setServiceDataItem,
|
|
28
29
|
setServiceEntryMetadata,
|
|
29
30
|
} from 'domains/store/slice'
|
|
@@ -73,7 +74,7 @@ const SeamlyEventSubscriber = () => {
|
|
|
73
74
|
channel?.leave()
|
|
74
75
|
}
|
|
75
76
|
}
|
|
76
|
-
return () =>
|
|
77
|
+
return () => undefined
|
|
77
78
|
}, [api, api.connectionInfo, api.conversation])
|
|
78
79
|
|
|
79
80
|
useEffect(() => {
|
|
@@ -201,7 +202,7 @@ const SeamlyEventSubscriber = () => {
|
|
|
201
202
|
case 'system':
|
|
202
203
|
if (payload.type === 'service_changed') {
|
|
203
204
|
const { serviceSettings, ...eventPayload } = payload
|
|
204
|
-
const { entry } = serviceSettings
|
|
205
|
+
const { entry, proactiveMessages } = serviceSettings
|
|
205
206
|
const { upload } = entry.options
|
|
206
207
|
|
|
207
208
|
dispatch(
|
|
@@ -211,6 +212,10 @@ const SeamlyEventSubscriber = () => {
|
|
|
211
212
|
}),
|
|
212
213
|
)
|
|
213
214
|
|
|
215
|
+
dispatch(
|
|
216
|
+
setProactiveMessages(proactiveMessages.enabled || false),
|
|
217
|
+
)
|
|
218
|
+
|
|
214
219
|
dispatch(setServiceEntryMetadata(entry))
|
|
215
220
|
if (payload.serviceSessionId) {
|
|
216
221
|
dispatch(setActiveService(payload.serviceSessionId))
|
|
@@ -244,6 +249,7 @@ const SeamlyEventSubscriber = () => {
|
|
|
244
249
|
)
|
|
245
250
|
break
|
|
246
251
|
case 'conversation_erred':
|
|
252
|
+
case 'attach_channel_erred':
|
|
247
253
|
const seamlyGeneralError = new SeamlyGeneralError(event)
|
|
248
254
|
dispatch(
|
|
249
255
|
setInterrupt({
|
|
@@ -283,6 +289,10 @@ const SeamlyEventSubscriber = () => {
|
|
|
283
289
|
|
|
284
290
|
const { channel } = api.conversation
|
|
285
291
|
|
|
292
|
+
if (messageChannelRef.current) {
|
|
293
|
+
channel?.off('message', messageChannelRef.current)
|
|
294
|
+
}
|
|
295
|
+
|
|
286
296
|
messageChannelRef.current = channel.on('message', (payload) => {
|
|
287
297
|
if (!EMITTABLE_MESSAGE_TYPES.includes(payload.type)) {
|
|
288
298
|
return payload
|
|
@@ -301,13 +311,7 @@ const SeamlyEventSubscriber = () => {
|
|
|
301
311
|
|
|
302
312
|
return true
|
|
303
313
|
})
|
|
304
|
-
|
|
305
|
-
return () => {
|
|
306
|
-
api.conversation.channel?.off('message', messageChannelRef.current)
|
|
307
|
-
}
|
|
308
314
|
}
|
|
309
|
-
|
|
310
|
-
return () => undefined
|
|
311
315
|
}, [api, api.connectionInfo, api.conversation.channel, eventBus])
|
|
312
316
|
|
|
313
317
|
useEffect(() => {
|
|
@@ -315,6 +319,10 @@ const SeamlyEventSubscriber = () => {
|
|
|
315
319
|
api.conversation.onConnection(({ connected }) => {
|
|
316
320
|
if (!connected) return false
|
|
317
321
|
|
|
322
|
+
if (syncChannelRef.current) {
|
|
323
|
+
api.conversation.channel?.off('sync', syncChannelRef.current)
|
|
324
|
+
}
|
|
325
|
+
|
|
318
326
|
syncChannelRef.current = api.conversation.channel.on(
|
|
319
327
|
'sync',
|
|
320
328
|
(payload) => {
|
|
@@ -348,13 +356,7 @@ const SeamlyEventSubscriber = () => {
|
|
|
348
356
|
|
|
349
357
|
return true
|
|
350
358
|
})
|
|
351
|
-
|
|
352
|
-
return () => {
|
|
353
|
-
api.conversation.channel?.off('sync', syncChannelRef.current)
|
|
354
|
-
}
|
|
355
359
|
}
|
|
356
|
-
|
|
357
|
-
return () => undefined
|
|
358
360
|
}, [api, api.connectionInfo, api.conversation.channel, events, dispatch])
|
|
359
361
|
|
|
360
362
|
return null
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { useCallback } from 'preact/hooks'
|
|
2
|
+
import { useDispatch } from 'react-redux'
|
|
3
|
+
import SeamlyFileUploadContext from 'ui/components/core/seamly-file-upload-context'
|
|
4
|
+
import { useSeamlyApiContext, useSeamlyCommands } from 'ui/hooks/seamly-hooks'
|
|
5
|
+
import { useI18n } from 'domains/i18n/hooks'
|
|
6
|
+
import {
|
|
7
|
+
doneProcessingImage,
|
|
8
|
+
registerUpload,
|
|
9
|
+
setUploadComplete,
|
|
10
|
+
setUploadError,
|
|
11
|
+
setUploadProgress,
|
|
12
|
+
startProcessingImage,
|
|
13
|
+
} from 'domains/store/slice'
|
|
14
|
+
import { MessageUpload } from 'domains/store/store.types'
|
|
15
|
+
import { randomId } from 'lib/id'
|
|
16
|
+
|
|
17
|
+
const calculateImgSize = (img) => {
|
|
18
|
+
const MAX_WIDTH = 600
|
|
19
|
+
const MAX_HEIGHT = 600
|
|
20
|
+
|
|
21
|
+
const { height, width } = img
|
|
22
|
+
if (width > height && width > MAX_WIDTH) {
|
|
23
|
+
return [MAX_WIDTH, (height * MAX_WIDTH) / width]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (height > MAX_HEIGHT) {
|
|
27
|
+
return [(width * MAX_HEIGHT) / height, MAX_HEIGHT]
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return [width, height]
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const resizeImage = (img) => {
|
|
34
|
+
const canvas = document.createElement('canvas')
|
|
35
|
+
const ctx = canvas.getContext('2d')
|
|
36
|
+
|
|
37
|
+
const [width, height] = calculateImgSize(img)
|
|
38
|
+
canvas.width = width
|
|
39
|
+
canvas.height = height
|
|
40
|
+
|
|
41
|
+
ctx.drawImage(img, 0, 0, width, height)
|
|
42
|
+
|
|
43
|
+
return canvas.toDataURL()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const toBase64 = (file): Promise<string> =>
|
|
47
|
+
new Promise((resolve, reject) => {
|
|
48
|
+
const reader = new FileReader()
|
|
49
|
+
reader.readAsDataURL(file)
|
|
50
|
+
reader.onload = () =>
|
|
51
|
+
typeof reader.result === 'string' ? resolve(reader.result) : undefined
|
|
52
|
+
reader.onerror = (error) => reject(error)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
const SeamlyFileUpload = ({ children }) => {
|
|
56
|
+
const { t } = useI18n()
|
|
57
|
+
const dispatch = useDispatch()
|
|
58
|
+
const api = useSeamlyApiContext()
|
|
59
|
+
const { addUploadBubble } = useSeamlyCommands()
|
|
60
|
+
|
|
61
|
+
const addImageToSessionStorage = useCallback(
|
|
62
|
+
async (file, fileId) => {
|
|
63
|
+
dispatch(startProcessingImage(fileId))
|
|
64
|
+
const base64 = await toBase64(file)
|
|
65
|
+
const img = new Image()
|
|
66
|
+
|
|
67
|
+
img.src = base64
|
|
68
|
+
|
|
69
|
+
await img.decode()
|
|
70
|
+
const newDataUri = resizeImage(img)
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
sessionStorage.setItem(`image-${fileId}`, newDataUri)
|
|
74
|
+
} catch (error) {
|
|
75
|
+
// Nothing to do!
|
|
76
|
+
} finally {
|
|
77
|
+
dispatch(doneProcessingImage(fileId))
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
[dispatch],
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
const onUploadFileHandler = useCallback(
|
|
84
|
+
(file) => {
|
|
85
|
+
const fileId = randomId()
|
|
86
|
+
|
|
87
|
+
const uploadHandle = api.uploadFile(
|
|
88
|
+
file,
|
|
89
|
+
(p) => {
|
|
90
|
+
dispatch(setUploadProgress({ fileId, progress: Math.ceil(p) }))
|
|
91
|
+
},
|
|
92
|
+
async (result: MessageUpload) => {
|
|
93
|
+
const {
|
|
94
|
+
id,
|
|
95
|
+
transactionId,
|
|
96
|
+
occurredAt,
|
|
97
|
+
body: { contentType, filename, filesize, url },
|
|
98
|
+
} = result
|
|
99
|
+
|
|
100
|
+
dispatch(setUploadComplete(fileId))
|
|
101
|
+
addUploadBubble(
|
|
102
|
+
id,
|
|
103
|
+
transactionId,
|
|
104
|
+
occurredAt,
|
|
105
|
+
contentType,
|
|
106
|
+
filename,
|
|
107
|
+
filesize,
|
|
108
|
+
url,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
await addImageToSessionStorage(file, id)
|
|
112
|
+
},
|
|
113
|
+
(err) => {
|
|
114
|
+
const errorKey = err?.error || ''
|
|
115
|
+
let errorText
|
|
116
|
+
|
|
117
|
+
switch (errorKey) {
|
|
118
|
+
case 'file_uploads_are_disabled':
|
|
119
|
+
errorText = t('fileUpload.errors.unavailable')
|
|
120
|
+
break
|
|
121
|
+
case 'request_entity_too_large':
|
|
122
|
+
errorText = t('fileUpload.errors.tooLarge')
|
|
123
|
+
break
|
|
124
|
+
case 'file_has_invalid_mime_type':
|
|
125
|
+
errorText = t('fileUpload.errors.wrongType')
|
|
126
|
+
break
|
|
127
|
+
case 'virus_found':
|
|
128
|
+
errorText = t('fileUpload.errors.virusFound')
|
|
129
|
+
break
|
|
130
|
+
default:
|
|
131
|
+
errorText = t('fileUpload.errors.general')
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
dispatch(setUploadError({ fileId, errorText }))
|
|
135
|
+
},
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
dispatch(
|
|
139
|
+
registerUpload({
|
|
140
|
+
fileId,
|
|
141
|
+
fileName: file.name,
|
|
142
|
+
uploadHandle,
|
|
143
|
+
}),
|
|
144
|
+
)
|
|
145
|
+
},
|
|
146
|
+
[addImageToSessionStorage, addUploadBubble, api, dispatch, t],
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
return (
|
|
150
|
+
<SeamlyFileUploadContext.Provider value={onUploadFileHandler}>
|
|
151
|
+
{children}
|
|
152
|
+
</SeamlyFileUploadContext.Provider>
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export default SeamlyFileUpload
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useContext, useEffect, useRef } from 'preact/hooks'
|
|
2
|
-
import { useDispatch } from 'react-redux'
|
|
2
|
+
import { useDispatch, useSelector } from 'react-redux'
|
|
3
3
|
import { userParticipantId } from 'config'
|
|
4
4
|
import {
|
|
5
5
|
useSeamlyActivityEventHandler,
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
import { actionTypes, sourceTypes } from 'ui/utils/seamly-utils'
|
|
13
13
|
import { useConfig } from 'domains/config/hooks'
|
|
14
14
|
import { updateConfig } from 'domains/config/slice'
|
|
15
|
-
import {
|
|
15
|
+
import { selectHasError } from 'domains/interrupt/selectors'
|
|
16
16
|
import { useTranslations } from 'domains/translations/hooks'
|
|
17
17
|
import { visibilityStates } from 'domains/visibility/constants'
|
|
18
18
|
import { useVisibility } from 'domains/visibility/hooks'
|
|
@@ -47,7 +47,7 @@ const SeamlyInstanceFunctionsLoader = () => {
|
|
|
47
47
|
const previousUnreadCount = useRef(null)
|
|
48
48
|
const previousVisibilityState = useRef(null)
|
|
49
49
|
const { isInline, isResolving } = useSeamlyLayoutMode()
|
|
50
|
-
const
|
|
50
|
+
const hasError = useSelector(selectHasError)
|
|
51
51
|
const currentConversationUrl = useSeamlyConversationUrl()
|
|
52
52
|
const prevConversationUrl = useRef(null)
|
|
53
53
|
const onActivityHandler = useSeamlyActivityEventHandler()
|
|
@@ -142,7 +142,7 @@ const SeamlyInstanceFunctionsLoader = () => {
|
|
|
142
142
|
)
|
|
143
143
|
|
|
144
144
|
useEffect(() => {
|
|
145
|
-
if (!isResolving && !
|
|
145
|
+
if (!isResolving && !hasError) {
|
|
146
146
|
// Check for app reset
|
|
147
147
|
if (
|
|
148
148
|
prevConversationUrl.current &&
|
|
@@ -178,7 +178,7 @@ const SeamlyInstanceFunctionsLoader = () => {
|
|
|
178
178
|
eventBus,
|
|
179
179
|
isInline,
|
|
180
180
|
isResolving,
|
|
181
|
-
|
|
181
|
+
hasError,
|
|
182
182
|
currentConversationUrl,
|
|
183
183
|
])
|
|
184
184
|
|
package/src/javascripts/ui/components/entry/abort-transaction-button/abort-transaction-button.tsx
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { useSeamlyApiContext } from 'ui/hooks/seamly-api-hooks'
|
|
2
|
+
import { actionTypes } from 'ui/utils/seamly-utils'
|
|
3
|
+
import { className } from 'lib/css'
|
|
4
|
+
import { useEntryAbortTransaction } from '../text-entry/hooks'
|
|
5
|
+
|
|
6
|
+
export default function AbortTransactionButton() {
|
|
7
|
+
const { abortTransaction, clearEntryAbortTransaction } =
|
|
8
|
+
useEntryAbortTransaction()
|
|
9
|
+
const api = useSeamlyApiContext()
|
|
10
|
+
|
|
11
|
+
if (!abortTransaction) return null
|
|
12
|
+
|
|
13
|
+
const handleAbortTransaction = () => {
|
|
14
|
+
api.send('action', {
|
|
15
|
+
type: actionTypes.setTopic,
|
|
16
|
+
body: {
|
|
17
|
+
name: abortTransaction.topicName,
|
|
18
|
+
fallbackMessage: abortTransaction.topicFallbackMessage,
|
|
19
|
+
},
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
clearEntryAbortTransaction()
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<li
|
|
27
|
+
className={className([
|
|
28
|
+
'cvco-conversation__item',
|
|
29
|
+
'cvco-conversation__item--abort-transaction',
|
|
30
|
+
])}
|
|
31
|
+
>
|
|
32
|
+
<button
|
|
33
|
+
className={className([
|
|
34
|
+
'button',
|
|
35
|
+
'button--secondary',
|
|
36
|
+
'abort-transaction__button',
|
|
37
|
+
])}
|
|
38
|
+
type="button"
|
|
39
|
+
onClick={handleAbortTransaction}
|
|
40
|
+
>
|
|
41
|
+
{abortTransaction.label}
|
|
42
|
+
</button>
|
|
43
|
+
</li>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useLayoutEffect, useRef } from 'preact/hooks'
|
|
2
|
+
import { useSelector } from 'react-redux'
|
|
2
3
|
import {
|
|
3
4
|
useFocusIfSeamlyContainedFocus,
|
|
4
5
|
useGeneratedId,
|
|
@@ -8,7 +9,7 @@ import {
|
|
|
8
9
|
useSkiplinkTargetFocusing,
|
|
9
10
|
} from 'ui/hooks/seamly-hooks'
|
|
10
11
|
import { useI18n } from 'domains/i18n/hooks'
|
|
11
|
-
import {
|
|
12
|
+
import { selectHasError } from 'domains/interrupt/selectors'
|
|
12
13
|
import { useVisibility } from 'domains/visibility/hooks'
|
|
13
14
|
import { className } from 'lib/css'
|
|
14
15
|
|
|
@@ -24,10 +25,10 @@ const DeprecatedToggleButton = ({ onOpenChat }) => {
|
|
|
24
25
|
const focusIfContained = useFocusIfSeamlyContainedFocus()
|
|
25
26
|
const currentAgent = useSeamlyCurrentAgent()
|
|
26
27
|
const agentSubtitle = useSeamlyHeaderData().subTitle
|
|
27
|
-
const
|
|
28
|
+
const hasError = useSelector(selectHasError)
|
|
28
29
|
const { headerCollapseButtonId } = useSeamlyStateContext()
|
|
29
30
|
|
|
30
|
-
const showAgentInfo = currentAgent && !
|
|
31
|
+
const showAgentInfo = currentAgent && !hasError
|
|
31
32
|
|
|
32
33
|
useLayoutEffect(() => {
|
|
33
34
|
// Because we can close the app from the external API we
|