@capillarytech/creatives-library 8.0.202 → 8.0.204

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@capillarytech/creatives-library",
3
3
  "author": "meharaj",
4
- "version": "8.0.202",
4
+ "version": "8.0.204",
5
5
  "description": "Capillary creatives ui",
6
6
  "main": "./index.js",
7
7
  "module": "./index.es.js",
@@ -139,196 +139,8 @@ 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 === "addLanguage") {
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 "copyPrimaryLanguage":
211
- case "useEditor":
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 !== "addLanguage" && 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
142
 
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 === 'addLanguage') {
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
- }
300
-
301
- if (!((event === 'onSelect' && data === 'New Version') || event === 'onContentChange')) {
302
- this.props.onChange(formData, this.state.tabCount, currentTab, val);
303
- }
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
143
  }
331
-
332
144
  isLiquidFlowSupported = () => {
333
145
  return Boolean(LIQUID_SUPPORTED_CHANNELS.includes(this.props?.schema?.channel?.toUpperCase()) && hasLiquidSupportFeature());
334
146
  }
@@ -457,7 +269,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
457
269
  }
458
270
  this.setState({formData: nextProps.formData, currentLangTab}, () => {
459
271
  if (nextProps?.isNewVersionFlow && !this.state?.formData[this.state?.currentTab - 1][this.state?.formData[this.state?.currentTab - 1]?.activeTab]?.tabKey) {
460
- this.resetTabKeys(nextProps.formData, nextProps.tabCount, false, true);
272
+ this.resetTabKeys(nextProps.formData, nextProps.tabCount, false, true);
461
273
  }
462
274
  if (type === 'embedded' || ( this.props.schema.channel && this.props.schema.channel.toUpperCase() === 'EMAIL')) {
463
275
  // Don't run validation if we're in Test & Preview mode
@@ -1104,7 +916,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
1104
916
  }
1105
917
  }
1106
918
  if(_.isEmpty(androidData) && this.state.currentTab == 2){
1107
- this.setState({androidValid, iosValid, errorData}, () => {
919
+ this.setState({androidValid, iosValid}, () => {
1108
920
  this.props.setModalContent('ios');
1109
921
  });
1110
922
  // modal.body = "Android template is not configured. Save without Android template";
@@ -1119,7 +931,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
1119
931
  }
1120
932
  }
1121
933
  if(_.isEmpty(iosData) && this.state.currentTab == 1){
1122
- this.setState({androidValid, iosValid, errorData}, () => {
934
+ this.setState({androidValid, iosValid}, () => {
1123
935
  this.props.setModalContent('android');
1124
936
  });
1125
937
  // modal.body = "IOS template is not configured, Save without IOS template";
@@ -2215,62 +2027,6 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
2215
2027
  }
2216
2028
 
2217
2029
  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) {
2274
2030
  const formData = _.cloneDeep(this.state.formData);
2275
2031
 
2276
2032
  const tabIndex = this.state.currentTab - 1;
@@ -2430,57 +2186,6 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
2430
2186
  hasClass(element, className) {
2431
2187
  return (` ${element.className} `).indexOf(` ${className} `) > -1;
2432
2188
  }
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
- }
2484
2189
  allowAddSecondaryCta = (val) => {
2485
2190
  if (val.fieldsCount > 0) {
2486
2191
  const errorData = _.cloneDeep(this.state.errorData);
@@ -2896,7 +2601,6 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
2896
2601
  label={val.label}
2897
2602
  autosize={val.autosize ? val.autosizeParams : false}
2898
2603
  onChange={(e) => this.updateFormData(e.target.value, val)}
2899
- onBlur={(e) => this.handleFieldBlur(e, val)}
2900
2604
  style={val.style ? val.style : {}}
2901
2605
  defaultValue={messageContent || ''}
2902
2606
  value={messageContent || ""}
@@ -2978,7 +2682,6 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
2978
2682
  style={val.style ? val.style : {}}
2979
2683
  placeholder={val.placeholder}
2980
2684
  onChange={(e) => this.updateFormData(e.target.value, val)}
2981
- onBlur={(e) => this.handleFieldBlur(e, val)}
2982
2685
  defaultValue={isVersionEnable ? this.state.formData[`${this.state.currentTab - 1}`][val.id] : this.state.formData[val.id]}
2983
2686
  value={value || ""}
2984
2687
  disabled={val.disabled}
@@ -3463,7 +3166,6 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
3463
3166
  style={val.style ? val.style : {}}
3464
3167
  placeholder={val.placeholder}
3465
3168
  onChange={(e) => this.updateFormData(e.target.value, val)}
3466
- onBlur={(e) => this.handleFieldBlur(e, val)}
3467
3169
  value={value || ""}
3468
3170
  defaultValue={isVersionEnable ? this.state.formData[`${this.state.currentTab - 1}`][val.id] : this.state.formData[val.id]}
3469
3171
  disabled={val.disabled}
@@ -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(React.memo(BeeEditor)));
352
+ export default compose(withReducer, withSaga, withConnect)(injectIntl(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, debounce,
14
+ isEmpty, get, forEach, cloneDeep,
15
15
  } from 'lodash';
16
16
  import { connect } from 'react-redux';
17
17
  import { createStructuredSelector } from 'reselect';
@@ -105,8 +105,6 @@ 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: '',
110
108
  };
111
109
  this.liquidFlow = Boolean(commonUtil.hasLiquidSupportFeature());
112
110
  this.creativesTemplateSteps = {
@@ -114,9 +112,6 @@ export class Creatives extends React.Component {
114
112
  2: 'templateSelection', // only for email in current flows wil be used for mpush, line and wechat as well.
115
113
  3: 'createTemplateContent',
116
114
  };
117
-
118
- // Performance optimization: Debounced template name update
119
- this.debouncedTemplateNameUpdate = debounce(this.performTemplateNameUpdate.bind(this), 300);
120
115
  }
121
116
 
122
117
  componentWillUnmount() {
@@ -124,11 +119,6 @@ export class Creatives extends React.Component {
124
119
  this.props.templateActions.resetTemplateStoreData();
125
120
  }
126
121
  this.props.globalActions.clearMetaEntities();
127
-
128
- // Cleanup debounced function
129
- if (this.debouncedTemplateNameUpdate) {
130
- this.debouncedTemplateNameUpdate.cancel();
131
- }
132
122
  }
133
123
 
134
124
  componentDidMount() {
@@ -146,29 +136,6 @@ export class Creatives extends React.Component {
146
136
 
147
137
  onEnterTemplateName = () => {
148
138
  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);
172
139
  };
173
140
 
174
141
  onRemoveTemplateName = () => {
@@ -1428,30 +1395,21 @@ export class Creatives extends React.Component {
1428
1395
  } />
1429
1396
  )
1430
1397
 
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
- }
1398
+ templateNameComponentInput = ({ formData, onFormDataChange, name }) => (
1399
+ <CapInput
1400
+ value={name}
1401
+ suffix={<span />}
1402
+ onBlur={() => { this.setState({ isEditName: false }, () => { this.showTemplateName({ formData, onFormDataChange }); }); }}
1403
+ onChange={(ev) => {
1404
+ const { value } = ev.currentTarget;
1405
+ const isEmptyTemplateName = !value.trim();
1406
+
1407
+ const newFormData = { ...formData, 'template-name': value, 'isTemplateNameEdited': true };
1408
+ this.setState({ isTemplateNameEmpty: isEmptyTemplateName });
1409
+ onFormDataChange(newFormData);
1410
+ }}
1411
+ />
1412
+ )
1455
1413
 
1456
1414
  showTemplateName = ({ formData, onFormDataChange }) => { //gets called from email/index after template data is fetched
1457
1415
  const { slidBoxContent, currentChannel, isEditName } = this.state;
@@ -1465,10 +1423,7 @@ export class Creatives extends React.Component {
1465
1423
  if (name && !isEditName) {
1466
1424
  this.setState({ showTemplateNameComponentEdit: false });
1467
1425
  } else if (isEditName) {
1468
- this.setState({
1469
- showTemplateNameComponentEdit: true,
1470
- localTemplateName: name || '' // Initialize local state with current value
1471
- });
1426
+ this.setState({ showTemplateNameComponentEdit: true });
1472
1427
  }
1473
1428
  }
1474
1429
  }
@@ -2516,9 +2516,6 @@ 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") {
2520
- obj.versions.history[0].subject = newdata;
2521
- }
2522
2519
  });
2523
2520
  //const data = formData[`${this.state.currentTab - 1}`];
2524
2521
  obj.name = formData['template-name'];
@@ -207,6 +207,9 @@ 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,
210
213
  },
211
214
  () => {
212
215
  if (isFullMode && showTemplateName) {
@@ -217,9 +220,6 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
217
220
  }
218
221
  }
219
222
  );
220
- this.setState({isSchemaChanged:
221
- compareValue?.toLowerCase() === EXTERNAL_LINK_LOWERCASE ||
222
- !this.state?.isSchemaChanged});
223
223
  };
224
224
 
225
225
  onTagSelect = (data, currentTab, srcComp) => {