diff --git a/package.json b/package.json index f41a71d8..c57edebd 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "classnames": "^2.3.1", "color-hash": "^2.0.1", "events": "^3.3.0", - "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#984dd26a138411ef73903ff4e635f2752e0829f2", + "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#e876482e62421bb6a1ba58078435c6b3d1210f15", "mermaid": "^8.13.8", "normalize.css": "^8.0.1", "pako": "^2.0.4", diff --git a/src/App.jsx b/src/App.jsx index 3c882c51..1782f69e 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -26,37 +26,45 @@ import { RoomRedirect } from "./room/RoomRedirect"; import { ClientProvider } from "./ClientContext"; import { usePageFocusStyle } from "./usePageFocusStyle"; import { SequenceDiagramViewerPage } from "./SequenceDiagramViewerPage"; +import { InspectorContextProvider } from "./room/GroupCallInspector"; +import { CrashView } from "./FullScreenView"; const SentryRoute = Sentry.withSentryRouting(Route); export default function App({ history }) { usePageFocusStyle(); + const errorPage = ; + return ( - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + ); diff --git a/src/FullScreenView.jsx b/src/FullScreenView.jsx index fc252868..6dd71277 100644 --- a/src/FullScreenView.jsx +++ b/src/FullScreenView.jsx @@ -4,6 +4,8 @@ import styles from "./FullScreenView.module.css"; import { Header, HeaderLogo, LeftNav, RightNav } from "./Header"; import classNames from "classnames"; import { LinkButton, Button } from "./button"; +import { useSubmitRageshake } from "./settings/submit-rageshake"; +import { ErrorMessage } from "./input/Input"; export function FullScreenView({ className, children }) { return ( @@ -59,6 +61,56 @@ export function ErrorView({ error }) { ); } +export function CrashView() { + const { submitRageshake, sending, sent, error } = useSubmitRageshake(); + + const sendDebugLogs = useCallback(() => { + submitRageshake({ + description: "**Soft Crash**", + sendLogs: true, + }); + }, [submitRageshake]); + + const onReload = useCallback(() => { + window.location = "/"; + }, []); + + let logsComponent; + if (sent) { + logsComponent =
Thanks! We'll get right on it.
; + } else if (sending) { + logsComponent =
Sending...
; + } else { + logsComponent = ( + + ); + } + + return ( + +

Oops, something's gone wrong.

+

Submitting debug logs will help us track down the problem.

+
{logsComponent}
+ {error && Couldn't send debug logs!} + +
+ ); +} + export function LoadingView() { return ( diff --git a/src/FullScreenView.module.css b/src/FullScreenView.module.css index f30d9f6c..9d8fc665 100644 --- a/src/FullScreenView.module.css +++ b/src/FullScreenView.module.css @@ -36,6 +36,12 @@ margin-bottom: 0; } -.homeLink { +/* Make the buttons the same width */ +.wideButton { width: 291px; } + +/* Fixed height to avoid content jumping around*/ +.sendLogsSection { + height: 50px; +} diff --git a/src/button/Button.tsx b/src/button/Button.tsx index 83e3697c..bd7a0dd4 100644 --- a/src/button/Button.tsx +++ b/src/button/Button.tsx @@ -30,6 +30,7 @@ import { ReactComponent as SettingsIcon } from "../icons/Settings.svg"; import { ReactComponent as AddUserIcon } from "../icons/AddUser.svg"; import { ReactComponent as ArrowDownIcon } from "../icons/ArrowDown.svg"; import { TooltipTrigger } from "../Tooltip"; +import { ReactComponent as OverflowIcon } from "../icons/Overflow.svg"; export type ButtonVariant = | "default" @@ -237,3 +238,14 @@ export function InviteButton({ ); } + +export function OptionsButton(props: Omit) { + return ( + + + {() => "Options"} + + ); +} diff --git a/src/home/RegisteredView.jsx b/src/home/RegisteredView.jsx index 1d07d1c7..b5fef745 100644 --- a/src/home/RegisteredView.jsx +++ b/src/home/RegisteredView.jsx @@ -47,7 +47,7 @@ export function RegisteredView({ client }) { setError(undefined); setLoading(true); - const roomIdOrAlias = await createRoom(client, roomName, ptt); + const [roomIdOrAlias] = await createRoom(client, roomName, ptt); if (roomIdOrAlias) { history.push(`/room/${roomIdOrAlias}`); diff --git a/src/home/UnauthenticatedView.jsx b/src/home/UnauthenticatedView.jsx index f324d504..a6d7a6ed 100644 --- a/src/home/UnauthenticatedView.jsx +++ b/src/home/UnauthenticatedView.jsx @@ -70,7 +70,7 @@ export function UnauthenticatedView() { let roomIdOrAlias; try { - roomIdOrAlias = await createRoom(client, roomName, ptt); + [roomIdOrAlias] = await createRoom(client, roomName, ptt); } catch (error) { if (error.errcode === "M_ROOM_IN_USE") { setOnFinished(() => () => { diff --git a/src/main.tsx b/src/main.tsx index 50aa6cd4..2330bedc 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -28,9 +28,7 @@ import { Integrations } from "@sentry/tracing"; import "./index.css"; import App from "./App"; -import { ErrorView } from "./FullScreenView"; import { init as initRageshake } from "./settings/rageshake"; -import { InspectorContextProvider } from "./room/GroupCallInspector"; initRageshake(); @@ -104,11 +102,7 @@ Sentry.init({ ReactDOM.render( - - - - - + , document.getElementById("root") ); diff --git a/src/matrix-utils.ts b/src/matrix-utils.ts index 9b6a005b..5986954b 100644 --- a/src/matrix-utils.ts +++ b/src/matrix-utils.ts @@ -220,8 +220,8 @@ export function isLocalRoomId(roomId: string): boolean { export async function createRoom( client: MatrixClient, name: string -): Promise { - await client.createRoom({ +): Promise<[string, string]> { + const result = await client.createRoom({ visibility: Visibility.Private, preset: Preset.PublicChat, name, @@ -251,7 +251,7 @@ export async function createRoom( }, }); - return fullAliasFromRoomName(name, client); + return [fullAliasFromRoomName(name, client), result.room_id]; } export function getRoomUrl(roomId: string): string { diff --git a/src/room/useLoadGroupCall.ts b/src/room/useLoadGroupCall.ts index 0d7fb7c7..301ba54f 100644 --- a/src/room/useLoadGroupCall.ts +++ b/src/room/useLoadGroupCall.ts @@ -21,6 +21,7 @@ import { GroupCallIntent, } from "matrix-js-sdk/src/webrtc/groupCall"; import { GroupCallEventHandlerEvent } from "matrix-js-sdk/src/webrtc/groupCallEventHandler"; +import { ClientEvent } from "matrix-js-sdk/src/client"; import type { MatrixClient } from "matrix-js-sdk/src/client"; import type { Room } from "matrix-js-sdk/src/models/room"; @@ -44,9 +45,38 @@ export const useLoadGroupCall = ( useEffect(() => { setState({ loading: true }); + const waitForRoom = async (roomId: string): Promise => { + const room = client.getRoom(roomId); + if (room) return room; + console.log(`Room ${roomId} hasn't arrived yet: waiting`); + + const waitPromise = new Promise((resolve) => { + const onRoomEvent = async (room: Room) => { + if (room.roomId === roomId) { + client.removeListener(ClientEvent.Room, onRoomEvent); + resolve(room); + } + }; + client.on(ClientEvent.Room, onRoomEvent); + }); + + // race the promise with a timeout so we don't + // wait forever for the room + const timeoutPromise = new Promise((_, reject) => { + setTimeout(() => { + reject(new Error("Timed out trying to join room")); + }, 30000); + }); + + return Promise.race([waitPromise, timeoutPromise]); + }; + const fetchOrCreateRoom = async (): Promise => { try { - return await client.joinRoom(roomIdOrAlias, { viaServers }); + const room = await client.joinRoom(roomIdOrAlias, { viaServers }); + // wait for the room to come down the sync stream, otherwise + // client.getRoom() won't return the room. + return waitForRoom(room.roomId); } catch (error) { if ( isLocalRoomId(roomIdOrAlias) && @@ -55,8 +85,12 @@ export const useLoadGroupCall = ( error.message.indexOf("Failed to fetch alias") !== -1)) ) { // The room doesn't exist, but we can create it - await createRoom(client, roomNameFromRoomId(roomIdOrAlias)); - return await client.joinRoom(roomIdOrAlias, { viaServers }); + const [, roomId] = await createRoom( + client, + roomNameFromRoomId(roomIdOrAlias) + ); + // likewise, wait for the room + return await waitForRoom(roomId); } else { throw error; } diff --git a/src/video-grid/VideoTile.jsx b/src/video-grid/VideoTile.jsx index 2dd41921..206f8a46 100644 --- a/src/video-grid/VideoTile.jsx +++ b/src/video-grid/VideoTile.jsx @@ -20,6 +20,7 @@ import classNames from "classnames"; import styles from "./VideoTile.module.css"; import { ReactComponent as MicMutedIcon } from "../icons/MicMuted.svg"; import { ReactComponent as VideoMutedIcon } from "../icons/VideoMuted.svg"; +import { OptionsButton } from "../button/Button"; export const VideoTile = forwardRef( ( @@ -35,6 +36,8 @@ export const VideoTile = forwardRef( name, showName, mediaRef, + onOptionsPress, + showOptions, ...rest }, ref @@ -62,13 +65,18 @@ export const VideoTile = forwardRef( ) : ( (showName || audioMuted || (videoMuted && !noVideo)) && ( -
+
{audioMuted && !(videoMuted && !noVideo) && } {videoMuted && !noVideo && } {showName && {name}}
) )} + {showOptions && ( +
+ +
+ )}