Updated sidebar & ui

This commit is contained in:
Alois 2026-04-28 15:04:49 +02:00
commit 92c39d38b1
4 changed files with 140 additions and 69 deletions

View file

@ -242,7 +242,7 @@
},
"overrides": {
"@tensamin/ttp-core": "https://git.methanium.net/tensamin/ttp/archive/0.0.12.tar.gz",
"@tensamin/ui": "https://git.methanium.net/tensamin/ui/archive/0.0.26.tar.gz",
"@tensamin/ui": "https://git.methanium.net/tensamin/ui/archive/0.0.29.tar.gz",
},
"packages": {
"@antfu/ni": ["@antfu/ni@25.0.0", "", { "dependencies": { "ansis": "^4.0.0", "fzf": "^0.5.2", "package-manager-detector": "^1.3.0", "tinyexec": "^1.0.1" }, "bin": { "na": "bin/na.mjs", "ni": "bin/ni.mjs", "nr": "bin/nr.mjs", "nci": "bin/nci.mjs", "nlx": "bin/nlx.mjs", "nun": "bin/nun.mjs", "nup": "bin/nup.mjs" } }, "sha512-9q/yCljni37pkMr4sPrI3G4jqdIk074+iukc5aFJl7kmDCCsiJrbZ6zKxnES1Gwg+i9RcDZwvktl23puGslmvA=="],
@ -695,7 +695,7 @@
"@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/ui": ["@tensamin/ui@https://git.methanium.net/tensamin/ui/archive/0.0.26.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-SH+voNvmg2TtTjZH561NZC4ekY0ypFQpPvC0u0s5Jt1nGG243TR3ybX3T3WI83Z59ayC+t7YPZOmQPHTHI9+Mw=="],
"@tensamin/ui": ["@tensamin/ui@https://git.methanium.net/tensamin/ui/archive/0.0.29.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-z83adnQtOLCr8ceVaUXZ7F7Hn/TrqsyqJM8hivhQxOX4OUNFaCBrbmfDpIK6OmtjRF/AnjqBNRua8FXAhJN35A=="],
"@tensamin/user": ["@tensamin/user@workspace:packages/user"],

View file

@ -37,7 +37,7 @@
"jsonc-parser": "^3.3.1"
},
"overrides": {
"@tensamin/ui": "https://git.methanium.net/tensamin/ui/archive/0.0.26.tar.gz",
"@tensamin/ui": "https://git.methanium.net/tensamin/ui/archive/0.0.29.tar.gz",
"@tensamin/ttp-core": "https://git.methanium.net/tensamin/ttp/archive/0.0.12.tar.gz"
},
"dependencies": {

View file

@ -1,7 +1,96 @@
import { Lock, LockOpen } from "lucide-react";
import { useCall } from "../context";
import {
Button,
Card,
CardContent,
CardHeader,
Popover,
PopoverContent,
PopoverTrigger,
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@tensamin/ui";
import { LeaveIcon } from "@livekit/components-react";
export default function SidebarBox() {
const { state } = useCall();
const { state, room, disconnect } = useCall();
return state === "closed" ? null : <div>Sidebar {state}</div>;
return state === "closed" ? null : (
<Card className="p-1.5 gap-1">
<CardHeader className="p-0!">
<ConnectionBar
state={state}
encrypted={
room.localParticipant.isE2EEEnabled &&
room.localParticipant.isEncrypted
}
/>
</CardHeader>
<CardContent className="p-0! flex justify-end">
<Button
size="lg"
className="w-9 h-9"
onClick={() => disconnect()}
variant="destructive"
>
<LeaveIcon />
</Button>
</CardContent>
</Card>
);
}
function ConnectionBar({
state,
encrypted,
}: {
state: "closed" | "closing" | "connecting" | "open" | "encrypting";
encrypted: boolean;
}) {
return (
<Popover>
<PopoverTrigger
render={
<Button
size="lg"
variant={
state === "open"
? encrypted
? "subtleDefault"
: "destructive"
: state === "closed" ||
state === "closing" ||
state === "connecting"
? "outline"
: "outline"
}
className="flex justify-between items-center"
>
{state === "connecting" && "Connecting..."}
{state === "open" && "Connected"}
{state === "closed" && "Closed"}
{state === "closing" && "Closing"}
<Tooltip>
<TooltipTrigger
render={
encrypted ? (
<Lock color="var(--primary-foreground)" />
) : (
<LockOpen color="var(--destructive)" />
)
}
/>
<TooltipContent>
{encrypted ? "Encrypted" : "Unencrypted"}
</TooltipContent>
</Tooltip>
</Button>
}
/>
<PopoverContent side="top">Test stuff</PopoverContent>
</Popover>
);
}

View file

@ -7,7 +7,7 @@ import { ttp } from "@tensamin/shared/data";
import { LiveKitRoom, useLocalParticipant } from "@livekit/components-react";
import { DeepFilterNoiseFilterProcessor } from "deepfilternet3-noise-filter";
import { LocalAudioTrack } from "livekit-client";
import { ExternalE2EEKeyProvider, LocalAudioTrack, Room } from "livekit-client";
import { useCrypto } from "@tensamin/crypto/context";
import { useStorage } from "@tensamin/storage/context";
import { useUser } from "@tensamin/user/context";
@ -17,12 +17,12 @@ export const context = createContext<contextType | undefined>(undefined);
export default function Provider(props: { children: React.ReactNode }) {
const navigate = useNavigate();
const { send } = useTTP();
const { encrypt, decrypt, getSharedSecret, decryptText } = useCrypto();
const { getSharedSecret, decryptText } = useCrypto();
const { load } = useStorage();
const { get } = useUser();
const [state, setState] = useState<
"closed" | "closing" | "connecting" | "open"
"closed" | "closing" | "connecting" | "open" | "encrypting"
>("closed");
const [callId, setCallId] = useState<string | null>(null);
@ -53,6 +53,8 @@ export default function Provider(props: { children: React.ReactNode }) {
setCallId(null);
setCallSecret(null);
setLivekitToken(null);
room.disconnect();
e2eeWorker.terminate();
}
// Utils
@ -124,12 +126,37 @@ export default function Provider(props: { children: React.ReactNode }) {
});
}, [callId, send, view]);
// Room & Encryption
const [keyProvider] = useState(() => new ExternalE2EEKeyProvider());
const [e2eeWorker] = useState(
() => new Worker(new URL("livekit-client/e2ee-worker", import.meta.url)),
);
const [room] = useState(
() =>
new Room({
encryption: {
keyProvider,
worker: e2eeWorker,
},
loggerName: "call",
}),
);
useEffect(() => {
async function enableStuff() {
await keyProvider.setKey(callSecret || "");
await room.setE2EEEnabled(true);
}
enableStuff();
}, [callSecret, keyProvider, room]);
return (
<context.Provider
value={{
state,
view,
setView,
room,
connect,
disconnect,
@ -141,67 +168,21 @@ export default function Provider(props: { children: React.ReactNode }) {
serverUrl="wss://call.tensamin.net"
token={livekitToken || ""}
connect={state !== "closed" && livekitToken !== ""}
options={{
encryption: {
e2eeManager: {
on(event, callback) {
void event;
void callback;
return this;
},
isEnabled: callSecret !== null,
isDataChannelEncryptionEnabled: true,
setup: async (room) => {
void room;
},
setupEngine: async (engine) => {
void engine;
},
setParticipantCryptorEnabled: async (
enabled: boolean,
participantIdentity: string,
) => {
console.log(
`Set participant ${participantIdentity} cryptor enabled: ${enabled}`,
);
},
setSifTrailer: async (trailer: Uint8Array) => {
void trailer;
},
// Encryption
encryptData: async (data: Uint8Array<ArrayBuffer>) => {
const encrypted = await encrypt(callSecret || "", data);
return {
uuid: "",
payload: encrypted,
iv: new Uint8Array(),
keyIndex: 0,
};
},
handleEncryptedData: async (
payload: Uint8Array<ArrayBuffer>,
iv: Uint8Array,
participantIdentity: string,
keyIndex: number,
) => {
void iv;
void participantIdentity;
void keyIndex;
const decrypted = await decrypt(callSecret || "", payload);
return {
uuid: "",
payload: decrypted,
};
},
},
},
loggerName: "call",
room={room}
onConnected={() => {
setState("open");
log(2, "call", "purple", "Connected to call", {
callId,
callSecret,
});
}}
onDisconnected={() => {
setState("closed");
log(2, "call", "purple", "Disconnected from call", {
callId,
callSecret,
});
}}
onConnected={() => setState("open")}
onDisconnected={() => setState("closed")}
onError={(error) => {
log(1, "call", "red", error.message);
toast("error", error.message);
@ -255,13 +236,14 @@ function NoiseFilter() {
}
type contextType = {
state: "closed" | "closing" | "connecting" | "open";
state: "closed" | "closing" | "connecting" | "open" | "encrypting";
view: "preview" | "focused" | "grid";
setView: (view: "preview" | "focused" | "grid") => void;
connect: (callId: string, callSecret: string) => void;
disconnect: () => void;
joinCall: (userId: number, callSecret?: string, callId?: string) => void;
currentCallData: z.infer<typeof ttp.call_data.response> | null;
room: Room;
};
export function useCall(): contextType {