diff --git a/package-lock.json b/package-lock.json index 19b0c92..cf60a01 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,25 @@ { "name": "numinia-oncyber", - "version": "1.2.0", + "version": "1.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "numinia-oncyber", - "version": "1.2.0", + "version": "1.3.0", "license": "ISC", - "devDependencies": { + "dependencies": { "@dimforge/rapier3d": "^0.12.0", + "animejs": "^3.2.2", + "gsap": "^3.12.5", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "three": "^0.163.0" + }, + "devDependencies": { "@testing-library/jest-dom": "^6.5.0", "@testing-library/react": "^16.0.1", + "@types/animejs": "^3.1.12", "@types/jest": "^29.5.13", "@types/react": "^18.3.0", "@types/three": "^0.163.0", @@ -20,16 +28,14 @@ "cross-env": "^7.0.3", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", + "eslint-plugin-jest": "^28.8.3", "eslint-plugin-prettier": "^5.1.3", + "eslint-plugin-react": "^7.37.1", "fetch-mock": "^11.1.4", - "gsap": "^3.12.5", "identity-obj-proxy": "^3.0.0", "jest": "^29.7.0", "pre-commit": "^1.2.2", "prettier": "^3.2.5", - "react": "^18.2.0", - "react-dom": "^18.3.1", - "three": "^0.163.0", "ts-jest": "^29.2.5", "tslib": "^2.6.2" } @@ -671,7 +677,7 @@ "version": "0.12.0", "resolved": "https://registry.npmjs.org/@dimforge/rapier3d/-/rapier3d-0.12.0.tgz", "integrity": "sha512-+31rbMJuhT5jrqNhXpLK8iaDsk9dg+8xiK+2QIOCAN++zDcLm2JvegQqsydzu6aL7OsAxBkGhWTfEmeLz+uAvw==", - "dev": true + "license": "Apache-2.0" }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", @@ -1394,6 +1400,13 @@ "integrity": "sha512-ZpboH7pCPPeyBWKf8c7TJswtCEQObFo3bOBYalm99NzZarATALYCo5OhbCa/n4RQyJyHfhkdx+hNrdL5ByFYDw==", "dev": true }, + "node_modules/@types/animejs": { + "version": "3.1.12", + "resolved": "https://registry.npmjs.org/@types/animejs/-/animejs-3.1.12.tgz", + "integrity": "sha512-fpdH+ZtlO0kqjTOqRaBdsEmvpRNOayI8k4EVkEtitL5l6wducDOXk0rgQgfZqWf/ZX9DzXrHf257S5i9xTcISQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/aria-query": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", @@ -1833,6 +1846,12 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/animejs": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/animejs/-/animejs-3.2.2.tgz", + "integrity": "sha512-Ao95qWLpDPXXM+WrmwcKbl6uNlC5tjnowlaRYtuVDHHoygjtIPfDUoK9NthrlZsQSKjZXlmji2TrBUAVbiH0LQ==", + "license": "MIT" + }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -1912,6 +1931,44 @@ "dequal": "^2.0.3" } }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", @@ -1921,12 +1978,127 @@ "node": ">=8" } }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", + "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", + "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/async": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", "dev": true }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/babel-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", @@ -2132,6 +2304,26 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -2362,6 +2554,60 @@ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "dev": true }, + "node_modules/data-view-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", + "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", + "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", + "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -2408,6 +2654,42 @@ "node": ">=0.10.0" } }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -2514,6 +2796,172 @@ "is-arrayish": "^0.2.1" } }, + "node_modules/es-abstract": { + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", + "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "data-view-buffer": "^1.0.1", + "data-view-byte-length": "^1.0.1", + "data-view-byte-offset": "^1.0.0", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.0.3", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "hasown": "^2.0.2", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.1", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.3", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.13", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.1", + "object-keys": "^1.1.1", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.2", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.9", + "string.prototype.trimend": "^1.0.8", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.6", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.15" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.0.19", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.19.tgz", + "integrity": "sha512-zoMwbCcH5hwUkKJkT8kDIBZSz9I6mVG//+lDCinLCGov4+r7NIy0ld8o03M0cJxl2spVf6ESYVS6/gpIfq1FFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.3", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "globalthis": "^1.0.3", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.7", + "iterator.prototype": "^1.1.2", + "safe-array-concat": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.0" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -2602,6 +3050,32 @@ "eslint": ">=7.0.0" } }, + "node_modules/eslint-plugin-jest": { + "version": "28.8.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-28.8.3.tgz", + "integrity": "sha512-HIQ3t9hASLKm2IhIOqnu+ifw7uLZkIlR7RYNv7fMcEi/p0CIiJmfriStQS2LDkgtY4nyLbIZAD+JL347Yc2ETQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/utils": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "engines": { + "node": "^16.10.0 || ^18.12.0 || >=20.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^6.0.0 || ^7.0.0 || ^8.0.0", + "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0", + "jest": "*" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + }, + "jest": { + "optional": true + } + } + }, "node_modules/eslint-plugin-prettier": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz", @@ -2632,26 +3106,100 @@ } } }, - "node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "node_modules/eslint-plugin-react": { + "version": "7.37.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.1.tgz", + "integrity": "sha512-xwTnwDqzbDRA8uJ7BMxPs/EXRB3i8ZfnOIp8BsxEQkT0nHPp+WWceqGgo6rKb9ctNi8GJLDT4Go5HAWELa/WMg==", "dev": true, + "license": "MIT", "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.2", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.0.19", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.8", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.0", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.11", + "string.prototype.repeat": "^1.0.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=4" }, - "funding": { - "url": "https://opencollective.com/eslint" + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" } }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "node_modules/eslint-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -2958,6 +3506,16 @@ "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.1.3" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -2987,6 +3545,35 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/function.prototype.name": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -3005,6 +3592,26 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/get-package-type": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", @@ -3026,6 +3633,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-symbol-description": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -3080,6 +3705,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/globby": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", @@ -3100,6 +3742,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -3116,7 +3771,7 @@ "version": "3.12.5", "resolved": "https://registry.npmjs.org/gsap/-/gsap-3.12.5.tgz", "integrity": "sha512-srBfnk4n+Oe/ZnMIOXt3gT605BX9x5+rh/prT2F1SsNJsU1XuMiP0E2aptW481OnonOGACZWBqseH5Z7csHxhQ==", - "dev": true + "license": "Standard 'no charge' license: https://gsap.com/standard-license. Club GSAP members get more: https://gsap.com/licensing/. Why GreenSock doesn't employ an MIT license: https://gsap.com/why-license/" }, "node_modules/harmony-reflect": { "version": "1.6.2", @@ -3124,6 +3779,16 @@ "integrity": "sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g==", "dev": true }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -3133,6 +3798,61 @@ "node": ">=8" } }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -3250,12 +3970,103 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, + "node_modules/internal-slot": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "dev": true }, + "node_modules/is-async-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", + "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-core-module": { "version": "2.15.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", @@ -3271,6 +4082,38 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-data-view": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", + "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -3280,6 +4123,19 @@ "node": ">=0.10.0" } }, + "node_modules/is-finalizationregistry": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz", + "integrity": "sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -3298,6 +4154,22 @@ "node": ">=6" } }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -3310,6 +4182,32 @@ "node": ">=0.10.0" } }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -3319,6 +4217,22 @@ "node": ">=0.12.0" } }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-path-inside": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", @@ -3328,6 +4242,52 @@ "node": ">=8" } }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -3340,12 +4300,103 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-subset": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz", "integrity": "sha512-6Ybun0IkarhmEqxXCNw/C0bna6Zb/TkfUX9UbwJtK6ObwAVCxmAP308WWTHviM/zAqXk05cdhYsUsZeGQh99iw==", "dev": true }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz", + "integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -3424,6 +4475,20 @@ "node": ">=8" } }, + "node_modules/iterator.prototype": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.2.tgz", + "integrity": "sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "reflect.getprototypeof": "^1.0.4", + "set-function-name": "^2.0.1" + } + }, "node_modules/jake": { "version": "10.9.2", "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", @@ -4000,8 +5065,7 @@ "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "node_modules/js-yaml": { "version": "4.1.0", @@ -4063,6 +5127,22 @@ "node": ">=6" } }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -4146,7 +5226,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, @@ -4292,25 +5371,129 @@ "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", "dev": true }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz", + "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==", "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" } }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", "dev": true, + "license": "MIT", "dependencies": { - "path-key": "^3.0.0" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", + "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/once": { @@ -4565,6 +5748,16 @@ "node": ">=8" } }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/pre-commit": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/pre-commit/-/pre-commit-1.2.2.tgz", @@ -4718,6 +5911,25 @@ "node": ">= 6" } }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/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==", + "dev": true, + "license": "MIT" + }, "node_modules/pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", @@ -4773,7 +5985,7 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", - "dev": true, + "license": "MIT", "dependencies": { "loose-envify": "^1.1.0" }, @@ -4785,7 +5997,7 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", - "dev": true, + "license": "MIT", "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -4834,12 +6046,53 @@ "node": ">=8" } }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz", + "integrity": "sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.1", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "globalthis": "^1.0.3", + "which-builtin-type": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", "dev": true }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/regexparam": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/regexparam/-/regexparam-3.0.0.tgz", @@ -4962,11 +6215,54 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/safe-array-concat": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", + "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-array-concat/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/safe-regex-test": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-regex": "^1.1.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/scheduler": { "version": "0.23.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", - "dev": true, "dependencies": { "loose-envify": "^1.1.0" } @@ -4983,6 +6279,40 @@ "node": ">=10" } }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -5004,6 +6334,25 @@ "node": ">=8" } }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -5124,6 +6473,96 @@ "node": ">=8" } }, + "node_modules/string.prototype.matchall": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz", + "integrity": "sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.7", + "regexp.prototype.flags": "^1.5.2", + "set-function-name": "^2.0.2", + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", + "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", + "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -5242,7 +6681,7 @@ "version": "0.163.0", "resolved": "https://registry.npmjs.org/three/-/three-0.163.0.tgz", "integrity": "sha512-HlMgCb2TF/dTLRtknBnjUTsR8FsDqBY43itYop2+Zg822I+Kd0Ua2vs8CvfBVefXkBdNDrLMoRTGCIIpfCuDew==", - "dev": true + "license": "MIT" }, "node_modules/tmpl": { "version": "1.0.5", @@ -5370,6 +6809,83 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/typed-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", + "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", + "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", + "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", @@ -5390,6 +6906,22 @@ "node": ">=14.17" } }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/undici-types": { "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", @@ -5479,6 +7011,96 @@ "node": ">= 8" } }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.1.4.tgz", + "integrity": "sha512-bppkmBSsHFmIMSl8BO9TbsyzsvGjVoppt8xUiGzwiu/bhDCGxnpOKCxgqj6GuyHE0mINMDecBFPlOm2hzY084w==", + "dev": true, + "license": "MIT", + "dependencies": { + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.0.5", + "is-finalizationregistry": "^1.0.2", + "is-generator-function": "^1.0.10", + "is-regex": "^1.1.4", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.15" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", + "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", diff --git a/package.json b/package.json index f8a54d6..064dcbc 100644 --- a/package.json +++ b/package.json @@ -28,9 +28,9 @@ }, "homepage": "https://github.com/numengames/numinia-oncyber#readme", "devDependencies": { - "@dimforge/rapier3d": "^0.12.0", "@testing-library/jest-dom": "^6.5.0", "@testing-library/react": "^16.0.1", + "@types/animejs": "^3.1.12", "@types/jest": "^29.5.13", "@types/react": "^18.3.0", "@types/three": "^0.163.0", @@ -39,17 +39,23 @@ "cross-env": "^7.0.3", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", + "eslint-plugin-jest": "^28.8.3", "eslint-plugin-prettier": "^5.1.3", + "eslint-plugin-react": "^7.37.1", "fetch-mock": "^11.1.4", - "gsap": "^3.12.5", "identity-obj-proxy": "^3.0.0", "jest": "^29.7.0", "pre-commit": "^1.2.2", "prettier": "^3.2.5", - "react": "^18.2.0", - "react-dom": "^18.3.1", - "three": "^0.163.0", "ts-jest": "^29.2.5", "tslib": "^2.6.2" + }, + "dependencies": { + "@dimforge/rapier3d": "^0.12.0", + "animejs": "^3.2.2", + "gsap": "^3.12.5", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "three": "^0.163.0" } -} \ No newline at end of file +} diff --git a/src/behaviors/emitters/BaseEmitter.ts b/src/behaviors/emitters/BaseEmitter.ts new file mode 100644 index 0000000..cde7db2 --- /dev/null +++ b/src/behaviors/emitters/BaseEmitter.ts @@ -0,0 +1,64 @@ +import { Folder, Param, $Param, ScriptBehavior } from '@oo/scripting'; + +import InteractionDirector from '../../common/interactions/InteractionDirector'; + +interface BaseEmitterParams { + triggerKey?: string; + interactionMode: string; + triggerDistance?: number; + yInteractionAdjustment?: number; +} + +/** + * Main class to handle the action in the script. + */ +export default class BaseEmitter extends ScriptBehavior { + static config = { + title: 'Emitter', + tip: 'Use this to launch an event after interacting with the 3D Object', + }; + + @Param({ name: 'Signal sender' }) + private enterSignal = $Param.Signal(); + + @Param({ type: 'boolean', defaultValue: false, name: 'Can emit signals multiple times?' }) + private doesEmitMultipleTimes = false; + + @Folder('Interaction Mode') + @Param({ + type: 'select', + defaultValue: 'Auto', + name: 'Interaction Mode', + options: ['Auto', 'Key'], + }) + private interactionMode = 'Auto'; + @Param({ + type: 'string', + name: 'Trigger key', + visible: (params: BaseEmitterParams) => params.interactionMode === 'Key', + }) + private triggerKey = 'E'; + @Param({ + step: 0.1, + type: 'number', + name: 'Key dialog adjustment', + visible: (params: BaseEmitterParams) => params.interactionMode === 'Key', + }) + private yInteractionAdjustment = 0; + @Param({ + min: 0.1, + step: 0.1, + type: 'number', + defaultValue: 2, + name: 'Trigger distance', + visible: (params: BaseEmitterParams) => params.interactionMode === 'Key', + }) + private triggerDistance = 2; + + /** + * Called when the script is ready. + */ + onReady = async () => { + await InteractionDirector.handle(this, () => this.enterSignal.emit()); + }; +} diff --git a/src/behaviors/emitters/insert-password/InsertPasswordEmitter.tsx b/src/behaviors/emitters/insert-password/InsertPasswordEmitter.tsx new file mode 100644 index 0000000..365d396 --- /dev/null +++ b/src/behaviors/emitters/insert-password/InsertPasswordEmitter.tsx @@ -0,0 +1,86 @@ +import * as React from 'react'; +import { Folder, Param, $Param, ScriptBehavior, UI } from '@oo/scripting'; + +import InsertPasswordPrompt from './InsertPasswordPrompt.tsx'; +import InteractionDirector from '../../../common/interactions/InteractionDirector'; + +interface InsertPasswordEmitterParams { + triggerKey?: string; + interactionMode: string; + triggerDistance?: number; + yInteractionAdjustment?: number; +} + +/** + * Main class to handle the emitter in the script. + */ +export default class InsertPasswordEmitter extends ScriptBehavior { + static config = { + title: 'Emitter - Insert Password', + tip: 'Use this to launch an event after solving the password', + }; + + private renderer = UI.createRenderer(); + + @Param({ name: 'Signal sender' }) + private enterSignal = $Param.Signal(); + + @Param({ + type: 'string', + name: 'Master password' + }) + private masterPassword = ''; + + @Param({ type: 'boolean', defaultValue: false, name: 'Can emit signals multiple times?' }) + private doesEmitMultipleTimes = false; + + @Folder('Interaction Mode') + @Param({ + type: 'select', + defaultValue: 'Auto', + name: 'Interaction Mode', + options: ['Auto', 'Key'], + }) + private interactionMode = 'Auto'; + @Param({ + type: 'string', + name: 'Trigger key', + visible: (params: InsertPasswordEmitterParams) => params.interactionMode === 'Key', + }) + + private triggerKey = 'E'; + @Param({ + step: 0.1, + type: 'number', + name: 'Key dialog adjustment', + visible: (params: InsertPasswordEmitterParams) => params.interactionMode === 'Key', + }) + private yInteractionAdjustment = 0; + @Param({ + min: 0.1, + step: 0.1, + type: 'number', + defaultValue: 2, + name: 'Trigger distance', + visible: (params: InsertPasswordEmitterParams) => params.interactionMode === 'Key', + }) + private triggerDistance = 2; + + onReady = async () => { + await InteractionDirector.handle(this, this.showInsertPassword.bind(this)); + }; + + private showInsertPassword() { + this.renderer.render( + + ); + } + + private handleSolvedPassword = () => { + this.passwordSolvedSignal.emit() + } +} diff --git a/src/behaviors/emitters/insert-password/InsertPasswordPrompt.tsx b/src/behaviors/emitters/insert-password/InsertPasswordPrompt.tsx new file mode 100644 index 0000000..7dbc32c --- /dev/null +++ b/src/behaviors/emitters/insert-password/InsertPasswordPrompt.tsx @@ -0,0 +1,162 @@ +import * as React from 'react'; +import { useState } from 'react'; + +interface PasswordPromptProps { + onSuccess: () => void; + masterPassword: string; +} + +const styles = ` + .ask-for-password-wrapper { + background: radial-gradient(circle, rgba(0, 255, 255, 0.1), transparent 60%), + radial-gradient(circle at top left, rgba(0, 255, 255, 0.15), transparent 70%), + radial-gradient(circle at bottom right, rgba(0, 255, 255, 0.15), transparent 70%); + background-color: #282c34; + position: fixed; + border: 1px solid #3e3e3e; + border-radius: 15px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.5); + padding: 40px; + left: 75%; + top: 50%; + transform: translate(-50%, -50%); + width: 300px; + overflow: hidden; + } + + .ask-for-password-wrapper > button { + position: absolute; + right: 10px; + top: 10px; + border: none; + background: none; + color: #f8f8f8; + font-size: 20px; + cursor: pointer; + } + + .ask-for-password-wrapper > button:hover { + color: #416792; + } + + .ask-for-password-form { + display: flex; + flex-direction: column; + align-items: center; + position: relative; + z-index: 1; + } + + .ask-for-password-form > h1 { + font-size: 24px; + color: #b3eaff; + margin-bottom: 20px; + text-shadow: 0 0 5px rgba(179, 234, 255, 0.7); + } + + #ask-for-password-input { + background-color: #1c1c1c; + border: 2px solid #3e3e3e; + border-radius: 4px; + color: #f8f8f8; + font-size: 16px; + margin-bottom: 20px; + padding: 10px 15px; + transition: border-color 0.3s; + width: 100%; + } + + #ask-for-password-input:focus { + border-color: #b3eaff; + outline: none; + } + + .ask-for-password-form > button { + background: linear-gradient(135deg, #00d4ff, #00a6ff); + border: none; + border-radius: 4px; + color: black; + font-weight: bold; + cursor: pointer; + font-size: 18px; + padding: 10px 15px; + transition: background-color 0.3s ease, box-shadow 0.3s ease; + width: 100%; + box-shadow: 0 0 15px rgba(0, 255, 255, 0.4); + } + + .ask-for-password-form > button:hover { + background: linear-gradient(135deg, #00a6ff, #00d4ff); + box-shadow: 0 0 25px rgba(0, 255, 255, 0.8); + } + + .ask-for-password-form > span { + color: #ff4d4d; + margin-top: 20px; + display: block; + font-size: 16px; + } +`; + +const InsertPasswordPrompt = ({ + onSuccess, + masterPassword, +}: PasswordPromptProps): JSX.Element | null => { + const [password, setPassword] = useState(''); + const [isVisible, setIsVisible] = useState(true); + const [errorMessage, setErrorMessage] = useState(''); + + const handleChange = (event: React.ChangeEvent) => { + setPassword(event.target.value); + }; + + const handleClick = (event: React.MouseEvent) => { + event.preventDefault(); + if (password === masterPassword) { + setIsVisible(false); + onSuccess(); + } else { + setPassword(''); + setErrorMessage('Incorrect password'); + } + }; + + const handleClose = () => { + setIsVisible(false); + }; + + if (!isVisible) return null; + + return ( + <> +
+ +
+

