(feat): add live messages
(feat): update licenses (feat): update livekit logging (feat): add basic grid layout (fix): remove deeplink log message
This commit is contained in:
parent
3768488e49
commit
8e294d230e
21 changed files with 510 additions and 51 deletions
|
|
@ -59,10 +59,6 @@ export default function DeeplinkProvider({
|
|||
};
|
||||
}, [isTauriEnv]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log(deeplinks);
|
||||
}, [deeplinks]);
|
||||
|
||||
return (
|
||||
<deeplinkContext.Provider
|
||||
value={{
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@
|
|||
"@tensamin/tauth": "workspace:*",
|
||||
"@tensamin/ui": "*",
|
||||
"@tensamin/user": "workspace:*",
|
||||
"@tensamin/notifications": "workspace:*",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"comlink": "^4.4.2",
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import { useInitializeCall } from "@tensamin/call/store";
|
|||
import { Provider as SocketContext } from "@tensamin/ttp";
|
||||
import UserContext from "@tensamin/user/context";
|
||||
import DeeplinkContext from "@tensamin/tauri/deeplinkHandler";
|
||||
import NotificationsProvider from "@tensamin/notifications/context";
|
||||
|
||||
import TAuthWrapper from "@tensamin/tauth/context";
|
||||
|
||||
|
|
@ -145,7 +146,9 @@ function AppShell() {
|
|||
<CallInit />
|
||||
<TAuthWrapper>
|
||||
<AppLayout>
|
||||
<Outlet />
|
||||
<NotificationsProvider>
|
||||
<Outlet />
|
||||
</NotificationsProvider>
|
||||
</AppLayout>
|
||||
</TAuthWrapper>
|
||||
</UserContext>
|
||||
|
|
|
|||
14
bun.lock
14
bun.lock
|
|
@ -7,6 +7,7 @@
|
|||
"dependencies": {
|
||||
"@tensamin/ttp-core": "*",
|
||||
"@tensamin/ui": "*",
|
||||
"sonner": "^2.0.7",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^10.0.1",
|
||||
|
|
@ -54,6 +55,7 @@
|
|||
"@tensamin/call": "workspace:*",
|
||||
"@tensamin/chat": "workspace:*",
|
||||
"@tensamin/crypto": "workspace:*",
|
||||
"@tensamin/notifications": "workspace:*",
|
||||
"@tensamin/shared": "workspace:*",
|
||||
"@tensamin/storage": "workspace:*",
|
||||
"@tensamin/tauri": "workspace:*",
|
||||
|
|
@ -164,7 +166,11 @@
|
|||
"name": "@tensamin/notifications",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@tensamin/ttp": "git+https://github.com/Tensamin/TTP.git",
|
||||
"@tensamin/crypto": "workspace:*",
|
||||
"@tensamin/shared": "workspace:*",
|
||||
"@tensamin/storage": "workspace:*",
|
||||
"@tensamin/ttp": "workspace:*",
|
||||
"@tensamin/user": "workspace:*",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"zod": "^4.3.6",
|
||||
|
|
@ -244,7 +250,7 @@
|
|||
},
|
||||
},
|
||||
"overrides": {
|
||||
"@tensamin/ttp-core": "https://git.methanium.net/tensamin/ttp/archive/0.0.12.tar.gz",
|
||||
"@tensamin/ttp-core": "https://git.methanium.net/tensamin/ttp/archive/0.0.13.tar.gz",
|
||||
"@tensamin/ui": "https://git.methanium.net/tensamin/ui/archive/0.0.30.tar.gz",
|
||||
},
|
||||
"packages": {
|
||||
|
|
@ -696,7 +702,7 @@
|
|||
|
||||
"@tensamin/ttp": ["@tensamin/ttp@workspace:packages/ttp"],
|
||||
|
||||
"@tensamin/ttp-core": ["@tensamin/ttp-core@https://git.methanium.net/tensamin/ttp/archive/0.0.12.tar.gz", { "dependencies": { "@eslint/js": "^10.0.1", "@typescript-eslint/parser": "^8.58.1", "@webtransport-bun/webtransport": "^0.3.0", "globals": "^17.5.0", "typescript": "^6.0.2", "typescript-eslint": "^8.58.1", "zod": "^4.3.6" } }, "sha512-YdwOocPnNMoE0N/yqksJM9JizkhmNv7q8sc7JMI/TOKM/0wDlQqHLqJBMd7fAGkGLCSx+DKx5z8SJbrA9YlzVg=="],
|
||||
"@tensamin/ttp-core": ["@tensamin/ttp-core@https://git.methanium.net/tensamin/ttp/archive/0.0.13.tar.gz", { "dependencies": { "@eslint/js": "^10.0.1", "@typescript-eslint/parser": "^8.58.1", "@webtransport-bun/webtransport": "^0.3.0", "globals": "^17.5.0", "typescript": "^6.0.2", "typescript-eslint": "^8.58.1", "zod": "^4.3.6" } }, "sha512-ib5E8n9IfMzai+ucOXwTNMtu4j6m1RSHANGbFVJxP1VwJDtqIVMqpg/NtTKdRNXQYM1hK5WAScfl6r+aeyaj4g=="],
|
||||
|
||||
"@tensamin/ui": ["@tensamin/ui@https://git.methanium.net/tensamin/ui/archive/0.0.30.tar.gz", { "dependencies": { "@base-ui/react": "^1.3.0", "@fontsource-variable/inter": "^5.2.6", "@tauri-apps/api": "^2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", "date-fns": "^4.1.0", "embla-carousel-react": "^8.6.0", "input-otp": "^1.4.2", "lucide-react": "^1.8.0", "next-themes": "^0.4.6", "react-day-picker": "^9.14.0", "react-resizable-panels": "^4.10.0", "recharts": "3.8.0", "shadcn": "^3.5.0", "sonner": "^2.0.7", "tailwind-merge": "^3.5.0", "tw-animate-css": "^1.3.0", "vaul": "^1.1.2" }, "peerDependencies": { "react": "^19.2.0", "react-dom": "^19.2.0" } }, "sha512-Eh/ko+4x4gF/25JyB38PNN2n/UVUIR6yxxZTpyZPx4maL00992pGmFt62kb1ejJQoooEpEfSo/ALtQDF5cjdoA=="],
|
||||
|
||||
|
|
@ -1662,8 +1668,6 @@
|
|||
|
||||
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"@tensamin/notifications/@tensamin/ttp": ["TTP@github:Tensamin/TTP#e246d1a", {}, "Tensamin-TTP-e246d1a", "sha512-ECvnNMtGemIawk/F1FcuOPwMmNgMUaAzcRZiEwHGuG1DtjcbQ8BSF1ZG9ZpGA/9HqcnHmoreG+QGjF6uLKbi+w=="],
|
||||
|
||||
"@tensamin/shared/sonner": ["sonner@1.7.4", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-DIS8z4PfJRbIyfVFDVnK9rO3eYDtse4Omcm6bt0oEr5/jtLgysmjuBl1frJ9E/EQZrFmKx2A8m/s5s9CRXIzhw=="],
|
||||
|
||||
"@tensamin/tauri/lucide-react": ["lucide-react@1.8.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-WuvlsjngSk7TnTBJ1hsCy3ql9V9VOdcPkd3PKcSmM34vJD8KG6molxz7m7zbYFgICwsanQWmJ13JlYs4Zp7Arw=="],
|
||||
|
|
|
|||
|
|
@ -173,11 +173,11 @@ Generated from bun.lock and installed packages in workspace node_modules folders
|
|||
- Folder: `licenses/@tauri-apps_plugin-opener@2.5.3`
|
||||
- Source package dir: `apps/tauri/node_modules/@tauri-apps/plugin-opener`
|
||||
|
||||
## @tensamin/ttp-core@0.0.12
|
||||
## @tensamin/ttp-core@0.0.13
|
||||
|
||||
- License: UNKNOWN
|
||||
- Included files: LICENSE
|
||||
- Folder: `licenses/@tensamin_ttp-core@0.0.12`
|
||||
- Folder: `licenses/@tensamin_ttp-core@0.0.13`
|
||||
- Source package dir: `packages/ttp/node_modules/@tensamin/ttp-core`
|
||||
|
||||
## @tensamin/ui@0.0.30
|
||||
|
|
@ -401,6 +401,26 @@ Generated from bun.lock and installed packages in workspace node_modules folders
|
|||
- Folder: `licenses/react-dom@19.2.5`
|
||||
- Source package dir: `apps/tauri/node_modules/react-dom`
|
||||
|
||||
## recharts@3.8.1
|
||||
|
||||
- License: MIT
|
||||
- Homepage: https://github.com/recharts/recharts
|
||||
- Repository: git+https://github.com/recharts/recharts.git
|
||||
- Description: React charts
|
||||
- Included files: LICENSE
|
||||
- Folder: `licenses/recharts@3.8.1`
|
||||
- Source package dir: `packages/call/node_modules/recharts`
|
||||
|
||||
## recharts@3.8.1
|
||||
|
||||
- License: MIT
|
||||
- Homepage: https://github.com/recharts/recharts
|
||||
- Repository: git+https://github.com/recharts/recharts.git
|
||||
- Description: React charts
|
||||
- Included files: LICENSE
|
||||
- Folder: `licenses/recharts@3.8.1`
|
||||
- Source package dir: `packages/call/node_modules/recharts`
|
||||
|
||||
## shadcn@4.2.0
|
||||
|
||||
- License: MIT
|
||||
|
|
@ -547,3 +567,13 @@ Generated from bun.lock and installed packages in workspace node_modules folders
|
|||
- Included files: LICENSE
|
||||
- Folder: `licenses/zod@4.3.6`
|
||||
- Source package dir: `apps/web/node_modules/zod`
|
||||
|
||||
## zustand@5.0.12
|
||||
|
||||
- License: MIT
|
||||
- Homepage: https://github.com/pmndrs/zustand
|
||||
- Repository: git+https://github.com/pmndrs/zustand.git
|
||||
- Description: 🐻 Bear necessities for state management in React
|
||||
- Included files: LICENSE
|
||||
- Folder: `licenses/zustand@5.0.12`
|
||||
- Source package dir: `packages/call/node_modules/zustand`
|
||||
|
|
|
|||
22
licenses/recharts@3.8.1/LICENSE
Normal file
22
licenses/recharts@3.8.1/LICENSE
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015-present recharts
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
"specVersion": "1.5",
|
||||
"version": 1,
|
||||
"metadata": {
|
||||
"timestamp": "2026-04-28T17:49:46.479Z",
|
||||
"timestamp": "2026-04-29T21:21:16.348Z",
|
||||
"tools": [
|
||||
{
|
||||
"vendor": "OpenAI",
|
||||
|
|
@ -615,15 +615,15 @@
|
|||
},
|
||||
{
|
||||
"type": "library",
|
||||
"bomRef": "pkg:npm/%40tensamin/ttp-core@0.0.12",
|
||||
"bomRef": "pkg:npm/%40tensamin/ttp-core@0.0.13",
|
||||
"name": "@tensamin/ttp-core",
|
||||
"version": "0.0.12",
|
||||
"purl": "pkg:npm/%40tensamin/ttp-core@0.0.12",
|
||||
"version": "0.0.13",
|
||||
"purl": "pkg:npm/%40tensamin/ttp-core@0.0.13",
|
||||
"externalReferences": [],
|
||||
"properties": [
|
||||
{
|
||||
"name": "local:licenseFolder",
|
||||
"value": "licenses/@tensamin_ttp-core@0.0.12"
|
||||
"value": "licenses/@tensamin_ttp-core@0.0.13"
|
||||
},
|
||||
{
|
||||
"name": "local:sourcePackageDir",
|
||||
|
|
@ -1395,6 +1395,76 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "library",
|
||||
"bomRef": "pkg:npm/recharts@3.8.1",
|
||||
"name": "recharts",
|
||||
"version": "3.8.1",
|
||||
"purl": "pkg:npm/recharts@3.8.1",
|
||||
"description": "React charts",
|
||||
"licenses": [
|
||||
{
|
||||
"license": {
|
||||
"id": "MIT"
|
||||
}
|
||||
}
|
||||
],
|
||||
"externalReferences": [
|
||||
{
|
||||
"type": "website",
|
||||
"url": "https://github.com/recharts/recharts"
|
||||
},
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "git+https://github.com/recharts/recharts.git"
|
||||
}
|
||||
],
|
||||
"properties": [
|
||||
{
|
||||
"name": "local:licenseFolder",
|
||||
"value": "licenses/recharts@3.8.1"
|
||||
},
|
||||
{
|
||||
"name": "local:sourcePackageDir",
|
||||
"value": "packages/call/node_modules/recharts"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "library",
|
||||
"bomRef": "pkg:npm/recharts@3.8.1",
|
||||
"name": "recharts",
|
||||
"version": "3.8.1",
|
||||
"purl": "pkg:npm/recharts@3.8.1",
|
||||
"description": "React charts",
|
||||
"licenses": [
|
||||
{
|
||||
"license": {
|
||||
"id": "MIT"
|
||||
}
|
||||
}
|
||||
],
|
||||
"externalReferences": [
|
||||
{
|
||||
"type": "website",
|
||||
"url": "https://github.com/recharts/recharts"
|
||||
},
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "git+https://github.com/recharts/recharts.git"
|
||||
}
|
||||
],
|
||||
"properties": [
|
||||
{
|
||||
"name": "local:licenseFolder",
|
||||
"value": "licenses/recharts@3.8.1"
|
||||
},
|
||||
{
|
||||
"name": "local:sourcePackageDir",
|
||||
"value": "packages/call/node_modules/recharts"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "library",
|
||||
"bomRef": "pkg:npm/shadcn@4.2.0",
|
||||
|
|
@ -1907,6 +1977,41 @@
|
|||
"value": "apps/web/node_modules/zod"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "library",
|
||||
"bomRef": "pkg:npm/zustand@5.0.12",
|
||||
"name": "zustand",
|
||||
"version": "5.0.12",
|
||||
"purl": "pkg:npm/zustand@5.0.12",
|
||||
"description": "🐻 Bear necessities for state management in React",
|
||||
"licenses": [
|
||||
{
|
||||
"license": {
|
||||
"id": "MIT"
|
||||
}
|
||||
}
|
||||
],
|
||||
"externalReferences": [
|
||||
{
|
||||
"type": "website",
|
||||
"url": "https://github.com/pmndrs/zustand"
|
||||
},
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "git+https://github.com/pmndrs/zustand.git"
|
||||
}
|
||||
],
|
||||
"properties": [
|
||||
{
|
||||
"name": "local:licenseFolder",
|
||||
"value": "licenses/zustand@5.0.12"
|
||||
},
|
||||
{
|
||||
"name": "local:sourcePackageDir",
|
||||
"value": "packages/call/node_modules/zustand"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"generatedAt": "2026-04-28T17:49:46.477Z",
|
||||
"packageCount": 57,
|
||||
"generatedAt": "2026-04-29T21:21:16.347Z",
|
||||
"packageCount": 60,
|
||||
"packages": [
|
||||
{
|
||||
"name": "@codemirror/commands",
|
||||
|
|
@ -240,7 +240,7 @@
|
|||
},
|
||||
{
|
||||
"name": "@tensamin/ttp-core",
|
||||
"version": "0.0.12",
|
||||
"version": "0.0.13",
|
||||
"license": "UNKNOWN",
|
||||
"homepage": null,
|
||||
"repository": null,
|
||||
|
|
@ -248,7 +248,7 @@
|
|||
"files": [
|
||||
"LICENSE"
|
||||
],
|
||||
"licenseFolder": "licenses/@tensamin_ttp-core@0.0.12",
|
||||
"licenseFolder": "licenses/@tensamin_ttp-core@0.0.13",
|
||||
"sourcePackageDir": "packages/ttp/node_modules/@tensamin/ttp-core"
|
||||
},
|
||||
{
|
||||
|
|
@ -549,6 +549,32 @@
|
|||
"licenseFolder": "licenses/react-dom@19.2.5",
|
||||
"sourcePackageDir": "apps/tauri/node_modules/react-dom"
|
||||
},
|
||||
{
|
||||
"name": "recharts",
|
||||
"version": "3.8.1",
|
||||
"license": "MIT",
|
||||
"homepage": "https://github.com/recharts/recharts",
|
||||
"repository": "git+https://github.com/recharts/recharts.git",
|
||||
"description": "React charts",
|
||||
"files": [
|
||||
"LICENSE"
|
||||
],
|
||||
"licenseFolder": "licenses/recharts@3.8.1",
|
||||
"sourcePackageDir": "packages/call/node_modules/recharts"
|
||||
},
|
||||
{
|
||||
"name": "recharts",
|
||||
"version": "3.8.1",
|
||||
"license": "MIT",
|
||||
"homepage": "https://github.com/recharts/recharts",
|
||||
"repository": "git+https://github.com/recharts/recharts.git",
|
||||
"description": "React charts",
|
||||
"files": [
|
||||
"LICENSE"
|
||||
],
|
||||
"licenseFolder": "licenses/recharts@3.8.1",
|
||||
"sourcePackageDir": "packages/call/node_modules/recharts"
|
||||
},
|
||||
{
|
||||
"name": "shadcn",
|
||||
"version": "4.2.0",
|
||||
|
|
@ -743,6 +769,19 @@
|
|||
],
|
||||
"licenseFolder": "licenses/zod@4.3.6",
|
||||
"sourcePackageDir": "apps/web/node_modules/zod"
|
||||
},
|
||||
{
|
||||
"name": "zustand",
|
||||
"version": "5.0.12",
|
||||
"license": "MIT",
|
||||
"homepage": "https://github.com/pmndrs/zustand",
|
||||
"repository": "git+https://github.com/pmndrs/zustand.git",
|
||||
"description": "🐻 Bear necessities for state management in React",
|
||||
"files": [
|
||||
"LICENSE"
|
||||
],
|
||||
"licenseFolder": "licenses/zustand@5.0.12",
|
||||
"sourcePackageDir": "packages/call/node_modules/zustand"
|
||||
}
|
||||
]
|
||||
}
|
||||
21
licenses/zustand@5.0.12/LICENSE
Normal file
21
licenses/zustand@5.0.12/LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2019 Paul Henschel
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
|
@ -38,10 +38,11 @@
|
|||
},
|
||||
"overrides": {
|
||||
"@tensamin/ui": "https://git.methanium.net/tensamin/ui/archive/0.0.30.tar.gz",
|
||||
"@tensamin/ttp-core": "https://git.methanium.net/tensamin/ttp/archive/0.0.12.tar.gz"
|
||||
"@tensamin/ttp-core": "https://git.methanium.net/tensamin/ttp/archive/0.0.13.tar.gz"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tensamin/ttp-core": "*",
|
||||
"@tensamin/ui": "*"
|
||||
"@tensamin/ui": "*",
|
||||
"sonner": "^2.0.7"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,14 +5,20 @@ import ScreenshareButton from "./buttons/screenshare";
|
|||
import LeaveButton from "./buttons/leave";
|
||||
|
||||
export default function Actions() {
|
||||
const sharedClasses = "w-14 h-10";
|
||||
const sharedIconSize = 15;
|
||||
|
||||
return (
|
||||
<div className="w-full flex justify-center py-3">
|
||||
<Card className="p-1.75">
|
||||
<CardContent className="p-0! flex gap-1.75">
|
||||
<MuteButton className="w-14 h-10" iconSize={25} />
|
||||
<DeafButton className="w-14 h-10" iconSize={25} />
|
||||
<ScreenshareButton className="w-14 h-10" iconSize={25} />
|
||||
<LeaveButton className="w-14 h-10" iconSize={25} />
|
||||
<MuteButton className={sharedClasses} iconSize={sharedIconSize} />
|
||||
<DeafButton className={sharedClasses} iconSize={sharedIconSize} />
|
||||
<ScreenshareButton
|
||||
className={sharedClasses}
|
||||
iconSize={sharedIconSize}
|
||||
/>
|
||||
<LeaveButton className={sharedClasses} iconSize={sharedIconSize} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { LeaveIcon } from "@livekit/components-react";
|
||||
import { Button } from "@tensamin/ui";
|
||||
import { disconnect } from "../../store";
|
||||
import { disconnect, useCall } from "../../store";
|
||||
|
||||
export default function LeaveButton({
|
||||
className,
|
||||
|
|
@ -9,10 +9,13 @@ export default function LeaveButton({
|
|||
className?: string;
|
||||
iconSize?: number;
|
||||
}) {
|
||||
const state = useCall((store) => store.state);
|
||||
|
||||
return (
|
||||
<Button
|
||||
className={className}
|
||||
onClick={() => disconnect()}
|
||||
disabled={state === "closing" || state === "closed"}
|
||||
variant="destructive"
|
||||
>
|
||||
<LeaveIcon style={{ scale: (iconSize ? iconSize + 100 : 100) + "%" }} />
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ export function TinyPingGraph() {
|
|||
<TooltipTrigger
|
||||
render={
|
||||
<div
|
||||
className="w-full h-6"
|
||||
className="h-6 w-24 min-w-0 shrink-0"
|
||||
style={{
|
||||
WebkitMaskImage:
|
||||
"linear-gradient(to right, transparent 0%, var(--primary) 15%, var(--primary) 85%, transparent 100%)",
|
||||
|
|
@ -122,13 +122,24 @@ export function TinyPingGraph() {
|
|||
}}
|
||||
>
|
||||
{data.length > 1 && (
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<ResponsiveContainer
|
||||
width="100%"
|
||||
height="100%"
|
||||
minWidth={0}
|
||||
minHeight={24}
|
||||
>
|
||||
<AreaChart
|
||||
data={data}
|
||||
margin={{ top: 2, right: 0, bottom: 2, left: 0 }}
|
||||
>
|
||||
<defs>
|
||||
<linearGradient id="pingGradient" x1="0" y1="0" x2="0" y2="1">
|
||||
<linearGradient
|
||||
id="pingGradient"
|
||||
x1="0"
|
||||
y1="0"
|
||||
x2="0"
|
||||
y2="1"
|
||||
>
|
||||
<stop
|
||||
offset="0%"
|
||||
stopColor="currentColor"
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ export default function TopBar() {
|
|||
let active = true;
|
||||
|
||||
if (userIds.length === 0) {
|
||||
setUsers([]);
|
||||
return () => {
|
||||
active = false;
|
||||
};
|
||||
|
|
@ -42,7 +41,7 @@ export default function TopBar() {
|
|||
return () => {
|
||||
active = false;
|
||||
};
|
||||
}, [get, userIdsKey]);
|
||||
}, [get, userIdsKey, userIds]);
|
||||
|
||||
return (
|
||||
<div className="w-full flex justify-between h-12">
|
||||
|
|
|
|||
|
|
@ -15,10 +15,21 @@ import {
|
|||
RoomEvent,
|
||||
type RemoteTrack,
|
||||
Track,
|
||||
setLogExtension,
|
||||
getLogger,
|
||||
} from "livekit-client";
|
||||
import z from "zod";
|
||||
import { toast as sonnerToast } from "sonner";
|
||||
|
||||
// logging
|
||||
setLogExtension(
|
||||
(level, message, context) =>
|
||||
context
|
||||
? log(level, "livekit", "blue", message, context)
|
||||
: log(level, "livekit", "blue", message),
|
||||
getLogger("tensamin"),
|
||||
);
|
||||
|
||||
type CallState = "closed" | "closing" | "connecting" | "open" | "encrypting";
|
||||
type CallView = "preview" | "focused" | "grid";
|
||||
type CurrentCallData =
|
||||
|
|
@ -76,6 +87,7 @@ const e2eeWorker = new Worker(
|
|||
const room = new Room({
|
||||
dynacast: true,
|
||||
adaptiveStream: true,
|
||||
loggerName: "tensamin",
|
||||
encryption: {
|
||||
keyProvider,
|
||||
worker: e2eeWorker,
|
||||
|
|
@ -83,6 +95,7 @@ const room = new Room({
|
|||
});
|
||||
const remoteAudioElements = new Map<string, HTMLMediaElement>();
|
||||
|
||||
// audio helpers
|
||||
function attachRemoteAudio(trackSid: string, element: HTMLMediaElement) {
|
||||
const existingElement = remoteAudioElements.get(trackSid);
|
||||
|
||||
|
|
@ -113,6 +126,7 @@ function clearRemoteAudio() {
|
|||
}
|
||||
}
|
||||
|
||||
// utils
|
||||
function requireRuntime(runtime: Runtime | null): Runtime {
|
||||
if (!runtime) {
|
||||
throw new Error("Call store is not initialized");
|
||||
|
|
@ -132,6 +146,7 @@ export function syncParticipantState() {
|
|||
});
|
||||
}
|
||||
|
||||
// set state functions
|
||||
export function setCallRuntime(runtime: Runtime) {
|
||||
useCall.setState({ runtime });
|
||||
}
|
||||
|
|
@ -154,6 +169,7 @@ export function setCurrentCallData(
|
|||
useCall.setState({ currentCallData });
|
||||
}
|
||||
|
||||
// more utils
|
||||
export async function openCallPage(callId: string) {
|
||||
await requireRuntime(useCall.getState().runtime).navigate({
|
||||
to: "/call",
|
||||
|
|
@ -225,6 +241,7 @@ export function disconnect() {
|
|||
});
|
||||
|
||||
room.disconnect();
|
||||
useCall.setState({ state: "closed" });
|
||||
syncParticipantState();
|
||||
}
|
||||
|
||||
|
|
@ -594,7 +611,7 @@ export function useInitializeCall() {
|
|||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
log(1, "Call", "red", "Failed to get call data", {
|
||||
log(1, "call", "red", "Failed to get call data", {
|
||||
callId,
|
||||
error: err,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,3 +1,173 @@
|
|||
import { RoomEvent } from "livekit-client";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useCall } from "../../store";
|
||||
|
||||
const TILE_ASPECT_RATIO = 16 / 9;
|
||||
const GRID_GAP = 12;
|
||||
|
||||
type GridLayout = {
|
||||
columns: number;
|
||||
rows: number;
|
||||
tileWidth: number;
|
||||
tileHeight: number;
|
||||
rowCounts: number[];
|
||||
};
|
||||
|
||||
function buildBalancedRows(itemCount: number, rows: number) {
|
||||
if (itemCount === 0 || rows === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const baseCount = Math.floor(itemCount / rows);
|
||||
const remainder = itemCount % rows;
|
||||
|
||||
return Array.from(
|
||||
{ length: rows },
|
||||
(_, index) => baseCount + (index < remainder ? 1 : 0),
|
||||
);
|
||||
}
|
||||
|
||||
function calculateOptimalGridLayout(
|
||||
containerWidth: number,
|
||||
containerHeight: number,
|
||||
itemCount: number,
|
||||
): GridLayout {
|
||||
if (containerWidth <= 0 || containerHeight <= 0 || itemCount <= 0) {
|
||||
return {
|
||||
columns: 0,
|
||||
rows: 0,
|
||||
tileWidth: 0,
|
||||
tileHeight: 0,
|
||||
rowCounts: [],
|
||||
};
|
||||
}
|
||||
|
||||
let bestLayout: GridLayout = {
|
||||
columns: 1,
|
||||
rows: itemCount,
|
||||
tileWidth: 0,
|
||||
tileHeight: 0,
|
||||
rowCounts: Array.from({ length: itemCount }, () => 1),
|
||||
};
|
||||
|
||||
for (let columns = 1; columns <= itemCount; columns++) {
|
||||
const rows = Math.ceil(itemCount / columns);
|
||||
const maxTileWidth =
|
||||
(containerWidth - GRID_GAP * Math.max(columns - 1, 0)) / columns;
|
||||
const maxTileHeight =
|
||||
(containerHeight - GRID_GAP * Math.max(rows - 1, 0)) / rows;
|
||||
const tileWidth = Math.min(maxTileWidth, maxTileHeight * TILE_ASPECT_RATIO);
|
||||
const tileHeight = tileWidth / TILE_ASPECT_RATIO;
|
||||
|
||||
if (tileWidth <= 0 || tileHeight <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const currentArea = tileWidth * tileHeight;
|
||||
const bestArea = bestLayout.tileWidth * bestLayout.tileHeight;
|
||||
|
||||
if (currentArea > bestArea) {
|
||||
bestLayout = {
|
||||
columns,
|
||||
rows,
|
||||
tileWidth,
|
||||
tileHeight,
|
||||
rowCounts: buildBalancedRows(itemCount, rows),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return bestLayout;
|
||||
}
|
||||
|
||||
export default function View() {
|
||||
return <div className="w-full h-full bg-red-500">Grid</div>;
|
||||
const room = useCall((state) => state.room);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const [containerSize, setContainerSize] = useState({ width: 0, height: 0 });
|
||||
const [participantCount, setParticipantCount] = useState(
|
||||
() => room.remoteParticipants.size + 1,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const element = containerRef.current;
|
||||
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
|
||||
const observer = new ResizeObserver(([entry]) => {
|
||||
const { width, height } = entry.contentRect;
|
||||
setContainerSize({ width, height });
|
||||
});
|
||||
|
||||
observer.observe(element);
|
||||
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const syncParticipantCount = () => {
|
||||
setParticipantCount(room.remoteParticipants.size + 1);
|
||||
};
|
||||
|
||||
syncParticipantCount();
|
||||
|
||||
room.on(RoomEvent.Connected, syncParticipantCount);
|
||||
room.on(RoomEvent.Disconnected, syncParticipantCount);
|
||||
room.on(RoomEvent.ParticipantConnected, syncParticipantCount);
|
||||
room.on(RoomEvent.ParticipantDisconnected, syncParticipantCount);
|
||||
|
||||
return () => {
|
||||
room.off(RoomEvent.Connected, syncParticipantCount);
|
||||
room.off(RoomEvent.Disconnected, syncParticipantCount);
|
||||
room.off(RoomEvent.ParticipantConnected, syncParticipantCount);
|
||||
room.off(RoomEvent.ParticipantDisconnected, syncParticipantCount);
|
||||
};
|
||||
}, [room]);
|
||||
|
||||
const layout = useMemo(
|
||||
() =>
|
||||
calculateOptimalGridLayout(
|
||||
containerSize.width,
|
||||
containerSize.height,
|
||||
participantCount,
|
||||
),
|
||||
[containerSize.height, containerSize.width, participantCount],
|
||||
);
|
||||
|
||||
const rows = useMemo(() => {
|
||||
let nextParticipant = 1;
|
||||
|
||||
return layout.rowCounts.map((count, rowIndex) => {
|
||||
const participants = Array.from(
|
||||
{ length: count },
|
||||
() => nextParticipant++,
|
||||
);
|
||||
return { rowIndex, participants };
|
||||
});
|
||||
}, [layout.rowCounts]);
|
||||
|
||||
return (
|
||||
<div ref={containerRef} className="h-full w-full overflow-hidden p-3">
|
||||
<div className="flex h-full w-full flex-col items-center justify-center gap-3">
|
||||
{rows.map((row) => (
|
||||
<div key={row.rowIndex} className="flex justify-center gap-3">
|
||||
{row.participants.map((participant) => (
|
||||
<div
|
||||
key={participant}
|
||||
className="flex items-center justify-center rounded-xl bg-red-500"
|
||||
style={{
|
||||
width: layout.tileWidth,
|
||||
height: layout.tileHeight,
|
||||
}}
|
||||
>
|
||||
Test {participant}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ export default function Preview() {
|
|||
let active = true;
|
||||
|
||||
if (!currentCallData?.exists) {
|
||||
setData([]);
|
||||
return () => {
|
||||
active = false;
|
||||
};
|
||||
|
|
@ -52,7 +51,7 @@ export default function Preview() {
|
|||
})}
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-2xl">Invalid call</p>
|
||||
<p className="text-2xl">Call expired</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -12,7 +12,11 @@
|
|||
"build": "tsc -p tsconfig.json --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tensamin/ttp": "git+https://github.com/Tensamin/TTP.git",
|
||||
"@tensamin/ttp": "workspace:*",
|
||||
"@tensamin/shared": "workspace:*",
|
||||
"@tensamin/crypto": "workspace:*",
|
||||
"@tensamin/storage": "workspace:*",
|
||||
"@tensamin/user": "workspace:*",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"zod": "^4.3.6"
|
||||
|
|
|
|||
|
|
@ -1,14 +1,43 @@
|
|||
import * as React from "react";
|
||||
import { useCrypto } from "@tensamin/crypto/context";
|
||||
import { useStorage } from "@tensamin/storage/context";
|
||||
import { useUser } from "@tensamin/user/context";
|
||||
import { useTTP } from "@tensamin/ttp";
|
||||
import { createContext, useEffect, useContext } from "react";
|
||||
import z from "zod";
|
||||
import { toast as sonnerToast } from "sonner";
|
||||
import { message as messageSchema } from "@tensamin/shared/data";
|
||||
|
||||
export const context = React.createContext<contextType | undefined>(undefined);
|
||||
export const context = createContext<contextType | undefined>(undefined);
|
||||
|
||||
/**
|
||||
* Executes Provider.
|
||||
* @param props Parameter props.
|
||||
* @returns unknown.
|
||||
*/
|
||||
export default function Provider(props: { children: React.ReactNode }) {
|
||||
// live_messages
|
||||
const { subscribePush } = useTTP();
|
||||
const { load } = useStorage();
|
||||
const { get } = useUser();
|
||||
const { decryptText, getSharedSecret } = useCrypto();
|
||||
|
||||
useEffect(() => {
|
||||
subscribePush(async (ttpMessage) => {
|
||||
if (ttpMessage.type === "message_live") {
|
||||
const { message, sender_id } = ttpMessage.data as {
|
||||
message: z.infer<typeof messageSchema>;
|
||||
sender_id: number;
|
||||
};
|
||||
|
||||
const decryptedContent = await decryptText(
|
||||
await getSharedSecret(
|
||||
await load("private_key"),
|
||||
await get(await load("user_id")).then((data) => data.public_key),
|
||||
await get(sender_id).then((data) => data.public_key),
|
||||
),
|
||||
message.content,
|
||||
);
|
||||
|
||||
sonnerToast(decryptedContent, {
|
||||
icon: <div>A</div>,
|
||||
});
|
||||
}
|
||||
});
|
||||
}, [subscribePush, decryptText, getSharedSecret, load, get]);
|
||||
|
||||
return (
|
||||
<context.Provider value={{ test: () => {} }}>
|
||||
|
|
@ -27,7 +56,7 @@ type contextType = {
|
|||
* @returns contextType.
|
||||
*/
|
||||
export function useNotifications(): contextType {
|
||||
const ctx = React.useContext(context);
|
||||
const ctx = useContext(context);
|
||||
if (!ctx) {
|
||||
throw new Error("useNotifications must be used within a ChatProvider");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,10 +12,10 @@ const fileFromMessage = z.object({
|
|||
type: z.enum(["image", "image_top_right", "file"]),
|
||||
});
|
||||
|
||||
const message = z.object({
|
||||
export const message = z.object({
|
||||
height: z.number(),
|
||||
not_encrypted: z.boolean().optional(),
|
||||
sent_by_self: z.boolean(),
|
||||
sent_by_self: z.boolean().optional(),
|
||||
send_time: z.number(),
|
||||
content: z.base64(),
|
||||
files: z.array(fileFromMessage).optional(),
|
||||
|
|
@ -113,10 +113,9 @@ export const ttp = {
|
|||
}),
|
||||
},
|
||||
message_live: {
|
||||
request: z.object({}),
|
||||
request: z.object({}).optional(),
|
||||
response: z.object({
|
||||
sender_id: z.number(),
|
||||
send_time: z.number(),
|
||||
message,
|
||||
}),
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in a new issue