Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cb07ce32cb | ||
|
|
6866d662f7 | ||
|
|
51a2027d64 | ||
|
|
0f6b8f9bb1 | ||
|
|
63229ce2d7 | ||
|
|
1d620910c5 | ||
|
|
47357b3fc6 | ||
|
|
3ed35f9477 | ||
|
|
a369444b62 | ||
|
|
742d658021 | ||
|
|
681c24a0ca | ||
|
|
fc057bf988 | ||
|
|
51561e2f4e | ||
|
|
4168540017 | ||
|
|
942630c2fc | ||
|
|
9251cd9964 | ||
|
|
145826d1f3 | ||
|
|
5e42881c5c | ||
|
|
0824bfb4ed | ||
|
|
6ec9e4b666 |
@@ -41,7 +41,8 @@
|
||||
"react-router": "6",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-use-clipboard": "^1.0.7",
|
||||
"react-use-measure": "^2.1.1"
|
||||
"react-use-measure": "^2.1.1",
|
||||
"unique-names-generator": "^4.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.16.5",
|
||||
|
||||
@@ -4,6 +4,7 @@ set -ex
|
||||
|
||||
export VITE_DEFAULT_HOMESERVER=https://call.ems.host
|
||||
export VITE_SENTRY_DSN=https://b1e328d49be3402ba96101338989fb35@sentry.matrix.org/41
|
||||
export VITE_RAGESHAKE_SUBMIT_URL=https://element.io/bugreports/submit
|
||||
|
||||
git clone https://github.com/matrix-org/matrix-js-sdk.git
|
||||
cd matrix-js-sdk
|
||||
|
||||
@@ -23,6 +23,7 @@ import React, {
|
||||
useContext,
|
||||
} from "react";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { ErrorView } from "./FullScreenView";
|
||||
import { initClient, defaultHomeserver } from "./matrix-utils";
|
||||
|
||||
const ClientContext = createContext();
|
||||
@@ -30,7 +31,7 @@ const ClientContext = createContext();
|
||||
export function ClientProvider({ children }) {
|
||||
const history = useHistory();
|
||||
const [
|
||||
{ loading, isAuthenticated, isPasswordlessUser, client, userName },
|
||||
{ loading, isAuthenticated, isPasswordlessUser, client, userName, error },
|
||||
setState,
|
||||
] = useState({
|
||||
loading: true,
|
||||
@@ -38,6 +39,7 @@ export function ClientProvider({ children }) {
|
||||
isPasswordlessUser: false,
|
||||
client: undefined,
|
||||
userName: null,
|
||||
error: undefined,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
@@ -143,35 +145,71 @@ export function ClientProvider({ children }) {
|
||||
[client]
|
||||
);
|
||||
|
||||
const setClient = useCallback((client, session) => {
|
||||
if (client) {
|
||||
localStorage.setItem("matrix-auth-store", JSON.stringify(session));
|
||||
const setClient = useCallback(
|
||||
(newClient, session) => {
|
||||
if (client && client !== newClient) {
|
||||
client.stopClient();
|
||||
}
|
||||
|
||||
setState({
|
||||
client,
|
||||
loading: false,
|
||||
isAuthenticated: true,
|
||||
isPasswordlessUser: !!session.passwordlessUser,
|
||||
userName: client.getUserIdLocalpart(),
|
||||
});
|
||||
} else {
|
||||
localStorage.removeItem("matrix-auth-store");
|
||||
if (newClient) {
|
||||
localStorage.setItem("matrix-auth-store", JSON.stringify(session));
|
||||
|
||||
setState({
|
||||
client: undefined,
|
||||
loading: false,
|
||||
isAuthenticated: false,
|
||||
isPasswordlessUser: false,
|
||||
userName: null,
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
setState({
|
||||
client: newClient,
|
||||
loading: false,
|
||||
isAuthenticated: true,
|
||||
isPasswordlessUser: !!session.passwordlessUser,
|
||||
userName: newClient.getUserIdLocalpart(),
|
||||
});
|
||||
} else {
|
||||
localStorage.removeItem("matrix-auth-store");
|
||||
|
||||
setState({
|
||||
client: undefined,
|
||||
loading: false,
|
||||
isAuthenticated: false,
|
||||
isPasswordlessUser: false,
|
||||
userName: null,
|
||||
});
|
||||
}
|
||||
},
|
||||
[client]
|
||||
);
|
||||
|
||||
const logout = useCallback(() => {
|
||||
localStorage.removeItem("matrix-auth-store");
|
||||
window.location = "/";
|
||||
}, [history]);
|
||||
|
||||
useEffect(() => {
|
||||
if ("BroadcastChannel" in window) {
|
||||
const loadTime = Date.now();
|
||||
const broadcastChannel = new BroadcastChannel("matrix-video-chat");
|
||||
|
||||
function onMessage({ data }) {
|
||||
if (data.load !== undefined && data.load > loadTime) {
|
||||
if (client) {
|
||||
client.stopClient();
|
||||
}
|
||||
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
error: new Error(
|
||||
"This application has been opened in another tab."
|
||||
),
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
broadcastChannel.addEventListener("message", onMessage);
|
||||
broadcastChannel.postMessage({ load: loadTime });
|
||||
|
||||
return () => {
|
||||
broadcastChannel.removeEventListener("message", onMessage);
|
||||
};
|
||||
}
|
||||
}, [client]);
|
||||
|
||||
const context = useMemo(
|
||||
() => ({
|
||||
loading,
|
||||
@@ -195,6 +233,14 @@ export function ClientProvider({ children }) {
|
||||
]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
window.matrixclient = client;
|
||||
}, [client]);
|
||||
|
||||
if (error) {
|
||||
return <ErrorView error={error} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<ClientContext.Provider value={context}>{children}</ClientContext.Provider>
|
||||
);
|
||||
|
||||
@@ -31,13 +31,7 @@ import { usePageTitle } from "../usePageTitle";
|
||||
export function RegisterPage() {
|
||||
usePageTitle("Register");
|
||||
|
||||
const {
|
||||
loading,
|
||||
client,
|
||||
changePassword,
|
||||
isAuthenticated,
|
||||
isPasswordlessUser,
|
||||
} = useClient();
|
||||
const { loading, isAuthenticated, isPasswordlessUser, client } = useClient();
|
||||
const confirmPasswordRef = useRef();
|
||||
const history = useHistory();
|
||||
const location = useLocation();
|
||||
@@ -64,11 +58,31 @@ export function RegisterPage() {
|
||||
async function submit() {
|
||||
setRegistering(true);
|
||||
|
||||
if (isPasswordlessUser) {
|
||||
await changePassword(password);
|
||||
} else {
|
||||
const recaptchaResponse = await execute();
|
||||
await register(userName, password, recaptchaResponse);
|
||||
let roomIds;
|
||||
|
||||
if (client && isPasswordlessUser) {
|
||||
const groupCalls = client.groupCallEventHandler.groupCalls.values();
|
||||
roomIds = Array.from(groupCalls).map(
|
||||
(groupCall) => groupCall.room.roomId
|
||||
);
|
||||
}
|
||||
|
||||
const recaptchaResponse = await execute();
|
||||
const newClient = await register(
|
||||
userName,
|
||||
password,
|
||||
userName,
|
||||
recaptchaResponse
|
||||
);
|
||||
|
||||
if (roomIds) {
|
||||
for (const roomId of roomIds) {
|
||||
try {
|
||||
await newClient.joinRoom(roomId);
|
||||
} catch (error) {
|
||||
console.warn(`Couldn't join room ${roomId}`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,15 +100,7 @@ export function RegisterPage() {
|
||||
reset();
|
||||
});
|
||||
},
|
||||
[
|
||||
register,
|
||||
changePassword,
|
||||
location,
|
||||
history,
|
||||
isPasswordlessUser,
|
||||
reset,
|
||||
execute,
|
||||
]
|
||||
[register, location, history, isPasswordlessUser, reset, execute, client]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -110,10 +116,10 @@ export function RegisterPage() {
|
||||
}, [password, passwordConfirmation]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading && isAuthenticated && !isPasswordlessUser) {
|
||||
if (!loading && isAuthenticated && !isPasswordlessUser && !registering) {
|
||||
history.push("/");
|
||||
}
|
||||
}, [history, isAuthenticated, isPasswordlessUser]);
|
||||
}, [history, isAuthenticated, isPasswordlessUser, registering]);
|
||||
|
||||
if (loading) {
|
||||
return <LoadingView />;
|
||||
@@ -137,12 +143,6 @@ export function RegisterPage() {
|
||||
autoCapitalize="none"
|
||||
prefix="@"
|
||||
suffix={`:${defaultHomeserverHost}`}
|
||||
value={
|
||||
isAuthenticated && isPasswordlessUser
|
||||
? client.getUserIdLocalpart()
|
||||
: undefined
|
||||
}
|
||||
disabled={isAuthenticated && isPasswordlessUser}
|
||||
/>
|
||||
</FieldRow>
|
||||
<FieldRow>
|
||||
@@ -168,22 +168,20 @@ export function RegisterPage() {
|
||||
ref={confirmPasswordRef}
|
||||
/>
|
||||
</FieldRow>
|
||||
{!isPasswordlessUser && (
|
||||
<Caption>
|
||||
This site is protected by ReCAPTCHA and the Google{" "}
|
||||
<Link href="https://www.google.com/policies/privacy/">
|
||||
Privacy Policy
|
||||
</Link>{" "}
|
||||
and{" "}
|
||||
<Link href="https://policies.google.com/terms">
|
||||
Terms of Service
|
||||
</Link>{" "}
|
||||
apply.
|
||||
<br />
|
||||
By clicking "Log in", you agree to our{" "}
|
||||
<Link href={privacyPolicyUrl}>Terms and conditions</Link>
|
||||
</Caption>
|
||||
)}
|
||||
<Caption>
|
||||
This site is protected by ReCAPTCHA and the Google{" "}
|
||||
<Link href="https://www.google.com/policies/privacy/">
|
||||
Privacy Policy
|
||||
</Link>{" "}
|
||||
and{" "}
|
||||
<Link href="https://policies.google.com/terms">
|
||||
Terms of Service
|
||||
</Link>{" "}
|
||||
apply.
|
||||
<br />
|
||||
By clicking "Register", you agree to our{" "}
|
||||
<Link href={privacyPolicyUrl}>Terms and conditions</Link>
|
||||
</Caption>
|
||||
{error && (
|
||||
<FieldRow>
|
||||
<ErrorMessage>{error.message}</ErrorMessage>
|
||||
|
||||
137
src/auth/generateRandomName.js
Normal file
137
src/auth/generateRandomName.js
Normal file
@@ -0,0 +1,137 @@
|
||||
import {
|
||||
uniqueNamesGenerator,
|
||||
adjectives,
|
||||
colors,
|
||||
animals,
|
||||
} from "unique-names-generator";
|
||||
|
||||
const elements = [
|
||||
"hydrogen",
|
||||
"helium",
|
||||
"lithium",
|
||||
"beryllium",
|
||||
"boron",
|
||||
"carbon",
|
||||
"nitrogen",
|
||||
"oxygen",
|
||||
"fluorine",
|
||||
"neon",
|
||||
"sodium",
|
||||
"magnesium",
|
||||
"aluminum",
|
||||
"silicon",
|
||||
"phosphorus",
|
||||
"sulfur",
|
||||
"chlorine",
|
||||
"argon",
|
||||
"potassium",
|
||||
"calcium",
|
||||
"scandium",
|
||||
"titanium",
|
||||
"vanadium",
|
||||
"chromium",
|
||||
"manganese",
|
||||
"iron",
|
||||
"cobalt",
|
||||
"nickel",
|
||||
"copper",
|
||||
"zinc",
|
||||
"gallium",
|
||||
"germanium",
|
||||
"arsenic",
|
||||
"selenium",
|
||||
"bromine",
|
||||
"krypton",
|
||||
"rubidium",
|
||||
"strontium",
|
||||
"yttrium",
|
||||
"zirconium",
|
||||
"niobium",
|
||||
"molybdenum",
|
||||
"technetium",
|
||||
"ruthenium",
|
||||
"rhodium",
|
||||
"palladium",
|
||||
"silver",
|
||||
"cadmium",
|
||||
"indium",
|
||||
"tin",
|
||||
"antimony",
|
||||
"tellurium",
|
||||
"iodine",
|
||||
"xenon",
|
||||
"cesium",
|
||||
"barium",
|
||||
"lanthanum",
|
||||
"cerium",
|
||||
"praseodymium",
|
||||
"neodymium",
|
||||
"promethium",
|
||||
"samarium",
|
||||
"europium",
|
||||
"gadolinium",
|
||||
"terbium",
|
||||
"dysprosium",
|
||||
"holmium",
|
||||
"erbium",
|
||||
"thulium",
|
||||
"ytterbium",
|
||||
"lutetium",
|
||||
"hafnium",
|
||||
"tantalum",
|
||||
"wolfram",
|
||||
"rhenium",
|
||||
"osmium",
|
||||
"iridium",
|
||||
"platinum",
|
||||
"gold",
|
||||
"mercury",
|
||||
"thallium",
|
||||
"lead",
|
||||
"bismuth",
|
||||
"polonium",
|
||||
"astatine",
|
||||
"radon",
|
||||
"francium",
|
||||
"radium",
|
||||
"actinium",
|
||||
"thorium",
|
||||
"protactinium",
|
||||
"uranium",
|
||||
"neptunium",
|
||||
"plutonium",
|
||||
"americium",
|
||||
"curium",
|
||||
"berkelium",
|
||||
"californium",
|
||||
"einsteinium",
|
||||
"fermium",
|
||||
"mendelevium",
|
||||
"nobelium",
|
||||
"lawrencium",
|
||||
"rutherfordium",
|
||||
"dubnium",
|
||||
"seaborgium",
|
||||
"bohrium",
|
||||
"hassium",
|
||||
"meitnerium",
|
||||
"darmstadtium",
|
||||
"roentgenium",
|
||||
"copernicium",
|
||||
"nihonium",
|
||||
"flerovium",
|
||||
"moscovium",
|
||||
"livermorium",
|
||||
"tennessine",
|
||||
"oganesson",
|
||||
];
|
||||
|
||||
export function generateRandomName(config) {
|
||||
return uniqueNamesGenerator({
|
||||
dictionaries: [colors, adjectives, animals, elements],
|
||||
style: "lowerCase",
|
||||
length: 3,
|
||||
separator: "-",
|
||||
...config,
|
||||
});
|
||||
}
|
||||
@@ -25,7 +25,13 @@ export function useInteractiveRegistration() {
|
||||
}, []);
|
||||
|
||||
const register = useCallback(
|
||||
async (username, password, recaptchaResponse, passwordlessUser) => {
|
||||
async (
|
||||
username,
|
||||
password,
|
||||
displayName,
|
||||
recaptchaResponse,
|
||||
passwordlessUser
|
||||
) => {
|
||||
const interactiveAuth = new InteractiveAuth({
|
||||
matrixClient: authClientRef.current,
|
||||
busyChanged(loading) {
|
||||
@@ -66,7 +72,7 @@ export function useInteractiveRegistration() {
|
||||
deviceId: device_id,
|
||||
});
|
||||
|
||||
await client.setDisplayName(username);
|
||||
await client.setDisplayName(displayName);
|
||||
|
||||
const session = { user_id, device_id, access_token, passwordlessUser };
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import { TooltipTrigger } from "../Tooltip";
|
||||
export const variantToClassName = {
|
||||
default: [styles.button],
|
||||
toolbar: [styles.toolbarButton],
|
||||
toolbarSecondary: [styles.toolbarButtonSecondary],
|
||||
icon: [styles.iconButton],
|
||||
secondary: [styles.secondary],
|
||||
copy: [styles.copyButton],
|
||||
@@ -103,7 +104,7 @@ export function VideoButton({ muted, ...rest }) {
|
||||
export function ScreenshareButton({ enabled, className, ...rest }) {
|
||||
return (
|
||||
<TooltipTrigger>
|
||||
<Button variant="toolbar" {...rest} on={enabled}>
|
||||
<Button variant="toolbarSecondary" {...rest} on={enabled}>
|
||||
<ScreenshareIcon />
|
||||
</Button>
|
||||
{() => (enabled ? "Stop sharing screen" : "Share screen")}
|
||||
|
||||
@@ -16,6 +16,7 @@ limitations under the License.
|
||||
|
||||
.button,
|
||||
.toolbarButton,
|
||||
.toolbarButtonSecondary,
|
||||
.iconButton,
|
||||
.iconCopyButton,
|
||||
.secondary,
|
||||
@@ -48,6 +49,7 @@ limitations under the License.
|
||||
|
||||
.button:focus,
|
||||
.toolbarButton:focus,
|
||||
.toolbarButtonSecondary:focus,
|
||||
.iconButton:focus,
|
||||
.iconCopyButton:focus,
|
||||
.secondary:focus,
|
||||
@@ -55,14 +57,16 @@ limitations under the License.
|
||||
outline: auto;
|
||||
}
|
||||
|
||||
.toolbarButton {
|
||||
.toolbarButton,
|
||||
.toolbarButtonSecondary {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 50px;
|
||||
background-color: var(--bgColor2);
|
||||
}
|
||||
|
||||
.toolbarButton:hover {
|
||||
.toolbarButton:hover,
|
||||
.toolbarButtonSecondary:hover {
|
||||
background-color: var(--bgColor4);
|
||||
}
|
||||
|
||||
@@ -71,6 +75,10 @@ limitations under the License.
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.toolbarButtonSecondary.on {
|
||||
background-color: #0dbd8b;
|
||||
}
|
||||
|
||||
.iconButton:not(.stroke) svg * {
|
||||
fill: #ffffff;
|
||||
}
|
||||
@@ -100,6 +108,10 @@ limitations under the License.
|
||||
fill: #21262c;
|
||||
}
|
||||
|
||||
.toolbarButtonSecondary.on svg * {
|
||||
fill: #ffffff;
|
||||
}
|
||||
|
||||
.secondary,
|
||||
.copyButton {
|
||||
color: #0dbd8b;
|
||||
|
||||
@@ -3,7 +3,6 @@ import { Link } from "react-router-dom";
|
||||
import { CopyButton } from "../button";
|
||||
import { Facepile } from "../Facepile";
|
||||
import { Avatar } from "../Avatar";
|
||||
import { ReactComponent as VideoIcon } from "../icons/Video.svg";
|
||||
import styles from "./CallList.module.css";
|
||||
import { getRoomUrl } from "../matrix-utils";
|
||||
import { Body, Caption } from "../typography/Typography";
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
display: flex;
|
||||
text-decoration: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.avatar,
|
||||
|
||||
@@ -17,6 +17,7 @@ import { Form } from "../form/Form";
|
||||
export function RegisteredView({ client }) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState();
|
||||
const history = useHistory();
|
||||
const onSubmit = useCallback(
|
||||
(e) => {
|
||||
e.preventDefault();
|
||||
@@ -55,7 +56,6 @@ export function RegisteredView({ client }) {
|
||||
|
||||
const { modalState, modalProps } = useModalTriggerState();
|
||||
const [existingRoomId, setExistingRoomId] = useState();
|
||||
const history = useHistory();
|
||||
const onJoinExistingRoom = useCallback(() => {
|
||||
history.push(`/${existingRoomId}`);
|
||||
}, [history, existingRoomId]);
|
||||
|
||||
@@ -14,6 +14,7 @@ import { Body, Caption, Link, Headline } from "../typography/Typography";
|
||||
import { Form } from "../form/Form";
|
||||
import styles from "./UnauthenticatedView.module.css";
|
||||
import commonStyles from "./common.module.css";
|
||||
import { generateRandomName } from "../auth/generateRandomName";
|
||||
|
||||
export function UnauthenticatedView() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -26,19 +27,20 @@ export function UnauthenticatedView() {
|
||||
e.preventDefault();
|
||||
const data = new FormData(e.target);
|
||||
const roomName = data.get("callName");
|
||||
const userName = data.get("userName");
|
||||
const displayName = data.get("displayName");
|
||||
|
||||
async function submit() {
|
||||
setError(undefined);
|
||||
setLoading(true);
|
||||
const recaptchaResponse = await execute();
|
||||
const userName = generateRandomName();
|
||||
const client = await register(
|
||||
userName,
|
||||
randomString(16),
|
||||
displayName,
|
||||
recaptchaResponse,
|
||||
true
|
||||
);
|
||||
|
||||
const roomIdOrAlias = await createRoom(client, roomName);
|
||||
|
||||
if (roomIdOrAlias) {
|
||||
@@ -100,10 +102,10 @@ export function UnauthenticatedView() {
|
||||
</FieldRow>
|
||||
<FieldRow>
|
||||
<InputField
|
||||
id="userName"
|
||||
name="userName"
|
||||
label="Username"
|
||||
placeholder="Username"
|
||||
id="displayName"
|
||||
name="displayName"
|
||||
label="Display Name"
|
||||
placeholder="Display Name"
|
||||
type="text"
|
||||
required
|
||||
autoComplete="off"
|
||||
|
||||
3
src/icons/Feedback.svg
Normal file
3
src/icons/Feedback.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.283 21.4401C17.6495 21.4401 21.9999 17.0881 21.9999 11.7196C21.9999 6.3511 17.6495 1.99908 12.283 1.99908C6.91643 1.99908 2.566 6.3511 2.566 11.7196C2.566 13.2234 2.90739 14.6476 3.51687 15.9186L2.04468 20.7049C1.80806 21.4742 2.5308 22.1936 3.29898 21.9535L8.04564 20.4696C9.32625 21.0914 10.7639 21.4401 12.283 21.4401Z" fill="#ffffff"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 496 B |
@@ -50,6 +50,31 @@ export function roomAliasFromRoomName(roomName) {
|
||||
.toLowerCase();
|
||||
}
|
||||
|
||||
export function roomNameFromRoomId(roomId) {
|
||||
return roomId
|
||||
.match(/([^:]+):.*$/)[1]
|
||||
.substring(1)
|
||||
.split("-")
|
||||
.map((part) =>
|
||||
part.length > 0 ? part.charAt(0).toUpperCase() + part.slice(1) : part
|
||||
)
|
||||
.join(" ");
|
||||
}
|
||||
|
||||
export function isLocalRoomId(roomId) {
|
||||
if (!roomId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const parts = roomId.match(/[^:]+:(.*)$/);
|
||||
|
||||
if (parts.length < 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return parts[1] === defaultHomeserverHost;
|
||||
}
|
||||
|
||||
export async function createRoom(client, name) {
|
||||
const { room_id, room_alias } = await client.createRoom({
|
||||
visibility: "private",
|
||||
|
||||
76
src/room/FeedbackModal.jsx
Normal file
76
src/room/FeedbackModal.jsx
Normal file
@@ -0,0 +1,76 @@
|
||||
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";
|
||||
import { randomString } from "matrix-js-sdk/src/randomstring";
|
||||
|
||||
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");
|
||||
const rageshakeRequestId = randomString(16);
|
||||
|
||||
submitRageshake({
|
||||
description,
|
||||
sendLogs,
|
||||
rageshakeRequestId,
|
||||
});
|
||||
|
||||
if (inCall && sendLogs) {
|
||||
sendRageshakeRequest(roomId, rageshakeRequestId);
|
||||
}
|
||||
},
|
||||
[inCall, submitRageshake, roomId, sendRageshakeRequest]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (sent) {
|
||||
rest.onClose();
|
||||
}
|
||||
}, [sent, rest.onClose]);
|
||||
|
||||
return (
|
||||
<Modal title="Submit Feedback" isDismissable {...rest}>
|
||||
<ModalContent>
|
||||
<Body>Having trouble? 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>
|
||||
);
|
||||
}
|
||||
@@ -2,6 +2,8 @@ import React from "react";
|
||||
import { useLoadGroupCall } from "./useLoadGroupCall";
|
||||
import { ErrorView, FullScreenView } from "../FullScreenView";
|
||||
import { usePageTitle } from "../usePageTitle";
|
||||
import { isLocalRoomId } from "../matrix-utils";
|
||||
import { RoomNotFoundView } from "./RoomNotFoundView";
|
||||
|
||||
export function GroupCallLoader({ client, roomId, viaServers, children }) {
|
||||
const { loading, error, groupCall } = useLoadGroupCall(
|
||||
@@ -20,6 +22,16 @@ export function GroupCallLoader({ client, roomId, viaServers, children }) {
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
error &&
|
||||
(error.errcode === "M_NOT_FOUND" ||
|
||||
(error.message &&
|
||||
error.message.indexOf("Failed to fetch alias") !== -1)) &&
|
||||
isLocalRoomId(roomId)
|
||||
) {
|
||||
return <RoomNotFoundView client={client} roomId={roomId} />;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <ErrorView error={error} />;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ import { OverflowMenu } from "./OverflowMenu";
|
||||
import { GridLayoutMenu } from "./GridLayoutMenu";
|
||||
import { Avatar } from "../Avatar";
|
||||
import { UserMenuContainer } from "../UserMenuContainer";
|
||||
import { useRageshakeRequestModal } from "../settings/rageshake";
|
||||
import { RageshakeRequestModal } from "./RageshakeRequestModal";
|
||||
|
||||
const canScreenshare = "getDisplayMedia" in navigator.mediaDevices;
|
||||
// There is currently a bug in Safari our our code with cloning and sending MediaStreams
|
||||
@@ -45,7 +47,7 @@ export function InCallView({
|
||||
showInspector,
|
||||
roomId,
|
||||
}) {
|
||||
const [layout, setLayout] = useVideoGridLayout();
|
||||
const [layout, setLayout] = useVideoGridLayout(screenshareFeeds.length > 0);
|
||||
|
||||
const items = useMemo(() => {
|
||||
const participants = [];
|
||||
@@ -55,7 +57,7 @@ export function InCallView({
|
||||
id: callFeed.stream.id,
|
||||
callFeed,
|
||||
focused:
|
||||
screenshareFeeds.length === 0
|
||||
screenshareFeeds.length === 0 && layout === "spotlight"
|
||||
? callFeed.userId === activeSpeaker
|
||||
: false,
|
||||
});
|
||||
@@ -78,14 +80,14 @@ export function InCallView({
|
||||
}
|
||||
|
||||
return participants;
|
||||
}, [userMediaFeeds, activeSpeaker, screenshareFeeds]);
|
||||
}, [userMediaFeeds, activeSpeaker, screenshareFeeds, layout]);
|
||||
|
||||
const onFocusTile = useCallback(
|
||||
(tiles, focusedTile) => {
|
||||
if (layout === "freedom") {
|
||||
return tiles.map((tile) => {
|
||||
if (tile === focusedTile) {
|
||||
return { ...tile, presenter: !tile.presenter };
|
||||
return { ...tile, focused: !tile.focused };
|
||||
}
|
||||
|
||||
return tile;
|
||||
@@ -120,6 +122,11 @@ export function InCallView({
|
||||
[client]
|
||||
);
|
||||
|
||||
const {
|
||||
modalState: rageshakeRequestModalState,
|
||||
modalProps: rageshakeRequestModalProps,
|
||||
} = useRageshakeRequestModal(groupCall.room.roomId);
|
||||
|
||||
return (
|
||||
<div className={styles.inRoom}>
|
||||
<Header>
|
||||
@@ -149,6 +156,7 @@ export function InCallView({
|
||||
key={item.id}
|
||||
item={item}
|
||||
getAvatar={renderAvatar}
|
||||
showName={items.length > 2 || item.focused}
|
||||
{...rest}
|
||||
/>
|
||||
)}
|
||||
@@ -164,10 +172,12 @@ export function InCallView({
|
||||
/>
|
||||
)}
|
||||
<OverflowMenu
|
||||
inCall
|
||||
roomId={roomId}
|
||||
setShowInspector={setShowInspector}
|
||||
showInspector={showInspector}
|
||||
client={client}
|
||||
groupCall={groupCall}
|
||||
/>
|
||||
<HangupButton onPress={onLeave} />
|
||||
</div>
|
||||
@@ -176,6 +186,9 @@ export function InCallView({
|
||||
groupCall={groupCall}
|
||||
show={showInspector}
|
||||
/>
|
||||
{rageshakeRequestModalState.isOpen && (
|
||||
<RageshakeRequestModal {...rageshakeRequestModalProps} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,21 +6,27 @@ import { Item } from "@react-stately/collections";
|
||||
import { ReactComponent as SettingsIcon } from "../icons/Settings.svg";
|
||||
import { ReactComponent as AddUserIcon } from "../icons/AddUser.svg";
|
||||
import { ReactComponent as OverflowIcon } from "../icons/Overflow.svg";
|
||||
import { ReactComponent as FeedbackIcon } from "../icons/Feedback.svg";
|
||||
import { useModalTriggerState } from "../Modal";
|
||||
import { SettingsModal } from "../settings/SettingsModal";
|
||||
import { InviteModal } from "./InviteModal";
|
||||
import { Tooltip, TooltipTrigger } from "../Tooltip";
|
||||
import { TooltipTrigger } from "../Tooltip";
|
||||
import { FeedbackModal } from "./FeedbackModal";
|
||||
|
||||
export function OverflowMenu({
|
||||
roomId,
|
||||
setShowInspector,
|
||||
showInspector,
|
||||
client,
|
||||
inCall,
|
||||
groupCall,
|
||||
}) {
|
||||
const { modalState: inviteModalState, modalProps: inviteModalProps } =
|
||||
useModalTriggerState();
|
||||
const { modalState: settingsModalState, modalProps: settingsModalProps } =
|
||||
useModalTriggerState();
|
||||
const { modalState: feedbackModalState, modalProps: feedbackModalProps } =
|
||||
useModalTriggerState();
|
||||
|
||||
// TODO: On closing modal, focus should be restored to the trigger button
|
||||
// https://github.com/adobe/react-spectrum/issues/2444
|
||||
@@ -32,6 +38,9 @@ export function OverflowMenu({
|
||||
case "settings":
|
||||
settingsModalState.open();
|
||||
break;
|
||||
case "feedback":
|
||||
feedbackModalState.open();
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -54,6 +63,10 @@ export function OverflowMenu({
|
||||
<SettingsIcon />
|
||||
<span>Settings</span>
|
||||
</Item>
|
||||
<Item key="feedback" textValue="Submit Feedback">
|
||||
<FeedbackIcon />
|
||||
<span>Submit Feedback</span>
|
||||
</Item>
|
||||
</Menu>
|
||||
)}
|
||||
</PopoverMenuTrigger>
|
||||
@@ -68,6 +81,13 @@ export function OverflowMenu({
|
||||
{inviteModalState.isOpen && (
|
||||
<InviteModal roomId={roomId} {...inviteModalProps} />
|
||||
)}
|
||||
{feedbackModalState.isOpen && (
|
||||
<FeedbackModal
|
||||
{...feedbackModalProps}
|
||||
roomId={groupCall?.room.roomId}
|
||||
inCall={inCall}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
45
src/room/RageshakeRequestModal.jsx
Normal file
45
src/room/RageshakeRequestModal.jsx
Normal file
@@ -0,0 +1,45 @@
|
||||
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({ rageshakeRequestId, ...rest }) {
|
||||
const { submitRageshake, sending, sent, error } = useSubmitRageshake();
|
||||
|
||||
useEffect(() => {
|
||||
if (sent) {
|
||||
rest.onClose();
|
||||
}
|
||||
}, [sent, rest.onClose]);
|
||||
|
||||
return (
|
||||
<Modal title="Debug Log Request" isDismissable {...rest}>
|
||||
<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,
|
||||
rageshakeRequestId,
|
||||
})
|
||||
}
|
||||
disabled={sending}
|
||||
>
|
||||
{sending ? "Sending debug log..." : "Send debug log"}
|
||||
</Button>
|
||||
</FieldRow>
|
||||
{error && (
|
||||
<FieldRow>
|
||||
<ErrorMessage>{error.message}</ErrorMessage>
|
||||
</FieldRow>
|
||||
)}
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import { randomString } from "matrix-js-sdk/src/randomstring";
|
||||
import { useInteractiveRegistration } from "../auth/useInteractiveRegistration";
|
||||
import { Form } from "../form/Form";
|
||||
import { UserMenuContainer } from "../UserMenuContainer";
|
||||
import { generateRandomName } from "../auth/generateRandomName";
|
||||
|
||||
export function RoomAuthView() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -21,13 +22,20 @@ export function RoomAuthView() {
|
||||
(e) => {
|
||||
e.preventDefault();
|
||||
const data = new FormData(e.target);
|
||||
const userName = data.get("userName");
|
||||
const displayName = data.get("displayName");
|
||||
|
||||
async function submit() {
|
||||
setError(undefined);
|
||||
setLoading(true);
|
||||
const recaptchaResponse = await execute();
|
||||
await register(userName, randomString(16), recaptchaResponse, true);
|
||||
const userName = generateRandomName();
|
||||
await register(
|
||||
userName,
|
||||
randomString(16),
|
||||
displayName,
|
||||
recaptchaResponse,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
submit().catch((error) => {
|
||||
@@ -58,10 +66,10 @@ export function RoomAuthView() {
|
||||
<Form className={styles.form} onSubmit={onSubmit}>
|
||||
<FieldRow>
|
||||
<InputField
|
||||
id="userName"
|
||||
name="userName"
|
||||
label="Pick a user name"
|
||||
placeholder="Pick a user name"
|
||||
id="displayName"
|
||||
name="displayName"
|
||||
label="Display Name"
|
||||
placeholder="Display Name"
|
||||
type="text"
|
||||
required
|
||||
autoComplete="off"
|
||||
|
||||
76
src/room/RoomNotFoundView.jsx
Normal file
76
src/room/RoomNotFoundView.jsx
Normal file
@@ -0,0 +1,76 @@
|
||||
import React, { useState, useCallback } from "react";
|
||||
import { FullScreenView } from "../FullScreenView";
|
||||
import { Headline, Subtitle } from "../typography/Typography";
|
||||
import { createRoom, roomNameFromRoomId } from "../matrix-utils";
|
||||
import { FieldRow, ErrorMessage, InputField } from "../input/Input";
|
||||
import { Button } from "../button";
|
||||
import { Form } from "../form/Form";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import styles from "./RoomNotFoundView.module.css";
|
||||
|
||||
export function RoomNotFoundView({ client, roomId }) {
|
||||
const history = useHistory();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState();
|
||||
const roomName = roomNameFromRoomId(roomId);
|
||||
const onSubmit = useCallback(
|
||||
(e) => {
|
||||
e.preventDefault();
|
||||
|
||||
async function submit() {
|
||||
setError(undefined);
|
||||
setLoading(true);
|
||||
|
||||
const roomIdOrAlias = await createRoom(client, roomName);
|
||||
|
||||
if (roomIdOrAlias) {
|
||||
history.push(`/room/${roomIdOrAlias}`);
|
||||
}
|
||||
}
|
||||
|
||||
submit().catch((error) => {
|
||||
console.error(error);
|
||||
setLoading(false);
|
||||
setError(error);
|
||||
});
|
||||
},
|
||||
[client, roomName]
|
||||
);
|
||||
|
||||
return (
|
||||
<FullScreenView>
|
||||
<Headline>Call Not Found</Headline>
|
||||
<Subtitle>Would you like to create this call?</Subtitle>
|
||||
<Form onSubmit={onSubmit} className={styles.form}>
|
||||
<FieldRow>
|
||||
<InputField
|
||||
id="callName"
|
||||
name="callName"
|
||||
label="Call name"
|
||||
placeholder="Call name"
|
||||
type="text"
|
||||
required
|
||||
autoComplete="off"
|
||||
value={roomName}
|
||||
disabled
|
||||
/>
|
||||
</FieldRow>
|
||||
<FieldRow>
|
||||
<Button
|
||||
type="submit"
|
||||
size="lg"
|
||||
disabled={loading}
|
||||
className={styles.button}
|
||||
>
|
||||
{loading ? "Loading..." : "Create Room"}
|
||||
</Button>
|
||||
</FieldRow>
|
||||
{error && (
|
||||
<FieldRow>
|
||||
<ErrorMessage>{error.message}</ErrorMessage>
|
||||
</FieldRow>
|
||||
)}
|
||||
</Form>
|
||||
</FullScreenView>
|
||||
);
|
||||
}
|
||||
11
src/room/RoomNotFoundView.module.css
Normal file
11
src/room/RoomNotFoundView.module.css
Normal file
@@ -0,0 +1,11 @@
|
||||
.form {
|
||||
padding: 0 24px;
|
||||
justify-content: center;
|
||||
max-width: 409px;
|
||||
width: calc(100% - 48px);
|
||||
margin-bottom: 72px;
|
||||
}
|
||||
|
||||
.button {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -32,7 +32,7 @@ export function RoomPage() {
|
||||
const params = new URLSearchParams(search);
|
||||
return [params.has("simple"), params.getAll("via")];
|
||||
}, [search]);
|
||||
const roomId = maybeRoomId || hash;
|
||||
const roomId = (maybeRoomId || hash || "").toLowerCase();
|
||||
|
||||
if (loading) {
|
||||
return <LoadingView />;
|
||||
|
||||
@@ -11,7 +11,7 @@ export function RoomRedirect() {
|
||||
let roomId = pathname;
|
||||
|
||||
if (pathname.startsWith("/")) {
|
||||
roomId = roomId.substr(1, roomId.length);
|
||||
roomId = roomId.substring(1, roomId.length);
|
||||
}
|
||||
|
||||
if (!roomId.startsWith("#") && !roomId.startsWith("!")) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from "react";
|
||||
import React from "react";
|
||||
import { Modal } from "../Modal";
|
||||
import styles from "./SettingsModal.module.css";
|
||||
import { TabContainer, TabItem } from "../tabs/Tabs";
|
||||
@@ -8,10 +8,9 @@ import { ReactComponent as DeveloperIcon } from "../icons/Developer.svg";
|
||||
import { SelectInput } from "../input/SelectInput";
|
||||
import { Item } from "@react-stately/collections";
|
||||
import { useMediaHandler } from "./useMediaHandler";
|
||||
import { FieldRow, InputField, ErrorMessage } from "../input/Input";
|
||||
import { FieldRow, InputField } from "../input/Input";
|
||||
import { Button } from "../button";
|
||||
import { useSubmitRageshake } from "./useSubmitRageshake";
|
||||
import { Subtitle } from "../typography/Typography";
|
||||
import { useDownloadDebugLog } from "./rageshake";
|
||||
|
||||
export function SettingsModal({
|
||||
client,
|
||||
@@ -28,10 +27,7 @@ export function SettingsModal({
|
||||
setVideoInput,
|
||||
} = useMediaHandler(client);
|
||||
|
||||
const [description, setDescription] = useState("");
|
||||
|
||||
const { submitRageshake, sending, sent, error, downloadDebugLog } =
|
||||
useSubmitRageshake();
|
||||
const downloadDebugLog = useDownloadDebugLog();
|
||||
|
||||
return (
|
||||
<Modal
|
||||
@@ -96,31 +92,6 @@ export function SettingsModal({
|
||||
onChange={(e) => setShowInspector(e.target.checked)}
|
||||
/>
|
||||
</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>
|
||||
<Button onPress={downloadDebugLog}>Download Debug Logs</Button>
|
||||
</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 pako from "pako";
|
||||
import { useClient } from "../ClientContext";
|
||||
import { InspectorContext } from "../room/GroupCallInspector";
|
||||
import { useModalTriggerState } from "../Modal";
|
||||
|
||||
export function useSubmitRageshake() {
|
||||
const { client } = useClient();
|
||||
@@ -171,23 +172,32 @@ export function useSubmitRageshake() {
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
const logs = await rageshake.getLogsForReport();
|
||||
if (opts.sendLogs) {
|
||||
const logs = await rageshake.getLogsForReport();
|
||||
|
||||
for (const entry of logs) {
|
||||
// encode as UTF-8
|
||||
let buf = new TextEncoder().encode(entry.lines);
|
||||
for (const entry of logs) {
|
||||
// encode as UTF-8
|
||||
let buf = new TextEncoder().encode(entry.lines);
|
||||
|
||||
// compress
|
||||
buf = pako.gzip(buf);
|
||||
// compress
|
||||
buf = pako.gzip(buf);
|
||||
|
||||
body.append("compressed-log", new Blob([buf]), entry.id);
|
||||
body.append("compressed-log", new Blob([buf]), entry.id);
|
||||
}
|
||||
|
||||
if (json) {
|
||||
body.append(
|
||||
"file",
|
||||
new Blob([JSON.stringify(json)], { type: "text/plain" }),
|
||||
"groupcall.txt"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (json) {
|
||||
if (opts.rageshakeRequestId) {
|
||||
body.append(
|
||||
"file",
|
||||
new Blob([JSON.stringify(json)], { type: "text/plain" }),
|
||||
"groupcall.txt"
|
||||
"group_call_rageshake_request_id",
|
||||
opts.rageshakeRequestId
|
||||
);
|
||||
}
|
||||
|
||||
@@ -209,6 +219,17 @@ export function useSubmitRageshake() {
|
||||
[client]
|
||||
);
|
||||
|
||||
return {
|
||||
submitRageshake,
|
||||
sending,
|
||||
sent,
|
||||
error,
|
||||
};
|
||||
}
|
||||
|
||||
export function useDownloadDebugLog() {
|
||||
const [{ json }] = useContext(InspectorContext);
|
||||
|
||||
const downloadDebugLog = useCallback(() => {
|
||||
const blob = new Blob([JSON.stringify(json)], { type: "application/json" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
@@ -222,7 +243,51 @@ export function useSubmitRageshake() {
|
||||
URL.revokeObjectURL(url);
|
||||
el.parentNode.removeChild(el);
|
||||
}, 0);
|
||||
});
|
||||
}, [json]);
|
||||
|
||||
return { submitRageshake, sending, sent, error, downloadDebugLog };
|
||||
return downloadDebugLog;
|
||||
}
|
||||
|
||||
export function useRageshakeRequest() {
|
||||
const { client } = useClient();
|
||||
|
||||
const sendRageshakeRequest = useCallback(
|
||||
(roomId, rageshakeRequestId) => {
|
||||
client.sendEvent(roomId, "org.matrix.rageshake_request", {
|
||||
request_id: rageshakeRequestId,
|
||||
});
|
||||
},
|
||||
[client]
|
||||
);
|
||||
|
||||
return sendRageshakeRequest;
|
||||
}
|
||||
|
||||
export function useRageshakeRequestModal(roomId) {
|
||||
const { modalState, modalProps } = useModalTriggerState();
|
||||
const { client } = useClient();
|
||||
const [rageshakeRequestId, setRageshakeRequestId] = useState();
|
||||
|
||||
useEffect(() => {
|
||||
const onEvent = (event) => {
|
||||
const type = event.getType();
|
||||
|
||||
if (
|
||||
type === "org.matrix.rageshake_request" &&
|
||||
roomId === event.getRoomId() &&
|
||||
client.getUserId() !== event.getSender()
|
||||
) {
|
||||
setRageshakeRequestId(event.getContent().request_id);
|
||||
modalState.open();
|
||||
}
|
||||
};
|
||||
|
||||
client.on("event", onEvent);
|
||||
|
||||
return () => {
|
||||
client.removeListener("event", onEvent);
|
||||
};
|
||||
}, [modalState.open, roomId]);
|
||||
|
||||
return { modalState, modalProps: { ...modalProps, rageshakeRequestId } };
|
||||
}
|
||||
@@ -15,7 +15,7 @@ export default {
|
||||
};
|
||||
|
||||
export const ParticipantsTest = () => {
|
||||
const [layout, setLayout] = useVideoGridLayout();
|
||||
const [layout, setLayout] = useVideoGridLayout(false);
|
||||
const [participantCount, setParticipantCount] = useState(1);
|
||||
|
||||
const items = useMemo(
|
||||
@@ -60,7 +60,12 @@ export const ParticipantsTest = () => {
|
||||
>
|
||||
<VideoGrid layout={layout} items={items}>
|
||||
{({ item, ...rest }) => (
|
||||
<VideoTile key={item.id} name={`User ${item.id}`} {...rest} />
|
||||
<VideoTile
|
||||
key={item.id}
|
||||
name={`User ${item.id}`}
|
||||
showName={items.length > 2 || item.focused}
|
||||
{...rest}
|
||||
/>
|
||||
)}
|
||||
</VideoGrid>
|
||||
</div>
|
||||
|
||||
@@ -12039,6 +12039,11 @@ unique-filename@^1.1.1:
|
||||
dependencies:
|
||||
unique-slug "^2.0.0"
|
||||
|
||||
unique-names-generator@^4.6.0:
|
||||
version "4.6.0"
|
||||
resolved "https://registry.yarnpkg.com/unique-names-generator/-/unique-names-generator-4.6.0.tgz#852c1db8149815d6cf665a601820fe80ec2fbc37"
|
||||
integrity sha512-m0fke1emBeT96UYn2psPQYwljooDWRTKt9oUZ5vlt88ZFMBGxqwPyLHXwCfkbgdm8jzioCp7oIpo6KdM+fnUlQ==
|
||||
|
||||
unique-slug@^2.0.0:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.2.tgz#baabce91083fc64e945b0f3ad613e264f7cd4e6c"
|
||||
|
||||
Reference in New Issue
Block a user