@configura/web-api 2.0.0-alpha.2 → 2.0.0-alpha.20

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.
Files changed (52) hide show
  1. package/dist/CatalogueAPI.d.ts +73 -43
  2. package/dist/CatalogueAPI.js +37 -2
  3. package/dist/CfgProduct.d.ts +73 -11
  4. package/dist/CfgProduct.js +130 -29
  5. package/dist/CfgReferencePathHelper.d.ts +16 -3
  6. package/dist/CfgReferencePathHelper.js +14 -1
  7. package/dist/ConfigurationConverter.d.ts +13 -4
  8. package/dist/ConfigurationConverter.js +106 -12
  9. package/dist/io/CfgHistoryManager.d.ts +37 -4
  10. package/dist/io/CfgHistoryManager.js +76 -8
  11. package/dist/io/CfgHistoryToProdConfConnector.d.ts +7 -10
  12. package/dist/io/CfgHistoryToProdConfConnector.js +29 -38
  13. package/dist/io/CfgIOManager.d.ts +5 -0
  14. package/dist/io/CfgIOManager.js +20 -1
  15. package/dist/io/CfgIOProdConfConnector.d.ts +21 -17
  16. package/dist/io/CfgIOProdConfConnector.js +54 -37
  17. package/dist/io/CfgIOWarningSupplier.d.ts +4 -0
  18. package/dist/io/CfgIOWarningSupplier.js +1 -0
  19. package/dist/io/CfgObservableStateManager.d.ts +4 -0
  20. package/dist/io/CfgObservableStateManager.js +4 -0
  21. package/dist/io/CfgObservableStateToProdConfConnector.d.ts +4 -4
  22. package/dist/io/CfgObservableStateToProdConfConnector.js +4 -4
  23. package/dist/io/CfgWindowMessageManager.d.ts +2 -2
  24. package/dist/io/CfgWindowMessageManager.js +9 -2
  25. package/dist/io/CfgWindowMessageToProdConfConnector.d.ts +4 -4
  26. package/dist/io/CfgWindowMessageToProdConfConnector.js +4 -4
  27. package/dist/productConfiguration/CfgFeature.d.ts +8 -6
  28. package/dist/productConfiguration/CfgFeature.js +19 -6
  29. package/dist/productConfiguration/CfgOption.d.ts +5 -5
  30. package/dist/productConfiguration/CfgOption.js +11 -5
  31. package/dist/productConfiguration/CfgProductConfiguration.d.ts +27 -15
  32. package/dist/productConfiguration/CfgProductConfiguration.js +54 -21
  33. package/dist/productConfiguration/filters.d.ts +2 -2
  34. package/dist/productConfiguration/productParamsGenerator.d.ts +3 -3
  35. package/dist/productConfiguration/utilitiesProductConfiguration.d.ts +1 -1
  36. package/dist/productConfiguration/utilitiesProductConfiguration.js +11 -4
  37. package/dist/productLoader.d.ts +3 -3
  38. package/dist/syncGroups/SyncGroupsHandler.d.ts +4 -1
  39. package/dist/syncGroups/SyncGroupsHandler.js +6 -2
  40. package/dist/syncGroups/SyncGroupsTransaction.js +34 -21
  41. package/dist/tasks/TaskHandler.d.ts +2 -2
  42. package/dist/tasks/TaskHandler.js +2 -1
  43. package/dist/tests/testData/dummyProductForTest.d.ts +2 -2
  44. package/dist/tests/testData/testDataAdditionalProductInAdditionalProductInProductForTest.js +1 -0
  45. package/dist/tests/testData/testDataCachedGetProduct.js +1 -0
  46. package/dist/tests/testData/testDataCachedPostValidate.js +1 -0
  47. package/dist/tests/testData/testDataProductAggregatedPrice.js +1 -0
  48. package/dist/tests/testData/testDataUpcharge.js +1 -0
  49. package/dist/utilitiesCatalogueData.d.ts +14 -9
  50. package/dist/utilitiesCatalogueData.js +7 -0
  51. package/dist/utilitiesCataloguePermission.d.ts +4 -4
  52. package/package.json +3 -3
@@ -9,13 +9,13 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  };
10
10
  import { AggregatedLoadingObservable, assert, assertDefined, augmentErrorMessage, compareArrays, count, Observable, toLengthUnit, } from "@configura/web-utilities";
11
11
  import { CfgMeasureDefinition } from "./CfgMeasure.js";
12
- import { convertDtoConfProdToV1 } from "./ConfigurationConverter.js";
12
+ import { convertDtoProductConfToV1 } from "./ConfigurationConverter.js";
13
13
  import { ProductConfigurationBubbleMode } from "./productConfiguration/CfgOption.js";
14
14
  import { CfgProductConfiguration } from "./productConfiguration/CfgProductConfiguration.js";
15
15
  import { collectAdditionalProductRefs } from "./productConfiguration/utilitiesProductConfiguration.js";
16
16
  import { wrapWithCache } from "./productLoader.js";
17
17
  import { SyncGroupsHandler } from "./syncGroups/SyncGroupsHandler.js";
