@meshagent/meshagent-tailwind 0.41.4 → 0.41.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/CHANGELOG.md +3 -0
  2. package/README.md +33 -0
  3. package/dist/cjs/chat/chat-thread.js +15 -10
  4. package/dist/cjs/chat/dataset-chat-thread.d.ts +4 -1
  5. package/dist/cjs/chat/dataset-chat-thread.js +130 -157
  6. package/dist/cjs/chat/multi-thread-view.d.ts +4 -1
  7. package/dist/cjs/chat/multi-thread-view.js +4 -0
  8. package/dist/cjs/chat/new-chat-thread.d.ts +4 -1
  9. package/dist/cjs/chat/new-chat-thread.js +43 -87
  10. package/dist/cjs/file-preview/file-preview.d.ts +6 -0
  11. package/dist/cjs/file-preview/file-preview.js +220 -0
  12. package/dist/cjs/meetings/camera-grid.d.ts +46 -0
  13. package/dist/cjs/meetings/camera-grid.js +435 -0
  14. package/dist/cjs/meetings/controls.d.ts +4 -2
  15. package/dist/cjs/meetings/controls.js +9 -3
  16. package/dist/cjs/meetings/lobby.d.ts +17 -0
  17. package/dist/cjs/meetings/lobby.js +595 -0
  18. package/dist/cjs/meetings/meeting-scope.d.ts +7 -6
  19. package/dist/cjs/meetings/meeting-scope.js +64 -15
  20. package/dist/cjs/meetings/meeting-view.d.ts +6 -0
  21. package/dist/cjs/meetings/meeting-view.js +635 -0
  22. package/dist/cjs/meetings/meetings.d.ts +3 -0
  23. package/dist/cjs/meetings/meetings.js +3 -0
  24. package/dist/cjs/meetings/wake-lock.js +2 -2
  25. package/dist/esm/chat/chat-thread.js +15 -10
  26. package/dist/esm/chat/dataset-chat-thread.d.ts +4 -1
  27. package/dist/esm/chat/dataset-chat-thread.js +129 -133
  28. package/dist/esm/chat/multi-thread-view.d.ts +4 -1
  29. package/dist/esm/chat/multi-thread-view.js +4 -0
  30. package/dist/esm/chat/new-chat-thread.d.ts +4 -1
  31. package/dist/esm/chat/new-chat-thread.js +43 -87
  32. package/dist/esm/file-preview/file-preview.d.ts +6 -0
  33. package/dist/esm/file-preview/file-preview.js +220 -0
  34. package/dist/esm/meetings/camera-grid.d.ts +46 -0
  35. package/dist/esm/meetings/camera-grid.js +405 -0
  36. package/dist/esm/meetings/controls.d.ts +4 -2
  37. package/dist/esm/meetings/controls.js +9 -3
  38. package/dist/esm/meetings/lobby.d.ts +17 -0
  39. package/dist/esm/meetings/lobby.js +595 -0
  40. package/dist/esm/meetings/meeting-scope.d.ts +7 -6
  41. package/dist/esm/meetings/meeting-scope.js +71 -16
  42. package/dist/esm/meetings/meeting-view.d.ts +6 -0
  43. package/dist/esm/meetings/meeting-view.js +630 -0
  44. package/dist/esm/meetings/meetings.d.ts +3 -0
  45. package/dist/esm/meetings/meetings.js +3 -0
  46. package/dist/esm/meetings/wake-lock.js +2 -2
  47. package/dist/index.css +1 -1
  48. package/package.json +9 -5
