mirror of
https://github.com/vector-im/element-call.git
synced 2026-09-25 22:35:49 +00:00
More refactors
This commit is contained in:
@@ -79,7 +79,7 @@ test("Can raise hand", async () => {
|
|||||||
expect(container).toMatchSnapshot();
|
expect(container).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
test.only("Can lower hand", async () => {
|
test("Can lower hand", async () => {
|
||||||
const user = userEvent.setup();
|
const user = userEvent.setup();
|
||||||
const { vm, rtcSession } = getBasicCallViewModelEnvironment([local, alice]);
|
const { vm, rtcSession } = getBasicCallViewModelEnvironment([local, alice]);
|
||||||
const { getByLabelText, container } = render(
|
const { getByLabelText, container } = render(
|
||||||
@@ -95,6 +95,7 @@ test.only("Can lower hand", async () => {
|
|||||||
reactions: {},
|
reactions: {},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
await user.click(getByLabelText("common.reactions"));
|
||||||
await user.click(getByLabelText("action.lower_hand"));
|
await user.click(getByLabelText("action.lower_hand"));
|
||||||
expect(rtcSession.room.client.redactEvent).toHaveBeenCalledWith(
|
expect(rtcSession.room.client.redactEvent).toHaveBeenCalledWith(
|
||||||
undefined,
|
undefined,
|
||||||
@@ -118,63 +119,55 @@ test.only("Can lower hand", async () => {
|
|||||||
|
|
||||||
test("Can react with emoji", async () => {
|
test("Can react with emoji", async () => {
|
||||||
const user = userEvent.setup();
|
const user = userEvent.setup();
|
||||||
const room = new MockRoom(memberUserIdAlice);
|
const { vm, rtcSession } = getBasicCallViewModelEnvironment([local, alice]);
|
||||||
const rtcSession = new MockRTCSession(room, membership);
|
|
||||||
const { getByLabelText, getByText } = render(
|
const { getByLabelText, getByText } = render(
|
||||||
<TestComponent rtcSession={rtcSession} />,
|
<TestComponent vm={vm} rtcSession={rtcSession} />,
|
||||||
);
|
);
|
||||||
await user.click(getByLabelText("common.reactions"));
|
await user.click(getByLabelText("common.reactions"));
|
||||||
await user.click(getByText("🐶"));
|
await user.click(getByText("🐶"));
|
||||||
expect(room.testSentEvents).toEqual([
|
expect(rtcSession.room.client.sendEvent).toHaveBeenCalledWith(
|
||||||
[
|
undefined,
|
||||||
undefined,
|
ElementCallReactionEventType,
|
||||||
ElementCallReactionEventType,
|
{
|
||||||
{
|
"m.relates_to": {
|
||||||
"m.relates_to": {
|
event_id: localRtcMember.eventId,
|
||||||
event_id: memberEventAlice,
|
rel_type: "m.reference",
|
||||||
rel_type: "m.reference",
|
|
||||||
},
|
|
||||||
name: "dog",
|
|
||||||
emoji: "🐶",
|
|
||||||
},
|
},
|
||||||
],
|
name: "dog",
|
||||||
]);
|
emoji: "🐶",
|
||||||
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Can fully expand emoji picker", async () => {
|
test("Can fully expand emoji picker", async () => {
|
||||||
const user = userEvent.setup();
|
const user = userEvent.setup();
|
||||||
const room = new MockRoom(memberUserIdAlice);
|
const { vm, rtcSession } = getBasicCallViewModelEnvironment([local, alice]);
|
||||||
const rtcSession = new MockRTCSession(room, membership);
|
const { getByLabelText, container, getByText } = render(
|
||||||
const { getByText, container, getByLabelText } = render(
|
<TestComponent vm={vm} rtcSession={rtcSession} />,
|
||||||
<TestComponent rtcSession={rtcSession} />,
|
|
||||||
);
|
);
|
||||||
await user.click(getByLabelText("common.reactions"));
|
await user.click(getByLabelText("common.reactions"));
|
||||||
await user.click(getByLabelText("action.show_more"));
|
await user.click(getByLabelText("action.show_more"));
|
||||||
expect(container).toMatchSnapshot();
|
expect(container).toMatchSnapshot();
|
||||||
await user.click(getByText("🦗"));
|
await user.click(getByText("🦗"));
|
||||||
|
expect(rtcSession.room.client.sendEvent).toHaveBeenCalledWith(
|
||||||
expect(room.testSentEvents).toEqual([
|
undefined,
|
||||||
[
|
ElementCallReactionEventType,
|
||||||
undefined,
|
{
|
||||||
ElementCallReactionEventType,
|
"m.relates_to": {
|
||||||
{
|
event_id: localRtcMember.eventId,
|
||||||
"m.relates_to": {
|
rel_type: "m.reference",
|
||||||
event_id: memberEventAlice,
|
|
||||||
rel_type: "m.reference",
|
|
||||||
},
|
|
||||||
name: "crickets",
|
|
||||||
emoji: "🦗",
|
|
||||||
},
|
},
|
||||||
],
|
name: "crickets",
|
||||||
]);
|
emoji: "🦗",
|
||||||
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Can close reaction dialog", async () => {
|
test("Can close reaction dialog", async () => {
|
||||||
const user = userEvent.setup();
|
const user = userEvent.setup();
|
||||||
const room = new MockRoom(memberUserIdAlice);
|
const { vm, rtcSession } = getBasicCallViewModelEnvironment([local, alice]);
|
||||||
const rtcSession = new MockRTCSession(room, membership);
|
|
||||||
const { getByLabelText, container } = render(
|
const { getByLabelText, container } = render(
|
||||||
<TestComponent rtcSession={rtcSession} />,
|
<TestComponent vm={vm} rtcSession={rtcSession} />,
|
||||||
);
|
);
|
||||||
await user.click(getByLabelText("common.reactions"));
|
await user.click(getByLabelText("common.reactions"));
|
||||||
await user.click(getByLabelText("action.show_more"));
|
await user.click(getByLabelText("action.show_more"));
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ export function ReactionToggleButton({
|
|||||||
vm.handsRaised.pipe(map((v) => !!v[identifier])),
|
vm.handsRaised.pipe(map((v) => !!v[identifier])),
|
||||||
);
|
);
|
||||||
const canReact = useObservableState(
|
const canReact = useObservableState(
|
||||||
vm.reactions.pipe(map((v) => !!v[identifier])),
|
vm.reactions.pipe(map((v) => !v[identifier])),
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ exports[`Can close reaction dialog 1`] = `
|
|||||||
aria-disabled="false"
|
aria-disabled="false"
|
||||||
aria-expanded="true"
|
aria-expanded="true"
|
||||||
aria-haspopup="true"
|
aria-haspopup="true"
|
||||||
aria-labelledby=":r9l:"
|
aria-labelledby=":rav:"
|
||||||
class="_button_i91xf_17 _has-icon_i91xf_66 _icon-only_i91xf_59"
|
class="_button_i91xf_17 _has-icon_i91xf_66 _icon-only_i91xf_59"
|
||||||
data-kind="primary"
|
data-kind="primary"
|
||||||
data-size="lg"
|
data-size="lg"
|
||||||
@@ -43,7 +43,7 @@ exports[`Can fully expand emoji picker 1`] = `
|
|||||||
aria-disabled="false"
|
aria-disabled="false"
|
||||||
aria-expanded="true"
|
aria-expanded="true"
|
||||||
aria-haspopup="true"
|
aria-haspopup="true"
|
||||||
aria-labelledby=":r6c:"
|
aria-labelledby=":r7m:"
|
||||||
class="_button_i91xf_17 _has-icon_i91xf_66 _icon-only_i91xf_59"
|
class="_button_i91xf_17 _has-icon_i91xf_66 _icon-only_i91xf_59"
|
||||||
data-kind="primary"
|
data-kind="primary"
|
||||||
data-size="lg"
|
data-size="lg"
|
||||||
@@ -68,35 +68,6 @@ exports[`Can fully expand emoji picker 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`Can lower hand 1`] = `
|
|
||||||
<div>
|
|
||||||
<button
|
|
||||||
aria-disabled="false"
|
|
||||||
aria-expanded="false"
|
|
||||||
aria-haspopup="true"
|
|
||||||
aria-labelledby=":r36:"
|
|
||||||
class="_button_i91xf_17 raisedButton _has-icon_i91xf_66 _icon-only_i91xf_59"
|
|
||||||
data-kind="primary"
|
|
||||||
data-size="lg"
|
|
||||||
role="button"
|
|
||||||
tabindex="0"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
fill="currentColor"
|
|
||||||
height="24"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
width="24"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M11 3a1 1 0 1 1 2 0v8.5a.5.5 0 0 0 1 0V4a1 1 0 1 1 2 0v10.2l3.284-2.597a1.081 1.081 0 0 1 1.47 1.577c-.613.673-1.214 1.367-1.818 2.064-1.267 1.463-2.541 2.934-3.944 4.235A6 6 0 0 1 5 15V7a1 1 0 0 1 2 0v5.5a.5.5 0 0 0 1 0V4a1 1 0 0 1 2 0v7.5a.5.5 0 0 0 1 0V3Z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`Can open menu 1`] = `
|
exports[`Can open menu 1`] = `
|
||||||
<div
|
<div
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
@@ -137,7 +108,7 @@ exports[`Can raise hand 1`] = `
|
|||||||
aria-disabled="false"
|
aria-disabled="false"
|
||||||
aria-expanded="false"
|
aria-expanded="false"
|
||||||
aria-haspopup="true"
|
aria-haspopup="true"
|
||||||
aria-labelledby=":r0:"
|
aria-labelledby=":r1j:"
|
||||||
class="_button_i91xf_17 raisedButton _has-icon_i91xf_66 _icon-only_i91xf_59"
|
class="_button_i91xf_17 raisedButton _has-icon_i91xf_66 _icon-only_i91xf_59"
|
||||||
data-kind="primary"
|
data-kind="primary"
|
||||||
data-size="lg"
|
data-size="lg"
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import { GroupCallView } from "./GroupCallView";
|
|||||||
import { leaveRTCSession } from "../rtcSessionHelpers";
|
import { leaveRTCSession } from "../rtcSessionHelpers";
|
||||||
import { type WidgetHelpers } from "../widget";
|
import { type WidgetHelpers } from "../widget";
|
||||||
import { LazyEventEmitter } from "../LazyEventEmitter";
|
import { LazyEventEmitter } from "../LazyEventEmitter";
|
||||||
|
import { RelationsContainer } from "matrix-js-sdk/src/models/relations-container";
|
||||||
|
|
||||||
vitest.mock("../soundUtils");
|
vitest.mock("../soundUtils");
|
||||||
vitest.mock("../useAudioContext");
|
vitest.mock("../useAudioContext");
|
||||||
@@ -85,6 +86,12 @@ function createGroupCallView(widget: WidgetHelpers | null): {
|
|||||||
getRoom: (rId) => (rId === roomId ? room : null),
|
getRoom: (rId) => (rId === roomId ? room : null),
|
||||||
} as Partial<MatrixClient> as MatrixClient;
|
} as Partial<MatrixClient> as MatrixClient;
|
||||||
const room = mockMatrixRoom({
|
const room = mockMatrixRoom({
|
||||||
|
relations: {
|
||||||
|
getChildEventsForEvent: () =>
|
||||||
|
vitest.mocked({
|
||||||
|
getRelations: () => [],
|
||||||
|
}),
|
||||||
|
} as unknown as RelationsContainer,
|
||||||
client,
|
client,
|
||||||
roomId,
|
roomId,
|
||||||
getMember: (userId) => roomMembers.get(userId) ?? null,
|
getMember: (userId) => roomMembers.get(userId) ?? null,
|
||||||
|
|||||||
@@ -115,7 +115,6 @@ export const ActiveCall: FC<ActiveCallProps> = (props) => {
|
|||||||
[connState],
|
[connState],
|
||||||
);
|
);
|
||||||
const [vm, setVm] = useState<CallViewModel | null>(null);
|
const [vm, setVm] = useState<CallViewModel | null>(null);
|
||||||
const reactions = useReactions();
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return (): void => {
|
return (): void => {
|
||||||
@@ -126,10 +125,6 @@ export const ActiveCall: FC<ActiveCallProps> = (props) => {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
vm?.updateReactions(reactions);
|
|
||||||
}, [vm, reactions]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (livekitRoom !== undefined) {
|
if (livekitRoom !== undefined) {
|
||||||
const vm = new CallViewModel(
|
const vm = new CallViewModel(
|
||||||
@@ -147,7 +142,7 @@ export const ActiveCall: FC<ActiveCallProps> = (props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<RoomContext.Provider value={livekitRoom}>
|
<RoomContext.Provider value={livekitRoom}>
|
||||||
<ReactionsProvider rtcSession={props.rtcSession}>
|
<ReactionsProvider vm={vm} rtcSession={props.rtcSession}>
|
||||||
<InCallView
|
<InCallView
|
||||||
{...props}
|
{...props}
|
||||||
vm={vm}
|
vm={vm}
|
||||||
|
|||||||
+103
-49
@@ -689,63 +689,117 @@ it("should show at least one tile per MatrixRTCSession", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: Add presenters and speakers?
|
it("Should correctly handle a raised hand event", () => {
|
||||||
it("should rank raised hands above video feeds and below speakers and presenters", () => {
|
withTestScheduler(({ expectObservable }) => {
|
||||||
withTestScheduler(({ schedule, expectObservable }) => {
|
const date = new Date();
|
||||||
// There should always be one tile for each MatrixRTCSession
|
|
||||||
const expectedLayoutMarbles = "a";
|
|
||||||
|
|
||||||
withCallViewModel(
|
withCallViewModel(
|
||||||
of([aliceParticipant, bobParticipant]),
|
of([]),
|
||||||
of([aliceRtcMember, bobRtcMember]),
|
of([]),
|
||||||
of(ConnectionState.Connected),
|
of(ConnectionState.Connected),
|
||||||
new Map(),
|
new Map(),
|
||||||
(vm) => {
|
(vm) => {
|
||||||
schedule("ah", {
|
vm.addHandRaised(alice.userId, date);
|
||||||
a: () => {
|
expectObservable(vm.handsRaised).toBe("a", {
|
||||||
// We imagine that only three tiles (the first three) will be visible
|
a: {
|
||||||
// on screen at a time
|
[alice.userId]: date,
|
||||||
vm.layout.subscribe((layout) => {
|
|
||||||
console.log(layout);
|
|
||||||
if (layout.type === "grid") {
|
|
||||||
for (let i = 0; i < layout.grid.length; i++)
|
|
||||||
layout.grid[i].setVisible(i <= 1);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
h: () => {
|
|
||||||
vm.updateReactions({
|
|
||||||
reactions: {},
|
|
||||||
raisedHands: {
|
|
||||||
[`${bobRtcMember.sender}:${bobRtcMember.deviceId}`]: new Date(),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expectObservable(summarizeLayout(vm.layout)).toBe(
|
expectObservable(vm.handRaised).toBe("a", {
|
||||||
expectedLayoutMarbles,
|
a: {
|
||||||
{
|
value: 1,
|
||||||
a: {
|
playSounds: true,
|
||||||
type: "grid",
|
|
||||||
spotlight: undefined,
|
|
||||||
grid: [
|
|
||||||
"local:0",
|
|
||||||
"@bob:example.org:BBBB:0",
|
|
||||||
"@alice:example.org:AAAA:0",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
h: {
|
|
||||||
type: "grid",
|
|
||||||
spotlight: undefined,
|
|
||||||
grid: [
|
|
||||||
"local:0",
|
|
||||||
"@bob:example.org:BBBB:0",
|
|
||||||
"@alice:example.org:AAAA:0",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
);
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it.only("Should correctly handle a lowered hand event", () => {
|
||||||
|
const date = new Date();
|
||||||
|
withTestScheduler(({ expectObservable }) => {
|
||||||
|
withCallViewModel(
|
||||||
|
of([]),
|
||||||
|
of([]),
|
||||||
|
of(ConnectionState.Connected),
|
||||||
|
new Map(),
|
||||||
|
(vm) => {
|
||||||
|
vm.addHandRaised(alice.userId, date);
|
||||||
|
vm.addHandRaised(bob.userId, date);
|
||||||
|
expectObservable(vm.handsRaised).toBe("a", {
|
||||||
|
a: {
|
||||||
|
[bob.userId]: date,
|
||||||
|
},
|
||||||
|
b: {
|
||||||
|
[bob.userId]: date,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: Add presenters and speakers?
|
||||||
|
it.todo(
|
||||||
|
"should rank raised hands above video feeds and below speakers and presenters",
|
||||||
|
() => {
|
||||||
|
withTestScheduler(({ schedule, expectObservable }) => {
|
||||||
|
// There should always be one tile for each MatrixRTCSession
|
||||||
|
const expectedLayoutMarbles = "a";
|
||||||
|
|
||||||
|
withCallViewModel(
|
||||||
|
of([aliceParticipant, bobParticipant]),
|
||||||
|
of([aliceRtcMember, bobRtcMember]),
|
||||||
|
of(ConnectionState.Connected),
|
||||||
|
new Map(),
|
||||||
|
(vm) => {
|
||||||
|
schedule("ah", {
|
||||||
|
a: () => {
|
||||||
|
// We imagine that only three tiles (the first three) will be visible
|
||||||
|
// on screen at a time
|
||||||
|
vm.layout.subscribe((layout) => {
|
||||||
|
console.log(layout);
|
||||||
|
if (layout.type === "grid") {
|
||||||
|
for (let i = 0; i < layout.grid.length; i++)
|
||||||
|
layout.grid[i].setVisible(i <= 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
h: () => {
|
||||||
|
vm.updateReactions({
|
||||||
|
reactions: {},
|
||||||
|
raisedHands: {
|
||||||
|
[`${bobRtcMember.sender}:${bobRtcMember.deviceId}`]:
|
||||||
|
new Date(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expectObservable(summarizeLayout(vm.layout)).toBe(
|
||||||
|
expectedLayoutMarbles,
|
||||||
|
{
|
||||||
|
a: {
|
||||||
|
type: "grid",
|
||||||
|
spotlight: undefined,
|
||||||
|
grid: [
|
||||||
|
"local:0",
|
||||||
|
"@bob:example.org:BBBB:0",
|
||||||
|
"@alice:example.org:AAAA:0",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
h: {
|
||||||
|
type: "grid",
|
||||||
|
spotlight: undefined,
|
||||||
|
grid: [
|
||||||
|
"local:0",
|
||||||
|
"@bob:example.org:BBBB:0",
|
||||||
|
"@alice:example.org:AAAA:0",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|||||||
+51
-13
@@ -1146,23 +1146,61 @@ export class CallViewModel extends ViewModel {
|
|||||||
);
|
);
|
||||||
|
|
||||||
private readonly handsRaisedSubject = new BehaviorSubject<
|
private readonly handsRaisedSubject = new BehaviorSubject<
|
||||||
Record<string, Date>
|
{ userId: string; date: Date | null }[]
|
||||||
>({});
|
>([]);
|
||||||
private readonly reactionsSubject = new BehaviorSubject<
|
private readonly reactionsSubject = new BehaviorSubject<
|
||||||
Record<string, ReactionOption>
|
{ userId: string; reaction: ReactionOption; ttl: number }[]
|
||||||
>({});
|
>([]);
|
||||||
|
|
||||||
public readonly handsRaised = this.handsRaisedSubject.asObservable();
|
public addHandRaised(userId: string, date: Date) {
|
||||||
public readonly reactions = this.reactionsSubject.asObservable();
|
this.handsRaisedSubject.next([{ userId, date }]);
|
||||||
|
|
||||||
public updateReactions(data: {
|
|
||||||
raisedHands: Record<string, Date>;
|
|
||||||
reactions: Record<string, ReactionOption>;
|
|
||||||
}): void {
|
|
||||||
this.handsRaisedSubject.next(data.raisedHands);
|
|
||||||
this.reactionsSubject.next(data.reactions);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public removeHandRaised(userId: string, date: Date | null) {
|
||||||
|
this.handsRaisedSubject.next([{ userId, date }]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public addReaction(userId: string, reaction: ReactionOption, ttl: number) {
|
||||||
|
this.reactionsSubject.next([{ userId, reaction, ttl }]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly reactions = this.reactionsSubject
|
||||||
|
.pipe(
|
||||||
|
scan<
|
||||||
|
{ userId: string; reaction: ReactionOption; ttl: number }[],
|
||||||
|
Record<string, { reaction: ReactionOption; ttl: number }>
|
||||||
|
>((acc, value) => {
|
||||||
|
for (const { userId, reaction, ttl } of value) {
|
||||||
|
acc[userId] = { reaction, ttl };
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, {}),
|
||||||
|
)
|
||||||
|
.pipe(
|
||||||
|
map((v) =>
|
||||||
|
Object.fromEntries(
|
||||||
|
Object.entries(v).map(([a, { reaction }]) => [a, reaction]),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
public readonly handsRaised = this.handsRaisedSubject.pipe(
|
||||||
|
scan<{ userId: string; date: Date | null }[], Record<string, Date>>(
|
||||||
|
(acc, value) => {
|
||||||
|
for (const { userId, date } of value) {
|
||||||
|
if (date) {
|
||||||
|
acc[userId] = date;
|
||||||
|
} else {
|
||||||
|
delete acc[userId];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log("handsRaised", acc);
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emits an array of reactions that should be visible on the screen.
|
* Emits an array of reactions that should be visible on the screen.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ import {
|
|||||||
createHandRaisedReaction,
|
createHandRaisedReaction,
|
||||||
createRedaction,
|
createRedaction,
|
||||||
MockRoom,
|
MockRoom,
|
||||||
ReactionsMockRTCSession,
|
|
||||||
TestReactionsWrapper,
|
|
||||||
} from "./utils/testReactions";
|
} from "./utils/testReactions";
|
||||||
|
|
||||||
const memberUserIdAlice = "@alice:example.org";
|
const memberUserIdAlice = "@alice:example.org";
|
||||||
|
|||||||
+19
-19
@@ -35,17 +35,10 @@ import {
|
|||||||
ReactionSet,
|
ReactionSet,
|
||||||
} from "./reactions";
|
} from "./reactions";
|
||||||
import { useLatest } from "./useLatest";
|
import { useLatest } from "./useLatest";
|
||||||
|
import { CallViewModel } from "./state/CallViewModel";
|
||||||
|
|
||||||
interface ReactionsContextType {
|
interface ReactionsContextType {
|
||||||
/**
|
|
||||||
* identifier (userId:deviceId => Date)
|
|
||||||
*/
|
|
||||||
raisedHands: Record<string, Date>;
|
|
||||||
supportsReactions: boolean;
|
supportsReactions: boolean;
|
||||||
/**
|
|
||||||
* reactions (userId:deviceId => Date)
|
|
||||||
*/
|
|
||||||
reactions: Record<string, ReactionOption>;
|
|
||||||
toggleRaisedHand: () => Promise<void>;
|
toggleRaisedHand: () => Promise<void>;
|
||||||
sendReaction: (reaction: ReactionOption) => Promise<void>;
|
sendReaction: (reaction: ReactionOption) => Promise<void>;
|
||||||
}
|
}
|
||||||
@@ -79,15 +72,23 @@ export const useReactions = (): ReactionsContextType => {
|
|||||||
return context;
|
return context;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HS plan:
|
||||||
|
* Provider should publish new hand raised, reaction events to CallViewModel
|
||||||
|
* Provider should listen for new events from CVM
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provider that handles raised hand reactions for a given `rtcSession`.
|
* Provider that handles raised hand reactions for a given `rtcSession`.
|
||||||
*/
|
*/
|
||||||
export const ReactionsProvider = ({
|
export const ReactionsProvider = ({
|
||||||
children,
|
children,
|
||||||
rtcSession,
|
rtcSession,
|
||||||
|
vm,
|
||||||
}: {
|
}: {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
rtcSession: MatrixRTCSession;
|
rtcSession: MatrixRTCSession;
|
||||||
|
vm: CallViewModel;
|
||||||
}): JSX.Element => {
|
}): JSX.Element => {
|
||||||
const [raisedHands, setRaisedHands] = useState<
|
const [raisedHands, setRaisedHands] = useState<
|
||||||
Record<string, RaisedHandInfo>
|
Record<string, RaisedHandInfo>
|
||||||
@@ -103,6 +104,15 @@ export const ReactionsProvider = ({
|
|||||||
const latestMemberships = useLatest(memberships);
|
const latestMemberships = useLatest(memberships);
|
||||||
const latestRaisedHands = useLatest(raisedHands);
|
const latestRaisedHands = useLatest(raisedHands);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
vm.updateReactions({
|
||||||
|
raisedHands: Object.fromEntries(
|
||||||
|
Object.entries(raisedHands).map(([uid, data]) => [uid, data.time]),
|
||||||
|
),
|
||||||
|
reactions,
|
||||||
|
});
|
||||||
|
}, [memberships, raisedHands]);
|
||||||
|
|
||||||
const myMembershipEvent = useMemo(
|
const myMembershipEvent = useMemo(
|
||||||
() =>
|
() =>
|
||||||
memberships.find(
|
memberships.find(
|
||||||
@@ -121,14 +131,6 @@ export const ReactionsProvider = ({
|
|||||||
{},
|
{},
|
||||||
);
|
);
|
||||||
|
|
||||||
// Reduce the data down for the consumers.
|
|
||||||
const resultRaisedHands = useMemo(
|
|
||||||
() =>
|
|
||||||
Object.fromEntries(
|
|
||||||
Object.entries(raisedHands).map(([uid, data]) => [uid, data.time]),
|
|
||||||
),
|
|
||||||
[raisedHands],
|
|
||||||
);
|
|
||||||
const addRaisedHand = useCallback((userId: string, info: RaisedHandInfo) => {
|
const addRaisedHand = useCallback((userId: string, info: RaisedHandInfo) => {
|
||||||
setRaisedHands((prevRaisedHands) => ({
|
setRaisedHands((prevRaisedHands) => ({
|
||||||
...prevRaisedHands,
|
...prevRaisedHands,
|
||||||
@@ -394,7 +396,7 @@ export const ReactionsProvider = ({
|
|||||||
|
|
||||||
const sendReaction = useCallback(
|
const sendReaction = useCallback(
|
||||||
async (reaction: ReactionOption) => {
|
async (reaction: ReactionOption) => {
|
||||||
if (!myMembershipIdentifier || !reactions[myMembershipIdentifier]) {
|
if (!myMembershipIdentifier || reactions[myMembershipIdentifier]) {
|
||||||
// We're still reacting
|
// We're still reacting
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -420,9 +422,7 @@ export const ReactionsProvider = ({
|
|||||||
return (
|
return (
|
||||||
<ReactionsContext.Provider
|
<ReactionsContext.Provider
|
||||||
value={{
|
value={{
|
||||||
raisedHands: resultRaisedHands,
|
|
||||||
supportsReactions,
|
supportsReactions,
|
||||||
reactions,
|
|
||||||
toggleRaisedHand,
|
toggleRaisedHand,
|
||||||
sendReaction,
|
sendReaction,
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { type PropsWithChildren, type ReactNode } from "react";
|
|
||||||
import { randomUUID } from "crypto";
|
import { randomUUID } from "crypto";
|
||||||
import EventEmitter from "events";
|
import EventEmitter from "events";
|
||||||
import { type MatrixClient } from "matrix-js-sdk/src/client";
|
import { type MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
@@ -16,52 +15,11 @@ import {
|
|||||||
EventTimelineSet,
|
EventTimelineSet,
|
||||||
type Room,
|
type Room,
|
||||||
} from "matrix-js-sdk/src/matrix";
|
} from "matrix-js-sdk/src/matrix";
|
||||||
import {
|
|
||||||
type MatrixRTCSession,
|
|
||||||
MatrixRTCSessionEvent,
|
|
||||||
} from "matrix-js-sdk/src/matrixrtc";
|
|
||||||
|
|
||||||
import { ReactionsProvider } from "../useReactions";
|
|
||||||
import {
|
import {
|
||||||
type ECallReactionEventContent,
|
type ECallReactionEventContent,
|
||||||
ElementCallReactionEventType,
|
ElementCallReactionEventType,
|
||||||
type ReactionOption,
|
type ReactionOption,
|
||||||
} from "../reactions";
|
} from "../reactions";
|
||||||
import { MockRTCSession } from "./test";
|
|
||||||
|
|
||||||
// export class ReactionsMockRTCSession extends EventEmitter {
|
|
||||||
// public memberships: {
|
|
||||||
// sender: string;
|
|
||||||
// eventId: string;
|
|
||||||
// createdTs: () => Date;
|
|
||||||
// }[];
|
|
||||||
|
|
||||||
// public constructor(
|
|
||||||
// public readonly room: MockRoom,
|
|
||||||
// membership: Record<string, string>,
|
|
||||||
// ) {
|
|
||||||
// super();
|
|
||||||
// this.memberships = Object.entries(membership).map(([eventId, sender]) => ({
|
|
||||||
// sender,
|
|
||||||
// eventId,
|
|
||||||
// createdTs: (): Date => new Date(),
|
|
||||||
// }));
|
|
||||||
// }
|
|
||||||
|
|
||||||
// public testRemoveMember(userId: string): void {
|
|
||||||
// this.memberships = this.memberships.filter((u) => u.sender !== userId);
|
|
||||||
// this.emit(MatrixRTCSessionEvent.MembershipsChanged);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// public testAddMember(sender: string): void {
|
|
||||||
// this.memberships.push({
|
|
||||||
// sender,
|
|
||||||
// eventId: `!fake-${randomUUID()}:event`,
|
|
||||||
// createdTs: (): Date => new Date(),
|
|
||||||
// });
|
|
||||||
// this.emit(MatrixRTCSessionEvent.MembershipsChanged);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
export function createHandRaisedReaction(
|
export function createHandRaisedReaction(
|
||||||
parentMemberEvent: string,
|
parentMemberEvent: string,
|
||||||
|
|||||||
Reference in New Issue
Block a user