@xipkg/calls-compactview 0.0.0

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.
package/dist/index.mjs ADDED
@@ -0,0 +1,1245 @@
1
+ import { createContext, useMemo, useState, useEffect, useRef, useCallback, useContext } from 'react';
2
+ import { useDraggable, useSensors, useSensor, PointerSensor, DndContext, useDndMonitor, DragOverlay, useDroppable } from '@dnd-kit/core';
3
+ import { useRoom, useCallsNavigation, useCalls } from '@xipkg/calls-providers';
4
+ import { restrictToWindowEdges } from '@dnd-kit/modifiers';
5
+ import { useTracks, usePersistentUserChoices, useLocalParticipant, useTrackToggle, useAudioWaveform, RoomAudioRenderer } from '@livekit/components-react';
6
+ import { CSS } from '@dnd-kit/utilities';
7
+ import { Track } from 'livekit-client';
8
+ import { useCallStore, useFeaturesStore, useFocusModeStore } from '@xipkg/calls-store';
9
+ import { useScreenShareCleanup, useVideoBlur, useModeSync, useParticipantJoinSync, useParticipantSounds } from '@xipkg/calls-hooks';
10
+ import { useMedia } from '@xipkg/calls-utils';
11
+ import { Button } from '@xipkg/button';
12
+ import { ArrowLeft, ArrowRight, Users, Account, External, ChevronUp, WhiteBoard, Maximize, ArrowUp, MicrophoneOff, RedLine, ChevronBottom } from '@xipkg/icons';
13
+ import { cn } from '@xipkg/utils';
14
+ import { createPortal } from 'react-dom';
15
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
16
+ import { ParticipantTile, DevicesBar, ScreenShareButton, DisconnectButton, PermissionsDialog, TrackMutedIndicator, ParticipantName, RaisedHandIndicator } from '@xipkg/calls-ui';
17
+ import { Tooltip, TooltipTrigger, TooltipContent } from '@xipkg/tooltip';
18
+ import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuLabel, DropdownMenuItem } from '@xipkg/dropdown';
19
+ import { ChatButton, useChatStore, Chat } from '@xipkg/calls-chat';
20
+ import { RaiseHandButton } from '@xipkg/calls-risehand';
21
+
22
+ // src/ui/CompactView.tsx
23
+
24
+ // src/constants.ts
25
+ var HEADER_HEIGHT_PX = 64;
26
+ var BOARD_BOTTOM_TOOLBAR_PX = 64;
27
+ var BOTTOM_OFFSET_DEFAULT_PX = 16;
28
+ var COMPACT_BOTTOM_BAR_PX = 40;
29
+ var TILE_GAP_PX = 8;
30
+ var EXPANDED_VIDEO_PADDING_VERTICAL_PX = 8;
31
+ var COMPACT_VIDEO_AREA_MARGIN_PX = 8;
32
+ var COMPACT_BOTTOM_OFFSET_BOARD_PX = BOARD_BOTTOM_TOOLBAR_PX + TILE_GAP_PX;
33
+ var COMPACT_PANEL_WIDTH_PX = 360;
34
+ var TILE_HEIGHT_16_9_PX = Math.round(COMPACT_PANEL_WIDTH_PX * 9 / 16);
35
+ var PIP_PANEL_WIDTH_PX = 380;
36
+ var PIP_BAR_HEIGHT_PX = 48;
37
+ var PIP_VIDEO_BAR_GAP_PX = 4;
38
+ var PIP_EXPANDED_INNER_PADDING_PX = 4;
39
+ var PIP_RESERVED_HEIGHT_PX = PIP_BAR_HEIGHT_PX + PIP_VIDEO_BAR_GAP_PX + EXPANDED_VIDEO_PADDING_VERTICAL_PX + PIP_EXPANDED_INNER_PADDING_PX;
40
+ var PIP_TILE_HEIGHT_16_9_PX = Math.round(PIP_PANEL_WIDTH_PX * 9 / 16);
41
+ var PIP_EXTRA_HEIGHT_PX = 36;
42
+ var PIP_EXTRA_HEIGHT_EXPANDED_PX = 12;
43
+ var PIP_HEIGHT_BASIC_PX = PIP_BAR_HEIGHT_PX + PIP_TILE_HEIGHT_16_9_PX + EXPANDED_VIDEO_PADDING_VERTICAL_PX;
44
+ function getPipHeightExpandedPx(participantCount) {
45
+ const n = Math.min(Math.max(1, participantCount), 4);
46
+ return PIP_BAR_HEIGHT_PX + n * PIP_TILE_HEIGHT_16_9_PX + (n - 1) * TILE_GAP_PX + EXPANDED_VIDEO_PADDING_VERTICAL_PX + PIP_EXTRA_HEIGHT_PX + PIP_EXTRA_HEIGHT_EXPANDED_PX;
47
+ }
48
+
49
+ // src/hooks/useCompactAvailableHeight.ts
50
+ function calcAvailableHeight(isOnBoardPage) {
51
+ if (typeof window === "undefined") return 400;
52
+ const bottomOffset = isOnBoardPage ? COMPACT_BOTTOM_OFFSET_BOARD_PX : BOTTOM_OFFSET_DEFAULT_PX;
53
+ return window.innerHeight - HEADER_HEIGHT_PX - bottomOffset;
54
+ }
55
+ function useCompactAvailableHeight(isOnBoardPage) {
56
+ const initialHeight = useMemo(() => calcAvailableHeight(isOnBoardPage), [isOnBoardPage]);
57
+ const [availableHeight, setAvailableHeight] = useState(initialHeight);
58
+ useEffect(() => {
59
+ setAvailableHeight(initialHeight);
60
+ const onResize = () => setAvailableHeight(calcAvailableHeight(isOnBoardPage));
61
+ window.addEventListener("resize", onResize);
62
+ return () => window.removeEventListener("resize", onResize);
63
+ }, [isOnBoardPage, initialHeight]);
64
+ return availableHeight;
65
+ }
66
+ var useCompactNavigation = () => {
67
+ const [currentParticipantIndex, setCurrentParticipantIndex] = useState(0);
68
+ const participants = useTracks(
69
+ [
70
+ { source: Track.Source.Camera, withPlaceholder: true },
71
+ { source: Track.Source.ScreenShare, withPlaceholder: false }
72
+ ],
73
+ {
74
+ onlySubscribed: false
75
+ // Получаем все треки, включая неподписанные для корректного подсчета участников
76
+ }
77
+ );
78
+ useScreenShareCleanup(participants);
79
+ const currentParticipant = participants[currentParticipantIndex] || null;
80
+ const totalParticipants = participants.length;
81
+ const canGoNext = currentParticipantIndex < totalParticipants - 1;
82
+ const canGoPrev = currentParticipantIndex > 0;
83
+ const goToNext = () => {
84
+ if (canGoNext) {
85
+ setCurrentParticipantIndex((prev) => prev + 1);
86
+ }
87
+ };
88
+ const goToPrev = () => {
89
+ if (canGoPrev) {
90
+ setCurrentParticipantIndex((prev) => prev - 1);
91
+ }
92
+ };
93
+ const goToParticipant = (index) => {
94
+ if (index >= 0 && index < totalParticipants) {
95
+ setCurrentParticipantIndex(index);
96
+ }
97
+ };
98
+ useEffect(() => {
99
+ if (currentParticipantIndex >= totalParticipants && totalParticipants > 0) {
100
+ setCurrentParticipantIndex(Math.max(0, totalParticipants - 1));
101
+ }
102
+ }, [totalParticipants, currentParticipantIndex]);
103
+ return {
104
+ currentParticipant,
105
+ participants,
106
+ currentIndex: currentParticipantIndex,
107
+ totalParticipants,
108
+ canGoNext,
109
+ canGoPrev,
110
+ goToNext,
111
+ goToPrev,
112
+ goToParticipant
113
+ };
114
+ };
115
+ function copyStylesToWindow(targetWindow) {
116
+ [...document.styleSheets].forEach((styleSheet) => {
117
+ try {
118
+ const cssRules = [...styleSheet.cssRules].map((rule) => rule.cssText).join("");
119
+ const style = document.createElement("style");
120
+ style.textContent = cssRules;
121
+ targetWindow.document.head.appendChild(style);
122
+ } catch {
123
+ if (styleSheet.href) {
124
+ const link = document.createElement("link");
125
+ link.rel = "stylesheet";
126
+ link.type = styleSheet.type || "text/css";
127
+ link.media = styleSheet.media.mediaText;
128
+ link.href = styleSheet.href;
129
+ targetWindow.document.head.appendChild(link);
130
+ }
131
+ }
132
+ });
133
+ try {
134
+ document.fonts.forEach((font) => {
135
+ targetWindow.document.fonts.add(font);
136
+ });
137
+ } catch {
138
+ }
139
+ }
140
+ function useDocumentPiP({
141
+ width = 380,
142
+ height = 270,
143
+ enabled = true,
144
+ microphoneActive = false,
145
+ cameraActive = false
146
+ } = {}) {
147
+ const [pipWindow, setPipWindow] = useState(null);
148
+ const pipWindowRef = useRef(null);
149
+ const micActiveRef = useRef(microphoneActive);
150
+ const camActiveRef = useRef(cameraActive);
151
+ micActiveRef.current = microphoneActive;
152
+ camActiveRef.current = cameraActive;
153
+ const isSupported = typeof window !== "undefined" && "documentPictureInPicture" in window;
154
+ useEffect(() => {
155
+ if (!enabled) return;
156
+ try {
157
+ navigator.mediaSession.setMicrophoneActive(microphoneActive);
158
+ } catch {
159
+ }
160
+ try {
161
+ navigator.mediaSession.setCameraActive(cameraActive);
162
+ } catch {
163
+ }
164
+ }, [enabled, microphoneActive, cameraActive]);
165
+ const openPiP = useCallback(
166
+ async (overrides) => {
167
+ if (!isSupported || !enabled) return null;
168
+ if (pipWindowRef.current) return pipWindowRef.current;
169
+ const w = overrides?.width ?? width;
170
+ const h = overrides?.height ?? height;
171
+ try {
172
+ const pip = await window.documentPictureInPicture.requestWindow({
173
+ width: w,
174
+ height: h
175
+ });
176
+ copyStylesToWindow(pip);
177
+ pip.document.body.style.margin = "0";
178
+ pip.document.body.style.overflow = "hidden";
179
+ pip.document.body.style.background = "var(--xi-gray-0, #fff)";
180
+ pipWindowRef.current = pip;
181
+ setPipWindow(pip);
182
+ pip.addEventListener("pagehide", () => {
183
+ pipWindowRef.current = null;
184
+ setPipWindow(null);
185
+ });
186
+ return pip;
187
+ } catch {
188
+ return null;
189
+ }
190
+ },
191
+ [isSupported, enabled, width, height]
192
+ );
193
+ const closePiP = useCallback(() => {
194
+ if (pipWindowRef.current) {
195
+ pipWindowRef.current.close();
196
+ }
197
+ }, []);
198
+ useEffect(() => {
199
+ if (!isSupported || !enabled) return;
200
+ const action = "enterpictureinpicture";
201
+ try {
202
+ navigator.mediaSession.setActionHandler(action, () => void openPiP());
203
+ } catch {
204
+ }
205
+ return () => {
206
+ try {
207
+ navigator.mediaSession.setActionHandler(action, null);
208
+ } catch {
209
+ }
210
+ };
211
+ }, [isSupported, enabled, openPiP]);
212
+ useEffect(() => {
213
+ if (!enabled) return;
214
+ const handleVisibilityChange = () => {
215
+ if (!document.hidden && pipWindowRef.current) {
216
+ closePiP();
217
+ }
218
+ };
219
+ document.addEventListener("visibilitychange", handleVisibilityChange);
220
+ return () => document.removeEventListener("visibilitychange", handleVisibilityChange);
221
+ }, [enabled, closePiP]);
222
+ useEffect(() => {
223
+ return () => {
224
+ pipWindowRef.current?.close();
225
+ pipWindowRef.current = null;
226
+ };
227
+ }, []);
228
+ const resizePiP = useCallback((w, h) => {
229
+ if (pipWindowRef.current) {
230
+ try {
231
+ pipWindowRef.current.resizeTo(w, h);
232
+ } catch {
233
+ }
234
+ }
235
+ }, []);
236
+ return {
237
+ isSupported,
238
+ pipWindow,
239
+ isPiPActive: pipWindow !== null,
240
+ openPiP,
241
+ closePiP,
242
+ resizePiP
243
+ };
244
+ }
245
+ var PiPContext = createContext(null);
246
+ function usePiP() {
247
+ const ctx = useContext(PiPContext);
248
+ return ctx;
249
+ }
250
+ function getMediaStreamTrack(pub) {
251
+ return pub?.track?.mediaStreamTrack;
252
+ }
253
+ function useMediaCaptureLive() {
254
+ const { microphoneTrack, cameraTrack } = useLocalParticipant();
255
+ const micTrack = getMediaStreamTrack(microphoneTrack);
256
+ const camTrack = getMediaStreamTrack(cameraTrack);
257
+ const micLive = !!micTrack && micTrack.readyState === "live" && micTrack.enabled;
258
+ const camLive = !!camTrack && camTrack.readyState === "live" && camTrack.enabled;
259
+ return { micLive, camLive };
260
+ }
261
+ function PiPProvider({ children }) {
262
+ const isMobile = useMedia("(max-width: 720px)");
263
+ const { micLive, camLive } = useMediaCaptureLive();
264
+ const { room } = useRoom();
265
+ const compactViewMode = useCallStore((s) => s.compactViewMode);
266
+ const { totalParticipants } = useCompactNavigation();
267
+ const {
268
+ pipWindow,
269
+ closePiP,
270
+ resizePiP,
271
+ openPiP: openPiPRaw,
272
+ isSupported
273
+ } = useDocumentPiP({
274
+ enabled: !isMobile,
275
+ width: PIP_PANEL_WIDTH_PX,
276
+ height: PIP_HEIGHT_BASIC_PX,
277
+ microphoneActive: micLive,
278
+ cameraActive: camLive
279
+ });
280
+ const openPiP = useCallback(async () => {
281
+ const height = compactViewMode === "basic" ? PIP_HEIGHT_BASIC_PX : getPipHeightExpandedPx(totalParticipants);
282
+ await openPiPRaw({ height });
283
+ }, [openPiPRaw, compactViewMode, totalParticipants]);
284
+ useEffect(() => {
285
+ if (!room || !pipWindow) return;
286
+ if (room.state !== "connected") {
287
+ closePiP();
288
+ return;
289
+ }
290
+ const handleState = () => {
291
+ if (room.state !== "connected") closePiP();
292
+ };
293
+ room.on("connectionStateChanged", handleState);
294
+ return () => {
295
+ room.off("connectionStateChanged", handleState);
296
+ };
297
+ }, [room, pipWindow, closePiP]);
298
+ useEffect(() => {
299
+ if (!pipWindow || !resizePiP) return;
300
+ const height = compactViewMode === "basic" ? PIP_HEIGHT_BASIC_PX + PIP_EXTRA_HEIGHT_PX : getPipHeightExpandedPx(totalParticipants);
301
+ resizePiP(PIP_PANEL_WIDTH_PX, height);
302
+ }, [pipWindow, resizePiP, compactViewMode, totalParticipants]);
303
+ const value = {
304
+ openPiP,
305
+ isPiPActive: pipWindow !== null,
306
+ isSupported: isSupported && !isMobile
307
+ };
308
+ return /* @__PURE__ */ jsxs(PiPContext.Provider, { value, children: [
309
+ children,
310
+ pipWindow && createPortal(/* @__PURE__ */ jsx(PiPCompactCall, { pipWindow }), pipWindow.document.body)
311
+ ] });
312
+ }
313
+ function CompactCallCollapsedBar({
314
+ participant,
315
+ audioTrack,
316
+ onExpand,
317
+ className
318
+ }) {
319
+ const { bars } = useAudioWaveform(audioTrack ?? void 0, {
320
+ barCount: 24,
321
+ volMultiplier: 4,
322
+ updateInterval: 50
323
+ });
324
+ return /* @__PURE__ */ jsxs(
325
+ "div",
326
+ {
327
+ className: cn(
328
+ "bg-gray-20 flex items-center gap-2 rounded-2xl px-2 py-2 shadow-lg",
329
+ className
330
+ ),
331
+ children: [
332
+ /* @__PURE__ */ jsx("div", { className: "bg-gray-0/80 flex h-6 max-w-[45%] min-w-0 shrink gap-1.5 rounded-lg px-1.5 py-1 backdrop-blur", children: participant ? /* @__PURE__ */ jsxs(Fragment, { children: [
333
+ /* @__PURE__ */ jsx(
334
+ TrackMutedIndicator,
335
+ {
336
+ trackRef: {
337
+ participant,
338
+ source: Track.Source.Microphone
339
+ },
340
+ show: "muted",
341
+ style: { marginRight: "0.45rem", background: "transparent" }
342
+ }
343
+ ),
344
+ /* @__PURE__ */ jsx("span", { className: "flex min-w-0 truncate", children: /* @__PURE__ */ jsx(ParticipantName, { participant }) })
345
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
346
+ /* @__PURE__ */ jsxs("div", { className: "relative w-3 shrink-0", children: [
347
+ /* @__PURE__ */ jsx(MicrophoneOff, { className: "absolute h-4 w-4 fill-gray-100" }),
348
+ /* @__PURE__ */ jsx(RedLine, { className: "fill-red-80 absolute h-4 w-4" })
349
+ ] }),
350
+ /* @__PURE__ */ jsx("span", { className: "text-xs-base-size leading-4 text-gray-100", children: "\u041D\u0435\u0442 \u0443\u0447\u0430\u0441\u0442\u043D\u0438\u043A\u043E\u0432" })
351
+ ] }) }),
352
+ participant && /* @__PURE__ */ jsx(RaisedHandIndicator, { participant: participant ?? "unknown" }),
353
+ /* @__PURE__ */ jsx("div", { className: "flex min-w-0 flex-1 items-center justify-center gap-0.5 py-1", children: bars.length > 0 ? /* @__PURE__ */ jsx("div", { className: "flex h-4 items-end justify-center gap-0.5", children: bars.map((h, i) => /* @__PURE__ */ jsx(
354
+ "div",
355
+ {
356
+ className: "bg-brand-80 w-0.5 min-w-0.5 rounded-full transition-all duration-75",
357
+ style: { height: `${Math.min(100, Math.max(20, h * 150))}%` }
358
+ },
359
+ i
360
+ )) }) : /* @__PURE__ */ jsx("div", { className: "bg-brand-60 h-2 w-12 rounded-full" }) }),
361
+ /* @__PURE__ */ jsx(
362
+ Button,
363
+ {
364
+ size: "icon",
365
+ variant: "none",
366
+ onClick: onExpand,
367
+ className: "bg-brand-100 hover:bg-brand-100/80 h-8 w-8 shrink-0 rounded-xl p-0 text-gray-100",
368
+ "aria-label": "\u0420\u0430\u0437\u0432\u0435\u0440\u043D\u0443\u0442\u044C",
369
+ children: /* @__PURE__ */ jsx(ChevronBottom, { className: "fill-gray-0 h-4 w-4" })
370
+ }
371
+ )
372
+ ]
373
+ }
374
+ );
375
+ }
376
+ function CompactNavigationControls({
377
+ canPrev,
378
+ canNext,
379
+ onPrev,
380
+ onNext,
381
+ currentIndex,
382
+ totalParticipants
383
+ }) {
384
+ if (totalParticipants <= 1) {
385
+ return null;
386
+ }
387
+ return /* @__PURE__ */ jsxs("div", { className: "pointer-events-none absolute inset-0", children: [
388
+ /* @__PURE__ */ jsx("div", { className: "pointer-events-auto absolute top-2 left-1/2 -translate-x-1/2", children: /* @__PURE__ */ jsx("div", { className: "bg-gray-10/80 flex gap-1 rounded-full px-2 py-1 transition-opacity duration-200 sm:opacity-0 sm:group-hover:opacity-100", children: Array.from({ length: totalParticipants }, (_, index) => /* @__PURE__ */ jsx(
389
+ "button",
390
+ {
391
+ type: "button",
392
+ onClick: () => {
393
+ },
394
+ className: `h-1.5 w-1.5 rounded-full transition-colors ${index === currentIndex ? "bg-gray-100" : "bg-gray-100/50 hover:bg-gray-100/75"}`,
395
+ "aria-label": `\u0423\u0447\u0430\u0441\u0442\u043D\u0438\u043A ${index + 1}`
396
+ },
397
+ index
398
+ )) }) }),
399
+ /* @__PURE__ */ jsx("div", { className: "pointer-events-auto absolute top-1/2 left-2 -translate-y-1/2 transition-opacity duration-200 sm:opacity-0 sm:group-hover:opacity-100", children: /* @__PURE__ */ jsxs(
400
+ "button",
401
+ {
402
+ type: "button",
403
+ disabled: !canPrev,
404
+ onClick: onPrev,
405
+ className: "bg-gray-10/80 hover:bg-gray-10 z-10 flex items-center justify-center rounded-full fill-gray-100 p-2 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50",
406
+ children: [
407
+ /* @__PURE__ */ jsx(ArrowLeft, { className: "h-4 w-4" }),
408
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: "\u041F\u0440\u0435\u0434\u044B\u0434\u0443\u0449\u0438\u0439 \u0443\u0447\u0430\u0441\u0442\u043D\u0438\u043A" })
409
+ ]
410
+ }
411
+ ) }),
412
+ /* @__PURE__ */ jsx("div", { className: "pointer-events-auto absolute top-1/2 right-2 -translate-y-1/2 transition-opacity duration-200 sm:opacity-0 sm:group-hover:opacity-100", children: /* @__PURE__ */ jsxs(
413
+ "button",
414
+ {
415
+ type: "button",
416
+ disabled: !canNext,
417
+ onClick: onNext,
418
+ className: "bg-gray-10/80 hover:bg-gray-10 z-10 flex items-center justify-center rounded-full fill-gray-100 p-2 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50",
419
+ children: [
420
+ /* @__PURE__ */ jsx(ArrowRight, { className: "h-4 w-4" }),
421
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: "\u0421\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u0439 \u0443\u0447\u0430\u0441\u0442\u043D\u0438\u043A" })
422
+ ]
423
+ }
424
+ ) })
425
+ ] });
426
+ }
427
+ function CompactMultiViewControls({
428
+ canPrev,
429
+ canNext,
430
+ onPrev,
431
+ onNext
432
+ }) {
433
+ return /* @__PURE__ */ jsxs("div", { className: "pointer-events-none absolute inset-0", children: [
434
+ canPrev && /* @__PURE__ */ jsx("div", { className: "pointer-events-auto absolute top-2 left-1/2 z-10 -translate-x-1/2", children: /* @__PURE__ */ jsx(
435
+ "button",
436
+ {
437
+ type: "button",
438
+ onClick: onPrev,
439
+ className: "bg-gray-10/80 hover:bg-gray-10 flex items-center justify-center rounded-full fill-gray-100 p-2",
440
+ "aria-label": "\u041B\u0438\u0441\u0442\u0430\u0442\u044C \u0432\u0432\u0435\u0440\u0445",
441
+ children: /* @__PURE__ */ jsx(ArrowUp, { className: "h-4 w-4 fill-inherit" })
442
+ }
443
+ ) }),
444
+ canNext && /* @__PURE__ */ jsx("div", { className: "pointer-events-auto absolute bottom-2 left-1/2 z-10 -translate-x-1/2", children: /* @__PURE__ */ jsx(
445
+ "button",
446
+ {
447
+ type: "button",
448
+ onClick: onNext,
449
+ className: "bg-gray-10/80 hover:bg-gray-10 flex items-center justify-center rounded-full fill-gray-100 p-2",
450
+ "aria-label": "\u041B\u0438\u0441\u0442\u0430\u0442\u044C \u0432\u043D\u0438\u0437",
451
+ children: /* @__PURE__ */ jsx(ArrowUp, { className: "h-4 w-4 rotate-180 fill-inherit" })
452
+ }
453
+ ) })
454
+ ] });
455
+ }
456
+ function CompactCallVideoArea({
457
+ isMobile,
458
+ isCollapsed,
459
+ onCollapsedChange,
460
+ withOutShadows,
461
+ dragAttributes = {},
462
+ dragListeners = {},
463
+ compactViewMode,
464
+ currentParticipant,
465
+ currentAudioTrack,
466
+ totalParticipants,
467
+ canGoPrev,
468
+ canGoNext,
469
+ onPrev,
470
+ onNext,
471
+ currentIndex,
472
+ multiVisibleParticipants,
473
+ multiTileHeightPx,
474
+ multiCanPrev,
475
+ multiCanNext,
476
+ onMultiPrev,
477
+ onMultiNext
478
+ }) {
479
+ const pip = usePiP();
480
+ const showPiPButton = pip?.isSupported && currentParticipant;
481
+ const emptyState = /* @__PURE__ */ jsx("div", { className: "bg-gray-40 flex h-full w-full items-center justify-center text-gray-100", children: /* @__PURE__ */ jsx("span", { className: "text-sm", children: "\u041D\u0435\u0442 \u0443\u0447\u0430\u0441\u0442\u043D\u0438\u043A\u043E\u0432" }) });
482
+ if (isMobile && isCollapsed) {
483
+ return /* @__PURE__ */ jsx(
484
+ CompactCallCollapsedBar,
485
+ {
486
+ participant: currentParticipant?.participant ?? null,
487
+ audioTrack: currentAudioTrack ?? null,
488
+ onExpand: () => onCollapsedChange(false),
489
+ className: cn("mb-2", withOutShadows ? "" : "shadow-lg")
490
+ }
491
+ );
492
+ }
493
+ return /* @__PURE__ */ jsxs(
494
+ "div",
495
+ {
496
+ ...isMobile ? {} : { ...dragAttributes, ...dragListeners },
497
+ className: cn(
498
+ "group relative mb-2 flex overflow-hidden rounded-2xl",
499
+ withOutShadows ? "" : "shadow-lg",
500
+ isMobile ? "h-auto w-full items-center justify-center" : "w-[360px] cursor-move flex-col",
501
+ !isMobile && compactViewMode === "basic" && "shrink-0",
502
+ !isMobile && compactViewMode === "expanded" && "h-auto"
503
+ ),
504
+ style: !isMobile && compactViewMode === "basic" ? { height: TILE_HEIGHT_16_9_PX } : void 0,
505
+ children: [
506
+ isMobile ? currentParticipant ? /* @__PURE__ */ jsx(
507
+ ParticipantTile,
508
+ {
509
+ trackRef: currentParticipant,
510
+ participant: currentParticipant.participant,
511
+ className: "h-full w-full",
512
+ isFocusToggleDisable: true
513
+ }
514
+ ) : emptyState : compactViewMode === "expanded" ? /* @__PURE__ */ jsxs(
515
+ "div",
516
+ {
517
+ className: "relative flex flex-col gap-2 overflow-hidden rounded-2xl p-1",
518
+ style: { gap: TILE_GAP_PX },
519
+ children: [
520
+ multiVisibleParticipants.length === 0 ? emptyState : multiVisibleParticipants.map((trackRef) => /* @__PURE__ */ jsx(
521
+ "div",
522
+ {
523
+ className: "aspect-video w-full shrink-0 overflow-hidden rounded-xl",
524
+ style: { minHeight: multiTileHeightPx },
525
+ children: /* @__PURE__ */ jsx(
526
+ ParticipantTile,
527
+ {
528
+ trackRef,
529
+ participant: trackRef.participant,
530
+ className: "h-full w-full [&_video]:object-cover",
531
+ isFocusToggleDisable: true
532
+ }
533
+ )
534
+ },
535
+ `${trackRef.participant.identity}-${trackRef.source}`
536
+ )),
537
+ /* @__PURE__ */ jsx(
538
+ CompactMultiViewControls,
539
+ {
540
+ canPrev: multiCanPrev,
541
+ canNext: multiCanNext,
542
+ onPrev: onMultiPrev,
543
+ onNext: onMultiNext
544
+ }
545
+ )
546
+ ]
547
+ }
548
+ ) : currentParticipant ? /* @__PURE__ */ jsx(
549
+ ParticipantTile,
550
+ {
551
+ trackRef: currentParticipant,
552
+ participant: currentParticipant.participant,
553
+ className: "h-full w-full",
554
+ isFocusToggleDisable: true
555
+ }
556
+ ) : emptyState,
557
+ showPiPButton && /* @__PURE__ */ jsxs(Tooltip, { children: [
558
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
559
+ Button,
560
+ {
561
+ size: "icon",
562
+ variant: "none",
563
+ onClick: () => void pip.openPiP(),
564
+ className: "bg-gray-10/80 hover:bg-gray-10 text-gray-0 absolute top-2 right-2 z-10 h-8 w-8 rounded-xl p-0",
565
+ "aria-label": "\u041E\u0442\u043A\u0440\u044B\u0442\u044C \u0432 \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u043E\u043C \u043E\u043A\u043D\u0435",
566
+ children: /* @__PURE__ */ jsx(External, { className: "h-4 w-4" })
567
+ }
568
+ ) }),
569
+ /* @__PURE__ */ jsx(TooltipContent, { children: "\u041E\u0442\u043A\u0440\u044B\u0442\u044C \u0432 \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u043E\u043C \u043E\u043A\u043D\u0435" })
570
+ ] }),
571
+ isMobile && /* @__PURE__ */ jsx(
572
+ Button,
573
+ {
574
+ size: "icon",
575
+ variant: "none",
576
+ onClick: () => onCollapsedChange(true),
577
+ className: "bg-brand-100 hover:bg-brand-100/80 absolute top-2 right-2 z-10 h-8 w-8 rounded-xl p-0 text-gray-100",
578
+ "aria-label": "\u0421\u0432\u0435\u0440\u043D\u0443\u0442\u044C",
579
+ children: /* @__PURE__ */ jsx(ChevronUp, { className: "fill-gray-0 h-4 w-4" })
580
+ }
581
+ ),
582
+ totalParticipants > 0 && (isMobile || compactViewMode === "basic") && /* @__PURE__ */ jsx(
583
+ CompactNavigationControls,
584
+ {
585
+ canPrev: canGoPrev,
586
+ canNext: canGoNext,
587
+ onPrev,
588
+ onNext,
589
+ currentIndex,
590
+ totalParticipants
591
+ }
592
+ )
593
+ ]
594
+ }
595
+ );
596
+ }
597
+ function CompactCallBottomBar({
598
+ withOutShadows,
599
+ devices,
600
+ isMobile,
601
+ compactViewMode,
602
+ onViewModeToggle,
603
+ showBackToBoardButton,
604
+ onBackToBoard,
605
+ onMaximize,
606
+ isTutor
607
+ }) {
608
+ const barCn = cn(
609
+ "bg-gray-0 border-gray-20 flex items-center justify-center rounded-2xl border p-1",
610
+ withOutShadows ? "" : "shadow-lg"
611
+ );
612
+ const {
613
+ chat: isChatEnabled,
614
+ raiseHand: isRiseHandEnabled,
615
+ whiteboard: isWhiteboardEnabled
616
+ } = useFeaturesStore((s) => s.features);
617
+ return /* @__PURE__ */ jsxs("div", { className: "flex h-10 flex-row", children: [
618
+ /* @__PURE__ */ jsx("div", { className: barCn, children: /* @__PURE__ */ jsx(
619
+ DevicesBar,
620
+ {
621
+ className: "h-8 w-8",
622
+ microTrack: devices.microTrack,
623
+ microEnabled: devices.microEnabled,
624
+ microTrackToggle: devices.microTrackToggle,
625
+ videoTrack: devices.videoTrack,
626
+ videoEnabled: devices.videoEnabled,
627
+ videoTrackToggle: devices.videoTrackToggle
628
+ }
629
+ ) }),
630
+ !isMobile && /* @__PURE__ */ jsx("div", { className: cn(barCn, "ml-1"), children: /* @__PURE__ */ jsxs(Tooltip, { children: [
631
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
632
+ Button,
633
+ {
634
+ size: "icon",
635
+ variant: "none",
636
+ onClick: onViewModeToggle,
637
+ className: "hover:bg-gray-5 h-8 w-8 rounded-xl p-0 text-gray-100",
638
+ "aria-label": compactViewMode === "basic" ? "\u0420\u0430\u0437\u0432\u0451\u0440\u043D\u0443\u0442\u044B\u0439 \u0432\u0438\u0434" : "\u041E\u0434\u0438\u043D \u0443\u0447\u0430\u0441\u0442\u043D\u0438\u043A",
639
+ children: compactViewMode === "basic" ? /* @__PURE__ */ jsx(Users, { className: "h-5 w-5" }) : /* @__PURE__ */ jsx(Account, { className: "h-5 w-5" })
640
+ }
641
+ ) }),
642
+ /* @__PURE__ */ jsx(TooltipContent, { children: compactViewMode === "basic" ? "\u0420\u0430\u0437\u0432\u0451\u0440\u043D\u0443\u0442\u044B\u0439 \u0432\u0438\u0434 (\u043D\u0435\u0441\u043A\u043E\u043B\u044C\u043A\u043E \u0443\u0447\u0430\u0441\u0442\u043D\u0438\u043A\u043E\u0432)" : "\u041E\u0434\u0438\u043D \u0443\u0447\u0430\u0441\u0442\u043D\u0438\u043A" })
643
+ ] }) }),
644
+ isWhiteboardEnabled && showBackToBoardButton && /* @__PURE__ */ jsx("div", { className: cn(barCn, "ml-1"), children: /* @__PURE__ */ jsxs(Tooltip, { children: [
645
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
646
+ Button,
647
+ {
648
+ size: "icon",
649
+ variant: "primary",
650
+ className: "h-8 w-8 rounded-xl p-0",
651
+ onClick: onBackToBoard,
652
+ "aria-label": "\u041D\u0430 \u0434\u043E\u0441\u043A\u0443",
653
+ children: /* @__PURE__ */ jsx(WhiteBoard, { className: "fill-gray-0 h-5 w-5" })
654
+ }
655
+ ) }),
656
+ /* @__PURE__ */ jsx(TooltipContent, { children: "\u041E\u0431\u0440\u0430\u0442\u043D\u043E \u043D\u0430 \u0434\u043E\u0441\u043A\u0443" })
657
+ ] }) }),
658
+ /* @__PURE__ */ jsxs("div", { className: cn(barCn, "ml-auto"), children: [
659
+ /* @__PURE__ */ jsx(ScreenShareButton, { className: "h-8 w-8" }),
660
+ isChatEnabled && /* @__PURE__ */ jsx(ChatButton, { className: "h-8 w-8 min-w-8 rounded-xl" }),
661
+ isRiseHandEnabled && /* @__PURE__ */ jsx(RaiseHandButton, { className: "h-8 w-8" })
662
+ ] }),
663
+ /* @__PURE__ */ jsxs("div", { className: cn(barCn, "ml-1"), children: [
664
+ isTutor ? /* @__PURE__ */ jsxs(DropdownMenu, { children: [
665
+ /* @__PURE__ */ jsxs(Tooltip, { children: [
666
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
667
+ Button,
668
+ {
669
+ size: "icon",
670
+ variant: "none",
671
+ className: "hover:bg-gray-5 relative m-0 h-8 w-8 rounded-xl p-0 text-gray-100",
672
+ children: /* @__PURE__ */ jsx(Maximize, { className: "fill-gray-100" })
673
+ }
674
+ ) }) }),
675
+ /* @__PURE__ */ jsx(TooltipContent, { children: "\u0412\u0435\u0440\u043D\u0443\u0442\u044C\u0441\u044F \u0432 \u043A\u043E\u043D\u0444\u0435\u0440\u0435\u043D\u0446\u0438\u044E" })
676
+ ] }),
677
+ /* @__PURE__ */ jsxs(DropdownMenuContent, { side: "top", align: "end", className: "z-1000 min-w-50", children: [
678
+ /* @__PURE__ */ jsx(DropdownMenuLabel, { className: "text-gray-60 text-sm", children: "\u0412\u0435\u0440\u043D\u0443\u0442\u044C \u0432 \u043A\u043E\u043D\u0444\u0435\u0440\u0435\u043D\u0446\u0438\u044E" }),
679
+ /* @__PURE__ */ jsx(
680
+ DropdownMenuItem,
681
+ {
682
+ onClick: () => onMaximize(false),
683
+ className: "text-gray-80 cursor-pointer text-sm",
684
+ children: "\u0422\u043E\u043B\u044C\u043A\u043E \u043C\u0435\u043D\u044F"
685
+ }
686
+ ),
687
+ /* @__PURE__ */ jsx(
688
+ DropdownMenuItem,
689
+ {
690
+ onClick: () => onMaximize(true),
691
+ className: "text-gray-80 cursor-pointer text-sm",
692
+ children: "\u0412\u0441\u0435\u0445 \u0443\u0447\u0430\u0441\u0442\u043D\u0438\u043A\u043E\u0432"
693
+ }
694
+ )
695
+ ] })
696
+ ] }) : /* @__PURE__ */ jsxs(Tooltip, { children: [
697
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
698
+ Button,
699
+ {
700
+ size: "icon",
701
+ variant: "none",
702
+ onClick: () => onMaximize(false),
703
+ className: "hover:bg-gray-5 relative m-0 h-8 w-8 rounded-xl p-0 text-gray-100",
704
+ children: /* @__PURE__ */ jsx(Maximize, { className: "fill-gray-100" })
705
+ }
706
+ ) }),
707
+ /* @__PURE__ */ jsx(TooltipContent, { children: "\u0412\u0435\u0440\u043D\u0443\u0442\u044C\u0441\u044F \u0432 \u043A\u043E\u043D\u0444\u0435\u0440\u0435\u043D\u0446\u0438\u044E" })
708
+ ] }),
709
+ /* @__PURE__ */ jsx(DisconnectButton, { className: "h-8 w-8 rounded-xl" })
710
+ ] })
711
+ ] });
712
+ }
713
+ var CompactCall = ({ saveUserChoices = true, withOutShadows = false }) => {
714
+ const isMobile = useMedia("(max-width: 720px)");
715
+ const { attributes, listeners, setNodeRef, transform } = useDraggable({
716
+ id: "draggable-call",
717
+ disabled: isMobile
718
+ });
719
+ const style = isMobile ? void 0 : { transform: CSS.Translate.toString(transform) };
720
+ const { saveAudioInputEnabled, saveVideoInputEnabled } = usePersistentUserChoices({
721
+ preventSave: !saveUserChoices
722
+ });
723
+ const { isMicrophoneEnabled, isCameraEnabled, microphoneTrack, cameraTrack } = useLocalParticipant();
724
+ const videoTrack = cameraTrack?.track;
725
+ const mode = useCallStore((state) => state.mode);
726
+ useVideoBlur(mode === "compact" ? videoTrack : null);
727
+ const microphoneToggle = useTrackToggle({
728
+ source: Track.Source.Microphone,
729
+ onChange: (enabled, isUserInitiated) => {
730
+ if (isUserInitiated) saveAudioInputEnabled(enabled);
731
+ }
732
+ });
733
+ const cameraToggle = useTrackToggle({
734
+ source: Track.Source.Camera,
735
+ onChange: (enabled, isUserInitiated) => {
736
+ if (isUserInitiated) saveVideoInputEnabled(enabled);
737
+ }
738
+ });
739
+ const handleMicrophoneToggle = useCallback(() => microphoneToggle.toggle(), [microphoneToggle]);
740
+ const handleCameraToggle = useCallback(() => cameraToggle.toggle(), [cameraToggle]);
741
+ const compactNavigation = useCompactNavigation();
742
+ const {
743
+ currentParticipant,
744
+ participants,
745
+ currentIndex,
746
+ totalParticipants,
747
+ canGoNext,
748
+ canGoPrev,
749
+ goToNext,
750
+ goToPrev
751
+ } = compactNavigation;
752
+ const callsNavigation = useCallsNavigation();
753
+ const [isCollapsed, setIsCollapsed] = useState(false);
754
+ const compactViewMode = useCallStore((state) => state.compactViewMode);
755
+ const updateStore = useCallStore((state) => state.updateStore);
756
+ const setCompactViewMode = useCallback(
757
+ (mode2) => updateStore("compactViewMode", mode2),
758
+ [updateStore]
759
+ );
760
+ const [multiScrollIndex, setMultiScrollIndex] = useState(0);
761
+ const currentAudioTrack = currentParticipant?.participant?.getTrackPublication(
762
+ Track.Source.Microphone
763
+ )?.track;
764
+ const { syncModeToOthers } = useModeSync();
765
+ const activeBoardId = useCallStore((state) => state.activeBoardId);
766
+ const activeClassroom = useCallStore((state) => state.activeClassroom);
767
+ const { room } = useRoom();
768
+ const token = useCallStore((state) => state.token);
769
+ const { data: user } = useCalls().auth.useCurrentUser();
770
+ const isTutor = user?.default_layout === "tutor";
771
+ const isBoardPage = callsNavigation.pathnameIncludes("/board");
772
+ const isOnBoardPage = callsNavigation.params.classroomId === activeClassroom && callsNavigation.params.boardId === activeBoardId;
773
+ const showBackToBoardButton = mode === "compact" && !!activeBoardId && !!activeClassroom && !isOnBoardPage;
774
+ const availableHeight = useCompactAvailableHeight(isBoardPage);
775
+ const multiViewLayout = useMemo(() => {
776
+ const heightForTiles = Math.max(
777
+ 0,
778
+ availableHeight - COMPACT_BOTTOM_BAR_PX - EXPANDED_VIDEO_PADDING_VERTICAL_PX - COMPACT_VIDEO_AREA_MARGIN_PX
779
+ );
780
+ const visibleCount = Math.min(
781
+ totalParticipants,
782
+ Math.max(1, Math.floor((heightForTiles + TILE_GAP_PX) / (TILE_HEIGHT_16_9_PX + TILE_GAP_PX)))
783
+ );
784
+ return { visibleCount, tileHeightPx: TILE_HEIGHT_16_9_PX };
785
+ }, [availableHeight, totalParticipants]);
786
+ const { visibleCount: multiVisibleCount, tileHeightPx: multiTileHeightPx } = multiViewLayout;
787
+ const multiCanPrev = multiScrollIndex > 0;
788
+ const multiCanNext = multiScrollIndex + multiVisibleCount < totalParticipants;
789
+ const multiVisibleParticipants = participants.slice(
790
+ multiScrollIndex,
791
+ multiScrollIndex + multiVisibleCount
792
+ );
793
+ useEffect(() => {
794
+ if (multiScrollIndex + multiVisibleCount > totalParticipants && totalParticipants > 0) {
795
+ setMultiScrollIndex(Math.max(0, totalParticipants - multiVisibleCount));
796
+ }
797
+ }, [totalParticipants, multiVisibleCount, multiScrollIndex]);
798
+ const handleBackToBoard = useCallback(() => {
799
+ if (!activeBoardId || !activeClassroom) return;
800
+ callsNavigation.navigateToClassroomBoard(activeClassroom, activeBoardId);
801
+ }, [activeBoardId, activeClassroom, callsNavigation]);
802
+ const handleMaximize = useCallback(
803
+ (syncToAll = false) => {
804
+ if (!room || !token || room.state !== "connected") return;
805
+ const callParam = callsNavigation.search.call;
806
+ const targetCallId = (typeof callParam === "string" ? callParam.replace(/^"|"$/g, "").trim() : "") || activeClassroom || callsNavigation.params.classroomId || callsNavigation.getCallId() || "";
807
+ if (syncToAll && isTutor && activeBoardId && targetCallId) {
808
+ updateStore("localFullView", false);
809
+ updateStore("mode", "full");
810
+ updateStore("activeBoardId", void 0);
811
+ updateStore("activeClassroom", void 0);
812
+ syncModeToOthers("full", void 0, targetCallId);
813
+ } else {
814
+ updateStore("localFullView", true);
815
+ updateStore("mode", "full");
816
+ }
817
+ if (targetCallId) {
818
+ callsNavigation.navigateToCall(targetCallId, { replace: true });
819
+ }
820
+ },
821
+ [
822
+ room,
823
+ token,
824
+ callsNavigation,
825
+ activeClassroom,
826
+ isTutor,
827
+ activeBoardId,
828
+ updateStore,
829
+ syncModeToOthers
830
+ ]
831
+ );
832
+ return /* @__PURE__ */ jsxs(
833
+ "div",
834
+ {
835
+ ref: setNodeRef,
836
+ style,
837
+ className: `compact-call-container flex flex-col ${isMobile ? "w-full" : "w-[360px]"}`,
838
+ children: [
839
+ /* @__PURE__ */ jsx(
840
+ CompactCallVideoArea,
841
+ {
842
+ isMobile,
843
+ isCollapsed,
844
+ onCollapsedChange: setIsCollapsed,
845
+ withOutShadows,
846
+ dragAttributes: attributes,
847
+ dragListeners: listeners,
848
+ compactViewMode,
849
+ currentParticipant,
850
+ currentAudioTrack: currentAudioTrack ?? null,
851
+ totalParticipants,
852
+ canGoPrev,
853
+ canGoNext,
854
+ onPrev: goToPrev,
855
+ onNext: goToNext,
856
+ currentIndex,
857
+ multiVisibleParticipants,
858
+ multiTileHeightPx,
859
+ multiCanPrev,
860
+ multiCanNext,
861
+ onMultiPrev: () => setMultiScrollIndex((i) => Math.max(0, i - 1)),
862
+ onMultiNext: () => setMultiScrollIndex((i) => Math.min(totalParticipants - multiVisibleCount, i + 1))
863
+ }
864
+ ),
865
+ /* @__PURE__ */ jsx(
866
+ CompactCallBottomBar,
867
+ {
868
+ withOutShadows,
869
+ devices: {
870
+ microTrack: microphoneTrack?.track,
871
+ microEnabled: isMicrophoneEnabled,
872
+ microTrackToggle: {
873
+ showIcon: true,
874
+ source: Track.Source.Microphone,
875
+ onChange: handleMicrophoneToggle
876
+ },
877
+ videoTrack: cameraTrack?.track,
878
+ videoEnabled: isCameraEnabled,
879
+ videoTrackToggle: {
880
+ showIcon: true,
881
+ source: Track.Source.Camera,
882
+ onChange: handleCameraToggle
883
+ }
884
+ },
885
+ isMobile,
886
+ compactViewMode,
887
+ onViewModeToggle: () => setCompactViewMode(compactViewMode === "basic" ? "expanded" : "basic"),
888
+ showBackToBoardButton: !!showBackToBoardButton,
889
+ onBackToBoard: handleBackToBoard,
890
+ onMaximize: handleMaximize,
891
+ isTutor: !!isTutor
892
+ }
893
+ )
894
+ ]
895
+ }
896
+ );
897
+ };
898
+ var OPPOSITE_CORNER = {
899
+ "top-left": "top-right",
900
+ "top-right": "top-left",
901
+ "bottom-left": "bottom-right",
902
+ "bottom-right": "bottom-left"
903
+ };
904
+ var DroppableCorner = ({ id, className }) => {
905
+ const { setNodeRef } = useDroppable({
906
+ id
907
+ });
908
+ const [isDragging, setIsDragging] = useState(false);
909
+ useDndMonitor({
910
+ onDragStart: () => {
911
+ setIsDragging(true);
912
+ },
913
+ onDragEnd: () => {
914
+ setIsDragging(false);
915
+ }
916
+ });
917
+ return /* @__PURE__ */ jsx(
918
+ "div",
919
+ {
920
+ ref: setNodeRef,
921
+ className: `absolute z-100 size-1/3 ${className} pointer-events-none transition-all duration-200 ${isDragging ? "bg-brand-0/20 ring-brand-20 pointer-events-auto rounded-lg ring-2" : ""}`
922
+ }
923
+ );
924
+ };
925
+ var DroppableAreas = () => {
926
+ const [isDragging, setIsDragging] = useState(false);
927
+ useDndMonitor({
928
+ onDragStart: () => {
929
+ setIsDragging(true);
930
+ },
931
+ onDragEnd: () => {
932
+ setIsDragging(false);
933
+ }
934
+ });
935
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
936
+ /* @__PURE__ */ jsx(DroppableCorner, { id: "top-left", className: "top-16 left-4" }),
937
+ /* @__PURE__ */ jsx(DroppableCorner, { id: "top-right", className: "top-16 right-4" }),
938
+ /* @__PURE__ */ jsx(DroppableCorner, { id: "bottom-left", className: "bottom-18 left-4" }),
939
+ /* @__PURE__ */ jsx(DroppableCorner, { id: "bottom-right", className: "right-4 bottom-18" }),
940
+ /* @__PURE__ */ jsx(DragOverlay, { children: isDragging ? /* @__PURE__ */ jsx(CompactCall, {}) : null })
941
+ ] });
942
+ };
943
+ var Compact = ({ children, hideOverlay = false }) => {
944
+ const navigation = useCallsNavigation();
945
+ const { activeCorner, updateStore } = useCallStore();
946
+ const focusMode = useFocusModeStore((s) => s.focusMode);
947
+ const isMobile = useMedia("(max-width: 720px)");
948
+ const headerRef = useRef(null);
949
+ const [headerHeight, setHeaderHeight] = useState(0);
950
+ const { chat: isChatEnabled } = useFeaturesStore((s) => s.features);
951
+ useEffect(() => {
952
+ if (!isMobile || hideOverlay || !headerRef.current) return;
953
+ const el = headerRef.current;
954
+ const observer = new ResizeObserver((entries) => {
955
+ const entry = entries[0];
956
+ if (entry) setHeaderHeight(entry.contentRect.height);
957
+ });
958
+ observer.observe(el);
959
+ setHeaderHeight(el.getBoundingClientRect().height);
960
+ return () => observer.disconnect();
961
+ }, [isMobile, hideOverlay]);
962
+ const sensors = useSensors(
963
+ useSensor(PointerSensor, {
964
+ activationConstraint: {
965
+ distance: 8
966
+ }
967
+ })
968
+ );
969
+ const handleDragEnd = (event) => {
970
+ const { over } = event;
971
+ if (over) {
972
+ updateStore("activeCorner", over.id);
973
+ }
974
+ };
975
+ const isBoardPage = navigation.pathnameIncludes("/board");
976
+ const bottomOffset = isBoardPage ? "bottom-[72px]" : "bottom-4";
977
+ const getCornerPosition = (corner) => {
978
+ switch (corner) {
979
+ case "top-left":
980
+ return "top-16 left-4";
981
+ case "top-right":
982
+ return "top-16 right-4";
983
+ case "bottom-left":
984
+ return `${bottomOffset} left-4`;
985
+ case "bottom-right":
986
+ return `${bottomOffset} right-4`;
987
+ }
988
+ };
989
+ const getChatPositionClasses = (corner) => {
990
+ const opposite = OPPOSITE_CORNER[corner];
991
+ const horizontal = opposite.includes("left") ? "left-22" : "right-4";
992
+ return `top-32 ${bottomOffset} ${horizontal}`;
993
+ };
994
+ const container = /* @__PURE__ */ jsxs(
995
+ "div",
996
+ {
997
+ className: `relative flex flex-col bg-transparent ${focusMode ? "h-dvh" : "h-[calc(100dvh-64px)]"}`,
998
+ children: [
999
+ isMobile ? /* @__PURE__ */ jsxs(Fragment, { children: [
1000
+ !hideOverlay && /* @__PURE__ */ jsx("div", { ref: headerRef, className: "fixed top-16 right-0 left-0 z-10 w-full px-2", children: /* @__PURE__ */ jsx(CompactCall, { withOutShadows: true }) }),
1001
+ /* @__PURE__ */ jsx(
1002
+ "div",
1003
+ {
1004
+ className: "flex min-h-0 flex-1 flex-col",
1005
+ style: { marginTop: hideOverlay ? 0 : headerHeight > 0 ? headerHeight : 120 },
1006
+ children
1007
+ }
1008
+ )
1009
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1010
+ !hideOverlay && /* @__PURE__ */ jsxs(Fragment, { children: [
1011
+ /* @__PURE__ */ jsx(DroppableAreas, {}),
1012
+ isChatEnabled && /* @__PURE__ */ jsx(Chat, { compactPositionClassName: getChatPositionClasses(activeCorner) }),
1013
+ /* @__PURE__ */ jsx(
1014
+ "div",
1015
+ {
1016
+ className: `absolute z-100 ${getCornerPosition(activeCorner)} transition-all duration-500 ease-out`,
1017
+ children: /* @__PURE__ */ jsx(CompactCall, {})
1018
+ }
1019
+ )
1020
+ ] }),
1021
+ children
1022
+ ] }),
1023
+ /* @__PURE__ */ jsx(RoomAudioRenderer, {})
1024
+ ]
1025
+ }
1026
+ );
1027
+ return /* @__PURE__ */ jsx(DndContext, { sensors, onDragEnd: handleDragEnd, modifiers: [restrictToWindowEdges], children: container });
1028
+ };
1029
+ var CompactViewInner = ({ children }) => {
1030
+ const { mode, token } = useCallStore();
1031
+ const { room } = useRoom();
1032
+ const pip = usePiP();
1033
+ const isPiPActive = !!pip?.isPiPActive;
1034
+ useParticipantJoinSync();
1035
+ useParticipantSounds();
1036
+ const navigation = useCallsNavigation();
1037
+ const { updateStore: updateChatStore } = useChatStore();
1038
+ useEffect(() => {
1039
+ const { activeClassroom } = useCallStore.getState();
1040
+ const isOnBoardPage = navigation.pathnameIncludes("/board");
1041
+ if ((!room || !token) && navigation.search.call && !isOnBoardPage && !activeClassroom) {
1042
+ navigation.clearCallSearchParam();
1043
+ const { clearAllRaisedHands, updateStore: updateCallStore } = useCallStore.getState();
1044
+ clearAllRaisedHands();
1045
+ updateChatStore("isChatOpen", false);
1046
+ updateChatStore("chatMessages", []);
1047
+ updateChatStore("unreadMessagesCount", 0);
1048
+ updateCallStore("activeBoardId", void 0);
1049
+ updateCallStore("activeClassroom", void 0);
1050
+ updateCallStore("mode", "full");
1051
+ }
1052
+ }, [room, token, navigation, updateChatStore]);
1053
+ const contentWrapper = /* @__PURE__ */ jsx("div", { className: "flex h-full min-h-0 flex-col", children: /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1", children }) });
1054
+ if (!room || !token) {
1055
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1056
+ contentWrapper,
1057
+ /* @__PURE__ */ jsx(PermissionsDialog, {})
1058
+ ] });
1059
+ }
1060
+ if (mode === "full") {
1061
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1062
+ contentWrapper,
1063
+ /* @__PURE__ */ jsx(PermissionsDialog, {})
1064
+ ] });
1065
+ }
1066
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1067
+ /* @__PURE__ */ jsx(Compact, { hideOverlay: isPiPActive, children }),
1068
+ /* @__PURE__ */ jsx(PermissionsDialog, {})
1069
+ ] });
1070
+ };
1071
+ var CompactView = ({ children }) => /* @__PURE__ */ jsx(PiPProvider, { children: /* @__PURE__ */ jsx(CompactViewInner, { children }) });
1072
+ function requiredPipHeight(n) {
1073
+ return PIP_RESERVED_HEIGHT_PX + n * PIP_TILE_HEIGHT_16_9_PX + Math.max(0, n - 1) * TILE_GAP_PX;
1074
+ }
1075
+ function PiPCompactCall({ pipWindow }) {
1076
+ const compactViewMode = useCallStore((s) => s.compactViewMode);
1077
+ const updateStore = useCallStore((s) => s.updateStore);
1078
+ const setViewMode = useCallback(
1079
+ (mode) => updateStore("compactViewMode", mode),
1080
+ [updateStore]
1081
+ );
1082
+ const [multiScrollIndex, setMultiScrollIndex] = useState(0);
1083
+ const [pipSize, setPipSize] = useState({
1084
+ width: pipWindow.innerWidth,
1085
+ height: pipWindow.innerHeight
1086
+ });
1087
+ const { saveAudioInputEnabled, saveVideoInputEnabled } = usePersistentUserChoices();
1088
+ const { isMicrophoneEnabled, isCameraEnabled, microphoneTrack, cameraTrack } = useLocalParticipant();
1089
+ const microphoneToggle = useTrackToggle({
1090
+ source: Track.Source.Microphone,
1091
+ onChange: (enabled, isUserInitiated) => {
1092
+ if (isUserInitiated) saveAudioInputEnabled(enabled);
1093
+ }
1094
+ });
1095
+ const cameraToggle = useTrackToggle({
1096
+ source: Track.Source.Camera,
1097
+ onChange: (enabled, isUserInitiated) => {
1098
+ if (isUserInitiated) saveVideoInputEnabled(enabled);
1099
+ }
1100
+ });
1101
+ const handleMicrophoneToggle = useCallback(() => microphoneToggle.toggle(), [microphoneToggle]);
1102
+ const handleCameraToggle = useCallback(() => cameraToggle.toggle(), [cameraToggle]);
1103
+ const {
1104
+ currentParticipant,
1105
+ participants,
1106
+ currentIndex,
1107
+ totalParticipants,
1108
+ canGoNext,
1109
+ canGoPrev,
1110
+ goToNext,
1111
+ goToPrev
1112
+ } = useCompactNavigation();
1113
+ const { raiseHand: isRiseHandEnabled } = useFeaturesStore((s) => s.features);
1114
+ useEffect(() => {
1115
+ const updateSize = () => setPipSize({ width: pipWindow.innerWidth, height: pipWindow.innerHeight });
1116
+ updateSize();
1117
+ pipWindow.addEventListener("resize", updateSize);
1118
+ return () => pipWindow.removeEventListener("resize", updateSize);
1119
+ }, [pipWindow]);
1120
+ const multiVisibleCount = useMemo(() => {
1121
+ let n = 1;
1122
+ while (n < totalParticipants && requiredPipHeight(n + 1) <= pipSize.height) {
1123
+ n++;
1124
+ }
1125
+ return n;
1126
+ }, [pipSize.height, totalParticipants]);
1127
+ const multiVisibleParticipants = useMemo(
1128
+ () => participants.slice(multiScrollIndex, multiScrollIndex + multiVisibleCount),
1129
+ [participants, multiScrollIndex, multiVisibleCount]
1130
+ );
1131
+ const multiCanPrev = multiScrollIndex > 0;
1132
+ const multiCanNext = multiScrollIndex + multiVisibleCount < totalParticipants;
1133
+ useEffect(() => {
1134
+ if (multiScrollIndex + multiVisibleCount > totalParticipants && totalParticipants > 0) {
1135
+ setMultiScrollIndex(Math.max(0, totalParticipants - multiVisibleCount));
1136
+ }
1137
+ }, [totalParticipants, multiVisibleCount, multiScrollIndex]);
1138
+ const emptyState = /* @__PURE__ */ jsx("div", { className: "bg-gray-40 flex h-full w-full items-center justify-center rounded-2xl text-gray-100", children: /* @__PURE__ */ jsx("span", { className: "text-sm", children: "\u041D\u0435\u0442 \u0443\u0447\u0430\u0441\u0442\u043D\u0438\u043A\u043E\u0432" }) });
1139
+ const barCn = cn(
1140
+ "bg-gray-0 border-gray-20 flex items-center justify-center rounded-2xl border p-0.5"
1141
+ );
1142
+ return /* @__PURE__ */ jsxs("div", { className: "flex h-full flex-col gap-1 p-1", children: [
1143
+ /* @__PURE__ */ jsx("div", { className: "group relative min-h-0 flex-1 overflow-hidden rounded-2xl", children: compactViewMode === "expanded" ? /* @__PURE__ */ jsxs(
1144
+ "div",
1145
+ {
1146
+ className: "relative flex h-full flex-col overflow-hidden rounded-2xl p-0.5",
1147
+ style: { gap: TILE_GAP_PX },
1148
+ children: [
1149
+ multiVisibleParticipants.length === 0 ? emptyState : multiVisibleParticipants.map((trackRef) => /* @__PURE__ */ jsx(
1150
+ "div",
1151
+ {
1152
+ className: "aspect-video w-full shrink-0 overflow-hidden rounded-xl",
1153
+ style: { minHeight: PIP_TILE_HEIGHT_16_9_PX },
1154
+ children: /* @__PURE__ */ jsx(
1155
+ ParticipantTile,
1156
+ {
1157
+ trackRef,
1158
+ participant: trackRef.participant,
1159
+ className: "h-full w-full [&_video]:object-cover",
1160
+ isFocusToggleDisable: true
1161
+ }
1162
+ )
1163
+ },
1164
+ `${trackRef.participant.identity}-${trackRef.source}`
1165
+ )),
1166
+ /* @__PURE__ */ jsx(
1167
+ CompactMultiViewControls,
1168
+ {
1169
+ canPrev: multiCanPrev,
1170
+ canNext: multiCanNext,
1171
+ onPrev: () => setMultiScrollIndex((i) => Math.max(0, i - 1)),
1172
+ onNext: () => setMultiScrollIndex((i) => Math.min(totalParticipants - multiVisibleCount, i + 1))
1173
+ }
1174
+ )
1175
+ ]
1176
+ }
1177
+ ) : currentParticipant ? /* @__PURE__ */ jsxs(Fragment, { children: [
1178
+ /* @__PURE__ */ jsx(
1179
+ ParticipantTile,
1180
+ {
1181
+ trackRef: currentParticipant,
1182
+ participant: currentParticipant.participant,
1183
+ className: "h-full w-full",
1184
+ isFocusToggleDisable: true
1185
+ }
1186
+ ),
1187
+ totalParticipants > 1 && /* @__PURE__ */ jsx(
1188
+ CompactNavigationControls,
1189
+ {
1190
+ canPrev: canGoPrev,
1191
+ canNext: canGoNext,
1192
+ onPrev: goToPrev,
1193
+ onNext: goToNext,
1194
+ currentIndex,
1195
+ totalParticipants
1196
+ }
1197
+ )
1198
+ ] }) : emptyState }),
1199
+ /* @__PURE__ */ jsxs("div", { className: "flex h-12 shrink-0 items-center gap-0.5", children: [
1200
+ /* @__PURE__ */ jsx("div", { className: barCn, children: /* @__PURE__ */ jsx(
1201
+ DevicesBar,
1202
+ {
1203
+ className: "h-7 w-7",
1204
+ microTrack: microphoneTrack?.track,
1205
+ microEnabled: isMicrophoneEnabled,
1206
+ microTrackToggle: {
1207
+ showIcon: true,
1208
+ source: Track.Source.Microphone,
1209
+ onChange: handleMicrophoneToggle
1210
+ },
1211
+ videoTrack: cameraTrack?.track,
1212
+ videoEnabled: isCameraEnabled,
1213
+ videoTrackToggle: {
1214
+ showIcon: true,
1215
+ source: Track.Source.Camera,
1216
+ onChange: handleCameraToggle
1217
+ }
1218
+ }
1219
+ ) }),
1220
+ /* @__PURE__ */ jsx("div", { className: barCn, children: /* @__PURE__ */ jsxs(Tooltip, { children: [
1221
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
1222
+ Button,
1223
+ {
1224
+ size: "icon",
1225
+ variant: "none",
1226
+ onClick: () => setViewMode(compactViewMode === "basic" ? "expanded" : "basic"),
1227
+ className: "hover:bg-gray-5 h-7 w-7 rounded-xl p-0 text-gray-100",
1228
+ "aria-label": compactViewMode === "basic" ? "\u0420\u0430\u0437\u0432\u0451\u0440\u043D\u0443\u0442\u044B\u0439 \u0432\u0438\u0434" : "\u041E\u0434\u0438\u043D \u0443\u0447\u0430\u0441\u0442\u043D\u0438\u043A",
1229
+ children: compactViewMode === "basic" ? /* @__PURE__ */ jsx(Users, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(Account, { className: "h-4 w-4" })
1230
+ }
1231
+ ) }),
1232
+ /* @__PURE__ */ jsx(TooltipContent, { children: compactViewMode === "basic" ? "\u0420\u0430\u0437\u0432\u0451\u0440\u043D\u0443\u0442\u044B\u0439 \u0432\u0438\u0434 (\u043D\u0435\u0441\u043A\u043E\u043B\u044C\u043A\u043E \u0443\u0447\u0430\u0441\u0442\u043D\u0438\u043A\u043E\u0432)" : "\u041E\u0434\u0438\u043D \u0443\u0447\u0430\u0441\u0442\u043D\u0438\u043A" })
1233
+ ] }) }),
1234
+ /* @__PURE__ */ jsxs("div", { className: cn(barCn, "ml-auto"), children: [
1235
+ /* @__PURE__ */ jsx(ScreenShareButton, { className: "h-7 w-7" }),
1236
+ isRiseHandEnabled && /* @__PURE__ */ jsx(RaiseHandButton, { className: "h-7 w-7" })
1237
+ ] }),
1238
+ /* @__PURE__ */ jsx("div", { className: barCn, children: /* @__PURE__ */ jsx(DisconnectButton, { className: "h-7 w-7 rounded-xl" }) })
1239
+ ] })
1240
+ ] });
1241
+ }
1242
+
1243
+ export { CompactCall, CompactNavigationControls, CompactView, PiPCompactCall, PiPProvider, useCompactAvailableHeight, useCompactNavigation, useDocumentPiP, usePiP };
1244
+ //# sourceMappingURL=index.mjs.map
1245
+ //# sourceMappingURL=index.mjs.map