diff --git a/.env b/.env index 3ad6561..0cfcc91 100644 --- a/.env +++ b/.env @@ -1 +1,2 @@ -HTTPS=false \ No newline at end of file +HTTPS=false +ENVIRONMENT=development diff --git a/package-lock.json b/package-lock.json index 2393baa..4ff677d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,13 +12,18 @@ "@tsparticles/react": "^3.0.0", "@tsparticles/slim": "^3.5.0", "antd": "^5.19.2", + "antd-style": "^3.7.1", "axios": "*", + "dotenv": "^16.5.0", "gcode-preview": "^2.17.0", + "keycloak-js": "^26.1.5", "moment": "*", + "prop-types": "^15.8.1", "react": "*", "react-dom": "*", "react-router-dom": "*", "react-scripts": "*", + "react-stl-viewer": "^2.5.0", "socket.io-client": "*", "styled-components": "*", "three": "^0.166.1", @@ -26,6 +31,13 @@ "web-vitals": "*" }, "devDependencies": { + "eslint": "^8.57.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.2.1", + "eslint-plugin-react": "^7.35.0", + "eslint-plugin-react-hooks": "^4.6.2", + "prettier": "^3.3.3", + "prettier-eslint": "^16.3.0", "standard": "^17.1.0" } }, @@ -64,9 +76,9 @@ } }, "node_modules/@ant-design/cssinjs": { - "version": "1.21.0", - "resolved": "https://registry.npmjs.org/@ant-design/cssinjs/-/cssinjs-1.21.0.tgz", - "integrity": "sha512-gIilraPl+9EoKdYxnupxjHB/Q6IHNRjEXszKbDxZdsgv4sAZ9pjkCq8yanDWNvyfjp4leir2OVAJm0vxwKK8YA==", + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@ant-design/cssinjs/-/cssinjs-1.23.0.tgz", + "integrity": "sha512-7GAg9bD/iC9ikWatU9ym+P9ugJhi/WbsTWzcKN6T4gU0aehsprtke1UAaaSxxkjjmkJb3llet/rbUSLPgwlY4w==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.11.1", @@ -75,13 +87,19 @@ "classnames": "^2.3.1", "csstype": "^3.1.3", "rc-util": "^5.35.0", - "stylis": "^4.0.13" + "stylis": "^4.3.4" }, "peerDependencies": { "react": ">=16.0.0", "react-dom": ">=16.0.0" } }, + "node_modules/@ant-design/cssinjs/node_modules/stylis": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", + "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==", + "license": "MIT" + }, "node_modules/@ant-design/icons": { "version": "5.4.0", "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-5.4.0.tgz", @@ -186,9 +204,9 @@ } }, "node_modules/@babel/eslint-parser": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.24.8.tgz", - "integrity": "sha512-nYAikI4XTGokU2QX7Jx+v4rxZKhKivaQaREZjuW3mrJrbdWJ5yUfohnoUULge+zEEaKjPYNxhoRgUKktjXtbwA==", + "version": "7.25.1", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.25.1.tgz", + "integrity": "sha512-Y956ghgTT4j7rKesabkh5WeqgSFZVFwaPR0IWFm7KFHFmmJ4afbG49SmfW4S+GyRPx0Dy5jxEWA5t0rpxfElWg==", "license": "MIT", "dependencies": { "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", @@ -2247,6 +2265,39 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "license": "MIT" }, + "node_modules/@chevrotain/cst-dts-gen": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-10.5.0.tgz", + "integrity": "sha512-lhmC/FyqQ2o7pGK4Om+hzuDrm9rhFYIJ/AXoQBeongmn870Xeb0L6oGEiuR8nohFNL5sMaQEJWCxr1oIVIVXrw==", + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/gast": "10.5.0", + "@chevrotain/types": "10.5.0", + "lodash": "4.17.21" + } + }, + "node_modules/@chevrotain/gast": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-10.5.0.tgz", + "integrity": "sha512-pXdMJ9XeDAbgOWKuD1Fldz4ieCs6+nLNmyVhe2gZVqoO7v8HXuHYs5OV2EzUtbuai37TlOAQHrTDvxMnvMJz3A==", + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/types": "10.5.0", + "lodash": "4.17.21" + } + }, + "node_modules/@chevrotain/types": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-10.5.0.tgz", + "integrity": "sha512-f1MAia0x/pAVPWH/T73BJVyO2XU5tI4/iE7cnxb7tqdNTNhQI3Uq3XkqcoteTmD4t1aM0LbHCJOhgIDn07kl2A==", + "license": "Apache-2.0" + }, + "node_modules/@chevrotain/utils": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-10.5.0.tgz", + "integrity": "sha512-hBzuU5+JjB2cqNZyszkDHZgOSrUUT8V3dhgRl8Q9Gp6dAj/H5+KILGjbhDpc3Iy9qmqlm/akuOI2ut9VUtzJxQ==", + "license": "Apache-2.0" + }, "node_modules/@csstools/normalize.css": { "version": "12.1.1", "resolved": "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-12.1.1.tgz", @@ -2542,6 +2593,108 @@ "node": ">=10" } }, + "node_modules/@emotion/babel-plugin": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" + }, + "node_modules/@emotion/babel-plugin/node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" + }, + "node_modules/@emotion/babel-plugin/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" + }, + "node_modules/@emotion/babel-plugin/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "license": "MIT" + }, + "node_modules/@emotion/cache": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", + "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/cache/node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" + }, + "node_modules/@emotion/cache/node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "license": "MIT" + }, + "node_modules/@emotion/css": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/css/-/css-11.13.5.tgz", + "integrity": "sha512-wQdD0Xhkn3Qy2VNcIzbLP9MR8TafI0MJb7BEAXKp+w4+XqErksWR4OXomuDzPsN4InLdGhVe6EYcn2ZIUCpB8w==", + "license": "MIT", + "dependencies": { + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.13.5", + "@emotion/serialize": "^1.3.3", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2" + } + }, "node_modules/@emotion/hash": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", @@ -2563,12 +2716,94 @@ "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==", "license": "MIT" }, + "node_modules/@emotion/react": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", + "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "license": "MIT", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/serialize/node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" + }, + "node_modules/@emotion/serialize/node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" + }, + "node_modules/@emotion/serialize/node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "license": "MIT" + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", + "license": "MIT" + }, "node_modules/@emotion/unitless": { "version": "0.7.5", "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==", "license": "MIT" }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", + "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==", + "license": "MIT" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", + "license": "MIT" + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -3604,6 +3839,19 @@ "node": ">=14" } }, + "node_modules/@pkgr/core": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", + "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/@pmmmwh/react-refresh-webpack-plugin": { "version": "0.5.15", "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.15.tgz", @@ -3801,6 +4049,64 @@ "react-dom": ">=16.9.0" } }, + "node_modules/@react-three/fiber": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-8.18.0.tgz", + "integrity": "sha512-FYZZqD0UUHUswKz3LQl2Z7H24AhD14XGTsIRw3SJaXUxyfVMi+1yiZGmqTcPt/CkPpdU7rrxqcyQ1zJE5DjvIQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.17.8", + "@types/react-reconciler": "^0.26.7", + "@types/webxr": "*", + "base64-js": "^1.5.1", + "buffer": "^6.0.3", + "its-fine": "^1.0.6", + "react-reconciler": "^0.27.0", + "react-use-measure": "^2.1.7", + "scheduler": "^0.21.0", + "suspend-react": "^0.1.3", + "zustand": "^3.7.1" + }, + "peerDependencies": { + "expo": ">=43.0", + "expo-asset": ">=8.4", + "expo-file-system": ">=11.0", + "expo-gl": ">=11.0", + "react": ">=18 <19", + "react-dom": ">=18 <19", + "react-native": ">=0.64", + "three": ">=0.133" + }, + "peerDependenciesMeta": { + "expo": { + "optional": true + }, + "expo-asset": { + "optional": true + }, + "expo-file-system": { + "optional": true + }, + "expo-gl": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/@react-three/fiber/node_modules/scheduler": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.21.0.tgz", + "integrity": "sha512-1r87x5fz9MXqswA2ERLo0EbOAU74DpIUO090gIasYTqlVoJeMcl+Z1Rg7WHz+qtPujhS/hGIt9kxZOYBV3faRQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, "node_modules/@remix-run/router": { "version": "1.18.0", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.18.0.tgz", @@ -3890,9 +4196,9 @@ "license": "MIT" }, "node_modules/@rushstack/eslint-patch": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.10.3.tgz", - "integrity": "sha512-qC/xYId4NMebE6w/V33Fh9gWxLgURiNYgVNObbJl2LZv0GUUItCcCqC5axQSwRaAgaxl2mELq1rMzlswaQ0Zxg==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.10.4.tgz", + "integrity": "sha512-WJgX9nzTqknM393q1QJDJmoW28kUfEnybeTfVNcNAPnIx210RXm2DiXiHzfNPJNIUUb1tJnz/l4QGtJ30PgWmA==", "license": "MIT" }, "node_modules/@simplewebauthn/browser": { @@ -4931,6 +5237,12 @@ "@types/node": "*" } }, + "node_modules/@types/offscreencanvas": { + "version": "2019.7.3", + "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.3.tgz", + "integrity": "sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==", + "license": "MIT" + }, "node_modules/@types/parse-json": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", @@ -4961,6 +5273,24 @@ "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", "license": "MIT" }, + "node_modules/@types/react": { + "version": "19.1.2", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.2.tgz", + "integrity": "sha512-oxLPMytKchWGbnQM9O7D67uPa9paTNxO7jVoNMXgkkErULBPhPARCfkKL9ytcIJJRGjbsVwW4ugJzyFFvm/Tiw==", + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-reconciler": { + "version": "0.26.7", + "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.26.7.tgz", + "integrity": "sha512-mBDYl8x+oyPX/VBb3E638N0B7xG+SPk/EAMcVPeexqus/5aTpTphQi0curhhshOqRrc9t6OPoJfEUkbymse/lQ==", + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/resolve": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", @@ -5039,6 +5369,12 @@ "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", "license": "MIT" }, + "node_modules/@types/webxr": { + "version": "0.5.22", + "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.22.tgz", + "integrity": "sha512-Vr6Stjv5jPRqH690f5I5GLjVk8GSsoQSYJ2FVd/3jJF7KaqfwPi3ehfBS96mlQ2kPCwZaX6U0rG2+NGHBKkA/A==", + "license": "MIT" + }, "node_modules/@types/ws": { "version": "8.5.11", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.11.tgz", @@ -5444,6 +5780,12 @@ "@xtuc/long": "4.2.2" } }, + "node_modules/@webgpu/glslang": { + "version": "0.0.15", + "resolved": "https://registry.npmjs.org/@webgpu/glslang/-/glslang-0.0.15.tgz", + "integrity": "sha512-niT+Prh3Aff8Uf1MVBVUsaNjFj9rJAKDXuoHIKiQbB+6IUP/3J3JIhBNyZ7lDhytvXxw6ppgnwKZdDJ08UMj4Q==", + "license": "glslang/LICENSE.txt" + }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", @@ -5759,6 +6101,26 @@ "react-dom": ">=16.9.0" } }, + "node_modules/antd-style": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/antd-style/-/antd-style-3.7.1.tgz", + "integrity": "sha512-CQOfddVp4aOvBfCepa+Kj2e7ap+2XBINg1Kn2osdE3oQvrD7KJu/K0sfnLcFLkgCJygbxmuazYdWLKb+drPDYA==", + "license": "MIT", + "dependencies": { + "@ant-design/cssinjs": "^1.21.1", + "@babel/runtime": "^7.24.1", + "@emotion/cache": "^11.11.0", + "@emotion/css": "^11.11.2", + "@emotion/react": "^11.11.4", + "@emotion/serialize": "^1.1.3", + "@emotion/utils": "^1.2.1", + "use-merge-value": "^1.2.0" + }, + "peerDependencies": { + "antd": ">=5.8.1", + "react": ">=18" + } + }, "node_modules/any-promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", @@ -6080,9 +6442,9 @@ } }, "node_modules/axe-core": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.9.1.tgz", - "integrity": "sha512-QbUdXJVTpvUTHU7871ppZkdOLBeGUKBQWHkHrvN2V9IQWGMt61zf3B45BtzjxEJzYuj0JBjBZP/hmYS/R9pmAw==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.0.tgz", + "integrity": "sha512-Mr2ZakwQ7XUAjp7pAwQWRhhK8mQQ6JAaNWSjmjxil0R8BPioMtQsTLOolGYkji1rcL++3dCqZA3zWqpT+9Ew6g==", "license": "MPL-2.0", "engines": { "node": ">=4" @@ -6415,6 +6777,26 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/batch": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", @@ -6609,6 +6991,30 @@ "node-int64": "^0.4.0" } }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -6784,6 +7190,20 @@ "integrity": "sha512-+67P1GkJRaxQD6PKK0Et9DhwQB+vGg3PM5+aavopCpZT1lj9jeqfvpgTLAWErNj8qApkkmXlu/Ug74kmhagkXg==", "license": "MIT" }, + "node_modules/chevrotain": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-10.5.0.tgz", + "integrity": "sha512-Pkv5rBY3+CsHOYfV5g/Vs5JY9WTHHDEKOlohI2XeygaZhUeqhAlldZ8Hz9cRmxu709bvS08YzxHdTPHhffc13A==", + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/cst-dts-gen": "10.5.0", + "@chevrotain/gast": "10.5.0", + "@chevrotain/types": "10.5.0", + "@chevrotain/utils": "10.5.0", + "lodash": "4.17.21", + "regexp-to-ast": "0.5.0" + } + }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -8022,12 +8442,15 @@ } }, "node_modules/dotenv": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", - "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", + "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", "license": "BSD-2-Clause", "engines": { - "node": ">=10" + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" } }, "node_modules/dotenv-expand": { @@ -8036,6 +8459,12 @@ "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==", "license": "BSD-2-Clause" }, + "node_modules/draco3d": { + "version": "1.5.7", + "resolved": "https://registry.npmjs.org/draco3d/-/draco3d-1.5.7.tgz", + "integrity": "sha512-m6WCKt/erDXcw+70IJXnG7M3awwQPAsZvJGX5zY7beBqpELw6RDGkYVU0W43AFxye4pDZ5i2Lbyc/NNGqwjUVQ==", + "license": "Apache-2.0" + }, "node_modules/duplexer": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", @@ -8494,6 +8923,19 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint-config-prettier": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", + "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, "node_modules/eslint-config-react-app": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-7.0.1.tgz", @@ -8828,6 +9270,37 @@ "eslint": ">=7.0.0" } }, + "node_modules/eslint-plugin-prettier": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.1.tgz", + "integrity": "sha512-gH3iR3g4JfF+yYPaJYkN7jEl9QbweL/YfkoRlNnuIEHEz1vHVlCmWOS+eGGiRuzHQXdJFCOTxRgvju9b8VUmrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.9.1" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": "*", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, "node_modules/eslint-plugin-promise": { "version": "6.6.0", "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.6.0.tgz", @@ -9460,6 +9933,13 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "license": "MIT" }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/fast-glob": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", @@ -9536,6 +10016,12 @@ "bser": "2.1.1" } }, + "node_modules/fflate": { + "version": "0.6.10", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.6.10.tgz", + "integrity": "sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==", + "license": "MIT" + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -9687,6 +10173,12 @@ "url": "https://github.com/avajs/find-cache-dir?sponsor=1" } }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "license": "MIT" + }, "node_modules/find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -10347,6 +10839,29 @@ "integrity": "sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g==", "license": "(Apache-2.0 OR MPL-1.1)" }, + "node_modules/has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-ansi/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/has-bigints": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", @@ -10437,6 +10952,21 @@ "he": "bin/he" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, "node_modules/hoopy": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", @@ -10744,6 +11274,26 @@ "node": ">=4" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/ignore": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", @@ -10816,6 +11366,16 @@ "node": ">=0.8.19" } }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -11504,6 +12064,27 @@ "set-function-name": "^2.0.1" } }, + "node_modules/its-fine": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/its-fine/-/its-fine-1.2.5.tgz", + "integrity": "sha512-fXtDA0X0t0eBYAGLVM5YsgJGsJ5jEmqZEPrGbzdf5awjv0xE7nqv3TVnvtUF060Tkes15DbDAKW/I48vsb6SyA==", + "license": "MIT", + "dependencies": { + "@types/react-reconciler": "^0.28.0" + }, + "peerDependencies": { + "react": ">=18.0" + } + }, + "node_modules/its-fine/node_modules/@types/react-reconciler": { + "version": "0.28.9", + "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.28.9.tgz", + "integrity": "sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*" + } + }, "node_modules/jackspeak": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", @@ -13844,6 +14425,12 @@ "node": ">=4.0" } }, + "node_modules/keycloak-js": { + "version": "26.1.5", + "resolved": "https://registry.npmjs.org/keycloak-js/-/keycloak-js-26.1.5.tgz", + "integrity": "sha512-5m8DQceKgBD+iVyy8GYeOiH3Hu12q6azAxhBRabVnZANt5BEi9r/qEXyOeye9yzrjqPF6RlH48TTTYNd1iXZMQ==", + "license": "Apache-2.0" + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -13880,6 +14467,12 @@ "node": ">= 8" } }, + "node_modules/ktx-parse": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/ktx-parse/-/ktx-parse-0.4.5.tgz", + "integrity": "sha512-MK3FOody4TXbFf8Yqv7EBbySw7aPvEcPX++Ipt6Sox+/YMFvR5xaTyhfNSk1AEmMy+RYIw81ctN4IMxCB8OAlg==", + "license": "MIT" + }, "node_modules/language-subtag-registry": { "version": "0.3.23", "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", @@ -14067,6 +14660,91 @@ "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", "license": "MIT" }, + "node_modules/loglevel": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.1.tgz", + "integrity": "sha512-hP3I3kCrDIMuRwAwHltphhDM1r8i55H33GgqjXbrisuJhF4kRhW1dNuxsRklp4bXl8DSdLaNLuiL4A/LWRfxvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + }, + "funding": { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/loglevel" + } + }, + "node_modules/loglevel-colored-level-prefix": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/loglevel-colored-level-prefix/-/loglevel-colored-level-prefix-1.0.0.tgz", + "integrity": "sha512-u45Wcxxc+SdAlh4yeF/uKlC1SPUPCy0gullSNKXod5I4bmifzk+Q4lSLExNEVn19tGaJipbZ4V4jbFn79/6mVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^1.1.3", + "loglevel": "^1.4.1" + } + }, + "node_modules/loglevel-colored-level-prefix/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/loglevel-colored-level-prefix/node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/loglevel-colored-level-prefix/node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/loglevel-colored-level-prefix/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/loglevel-colored-level-prefix/node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -14319,6 +14997,12 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/mmd-parser": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mmd-parser/-/mmd-parser-1.0.4.tgz", + "integrity": "sha512-Qi0VCU46t2IwfGv5KF0+D/t9cizcDug7qnNoy9Ggk7aucp0tssV8IwTMkBlDbm+VqAf3cdQHTCARKSsuS2MYFg==", + "license": "MIT" + }, "node_modules/moment": { "version": "2.30.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", @@ -14719,6 +15403,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/opentype.js": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/opentype.js/-/opentype.js-1.3.4.tgz", + "integrity": "sha512-d2JE9RP/6uagpQAVtJoF0pJJA/fgai89Cc50Yp0EJHk+eLp6QQ7gBoblsnubRULNY132I0J1QKMJ+JTbMqz4sw==", + "license": "MIT", + "dependencies": { + "string.prototype.codepointat": "^0.2.1", + "tiny-inflate": "^1.0.3" + }, + "bin": { + "ot": "bin/ot" + }, + "engines": { + "node": ">= 8.0.0" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -16395,6 +17095,12 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "license": "MIT" }, + "node_modules/potpack": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/potpack/-/potpack-1.0.2.tgz", + "integrity": "sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==", + "license": "ISC" + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -16404,6 +17110,267 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", + "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-eslint": { + "version": "16.3.0", + "resolved": "https://registry.npmjs.org/prettier-eslint/-/prettier-eslint-16.3.0.tgz", + "integrity": "sha512-Lh102TIFCr11PJKUMQ2kwNmxGhTsv/KzUg9QYF2Gkw259g/kPgndZDWavk7/ycbRvj2oz4BPZ1gCU8bhfZH/Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/parser": "^6.7.5", + "common-tags": "^1.4.0", + "dlv": "^1.1.0", + "eslint": "^8.7.0", + "indent-string": "^4.0.0", + "lodash.merge": "^4.6.0", + "loglevel-colored-level-prefix": "^1.0.0", + "prettier": "^3.0.1", + "pretty-format": "^29.7.0", + "require-relative": "^0.8.7", + "typescript": "^5.2.2", + "vue-eslint-parser": "^9.1.0" + }, + "engines": { + "node": ">=16.10.0" + }, + "peerDependencies": { + "prettier-plugin-svelte": "^3.0.0", + "svelte-eslint-parser": "*" + }, + "peerDependenciesMeta": { + "prettier-plugin-svelte": { + "optional": true + }, + "svelte-eslint-parser": { + "optional": true + } + } + }, + "node_modules/prettier-eslint/node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/prettier-eslint/node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/prettier-eslint/node_modules/@typescript-eslint/parser": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", + "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/prettier-eslint/node_modules/@typescript-eslint/scope-manager": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/prettier-eslint/node_modules/@typescript-eslint/types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/prettier-eslint/node_modules/@typescript-eslint/typescript-estree": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/prettier-eslint/node_modules/@typescript-eslint/visitor-keys": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/prettier-eslint/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prettier-eslint/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/prettier-eslint/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/prettier-eslint/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/prettier-eslint/node_modules/typescript": { + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", + "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/pretty-bytes": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", @@ -17514,6 +18481,31 @@ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "license": "MIT" }, + "node_modules/react-reconciler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.27.0.tgz", + "integrity": "sha512-HmMDKciQjYmBRGuuhIaKA1ba/7a+UsM5FzOZsMO2JYHt9Jh8reCb7j1eDC95NOyUlKM9KRyvdx0flBuDvYSBoA==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.21.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "react": "^18.0.0" + } + }, + "node_modules/react-reconciler/node_modules/scheduler": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.21.0.tgz", + "integrity": "sha512-1r87x5fz9MXqswA2ERLo0EbOAU74DpIUO090gIasYTqlVoJeMcl+Z1Rg7WHz+qtPujhS/hGIt9kxZOYBV3faRQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, "node_modules/react-refresh": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", @@ -17628,6 +18620,45 @@ } } }, + "node_modules/react-scripts/node_modules/dotenv": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", + "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=10" + } + }, + "node_modules/react-stl-viewer": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/react-stl-viewer/-/react-stl-viewer-2.5.0.tgz", + "integrity": "sha512-jlYId05N0P9rKVEdOfLO1bPsS9SYfMRxjnKvhks6T/c4HU0BxnNcZpRr4gfpJU0nFL6HHgmVKhKTh1LCHNcZuA==", + "license": "MIT", + "dependencies": { + "@react-three/fiber": "^8.15.5", + "three-stdlib": "2.17.2" + }, + "peerDependencies": { + "react": ">=18.0", + "react-dom": ">=18.0", + "three": ">=0.154" + } + }, + "node_modules/react-use-measure": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.7.tgz", + "integrity": "sha512-KrvcAo13I/60HpwGO5jpW7E9DfusKyLPLvuHlUyP5zqnmAPhNc6qTRjUQrdTADl0lpPpDVU2/Gg51UlOGHXbdg==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.13", + "react-dom": ">=16.13" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -17744,6 +18775,12 @@ "integrity": "sha512-TVILVSz2jY5D47F4mA4MppkBrafEaiUWJO/TcZHEIuI13AqoZMkK1WMA4Om1YkYbTx+9Ki1/tSUXbceyr9saRg==", "license": "MIT" }, + "node_modules/regexp-to-ast": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/regexp-to-ast/-/regexp-to-ast-0.5.0.tgz", + "integrity": "sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw==", + "license": "MIT" + }, "node_modules/regexp.prototype.flags": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", @@ -17852,6 +18889,13 @@ "node": ">=0.10.0" } }, + "node_modules/require-relative": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/require-relative/-/require-relative-0.8.7.tgz", + "integrity": "sha512-AKGr4qvHiryxRb19m3PsLRGuKVAbJLUD7E6eOaHkfKhwc+vSgVOCY5xNvm9EkolBKTOf0GrQAZKLimOCz81Khg==", + "dev": true, + "license": "MIT" + }, "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", @@ -19039,6 +20083,12 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "license": "MIT" }, + "node_modules/string.prototype.codepointat": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz", + "integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==", + "license": "MIT" + }, "node_modules/string.prototype.includes": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.0.tgz", @@ -19451,6 +20501,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/suspend-react": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/suspend-react/-/suspend-react-0.1.3.tgz", + "integrity": "sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ==", + "license": "MIT", + "peerDependencies": { + "react": ">=17.0" + } + }, "node_modules/svg-parser": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", @@ -19550,6 +20609,23 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "license": "MIT" }, + "node_modules/synckit": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.1.tgz", + "integrity": "sha512-7gr8p9TQP6RAHusBOSLs46F4564ZrjV8xFmw5zCmgmhGUcw2hxsShhJ6CEiHQMgPDwAQ1fWHPM0ypc4RMAig4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/tailwindcss": { "version": "3.4.6", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.6.tgz", @@ -19774,6 +20850,28 @@ "integrity": "sha512-LtuafkKHHzm61AQA1be2MAYIw1IjmhOUxhBa0prrLpEMWbV7ijvxCRHjSgHPGp2493wLBzwKV46tA9nivLEgKg==", "license": "MIT" }, + "node_modules/three-stdlib": { + "version": "2.17.2", + "resolved": "https://registry.npmjs.org/three-stdlib/-/three-stdlib-2.17.2.tgz", + "integrity": "sha512-7ZLCJJogtn1D1MlUi7q0iLUbrxj7K++YxjHIIz5AZ4wX4E137BgiiTmhH4XhAuvXGRk9ph3ZtoHTfJBXhqDX3w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.16.7", + "@types/offscreencanvas": "^2019.6.4", + "@webgpu/glslang": "^0.0.15", + "chevrotain": "^10.1.2", + "draco3d": "^1.4.1", + "fflate": "^0.6.9", + "ktx-parse": "^0.4.5", + "mmd-parser": "^1.0.4", + "opentype.js": "^1.3.3", + "potpack": "^1.0.1", + "zstddec": "^0.0.2" + }, + "peerDependencies": { + "three": ">=0.122.0" + } + }, "node_modules/throat": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.2.tgz", @@ -19795,6 +20893,12 @@ "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", "license": "MIT" }, + "node_modules/tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", + "license": "MIT" + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -19879,6 +20983,19 @@ "integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==", "license": "MIT" }, + "node_modules/ts-api-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, "node_modules/ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", @@ -20284,6 +21401,15 @@ "requires-port": "^1.0.0" } }, + "node_modules/use-merge-value": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-merge-value/-/use-merge-value-1.2.0.tgz", + "integrity": "sha512-DXgG0kkgJN45TcyoXL49vJnn55LehnrmoHc7MbKi+QDBvr8dsesqws8UlyIWGHMR+JXgxc1nvY+jDGMlycsUcw==", + "license": "MIT", + "peerDependencies": { + "react": ">= 16.x" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -20368,6 +21494,31 @@ "node": ">=0.10.48" } }, + "node_modules/vue-eslint-parser": { + "version": "9.4.3", + "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.3.tgz", + "integrity": "sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "eslint-scope": "^7.1.1", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.3.1", + "esquery": "^1.4.0", + "lodash": "^4.17.21", + "semver": "^7.3.6" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, "node_modules/w3c-hr-time": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", @@ -21401,6 +22552,29 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zstddec": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/zstddec/-/zstddec-0.0.2.tgz", + "integrity": "sha512-DCo0oxvcvOTGP/f5FA6tz2Z6wF+FIcEApSTu0zV5sQgn9hoT5lZ9YRAKUraxt9oP7l4e8TnNdi8IZTCX6WCkwA==", + "license": "MIT AND BSD-3-Clause" + }, + "node_modules/zustand": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-3.7.2.tgz", + "integrity": "sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA==", + "license": "MIT", + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + } + } } } } diff --git a/package.json b/package.json index 749cd68..f4c34b5 100644 --- a/package.json +++ b/package.json @@ -7,13 +7,18 @@ "@tsparticles/react": "^3.0.0", "@tsparticles/slim": "^3.5.0", "antd": "^5.19.2", + "antd-style": "^3.7.1", "axios": "*", + "dotenv": "^16.5.0", "gcode-preview": "^2.17.0", + "keycloak-js": "^26.1.5", "moment": "*", + "prop-types": "^15.8.1", "react": "*", "react-dom": "*", "react-router-dom": "*", "react-scripts": "*", + "react-stl-viewer": "^2.5.0", "socket.io-client": "*", "styled-components": "*", "three": "^0.166.1", @@ -21,7 +26,7 @@ "web-vitals": "*" }, "scripts": { - "start": "react-scripts start", + "dev": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" @@ -45,6 +50,13 @@ ] }, "devDependencies": { + "eslint": "^8.57.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.2.1", + "eslint-plugin-react": "^7.35.0", + "eslint-plugin-react-hooks": "^4.6.2", + "prettier": "^3.3.3", + "prettier-eslint": "^16.3.0", "standard": "^17.1.0" } } diff --git a/public/logo.svg b/public/logo.svg index 1a06d54..8004a88 100644 --- a/public/logo.svg +++ b/public/logo.svg @@ -1,18 +1,33 @@ - - - - - - Farm Control - - - - + + + + + + + + + + + + + + + + + + + - + + + + + + + diff --git a/src/App.jsx b/src/App.jsx index 5455e5a..db04ebf 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,89 +1,131 @@ -import React, { useContext, useState } from "react"; +import React from 'react' import { BrowserRouter as Router, Routes, Route, - Navigate, -} from "react-router-dom"; -import { App, ConfigProvider, theme } from "antd"; -import LoginUser from "./components/Auth/LoginUser.jsx"; -import RegisterPasskey from "./components/Auth/RegisterPasskey.jsx"; -import Profile from "./components/Dashboard/Profile.jsx"; -import Overview from "./components/Dashboard/Overview"; + Navigate +} from 'react-router-dom' +import { App, ConfigProvider, theme } from 'antd' +import AuthLayout from './components/Auth/AuthLayout.jsx' +import ProductionOverview from './components/Dashboard/Production/Overview' -import Printers from "./components/Dashboard/Printers/Printers"; -import EditPrinter from "./components/Dashboard/Printers/EditPrinter.jsx"; -import ControlPrinter from "./components/Dashboard/Printers/ControlPrinter.jsx"; +import Printers from './components/Dashboard/Production/Printers' +import ControlPrinter from './components/Dashboard/Production/Printers/ControlPrinter.jsx' +import PrinterInfo from './components/Dashboard/Production/Printers/PrinterInfo.jsx' -import PrintJobs from "./components/Dashboard/PrintJobs/PrintJobs.jsx"; +import PrintJobs from './components/Dashboard/Production/PrintJobs.jsx' +import PrintJobInfo from './components/Dashboard/Production/PrintJobs/PrintJobInfo.jsx' -import Fillaments from "./components/Dashboard/Fillaments/Fillaments.jsx"; +import Spools from './components/Dashboard/Inventory/Spools' -import GCodeFiles from "./components/Dashboard/GCodeFiles/GCodeFiles.jsx"; +import Filaments from './components/Dashboard/Management/Filaments' +import FilamentInfo from './components/Dashboard/Management/Filaments/FilamentInfo.jsx' -import Dashboard from "./components/Dashboard/common/Dashboard"; -import PrivateRoute from "./components/PrivateRoute"; -import PublicRoute from "./components/PublicRoute.jsx"; -import "./App.css"; -import { SocketProvider } from "./components/Dashboard/context/SocketContext.js"; -import { AuthProvider } from "./components/Auth/AuthContext.js"; +import GCodeFiles from './components/Dashboard/Production/GCodeFiles' +import GCodeFileInfo from './components/Dashboard/Production/GCodeFiles/GCodeFileInfo.jsx' + +import Parts from './components/Dashboard/Management/Parts.jsx' +import PartInfo from './components/Dashboard/Management/Parts/PartInfo.jsx' + +import Products from './components/Dashboard/Management/Products.jsx' +import ProductInfo from './components/Dashboard/Management/Products/ProductInfo.jsx' + +import Dashboard from './components/Dashboard/common/Dashboard' +import PrivateRoute from './components/PrivateRoute' +import PublicRoute from './components/PublicRoute.jsx' +import './App.css' +import { SocketProvider } from './components/Dashboard/context/SocketContext.js' +import { AuthProvider } from './components/Auth/AuthContext.js' +import { SpotlightProvider } from './components/Dashboard/context/SpotlightContext.js' +import Vendors from './components/Dashboard/Management/Vendors' +import VendorInfo from './components/Dashboard/Management/Vendors/VendorInfo' const FarmControlApp = () => { return ( - - - } />} - /> + + + + ( + + )} + /> + } + /> + } />} + /> - } /> - } - /> + } />} + > + } /> + } /> + } + /> + } /> + } /> + } /> + } /> + } /> + - } />} - > - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - - - + } />} + > + } /> + + + } />} + > + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + + - ); -}; + ) +} -export default FarmControlApp; +export default FarmControlApp diff --git a/src/App.test.js b/src/App.test.js deleted file mode 100644 index 1f03afe..0000000 --- a/src/App.test.js +++ /dev/null @@ -1,8 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import App from './App'; - -test('renders learn react link', () => { - render(); - const linkElement = screen.getByText(/learn react/i); - expect(linkElement).toBeInTheDocument(); -}); diff --git a/src/assets/icons/fillamenticon.afdesign b/src/assets/icons/fillamenticon.afdesign deleted file mode 100644 index 3e5d04a..0000000 Binary files a/src/assets/icons/fillamenticon.afdesign and /dev/null differ diff --git a/src/assets/icons/fillamenticon.svg b/src/assets/icons/fillamenticon.svg deleted file mode 100644 index 52ae03f..0000000 --- a/src/assets/icons/fillamenticon.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/components/Auth/AuthContext.js b/src/components/Auth/AuthContext.js index 902c5d3..b38bdef 100644 --- a/src/components/Auth/AuthContext.js +++ b/src/components/Auth/AuthContext.js @@ -1,223 +1,266 @@ // src/contexts/AuthContext.js -import React, { createContext, useState, useEffect, useCallback } from "react"; -import axios from "axios"; -import { message } from "antd"; +import React, { createContext, useState, useCallback, useEffect } from 'react' +import axios from 'axios' +import { message, Modal, notification, Progress, Button, Space } from 'antd' +import PropTypes from 'prop-types' import { - startAuthentication, - startRegistration, -} from "@simplewebauthn/browser"; + ExclamationCircleOutlined, + InfoCircleOutlined +} from '@ant-design/icons' -const AuthContext = createContext(); +const AuthContext = createContext() const AuthProvider = ({ children }) => { - const [messageApi, contextHolder] = message.useMessage(); - const [token, setToken] = useState( - localStorage.getItem("access_token") || null - ); - const [loading, setLoading] = useState(false); + const [messageApi, contextHolder] = message.useMessage() + const [notificationApi, notificationContextHolder] = + notification.useNotification() + const [authenticated, setAuthenticated] = useState(false) + const [loading, setLoading] = useState(false) + const [token, setToken] = useState(null) + const [expiresAt, setExpiresAt] = useState(null) + const [userProfile, setUserProfile] = useState(null) + const [showSessionExpiredModal, setShowSessionExpiredModal] = useState(false) + const [showUnauthorizedModal, setShowUnauthorizedModal] = useState(false) - const validateToken = useCallback(async (token) => { - if (!token) { - return false; - } - setLoading(true); - try { - const reponse = await axios.post( - "http://localhost:8080/auth/validate-token", - { - token, - } - ); - if (reponse.data.status === "OK") { - setLoading(false); - return true; - } - } catch (error) { - console.error("Invalid token", error); - messageApi.error("Session invalid."); - setToken(null); - localStorage.removeItem("access_token"); - } - setLoading(false); - return false; - }, [messageApi]); + const logout = useCallback((redirectUri = '/login') => { + setAuthenticated(false) + setToken(null) + setExpiresAt(null) + setUserProfile(null) + window.location.href = `http://localhost:8080/auth/logout?redirect_uri=${encodeURIComponent(redirectUri)}` + }, []) - const getAuthMode = useCallback(async (email) => { - if (!email) { - return { successful: false }; - } - setLoading(true); + // Login using query parameters + const loginWithSSO = useCallback( + (redirectUri = window.location.pathname + window.location.search) => { + messageApi.info('Logging in with tombutcher.work') + window.location.href = `http://localhost:8080/auth/login?redirect_uri=${encodeURIComponent(redirectUri)}` + }, + [messageApi] + ) + // Function to check if the user is logged in + const checkAuthStatus = useCallback(async () => { + setLoading(true) try { - const response = await axios.post("http://localhost:8080/auth/modes", { - email, - }); - const { authModes } = response.data; - setLoading(false); - return { successful: true, authModes }; - } catch (error) { - if (error.response === undefined) { - messageApi.error( - "An error occoured obtaining the auth mode: " + error.message - ); + // Make a call to your backend to check auth status + const response = await axios.get('http://localhost:8080/auth/user', { + withCredentials: true // Important for including cookies + }) + + if (response.status === 200 && response.data) { + console.log('User is authenticated!') + setAuthenticated(true) + setToken(response.data.access_token) + setExpiresAt(response.data.expires_at) + setUserProfile(response.data) } else { - if (error.response.status === 400) { - messageApi.error(error.response.data.error); - } else { - messageApi.error( - "An unexpected error occoured obtaining the auth mode. (" + - error.response.status + - ")" - ); - } + setAuthenticated(false) } - } - setLoading(false); - return { successful: false }; - }, [messageApi]); - - const handleLoginFinished = (user, access_token) => { - setToken(access_token); - localStorage.setItem("access_token", access_token); - messageApi.info("Welcome, " + user.name + "."); - return { successful: true, hasPasskey: user.hasPasskey }; - }; - - const loginWithPassword = useCallback(async (email, password) => { - if (!email || !password) { - return { successful: false }; - } - setLoading(true); - try { - const response = await axios.post("http://localhost:8080/auth/login", { - email, - password, - }); - const { user, access_token } = response.data; - return handleLoginFinished(user, access_token); } catch (error) { - if (error.response === undefined) { - messageApi.error("An error occoured: " + error.message); + console.log('Auth check failed', error) + if (error.response?.status === 401) { + setShowUnauthorizedModal(true) + } + setAuthenticated(false) + } finally { + setLoading(false) + } + }, []) + + const refreshToken = useCallback(async () => { + try { + const response = await axios.get('http://localhost:8080/auth/refresh', { + withCredentials: true + }) + if (response.status === 200 && response.data) { + setToken(response.data.access_token) + setExpiresAt(response.data.expires_at) + } + } catch (error) { + console.error('Token refresh failed', error) + } + }, []) + + const showTokenExpirationMessage = useCallback( + (expiresAt) => { + const now = new Date() + const expirationDate = new Date(expiresAt) + const timeRemaining = expirationDate - now + + if (timeRemaining <= 0) { + if (authenticated) { + setShowSessionExpiredModal(true) + setAuthenticated(false) + notificationApi.destroy('token-expiration') + } } else { - if (error.response.status === 400) { - messageApi.error(error.response.data.error); - } else { - messageApi.error( - "An unexpected error occoured. (" + error.response.status + ")" - ); + const minutes = Math.floor(timeRemaining / 60000) + const seconds = Math.floor((timeRemaining % 60000) / 1000) + + // Only show notification in the final minute + if (minutes === 0) { + const totalSeconds = 60 + const remainingSeconds = totalSeconds - seconds + const progress = (remainingSeconds / totalSeconds) * 100 + + notificationApi.info({ + message: 'Session Expiring Soon', + description: ( +
+
+ Your session will expire in {seconds} seconds +
+ +
+ ), + duration: 0, + key: 'token-expiration', + icon: null, + placement: 'bottomRight', + style: { + width: 360 + }, + className: 'token-expiration-notification', + closeIcon: null, + onClose: () => {}, + btn: ( + + ) + }) + } else if (minutes === 1) { + // Clear any existing notification when we enter the final minute + notificationApi.destroy('token-expiration') } } + }, + [authenticated, notificationApi] + ) + + const handleSessionExpiredModalOk = () => { + setShowSessionExpiredModal(false) + loginWithSSO() + } + + // Initialize on component mount + useEffect(() => { + let intervalId + + const tokenRefreshInterval = () => { + if (expiresAt) { + showTokenExpirationMessage(expiresAt) + } } - setLoading(false); - return { successful: false }; - }, [messageApi]); - const loginWithPasskey = useCallback(async (email) => { - if (!email) { - return { successful: false }; + if (authenticated) { + intervalId = setInterval(tokenRefreshInterval, 1000) } - setLoading(true); - try { - const loginOptionsResponse = await axios.post( - "http://localhost:8080/auth/passkey/login", - { email }, - { - headers: { - Authorization: `Bearer ${token}`, - }, - } - ); - const loginOptions = loginOptionsResponse.data; - console.log(loginOptions); - const attestationResponse = await startAuthentication(loginOptions); - - const loginResponse = await axios.post( - "http://localhost:8080/auth/passkey/login", { email, attestationResponse }, - { - headers: { - Authorization: `Bearer ${token}`, - }, - } - ); - const { user, access_token } = loginResponse.data; - return handleLoginFinished(user, access_token); - } catch (error) { - console.log(error); - messageApi.error("An error occoured: " + error.name); + return () => { + if (intervalId) { + clearInterval(intervalId) + } } - setLoading(false); - return { successful: false }; - }, [messageApi]); - - const logout = useCallback(() => { - setToken(null); - localStorage.removeItem("access_token"); - messageApi.info("Sucessfully logged out."); - }, [messageApi]); - - const registerPasskey = useCallback(async () => { - if (!token) { - return { successful: false }; - } - setLoading(true); - try { - const registerOptionsResponse = await axios.post( - "http://localhost:8080/auth/passkey/register", - { token }, - { - headers: { - Authorization: `Bearer ${token}`, - }, - } - ); - - const registerOptions = registerOptionsResponse.data; - console.log(registerOptions); - const attestationResponse = await startRegistration(registerOptions); - - await axios.post( - "http://localhost:8080/auth/passkey/register", - attestationResponse, - { - headers: { - Authorization: `Bearer ${token}`, - }, - } - ); - messageApi.success("Passkey registered!"); - setLoading(false); - return { successful: true }; - } catch (error) { - console.log(error); - messageApi.error("An error occoured: " + error.name); - } - setLoading(false); - return { successful: false }; - }, [messageApi]); + }, [expiresAt, authenticated, showTokenExpirationMessage]) useEffect(() => { - console.log("Token changed!" + token) - validateToken(token); - }, [token]); // if token changes, validate it. + checkAuthStatus() + }, [checkAuthStatus]) return ( <> {contextHolder} + {notificationContextHolder} {children} + + + Session Expired + + } + open={showSessionExpiredModal} + onOk={handleSessionExpiredModalOk} + okText='Log In' + style={{ maxWidth: 430 }} + closable={false} + centered + maskClosable={false} + footer={[ + + ]} + > + Your session has expired. Please log in again to continue. + + + + Please log in to continue + + } + open={showUnauthorizedModal} + onOk={() => { + setShowUnauthorizedModal(false) + loginWithSSO() + }} + okText='Log In' + style={{ maxWidth: 430 }} + closable={false} + centered + maskClosable={false} + footer={[ + + ]} + > + You need to be logged in to access FarmControl. Please log in with + tombutcher.work to continue. + - ); -}; + ) +} -export { AuthContext, AuthProvider }; +AuthProvider.propTypes = { + children: PropTypes.node.isRequired +} + +export { AuthContext, AuthProvider } diff --git a/src/components/Auth/AuthLayout.jsx b/src/components/Auth/AuthLayout.jsx index 20adf16..18150b3 100644 --- a/src/components/Auth/AuthLayout.jsx +++ b/src/components/Auth/AuthLayout.jsx @@ -1,29 +1,38 @@ -import React, { useContext } from "react"; -import { Spin, Flex, Card } from "antd"; -import { LoadingOutlined } from "@ant-design/icons"; -import { AuthContext } from "./AuthContext"; -import AuthParticles from "./AuthParticles"; -import "./Auth.css"; +import PropTypes from 'prop-types' +import React, { useContext } from 'react' +import { Spin, Flex, Card } from 'antd' +import { LoadingOutlined } from '@ant-design/icons' +import { AuthContext } from './AuthContext' +import AuthParticles from './AuthParticles' +import './Auth.css' const AuthLayout = ({ children }) => { - const { loading } = useContext(AuthContext); + const { loading } = useContext(AuthContext) return ( <> - } size="large"> + } + size='large' + > {children} - ); -}; + ) +} -export default AuthLayout; +AuthLayout.propTypes = { + children: PropTypes.node.isRequired +} + +export default AuthLayout diff --git a/src/components/Auth/AuthParticles.jsx b/src/components/Auth/AuthParticles.jsx index c147f02..e8f01fd 100644 --- a/src/components/Auth/AuthParticles.jsx +++ b/src/components/Auth/AuthParticles.jsx @@ -1,105 +1,108 @@ -import React, { useState, useEffect, useMemo, useCallback } from "react"; -import Particles, { initParticlesEngine } from "@tsparticles/react"; -import { loadSlim } from "@tsparticles/slim"; +import PropTypes from 'prop-types' +import React, { useState, useEffect, useMemo, useCallback } from 'react' +import Particles, { initParticlesEngine } from '@tsparticles/react' +import { loadSlim } from '@tsparticles/slim' -import "./Auth.css"; +import './Auth.css' const ParticlesComponent = React.memo(({ options, particlesLoaded }) => { return ( - ); -}); + ) +}) + +ParticlesComponent.displayName = 'ParticlesComponent' const AuthParticles = () => { - const [init, setInit] = useState(false); + const [init, setInit] = useState(false) // this should be run only once per application lifetime useEffect(() => { initParticlesEngine(async (engine) => { - await loadSlim(engine); + await loadSlim(engine) }).then(() => { - setInit(true); - }); - }, []); + setInit(true) + }) + }, []) - const particlesLoaded = useCallback((container) => { - console.log(container); - }, []); + const particlesLoaded = useCallback(() => { + console.log('Particles Loaded!') + }, []) const options = useMemo( () => ({ background: { color: { - value: "#141414", - }, + value: '#141414' + } }, fpsLimit: 120, interactivity: { events: { onClick: { enable: true, - mode: "push", + mode: 'push' }, onHover: { enable: true, - mode: "repulse", - }, + mode: 'repulse' + } }, modes: { push: { - quantity: 4, + quantity: 4 }, repulse: { distance: 200, - duration: 0.4, - }, - }, + duration: 0.4 + } + } }, particles: { color: { - value: "#ffffff", + value: '#ffffff' }, links: { - color: "#ffffff", + color: '#ffffff', distance: 150, enable: true, opacity: 0.5, - width: 1, + width: 1 }, move: { - direction: "none", + direction: 'none', enable: true, outModes: { - default: "bounce", + default: 'bounce' }, random: false, speed: 1, - straight: false, + straight: false }, number: { density: { - enable: true, + enable: true }, - value: 160, + value: 160 }, opacity: { - value: 0.5, + value: 0.5 }, shape: { - type: "circle", + type: 'circle' }, size: { - value: { min: 1, max: 5 }, - }, + value: { min: 1, max: 5 } + } }, - detectRetina: true, + detectRetina: true }), [] - ); + ) return ( <> {init && ( @@ -109,7 +112,12 @@ const AuthParticles = () => { /> )} - ); -}; + ) +} -export default AuthParticles; +ParticlesComponent.propTypes = { + options: PropTypes.object.isRequired, + particlesLoaded: PropTypes.func.isRequired +} + +export default AuthParticles diff --git a/src/components/Auth/LoginUser.jsx b/src/components/Auth/LoginUser.jsx index fcdc6f4..ac0a382 100644 --- a/src/components/Auth/LoginUser.jsx +++ b/src/components/Auth/LoginUser.jsx @@ -1,178 +1,52 @@ -import React, { useState, useContext, useEffect, useMemo, useRef } from "react"; -import axios from "axios"; -import { useNavigate } from "react-router-dom"; -import { - Form, - Input, - Button, - Checkbox, - Spin, - Divider, - Typography, - Flex, - Card, - Space, - message, -} from "antd"; -import { - UserOutlined, - LockOutlined, - LoginOutlined, - UserAddOutlined, - ArrowRightOutlined, -} from "@ant-design/icons"; -import Particles, { initParticlesEngine } from "@tsparticles/react"; -import { loadSlim } from "@tsparticles/slim"; -import { AuthContext } from "./AuthContext"; -import AuthLayout from "./AuthLayout"; +import React, { useContext } from 'react' +//import { useNavigate } from 'react-router-dom' +import { Form, Button, Divider, Typography, Flex } from 'antd' +import { UserAddOutlined } from '@ant-design/icons' +import { AuthContext } from './AuthContext' +import AuthLayout from './AuthLayout' -import PassKeysIcon from "../Icons/PassKeysIcon"; // Adjust the path if necessary +import './Auth.css' -import "./Auth.css"; - -const { Title, Text } = Typography; +const { Text } = Typography const LoginUser = () => { - const [email, setEmail] = useState(""); - const [password, setPassword] = useState(""); - const [authModes, setAuthModes] = useState([]); - const [error, setError] = useState(""); - const navigate = useNavigate(); - const { loginWithPassword, loginWithPasskey, getAuthMode } = - useContext(AuthContext); - - const handleLogin = async (e) => { - if (email === "") { - return; - } - if (authModes.length === 0) { - const result = await getAuthMode(email); - if (result.successful === true) { - setAuthModes(result.authModes); - } - return; - } - var result; - if (password.length > 0) { - result = await loginWithPassword(email, password); - } else { - result = await loginWithPasskey(email); - } - if (result.successful === true) { - if (authModes.includes("passkey")) { - setTimeout(() => { - navigate("/dashboard/overview"); - }, 200); - } else { - setTimeout(() => { - navigate("/login/register-passkey"); - }, 200); - } - } - }; + //const [error] = useState('') + //const navigate = useNavigate() + const { loginWithSSO } = useContext(AuthContext) + const handleLogin = async () => { + loginWithSSO('/production/overview') + } return ( - + Farm Control Logo

Farm Control

- - Please sign in using your credentials below. - + Please sign in below.
-
{ - handleLogin(e); - }} - > - - - } // Use UserOutlined icon - type="email" - value={email} - onChange={(e) => setEmail(e.target.value)} - placeholder="Email" - disabled={authModes.length > 0 ? true : false} - /> -
- {authModes.includes("password") ? ( -
{ - handleLogin(e); - }} - > - - } // Use LockOutlined icon - type="password" - value={password} - onChange={(e) => setPassword(e.target.value)} - placeholder="Password" - /> - - - - {authModes.includes("passkey") ? ( - - ) : ( - <> - )} - - - {error &&

