kitchen-simulator
Version:
It is a kitchen simulator (self-contained micro-frontend).
500 lines (488 loc) • 21.3 kB
JavaScript
import _extends from "@babel/runtime/helpers/esm/extends";
import _asyncToGenerator from "@babel/runtime/helpers/esm/asyncToGenerator";
import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray";
import _defineProperty from "@babel/runtime/helpers/esm/defineProperty";
import _objectWithoutProperties from "@babel/runtime/helpers/esm/objectWithoutProperties";
import _classCallCheck from "@babel/runtime/helpers/esm/classCallCheck";
import _createClass from "@babel/runtime/helpers/esm/createClass";
import _possibleConstructorReturn from "@babel/runtime/helpers/esm/possibleConstructorReturn";
import _getPrototypeOf from "@babel/runtime/helpers/esm/getPrototypeOf";
import _inherits from "@babel/runtime/helpers/esm/inherits";
var _excluded = ["width", "height", "projectElement", "dataBundle", "configData", "options", "user", "auth", "featureFlags", "sentry", "analytics", "externalEvent", "onError"];
import _regeneratorRuntime from "@babel/runtime/regenerator";
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; }
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) { _defineProperty(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; }
function _callSuper(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e)); }
function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); }
import React, { useCallback, useEffect, useRef } from 'react';
import PropTypes from 'prop-types';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import * as Models from "./models";
import { State } from "./models";
import PlannerReducer from "./reducers/reducer";
import AppContext from "./AppContext";
import Catalog from "./catalog/catalog";
import { SVGLoader } from 'three/addons/loaders/SVGLoader';
import { isEmpty } from "./utils/helper";
import { TOE_KICK_MOLDING } from "./constants";
import * as Areas from "./catalog/areas/area/planner-element";
import * as Lines from "./catalog/lines/wall/planner-element";
import * as Holes from "./catalog/holes/export";
import { render2DItem, render3DApplianceItem, render3DItem, render3DLightingItem } from "./catalog/utils/item-loader";
import { ConsoleDebugger, Keyboard } from "./plugins/export";
import { Map } from 'immutable';
import * as Sentry from '@sentry/react';
import exporter from "./catalog/utils/exporter";
import * as THREE from 'three';
import LiteKitchenConfigurator from "./LiteKitchenConfigurator";
if (typeof window !== 'undefined') window.THREE = THREE;
/* ============================== component ============================= */
var MyCatalog = new Catalog();
var AppState = Map({
KitchenConfigurator: new State()
});
console.log('Version: 378.45-202509_DIY-364-mbox-crash');
isProduction && Sentry.init({
dsn: process.env.SENTRY_DSN,
environment: process.env.SENTRY_ENVIRONMENT
});
//define reducer
var reducer = function reducer(state, action) {
state = state || AppState;
state = state.update('KitchenConfigurator', function (plannerState) {
return PlannerReducer(plannerState, action);
});
return state;
};
var store = createStore(reducer, null, !isProduction && window.devToolsExtension ? window.devToolsExtension({
features: {
pause: true,
// start/pause recording of dispatched actions
lock: true,
// lock/unlock dispatching actions and side effects
persist: true,
// persist states on page reloading
"export": true,
// export history of actions in a file
"import": 'custom',
// import history of actions from a file
jump: true,
// jump back and forth (time travelling)
skip: true,
// skip (cancel) actions
reorder: true,
// drag and drop actions in the history list
dispatch: true,
// dispatch custom actions or action creators
test: true // generate tests for the selected actions
},
maxAge: 999999
}) : function (f) {
return f;
});
var plugins = [Keyboard(), ConsoleDebugger()];
function serializeError(err) {
try {
if (!err) return {
message: 'Unknown error'
};
if (err instanceof Error) {
return {
name: err.name,
message: err.message,
stack: err.stack
};
}
return {
message: String(err),
raw: safeJson(err)
};
} catch (_unused) {
return {
message: 'Error serializing error'
};
}
}
function safeJson(v) {
try {
return JSON.parse(JSON.stringify(v));
} catch (_unused2) {
return undefined;
}
}
/* ---------- Error Boundary that pushes into buffer ---------- */
var ToolErrorBoundary = /*#__PURE__*/function (_React$Component) {
function ToolErrorBoundary() {
_classCallCheck(this, ToolErrorBoundary);
return _callSuper(this, ToolErrorBoundary, arguments);
}
_inherits(ToolErrorBoundary, _React$Component);
return _createClass(ToolErrorBoundary, [{
key: "componentDidCatch",
value: function componentDidCatch(error, info) {
var pushError = this.props.pushError;
pushError({
type: 'render-error',
error: serializeError(error),
info: {
componentStack: info === null || info === void 0 ? void 0 : info.componentStack
}
});
}
}, {
key: "render",
value: function render() {
return this.props.children;
}
}]);
}(React.Component);
export default function LiteRenderer(props) {
var width = props.width,
height = props.height,
projectElement = props.projectElement,
dataBundle = props.dataBundle,
configData = props.configData,
options = props.options,
user = props.user,
auth = props.auth,
featureFlags = props.featureFlags,
sentry = props.sentry,
analytics = props.analytics,
externalEvent = props.externalEvent,
onError = props.onError,
passThrough = _objectWithoutProperties(props, _excluded);
/* ---------- track last external event ---------- */
var lastExternalEventRef = useRef(null);
useEffect(function () {
if (externalEvent) {
lastExternalEventRef.current = externalEvent;
}
}, [externalEvent]);
/* ---------- error buffer + last emitted bundle ---------- */
var errorsBufferRef = useRef([]); // pending errors (not yet emitted)
var lastEmittedRef = useRef({
externalEvent: null,
errors: []
}); // last bundle we sent
var flushTimerRef = useRef(null);
var emit = useCallback(function (external, errors) {
var payload = {
externalEvent: external || null,
errors: errors || []
};
try {
onError === null || onError === void 0 || onError(payload);
} catch (_unused3) {}
// eslint-disable-next-line no-console
console.debug('[LiteRenderer:onError]', payload);
lastEmittedRef.current = payload;
}, [onError]);
// batch short bursts (e.g., multiple async errors in same tick)
var scheduleFlush = useCallback(function () {
if (flushTimerRef.current) return;
flushTimerRef.current = setTimeout(function () {
flushTimerRef.current = null;
var errors = errorsBufferRef.current;
if (!errors.length) return;
errorsBufferRef.current = [];
emit(lastExternalEventRef.current, errors);
}, 0); // micro-batch; increase (e.g. 50ms) if you want coarser batching
}, [emit]);
var pushError = useCallback(function (errPayload) {
errorsBufferRef.current.push(_objectSpread({
ts: Date.now()
}, errPayload));
scheduleFlush();
}, [scheduleFlush]);
/* ---------- global runtime + async error capture ---------- */
useEffect(function () {
var onWindowError = function onWindowError(event) {
pushError({
type: 'runtime-error',
error: serializeError((event === null || event === void 0 ? void 0 : event.error) || (event === null || event === void 0 ? void 0 : event.message)),
meta: {
filename: event === null || event === void 0 ? void 0 : event.filename,
lineno: event === null || event === void 0 ? void 0 : event.lineno,
colno: event === null || event === void 0 ? void 0 : event.colno
}
});
};
var onUnhandledRejection = function onUnhandledRejection(event) {
pushError({
type: 'unhandled-rejection',
error: serializeError(event === null || event === void 0 ? void 0 : event.reason)
});
};
window.addEventListener('error', onWindowError);
window.addEventListener('unhandledrejection', onUnhandledRejection);
return function () {
window.removeEventListener('error', onWindowError);
window.removeEventListener('unhandledrejection', onUnhandledRejection);
};
}, [pushError]);
/* ---------- re-trigger last batch when externalEvent changes ---------- */
var prevExternalEventRef = useRef(null);
useEffect(function () {
var prev = prevExternalEventRef.current;
if (prev === externalEvent) return;
prevExternalEventRef.current = externalEvent;
// if we already emitted something before, re-send it with the new event context
var last = lastEmittedRef.current;
if (last && last.errors && last.errors.length) {
emit(lastExternalEventRef.current, last.errors);
}
}, [externalEvent, emit]);
var _ref = dataBundle || {},
_ref$data = _ref.data,
data = _ref$data === void 0 ? [] : _ref$data,
_ref$appliances = _ref.appliances,
appliances = _ref$appliances === void 0 ? [] : _ref$appliances,
_ref$furnishing = _ref.furnishing,
furnishing = _ref$furnishing === void 0 ? [] : _ref$furnishing,
_ref$lighting = _ref.lighting,
lighting = _ref$lighting === void 0 ? [] : _ref$lighting;
var id = configData.id,
logoImg = configData.logoImg,
companyUrl = configData.companyUrl;
var _React$useState = React.useState([]),
_React$useState2 = _slicedToArray(_React$useState, 2),
outlineSVGData = _React$useState2[0],
setOutlineSVGData = _React$useState2[1];
useEffect(function () {
var initMyCatalog = /*#__PURE__*/function () {
var _ref2 = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee() {
var x, _x, _x2;
return _regeneratorRuntime.wrap(function (_context) {
while (1) switch (_context.prev = _context.next) {
case 0:
for (x in Areas) MyCatalog.registerElement(Areas[x]);
for (_x in Lines) MyCatalog.registerElement(Lines[_x]);
for (_x2 in Holes) MyCatalog.registerElement(Holes[_x2]);
MyCatalog.registerCategory('Windows', 'Windows', [Holes.windowClear, Holes.windowCross, Holes.windowDoubleHung, Holes.windowVertical]);
MyCatalog.registerCategory('Doors', 'Doors', [Holes.doorInterior, Holes.doorExterior, Holes.doorCloset, Holes.doorSliding, Holes.doorwayFramed, Holes.doorwayFrameless]);
case 1:
case "end":
return _context.stop();
}
}, _callee);
}));
return function initMyCatalog() {
return _ref2.apply(this, arguments);
};
}();
var loadMoldings = /*#__PURE__*/function () {
var _ref3 = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee2() {
var molding, toeMoldingData, moldingData, promises;
return _regeneratorRuntime.wrap(function (_context2) {
while (1) switch (_context2.prev = _context2.next) {
case 0:
molding = [];
toeMoldingData = {};
moldingData = {};
if (moldingData.name = TOE_KICK_MOLDING) toeMoldingData = moldingData;
promises = molding.map(function (child) {
return new Promise(function (resolve, reject) {
var url = child === null || child === void 0 ? void 0 : child.shape_svg;
if (!url) return resolve();
var loader = new SVGLoader();
loader.load(url, function (data) {
var _data$xml$viewBox$ani, _data$xml, _data$xml$viewBox$ani2, _data$xml2;
child.data = {
paths: data.paths,
svg_width: (_data$xml$viewBox$ani = (_data$xml = data.xml) === null || _data$xml === void 0 || (_data$xml = _data$xml.viewBox) === null || _data$xml === void 0 || (_data$xml = _data$xml.animVal) === null || _data$xml === void 0 ? void 0 : _data$xml.width) !== null && _data$xml$viewBox$ani !== void 0 ? _data$xml$viewBox$ani : 0,
svg_height: (_data$xml$viewBox$ani2 = (_data$xml2 = data.xml) === null || _data$xml2 === void 0 || (_data$xml2 = _data$xml2.viewBox) === null || _data$xml2 === void 0 || (_data$xml2 = _data$xml2.animVal) === null || _data$xml2 === void 0 ? void 0 : _data$xml2.height) !== null && _data$xml$viewBox$ani2 !== void 0 ? _data$xml$viewBox$ani2 : 0
};
resolve();
}, null, function (error) {
console.error(error);
reject(error);
});
});
});
return _context2.abrupt("return", Promise.all(promises));
case 1:
case "end":
return _context2.stop();
}
}, _callee2);
}));
return function loadMoldings() {
return _ref3.apply(this, arguments);
};
}();
var loadSVGs = /*#__PURE__*/function () {
var _ref4 = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee4() {
var svgLoadPromises, outlineData;
return _regeneratorRuntime.wrap(function (_context4) {
while (1) switch (_context4.prev = _context4.next) {
case 0:
svgLoadPromises = data.map(/*#__PURE__*/function () {
var _ref5 = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee3(item) {
var _parsed$xml$viewBox, _parsed$xml$viewBox2, response, svgText, loader, parsed, _t;
return _regeneratorRuntime.wrap(function (_context3) {
while (1) switch (_context3.prev = _context3.next) {
case 0:
if (item.outline) {
_context3.next = 1;
break;
}
return _context3.abrupt("return", null);
case 1:
_context3.prev = 1;
_context3.next = 2;
return fetch(item.outline, {
cache: 'no-store'
});
case 2:
response = _context3.sent;
_context3.next = 3;
return response.text();
case 3:
svgText = _context3.sent;
loader = new SVGLoader();
parsed = loader.parse(svgText);
if (!isEmpty(parsed.paths)) {
_context3.next = 4;
break;
}
return _context3.abrupt("return", null);
case 4:
return _context3.abrupt("return", {
paths: parsed.paths,
svgWidth: parseFloat(parsed.xml.getAttribute('width')) || ((_parsed$xml$viewBox = parsed.xml.viewBox) === null || _parsed$xml$viewBox === void 0 || (_parsed$xml$viewBox = _parsed$xml$viewBox.animVal) === null || _parsed$xml$viewBox === void 0 ? void 0 : _parsed$xml$viewBox.width) || 0,
svgHeight: parseFloat(parsed.xml.getAttribute('height')) || ((_parsed$xml$viewBox2 = parsed.xml.viewBox) === null || _parsed$xml$viewBox2 === void 0 || (_parsed$xml$viewBox2 = _parsed$xml$viewBox2.animVal) === null || _parsed$xml$viewBox2 === void 0 ? void 0 : _parsed$xml$viewBox2.height) || 0,
reverse: !parseFloat(parsed.xml.getAttribute('height'))
});
case 5:
_context3.prev = 5;
_t = _context3["catch"](1);
console.error('Failed to load SVG:', item.outline, _t);
return _context3.abrupt("return", null);
case 6:
case "end":
return _context3.stop();
}
}, _callee3, null, [[1, 5]]);
}));
return function (_x3) {
return _ref5.apply(this, arguments);
};
}());
_context4.next = 1;
return Promise.all(svgLoadPromises);
case 1:
outlineData = _context4.sent;
setOutlineSVGData(outlineData);
case 2:
case "end":
return _context4.stop();
}
}, _callee4);
}));
return function loadSVGs() {
return _ref4.apply(this, arguments);
};
}();
var initCatalog = /*#__PURE__*/function () {
var _ref6 = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee5() {
return _regeneratorRuntime.wrap(function (_context5) {
while (1) switch (_context5.prev = _context5.next) {
case 0:
_context5.next = 1;
return initMyCatalog();
case 1:
_context5.next = 2;
return loadMoldings();
case 2:
_context5.next = 3;
return loadSVGs();
case 3:
case "end":
return _context5.stop();
}
}, _callee5);
}));
return function initCatalog() {
return _ref6.apply(this, arguments);
};
}();
initCatalog();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [data, id]);
// Register items once SVGs are loaded
useEffect(function () {
if (!outlineSVGData.length) return;
var Item = [];
data.forEach(function (obj, index) {
Item.push(exporter(_objectSpread(_objectSpread({}, obj), {}, {
type: 'cabinet',
outlineSVGData: outlineSVGData[index],
render2DItem: render2DItem,
render3DItem: render3DItem
})));
});
appliances.forEach(function (obj) {
Item.push(exporter(_objectSpread(_objectSpread({}, obj), {}, {
render2DItem: render2DItem,
render3DItem: render3DApplianceItem,
type: 'appliance'
})));
});
lighting.forEach(function (obj) {
Item.push(exporter(_objectSpread(_objectSpread({}, obj), {}, {
type: 'lighting',
render2DItem: render2DItem,
render3DItem: render3DLightingItem
})));
});
furnishing.forEach(function (obj) {
Item.push(exporter(_objectSpread(_objectSpread({}, obj), {}, {
type: 'furnishing',
render2DItem: render2DItem,
render3DItem: render3DApplianceItem
})));
});
for (var x in Item) MyCatalog.registerElement(Item[x]);
}, [outlineSVGData, data, appliances, furnishing, lighting]);
return /*#__PURE__*/React.createElement(AppContext.Provider, null, /*#__PURE__*/React.createElement(Provider, {
store: store
}, /*#__PURE__*/React.createElement(ToolErrorBoundary, {
pushError: pushError
}, /*#__PURE__*/React.createElement(LiteKitchenConfigurator, _extends({
catalog: MyCatalog,
width: width,
height: height,
logoImage: logoImg,
companyURL: companyUrl,
plugins: plugins,
stateExtractor: function stateExtractor(state) {
return state.get('KitchenConfigurator');
},
data: data,
configData: configData,
externalEvent: externalEvent
}, passThrough)))));
}
/* ============================== prop types ============================== */
LiteRenderer.propTypes = {
width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
height: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
projectElement: PropTypes.arrayOf(PropTypes.object),
dataBundle: PropTypes.any,
configData: PropTypes.any,
catalog: PropTypes.oneOfType([PropTypes.object, PropTypes.instanceOf(Models.Catalog)]),
logoImg: PropTypes.any,
companyUrl: PropTypes.string,
options: PropTypes.object,
user: PropTypes.object,
auth: PropTypes.object,
featureFlags: PropTypes.object,
sentry: PropTypes.shape({
dsn: PropTypes.string,
environment: PropTypes.string
}),
externalEvent: PropTypes.object,
onError: PropTypes.func,
store: PropTypes.object
};