Merge branch 'livekit' into johannes/remove-call

This commit is contained in:
Johannes Marbach 2024-09-13 12:47:18 +02:00
commit d2fb1e566f
26 changed files with 191 additions and 467 deletions

View File

@ -1,17 +1,8 @@
const COPYRIGHT_HEADER = `/*
Copyright %%CURRENT_YEAR%% New Vector Ltd
Copyright %%CURRENT_YEAR%% 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.
SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/
`;

View File

@ -39,7 +39,7 @@ jobs:
VITE_APP_VERSION: ${{ inputs.vite_app_version }}
NODE_OPTIONS: "--max-old-space-size=4096"
- name: Upload Artifact
uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4
with:
name: build-output
path: dist

View File

@ -39,7 +39,7 @@ jobs:
- name: Create Pull Request
id: cpr
uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c # v6.1.0
uses: peter-evans/create-pull-request@d121e62763d8cc35b5fb1710e887d6e69a52d3a4 # v7.0.2
with:
token: ${{ secrets.ELEMENT_BOT_TOKEN }}
branch: actions/localazy-download

View File

@ -8,14 +8,20 @@ Please see LICENSE in the repository root for full details.
import { FC } from "react";
import { Trans } from "react-i18next";
import { Link } from "../typography/Typography";
import { ExternalLink } from "../button/Link";
export const AnalyticsNotice: FC = () => (
<Trans i18nKey="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{" "}
<Link href="https://element.io/privacy">Privacy Policy</Link> and our{" "}
<Link href="https://element.io/cookie-policy">Cookie Policy</Link>.
<ExternalLink href="https://element.io/privacy">
Privacy Policy
</ExternalLink>{" "}
and our{" "}
<ExternalLink href="https://element.io/cookie-policy">
Cookie Policy
</ExternalLink>
.
</Trans>
);

View File