18
- import { comparePricesObjects, correctDefaultsOnCatalogueParams, isSameCatalogueParams, isSameProductRef, makeProductKey, } from "./utilitiesCatalogueData.js";
18
+ import { compareCfgProductData, comparePricesObjects, correctDefaultsOnCatalogueParams, isSameCatalogueParams, isSameProductRef, makeProductKey, } from "./utilitiesCatalogueData.js";
19
19
  function completeSettings(incompleteSettings) {
20
20
  var _a;
21
21
  return {
@@ -52,7 +52,7 @@ function isDescriptionMatch(l, r) {
52
52
  * the class that should be used and interacted with.
53
53
  */
54
54
  export class _CfgProductInternal {
55
- constructor(initSuccess, initFail, _productLoaderRaw, prodParams, settings, optional, selected, rootFeatureRefs, allRawFeatures, uuid, _rawUnit, _rawProductData, apiSelection, loadingObservable, parent, root, _additionalProductRef, _syncGroupHandler) {
55
+ constructor(initSuccess, initFail, _productLoaderRaw, prodParams, settings, optional, selected, rootFeatureRefs, rawFeatures, uuid, _rawUnit, _rawProductData, apiSelection, loadingObservable, parent, root, _additionalProductRef, _syncGroupHandler) {
56
56
  var _a;
57
57
  this._productLoaderRaw = _productLoaderRaw;
58
58
  this.prodParams = prodParams;
@@ -67,14 +67,24 @@ export class _CfgProductInternal {
67
67
  this._destroyed = false;
68
68
  this.additionalProducts = [];
69
69
  this.changeObservable = new Observable();
70
+ /** Mark this and its descendants as destroyed and remove all listeners */
70
71
  this.destroy = () => {
72
+ var _a;
71
73
  this._destroyed = true;
72
74
  this.changeObservable.stopAllListen();
73
75
  this.configuration.stopAllListenForChange();
74
- for (const additionalProduct of this.additionalProducts || []) {
76
+ for (const additionalProduct of (_a = this.additionalProducts) !== null && _a !== void 0 ? _a : []) {
75
77
  additionalProduct.destroy();
76
78
  }
77
79
  };
80
+ /**
81
+ * Reset will reset the product to its initial state
82
+ */
83
+ this.reset = () => __awaiter(this, void 0, void 0, function* () {
84
+ if (this._initialClone !== undefined) {
85
+ yield this.copyFrom(this._initialClone, true);
86
+ }
87
+ });
78
88
  this._notifyAllOfChange = (bubbleMode, committed) => __awaiter(this, void 0, void 0, function* () {
79
89
  if (bubbleMode === CfgProductBubbleMode.Stop) {
80
90
  return;
@@ -164,12 +174,14 @@ export class _CfgProductInternal {
164
174
  }
165
175
  return conf;
166
176
  };
167
- this.setDtoConf = (s, doValidate, productLoaderForGroupedLoad) => this.setApiSelection(convertDtoConfProdToV1(s), doValidate, productLoaderForGroupedLoad);
177
+ this.setDtoConf = (s, doValidate, productLoaderForGroupedLoad) => __awaiter(this, void 0, void 0, function* () {
178
+ return yield this.setApiSelection(convertDtoProductConfToV1(s), doValidate, productLoaderForGroupedLoad);
179
+ });
168
180
  this.setApiSelection = (s, doValidate, productLoaderForGroupedLoad) => __awaiter(this, void 0, void 0, function* () {
169
- return this._setApiSelectionWithOtherProduct(s, doValidate, productLoaderForGroupedLoad, undefined);
181
+ return yield this._setApiSelectionWithOtherProduct(s, doValidate, productLoaderForGroupedLoad, undefined);
170
182
  });
171
183
  this.copyFrom = (source, doValidate, productLoaderForGroupedLoad) => __awaiter(this, void 0, void 0, function* () {
172
- return yield this._setApiSelectionWithOtherProduct(convertDtoConfProdToV1(source.getDtoConf(false, false)), doValidate, productLoaderForGroupedLoad, source);
184
+ return yield this._setApiSelectionWithOtherProduct(convertDtoProductConfToV1(source.getDtoConf(false, false)), doValidate, productLoaderForGroupedLoad, source);
173
185
  });
174
186
  this._setApiSelectionWithOtherProduct = (s, doValidate, productLoaderForGroupedLoad, sourceProduct) => __awaiter(this, void 0, void 0, function* () {
175
187
  // Wrap with cache will make getProduct for this function call use the same server call
@@ -179,9 +191,16 @@ export class _CfgProductInternal {
179
191
  productLoaderForGroupedLoad || wrapWithCache(this._productLoaderRaw);
180
192
  let change = false;
181
193
  if (sourceProduct !== undefined) {
182
- this._rawProductData = sourceProduct.rawProductData;
183
- this.configuration._internal.populateFeatures(sourceProduct.configuration.rootFeatureRefs);
184
- change = true; // We can not know if this is an actual change, so we assume it is
194
+ if (!compareCfgProductData(this._rawProductData, sourceProduct.rawProductData)) {
195
+ this._rawProductData = sourceProduct.rawProductData;
196
+ change = true;
197
+ }
198
+ if (this.configuration._internal.addRawFeatures(sourceProduct.configuration.rawFeatures, false)) {
199
+ change = true;
200
+ }
201
+ if (this.configuration._internal.populateFeatures(sourceProduct.configuration.rootFeatureRefs)) {
202
+ change = true;
203
+ }
185
204
  }
186
205
  const configurationChange = yield this.configuration._internal.setApiSelection(s.selOptions, false);
187
206
  if (configurationChange) {
@@ -203,8 +222,8 @@ export class _CfgProductInternal {
203
222
  const sourceProductAdditionalProducts = sourceProduct === null || sourceProduct === void 0 ? void 0 : sourceProduct.additionalProducts;
204
223
  assert(!sourceProductAdditionalProducts ||
205
224
  additionalProductsCount === sourceProductAdditionalProducts.length, `Passed sourceProduct does not have the same number of additional products as this.`);
206
- if ((yield Promise.all(apiSelectionAdditionalProducts.map((apiSelectionAdditionalProduct, index) => {
207
- var _a;
225
+ if ((yield Promise.all(apiSelectionAdditionalProducts.map((apiSelectionAdditionalProduct) => __awaiter(this, void 0, void 0, function* () {
226
+ var _b;
208
227
  const refKey = apiSelectionAdditionalProduct.refKey;
209
228
  assertDefined(refKey, "Additional product api configurations must have refKey.");
210
229
  const i = additionalProducts.findIndex((a) => refKey === a.refKey);
@@ -212,12 +231,12 @@ export class _CfgProductInternal {
212
231
  let sourceProductAdditionalProduct = undefined;
213
232
  if (sourceProductAdditionalProducts !== undefined) {
214
233
  sourceProductAdditionalProduct =
215
- (_a = sourceProductAdditionalProducts.find((a) => refKey === a.refKey)) === null || _a === void 0 ? void 0 : _a._internal;
234
+ (_b = sourceProductAdditionalProducts.find((a) => refKey === a.refKey)) === null || _b === void 0 ? void 0 : _b._internal;
216
235
  assertDefined(sourceProductAdditionalProduct, "Additional product not found in sourceProduct");
217
236
  }
218
237
  const additionalProduct = additionalProducts.splice(i, 1)[0]; // Splicing like this is okay because this is done synchronous. The setCon. is what is async.
219
- return additionalProduct._internal._setApiSelectionWithOtherProduct(apiSelectionAdditionalProduct, doValidate, productLoaderForGroupedLoad, sourceProductAdditionalProduct);
220
- }))).some((b) => b)) {
238
+ return yield additionalProduct._internal._setApiSelectionWithOtherProduct(apiSelectionAdditionalProduct, doValidate, productLoaderForGroupedLoad, sourceProductAdditionalProduct);
239
+ })))).some((b) => b)) {
221
240
  change = true;
222
241
  }
223
242
  if (doValidate && configurationChange) {
@@ -310,7 +329,10 @@ export class _CfgProductInternal {
310
329
  const token = this.loadingObservable.startChildLoading();
311
330
  this._revalidateInProgressToken = token;
312
331
  try {
313
- const response = yield productLoader.postValidate(correctDefaultsOnCatalogueParams(this.prodParams), { selOptions: configuration.getApiSelection() });
332
+ const response = yield productLoader.postValidate(correctDefaultsOnCatalogueParams(this.prodParams), {
333
+ selOptions: configuration._internal.getApiSelection(),
334
+ knownFeatureCodes: configuration.rawFeatures.map((f) => f.code),
335
+ });
314
336
  // The revalidateInProgressToken is used to know if some other revalidate
315
337
  // call has happened after this call, thereby making this call obsolete.
316
338
  // This is a bit crude in that it does not actually cancel previous validate
@@ -329,9 +351,10 @@ export class _CfgProductInternal {
329
351
  if (this._destroyed) {
330
352
  return false;
331
353
  }
332
- const { productData, rootFeatureRefs } = response;
354
+ const { productData, rootFeatureRefs, features } = response;
333
355
  const pricesUpdated = !comparePricesObjects(this.prices, productData.partsData.prices);
334
356
  this._rawProductData = productData;
357
+ configuration._internal.addRawFeatures(features, true);
335
358
  if (rootFeatureRefs !== undefined) {
336
359
  configuration._internal.populateFeatures(rootFeatureRefs);
337
360
  }
@@ -348,7 +371,7 @@ export class _CfgProductInternal {
348
371
  return true;
349
372
  }
350
373
  catch (e) {
351
- throw augmentErrorMessage(e, "Validate product configuration request failure");
374
+ throw augmentErrorMessage(e, `Validate product configuration request (${this.prodParams.partNumber}) failure`);
352
375
  }
353
376
  finally {
354
377
  this.loadingObservable.stopChildLoading(token);
@@ -421,19 +444,25 @@ export class _CfgProductInternal {
421
444
  this.root = root !== null && root !== void 0 ? root : this;
422
445
  this.key = makeProductKey(Object.assign(Object.assign({}, prodParams), { partNumber: (_a = _additionalProductRef === null || _additionalProductRef === void 0 ? void 0 : _additionalProductRef.refKey) !== null && _a !== void 0 ? _a : prodParams.partNumber }));
423
446
  this._selected = optional ? selected : undefined;
424
- this.isAdditionalProduct = parent !== undefined;
425
- this._configuration = CfgProductConfiguration.make(initSuccess, initFail, rootFeatureRefs, allRawFeatures, apiSelection, this, this.root);
447
+ this.isAdditionalProduct = _additionalProductRef !== undefined;
448
+ this._configuration = CfgProductConfiguration.make(initSuccess, initFail, rootFeatureRefs, rawFeatures, apiSelection, this, this.root);
426
449
  }
427
450
  get selected() {
428
451
  return this._selected !== false;
429
452
  }
453
+ /**
454
+ * Please note that cloning an additional product will make the clone believe is is
455
+ * an additional product, even if it has no parent and root.
456
+ * Providing the parent and root of what you clone as arguments is unwise as it will
457
+ * make changes you do on the clone be propagated up to the original non-clone root product.
458
+ */
430
459
  clone(parent, root) {
431
460
  return __awaiter(this, void 0, void 0, function* () {
432
461
  const product = yield new Promise((initSuccess, initFail) => {
433
462
  var _a;
434
463
  const p = new _CfgProductInternal(() => {
435
464
  initSuccess(p);
436
- }, initFail, this._productLoaderRaw, this.prodParams, this.settings, this.optional, this.selected, this._configuration.rootFeatureRefs, this._configuration.allRawFeatures, this.uuid, this._rawUnit, this._rawProductData, this.configuration.getApiSelection(), new AggregatedLoadingObservable(), parent, root, this._additionalProductRef, (_a = this._syncGroupHandler) === null || _a === void 0 ? void 0 : _a.clone());
465
+ }, initFail, this._productLoaderRaw, this.prodParams, this.settings, this.optional, this.selected, this._configuration.rootFeatureRefs, this._configuration.rawFeatures, this.uuid, this._rawUnit, this._rawProductData, this.configuration._internal.getApiSelection(), new AggregatedLoadingObservable(), parent, root, this._additionalProductRef, (_a = this._syncGroupHandler) === null || _a === void 0 ? void 0 : _a.clone());
437
466
  });
438
467
  for (const additionalProduct of this.additionalProducts) {
439
468
  product.additionalProducts.push(CfgProduct._makeNewRefFrom(yield additionalProduct._internal.clone(product, root || product)));
@@ -441,12 +470,21 @@ export class _CfgProductInternal {
441
470
  return product;
442
471
  });
443
472
  }
473
+ /**
474
+ * Internal use. Used when this product is an additional product, and
475
+ * changing a parent product has made the settings for this product
476
+ * change.
477
+ */
444
478
  _updateAdditionalProdRef(p) {
445
479
  this._additionalProductRef = p;
446
480
  if (p.optional !== this.optional) {
447
481
  this._selected = p.optional ? false : undefined;
448
482
  }
449
483
  }
484
+ get hasRootFeaturesChanged() {
485
+ return (this._configuration._internal.hasRootFeaturesChanged ||
486
+ this.additionalProducts.some((p) => p._internal.hasRootFeaturesChanged));
487
+ }
450
488
  get description() {
451
489
  var _a, _b;
452
490
  return (_b = (_a = this._additionalProductRef) === null || _a === void 0 ? void 0 : _a.refDescription) !== null && _b !== void 0 ? _b : this._rawProductData.description;
@@ -530,7 +568,19 @@ export class _CfgProductInternal {
530
568
  if (this._selected === v) {
531
569
  return false;
532
570
  }
571
+ // Vitally important that this happens before the call to reset. An optional
572
+ // additional product is always deselected at start, so this way the reset won't cause
573
+ // infinite loops
533
574
  this._selected = v;
575
+ if (!v) {
576
+ yield this.reset();
577
+ }
578
+ if (v) {
579
+ const syncGroupHandler = this.syncGroupHandler;
580
+ if (syncGroupHandler !== undefined) {
581
+ yield syncGroupHandler.init(this.root, wrapWithCache(this._productLoaderRaw));
582
+ }
583
+ }
534
584
  yield this._notifyAllOfChange(bubbleMode, true);
535
585
  return true;
536
586
  });
@@ -594,22 +644,26 @@ prodParams, settings, optional, loadingObservable, parent, root, additionalProdu
594
644
  : undefined;
595
645
  try {
596
646
  const productResponse = yield productLoaderForGroupedLoad.getProduct(correctDefaultsOnCatalogueParams(prodParams));
597
- const { productData, rootFeatureRefs, features: allRawFeatures, uuid, unit, } = productResponse;
647
+ const { productData, rootFeatureRefs, features: rawFeatures, uuid, unit, } = productResponse;
598
648
  const product = yield new Promise((initSuccess, initFail) => {
649
+ var _a;
599
650
  const p = new _CfgProductInternal(() => {
600
651
  // We absolutely do not want anyone to assign to this._configuration. So we want that field private.
601
652
  // But we can not set the api selection synchronously. And the product configuration needs "this". So we use this callback.
602
653
  // Feel free to find a nicer more readable solution :)
603
654
  initSuccess(p);
604
- }, initFail, productLoaderRaw, prodParams, settings, optional, !optional, rootFeatureRefs, allRawFeatures, uuid, unit, productData, productData.partsData.selOptions || [], loadingObservable, parent, root, additionalProductRef, syncGroupHandler);
655
+ }, initFail, productLoaderRaw, prodParams, settings, optional, !optional, rootFeatureRefs, rawFeatures, uuid, unit, productData, (_a = productData.partsData.selOptions) !== null && _a !== void 0 ? _a : [], loadingObservable, parent, root, additionalProductRef, syncGroupHandler);
605
656
  });
606
657
  yield product._syncAndLoadAdditionalProducts(productLoaderForGroupedLoad);
607
- // Product is guaranteed to be root
608
- yield (syncGroupHandler === null || syncGroupHandler === void 0 ? void 0 : syncGroupHandler.init(product, productLoaderForGroupedLoad));
658
+ product._initialClone = yield product.clone();
659
+ if (syncGroupHandler !== undefined) {
660
+ // As syncGroupHandler is only set for root product we know that we will init with root
661
+ yield syncGroupHandler.init(product, productLoaderForGroupedLoad);
662
+ }
609
663
  return product;
610
664
  }
611
665
  catch (e) {
612
- throw augmentErrorMessage(e, "Load product request failure");
666
+ throw augmentErrorMessage(e, `Load product request (${prodParams.partNumber}) failure`);
613
667
  }
614
668
  });
615
669
  export class CfgProduct {
@@ -648,11 +702,43 @@ export class CfgProduct {
648
702
  * Gets what selections has been made on the product, recursively including product
649
703
  * configuration, optional products and additional products. Used when a full view of all
650
704
  * selections on a product is needed, such as when doing Render or Export.
705
+ * @deprecated getDtoConf provides a newer format.
706
+ * @see getDtoConf
707
+ */
708
+ this.getApiSelection = () => convertDtoProductConfToV1(this._internal.getDtoConf(false, false), true);
709
+ /**
710
+ * Applies the configuration (selections) in the passed object onto the product recursively
711
+ * including product configuration, optional products and additional products.
712
+ * @param doValidate Makes a server side validation call. These are necessary to ensure that
713
+ * the right models are loaded.
714
+ * @deprecated setDtoConf uses a newer format.
715
+ */
716
+ this.setApiSelection = (configuration, doValidate = true) => __awaiter(this, void 0, void 0, function* () { return yield this._internal.setApiSelection(configuration, doValidate); });
717
+ /**
718
+ * A newer alternative version of getApiSelection. This returns the configuration (selections)
719
+ * on the product, recursively including product configuration, optional products and additional
720
+ * products.
721
+ * This version has the following advantages over getApiSelection:
722
+ * - The format is clearer, designed to be readable
723
+ * - Makes less assumptions about the structure in the Product being unchanging over time. In
724
+ * particular, the Feature codes are included in the data, so that changes to what Features
725
+ * are used in a Product is less likely to lead to unexpected results.
726
+ * - You can request ExtendedData and/or ProductParams to be included in the result. This extra
727
+ * data is ignored when passed back into the API, but it can be very useful for external
728
+ * applications.
729
+ * The other version (getApiSelection) has the advantage of using a format directly compatible with the API:s.
730
+ * @param includeExtendedData Includes extra data which is not an actual part of the configuration,
731
+ * i.e. units and groupCodes
732
+ * @param includeProductParams Includes what Product this was generated for, and the same for any
733
+ * Additional Products.
651
734
  */
652
- this.getApiSelection = () => convertDtoConfProdToV1(this._internal.getDtoConf(false, false), true);
653
735
  this.getDtoConf = (includeExtendedData = false, includeProductParams = false) => this._internal.getDtoConf(includeExtendedData, includeProductParams);
654
- this.setDtoConf = (s, doValidate = false) => __awaiter(this, void 0, void 0, function* () { return yield this._internal.setDtoConf(s, doValidate); });
655
- this.setApiSelection = (s, doValidate = false) => __awaiter(this, void 0, void 0, function* () { return yield this._internal.setApiSelection(s, doValidate); });
736
+ /**
737
+ * A newer alternative version of setApiSelection.
738
+ * @param doValidate Makes a server side validation call. These are necessary to ensure that
739
+ * the right models are loaded.
740
+ */
741
+ this.setDtoConf = (configuration, doValidate = true) => __awaiter(this, void 0, void 0, function* () { return yield this._internal.setDtoConf(configuration, doValidate); });
656
742
  this.listenForChange = (l) => this._internal.changeObservable.listen(l);
657
743
  this.stopListenForChange = (l) => this._internal.changeObservable.stopListen(l);
658
744
  this.stopAllListenForChange = () => this._internal.changeObservable.stopAllListen();
@@ -731,6 +817,21 @@ export class CfgProduct {
731
817
  get visible() {
732
818
  return this._internal.visible;
733
819
  }
820
+ // A similar text to the one below exists in global-message-managers.md and should be kept in sync.
821
+ /**
822
+ * Functional selection is a Catalogues feature where selecting Options on Features result in that you
823
+ * "jump" to another Product as a result of the Validate call. You normally do not notice that a functional
824
+ * selection has occurred except for the styleNr changing. Functional selection can change which Features
825
+ * from the original product call are used as root Features. This can in turn affect if serialized
826
+ * configuration can be applied or not.
827
+ *
828
+ * The SDK can currently only apply serialized configuration if the list of root Features has not changed.
829
+ * For this reason, when functional selection has happened, extracting data for external systems might work
830
+ * well, but reapplying back into Stage will probably fail.
831
+ */
832
+ get hasRootFeaturesChanged() {
833
+ return this._internal.hasRootFeaturesChanged;
834
+ }
734
835
  get rawProductData() {
735
836
  return this._internal.rawProductData;
736
837
  }
@@ -1,4 +1,4 @@
1
- import { DtoCatalogueParamsWithLang, DtoProductParamsWithLang } from "./CatalogueAPI";
1
+ import { DtoCatalogueParamsWithCidAndLang, DtoProductParamsWithCidAndLang } from "./CatalogueAPI";
2
2
  /**
3
3
  * These methods aims to provide a default suggested way of building
4
4
  * URLs to Products and Catalogues. By using consistent URL:s copy-paste
@@ -8,7 +8,20 @@ import { DtoCatalogueParamsWithLang, DtoProductParamsWithLang } from "./Catalogu
8
8
  * Internally at Configura we try to stick with this format.
9
9
  */
10
10
  export declare class CfgReferencePathHelper {
11
- static getCataloguePath: (browsingRootUrl: string, catParams: DtoCatalogueParamsWithLang) => string;
12
- static getProductPath: (browsingRootUrl: string, productParams: DtoProductParamsWithLang) => string;
11
+ /**
12
+ * Use to generate URLs in our reference format. This is the format Configura uses in our integrations.
13
+ * @param browsingRootUrl The URL where Stage browsing begins
14
+ * @param catParams What catalogue to generate URL for.
15
+ * @returns An URL to a catalogue
16
+ */
17
+ static getCataloguePath: (browsingRootUrl: string, catParams: DtoCatalogueParamsWithCidAndLang) => string;
18
+ /**
19
+ * Use to generate URLs in our reference format. This is the format Configura uses in our integrations.
20
+ * @param browsingRootUrl The URL where Stage browsing begins
21
+ * @param productParams What product to generate URL for.
22
+ * @param separator Optional, defaults to "product", but can be changed to indicate another function.
23
+ * @returns An URL to a product
24
+ */
25
+ static getProductPath: (browsingRootUrl: string, productParams: DtoProductParamsWithCidAndLang, separator?: string) => string;
13
26
  }
14
27
  //# sourceMappingURL=CfgReferencePathHelper.d.ts.map
@@ -9,5 +9,18 @@ import { encodeURIComponents } from "@configura/web-utilities";
9
9
  */
10
10
  export class CfgReferencePathHelper {
11
11
  }
12
+ /**
13
+ * Use to generate URLs in our reference format. This is the format Configura uses in our integrations.
14
+ * @param browsingRootUrl The URL where Stage browsing begins
15
+ * @param catParams What catalogue to generate URL for.
16
+ * @returns An URL to a catalogue
17
+ */
12
18
  CfgReferencePathHelper.getCataloguePath = (browsingRootUrl, catParams) => `${browsingRootUrl}/${encodeURIComponents(catParams.cid, catParams.lang, catParams.enterprise, catParams.prdCat, catParams.prdCatVersion, catParams.vendor, catParams.priceList)}`;
13
- CfgReferencePathHelper.getProductPath = (browsingRootUrl, productParams) => `${CfgReferencePathHelper.getCataloguePath(browsingRootUrl, productParams)}/product/${encodeURIComponent(productParams.partNumber)}`;
19
+ /**
20
+ * Use to generate URLs in our reference format. This is the format Configura uses in our integrations.
21
+ * @param browsingRootUrl The URL where Stage browsing begins
22
+ * @param productParams What product to generate URL for.
23
+ * @param separator Optional, defaults to "product", but can be changed to indicate another function.
24
+ * @returns An URL to a product
25
+ */
26
+ CfgReferencePathHelper.getProductPath = (browsingRootUrl, productParams, separator = "product") => `${CfgReferencePathHelper.getCataloguePath(browsingRootUrl, productParams)}/${separator}/${encodeURIComponent(productParams.partNumber)}`;
@@ -1,5 +1,14 @@
1
- import { DtoAdditionalProductConfiguration, DtoConfAddProd, DtoConfFeature, DtoConfProd, DtoSelectedOption } from "./CatalogueAPI.js";
2
- export declare const isDtoConfProdAdditional: (value: DtoConfProd) => value is DtoConfAddProd;
3
- export declare const convertDtoConfProdToV1: (conf: DtoConfProd, silenceWarnings?: boolean) => DtoAdditionalProductConfiguration;
4
- export declare const convertDtoConfFeaturesToSelOptions: (features: DtoConfFeature[], silenceWarnings?: boolean) => DtoSelectedOption[];
1
+ import { DtoAdditionalProductConf, DtoAdditionalProductConfiguration, DtoFeatureConf, DtoProductConf, DtoSelectedOption } from "./CatalogueAPI.js";
2
+ export declare const isDtoProductConfAdditional: (value: DtoProductConf) => value is DtoAdditionalProductConf;
3
+ export declare const convertDtoProductConfToV1: (conf: DtoProductConf, silenceWarnings?: boolean) => DtoAdditionalProductConfiguration;
4
+ export declare const convertDtoFeatureConfsToSelOptions: (features: DtoFeatureConf[], silenceWarnings?: boolean) => DtoSelectedOption[];
5
+ /**
6
+ * Serializes and compacts the configuration into a format especially suited for URLs
7
+ */
8
+ export declare const dtoProductConfigurationToCompactString: (conf: DtoProductConf) => string;
9
+ /**
10
+ * Deserializes and inflates the configuration from the compacted format
11
+ */
12
+ export declare const compactStringToDtoProductConf: (versionAndConf: string) => DtoProductConf;
13
+ export declare const stripExtendedDataFromDtoProductConf: (conf: DtoProductConf) => DtoProductConf;
5
14
  //# sourceMappingURL=ConfigurationConverter.d.ts.map
@@ -1,33 +1,33 @@
1
- export const isDtoConfProdAdditional = (value) => "refKey" in value;
1
+ export const isDtoProductConfAdditional = (value) => "refKey" in value;
2
2
  // As this has potential to flood the terminal we only inform once
3
3
  let hasInformedAboutProdParams = false;
4
4
  let hasInformedAboutGroupCode = false;
5
5
  let hasInformedAboutUnit = false;
6
- export const convertDtoConfProdToV1 = (conf, silenceWarnings = false) => {
6
+ export const convertDtoProductConfToV1 = (conf, silenceWarnings = false) => {
7
7
  var _a, _b;
8
8
  if (!silenceWarnings && conf.prodParams !== undefined && !hasInformedAboutProdParams) {
9
9
  hasInformedAboutProdParams = true;
10
- console.info("Incoming DtoProductConfiguration contains prodParams. These will be ignored.");
10
+ console.info("Incoming DtoProductConf contains prodParams. These will be ignored.");
11
11
  }
12
12
  const result = {
13
13
  selOptions: ((_a = conf.features) !== null && _a !== void 0 ? _a : []).map((f) => ({
14
14
  code: "!~!",
15
- next: convertDtoConfFeatureToApiSelection(f, silenceWarnings),
15
+ next: convertDtoFeatureConfToApiSelection(f, silenceWarnings),
16
16
  })),
17
- additionalProducts: ((_b = conf.additionalProducts) !== null && _b !== void 0 ? _b : []).map((p) => convertDtoConfProdToV1(p, silenceWarnings)),
17
+ additionalProducts: ((_b = conf.additionalProducts) !== null && _b !== void 0 ? _b : []).map((p) => convertDtoProductConfToV1(p, silenceWarnings)),
18
18
  selected: true,
19
19
  };
20
- if (isDtoConfProdAdditional(conf)) {
20
+ if (isDtoProductConfAdditional(conf)) {
21
21
  result.refKey = conf.refKey;
22
22
  result.selected = conf.selected;
23
23
  }
24
24
  return result;
25
25
  };
26
- export const convertDtoConfFeaturesToSelOptions = (features, silenceWarnings = false) => (features !== null && features !== void 0 ? features : []).map((f) => ({
26
+ export const convertDtoFeatureConfsToSelOptions = (features, silenceWarnings = false) => (features !== null && features !== void 0 ? features : []).map((f) => ({
27
27
  code: "!~!",
28
- next: convertDtoConfFeatureToApiSelection(f, silenceWarnings),
28
+ next: convertDtoFeatureConfToApiSelection(f, silenceWarnings),
29
29
  }));
30
- const convertDtoConfFeatureToApiSelection = (feature, silenceWarnings) => {
30
+ const convertDtoFeatureConfToApiSelection = (feature, silenceWarnings) => {
31
31
  const { groupCode, options, unit } = feature;
32
32
  if (!silenceWarnings && groupCode !== undefined && !hasInformedAboutGroupCode) {
33
33
  hasInformedAboutGroupCode = true;
@@ -39,13 +39,13 @@ const convertDtoConfFeatureToApiSelection = (feature, silenceWarnings) => {
39
39
  }
40
40
  const result = {};
41
41
  for (const option of (options !== null && options !== void 0 ? options : []).filter((o) => o.selected)) {
42
- result[option.code] = convertDtoConfOptionToSelectedOption(option, silenceWarnings);
42
+ result[option.code] = convertDtoOptionConfToSelectedOption(option, silenceWarnings);
43
43
  }
44
44
  return result;
45
45
  };
46
- const convertDtoConfOptionToSelectedOption = (option, silenceWarnings) => {
46
+ const convertDtoOptionConfToSelectedOption = (option, silenceWarnings) => {
47
47
  const { features, code, numericValue } = option;
48
- const selectionTrees = (features !== null && features !== void 0 ? features : []).map((f) => convertDtoConfFeatureToApiSelection(f, silenceWarnings));
48
+ const selectionTrees = (features !== null && features !== void 0 ? features : []).map((f) => convertDtoFeatureConfToApiSelection(f, silenceWarnings));
49
49
  const mergedSelectionTree = {};
50
50
  let anyItems = false;
51
51
  for (const selectionTree of selectionTrees) {
@@ -70,3 +70,97 @@ const convertDtoConfOptionToSelectedOption = (option, silenceWarnings) => {
70
70
  }
71
71
  return selectedOption;
72
72
  };
73
+ const versionedRegex = /^v(\d+)(.+)$/;
74
+ const jsonKeyRegex = /"([^"]+)":/g;
75
+ const swapForUrlAdaptedRegex = /[-~_{}"]/g;
76
+ const shortToLong = new Map();
77
+ const longToShort = new Map();
78
+ // The replacement scheme here assumes this will only be used for keys
79
+ // in DtoProductConf and the knowledge that it contains no one
80
+ // one char keys.
81
+ for (const [long, short] of [
82
+ ["configuration", "c"],
83
+ ["additionalProducts", "a"],
84
+ ["prodParams", "p"],
85
+ ["refKey", "r"],
86
+ ["selected", "s"],
87
+ ["features", "f"],
88
+ ["code", "d"],
89
+ ["options", "o"],
90
+ ["numericValue", "n"],
91
+ ["groupCode", "g"],
92
+ ["unit", "u"],
93
+ ]) {
94
+ shortToLong.set(short, long);
95
+ longToShort.set(long, short);
96
+ }
97
+ /**
98
+ * As certain chars are abundant in JSON but less abundant in the actual data
99
+ * we swap these so that frequent characters do not need to be URL-encoded.
100
+ */
101
+ const jsonStringSwapCharsForUrl = (data) => data.replace(swapForUrlAdaptedRegex, (char) => {
102
+ switch (char) {
103
+ case "{":
104
+ return "~";
105
+ case "}":
106
+ return "-";
107
+ case '"':
108
+ return "_";
109
+ case "~":
110
+ return "{";
111
+ case "-":
112
+ return "}";
113
+ case "_":
114
+ return '"';
115
+ default:
116
+ throw new Error(`Unexpected char "${char}" in swap for URL`);
117
+ }
118
+ });
119
+ const compactDtoProductConfJsonKeys = (data) => data.replace(jsonKeyRegex, (_, key) => { var _a; return `"${(_a = longToShort.get(key)) !== null && _a !== void 0 ? _a : key}":`; });
120
+ const expandDtoProductConfJsonKeys = (data) => data.replace(jsonKeyRegex, (_, key) => { var _a; return `"${(_a = shortToLong.get(key)) !== null && _a !== void 0 ? _a : key}":`; });
121
+ /**
122
+ * Serializes and compacts the configuration into a format especially suited for URLs
123
+ */
124
+ export const dtoProductConfigurationToCompactString = (conf) => "v1" +
125
+ jsonStringSwapCharsForUrl(compactDtoProductConfJsonKeys(JSON.stringify(conf, undefined, "")));
126
+ /**
127
+ * Deserializes and inflates the configuration from the compacted format
128
+ */
129
+ export const compactStringToDtoProductConf = (versionAndConf) => {
130
+ const match = versionedRegex.exec(versionAndConf);
131
+ if (match === null) {
132
+ throw new Error("Could not match version string");
133
+ }
134
+ const [, version, conf] = match;
135
+ if (version !== "1") {
136
+ throw new Error("Unknown packed URL version");
137
+ }
138
+ if (conf === "") {
139
+ throw new Error("No conf found");
140
+ }
141
+ return JSON.parse(expandDtoProductConfJsonKeys(jsonStringSwapCharsForUrl(conf)));
142
+ };
143
+ export const stripExtendedDataFromDtoProductConf = (conf) => {
144
+ var _a, _b;
145
+ return ({
146
+ features: (_a = conf.features) === null || _a === void 0 ? void 0 : _a.map(stripExtendedDataFromDtoFeatureConf),
147
+ additionalProducts: (_b = conf.additionalProducts) === null || _b === void 0 ? void 0 : _b.map(stripExtendedDataFromDtoAdditionalProductConfiguration),
148
+ });
149
+ };
150
+ const stripExtendedDataFromDtoAdditionalProductConfiguration = (conf) => (Object.assign(Object.assign({}, stripExtendedDataFromDtoProductConf(conf)), { refKey: conf.refKey, selected: conf.selected }));
151
+ const stripExtendedDataFromDtoFeatureConf = (conf) => {
152
+ var _a;
153
+ return ({
154
+ code: conf.code,
155
+ options: (_a = conf.options) === null || _a === void 0 ? void 0 : _a.map(stripExtendedDataFromDtoOptionConf),
156
+ });
157
+ };
158
+ const stripExtendedDataFromDtoOptionConf = (conf) => {
159
+ var _a;
160
+ return ({
161
+ code: conf.code,
162
+ selected: conf.selected,
163
+ numericValue: conf.numericValue,
164
+ features: (_a = conf.features) === null || _a === void 0 ? void 0 : _a.map(stripExtendedDataFromDtoFeatureConf),
165
+ });
166
+ };
@@ -2,25 +2,57 @@ import { CfgWindowEventManager } from "./CfgWindowEventManager.js";
2
2
  declare type CfgHistoryManagerMessageData = {
3
3
  initial: boolean;
4
4
  };
5
+ /**
6
+ * How the history is updated.
7
+ * @param DoNotWrite Only listens to initial URL-values.
8
+ * @param Replace Replaces the current history frame at updates.
9
+ * @param Push Adds history frames at updates.
10
+ * @param ReplaceAndUpdateUrl Replaces the current history frame at updates and updates the browser URL
11
+ * @param PushAndUpdateUrl Adds history frames at updates and updates the browser URL
12
+ */
13
+ export declare enum HistoryMode {
14
+ DoNotWrite = 0,
15
+ Replace = 1,
16
+ Push = 2,
17
+ ReplaceAndUpdateUrl = 3,
18
+ PushAndUpdateUrl = 4
19
+ }
5
20
  /**
6
21
  * The collected data used when sending. For history this both contains the message, which is
7
- * used as the "state" in the history-frame, and the qsKeyValues, which are used in the URL.
22
+ * used as the "state" in the history-frame, and the qsKeyValues, which are query string
23
+ * key-values used in the URL.
8
24
  * When navigating back and forth the stage (message) is used to restore the old state. When
9
25
  * opening a window with no prior history the Query String will be used.
10
26
  */
11
27
  export declare type CfgHistoryManagerSendData<D> = {
12
28
  message: D;
13
- useHistoryPush: boolean;
29
+ mode: HistoryMode;
14
30
  qsKeyValues: Map<string, string | undefined>;
15
31
  };
16
32
  /**
17
- * This class does nothing on it's own. It is used to coordinate writing to the history, that
18
- * is, updating the browsing history.
33
+ * This class is used to coordinate writing and reading to the browser history.
34
+ * It handles messages sent from the connectors.
19
35
  */
20
36
  export declare class CfgHistoryManager extends CfgWindowEventManager<"popstate", CfgHistoryManagerSendData<CfgHistoryManagerMessageData>> {
21
37
  private static _instance;
22
38
  static get instance(): CfgHistoryManager;
23
39
  private constructor();
40
+ private _aggregatedQsKeyValues;
41
+ private _urlUpdateObservable;
42
+ /**
43
+ * @returns The current browser URL updated with the latest updates from
44
+ * the Connectors.
45
+ */
46
+ getUrl(): string;
47
+ /**
48
+ * Listen for updated URL:s. This doesn't have to mean the URL in the
49
+ * browser has been updated.
50
+ */
51
+ listenForUrl(listener: (url: string) => void): void;
52
+ /**
53
+ * Stop listen.
54
+ */
55
+ stopListenForUrl(listener: (url: string) => void): void;
24
56
  /**
25
57
  * Write to the history
26
58
  */
@@ -46,6 +78,7 @@ export declare class CfgHistoryManager extends CfgWindowEventManager<"popstate",
46
78
  private static _makeUpdatedState;
47
79
  protected readonly eventType = "popstate";
48
80
  protected getDataFromEvent(event: PopStateEvent): unknown;
81
+ static getMessageFromCurrentHistoryState(messageKey: string): unknown | undefined;
49
82
  }
50
83
  export {};
51
84
  //# sourceMappingURL=CfgHistoryManager.d.ts.map