Enter the Password

+ + + {errorMessage && ( + + {errorMessage} + + )} +
+
+ + + ); +}; + +export default InsertPasswordPrompt; diff --git a/src/behaviors/receivers/RedirectReceiver.ts b/src/behaviors/receivers/RedirectReceiver.ts new file mode 100644 index 0000000..77467d1 --- /dev/null +++ b/src/behaviors/receivers/RedirectReceiver.ts @@ -0,0 +1,90 @@ +import { Player, Component3D, Receiver, Param, ScriptBehavior, Folder } from '@oo/scripting'; + +import fadeOut from '../../common/utils/fadeOut'; +import isValidUrl from '../../common/utils/isValidUrl'; + +interface RedirectReceiverParams { + redirectMode: string; + redirectToUrl: string; + fadeOutDuration?: number; + holdTimeDuration?: number; + isFadingAvailable?: boolean; +} + +const goToAnotherSpace = (url: string, mode: string): void => { + const newWindow = window.open(url, mode); + + if (newWindow) { + newWindow.focus(); + } else { + console.error('Failed to open new window or tab.'); + } +}; + +const applyFadeOut = (target: Component3D, duration: number): void => { + fadeOut({ target, duration }); +}; + +const performRedirection = (redirectMode: string, redirectToUrl: string) => { + if (redirectMode === 'Existing') { + window.location.href = redirectToUrl; + } else { + goToAnotherSpace(redirectToUrl, redirectMode); + } +}; + +export default class RedirectReceiver extends ScriptBehavior { + @Param({ type: 'string', name: 'Web URL' }) + private redirectToUrl = ''; + + @Param({ + type: 'select', + options: ['New tab', 'Existing'], + name: 'Redirect or open in new tab', + }) + private redirectMode = 'Existing'; + + @Param({ type: 'boolean', defaultValue: false, name: 'Enable fadeIn - fadeOut' }) + private isFadingAvailable = false; + + @Param({ + min: 0.1, + step: 0.1, + type: 'number', + name: 'FadeOut duration', + visible: (params: RedirectReceiverParams) => params.isFadingAvailable === true, + }) + private fadeOutDuration = 0; + @Param({ + min: 0, + step: 0.1, + type: 'number', + defaultValue: 1, + name: 'Receiver execution delay (in seconds)', + visible: (params: RedirectReceiverParams) => params.isFadingAvailable === true, + }) + private holdTimeDuration = 0; + + @Folder('Signal Receiver') + @Receiver() + run() { + try { + if (!isValidUrl(this.redirectToUrl)) { + console.error('Invalid URL provided:', this.redirectToUrl); + return; + } + + if (this.isFadingAvailable && this.fadeOutDuration) { + applyFadeOut(Player.avatar, this.fadeOutDuration); + } + + const delay = this.holdTimeDuration * 1000; + + setTimeout(() => { + performRedirection(this.redirectMode, this.redirectToUrl); + }, delay); + } catch (error) { + console.error('Redirection failed -', (error as Error).message); + } + } +} diff --git a/src/behaviors/receivers/RotateReceiver.ts b/src/behaviors/receivers/RotateReceiver.ts new file mode 100644 index 0000000..dc750a1 --- /dev/null +++ b/src/behaviors/receivers/RotateReceiver.ts @@ -0,0 +1,77 @@ +import { ScriptBehavior, Component3D, Receiver, Folder, Param } from '@oo/scripting'; + +import anime from 'animejs'; + +export default class RotateReceiver extends ScriptBehavior { + @Param({ + min: 0, + step: 0.1, + type: 'number', + defaultValue: 1, + name: 'Receiver execution delay (in seconds)', + }) + private holdTimeDuration = 0; + + @Folder('Rotate Action Config') + @Param({ + min: 0, + max: 360, + step: 0.1, + type: 'number', + defaultValue: 0, + name: 'Rotation X axis', + }) + private rotationX = 0; + @Param({ + min: 0, + max: 360, + step: 0.1, + type: 'number', + defaultValue: 0, + name: 'Rotation Y axis', + }) + private rotationY = 0; + @Param({ + min: 0, + max: 360, + step: 0.1, + type: 'number', + defaultValue: 0, + name: 'Rotation Z axis', + }) + private rotationZ = 0; + @Param({ + min: 0, + max: 20, + step: 0.1, + type: 'number', + defaultValue: 0.5, + name: 'Rotation duration', + }) + private duration = 0.5; + + @Folder() + @Param({ + type: 'select', + name: 'Easing', + defaultValue: 'linear', + options: ['linear', 'easeInQuad', 'easeOutQuad', 'easeInOutQuad', 'easeInCubic', 'easeOutCubic', 'easeInOutCubic', 'easeInQuart', 'easeOutQuart', 'easeInOutQuart', 'easeInExpo', 'easeOutExpo', 'easeInOutExpo', 'easeInBack', 'easeOutBack', 'easeInOutBack', 'easeInElastic', 'easeOutElastic', 'easeInOutElastic', 'easeInBounce', 'easeOutBounce', 'easeInOutBounce'] + }) + private easing = 'linear'; + + @Receiver() + run() { + const delay = this.holdTimeDuration * 1000; + + setTimeout(() => { + anime({ + easing: this.easing, + targets: this.host.rotation, + duration: this.duration * 1000, + rotateX: this.host.rotation.x + this.rotationX * (Math.PI / 180), + rotateY: this.host.rotation.y + this.rotationY * (Math.PI / 180), + rotateZ: this.host.rotation.z + this.rotationZ * (Math.PI / 180), + }); + }, delay); + } +} diff --git a/src/behaviors/receivers/SoundReceiver.ts b/src/behaviors/receivers/SoundReceiver.ts new file mode 100644 index 0000000..06c48ba --- /dev/null +++ b/src/behaviors/receivers/SoundReceiver.ts @@ -0,0 +1,74 @@ +import { Param, Folder, ScriptBehavior, Receiver, AudioComponent } from '@oo/scripting'; + +export default class SoundReceiver extends ScriptBehavior { + @Folder('Sound Receiver Config') + @Param({ + min: 0, + max: 1, + step: 0.05, + type: 'number', + name: 'Volume', + defaultValue: 0.5, + }) + private volume = 0.5; + @Param({ type: 'boolean', defaultValue: false, name: 'loop?' }) + private loop = false; + @Param({ type: 'boolean', defaultValue: false, name: 'Ambient sound?' }) + private isAmbientSound = false; + @Param({ + min: 0, + step: 0.1, + type: 'number', + defaultValue: 1, + name: 'Receiver execution delay (in seconds)', + }) + private holdTimeDuration = 0; + + @Folder('Signal Receiver') + @Receiver() + play(): void { + try { + this.host.loop = this.loop; + this.host.volume = this.volume; + this.host.ambient = this.isAmbientSound; + + const delay = this.holdTimeDuration * 1000; + + setTimeout(() => { + this.host.play(); + }, delay); + } catch (error) { + console.error('Error trying to play the sound', error); + } + } + + @Receiver() + stop(): void { + try { + const delay = this.holdTimeDuration * 1000; + + setTimeout(() => { + if (this.host.isPlaying) { + this.host.stop(); + } + }, delay); + } catch (error) { + console.error('Error trying to stop the sound', error); + } + } + + @Receiver() + pause(): void { + try { + const delay = this.holdTimeDuration * 1000; + + setTimeout(() => { + if (this.host.isPlaying) { + this.host.pause(); + } + }, delay); + } catch (error) { + console.error('Error trying to pause the sound', error); + } + } +} diff --git a/src/behaviors/receivers/TeleportReceiver.ts b/src/behaviors/receivers/TeleportReceiver.ts new file mode 100644 index 0000000..6647c35 --- /dev/null +++ b/src/behaviors/receivers/TeleportReceiver.ts @@ -0,0 +1,110 @@ +import { + Param, + Player, + Folder, + $Param, + Receiver, + Component3D, + ScriptBehavior, +} from '@oo/scripting'; +import { Vector3 } from 'three'; + +import fadeIn from '../../common/utils/fadeIn'; +import fadeOut from '../../common/utils/fadeOut'; + +interface TeleportReceiverInputParams { + fadeInDuration?: number; + fadeOutDuration?: number; + holdTimeDuration?: number; + isFadingAvailable?: boolean; + targetComponent: Component3D; +} + +const applyFadeOut = (target: Component3D, duration: number) => { + fadeOut({ target, duration }); +}; + +const applyFadeIn = (target: Component3D, duration: number) => { + fadeIn({ target, duration }); +}; + +export default class TeleportReceiver extends ScriptBehavior { + @Param({ + type: 'component', + name: 'Component target', + }) + private targetComponent = $Param.Component('any'); + + @Folder('Animations') + @Param({ type: 'boolean', defaultValue: false, name: 'Enable fadeIn - fadeOut' }) + private isFadingAvailable = false; + @Param({ + min: 0.1, + step: 0.1, + type: 'number', + name: 'FadeIn duration', + visible: (params: TeleportReceiverInputParams) => params.isFadingAvailable === true, + }) + private fadeInDuration = 0; + @Param({ + min: 0.1, + step: 0.1, + type: 'number', + name: 'FadeOut duration', + visible: (params: TeleportReceiverInputParams) => params.isFadingAvailable === true, + }) + private fadeOutDuration = 0; + @Param({ + min: 0, + step: 0.1, + type: 'number', + defaultValue: 1, + name: 'Receiver execution delay (in seconds)', + visible: (params: TeleportReceiverInputParams) => params.isFadingAvailable === true, + }) + private holdTimeDuration = 0; + + private teleportAvatar(target: Component3D) { + const targetPosition = this.targetComponent.position; + const dimension = this.targetComponent.getDimensions(); + + const targetVector = new Vector3( + targetPosition.x, + targetPosition.y + dimension.y / 2, + targetPosition.z, + ); + + target.rigidBody.teleport(targetVector, this.targetComponent.quaternion); + } + + @Folder('Signal Receiver') + @Receiver() + run(): void { + const target = this.host.name.includes('Avatar Picker') ? Player.avatar : this.host; + + try { + if (!target.rigidBody) { + throw new Error('Collider must be activated to have rigidBody property available'); + } + + if (this.isFadingAvailable && this.fadeOutDuration) { + applyFadeOut(target, this.fadeOutDuration); + } + + const delay = this.holdTimeDuration * 1000; + + setTimeout(() => { + this.teleportAvatar(target); + + if (this.isFadingAvailable && this.fadeInDuration) { + applyFadeIn(target, this.fadeInDuration); + } + }, delay); + } catch (error) { + console.error( + `Teleportation failed to ${this.targetComponent.name || 'unknown target'}:`, + error, + ); + } + } +} diff --git a/src/behaviors/receivers/TranslateReceiver.ts b/src/behaviors/receivers/TranslateReceiver.ts new file mode 100644 index 0000000..28f361d --- /dev/null +++ b/src/behaviors/receivers/TranslateReceiver.ts @@ -0,0 +1,98 @@ +import { ScriptBehavior, Component3D, Receiver, Folder, Param } from '@oo/scripting'; + +import anime from 'animejs'; + +export default class TranslateReceiver extends ScriptBehavior { + @Param({ + min: 0, + step: 0.1, + type: 'number', + defaultValue: 1, + name: 'Receiver execution delay (in seconds)', + }) + private holdTimeDuration = 0; + + @Folder('Translate Receiver Config') + @Param({ + min: 0, + max: 360, + step: 0.1, + type: 'number', + defaultValue: 0, + name: 'Translate to X axis', + }) + private positionX = 0; + @Param({ + min: 0, + max: 360, + step: 0.1, + type: 'number', + defaultValue: 0, + name: 'Translate to Y axis', + }) + private positionY = 0; + @Param({ + min: 0, + max: 360, + step: 0.1, + type: 'number', + defaultValue: 0, + name: 'Translate to Z axis', + }) + private positionZ = 0; + @Param({ + min: 0, + max: 20, + step: 0.1, + type: 'number', + defaultValue: 0.5, + name: 'translate duration', + }) + private duration = 0.5; + @Param({ + type: 'select', + name: 'Easing', + defaultValue: 'linear', + options: [ + 'linear', + 'easeInQuad', + 'easeOutQuad', + 'easeInOutQuad', + 'easeInCubic', + 'easeOutCubic', + 'easeInOutCubic', + 'easeInQuart', + 'easeOutQuart', + 'easeInOutQuart', + 'easeInExpo', + 'easeOutExpo', + 'easeInOutExpo', + 'easeInBack', + 'easeOutBack', + 'easeInOutBack', + 'easeInElastic', + 'easeOutElastic', + 'easeInOutElastic', + 'easeInBounce', + 'easeOutBounce', + 'easeInOutBounce', + ], + }) + private easing = 'linear'; + + @Receiver() + run() { + const delay = this.holdTimeDuration * 1000; + + setTimeout(() => { + anime({ + easing: this.easing, + targets: this.host.position, + duration: this.duration * 1000, + translateX: this.host.position.x + this.positionX, + translateY: this.host.position.y + this.positionY, + translateZ: this.host.position.z + this.positionZ, + }); + }, delay); + } +} diff --git a/src/behaviors/redirect/ImplementationGuideOverlay.tsx b/src/behaviors/redirect/ImplementationGuideOverlay.tsx deleted file mode 100644 index c43c3cc..0000000 --- a/src/behaviors/redirect/ImplementationGuideOverlay.tsx +++ /dev/null @@ -1,257 +0,0 @@ -import * as React from 'react'; - -interface ImplementationGuideOverlayProps {} - -interface ImplementationGuideOverlayState { - isHelpVisible: boolean; -} - -export default class ImplementationGuideOverlay extends React.Component< - ImplementationGuideOverlayProps, - ImplementationGuideOverlayState -> { - constructor(props: ImplementationGuideOverlayProps) { - super(props); - this.state = { isHelpVisible: false }; - } - - handleClose = (event: React.MouseEvent) => { - event.preventDefault(); - this.setState({ isHelpVisible: false }); - }; - - handleOpen = (event: React.MouseEvent) => { - event.preventDefault(); - this.setState({ isHelpVisible: true }); - }; - - render() { - const styles = ` - body { - font-family: Arial, sans-serif; - background-color: #000; - color: #fff; - padding: 20px; - } - .folder-structure { - list-style-type: none; - padding-left: 20px; - } - .folder-structure li { - margin: 5px 0; - padding-left: 20px; - position: relative; - } - .folder-structure li::before { - content: ' '; - position: absolute; - top: 10px; - left: -20px; - width: 15px; - height: 2px; - background-color: #fff; - } - .folder-structure li::after { - content: ' '; - position: absolute; - top: 0; - left: -20px; - width: 2px; - height: 100%; - background-color: #fff; - } - .folder-structure li:last-child::after { - height: 10px; - } - .folder-icon, .file-icon { - margin-right: 5px; - } - .folder-icon::before { - content: '📁'; - } - .file-icon::before { - content: '📄'; - } - .image-button { - position: fixed; - top: 5%; - right: 3%; - border: none; - background: none; - padding: 0; - cursor: pointer; - } - .image-button img { - display: block; - width: 50px; - height: auto; - } - .wrapper { - position: fixed; - width: 100vw; - height: 100vh; - background-color: #000; - color: #fff; - display: flex; - flex-direction: column; - justify-content: flex-start; - align-items: center; - padding: 40px; - box-sizing: border-box; - overflow-y: auto; - } - .wrapper > button { - cursor: pointer; - color: #fff; - background: none; - border: 1px solid #fff; - padding: 10px; - margin-bottom: 20px; - font-size: 16px; - align-self: flex-end; - } - .section { - text-align: left; - margin-bottom: 20px; - width: 80%; - } - .section h2 { - margin-bottom: 10px; - font-size: 1.5em; - } - .section p { - margin-bottom: 10px; - font-size: 1em; - } - .m10 { - margin-left: 5px; - } - .sub-section { - margin: 15px 0; - } - .section ul { - list-style-type: none; - } - .section ul li { - margin-bottom: 10px; - } - `; - - return ( - <> - - {this.state.isHelpVisible && ( -
- -
-

Behavioral Component - Redirect

-

- The Redirect behavioral component allows users or entities within a virtual - environment to be seamlessly redirected to another URL or space, either in the same - window or in a new tab. -

-
-
-

Project Structure

-
    -
  • - Local Scripts -
      -
    • - RedirectBehavior.ts: Handles the behavior -
    • -
    • - RedirectAction.ts: Handles the redirect -
    • -
    • - FadeOut.ts: Optional fade-out effect for - redirection -
    • -
    • - InteractionDirector.ts: Utility for - managing interactions (By Key or Auto) -
    • -
    • - isValidUrl.ts: Checks that the URL is - valid -
    • -
    -
  • -
-
-
-

How to Use

-

To set up the Redirect component, configure the following parameters:

-
-

1. Web URL (Required):

-

- - Input the desired URL or the address of the space within your virtual - environment. -

-
-
-

2. Redirect Mode (Required):

-

- - Choose between redirecting in the same tab ("Existing") or opening in a new tab - ("New tab"). -

-
-
-

3. Interaction Mode (Required):

-

- - Select how the redirection is triggered: either by pressing a key ("Key") or - automatically when in proximity ("Auto"). -

-
-
-

4. Trigger Key (Depends on Interaction Mode):

-

- - If "Key" is selected, specify which key will trigger the redirect. -

-
-
-

5. Trigger Distance (Depends on Interaction Mode):

-

- - Adjust the distance within which the user can activate the redirect component. -

-
-
-

6. FadeOut Duration (Optional):

-

- - Set the duration of the fade-out effect before the redirection occurs. -

-
-
-

7. Interaction Hold Time (Optional):

-

- - Specify the delay time in seconds before the redirection is executed after - interaction. -

-
-

- {' '} - * Ensure that the required parameters are properly configured for the component to - function as expected. -

-
-
-

Changelog

-
    -
  • Sept 02, 2024 - v1.0.0: Initial release of the Redirect component.
  • -
-
-
-

Made with ❤️ by the NumenGames team

-
-
- )} - - - ); - } -} diff --git a/src/behaviors/redirect/README.md b/src/behaviors/redirect/README.md deleted file mode 100644 index d401b4d..0000000 --- a/src/behaviors/redirect/README.md +++ /dev/null @@ -1,94 +0,0 @@ -# Redirect Behavioral Component - -## What is the Redirect Behavioral Component? - -The **Redirect** behavioral component is a specialized feature developed for the **Oncyber** platform. It allows users or entities within a virtual environment to be seamlessly redirected to another URL or space, either in the same window or in a new tab. This enhances the ability to navigate across different virtual environments or web pages directly from within the platform. - -## Purpose - -The primary purpose of the **Redirect** component is to facilitate smooth transitions between different virtual spaces or web pages. This can be particularly useful in scenarios where you want to guide users to related content, external resources, or different sections of a virtual environment without requiring manual navigation. - -## Implementation Methods - -There are two main methods to implement the Redirect functionality in your project: - -### 1. Using the **all-in-one** Script - -The **all-in-one** script includes everything necessary to quickly integrate the Redirect functionality into your project. This method is ideal for projects that require a simple and quick setup, especially in smaller environments. - -### 2. Manual Integration - -For larger projects or components that are expected to be reused multiple times, you can manually integrate the Redirect functionality by copying the necessary scripts from both the `behaviors/redirect/RedirectBehavior.ts` directory and the `common` directory. This approach promotes better code reusability and maintainability. - -## Configuration Parameters - -To properly set up the Redirect component, certain parameters need to be configured. These parameters ensure that the component works correctly within your virtual environment. Below is a detailed explanation of each parameter: - -### 1. **Web URL** (Required) - -This is the URL or the virtual space to which the user will be redirected. - -- **How to configure**: Input the desired URL or the address of the space within your virtual environment. - -### 2. **Redirect Mode** (Required) - -This setting determines how the redirection will occur. There are two options: - -- **Existing**: The redirection will occur in the same tab or window. -- **New tab**: The redirection will open in a new tab or window. - -**Important Note**: Consider the user experience when choosing between the existing tab or a new tab, as it can impact the flow of navigation. - -### 3. **Interaction Mode** (Required) - -This setting determines how the player will interact with the Redirect component. There are two options: - -- **Key**: The player will need to press a specific key to trigger the redirect. -- **Auto**: The redirect will trigger automatically when the player comes into proximity with the component. - -**Important Note**: If you select "Auto" mode, ensure that the collision and sensor functionalities are activated on the component to detect the player’s presence and trigger the redirect. - -### 4. **Trigger Key** (Depends on Interaction Mode) - -This parameter is only required if you have selected "Key" as your Interaction Mode. It specifies which key the player needs to press to interact with the Redirect component. - -- **How to configure**: Choose the key from a predefined list that the player will use to trigger the redirect. - -### 5. **Trigger Distance** (Depends on Interaction Mode) - -This setting is also dependent on selecting "Key" as your Interaction Mode. It defines the distance within which the player can activate the Redirect component. - -- **How to configure**: Adjust the distance parameter to control how close the player needs to be to the component before they can interact with it using the specified key. - -### 6. FadeOut Duration (Optional) - -If you want to apply a fade-out effect before the redirection occurs, this parameter allows you to set the duration of the effect. - -- **How to configure**: Set the duration for the fade-out effect in seconds. - -### 7. Interaction Hold Time (Optional) - -This setting defines the delay (in seconds) before the redirection occurs after the interaction is triggered. - -- **How to configure**: Specify the delay time in seconds to control how long after the interaction the redirection will be executed. - -## Directory Structure - -Below is a representation of the relevant directory structure for the Redirect component and its dependencies: - -```plaintext -├── behaviors -│ └── redirect -│ └── RedirectBehavior.ts # Handles the behavior -└── common - ├── components - │ └── RedirectAction.ts # Handles the redirect - ├── effects - │ └── FadeOut.ts # Fade out effect optionally used by the redirect if defined - └── interactions - └── InteractionDirector.ts # Utility for managing interactions (By Key or Auto) - └── utils - └── isValidUrl.ts # Checks that the URL is valid -``` - -This directory structure highlights the main Redirect behavior script and its dependencies located in the common directory, which are essential for implementing the Redirect functionality in a scalable way. diff --git a/src/behaviors/redirect/RedirectBehavior.ts b/src/behaviors/redirect/RedirectBehavior.ts deleted file mode 100644 index 476ea65..0000000 --- a/src/behaviors/redirect/RedirectBehavior.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { Folder, Param, ScriptBehavior } from '@oo/scripting'; - -import RedirectAction from '../../common/components/RedirectAction.ts'; -import InteractionDirector from '../../common/interactions/InteractionDirector.ts'; - -interface RedirectBehaviorParams { - triggerKey: string; - redirectToUrl: string; - redirectAction: string; - interactionMode: string; - triggerDistance: number; - fadeOutDuration: number; - isFadingAvailable: boolean; - interactionHoldTime: number; - yInteractionAdjustment: number; -} - -/** - * Main class to handle redirect behavior in the script. - */ -export default class RedirectBehavior extends ScriptBehavior { - static config = { - title: 'Behavior - Redirect', - description: 'A behavior to redirect to another space or URL', - tip: 'Use this if you wanna change or open the navigator with a new URL', - }; - - @Param({ type: 'string', name: 'Web URL' }) - private redirectToUrl = ''; - - @Param({ - type: 'select', - options: ['New tab', 'Existing'], - name: 'Redirect or open in new tab', - }) - private redirectMode = 'Existing'; - - @Folder('Interaction Mode') - @Param({ - type: 'select', - name: 'Interaction Mode', - options: ['Auto', 'Key'], - }) - private interactionMode = 'Auto'; - @Param({ - type: 'string', - name: 'Trigger key', - visible: (params: RedirectBehaviorParams) => params.interactionMode === 'Key', - }) - private triggerKey = 'E'; - @Param({ - step: 0.1, - type: 'number', - name: 'Key dialog adjustment', - visible: (params: RedirectBehaviorParams) => params.interactionMode === 'Key', - }) - private yInteractionAdjustment = 0; - @Param({ - min: 0.1, - step: 0.1, - type: 'number', - defaultValue: 2, - name: 'Trigger distance', - visible: (params: RedirectBehaviorParams) => params.interactionMode === 'Key', - }) - private triggerDistance = 2; - - @Folder('Animations') - @Param({ type: 'boolean', defaultValue: false, name: 'Enable fadeIn - fadeOut' }) - private isFadingAvailable = false; - @Param({ - min: 0.1, - step: 0.1, - type: 'number', - name: 'FadeOut duration', - visible: (params: RedirectBehaviorParams) => params.isFadingAvailable === true, - }) - private fadeOutDuration = 0; - @Param({ - min: 0, - step: 0.1, - type: 'number', - defaultValue: 1, - name: 'Teleport delay (seconds)', - visible: (params: RedirectBehaviorParams) => params.isFadingAvailable === true, - }) - private interactionHoldTime = 0; - - /** - * Called when the script is ready. - */ - onReady = async () => { - await InteractionDirector.handle(this, this.redirectAction.bind(this)); - }; - - /** - * Executes the redirect action. - */ - private redirectAction() { - RedirectAction({ - redirectMode: this.redirectMode, - redirectToUrl: this.redirectToUrl, - fadeOutDuration: this.fadeOutDuration, - isFadingAvailable: this.isFadingAvailable, - holdTimeDuration: this.interactionHoldTime, - }); - } -} diff --git a/src/behaviors/redirect/all-in-one.ts b/src/behaviors/redirect/all-in-one.ts deleted file mode 100644 index 977b8ac..0000000 --- a/src/behaviors/redirect/all-in-one.ts +++ /dev/null @@ -1,198 +0,0 @@ -import gsap from 'gsap'; -import { Param, Folder, Player, Components, Component3D, ScriptBehavior } from '@oo/scripting'; - -interface RedirectBehaviorParams { - triggerKey: string; - redirectToUrl: string; - redirectAction: string; - interactionMode: string; - triggerDistance: number; - fadeOutDuration: number; - isFadingAvailable: boolean; - interactionHoldTime: number; - yInteractionAdjustment: number; -} - -/** - * Main class to handle redirect behavior in the script. - */ -export default class RedirectBehavior extends ScriptBehavior { - static config = { - title: 'Behavior - Redirect All-In-One', - description: 'A behavior to redirect to another space or URL', - tip: 'Use this if you wanna change or open the navigator with a new URL', - }; - - @Param({ type: 'string', name: 'Web URL' }) - private redirectToUrl = ''; - - @Param({ - type: 'select', - options: ['New tab', 'Existing'], - name: 'Redirect or open in new tab', - }) - private redirectMode = 'Existing'; - - @Folder('Interaction Mode') - @Param({ - type: 'select', - name: 'Interaction Mode', - options: ['Auto', 'Key'], - }) - private interactionMode = 'Auto'; - @Param({ - type: 'string', - name: 'Trigger key', - visible: (params: RedirectBehaviorParams) => params.interactionMode === 'Key', - }) - private triggerKey = 'E'; - @Param({ - step: 0.1, - type: 'number', - name: 'Key dialog adjustment', - visible: (params: RedirectBehaviorParams) => params.interactionMode === 'Key', - }) - private yInteractionAdjustment = 0; - @Param({ - min: 0.1, - step: 0.1, - type: 'number', - defaultValue: 2, - name: 'Trigger distance', - visible: (params: RedirectBehaviorParams) => params.interactionMode === 'Key', - }) - private triggerDistance = 2; - - @Folder('Animations') - @Param({ type: 'boolean', defaultValue: false, name: 'Enable fadeIn - fadeOut' }) - private isFadingAvailable = false; - @Param({ - min: 0.1, - step: 0.1, - type: 'number', - name: 'FadeOut duration', - visible: (params: RedirectBehaviorParams) => params.isFadingAvailable === true, - }) - private fadeOutDuration = 0; - @Param({ - min: 0, - step: 0.1, - type: 'number', - defaultValue: 1, - name: 'Teleport delay (seconds)', - visible: (params: RedirectBehaviorParams) => params.isFadingAvailable === true, - }) - private interactionHoldTime = 0; - - /** - * Called when the script is ready. - */ - onReady = async () => { - switch (this.interactionMode) { - case 'Key': - await this.createInteractionByKey(); - break; - case 'Auto': - await this.createInteractionAuto(); - break; - default: - return; - } - }; - - /** - * Creates an interaction that is triggered by a key press. - */ - private async createInteractionByKey() { - const interaction = await Components.create({ - type: 'interaction', - distance: this.triggerDistance, - distanceTarget: Player.avatar.position, - key: `Key${this.triggerKey.toUpperCase()}`, - atlas: `keyboard_${this.triggerKey.toLowerCase()}_outline`, - }); - - this.updateInteractionPosition(interaction); - - interaction.active = true; - - interaction.onInteraction(this.redirectAction.bind(this)); - } - - /** - * Creates an automatic interaction based on proximity. - */ - private async createInteractionAuto() { - this.host.onSensorEnter(async () => { - await this.redirectAction(); - }); - } - - /** - * Updates the position of the interaction. - * @param {Object} interaction - The interaction to update. - */ - private updateInteractionPosition(interaction: Component3D) { - interaction.position.copy(this.host.position); - interaction.position.y = - this.host.position.y + this.host.getDimensions().y + this.yInteractionAdjustment; - } - - /** - * Executes the teleportation action. - */ - private async redirectAction() { - try { - if (!this.isValidUrl(this.redirectToUrl)) { - console.error('Invalid URL provided:', this.redirectToUrl); - return; - } - - if (this.isFadingAvailable && this.fadeOutDuration) { - this.applyFadeOut(); - } - - const delay = this.interactionHoldTime * 1000; - - setTimeout(() => { - this.performRedirection(this.redirectMode, this.redirectToUrl); - }, delay); - } catch (error) { - console.error('Redirection failed -', (error as Error).message); - } - } - - private goToAnotherSpace(url: string, mode: string): void { - const newWindow = window.open(url, mode); - - if (newWindow) { - newWindow.focus(); - } else { - console.error('Failed to open new window or tab.'); - } - } - - private performRedirection(redirectMode: string, redirectToUrl: string) { - if (redirectMode === 'Existing') { - window.location.href = redirectToUrl; - } else { - this.goToAnotherSpace(redirectToUrl, redirectMode); - } - } - - private isValidUrl(url: string): boolean { - try { - new URL(url); - return true; - } catch { - return false; - } - } - - private applyFadeOut() { - gsap.to(Player.avatar, { - duration: this.fadeOutDuration, - opacity: 0, - }); - } -} diff --git a/src/behaviors/teleport/ImplementationGuideOverlay.tsx b/src/behaviors/teleport/ImplementationGuideOverlay.tsx deleted file mode 100644 index fb5eb1f..0000000 --- a/src/behaviors/teleport/ImplementationGuideOverlay.tsx +++ /dev/null @@ -1,259 +0,0 @@ -import * as React from 'react'; - -interface ImplementationGuideOverlayProps {} - -interface ImplementationGuideOverlayState { - isHelpVisible: boolean; -} - -export default class ImplementationGuideOverlay extends React.Component< - ImplementationGuideOverlayProps, - ImplementationGuideOverlayState -> { - constructor(props: ImplementationGuideOverlayProps) { - super(props); - this.state = { isHelpVisible: false }; - } - - handleClose = (event: React.MouseEvent) => { - event.preventDefault(); - this.setState({ isHelpVisible: false }); - }; - - handleOpen = (event: React.MouseEvent) => { - event.preventDefault(); - this.setState({ isHelpVisible: true }); - }; - - render() { - const styles = ` - body { - font-family: Arial, sans-serif; - background-color: #000; - color: #fff; - padding: 20px; - } - .folder-structure { - list-style-type: none; - padding-left: 20px; - } - .folder-structure li { - margin: 5px 0; - padding-left: 20px; - position: relative; - } - .folder-structure li::before { - content: ' '; - position: absolute; - top: 10px; - left: -20px; - width: 15px; - height: 2px; - background-color: #fff; - } - .folder-structure li::after { - content: ' '; - position: absolute; - top: 0; - left: -20px; - width: 2px; - height: 100%; - background-color: #fff; - } - .folder-structure li:last-child::after { - height: 10px; - } - .folder-icon, .file-icon { - margin-right: 5px; - } - .folder-icon::before { - content: '📁'; - } - .file-icon::before { - content: '📄'; - } - .image-button { - position: fixed; - top: 5%; - right: 3%; - border: none; - background: none; - padding: 0; - cursor: pointer; - } - .image-button img { - display: block; - width: 50px; - height: auto; - } - .wrapper { - position: fixed; - width: 100vw; - height: 100vh; - background-color: #000; - color: #fff; - display: flex; - flex-direction: column; - justify-content: flex-start; - align-items: center; - padding: 40px; - box-sizing: border-box; - overflow-y: auto; - } - .wrapper > button { - cursor: pointer; - color: #fff; - background: none; - border: 1px solid #fff; - padding: 10px; - margin-bottom: 20px; - font-size: 16px; - align-self: flex-end; - } - .section { - text-align: left; - margin-bottom: 20px; - width: 80%; - } - .section h2 { - margin-bottom: 10px; - font-size: 1.5em; - } - .section p { - margin-bottom: 10px; - font-size: 1em; - } - .m10 { - margin-left: 5px; - } - .sub-section { - margin: 15px 0; - } - .section ul { - list-style-type: none; - } - .section ul li { - margin-bottom: 10px; - } - `; - - return ( - <> - - {this.state.isHelpVisible && ( -
- -
-