@ -19,7 +19,7 @@ import { captureException } from "@sentry/react";
import { sleep } from "matrix-js-sdk/src/utils";
import { Trans, useTranslation } from "react-i18next";
import { logger } from "matrix-js-sdk/src/logger";
import { Button } from "@vector-im/compound-web";
import { Button, Text } from "@vector-im/compound-web";
import { FieldRow, InputField, ErrorMessage } from "../input/Input";
import { useClientLegacy } from "../ClientContext";
@ -28,10 +28,10 @@ import styles from "./LoginPage.module.css";
import Logo from "../icons/LogoLarge.svg?react";
import { LoadingView } from "../FullScreenView";
import { useRecaptcha } from "./useRecaptcha";
import { Caption, Link } from "../typography/Typography";
import { usePageTitle } from "../usePageTitle";
import { PosthogAnalytics } from "../analytics/PosthogAnalytics";
import { Config } from "../config/Config";
import { ExternalLink, Link } from "../button/Link";
export const RegisterPage: FC = () => {
const { t } = useTranslation();
@ -201,24 +201,24 @@ export const RegisterPage: FC = () => {
data-testid="register_confirm_password"
/>
</FieldRow>
<Caption>
<Text size="sm">
<Trans i18nKey="recaptcha_caption">
This site is protected by ReCAPTCHA and the Google{" "}
<Link href="https://www.google.com/policies/privacy/">
<ExternalLink href="https://www.google.com/policies/privacy/">
Privacy Policy
</Link>{" "}
</ExternalLink>{" "}
and{" "}
<Link href="https://policies.google.com/terms">
<ExternalLink href="https://policies.google.com/terms">
Terms of Service
</Link>{" "}
</ExternalLink>{" "}
apply.
<br />
By clicking "Register", you agree to our{" "}
<Link href={Config.get().eula}>
<ExternalLink href={Config.get().eula}>
End User Licensing Agreement (EULA)
</Link>
</ExternalLink>
</Trans>
</Caption>
</Text>
{error && (
<FieldRow>
<ErrorMessage error={error} />

View File

@ -0,0 +1,13 @@
/*
Copyright 2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/
.external {
/* By default links will be blue/purple (or whatever the user agent does), but
in our designs we generally want external links to be the same color as the
surrounding text */
color: inherit;
}

View File

@ -15,10 +15,16 @@ import {
import { Link as CpdLink } from "@vector-im/compound-web";
import { useHistory } from "react-router-dom";
import { createPath, LocationDescriptor, Path } from "history";
import classNames from "classnames";
import { useLatest } from "../useLatest";
import styles from "./Link.module.css";
export function useLink(
to: LocationDescriptor,
state?: unknown,
): [Path, (e: MouseEvent) => void] {
const latestState = useLatest(state);
const history = useHistory();
const path = useMemo(
() => (typeof to === "string" ? to : createPath(to)),
@ -27,9 +33,9 @@ export function useLink(
const onClick = useCallback(
(e: MouseEvent) => {
e.preventDefault();
history.push(to);
history.push(to, latestState.current);
},
[history, to],
[history, to, latestState],
);
return [path, onClick];
@ -38,15 +44,37 @@ export function useLink(
type Props = Omit<
ComponentPropsWithoutRef<typeof CpdLink>,
"href" | "onClick"
> & { to: LocationDescriptor };
> & { to: LocationDescriptor; state?: unknown };
/**
* A version of Compound's link component that integrates with our router setup.
* This is only for app-internal links.
*/
export const Link = forwardRef<HTMLAnchorElement, Props>(function Link(
{ to, ...props },
{ to, state, ...props },
ref,
) {
const [path, onClick] = useLink(to);
const [path, onClick] = useLink(to, state);
return <CpdLink ref={ref} {...props} href={path} onClick={onClick} />;
});
/**
* A link to an external web page, made to fit into blocks of text more subtly
* than the normal Compound link component.
*/
export const ExternalLink = forwardRef<
HTMLAnchorElement,
ComponentPropsWithoutRef<"a">
>(function ExternalLink({ className, children, ...props }, ref) {
return (
<a
ref={ref}
className={classNames(className, styles.external)}
target="_blank"
rel="noreferrer noopener"
{...props}
>
{children}
</a>
);
});

View File

@ -50,6 +50,12 @@ Please see LICENSE in the repository root for full details.
margin-bottom: 0;
}
.callName {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.facePile {
margin-top: 8px;
}

View File

@ -11,14 +11,13 @@ import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import { Room } from "matrix-js-sdk/src/models/room";
import { FC, useCallback, MouseEvent, useState } from "react";
import { useTranslation } from "react-i18next";
import { IconButton } from "@vector-im/compound-web";
import { IconButton, Text } from "@vector-im/compound-web";
import { CloseIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import classNames from "classnames";
import { Avatar, Size } from "../Avatar";
import styles from "./CallList.module.css";
import { getRelativeRoomUrl } from "../utils/matrix";
import { Body } from "../typography/Typography";
import { GroupCallRoom } from "./useGroupCallRooms";
import { useRoomEncryptionSystem } from "../e2ee/sharedKeyManagement";
@ -78,9 +77,9 @@ const CallTile: FC<CallTileProps> = ({ name, avatarUrl, room, client }) => {
<>
<Avatar id={room.roomId} name={name} size={Size.LG} src={avatarUrl} />
<div className={styles.callInfo}>
<Body overflowEllipsis fontWeight="semiBold">
<Text weight="semibold" className={styles.callName}>
{name}
</Body>
</Text>
</div>
<IconButton
onClick={onRemove}

View File

@ -9,7 +9,7 @@ import { useState, useCallback, FormEvent, FormEventHandler, FC } from "react";
import { useHistory } from "react-router-dom";
import { MatrixClient } from "matrix-js-sdk/src/client";
import { useTranslation } from "react-i18next";
import { Heading } from "@vector-im/compound-web";
import { Heading, Text } from "@vector-im/compound-web";
import { logger } from "matrix-js-sdk/src/logger";
import { Button } from "@vector-im/compound-web";
@ -27,7 +27,6 @@ import { FieldRow, InputField, ErrorMessage } from "../input/Input";
import { CallList } from "./CallList";
import { UserMenuContainer } from "../UserMenuContainer";
import { JoinExistingCallModal } from "./JoinExistingCallModal";
import { Caption } from "../typography/Typography";
import { Form } from "../form/Form";
import { AnalyticsNotice } from "../analytics/AnalyticsNotice";
import { E2eeType } from "../e2ee/e2eeType";
@ -144,9 +143,9 @@ export const RegisteredView: FC<Props> = ({ client }) => {
</Button>
</FieldRow>
{optInAnalytics === null && (
<Caption className={styles.notice}>
<Text size="sm" className={styles.notice}>
<AnalyticsNotice />
</Caption>
</Text>
)}
{error && (
<FieldRow className={styles.fieldRow}>

View File

@ -9,7 +9,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 { Button, Heading } from "@vector-im/compound-web";
import { Button, Heading, Text } from "@vector-im/compound-web";
import { logger } from "matrix-js-sdk/src/logger";
import { useClient } from "../ClientContext";
@ -25,7 +25,6 @@ import {
import { useInteractiveRegistration } from "../auth/useInteractiveRegistration";
import { JoinExistingCallModal } from "./JoinExistingCallModal";
import { useRecaptcha } from "../auth/useRecaptcha";
import { Body, Caption, Link } from "../typography/Typography";
import { Form } from "../form/Form";
import styles from "./UnauthenticatedView.module.css";
import commonStyles from "./common.module.css";
@ -34,6 +33,7 @@ import { AnalyticsNotice } from "../analytics/AnalyticsNotice";
import { Config } from "../config/Config";
import { E2eeType } from "../e2ee/e2eeType";
import { useOptInAnalytics } from "../settings/settings";
import { ExternalLink, Link } from "../button/Link";
export const UnauthenticatedView: FC = () => {
const { setClient } = useClient();
@ -178,18 +178,18 @@ export const UnauthenticatedView: FC = () => {
/>
</FieldRow>
{optInAnalytics === null && (
<Caption className={styles.notice}>
<Text size="sm" className={styles.notice}>
<AnalyticsNotice />
</Caption>
</Text>
)}
<Caption className={styles.notice}>
<Text size="sm" className={styles.notice}>
<Trans i18nKey="unauthenticated_view_eula_caption">
By clicking "Go", you agree to our{" "}
<Link href={Config.get().eula}>
<ExternalLink href={Config.get().eula}>
End User Licensing Agreement (EULA)
</Link>
</ExternalLink>
</Trans>
</Caption>
</Text>
{error && (
<FieldRow>
<ErrorMessage error={error} />
@ -207,19 +207,19 @@ export const UnauthenticatedView: FC = () => {
</Form>
</main>
<footer className={styles.footer}>
<Body className={styles.mobileLoginLink}>
<Link color="primary" to="/login" data-testid="home_login">
<Text className={styles.mobileLoginLink}>
<Link to="/login" data-testid="home_login">
{t("unauthenticated_view_login_button")}
</Link>
</Body>
<Body>
</Text>
<Text>
<Trans i18nKey="unauthenticated_view_body">
Not registered yet?{" "}
<Link color="primary" to="/register" data-testid="home_register">
<Link to="/register" data-testid="home_register">
Create an account
</Link>
</Trans>
</Body>
</Text>
</footer>
</div>
{onFinished && (

View File

@ -237,16 +237,6 @@ body[data-platform="desktop"] {
line-height: var(--font-size-title);
}
a {
color: var(--cpd-color-text-action-accent);
text-decoration: none;
}
a:hover,
a:active {
opacity: 0.8;
}
hr {
width: calc(100% - 24px);
border: none;

View File

@ -18,7 +18,7 @@ import "./index.css";
import { logger } from "matrix-js-sdk/src/logger";
import {
setLogExtension as setLKLogExtension,
setLogLevel,
setLogLevel as setLKLogLevel,
} from "livekit-client";
import { App } from "./App";
@ -28,9 +28,11 @@ import { Initializer } from "./initializer";
initRageshake().catch((e) => {
logger.error("Failed to initialize rageshake", e);
});
setLogLevel("debug");
setLKLogExtension(global.mx_rage_logger.log);
setLKLogLevel("debug");
setLKLogExtension((level, msg, context) => {
// we pass a synthetic logger name of "livekit" to the rageshake to make it easier to read
global.mx_rage_logger.log(level, "livekit", msg, context);
});
logger.info(`Element Call ${import.meta.env.VITE_APP_VERSION || "dev"}`);

View File

@ -9,12 +9,11 @@ import { FC, FormEventHandler, ReactNode, useCallback, useState } from "react";
import { MatrixClient } from "matrix-js-sdk/src/client";
import { Trans, useTranslation } from "react-i18next";
import { useHistory } from "react-router-dom";
import { Button } from "@vector-im/compound-web";
import { Button, Heading, Text } from "@vector-im/compound-web";
import styles from "./CallEndedView.module.css";
import feedbackStyle from "../input/FeedbackInput.module.css";
import { useProfile } from "../profile/useProfile";
import { Body, Headline } from "../typography/Typography";
import { Header, HeaderLogo, LeftNav, RightNav } from "../Header";
import { PosthogAnalytics } from "../analytics/PosthogAnalytics";
import { FieldRow, InputField } from "../input/Input";
@ -139,11 +138,11 @@ export const CallEndedView: FC<Props> = ({
return (
<>
<main className={styles.main}>
<Headline className={styles.headline}>
<Heading size="xl" weight="semibold" className={styles.headline}>
<Trans i18nKey="call_ended_view.body">
You were disconnected from the call
</Trans>
</Headline>
</Heading>
<div className={styles.disconnectedButtons}>
<Button onClick={reconnect}>
{t("call_ended_view.reconnect_button")}
@ -154,9 +153,9 @@ export const CallEndedView: FC<Props> = ({
</div>
</main>
{!confineToRoom && (
<Body className={styles.footer}>
<Text className={styles.footer}>
<Link to="/"> {t("return_home_button")} </Link>
</Body>
</Text>
)}
</>
);
@ -164,7 +163,7 @@ export const CallEndedView: FC<Props> = ({
return (
<>
<main className={styles.main}>
<Headline className={styles.headline}>
<Heading size="xl" weight="semibold" className={styles.headline}>
{surveySubmitted
? t("call_ended_view.headline", {
displayName,
@ -174,16 +173,16 @@ export const CallEndedView: FC<Props> = ({
}) +
"\n" +
t("call_ended_view.survey_prompt")}
</Headline>
</Heading>
{(!surveySubmitted || confineToRoom) &&
PosthogAnalytics.instance.isEnabled()
? qualitySurveyDialog
: createAccountDialog}
</main>
{!confineToRoom && (
<Body className={styles.footer}>
<Text className={styles.footer}>
<Link to="/"> {t("call_ended_view.not_now_button")} </Link>
</Body>
</Text>
)}
</>
);

View File

@ -5,13 +5,12 @@ SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/
import { useCallback } from "react";
import { MatrixClient } from "matrix-js-sdk/src/client";
import { useTranslation } from "react-i18next";
import { MatrixError } from "matrix-js-sdk/src/matrix";
import { useHistory } from "react-router-dom";
import { Heading, Link, Text } from "@vector-im/compound-web";
import { Heading, Text } from "@vector-im/compound-web";
import { Link } from "../button/Link";
import {
useLoadGroupCall,
GroupCallStatus,
@ -35,15 +34,6 @@ export function GroupCallLoader({
const { t } = useTranslation();
const groupCallState = useLoadGroupCall(client, roomIdOrAlias, viaServers);
const history = useHistory();
const onHomeClick = useCallback(
(ev: React.MouseEvent) => {
ev.preventDefault();
history.push("/");
},
[history],
);
switch (groupCallState.kind) {
case "loaded":
case "waitForInvite":
@ -63,9 +53,7 @@ export function GroupCallLoader({
<Text>{t("group_call_loader.failed_text")}</Text>
{/* XXX: A 'create it for me' button would be the obvious UX here. Two screens already have
dupes of this flow, let's make a common component and put it here. */}
<Link href="/" onClick={onHomeClick}>
{t("common.home")}
</Link>
<Link to="/">{t("common.home")}</Link>
</FullScreenView>
);
} else if (groupCallState.error instanceof CallTerminatedMessage) {
@ -79,9 +67,7 @@ export function GroupCallLoader({
<Text size="sm">"{groupCallState.error.reason}"</Text>
</>
)}
<Link href="/" onClick={onHomeClick}>
{t("common.home")}
</Link>
<Link to="/">{t("common.home")}</Link>
</FullScreenView>
);
} else {

View File

@ -15,7 +15,7 @@ import {
import { logger } from "matrix-js-sdk/src/logger";
import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
import { JoinRule } from "matrix-js-sdk/src/matrix";
import { Heading, Link, Text } from "@vector-im/compound-web";
import { Heading, Text } from "@vector-im/compound-web";
import { useTranslation } from "react-i18next";
import type { IWidgetApiRequest } from "matrix-widget-api";
@ -40,6 +40,7 @@ import { useJoinRule } from "./useJoinRule";
import { InviteModal } from "./InviteModal";
import { useUrlParams } from "../UrlParams";
import { E2eeType } from "../e2ee/e2eeType";
import { Link } from "../button/Link";
declare global {
interface Window {
@ -85,6 +86,14 @@ export const GroupCallView: FC<Props> = ({
};
}, [rtcSession]);
useEffect(() => {
// Sanity check the room object
if (client.getRoom(rtcSession.room.roomId) !== rtcSession.room)
logger.warn(
`We've ended up with multiple rooms for the same ID (${rtcSession.room.roomId}). This indicates a bug in the group call loading code, and may lead to incomplete room state.`,
);
}, [client, rtcSession.room]);
const { displayName, avatarUrl } = useProfile(client);
const roomName = useRoomName(rtcSession.room);
const roomAvatar = useRoomAvatar(rtcSession.room);
@ -273,14 +282,6 @@ export const GroupCallView: FC<Props> = ({
);
const onShareClick = joinRule === JoinRule.Public ? onShareClickFn : null;
const onHomeClick = useCallback(
(ev: React.MouseEvent) => {
ev.preventDefault();
history.push("/");
},
[history],
);
const { t } = useTranslation();
if (!isE2EESupportedBrowser() && e2eeSystem.kind !== E2eeType.NONE) {
@ -289,9 +290,7 @@ export const GroupCallView: FC<Props> = ({
<FullScreenView>
<Heading>{t("browser_media_e2ee_unsupported_heading")}</Heading>
<Text>{t("browser_media_e2ee_unsupported")}</Text>
<Link href="/" onClick={onHomeClick}>
{t("common.home")}
</Link>
<Link to="/">{t("common.home")}</Link>
</FullScreenView>
);
}

View File

@ -7,12 +7,11 @@ Please see LICENSE in the repository root for full details.
import { FC, useEffect } from "react";
import { useTranslation } from "react-i18next";
import { Button } from "@vector-im/compound-web";
import { Button, Text } from "@vector-im/compound-web";
import { Modal, Props as ModalProps } from "../Modal";
import { FieldRow, ErrorMessage } from "../input/Input";
import { useSubmitRageshake } from "../settings/submit-rageshake";
import { Body } from "../typography/Typography";
interface Props extends Omit<ModalProps, "title" | "children"> {
rageshakeRequestId: string;
@ -40,7 +39,7 @@ export const RageshakeRequestModal: FC<Props> = ({
open={open}
onDismiss={onDismiss}
>
<Body>{t("rageshake_request_modal.body")}</Body>
<Text>{t("rageshake_request_modal.body")}</Text>
<FieldRow>
<Button
onClick={(): void =>

View File

@ -9,16 +9,16 @@ import { FC, useCallback, useState } from "react";
import { useLocation } from "react-router-dom";
import { Trans, useTranslation } from "react-i18next";
import { logger } from "matrix-js-sdk/src/logger";
import { Button } from "@vector-im/compound-web";
import { Button, Heading, Text } from "@vector-im/compound-web";
import styles from "./RoomAuthView.module.css";
import { Body, Caption, Link, Headline } from "../typography/Typography";
import { Header, HeaderLogo, LeftNav, RightNav } from "../Header";
import { FieldRow, InputField, ErrorMessage } from "../input/Input";
import { Form } from "../form/Form";
import { UserMenuContainer } from "../UserMenuContainer";
import { useRegisterPasswordlessUser } from "../auth/useRegisterPasswordlessUser";
import { Config } from "../config/Config";
import { ExternalLink, Link } from "../button/Link";
export const RoomAuthView: FC = () => {
const [loading, setLoading] = useState(false);
@ -63,9 +63,9 @@ export const RoomAuthView: FC = () => {
</Header>
<div className={styles.container}>
<main className={styles.main}>
<Headline className={styles.headline}>
<Heading size="xl" weight="semibold" className={styles.headline}>
{t("lobby.join_button")}
</Headline>
</Heading>
<Form className={styles.form} onSubmit={onSubmit}>
<FieldRow>
<InputField
@ -79,14 +79,14 @@ export const RoomAuthView: FC = () => {
autoComplete="off"
/>
</FieldRow>
<Caption>
<Text size="sm">
<Trans i18nKey="room_auth_view_eula_caption">
By clicking "Join call now", you agree to our{" "}
<Link href={Config.get().eula}>
<ExternalLink href={Config.get().eula}>
End User Licensing Agreement (EULA)
</Link>
</ExternalLink>
</Trans>
</Caption>
</Text>
{error && (
<FieldRow>
<ErrorMessage error={error} />
@ -103,17 +103,14 @@ export const RoomAuthView: FC = () => {
<div id={recaptchaId} />
</Form>
</main>
<Body className={styles.footer}>
<Text className={styles.footer}>
<Trans i18nKey="unauthenticated_view_body">
Not registered yet?{" "}
<Link
color="primary"
to={{ pathname: "/register", state: { from: location } }}
>
<Link to="/register" state={{ from: location }}>
Create an account
</Link>
</Trans>
</Body>
</Text>
</div>
</>
);

View File

@ -8,12 +8,11 @@ Please see LICENSE in the repository root for full details.
import { FC, useCallback } from "react";
import { randomString } from "matrix-js-sdk/src/randomstring";
import { useTranslation } from "react-i18next";
import { Button } from "@vector-im/compound-web";
import { Button, Text } from "@vector-im/compound-web";
import { logger } from "matrix-js-sdk/src/logger";
import { FieldRow, InputField, ErrorMessage } from "../input/Input";
import { useSubmitRageshake, useRageshakeRequest } from "./submit-rageshake";
import { Body } from "../typography/Typography";
import feedbackStyles from "../input/FeedbackInput.module.css";
interface Props {
@ -56,7 +55,7 @@ export const FeedbackSettingsTab: FC<Props> = ({ roomId }) => {
return (
<div>
<h4>{t("settings.feedback_tab_h4")}</h4>
<Body>{t("settings.feedback_tab_body")}</Body>
<Text>{t("settings.feedback_tab_body")}</Text>
<form onSubmit={onSubmitFeedback}>
<FieldRow>
<InputField
@ -69,9 +68,7 @@ export const FeedbackSettingsTab: FC<Props> = ({ roomId }) => {
disabled={sending || sent}
/>
</FieldRow>
{sent ? (
<Body> {t("settings.feedback_tab_thank_you")}</Body>
) : (
{!sent && (
<FieldRow>
<InputField
id="sendLogs"
@ -80,16 +77,15 @@ export const FeedbackSettingsTab: FC<Props> = ({ roomId }) => {
type="checkbox"
defaultChecked
/>
{error && (
<FieldRow>
<ErrorMessage error={error} />
</FieldRow>
)}
<Button type="submit" disabled={sending}>
{sending ? t("submitting") : t("action.submit")}
</Button>
</FieldRow>
)}
<FieldRow>
{error && <ErrorMessage error={error} />}
{sent && <Text>{t("settings.feedback_tab_thank_you")}</Text>}
</FieldRow>
</form>
</div>
);

View File

@ -8,13 +8,12 @@ Please see LICENSE in the repository root for full details.
import { ChangeEvent, FC, ReactNode, useCallback } from "react";
import { Trans, useTranslation } from "react-i18next";
import { MatrixClient } from "matrix-js-sdk/src/matrix";
import { Dropdown } from "@vector-im/compound-web";
import { Dropdown, Text } from "@vector-im/compound-web";
import { Modal } from "../Modal";
import styles from "./SettingsModal.module.css";
import { Tab, TabContainer } from "../tabs/Tabs";
import { FieldRow, InputField } from "../input/Input";
import { Caption } from "../typography/Typography";
import { AnalyticsNotice } from "../analytics/AnalyticsNotice";
import { ProfileSettingsTab } from "./ProfileSettingsTab";
import { FeedbackSettingsTab } from "./FeedbackSettingsTab";
@ -102,14 +101,14 @@ export const SettingsModal: FC<Props> = ({
};
const optInDescription = (
<Caption>
<Text size="sm">
<Trans i18nKey="settings.opt_in_description">
<AnalyticsNotice />
<br />
You may withdraw consent by unchecking this box. If you are currently in
a call, this setting will take effect at the end of the call.
</Trans>
</Caption>
</Text>
);
const devices = useMediaDevices();

View File

@ -29,9 +29,9 @@ Please see LICENSE in the repository root for full details.
import EventEmitter from "events";
import { throttle } from "lodash";
import { logger } from "matrix-js-sdk/src/logger";
import { Logger, logger } from "matrix-js-sdk/src/logger";
import { randomString } from "matrix-js-sdk/src/randomstring";
import { LoggingMethod } from "loglevel";
import loglevel, { LoggingMethod } from "loglevel";
// the length of log data we keep in indexeddb (and include in the reports)
const MAX_LOG_SIZE = 1024 * 1024 * 5; // 5 MB
@ -467,7 +467,12 @@ declare global {
*/
export async function init(): Promise<void> {
global.mx_rage_logger = new ConsoleLogger();
setLogExtension(global.mx_rage_logger.log);
setLogExtension(logger, global.mx_rage_logger.log);
// these are the child/prefixed loggers we want to capture from js-sdk
// there doesn't seem to be an easy way to capture all children
["MatrixRTCSession", "MatrixRTCSessionManager"].forEach((loggerName) => {
setLogExtension(logger.getChild(loggerName), global.mx_rage_logger.log);
});
return tryInitStorage();
}
@ -580,10 +585,14 @@ type LogLevelString = keyof typeof LogLevel;
* took loglevel's example honouring log levels). Adds a loglevel logging extension
* in the recommended way.
*/
export function setLogExtension(extension: LogExtensionFunc): void {
const originalFactory = logger.methodFactory;
function setLogExtension(
_loggerToExtend: Logger,
extension: LogExtensionFunc,
): void {
const loggerToExtend = _loggerToExtend as unknown as loglevel.Logger;
const originalFactory = loggerToExtend.methodFactory;
logger.methodFactory = function (
loggerToExtend.methodFactory = function (
methodName,
configLevel,
loggerName,
@ -594,11 +603,14 @@ export function setLogExtension(extension: LogExtensionFunc): void {
const needLog = logLevel >= configLevel && logLevel < LogLevel.silent;
return (...args) => {
// we don't send the logger name to the raw method as some of them are already outputting the prefix
rawMethod.apply(this, args);
if (needLog) {
extension(logLevel, ...args);
// we prefix the logger name to the extension
// this makes sure that the rageshake contains the logger name
extension(logLevel, loggerName?.toString(), ...args);
}
};
};
logger.setLevel(logger.getLevel()); // Be sure to call setLevel method in order to apply plugin
loggerToExtend.setLevel(loggerToExtend.getLevel()); // Be sure to call setLevel method in order to apply plugin
}

View File

@ -270,11 +270,17 @@ export function useSubmitRageshake(): {
);
}
await fetch(Config.get().rageshake!.submit_url, {
const res = await fetch(Config.get().rageshake!.submit_url, {
method: "POST",
body,
});
if (res.status !== 200) {
throw new Error(
`Failed to submit feedback: receive HTTP ${res.status} ${res.statusText}`,
);
}
setState({ sending: false, sent: true, error: undefined });
} catch (error) {
setState({ sending: false, sent: false, error: error as Error });

View File

@ -16,7 +16,11 @@ import {
ParticipantEvent,
RemoteParticipant,
} from "livekit-client";
import { Room as MatrixRoom, RoomMember } from "matrix-js-sdk/src/matrix";
import {
Room as MatrixRoom,
RoomMember,
RoomStateEvent,
} from "matrix-js-sdk/src/matrix";
import {
EMPTY,
Observable,
@ -321,6 +325,8 @@ export class CallViewModel extends ViewModel {
this.remoteParticipants,
observeParticipantMedia(this.livekitRoom.localParticipant),
duplicateTiles.value,
// Also react to changes in the list of members
fromEvent(this.matrixRoom, RoomStateEvent.Update).pipe(startWith(null)),
]).pipe(
scan(
(

View File

@ -1,47 +0,0 @@
/*
Copyright 2022-2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/
.caption {
font-size: var(--font-size-caption);
line-height: var(--font-size-body);
}
.micro {
font-size: var(--font-size-micro);
line-height: var(--font-size-caption);
}
.regular {
font-weight: 400;
}
.semiBold {
font-weight: 600;
}
.bold {
font-weight: 700;
}
.link {
color: var(--cpd-color-text-link-external);
}
.link:hover {
text-decoration: underline;
opacity: initial;
}
.primary {
color: var(--cpd-color-text-action-accent);
}
.overflowEllipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

View File

@ -1,262 +0,0 @@
/*
Copyright 2022-2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/
import { createElement, forwardRef, ReactNode } from "react";
import classNames from "classnames";
import { Link as RouterLink } from "react-router-dom";
import * as H from "history";
import styles from "./Typography.module.css";
interface TypographyProps {
children: ReactNode;
fontWeight?: string;
className?: string;
overflowEllipsis?: boolean;
as?: string;
}
export const Headline = forwardRef<HTMLHeadingElement, TypographyProps>(
(
{
as: Component = "h1",
children,
className,
fontWeight,
overflowEllipsis,
...rest
},
ref,
) => {
return createElement(
Component,
{
...rest,
className: classNames(
styles[fontWeight ?? ""],
{ [styles.overflowEllipsis]: overflowEllipsis },
className,
),
ref,
},
children,
);
},
);
Headline.displayName = "Headline";
export const Title = forwardRef<HTMLHeadingElement, TypographyProps>(
(
{
as: Component = "h2",
children,
className,
fontWeight,
overflowEllipsis,
...rest
},
ref,
) => {
return createElement(
Component,
{
...rest,
className: classNames(
styles[fontWeight ?? ""],
{ [styles.overflowEllipsis]: overflowEllipsis },
className,
),
ref,
},
children,
);
},
);
Title.displayName = "Title";
export const Subtitle = forwardRef<HTMLParagraphElement, TypographyProps>(
(
{
as: Component = "h3",
children,
className,
fontWeight,
overflowEllipsis,
...rest
},
ref,
) => {
return createElement(
Component,
{
...rest,
className: classNames(
styles[fontWeight ?? ""],
{ [styles.overflowEllipsis]: overflowEllipsis },
className,
),
ref,
},
children,
);
},
);
Subtitle.displayName = "Subtitle";
export const Body = forwardRef<HTMLParagraphElement, TypographyProps>(
(
{
as: Component = "p",
children,
className,
fontWeight,
overflowEllipsis,
...rest
},
ref,
) => {
return createElement(
Component,
{
...rest,
className: classNames(
styles[fontWeight ?? ""],
{ [styles.overflowEllipsis]: overflowEllipsis },
className,
),
ref,
},
children,
);
},
);
Body.displayName = "Body";
export const Caption = forwardRef<HTMLParagraphElement, TypographyProps>(
(
{
as: Component = "p",
children,
className,
fontWeight,
overflowEllipsis,
...rest
},
ref,
) => {
return createElement(
Component,
{
...rest,
className: classNames(
styles.caption,
styles[fontWeight ?? ""],
{ [styles.overflowEllipsis]: overflowEllipsis },
className,
),
ref,
},
children,
);
},
);
Caption.displayName = "Caption";
export const Micro = forwardRef<HTMLParagraphElement, TypographyProps>(
(
{
as: Component = "p",
children,
className,
fontWeight,
overflowEllipsis,
...rest
},
ref,
) => {
return createElement(
Component,
{
...rest,
className: classNames(
styles.micro,
styles[fontWeight ?? ""],
{ [styles.overflowEllipsis]: overflowEllipsis },
className,
),
ref,
},
children,
);
},
);
Micro.displayName = "Micro";
interface LinkProps extends TypographyProps {
to?: H.LocationDescriptor<unknown>;
color?: string;
href?: string;
}
export const Link = forwardRef<HTMLAnchorElement, LinkProps>(
(
{
as,
children,
className,
color = "link",
href,
to,
fontWeight,
overflowEllipsis,
...rest
},
ref,
) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const Component: string | RouterLink = as || (to ? RouterLink : "a");
let externalLinkProps: { href: string; target: string; rel: string };
if (href) {
externalLinkProps = {
href,
target: "_blank",
rel: "noreferrer noopener",
};
}
return createElement(
Component,
{
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
...externalLinkProps,
...rest,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
to: to,
className: classNames(
styles[color],
styles[fontWeight ?? ""],
{ [styles.overflowEllipsis]: overflowEllipsis },
className,
),
ref: ref,
},
children,
);
},
);
Link.displayName = "Link";

View File

@ -1864,21 +1864,21 @@
"@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14"
"@livekit/components-core@0.11.2", "@livekit/components-core@^0.11.0":
version "0.11.2"
resolved "https://registry.yarnpkg.com/@livekit/components-core/-/components-core-0.11.2.tgz#fded2e207155e4737ed52830d48b75ae2eaaf449"
integrity sha512-rXQ1OvyGe9gY8BCpH5FTr4Il17/sS/ecJQbG3PoOXAkQVl5JP965eqUPyKXZTdxNKlVLef00AygrO2pPArwOTA==
"@livekit/components-core@0.11.4", "@livekit/components-core@^0.11.0":
version "0.11.4"
resolved "https://registry.yarnpkg.com/@livekit/components-core/-/components-core-0.11.4.tgz#33248bb750d6e2492cca919a2409d511ae0a73f0"
integrity sha512-8Dr/9WCEov5AW42KrTnLTuoEIK1yWHcdvvJl8znxx59uUVlE8o6IvsFnofNlfRJ2fCADSnw0K2yEpLQjwLel2Q==
dependencies:
"@floating-ui/dom" "1.6.8"
loglevel "1.9.1"
rxjs "7.8.1"
"@livekit/components-react@^2.0.0":
version "2.4.3"
resolved "https://registry.yarnpkg.com/@livekit/components-react/-/components-react-2.4.3.tgz#634b507be2dfede2267f304fc5922d1b69e2be56"
integrity sha512-XhCvwFvNjhBJcoQHIY4Hk6MBp7mM9q0n0i7sN/xK3fB1DSjkxIkpc7lh/+Pjqdu6F6OJT3MjwNFYnftqy6kcmw==
version "2.5.3"
resolved "https://registry.yarnpkg.com/@livekit/components-react/-/components-react-2.5.3.tgz#49b53207ba5f25c738e21713d454c99a0c59f07f"
integrity sha512-RKqi2KDltsqTdw5xx0ahEfHtuFvQoTjmJ/M1pukqU7Eczcuwga4AuEESxWkq6ct7Zenabru4eYjFbScc60L3og==
dependencies:
"@livekit/components-core" "0.11.2"
"@livekit/components-core" "0.11.4"
clsx "2.1.1"
usehooks-ts "3.1.0"