@@ -0,0 +1,405 @@
1
+ import { Fragment, jsx } from "react/jsx-runtime";
2
+ import React, { useMemo, useSyncExternalStore } from "react";
3
+ import { Track } from "livekit-client";
4
+ const TrackSource = {
5
+ Camera: Track.Source.Camera,
6
+ ScreenShareVideo: Track.Source.ScreenShare
7
+ };
8
+ function useParticipantsSnapshot(participants) {
9
+ useSyncExternalStore(
10
+ (listener) => {
11
+ const events = [
12
+ "trackPublished",
13
+ "trackSubscribed",
14
+ "trackUnpublished",
15
+ "trackUnsubscribed",
16
+ "trackMuted",
17
+ "trackUnmuted",
18
+ "localTrackPublished",
19
+ "localTrackUnpublished",
20
+ "participantNameChanged",
21
+ "isSpeakingChanged",
22
+ "attributesChanged"
23
+ ];
24
+ for (const participant of participants) {
25
+ for (const eventName of events) {
26
+ participant.on(eventName, listener);
27
+ }
28
+ }
29
+ return () => {
30
+ for (const participant of participants) {
31
+ for (const eventName of events) {
32
+ participant.off(eventName, listener);
33
+ }
34
+ }
35
+ };
36
+ },
37
+ () => participants.map(
38
+ (participant) => [
39
+ participant.sid,
40
+ participant.identity,
41
+ participant.isCameraEnabled,
42
+ participant.isScreenShareEnabled,
43
+ participant.isMicrophoneEnabled,
44
+ participant.isSpeaking,
45
+ participant.trackPublications.size,
46
+ participant.name ?? ""
47
+ ].join(":")
48
+ ).join("|"),
49
+ () => ""
50
+ );
51
+ }
52
+ function publicationAspectRatio(publication) {
53
+ const dimensions = publication?.dimensions ?? { width: 640, height: 480 };
54
+ return dimensions.height > 0 ? dimensions.width / dimensions.height : 4 / 3;
55
+ }
56
+ function layoutCameras(publications, width, height) {
57
+ const slots = Math.max(publications.length, 1);
58
+ let bestRows = 1;
59
+ let bestCols = slots;
60
+ let minWaste = Number.POSITIVE_INFINITY;
61
+ for (let rows = 1; rows <= slots; rows += 1) {
62
+ const cols = Math.ceil(slots / rows);
63
+ const gridAspectRatio = width / cols / (height / rows);
64
+ let aspectRatioWaste = 0;
65
+ for (const publication of publications) {
66
+ aspectRatioWaste += Math.abs(
67
+ publicationAspectRatio(publication) - gridAspectRatio
68
+ );
69
+ }
70
+ if (aspectRatioWaste < minWaste) {
71
+ minWaste = aspectRatioWaste;
72
+ bestRows = rows;
73
+ bestCols = cols;
74
+ }
75
+ }
76
+ return [bestRows, bestCols];
77
+ }
78
+ function CameraGrid({
79
+ participants,
80
+ room,
81
+ spacing = 0,
82
+ showNames = true,
83
+ showAllVideos = false,
84
+ preferredSource,
85
+ rowsDesired = 0,
86
+ columnsDesired = 0,
87
+ tryFill = true,
88
+ activeVideoPublicationForSource,
89
+ activeVideoPublications,
90
+ renderVideoTrack,
91
+ renderAudioStats,
92
+ frameBuilder
93
+ }) {
94
+ useParticipantsSnapshot(participants);
95
+ const items = useMemo(() => {
96
+ if (!room) return [];
97
+ const hasShare = participants.some(
98
+ (participant) => activeVideoPublicationForSource(
99
+ participant,
100
+ TrackSource.ScreenShareVideo
101
+ ) != null
102
+ );
103
+ const videoSource = preferredSource ?? (hasShare ? TrackSource.ScreenShareVideo : TrackSource.Camera);
104
+ const shouldShowCameraPlaceholder = videoSource === TrackSource.Camera;
105
+ const nextItems = [];
106
+ for (const participant of participants) {
107
+ let added = false;
108
+ const publications = showAllVideos ? activeVideoPublications(participant) : activeVideoPublications(participant, { source: videoSource });
109
+ for (const publication of publications) {
110
+ const track = publication.videoTrack;
111
+ if (track == null) continue;
112
+ added = true;
113
+ nextItems.push({
114
+ participant,
115
+ publication,
116
+ trackNode: /* @__PURE__ */ jsx("div", { style: { pointerEvents: "none", width: "100%", height: "100%" }, children: renderVideoTrack({
117
+ track,
118
+ fit: publication.source === TrackSource.ScreenShareVideo ? "contain" : "cover",
119
+ publication,
120
+ participant
121
+ }) })
122
+ });
123
+ }
124
+ if (shouldShowCameraPlaceholder && !added) {
125
+ nextItems.push({
126
+ participant,
127
+ publication: null,
128
+ trackNode: /* @__PURE__ */ jsx(
129
+ "div",
130
+ {
131
+ style: {
132
+ width: "100%",
133
+ height: "100%",
134
+ background: "#222222",
135
+ display: "flex",
136
+ alignItems: "center",
137
+ justifyContent: "center"
138
+ },
139
+ children: participant.identity.includes(".agent") && renderAudioStats ? renderAudioStats({ room, participant }) : null
140
+ }
141
+ )
142
+ });
143
+ }
144
+ }
145
+ return nextItems;
146
+ }, [
147
+ activeVideoPublicationForSource,
148
+ activeVideoPublications,
149
+ participants,
150
+ preferredSource,
151
+ renderAudioStats,
152
+ renderVideoTrack,
153
+ room,
154
+ showAllVideos
155
+ ]);
156
+ if (!room || items.length === 0) return null;
157
+ return /* @__PURE__ */ jsx("div", { style: { width: "100%", height: "100%", position: "relative" }, children: /* @__PURE__ */ jsx(
158
+ CameraGridLayout,
159
+ {
160
+ items,
161
+ spacing,
162
+ showNames,
163
+ rowsDesired,
164
+ columnsDesired,
165
+ tryFill,
166
+ frameBuilder
167
+ }
168
+ ) });
169
+ }
170
+ function CameraGridLayout({
171
+ items,
172
+ spacing,
173
+ showNames,
174
+ rowsDesired,
175
+ columnsDesired,
176
+ tryFill,
177
+ frameBuilder
178
+ }) {
179
+ return /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0 }, children: /* @__PURE__ */ jsx(
180
+ ResponsiveAbsoluteLayout,
181
+ {
182
+ items,
183
+ spacing,
184
+ showNames,
185
+ rowsDesired,
186
+ columnsDesired,
187
+ tryFill,
188
+ frameBuilder
189
+ }
190
+ ) });
191
+ }
192
+ function ResponsiveAbsoluteLayout(props) {
193
+ const { items } = props;
194
+ return /* @__PURE__ */ jsx(SizeObserver, { children: ({ width, height }) => {
195
+ if (width <= 0 || height <= 0) return null;
196
+ return renderAbsoluteItems({ ...props, width, height, slots: items.length });
197
+ } });
198
+ }
199
+ function renderAbsoluteItems({
200
+ items,
201
+ spacing,
202
+ showNames,
203
+ rowsDesired,
204
+ columnsDesired,
205
+ tryFill,
206
+ frameBuilder,
207
+ width,
208
+ height,
209
+ slots
210
+ }) {
211
+ const positioned = [];
212
+ if (rowsDesired === 0 && columnsDesired === 0) {
213
+ const [rows, cols] = layoutCameras(
214
+ items.map((item) => item.publication),
215
+ width,
216
+ height
217
+ );
218
+ const availableWidth = Math.max(width - spacing * (cols - 1), 0);
219
+ const availableHeight = Math.max(height - spacing * (rows - 1), 0);
220
+ const cellWidth = availableWidth / cols;
221
+ const cellHeight = availableHeight / rows;
222
+ for (let row = 0; row < rows; row += 1) {
223
+ for (let col = 0; col < cols; col += 1) {
224
+ const index = row * cols + col;
225
+ const item = items[index];
226
+ if (!item) break;
227
+ positioned.push(
228
+ absoluteCell({
229
+ key: index,
230
+ left: col * (cellWidth + spacing),
231
+ top: row * (cellHeight + spacing),
232
+ width: cellWidth,
233
+ height: cellHeight,
234
+ item,
235
+ showNames,
236
+ frameBuilder
237
+ })
238
+ );
239
+ }
240
+ }
241
+ return /* @__PURE__ */ jsx(Fragment, { children: positioned });
242
+ }
243
+ let x = 0;
244
+ let y = 0;
245
+ if (rowsDesired > 0 || columnsDesired > 0 || Math.min(width / height, height / width) > 0.5 || slots < 4 && tryFill) {
246
+ let rows;
247
+ let cols;
248
+ if (width < height) {
249
+ rows = Math.trunc(
250
+ rowsDesired > 0 ? rowsDesired : columnsDesired > 0 ? slots / columnsDesired : Math.ceil(Math.sqrt(slots))
251
+ );
252
+ cols = columnsDesired > 0 ? columnsDesired : Math.ceil(slots / rows);
253
+ } else {
254
+ cols = Math.trunc(
255
+ columnsDesired > 0 ? columnsDesired : rowsDesired > 0 ? slots / rowsDesired : Math.ceil(Math.sqrt(slots))
256
+ );
257
+ rows = rowsDesired > 0 ? rowsDesired : Math.ceil(slots / cols);
258
+ }
259
+ const cellWidth = width / cols + 1 - spacing * (cols - 1) / cols;
260
+ const cellHeight = height / rows - spacing * (rows - 1) / rows;
261
+ for (let row = 0; row < rows; row += 1) {
262
+ for (let col = 0; col < cols; col += 1) {
263
+ const index = col + row * cols;
264
+ const item = items[index];
265
+ if (!item) continue;
266
+ positioned.push(
267
+ absoluteCell({
268
+ key: index,
269
+ left: col * cellWidth + spacing * col,
270
+ top: row * cellHeight + spacing * row,
271
+ width: cellWidth,
272
+ height: cellHeight,
273
+ item,
274
+ showNames,
275
+ frameBuilder
276
+ })
277
+ );
278
+ }
279
+ }
280
+ } else {
281
+ const totalSpace = width * height;
282
+ let rowUsedSpace = totalSpace;
283
+ let rows = 1;
284
+ let vertRows = false;
285
+ for (let i = 1; i < 10; i += 0.1) {
286
+ const floored = Math.floor(i);
287
+ const itemSize = height / i;
288
+ const usedSpace = itemSize * itemSize * Math.max(slots, 1);
289
+ if (itemSize * Math.ceil(slots / floored) <= width && itemSize * floored <= height && usedSpace <= totalSpace && totalSpace - usedSpace < rowUsedSpace) {
290
+ rows = i;
291
+ rowUsedSpace = totalSpace - usedSpace;
292
+ vertRows = true;
293
+ }
294
+ }
295
+ for (let i = 1; i < 10; i += 0.1) {
296
+ const floored = Math.floor(i);
297
+ const itemSize = width / i;
298
+ const usedSpace = itemSize * itemSize * Math.max(slots, 1);
299
+ if (itemSize * Math.ceil(slots / floored) <= height && itemSize * floored <= width && usedSpace <= totalSpace && totalSpace - usedSpace < rowUsedSpace) {
300
+ rows = i;
301
+ rowUsedSpace = totalSpace - usedSpace;
302
+ vertRows = false;
303
+ }
304
+ }
305
+ if (vertRows) {
306
+ const itemSize = (height - spacing * rows) / rows;
307
+ for (let index = 0; index < items.length; index += 1) {
308
+ const item = items[index];
309
+ positioned.push(
310
+ absoluteCell({
311
+ key: index,
312
+ left: x,
313
+ top: y,
314
+ width: itemSize,
315
+ height: itemSize,
316
+ item,
317
+ showNames,
318
+ frameBuilder
319
+ })
320
+ );
321
+ x += itemSize + spacing;
322
+ if (x + itemSize > width) {
323
+ x = spacing;
324
+ y += itemSize + spacing;
325
+ }
326
+ }
327
+ } else {
328
+ const itemSize = (width - spacing * rows) / rows;
329
+ for (let index = 0; index < items.length; index += 1) {
330
+ const item = items[index];
331
+ positioned.push(
332
+ absoluteCell({
333
+ key: index,
334
+ left: x,
335
+ top: y,
336
+ width: itemSize,
337
+ height: itemSize,
338
+ item,
339
+ showNames,
340
+ frameBuilder
341
+ })
342
+ );
343
+ y += itemSize + spacing;
344
+ if (y + itemSize > height) {
345
+ y = spacing;
346
+ x += itemSize + spacing;
347
+ }
348
+ }
349
+ }
350
+ }
351
+ return /* @__PURE__ */ jsx(Fragment, { children: positioned });
352
+ }
353
+ function absoluteCell({
354
+ key,
355
+ left,
356
+ top,
357
+ width,
358
+ height,
359
+ item,
360
+ showNames,
361
+ frameBuilder
362
+ }) {
363
+ return /* @__PURE__ */ jsx(
364
+ "div",
365
+ {
366
+ style: {
367
+ position: "absolute",
368
+ left,
369
+ top,
370
+ width,
371
+ height,
372
+ overflow: "hidden"
373
+ },
374
+ children: frameBuilder({
375
+ participant: item.participant,
376
+ publication: item.publication,
377
+ trackNode: item.trackNode,
378
+ showName: showNames
379
+ })
380
+ },
381
+ key
382
+ );
383
+ }
384
+ function SizeObserver({ children }) {
385
+ const ref = React.useRef(null);
386
+ const [size, setSize] = React.useState({
387
+ width: 0,
388
+ height: 0
389
+ });
390
+ React.useLayoutEffect(() => {
391
+ const element = ref.current;
392
+ if (!element) return;
393
+ const observer = new ResizeObserver(([entry]) => {
394
+ const rect = entry.contentRect;
395
+ setSize({ width: rect.width, height: rect.height });
396
+ });
397
+ observer.observe(element);
398
+ return () => observer.disconnect();
399
+ }, []);
400
+ return /* @__PURE__ */ jsx("div", { ref, className: "absolute inset-0", children: children(size) });
401
+ }
402
+ export {
403
+ CameraGrid,
404
+ TrackSource
405
+ };
@@ -1,8 +1,9 @@
1
1
  import type { ReactElement } from "react";
