Rewrite <CallScreen> component with hooks
This commit is contained in:
parent
05a91a100f
commit
8073ccd32c
2 changed files with 251 additions and 325 deletions
|
@ -1,4 +1,5 @@
|
||||||
import React from 'react';
|
import React, { useState, useRef, useEffect, useCallback } from 'react';
|
||||||
|
import { noop } from 'lodash';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import {
|
import {
|
||||||
CallDetailsType,
|
CallDetailsType,
|
||||||
|
@ -29,329 +30,269 @@ export type PropsType = {
|
||||||
toggleSettings: () => void;
|
toggleSettings: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
type StateType = {
|
export const CallScreen: React.FC<PropsType> = ({
|
||||||
acceptedDuration: number | null;
|
callDetails,
|
||||||
showControls: boolean;
|
callState,
|
||||||
};
|
hangUp,
|
||||||
|
hasLocalAudio,
|
||||||
|
hasLocalVideo,
|
||||||
|
hasRemoteVideo,
|
||||||
|
i18n,
|
||||||
|
setLocalAudio,
|
||||||
|
setLocalVideo,
|
||||||
|
setLocalPreview,
|
||||||
|
setRendererCanvas,
|
||||||
|
togglePip,
|
||||||
|
toggleSettings,
|
||||||
|
}) => {
|
||||||
|
const { acceptedTime, callId } = callDetails || {};
|
||||||
|
|
||||||
export class CallScreen extends React.Component<PropsType, StateType> {
|
const toggleAudio = useCallback(() => {
|
||||||
private interval: NodeJS.Timeout | null;
|
if (!callId) {
|
||||||
|
|
||||||
private controlsFadeTimer: NodeJS.Timeout | null;
|
|
||||||
|
|
||||||
private readonly localVideoRef: React.RefObject<HTMLVideoElement>;
|
|
||||||
|
|
||||||
private readonly remoteVideoRef: React.RefObject<HTMLCanvasElement>;
|
|
||||||
|
|
||||||
constructor(props: PropsType) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
acceptedDuration: null,
|
|
||||||
showControls: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.interval = null;
|
|
||||||
this.controlsFadeTimer = null;
|
|
||||||
this.localVideoRef = React.createRef();
|
|
||||||
this.remoteVideoRef = React.createRef();
|
|
||||||
}
|
|
||||||
|
|
||||||
public componentDidMount(): void {
|
|
||||||
const { setLocalPreview, setRendererCanvas } = this.props;
|
|
||||||
|
|
||||||
// It's really jump with a value of 500ms.
|
|
||||||
this.interval = setInterval(this.updateAcceptedTimer, 100);
|
|
||||||
this.fadeControls();
|
|
||||||
|
|
||||||
document.addEventListener('keydown', this.handleKeyDown);
|
|
||||||
|
|
||||||
setLocalPreview({ element: this.localVideoRef });
|
|
||||||
setRendererCanvas({ element: this.remoteVideoRef });
|
|
||||||
}
|
|
||||||
|
|
||||||
public componentWillUnmount(): void {
|
|
||||||
const { setLocalPreview, setRendererCanvas } = this.props;
|
|
||||||
|
|
||||||
document.removeEventListener('keydown', this.handleKeyDown);
|
|
||||||
|
|
||||||
if (this.interval) {
|
|
||||||
clearInterval(this.interval);
|
|
||||||
}
|
|
||||||
if (this.controlsFadeTimer) {
|
|
||||||
clearTimeout(this.controlsFadeTimer);
|
|
||||||
}
|
|
||||||
|
|
||||||
setLocalPreview({ element: undefined });
|
|
||||||
setRendererCanvas({ element: undefined });
|
|
||||||
}
|
|
||||||
|
|
||||||
updateAcceptedTimer = (): void => {
|
|
||||||
const { callDetails } = this.props;
|
|
||||||
|
|
||||||
if (!callDetails) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (callDetails.acceptedTime) {
|
|
||||||
this.setState({
|
|
||||||
acceptedDuration: Date.now() - callDetails.acceptedTime,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
handleKeyDown = (event: KeyboardEvent): void => {
|
|
||||||
const { callDetails } = this.props;
|
|
||||||
|
|
||||||
if (!callDetails) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let eventHandled = false;
|
|
||||||
|
|
||||||
if (event.shiftKey && (event.key === 'V' || event.key === 'v')) {
|
|
||||||
this.toggleVideo();
|
|
||||||
eventHandled = true;
|
|
||||||
} else if (event.shiftKey && (event.key === 'M' || event.key === 'm')) {
|
|
||||||
this.toggleAudio();
|
|
||||||
eventHandled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (eventHandled) {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
this.showControls();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
showControls = (): void => {
|
|
||||||
const { showControls } = this.state;
|
|
||||||
|
|
||||||
if (!showControls) {
|
|
||||||
this.setState({
|
|
||||||
showControls: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.fadeControls();
|
|
||||||
};
|
|
||||||
|
|
||||||
fadeControls = (): void => {
|
|
||||||
if (this.controlsFadeTimer) {
|
|
||||||
clearTimeout(this.controlsFadeTimer);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.controlsFadeTimer = setTimeout(() => {
|
|
||||||
this.setState({
|
|
||||||
showControls: false,
|
|
||||||
});
|
|
||||||
}, 5000);
|
|
||||||
};
|
|
||||||
|
|
||||||
toggleAudio = (): void => {
|
|
||||||
const { callDetails, hasLocalAudio, setLocalAudio } = this.props;
|
|
||||||
|
|
||||||
if (!callDetails) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setLocalAudio({
|
setLocalAudio({
|
||||||
callId: callDetails.callId,
|
callId,
|
||||||
enabled: !hasLocalAudio,
|
enabled: !hasLocalAudio,
|
||||||
});
|
});
|
||||||
};
|
}, [callId, setLocalAudio, hasLocalAudio]);
|
||||||
|
|
||||||
toggleVideo = (): void => {
|
const toggleVideo = useCallback(() => {
|
||||||
const { callDetails, hasLocalVideo, setLocalVideo } = this.props;
|
if (!callId) {
|
||||||
|
|
||||||
if (!callDetails) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setLocalVideo({ callId: callDetails.callId, enabled: !hasLocalVideo });
|
setLocalVideo({
|
||||||
};
|
callId,
|
||||||
|
enabled: !hasLocalVideo,
|
||||||
public render(): JSX.Element | null {
|
|
||||||
const {
|
|
||||||
callDetails,
|
|
||||||
callState,
|
|
||||||
hangUp,
|
|
||||||
hasLocalAudio,
|
|
||||||
hasLocalVideo,
|
|
||||||
hasRemoteVideo,
|
|
||||||
i18n,
|
|
||||||
togglePip,
|
|
||||||
toggleSettings,
|
|
||||||
} = this.props;
|
|
||||||
const { showControls } = this.state;
|
|
||||||
const isAudioOnly = !hasLocalVideo && !hasRemoteVideo;
|
|
||||||
|
|
||||||
if (!callDetails || !callState) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const controlsFadeClass = classNames({
|
|
||||||
'module-ongoing-call__controls--fadeIn':
|
|
||||||
(showControls || isAudioOnly) && callState !== CallState.Accepted,
|
|
||||||
'module-ongoing-call__controls--fadeOut':
|
|
||||||
!showControls && !isAudioOnly && callState === CallState.Accepted,
|
|
||||||
});
|
});
|
||||||
|
}, [callId, setLocalVideo, hasLocalVideo]);
|
||||||
|
|
||||||
const videoButtonType = hasLocalVideo
|
const [acceptedDuration, setAcceptedDuration] = useState<number | null>(null);
|
||||||
? CallingButtonType.VIDEO_ON
|
const [showControls, setShowControls] = useState(true);
|
||||||
: CallingButtonType.VIDEO_OFF;
|
|
||||||
const audioButtonType = hasLocalAudio
|
|
||||||
? CallingButtonType.AUDIO_ON
|
|
||||||
: CallingButtonType.AUDIO_OFF;
|
|
||||||
|
|
||||||
return (
|
const localVideoRef = useRef<HTMLVideoElement | null>(null);
|
||||||
|
const remoteVideoRef = useRef<HTMLCanvasElement | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setLocalPreview({ element: localVideoRef });
|
||||||
|
setRendererCanvas({ element: remoteVideoRef });
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
setLocalPreview({ element: undefined });
|
||||||
|
setRendererCanvas({ element: undefined });
|
||||||
|
};
|
||||||
|
}, [setLocalPreview, setRendererCanvas]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!acceptedTime) {
|
||||||
|
return noop;
|
||||||
|
}
|
||||||
|
// It's really jumpy with a value of 500ms.
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
setAcceptedDuration(Date.now() - acceptedTime);
|
||||||
|
}, 100);
|
||||||
|
return clearInterval.bind(null, interval);
|
||||||
|
}, [acceptedTime]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!showControls) {
|
||||||
|
return noop;
|
||||||
|
}
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
setShowControls(false);
|
||||||
|
}, 5000);
|
||||||
|
return clearInterval.bind(null, timer);
|
||||||
|
}, [showControls]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleKeyDown = (event: KeyboardEvent): void => {
|
||||||
|
let eventHandled = false;
|
||||||
|
|
||||||
|
if (event.shiftKey && (event.key === 'V' || event.key === 'v')) {
|
||||||
|
toggleVideo();
|
||||||
|
eventHandled = true;
|
||||||
|
} else if (event.shiftKey && (event.key === 'M' || event.key === 'm')) {
|
||||||
|
toggleAudio();
|
||||||
|
eventHandled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eventHandled) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
setShowControls(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('keydown', handleKeyDown);
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('keydown', handleKeyDown);
|
||||||
|
};
|
||||||
|
}, [toggleAudio, toggleVideo]);
|
||||||
|
|
||||||
|
const isAudioOnly = !hasLocalVideo && !hasRemoteVideo;
|
||||||
|
|
||||||
|
if (!callDetails || !callState) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const controlsFadeClass = classNames({
|
||||||
|
'module-ongoing-call__controls--fadeIn':
|
||||||
|
(showControls || isAudioOnly) && callState !== CallState.Accepted,
|
||||||
|
'module-ongoing-call__controls--fadeOut':
|
||||||
|
!showControls && !isAudioOnly && callState === CallState.Accepted,
|
||||||
|
});
|
||||||
|
|
||||||
|
const videoButtonType = hasLocalVideo
|
||||||
|
? CallingButtonType.VIDEO_ON
|
||||||
|
: CallingButtonType.VIDEO_OFF;
|
||||||
|
const audioButtonType = hasLocalAudio
|
||||||
|
? CallingButtonType.AUDIO_ON
|
||||||
|
: CallingButtonType.AUDIO_OFF;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="module-calling__container"
|
||||||
|
onMouseMove={() => {
|
||||||
|
setShowControls(true);
|
||||||
|
}}
|
||||||
|
role="group"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
className="module-calling__container"
|
className={classNames(
|
||||||
onMouseMove={this.showControls}
|
'module-calling__header',
|
||||||
role="group"
|
'module-ongoing-call__header',
|
||||||
|
controlsFadeClass
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<div
|
<div className="module-calling__header--header-name">
|
||||||
className={classNames(
|
{callDetails.title}
|
||||||
'module-calling__header',
|
|
||||||
'module-ongoing-call__header',
|
|
||||||
controlsFadeClass
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div className="module-calling__header--header-name">
|
|
||||||
{callDetails.title}
|
|
||||||
</div>
|
|
||||||
{this.renderMessage(callState)}
|
|
||||||
<div className="module-calling-tools">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
aria-label={i18n('callingDeviceSelection__settings')}
|
|
||||||
className="module-calling-tools__button module-calling-button__settings"
|
|
||||||
onClick={toggleSettings}
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
aria-label={i18n('calling__pip')}
|
|
||||||
className="module-calling-tools__button module-calling-button__pip"
|
|
||||||
onClick={togglePip}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{hasRemoteVideo
|
{renderHeaderMessage(i18n, callState, acceptedDuration)}
|
||||||
? this.renderRemoteVideo()
|
<div className="module-calling-tools">
|
||||||
: this.renderAvatar(callDetails)}
|
<button
|
||||||
{hasLocalVideo ? this.renderLocalVideo() : null}
|
type="button"
|
||||||
<div
|
aria-label={i18n('callingDeviceSelection__settings')}
|
||||||
className={classNames(
|
className="module-calling-tools__button module-calling-button__settings"
|
||||||
'module-ongoing-call__actions',
|
onClick={toggleSettings}
|
||||||
controlsFadeClass
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<CallingButton
|
|
||||||
buttonType={videoButtonType}
|
|
||||||
i18n={i18n}
|
|
||||||
onClick={this.toggleVideo}
|
|
||||||
tooltipDistance={24}
|
|
||||||
/>
|
/>
|
||||||
<CallingButton
|
<button
|
||||||
buttonType={audioButtonType}
|
type="button"
|
||||||
i18n={i18n}
|
aria-label={i18n('calling__pip')}
|
||||||
onClick={this.toggleAudio}
|
className="module-calling-tools__button module-calling-button__pip"
|
||||||
tooltipDistance={24}
|
onClick={togglePip}
|
||||||
/>
|
|
||||||
<CallingButton
|
|
||||||
buttonType={CallingButtonType.HANG_UP}
|
|
||||||
i18n={i18n}
|
|
||||||
onClick={() => {
|
|
||||||
hangUp({ callId: callDetails.callId });
|
|
||||||
}}
|
|
||||||
tooltipDistance={24}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
{hasRemoteVideo ? (
|
||||||
}
|
<canvas
|
||||||
|
className="module-ongoing-call__remote-video-enabled"
|
||||||
private renderAvatar(callDetails: CallDetailsType) {
|
ref={remoteVideoRef}
|
||||||
const { i18n } = this.props;
|
/>
|
||||||
const {
|
) : (
|
||||||
avatarPath,
|
renderAvatar(i18n, callDetails)
|
||||||
color,
|
)}
|
||||||
name,
|
{hasLocalVideo && (
|
||||||
phoneNumber,
|
<video
|
||||||
profileName,
|
className="module-ongoing-call__local-video"
|
||||||
title,
|
ref={localVideoRef}
|
||||||
} = callDetails;
|
autoPlay
|
||||||
return (
|
/>
|
||||||
<div className="module-ongoing-call__remote-video-disabled">
|
)}
|
||||||
<Avatar
|
<div
|
||||||
avatarPath={avatarPath}
|
className={classNames(
|
||||||
color={color || 'ultramarine'}
|
'module-ongoing-call__actions',
|
||||||
noteToSelf={false}
|
controlsFadeClass
|
||||||
conversationType="direct"
|
)}
|
||||||
|
>
|
||||||
|
<CallingButton
|
||||||
|
buttonType={videoButtonType}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
name={name}
|
onClick={toggleVideo}
|
||||||
phoneNumber={phoneNumber}
|
tooltipDistance={24}
|
||||||
profileName={profileName}
|
/>
|
||||||
title={title}
|
<CallingButton
|
||||||
size={112}
|
buttonType={audioButtonType}
|
||||||
|
i18n={i18n}
|
||||||
|
onClick={toggleAudio}
|
||||||
|
tooltipDistance={24}
|
||||||
|
/>
|
||||||
|
<CallingButton
|
||||||
|
buttonType={CallingButtonType.HANG_UP}
|
||||||
|
i18n={i18n}
|
||||||
|
onClick={() => {
|
||||||
|
hangUp({ callId });
|
||||||
|
}}
|
||||||
|
tooltipDistance={24}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
</div>
|
||||||
}
|
);
|
||||||
|
};
|
||||||
|
|
||||||
private renderLocalVideo() {
|
function renderAvatar(
|
||||||
return (
|
i18n: LocalizerType,
|
||||||
<video
|
callDetails: CallDetailsType
|
||||||
className="module-ongoing-call__local-video"
|
): JSX.Element {
|
||||||
ref={this.localVideoRef}
|
const {
|
||||||
autoPlay
|
avatarPath,
|
||||||
|
color,
|
||||||
|
name,
|
||||||
|
phoneNumber,
|
||||||
|
profileName,
|
||||||
|
title,
|
||||||
|
} = callDetails;
|
||||||
|
return (
|
||||||
|
<div className="module-ongoing-call__remote-video-disabled">
|
||||||
|
<Avatar
|
||||||
|
avatarPath={avatarPath}
|
||||||
|
color={color || 'ultramarine'}
|
||||||
|
noteToSelf={false}
|
||||||
|
conversationType="direct"
|
||||||
|
i18n={i18n}
|
||||||
|
name={name}
|
||||||
|
phoneNumber={phoneNumber}
|
||||||
|
profileName={profileName}
|
||||||
|
title={title}
|
||||||
|
size={112}
|
||||||
/>
|
/>
|
||||||
);
|
</div>
|
||||||
}
|
);
|
||||||
|
}
|
||||||
private renderRemoteVideo() {
|
|
||||||
return (
|
function renderHeaderMessage(
|
||||||
<canvas
|
i18n: LocalizerType,
|
||||||
className="module-ongoing-call__remote-video-enabled"
|
callState: CallState,
|
||||||
ref={this.remoteVideoRef}
|
acceptedDuration: null | number
|
||||||
/>
|
): JSX.Element | null {
|
||||||
);
|
let message = null;
|
||||||
}
|
if (callState === CallState.Prering) {
|
||||||
|
message = i18n('outgoingCallPrering');
|
||||||
private renderMessage(callState: CallState) {
|
} else if (callState === CallState.Ringing) {
|
||||||
const { i18n } = this.props;
|
message = i18n('outgoingCallRinging');
|
||||||
const { acceptedDuration } = this.state;
|
} else if (callState === CallState.Reconnecting) {
|
||||||
|
message = i18n('callReconnecting');
|
||||||
let message = null;
|
} else if (callState === CallState.Accepted && acceptedDuration) {
|
||||||
if (callState === CallState.Prering) {
|
message = i18n('callDuration', [renderDuration(acceptedDuration)]);
|
||||||
message = i18n('outgoingCallPrering');
|
}
|
||||||
} else if (callState === CallState.Ringing) {
|
|
||||||
message = i18n('outgoingCallRinging');
|
if (!message) {
|
||||||
} else if (callState === CallState.Reconnecting) {
|
return null;
|
||||||
message = i18n('callReconnecting');
|
}
|
||||||
} else if (callState === CallState.Accepted && acceptedDuration) {
|
return <div className="module-ongoing-call__header-message">{message}</div>;
|
||||||
message = i18n('callDuration', [
|
}
|
||||||
CallScreen.renderDuration(acceptedDuration),
|
|
||||||
]);
|
function renderDuration(ms: number): string {
|
||||||
}
|
const secs = Math.floor((ms / 1000) % 60)
|
||||||
|
.toString()
|
||||||
if (!message) {
|
.padStart(2, '0');
|
||||||
return null;
|
const mins = Math.floor((ms / 60000) % 60)
|
||||||
}
|
.toString()
|
||||||
return <div className="module-ongoing-call__header-message">{message}</div>;
|
.padStart(2, '0');
|
||||||
}
|
const hours = Math.floor(ms / 3600000);
|
||||||
|
if (hours > 0) {
|
||||||
static renderDuration(ms: number): string {
|
return `${hours}:${mins}:${secs}`;
|
||||||
const secs = Math.floor((ms / 1000) % 60)
|
}
|
||||||
.toString()
|
return `${mins}:${secs}`;
|
||||||
.padStart(2, '0');
|
|
||||||
const mins = Math.floor((ms / 60000) % 60)
|
|
||||||
.toString()
|
|
||||||
.padStart(2, '0');
|
|
||||||
const hours = Math.floor(ms / 3600000);
|
|
||||||
if (hours > 0) {
|
|
||||||
return `${hours}:${mins}:${secs}`;
|
|
||||||
}
|
|
||||||
return `${mins}:${secs}`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14388,37 +14388,22 @@
|
||||||
"reasonDetail": "Doesn't touch the DOM."
|
"reasonDetail": "Doesn't touch the DOM."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "React-createRef",
|
"rule": "React-useRef",
|
||||||
"path": "ts/components/CallScreen.js",
|
"path": "ts/components/CallScreen.js",
|
||||||
"line": " this.localVideoRef = react_1.default.createRef();",
|
"line": " const localVideoRef = react_1.useRef(null);",
|
||||||
"lineNumber": 87,
|
"lineNumber": 41,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2020-09-14T23:03:44.863Z"
|
"updated": "2020-10-26T21:35:52.858Z",
|
||||||
|
"reasonDetail": "Used to get the local video element for rendering."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "React-createRef",
|
"rule": "React-useRef",
|
||||||
"path": "ts/components/CallScreen.js",
|
"path": "ts/components/CallScreen.js",
|
||||||
"line": " this.remoteVideoRef = react_1.default.createRef();",
|
"line": " const remoteVideoRef = react_1.useRef(null);",
|
||||||
"lineNumber": 88,
|
"lineNumber": 42,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2020-09-14T23:03:44.863Z"
|
"updated": "2020-10-26T21:35:52.858Z",
|
||||||
},
|
"reasonDetail": "Used to get the remote video element for rendering."
|
||||||
{
|
|
||||||
"rule": "React-createRef",
|
|
||||||
"path": "ts/components/CallScreen.tsx",
|
|
||||||
"line": " this.localVideoRef = React.createRef();",
|
|
||||||
"lineNumber": 56,
|
|
||||||
"reasonCategory": "usageTrusted",
|
|
||||||
"updated": "2020-06-02T21:51:34.813Z",
|
|
||||||
"reasonDetail": "Used to render local preview video"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"rule": "React-createRef",
|
|
||||||
"path": "ts/components/CallScreen.tsx",
|
|
||||||
"line": " this.remoteVideoRef = React.createRef();",
|
|
||||||
"lineNumber": 57,
|
|
||||||
"reasonCategory": "usageTrusted",
|
|
||||||
"updated": "2020-09-14T23:03:44.863Z"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "React-useRef",
|
"rule": "React-useRef",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue