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 { _t } from "../../../languageHandler";
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 Clock from "../../../components/views/audio_messages/Clock";
import { formatTimeLeft } from "../../../DateUtils";
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";
interface VoiceBroadcastHeaderProps {
linkToRoom?: boolean;
live?: VoiceBroadcastLiveness;
onCloseClick?: () => void;
onMicrophoneLineClick?: () => void;
onMicrophoneLineClick?: ((e: ButtonEvent) => void | Promise<void>) | null;
room: Room;
microphoneLabel?: string;
showBroadcast?: boolean;
@ -41,9 +45,10 @@ interface VoiceBroadcastHeaderProps {
}
export const VoiceBroadcastHeader: React.FC<VoiceBroadcastHeaderProps> = ({
linkToRoom = false,
live = "not-live",
onCloseClick = () => {},
onMicrophoneLineClick,
onMicrophoneLineClick = null,
room,
microphoneLabel,
showBroadcast = false,
@ -96,11 +101,28 @@ export const VoiceBroadcastHeader: React.FC<VoiceBroadcastHeaderProps> = ({
</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 (
<div className="mx_VoiceBroadcastHeader">
<RoomAvatar room={room} width={32} height={32} />
{roomAvatar}
<div className="mx_VoiceBroadcastHeader_content">
<div className="mx_VoiceBroadcastHeader_room">{room.name}</div>
{roomName}
{microphoneLine}
{timeLeftLine}
{broadcast}

View file

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

View file

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

View file

@ -74,7 +74,7 @@ export const VoiceBroadcastRecordingPip: React.FC<VoiceBroadcastRecordingPipProp
return (
<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" />
<div className="mx_VoiceBroadcastBody_controls">
{toggleControl}

View file

@ -30,6 +30,10 @@ import {
} from "../../../../src/voice-broadcast";
import { stubClient } from "../../../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
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`, () => {

View file

@ -29,7 +29,10 @@ import {
import { flushPromises, stubClient } from "../../../test-utils";
import { requestMediaPermissions } from "../../../../src/utils/media/requestMediaPermissions";
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");
// mock RoomAvatar, because it is doing too much fancy stuff
@ -49,19 +52,25 @@ describe("VoiceBroadcastPreRecordingPip", () => {
let room: Room;
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(() => {
client = stubClient();
room = new Room("!room@example.com", client, client.getUserId() || "");
sender = new RoomMember(room.roomId, client.getUserId() || "");
playbacksStore = new VoiceBroadcastPlaybacksStore();
recordingsStore = new VoiceBroadcastRecordingsStore();
mocked(requestMediaPermissions).mockReturnValue(
new Promise<MediaStream>((r) => {
r({
getTracks: () => [],
} as unknown as MediaStream);
}),
);
mocked(requestMediaPermissions).mockResolvedValue({
getTracks: (): Array<MediaStreamTrack> => [],
} as unknown as MediaStream);
jest.spyOn(MediaDeviceHandler, "getDevices").mockResolvedValue({
[MediaDeviceKindEnum.AudioInput]: [
{
@ -97,6 +106,22 @@ describe("VoiceBroadcastPreRecordingPip", () => {
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", () => {
beforeEach(async () => {
await act(async () => {

View file

@ -31,7 +31,10 @@ import { filterConsole, flushPromises, stubClient } from "../../../test-utils";
import { mkVoiceBroadcastInfoStateEvent } from "../../utils/test-utils";
import { requestMediaPermissions } from "../../../../src/utils/media/requestMediaPermissions";
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");
// 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(() => {
client = stubClient();
mocked(requestMediaPermissions).mockReturnValue(
new Promise<MediaStream>((r) => {
r({
getTracks: () => [],
} as unknown as MediaStream);
}),
);
mocked(requestMediaPermissions).mockResolvedValue({
getTracks: (): Array<MediaStreamTrack> => [],
} as unknown as MediaStream);
jest.spyOn(MediaDeviceHandler, "getDevices").mockResolvedValue({
[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", () => {
beforeEach(async () => {
await userEvent.click(screen.getByLabelText("pause voice broadcast"));

View file

@ -345,6 +345,125 @@ exports[`VoiceBroadcastPlaybackBody when rendering a buffering voice broadcast s
</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`] = `
<div>
<div

View file

@ -9,18 +9,30 @@ exports[`VoiceBroadcastPreRecordingPip when rendered should match the snapshot 1
class="mx_VoiceBroadcastHeader"
>
<div
data-testid="room-avatar"
class="mx_AccessibleButton"
role="button"
tabindex="0"
>
room avatar:
!room@example.com
<div
data-testid="room-avatar"
>
room avatar:
!room@example.com
</div>
</div>
<div
class="mx_VoiceBroadcastHeader_content"
>
<div
class="mx_VoiceBroadcastHeader_room"
class="mx_AccessibleButton"
role="button"
tabindex="0"
>
!room@example.com
<div
class="mx_VoiceBroadcastHeader_room"
>
!room@example.com
</div>
</div>
<div
aria-label="Change input device"

View file

@ -9,18 +9,30 @@ exports[`VoiceBroadcastRecordingPip when rendering a paused recording should ren
class="mx_VoiceBroadcastHeader"
>
<div
data-testid="room-avatar"
class="mx_AccessibleButton"
role="button"
tabindex="0"
>
room avatar:
My room
<div
data-testid="room-avatar"
>
room avatar:
My room
</div>
</div>
<div
class="mx_VoiceBroadcastHeader_content"
>
<div
class="mx_VoiceBroadcastHeader_room"
class="mx_AccessibleButton"
role="button"
tabindex="0"
>
My room
<div
class="mx_VoiceBroadcastHeader_room"
>
My room
</div>
</div>
<div
class="mx_VoiceBroadcastHeader_line"
@ -94,18 +106,30 @@ exports[`VoiceBroadcastRecordingPip when rendering a started recording should re
class="mx_VoiceBroadcastHeader"
>
<div
data-testid="room-avatar"
class="mx_AccessibleButton"
role="button"
tabindex="0"
>
room avatar:
My room
<div
data-testid="room-avatar"
>
room avatar:
My room
</div>
</div>
<div
class="mx_VoiceBroadcastHeader_content"
>
<div
class="mx_VoiceBroadcastHeader_room"
class="mx_AccessibleButton"
role="button"
tabindex="0"
>
My room
<div
class="mx_VoiceBroadcastHeader_room"
>
My room
</div>
</div>
<div
class="mx_VoiceBroadcastHeader_line"