Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for break-out rooms #1615

Draft
wants to merge 35 commits into
base: livekit
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
b5d710a
Add `arrayFastClone`
SimonBrandner Sep 26, 2023
1f72be4
Add `Remove` icon
SimonBrandner Sep 26, 2023
e1b1586
Add `RemoveButton`
SimonBrandner Sep 26, 2023
430fca6
Add basic `BreakoutRoomModal`
SimonBrandner Sep 26, 2023
7e3b05f
Add `BreakoutRoom` icon
SimonBrandner Sep 26, 2023
47f171c
Add `BreakoutRoomButton`
SimonBrandner Sep 26, 2023
0cda5fc
Present `BreakoutRoomButton` in UI
SimonBrandner Sep 26, 2023
fb5dcf8
Add new BreakoutRooms button
SimonBrandner Sep 29, 2023
65a2fab
Make sure we show loading when joining a new room
SimonBrandner Oct 1, 2023
83f2b80
Make sure to create a key for new breakout rooms
SimonBrandner Oct 1, 2023
e5838e1
Add `BreakoutRoomsOverlay`
SimonBrandner Oct 1, 2023
bbe70b3
Hack to make rooms visible in home screen
SimonBrandner Oct 1, 2023
af72a68
Add `ButtonWithDropdown`
SimonBrandner Oct 16, 2023
5938ea6
Add ability to add users
SimonBrandner Oct 16, 2023
c8183d4
Display users in overlay
SimonBrandner Oct 17, 2023
21052aa
Merge branch 'livekit' into break-out-rooms
SimonBrandner Oct 18, 2023
1cd0545
Update-js sdk
SimonBrandner Oct 19, 2023
17d6bfa
Merge remote-tracking branch 'upstream/livekit' into break-out-rooms
SimonBrandner Oct 19, 2023
4631a7d
Post-merge fix-up
SimonBrandner Oct 19, 2023
099b7ff
Post-merge fix-up
SimonBrandner Oct 19, 2023
90e2acd
Delint
SimonBrandner Oct 19, 2023
c66c1bc
Simplify break-out room handling
SimonBrandner Oct 19, 2023
a59ab95
Share break-out room keys
SimonBrandner Oct 19, 2023
95547f1
Delint
SimonBrandner Oct 19, 2023
0887065
i18n
SimonBrandner Oct 19, 2023
61d8927
Update js-sdk
SimonBrandner Oct 19, 2023
16bdcca
Delint
SimonBrandner Oct 19, 2023
b6b2911
Fix `ButtonWithDropdown` behaviour
SimonBrandner Oct 19, 2023
3635689
Don't forget to reset submitting state
SimonBrandner Oct 19, 2023
9428ee6
Handle existing break-out rooms
SimonBrandner Oct 19, 2023
82c1e63
Merge branch 'livekit' into break-out-rooms
SimonBrandner Jan 27, 2024
2d43391
Update `yarn.lock`
SimonBrandner Jan 27, 2024
6a9b9f9
i18n
SimonBrandner Jan 27, 2024
45f02d2
i18n
SimonBrandner Jan 27, 2024
b13bc63
Fix merge mess up
SimonBrandner Jan 27, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
"i18next-http-backend": "^2.0.0",
"livekit-client": "^1.12.3",
"lodash": "^4.17.21",
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#2cd63ca4b90eb2e4d22b45ae281a81c4514e757a",
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#59b75c6eec32b69f35bc8599aa7e823b875721f6",
"matrix-widget-api": "^1.3.1",
"normalize.css": "^8.0.1",
"pako": "^2.0.4",
Expand Down
11 changes: 11 additions & 0 deletions public/locales/en-GB/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
"sign_out": "Sign out",
"submit": "Submit"
},
"Add break-out room": "Add break-out room",
"Add user": "Add user",
"analytics_notice": "By participating in this beta, you consent to the collection of anonymous data, which we use to improve the product. You can find more information about which data we track in our <2>Privacy Policy</2> and our <5>Cookie Policy</5>.",
"app_selection_modal": {
"continue_in_browser": "Continue in browser",
Expand All @@ -23,6 +25,10 @@
"title": "Select app"
},
"application_opened_another_tab": "This application has been opened in another tab.",
"Break-out room": "Break-out room",
"Break-out room 1": "Break-out room 1",
"Break-out room 2": "Break-out room 2",
"Break-out rooms": "Break-out rooms",
"browser_media_e2ee_unsupported": "Your web browser does not support media end-to-end encryption. Supported Browsers are Chrome, Safari, Firefox >=117",
"browser_media_e2ee_unsupported_heading": "Incompatible Browser",
"call_ended_view": {
Expand Down Expand Up @@ -57,6 +63,8 @@
"username": "Username",
"video": "Video"
},
"Create rooms": "Create rooms",
"Creating rooms": "Creating rooms",
"disconnected_banner": "Connectivity to the server has been lost.",
"full_screen_view_description": "<0>Submitting debug logs will help us track down the problem.</0>",
"full_screen_view_h1": "<0>Oops, something's gone wrong.</0>",
Expand Down Expand Up @@ -110,7 +118,9 @@
"register_auth_links": "<0>Already have an account?</0><1><0>Log in</0> Or <2>Access as a guest</2></1>",
"register_confirm_password_label": "Confirm password",
"register_heading": "Create your account",
"Remove": "Remove",
"return_home_button": "Return to home screen",
"Room name": "Room name",
"room_auth_view_eula_caption": "By clicking \"Join call now\", you agree to our <2>End User Licensing Agreement (EULA)</2>",
"room_auth_view_join_button": "Join call now",
"screenshare_button_label": "Share screen",
Expand All @@ -130,6 +140,7 @@
"show_connection_stats_label": "Show connection stats",
"speaker_device_selection_label": "Speaker"
},
"Settings": "Settings",
"star_rating_input_label_one": "{{count}} stars",
"star_rating_input_label_other": "{{count}} stars",
"start_new_call": "Start new call",
Expand Down
25 changes: 24 additions & 1 deletion src/ClientContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import { logger } from "matrix-js-sdk/src/logger";
import { useTranslation } from "react-i18next";
import { ISyncStateData, SyncState } from "matrix-js-sdk/src/sync";
import { MatrixEvent } from "matrix-js-sdk";

