@whereby.com/browser-sdk 3.1.0 → 3.2.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/cdn/v3-embed.js +6 -6
- package/dist/cdn/v3-react.js +2 -2
- package/dist/react/index.d.ts +23 -31
- package/dist/react/index.esm.js +228 -38
- package/package.json +4 -2
package/dist/react/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { LocalMediaOptions, RemoteParticipant, LocalParticipant, Screenshare, ConnectionStatus, NotificationsEventEmitter, ClientView, ChatMessage as ChatMessage$1 } from '@whereby.com/core';
|
|
3
3
|
import { RoleName } from '@whereby.com/media';
|
|
4
|
+
import * as _radix_ui_react_popover from '@radix-ui/react-popover';
|
|
5
|
+
import { PopoverProps } from '@radix-ui/react-popover';
|
|
4
6
|
|
|
5
7
|
interface ProviderProps {
|
|
6
8
|
children: React.ReactNode;
|
|
@@ -142,41 +144,31 @@ declare function useRoomConnection(roomUrl: string, roomConnectionOptions?: UseR
|
|
|
142
144
|
|
|
143
145
|
declare function useLocalMedia(optionsOrStream?: UseLocalMediaOptions | MediaStream): UseLocalMediaResult;
|
|
144
146
|
|
|
145
|
-
type
|
|
146
|
-
|
|
147
|
-
left: number;
|
|
147
|
+
type GridCellSelfProps = {
|
|
148
|
+
participant: ClientView;
|
|
148
149
|
};
|
|
149
|
-
type
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
aspectRatio?: number;
|
|
155
|
-
avatarSize?: number;
|
|
156
|
-
cellPaddings?: {
|
|
157
|
-
top: number;
|
|
158
|
-
right: number;
|
|
159
|
-
};
|
|
160
|
-
client?: ClientView;
|
|
161
|
-
clientId: string;
|
|
162
|
-
isDraggable?: boolean;
|
|
163
|
-
isPlaceholder?: boolean;
|
|
164
|
-
isSubgrid?: boolean;
|
|
165
|
-
type: string;
|
|
166
|
-
};
|
|
167
|
-
|
|
150
|
+
type GridCellProps = GridCellSelfProps & React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>;
|
|
151
|
+
declare const GridCell: React.ForwardRefExoticComponent<Omit<GridCellProps, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
152
|
+
declare const GridVideoView: React.ForwardRefExoticComponent<Omit<VideoViewProps, "ref" | "stream"> & {
|
|
153
|
+
stream?: MediaStream | undefined;
|
|
154
|
+
} & React.RefAttributes<WherebyVideoElement>>;
|
|
168
155
|
interface GridProps {
|
|
169
|
-
renderParticipant?: ({
|
|
170
|
-
|
|
171
|
-
clientId: string;
|
|
172
|
-
bounds: Bounds;
|
|
173
|
-
origin: Origin;
|
|
174
|
-
};
|
|
175
|
-
participant: CellView["client"];
|
|
156
|
+
renderParticipant?: ({ participant }: {
|
|
157
|
+
participant: ClientView;
|
|
176
158
|
}) => React.ReactNode;
|
|
159
|
+
gridGap?: number;
|
|
177
160
|
videoGridGap?: number;
|
|
161
|
+
enableSubgrid?: boolean;
|
|
178
162
|
stageParticipantLimit?: number;
|
|
163
|
+
enableParticipantMenu?: boolean;
|
|
179
164
|
}
|
|
180
|
-
declare function Grid({ renderParticipant, stageParticipantLimit, videoGridGap }: GridProps): React.JSX.Element;
|
|
165
|
+
declare function Grid({ renderParticipant, stageParticipantLimit, gridGap, videoGridGap, enableSubgrid, enableParticipantMenu, }: GridProps): React.JSX.Element;
|
|
166
|
+
|
|
167
|
+
declare const ParticipantMenu: (props: PopoverProps) => React.JSX.Element;
|
|
168
|
+
declare const ParticipantMenuContent: React.ForwardRefExoticComponent<Omit<Omit<_radix_ui_react_popover.PopoverContentProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
169
|
+
declare const ParticipantMenuTrigger: React.ForwardRefExoticComponent<Omit<_radix_ui_react_popover.PopoverTriggerProps & React.RefAttributes<HTMLButtonElement>, "ref"> & React.RefAttributes<HTMLButtonElement>>;
|
|
170
|
+
declare const ParticipantMenuItem: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, "ref"> & {
|
|
171
|
+
participantAction?: "maximize" | "spotlight" | undefined;
|
|
172
|
+
} & React.RefAttributes<HTMLButtonElement>>;
|
|
181
173
|
|
|
182
|
-
export { type ChatMessageState as ChatMessage, type LocalParticipantState as LocalParticipant, type RemoteParticipantState as RemoteParticipant, type RoomConnectionState as RoomConnection, type ScreenshareState as Screenshare, type UseLocalMediaResult, Grid as VideoGrid, VideoView, type WaitingParticipantState as WaitingParticipant, Provider as WherebyProvider, useLocalMedia, useRoomConnection };
|
|
174
|
+
export { type ChatMessageState as ChatMessage, GridCell, GridVideoView, type LocalParticipantState as LocalParticipant, ParticipantMenu, ParticipantMenuContent, ParticipantMenuItem, ParticipantMenuTrigger, type RemoteParticipantState as RemoteParticipant, type RoomConnectionState as RoomConnection, type ScreenshareState as Screenshare, type UseLocalMediaResult, Grid as VideoGrid, VideoView, type WaitingParticipantState as WaitingParticipant, Provider as WherebyProvider, useLocalMedia, useRoomConnection };
|
package/dist/react/index.esm.js
CHANGED
|
@@ -3,6 +3,7 @@ import { Provider as Provider$1, useDispatch, useSelector } from 'react-redux';
|
|
|
3
3
|
import { createServices, createStore, selectCurrentSpeakerDeviceId, debounce, doRtcReportStreamResolution, selectChatMessages, selectCloudRecordingRaw, selectLocalParticipantRaw, selectLocalMediaStream, selectRemoteParticipants, selectScreenshares, selectRoomConnectionStatus, selectStreamingRaw, selectWaitingParticipants, selectNotificationsEmitter, selectSpotlightedClientViews, doAppStop, doSendChatMessage, doKnockRoom, doSetDisplayName, toggleCameraEnabled, toggleMicrophoneEnabled, toggleLowDataModeEnabled, doSetLocalStickyReaction, doRequestAudioEnable, doAcceptWaitingParticipant, doRejectWaitingParticipant, doStartCloudRecording, doStartScreenshare, doStopCloudRecording, doStopScreenshare, doAppStart, doLockRoom, doSpotlightParticipant, doRemoveSpotlight, doKickParticipant, doEndMeeting, selectCameraDeviceError, selectCameraDevices, selectCurrentCameraDeviceId, selectCurrentMicrophoneDeviceId, selectIsSettingCameraDevice, selectIsSettingMicrophoneDevice, selectIsLocalMediaStarting, selectMicrophoneDeviceError, selectMicrophoneDevices, selectSpeakerDevices, selectLocalMediaStartError, doStartLocalMedia, doStopLocalMedia, setCurrentCameraDeviceId, setCurrentMicrophoneDeviceId, setCurrentSpeakerDeviceId, selectAllClientViews, selectNumParticipants } from '@whereby.com/core';
|
|
4
4
|
import { createSelector } from '@reduxjs/toolkit';
|
|
5
5
|
import runes from 'runes';
|
|
6
|
+
import * as PopoverPrimitive from '@radix-ui/react-popover';
|
|
6
7
|
|
|
7
8
|
function Provider({ children }) {
|
|
8
9
|
const services = createServices();
|
|
@@ -59,8 +60,8 @@ const VideoView = React.forwardRef((_a, ref) => {
|
|
|
59
60
|
if (!videoEl.current) {
|
|
60
61
|
return null;
|
|
61
62
|
}
|
|
62
|
-
const h = videoEl.current.
|
|
63
|
-
const w = videoEl.current.
|
|
63
|
+
const h = videoEl.current.videoHeight;
|
|
64
|
+
const w = videoEl.current.videoWidth;
|
|
64
65
|
if (w && h && w + h > 20) {
|
|
65
66
|
return w / h;
|
|
66
67
|
}
|
|
@@ -145,7 +146,7 @@ const selectRoomConnectionState = createSelector(selectChatMessages, selectCloud
|
|
|
145
146
|
return state;
|
|
146
147
|
});
|
|
147
148
|
|
|
148
|
-
const browserSdkVersion = "3.
|
|
149
|
+
const browserSdkVersion = "3.2.0";
|
|
149
150
|
|
|
150
151
|
const defaultRoomConnectionOptions = {
|
|
151
152
|
localMediaOptions: {
|
|
@@ -1468,13 +1469,16 @@ function makeVideoCellView({ aspectRatio, avatarSize, cellPaddings, client = und
|
|
|
1468
1469
|
const STAGE_PARTICIPANT_LIMIT = 12;
|
|
1469
1470
|
const ACTIVE_VIDEO_SUBGRID_TRIGGER = 12;
|
|
1470
1471
|
|
|
1471
|
-
function calculateSubgridViews({ clientViews, activeVideosSubgridTrigger, shouldShowSubgrid, spotlightedParticipants, }) {
|
|
1472
|
+
function calculateSubgridViews({ clientViews, activeVideosSubgridTrigger, shouldShowSubgrid, spotlightedParticipants, maximizedParticipant, }) {
|
|
1472
1473
|
if (!shouldShowSubgrid) {
|
|
1473
1474
|
return [];
|
|
1474
1475
|
}
|
|
1475
1476
|
const hasSpotlights = spotlightedParticipants.length > 0;
|
|
1476
1477
|
const hasPresentationStage = hasSpotlights;
|
|
1477
|
-
const
|
|
1478
|
+
const notMaximized = maximizedParticipant
|
|
1479
|
+
? clientViews.filter((client) => client.id !== maximizedParticipant.id)
|
|
1480
|
+
: clientViews;
|
|
1481
|
+
const notSpotlighted = notMaximized.filter((client) => !client.isPresentation && !spotlightedParticipants.includes(client));
|
|
1478
1482
|
const noVideoViews = notSpotlighted.filter((client) => !client.isVideoEnabled);
|
|
1479
1483
|
const videoLimitReached = notSpotlighted.filter((client) => client.isVideoEnabled).length > activeVideosSubgridTrigger;
|
|
1480
1484
|
const unmutedVideos = notSpotlighted.filter((client) => !noVideoViews.includes(client) && client.isAudioEnabled);
|
|
@@ -1494,27 +1498,34 @@ function calculateSubgridViews({ clientViews, activeVideosSubgridTrigger, should
|
|
|
1494
1498
|
}
|
|
1495
1499
|
return noVideoViews;
|
|
1496
1500
|
}
|
|
1497
|
-
function useGridParticipants({ activeVideosSubgridTrigger = ACTIVE_VIDEO_SUBGRID_TRIGGER, stageParticipantLimit = STAGE_PARTICIPANT_LIMIT, forceSubgrid = true, } = {}) {
|
|
1501
|
+
function useGridParticipants({ activeVideosSubgridTrigger = ACTIVE_VIDEO_SUBGRID_TRIGGER, stageParticipantLimit = STAGE_PARTICIPANT_LIMIT, forceSubgrid = true, enableSubgrid = true, maximizedParticipant, } = {}) {
|
|
1498
1502
|
const allClientViews = useAppSelector(selectAllClientViews);
|
|
1499
1503
|
const spotlightedParticipants = useAppSelector(selectSpotlightedClientViews);
|
|
1500
1504
|
const numParticipants = useAppSelector(selectNumParticipants);
|
|
1501
1505
|
const shouldShowSubgrid = React.useMemo(() => {
|
|
1506
|
+
if (!enableSubgrid) {
|
|
1507
|
+
return false;
|
|
1508
|
+
}
|
|
1502
1509
|
return forceSubgrid ? true : numParticipants > stageParticipantLimit;
|
|
1503
|
-
}, [forceSubgrid, numParticipants, stageParticipantLimit]);
|
|
1510
|
+
}, [forceSubgrid, numParticipants, stageParticipantLimit, enableSubgrid]);
|
|
1504
1511
|
const clientViewsInSubgrid = React.useMemo(() => {
|
|
1505
1512
|
return calculateSubgridViews({
|
|
1506
1513
|
clientViews: allClientViews,
|
|
1507
1514
|
activeVideosSubgridTrigger,
|
|
1508
1515
|
shouldShowSubgrid,
|
|
1509
1516
|
spotlightedParticipants,
|
|
1517
|
+
maximizedParticipant,
|
|
1510
1518
|
});
|
|
1511
1519
|
}, [allClientViews, shouldShowSubgrid, activeVideosSubgridTrigger, spotlightedParticipants]);
|
|
1512
1520
|
const clientViewsOnStage = React.useMemo(() => {
|
|
1513
1521
|
return allClientViews.filter((client) => !clientViewsInSubgrid.includes(client));
|
|
1514
1522
|
}, [allClientViews, clientViewsInSubgrid]);
|
|
1515
1523
|
const clientViewsInPresentationGrid = React.useMemo(() => {
|
|
1524
|
+
if (maximizedParticipant) {
|
|
1525
|
+
return [maximizedParticipant];
|
|
1526
|
+
}
|
|
1516
1527
|
return spotlightedParticipants;
|
|
1517
|
-
}, [spotlightedParticipants]);
|
|
1528
|
+
}, [spotlightedParticipants, maximizedParticipant]);
|
|
1518
1529
|
const clientViewsInGrid = React.useMemo(() => {
|
|
1519
1530
|
return clientViewsOnStage.filter((client) => !clientViewsInPresentationGrid.includes(client));
|
|
1520
1531
|
}, [clientViewsOnStage, clientViewsInPresentationGrid]);
|
|
@@ -1525,13 +1536,16 @@ function useGridParticipants({ activeVideosSubgridTrigger = ACTIVE_VIDEO_SUBGRID
|
|
|
1525
1536
|
};
|
|
1526
1537
|
}
|
|
1527
1538
|
|
|
1528
|
-
function useGrid({ activeVideosSubgridTrigger, forceSubgrid, stageParticipantLimit = STAGE_PARTICIPANT_LIMIT, videoGridGap = 8, } = {}) {
|
|
1539
|
+
function useGrid({ activeVideosSubgridTrigger, forceSubgrid, stageParticipantLimit = STAGE_PARTICIPANT_LIMIT, gridGap = 8, videoGridGap = 8, enableSubgrid = true, } = {}) {
|
|
1529
1540
|
const [containerBounds, setContainerBounds] = React.useState({ width: 0, height: 0 });
|
|
1530
1541
|
const [clientAspectRatios, setClientAspectRatios] = React.useState({});
|
|
1542
|
+
const [maximizedParticipant, setMaximizedParticipant] = React.useState(null);
|
|
1531
1543
|
const { clientViewsInGrid, clientViewsInPresentationGrid, clientViewsInSubgrid } = useGridParticipants({
|
|
1532
1544
|
activeVideosSubgridTrigger,
|
|
1533
1545
|
forceSubgrid,
|
|
1534
1546
|
stageParticipantLimit,
|
|
1547
|
+
enableSubgrid,
|
|
1548
|
+
maximizedParticipant,
|
|
1535
1549
|
});
|
|
1536
1550
|
const cellViewsVideoGrid = React.useMemo(() => {
|
|
1537
1551
|
return clientViewsInGrid.map((client) => {
|
|
@@ -1570,7 +1584,7 @@ function useGrid({ activeVideosSubgridTrigger, forceSubgrid, stageParticipantLim
|
|
|
1570
1584
|
const videoStage = React.useMemo(() => {
|
|
1571
1585
|
return calculateLayout({
|
|
1572
1586
|
frame: containerFrame,
|
|
1573
|
-
gridGap
|
|
1587
|
+
gridGap,
|
|
1574
1588
|
isConstrained: false,
|
|
1575
1589
|
roomBounds: containerFrame.bounds,
|
|
1576
1590
|
videos: cellViewsVideoGrid,
|
|
@@ -1578,14 +1592,17 @@ function useGrid({ activeVideosSubgridTrigger, forceSubgrid, stageParticipantLim
|
|
|
1578
1592
|
presentationVideos: cellViewsInPresentationGrid,
|
|
1579
1593
|
subgridVideos: cellViewsInSubgrid,
|
|
1580
1594
|
});
|
|
1581
|
-
}, [containerFrame, cellViewsVideoGrid, cellViewsInPresentationGrid, cellViewsInSubgrid]);
|
|
1595
|
+
}, [containerFrame, cellViewsVideoGrid, cellViewsInPresentationGrid, cellViewsInSubgrid, gridGap, videoGridGap]);
|
|
1582
1596
|
return {
|
|
1583
1597
|
cellViewsVideoGrid,
|
|
1584
1598
|
cellViewsInPresentationGrid,
|
|
1585
1599
|
cellViewsInSubgrid,
|
|
1600
|
+
clientAspectRatios,
|
|
1586
1601
|
videoStage,
|
|
1587
1602
|
setContainerBounds,
|
|
1588
1603
|
setClientAspectRatios,
|
|
1604
|
+
maximizedParticipant,
|
|
1605
|
+
setMaximizedParticipant,
|
|
1589
1606
|
};
|
|
1590
1607
|
}
|
|
1591
1608
|
|
|
@@ -1647,36 +1664,200 @@ function VideoMutedIndicator({ avatarUrl, displayName, isSmallCell, withRoundedC
|
|
|
1647
1664
|
React.createElement(Avatar, { variant: "square", avatarUrl: avatarUrl, name: displayName, size: isSmallCell ? 60 : 80 }))));
|
|
1648
1665
|
}
|
|
1649
1666
|
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1667
|
+
const Popover = PopoverPrimitive.Root;
|
|
1668
|
+
const PopoverTrigger = PopoverPrimitive.Trigger;
|
|
1669
|
+
const PopoverContent = React.forwardRef((_a, ref) => {
|
|
1670
|
+
var { style, align = "center", sideOffset = 4 } = _a, props = __rest(_a, ["style", "align", "sideOffset"]);
|
|
1671
|
+
return (React.createElement(PopoverPrimitive.Portal, null,
|
|
1672
|
+
React.createElement(PopoverPrimitive.Content, Object.assign({ ref: ref, align: align, sideOffset: sideOffset, style: Object.assign({ width: "200px", backgroundColor: "#fff", border: "1px solid #e5e5e5", borderRadius: "0.375rem", boxShadow: "0 1px 2px 0 rgb(0 0 0 / 0.05)", color: "#333", fontSize: "14px", lineHeight: "1.5", padding: "16px", zIndex: 50, outline: "none" }, style) }, props))));
|
|
1673
|
+
});
|
|
1674
|
+
PopoverContent.displayName = PopoverPrimitive.Content.displayName;
|
|
1675
|
+
|
|
1676
|
+
const GridContext = React.createContext({});
|
|
1677
|
+
const GridCellContext = React.createContext({});
|
|
1678
|
+
const useGridCell = () => {
|
|
1679
|
+
const gridContext = React.useContext(GridContext);
|
|
1680
|
+
const gridCellContext = React.useContext(GridCellContext);
|
|
1681
|
+
if (!gridCellContext) {
|
|
1682
|
+
throw new Error("useGridCell must be used within a GridCell");
|
|
1683
|
+
}
|
|
1684
|
+
return Object.assign(Object.assign({}, gridContext), gridCellContext);
|
|
1685
|
+
};
|
|
1686
|
+
|
|
1687
|
+
const ParticipantMenuContext = React.createContext({});
|
|
1688
|
+
const useParticipantMenu = () => {
|
|
1689
|
+
const context = React.useContext(ParticipantMenuContext);
|
|
1690
|
+
const gridCellContext = useGridCell();
|
|
1691
|
+
if (!context) {
|
|
1692
|
+
throw new Error("useParticipantMenu must be used within a ParticipantMenu");
|
|
1693
|
+
}
|
|
1694
|
+
return Object.assign(Object.assign({}, context), gridCellContext);
|
|
1695
|
+
};
|
|
1696
|
+
const ParticipantMenu = (props) => {
|
|
1697
|
+
const { children } = props, rest = __rest(props, ["children"]);
|
|
1698
|
+
const [open, setOpen] = React.useState(false);
|
|
1699
|
+
return (React.createElement(ParticipantMenuContext.Provider, { value: { open, setOpen } },
|
|
1700
|
+
React.createElement(Popover, Object.assign({}, rest, { open: open, onOpenChange: setOpen }), children)));
|
|
1701
|
+
};
|
|
1702
|
+
const ParticipantMenuContent = React.forwardRef((_a, ref) => {
|
|
1703
|
+
var { children, style } = _a, props = __rest(_a, ["children", "style"]);
|
|
1704
|
+
return (React.createElement(PopoverContent, Object.assign({ ref: ref, style: Object.assign({ display: "flex", flexDirection: "column", justifyContent: "center", alignItems: "center", minWidth: "180px", maxWidth: "300px", maxHeight: "100vh", overflowY: "auto", padding: 0 }, style) }, props), children));
|
|
1705
|
+
});
|
|
1706
|
+
ParticipantMenuContent.displayName = "ParticipantMenuContent";
|
|
1707
|
+
const ParticipantMenuTrigger = React.forwardRef((_a, ref) => {
|
|
1708
|
+
var { children, style } = _a, props = __rest(_a, ["children", "style"]);
|
|
1709
|
+
return (React.createElement(PopoverTrigger, Object.assign({ ref: ref, style: Object.assign({ position: "absolute", top: "10px", right: "20px", textDecoration: "none", whiteSpace: "nowrap", border: "none", cursor: "pointer" }, style) }, props), children));
|
|
1710
|
+
});
|
|
1711
|
+
ParticipantMenuTrigger.displayName = PopoverTrigger.displayName;
|
|
1712
|
+
const ParticipantMenuItem = React.forwardRef((_a, ref) => {
|
|
1713
|
+
var { children, style, participantAction } = _a, props = __rest(_a, ["children", "style", "participantAction"]);
|
|
1714
|
+
const { participant, setOpen, maximizedParticipant, setMaximizedParticipant } = useParticipantMenu();
|
|
1715
|
+
const dispatch = useAppDispatch();
|
|
1716
|
+
const spotlightedParticipants = useAppSelector(selectSpotlightedClientViews);
|
|
1717
|
+
const isSpotlighted = spotlightedParticipants.find((p) => p.id === participant.id);
|
|
1718
|
+
const isMaximized = (maximizedParticipant === null || maximizedParticipant === void 0 ? void 0 : maximizedParticipant.id) === participant.id;
|
|
1719
|
+
let onClick;
|
|
1720
|
+
switch (participantAction) {
|
|
1721
|
+
case "maximize":
|
|
1722
|
+
onClick = () => {
|
|
1723
|
+
if (isMaximized) {
|
|
1724
|
+
setMaximizedParticipant(null);
|
|
1725
|
+
}
|
|
1726
|
+
else {
|
|
1727
|
+
setMaximizedParticipant(participant);
|
|
1728
|
+
}
|
|
1729
|
+
setOpen(false);
|
|
1730
|
+
};
|
|
1731
|
+
break;
|
|
1732
|
+
case "spotlight":
|
|
1733
|
+
onClick = () => {
|
|
1734
|
+
if (isSpotlighted) {
|
|
1735
|
+
dispatch(doRemoveSpotlight({ id: participant.id }));
|
|
1736
|
+
}
|
|
1737
|
+
else {
|
|
1738
|
+
dispatch(doSpotlightParticipant({ id: participant.id }));
|
|
1739
|
+
}
|
|
1740
|
+
setOpen(false);
|
|
1741
|
+
};
|
|
1742
|
+
break;
|
|
1743
|
+
}
|
|
1744
|
+
return (React.createElement("button", Object.assign({ ref: ref, role: "menuitem", tabIndex: -1, onClick: onClick !== null && onClick !== void 0 ? onClick : props.onClick, style: Object.assign({ alignItems: "stretch", backgroundColor: "transparent", border: "none", cursor: "pointer", display: "flex", height: "40px", lineHeight: "40px", minWidth: "140px", padding: "0 12px", textAlign: "left", textDecoration: "none", whiteSpace: "nowrap", width: "100%" }, style) }, props), children));
|
|
1745
|
+
});
|
|
1746
|
+
ParticipantMenuItem.displayName = "ParticipantMenuItem";
|
|
1747
|
+
|
|
1748
|
+
function EllipsisIcon(props) {
|
|
1749
|
+
return (React.createElement("svg", Object.assign({ width: "100%", height: "100%", viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg" }, props),
|
|
1750
|
+
React.createElement("g", null,
|
|
1751
|
+
React.createElement("circle", { cx: "6", cy: "12", r: "2" }),
|
|
1752
|
+
React.createElement("circle", { cx: "12", cy: "12", r: "2" }),
|
|
1753
|
+
React.createElement("circle", { cx: "18", cy: "12", r: "2" }))));
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
function MaximizeOnIcon(props) {
|
|
1757
|
+
return (React.createElement("svg", Object.assign({ width: "100%", height: "100%", viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg" }, props),
|
|
1758
|
+
React.createElement("path", { fillRule: "evenodd", clipRule: "evenodd", d: "m13.875 3c0 .62132.5037 1.125 1.125 1.125h3.284l-4.0795 4.0795c-.4393.43934-.4393 1.15166 0 1.591.4393.4393 1.1517.4393 1.591 0l4.0795-4.07951v3.28401c0 .62132.5037 1.125 1.125 1.125s1.125-.50368 1.125-1.125v-6c0-.15254-.0304-.29799-.0854-.43063-.0536-.12968-.1326-.25135-.2368-.35749l-.0147-.01469c-.203-.19928-.4812-.32219-.7881-.32219h-6c-.6213 0-1.125.50368-1.125 1.125zm-9.75 12c0-.6213-.50368-1.125-1.125-1.125s-1.125.5037-1.125 1.125v6c0 .3069.12291.5851.32219.7881.00486.005.00975.0099.01469.0147.10614.1042.22781.1832.35749.2368.13264.055.27809.0854.43063.0854h6c.62132 0 1.125-.5037 1.125-1.125s-.50368-1.125-1.125-1.125h-3.28401l4.07951-4.0795c.4393-.4393.4393-1.1517 0-1.591-.43934-.4393-1.15166-.4393-1.591 0l-4.0795 4.0795z" })));
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
function SpotlightIcon(props) {
|
|
1762
|
+
return (React.createElement("svg", Object.assign({ width: "100%", height: "100%", viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg" }, props),
|
|
1763
|
+
React.createElement("path", { fillRule: "evenodd", clipRule: "evenodd", d: "m15.241.146691c-.1011-.1955882-.3809-.1955879-.482 0l-1.1063 2.139639c-.0258.04991-.0665.09058-.1164.11639l-2.1396 1.10629c-.1956.10113-.1956.38085 0 .48198l2.1396 1.10629c.0499.02581.0906.06648.1164.11639l1.1063 2.13964c.1011.19559.3809.19559.482 0l1.1063-2.13964c.0258-.04991.0665-.09058.1164-.11639l2.1396-1.10629c.1956-.10113.1956-.38085 0-.48198l-2.1396-1.10629c-.0499-.02581-.0906-.06648-.1164-.11639zm-13.741 2.900259v14.67495c0 .0511.00116.1018.00345.1523-.00229.0418-.00345.0837-.00345.1258 0 .5674.21005 1.1103.59339 1.6103.24265.3472.55042.6455.90566.8773 1.61225 1.213 4.36988 2.0124 7.50095 2.0124 4.3315 0 7.9484-1.53 8.8068-3.5681.0651-.1472.1148-.3024.1455-.4658.0315-.1532.0477-.3087.0477-.4661 0-.6032-.2374-1.1788-.668-1.7045-.1377-.1975-.2995-.4017-.4877-.6127l-14.22219-13.74809c-.28845-.27884-.67396-.43471-1.07516-.43471-.85436 0-1.54695.69259-1.54695 1.54695zm19.2646 4.59631c.0988-.19101.372-.19101.4708 0l.8546 1.65303c.0253.04874.065.08846.1137.11366l1.653.85465c.1911.0988.1911.372 0 .4708l-1.653.8546c-.0487.0253-.0884.065-.1137.1137l-.8546 1.653c-.0988.1911-.372.1911-.4708 0l-.8546-1.653c-.0253-.0487-.065-.0884-.1137-.1137l-1.653-.8546c-.1911-.0988-.1911-.372 0-.4708l1.653-.85465c.0487-.0252.0884-.06492.1137-.11366zm-3.2651 10.35684c0 .0769-.1144.6851-1.5305 1.3931-1.2964.6482-3.2274 1.1069-5.4695 1.1069s-4.17308-.4587-5.46952-1.1069c-1.41607-.708-1.53047-1.3162-1.53047-1.3931 0-.077.1144-.6851 1.53047-1.3932 1.29644-.6482 3.22742-1.1068 5.46952-1.1068s4.1731.4586 5.4695 1.1068c1.4161.7081 1.5305 1.3162 1.5305 1.3932z" })));
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
function DefaultParticipantMenu({ participant }) {
|
|
1767
|
+
const spotlightedParticipants = useAppSelector(selectSpotlightedClientViews);
|
|
1768
|
+
const isSpotlighted = spotlightedParticipants.find((p) => p.id === participant.id);
|
|
1769
|
+
const { isHovered, maximizedParticipant } = useGridCell();
|
|
1770
|
+
const isMaximized = (maximizedParticipant === null || maximizedParticipant === void 0 ? void 0 : maximizedParticipant.id) === participant.id;
|
|
1771
|
+
if (!isHovered) {
|
|
1772
|
+
return null;
|
|
1654
1773
|
}
|
|
1774
|
+
return (React.createElement(ParticipantMenu, null,
|
|
1775
|
+
React.createElement(ParticipantMenuTrigger, { style: {
|
|
1776
|
+
display: "flex",
|
|
1777
|
+
justifyContent: "center",
|
|
1778
|
+
alignItems: "center",
|
|
1779
|
+
backgroundColor: "#fff",
|
|
1780
|
+
borderRadius: "6px",
|
|
1781
|
+
padding: "4px",
|
|
1782
|
+
} },
|
|
1783
|
+
React.createElement(EllipsisIcon, { height: 20, width: 20, transform: "rotate(90)" })),
|
|
1784
|
+
React.createElement(ParticipantMenuContent, null,
|
|
1785
|
+
React.createElement(ParticipantMenuItem, { participantAction: "maximize", style: {
|
|
1786
|
+
display: "flex",
|
|
1787
|
+
alignItems: "center",
|
|
1788
|
+
gap: "10px",
|
|
1789
|
+
} },
|
|
1790
|
+
React.createElement(MaximizeOnIcon, { height: 16, width: 16 }),
|
|
1791
|
+
isMaximized ? "Minimize" : "Maximize"),
|
|
1792
|
+
React.createElement(ParticipantMenuItem, { participantAction: "spotlight", style: {
|
|
1793
|
+
display: "flex",
|
|
1794
|
+
alignItems: "center",
|
|
1795
|
+
gap: "10px",
|
|
1796
|
+
} },
|
|
1797
|
+
React.createElement(SpotlightIcon, { height: 16, width: 16 }),
|
|
1798
|
+
isSpotlighted ? "Remove spotlight" : "Spotlight"))));
|
|
1655
1799
|
}
|
|
1656
|
-
|
|
1800
|
+
|
|
1801
|
+
const GridCell = React.forwardRef(({ className, participant, children }, ref) => {
|
|
1802
|
+
const [isHovered, setIsHovered] = React.useState(false);
|
|
1803
|
+
const handleMouseEnter = React.useCallback(() => {
|
|
1804
|
+
setIsHovered(true);
|
|
1805
|
+
}, []);
|
|
1806
|
+
const handleMouseLeave = React.useCallback(() => {
|
|
1807
|
+
setIsHovered(false);
|
|
1808
|
+
}, []);
|
|
1809
|
+
return (React.createElement(GridCellContext.Provider, { value: { participant, isHovered } },
|
|
1810
|
+
React.createElement("div", { ref: ref, className: className, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave }, children)));
|
|
1811
|
+
});
|
|
1812
|
+
GridCell.displayName = "GridCell";
|
|
1813
|
+
const GridVideoView = React.forwardRef((_a, ref) => {
|
|
1814
|
+
var { stream, style } = _a, rest = __rest(_a, ["stream", "style"]);
|
|
1657
1815
|
const videoEl = React.useRef(null);
|
|
1816
|
+
const { onSetClientAspectRatio, clientAspectRatios, participant } = useGridCell();
|
|
1817
|
+
if (!participant)
|
|
1818
|
+
return null;
|
|
1819
|
+
const aspectRatio = clientAspectRatios[participant.id];
|
|
1820
|
+
React.useImperativeHandle(ref, () => {
|
|
1821
|
+
return videoEl.current;
|
|
1822
|
+
});
|
|
1658
1823
|
const handleResize = React.useCallback(() => {
|
|
1659
1824
|
const ar = videoEl.current && videoEl.current.captureAspectRatio();
|
|
1660
|
-
if (ar && ar !== aspectRatio &&
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1825
|
+
if (ar && ar !== aspectRatio && participant.id) {
|
|
1826
|
+
onSetClientAspectRatio({ aspectRatio: ar, clientId: participant.id });
|
|
1827
|
+
}
|
|
1828
|
+
}, [clientAspectRatios, participant.id, onSetClientAspectRatio]);
|
|
1829
|
+
const s = stream || participant.stream;
|
|
1830
|
+
if (!s) {
|
|
1831
|
+
return null;
|
|
1832
|
+
}
|
|
1833
|
+
return (React.createElement(VideoView, Object.assign({ ref: videoEl, style: Object.assign({ borderRadius: "8px" }, style) }, rest, { stream: s, onVideoResize: handleResize })));
|
|
1834
|
+
});
|
|
1835
|
+
GridVideoView.displayName = "GridVideoView";
|
|
1836
|
+
function renderCellView({ cellView, enableParticipantMenu, render }) {
|
|
1837
|
+
const participant = cellView.client;
|
|
1838
|
+
if (!participant) {
|
|
1839
|
+
return undefined;
|
|
1840
|
+
}
|
|
1841
|
+
switch (cellView.type) {
|
|
1842
|
+
case "video":
|
|
1843
|
+
return (React.createElement(GridCell, { participant: participant }, participant.isVideoEnabled ? (React.createElement(React.Fragment, null, render ? (render({ participant })) : (React.createElement(React.Fragment, null,
|
|
1844
|
+
React.createElement(GridVideoView, null),
|
|
1845
|
+
enableParticipantMenu ? (React.createElement(DefaultParticipantMenu, { participant: participant })) : null)))) : (React.createElement(VideoMutedIndicator, { isSmallCell: false, displayName: (participant === null || participant === void 0 ? void 0 : participant.displayName) || "Guest", withRoundedCorners: true }))));
|
|
1846
|
+
}
|
|
1665
1847
|
}
|
|
1666
|
-
function Grid({ renderParticipant, stageParticipantLimit, videoGridGap }) {
|
|
1848
|
+
function Grid({ renderParticipant, stageParticipantLimit, gridGap, videoGridGap, enableSubgrid, enableParticipantMenu, }) {
|
|
1667
1849
|
const gridRef = React.useRef(null);
|
|
1668
|
-
const { cellViewsVideoGrid, cellViewsInPresentationGrid, cellViewsInSubgrid, videoStage, setContainerBounds, setClientAspectRatios, } = useGrid({ activeVideosSubgridTrigger: 12, stageParticipantLimit, videoGridGap });
|
|
1669
|
-
const
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
})), [cellViewsVideoGrid]);
|
|
1850
|
+
const { cellViewsVideoGrid, cellViewsInPresentationGrid, cellViewsInSubgrid, clientAspectRatios, videoStage, setContainerBounds, setClientAspectRatios, maximizedParticipant, setMaximizedParticipant, } = useGrid({ activeVideosSubgridTrigger: 12, stageParticipantLimit, gridGap, videoGridGap, enableSubgrid });
|
|
1851
|
+
const handleSetClientAspectRatio = React.useCallback(({ aspectRatio, clientId }) => {
|
|
1852
|
+
setClientAspectRatios((prev) => (Object.assign(Object.assign({}, prev), { [clientId]: aspectRatio })));
|
|
1853
|
+
}, [setClientAspectRatios]);
|
|
1854
|
+
const presentationGridContent = React.useMemo(() => cellViewsInPresentationGrid.map((cellView) => renderCellView(Object.assign({ cellView,
|
|
1855
|
+
enableParticipantMenu }, (renderParticipant ? { render: ({ participant }) => renderParticipant({ participant }) } : {})))), [cellViewsInPresentationGrid]);
|
|
1856
|
+
const gridContent = React.useMemo(() => cellViewsVideoGrid.map((cellView) => renderCellView(Object.assign({ cellView,
|
|
1857
|
+
enableParticipantMenu }, (renderParticipant ? { render: ({ participant }) => renderParticipant({ participant }) } : {})))), [cellViewsVideoGrid]);
|
|
1677
1858
|
const subgridContent = React.useMemo(() => cellViewsInSubgrid.map((cellView) => renderCellView({
|
|
1678
1859
|
cellView,
|
|
1679
|
-
|
|
1860
|
+
enableParticipantMenu,
|
|
1680
1861
|
})), [cellViewsInSubgrid]);
|
|
1681
1862
|
React.useEffect(() => {
|
|
1682
1863
|
if (!gridRef.current) {
|
|
@@ -1694,12 +1875,21 @@ function Grid({ renderParticipant, stageParticipantLimit, videoGridGap }) {
|
|
|
1694
1875
|
resizeObserver.disconnect();
|
|
1695
1876
|
};
|
|
1696
1877
|
}, []);
|
|
1697
|
-
return (React.createElement(
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1878
|
+
return (React.createElement(GridContext.Provider, { value: {
|
|
1879
|
+
onSetClientAspectRatio: handleSetClientAspectRatio,
|
|
1880
|
+
cellViewsVideoGrid,
|
|
1881
|
+
cellViewsInPresentationGrid,
|
|
1882
|
+
cellViewsInSubgrid,
|
|
1883
|
+
clientAspectRatios,
|
|
1884
|
+
maximizedParticipant,
|
|
1885
|
+
setMaximizedParticipant,
|
|
1701
1886
|
} },
|
|
1702
|
-
React.createElement(
|
|
1887
|
+
React.createElement("div", { ref: gridRef, style: {
|
|
1888
|
+
width: "100%",
|
|
1889
|
+
height: "100%",
|
|
1890
|
+
position: "relative",
|
|
1891
|
+
} },
|
|
1892
|
+
React.createElement(VideoStageLayout, { layoutVideoStage: videoStage, presentationGridContent: presentationGridContent, gridContent: gridContent, subgridContent: subgridContent }))));
|
|
1703
1893
|
}
|
|
1704
1894
|
|
|
1705
|
-
export { Grid as VideoGrid, VideoView, Provider as WherebyProvider, useLocalMedia, useRoomConnection };
|
|
1895
|
+
export { GridCell, GridVideoView, ParticipantMenu, ParticipantMenuContent, ParticipantMenuItem, ParticipantMenuTrigger, Grid as VideoGrid, VideoView, Provider as WherebyProvider, useLocalMedia, useRoomConnection };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@whereby.com/browser-sdk",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.0",
|
|
4
4
|
"description": "Modules for integration Whereby video in web apps",
|
|
5
5
|
"author": "Whereby AS",
|
|
6
6
|
"license": "MIT",
|
|
@@ -72,8 +72,10 @@
|
|
|
72
72
|
"yalc": "^1.0.0-pre.53"
|
|
73
73
|
},
|
|
74
74
|
"dependencies": {
|
|
75
|
+
"@radix-ui/react-popover": "^1.0.7",
|
|
75
76
|
"@reduxjs/toolkit": "^2.2.3",
|
|
76
|
-
"@whereby.com/core": "0.
|
|
77
|
+
"@whereby.com/core": "0.17.0",
|
|
78
|
+
"clsx": "^2.1.1",
|
|
77
79
|
"heresy": "^1.0.4",
|
|
78
80
|
"react-redux": "^9.1.1",
|
|
79
81
|
"runes": "^0.4.3"
|