@xipkg/calls-compactview 0.1.53 → 0.1.55
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.d.ts +9 -3
- package/dist/index.mjs +314 -64
- package/dist/index.mjs.map +1 -1
- package/package.json +10 -10
package/dist/index.d.ts
CHANGED
|
@@ -6,9 +6,11 @@ type CompactViewPropsT = {
|
|
|
6
6
|
};
|
|
7
7
|
declare const CompactView: ({ children }: CompactViewPropsT) => react.JSX.Element;
|
|
8
8
|
|
|
9
|
-
declare const CompactCall: ({ saveUserChoices, withOutShadows }: {
|
|
10
|
-
saveUserChoices?: boolean
|
|
11
|
-
withOutShadows?: boolean
|
|
9
|
+
declare const CompactCall: ({ saveUserChoices, withOutShadows, freeDrag, }: {
|
|
10
|
+
saveUserChoices?: boolean;
|
|
11
|
+
withOutShadows?: boolean;
|
|
12
|
+
/** Панель тащится целиком в любую точку, в том числе на телефоне */
|
|
13
|
+
freeDrag?: boolean;
|
|
12
14
|
}) => react.JSX.Element;
|
|
13
15
|
|
|
14
16
|
interface CompactNavigationControlsProps {
|
|
@@ -48,6 +50,10 @@ type PiPProviderProps = {
|
|
|
48
50
|
*/
|
|
49
51
|
declare function PiPProvider({ children }: PiPProviderProps): react.JSX.Element;
|
|
50
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Максимальная высота контейнера compact call (видео + нижняя панель).
|
|
55
|
+
* На доске учитывает отступы тулбара из CSS-переменных `--board-chrome-*`.
|
|
56
|
+
*/
|
|
51
57
|
declare function useCompactAvailableHeight(isOnBoardPage: boolean): number;
|
|
52
58
|
|
|
53
59
|
declare const useCompactNavigation: () => {
|
package/dist/index.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import '@xipkg/calls-ui/grid.css';
|
|
2
|
-
import { createContext,
|
|
2
|
+
import { createContext, useState, useEffect, useMemo, useRef, useCallback, useContext, useLayoutEffect } from 'react';
|
|
3
3
|
import { useDraggable, PointerSensor, useSensors, useSensor, DndContext, TouchSensor, DragOverlay, useDndMonitor, useDroppable } from '@dnd-kit/core';
|
|
4
4
|
import { useRoom, useCallsNavigation, useCalls } from '@xipkg/calls-providers';
|
|
5
5
|
import { restrictToWindowEdges } from '@dnd-kit/modifiers';
|
|
6
6
|
import { CSS } from '@dnd-kit/utilities';
|
|
7
|
-
import { useTracks, usePersistentUserChoices, useLocalParticipant, useTrackToggle, useAudioWaveform } from '@livekit/components-react';
|
|
7
|
+
import { useTracks, usePersistentUserChoices, useLocalParticipant, useTrackToggle, useAudioWaveform, ScreenShareIcon } from '@livekit/components-react';
|
|
8
8
|
import { Track, RemoteTrackPublication } from 'livekit-client';
|
|
9
9
|
import { applyPinsFirst, useCallStore } from '@xipkg/calls-store';
|
|
10
10
|
import { useScreenShareCleanup, useSortedTracks, useClassroomPins, useVideoBlur, useDeviceMenuGroups, useSyncModeToOthers, useParticipantJoinSync, useParticipantSounds } from '@xipkg/calls-hooks';
|
|
@@ -15,7 +15,7 @@ import { Button } from '@xipkg/button';
|
|
|
15
15
|
import { ArrowLeft, ArrowRight, Account, SoundTwo, Users, External, ChevronUp, WhiteBoard, Maximize, MicrophoneOff, RedLine, ChevronBottom, ArrowUp } from '@xipkg/icons';
|
|
16
16
|
import { cn } from '@xipkg/utils';
|
|
17
17
|
import { useTranslation } from 'react-i18next';
|
|
18
|
-
import { ParticipantTile, DevicesBar, LessonRecordButton, ScreenShareButton, DisconnectButton, ParticipantPinToggle, tileOverlayButtonClassName, PermissionsDialog, TrackMutedIndicator, ParticipantName, RaisedHandIndicator } from '@xipkg/calls-ui';
|
|
18
|
+
import { ParticipantTile, DevicesBar, LessonRecordButton, ScreenShareButton, DisconnectButton, ScreenShareStageToggle, ParticipantPinToggle, tileOverlayButtonClassName, PermissionsDialog, TrackMutedIndicator, ParticipantName, RaisedHandIndicator, ScreenShareZoom, VideoTrack } from '@xipkg/calls-ui';
|
|
19
19
|
import { Tooltip, TooltipTrigger, TooltipContent } from '@xipkg/tooltip';
|
|
20
20
|
import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuLabel, DropdownMenuItem } from '@xipkg/dropdown';
|
|
21
21
|
import { useChatStore, Chat, ChatButton } from '@xipkg/calls-chat';
|
|
@@ -120,21 +120,91 @@ function getPipHeightForMode(mode, participantCount) {
|
|
|
120
120
|
return getPipWindowHeight(mode, 1);
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
// src/
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
123
|
+
// src/boardChromeInsets.ts
|
|
124
|
+
var TOP_VAR = "--board-chrome-top";
|
|
125
|
+
var RIGHT_VAR = "--board-chrome-right";
|
|
126
|
+
var BOTTOM_VAR = "--board-chrome-bottom";
|
|
127
|
+
var LEFT_VAR = "--board-chrome-left";
|
|
128
|
+
var EDGE_PX = 16;
|
|
129
|
+
var CHAT_TOP_PX = 128;
|
|
130
|
+
var CHAT_LEFT_EXTRA_PX = 72;
|
|
131
|
+
function readPx(name) {
|
|
132
|
+
if (typeof document === "undefined") return null;
|
|
133
|
+
const raw = getComputedStyle(document.documentElement).getPropertyValue(name).trim();
|
|
134
|
+
if (!raw) return null;
|
|
135
|
+
const value = Number.parseFloat(raw);
|
|
136
|
+
return Number.isFinite(value) ? value : null;
|
|
137
|
+
}
|
|
138
|
+
function defaultBoardChromeInsets(isBoardPage) {
|
|
139
|
+
return {
|
|
140
|
+
top: HEADER_HEIGHT_PX,
|
|
141
|
+
right: EDGE_PX,
|
|
142
|
+
bottom: isBoardPage ? COMPACT_BOTTOM_OFFSET_BOARD_PX : BOTTOM_OFFSET_DEFAULT_PX,
|
|
143
|
+
left: EDGE_PX
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
function readBoardChromeInsets(isBoardPage) {
|
|
147
|
+
const fallback = defaultBoardChromeInsets(isBoardPage);
|
|
148
|
+
return {
|
|
149
|
+
top: readPx(TOP_VAR) ?? fallback.top,
|
|
150
|
+
right: readPx(RIGHT_VAR) ?? fallback.right,
|
|
151
|
+
bottom: readPx(BOTTOM_VAR) ?? fallback.bottom,
|
|
152
|
+
left: readPx(LEFT_VAR) ?? fallback.left
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function sameInsets(left, right) {
|
|
156
|
+
return left.top === right.top && left.right === right.right && left.bottom === right.bottom && left.left === right.left;
|
|
157
|
+
}
|
|
158
|
+
function useBoardChromeInsets(isBoardPage) {
|
|
159
|
+
const [insets, setInsets] = useState(() => readBoardChromeInsets(isBoardPage));
|
|
160
|
+
useLayoutEffect(() => {
|
|
161
|
+
const update = () => {
|
|
162
|
+
const next = readBoardChromeInsets(isBoardPage);
|
|
163
|
+
setInsets((current) => sameInsets(current, next) ? current : next);
|
|
164
|
+
};
|
|
165
|
+
update();
|
|
166
|
+
const observer = new MutationObserver(update);
|
|
167
|
+
observer.observe(document.documentElement, {
|
|
168
|
+
attributes: true,
|
|
169
|
+
attributeFilter: ["style"]
|
|
170
|
+
});
|
|
171
|
+
return () => observer.disconnect();
|
|
172
|
+
}, [isBoardPage]);
|
|
173
|
+
return insets;
|
|
128
174
|
}
|
|
175
|
+
function cornerStyle(corner, insets) {
|
|
176
|
+
switch (corner) {
|
|
177
|
+
case "top-left":
|
|
178
|
+
return { top: insets.top, left: insets.left };
|
|
179
|
+
case "top-right":
|
|
180
|
+
return { top: insets.top, right: insets.right };
|
|
181
|
+
case "bottom-left":
|
|
182
|
+
return { bottom: insets.bottom, left: insets.left };
|
|
183
|
+
case "bottom-right":
|
|
184
|
+
return { bottom: insets.bottom, right: insets.right };
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
function chatPositionStyle(corner, insets) {
|
|
188
|
+
const oppositeLeft = corner === "top-right" || corner === "bottom-right";
|
|
189
|
+
const top = CHAT_TOP_PX + Math.max(0, insets.top - HEADER_HEIGHT_PX);
|
|
190
|
+
return oppositeLeft ? { top, bottom: insets.bottom, left: insets.left + CHAT_LEFT_EXTRA_PX } : { top, bottom: insets.bottom, right: insets.right };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// src/hooks/useCompactAvailableHeight.ts
|
|
129
194
|
function useCompactAvailableHeight(isOnBoardPage) {
|
|
130
|
-
const
|
|
131
|
-
const [availableHeight, setAvailableHeight] = useState(
|
|
195
|
+
const insets = useBoardChromeInsets(isOnBoardPage);
|
|
196
|
+
const [availableHeight, setAvailableHeight] = useState(() => {
|
|
197
|
+
if (typeof window === "undefined") return 400;
|
|
198
|
+
return window.innerHeight - insets.top - insets.bottom;
|
|
199
|
+
});
|
|
132
200
|
useEffect(() => {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
201
|
+
const update = () => {
|
|
202
|
+
setAvailableHeight(window.innerHeight - insets.top - insets.bottom);
|
|
203
|
+
};
|
|
204
|
+
update();
|
|
205
|
+
window.addEventListener("resize", update);
|
|
206
|
+
return () => window.removeEventListener("resize", update);
|
|
207
|
+
}, [insets.top, insets.bottom]);
|
|
138
208
|
return availableHeight;
|
|
139
209
|
}
|
|
140
210
|
var useCompactNavigation = () => {
|
|
@@ -162,8 +232,13 @@ var useCompactNavigation = () => {
|
|
|
162
232
|
const baseSorted = useSortedTracks(participants, 1);
|
|
163
233
|
const { pins } = useClassroomPins();
|
|
164
234
|
const sorted = useMemo(() => applyPinsFirst(baseSorted, pins), [baseSorted, pins]);
|
|
165
|
-
const
|
|
166
|
-
const
|
|
235
|
+
const screenShareStageIdentity = useCallStore((state) => state.screenShareStageIdentity);
|
|
236
|
+
const visibleSorted = useMemo(() => {
|
|
237
|
+
if (!screenShareStageIdentity) return sorted;
|
|
238
|
+
return sorted.filter((trackRef) => trackRef.source !== Track.Source.ScreenShare);
|
|
239
|
+
}, [screenShareStageIdentity, sorted]);
|
|
240
|
+
const currentParticipant = visibleSorted[currentParticipantIndex] || null;
|
|
241
|
+
const totalParticipants = visibleSorted.length;
|
|
167
242
|
const canGoNext = currentParticipantIndex < totalParticipants - 1;
|
|
168
243
|
const canGoPrev = currentParticipantIndex > 0;
|
|
169
244
|
const goToNext = () => {
|
|
@@ -192,9 +267,13 @@ var useCompactNavigation = () => {
|
|
|
192
267
|
setCurrentParticipantIndex(0);
|
|
193
268
|
}
|
|
194
269
|
}, [pinsKey, pins.length]);
|
|
270
|
+
useEffect(() => {
|
|
271
|
+
if (!screenShareStageIdentity) return;
|
|
272
|
+
setCurrentParticipantIndex(0);
|
|
273
|
+
}, [screenShareStageIdentity]);
|
|
195
274
|
return {
|
|
196
275
|
currentParticipant,
|
|
197
|
-
participants:
|
|
276
|
+
participants: visibleSorted,
|
|
198
277
|
currentIndex: currentParticipantIndex,
|
|
199
278
|
totalParticipants,
|
|
200
279
|
canGoNext,
|
|
@@ -484,7 +563,8 @@ function CompactCallCollapsedBar({
|
|
|
484
563
|
participant,
|
|
485
564
|
audioTrack,
|
|
486
565
|
onExpand,
|
|
487
|
-
className
|
|
566
|
+
className,
|
|
567
|
+
style
|
|
488
568
|
}) {
|
|
489
569
|
const { t } = useTranslation("calls");
|
|
490
570
|
const { bars } = useAudioWaveform(audioTrack ?? void 0, {
|
|
@@ -495,6 +575,7 @@ function CompactCallCollapsedBar({
|
|
|
495
575
|
return /* @__PURE__ */ jsxs(
|
|
496
576
|
"div",
|
|
497
577
|
{
|
|
578
|
+
style,
|
|
498
579
|
className: cn(
|
|
499
580
|
"bg-background-surface border-border-default flex items-center gap-2 rounded-2xl border px-2 py-2 shadow-lg",
|
|
500
581
|
className
|
|
@@ -751,6 +832,7 @@ function CompactCallVideoArea({
|
|
|
751
832
|
}
|
|
752
833
|
) }) : emptyState,
|
|
753
834
|
showPinPiPOverlay && /* @__PURE__ */ jsxs("div", { className: "absolute top-2 right-2 z-10 flex items-center gap-1", children: [
|
|
835
|
+
overlayTrackRef.source === Track.Source.ScreenShare ? /* @__PURE__ */ jsx(ScreenShareStageToggle, { trackRef: overlayTrackRef, inline: true }) : null,
|
|
754
836
|
/* @__PURE__ */ jsx(ParticipantPinToggle, { trackRef: overlayTrackRef, inline: true }),
|
|
755
837
|
showPiPButton && /* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
756
838
|
/* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
@@ -804,6 +886,7 @@ function CompactCallBottomBar({
|
|
|
804
886
|
isTutor
|
|
805
887
|
}) {
|
|
806
888
|
const { t } = useTranslation("calls");
|
|
889
|
+
const screenShareStage = useCallStore((state) => state.screenShareStageIdentity);
|
|
807
890
|
const nextViewMode = getNextCompactViewMode(compactViewMode);
|
|
808
891
|
const viewModeToggleMeta = {
|
|
809
892
|
expanded: { Icon: Users, label: t("compactView.mode.expanded") },
|
|
@@ -863,7 +946,7 @@ function CompactCallBottomBar({
|
|
|
863
946
|
/* @__PURE__ */ jsx(RaiseHandButton, { className: "h-8 w-8 min-w-8" })
|
|
864
947
|
] }),
|
|
865
948
|
/* @__PURE__ */ jsxs("div", { className: cn(barCn, "ml-1"), children: [
|
|
866
|
-
isTutor ? /* @__PURE__ */ jsxs(DropdownMenu, { children: [
|
|
949
|
+
!screenShareStage && (isTutor ? /* @__PURE__ */ jsxs(DropdownMenu, { children: [
|
|
867
950
|
/* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
868
951
|
/* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
869
952
|
Button,
|
|
@@ -907,18 +990,22 @@ function CompactCallBottomBar({
|
|
|
907
990
|
}
|
|
908
991
|
) }),
|
|
909
992
|
/* @__PURE__ */ jsx(TooltipContent, { children: t("compactView.returnToConference") })
|
|
910
|
-
] }),
|
|
993
|
+
] })),
|
|
911
994
|
/* @__PURE__ */ jsx(DisconnectButton, { className: "h-[32px] w-[32px] rounded-xl" })
|
|
912
995
|
] })
|
|
913
996
|
] });
|
|
914
997
|
}
|
|
915
|
-
var CompactCall = ({
|
|
998
|
+
var CompactCall = ({
|
|
999
|
+
saveUserChoices = true,
|
|
1000
|
+
withOutShadows = false,
|
|
1001
|
+
freeDrag = false
|
|
1002
|
+
}) => {
|
|
916
1003
|
const isMobile = usePhoneLayout();
|
|
917
1004
|
const { attributes, listeners, setNodeRef, transform } = useDraggable({
|
|
918
|
-
id: "draggable-call",
|
|
919
|
-
disabled: isMobile
|
|
1005
|
+
id: freeDrag ? "screen-share-stage-compact" : "draggable-call",
|
|
1006
|
+
disabled: freeDrag ? false : isMobile
|
|
920
1007
|
});
|
|
921
|
-
const style = isMobile ? void 0 : { transform: CSS.Translate.toString(transform) };
|
|
1008
|
+
const style = !freeDrag && isMobile ? void 0 : { transform: CSS.Translate.toString(transform) };
|
|
922
1009
|
const { saveAudioInputEnabled, saveVideoInputEnabled } = usePersistentUserChoices({
|
|
923
1010
|
preventSave: !saveUserChoices
|
|
924
1011
|
});
|
|
@@ -1051,11 +1138,12 @@ var CompactCall = ({ saveUserChoices = true, withOutShadows = false }) => {
|
|
|
1051
1138
|
"div",
|
|
1052
1139
|
{
|
|
1053
1140
|
ref: setNodeRef,
|
|
1141
|
+
...freeDrag ? listeners : {},
|
|
1054
1142
|
style: {
|
|
1055
1143
|
...style,
|
|
1056
|
-
...isMobile ? {} : { width: COMPACT_PANEL_WIDTH_PX, touchAction: "none" }
|
|
1144
|
+
...isMobile && !freeDrag ? {} : { width: COMPACT_PANEL_WIDTH_PX, touchAction: "none" }
|
|
1057
1145
|
},
|
|
1058
|
-
className: `compact-call-container flex flex-col ${isMobile ? "w-full" : "touch-none [&_video]:touch-none"}`,
|
|
1146
|
+
className: `compact-call-container flex flex-col ${isMobile && !freeDrag ? "w-full" : "touch-none [&_video]:touch-none"} ${freeDrag ? "cursor-grab active:cursor-grabbing" : ""}`,
|
|
1059
1147
|
children: [
|
|
1060
1148
|
/* @__PURE__ */ jsx(
|
|
1061
1149
|
CompactCallVideoArea,
|
|
@@ -1064,8 +1152,8 @@ var CompactCall = ({ saveUserChoices = true, withOutShadows = false }) => {
|
|
|
1064
1152
|
isCollapsed,
|
|
1065
1153
|
onCollapsedChange: setIsCollapsed,
|
|
1066
1154
|
withOutShadows,
|
|
1067
|
-
dragAttributes: attributes,
|
|
1068
|
-
dragListeners: listeners,
|
|
1155
|
+
dragAttributes: freeDrag ? void 0 : attributes,
|
|
1156
|
+
dragListeners: freeDrag ? void 0 : listeners,
|
|
1069
1157
|
compactViewMode,
|
|
1070
1158
|
currentParticipant,
|
|
1071
1159
|
currentAudioTrack: currentAudioTrack ?? null,
|
|
@@ -1119,11 +1207,177 @@ var CompactCall = ({ saveUserChoices = true, withOutShadows = false }) => {
|
|
|
1119
1207
|
}
|
|
1120
1208
|
);
|
|
1121
1209
|
};
|
|
1122
|
-
var
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1210
|
+
var EDGE_PX2 = 16;
|
|
1211
|
+
var STAGE_GONE_MS = 500;
|
|
1212
|
+
function clampStagePosition(x, y, width, height) {
|
|
1213
|
+
const maxX = Math.max(EDGE_PX2, window.innerWidth - width - EDGE_PX2);
|
|
1214
|
+
const maxY = Math.max(EDGE_PX2, window.innerHeight - height - EDGE_PX2);
|
|
1215
|
+
return {
|
|
1216
|
+
x: Math.min(Math.max(EDGE_PX2, x), maxX),
|
|
1217
|
+
y: Math.min(Math.max(EDGE_PX2, y), maxY)
|
|
1218
|
+
};
|
|
1219
|
+
}
|
|
1220
|
+
var ScreenShareStage = ({ identity }) => {
|
|
1221
|
+
const { t } = useTranslation("calls");
|
|
1222
|
+
const updateStore = useCallStore((state) => state.updateStore);
|
|
1223
|
+
const closePiP = usePiP()?.closePiP;
|
|
1224
|
+
const overlayRef = useRef(null);
|
|
1225
|
+
const panelRef = useRef(null);
|
|
1226
|
+
const [position, setPosition] = useState({ x: EDGE_PX2, y: EDGE_PX2 });
|
|
1227
|
+
const tracks = useTracks([{ source: Track.Source.ScreenShare, withPlaceholder: false }], {
|
|
1228
|
+
onlySubscribed: false
|
|
1229
|
+
});
|
|
1230
|
+
const track = tracks.find((item) => item.participant.identity === identity) ?? null;
|
|
1231
|
+
const publication = track?.publication;
|
|
1232
|
+
const hasTrack = Boolean(publication);
|
|
1233
|
+
const sensors = useSensors(
|
|
1234
|
+
useSensor(CompactPointerSensor, { activationConstraint: { distance: 8 } }),
|
|
1235
|
+
useSensor(CompactTouchSensor, { activationConstraint: { distance: 8 } })
|
|
1236
|
+
);
|
|
1237
|
+
useEffect(() => {
|
|
1238
|
+
closePiP?.();
|
|
1239
|
+
}, [closePiP]);
|
|
1240
|
+
useEffect(() => {
|
|
1241
|
+
if (!(publication instanceof RemoteTrackPublication) || publication.isSubscribed) return;
|
|
1242
|
+
publication.setSubscribed(true);
|
|
1243
|
+
}, [publication]);
|
|
1244
|
+
useEffect(() => {
|
|
1245
|
+
if (hasTrack) return void 0;
|
|
1246
|
+
const timeoutId = window.setTimeout(() => {
|
|
1247
|
+
if (useCallStore.getState().screenShareStageIdentity === identity) {
|
|
1248
|
+
useCallStore.getState().updateStore("screenShareStageIdentity", null);
|
|
1249
|
+
}
|
|
1250
|
+
}, STAGE_GONE_MS);
|
|
1251
|
+
return () => window.clearTimeout(timeoutId);
|
|
1252
|
+
}, [hasTrack, identity]);
|
|
1253
|
+
useEffect(() => {
|
|
1254
|
+
const onKeyDown = (event) => {
|
|
1255
|
+
if (event.key !== "Escape" || event.defaultPrevented) return;
|
|
1256
|
+
event.preventDefault();
|
|
1257
|
+
updateStore("screenShareStageIdentity", null);
|
|
1258
|
+
};
|
|
1259
|
+
window.addEventListener("keydown", onKeyDown);
|
|
1260
|
+
return () => window.removeEventListener("keydown", onKeyDown);
|
|
1261
|
+
}, [updateStore]);
|
|
1262
|
+
useEffect(() => {
|
|
1263
|
+
const portalNode = overlayRef.current;
|
|
1264
|
+
if (!portalNode) return void 0;
|
|
1265
|
+
const hidden = [];
|
|
1266
|
+
Array.from(document.body.children).forEach((node) => {
|
|
1267
|
+
if (!(node instanceof HTMLElement)) return;
|
|
1268
|
+
if (node === portalNode || node.contains(portalNode)) return;
|
|
1269
|
+
if (node.hasAttribute("data-sonner-toaster")) return;
|
|
1270
|
+
if (node.hasAttribute("inert")) return;
|
|
1271
|
+
node.setAttribute("inert", "");
|
|
1272
|
+
hidden.push(node);
|
|
1273
|
+
});
|
|
1274
|
+
const { body, documentElement } = document;
|
|
1275
|
+
const previousBodyOverflow = body.style.overflow;
|
|
1276
|
+
const previousHtmlOverflow = documentElement.style.overflow;
|
|
1277
|
+
body.style.overflow = "hidden";
|
|
1278
|
+
documentElement.style.overflow = "hidden";
|
|
1279
|
+
portalNode?.focus();
|
|
1280
|
+
return () => {
|
|
1281
|
+
hidden.forEach((node) => node.removeAttribute("inert"));
|
|
1282
|
+
body.style.overflow = previousBodyOverflow;
|
|
1283
|
+
documentElement.style.overflow = previousHtmlOverflow;
|
|
1284
|
+
};
|
|
1285
|
+
}, []);
|
|
1286
|
+
useLayoutEffect(() => {
|
|
1287
|
+
const node = panelRef.current;
|
|
1288
|
+
if (!node) return void 0;
|
|
1289
|
+
const clamp = () => {
|
|
1290
|
+
const current = panelRef.current;
|
|
1291
|
+
if (!current) return;
|
|
1292
|
+
setPosition((prev) => {
|
|
1293
|
+
const next = clampStagePosition(prev.x, prev.y, current.offsetWidth, current.offsetHeight);
|
|
1294
|
+
if (next.x === prev.x && next.y === prev.y) return prev;
|
|
1295
|
+
return next;
|
|
1296
|
+
});
|
|
1297
|
+
};
|
|
1298
|
+
clamp();
|
|
1299
|
+
const observer = new ResizeObserver(clamp);
|
|
1300
|
+
observer.observe(node);
|
|
1301
|
+
window.addEventListener("resize", clamp);
|
|
1302
|
+
return () => {
|
|
1303
|
+
observer.disconnect();
|
|
1304
|
+
window.removeEventListener("resize", clamp);
|
|
1305
|
+
};
|
|
1306
|
+
}, []);
|
|
1307
|
+
const handleDragEnd = (event) => {
|
|
1308
|
+
const node = panelRef.current;
|
|
1309
|
+
setPosition((prev) => {
|
|
1310
|
+
const next = clampStagePosition(
|
|
1311
|
+
prev.x + event.delta.x,
|
|
1312
|
+
prev.y + event.delta.y,
|
|
1313
|
+
node?.offsetWidth ?? COMPACT_PANEL_WIDTH_PX,
|
|
1314
|
+
node?.offsetHeight ?? 320
|
|
1315
|
+
);
|
|
1316
|
+
if (next.x === prev.x && next.y === prev.y) return prev;
|
|
1317
|
+
return next;
|
|
1318
|
+
});
|
|
1319
|
+
};
|
|
1320
|
+
if (typeof document === "undefined") return null;
|
|
1321
|
+
const sharerName = track?.participant.name;
|
|
1322
|
+
return createPortal(
|
|
1323
|
+
/* @__PURE__ */ jsxs(
|
|
1324
|
+
"div",
|
|
1325
|
+
{
|
|
1326
|
+
ref: overlayRef,
|
|
1327
|
+
role: "dialog",
|
|
1328
|
+
"aria-modal": "true",
|
|
1329
|
+
"aria-label": t("participant.stageTitle"),
|
|
1330
|
+
tabIndex: -1,
|
|
1331
|
+
className: "screen-share-stage fixed inset-0 z-[2100] bg-black outline-none",
|
|
1332
|
+
children: [
|
|
1333
|
+
/* @__PURE__ */ jsxs("div", { className: "relative h-full w-full", children: [
|
|
1334
|
+
track && publication ? /* @__PURE__ */ jsx(ScreenShareZoom, { trackRef: track, className: "rounded-none", children: /* @__PURE__ */ jsx(
|
|
1335
|
+
VideoTrack,
|
|
1336
|
+
{
|
|
1337
|
+
trackRef: track,
|
|
1338
|
+
manageSubscription: false,
|
|
1339
|
+
className: "h-full w-full bg-black object-contain"
|
|
1340
|
+
}
|
|
1341
|
+
) }) : null,
|
|
1342
|
+
track ? /* @__PURE__ */ jsxs("div", { className: "bg-background-surface/80 pointer-events-none absolute bottom-4 left-4 z-20 flex h-6 items-center gap-1.5 rounded-lg px-1.5 py-1 backdrop-blur", children: [
|
|
1343
|
+
/* @__PURE__ */ jsx(ScreenShareIcon, { style: { marginRight: "0.25rem" } }),
|
|
1344
|
+
/* @__PURE__ */ jsxs("span", { className: "text-xs-base-size text-text-primary leading-[16px]", children: [
|
|
1345
|
+
t("participant.screenSharePrefix"),
|
|
1346
|
+
sharerName ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1347
|
+
"\xA0",
|
|
1348
|
+
sharerName
|
|
1349
|
+
] }) : null
|
|
1350
|
+
] })
|
|
1351
|
+
] }) : null,
|
|
1352
|
+
/* @__PURE__ */ jsxs("div", { className: "absolute top-3 right-3 z-40 flex items-center gap-1", children: [
|
|
1353
|
+
/* @__PURE__ */ jsx(ScreenShareStageToggle, { trackRef: track ?? void 0, inline: true }),
|
|
1354
|
+
track ? /* @__PURE__ */ jsx(ParticipantPinToggle, { trackRef: track, inline: true }) : null
|
|
1355
|
+
] })
|
|
1356
|
+
] }),
|
|
1357
|
+
/* @__PURE__ */ jsx(
|
|
1358
|
+
DndContext,
|
|
1359
|
+
{
|
|
1360
|
+
sensors,
|
|
1361
|
+
onDragEnd: handleDragEnd,
|
|
1362
|
+
modifiers: [restrictToWindowEdges],
|
|
1363
|
+
autoScroll: false,
|
|
1364
|
+
children: /* @__PURE__ */ jsx(
|
|
1365
|
+
"div",
|
|
1366
|
+
{
|
|
1367
|
+
ref: panelRef,
|
|
1368
|
+
className: "absolute z-30 w-max",
|
|
1369
|
+
style: { left: position.x, top: position.y },
|
|
1370
|
+
children: /* @__PURE__ */ jsx(CompactCall, { freeDrag: true })
|
|
1371
|
+
}
|
|
1372
|
+
)
|
|
1373
|
+
}
|
|
1374
|
+
),
|
|
1375
|
+
/* @__PURE__ */ jsx(Chat, { compactPositionStyle: { top: 56, right: 16, bottom: 16, zIndex: 20 } })
|
|
1376
|
+
]
|
|
1377
|
+
}
|
|
1378
|
+
),
|
|
1379
|
+
document.body
|
|
1380
|
+
);
|
|
1127
1381
|
};
|
|
1128
1382
|
var useCompactDragActive = () => {
|
|
1129
1383
|
const [isDragging, setIsDragging] = useState(false);
|
|
@@ -1142,7 +1396,7 @@ var useCompactDragActive = () => {
|
|
|
1142
1396
|
});
|
|
1143
1397
|
return isDragging;
|
|
1144
1398
|
};
|
|
1145
|
-
var DroppableCorner = ({ id,
|
|
1399
|
+
var DroppableCorner = ({ id, style }) => {
|
|
1146
1400
|
const { setNodeRef } = useDroppable({
|
|
1147
1401
|
id
|
|
1148
1402
|
});
|
|
@@ -1151,17 +1405,18 @@ var DroppableCorner = ({ id, className }) => {
|
|
|
1151
1405
|
"div",
|
|
1152
1406
|
{
|
|
1153
1407
|
ref: setNodeRef,
|
|
1154
|
-
|
|
1408
|
+
style,
|
|
1409
|
+
className: `pointer-events-none absolute z-20 size-1/3 transition-all duration-200 ${isDragging ? "bg-status-info-background/20 ring-border-selected rounded-lg ring-2" : ""}`
|
|
1155
1410
|
}
|
|
1156
1411
|
);
|
|
1157
1412
|
};
|
|
1158
|
-
var DroppableAreas = () => {
|
|
1413
|
+
var DroppableAreas = ({ insets }) => {
|
|
1159
1414
|
const isDragging = useCompactDragActive();
|
|
1160
1415
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1161
|
-
/* @__PURE__ */ jsx(DroppableCorner, { id: "top-left",
|
|
1162
|
-
/* @__PURE__ */ jsx(DroppableCorner, { id: "top-right",
|
|
1163
|
-
/* @__PURE__ */ jsx(DroppableCorner, { id: "bottom-left",
|
|
1164
|
-
/* @__PURE__ */ jsx(DroppableCorner, { id: "bottom-right",
|
|
1416
|
+
/* @__PURE__ */ jsx(DroppableCorner, { id: "top-left", style: cornerStyle("top-left", insets) }),
|
|
1417
|
+
/* @__PURE__ */ jsx(DroppableCorner, { id: "top-right", style: cornerStyle("top-right", insets) }),
|
|
1418
|
+
/* @__PURE__ */ jsx(DroppableCorner, { id: "bottom-left", style: cornerStyle("bottom-left", insets) }),
|
|
1419
|
+
/* @__PURE__ */ jsx(DroppableCorner, { id: "bottom-right", style: cornerStyle("bottom-right", insets) }),
|
|
1165
1420
|
/* @__PURE__ */ jsx(DragOverlay, { style: { pointerEvents: "none" }, children: isDragging ? /* @__PURE__ */ jsx(CompactCall, {}) : null })
|
|
1166
1421
|
] });
|
|
1167
1422
|
};
|
|
@@ -1201,26 +1456,9 @@ var Compact = ({ children, hideOverlay = false }) => {
|
|
|
1201
1456
|
}
|
|
1202
1457
|
};
|
|
1203
1458
|
const isBoardPage = navigation.pathnameIncludes("/board");
|
|
1204
|
-
const
|
|
1459
|
+
const insets = useBoardChromeInsets(isBoardPage);
|
|
1205
1460
|
const mobileContentMarginTop = hideOverlay || !isMobile ? 0 : (headerHeight > 0 ? headerHeight : COMPACT_MOBILE_OVERLAY_FALLBACK_PX) + COMPACT_MOBILE_CONTENT_GAP_PX;
|
|
1206
|
-
const
|
|
1207
|
-
switch (corner) {
|
|
1208
|
-
case "top-left":
|
|
1209
|
-
return "top-16 left-4";
|
|
1210
|
-
case "top-right":
|
|
1211
|
-
return "top-16 right-4";
|
|
1212
|
-
case "bottom-left":
|
|
1213
|
-
return `${bottomOffset} left-4`;
|
|
1214
|
-
case "bottom-right":
|
|
1215
|
-
return `${bottomOffset} right-4`;
|
|
1216
|
-
}
|
|
1217
|
-
};
|
|
1218
|
-
const getChatPositionClasses = (corner) => {
|
|
1219
|
-
const opposite = OPPOSITE_CORNER[corner];
|
|
1220
|
-
const horizontal = opposite.includes("left") ? "left-22" : "right-4";
|
|
1221
|
-
return `top-32 ${bottomOffset} ${horizontal}`;
|
|
1222
|
-
};
|
|
1223
|
-
const container = /* @__PURE__ */ jsx("div", { className: `relative flex h-dvh flex-col bg-transparent`, children: isMobile ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1461
|
+
const container = /* @__PURE__ */ jsx("div", { className: `relative flex h-dvh flex-col bg-transparent`, "data-board-chrome-aware": "", children: isMobile ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1224
1462
|
!hideOverlay && /* @__PURE__ */ jsx("div", { ref: headerRef, className: "fixed top-2 right-0 left-0 z-10 w-full px-2", children: /* @__PURE__ */ jsx(CompactCall, { withOutShadows: true }) }),
|
|
1225
1463
|
/* @__PURE__ */ jsx(
|
|
1226
1464
|
"div",
|
|
@@ -1232,12 +1470,13 @@ var Compact = ({ children, hideOverlay = false }) => {
|
|
|
1232
1470
|
)
|
|
1233
1471
|
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1234
1472
|
!hideOverlay && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1235
|
-
/* @__PURE__ */ jsx(DroppableAreas, {}),
|
|
1236
|
-
/* @__PURE__ */ jsx(Chat, {
|
|
1473
|
+
/* @__PURE__ */ jsx(DroppableAreas, { insets }),
|
|
1474
|
+
/* @__PURE__ */ jsx(Chat, { compactPositionStyle: chatPositionStyle(activeCorner, insets) }),
|
|
1237
1475
|
/* @__PURE__ */ jsx(
|
|
1238
1476
|
"div",
|
|
1239
1477
|
{
|
|
1240
|
-
className:
|
|
1478
|
+
className: "absolute z-20 transition-all duration-500 ease-out",
|
|
1479
|
+
style: cornerStyle(activeCorner, insets),
|
|
1241
1480
|
children: /* @__PURE__ */ jsx(CompactCall, {})
|
|
1242
1481
|
}
|
|
1243
1482
|
)
|
|
@@ -1248,11 +1487,19 @@ var Compact = ({ children, hideOverlay = false }) => {
|
|
|
1248
1487
|
};
|
|
1249
1488
|
var CompactViewInner = ({ children }) => {
|
|
1250
1489
|
const { mode, token } = useCallStore();
|
|
1490
|
+
const screenShareStageIdentity = useCallStore((state) => state.screenShareStageIdentity);
|
|
1491
|
+
const isStarted = useCallStore((state) => state.isStarted);
|
|
1251
1492
|
const { room } = useRoom();
|
|
1252
1493
|
const pip = usePiP();
|
|
1253
1494
|
const isPiPActive = !!pip?.isPiPActive;
|
|
1254
1495
|
useParticipantJoinSync();
|
|
1255
1496
|
useParticipantSounds();
|
|
1497
|
+
useEffect(() => {
|
|
1498
|
+
if (!screenShareStageIdentity) return;
|
|
1499
|
+
if (!room || !token || !isStarted) {
|
|
1500
|
+
useCallStore.getState().updateStore("screenShareStageIdentity", null);
|
|
1501
|
+
}
|
|
1502
|
+
}, [isStarted, room, screenShareStageIdentity, token]);
|
|
1256
1503
|
const navigation = useCallsNavigation();
|
|
1257
1504
|
const { updateStore: updateChatStore } = useChatStore();
|
|
1258
1505
|
useEffect(() => {
|
|
@@ -1277,14 +1524,17 @@ var CompactViewInner = ({ children }) => {
|
|
|
1277
1524
|
/* @__PURE__ */ jsx(PermissionsDialog, {})
|
|
1278
1525
|
] });
|
|
1279
1526
|
}
|
|
1527
|
+
const stageLayer = screenShareStageIdentity && room && token && isStarted ? /* @__PURE__ */ jsx(ScreenShareStage, { identity: screenShareStageIdentity }, screenShareStageIdentity) : null;
|
|
1280
1528
|
if (mode === "full") {
|
|
1281
1529
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1282
1530
|
contentWrapper,
|
|
1531
|
+
stageLayer,
|
|
1283
1532
|
/* @__PURE__ */ jsx(PermissionsDialog, {})
|
|
1284
1533
|
] });
|
|
1285
1534
|
}
|
|
1286
1535
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1287
|
-
/* @__PURE__ */ jsx(Compact, { hideOverlay: isPiPActive, children }),
|
|
1536
|
+
/* @__PURE__ */ jsx(Compact, { hideOverlay: isPiPActive || Boolean(screenShareStageIdentity), children }),
|
|
1537
|
+
stageLayer,
|
|
1288
1538
|
/* @__PURE__ */ jsx(PermissionsDialog, {})
|
|
1289
1539
|
] });
|
|
1290
1540
|
};
|