import { ErrorView } from "./FullScreenView";
import {
Expand All @@ -44,6 +45,11 @@
import { translatedError } from "./TranslatedError";
import { useEventTarget } from "./useEvents";
import { Config } from "./config/Config";
import {
SHARE_ROOM_KEY_EVENT_TYPE,
getRoomSharedKeyLocalStorageKey,
} from "./e2ee/sharedKeyManagement";
import { getLocalStorageItem, setLocalStorageItem } from "./useLocalStorage";

declare global {
interface Window {
Expand Down Expand Up @@ -302,6 +308,18 @@
[],
);

const onToDevice = useCallback((event: MatrixEvent) => {
if (event.getType() !== SHARE_ROOM_KEY_EVENT_TYPE) return;

for (const [roomId, key] of Object.entries(event.getContent())) {

Check warning on line 314 in src/ClientContext.tsx

View check run for this annotation

Codecov / codecov/patch

src/ClientContext.tsx#L314

Added line #L314 was not covered by tests
// XXX: We need a better way to prevent injection attacks here!
const localStorageKey = getRoomSharedKeyLocalStorageKey(roomId);

Check warning on line 316 in src/ClientContext.tsx

View check run for this annotation

Codecov / codecov/patch

src/ClientContext.tsx#L316

Added line #L316 was not covered by tests
if (getLocalStorageItem(localStorageKey)) continue;

setLocalStorageItem(localStorageKey, key);

Check warning on line 319 in src/ClientContext.tsx

View check run for this annotation

Codecov / codecov/patch

src/ClientContext.tsx#L319

Added line #L319 was not covered by tests
}
}, []);

useEffect(() => {
if (!initClientState) {
return;
Expand All @@ -315,14 +333,19 @@

if (initClientState.client) {
initClientState.client.on(ClientEvent.Sync, onSync);
initClientState.client.on(ClientEvent.ToDeviceEvent, onToDevice);

Check warning on line 336 in src/ClientContext.tsx

View check run for this annotation

Codecov / codecov/patch

src/ClientContext.tsx#L336

Added line #L336 was not covered by tests
}

return () => {
if (initClientState.client) {
initClientState.client.removeListener(ClientEvent.Sync, onSync);
initClientState.client.removeListener(

Check warning on line 342 in src/ClientContext.tsx

View check run for this annotation

Codecov / codecov/patch

src/ClientContext.tsx#L342

Added line #L342 was not covered by tests
ClientEvent.ToDeviceEvent,
onToDevice,
);
}
};
}, [initClientState, onSync]);
}, [initClientState, onSync, onToDevice]);

if (alreadyOpenedErr) {
return <ErrorView error={alreadyOpenedErr} />;
Expand Down
17 changes: 17 additions & 0 deletions src/button/Button.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,23 @@ limitations under the License.
cursor: pointer;
}

.removeButton {
width: 24px;
height: 24px;

margin: 12px;

background-color: var(--cpd-color-bg-subtle-secondary);
border-radius: 100%;
}

.buttonWithDropdown {
display: flex;
flex-direction: row;

gap: 8px;
}

@media (hover: hover) {
.toolbarButton:hover,
.toolbarButtonSecondary:hover {
Expand Down
91 changes: 88 additions & 3 deletions src/button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,14 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
import { FC, forwardRef } from "react";
import { FC, forwardRef, useCallback, useEffect, useState } from "react";
import { PressEvent } from "@react-types/shared";
import classNames from "classnames";
import { useButton } from "@react-aria/button";
import { mergeProps, useObjectRef } from "@react-aria/utils";
import { useTranslation } from "react-i18next";
import { Tooltip } from "@vector-im/compound-web";
import { ChangeEvent } from "react";
import MicOnSolidIcon from "@vector-im/compound-design-tokens/icons/mic-on-solid.svg?react";
import MicOffSolidIcon from "@vector-im/compound-design-tokens/icons/mic-off-solid.svg?react";
import VideoCallSolidIcon from "@vector-im/compound-design-tokens/icons/video-call-solid.svg?react";
Expand All @@ -30,6 +31,9 @@
import ChevronDownIcon from "@vector-im/compound-design-tokens/icons/chevron-down.svg?react";

import styles from "./Button.module.css";
import RemoveIcon from "../icons/Remove.svg?react";
import AddBreakoutRoomIcon from "../icons/AddBreakoutRoom.svg?react";
import BreakoutRoomsIcon from "../icons/BreakoutRooms.svg?react";

export type ButtonVariant =
| "default"
Expand Down Expand Up @@ -132,9 +136,48 @@
);
},
);