Behavioral Component - Teleport

-

- The Teleport behavioral component allows users or entities within a virtual - environment to instantly move from one location to another, either by pressing a key - or automatically interacting with an element. -

-
-
-

Project Structure

-
    -
  • - Local Scripts -
      -
    • - TeleportBehavior.ts: Handles the - teleportation behavior -
    • -
    • - TeleportAction.ts: Executes the - teleportation logic -
    • -
    • - FadeIn.ts: Optional fade-in effect used - during teleportation -
    • -
    • - FadeOut.ts: Optional fade-out effect used - during teleportation -
    • -
    • - InteractionDirector.ts: Manages - interactions (Key or Auto) -
    • -
    -
  • -
-
-
-

How to Use

-

To set up the Teleport component, configure the following parameters:

-
-

1. Component Target (Required):

-

- Specify the component that the user will be teleported to.

-
-
-

2. Interaction Mode (Required):

-

- - Choose how the teleportation is triggered: either by pressing a key ("Key") or - automatically when in proximity ("Auto"). -

-
-
-

3. Trigger Key (Depends on Interaction Mode):

-

- - If "Key" is selected, specify the key that will trigger the teleportation. -

-
-
-

4. Trigger Distance (Depends on Interaction Mode):

-

- - Adjust the distance within which the user can activate the teleportation. -