{error}

} -
-
- ) : ( - <> - )} -
- ); -}; + ) +} -export default LoginUser; +export default LoginUser diff --git a/src/components/Auth/RegisterPasskey.jsx b/src/components/Auth/RegisterPasskey.jsx deleted file mode 100644 index 5c97a7c..0000000 --- a/src/components/Auth/RegisterPasskey.jsx +++ /dev/null @@ -1,69 +0,0 @@ -import React, { useState, useContext, useEffect, useMemo } from "react"; -import axios from "axios"; -import { useNavigate } from "react-router-dom"; -import { - Form, - Input, - Button, - Checkbox, - Spin, - Divider, - Typography, - Flex, - Card, - Space, -} from "antd"; -import { LockOutlined } from "@ant-design/icons"; -import Particles, { initParticlesEngine } from "@tsparticles/react"; -import { loadSlim } from "@tsparticles/slim"; -import { AuthContext } from "./AuthContext"; - -import PassKeysIcon from "../Icons/PassKeysIcon"; // Adjust the path if necessary - -import "./Auth.css"; -import AuthLayout from "./AuthLayout"; - -const { Title, Text } = Typography; - -const RegisterPasskey = () => { - const [email, setEmail] = useState(""); - const [password, setPassword] = useState(""); - const [error, setError] = useState(""); - const navigate = useNavigate(); - const { registerPasskey } = useContext(AuthContext); - const [init, setInit] = useState(false); - - const handleRegisterPasskey = async (e) => { - const result = await registerPasskey(email, password); - if (result.successful === true) { - setTimeout(() => { - navigate("/dashboard/overview"); - }, 500); - } else {} - }; - - return ( - - - -

