diff --git a/globals.d.ts b/globals.d.ts index 78eb1b7291..3b5a68941f 100644 --- a/globals.d.ts +++ b/globals.d.ts @@ -21,6 +21,7 @@ declare global { JitsiMeetElectron?: any; // selenium tests handler _sharedVideoPlayer: any; + alwaysOnTop: { api: any }; } interface Document { diff --git a/react/features/always-on-top/AlwaysOnTop.js b/react/features/always-on-top/AlwaysOnTop.tsx similarity index 91% rename from react/features/always-on-top/AlwaysOnTop.js rename to react/features/always-on-top/AlwaysOnTop.tsx index 53a62887c8..dfabac2fab 100644 --- a/react/features/always-on-top/AlwaysOnTop.js +++ b/react/features/always-on-top/AlwaysOnTop.tsx @@ -1,5 +1,3 @@ -// @flow - import React, { Component } from 'react'; // We need to reference these files directly to avoid loading things that are not available @@ -19,15 +17,15 @@ const TOOLBAR_TIMEOUT = 4000; /** * The type of the React {@code Component} state of {@link AlwaysOnTop}. */ -type State = { - avatarURL: string, - customAvatarBackgrounds: Array, - displayName: string, - formattedDisplayName: string, - isVideoDisplayed: boolean, - userID: string, - visible: boolean -}; +interface IState { + avatarURL: string; + customAvatarBackgrounds: Array; + displayName: string; + formattedDisplayName: string; + isVideoDisplayed: boolean; + userID: string; + visible: boolean; +} /** * Represents the always on top page. @@ -35,7 +33,7 @@ type State = { * @class AlwaysOnTop * @augments Component */ -export default class AlwaysOnTop extends Component<*, State> { +export default class AlwaysOnTop extends Component { _hovered: boolean; /** @@ -44,7 +42,7 @@ export default class AlwaysOnTop extends Component<*, State> { * @param {*} props - The read-only properties with which the new instance * is to be initialized. */ - constructor(props: *) { + constructor(props: any) { super(props); this.state = { @@ -68,28 +66,25 @@ export default class AlwaysOnTop extends Component<*, State> { this._onMouseOver = this._onMouseOver.bind(this); } - _avatarChangedListener: () => void; - /** * Handles avatar changed api events. * * @returns {void} */ - _avatarChangedListener({ avatarURL, id }) { + _avatarChangedListener({ avatarURL, id }: { avatarURL: string; id: string; }) { if (api._getOnStageParticipant() === id && avatarURL !== this.state.avatarURL) { this.setState({ avatarURL }); } } - _displayNameChangedListener: () => void; - /** * Handles display name changed api events. * * @returns {void} */ - _displayNameChangedListener({ displayname, formattedDisplayName, id }) { + _displayNameChangedListener({ displayname, formattedDisplayName, id }: { displayname: string; + formattedDisplayName: string; id: string; }) { if (api._getOnStageParticipant() === id && (formattedDisplayName !== this.state.formattedDisplayName || displayname !== this.state.displayName)) { @@ -118,8 +113,6 @@ export default class AlwaysOnTop extends Component<*, State> { TOOLBAR_TIMEOUT); } - _videoChangedListener: () => void; - /** * Handles large video changed api events. * @@ -141,8 +134,6 @@ export default class AlwaysOnTop extends Component<*, State> { }); } - _mouseMove: () => void; - /** * Handles mouse move events. * @@ -152,8 +143,6 @@ export default class AlwaysOnTop extends Component<*, State> { this.state.visible || this.setState({ visible: true }); } - _onMouseOut: () => void; - /** * Toolbar mouse out handler. * @@ -163,8 +152,6 @@ export default class AlwaysOnTop extends Component<*, State> { this._hovered = false; } - _onMouseOver: () => void; - /** * Toolbar mouse over handler. * @@ -229,7 +216,7 @@ export default class AlwaysOnTop extends Component<*, State> { this._hideToolbarAfterTimeout(); api.getCustomAvatarBackgrounds() - .then(res => + .then((res: { avatarBackgrounds?: string[]; }) => this.setState({ customAvatarBackgrounds: res.avatarBackgrounds || [] })) @@ -242,7 +229,7 @@ export default class AlwaysOnTop extends Component<*, State> { * @inheritdoc * @returns {void} */ - componentDidUpdate(prevProps: *, prevState: State) { + componentDidUpdate(_prevProps: any, prevState: IState) { if (!prevState.visible && this.state.visible) { this._hideToolbarAfterTimeout(); } diff --git a/react/features/always-on-top/AudioMuteButton.js b/react/features/always-on-top/AudioMuteButton.tsx similarity index 85% rename from react/features/always-on-top/AudioMuteButton.js rename to react/features/always-on-top/AudioMuteButton.tsx index f1a6169095..7e034096b4 100644 --- a/react/features/always-on-top/AudioMuteButton.js +++ b/react/features/always-on-top/AudioMuteButton.tsx @@ -1,11 +1,9 @@ -// @flow - import React, { Component } from 'react'; // We need to reference these files directly to avoid loading things that are not available // in this environment (e.g. JitsiMeetJS or interfaceConfig) import { IconMic, IconMicSlash } from '../base/icons/svg'; -import type { Props } from '../base/toolbox/components/AbstractButton'; +import { IProps } from '../base/toolbox/components/AbstractButton'; import ToolbarButton from './ToolbarButton'; @@ -14,23 +12,25 @@ const { api } = window.alwaysOnTop; /** * The type of the React {@code Component} state of {@link AudioMuteButton}. */ -type State = { +interface IState { /** * Whether audio is available is not. */ - audioAvailable: boolean, + audioAvailable: boolean; /** * Whether audio is muted or not. */ - audioMuted: boolean -}; + audioMuted: boolean; +} + +type Props = Partial; /** * Stateless "mute/unmute audio" button for the Always-on-Top windows. */ -export default class AudioMuteButton extends Component { +export default class AudioMuteButton extends Component { icon = IconMic; toggledIcon = IconMicSlash; accessibilityLabel = 'Audio mute'; @@ -38,7 +38,7 @@ export default class AudioMuteButton extends Component { /** * Initializes a new {@code AudioMuteButton} instance. * - * @param {Props} props - The React {@code Component} props to initialize + * @param {IProps} props - The React {@code Component} props to initialize * the new {@code AudioMuteButton} instance with. */ constructor(props: Props) { @@ -94,27 +94,23 @@ export default class AudioMuteButton extends Component { this._audioMutedListener); } - _audioAvailabilityListener: ({ available: boolean }) => void; - /** * Handles audio available api events. * * @param {{ available: boolean }} status - The new available status. * @returns {void} */ - _audioAvailabilityListener({ available }) { + _audioAvailabilityListener({ available }: { available: boolean; }) { this.setState({ audioAvailable: available }); } - _audioMutedListener: ({ muted: boolean }) => void; - /** * Handles audio muted api events. * * @param {{ muted: boolean }} status - The new muted status. * @returns {void} */ - _audioMutedListener({ muted }) { + _audioMutedListener({ muted }: { muted: boolean; }) { this.setState({ audioMuted: muted }); } @@ -144,16 +140,14 @@ export default class AudioMuteButton extends Component { * Changes the muted state. * * @override - * @param {boolean} audioMuted - Whether audio should be muted or not. + * @param {boolean} _audioMuted - Whether audio should be muted or not. * @protected * @returns {void} */ - _setAudioMuted(audioMuted: boolean) { // eslint-disable-line no-unused-vars + _setAudioMuted(_audioMuted: boolean) { this.state.audioAvailable && api.executeCommand('toggleAudio'); } - _onClick: () => {}; - /** * Handles clicking / pressing the button, and toggles the audio mute state * accordingly. diff --git a/react/features/always-on-top/HangupButton.js b/react/features/always-on-top/HangupButton.tsx similarity index 84% rename from react/features/always-on-top/HangupButton.js rename to react/features/always-on-top/HangupButton.tsx index 3f39937022..1168d8c30d 100644 --- a/react/features/always-on-top/HangupButton.js +++ b/react/features/always-on-top/HangupButton.tsx @@ -1,19 +1,20 @@ -// @flow import React, { Component } from 'react'; // We need to reference these files directly to avoid loading things that are not available // in this environment (e.g. JitsiMeetJS or interfaceConfig) import { IconHangup } from '../base/icons/svg'; -import type { Props } from '../base/toolbox/components/AbstractButton'; +import { IProps } from '../base/toolbox/components/AbstractButton'; import ToolbarButton from './ToolbarButton'; const { api } = window.alwaysOnTop; +type Props = Partial; + /** * Stateless hangup button for the Always-on-Top windows. */ -export default class HangupButton extends Component { +export default class HangupButton extends Component { accessibilityLabel = 'Hangup'; icon = IconHangup; @@ -21,7 +22,7 @@ export default class HangupButton extends Component { /** * Initializes a new {@code HangupButton} instance. * - * @param {Props} props - The React {@code Component} props to initialize + * @param {IProps} props - The React {@code Component} props to initialize * the new {@code HangupButton} instance with. */ constructor(props: Props) { @@ -31,8 +32,6 @@ export default class HangupButton extends Component { this._onClick = this._onClick.bind(this); } - _onClick: () => {}; - /** * Handles clicking / pressing the button, and disconnects the conference. * diff --git a/react/features/always-on-top/Toolbar.js b/react/features/always-on-top/Toolbar.tsx similarity index 86% rename from react/features/always-on-top/Toolbar.js rename to react/features/always-on-top/Toolbar.tsx index 8594bbd1b1..845e9fdddc 100644 --- a/react/features/always-on-top/Toolbar.js +++ b/react/features/always-on-top/Toolbar.tsx @@ -1,5 +1,3 @@ -// @flow - import React, { Component } from 'react'; import AudioMuteButton from './AudioMuteButton'; @@ -9,30 +7,30 @@ import VideoMuteButton from './VideoMuteButton'; /** * The type of the React {@code Component} props of {@link Toolbar}. */ -type Props = { +interface IProps { /** * Additional CSS class names to add to the root of the toolbar. */ - className: string, + className: string; /** * Callback invoked when no longer moused over the toolbar. */ - onMouseOut: Function, + onMouseOut: (e?: React.MouseEvent) => void; /** * Callback invoked when the mouse has moved over the toolbar. */ - onMouseOver: Function -}; + onMouseOver: (e?: React.MouseEvent) => void; +} /** * Represents the toolbar in the Always On Top window. * * @augments Component */ -export default class Toolbar extends Component { +export default class Toolbar extends Component { /** * Implements React's {@link Component#render()}. * diff --git a/react/features/always-on-top/ToolbarButton.js b/react/features/always-on-top/ToolbarButton.tsx similarity index 87% rename from react/features/always-on-top/ToolbarButton.js rename to react/features/always-on-top/ToolbarButton.tsx index 831b985bf8..5bcafacfc8 100644 --- a/react/features/always-on-top/ToolbarButton.js +++ b/react/features/always-on-top/ToolbarButton.tsx @@ -2,38 +2,38 @@ import React, { useCallback } from 'react'; import Icon from '../base/icons/components/Icon'; -type Props = { +interface IProps { /** * Accessibility label for button. */ - accessibilityLabel: string, + accessibilityLabel: string; /** * An extra class name to be added at the end of the element's class name * in order to enable custom styling. */ - customClass?: string, + customClass?: string; /** * Whether or not the button is disabled. */ - disabled?: boolean, + disabled?: boolean; /** - * Click handler. + * Button icon. */ - onClick: Function, + icon: Function; /** - * Button icon. + * Click handler. */ - icon: Object, + onClick: (e?: React.MouseEvent) => void; /** * Whether or not the button is toggled. */ - toggled?: boolean + toggled?: boolean; } const ToolbarButton = ({ @@ -43,7 +43,7 @@ const ToolbarButton = ({ onClick, icon, toggled = false -}: Props) => { +}: IProps) => { const onKeyPress = useCallback(event => { if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); diff --git a/react/features/always-on-top/VideoMuteButton.js b/react/features/always-on-top/VideoMuteButton.tsx similarity index 88% rename from react/features/always-on-top/VideoMuteButton.js rename to react/features/always-on-top/VideoMuteButton.tsx index f3789e420b..0f8dc5719e 100644 --- a/react/features/always-on-top/VideoMuteButton.js +++ b/react/features/always-on-top/VideoMuteButton.tsx @@ -1,15 +1,16 @@ -// @flow import React, { Component } from 'react'; // We need to reference these files directly to avoid loading things that are not available // in this environment (e.g. JitsiMeetJS or interfaceConfig) import { IconVideo, IconVideoOff } from '../base/icons/svg'; -import type { Props } from '../base/toolbox/components/AbstractButton'; +import { IProps } from '../base/toolbox/components/AbstractButton'; import ToolbarButton from './ToolbarButton'; const { api } = window.alwaysOnTop; +type Props = Partial; + /** * The type of the React {@code Component} state of {@link VideoMuteButton}. */ @@ -18,12 +19,12 @@ type State = { /** * Whether video is available is not. */ - videoAvailable: boolean, + videoAvailable: boolean; /** * Whether video is muted or not. */ - videoMuted: boolean + videoMuted: boolean; }; /** @@ -119,40 +120,34 @@ export default class VideoMuteButton extends Component { * Changes the muted state. * * @override - * @param {boolean} videoMuted - Whether video should be muted or not. + * @param {boolean} _videoMuted - Whether video should be muted or not. * @protected * @returns {void} */ - _setVideoMuted(videoMuted: boolean) { // eslint-disable-line no-unused-vars + _setVideoMuted(_videoMuted: boolean) { this.state.videoAvailable && api.executeCommand('toggleVideo', false, true); } - _videoAvailabilityListener: ({ available: boolean }) => void; - /** * Handles video available api events. * * @param {{ available: boolean }} status - The new available status. * @returns {void} */ - _videoAvailabilityListener({ available }) { + _videoAvailabilityListener({ available }: { available: boolean; }) { this.setState({ videoAvailable: available }); } - _videoMutedListener: ({ muted: boolean }) => void; - /** * Handles video muted api events. * * @param {{ muted: boolean }} status - The new muted status. * @returns {void} */ - _videoMutedListener({ muted }) { + _videoMutedListener({ muted }: { muted: boolean; }) { this.setState({ videoMuted: muted }); } - _onClick: () => {}; - /** * Handles clicking / pressing the button, and toggles the video mute state * accordingly. diff --git a/react/features/always-on-top/index.js b/react/features/always-on-top/index.tsx similarity index 87% rename from react/features/always-on-top/index.js rename to react/features/always-on-top/index.tsx index 290298abfa..d493c4c306 100644 --- a/react/features/always-on-top/index.js +++ b/react/features/always-on-top/index.tsx @@ -1,14 +1,11 @@ -// @flow - import React from 'react'; import ReactDOM from 'react-dom'; import AlwaysOnTop from './AlwaysOnTop'; // Render the main/root Component. -// $FlowExpectedError ReactDOM.render(, document.getElementById('react')); window.addEventListener( 'beforeunload', - () => ReactDOM.unmountComponentAtNode(document.getElementById('react'))); + () => ReactDOM.unmountComponentAtNode(document.getElementById('react') ?? document.body)); diff --git a/tsconfig.native.json b/tsconfig.native.json index 885455f2f9..378e801fa9 100644 --- a/tsconfig.native.json +++ b/tsconfig.native.json @@ -16,6 +16,7 @@ }, "exclude": [ "node_modules", + "react/features/always-on-top", "react/features/analytics/handlers/GoogleAnalyticsHandler.ts", "react/features/base/components/participants-pane-list", "react/features/base/tooltip", diff --git a/webpack.config.js b/webpack.config.js index d5af9be5f4..d5146227ff 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -299,7 +299,7 @@ module.exports = (_env, argv) => { }), Object.assign({}, config, { entry: { - 'alwaysontop': './react/features/always-on-top/index.js' + 'alwaysontop': './react/features/always-on-top/index.tsx' }, plugins: [ ...config.plugins,