Files
jitsi-meet/react/features/welcome/components/WelcomePage.web.js
Hristo Terezov ae565aaac6 fix(device-selection): Handle properly on prejoin
The device selection initialization on the prejoin use case was handled
like the welcome page. This was introducing issues with selecting the
stored devices and not the ones used, enabling the device selection when
it will fail and others.
2022-05-24 15:53:12 -05:00

519 lines
18 KiB
JavaScript

/* global interfaceConfig */
import React from 'react';
import { isMobileBrowser } from '../../base/environment/utils';
import { translate, translateToHTML } from '../../base/i18n';
import { Icon, IconWarning } from '../../base/icons';
import { Watermarks } from '../../base/react';
import { connect } from '../../base/redux';
import { CalendarList } from '../../calendar-sync';
import { RecentList } from '../../recent-list';
import { SettingsButton, SETTINGS_TABS } from '../../settings';
import { AbstractWelcomePage, _mapStateToProps } from './AbstractWelcomePage';
import Tabs from './Tabs';
/**
* The pattern used to validate room name.
*
* @type {string}
*/
export const ROOM_NAME_VALIDATE_PATTERN_STR = '^[^?&:\u0022\u0027%#]+$';
/**
* The Web container rendering the welcome page.
*
* @augments AbstractWelcomePage
*/
class WelcomePage extends AbstractWelcomePage {
/**
* Default values for {@code WelcomePage} component's properties.
*
* @static
*/
static defaultProps = {
_room: ''
};
/**
* Initializes a new WelcomePage instance.
*
* @param {Object} props - The read-only properties with which the new
* instance is to be initialized.
*/
constructor(props) {
super(props);
this.state = {
...this.state,
generateRoomnames:
interfaceConfig.GENERATE_ROOMNAMES_ON_WELCOME_PAGE,
selectedTab: 0
};
/**
* The HTML Element used as the container for additional content. Used
* for directly appending the additional content template to the dom.
*
* @private
* @type {HTMLTemplateElement|null}
*/
this._additionalContentRef = null;
this._roomInputRef = null;
/**
* The HTML Element used as the container for additional toolbar content. Used
* for directly appending the additional content template to the dom.
*
* @private
* @type {HTMLTemplateElement|null}
*/
this._additionalToolbarContentRef = null;
this._additionalCardRef = null;
/**
* The template to use as the additional card displayed near the main one.
*
* @private
* @type {HTMLTemplateElement|null}
*/
this._additionalCardTemplate = document.getElementById(
'welcome-page-additional-card-template');
/**
* The template to use as the main content for the welcome page. If
* not found then only the welcome page head will display.
*
* @private
* @type {HTMLTemplateElement|null}
*/
this._additionalContentTemplate = document.getElementById(
'welcome-page-additional-content-template');
/**
* The template to use as the additional content for the welcome page header toolbar.
* If not found then only the settings icon will be displayed.
*
* @private
* @type {HTMLTemplateElement|null}
*/
this._additionalToolbarContentTemplate = document.getElementById(
'settings-toolbar-additional-content-template'
);
// Bind event handlers so they are only bound once per instance.
this._onFormSubmit = this._onFormSubmit.bind(this);
this._onRoomChange = this._onRoomChange.bind(this);
this._setAdditionalCardRef = this._setAdditionalCardRef.bind(this);
this._setAdditionalContentRef
= this._setAdditionalContentRef.bind(this);
this._setRoomInputRef = this._setRoomInputRef.bind(this);
this._setAdditionalToolbarContentRef
= this._setAdditionalToolbarContentRef.bind(this);
this._onTabSelected = this._onTabSelected.bind(this);
this._renderFooter = this._renderFooter.bind(this);
}
/**
* Implements React's {@link Component#componentDidMount()}. Invoked
* immediately after this component is mounted.
*
* @inheritdoc
* @returns {void}
*/
componentDidMount() {
super.componentDidMount();
document.body.classList.add('welcome-page');
document.title = interfaceConfig.APP_NAME;
if (this.state.generateRoomnames) {
this._updateRoomname();
}
if (this._shouldShowAdditionalContent()) {
this._additionalContentRef.appendChild(
this._additionalContentTemplate.content.cloneNode(true));
}
if (this._shouldShowAdditionalToolbarContent()) {
this._additionalToolbarContentRef.appendChild(
this._additionalToolbarContentTemplate.content.cloneNode(true)
);
}
if (this._shouldShowAdditionalCard()) {
this._additionalCardRef.appendChild(
this._additionalCardTemplate.content.cloneNode(true)
);
}
}
/**
* Removes the classname used for custom styling of the welcome page.
*
* @inheritdoc
* @returns {void}
*/
componentWillUnmount() {
super.componentWillUnmount();
document.body.classList.remove('welcome-page');
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement|null}
*/
render() {
const { _moderatedRoomServiceUrl, t } = this.props;
const { DEFAULT_WELCOME_PAGE_LOGO_URL, DISPLAY_WELCOME_FOOTER } = interfaceConfig;
const showAdditionalCard = this._shouldShowAdditionalCard();
const showAdditionalContent = this._shouldShowAdditionalContent();
const showAdditionalToolbarContent = this._shouldShowAdditionalToolbarContent();
const contentClassName = showAdditionalContent ? 'with-content' : 'without-content';
const footerClassName = DISPLAY_WELCOME_FOOTER ? 'with-footer' : 'without-footer';
return (
<div
className = { `welcome ${contentClassName} ${footerClassName}` }
id = 'welcome_page'>
<div className = 'welcome-watermark'>
<Watermarks defaultJitsiLogoURL = { DEFAULT_WELCOME_PAGE_LOGO_URL } />
</div>
<div className = 'header'>
<div className = 'welcome-page-settings'>
<SettingsButton
defaultTab = { SETTINGS_TABS.CALENDAR }
isDisplayedOnWelcomePage = { true } />
{ showAdditionalToolbarContent
? <div
className = 'settings-toolbar-content'
ref = { this._setAdditionalToolbarContentRef } />
: null
}
</div>
<div className = 'header-image' />
<div className = 'header-container'>
<h1 className = 'header-text-title'>
{ t('welcomepage.headerTitle') }
</h1>
<span className = 'header-text-subtitle'>
{ t('welcomepage.headerSubtitle')}
</span>
<div id = 'enter_room'>
<div className = 'enter-room-input-container'>
<form onSubmit = { this._onFormSubmit }>
<input
aria-disabled = 'false'
aria-label = 'Meeting name input'
autoFocus = { true }
className = 'enter-room-input'
id = 'enter_room_field'
onChange = { this._onRoomChange }
pattern = { ROOM_NAME_VALIDATE_PATTERN_STR }
placeholder = { this.state.roomPlaceholder }
ref = { this._setRoomInputRef }
title = { t('welcomepage.roomNameAllowedChars') }
type = 'text'
value = { this.state.room } />
<div
className = { _moderatedRoomServiceUrl
? 'warning-with-link'
: 'warning-without-link' }>
{ this._renderInsecureRoomNameWarning() }
</div>
</form>
</div>
<button
aria-disabled = 'false'
aria-label = 'Start meeting'
className = 'welcome-page-button'
id = 'enter_room_button'
onClick = { this._onFormSubmit }
tabIndex = '0'
type = 'button'>
{ t('welcomepage.startMeeting') }
</button>
</div>
{ _moderatedRoomServiceUrl && (
<div id = 'moderated-meetings'>
<p>
{
translateToHTML(
t, 'welcomepage.moderatedMessage', { url: _moderatedRoomServiceUrl })
}
</p>
</div>)}
</div>
</div>
<div className = 'welcome-cards-container'>
<div className = 'welcome-card-row'>
<div className = 'welcome-tabs welcome-card welcome-card--blue'>
{ this._renderTabs() }
</div>
{ showAdditionalCard
? <div
className = 'welcome-card welcome-card--dark'
ref = { this._setAdditionalCardRef } />
: null }
</div>
{ showAdditionalContent
? <div
className = 'welcome-page-content'
ref = { this._setAdditionalContentRef } />
: null }
</div>
{ DISPLAY_WELCOME_FOOTER && this._renderFooter()}
</div>
);
}
/**
* Renders the insecure room name warning.
*
* @inheritdoc
*/
_doRenderInsecureRoomNameWarning() {
return (
<div className = 'insecure-room-name-warning'>
<Icon src = { IconWarning } />
<span>
{ this.props.t('security.insecureRoomNameWarning') }
</span>
</div>
);
}
/**
* Prevents submission of the form and delegates join logic.
*
* @param {Event} event - The HTML Event which details the form submission.
* @private
* @returns {void}
*/
_onFormSubmit(event) {
event.preventDefault();
if (!this._roomInputRef || this._roomInputRef.reportValidity()) {
this._onJoin();
}
}
/**
* Overrides the super to account for the differences in the argument types
* provided by HTML and React Native text inputs.
*
* @inheritdoc
* @override
* @param {Event} event - The (HTML) Event which details the change such as
* the EventTarget.
* @protected
*/
_onRoomChange(event) {
super._onRoomChange(event.target.value);
}
/**
* Callback invoked when the desired tab to display should be changed.
*
* @param {number} tabIndex - The index of the tab within the array of
* displayed tabs.
* @private
* @returns {void}
*/
_onTabSelected(tabIndex) {
this.setState({ selectedTab: tabIndex });
}
/**
* Renders the footer.
*
* @returns {ReactElement}
*/
_renderFooter() {
const { t } = this.props;
const {
MOBILE_DOWNLOAD_LINK_ANDROID,
MOBILE_DOWNLOAD_LINK_F_DROID,
MOBILE_DOWNLOAD_LINK_IOS
} = interfaceConfig;
return (<footer className = 'welcome-footer'>
<div className = 'welcome-footer-centered'>
<div className = 'welcome-footer-padded'>
<div className = 'welcome-footer-row-block welcome-footer--row-1'>
<div className = 'welcome-footer-row-1-text'>{t('welcomepage.jitsiOnMobile')}</div>
<a
className = 'welcome-badge'
href = { MOBILE_DOWNLOAD_LINK_IOS }>
<img
alt = { t('welcomepage.mobileDownLoadLinkIos') }
src = './images/app-store-badge.png' />
</a>
<a
className = 'welcome-badge'
href = { MOBILE_DOWNLOAD_LINK_ANDROID }>
<img
alt = { t('welcomepage.mobileDownLoadLinkAndroid') }
src = './images/google-play-badge.png' />
</a>
<a
className = 'welcome-badge'
href = { MOBILE_DOWNLOAD_LINK_F_DROID }>
<img
alt = { t('welcomepage.mobileDownLoadLinkFDroid') }
src = './images/f-droid-badge.png' />
</a>
</div>
</div>
</div>
</footer>);
}
/**
* Renders tabs to show previous meetings and upcoming calendar events. The
* tabs are purposefully hidden on mobile browsers.
*
* @returns {ReactElement|null}
*/
_renderTabs() {
if (isMobileBrowser()) {
return null;
}
const { _calendarEnabled, _recentListEnabled, t } = this.props;
const tabs = [];
if (_calendarEnabled) {
tabs.push({
label: t('welcomepage.calendar'),
content: <CalendarList />
});
}
if (_recentListEnabled) {
tabs.push({
label: t('welcomepage.recentList'),
content: <RecentList />
});
}
if (tabs.length === 0) {
return null;
}
return (
<Tabs
onSelect = { this._onTabSelected }
selected = { this.state.selectedTab }
tabs = { tabs } />);
}
/**
* Sets the internal reference to the HTMLDivElement used to hold the
* additional card shown near the tabs card.
*
* @param {HTMLDivElement} el - The HTMLElement for the div that is the root
* of the welcome page content.
* @private
* @returns {void}
*/
_setAdditionalCardRef(el) {
this._additionalCardRef = el;
}
/**
* Sets the internal reference to the HTMLDivElement used to hold the
* welcome page content.
*
* @param {HTMLDivElement} el - The HTMLElement for the div that is the root
* of the welcome page content.
* @private
* @returns {void}
*/
_setAdditionalContentRef(el) {
this._additionalContentRef = el;
}
/**
* Sets the internal reference to the HTMLDivElement used to hold the
* toolbar additional content.
*
* @param {HTMLDivElement} el - The HTMLElement for the div that is the root
* of the additional toolbar content.
* @private
* @returns {void}
*/
_setAdditionalToolbarContentRef(el) {
this._additionalToolbarContentRef = el;
}
/**
* Sets the internal reference to the HTMLInputElement used to hold the
* welcome page input room element.
*
* @param {HTMLInputElement} el - The HTMLElement for the input of the room name on the welcome page.
* @private
* @returns {void}
*/
_setRoomInputRef(el) {
this._roomInputRef = el;
}
/**
* Returns whether or not an additional card should be displayed near the tabs.
*
* @private
* @returns {boolean}
*/
_shouldShowAdditionalCard() {
return interfaceConfig.DISPLAY_WELCOME_PAGE_ADDITIONAL_CARD
&& this._additionalCardTemplate
&& this._additionalCardTemplate.content
&& this._additionalCardTemplate.innerHTML.trim();
}
/**
* Returns whether or not additional content should be displayed below
* the welcome page's header for entering a room name.
*
* @private
* @returns {boolean}
*/
_shouldShowAdditionalContent() {
return interfaceConfig.DISPLAY_WELCOME_PAGE_CONTENT
&& this._additionalContentTemplate
&& this._additionalContentTemplate.content
&& this._additionalContentTemplate.innerHTML.trim();
}
/**
* Returns whether or not additional content should be displayed inside
* the header toolbar.
*
* @private
* @returns {boolean}
*/
_shouldShowAdditionalToolbarContent() {
return interfaceConfig.DISPLAY_WELCOME_PAGE_TOOLBAR_ADDITIONAL_CONTENT
&& this._additionalToolbarContentTemplate
&& this._additionalToolbarContentTemplate.content
&& this._additionalToolbarContentTemplate.innerHTML.trim();
}
}
export default translate(connect(_mapStateToProps)(WelcomePage));