@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,370 +1,370 @@
|
|
|
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 { SelectionType } from "../productConfiguration/CfgFeature.js";
|
|
11
|
-
import { ProductConfigurationBubbleMode, } from "../productConfiguration/CfgOption.js";
|
|
12
|
-
import { SyncGroupsApplyMode } from "./SyncGroupsApplyMode.js";
|
|
13
|
-
import { SyncGroupsPathHelper } from "./SyncGroupsPathHelper.js";
|
|
14
|
-
import { SyncGroupsState } from "./SyncGroupsState.js";
|
|
15
|
-
import { SyncGroupsTransaction } from "./SyncGroupsTransaction.js";
|
|
16
|
-
/* SyncGroup Concepts
|
|
17
|
-
* ==================
|
|
18
|
-
*
|
|
19
|
-
* SyncGroups are a concept in Catalogues that gives the creator the option to attempt to
|
|
20
|
-
* synchronize selections between otherwise independent Features.
|
|
21
|
-
*
|
|
22
|
-
* Each Feature can optionally specify a "Sync Group Code" in the Catalogue. All features with the
|
|
23
|
-
* same sync group code is said to belong to the same Sync Group.
|
|
24
|
-
*
|
|
25
|
-
* In addition, each Feature that is part of a Sync Group should have a "Sync Mode" set which can
|
|
26
|
-
* be either "Read", "Write" or "Read and Write". The code in the SDK refers to them as "pull",
|
|
27
|
-
* "push" and "twoWay" respectively.
|
|
28
|
-
*
|
|
29
|
-
* The current state of all the SyncGroups is stored in a SyncState. The sync state keeps track of
|
|
30
|
-
* each sync group even if no features are currently visible for that sync group code. In that way,
|
|
31
|
-
* the sync state acts like a kind of short-term memory when a user is configuring a product.
|
|
32
|
-
*
|
|
33
|
-
* The sync state is always discarded when you leave or reload a product and is thus always an
|
|
34
|
-
* empty slate when a product is loaded.
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* Best Effort and Conflicts
|
|
38
|
-
* =========================
|
|
39
|
-
*
|
|
40
|
-
* Catalogues had been along for a long time when the Sync Group functionality was added in 2021,
|
|
41
|
-
* and as a result, it is sort of a layer on top off the normal handling of selecting Options on
|
|
42
|
-
* Features and it is applied in a "best effort"-manner.
|
|
43
|
-
*
|
|
44
|
-
* Two or more Features belonging to the same SyncGroup shows the intent that they should be
|
|
45
|
-
* synchronized, with how being controlled by their respective sync mode.
|
|
46
|
-
*
|
|
47
|
-
* However, the Catalogue format is not strict on how you define the features you want tp keep
|
|
48
|
-
* synchronized. They can for example have only partially overlapping domains (i.e. what Options
|
|
49
|
-
* can be selected) or even no overlap at all which means there is no guarantee that they can
|
|
50
|
-
* always stay in sync.
|
|
51
|
-
*
|
|
52
|
-
* Using different sync modes and bringing features into scope also adds complexity as well as
|
|
53
|
-
* power to create "creative" solutions to design problems.
|
|
54
|
-
*
|
|
55
|
-
* There is also a use case for adding a unique sync group code to single features, which signals
|
|
56
|
-
* the intent that the creator wants those features to keep their previously selected values even
|
|
57
|
-
* when they are not currently visible in the current configuration.
|
|
58
|
-
*
|
|
59
|
-
* In the end, it's up to the Catalogue creator to build the products inside their Catalogues so
|
|
60
|
-
* that his or her intentions are met without unexpected side effects.
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
* Technical Details
|
|
64
|
-
* =================
|
|
65
|
-
*
|
|
66
|
-
* Feature types
|
|
67
|
-
* -------------
|
|
68
|
-
*
|
|
69
|
-
* Features can be of three types. (Or that is the model we use in Stage, it seems to hold.)
|
|
70
|
-
*
|
|
71
|
-
* 1. SelectOne Features are like radio buttons, one Option is selected at a time.
|
|
72
|
-
* 2. SelectMany Features ("optional" in Catalogues) are like check boxes, any number of Options
|
|
73
|
-
* are selected at a time.
|
|
74
|
-
* 3. Group Features ("multiple" in Catalogues) act like a grouping mechanism where all the Options
|
|
75
|
-
* are permanently selected. No user interaction allowed.
|
|
76
|
-
*
|
|
77
|
-
* Group Features work like any other Feature without SyncGroups. They are just transparent.
|
|
78
|
-
*
|
|
79
|
-
* SelectOne and SelectMany have fully separated sync states. This means that there is never a sync
|
|
80
|
-
* connection between SelectOne and SelectMany Features even if they have the same Sync Group Code.
|
|
81
|
-
*
|
|
82
|
-
* In other words, SelectOne Features only sync with other SelectOne features and vice versa.
|
|
83
|
-
*
|
|
84
|
-
* On what level sync happens is different between SelectOne and SelectMany Features. For SelectOne
|
|
85
|
-
* we store an option code per SyncGroup. For SelectMany we store "On" or "Off" per option code per
|
|
86
|
-
* SyncGroup.
|
|
87
|
-
*
|
|
88
|
-
* You can say that SelectOne is synced on Feature level, and SelectMany is synced on Option level.
|
|
89
|
-
*
|
|
90
|
-
* Note that the same Feature (as in feature code) can exist in multiple places in a configuration
|
|
91
|
-
* tree. In this context, they are treated as individual Features that just happens to have an
|
|
92
|
-
* identical set of settings in the Catalogue.
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
* Transactions
|
|
96
|
-
* ------------
|
|
97
|
-
*
|
|
98
|
-
* Selecting an Option in a Feature can trigger a bunch of changes, each of them cascading into new
|
|
99
|
-
* features coming into scope and in turn changing other SyncGroups. As far as the user goes these
|
|
100
|
-
* changes is under the hood and is part of a single change triggered by the first selection.
|
|
101
|
-
*
|
|
102
|
-
* This is also how it is implemented. All the changes created by the initial selection are
|
|
103
|
-
* validated and propagated inside a single "transaction". The transaction is considered complete
|
|
104
|
-
* when the last round of changes did not trigger any new changes in the sync state.
|
|
105
|
-
*
|
|
106
|
-
* In every transaction, each SyncGroup is allowed to be updated only once for SelectOne features or
|
|
107
|
-
* once per unique option code in the case of SelectMany features. This is done to ensure that the
|
|
108
|
-
* propagated changes is guaranteed to stabilize over time and eliminates the risk of loops.
|
|
109
|
-
*
|
|
110
|
-
* Since a Feature can only have a single sync group code, this also means that a Feature will be
|
|
111
|
-
* changed at most once during a single transaction.
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
* Applying SyncGroups => Features/Options
|
|
115
|
-
* ---------------------------------------
|
|
116
|
-
*
|
|
117
|
-
* The state of the SyncGroups (as stored in the SyncState) is applied to a Feature/Option under
|
|
118
|
-
* the following conditions:
|
|
119
|
-
* A) The Feature belongs to a SyncGroup, i.e. has a Sync Group Code.
|
|
120
|
-
* B) The feature's sync mode is set to "pull" or "twoWay".
|
|
121
|
-
*
|
|
122
|
-
* ...for SelectOne:
|
|
123
|
-
* C) The SyncState has an option code for this SyncGroup.
|
|
124
|
-
* D) The option code in the SyncState for the SyncGroup is not the Option selected.
|
|
125
|
-
* E) The Feature has an Option with the right option code.
|
|
126
|
-
* F) The Feature has not previously been affected in this transaction, unless it was affected
|
|
127
|
-
* at Feature initialization.
|
|
128
|
-
*
|
|
129
|
-
* ...for SelectMany (done for every Option):
|
|
130
|
-
* C) The SyncState has a value (on or off) for this SyncGroup and option code.
|
|
131
|
-
* D) The Option is on when it should be off or the other way around.
|
|
132
|
-
* E) The Option has not previously been affected in this transaction.
|
|
133
|
-
*
|
|
134
|
-
* Applying the sync state typically happen when:
|
|
135
|
-
* - A user action on a Feature has changed the SyncState.
|
|
136
|
-
* - Features "comes into scope"
|
|
137
|
-
*
|
|
138
|
-
* A feature "coming into scope" refers features "appearing" during for example a Product load, a
|
|
139
|
-
* parent feature changing selected options to expose new children or an Additional Product coming
|
|
140
|
-
* into scope.
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
* Applying Features/Options => SyncGroups
|
|
144
|
-
* ---------------------------------------
|
|
145
|
-
*
|
|
146
|
-
* The Feature/Option is applied to the SyncState under the following conditions:
|
|
147
|
-
* A) The Feature belongs to a SyncGroup, i.e. has a Sync Group Code.
|
|
148
|
-
* B) The feature's sync mode is set to "push" or "twoWay".
|
|
149
|
-
*
|
|
150
|
-
* ...for SelectOne:
|
|
151
|
-
* C) The Option selected is not the option code currently in the SyncState for the SyncGroup.
|
|
152
|
-
* D) The SyncState has not been affected for this SyncGroup in this transaction.
|
|
153
|
-
* E) Any one of the below:
|
|
154
|
-
* - A user actively selects an Option.
|
|
155
|
-
* - There is no option code in the SyncState for this SyncGroup.
|
|
156
|
-
* - The Feature did just come into scope, and the option code in the SyncState for this
|
|
157
|
-
* SyncGroup is not one of the Options in the Feature. The option code is not in the Feature
|
|
158
|
-
* Domain for this Feature that is. So, a Feature appearing which cannot take it's selection
|
|
159
|
-
* from the SyncState will instead write it back.
|
|
160
|
-
*
|
|
161
|
-
* ...for SelectMany (for each Option):
|
|
162
|
-
* C) The SyncState has not been affected for this SyncGroup and option code in this transaction.
|
|
163
|
-
* D) Any one of the below:
|
|
164
|
-
* - A user actively selects or deselects an Option, and the on or off status differs from what
|
|
165
|
-
* is in the SyncState for the SyncGroup and option code.
|
|
166
|
-
* - There is no entry for SyncGroup and option code in the SyncState and the Option is
|
|
167
|
-
* selected. We only implicitly initialize the SyncState for "on" as there is no way to
|
|
168
|
-
* explicitly chose what is default of in Catalogues.
|
|
169
|
-
*
|
|
170
|
-
* This typically happens when:
|
|
171
|
-
* - A user selects or deselects an option.
|
|
172
|
-
* - Features comes into scope, like at Product load, a parent feature changing selected options to
|
|
173
|
-
* expose new children or an Additional Product coming into scope.
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
* Implementation Details
|
|
177
|
-
* ======================
|
|
178
|
-
*
|
|
179
|
-
* There are two entry points into the process. One for when a new Product is loaded, when we want
|
|
180
|
-
* to move it into a synced state. The other is when a user selects or deselects an Option.
|
|
181
|
-
*
|
|
182
|
-
* The process can be thought of as a state machine with three states or stages:
|
|
183
|
-
* A) Select/deselect Option and force to SyncState
|
|
184
|
-
* B) Apply the SyncState onto the Product
|
|
185
|
-
* C) Apply the Product onto the SyncState
|
|
186
|
-
*
|
|
187
|
-
* The entry point for a new Product is C. This makes sense as a new Product has no SyncState at
|
|
188
|
-
* all, so applying the Product onto the SyncState is good start.
|
|
189
|
-
*
|
|
190
|
-
* The entry point for user selection is A.
|
|
191
|
-
*
|
|
192
|
-
* Let's look at how we move between states starting with A.
|
|
193
|
-
*
|
|
194
|
-
* State A will apply the selection onto the Option. It will write to the SyncState. It will then
|
|
195
|
-
* transition to state B.
|
|
196
|
-
*
|
|
197
|
-
* State A is only run once for each user interaction, and not at all for Product Load.
|
|
198
|
-
*
|
|
199
|
-
* State B will apply the SyncState onto Features by recursively going through the configuration
|
|
200
|
-
* tree. Once it finds a Feature/Option which should be changed by the state it:
|
|
201
|
-
* - Checks if it can change it (it can't if the belongs to a so far uninitialized SyncGroup) and
|
|
202
|
-
* if it can then:
|
|
203
|
-
* - Changes the selection on the Feature.
|
|
204
|
-
* - Add the parent Product for the Feature to a list of Products for which a validate call
|
|
205
|
-
* must later be made.
|
|
206
|
-
* - Stop recursing down this branch as the validation call might actually change the structure
|
|
207
|
-
* and children in the branch.
|
|
208
|
-
*
|
|
209
|
-
* Once we have recursed the entire tree the result can be either:
|
|
210
|
-
* - There are Products to validate.
|
|
211
|
-
* Then we do the validations, and once they are done we move back into state B. We run it again
|
|
212
|
-
* as we did not recurse all the way down the branches that changed value. With each run, we will
|
|
213
|
-
* get further out on the branches until finally reaching the end.
|
|
214
|
-
* - There are no Products to validate.
|
|
215
|
-
* This means that there were no Features which we could apply the SyncState onto. We now move
|
|
216
|
-
* into state C as the selection changes may have brought new Features into scope, Features which
|
|
217
|
-
* might write to the SyncState.
|
|
218
|
-
*
|
|
219
|
-
* State C will apply Features onto the SyncState. It will recursively go through the configuration
|
|
220
|
-
* tree. When it's done one of two has happened:
|
|
221
|
-
* - There was a write to the SyncState.
|
|
222
|
-
* We then move back to state B, as this change in the SyncState could open up things in B. For
|
|
223
|
-
* example a Feature that could not pull from the SyncState before might be able to do that now.
|
|
224
|
-
* - There was no write to the SyncState.
|
|
225
|
-
* All is calm. We are done. The settling of the SyncState vs configuration is done. Things are
|
|
226
|
-
* as in sync as they will be. This is the exit.
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
-
* Side Notes
|
|
230
|
-
* ==========
|
|
231
|
-
*
|
|
232
|
-
* _CfgProductInternal, _CfgFeatureInternal and _CfgOptionInternal are used in Sets and Maps in the
|
|
233
|
-
* code. These objects are only created once for the data they represent. That is, even if a Feature
|
|
234
|
-
* goes in or out of scope the object is the same. This is not true for their wrapper classer
|
|
235
|
-
* CfgProduct, CfgFeature and CfgOption. Those are frequently replaced while running.
|
|
236
|
-
*
|
|
237
|
-
*/
|
|
238
|
-
/**
|
|
239
|
-
* Send to root for the passed option and any sibling which is selected, provided the Feature
|
|
240
|
-
* is SelectOne. (As that one (should only be one) can be assumed to be affected).
|
|
241
|
-
*/
|
|
242
|
-
function notifyOptionAndSelectedSiblings(option) {
|
|
243
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
244
|
-
const committed = false;
|
|
245
|
-
const parentFeature = option.parent;
|
|
246
|
-
if (parentFeature.selectionType === SelectionType.SelectOne) {
|
|
247
|
-
// These only need to be OneLevel, as the final is ToRoot and they share their parent.
|
|
248
|
-
yield Promise.all(option.parent.selectedOptions.map((o) => parentFeature._childHasChanged(o._internal, ProductConfigurationBubbleMode.OneLevel, committed)));
|
|
249
|
-
}
|
|
250
|
-
yield parentFeature._childHasChanged(option, ProductConfigurationBubbleMode.ToRoot, committed);
|
|
251
|
-
});
|
|
252
|
-
}
|
|
253
|
-
/**
|
|
254
|
-
* Is used to apply the SyncGroups functionality on the Configuration and the other way around.
|
|
255
|
-
* It also keeps the SyncState.
|
|
256
|
-
*/
|
|
257
|
-
export class SyncGroupsHandler {
|
|
258
|
-
constructor(_syncState, updateMode, _loadingObservable) {
|
|
259
|
-
this._syncState = _syncState;
|
|
260
|
-
this.updateMode = updateMode;
|
|
261
|
-
this._loadingObservable = _loadingObservable;
|
|
262
|
-
}
|
|
263
|
-
/**
|
|
264
|
-
* @param verboseLogging Set to true to get verbose sync state changes logged to the console.
|
|
265
|
-
*/
|
|
266
|
-
static make(updateMode = SyncGroupsApplyMode.Strict, loadingObservable, initial, verboseLogging = false) {
|
|
267
|
-
return new SyncGroupsHandler(new SyncGroupsState(verboseLogging, initial), updateMode, loadingObservable);
|
|
268
|
-
}
|
|
269
|
-
/** Please note that clones will use the same loadingObservable as their source. */
|
|
270
|
-
clone() {
|
|
271
|
-
return new SyncGroupsHandler(this._syncState.clone(), this.updateMode, this._loadingObservable);
|
|
272
|
-
}
|
|
273
|
-
getCompactSyncGroupState() {
|
|
274
|
-
return this._syncState.getCompact();
|
|
275
|
-
}
|
|
276
|
-
/** Overwrites the sync state */
|
|
277
|
-
setCompactSyncGroupState(s) {
|
|
278
|
-
this._syncState.setCompact(s);
|
|
279
|
-
}
|
|
280
|
-
get verboseLogging() {
|
|
281
|
-
return this._syncState.verboseLogging;
|
|
282
|
-
}
|
|
283
|
-
set verboseLogging(v) {
|
|
284
|
-
this._syncState.verboseLogging = v;
|
|
285
|
-
}
|
|
286
|
-
/**
|
|
287
|
-
* Used to initially apply the sync state onto a new product so that it is "in sync"
|
|
288
|
-
* and to reapply the sync state when an optional additional product is selected.
|
|
289
|
-
*/
|
|
290
|
-
init(product, productLoader) {
|
|
291
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
292
|
-
const transaction = yield this.newTransaction(product, productLoader, true);
|
|
293
|
-
try {
|
|
294
|
-
yield transaction.applyRootProduct();
|
|
295
|
-
yield this.commitTransaction(transaction);
|
|
296
|
-
}
|
|
297
|
-
finally {
|
|
298
|
-
this.closeTransaction(transaction);
|
|
299
|
-
}
|
|
300
|
-
});
|
|
301
|
-
}
|
|
302
|
-
/**
|
|
303
|
-
* Used when an Option is selected or deselected to apply all consequences of the sync groups.
|
|
304
|
-
* Can cause multiple extra validation calls to the server.
|
|
305
|
-
*/
|
|
306
|
-
selectOption(product, option, on, productLoader) {
|
|
307
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
308
|
-
if (product.parent !== undefined) {
|
|
309
|
-
console.info("Normally the product passed shall be the root product");
|
|
310
|
-
}
|
|
311
|
-
yield this.setPending(option);
|
|
312
|
-
const transaction = yield this.newTransaction(product, productLoader, false);
|
|
313
|
-
try {
|
|
314
|
-
const change = yield transaction.selectOption(SyncGroupsPathHelper.getPath(option), on);
|
|
315
|
-
// Always commit the transaction. The change-result above only tells if the
|
|
316
|
-
// configuration has changed. The SyncState may however also have changed.
|
|
317
|
-
yield this.commitTransaction(transaction);
|
|
318
|
-
return change;
|
|
319
|
-
}
|
|
320
|
-
finally {
|
|
321
|
-
if (this._pending === option) {
|
|
322
|
-
yield this.setPending(undefined);
|
|
323
|
-
}
|
|
324
|
-
this.closeTransaction(transaction);
|
|
325
|
-
}
|
|
326
|
-
});
|
|
327
|
-
}
|
|
328
|
-
setPending(newPending) {
|
|
329
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
330
|
-
const oldPending = this._pending;
|
|
331
|
-
this._pending = newPending;
|
|
332
|
-
if (oldPending !== undefined) {
|
|
333
|
-
yield notifyOptionAndSelectedSiblings(oldPending);
|
|
334
|
-
}
|
|
335
|
-
if (newPending !== undefined) {
|
|
336
|
-
yield notifyOptionAndSelectedSiblings(newPending);
|
|
337
|
-
}
|
|
338
|
-
});
|
|
339
|
-
}
|
|
340
|
-
get pending() {
|
|
341
|
-
return this._pending;
|
|
342
|
-
}
|
|
343
|
-
newTransaction(product, productLoader, assumeNoStartProductState) {
|
|
344
|
-
var _a;
|
|
345
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
346
|
-
if (this._currentTransaction !== undefined) {
|
|
347
|
-
this.closeTransaction(this._currentTransaction);
|
|
348
|
-
}
|
|
349
|
-
const transaction = yield SyncGroupsTransaction.make(this._syncState, this.updateMode, product, productLoader, assumeNoStartProductState);
|
|
350
|
-
this._currentTransaction = transaction;
|
|
351
|
-
// The transaction object is used as loading token
|
|
352
|
-
(_a = this._loadingObservable) === null || _a === void 0 ? void 0 : _a.startChildLoading(transaction);
|
|
353
|
-
return transaction;
|
|
354
|
-
});
|
|
355
|
-
}
|
|
356
|
-
closeTransaction(transaction) {
|
|
357
|
-
var _a;
|
|
358
|
-
transaction.close();
|
|
359
|
-
(_a = this._loadingObservable) === null || _a === void 0 ? void 0 : _a.stopChildLoading(transaction);
|
|
360
|
-
}
|
|
361
|
-
commitTransaction(transaction) {
|
|
362
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
363
|
-
if (transaction.isClosed) {
|
|
364
|
-
return;
|
|
365
|
-
}
|
|
366
|
-
yield transaction.commit();
|
|
367
|
-
this.closeTransaction(transaction);
|
|
368
|
-
});
|
|
369
|
-
}
|
|
370
|
-
}
|
|
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 { SelectionType } from "../productConfiguration/CfgFeature.js";
|
|
11
|
+
import { ProductConfigurationBubbleMode, } from "../productConfiguration/CfgOption.js";
|
|
12
|
+
import { SyncGroupsApplyMode } from "./SyncGroupsApplyMode.js";
|
|
13
|
+
import { SyncGroupsPathHelper } from "./SyncGroupsPathHelper.js";
|
|
14
|
+
import { SyncGroupsState } from "./SyncGroupsState.js";
|
|
15
|
+
import { SyncGroupsTransaction } from "./SyncGroupsTransaction.js";
|
|
16
|
+
/* SyncGroup Concepts
|
|
17
|
+
* ==================
|
|
18
|
+
*
|
|
19
|
+
* SyncGroups are a concept in Catalogues that gives the creator the option to attempt to
|
|
20
|
+
* synchronize selections between otherwise independent Features.
|
|
21
|
+
*
|
|
22
|
+
* Each Feature can optionally specify a "Sync Group Code" in the Catalogue. All features with the
|
|
23
|
+
* same sync group code is said to belong to the same Sync Group.
|
|
24
|
+
*
|
|
25
|
+
* In addition, each Feature that is part of a Sync Group should have a "Sync Mode" set which can
|
|
26
|
+
* be either "Read", "Write" or "Read and Write". The code in the SDK refers to them as "pull",
|
|
27
|
+
* "push" and "twoWay" respectively.
|
|
28
|
+
*
|
|
29
|
+
* The current state of all the SyncGroups is stored in a SyncState. The sync state keeps track of
|
|
30
|
+
* each sync group even if no features are currently visible for that sync group code. In that way,
|
|
31
|
+
* the sync state acts like a kind of short-term memory when a user is configuring a product.
|
|
32
|
+
*
|
|
33
|
+
* The sync state is always discarded when you leave or reload a product and is thus always an
|
|
34
|
+
* empty slate when a product is loaded.
|
|
35
|
+
*
|
|
36
|
+
*
|
|
37
|
+
* Best Effort and Conflicts
|
|
38
|
+
* =========================
|
|
39
|
+
*
|
|
40
|
+
* Catalogues had been along for a long time when the Sync Group functionality was added in 2021,
|
|
41
|
+
* and as a result, it is sort of a layer on top off the normal handling of selecting Options on
|
|
42
|
+
* Features and it is applied in a "best effort"-manner.
|
|
43
|
+
*
|
|
44
|
+
* Two or more Features belonging to the same SyncGroup shows the intent that they should be
|
|
45
|
+
* synchronized, with how being controlled by their respective sync mode.
|
|
46
|
+
*
|
|
47
|
+
* However, the Catalogue format is not strict on how you define the features you want tp keep
|
|
48
|
+
* synchronized. They can for example have only partially overlapping domains (i.e. what Options
|
|
49
|
+
* can be selected) or even no overlap at all which means there is no guarantee that they can
|
|
50
|
+
* always stay in sync.
|
|
51
|
+
*
|
|
52
|
+
* Using different sync modes and bringing features into scope also adds complexity as well as
|
|
53
|
+
* power to create "creative" solutions to design problems.
|
|
54
|
+
*
|
|
55
|
+
* There is also a use case for adding a unique sync group code to single features, which signals
|
|
56
|
+
* the intent that the creator wants those features to keep their previously selected values even
|
|
57
|
+
* when they are not currently visible in the current configuration.
|
|
58
|
+
*
|
|
59
|
+
* In the end, it's up to the Catalogue creator to build the products inside their Catalogues so
|
|
60
|
+
* that his or her intentions are met without unexpected side effects.
|
|
61
|
+
*
|
|
62
|
+
*
|
|
63
|
+
* Technical Details
|
|
64
|
+
* =================
|
|
65
|
+
*
|
|
66
|
+
* Feature types
|
|
67
|
+
* -------------
|
|
68
|
+
*
|
|
69
|
+
* Features can be of three types. (Or that is the model we use in Stage, it seems to hold.)
|
|
70
|
+
*
|
|
71
|
+
* 1. SelectOne Features are like radio buttons, one Option is selected at a time.
|
|
72
|
+
* 2. SelectMany Features ("optional" in Catalogues) are like check boxes, any number of Options
|
|
73
|
+
* are selected at a time.
|
|
74
|
+
* 3. Group Features ("multiple" in Catalogues) act like a grouping mechanism where all the Options
|
|
75
|
+
* are permanently selected. No user interaction allowed.
|
|
76
|
+
*
|
|
77
|
+
* Group Features work like any other Feature without SyncGroups. They are just transparent.
|
|
78
|
+
*
|
|
79
|
+
* SelectOne and SelectMany have fully separated sync states. This means that there is never a sync
|
|
80
|
+
* connection between SelectOne and SelectMany Features even if they have the same Sync Group Code.
|
|
81
|
+
*
|
|
82
|
+
* In other words, SelectOne Features only sync with other SelectOne features and vice versa.
|
|
83
|
+
*
|
|
84
|
+
* On what level sync happens is different between SelectOne and SelectMany Features. For SelectOne
|
|
85
|
+
* we store an option code per SyncGroup. For SelectMany we store "On" or "Off" per option code per
|
|
86
|
+
* SyncGroup.
|
|
87
|
+
*
|
|
88
|
+
* You can say that SelectOne is synced on Feature level, and SelectMany is synced on Option level.
|
|
89
|
+
*
|
|
90
|
+
* Note that the same Feature (as in feature code) can exist in multiple places in a configuration
|
|
91
|
+
* tree. In this context, they are treated as individual Features that just happens to have an
|
|
92
|
+
* identical set of settings in the Catalogue.
|
|
93
|
+
*
|
|
94
|
+
*
|
|
95
|
+
* Transactions
|
|
96
|
+
* ------------
|
|
97
|
+
*
|
|
98
|
+
* Selecting an Option in a Feature can trigger a bunch of changes, each of them cascading into new
|
|
99
|
+
* features coming into scope and in turn changing other SyncGroups. As far as the user goes these
|
|
100
|
+
* changes is under the hood and is part of a single change triggered by the first selection.
|
|
101
|
+
*
|
|
102
|
+
* This is also how it is implemented. All the changes created by the initial selection are
|
|
103
|
+
* validated and propagated inside a single "transaction". The transaction is considered complete
|
|
104
|
+
* when the last round of changes did not trigger any new changes in the sync state.
|
|
105
|
+
*
|
|
106
|
+
* In every transaction, each SyncGroup is allowed to be updated only once for SelectOne features or
|
|
107
|
+
* once per unique option code in the case of SelectMany features. This is done to ensure that the
|
|
108
|
+
* propagated changes is guaranteed to stabilize over time and eliminates the risk of loops.
|
|
109
|
+
*
|
|
110
|
+
* Since a Feature can only have a single sync group code, this also means that a Feature will be
|
|
111
|
+
* changed at most once during a single transaction.
|
|
112
|
+
*
|
|
113
|
+
*
|
|
114
|
+
* Applying SyncGroups => Features/Options
|
|
115
|
+
* ---------------------------------------
|
|
116
|
+
*
|
|
117
|
+
* The state of the SyncGroups (as stored in the SyncState) is applied to a Feature/Option under
|
|
118
|
+
* the following conditions:
|
|
119
|
+
* A) The Feature belongs to a SyncGroup, i.e. has a Sync Group Code.
|
|
120
|
+
* B) The feature's sync mode is set to "pull" or "twoWay".
|
|
121
|
+
*
|
|
122
|
+
* ...for SelectOne:
|
|
123
|
+
* C) The SyncState has an option code for this SyncGroup.
|
|
124
|
+
* D) The option code in the SyncState for the SyncGroup is not the Option selected.
|
|
125
|
+
* E) The Feature has an Option with the right option code.
|
|
126
|
+
* F) The Feature has not previously been affected in this transaction, unless it was affected
|
|
127
|
+
* at Feature initialization.
|
|
128
|
+
*
|
|
129
|
+
* ...for SelectMany (done for every Option):
|
|
130
|
+
* C) The SyncState has a value (on or off) for this SyncGroup and option code.
|
|
131
|
+
* D) The Option is on when it should be off or the other way around.
|
|
132
|
+
* E) The Option has not previously been affected in this transaction.
|
|
133
|
+
*
|
|
134
|
+
* Applying the sync state typically happen when:
|
|
135
|
+
* - A user action on a Feature has changed the SyncState.
|
|
136
|
+
* - Features "comes into scope"
|
|
137
|
+
*
|
|
138
|
+
* A feature "coming into scope" refers features "appearing" during for example a Product load, a
|
|
139
|
+
* parent feature changing selected options to expose new children or an Additional Product coming
|
|
140
|
+
* into scope.
|
|
141
|
+
*
|
|
142
|
+
*
|
|
143
|
+
* Applying Features/Options => SyncGroups
|
|
144
|
+
* ---------------------------------------
|
|
145
|
+
*
|
|
146
|
+
* The Feature/Option is applied to the SyncState under the following conditions:
|
|
147
|
+
* A) The Feature belongs to a SyncGroup, i.e. has a Sync Group Code.
|
|
148
|
+
* B) The feature's sync mode is set to "push" or "twoWay".
|
|
149
|
+
*
|
|
150
|
+
* ...for SelectOne:
|
|
151
|
+
* C) The Option selected is not the option code currently in the SyncState for the SyncGroup.
|
|
152
|
+
* D) The SyncState has not been affected for this SyncGroup in this transaction.
|
|
153
|
+
* E) Any one of the below:
|
|
154
|
+
* - A user actively selects an Option.
|
|
155
|
+
* - There is no option code in the SyncState for this SyncGroup.
|
|
156
|
+
* - The Feature did just come into scope, and the option code in the SyncState for this
|
|
157
|
+
* SyncGroup is not one of the Options in the Feature. The option code is not in the Feature
|
|
158
|
+
* Domain for this Feature that is. So, a Feature appearing which cannot take it's selection
|
|
159
|
+
* from the SyncState will instead write it back.
|
|
160
|
+
*
|
|
161
|
+
* ...for SelectMany (for each Option):
|
|
162
|
+
* C) The SyncState has not been affected for this SyncGroup and option code in this transaction.
|
|
163
|
+
* D) Any one of the below:
|
|
164
|
+
* - A user actively selects or deselects an Option, and the on or off status differs from what
|
|
165
|
+
* is in the SyncState for the SyncGroup and option code.
|
|
166
|
+
* - There is no entry for SyncGroup and option code in the SyncState and the Option is
|
|
167
|
+
* selected. We only implicitly initialize the SyncState for "on" as there is no way to
|
|
168
|
+
* explicitly chose what is default of in Catalogues.
|
|
169
|
+
*
|
|
170
|
+
* This typically happens when:
|
|
171
|
+
* - A user selects or deselects an option.
|
|
172
|
+
* - Features comes into scope, like at Product load, a parent feature changing selected options to
|
|
173
|
+
* expose new children or an Additional Product coming into scope.
|
|
174
|
+
*
|
|
175
|
+
*
|
|
176
|
+
* Implementation Details
|
|
177
|
+
* ======================
|
|
178
|
+
*
|
|
179
|
+
* There are two entry points into the process. One for when a new Product is loaded, when we want
|
|
180
|
+
* to move it into a synced state. The other is when a user selects or deselects an Option.
|
|
181
|
+
*
|
|
182
|
+
* The process can be thought of as a state machine with three states or stages:
|
|
183
|
+
* A) Select/deselect Option and force to SyncState
|
|
184
|
+
* B) Apply the SyncState onto the Product
|
|
185
|
+
* C) Apply the Product onto the SyncState
|
|
186
|
+
*
|
|
187
|
+
* The entry point for a new Product is C. This makes sense as a new Product has no SyncState at
|
|
188
|
+
* all, so applying the Product onto the SyncState is good start.
|
|
189
|
+
*
|
|
190
|
+
* The entry point for user selection is A.
|
|
191
|
+
*
|
|
192
|
+
* Let's look at how we move between states starting with A.
|
|
193
|
+
*
|
|
194
|
+
* State A will apply the selection onto the Option. It will write to the SyncState. It will then
|
|
195
|
+
* transition to state B.
|
|
196
|
+
*
|
|
197
|
+
* State A is only run once for each user interaction, and not at all for Product Load.
|
|
198
|
+
*
|
|
199
|
+
* State B will apply the SyncState onto Features by recursively going through the configuration
|
|
200
|
+
* tree. Once it finds a Feature/Option which should be changed by the state it:
|
|
201
|
+
* - Checks if it can change it (it can't if the belongs to a so far uninitialized SyncGroup) and
|
|
202
|
+
* if it can then:
|
|
203
|
+
* - Changes the selection on the Feature.
|
|
204
|
+
* - Add the parent Product for the Feature to a list of Products for which a validate call
|
|
205
|
+
* must later be made.
|
|
206
|
+
* - Stop recursing down this branch as the validation call might actually change the structure
|
|
207
|
+
* and children in the branch.
|
|
208
|
+
*
|
|
209
|
+
* Once we have recursed the entire tree the result can be either:
|
|
210
|
+
* - There are Products to validate.
|
|
211
|
+
* Then we do the validations, and once they are done we move back into state B. We run it again
|
|
212
|
+
* as we did not recurse all the way down the branches that changed value. With each run, we will
|
|
213
|
+
* get further out on the branches until finally reaching the end.
|
|
214
|
+
* - There are no Products to validate.
|
|
215
|
+
* This means that there were no Features which we could apply the SyncState onto. We now move
|
|
216
|
+
* into state C as the selection changes may have brought new Features into scope, Features which
|
|
217
|
+
* might write to the SyncState.
|
|
218
|
+
*
|
|
219
|
+
* State C will apply Features onto the SyncState. It will recursively go through the configuration
|
|
220
|
+
* tree. When it's done one of two has happened:
|
|
221
|
+
* - There was a write to the SyncState.
|
|
222
|
+
* We then move back to state B, as this change in the SyncState could open up things in B. For
|
|
223
|
+
* example a Feature that could not pull from the SyncState before might be able to do that now.
|
|
224
|
+
* - There was no write to the SyncState.
|
|
225
|
+
* All is calm. We are done. The settling of the SyncState vs configuration is done. Things are
|
|
226
|
+
* as in sync as they will be. This is the exit.
|
|
227
|
+
*
|
|
228
|
+
*
|
|
229
|
+
* Side Notes
|
|
230
|
+
* ==========
|
|
231
|
+
*
|
|
232
|
+
* _CfgProductInternal, _CfgFeatureInternal and _CfgOptionInternal are used in Sets and Maps in the
|
|
233
|
+
* code. These objects are only created once for the data they represent. That is, even if a Feature
|
|
234
|
+
* goes in or out of scope the object is the same. This is not true for their wrapper classer
|
|
235
|
+
* CfgProduct, CfgFeature and CfgOption. Those are frequently replaced while running.
|
|
236
|
+
*
|
|
237
|
+
*/
|
|
238
|
+
/**
|
|
239
|
+
* Send to root for the passed option and any sibling which is selected, provided the Feature
|
|
240
|
+
* is SelectOne. (As that one (should only be one) can be assumed to be affected).
|
|
241
|
+
*/
|
|
242
|
+
function notifyOptionAndSelectedSiblings(option) {
|
|
243
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
244
|
+
const committed = false;
|
|
245
|
+
const parentFeature = option.parent;
|
|
246
|
+
if (parentFeature.selectionType === SelectionType.SelectOne) {
|
|
247
|
+
// These only need to be OneLevel, as the final is ToRoot and they share their parent.
|
|
248
|
+
yield Promise.all(option.parent.selectedOptions.map((o) => parentFeature._childHasChanged(o._internal, ProductConfigurationBubbleMode.OneLevel, committed)));
|
|
249
|
+
}
|
|
250
|
+
yield parentFeature._childHasChanged(option, ProductConfigurationBubbleMode.ToRoot, committed);
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Is used to apply the SyncGroups functionality on the Configuration and the other way around.
|
|
255
|
+
* It also keeps the SyncState.
|
|
256
|
+
*/
|
|
257
|
+
export class SyncGroupsHandler {
|
|
258
|
+
constructor(_syncState, updateMode, _loadingObservable) {
|
|
259
|
+
this._syncState = _syncState;
|
|
260
|
+
this.updateMode = updateMode;
|
|
261
|
+
this._loadingObservable = _loadingObservable;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* @param verboseLogging Set to true to get verbose sync state changes logged to the console.
|
|
265
|
+
*/
|
|
266
|
+
static make(updateMode = SyncGroupsApplyMode.Strict, loadingObservable, initial, verboseLogging = false) {
|
|
267
|
+
return new SyncGroupsHandler(new SyncGroupsState(verboseLogging, initial), updateMode, loadingObservable);
|
|
268
|
+
}
|
|
269
|
+
/** Please note that clones will use the same loadingObservable as their source. */
|
|
270
|
+
clone() {
|
|
271
|
+
return new SyncGroupsHandler(this._syncState.clone(), this.updateMode, this._loadingObservable);
|
|
272
|
+
}
|
|
273
|
+
getCompactSyncGroupState() {
|
|
274
|
+
return this._syncState.getCompact();
|
|
275
|
+
}
|
|
276
|
+
/** Overwrites the sync state */
|
|
277
|
+
setCompactSyncGroupState(s) {
|
|
278
|
+
this._syncState.setCompact(s);
|
|
279
|
+
}
|
|
280
|
+
get verboseLogging() {
|
|
281
|
+
return this._syncState.verboseLogging;
|
|
282
|
+
}
|
|
283
|
+
set verboseLogging(v) {
|
|
284
|
+
this._syncState.verboseLogging = v;
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Used to initially apply the sync state onto a new product so that it is "in sync"
|
|
288
|
+
* and to reapply the sync state when an optional additional product is selected.
|
|
289
|
+
*/
|
|
290
|
+
init(product, productLoader) {
|
|
291
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
292
|
+
const transaction = yield this.newTransaction(product, productLoader, true);
|
|
293
|
+
try {
|
|
294
|
+
yield transaction.applyRootProduct();
|
|
295
|
+
yield this.commitTransaction(transaction);
|
|
296
|
+
}
|
|
297
|
+
finally {
|
|
298
|
+
this.closeTransaction(transaction);
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Used when an Option is selected or deselected to apply all consequences of the sync groups.
|
|
304
|
+
* Can cause multiple extra validation calls to the server.
|
|
305
|
+
*/
|
|
306
|
+
selectOption(product, option, on, productLoader) {
|
|
307
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
308
|
+
if (product.parent !== undefined) {
|
|
309
|
+
console.info("Normally the product passed shall be the root product");
|
|
310
|
+
}
|
|
311
|
+
yield this.setPending(option);
|
|
312
|
+
const transaction = yield this.newTransaction(product, productLoader, false);
|
|
313
|
+
try {
|
|
314
|
+
const change = yield transaction.selectOption(SyncGroupsPathHelper.getPath(option), on);
|
|
315
|
+
// Always commit the transaction. The change-result above only tells if the
|
|
316
|
+
// configuration has changed. The SyncState may however also have changed.
|
|
317
|
+
yield this.commitTransaction(transaction);
|
|
318
|
+
return change;
|
|
319
|
+
}
|
|
320
|
+
finally {
|
|
321
|
+
if (this._pending === option) {
|
|
322
|
+
yield this.setPending(undefined);
|
|
323
|
+
}
|
|
324
|
+
this.closeTransaction(transaction);
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
setPending(newPending) {
|
|
329
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
330
|
+
const oldPending = this._pending;
|
|
331
|
+
this._pending = newPending;
|
|
332
|
+
if (oldPending !== undefined) {
|
|
333
|
+
yield notifyOptionAndSelectedSiblings(oldPending);
|
|
334
|
+
}
|
|
335
|
+
if (newPending !== undefined) {
|
|
336
|
+
yield notifyOptionAndSelectedSiblings(newPending);
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
get pending() {
|
|
341
|
+
return this._pending;
|
|
342
|
+
}
|
|
343
|
+
newTransaction(product, productLoader, assumeNoStartProductState) {
|
|
344
|
+
var _a;
|
|
345
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
346
|
+
if (this._currentTransaction !== undefined) {
|
|
347
|
+
this.closeTransaction(this._currentTransaction);
|
|
348
|
+
}
|
|
349
|
+
const transaction = yield SyncGroupsTransaction.make(this._syncState, this.updateMode, product, productLoader, assumeNoStartProductState);
|
|
350
|
+
this._currentTransaction = transaction;
|
|
351
|
+
// The transaction object is used as loading token
|
|
352
|
+
(_a = this._loadingObservable) === null || _a === void 0 ? void 0 : _a.startChildLoading(transaction);
|
|
353
|
+
return transaction;
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
closeTransaction(transaction) {
|
|
357
|
+
var _a;
|
|
358
|
+
transaction.close();
|
|
359
|
+
(_a = this._loadingObservable) === null || _a === void 0 ? void 0 : _a.stopChildLoading(transaction);
|
|
360
|
+
}
|
|
361
|
+
commitTransaction(transaction) {
|
|
362
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
363
|
+
if (transaction.isClosed) {
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
yield transaction.commit();
|
|
367
|
+
this.closeTransaction(transaction);
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
}
|