-
-
-

5. Enable FadeIn - FadeOut (Optional):

-

- - Enable and set the duration for fade-in and fade-out effects during - teleportation. -

-
-
-

6. Teleport Delay (Optional):

-

- - Set the delay in seconds before the teleportation occurs after interaction. -

-
-

- * Ensure that the required parameters are properly configured for the component to - function as expected. -

-
-
-

Changelog

-
    -
  • - Sep 02, 2024 - v2.0.1: Fixed an issue where the delay could be undefined in - certain scenarios. -
  • -
  • - Aug 25, 2024 - v2.0.0: Restructured the component to support all-in-one or modular - usages. -
  • -
  • - Aug 5, 2024 - v1.0.0: Initial release of the component with teleportation - functionality. -
  • -
-
-
-

Made with ❤️ by the NumenGames team

-
-
- )} - - - ); - } -} diff --git a/src/behaviors/teleport/README.md b/src/behaviors/teleport/README.md deleted file mode 100644 index 986e152..0000000 --- a/src/behaviors/teleport/README.md +++ /dev/null @@ -1,72 +0,0 @@ -# Teleport Behavioral Component - -## What is the Teleport Component? - -The **Teleport** component is a specialized feature developed for the **Oncyber** platform. It is designed to enable users or entities within a virtual environment to instantly move from one location to another, thereby enhancing navigation and usability within the environment. - -## Purpose - -The primary purpose of the **Teleport** component is to facilitate quick navigation across different areas of a virtual environment. This eliminates the need to manually traverse large distances, which is especially useful in extensive virtual spaces where rapid movement is essential for improving user experience. - -## Implementation Methods - -There are two main methods to implement the Teleport functionality in your project: - -### 1. Using the **all-in-one** Script - -The **all-in-one** script includes everything necessary to quickly integrate the Teleport functionality into your project. This method is ideal for projects that are not expected to scale significantly or those that are relatively small in scope. - -### 2. Manual Integration - -For larger projects or components that are expected to be reused multiple times, you can manually integrate the Teleport functionality by copying the necessary scripts from both the `behaviors/teleport/TeleportBehavior.ts` directory and the `common` directory. This method helps avoid repeating the same code structures across your project, making it more maintainable and scalable. - -## Configuration Parameters - -To properly set up the Teleport component, certain parameters need to be configured. These parameters ensure that the component works correctly within your virtual environment. Below is a detailed explanation of each parameter: - -### 1. **Component Target** (Required) - -This is the target component to which the player will be teleported when they interact with the component associated with the Teleport behavioral component. - -- **How to configure**: Select the object or area within your virtual environment that you want the player to be teleported to. - -### 2. **Interaction Mode** (Required) - -This setting determines how the player will interact with the Teleport component. There are two options: - -- **Key**: The player will need to press a specific key to trigger the teleport. -- **Auto**: The teleport will trigger automatically when the player comes into proximity with the component. - -**Important Note**: If you select "Auto" mode, ensure that the collision and sensor functionalities are activated on the component to detect the player’s presence and trigger the teleport. - -### 3. **Trigger Key** (Depends on Interaction Mode) - -This parameter is only required if you have selected "Key" as your Interaction Mode. It specifies which key the player needs to press to interact with the Teleport component. - -- **How to configure**: Choose the key from a predefined list that the player will use to trigger the teleport. - -### 4. **Key Dialog Adjustment** (Depends on Interaction Mode) - -This setting is also dependent on selecting "Key" as your Interaction Mode. It defines the distance within which the player can activate the Teleport component. - -- **How to configure**: Adjust the distance parameter to control how close the player needs to be to the component before they can interact with it using the specified key. - -## Directory Structure - -Below is a representation of the relevant directory structure for the Teleport component and its dependencies: - -```plaintext -├── behaviors -│ └── teleport -│ └── TeleportBehavior.ts # Handles the behavior -└── common - ├── components - │ └── TeleportAction.ts # Handles the teleport - ├── effects - │ ├── FadeIn.ts # Fade in effect optionally used by the teleport if defined - │ └── FadeOut.ts # Fade out effect optionally used by the teleport if defined - └── utils - └── InteractionDirector.ts # Utility for managing interactions (By Key or Auto) -``` - -This directory structure highlights the main Teleport behavior script and its dependencies located in the common directory, which are essential for implementing the Teleport functionality in a scalable way. diff --git a/src/behaviors/teleport/TeleportBehavior.ts b/src/behaviors/teleport/TeleportBehavior.ts deleted file mode 100644 index 55ba234..0000000 --- a/src/behaviors/teleport/TeleportBehavior.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { Folder, Param, $Param, ScriptBehavior } from '@oo/scripting'; - -import TeleportAction from '../../common/components/TeleportAction.ts'; -import InteractionDirector from '../../common/interactions/InteractionDirector.ts'; - -interface TeleportBehaviorParams { - triggerKey: string; - fadeInDuration: number; - interactionMode: string; - triggerDistance: number; - fadeOutDuration: number; - isFadingAvailable: boolean; - interactionHoldTime: number; - teleportToComponentId: string; - yInteractionAdjustment: number; -} - -/** - * Main class to handle teleportation behavior in the script. - */ -export default class TeleportBehavior extends ScriptBehavior { - static config = { - title: 'Behavior - Teleport', - description: 'A behavior to teleport the avatar to a component', - tip: 'Use this to move the avatar between the space objects.', - }; - - @Param({ - type: 'component', - name: 'Component target', - }) - private componentTarget = $Param.Component('any'); - - @Folder('Interaction Mode') - @Param({ - type: 'select', - name: 'Interaction Mode', - options: ['Auto', 'Key'], - }) - private interactionMode = 'Auto'; - @Param({ - type: 'string', - name: 'Trigger key', - visible: (params: TeleportBehaviorParams) => params.interactionMode === 'Key', - }) - private triggerKey = 'E'; - @Param({ - step: 0.1, - type: 'number', - name: 'Key dialog adjustment', - visible: (params: TeleportBehaviorParams) => params.interactionMode === 'Key', - }) - private yInteractionAdjustment = 0; - @Param({ - min: 0.1, - step: 0.1, - type: 'number', - defaultValue: 2, - name: 'Trigger distance', - visible: (params: TeleportBehaviorParams) => params.interactionMode === 'Key', - }) - private triggerDistance = 2; - - @Folder('Animations') - @Param({ type: 'boolean', defaultValue: false, name: 'Enable fadeIn - fadeOut' }) - private isFadingAvailable = false; - @Param({ - min: 0.1, - step: 0.1, - type: 'number', - name: 'FadeIn duration', - visible: (params: TeleportBehaviorParams) => params.isFadingAvailable === true, - }) - private fadeInDuration = 0; - @Param({ - min: 0.1, - step: 0.1, - type: 'number', - name: 'FadeOut duration', - visible: (params: TeleportBehaviorParams) => params.isFadingAvailable === true, - }) - private fadeOutDuration = 0; - @Param({ - min: 0, - step: 0.1, - type: 'number', - defaultValue: 1, - name: 'Teleport delay (seconds)', - visible: (params: TeleportBehaviorParams) => params.isFadingAvailable === true, - }) - private interactionHoldTime = 0; - - /** - * Called when the script is ready. - */ - onReady = async () => { - await InteractionDirector.handle(this, this.teleportAction.bind(this)); - }; - - /** - * Executes the teleportation action. - */ - private teleportAction() { - TeleportAction({ - fadeInDuration: this.fadeInDuration, - targetComponent: this.componentTarget, - fadeOutDuration: this.fadeOutDuration, - isFadingAvailable: this.isFadingAvailable, - holdTimeDuration: this.interactionHoldTime, - }); - } -} diff --git a/src/behaviors/teleport/all-in-one.ts b/src/behaviors/teleport/all-in-one.ts deleted file mode 100644 index 0752a8d..0000000 --- a/src/behaviors/teleport/all-in-one.ts +++ /dev/null @@ -1,203 +0,0 @@ -import { - Param, - Folder, - Player, - $Param, - Components, - Component3D, - ScriptBehavior, -} from '@oo/scripting'; -import gsap from 'gsap'; -import { Vector3 } from 'three'; - -interface TeleportInterfaceParams { - triggerKey: string; - fadeInDuration: number; - interactionMode: string; - triggerDistance: number; - fadeOutDuration: number; - isFadingAvailable: boolean; - interactionHoldTime: number; - teleportToComponentId: string; - yInteractionAdjustment: number; -} - -/** - * Main class to handle teleportation behavior in the script. - */ -export default class TeleportBehavior extends ScriptBehavior { - static config = { - title: 'Behavior - Teleport All-In-One', - description: 'A behavior to teleport the avatar to a component', - tip: 'Use this when you need to move the avatar between the space objects.', - }; - - @Param({ - type: 'component', - name: 'Component target', - }) - private componentTarget = $Param.Component('any'); - - @Folder('Interaction Mode') - @Param({ - type: 'select', - name: 'Interaction Mode', - options: ['Auto', 'Key'], - }) - private interactionMode = 'Auto'; - @Param({ - type: 'string', - name: 'Trigger key', - visible: (params: TeleportInterfaceParams) => params.interactionMode === 'Key', - }) - private triggerKey = 'E'; - @Param({ - step: 0.1, - type: 'number', - name: 'Key dialog adjustment', - visible: (params: TeleportInterfaceParams) => params.interactionMode === 'Key', - }) - private yInteractionAdjustment = 0; - @Param({ - max: 50, - min: 0.1, - step: 0.1, - type: 'number', - defaultValue: 2, - name: 'Trigger distance (s)', - visible: (params: TeleportInterfaceParams) => params.interactionMode === 'Key', - }) - private triggerDistance = 2; - - @Folder('Animations') - @Param({ type: 'boolean', defaultValue: false, name: 'Enable fadeIn - fadeOut' }) - private isFadingAvailable = false; - @Param({ - max: 10, - min: 0.1, - type: 'number', - defaultValue: 1, - name: 'FadeIn duration', - visible: (params: TeleportInterfaceParams) => params.isFadingAvailable === true, - }) - private fadeInDuration = 0; - @Param({ - max: 10, - min: 0.1, - type: 'number', - defaultValue: 1, - name: 'FadeOut duration', - visible: (params: TeleportInterfaceParams) => params.isFadingAvailable === true, - }) - private fadeOutDuration = 0; - @Param({ - min: 0, - step: 0.1, - type: 'number', - defaultValue: 1, - name: 'Teleport delay (seconds)', - visible: (params: TeleportInterfaceParams) => params.isFadingAvailable === true, - }) - private interactionHoldTime = 0; - - /** - * Called when the script is ready. - */ - onReady = async () => { - switch (this.interactionMode) { - case 'Key': - await this.createInteractionByKey(); - break; - case 'Auto': - await this.createInteractionAuto(); - break; - default: - return; - } - }; - - /** - * Creates an interaction that is triggered by a key press. - */ - private async createInteractionByKey() { - const interaction = await Components.create({ - type: 'interaction', - distance: this.triggerDistance, - distanceTarget: Player.avatar.position, - key: `Key${this.triggerKey.toUpperCase()}`, - atlas: `keyboard_${this.triggerKey.toLowerCase()}_outline`, - }); - - this.updateInteractionPosition(interaction); - - interaction.active = true; - - interaction.onInteraction(this.teleportAction.bind(this)); - } - - /** - * Creates an automatic interaction based on proximity. - */ - private async createInteractionAuto() { - this.host.onSensorEnter(async () => { - await this.teleportAction(); - }); - } - - /** - * Updates the position of the interaction. - * @param {Object} interaction - The interaction to update. - */ - private updateInteractionPosition(interaction: Component3D) { - interaction.position.copy(this.host.position); - interaction.position.y = - this.host.position.y + this.host.getDimensions().y + this.yInteractionAdjustment; - } - - /** - * Executes the teleportation action. - */ - private async teleportAction() { - try { - this.isFadingAvailable ? this.applyFadeOut() : null; - - setTimeout(() => { - this.teleportAvatar(); - this.isFadingAvailable ? this.applyFadeIn() : null; - }, this.interactionHoldTime * 1000); - } catch (error) { - console.error( - `Teleportation failed to ${this.componentTarget.name || 'unknown target'}:`, - error, - ); - } - } - - private teleportAvatar() { - const targetPosition = this.componentTarget.position; - const dimension = this.componentTarget.getDimensions(); - - const targetVector = new Vector3( - targetPosition.x, - targetPosition.y + dimension.y / 2, - targetPosition.z, - ); - - Player.avatar.rigidBody.teleport(targetVector, this.componentTarget.quaternion); - console.log('Teleportation successful.'); - } - - private applyFadeOut() { - gsap.to(Player.avatar, { - duration: this.fadeOutDuration, - opacity: 0, - }); - } - - private applyFadeIn() { - gsap.to(Player.avatar, { - opacity: 1, - duration: this.fadeInDuration, - }); - } -} diff --git a/src/behaviors/teleport/main.ts b/src/behaviors/teleport/main.ts deleted file mode 100644 index b41adc2..0000000 --- a/src/behaviors/teleport/main.ts +++ /dev/null @@ -1,58 +0,0 @@ -import * as React from 'react'; -import { UI } from '@oo/scripting'; - -import ImplementationGuideOverlay from './ImplementationGuideOverlay.tsx'; - -export default class Game { - private renderer; - - constructor() { - this.renderer = UI.createRenderer(); - } - - async onPreload() { - // invoked once as soon as the game starts loading - // Use this load resources not already on the scene - console.log('Game: preload'); - } - - onReady = async () => { - // invoked once when the game has finished loading - // Use this for one time initialization logic - console.log('Game: ready'); - }; - - onStart = async () => { - // invoked each time user starts or replays the game (everytime World.start() is called, we call it by default in Display script) - // Use this for each play/replay game logic - console.log('Game: start'); - - this.renderer.render(React.createElement(ImplementationGuideOverlay)); - }; - - onUpdate = () => { - // this will be invoked on each frame (assuming the game is not paused) - // Use this to update the game state, 3D position ... - }; - - onPause = async () => { - // invoked when the game has been paused from the frontend (pause button, login ...) - console.log('Game: pause'); - }; - - onResume = async () => { - // invoked when the game resumes after pause from the frontend (eg login finished) - console.log('Game: resume'); - }; - - onEnd = async () => { - // invoked each time user ends the game (everytime World.stop() is called.) - // Use this for each end game logic - console.log('Game: end'); - }; - - onDispose = async () => { - // invoked when it's time to dispose resources (e.g. refreshing the page/switching pages) - console.log('Game: dispose'); - }; -} diff --git a/src/common/components/DiscordNotification.ts b/src/common/components/DiscordNotification.ts deleted file mode 100644 index 87d8a14..0000000 --- a/src/common/components/DiscordNotification.ts +++ /dev/null @@ -1,31 +0,0 @@ -import network from '../network/network.ts'; -import isValidUrl from '../utils/isValidUrl.ts'; - -const SERVER_URL = 'https://jolly-poetry-c57a.sy54bjwfmg.workers.dev/api/notifications/discord'; - -export interface DiscordNotificationParams { - message: string; - webhookUrl: string; -} - -export default async ({ webhookUrl, message }: DiscordNotificationParams): Promise => { - if (!webhookUrl || !isValidUrl(webhookUrl)) { - console.error('No valid webhook URL provided!'); - return; - } - - try { - await network.postRequest({ - url: SERVER_URL, - options: { - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ webhookUrl, message }), - }, - }); - console.log('Discord notification sent successfully.'); - } catch (error) { - console.error('Failed to send Discord notification:', error); - } -}; diff --git a/src/common/components/RedirectAction.ts b/src/common/components/RedirectAction.ts deleted file mode 100644 index a3a9fff..0000000 --- a/src/common/components/RedirectAction.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { Player, Component3D } from '@oo/scripting'; - -import fadeOut from '../effects/FadeOut.ts'; -import isValidUrl from '../utils/isValidUrl.ts'; - -interface RedirectActionParams { - redirectMode: string; - redirectToUrl: string; - fadeOutDuration?: number; - holdTimeDuration?: number; - isFadingAvailable?: boolean; -} - -const goToAnotherSpace = (url: string, mode: string): void => { - const newWindow = window.open(url, mode); - - if (newWindow) { - newWindow.focus(); - } else { - console.error('Failed to open new window or tab.'); - } -}; - -const applyFadeOut = (targetComponent: Component3D, duration: number): void => { - fadeOut({ targetComponent, duration }); -}; - -const performRedirection = (redirectMode: string, redirectToUrl: string) => { - if (redirectMode === 'Existing') { - window.location.href = redirectToUrl; - } else { - goToAnotherSpace(redirectToUrl, redirectMode); - } -}; - -export default ({ - redirectMode, - redirectToUrl, - fadeOutDuration, - holdTimeDuration = 0, - isFadingAvailable = false, -}: RedirectActionParams) => { - try { - if (!isValidUrl(redirectToUrl)) { - console.error('Invalid URL provided:', redirectToUrl); - return; - } - - if (isFadingAvailable && fadeOutDuration) { - applyFadeOut(Player.avatar, fadeOutDuration); - } - - const delay = holdTimeDuration * 1000; - - setTimeout(() => { - performRedirection(redirectMode, redirectToUrl); - }, delay); - } catch (error) { - console.error('Redirection failed -', (error as Error).message); - } -}; diff --git a/src/common/components/TeleportAction.ts b/src/common/components/TeleportAction.ts deleted file mode 100644 index e40406d..0000000 --- a/src/common/components/TeleportAction.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { Vector3 } from 'three'; -import { Player, Component3D } from '@oo/scripting'; - -import fadeIn from '../effects/FadeIn.ts'; -import fadeOut from '../effects/FadeOut.ts'; - -interface TeleportActionInputParams { - fadeInDuration?: number; - fadeOutDuration?: number; - holdTimeDuration?: number; - isFadingAvailable?: boolean; - targetComponent: Component3D; -} - -const applyFadeOut = (targetComponent: Component3D, duration: number) => { - fadeOut({ targetComponent, duration }); -}; - -const teleportAvatar = (targetComponent: Component3D) => { - const targetPosition = targetComponent.position; - const dimension = targetComponent.getDimensions(); - - const targetVector = new Vector3( - targetPosition.x, - targetPosition.y + dimension.y / 2, - targetPosition.z, - ); - - Player.avatar.rigidBody.teleport(targetVector, targetComponent.quaternion); - console.log('Teleportation successful.'); -}; - -const applyFadeIn = (targetComponent: Component3D, duration: number) => { - fadeIn({ targetComponent, duration }); -}; - -export default ({ - fadeInDuration, - targetComponent, - fadeOutDuration, - holdTimeDuration = 0, - isFadingAvailable = false, -}: TeleportActionInputParams): void => { - const delay = holdTimeDuration * 1000; - - try { - if (isFadingAvailable && fadeOutDuration) { - applyFadeOut(Player.avatar, fadeOutDuration); - } - - setTimeout(() => { - teleportAvatar(targetComponent); - - if (isFadingAvailable && fadeInDuration) { - applyFadeIn(Player.avatar, fadeInDuration); - } - }, delay); - } catch (error) { - console.error(`Teleportation failed to ${targetComponent.name || 'unknown target'}:`, error); - } -}; diff --git a/src/common/effects/FadeIn.ts b/src/common/effects/FadeIn.ts deleted file mode 100644 index c9a7b69..0000000 --- a/src/common/effects/FadeIn.ts +++ /dev/null @@ -1,14 +0,0 @@ -import gsap from 'gsap'; - -interface FadeInInputParams { - opacity?: number; - duration?: number; - targetComponent: any; -} - -export default ({ targetComponent, duration = 0, opacity = 1 }: FadeInInputParams) => { - gsap.to(targetComponent, { - opacity, - duration, - }); -}; diff --git a/src/common/effects/FadeOut.ts b/src/common/effects/FadeOut.ts deleted file mode 100644 index ef31217..0000000 --- a/src/common/effects/FadeOut.ts +++ /dev/null @@ -1,14 +0,0 @@ -import gsap from 'gsap'; - -interface FadeOutInputParams { - opacity?: number; - duration?: number; - targetComponent: any; -} - -export default ({ targetComponent, duration = 0, opacity = 0 }: FadeOutInputParams) => { - gsap.to(targetComponent, { - opacity, - duration, - }); -}; diff --git a/src/common/interactions/InteractionDirector.ts b/src/common/interactions/InteractionDirector.ts index 1ff6211..2a11952 100644 --- a/src/common/interactions/InteractionDirector.ts +++ b/src/common/interactions/InteractionDirector.ts @@ -23,6 +23,11 @@ interface InteractionDirectorParams { * Adjustment to the Y position of the interaction. */ yInteractionAdjustment: number; + + /** + * Define if its possible to interact with the element just once or multiple times. + */ + isActiveMultipleTimes: boolean; } /** @@ -55,7 +60,7 @@ export default class InteractionDirector { * @param {() => void} action - The action to be executed when the interaction is triggered. */ private async createInteractionByKey( - { distance, triggerKey, host, yInteractionAdjustment }: InteractionDirectorParams, + { distance, triggerKey, host, yInteractionAdjustment, isActiveMultipleTimes }: InteractionDirectorParams, action: () => void, ) { const interaction = await Components.create({ @@ -70,7 +75,10 @@ export default class InteractionDirector { interaction.active = true; - interaction.onInteraction(action.bind(this)); + interaction.onInteraction(() => { + action(); + interaction.active = isActiveMultipleTimes; + }); } /** @@ -78,8 +86,13 @@ export default class InteractionDirector { * @param {InteractionDirectorParams} params - The parameters for creating the proximity-based interaction. * @param {() => void} action - The action to be executed when the interaction is triggered. */ - private async createInteractionAuto({ host }: InteractionDirectorParams, action: () => void) { - host.onSensorEnter(action); + private async createInteractionAuto({ host, isActiveMultipleTimes }: InteractionDirectorParams, action: () => void) { + function handleAction() { + action(); + host.collider.isSensor = isActiveMultipleTimes; + }; + + host.onSensorEnter(handleAction); } /** diff --git a/src/common/network/network.ts b/src/common/network/network.ts index 4002629..ab1db6f 100644 --- a/src/common/network/network.ts +++ b/src/common/network/network.ts @@ -1,5 +1,3 @@ -// src/common/network/network.ts - interface PostRequestOptions { url: string; options?: RequestInit; @@ -19,7 +17,11 @@ const network = { const id = setTimeout(() => controller.abort(), timeout); try { - const response = await fetch(url, { ...options, method: 'POST', signal: controller.signal }); + const response = await fetch(url, { + ...options, + method: 'POST', + signal: controller.signal, + }); if (!response.ok) { throw new Error(`Request failed with status ${response.status}`); } @@ -36,4 +38,4 @@ const network = { }, }; -export default network; \ No newline at end of file +export default network; diff --git a/src/common/state/appState.ts b/src/common/state/appState.ts new file mode 100644 index 0000000..1e42beb --- /dev/null +++ b/src/common/state/appState.ts @@ -0,0 +1,48 @@ +import * as React from 'react'; +import { UI } from '@oo/scripting'; + +import App from '../../components/App.tsx'; + +class Store> { + renderer = UI.createRenderer(); + + private data: Data; + private idCounter = 0; + private listeners: Record void> = {}; + + constructor(data: Data) { + this.data = data; + } + + subscribe(listener: () => void): () => void { + const id = `listener_${this.idCounter++}`; + this.listeners[id] = listener; + return () => { + delete this.listeners[id]; + }; + } + + getSnapshot(): Data { + return this.data; + } + + setState(newState: Partial) { + const hasChanged = Object.keys(newState).some( + key => this.data[key as keyof Data] !== newState[key as keyof Data], + ); + + if (hasChanged) { + this.data = { + ...this.data, + ...newState, + }; + Object.values(this.listeners).forEach(listener => listener()); + } + } +} + +export const store = new Store({}); + +store.subscribe(() => { + store.renderer.render(React.createElement(App)); +}); diff --git a/src/common/store/App.tsx b/src/common/store/App.tsx deleted file mode 100644 index 1d8c8f1..0000000 --- a/src/common/store/App.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import * as React from 'react'; - -import Styles from './Styles.tsx'; - -interface AppProps { - components: React.ReactNode[]; -} - -export default function App({ components }: AppProps) { - return ( -
- {components.map((Component, index) => ( - {Component} - ))} - -
- ); -} diff --git a/src/common/store/AppStore.ts b/src/common/store/AppStore.ts deleted file mode 100644 index 0c56ec7..0000000 --- a/src/common/store/AppStore.ts +++ /dev/null @@ -1,28 +0,0 @@ -export default class AppStore { - listeners: Record void> = {}; - data: Data; - id = 0; - - constructor(data: Data) { - this.data = data; - } - - subscribe(f: any) { - this.id++; - this.listeners[this.id] = f; - return () => delete this.listeners[this.id]; - } - - getSnapshot(): Data { - return this.data; - } - - setState(newState: Partial) { - this.data = { - ...this.data, - ...newState, - }; - - Object.values(this.listeners).forEach(x => x()); - } -} diff --git a/src/common/store/Styles.tsx b/src/common/store/Styles.tsx deleted file mode 100644 index 6266f9f..0000000 --- a/src/common/store/Styles.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import * as React from 'react'; - -export default function Styles() { - const css = ` - html, - body { - height: 100dvh; - } - - .app { - height: 500dvh; - } - `; - - return ; -} diff --git a/src/common/utils/fadeIn.ts b/src/common/utils/fadeIn.ts new file mode 100644 index 0000000..e568646 --- /dev/null +++ b/src/common/utils/fadeIn.ts @@ -0,0 +1,18 @@ +import anime from 'animejs'; +import { Component3D } from '@oo/scripting'; + +interface FadeInInputParams { + easing?: string; + opacity?: number; + duration?: number; + target: Component3D; +} + +export default ({ target, duration = 0, opacity = 1, easing = 'linear' }: FadeInInputParams) => { + anime({ + easing, + opacity, + targets: target, + duration: duration * 1000, + }); +}; \ No newline at end of file diff --git a/src/common/utils/fadeOut.ts b/src/common/utils/fadeOut.ts new file mode 100644 index 0000000..753f080 --- /dev/null +++ b/src/common/utils/fadeOut.ts @@ -0,0 +1,18 @@ +import anime from 'animejs'; +import { Component3D } from '@oo/scripting'; + +interface FadeOutInputParams { + easing?: string; + opacity?: number; + duration?: number; + target: Component3D; +} + +export default ({ target, duration = 0, opacity = 0, easing = 'linear' }: FadeOutInputParams) => { + anime({ + easing, + opacity, + targets: target, + duration: duration * 1000, + }); +}; diff --git a/src/common/utils/isValidUrl.ts b/src/common/utils/isValidUrl.ts index a7216b4..ffeb145 100644 --- a/src/common/utils/isValidUrl.ts +++ b/src/common/utils/isValidUrl.ts @@ -1,10 +1,8 @@ -export default function isValidUrl( - url: string, -): boolean { +export default function isValidUrl(url: string): boolean { try { const parsedUrl = new URL(url); return parsedUrl.protocol === 'https:'; } catch { return false; } -} \ No newline at end of file +} diff --git a/src/components/App.tsx b/src/components/App.tsx new file mode 100644 index 0000000..1c521f9 --- /dev/null +++ b/src/components/App.tsx @@ -0,0 +1,13 @@ +import * as React from 'react'; + +import globalStyles from './Styles.tsx'; +import ImplementationGuideOverlay from './overlays/ImplementationGuide.tsx'; + +export default () => { + return ( + <> + + + + ); +} diff --git a/src/components/Styles.tsx b/src/components/Styles.tsx new file mode 100644 index 0000000..468d6f2 --- /dev/null +++ b/src/components/Styles.tsx @@ -0,0 +1,10 @@ +export default ` + html, + body { + height: 100dvh; + } + + .app { + height: 500dvh; + } +`; diff --git a/src/components/discord-login-notifier/DiscordLoginNotifierComponent.ts b/src/components/discord-login-notifier/DiscordLoginNotifierComponent.ts deleted file mode 100644 index 3e44f9d..0000000 --- a/src/components/discord-login-notifier/DiscordLoginNotifierComponent.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { Param, ScriptComponent, World, Player } from '@oo/scripting'; - -import sendDiscordNotification, { DiscordNotificationParams } from '../../common/components/DiscordNotification.ts'; - -/** - * Main class to handle discord notifier component in the script. - */ -export default class DiscordLoginNotifierComponent extends ScriptComponent { - static config = { - title: 'Component - Discord Notifier', - description: - 'The DiscordNotifier component sends real-time messages to a Discord channel using a webhook, perfect for keeping your community informed during gameplay.', - tip: 'Use it to announce key in-game events, player achievements, or important updates directly to your Discord server.', - }; - - @Param({ - type: 'string', - name: 'Webhook URL', - }) - private webhookUrl = ''; - @Param({ type: 'string', name: 'Default message' }) - message = ''; - @Param({ - type: 'boolean', - defaultValue: false, - name: 'Does the message contains the name of the space?', - }) - private messageHasSpaceName = false; - @Param({ - type: 'boolean', - defaultValue: false, - name: 'Does the message contains the walletId of the user?', - }) - private messageContainsWalletId = false; - - /** - * Called when the script starts. - */ - async onStart() { - try { - const messageParts = [ - this.messageHasSpaceName ? World.name : '', - this.message, - this.messageContainsWalletId ? Player.userId : '', - ]; - const message = messageParts.filter(part => part).join(' '); - - const params: DiscordNotificationParams = { - webhookUrl: this.webhookUrl, - message, - }; - - await sendDiscordNotification(params); - } catch (error) { - console.error('Failed to send Discord notification:', error); - } - } -} diff --git a/src/components/discord-login-notifier/ImplementationGuideOverlay.tsx b/src/components/discord-login-notifier/ImplementationGuideOverlay.tsx deleted file mode 100644 index aee3fce..0000000 --- a/src/components/discord-login-notifier/ImplementationGuideOverlay.tsx +++ /dev/null @@ -1,275 +0,0 @@ -import * as React from 'react'; - -interface ImplementationGuideOverlayProps {} - -interface ImplementationGuideOverlayState { - isHelpVisible: boolean; -} - -export default class ImplementationGuideOverlay extends React.Component< - ImplementationGuideOverlayProps, - ImplementationGuideOverlayState -> { - constructor(props: ImplementationGuideOverlayProps) { - super(props); - this.state = { isHelpVisible: false }; - } - - handleClose = (event: React.MouseEvent) => { - event.preventDefault(); - this.setState({ isHelpVisible: false }); - }; - - handleOpen = (event: React.MouseEvent) => { - event.preventDefault(); - this.setState({ isHelpVisible: true }); - }; - - render() { - const styles = ` - body { - font-family: Arial, sans-serif; - background-color: #000; - color: #fff; - padding: 20px; - } - .folder-structure { - list-style-type: none; - padding-left: 20px; - } - .folder-structure li { - margin: 5px 0; - padding-left: 20px; - position: relative; - } - .folder-structure li::before { - content: ' '; - position: absolute; - top: 10px; - left: -20px; - width: 15px; - height: 2px; - background-color: #fff; - } - .folder-structure li::after { - content: ' '; - position: absolute; - top: 0; - left: -20px; - width: 2px; - height: 100%; - background-color: #fff; - } - .folder-structure li:last-child::after { - height: 10px; - } - .folder-icon, .file-icon { - margin-right: 5px; - } - .folder-icon::before { - content: '📁'; - } - .file-icon::before { - content: '📄'; - } - .image-button { - position: fixed; - top: 5%; - right: 3%; - border: none; - background: none; - padding: 0; - cursor: pointer; - } - .image-button img { - display: block; - width: 50px; - height: auto; - } - .wrapper { - position: fixed; - width: 100vw; - height: 100vh; - background-color: #000; - color: #fff; - display: flex; - flex-direction: column; - justify-content: flex-start; - align-items: center; - padding: 40px; - box-sizing: border-box; - overflow-y: auto; - } - .wrapper > button { - cursor: pointer; - color: #fff; - background: none; - border: 1px solid #fff; - padding: 10px; - margin-bottom: 20px; - font-size: 16px; - align-self: flex-end; - } - .section { - text-align: left; - margin-bottom: 20px; - width: 80%; - } - .section h2 { - margin-bottom: 10px; - font-size: 1.5em; - } - .section p { - margin-bottom: 10px; - font-size: 1em; - } - .m10 { - margin-left: 5px; - } - .sub-section { - margin: 15px 0; - } - .section ul { - list-style-type: none; - } - .section ul li { - margin-bottom: 10px; - } - `; - - return ( - <> - - {this.state.isHelpVisible && ( -
- -
-

Component - Discord Notifier

-

- The Discord Notifier component allows integration with Discord to send notifications - directly from the Oncyber platform to a specified Discord channel. This can be used - to announce in-game events, player achievements, or other important updates. -

-
-
-

Project Structure

-
    -
  • - components -
      -
    • - DiscordLoginNotifierComponent.ts: Handles - the component -
    • -
    -
  • -
  • - common -
      -
    • - DiscordNotification.ts: Handles the - Discord notifications -
    • -
    • - network.ts: Utility for handling network - requests -
    • -
    • - isValidUrl.ts: Validates the webhook URL -
    • -
    -
  • -
-
-
-

How to Use

-

To set up the Discord Notifier component, configure the following parameters:

-
-

1. Webhook URL (Required):

-

- - Specify the URL of the Discord webhook that will receive the notifications. -

-
-
-

2. Default Message (Optional):

-

- - Enter the default message to be sent to the Discord channel. -

-
-
-

3. Space Name (Optional):

-

- - Provide the name of the virtual space where the event occurs, if applicable. -

-
-
-

4. Does message contain walletId (Optional):

-

- - Set to true to include the wallet ID of the user in the message; otherwise, set - to false. -

-
-
-

5. Backend Setup (Required):

-

- - Since third-party requests (like sending notifications to Discord) are - restricted, this component relies on a backend worker. You need to configure this - worker to handle network requests. We have provided a backend solution on - - GitHub - - . Follow the instructions below to set this up. -

-
-
-

Backend Setup Instructions:

-

- 1. Create an account in Cloudflare if you - don't have one. -

-

2. Navigate to the "Workers" section in Cloudflare Dashboard.

-

- 3. Deploy the code from our{' '} - - GitHub repository - {' '} - to a new Cloudflare Worker. -

-

- 4. Set the required environment variables in Cloudflare for your webhook URL and - other configurations. -

-

- 5. Update your front-end component to use this backend endpoint for sending - Discord notifications. -

-
-

- * Ensure that the required parameters and backend are properly configured for the - component to function as expected. -

-
-
-

Changelog

-
    -
  • - Sept 3, 2024 - v1.0.0: Initial release of the component with discord login - notifier functionality. -
  • -
-
-
-

Made with ❤️ by the NumenGames team

-
-
- )} - - - ); - } -} diff --git a/src/components/discord-login-notifier/README.md b/src/components/discord-login-notifier/README.md deleted file mode 100644 index d81321c..0000000 --- a/src/components/discord-login-notifier/README.md +++ /dev/null @@ -1,104 +0,0 @@ -# Discord Notifier Component - -## What is the Discord Notifier Component? - -The **Discord Notifier** component is a specialized feature developed for the **Oncyber** platform. It allows the integration of Discord notifications directly within your virtual environment, enabling real-time communication of key events to a designated Discord channel. - -## Purpose - -The primary purpose is to send customized notifications to a Discord server using a webhook. This feature is particularly useful for announcing in-game events, player achievements, or important updates to keep your community engaged and informed during gameplay. - -## Implementation Methods - -There are two main methods to implement the **Discord Notifier** functionality in your project: - -### 1. Using the **all-in-one** Script - -The **All-in-One** script encapsulates all necessary functionalities within a single file. This method is ideal for quick integrations and smaller projects where simplicity and rapid setup are prioritized. - -### 2. Manual (Modular) Integration - -For larger projects or those requiring greater maintainability and scalability, the **Modular** approach is recommended. This method involves integrating multiple files, promoting code reusability and better organization. - -- **Components**: `DiscordLoginNotifierComponent.ts` -- **Common Utilities**: Located in the `common` directory, including `DiscordNotification.ts`, `network.ts`, and `isValidUrl.ts` - -## Configuration Parameters - -To properly set up the **Discord Notifier** component, certain parameters need to be configured. These parameters ensure that the component works correctly within your virtual environment. The following parameters apply to both implementation methods: - -### 1. **Webhook URL** (Required) - -This is the URL of the Discord webhook that will receive the notifications. It needs to be configured for the component to send messages. - -- **Description**: The URL of the Discord webhook that will receive the notifications. -- **How to configure**: Copy your Discord webhook URL into this field. - -### 2. **Default Message** (Optional) - -- **Description**: The message that will be sent to the Discord channel. -- **How to configure**: Enter the text message you want to send. - -### 3. **Space Name** (Optional) - -- **Description**: The name of the virtual space or environment where the event occurs. -- **How to configure**: Provide a name for your space or leave it blank if not needed. - -### 4. **Does message contains walletId** (Optional) - -- **Description**: When enabled, the message sent to Discord will include the wallet ID of the user triggering the notification. -- **How to configure**: Set to true to include the wallet ID in the message; otherwise, set to false. - -## Directory Structure - -Below is a representation of the relevant directory structure for the Discord Notifier component and its dependencies: - -```plaintext -├── components -│ └── discord-login-notifier -│ └── DiscordLoginNotifierComponent.ts # Handles the component -└── common - ├── components - │ └── DiscordNotification.ts # Handles the Discord notifications - ├── network - │ └── network.ts # Utility for handling network requests - └── utils - └── isValidUrl.ts # Validate that the webhook url is valid -``` - -This directory structure highlights the main Discord Notifier behavior script and its dependencies located in the common directory, which are essential for implementing the Discord Notifier functionality in a scalable way. - -## 6. Backend Setup (Required) - -Since third-party requests (like sending notifications to Discord) are restricted, this component relies on a backend worker. Follow these steps to set up the backend: - -1. **Create a Cloudflare Account**: - - - If you don't have one, sign up at [Cloudflare](https://cloudflare.com). - -2. **Navigate to Workers**: - - - In the Cloudflare Dashboard, go to the "Workers" section. - -3. **Deploy the Backend Worker**: - - - Clone the repository from [GitHub](https://github.com/numengames/numinia-oncyber/tree/main/backend/worker). - - Deploy the code to a new Cloudflare Worker following Cloudflare's deployment guidelines. - -4. **Configure Environment Variables**: - - - Set the required environment variables in Cloudflare for your webhook URL and other configurations. - - Example: - - `DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/...` - - Ensure sensitive information is stored securely and not exposed in the frontend. - -5. **Update Frontend Component**: - - - Modify your frontend component to use the deployed backend endpoint (`POST /api/notifications/discord`) for sending Discord notifications. - -6. **Test the Integration**: - - Verify that notifications are successfully sent to your Discord channel by triggering events in your virtual environment. - -## Changelog - -- **September 20, 2024 - v1.0.0**: Initial release of the component with Discord notifier functionality. diff --git a/src/components/discord-login-notifier/all-in-one.ts b/src/components/discord-login-notifier/all-in-one.ts deleted file mode 100644 index 5971afc..0000000 --- a/src/components/discord-login-notifier/all-in-one.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { Param, Player, ScriptComponent } from '@oo/scripting'; - -const SERVER_URL = 'https://jolly-poetry-c57a.sy54bjwfmg.workers.dev/api/notifications/discord'; - -/** - * Main class to handle redirect behavior in the script. - */ -export default class DiscordLoginNotifierComponent extends ScriptComponent { - static config = { - title: 'Component - Discord Notifier', - description: - 'The DiscordNotifier component sends real-time messages to a Discord channel using a webhook, perfect for keeping your community informed during gameplay.', - tip: 'Use it to announce key in-game events, player achievements, or important updates directly to your Discord server.', - }; - - @Param({ - type: 'string', - name: 'Webhook URL', - }) - private webhookUrl = ''; - @Param({ type: 'string', name: 'Default message' }) - message = ''; - @Param({ - type: 'string', - name: 'The name of the space', - }) - private spaceName = false; - @Param({ - type: 'boolean', - defaultValue: false, - name: 'Does the message contains the walletId of the user?', - }) - private messageContainsWalletId = false; - - /** - * Called when the script starts. - */ - async onStart() { - const message = `${this.spaceName || ''} ${this.message} ${this.messageContainsWalletId ? Player.userId : null}`; - - await this.sendDiscordNotification(message); - } - - private async sendDiscordNotification(message: string): Promise { - if (!this.webhookUrl || !this.isValidUrl(this.webhookUrl)) { - console.error('No webhook URL provided!'); - return; - } - - try { - const response = await fetch(SERVER_URL, { - method: 'POST', - ...{ body: JSON.stringify({ webhookUrl: this.webhookUrl, message }) }, - }); - if (!response.ok) { - throw new Error(`Request failed with status ${response.status}`); - } - - return response; - } catch (error) { - console.error('Network error:', error); - throw error; - } - } - - private isValidUrl(url: string): boolean { - try { - new URL(url); - return true; - } catch { - return false; - } - } -} diff --git a/src/components/discord-login-notifier/main.ts b/src/components/discord-login-notifier/main.ts deleted file mode 100644 index 1fd1c3d..0000000 --- a/src/components/discord-login-notifier/main.ts +++ /dev/null @@ -1,64 +0,0 @@ -import * as React from 'react'; -import { UI, World } from '@oo/scripting'; - -import ImplementationGuideOverlay from './ImplementationGuideOverlay.tsx'; - -export default class Game { - private renderer; - - constructor() { - this.renderer = UI.createRenderer(); - } - - async onPreload() { - // invoked once as soon as the game starts loading - // Use this load resources not already on the scene - console.log('Game: preload'); - } - - onReady = async () => { - // invoked once when the game has finished loading - // Use this for one time initialization logic - console.log('Game: ready'); - }; - - onStart = async () => { - // invoked each time user starts or replays the game (everytime World.start() is called, we call it by default in Display script) - // Use this for each play/replay game logic - console.log('Game: start'); - - try { - World.name = 'Numinian tools - Discord Login Notifier'; - - this.renderer.render(React.createElement(ImplementationGuideOverlay)); - } catch (error) { - console.error('Error during onStart:', error); - } - }; - - onUpdate = () => { - // this will be invoked on each frame (assuming the game is not paused) - // Use this to update the game state, 3D position ... - }; - - onPause = async () => { - // invoked when the game has been paused from the frontend (pause button, login ...) - console.log('Game: pause'); - }; - - onResume = async () => { - // invoked when the game resumes after pause from the frontend (eg login finished) - console.log('Game: resume'); - }; - - onEnd = async () => { - // invoked each time user ends the game (everytime World.stop() is called.) - // Use this for each end game logic - console.log('Game: end'); - }; - - onDispose = async () => { - // invoked when it's time to dispose resources (e.g. refreshing the page/switching pages) - console.log('Game: dispose'); - }; -} diff --git a/src/components/overlays/ImplementationGuide.tsx b/src/components/overlays/ImplementationGuide.tsx new file mode 100644 index 0000000..fe29651 --- /dev/null +++ b/src/components/overlays/ImplementationGuide.tsx @@ -0,0 +1,207 @@ +import * as React from 'react'; +import { useState } from 'react'; + +interface HelpContentProps { + onClose: () => void; +} + +const HelpContent = ({ onClose }: HelpContentProps): JSX.Element => ( +
+ +
+

