Skip to content

Commit 9001f3a

Browse files
clauderppt
authored andcommitted
feat: matrix chat integration
Add Matrix chat panel integration to BigBlueButton. Matrix room ID and user credentials are passed via userdata parameters (matrixroomid, mail, regcode) and displayed in an embedded iframe. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent f3d33d9 commit 9001f3a

11 files changed

Lines changed: 430 additions & 3 deletions

File tree

bigbluebutton-html5/imports/ui/components/layout/enums.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ export const PANELS = {
154154
POLL: 'poll',
155155
CAPTIONS: 'captions',
156156
BREAKOUT: 'breakoutroom',
157+
MATRIX: 'matrix',
157158
SHARED_NOTES: 'shared-notes',
158159
TIMER: 'timer',
159160
WAITING_USERS: 'waiting-users',

bigbluebutton-html5/imports/ui/components/layout/observer.tsx

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import getFromUserSettings from '/imports/ui/services/users-settings';
1515
import useMeeting from '/imports/ui/core/hooks/useMeeting';
1616
import MediaService from '/imports/ui/components/media/service';
1717
import { useVideoStreams, useVideoStreamsCount } from '/imports/ui/components/video-provider/hooks';
18-
import { useIsChatEnabled, useIsPresentationEnabled, useIsScreenSharingEnabled } from '/imports/ui/services/features';
18+
import { useIsChatEnabled, useIsMatrixEnabled, useIsPresentationEnabled, useIsScreenSharingEnabled } from '/imports/ui/services/features';
1919
import useUserChangedLocalSettings from '/imports/ui/services/settings/hooks/useUserChangedLocalSettings';
2020
import Session from '/imports/ui/services/storage/in-memory';
2121
import deviceInfo from '/imports/utils/deviceInfo';
@@ -49,6 +49,7 @@ const LayoutObserver: React.FC = () => {
4949
const isScreenSharingEnabled = useIsScreenSharingEnabled();
5050
const isPresentationEnabled = useIsPresentationEnabled();
5151
const isChatEnabled = useIsChatEnabled();
52+
const isMatrixEnabled = useIsMatrixEnabled();
5253

5354
const setLocalSettings = useUserChangedLocalSettings();
5455

@@ -219,7 +220,30 @@ const LayoutObserver: React.FC = () => {
219220

220221
useEffect(() => {
221222
if (layoutIsReady) {
222-
if (isChatEnabled && getFromUserSettings('bbb_show_public_chat_on_login', !window.meetingClientSettings.public.chat.startClosed) && !deviceInfo.isPhone) {
223+
const matrixRoomId = getFromUserSettings('matrixroomid', '');
224+
const showMatrixOnLogin = isMatrixEnabled
225+
&& matrixRoomId
226+
&& getFromUserSettings('bbb_show_matrix_on_login', !window.meetingClientSettings.public.matrix?.startClosed)
227+
&& !deviceInfo.isPhone;
228+
const showChatOnLogin = isChatEnabled
229+
&& getFromUserSettings('bbb_show_public_chat_on_login', !window.meetingClientSettings.public.chat.startClosed)
230+
&& !deviceInfo.isPhone;
231+
232+
if (showMatrixOnLogin && !showChatOnLogin) {
233+
// Open Matrix panel if Matrix is configured and chat is not set to open
234+
layoutContextDispatch({
235+
type: ACTIONS.SET_SIDEBAR_NAVIGATION_IS_OPEN,
236+
value: true,
237+
});
238+
layoutContextDispatch({
239+
type: ACTIONS.SET_SIDEBAR_CONTENT_IS_OPEN,
240+
value: true,
241+
});
242+
layoutContextDispatch({
243+
type: ACTIONS.SET_SIDEBAR_CONTENT_PANEL,
244+
value: PANELS.MATRIX,
245+
});
246+
} else if (showChatOnLogin) {
223247
const PUBLIC_CHAT_ID = window.meetingClientSettings.public.chat.public_group_id;
224248
layoutContextDispatch({
225249
type: ACTIONS.SET_SIDEBAR_CONTENT_IS_OPEN,
@@ -240,7 +264,7 @@ const LayoutObserver: React.FC = () => {
240264
});
241265
}
242266
}
243-
}, [isChatEnabled, layoutIsReady]);
267+
}, [isChatEnabled, isMatrixEnabled, layoutIsReady]);
244268

245269
useEffect(() => {
246270
if (layoutIsReady && sidebarContentPanel === PANELS.NONE) {
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import React from 'react';
2+
import { defineMessages, useIntl } from 'react-intl';
3+
import Styled from './styles';
4+
import Header from '/imports/ui/components/common/control-header/component';
5+
import browserInfo from '/imports/utils/browserInfo';
6+
import { PANELS, ACTIONS } from '../layout/enums';
7+
import { layoutSelectInput, layoutDispatch } from '../layout/context';
8+
import { Input } from '../layout/layoutTypes';
9+
import getFromUserSettings from '/imports/ui/services/users-settings';
10+
import useMeeting from '/imports/ui/core/hooks/useMeeting';
11+
12+
const intlMessages = defineMessages({
13+
hide: {
14+
id: 'app.matrix.hide',
15+
description: 'Label for hiding matrix button',
16+
},
17+
title: {
18+
id: 'app.matrix.title',
19+
description: 'Title for Matrix',
20+
},
21+
tipLabel: {
22+
id: 'app.matrix.tipLabel',
23+
description: 'Label for tip on how to escape iframe',
24+
},
25+
});
26+
27+
interface MatrixProps {
28+
isResizing: boolean;
29+
isVisible: boolean;
30+
}
31+
32+
const Matrix: React.FC<MatrixProps> = ({
33+
isResizing,
34+
isVisible,
35+
}) => {
36+
const intl = useIntl();
37+
const { isChrome } = browserInfo;
38+
const layoutContextDispatch = layoutDispatch();
39+
40+
const { data: meetingData } = useMeeting((m) => ({
41+
name: m.name,
42+
}));
43+
44+
// Get Matrix settings from user settings (passed via API as userdata-* parameters)
45+
const matrixRoomID = getFromUserSettings('matrixroomid', '');
46+
const urlemail = getFromUserSettings('mail', '');
47+
const urlregcode = getFromUserSettings('regcode', '');
48+
49+
const matrixtitle = `Matrix integration for ${meetingData?.name || 'BigBlueButton'}`;
50+
const matrixurl = `/riot-embedded/index.html?urlroomid=${encodeURIComponent(matrixRoomID)}&urlemail=${encodeURIComponent(urlemail)}&urlregcode=${encodeURIComponent(urlregcode)}`;
51+
52+
// Don't render the iframe content if Matrix is not configured
53+
if (!matrixRoomID) {
54+
return null;
55+
}
56+
57+
return (
58+
<Styled.Matrix
59+
data-test="matrix"
60+
isChrome={isChrome}
61+
style={isVisible ? {} : { display: 'none' }}
62+
>
63+
<Header
64+
leftButtonProps={{
65+
onClick: () => {
66+
layoutContextDispatch({
67+
type: ACTIONS.SET_SIDEBAR_CONTENT_IS_OPEN,
68+
value: false,
69+
});
70+
layoutContextDispatch({
71+
type: ACTIONS.SET_SIDEBAR_CONTENT_PANEL,
72+
value: PANELS.NONE,
73+
});
74+
},
75+
'data-test': 'hideMatrixLabel',
76+
'aria-label': intl.formatMessage(intlMessages.hide),
77+
label: intl.formatMessage(intlMessages.title),
78+
}}
79+
customRightButton={null}
80+
rightButtonProps={null}
81+
/>
82+
<Styled.IFrame
83+
title={matrixtitle}
84+
src={matrixurl}
85+
aria-describedby="MatrixEscapeHint"
86+
style={{
87+
pointerEvents: isResizing ? 'none' : 'inherit',
88+
}}
89+
/>
90+
<Styled.Hint
91+
id="MatrixEscapeHint"
92+
aria-hidden
93+
>
94+
{intl.formatMessage(intlMessages.tipLabel)}
95+
</Styled.Hint>
96+
</Styled.Matrix>
97+
);
98+
};
99+
100+
const MatrixContainer: React.FC<{ isVisible?: boolean }> = ({ isVisible = true }) => {
101+
const sidebarContent = layoutSelectInput((i: Input) => i.sidebarContent);
102+
const cameraDock = layoutSelectInput((i: Input) => i.cameraDock);
103+
const { isResizing } = cameraDock;
104+
105+
// Always render but hide when not visible (for persistent iframe)
106+
const shouldShow = isVisible || sidebarContent.sidebarContentPanel === PANELS.MATRIX;
107+
108+
return (
109+
<Matrix
110+
isResizing={isResizing}
111+
isVisible={shouldShow}
112+
/>
113+
);
114+
};
115+
116+
export default MatrixContainer;
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import styled from 'styled-components';
2+
import {
3+
mdPaddingX,
4+
mdPaddingY,
5+
smPaddingX,
6+
lgPaddingY,
7+
} from '/imports/ui/stylesheets/styled-components/general';
8+
import {
9+
colorWhite,
10+
colorGray,
11+
colorGrayLightest,
12+
} from '/imports/ui/stylesheets/styled-components/palette';
13+
import { smallOnly } from '/imports/ui/stylesheets/styled-components/breakpoints';
14+
import { fontSizeSmall } from '/imports/ui/stylesheets/styled-components/typography';
15+
16+
interface MatrixProps {
17+
isChrome: boolean;
18+
}
19+
20+
const Matrix = styled.div<MatrixProps>`
21+
background-color: ${colorWhite};
22+
padding: ${mdPaddingX} ${mdPaddingY} ${mdPaddingX} ${mdPaddingX};
23+
display: flex;
24+
flex-grow: 1;
25+
flex-direction: column;
26+
overflow: hidden;
27+
height: 100%;
28+
29+
${({ isChrome }) => isChrome && `
30+
transform: translateZ(0);
31+
`}
32+
33+
@media ${smallOnly} {
34+
transform: none !important;
35+
}
36+
`;
37+
38+
const Hint = styled.span`
39+
visibility: hidden;
40+
position: absolute;
41+
@media (pointer: none) {
42+
visibility: visible;
43+
position: relative;
44+
color: ${colorGray};
45+
font-size: ${fontSizeSmall};
46+
font-style: italic;
47+
padding: ${smPaddingX} 0 0 ${smPaddingX};
48+
text-align: left;
49+
[dir="rtl"] & {
50+
padding-right: ${lgPaddingY} ${lgPaddingY} 0 0;
51+
text-align: right;
52+
}
53+
}
54+
`;
55+
56+
const IFrame = styled.iframe`
57+
width: 100%;
58+
height: 100%;
59+
overflow: hidden;
60+
border-style: none;
61+
border-bottom: 1px solid ${colorGrayLightest};
62+
padding-bottom: 5px;
63+
`;
64+
65+
export default {
66+
Matrix,
67+
Hint,
68+
IFrame,
69+
};

bigbluebutton-html5/imports/ui/components/sidebar-content/component.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
33
import { Resizable } from 're-resizable';
44
import { ACTIONS, PANELS } from '../layout/enums';
55
import ChatContainer from '/imports/ui/components/chat/chat-graphql/component';
6+
import MatrixContainer from '/imports/ui/components/matrix/component';
67
import NotesContainer from '/imports/ui/components/notes/component';
78
import PollContainer from '/imports/ui/components/poll/container';
89
import BreakoutRoomContainer from '../breakout-room/breakout-room/component';
@@ -148,6 +149,7 @@ const SidebarContent = (props) => {
148149
isToSharedNotesBeShow={sidebarContentPanel === PANELS.SHARED_NOTES}
149150
/>
150151
)}
152+
<MatrixContainer isVisible={sidebarContentPanel === PANELS.MATRIX} />
151153
{sidebarContentPanel === PANELS.BREAKOUT && <BreakoutRoomContainer />}
152154
{sidebarContentPanel === PANELS.TIMER && <TimerContainer isModerator={amIModerator} />}
153155
{sidebarContentPanel === PANELS.WAITING_USERS && <GuestUsersManagementPanel />}

bigbluebutton-html5/imports/ui/components/user-list/user-list-content/component.jsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import Styled from './styles';
44
import UserListParticipants from './user-participants/user-list-participants/component';
55
import ChatList from './user-messages/chat-list/component';
66
import UserNotesContainer from '../user-list-graphql/user-list-content/user-notes/component';
7+
import UserMatrixContainer from '../user-list-graphql/user-list-content/user-matrix/component';
78
import TimerContainer from './timer/container';
89
import GuestPanelOpenerContainer from '../user-list-graphql/user-participants-title/guest-panel-opener/component';
910
import UserPollsContainer from './user-polls/container';
@@ -50,6 +51,7 @@ class UserContent extends PureComponent {
5051
<Styled.ScrollableList role="tabpanel" tabIndex={0}>
5152
<Styled.List>
5253
<ChatList />
54+
<UserMatrixContainer />
5355
<UserNotesContainer />
5456
{isTimerActive
5557
&& <TimerContainer isModerator={currentUser?.role === ROLE_MODERATOR} />}
@@ -65,6 +67,7 @@ class UserContent extends PureComponent {
6567
) : (
6668
<>
6769
<ChatList />
70+
<UserMatrixContainer />
6871
<UserNotesContainer />
6972
{isTimerActive && <TimerContainer isModerator={currentUser?.role === ROLE_MODERATOR} />}
7073
{currentUser?.role === ROLE_MODERATOR ? <GuestPanelOpenerContainer /> : null}

0 commit comments

Comments
 (0)