kitchen-simulator 3.16.0 → 3.16.2-test-renderer-fix

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.
@@ -31,8 +31,8 @@ var _translator = _interopRequireDefault(require("./translator/translator"));
31
31
  var _objectsUtils = require("./utils/objects-utils");
32
32
  var _version = require("./version");
33
33
  var _isolateEventHandler = require("./utils/isolate-event-handler");
34
- var _excluded = ["width", "height", "state", "stateExtractor", "measurementUnit"];
35
- var _templateObject, _templateObject2;
34
+ var _excluded = ["width", "height", "extractedState"];
35
+ var _templateObject, _templateObject2; // LiteKitchenConfigurator.jsx
36
36
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, "default": e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
37
37
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
38
38
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2["default"])(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
@@ -43,7 +43,7 @@ var wrapperStyle = {
43
43
  display: 'flex',
44
44
  flexFlow: 'row nowrap'
45
45
  };
46
- var WarningItem = _styledComponents["default"].div(_templateObject || (_templateObject = (0, _taggedTemplateLiteral2["default"])(["\n opacity: 0.8;\n position: absolute;\n border-radius: 6px;\n z-index: 11;\n background-color: #ff7400;\n width: max-content;\n align-items: center;\n padding: 10px;\n display: none;\n box-shadow: 0px 3px 5px -1px rgba(0, 0, 0, 0.2),\n 0px 6px 10px 0px rgba(0, 0, 0, 0.39), 0px 1px 18px 0px rgba(0, 0, 0, 0.12);\n top: -50px;\n left: 5px;\n color: white;\n"])));
46
+ var WarningItem = _styledComponents["default"].div(_templateObject || (_templateObject = (0, _taggedTemplateLiteral2["default"])(["\n opacity: 0.8;\n position: absolute;\n border-radius: 6px;\n z-index: 11;\n background-color: #ff7400;\n width: max-content;\n align-items: center;\n padding: 10px;\n display: none;\n box-shadow:\n 0px 3px 5px -1px rgba(0, 0, 0, 0.2),\n 0px 6px 10px 0px rgba(0, 0, 0, 0.39),\n 0px 1px 18px 0px rgba(0, 0, 0, 0.12);\n top: -50px;\n left: 5px;\n color: white;\n"])));
47
47
  var SubCategoryItemImage = _styledComponents["default"].img(_templateObject2 || (_templateObject2 = (0, _taggedTemplateLiteral2["default"])(["\n background-color: ", ";\n height: 30px;\n width: 30px;\n -webkit-mask-image: url(", ");\n -webkit-mask-size: calc(100% - 2px) calc(100% - 2px);\n -webkit-mask-repeat: no-repeat;\n -webkit-mask-position: 1px 1px;\n"])), constants.SECONDARY_PURPLE_COLOR, function (props) {
48
48
  return props.maskImage + '?nocache=2025';
49
49
  });
@@ -53,8 +53,6 @@ var LiteKitchenConfigurator = /*#__PURE__*/function (_Component) {
53
53
  var _this;
54
54
  (0, _classCallCheck2["default"])(this, LiteKitchenConfigurator);
55
55
  _this = _callSuper(this, LiteKitchenConfigurator, [props]);
56
-
57
- // utm tracking
58
56
  var utmDetailParams = new URLSearchParams(_this.props.location && _this.props.location.search);
59
57
  var utmStrEncoded = utmDetailParams.get('details');
60
58
  var utmRequestData = null;
@@ -64,7 +62,7 @@ var LiteKitchenConfigurator = /*#__PURE__*/function (_Component) {
64
62
  utmRequestData = JSON.parse((0, _helper.base64Decode)(utmStrEncoded));
65
63
  utmRequestData = JSON.parse((_utmRequestData = utmRequestData) === null || _utmRequestData === void 0 ? void 0 : _utmRequestData.utm);
66
64
  } catch (e) {
67
- console.error('Cannot parse utm parameter: ', error);
65
+ console.error('Cannot parse utm parameter: ', e);
68
66
  utmRequestData = null;
69
67
  }
70
68
  }
@@ -83,7 +81,6 @@ var LiteKitchenConfigurator = /*#__PURE__*/function (_Component) {
83
81
  myProjectsOpen: false,
84
82
  myProjectsToLogin: false,
85
83
  downloadPopupVisible: false,
86
- // For Toolbar Item
87
84
  toolbar: '',
88
85
  reviewQuotePopupOpened: false,
89
86
  floorOpened: false,
@@ -101,8 +98,6 @@ var LiteKitchenConfigurator = /*#__PURE__*/function (_Component) {
101
98
  isSaved: false,
102
99
  isLeaving: false
103
100
  };
104
-
105
- // For UTM tracking
106
101
  _this.utm = {
107
102
  utm_source: ((_utmRequestData2 = utmRequestData) === null || _utmRequestData2 === void 0 ? void 0 : _utmRequestData2.source) || 'source',
108
103
  utm_medium: ((_utmRequestData3 = utmRequestData) === null || _utmRequestData3 === void 0 ? void 0 : _utmRequestData3.medium) || 'medium',
@@ -133,10 +128,13 @@ var LiteKitchenConfigurator = /*#__PURE__*/function (_Component) {
133
128
  _this.setDownloadPopupVisible = _this.setDownloadPopupVisible.bind(_this);
134
129
  _this.neverShowInput = /*#__PURE__*/_react["default"].createRef(null);
135
130
  _this.setShowProperty = _this.setShowProperty.bind(_this);
131
+
132
+ // cache for viewer2D init so we don't allocate merges every render
133
+ _this._lastExtractedStateRef = null;
134
+ _this._cachedViewer2DKey = null;
135
+ _this._cachedExtractedState = null;
136
136
  return _this;
137
137
  }
138
-
139
- // Toolbar control functions
140
138
  (0, _inherits2["default"])(LiteKitchenConfigurator, _Component);
141
139
  return (0, _createClass2["default"])(LiteKitchenConfigurator, [{
142
140
  key: "setToolbar",
@@ -220,26 +218,32 @@ var LiteKitchenConfigurator = /*#__PURE__*/function (_Component) {
220
218
  }, {
221
219
  key: "openFloor",
222
220
  value: function openFloor() {
221
+ var _el$parentElement;
223
222
  this.setState({
224
223
  floorOpened: true
225
224
  });
226
- document.getElementById('make_floorplan_inactive').parentElement.parentElement.style.zIndex = 999;
225
+ var el = document.getElementById('make_floorplan_inactive');
226
+ if (el !== null && el !== void 0 && (_el$parentElement = el.parentElement) !== null && _el$parentElement !== void 0 && _el$parentElement.parentElement) el.parentElement.parentElement.style.zIndex = 999;
227
227
  }
228
228
  }, {
229
229
  key: "openCabinet",
230
230
  value: function openCabinet() {
231
+ var _el$parentElement2;
231
232
  this.setState({
232
233
  cabinetOpened: true
233
234
  });
234
- document.getElementById('add_cabinets_inactive').parentElement.parentElement.style.zIndex = 999;
235
+ var el = document.getElementById('add_cabinets_inactive');
236
+ if (el !== null && el !== void 0 && (_el$parentElement2 = el.parentElement) !== null && _el$parentElement2 !== void 0 && _el$parentElement2.parentElement) el.parentElement.parentElement.style.zIndex = 999;
235
237
  }
236
238
  }, {
237
239
  key: "toggleDoorStyle",
238
240
  value: function toggleDoorStyle(visible) {
241
+ var _el$parentElement3;
239
242
  this.setState({
240
243
  doorStyleOpen: visible
241
244
  });
242
- document.getElementById('select_doorstyle_inactive').parentElement.parentElement.style.zIndex = visible ? 999 : 6;
245
+ var el = document.getElementById('select_doorstyle_inactive');
246
+ if (el !== null && el !== void 0 && (_el$parentElement3 = el.parentElement) !== null && _el$parentElement3 !== void 0 && _el$parentElement3.parentElement) el.parentElement.parentElement.style.zIndex = visible ? 999 : 6;
243
247
  }
244
248
  }, {
245
249
  key: "replaceCabinet",
@@ -251,70 +255,72 @@ var LiteKitchenConfigurator = /*#__PURE__*/function (_Component) {
251
255
  }, {
252
256
  key: "openAppliance",
253
257
  value: function openAppliance() {
258
+ var _el$parentElement4;
254
259
  this.setState({
255
260
  applianceOpened: true
256
261
  });
257
- document.getElementById('add_appliances_inactive').parentElement.parentElement.style.zIndex = 999;
262
+ var el = document.getElementById('add_appliances_inactive');
263
+ if (el !== null && el !== void 0 && (_el$parentElement4 = el.parentElement) !== null && _el$parentElement4 !== void 0 && _el$parentElement4.parentElement) el.parentElement.parentElement.style.zIndex = 999;
258
264
  }
259
265
  }, {
260
266
  key: "openFinishing",
261
267
  value: function openFinishing() {
268
+ var _el$parentElement5;
262
269
  this.setState({
263
270
  finishingOpened: true
264
271
  });
265
- document.getElementById('finishing_touches_inactive').parentElement.parentElement.style.zIndex = 999;
272
+ var el = document.getElementById('finishing_touches_inactive');
273
+ if (el !== null && el !== void 0 && (_el$parentElement5 = el.parentElement) !== null && _el$parentElement5 !== void 0 && _el$parentElement5.parentElement) el.parentElement.parentElement.style.zIndex = 999;
266
274
  }
267
275
  }, {
268
276
  key: "onReviewQuoteClicked",
269
277
  value: function onReviewQuoteClicked(visible) {
278
+ var _el$parentElement6;
270
279
  this.setState({
271
280
  reviewQuotePopupOpened: visible
272
281
  });
273
- document.getElementById('review_quote_inactive').parentElement.parentElement.style.zIndex = visible ? 999 : 6;
282
+ var el = document.getElementById('review_quote_inactive');
283
+ if (el !== null && el !== void 0 && (_el$parentElement6 = el.parentElement) !== null && _el$parentElement6 !== void 0 && _el$parentElement6.parentElement) el.parentElement.parentElement.style.zIndex = visible ? 999 : 6;
274
284
  }
275
285
  }, {
276
286
  key: "closeFloorTB",
277
287
  value: function closeFloorTB() {
288
+ var _el$parentElement7;
278
289
  this.setState({
279
290
  floorOpened: false
280
291
  });
281
- document.getElementById('make_floorplan_inactive') && (document.getElementById('make_floorplan_inactive').parentElement.parentElement.style.zIndex = 6);
292
+ var el = document.getElementById('make_floorplan_inactive');
293
+ if (el !== null && el !== void 0 && (_el$parentElement7 = el.parentElement) !== null && _el$parentElement7 !== void 0 && _el$parentElement7.parentElement) el.parentElement.parentElement.style.zIndex = 6;
282
294
  }
283
295
  }, {
284
296
  key: "closeCabinetTB",
285
297
  value: function closeCabinetTB() {
298
+ var _el$parentElement8;
286
299
  this.setState({
287
300
  cabinetOpened: false
288
301
  });
289
- document.getElementById('add_cabinets_inactive').parentElement.parentElement.style.zIndex = 6;
302
+ var el = document.getElementById('add_cabinets_inactive');
303
+ if (el !== null && el !== void 0 && (_el$parentElement8 = el.parentElement) !== null && _el$parentElement8 !== void 0 && _el$parentElement8.parentElement) el.parentElement.parentElement.style.zIndex = 6;
290
304
  }
291
305
  }, {
292
306
  key: "closeFinishingTB",
293
307
  value: function closeFinishingTB() {
308
+ var _el$parentElement9;
294
309
  this.setState({
295
310
  finishingOpened: false
296
311
  });
297
- document.getElementById('finishing_touches_inactive').parentElement.parentElement.style.zIndex = 6;
312
+ var el = document.getElementById('finishing_touches_inactive');
313
+ if (el !== null && el !== void 0 && (_el$parentElement9 = el.parentElement) !== null && _el$parentElement9 !== void 0 && _el$parentElement9.parentElement) el.parentElement.parentElement.style.zIndex = 6;
298
314
  }
299
315
  }, {
300
316
  key: "closeApplianceTB",
301
317
  value: function closeApplianceTB() {
318
+ var _el$parentElement0;
302
319
  this.setState({
303
320
  applianceOpened: false
304
321
  });
305
- document.getElementById('add_appliances_inactive').parentElement.parentElement.style.zIndex = 6;
306
- }
307
- }, {
308
- key: "closeDoorstyle",
309
- value: function closeDoorstyle() {
310
- this.setState({});
311
- document.getElementById('select_doorstyle_inactive').parentElement.parentElement.style.zIndex = 6;
312
- }
313
- }, {
314
- key: "closeReviewQuote",
315
- value: function closeReviewQuote() {
316
- this.setState({});
317
- document.getElementById('review_quote_inactive').parentElement.parentElement.style.zIndex = 6;
322
+ var el = document.getElementById('add_appliances_inactive');
323
+ if (el !== null && el !== void 0 && (_el$parentElement0 = el.parentElement) !== null && _el$parentElement0 !== void 0 && _el$parentElement0.parentElement) el.parentElement.parentElement.style.zIndex = 6;
318
324
  }
319
325
  }, {
320
326
  key: "getChildContext",
@@ -330,36 +336,36 @@ var LiteKitchenConfigurator = /*#__PURE__*/function (_Component) {
330
336
  }, {
331
337
  key: "componentDidMount",
332
338
  value: function componentDidMount() {
333
- console.log('context =>', this.context);
334
339
  window.forRedo = [];
335
340
  var store = this.context.store;
336
341
  var _this$props = this.props,
337
342
  stateExtractor = _this$props.stateExtractor,
338
343
  plugins = _this$props.plugins;
344
+
345
+ // keep old behavior: run plugins once on mount
339
346
  var newplugins = (0, _toConsumableArray2["default"])(plugins);
340
347
  newplugins.forEach(function (newplugin) {
341
348
  return newplugin(store, stateExtractor);
342
349
  });
343
350
  }
344
351
  }, {
345
- key: "componentWillReceiveProps",
346
- value: function componentWillReceiveProps(nextProps) {
347
- var stateExtractor = nextProps.stateExtractor,
348
- state = nextProps.state,
349
- projectActions = nextProps.projectActions,
350
- catalog = nextProps.catalog,
351
- externalEvent = nextProps.externalEvent,
352
- onInternalEvent = nextProps.onInternalEvent;
352
+ key: "componentDidUpdate",
353
+ value: function componentDidUpdate(prevProps) {
354
+ var _extractedState$getIn;
355
+ var _this$props2 = this.props,
356
+ externalEvent = _this$props2.externalEvent,
357
+ extractedState = _this$props2.extractedState,
358
+ projectActions = _this$props2.projectActions,
359
+ catalog = _this$props2.catalog;
353
360
 
354
- // handle external events
355
- if (this.props.externalEvent !== externalEvent) {
356
- (0, _isolateEventHandler.handleExternalEvent)(nextProps);
357
- }
358
- var plannerState = stateExtractor(state);
359
- var catalogReady = plannerState.getIn(['catalog', 'ready']);
360
- if (!catalogReady) {
361
- projectActions.initCatalog(catalog);
361
+ // same behavior: handle external event when it changes
362
+ if (prevProps.externalEvent !== externalEvent) {
363
+ (0, _isolateEventHandler.handleExternalEvent)(this.props);
362
364
  }
365
+
366
+ // same behavior: init catalog until ready
367
+ var catalogReady = extractedState === null || extractedState === void 0 || (_extractedState$getIn = extractedState.getIn) === null || _extractedState$getIn === void 0 ? void 0 : _extractedState$getIn.call(extractedState, ['catalog', 'ready']);
368
+ if (!catalogReady) projectActions.initCatalog(catalog);
363
369
  }
364
370
  }, {
365
371
  key: "isProjectEmpty",
@@ -369,16 +375,51 @@ var LiteKitchenConfigurator = /*#__PURE__*/function (_Component) {
369
375
  var layer = layers.get(selectedLayer);
370
376
  return layer.areas.size + layer.lines.size + layer.holes.size + layer.items.size === 0;
371
377
  }
378
+ }, {
379
+ key: "getExtractedStateWithViewer2DInit",
380
+ value: function getExtractedStateWithViewer2DInit(extractedState, width, height) {
381
+ if (!extractedState) return extractedState;
382
+
383
+ // ✅ If the extractedState reference changed, drop cache immediately.
384
+ if (this._lastExtractedStateRef !== extractedState) {
385
+ this._lastExtractedStateRef = extractedState;
386
+ this._cachedViewer2DKey = null;
387
+ this._cachedExtractedState = null;
388
+ }
389
+ var _viewer2D = extractedState.getIn(['viewer2D']);
390
+ if (!_viewer2D || _viewer2D.size <= 0) return extractedState;
391
+ var v = _viewer2D.toJS();
392
+
393
+ // Only do the "center viewer2D if e/f are zero" logic.
394
+ // ✅ No need to build keys from unrelated parts of state.
395
+ if (v.e !== 0 || v.f !== 0) return extractedState;
396
+
397
+ // Build a cache key ONLY for this viewer2D-centering computation.
398
+ var cacheKey = "".concat(width, ":").concat(height, ":").concat(v.viewerWidth, ":").concat(v.viewerHeight, ":").concat(v.SVGWidth, ":").concat(v.SVGHeight);
399
+ if (this._cachedViewer2DKey === cacheKey && this._cachedExtractedState) {
400
+ return this._cachedExtractedState;
401
+ }
402
+ var centeredViewer2D = _viewer2D.merge({
403
+ e: v.viewerWidth / 2 - v.SVGWidth / 2,
404
+ f: v.viewerHeight / 2 - v.SVGHeight / 2,
405
+ a: 0.99,
406
+ d: 0.99
407
+ });
408
+ var merged = extractedState.merge({
409
+ viewer2D: centeredViewer2D
410
+ });
411
+ this._cachedViewer2DKey = cacheKey;
412
+ this._cachedExtractedState = merged;
413
+ return merged;
414
+ }
372
415
  }, {
373
416
  key: "render",
374
417
  value: function render() {
375
- var _this$props2 = this.props,
376
- width = _this$props2.width,
377
- height = _this$props2.height,
378
- state = _this$props2.state,
379
- stateExtractor = _this$props2.stateExtractor,
380
- measurementUnit = _this$props2.measurementUnit,
381
- props = (0, _objectWithoutProperties2["default"])(_this$props2, _excluded);
418
+ var _this$props3 = this.props,
419
+ width = _this$props3.width,
420
+ height = _this$props3.height,
421
+ extractedState = _this$props3.extractedState,
422
+ props = (0, _objectWithoutProperties2["default"])(_this$props3, _excluded);
382
423
  var _this$state = this.state,
383
424
  savePopupVisible = _this$state.savePopupVisible,
384
425
  quotePopupVisible = _this$state.quotePopupVisible,
@@ -386,40 +427,10 @@ var LiteKitchenConfigurator = /*#__PURE__*/function (_Component) {
386
427
  signOpen = _this$state.signOpen,
387
428
  myProjectsOpen = _this$state.myProjectsOpen;
388
429
  var contentW = width - toolbarW;
389
- // let contentW = width - toolbarW - sidebarW;
390
- // let toolbarH = height - footerBarH;
391
- // let contentH = height - footerBarH;
392
- // let sidebarH = height - footerBarH;
393
- var toolbarH = height;
394
430
  var contentH = height;
395
- var sidebarH = height;
396
- var headerW = width;
397
- var headerH = 60;
398
- var extractedState = stateExtractor(state);
399
- var firstVisit = this.state.wizardStepOpend && this.isProjectEmpty(extractedState.scene);
431
+ var extracted = this.getExtractedStateWithViewer2DInit(extractedState, width, height);
432
+ var firstVisit = this.state.wizardStepOpend && this.isProjectEmpty(extracted.scene);
400
433
  var allVisible = firstVisit || signOpen || myProjectsOpen;
401
- var _scene = extractedState.getIn(['scene']);
402
- var len = (0, _convertUnitsLite.convert)(_scene.width).from(_scene.unit).to('cm');
403
- var _viewer2D = extractedState.getIn(['viewer2D']);
404
- if (_viewer2D.size > 0) {
405
- var scaleX = width / len * 3;
406
- var scaleY = height / len * 3;
407
- var scale = scaleX > scaleY ? scaleY : scaleX;
408
- var _e = width - len * scale,
409
- _f = height - len * scale;
410
- var viewer = _viewer2D.toJS();
411
- if (viewer.e === 0 && viewer.f === 0) {
412
- _viewer2D = _viewer2D.merge({
413
- e: viewer.viewerWidth / 2 - viewer.SVGWidth / 2,
414
- f: viewer.viewerHeight / 2 - viewer.SVGHeight / 2,
415
- a: 0.99,
416
- d: 0.99
417
- });
418
- }
419
- }
420
- extractedState = extractedState.merge({
421
- viewer2D: _viewer2D
422
- });
423
434
  return /*#__PURE__*/_react["default"].createElement("section", null, /*#__PURE__*/_react["default"].createElement("div", {
424
435
  style: _objectSpread(_objectSpread({}, wrapperStyle), {}, {
425
436
  height: height,
@@ -431,7 +442,7 @@ var LiteKitchenConfigurator = /*#__PURE__*/function (_Component) {
431
442
  width: contentW,
432
443
  height: contentH,
433
444
  catalog: this.props.catalog,
434
- state: extractedState,
445
+ state: extracted,
435
446
  toolBar: this.state.toolbar,
436
447
  setToolbar: this.setToolbar,
437
448
  replaceCabinet: this.replaceCabinet,
@@ -468,12 +479,11 @@ LiteKitchenConfigurator.propTypes = {
468
479
  width: _propTypes["default"].number.isRequired,
469
480
  height: _propTypes["default"].number.isRequired,
470
481
  stateExtractor: _propTypes["default"].func.isRequired,
471
- sidebarComponents: _propTypes["default"].array,
472
- footerbarComponents: _propTypes["default"].array,
473
- customContents: _propTypes["default"].object,
474
- softwareSignature: _propTypes["default"].string,
475
482
  configData: _propTypes["default"].object,
476
- onInternalEvent: _propTypes["default"].func
483
+ onInternalEvent: _propTypes["default"].func,
484
+ extractedState: _propTypes["default"].object,
485
+ // ✅ injected by connect
486
+ externalEvent: _propTypes["default"].object
477
487
  };
478
488
  LiteKitchenConfigurator.contextTypes = {
479
489
  store: _propTypes["default"].object.isRequired
@@ -496,10 +506,16 @@ LiteKitchenConfigurator.defaultProps = {
496
506
  configData: {}
497
507
  };
498
508
 
499
- //redux connect
500
- function mapStateToProps(reduxState) {
509
+ // ✅ Only select the slice you actually use.
510
+ // This preserves behavior but massively reduces rerenders.
511
+ function mapStateToProps(reduxState, ownProps) {
512
+ var stateExtractor = ownProps.stateExtractor || function (s) {
513
+ return s;
514
+ };
515
+ var extractedState = stateExtractor(reduxState);
501
516
  return {
502
- state: reduxState
517
+ extractedState: extractedState,
518
+ state: extractedState
503
519
  };
504
520
  }
505
521
  function mapDispatchToProps(dispatch) {