@configura/web-api 2.2.0-alpha.1 → 2.2.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.
- package/.eslintrc.json +5 -5
- package/LICENSE +201 -201
- package/README.md +1 -1
- package/dist/CatalogueAPI.d.ts +633 -633
- package/dist/CatalogueAPI.js +312 -312
- package/dist/CfgMeasure.d.ts +32 -32
- package/dist/CfgMeasure.js +30 -30
- package/dist/CfgProduct.d.ts +359 -344
- package/dist/CfgProduct.js +1005 -992
- package/dist/CfgReferencePathHelper.d.ts +26 -26
- package/dist/CfgReferencePathHelper.js +26 -26
- package/dist/index.d.ts +24 -24
- package/dist/index.js +24 -24
- package/dist/io/CfgHistoryManager.d.ts +83 -83
- package/dist/io/CfgHistoryManager.js +144 -144
- package/dist/io/CfgHistoryToProdConfConnector.d.ts +21 -21
- package/dist/io/CfgHistoryToProdConfConnector.js +50 -50
- package/dist/io/CfgIOManager.d.ts +53 -53
- package/dist/io/CfgIOManager.js +134 -134
- package/dist/io/CfgIOProdConfConnector.d.ts +54 -54
- package/dist/io/CfgIOProdConfConnector.js +139 -139
- package/dist/io/CfgIOWarningSupplier.d.ts +3 -3
- package/dist/io/CfgIOWarningSupplier.js +1 -1
- package/dist/io/CfgObservableStateManager.d.ts +25 -25
- package/dist/io/CfgObservableStateManager.js +69 -69
- package/dist/io/CfgObservableStateToProdConfConnector.d.ts +15 -15
- package/dist/io/CfgObservableStateToProdConfConnector.js +17 -17
- package/dist/io/CfgWindowEventManager.d.ts +21 -21
- package/dist/io/CfgWindowEventManager.js +38 -38
- package/dist/io/CfgWindowMessageManager.d.ts +40 -40
- package/dist/io/CfgWindowMessageManager.js +91 -91
- package/dist/io/CfgWindowMessageToProdConfConnector.d.ts +17 -17
- package/dist/io/CfgWindowMessageToProdConfConnector.js +19 -19
- package/dist/io/index.d.ts +8 -8
- package/dist/io/index.js +8 -8
- package/dist/material/CfgMaterialMapping.d.ts +7 -7
- package/dist/material/CfgMaterialMapping.js +181 -181
- package/dist/material/CfgMtrlApplication.d.ts +18 -18
- package/dist/material/CfgMtrlApplication.js +43 -43
- package/dist/material/CfgMtrlApplicationSource.d.ts +7 -7
- package/dist/material/CfgMtrlApplicationSource.js +8 -8
- package/dist/material/CfgMtrlSource.d.ts +19 -19
- package/dist/material/CfgMtrlSource.js +40 -40
- package/dist/material/CfgMtrlSourceWithMetaData.d.ts +7 -7
- package/dist/material/CfgMtrlSourceWithMetaData.js +1 -1
- package/dist/productConfiguration/CfgFeature.d.ts +199 -199
- package/dist/productConfiguration/CfgFeature.js +691 -691
- package/dist/productConfiguration/CfgOption.d.ts +160 -160
- package/dist/productConfiguration/CfgOption.js +464 -464
- package/dist/productConfiguration/CfgProductConfiguration.d.ts +136 -129
- package/dist/productConfiguration/CfgProductConfiguration.js +355 -346
- package/dist/productConfiguration/filters.d.ts +17 -17
- package/dist/productConfiguration/filters.js +141 -141
- package/dist/productConfiguration/productParamsGenerator.d.ts +15 -15
- package/dist/productConfiguration/productParamsGenerator.js +65 -65
- package/dist/productConfiguration/utilitiesProductConfiguration.d.ts +17 -17
- package/dist/productConfiguration/utilitiesProductConfiguration.js +89 -87
- package/dist/productLoader.d.ts +33 -33
- package/dist/productLoader.js +49 -49
- package/dist/syncGroups/SyncGroupsApplyMode.d.ts +20 -20
- package/dist/syncGroups/SyncGroupsApplyMode.js +21 -21
- package/dist/syncGroups/SyncGroupsHandler.d.ts +47 -47
- package/dist/syncGroups/SyncGroupsHandler.js +370 -370
- package/dist/syncGroups/SyncGroupsPathHelper.d.ts +26 -26
- package/dist/syncGroups/SyncGroupsPathHelper.js +90 -90
- package/dist/syncGroups/SyncGroupsState.d.ts +39 -39
- package/dist/syncGroups/SyncGroupsState.js +167 -167
- package/dist/syncGroups/SyncGroupsTransaction.d.ts +154 -154
- package/dist/syncGroups/SyncGroupsTransaction.js +589 -589
- package/dist/tasks/TaskHandler.d.ts +77 -77
- package/dist/tasks/TaskHandler.js +276 -276
- package/dist/tasks/formats.d.ts +4 -4
- package/dist/tasks/formats.js +7 -7
- package/dist/tests/testData/collectorForTest.d.ts +73 -73
- package/dist/tests/testData/collectorForTest.js +194 -194
- package/dist/tests/testData/dummyProductForTest.d.ts +4 -4
- package/dist/tests/testData/dummyProductForTest.js +32 -32
- package/dist/tests/testData/testDataAdditionalProductInAdditionalProductInProductForTest.d.ts +11 -11
- package/dist/tests/testData/testDataAdditionalProductInAdditionalProductInProductForTest.js +282 -282
- package/dist/tests/testData/testDataCachedGetProduct.d.ts +5 -5
- package/dist/tests/testData/testDataCachedGetProduct.js +187 -187
- package/dist/tests/testData/testDataCachedPostValidate.d.ts +7 -7
- package/dist/tests/testData/testDataCachedPostValidate.js +185 -185
- package/dist/tests/testData/testDataConstraints.d.ts +3 -3
- package/dist/tests/testData/testDataConstraints.js +174 -174
- package/dist/tests/testData/testDataNoAdditionalProductNoPropagateForTest.d.ts +3 -3
- package/dist/tests/testData/testDataNoAdditionalProductNoPropagateForTest.js +1099 -1099
- package/dist/tests/testData/testDataOptions.d.ts +12 -12
- package/dist/tests/testData/testDataOptions.js +60 -60
- package/dist/tests/testData/testDataProductAggregatedPrice.d.ts +6 -6
- package/dist/tests/testData/testDataProductAggregatedPrice.js +189 -189
- package/dist/tests/testData/testDataUpcharge.d.ts +8 -8
- package/dist/tests/testData/testDataUpcharge.js +121 -121
- package/dist/utilitiesCatalogueData.d.ts +47 -47
- package/dist/utilitiesCatalogueData.js +180 -180
- package/dist/utilitiesCataloguePermission.d.ts +38 -38
- package/dist/utilitiesCataloguePermission.js +79 -79
- package/dist/utilitiesConfiguration.d.ts +28 -28
- package/dist/utilitiesConfiguration.js +200 -200
- package/dist/utilitiesNumericValues.d.ts +24 -24
- package/dist/utilitiesNumericValues.js +114 -114
- package/package.json +3 -3
|
@@ -1,589 +1,589 @@
|
|
|
1
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
-
});
|
|
9
|
-
};
|
|
10
|
-
import { assert } from "@configura/web-utilities";
|
|
11
|
-
import { CfgProductBubbleMode } from "../CfgProduct.js";
|
|
12
|
-
import { SelectionType, } from "../productConfiguration/CfgFeature.js";
|
|
13
|
-
import { ProductConfigurationBubbleMode, } from "../productConfiguration/CfgOption.js";
|
|
14
|
-
import { SyncGroupsApplyMode } from "./SyncGroupsApplyMode.js";
|
|
15
|
-
import { SyncGroupsPathHelper } from "./SyncGroupsPathHelper.js";
|
|
16
|
-
var AffectedLevel;
|
|
17
|
-
(function (AffectedLevel) {
|
|
18
|
-
AffectedLevel[AffectedLevel["None"] = 0] = "None";
|
|
19
|
-
AffectedLevel[AffectedLevel["Initialized"] = 1] = "Initialized";
|
|
20
|
-
AffectedLevel[AffectedLevel["Set"] = 2] = "Set";
|
|
21
|
-
})(AffectedLevel || (AffectedLevel = {}));
|
|
22
|
-
/**
|
|
23
|
-
* The Transaction is a transient object used to track all the changes made to the supplied
|
|
24
|
-
* SyncGroupState as a result of a (normally) single initial event, like when opening a product
|
|
25
|
-
* or the user selecting an option on an already open product.
|
|
26
|
-
*
|
|
27
|
-
* All state changes are made on an internal copy of the original SyncGroupState (called target)
|
|
28
|
-
* so they can later be used (i.e. committed) at once to the visible product, or safely discarded
|
|
29
|
-
* without affecting anything.
|
|
30
|
-
*
|
|
31
|
-
* The Transaction keeps track of which Features and SyncGroups have been affected so far, to
|
|
32
|
-
* eliminate the risk of infinite loops.
|
|
33
|
-
*
|
|
34
|
-
* Terminology
|
|
35
|
-
* ===========
|
|
36
|
-
*
|
|
37
|
-
* You APPLY things onto the transaction in order to update the sync state inside the transaction.
|
|
38
|
-
* The transaction can then UPDATE other things in order to apply it's sync state onto them.
|
|
39
|
-
*
|
|
40
|
-
* Transaction.applyThing() ...onto the transaction
|
|
41
|
-
* Transaction.updateThing() ...with the transaction
|
|
42
|
-
*
|
|
43
|
-
* @see SyncGroupHandler.ts for more information, including the general resolution algorithm.
|
|
44
|
-
*/
|
|
45
|
-
export class SyncGroupsTransaction {
|
|
46
|
-
constructor(syncState, updateMode, productLoader, original, target, initial) {
|
|
47
|
-
this._closed = false;
|
|
48
|
-
this.affectedSelectOneFeatures = new Map();
|
|
49
|
-
this.affectedSelectManyOptions = new Set();
|
|
50
|
-
this.affectedSelectOneSyncGroups = new Set();
|
|
51
|
-
this.affectedSelectManySyncGroupsAndOptions = new Map();
|
|
52
|
-
this.syncState = syncState.clone();
|
|
53
|
-
this.originalSyncState = syncState;
|
|
54
|
-
this.updateMode = updateMode;
|
|
55
|
-
this.productLoader = productLoader;
|
|
56
|
-
this.originalProduct = original;
|
|
57
|
-
this.target = target;
|
|
58
|
-
this.initial = initial;
|
|
59
|
-
}
|
|
60
|
-
static make(syncState, updateMode, product, productLoader, assumeNoStartState) {
|
|
61
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
62
|
-
const t = new this(syncState, updateMode, productLoader, product, yield product.clone(), assumeNoStartState ? undefined : yield product.clone());
|
|
63
|
-
return t;
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
/************************************************************************
|
|
67
|
-
* Public API (intentionally limited)
|
|
68
|
-
************************************************************************/
|
|
69
|
-
get isClosed() {
|
|
70
|
-
return this._closed;
|
|
71
|
-
}
|
|
72
|
-
close() {
|
|
73
|
-
this._closed = true;
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* This is (among other) the entry point when loading a new product.
|
|
77
|
-
*
|
|
78
|
-
* @returns true if at least one Feature changed it state. This is a bit counter intuitive,
|
|
79
|
-
* but as this method will hand over to applyProduct if it updates the SyncState it can cause
|
|
80
|
-
* the Features to change. And we need to pass that information back to know when to stop.
|
|
81
|
-
*/
|
|
82
|
-
applyRootProduct() {
|
|
83
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
84
|
-
const productWithInitial = { target: this.target, initial: this.initial };
|
|
85
|
-
if (!(yield this.applyProduct(productWithInitial))) {
|
|
86
|
-
// All done!
|
|
87
|
-
return false;
|
|
88
|
-
}
|
|
89
|
-
return yield this.updateRootProduct(undefined);
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* This is the entry point for an active (often user) selection of an option.
|
|
94
|
-
*/
|
|
95
|
-
selectOption(optionPath, on) {
|
|
96
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
97
|
-
const option = SyncGroupsPathHelper.getOptionFromPath(optionPath, this.target);
|
|
98
|
-
const feature = option.parent;
|
|
99
|
-
let change = false;
|
|
100
|
-
let productToValidate = undefined;
|
|
101
|
-
// At this point in handling sync groups we go back to normal selecting.
|
|
102
|
-
//
|
|
103
|
-
// This is because a normal selection can bring features into scope which should push to or
|
|
104
|
-
// (if the SyncGroup is defined) pull from the Sync State.
|
|
105
|
-
//
|
|
106
|
-
// We use BubbleSelected (without validation) as we can delay the validation until after we
|
|
107
|
-
// have applied the SyncGroup (if any).
|
|
108
|
-
if (yield feature.selectOption(option, on, ProductConfigurationBubbleMode.BubbleSelected)) {
|
|
109
|
-
productToValidate = feature.parentProduct;
|
|
110
|
-
change = true;
|
|
111
|
-
switch (feature.selectionType) {
|
|
112
|
-
case SelectionType.SelectOne:
|
|
113
|
-
this.affectedSelectOneFeatures.set(feature, AffectedLevel.Set);
|
|
114
|
-
break;
|
|
115
|
-
case SelectionType.SelectMany:
|
|
116
|
-
this.affectedSelectManyOptions.add(option);
|
|
117
|
-
break;
|
|
118
|
-
default:
|
|
119
|
-
throw new Error("Should not happen");
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
switch (feature.selectionType) {
|
|
123
|
-
case SelectionType.SelectOne:
|
|
124
|
-
this.applySelectOneFeature(feature, true, false);
|
|
125
|
-
break;
|
|
126
|
-
case SelectionType.SelectMany:
|
|
127
|
-
this.applySelectManyOption(option, true);
|
|
128
|
-
break;
|
|
129
|
-
}
|
|
130
|
-
if (yield this.updateRootProduct(productToValidate)) {
|
|
131
|
-
change = true;
|
|
132
|
-
}
|
|
133
|
-
return change;
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
/**
|
|
137
|
-
* Overwrites the original Product and SyncGroupState (supplied when creating the Transaction)
|
|
138
|
-
* with the internal versions inside this Transaction.
|
|
139
|
-
*
|
|
140
|
-
* @throws error if the transaction has already been closed.
|
|
141
|
-
*/
|
|
142
|
-
commit() {
|
|
143
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
144
|
-
assert(this.isClosed === false, "Trying to commit a closed Transaction");
|
|
145
|
-
this.originalSyncState.copyFrom(this.syncState);
|
|
146
|
-
yield this.originalProduct.copyFrom(this.target, false, this.productLoader);
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
/************************************************************************
|
|
150
|
-
* Updating things with the Transaction's SyncState
|
|
151
|
-
************************************************************************/
|
|
152
|
-
/**
|
|
153
|
-
* Apply current sync groups on those who wants to listen until there is no more to settle.
|
|
154
|
-
* @returns true if at least one Feature changed selected Option
|
|
155
|
-
*/
|
|
156
|
-
updateRootProduct(productToValidate) {
|
|
157
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
158
|
-
const productsToValidate = new Set();
|
|
159
|
-
if (productToValidate !== undefined) {
|
|
160
|
-
productsToValidate.add(productToValidate);
|
|
161
|
-
}
|
|
162
|
-
const productWithInitial = { target: this.target, initial: this.initial };
|
|
163
|
-
yield this.updateProduct(productWithInitial, productsToValidate);
|
|
164
|
-
if (productsToValidate.size === 0) {
|
|
165
|
-
// All have been settled, continue to pullPhase
|
|
166
|
-
return yield this.applyRootProduct();
|
|
167
|
-
}
|
|
168
|
-
if (this.isClosed) {
|
|
169
|
-
// We could exit in more places when the transaction has been aborted, but as
|
|
170
|
-
// revalidate is really the only thing that could be expensive time consuming we only
|
|
171
|
-
// check here.
|
|
172
|
-
return false;
|
|
173
|
-
}
|
|
174
|
-
const promises = [];
|
|
175
|
-
for (const product of productsToValidate) {
|
|
176
|
-
promises.push(product._revalidate(CfgProductBubbleMode.ToRoot, this.productLoader, true));
|
|
177
|
-
}
|
|
178
|
-
const revalidationResults = yield Promise.all(promises);
|
|
179
|
-
if (revalidationResults.every((r) => !r)) {
|
|
180
|
-
this.close();
|
|
181
|
-
return false;
|
|
182
|
-
}
|
|
183
|
-
// Apply over again, to settle deeper down. Our theory is that the front of "settled" will
|
|
184
|
-
// move deeper and deeper into the tree.
|
|
185
|
-
yield this.updateRootProduct(undefined);
|
|
186
|
-
// We had productsToValidate, and so there must have been a change.
|
|
187
|
-
return true;
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
/**
|
|
191
|
-
* Applies the SyncState to the Product and it's AdditionalProducts (sub-products).
|
|
192
|
-
* @param productsToValidate To this all products that will need validation are added.
|
|
193
|
-
*/
|
|
194
|
-
updateProduct(product, productsToValidate) {
|
|
195
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
196
|
-
const promises = [];
|
|
197
|
-
promises.push(this.updateFeatures(getFeaturesFromProduct(product), productsToValidate));
|
|
198
|
-
for (const additionalProduct of getAdditionalProducts(product)) {
|
|
199
|
-
promises.push(this.updateProduct(additionalProduct, productsToValidate));
|
|
200
|
-
}
|
|
201
|
-
yield Promise.all(promises);
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
/**
|
|
205
|
-
* Applies the SyncState to an array of Features.
|
|
206
|
-
* @param productsToValidate To this all products that will need validation are added.
|
|
207
|
-
*/
|
|
208
|
-
updateFeatures(features, productsToValidate) {
|
|
209
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
210
|
-
yield Promise.all(features.map((feature) => __awaiter(this, void 0, void 0, function* () {
|
|
211
|
-
switch (yield this.updateFeature(feature, productsToValidate)) {
|
|
212
|
-
case "stop":
|
|
213
|
-
return;
|
|
214
|
-
case "recurseDown":
|
|
215
|
-
yield this.updateOptions(getSelectedOptions(feature), productsToValidate);
|
|
216
|
-
return;
|
|
217
|
-
}
|
|
218
|
-
})));
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
/**
|
|
222
|
-
* Applies the SyncState to an array of Options.
|
|
223
|
-
* @param productsToValidate To this all products that will need validation are added.
|
|
224
|
-
*/
|
|
225
|
-
updateOptions(options, productsToValidate) {
|
|
226
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
227
|
-
yield Promise.all(options.map((option) => {
|
|
228
|
-
return this.updateFeatures(getFeaturesFromOption(option), productsToValidate);
|
|
229
|
-
}));
|
|
230
|
-
});
|
|
231
|
-
}
|
|
232
|
-
/**
|
|
233
|
-
* Applies the SyncState to a Feature
|
|
234
|
-
* @param productsToValidate To this all products that will need validation are added
|
|
235
|
-
* @returns Whether we shall stop recursing down (because we are in a state which we
|
|
236
|
-
* expect to be resolved later), we shall continue recursing down.
|
|
237
|
-
*/
|
|
238
|
-
updateFeature(featureWithInitial, productsToValidate) {
|
|
239
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
240
|
-
const feature = featureWithInitial.target;
|
|
241
|
-
const syncCode = feature.getSyncCode("pull");
|
|
242
|
-
if (syncCode === undefined) {
|
|
243
|
-
// Just continue down, features with no sync groups are always settled
|
|
244
|
-
return "recurseDown";
|
|
245
|
-
}
|
|
246
|
-
if (syncCode === false) {
|
|
247
|
-
// Here we only handle pull. Initializing those missing in SyncMap
|
|
248
|
-
// we do later. We wait until we have settled as much as we can with what
|
|
249
|
-
// we have now, to increase chances of the values being written to the
|
|
250
|
-
// SyncState being the right ones.
|
|
251
|
-
return "stop";
|
|
252
|
-
}
|
|
253
|
-
switch (feature.selectionType) {
|
|
254
|
-
case SelectionType.Group:
|
|
255
|
-
return "recurseDown";
|
|
256
|
-
case SelectionType.SelectOne:
|
|
257
|
-
return yield this.updateSelectOneFeature(syncCode, featureWithInitial, productsToValidate);
|
|
258
|
-
case SelectionType.SelectMany:
|
|
259
|
-
return yield this.updateSelectManyFeature(syncCode, featureWithInitial, productsToValidate);
|
|
260
|
-
}
|
|
261
|
-
});
|
|
262
|
-
}
|
|
263
|
-
/**
|
|
264
|
-
* Decides if the SyncState can be applied to the SelectOne Feature, and then changes
|
|
265
|
-
* the state of the Feature if so.
|
|
266
|
-
* @param syncCode What SyncGroup the Feature belongs to
|
|
267
|
-
* @param productsToValidate To this all products that will need validation are added
|
|
268
|
-
* @returns Whether we shall stop recursing down (because we are in a state which we
|
|
269
|
-
* expect to be resolved later), we shall continue recursing down.
|
|
270
|
-
*/
|
|
271
|
-
updateSelectOneFeature(syncCode, featureWithInitial, productsToValidate) {
|
|
272
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
273
|
-
const feature = featureWithInitial.target;
|
|
274
|
-
const featureDidJustComeIntoScope = featureWithInitial.initial === undefined;
|
|
275
|
-
const hasBeenAffectedLevel = this.affectedSelectOneFeatures.get(feature);
|
|
276
|
-
const syncGroupHasBeenUpdated = this.affectedSelectOneSyncGroups.has(syncCode);
|
|
277
|
-
if (hasBeenAffectedLevel === AffectedLevel.Set ||
|
|
278
|
-
(!syncGroupHasBeenUpdated && hasBeenAffectedLevel === AffectedLevel.Initialized)) {
|
|
279
|
-
// A feature can change value for two reasons:
|
|
280
|
-
// 1. The feature did just come into scope and loads from the sync group to set its "defaults"
|
|
281
|
-
// 2. The sync group has updated with a new value
|
|
282
|
-
// The rule is that in one sync group transaction the feature value is allowed to be updated
|
|
283
|
-
// once or twice. Once for initialization and once for new sync group value. But they have to
|
|
284
|
-
// happen in the right order.
|
|
285
|
-
return "recurseDown";
|
|
286
|
-
}
|
|
287
|
-
if (!featureDidJustComeIntoScope && !syncGroupHasBeenUpdated) {
|
|
288
|
-
return "recurseDown";
|
|
289
|
-
}
|
|
290
|
-
const currentSyncGroupValue = this.syncState.getForSelectOne(syncCode);
|
|
291
|
-
if (currentSyncGroupValue === undefined) {
|
|
292
|
-
// This branch will have to be settled later. Don't go further down here.
|
|
293
|
-
return "stop";
|
|
294
|
-
}
|
|
295
|
-
const selectedOption = feature.selectedOptions[0];
|
|
296
|
-
if (selectedOption !== undefined && selectedOption.code === currentSyncGroupValue) {
|
|
297
|
-
// Settled, continue
|
|
298
|
-
return "recurseDown";
|
|
299
|
-
}
|
|
300
|
-
const optionToSelect = feature.options.find((o) => o.code === currentSyncGroupValue);
|
|
301
|
-
if (optionToSelect === undefined) {
|
|
302
|
-
// No option which can be selected (keep current value and recurse down)
|
|
303
|
-
return "recurseDown";
|
|
304
|
-
}
|
|
305
|
-
// Update the value. Validations are collected so that we do not send more than necessary.
|
|
306
|
-
// Do not recurse further as we will change the state and so what is selected now won't be
|
|
307
|
-
// selected then.
|
|
308
|
-
yield feature.selectOption(optionToSelect._internal, true, ProductConfigurationBubbleMode.ToRoot);
|
|
309
|
-
this.affectedSelectOneFeatures.set(feature, syncGroupHasBeenUpdated ? AffectedLevel.Set : AffectedLevel.Initialized);
|
|
310
|
-
productsToValidate.add(feature.parentProduct);
|
|
311
|
-
return "stop";
|
|
312
|
-
});
|
|
313
|
-
}
|
|
314
|
-
/**
|
|
315
|
-
* Decides if the SyncState can be applied to Options in the SelectMany Feature, and
|
|
316
|
-
* then changes the state of the Options if so.
|
|
317
|
-
* @param syncCode What SyncGroup the Feature belongs to
|
|
318
|
-
* @param productsToValidate To this all products that will need validation are added
|
|
319
|
-
* @returns Always "stop" as recursion is handled internally. Return for consistency.
|
|
320
|
-
*/
|
|
321
|
-
updateSelectManyFeature(syncCode, feature, productsToValidate) {
|
|
322
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
323
|
-
const optionsToContinueDown = [];
|
|
324
|
-
for (const option of getOptions(feature)) {
|
|
325
|
-
switch (yield this.updateSelectManyOption(syncCode, option, productsToValidate)) {
|
|
326
|
-
case "stop":
|
|
327
|
-
continue;
|
|
328
|
-
case "recurseDown":
|
|
329
|
-
optionsToContinueDown.push(option);
|
|
330
|
-
continue;
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
yield this.updateOptions(optionsToContinueDown, productsToValidate);
|
|
334
|
-
// stop as the method above handles the recursion down
|
|
335
|
-
return "stop";
|
|
336
|
-
});
|
|
337
|
-
}
|
|
338
|
-
/**
|
|
339
|
-
* Decides if the SyncState can be applied to the SelectMany Option, and then changes
|
|
340
|
-
* the state of the Option if so.
|
|
341
|
-
* @param syncCode What SyncGroup the Feature belongs to
|
|
342
|
-
* @param productsToValidate To this all products that will need validation are added
|
|
343
|
-
* @returns Whether we shall stop recursing down (because we are in a state which we
|
|
344
|
-
* expect to be resolved later), we shall continue recursing down.
|
|
345
|
-
*/
|
|
346
|
-
updateSelectManyOption(syncCode, optionWithInitial, productsToValidate) {
|
|
347
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
348
|
-
const option = optionWithInitial.target;
|
|
349
|
-
const featureDidJustComeIntoScope = optionWithInitial.initial === undefined;
|
|
350
|
-
const optionSelected = option.selected;
|
|
351
|
-
const recurseOrStopIfNoChange = optionSelected ? "recurseDown" : "stop";
|
|
352
|
-
if (this.affectedSelectManyOptions.has(option)) {
|
|
353
|
-
// This option has already changed selection once for this transaction. We expect
|
|
354
|
-
// this to happen very rarely, as the algorithm should settle the selection tree
|
|
355
|
-
// further and further out. Nevertheless, this safeguard is needed to avoid infinite
|
|
356
|
-
// looping if for example the server would return the same data over and over.
|
|
357
|
-
return recurseOrStopIfNoChange;
|
|
358
|
-
}
|
|
359
|
-
if (!featureDidJustComeIntoScope &&
|
|
360
|
-
!this.hasSyncGroupAffectedForSelectMany(syncCode, option)) {
|
|
361
|
-
return "recurseDown";
|
|
362
|
-
}
|
|
363
|
-
const syncGroupValueForOption = this.syncState.getForSelectMany(syncCode, option.code);
|
|
364
|
-
if (syncGroupValueForOption === undefined) {
|
|
365
|
-
// The sync group has no opinion on this. If it is selected, just continue down.
|
|
366
|
-
return recurseOrStopIfNoChange;
|
|
367
|
-
}
|
|
368
|
-
if (syncGroupValueForOption === optionSelected) {
|
|
369
|
-
// We are in sync for this option
|
|
370
|
-
return recurseOrStopIfNoChange;
|
|
371
|
-
}
|
|
372
|
-
const feature = option.parent;
|
|
373
|
-
// Update the value. Validations are collected so that we do not
|
|
374
|
-
// send more than necessary. Do not recurse further as we will change
|
|
375
|
-
// the state and so what is selected now won't be selected then.
|
|
376
|
-
yield feature.selectOption(option, syncGroupValueForOption, ProductConfigurationBubbleMode.ToRoot);
|
|
377
|
-
this.affectedSelectManyOptions.add(option);
|
|
378
|
-
productsToValidate.add(feature.parentProduct);
|
|
379
|
-
return "stop";
|
|
380
|
-
});
|
|
381
|
-
}
|
|
382
|
-
/************************************************************************
|
|
383
|
-
* Applying things to the Transaction's SyncState
|
|
384
|
-
************************************************************************/
|
|
385
|
-
applyProduct(product) {
|
|
386
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
387
|
-
let change = false;
|
|
388
|
-
if (yield this.applyFeatures(getFeaturesFromProduct(product))) {
|
|
389
|
-
if (this.updateMode === SyncGroupsApplyMode.Strict) {
|
|
390
|
-
return true;
|
|
391
|
-
}
|
|
392
|
-
change = true;
|
|
393
|
-
}
|
|
394
|
-
for (const additionalProduct of getAdditionalProducts(product)) {
|
|
395
|
-
if (yield this.applyProduct(additionalProduct)) {
|
|
396
|
-
if (this.updateMode === SyncGroupsApplyMode.Strict) {
|
|
397
|
-
return true;
|
|
398
|
-
}
|
|
399
|
-
change = true;
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
return change;
|
|
403
|
-
});
|
|
404
|
-
}
|
|
405
|
-
applyFeatures(featureWithInitials) {
|
|
406
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
407
|
-
let change = false;
|
|
408
|
-
for (const featureWithInitial of featureWithInitials) {
|
|
409
|
-
const feature = featureWithInitial.target;
|
|
410
|
-
switch (feature.selectionType) {
|
|
411
|
-
case SelectionType.SelectOne:
|
|
412
|
-
if (this.applySelectOneFeature(feature, false, featureWithInitial.initial === undefined)) {
|
|
413
|
-
change = true;
|
|
414
|
-
}
|
|
415
|
-
break;
|
|
416
|
-
case SelectionType.SelectMany:
|
|
417
|
-
if (this.applySelectManyFeature(featureWithInitial)) {
|
|
418
|
-
change = true;
|
|
419
|
-
}
|
|
420
|
-
break;
|
|
421
|
-
}
|
|
422
|
-
if (change && this.updateMode === SyncGroupsApplyMode.Strict) {
|
|
423
|
-
return true;
|
|
424
|
-
}
|
|
425
|
-
if (yield this.applyOptions(getSelectedOptions(featureWithInitial))) {
|
|
426
|
-
if (this.updateMode === SyncGroupsApplyMode.Strict) {
|
|
427
|
-
return true;
|
|
428
|
-
}
|
|
429
|
-
change = true;
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
return change;
|
|
433
|
-
});
|
|
434
|
-
}
|
|
435
|
-
applyOptions(options) {
|
|
436
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
437
|
-
let change = false;
|
|
438
|
-
for (const option of options) {
|
|
439
|
-
if (yield this.applyFeatures(getFeaturesFromOption(option))) {
|
|
440
|
-
if (this.updateMode === SyncGroupsApplyMode.Strict) {
|
|
441
|
-
return true;
|
|
442
|
-
}
|
|
443
|
-
change = true;
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
return change;
|
|
447
|
-
});
|
|
448
|
-
}
|
|
449
|
-
applySelectOneFeature(feature, userInitiated, featureDidJustComeIntoScope) {
|
|
450
|
-
const selectionType = feature.selectionType;
|
|
451
|
-
if (selectionType !== SelectionType.SelectOne) {
|
|
452
|
-
throw new Error("can only be used for selectOne");
|
|
453
|
-
}
|
|
454
|
-
const syncCode = feature.getSyncCode("push");
|
|
455
|
-
if (syncCode === undefined || syncCode === false) {
|
|
456
|
-
return false;
|
|
457
|
-
}
|
|
458
|
-
if (this.affectedSelectOneSyncGroups.has(syncCode)) {
|
|
459
|
-
return false;
|
|
460
|
-
}
|
|
461
|
-
const currentSyncGroupOptionCode = this.syncState.getForSelectOne(syncCode);
|
|
462
|
-
const option = feature.selectedOptions[0];
|
|
463
|
-
if (option === undefined) {
|
|
464
|
-
// Options with no default are never written
|
|
465
|
-
return false;
|
|
466
|
-
}
|
|
467
|
-
if (userInitiated) {
|
|
468
|
-
// To make re-apply happen, even if it actually does not update the sync group
|
|
469
|
-
this.affectedSelectOneSyncGroups.add(syncCode);
|
|
470
|
-
}
|
|
471
|
-
if (option.code === currentSyncGroupOptionCode) {
|
|
472
|
-
return false;
|
|
473
|
-
}
|
|
474
|
-
// featureDidJustComeIntoScope, in CET there is a feature that if a feature appears which
|
|
475
|
-
// can not be set to the current sync group value, then it will set in the opposite
|
|
476
|
-
// direction. Like if the sync group was empty. To avoid bouncing back and forth we will
|
|
477
|
-
// need to enforce that a sync group can only be updated once per transaction
|
|
478
|
-
if (!(userInitiated ||
|
|
479
|
-
currentSyncGroupOptionCode === undefined ||
|
|
480
|
-
(featureDidJustComeIntoScope &&
|
|
481
|
-
feature.options.every((o) => currentSyncGroupOptionCode !== o.code)))) {
|
|
482
|
-
return false;
|
|
483
|
-
}
|
|
484
|
-
this.affectedSelectOneSyncGroups.add(syncCode);
|
|
485
|
-
this.syncState.setForSelectOne(syncCode, option.code);
|
|
486
|
-
return true;
|
|
487
|
-
}
|
|
488
|
-
applySelectManyFeature(featureWithInitial) {
|
|
489
|
-
let change = false;
|
|
490
|
-
for (const optionWithInitial of getOptions(featureWithInitial)) {
|
|
491
|
-
if (this.applySelectManyOption(optionWithInitial.target, false)) {
|
|
492
|
-
if (this.updateMode === SyncGroupsApplyMode.Strict) {
|
|
493
|
-
return true;
|
|
494
|
-
}
|
|
495
|
-
change = true;
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
return change;
|
|
499
|
-
}
|
|
500
|
-
applySelectManyOption(option, userInitiated) {
|
|
501
|
-
const feature = option.parent;
|
|
502
|
-
if (feature.selectionType !== SelectionType.SelectMany) {
|
|
503
|
-
throw new Error("can only be used for selectMany");
|
|
504
|
-
}
|
|
505
|
-
const syncCode = feature.getSyncCode("push");
|
|
506
|
-
if (syncCode === undefined || syncCode === false) {
|
|
507
|
-
return false;
|
|
508
|
-
}
|
|
509
|
-
if (this.hasSyncGroupAffectedForSelectMany(syncCode, option)) {
|
|
510
|
-
return false;
|
|
511
|
-
}
|
|
512
|
-
const optionSelected = option.selected;
|
|
513
|
-
const currentSyncGroupValue = this.syncState.getForSelectMany(syncCode, option.code);
|
|
514
|
-
if (userInitiated) {
|
|
515
|
-
// To make re-apply happen, even if it actually does not update the sync group
|
|
516
|
-
this.addSyncGroupAffectedForSelectMany(syncCode, option);
|
|
517
|
-
}
|
|
518
|
-
// We only initialize if the option has not been initiated or if it is userInitiated.
|
|
519
|
-
// userInitiated = active selection by the user.
|
|
520
|
-
if (currentSyncGroupValue !== undefined &&
|
|
521
|
-
!(userInitiated && currentSyncGroupValue !== optionSelected)) {
|
|
522
|
-
return false;
|
|
523
|
-
}
|
|
524
|
-
this.addSyncGroupAffectedForSelectMany(syncCode, option);
|
|
525
|
-
this.syncState.setForSelectMany(syncCode, option.code, optionSelected);
|
|
526
|
-
return true;
|
|
527
|
-
}
|
|
528
|
-
addSyncGroupAffectedForSelectMany(syncCode, option) {
|
|
529
|
-
let forSyncCode = this.affectedSelectManySyncGroupsAndOptions.get(syncCode);
|
|
530
|
-
if (forSyncCode === undefined) {
|
|
531
|
-
forSyncCode = new Set();
|
|
532
|
-
this.affectedSelectManySyncGroupsAndOptions.set(syncCode, forSyncCode);
|
|
533
|
-
}
|
|
534
|
-
forSyncCode.add(option.code);
|
|
535
|
-
}
|
|
536
|
-
hasSyncGroupAffectedForSelectMany(syncCode, option) {
|
|
537
|
-
var _a;
|
|
538
|
-
return ((_a = this.affectedSelectManySyncGroupsAndOptions.get(syncCode)) === null || _a === void 0 ? void 0 : _a.has(option.code)) === true;
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
function getAdditionalProducts(product) {
|
|
542
|
-
const initial = product.initial;
|
|
543
|
-
return product.target.additionalProducts
|
|
544
|
-
.filter((p) => p.selected)
|
|
545
|
-
.map((childTarget) => {
|
|
546
|
-
const refKey = childTarget.refKey;
|
|
547
|
-
const childInitial = initial === null || initial === void 0 ? void 0 : initial.additionalProducts.find((p) => refKey === p.refKey);
|
|
548
|
-
return {
|
|
549
|
-
target: childTarget._internal,
|
|
550
|
-
initial: childInitial === null || childInitial === void 0 ? void 0 : childInitial._internal,
|
|
551
|
-
};
|
|
552
|
-
});
|
|
553
|
-
}
|
|
554
|
-
function pairOptions(targets, initials) {
|
|
555
|
-
return targets.map((childTarget) => {
|
|
556
|
-
const key = childTarget.key;
|
|
557
|
-
const childInitial = initials === null || initials === void 0 ? void 0 : initials.find((o) => key === o.key);
|
|
558
|
-
return {
|
|
559
|
-
target: childTarget._internal,
|
|
560
|
-
initial: childInitial === null || childInitial === void 0 ? void 0 : childInitial._internal,
|
|
561
|
-
};
|
|
562
|
-
});
|
|
563
|
-
}
|
|
564
|
-
function getSelectedOptions(feature) {
|
|
565
|
-
var _a;
|
|
566
|
-
return pairOptions(feature.target.selectedOptions, (_a = feature.initial) === null || _a === void 0 ? void 0 : _a.selectedOptions);
|
|
567
|
-
}
|
|
568
|
-
function getOptions(feature) {
|
|
569
|
-
var _a;
|
|
570
|
-
return pairOptions(feature.target.options, (_a = feature.initial) === null || _a === void 0 ? void 0 : _a.options);
|
|
571
|
-
}
|
|
572
|
-
function pairFeatures(targets, initials) {
|
|
573
|
-
return targets.map((childTarget) => {
|
|
574
|
-
const code = childTarget.code;
|
|
575
|
-
const childInitial = initials === null || initials === void 0 ? void 0 : initials.find((f) => code === f.code);
|
|
576
|
-
return {
|
|
577
|
-
target: childTarget._internal,
|
|
578
|
-
initial: childInitial === null || childInitial === void 0 ? void 0 : childInitial._internal,
|
|
579
|
-
};
|
|
580
|
-
});
|
|
581
|
-
}
|
|
582
|
-
function getFeaturesFromProduct(product) {
|
|
583
|
-
var _a;
|
|
584
|
-
return pairFeatures(product.target.configuration.features, (_a = product.initial) === null || _a === void 0 ? void 0 : _a.configuration.features);
|
|
585
|
-
}
|
|
586
|
-
function getFeaturesFromOption(option) {
|
|
587
|
-
var _a;
|
|
588
|
-
return pairFeatures(option.target.features, (_a = option.initial) === null || _a === void 0 ? void 0 : _a.features);
|
|
589
|
-
}
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { assert } from "@configura/web-utilities";
|
|
11
|
+
import { CfgProductBubbleMode } from "../CfgProduct.js";
|
|
12
|
+
import { SelectionType, } from "../productConfiguration/CfgFeature.js";
|
|
13
|
+
import { ProductConfigurationBubbleMode, } from "../productConfiguration/CfgOption.js";
|
|
14
|
+
import { SyncGroupsApplyMode } from "./SyncGroupsApplyMode.js";
|
|
15
|
+
import { SyncGroupsPathHelper } from "./SyncGroupsPathHelper.js";
|
|
16
|
+
var AffectedLevel;
|
|
17
|
+
(function (AffectedLevel) {
|
|
18
|
+
AffectedLevel[AffectedLevel["None"] = 0] = "None";
|
|
19
|
+
AffectedLevel[AffectedLevel["Initialized"] = 1] = "Initialized";
|
|
20
|
+
AffectedLevel[AffectedLevel["Set"] = 2] = "Set";
|
|
21
|
+
})(AffectedLevel || (AffectedLevel = {}));
|
|
22
|
+
/**
|
|
23
|
+
* The Transaction is a transient object used to track all the changes made to the supplied
|
|
24
|
+
* SyncGroupState as a result of a (normally) single initial event, like when opening a product
|
|
25
|
+
* or the user selecting an option on an already open product.
|
|
26
|
+
*
|
|
27
|
+
* All state changes are made on an internal copy of the original SyncGroupState (called target)
|
|
28
|
+
* so they can later be used (i.e. committed) at once to the visible product, or safely discarded
|
|
29
|
+
* without affecting anything.
|
|
30
|
+
*
|
|
31
|
+
* The Transaction keeps track of which Features and SyncGroups have been affected so far, to
|
|
32
|
+
* eliminate the risk of infinite loops.
|
|
33
|
+
*
|
|
34
|
+
* Terminology
|
|
35
|
+
* ===========
|
|
36
|
+
*
|
|
37
|
+
* You APPLY things onto the transaction in order to update the sync state inside the transaction.
|
|
38
|
+
* The transaction can then UPDATE other things in order to apply it's sync state onto them.
|
|
39
|
+
*
|
|
40
|
+
* Transaction.applyThing() ...onto the transaction
|
|
41
|
+
* Transaction.updateThing() ...with the transaction
|
|
42
|
+
*
|
|
43
|
+
* @see SyncGroupHandler.ts for more information, including the general resolution algorithm.
|
|
44
|
+
*/
|
|
45
|
+
export class SyncGroupsTransaction {
|
|
46
|
+
constructor(syncState, updateMode, productLoader, original, target, initial) {
|
|
47
|
+
this._closed = false;
|
|
48
|
+
this.affectedSelectOneFeatures = new Map();
|
|
49
|
+
this.affectedSelectManyOptions = new Set();
|
|
50
|
+
this.affectedSelectOneSyncGroups = new Set();
|
|
51
|
+
this.affectedSelectManySyncGroupsAndOptions = new Map();
|
|
52
|
+
this.syncState = syncState.clone();
|
|
53
|
+
this.originalSyncState = syncState;
|
|
54
|
+
this.updateMode = updateMode;
|
|
55
|
+
this.productLoader = productLoader;
|
|
56
|
+
this.originalProduct = original;
|
|
57
|
+
this.target = target;
|
|
58
|
+
this.initial = initial;
|
|
59
|
+
}
|
|
60
|
+
static make(syncState, updateMode, product, productLoader, assumeNoStartState) {
|
|
61
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
62
|
+
const t = new this(syncState, updateMode, productLoader, product, yield product.clone(), assumeNoStartState ? undefined : yield product.clone());
|
|
63
|
+
return t;
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
/************************************************************************
|
|
67
|
+
* Public API (intentionally limited)
|
|
68
|
+
************************************************************************/
|
|
69
|
+
get isClosed() {
|
|
70
|
+
return this._closed;
|
|
71
|
+
}
|
|
72
|
+
close() {
|
|
73
|
+
this._closed = true;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* This is (among other) the entry point when loading a new product.
|
|
77
|
+
*
|
|
78
|
+
* @returns true if at least one Feature changed it state. This is a bit counter intuitive,
|
|
79
|
+
* but as this method will hand over to applyProduct if it updates the SyncState it can cause
|
|
80
|
+
* the Features to change. And we need to pass that information back to know when to stop.
|
|
81
|
+
*/
|
|
82
|
+
applyRootProduct() {
|
|
83
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
84
|
+
const productWithInitial = { target: this.target, initial: this.initial };
|
|
85
|
+
if (!(yield this.applyProduct(productWithInitial))) {
|
|
86
|
+
// All done!
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
return yield this.updateRootProduct(undefined);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* This is the entry point for an active (often user) selection of an option.
|
|
94
|
+
*/
|
|
95
|
+
selectOption(optionPath, on) {
|
|
96
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
97
|
+
const option = SyncGroupsPathHelper.getOptionFromPath(optionPath, this.target);
|
|
98
|
+
const feature = option.parent;
|
|
99
|
+
let change = false;
|
|
100
|
+
let productToValidate = undefined;
|
|
101
|
+
// At this point in handling sync groups we go back to normal selecting.
|
|
102
|
+
//
|
|
103
|
+
// This is because a normal selection can bring features into scope which should push to or
|
|
104
|
+
// (if the SyncGroup is defined) pull from the Sync State.
|
|
105
|
+
//
|
|
106
|
+
// We use BubbleSelected (without validation) as we can delay the validation until after we
|
|
107
|
+
// have applied the SyncGroup (if any).
|
|
108
|
+
if (yield feature.selectOption(option, on, ProductConfigurationBubbleMode.BubbleSelected)) {
|
|
109
|
+
productToValidate = feature.parentProduct;
|
|
110
|
+
change = true;
|
|
111
|
+
switch (feature.selectionType) {
|
|
112
|
+
case SelectionType.SelectOne:
|
|
113
|
+
this.affectedSelectOneFeatures.set(feature, AffectedLevel.Set);
|
|
114
|
+
break;
|
|
115
|
+
case SelectionType.SelectMany:
|
|
116
|
+
this.affectedSelectManyOptions.add(option);
|
|
117
|
+
break;
|
|
118
|
+
default:
|
|
119
|
+
throw new Error("Should not happen");
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
switch (feature.selectionType) {
|
|
123
|
+
case SelectionType.SelectOne:
|
|
124
|
+
this.applySelectOneFeature(feature, true, false);
|
|
125
|
+
break;
|
|
126
|
+
case SelectionType.SelectMany:
|
|
127
|
+
this.applySelectManyOption(option, true);
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
if (yield this.updateRootProduct(productToValidate)) {
|
|
131
|
+
change = true;
|
|
132
|
+
}
|
|
133
|
+
return change;
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Overwrites the original Product and SyncGroupState (supplied when creating the Transaction)
|
|
138
|
+
* with the internal versions inside this Transaction.
|
|
139
|
+
*
|
|
140
|
+
* @throws error if the transaction has already been closed.
|
|
141
|
+
*/
|
|
142
|
+
commit() {
|
|
143
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
144
|
+
assert(this.isClosed === false, "Trying to commit a closed Transaction");
|
|
145
|
+
this.originalSyncState.copyFrom(this.syncState);
|
|
146
|
+
yield this.originalProduct.copyFrom(this.target, false, this.productLoader);
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
/************************************************************************
|
|
150
|
+
* Updating things with the Transaction's SyncState
|
|
151
|
+
************************************************************************/
|
|
152
|
+
/**
|
|
153
|
+
* Apply current sync groups on those who wants to listen until there is no more to settle.
|
|
154
|
+
* @returns true if at least one Feature changed selected Option
|
|
155
|
+
*/
|
|
156
|
+
updateRootProduct(productToValidate) {
|
|
157
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
158
|
+
const productsToValidate = new Set();
|
|
159
|
+
if (productToValidate !== undefined) {
|
|
160
|
+
productsToValidate.add(productToValidate);
|
|
161
|
+
}
|
|
162
|
+
const productWithInitial = { target: this.target, initial: this.initial };
|
|
163
|
+
yield this.updateProduct(productWithInitial, productsToValidate);
|
|
164
|
+
if (productsToValidate.size === 0) {
|
|
165
|
+
// All have been settled, continue to pullPhase
|
|
166
|
+
return yield this.applyRootProduct();
|
|
167
|
+
}
|
|
168
|
+
if (this.isClosed) {
|
|
169
|
+
// We could exit in more places when the transaction has been aborted, but as
|
|
170
|
+
// revalidate is really the only thing that could be expensive time consuming we only
|
|
171
|
+
// check here.
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
const promises = [];
|
|
175
|
+
for (const product of productsToValidate) {
|
|
176
|
+
promises.push(product._revalidate(CfgProductBubbleMode.ToRoot, this.productLoader, true));
|
|
177
|
+
}
|
|
178
|
+
const revalidationResults = yield Promise.all(promises);
|
|
179
|
+
if (revalidationResults.every((r) => !r)) {
|
|
180
|
+
this.close();
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
// Apply over again, to settle deeper down. Our theory is that the front of "settled" will
|
|
184
|
+
// move deeper and deeper into the tree.
|
|
185
|
+
yield this.updateRootProduct(undefined);
|
|
186
|
+
// We had productsToValidate, and so there must have been a change.
|
|
187
|
+
return true;
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Applies the SyncState to the Product and it's AdditionalProducts (sub-products).
|
|
192
|
+
* @param productsToValidate To this all products that will need validation are added.
|
|
193
|
+
*/
|
|
194
|
+
updateProduct(product, productsToValidate) {
|
|
195
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
196
|
+
const promises = [];
|
|
197
|
+
promises.push(this.updateFeatures(getFeaturesFromProduct(product), productsToValidate));
|
|
198
|
+
for (const additionalProduct of getAdditionalProducts(product)) {
|
|
199
|
+
promises.push(this.updateProduct(additionalProduct, productsToValidate));
|
|
200
|
+
}
|
|
201
|
+
yield Promise.all(promises);
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Applies the SyncState to an array of Features.
|
|
206
|
+
* @param productsToValidate To this all products that will need validation are added.
|
|
207
|
+
*/
|
|
208
|
+
updateFeatures(features, productsToValidate) {
|
|
209
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
210
|
+
yield Promise.all(features.map((feature) => __awaiter(this, void 0, void 0, function* () {
|
|
211
|
+
switch (yield this.updateFeature(feature, productsToValidate)) {
|
|
212
|
+
case "stop":
|
|
213
|
+
return;
|
|
214
|
+
case "recurseDown":
|
|
215
|
+
yield this.updateOptions(getSelectedOptions(feature), productsToValidate);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
})));
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Applies the SyncState to an array of Options.
|
|
223
|
+
* @param productsToValidate To this all products that will need validation are added.
|
|
224
|
+
*/
|
|
225
|
+
updateOptions(options, productsToValidate) {
|
|
226
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
227
|
+
yield Promise.all(options.map((option) => {
|
|
228
|
+
return this.updateFeatures(getFeaturesFromOption(option), productsToValidate);
|
|
229
|
+
}));
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Applies the SyncState to a Feature
|
|
234
|
+
* @param productsToValidate To this all products that will need validation are added
|
|
235
|
+
* @returns Whether we shall stop recursing down (because we are in a state which we
|
|
236
|
+
* expect to be resolved later), we shall continue recursing down.
|
|
237
|
+
*/
|
|
238
|
+
updateFeature(featureWithInitial, productsToValidate) {
|
|
239
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
240
|
+
const feature = featureWithInitial.target;
|
|
241
|
+
const syncCode = feature.getSyncCode("pull");
|
|
242
|
+
if (syncCode === undefined) {
|
|
243
|
+
// Just continue down, features with no sync groups are always settled
|
|
244
|
+
return "recurseDown";
|
|
245
|
+
}
|
|
246
|
+
if (syncCode === false) {
|
|
247
|
+
// Here we only handle pull. Initializing those missing in SyncMap
|
|
248
|
+
// we do later. We wait until we have settled as much as we can with what
|
|
249
|
+
// we have now, to increase chances of the values being written to the
|
|
250
|
+
// SyncState being the right ones.
|
|
251
|
+
return "stop";
|
|
252
|
+
}
|
|
253
|
+
switch (feature.selectionType) {
|
|
254
|
+
case SelectionType.Group:
|
|
255
|
+
return "recurseDown";
|
|
256
|
+
case SelectionType.SelectOne:
|
|
257
|
+
return yield this.updateSelectOneFeature(syncCode, featureWithInitial, productsToValidate);
|
|
258
|
+
case SelectionType.SelectMany:
|
|
259
|
+
return yield this.updateSelectManyFeature(syncCode, featureWithInitial, productsToValidate);
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Decides if the SyncState can be applied to the SelectOne Feature, and then changes
|
|
265
|
+
* the state of the Feature if so.
|
|
266
|
+
* @param syncCode What SyncGroup the Feature belongs to
|
|
267
|
+
* @param productsToValidate To this all products that will need validation are added
|
|
268
|
+
* @returns Whether we shall stop recursing down (because we are in a state which we
|
|
269
|
+
* expect to be resolved later), we shall continue recursing down.
|
|
270
|
+
*/
|
|
271
|
+
updateSelectOneFeature(syncCode, featureWithInitial, productsToValidate) {
|
|
272
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
273
|
+
const feature = featureWithInitial.target;
|
|
274
|
+
const featureDidJustComeIntoScope = featureWithInitial.initial === undefined;
|
|
275
|
+
const hasBeenAffectedLevel = this.affectedSelectOneFeatures.get(feature);
|
|
276
|
+
const syncGroupHasBeenUpdated = this.affectedSelectOneSyncGroups.has(syncCode);
|
|
277
|
+
if (hasBeenAffectedLevel === AffectedLevel.Set ||
|
|
278
|
+
(!syncGroupHasBeenUpdated && hasBeenAffectedLevel === AffectedLevel.Initialized)) {
|
|
279
|
+
// A feature can change value for two reasons:
|
|
280
|
+
// 1. The feature did just come into scope and loads from the sync group to set its "defaults"
|
|
281
|
+
// 2. The sync group has updated with a new value
|
|
282
|
+
// The rule is that in one sync group transaction the feature value is allowed to be updated
|
|
283
|
+
// once or twice. Once for initialization and once for new sync group value. But they have to
|
|
284
|
+
// happen in the right order.
|
|
285
|
+
return "recurseDown";
|
|
286
|
+
}
|
|
287
|
+
if (!featureDidJustComeIntoScope && !syncGroupHasBeenUpdated) {
|
|
288
|
+
return "recurseDown";
|
|
289
|
+
}
|
|
290
|
+
const currentSyncGroupValue = this.syncState.getForSelectOne(syncCode);
|
|
291
|
+
if (currentSyncGroupValue === undefined) {
|
|
292
|
+
// This branch will have to be settled later. Don't go further down here.
|
|
293
|
+
return "stop";
|
|
294
|
+
}
|
|
295
|
+
const selectedOption = feature.selectedOptions[0];
|
|
296
|
+
if (selectedOption !== undefined && selectedOption.code === currentSyncGroupValue) {
|
|
297
|
+
// Settled, continue
|
|
298
|
+
return "recurseDown";
|
|
299
|
+
}
|
|
300
|
+
const optionToSelect = feature.options.find((o) => o.code === currentSyncGroupValue);
|
|
301
|
+
if (optionToSelect === undefined) {
|
|
302
|
+
// No option which can be selected (keep current value and recurse down)
|
|
303
|
+
return "recurseDown";
|
|
304
|
+
}
|
|
305
|
+
// Update the value. Validations are collected so that we do not send more than necessary.
|
|
306
|
+
// Do not recurse further as we will change the state and so what is selected now won't be
|
|
307
|
+
// selected then.
|
|
308
|
+
yield feature.selectOption(optionToSelect._internal, true, ProductConfigurationBubbleMode.ToRoot);
|
|
309
|
+
this.affectedSelectOneFeatures.set(feature, syncGroupHasBeenUpdated ? AffectedLevel.Set : AffectedLevel.Initialized);
|
|
310
|
+
productsToValidate.add(feature.parentProduct);
|
|
311
|
+
return "stop";
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Decides if the SyncState can be applied to Options in the SelectMany Feature, and
|
|
316
|
+
* then changes the state of the Options if so.
|
|
317
|
+
* @param syncCode What SyncGroup the Feature belongs to
|
|
318
|
+
* @param productsToValidate To this all products that will need validation are added
|
|
319
|
+
* @returns Always "stop" as recursion is handled internally. Return for consistency.
|
|
320
|
+
*/
|
|
321
|
+
updateSelectManyFeature(syncCode, feature, productsToValidate) {
|
|
322
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
323
|
+
const optionsToContinueDown = [];
|
|
324
|
+
for (const option of getOptions(feature)) {
|
|
325
|
+
switch (yield this.updateSelectManyOption(syncCode, option, productsToValidate)) {
|
|
326
|
+
case "stop":
|
|
327
|
+
continue;
|
|
328
|
+
case "recurseDown":
|
|
329
|
+
optionsToContinueDown.push(option);
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
yield this.updateOptions(optionsToContinueDown, productsToValidate);
|
|
334
|
+
// stop as the method above handles the recursion down
|
|
335
|
+
return "stop";
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Decides if the SyncState can be applied to the SelectMany Option, and then changes
|
|
340
|
+
* the state of the Option if so.
|
|
341
|
+
* @param syncCode What SyncGroup the Feature belongs to
|
|
342
|
+
* @param productsToValidate To this all products that will need validation are added
|
|
343
|
+
* @returns Whether we shall stop recursing down (because we are in a state which we
|
|
344
|
+
* expect to be resolved later), we shall continue recursing down.
|
|
345
|
+
*/
|
|
346
|
+
updateSelectManyOption(syncCode, optionWithInitial, productsToValidate) {
|
|
347
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
348
|
+
const option = optionWithInitial.target;
|
|
349
|
+
const featureDidJustComeIntoScope = optionWithInitial.initial === undefined;
|
|
350
|
+
const optionSelected = option.selected;
|
|
351
|
+
const recurseOrStopIfNoChange = optionSelected ? "recurseDown" : "stop";
|
|
352
|
+
if (this.affectedSelectManyOptions.has(option)) {
|
|
353
|
+
// This option has already changed selection once for this transaction. We expect
|
|
354
|
+
// this to happen very rarely, as the algorithm should settle the selection tree
|
|
355
|
+
// further and further out. Nevertheless, this safeguard is needed to avoid infinite
|
|
356
|
+
// looping if for example the server would return the same data over and over.
|
|
357
|
+
return recurseOrStopIfNoChange;
|
|
358
|
+
}
|
|
359
|
+
if (!featureDidJustComeIntoScope &&
|
|
360
|
+
!this.hasSyncGroupAffectedForSelectMany(syncCode, option)) {
|
|
361
|
+
return "recurseDown";
|
|
362
|
+
}
|
|
363
|
+
const syncGroupValueForOption = this.syncState.getForSelectMany(syncCode, option.code);
|
|
364
|
+
if (syncGroupValueForOption === undefined) {
|
|
365
|
+
// The sync group has no opinion on this. If it is selected, just continue down.
|
|
366
|
+
return recurseOrStopIfNoChange;
|
|
367
|
+
}
|
|
368
|
+
if (syncGroupValueForOption === optionSelected) {
|
|
369
|
+
// We are in sync for this option
|
|
370
|
+
return recurseOrStopIfNoChange;
|
|
371
|
+
}
|
|
372
|
+
const feature = option.parent;
|
|
373
|
+
// Update the value. Validations are collected so that we do not
|
|
374
|
+
// send more than necessary. Do not recurse further as we will change
|
|
375
|
+
// the state and so what is selected now won't be selected then.
|
|
376
|
+
yield feature.selectOption(option, syncGroupValueForOption, ProductConfigurationBubbleMode.ToRoot);
|
|
377
|
+
this.affectedSelectManyOptions.add(option);
|
|
378
|
+
productsToValidate.add(feature.parentProduct);
|
|
379
|
+
return "stop";
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
/************************************************************************
|
|
383
|
+
* Applying things to the Transaction's SyncState
|
|
384
|
+
************************************************************************/
|
|
385
|
+
applyProduct(product) {
|
|
386
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
387
|
+
let change = false;
|
|
388
|
+
if (yield this.applyFeatures(getFeaturesFromProduct(product))) {
|
|
389
|
+
if (this.updateMode === SyncGroupsApplyMode.Strict) {
|
|
390
|
+
return true;
|
|
391
|
+
}
|
|
392
|
+
change = true;
|
|
393
|
+
}
|
|
394
|
+
for (const additionalProduct of getAdditionalProducts(product)) {
|
|
395
|
+
if (yield this.applyProduct(additionalProduct)) {
|
|
396
|
+
if (this.updateMode === SyncGroupsApplyMode.Strict) {
|
|
397
|
+
return true;
|
|
398
|
+
}
|
|
399
|
+
change = true;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
return change;
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
applyFeatures(featureWithInitials) {
|
|
406
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
407
|
+
let change = false;
|
|
408
|
+
for (const featureWithInitial of featureWithInitials) {
|
|
409
|
+
const feature = featureWithInitial.target;
|
|
410
|
+
switch (feature.selectionType) {
|
|
411
|
+
case SelectionType.SelectOne:
|
|
412
|
+
if (this.applySelectOneFeature(feature, false, featureWithInitial.initial === undefined)) {
|
|
413
|
+
change = true;
|
|
414
|
+
}
|
|
415
|
+
break;
|
|
416
|
+
case SelectionType.SelectMany:
|
|
417
|
+
if (this.applySelectManyFeature(featureWithInitial)) {
|
|
418
|
+
change = true;
|
|
419
|
+
}
|
|
420
|
+
break;
|
|
421
|
+
}
|
|
422
|
+
if (change && this.updateMode === SyncGroupsApplyMode.Strict) {
|
|
423
|
+
return true;
|
|
424
|
+
}
|
|
425
|
+
if (yield this.applyOptions(getSelectedOptions(featureWithInitial))) {
|
|
426
|
+
if (this.updateMode === SyncGroupsApplyMode.Strict) {
|
|
427
|
+
return true;
|
|
428
|
+
}
|
|
429
|
+
change = true;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
return change;
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
applyOptions(options) {
|
|
436
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
437
|
+
let change = false;
|
|
438
|
+
for (const option of options) {
|
|
439
|
+
if (yield this.applyFeatures(getFeaturesFromOption(option))) {
|
|
440
|
+
if (this.updateMode === SyncGroupsApplyMode.Strict) {
|
|
441
|
+
return true;
|
|
442
|
+
}
|
|
443
|
+
change = true;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
return change;
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
applySelectOneFeature(feature, userInitiated, featureDidJustComeIntoScope) {
|
|
450
|
+
const selectionType = feature.selectionType;
|
|
451
|
+
if (selectionType !== SelectionType.SelectOne) {
|
|
452
|
+
throw new Error("can only be used for selectOne");
|
|
453
|
+
}
|
|
454
|
+
const syncCode = feature.getSyncCode("push");
|
|
455
|
+
if (syncCode === undefined || syncCode === false) {
|
|
456
|
+
return false;
|
|
457
|
+
}
|
|
458
|
+
if (this.affectedSelectOneSyncGroups.has(syncCode)) {
|
|
459
|
+
return false;
|
|
460
|
+
}
|
|
461
|
+
const currentSyncGroupOptionCode = this.syncState.getForSelectOne(syncCode);
|
|
462
|
+
const option = feature.selectedOptions[0];
|
|
463
|
+
if (option === undefined) {
|
|
464
|
+
// Options with no default are never written
|
|
465
|
+
return false;
|
|
466
|
+
}
|
|
467
|
+
if (userInitiated) {
|
|
468
|
+
// To make re-apply happen, even if it actually does not update the sync group
|
|
469
|
+
this.affectedSelectOneSyncGroups.add(syncCode);
|
|
470
|
+
}
|
|
471
|
+
if (option.code === currentSyncGroupOptionCode) {
|
|
472
|
+
return false;
|
|
473
|
+
}
|
|
474
|
+
// featureDidJustComeIntoScope, in CET there is a feature that if a feature appears which
|
|
475
|
+
// can not be set to the current sync group value, then it will set in the opposite
|
|
476
|
+
// direction. Like if the sync group was empty. To avoid bouncing back and forth we will
|
|
477
|
+
// need to enforce that a sync group can only be updated once per transaction
|
|
478
|
+
if (!(userInitiated ||
|
|
479
|
+
currentSyncGroupOptionCode === undefined ||
|
|
480
|
+
(featureDidJustComeIntoScope &&
|
|
481
|
+
feature.options.every((o) => currentSyncGroupOptionCode !== o.code)))) {
|
|
482
|
+
return false;
|
|
483
|
+
}
|
|
484
|
+
this.affectedSelectOneSyncGroups.add(syncCode);
|
|
485
|
+
this.syncState.setForSelectOne(syncCode, option.code);
|
|
486
|
+
return true;
|
|
487
|
+
}
|
|
488
|
+
applySelectManyFeature(featureWithInitial) {
|
|
489
|
+
let change = false;
|
|
490
|
+
for (const optionWithInitial of getOptions(featureWithInitial)) {
|
|
491
|
+
if (this.applySelectManyOption(optionWithInitial.target, false)) {
|
|
492
|
+
if (this.updateMode === SyncGroupsApplyMode.Strict) {
|
|
493
|
+
return true;
|
|
494
|
+
}
|
|
495
|
+
change = true;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
return change;
|
|
499
|
+
}
|
|
500
|
+
applySelectManyOption(option, userInitiated) {
|
|
501
|
+
const feature = option.parent;
|
|
502
|
+
if (feature.selectionType !== SelectionType.SelectMany) {
|
|
503
|
+
throw new Error("can only be used for selectMany");
|
|
504
|
+
}
|
|
505
|
+
const syncCode = feature.getSyncCode("push");
|
|
506
|
+
if (syncCode === undefined || syncCode === false) {
|
|
507
|
+
return false;
|
|
508
|
+
}
|
|
509
|
+
if (this.hasSyncGroupAffectedForSelectMany(syncCode, option)) {
|
|
510
|
+
return false;
|
|
511
|
+
}
|
|
512
|
+
const optionSelected = option.selected;
|
|
513
|
+
const currentSyncGroupValue = this.syncState.getForSelectMany(syncCode, option.code);
|
|
514
|
+
if (userInitiated) {
|
|
515
|
+
// To make re-apply happen, even if it actually does not update the sync group
|
|
516
|
+
this.addSyncGroupAffectedForSelectMany(syncCode, option);
|
|
517
|
+
}
|
|
518
|
+
// We only initialize if the option has not been initiated or if it is userInitiated.
|
|
519
|
+
// userInitiated = active selection by the user.
|
|
520
|
+
if (currentSyncGroupValue !== undefined &&
|
|
521
|
+
!(userInitiated && currentSyncGroupValue !== optionSelected)) {
|
|
522
|
+
return false;
|
|
523
|
+
}
|
|
524
|
+
this.addSyncGroupAffectedForSelectMany(syncCode, option);
|
|
525
|
+
this.syncState.setForSelectMany(syncCode, option.code, optionSelected);
|
|
526
|
+
return true;
|
|
527
|
+
}
|
|
528
|
+
addSyncGroupAffectedForSelectMany(syncCode, option) {
|
|
529
|
+
let forSyncCode = this.affectedSelectManySyncGroupsAndOptions.get(syncCode);
|
|
530
|
+
if (forSyncCode === undefined) {
|
|
531
|
+
forSyncCode = new Set();
|
|
532
|
+
this.affectedSelectManySyncGroupsAndOptions.set(syncCode, forSyncCode);
|
|
533
|
+
}
|
|
534
|
+
forSyncCode.add(option.code);
|
|
535
|
+
}
|
|
536
|
+
hasSyncGroupAffectedForSelectMany(syncCode, option) {
|
|
537
|
+
var _a;
|
|
538
|
+
return ((_a = this.affectedSelectManySyncGroupsAndOptions.get(syncCode)) === null || _a === void 0 ? void 0 : _a.has(option.code)) === true;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
function getAdditionalProducts(product) {
|
|
542
|
+
const initial = product.initial;
|
|
543
|
+
return product.target.additionalProducts
|
|
544
|
+
.filter((p) => p.selected)
|
|
545
|
+
.map((childTarget) => {
|
|
546
|
+
const refKey = childTarget.refKey;
|
|
547
|
+
const childInitial = initial === null || initial === void 0 ? void 0 : initial.additionalProducts.find((p) => refKey === p.refKey);
|
|
548
|
+
return {
|
|
549
|
+
target: childTarget._internal,
|
|
550
|
+
initial: childInitial === null || childInitial === void 0 ? void 0 : childInitial._internal,
|
|
551
|
+
};
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
function pairOptions(targets, initials) {
|
|
555
|
+
return targets.map((childTarget) => {
|
|
556
|
+
const key = childTarget.key;
|
|
557
|
+
const childInitial = initials === null || initials === void 0 ? void 0 : initials.find((o) => key === o.key);
|
|
558
|
+
return {
|
|
559
|
+
target: childTarget._internal,
|
|
560
|
+
initial: childInitial === null || childInitial === void 0 ? void 0 : childInitial._internal,
|
|
561
|
+
};
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
function getSelectedOptions(feature) {
|
|
565
|
+
var _a;
|
|
566
|
+
return pairOptions(feature.target.selectedOptions, (_a = feature.initial) === null || _a === void 0 ? void 0 : _a.selectedOptions);
|
|
567
|
+
}
|
|
568
|
+
function getOptions(feature) {
|
|
569
|
+
var _a;
|
|
570
|
+
return pairOptions(feature.target.options, (_a = feature.initial) === null || _a === void 0 ? void 0 : _a.options);
|
|
571
|
+
}
|
|
572
|
+
function pairFeatures(targets, initials) {
|
|
573
|
+
return targets.map((childTarget) => {
|
|
574
|
+
const code = childTarget.code;
|
|
575
|
+
const childInitial = initials === null || initials === void 0 ? void 0 : initials.find((f) => code === f.code);
|
|
576
|
+
return {
|
|
577
|
+
target: childTarget._internal,
|
|
578
|
+
initial: childInitial === null || childInitial === void 0 ? void 0 : childInitial._internal,
|
|
579
|
+
};
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
function getFeaturesFromProduct(product) {
|
|
583
|
+
var _a;
|
|
584
|
+
return pairFeatures(product.target.configuration.features, (_a = product.initial) === null || _a === void 0 ? void 0 : _a.configuration.features);
|
|
585
|
+
}
|
|
586
|
+
function getFeaturesFromOption(option) {
|
|
587
|
+
var _a;
|
|
588
|
+
return pairFeatures(option.target.features, (_a = option.initial) === null || _a === void 0 ? void 0 : _a.features);
|
|
589
|
+
}
|