@configura/web-api 3.0.0-alpha.2 → 3.0.0-alpha.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.
@@ -2,7 +2,7 @@ import { AggregatedLoadingObservable, LengthUnit, Observable, SingleArgCallback
2
2
  import { DtoAdditionalProductConfiguration, DtoAdditionalProductRef, DtoCatalogueParams, DtoMeasureParam, DtoMiscFile, DtoMtrlApplication, DtoNote, DtoPrices, DtoProductConf, DtoProductParamsWithLang, DtoTransform } from "./CatalogueAPI.js";
3
3
  import { CfgMeasureDefinition } from "./CfgMeasure.js";
4
4
  import { _CfgFeatureInternal } from "./productConfiguration/CfgFeature.js";
5
- import { ProductConfigurationBubbleMode } from "./productConfiguration/CfgOption.js";
5
+ import { ProductConfigurationBubbleMode, _CfgOptionInternal } from "./productConfiguration/CfgOption.js";
6
6
  import { CfgProductConfiguration } from "./productConfiguration/CfgProductConfiguration.js";
7
7
  import { ProductLoader } from "./productLoader.js";
8
8
  import { SyncGroupsApplyMode } from "./syncGroups/SyncGroupsApplyMode.js";
@@ -102,6 +102,13 @@ export declare class _CfgProductInternal {
102
102
  private _configuration;
103
103
  private _notes;
104
104
  readonly changeObservable: Observable<CfgProductChangeNotification>;
105
+ /**
106
+ * The last diff that was generated during the last validate call.
107
+ *
108
+ * This is used to keep track of the changes that were made to the
109
+ * configuration as a result of apply constraints.
110
+ */
111
+ private _lastDiff;
105
112
  get selected(): boolean;
106
113
  readonly isAdditionalProduct: boolean;
107
114
  /**
@@ -198,10 +205,22 @@ export declare class _CfgProductInternal {
198
205
  tryMatchSelection: (other: _CfgProductInternal, descriptionMatch?: boolean, productLoaderForGroupedLoad?: ProductLoader | undefined) => Promise<boolean>;
199
206
  /** Only features in selected options and selected additional products. */
200
207
  _getDescendantFeaturesWithCode: (code: string) => _CfgFeatureInternal[];
208
+ /**
209
+ * Obtains the differences found in the last validate call.
210
+ * Used to update the sync group state.
211
+ *
212
+ * Removes the diff from the product.
213
+ */
214
+ takeDiff(): _CfgOptionInternal[] | undefined;
201
215
  private _revalidateInProgressToken;
202
216
  /**
203
- * Do a validate call for this product. It does not validate additional products, only this
204
- * product in isolation. The validation result is applied on the configuration. Then additional
217
+ * Do a validate call for this product.
218
+ *
219
+ * Does not validate additional products, unless application of sync groups
220
+ * on the result of the validate call results in changes in the additional
221
+ * product.
222
+ *
223
+ * The validation result is applied on the configuration. Then additional
205
224
  * products are synced (unloaded, loaded etc.) Finally the changes bubble up the tree.
206
225
  */
207
226
  _revalidate: (bubbleMode: CfgProductBubbleMode, productLoader: ProductLoader, committed: boolean) => Promise<boolean>;
@@ -1,11 +1,12 @@
1
1
  var _a;
2
2
  import { AggregatedLoadingObservable, assert, assertDefined, augmentErrorMessage, compareArrays, count, Observable, toLengthUnit, } from "@configura/web-utilities";
3
3
  import { CfgMeasureDefinition } from "./CfgMeasure.js";
4
- import { ProductConfigurationBubbleMode } from "./productConfiguration/CfgOption.js";
4
+ import { ProductConfigurationBubbleMode, } from "./productConfiguration/CfgOption.js";
5
5
  import { CfgProductConfiguration } from "./productConfiguration/CfgProductConfiguration.js";
6
6
  import { collectAdditionalProductRefs } from "./productConfiguration/utilitiesProductConfiguration.js";
7
7
  import { wrapWithCache } from "./productLoader.js";
8
8
  import { SyncGroupsHandler } from "./syncGroups/SyncGroupsHandler.js";
9
+ import { generateSelectionDiff } from "./syncGroups/SyncGroupsTransaction.js";
9
10
  import { compareCfgProductData, correctDefaultsOnCatalogueParams, isSameCatalogueParams, isSameProductRef, makeProductKey, } from "./utilitiesCatalogueData.js";
10
11
  import { CfgProdConfParts, convertDtoProductConfToV1, isAdditionalProductConfiguration, isProductConf, } from "./utilitiesConfiguration.js";
11
12
  function completeSettings(incompleteSettings) {
@@ -59,6 +60,13 @@ export class _CfgProductInternal {
59
60
  this.additionalProducts = [];
60
61
  this._notes = new Map();
61
62
  this.changeObservable = new Observable();
63
+ /**
64
+ * The last diff that was generated during the last validate call.
65
+ *
66
+ * This is used to keep track of the changes that were made to the
67
+ * configuration as a result of apply constraints.
68
+ */
69
+ this._lastDiff = undefined;
62
70
  /** Mark this and its descendants as destroyed and remove all listeners */
63
71
  this.destroy = () => {
64
72
  this._destroyed = true;
@@ -229,6 +237,10 @@ export class _CfgProductInternal {
229
237
  change = true;
230
238
  }
231
239
  }
240
+ if (sourceProduct?._lastDiff) {
241
+ this._lastDiff = sourceProduct._lastDiff;
242
+ change = true;
243
+ }
232
244
  await this._syncAndLoadAdditionalProducts(productLoaderForGroupedLoad, productConfiguration);
233
245
  const apiSelectionAdditionalProducts = productConfiguration.additionalProducts || [];
234
246
  const additionalProducts = this.additionalProducts.slice();
@@ -337,8 +349,13 @@ export class _CfgProductInternal {
337
349
  return agg;
338
350
  }, this._configuration._internal._getFeaturesWithCode(code));
339
351
  /**
340
- * Do a validate call for this product. It does not validate additional products, only this
341
- * product in isolation. The validation result is applied on the configuration. Then additional
352
+ * Do a validate call for this product.
353
+ *
354
+ * Does not validate additional products, unless application of sync groups
355
+ * on the result of the validate call results in changes in the additional
356
+ * product.
357
+ *
358
+ * The validation result is applied on the configuration. Then additional
342
359
  * products are synced (unloaded, loaded etc.) Finally the changes bubble up the tree.
343
360
  */
344
361
  this._revalidate = async (bubbleMode, productLoader, committed) => {
@@ -377,7 +394,17 @@ export class _CfgProductInternal {
377
394
  if (rootFeatureRefs !== undefined) {
378
395
  configuration._internal.populateFeatures(rootFeatureRefs);
379
396
  }
397
+ const originalProduct = await this.clone();
380
398
  await configuration._internal.setApiSelection(productData.partsData.selOptions || [], productData.partsData.constrOptions, false);
399
+ if (this.parent === undefined) {
400
+ const diff = generateSelectionDiff(originalProduct, this);
401
+ if (this._lastDiff && diff) {
402
+ this._lastDiff.push(...diff);
403
+ }
404
+ else if (diff) {
405
+ this._lastDiff = diff;
406
+ }
407
+ }
381
408
  await this._syncAndLoadAdditionalProducts(productLoader, undefined);
382
409
  if (this._destroyed) {
383
410
  return false;
@@ -699,6 +726,17 @@ export class _CfgProductInternal {
699
726
  }
700
727
  syncGroupHandler.verboseLogging = v;
701
728
  }
729
+ /**
730
+ * Obtains the differences found in the last validate call.
731
+ * Used to update the sync group state.
732
+ *
733
+ * Removes the diff from the product.
734
+ */
735
+ takeDiff() {
736
+ const diff = this._lastDiff?.filter((option) => option.selected);
737
+ this._lastDiff = undefined;
738
+ return diff;
739
+ }
702
740
  }
703
741
  _a = _CfgProductInternal;
704
742
  _CfgProductInternal.make = async (productLoaderRaw, productLoaderForGroupedLoad, // Used when instantiating the current product
@@ -1,4 +1,4 @@
1
- import { Observable, assertDefined, compareArrays, convertLength, count, someMatch, toLengthUnit, } from "@configura/web-utilities";
1
+ import { assertDefined, compareArrays, convertLength, count, Observable, someMatch, toLengthUnit, } from "@configura/web-utilities";
2
2
  import { CfgProduct } from "../CfgProduct.js";
3
3
  import { CfgMtrlApplication } from "../material/CfgMtrlApplication.js";
4
4
  import { CfgMtrlApplicationSource } from "../material/CfgMtrlApplicationSource.js";
@@ -199,6 +199,11 @@ export class _CfgFeatureInternal {
199
199
  change = true;
200
200
  }
201
201
  }
202
+ // Triggers `pushStretch` when it is a stretch feature
203
+ // Fixes https://configura.atlassian.net/browse/WEB-16599
204
+ if (this.measureParamCodes?.length) {
205
+ change = true;
206
+ }
202
207
  }
203
208
  if (selectedIndex === -1) {
204
209
  if (isAllOptionsAffectedByAnySelection) {
@@ -1,4 +1,4 @@
1
- import { Observable, compareArrays, count, } from "@configura/web-utilities";
1
+ import { compareArrays, count, Observable, } from "@configura/web-utilities";
2
2
  import { CfgProduct } from "../CfgProduct.js";
3
3
  import { CfgMtrlApplication } from "../material/CfgMtrlApplication.js";
4
4
  import { CfgMtrlApplicationSource } from "../material/CfgMtrlApplicationSource.js";
@@ -1,4 +1,5 @@
1
1
  import { _CfgProductInternal } from "../CfgProduct.js";
2
+ import { _CfgOptionInternal } from "../productConfiguration/CfgOption.js";
2
3
  import { ProductLoader } from "../productLoader.js";
3
4
  import { SyncGroupsApplyMode } from "./SyncGroupsApplyMode.js";
4
5
  import { CfgPath } from "./SyncGroupsPathHelper.js";
@@ -91,7 +92,7 @@ export declare class SyncGroupsTransaction {
91
92
  * Apply current sync groups on those who wants to listen until there is no more to settle.
92
93
  * @returns true if at least one Feature changed selected Option
93
94
  */
94
- private updateRootProduct;
95
+ updateRootProduct(productToValidate: _CfgProductInternal | undefined): Promise<boolean>;
95
96
  /**
96
97
  * Applies the SyncState to the Product and it's AdditionalProducts (sub-products).
97
98
  * @param productsToValidate To this all products that will need validation are added.
@@ -149,7 +150,24 @@ export declare class SyncGroupsTransaction {
149
150
  private applySelectOneFeature;
150
151
  private applySelectManyFeature;
151
152
  private applySelectManyOption;
153
+ /**
154
+ * Applies the given diff to the transaction's sync state.
155
+ */
156
+ applyFromDiff(diffs: _CfgOptionInternal[]): void;
157
+ /**
158
+ * Applies the new state for the given option to the transaction's sync state.
159
+ */
160
+ private applyNewStateFor;
152
161
  private addSyncGroupAffectedForSelectMany;
153
162
  private hasSyncGroupAffectedForSelectMany;
154
163
  }
164
+ /************************************************************************
165
+ * Product diffing
166
+ ************************************************************************/
167
+ /**
168
+ * Finds all options that have changed in the new product compared to the original product.
169
+ *
170
+ * This is used to determine which options need to be updated in the sync state.
171
+ */
172
+ export declare function generateSelectionDiff(originalProduct: _CfgProductInternal, newProduct: _CfgProductInternal): _CfgOptionInternal[] | null;
155
173
  //# sourceMappingURL=SyncGroupsTransaction.d.ts.map
@@ -153,12 +153,15 @@ export class SyncGroupsTransaction {
153
153
  // check here.
154
154
  return false;
155
155
  }
156
- const promises = [];
156
+ let anyChanges = false;
157
157
  for (const product of productsToValidate) {
158
- promises.push(product._revalidate(CfgProductBubbleMode.ToRoot, this.productLoader, true));
158
+ anyChanges =
159
+ (await product._revalidate(CfgProductBubbleMode.ToRoot, this.productLoader, true)) || anyChanges;
160
+ const diff = product.takeDiff();
161
+ if (diff)
162
+ this.applyFromDiff(diff);
159
163
  }
160
- const revalidationResults = await Promise.all(promises);
161
- if (revalidationResults.every((r) => !r)) {
164
+ if (!anyChanges) {
162
165
  this.close();
163
166
  return false;
164
167
  }
@@ -486,6 +489,44 @@ export class SyncGroupsTransaction {
486
489
  this.syncState.setForSelectMany(syncCode, option.code, optionSelected);
487
490
  return true;
488
491
  }
492
+ /**
493
+ * Applies the given diff to the transaction's sync state.
494
+ */
495
+ applyFromDiff(diffs) {
496
+ for (const diff of diffs) {
497
+ this.applyNewStateFor(diff);
498
+ }
499
+ }
500
+ /**
501
+ * Applies the new state for the given option to the transaction's sync state.
502
+ */
503
+ applyNewStateFor(option) {
504
+ const feature = option.parent;
505
+ const syncCode = feature.getSyncCode("push");
506
+ if (syncCode === undefined || syncCode === false)
507
+ return false;
508
+ switch (feature.selectionType) {
509
+ case SelectionType.SelectOne: {
510
+ if (this.affectedSelectOneSyncGroups.has(syncCode)) {
511
+ return false;
512
+ }
513
+ this.affectedSelectOneSyncGroups.add(syncCode);
514
+ this.syncState.setForSelectOne(syncCode, option.code);
515
+ return true;
516
+ }
517
+ case SelectionType.SelectMany: {
518
+ if (this.hasSyncGroupAffectedForSelectMany(syncCode, option)) {
519
+ return false;
520
+ }
521
+ this.addSyncGroupAffectedForSelectMany(syncCode, option);
522
+ this.syncState.setForSelectMany(syncCode, option.code, option.selected);
523
+ return true;
524
+ }
525
+ case SelectionType.Group:
526
+ console.warn("No state to update for group: ", option.code);
527
+ return false;
528
+ }
529
+ }
489
530
  addSyncGroupAffectedForSelectMany(syncCode, option) {
490
531
  let forSyncCode = this.affectedSelectManySyncGroupsAndOptions.get(syncCode);
491
532
  if (forSyncCode === undefined) {
@@ -498,6 +539,58 @@ export class SyncGroupsTransaction {
498
539
  return this.affectedSelectManySyncGroupsAndOptions.get(syncCode)?.has(option.code) === true;
499
540
  }
500
541
  }
542
+ /************************************************************************
543
+ * Product diffing
544
+ ************************************************************************/
545
+ /**
546
+ * Finds all options that have changed in the new product compared to the original product.
547
+ *
548
+ * This is used to determine which options need to be updated in the sync state.
549
+ */
550
+ export function generateSelectionDiff(originalProduct, newProduct) {
551
+ const diff = generateFeaturesDiff(getFeaturesFromProduct({ initial: originalProduct, target: newProduct }));
552
+ return diff.length ? diff : null;
553
+ }
554
+ function generateFeaturesDiff(features) {
555
+ const changed = [];
556
+ for (const feature of features) {
557
+ switch (feature.target.selectionType) {
558
+ case SelectionType.SelectOne: {
559
+ const option = generateDiffForSelectOne(feature);
560
+ if (option)
561
+ changed.push(option);
562
+ break;
563
+ }
564
+ case SelectionType.SelectMany:
565
+ changed.push(...generateDiffForSelectMany(feature));
566
+ break;
567
+ }
568
+ for (const option of getSelectedOptions(feature)) {
569
+ changed.push(...generateFeaturesDiff(getFeaturesFromOption(option)));
570
+ }
571
+ }
572
+ return changed;
573
+ }
574
+ function generateDiffForSelectOne({ initial, target, }) {
575
+ if (!target.getSyncCode("push"))
576
+ return null;
577
+ const selected = target.selectedOptions[0];
578
+ if (selected && initial?.selectedOptions?.[0]?.code !== selected.code) {
579
+ return selected._internal;
580
+ }
581
+ return null;
582
+ }
583
+ function generateDiffForSelectMany(feature) {
584
+ const changed = [];
585
+ if (!feature.target.getSyncCode("push"))
586
+ return changed;
587
+ for (const { initial, target } of getOptions(feature)) {
588
+ if (initial?.selected !== target.selected) {
589
+ changed.push(target);
590
+ }
591
+ }
592
+ return changed;
593
+ }
501
594
  function getAdditionalProducts(product) {
502
595
  const initial = product.initial;
503
596
  return product.target.additionalProducts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@configura/web-api",
3
- "version": "3.0.0-alpha.2",
3
+ "version": "3.0.0-alpha.3",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -23,7 +23,7 @@
23
23
  "access": "public"
24
24
  },
25
25
  "dependencies": {
26
- "@configura/web-utilities": "3.0.0-alpha.2"
26
+ "@configura/web-utilities": "3.0.0-alpha.3"
27
27
  },
28
- "gitHead": "5b72d2813b949b3ccef8f1864382b9153593962b"
28
+ "gitHead": "4332ed866e78584bfa40bf2d6a3258108dc4093c"
29
29
  }