Skip to content

Commit 412eb7e

Browse files
committed
feat(chat): Add disableChat configuration option
Introduces a comprehensive disableChat config option that disables the entire chat feature including button visibility, notifications, sounds, private messages, and keyboard shortcuts. When disabled, the chat tab is hidden from the chat panel while allowing other tabs (polls, files, CC) to remain accessible.
1 parent 4823991 commit 412eb7e

File tree

18 files changed

+238
-60
lines changed

18 files changed

+238
-60
lines changed

config.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,9 @@ var config = {
139139
// Disables polls feature.
140140
// disablePolls: false,
141141

142+
// Disables chat feature entirely including notifications, sounds, and private messages.
143+
// disableChat: false,
144+
142145
// Disables demote button from self-view
143146
// disableSelfDemote: false,
144147

lang/main.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,16 @@
126126
"messagebox": "Type a message",
127127
"newMessages": "New messages",
128128
"nickname": {
129+
"featureChat": "chat",
130+
"featureClosedCaptions": "closed captions",
131+
"featureFileSharing": "file sharing",
132+
"featurePolls": "polls",
129133
"popover": "Choose a nickname",
130134
"title": "Enter a nickname to use chat",
135+
"titleWith1Features": "Enter a nickname to use {feature1}",
136+
"titleWith2Features": "Enter a nickname to use {feature1} and {feature2}",
137+
"titleWith3Features": "Enter a nickname to use {feature1}, {feature2} and {feature3}",
138+
"titleWith4Features": "Enter a nickname to use {feature1}, {feature2}, {feature3} and {feature4}",
131139
"titleWithCC": "Enter a nickname to use chat and closed captions",
132140
"titleWithPolls": "Enter a nickname to use chat and polls",
133141
"titleWithPollsAndCC": "Enter a nickname to use chat, polls and closed captions",

react/features/base/config/configType.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,7 @@ export interface IConfig {
285285
disableAudioLevels?: boolean;
286286
disableBeforeUnloadHandlers?: boolean;
287287
disableCameraTintForeground?: boolean;
288+
disableChat?: boolean;
288289
disableChatSmileys?: boolean;
289290
disableDeepLinking?: boolean;
290291
disableFilmstripAutohiding?: boolean;

react/features/base/config/configWhitelist.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ export default [
9494
'disableAudioLevels',
9595
'disableBeforeUnloadHandlers',
9696
'disableCameraTintForeground',
97+
'disableChat',
9798
'disableChatSmileys',
9899
'disableDeepLinking',
99100
'disabledNotifications',
Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,33 @@
1+
import { IStore } from '../app/types';
12
import { IParticipant } from '../base/participants/types';
23
import { navigate } from '../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
34
import { screen } from '../mobile/navigation/routes';
45

56
import { OPEN_CHAT } from './actionTypes';
7+
import { setFocusedTab } from './actions.any';
8+
import { ChatTabs } from './constants';
69

710
export * from './actions.any';
811

912
/**
10-
* Displays the chat panel.
13+
* Displays the chat panel with the CHAT tab active.
1114
*
1215
* @param {Object} participant - The recipient for the private chat.
1316
* @param {boolean} disablePolls - Checks if polls are disabled.
1417
*
15-
* @returns {{
16-
* participant: participant,
17-
* type: OPEN_CHAT
18-
* }}
18+
* @returns {Function}
1919
*/
2020
export function openChat(participant?: IParticipant | undefined | Object, disablePolls?: boolean) {
21-
if (disablePolls) {
22-
navigate(screen.conference.chat);
23-
}
24-
navigate(screen.conference.chatandpolls.main);
21+
return (dispatch: IStore['dispatch']) => {
22+
if (disablePolls) {
23+
navigate(screen.conference.chat);
24+
}
25+
navigate(screen.conference.chatandpolls.main);
2526

26-
return {
27-
participant,
28-
type: OPEN_CHAT
27+
dispatch(setFocusedTab(ChatTabs.CHAT));
28+
dispatch({
29+
participant,
30+
type: OPEN_CHAT
31+
});
2932
};
3033
}

react/features/chat/actions.web.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@ import {
88
SET_CHAT_WIDTH,
99
SET_USER_CHAT_WIDTH
1010
} from './actionTypes';
11-
import { closeChat } from './actions.any';
11+
import { closeChat, setFocusedTab } from './actions.any';
12+
import { ChatTabs } from './constants';
1213

1314
export * from './actions.any';
1415

1516
/**
16-
* Displays the chat panel.
17+
* Displays the chat panel with the CHAT tab active.
1718
*
1819
* @param {Object} participant - The recipient for the private chat.
1920
* @param {Object} _disablePolls - Used on native.
@@ -24,6 +25,7 @@ export * from './actions.any';
2425
*/
2526
export function openChat(participant?: Object, _disablePolls?: boolean) {
2627
return function(dispatch: IStore['dispatch']) {
28+
dispatch(setFocusedTab(ChatTabs.CHAT));
2729
dispatch({
2830
participant,
2931
type: OPEN_CHAT

react/features/chat/components/native/ChatButton.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { arePollsDisabled } from '../../../conference/functions.any';
1010
import { navigate } from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
1111
import { screen } from '../../../mobile/navigation/routes';
1212
import { getUnreadPollCount } from '../../../polls/functions';
13-
import { getUnreadCount, getUnreadFilesCount } from '../../functions';
13+
import { getUnreadCount, getUnreadFilesCount, isChatDisabled } from '../../functions';
1414

1515
interface IProps extends AbstractButtonProps {
1616

@@ -65,7 +65,7 @@ class ChatButton extends AbstractButton<IProps> {
6565
* @returns {IProps}
6666
*/
6767
function _mapStateToProps(state: IReduxState, ownProps: any) {
68-
const enabled = getFeatureFlag(state, CHAT_ENABLED, true);
68+
const enabled = getFeatureFlag(state, CHAT_ENABLED, true) && !isChatDisabled(state);
6969
const { visible = enabled } = ownProps;
7070

7171
return {

react/features/chat/components/web/Chat.tsx

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import {
2424
toggleChat
2525
} from '../../actions.web';
2626
import { CHAT_SIZE, ChatTabs, OPTION_GROUPCHAT, SMALL_WIDTH_THRESHOLD } from '../../constants';
27-
import { getChatMaxSize } from '../../functions';
27+
import { getChatMaxSize, getFocusedTab, isChatDisabled } from '../../functions';
2828
import { IChatProps as AbstractProps } from '../../types';
2929

3030
import ChatHeader from './ChatHeader';
@@ -41,13 +41,18 @@ interface IProps extends AbstractProps {
4141
/**
4242
* The currently focused tab.
4343
*/
44-
_focusedTab: ChatTabs;
44+
_focusedTab?: ChatTabs;
4545

4646
/**
4747
* True if the CC tab is enabled and false otherwise.
4848
*/
4949
_isCCTabEnabled: boolean;
5050

51+
/**
52+
* True if chat is disabled.
53+
*/
54+
_isChatDisabled: boolean;
55+
5156
/**
5257
* True if file sharing tab is enabled.
5358
*/
@@ -217,6 +222,7 @@ const Chat = ({
217222
_isOpen,
218223
_isPollsEnabled,
219224
_isCCTabEnabled,
225+
_isChatDisabled,
220226
_isFileSharingTabEnabled,
221227
_focusedTab,
222228
_isResizing,
@@ -229,6 +235,11 @@ const Chat = ({
229235
dispatch,
230236
t
231237
}: IProps) => {
238+
// If no tabs are available, don't render the chat panel at all.
239+
if (_isChatDisabled && !_isPollsEnabled && !_isCCTabEnabled && !_isFileSharingTabEnabled) {
240+
return null;
241+
}
242+
232243
const { classes, cx } = useStyles({ _isResizing, width: _width });
233244
const [ isMouseDown, setIsMouseDown ] = useState(false);
234245
const [ mousePosition, setMousePosition ] = useState<number | null>(null);
@@ -416,7 +427,7 @@ const Chat = ({
416427
return (
417428
<>
418429
{renderTabs()}
419-
<div
430+
{!_isChatDisabled && (<div
420431
aria-labelledby = { ChatTabs.CHAT }
421432
className = { cx(
422433
classes.chatPanel,
@@ -442,7 +453,7 @@ const Chat = ({
442453
)}
443454
<ChatInput
444455
onSend = { onSendMessage } />
445-
</div>
456+
</div>) }
446457
{ _isPollsEnabled && (
447458
<>
448459
<div
@@ -484,17 +495,27 @@ const Chat = ({
484495
* @returns {ReactElement}
485496
*/
486497
function renderTabs() {
487-
let tabs = [
488-
{
498+
// The only way focused tab will be undefined is when no tab is enabled. Therefore this function won't be
499+
// executed because Chat component won't render anything. This should never happen but adding the check
500+
// here to make TS happy (when passing the _focusedTab in the selected prop for Tabs).
501+
if (!_focusedTab) {
502+
return null;
503+
}
504+
505+
let tabs = [];
506+
507+
// Only add chat tab if chat is not disabled.
508+
if (!_isChatDisabled) {
509+
tabs.push({
489510
accessibilityLabel: t('chat.tabs.chat'),
490511
countBadge:
491512
_focusedTab !== ChatTabs.CHAT && _unreadMessagesCount > 0 ? _unreadMessagesCount : undefined,
492513
id: ChatTabs.CHAT,
493514
controlsId: `${ChatTabs.CHAT}-panel`,
494515
icon: IconMessage,
495516
title: t('chat.tabs.chat')
496-
}
497-
];
517+
});
518+
}
498519

499520
if (_isPollsEnabled) {
500521
tabs.push({
@@ -564,6 +585,8 @@ const Chat = ({
564585
{_showNamePrompt
565586
? <DisplayNameForm
566587
isCCTabEnabled = { _isCCTabEnabled }
588+
isChatDisabled = { _isChatDisabled }
589+
isFileSharingEnabled = { _isFileSharingTabEnabled }
567590
isPollsEnabled = { _isPollsEnabled } />
568591
: renderChat()}
569592
<div
@@ -602,7 +625,7 @@ const Chat = ({
602625
* }}
603626
*/
604627
function _mapStateToProps(state: IReduxState, _ownProps: any) {
605-
const { isOpen, focusedTab, messages, unreadMessagesCount, unreadFilesCount, width, isResizing } = state['features/chat'];
628+
const { isOpen, messages, unreadMessagesCount, unreadFilesCount, width, isResizing } = state['features/chat'];
606629
const { unreadPollsCount } = state['features/polls'];
607630
const _localParticipant = getLocalParticipant(state);
608631

@@ -611,8 +634,9 @@ function _mapStateToProps(state: IReduxState, _ownProps: any) {
611634
_isOpen: isOpen,
612635
_isPollsEnabled: !arePollsDisabled(state),
613636
_isCCTabEnabled: isCCTabEnabled(state),
637+
_isChatDisabled: isChatDisabled(state),
614638
_isFileSharingTabEnabled: isFileSharingEnabled(state),
615-
_focusedTab: focusedTab,
639+
_focusedTab: getFocusedTab(state),
616640
_messages: messages,
617641
_unreadMessagesCount: unreadMessagesCount,
618642
_unreadPollsCount: unreadPollsCount,

react/features/chat/components/web/ChatButton.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { IconMessage } from '../../../base/icons/svg';
99
import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton';
1010
import { closeOverflowMenuIfOpen } from '../../../toolbox/actions.web';
1111
import { toggleChat } from '../../actions.web';
12+
import { isChatDisabled } from '../../functions';
1213

1314
import ChatCounter from './ChatCounter';
1415

@@ -91,7 +92,8 @@ class ChatButton extends AbstractButton<IProps> {
9192
*/
9293
const mapStateToProps = (state: IReduxState) => {
9394
return {
94-
_chatOpen: state['features/chat'].isOpen
95+
_chatOpen: state['features/chat'].isOpen,
96+
visible: !isChatDisabled(state)
9597
};
9698
};
9799

react/features/chat/components/web/ChatHeader.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ import React, { useCallback } from 'react';
22
import { useTranslation } from 'react-i18next';
33
import { useDispatch, useSelector } from 'react-redux';
44

5-
import { IReduxState } from '../../../app/types';
65
import Icon from '../../../base/icons/components/Icon';
76
import { IconCloseLarge } from '../../../base/icons/svg';
87
import { isFileSharingEnabled } from '../../../file-sharing/functions.any';
98
import { toggleChat } from '../../actions.web';
109
import { ChatTabs } from '../../constants';
10+
import { getFocusedTab, isChatDisabled } from '../../functions';
1111

1212
interface IProps {
1313

@@ -40,7 +40,8 @@ interface IProps {
4040
function ChatHeader({ className, isCCTabEnabled, isPollsEnabled }: IProps) {
4141
const dispatch = useDispatch();
4242
const { t } = useTranslation();
43-
const { focusedTab } = useSelector((state: IReduxState) => state['features/chat']);
43+
const _isChatDisabled = useSelector(isChatDisabled);
44+
const focusedTab = useSelector(getFocusedTab);
4445
const fileSharingTabEnabled = useSelector(isFileSharingEnabled);
4546

4647
const onCancel = useCallback(() => {
@@ -56,14 +57,19 @@ function ChatHeader({ className, isCCTabEnabled, isPollsEnabled }: IProps) {
5657

5758
let title = 'chat.title';
5859

59-
if (focusedTab === ChatTabs.CHAT) {
60+
if (!_isChatDisabled && focusedTab === ChatTabs.CHAT) {
6061
title = 'chat.tabs.chat';
6162
} else if (isPollsEnabled && focusedTab === ChatTabs.POLLS) {
6263
title = 'chat.tabs.polls';
6364
} else if (isCCTabEnabled && focusedTab === ChatTabs.CLOSED_CAPTIONS) {
6465
title = 'chat.tabs.closedCaptions';
6566
} else if (fileSharingTabEnabled && focusedTab === ChatTabs.FILE_SHARING) {
6667
title = 'chat.tabs.fileSharing';
68+
} else {
69+
// If the focused tab is not enabled, don't render the header.
70+
// This should not happen in normal circumstances since Chat.tsx already checks
71+
// if any tabs are available before rendering.
72+
return null;
6773
}
6874

6975
return (

0 commit comments

Comments
 (0)