UNPKG

kitchen-simulator

Version:

It is a kitchen simulator (self-contained micro-frontend).

500 lines (488 loc) 21.3 kB
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 };