ref(TS) Convert some components to TS (#13192)

This commit is contained in:
Robert Pintilii
2023-04-11 12:10:37 +03:00
committed by GitHub
parent 00c3ea07e7
commit 373be54b04
33 changed files with 333 additions and 394 deletions

View File

@@ -43,7 +43,7 @@ export class App extends AbstractApp {
*
* @override
*/
_createMainElement(component, props) {
_createMainElement(component: React.ComponentType, props: any) {
return (
<JitsiThemeProvider>
<AtlasKitThemeProvider mode = 'dark'>

View File

@@ -10,8 +10,9 @@ const route = {
* Determines which route is to be rendered in order to depict a specific Redux
* store.
*
* @param {any} _stateful - Used on web.
* @returns {Promise<Object>}
*/
export function _getRouteToRender() {
export function _getRouteToRender(_stateful: any) {
return Promise.resolve(route);
}

View File

@@ -1,16 +1,21 @@
// @ts-expect-error
import { generateRoomWithoutSeparator } from '@jitsi/js-utils/random';
import { IStateful } from '../base/app/types';
import { isRoomValid } from '../base/conference/functions';
import { isSupportedBrowser } from '../base/environment/environment';
import { toState } from '../base/redux/functions';
// eslint-disable-next-line lines-around-comment
// @ts-ignore
import Conference from '../conference/components/web/Conference';
import { getDeepLinkingPage } from '../deep-linking/functions';
import UnsupportedDesktopBrowser from '../unsupported-browser/components/UnsupportedDesktopBrowser';
import BlankPage from '../welcome/components/BlankPage';
import WelcomePage from '../welcome/components/WelcomePage';
import BlankPage from '../welcome/components/BlankPage.web';
import WelcomePage from '../welcome/components/WelcomePage.web';
import { getCustomLandingPageURL, isWelcomePageEnabled } from '../welcome/functions';
import { IReduxState } from './types';
/**
* Determines which route is to be rendered in order to depict a specific Redux
* store.
@@ -19,7 +24,7 @@ import { getCustomLandingPageURL, isWelcomePageEnabled } from '../welcome/functi
* {@code getState} function.
* @returns {Promise<Object>}
*/
export function _getRouteToRender(stateful) {
export function _getRouteToRender(stateful: IStateful) {
const state = toState(stateful);
return _getWebConferenceRoute(state) || _getWebWelcomePageRoute(state);
@@ -32,7 +37,7 @@ export function _getRouteToRender(stateful) {
* @param {Object} state - The redux state.
* @returns {Promise|undefined}
*/
function _getWebConferenceRoute(state) {
function _getWebConferenceRoute(state: IReduxState) {
if (!isRoomValid(state['features/base/conference'].room)) {
return;
}
@@ -45,8 +50,8 @@ function _getWebConferenceRoute(state) {
// room into account.
const { locationURL } = state['features/base/connection'];
if (window.location.href !== locationURL.href) {
route.href = locationURL.href;
if (window.location.href !== locationURL?.href) {
route.href = locationURL?.href;
return Promise.resolve(route);
}
@@ -71,7 +76,7 @@ function _getWebConferenceRoute(state) {
* @param {Object} state - The redux state.
* @returns {Promise<Object>}
*/
function _getWebWelcomePageRoute(state) {
function _getWebWelcomePageRoute(state: IReduxState) {
const route = _getEmptyRoute();
if (isWelcomePageEnabled(state)) {
@@ -102,7 +107,10 @@ function _getWebWelcomePageRoute(state) {
*
* @returns {Object}
*/
function _getEmptyRoute() {
function _getEmptyRoute(): {
component: React.ReactNode;
href?: string;
} {
return {
component: BlankPage,
href: undefined

View File

@@ -1,4 +1,4 @@
// @flow
import { AnyAction } from 'redux';
import { createConnectionEvent } from '../analytics/AnalyticsEvents';
import { sendAnalytics } from '../analytics/functions';
@@ -10,6 +10,7 @@ import { inIframe } from '../base/util/iframeUtils';
import { reloadNow } from './actions';
import { _getRouteToRender } from './getRouteToRender';
import { IStore } from './types';
MiddlewareRegistry.register(store => next => action => {
switch (action.type) {
@@ -39,7 +40,7 @@ MiddlewareRegistry.register(store => next => action => {
* @returns {Object} The new state that is the result of the reduction of the
* specified {@code action}.
*/
function _connectionEstablished(store, next, action) {
function _connectionEstablished(store: IStore, next: Function, action: AnyAction) {
const result = next(action);
// In the Web app we explicitly do not want to display the hash and
@@ -47,6 +48,7 @@ function _connectionEstablished(store, next, action) {
// importantly, its params are used not only in jitsi-meet but also in
// lib-jitsi-meet. Consequently, the time to remove the params is
// determined by when no one needs them anymore.
// @ts-ignore
const { history, location } = window;
if (inIframe()) {
@@ -57,12 +59,14 @@ function _connectionEstablished(store, next, action) {
&& location
&& history.length
&& typeof history.replaceState === 'function') {
// @ts-ignore
const replacement = getURLWithoutParams(location);
// @ts-ignore
if (location !== replacement) {
history.replaceState(
history.state,
(document && document.title) || '',
document?.title || '',
replacement);
}
}
@@ -81,7 +85,7 @@ function _connectionEstablished(store, next, action) {
* @returns {Object}
* @private
*/
function _connectionFailed({ dispatch, getState }, next, action) {
function _connectionFailed({ dispatch, getState }: IStore, next: Function, action: AnyAction) {
// In the case of a split-brain error, reload early and prevent further
// handling of the action.
if (_isMaybeSplitBrainError(getState, action)) {
@@ -104,7 +108,7 @@ function _connectionFailed({ dispatch, getState }, next, action) {
* @private
* @returns {boolean}
*/
function _isMaybeSplitBrainError(getState, action) {
function _isMaybeSplitBrainError(getState: IStore['getState'], action: AnyAction) {
const { error } = action;
const isShardChangedError = error
&& error.message === 'item-not-found'
@@ -116,7 +120,7 @@ function _isMaybeSplitBrainError(getState, action) {
const { timeEstablished } = state['features/base/connection'];
const { _immediateReloadThreshold } = state['features/base/config'];
const timeSinceConnectionEstablished = timeEstablished && Date.now() - timeEstablished;
const timeSinceConnectionEstablished = Number(timeEstablished && Date.now() - timeEstablished);
const reloadThreshold = typeof _immediateReloadThreshold === 'number' ? _immediateReloadThreshold : 1500;
const isWithinSplitBrainThreshold = !timeEstablished || timeSinceConnectionEstablished <= reloadThreshold;
@@ -142,7 +146,7 @@ function _isMaybeSplitBrainError(getState, action) {
* @private
* @returns {void}
*/
function _navigate({ getState }) {
function _navigate({ getState }: IStore) {
const state = getState();
const { app } = state['features/base/app'];
@@ -163,7 +167,7 @@ function _navigate({ getState }) {
* @returns {Object} The new state that is the result of the reduction of the
* specified {@code action}.
*/
function _setRoom(store, next, action) {
function _setRoom(store: IStore, next: Function, action: AnyAction) {
const result = next(action);
_navigate(store);

View File

@@ -186,7 +186,7 @@ export default class BaseApp<P> extends Component<P, IState> {
* @abstract
* @protected
*/
_createExtraElement() {
_createExtraElement(): React.ReactElement | null {
return null;
}
@@ -280,5 +280,7 @@ export default class BaseApp<P> extends Component<P, IState> {
*
* @returns {React$Element}
*/
_renderDialogContainer: () => React.ReactElement;
_renderDialogContainer(): React.ReactElement | null {
return null;
}
}

View File

@@ -112,8 +112,8 @@ export interface IDeeplinkingMobileConfig extends IDeeplinkingPlatformConfig {
export interface IDeeplinkingConfig {
android?: IDeeplinkingMobileConfig;
desktop?: IDeeplinkingPlatformConfig;
disabled: boolean;
hideLogo: boolean;
disabled?: boolean;
hideLogo?: boolean;
ios?: IDeeplinkingMobileConfig;
}
@@ -127,7 +127,8 @@ export interface INoiseSuppressionConfig {
export interface IConfig {
_desktopSharingSourceDevice?: string;
_screenshotHistoryRegionUrl?: string;
_immediateReloadThreshold?: string;
_screenshotHistoryRegionUrl?: number;
analytics?: {
amplitudeAPPKey?: string;
blackListedEvents?: string[];

View File

@@ -1,51 +1,48 @@
// @flow
import React, { useEffect } from 'react';
import { connect } from 'react-redux';
import { getDisplayName } from '../../../../base/settings/functions.web';
import { IReduxState } from '../../../../app/types';
import Avatar from '../../../avatar/components/Avatar';
import Video from '../../../media/components/web/Video';
import { getLocalParticipant } from '../../../participants/functions';
import { getDisplayName } from '../../../settings/functions.web';
import { getLocalVideoTrack } from '../../../tracks/functions.web';
declare var APP: Object;
export type Props = {
export interface IProps {
/**
* Local participant id.
*/
_participantId: string,
_participantId: string;
/**
* Flag controlling whether the video should be flipped or not.
*/
flipVideo: boolean,
flipVideo: boolean;
/**
* The name of the user that is about to join.
*/
name: string,
name: string;
/**
* Flag signaling the visibility of camera preview.
*/
videoMuted: boolean,
videoMuted: boolean;
/**
* The JitsiLocalTrack to display.
*/
videoTrack: ?Object,
};
videoTrack?: Object;
}
/**
* Component showing the video preview and device status.
*
* @param {Props} props - The props of the component.
* @param {IProps} props - The props of the component.
* @returns {ReactElement}
*/
function Preview(props: Props) {
function Preview(props: IProps) {
const { _participantId, flipVideo, name, videoMuted, videoTrack } = props;
const className = flipVideo ? 'flipVideoX' : '';
@@ -83,19 +80,19 @@ function Preview(props: Props) {
* Maps part of the Redux state to the props of this component.
*
* @param {Object} state - The Redux state.
* @param {Props} ownProps - The own props of the component.
* @returns {Props}
* @param {IProps} ownProps - The own props of the component.
* @returns {IProps}
*/
function _mapStateToProps(state, ownProps) {
function _mapStateToProps(state: IReduxState, ownProps: any) {
const name = getDisplayName(state);
const { id: _participantId } = getLocalParticipant(state);
const { id: _participantId } = getLocalParticipant(state) ?? {};
return {
_participantId,
flipVideo: state['features/base/settings'].localFlipX,
_participantId: _participantId ?? '',
flipVideo: Boolean(state['features/base/settings'].localFlipX),
name,
videoMuted: ownProps.videoTrack ? ownProps.videoMuted : state['features/base/media'].video.muted,
videoTrack: ownProps.videoTrack || (getLocalVideoTrack(state['features/base/tracks']) || {}).jitsiTrack
videoTrack: ownProps.videoTrack || getLocalVideoTrack(state['features/base/tracks'])?.jitsiTrack
};
}

View File

@@ -1,38 +1,29 @@
/* @flow */
import React, { Component } from 'react';
import { WithTranslation } from 'react-i18next';
import { translate } from '../../../i18n/functions';
import Button from '../../../ui/components/web/Button';
declare var interfaceConfig: Object;
/**
* The type of the React {@code Component} props of {@link InlineDialogFailure}.
*/
type Props = {
interface IProps extends WithTranslation {
/**
* Allows to retry the call that previously didn't succeed.
*/
onRetry: Function,
/**
* Invoked to obtain translated strings.
*/
t: Function,
onRetry: Function;
/**
* Indicates whether the support link should be shown in case of an error.
*/
showSupportLink: Boolean,
};
showSupportLink: Boolean;
}
/**
* Inline dialog that represents a failure and allows a retry.
*/
class InlineDialogFailure extends Component<Props> {
class InlineDialogFailure extends Component<IProps> {
/**
* Renders the content of this component.
*

View File

@@ -1,6 +1,5 @@
// @flow
import React, { Component } from 'react';
import { WithTranslation } from 'react-i18next';
import { getLocalizedDateFormatter, getLocalizedDurationFormatter } from '../../../i18n/dateUtil';
import { translate } from '../../../i18n/functions';
@@ -10,43 +9,47 @@ import { IconTrash } from '../../../icons/svg';
import Container from './Container';
import Text from './Text';
type Props = {
interface IMeeting {
date: Date;
duration?: number;
elementAfter?: React.ReactElement;
time: Date[];
title: string;
url: string;
}
interface IProps extends WithTranslation {
/**
* Indicates if the list is disabled or not.
*/
disabled: boolean,
disabled: boolean;
/**
* Indicates if the URL should be hidden or not.
*/
hideURL: boolean,
/**
* Function to be invoked when an item is pressed. The item's URL is passed.
*/
onPress: Function,
hideURL?: boolean;
/**
* Rendered when the list is empty. Should be a rendered element.
*/
listEmptyComponent: Object,
listEmptyComponent: React.ReactNode;
/**
* An array of meetings.
*/
meetings: Array<Object>,
meetings: IMeeting[];
/**
* Handler for deleting an item.
*/
onItemDelete?: Function,
onItemDelete?: Function;
/**
* Invoked to obtain translated strings.
* Function to be invoked when an item is pressed. The item's URL is passed.
*/
t: Function
};
onPress: Function;
}
/**
* Generates a date string for a given date.
@@ -55,7 +58,7 @@ type Props = {
* @private
* @returns {string}
*/
function _toDateString(date) {
function _toDateString(date: Date) {
return getLocalizedDateFormatter(date).format('ll');
}
@@ -67,7 +70,7 @@ function _toDateString(date) {
* @private
* @returns {string}
*/
function _toTimeString(times) {
function _toTimeString(times: Date[]) {
if (times && times.length > 0) {
return (
times
@@ -84,13 +87,13 @@ function _toTimeString(times) {
*
* @augments Component
*/
class MeetingsList extends Component<Props> {
class MeetingsList extends Component<IProps> {
/**
* Constructor of the MeetingsList component.
*
* @inheritdoc
*/
constructor(props: Props) {
constructor(props: IProps) {
super(props);
this._onPress = this._onPress.bind(this);
@@ -123,8 +126,6 @@ class MeetingsList extends Component<Props> {
return null;
}
_onPress: string => Function;
/**
* Returns a function that is used in the onPress callback of the items.
*
@@ -132,18 +133,16 @@ class MeetingsList extends Component<Props> {
* @private
* @returns {Function}
*/
_onPress(url) {
_onPress(url: string) {
const { disabled, onPress } = this.props;
if (!disabled && url && typeof onPress === 'function') {
return () => onPress(url);
}
return null;
return undefined;
}
_onKeyPress: string => Function;
/**
* Returns a function that is used in the onPress callback of the items.
*
@@ -151,22 +150,20 @@ class MeetingsList extends Component<Props> {
* @private
* @returns {Function}
*/
_onKeyPress(url) {
_onKeyPress(url: string) {
const { disabled, onPress } = this.props;
if (!disabled && url && typeof onPress === 'function') {
return e => {
return (e: React.KeyboardEvent) => {
if (e.key === ' ' || e.key === 'Enter') {
onPress(url);
}
};
}
return null;
return undefined;
}
_onDelete: Object => Function;
/**
* Returns a function that is used on the onDelete callback.
*
@@ -174,18 +171,16 @@ class MeetingsList extends Component<Props> {
* @private
* @returns {Function}
*/
_onDelete(item) {
_onDelete(item: Object) {
const { onItemDelete } = this.props;
return evt => {
return (evt: React.MouseEvent) => {
evt.stopPropagation();
onItemDelete && onItemDelete(item);
onItemDelete?.(item);
};
}
_onDeleteKeyPress: Object => Function;
/**
* Returns a function that is used on the onDelete keypress callback.
*
@@ -193,10 +188,10 @@ class MeetingsList extends Component<Props> {
* @private
* @returns {Function}
*/
_onDeleteKeyPress(item) {
_onDeleteKeyPress(item: Object) {
const { onItemDelete } = this.props;
return e => {
return (e: React.KeyboardEvent) => {
if (onItemDelete && (e.key === ' ' || e.key === 'Enter')) {
e.preventDefault();
e.stopPropagation();
@@ -205,8 +200,6 @@ class MeetingsList extends Component<Props> {
};
}
_renderItem: (Object, number) => React$Node;
/**
* Renders an item for the list.
*
@@ -214,7 +207,7 @@ class MeetingsList extends Component<Props> {
* @param {number} index - The index of the item.
* @returns {Node}
*/
_renderItem(meeting, index) {
_renderItem(meeting: IMeeting, index: number) {
const {
date,
duration,

View File

@@ -1,5 +1,3 @@
// @flow
import React, { Component } from 'react';
import { Item } from '../../types';
@@ -11,18 +9,18 @@ import Text from './Text';
* The type of the React {@code Component} props of
* {@link NavigateSectionListItem}.
*/
type Props = {
/**
* Function to be invoked when an item is pressed. The item's URL is passed.
*/
onPress: ?Function,
interface IProps {
/**
* A item containing data to be rendered.
*/
item: Item
};
item: Item;
/**
* Function to be invoked when an item is pressed. The item's URL is passed.
*/
onPress?: Function;
}
/**
* Implements a React/Web {@link Component} for displaying an item in a
@@ -30,7 +28,7 @@ type Props = {
*
* @augments Component
*/
export default class NavigateSectionListItem<P: Props>
export default class NavigateSectionListItem<P extends IProps>
extends Component<P> {
/**

View File

@@ -1,17 +1,15 @@
// @flow
import React, { Component } from 'react';
import { Section } from '../../types';
import Text from './Text';
type Props = {
interface IProps {
/**
* A section containing the data to be rendered.
*/
section: Section
section: Section;
}
/**
@@ -20,7 +18,7 @@ type Props = {
*
* @augments Component
*/
export default class NavigateSectionListSectionHeader extends Component<Props> {
export default class NavigateSectionListSectionHeader extends Component<IProps> {
/**
* Renders the content of this component.
*

View File

@@ -1,44 +1,42 @@
// @flow
import React, { Component } from 'react';
import { Section } from '../../types';
import Container from './Container';
type Props = {
interface IProps {
/**
* Rendered when the list is empty. Should be a rendered element.
*/
ListEmptyComponent: Object,
ListEmptyComponent: Object;
/**
* Used to extract a unique key for a given item at the specified index.
* Key is used for caching and as the react key to track item re-ordering.
*/
keyExtractor: Function,
/**
* Returns a React component that renders each Item in the list.
*/
renderItem: Function,
/**
* Returns a React component that renders the header for every section.
*/
renderSectionHeader: Function,
/**
* An array of sections.
*/
sections: Array<Section>,
keyExtractor: Function;
/**
* Defines what happens when an item in the section list is clicked.
*/
onItemClick: Function
};
onItemClick: Function;
/**
* Returns a React component that renders each Item in the list.
*/
renderItem: Function;
/**
* Returns a React component that renders the header for every section.
*/
renderSectionHeader: Function;
/**
* An array of sections.
*/
sections: Array<Section>;
}
/**
* Implements a React/Web {@link Component} for displaying a list with
@@ -47,7 +45,7 @@ type Props = {
*
* @augments Component
*/
export default class SectionList extends Component<Props> {
export default class SectionList extends Component<IProps> {
/**
* Renders the content of this component.
*

View File

@@ -1,14 +1,11 @@
/* @flow */
import React, { Component } from 'react';
import { WithTranslation } from 'react-i18next';
import { connect } from 'react-redux';
import { IReduxState } from '../../../../app/types';
import { isVpaasMeeting } from '../../../../jaas/functions';
import { translate } from '../../../i18n/functions';
declare var interfaceConfig: Object;
/**
* The CSS style of the element with CSS class {@code rightwatermark}.
*
@@ -21,38 +18,33 @@ const _RIGHT_WATERMARK_STYLE = {
/**
* The type of the React {@code Component} props of {@link Watermarks}.
*/
type Props = {
interface IProps extends WithTranslation {
/**
* The link used to navigate to on logo click.
*/
_logoLink: string,
_logoLink: string;
/**
* The url for the logo.
*/
_logoUrl: string,
_logoUrl?: string;
/**
* If the Jitsi watermark should be displayed or not.
*/
_showJitsiWatermark: boolean,
_showJitsiWatermark: boolean;
/**
* The default value for the Jitsi logo URL.
*/
defaultJitsiLogoURL?: string;
/**
* Whether the watermark should have a `top` and `left` value.
*/
noMargins: boolean;
/**
* The default value for the Jitsi logo URL.
*/
defaultJitsiLogoURL: ?string,
/**
* Invoked to obtain translated strings.
*/
t: Function
};
}
/**
* The type of the React {@code Component} state of {@link Watermarks}.
@@ -62,31 +54,31 @@ type State = {
/**
* The url to open when clicking the brand watermark.
*/
brandWatermarkLink: string,
brandWatermarkLink: string;
/**
* Whether or not the brand watermark should be displayed.
*/
showBrandWatermark: boolean,
showBrandWatermark: boolean;
/**
* Whether or not the show the "powered by Jitsi.org" link.
*/
showPoweredBy: boolean
showPoweredBy: boolean;
};
/**
* A Web Component which renders watermarks such as Jits, brand, powered by,
* etc.
*/
class Watermarks extends Component<Props, State> {
class Watermarks extends Component<IProps, State> {
/**
* Initializes a new Watermarks instance.
*
* @param {Object} props - The read-only properties with which the new
* instance is to be initialized.
*/
constructor(props: Props) {
constructor(props: IProps) {
super(props);
const showBrandWatermark = interfaceConfig.SHOW_BRAND_WATERMARK;
@@ -179,7 +171,7 @@ class Watermarks extends Component<Props, State> {
};
reactElement = (<div
className = { className }
className = { className } // @ts-ignore
style = { style } />);
if (_logoLink) {
@@ -227,9 +219,9 @@ class Watermarks extends Component<Props, State> {
*
* @param {Object} state - Snapshot of Redux store.
* @param {Object} ownProps - Component's own props.
* @returns {Props}
* @returns {IProps}
*/
function _mapStateToProps(state, ownProps) {
function _mapStateToProps(state: IReduxState, ownProps: any) {
const {
customizationReady,
customizationFailed,
@@ -248,7 +240,7 @@ function _mapStateToProps(state, ownProps) {
customizationReady && !customizationFailed
&& SHOW_JITSI_WATERMARK)
|| !isValidRoom;
let _logoUrl = logoImageUrl;
let _logoUrl: string | undefined = logoImageUrl;
let _logoLink = logoClickUrl;
if (useDynamicBrandingData) {
@@ -272,4 +264,4 @@ function _mapStateToProps(state, ownProps) {
};
}
export default connect(_mapStateToProps)(translate(Watermarks));
export default translate(connect(_mapStateToProps)(Watermarks));

View File

@@ -2,7 +2,6 @@ import React, { Component, ComponentType } from 'react';
import { connect } from 'react-redux';
import { IReduxState } from '../../../../app/types';
import { IReactionEmojiProps } from '../../../../reactions/constants';
import JitsiPortal from '../../../../toolbox/components/web/JitsiPortal';
import { showOverflowDrawer } from '../../../../toolbox/functions.web';
@@ -25,11 +24,6 @@ interface IProps {
*/
_overflowDrawer: boolean;
/**
* Array of reactions to be displayed.
*/
_reactionsQueue: Array<IReactionEmojiProps>;
/**
* True if the UI is in a compact state where we don't show dialogs.
*/

View File

@@ -1,55 +1,49 @@
// @flow
import React, { Component } from 'react';
import { WithTranslation } from 'react-i18next';
import { connect } from 'react-redux';
import type { Dispatch } from 'redux';
import { createCalendarClickedEvent } from '../../analytics/AnalyticsEvents';
import { sendAnalytics } from '../../analytics/functions';
import { IStore } from '../../app/types';
import { translate } from '../../base/i18n/functions';
import Icon from '../../base/icons/components/Icon';
import { IconPlus } from '../../base/icons/svg';
import Tooltip from '../../base/tooltip/components/Tooltip';
import { updateCalendarEvent } from '../actions';
import { updateCalendarEvent } from '../actions.web';
/**
* The type of the React {@code Component} props of {@link AddMeetingUrlButton}.
*/
type Props = {
interface IProps extends WithTranslation {
/**
* The calendar ID associated with the calendar event.
*/
calendarId: string,
calendarId: string;
/**
* Invoked to add a meeting URL to a calendar event.
*/
dispatch: Dispatch<any>,
dispatch: IStore['dispatch'];
/**
* The ID of the calendar event that will have a meeting URL added on click.
*/
eventId: string,
/**
* Invoked to obtain translated strings.
*/
t: Function
};
eventId: string;
}
/**
* A React Component for adding a meeting URL to an existing calendar event.
*
* @augments Component
*/
class AddMeetingUrlButton extends Component<Props> {
class AddMeetingUrlButton extends Component<IProps> {
/**
* Initializes a new {@code AddMeetingUrlButton} instance.
*
* @inheritdoc
*/
constructor(props: Props) {
constructor(props: IProps) {
super(props);
// Bind event handler so it is only bound once for every instance.
@@ -76,8 +70,6 @@ class AddMeetingUrlButton extends Component<Props> {
);
}
_onClick: () => void;
/**
* Dispatches an action to adding a meeting URL to a calendar event.
*
@@ -91,8 +83,6 @@ class AddMeetingUrlButton extends Component<Props> {
dispatch(updateCalendarEvent(eventId, calendarId));
}
_onKeyPress: (Object) => void;
/**
* KeyPress handler for accessibility.
*
@@ -100,7 +90,7 @@ class AddMeetingUrlButton extends Component<Props> {
*
* @returns {void}
*/
_onKeyPress(e) {
_onKeyPress(e: React.KeyboardEvent) {
if (e.key === ' ' || e.key === 'Enter') {
e.preventDefault();
this._onClick();

View File

@@ -1,71 +1,64 @@
// @flow
import React from 'react';
import { WithTranslation } from 'react-i18next';
import { connect } from 'react-redux';
import { createCalendarClickedEvent } from '../../analytics/AnalyticsEvents';
import { sendAnalytics } from '../../analytics/functions';
import { IReduxState } from '../../app/types';
import { translate } from '../../base/i18n/functions';
import Icon from '../../base/icons/components/Icon';
import { IconCalendar } from '../../base/icons/svg';
import AbstractPage from '../../base/react/components/AbstractPage';
import Spinner from '../../base/ui/components/web/Spinner';
import { openSettingsDialog } from '../../settings/actions';
import { openSettingsDialog } from '../../settings/actions.web';
import { SETTINGS_TABS } from '../../settings/constants';
import { refreshCalendar } from '../actions';
import { refreshCalendar } from '../actions.web';
import { ERRORS } from '../constants';
import CalendarListContent from './CalendarListContent';
declare var interfaceConfig: Object;
import CalendarListContent from './CalendarListContent.web';
/**
* The type of the React {@code Component} props of {@link CalendarList}.
*/
type Props = {
interface IProps extends WithTranslation {
/**
* The error object containing details about any error that has occurred
* while interacting with calendar integration.
*/
_calendarError: ?Object,
_calendarError?: { error: string; };
/**
* Whether or not a calendar may be connected for fetching calendar events.
*/
_hasIntegrationSelected: boolean,
_hasIntegrationSelected: boolean;
/**
* Whether or not events have been fetched from a calendar.
*/
_hasLoadedEvents: boolean,
_hasLoadedEvents: boolean;
/**
* Indicates if the list is disabled or not.
*/
disabled: boolean,
disabled?: boolean;
/**
* The Redux dispatch function.
*/
dispatch: Function,
/**
* The translate function.
*/
t: Function
};
dispatch: Function;
}
/**
* Component to display a list of events from the user's calendar.
*/
class CalendarList extends AbstractPage<Props> {
class CalendarList extends AbstractPage<IProps> {
/**
* Initializes a new {@code CalendarList} instance.
*
* @inheritdoc
*/
constructor(props) {
constructor(props: IProps) {
super(props);
// Bind event handlers so they are only bound once per instance.
@@ -87,7 +80,7 @@ class CalendarList extends AbstractPage<Props> {
return (
CalendarListContent
? <CalendarListContent
disabled = { disabled }
disabled = { Boolean(disabled) }
listEmptyComponent
= { this._getRenderListEmptyComponent() } />
: null
@@ -102,7 +95,7 @@ class CalendarList extends AbstractPage<Props> {
* @returns {React$Component}
*/
_getErrorMessage() {
const { _calendarError = {}, t } = this.props;
const { _calendarError = { error: undefined }, t } = this.props;
let errorMessageKey = 'calendarSync.error.generic';
let showRefreshButton = true;
@@ -142,8 +135,6 @@ class CalendarList extends AbstractPage<Props> {
);
}
_getRenderListEmptyComponent: () => Object;
/**
* Returns a list empty component if a custom one has to be rendered instead
* of the default one in the {@link NavigateSectionList}.
@@ -210,8 +201,6 @@ class CalendarList extends AbstractPage<Props> {
);
}
_onOpenSettings: () => void;
/**
* Opens {@code SettingsDialog}.
*
@@ -224,8 +213,6 @@ class CalendarList extends AbstractPage<Props> {
this.props.dispatch(openSettingsDialog(SETTINGS_TABS.CALENDAR));
}
_onKeyPressOpenSettings: (Object) => void;
/**
* KeyPress handler for accessibility.
*
@@ -233,15 +220,13 @@ class CalendarList extends AbstractPage<Props> {
*
* @returns {void}
*/
_onKeyPressOpenSettings(e) {
_onKeyPressOpenSettings(e: React.KeyboardEvent) {
if (e.key === ' ' || e.key === 'Enter') {
e.preventDefault();
this._onOpenSettings();
}
}
_onRefreshEvents: () => void;
/**
* Gets an updated list of calendar events.
@@ -266,7 +251,7 @@ class CalendarList extends AbstractPage<Props> {
* _hasLoadedEvents: boolean
* }}
*/
function _mapStateToProps(state) {
function _mapStateToProps(state: IReduxState) {
const {
error,
events,

View File

@@ -1,47 +1,46 @@
// @flow
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createCalendarClickedEvent, createCalendarSelectedEvent } from '../../analytics/AnalyticsEvents';
import { sendAnalytics } from '../../analytics/functions';
import { appNavigate } from '../../app/actions';
import { appNavigate } from '../../app/actions.web';
import { IReduxState } from '../../app/types';
import MeetingsList from '../../base/react/components/web/MeetingsList';
import AddMeetingUrlButton from './AddMeetingUrlButton';
import JoinButton from './JoinButton';
import AddMeetingUrlButton from './AddMeetingUrlButton.web';
import JoinButton from './JoinButton.web';
/**
* The type of the React {@code Component} props of
* {@link CalendarListContent}.
*/
type Props = {
interface IProps {
/**
* The calendar event list.
*/
_eventList: Array<Object>,
_eventList: Array<Object>;
/**
* Indicates if the list is disabled or not.
*/
disabled: boolean,
disabled: boolean;
/**
* The Redux dispatch function.
*/
dispatch: Function,
dispatch: Function;
/**
*
*/
listEmptyComponent: React$Node,
};
listEmptyComponent: React.ReactNode;
}
/**
* Component to display a list of events from a connected calendar.
*/
class CalendarListContent extends Component<Props> {
class CalendarListContent extends Component<IProps> {
/**
* Default values for the component's props.
*/
@@ -54,7 +53,7 @@ class CalendarListContent extends Component<Props> {
*
* @inheritdoc
*/
constructor(props: Props) {
constructor(props: IProps) {
super(props);
// Bind event handlers so they are only bound once per instance.
@@ -93,8 +92,6 @@ class CalendarListContent extends Component<Props> {
);
}
_onJoinPress: (Object, string) => Function;
/**
* Handles the list's navigate action.
*
@@ -103,14 +100,12 @@ class CalendarListContent extends Component<Props> {
* @param {string} url - The url string to navigate to.
* @returns {void}
*/
_onJoinPress(event, url) {
_onJoinPress(event: React.KeyboardEvent, url: string) {
event.stopPropagation();
this._onPress(url, 'meeting.join');
}
_onPress: (string, ?string) => Function;
/**
* Handles the list's navigate action.
*
@@ -120,14 +115,12 @@ class CalendarListContent extends Component<Props> {
* associated with this action.
* @returns {void}
*/
_onPress(url, analyticsEventName = 'meeting.tile') {
_onPress(url: string, analyticsEventName = 'meeting.tile') {
sendAnalytics(createCalendarClickedEvent(analyticsEventName));
this.props.dispatch(appNavigate(url));
}
_toDisplayableItem: Object => Object;
/**
* Creates a displayable object from an event.
*
@@ -135,7 +128,7 @@ class CalendarListContent extends Component<Props> {
* @private
* @returns {Object}
*/
_toDisplayableItem(event) {
_toDisplayableItem(event: any) {
return {
elementAfter: event.url
? <JoinButton
@@ -161,7 +154,7 @@ class CalendarListContent extends Component<Props> {
* _eventList: Array<Object>
* }}
*/
function _mapStateToProps(state: Object) {
function _mapStateToProps(state: IReduxState) {
return {
_eventList: state['features/calendar-sync'].events
};

View File

@@ -1,6 +1,5 @@
// @flow
import React, { Component } from 'react';
import { WithTranslation } from 'react-i18next';
import { translate } from '../../base/i18n/functions';
import Icon from '../../base/icons/components/Icon';
@@ -10,30 +9,25 @@ import Tooltip from '../../base/tooltip/components/Tooltip';
/**
* The type of the React {@code Component} props of {@link JoinButton}.
*/
type Props = {
interface IProps extends WithTranslation {
/**
* The function called when the button is pressed.
*/
onPress: Function,
onPress: Function;
/**
* The meeting URL associated with the {@link JoinButton} instance.
*/
url: string,
/**
* Invoked to obtain translated strings.
*/
t: Function
};
url: string;
}
/**
* A React Component for joining an existing calendar meeting.
*
* @augments Component
*/
class JoinButton extends Component<Props> {
class JoinButton extends Component<IProps> {
/**
* Initializes a new {@code JoinButton} instance.
@@ -41,7 +35,7 @@ class JoinButton extends Component<Props> {
* @param {*} props - The read-only properties with which the new instance
* is to be initialized.
*/
constructor(props) {
constructor(props: IProps) {
super(props);
// Bind event handler so it is only bound once for every instance.
@@ -73,8 +67,6 @@ class JoinButton extends Component<Props> {
);
}
_onClick: (Object) => void;
/**
* Callback invoked when the component is clicked.
*
@@ -82,12 +74,10 @@ class JoinButton extends Component<Props> {
* @private
* @returns {void}
*/
_onClick(event) {
_onClick(event?: React.MouseEvent) {
this.props.onPress(event, this.props.url);
}
_onKeyPress: (Object) => void;
/**
* KeyPress handler for accessibility.
*
@@ -95,7 +85,7 @@ class JoinButton extends Component<Props> {
*
* @returns {void}
*/
_onKeyPress(e) {
_onKeyPress(e: React.KeyboardEvent) {
if (e.key === ' ' || e.key === 'Enter') {
e.preventDefault();
this._onClick();

View File

@@ -28,7 +28,7 @@ const DEFAULT_STATE = {
export interface ICalendarSyncState {
authorization?: string;
error?: Object;
error?: { error: string; };
events: Array<{
calendarId: string;
endDate: string;

View File

@@ -1,13 +1,15 @@
// @flow
// @ts-expect-error
import { jitsiLocalStorage } from '@jitsi/js-utils';
import React, { PureComponent } from 'react';
import { WithTranslation } from 'react-i18next';
import { connect } from 'react-redux';
import { createChromeExtensionBannerEvent } from '../../analytics/AnalyticsEvents';
import { sendAnalytics } from '../../analytics/functions';
import { IReduxState } from '../../app/types';
import { getCurrentConference } from '../../base/conference/functions';
import checkChromeExtensionsInstalled from '../../base/environment/checkChromeExtensionsInstalled';
import { IJitsiConference } from '../../base/conference/reducer';
import checkChromeExtensionsInstalled from '../../base/environment/checkChromeExtensionsInstalled.web';
import {
isMobileBrowser
} from '../../base/environment/utils';
@@ -18,9 +20,6 @@ import { browser } from '../../base/lib-jitsi-meet';
import { isVpaasMeeting } from '../../jaas/functions';
import logger from '../logger';
declare var interfaceConfig: Object;
const emptyObject = {};
/**
@@ -33,54 +32,53 @@ const DONT_SHOW_AGAIN_CHECKED = 'hide_chrome_extension_banner';
/**
* The type of the React {@code PureComponent} props of {@link ChromeExtensionBanner}.
*/
type Props = {
interface IProps extends WithTranslation {
/**
* Contains info about installed/to be installed chrome extension(s).
*/
bannerCfg: Object,
bannerCfg: {
chromeExtensionsInfo?: string[];
edgeUrl?: string;
url?: string;
};
/**
* Conference data, if any.
*/
conference: Object,
conference?: IJitsiConference;
/**
* Whether I am the current recorder.
*/
iAmRecorder: boolean,
iAmRecorder: boolean;
/**
* Whether it's a vpaas meeting or not.
*/
isVpaas: boolean,
/**
* Invoked to obtain translated strings.
*/
t: Function,
};
isVpaas: boolean;
}
/**
* The type of the React {@link PureComponent} state of {@link ChromeExtensionBanner}.
*/
type State = {
/**
* Keeps the current value of dont show again checkbox.
*/
dontShowAgainChecked: boolean,
interface IState {
/**
* Tells whether user pressed install extension or close button.
*/
closePressed: boolean,
closePressed: boolean;
/**
* Keeps the current value of dont show again checkbox.
*/
dontShowAgainChecked: boolean;
/**
* Tells whether should show the banner or not based on extension being installed or not.
*/
shouldShow: boolean,
};
shouldShow: boolean;
}
/**
* Implements a React {@link PureComponent} which displays a banner having a link to the chrome extension.
@@ -88,14 +86,16 @@ type State = {
* @class ChromeExtensionBanner
* @augments PureComponent
*/
class ChromeExtensionBanner extends PureComponent<Props, State> {
class ChromeExtensionBanner extends PureComponent<IProps, IState> {
isEdge: boolean;
/**
* Initializes a new {@code ChromeExtensionBanner} instance.
*
* @param {Object} props - The read-only React {@code PureComponent} props with
* which the new instance is to be initialized.
*/
constructor(props: Props) {
constructor(props: IProps) {
super(props);
this.state = {
dontShowAgainChecked: false,
@@ -118,7 +118,7 @@ class ChromeExtensionBanner extends PureComponent<Props, State> {
*
* @inheritdoc
*/
async componentDidUpdate(prevProps) {
async componentDidUpdate(prevProps: IProps) {
if (!this._isSupportedEnvironment()) {
return;
}
@@ -137,8 +137,7 @@ class ChromeExtensionBanner extends PureComponent<Props, State> {
const hasExtensions = await checkChromeExtensionsInstalled(this.props.bannerCfg);
if (
hasExtensions
&& hasExtensions.length
hasExtensions?.length
&& hasExtensions.every(ext => !ext)
&& !this.state.shouldShow
) {
@@ -160,8 +159,6 @@ class ChromeExtensionBanner extends PureComponent<Props, State> {
&& !this.props.isVpaas;
}
_onClosePressed: () => void;
/**
* Closes the banner for the current session.
*
@@ -172,8 +169,6 @@ class ChromeExtensionBanner extends PureComponent<Props, State> {
this.setState({ closePressed: true });
}
_onCloseKeyPress: (Object) => void;
/**
* KeyPress handler for accessibility.
*
@@ -181,15 +176,13 @@ class ChromeExtensionBanner extends PureComponent<Props, State> {
*
* @returns {void}
*/
_onCloseKeyPress(e) {
_onCloseKeyPress(e: React.KeyboardEvent) {
if (e.key === ' ' || e.key === 'Enter') {
e.preventDefault();
this._onClosePressed();
}
}
_onInstallExtensionClick: () => void;
/**
* Opens the chrome extension page.
*
@@ -203,8 +196,6 @@ class ChromeExtensionBanner extends PureComponent<Props, State> {
this.setState({ closePressed: true });
}
_onInstallExtensionKeyPress: (Object) => void;
/**
* KeyPress handler for accessibility.
*
@@ -212,15 +203,13 @@ class ChromeExtensionBanner extends PureComponent<Props, State> {
*
* @returns {void}
*/
_onInstallExtensionKeyPress(e) {
_onInstallExtensionKeyPress(e: React.KeyboardEvent) {
if (e.key === ' ' || e.key === 'Enter') {
e.preventDefault();
this._onClosePressed();
}
}
_shouldNotRender: () => boolean;
/**
* Checks whether the banner should not be rendered.
*
@@ -240,15 +229,13 @@ class ChromeExtensionBanner extends PureComponent<Props, State> {
|| this.props.iAmRecorder;
}
_onDontShowAgainChange: (object: Object) => void;
/**
* Handles the current `don't show again` checkbox state.
*
* @param {Object} event - Input change event.
* @returns {void}
*/
_onDontShowAgainChange(event) {
_onDontShowAgainChange(event: React.ChangeEvent<HTMLInputElement>) {
this.setState({ dontShowAgainChecked: event.target.checked });
}
@@ -258,7 +245,7 @@ class ChromeExtensionBanner extends PureComponent<Props, State> {
* @inheritdoc
* @returns {ReactElement}
*/
render() {
render(): React.ReactNode {
if (this._shouldNotRender()) {
if (this.state.dontShowAgainChecked) {
jitsiLocalStorage.setItem(DONT_SHOW_AGAIN_CHECKED, 'true');
@@ -340,12 +327,12 @@ class ChromeExtensionBanner extends PureComponent<Props, State> {
* @param {Object} state - Redux state.
* @returns {Object}
*/
const _mapStateToProps = state => {
const _mapStateToProps = (state: IReduxState) => {
return {
// Using emptyObject so that we don't change the reference every time when _mapStateToProps is called.
bannerCfg: state['features/base/config'].chromeExtensionBanner || emptyObject,
conference: getCurrentConference(state),
iAmRecorder: state['features/base/config'].iAmRecorder,
iAmRecorder: Boolean(state['features/base/config'].iAmRecorder),
isVpaas: isVpaasMeeting(state)
};
};

View File

@@ -60,6 +60,8 @@ export function getDeepLinkingPage(state: IReduxState) {
const { room } = state['features/base/conference'];
const { launchInWeb } = state['features/deep-linking'];
const deeplinking = state['features/base/config'].deeplinking || {};
// @ts-ignore
const { appScheme } = deeplinking?.[Platform.OS as keyof typeof deeplinking] || {};
// Show only if we are about to join a conference.

View File

@@ -1,56 +1,47 @@
// @flow
import React from 'react';
import { WithTranslation } from 'react-i18next';
import { connect } from 'react-redux';
import type { Dispatch } from 'redux';
import { IReduxState, IStore } from '../../app/types';
import { translate } from '../../base/i18n/functions';
import MeetingsList from '../../base/react/components/web/MeetingsList';
import { deleteRecentListEntry } from '../actions';
import { isRecentListEnabled, toDisplayableList } from '../functions';
import { isRecentListEnabled, toDisplayableList } from '../functions.web';
import AbstractRecentList from './AbstractRecentList';
/**
* The type of the React {@code Component} props of {@link RecentList}.
*/
type Props = {
/**
* Renders the list disabled.
*/
disabled: boolean,
/**
* The redux store's {@code dispatch} function.
*/
dispatch: Dispatch<any>,
/**
* The translate function.
*/
t: Function,
interface IProps extends WithTranslation {
/**
* The recent list from the Redux store.
*/
_recentList: Array<Object>
};
_recentList: Array<any>;
/**
* Renders the list disabled.
*/
disabled?: boolean;
/**
* The redux store's {@code dispatch} function.
*/
dispatch: IStore['dispatch'];
}
/**
* The cross platform container rendering the list of the recently joined rooms.
*
*/
class RecentList extends AbstractRecentList<Props> {
_getRenderListEmptyComponent: () => React$Node;
_onPress: string => {};
class RecentList extends AbstractRecentList<IProps> {
/**
* Initializes a new {@code RecentList} instance.
*
* @inheritdoc
*/
constructor(props: Props) {
constructor(props: IProps) {
super(props);
this._getRenderListEmptyComponent
@@ -59,15 +50,13 @@ class RecentList extends AbstractRecentList<Props> {
this._onItemDelete = this._onItemDelete.bind(this);
}
_onItemDelete: Object => void;
/**
* Deletes a recent entry.
*
* @param {Object} entry - The entry to be deleted.
* @inheritdoc
*/
_onItemDelete(entry) {
_onItemDelete(entry: Object) {
this.props.dispatch(deleteRecentListEntry(entry));
}
@@ -88,7 +77,7 @@ class RecentList extends AbstractRecentList<Props> {
return (
<MeetingsList
disabled = { disabled }
disabled = { Boolean(disabled) }
hideURL = { true }
listEmptyComponent = { this._getRenderListEmptyComponent() }
meetings = { recentList }
@@ -107,7 +96,7 @@ class RecentList extends AbstractRecentList<Props> {
* _recentList: Array
* }}
*/
export function _mapStateToProps(state: Object) {
export function _mapStateToProps(state: IReduxState) {
return {
_recentList: state['features/recent-list']
};

View File

@@ -1,6 +1,7 @@
// @ts-expect-error
import { generateRoomWithoutSeparator } from '@jitsi/js-utils/random';
import { Component } from 'react';
import { WithTranslation } from 'react-i18next';
import { createWelcomePageEvent } from '../../analytics/AnalyticsEvents';
import { sendAnalytics } from '../../analytics/functions';
@@ -14,7 +15,7 @@ import { isRecentListEnabled } from '../../recent-list/functions';
/**
* {@code AbstractWelcomePage}'s React {@code Component} prop types.
*/
export interface IProps {
export interface IProps extends WithTranslation {
/**
* Whether the calendar functionality is enabled or not.
@@ -57,12 +58,23 @@ export interface IProps {
dispatch: IStore['dispatch'];
}
interface IState {
animateTimeoutId?: number;
generateRoomNames?: string;
generatedRoomName: string;
insecureRoomName: boolean;
joining: boolean;
room: string;
roomPlaceholder: string;
updateTimeoutId?: number;
}
/**
* Base (abstract) class for container component rendering the welcome page.
*
* @abstract
*/
export class AbstractWelcomePage<P extends IProps> extends Component<P> {
export class AbstractWelcomePage<P extends IProps> extends Component<P, IState> {
_mounted: boolean | undefined;
/**
@@ -81,6 +93,7 @@ export class AbstractWelcomePage<P extends IProps> extends Component<P> {
state = {
animateTimeoutId: undefined,
generatedRoomName: '',
generateRoomNames: undefined,
insecureRoomName: false,
joining: false,
room: '',
@@ -142,7 +155,7 @@ export class AbstractWelcomePage<P extends IProps> extends Component<P> {
if (word.length > 1) {
animateTimeoutId
= setTimeout(
= window.setTimeout(
() => {
this._animateRoomNameChanging(
word.substring(1, word.length));
@@ -171,7 +184,9 @@ export class AbstractWelcomePage<P extends IProps> extends Component<P> {
*
* @returns {ReactElement}
*/
_doRenderInsecureRoomNameWarning: () => React.Component<any>;
_doRenderInsecureRoomNameWarning(): JSX.Element | null {
return null;
}
/**
* Handles joining. Either by clicking on 'Join' button
@@ -213,7 +228,7 @@ export class AbstractWelcomePage<P extends IProps> extends Component<P> {
_onRoomChange(value: string) {
this.setState({
room: value,
insecureRoomName: this.props._enableInsecureRoomNameWarning && value && isInsecureRoomName(value)
insecureRoomName: Boolean(this.props._enableInsecureRoomNameWarning && value && isInsecureRoomName(value))
});
}
@@ -240,7 +255,7 @@ export class AbstractWelcomePage<P extends IProps> extends Component<P> {
_updateRoomName() {
const generatedRoomName = generateRoomWithoutSeparator();
const roomPlaceholder = '';
const updateTimeoutId = setTimeout(this._updateRoomName, 10000);
const updateTimeoutId = window.setTimeout(this._updateRoomName, 10000);
this._clearTimeouts();
this.setState(
@@ -268,7 +283,7 @@ export function _mapStateToProps(state: IReduxState) {
_enableInsecureRoomNameWarning: state['features/base/config'].enableInsecureRoomNameWarning || false,
_moderatedRoomServiceUrl: state['features/base/config'].moderatedRoomServiceUrl,
_recentListEnabled: isRecentListEnabled(),
_room: state['features/base/conference'].room,
_room: state['features/base/conference'].room ?? '',
_settings: state['features/base/settings']
};
}

View File

@@ -1,37 +1,40 @@
// @flow
import React, { useCallback, useEffect, useState } from 'react';
/**
* The type of the React {@code Component} props of {@link Tabs}.
*/
type Props = {
interface IProps {
/**
* Accessibility label for the tabs container.
*
*/
accessibilityLabel: string,
accessibilityLabel: string;
/**
* Tabs information.
*/
tabs: Object
};
tabs: {
content: any;
id: string;
label: string;
}[];
}
/**
* A React component that implements tabs.
*
* @returns {ReactElement} The component.
*/
const Tabs = ({ accessibilityLabel, tabs }: Props) => {
const Tabs = ({ accessibilityLabel, tabs }: IProps) => {
const [ current, setCurrent ] = useState(0);
const onClick = useCallback(index => event => {
const onClick = useCallback(index => (event: React.MouseEvent) => {
event.preventDefault();
setCurrent(index);
}, []);
const onKeyDown = useCallback(index => event => {
const onKeyDown = useCallback(index => (event: React.KeyboardEvent) => {
let newIndex = null;
if (event.key === 'ArrowLeft') {
@@ -52,6 +55,7 @@ const Tabs = ({ accessibilityLabel, tabs }: Props) => {
useEffect(() => {
// this test is needed to make sure the effect is triggered because of user actually changing tab
if (document.activeElement?.getAttribute('role') === 'tab') {
// @ts-ignore
document.querySelector(`#${`${tabs[current].id}-tab`}`)?.focus();
}

View File

@@ -1,5 +1,3 @@
/* global interfaceConfig */
import React from 'react';
import { connect } from 'react-redux';
@@ -13,7 +11,7 @@ import RecentList from '../../recent-list/components/RecentList.web';
import SettingsButton from '../../settings/components/web/SettingsButton';
import { SETTINGS_TABS } from '../../settings/constants';
import { AbstractWelcomePage, _mapStateToProps } from './AbstractWelcomePage';
import { AbstractWelcomePage, IProps, _mapStateToProps } from './AbstractWelcomePage';
import Tabs from './Tabs';
/**
@@ -28,7 +26,15 @@ export const ROOM_NAME_VALIDATE_PATTERN_STR = '^[^?&:\u0022\u0027%#]+$';
*
* @augments AbstractWelcomePage
*/
class WelcomePage extends AbstractWelcomePage {
class WelcomePage extends AbstractWelcomePage<IProps> {
_additionalContentRef: HTMLDivElement | null;
_additionalToolbarContentRef: HTMLDivElement | null;
_additionalCardRef: HTMLDivElement | null;
_roomInputRef: HTMLInputElement | null;
_additionalCardTemplate: HTMLTemplateElement | null;
_additionalContentTemplate: HTMLTemplateElement | null;
_additionalToolbarContentTemplate: HTMLTemplateElement | null;
/**
* Default values for {@code WelcomePage} component's properties.
*
@@ -44,7 +50,7 @@ class WelcomePage extends AbstractWelcomePage {
* @param {Object} props - The read-only properties with which the new
* instance is to be initialized.
*/
constructor(props) {
constructor(props: IProps) {
super(props);
this.state = {
@@ -83,7 +89,7 @@ class WelcomePage extends AbstractWelcomePage {
* @type {HTMLTemplateElement|null}
*/
this._additionalCardTemplate = document.getElementById(
'welcome-page-additional-card-template');
'welcome-page-additional-card-template') as HTMLTemplateElement;
/**
* The template to use as the main content for the welcome page. If
@@ -93,7 +99,7 @@ class WelcomePage extends AbstractWelcomePage {
* @type {HTMLTemplateElement|null}
*/
this._additionalContentTemplate = document.getElementById(
'welcome-page-additional-content-template');
'welcome-page-additional-content-template') as HTMLTemplateElement;
/**
* The template to use as the additional content for the welcome page header toolbar.
@@ -104,7 +110,7 @@ class WelcomePage extends AbstractWelcomePage {
*/
this._additionalToolbarContentTemplate = document.getElementById(
'settings-toolbar-additional-content-template'
);
) as HTMLTemplateElement;
// Bind event handlers so they are only bound once per instance.
this._onFormSubmit = this._onFormSubmit.bind(this);
@@ -136,19 +142,19 @@ class WelcomePage extends AbstractWelcomePage {
}
if (this._shouldShowAdditionalContent()) {
this._additionalContentRef.appendChild(
this._additionalContentTemplate.content.cloneNode(true));
this._additionalContentRef?.appendChild(
this._additionalContentTemplate?.content.cloneNode(true) as Node);
}
if (this._shouldShowAdditionalToolbarContent()) {
this._additionalToolbarContentRef.appendChild(
this._additionalToolbarContentTemplate.content.cloneNode(true)
this._additionalToolbarContentRef?.appendChild(
this._additionalToolbarContentTemplate?.content.cloneNode(true) as Node
);
}
if (this._shouldShowAdditionalCard()) {
this._additionalCardRef.appendChild(
this._additionalCardTemplate.content.cloneNode(true)
this._additionalCardRef?.appendChild(
this._additionalCardTemplate?.content.cloneNode(true) as Node
);
}
}
@@ -236,7 +242,7 @@ class WelcomePage extends AbstractWelcomePage {
className = 'welcome-page-button'
id = 'enter_room_button'
onClick = { this._onFormSubmit }
tabIndex = '0'
tabIndex = { 0 }
type = 'button'>
{ t('welcomepage.startMeeting') }
</button>
@@ -301,7 +307,7 @@ class WelcomePage extends AbstractWelcomePage {
* @private
* @returns {void}
*/
_onFormSubmit(event) {
_onFormSubmit(event: React.FormEvent) {
event.preventDefault();
if (!this._roomInputRef || this._roomInputRef.reportValidity()) {
@@ -319,7 +325,9 @@ class WelcomePage extends AbstractWelcomePage {
* the EventTarget.
* @protected
*/
_onRoomChange(event) {
// @ts-ignore
// eslint-disable-next-line require-jsdoc
_onRoomChange(event: React.ChangeEvent<HTMLInputElement>) {
super._onRoomChange(event.target.value);
}
@@ -332,8 +340,9 @@ class WelcomePage extends AbstractWelcomePage {
const {
t,
_deeplinkingCfg: {
ios = {},
android = {}
ios = { downloadLink: undefined },
android = { fDroidUrl: undefined,
downloadLink: undefined }
}
} = this.props;
@@ -424,7 +433,7 @@ class WelcomePage extends AbstractWelcomePage {
* @private
* @returns {void}
*/
_setAdditionalCardRef(el) {
_setAdditionalCardRef(el: HTMLDivElement) {
this._additionalCardRef = el;
}
@@ -437,7 +446,7 @@ class WelcomePage extends AbstractWelcomePage {
* @private
* @returns {void}
*/
_setAdditionalContentRef(el) {
_setAdditionalContentRef(el: HTMLDivElement) {
this._additionalContentRef = el;
}
@@ -450,7 +459,7 @@ class WelcomePage extends AbstractWelcomePage {
* @private
* @returns {void}
*/
_setAdditionalToolbarContentRef(el) {
_setAdditionalToolbarContentRef(el: HTMLDivElement) {
this._additionalToolbarContentRef = el;
}
@@ -462,7 +471,7 @@ class WelcomePage extends AbstractWelcomePage {
* @private
* @returns {void}
*/
_setRoomInputRef(el) {
_setRoomInputRef(el: HTMLInputElement) {
this._roomInputRef = el;
}