Merge pull request #1285 from vector-im/dbkr/react_to_livekit_disconnect
Add disconnected screen for when livekit disconnects from the call
This commit is contained in:
@@ -77,9 +77,11 @@
|
|||||||
"Profile": "Profile",
|
"Profile": "Profile",
|
||||||
"Recaptcha dismissed": "Recaptcha dismissed",
|
"Recaptcha dismissed": "Recaptcha dismissed",
|
||||||
"Recaptcha not loaded": "Recaptcha not loaded",
|
"Recaptcha not loaded": "Recaptcha not loaded",
|
||||||
|
"Reconnect": "Reconnect",
|
||||||
"Register": "Register",
|
"Register": "Register",
|
||||||
"Registering…": "Registering…",
|
"Registering…": "Registering…",
|
||||||
"Remove": "Remove",
|
"Remove": "Remove",
|
||||||
|
"Retry sending logs": "Retry sending logs",
|
||||||
"Return to home screen": "Return to home screen",
|
"Return to home screen": "Return to home screen",
|
||||||
"Select an option": "Select an option",
|
"Select an option": "Select an option",
|
||||||
"Send debug logs": "Send debug logs",
|
"Send debug logs": "Send debug logs",
|
||||||
@@ -99,7 +101,7 @@
|
|||||||
"Submitting…": "Submitting…",
|
"Submitting…": "Submitting…",
|
||||||
"Take me Home": "Take me Home",
|
"Take me Home": "Take me Home",
|
||||||
"Thanks, we received your feedback!": "Thanks, we received your feedback!",
|
"Thanks, we received your feedback!": "Thanks, we received your feedback!",
|
||||||
"Thanks! We'll get right on it.": "Thanks! We'll get right on it.",
|
"Thanks!": "Thanks!",
|
||||||
"This call already exists, would you like to join?": "This call already exists, would you like to join?",
|
"This call already exists, would you like to join?": "This call already exists, would you like to join?",
|
||||||
"This site is protected by ReCAPTCHA and the Google <2>Privacy Policy</2> and <6>Terms of Service</6> apply.<9></9>By clicking \"Register\", you agree to our <12>End User Licensing Agreement (EULA)</12>": "This site is protected by ReCAPTCHA and the Google <2>Privacy Policy</2> and <6>Terms of Service</6> apply.<9></9>By clicking \"Register\", you agree to our <12>End User Licensing Agreement (EULA)</12>",
|
"This site is protected by ReCAPTCHA and the Google <2>Privacy Policy</2> and <6>Terms of Service</6> apply.<9></9>By clicking \"Register\", you agree to our <12>End User Licensing Agreement (EULA)</12>": "This site is protected by ReCAPTCHA and the Google <2>Privacy Policy</2> and <6>Terms of Service</6> apply.<9></9>By clicking \"Register\", you agree to our <12>End User Licensing Agreement (EULA)</12>",
|
||||||
"Turn off camera": "Turn off camera",
|
"Turn off camera": "Turn off camera",
|
||||||
@@ -116,6 +118,7 @@
|
|||||||
"Walkie-talkie call name": "Walkie-talkie call name",
|
"Walkie-talkie call name": "Walkie-talkie call name",
|
||||||
"WebRTC is not supported or is being blocked in this browser.": "WebRTC is not supported or is being blocked in this browser.",
|
"WebRTC is not supported or is being blocked in this browser.": "WebRTC is not supported or is being blocked in this browser.",
|
||||||
"Yes, join call": "Yes, join call",
|
"Yes, join call": "Yes, join call",
|
||||||
|
"You were disconnected from the call": "You were disconnected from the call",
|
||||||
"Your feedback": "Your feedback",
|
"Your feedback": "Your feedback",
|
||||||
"Your recent calls": "Your recent calls"
|
"Your recent calls": "Your recent calls"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,11 +22,10 @@ import * as Sentry from "@sentry/react";
|
|||||||
|
|
||||||
import { Header, HeaderLogo, LeftNav, RightNav } from "./Header";
|
import { Header, HeaderLogo, LeftNav, RightNav } from "./Header";
|
||||||
import { LinkButton, Button } from "./button";
|
import { LinkButton, Button } from "./button";
|
||||||
import { useSubmitRageshake } from "./settings/submit-rageshake";
|
|
||||||
import { ErrorMessage } from "./input/Input";
|
|
||||||
import styles from "./FullScreenView.module.css";
|
import styles from "./FullScreenView.module.css";
|
||||||
import { translatedError, TranslatedError } from "./TranslatedError";
|
import { TranslatedError } from "./TranslatedError";
|
||||||
import { Config } from "./config/Config";
|
import { Config } from "./config/Config";
|
||||||
|
import { RageshakeButton } from "./settings/RageshakeButton";
|
||||||
|
|
||||||
interface FullScreenViewProps {
|
interface FullScreenViewProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
@@ -99,37 +98,11 @@ export function ErrorView({ error }: ErrorViewProps) {
|
|||||||
|
|
||||||
export function CrashView() {
|
export function CrashView() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { submitRageshake, sending, sent, error } = useSubmitRageshake();
|
|
||||||
|
|
||||||
const sendDebugLogs = useCallback(() => {
|
|
||||||
submitRageshake({
|
|
||||||
description: "**Soft Crash**",
|
|
||||||
sendLogs: true,
|
|
||||||
});
|
|
||||||
}, [submitRageshake]);
|
|
||||||
|
|
||||||
const onReload = useCallback(() => {
|
const onReload = useCallback(() => {
|
||||||
window.location.href = "/";
|
window.location.href = "/";
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
let logsComponent: JSX.Element | null = null;
|
|
||||||
if (sent) {
|
|
||||||
logsComponent = <div>{t("Thanks! We'll get right on it.")}</div>;
|
|
||||||
} else if (sending) {
|
|
||||||
logsComponent = <div>{t("Sending…")}</div>;
|
|
||||||
} else if (Config.get().rageshake?.submit_url) {
|
|
||||||
logsComponent = (
|
|
||||||
<Button
|
|
||||||
size="lg"
|
|
||||||
variant="default"
|
|
||||||
onPress={sendDebugLogs}
|
|
||||||
className={styles.wideButton}
|
|
||||||
>
|
|
||||||
{t("Send debug logs")}
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FullScreenView>
|
<FullScreenView>
|
||||||
<Trans>
|
<Trans>
|
||||||
@@ -141,10 +114,7 @@ export function CrashView() {
|
|||||||
</Trans>
|
</Trans>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className={styles.sendLogsSection}>{logsComponent}</div>
|
<RageshakeButton description="***Soft Crash***" />
|
||||||
{error && (
|
|
||||||
<ErrorMessage error={translatedError("Couldn't send debug logs!", t)} />
|
|
||||||
)}
|
|
||||||
<Button
|
<Button
|
||||||
size="lg"
|
size="lg"
|
||||||
variant="default"
|
variant="default"
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import {
|
|||||||
MuteMicrophoneTracker,
|
MuteMicrophoneTracker,
|
||||||
UndecryptableToDeviceEventTracker,
|
UndecryptableToDeviceEventTracker,
|
||||||
QualitySurveyEventTracker,
|
QualitySurveyEventTracker,
|
||||||
|
CallDisconnectedEventTracker,
|
||||||
} from "./PosthogEvents";
|
} from "./PosthogEvents";
|
||||||
import { Config } from "../config/Config";
|
import { Config } from "../config/Config";
|
||||||
import { getUrlParams } from "../UrlParams";
|
import { getUrlParams } from "../UrlParams";
|
||||||
@@ -437,4 +438,5 @@ export class PosthogAnalytics {
|
|||||||
public eventMuteCamera = new MuteCameraTracker();
|
public eventMuteCamera = new MuteCameraTracker();
|
||||||
public eventUndecryptableToDevice = new UndecryptableToDeviceEventTracker();
|
public eventUndecryptableToDevice = new UndecryptableToDeviceEventTracker();
|
||||||
public eventQualitySurvey = new QualitySurveyEventTracker();
|
public eventQualitySurvey = new QualitySurveyEventTracker();
|
||||||
|
public eventCallDisconnected = new CallDisconnectedEventTracker();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { DisconnectReason } from "livekit-client";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
IPosthogEvent,
|
IPosthogEvent,
|
||||||
PosthogAnalytics,
|
PosthogAnalytics,
|
||||||
@@ -181,3 +183,17 @@ export class QualitySurveyEventTracker {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface CallDisconnectedEvent {
|
||||||
|
eventName: "CallDisconnected";
|
||||||
|
reason?: DisconnectReason;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CallDisconnectedEventTracker {
|
||||||
|
track(reason?: DisconnectReason) {
|
||||||
|
PosthogAnalytics.instance.trackEvent<CallDisconnectedEvent>({
|
||||||
|
eventName: "CallDisconnected",
|
||||||
|
reason,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -31,6 +31,15 @@ limitations under the License.
|
|||||||
margin-bottom: 32px;
|
margin-bottom: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.disconnectedButtons {
|
||||||
|
display: grid;
|
||||||
|
gap: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rageshakeButton {
|
||||||
|
grid-column: 2;
|
||||||
|
}
|
||||||
|
|
||||||
.callEndedButton {
|
.callEndedButton {
|
||||||
margin-top: 54px;
|
margin-top: 54px;
|
||||||
margin-left: 30px;
|
margin-left: 30px;
|
||||||
|
|||||||
@@ -28,15 +28,20 @@ import { Header, HeaderLogo, LeftNav, RightNav } from "../Header";
|
|||||||
import { PosthogAnalytics } from "../analytics/PosthogAnalytics";
|
import { PosthogAnalytics } from "../analytics/PosthogAnalytics";
|
||||||
import { FieldRow, InputField } from "../input/Input";
|
import { FieldRow, InputField } from "../input/Input";
|
||||||
import { StarRatingInput } from "../input/StarRatingInput";
|
import { StarRatingInput } from "../input/StarRatingInput";
|
||||||
|
import { RageshakeButton } from "../settings/RageshakeButton";
|
||||||
|
|
||||||
export function CallEndedView({
|
export function CallEndedView({
|
||||||
client,
|
client,
|
||||||
isPasswordlessUser,
|
isPasswordlessUser,
|
||||||
endedCallId,
|
endedCallId,
|
||||||
|
leaveError,
|
||||||
|
reconnect,
|
||||||
}: {
|
}: {
|
||||||
client: MatrixClient;
|
client: MatrixClient;
|
||||||
isPasswordlessUser: boolean;
|
isPasswordlessUser: boolean;
|
||||||
endedCallId: string;
|
endedCallId: string;
|
||||||
|
leaveError?: Error;
|
||||||
|
reconnect: () => void;
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
@@ -76,6 +81,7 @@ export function CallEndedView({
|
|||||||
},
|
},
|
||||||
[endedCallId, history, isPasswordlessUser, starRating]
|
[endedCallId, history, isPasswordlessUser, starRating]
|
||||||
);
|
);
|
||||||
|
|
||||||
const createAccountDialog = isPasswordlessUser && (
|
const createAccountDialog = isPasswordlessUser && (
|
||||||
<div className={styles.callEndedContent}>
|
<div className={styles.callEndedContent}>
|
||||||
<Trans>
|
<Trans>
|
||||||
@@ -138,15 +144,33 @@ export function CallEndedView({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const renderBody = () => {
|
||||||
|
if (leaveError) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<main className={styles.main}>
|
||||||
|
<Headline className={styles.headline}>
|
||||||
|
<Trans>You were disconnected from the call</Trans>
|
||||||
|
</Headline>
|
||||||
|
<div className={styles.disconnectedButtons}>
|
||||||
|
<Button size="lg" variant="default" onClick={reconnect}>
|
||||||
|
{t("Reconnect")}
|
||||||
|
</Button>
|
||||||
|
<div className={styles.rageshakeButton}>
|
||||||
|
<RageshakeButton description="***Call disconnected***" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<Body className={styles.footer}>
|
||||||
|
<Link color="primary" to="/">
|
||||||
|
{t("Return to home screen")}
|
||||||
|
</Link>
|
||||||
|
</Body>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Header>
|
|
||||||
<LeftNav>
|
|
||||||
<HeaderLogo />
|
|
||||||
</LeftNav>
|
|
||||||
<RightNav />
|
|
||||||
</Header>
|
|
||||||
<div className={styles.container}>
|
|
||||||
<main className={styles.main}>
|
<main className={styles.main}>
|
||||||
<Headline className={styles.headline}>
|
<Headline className={styles.headline}>
|
||||||
{surveySubmitted
|
{surveySubmitted
|
||||||
@@ -168,7 +192,20 @@ export function CallEndedView({
|
|||||||
{t("Not now, return to home screen")}
|
{t("Not now, return to home screen")}
|
||||||
</Link>
|
</Link>
|
||||||
</Body>
|
</Body>
|
||||||
</div>
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Header>
|
||||||
|
<LeftNav>
|
||||||
|
<HeaderLogo />
|
||||||
|
</LeftNav>
|
||||||
|
<RightNav />
|
||||||
|
</Header>
|
||||||
|
<div className={styles.container}>{renderBody()}</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -163,9 +163,12 @@ export function GroupCallView({
|
|||||||
useSentryGroupCallHandler(groupCall);
|
useSentryGroupCallHandler(groupCall);
|
||||||
|
|
||||||
const [left, setLeft] = useState(false);
|
const [left, setLeft] = useState(false);
|
||||||
|
const [leaveError, setLeaveError] = useState<Error | undefined>(undefined);
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
const onLeave = useCallback(async () => {
|
const onLeave = useCallback(
|
||||||
|
async (leaveError?: Error) => {
|
||||||
|
setLeaveError(leaveError);
|
||||||
setLeft(true);
|
setLeft(true);
|
||||||
|
|
||||||
let participantCount = 0;
|
let participantCount = 0;
|
||||||
@@ -198,7 +201,9 @@ export function GroupCallView({
|
|||||||
) {
|
) {
|
||||||
history.push("/");
|
history.push("/");
|
||||||
}
|
}
|
||||||
}, [groupCall, leave, isPasswordlessUser, isEmbedded, history]);
|
},
|
||||||
|
[groupCall, leave, isPasswordlessUser, isEmbedded, history]
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (widget && state === GroupCallState.Entered) {
|
if (widget && state === GroupCallState.Entered) {
|
||||||
@@ -218,6 +223,12 @@ export function GroupCallView({
|
|||||||
undefined
|
undefined
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const onReconnect = useCallback(() => {
|
||||||
|
setLeft(false);
|
||||||
|
setLeaveError(undefined);
|
||||||
|
groupCall.enter();
|
||||||
|
}, [groupCall]);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return <ErrorView error={error} />;
|
return <ErrorView error={error} />;
|
||||||
} else if (state === GroupCallState.Entered && userChoices) {
|
} else if (state === GroupCallState.Entered && userChoices) {
|
||||||
@@ -248,13 +259,16 @@ export function GroupCallView({
|
|||||||
// submitting anything.
|
// submitting anything.
|
||||||
if (
|
if (
|
||||||
isPasswordlessUser ||
|
isPasswordlessUser ||
|
||||||
(PosthogAnalytics.instance.isEnabled() && !isEmbedded)
|
(PosthogAnalytics.instance.isEnabled() && !isEmbedded) ||
|
||||||
|
leaveError
|
||||||
) {
|
) {
|
||||||
return (
|
return (
|
||||||
<CallEndedView
|
<CallEndedView
|
||||||
endedCallId={groupCall.groupCallId}
|
endedCallId={groupCall.groupCallId}
|
||||||
client={client}
|
client={client}
|
||||||
isPasswordlessUser={isPasswordlessUser}
|
isPasswordlessUser={isPasswordlessUser}
|
||||||
|
leaveError={leaveError}
|
||||||
|
reconnect={onReconnect}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import {
|
|||||||
} from "@livekit/components-react";
|
} from "@livekit/components-react";
|
||||||
import { usePreventScroll } from "@react-aria/overlays";
|
import { usePreventScroll } from "@react-aria/overlays";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { Room, Track } from "livekit-client";
|
import { DisconnectReason, Room, RoomEvent, Track } from "livekit-client";
|
||||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
||||||
import { GroupCall } from "matrix-js-sdk/src/webrtc/groupCall";
|
import { GroupCall } from "matrix-js-sdk/src/webrtc/groupCall";
|
||||||
@@ -33,6 +33,7 @@ import { useTranslation } from "react-i18next";
|
|||||||
import useMeasure from "react-use-measure";
|
import useMeasure from "react-use-measure";
|
||||||
import { OverlayTriggerState } from "@react-stately/overlays";
|
import { OverlayTriggerState } from "@react-stately/overlays";
|
||||||
import { JoinRule } from "matrix-js-sdk/src/@types/partials";
|
import { JoinRule } from "matrix-js-sdk/src/@types/partials";
|
||||||
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|
||||||
import type { IWidgetApiRequest } from "matrix-widget-api";
|
import type { IWidgetApiRequest } from "matrix-widget-api";
|
||||||
import {
|
import {
|
||||||
@@ -83,6 +84,7 @@ import { useFullscreen } from "./useFullscreen";
|
|||||||
import { useLayoutStates } from "../video-grid/Layout";
|
import { useLayoutStates } from "../video-grid/Layout";
|
||||||
import { useSFUConfig } from "../livekit/OpenIDLoader";
|
import { useSFUConfig } from "../livekit/OpenIDLoader";
|
||||||
import { E2EELock } from "../E2EELock";
|
import { E2EELock } from "../E2EELock";
|
||||||
|
import { useEventEmitterThree } from "../useEvents";
|
||||||
import { useWakeLock } from "../useWakeLock";
|
import { useWakeLock } from "../useWakeLock";
|
||||||
|
|
||||||
const canScreenshare = "getDisplayMedia" in (navigator.mediaDevices ?? {});
|
const canScreenshare = "getDisplayMedia" in (navigator.mediaDevices ?? {});
|
||||||
@@ -115,7 +117,7 @@ export interface InCallViewProps {
|
|||||||
groupCall: GroupCall;
|
groupCall: GroupCall;
|
||||||
livekitRoom: Room;
|
livekitRoom: Room;
|
||||||
participants: Map<RoomMember, Map<string, ParticipantInfo>>;
|
participants: Map<RoomMember, Map<string, ParticipantInfo>>;
|
||||||
onLeave: () => void;
|
onLeave: (error?: Error) => void;
|
||||||
unencryptedEventsFromUsers: Set<string>;
|
unencryptedEventsFromUsers: Set<string>;
|
||||||
hideHeader: boolean;
|
hideHeader: boolean;
|
||||||
otelGroupCallMembership?: OTelGroupCallMembership;
|
otelGroupCallMembership?: OTelGroupCallMembership;
|
||||||
@@ -190,6 +192,23 @@ export function InCallView({
|
|||||||
async (muted) => await localParticipant.setMicrophoneEnabled(!muted)
|
async (muted) => await localParticipant.setMicrophoneEnabled(!muted)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const onDisconnected = useCallback(
|
||||||
|
(reason?: DisconnectReason) => {
|
||||||
|
PosthogAnalytics.instance.eventCallDisconnected.track(reason);
|
||||||
|
logger.info("Disconnected from livekit call with reason ", reason);
|
||||||
|
onLeave(
|
||||||
|
new Error("Disconnected from LiveKit call with reason " + reason)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[onLeave]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onLeavePress = useCallback(() => {
|
||||||
|
onLeave();
|
||||||
|
}, [onLeave]);
|
||||||
|
|
||||||
|
useEventEmitterThree(livekitRoom, RoomEvent.Disconnected, onDisconnected);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
widget?.api.transport.send(
|
widget?.api.transport.send(
|
||||||
layout === "freedom"
|
layout === "freedom"
|
||||||
@@ -386,7 +405,7 @@ export function InCallView({
|
|||||||
}
|
}
|
||||||
|
|
||||||
buttons.push(
|
buttons.push(
|
||||||
<HangupButton key="6" onPress={onLeave} data-testid="incall_leave" />
|
<HangupButton key="6" onPress={onLeavePress} data-testid="incall_leave" />
|
||||||
);
|
);
|
||||||
footer = <div className={styles.footer}>{buttons}</div>;
|
footer = <div className={styles.footer}>{buttons}</div>;
|
||||||
}
|
}
|
||||||
|
|||||||
21
src/settings/RageshakeButton.module.css
Normal file
21
src/settings/RageshakeButton.module.css
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 New Vector Ltd
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.rageshakeControl {
|
||||||
|
height: 50px;
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
67
src/settings/RageshakeButton.tsx
Normal file
67
src/settings/RageshakeButton.tsx
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2023 New Vector Ltd
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { useCallback } from "react";
|
||||||
|
|
||||||
|
import { Button } from "../button";
|
||||||
|
import { Config } from "../config/Config";
|
||||||
|
import styles from "./RageshakeButton.module.css";
|
||||||
|
import { useSubmitRageshake } from "./submit-rageshake";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RageshakeButton = ({ description }: Props) => {
|
||||||
|
const { submitRageshake, sending, sent, error } = useSubmitRageshake();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const sendDebugLogs = useCallback(() => {
|
||||||
|
submitRageshake({
|
||||||
|
description,
|
||||||
|
sendLogs: true,
|
||||||
|
});
|
||||||
|
}, [submitRageshake, description]);
|
||||||
|
|
||||||
|
if (!Config.get().rageshake?.submit_url) return null;
|
||||||
|
|
||||||
|
let logsComponent: JSX.Element | null = null;
|
||||||
|
if (sending) {
|
||||||
|
logsComponent = <span>{t("Sending…")}</span>;
|
||||||
|
} else if (sent) {
|
||||||
|
logsComponent = <div>{t("Thanks!")}</div>;
|
||||||
|
} else {
|
||||||
|
let caption = t("Send debug logs");
|
||||||
|
if (error) {
|
||||||
|
caption = t("Retry sending logs");
|
||||||
|
}
|
||||||
|
|
||||||
|
logsComponent = (
|
||||||
|
<Button
|
||||||
|
size="lg"
|
||||||
|
variant="default"
|
||||||
|
onPress={sendDebugLogs}
|
||||||
|
className={styles.wideButton}
|
||||||
|
disabled={sending}
|
||||||
|
>
|
||||||
|
{caption}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className={styles.rageshakeControl}>{logsComponent}</div>;
|
||||||
|
};
|
||||||
@@ -15,6 +15,7 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
import EventEmitter from "eventemitter3";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
Listener,
|
Listener,
|
||||||
@@ -59,3 +60,20 @@ export const useTypedEventEmitter = <
|
|||||||
};
|
};
|
||||||
}, [emitter, eventType, listener]);
|
}, [emitter, eventType, listener]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Shortcut for registering a listener on an eventemitter3 EventEmitter (ie. what the LiveKit SDK uses)
|
||||||
|
export const useEventEmitterThree = <
|
||||||
|
EventType extends EventEmitter.ValidEventTypes,
|
||||||
|
T extends EventEmitter.EventNames<EventType>
|
||||||
|
>(
|
||||||
|
emitter: EventEmitter<EventType>,
|
||||||
|
eventType: T,
|
||||||
|
listener: EventEmitter.EventListener<EventType, T>
|
||||||
|
) => {
|
||||||
|
useEffect(() => {
|
||||||
|
emitter.on(eventType, listener);
|
||||||
|
return () => {
|
||||||
|
emitter.off(eventType, listener);
|
||||||
|
};
|
||||||
|
}, [emitter, eventType, listener]);
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user