@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,691 +1,691 @@
|
|
|
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 { assertDefined, compareArrays, convertLength, count, Observable, someMatch, toLengthUnit, } from "@configura/web-utilities";
|
|
11
|
-
import { CfgProduct } from "../CfgProduct.js";
|
|
12
|
-
import { CfgMtrlApplication } from "../material/CfgMtrlApplication.js";
|
|
13
|
-
import { CfgMtrlApplicationSource } from "../material/CfgMtrlApplicationSource.js";
|
|
14
|
-
import { wrapWithCache } from "../productLoader.js";
|
|
15
|
-
import { CfgOption, ProductConfigurationBubbleMode } from "./CfgOption.js";
|
|
16
|
-
import { _CfgProductConfigurationInternal } from "./CfgProductConfiguration.js";
|
|
17
|
-
import { getMtrlPreview } from "./utilitiesProductConfiguration.js";
|
|
18
|
-
export var SelectionType;
|
|
19
|
-
(function (SelectionType) {
|
|
20
|
-
/**
|
|
21
|
-
* All options are permanently selected. In our ui-component for this we skip over this level,
|
|
22
|
-
* and immediately show the children, but you could do this differently, like for instance
|
|
23
|
-
* showing the group heading.
|
|
24
|
-
*/
|
|
25
|
-
SelectionType[SelectionType["Group"] = 0] = "Group";
|
|
26
|
-
/**
|
|
27
|
-
* One and only one can be selected at a time. This normally corresponds to either a dropdown
|
|
28
|
-
* menu or radio buttons.
|
|
29
|
-
*/
|
|
30
|
-
SelectionType[SelectionType["SelectOne"] = 1] = "SelectOne";
|
|
31
|
-
/** Zero to all can be selected at a time. This normally corresponds to checkboxes. */
|
|
32
|
-
SelectionType[SelectionType["SelectMany"] = 2] = "SelectMany";
|
|
33
|
-
})(SelectionType || (SelectionType = {}));
|
|
34
|
-
function getOptionFilter(option1) {
|
|
35
|
-
return (option2) => option2._internal === option1;
|
|
36
|
-
}
|
|
37
|
-
/** Returns a fresh reference for the option at the given index */
|
|
38
|
-
function doFreshRefOptionAtIndex(options, index, committed, beforeNotify) {
|
|
39
|
-
const freshRef = CfgOption._makeNewRefFrom(options[index]._internal);
|
|
40
|
-
options[index] = freshRef;
|
|
41
|
-
if (beforeNotify) {
|
|
42
|
-
beforeNotify(freshRef);
|
|
43
|
-
}
|
|
44
|
-
freshRef._internal.changeObservable.notifyAll({ freshRef, committed });
|
|
45
|
-
return freshRef;
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Returns a fresh reference for the given option.
|
|
49
|
-
* @throws Throws an error if the option is not found in options.
|
|
50
|
-
*/
|
|
51
|
-
function doFreshRefOption(options, optionInternal, committed, beforeNotify) {
|
|
52
|
-
const i = options.findIndex(getOptionFilter(optionInternal));
|
|
53
|
-
if (i === -1) {
|
|
54
|
-
throw new Error("Did not find the option in options");
|
|
55
|
-
}
|
|
56
|
-
return doFreshRefOptionAtIndex(options, i, committed, beforeNotify);
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* This class is meant to only be used through CfgFeature. It should
|
|
60
|
-
* never be instantiated on its own. Normally the internal state of this class
|
|
61
|
-
* should never be directly modified. CfgFeature is the class that
|
|
62
|
-
* should be used and interacted with.
|
|
63
|
-
*/
|
|
64
|
-
export class _CfgFeatureInternal {
|
|
65
|
-
constructor(rawFeature, rawFeatures, _key, // Unique amongst siblings
|
|
66
|
-
parent, parentConfiguration, parentProduct, rootProduct) {
|
|
67
|
-
this.rawFeature = rawFeature;
|
|
68
|
-
this.rawFeatures = rawFeatures;
|
|
69
|
-
this._key = _key;
|
|
70
|
-
this.parent = parent;
|
|
71
|
-
this.parentConfiguration = parentConfiguration;
|
|
72
|
-
this.parentProduct = parentProduct;
|
|
73
|
-
this.rootProduct = rootProduct;
|
|
74
|
-
this._selectedOptions = [];
|
|
75
|
-
this._constrainedOptions = [];
|
|
76
|
-
this.changeObservable = new Observable();
|
|
77
|
-
this.setNumericValue = (val) => __awaiter(this, void 0, void 0, function* () {
|
|
78
|
-
if (this.selectionType !== SelectionType.SelectOne) {
|
|
79
|
-
throw new Error("Only SelectOne Features can have numeric values");
|
|
80
|
-
}
|
|
81
|
-
const option = this.options.find((option) => option.isAllowedNumericValue(val));
|
|
82
|
-
if (option === undefined) {
|
|
83
|
-
throw new Error("No Option allows the value you tried to set");
|
|
84
|
-
}
|
|
85
|
-
return yield option.setNumericValue(val, true);
|
|
86
|
-
});
|
|
87
|
-
this._notifyAllOfChange = (bubbleMode, committed) => __awaiter(this, void 0, void 0, function* () {
|
|
88
|
-
if (bubbleMode === ProductConfigurationBubbleMode.Stop) {
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
const freshRef = CfgFeature._makeNewRefFrom(this);
|
|
92
|
-
this.changeObservable.notifyAll({ freshRef, committed });
|
|
93
|
-
const parent = this.parent;
|
|
94
|
-
if (parent !== undefined) {
|
|
95
|
-
yield parent._childHasChanged(freshRef, bubbleMode === ProductConfigurationBubbleMode.OneLevel
|
|
96
|
-
? ProductConfigurationBubbleMode.Stop
|
|
97
|
-
: bubbleMode, committed);
|
|
98
|
-
}
|
|
99
|
-
});
|
|
100
|
-
/**
|
|
101
|
-
* Called by child to tell its parent that it has changed.
|
|
102
|
-
* @throws Will throw if options have not yet been generated. This should be impossible
|
|
103
|
-
* as nonexisting children can not call their parent.
|
|
104
|
-
*/
|
|
105
|
-
this._childHasChanged = (childOption, bubbleMode, committed) => __awaiter(this, void 0, void 0, function* () {
|
|
106
|
-
const options = this._options;
|
|
107
|
-
if (options === undefined) {
|
|
108
|
-
throw Error("Child says it changed, but no children has actually been generated");
|
|
109
|
-
}
|
|
110
|
-
const freshRef = doFreshRefOption(options, childOption, committed);
|
|
111
|
-
const selectedOptions = this._selectedOptions;
|
|
112
|
-
const i = selectedOptions.findIndex(getOptionFilter(childOption));
|
|
113
|
-
if (i !== -1) {
|
|
114
|
-
selectedOptions[i] = freshRef;
|
|
115
|
-
}
|
|
116
|
-
yield this._notifyAllOfChange(bubbleMode, committed);
|
|
117
|
-
});
|
|
118
|
-
// Keep in sync with stripExtendedDataFromDtoFeatureConf
|
|
119
|
-
this.getDtoConf = (includeExtendedData) => {
|
|
120
|
-
const result = {
|
|
121
|
-
code: this.code,
|
|
122
|
-
};
|
|
123
|
-
if (includeExtendedData) {
|
|
124
|
-
result.groupCode = this.groupCode;
|
|
125
|
-
result.unit = this.unit;
|
|
126
|
-
}
|
|
127
|
-
const selectedOptions = this._selectedOptions;
|
|
128
|
-
if (0 < selectedOptions.length) {
|
|
129
|
-
result.options = selectedOptions.map((o) => o._internal.getDtoConf(includeExtendedData));
|
|
130
|
-
}
|
|
131
|
-
return result;
|
|
132
|
-
};
|
|
133
|
-
// DtoFeatureConf is the newer more easily readable format for configuration. As
|
|
134
|
-
// the API:s are still using the older format (and will for the forseeable future) we need
|
|
135
|
-
// to support both formats. The new format can be converted to the old, but not the other
|
|
136
|
-
// way around. For that reason the get-method above uses the new format, and the set-method
|
|
137
|
-
// below the old format. As these functions are meant to only be used internally this should't
|
|
138
|
-
// cause too much confusion.
|
|
139
|
-
this.setApiSelection = (apiOptionSelectionMap, apiOptionConstraintMap) => __awaiter(this, void 0, void 0, function* () {
|
|
140
|
-
var _a, _b;
|
|
141
|
-
const selectionType = this.selectionType;
|
|
142
|
-
const isGroup = selectionType === SelectionType.Group;
|
|
143
|
-
const isSelectOne = selectionType === SelectionType.SelectOne;
|
|
144
|
-
const isAllOptionsAffectedByAnySelection = this.isAllOptionsAffectedByAnySelection;
|
|
145
|
-
const committed = true;
|
|
146
|
-
let options;
|
|
147
|
-
if (apiOptionSelectionMap === undefined) {
|
|
148
|
-
options = this._options || []; // Already generated children (all or none)
|
|
149
|
-
apiOptionSelectionMap = {};
|
|
150
|
-
}
|
|
151
|
-
else {
|
|
152
|
-
options = this.options; // This will generate all children
|
|
153
|
-
}
|
|
154
|
-
let change = false;
|
|
155
|
-
const constraintMap = apiOptionConstraintMap !== null && apiOptionConstraintMap !== void 0 ? apiOptionConstraintMap : {};
|
|
156
|
-
// The first value that belongs to this features options is used as the key to look up constrained options
|
|
157
|
-
const featureConstrainedOptions = (_b = (_a = options.map((o) => constraintMap[o.code]).find((o) => !!o)) === null || _a === void 0 ? void 0 : _a.constrOptions) !== null && _b !== void 0 ? _b : [];
|
|
158
|
-
for (let i = 0; i < options.length; i++) {
|
|
159
|
-
const option = options[i];
|
|
160
|
-
const apiOptionSelection = apiOptionSelectionMap[option.code];
|
|
161
|
-
const isConstrained = featureConstrainedOptions.indexOf(option.code) >= 0;
|
|
162
|
-
const selectedIndex = this._selectedOptions.findIndex(getOptionFilter(option._internal));
|
|
163
|
-
const constrainedIndex = this._constrainedOptions.findIndex(getOptionFilter(option._internal));
|
|
164
|
-
const isOn = isGroup || apiOptionSelection;
|
|
165
|
-
if (isConstrained) {
|
|
166
|
-
if (constrainedIndex === -1) {
|
|
167
|
-
doFreshRefOptionAtIndex(options, i, committed, (freshRef) => this._constrainedOptions.push(freshRef));
|
|
168
|
-
change = true;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
else {
|
|
172
|
-
if (constrainedIndex !== -1) {
|
|
173
|
-
this._constrainedOptions.splice(constrainedIndex, 1);
|
|
174
|
-
doFreshRefOptionAtIndex(options, i, committed);
|
|
175
|
-
change = true;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
if (isOn) {
|
|
179
|
-
if (isSelectOne) {
|
|
180
|
-
if (this.isUseNumericValue) {
|
|
181
|
-
const { numericValue } = apiOptionSelection;
|
|
182
|
-
if (numericValue === undefined) {
|
|
183
|
-
throw new Error("No numeric value for numeric feature");
|
|
184
|
-
}
|
|
185
|
-
const { value, unit } = numericValue;
|
|
186
|
-
if (yield option.setNumericValue(unit === undefined
|
|
187
|
-
? value
|
|
188
|
-
: convertLength(value, toLengthUnit(unit), this.unit), false)) {
|
|
189
|
-
change = true;
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
if (selectedIndex === -1) {
|
|
194
|
-
if (isAllOptionsAffectedByAnySelection) {
|
|
195
|
-
this._selectedOptions.push(option);
|
|
196
|
-
}
|
|
197
|
-
else {
|
|
198
|
-
doFreshRefOptionAtIndex(options, i, committed, (freshRef) => this._selectedOptions.push(freshRef));
|
|
199
|
-
}
|
|
200
|
-
change = true;
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
else {
|
|
204
|
-
if (selectedIndex !== -1) {
|
|
205
|
-
this._selectedOptions.splice(selectedIndex, 1);
|
|
206
|
-
if (!isAllOptionsAffectedByAnySelection) {
|
|
207
|
-
doFreshRefOptionAtIndex(options, i, committed);
|
|
208
|
-
}
|
|
209
|
-
change = true;
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
if (yield option._internal.setApiSelection(apiOptionSelection, constraintMap[option.code])) {
|
|
213
|
-
change = true;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
if (this.ancestorsSelected) {
|
|
217
|
-
const selectionCount = this._selectedOptions.length;
|
|
218
|
-
if (isSelectOne && selectionCount !== 1) {
|
|
219
|
-
const wrongCountWarning = `A select-one Feature (i.e. neither optional nor multiple) should have exactly one Option selected. Feature key: "${this.key}". Actual: ${selectionCount}`;
|
|
220
|
-
if (this.rootProduct.settings.strictSelectOneSelectionCount) {
|
|
221
|
-
throw new Error(wrongCountWarning);
|
|
222
|
-
}
|
|
223
|
-
else {
|
|
224
|
-
console.warn(wrongCountWarning);
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
if (change) {
|
|
229
|
-
if (isAllOptionsAffectedByAnySelection) {
|
|
230
|
-
this._freshRefAllOptions(committed);
|
|
231
|
-
}
|
|
232
|
-
if (selectionType === SelectionType.SelectOne) {
|
|
233
|
-
yield this.pushStretch();
|
|
234
|
-
}
|
|
235
|
-
// setApiSelection works its way top down and handles notifications on
|
|
236
|
-
// each level by looking at change returned from its children. That way
|
|
237
|
-
// we do not get multiple notifications for the same ancestral node even
|
|
238
|
-
// if multiple descendants are affected by the setApiSelection. Therefore
|
|
239
|
-
// we use OneLevel to make this feature notify and the parent update its
|
|
240
|
-
// reference.
|
|
241
|
-
yield this._notifyAllOfChange(ProductConfigurationBubbleMode.OneLevel, committed);
|
|
242
|
-
}
|
|
243
|
-
return change;
|
|
244
|
-
});
|
|
245
|
-
this.addForApiConstrained = (next) => {
|
|
246
|
-
const constrOptions = this._constrainedOptions;
|
|
247
|
-
for (const so of this._selectedOptions) {
|
|
248
|
-
next[so.code] = so._internal.getApiConstrained(constrOptions);
|
|
249
|
-
}
|
|
250
|
-
};
|
|
251
|
-
/** Pushes to refresh stretch. Does not cause validation. */
|
|
252
|
-
this.pushStretch = () => __awaiter(this, void 0, void 0, function* () {
|
|
253
|
-
if (this.selectionType !== SelectionType.SelectOne) {
|
|
254
|
-
throw new Error("Can only push stretch for SelectOne");
|
|
255
|
-
}
|
|
256
|
-
const value = this.numericValue;
|
|
257
|
-
return (yield Promise.all((this.measureParamCodes || []).map((measureParamCode) => this.parentConfiguration.setStretchReferenceLength(measureParamCode, value, this.unit)))).some((change) => change);
|
|
258
|
-
});
|
|
259
|
-
this.structureCompare = (other, strictOrder = true, descriptionMatch = false) => this.keyMatch(other, descriptionMatch) &&
|
|
260
|
-
compareArrays(this.options, other.options, (l, r) => l._internal.structureCompare(r._internal, strictOrder, descriptionMatch), strictOrder);
|
|
261
|
-
this.tryMatchSelection = (other, descriptionMatch = false) => __awaiter(this, void 0, void 0, function* () {
|
|
262
|
-
const { selectionType, isUseNumericValue } = this;
|
|
263
|
-
if (selectionType !== other.selectionType) {
|
|
264
|
-
// Will not try if the selection type is different
|
|
265
|
-
return false;
|
|
266
|
-
}
|
|
267
|
-
if (isUseNumericValue !== other.isUseNumericValue) {
|
|
268
|
-
return false;
|
|
269
|
-
}
|
|
270
|
-
let change = false;
|
|
271
|
-
if (isUseNumericValue) {
|
|
272
|
-
// For numeric options we do not try to go any further down. It's becoming to
|
|
273
|
-
// strange to make informed guesses on how to progress down the tree.
|
|
274
|
-
const otherNumericValue = other.numericValue;
|
|
275
|
-
if (otherNumericValue === undefined) {
|
|
276
|
-
throw new Error("Numeric feature without numeric value, this should never happen");
|
|
277
|
-
}
|
|
278
|
-
else {
|
|
279
|
-
if (yield this.setNumericValue(otherNumericValue)) {
|
|
280
|
-
change = true;
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
else {
|
|
285
|
-
const thisOptions = this.options;
|
|
286
|
-
const otherOptions = other.options;
|
|
287
|
-
change = (yield Promise.all(otherOptions.map((otherO) => (() => __awaiter(this, void 0, void 0, function* () {
|
|
288
|
-
const otherOSelected = otherO.selected;
|
|
289
|
-
if (selectionType === SelectionType.SelectOne && !otherOSelected) {
|
|
290
|
-
return false;
|
|
291
|
-
}
|
|
292
|
-
if (1 <
|
|
293
|
-
count(otherOptions, (item) => item._internal.keyMatch(otherO._internal, descriptionMatch))) {
|
|
294
|
-
console.warn("tryMatchSelection will ignore options that have the same key");
|
|
295
|
-
return false;
|
|
296
|
-
}
|
|
297
|
-
const toTryChangeOptions = thisOptions.filter((o) => otherO._internal.keyMatch(o._internal, descriptionMatch));
|
|
298
|
-
if (1 < toTryChangeOptions.length) {
|
|
299
|
-
console.warn("tryMatchSelection will ignore options that have the same key");
|
|
300
|
-
return false;
|
|
301
|
-
}
|
|
302
|
-
if (toTryChangeOptions.length === 0) {
|
|
303
|
-
return false;
|
|
304
|
-
}
|
|
305
|
-
const toTryChangeOption = toTryChangeOptions[0];
|
|
306
|
-
let change = false;
|
|
307
|
-
if (selectionType === SelectionType.SelectMany ||
|
|
308
|
-
selectionType === SelectionType.SelectOne) {
|
|
309
|
-
// The setSelected will only affect ourselves,
|
|
310
|
-
// so we do not need to bubble the notification.
|
|
311
|
-
// Instead we use the change variable to know
|
|
312
|
-
// when to notify for this feature.
|
|
313
|
-
change = yield toTryChangeOption._internal.parent.selectOption(toTryChangeOption._internal, otherOSelected, ProductConfigurationBubbleMode.Stop);
|
|
314
|
-
}
|
|
315
|
-
if (otherOSelected &&
|
|
316
|
-
(yield toTryChangeOption._internal.tryMatchSelection(otherO, descriptionMatch))) {
|
|
317
|
-
change = true;
|
|
318
|
-
}
|
|
319
|
-
return change;
|
|
320
|
-
}))()))).some((b) => b);
|
|
321
|
-
}
|
|
322
|
-
if (change) {
|
|
323
|
-
// tryMatchSelection works its way top down and handles notifications on
|
|
324
|
-
// each level by looking at change returned from its children. That way
|
|
325
|
-
// we do not get multiple notifications for the same ancestral node even
|
|
326
|
-
// if multiple descendants are affected by the tryMatchSelection. Therefore
|
|
327
|
-
// we use OneLevel to make this feature notify and the parent update its
|
|
328
|
-
// reference.
|
|
329
|
-
yield this._notifyAllOfChange(ProductConfigurationBubbleMode.OneLevel, true);
|
|
330
|
-
}
|
|
331
|
-
return change;
|
|
332
|
-
});
|
|
333
|
-
/**
|
|
334
|
-
* Normally this is used through methods on CfgFeature and CfgOption. Use this internal version
|
|
335
|
-
* if you need to control the bubbleMode.
|
|
336
|
-
*
|
|
337
|
-
* Using a validate bubbleMode will cause validation calls to the server.
|
|
338
|
-
*/
|
|
339
|
-
this.selectOption = (optionInternal, on, bubbleMode) => __awaiter(this, void 0, void 0, function* () {
|
|
340
|
-
if (bubbleMode ===
|
|
341
|
-
ProductConfigurationBubbleMode.ValidateAndBubbleSelectedAndApplySyncGroups) {
|
|
342
|
-
const product = this.rootProduct;
|
|
343
|
-
const syncGroupHandler = product.syncGroupHandler;
|
|
344
|
-
assertDefined(syncGroupHandler, `Sync group handler is required for bubble mode ${ProductConfigurationBubbleMode.ValidateAndBubbleSelectedAndApplySyncGroups}`);
|
|
345
|
-
return yield syncGroupHandler.selectOption(product, optionInternal, on, wrapWithCache(product._productLoaderRaw));
|
|
346
|
-
}
|
|
347
|
-
if (!on) {
|
|
348
|
-
if (this.selectionType === SelectionType.Group) {
|
|
349
|
-
throw new Error(`Multiple features are always selected and are not user selectable. Feature key: "${this.key}".`);
|
|
350
|
-
}
|
|
351
|
-
if (this.selectionType === SelectionType.SelectOne) {
|
|
352
|
-
throw new Error(`Select one can never be deselected. Feature key: "${this.key}".`);
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
const selectedOptions = this._selectedOptions;
|
|
356
|
-
const isAllOptionsAffectedByAnySelection = this.isAllOptionsAffectedByAnySelection;
|
|
357
|
-
const index = selectedOptions.findIndex(getOptionFilter(optionInternal));
|
|
358
|
-
const isActualChange = (index === -1) === on;
|
|
359
|
-
const committed = true;
|
|
360
|
-
if (this.selectionType !== SelectionType.Group) {
|
|
361
|
-
if (on) {
|
|
362
|
-
// Calling this.options will populate (generate) all Options if they have not yet
|
|
363
|
-
// been generated. As we are selecting an option we need all Options to be generated.
|
|
364
|
-
const options = this.options;
|
|
365
|
-
if (this.selectionType === SelectionType.SelectOne) {
|
|
366
|
-
let removeOption = selectedOptions.shift();
|
|
367
|
-
while (removeOption !== undefined) {
|
|
368
|
-
if (!isAllOptionsAffectedByAnySelection) {
|
|
369
|
-
doFreshRefOption(options, removeOption._internal, committed);
|
|
370
|
-
}
|
|
371
|
-
removeOption = selectedOptions.shift();
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
if (isAllOptionsAffectedByAnySelection) {
|
|
375
|
-
selectedOptions.push(CfgOption._makeNewRefFrom(optionInternal));
|
|
376
|
-
}
|
|
377
|
-
else {
|
|
378
|
-
doFreshRefOption(options, optionInternal, committed, (freshRef) => selectedOptions.push(freshRef));
|
|
379
|
-
}
|
|
380
|
-
if (this.selectionType === SelectionType.SelectOne) {
|
|
381
|
-
yield this.pushStretch();
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
else {
|
|
385
|
-
selectedOptions.splice(index, 1);
|
|
386
|
-
if (!isAllOptionsAffectedByAnySelection) {
|
|
387
|
-
// Accessing this._options gives us the Options that have been generated
|
|
388
|
-
// or undefined if the Options have not yet been generated. As this action
|
|
389
|
-
// is deselect and deselected is the default uninitialized state we can
|
|
390
|
-
// safely ignore if undefined.
|
|
391
|
-
const options = this._options;
|
|
392
|
-
if (options !== undefined) {
|
|
393
|
-
doFreshRefOption(options, optionInternal, committed);
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
if (isAllOptionsAffectedByAnySelection) {
|
|
398
|
-
this._freshRefAllOptions(committed);
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
let nextLevelBubbleMode = bubbleMode;
|
|
402
|
-
if (!on) {
|
|
403
|
-
// If this was a deselect action we shall not bubble selected, that is, we shall not
|
|
404
|
-
// select the ancestors if this action was deselect.
|
|
405
|
-
if (bubbleMode === ProductConfigurationBubbleMode.ValidateAndBubbleSelected) {
|
|
406
|
-
nextLevelBubbleMode = ProductConfigurationBubbleMode.Validate;
|
|
407
|
-
}
|
|
408
|
-
else if (bubbleMode === ProductConfigurationBubbleMode.BubbleSelected) {
|
|
409
|
-
nextLevelBubbleMode = ProductConfigurationBubbleMode.ToRoot;
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
yield this._notifyAllOfChange(nextLevelBubbleMode, committed);
|
|
413
|
-
return isActualChange;
|
|
414
|
-
});
|
|
415
|
-
this.isSelected = (option) => this.selectionType === SelectionType.Group ||
|
|
416
|
-
this._selectedOptions.some(getOptionFilter(option));
|
|
417
|
-
this.isDisabled = (option) => this._constrainedOptions.some(getOptionFilter(option));
|
|
418
|
-
this.keyMatch = (other, descriptionMatch = false) => descriptionMatch
|
|
419
|
-
? this.description.toLowerCase() === other.description.toLowerCase()
|
|
420
|
-
: this.code === other.code;
|
|
421
|
-
/** Only in selected options */
|
|
422
|
-
this._getFeaturesWithCode = (code) => this._selectedOptions.reduce((agg, selectedOption) => {
|
|
423
|
-
agg.push(...selectedOption._internal._getFeaturesWithCode(code));
|
|
424
|
-
return agg;
|
|
425
|
-
}, code === this.code ? [this] : []);
|
|
426
|
-
if (rawFeature.multiple) {
|
|
427
|
-
this.selectionType = SelectionType.Group;
|
|
428
|
-
}
|
|
429
|
-
else if (rawFeature.optional) {
|
|
430
|
-
this.selectionType = SelectionType.SelectMany;
|
|
431
|
-
}
|
|
432
|
-
else {
|
|
433
|
-
this.selectionType = SelectionType.SelectOne;
|
|
434
|
-
}
|
|
435
|
-
this.hasUpcharge = rawFeature.options.some((option) => (option.upcharge !== undefined && option.upcharge !== 0) ||
|
|
436
|
-
(option.priceCodes !== undefined && option.priceCodes.length > 0));
|
|
437
|
-
}
|
|
438
|
-
get code() {
|
|
439
|
-
return this.rawFeature.code;
|
|
440
|
-
}
|
|
441
|
-
get groupCode() {
|
|
442
|
-
return this.rawFeature.groupCode;
|
|
443
|
-
}
|
|
444
|
-
get key() {
|
|
445
|
-
return this._key;
|
|
446
|
-
}
|
|
447
|
-
set key(k) {
|
|
448
|
-
this._key = k;
|
|
449
|
-
}
|
|
450
|
-
get notes() {
|
|
451
|
-
var _a;
|
|
452
|
-
return this.parentProduct.getNotes((_a = this.rawFeature.noteRefs) !== null && _a !== void 0 ? _a : []);
|
|
453
|
-
}
|
|
454
|
-
get isUseNumericValue() {
|
|
455
|
-
return this.rawFeature.numericOrder;
|
|
456
|
-
}
|
|
457
|
-
get numericValue() {
|
|
458
|
-
if (this.selectionType !== SelectionType.SelectOne) {
|
|
459
|
-
throw new Error("Only SelectOne Features can have numeric values");
|
|
460
|
-
}
|
|
461
|
-
const { selectedOptions } = this;
|
|
462
|
-
if (selectedOptions.length === 0) {
|
|
463
|
-
return undefined;
|
|
464
|
-
}
|
|
465
|
-
const value = selectedOptions[0].numericValue;
|
|
466
|
-
if (value === undefined) {
|
|
467
|
-
if (this.isUseNumericValue) {
|
|
468
|
-
throw new Error("No numericValue set on option with numeric sku feature");
|
|
469
|
-
}
|
|
470
|
-
return undefined;
|
|
471
|
-
}
|
|
472
|
-
return value;
|
|
473
|
-
}
|
|
474
|
-
get description() {
|
|
475
|
-
return this.rawFeature.description;
|
|
476
|
-
}
|
|
477
|
-
get omitOnOrder() {
|
|
478
|
-
return this.rawFeature.omitOnOrder === true;
|
|
479
|
-
}
|
|
480
|
-
get syncGroup() {
|
|
481
|
-
return this.rawFeature.syncGroup;
|
|
482
|
-
}
|
|
483
|
-
/**
|
|
484
|
-
* @return one of the following, in order:
|
|
485
|
-
* - undefined if the Feature lacks a syncGroup.
|
|
486
|
-
* - false if the syncGroup doesn't fulfill the optional mustSupport requirement.
|
|
487
|
-
* - syncCode from the syncGroup.
|
|
488
|
-
*/
|
|
489
|
-
getSyncCode(mustSupport) {
|
|
490
|
-
if (this.syncGroup === undefined) {
|
|
491
|
-
return undefined;
|
|
492
|
-
}
|
|
493
|
-
const { syncGroupCode, syncMethod } = this.syncGroup;
|
|
494
|
-
if (mustSupport === undefined || syncMethod === "twoWay" || syncMethod === mustSupport) {
|
|
495
|
-
return syncGroupCode;
|
|
496
|
-
}
|
|
497
|
-
return false;
|
|
498
|
-
}
|
|
499
|
-
/**
|
|
500
|
-
* The DtoMeasureParam class is re-used for different purposes. In Features it is used
|
|
501
|
-
* to indicate which stretch measures inside Models shall be affected by this state
|
|
502
|
-
* of this Feature. Hence only the code property is used.
|
|
503
|
-
*/
|
|
504
|
-
get measureParamCodes() {
|
|
505
|
-
var _a;
|
|
506
|
-
return (_a = this.rawFeature.measureParams) === null || _a === void 0 ? void 0 : _a.map((measureParam) => measureParam.code);
|
|
507
|
-
}
|
|
508
|
-
get unit() {
|
|
509
|
-
const unit = this.rawFeature.unit;
|
|
510
|
-
return unit === undefined ? this.parentProduct.unit : toLengthUnit(unit);
|
|
511
|
-
}
|
|
512
|
-
get mtrlApplications() {
|
|
513
|
-
if (this._mtrlApplications === undefined) {
|
|
514
|
-
this._mtrlApplications = (this.rawFeature.mtrlApplications || []).map((m) => CfgMtrlApplication.fromMtrlLikeApplication(CfgMtrlApplicationSource.Feature, m));
|
|
515
|
-
}
|
|
516
|
-
return this._mtrlApplications;
|
|
517
|
-
}
|
|
518
|
-
get selectedOptions() {
|
|
519
|
-
return this._selectedOptions;
|
|
520
|
-
}
|
|
521
|
-
get ancestorsSelected() {
|
|
522
|
-
return (this.parent instanceof _CfgProductConfigurationInternal || this.parent.ancestorsSelected);
|
|
523
|
-
}
|
|
524
|
-
/**
|
|
525
|
-
* If one option is selected or deselected this will potentially
|
|
526
|
-
* affect all other Options on this Feature
|
|
527
|
-
*/
|
|
528
|
-
get isAllOptionsAffectedByAnySelection() {
|
|
529
|
-
return this.selectionType === SelectionType.SelectOne && this.hasUpcharge !== false;
|
|
530
|
-
}
|
|
531
|
-
get preview() {
|
|
532
|
-
return getMtrlPreview(this._mtrlApplications);
|
|
533
|
-
}
|
|
534
|
-
/**
|
|
535
|
-
* Please note that this relates to the visibility in the Configuration tree.
|
|
536
|
-
* It does not affect the visibility of anything in the 3D view at all.
|
|
537
|
-
*/
|
|
538
|
-
get visibleIfAdditionalProduct() {
|
|
539
|
-
return this.rawFeature.hideIfAdditionalProduct !== true;
|
|
540
|
-
}
|
|
541
|
-
/**
|
|
542
|
-
* Please note that this relates to the visibility in the Configuration tree.
|
|
543
|
-
* It does not affect the visibility of anything in the 3D view at all.
|
|
544
|
-
*/
|
|
545
|
-
get visibleIfMainProduct() {
|
|
546
|
-
return this.rawFeature.hideIfMainProduct !== true;
|
|
547
|
-
}
|
|
548
|
-
/**
|
|
549
|
-
* Please note that this relates to the visibility in the Configuration tree.
|
|
550
|
-
* It does not affect the visibility of anything in the 3D view at all.
|
|
551
|
-
*/
|
|
552
|
-
get visible() {
|
|
553
|
-
return this.parentProduct.isAdditionalProduct
|
|
554
|
-
? this.visibleIfAdditionalProduct
|
|
555
|
-
: this.visibleIfMainProduct;
|
|
556
|
-
}
|
|
557
|
-
get options() {
|
|
558
|
-
if (this._options === undefined) {
|
|
559
|
-
const hasDuplicateDescription =
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
this._options = this.rawFeature.options.map((o) => CfgOption.make(o, this.rawFeatures, hasDuplicateDescription, this, this.parentConfiguration, this.parentProduct, this.rootProduct));
|
|
563
|
-
}
|
|
564
|
-
return this._options;
|
|
565
|
-
}
|
|
566
|
-
/**
|
|
567
|
-
* Make fresh references to all options on this feature.
|
|
568
|
-
* Also includes currently selected options.
|
|
569
|
-
* @throws Will throw if options have not yet been generated.
|
|
570
|
-
*/
|
|
571
|
-
_freshRefAllOptions(committed) {
|
|
572
|
-
const options = this._options;
|
|
573
|
-
const selectedOptions = this._selectedOptions;
|
|
574
|
-
if (options === undefined) {
|
|
575
|
-
throw new Error("We expect all options to be generated at freshref");
|
|
576
|
-
}
|
|
577
|
-
for (let i = 0; i < options.length; i++) {
|
|
578
|
-
doFreshRefOptionAtIndex(options, i, committed, (freshRef) => {
|
|
579
|
-
const selectedIndex = selectedOptions.findIndex(getOptionFilter(freshRef._internal));
|
|
580
|
-
if (selectedIndex !== -1) {
|
|
581
|
-
selectedOptions[selectedIndex] = freshRef;
|
|
582
|
-
}
|
|
583
|
-
});
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
export class CfgFeature {
|
|
588
|
-
/**
|
|
589
|
-
* Private constructor and make-method because make new ref requires the constructor to
|
|
590
|
-
* take an internal and we don't want those who instantiate CfgFeature to have to be aware
|
|
591
|
-
* of the internal.
|
|
592
|
-
*/
|
|
593
|
-
constructor(_internal) {
|
|
594
|
-
this._internal = _internal;
|
|
595
|
-
this.isBackedBySame = (other) => this._internal === other._internal;
|
|
596
|
-
/**
|
|
597
|
-
* This will find the first option allowing the value, set the value on it and select it.
|
|
598
|
-
* This is an implicit option-select.
|
|
599
|
-
*/
|
|
600
|
-
this.setNumericValue = (val) => __awaiter(this, void 0, void 0, function* () { return yield this._internal.setNumericValue(val); });
|
|
601
|
-
/**
|
|
602
|
-
* Selects the passed Option.
|
|
603
|
-
* Only Options belonging to Features that are "select many" can be deselected.
|
|
604
|
-
* Calling this will cause a validation call to the server.
|
|
605
|
-
*/
|
|
606
|
-
this.selectOption = (option, on) => __awaiter(this, void 0, void 0, function* () {
|
|
607
|
-
return yield this._internal.selectOption(option._internal, on, ProductConfigurationBubbleMode.ValidateAndBubbleSelectedAndApplySyncGroups);
|
|
608
|
-
});
|
|
609
|
-
this.isSelected = (option) => this._internal.isSelected(option._internal);
|
|
610
|
-
this.listenForChange = (l) => this._internal.changeObservable.listen(l);
|
|
611
|
-
this.stopListenForChange = (l) => this._internal.changeObservable.stopListen(l);
|
|
612
|
-
}
|
|
613
|
-
static make(rawFeature, rawFeatures, key, parent, parentConfiguration, parentProduct, rootProduct) {
|
|
614
|
-
return new this(new _CfgFeatureInternal(rawFeature, rawFeatures, key, parent, parentConfiguration, parentProduct, rootProduct));
|
|
615
|
-
}
|
|
616
|
-
/**
|
|
617
|
-
* Makes an object wrapping the passed object. This is not a clone method,
|
|
618
|
-
* it is a method to make a new outer reference. Like a shallow copy.
|
|
619
|
-
* We use this to help frameworks that are built around using equals to detect change.
|
|
620
|
-
*/
|
|
621
|
-
static _makeNewRefFrom(internal) {
|
|
622
|
-
return new this(internal);
|
|
623
|
-
}
|
|
624
|
-
get parentProduct() {
|
|
625
|
-
return CfgProduct._makeNewRefFrom(this._internal.parentProduct);
|
|
626
|
-
}
|
|
627
|
-
get rootProduct() {
|
|
628
|
-
return CfgProduct._makeNewRefFrom(this._internal.rootProduct);
|
|
629
|
-
}
|
|
630
|
-
get selectionType() {
|
|
631
|
-
return this._internal.selectionType;
|
|
632
|
-
}
|
|
633
|
-
// Unique amongst siblings. Can change. Only use for presentation layer i.e. React.
|
|
634
|
-
get key() {
|
|
635
|
-
return this._internal.key;
|
|
636
|
-
}
|
|
637
|
-
get code() {
|
|
638
|
-
return this._internal.code;
|
|
639
|
-
}
|
|
640
|
-
get groupCode() {
|
|
641
|
-
return this._internal.groupCode;
|
|
642
|
-
}
|
|
643
|
-
get notes() {
|
|
644
|
-
return this._internal.notes;
|
|
645
|
-
}
|
|
646
|
-
/**
|
|
647
|
-
* If true the options in the feature is selected by both sending its code and numeric value
|
|
648
|
-
* when selecting.
|
|
649
|
-
*/
|
|
650
|
-
get isUseNumericValue() {
|
|
651
|
-
return this._internal.isUseNumericValue;
|
|
652
|
-
}
|
|
653
|
-
/** This will read the numeric value of the selected option. */
|
|
654
|
-
get numericValue() {
|
|
655
|
-
return this._internal.numericValue;
|
|
656
|
-
}
|
|
657
|
-
get unit() {
|
|
658
|
-
return this._internal.unit;
|
|
659
|
-
}
|
|
660
|
-
get description() {
|
|
661
|
-
return this._internal.description;
|
|
662
|
-
}
|
|
663
|
-
get omitOnOrder() {
|
|
664
|
-
return this._internal.omitOnOrder;
|
|
665
|
-
}
|
|
666
|
-
get hasUpcharge() {
|
|
667
|
-
return this._internal.hasUpcharge;
|
|
668
|
-
}
|
|
669
|
-
get selectedOptions() {
|
|
670
|
-
return this._internal.selectedOptions;
|
|
671
|
-
}
|
|
672
|
-
/** Are all ancestors up to the CfgProductConfiguration selected? */
|
|
673
|
-
get ancestorsSelected() {
|
|
674
|
-
return this._internal.ancestorsSelected;
|
|
675
|
-
}
|
|
676
|
-
get preview() {
|
|
677
|
-
return this._internal.preview;
|
|
678
|
-
}
|
|
679
|
-
get options() {
|
|
680
|
-
return this._internal.options;
|
|
681
|
-
}
|
|
682
|
-
/**
|
|
683
|
-
* Please note that this relates to the visibility in the Configuration tree.
|
|
684
|
-
* It does not affect the visibility of anything in the 3D view at all.
|
|
685
|
-
* Visibility is not inherited. If this is hidden the children
|
|
686
|
-
* of this Feature might still be visible, depending on their settings.
|
|
687
|
-
*/
|
|
688
|
-
get visible() {
|
|
689
|
-
return this._internal.visible;
|
|
690
|
-
}
|
|
691
|
-
}
|
|
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 { assertDefined, compareArrays, convertLength, count, Observable, someMatch, toLengthUnit, } from "@configura/web-utilities";
|
|
11
|
+
import { CfgProduct } from "../CfgProduct.js";
|
|
12
|
+
import { CfgMtrlApplication } from "../material/CfgMtrlApplication.js";
|
|
13
|
+
import { CfgMtrlApplicationSource } from "../material/CfgMtrlApplicationSource.js";
|
|
14
|
+
import { wrapWithCache } from "../productLoader.js";
|
|
15
|
+
import { CfgOption, ProductConfigurationBubbleMode } from "./CfgOption.js";
|
|
16
|
+
import { _CfgProductConfigurationInternal } from "./CfgProductConfiguration.js";
|
|
17
|
+
import { getMtrlPreview } from "./utilitiesProductConfiguration.js";
|
|
18
|
+
export var SelectionType;
|
|
19
|
+
(function (SelectionType) {
|
|
20
|
+
/**
|
|
21
|
+
* All options are permanently selected. In our ui-component for this we skip over this level,
|
|
22
|
+
* and immediately show the children, but you could do this differently, like for instance
|
|
23
|
+
* showing the group heading.
|
|
24
|
+
*/
|
|
25
|
+
SelectionType[SelectionType["Group"] = 0] = "Group";
|
|
26
|
+
/**
|
|
27
|
+
* One and only one can be selected at a time. This normally corresponds to either a dropdown
|
|
28
|
+
* menu or radio buttons.
|
|
29
|
+
*/
|
|
30
|
+
SelectionType[SelectionType["SelectOne"] = 1] = "SelectOne";
|
|
31
|
+
/** Zero to all can be selected at a time. This normally corresponds to checkboxes. */
|
|
32
|
+
SelectionType[SelectionType["SelectMany"] = 2] = "SelectMany";
|
|
33
|
+
})(SelectionType || (SelectionType = {}));
|
|
34
|
+
function getOptionFilter(option1) {
|
|
35
|
+
return (option2) => option2._internal === option1;
|
|
36
|
+
}
|
|
37
|
+
/** Returns a fresh reference for the option at the given index */
|
|
38
|
+
function doFreshRefOptionAtIndex(options, index, committed, beforeNotify) {
|
|
39
|
+
const freshRef = CfgOption._makeNewRefFrom(options[index]._internal);
|
|
40
|
+
options[index] = freshRef;
|
|
41
|
+
if (beforeNotify) {
|
|
42
|
+
beforeNotify(freshRef);
|
|
43
|
+
}
|
|
44
|
+
freshRef._internal.changeObservable.notifyAll({ freshRef, committed });
|
|
45
|
+
return freshRef;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Returns a fresh reference for the given option.
|
|
49
|
+
* @throws Throws an error if the option is not found in options.
|
|
50
|
+
*/
|
|
51
|
+
function doFreshRefOption(options, optionInternal, committed, beforeNotify) {
|
|
52
|
+
const i = options.findIndex(getOptionFilter(optionInternal));
|
|
53
|
+
if (i === -1) {
|
|
54
|
+
throw new Error("Did not find the option in options");
|
|
55
|
+
}
|
|
56
|
+
return doFreshRefOptionAtIndex(options, i, committed, beforeNotify);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* This class is meant to only be used through CfgFeature. It should
|
|
60
|
+
* never be instantiated on its own. Normally the internal state of this class
|
|
61
|
+
* should never be directly modified. CfgFeature is the class that
|
|
62
|
+
* should be used and interacted with.
|
|
63
|
+
*/
|
|
64
|
+
export class _CfgFeatureInternal {
|
|
65
|
+
constructor(rawFeature, rawFeatures, _key, // Unique amongst siblings
|
|
66
|
+
parent, parentConfiguration, parentProduct, rootProduct) {
|
|
67
|
+
this.rawFeature = rawFeature;
|
|
68
|
+
this.rawFeatures = rawFeatures;
|
|
69
|
+
this._key = _key;
|
|
70
|
+
this.parent = parent;
|
|
71
|
+
this.parentConfiguration = parentConfiguration;
|
|
72
|
+
this.parentProduct = parentProduct;
|
|
73
|
+
this.rootProduct = rootProduct;
|
|
74
|
+
this._selectedOptions = [];
|
|
75
|
+
this._constrainedOptions = [];
|
|
76
|
+
this.changeObservable = new Observable();
|
|
77
|
+
this.setNumericValue = (val) => __awaiter(this, void 0, void 0, function* () {
|
|
78
|
+
if (this.selectionType !== SelectionType.SelectOne) {
|
|
79
|
+
throw new Error("Only SelectOne Features can have numeric values");
|
|
80
|
+
}
|
|
81
|
+
const option = this.options.find((option) => option.isAllowedNumericValue(val));
|
|
82
|
+
if (option === undefined) {
|
|
83
|
+
throw new Error("No Option allows the value you tried to set");
|
|
84
|
+
}
|
|
85
|
+
return yield option.setNumericValue(val, true);
|
|
86
|
+
});
|
|
87
|
+
this._notifyAllOfChange = (bubbleMode, committed) => __awaiter(this, void 0, void 0, function* () {
|
|
88
|
+
if (bubbleMode === ProductConfigurationBubbleMode.Stop) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const freshRef = CfgFeature._makeNewRefFrom(this);
|
|
92
|
+
this.changeObservable.notifyAll({ freshRef, committed });
|
|
93
|
+
const parent = this.parent;
|
|
94
|
+
if (parent !== undefined) {
|
|
95
|
+
yield parent._childHasChanged(freshRef, bubbleMode === ProductConfigurationBubbleMode.OneLevel
|
|
96
|
+
? ProductConfigurationBubbleMode.Stop
|
|
97
|
+
: bubbleMode, committed);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
/**
|
|
101
|
+
* Called by child to tell its parent that it has changed.
|
|
102
|
+
* @throws Will throw if options have not yet been generated. This should be impossible
|
|
103
|
+
* as nonexisting children can not call their parent.
|
|
104
|
+
*/
|
|
105
|
+
this._childHasChanged = (childOption, bubbleMode, committed) => __awaiter(this, void 0, void 0, function* () {
|
|
106
|
+
const options = this._options;
|
|
107
|
+
if (options === undefined) {
|
|
108
|
+
throw Error("Child says it changed, but no children has actually been generated");
|
|
109
|
+
}
|
|
110
|
+
const freshRef = doFreshRefOption(options, childOption, committed);
|
|
111
|
+
const selectedOptions = this._selectedOptions;
|
|
112
|
+
const i = selectedOptions.findIndex(getOptionFilter(childOption));
|
|
113
|
+
if (i !== -1) {
|
|
114
|
+
selectedOptions[i] = freshRef;
|
|
115
|
+
}
|
|
116
|
+
yield this._notifyAllOfChange(bubbleMode, committed);
|
|
117
|
+
});
|
|
118
|
+
// Keep in sync with stripExtendedDataFromDtoFeatureConf
|
|
119
|
+
this.getDtoConf = (includeExtendedData) => {
|
|
120
|
+
const result = {
|
|
121
|
+
code: this.code,
|
|
122
|
+
};
|
|
123
|
+
if (includeExtendedData) {
|
|
124
|
+
result.groupCode = this.groupCode;
|
|
125
|
+
result.unit = this.unit;
|
|
126
|
+
}
|
|
127
|
+
const selectedOptions = this._selectedOptions;
|
|
128
|
+
if (0 < selectedOptions.length) {
|
|
129
|
+
result.options = selectedOptions.map((o) => o._internal.getDtoConf(includeExtendedData));
|
|
130
|
+
}
|
|
131
|
+
return result;
|
|
132
|
+
};
|
|
133
|
+
// DtoFeatureConf is the newer more easily readable format for configuration. As
|
|
134
|
+
// the API:s are still using the older format (and will for the forseeable future) we need
|
|
135
|
+
// to support both formats. The new format can be converted to the old, but not the other
|
|
136
|
+
// way around. For that reason the get-method above uses the new format, and the set-method
|
|
137
|
+
// below the old format. As these functions are meant to only be used internally this should't
|
|
138
|
+
// cause too much confusion.
|
|
139
|
+
this.setApiSelection = (apiOptionSelectionMap, apiOptionConstraintMap) => __awaiter(this, void 0, void 0, function* () {
|
|
140
|
+
var _a, _b;
|
|
141
|
+
const selectionType = this.selectionType;
|
|
142
|
+
const isGroup = selectionType === SelectionType.Group;
|
|
143
|
+
const isSelectOne = selectionType === SelectionType.SelectOne;
|
|
144
|
+
const isAllOptionsAffectedByAnySelection = this.isAllOptionsAffectedByAnySelection;
|
|
145
|
+
const committed = true;
|
|
146
|
+
let options;
|
|
147
|
+
if (apiOptionSelectionMap === undefined) {
|
|
148
|
+
options = this._options || []; // Already generated children (all or none)
|
|
149
|
+
apiOptionSelectionMap = {};
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
options = this.options; // This will generate all children
|
|
153
|
+
}
|
|
154
|
+
let change = false;
|
|
155
|
+
const constraintMap = apiOptionConstraintMap !== null && apiOptionConstraintMap !== void 0 ? apiOptionConstraintMap : {};
|
|
156
|
+
// The first value that belongs to this features options is used as the key to look up constrained options
|
|
157
|
+
const featureConstrainedOptions = (_b = (_a = options.map((o) => constraintMap[o.code]).find((o) => !!o)) === null || _a === void 0 ? void 0 : _a.constrOptions) !== null && _b !== void 0 ? _b : [];
|
|
158
|
+
for (let i = 0; i < options.length; i++) {
|
|
159
|
+
const option = options[i];
|
|
160
|
+
const apiOptionSelection = apiOptionSelectionMap[option.code];
|
|
161
|
+
const isConstrained = featureConstrainedOptions.indexOf(option.code) >= 0;
|
|
162
|
+
const selectedIndex = this._selectedOptions.findIndex(getOptionFilter(option._internal));
|
|
163
|
+
const constrainedIndex = this._constrainedOptions.findIndex(getOptionFilter(option._internal));
|
|
164
|
+
const isOn = isGroup || apiOptionSelection;
|
|
165
|
+
if (isConstrained) {
|
|
166
|
+
if (constrainedIndex === -1) {
|
|
167
|
+
doFreshRefOptionAtIndex(options, i, committed, (freshRef) => this._constrainedOptions.push(freshRef));
|
|
168
|
+
change = true;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
if (constrainedIndex !== -1) {
|
|
173
|
+
this._constrainedOptions.splice(constrainedIndex, 1);
|
|
174
|
+
doFreshRefOptionAtIndex(options, i, committed);
|
|
175
|
+
change = true;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (isOn) {
|
|
179
|
+
if (isSelectOne) {
|
|
180
|
+
if (this.isUseNumericValue) {
|
|
181
|
+
const { numericValue } = apiOptionSelection;
|
|
182
|
+
if (numericValue === undefined) {
|
|
183
|
+
throw new Error("No numeric value for numeric feature");
|
|
184
|
+
}
|
|
185
|
+
const { value, unit } = numericValue;
|
|
186
|
+
if (yield option.setNumericValue(unit === undefined
|
|
187
|
+
? value
|
|
188
|
+
: convertLength(value, toLengthUnit(unit), this.unit), false)) {
|
|
189
|
+
change = true;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (selectedIndex === -1) {
|
|
194
|
+
if (isAllOptionsAffectedByAnySelection) {
|
|
195
|
+
this._selectedOptions.push(option);
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
doFreshRefOptionAtIndex(options, i, committed, (freshRef) => this._selectedOptions.push(freshRef));
|
|
199
|
+
}
|
|
200
|
+
change = true;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
if (selectedIndex !== -1) {
|
|
205
|
+
this._selectedOptions.splice(selectedIndex, 1);
|
|
206
|
+
if (!isAllOptionsAffectedByAnySelection) {
|
|
207
|
+
doFreshRefOptionAtIndex(options, i, committed);
|
|
208
|
+
}
|
|
209
|
+
change = true;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
if (yield option._internal.setApiSelection(apiOptionSelection, constraintMap[option.code])) {
|
|
213
|
+
change = true;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (this.ancestorsSelected) {
|
|
217
|
+
const selectionCount = this._selectedOptions.length;
|
|
218
|
+
if (isSelectOne && selectionCount !== 1) {
|
|
219
|
+
const wrongCountWarning = `A select-one Feature (i.e. neither optional nor multiple) should have exactly one Option selected. Feature key: "${this.key}". Actual: ${selectionCount}`;
|
|
220
|
+
if (this.rootProduct.settings.strictSelectOneSelectionCount) {
|
|
221
|
+
throw new Error(wrongCountWarning);
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
console.warn(wrongCountWarning);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
if (change) {
|
|
229
|
+
if (isAllOptionsAffectedByAnySelection) {
|
|
230
|
+
this._freshRefAllOptions(committed);
|
|
231
|
+
}
|
|
232
|
+
if (selectionType === SelectionType.SelectOne) {
|
|
233
|
+
yield this.pushStretch();
|
|
234
|
+
}
|
|
235
|
+
// setApiSelection works its way top down and handles notifications on
|
|
236
|
+
// each level by looking at change returned from its children. That way
|
|
237
|
+
// we do not get multiple notifications for the same ancestral node even
|
|
238
|
+
// if multiple descendants are affected by the setApiSelection. Therefore
|
|
239
|
+
// we use OneLevel to make this feature notify and the parent update its
|
|
240
|
+
// reference.
|
|
241
|
+
yield this._notifyAllOfChange(ProductConfigurationBubbleMode.OneLevel, committed);
|
|
242
|
+
}
|
|
243
|
+
return change;
|
|
244
|
+
});
|
|
245
|
+
this.addForApiConstrained = (next) => {
|
|
246
|
+
const constrOptions = this._constrainedOptions;
|
|
247
|
+
for (const so of this._selectedOptions) {
|
|
248
|
+
next[so.code] = so._internal.getApiConstrained(constrOptions);
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
/** Pushes to refresh stretch. Does not cause validation. */
|
|
252
|
+
this.pushStretch = () => __awaiter(this, void 0, void 0, function* () {
|
|
253
|
+
if (this.selectionType !== SelectionType.SelectOne) {
|
|
254
|
+
throw new Error("Can only push stretch for SelectOne");
|
|
255
|
+
}
|
|
256
|
+
const value = this.numericValue;
|
|
257
|
+
return (yield Promise.all((this.measureParamCodes || []).map((measureParamCode) => this.parentConfiguration.setStretchReferenceLength(measureParamCode, value, this.unit)))).some((change) => change);
|
|
258
|
+
});
|
|
259
|
+
this.structureCompare = (other, strictOrder = true, descriptionMatch = false) => this.keyMatch(other, descriptionMatch) &&
|
|
260
|
+
compareArrays(this.options, other.options, (l, r) => l._internal.structureCompare(r._internal, strictOrder, descriptionMatch), strictOrder);
|
|
261
|
+
this.tryMatchSelection = (other, descriptionMatch = false) => __awaiter(this, void 0, void 0, function* () {
|
|
262
|
+
const { selectionType, isUseNumericValue } = this;
|
|
263
|
+
if (selectionType !== other.selectionType) {
|
|
264
|
+
// Will not try if the selection type is different
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
if (isUseNumericValue !== other.isUseNumericValue) {
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
let change = false;
|
|
271
|
+
if (isUseNumericValue) {
|
|
272
|
+
// For numeric options we do not try to go any further down. It's becoming to
|
|
273
|
+
// strange to make informed guesses on how to progress down the tree.
|
|
274
|
+
const otherNumericValue = other.numericValue;
|
|
275
|
+
if (otherNumericValue === undefined) {
|
|
276
|
+
throw new Error("Numeric feature without numeric value, this should never happen");
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
if (yield this.setNumericValue(otherNumericValue)) {
|
|
280
|
+
change = true;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
const thisOptions = this.options;
|
|
286
|
+
const otherOptions = other.options;
|
|
287
|
+
change = (yield Promise.all(otherOptions.map((otherO) => (() => __awaiter(this, void 0, void 0, function* () {
|
|
288
|
+
const otherOSelected = otherO.selected;
|
|
289
|
+
if (selectionType === SelectionType.SelectOne && !otherOSelected) {
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
if (1 <
|
|
293
|
+
count(otherOptions, (item) => item._internal.keyMatch(otherO._internal, descriptionMatch))) {
|
|
294
|
+
console.warn("tryMatchSelection will ignore options that have the same key");
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
297
|
+
const toTryChangeOptions = thisOptions.filter((o) => otherO._internal.keyMatch(o._internal, descriptionMatch));
|
|
298
|
+
if (1 < toTryChangeOptions.length) {
|
|
299
|
+
console.warn("tryMatchSelection will ignore options that have the same key");
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
if (toTryChangeOptions.length === 0) {
|
|
303
|
+
return false;
|
|
304
|
+
}
|
|
305
|
+
const toTryChangeOption = toTryChangeOptions[0];
|
|
306
|
+
let change = false;
|
|
307
|
+
if (selectionType === SelectionType.SelectMany ||
|
|
308
|
+
selectionType === SelectionType.SelectOne) {
|
|
309
|
+
// The setSelected will only affect ourselves,
|
|
310
|
+
// so we do not need to bubble the notification.
|
|
311
|
+
// Instead we use the change variable to know
|
|
312
|
+
// when to notify for this feature.
|
|
313
|
+
change = yield toTryChangeOption._internal.parent.selectOption(toTryChangeOption._internal, otherOSelected, ProductConfigurationBubbleMode.Stop);
|
|
314
|
+
}
|
|
315
|
+
if (otherOSelected &&
|
|
316
|
+
(yield toTryChangeOption._internal.tryMatchSelection(otherO, descriptionMatch))) {
|
|
317
|
+
change = true;
|
|
318
|
+
}
|
|
319
|
+
return change;
|
|
320
|
+
}))()))).some((b) => b);
|
|
321
|
+
}
|
|
322
|
+
if (change) {
|
|
323
|
+
// tryMatchSelection works its way top down and handles notifications on
|
|
324
|
+
// each level by looking at change returned from its children. That way
|
|
325
|
+
// we do not get multiple notifications for the same ancestral node even
|
|
326
|
+
// if multiple descendants are affected by the tryMatchSelection. Therefore
|
|
327
|
+
// we use OneLevel to make this feature notify and the parent update its
|
|
328
|
+
// reference.
|
|
329
|
+
yield this._notifyAllOfChange(ProductConfigurationBubbleMode.OneLevel, true);
|
|
330
|
+
}
|
|
331
|
+
return change;
|
|
332
|
+
});
|
|
333
|
+
/**
|
|
334
|
+
* Normally this is used through methods on CfgFeature and CfgOption. Use this internal version
|
|
335
|
+
* if you need to control the bubbleMode.
|
|
336
|
+
*
|
|
337
|
+
* Using a validate bubbleMode will cause validation calls to the server.
|
|
338
|
+
*/
|
|
339
|
+
this.selectOption = (optionInternal, on, bubbleMode) => __awaiter(this, void 0, void 0, function* () {
|
|
340
|
+
if (bubbleMode ===
|
|
341
|
+
ProductConfigurationBubbleMode.ValidateAndBubbleSelectedAndApplySyncGroups) {
|
|
342
|
+
const product = this.rootProduct;
|
|
343
|
+
const syncGroupHandler = product.syncGroupHandler;
|
|
344
|
+
assertDefined(syncGroupHandler, `Sync group handler is required for bubble mode ${ProductConfigurationBubbleMode.ValidateAndBubbleSelectedAndApplySyncGroups}`);
|
|
345
|
+
return yield syncGroupHandler.selectOption(product, optionInternal, on, wrapWithCache(product._productLoaderRaw));
|
|
346
|
+
}
|
|
347
|
+
if (!on) {
|
|
348
|
+
if (this.selectionType === SelectionType.Group) {
|
|
349
|
+
throw new Error(`Multiple features are always selected and are not user selectable. Feature key: "${this.key}".`);
|
|
350
|
+
}
|
|
351
|
+
if (this.selectionType === SelectionType.SelectOne) {
|
|
352
|
+
throw new Error(`Select one can never be deselected. Feature key: "${this.key}".`);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
const selectedOptions = this._selectedOptions;
|
|
356
|
+
const isAllOptionsAffectedByAnySelection = this.isAllOptionsAffectedByAnySelection;
|
|
357
|
+
const index = selectedOptions.findIndex(getOptionFilter(optionInternal));
|
|
358
|
+
const isActualChange = (index === -1) === on;
|
|
359
|
+
const committed = true;
|
|
360
|
+
if (this.selectionType !== SelectionType.Group) {
|
|
361
|
+
if (on) {
|
|
362
|
+
// Calling this.options will populate (generate) all Options if they have not yet
|
|
363
|
+
// been generated. As we are selecting an option we need all Options to be generated.
|
|
364
|
+
const options = this.options;
|
|
365
|
+
if (this.selectionType === SelectionType.SelectOne) {
|
|
366
|
+
let removeOption = selectedOptions.shift();
|
|
367
|
+
while (removeOption !== undefined) {
|
|
368
|
+
if (!isAllOptionsAffectedByAnySelection) {
|
|
369
|
+
doFreshRefOption(options, removeOption._internal, committed);
|
|
370
|
+
}
|
|
371
|
+
removeOption = selectedOptions.shift();
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
if (isAllOptionsAffectedByAnySelection) {
|
|
375
|
+
selectedOptions.push(CfgOption._makeNewRefFrom(optionInternal));
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
doFreshRefOption(options, optionInternal, committed, (freshRef) => selectedOptions.push(freshRef));
|
|
379
|
+
}
|
|
380
|
+
if (this.selectionType === SelectionType.SelectOne) {
|
|
381
|
+
yield this.pushStretch();
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
else {
|
|
385
|
+
selectedOptions.splice(index, 1);
|
|
386
|
+
if (!isAllOptionsAffectedByAnySelection) {
|
|
387
|
+
// Accessing this._options gives us the Options that have been generated
|
|
388
|
+
// or undefined if the Options have not yet been generated. As this action
|
|
389
|
+
// is deselect and deselected is the default uninitialized state we can
|
|
390
|
+
// safely ignore if undefined.
|
|
391
|
+
const options = this._options;
|
|
392
|
+
if (options !== undefined) {
|
|
393
|
+
doFreshRefOption(options, optionInternal, committed);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
if (isAllOptionsAffectedByAnySelection) {
|
|
398
|
+
this._freshRefAllOptions(committed);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
let nextLevelBubbleMode = bubbleMode;
|
|
402
|
+
if (!on) {
|
|
403
|
+
// If this was a deselect action we shall not bubble selected, that is, we shall not
|
|
404
|
+
// select the ancestors if this action was deselect.
|
|
405
|
+
if (bubbleMode === ProductConfigurationBubbleMode.ValidateAndBubbleSelected) {
|
|
406
|
+
nextLevelBubbleMode = ProductConfigurationBubbleMode.Validate;
|
|
407
|
+
}
|
|
408
|
+
else if (bubbleMode === ProductConfigurationBubbleMode.BubbleSelected) {
|
|
409
|
+
nextLevelBubbleMode = ProductConfigurationBubbleMode.ToRoot;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
yield this._notifyAllOfChange(nextLevelBubbleMode, committed);
|
|
413
|
+
return isActualChange;
|
|
414
|
+
});
|
|
415
|
+
this.isSelected = (option) => this.selectionType === SelectionType.Group ||
|
|
416
|
+
this._selectedOptions.some(getOptionFilter(option));
|
|
417
|
+
this.isDisabled = (option) => this._constrainedOptions.some(getOptionFilter(option));
|
|
418
|
+
this.keyMatch = (other, descriptionMatch = false) => descriptionMatch
|
|
419
|
+
? this.description.toLowerCase() === other.description.toLowerCase()
|
|
420
|
+
: this.code === other.code;
|
|
421
|
+
/** Only in selected options */
|
|
422
|
+
this._getFeaturesWithCode = (code) => this._selectedOptions.reduce((agg, selectedOption) => {
|
|
423
|
+
agg.push(...selectedOption._internal._getFeaturesWithCode(code));
|
|
424
|
+
return agg;
|
|
425
|
+
}, code === this.code ? [this] : []);
|
|
426
|
+
if (rawFeature.multiple) {
|
|
427
|
+
this.selectionType = SelectionType.Group;
|
|
428
|
+
}
|
|
429
|
+
else if (rawFeature.optional) {
|
|
430
|
+
this.selectionType = SelectionType.SelectMany;
|
|
431
|
+
}
|
|
432
|
+
else {
|
|
433
|
+
this.selectionType = SelectionType.SelectOne;
|
|
434
|
+
}
|
|
435
|
+
this.hasUpcharge = rawFeature.options.some((option) => (option.upcharge !== undefined && option.upcharge !== 0) ||
|
|
436
|
+
(option.priceCodes !== undefined && option.priceCodes.length > 0));
|
|
437
|
+
}
|
|
438
|
+
get code() {
|
|
439
|
+
return this.rawFeature.code;
|
|
440
|
+
}
|
|
441
|
+
get groupCode() {
|
|
442
|
+
return this.rawFeature.groupCode;
|
|
443
|
+
}
|
|
444
|
+
get key() {
|
|
445
|
+
return this._key;
|
|
446
|
+
}
|
|
447
|
+
set key(k) {
|
|
448
|
+
this._key = k;
|
|
449
|
+
}
|
|
450
|
+
get notes() {
|
|
451
|
+
var _a;
|
|
452
|
+
return this.parentProduct.getNotes((_a = this.rawFeature.noteRefs) !== null && _a !== void 0 ? _a : []);
|
|
453
|
+
}
|
|
454
|
+
get isUseNumericValue() {
|
|
455
|
+
return this.rawFeature.numericOrder;
|
|
456
|
+
}
|
|
457
|
+
get numericValue() {
|
|
458
|
+
if (this.selectionType !== SelectionType.SelectOne) {
|
|
459
|
+
throw new Error("Only SelectOne Features can have numeric values");
|
|
460
|
+
}
|
|
461
|
+
const { selectedOptions } = this;
|
|
462
|
+
if (selectedOptions.length === 0) {
|
|
463
|
+
return undefined;
|
|
464
|
+
}
|
|
465
|
+
const value = selectedOptions[0].numericValue;
|
|
466
|
+
if (value === undefined) {
|
|
467
|
+
if (this.isUseNumericValue) {
|
|
468
|
+
throw new Error("No numericValue set on option with numeric sku feature");
|
|
469
|
+
}
|
|
470
|
+
return undefined;
|
|
471
|
+
}
|
|
472
|
+
return value;
|
|
473
|
+
}
|
|
474
|
+
get description() {
|
|
475
|
+
return this.rawFeature.description;
|
|
476
|
+
}
|
|
477
|
+
get omitOnOrder() {
|
|
478
|
+
return this.rawFeature.omitOnOrder === true;
|
|
479
|
+
}
|
|
480
|
+
get syncGroup() {
|
|
481
|
+
return this.rawFeature.syncGroup;
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* @return one of the following, in order:
|
|
485
|
+
* - undefined if the Feature lacks a syncGroup.
|
|
486
|
+
* - false if the syncGroup doesn't fulfill the optional mustSupport requirement.
|
|
487
|
+
* - syncCode from the syncGroup.
|
|
488
|
+
*/
|
|
489
|
+
getSyncCode(mustSupport) {
|
|
490
|
+
if (this.syncGroup === undefined) {
|
|
491
|
+
return undefined;
|
|
492
|
+
}
|
|
493
|
+
const { syncGroupCode, syncMethod } = this.syncGroup;
|
|
494
|
+
if (mustSupport === undefined || syncMethod === "twoWay" || syncMethod === mustSupport) {
|
|
495
|
+
return syncGroupCode;
|
|
496
|
+
}
|
|
497
|
+
return false;
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* The DtoMeasureParam class is re-used for different purposes. In Features it is used
|
|
501
|
+
* to indicate which stretch measures inside Models shall be affected by this state
|
|
502
|
+
* of this Feature. Hence only the code property is used.
|
|
503
|
+
*/
|
|
504
|
+
get measureParamCodes() {
|
|
505
|
+
var _a;
|
|
506
|
+
return (_a = this.rawFeature.measureParams) === null || _a === void 0 ? void 0 : _a.map((measureParam) => measureParam.code);
|
|
507
|
+
}
|
|
508
|
+
get unit() {
|
|
509
|
+
const unit = this.rawFeature.unit;
|
|
510
|
+
return unit === undefined ? this.parentProduct.unit : toLengthUnit(unit);
|
|
511
|
+
}
|
|
512
|
+
get mtrlApplications() {
|
|
513
|
+
if (this._mtrlApplications === undefined) {
|
|
514
|
+
this._mtrlApplications = (this.rawFeature.mtrlApplications || []).map((m) => CfgMtrlApplication.fromMtrlLikeApplication(CfgMtrlApplicationSource.Feature, m));
|
|
515
|
+
}
|
|
516
|
+
return this._mtrlApplications;
|
|
517
|
+
}
|
|
518
|
+
get selectedOptions() {
|
|
519
|
+
return this._selectedOptions;
|
|
520
|
+
}
|
|
521
|
+
get ancestorsSelected() {
|
|
522
|
+
return (this.parent instanceof _CfgProductConfigurationInternal || this.parent.ancestorsSelected);
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* If one option is selected or deselected this will potentially
|
|
526
|
+
* affect all other Options on this Feature
|
|
527
|
+
*/
|
|
528
|
+
get isAllOptionsAffectedByAnySelection() {
|
|
529
|
+
return this.selectionType === SelectionType.SelectOne && this.hasUpcharge !== false;
|
|
530
|
+
}
|
|
531
|
+
get preview() {
|
|
532
|
+
return getMtrlPreview(this._mtrlApplications);
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Please note that this relates to the visibility in the Configuration tree.
|
|
536
|
+
* It does not affect the visibility of anything in the 3D view at all.
|
|
537
|
+
*/
|
|
538
|
+
get visibleIfAdditionalProduct() {
|
|
539
|
+
return this.rawFeature.hideIfAdditionalProduct !== true;
|
|
540
|
+
}
|
|
541
|
+
/**
|
|
542
|
+
* Please note that this relates to the visibility in the Configuration tree.
|
|
543
|
+
* It does not affect the visibility of anything in the 3D view at all.
|
|
544
|
+
*/
|
|
545
|
+
get visibleIfMainProduct() {
|
|
546
|
+
return this.rawFeature.hideIfMainProduct !== true;
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* Please note that this relates to the visibility in the Configuration tree.
|
|
550
|
+
* It does not affect the visibility of anything in the 3D view at all.
|
|
551
|
+
*/
|
|
552
|
+
get visible() {
|
|
553
|
+
return this.parentProduct.isAdditionalProduct
|
|
554
|
+
? this.visibleIfAdditionalProduct
|
|
555
|
+
: this.visibleIfMainProduct;
|
|
556
|
+
}
|
|
557
|
+
get options() {
|
|
558
|
+
if (this._options === undefined) {
|
|
559
|
+
const hasDuplicateDescription = this.parentProduct.settings.disableMatchOnDescription
|
|
560
|
+
? true
|
|
561
|
+
: someMatch(this.rawFeature.options, (left, right) => left.description.toLowerCase() === right.description.toLowerCase());
|
|
562
|
+
this._options = this.rawFeature.options.map((o) => CfgOption.make(o, this.rawFeatures, hasDuplicateDescription, this, this.parentConfiguration, this.parentProduct, this.rootProduct));
|
|
563
|
+
}
|
|
564
|
+
return this._options;
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Make fresh references to all options on this feature.
|
|
568
|
+
* Also includes currently selected options.
|
|
569
|
+
* @throws Will throw if options have not yet been generated.
|
|
570
|
+
*/
|
|
571
|
+
_freshRefAllOptions(committed) {
|
|
572
|
+
const options = this._options;
|
|
573
|
+
const selectedOptions = this._selectedOptions;
|
|
574
|
+
if (options === undefined) {
|
|
575
|
+
throw new Error("We expect all options to be generated at freshref");
|
|
576
|
+
}
|
|
577
|
+
for (let i = 0; i < options.length; i++) {
|
|
578
|
+
doFreshRefOptionAtIndex(options, i, committed, (freshRef) => {
|
|
579
|
+
const selectedIndex = selectedOptions.findIndex(getOptionFilter(freshRef._internal));
|
|
580
|
+
if (selectedIndex !== -1) {
|
|
581
|
+
selectedOptions[selectedIndex] = freshRef;
|
|
582
|
+
}
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
export class CfgFeature {
|
|
588
|
+
/**
|
|
589
|
+
* Private constructor and make-method because make new ref requires the constructor to
|
|
590
|
+
* take an internal and we don't want those who instantiate CfgFeature to have to be aware
|
|
591
|
+
* of the internal.
|
|
592
|
+
*/
|
|
593
|
+
constructor(_internal) {
|
|
594
|
+
this._internal = _internal;
|
|
595
|
+
this.isBackedBySame = (other) => this._internal === other._internal;
|
|
596
|
+
/**
|
|
597
|
+
* This will find the first option allowing the value, set the value on it and select it.
|
|
598
|
+
* This is an implicit option-select.
|
|
599
|
+
*/
|
|
600
|
+
this.setNumericValue = (val) => __awaiter(this, void 0, void 0, function* () { return yield this._internal.setNumericValue(val); });
|
|
601
|
+
/**
|
|
602
|
+
* Selects the passed Option.
|
|
603
|
+
* Only Options belonging to Features that are "select many" can be deselected.
|
|
604
|
+
* Calling this will cause a validation call to the server.
|
|
605
|
+
*/
|
|
606
|
+
this.selectOption = (option, on) => __awaiter(this, void 0, void 0, function* () {
|
|
607
|
+
return yield this._internal.selectOption(option._internal, on, ProductConfigurationBubbleMode.ValidateAndBubbleSelectedAndApplySyncGroups);
|
|
608
|
+
});
|
|
609
|
+
this.isSelected = (option) => this._internal.isSelected(option._internal);
|
|
610
|
+
this.listenForChange = (l) => this._internal.changeObservable.listen(l);
|
|
611
|
+
this.stopListenForChange = (l) => this._internal.changeObservable.stopListen(l);
|
|
612
|
+
}
|
|
613
|
+
static make(rawFeature, rawFeatures, key, parent, parentConfiguration, parentProduct, rootProduct) {
|
|
614
|
+
return new this(new _CfgFeatureInternal(rawFeature, rawFeatures, key, parent, parentConfiguration, parentProduct, rootProduct));
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Makes an object wrapping the passed object. This is not a clone method,
|
|
618
|
+
* it is a method to make a new outer reference. Like a shallow copy.
|
|
619
|
+
* We use this to help frameworks that are built around using equals to detect change.
|
|
620
|
+
*/
|
|
621
|
+
static _makeNewRefFrom(internal) {
|
|
622
|
+
return new this(internal);
|
|
623
|
+
}
|
|
624
|
+
get parentProduct() {
|
|
625
|
+
return CfgProduct._makeNewRefFrom(this._internal.parentProduct);
|
|
626
|
+
}
|
|
627
|
+
get rootProduct() {
|
|
628
|
+
return CfgProduct._makeNewRefFrom(this._internal.rootProduct);
|
|
629
|
+
}
|
|
630
|
+
get selectionType() {
|
|
631
|
+
return this._internal.selectionType;
|
|
632
|
+
}
|
|
633
|
+
// Unique amongst siblings. Can change. Only use for presentation layer i.e. React.
|
|
634
|
+
get key() {
|
|
635
|
+
return this._internal.key;
|
|
636
|
+
}
|
|
637
|
+
get code() {
|
|
638
|
+
return this._internal.code;
|
|
639
|
+
}
|
|
640
|
+
get groupCode() {
|
|
641
|
+
return this._internal.groupCode;
|
|
642
|
+
}
|
|
643
|
+
get notes() {
|
|
644
|
+
return this._internal.notes;
|
|
645
|
+
}
|
|
646
|
+
/**
|
|
647
|
+
* If true the options in the feature is selected by both sending its code and numeric value
|
|
648
|
+
* when selecting.
|
|
649
|
+
*/
|
|
650
|
+
get isUseNumericValue() {
|
|
651
|
+
return this._internal.isUseNumericValue;
|
|
652
|
+
}
|
|
653
|
+
/** This will read the numeric value of the selected option. */
|
|
654
|
+
get numericValue() {
|
|
655
|
+
return this._internal.numericValue;
|
|
656
|
+
}
|
|
657
|
+
get unit() {
|
|
658
|
+
return this._internal.unit;
|
|
659
|
+
}
|
|
660
|
+
get description() {
|
|
661
|
+
return this._internal.description;
|
|
662
|
+
}
|
|
663
|
+
get omitOnOrder() {
|
|
664
|
+
return this._internal.omitOnOrder;
|
|
665
|
+
}
|
|
666
|
+
get hasUpcharge() {
|
|
667
|
+
return this._internal.hasUpcharge;
|
|
668
|
+
}
|
|
669
|
+
get selectedOptions() {
|
|
670
|
+
return this._internal.selectedOptions;
|
|
671
|
+
}
|
|
672
|
+
/** Are all ancestors up to the CfgProductConfiguration selected? */
|
|
673
|
+
get ancestorsSelected() {
|
|
674
|
+
return this._internal.ancestorsSelected;
|
|
675
|
+
}
|
|
676
|
+
get preview() {
|
|
677
|
+
return this._internal.preview;
|
|
678
|
+
}
|
|
679
|
+
get options() {
|
|
680
|
+
return this._internal.options;
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* Please note that this relates to the visibility in the Configuration tree.
|
|
684
|
+
* It does not affect the visibility of anything in the 3D view at all.
|
|
685
|
+
* Visibility is not inherited. If this is hidden the children
|
|
686
|
+
* of this Feature might still be visible, depending on their settings.
|
|
687
|
+
*/
|
|
688
|
+
get visible() {
|
|
689
|
+
return this._internal.visible;
|
|
690
|
+
}
|
|
691
|
+
}
|