Add rageshake request modal
This commit is contained in:
parent
ec447429c5
commit
6ec9e4b666
6 changed files with 216 additions and 51 deletions
69
src/room/FeedbackModal.jsx
Normal file
69
src/room/FeedbackModal.jsx
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
import React, { useCallback, useEffect } from "react";
|
||||||
|
import { Modal, ModalContent } from "../Modal";
|
||||||
|
import { Button } from "../button";
|
||||||
|
import { FieldRow, InputField, ErrorMessage } from "../input/Input";
|
||||||
|
import { useSubmitRageshake, useRageshakeRequest } from "../settings/rageshake";
|
||||||
|
import { Body } from "../typography/Typography";
|
||||||
|
|
||||||
|
export function FeedbackModal({ inCall, roomId, ...rest }) {
|
||||||
|
const { submitRageshake, sending, sent, error } = useSubmitRageshake();
|
||||||
|
const sendRageshakeRequest = useRageshakeRequest();
|
||||||
|
|
||||||
|
const onSubmitFeedback = useCallback(
|
||||||
|
(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const data = new FormData(e.target);
|
||||||
|
const description = data.get("description");
|
||||||
|
const sendLogs = data.get("sendLogs");
|
||||||
|
submitRageshake({ description, sendLogs });
|
||||||
|
|
||||||
|
if (inCall && sendLogs) {
|
||||||
|
sendRageshakeRequest(roomId);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[inCall, submitRageshake, roomId, sendRageshakeRequest]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (sent) {
|
||||||
|
rest.onClose();
|
||||||
|
}
|
||||||
|
}, [sent, rest.onClose]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal title="Submit Feedback" isDismissable {...rest}>
|
||||||
|
<ModalContent>
|
||||||
|
<Body>Having trouble on this call? Help us fix it.</Body>
|
||||||
|
<form onSubmit={onSubmitFeedback}>
|
||||||
|
<FieldRow>
|
||||||
|
<InputField
|
||||||
|
id="description"
|
||||||
|
name="description"
|
||||||
|
label="Description (optional)"
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
</FieldRow>
|
||||||
|
<FieldRow>
|
||||||
|
<InputField
|
||||||
|
id="sendLogs"
|
||||||
|
name="sendLogs"
|
||||||
|
label="Include Debug Logs"
|
||||||
|
type="checkbox"
|
||||||
|
defaultChecked
|
||||||
|
/>
|
||||||
|
</FieldRow>
|
||||||
|
{error && (
|
||||||
|
<FieldRow>
|
||||||
|
<ErrorMessage>{error.message}</ErrorMessage>
|
||||||
|
</FieldRow>
|
||||||
|
)}
|
||||||
|
<FieldRow>
|
||||||
|
<Button type="submit" disabled={sending}>
|
||||||
|
{sending ? "Submitting feedback..." : "Submit Feedback"}
|
||||||
|
</Button>
|
||||||
|
</FieldRow>
|
||||||
|
</form>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
|
@ -19,6 +19,8 @@ import { OverflowMenu } from "./OverflowMenu";
|
||||||
import { GridLayoutMenu } from "./GridLayoutMenu";
|
import { GridLayoutMenu } from "./GridLayoutMenu";
|
||||||
import { Avatar } from "../Avatar";
|
import { Avatar } from "../Avatar";
|
||||||
import { UserMenuContainer } from "../UserMenuContainer";
|
import { UserMenuContainer } from "../UserMenuContainer";
|
||||||
|
import { useRageshakeRequestModal } from "../settings/rageshake";
|
||||||
|
import { RageshakeRequestModal } from "./RageshakeRequestModal";
|
||||||
|
|
||||||
const canScreenshare = "getDisplayMedia" in navigator.mediaDevices;
|
const canScreenshare = "getDisplayMedia" in navigator.mediaDevices;
|
||||||
// There is currently a bug in Safari our our code with cloning and sending MediaStreams
|
// There is currently a bug in Safari our our code with cloning and sending MediaStreams
|
||||||
|
@ -120,6 +122,11 @@ export function InCallView({
|
||||||
[client]
|
[client]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const {
|
||||||
|
modalState: rageshakeRequestModalState,
|
||||||
|
modalProps: rageshakeRequestModalProps,
|
||||||
|
} = useRageshakeRequestModal(groupCall.room.roomId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.inRoom}>
|
<div className={styles.inRoom}>
|
||||||
<Header>
|
<Header>
|
||||||
|
@ -164,10 +171,12 @@ export function InCallView({
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<OverflowMenu
|
<OverflowMenu
|
||||||
|
inCall
|
||||||
roomId={roomId}
|
roomId={roomId}
|
||||||
setShowInspector={setShowInspector}
|
setShowInspector={setShowInspector}
|
||||||
showInspector={showInspector}
|
showInspector={showInspector}
|
||||||
client={client}
|
client={client}
|
||||||
|
groupCall={groupCall}
|
||||||
/>
|
/>
|
||||||
<HangupButton onPress={onLeave} />
|
<HangupButton onPress={onLeave} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -176,6 +185,9 @@ export function InCallView({
|
||||||
groupCall={groupCall}
|
groupCall={groupCall}
|
||||||
show={showInspector}
|
show={showInspector}
|
||||||
/>
|
/>
|
||||||
|
{rageshakeRequestModalState.isOpen && (
|
||||||
|
<RageshakeRequestModal {...rageshakeRequestModalProps} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,18 +9,23 @@ import { ReactComponent as OverflowIcon } from "../icons/Overflow.svg";
|
||||||
import { useModalTriggerState } from "../Modal";
|
import { useModalTriggerState } from "../Modal";
|
||||||
import { SettingsModal } from "../settings/SettingsModal";
|
import { SettingsModal } from "../settings/SettingsModal";
|
||||||
import { InviteModal } from "./InviteModal";
|
import { InviteModal } from "./InviteModal";
|
||||||
import { Tooltip, TooltipTrigger } from "../Tooltip";
|
import { TooltipTrigger } from "../Tooltip";
|
||||||
|
import { FeedbackModal } from "./FeedbackModal";
|
||||||
|
|
||||||
export function OverflowMenu({
|
export function OverflowMenu({
|
||||||
roomId,
|
roomId,
|
||||||
setShowInspector,
|
setShowInspector,
|
||||||
showInspector,
|
showInspector,
|
||||||
client,
|
client,
|
||||||
|
inCall,
|
||||||
|
groupCall,
|
||||||
}) {
|
}) {
|
||||||
const { modalState: inviteModalState, modalProps: inviteModalProps } =
|
const { modalState: inviteModalState, modalProps: inviteModalProps } =
|
||||||
useModalTriggerState();
|
useModalTriggerState();
|
||||||
const { modalState: settingsModalState, modalProps: settingsModalProps } =
|
const { modalState: settingsModalState, modalProps: settingsModalProps } =
|
||||||
useModalTriggerState();
|
useModalTriggerState();
|
||||||
|
const { modalState: feedbackModalState, modalProps: feedbackModalProps } =
|
||||||
|
useModalTriggerState();
|
||||||
|
|
||||||
// TODO: On closing modal, focus should be restored to the trigger button
|
// TODO: On closing modal, focus should be restored to the trigger button
|
||||||
// https://github.com/adobe/react-spectrum/issues/2444
|
// https://github.com/adobe/react-spectrum/issues/2444
|
||||||
|
@ -32,6 +37,9 @@ export function OverflowMenu({
|
||||||
case "settings":
|
case "settings":
|
||||||
settingsModalState.open();
|
settingsModalState.open();
|
||||||
break;
|
break;
|
||||||
|
case "feedback":
|
||||||
|
feedbackModalState.open();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -54,6 +62,10 @@ export function OverflowMenu({
|
||||||
<SettingsIcon />
|
<SettingsIcon />
|
||||||
<span>Settings</span>
|
<span>Settings</span>
|
||||||
</Item>
|
</Item>
|
||||||
|
<Item key="feedback" textValue="Submit Feedback">
|
||||||
|
<SettingsIcon />
|
||||||
|
<span>Submit Feedback</span>
|
||||||
|
</Item>
|
||||||
</Menu>
|
</Menu>
|
||||||
)}
|
)}
|
||||||
</PopoverMenuTrigger>
|
</PopoverMenuTrigger>
|
||||||
|
@ -68,6 +80,13 @@ export function OverflowMenu({
|
||||||
{inviteModalState.isOpen && (
|
{inviteModalState.isOpen && (
|
||||||
<InviteModal roomId={roomId} {...inviteModalProps} />
|
<InviteModal roomId={roomId} {...inviteModalProps} />
|
||||||
)}
|
)}
|
||||||
|
{feedbackModalState.isOpen && (
|
||||||
|
<FeedbackModal
|
||||||
|
{...feedbackModalProps}
|
||||||
|
roomId={groupCall?.room.roomId}
|
||||||
|
inCall={inCall}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
40
src/room/RageshakeRequestModal.jsx
Normal file
40
src/room/RageshakeRequestModal.jsx
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import React, { useEffect } from "react";
|
||||||
|
import { Modal, ModalContent } from "../Modal";
|
||||||
|
import { Button } from "../button";
|
||||||
|
import { FieldRow, ErrorMessage } from "../input/Input";
|
||||||
|
import { useSubmitRageshake } from "../settings/rageshake";
|
||||||
|
import { Body } from "../typography/Typography";
|
||||||
|
|
||||||
|
export function RageshakeRequestModal(props) {
|
||||||
|
const { submitRageshake, sending, sent, error } = useSubmitRageshake();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (sent) {
|
||||||
|
props.onClose();
|
||||||
|
}
|
||||||
|
}, [sent, props.onClose]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal title="Debug Log Request" isDismissable {...props}>
|
||||||
|
<ModalContent>
|
||||||
|
<Body>
|
||||||
|
Another user on this call is having an issue. In order to better
|
||||||
|
diagnose these issues we'd like to collect a debug log.
|
||||||
|
</Body>
|
||||||
|
<FieldRow>
|
||||||
|
<Button
|
||||||
|
onPress={() => submitRageshake({ sendLogs: true })}
|
||||||
|
disabled={sending}
|
||||||
|
>
|
||||||
|
{sending ? "Sending debug log..." : "Send debug log"}
|
||||||
|
</Button>
|
||||||
|
</FieldRow>
|
||||||
|
{error && (
|
||||||
|
<FieldRow>
|
||||||
|
<ErrorMessage>{error.message}</ErrorMessage>
|
||||||
|
</FieldRow>
|
||||||
|
)}
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useState } from "react";
|
import React from "react";
|
||||||
import { Modal } from "../Modal";
|
import { Modal } from "../Modal";
|
||||||
import styles from "./SettingsModal.module.css";
|
import styles from "./SettingsModal.module.css";
|
||||||
import { TabContainer, TabItem } from "../tabs/Tabs";
|
import { TabContainer, TabItem } from "../tabs/Tabs";
|
||||||
|
@ -8,10 +8,9 @@ import { ReactComponent as DeveloperIcon } from "../icons/Developer.svg";
|
||||||
import { SelectInput } from "../input/SelectInput";
|
import { SelectInput } from "../input/SelectInput";
|
||||||
import { Item } from "@react-stately/collections";
|
import { Item } from "@react-stately/collections";
|
||||||
import { useMediaHandler } from "./useMediaHandler";
|
import { useMediaHandler } from "./useMediaHandler";
|
||||||
import { FieldRow, InputField, ErrorMessage } from "../input/Input";
|
import { FieldRow, InputField } from "../input/Input";
|
||||||
import { Button } from "../button";
|
import { Button } from "../button";
|
||||||
import { useSubmitRageshake } from "./useSubmitRageshake";
|
import { useDownloadDebugLog } from "./rageshake";
|
||||||
import { Subtitle } from "../typography/Typography";
|
|
||||||
|
|
||||||
export function SettingsModal({
|
export function SettingsModal({
|
||||||
client,
|
client,
|
||||||
|
@ -28,10 +27,7 @@ export function SettingsModal({
|
||||||
setVideoInput,
|
setVideoInput,
|
||||||
} = useMediaHandler(client);
|
} = useMediaHandler(client);
|
||||||
|
|
||||||
const [description, setDescription] = useState("");
|
const downloadDebugLog = useDownloadDebugLog();
|
||||||
|
|
||||||
const { submitRageshake, sending, sent, error, downloadDebugLog } =
|
|
||||||
useSubmitRageshake();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
|
@ -96,31 +92,6 @@ export function SettingsModal({
|
||||||
onChange={(e) => setShowInspector(e.target.checked)}
|
onChange={(e) => setShowInspector(e.target.checked)}
|
||||||
/>
|
/>
|
||||||
</FieldRow>
|
</FieldRow>
|
||||||
<Subtitle>Feedback</Subtitle>
|
|
||||||
<FieldRow>
|
|
||||||
<InputField
|
|
||||||
id="description"
|
|
||||||
name="description"
|
|
||||||
label="Description"
|
|
||||||
type="text"
|
|
||||||
value={description}
|
|
||||||
onChange={(e) => setDescription(e.target.value)}
|
|
||||||
/>
|
|
||||||
</FieldRow>
|
|
||||||
<FieldRow>
|
|
||||||
<Button onPress={() => submitRageshake({ description })}>
|
|
||||||
{sent
|
|
||||||
? "Debug Logs Sent"
|
|
||||||
: sending
|
|
||||||
? "Sending Debug Logs..."
|
|
||||||
: "Send Debug Logs"}
|
|
||||||
</Button>
|
|
||||||
</FieldRow>
|
|
||||||
{error && (
|
|
||||||
<FieldRow>
|
|
||||||
<ErrorMessage>{error.message}</ErrorMessage>
|
|
||||||
</FieldRow>
|
|
||||||
)}
|
|
||||||
<FieldRow>
|
<FieldRow>
|
||||||
<Button onPress={downloadDebugLog}>Download Debug Logs</Button>
|
<Button onPress={downloadDebugLog}>Download Debug Logs</Button>
|
||||||
</FieldRow>
|
</FieldRow>
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import { useCallback, useContext, useState } from "react";
|
import { useCallback, useContext, useEffect, useState } from "react";
|
||||||
import * as rageshake from "matrix-react-sdk/src/rageshake/rageshake";
|
import * as rageshake from "matrix-react-sdk/src/rageshake/rageshake";
|
||||||
import pako from "pako";
|
import pako from "pako";
|
||||||
import { useClient } from "../ClientContext";
|
import { useClient } from "../ClientContext";
|
||||||
import { InspectorContext } from "../room/GroupCallInspector";
|
import { InspectorContext } from "../room/GroupCallInspector";
|
||||||
|
import { useModalTriggerState } from "../Modal";
|
||||||
|
|
||||||
export function useSubmitRageshake() {
|
export function useSubmitRageshake() {
|
||||||
const { client } = useClient();
|
const { client } = useClient();
|
||||||
|
@ -171,24 +172,26 @@ export function useSubmitRageshake() {
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
const logs = await rageshake.getLogsForReport();
|
if (opts.sendLogs) {
|
||||||
|
const logs = await rageshake.getLogsForReport();
|
||||||
|
|
||||||
for (const entry of logs) {
|
for (const entry of logs) {
|
||||||
// encode as UTF-8
|
// encode as UTF-8
|
||||||
let buf = new TextEncoder().encode(entry.lines);
|
let buf = new TextEncoder().encode(entry.lines);
|
||||||
|
|
||||||
// compress
|
// compress
|
||||||
buf = pako.gzip(buf);
|
buf = pako.gzip(buf);
|
||||||
|
|
||||||
body.append("compressed-log", new Blob([buf]), entry.id);
|
body.append("compressed-log", new Blob([buf]), entry.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (json) {
|
if (json) {
|
||||||
body.append(
|
body.append(
|
||||||
"file",
|
"file",
|
||||||
new Blob([JSON.stringify(json)], { type: "text/plain" }),
|
new Blob([JSON.stringify(json)], { type: "text/plain" }),
|
||||||
"groupcall.txt"
|
"groupcall.txt"
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await fetch(
|
await fetch(
|
||||||
|
@ -209,6 +212,17 @@ export function useSubmitRageshake() {
|
||||||
[client]
|
[client]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
submitRageshake,
|
||||||
|
sending,
|
||||||
|
sent,
|
||||||
|
error,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useDownloadDebugLog() {
|
||||||
|
const [{ json }] = useContext(InspectorContext);
|
||||||
|
|
||||||
const downloadDebugLog = useCallback(() => {
|
const downloadDebugLog = useCallback(() => {
|
||||||
const blob = new Blob([JSON.stringify(json)], { type: "application/json" });
|
const blob = new Blob([JSON.stringify(json)], { type: "application/json" });
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
|
@ -222,7 +236,47 @@ export function useSubmitRageshake() {
|
||||||
URL.revokeObjectURL(url);
|
URL.revokeObjectURL(url);
|
||||||
el.parentNode.removeChild(el);
|
el.parentNode.removeChild(el);
|
||||||
}, 0);
|
}, 0);
|
||||||
});
|
}, [json]);
|
||||||
|
|
||||||
return { submitRageshake, sending, sent, error, downloadDebugLog };
|
return downloadDebugLog;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useRageshakeRequest() {
|
||||||
|
const { client } = useClient();
|
||||||
|
|
||||||
|
const sendRageshakeRequest = useCallback(
|
||||||
|
(roomId) => {
|
||||||
|
client.sendEvent(roomId, "org.matrix.rageshake_request", {});
|
||||||
|
},
|
||||||
|
[client]
|
||||||
|
);
|
||||||
|
|
||||||
|
return sendRageshakeRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useRageshakeRequestModal(roomId) {
|
||||||
|
const { modalState, modalProps } = useModalTriggerState();
|
||||||
|
const { client } = useClient();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const onEvent = (event) => {
|
||||||
|
const type = event.getType();
|
||||||
|
|
||||||
|
if (
|
||||||
|
type === "org.matrix.rageshake_request" &&
|
||||||
|
roomId === event.getRoomId() &&
|
||||||
|
client.getUserId() !== event.getSender()
|
||||||
|
) {
|
||||||
|
modalState.open();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
client.on("event", onEvent);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
client.removeListener("event", onEvent);
|
||||||
|
};
|
||||||
|
}, [modalState.open, roomId]);
|
||||||
|
|
||||||
|
return { modalState, modalProps };
|
||||||
}
|
}
|
Loading…
Add table
Reference in a new issue