2
2
  import { Room } from "livekit-client";
3
3
  import { MeetingController } from "./meeting-scope";
4
- export declare function MeetingControls({ controller: providedController, spacing, }: {
4
+ export declare function MeetingControls({ controller: providedController, onDisconnect, spacing, }: {
5
5
  controller?: MeetingController;
6
+ onDisconnect?: () => void;
6
7
  spacing?: number;
7
8
  }): ReactElement;
8
9
  export declare function CameraToggle({ controller }: {
@@ -11,8 +12,9 @@ export declare function CameraToggle({ controller }: {
11
12
  export declare function MicToggle({ controller }: {
12
13
  controller?: MeetingController;
13
14
  }): ReactElement | null;
14
- export declare function ConnectionButton({ controller }: {
15
+ export declare function ConnectionButton({ controller, onDisconnect, }: {
15
16
  controller?: MeetingController;
17
+ onDisconnect?: () => void;
16
18
  }): ReactElement;
17
19
  export declare function ChangeSettings({ room }: {
18
20
  room: Room;
@@ -36,6 +36,7 @@ function useControllerVersion(controller) {
36
36
  }
37
37
  function MeetingControls({
38
38
  controller: providedController,
39
+ onDisconnect,
39
40
  spacing = 5
40
41
  }) {
41
42
  const controller = useMeetingController(providedController);
@@ -47,7 +48,7 @@ function MeetingControls({
47
48
  className: "flex flex-wrap items-center justify-center",
48
49
  style: { gap: spacing },
49
50
  children: [
50
- /* @__PURE__ */ jsx(ConnectionButton, { controller }),
51
+ /* @__PURE__ */ jsx(ConnectionButton, { controller, onDisconnect }),
51
52
  hasLocalParticipant ? /* @__PURE__ */ jsxs(Fragment, { children: [
52
53
  /* @__PURE__ */ jsx(MicToggle, { controller }),
53
54
  /* @__PURE__ */ jsx(CameraToggle, { controller }),
@@ -166,7 +167,10 @@ function MicToggle({ controller }) {
166
167
  }
167
168
  );
168
169
  }
169
- function ConnectionButton({ controller }) {
170
+ function ConnectionButton({
171
+ controller,
172
+ onDisconnect
173
+ }) {
170
174
  const resolvedController = useMeetingController(controller);
171
175
  useControllerVersion(resolvedController);
172
176
  const state = resolvedController.livekitRoom.state;
@@ -178,7 +182,9 @@ function ConnectionButton({ controller }) {
178
182
  destructive: true,
179
183
  icon: /* @__PURE__ */ jsx(Phone, {}),
180
184
  onClick: () => {
181
- void resolvedController.disconnect();
185
+ void resolvedController.disconnect().catch((error) => {
186
+ console.warn("unable to disconnect meeting", error);
187
+ }).finally(() => onDisconnect?.());
182
188
  }
183
189
  }
184
190
  );
@@ -0,0 +1,17 @@
1
+ import type { ReactElement } from "react";
2
+ import { type MeetingController, type MeetingFastConnectOptions } from "./meeting-scope";
3
+ export interface MeetingLobbyJoinOptions {
4
+ enableVideo: boolean;
5
+ enableAudio: boolean;
6
+ videoUnavailable: boolean;
7
+ audioUnavailable: boolean;
8
+ videoDeviceId?: string;
9
+ audioDeviceId?: string;
10
+ audioOutputDeviceId?: string;
11
+ }
12
+ export declare function meetingFastConnectOptions(options: MeetingLobbyJoinOptions): MeetingFastConnectOptions;
13
+ export declare function MeetingLobby({ controller: providedController, onCancel, onJoin, }: {
14
+ controller?: MeetingController;
15
+ onCancel?: () => void;
16
+ onJoin?: (options: MeetingLobbyJoinOptions) => void | Promise<void>;
17
+ }): ReactElement;