From b3ceb5300c2d21c32bc40715d0bfa8257a45b7b1 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Tue, 19 Nov 2024 16:57:57 +0000 Subject: [PATCH 01/10] Add keyboard shortcuts for raised hand / reactions (#2784) * Add support for reactions / raised-hands via keyboard shortcuts. * Add tests * Fixup shortcuts * update snapshotr * fix type * keyshortcuts * remove mistakenly commited file * fix label logic * Add renderer for call joined / left * Use caption * lint * remove unexpected file * remove other unexpected change * Remove other other unexpected change. --- src/button/ReactionToggleButton.test.tsx | 22 ++- src/button/ReactionToggleButton.tsx | 128 ++++++------------ .../ReactionToggleButton.test.tsx.snap | 2 +- src/reactions/index.ts | 4 +- src/room/InCallView.tsx | 8 +- src/tile/GridTile.tsx | 7 +- src/useCallViewKeyboardShortcuts.test.tsx | 33 ++++- src/useCallViewKeyboardShortcuts.ts | 15 ++ src/useReactions.tsx | 81 +++++++++-- 9 files changed, 173 insertions(+), 127 deletions(-) diff --git a/src/button/ReactionToggleButton.test.tsx b/src/button/ReactionToggleButton.test.tsx index 15997637..a1498304 100644 --- a/src/button/ReactionToggleButton.test.tsx +++ b/src/button/ReactionToggleButton.test.tsx @@ -7,7 +7,6 @@ Please see LICENSE in the repository root for full details. import { render } from "@testing-library/react"; import { expect, test } from "vitest"; -import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc"; import { TooltipProvider } from "@vector-im/compound-web"; import { userEvent } from "@testing-library/user-event"; import { ReactNode } from "react"; @@ -29,18 +28,13 @@ const membership: Record = { function TestComponent({ rtcSession, - room, }: { rtcSession: MockRTCSession; - room: MockRoom; }): ReactNode { return ( - + ); @@ -51,7 +45,7 @@ test("Can open menu", async () => { const room = new MockRoom(memberUserIdAlice); const rtcSession = new MockRTCSession(room, membership); const { getByLabelText, container } = render( - , + , ); await user.click(getByLabelText("common.reactions")); expect(container).toMatchSnapshot(); @@ -62,7 +56,7 @@ test("Can raise hand", async () => { const room = new MockRoom(memberUserIdAlice); const rtcSession = new MockRTCSession(room, membership); const { getByLabelText, container } = render( - , + , ); await user.click(getByLabelText("common.reactions")); await user.click(getByLabelText("action.raise_hand")); @@ -87,7 +81,7 @@ test("Can lower hand", async () => { const room = new MockRoom(memberUserIdAlice); const rtcSession = new MockRTCSession(room, membership); const { getByLabelText, container } = render( - , + , ); const reactionEvent = room.testSendHandRaise(memberEventAlice, membership); await user.click(getByLabelText("common.reactions")); @@ -101,7 +95,7 @@ test("Can react with emoji", async () => { const room = new MockRoom(memberUserIdAlice); const rtcSession = new MockRTCSession(room, membership); const { getByLabelText, getByText } = render( - , + , ); await user.click(getByLabelText("common.reactions")); await user.click(getByText("🐶")); @@ -126,7 +120,7 @@ test("Can fully expand emoji picker", async () => { const room = new MockRoom(memberUserIdAlice); const rtcSession = new MockRTCSession(room, membership); const { getByText, container, getByLabelText } = render( - , + , ); await user.click(getByLabelText("common.reactions")); await user.click(getByLabelText("action.show_more")); @@ -149,12 +143,12 @@ test("Can fully expand emoji picker", async () => { ]); }); -test("Can close search", async () => { +test("Can close reaction dialog", async () => { const user = userEvent.setup(); const room = new MockRoom(memberUserIdAlice); const rtcSession = new MockRTCSession(room, membership); const { getByLabelText, container } = render( - , + , ); await user.click(getByLabelText("common.reactions")); await user.click(getByLabelText("action.show_more")); diff --git a/src/button/ReactionToggleButton.tsx b/src/button/ReactionToggleButton.tsx index f203947e..b1d6ec3e 100644 --- a/src/button/ReactionToggleButton.tsx +++ b/src/button/ReactionToggleButton.tsx @@ -23,19 +23,11 @@ import { } from "react"; import { useTranslation } from "react-i18next"; import { logger } from "matrix-js-sdk/src/logger"; -import { EventType, RelationType } from "matrix-js-sdk/src/matrix"; -import { MatrixClient } from "matrix-js-sdk/src/client"; -import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession"; import classNames from "classnames"; import { useReactions } from "../useReactions"; -import { useMatrixRTCSessionMemberships } from "../useMatrixRTCSessionMemberships"; import styles from "./ReactionToggleButton.module.css"; -import { - ReactionOption, - ReactionSet, - ElementCallReactionEventType, -} from "../reactions"; +import { ReactionOption, ReactionSet, ReactionsRowSize } from "../reactions"; import { Modal } from "../Modal"; interface InnerButtonProps extends ComponentPropsWithoutRef<"button"> { @@ -95,9 +87,10 @@ export function ReactionPopupMenu({ )}
- + toggleRaisedHand()} @@ -114,14 +107,26 @@ export function ReactionPopupMenu({ styles.reactionsMenu, )} > - {filteredReactionSet.map((reaction) => ( + {filteredReactionSet.map((reaction, index) => (
  • - + sendReaction(reaction)} + aria-keyshortcuts={ + index < ReactionsRowSize + ? (index + 1).toString() + : undefined + } > {reaction.emoji} @@ -153,52 +158,33 @@ export function ReactionPopupMenu({ } interface ReactionToggleButtonProps extends ComponentPropsWithoutRef<"button"> { - rtcSession: MatrixRTCSession; - client: MatrixClient; + userId: string; } export function ReactionToggleButton({ - client, - rtcSession, + userId, ...props }: ReactionToggleButtonProps): ReactNode { const { t } = useTranslation(); - const { raisedHands, lowerHand, reactions } = useReactions(); + const { raisedHands, toggleRaisedHand, sendReaction, reactions } = + useReactions(); const [busy, setBusy] = useState(false); - const userId = client.getUserId()!; - const isHandRaised = !!raisedHands[userId]; - const memberships = useMatrixRTCSessionMemberships(rtcSession); const [showReactionsMenu, setShowReactionsMenu] = useState(false); const [errorText, setErrorText] = useState(); + const isHandRaised = !!raisedHands[userId]; + const canReact = !reactions[userId]; + useEffect(() => { // Clear whenever the reactions menu state changes. setErrorText(undefined); }, [showReactionsMenu]); - const canReact = !reactions[userId]; - const sendRelation = useCallback( async (reaction: ReactionOption) => { try { - const myMembership = memberships.find((m) => m.sender === userId); - if (!myMembership?.eventId) { - throw new Error("Cannot find own membership event"); - } - const parentEventId = myMembership.eventId; setBusy(true); - await client.sendEvent( - rtcSession.room.roomId, - ElementCallReactionEventType, - { - "m.relates_to": { - rel_type: RelationType.Reference, - event_id: parentEventId, - }, - emoji: reaction.emoji, - name: reaction.name, - }, - ); + await sendReaction(reaction); setErrorText(undefined); setShowReactionsMenu(false); } catch (ex) { @@ -208,59 +194,25 @@ export function ReactionToggleButton({ setBusy(false); } }, - [memberships, client, userId, rtcSession], + [sendReaction], ); - const toggleRaisedHand = useCallback(() => { - const raiseHand = async (): Promise => { - if (isHandRaised) { - try { - setBusy(true); - await lowerHand(); - setShowReactionsMenu(false); - } finally { - setBusy(false); - } - } else { - try { - const myMembership = memberships.find((m) => m.sender === userId); - if (!myMembership?.eventId) { - throw new Error("Cannot find own membership event"); - } - const parentEventId = myMembership.eventId; - setBusy(true); - const reaction = await client.sendEvent( - rtcSession.room.roomId, - EventType.Reaction, - { - "m.relates_to": { - rel_type: RelationType.Annotation, - event_id: parentEventId, - key: "🖐️", - }, - }, - ); - logger.debug("Sent raise hand event", reaction.event_id); - setErrorText(undefined); - setShowReactionsMenu(false); - } catch (ex) { - setErrorText(ex instanceof Error ? ex.message : "Unknown error"); - logger.error("Failed to raise hand", ex); - } finally { - setBusy(false); - } + const wrappedToggleRaisedHand = useCallback(() => { + const toggleHand = async (): Promise => { + try { + setBusy(true); + await toggleRaisedHand(); + setShowReactionsMenu(false); + } catch (ex) { + setErrorText(ex instanceof Error ? ex.message : "Unknown error"); + logger.error("Failed to raise/lower hand", ex); + } finally { + setBusy(false); } }; - void raiseHand(); - }, [ - client, - isHandRaised, - memberships, - lowerHand, - rtcSession.room.roomId, - userId, - ]); + void toggleHand(); + }, [toggleRaisedHand]); return ( <> @@ -284,7 +236,7 @@ export function ReactionToggleButton({ isHandRaised={isHandRaised} canReact={!busy && canReact} sendReaction={(reaction) => void sendRelation(reaction)} - toggleRaisedHand={toggleRaisedHand} + toggleRaisedHand={wrappedToggleRaisedHand} /> diff --git a/src/button/__snapshots__/ReactionToggleButton.test.tsx.snap b/src/button/__snapshots__/ReactionToggleButton.test.tsx.snap index e4980da6..dd4227e1 100644 --- a/src/button/__snapshots__/ReactionToggleButton.test.tsx.snap +++ b/src/button/__snapshots__/ReactionToggleButton.test.tsx.snap @@ -1,6 +1,6 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`Can close search 1`] = ` +exports[`Can close reaction dialog 1`] = `