(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:
Alois 2026-04-29 23:30:41 +02:00
commit 8e294d230e
21 changed files with 510 additions and 51 deletions

View file

@ -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>
);
}

View file

@ -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>
);