Button.displayName = "Button";

export const ButtonWithDropdown: FC<{
label: string;
options: { label: string; id: string }[];
onOptionSelect: (id: string) => void;
}> = ({ label, options, onOptionSelect, ...rest }) => {
const [selectedUserId, setSelectedUserId] = useState<string>(options[0].id);

Check warning on line 146 in src/button/Button.tsx

View check run for this annotation

Codecov / codecov/patch

src/button/Button.tsx#L146

Added line #L146 was not covered by tests

const onPress = useCallback(() => {
onOptionSelect(selectedUserId);

Check warning on line 149 in src/button/Button.tsx

View check run for this annotation

Codecov / codecov/patch

src/button/Button.tsx#L148-L149

Added lines #L148 - L149 were not covered by tests
}, [onOptionSelect, selectedUserId]);

const onSelectedOptionChange = useCallback(
(event: ChangeEvent<HTMLSelectElement>) => {
setSelectedUserId(event.target.options[event.target.selectedIndex].id);

Check warning on line 154 in src/button/Button.tsx

View check run for this annotation

Codecov / codecov/patch

src/button/Button.tsx#L152-L154

Added lines #L152 - L154 were not covered by tests
},
[setSelectedUserId],
);

useEffect(() => {

Check warning on line 159 in src/button/Button.tsx

View check run for this annotation

Codecov / codecov/patch

src/button/Button.tsx#L159

Added line #L159 was not covered by tests
if (!options.find((o) => o.id === selectedUserId)) {
setSelectedUserId(options[0].id);

Check warning on line 161 in src/button/Button.tsx

View check run for this annotation

Codecov / codecov/patch

src/button/Button.tsx#L161

Added line #L161 was not covered by tests
}
}, [options, selectedUserId, setSelectedUserId]);

return (

Check warning on line 165 in src/button/Button.tsx

View check run for this annotation

Codecov / codecov/patch

src/button/Button.tsx#L165

Added line #L165 was not covered by tests
<div className={styles.buttonWithDropdown}>
<Tooltip label={label}>
<Button onPress={onPress} {...rest}>
{label}
</Button>
</Tooltip>
<select onChange={onSelectedOptionChange}>
{options.map((o) => (
<option id={o.id}> {o.label}</option>

Check warning on line 174 in src/button/Button.tsx

View check run for this annotation

Codecov / codecov/patch

src/button/Button.tsx#L173-L174

Added lines #L173 - L174 were not covered by tests
))}
</select>
</div>
);
};

