Link voice broadcast avatar and room name to room (#9722)

This commit is contained in:
Michael Weimann 2022-12-13 13:32:19 +01:00 committed by GitHub
parent 9c5b1f3540
commit a701296f33
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 304 additions and 35 deletions

View file

@ -21,17 +21,21 @@ import { Icon as MicrophoneIcon } from "../../../../res/img/voip/call-view/mic-o
import { Icon as TimerIcon } from "../../../../res/img/element-icons/Timer.svg"; import { Icon as TimerIcon } from "../../../../res/img/element-icons/Timer.svg";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
import RoomAvatar from "../../../components/views/avatars/RoomAvatar"; import RoomAvatar from "../../../components/views/avatars/RoomAvatar";
import AccessibleButton from "../../../components/views/elements/AccessibleButton"; import AccessibleButton, { ButtonEvent } from "../../../components/views/elements/AccessibleButton";
import { Icon as XIcon } from "../../../../res/img/element-icons/cancel-rounded.svg"; import { Icon as XIcon } from "../../../../res/img/element-icons/cancel-rounded.svg";
import Clock from "../../../components/views/audio_messages/Clock"; import Clock from "../../../components/views/audio_messages/Clock";
import { formatTimeLeft } from "../../../DateUtils"; import { formatTimeLeft } from "../../../DateUtils";
import Spinner from "../../../components/views/elements/Spinner"; import Spinner from "../../../components/views/elements/Spinner";
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
import { Action } from "../../../dispatcher/actions";
import dis from "../../../dispatcher/dispatcher";
import AccessibleTooltipButton from "../../../components/views/elements/AccessibleTooltipButton"; import AccessibleTooltipButton from "../../../components/views/elements/AccessibleTooltipButton";
interface VoiceBroadcastHeaderProps { interface VoiceBroadcastHeaderProps {
linkToRoom?: boolean;
live?: VoiceBroadcastLiveness; live?: VoiceBroadcastLiveness;
onCloseClick?: () => void; onCloseClick?: () => void;
onMicrophoneLineClick?: () => void; onMicrophoneLineClick?: ((e: ButtonEvent) => void | Promise<void>) | null;
room: Room; room: Room;
microphoneLabel?: string; microphoneLabel?: string;
showBroadcast?: boolean; showBroadcast?: boolean;
@ -41,9 +45,10 @@ interface VoiceBroadcastHeaderProps {
} }
export const VoiceBroadcastHeader: React.FC<VoiceBroadcastHeaderProps> = ({ export const VoiceBroadcastHeader: React.FC<VoiceBroadcastHeaderProps> = ({
linkToRoom = false,
live = "not-live", live = "not-live",
onCloseClick = () => {}, onCloseClick = () => {},
onMicrophoneLineClick, onMicrophoneLineClick = null,
room, room,
microphoneLabel, microphoneLabel,
showBroadcast = false, showBroadcast = false,
@ -96,11 +101,28 @@ export const VoiceBroadcastHeader: React.FC<VoiceBroadcastHeaderProps> = ({
</AccessibleTooltipButton> </AccessibleTooltipButton>
); );
const onRoomAvatarOrNameClick = (): void => {
dis.dispatch<ViewRoomPayload>({
action: Action.ViewRoom,
room_id: room.roomId,
metricsTrigger: undefined, // other
});
};
let roomAvatar = <RoomAvatar room={room} width={32} height={32} />;
let roomName = <div className="mx_VoiceBroadcastHeader_room">{room.name}</div>;
if (linkToRoom) {
roomAvatar = <AccessibleButton onClick={onRoomAvatarOrNameClick}>{roomAvatar}</AccessibleButton>;
roomName = <AccessibleButton onClick={onRoomAvatarOrNameClick}>{roomName}</AccessibleButton>;
}
return ( return (
<div className="mx_VoiceBroadcastHeader"> <div className="mx_VoiceBroadcastHeader">
<RoomAvatar room={room} width={32} height={32} /> {roomAvatar}
<div className="mx_VoiceBroadcastHeader_content"> <div className="mx_VoiceBroadcastHeader_content">
<div className="mx_VoiceBroadcastHeader_room">{room.name}</div> {roomName}
{microphoneLine} {microphoneLine}
{timeLeftLine} {timeLeftLine}
{broadcast} {broadcast}

View file

@ -98,6 +98,7 @@ export const VoiceBroadcastPlaybackBody: React.FC<VoiceBroadcastPlaybackBodyProp
return ( return (
<div className={classes}> <div className={classes}>
<VoiceBroadcastHeader <VoiceBroadcastHeader
linkToRoom={pip}
live={liveness} live={liveness}
microphoneLabel={sender?.name} microphoneLabel={sender?.name}
room={room} room={room}

View file

@ -41,6 +41,7 @@ export const VoiceBroadcastPreRecordingPip: React.FC<Props> = ({ voiceBroadcastP
return ( return (
<div className="mx_VoiceBroadcastBody mx_VoiceBroadcastBody--pip" ref={pipRef}> <div className="mx_VoiceBroadcastBody mx_VoiceBroadcastBody--pip" ref={pipRef}>
<VoiceBroadcastHeader <VoiceBroadcastHeader
linkToRoom={true}
onCloseClick={voiceBroadcastPreRecording.cancel} onCloseClick={voiceBroadcastPreRecording.cancel}
onMicrophoneLineClick={() => setShowDeviceSelect(true)} onMicrophoneLineClick={() => setShowDeviceSelect(true)}
room={voiceBroadcastPreRecording.room} room={voiceBroadcastPreRecording.room}

View file

@ -74,7 +74,7 @@ export const VoiceBroadcastRecordingPip: React.FC<VoiceBroadcastRecordingPipProp
return ( return (
<div className="mx_VoiceBroadcastBody mx_VoiceBroadcastBody--pip" ref={pipRef}> <div className="mx_VoiceBroadcastBody mx_VoiceBroadcastBody--pip" ref={pipRef}>
<VoiceBroadcastHeader live={live ? "live" : "grey"} room={room} timeLeft={timeLeft} /> <VoiceBroadcastHeader linkToRoom={true} live={live ? "live" : "grey"} room={room} timeLeft={timeLeft} />
<hr className="mx_VoiceBroadcastBody_divider" /> <hr className="mx_VoiceBroadcastBody_divider" />
<div className="mx_VoiceBroadcastBody_controls"> <div className="mx_VoiceBroadcastBody_controls">
{toggleControl} {toggleControl}

View file

@ -30,6 +30,10 @@ import {
} from "../../../../src/voice-broadcast"; } from "../../../../src/voice-broadcast";
import { stubClient } from "../../../test-utils"; import { stubClient } from "../../../test-utils";
import { mkVoiceBroadcastInfoStateEvent } from "../../utils/test-utils"; import { mkVoiceBroadcastInfoStateEvent } from "../../utils/test-utils";
import dis from "../../../../src/dispatcher/dispatcher";
import { Action } from "../../../../src/dispatcher/actions";
jest.mock("../../../../src/dispatcher/dispatcher");
// mock RoomAvatar, because it is doing too much fancy stuff // mock RoomAvatar, because it is doing too much fancy stuff
jest.mock("../../../../src/components/views/avatars/RoomAvatar", () => ({ jest.mock("../../../../src/components/views/avatars/RoomAvatar", () => ({
@ -128,6 +132,42 @@ describe("VoiceBroadcastPlaybackBody", () => {
}); });
}); });
}); });
describe("and clicking the room name", () => {
beforeEach(async () => {
await userEvent.click(screen.getByText("My room"));
});
it("should not view the room", () => {
expect(dis.dispatch).not.toHaveBeenCalled();
});
});
});
describe("when rendering a playing broadcast in pip mode", () => {
beforeEach(() => {
mocked(playback.getState).mockReturnValue(VoiceBroadcastPlaybackState.Playing);
mocked(playback.getLiveness).mockReturnValue("not-live");
renderResult = render(<VoiceBroadcastPlaybackBody pip={true} playback={playback} />);
});
it("should render as expected", () => {
expect(renderResult.container).toMatchSnapshot();
});
describe("and clicking the room name", () => {
beforeEach(async () => {
await userEvent.click(screen.getByText("My room"));
});
it("should view the room", () => {
expect(dis.dispatch).toHaveBeenCalledWith({
action: Action.ViewRoom,
room_id: roomId,
metricsTrigger: undefined,
});
});
});
}); });
describe(`when rendering a stopped broadcast`, () => { describe(`when rendering a stopped broadcast`, () => {

View file

@ -29,7 +29,10 @@ import {
import { flushPromises, stubClient } from "../../../test-utils"; import { flushPromises, stubClient } from "../../../test-utils";
import { requestMediaPermissions } from "../../../../src/utils/media/requestMediaPermissions"; import { requestMediaPermissions } from "../../../../src/utils/media/requestMediaPermissions";
import MediaDeviceHandler, { MediaDeviceKindEnum } from "../../../../src/MediaDeviceHandler"; import MediaDeviceHandler, { MediaDeviceKindEnum } from "../../../../src/MediaDeviceHandler";
import dis from "../../../../src/dispatcher/dispatcher";
import { Action } from "../../../../src/dispatcher/actions";
jest.mock("../../../../src/dispatcher/dispatcher");
jest.mock("../../../../src/utils/media/requestMediaPermissions"); jest.mock("../../../../src/utils/media/requestMediaPermissions");
// mock RoomAvatar, because it is doing too much fancy stuff // mock RoomAvatar, because it is doing too much fancy stuff
@ -49,19 +52,25 @@ describe("VoiceBroadcastPreRecordingPip", () => {
let room: Room; let room: Room;
let sender: RoomMember; let sender: RoomMember;
const itShouldShowTheBroadcastRoom = () => {
it("should show the broadcast room", () => {
expect(dis.dispatch).toHaveBeenCalledWith({
action: Action.ViewRoom,
room_id: room.roomId,
metricsTrigger: undefined,
});
});
};
beforeEach(() => { beforeEach(() => {
client = stubClient(); client = stubClient();
room = new Room("!room@example.com", client, client.getUserId() || ""); room = new Room("!room@example.com", client, client.getUserId() || "");
sender = new RoomMember(room.roomId, client.getUserId() || ""); sender = new RoomMember(room.roomId, client.getUserId() || "");
playbacksStore = new VoiceBroadcastPlaybacksStore(); playbacksStore = new VoiceBroadcastPlaybacksStore();
recordingsStore = new VoiceBroadcastRecordingsStore(); recordingsStore = new VoiceBroadcastRecordingsStore();
mocked(requestMediaPermissions).mockReturnValue( mocked(requestMediaPermissions).mockResolvedValue({
new Promise<MediaStream>((r) => { getTracks: (): Array<MediaStreamTrack> => [],
r({
getTracks: () => [],
} as unknown as MediaStream); } as unknown as MediaStream);
}),
);
jest.spyOn(MediaDeviceHandler, "getDevices").mockResolvedValue({ jest.spyOn(MediaDeviceHandler, "getDevices").mockResolvedValue({
[MediaDeviceKindEnum.AudioInput]: [ [MediaDeviceKindEnum.AudioInput]: [
{ {
@ -97,6 +106,22 @@ describe("VoiceBroadcastPreRecordingPip", () => {
expect(renderResult.container).toMatchSnapshot(); expect(renderResult.container).toMatchSnapshot();
}); });
describe("and clicking the room name", () => {
beforeEach(async () => {
await userEvent.click(screen.getByText(room.name));
});
itShouldShowTheBroadcastRoom();
});
describe("and clicking the room avatar", () => {
beforeEach(async () => {
await userEvent.click(screen.getByText(`room avatar: ${room.name}`));
});
itShouldShowTheBroadcastRoom();
});
describe("and clicking the device label", () => { describe("and clicking the device label", () => {
beforeEach(async () => { beforeEach(async () => {
await act(async () => { await act(async () => {

View file

@ -31,7 +31,10 @@ import { filterConsole, flushPromises, stubClient } from "../../../test-utils";
import { mkVoiceBroadcastInfoStateEvent } from "../../utils/test-utils"; import { mkVoiceBroadcastInfoStateEvent } from "../../utils/test-utils";
import { requestMediaPermissions } from "../../../../src/utils/media/requestMediaPermissions"; import { requestMediaPermissions } from "../../../../src/utils/media/requestMediaPermissions";
import MediaDeviceHandler, { MediaDeviceKindEnum } from "../../../../src/MediaDeviceHandler"; import MediaDeviceHandler, { MediaDeviceKindEnum } from "../../../../src/MediaDeviceHandler";
import dis from "../../../../src/dispatcher/dispatcher";
import { Action } from "../../../../src/dispatcher/actions";
jest.mock("../../../../src/dispatcher/dispatcher");
jest.mock("../../../../src/utils/media/requestMediaPermissions"); jest.mock("../../../../src/utils/media/requestMediaPermissions");
// mock RoomAvatar, because it is doing too much fancy stuff // mock RoomAvatar, because it is doing too much fancy stuff
@ -72,15 +75,21 @@ describe("VoiceBroadcastRecordingPip", () => {
}); });
}; };
const itShouldShowTheBroadcastRoom = () => {
it("should show the broadcast room", () => {
expect(dis.dispatch).toHaveBeenCalledWith({
action: Action.ViewRoom,
room_id: roomId,
metricsTrigger: undefined,
});
});
};
beforeAll(() => { beforeAll(() => {
client = stubClient(); client = stubClient();
mocked(requestMediaPermissions).mockReturnValue( mocked(requestMediaPermissions).mockResolvedValue({
new Promise<MediaStream>((r) => { getTracks: (): Array<MediaStreamTrack> => [],
r({
getTracks: () => [],
} as unknown as MediaStream); } as unknown as MediaStream);
}),
);
jest.spyOn(MediaDeviceHandler, "getDevices").mockResolvedValue({ jest.spyOn(MediaDeviceHandler, "getDevices").mockResolvedValue({
[MediaDeviceKindEnum.AudioInput]: [ [MediaDeviceKindEnum.AudioInput]: [
{ {
@ -130,6 +139,22 @@ describe("VoiceBroadcastRecordingPip", () => {
}); });
}); });
describe("and clicking the room name", () => {
beforeEach(async () => {
await userEvent.click(screen.getByText("My room"));
});
itShouldShowTheBroadcastRoom();
});
describe("and clicking the room avatar", () => {
beforeEach(async () => {
await userEvent.click(screen.getByText("room avatar: My room"));
});
itShouldShowTheBroadcastRoom();
});
describe("and clicking the pause button", () => { describe("and clicking the pause button", () => {
beforeEach(async () => { beforeEach(async () => {
await userEvent.click(screen.getByLabelText("pause voice broadcast")); await userEvent.click(screen.getByLabelText("pause voice broadcast"));

View file

@ -345,6 +345,125 @@ exports[`VoiceBroadcastPlaybackBody when rendering a buffering voice broadcast s
</div> </div>
`; `;
exports[`VoiceBroadcastPlaybackBody when rendering a playing broadcast in pip mode should render as expected 1`] = `
<div>
<div
class="mx_VoiceBroadcastBody mx_VoiceBroadcastBody--pip"
>
<div
class="mx_VoiceBroadcastHeader"
>
<div
class="mx_AccessibleButton"
role="button"
tabindex="0"
>
<div
data-testid="room-avatar"
>
room avatar:
My room
</div>
</div>
<div
class="mx_VoiceBroadcastHeader_content"
>
<div
class="mx_AccessibleButton"
role="button"
tabindex="0"
>
<div
class="mx_VoiceBroadcastHeader_room"
>
My room
</div>
</div>
<div
aria-label="Change input device"
class="mx_AccessibleButton mx_VoiceBroadcastHeader_line"
role="button"
tabindex="0"
>
<div
class="mx_Icon mx_Icon_16"
/>
<span>
@user:example.com
</span>
</div>
<div
class="mx_VoiceBroadcastHeader_line"
>
<div
class="mx_Icon mx_Icon_16"
/>
Voice broadcast
</div>
</div>
</div>
<div
class="mx_VoiceBroadcastBody_controls"
>
<div
aria-label="30s backward"
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_secondary_content"
role="button"
tabindex="0"
>
<div
class="mx_Icon mx_Icon_24"
/>
</div>
<div
aria-label="pause voice broadcast"
class="mx_AccessibleButton mx_VoiceBroadcastControl"
role="button"
tabindex="0"
>
<div
class="mx_Icon mx_Icon_16"
/>
</div>
<div
aria-label="30s forward"
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_secondary_content"
role="button"
tabindex="0"
>
<div
class="mx_Icon mx_Icon_24"
/>
</div>
</div>
<input
class="mx_SeekBar"
max="1"
min="0"
step="0.001"
style="--fillTo: 0;"
tabindex="0"
type="range"
value="0"
/>
<div
class="mx_VoiceBroadcastBody_timerow"
>
<span
class="mx_Clock"
>
00:00
</span>
<span
class="mx_Clock"
>
-23:42
</span>
</div>
</div>
</div>
`;
exports[`VoiceBroadcastPlaybackBody when rendering a playing broadcast should render as expected 1`] = ` exports[`VoiceBroadcastPlaybackBody when rendering a playing broadcast should render as expected 1`] = `
<div> <div>
<div <div

View file

@ -7,6 +7,11 @@ exports[`VoiceBroadcastPreRecordingPip when rendered should match the snapshot 1
> >
<div <div
class="mx_VoiceBroadcastHeader" class="mx_VoiceBroadcastHeader"
>
<div
class="mx_AccessibleButton"
role="button"
tabindex="0"
> >
<div <div
data-testid="room-avatar" data-testid="room-avatar"
@ -14,14 +19,21 @@ exports[`VoiceBroadcastPreRecordingPip when rendered should match the snapshot 1
room avatar: room avatar:
!room@example.com !room@example.com
</div> </div>
</div>
<div <div
class="mx_VoiceBroadcastHeader_content" class="mx_VoiceBroadcastHeader_content"
>
<div
class="mx_AccessibleButton"
role="button"
tabindex="0"
> >
<div <div
class="mx_VoiceBroadcastHeader_room" class="mx_VoiceBroadcastHeader_room"
> >
!room@example.com !room@example.com
</div> </div>
</div>
<div <div
aria-label="Change input device" aria-label="Change input device"
class="mx_AccessibleButton mx_VoiceBroadcastHeader_line mx_VoiceBroadcastHeader_mic--clickable" class="mx_AccessibleButton mx_VoiceBroadcastHeader_line mx_VoiceBroadcastHeader_mic--clickable"

View file

@ -7,6 +7,11 @@ exports[`VoiceBroadcastRecordingPip when rendering a paused recording should ren
> >
<div <div
class="mx_VoiceBroadcastHeader" class="mx_VoiceBroadcastHeader"
>
<div
class="mx_AccessibleButton"
role="button"
tabindex="0"
> >
<div <div
data-testid="room-avatar" data-testid="room-avatar"
@ -14,14 +19,21 @@ exports[`VoiceBroadcastRecordingPip when rendering a paused recording should ren
room avatar: room avatar:
My room My room
</div> </div>
</div>
<div <div
class="mx_VoiceBroadcastHeader_content" class="mx_VoiceBroadcastHeader_content"
>
<div
class="mx_AccessibleButton"
role="button"
tabindex="0"
> >
<div <div
class="mx_VoiceBroadcastHeader_room" class="mx_VoiceBroadcastHeader_room"
> >
My room My room
</div> </div>
</div>
<div <div
class="mx_VoiceBroadcastHeader_line" class="mx_VoiceBroadcastHeader_line"
> >
@ -92,6 +104,11 @@ exports[`VoiceBroadcastRecordingPip when rendering a started recording should re
> >
<div <div
class="mx_VoiceBroadcastHeader" class="mx_VoiceBroadcastHeader"
>
<div
class="mx_AccessibleButton"
role="button"
tabindex="0"
> >
<div <div
data-testid="room-avatar" data-testid="room-avatar"
@ -99,14 +116,21 @@ exports[`VoiceBroadcastRecordingPip when rendering a started recording should re
room avatar: room avatar:
My room My room
</div> </div>
</div>
<div <div
class="mx_VoiceBroadcastHeader_content" class="mx_VoiceBroadcastHeader_content"
>
<div
class="mx_AccessibleButton"
role="button"
tabindex="0"
> >
<div <div
class="mx_VoiceBroadcastHeader_room" class="mx_VoiceBroadcastHeader_room"
> >
My room My room
</div> </div>
</div>
<div <div
class="mx_VoiceBroadcastHeader_line" class="mx_VoiceBroadcastHeader_line"
> >