Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7a7ab3ad2e | |||
| 1a563f63a2 | |||
| be56080ba1 | |||
|
|
2a8fade420 |
30 changed files with 171 additions and 340 deletions
|
|
@ -1,6 +1,11 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://raw.githubusercontent.com/fallow-rs/fallow/main/schema.json",
|
"$schema": "https://raw.githubusercontent.com/fallow-rs/fallow/main/schema.json",
|
||||||
"entry": ["src/index.{ts,tsx,js,jsx}", "src/main.{ts,tsx,js,jsx}"],
|
"entry": [
|
||||||
|
"src/index.{ts,tsx,js,jsx}",
|
||||||
|
"src/main.{ts,tsx,js,jsx}",
|
||||||
|
"apps/tauri/render-version.ts",
|
||||||
|
"packages/**/*.test.ts"
|
||||||
|
],
|
||||||
"workspaces": {
|
"workspaces": {
|
||||||
"packages": ["packages/*", "apps/*"]
|
"packages": ["packages/*", "apps/*"]
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
"main": "dist/main/main.js",
|
"main": "dist/main/main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"clean": "rm -rf dist release",
|
"clean": "rm -rf dist release",
|
||||||
|
"lint": "eslint src",
|
||||||
"build:web": "cd ../.. && bun run build:web",
|
"build:web": "cd ../.. && bun run build:web",
|
||||||
"build": "tsc -p tsconfig.json && esbuild src/preload/preload.ts --bundle --platform=node --format=cjs --external:electron --outfile=dist/preload/preload.cjs",
|
"build": "tsc -p tsconfig.json && esbuild src/preload/preload.ts --bundle --platform=node --format=cjs --external:electron --outfile=dist/preload/preload.cjs",
|
||||||
"dev:raw": "cd ../.. && bun run build:web && cd apps/electron && bun run build && electron . --verbose",
|
"dev:raw": "cd ../.. && bun run build:web && cd apps/electron && bun run build && electron . --verbose",
|
||||||
|
|
@ -26,9 +27,7 @@
|
||||||
"validate:raw": "bun run build && bun run package:linux:raw",
|
"validate:raw": "bun run build && bun run package:linux:raw",
|
||||||
"validate": "if command -v nix >/dev/null 2>&1 && [ -z \"$IN_NIX_SHELL\" ]; then nix develop --command bun run validate:raw; else bun run validate:raw; fi"
|
"validate": "if command -v nix >/dev/null 2>&1 && [ -z \"$IN_NIX_SHELL\" ]; then nix develop --command bun run validate:raw; else bun run validate:raw; fi"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {},
|
||||||
"electron-updater": "^6.6.2"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^25.9.1",
|
"@types/node": "^25.9.1",
|
||||||
"electron": "^39.2.7",
|
"electron": "^39.2.7",
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import {
|
||||||
import { checkForUpdates } from "./updates.js";
|
import { checkForUpdates } from "./updates.js";
|
||||||
import {
|
import {
|
||||||
ipcChannels,
|
ipcChannels,
|
||||||
|
type DesktopScreenShareAudioOutput,
|
||||||
type DesktopScreenShareCapabilities,
|
type DesktopScreenShareCapabilities,
|
||||||
} from "../shared/ipc.js";
|
} from "../shared/ipc.js";
|
||||||
|
|
||||||
|
|
@ -88,7 +89,7 @@ function execJson(command: string, args: string[]) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function listAudioOutputs() {
|
async function listAudioOutputs(): Promise<DesktopScreenShareAudioOutput[]> {
|
||||||
verboseLog("listAudioOutputs", { platform: process.platform });
|
verboseLog("listAudioOutputs", { platform: process.platform });
|
||||||
|
|
||||||
if (process.platform !== "linux") return [];
|
if (process.platform !== "linux") return [];
|
||||||
|
|
@ -106,7 +107,9 @@ async function listAudioOutputs() {
|
||||||
if (!id || !name) return null;
|
if (!id || !name) return null;
|
||||||
return { id, name, isDefault: false };
|
return { id, name, isDefault: false };
|
||||||
})
|
})
|
||||||
.filter(Boolean);
|
.filter(
|
||||||
|
(output): output is DesktopScreenShareAudioOutput => output != null,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function listScreenShareSources() {
|
async function listScreenShareSources() {
|
||||||
|
|
|
||||||
|
|
@ -30,12 +30,10 @@
|
||||||
"@tauri-apps/api": "^2",
|
"@tauri-apps/api": "^2",
|
||||||
"@tauri-apps/plugin-barcode-scanner": "~2",
|
"@tauri-apps/plugin-barcode-scanner": "~2",
|
||||||
"@tauri-apps/plugin-deep-link": "~2",
|
"@tauri-apps/plugin-deep-link": "~2",
|
||||||
"@tauri-apps/plugin-opener": "^2",
|
"react-dom": "^19.2.0",
|
||||||
"@tensamin/ui": "*",
|
"@tensamin/ui": "*",
|
||||||
"@tensamin/shared": "workspace:*",
|
"@tensamin/shared": "workspace:*",
|
||||||
"lucide-react": "^1.14.0",
|
"react": "^19.2.0"
|
||||||
"react": "^19.2.0",
|
|
||||||
"react-dom": "^19.2.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tauri-apps/cli": "^2",
|
"@tauri-apps/cli": "^2",
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fontsource-variable/inter": "^5.2.8",
|
"@fontsource-variable/inter": "^5.2.8",
|
||||||
"@noble/curves": "^2.2.0",
|
|
||||||
"@tailwindcss/vite": "^4.2.4",
|
"@tailwindcss/vite": "^4.2.4",
|
||||||
"@tanstack/react-router": "^1.169.1",
|
"@tanstack/react-router": "^1.169.1",
|
||||||
"@tanstack/react-virtual": "^3.13.24",
|
"@tanstack/react-virtual": "^3.13.24",
|
||||||
|
|
@ -30,21 +29,13 @@
|
||||||
"@tensamin/ui": "*",
|
"@tensamin/ui": "*",
|
||||||
"@tensamin/user": "workspace:*",
|
"@tensamin/user": "workspace:*",
|
||||||
"@tensamin/notifications": "workspace:*",
|
"@tensamin/notifications": "workspace:*",
|
||||||
"class-variance-authority": "^0.7.1",
|
"tw-animate-css": "^1.4.0",
|
||||||
"clsx": "^2.1.1",
|
|
||||||
"comlink": "^4.4.2",
|
|
||||||
"framer-motion": "^12.38.0",
|
|
||||||
"lucide-react": "^1.14.0",
|
"lucide-react": "^1.14.0",
|
||||||
"qrcode": "^1.5.4",
|
"qrcode": "^1.5.4",
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
"shadcn": "^4.6.0",
|
|
||||||
"sonner": "^2.0.7",
|
|
||||||
"tailwind-merge": "^3.5.0",
|
|
||||||
"tailwind-scrollbar-hide": "^4.0.0",
|
|
||||||
"tailwindcss": "^4.2.4",
|
"tailwindcss": "^4.2.4",
|
||||||
"tauri-plugin-app-events-api": "^0.2.0",
|
"tailwind-scrollbar-hide": "^4.0.0",
|
||||||
"tw-animate-css": "^1.4.0",
|
|
||||||
"zod": "^4.3.6"
|
"zod": "^4.3.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ import { useIsMobile } from "@tensamin/ui";
|
||||||
import { MobileNavbar } from "./navbar";
|
import { MobileNavbar } from "./navbar";
|
||||||
|
|
||||||
import SidebarBox from "@tensamin/call/sidebarBox";
|
import SidebarBox from "@tensamin/call/sidebarBox";
|
||||||
import { useShowMobileNavbar } from "@/routes/app/layout";
|
import { useShowMobileNavbar } from "@/routes/app/useShowMobileNavbar";
|
||||||
import { Ellipsis, Check } from "lucide-react";
|
import { Ellipsis, Check } from "lucide-react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import type { User } from "@tensamin/user/context";
|
import type { User } from "@tensamin/user/context";
|
||||||
|
|
|
||||||
|
|
@ -220,7 +220,7 @@ function ContinueButton({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function BigCheckbox({
|
function BigCheckbox({
|
||||||
id,
|
id,
|
||||||
label,
|
label,
|
||||||
checked,
|
checked,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { Label } from "@tensamin/ui";
|
import { Label } from "@tensamin/ui";
|
||||||
import { Switch as UISwitch, Input as UIInput } from "@tensamin/ui";
|
import { Switch as UISwitch } from "@tensamin/ui";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
import type { Storage } from "@tensamin/shared/data";
|
import type { Storage } from "@tensamin/shared/data";
|
||||||
|
|
@ -10,28 +10,6 @@ type BooleanStorageKey = {
|
||||||
[K in keyof Storage]: Storage[K] extends boolean ? K : never;
|
[K in keyof Storage]: Storage[K] extends boolean ? K : never;
|
||||||
}[keyof Storage];
|
}[keyof Storage];
|
||||||
|
|
||||||
type StringStorageKey = {
|
|
||||||
[K in keyof Storage]: Storage[K] extends string ? K : never;
|
|
||||||
}[keyof Storage];
|
|
||||||
|
|
||||||
type NumberStorageKey = {
|
|
||||||
[K in keyof Storage]: Storage[K] extends number ? K : never;
|
|
||||||
}[keyof Storage];
|
|
||||||
|
|
||||||
type InputProps =
|
|
||||||
| {
|
|
||||||
label: string;
|
|
||||||
id: StringStorageKey;
|
|
||||||
placeholder?: string;
|
|
||||||
type?: "text";
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
label: string;
|
|
||||||
id: NumberStorageKey;
|
|
||||||
placeholder?: string;
|
|
||||||
type: "number";
|
|
||||||
};
|
|
||||||
|
|
||||||
export function Switch({
|
export function Switch({
|
||||||
label,
|
label,
|
||||||
id,
|
id,
|
||||||
|
|
@ -60,49 +38,3 @@ export function Switch({
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Input({ label, id, placeholder, type }: InputProps) {
|
|
||||||
const { save, load } = useStorage();
|
|
||||||
const [value, setValue] = useState<string | number>(
|
|
||||||
type === "number" ? 0 : "",
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
load(id).then((loadedValue) => {
|
|
||||||
if (type === "number") {
|
|
||||||
if (typeof loadedValue === "number") {
|
|
||||||
setValue(loadedValue);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof loadedValue === "string") {
|
|
||||||
setValue(loadedValue);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, [id, load, type]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col gap-1">
|
|
||||||
<Label htmlFor={id}>{label}</Label>
|
|
||||||
<UIInput
|
|
||||||
id={id}
|
|
||||||
type={type || "text"}
|
|
||||||
placeholder={placeholder}
|
|
||||||
value={value}
|
|
||||||
onChange={(e) => {
|
|
||||||
if (type === "number") {
|
|
||||||
const nextValue = Number(e.target.value);
|
|
||||||
setValue(nextValue);
|
|
||||||
save(id, nextValue);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const nextValue = e.target.value;
|
|
||||||
setValue(nextValue);
|
|
||||||
save(id, nextValue);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,12 @@ import { type ReactNode } from "react";
|
||||||
|
|
||||||
import Sidebar from "@/components/sidebar";
|
import Sidebar from "@/components/sidebar";
|
||||||
import Navbar, { MobileNavbar } from "@/components/navbar";
|
import Navbar, { MobileNavbar } from "@/components/navbar";
|
||||||
|
import { useShowMobileNavbar } from "./useShowMobileNavbar";
|
||||||
|
|
||||||
import { useIsMobile, cn, SidebarProvider } from "@tensamin/ui";
|
import { useIsMobile, cn, SidebarProvider } from "@tensamin/ui";
|
||||||
|
|
||||||
import { isTauri } from "@tauri-apps/api/core";
|
import { isTauri } from "@tauri-apps/api/core";
|
||||||
import { useLocation, useMatches } from "@tanstack/react-router";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Executes Layout.
|
|
||||||
* @param props Parameter props.
|
|
||||||
* @returns unknown.
|
|
||||||
*/
|
|
||||||
export default function Layout({ children }: { children: ReactNode }) {
|
export default function Layout({ children }: { children: ReactNode }) {
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
const showMobileNavbar = useShowMobileNavbar();
|
const showMobileNavbar = useShowMobileNavbar();
|
||||||
|
|
@ -47,17 +42,3 @@ export default function Layout({ children }: { children: ReactNode }) {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useShowMobileNavbar(): boolean {
|
|
||||||
const matches = useMatches();
|
|
||||||
const location = useLocation();
|
|
||||||
|
|
||||||
const value = matches.some(
|
|
||||||
(match) =>
|
|
||||||
match.pathname === location.pathname &&
|
|
||||||
// @ts-expect-error Stuff
|
|
||||||
match.staticData?.showMobileNavbar === true,
|
|
||||||
);
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
14
apps/web/src/routes/app/useShowMobileNavbar.ts
Normal file
14
apps/web/src/routes/app/useShowMobileNavbar.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { useLocation, useMatches } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
export function useShowMobileNavbar(): boolean {
|
||||||
|
const matches = useMatches();
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
|
return matches.some(
|
||||||
|
(match) =>
|
||||||
|
match.pathname === location.pathname &&
|
||||||
|
// Router staticData is app-defined and not typed by the route matcher here.
|
||||||
|
// @ts-expect-error App route metadata
|
||||||
|
match.staticData?.showMobileNavbar === true,
|
||||||
|
);
|
||||||
|
}
|
||||||
50
bun.lock
50
bun.lock
|
|
@ -28,9 +28,6 @@
|
||||||
"apps/electron": {
|
"apps/electron": {
|
||||||
"name": "@tensamin/electron",
|
"name": "@tensamin/electron",
|
||||||
"version": "0.0.3",
|
"version": "0.0.3",
|
||||||
"dependencies": {
|
|
||||||
"electron-updater": "^6.6.2",
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^25.9.1",
|
"@types/node": "^25.9.1",
|
||||||
"electron": "^39.2.7",
|
"electron": "^39.2.7",
|
||||||
|
|
@ -46,10 +43,8 @@
|
||||||
"@tauri-apps/api": "^2",
|
"@tauri-apps/api": "^2",
|
||||||
"@tauri-apps/plugin-barcode-scanner": "~2",
|
"@tauri-apps/plugin-barcode-scanner": "~2",
|
||||||
"@tauri-apps/plugin-deep-link": "~2",
|
"@tauri-apps/plugin-deep-link": "~2",
|
||||||
"@tauri-apps/plugin-opener": "^2",
|
|
||||||
"@tensamin/shared": "workspace:*",
|
"@tensamin/shared": "workspace:*",
|
||||||
"@tensamin/ui": "*",
|
"@tensamin/ui": "*",
|
||||||
"lucide-react": "^1.14.0",
|
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
},
|
},
|
||||||
|
|
@ -63,7 +58,6 @@
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fontsource-variable/inter": "^5.2.8",
|
"@fontsource-variable/inter": "^5.2.8",
|
||||||
"@noble/curves": "^2.2.0",
|
|
||||||
"@tailwindcss/vite": "^4.2.4",
|
"@tailwindcss/vite": "^4.2.4",
|
||||||
"@tanstack/react-router": "^1.169.1",
|
"@tanstack/react-router": "^1.169.1",
|
||||||
"@tanstack/react-virtual": "^3.13.24",
|
"@tanstack/react-virtual": "^3.13.24",
|
||||||
|
|
@ -80,20 +74,12 @@
|
||||||
"@tensamin/ttp": "workspace:*",
|
"@tensamin/ttp": "workspace:*",
|
||||||
"@tensamin/ui": "*",
|
"@tensamin/ui": "*",
|
||||||
"@tensamin/user": "workspace:*",
|
"@tensamin/user": "workspace:*",
|
||||||
"class-variance-authority": "^0.7.1",
|
|
||||||
"clsx": "^2.1.1",
|
|
||||||
"comlink": "^4.4.2",
|
|
||||||
"framer-motion": "^12.38.0",
|
|
||||||
"lucide-react": "^1.14.0",
|
"lucide-react": "^1.14.0",
|
||||||
"qrcode": "^1.5.4",
|
"qrcode": "^1.5.4",
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
"shadcn": "^4.6.0",
|
|
||||||
"sonner": "^2.0.7",
|
|
||||||
"tailwind-merge": "^3.5.0",
|
|
||||||
"tailwind-scrollbar-hide": "^4.0.0",
|
"tailwind-scrollbar-hide": "^4.0.0",
|
||||||
"tailwindcss": "^4.2.4",
|
"tailwindcss": "^4.2.4",
|
||||||
"tauri-plugin-app-events-api": "^0.2.0",
|
|
||||||
"tw-animate-css": "^1.4.0",
|
"tw-animate-css": "^1.4.0",
|
||||||
"zod": "^4.3.6",
|
"zod": "^4.3.6",
|
||||||
},
|
},
|
||||||
|
|
@ -116,15 +102,11 @@
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@livekit/components-react": "^2.9.20",
|
"@livekit/components-react": "^2.9.20",
|
||||||
"@tanstack/react-query": "^5.100.7",
|
|
||||||
"@tanstack/react-router": "^1.169.1",
|
"@tanstack/react-router": "^1.169.1",
|
||||||
"@tanstack/react-virtual": "^3.13.24",
|
|
||||||
"@tauri-apps/api": "^2",
|
"@tauri-apps/api": "^2",
|
||||||
"@tensamin/crypto": "workspace:*",
|
"@tensamin/crypto": "workspace:*",
|
||||||
"@tensamin/markdown": "workspace:*",
|
|
||||||
"@tensamin/shared": "workspace:*",
|
"@tensamin/shared": "workspace:*",
|
||||||
"@tensamin/storage": "workspace:*",
|
"@tensamin/storage": "workspace:*",
|
||||||
"@tensamin/tauri": "workspace:*",
|
|
||||||
"@tensamin/ttp": "workspace:*",
|
"@tensamin/ttp": "workspace:*",
|
||||||
"@tensamin/ui": "*",
|
"@tensamin/ui": "*",
|
||||||
"@tensamin/user": "workspace:*",
|
"@tensamin/user": "workspace:*",
|
||||||
|
|
@ -134,7 +116,6 @@
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
"recharts": "^3.8.1",
|
"recharts": "^3.8.1",
|
||||||
"sonner": "^2.0.7",
|
|
||||||
"zod": "^4.3.6",
|
"zod": "^4.3.6",
|
||||||
"zustand": "^5.0.8",
|
"zustand": "^5.0.8",
|
||||||
},
|
},
|
||||||
|
|
@ -177,7 +158,6 @@
|
||||||
"@codemirror/lang-markdown": "^6.5.0",
|
"@codemirror/lang-markdown": "^6.5.0",
|
||||||
"@codemirror/state": "^6.5.4",
|
"@codemirror/state": "^6.5.4",
|
||||||
"@codemirror/view": "^6.41.1",
|
"@codemirror/view": "^6.41.1",
|
||||||
"@tensamin/ui": "*",
|
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
},
|
},
|
||||||
|
|
@ -193,9 +173,11 @@
|
||||||
"@tensamin/shared": "workspace:*",
|
"@tensamin/shared": "workspace:*",
|
||||||
"@tensamin/storage": "workspace:*",
|
"@tensamin/storage": "workspace:*",
|
||||||
"@tensamin/ttp": "workspace:*",
|
"@tensamin/ttp": "workspace:*",
|
||||||
|
"@tensamin/ui": "*",
|
||||||
"@tensamin/user": "workspace:*",
|
"@tensamin/user": "workspace:*",
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
|
"sonner": "^2.0.7",
|
||||||
"zod": "^4.3.6",
|
"zod": "^4.3.6",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -207,7 +189,6 @@
|
||||||
"lucide-react": "^1.14.0",
|
"lucide-react": "^1.14.0",
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
"sonner": "^2.0.7",
|
|
||||||
"zod": "^4.3.6",
|
"zod": "^4.3.6",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -238,8 +219,6 @@
|
||||||
"lucide-react": "^1.8.0",
|
"lucide-react": "^1.8.0",
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
"sonner": "^2.0.7",
|
|
||||||
"zod": "^4.3.6",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"packages/ttp": {
|
"packages/ttp": {
|
||||||
|
|
@ -256,7 +235,6 @@
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
"tauri-plugin-app-events-api": "^0.2.0",
|
"tauri-plugin-app-events-api": "^0.2.0",
|
||||||
"zod": "^4.3.6",
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"eslint": "^10.0.3",
|
"eslint": "^10.0.3",
|
||||||
|
|
@ -740,8 +718,6 @@
|
||||||
|
|
||||||
"@tauri-apps/plugin-deep-link": ["@tauri-apps/plugin-deep-link@2.4.9", "", { "dependencies": { "@tauri-apps/api": "^2.11.0" } }, "sha512-u0SKOUHnJ1wqeqXsDFq2+kASCBj9xxbG0g9XZWPy9SOmU4wXtp6b/wiYpm6oH6/5fBTQsLqnLhIvqLBRpgHJlA=="],
|
"@tauri-apps/plugin-deep-link": ["@tauri-apps/plugin-deep-link@2.4.9", "", { "dependencies": { "@tauri-apps/api": "^2.11.0" } }, "sha512-u0SKOUHnJ1wqeqXsDFq2+kASCBj9xxbG0g9XZWPy9SOmU4wXtp6b/wiYpm6oH6/5fBTQsLqnLhIvqLBRpgHJlA=="],
|
||||||
|
|
||||||
"@tauri-apps/plugin-opener": ["@tauri-apps/plugin-opener@2.5.4", "", { "dependencies": { "@tauri-apps/api": "^2.11.0" } }, "sha512-1HnPkb+AmgO29HBazm4uPLKB+r7zzcTBW1d0fyYp1uP+jwtpoiNDGKMMzz58SFp49nOIrxdE3aUJtT57lfO9CQ=="],
|
|
||||||
|
|
||||||
"@tensamin/call": ["@tensamin/call@workspace:packages/call"],
|
"@tensamin/call": ["@tensamin/call@workspace:packages/call"],
|
||||||
|
|
||||||
"@tensamin/chat": ["@tensamin/chat@workspace:packages/chat"],
|
"@tensamin/chat": ["@tensamin/chat@workspace:packages/chat"],
|
||||||
|
|
@ -1122,8 +1098,6 @@
|
||||||
|
|
||||||
"electron-to-chromium": ["electron-to-chromium@1.5.349", "", {}, "sha512-QsWVGyRuY07Aqb234QytTfwd5d9AJlfNIQ5wIOl1L+PZDzI9d9+Fn0FRale/QYlFxt/bUnB0/nLd1jFPGxGK1A=="],
|
"electron-to-chromium": ["electron-to-chromium@1.5.349", "", {}, "sha512-QsWVGyRuY07Aqb234QytTfwd5d9AJlfNIQ5wIOl1L+PZDzI9d9+Fn0FRale/QYlFxt/bUnB0/nLd1jFPGxGK1A=="],
|
||||||
|
|
||||||
"electron-updater": ["electron-updater@6.8.3", "", { "dependencies": { "builder-util-runtime": "9.5.1", "fs-extra": "^10.1.0", "js-yaml": "^4.1.0", "lazy-val": "^1.0.5", "lodash.escaperegexp": "^4.1.2", "lodash.isequal": "^4.5.0", "semver": "~7.7.3", "tiny-typed-emitter": "^2.1.0" } }, "sha512-Z6sgw3jgbikWKXei1ENdqFOxBP0WlXg3TtKfz0rgw2vIZFJUyI4pD7ZN7jrkm7EoMK+tcm/qTnPUdqfZukBlBQ=="],
|
|
||||||
|
|
||||||
"electron-winstaller": ["electron-winstaller@5.4.0", "", { "dependencies": { "@electron/asar": "^3.2.1", "debug": "^4.1.1", "fs-extra": "^7.0.1", "lodash": "^4.17.21", "temp": "^0.9.0" }, "optionalDependencies": { "@electron/windows-sign": "^1.1.2" } }, "sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg=="],
|
"electron-winstaller": ["electron-winstaller@5.4.0", "", { "dependencies": { "@electron/asar": "^3.2.1", "debug": "^4.1.1", "fs-extra": "^7.0.1", "lodash": "^4.17.21", "temp": "^0.9.0" }, "optionalDependencies": { "@electron/windows-sign": "^1.1.2" } }, "sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg=="],
|
||||||
|
|
||||||
"embla-carousel": ["embla-carousel@8.6.0", "", {}, "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA=="],
|
"embla-carousel": ["embla-carousel@8.6.0", "", {}, "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA=="],
|
||||||
|
|
@ -1256,8 +1230,6 @@
|
||||||
|
|
||||||
"forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
|
"forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
|
||||||
|
|
||||||
"framer-motion": ["framer-motion@12.38.0", "", { "dependencies": { "motion-dom": "^12.38.0", "motion-utils": "^12.36.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g=="],
|
|
||||||
|
|
||||||
"fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="],
|
"fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="],
|
||||||
|
|
||||||
"fs-extra": ["fs-extra@10.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ=="],
|
"fs-extra": ["fs-extra@10.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ=="],
|
||||||
|
|
@ -1476,10 +1448,6 @@
|
||||||
|
|
||||||
"lodash.debounce": ["lodash.debounce@4.0.8", "", {}, "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="],
|
"lodash.debounce": ["lodash.debounce@4.0.8", "", {}, "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="],
|
||||||
|
|
||||||
"lodash.escaperegexp": ["lodash.escaperegexp@4.1.2", "", {}, "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw=="],
|
|
||||||
|
|
||||||
"lodash.isequal": ["lodash.isequal@4.5.0", "", {}, "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ=="],
|
|
||||||
|
|
||||||
"log-symbols": ["log-symbols@6.0.0", "", { "dependencies": { "chalk": "^5.3.0", "is-unicode-supported": "^1.3.0" } }, "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw=="],
|
"log-symbols": ["log-symbols@6.0.0", "", { "dependencies": { "chalk": "^5.3.0", "is-unicode-supported": "^1.3.0" } }, "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw=="],
|
||||||
|
|
||||||
"loglevel": ["loglevel@1.9.2", "", {}, "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg=="],
|
"loglevel": ["loglevel@1.9.2", "", {}, "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg=="],
|
||||||
|
|
@ -1528,10 +1496,6 @@
|
||||||
|
|
||||||
"mkdirp": ["mkdirp@0.5.6", "", { "dependencies": { "minimist": "^1.2.6" }, "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw=="],
|
"mkdirp": ["mkdirp@0.5.6", "", { "dependencies": { "minimist": "^1.2.6" }, "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw=="],
|
||||||
|
|
||||||
"motion-dom": ["motion-dom@12.38.0", "", { "dependencies": { "motion-utils": "^12.36.0" } }, "sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA=="],
|
|
||||||
|
|
||||||
"motion-utils": ["motion-utils@12.36.0", "", {}, "sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg=="],
|
|
||||||
|
|
||||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||||
|
|
||||||
"msw": ["msw@2.14.2", "", { "dependencies": { "@inquirer/confirm": "^6.0.11", "@mswjs/interceptors": "^0.41.3", "@open-draft/deferred-promise": "^3.0.0", "@types/statuses": "^2.0.6", "cookie": "^1.1.1", "graphql": "^16.13.2", "headers-polyfill": "^5.0.1", "is-node-process": "^1.2.0", "outvariant": "^1.4.3", "path-to-regexp": "^6.3.0", "picocolors": "^1.1.1", "rettime": "^0.11.7", "statuses": "^2.0.2", "strict-event-emitter": "^0.5.1", "tough-cookie": "^6.0.1", "type-fest": "^5.5.0", "until-async": "^3.0.2", "yargs": "^17.7.2" }, "peerDependencies": { "typescript": ">= 4.8.x" }, "optionalPeers": ["typescript"], "bin": { "msw": "cli/index.js" } }, "sha512-D2bTe0tpuf9nw4DA39wFaqUD/hRPKj0DKpo2lAqu+A47Ifg4+h0hbfn6QxVOsiUY2uhgEN6TTpGSHDsc+ysYNg=="],
|
"msw": ["msw@2.14.2", "", { "dependencies": { "@inquirer/confirm": "^6.0.11", "@mswjs/interceptors": "^0.41.3", "@open-draft/deferred-promise": "^3.0.0", "@types/statuses": "^2.0.6", "cookie": "^1.1.1", "graphql": "^16.13.2", "headers-polyfill": "^5.0.1", "is-node-process": "^1.2.0", "outvariant": "^1.4.3", "path-to-regexp": "^6.3.0", "picocolors": "^1.1.1", "rettime": "^0.11.7", "statuses": "^2.0.2", "strict-event-emitter": "^0.5.1", "tough-cookie": "^6.0.1", "type-fest": "^5.5.0", "until-async": "^3.0.2", "yargs": "^17.7.2" }, "peerDependencies": { "typescript": ">= 4.8.x" }, "optionalPeers": ["typescript"], "bin": { "msw": "cli/index.js" } }, "sha512-D2bTe0tpuf9nw4DA39wFaqUD/hRPKj0DKpo2lAqu+A47Ifg4+h0hbfn6QxVOsiUY2uhgEN6TTpGSHDsc+ysYNg=="],
|
||||||
|
|
@ -1770,7 +1734,7 @@
|
||||||
|
|
||||||
"setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],
|
"setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],
|
||||||
|
|
||||||
"shadcn": ["shadcn@4.6.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/parser": "^7.28.0", "@babel/plugin-transform-typescript": "^7.28.0", "@babel/preset-typescript": "^7.27.1", "@dotenvx/dotenvx": "^1.48.4", "@modelcontextprotocol/sdk": "^1.26.0", "@types/validate-npm-package-name": "^4.0.2", "browserslist": "^4.26.2", "commander": "^14.0.0", "cosmiconfig": "^9.0.0", "dedent": "^1.6.0", "deepmerge": "^4.3.1", "diff": "^8.0.2", "execa": "^9.6.0", "fast-glob": "^3.3.3", "fs-extra": "^11.3.1", "fuzzysort": "^3.1.0", "https-proxy-agent": "^7.0.6", "kleur": "^4.1.5", "msw": "^2.10.4", "node-fetch": "^3.3.2", "open": "^11.0.0", "ora": "^8.2.0", "postcss": "^8.5.6", "postcss-selector-parser": "^7.1.0", "prompts": "^2.4.2", "recast": "^0.23.11", "stringify-object": "^5.0.0", "tailwind-merge": "^3.0.1", "ts-morph": "^26.0.0", "tsconfig-paths": "^4.2.0", "validate-npm-package-name": "^7.0.1", "zod": "^3.24.1", "zod-to-json-schema": "^3.24.6" }, "bin": { "shadcn": "dist/index.js" } }, "sha512-4XeMwFf8ZZxmqQQp+U+Nsq2M+cY4Da8Joo/EaMdHVc4uVuWSTJoeidlZ3gDjyxXCjYB1FLcxYwR4lYQAH8emOg=="],
|
"shadcn": ["shadcn@3.8.5", "", { "dependencies": { "@antfu/ni": "^25.0.0", "@babel/core": "^7.28.0", "@babel/parser": "^7.28.0", "@babel/plugin-transform-typescript": "^7.28.0", "@babel/preset-typescript": "^7.27.1", "@dotenvx/dotenvx": "^1.48.4", "@modelcontextprotocol/sdk": "^1.26.0", "@types/validate-npm-package-name": "^4.0.2", "browserslist": "^4.26.2", "commander": "^14.0.0", "cosmiconfig": "^9.0.0", "dedent": "^1.6.0", "deepmerge": "^4.3.1", "diff": "^8.0.2", "execa": "^9.6.0", "fast-glob": "^3.3.3", "fs-extra": "^11.3.1", "fuzzysort": "^3.1.0", "https-proxy-agent": "^7.0.6", "kleur": "^4.1.5", "msw": "^2.10.4", "node-fetch": "^3.3.2", "open": "^11.0.0", "ora": "^8.2.0", "postcss": "^8.5.6", "postcss-selector-parser": "^7.1.0", "prompts": "^2.4.2", "recast": "^0.23.11", "stringify-object": "^5.0.0", "tailwind-merge": "^3.0.1", "ts-morph": "^26.0.0", "tsconfig-paths": "^4.2.0", "validate-npm-package-name": "^7.0.1", "zod": "^3.24.1", "zod-to-json-schema": "^3.24.6" }, "bin": { "shadcn": "dist/index.js" } }, "sha512-jPRx44e+eyeV7xwY3BLJXcfrks00+M0h5BGB9l6DdcBW4BpAj4x3lVmVy0TXPEs2iHEisxejr62sZAAw6B1EVA=="],
|
||||||
|
|
||||||
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
|
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
|
||||||
|
|
||||||
|
|
@ -1850,8 +1814,6 @@
|
||||||
|
|
||||||
"tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="],
|
"tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="],
|
||||||
|
|
||||||
"tiny-typed-emitter": ["tiny-typed-emitter@2.1.0", "", {}, "sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA=="],
|
|
||||||
|
|
||||||
"tinyexec": ["tinyexec@1.1.2", "", {}, "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA=="],
|
"tinyexec": ["tinyexec@1.1.2", "", {}, "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA=="],
|
||||||
|
|
||||||
"tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="],
|
"tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="],
|
||||||
|
|
@ -2054,8 +2016,6 @@
|
||||||
|
|
||||||
"@tensamin/ui/recharts": ["recharts@3.8.0", "", { "dependencies": { "@reduxjs/toolkit": "^1.9.0 || 2.x.x", "clsx": "^2.1.1", "decimal.js-light": "^2.5.1", "es-toolkit": "^1.39.3", "eventemitter3": "^5.0.1", "immer": "^10.1.1", "react-redux": "8.x.x || 9.x.x", "reselect": "5.1.1", "tiny-invariant": "^1.3.3", "use-sync-external-store": "^1.2.2", "victory-vendor": "^37.0.2" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Z/m38DX3L73ExO4Tpc9/iZWHmHnlzWG4njQbxsF5aSjwqmHNDDIm0rdEBArkwsBvR8U6EirlEHiQNYWCVh9sGQ=="],
|
"@tensamin/ui/recharts": ["recharts@3.8.0", "", { "dependencies": { "@reduxjs/toolkit": "^1.9.0 || 2.x.x", "clsx": "^2.1.1", "decimal.js-light": "^2.5.1", "es-toolkit": "^1.39.3", "eventemitter3": "^5.0.1", "immer": "^10.1.1", "react-redux": "8.x.x || 9.x.x", "reselect": "5.1.1", "tiny-invariant": "^1.3.3", "use-sync-external-store": "^1.2.2", "victory-vendor": "^37.0.2" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Z/m38DX3L73ExO4Tpc9/iZWHmHnlzWG4njQbxsF5aSjwqmHNDDIm0rdEBArkwsBvR8U6EirlEHiQNYWCVh9sGQ=="],
|
||||||
|
|
||||||
"@tensamin/ui/shadcn": ["shadcn@3.8.5", "", { "dependencies": { "@antfu/ni": "^25.0.0", "@babel/core": "^7.28.0", "@babel/parser": "^7.28.0", "@babel/plugin-transform-typescript": "^7.28.0", "@babel/preset-typescript": "^7.27.1", "@dotenvx/dotenvx": "^1.48.4", "@modelcontextprotocol/sdk": "^1.26.0", "@types/validate-npm-package-name": "^4.0.2", "browserslist": "^4.26.2", "commander": "^14.0.0", "cosmiconfig": "^9.0.0", "dedent": "^1.6.0", "deepmerge": "^4.3.1", "diff": "^8.0.2", "execa": "^9.6.0", "fast-glob": "^3.3.3", "fs-extra": "^11.3.1", "fuzzysort": "^3.1.0", "https-proxy-agent": "^7.0.6", "kleur": "^4.1.5", "msw": "^2.10.4", "node-fetch": "^3.3.2", "open": "^11.0.0", "ora": "^8.2.0", "postcss": "^8.5.6", "postcss-selector-parser": "^7.1.0", "prompts": "^2.4.2", "recast": "^0.23.11", "stringify-object": "^5.0.0", "tailwind-merge": "^3.0.1", "ts-morph": "^26.0.0", "tsconfig-paths": "^4.2.0", "validate-npm-package-name": "^7.0.1", "zod": "^3.24.1", "zod-to-json-schema": "^3.24.6" }, "bin": { "shadcn": "dist/index.js" } }, "sha512-jPRx44e+eyeV7xwY3BLJXcfrks00+M0h5BGB9l6DdcBW4BpAj4x3lVmVy0TXPEs2iHEisxejr62sZAAw6B1EVA=="],
|
|
||||||
|
|
||||||
"@types/cacheable-request/@types/node": ["@types/node@25.9.1", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg=="],
|
"@types/cacheable-request/@types/node": ["@types/node@25.9.1", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg=="],
|
||||||
|
|
||||||
"@types/fs-extra/@types/node": ["@types/node@25.9.1", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg=="],
|
"@types/fs-extra/@types/node": ["@types/node@25.9.1", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg=="],
|
||||||
|
|
@ -2180,10 +2140,6 @@
|
||||||
|
|
||||||
"@tensamin/tauri/@types/node/undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="],
|
"@tensamin/tauri/@types/node/undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="],
|
||||||
|
|
||||||
"@tensamin/ui/shadcn/fs-extra": ["fs-extra@11.3.4", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA=="],
|
|
||||||
|
|
||||||
"@tensamin/ui/shadcn/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
|
||||||
|
|
||||||
"@types/cacheable-request/@types/node/undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="],
|
"@types/cacheable-request/@types/node/undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="],
|
||||||
|
|
||||||
"@types/fs-extra/@types/node/undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="],
|
"@types/fs-extra/@types/node/undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="],
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@
|
||||||
outputs = {nixpkgs, ...}: let
|
outputs = {nixpkgs, ...}: let
|
||||||
systems = ["x86_64-linux"];
|
systems = ["x86_64-linux"];
|
||||||
forAllSystems = nixpkgs.lib.genAttrs systems;
|
forAllSystems = nixpkgs.lib.genAttrs systems;
|
||||||
version = "0.0.7";
|
version = "0.0.8";
|
||||||
x86_64DebHash = "sha256-gRkUEx1T7mrCgFEVp0X/ZiaGrKWzO+cCvv33DKCkB+o=";
|
x86_64DebHash = "sha256-xC8YHVk+JpeMvXHB1dZihYLFyzFDXne4bkV6TZiOFrE=";
|
||||||
forgejoBaseUrl = "https://git.methanium.net/tensamin/client/releases/download/${version}";
|
forgejoBaseUrl = "https://git.methanium.net/tensamin/client/releases/download/${version}";
|
||||||
in {
|
in {
|
||||||
packages = forAllSystems (system: let
|
packages = forAllSystems (system: let
|
||||||
|
|
|
||||||
|
|
@ -16,15 +16,11 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@livekit/components-react": "^2.9.20",
|
"@livekit/components-react": "^2.9.20",
|
||||||
"@tanstack/react-query": "^5.100.7",
|
|
||||||
"@tanstack/react-router": "^1.169.1",
|
"@tanstack/react-router": "^1.169.1",
|
||||||
"@tanstack/react-virtual": "^3.13.24",
|
|
||||||
"@tauri-apps/api": "^2",
|
"@tauri-apps/api": "^2",
|
||||||
"@tensamin/crypto": "workspace:*",
|
"@tensamin/crypto": "workspace:*",
|
||||||
"@tensamin/markdown": "workspace:*",
|
|
||||||
"@tensamin/shared": "workspace:*",
|
"@tensamin/shared": "workspace:*",
|
||||||
"@tensamin/storage": "workspace:*",
|
"@tensamin/storage": "workspace:*",
|
||||||
"@tensamin/tauri": "workspace:*",
|
|
||||||
"@tensamin/ttp": "workspace:*",
|
"@tensamin/ttp": "workspace:*",
|
||||||
"@tensamin/ui": "*",
|
"@tensamin/ui": "*",
|
||||||
"@tensamin/user": "workspace:*",
|
"@tensamin/user": "workspace:*",
|
||||||
|
|
@ -34,7 +30,6 @@
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
"recharts": "^3.8.1",
|
"recharts": "^3.8.1",
|
||||||
"sonner": "^2.0.7",
|
|
||||||
"zod": "^4.3.6",
|
"zod": "^4.3.6",
|
||||||
"zustand": "^5.0.8"
|
"zustand": "^5.0.8"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,14 @@
|
||||||
import { Button, Tooltip, TooltipContent, TooltipTrigger } from "@tensamin/ui";
|
import { Button } from "@tensamin/ui";
|
||||||
import { toggleDeaf, useCall } from "../../store";
|
import { toggleDeaf, useCall } from "../../store";
|
||||||
import { HeadphoneOff, Headphones } from "lucide-react";
|
import { HeadphoneOff, Headphones } from "lucide-react";
|
||||||
|
import { type CallButtonProps, iconScale, withTooltip } from "./tooltipButton";
|
||||||
|
|
||||||
export default function DeafButton({
|
export default function DeafButton({
|
||||||
className,
|
className,
|
||||||
iconSize,
|
iconSize,
|
||||||
tooltip,
|
tooltip,
|
||||||
portalContainer,
|
portalContainer,
|
||||||
}: {
|
}: CallButtonProps) {
|
||||||
className?: string;
|
|
||||||
iconSize?: number;
|
|
||||||
tooltip?: string;
|
|
||||||
portalContainer?: HTMLElement;
|
|
||||||
}) {
|
|
||||||
const deaf = useCall((state) => state.deaf);
|
const deaf = useCall((state) => state.deaf);
|
||||||
|
|
||||||
const button = (
|
const button = (
|
||||||
|
|
@ -22,27 +18,12 @@ export default function DeafButton({
|
||||||
className={className}
|
className={className}
|
||||||
>
|
>
|
||||||
{deaf ? (
|
{deaf ? (
|
||||||
<HeadphoneOff
|
<HeadphoneOff style={iconScale(iconSize)} />
|
||||||
style={{ scale: (iconSize ? iconSize + 100 : 100) + "%" }}
|
|
||||||
/>
|
|
||||||
) : (
|
) : (
|
||||||
<Headphones
|
<Headphones style={iconScale(iconSize)} />
|
||||||
style={{ scale: (iconSize ? iconSize + 100 : 100) + "%" }}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!tooltip) {
|
return withTooltip(button, tooltip, portalContainer);
|
||||||
return button;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger render={button} />
|
|
||||||
<TooltipContent portalProps={{ container: portalContainer }}>
|
|
||||||
{tooltip}
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,14 @@
|
||||||
import { LeaveIcon } from "@livekit/components-react";
|
import { LeaveIcon } from "@livekit/components-react";
|
||||||
import { Button, Tooltip, TooltipContent, TooltipTrigger } from "@tensamin/ui";
|
import { Button } from "@tensamin/ui";
|
||||||
import { disconnect, useCall } from "../../store";
|
import { disconnect, useCall } from "../../store";
|
||||||
|
import { type CallButtonProps, iconScale, withTooltip } from "./tooltipButton";
|
||||||
|
|
||||||
export default function LeaveButton({
|
export default function LeaveButton({
|
||||||
className,
|
className,
|
||||||
iconSize,
|
iconSize,
|
||||||
tooltip,
|
tooltip,
|
||||||
portalContainer,
|
portalContainer,
|
||||||
}: {
|
}: CallButtonProps) {
|
||||||
className?: string;
|
|
||||||
iconSize?: number;
|
|
||||||
tooltip?: string;
|
|
||||||
portalContainer?: HTMLElement;
|
|
||||||
}) {
|
|
||||||
const state = useCall((store) => store.state);
|
const state = useCall((store) => store.state);
|
||||||
|
|
||||||
const button = (
|
const button = (
|
||||||
|
|
@ -22,20 +18,9 @@ export default function LeaveButton({
|
||||||
disabled={state === "closing" || state === "closed"}
|
disabled={state === "closing" || state === "closed"}
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
>
|
>
|
||||||
<LeaveIcon style={{ scale: (iconSize ? iconSize + 100 : 100) + "%" }} />
|
<LeaveIcon style={iconScale(iconSize)} />
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!tooltip) {
|
return withTooltip(button, tooltip, portalContainer);
|
||||||
return button;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger render={button} />
|
|
||||||
<TooltipContent portalProps={{ container: portalContainer }}>
|
|
||||||
{tooltip}
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,14 @@
|
||||||
import { Button, Tooltip, TooltipContent, TooltipTrigger } from "@tensamin/ui";
|
import { Button } from "@tensamin/ui";
|
||||||
import { toggleMute, useCall } from "../../store";
|
import { toggleMute, useCall } from "../../store";
|
||||||
import { Mic, MicOff } from "lucide-react";
|
import { Mic, MicOff } from "lucide-react";
|
||||||
|
import { type CallButtonProps, iconScale, withTooltip } from "./tooltipButton";
|
||||||
|
|
||||||
export default function MuteButton({
|
export default function MuteButton({
|
||||||
className,
|
className,
|
||||||
iconSize,
|
iconSize,
|
||||||
tooltip,
|
tooltip,
|
||||||
portalContainer,
|
portalContainer,
|
||||||
}: {
|
}: CallButtonProps) {
|
||||||
className?: string;
|
|
||||||
iconSize?: number;
|
|
||||||
tooltip?: string;
|
|
||||||
portalContainer?: HTMLElement;
|
|
||||||
}) {
|
|
||||||
const micEnabled = useCall((state) => state.micEnabled);
|
const micEnabled = useCall((state) => state.micEnabled);
|
||||||
|
|
||||||
const button = (
|
const button = (
|
||||||
|
|
@ -22,23 +18,12 @@ export default function MuteButton({
|
||||||
onClick={() => void toggleMute()}
|
onClick={() => void toggleMute()}
|
||||||
>
|
>
|
||||||
{micEnabled ? (
|
{micEnabled ? (
|
||||||
<Mic style={{ scale: (iconSize ? iconSize + 100 : 100) + "%" }} />
|
<Mic style={iconScale(iconSize)} />
|
||||||
) : (
|
) : (
|
||||||
<MicOff style={{ scale: (iconSize ? iconSize + 100 : 100) + "%" }} />
|
<MicOff style={iconScale(iconSize)} />
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!tooltip) {
|
return withTooltip(button, tooltip, portalContainer);
|
||||||
return button;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger render={button} />
|
|
||||||
<TooltipContent portalProps={{ container: portalContainer }}>
|
|
||||||
{tooltip}
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
32
packages/call/src/components/buttons/tooltipButton.tsx
Normal file
32
packages/call/src/components/buttons/tooltipButton.tsx
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
import { type ReactElement } from "react";
|
||||||
|
import { Tooltip, TooltipContent, TooltipTrigger } from "@tensamin/ui";
|
||||||
|
|
||||||
|
export type CallButtonProps = {
|
||||||
|
className?: string;
|
||||||
|
iconSize?: number;
|
||||||
|
tooltip?: string;
|
||||||
|
portalContainer?: HTMLElement;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function iconScale(iconSize?: number) {
|
||||||
|
return { scale: (iconSize ? iconSize + 100 : 100) + "%" };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function withTooltip(
|
||||||
|
button: ReactElement,
|
||||||
|
tooltip?: string,
|
||||||
|
portalContainer?: HTMLElement,
|
||||||
|
) {
|
||||||
|
if (!tooltip) {
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger render={button} />
|
||||||
|
<TooltipContent portalProps={{ container: portalContainer }}>
|
||||||
|
{tooltip}
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -18,7 +18,7 @@ import {
|
||||||
import { Track, type Participant } from "livekit-client";
|
import { Track, type Participant } from "livekit-client";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { useUser, type User } from "@tensamin/user/context";
|
import { useUser, type User } from "@tensamin/user/context";
|
||||||
import { useIsSpeaking } from "../../speakingIndicator";
|
import { useIsSpeaking } from "../../speakingState";
|
||||||
import VideoViewer from "../videoViewer";
|
import VideoViewer from "../videoViewer";
|
||||||
import { HeadphoneOff, MicOff, Monitor, Plus, Shield } from "lucide-react";
|
import { HeadphoneOff, MicOff, Monitor, Plus, Shield } from "lucide-react";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,10 @@
|
||||||
import { log } from "@tensamin/shared/log";
|
import { log } from "@tensamin/shared/log";
|
||||||
import { useCall } from "./store";
|
import {
|
||||||
|
clearSpeakingParticipants,
|
||||||
|
removeSpeakingParticipant,
|
||||||
|
setMicGated,
|
||||||
|
updateSpeakingParticipants,
|
||||||
|
} from "./speakingState";
|
||||||
|
|
||||||
const SPEAKING_THRESHOLD = 0.01;
|
const SPEAKING_THRESHOLD = 0.01;
|
||||||
const SPEAKING_HANGTIME_MS = 500;
|
const SPEAKING_HANGTIME_MS = 500;
|
||||||
|
|
@ -85,12 +90,7 @@ class SpeakingDetector {
|
||||||
}
|
}
|
||||||
this.entries.delete(participantId);
|
this.entries.delete(participantId);
|
||||||
|
|
||||||
useCall.setState((state) => {
|
removeSpeakingParticipant(participantId);
|
||||||
if (!state.speakingParticipantIds.has(participantId)) return state;
|
|
||||||
const next = new Set(state.speakingParticipantIds);
|
|
||||||
next.delete(participantId);
|
|
||||||
return { speakingParticipantIds: next };
|
|
||||||
});
|
|
||||||
|
|
||||||
if (participantId === this.localParticipantId && this.localMicGateClosed) {
|
if (participantId === this.localParticipantId && this.localMicGateClosed) {
|
||||||
this.muteLocalTrack(false);
|
this.muteLocalTrack(false);
|
||||||
|
|
@ -104,7 +104,7 @@ class SpeakingDetector {
|
||||||
entry.isSpeaking = false;
|
entry.isSpeaking = false;
|
||||||
entry.lastSpeakingTime = 0;
|
entry.lastSpeakingTime = 0;
|
||||||
}
|
}
|
||||||
useCall.setState({ speakingParticipantIds: new Set() });
|
clearSpeakingParticipants();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -131,7 +131,7 @@ class SpeakingDetector {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.localMicGateClosed = muted;
|
this.localMicGateClosed = muted;
|
||||||
useCall.setState({ micGated: muted });
|
setMicGated(muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
private applyNoiseGate(rms: number) {
|
private applyNoiseGate(rms: number) {
|
||||||
|
|
@ -197,24 +197,7 @@ class SpeakingDetector {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changed.size > 0) {
|
if (changed.size > 0) {
|
||||||
useCall.setState((state) => {
|
updateSpeakingParticipants(changed);
|
||||||
let hasDiff = false;
|
|
||||||
const next = new Set(state.speakingParticipantIds);
|
|
||||||
for (const [id, speaking] of changed) {
|
|
||||||
if (speaking) {
|
|
||||||
if (!next.has(id)) {
|
|
||||||
next.add(id);
|
|
||||||
hasDiff = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (next.has(id)) {
|
|
||||||
next.delete(id);
|
|
||||||
hasDiff = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return hasDiff ? { speakingParticipantIds: next } : state;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -246,7 +229,3 @@ export function disposeSpeakingDetector(): void {
|
||||||
detectorInstance = null;
|
detectorInstance = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useIsSpeaking(participantId: number): boolean {
|
|
||||||
return useCall((state) => state.speakingParticipantIds.has(participantId));
|
|
||||||
}
|
|
||||||
|
|
|
||||||
55
packages/call/src/speakingState.ts
Normal file
55
packages/call/src/speakingState.ts
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
import { create } from "zustand";
|
||||||
|
|
||||||
|
type SpeakingState = {
|
||||||
|
speakingParticipantIds: Set<number>;
|
||||||
|
micGated: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const useSpeakingState = create<SpeakingState>(() => ({
|
||||||
|
speakingParticipantIds: new Set(),
|
||||||
|
micGated: false,
|
||||||
|
}));
|
||||||
|
|
||||||
|
export function setMicGated(micGated: boolean) {
|
||||||
|
useSpeakingState.setState({ micGated });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearSpeakingParticipants() {
|
||||||
|
useSpeakingState.setState({ speakingParticipantIds: new Set() });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeSpeakingParticipant(participantId: number) {
|
||||||
|
useSpeakingState.setState((state) => {
|
||||||
|
if (!state.speakingParticipantIds.has(participantId)) return state;
|
||||||
|
const next = new Set(state.speakingParticipantIds);
|
||||||
|
next.delete(participantId);
|
||||||
|
return { speakingParticipantIds: next };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateSpeakingParticipants(changed: Map<number, boolean>) {
|
||||||
|
useSpeakingState.setState((state) => {
|
||||||
|
let hasDiff = false;
|
||||||
|
const next = new Set(state.speakingParticipantIds);
|
||||||
|
|
||||||
|
for (const [id, speaking] of changed) {
|
||||||
|
if (speaking) {
|
||||||
|
if (!next.has(id)) {
|
||||||
|
next.add(id);
|
||||||
|
hasDiff = true;
|
||||||
|
}
|
||||||
|
} else if (next.has(id)) {
|
||||||
|
next.delete(id);
|
||||||
|
hasDiff = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasDiff ? { speakingParticipantIds: next } : state;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useIsSpeaking(participantId: number): boolean {
|
||||||
|
return useSpeakingState((state) =>
|
||||||
|
state.speakingParticipantIds.has(participantId),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -109,8 +109,6 @@ type CallStore = {
|
||||||
layoutVersion: number;
|
layoutVersion: number;
|
||||||
screenRef: React.RefObject<HTMLDivElement | null> | null;
|
screenRef: React.RefObject<HTMLDivElement | null> | null;
|
||||||
runtime: Runtime | null;
|
runtime: Runtime | null;
|
||||||
speakingParticipantIds: Set<number>;
|
|
||||||
micGated: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let _keyProvider: ExternalE2EEKeyProvider | null = null;
|
let _keyProvider: ExternalE2EEKeyProvider | null = null;
|
||||||
|
|
@ -857,7 +855,6 @@ export async function disconnect() {
|
||||||
watchedStreamParticipantIds: [],
|
watchedStreamParticipantIds: [],
|
||||||
pendingWatchedParticipantIds: [],
|
pendingWatchedParticipantIds: [],
|
||||||
activeScreenShareParticipantIds: [],
|
activeScreenShareParticipantIds: [],
|
||||||
micGated: false,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
getRoom().remoteParticipants.forEach((participant) => {
|
getRoom().remoteParticipants.forEach((participant) => {
|
||||||
|
|
@ -1079,8 +1076,6 @@ export const useCall = create<CallStore>(() => ({
|
||||||
layoutVersion: 0,
|
layoutVersion: 0,
|
||||||
screenRef: null,
|
screenRef: null,
|
||||||
runtime: null,
|
runtime: null,
|
||||||
speakingParticipantIds: new Set(),
|
|
||||||
micGated: false,
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Register app-level call listeners and wire React dependencies into the store.
|
// Register app-level call listeners and wire React dependencies into the store.
|
||||||
|
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
import { z } from "zod";
|
|
||||||
import { ttp } from "@tensamin/shared/data";
|
|
||||||
|
|
||||||
export type RawMessages = z.infer<typeof ttp.messages_get.response>["messages"];
|
|
||||||
|
|
||||||
export type RawMessage = RawMessages[number];
|
|
||||||
|
|
||||||
export type LiveMessage = RawMessage & {
|
|
||||||
failed?: boolean;
|
|
||||||
localId: string;
|
|
||||||
};
|
|
||||||
|
|
@ -395,49 +395,12 @@ export async function getSharedSecret(
|
||||||
return out;
|
return out;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns WebCrypto subtle API when available.
|
|
||||||
* @returns SubtleCrypto instance or undefined.
|
|
||||||
*/
|
|
||||||
const getSubtle = () => globalThis.crypto?.subtle;
|
const getSubtle = () => globalThis.crypto?.subtle;
|
||||||
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
const hkdfAesGcmFromShared = async (
|
|
||||||
sharedSecret: BufferSource,
|
|
||||||
infoStr: string
|
|
||||||
): Promise<CryptoKey> => {
|
|
||||||
const subtle = getSubtle();
|
|
||||||
if (!subtle) throw new Error("WebCrypto subtle not available");
|
|
||||||
const info = textEncoder.encode(infoStr);
|
|
||||||
const baseKey = await subtle.importKey(
|
|
||||||
"raw",
|
|
||||||
sharedSecret,
|
|
||||||
"HKDF",
|
|
||||||
false,
|
|
||||||
["deriveKey"]
|
|
||||||
);
|
|
||||||
return await subtle.deriveKey(
|
|
||||||
{
|
|
||||||
name: "HKDF",
|
|
||||||
hash: "SHA-256",
|
|
||||||
salt: new Uint8Array(0),
|
|
||||||
info,
|
|
||||||
},
|
|
||||||
baseKey,
|
|
||||||
{ name: "AES-GCM", length: 256 },
|
|
||||||
false,
|
|
||||||
["encrypt", "decrypt"]
|
|
||||||
);
|
|
||||||
};
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
const myJwk: JWK = normalizeOkpX448Jwk(ownJwk, "own_jwk");
|
const myJwk: JWK = normalizeOkpX448Jwk(ownJwk, "own_jwk");
|
||||||
const peerJwk: JWK = normalizeOkpX448Jwk(otherJwk, "other_jwk");
|
const peerJwk: JWK = normalizeOkpX448Jwk(otherJwk, "other_jwk");
|
||||||
|
|
||||||
const subtle = getSubtle();
|
const subtle = getSubtle();
|
||||||
//const infoStr = `ECDH-X448-AES-GCM-v1|my=${myJwk.x}|peer=${peerJwk.x}`;
|
|
||||||
|
|
||||||
if (subtle) {
|
if (subtle) {
|
||||||
const algorithms = [{ name: "ECDH", namedCurve: "X448" }, { name: "X448" }];
|
const algorithms = [{ name: "ECDH", namedCurve: "X448" }, { name: "X448" }];
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@
|
||||||
"@codemirror/lang-markdown": "^6.5.0",
|
"@codemirror/lang-markdown": "^6.5.0",
|
||||||
"@codemirror/state": "^6.5.4",
|
"@codemirror/state": "^6.5.4",
|
||||||
"@codemirror/view": "^6.41.1",
|
"@codemirror/view": "^6.41.1",
|
||||||
"@tensamin/ui": "*",
|
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0"
|
"react-dom": "^19.2.0"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,7 @@ const INLINE_TOKEN_REGEX =
|
||||||
* @param input Parameter input.
|
* @param input Parameter input.
|
||||||
* @returns InlineNode[].
|
* @returns InlineNode[].
|
||||||
*/
|
*/
|
||||||
export function parseInlineNodes(input: string): InlineNode[] {
|
function parseInlineNodes(input: string): InlineNode[] {
|
||||||
const nodes: InlineNode[] = [];
|
const nodes: InlineNode[] = [];
|
||||||
|
|
||||||
let cursor = 0;
|
let cursor = 0;
|
||||||
|
|
@ -369,7 +369,7 @@ export function parseMarkdownBlocks(markdown: string): MarkdownBlock[] {
|
||||||
* @param nodes Parameter nodes.
|
* @param nodes Parameter nodes.
|
||||||
* @returns React.ReactNode[].
|
* @returns React.ReactNode[].
|
||||||
*/
|
*/
|
||||||
export function renderInline(nodes: InlineNode[]): React.ReactNode[] {
|
function renderInline(nodes: InlineNode[]): React.ReactNode[] {
|
||||||
return nodes.map((node, index) => {
|
return nodes.map((node, index) => {
|
||||||
if (node.type === "text") {
|
if (node.type === "text") {
|
||||||
return node.value;
|
return node.value;
|
||||||
|
|
@ -636,7 +636,7 @@ function readTable(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const markdownStyles = `
|
const markdownStyles = `
|
||||||
.tm-md-root { color: hsl(var(--foreground)); line-height: 1.55; font-size: 0.95rem; }
|
.tm-md-root { color: hsl(var(--foreground)); line-height: 1.55; font-size: 0.95rem; }
|
||||||
.tm-md-heading { margin: 0.2rem 0 0.35rem; font-weight: 700; line-height: 1.25; }
|
.tm-md-heading { margin: 0.2rem 0 0.35rem; font-weight: 700; line-height: 1.25; }
|
||||||
.tm-md-h1 { font-size: 1.65rem; }
|
.tm-md-h1 { font-size: 1.65rem; }
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,11 @@
|
||||||
"@tensamin/chat": "workspace:*",
|
"@tensamin/chat": "workspace:*",
|
||||||
"@tensamin/storage": "workspace:*",
|
"@tensamin/storage": "workspace:*",
|
||||||
"@tensamin/ttp": "workspace:*",
|
"@tensamin/ttp": "workspace:*",
|
||||||
|
"@tensamin/ui": "*",
|
||||||
"@tensamin/user": "workspace:*",
|
"@tensamin/user": "workspace:*",
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
|
"sonner": "^2.0.7",
|
||||||
"zod": "^4.3.6"
|
"zod": "^4.3.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@
|
||||||
"lucide-react": "^1.14.0",
|
"lucide-react": "^1.14.0",
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
"sonner": "^2.0.7",
|
|
||||||
"zod": "^4.3.6"
|
"zod": "^4.3.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,9 +25,15 @@ export default function SessionProvider({ children }: { children: ReactNode }) {
|
||||||
const { load, save } = useStorage();
|
const { load, save } = useStorage();
|
||||||
const [contacts, setContacts] = useState<Contacts>([]);
|
const [contacts, setContacts] = useState<Contacts>([]);
|
||||||
const [communities, setCommunities] = useState<Communities>([]);
|
const [communities, setCommunities] = useState<Communities>([]);
|
||||||
const [calls, setCalls] = useState<Calls>([]);
|
const [localCalls, setLocalCalls] = useState<Calls>([]);
|
||||||
|
const calls = [
|
||||||
|
...freshCalls,
|
||||||
|
...localCalls.filter(
|
||||||
|
(call) => !freshCalls.some((fresh) => fresh.call_id === call.call_id),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
// Get cached data and merge fresh data
|
// Cached session data fills in items the server did not return freshly.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
load("cached_contacts").then((cachedData) => {
|
load("cached_contacts").then((cachedData) => {
|
||||||
setContacts([
|
setContacts([
|
||||||
|
|
@ -53,16 +59,6 @@ export default function SessionProvider({ children }: { children: ReactNode }) {
|
||||||
});
|
});
|
||||||
}, [load, freshContacts, freshCommunities]);
|
}, [load, freshContacts, freshCommunities]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setCalls((prevCalls) => [
|
|
||||||
...freshCalls,
|
|
||||||
...prevCalls.filter(
|
|
||||||
(call) => !freshCalls.some((fresh) => fresh.call_id === call.call_id),
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
}, [freshCalls]);
|
|
||||||
|
|
||||||
// Save data
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
save("cached_contacts", contacts);
|
save("cached_contacts", contacts);
|
||||||
}, [contacts, save]);
|
}, [contacts, save]);
|
||||||
|
|
@ -98,7 +94,7 @@ export default function SessionProvider({ children }: { children: ReactNode }) {
|
||||||
};
|
};
|
||||||
|
|
||||||
const insertCall = (call: Calls[number]) => {
|
const insertCall = (call: Calls[number]) => {
|
||||||
setCalls((prevCalls) => {
|
setLocalCalls((prevCalls) => {
|
||||||
if (prevCalls.some((prevCall) => prevCall.call_id === call.call_id)) {
|
if (prevCalls.some((prevCall) => prevCall.call_id === call.call_id)) {
|
||||||
return prevCalls;
|
return prevCalls;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,6 @@
|
||||||
"@tensamin/user": "workspace:*",
|
"@tensamin/user": "workspace:*",
|
||||||
"lucide-react": "^1.8.0",
|
"lucide-react": "^1.8.0",
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0"
|
||||||
"sonner": "^2.0.7",
|
|
||||||
"zod": "^4.3.6"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,7 @@
|
||||||
"@tauri-apps/api": "^2",
|
"@tauri-apps/api": "^2",
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
"tauri-plugin-app-events-api": "^0.2.0",
|
"tauri-plugin-app-events-api": "^0.2.0"
|
||||||
"zod": "^4.3.6"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"eslint": "^10.0.3"
|
"eslint": "^10.0.3"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue