Skip to content

Commit dc8c404

Browse files
committed
chat to retain videoID information, remove empty state.
1 parent 2423877 commit dc8c404

8 files changed

Lines changed: 106 additions & 86 deletions

File tree

packages/react/src/components/multiview/ABOUT_MULTIVIEW.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ Multiview allows users to watch multiple video streams simultaneously with live
1414

1515
| Concept | Description |
1616
|---------|-------------|
17-
| **Cell** | Atomic unit of the grid. Contains content (`video`, `chat`, `empty`) + position (`x`, `y`, `w`, `h`) + `visibility` state. |
17+
| **Cell** | Atomic unit of the grid. Contains content (`video`, `chat`) + position (`x`, `y`, `w`, `h`) + `visibility` state. Empty state is indicated by `videoId === undefined`. |
1818
| **CellEntry** | Wrapper containing `{ id: string, atom: PrimitiveAtom<Cell> }` for granular subscriptions. |
1919
| **Cell Queue** | Append-only array of `CellEntry[]`. Cells are marked `visibility: 'hidden'` rather than deleted. |
2020
| **LayoutPreset** | Pure positional data encoded as URL-safe string. Defines slot positions and types. |
@@ -79,11 +79,11 @@ Uses `react-grid-layout` library for:
7979
Compact URL-safe string format: `xywh[content],xywh[content],...`
8080

8181
- `x, y, w, h`: Base64 encoded (single char each, max 63)
82-
- `content`: Empty, `chat{tab}`, or 11-char video ID
82+
- `content`: Empty (video slot), `chat`, or 11-char video ID
8383

8484
**Examples**:
85-
- `AACC`Cell at (0,0) with size 2×2
86-
- `AACCchat0` → Chat cell, tab 0
85+
- `AACC`Video cell at (0,0) with size 2×2 (empty, no content)
86+
- `AACCchat` → Chat cell (videoId assigned at apply time)
8787
- `AACCdQw4w9WgXcQ` → Video cell with YouTube ID
8888

8989
---

packages/react/src/components/multiview/ChatCell.tsx

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,19 @@
1-
import { activeVideosAtom } from "@/store/multiview";
2-
import { useAtomValue } from "jotai";
3-
import { useMemo } from "react";
4-
51
interface ChatCellProps {
6-
chatTab: number;
2+
videoId: string;
73
}
84

