@capillarytech/creatives-library 8.0.204 → 8.0.205
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/package.json +1 -1
- package/v2Components/FormBuilder/constants.js +4 -0
- package/v2Components/FormBuilder/index.js +307 -9
- package/v2Containers/BeeEditor/index.js +1 -1
- package/v2Containers/CreativesContainer/index.js +62 -17
- package/v2Containers/Email/index.js +3 -0
- package/v2Containers/MobilePush/Create/index.js +3 -3
package/package.json
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
export const OUTBOUND = 'OUTBOUND';
|
|
2
|
+
export const ADD_LANGUAGE = 'addLanguage';
|
|
3
|
+
export const UPLOAD = 'upload';
|
|
4
|
+
export const USE_EDITOR = 'useEditor';
|
|
5
|
+
export const COPY_PRIMARY_LANGUAGE = 'copyPrimaryLanguage';
|
|
2
6
|
export const GLOBAL_CONVERT_OPTIONS = {
|
|
3
7
|
selectors: [
|
|
4
8
|
...[1, 2, 3, 4, 5, 6].map(level => ({
|
|
@@ -54,7 +54,7 @@ import { containsBase64Images } from '../../utils/content';
|
|
|
54
54
|
import { SMS, MOBILE_PUSH, LINE, ENABLE_AI_SUGGESTIONS,AI_CONTENT_BOT_DISABLED, EMAIL, LIQUID_SUPPORTED_CHANNELS, INAPP } from '../../v2Containers/CreativesContainer/constants';
|
|
55
55
|
import globalMessages from '../../v2Containers/Cap/messages';
|
|
56
56
|
import { convert } from 'html-to-text';
|
|
57
|
-
import { OUTBOUND } from './constants';
|
|
57
|
+
import { OUTBOUND, ADD_LANGUAGE, UPLOAD, USE_EDITOR, COPY_PRIMARY_LANGUAGE } from './constants';
|
|
58
58
|
import { GET_TRANSLATION_MAPPED } from '../../constants/unified';
|
|
59
59
|
import moment from 'moment';
|
|
60
60
|
import { CUSTOMER_BARCODE_TAG , COPY_OF, ENTRY_TRIGGER_TAG_REGEX} from '../../constants/unified';
|
|
@@ -139,8 +139,196 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
139
139
|
// Check if the liquid flow feature is supported and the channel is in the supported list.
|
|
140
140
|
this.liquidFlow = this.isLiquidFlowSupported.bind(this);
|
|
141
141
|
this.onSubmitWrapper = this.onSubmitWrapper.bind(this);
|
|
142
|
+
|
|
143
|
+
// Performance optimization: Debounced functions for high-frequency updates
|
|
144
|
+
this.debouncedUpdateFormData = _.debounce((data, val, event, skipStateUpdate) => {
|
|
145
|
+
this.performFormDataUpdate(data, val, event, skipStateUpdate);
|
|
146
|
+
}, 300);
|
|
147
|
+
this.debouncedValidation = _.debounce(this.validateForm.bind(this), 500);
|
|
148
|
+
|
|
149
|
+
// Memoized validation cache
|
|
150
|
+
this.validationCache = new Map();
|
|
151
|
+
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Helper function to generate unique tab ID
|
|
155
|
+
generateUniqueTabId(formData, tabIndex) {
|
|
156
|
+
let id = _.uniqueId();
|
|
157
|
+
let validId = false;
|
|
158
|
+
|
|
159
|
+
while (!validId) {
|
|
160
|
+
validId = true;
|
|
161
|
+
for (let idx = 0; idx < formData[tabIndex].selectedLanguages.length; idx += 1) {
|
|
162
|
+
if (!formData[tabIndex]) {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (id === formData[tabIndex][formData[tabIndex].selectedLanguages[idx]].tabKey) {
|
|
166
|
+
validId = false;
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (!validId) {
|
|
171
|
+
id = _.uniqueId();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return id;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Performance optimized form data update function
|
|
179
|
+
performFormDataUpdate(data, val, event, skipStateUpdate = false) {
|
|
180
|
+
|
|
181
|
+
// Use optimized state update instead of deep cloning
|
|
182
|
+
const formData = this.optimizedFormDataUpdate(data, val, event);
|
|
183
|
+
|
|
184
|
+
const tabIndex = this.state.currentTab - 1;
|
|
185
|
+
let currentTab = this.state.currentTab;
|
|
186
|
+
|
|
187
|
+
if (this.state.usingTabContainer && !val.standalone) {
|
|
188
|
+
const data1 = data;
|
|
189
|
+
if (event === ADD_LANGUAGE) {
|
|
190
|
+
const addLanguageType = this.props.addLanguageType;
|
|
191
|
+
if (addLanguageType === '') {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
const currentLang = formData[tabIndex].activeTab;
|
|
195
|
+
let baseTab;
|
|
196
|
+
|
|
197
|
+
switch (addLanguageType) {
|
|
198
|
+
case UPLOAD:
|
|
199
|
+
baseTab = _.cloneDeep(formData[tabIndex][currentLang]);
|
|
200
|
+
|
|
201
|
+
baseTab.iso_code = data.iso_code;
|
|
202
|
+
baseTab.lang_id = data.lang_id;
|
|
203
|
+
baseTab.language = data.language;
|
|
204
|
+
baseTab.tabKey = this.generateUniqueTabId(formData, tabIndex);
|
|
205
|
+
|
|
206
|
+
formData[tabIndex][data.iso_code] = baseTab;
|
|
207
|
+
formData[tabIndex].activeTab = data.iso_code;
|
|
208
|
+
formData[tabIndex].tabKey = baseTab.tabKey;
|
|
209
|
+
break;
|
|
210
|
+
case COPY_PRIMARY_LANGUAGE:
|
|
211
|
+
case USE_EDITOR:
|
|
212
|
+
baseTab = _.cloneDeep(formData[tabIndex][this.props.baseLanguage]);
|
|
213
|
+
|
|
214
|
+
baseTab.iso_code = data.iso_code;
|
|
215
|
+
baseTab.lang_id = data.lang_id;
|
|
216
|
+
baseTab.language = data.language;
|
|
217
|
+
baseTab.tabKey = this.generateUniqueTabId(formData, tabIndex);
|
|
218
|
+
|
|
219
|
+
formData[tabIndex].selectedLanguages.push(data.iso_code);
|
|
220
|
+
formData[tabIndex][data.iso_code] = baseTab;
|
|
221
|
+
formData[tabIndex].activeTab = data.iso_code;
|
|
222
|
+
formData[tabIndex].tabKey = baseTab.tabKey;
|
|
223
|
+
break;
|
|
224
|
+
case '':
|
|
225
|
+
return;
|
|
226
|
+
default:
|
|
227
|
+
break;
|
|
228
|
+
}
|
|
229
|
+
const that = this;
|
|
230
|
+
setTimeout(() => {
|
|
231
|
+
that.setState({tabKey: baseTab.tabKey});
|
|
232
|
+
}, 0);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (!this.props.isNewVersionFlow) {
|
|
236
|
+
formData[tabIndex][val.id] = data1;
|
|
237
|
+
} else if (this.props.isNewVersionFlow && event !== ADD_LANGUAGE && event !== "onContentChange") {
|
|
238
|
+
formData[tabIndex][this.props.baseLanguage][val.id] = data1;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (formData[tabIndex].base) {
|
|
242
|
+
if (!this.props.isNewVersionFlow) {
|
|
243
|
+
formData.base[val.id] = data1;
|
|
244
|
+
} else {
|
|
245
|
+
formData.base[data1.iso_code] = formData[tabIndex][data1.iso_code];
|
|
246
|
+
formData.base.tabKey = formData[tabIndex].tabKey;
|
|
247
|
+
formData.base.activeTab = formData[tabIndex].activeTab;
|
|
248
|
+
formData.base.selectedLanguages = formData[tabIndex].selectedLanguages;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
} else {
|
|
252
|
+
formData[val.id] = data;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (this.props.isNewVersionFlow) {
|
|
256
|
+
if (event === 'onSelect' && data === 'New Version') {
|
|
257
|
+
this.callChildEvent(data, val, 'addVersion', event);
|
|
258
|
+
} else if (event === 'onSelect' && data !== 'New Version') {
|
|
259
|
+
currentTab = _.findIndex(this.state.formData['template-version-options'], { key: data}) + 1;
|
|
260
|
+
this.setState({currentTab, tabKey: formData[`${currentTab - 1}`].tabKey}, () => {
|
|
261
|
+
val.injectedEvents[event].call(this, this.state.formData['template-version-options'][currentTab - 1].key, formData, val);
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (event === 'onContentChange') {
|
|
266
|
+
// Content change handling
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Only update state if not already updated (for immediate UI feedback)
|
|
271
|
+
if (!skipStateUpdate) {
|
|
272
|
+
this.setState({formData}, () => {
|
|
273
|
+
if (this.props.startValidation) {
|
|
274
|
+
this.debouncedValidation();
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
} else {
|
|
278
|
+
// Just trigger validation if state was already updated
|
|
279
|
+
if (this.props.startValidation) {
|
|
280
|
+
this.debouncedValidation();
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (event && val.injectedEvents[event]) {
|
|
285
|
+
if (event === "onRowClick") {
|
|
286
|
+
val.injectedEvents[event].call(this, data);
|
|
287
|
+
} else if (this.props.isNewVersionFlow && event !== 'onSelect') {
|
|
288
|
+
if (event === ADD_LANGUAGE) {
|
|
289
|
+
val.injectedEvents[event].call(this, data, formData, val);
|
|
290
|
+
this.setState({currentEventVal: {}, currentEvent: {}, currentEventData: {}});
|
|
291
|
+
} else {
|
|
292
|
+
val.injectedEvents[event].call(this, true, formData, val);
|
|
293
|
+
}
|
|
294
|
+
} else if (!this.props.isNewVersionFlow) {
|
|
295
|
+
val.injectedEvents[event].call(this, true, formData, val);
|
|
296
|
+
}
|
|
297
|
+
} else if (val.injectedEvents && val.injectedEvents.onChange) {
|
|
298
|
+
val.injectedEvents.onChange.call(this, true, formData, val);
|
|
299
|
+
}
|
|
142
300
|
|
|
301
|
+
if (!((event === 'onSelect' && data === 'New Version') || event === 'onContentChange')) {
|
|
302
|
+
this.props.onChange(formData, this.state.tabCount, currentTab, val);
|
|
303
|
+
}
|
|
143
304
|
}
|
|
305
|
+
|
|
306
|
+
// Optimized form data update - only clone what's necessary
|
|
307
|
+
optimizedFormDataUpdate(data, val, event) {
|
|
308
|
+
const currentFormData = this.state.formData;
|
|
309
|
+
|
|
310
|
+
// For simple field updates, use spread operator instead of deep clone
|
|
311
|
+
if (!this.state.usingTabContainer || val.standalone) {
|
|
312
|
+
return {
|
|
313
|
+
...currentFormData,
|
|
314
|
+
[val.id]: data
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// For tab container updates, only clone the affected tab
|
|
319
|
+
const tabIndex = this.state.currentTab - 1;
|
|
320
|
+
const updatedFormData = { ...currentFormData };
|
|
321
|
+
|
|
322
|
+
if (updatedFormData[tabIndex]) {
|
|
323
|
+
updatedFormData[tabIndex] = {
|
|
324
|
+
...updatedFormData[tabIndex],
|
|
325
|
+
[val.id]: data
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return updatedFormData;
|
|
330
|
+
}
|
|
331
|
+
|
|
144
332
|
isLiquidFlowSupported = () => {
|
|
145
333
|
return Boolean(LIQUID_SUPPORTED_CHANNELS.includes(this.props?.schema?.channel?.toUpperCase()) && hasLiquidSupportFeature());
|
|
146
334
|
}
|
|
@@ -269,7 +457,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
269
457
|
}
|
|
270
458
|
this.setState({formData: nextProps.formData, currentLangTab}, () => {
|
|
271
459
|
if (nextProps?.isNewVersionFlow && !this.state?.formData[this.state?.currentTab - 1][this.state?.formData[this.state?.currentTab - 1]?.activeTab]?.tabKey) {
|
|
272
|
-
|
|
460
|
+
this.resetTabKeys(nextProps.formData, nextProps.tabCount, false, true);
|
|
273
461
|
}
|
|
274
462
|
if (type === 'embedded' || ( this.props.schema.channel && this.props.schema.channel.toUpperCase() === 'EMAIL')) {
|
|
275
463
|
// Don't run validation if we're in Test & Preview mode
|
|
@@ -401,7 +589,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
401
589
|
const event = this.state.currentEvent;
|
|
402
590
|
if (!_.isEmpty(this.state.currentEvent)) {
|
|
403
591
|
switch (event) {
|
|
404
|
-
case
|
|
592
|
+
case ADD_LANGUAGE:
|
|
405
593
|
this.updateFormData(this.state.currentEventData, this.state.currentEventVal, this.state.currentEvent);
|
|
406
594
|
break;
|
|
407
595
|
default:
|
|
@@ -916,7 +1104,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
916
1104
|
}
|
|
917
1105
|
}
|
|
918
1106
|
if(_.isEmpty(androidData) && this.state.currentTab == 2){
|
|
919
|
-
this.setState({androidValid, iosValid}, () => {
|
|
1107
|
+
this.setState({androidValid, iosValid, errorData}, () => {
|
|
920
1108
|
this.props.setModalContent('ios');
|
|
921
1109
|
});
|
|
922
1110
|
// modal.body = "Android template is not configured. Save without Android template";
|
|
@@ -931,7 +1119,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
931
1119
|
}
|
|
932
1120
|
}
|
|
933
1121
|
if(_.isEmpty(iosData) && this.state.currentTab == 1){
|
|
934
|
-
this.setState({androidValid, iosValid}, () => {
|
|
1122
|
+
this.setState({androidValid, iosValid, errorData}, () => {
|
|
935
1123
|
this.props.setModalContent('android');
|
|
936
1124
|
});
|
|
937
1125
|
// modal.body = "IOS template is not configured, Save without IOS template";
|
|
@@ -2027,6 +2215,62 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
2027
2215
|
}
|
|
2028
2216
|
|
|
2029
2217
|
updateFormData(data, val, event) {
|
|
2218
|
+
|
|
2219
|
+
// Check if this is a high-frequency input field that should be optimized
|
|
2220
|
+
const isHighFrequencyField = val && (
|
|
2221
|
+
val.id === 'template-name' ||
|
|
2222
|
+
val.id === 'template-subject'
|
|
2223
|
+
);
|
|
2224
|
+
|
|
2225
|
+
if (isHighFrequencyField && !event) {
|
|
2226
|
+
// For high-frequency fields: immediate UI update + debounced expensive operations
|
|
2227
|
+
this.updateFormDataOptimized(data, val, event);
|
|
2228
|
+
return;
|
|
2229
|
+
}
|
|
2230
|
+
|
|
2231
|
+
// For non-high-frequency fields or special events, use immediate update
|
|
2232
|
+
this.performFormDataUpdate(data, val, event);
|
|
2233
|
+
}
|
|
2234
|
+
|
|
2235
|
+
// Optimized update for high-frequency fields
|
|
2236
|
+
updateFormDataOptimized(data, val, event) {
|
|
2237
|
+
// 1. Immediate UI update - update the field value instantly
|
|
2238
|
+
this.updateFieldValueImmediately(data, val);
|
|
2239
|
+
|
|
2240
|
+
// 2. Debounce expensive operations (validation, parent updates) - skip state update since we already did it
|
|
2241
|
+
this.debouncedUpdateFormData(data, val, event, true);
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2244
|
+
// Update field value immediately for UI feedback
|
|
2245
|
+
updateFieldValueImmediately(data, val) {
|
|
2246
|
+
const currentFormData = this.state.formData;
|
|
2247
|
+
let updatedFormData;
|
|
2248
|
+
|
|
2249
|
+
if (!this.state.usingTabContainer || val.standalone) {
|
|
2250
|
+
// Simple field update
|
|
2251
|
+
updatedFormData = {
|
|
2252
|
+
...currentFormData,
|
|
2253
|
+
[val.id]: data
|
|
2254
|
+
};
|
|
2255
|
+
} else {
|
|
2256
|
+
// Tab container update
|
|
2257
|
+
const tabIndex = this.state.currentTab - 1;
|
|
2258
|
+
updatedFormData = { ...currentFormData };
|
|
2259
|
+
|
|
2260
|
+
if (updatedFormData[tabIndex]) {
|
|
2261
|
+
updatedFormData[tabIndex] = {
|
|
2262
|
+
...updatedFormData[tabIndex],
|
|
2263
|
+
[val.id]: data
|
|
2264
|
+
};
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
2267
|
+
|
|
2268
|
+
// Update state immediately for UI feedback
|
|
2269
|
+
this.setState({ formData: updatedFormData });
|
|
2270
|
+
}
|
|
2271
|
+
|
|
2272
|
+
// Legacy updateFormData function - kept for backward compatibility
|
|
2273
|
+
updateFormDataLegacy(data, val, event) {
|
|
2030
2274
|
const formData = _.cloneDeep(this.state.formData);
|
|
2031
2275
|
|
|
2032
2276
|
const tabIndex = this.state.currentTab - 1;
|
|
@@ -2034,7 +2278,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
2034
2278
|
|
|
2035
2279
|
if (this.state.usingTabContainer && !val.standalone) {
|
|
2036
2280
|
const data1 = data;
|
|
2037
|
-
if (event ===
|
|
2281
|
+
if (event === ADD_LANGUAGE) {
|
|
2038
2282
|
const addLanguageType = this.props.addLanguageType;
|
|
2039
2283
|
if (addLanguageType === '') {
|
|
2040
2284
|
return;
|
|
@@ -2117,7 +2361,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
2117
2361
|
|
|
2118
2362
|
if (!this.props.isNewVersionFlow) {
|
|
2119
2363
|
formData[tabIndex][val.id] = data1;
|
|
2120
|
-
} else if (this.props.isNewVersionFlow && event !==
|
|
2364
|
+
} else if (this.props.isNewVersionFlow && event !== ADD_LANGUAGE && event !== "onContentChange") {
|
|
2121
2365
|
formData[tabIndex][this.props.baseLanguage][val.id] = data1;
|
|
2122
2366
|
}
|
|
2123
2367
|
|
|
@@ -2164,7 +2408,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
2164
2408
|
if (event === "onRowClick") {
|
|
2165
2409
|
val.injectedEvents[event].call(this, data);
|
|
2166
2410
|
} else if (this.props.isNewVersionFlow && event !== 'onSelect') {
|
|
2167
|
-
if (event ===
|
|
2411
|
+
if (event === ADD_LANGUAGE) {
|
|
2168
2412
|
val.injectedEvents[event].call(this, data, formData, val);
|
|
2169
2413
|
this.setState({currentEventVal: {}, currentEvent: {}, currentEventData: {}});
|
|
2170
2414
|
} else {
|
|
@@ -2186,6 +2430,57 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
2186
2430
|
hasClass(element, className) {
|
|
2187
2431
|
return (` ${element.className} `).indexOf(` ${className} `) > -1;
|
|
2188
2432
|
}
|
|
2433
|
+
|
|
2434
|
+
// Handle field blur for validation
|
|
2435
|
+
handleFieldBlur = (e, val) => {
|
|
2436
|
+
// Trigger validation on blur for high-frequency fields
|
|
2437
|
+
if (val && (val.id === 'template-name' || val.id === 'template-subject')) {
|
|
2438
|
+
this.debouncedValidation();
|
|
2439
|
+
}
|
|
2440
|
+
}
|
|
2441
|
+
|
|
2442
|
+
// Memoized validation for specific fields
|
|
2443
|
+
getMemoizedValidation = (fieldId, fieldValue) => {
|
|
2444
|
+
const cacheKey = `${fieldId}_${fieldValue}`;
|
|
2445
|
+
|
|
2446
|
+
if (this.validationCache.has(cacheKey)) {
|
|
2447
|
+
return this.validationCache.get(cacheKey);
|
|
2448
|
+
}
|
|
2449
|
+
|
|
2450
|
+
// Perform validation logic here
|
|
2451
|
+
const isValid = this.performFieldValidation(fieldId, fieldValue);
|
|
2452
|
+
this.validationCache.set(cacheKey, isValid);
|
|
2453
|
+
|
|
2454
|
+
// Clear cache if it gets too large
|
|
2455
|
+
if (this.validationCache.size > 100) {
|
|
2456
|
+
this.validationCache.clear();
|
|
2457
|
+
}
|
|
2458
|
+
|
|
2459
|
+
return isValid;
|
|
2460
|
+
}
|
|
2461
|
+
|
|
2462
|
+
// Perform validation for a specific field
|
|
2463
|
+
performFieldValidation = (fieldId, fieldValue) => {
|
|
2464
|
+
// Basic validation logic for template-name and template-subject
|
|
2465
|
+
if (fieldId === 'template-name' || fieldId === 'template-subject') {
|
|
2466
|
+
return fieldValue && fieldValue.trim().length > 0;
|
|
2467
|
+
}
|
|
2468
|
+
return true;
|
|
2469
|
+
}
|
|
2470
|
+
|
|
2471
|
+
// Cleanup debounced functions on component unmount
|
|
2472
|
+
componentWillUnmount() {
|
|
2473
|
+
if (this.debouncedUpdateFormData) {
|
|
2474
|
+
this.debouncedUpdateFormData.cancel();
|
|
2475
|
+
}
|
|
2476
|
+
if (this.debouncedValidation) {
|
|
2477
|
+
this.debouncedValidation.cancel();
|
|
2478
|
+
}
|
|
2479
|
+
// Clear validation cache
|
|
2480
|
+
if (this.validationCache) {
|
|
2481
|
+
this.validationCache.clear();
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2189
2484
|
allowAddSecondaryCta = (val) => {
|
|
2190
2485
|
if (val.fieldsCount > 0) {
|
|
2191
2486
|
const errorData = _.cloneDeep(this.state.errorData);
|
|
@@ -2601,6 +2896,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
2601
2896
|
label={val.label}
|
|
2602
2897
|
autosize={val.autosize ? val.autosizeParams : false}
|
|
2603
2898
|
onChange={(e) => this.updateFormData(e.target.value, val)}
|
|
2899
|
+
onBlur={(e) => this.handleFieldBlur(e, val)}
|
|
2604
2900
|
style={val.style ? val.style : {}}
|
|
2605
2901
|
defaultValue={messageContent || ''}
|
|
2606
2902
|
value={messageContent || ""}
|
|
@@ -2682,6 +2978,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
2682
2978
|
style={val.style ? val.style : {}}
|
|
2683
2979
|
placeholder={val.placeholder}
|
|
2684
2980
|
onChange={(e) => this.updateFormData(e.target.value, val)}
|
|
2981
|
+
onBlur={(e) => this.handleFieldBlur(e, val)}
|
|
2685
2982
|
defaultValue={isVersionEnable ? this.state.formData[`${this.state.currentTab - 1}`][val.id] : this.state.formData[val.id]}
|
|
2686
2983
|
value={value || ""}
|
|
2687
2984
|
disabled={val.disabled}
|
|
@@ -3166,6 +3463,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
3166
3463
|
style={val.style ? val.style : {}}
|
|
3167
3464
|
placeholder={val.placeholder}
|
|
3168
3465
|
onChange={(e) => this.updateFormData(e.target.value, val)}
|
|
3466
|
+
onBlur={(e) => this.handleFieldBlur(e, val)}
|
|
3169
3467
|
value={value || ""}
|
|
3170
3468
|
defaultValue={isVersionEnable ? this.state.formData[`${this.state.currentTab - 1}`][val.id] : this.state.formData[val.id]}
|
|
3171
3469
|
disabled={val.disabled}
|
|
@@ -3743,7 +4041,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
3743
4041
|
id={val.id}
|
|
3744
4042
|
popOverList={this.props.supportedLanguages}
|
|
3745
4043
|
excludeList={this.state.formData[`${this.state.currentTab - 1}`].selectedLanguages}
|
|
3746
|
-
handlePopOverClick={(data) => this.handleAddLanguageFlow(data, val,
|
|
4044
|
+
handlePopOverClick={(data) => this.handleAddLanguageFlow(data, val, ADD_LANGUAGE)}
|
|
3747
4045
|
visible={this.state.customPopoverVisible}
|
|
3748
4046
|
onVisibleChange={this.handleCustomPopoverVisibleChange}
|
|
3749
4047
|
/>
|
|
@@ -349,4 +349,4 @@ const withConnect = connect(mapStateToProps, mapDispatchToProps);
|
|
|
349
349
|
const withSaga = injectSaga({ key: 'beeEditor', saga: v2BeeEditionSagas });
|
|
350
350
|
const withReducer = injectReducer({ key: 'beeEditor', reducer: v2BeeEditionReducer });
|
|
351
351
|
|
|
352
|
-
export default compose(withReducer, withSaga, withConnect)(injectIntl(BeeEditor));
|
|
352
|
+
export default compose(withReducer, withSaga, withConnect)(injectIntl(React.memo(BeeEditor)));
|
|
@@ -11,7 +11,7 @@ import CapNotification from '@capillarytech/cap-ui-library/CapNotification';
|
|
|
11
11
|
import { injectIntl, FormattedMessage } from 'react-intl';
|
|
12
12
|
import classnames from 'classnames';
|
|
13
13
|
import {
|
|
14
|
-
isEmpty, get, forEach, cloneDeep,
|
|
14
|
+
isEmpty, get, forEach, cloneDeep, debounce,
|
|
15
15
|
} from 'lodash';
|
|
16
16
|
import { connect } from 'react-redux';
|
|
17
17
|
import { createStructuredSelector } from 'reselect';
|
|
@@ -105,6 +105,8 @@ export class Creatives extends React.Component {
|
|
|
105
105
|
// NEW: Test and Preview feature state
|
|
106
106
|
showTestAndPreviewSlidebox: false,
|
|
107
107
|
isTestAndPreviewMode: false, // Add flag to track Test & Preview mode
|
|
108
|
+
// Performance optimization: Local template name for immediate UI feedback
|
|
109
|
+
localTemplateName: '',
|
|
108
110
|
};
|
|
109
111
|
this.liquidFlow = Boolean(commonUtil.hasLiquidSupportFeature());
|
|
110
112
|
this.creativesTemplateSteps = {
|
|
@@ -112,6 +114,9 @@ export class Creatives extends React.Component {
|
|
|
112
114
|
2: 'templateSelection', // only for email in current flows wil be used for mpush, line and wechat as well.
|
|
113
115
|
3: 'createTemplateContent',
|
|
114
116
|
};
|
|
117
|
+
|
|
118
|
+
// Performance optimization: Debounced template name update
|
|
119
|
+
this.debouncedTemplateNameUpdate = debounce(this.performTemplateNameUpdate.bind(this), 300);
|
|
115
120
|
}
|
|
116
121
|
|
|
117
122
|
componentWillUnmount() {
|
|
@@ -119,6 +124,11 @@ export class Creatives extends React.Component {
|
|
|
119
124
|
this.props.templateActions.resetTemplateStoreData();
|
|
120
125
|
}
|
|
121
126
|
this.props.globalActions.clearMetaEntities();
|
|
127
|
+
|
|
128
|
+
// Cleanup debounced function
|
|
129
|
+
if (this.debouncedTemplateNameUpdate) {
|
|
130
|
+
this.debouncedTemplateNameUpdate.cancel();
|
|
131
|
+
}
|
|
122
132
|
}
|
|
123
133
|
|
|
124
134
|
componentDidMount() {
|
|
@@ -136,6 +146,29 @@ export class Creatives extends React.Component {
|
|
|
136
146
|
|
|
137
147
|
onEnterTemplateName = () => {
|
|
138
148
|
this.setState({ templateNameExists: true });
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Performance optimized template name update
|
|
152
|
+
performTemplateNameUpdate = (value, formData, onFormDataChange) => {
|
|
153
|
+
const isEmptyTemplateName = !value.trim();
|
|
154
|
+
const newFormData = { ...formData, 'template-name': value, 'isTemplateNameEdited': true };
|
|
155
|
+
|
|
156
|
+
this.setState({ isTemplateNameEmpty: isEmptyTemplateName });
|
|
157
|
+
onFormDataChange(newFormData);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Update template name immediately for UI feedback
|
|
161
|
+
updateTemplateNameImmediately = (value, formData, onFormDataChange) => {
|
|
162
|
+
const isEmptyTemplateName = !value.trim();
|
|
163
|
+
|
|
164
|
+
// 1. IMMEDIATE: Update local state for instant UI feedback
|
|
165
|
+
this.setState({
|
|
166
|
+
isTemplateNameEmpty: isEmptyTemplateName,
|
|
167
|
+
localTemplateName: value
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// 2. DEBOUNCED: Only debounce the expensive onFormDataChange call
|
|
171
|
+
this.debouncedTemplateNameUpdate(value, formData, onFormDataChange);
|
|
139
172
|
};
|
|
140
173
|
|
|
141
174
|
onRemoveTemplateName = () => {
|
|
@@ -1395,21 +1428,30 @@ export class Creatives extends React.Component {
|
|
|
1395
1428
|
} />
|
|
1396
1429
|
)
|
|
1397
1430
|
|
|
1398
|
-
templateNameComponentInput = ({ formData, onFormDataChange, name }) =>
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1431
|
+
templateNameComponentInput = ({ formData, onFormDataChange, name }) => {
|
|
1432
|
+
// Use local state for immediate UI feedback, fallback to prop value
|
|
1433
|
+
const displayValue = this.state.localTemplateName !== '' ? this.state.localTemplateName : name;
|
|
1434
|
+
|
|
1435
|
+
return (
|
|
1436
|
+
<CapInput
|
|
1437
|
+
value={displayValue}
|
|
1438
|
+
suffix={<span />}
|
|
1439
|
+
onBlur={() => {
|
|
1440
|
+
this.setState({
|
|
1441
|
+
isEditName: false,
|
|
1442
|
+
localTemplateName: '' // Clear local state on blur
|
|
1443
|
+
}, () => {
|
|
1444
|
+
this.showTemplateName({ formData, onFormDataChange });
|
|
1445
|
+
});
|
|
1446
|
+
}}
|
|
1447
|
+
onChange={(ev) => {
|
|
1448
|
+
const { value } = ev.currentTarget;
|
|
1449
|
+
// Use optimized update for better performance
|
|
1450
|
+
this.updateTemplateNameImmediately(value, formData, onFormDataChange);
|
|
1451
|
+
}}
|
|
1452
|
+
/>
|
|
1453
|
+
);
|
|
1454
|
+
}
|
|
1413
1455
|
|
|
1414
1456
|
showTemplateName = ({ formData, onFormDataChange }) => { //gets called from email/index after template data is fetched
|
|
1415
1457
|
const { slidBoxContent, currentChannel, isEditName } = this.state;
|
|
@@ -1423,7 +1465,10 @@ export class Creatives extends React.Component {
|
|
|
1423
1465
|
if (name && !isEditName) {
|
|
1424
1466
|
this.setState({ showTemplateNameComponentEdit: false });
|
|
1425
1467
|
} else if (isEditName) {
|
|
1426
|
-
this.setState({
|
|
1468
|
+
this.setState({
|
|
1469
|
+
showTemplateNameComponentEdit: true,
|
|
1470
|
+
localTemplateName: name || '' // Initialize local state with current value
|
|
1471
|
+
});
|
|
1427
1472
|
}
|
|
1428
1473
|
}
|
|
1429
1474
|
}
|
|
@@ -2516,6 +2516,9 @@ export class Email extends React.Component { // eslint-disable-line react/prefer
|
|
|
2516
2516
|
});
|
|
2517
2517
|
obj.versions.history.push(newdata);
|
|
2518
2518
|
}
|
|
2519
|
+
if (index === "template-subject" && obj?.versions?.history?.[0]) {
|
|
2520
|
+
obj.versions.history[0].subject = newdata;
|
|
2521
|
+
}
|
|
2519
2522
|
});
|
|
2520
2523
|
//const data = formData[`${this.state.currentTab - 1}`];
|
|
2521
2524
|
obj.name = formData['template-name'];
|
|
@@ -207,9 +207,6 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
|
|
|
207
207
|
{
|
|
208
208
|
formData: newFormData,
|
|
209
209
|
tabCount,
|
|
210
|
-
isSchemaChanged:
|
|
211
|
-
compareValue?.toLowerCase() === EXTERNAL_LINK_LOWERCASE ||
|
|
212
|
-
!this.state?.isSchemaChanged,
|
|
213
210
|
},
|
|
214
211
|
() => {
|
|
215
212
|
if (isFullMode && showTemplateName) {
|
|
@@ -220,6 +217,9 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
|
|
|
220
217
|
}
|
|
221
218
|
}
|
|
222
219
|
);
|
|
220
|
+
this.setState({isSchemaChanged:
|
|
221
|
+
compareValue?.toLowerCase() === EXTERNAL_LINK_LOWERCASE ||
|
|
222
|
+
!this.state?.isSchemaChanged});
|
|
223
223
|
};
|
|
224
224
|
|
|
225
225
|
onTagSelect = (data, currentTab, srcComp) => {
|