Behavioral Component - Teleport

+

+ The Teleport behavioral component allows users or entities within a virtual + environment to instantly move from one location to another, either by pressing a key + or automatically interacting with an element. +

+
+
+

Project Structure

+
    +
  • + Local Scripts +
      +
    • TeleportBehavior.ts: Handles the teleportation behavior
    • +
    • TeleportAction.ts: Executes the teleportation logic
    • +
    • FadeIn.ts: Optional fade-in effect during teleportation
    • +
    • FadeOut.ts: Optional fade-out effect during teleportation
    • +
    • InteractionDirector.ts: Manages interactions (Key or Auto)
    • +
    +
  • +
+
+
+

How to Use

+

To set up the Teleport component, configure the following parameters:

+
+

1. Component Target (Required):

+

- Specify the component that the user will be teleported to.

+
+
+

2. Interaction Mode (Required):

+

- Choose how the teleportation is triggered: either by pressing a key ("Key") or automatically when in proximity ("Auto").

+
+
+

3. Trigger Key (Depends on Interaction Mode):

+

- If "Key" is selected, specify the key that will trigger the teleportation.

+
+
+

4. Trigger Distance (Depends on Interaction Mode):

+

- Adjust the distance within which the user can activate the teleportation.

+
+
+

5. Enable FadeIn - FadeOut (Optional):

