@configuratorware/configurator-frontendgui 1.57.8 → 1.57.10
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.
|
@@ -39,6 +39,10 @@ function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object
|
|
|
39
39
|
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
|
|
40
40
|
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
|
41
41
|
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
|
|
42
|
+
function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
|
|
43
|
+
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
|
|
44
|
+
function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
|
|
45
|
+
function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
|
|
42
46
|
function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; }
|
|
43
47
|
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
|
|
44
48
|
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
|
|
@@ -243,18 +247,35 @@ var applyVariantAmountPreSelection = exports.applyVariantAmountPreSelection = fu
|
|
|
243
247
|
var designerPreSelection = (0, _configuration.getConf)('designerPreSelection', {});
|
|
244
248
|
var itemAmounts = designerPreSelection.itemAmounts;
|
|
245
249
|
if (itemAmounts) {
|
|
246
|
-
var children = (0, _Selectors.getChildren)(configurator).map(function (variant) {
|
|
247
|
-
return variant
|
|
250
|
+
var children = (0, _flatten["default"])((0, _Selectors.getChildren)(configurator).map(function (variant) {
|
|
251
|
+
return Object.values((0, _Selectors.getDeepestChildIdentifiers)(variant));
|
|
252
|
+
}));
|
|
253
|
+
var applicableAmounts = Object.keys(itemAmounts).filter(function (identifier) {
|
|
254
|
+
return children.indexOf(identifier) !== -1 && +itemAmounts[identifier] > 0;
|
|
255
|
+
}).map(function (identifier) {
|
|
256
|
+
return [identifier, +itemAmounts[identifier]];
|
|
248
257
|
});
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
}
|
|
258
|
+
|
|
259
|
+
// designerPreSelection is parsed once from the URL and stays in conf for the whole session, so
|
|
260
|
+
// without consuming it here it would re-apply (and re-clear existing amounts) on every later
|
|
261
|
+
// configuration load, e.g. switching the configuration variant or loading a saved configuration
|
|
262
|
+
(0, _configuration.setConf)('designerPreSelection.itemAmounts', undefined);
|
|
263
|
+
if (applicableAmounts.length === 0) {
|
|
264
|
+
return;
|
|
257
265
|
}
|
|
266
|
+
|
|
267
|
+
// only the explicitly passed amounts should apply, so clear whatever the item already had - by
|
|
268
|
+
// the keys actually selected, since a selected amount can be stored at any item level, not just
|
|
269
|
+
// on the deepest children
|
|
270
|
+
Object.keys((0, _Selectors.getSelectedAmounts)(configurator) || {}).forEach(function (identifier) {
|
|
271
|
+
return _ServiceLocator.Services.configurator.setSelectedAmount(identifier, 0);
|
|
272
|
+
});
|
|
273
|
+
applicableAmounts.forEach(function (_ref4) {
|
|
274
|
+
var _ref5 = _slicedToArray(_ref4, 2),
|
|
275
|
+
identifier = _ref5[0],
|
|
276
|
+
amount = _ref5[1];
|
|
277
|
+
return _ServiceLocator.Services.configurator.setSelectedAmount(identifier, amount);
|
|
278
|
+
});
|
|
258
279
|
}
|
|
259
280
|
};
|
|
260
281
|
var selectOptionclassification = exports.selectOptionclassification = function selectOptionclassification(optionclassification) {
|
|
@@ -413,7 +434,7 @@ var receiveCodesnippets = exports.receiveCodesnippets = function receiveCodesnip
|
|
|
413
434
|
};
|
|
414
435
|
var fetchConfigurationForDesignerProductPreview = exports.fetchConfigurationForDesignerProductPreview = function fetchConfigurationForDesignerProductPreview() {
|
|
415
436
|
return /*#__PURE__*/function () {
|
|
416
|
-
var
|
|
437
|
+
var _ref6 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee3(dispatch, getState) {
|
|
417
438
|
var itemIdentifier, _getAvailableConfigur2, designerEnabled, initialDesignData;
|
|
418
439
|
return _regeneratorRuntime().wrap(function _callee3$(_context3) {
|
|
419
440
|
while (1) switch (_context3.prev = _context3.next) {
|
|
@@ -448,7 +469,7 @@ var fetchConfigurationForDesignerProductPreview = exports.fetchConfigurationForD
|
|
|
448
469
|
}, _callee3);
|
|
449
470
|
}));
|
|
450
471
|
return function (_x4, _x5) {
|
|
451
|
-
return
|
|
472
|
+
return _ref6.apply(this, arguments);
|
|
452
473
|
};
|
|
453
474
|
}();
|
|
454
475
|
};
|
|
@@ -866,10 +887,10 @@ var getSwitchOptionData = function getSwitchOptionData(optionsList) {
|
|
|
866
887
|
try {
|
|
867
888
|
for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
|
|
868
889
|
var component = _step4.value;
|
|
869
|
-
switchOptionData[component.identifier] = component.selectedoptions.map(function (
|
|
870
|
-
var identifier =
|
|
871
|
-
amount =
|
|
872
|
-
inputText =
|
|
890
|
+
switchOptionData[component.identifier] = component.selectedoptions.map(function (_ref7) {
|
|
891
|
+
var identifier = _ref7.identifier,
|
|
892
|
+
amount = _ref7.amount,
|
|
893
|
+
inputText = _ref7.inputText;
|
|
873
894
|
return {
|
|
874
895
|
identifier: identifier,
|
|
875
896
|
inputText: inputText,
|
|
@@ -902,7 +923,7 @@ var viewOptionDetails = exports.viewOptionDetails = function viewOptionDetails(o
|
|
|
902
923
|
};
|
|
903
924
|
var loadConfiguration = exports.loadConfiguration = function loadConfiguration() {
|
|
904
925
|
return /*#__PURE__*/function () {
|
|
905
|
-
var
|
|
926
|
+
var _ref8 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee4(dispatch, getState) {
|
|
906
927
|
var loadCode, action;
|
|
907
928
|
return _regeneratorRuntime().wrap(function _callee4$(_context4) {
|
|
908
929
|
while (1) switch (_context4.prev = _context4.next) {
|
|
@@ -939,7 +960,7 @@ var loadConfiguration = exports.loadConfiguration = function loadConfiguration()
|
|
|
939
960
|
}, _callee4, null, [[5, 13, 17, 20]]);
|
|
940
961
|
}));
|
|
941
962
|
return function (_x6, _x7) {
|
|
942
|
-
return
|
|
963
|
+
return _ref8.apply(this, arguments);
|
|
943
964
|
};
|
|
944
965
|
}();
|
|
945
966
|
};
|
|
@@ -952,7 +973,7 @@ var receiveDesignerData = exports.receiveDesignerData = function receiveDesigner
|
|
|
952
973
|
};
|
|
953
974
|
var loadDesignerData = exports.loadDesignerData = function loadDesignerData(configuration) {
|
|
954
975
|
return /*#__PURE__*/function () {
|
|
955
|
-
var
|
|
976
|
+
var _ref9 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee5(dispatch) {
|
|
956
977
|
return _regeneratorRuntime().wrap(function _callee5$(_context5) {
|
|
957
978
|
while (1) switch (_context5.prev = _context5.next) {
|
|
958
979
|
case 0:
|
|
@@ -986,7 +1007,7 @@ var loadDesignerData = exports.loadDesignerData = function loadDesignerData(conf
|
|
|
986
1007
|
}, _callee5, null, [[4, 10, 13, 16]]);
|
|
987
1008
|
}));
|
|
988
1009
|
return function (_x8) {
|
|
989
|
-
return
|
|
1010
|
+
return _ref9.apply(this, arguments);
|
|
990
1011
|
};
|
|
991
1012
|
}();
|
|
992
1013
|
};
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@configuratorware/configurator-frontendgui",
|
|
3
|
-
"version": "1.57.
|
|
3
|
+
"version": "1.57.10",
|
|
4
4
|
"license": "UNLICENSED",
|
|
5
5
|
"private": false,
|
|
6
6
|
"main": "./index.js",
|
|
7
7
|
"dependencies": {
|
|
8
8
|
"@babel/polyfill": "^7.12.1",
|
|
9
|
-
"@configuratorware/scripts": "1.57.
|
|
9
|
+
"@configuratorware/scripts": "1.57.10",
|
|
10
10
|
"@material-ui/core": "^4.12.4",
|
|
11
11
|
"@material-ui/icons": "^4.11.3",
|
|
12
12
|
"@material-ui/lab": "^4.0.0-alpha.61",
|
|
@@ -36,8 +36,8 @@
|
|
|
36
36
|
"react-router-dom": "^5.3.4",
|
|
37
37
|
"react-swipeable": "^5.5.1",
|
|
38
38
|
"react-zoom-pan-pinch": "^2.1.3",
|
|
39
|
-
"redhotmagma-graphics-editor": "1.57.
|
|
40
|
-
"redhotmagma-visualization": "1.57.
|
|
39
|
+
"redhotmagma-graphics-editor": "1.57.10",
|
|
40
|
+
"redhotmagma-visualization": "1.57.10",
|
|
41
41
|
"redux": "^4.1.0",
|
|
42
42
|
"redux-logger": "^3.0.6",
|
|
43
43
|
"redux-persist": "^5.10.0",
|
|
@@ -9,7 +9,7 @@ import uniq from 'lodash/uniq';
|
|
|
9
9
|
import { t } from 'Framework/i18n';
|
|
10
10
|
import Api from 'Framework/Api';
|
|
11
11
|
import { createActionWithGlobalState } from 'Utils/Redux/createActionWithGlobalState';
|
|
12
|
-
import { getConf } from '../../configuration';
|
|
12
|
+
import { getConf, setConf } from '../../configuration';
|
|
13
13
|
import { Services } from '../../ServiceLocator';
|
|
14
14
|
import {
|
|
15
15
|
dismissError,
|
|
@@ -200,19 +200,33 @@ export const applyVariantAmountPreSelection = configurator => {
|
|
|
200
200
|
const itemAmounts = designerPreSelection.itemAmounts;
|
|
201
201
|
|
|
202
202
|
if (itemAmounts) {
|
|
203
|
-
const children =
|
|
203
|
+
const children = flatten(
|
|
204
|
+
getChildren(configurator).map(variant => Object.values(getDeepestChildIdentifiers(variant)))
|
|
205
|
+
);
|
|
204
206
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
}
|
|
207
|
+
const applicableAmounts = Object.keys(itemAmounts)
|
|
208
|
+
.filter(identifier => children.indexOf(identifier) !== -1 && +itemAmounts[identifier] > 0)
|
|
209
|
+
.map(identifier => [identifier, +itemAmounts[identifier]]);
|
|
209
210
|
|
|
210
|
-
|
|
211
|
+
// designerPreSelection is parsed once from the URL and stays in conf for the whole session, so
|
|
212
|
+
// without consuming it here it would re-apply (and re-clear existing amounts) on every later
|
|
213
|
+
// configuration load, e.g. switching the configuration variant or loading a saved configuration
|
|
214
|
+
setConf('designerPreSelection.itemAmounts', undefined);
|
|
211
215
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
}
|
|
216
|
+
if (applicableAmounts.length === 0) {
|
|
217
|
+
return;
|
|
215
218
|
}
|
|
219
|
+
|
|
220
|
+
// only the explicitly passed amounts should apply, so clear whatever the item already had - by
|
|
221
|
+
// the keys actually selected, since a selected amount can be stored at any item level, not just
|
|
222
|
+
// on the deepest children
|
|
223
|
+
Object.keys(getSelectedAmounts(configurator) || {}).forEach(identifier =>
|
|
224
|
+
Services.configurator.setSelectedAmount(identifier, 0)
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
applicableAmounts.forEach(([identifier, amount]) =>
|
|
228
|
+
Services.configurator.setSelectedAmount(identifier, amount)
|
|
229
|
+
);
|
|
216
230
|
}
|
|
217
231
|
};
|
|
218
232
|
|
|
@@ -84,6 +84,170 @@ describe('Reducers/Configurator/Actions', () => {
|
|
|
84
84
|
expect(configuratorService.setSelectedAmount).toBeCalledWith('hoodie2', 5);
|
|
85
85
|
});
|
|
86
86
|
|
|
87
|
+
test('applyVariantAmountPreSelection sets the amount for a non-representative size of a color', () => {
|
|
88
|
+
const mockConfigurator = {
|
|
89
|
+
configuration: {
|
|
90
|
+
item: {
|
|
91
|
+
itemGroup: {
|
|
92
|
+
children: [
|
|
93
|
+
{
|
|
94
|
+
identifier: 'hoodie_red_m',
|
|
95
|
+
itemGroup: {
|
|
96
|
+
children: [
|
|
97
|
+
{ identifier: 'hoodie_red_s', itemgroupentryIdentifier: 's' },
|
|
98
|
+
{ identifier: 'hoodie_red_m', itemgroupentryIdentifier: 'm' },
|
|
99
|
+
],
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
identifier: 'hoodie_blue_m',
|
|
104
|
+
itemGroup: {
|
|
105
|
+
children: [
|
|
106
|
+
{ identifier: 'hoodie_blue_m', itemgroupentryIdentifier: 'm' },
|
|
107
|
+
{ identifier: 'hoodie_blue_l', itemgroupentryIdentifier: 'l' },
|
|
108
|
+
],
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const mockPreSelection = {
|
|
118
|
+
itemAmounts: {
|
|
119
|
+
hoodie_red_s: 2,
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
setConf('designerPreSelection', mockPreSelection);
|
|
124
|
+
|
|
125
|
+
applyVariantAmountPreSelection(mockConfigurator);
|
|
126
|
+
|
|
127
|
+
expect(configuratorService.setSelectedAmount).toBeCalledWith('hoodie_red_s', 2);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test('applyVariantAmountPreSelection clears the item default amount before applying the preselection', () => {
|
|
131
|
+
const mockConfigurator = {
|
|
132
|
+
configuration: {
|
|
133
|
+
item: {
|
|
134
|
+
itemGroup: {
|
|
135
|
+
children: [{ identifier: 'hoodie_white_default' }, { identifier: 'hoodie_black' }],
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
customdata: {
|
|
139
|
+
selectedAmounts: { hoodie_white_default: 1 },
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const mockPreSelection = {
|
|
145
|
+
itemAmounts: {
|
|
146
|
+
hoodie_black: 50,
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
setConf('designerPreSelection', mockPreSelection);
|
|
151
|
+
|
|
152
|
+
applyVariantAmountPreSelection(mockConfigurator);
|
|
153
|
+
|
|
154
|
+
expect(configuratorService.setSelectedAmount).toBeCalledWith('hoodie_white_default', 0);
|
|
155
|
+
expect(configuratorService.setSelectedAmount).toBeCalledWith('hoodie_black', 50);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test('applyVariantAmountPreSelection clears a default amount stored at a non-leaf identifier', () => {
|
|
159
|
+
const mockConfigurator = {
|
|
160
|
+
configuration: {
|
|
161
|
+
item: {
|
|
162
|
+
itemGroup: {
|
|
163
|
+
children: [
|
|
164
|
+
{
|
|
165
|
+
identifier: 'hoodie_red',
|
|
166
|
+
itemGroup: {
|
|
167
|
+
children: [
|
|
168
|
+
{ identifier: 'hoodie_red_s', itemgroupentryIdentifier: 's' },
|
|
169
|
+
{ identifier: 'hoodie_red_m', itemgroupentryIdentifier: 'm' },
|
|
170
|
+
],
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
],
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
customdata: {
|
|
177
|
+
// the default amount is stored on the colour-level identifier, not a deepest child
|
|
178
|
+
selectedAmounts: { hoodie_red: 1 },
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const mockPreSelection = {
|
|
184
|
+
itemAmounts: {
|
|
185
|
+
hoodie_red_s: 50,
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
setConf('designerPreSelection', mockPreSelection);
|
|
190
|
+
|
|
191
|
+
applyVariantAmountPreSelection(mockConfigurator);
|
|
192
|
+
|
|
193
|
+
expect(configuratorService.setSelectedAmount).toBeCalledWith('hoodie_red', 0);
|
|
194
|
+
expect(configuratorService.setSelectedAmount).toBeCalledWith('hoodie_red_s', 50);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test('applyVariantAmountPreSelection leaves existing amounts untouched when nothing in the preselection applies', () => {
|
|
198
|
+
const mockConfigurator = {
|
|
199
|
+
configuration: {
|
|
200
|
+
item: {
|
|
201
|
+
itemGroup: {
|
|
202
|
+
children: [{ identifier: 'hoodie_untouched' }],
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
customdata: {
|
|
206
|
+
selectedAmounts: { hoodie_untouched: 1 },
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
const mockPreSelection = {
|
|
212
|
+
itemAmounts: {
|
|
213
|
+
unknown_identifier: 50,
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
setConf('designerPreSelection', mockPreSelection);
|
|
218
|
+
|
|
219
|
+
const callsBefore = configuratorService.setSelectedAmount.mock.calls.length;
|
|
220
|
+
|
|
221
|
+
applyVariantAmountPreSelection(mockConfigurator);
|
|
222
|
+
|
|
223
|
+
expect(configuratorService.setSelectedAmount.mock.calls.length).toBe(callsBefore);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test('applyVariantAmountPreSelection only applies the preselection once', () => {
|
|
227
|
+
const mockConfigurator = {
|
|
228
|
+
configuration: {
|
|
229
|
+
item: {
|
|
230
|
+
itemGroup: {
|
|
231
|
+
children: [{ identifier: 'hoodie_once' }],
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
setConf('designerPreSelection', { itemAmounts: { hoodie_once: 7 } });
|
|
238
|
+
|
|
239
|
+
applyVariantAmountPreSelection(mockConfigurator);
|
|
240
|
+
expect(configuratorService.setSelectedAmount).toBeCalledWith('hoodie_once', 7);
|
|
241
|
+
|
|
242
|
+
const callsAfterFirstApply = configuratorService.setSelectedAmount.mock.calls.length;
|
|
243
|
+
|
|
244
|
+
// simulates a later configuration load (e.g. switching variant, loading a saved configuration) -
|
|
245
|
+
// designerPreSelection is still in conf, but its itemAmounts must have been consumed by now
|
|
246
|
+
applyVariantAmountPreSelection(mockConfigurator);
|
|
247
|
+
|
|
248
|
+
expect(configuratorService.setSelectedAmount.mock.calls.length).toBe(callsAfterFirstApply);
|
|
249
|
+
});
|
|
250
|
+
|
|
87
251
|
describe('receiveConfiguration', () => {
|
|
88
252
|
const configurator = {
|
|
89
253
|
configuration: {
|