feat: Update Chat Panel (#9)
* chore: Upgrade the @dcl/eslint-config package * fix: eslint * feat: Add new SendIcon * fix: Show a max of 12 participants * feat: Update layout style * feat: Update Chat Panel styles * refactor: Move useChat definition to hooks and utils * feat: Update ChatEntry component * feat: Add translations
This commit is contained in:
parent
61aaaeeebc
commit
8e62a25833
186
package-lock.json
generated
186
package-lock.json
generated
@ -35,7 +35,7 @@
|
||||
"rxjs": "^7.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@dcl/eslint-config": "1.1.7",
|
||||
"@dcl/eslint-config": "^1.1.10",
|
||||
"@esbuild-plugins/node-globals-polyfill": "^0.2.3",
|
||||
"@esbuild-plugins/node-modules-polyfill": "^0.2.2",
|
||||
"@swc/core": "^1.3.69",
|
||||
@ -1327,14 +1327,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@dcl/eslint-config": {
|
||||
"version": "1.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@dcl/eslint-config/-/eslint-config-1.1.7.tgz",
|
||||
"integrity": "sha512-MwAL4hFxwXZ5VeJTPFDnzjX6Qe/PKOaxJrkOQN8a0jkwDjYw9rTMcj+FIAoLu+pBzUg6B+XyFm0OqpAvIxHjeQ==",
|
||||
"version": "1.1.10",
|
||||
"resolved": "https://registry.npmjs.org/@dcl/eslint-config/-/eslint-config-1.1.10.tgz",
|
||||
"integrity": "sha512-6cIgvYKExnhn6DD6C/p3eQo+jlqFH5cMIbI8leaxAL6mym6jlLzKEqvksHW0S120Zgh5QqQ2B3MZGUgj8CBStQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^5.60.1",
|
||||
"@typescript-eslint/parser": "^5.60.1",
|
||||
"eslint": "^8.43.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.62.0",
|
||||
"@typescript-eslint/parser": "^5.62.0",
|
||||
"eslint": "^8.44.0",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-import-resolver-babel-module": "^5.3.2",
|
||||
"eslint-import-resolver-typescript": "^3.5.5",
|
||||
@ -5096,15 +5096,15 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "5.61.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.61.0.tgz",
|
||||
"integrity": "sha512-A5l/eUAug103qtkwccSCxn8ZRwT+7RXWkFECdA4Cvl1dOlDUgTpAOfSEElZn2uSUxhdDpnCdetrf0jvU4qrL+g==",
|
||||
"version": "5.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz",
|
||||
"integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/regexpp": "^4.4.0",
|
||||
"@typescript-eslint/scope-manager": "5.61.0",
|
||||
"@typescript-eslint/type-utils": "5.61.0",
|
||||
"@typescript-eslint/utils": "5.61.0",
|
||||
"@typescript-eslint/scope-manager": "5.62.0",
|
||||
"@typescript-eslint/type-utils": "5.62.0",
|
||||
"@typescript-eslint/utils": "5.62.0",
|
||||
"debug": "^4.3.4",
|
||||
"graphemer": "^1.4.0",
|
||||
"ignore": "^5.2.0",
|
||||
@ -5130,14 +5130,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser": {
|
||||
"version": "5.61.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.61.0.tgz",
|
||||
"integrity": "sha512-yGr4Sgyh8uO6fSi9hw3jAFXNBHbCtKKFMdX2IkT3ZqpKmtAq3lHS4ixB/COFuAIJpwl9/AqF7j72ZDWYKmIfvg==",
|
||||
"version": "5.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz",
|
||||
"integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "5.61.0",
|
||||
"@typescript-eslint/types": "5.61.0",
|
||||
"@typescript-eslint/typescript-estree": "5.61.0",
|
||||
"@typescript-eslint/scope-manager": "5.62.0",
|
||||
"@typescript-eslint/types": "5.62.0",
|
||||
"@typescript-eslint/typescript-estree": "5.62.0",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
@ -5157,13 +5157,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "5.61.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.61.0.tgz",
|
||||
"integrity": "sha512-W8VoMjoSg7f7nqAROEmTt6LoBpn81AegP7uKhhW5KzYlehs8VV0ZW0fIDVbcZRcaP3aPSW+JZFua+ysQN+m/Nw==",
|
||||
"version": "5.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz",
|
||||
"integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "5.61.0",
|
||||
"@typescript-eslint/visitor-keys": "5.61.0"
|
||||
"@typescript-eslint/types": "5.62.0",
|
||||
"@typescript-eslint/visitor-keys": "5.62.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||
@ -5174,13 +5174,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "5.61.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.61.0.tgz",
|
||||
"integrity": "sha512-kk8u//r+oVK2Aj3ph/26XdH0pbAkC2RiSjUYhKD+PExemG4XSjpGFeyZ/QM8lBOa7O8aGOU+/yEbMJgQv/DnCg==",
|
||||
"version": "5.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz",
|
||||
"integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/typescript-estree": "5.61.0",
|
||||
"@typescript-eslint/utils": "5.61.0",
|
||||
"@typescript-eslint/typescript-estree": "5.62.0",
|
||||
"@typescript-eslint/utils": "5.62.0",
|
||||
"debug": "^4.3.4",
|
||||
"tsutils": "^3.21.0"
|
||||
},
|
||||
@ -5201,9 +5201,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "5.61.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.61.0.tgz",
|
||||
"integrity": "sha512-ldyueo58KjngXpzloHUog/h9REmHl59G1b3a5Sng1GfBo14BkS3ZbMEb3693gnP1k//97lh7bKsp6/V/0v1veQ==",
|
||||
"version": "5.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz",
|
||||
"integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||
@ -5214,13 +5214,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "5.61.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.61.0.tgz",
|
||||
"integrity": "sha512-Fud90PxONnnLZ36oR5ClJBLTLfU4pIWBmnvGwTbEa2cXIqj70AEDEmOmpkFComjBZ/037ueKrOdHuYmSFVD7Rw==",
|
||||
"version": "5.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz",
|
||||
"integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "5.61.0",
|
||||
"@typescript-eslint/visitor-keys": "5.61.0",
|
||||
"@typescript-eslint/types": "5.62.0",
|
||||
"@typescript-eslint/visitor-keys": "5.62.0",
|
||||
"debug": "^4.3.4",
|
||||
"globby": "^11.1.0",
|
||||
"is-glob": "^4.0.3",
|
||||
@ -5241,17 +5241,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils": {
|
||||
"version": "5.61.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.61.0.tgz",
|
||||
"integrity": "sha512-mV6O+6VgQmVE6+xzlA91xifndPW9ElFW8vbSF0xCT/czPXVhwDewKila1jOyRwa9AE19zKnrr7Cg5S3pJVrTWQ==",
|
||||
"version": "5.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz",
|
||||
"integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.2.0",
|
||||
"@types/json-schema": "^7.0.9",
|
||||
"@types/semver": "^7.3.12",
|
||||
"@typescript-eslint/scope-manager": "5.61.0",
|
||||
"@typescript-eslint/types": "5.61.0",
|
||||
"@typescript-eslint/typescript-estree": "5.61.0",
|
||||
"@typescript-eslint/scope-manager": "5.62.0",
|
||||
"@typescript-eslint/types": "5.62.0",
|
||||
"@typescript-eslint/typescript-estree": "5.62.0",
|
||||
"eslint-scope": "^5.1.1",
|
||||
"semver": "^7.3.7"
|
||||
},
|
||||
@ -5267,12 +5267,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "5.61.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.61.0.tgz",
|
||||
"integrity": "sha512-50XQ5VdbWrX06mQXhy93WywSFZZGsv3EOjq+lqp6WC2t+j3mb6A9xYVdrRxafvK88vg9k9u+CT4l6D8PEatjKg==",
|
||||
"version": "5.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz",
|
||||
"integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "5.61.0",
|
||||
"@typescript-eslint/types": "5.62.0",
|
||||
"eslint-visitor-keys": "^3.3.0"
|
||||
},
|
||||
"engines": {
|
||||
@ -20784,14 +20784,14 @@
|
||||
}
|
||||
},
|
||||
"@dcl/eslint-config": {
|
||||
"version": "1.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@dcl/eslint-config/-/eslint-config-1.1.7.tgz",
|
||||
"integrity": "sha512-MwAL4hFxwXZ5VeJTPFDnzjX6Qe/PKOaxJrkOQN8a0jkwDjYw9rTMcj+FIAoLu+pBzUg6B+XyFm0OqpAvIxHjeQ==",
|
||||
"version": "1.1.10",
|
||||
"resolved": "https://registry.npmjs.org/@dcl/eslint-config/-/eslint-config-1.1.10.tgz",
|
||||
"integrity": "sha512-6cIgvYKExnhn6DD6C/p3eQo+jlqFH5cMIbI8leaxAL6mym6jlLzKEqvksHW0S120Zgh5QqQ2B3MZGUgj8CBStQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@typescript-eslint/eslint-plugin": "^5.60.1",
|
||||
"@typescript-eslint/parser": "^5.60.1",
|
||||
"eslint": "^8.43.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.62.0",
|
||||
"@typescript-eslint/parser": "^5.62.0",
|
||||
"eslint": "^8.44.0",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-import-resolver-babel-module": "^5.3.2",
|
||||
"eslint-import-resolver-typescript": "^3.5.5",
|
||||
@ -23593,15 +23593,15 @@
|
||||
"dev": true
|
||||
},
|
||||
"@typescript-eslint/eslint-plugin": {
|
||||
"version": "5.61.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.61.0.tgz",
|
||||
"integrity": "sha512-A5l/eUAug103qtkwccSCxn8ZRwT+7RXWkFECdA4Cvl1dOlDUgTpAOfSEElZn2uSUxhdDpnCdetrf0jvU4qrL+g==",
|
||||
"version": "5.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz",
|
||||
"integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@eslint-community/regexpp": "^4.4.0",
|
||||
"@typescript-eslint/scope-manager": "5.61.0",
|
||||
"@typescript-eslint/type-utils": "5.61.0",
|
||||
"@typescript-eslint/utils": "5.61.0",
|
||||
"@typescript-eslint/scope-manager": "5.62.0",
|
||||
"@typescript-eslint/type-utils": "5.62.0",
|
||||
"@typescript-eslint/utils": "5.62.0",
|
||||
"debug": "^4.3.4",
|
||||
"graphemer": "^1.4.0",
|
||||
"ignore": "^5.2.0",
|
||||
@ -23611,53 +23611,53 @@
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/parser": {
|
||||
"version": "5.61.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.61.0.tgz",
|
||||
"integrity": "sha512-yGr4Sgyh8uO6fSi9hw3jAFXNBHbCtKKFMdX2IkT3ZqpKmtAq3lHS4ixB/COFuAIJpwl9/AqF7j72ZDWYKmIfvg==",
|
||||
"version": "5.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz",
|
||||
"integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@typescript-eslint/scope-manager": "5.61.0",
|
||||
"@typescript-eslint/types": "5.61.0",
|
||||
"@typescript-eslint/typescript-estree": "5.61.0",
|
||||
"@typescript-eslint/scope-manager": "5.62.0",
|
||||
"@typescript-eslint/types": "5.62.0",
|
||||
"@typescript-eslint/typescript-estree": "5.62.0",
|
||||
"debug": "^4.3.4"
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/scope-manager": {
|
||||
"version": "5.61.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.61.0.tgz",
|
||||
"integrity": "sha512-W8VoMjoSg7f7nqAROEmTt6LoBpn81AegP7uKhhW5KzYlehs8VV0ZW0fIDVbcZRcaP3aPSW+JZFua+ysQN+m/Nw==",
|
||||
"version": "5.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz",
|
||||
"integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@typescript-eslint/types": "5.61.0",
|
||||
"@typescript-eslint/visitor-keys": "5.61.0"
|
||||
"@typescript-eslint/types": "5.62.0",
|
||||
"@typescript-eslint/visitor-keys": "5.62.0"
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/type-utils": {
|
||||
"version": "5.61.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.61.0.tgz",
|
||||
"integrity": "sha512-kk8u//r+oVK2Aj3ph/26XdH0pbAkC2RiSjUYhKD+PExemG4XSjpGFeyZ/QM8lBOa7O8aGOU+/yEbMJgQv/DnCg==",
|
||||
"version": "5.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz",
|
||||
"integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@typescript-eslint/typescript-estree": "5.61.0",
|
||||
"@typescript-eslint/utils": "5.61.0",
|
||||
"@typescript-eslint/typescript-estree": "5.62.0",
|
||||
"@typescript-eslint/utils": "5.62.0",
|
||||
"debug": "^4.3.4",
|
||||
"tsutils": "^3.21.0"
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/types": {
|
||||
"version": "5.61.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.61.0.tgz",
|
||||
"integrity": "sha512-ldyueo58KjngXpzloHUog/h9REmHl59G1b3a5Sng1GfBo14BkS3ZbMEb3693gnP1k//97lh7bKsp6/V/0v1veQ==",
|
||||
"version": "5.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz",
|
||||
"integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@typescript-eslint/typescript-estree": {
|
||||
"version": "5.61.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.61.0.tgz",
|
||||
"integrity": "sha512-Fud90PxONnnLZ36oR5ClJBLTLfU4pIWBmnvGwTbEa2cXIqj70AEDEmOmpkFComjBZ/037ueKrOdHuYmSFVD7Rw==",
|
||||
"version": "5.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz",
|
||||
"integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@typescript-eslint/types": "5.61.0",
|
||||
"@typescript-eslint/visitor-keys": "5.61.0",
|
||||
"@typescript-eslint/types": "5.62.0",
|
||||
"@typescript-eslint/visitor-keys": "5.62.0",
|
||||
"debug": "^4.3.4",
|
||||
"globby": "^11.1.0",
|
||||
"is-glob": "^4.0.3",
|
||||
@ -23666,28 +23666,28 @@
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/utils": {
|
||||
"version": "5.61.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.61.0.tgz",
|
||||
"integrity": "sha512-mV6O+6VgQmVE6+xzlA91xifndPW9ElFW8vbSF0xCT/czPXVhwDewKila1jOyRwa9AE19zKnrr7Cg5S3pJVrTWQ==",
|
||||
"version": "5.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz",
|
||||
"integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@eslint-community/eslint-utils": "^4.2.0",
|
||||
"@types/json-schema": "^7.0.9",
|
||||
"@types/semver": "^7.3.12",
|
||||
"@typescript-eslint/scope-manager": "5.61.0",
|
||||
"@typescript-eslint/types": "5.61.0",
|
||||
"@typescript-eslint/typescript-estree": "5.61.0",
|
||||
"@typescript-eslint/scope-manager": "5.62.0",
|
||||
"@typescript-eslint/types": "5.62.0",
|
||||
"@typescript-eslint/typescript-estree": "5.62.0",
|
||||
"eslint-scope": "^5.1.1",
|
||||
"semver": "^7.3.7"
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/visitor-keys": {
|
||||
"version": "5.61.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.61.0.tgz",
|
||||
"integrity": "sha512-50XQ5VdbWrX06mQXhy93WywSFZZGsv3EOjq+lqp6WC2t+j3mb6A9xYVdrRxafvK88vg9k9u+CT4l6D8PEatjKg==",
|
||||
"version": "5.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz",
|
||||
"integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@typescript-eslint/types": "5.61.0",
|
||||
"@typescript-eslint/types": "5.62.0",
|
||||
"eslint-visitor-keys": "^3.3.0"
|
||||
}
|
||||
},
|
||||
|
||||
@ -44,7 +44,7 @@
|
||||
"rxjs": "^7.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@dcl/eslint-config": "1.1.7",
|
||||
"@dcl/eslint-config": "^1.1.10",
|
||||
"@esbuild-plugins/node-globals-polyfill": "^0.2.3",
|
||||
"@esbuild-plugins/node-modules-polyfill": "^0.2.2",
|
||||
"@swc/core": "^1.3.69",
|
||||
|
||||
23
src/assets/icons/SendIcon.tsx
Normal file
23
src/assets/icons/SendIcon.tsx
Normal file
@ -0,0 +1,23 @@
|
||||
import * as React from 'react'
|
||||
import type { SVGProps } from 'react'
|
||||
|
||||
const SendIcon = (props: SVGProps<SVGSVGElement>) => (
|
||||
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<path
|
||||
d="M25.7422 5.75146L14.0085 24.4085L12.2228 15.2179L4.19727 10.3964L25.7422 5.75146Z"
|
||||
stroke={props.stroke ?? 'white'}
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M12.167 15.2561L25.7411 5.75146"
|
||||
stroke={props.stroke ?? 'white'}
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export default SendIcon
|
||||
@ -1,5 +1,7 @@
|
||||
.ControlBarContainer {
|
||||
position: relative;
|
||||
height: var(--lk-control-bar-height);
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.ControlBarRightButtonGroup {
|
||||
@ -10,3 +12,7 @@
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.chatToggleButton {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
@ -176,7 +176,7 @@ export function ControlBar({ variation, controls, ...props }: ControlBarProps) {
|
||||
<StartAudio label="Start Audio" />
|
||||
<div className={styles.ControlBarRightButtonGroup}>
|
||||
{visibleControls.chat && (
|
||||
<ChatToggle>
|
||||
<ChatToggle className={styles.chatToggleButton}>
|
||||
{showIcon && <ChatIcon />}
|
||||
{showText && 'Chat'}
|
||||
</ChatToggle>
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import * as React from 'react'
|
||||
import { isParticipantSourcePinned } from '@livekit/components-core'
|
||||
import type { TrackReferenceOrPlaceholder } from '@livekit/components-core'
|
||||
import {
|
||||
AudioTrack,
|
||||
ConnectionQualityIndicator,
|
||||
@ -15,10 +16,9 @@ import {
|
||||
useParticipantTile
|
||||
} from '@livekit/components-react'
|
||||
import { Track } from 'livekit-client'
|
||||
import type { Participant } from 'livekit-client'
|
||||
import Profile from 'decentraland-dapps/dist/containers/Profile'
|
||||
import type { Props } from './ParticipantTile.types'
|
||||
import type { TrackReferenceOrPlaceholder } from '@livekit/components-core'
|
||||
import type { Participant } from 'livekit-client'
|
||||
|
||||
/** @public */
|
||||
export function ParticipantContextIfNeeded(
|
||||
|
||||
@ -1,6 +1,57 @@
|
||||
.container {
|
||||
display: none;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.open {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.headerContainer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.close:global(.ui.button) {
|
||||
padding: 0;
|
||||
min-width: 24px;
|
||||
width: 24px;
|
||||
background: url('../../../../assets/icons/Close.svg');
|
||||
color: var(--toast-text);
|
||||
}
|
||||
|
||||
.close:global(.ui.button):hover {
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.title:global(.ui.header) {
|
||||
font-size: 24px;
|
||||
margin-bottom: 0px;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
line-height: 24px;
|
||||
color: var(--toast-text);
|
||||
}
|
||||
|
||||
.chatMessages {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.form {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.input {
|
||||
width: 100%;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.button {
|
||||
height: 42px;
|
||||
}
|
||||
|
||||
@ -1,32 +1,17 @@
|
||||
import * as React from 'react'
|
||||
import type { ChatMessage, ReceivedChatMessage } from '@livekit/components-core'
|
||||
import { MessageFormatter, useLocalParticipant, useMaybeLayoutContext, useRoomContext } from '@livekit/components-react'
|
||||
import React, { useCallback, useEffect, useRef } from 'react'
|
||||
import type { ChatMessage } from '@livekit/components-core'
|
||||
import { useLocalParticipant } from '@livekit/components-react'
|
||||
import classNames from 'classnames'
|
||||
import { t } from 'decentraland-dapps/dist/modules/translation/utils'
|
||||
import { Button, Header, Input } from 'decentraland-ui'
|
||||
import SendIcon from '../../../../assets/icons/SendIcon'
|
||||
import { useChat } from '../../../../hooks/useChat'
|
||||
import { useLayoutContext } from '../../../../hooks/useLayoutContext'
|
||||
import { cloneSingleChild } from '../../../../utils/chat'
|
||||
import ChatEntry from './ChatEntry'
|
||||
import { cloneSingleChild, setupChat, useObservableState } from './utils'
|
||||
import { Props } from './Chat.types'
|
||||
import styles from './Chat.module.css'
|
||||
|
||||
export type { ChatMessage, ReceivedChatMessage }
|
||||
|
||||
export interface ChatProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
messageFormatter?: MessageFormatter
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export function useChat() {
|
||||
const room = useRoomContext()
|
||||
const [setup, setSetup] = React.useState<ReturnType<typeof setupChat>>()
|
||||
const isSending = useObservableState(setup?.isSendingObservable, false)
|
||||
const chatMessages = useObservableState(setup?.messageObservable, [])
|
||||
|
||||
React.useEffect(() => {
|
||||
const setupChatReturn = setupChat(room)
|
||||
setSetup(setupChatReturn)
|
||||
return setupChatReturn.destroy
|
||||
}, [room])
|
||||
|
||||
return { send: setup?.send, chatMessages, isSending }
|
||||
}
|
||||
|
||||
/**
|
||||
* The Chat component adds a basis chat functionality to the LiveKit room. The messages are distributed to all participants
|
||||
* in the room. Only users who are in the room at the time of dispatch will receive the message.
|
||||
@ -39,14 +24,13 @@ export function useChat() {
|
||||
* ```
|
||||
* @public
|
||||
*/
|
||||
export default function Chat({ messageFormatter, ...props }: ChatProps) {
|
||||
const inputRef = React.useRef<HTMLInputElement>(null)
|
||||
const ulRef = React.useRef<HTMLUListElement>(null)
|
||||
|
||||
export default function Chat({ messageFormatter, isOpen, ...props }: Props) {
|
||||
const { send, chatMessages, isSending } = useChat()
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
const ulRef = useRef<HTMLUListElement>(null)
|
||||
|
||||
const layoutContext = useMaybeLayoutContext()
|
||||
const lastReadMsgAt = React.useRef<ChatMessage['timestamp']>(0)
|
||||
const layoutContext = useLayoutContext()
|
||||
const lastReadMsgAt = useRef<ChatMessage['timestamp']>(0)
|
||||
|
||||
async function handleSubmit(event: React.FormEvent) {
|
||||
event.preventDefault()
|
||||
@ -59,13 +43,13 @@ export default function Chat({ messageFormatter, ...props }: ChatProps) {
|
||||
}
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
if (ulRef) {
|
||||
ulRef.current?.scrollTo({ top: ulRef.current.scrollHeight })
|
||||
}
|
||||
}, [ulRef, chatMessages])
|
||||
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
if (!layoutContext || chatMessages.length === 0) {
|
||||
return
|
||||
}
|
||||
@ -87,11 +71,24 @@ export default function Chat({ messageFormatter, ...props }: ChatProps) {
|
||||
}
|
||||
}, [chatMessages, layoutContext?.widget])
|
||||
|
||||
const handleClosePanel = useCallback(() => {
|
||||
const { dispatch } = layoutContext.widget
|
||||
if (dispatch) {
|
||||
dispatch({ msg: 'hide_chat' })
|
||||
}
|
||||
}, [layoutContext])
|
||||
|
||||
const localParticipant = useLocalParticipant().localParticipant
|
||||
|
||||
return (
|
||||
<div {...props} className={styles.container}>
|
||||
<ul className="lk-list lk-chat-messages" ref={ulRef}>
|
||||
<div {...props} className={classNames(styles.container, { [styles['open']]: isOpen })}>
|
||||
<div className={styles.headerContainer}>
|
||||
<Header className={styles.title} size="medium">
|
||||
{t('chat_panel.title')}
|
||||
</Header>
|
||||
<Button className={styles.close} onClick={handleClosePanel} />
|
||||
</div>
|
||||
<ul className={classNames(styles.chatMessages, 'lk-list', 'lk-chat-messages')} ref={ulRef}>
|
||||
{props.children
|
||||
? chatMessages.map((msg, idx) =>
|
||||
cloneSingleChild(props.children, {
|
||||
@ -101,9 +98,9 @@ export default function Chat({ messageFormatter, ...props }: ChatProps) {
|
||||
})
|
||||
)
|
||||
: chatMessages.map((msg, idx, allMsg) => {
|
||||
const hideName = idx >= 1 && allMsg[idx - 1].from === msg.from
|
||||
// If the time delta between two messages is bigger than 60s show timestamp.
|
||||
const hideTimestamp = idx >= 1 && msg.timestamp - allMsg[idx - 1].timestamp < 60_000
|
||||
const hideName = idx >= 1 && allMsg[idx - 1].from === msg.from && hideTimestamp
|
||||
|
||||
return (
|
||||
<ChatEntry
|
||||
@ -117,17 +114,13 @@ export default function Chat({ messageFormatter, ...props }: ChatProps) {
|
||||
})}
|
||||
</ul>
|
||||
{localParticipant.permissions?.canPublish && (
|
||||
<form className="lk-chat-form" onSubmit={handleSubmit}>
|
||||
<input
|
||||
className="lk-form-control lk-chat-form-input"
|
||||
disabled={isSending}
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
placeholder="Enter a message..."
|
||||
/>
|
||||
<button type="submit" className="lk-button lk-chat-form-button" disabled={isSending}>
|
||||
Send
|
||||
</button>
|
||||
<form className={styles.form} onSubmit={handleSubmit}>
|
||||
<Input className={styles.input} disabled={isSending} placeholder="Enter a message">
|
||||
<input ref={inputRef} />
|
||||
<Button type="submit" className={styles.button} basic size="small" disabled={isSending}>
|
||||
<SendIcon />
|
||||
</Button>
|
||||
</Input>
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
import type { ChatMessage, ReceivedChatMessage } from '@livekit/components-core'
|
||||
import { MessageFormatter } from '@livekit/components-react'
|
||||
|
||||
export type { ChatMessage, ReceivedChatMessage }
|
||||
|
||||
export type Props = React.HTMLAttributes<HTMLDivElement> & {
|
||||
isOpen: boolean
|
||||
messageFormatter?: MessageFormatter
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
import { connect } from 'react-redux'
|
||||
import { getData as getProfiles } from 'decentraland-dapps/dist/modules/profile/selectors'
|
||||
import { getAddress } from 'decentraland-dapps/dist/modules/wallet/selectors'
|
||||
import { RootState } from '../../../../../modules/reducer'
|
||||
import ChatEntry from './ChatEntry'
|
||||
import type { MapStateProps } from './ChatEntry.types'
|
||||
|
||||
const mapStateToProps = (state: RootState): MapStateProps => {
|
||||
return {
|
||||
address: getAddress(state) as string,
|
||||
profiles: getProfiles(state)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(ChatEntry)
|
||||
@ -0,0 +1,42 @@
|
||||
.chatEntry:not(:last-child) {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.sameSender {
|
||||
margin-top: -16px;
|
||||
}
|
||||
|
||||
.info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
:global(li.lk-chat-entry span.Profile > div.dcl.avatar-face.tiny.inline) {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
border: 2px solid #fcfcfc;
|
||||
background: #ecebed;
|
||||
}
|
||||
|
||||
.name {
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: normal;
|
||||
text-transform: uppercase;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.timestamp {
|
||||
color: #716b7c;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.message {
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-break: anywhere;
|
||||
}
|
||||
@ -1,23 +1,10 @@
|
||||
import * as React from 'react'
|
||||
import { tokenize, createDefaultGrammar, ReceivedChatMessage } from '@livekit/components-core'
|
||||
import { MessageFormatter } from '@livekit/components-react'
|
||||
import React, { useMemo } from 'react'
|
||||
import { tokenize, createDefaultGrammar } from '@livekit/components-core'
|
||||
import classNames from 'classnames'
|
||||
import Profile from 'decentraland-dapps/dist/containers/Profile'
|
||||
|
||||
/**
|
||||
* ChatEntry composes the HTML div element under the hood, so you can pass all its props.
|
||||
* These are the props specific to the ChatEntry component:
|
||||
* @public
|
||||
*/
|
||||
export interface ChatEntryProps extends React.HTMLAttributes<HTMLLIElement> {
|
||||
/** The chat massage object to display. */
|
||||
entry: ReceivedChatMessage
|
||||
/** Hide sender name. Useful when displaying multiple consecutive chat messages from the same person. */
|
||||
hideName?: boolean
|
||||
/** Hide message timestamp. */
|
||||
hideTimestamp?: boolean
|
||||
/** An optional formatter for the message body. */
|
||||
messageFormatter?: MessageFormatter
|
||||
}
|
||||
import { t } from 'decentraland-dapps/dist/modules/translation/utils'
|
||||
import { Props } from './ChatEntry.types'
|
||||
import styles from './ChatEntry.module.css'
|
||||
|
||||
/**
|
||||
* The `ChatEntry` component holds and displays one chat message.
|
||||
@ -31,28 +18,33 @@ export interface ChatEntryProps extends React.HTMLAttributes<HTMLLIElement> {
|
||||
* @see `Chat`
|
||||
* @public
|
||||
*/
|
||||
export function ChatEntry({ entry, hideName = false, hideTimestamp = false, messageFormatter, ...props }: ChatEntryProps) {
|
||||
const formattedMessage = React.useMemo(() => {
|
||||
export function ChatEntry({ entry, hideName = false, hideTimestamp = false, messageFormatter, address, profiles, ...props }: Props) {
|
||||
const formattedMessage = useMemo(() => {
|
||||
return messageFormatter ? messageFormatter(entry.message) : entry.message
|
||||
}, [entry.message, messageFormatter])
|
||||
|
||||
const time = new Date(entry.timestamp)
|
||||
const locale = navigator ? navigator.language : 'en-US'
|
||||
|
||||
return (
|
||||
<li
|
||||
className="lk-chat-entry"
|
||||
className={classNames('lk-chat-entry', styles.chatEntry, { [styles.sameSender]: hideName && hideTimestamp })}
|
||||
title={time.toLocaleTimeString(locale, { timeStyle: 'full' })}
|
||||
data-lk-message-origin={entry.from?.isLocal ? 'local' : 'remote'}
|
||||
{...props}
|
||||
>
|
||||
{(!hideTimestamp || !hideName) && (
|
||||
<span className="lk-meta-data">
|
||||
<span className={styles.info}>
|
||||
{entry.from?.identity && <Profile address={entry.from?.identity} imageOnly size="normal" />}
|
||||
{!hideName && <strong className="lk-participant-name">{entry.from?.name ?? entry.from?.identity}</strong>}
|
||||
{!hideTimestamp && <span className="lk-timestamp">{time.toLocaleTimeString(locale, { timeStyle: 'short' })}</span>}
|
||||
{!hideName && entry.from?.identity && (
|
||||
<strong className={styles.name}>
|
||||
{entry.from?.identity === address ? t('chat_entry.you') : profiles[entry.from?.identity].avatars[0].name}
|
||||
</strong>
|
||||
)}
|
||||
{!hideTimestamp && <span className={styles.timestamp}>{time.toLocaleTimeString(locale, { timeStyle: 'short' })}</span>}
|
||||
</span>
|
||||
)}
|
||||
<span className="lk-message-body">{formattedMessage}</span>
|
||||
<span className={styles.message}>{formattedMessage}</span>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
@ -60,11 +52,11 @@ export function ChatEntry({ entry, hideName = false, hideTimestamp = false, mess
|
||||
/** @public */
|
||||
export function formatChatMessageLinks(message: string): React.ReactNode {
|
||||
return tokenize(message, createDefaultGrammar()).map((tok, i) => {
|
||||
if (typeof tok === `string`) {
|
||||
if (typeof tok === 'string') {
|
||||
return tok
|
||||
} else {
|
||||
const content = tok.content.toString()
|
||||
const href = tok.type === `url` ? (/^http(s?):\/\//.test(content) ? content : `https://${content}`) : `mailto:${content}`
|
||||
const href = tok.type === 'url' ? (/^http(s?):\/\//.test(content) ? content : `https://${content}`) : `mailto:${content}`
|
||||
return (
|
||||
<a className="lk-chat-link" key={i} href={href} target="_blank" rel="noreferrer">
|
||||
{content}
|
||||
@ -73,3 +65,5 @@ export function formatChatMessageLinks(message: string): React.ReactNode {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export default React.memo(ChatEntry)
|
||||
|
||||
@ -0,0 +1,25 @@
|
||||
import { ReceivedChatMessage } from '@livekit/components-core'
|
||||
import { MessageFormatter } from '@livekit/components-react'
|
||||
|
||||
/**
|
||||
* ChatEntry composes the HTML div element under the hood, so you can pass all its props.
|
||||
* These are the props specific to the ChatEntry component:
|
||||
* @public
|
||||
*/
|
||||
export type OwnProps = React.HTMLAttributes<HTMLLIElement> & {
|
||||
/** The chat massage object to display. */
|
||||
entry: ReceivedChatMessage
|
||||
/** Hide sender name. Useful when displaying multiple consecutive chat messages from the same person. */
|
||||
hideName?: boolean
|
||||
/** Hide message timestamp. */
|
||||
hideTimestamp?: boolean
|
||||
/** An optional formatter for the message body. */
|
||||
messageFormatter?: MessageFormatter
|
||||
}
|
||||
|
||||
export type Props = OwnProps & {
|
||||
address: string
|
||||
profiles: ReturnType<typeof import('decentraland-dapps/dist/modules/profile/selectors').getData>
|
||||
}
|
||||
|
||||
export type MapStateProps = Pick<Props, 'address' | 'profiles'>
|
||||
@ -1,3 +1,3 @@
|
||||
import { ChatEntry } from './ChatEntry'
|
||||
import ChatEntry from './ChatEntry.container'
|
||||
|
||||
export default ChatEntry
|
||||
|
||||
@ -47,7 +47,7 @@
|
||||
color: var(--toast-text);
|
||||
}
|
||||
|
||||
.profile:not(:last-child) {
|
||||
.profile {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
@ -69,3 +69,17 @@
|
||||
font-weight: 600;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
:global(div.ProfileContainer > span.Profile > div.dcl.avatar-face.tiny.inline),
|
||||
:global(div.TrimmedProfileContainer span.Profile div.dcl.avatar-face.tiny.inline) {
|
||||
border: 2px solid #fcfcfc;
|
||||
background: #ecebed;
|
||||
}
|
||||
|
||||
:global(div.TrimmedProfileContainer) {
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
:global(div.TrimmedProfileContainer span.Profile:not(:last-child) div.dcl.avatar-face.tiny.inline) {
|
||||
margin-right: -8px;
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { useCallback } from 'react'
|
||||
import React, { useCallback, useMemo } from 'react'
|
||||
import classNames from 'classnames'
|
||||
import { t } from 'decentraland-dapps/dist/modules/translation/utils'
|
||||
import { Button, Header, Profile } from 'decentraland-ui'
|
||||
@ -6,12 +6,14 @@ import { useLayoutContext } from '../../../../hooks/useLayoutContext'
|
||||
import type { Props } from './PeoplePanel.types'
|
||||
import styles from './PeoplePanel.module.css'
|
||||
|
||||
const MAX_VISIBLE_PROFILES = 12
|
||||
|
||||
/**
|
||||
* The PeoplePanel component shows all the participants in a list.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* <PeoplePanel />
|
||||
* <PeoplePanel isOpen={true} />
|
||||
* ```
|
||||
* @public
|
||||
*/
|
||||
@ -25,6 +27,14 @@ export const PeoplePanel: React.FC<Props> = ({ profiles, isOpen }: Props) => {
|
||||
}
|
||||
}, [layoutContext])
|
||||
|
||||
const visibleProfiles = useMemo(() => {
|
||||
return Object.entries(profiles).slice(0, MAX_VISIBLE_PROFILES)
|
||||
}, [profiles])
|
||||
|
||||
const trimmedProfiles = useMemo(() => {
|
||||
return Object.entries(profiles).slice(MAX_VISIBLE_PROFILES)
|
||||
}, [profiles])
|
||||
|
||||
return (
|
||||
<div className={classNames(styles.container, { [styles['open']]: isOpen })}>
|
||||
<div className={styles.headerContainer}>
|
||||
@ -37,13 +47,22 @@ export const PeoplePanel: React.FC<Props> = ({ profiles, isOpen }: Props) => {
|
||||
{t('people_panel.subtitle')}
|
||||
</Header>
|
||||
|
||||
{Object.entries(profiles).map(([address, profile]) =>
|
||||
{visibleProfiles.map(([address, profile]) =>
|
||||
address ? (
|
||||
<div className={`${styles.profile} ProfileContainer`}>
|
||||
<Profile key={address} address={address} avatar={profile?.avatars[0]} />
|
||||
</div>
|
||||
) : null
|
||||
)}
|
||||
|
||||
{visibleProfiles.length === MAX_VISIBLE_PROFILES ? (
|
||||
<div className={`${styles.profile} TrimmedProfileContainer`}>
|
||||
{trimmedProfiles.slice(0, 3).map(([address, profile]) => (
|
||||
<Profile key={address} address={address} avatar={profile?.avatars[0]} imageOnly />
|
||||
))}
|
||||
{t('people_panel.more', { name: trimmedProfiles[0][1].avatars[0].name, count: trimmedProfiles.length - 1 })}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -15,7 +15,7 @@ export const RightPanel = () => {
|
||||
return (
|
||||
<div className={classNames(styles.container, { [styles.open]: showChat || showPeoplePanel })}>
|
||||
<PeoplePanel isOpen={showPeoplePanel} />
|
||||
<Chat style={{ display: showChat ? 'flex' : 'none' }} />
|
||||
<Chat isOpen={showChat} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,7 +1,14 @@
|
||||
.VideoConferenceInnerContainer {
|
||||
padding: 0px 60px;
|
||||
}
|
||||
|
||||
.LayoutWrapper {
|
||||
flex-direction: row;
|
||||
padding: 8px 0px;
|
||||
}
|
||||
|
||||
.GridLayout {
|
||||
flex: 1;
|
||||
padding: 0;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import React, { useRef, useEffect } from 'react'
|
||||
import { isEqualTrackRef, isTrackReference, log, isWeb } from '@livekit/components-core'
|
||||
import type { TrackReferenceOrPlaceholder } from '@livekit/components-core'
|
||||
import {
|
||||
CarouselView,
|
||||
ConnectionStateToast,
|
||||
@ -18,7 +19,6 @@ import { ControlBar } from '../ControlBar'
|
||||
import ParticipantTile from '../ParticipantTile'
|
||||
import RightPanel from '../RightPanel'
|
||||
import type { VideoConferenceProps } from './VideoConference.types'
|
||||
import type { TrackReferenceOrPlaceholder } from '@livekit/components-core'
|
||||
import styles from './VideoConference.module.css'
|
||||
|
||||
/**
|
||||
@ -75,7 +75,7 @@ export function VideoConference(props: VideoConferenceProps) {
|
||||
<div className="lk-video-conference" {...props}>
|
||||
{isWeb() && (
|
||||
<LayoutContextProvider value={layoutContext}>
|
||||
<div className="lk-video-conference-inner">
|
||||
<div className={`${styles.VideoConferenceInnerContainer} lk-video-conference-inner`}>
|
||||
{!focusTrack ? (
|
||||
<div className={classNames('lk-grid-layout-wrapper', styles.LayoutWrapper)}>
|
||||
<GridLayout tracks={tracks} className={styles.GridLayout}>
|
||||
|
||||
19
src/hooks/useChat.ts
Normal file
19
src/hooks/useChat.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useRoomContext } from '@livekit/components-react'
|
||||
import { setupChat, useObservableState } from '../utils/chat'
|
||||
|
||||
/** @public */
|
||||
export function useChat() {
|
||||
const room = useRoomContext()
|
||||
const [setup, setSetup] = useState<ReturnType<typeof setupChat>>()
|
||||
const isSending = useObservableState(setup?.isSendingObservable, false)
|
||||
const chatMessages = useObservableState(setup?.messageObservable, [])
|
||||
|
||||
useEffect(() => {
|
||||
const setupChatReturn = setupChat(room)
|
||||
setSetup(setupChatReturn)
|
||||
return setupChatReturn.destroy
|
||||
}, [room])
|
||||
|
||||
return { send: setup?.send, chatMessages, isSending }
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
import React, { useContext, useReducer } from 'react'
|
||||
import { WIDGET_DEFAULT_STATE, PIN_DEFAULT_STATE } from '@livekit/components-core'
|
||||
import { LayoutContext } from '@livekit/components-react'
|
||||
import type { WidgetState as LivekitWidgetState, PinState, TrackReference } from '@livekit/components-core'
|
||||
import { LayoutContext } from '@livekit/components-react'
|
||||
|
||||
export type PinAction =
|
||||
| {
|
||||
|
||||
@ -137,3 +137,7 @@ code {
|
||||
margin-right: unset;
|
||||
}
|
||||
}
|
||||
|
||||
[data-lk-theme='default'] {
|
||||
--lk-control-bar-height: 44px;
|
||||
}
|
||||
|
||||
@ -9,6 +9,13 @@
|
||||
},
|
||||
"people_panel": {
|
||||
"title": "People",
|
||||
"subtitle": "People in this meeting"
|
||||
"subtitle": "People in this meeting",
|
||||
"more": "{name} and {count} people more."
|
||||
},
|
||||
"chat_panel": {
|
||||
"title": "Chat"
|
||||
},
|
||||
"chat_entry": {
|
||||
"you": "You"
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,6 +9,13 @@
|
||||
},
|
||||
"people_panel": {
|
||||
"title": "Personas",
|
||||
"subtitle": "Personas en esta reunión"
|
||||
"subtitle": "Personas en esta reunión",
|
||||
"more": "{name} y {count} personas más."
|
||||
},
|
||||
"chat_panel": {
|
||||
"title": "Chat"
|
||||
},
|
||||
"chat_entry": {
|
||||
"you": "Tu"
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,6 +9,13 @@
|
||||
},
|
||||
"people_panel": {
|
||||
"title": "人们",
|
||||
"subtitle": "这次会议的人"
|
||||
"subtitle": "这次会议的人",
|
||||
"more": "{name} 和另外 {count} 人。"
|
||||
},
|
||||
"chat_panel": {
|
||||
"title": "聊天"
|
||||
},
|
||||
"chat_entry": {
|
||||
"you": "你"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import React, { useEffect, useState, isValidElement, cloneElement } from 'react'
|
||||
import { DataTopic, sendMessage, setupDataMessageHandler } from '@livekit/components-core'
|
||||
import { ReceivedChatMessage } from '@livekit/components-react'
|
||||
import { DataPacket_Kind, Participant, Room } from 'livekit-client'
|
||||
import * as React from 'react'
|
||||
import { BehaviorSubject, Subject, takeUntil, map, scan, filter } from 'rxjs'
|
||||
import type { Observable } from 'rxjs'
|
||||
import { Packet } from '@dcl/protocol/out-js/decentraland/kernel/comms/rfc4/comms.gen'
|
||||
@ -10,8 +10,8 @@ import { Packet } from '@dcl/protocol/out-js/decentraland/kernel/comms/rfc4/comm
|
||||
* @internal
|
||||
*/
|
||||
export function useObservableState<T>(observable: Observable<T> | undefined, startWith: T) {
|
||||
const [state, setState] = React.useState<T>(startWith)
|
||||
React.useEffect(() => {
|
||||
const [state, setState] = useState<T>(startWith)
|
||||
useEffect(() => {
|
||||
// observable state doesn't run in SSR
|
||||
if (typeof window === 'undefined' || !observable) return
|
||||
const subscription = observable.subscribe(setState)
|
||||
@ -24,8 +24,8 @@ export function cloneSingleChild(children: React.ReactNode | React.ReactNode[],
|
||||
return React.Children.map(children, child => {
|
||||
// Checking isValidElement is the safe way and avoids a typescript
|
||||
// error too.
|
||||
if (React.isValidElement(child) && React.Children.only(children)) {
|
||||
return React.cloneElement(child, { ...props, key })
|
||||
if (isValidElement(child) && React.Children.only(children)) {
|
||||
return cloneElement(child, { ...props, key })
|
||||
}
|
||||
return child
|
||||
})
|
||||
Loading…
x
Reference in New Issue
Block a user