Skip to content

Commit

Permalink
Tie up last bits of useReactions
Browse files Browse the repository at this point in the history
  • Loading branch information
Half-Shot committed Dec 9, 2024
1 parent b10863d commit de19565
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 38 deletions.
26 changes: 17 additions & 9 deletions src/button/ReactionToggleButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ import { useReactions } from "../useReactions";
import styles from "./ReactionToggleButton.module.css";
import { ReactionOption, ReactionSet, ReactionsRowSize } from "../reactions";
import { Modal } from "../Modal";
import { CallViewModel } from "../state/CallViewModel";

Check failure on line 32 in src/button/ReactionToggleButton.tsx

View workflow job for this annotation

GitHub Actions / Lint, format & type check

There should be at least one empty line between import groups
import { useObservableState } from "observable-hooks";

Check failure on line 33 in src/button/ReactionToggleButton.tsx

View workflow job for this annotation

GitHub Actions / Lint, format & type check

`observable-hooks` import should occur before import of `../useReactions`
import { map } from "rxjs";

Check failure on line 34 in src/button/ReactionToggleButton.tsx

View workflow job for this annotation

GitHub Actions / Lint, format & type check

`rxjs` import should occur before import of `../useReactions`

interface InnerButtonProps extends ComponentPropsWithoutRef<"button"> {
raised: boolean;
Expand Down Expand Up @@ -158,22 +161,27 @@ export function ReactionPopupMenu({
}

interface ReactionToggleButtonProps extends ComponentPropsWithoutRef<"button"> {
userId: string;
identifier: string;
vm: CallViewModel;
}

export function ReactionToggleButton({
userId,
identifier,
vm,
...props
}: ReactionToggleButtonProps): ReactNode {
const { t } = useTranslation();
const { raisedHands, toggleRaisedHand, sendReaction, reactions } =
useReactions();
const { toggleRaisedHand, sendReaction } = useReactions();
const [busy, setBusy] = useState(false);
const [showReactionsMenu, setShowReactionsMenu] = useState(false);
const [errorText, setErrorText] = useState<string>();

const isHandRaised = !!raisedHands[userId];
const canReact = !reactions[userId];
const isHandRaised = useObservableState(
vm.handsRaised.pipe(map((v) => !!v[identifier])),
);
const canReact = useObservableState(
vm.reactions.pipe(map((v) => !!v[identifier])),
);

useEffect(() => {
// Clear whenever the reactions menu state changes.
Expand Down Expand Up @@ -219,7 +227,7 @@ export function ReactionToggleButton({
<InnerButton
disabled={busy}
onClick={() => setShowReactionsMenu((show) => !show)}
raised={isHandRaised}
raised={!!isHandRaised}
open={showReactionsMenu}
{...props}
/>
Expand All @@ -233,8 +241,8 @@ export function ReactionToggleButton({
>
<ReactionPopupMenu
errorText={errorText}
isHandRaised={isHandRaised}
canReact={!busy && canReact}
isHandRaised={!!isHandRaised}
canReact={!busy && !!canReact}
sendReaction={(reaction) => void sendRelation(reaction)}
toggleRaisedHand={wrappedToggleRaisedHand}
/>
Expand Down
5 changes: 2 additions & 3 deletions src/room/CallEventAudioRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/

import { ReactNode, useDeferredValue, useEffect, useMemo } from "react";
import { filter, interval, map, scan, throttle } from "rxjs";
import { ReactNode, useEffect } from "react";
import { filter, interval, throttle } from "rxjs";

import { CallViewModel } from "../state/CallViewModel";
import joinCallSoundMp3 from "../sound/join_call.mp3";
Expand All @@ -17,7 +17,6 @@ import handSoundOgg from "../sound/raise_hand.ogg?url";
import handSoundMp3 from "../sound/raise_hand.mp3?url";
import { useAudioContext } from "../useAudioContext";
import { prefetchSounds } from "../soundUtils";
import { useReactions } from "../useReactions";
import { useLatest } from "../useLatest";

// Do not play any sounds if the participant count has exceeded this
Expand Down
3 changes: 2 additions & 1 deletion src/room/InCallView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -544,9 +544,10 @@ export const InCallView: FC<InCallViewProps> = ({
if (supportsReactions) {
buttons.push(
<ReactionToggleButton
vm={vm}
key="raise_hand"
className={styles.raiseHand}
userId={client.getUserId()!}
identifier={`${client.getUserId()}:${client.getDeviceId()}`}
onTouchEnd={onControlsTouchEnd}
/>,
);
Expand Down
2 changes: 1 addition & 1 deletion src/state/CallViewModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1118,7 +1118,7 @@ export class CallViewModel extends ViewModel {
);

public readonly handsRaised = new Subject<Record<string, Date>>();

Check failure on line 1120 in src/state/CallViewModel.ts

View workflow job for this annotation

GitHub Actions / Lint, format & type check

Subject 'handsRaised' must be private
private readonly reactions = new Subject<Record<string, ReactionOption>>();
public readonly reactions = new Subject<Record<string, ReactionOption>>();

Check failure on line 1121 in src/state/CallViewModel.ts

View workflow job for this annotation

GitHub Actions / Lint, format & type check

Subject 'reactions' must be private

public updateReactions(data: ReturnType<typeof useReactions>) {

Check failure on line 1123 in src/state/CallViewModel.ts

View workflow job for this annotation

GitHub Actions / Lint, format & type check

Missing return type on function
this.handsRaised.next(data.raisedHands);
Expand Down
61 changes: 37 additions & 24 deletions src/useReactions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,14 @@ import {
import { useLatest } from "./useLatest";

interface ReactionsContextType {
/**
* identifier (userId:deviceId => Date)
*/
raisedHands: Record<string, Date>;
supportsReactions: boolean;
/**
* reactions (userId:deviceId => Date)
*/
reactions: Record<string, ReactionOption>;
toggleRaisedHand: () => Promise<void>;
sendReaction: (reaction: ReactionOption) => Promise<void>;
Expand Down Expand Up @@ -92,6 +98,24 @@ export const ReactionsProvider = ({
clientState?.state === "valid" && clientState.supportedFeatures.reactions;
const room = rtcSession.room;
const myUserId = room.client.getUserId();
const myDeviceId = room.client.getDeviceId();

Check failure on line 101 in src/useReactions.tsx

View workflow job for this annotation

GitHub Actions / Run vitest tests

src/useReactions.test.tsx > useReactions > starts with an empty list

TypeError: room.client.getDeviceId is not a function ❯ ReactionsProvider src/useReactions.tsx:101:34 ❯ renderWithHooks node_modules/react-dom/cjs/react-dom.development.js:15486:18 ❯ mountIndeterminateComponent node_modules/react-dom/cjs/react-dom.development.js:20103:13 ❯ beginWork node_modules/react-dom/cjs/react-dom.development.js:21626:16 ❯ beginWork$1 node_modules/react-dom/cjs/react-dom.development.js:27465:14 ❯ performUnitOfWork node_modules/react-dom/cjs/react-dom.development.js:26599:12 ❯ workLoopSync node_modules/react-dom/cjs/react-dom.development.js:26505:5 ❯ renderRootSync node_modules/react-dom/cjs/react-dom.development.js:26473:7 ❯ recoverFromConcurrentError node_modules/react-dom/cjs/react-dom.development.js:25889:20 ❯ performConcurrentWorkOnRoot node_modules/react-dom/cjs/react-dom.development.js:25789:22

Check failure on line 101 in src/useReactions.tsx

View workflow job for this annotation

GitHub Actions / Run vitest tests

src/useReactions.test.tsx > useReactions > handles incoming raised hand

TypeError: room.client.getDeviceId is not a function ❯ ReactionsProvider src/useReactions.tsx:101:34 ❯ renderWithHooks node_modules/react-dom/cjs/react-dom.development.js:15486:18 ❯ mountIndeterminateComponent node_modules/react-dom/cjs/react-dom.development.js:20103:13 ❯ beginWork node_modules/react-dom/cjs/react-dom.development.js:21626:16 ❯ beginWork$1 node_modules/react-dom/cjs/react-dom.development.js:27465:14 ❯ performUnitOfWork node_modules/react-dom/cjs/react-dom.development.js:26599:12 ❯ workLoopSync node_modules/react-dom/cjs/react-dom.development.js:26505:5 ❯ renderRootSync node_modules/react-dom/cjs/react-dom.development.js:26473:7 ❯ recoverFromConcurrentError node_modules/react-dom/cjs/react-dom.development.js:25889:20 ❯ performConcurrentWorkOnRoot node_modules/react-dom/cjs/react-dom.development.js:25789:22

Check failure on line 101 in src/useReactions.tsx

View workflow job for this annotation

GitHub Actions / Run vitest tests

src/useReactions.test.tsx > useReactions > handles incoming unraised hand

TypeError: room.client.getDeviceId is not a function ❯ ReactionsProvider src/useReactions.tsx:101:34 ❯ renderWithHooks node_modules/react-dom/cjs/react-dom.development.js:15486:18 ❯ mountIndeterminateComponent node_modules/react-dom/cjs/react-dom.development.js:20103:13 ❯ beginWork node_modules/react-dom/cjs/react-dom.development.js:21626:16 ❯ beginWork$1 node_modules/react-dom/cjs/react-dom.development.js:27465:14 ❯ performUnitOfWork node_modules/react-dom/cjs/react-dom.development.js:26599:12 ❯ workLoopSync node_modules/react-dom/cjs/react-dom.development.js:26505:5 ❯ renderRootSync node_modules/react-dom/cjs/react-dom.development.js:26473:7 ❯ recoverFromConcurrentError node_modules/react-dom/cjs/react-dom.development.js:25889:20 ❯ performConcurrentWorkOnRoot node_modules/react-dom/cjs/react-dom.development.js:25789:22

Check failure on line 101 in src/useReactions.tsx

View workflow job for this annotation

GitHub Actions / Run vitest tests

src/useReactions.test.tsx > useReactions > handles loading prior raised hand events

TypeError: room.client.getDeviceId is not a function ❯ ReactionsProvider src/useReactions.tsx:101:34 ❯ renderWithHooks node_modules/react-dom/cjs/react-dom.development.js:15486:18 ❯ mountIndeterminateComponent node_modules/react-dom/cjs/react-dom.development.js:20103:13 ❯ beginWork node_modules/react-dom/cjs/react-dom.development.js:21626:16 ❯ beginWork$1 node_modules/react-dom/cjs/react-dom.development.js:27465:14 ❯ performUnitOfWork node_modules/react-dom/cjs/react-dom.development.js:26599:12 ❯ workLoopSync node_modules/react-dom/cjs/react-dom.development.js:26505:5 ❯ renderRootSync node_modules/react-dom/cjs/react-dom.development.js:26473:7 ❯ recoverFromConcurrentError node_modules/react-dom/cjs/react-dom.development.js:25889:20 ❯ performConcurrentWorkOnRoot node_modules/react-dom/cjs/react-dom.development.js:25789:22

Check failure on line 101 in src/useReactions.tsx

View workflow job for this annotation

GitHub Actions / Run vitest tests

src/useReactions.test.tsx > useReactions > will remove reaction when a member leaves the call

TypeError: room.client.getDeviceId is not a function ❯ ReactionsProvider src/useReactions.tsx:101:34 ❯ renderWithHooks node_modules/react-dom/cjs/react-dom.development.js:15486:18 ❯ mountIndeterminateComponent node_modules/react-dom/cjs/react-dom.development.js:20103:13 ❯ beginWork node_modules/react-dom/cjs/react-dom.development.js:21626:16 ❯ beginWork$1 node_modules/react-dom/cjs/react-dom.development.js:27465:14 ❯ performUnitOfWork node_modules/react-dom/cjs/react-dom.development.js:26599:12 ❯ workLoopSync node_modules/react-dom/cjs/react-dom.development.js:26505:5 ❯ renderRootSync node_modules/react-dom/cjs/react-dom.development.js:26473:7 ❯ recoverFromConcurrentError node_modules/react-dom/cjs/react-dom.development.js:25889:20 ❯ performConcurrentWorkOnRoot node_modules/react-dom/cjs/react-dom.development.js:25789:22

Check failure on line 101 in src/useReactions.tsx

View workflow job for this annotation

GitHub Actions / Run vitest tests

src/useReactions.test.tsx > useReactions > will remove reaction when a member joins via a new event

TypeError: room.client.getDeviceId is not a function ❯ ReactionsProvider src/useReactions.tsx:101:34 ❯ renderWithHooks node_modules/react-dom/cjs/react-dom.development.js:15486:18 ❯ mountIndeterminateComponent node_modules/react-dom/cjs/react-dom.development.js:20103:13 ❯ beginWork node_modules/react-dom/cjs/react-dom.development.js:21626:16 ❯ beginWork$1 node_modules/react-dom/cjs/react-dom.development.js:27465:14 ❯ performUnitOfWork node_modules/react-dom/cjs/react-dom.development.js:26599:12 ❯ workLoopSync node_modules/react-dom/cjs/react-dom.development.js:26505:5 ❯ renderRootSync node_modules/react-dom/cjs/react-dom.development.js:26473:7 ❯ recoverFromConcurrentError node_modules/react-dom/cjs/react-dom.development.js:25889:20 ❯ performConcurrentWorkOnRoot node_modules/react-dom/cjs/react-dom.development.js:25789:22

Check failure on line 101 in src/useReactions.tsx

View workflow job for this annotation

GitHub Actions / Run vitest tests

src/useReactions.test.tsx > useReactions > ignores invalid sender for historic event

TypeError: room.client.getDeviceId is not a function ❯ ReactionsProvider src/useReactions.tsx:101:34 ❯ renderWithHooks node_modules/react-dom/cjs/react-dom.development.js:15486:18 ❯ mountIndeterminateComponent node_modules/react-dom/cjs/react-dom.development.js:20103:13 ❯ beginWork node_modules/react-dom/cjs/react-dom.development.js:21626:16 ❯ beginWork$1 node_modules/react-dom/cjs/react-dom.development.js:27465:14 ❯ performUnitOfWork node_modules/react-dom/cjs/react-dom.development.js:26599:12 ❯ workLoopSync node_modules/react-dom/cjs/react-dom.development.js:26505:5 ❯ renderRootSync node_modules/react-dom/cjs/react-dom.development.js:26473:7 ❯ recoverFromConcurrentError node_modules/react-dom/cjs/react-dom.development.js:25889:20 ❯ performConcurrentWorkOnRoot node_modules/react-dom/cjs/react-dom.development.js:25789:22

Check failure on line 101 in src/useReactions.tsx

View workflow job for this annotation

GitHub Actions / Run vitest tests

src/useReactions.test.tsx > useReactions > ignores invalid sender for new event

TypeError: room.client.getDeviceId is not a function ❯ ReactionsProvider src/useReactions.tsx:101:34 ❯ renderWithHooks node_modules/react-dom/cjs/react-dom.development.js:15486:18 ❯ mountIndeterminateComponent node_modules/react-dom/cjs/react-dom.development.js:20103:13 ❯ beginWork node_modules/react-dom/cjs/react-dom.development.js:21626:16 ❯ beginWork$1 node_modules/react-dom/cjs/react-dom.development.js:27465:14 ❯ performUnitOfWork node_modules/react-dom/cjs/react-dom.development.js:26599:12 ❯ workLoopSync node_modules/react-dom/cjs/react-dom.development.js:26505:5 ❯ renderRootSync node_modules/react-dom/cjs/react-dom.development.js:26473:7 ❯ recoverFromConcurrentError node_modules/react-dom/cjs/react-dom.development.js:25889:20 ❯ performConcurrentWorkOnRoot node_modules/react-dom/cjs/react-dom.development.js:25789:22

Check failure on line 101 in src/useReactions.tsx

View workflow job for this annotation

GitHub Actions / Run vitest tests

src/button/ReactionToggleButton.test.tsx > Can open menu

TypeError: room.client.getDeviceId is not a function ❯ ReactionsProvider src/useReactions.tsx:101:34 ❯ renderWithHooks node_modules/react-dom/cjs/react-dom.development.js:15486:18 ❯ mountIndeterminateComponent node_modules/react-dom/cjs/react-dom.development.js:20103:13 ❯ beginWork node_modules/react-dom/cjs/react-dom.development.js:21626:16 ❯ beginWork$1 node_modules/react-dom/cjs/react-dom.development.js:27465:14 ❯ performUnitOfWork node_modules/react-dom/cjs/react-dom.development.js:26599:12 ❯ workLoopSync node_modules/react-dom/cjs/react-dom.development.js:26505:5 ❯ renderRootSync node_modules/react-dom/cjs/react-dom.development.js:26473:7 ❯ recoverFromConcurrentError node_modules/react-dom/cjs/react-dom.development.js:25889:20 ❯ performConcurrentWorkOnRoot node_modules/react-dom/cjs/react-dom.development.js:25789:22

Check failure on line 101 in src/useReactions.tsx

View workflow job for this annotation

GitHub Actions / Run vitest tests

src/button/ReactionToggleButton.test.tsx > Can raise hand

TypeError: room.client.getDeviceId is not a function ❯ ReactionsProvider src/useReactions.tsx:101:34 ❯ renderWithHooks node_modules/react-dom/cjs/react-dom.development.js:15486:18 ❯ mountIndeterminateComponent node_modules/react-dom/cjs/react-dom.development.js:20103:13 ❯ beginWork node_modules/react-dom/cjs/react-dom.development.js:21626:16 ❯ beginWork$1 node_modules/react-dom/cjs/react-dom.development.js:27465:14 ❯ performUnitOfWork node_modules/react-dom/cjs/react-dom.development.js:26599:12 ❯ workLoopSync node_modules/react-dom/cjs/react-dom.development.js:26505:5 ❯ renderRootSync node_modules/react-dom/cjs/react-dom.development.js:26473:7 ❯ recoverFromConcurrentError node_modules/react-dom/cjs/react-dom.development.js:25889:20 ❯ performConcurrentWorkOnRoot node_modules/react-dom/cjs/react-dom.development.js:25789:22

const latestMemberships = useLatest(memberships);
const latestRaisedHands = useLatest(raisedHands);

const myMembershipEvent = useMemo(
() =>
memberships.find(
(m) => m.sender === myUserId && m.deviceId === myDeviceId,
)?.eventId,
[memberships, myUserId],

Check failure on line 111 in src/useReactions.tsx

View workflow job for this annotation

GitHub Actions / Lint, format & type check

React Hook useMemo has a missing dependency: 'myDeviceId'. Either include it or remove the dependency array
);
const myMembershipIdentifier = useMemo(() => {
const membership = memberships.find((m) => m.sender === myUserId);
return membership
? `${membership.sender}:${membership.deviceId}`
: undefined;
}, [memberships, myUserId]);

const [reactions, setReactions] = useState<Record<string, ReactionOption>>(
{},
Expand Down Expand Up @@ -177,21 +201,8 @@ export const ReactionsProvider = ({
// Ignoring raisedHands here because we don't want to trigger each time the raised
// hands set is updated.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [room, memberships, myUserId, addRaisedHand, removeRaisedHand]);
}, [room, memberships, addRaisedHand, removeRaisedHand]);

const latestMemberships = useLatest(memberships);
const latestRaisedHands = useLatest(raisedHands);

const myMembershipEvent = useMemo(
() => memberships.find((m) => m.sender === myUserId)?.eventId,
[memberships, myUserId],
);
const myMembershipIdentifier = useMemo(() => {
const membership = memberships.find((m) => m.sender === myUserId);
return membership
? `${membership.sender}:${membership.deviceId}`
: undefined;
}, [memberships, myUserId]);
// This effect handles any *live* reaction/redactions in the room.
useEffect(() => {
const reactionTimeouts = new Set<number>();
Expand All @@ -215,18 +226,18 @@ export const ReactionsProvider = ({
const content: ECallReactionEventContent = event.getContent();

const membershipEventId = content?.["m.relates_to"]?.event_id;
const membershipEvent = latestMemberships.current.find(
(e) => e.eventId === membershipEventId && e.sender === sender,
);
// Check to see if this reaction was made to a membership event (and the
// sender of the reaction matches the membership)
if (
!latestMemberships.current.some(
(e) => e.eventId === membershipEventId && e.sender === sender,
)
) {
if (!membershipEvent) {
logger.warn(
`Reaction target was not a membership event for ${sender}, ignoring`,
);
return;
}
const identifier = `${membershipEvent.sender}:${membershipEvent.deviceId}`;

if (!content.emoji) {
logger.warn(`Reaction had no emoji from ${reactionEventId}`);
Expand Down Expand Up @@ -256,19 +267,21 @@ export const ReactionsProvider = ({
};

setReactions((reactions) => {
if (reactions[sender]) {
if (reactions[identifier]) {
// We've still got a reaction from this user, ignore it to prevent spamming
return reactions;
}
const timeout = window.setTimeout(() => {
// Clear the reaction after some time.
setReactions(({ [sender]: _unused, ...remaining }) => remaining);
setReactions(
({ [identifier]: _unused, ...remaining }) => remaining,
);
reactionTimeouts.delete(timeout);
}, REACTION_ACTIVE_TIME_MS);
reactionTimeouts.add(timeout);
return {
...reactions,
[sender]: reaction,
[identifier]: reaction,
};
});
} else if (event.getType() === EventType.Reaction) {
Expand Down Expand Up @@ -380,7 +393,7 @@ export const ReactionsProvider = ({

const sendReaction = useCallback(
async (reaction: ReactionOption) => {
if (!myUserId || reactions[myUserId]) {
if (!myMembershipIdentifier || !reactions[myMembershipIdentifier]) {
// We're still reacting
return;
}
Expand All @@ -400,7 +413,7 @@ export const ReactionsProvider = ({
},
);
},
[myMembershipEvent, reactions, room, myUserId, rtcSession],
[myMembershipEvent, reactions, room, myMembershipIdentifier, rtcSession],
);

return (
Expand Down

0 comments on commit de19565

Please sign in to comment.