diff --git a/react/features/app/components/App.web.js b/react/features/app/components/App.web.tsx similarity index 96% rename from react/features/app/components/App.web.js rename to react/features/app/components/App.web.tsx index ba03db6faa..ed7ec0e1ec 100644 --- a/react/features/app/components/App.web.js +++ b/react/features/app/components/App.web.tsx @@ -43,7 +43,7 @@ export class App extends AbstractApp { * * @override */ - _createMainElement(component, props) { + _createMainElement(component: React.ComponentType, props: any) { return ( diff --git a/react/features/app/getRouteToRender.native.js b/react/features/app/getRouteToRender.native.ts similarity index 78% rename from react/features/app/getRouteToRender.native.js rename to react/features/app/getRouteToRender.native.ts index ef929dd4de..3e42f41b4e 100644 --- a/react/features/app/getRouteToRender.native.js +++ b/react/features/app/getRouteToRender.native.ts @@ -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} */ -export function _getRouteToRender() { +export function _getRouteToRender(_stateful: any) { return Promise.resolve(route); } diff --git a/react/features/app/getRouteToRender.web.js b/react/features/app/getRouteToRender.web.ts similarity index 82% rename from react/features/app/getRouteToRender.web.js rename to react/features/app/getRouteToRender.web.ts index 85d4089c2d..932ac1ab44 100644 --- a/react/features/app/getRouteToRender.web.js +++ b/react/features/app/getRouteToRender.web.ts @@ -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} */ -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} */ -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 diff --git a/react/features/app/middleware.js b/react/features/app/middleware.ts similarity index 89% rename from react/features/app/middleware.js rename to react/features/app/middleware.ts index 5d1ae78ad0..96dd8715ad 100644 --- a/react/features/app/middleware.js +++ b/react/features/app/middleware.ts @@ -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); diff --git a/react/features/base/app/components/BaseApp.tsx b/react/features/base/app/components/BaseApp.tsx index faf7a0cfbe..35b2c0145c 100644 --- a/react/features/base/app/components/BaseApp.tsx +++ b/react/features/base/app/components/BaseApp.tsx @@ -186,7 +186,7 @@ export default class BaseApp

extends Component { * @abstract * @protected */ - _createExtraElement() { + _createExtraElement(): React.ReactElement | null { return null; } @@ -280,5 +280,7 @@ export default class BaseApp

extends Component { * * @returns {React$Element} */ - _renderDialogContainer: () => React.ReactElement; + _renderDialogContainer(): React.ReactElement | null { + return null; + } } diff --git a/react/features/base/config/configType.ts b/react/features/base/config/configType.ts index 4a20a9ea0a..fbada63788 100644 --- a/react/features/base/config/configType.ts +++ b/react/features/base/config/configType.ts @@ -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[]; diff --git a/react/features/base/label/components/web/ExpandedLabel.js b/react/features/base/label/components/web/ExpandedLabel.ts similarity index 100% rename from react/features/base/label/components/web/ExpandedLabel.js rename to react/features/base/label/components/web/ExpandedLabel.ts diff --git a/react/features/base/popover/components/Popover.native.js b/react/features/base/popover/components/Popover.native.ts similarity index 100% rename from react/features/base/popover/components/Popover.native.js rename to react/features/base/popover/components/Popover.native.ts diff --git a/react/features/base/premeeting/components/web/Preview.js b/react/features/base/premeeting/components/web/Preview.tsx similarity index 73% rename from react/features/base/premeeting/components/web/Preview.js rename to react/features/base/premeeting/components/web/Preview.tsx index cbe58101d4..6491134601 100644 --- a/react/features/base/premeeting/components/web/Preview.js +++ b/react/features/base/premeeting/components/web/Preview.tsx @@ -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 }; } diff --git a/react/features/base/react/components/web/InlineDialogFailure.js b/react/features/base/react/components/web/InlineDialogFailure.tsx similarity index 88% rename from react/features/base/react/components/web/InlineDialogFailure.js rename to react/features/base/react/components/web/InlineDialogFailure.tsx index 1e0f14b925..7245ecbf4e 100644 --- a/react/features/base/react/components/web/InlineDialogFailure.js +++ b/react/features/base/react/components/web/InlineDialogFailure.tsx @@ -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 { +class InlineDialogFailure extends Component { /** * Renders the content of this component. * diff --git a/react/features/base/react/components/web/MeetingsList.js b/react/features/base/react/components/web/MeetingsList.tsx similarity index 87% rename from react/features/base/react/components/web/MeetingsList.js rename to react/features/base/react/components/web/MeetingsList.tsx index 6335209a41..5100fcde88 100644 --- a/react/features/base/react/components/web/MeetingsList.js +++ b/react/features/base/react/components/web/MeetingsList.tsx @@ -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, + 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 { +class MeetingsList extends Component { /** * 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 { 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 { * @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 { * @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 { * @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 { * @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 { }; } - _renderItem: (Object, number) => React$Node; - /** * Renders an item for the list. * @@ -214,7 +207,7 @@ class MeetingsList extends Component { * @param {number} index - The index of the item. * @returns {Node} */ - _renderItem(meeting, index) { + _renderItem(meeting: IMeeting, index: number) { const { date, duration, diff --git a/react/features/base/react/components/web/NavigateSectionListEmptyComponent.js b/react/features/base/react/components/web/NavigateSectionListEmptyComponent.ts similarity index 100% rename from react/features/base/react/components/web/NavigateSectionListEmptyComponent.js rename to react/features/base/react/components/web/NavigateSectionListEmptyComponent.ts diff --git a/react/features/base/react/components/web/NavigateSectionListItem.js b/react/features/base/react/components/web/NavigateSectionListItem.tsx similarity index 94% rename from react/features/base/react/components/web/NavigateSectionListItem.js rename to react/features/base/react/components/web/NavigateSectionListItem.tsx index 2857440ac2..8d46a69f93 100644 --- a/react/features/base/react/components/web/NavigateSectionListItem.js +++ b/react/features/base/react/components/web/NavigateSectionListItem.tsx @@ -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 +export default class NavigateSectionListItem

extends Component

{ /** diff --git a/react/features/base/react/components/web/NavigateSectionListSectionHeader.js b/react/features/base/react/components/web/NavigateSectionListSectionHeader.tsx similarity index 91% rename from react/features/base/react/components/web/NavigateSectionListSectionHeader.js rename to react/features/base/react/components/web/NavigateSectionListSectionHeader.tsx index 0ea11c7097..1cede21bb0 100644 --- a/react/features/base/react/components/web/NavigateSectionListSectionHeader.js +++ b/react/features/base/react/components/web/NavigateSectionListSectionHeader.tsx @@ -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 { +export default class NavigateSectionListSectionHeader extends Component { /** * Renders the content of this component. * diff --git a/react/features/base/react/components/web/SectionList.js b/react/features/base/react/components/web/SectionList.tsx similarity index 90% rename from react/features/base/react/components/web/SectionList.js rename to react/features/base/react/components/web/SectionList.tsx index 188a35dbce..0ff5c5ce51 100644 --- a/react/features/base/react/components/web/SectionList.js +++ b/react/features/base/react/components/web/SectionList.tsx @@ -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

, + 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
; +} /** * 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 { +export default class SectionList extends Component { /** * Renders the content of this component. * diff --git a/react/features/base/react/components/web/Watermarks.js b/react/features/base/react/components/web/Watermarks.tsx similarity index 90% rename from react/features/base/react/components/web/Watermarks.js rename to react/features/base/react/components/web/Watermarks.tsx index 7c760ab877..9c9a8c620b 100644 --- a/react/features/base/react/components/web/Watermarks.js +++ b/react/features/base/react/components/web/Watermarks.tsx @@ -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 { +class Watermarks extends Component { /** * 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 { }; reactElement = (
); if (_logoLink) { @@ -227,9 +219,9 @@ class Watermarks extends Component { * * @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)); diff --git a/react/features/base/responsive-ui/components/DimensionsDetector.web.js b/react/features/base/responsive-ui/components/DimensionsDetector.web.ts similarity index 100% rename from react/features/base/responsive-ui/components/DimensionsDetector.web.js rename to react/features/base/responsive-ui/components/DimensionsDetector.web.ts diff --git a/react/features/base/testing/components/TestHint.web.js b/react/features/base/testing/components/TestHint.web.ts similarity index 100% rename from react/features/base/testing/components/TestHint.web.js rename to react/features/base/testing/components/TestHint.web.ts diff --git a/react/features/base/ui/components/web/DialogContainer.tsx b/react/features/base/ui/components/web/DialogContainer.tsx index 4cbaa884e0..dd3780ccd9 100644 --- a/react/features/base/ui/components/web/DialogContainer.tsx +++ b/react/features/base/ui/components/web/DialogContainer.tsx @@ -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; - /** * True if the UI is in a compact state where we don't show dialogs. */ diff --git a/react/features/calendar-sync/components/AddMeetingUrlButton.web.js b/react/features/calendar-sync/components/AddMeetingUrlButton.web.tsx similarity index 84% rename from react/features/calendar-sync/components/AddMeetingUrlButton.web.js rename to react/features/calendar-sync/components/AddMeetingUrlButton.web.tsx index 3fc6c33159..a15874ed96 100644 --- a/react/features/calendar-sync/components/AddMeetingUrlButton.web.js +++ b/react/features/calendar-sync/components/AddMeetingUrlButton.web.tsx @@ -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, + 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 { +class AddMeetingUrlButton extends Component { /** * 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 { ); } - _onClick: () => void; - /** * Dispatches an action to adding a meeting URL to a calendar event. * @@ -91,8 +83,6 @@ class AddMeetingUrlButton extends Component { dispatch(updateCalendarEvent(eventId, calendarId)); } - _onKeyPress: (Object) => void; - /** * KeyPress handler for accessibility. * @@ -100,7 +90,7 @@ class AddMeetingUrlButton extends Component { * * @returns {void} */ - _onKeyPress(e) { + _onKeyPress(e: React.KeyboardEvent) { if (e.key === ' ' || e.key === 'Enter') { e.preventDefault(); this._onClick(); diff --git a/react/features/calendar-sync/components/CalendarList.web.js b/react/features/calendar-sync/components/CalendarList.web.tsx similarity index 89% rename from react/features/calendar-sync/components/CalendarList.web.js rename to react/features/calendar-sync/components/CalendarList.web.tsx index 6a57b61e6f..eaad42ddfe 100644 --- a/react/features/calendar-sync/components/CalendarList.web.js +++ b/react/features/calendar-sync/components/CalendarList.web.tsx @@ -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 { +class CalendarList extends AbstractPage { /** * 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 { return ( CalendarListContent ? : null @@ -102,7 +95,7 @@ class CalendarList extends AbstractPage { * @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 { ); } - _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 { ); } - _onOpenSettings: () => void; - /** * Opens {@code SettingsDialog}. * @@ -224,8 +213,6 @@ class CalendarList extends AbstractPage { this.props.dispatch(openSettingsDialog(SETTINGS_TABS.CALENDAR)); } - _onKeyPressOpenSettings: (Object) => void; - /** * KeyPress handler for accessibility. * @@ -233,15 +220,13 @@ class CalendarList extends AbstractPage { * * @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 { * _hasLoadedEvents: boolean * }} */ -function _mapStateToProps(state) { +function _mapStateToProps(state: IReduxState) { const { error, events, diff --git a/react/features/calendar-sync/components/CalendarListContent.web.js b/react/features/calendar-sync/components/CalendarListContent.web.tsx similarity index 84% rename from react/features/calendar-sync/components/CalendarListContent.web.js rename to react/features/calendar-sync/components/CalendarListContent.web.tsx index 7f2e14b383..9ab7b746cc 100644 --- a/react/features/calendar-sync/components/CalendarListContent.web.js +++ b/react/features/calendar-sync/components/CalendarListContent.web.tsx @@ -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, + _eventList: Array; /** * 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 { +class CalendarListContent extends Component { /** * Default values for the component's props. */ @@ -54,7 +53,7 @@ class CalendarListContent extends Component { * * @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 { ); } - _onJoinPress: (Object, string) => Function; - /** * Handles the list's navigate action. * @@ -103,14 +100,12 @@ class CalendarListContent extends Component { * @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 { * 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 { * @private * @returns {Object} */ - _toDisplayableItem(event) { + _toDisplayableItem(event: any) { return { elementAfter: event.url ? { * _eventList: Array * }} */ -function _mapStateToProps(state: Object) { +function _mapStateToProps(state: IReduxState) { return { _eventList: state['features/calendar-sync'].events }; diff --git a/react/features/calendar-sync/components/JoinButton.web.js b/react/features/calendar-sync/components/JoinButton.web.tsx similarity index 86% rename from react/features/calendar-sync/components/JoinButton.web.js rename to react/features/calendar-sync/components/JoinButton.web.tsx index 3caa39a475..f8452dffa2 100644 --- a/react/features/calendar-sync/components/JoinButton.web.js +++ b/react/features/calendar-sync/components/JoinButton.web.tsx @@ -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 { +class JoinButton extends Component { /** * Initializes a new {@code JoinButton} instance. @@ -41,7 +35,7 @@ class JoinButton extends Component { * @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 { ); } - _onClick: (Object) => void; - /** * Callback invoked when the component is clicked. * @@ -82,12 +74,10 @@ class JoinButton extends Component { * @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 { * * @returns {void} */ - _onKeyPress(e) { + _onKeyPress(e: React.KeyboardEvent) { if (e.key === ' ' || e.key === 'Enter') { e.preventDefault(); this._onClick(); diff --git a/react/features/calendar-sync/reducer.tsx b/react/features/calendar-sync/reducer.tsx index f31c453624..0b6950d639 100644 --- a/react/features/calendar-sync/reducer.tsx +++ b/react/features/calendar-sync/reducer.tsx @@ -28,7 +28,7 @@ const DEFAULT_STATE = { export interface ICalendarSyncState { authorization?: string; - error?: Object; + error?: { error: string; }; events: Array<{ calendarId: string; endDate: string; diff --git a/react/features/chrome-extension-banner/components/ChromeExtensionBanner.web.js b/react/features/chrome-extension-banner/components/ChromeExtensionBanner.web.tsx similarity index 90% rename from react/features/chrome-extension-banner/components/ChromeExtensionBanner.web.js rename to react/features/chrome-extension-banner/components/ChromeExtensionBanner.web.tsx index 1a28a68edf..be41282a49 100644 --- a/react/features/chrome-extension-banner/components/ChromeExtensionBanner.web.js +++ b/react/features/chrome-extension-banner/components/ChromeExtensionBanner.web.tsx @@ -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 { +class ChromeExtensionBanner extends PureComponent { + 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 { * * @inheritdoc */ - async componentDidUpdate(prevProps) { + async componentDidUpdate(prevProps: IProps) { if (!this._isSupportedEnvironment()) { return; } @@ -137,8 +137,7 @@ class ChromeExtensionBanner extends PureComponent { 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 { && !this.props.isVpaas; } - _onClosePressed: () => void; - /** * Closes the banner for the current session. * @@ -172,8 +169,6 @@ class ChromeExtensionBanner extends PureComponent { this.setState({ closePressed: true }); } - _onCloseKeyPress: (Object) => void; - /** * KeyPress handler for accessibility. * @@ -181,15 +176,13 @@ class ChromeExtensionBanner extends PureComponent { * * @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 { this.setState({ closePressed: true }); } - _onInstallExtensionKeyPress: (Object) => void; - /** * KeyPress handler for accessibility. * @@ -212,15 +203,13 @@ class ChromeExtensionBanner extends PureComponent { * * @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 { || 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) { this.setState({ dontShowAgainChecked: event.target.checked }); } @@ -258,7 +245,7 @@ class ChromeExtensionBanner extends PureComponent { * @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 { * @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) }; }; diff --git a/react/features/deep-linking/functions.ts b/react/features/deep-linking/functions.ts index a1c2e43c36..685fc5eac2 100644 --- a/react/features/deep-linking/functions.ts +++ b/react/features/deep-linking/functions.ts @@ -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. diff --git a/react/features/recent-list/components/RecentList.web.js b/react/features/recent-list/components/RecentList.web.tsx similarity index 80% rename from react/features/recent-list/components/RecentList.web.js rename to react/features/recent-list/components/RecentList.web.tsx index 379bad8d6c..35a6d2d299 100644 --- a/react/features/recent-list/components/RecentList.web.js +++ b/react/features/recent-list/components/RecentList.web.tsx @@ -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, - - /** - * The translate function. - */ - t: Function, +interface IProps extends WithTranslation { /** * The recent list from the Redux store. */ - _recentList: Array -}; + _recentList: Array; + + /** + * 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 { - _getRenderListEmptyComponent: () => React$Node; - _onPress: string => {}; - +class RecentList extends AbstractRecentList { /** * 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 { 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 { return ( { * _recentList: Array * }} */ -export function _mapStateToProps(state: Object) { +export function _mapStateToProps(state: IReduxState) { return { _recentList: state['features/recent-list'] }; diff --git a/react/features/unsupported-browser/components/DefaultUnsupportedDesktopBrowser.js b/react/features/unsupported-browser/components/DefaultUnsupportedDesktopBrowser.tsx similarity index 98% rename from react/features/unsupported-browser/components/DefaultUnsupportedDesktopBrowser.js rename to react/features/unsupported-browser/components/DefaultUnsupportedDesktopBrowser.tsx index c62c0fdaeb..68b3f9cb48 100644 --- a/react/features/unsupported-browser/components/DefaultUnsupportedDesktopBrowser.js +++ b/react/features/unsupported-browser/components/DefaultUnsupportedDesktopBrowser.tsx @@ -1,5 +1,3 @@ -/* @flow */ - import React, { Component } from 'react'; /** diff --git a/react/features/unsupported-browser/components/UnsupportedDesktopBrowser.js b/react/features/unsupported-browser/components/UnsupportedDesktopBrowser.ts similarity index 100% rename from react/features/unsupported-browser/components/UnsupportedDesktopBrowser.js rename to react/features/unsupported-browser/components/UnsupportedDesktopBrowser.ts diff --git a/react/features/welcome/components/AbstractWelcomePage.ts b/react/features/welcome/components/AbstractWelcomePage.ts index c6b5929f91..dd12714264 100644 --- a/react/features/welcome/components/AbstractWelcomePage.ts +++ b/react/features/welcome/components/AbstractWelcomePage.ts @@ -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

extends Component

{ +export class AbstractWelcomePage

extends Component { _mounted: boolean | undefined; /** @@ -81,6 +93,7 @@ export class AbstractWelcomePage

extends Component

{ state = { animateTimeoutId: undefined, generatedRoomName: '', + generateRoomNames: undefined, insecureRoomName: false, joining: false, room: '', @@ -142,7 +155,7 @@ export class AbstractWelcomePage

extends Component

{ if (word.length > 1) { animateTimeoutId - = setTimeout( + = window.setTimeout( () => { this._animateRoomNameChanging( word.substring(1, word.length)); @@ -171,7 +184,9 @@ export class AbstractWelcomePage

extends Component

{ * * @returns {ReactElement} */ - _doRenderInsecureRoomNameWarning: () => React.Component; + _doRenderInsecureRoomNameWarning(): JSX.Element | null { + return null; + } /** * Handles joining. Either by clicking on 'Join' button @@ -213,7 +228,7 @@ export class AbstractWelcomePage

extends Component

{ _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

extends Component

{ _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'] }; } diff --git a/react/features/welcome/components/BlankPage.web.js b/react/features/welcome/components/BlankPage.web.ts similarity index 100% rename from react/features/welcome/components/BlankPage.web.js rename to react/features/welcome/components/BlankPage.web.ts diff --git a/react/features/welcome/components/Tabs.js b/react/features/welcome/components/Tabs.tsx similarity index 89% rename from react/features/welcome/components/Tabs.js rename to react/features/welcome/components/Tabs.tsx index 70ba7ac9e4..f7b4d8e1ee 100644 --- a/react/features/welcome/components/Tabs.js +++ b/react/features/welcome/components/Tabs.tsx @@ -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(); } diff --git a/react/features/welcome/components/WelcomePage.web.js b/react/features/welcome/components/WelcomePage.web.tsx similarity index 90% rename from react/features/welcome/components/WelcomePage.web.js rename to react/features/welcome/components/WelcomePage.web.tsx index 8d850c41c0..07b2a68982 100644 --- a/react/features/welcome/components/WelcomePage.web.js +++ b/react/features/welcome/components/WelcomePage.web.tsx @@ -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 { + _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') } @@ -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) { 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; }