jiti-meet/react/features/welcome/components/WelcomePage.web.js

510 lines
17 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.
*
* @extends 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();
return (
<div
className = { `welcome ${showAdditionalContent
? 'with-content' : 'without-content'}` }
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 } />
{ 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.jitsiMeet') }
</h1>
<span className = 'header-text-subtitle'>
{ t('welcomepage.secureMeetings')}
</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 src = './images/app-store-badge.png' />
</a>
<a
className = 'welcome-badge'
href = { MOBILE_DOWNLOAD_LINK_ANDROID }>
<img src = './images/google-play-badge.png' />
</a>
<a
className = 'welcome-badge'
href = { MOBILE_DOWNLOAD_LINK_F_DROID }>
<img 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));