mirror of
https://github.com/vector-im/element-call.git
synced 2026-07-03 18:12:58 +00:00
finnish notation
This commit is contained in:
@@ -53,11 +53,8 @@ test("Can open menu", async () => {
|
||||
|
||||
test("Can raise hand", async () => {
|
||||
const user = userEvent.setup();
|
||||
const {
|
||||
vm,
|
||||
rtcSession,
|
||||
handRaisedSubject$: handRaisedSubject,
|
||||
} = getBasicCallViewModelEnvironment([local, alice]);
|
||||
const { vm, rtcSession, handRaisedSubject$ } =
|
||||
getBasicCallViewModelEnvironment([local, alice]);
|
||||
const { getByLabelText, container } = render(
|
||||
<TestComponent vm={vm} rtcSession={rtcSession} />,
|
||||
);
|
||||
@@ -76,7 +73,7 @@ test("Can raise hand", async () => {
|
||||
);
|
||||
act(() => {
|
||||
// Mock receiving a reaction.
|
||||
handRaisedSubject.next({
|
||||
handRaisedSubject$.next({
|
||||
[localIdent]: {
|
||||
time: new Date(),
|
||||
reactionEventId: "",
|
||||
@@ -90,18 +87,15 @@ test("Can raise hand", async () => {
|
||||
test("Can lower hand", async () => {
|
||||
const reactionEventId = "$my-reaction-event:example.org";
|
||||
const user = userEvent.setup();
|
||||
const {
|
||||
vm,
|
||||
rtcSession,
|
||||
handRaisedSubject$: handRaisedSubject,
|
||||
} = getBasicCallViewModelEnvironment([local, alice]);
|
||||
const { vm, rtcSession, handRaisedSubject$ } =
|
||||
getBasicCallViewModelEnvironment([local, alice]);
|
||||
const { getByLabelText, container } = render(
|
||||
<TestComponent vm={vm} rtcSession={rtcSession} />,
|
||||
);
|
||||
await user.click(getByLabelText("common.reactions"));
|
||||
await user.click(getByLabelText("action.raise_hand"));
|
||||
act(() => {
|
||||
handRaisedSubject.next({
|
||||
handRaisedSubject$.next({
|
||||
[localIdent]: {
|
||||
time: new Date(),
|
||||
reactionEventId,
|
||||
@@ -117,7 +111,7 @@ test("Can lower hand", async () => {
|
||||
);
|
||||
act(() => {
|
||||
// Mock receiving a redacted reaction.
|
||||
handRaisedSubject.next({});
|
||||
handRaisedSubject$.next({});
|
||||
});
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
@@ -181,10 +181,10 @@ export function ReactionToggleButton({
|
||||
const [errorText, setErrorText] = useState<string>();
|
||||
|
||||
const isHandRaised = useObservableState(
|
||||
vm.handsRaised.pipe(map((v) => !!v[identifier])),
|
||||
vm.handsRaised$.pipe(map((v) => !!v[identifier])),
|
||||
);
|
||||
const canReact = useObservableState(
|
||||
vm.reactions.pipe(map((v) => !v[identifier])),
|
||||
vm.reactions$.pipe(map((v) => !v[identifier])),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -36,32 +36,32 @@ const REACTION_ACTIVE_TIME_MS = 3000;
|
||||
* @param rtcSession
|
||||
*/
|
||||
export default function useReactionsReader(rtcSession: MatrixRTCSession): {
|
||||
raisedHands: Observable<Record<string, RaisedHandInfo>>;
|
||||
reactions: Observable<Record<string, ReactionInfo>>;
|
||||
raisedHands$: Observable<Record<string, RaisedHandInfo>>;
|
||||
reactions$: Observable<Record<string, ReactionInfo>>;
|
||||
} {
|
||||
const raisedHandsSubject = useRef(
|
||||
const raisedHandsSubject$ = useRef(
|
||||
new BehaviorSubject<Record<string, RaisedHandInfo>>({}),
|
||||
);
|
||||
const reactionsSubject = useRef(
|
||||
const reactionsSubject$ = useRef(
|
||||
new BehaviorSubject<Record<string, ReactionInfo>>({}),
|
||||
);
|
||||
|
||||
const memberships = useMatrixRTCSessionMemberships(rtcSession);
|
||||
const latestMemberships = useLatest(memberships);
|
||||
const latestRaisedHands = useLatest(raisedHandsSubject.current);
|
||||
const latestRaisedHands = useLatest(raisedHandsSubject$.current);
|
||||
const room = rtcSession.room;
|
||||
|
||||
const addRaisedHand = useCallback((userId: string, info: RaisedHandInfo) => {
|
||||
raisedHandsSubject.current.next({
|
||||
...raisedHandsSubject.current.value,
|
||||
raisedHandsSubject$.current.next({
|
||||
...raisedHandsSubject$.current.value,
|
||||
[userId]: info,
|
||||
});
|
||||
}, []);
|
||||
|
||||
const removeRaisedHand = useCallback((userId: string) => {
|
||||
raisedHandsSubject.current.next(
|
||||
raisedHandsSubject$.current.next(
|
||||
Object.fromEntries(
|
||||
Object.entries(raisedHandsSubject.current.value).filter(
|
||||
Object.entries(raisedHandsSubject$.current.value).filter(
|
||||
([uId]) => uId !== userId,
|
||||
),
|
||||
),
|
||||
@@ -90,7 +90,7 @@ export default function useReactionsReader(rtcSession: MatrixRTCSession): {
|
||||
};
|
||||
|
||||
// Remove any raised hands for users no longer joined to the call.
|
||||
for (const identifier of Object.keys(raisedHandsSubject).filter(
|
||||
for (const identifier of Object.keys(raisedHandsSubject$).filter(
|
||||
(rhId) => !memberships.find((u) => u.sender == rhId),
|
||||
)) {
|
||||
removeRaisedHand(identifier);
|
||||
@@ -104,8 +104,8 @@ export default function useReactionsReader(rtcSession: MatrixRTCSession): {
|
||||
}
|
||||
const identifier = `${m.sender}:${m.deviceId}`;
|
||||
if (
|
||||
raisedHandsSubject.current.value[identifier] &&
|
||||
raisedHandsSubject.current.value[identifier].membershipEventId !==
|
||||
raisedHandsSubject$.current.value[identifier] &&
|
||||
raisedHandsSubject$.current.value[identifier].membershipEventId !==
|
||||
m.eventId
|
||||
) {
|
||||
// Membership event for sender has changed since the hand
|
||||
@@ -193,16 +193,16 @@ export default function useReactionsReader(rtcSession: MatrixRTCSession): {
|
||||
...ReactionSet.find((r) => r.name === content.name),
|
||||
};
|
||||
|
||||
const currentReactions = reactionsSubject.current.value;
|
||||
const currentReactions = reactionsSubject$.current.value;
|
||||
if (currentReactions[identifier]) {
|
||||
// We've still got a reaction from this user, ignore it to prevent spamming
|
||||
return;
|
||||
}
|
||||
const timeout = globalThis.setTimeout(() => {
|
||||
// Clear the reaction after some time.
|
||||
reactionsSubject.current.next(
|
||||
reactionsSubject$.current.next(
|
||||
Object.fromEntries(
|
||||
Object.entries(reactionsSubject.current.value).filter(
|
||||
Object.entries(reactionsSubject$.current.value).filter(
|
||||
([id]) => id !== identifier,
|
||||
),
|
||||
),
|
||||
@@ -210,7 +210,7 @@ export default function useReactionsReader(rtcSession: MatrixRTCSession): {
|
||||
reactionTimeouts.delete(timeout);
|
||||
}, REACTION_ACTIVE_TIME_MS);
|
||||
reactionTimeouts.add(timeout);
|
||||
reactionsSubject.current.next({
|
||||
reactionsSubject$.current.next({
|
||||
...currentReactions,
|
||||
[identifier]: {
|
||||
reactionOption: reaction,
|
||||
@@ -264,7 +264,7 @@ export default function useReactionsReader(rtcSession: MatrixRTCSession): {
|
||||
// may still be sending.
|
||||
room.on(MatrixRoomEvent.LocalEchoUpdated, handleReactionEvent);
|
||||
|
||||
const innerReactionsSubject = reactionsSubject.current;
|
||||
const innerReactionsSubject$ = reactionsSubject$.current;
|
||||
return (): void => {
|
||||
room.off(MatrixRoomEvent.Timeline, handleReactionEvent);
|
||||
room.off(MatrixRoomEvent.Redaction, handleReactionEvent);
|
||||
@@ -272,7 +272,7 @@ export default function useReactionsReader(rtcSession: MatrixRTCSession): {
|
||||
room.off(MatrixRoomEvent.LocalEchoUpdated, handleReactionEvent);
|
||||
reactionTimeouts.forEach((t) => clearTimeout(t));
|
||||
// If we're clearing timeouts, we also clear all reactions.
|
||||
innerReactionsSubject.next({});
|
||||
innerReactionsSubject$.next({});
|
||||
};
|
||||
}, [
|
||||
room,
|
||||
@@ -283,7 +283,7 @@ export default function useReactionsReader(rtcSession: MatrixRTCSession): {
|
||||
]);
|
||||
|
||||
return {
|
||||
reactions: reactionsSubject.current.asObservable(),
|
||||
raisedHands: raisedHandsSubject.current.asObservable(),
|
||||
reactions$: reactionsSubject$.current.asObservable(),
|
||||
raisedHands$: raisedHandsSubject$.current.asObservable(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ export const ReactionsSenderProvider = ({
|
||||
}, [memberships, myUserId]);
|
||||
|
||||
const myReaction = useObservableEagerState(
|
||||
vm.reactions.pipe(
|
||||
vm.reactions$.pipe(
|
||||
map((v) =>
|
||||
myMembershipIdentifier !== undefined
|
||||
? v[myMembershipIdentifier]
|
||||
@@ -86,7 +86,7 @@ export const ReactionsSenderProvider = ({
|
||||
);
|
||||
|
||||
const myRaisedHand = useObservableEagerState(
|
||||
vm.handsRaised.pipe(
|
||||
vm.handsRaised$.pipe(
|
||||
map((v) =>
|
||||
myMembershipIdentifier !== undefined
|
||||
? v[myMembershipIdentifier]
|
||||
|
||||
@@ -66,36 +66,42 @@ beforeEach(() => {
|
||||
* a noise every time.
|
||||
*/
|
||||
test("plays one sound when entering a call", () => {
|
||||
const { vm, remoteRtcMemberships$: remoteRtcMemberships } =
|
||||
getBasicCallViewModelEnvironment([local, alice]);
|
||||
const { vm, remoteRtcMemberships$ } = getBasicCallViewModelEnvironment([
|
||||
local,
|
||||
alice,
|
||||
]);
|
||||
render(<CallEventAudioRenderer vm={vm} />);
|
||||
|
||||
// Joining a call usually means remote participants are added later.
|
||||
act(() => {
|
||||
remoteRtcMemberships.next([aliceRtcMember, bobRtcMember]);
|
||||
remoteRtcMemberships$.next([aliceRtcMember, bobRtcMember]);
|
||||
});
|
||||
expect(playSound).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
test("plays a sound when a user joins", () => {
|
||||
const { vm, remoteRtcMemberships$: remoteRtcMemberships } =
|
||||
getBasicCallViewModelEnvironment([local, alice]);
|
||||
const { vm, remoteRtcMemberships$ } = getBasicCallViewModelEnvironment([
|
||||
local,
|
||||
alice,
|
||||
]);
|
||||
render(<CallEventAudioRenderer vm={vm} />);
|
||||
|
||||
act(() => {
|
||||
remoteRtcMemberships.next([aliceRtcMember, bobRtcMember]);
|
||||
remoteRtcMemberships$.next([aliceRtcMember, bobRtcMember]);
|
||||
});
|
||||
// Play a sound when joining a call.
|
||||
expect(playSound).toBeCalledWith("join");
|
||||
});
|
||||
|
||||
test("plays a sound when a user leaves", () => {
|
||||
const { vm, remoteRtcMemberships$: remoteRtcMemberships } =
|
||||
getBasicCallViewModelEnvironment([local, alice]);
|
||||
const { vm, remoteRtcMemberships$ } = getBasicCallViewModelEnvironment([
|
||||
local,
|
||||
alice,
|
||||
]);
|
||||
render(<CallEventAudioRenderer vm={vm} />);
|
||||
|
||||
act(() => {
|
||||
remoteRtcMemberships.next([]);
|
||||
remoteRtcMemberships$.next([]);
|
||||
});
|
||||
expect(playSound).toBeCalledWith("left");
|
||||
});
|
||||
@@ -108,13 +114,15 @@ test("plays no sound when the participant list is more than the maximum size", (
|
||||
);
|
||||
}
|
||||
|
||||
const { vm, remoteRtcMemberships$: remoteRtcMemberships } =
|
||||
getBasicCallViewModelEnvironment([local, alice], mockRtcMemberships);
|
||||
const { vm, remoteRtcMemberships$ } = getBasicCallViewModelEnvironment(
|
||||
[local, alice],
|
||||
mockRtcMemberships,
|
||||
);
|
||||
|
||||
render(<CallEventAudioRenderer vm={vm} />);
|
||||
expect(playSound).not.toBeCalled();
|
||||
act(() => {
|
||||
remoteRtcMemberships.next(
|
||||
remoteRtcMemberships$.next(
|
||||
mockRtcMemberships.slice(0, MAX_PARTICIPANT_COUNT_FOR_SOUND - 1),
|
||||
);
|
||||
});
|
||||
@@ -122,12 +130,14 @@ test("plays no sound when the participant list is more than the maximum size", (
|
||||
});
|
||||
|
||||
test("plays one sound when a hand is raised", () => {
|
||||
const { vm, handRaisedSubject$: handRaisedSubject } =
|
||||
getBasicCallViewModelEnvironment([local, alice]);
|
||||
const { vm, handRaisedSubject$ } = getBasicCallViewModelEnvironment([
|
||||
local,
|
||||
alice,
|
||||
]);
|
||||
render(<CallEventAudioRenderer vm={vm} />);
|
||||
|
||||
act(() => {
|
||||
handRaisedSubject.next({
|
||||
handRaisedSubject$.next({
|
||||
[bobRtcMember.callId]: {
|
||||
time: new Date(),
|
||||
membershipEventId: "",
|
||||
@@ -139,12 +149,14 @@ test("plays one sound when a hand is raised", () => {
|
||||
});
|
||||
|
||||
test("should not play a sound when a hand raise is retracted", () => {
|
||||
const { vm, handRaisedSubject$: handRaisedSubject } =
|
||||
getBasicCallViewModelEnvironment([local, alice]);
|
||||
const { vm, handRaisedSubject$ } = getBasicCallViewModelEnvironment([
|
||||
local,
|
||||
alice,
|
||||
]);
|
||||
render(<CallEventAudioRenderer vm={vm} />);
|
||||
|
||||
act(() => {
|
||||
handRaisedSubject.next({
|
||||
handRaisedSubject$.next({
|
||||
["foo"]: {
|
||||
time: new Date(),
|
||||
membershipEventId: "",
|
||||
@@ -159,7 +171,7 @@ test("should not play a sound when a hand raise is retracted", () => {
|
||||
});
|
||||
expect(playSound).toHaveBeenCalledTimes(2);
|
||||
act(() => {
|
||||
handRaisedSubject.next({
|
||||
handRaisedSubject$.next({
|
||||
["foo"]: {
|
||||
time: new Date(),
|
||||
membershipEventId: "",
|
||||
|
||||
@@ -75,7 +75,7 @@ export function CallEventAudioRenderer({
|
||||
void audioEngineRef.current?.playSound("left");
|
||||
});
|
||||
|
||||
const handRaisedSub = vm.newHandRaised.subscribe(() => {
|
||||
const handRaisedSub = vm.newHandRaised$.subscribe(() => {
|
||||
void audioEngineRef.current?.playSound("raiseHand");
|
||||
});
|
||||
|
||||
|
||||
@@ -139,8 +139,8 @@ export const ActiveCall: FC<ActiveCallProps> = (props) => {
|
||||
livekitRoom,
|
||||
props.e2eeSystem,
|
||||
connStateObservable$,
|
||||
reader.current.raisedHands,
|
||||
reader.current.reactions,
|
||||
reader.current.raisedHands$,
|
||||
reader.current.reactions$,
|
||||
);
|
||||
setVm(vm);
|
||||
return (): void => vm.destroy();
|
||||
|
||||
@@ -80,8 +80,10 @@ test("preloads all audio elements", () => {
|
||||
});
|
||||
|
||||
test("will play an audio sound when there is a reaction", () => {
|
||||
const { vm, reactionsSubject$: reactionsSubject } =
|
||||
getBasicCallViewModelEnvironment([local, alice]);
|
||||
const { vm, reactionsSubject$ } = getBasicCallViewModelEnvironment([
|
||||
local,
|
||||
alice,
|
||||
]);
|
||||
playReactionsSound.setValue(true);
|
||||
render(<TestComponent vm={vm} />);
|
||||
|
||||
@@ -93,7 +95,7 @@ test("will play an audio sound when there is a reaction", () => {
|
||||
);
|
||||
}
|
||||
act(() => {
|
||||
reactionsSubject.next({
|
||||
reactionsSubject$.next({
|
||||
[aliceRtcMember.deviceId]: { reactionOption: chosenReaction, ttl: 0 },
|
||||
});
|
||||
});
|
||||
@@ -101,8 +103,10 @@ test("will play an audio sound when there is a reaction", () => {
|
||||
});
|
||||
|
||||
test("will play the generic audio sound when there is soundless reaction", () => {
|
||||
const { vm, reactionsSubject$: reactionsSubject } =
|
||||
getBasicCallViewModelEnvironment([local, alice]);
|
||||
const { vm, reactionsSubject$ } = getBasicCallViewModelEnvironment([
|
||||
local,
|
||||
alice,
|
||||
]);
|
||||
playReactionsSound.setValue(true);
|
||||
render(<TestComponent vm={vm} />);
|
||||
|
||||
@@ -114,7 +118,7 @@ test("will play the generic audio sound when there is soundless reaction", () =>
|
||||
);
|
||||
}
|
||||
act(() => {
|
||||
reactionsSubject.next({
|
||||
reactionsSubject$.next({
|
||||
[aliceRtcMember.deviceId]: { reactionOption: chosenReaction, ttl: 0 },
|
||||
});
|
||||
});
|
||||
@@ -122,8 +126,10 @@ test("will play the generic audio sound when there is soundless reaction", () =>
|
||||
});
|
||||
|
||||
test("will play multiple audio sounds when there are multiple different reactions", () => {
|
||||
const { vm, reactionsSubject$: reactionsSubject } =
|
||||
getBasicCallViewModelEnvironment([local, alice]);
|
||||
const { vm, reactionsSubject$ } = getBasicCallViewModelEnvironment([
|
||||
local,
|
||||
alice,
|
||||
]);
|
||||
playReactionsSound.setValue(true);
|
||||
render(<TestComponent vm={vm} />);
|
||||
|
||||
@@ -135,7 +141,7 @@ test("will play multiple audio sounds when there are multiple different reaction
|
||||
);
|
||||
}
|
||||
act(() => {
|
||||
reactionsSubject.next({
|
||||
reactionsSubject$.next({
|
||||
[aliceRtcMember.deviceId]: { reactionOption: reaction1, ttl: 0 },
|
||||
[bobRtcMember.deviceId]: { reactionOption: reaction2, ttl: 0 },
|
||||
[localRtcMember.deviceId]: { reactionOption: reaction1, ttl: 0 },
|
||||
|
||||
@@ -48,7 +48,7 @@ export function ReactionsAudioRenderer({
|
||||
}, [soundCache, shouldPlay]);
|
||||
|
||||
useEffect(() => {
|
||||
const sub = vm.audibleReactions.subscribe((newReactions) => {
|
||||
const sub = vm.audibleReactions$.subscribe((newReactions) => {
|
||||
for (const reactionName of newReactions) {
|
||||
if (soundMap[reactionName]) {
|
||||
void audioEngineRef.current?.playSound(reactionName);
|
||||
|
||||
@@ -34,12 +34,14 @@ test("defaults to showing no reactions", () => {
|
||||
|
||||
test("shows a reaction when sent", () => {
|
||||
showReactions.setValue(true);
|
||||
const { vm, reactionsSubject$: reactionsSubject } =
|
||||
getBasicCallViewModelEnvironment([local, alice]);
|
||||
const { vm, reactionsSubject$ } = getBasicCallViewModelEnvironment([
|
||||
local,
|
||||
alice,
|
||||
]);
|
||||
const { getByRole } = render(<ReactionsOverlay vm={vm} />);
|
||||
const reaction = ReactionSet[0];
|
||||
act(() => {
|
||||
reactionsSubject.next({
|
||||
reactionsSubject$.next({
|
||||
[aliceRtcMember.deviceId]: { reactionOption: reaction, ttl: 0 },
|
||||
});
|
||||
});
|
||||
@@ -51,11 +53,13 @@ test("shows a reaction when sent", () => {
|
||||
test("shows two of the same reaction when sent", () => {
|
||||
showReactions.setValue(true);
|
||||
const reaction = ReactionSet[0];
|
||||
const { vm, reactionsSubject$: reactionsSubject } =
|
||||
getBasicCallViewModelEnvironment([local, alice]);
|
||||
const { vm, reactionsSubject$ } = getBasicCallViewModelEnvironment([
|
||||
local,
|
||||
alice,
|
||||
]);
|
||||
const { getAllByRole } = render(<ReactionsOverlay vm={vm} />);
|
||||
act(() => {
|
||||
reactionsSubject.next({
|
||||
reactionsSubject$.next({
|
||||
[aliceRtcMember.deviceId]: { reactionOption: reaction, ttl: 0 },
|
||||
[bobRtcMember.deviceId]: { reactionOption: reaction, ttl: 0 },
|
||||
});
|
||||
@@ -66,11 +70,13 @@ test("shows two of the same reaction when sent", () => {
|
||||
test("shows two different reactions when sent", () => {
|
||||
showReactions.setValue(true);
|
||||
const [reactionA, reactionB] = ReactionSet;
|
||||
const { vm, reactionsSubject$: reactionsSubject } =
|
||||
getBasicCallViewModelEnvironment([local, alice]);
|
||||
const { vm, reactionsSubject$ } = getBasicCallViewModelEnvironment([
|
||||
local,
|
||||
alice,
|
||||
]);
|
||||
const { getAllByRole } = render(<ReactionsOverlay vm={vm} />);
|
||||
act(() => {
|
||||
reactionsSubject.next({
|
||||
reactionsSubject$.next({
|
||||
[aliceRtcMember.deviceId]: { reactionOption: reactionA, ttl: 0 },
|
||||
[bobRtcMember.deviceId]: { reactionOption: reactionB, ttl: 0 },
|
||||
});
|
||||
@@ -83,11 +89,13 @@ test("shows two different reactions when sent", () => {
|
||||
test("hides reactions when reaction animations are disabled", () => {
|
||||
showReactions.setValue(false);
|
||||
const reaction = ReactionSet[0];
|
||||
const { vm, reactionsSubject$: reactionsSubject } =
|
||||
getBasicCallViewModelEnvironment([local, alice]);
|
||||
const { vm, reactionsSubject$ } = getBasicCallViewModelEnvironment([
|
||||
local,
|
||||
alice,
|
||||
]);
|
||||
const { container } = render(<ReactionsOverlay vm={vm} />);
|
||||
act(() => {
|
||||
reactionsSubject.next({
|
||||
reactionsSubject$.next({
|
||||
[aliceRtcMember.deviceId]: { reactionOption: reaction, ttl: 0 },
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,7 +12,7 @@ import styles from "./ReactionsOverlay.module.css";
|
||||
import { type CallViewModel } from "../state/CallViewModel";
|
||||
|
||||
export function ReactionsOverlay({ vm }: { vm: CallViewModel }): ReactNode {
|
||||
const reactionsIcons = useObservableState(vm.visibleReactions);
|
||||
const reactionsIcons = useObservableState(vm.visibleReactions$);
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
{reactionsIcons?.map(({ sender, emoji, startX }) => (
|
||||
|
||||
@@ -194,7 +194,7 @@ function withCallViewModel(
|
||||
speaking: Map<Participant, Observable<boolean>>,
|
||||
continuation: (
|
||||
vm: CallViewModel,
|
||||
subjects: { raisedHands: BehaviorSubject<Record<string, RaisedHandInfo>> },
|
||||
subjects: { raisedHands$: BehaviorSubject<Record<string, RaisedHandInfo>> },
|
||||
) => void,
|
||||
): void {
|
||||
const room = mockMatrixRoom({
|
||||
@@ -240,7 +240,7 @@ function withCallViewModel(
|
||||
{ remoteParticipants$ },
|
||||
);
|
||||
|
||||
const raisedHands = new BehaviorSubject<Record<string, RaisedHandInfo>>({});
|
||||
const raisedHands$ = new BehaviorSubject<Record<string, RaisedHandInfo>>({});
|
||||
|
||||
const vm = new CallViewModel(
|
||||
rtcSession as unknown as MatrixRTCSession,
|
||||
@@ -249,7 +249,7 @@ function withCallViewModel(
|
||||
kind: E2eeType.PER_PARTICIPANT,
|
||||
},
|
||||
connectionState$,
|
||||
raisedHands,
|
||||
raisedHands$,
|
||||
new BehaviorSubject({}),
|
||||
);
|
||||
|
||||
@@ -261,7 +261,7 @@ function withCallViewModel(
|
||||
roomEventSelectorSpy!.mockRestore();
|
||||
});
|
||||
|
||||
continuation(vm, { raisedHands });
|
||||
continuation(vm, { raisedHands$: raisedHands$ });
|
||||
}
|
||||
|
||||
test("participants are retained during a focus switch", () => {
|
||||
@@ -802,7 +802,7 @@ it("should rank raised hands above video feeds and below speakers and presenters
|
||||
of([aliceRtcMember, bobRtcMember]),
|
||||
of(ConnectionState.Connected),
|
||||
new Map(),
|
||||
(vm, { raisedHands }) => {
|
||||
(vm, { raisedHands$ }) => {
|
||||
schedule("ab", {
|
||||
a: () => {
|
||||
// We imagine that only three tiles (the first three) will be visible
|
||||
@@ -814,7 +814,7 @@ it("should rank raised hands above video feeds and below speakers and presenters
|
||||
});
|
||||
},
|
||||
b: () => {
|
||||
raisedHands.next({
|
||||
raisedHands$.next({
|
||||
[`${bobRtcMember.sender}:${bobRtcMember.deviceId}`]: {
|
||||
time: new Date(),
|
||||
reactionEventId: "",
|
||||
|
||||
@@ -258,8 +258,8 @@ class UserMedia {
|
||||
participant: LocalParticipant | RemoteParticipant | undefined,
|
||||
encryptionSystem: EncryptionSystem,
|
||||
livekitRoom: LivekitRoom,
|
||||
handRaised: Observable<Date | null>,
|
||||
reaction: Observable<ReactionOption | null>,
|
||||
handRaised$: Observable<Date | null>,
|
||||
reaction$: Observable<ReactionOption | null>,
|
||||
) {
|
||||
this.participant$ = new BehaviorSubject(participant);
|
||||
|
||||
@@ -270,8 +270,8 @@ class UserMedia {
|
||||
this.participant$.asObservable() as Observable<LocalParticipant>,
|
||||
encryptionSystem,
|
||||
livekitRoom,
|
||||
handRaised,
|
||||
reaction,
|
||||
handRaised$,
|
||||
reaction$,
|
||||
);
|
||||
} else {
|
||||
this.vm = new RemoteUserMediaViewModel(
|
||||
@@ -282,8 +282,8 @@ class UserMedia {
|
||||
>,
|
||||
encryptionSystem,
|
||||
livekitRoom,
|
||||
handRaised,
|
||||
reaction,
|
||||
handRaised$,
|
||||
reaction$,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -544,10 +544,10 @@ export class CallViewModel extends ViewModel {
|
||||
participant,
|
||||
this.encryptionSystem,
|
||||
this.livekitRoom,
|
||||
this.handsRaised.pipe(
|
||||
this.handsRaised$.pipe(
|
||||
map((v) => v[matrixIdentifier]?.time ?? null),
|
||||
),
|
||||
this.reactions.pipe(
|
||||
this.reactions$.pipe(
|
||||
map((v) => v[matrixIdentifier] ?? undefined),
|
||||
),
|
||||
),
|
||||
@@ -711,7 +711,7 @@ export class CallViewModel extends ViewModel {
|
||||
m.speaker$,
|
||||
m.presenter$,
|
||||
m.vm.videoEnabled$,
|
||||
m.vm.handRaised,
|
||||
m.vm.handRaised$,
|
||||
m.vm instanceof LocalUserMediaViewModel
|
||||
? m.vm.alwaysShow$
|
||||
: of(false),
|
||||
@@ -1203,7 +1203,7 @@ export class CallViewModel extends ViewModel {
|
||||
this.scope.state(),
|
||||
);
|
||||
|
||||
public readonly reactions = this.reactionsSubject.pipe(
|
||||
public readonly reactions$ = this.reactionsSubject$.pipe(
|
||||
map((v) =>
|
||||
Object.fromEntries(
|
||||
Object.entries(v).map(([a, { reactionOption }]) => [a, reactionOption]),
|
||||
@@ -1211,13 +1211,13 @@ export class CallViewModel extends ViewModel {
|
||||
),
|
||||
);
|
||||
|
||||
public readonly handsRaised = this.handsRaisedSubject.pipe();
|
||||
public readonly handsRaised$ = this.handsRaisedSubject$.pipe();
|
||||
|
||||
/**
|
||||
* Emits an array of reactions that should be visible on the screen.
|
||||
*/
|
||||
public readonly visibleReactions = showReactions.value$
|
||||
.pipe(switchMap((show) => (show ? this.reactions : of({}))))
|
||||
public readonly visibleReactions$ = showReactions.value$
|
||||
.pipe(switchMap((show) => (show ? this.reactions$ : of({}))))
|
||||
.pipe(
|
||||
scan<
|
||||
Record<string, ReactionOption>,
|
||||
@@ -1238,10 +1238,10 @@ export class CallViewModel extends ViewModel {
|
||||
/**
|
||||
* Emits an array of reactions that should be played.
|
||||
*/
|
||||
public readonly audibleReactions = playReactionsSound.value$
|
||||
public readonly audibleReactions$ = playReactionsSound.value$
|
||||
.pipe(
|
||||
switchMap((show) =>
|
||||
show ? this.reactions : of<Record<string, ReactionOption>>({}),
|
||||
show ? this.reactions$ : of<Record<string, ReactionOption>>({}),
|
||||
),
|
||||
)
|
||||
.pipe(
|
||||
@@ -1267,7 +1267,7 @@ export class CallViewModel extends ViewModel {
|
||||
* Emits an event every time a new hand is raised in
|
||||
* the call.
|
||||
*/
|
||||
public readonly newHandRaised = this.handsRaised.pipe(
|
||||
public readonly newHandRaised$ = this.handsRaised$.pipe(
|
||||
map((v) => Object.keys(v).length),
|
||||
scan(
|
||||
(acc, newValue) => ({
|
||||
@@ -1285,10 +1285,12 @@ export class CallViewModel extends ViewModel {
|
||||
private readonly livekitRoom: LivekitRoom,
|
||||
private readonly encryptionSystem: EncryptionSystem,
|
||||
private readonly connectionState$: Observable<ECConnectionState>,
|
||||
private readonly handsRaisedSubject: Observable<
|
||||
private readonly handsRaisedSubject$: Observable<
|
||||
Record<string, RaisedHandInfo>
|
||||
>,
|
||||
private readonly reactionsSubject: Observable<Record<string, ReactionInfo>>,
|
||||
private readonly reactionsSubject$: Observable<
|
||||
Record<string, ReactionInfo>
|
||||
>,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
@@ -372,8 +372,8 @@ abstract class BaseUserMediaViewModel extends BaseMediaViewModel {
|
||||
participant$: Observable<LocalParticipant | RemoteParticipant | undefined>,
|
||||
encryptionSystem: EncryptionSystem,
|
||||
livekitRoom: LivekitRoom,
|
||||
public readonly handRaised: Observable<Date | null>,
|
||||
public readonly reaction: Observable<ReactionOption | null>,
|
||||
public readonly handRaised$: Observable<Date | null>,
|
||||
public readonly reaction$: Observable<ReactionOption | null>,
|
||||
) {
|
||||
super(
|
||||
id,
|
||||
@@ -440,8 +440,8 @@ export class LocalUserMediaViewModel extends BaseUserMediaViewModel {
|
||||
participant$: Observable<LocalParticipant | undefined>,
|
||||
encryptionSystem: EncryptionSystem,
|
||||
livekitRoom: LivekitRoom,
|
||||
handRaised: Observable<Date | null>,
|
||||
reaction: Observable<ReactionOption | null>,
|
||||
handRaised$: Observable<Date | null>,
|
||||
reaction$: Observable<ReactionOption | null>,
|
||||
) {
|
||||
super(
|
||||
id,
|
||||
@@ -449,8 +449,8 @@ export class LocalUserMediaViewModel extends BaseUserMediaViewModel {
|
||||
participant$,
|
||||
encryptionSystem,
|
||||
livekitRoom,
|
||||
handRaised,
|
||||
reaction,
|
||||
handRaised$,
|
||||
reaction$,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -511,8 +511,8 @@ export class RemoteUserMediaViewModel extends BaseUserMediaViewModel {
|
||||
participant$: Observable<RemoteParticipant | undefined>,
|
||||
encryptionSystem: EncryptionSystem,
|
||||
livekitRoom: LivekitRoom,
|
||||
handRaised: Observable<Date | null>,
|
||||
reaction: Observable<ReactionOption | null>,
|
||||
handRaised$: Observable<Date | null>,
|
||||
reaction$: Observable<ReactionOption | null>,
|
||||
) {
|
||||
super(
|
||||
id,
|
||||
@@ -520,8 +520,8 @@ export class RemoteUserMediaViewModel extends BaseUserMediaViewModel {
|
||||
participant$,
|
||||
encryptionSystem,
|
||||
livekitRoom,
|
||||
handRaised,
|
||||
reaction,
|
||||
handRaised$,
|
||||
reaction$,
|
||||
);
|
||||
|
||||
// Sync the local volume with LiveKit
|
||||
|
||||
@@ -53,8 +53,8 @@ test("GridTile is accessible", async () => {
|
||||
memberships: [],
|
||||
} as unknown as MatrixRTCSession;
|
||||
const cVm = {
|
||||
reactions: of({}),
|
||||
handsRaised: of({}),
|
||||
reactions$: of({}),
|
||||
handsRaised$: of({}),
|
||||
} as Partial<CallViewModel> as CallViewModel;
|
||||
const { container } = render(
|
||||
<ReactionsSenderProvider vm={cVm} rtcSession={fakeRtcSession}>
|
||||
|
||||
@@ -97,8 +97,8 @@ const UserMediaTile = forwardRef<HTMLDivElement, UserMediaTileProps>(
|
||||
},
|
||||
[vm],
|
||||
);
|
||||
const handRaised = useObservableState(vm.handRaised);
|
||||
const reaction = useObservableState(vm.reaction);
|
||||
const handRaised = useObservableState(vm.handRaised$);
|
||||
const reaction = useObservableState(vm.reaction$);
|
||||
|
||||
const AudioIcon = locallyMuted
|
||||
? VolumeOffSolidIcon
|
||||
|
||||
@@ -66,17 +66,17 @@ export function getBasicCallViewModelEnvironment(
|
||||
roomId: matrixRoomId,
|
||||
});
|
||||
|
||||
const remoteRtcMemberships = new BehaviorSubject<CallMembership[]>(
|
||||
const remoteRtcMemberships$ = new BehaviorSubject<CallMembership[]>(
|
||||
initialRemoteRtcMemberships,
|
||||
);
|
||||
|
||||
const handRaisedSubject = new BehaviorSubject({});
|
||||
const reactionsSubject = new BehaviorSubject({});
|
||||
const handRaisedSubject$ = new BehaviorSubject({});
|
||||
const reactionsSubject$ = new BehaviorSubject({});
|
||||
|
||||
const rtcSession = new MockRTCSession(
|
||||
matrixRoom,
|
||||
localRtcMember,
|
||||
).withMemberships(remoteRtcMemberships);
|
||||
).withMemberships(remoteRtcMemberships$);
|
||||
|
||||
const vm = new CallViewModel(
|
||||
rtcSession as unknown as MatrixRTCSession,
|
||||
@@ -85,14 +85,14 @@ export function getBasicCallViewModelEnvironment(
|
||||
kind: E2eeType.PER_PARTICIPANT,
|
||||
},
|
||||
of(ConnectionState.Connected),
|
||||
handRaisedSubject,
|
||||
reactionsSubject,
|
||||
handRaisedSubject$,
|
||||
reactionsSubject$,
|
||||
);
|
||||
return {
|
||||
vm,
|
||||
remoteRtcMemberships$: remoteRtcMemberships,
|
||||
remoteRtcMemberships$: remoteRtcMemberships$,
|
||||
rtcSession,
|
||||
handRaisedSubject$: handRaisedSubject,
|
||||
reactionsSubject$: reactionsSubject,
|
||||
handRaisedSubject$: handRaisedSubject$,
|
||||
reactionsSubject$: reactionsSubject$,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user