Merge branch 'livekit' into renovate/sentry-vite-plugin-0.x
This commit is contained in:
@@ -22,11 +22,11 @@
|
||||
"By clicking \"Join call now\", you agree to our <2>End User Licensing Agreement (EULA)</2>": "By clicking \"Join call now\", you agree to our <2>End User Licensing Agreement (EULA)</2>",
|
||||
"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>.": "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>.",
|
||||
"Call link copied": "Call link copied",
|
||||
"Call type menu": "Call type menu",
|
||||
"Camera": "Camera",
|
||||
"Close": "Close",
|
||||
"Confirm password": "Confirm password",
|
||||
"Connectivity to the server has been lost.": "Connectivity to the server has been lost.",
|
||||
"Continue in browser": "Continue in browser",
|
||||
"Copied!": "Copied!",
|
||||
"Copy": "Copy",
|
||||
"Copy and share this call link": "Copy and share this call link",
|
||||
@@ -66,13 +66,17 @@
|
||||
"Microphone off": "Microphone off",
|
||||
"Microphone on": "Microphone on",
|
||||
"More": "More",
|
||||
"Mute microphone": "Mute microphone",
|
||||
"Name of call": "Name of call",
|
||||
"No": "No",
|
||||
"Not encrypted": "Not encrypted",
|
||||
"Not now, return to home screen": "Not now, return to home screen",
|
||||
"Not registered yet? <2>Create an account</2>": "Not registered yet? <2>Create an account</2>",
|
||||
"Open in the app": "Open in the app",
|
||||
"Password": "Password",
|
||||
"Passwords must match": "Passwords must match",
|
||||
"Profile": "Profile",
|
||||
"Ready to join?": "Ready to join?",
|
||||
"Recaptcha dismissed": "Recaptcha dismissed",
|
||||
"Recaptcha not loaded": "Recaptcha not loaded",
|
||||
"Reconnect": "Reconnect",
|
||||
@@ -82,6 +86,7 @@
|
||||
"Retry sending logs": "Retry sending logs",
|
||||
"Return to home screen": "Return to home screen",
|
||||
"Select an option": "Select an option",
|
||||
"Select app": "Select app",
|
||||
"Send debug logs": "Send debug logs",
|
||||
"Sending debug logs…": "Sending debug logs…",
|
||||
"Sending…": "Sending…",
|
||||
@@ -96,6 +101,9 @@
|
||||
"Sign out": "Sign out",
|
||||
"Speaker": "Speaker",
|
||||
"Spotlight": "Spotlight",
|
||||
"Start new call": "Start new call",
|
||||
"Start video": "Start video",
|
||||
"Stop video": "Stop video",
|
||||
"Submit": "Submit",
|
||||
"Submit feedback": "Submit feedback",
|
||||
"Submitting…": "Submitting…",
|
||||
@@ -104,20 +112,14 @@
|
||||
"Thanks!": "Thanks!",
|
||||
"This call already exists, would you like to join?": "This call already exists, would you like to join?",
|
||||
"This site is protected by ReCAPTCHA and the Google <2>Privacy Policy</2> and <6>Terms of Service</6> apply.<9></9>By clicking \"Register\", you agree to our <12>End User Licensing Agreement (EULA)</12>": "This site is protected by ReCAPTCHA and the Google <2>Privacy Policy</2> and <6>Terms of Service</6> apply.<9></9>By clicking \"Register\", you agree to our <12>End User Licensing Agreement (EULA)</12>",
|
||||
"Unmute microphone": "Unmute microphone",
|
||||
"User menu": "User menu",
|
||||
"Username": "Username",
|
||||
"Version: {{version}}": "Version: {{version}}",
|
||||
"Video": "Video",
|
||||
"Video call": "Video call",
|
||||
"Video call name": "Video call name",
|
||||
"Video off": "Video off",
|
||||
"Video on": "Video on",
|
||||
"Waiting for other participants…": "Waiting for other participants…",
|
||||
"Walkie-talkie call": "Walkie-talkie call",
|
||||
"Walkie-talkie call name": "Walkie-talkie call name",
|
||||
"Yes, join call": "Yes, join call",
|
||||
"You": "You",
|
||||
"You were disconnected from the call": "You were disconnected from the call",
|
||||
"Your feedback": "Your feedback",
|
||||
"Your recent calls": "Your recent calls"
|
||||
"Your feedback": "Your feedback"
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ import { ReactComponent as CloseIcon } from "@vector-im/compound-design-tokens/i
|
||||
import classNames from "classnames";
|
||||
import { Heading } from "@vector-im/compound-web";
|
||||
|
||||
import styles from "./NewModal.module.css";
|
||||
import styles from "./Modal.module.css";
|
||||
import { useMediaQuery } from "./useMediaQuery";
|
||||
import { Glass } from "./Glass";
|
||||
|
||||
|
||||
@@ -43,10 +43,6 @@ interface UrlParams {
|
||||
* Whether to hide the screen-sharing button.
|
||||
*/
|
||||
hideScreensharing: boolean;
|
||||
/**
|
||||
* Whether to start a walkie-talkie call instead of a video call.
|
||||
*/
|
||||
isPtt: boolean;
|
||||
/**
|
||||
* Whether to use end-to-end encryption.
|
||||
*/
|
||||
@@ -94,6 +90,22 @@ interface UrlParams {
|
||||
password: string | null;
|
||||
}
|
||||
|
||||
export function editFragmentQuery(
|
||||
hash: string,
|
||||
edit: (params: URLSearchParams) => URLSearchParams
|
||||
): string {
|
||||
const fragmentQueryStart = hash.indexOf("?");
|
||||
const fragmentParams = edit(
|
||||
new URLSearchParams(
|
||||
fragmentQueryStart === -1 ? "" : hash.substring(fragmentQueryStart)
|
||||
)
|
||||
);
|
||||
return `${hash.substring(
|
||||
0,
|
||||
fragmentQueryStart
|
||||
)}?${fragmentParams.toString()}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the app parameters for the current URL.
|
||||
* @param ignoreRoomAlias If true, does not try to parse a room alias from the URL
|
||||
@@ -179,7 +191,6 @@ export const getUrlParams = (
|
||||
preload: hasParam("preload"),
|
||||
hideHeader: hasParam("hideHeader"),
|
||||
hideScreensharing: hasParam("hideScreensharing"),
|
||||
isPtt: hasParam("ptt"),
|
||||
e2eEnabled: getParam("enableE2e") !== "false", // Defaults to true
|
||||
userId: getParam("userId"),
|
||||
displayName: getParam("displayName"),
|
||||
|
||||
@@ -145,11 +145,11 @@ export function MicButton({
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const Icon = muted ? MicOffSolidIcon : MicOnSolidIcon;
|
||||
const label = muted ? t("Microphone off") : t("Microphone on");
|
||||
const label = muted ? t("Unmute microphone") : t("Mute microphone");
|
||||
|
||||
return (
|
||||
<Tooltip label={label}>
|
||||
<Button variant="toolbar" {...rest} on={!muted}>
|
||||
<Button variant="toolbar" {...rest} on={muted}>
|
||||
<Icon aria-label={label} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
@@ -166,11 +166,11 @@ export function VideoButton({
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const Icon = muted ? VideoCallOffIcon : VideoCallIcon;
|
||||
const label = muted ? t("Video off") : t("Video on");
|
||||
const label = muted ? t("Start video") : t("Stop video");
|
||||
|
||||
return (
|
||||
<Tooltip label={label}>
|
||||
<Button variant="toolbar" {...rest} on={!muted}>
|
||||
<Button variant="toolbar" {...rest} on={muted}>
|
||||
<Icon aria-label={label} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
/*
|
||||
Copyright 2022 New Vector Ltd
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
import { FC } from "react";
|
||||
import { Item } from "@react-stately/collections";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { Headline } from "../typography/Typography";
|
||||
import { Button } from "../button";
|
||||
import { PopoverMenuTrigger } from "../popover/PopoverMenu";
|
||||
import { ReactComponent as VideoIcon } from "../icons/Video.svg";
|
||||
import { ReactComponent as MicIcon } from "../icons/Mic.svg";
|
||||
import { ReactComponent as CheckIcon } from "../icons/Check.svg";
|
||||
import styles from "./CallTypeDropdown.module.css";
|
||||
import commonStyles from "./common.module.css";
|
||||
import menuStyles from "../Menu.module.css";
|
||||
import { Menu } from "../Menu";
|
||||
|
||||
export enum CallType {
|
||||
Video = "video",
|
||||
Radio = "radio",
|
||||
}
|
||||
|
||||
interface Props {
|
||||
callType: CallType;
|
||||
setCallType: (value: CallType) => void;
|
||||
}
|
||||
|
||||
export const CallTypeDropdown: FC<Props> = ({ callType, setCallType }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const onAction = (key: React.Key) => {
|
||||
setCallType(key.toString() as CallType);
|
||||
};
|
||||
|
||||
const onClose = () => {};
|
||||
|
||||
return (
|
||||
<PopoverMenuTrigger placement="bottom">
|
||||
<Button variant="dropdown" className={commonStyles.headline}>
|
||||
<Headline className={styles.label}>
|
||||
{callType === CallType.Video
|
||||
? t("Video call")
|
||||
: t("Walkie-talkie call")}
|
||||
</Headline>
|
||||
</Button>
|
||||
{(props: JSX.IntrinsicAttributes) => (
|
||||
<Menu
|
||||
{...props}
|
||||
label={t("Call type menu")}
|
||||
onAction={onAction}
|
||||
onClose={onClose}
|
||||
>
|
||||
<Item key={CallType.Video} textValue={t("Video call")}>
|
||||
<VideoIcon />
|
||||
<span>{t("Video call")}</span>
|
||||
{callType === CallType.Video && (
|
||||
<CheckIcon className={menuStyles.checkIcon} />
|
||||
)}
|
||||
</Item>
|
||||
<Item key={CallType.Radio} textValue={t("Walkie-talkie call")}>
|
||||
<MicIcon />
|
||||
<span>{t("Walkie-talkie call")}</span>
|
||||
{callType === CallType.Radio && (
|
||||
<CheckIcon className={menuStyles.checkIcon} />
|
||||
)}
|
||||
</Item>
|
||||
</Menu>
|
||||
)}
|
||||
</PopoverMenuTrigger>
|
||||
);
|
||||
};
|
||||
@@ -34,10 +34,7 @@ export function HomePage() {
|
||||
return <ErrorView error={clientState.error} />;
|
||||
} else {
|
||||
return clientState.authenticated ? (
|
||||
<RegisteredView
|
||||
isPasswordlessUser={clientState.authenticated.isPasswordlessUser}
|
||||
client={clientState.authenticated.client}
|
||||
/>
|
||||
<RegisteredView client={clientState.authenticated.client} />
|
||||
) : (
|
||||
<UnauthenticatedView />
|
||||
);
|
||||
|
||||
@@ -19,6 +19,7 @@ import { useHistory } from "react-router-dom";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
import { randomString } from "matrix-js-sdk/src/randomstring";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Heading } from "@vector-im/compound-web";
|
||||
|
||||
import {
|
||||
createRoom,
|
||||
@@ -34,9 +35,8 @@ import { Button } from "../button";
|
||||
import { CallList } from "./CallList";
|
||||
import { UserMenuContainer } from "../UserMenuContainer";
|
||||
import { JoinExistingCallModal } from "./JoinExistingCallModal";
|
||||
import { Caption, Title } from "../typography/Typography";
|
||||
import { Caption } from "../typography/Typography";
|
||||
import { Form } from "../form/Form";
|
||||
import { CallType, CallTypeDropdown } from "./CallTypeDropdown";
|
||||
import { useEnableE2EE, useOptInAnalytics } from "../settings/useSetting";
|
||||
import { AnalyticsNotice } from "../analytics/AnalyticsNotice";
|
||||
import { E2EEBanner } from "../E2EEBanner";
|
||||
@@ -45,11 +45,9 @@ import { getRoomSharedKeyLocalStorageKey } from "../e2ee/sharedKeyManagement";
|
||||
|
||||
interface Props {
|
||||
client: MatrixClient;
|
||||
isPasswordlessUser: boolean;
|
||||
}
|
||||
|
||||
export function RegisteredView({ client, isPasswordlessUser }: Props) {
|
||||
const [callType, setCallType] = useState(CallType.Video);
|
||||
export function RegisteredView({ client }: Props) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<Error>();
|
||||
const [optInAnalytics] = useOptInAnalytics();
|
||||
@@ -72,14 +70,13 @@ export function RegisteredView({ client, isPasswordlessUser }: Props) {
|
||||
typeof roomNameData === "string"
|
||||
? sanitiseRoomNameInput(roomNameData)
|
||||
: "";
|
||||
const ptt = callType === CallType.Radio;
|
||||
|
||||
async function submit() {
|
||||
setError(undefined);
|
||||
setLoading(true);
|
||||
|
||||
const roomId = (
|
||||
await createRoom(client, roomName, ptt, e2eeEnabled ?? false)
|
||||
await createRoom(client, roomName, e2eeEnabled ?? false)
|
||||
)[1];
|
||||
|
||||
if (e2eeEnabled) {
|
||||
@@ -105,7 +102,7 @@ export function RegisteredView({ client, isPasswordlessUser }: Props) {
|
||||
}
|
||||
});
|
||||
},
|
||||
[client, history, setJoinExistingCallModalOpen, callType, e2eeEnabled]
|
||||
[client, history, setJoinExistingCallModalOpen, e2eeEnabled]
|
||||
);
|
||||
|
||||
const recentRooms = useGroupCallRooms(client);
|
||||
@@ -115,11 +112,6 @@ export function RegisteredView({ client, isPasswordlessUser }: Props) {
|
||||
history.push(`/${existingAlias}`);
|
||||
}, [history, existingAlias]);
|
||||
|
||||
const callNameLabel =
|
||||
callType === CallType.Video
|
||||
? t("Video call name")
|
||||
: t("Walkie-talkie call name");
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header>
|
||||
@@ -133,14 +125,16 @@ export function RegisteredView({ client, isPasswordlessUser }: Props) {
|
||||
<div className={commonStyles.container}>
|
||||
<main className={commonStyles.main}>
|
||||
<HeaderLogo className={commonStyles.logo} />
|
||||
<CallTypeDropdown callType={callType} setCallType={setCallType} />
|
||||
<Heading size="lg" weight="semibold">
|
||||
{t("Start new call")}
|
||||
</Heading>
|
||||
<Form className={styles.form} onSubmit={onSubmit}>
|
||||
<FieldRow className={styles.fieldRow}>
|
||||
<InputField
|
||||
id="callName"
|
||||
name="callName"
|
||||
label={callNameLabel}
|
||||
placeholder={callNameLabel}
|
||||
label={t("Name of call")}
|
||||
placeholder={t("Name of call")}
|
||||
type="text"
|
||||
required
|
||||
autoComplete="off"
|
||||
@@ -170,12 +164,7 @@ export function RegisteredView({ client, isPasswordlessUser }: Props) {
|
||||
)}
|
||||
</Form>
|
||||
{recentRooms.length > 0 && (
|
||||
<>
|
||||
<Title className={styles.recentCallsTitle}>
|
||||
{t("Your recent calls")}
|
||||
</Title>
|
||||
<CallList rooms={recentRooms} client={client} />
|
||||
</>
|
||||
<CallList rooms={recentRooms} client={client} />
|
||||
)}
|
||||
</main>
|
||||
</div>
|
||||
|
||||
@@ -18,6 +18,7 @@ import { FC, useCallback, useState, FormEventHandler } from "react";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { randomString } from "matrix-js-sdk/src/randomstring";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { Heading } from "@vector-im/compound-web";
|
||||
|
||||
import { useClient } from "../ClientContext";
|
||||
import { Header, HeaderLogo, LeftNav, RightNav } from "../Header";
|
||||
@@ -34,7 +35,6 @@ import { JoinExistingCallModal } from "./JoinExistingCallModal";
|
||||
import { useRecaptcha } from "../auth/useRecaptcha";
|
||||
import { Body, Caption, Link } from "../typography/Typography";
|
||||
import { Form } from "../form/Form";
|
||||
import { CallType, CallTypeDropdown } from "./CallTypeDropdown";
|
||||
import styles from "./UnauthenticatedView.module.css";
|
||||
import commonStyles from "./common.module.css";
|
||||
import { generateRandomName } from "../auth/generateRandomName";
|
||||
@@ -47,7 +47,6 @@ import { setLocalStorageItem } from "../useLocalStorage";
|
||||
|
||||
export const UnauthenticatedView: FC = () => {
|
||||
const { setClient } = useClient();
|
||||
const [callType, setCallType] = useState(CallType.Video);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<Error>();
|
||||
const [optInAnalytics] = useOptInAnalytics();
|
||||
@@ -72,7 +71,6 @@ export const UnauthenticatedView: FC = () => {
|
||||
const data = new FormData(e.target as HTMLFormElement);
|
||||
const roomName = sanitiseRoomNameInput(data.get("callName") as string);
|
||||
const displayName = data.get("displayName") as string;
|
||||
const ptt = callType === CallType.Radio;
|
||||
|
||||
async function submit() {
|
||||
setError(undefined);
|
||||
@@ -90,7 +88,7 @@ export const UnauthenticatedView: FC = () => {
|
||||
let roomId: string;
|
||||
try {
|
||||
roomId = (
|
||||
await createRoom(client, roomName, ptt, e2eeEnabled ?? false)
|
||||
await createRoom(client, roomName, e2eeEnabled ?? false)
|
||||
)[1];
|
||||
|
||||
if (e2eeEnabled) {
|
||||
@@ -142,18 +140,12 @@ export const UnauthenticatedView: FC = () => {
|
||||
reset,
|
||||
execute,
|
||||
history,
|
||||
callType,
|
||||
setJoinExistingCallModalOpen,
|
||||
setClient,
|
||||
e2eeEnabled,
|
||||
]
|
||||
);
|
||||
|
||||
const callNameLabel =
|
||||
callType === CallType.Video
|
||||
? t("Video call name")
|
||||
: t("Walkie-talkie call name");
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header>
|
||||
@@ -167,14 +159,16 @@ export const UnauthenticatedView: FC = () => {
|
||||
<div className={commonStyles.container}>
|
||||
<main className={commonStyles.main}>
|
||||
<HeaderLogo className={commonStyles.logo} />
|
||||
<CallTypeDropdown callType={callType} setCallType={setCallType} />
|
||||
<Heading size="lg" weight="semibold">
|
||||
{t("Start new call")}
|
||||
</Heading>
|
||||
<Form className={styles.form} onSubmit={onSubmit}>
|
||||
<FieldRow>
|
||||
<InputField
|
||||
id="callName"
|
||||
name="callName"
|
||||
label={callNameLabel}
|
||||
placeholder={callNameLabel}
|
||||
label={t("Name of call")}
|
||||
placeholder={t("Name of call")}
|
||||
type="text"
|
||||
required
|
||||
autoComplete="off"
|
||||
|
||||
@@ -272,7 +272,6 @@ export function isLocalRoomId(roomId: string, client: MatrixClient): boolean {
|
||||
export async function createRoom(
|
||||
client: MatrixClient,
|
||||
name: string,
|
||||
ptt: boolean,
|
||||
e2ee: boolean
|
||||
): Promise<[string, string]> {
|
||||
logger.log(`Creating room for group call`);
|
||||
@@ -327,14 +326,12 @@ export async function createRoom(
|
||||
|
||||
const result = await createPromise;
|
||||
|
||||
logger.log(
|
||||
`Creating ${ptt ? "PTT" : "video"} group call in ${result.room_id}`
|
||||
);
|
||||
logger.log(`Creating group call in ${result.room_id}`);
|
||||
|
||||
await client.createGroupCall(
|
||||
result.room_id,
|
||||
ptt ? GroupCallType.Voice : GroupCallType.Video,
|
||||
ptt,
|
||||
GroupCallType.Video,
|
||||
false,
|
||||
GroupCallIntent.Room,
|
||||
true
|
||||
);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
Copyright 2022 New Vector Ltd
|
||||
Copyright 2023 New Vector Ltd
|
||||
|
||||
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
|
||||
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,
|
||||
@@ -14,6 +14,20 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
.label {
|
||||
margin-bottom: 0;
|
||||
.modal p {
|
||||
text-align: center;
|
||||
margin-block-end: var(--cpd-space-8x);
|
||||
}
|
||||
|
||||
.modal button,
|
||||
.modal a {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.modal button {
|
||||
margin-block-end: var(--cpd-space-6x);
|
||||
}
|
||||
|
||||
.modal a {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
80
src/room/AppSelectionModal.tsx
Normal file
80
src/room/AppSelectionModal.tsx
Normal file
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
Copyright 2023 New Vector Ltd
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
import { FC, MouseEvent, useCallback, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Button, Text } from "@vector-im/compound-web";
|
||||
import { ReactComponent as PopOutIcon } from "@vector-im/compound-design-tokens/icons/pop-out.svg";
|
||||
|
||||
import { Modal } from "../Modal";
|
||||
import { useRoomSharedKey } from "../e2ee/sharedKeyManagement";
|
||||
import { getRoomUrl } from "../matrix-utils";
|
||||
import styles from "./AppSelectionModal.module.css";
|
||||
import { editFragmentQuery } from "../UrlParams";
|
||||
|
||||
interface Props {
|
||||
roomId: string | null;
|
||||
}
|
||||
|
||||
export const AppSelectionModal: FC<Props> = ({ roomId }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [open, setOpen] = useState(true);
|
||||
const onBrowserClick = useCallback(
|
||||
(e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setOpen(false);
|
||||
},
|
||||
[setOpen]
|
||||
);
|
||||
|
||||
const roomSharedKey = useRoomSharedKey(roomId ?? "");
|
||||
const appUrl = useMemo(() => {
|
||||
// If the room ID is not known, fall back to the URL of the current page
|
||||
const url = new URL(
|
||||
roomId === null
|
||||
? window.location.href
|
||||
: getRoomUrl(roomId, roomSharedKey ?? undefined)
|
||||
);
|
||||
// Edit the URL so that it opens in embedded mode. We do this for two
|
||||
// reasons: It causes the mobile app to limit the user to only visiting the
|
||||
// room in question, and it prevents this app selection prompt from being
|
||||
// shown a second time.
|
||||
url.hash = editFragmentQuery(url.hash, (params) => {
|
||||
params.set("isEmbedded", "");
|
||||
return params;
|
||||
});
|
||||
|
||||
const result = new URL("element://call");
|
||||
result.searchParams.set("url", url.toString());
|
||||
return result.toString();
|
||||
}, [roomId, roomSharedKey]);
|
||||
|
||||
return (
|
||||
<Modal className={styles.modal} title={t("Select app")} open={open}>
|
||||
<Text size="md" weight="semibold">
|
||||
{t("Ready to join?")}
|
||||
</Text>
|
||||
<Button kind="secondary" onClick={onBrowserClick}>
|
||||
{t("Continue in browser")}
|
||||
</Button>
|
||||
<Button as="a" href={appUrl} Icon={PopOutIcon}>
|
||||
{t("Open in the app")}
|
||||
</Button>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
@@ -17,7 +17,8 @@ limitations under the License.
|
||||
.headline {
|
||||
text-align: center;
|
||||
margin-bottom: 60px;
|
||||
white-space: pre;
|
||||
white-space: pre-wrap;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.callEndedContent {
|
||||
@@ -66,6 +67,7 @@ limitations under the License.
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding-inline: var(--inline-content-inset);
|
||||
}
|
||||
|
||||
.logo {
|
||||
|
||||
@@ -27,7 +27,6 @@ interface Props {
|
||||
roomIdOrAlias: string;
|
||||
viaServers: string[];
|
||||
children: (rtcSession: MatrixRTCSession) => ReactNode;
|
||||
createPtt: boolean;
|
||||
}
|
||||
|
||||
export function GroupCallLoader({
|
||||
@@ -35,15 +34,9 @@ export function GroupCallLoader({
|
||||
roomIdOrAlias,
|
||||
viaServers,
|
||||
children,
|
||||
createPtt,
|
||||
}: Props): JSX.Element {
|
||||
const { t } = useTranslation();
|
||||
const groupCallState = useLoadGroupCall(
|
||||
client,
|
||||
roomIdOrAlias,
|
||||
viaServers,
|
||||
createPtt
|
||||
);
|
||||
const groupCallState = useLoadGroupCall(client, roomIdOrAlias, viaServers);
|
||||
|
||||
switch (groupCallState.kind) {
|
||||
case "loading":
|
||||
|
||||
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { FC, useEffect, useState, useCallback } from "react";
|
||||
import { FC, useEffect, useState, useCallback, ReactNode } from "react";
|
||||
import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
|
||||
|
||||
import { useClientLegacy } from "../ClientContext";
|
||||
@@ -26,6 +26,8 @@ import { useUrlParams } from "../UrlParams";
|
||||
import { useRegisterPasswordlessUser } from "../auth/useRegisterPasswordlessUser";
|
||||
import { useOptInAnalytics } from "../settings/useSetting";
|
||||
import { HomePage } from "../home/HomePage";
|
||||
import { platform } from "../Platform";
|
||||
import { AppSelectionModal } from "./AppSelectionModal";
|
||||
|
||||
export const RoomPage: FC = () => {
|
||||
const {
|
||||
@@ -35,7 +37,6 @@ export const RoomPage: FC = () => {
|
||||
isEmbedded,
|
||||
preload,
|
||||
hideHeader,
|
||||
isPtt,
|
||||
displayName,
|
||||
} = useUrlParams();
|
||||
const roomIdOrAlias = roomId ?? roomAlias;
|
||||
@@ -86,30 +87,36 @@ export const RoomPage: FC = () => {
|
||||
[client, passwordlessUser, isEmbedded, preload, hideHeader]
|
||||
);
|
||||
|
||||
let content: ReactNode;
|
||||
if (loading || isRegistering) {
|
||||
return <LoadingView />;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <ErrorView error={error} />;
|
||||
}
|
||||
|
||||
if (!client) {
|
||||
return <RoomAuthView />;
|
||||
}
|
||||
|
||||
if (!roomIdOrAlias) {
|
||||
return <HomePage />;
|
||||
content = <LoadingView />;
|
||||
} else if (error) {
|
||||
content = <ErrorView error={error} />;
|
||||
} else if (!client) {
|
||||
content = <RoomAuthView />;
|
||||
} else if (!roomIdOrAlias) {
|
||||
// TODO: This doesn't belong here, the app routes need to be reworked
|
||||
content = <HomePage />;
|
||||
} else {
|
||||
content = (
|
||||
<GroupCallLoader
|
||||
client={client}
|
||||
roomIdOrAlias={roomIdOrAlias}
|
||||
viaServers={viaServers}
|
||||
>
|
||||
{groupCallView}
|
||||
</GroupCallLoader>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<GroupCallLoader
|
||||
client={client}
|
||||
roomIdOrAlias={roomIdOrAlias}
|
||||
viaServers={viaServers}
|
||||
createPtt={isPtt}
|
||||
>
|
||||
{groupCallView}
|
||||
</GroupCallLoader>
|
||||
<>
|
||||
{content}
|
||||
{/* On mobile, show a prompt to launch the mobile app. If in embedded mode,
|
||||
that means we *are* in the mobile app and should show no further prompt. */}
|
||||
{(platform === "android" || platform === "ios") && !isEmbedded && (
|
||||
<AppSelectionModal roomId={roomId} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -56,8 +56,7 @@ export interface GroupCallLoadState {
|
||||
export const useLoadGroupCall = (
|
||||
client: MatrixClient,
|
||||
roomIdOrAlias: string,
|
||||
viaServers: string[],
|
||||
createPtt: boolean
|
||||
viaServers: string[]
|
||||
): GroupCallStatus => {
|
||||
const { t } = useTranslation();
|
||||
const [state, setState] = useState<GroupCallStatus>({ kind: "loading" });
|
||||
@@ -101,7 +100,6 @@ export const useLoadGroupCall = (
|
||||
const [, roomId] = await createRoom(
|
||||
client,
|
||||
roomNameFromRoomId(roomIdOrAlias),
|
||||
createPtt,
|
||||
e2eeEnabled ?? false
|
||||
);
|
||||
|
||||
@@ -151,7 +149,7 @@ export const useLoadGroupCall = (
|
||||
.then(fetchOrCreateGroupCall)
|
||||
.then((rtcSession) => setState({ kind: "loaded", rtcSession }))
|
||||
.catch((error) => setState({ kind: "failed", error }));
|
||||
}, [client, roomIdOrAlias, viaServers, createPtt, t, e2eeEnabled]);
|
||||
}, [client, roomIdOrAlias, viaServers, t, e2eeEnabled]);
|
||||
|
||||
return state;
|
||||
};
|
||||
|
||||
53
yarn.lock
53
yarn.lock
@@ -4567,11 +4567,16 @@
|
||||
"@types/tough-cookie" "*"
|
||||
parse5 "^7.0.0"
|
||||
|
||||
"@types/json-schema@*", "@types/json-schema@^7.0.12", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8":
|
||||
"@types/json-schema@*", "@types/json-schema@^7.0.12", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.8":
|
||||
version "7.0.12"
|
||||
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb"
|
||||
integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==
|
||||
|
||||
"@types/json-schema@^7.0.5":
|
||||
version "7.0.13"
|
||||
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.13.tgz#02c24f4363176d2d18fc8b70b9f3c54aba178a85"
|
||||
integrity sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ==
|
||||
|
||||
"@types/json5@^0.0.29":
|
||||
version "0.0.29"
|
||||
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
|
||||
@@ -4618,9 +4623,9 @@
|
||||
integrity sha512-Mnq3O9Xz52exs3mlxMcQuA7/9VFe/dXcrgAyfjLkABIqxXKOgBRjyazTxUbjsxDa4BP7hhPliyjVTP9RDP14xg==
|
||||
|
||||
"@types/node@^18.13.0":
|
||||
version "18.13.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.13.0.tgz#0400d1e6ce87e9d3032c19eb6c58205b0d3f7850"
|
||||
integrity sha512-gC3TazRzGoOnoKAhUx+Q0t8S9Tzs74z7m0ipwGpSqQrleP14hKxP4/JUeEQcD3W1/aIpnWl8pHowI7WokuZpXg==
|
||||
version "18.17.17"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.17.17.tgz#53cc07ce582c9d7c5850702a3c2cb0af0d7b0ca1"
|
||||
integrity sha512-cOxcXsQ2sxiwkykdJqvyFS+MLQPLvIdwh5l6gNg8qF6s+C7XSkEWOZjK+XhUZd+mYvHV/180g2cnCcIl4l06Pw==
|
||||
|
||||
"@types/normalize-package-data@^2.4.0":
|
||||
version "2.4.1"
|
||||
@@ -4653,9 +4658,9 @@
|
||||
integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==
|
||||
|
||||
"@types/q@^1.5.1":
|
||||
version "1.5.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.5.tgz#75a2a8e7d8ab4b230414505d92335d1dcb53a6df"
|
||||
integrity sha512-L28j2FcJfSZOnL1WBjDYp2vUHCeIFlyYI/53EwD/rKUBQ7MtUUfbQWiyKJGpcnv4/WgrhWsFKrcPstcAt/J0tQ==
|
||||
version "1.5.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.6.tgz#a6edffe8283910e46dc7a573621f928e6b47fa56"
|
||||
integrity sha512-IKjZ8RjTSwD4/YG+2gtj7BPFRB/lNbWKTiSj3M7U/TD2B7HfYCxvp2Zz6xA2WIY7pAuL1QOUPw8gQRbUrrq4fQ==
|
||||
|
||||
"@types/qs@^6.9.5":
|
||||
version "6.9.8"
|
||||
@@ -4663,9 +4668,9 @@
|
||||
integrity sha512-u95svzDlTysU5xecFNTgfFG5RUWu1A9P0VzgpcIiGZA9iraHOdSzcxMxQ55DyeRaGCSxQi7LxXDI4rzq/MYfdg==
|
||||
|
||||
"@types/react-dom@^18.0.0":
|
||||
version "18.0.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.7.tgz#ee7cf8ec4e6977e3f0a7b1d38bd89c75aa2aec28"
|
||||
integrity sha512-HaXc+BbqAZE1RdsK3tC8SbkFy6UL2xF76lT9rQs5JkPrJg3rWA3Ou/Lhw3YJQzEDkBpmJ79nBsfnd05WrBd2QQ==
|
||||
version "18.2.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.7.tgz#67222a08c0a6ae0a0da33c3532348277c70abb63"
|
||||
integrity sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
@@ -4908,17 +4913,17 @@
|
||||
"@typescript-eslint/types" "6.1.0"
|
||||
eslint-visitor-keys "^3.4.1"
|
||||
|
||||
"@use-gesture/core@10.2.27":
|
||||
version "10.2.27"
|
||||
resolved "https://registry.yarnpkg.com/@use-gesture/core/-/core-10.2.27.tgz#0f24b17c036cd828ba07e3451ff45e2df959c6f5"
|
||||
integrity sha512-V4XV7hn9GAD2MYu8yBBVi5iuWBsAMfjPRMsEVzoTNGYH72tf0kFP+OKqGKc8YJFQIJx6yj+AOqxmEHOmx2/MEA==
|
||||
"@use-gesture/core@10.3.0":
|
||||
version "10.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@use-gesture/core/-/core-10.3.0.tgz#9afd3777a45b2a08990a5dcfcf8d9ddd55b00db9"
|
||||
integrity sha512-rh+6MND31zfHcy9VU3dOZCqGY511lvGcfyJenN4cWZe0u1BH6brBpBddLVXhF2r4BMqWbvxfsbL7D287thJU2A==
|
||||
|
||||
"@use-gesture/react@^10.2.11":
|
||||
version "10.2.27"
|
||||
resolved "https://registry.yarnpkg.com/@use-gesture/react/-/react-10.2.27.tgz#7fbd50d14449ec5bc49c9b6cfef8a2845f5e0608"
|
||||
integrity sha512-7E5vnWCxeslWlxwZ8uKIcnUZVMTRMZ8cvSnLLKF1NkyNb3PnNiAzoXM4G1vTKJKRhgOTeI6wK1YsEpwo9ABV5w==
|
||||
version "10.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@use-gesture/react/-/react-10.3.0.tgz#180534c821fd635c2853cbcfa813f92c94f27e3f"
|
||||
integrity sha512-3zc+Ve99z4usVP6l9knYVbVnZgfqhKah7sIG+PS2w+vpig2v2OLct05vs+ZXMzwxdNCMka8B+8WlOo0z6Pn6DA==
|
||||
dependencies:
|
||||
"@use-gesture/core" "10.2.27"
|
||||
"@use-gesture/core" "10.3.0"
|
||||
|
||||
"@vector-im/compound-design-tokens@^0.0.5":
|
||||
version "0.0.5"
|
||||
@@ -5792,7 +5797,7 @@ babel-jest@^29.7.0:
|
||||
graceful-fs "^4.2.9"
|
||||
slash "^3.0.0"
|
||||
|
||||
babel-loader@^8.0.0:
|
||||
babel-loader@^8.0.0, babel-loader@^8.2.3:
|
||||
version "8.3.0"
|
||||
resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.3.0.tgz#124936e841ba4fe8176786d6ff28add1f134d6a8"
|
||||
integrity sha512-H8SvsMF+m9t15HNLMipppzkC+Y2Yq+v3SonZyU70RBL/h1gxPkH08Ot8pEE9Z4Kd+czyWJClmFS8qzIP9OZ04Q==
|
||||
@@ -5802,16 +5807,6 @@ babel-loader@^8.0.0:
|
||||
make-dir "^3.1.0"
|
||||
schema-utils "^2.6.5"
|
||||
|
||||
babel-loader@^8.2.3:
|
||||
version "8.2.5"
|
||||
resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.2.5.tgz#d45f585e654d5a5d90f5350a779d7647c5ed512e"
|
||||
integrity sha512-OSiFfH89LrEMiWd4pLNqGz4CwJDtbs2ZVc+iGu2HrkRfPxId9F2anQj38IxWpmRfsUY0aBZYi1EFcd3mhtRMLQ==
|
||||
dependencies:
|
||||
find-cache-dir "^3.3.1"
|
||||
loader-utils "^2.0.0"
|
||||
make-dir "^3.1.0"
|
||||
schema-utils "^2.6.5"
|
||||
|
||||
babel-plugin-add-react-displayname@^0.0.5:
|
||||
version "0.0.5"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-add-react-displayname/-/babel-plugin-add-react-displayname-0.0.5.tgz#339d4cddb7b65fd62d1df9db9fe04de134122bd5"
|
||||
|
||||
Reference in New Issue
Block a user