export const MicButton: FC<{
muted: boolean;
// TODO: add all props for <Button>
Expand Down Expand Up @@ -225,7 +268,49 @@
return (
<Tooltip label={t("common.settings")}>
<Button variant="toolbar" {...rest}>
<SettingsSolidIcon aria-label={t("common.settings")} />
<SettingsSolidIcon aria-label={t("Settings")} />
</Button>
</Tooltip>
);
};

export const AddBreakoutRoomButton: FC<{
// TODO: add all props for <Button>
[index: string]: unknown;
}> = ({ ...rest }) => {
const { t } = useTranslation();

Check warning on line 281 in src/button/Button.tsx

View check run for this annotation

Codecov / codecov/patch

src/button/Button.tsx#L281

Added line #L281 was not covered by tests

return (

Check warning on line 283 in src/button/Button.tsx

View check run for this annotation

Codecov / codecov/patch

src/button/Button.tsx#L283

Added line #L283 was not covered by tests
<Tooltip label={t("Break-out room")}>
<Button variant="toolbar" {...rest}>
<AddBreakoutRoomIcon aria-label={t("Break-out room")} />
</Button>
</Tooltip>
);
};

export const BreakoutRoomsButton: FC<{
// TODO: add all props for <Button>
[index: string]: unknown;
}> = ({ ...rest }) => {
const { t } = useTranslation();

Check warning on line 296 in src/button/Button.tsx

View check run for this annotation

Codecov / codecov/patch

src/button/Button.tsx#L296

Added line #L296 was not covered by tests

return (

Check warning on line 298 in src/button/Button.tsx

View check run for this annotation

Codecov / codecov/patch

src/button/Button.tsx#L298

Added line #L298 was not covered by tests
<Tooltip label={t("Break-out rooms")}>
<Button variant="toolbar" {...rest}>
<BreakoutRoomsIcon aria-label={t("Break-out rooms")} />
</Button>
</Tooltip>
);
};

export const RemoveButton: FC<Omit<Props, "variant">> = ({ ...rest }) => {
const { t } = useTranslation();

Check warning on line 308 in src/button/Button.tsx

View check run for this annotation

Codecov / codecov/patch

src/button/Button.tsx#L308

Added line #L308 was not covered by tests

return (

Check warning on line 310 in src/button/Button.tsx

View check run for this annotation

Codecov / codecov/patch

src/button/Button.tsx#L310

Added line #L310 was not covered by tests
<Tooltip label={t("Remove")}>
<Button className={styles.removeButton} variant="icon" {...rest}>
<RemoveIcon aria-label={t("Remove")} />
</Button>
</Tooltip>
);
Expand Down
5 changes: 4 additions & 1 deletion src/e2ee/sharedKeyManagement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,14 @@ import { useClient } from "../ClientContext";
import { UrlParams, getUrlParams, useUrlParams } from "../UrlParams";
import { widget } from "../widget";

export type ShareRoomKeyEventContent = Record<string, string>;
export const SHARE_ROOM_KEY_EVENT_TYPE = "io.element.share_room_key";

export function saveKeyForRoom(roomId: string, password: string): void {
setLocalStorageItem(getRoomSharedKeyLocalStorageKey(roomId), password);
}

const getRoomSharedKeyLocalStorageKey = (roomId: string): string =>
export const getRoomSharedKeyLocalStorageKey = (roomId: string): string =>
`room-shared-key-${roomId}`;

const useInternalRoomSharedKey = (roomId: string): string | null => {
Expand Down
3 changes: 3 additions & 0 deletions src/icons/AddBreakoutRoom.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/icons/BreakoutRooms.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/icons/Remove.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
59 changes: 59 additions & 0 deletions src/room/BreakoutRoomModal.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
Copyright 2023 Šimon Brandner <[email protected]>

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

.breakoutRooms {
max-block-size: 350px;
overflow-y: auto;

margin-bottom: 24px;
}

.breakoutRoom {
display: flex;
flex-direction: column;
width: 100%;
}

.breakoutRoomNameFieldRow {
display: flex;
flex-direction: row;
align-items: center;

margin-top: 10px;
margin-bottom: 10px;

width: 100%;
}

.breakoutRoomNameField {
margin-right: 0;
}

.breakoutRoomUser {
display: flex;
flex-direction: row;
}

.breakoutRoomUser button {
margin: 0;
margin-left: 6px;
}

.breakoutRoomsButtons {
display: flex;
flex-direction: row;
justify-content: space-between;
}
Loading