@seamly/web-ui 18.3.0 → 19.0.0-beta.3
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/index.debug.js +349 -74
- package/build/dist/lib/index.debug.min.js +1 -1
- package/build/dist/lib/index.debug.min.js.LICENSE.txt +108 -8
- package/build/dist/lib/index.js +6103 -5988
- package/build/dist/lib/index.min.js +1 -1
- package/build/dist/lib/index.min.js.LICENSE.txt +1 -1
- package/build/dist/lib/standalone.js +2414 -2226
- package/build/dist/lib/standalone.min.js +1 -1
- package/build/dist/lib/standalone.min.js.LICENSE.txt +1 -1
- package/build/dist/lib/storage.js +8 -1
- package/build/dist/lib/storage.min.js +1 -1
- package/build/dist/lib/style-guide.js +1517 -785
- package/build/dist/lib/style-guide.min.js +1 -1
- package/build/dist/lib/styles.css +1 -1
- package/package.json +27 -28
- package/src/javascripts/api/index.js +25 -40
- package/src/javascripts/api/producer.js +3 -6
- package/src/javascripts/config.js +3 -3
- package/src/javascripts/domains/app/actions.js +28 -11
- package/src/javascripts/domains/app/hooks.js +6 -0
- package/src/javascripts/domains/app/index.js +3 -0
- package/src/javascripts/domains/app/reducer.js +16 -0
- package/src/javascripts/domains/app/selectors.js +8 -0
- package/src/javascripts/domains/app/utils.js +4 -0
- package/src/javascripts/domains/config/actions.js +1 -3
- package/src/javascripts/domains/config/middleware.js +0 -4
- package/src/javascripts/domains/config/reducer.js +2 -13
- package/src/javascripts/domains/config/selectors.js +3 -3
- package/src/javascripts/domains/config/utils.js +4 -0
- package/src/javascripts/domains/forms/actions.js +1 -3
- package/src/javascripts/domains/forms/reducer.js +1 -2
- package/src/javascripts/domains/forms/selectors.js +2 -2
- package/src/javascripts/domains/forms/utils.js +5 -0
- package/src/javascripts/domains/i18n/actions.js +20 -0
- package/src/javascripts/domains/i18n/hooks.js +38 -0
- package/src/javascripts/domains/i18n/index.js +5 -84
- package/src/javascripts/domains/i18n/reducer.js +64 -0
- package/src/javascripts/domains/i18n/selectors.js +15 -0
- package/src/javascripts/domains/i18n/utils.js +4 -0
- package/src/javascripts/domains/interrupt/actions.js +1 -3
- package/src/javascripts/domains/interrupt/reducer.js +1 -2
- package/src/javascripts/domains/interrupt/selectors.js +3 -2
- package/src/javascripts/domains/interrupt/utils.js +4 -0
- package/src/javascripts/domains/redux/hooks.js +1 -0
- package/src/javascripts/domains/store/index.js +7 -1
- package/src/javascripts/domains/translations/actions.js +1 -3
- package/src/javascripts/domains/translations/components/chat-status.js +1 -1
- package/src/javascripts/domains/translations/components/options-dialog/form.js +11 -6
- package/src/javascripts/domains/translations/index.js +1 -0
- package/src/javascripts/domains/translations/middleware.js +43 -0
- package/src/javascripts/domains/translations/reducer.js +2 -9
- package/src/javascripts/domains/translations/selectors.js +2 -2
- package/src/javascripts/domains/translations/utils.js +4 -0
- package/src/javascripts/index.js +3 -0
- package/src/javascripts/lib/engine/index.js +1 -0
- package/src/javascripts/lib/mutex.js +30 -0
- package/src/javascripts/lib/redux-helpers/index.js +55 -16
- package/src/javascripts/lib/store/providers/session-storage.js +6 -1
- package/src/javascripts/style-guide/components/app.js +7 -2
- package/src/javascripts/style-guide/components/static-core.js +9 -3
- package/src/javascripts/style-guide/states.js +8 -8
- package/src/javascripts/style-guide/style-guide-engine.js +14 -11
- package/src/javascripts/ui/components/conversation/event/divider/variants/new-translation.js +1 -1
- package/src/javascripts/ui/components/conversation/event/upload.js +2 -2
- package/src/javascripts/ui/components/core/seamly-activity-monitor.js +2 -0
- package/src/javascripts/ui/components/core/seamly-event-subscriber.js +2 -0
- package/src/javascripts/ui/components/core/seamly-instance-functions-loader.js +1 -7
- package/src/javascripts/ui/components/core/seamly-new-notifications.js +5 -6
- package/src/javascripts/ui/components/core/seamly-read-state.js +6 -4
- package/src/javascripts/ui/components/entry/text-entry/hooks.js +6 -4
- package/src/javascripts/ui/components/entry/text-entry/text-entry-form.js +10 -3
- package/src/javascripts/ui/components/entry/upload/file-upload-form.js +6 -3
- package/src/javascripts/ui/components/entry/upload/index.js +8 -3
- package/src/javascripts/ui/components/faq/faq.js +2 -2
- package/src/javascripts/ui/components/layout/app-frame.js +11 -8
- package/src/javascripts/ui/components/layout/interrupt.js +6 -2
- package/src/javascripts/ui/components/warnings/resume-conversation-prompt.js +1 -1
- package/src/javascripts/ui/components/widgets/upload-progress.js +1 -1
- package/src/javascripts/ui/hooks/seamly-api-hooks.js +0 -6
- package/src/javascripts/ui/hooks/seamly-entry-hooks.js +17 -21
- package/src/javascripts/ui/hooks/seamly-hooks.js +0 -1
- package/src/javascripts/ui/hooks/use-seamly-commands.js +5 -6
- package/src/javascripts/ui/hooks/use-seamly-visibility.js +3 -5
- package/src/javascripts/ui/hooks/use-single-file-upload.js +4 -1
- package/src/javascripts/ui/utils/general-utils.js +6 -13
- package/src/stylesheets/1-settings/_config.scss +2 -1
- package/src/stylesheets/3-app/_app.scss +3 -4
- package/src/stylesheets/5-components/_faq.scss +3 -8
- package/src/stylesheets/5-components/_modal.scss +3 -3
- package/webpack/config.package.js +0 -18
- package/webpack/config.site.js +6 -0
- package/webpack/defaults.js +0 -3
- package/CHANGELOG.md +0 -573
- package/build/dist/translations/de-informal.js +0 -274
- package/build/dist/translations/de-informal.min.js +0 -1
- package/build/dist/translations/en.js +0 -274
- package/build/dist/translations/en.min.js +0 -1
- package/build/dist/translations/es-informal.js +0 -280
- package/build/dist/translations/es-informal.min.js +0 -1
- package/build/dist/translations/nl-formal.js +0 -274
- package/build/dist/translations/nl-formal.min.js +0 -1
- package/build/dist/translations/nl-informal.js +0 -274
- package/build/dist/translations/nl-informal.min.js +0 -1
- package/src/javascripts/lib/i18n.js +0 -46
- package/translations/de-informal.js +0 -235
- package/translations/en.js +0 -232
- package/translations/es-informal.js +0 -241
- package/translations/nl-formal.js +0 -228
- package/translations/nl-informal.js +0 -228
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { randomId } from '../id'
|
|
2
|
+
|
|
1
3
|
export const SLICE_DELIMITER = '/'
|
|
2
4
|
export const DOMAIN_DELIMITER = '//'
|
|
3
5
|
|
|
@@ -10,7 +12,8 @@ export function createAction(
|
|
|
10
12
|
identityReducer = (payload) => ({ payload }),
|
|
11
13
|
) {
|
|
12
14
|
const action = (...params) => ({ type, ...identityReducer(...params) })
|
|
13
|
-
action.toString = () => type
|
|
15
|
+
action.toString = () => String(type)
|
|
16
|
+
action.match = (obj) => obj?.type === String(type)
|
|
14
17
|
return action
|
|
15
18
|
}
|
|
16
19
|
|
|
@@ -32,22 +35,47 @@ export function createActions(baseType, ...args) {
|
|
|
32
35
|
return handlers.map((handler) => create(...handler))
|
|
33
36
|
}
|
|
34
37
|
|
|
35
|
-
export function createThunk(type,
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
38
|
+
export function createThunk(type, payloadCreator) {
|
|
39
|
+
const [pending, fulfilled, rejected] = createActions(type, {
|
|
40
|
+
pending: (arg, requestId) => ({
|
|
41
|
+
meta: { arg, requestId, status: 'pending' },
|
|
42
|
+
}),
|
|
43
|
+
fulfilled: (arg, payload, requestId) => ({
|
|
44
|
+
payload,
|
|
45
|
+
meta: { arg, requestId, status: 'fulfilled' },
|
|
46
|
+
}),
|
|
47
|
+
rejected: (arg, error, requestId) => ({
|
|
48
|
+
error,
|
|
49
|
+
meta: { arg, requestId, status: 'rejected', error: String(error) },
|
|
50
|
+
}),
|
|
51
|
+
})
|
|
52
|
+
const thunkCreator = (arg) => (dispatch, getState, extra) => {
|
|
53
|
+
const requestId = randomId()
|
|
54
|
+
const promise = (async () => {
|
|
55
|
+
let finalAction
|
|
56
|
+
try {
|
|
57
|
+
dispatch(pending(arg, requestId))
|
|
58
|
+
const prms = payloadCreator(arg, { dispatch, getState, extra })
|
|
59
|
+
const result = await prms
|
|
60
|
+
finalAction = fulfilled(arg, result, requestId)
|
|
61
|
+
} catch (error) {
|
|
62
|
+
finalAction = rejected(arg, error, requestId)
|
|
63
|
+
}
|
|
64
|
+
dispatch(finalAction)
|
|
65
|
+
return finalAction
|
|
66
|
+
})()
|
|
67
|
+
return Object.assign(promise, {
|
|
68
|
+
type,
|
|
69
|
+
arg,
|
|
70
|
+
requestId,
|
|
71
|
+
})
|
|
50
72
|
}
|
|
73
|
+
return Object.assign(thunkCreator, {
|
|
74
|
+
type,
|
|
75
|
+
pending,
|
|
76
|
+
fulfilled,
|
|
77
|
+
rejected,
|
|
78
|
+
})
|
|
51
79
|
}
|
|
52
80
|
|
|
53
81
|
export function createReducer(domain, handlers = {}, defaultState) {
|
|
@@ -62,3 +90,14 @@ export function createReducer(domain, handlers = {}, defaultState) {
|
|
|
62
90
|
reducer.toString = () => domain
|
|
63
91
|
return reducer
|
|
64
92
|
}
|
|
93
|
+
|
|
94
|
+
export function createDomain(domain) {
|
|
95
|
+
return {
|
|
96
|
+
createAction: prefixType(domain, createAction, DOMAIN_DELIMITER),
|
|
97
|
+
createActions: prefixType(domain, createActions, DOMAIN_DELIMITER),
|
|
98
|
+
createThunk: prefixType(domain, createThunk, DOMAIN_DELIMITER),
|
|
99
|
+
createReducer: (handlers, defaultState) =>
|
|
100
|
+
createReducer(domain, handlers, defaultState),
|
|
101
|
+
selectState: (state) => state[domain],
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -3,7 +3,12 @@ export default function store(key) {
|
|
|
3
3
|
|
|
4
4
|
return {
|
|
5
5
|
get() {
|
|
6
|
-
|
|
6
|
+
const candidates = [KEY, KEY.split('.').slice(0, -1).join('.')]
|
|
7
|
+
let val
|
|
8
|
+
do {
|
|
9
|
+
val = sessionStorage.getItem(candidates[0])
|
|
10
|
+
} while (candidates.shift() && !val)
|
|
11
|
+
return JSON.parse(val)
|
|
7
12
|
},
|
|
8
13
|
|
|
9
14
|
set(value) {
|
|
@@ -4,7 +4,12 @@ import StyleGuideView from './view'
|
|
|
4
4
|
import StyleGuideLinks from './links'
|
|
5
5
|
import { getStateObj } from '../states'
|
|
6
6
|
|
|
7
|
-
const StyleGuideApp = ({
|
|
7
|
+
const StyleGuideApp = ({
|
|
8
|
+
config,
|
|
9
|
+
styleGuideConfig,
|
|
10
|
+
translations,
|
|
11
|
+
headingLevel = 2,
|
|
12
|
+
}) => {
|
|
8
13
|
const [staticState, setStaticState] = useState(null)
|
|
9
14
|
const [selectedStateDescription, setSelectedStateDescription] = useState('')
|
|
10
15
|
const [showStyleGuide, setShowStyleGuide] = useState(true)
|
|
@@ -138,7 +143,7 @@ const StyleGuideApp = ({ config, styleGuideConfig, headingLevel = 2 }) => {
|
|
|
138
143
|
{showStyleGuide && (
|
|
139
144
|
<StyleGuideView
|
|
140
145
|
customComponents={styleGuideConfig.customComponents}
|
|
141
|
-
translations={
|
|
146
|
+
translations={translations}
|
|
142
147
|
state={staticState}
|
|
143
148
|
/>
|
|
144
149
|
)}
|
|
@@ -9,8 +9,12 @@ import {
|
|
|
9
9
|
import stateReducer from '../../domains/store/state-reducer'
|
|
10
10
|
import { Reducer as formReducer } from '../../domains/forms'
|
|
11
11
|
import { Reducer as translationsReducer } from '../../domains/translations'
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
Reducer as i18nReducer,
|
|
14
|
+
Actions as I18nActions,
|
|
15
|
+
} from '../../domains/i18n'
|
|
13
16
|
import { Reducer as interruptReducer } from '../../domains/interrupt'
|
|
17
|
+
import { Reducer as appReducer } from '../../domains/app'
|
|
14
18
|
import {
|
|
15
19
|
Reducer as configReducer,
|
|
16
20
|
Actions as ConfigActions,
|
|
@@ -24,7 +28,7 @@ const bareApi = {
|
|
|
24
28
|
store: { get: () => {}, set: () => {} },
|
|
25
29
|
}
|
|
26
30
|
|
|
27
|
-
const SeamlyTestCore = ({ state, children }) => {
|
|
31
|
+
const SeamlyTestCore = ({ state, translations, children }) => {
|
|
28
32
|
const liveMsgRef = useRef(() => {})
|
|
29
33
|
const eventBusRef = useRef({ emit: () => {} })
|
|
30
34
|
|
|
@@ -38,6 +42,7 @@ const SeamlyTestCore = ({ state, children }) => {
|
|
|
38
42
|
const newStore = createReduxStore({
|
|
39
43
|
reducers: {
|
|
40
44
|
state: stateReducer,
|
|
45
|
+
[String(appReducer)]: appReducer,
|
|
41
46
|
[String(configReducer)]: configReducer,
|
|
42
47
|
[String(formReducer)]: formReducer,
|
|
43
48
|
[String(translationsReducer)]: translationsReducer,
|
|
@@ -51,8 +56,9 @@ const SeamlyTestCore = ({ state, children }) => {
|
|
|
51
56
|
},
|
|
52
57
|
})
|
|
53
58
|
newStore.dispatch(ConfigActions.initialize(configSlice || {}))
|
|
59
|
+
newStore.dispatch(I18nActions.setLocale.fulfilled('en-GB', translations))
|
|
54
60
|
return newStore
|
|
55
|
-
}, [state])
|
|
61
|
+
}, [state, translations])
|
|
56
62
|
|
|
57
63
|
return (
|
|
58
64
|
state && (
|
|
@@ -479,7 +479,7 @@ const imageMessage = {
|
|
|
479
479
|
description: 'Plaatje',
|
|
480
480
|
isZoomable: false,
|
|
481
481
|
type: 'image',
|
|
482
|
-
url: 'https://
|
|
482
|
+
url: 'https://developers.seamly.ai/clients/web-ui/static/photos/image-square-small.jpg',
|
|
483
483
|
},
|
|
484
484
|
fromClient: false,
|
|
485
485
|
fromHistory: true,
|
|
@@ -504,7 +504,7 @@ const imageMessageWithLightbox = {
|
|
|
504
504
|
description: 'Plaatje',
|
|
505
505
|
isZoomable: true,
|
|
506
506
|
type: 'image',
|
|
507
|
-
url: 'https://
|
|
507
|
+
url: 'https://developers.seamly.ai/clients/web-ui/static/photos/image-portrait.jpg',
|
|
508
508
|
},
|
|
509
509
|
fromClient: false,
|
|
510
510
|
fromHistory: true,
|
|
@@ -654,7 +654,7 @@ const fileDownloadPayload = {
|
|
|
654
654
|
contentType: 'image/jpg',
|
|
655
655
|
filename: 'placeholder.jpg',
|
|
656
656
|
filesize: 991078,
|
|
657
|
-
url: 'https://
|
|
657
|
+
url: 'https://developers.seamly.ai/clients/web-ui/static/photos/image-square-small.jpg',
|
|
658
658
|
},
|
|
659
659
|
}
|
|
660
660
|
|
|
@@ -796,7 +796,7 @@ const cardAskText = {
|
|
|
796
796
|
description:
|
|
797
797
|
'Pizza Margherita is a **typical Neapolitan pizza**.\n\nIt is made with San Marzano tomatoes, mozzarella cheese, fresh basil, salt, and extra-virgin olive oil.',
|
|
798
798
|
image:
|
|
799
|
-
'https://
|
|
799
|
+
'https://developers.seamly.ai/clients/web-ui/static/photos/card-square.jpg',
|
|
800
800
|
title: 'Pizza Margherita',
|
|
801
801
|
},
|
|
802
802
|
},
|
|
@@ -815,7 +815,7 @@ const cardNavigate = {
|
|
|
815
815
|
buttonText: 'Order now!',
|
|
816
816
|
description: 'Pizza Margherita is a **typical Neapolitan pizza**.',
|
|
817
817
|
image:
|
|
818
|
-
'https://
|
|
818
|
+
'https://developers.seamly.ai/clients/web-ui/static/photos/card-landscape.jpg',
|
|
819
819
|
title: 'Pizza Margherita',
|
|
820
820
|
},
|
|
821
821
|
},
|
|
@@ -832,7 +832,7 @@ const cardTopic = {
|
|
|
832
832
|
},
|
|
833
833
|
buttonText: 'Set topic! (title & description optional)',
|
|
834
834
|
image:
|
|
835
|
-
'https://
|
|
835
|
+
'https://developers.seamly.ai/clients/web-ui/static/photos/card-portrait.jpg',
|
|
836
836
|
},
|
|
837
837
|
},
|
|
838
838
|
}
|
|
@@ -1182,9 +1182,9 @@ const standardState = {
|
|
|
1182
1182
|
showDisclaimer: true,
|
|
1183
1183
|
},
|
|
1184
1184
|
},
|
|
1185
|
-
|
|
1185
|
+
chatStatusBar: {
|
|
1186
1186
|
category: categoryKeys.features,
|
|
1187
|
-
headingText: `
|
|
1187
|
+
headingText: `Chat status bar`,
|
|
1188
1188
|
description: '',
|
|
1189
1189
|
...baseState,
|
|
1190
1190
|
options: {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { render } from 'preact'
|
|
2
|
-
import { Engine } from '@seamly/web-ui'
|
|
3
|
-
import en from '@seamly/web-ui/translations/en'
|
|
2
|
+
import { API, Engine } from '@seamly/web-ui'
|
|
4
3
|
import StyleGuideApp from './components/app'
|
|
5
4
|
|
|
6
5
|
class SeamlyStyleGuideInstance extends Engine {
|
|
@@ -9,31 +8,35 @@ class SeamlyStyleGuideInstance extends Engine {
|
|
|
9
8
|
this.styleGuideConfig = styleGuideConfig || {}
|
|
10
9
|
}
|
|
11
10
|
|
|
12
|
-
render() {
|
|
11
|
+
async render() {
|
|
13
12
|
const restComponents = {
|
|
14
13
|
...(this.config.customComponents || {}),
|
|
15
14
|
view: undefined,
|
|
16
15
|
}
|
|
17
16
|
|
|
17
|
+
const api = new API({
|
|
18
|
+
namespace: this.config.namespace,
|
|
19
|
+
config: this.config.api,
|
|
20
|
+
})
|
|
21
|
+
api.URLS = {
|
|
22
|
+
translations: `/client/${this.config.api.key}/translations/{version}/{locale}.json`,
|
|
23
|
+
}
|
|
24
|
+
const translations = await api.getTranslations(
|
|
25
|
+
this.config.context.locale || 'en-GB',
|
|
26
|
+
)
|
|
27
|
+
|
|
18
28
|
const renderConfig = {
|
|
19
29
|
...this.config,
|
|
20
30
|
customComponents: Object.keys(restComponents).length
|
|
21
31
|
? restComponents
|
|
22
32
|
: undefined,
|
|
23
|
-
translations: this.config.translations || {
|
|
24
|
-
...en,
|
|
25
|
-
disclaimer: {
|
|
26
|
-
...en.disclaimer,
|
|
27
|
-
content:
|
|
28
|
-
'This chat session will be saved to help us improve our service delivery. <a href="https://seamly.ai/">More information</a>',
|
|
29
|
-
},
|
|
30
|
-
},
|
|
31
33
|
}
|
|
32
34
|
|
|
33
35
|
render(
|
|
34
36
|
<StyleGuideApp
|
|
35
37
|
config={renderConfig}
|
|
36
38
|
styleGuideConfig={this.styleGuideConfig}
|
|
39
|
+
translations={translations}
|
|
37
40
|
/>,
|
|
38
41
|
this.parentElement,
|
|
39
42
|
)
|
|
@@ -23,8 +23,8 @@ const Upload = ({ event, ...props }) => {
|
|
|
23
23
|
const srText = useMemo(
|
|
24
24
|
() =>
|
|
25
25
|
url
|
|
26
|
-
? t('fileUpload.srFileDownloadText', filename)
|
|
27
|
-
: t('fileUpload.srFileUploadedText', filename),
|
|
26
|
+
? t('fileUpload.srFileDownloadText', { fileName: filename })
|
|
27
|
+
: t('fileUpload.srFileUploadedText', { fileName: filename }),
|
|
28
28
|
[url, filename, t],
|
|
29
29
|
)
|
|
30
30
|
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
useSeamlyIdleDetachCountdown,
|
|
6
6
|
} from '../../hooks/seamly-hooks'
|
|
7
7
|
import { activitySendDelay } from '../../../config'
|
|
8
|
+
import { className } from '../../../lib/css'
|
|
8
9
|
|
|
9
10
|
const SeamlyActivityMonitor = ({ children }) => {
|
|
10
11
|
const prevSendTimestamp = useRef(0)
|
|
@@ -35,6 +36,7 @@ const SeamlyActivityMonitor = ({ children }) => {
|
|
|
35
36
|
// be fired inside the container on the initial focus event.
|
|
36
37
|
return (
|
|
37
38
|
<div
|
|
39
|
+
className={className('activity-monitor')}
|
|
38
40
|
tabIndex="-1"
|
|
39
41
|
onMouseDown={onActivityHandler}
|
|
40
42
|
onKeyUp={onActivityHandler}
|
|
@@ -13,6 +13,7 @@ import SeamlyGeneralError from '../../../api/errors/seamly-general-error'
|
|
|
13
13
|
import SeamlySessionExpiredError from '../../../api/errors/seamly-session-expired-error'
|
|
14
14
|
import SeamlyOfflineError from '../../../api/errors/seamly-offline-error'
|
|
15
15
|
import { Actions as InterruptActions } from '../../../domains/interrupt'
|
|
16
|
+
import { Actions as AppActions } from '../../../domains/app'
|
|
16
17
|
|
|
17
18
|
const {
|
|
18
19
|
ADD_EVENT,
|
|
@@ -92,6 +93,7 @@ const SeamlyEventSubscriber = ({ eventBus }) => {
|
|
|
92
93
|
dispatch({ type: INIT_RESUME_CONVERSATION_PROMPT })
|
|
93
94
|
break
|
|
94
95
|
case 'user_first_response':
|
|
96
|
+
dispatch(AppActions.setHasResponded(true))
|
|
95
97
|
eventBus.emit('system.userFirstResponse', payload.body)
|
|
96
98
|
break
|
|
97
99
|
}
|
|
@@ -59,13 +59,7 @@ const SeamlyInstanceFunctionsLoader = () => {
|
|
|
59
59
|
},
|
|
60
60
|
[api?.send],
|
|
61
61
|
)
|
|
62
|
-
|
|
63
|
-
'setLocale',
|
|
64
|
-
(locale) => {
|
|
65
|
-
sendContext({ locale })
|
|
66
|
-
},
|
|
67
|
-
[api?.send],
|
|
68
|
-
)
|
|
62
|
+
|
|
69
63
|
useSeamlyInstanceFunction(
|
|
70
64
|
'setVariables',
|
|
71
65
|
(variables) => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect, useRef,
|
|
1
|
+
import { useEffect, useRef, useMemo } from 'preact/hooks'
|
|
2
2
|
import { useI18n } from '../../../domains/i18n'
|
|
3
3
|
import {
|
|
4
4
|
useEvents,
|
|
@@ -20,8 +20,8 @@ const SeamlyNewNotifications = () => {
|
|
|
20
20
|
const prevIsOpen = useRef(null)
|
|
21
21
|
const debounceFunc = useRef(null)
|
|
22
22
|
|
|
23
|
-
const notifyUnread =
|
|
24
|
-
debounce((eventArray) => {
|
|
23
|
+
const notifyUnread = useMemo(() => {
|
|
24
|
+
return debounce((eventArray) => {
|
|
25
25
|
const serverEventCount = eventArray.filter(
|
|
26
26
|
({ payload }) => !payload.fromClient && !payload.fromHistory,
|
|
27
27
|
).length
|
|
@@ -33,9 +33,8 @@ const SeamlyNewNotifications = () => {
|
|
|
33
33
|
)
|
|
34
34
|
previousServerEventCount.current = serverEventCount
|
|
35
35
|
}
|
|
36
|
-
}, newMessageScreenReaderWait)
|
|
37
|
-
|
|
38
|
-
)
|
|
36
|
+
}, newMessageScreenReaderWait)
|
|
37
|
+
}, [sendPolite, t])
|
|
39
38
|
|
|
40
39
|
useEffect(() => {
|
|
41
40
|
if (events.length > previousEventCount.current) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect,
|
|
1
|
+
import { useEffect, useRef, useMemo } from 'preact/hooks'
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
useEvents,
|
|
@@ -28,9 +28,11 @@ const SeamlyReadState = () => {
|
|
|
28
28
|
const { sendAction } = useSeamlyCommands()
|
|
29
29
|
const unreadCount = useSeamlyUnreadCount()
|
|
30
30
|
const { sendPolite } = useLiveRegion()
|
|
31
|
-
const sendLive =
|
|
32
|
-
sendPolite,
|
|
33
|
-
|
|
31
|
+
const sendLive = useMemo(
|
|
32
|
+
() => debounce(sendPolite, unreadScreenReaderWait),
|
|
33
|
+
|
|
34
|
+
[sendPolite],
|
|
35
|
+
)
|
|
34
36
|
const prevIsVisible = useRef(null)
|
|
35
37
|
const cancelSend = useRef(null)
|
|
36
38
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useEffect, useMemo } from 'preact/hooks'
|
|
2
2
|
import { useI18n } from '../../../../domains/i18n'
|
|
3
3
|
import { useLiveRegion } from '../../../hooks/live-region-hooks'
|
|
4
4
|
import { useEntryTextLimit } from '../../../hooks/seamly-state-hooks'
|
|
@@ -14,14 +14,16 @@ export function useCharacterLimit(controlName) {
|
|
|
14
14
|
const { sendAssertive } = useLiveRegion()
|
|
15
15
|
const { hasLimit, limit } = useEntryTextLimit()
|
|
16
16
|
|
|
17
|
-
const debouncedSendAssertive =
|
|
18
|
-
debounce(sendAssertive, maxCharacterSrDebounceDelay),
|
|
17
|
+
const debouncedSendAssertive = useMemo(
|
|
18
|
+
() => debounce(sendAssertive, maxCharacterSrDebounceDelay),
|
|
19
19
|
[sendAssertive],
|
|
20
20
|
)
|
|
21
21
|
const validateLimit = useMemo(() => {
|
|
22
22
|
return debounce((_reachedCharacterWarning, _remainingChars) => {
|
|
23
23
|
if (_reachedCharacterWarning) {
|
|
24
|
-
debouncedSendAssertive(
|
|
24
|
+
debouncedSendAssertive(
|
|
25
|
+
t('input.srCharacterLimitText', { limit: _remainingChars }),
|
|
26
|
+
)
|
|
25
27
|
}
|
|
26
28
|
}, maxCharacterSrDebounceDelay)
|
|
27
29
|
}, [debouncedSendAssertive, t])
|
|
@@ -33,18 +33,25 @@ export default function TextEntryForm({ controlName, skipLinkId }) {
|
|
|
33
33
|
|
|
34
34
|
const handleFocus = useCallback(() => {
|
|
35
35
|
if (reachedCharacterWarning) {
|
|
36
|
-
sendAssertive(t('input.srCharacterLimitText', remainingChars))
|
|
36
|
+
sendAssertive(t('input.srCharacterLimitText', { limit: remainingChars }))
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
emitEvent('ui.inputFocus')
|
|
40
40
|
}, [t, sendAssertive, reachedCharacterWarning, remainingChars, emitEvent])
|
|
41
41
|
const placeholder = useMemo(
|
|
42
42
|
() =>
|
|
43
|
-
t('input.inputPlaceholder',
|
|
43
|
+
t('input.inputPlaceholder', {
|
|
44
|
+
hasLimit: hasCharacterLimit,
|
|
45
|
+
limit: hasCharacterLimit ? characterLimit : null,
|
|
46
|
+
}),
|
|
44
47
|
[t, hasCharacterLimit, characterLimit],
|
|
45
48
|
)
|
|
46
49
|
const labelText = useMemo(
|
|
47
|
-
() =>
|
|
50
|
+
() =>
|
|
51
|
+
t('input.inputLabel', {
|
|
52
|
+
hasLimit: hasCharacterLimit,
|
|
53
|
+
limit: hasCharacterLimit ? characterLimit : null,
|
|
54
|
+
}),
|
|
48
55
|
[t, hasCharacterLimit, characterLimit],
|
|
49
56
|
)
|
|
50
57
|
// When the input holds a value, the component should be blocked from switching
|
|
@@ -15,8 +15,8 @@ export default function FileInputForm({
|
|
|
15
15
|
}) {
|
|
16
16
|
const { t } = useI18n()
|
|
17
17
|
const [{ value: fileList }] = useFormControl(controlName)
|
|
18
|
-
const
|
|
19
|
-
|
|
18
|
+
const hasFile = fileList && fileList.length > 0
|
|
19
|
+
const selectedFileName = hasFile ? fileList[0].name : ''
|
|
20
20
|
|
|
21
21
|
return (
|
|
22
22
|
<Form className={className('input', 'input--file')}>
|
|
@@ -25,7 +25,10 @@ export default function FileInputForm({
|
|
|
25
25
|
id={skiplinkId}
|
|
26
26
|
accept={accept}
|
|
27
27
|
labelText={t('fileUpload.labelText')}
|
|
28
|
-
outputText={t('fileUpload.selectedText',
|
|
28
|
+
outputText={t('fileUpload.selectedText', {
|
|
29
|
+
hasFile,
|
|
30
|
+
filename: selectedFileName,
|
|
31
|
+
})}
|
|
29
32
|
contentHint={contentHint}
|
|
30
33
|
/>
|
|
31
34
|
<div className={className('upload__button-container')}>
|
|
@@ -40,12 +40,14 @@ const Upload = () => {
|
|
|
40
40
|
|
|
41
41
|
const hasError = false
|
|
42
42
|
|
|
43
|
-
const { selectedFileName, uploadHandle, hasServerError, progress } =
|
|
43
|
+
const { hasFile, selectedFileName, uploadHandle, hasServerError, progress } =
|
|
44
44
|
useSingleFileUpload(formName, fileInputName)
|
|
45
45
|
const notificationId = useGeneratedId()
|
|
46
46
|
const prevIsComplete = useRef(true)
|
|
47
47
|
|
|
48
|
-
const contentHintText = t('fileUpload.contentHint',
|
|
48
|
+
const contentHintText = t('fileUpload.contentHint', {
|
|
49
|
+
size: formatBytes(maxSize),
|
|
50
|
+
})
|
|
49
51
|
const prevContentHintText = useRef('')
|
|
50
52
|
const containerRef = useRef(null)
|
|
51
53
|
|
|
@@ -177,7 +179,10 @@ const Upload = () => {
|
|
|
177
179
|
contentHint={contentHintText}
|
|
178
180
|
isComplete={isComplete}
|
|
179
181
|
isUploading={isUploading}
|
|
180
|
-
outputText={t('fileUpload.selectedText',
|
|
182
|
+
outputText={t('fileUpload.selectedText', {
|
|
183
|
+
hasFile,
|
|
184
|
+
filename: selectedFileName,
|
|
185
|
+
})}
|
|
181
186
|
onClickCancel={handleOnClickCancel}
|
|
182
187
|
/>
|
|
183
188
|
)}
|
|
@@ -10,7 +10,6 @@ import {
|
|
|
10
10
|
} from '../../hooks/seamly-state-hooks'
|
|
11
11
|
import { useGeneratedId } from '../../hooks/utility-hooks'
|
|
12
12
|
import { useSkiplinkTargetFocusing } from '../../hooks/focus-helper-hooks'
|
|
13
|
-
import { useSeamlyHasUserResponded } from '../../hooks/seamly-api-hooks'
|
|
14
13
|
import { useLiveRegion } from '../../hooks/live-region-hooks'
|
|
15
14
|
import useSeamlyIdleDetachCountdown from '../../hooks/use-seamly-idle-detach-countdown'
|
|
16
15
|
import useSeamlyResumeConversationPrompt from '../../hooks/use-seamly-resume-conversation-prompt'
|
|
@@ -20,6 +19,7 @@ import InOutTransition, {
|
|
|
20
19
|
} from '../widgets/in-out-transition'
|
|
21
20
|
import { useTranslatedEventData } from '../../../domains/translations'
|
|
22
21
|
import { useInterrupt } from '../../../domains/interrupt'
|
|
22
|
+
import { useUserHasResponded } from '../../../domains/app'
|
|
23
23
|
|
|
24
24
|
const Faq = () => {
|
|
25
25
|
const { t } = useI18n()
|
|
@@ -55,7 +55,7 @@ const Faq = () => {
|
|
|
55
55
|
const prevHasFaqs = useRef(false)
|
|
56
56
|
|
|
57
57
|
const { isInline } = useSeamlyLayoutMode()
|
|
58
|
-
const hasResponded =
|
|
58
|
+
const hasResponded = useUserHasResponded()
|
|
59
59
|
const hideForWindow = !isInline && hasResponded
|
|
60
60
|
const prevHideForWindow = useRef(hideForWindow)
|
|
61
61
|
|
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useCallback, useMemo } from 'preact/hooks'
|
|
2
2
|
import { className } from '../../../lib/css'
|
|
3
3
|
import {
|
|
4
4
|
useSeamlyAppContainerClassNames,
|
|
5
5
|
useSeamlyVisibility,
|
|
6
6
|
useSeamlyLayoutMode,
|
|
7
|
-
useSeamlyHasUserResponded,
|
|
8
7
|
useSeamlyContainerElement,
|
|
9
8
|
} from '../../hooks/seamly-hooks'
|
|
10
9
|
import Faq from '../faq/faq'
|
|
11
10
|
import { visibilityStates } from '../../utils/seamly-utils'
|
|
12
11
|
import { useConfig } from '../../../domains/config'
|
|
12
|
+
import { useUserHasResponded } from '../../../domains/app'
|
|
13
|
+
import { useI18n } from '../../../domains/i18n'
|
|
13
14
|
|
|
14
15
|
const AppFrame = ({ children }) => {
|
|
15
16
|
const [, setSeamlyContainerElement] = useSeamlyContainerElement()
|
|
16
17
|
const { isOpen, isVisible, setVisibility } = useSeamlyVisibility()
|
|
17
|
-
const {
|
|
18
|
+
const { zIndex, showFaq } = useConfig()
|
|
18
19
|
const { isModal, isInline } = useSeamlyLayoutMode()
|
|
19
20
|
const appContainerClassNames = useSeamlyAppContainerClassNames()
|
|
20
|
-
const userResponded =
|
|
21
|
-
const
|
|
22
|
-
const { locale } = context || {}
|
|
21
|
+
const userResponded = useUserHasResponded()
|
|
22
|
+
const { locale } = useI18n()
|
|
23
23
|
|
|
24
24
|
const containerElementRef = useCallback(
|
|
25
25
|
(container) => {
|
|
@@ -28,13 +28,16 @@ const AppFrame = ({ children }) => {
|
|
|
28
28
|
[setSeamlyContainerElement],
|
|
29
29
|
)
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
const blockLang = useMemo(() => {
|
|
32
32
|
if (locale) {
|
|
33
33
|
const htmlElementLang = document
|
|
34
34
|
.querySelector('html')
|
|
35
35
|
.getAttribute('lang')
|
|
36
|
-
if (
|
|
36
|
+
if (htmlElementLang !== locale) {
|
|
37
|
+
return locale
|
|
38
|
+
}
|
|
37
39
|
}
|
|
40
|
+
return undefined
|
|
38
41
|
}, [locale])
|
|
39
42
|
|
|
40
43
|
const classNames = ['app', ...appContainerClassNames]
|
|
@@ -25,14 +25,18 @@ const Interrupt = ({
|
|
|
25
25
|
useEffect(() => {
|
|
26
26
|
if (isExpiredError) {
|
|
27
27
|
seamlyCommands[action]()
|
|
28
|
-
}
|
|
28
|
+
}
|
|
29
|
+
}, [action, seamlyCommands, isExpiredError])
|
|
30
|
+
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (!isExpiredError && srText) {
|
|
29
33
|
// Wait for live regions to stabilise in case this occurs
|
|
30
34
|
// at an initial render
|
|
31
35
|
setTimeout(() => {
|
|
32
36
|
sendPolite(srText)
|
|
33
37
|
}, 200)
|
|
34
38
|
}
|
|
35
|
-
}, [sendPolite,
|
|
39
|
+
}, [sendPolite, srText, isExpiredError])
|
|
36
40
|
|
|
37
41
|
const onClickHandler = () => {
|
|
38
42
|
seamlyCommands[action]()
|
|
@@ -20,7 +20,7 @@ const ResumeConversationPrompt = () => {
|
|
|
20
20
|
return (
|
|
21
21
|
<Prompt
|
|
22
22
|
baseClassName="prompt"
|
|
23
|
-
title={t('resumeConversationPrompt.title', currentAgentName)}
|
|
23
|
+
title={t('resumeConversationPrompt.title', { name: currentAgentName })}
|
|
24
24
|
>
|
|
25
25
|
<div className={className('prompt__options')}>
|
|
26
26
|
<button
|
|
@@ -25,7 +25,7 @@ const UploadProgress = () => {
|
|
|
25
25
|
role="progressbar"
|
|
26
26
|
aria-valuemin="0"
|
|
27
27
|
aria-valuemax="100"
|
|
28
|
-
aria-label={t('fileUpload.srProgressLabel', name)}
|
|
28
|
+
aria-label={t('fileUpload.srProgressLabel', { fileName: name })}
|
|
29
29
|
max="100"
|
|
30
30
|
aria-valuenow={progress}
|
|
31
31
|
value={progress}
|
|
@@ -22,9 +22,3 @@ export const useSeamlyHasConversation = () => {
|
|
|
22
22
|
const url = useSeamlyConversationUrl()
|
|
23
23
|
return !!url
|
|
24
24
|
}
|
|
25
|
-
|
|
26
|
-
export const useSeamlyHasUserResponded = () => {
|
|
27
|
-
const { get } = useSeamlyObjectStore()
|
|
28
|
-
|
|
29
|
-
return get ? !!get('userResponded') : false
|
|
30
|
-
}
|