init
Some checks failed
Close stale issues and PRs / stale (push) Has been cancelled

This commit is contained in:
2025-09-02 14:49:16 +08:00
commit 38ba663466
2885 changed files with 391107 additions and 0 deletions

View File

@@ -0,0 +1,115 @@
import React, { useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import Input from '../../../../base/ui/components/web/Input';
import { LOCKED_LOCALLY } from '../../../../room-lock/constants';
/**
* The type of the React {@code Component} props of {@link PasswordForm}.
*/
interface IProps {
/**
* Whether or not to show the password editing field.
*/
editEnabled: boolean;
/**
* The value for how the conference is locked (or undefined if not locked)
* as defined by room-lock constants.
*/
locked?: string;
/**
* Callback to invoke when the local participant is submitting a password
* set request.
*/
onSubmit: Function;
/**
* The current known password for the JitsiConference.
*/
password?: string;
/**
* The number of digits to be used in the password.
*/
passwordNumberOfDigits?: number;
/**
* Whether or not the password should be visible.
*/
visible: boolean;
}
/**
* React {@code Component} for displaying and editing the conference password.
*
* @returns {ReactElement}
*/
export default function PasswordForm({
editEnabled,
locked,
onSubmit,
password,
passwordNumberOfDigits,
visible
}: IProps) {
const { t } = useTranslation();
const [ enteredPassword, setEnteredPassword ] = useState('');
const onKeyPress = useCallback(event => {
if (event.key === 'Enter') {
event.preventDefault();
event.stopPropagation();
onSubmit(enteredPassword);
}
}, [ onSubmit, enteredPassword ]);
if (!editEnabled && enteredPassword && enteredPassword !== '') {
setEnteredPassword('');
}
const placeHolderText
= passwordNumberOfDigits ? t('passwordDigitsOnly', { number: passwordNumberOfDigits }) : t('dialog.password');
return (
<div className = 'info-password'>
{ locked && <>
<span className = 'info-label'>
{t('info.password')}
</span>
<span className = 'spacer'>&nbsp;</span>
<span className = 'info-password-field info-value'>
{locked === LOCKED_LOCALLY ? (
<div className = 'info-password-local'>
{ visible ? password : '******' }
</div>
) : (
<div className = 'info-password-remote'>
{ t('passwordSetRemotely') }
</div>
) }
</span>
</>
}
{
editEnabled && <div
className = 'info-password-form'>
<Input
accessibilityLabel = { t('info.addPassword') }
autoFocus = { true }
id = 'info-password-input'
maxLength = { passwordNumberOfDigits }
mode = { passwordNumberOfDigits ? 'numeric' : undefined }
onChange = { setEnteredPassword }
onKeyPress = { onKeyPress }
placeholder = { placeHolderText }
type = 'password'
value = { enteredPassword } />
</div>
}
</div>
);
}

View File

@@ -0,0 +1,189 @@
/* eslint-disable react/jsx-no-bind */
import React, { useCallback, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
import { IReduxState } from '../../../../app/types';
import { setPassword } from '../../../../base/conference/actions';
import { isLocalParticipantModerator } from '../../../../base/participants/functions';
import { copyText } from '../../../../base/util/copyText.web';
import { LOCKED_LOCALLY } from '../../../../room-lock/constants';
import { NOTIFY_CLICK_MODE } from '../../../../toolbox/types';
import PasswordForm from './PasswordForm';
const DIGITS_ONLY = /^\d+$/;
const KEY = 'add-passcode';
/**
* Component that handles the password manipulation from the invite dialog.
*
* @returns {React$Element<any>}
*/
function PasswordSection() {
const { t } = useTranslation();
const dispatch = useDispatch();
const canEditPassword = useSelector(isLocalParticipantModerator);
const passwordNumberOfDigits = useSelector(
(state: IReduxState) => state['features/base/config'].roomPasswordNumberOfDigits);
const conference = useSelector((state: IReduxState) => state['features/base/conference'].conference);
const locked = useSelector((state: IReduxState) => state['features/base/conference'].locked);
const password = useSelector((state: IReduxState) => state['features/base/conference'].password);
const formRef = useRef<HTMLDivElement>(null);
const [ passwordVisible, setPasswordVisible ] = useState(false);
const buttonsWithNotifyClick = useSelector(
(state: IReduxState) => state['features/toolbox'].buttonsWithNotifyClick);
const [ passwordEditEnabled, setPasswordEditEnabled ] = useState(false);
if (passwordEditEnabled && (password || locked)) {
setPasswordEditEnabled(false);
}
const onPasswordSubmit = useCallback((enteredPassword: string) => {
if (enteredPassword && passwordNumberOfDigits && !DIGITS_ONLY.test(enteredPassword)) {
// Don't set the password.
return;
}
dispatch(setPassword(conference, conference?.lock, enteredPassword));
}, [ dispatch, passwordNumberOfDigits, conference?.lock ]);
const onTogglePasswordEditState = useCallback(() => {
if (typeof APP === 'undefined' || !buttonsWithNotifyClick?.size) {
setPasswordEditEnabled(!passwordEditEnabled);
return;
}
const notifyMode = buttonsWithNotifyClick?.get(KEY);
if (notifyMode) {
APP.API.notifyToolbarButtonClicked(
KEY, notifyMode === NOTIFY_CLICK_MODE.PREVENT_AND_NOTIFY
);
}
if (!notifyMode || notifyMode === NOTIFY_CLICK_MODE.ONLY_NOTIFY) {
setPasswordEditEnabled(!passwordEditEnabled);
}
}, [ buttonsWithNotifyClick, setPasswordEditEnabled, passwordEditEnabled ]);
const onPasswordSave = useCallback(() => {
if (formRef.current) {
// @ts-ignore
const { value } = formRef.current.querySelector('div > input');
if (value) {
onPasswordSubmit(value);
}
}
}, [ formRef.current, onPasswordSubmit ]);
const onPasswordRemove = useCallback(() => {
onPasswordSubmit('');
}, [ onPasswordSubmit ]);
const onPasswordCopy = useCallback(() => {
copyText(password ?? '');
}, [ password ]);
const onPasswordShow = useCallback(() => {
setPasswordVisible(true);
}, [ setPasswordVisible ]);
const onPasswordHide = useCallback(() => {
setPasswordVisible(false);
}, [ setPasswordVisible ]);
let actions = null;
if (canEditPassword) {
if (passwordEditEnabled) {
actions = (
<>
<button
className = 'as-link'
onClick = { onTogglePasswordEditState }
type = 'button'>
{ t('dialog.Cancel') }
<span className = 'sr-only'>({ t('dialog.password') })</span>
</button>
<button
className = 'as-link'
onClick = { onPasswordSave }
type = 'button'>
{ t('dialog.add') }
<span className = 'sr-only'>({ t('dialog.password') })</span>
</button>
</>
);
} else if (locked) {
actions = (
<>
<button
className = 'remove-password as-link'
onClick = { onPasswordRemove }
type = 'button'>
{ t('dialog.Remove') }
<span className = 'sr-only'>({ t('dialog.password') })</span>
</button>
{
// There are cases like lobby and grant moderator when password is not available
password ? <>
<button
className = 'copy-password as-link'
onClick = { onPasswordCopy }
type = 'button'>
{ t('dialog.copy') }
<span className = 'sr-only'>({ t('dialog.password') })</span>
</button>
</> : null
}
{locked === LOCKED_LOCALLY && (
<button
className = 'as-link'
onClick = { passwordVisible ? onPasswordHide : onPasswordShow }
type = 'button'>
{t(passwordVisible ? 'dialog.hide' : 'dialog.show')}
<span className = 'sr-only'>({ t('dialog.password') })</span>
</button>
)}
</>
);
} else {
actions = (
<button
className = 'add-password as-link'
onClick = { onTogglePasswordEditState }
type = 'button'>{ t('info.addPassword') }</button>
);
}
}
return (
<div className = 'security-dialog password-section'>
<p className = 'description'>
{ t(canEditPassword ? 'security.about' : 'security.aboutReadOnly') }
</p>
<div className = 'security-dialog password'>
<div
className = 'info-dialog info-dialog-column info-dialog-password'
ref = { formRef }>
<PasswordForm
editEnabled = { passwordEditEnabled }
locked = { locked }
onSubmit = { onPasswordSubmit }
password = { password }
passwordNumberOfDigits = { passwordNumberOfDigits }
visible = { passwordVisible } />
</div>
<div className = 'security-dialog password-actions'>
{ actions }
</div>
</div>
</div>
);
}
export default PasswordSection;

View File

@@ -0,0 +1,65 @@
import React from 'react';
import { useSelector } from 'react-redux';
import { IReduxState } from '../../../../app/types';
import { getSecurityUiConfig } from '../../../../base/config/functions.any';
import { isLocalParticipantModerator } from '../../../../base/participants/functions';
import Dialog from '../../../../base/ui/components/web/Dialog';
import { isInBreakoutRoom } from '../../../../breakout-rooms/functions';
import E2EESection from '../../../../e2ee/components/E2EESection';
import LobbySection from '../../../../lobby/components/web/LobbySection';
import { isEnablingLobbyAllowed } from '../../../../lobby/functions';
import PasswordSection from './PasswordSection';
export interface INotifyClick {
key: string;
preventExecution: boolean;
}
/**
* Component that renders the security options dialog.
*
* @returns {React$Element<any>}
*/
export default function SecurityDialog() {
const lobbySupported = useSelector((state: IReduxState) =>
state['features/base/conference'].conference?.isLobbySupported());
const e2eeSupported = useSelector((state: IReduxState) => state['features/base/conference'].e2eeSupported);
const isInBreakout = useSelector(isInBreakoutRoom);
const disableLobbyPassword = useSelector((state: IReduxState) => getSecurityUiConfig(state)?.disableLobbyPassword)
|| isInBreakout;
const isModerator = useSelector(isLocalParticipantModerator);
const { hideLobbyButton } = useSelector(getSecurityUiConfig);
const _isLobbyVisible = useSelector(isEnablingLobbyAllowed)
&& lobbySupported && isModerator && !isInBreakout && !hideLobbyButton;
const showE2ee = Boolean(e2eeSupported) && isModerator;
return (
<Dialog
cancel = {{ hidden: true }}
ok = {{ hidden: true }}
titleKey = 'security.title'>
<div className = 'security-dialog'>
{
_isLobbyVisible && <LobbySection />
}
{
!disableLobbyPassword && (
<>
{ _isLobbyVisible && <div className = 'separator-line' /> }
<PasswordSection />
</>
)
}
{
showE2ee ? <>
{ (_isLobbyVisible || !disableLobbyPassword) && <div className = 'separator-line' /> }
<E2EESection />
</> : null
}
</div>
</Dialog>
);
}

View File

@@ -0,0 +1,28 @@
import { connect } from 'react-redux';
import { translate } from '../../../../base/i18n/functions';
import { toggleSecurityDialog } from '../../../actions';
import AbstractSecurityDialogButton, {
IProps as AbstractSecurityDialogButtonProps,
_mapStateToProps as _abstractMapStateToProps
} from '../AbstractSecurityDialogButton';
/**
* Implements an {@link AbstractSecurityDialogButton} to open the security dialog.
*/
class SecurityDialogButton<P extends AbstractSecurityDialogButtonProps, S> extends AbstractSecurityDialogButton<P, S> {
/**
* Opens / closes the security dialog.
*
* @private
* @returns {void}
*/
override _handleClickSecurityButton() {
const { dispatch } = this.props;
dispatch(toggleSecurityDialog());
}
}
export default translate(connect(_abstractMapStateToProps)(SecurityDialogButton));