Register a Passkey

- - Please setup a passkey in order to continue. The passkey may use - another device for encryption. - -
- -
- ); -}; - -export default RegisterPasskey; diff --git a/src/components/Dashboard/Fillaments/EditFillament.jsx b/src/components/Dashboard/Fillaments/EditFillament.jsx deleted file mode 100644 index 924d408..0000000 --- a/src/components/Dashboard/Fillaments/EditFillament.jsx +++ /dev/null @@ -1,205 +0,0 @@ -import React, { useEffect, useState, useContext } from 'react'; -import axios from 'axios'; -import { useLocation, useNavigate } from 'react-router-dom'; -import { Form, Input, InputNumber, Button, message, Spin, Typography, Select, Flex, Steps, Col, Row, Skeleton, ColorPicker, Upload, Descriptions, Badge, Popconfirm } from "antd"; -import { LoadingOutlined, UploadOutlined, BarcodeOutlined, LinkOutlined } from "@ant-design/icons"; - -import { AuthContext } from "../../Auth/AuthContext"; - -const { Title, Text } = Typography; - -const EditFillament = ({ id, onOk }) => { - const [messageApi, contextHolder] = message.useMessage(); - - const [dataLoading, setDataLoading] = useState(false); - const [editFillamentLoading, setEditFillamentLoading] = useState(false); - const [deleteFillamentLoading, setDeleteFillamentLoading] = useState(false); - - const [currentStep, setCurrentStep] = useState(0); - const [fillament, setFillament] = useState(null); - - const [imageList, setImageList] = useState([]); - const [image, setImage] = useState(""); - - const [editFillamentForm] = Form.useForm(); - const [editFillamentFormValues, setEditFillamentFormValues] = useState({}); - - const { token } = useContext(AuthContext); - - useEffect(() => { - // Fetch printer details when the component mounts - const fetchFillamentDetails = async () => { - if (id) { - try { - setDataLoading(true); - const response = await axios.get(`http://localhost:8080/fillaments/${id}`, { - headers: { - Authorization: `Bearer ${token}`, - }, - }); - setDataLoading(false); - editFillamentForm.setFieldsValue(response.data); // Set form values with fetched data - setEditFillamentFormValues(response.data); - } catch (error) { - messageApi.error('Error fetching printer details:' + error.message); - } - } - }; - fetchFillamentDetails(); - }, [id, editFillamentForm, token]); - - const handleEditFillament = async () => { - setEditFillamentLoading(true); - // Exclude the 'online' field from the submission - try { - await axios.put(`http://localhost:8080/fillaments/${id}`, editFillamentFormValues, { - headers: { - Authorization: `Bearer ${token}`, - } - }); - messageApi.success('Fillament details updated successfully.'); - onOk(); - } catch (error) { - messageApi.error('Error updating fillament details: ' + error.message); - } finally { - setEditFillamentLoading(false); - } - }; - - const handleDeleteFillament = async () => { - setDeleteFillamentLoading(true); - try { - await axios.delete(`http://localhost:8080/fillaments/${id}`, "", { - headers: { - Authorization: `Bearer ${token}`, - } - }); - messageApi.success('Fillament deleted successfully.'); - onOk(); - } catch (error) { - messageApi.error('Error updating fillament details: ' + error.message); - } finally { - setDeleteFillamentLoading(false); - } - }; - - const handleImageUpload = ({ file, onSuccess }) => { - const reader = new FileReader(); - reader.onload = (e) => { - console.log("Setting image buffer", e.target.result); - //setImage(e.target.result); - onSuccess("ok"); - }; - reader.readAsDataURL(file); - }; - return ( - <> - {contextHolder} - } size="large"> -
setEditFillamentFormValues((prevValues) => ({ - ...prevValues, - ...changedValues, - }))} - > - - - - - - - - - - - { - if (!value) return '£'; - return `£${value}`; - }} step={0.01} style={{ width: "100%" }} addonAfter="per kg" /> - - - { - return "#" + color.toHex(); - }} - > - - - - - - - - { setImageList(fileList) }} - > - - - - - } - /> - - - } - /> - - - - - - - - - - -
- -
- - ); -}; - -export default EditFillament; diff --git a/src/components/Dashboard/Fillaments/Fillaments.jsx b/src/components/Dashboard/Fillaments/Fillaments.jsx deleted file mode 100644 index 891d808..0000000 --- a/src/components/Dashboard/Fillaments/Fillaments.jsx +++ /dev/null @@ -1,170 +0,0 @@ -// src/fillaments.js - -import React, { useEffect, useState, useReducer, useContext } from "react"; -import axios from "axios"; -import moment from "moment"; -import { useNavigate, useOutletContext } from "react-router-dom"; -import { Table, Typography, Badge, Button, Flex, Progress, Space, Tooltip, Modal, Drawer, message } from "antd"; -import { InfoCircleOutlined, EditOutlined, LoadingOutlined, ControlOutlined, PlusOutlined, CopyOutlined } from "@ant-design/icons"; - -import { AuthContext } from "../../Auth/AuthContext"; -import { SocketContext } from "../context/SocketContext"; - -import NewFillament from "./NewFillament"; -import EditFillament from "./EditFillament"; - - -const { Title } = Typography; - -const Fillaments = () => { - const [messageApi, contextHolder] = message.useMessage(); - const initialState = { - error: null, - }; - - const [fillamentsData, setFillamentsData] = useState([]); - - const [pagination, setPagination] = useState({ - current: 1, - pageSize: 10, - total: 0, - }); - - const [newFillamentOpen, setNewFillamentOpen] = useState(false); - const [newFillament, setNewFillament] = useState(null); - - const [loading, setLoading] = useState(true); - - const [editFillamentOpen, setEditFillamentOpen] = useState(false); - const [editFillament, setEditFillament] = useState(null); - - const { token, logout } = useContext(AuthContext); - const { socket } = useContext(SocketContext); - - const navigate = useNavigate(); - - const fetchFillamentsData = async () => { - try { - const response = await axios.get("http://localhost:8080/fillaments", { - params: { - page: 1, - limit: 25, - }, - headers: { - Authorization: `Bearer ${token}`, - }, - }); - setFillamentsData(response.data); - setLoading(false); - //setPagination({ ...pagination, total: response.data.totalItems }); // Update total count - } catch (err) { - console.error(err); - } - }; - - useEffect(() => { - // Fetch initial data - fetchFillamentsData(); - - }, [token]); - - // Column definitions - const columns = [ - { - title: "Name", - dataIndex: "name", - key: "name", - - }, - { - title: "Brand", - dataIndex: "brand", - key: "brand", - - }, - { - title: "Material", - dataIndex: "type", - key: "type", - - }, - { - title: "Price", - dataIndex: "price", - key: "type", - render: (price) => { - return "£" + price + " per kg"; - }, - }, - { - title: "Colour", - dataIndex: "color", - key: "color", - render: (color) => { - return ; - }, - }, - { - title: "Updated At", - dataIndex: "updated_at", - key: "updated_at", - render: (updated_at) => { - if (updated_at !== null) { - const formattedDate = moment(updated_at.$date).format( - "YYYY-MM-DD HH:mm:ss" - ); - return {formattedDate}; - } else { - return "n/a"; - } - }, - }, - { - title: "Actions", - key: "operation", - fixed: "right", - width: 100, - render: (text, record) => { - return ( - - - - }} - /> - - { setNewFillamentOpen(false); }}> - { setNewFillamentOpen(false); fetchFillamentsData(); }} reset={newFillamentOpen}/> - - { setEditFillamentOpen(false); }}> - {editFillament} - - - ); -}; - -export default Fillaments; diff --git a/src/components/Dashboard/Fillaments/NewFillament.jsx b/src/components/Dashboard/Fillaments/NewFillament.jsx deleted file mode 100644 index e4c8d56..0000000 --- a/src/components/Dashboard/Fillaments/NewFillament.jsx +++ /dev/null @@ -1,331 +0,0 @@ -import React, { useEffect, useState, useContext } from 'react'; -import axios from 'axios'; -import { useLocation, useNavigate } from 'react-router-dom'; -import { Form, Input, InputNumber, Button, message, Spin, Typography, Select, Flex, Steps, Col, Row, Divider, ColorPicker, Upload, Descriptions, Badge } from "antd"; -import { LoadingOutlined, UploadOutlined, BarcodeOutlined, LinkOutlined } from "@ant-design/icons"; - -import { AuthContext } from "../../Auth/AuthContext"; - -const { Title, Text } = Typography; - -const initialNewFillamentForm = { - name: "", - brand: "", - type: "", - price: 0, - color: "#FFFFFF", - diameter: "1.75", - image: null, - url: "", - barcode: "", -}; - -const NewFillament = ({ onOk, reset }) => { - const [messageApi, contextHolder] = message.useMessage(); - - const [newFillamentLoading, setNewFillamentLoading] = useState(false); - const [currentStep, setCurrentStep] = useState(0); - const [nextEnabled, setNextEnabled] = useState(false); - - const [newFillamentForm] = Form.useForm(); - const [newFillamentFormValues, setNewFillamentFormValues] = useState(initialNewFillamentForm); - - const [imageList, setImageList] = useState([]); - - const newFillamentFormUpdateValues = Form.useWatch([], newFillamentForm); - - const { token } = useContext(AuthContext); - - React.useEffect(() => { - newFillamentForm - .validateFields({ - validateOnly: true, - }) - .then(() => setNextEnabled(true)) - .catch(() => setNextEnabled(false)); - }, [newFillamentForm, newFillamentFormUpdateValues]); - - const summaryItems = [ - { - key: 'name', - label: 'Name', - children: newFillamentFormValues.name, - }, - { - key: 'brand', - label: 'Brand', - children: newFillamentFormValues.brand, - }, - { - key: 'type', - label: 'Material', - children: newFillamentFormValues.type, - }, - { - key: 'price', - label: 'Price', - children: "£" + newFillamentFormValues.price + " per kg", - }, - { - key: 'color', - label: 'Colour', - children: () - }, - { - key: 'diameter', - label: 'Diameter', - children: newFillamentFormValues.diameter + "mm", - }, - { - key: 'image', - label: 'Image', - children: (), - }, - { - key: 'url', - label: 'URL', - children: newFillamentFormValues.url, - }, - { - key: 'barcode', - label: 'Barcode', - children: newFillamentFormValues.barcode, - }, - ]; - - React.useEffect(() => { - console.log("reset changed") - if (reset) { - console.log("resetting") - newFillamentForm.resetFields(); - } - }, [reset, newFillamentForm]) - - - const handleNewFillament = async () => { - setNewFillamentLoading(true); - try { - await axios.post(`http://localhost:8080/fillaments`, newFillamentFormValues, { - headers: { - Authorization: `Bearer ${token}`, - } - }); - messageApi.success('New fillament created successfully.'); - onOk(); - } catch (error) { - messageApi.error('Error creating new fillament: ' + error.message); - } finally { - setNewFillamentLoading(false); - } - }; - - const getBase64 = (file) => { - return new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.readAsDataURL(file); - reader.onload = () => resolve(reader.result); - reader.onerror = (error) => reject(error); - }); - }; - - const handleImageUpload = async ({ file, fileList }) => { - console.log(fileList); - if (fileList.length == 0) { - setImageList(fileList) - newFillamentForm.setFieldsValue({ image: "" }); - return; - } - const base64 = await getBase64(file); - setNewFillamentFormValues((prevValues) => ({ - ...prevValues, - image: base64, - })); - fileList[0].name = "Fillament Image" - setImageList(fileList) - newFillamentForm.setFieldsValue({ image: base64 }); - }; - - const steps = [ - { - title: 'Required', - key: 'required', - content: ( - <> - - Required information: - - - - - - - - - - - - - { - if (!value) return '£'; - return `£${value}`; - }} step={0.01} style={{ width: "100%" }} addonAfter="per kg" /> - - - ), - }, - { - title: 'Optional', - key: 'optional', - content: ( - <> - - Optional information: - - - { - return "#" + color.toHex(); - }} - > - - - - - - - (Array.isArray(e) ? e : e && e.fileList)}> - false} // Prevent automatic upload - onChange={handleImageUpload} - > - - - - - } - /> - - - } - /> - - - ), - }, - { - title: 'Summary', - key: 'done', - content: ( - - - - ), - }, - ]; - - return ( - - {contextHolder} - - - - - - - - - New Fillament -
setNewFillamentFormValues((prevValues) => ({ - ...prevValues, - ...changedValues, - }))} - initialValues={initialNewFillamentForm} - > - {steps[currentStep].content} - - - - {currentStep < steps.length - 1 && ( - - )} - {currentStep === steps.length - 1 && ( - - )} - - -
- - - - - ); -}; - -export default NewFillament; diff --git a/src/components/Dashboard/GCodeFiles/EditGCodeFile.jsx b/src/components/Dashboard/GCodeFiles/EditGCodeFile.jsx deleted file mode 100644 index f283b7a..0000000 --- a/src/components/Dashboard/GCodeFiles/EditGCodeFile.jsx +++ /dev/null @@ -1,205 +0,0 @@ -import React, { useEffect, useState, useContext } from 'react'; -import axios from 'axios'; -import { useLocation, useNavigate } from 'react-router-dom'; -import { Form, Input, InputNumber, Button, message, Spin, Typography, Select, Flex, Steps, Col, Row, Skeleton, ColorPicker, Upload, Descriptions, Badge, Popconfirm } from "antd"; -import { LoadingOutlined, UploadOutlined, BarcodeOutlined, LinkOutlined } from "@ant-design/icons"; - -import { AuthContext } from "../../Auth/AuthContext"; - -const { Title, Text } = Typography; - -const EditFillament = ({ id, onOk }) => { - const [messageApi, contextHolder] = message.useMessage(); - - const [dataLoading, setDataLoading] = useState(false); - const [editFillamentLoading, setEditFillamentLoading] = useState(false); - const [deleteFillamentLoading, setDeleteFillamentLoading] = useState(false); - - const [currentStep, setCurrentStep] = useState(0); - const [fillament, setFillament] = useState(null); - - const [imageList, setImageList] = useState([]); - const [image, setImage] = useState(""); - - const [editFillamentForm] = Form.useForm(); - const [editFillamentFormValues, setEditFillamentFormValues] = useState({}); - - const { token } = useContext(AuthContext); - - useEffect(() => { - // Fetch printer details when the component mounts - const fetchFillamentDetails = async () => { - if (id) { - try { - setDataLoading(true); - const response = await axios.get(`http://localhost:8080/fillaments/${id}`, { - headers: { - Authorization: `Bearer ${token}`, - }, - }); - setDataLoading(false); - editFillamentForm.setFieldsValue(response.data); // Set form values with fetched data - setEditFillamentFormValues(response.data); - } catch (error) { - messageApi.error('Error fetching printer details:' + error.message); - } - } - }; - fetchFillamentDetails(); - }, [id, editFillamentForm]); - - const handleEditFillament = async () => { - setEditFillamentLoading(true); - // Exclude the 'online' field from the submission - try { - await axios.put(`http://localhost:8080/fillaments/${id}`, editFillamentFormValues, { - headers: { - Authorization: `Bearer ${token}`, - } - }); - messageApi.success('Fillament details updated successfully.'); - onOk(); - } catch (error) { - messageApi.error('Error updating fillament details: ' + error.message); - } finally { - setEditFillamentLoading(false); - } - }; - - const handleDeleteFillament = async () => { - setDeleteFillamentLoading(true); - try { - await axios.delete(`http://localhost:8080/fillaments/${id}`, "", { - headers: { - Authorization: `Bearer ${token}`, - } - }); - messageApi.success('Fillament deleted successfully.'); - onOk(); - } catch (error) { - messageApi.error('Error updating fillament details: ' + error.message); - } finally { - setDeleteFillamentLoading(false); - } - }; - - const handleImageUpload = ({ file, onSuccess }) => { - const reader = new FileReader(); - reader.onload = (e) => { - console.log("Setting image buffer", e.target.result); - //setImage(e.target.result); - onSuccess("ok"); - }; - reader.readAsDataURL(file); - }; - return ( - <> - {contextHolder} - } size="large"> -
setEditFillamentFormValues((prevValues) => ({ - ...prevValues, - ...changedValues, - }))} - > - - - - - - - - - - - { - if (!value) return '£'; - return `£${value}`; - }} step={0.01} style={{ width: "100%" }} addonAfter="per kg" /> - - - { - return "#" + color.toHex(); - }} - > - - - - - - - - { setImageList(fileList) }} - > - - - - - } - /> - - - } - /> - - - - - - - - - - - - -
- - ); -}; - -export default EditFillament; diff --git a/src/components/Dashboard/GCodeFiles/GCodeFiles.jsx b/src/components/Dashboard/GCodeFiles/GCodeFiles.jsx deleted file mode 100644 index 35d86cc..0000000 --- a/src/components/Dashboard/GCodeFiles/GCodeFiles.jsx +++ /dev/null @@ -1,170 +0,0 @@ -// src/gcodefiles.js - -import React, { useEffect, useState, useReducer, useContext } from "react"; -import axios from "axios"; -import moment from "moment"; -import { useNavigate, useOutletContext } from "react-router-dom"; -import { Table, Typography, Badge, Button, Flex, Progress, Space, Tooltip, Modal, Drawer, message } from "antd"; -import { InfoCircleOutlined, EditOutlined, LoadingOutlined, ControlOutlined, PlusOutlined, CopyOutlined } from "@ant-design/icons"; - -import { AuthContext } from "../../Auth/AuthContext"; -import { SocketContext } from "../context/SocketContext"; - -import NewGCodeFile from "./NewGCodeFile"; -import EditGCodeFile from "./EditGCodeFile"; - - -const { Title } = Typography; - -const GCodeFiles = () => { - const [messageApi, contextHolder] = message.useMessage(); - const initialState = { - error: null, - }; - - const [gcodeFilesData, setGCodeFilesData] = useState([]); - - const [pagination, setPagination] = useState({ - current: 1, - pageSize: 10, - total: 0, - }); - - const [newGCodeFileOpen, setNewGCodeFileOpen] = useState(false); - const [newGCodeFile, setNewGCodeFile] = useState(null); - - const [loading, setLoading] = useState(true); - - const [editGCodeFileOpen, setEditGCodeFileOpen] = useState(false); - const [editGCodeFile, setEditGCodeFile] = useState(null); - - const { token, logout } = useContext(AuthContext); - const { socket } = useContext(SocketContext); - - const navigate = useNavigate(); - - const fetchGCodeFilesData = async () => { - try { - const response = await axios.get("http://localhost:8080/gcodefiles", { - params: { - page: 1, - limit: 25, - }, - headers: { - Authorization: `Bearer ${token}`, - }, - }); - setGCodeFilesData(response.data); - setLoading(false); - //setPagination({ ...pagination, total: response.data.totalItems }); // Update total count - } catch (err) { - console.error(err); - } - }; - - useEffect(() => { - // Fetch initial data - fetchGCodeFilesData(); - - }, [token]); - - // Column definitions - const columns = [ - { - title: "Name", - dataIndex: "name", - key: "name", - - }, - { - title: "Brand", - dataIndex: "brand", - key: "brand", - - }, - { - title: "Material", - dataIndex: "type", - key: "type", - - }, - { - title: "Price", - dataIndex: "price", - key: "type", - render: (price) => { - return "£" + price + " per kg"; - }, - }, - { - title: "Colour", - dataIndex: "color", - key: "color", - render: (color) => { - return ; - }, - }, - { - title: "Updated At", - dataIndex: "updated_at", - key: "updated_at", - render: (updated_at) => { - if (updated_at !== null) { - const formattedDate = moment(updated_at.$date).format( - "YYYY-MM-DD HH:mm:ss" - ); - return {formattedDate}; - } else { - return "n/a"; - } - }, - }, - { - title: "Actions", - key: "operation", - fixed: "right", - width: 100, - render: (text, record) => { - return ( - - - -
}} - /> - - { setNewGCodeFileOpen(false); }}> - { setNewGCodeFileOpen(false); fetchGCodeFilesData(); }} reset={newGCodeFileOpen}/> - - { setEditGCodeFileOpen(false); }}> - {editGCodeFile} - - - ); -}; - -export default GCodeFiles; diff --git a/src/components/Dashboard/GCodeFiles/NewGCodeFile.jsx b/src/components/Dashboard/GCodeFiles/NewGCodeFile.jsx deleted file mode 100644 index 52e9183..0000000 --- a/src/components/Dashboard/GCodeFiles/NewGCodeFile.jsx +++ /dev/null @@ -1,295 +0,0 @@ -import React, { useEffect, useState, useContext } from 'react'; -import axios from 'axios'; -import { useLocation, useNavigate } from 'react-router-dom'; -import { Form, Input, InputNumber, Button, message, Spin, Typography, Select, Flex, Steps, Col, Row, Divider, ColorPicker, Upload, Descriptions, Badge, } from "antd"; -import { LoadingOutlined, UploadOutlined, BarcodeOutlined, LinkOutlined } from "@ant-design/icons"; - -import { AuthContext } from "../../Auth/AuthContext"; - -import GCodeFileIcon from '../../Icons/GCodeFileIcon'; - -import FillamentSelect from '../common/FillamentSelect'; - -const { Dragger } = Upload; - - -const { Title, Text } = Typography; - -const initialNewGCodeFileForm = { - name: "", - brand: "", - type: "", - price: 0, - color: "#FFFFFF", - diameter: "1.75", - image: null, - url: "", - barcode: "", -}; - -const NewGCodeFile = ({ onOk, reset }) => { - const [messageApi, contextHolder] = message.useMessage(); - - const [newGCodeFileLoading, setNewGCodeFileLoading] = useState(false); - const [currentStep, setCurrentStep] = useState(0); - const [nextEnabled, setNextEnabled] = useState(false); - - const [newGCodeFileForm] = Form.useForm(); - const [newGCodeFileFormValues, setNewGCodeFileFormValues] = useState(initialNewGCodeFileForm); - - const [imageList, setImageList] = useState([]); - - const [gcode, setGCode] = useState(""); - - const newGCodeFileFormUpdateValues = Form.useWatch([], newGCodeFileForm); - - const { token } = useContext(AuthContext); - - React.useEffect(() => { - newGCodeFileForm - .validateFields({ - validateOnly: true, - }) - .then(() => setNextEnabled(true)) - .catch(() => setNextEnabled(false)); - }, [newGCodeFileForm, newGCodeFileFormUpdateValues]); - - const summaryItems = [ - { - key: 'name', - label: 'Name', - children: newGCodeFileFormValues.name, - }, - { - key: 'brand', - label: 'Brand', - children: newGCodeFileFormValues.brand, - }, - { - key: 'type', - label: 'Material', - children: newGCodeFileFormValues.type, - }, - { - key: 'price', - label: 'Price', - children: "£" + newGCodeFileFormValues.price + " per kg", - }, - { - key: 'color', - label: 'Colour', - children: () - }, - { - key: 'diameter', - label: 'Diameter', - children: newGCodeFileFormValues.diameter + "mm", - }, - { - key: 'image', - label: 'Image', - children: (), - }, - { - key: 'url', - label: 'URL', - children: newGCodeFileFormValues.url, - }, - { - key: 'barcode', - label: 'Barcode', - children: newGCodeFileFormValues.barcode, - }, - ]; - - React.useEffect(() => { - console.log("reset changed") - if (reset) { - console.log("resetting") - newGCodeFileForm.resetFields(); - } - }, [reset, newGCodeFileForm]) - - - const handleNewGCodeFile = async () => { - setNewGCodeFileLoading(true); - try { - await axios.post(`http://localhost:8080/gcodefiles`, newGCodeFileFormValues, { - headers: { - Authorization: `Bearer ${token}`, - } - }); - messageApi.success('New G Code file created successfully.'); - onOk(); - } catch (error) { - messageApi.error('Error creating new gcode file: ' + error.message); - } finally { - setNewGCodeFileLoading(false); - } - }; - - const getBase64 = (file) => { - return new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.readAsDataURL(file); - reader.onload = () => resolve(reader.result); - reader.onerror = (error) => reject(error); - }); - }; - - const handleImageUpload = async ({ file, fileList }) => { - console.log(fileList); - if (fileList.length == 0) { - setImageList(fileList) - newGCodeFileForm.setFieldsValue({ image: "" }); - return; - } - const base64 = await getBase64(file); - setNewGCodeFileFormValues((prevValues) => ({ - ...prevValues, - image: base64, - })); - fileList[0].name = "GCodeFile Image" - setImageList(fileList) - newGCodeFileForm.setFieldsValue({ image: base64 }); - }; - - const steps = [ - { - title: 'Details', - key: 'details', - content: ( - <> - - Please provide the following information: - - - - - - - - - - ), - }, - { - title: 'Upload', - key: 'upload', - content: ( - <> - (Array.isArray(e) ? e : e && e.fileList)}> - -

- -

-

Click or drag .gcode or .g file here.

-

- Support for a single or bulk upload. Strictly prohibited from uploading company data or other - banned files. -

- -
-
- - ), - }, - { - title: 'Preview', - key: 'preview', - content: ( - <> - - - ), - }, - { - title: 'Summary', - key: 'done', - content: ( - - - - ), - }, - ]; - - return ( - - {contextHolder} -
- - - - - - - - New G Code File -
setNewGCodeFileFormValues((prevValues) => ({ - ...prevValues, - ...changedValues, - }))} - initialValues={initialNewGCodeFileForm} - > - {steps[currentStep].content} - - - - {currentStep < steps.length - 1 && ( - - )} - {currentStep === steps.length - 1 && ( - - )} - - -
- - - - - ); -}; - -export default NewGCodeFile; diff --git a/src/components/Dashboard/Overview.jsx b/src/components/Dashboard/Overview.jsx deleted file mode 100644 index d0f6e3e..0000000 --- a/src/components/Dashboard/Overview.jsx +++ /dev/null @@ -1,44 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import DashboardLayout from './common/DashboardLayout'; -import axios from 'axios'; -import { useNavigate } from 'react-router-dom'; - -const Overview = ({ setToken }) => { - const [user, setUser] = useState(null); - const navigate = useNavigate(); - - useEffect(() => { - const fetchUserData = async () => { - const access_token = localStorage.getItem('access_token'); - if (access_token) { - try { - const response = await axios.get('http://localhost:8080/overview', { - headers: { - Authorization: `Bearer ${access_token}` - } - }); - //setUser(response.data); - } catch (err) { - console.error(err); - } - } - }; - fetchUserData(); - }, [setToken, navigate]); - - return ( -
-

Overview

- {user ? ( -
-

Welcome, {user.username}!

- -
- ) : ( -

Loading...

- )} -
- ); -}; - -export default Overview; diff --git a/src/components/Dashboard/PrintJobs/NewPrintJob.jsx b/src/components/Dashboard/PrintJobs/NewPrintJob.jsx deleted file mode 100644 index 3dcf272..0000000 --- a/src/components/Dashboard/PrintJobs/NewPrintJob.jsx +++ /dev/null @@ -1,56 +0,0 @@ -import React, { useEffect, useState, useContext } from 'react'; -import axios from 'axios'; -import { useLocation, useNavigate } from 'react-router-dom'; -import { Form, Input, Button, message, Spin, Typography, Tag, Flex, Steps } from "antd"; -import { LoadingOutlined } from "@ant-design/icons"; - -import { AuthContext } from "../../Auth/AuthContext"; - -const { Title } = Typography; - -const NewPrintJob = () => { - const navigate = useNavigate(); - const [form] = Form.useForm(); - const [loading, setLoading] = useState(false); - const [currentStep, setCurrentStep] = useState(false); - - - const { token } = useContext(AuthContext); - const handleFormSubmit = async (values) => { - setLoading(true); - // Exclude the 'online' field from the submission - const { online, remoteAddress, hostId, ...rest } = values; - try { - await axios.put(`http://localhost:8080/printers/${remoteAddress}`, rest, { - headers: { - Authorization: `Bearer ${token}`, - } - }); - message.success('Printer details updated successfully'); - - } catch (error) { - message.error('Error updating printer details'); - } finally { - setLoading(false); - } - }; - - return ( -
- Select G-Code - -
- ); -}; - -export default NewPrintJob; diff --git a/src/components/Dashboard/PrintJobs/PrintJobs.jsx b/src/components/Dashboard/PrintJobs/PrintJobs.jsx deleted file mode 100644 index 0ca9bdc..0000000 --- a/src/components/Dashboard/PrintJobs/PrintJobs.jsx +++ /dev/null @@ -1,285 +0,0 @@ -// src/PrintJobs.js - -import React, { useEffect, useState, useReducer, useContext } from "react"; -import axios from "axios"; -import moment from "moment"; -import { useNavigate, useOutletContext } from "react-router-dom"; -import { Table, Typography, Badge, Button, Flex, Progress, Space, Tooltip, Modal, message } from "antd"; -import { InfoCircleOutlined, EditOutlined, ControlOutlined, PlusOutlined, CopyOutlined, LoadingOutlined } from "@ant-design/icons"; - -import { AuthContext } from "../../Auth/AuthContext"; -import { SocketContext } from "../context/SocketContext"; - -import NewPrintJob from "./NewPrintJob"; - - -const { Title } = Typography; - -// Action types for reducer -const actionTypes = { - UPDATE_PRINTER_DATA: 'UPDATE_PRINTER_DATA', - FETCH_DATA_SUCCESS: 'FETCH_DATA_SUCCESS', - FETCH_DATA_FAILURE: 'FETCH_DATA_FAILURE', -}; - -// Reducer function to manage state updates -const reducer = (state, action) => { - switch (action.type) { - case actionTypes.UPDATE_PRINTER_DATA: - return { - ...state, - printerData: updatePrinterData(state.printerData, action.payload), - }; - case actionTypes.FETCH_DATA_SUCCESS: - return { - ...state, - printJobsData: action.payload, - error: null, - }; - case actionTypes.FETCH_DATA_FAILURE: - return { - ...state, - error: action.payload, - }; - default: - return state; - } -}; - -// Helper function to update printerData based on wsData -const updatePrinterData = (printerData, newData) => { - const updatedData = [...printerData]; // Copy current state - const existingIndex = updatedData.findIndex(printer => printer.remoteAddress === newData.remoteAddress); - - if (existingIndex !== -1) { - // Update existing entry - const existingEntry = updatedData[existingIndex]; - const updatedEntry = { ...existingEntry }; - - // Update only the parameters that exist in newData - for (const param in newData) { - if (newData.hasOwnProperty(param)) { - updatedEntry[param] = newData[param]; - } - } - - updatedData[existingIndex] = updatedEntry; - } else { - // Add new entry - updatedData.push(newData); - } - - return updatedData; -}; - -const PrintJobs = () => { - const [messageApi, contextHolder] = message.useMessage(); - const initialState = { - printJobsData: [], - error: null, - }; - const [pagination, setPagination] = useState({ - current: 1, - pageSize: 10, - total: 0, - }); - - const [state, dispatch] = useReducer(reducer, initialState); - const [newPrintJobOpen, setNewPrintJobOpen] = useState(false); - - const [loading, setLoading] = useState(true); - - const { token, logout } = useContext(AuthContext); - const { socket } = useContext(SocketContext); - - const navigate = useNavigate(); - - useEffect(() => { - // Fetch initial data - const fetchData = async () => { - try { - const response = await axios.get("http://localhost:8080/printjobs", { - params: { - page: 1, - limit: 25, - }, - headers: { - Authorization: `Bearer ${token}`, - }, - }); - setLoading(false); - dispatch({ type: actionTypes.FETCH_DATA_SUCCESS, payload: response.data }); - //setPagination({ ...pagination, total: response.data.totalItems }); // Update total count - } catch (err) { - console.error(err); - } - }; - - fetchData(); - - }, [token]); - - useEffect(() => { - if (socket) { - socket.on("status", (statusUpdate) => { - console.log("Received status:", statusUpdate); - dispatch({ type: "UPDATE_PRINTER_DATA", payload: statusUpdate }); - }); - - return () => { - socket.off("status"); - }; - } - }, [socket]); - - - const handleTableChange = (pagination, filters, sorter) => { - setPagination(pagination); // Update pagination state on table change - }; - - // Column definitions - const columns = [ - { - title: "ID", - dataIndex: "id", - key: "id", - render: (text) => ( - - {text.slice(-8)} - - - -
}} - /> - - - - - - ); -}; - -export default PrintJobs; diff --git a/src/components/Dashboard/Printers/ControlPrinter.jsx b/src/components/Dashboard/Printers/ControlPrinter.jsx deleted file mode 100644 index fba1695..0000000 --- a/src/components/Dashboard/Printers/ControlPrinter.jsx +++ /dev/null @@ -1,232 +0,0 @@ -import React, { - useEffect, - useState, - useContext, - useReducer, - useRef, -} from "react"; -import axios from "axios"; -import { useLocation, useNavigate } from "react-router-dom"; -import { - Form, - Input, - Button, - message, - Spin, - Typography, - Tag, - Flex, - Col, - Row, - Dropdown, - Space, - Card, - Upload, -} from "antd"; -import { LoadingOutlined, UploadOutlined } from "@ant-design/icons"; - -import { AuthContext } from "../../Auth/AuthContext"; -import { SocketContext } from "../context/SocketContext"; - -import DashboardTemperaturePanel from "../common/DashboardTemperaturePanel"; -import DashboardMovementPanel from "../common/DashboardMovementPanel"; - -const { Title } = Typography; - -// Action types for reducer -const actionTypes = { - UPDATE_PRINTER_DATA: "UPDATE_PRINTER_DATA", - FETCH_DATA_SUCCESS: "FETCH_DATA_SUCCESS", - FETCH_DATA_FAILURE: "FETCH_DATA_FAILURE", -}; - -// Reducer function to manage state updates -const reducer = (state, action) => { - switch (action.type) { - case actionTypes.UPDATE_PRINTER_DATA: - return { - ...state, - printerData: updatePrinterData(state.printerData, action.payload), - }; - case actionTypes.FETCH_DATA_SUCCESS: - return { - ...state, - printerData: action.payload, - error: null, - }; - case actionTypes.FETCH_DATA_FAILURE: - return { - ...state, - error: action.payload, - }; - default: - return state; - } -}; - -// Helper function to update printerData based on wsData -const updatePrinterData = (printerData, newData) => { - const updatedData = [...printerData]; // Copy current state - const existingIndex = updatedData.findIndex( - (printer) => printer.remoteAddress === newData.remoteAddress - ); - - if (existingIndex !== -1) { - // Update existing entry - updatedData[existingIndex] = { ...updatedData[existingIndex], ...newData }; - } else { - // Add new entry - updatedData.push(newData); - } - - return updatedData; -}; - -// Helper function to parse query parameters -const useQuery = () => { - return new URLSearchParams(useLocation().search); -}; - -const ControlPrinter = () => { - const initialState = { - printerData: [], - error: null, - }; - const query = useQuery(); - const remoteAddress = query.get("remoteAddress"); - const navigate = useNavigate(); - const [printer, setPrinter] = useState(null); - - const { token, logout } = useContext(AuthContext); - const { socket } = useContext(SocketContext); - - const [state, dispatch] = useReducer(reducer, initialState); - - useEffect(() => { - // Fetch printer details when the component mounts - const fetchPrinterDetails = async () => { - if (remoteAddress) { - try { - const response = await axios.get( - `http://localhost:8080/printers/${remoteAddress}`, - { - headers: { - Authorization: `Bearer ${token}`, - }, - } - ); - setPrinter(response.data); - } catch (error) { - message.error("Error fetching printer details"); - } - } - }; - fetchPrinterDetails(); - }, [token, logout, remoteAddress]); - - useEffect(() => { - const joinPrinterRoom = () => { - if (socket) { - socket.on("status", (statusUpdate) => { - console.log("Received status:", statusUpdate); - dispatch({ type: "UPDATE_WS_DATA", payload: statusUpdate }); - }); - - socket.emit("join", { remoteAddress }); - - return () => { - socket.off("status"); - socket.emit("leave", { remoteAddress }); - }; - } - }; - joinPrinterRoom(); - }, [socket, remoteAddress]); - - const sendCommand = (type, data) => { - const commandData = { - remoteAddress, - type, - data, - }; - socket.emit("command", commandData); - }; - - const handleUpload = (file) => { - const reader = new FileReader(); - reader.onload = () => { - sendCommand("writeToSD", { - filename: "test.g", - gcode: reader.result, - }); - message.success("File uploaded successfully"); - }; - reader.readAsText(file); - }; - - const handleUploadFileButtonClick = () => {}; - - const uploadProps = { - beforeUpload: (file) => { - const isGCODE = file.name.endsWith(".gcode"); - if (!isGCODE) { - message.error(`${file.name} is not a gcode file`); - } - return isGCODE || Upload.LIST_IGNORE; - }, - onChange: (info) => { - }, - }; - - return ( - - - { - handleUpload(file); - setTimeout(() => { - onSuccess("ok"); - }, 0); - }} - > - - - - - {printer ? ( - - - - - - - - - - - - - - Card content - - - - ) : ( - } size="large" /> - )} - - ); -}; - -export default ControlPrinter; diff --git a/src/components/Dashboard/Printers/EditPrinter.jsx b/src/components/Dashboard/Printers/EditPrinter.jsx deleted file mode 100644 index d03e85e..0000000 --- a/src/components/Dashboard/Printers/EditPrinter.jsx +++ /dev/null @@ -1,129 +0,0 @@ -import React, { useEffect, useState, useContext } from 'react'; -import DashboardLayout from '../common/DashboardLayout'; -import axios from 'axios'; -import { useLocation, useNavigate } from 'react-router-dom'; -import { Form, Input, Button, message, Spin, Typography, Tag, Flex, Popconfirm, Skeleton } from "antd"; -import { LoadingOutlined } from "@ant-design/icons"; - -import { AuthContext } from "../../Auth/AuthContext"; -import { SocketContext } from "../context/SocketContext"; - - -const { Title } = Typography; - -const EditPrinter = ({ remoteAddress, onOk }) => { - const [messageApi, contextHolder] = message.useMessage(); - const navigate = useNavigate(); - const [editPrinterForm] = Form.useForm(); - const [editLoading, setEditLoading] = useState(false); - const [deleteLoading, setDeleteLoading] = useState(false); - const [printer, setPrinter] = useState(null); - - const { token, logout } = useContext(AuthContext); - - useEffect(() => { - // Fetch printer details when the component mounts - const fetchPrinterDetails = async () => { - if (remoteAddress) { - try { - const response = await axios.get(`http://localhost:8080/printers/${remoteAddress}`, { - headers: { - Authorization: `Bearer ${token}`, - }, - }); - setPrinter(response.data); - editPrinterForm.setFieldsValue(response.data); // Set form values with fetched data - } catch (error) { - messageApi.error('Error fetching printer details:' + error.message); - } - } - }; - - fetchPrinterDetails(); - }, [remoteAddress, editPrinterForm]); - - const handleEdit = async (values) => { - setEditLoading(true); - // Exclude the 'online' field from the submission - const { online, remoteAddress, hostId, ...rest } = values; - try { - await axios.put(`http://localhost:8080/printers/${remoteAddress}`, rest, { - headers: { - Authorization: `Bearer ${token}`, - } - }); - messageApi.success('Printer details updated successfully.'); - onOk(); - } catch (error) { - messageApi.error('Error updating printer details: ' + error.message); - } finally { - setEditLoading(false); - } - }; - - const handleDelete = async () => { - setDeleteLoading(true); - try { - await axios.delete(`http://localhost:8080/printers/${remoteAddress}`, "", { - headers: { - Authorization: `Bearer ${token}`, - } - }); - messageApi.success('Printer details updated successfully.'); - - } catch (error) { - messageApi.error('Error updating printer details: ' + error.message); - } finally { - setDeleteLoading(false); - } - }; - - return ( - <> - {contextHolder} - -
- - - - - - - - - - - - - - - - - - - -
- - ); -}; - -export default EditPrinter; diff --git a/src/components/Dashboard/Printers/Printers.jsx b/src/components/Dashboard/Printers/Printers.jsx deleted file mode 100644 index a13f0c6..0000000 --- a/src/components/Dashboard/Printers/Printers.jsx +++ /dev/null @@ -1,270 +0,0 @@ -// src/Printers.js - -import React, { useEffect, useState, useReducer, useContext } from "react"; -import axios from "axios"; -import moment from "moment"; -import { useNavigate, useOutletContext } from "react-router-dom"; -import { Table, Typography, Badge, Button, Flex, Progress, Space, Drawer } from "antd"; -import { InfoCircleOutlined, EditOutlined, ControlOutlined, LoadingOutlined } from "@ant-design/icons"; - -import { AuthContext } from "../../Auth/AuthContext"; -import { SocketContext } from "../context/SocketContext"; - -import EditPrinter from "./EditPrinter" - -const { Title } = Typography; - -// Action types for reducer -const actionTypes = { - UPDATE_PRINTER_DATA: 'UPDATE_PRINTER_DATA', - FETCH_DATA_SUCCESS: 'FETCH_DATA_SUCCESS', - FETCH_DATA_FAILURE: 'FETCH_DATA_FAILURE', -}; - -// Reducer function to manage state updates -const reducer = (state, action) => { - switch (action.type) { - case actionTypes.UPDATE_PRINTER_DATA: - return { - ...state, - printerData: updatePrinterData(state.printerData, action.payload), - }; - case actionTypes.FETCH_DATA_SUCCESS: - return { - ...state, - printerData: action.payload, - error: null, - }; - case actionTypes.FETCH_DATA_FAILURE: - return { - ...state, - error: action.payload, - }; - default: - return state; - } -}; - -// Helper function to update printerData based on wsData -const updatePrinterData = (printerData, newData) => { - const updatedData = [...printerData]; // Copy current state - const existingIndex = updatedData.findIndex(printer => printer.remoteAddress === newData.remoteAddress); - - if (existingIndex !== -1) { - // Update existing entry - const existingEntry = updatedData[existingIndex]; - const updatedEntry = { ...existingEntry }; - - // Update only the parameters that exist in newData - for (const param in newData) { - if (newData.hasOwnProperty(param)) { - updatedEntry[param] = newData[param]; - } - } - - updatedData[existingIndex] = updatedEntry; - } else { - // Add new entry - updatedData.push(newData); - } - - return updatedData; -}; - -const Printers = () => { - const initialState = { - printerData: [], - error: null, - }; - const [pagination, setPagination] = useState({ - current: 1, - pageSize: 10, - total: 0, - }); - - const [state, dispatch] = useReducer(reducer, initialState); - - const [loading, setLoading] = useState(true); - - const [editPrinterOpen, setEditPrinterOpen] = useState(false); - const [editPrinter, setEditPrinter] = useState(null); - - - const { token, logout } = useContext(AuthContext); - const { socket } = useContext(SocketContext); - - const navigate = useNavigate(); - - const fetchPrintersData = async () => { - try { - const response = await axios.get("http://localhost:8080/printers", { - params: { - page: 1, - limit: 25, - }, - headers: { - Authorization: `Bearer ${token}`, - }, - }); - setLoading(false); - dispatch({ type: actionTypes.FETCH_DATA_SUCCESS, payload: response.data }); - //setPagination({ ...pagination, total: response.data.totalItems }); // Update total count - } catch (err) { - console.error(err); - } - }; - useEffect(() => { - // Fetch initial data - fetchPrintersData(); - }, [token]); - - useEffect(() => { - if (socket) { - socket.on("status", (statusUpdate) => { - console.log("Received status:", statusUpdate); - dispatch({ type: "UPDATE_PRINTER_DATA", payload: statusUpdate }); - }); - - return () => { - socket.off("status"); - }; - } - }, [socket]); - - - const handleTableChange = (pagination, filters, sorter) => { - setPagination(pagination); // Update pagination state on table change - }; - - const handleEdit = (remoteAddress) => { - setEditPrinter( { setEditPrinterOpen(false); fetchPrintersData(); }} />); - setEditPrinterOpen(true); - }; - - // Column definitions - const columns = [ - { - title: "Name", - dataIndex: "friendlyName", - key: "friendlyName", - }, - { - title: "Remote Addresss", - dataIndex: "remoteAddress", - key: "remoteAddress", - }, - { - title: "Host", - dataIndex: "hostId", - key: "hostId", - }, - { - title: 'Status', - key: 'status', - dataIndex: 'status', - render: (status) => { - let badgeStatus; - let badgeText; - - switch (status.type) { - case 'Online': - badgeStatus = 'success'; - badgeText = 'Online'; - break; - case 'Offline': - badgeStatus = 'default'; - badgeText = 'Offline'; - break; - case 'Initializing': - badgeStatus = 'warning'; - badgeText = 'Initializing'; - break; - case 'Printing': - badgeStatus = 'processing'; - badgeText = 'Printing'; - break; - case 'Processing': - badgeStatus = 'processing'; - badgeText = 'Processing'; - break; - case 'Idle': - badgeStatus = 'success'; - badgeText = 'Idle'; - break; - case 'Error': - badgeStatus = 'error'; - badgeText = 'Error'; - break; - default: - badgeStatus = 'default'; - badgeText = 'Unknown'; - } - - return ( - - ); - }, - }, - { - title: "Print Job", - dataIndex: "status", - key: "printJob", - width: "15%", - render: (status) => { - if (status.type == "Printing") { - return ( - - ); - } - }, - }, - { - title: "Connected At", - dataIndex: "connectedAt", - key: "connectedAt", - render: (connectedAt) => { - if (connectedAt !== null) { - const formattedDate = moment(connectedAt.$date).format( - "YYYY-MM-DD HH:mm:ss" - ); - return {formattedDate}; - } else { - return "n/a"; - } - }, - }, - { - title: "Actions", - key: "operation", - fixed: "right", - width: 100, - render: (text, record) => { - return ( - -
}} - /> - { setEditPrinterOpen(false); }}> - {editPrinter} - - - ); -}; - -export default Printers; diff --git a/src/components/Dashboard/Profile.jsx b/src/components/Dashboard/Profile.jsx deleted file mode 100644 index 88a42ae..0000000 --- a/src/components/Dashboard/Profile.jsx +++ /dev/null @@ -1,108 +0,0 @@ -import React, { useEffect, useState, useContext } from 'react'; -import DashboardLayout from './common/DashboardLayout'; -import axios from 'axios'; -import { useLocation, useNavigate } from 'react-router-dom'; -import { Form, Input, Button, message, Spin, Typography, Tag, Flex } from "antd"; -import { LoadingOutlined } from "@ant-design/icons"; - -import { AuthContext } from "../Auth/AuthContext"; - -const { Title } = Typography; - -const Profile = () => { - - const navigate = useNavigate(); - const [form] = Form.useForm(); - const [loading, setLoading] = useState(false); - const [printer, setPrinter] = useState(null); - - const { token } = useContext(AuthContext); - - useEffect(() => { - // Fetch printer details when the component mounts - const fetchPrinterDetails = async () => { - if (token) { - try { - const response = await axios.get(`http://localhost:8080/profile`, { - headers: { - Authorization: `Bearer ${token}`, - }, - }); - setPrinter(response.data); - form.setFieldsValue(response.data); // Set form values with fetched data - } catch (error) { - message.error('Error fetching printer details'); - } - } - }; - - fetchPrinterDetails(); - }, [form]); - - const handleFormSubmit = async (values) => { - setLoading(true); - // Exclude the 'online' field from the submission - const { online, remoteAddress, hostId, ...rest } = values; - try { - await axios.put(`http://localhost:8080/profile`, rest, { - headers: { - Authorization: `Bearer ${token}`, - } - }); - message.success('Profile updated successfully.'); - } catch (error) { - message.error('Error updating profile: ' + error.message); - } finally { - setLoading(false); - } - }; - - return ( -
- Edit Printer - {printer ? ( -
- - - - - - - - - - - {printer.online ? Online : Offline} - - - - - - - - - ) : ( - } size="large" /> - )} -
- ); -}; - -export default Profile; diff --git a/src/components/Dashboard/common/Dashboard.jsx b/src/components/Dashboard/common/Dashboard.jsx index 2f5f822..4dfe03f 100644 --- a/src/components/Dashboard/common/Dashboard.jsx +++ b/src/components/Dashboard/common/Dashboard.jsx @@ -1,14 +1,14 @@ // Dashboard.js -import React, { useEffect, useState } from 'react'; -import DashboardLayout from './DashboardLayout'; -import { useNavigate, Outlet } from 'react-router-dom'; +import React from 'react' +import DashboardLayout from './DashboardLayout' +import { Outlet } from 'react-router-dom' const Dashboard = () => { return ( - + - ); -}; + ) +} -export default Dashboard; +export default Dashboard diff --git a/src/components/Dashboard/common/DashboardBreadcrumb.jsx b/src/components/Dashboard/common/DashboardBreadcrumb.jsx index 6cf770b..326857d 100644 --- a/src/components/Dashboard/common/DashboardBreadcrumb.jsx +++ b/src/components/Dashboard/common/DashboardBreadcrumb.jsx @@ -1,37 +1,68 @@ // DashboardBreadcrumb.js -import React from 'react'; -import { Breadcrumb } from 'antd'; -import { Link, useLocation } from 'react-router-dom'; +import React from 'react' +import { Breadcrumb, Button, Flex, Space } from 'antd' +import { Link, useLocation, useNavigate } from 'react-router-dom' +import { ArrowLeftOutlined, ArrowRightOutlined } from '@ant-design/icons' const breadcrumbNameMap = { - '/dashboard': 'Dashboard', - '/dashboard/overview': 'Overview', - '/dashboard/printers': 'Printers', - '/dashboard/printers/control': 'Control Printer', - '/dashboard/printers/edit': 'Edit Printer', - '/dashboard/printjobs': 'Print Jobs', - '/dashboard/fillaments': 'Fillaments', - '/dashboard/gcodefiles': 'G Code Files', -}; + '/production': 'Production', + '/management': 'Management', + '/production/overview': 'Overview', + '/production/printers': 'Printers', + '/production/printers/control': 'Control', + '/production/printers/info': 'Info', + '/production/printjobs': 'Print Jobs', + '/production/printjobs/info': 'Info', + '/production/gcodefiles': 'G Code Files', + '/production/gcodefiles/info': 'Info', + '/management/filaments': 'Filaments', + '/management/filaments/info': 'Info', + '/management/parts': 'Parts', + '/management/parts/info': 'Info', + '/management/products': 'Products', + '/management/products/info': 'Info', + '/management/vendors': 'Vendors', + '/management/vendors/info': 'Info' +} const DashboardBreadcrumb = () => { - const location = useLocation(); - const pathSnippets = location.pathname.split('/').filter(i => i); - - const breadcrumbItems = pathSnippets.map((_, index) => { - const url = `/${pathSnippets.slice(0, index + 1).join('/')}`; - return ( - - {breadcrumbNameMap[url]} - - ); - }); - - return ( - - {breadcrumbItems} - - ); - }; - - export default DashboardBreadcrumb; \ No newline at end of file + const location = useLocation() + const navigate = useNavigate() + const pathSnippets = location.pathname.split('/').filter((i) => i) + + const breadcrumbItems = pathSnippets.map((_, index) => { + const url = `/${pathSnippets.slice(0, index + 1).join('/')}` + return { + title: ( + + {breadcrumbNameMap[url]} + + ), + key: url + } + }) + + return ( + + + + - - - - - - - - - - - - - - - - - - 0.1 - 1 - 10 - 100 - - - `${value} mm`} - parser={(value) => value?.replace(" mm", "")} - onChange={handlePosInputChange} - placeholder="10 mm" - name="posInput" - /> - `${value} mm/s`} - parser={(value) => value?.replace(" mm/s", "")} - onChange={handleRateInputChange} - placeholder="100 mm/s" - name="rateInput" - /> - - - - - ); -}; - -export default DashboardMovementPanel; diff --git a/src/components/Dashboard/common/DashboardNavigation.jsx b/src/components/Dashboard/common/DashboardNavigation.jsx index ab139fa..d262d1c 100644 --- a/src/components/Dashboard/common/DashboardNavigation.jsx +++ b/src/components/Dashboard/common/DashboardNavigation.jsx @@ -1,48 +1,193 @@ // DashboardNavigation.js -import React, { useContext, useEffect, useState } from "react"; -import { Layout, Menu, message } from "antd"; -import { UserOutlined, LogoutOutlined } from "@ant-design/icons"; -import { AuthContext } from "../../Auth/AuthContext"; -import { useNavigate } from "react-router-dom"; -import Title from "antd/es/skeleton/Title"; +import React, { useContext, useEffect, useState } from 'react' +import { + Menu, + Flex, + Tag, + Space, + Dropdown, + Button, + Tooltip, + Typography +} from 'antd' +import { + UserOutlined, + LogoutOutlined, + PrinterOutlined, + SettingOutlined, + ProductOutlined, + ShoppingCartOutlined, + PoundOutlined, + MailOutlined, + SearchOutlined, + BellOutlined, + DisconnectOutlined, + MenuOutlined +} from '@ant-design/icons' +import { AuthContext } from '../../Auth/AuthContext' +import { SocketContext } from '../context/SocketContext' +import { SpotlightContext } from '../context/SpotlightContext' +import { useNavigate, useLocation } from 'react-router-dom' +import { Header } from 'antd/es/layout/layout' -const { Header } = Layout; +const { Text } = Typography const DashboardNavigation = () => { - const { logout } = useContext(AuthContext); - const navigate = useNavigate(); + const { logout, userProfile } = useContext(AuthContext) + const { showSpotlight } = useContext(SpotlightContext) + const { socket } = useContext(SocketContext) + const [socketConnected, setSocketConnected] = useState(false) + const navigate = useNavigate() + const location = useLocation() + const [selectedKey, setSelectedKey] = useState('production') - const handleLogout = async (e) => { - logout(); - setTimeout(() => { - navigate('/login') - }, 500) - }; + useEffect(() => { + const pathParts = location.pathname.split('/').filter(Boolean) + if (pathParts.length > 1) { + setSelectedKey(pathParts[0]) // Return the section (production/management) + } + }, [location.pathname]) - const menuItems = [ + useEffect(() => { + setSocketConnected(socket?.connected) + }, [socket?.connected]) + + const mainMenuItems = [ { - key: "1", - label: "Profile", - icon: , + key: 'production', + label: 'Production', + icon: }, { - key: "2", - label: "Logout", - icon: , - onClick: () => {handleLogout();}, + key: 'inventory', + label: 'Inventory', + icon: }, - ]; + { + key: 'shop', + label: 'Commerce', + icon: + }, + { + key: 'finance', + label: 'Finance', + icon: + }, + + { + key: 'management', + label: 'Management', + icon: + } + ] + + const userMenuItems = { + items: [ + { + key: 'username', + label: userProfile?.username, + icon: , + disabled: true + }, + { + key: 'email', + label: userProfile?.email, + icon: , + disabled: true + }, + { + key: 'logout', + label: 'Logout', + icon: + } + ], + onClick: (key) => { + if (key === 'profile') { + navigate('/profile') + } else if (key === 'logout') { + logout() + } + } + } + + const handleMainMenuClick = ({ key }) => { + if (key === 'production') { + navigate('/production/overview') + } else if (key === 'inventory') { + navigate('/inventory/spools') + } else if (key === 'management') { + navigate('/management/filaments') + } + } return ( -
- +
+ + Logo + } + /> + + ⌘ ⇧ P} arrow={false}> + + + + {!socketConnected ? ( + + } + > + Disconnected + + + ) : null} + + + Dev + + + {userProfile ? ( + + + }> + {userProfile?.name ? userProfile.name : userProfile.username} + + + + ) : null} + +
- ); -}; + ) +} -export default DashboardNavigation; +export default DashboardNavigation diff --git a/src/components/Dashboard/common/DashboardPrintStatus.jsx b/src/components/Dashboard/common/DashboardPrintStatus.jsx deleted file mode 100644 index e0784d1..0000000 --- a/src/components/Dashboard/common/DashboardPrintStatus.jsx +++ /dev/null @@ -1,237 +0,0 @@ -// DashboardTemperaturePanel.js -import React, { useContext, useEffect, useState } from "react"; -import { - Layout, - Progress, - Typography, - Spin, - Flex, - Space, - Collapse, - InputNumber, - Button, -} from "antd"; -import { LoadingOutlined } from "@ant-design/icons"; -import { SocketContext } from "../context/SocketContext"; -import styled from "styled-components"; - -const { Text, Link } = Typography; -const { Panel } = Collapse; -const { Header } = Layout; - -const CustomCollapse = styled(Collapse)` - .ant-collapse-header { - padding: 0 !important; - } - .ant-collapse-content-box { - padding-left: 0 !important; - padding-right: 0 !important; - padding-bottom: 0 !important; - } -`; - -const DashboardTemperaturePanel = ({ - remoteAddress, - showControls = true, - showMoreInfo = true, -}) => { - const [loading, setLoading] = React.useState(false); - const [hotEndTemperature, setHotEndTemperature] = useState(0); - const [heatedBedTemperature, setHeatedBedTemperature] = useState(0); - const [temperatureData, setTemperatureData] = useState(null); - const { socket } = useContext(SocketContext); - - useEffect(() => { - if (socket) { - socket.on("temperature", (data) => { - setTemperatureData(data.temperatures); - }); - return () => { - socket.off("temperature"); - }; - } - }, [socket]); - - const sendCommand = (type, data) => { - const commandData = { - remoteAddress, - type, - data, - }; - console.log(commandData); - socket.emit("command", commandData); - }; - - const handleSetTemperatureClick = (target, value) => { - sendCommand("setTemperature", { target, value }); - }; - - const moreInfoItems = [ - { - key: "1", - label: "More Temperature Data", - children: - - {temperatureData ? ( - <> - {typeof temperatureData.hotendPower !== "undefined" && ( - - - Hot End Power:{" "} - {Math.round((temperatureData.hotendPower / 127) * 100)}% - - - - )} - - {typeof temperatureData.bedPower !== "undefined" && ( - - - Bed Power:{" "} - {Math.round((temperatureData.bedPower / 127) * 100)}% - - - - )} - - {typeof temperatureData.pindaTemp !== "undefined" && ( - - Pinda Temp: {temperatureData.pindaTemp}°C - - )} - - {typeof temperatureData.ambiantActual !== "undefined" && ( - - Ambient Actual: {temperatureData.ambiantActual}°C - - )} - - ) : ( - - } size="large" /> - - )} - - }, - ]; - - return ( -
- {temperatureData ? ( - - {temperatureData.hotEnd && ( - - - Hot End: {temperatureData.hotEnd.current}°C /{" "} - {temperatureData.hotEnd.target}°C - - - {showControls === true && ( - - - `${value}°C`} - parser={(value) => value.replace("°C", "")} - onChange={(value) => setHotEndTemperature(value)} - /> - - - - - )} - - )} - - {temperatureData.heatedBed && ( - - - Heated Bed: {temperatureData.heatedBed.current}°C /{" "} - {temperatureData.heatedBed.target}°C - - - {showControls === true && ( - - - `${value}°C`} - parser={(value) => value.replace("°C", "")} - onChange={(value) => setHeatedBedTemperature(value)} - /> - - - - - )} - - )} - {showMoreInfo === true && ( - - )} - - ) : ( - - } size="large" /> - - )} -
- ); -}; - -export default DashboardTemperaturePanel; diff --git a/src/components/Dashboard/common/DashboardSidebar.jsx b/src/components/Dashboard/common/DashboardSidebar.jsx index 8396b29..7f1c4d6 100644 --- a/src/components/Dashboard/common/DashboardSidebar.jsx +++ b/src/components/Dashboard/common/DashboardSidebar.jsx @@ -1,58 +1,57 @@ // Sidebar.js -import React from "react"; -import { Link } from "react-router-dom"; -import { Layout, Menu } from "antd"; +import React from 'react' +import { Link } from 'react-router-dom' +import { Layout, Menu } from 'antd' import { DashboardOutlined, PrinterOutlined, - PlayCircleOutlined, - FileOutlined, -} from "@ant-design/icons"; + PlayCircleOutlined +} from '@ant-design/icons' -import FillamentIcon from "../../Icons/FillamentIcon" -import GCodeFileIcon from "../../Icons/GCodeFileIcon"; +import FilamentIcon from '../../Icons/FilamentIcon' +import GCodeFileIcon from '../../Icons/GCodeFileIcon' -const { Sider } = Layout; +const { Sider } = Layout const Sidebar = () => { const items = [ { - key: "overview", - label: Overview, - icon: , + key: 'overview', + label: Overview, + icon: }, { - key: "printers", - label: Printers, - icon: , + key: 'printers', + label: Printers, + icon: }, { - key: "jobs", - label: Print Jobs, - icon: , + key: 'jobs', + label: Print Jobs, + icon: }, { - key: "fillaments", - label: Fillaments, - icon: , + key: 'filaments', + label: Filaments, + icon: }, { - key: "gcodefiles", - label: G Code Files, - icon: , - }, - ]; + key: 'gcodefiles', + label: G Code Files, + icon: + } + ] return ( - + - ); -}; + ) +} -export default Sidebar; +export default Sidebar diff --git a/src/components/Dashboard/common/DashboardTemperaturePanel.jsx b/src/components/Dashboard/common/DashboardTemperaturePanel.jsx deleted file mode 100644 index e0784d1..0000000 --- a/src/components/Dashboard/common/DashboardTemperaturePanel.jsx +++ /dev/null @@ -1,237 +0,0 @@ -// DashboardTemperaturePanel.js -import React, { useContext, useEffect, useState } from "react"; -import { - Layout, - Progress, - Typography, - Spin, - Flex, - Space, - Collapse, - InputNumber, - Button, -} from "antd"; -import { LoadingOutlined } from "@ant-design/icons"; -import { SocketContext } from "../context/SocketContext"; -import styled from "styled-components"; - -const { Text, Link } = Typography; -const { Panel } = Collapse; -const { Header } = Layout; - -const CustomCollapse = styled(Collapse)` - .ant-collapse-header { - padding: 0 !important; - } - .ant-collapse-content-box { - padding-left: 0 !important; - padding-right: 0 !important; - padding-bottom: 0 !important; - } -`; - -const DashboardTemperaturePanel = ({ - remoteAddress, - showControls = true, - showMoreInfo = true, -}) => { - const [loading, setLoading] = React.useState(false); - const [hotEndTemperature, setHotEndTemperature] = useState(0); - const [heatedBedTemperature, setHeatedBedTemperature] = useState(0); - const [temperatureData, setTemperatureData] = useState(null); - const { socket } = useContext(SocketContext); - - useEffect(() => { - if (socket) { - socket.on("temperature", (data) => { - setTemperatureData(data.temperatures); - }); - return () => { - socket.off("temperature"); - }; - } - }, [socket]); - - const sendCommand = (type, data) => { - const commandData = { - remoteAddress, - type, - data, - }; - console.log(commandData); - socket.emit("command", commandData); - }; - - const handleSetTemperatureClick = (target, value) => { - sendCommand("setTemperature", { target, value }); - }; - - const moreInfoItems = [ - { - key: "1", - label: "More Temperature Data", - children: - - {temperatureData ? ( - <> - {typeof temperatureData.hotendPower !== "undefined" && ( - - - Hot End Power:{" "} - {Math.round((temperatureData.hotendPower / 127) * 100)}% - - - - )} - - {typeof temperatureData.bedPower !== "undefined" && ( - - - Bed Power:{" "} - {Math.round((temperatureData.bedPower / 127) * 100)}% - - - - )} - - {typeof temperatureData.pindaTemp !== "undefined" && ( - - Pinda Temp: {temperatureData.pindaTemp}°C - - )} - - {typeof temperatureData.ambiantActual !== "undefined" && ( - - Ambient Actual: {temperatureData.ambiantActual}°C - - )} - - ) : ( - - } size="large" /> - - )} - - }, - ]; - - return ( -
- {temperatureData ? ( - - {temperatureData.hotEnd && ( - - - Hot End: {temperatureData.hotEnd.current}°C /{" "} - {temperatureData.hotEnd.target}°C - - - {showControls === true && ( - - - `${value}°C`} - parser={(value) => value.replace("°C", "")} - onChange={(value) => setHotEndTemperature(value)} - /> - - - - - )} - - )} - - {temperatureData.heatedBed && ( - - - Heated Bed: {temperatureData.heatedBed.current}°C /{" "} - {temperatureData.heatedBed.target}°C - - - {showControls === true && ( - - - `${value}°C`} - parser={(value) => value.replace("°C", "")} - onChange={(value) => setHeatedBedTemperature(value)} - /> - - - - - )} - - )} - {showMoreInfo === true && ( - - )} - - ) : ( - - } size="large" /> - - )} -
- ); -}; - -export default DashboardTemperaturePanel; diff --git a/src/components/Dashboard/common/FillamentSelect.jsx b/src/components/Dashboard/common/FillamentSelect.jsx deleted file mode 100644 index 1986e4b..0000000 --- a/src/components/Dashboard/common/FillamentSelect.jsx +++ /dev/null @@ -1,150 +0,0 @@ -// FillamentSelect.js -import { TreeSelect, Badge } from 'antd'; -import React, { useEffect, useState, useContext, useRef } from 'react'; -import axios from 'axios'; -import { AuthContext } from "../../Auth/AuthContext"; - -const propertyOrder = ['diameter', 'type', 'brand']; - -const FillamentSelect = ({ onChange }) => { - const [fillamentsData, setFillamentsData] = useState([]); - const [fillamentsTreeData, setFillamentsTreeData] = useState([]); - const { token, logout } = useContext(AuthContext); - const tokenRef = useRef(token); - const [loading, setLoading] = useState(true); - - const fetchFillamentsData = async (property, filter) => { - console.log("Current ref", tokenRef); - try { - const response = await axios.get("http://localhost:8080/fillaments", { - params: { - ...filter, - property, - }, - headers: { - Authorization: `Bearer ${tokenRef.current}`, - }, - }); - setLoading(false); - return response.data; - //setPagination({ ...pagination, total: response.data.totalItems }); // Update total count - } catch (err) { - console.error(err); - } - }; - - const getFilter = (node) => { - var filter = {}; - var currentId = node.id; - while (currentId != 0) { - const currentNode = fillamentsTreeData.filter(treeData => treeData['id'] == currentId)[0] - filter[propertyOrder[currentNode.propertyId]] = currentNode.value.split("-")[0]; - currentId = currentNode.pId; - } - return filter; - } - - const generateFillamentTreeNodes = async (node = null) => { - if (!node) { - return; - } - - const fillamentData = await fetchFillamentsData(null, getFilter(node)); - - let newNodeList = []; - - for (var i = 0; i < fillamentData.length; i++) { - - const fillament = fillamentData[i]; - const random = Math.random().toString(36).substring(2, 6); - - const newNode = { - id: random, - pId: node.id, - value: fillament._id, - key: fillament._id, - title: (), - isLeaf: true - } - - newNodeList.push(newNode); - } - - setFillamentsTreeData(fillamentsTreeData.concat(newNodeList)) - console.log(newNodeList); - }; - - const generateFillamentCategoryTreeNodes = async (node = null) => { - var filter = {}; - console.log("Init node: ", node); - var propertyId = 0; - - if (!node) { - node = {}; - node.id = 0; - } else { - filter = getFilter(node); - propertyId = node.propertyId + 1; - } - - - const propertyName = propertyOrder[propertyId]; - - console.log("Next Property Id", propertyId) - console.log("Filter", filter); - - const propertyData = await fetchFillamentsData(propertyName, filter) - - let newNodeList = []; - - for (var i = 0; i < propertyData.length; i++) { - - const property = propertyData[i][propertyName]; - const random = Math.random().toString(36).substring(2, 6); - - const newNode = { - id: random, - pId: node.id, - value: property + "-" + random, - key: property + "-" + random, - propertyId: propertyId, - title: property, - isLeaf: false, - selectable: false - } - - newNodeList.push(newNode); - } - - setFillamentsTreeData(fillamentsTreeData.concat(newNodeList)) - console.log(newNodeList); - }; - - const handleFillamentsTreeLoad = async (node) => { - console.log(node); - if (node) { - if (node.propertyId != propertyOrder.length - 1) { - generateFillamentCategoryTreeNodes(node); - } else { - console.log("Generating printer node..."); - generateFillamentTreeNodes(node); // End of properties - } - } else { - generateFillamentCategoryTreeNodes(null); // First property - } - }; - - useEffect(() => { - if (fillamentsTreeData.length == 0) { - handleFillamentsTreeLoad(null) - } - }, [token]); - - return ( - - - - ); -}; - -export default FillamentSelect; \ No newline at end of file diff --git a/src/components/Dashboard/common/GCodePreview.jsx b/src/components/Dashboard/common/GCodePreview.jsx index cc73c8a..da4685f 100644 --- a/src/components/Dashboard/common/GCodePreview.jsx +++ b/src/components/Dashboard/common/GCodePreview.jsx @@ -1,67 +1,72 @@ -import * as GCodePreview from 'gcode-preview'; -import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react'; -import * as THREE from 'three'; +import * as GCodePreview from 'gcode-preview' +import { + forwardRef, + useEffect, + useImperativeHandle, + useRef, + useState +} from 'react' +import * as THREE from 'three' -function GCodePreviewUI(props, ref) { +function GCodePreviewUI(props, ref, initialGCode) { const { - topLayerColor = '', - lastSegmentColor = '', - startLayer, - endLayer, - lineWidth - } = props; - const canvasRef = useRef(null); - const [preview, setPreview] = useState(); + topLayerColor = '', + lastSegmentColor = '', + startLayer, + endLayer, + lineWidth + } = props + const canvasRef = useRef(null) + const [preview, setPreview] = useState() const resizePreview = () => { - preview?.resize(); - }; + preview?.resize() + } useImperativeHandle(ref, () => ({ - getLayerCount() { - return preview?.layers.length; - }, - processGCode(gcode) { - preview?.processGCode(gcode); - } - })); + getLayerCount() { + return preview?.layers.length + }, + processGCode(gcode) { + preview?.processGCode(gcode) + } + })) useEffect(() => { - setPreview( - GCodePreview.init({ - canvas: canvasRef.current, - startLayer, - endLayer, - lineWidth, - topLayerColor: new THREE.Color(topLayerColor).getHex(), - lastSegmentColor: new THREE.Color(lastSegmentColor).getHex(), - buildVolume: { x: 250, y: 220, z: 150 }, - initialCameraPosition: [0, 400, 450], - allowDragNDrop: false - }) - ); + setPreview( + GCodePreview.init({ + canvas: canvasRef.current, + startLayer, + endLayer, + lineWidth, + topLayerColor: new THREE.Color(topLayerColor).getHex(), + lastSegmentColor: new THREE.Color(lastSegmentColor).getHex(), + buildVolume: { x: 250, y: 220, z: 150 }, + initialCameraPosition: [0, 400, 450], + allowDragNDrop: false + }) + ) - window.addEventListener('resize', resizePreview); + window.addEventListener('resize', resizePreview) - return () => { - window.removeEventListener('resize', resizePreview); - }; - }, []); + return () => { + window.removeEventListener('resize', resizePreview) + } + }, []) return ( -
- +
+ -
-
topLayerColor: {topLayerColor}
-
lastSegmentColor: {lastSegmentColor}
-
startLayer: {startLayer}
-
endLayer: {endLayer}
-
lineWidth: {lineWidth}
-
-
- ); +
+
topLayerColor: {topLayerColor}
+
lastSegmentColor: {lastSegmentColor}
+
startLayer: {startLayer}
+
endLayer: {endLayer}
+
lineWidth: {lineWidth}
+
+
+ ) } -export default forwardRef(GCodePreviewUI); - +export default forwardRef(GCodePreviewUI) diff --git a/src/components/Dashboard/context/SocketContext.js b/src/components/Dashboard/context/SocketContext.js index 5ee3a57..e89a7b4 100644 --- a/src/components/Dashboard/context/SocketContext.js +++ b/src/components/Dashboard/context/SocketContext.js @@ -1,75 +1,96 @@ // src/contexts/SocketContext.js -import React, { createContext, useEffect, useState, useContext, useRef } from "react"; -import io from "socket.io-client"; -import { message } from "antd"; -import { AuthContext } from "../../Auth/AuthContext"; +import React, { + createContext, + useEffect, + useState, + useContext, + useRef +} from 'react' +import io from 'socket.io-client' +import { message, notification } from 'antd' +import PropTypes from 'prop-types' +import { AuthContext } from '../../Auth/AuthContext' -const SocketContext = createContext(); +const SocketContext = createContext() const SocketProvider = ({ children }) => { - const { token } = useContext(AuthContext); - const socketRef = useRef(null); - const [connecting, setConnecting] = useState(false); - const [error, setError] = useState(null); - const [messageApi, contextHolder] = message.useMessage(); + const { token } = useContext(AuthContext) + const socketRef = useRef(null) + const [connecting, setConnecting] = useState(false) + const [error, setError] = useState(null) + const [messageApi, contextHolder] = message.useMessage() + const [notificationApi] = notification.useNotification() useEffect(() => { if (token) { - console.log("Token is available, connecting to web socket server..."); + console.log('Token is available, connecting to web socket server...') - const newSocket = io("http://localhost:5050", { + const newSocket = io('http://localhost:8081', { reconnectionAttempts: 3, timeout: 3000, - query: { token }, - }); + auth: { token: token } + }) - setConnecting(true); + setConnecting(true) - newSocket.on("connect", () => { - console.log("Socket connected"); - setConnecting(false); - setError(null); - }); + newSocket.on('connect', () => { + console.log('Socket connected') + setConnecting(false) + setError(null) + }) - newSocket.on("disconnect", () => { - console.log("Socket disconnected"); - setError("Socket disconnected"); - }); + newSocket.on('disconnect', () => { + console.log('Socket disconnected') + setError('Socket disconnected') + }) - newSocket.on("connect_error", (err) => { - console.error("Socket connection error:", err); - messageApi.error("Socket connection error: " + err.message); - setError("Socket connection error"); - }); + newSocket.on('connect_error', (err) => { + console.error('Socket connection error:', err) + messageApi.error('Socket connection error: ' + err.message) + setError('Socket connection error') + }) - newSocket.on("error", (err) => { - console.error("Socket error:", err); - setError("Socket error"); - }); + newSocket.on('bridge.notification', (data) => { + notificationApi[data.type]({ + title: data.title, + message: data.message + }) + }) - socketRef.current = newSocket; + newSocket.on('error', (err) => { + console.error('Socket error:', err) + setError('Socket error') + }) + + socketRef.current = newSocket // Clean up function return () => { if (socketRef.current) { - console.log("Cleaning up socket connection..."); - socketRef.current.disconnect(); - socketRef.current = null; + console.log('Cleaning up socket connection...') + socketRef.current.disconnect() + socketRef.current = null } - }; + } } else if (!token && socketRef.current) { - console.log("Token not available, disconnecting socket..."); - socketRef.current.disconnect(); - socketRef.current = null; + console.log('Token not available, disconnecting socket...') + socketRef.current.disconnect() + socketRef.current = null } - }, [token, messageApi]); + }, [token, messageApi]) return ( - + {contextHolder} {children} - ); -}; + ) +} -export { SocketContext, SocketProvider }; \ No newline at end of file +SocketProvider.propTypes = { + children: PropTypes.node.isRequired +} + +export { SocketContext, SocketProvider } diff --git a/src/components/Icons/FillamentIcon.jsx b/src/components/Icons/FillamentIcon.jsx deleted file mode 100644 index 3b70687..0000000 --- a/src/components/Icons/FillamentIcon.jsx +++ /dev/null @@ -1,9 +0,0 @@ -import React from 'react'; -import Icon from '@ant-design/icons'; -import { ReactComponent as CustomIconSvg } from '../../assets/icons/fillamenticon.svg'; - -const FillamentIcon = (props) => ( - -); - -export default FillamentIcon; \ No newline at end of file diff --git a/src/components/Icons/GCodeFileIcon.jsx b/src/components/Icons/GCodeFileIcon.jsx index e3484e9..f686279 100644 --- a/src/components/Icons/GCodeFileIcon.jsx +++ b/src/components/Icons/GCodeFileIcon.jsx @@ -1,9 +1,7 @@ -import React from 'react'; -import Icon from '@ant-design/icons'; -import { ReactComponent as CustomIconSvg } from '../../assets/icons/gcodefileicon.svg'; +import React from 'react' +import Icon from '@ant-design/icons' +import { ReactComponent as CustomIconSvg } from '../../assets/icons/gcodefileicon.svg' -const GCodeFileIcon = (props) => ( - -); +const GCodeFileIcon = (props) => -export default GCodeFileIcon; \ No newline at end of file +export default GCodeFileIcon diff --git a/src/components/Icons/PassKeysIcon.jsx b/src/components/Icons/PassKeysIcon.jsx index bff5c84..cfdc38e 100644 --- a/src/components/Icons/PassKeysIcon.jsx +++ b/src/components/Icons/PassKeysIcon.jsx @@ -1,9 +1,7 @@ -import React from 'react'; -import Icon from '@ant-design/icons'; -import { ReactComponent as CustomIconSvg } from '../../assets/icons/passkeysicon.svg'; +import React from 'react' +import Icon from '@ant-design/icons' +import { ReactComponent as CustomIconSvg } from '../../assets/icons/passkeysicon.svg' -const PassKeysIcon = (props) => ( - -); +const PassKeysIcon = (props) => -export default PassKeysIcon; \ No newline at end of file +export default PassKeysIcon diff --git a/src/components/PrivateRoute.jsx b/src/components/PrivateRoute.jsx index e1492c0..660837a 100644 --- a/src/components/PrivateRoute.jsx +++ b/src/components/PrivateRoute.jsx @@ -1,8 +1,28 @@ -import React from 'react'; -import { Navigate } from 'react-router-dom'; +// PrivateRoute.js +import PropTypes from 'prop-types' +import React, { useContext } from 'react' +//import { Navigate } from 'react-router-dom' +import { AuthContext } from './Auth/AuthContext' const PrivateRoute = ({ component: Component }) => { - return localStorage.getItem('access_token') ? : ; -}; + const { authenticated, loading, showSessionExpiredModal } = + useContext(AuthContext) -export default PrivateRoute; + // Show loading state while auth state is being determined + if (loading) { + return
Loading...
+ } + + // Redirect to login if not authenticated + return authenticated || showSessionExpiredModal ? ( + + ) : ( + + ) +} + +PrivateRoute.propTypes = { + component: PropTypes.func.isRequired +} + +export default PrivateRoute diff --git a/src/components/PublicRoute.jsx b/src/components/PublicRoute.jsx index 8877e35..ef019ff 100644 --- a/src/components/PublicRoute.jsx +++ b/src/components/PublicRoute.jsx @@ -1,8 +1,23 @@ -import React from 'react'; -import { Navigate } from 'react-router-dom'; +// PublicRoute.js +import PropTypes from 'prop-types' +import React, { useContext } from 'react' +import { Navigate } from 'react-router-dom' +import { AuthContext } from './Auth/AuthContext' const PublicRoute = ({ component: Component }) => { - return !localStorage.getItem('access_token') ? : ; -}; + const { authenticated, loading } = useContext(AuthContext) -export default PublicRoute; + // Show loading state while auth state is being determined + if (loading) { + return
Loading...
+ } + + // Redirect to login if not authenticated + return !authenticated ? : +} + +PublicRoute.propTypes = { + component: PropTypes.func.isRequired +} + +export default PublicRoute diff --git a/src/index.css b/src/index.css index ec2585e..826eea2 100644 --- a/src/index.css +++ b/src/index.css @@ -11,3 +11,7 @@ code { font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; } + +.ant-modal-mask { + backdrop-filter: blur(3px); +} diff --git a/src/index.js b/src/index.js index 1ee10af..f49c8e5 100644 --- a/src/index.js +++ b/src/index.js @@ -1,17 +1,17 @@ -import reportWebVitals from "./reportWebVitals"; -import React from "react"; -import ReactDOM from "react-dom/client"; -import FarmControlApp from "./App"; -import "./index.css"; +import reportWebVitals from './reportWebVitals' +import React from 'react' +import ReactDOM from 'react-dom/client' +import FarmControlApp from './App' +import './index.css' -const root = ReactDOM.createRoot(document.getElementById("root")); +const root = ReactDOM.createRoot(document.getElementById('root')) root.render( -); +) // If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log)) // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals -reportWebVitals(); +reportWebVitals() diff --git a/src/reportWebVitals.js b/src/reportWebVitals.js index 5253d3a..dc6ff07 100644 --- a/src/reportWebVitals.js +++ b/src/reportWebVitals.js @@ -1,13 +1,13 @@ -const reportWebVitals = onPerfEntry => { +const reportWebVitals = (onPerfEntry) => { if (onPerfEntry && onPerfEntry instanceof Function) { import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { - getCLS(onPerfEntry); - getFID(onPerfEntry); - getFCP(onPerfEntry); - getLCP(onPerfEntry); - getTTFB(onPerfEntry); - }); + getCLS(onPerfEntry) + getFID(onPerfEntry) + getFCP(onPerfEntry) + getLCP(onPerfEntry) + getTTFB(onPerfEntry) + }) } -}; +} -export default reportWebVitals; +export default reportWebVitals diff --git a/src/setupTests.js b/src/setupTests.js index 8f2609b..52aaef1 100644 --- a/src/setupTests.js +++ b/src/setupTests.js @@ -2,4 +2,4 @@ // allows you to do things like: // expect(element).toHaveTextContent(/react/i) // learn more: https://github.com/testing-library/jest-dom -import '@testing-library/jest-dom'; +import '@testing-library/jest-dom'