95
/**
106
* Chat iframe cell for multiview.
11-
* Shows YouTube chat for the video at the specified tab index.
7+
* Shows YouTube chat for the specified video ID.
8+
* Now receives videoId directly instead of a tab index.
129
*/
13-
export function ChatCell({ chatTab }: ChatCellProps) {
14-
const activeVideos = useAtomValue(activeVideosAtom);
15-
16-
// Get video ID from tab index
17-
const videoId = useMemo(() => {
18-
if (chatTab >= 0 && chatTab < activeVideos.length) {
19-
return activeVideos[chatTab].videoId;
20-
}
21-
return null;
22-
}, [activeVideos, chatTab]);
23-
10+
export function ChatCell({ videoId }: ChatCellProps) {
2411
if (!videoId) {
2512
return (
2613
<div className="bg-base-3 text-base-11 flex h-full w-full items-center justify-center">
2714
<div className="text-center">
2815
<div className="i-lucide:message-square-off mb-2 h-8 w-8 opacity-50" />
29-
<div className="text-sm">No video for tab {chatTab}</div>
16+
<div className="text-sm">No video assigned to chat</div>
3017
</div>
3118
</div>
3219
);

packages/react/src/components/multiview/MultiviewCell.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -72,15 +72,16 @@ export const MultiviewCell = forwardRef<HTMLDivElement, MultiviewCellProps>(
7272

7373
{/* Cell info */}
7474
<div className="mb-2 text-sm text-white/70">
75-
{cell.type === "video" && cell.videoId && `Video: ${cell.videoId}`}
76-
{cell.type === "chat" && `Chat (Tab ${cell.chatTab})`}
77-
{cell.type === "empty" && "Empty Cell"}
75+
{cell.type === "video" &&
76+
(cell.videoId ? `Video: ${cell.videoId}` : "Empty Cell")}
77+
{cell.type === "chat" &&
78+
(cell.videoId ? `Chat: ${cell.videoId}` : "Chat (empty)")}
7879
</div>
7980

8081
{/* Control buttons - explicitly clickable */}
8182
<div className="flex flex-wrap gap-2">
8283
{/* Refresh button - reloads the video/chat iframe */}
83-
{cell.type !== "empty" && (
84+
{cell.videoId && (
8485
<Tooltip>
8586
<TooltipTrigger asChild>
8687
<button
@@ -93,7 +94,7 @@ export const MultiviewCell = forwardRef<HTMLDivElement, MultiviewCellProps>(
9394
</button>
9495
</TooltipTrigger>
9596
<TooltipContent side="bottom">
96-
<p>Reload this cell's content (recreates the iframe)</p>
97+
<p>Reload this cell's content</p>
9798
</TooltipContent>
9899
</Tooltip>
99100
)}
@@ -118,7 +119,7 @@ export const MultiviewCell = forwardRef<HTMLDivElement, MultiviewCellProps>(
118119
)}
119120

120121
{/* Clear button - removes content but keeps the cell */}
121-
{cell.type !== "empty" && (
122+
{cell.videoId && (
122123
<Tooltip>
123124
<TooltipTrigger asChild>
124125
<button

packages/react/src/components/multiview/MultiviewFrames.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -84,18 +84,18 @@ function MultiviewFrameItem({ cell }: MultiviewFrameItemProps) {
8484
data-cell-id={cell.id}
8585
data-cell-type={cell.type}
8686
>
87-
{cell.type === "video" && cell.videoId && (
88-
<VideoCell videoId={cell.videoId} />
89-
)}
90-
{cell.type === "chat" && <ChatCell chatTab={cell.chatTab ?? 0} />}
91-
{cell.type === "empty" && (
87+
{!cell.videoId ? (
9288
<iframe
9389
key={`frame-${cell.id}`}
9490
src={BLANK_IFRAME_URL}
9591
className="h-full w-full border-0"
9692
title="blank"
9793
/>
98-
)}
94+
) : cell.type === "video" ? (
95+
<VideoCell videoId={cell.videoId} />
96+
) : cell.type === "chat" ? (
97+
<ChatCell videoId={cell.videoId} />
98+
) : null}
9999
</div>
100100
);
101101
}

packages/react/src/components/multiview/PresetPreview.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export function PresetPreview({
3939
>
4040
{cells.map((cell, i) => {
4141
const isChat = cell.type === "chat";
42-
const isVideo = cell.type === "video" || cell.type === "empty";
42+
const isVideo = cell.type === "video";
4343

4444
return (
4545
<div

packages/react/src/lib/multiview-layout.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ interface EncodeOptions {
4545
* Encode a layout to a compact URL-safe string.
4646
* Format: "xywh[content],xywh[content],..."
4747
* - x, y, w, h are base64 encoded (single char each, max 63)
48-
* - content is either empty, "chat{tab}", or video ID
48+
* - content is either empty, "chat", or video ID
4949
*/
5050
export function encodeLayout({
5151
cells,
@@ -67,7 +67,7 @@ export function encodeLayout({
6767
encoded += b64[cell.h];
6868

6969
if (cell.type === "chat") {
70-
encoded += `chat${cell.chatTab ?? 0}`;
70+
encoded += cell.videoId ? `chat${cell.videoId}` : "chat";
7171
} else if (cell.type === "video" && includeVideo && cell.videoId) {
7272
encoded += cell.videoId;
7373
}
@@ -100,20 +100,25 @@ export function decodeLayout(encodedStr: string): DecodeResult {
100100
const content = str.substring(4);
101101

102102
const isChat = content.startsWith("chat");
103-
const chatTab =
104-
isChat && content.length > 4 ? parseInt(content[4], 10) : undefined;
105-
const videoId = !isChat && content.length === 11 ? content : undefined;
103+
// For chat cells: content is "chat" (empty) or "chat{videoId}"
104+
// For video cells: content is empty (slot) or videoId (min 5 chars)
105+
const videoId = isChat
106+
? content.length > 4
107+
? content.substring(4)
108+
: undefined
109+
: content.length >= 5
110+
? content
111+
: undefined;
106112

107113
const cell: Cell = {
108114
id,
109-
visibility: "visible",
115+
visibility: "visible" as const,
110116
x: b64.indexOf(xywh[0]),
111117
y: b64.indexOf(xywh[1]),
112118
w: b64.indexOf(xywh[2]),
113119
h: b64.indexOf(xywh[3]),
114-
type: isChat ? "chat" : videoId ? "video" : "empty",
120+
type: isChat ? ("chat" as const) : ("video" as const),
115121
...(videoId && { videoId }),
116-
...(chatTab !== undefined && { chatTab }),
117122
};
118123

119124
cells.push(cell);

packages/react/src/lib/multiview-utils.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ interface CellDef {
1212
w: number;
1313
h: number;
1414
type?: "video" | "chat";
15-
chatTab?: number;
1615
}
1716

1817
/**
@@ -25,7 +24,7 @@ function encodeCell(cell: CellDef): string {
2524
encoded += b64[cell.w];
2625
encoded += b64[cell.h];
2726
if (cell.type === "chat") {
28-
encoded += `chat${cell.chatTab ?? 0}`;
27+
encoded += "chat";
2928
}
3029
return encoded;
3130
}
@@ -57,7 +56,7 @@ export const horizontalPresets: LayoutPreset[] = [
5756
createPreset(
5857
[
5958
{ x: 0, y: 0, w: 15, h: 12 },
60-
{ x: 15, y: 0, w: 5, h: 12, type: "chat", chatTab: 0 },
59+
{ x: 15, y: 0, w: 5, h: 12, type: "chat" },
6160
],
6261
"Side Chat 1",
6362
),
@@ -246,7 +245,7 @@ export const verticalPresets: LayoutPreset[] = [
246245
createPreset(
247246
[
248247
{ x: 0, y: 0, w: 12, h: 10 },
249-
{ x: 0, y: 10, w: 12, h: 10, type: "chat", chatTab: 0 },
248+
{ x: 0, y: 10, w: 12, h: 10, type: "chat" },
250249
],
251250
"Mobile 1",
252251
1,
@@ -332,10 +331,12 @@ export function getDefaultLayout(
332331

333332
/**
334333
* Find the first visible empty cell in the layout.
334+
* An empty cell is a video cell with no videoId.
335335
*/
336336
export function findEmptyCell(cells: Cell[]): Cell | undefined {
337337
return cells.find(
338-
(cell) => cell.type === "empty" && cell.visibility === "visible",
338+
(cell) =>
339+
cell.type === "video" && !cell.videoId && cell.visibility === "visible",
339340
);
340341
}
341342

0 commit comments

Comments
 (0)