+

- Enable and set the duration for fade-in and fade-out effects during teleportation.

+
+
+

6. Teleport Delay (Optional):

+

- Set the delay in seconds before the teleportation occurs after interaction.

+
+
+
+

Changelog

+
    +
  • Sep 02, 2024 - v2.0.1: Fixed an issue where the delay could be undefined in certain scenarios.
  • +
  • Aug 25, 2024 - v2.0.0: Restructured the component to support all-in-one or modular usages.
  • +
  • Aug 5, 2024 - v1.0.0: Initial release of the component with teleportation functionality.
  • +
+
+
+

Made with ❤️ by the NumenGames team

+
+
+); + +const ImplementationGuideOverlay = (): JSX.Element => { + const [isHelpVisible, setIsHelpVisible] = useState(false); + + const handleOpen = () => setIsHelpVisible(true); + const handleClose = () => setIsHelpVisible(false); + + const styles = ` + body { + font-family: Arial, sans-serif; + background-color: #000; + color: #fff; + padding: 20px; + } + .folder-structure { + list-style-type: none; + padding-left: 20px; + } + .folder-structure li { + margin: 5px 0; + padding-left: 20px; + position: relative; + } + .folder-structure li::before { + content: ' '; + position: absolute; + top: 10px; + left: -20px; + width: 15px; + height: 2px; + background-color: #fff; + } + .folder-structure li::after { + content: ' '; + position: absolute; + top: 0; + left: -20px; + width: 2px; + height: 100%; + background-color: #fff; + } + .folder-structure li:last-child::after { + height: 10px; + } + .folder-icon, .file-icon { + margin-right: 5px; + } + .folder-icon::before { + content: '📁'; + } + .file-icon::before { + content: '📄'; + } + .image-button { + position: fixed; + top: 5%; + right: 3%; + border: none; + background: none; + padding: 0; + cursor: pointer; + } + .image-button img { + display: block; + width: 50px; + height: auto; + } + .wrapper { + position: fixed; + width: 100vw; + height: 100vh; + background-color: #000; + color: #fff; + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: center; + padding: 40px; + box-sizing: border-box; + overflow-y: auto; + } + .wrapper > button { + cursor: pointer; + color: #fff; + background: none; + border: 1px solid #fff; + padding: 10px; + margin-bottom: 20px; + font-size: 16px; + align-self: flex-end; + } + .section { + text-align: left; + margin-bottom: 20px; + width: 80%; + } + .section h2 { + margin-bottom: 10px; + font-size: 1.5em; + } + .section p { + margin-bottom: 10px; + font-size: 1em; + } + .m10 { + margin-left: 5px; + } + .sub-section { + margin: 15px 0; + } + .section ul { + list-style-type: none; + } + .section ul li { + margin-bottom: 10px; + } + `; + + return ( + <> + + {isHelpVisible && } + + + ); +}; + +export default ImplementationGuideOverlay; \ No newline at end of file diff --git a/src/behaviors/redirect/main.ts b/src/core/main.ts similarity index 82% rename from src/behaviors/redirect/main.ts rename to src/core/main.ts index b41adc2..5677e10 100644 --- a/src/behaviors/redirect/main.ts +++ b/src/core/main.ts @@ -1,15 +1,8 @@ -import * as React from 'react'; -import { UI } from '@oo/scripting'; +import { World } from '@oo/scripting'; -import ImplementationGuideOverlay from './ImplementationGuideOverlay.tsx'; +import { store } from '../common/state/appState'; export default class Game { - private renderer; - - constructor() { - this.renderer = UI.createRenderer(); - } - async onPreload() { // invoked once as soon as the game starts loading // Use this load resources not already on the scene @@ -25,9 +18,10 @@ export default class Game { onStart = async () => { // invoked each time user starts or replays the game (everytime World.start() is called, we call it by default in Display script) // Use this for each play/replay game logic + World.name = 'Numinian tools - Insert Password'; console.log('Game: start'); - this.renderer.render(React.createElement(ImplementationGuideOverlay)); + store.setState({}); }; onUpdate = () => { diff --git a/src/oo-oncyber.d.ts b/src/oo-oncyber.d.ts index 11a537b..c9a6d7c 100644 --- a/src/oo-oncyber.d.ts +++ b/src/oo-oncyber.d.ts @@ -13,6 +13,7 @@ declare module '@oo/scripting' { quaternion: any; getDimensions: () => any; }; + export type AudioComponent = {}; export const UI: UI; export const Param: any; @@ -26,6 +27,7 @@ declare module '@oo/scripting' { export const Physics: any; export const Display: any; export const Emitter: any; + export const Receiver: any; export const Controls: any; export const Component: any; export const PlayerData: any; diff --git a/test/DiscordNotification.test.ts b/test/DiscordNotification.test.ts deleted file mode 100644 index 09ee152..0000000 --- a/test/DiscordNotification.test.ts +++ /dev/null @@ -1,76 +0,0 @@ -import fetchMock from 'fetch-mock'; - -import sendDiscordNotification, { - DiscordNotificationParams, -} from '../src/common/components/DiscordNotification'; -import isValidUrl from '../src/common/utils/isValidUrl'; - -describe('sendDiscordNotification', () => { - const SERVER_URL = 'https://jolly-poetry-c57a.sy54bjwfmg.workers.dev/api/notifications/discord'; - - beforeAll(() => { - fetchMock.config.fallbackToNetwork = false; - }); - - afterAll(() => fetchMock.restore()); - - afterEach(() => fetchMock.reset()); - - it('should send a notification successfully', async () => { - const params: DiscordNotificationParams = { - message: 'Test message', - webhookUrl: 'https://discord.com/api/webhooks/test', - }; - - expect(isValidUrl(params.webhookUrl)).toBe(true); - - fetchMock.post(SERVER_URL, { - status: 201, - }); - - await sendDiscordNotification(params); - - expect(fetchMock.called(SERVER_URL)).toBe(true); - - const lastCall = fetchMock.lastCall(SERVER_URL); - expect(lastCall).toBeTruthy(); - expect(lastCall![1]!.method).toBe('POST'); - expect(fetchMock.lastOptions(SERVER_URL)!.body).toEqual( - JSON.stringify({ webhookUrl: params.webhookUrl, message: params.message }) - ); - }); - - it('should log an error if webhook URL is invalid', async () => { - const params: DiscordNotificationParams = { - webhookUrl: 'invalid-url', - message: 'Test message', - }; - - const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => { }); - - await sendDiscordNotification(params); - - expect(consoleErrorSpy).toHaveBeenCalledWith('No valid webhook URL provided!'); - - expect(fetchMock.called()).toBe(false); - - consoleErrorSpy.mockRestore(); - }); - - it('should handle network errors gracefully', async () => { - const params: DiscordNotificationParams = { - webhookUrl: 'https://discord.com/api/webhooks/test', - message: 'Test message', - }; - - fetchMock.post(SERVER_URL, { throws: new Error('Network failure') }); - - const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => { }); - - await sendDiscordNotification(params); - - expect(consoleErrorSpy).toHaveBeenCalledWith('Failed to send Discord notification:', expect.any(Error)); - - consoleErrorSpy.mockRestore(); - }); -}); \ No newline at end of file