Compare commits

...

17 Commits

Author SHA1 Message Date
Quentin Gliech
e3411a7a5b Use optimized fonts from fontsource 2024-11-14 11:31:48 +01:00
Quentin Gliech
8f4b932c3c Enable Codecov bundle analysis plugin (#2774) 2024-11-14 10:07:21 +00:00
Robin
88782a0332 Merge pull request #2772 from robintown/local-mute-icon
Show a different icon for participants that you've muted
2024-11-13 13:34:39 -05:00
Robin
9eae1f4145 Show a different icon for participants that you've muted
As a non-intrusive way to help you remember when you've locally muted a participant (so you're less likely to be confused about not hearing their audio), we can show a different icon on the tile.
2024-11-13 11:10:26 -05:00
Will Hunt
3d096426be Quieten rock sound effect. (#2768) 2024-11-12 15:59:58 +00:00
Will Hunt
83fa9661cf Init client with a memory store (for MSC4222) (#2764)
* Bump matrix-js-sdk for MSC4222 support

* Init client with a memory store.

---------

Co-authored-by: Robin <robin@robin.town>
2024-11-12 12:27:49 +00:00
Robin
6c24763196 Merge pull request #2759 from robintown/msc4222
Bump matrix-js-sdk for MSC4222 support
2024-11-12 07:22:49 -05:00
Will Hunt
c4c266536b Sound effect volume adjustment & tooltip (#2762)
* Set the default sound effect volume to 50%.

* Add a tooltip to describe the current volume level.

* lint import
2024-11-12 10:18:45 +00:00
ElementRobot
3535cbe2e6 Merge pull request #2763 from element-hq/actions/localazy-download
Localazy Download
2024-11-12 09:29:50 +00:00
fkwp
dac89b97ae Translations updates 2024-11-12 09:28:30 +00:00
Robin
854e0ab7ae Decrypt potential reaction events before checking their type (#2761)
By rights, this fix I had made to decrypt reaction events shouldn't have appeared successful, because I was requiring the event to have a certain type before asking matrix-js-sdk to decrypt it, and you can't know an event's type before it's decrypted. Probably what was happening is that another code path was requesting the events to be decrypted so that this mistake didn't matter.
2024-11-12 10:05:01 +01:00
Robin
20976e1ee3 Bump matrix-js-sdk for MSC4222 support 2024-11-11 18:01:28 -05:00
Andrew Ferrazzutti
f12e6601fe Refactor if expression (#2758)
Split the check on a boolean into another branch
2024-11-11 12:51:31 -05:00
Robin
4b2d8403d7 Merge pull request #2748 from element-hq/renovate/all-minor-patch
Update all non-major dependencies
2024-11-11 12:37:41 -05:00
renovate[bot]
03f0fffa57 Update all non-major dependencies 2024-11-11 17:33:23 +00:00
Andrew Ferrazzutti
6486ffe777 Explicitly bump matrix-widget-api (#2757)
Was already present in yarn.lock
2024-11-11 12:32:34 -05:00
Timo
3dad62f9e3 Skip lobby when coming from waitForInvite state. (#2753)
* Skip lobby if when coming from waitForInvite state.

* knock reject wording update (shorter/simpler)

* Automatically enter session also in non widget mode when skipLobby = true

* Update public/locales/en-GB/app.json

Co-authored-by: Robin <robin@robin.town>

* review

* review andrew

---------

Co-authored-by: Robin <robin@robin.town>
2024-11-11 18:30:15 +01:00
36 changed files with 803 additions and 680 deletions

View File

@@ -23,3 +23,4 @@ jobs:
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
SENTRY_URL: ${{ secrets.SENTRY_URL }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

View File

@@ -14,6 +14,8 @@ on:
required: true
SENTRY_AUTH_TOKEN:
required: true
CODECOV_TOKEN:
required: false
jobs:
build:
@@ -36,6 +38,7 @@ jobs:
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
SENTRY_URL: ${{ secrets.SENTRY_URL }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
VITE_APP_VERSION: ${{ inputs.vite_app_version }}
NODE_OPTIONS: "--max-old-space-size=4096"
- name: Upload Artifact

View File

@@ -106,7 +106,7 @@ rc_message:
MSC3266 allows to request a room summary of rooms you are not joined. The
summary contains the room join rules. We need that to decide if the user gets
prompted with the option to knock ("ask to join"), a cannot join error or the
prompted with the option to knock ("Request to join call"), a cannot join error or the
join view.
Element Call requires a Livekit SFU alongside a [Livekit JWT

View File

@@ -24,6 +24,9 @@
"@babel/preset-env": "^7.22.20",
"@babel/preset-react": "^7.22.15",
"@babel/preset-typescript": "^7.23.0",
"@codecov/vite-plugin": "^1.3.0",
"@fontsource/inconsolata": "^5.1.0",
"@fontsource/inter": "^5.1.0",
"@formatjs/intl-durationformat": "^0.6.1",
"@formatjs/intl-segmenter": "^11.7.3",
"@livekit/components-core": "^0.11.0",
@@ -87,8 +90,8 @@
"livekit-client": "^2.5.7",
"lodash": "^4.17.21",
"loglevel": "^1.9.1",
"matrix-js-sdk": "matrix-org/matrix-js-sdk#fcb69b16ad8d170c67ea844f83543d467bbd7707",
"matrix-widget-api": "^1.8.2",
"matrix-js-sdk": "matrix-org/matrix-js-sdk#8e9a04cdec0f88fc876bbbf406db55b0677f005d",
"matrix-widget-api": "^1.10.0",
"normalize.css": "^8.0.1",
"observable-hooks": "^4.2.3",
"pako": "^2.0.4",

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -81,8 +81,8 @@
"call_ended_heading": "Anruf beendet",
"failed_heading": "Beitreten fehlgeschlagen",
"failed_text": "Anruf nicht gefunden oder Beitritt nicht erlaubt",
"knock_reject_body": "Die Mitglieder des Raums haben Deine Beitrittsanfrage abgelehnt.",
"knock_reject_heading": "Beitritt nicht erlaubt",
"knock_reject_body": "Die Teilnahmeanfrage wurde abgelehnt.",
"knock_reject_heading": "Zugriff verweigert",
"reason": "Grund"
},
"hangup_button_label": "Anruf beenden",
@@ -100,11 +100,11 @@
"layout_grid_label": "Raster",
"layout_spotlight_label": "Fokus",
"lobby": {
"ask_to_join": "Beitritt anfragen",
"ask_to_join": "Teilnahmeanfrage senden",
"join_as_guest": "Als Gast beitreten",
"join_button": "Anruf beitreten",
"leave_button": "Zurück zu kürzlichen Anrufen",
"waiting_for_invite": "Anfrage gesendet"
"waiting_for_invite": "Anfrage gesendet! Warten auf Teilnahmefreigabe …"
},
"log_in": "Anmelden",
"logging_in": "Anmelden …",

View File

@@ -81,8 +81,8 @@
"call_ended_heading": "Call ended",
"failed_heading": "Failed to join",
"failed_text": "Call not found or is not accessible.",
"knock_reject_body": "The room members declined your request to join.",
"knock_reject_heading": "Not allowed to join",
"knock_reject_body": "Your request to join was declined.",
"knock_reject_heading": "Access denied",
"reason": "Reason"
},
"hangup_button_label": "End call",
@@ -100,11 +100,11 @@
"layout_grid_label": "Grid",
"layout_spotlight_label": "Spotlight",
"lobby": {
"ask_to_join": "Ask to join call",
"ask_to_join": "Request to join call",
"join_as_guest": "Join as guest",
"join_button": "Join call",
"leave_button": "Back to recents",
"waiting_for_invite": "Request sent"
"waiting_for_invite": "Request sent! Waiting for permission to join…"
},
"log_in": "Log In",
"logging_in": "Logging in…",
@@ -192,6 +192,7 @@
"collapse": "Collapse",
"expand": "Expand",
"mute_for_me": "Mute for me",
"muted_for_me": "Muted for me",
"volume": "Volume"
}
}

View File

@@ -8,6 +8,7 @@ Please see LICENSE in the repository root for full details.
import { FC, useCallback } from "react";
import { Root, Track, Range, Thumb } from "@radix-ui/react-slider";
import classNames from "classnames";
import { Tooltip } from "@vector-im/compound-web";
import styles from "./Slider.module.css";
@@ -66,7 +67,10 @@ export const Slider: FC<Props> = ({
<Track className={styles.track}>
<Range className={styles.highlight} />
</Track>
<Thumb className={styles.handle} aria-label={label} />
{/* Note: This is expected not to be visible on mobile.*/}
<Tooltip placement="top" label={Math.round(value * 100).toString() + "%"}>
<Thumb className={styles.handle} aria-label={label} />
</Tooltip>
</Root>
);
};

View File

@@ -5,14 +5,15 @@ SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/
/* Inter unexpectedly contains various codepoints which collide with emoji, even
when variation-16 is applied to request the emoji variant. From eyeballing
the emoji picker, these are: 20e3, 23cf, 24c2, 25a0-25c1, 2665, 2764, 2b06, 2b1c.
Therefore we define a unicode-range to load which excludes the glyphs
(to avoid having to maintain a fork of Inter). */
@layer normalize, compound-legacy, compound;
@import url("@fontsource/inter/400.css");
@import url("@fontsource/inter/500.css");
@import url("@fontsource/inter/600.css");
@import url("@fontsource/inter/700.css");
@import url("@fontsource/inconsolata/400.css");
@import url("@fontsource/inconsolata/700.css");
@import url("normalize.css/normalize.css") layer(normalize);
@import url("@vector-im/compound-design-tokens/assets/web/css/compound-design-tokens.css")
layer(compound);
@@ -52,94 +53,6 @@ layer(compound);
--stopgap-background-85: rgba(16, 19, 23, 0.85);
}
@font-face {
font-family: "Inter";
font-style: normal;
font-weight: 400;
font-display: swap;
unicode-range: var(--inter-unicode-range);
src:
url("/fonts/Inter/Inter-Regular.woff2") format("woff2"),
url("/fonts/Inter/Inter-Regular.woff") format("woff");
}
@font-face {
font-family: "Inter";
font-style: italic;
font-weight: 400;
font-display: swap;
unicode-range: var(--inter-unicode-range);
src:
url("/fonts/Inter/Inter-Italic.woff2") format("woff2"),
url("/fonts/Inter/Inter-Italic.woff") format("woff");
}
@font-face {
font-family: "Inter";
font-style: normal;
font-weight: 500;
font-display: swap;
unicode-range: var(--inter-unicode-range);
src:
url("/fonts/Inter/Inter-Medium.woff2") format("woff2"),
url("/fonts/Inter/Inter-Medium.woff") format("woff");
}
@font-face {
font-family: "Inter";
font-style: italic;
font-weight: 500;
font-display: swap;
unicode-range: var(--inter-unicode-range);
src:
url("/fonts/Inter/Inter-MediumItalic.woff2") format("woff2"),
url("/fonts/Inter/Inter-MediumItalic.woff") format("woff");
}
@font-face {
font-family: "Inter";
font-style: normal;
font-weight: 600;
font-display: swap;
unicode-range: var(--inter-unicode-range);
src:
url("/fonts/Inter/Inter-SemiBold.woff2") format("woff2"),
url("/fonts/Inter/Inter-SemiBold.woff") format("woff");
}
@font-face {
font-family: "Inter";
font-style: italic;
font-weight: 600;
font-display: swap;
unicode-range: var(--inter-unicode-range);
src:
url("/fonts/Inter/Inter-SemiBoldItalic.woff2") format("woff2"),
url("/fonts/Inter/Inter-SemiBoldItalic.woff") format("woff");
}
@font-face {
font-family: "Inter";
font-style: normal;
font-weight: 700;
font-display: swap;
unicode-range: var(--inter-unicode-range);
src:
url("/fonts/Inter/Inter-Bold.woff2") format("woff2"),
url("/fonts/Inter/Inter-Bold.woff") format("woff");
}
@font-face {
font-family: "Inter";
font-style: italic;
font-weight: 700;
font-display: swap;
unicode-range: var(--inter-unicode-range);
src:
url("/fonts/Inter/Inter-BoldItalic.woff2") format("woff2"),
url("/fonts/Inter/Inter-BoldItalic.woff") format("woff");
}
body {
background-color: var(--cpd-color-bg-canvas-default);
background-size: calc(max(1440px, 100vw)) calc(max(800px, 100vh));
@@ -185,10 +98,6 @@ body[data-platform="ios"] {
--cpd-font-family-sans: -apple-system, BlinkMacSystemFont, "Inter", sans-serif;
}
body[data-platform="desktop"] {
--cpd-font-family-sans: "Inter", sans-serif;
}
@layer compound-legacy {
h1,
h2,

View File

@@ -1,77 +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 { MatrixClient } from "matrix-js-sdk/src/client";
import { useTranslation } from "react-i18next";
import { MatrixError } from "matrix-js-sdk/src/matrix";
import { Heading, Text } from "@vector-im/compound-web";
import { Link } from "../button/Link";
import {
useLoadGroupCall,
GroupCallStatus,
CallTerminatedMessage,
} from "./useLoadGroupCall";
import { ErrorView, FullScreenView } from "../FullScreenView";
interface Props {
client: MatrixClient;
roomIdOrAlias: string;
viaServers: string[];
children: (groupCallState: GroupCallStatus) => JSX.Element;
}
export function GroupCallLoader({
client,
roomIdOrAlias,
viaServers,
children,
}: Props): JSX.Element {
const { t } = useTranslation();
const groupCallState = useLoadGroupCall(client, roomIdOrAlias, viaServers);
switch (groupCallState.kind) {
case "loaded":
case "waitForInvite":
case "canKnock":
return children(groupCallState);
case "loading":
return (
<FullScreenView>
<h1>{t("common.loading")}</h1>
</FullScreenView>
);
case "failed":
if ((groupCallState.error as MatrixError).errcode === "M_NOT_FOUND") {
return (
<FullScreenView>
<Heading>{t("group_call_loader.failed_heading")}</Heading>
<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 to="/">{t("common.home")}</Link>
</FullScreenView>
);
} else if (groupCallState.error instanceof CallTerminatedMessage) {
return (
<FullScreenView>
<Heading>{groupCallState.error.message}</Heading>
<Text>{groupCallState.error.messageBody}</Text>
{groupCallState.error.reason && (
<>
{t("group_call_loader.reason")}:
<Text size="sm">"{groupCallState.error.reason}"</Text>
</>
)}
<Link to="/">{t("common.home")}</Link>
</FullScreenView>
);
} else {
return <ErrorView error={groupCallState.error} />;
}
}
}

View File

@@ -177,29 +177,37 @@ export const GroupCallView: FC<Props> = ({
}
};
if (widget && preload && skipLobby) {
// In preload mode without lobby we wait for a join action before entering
const onJoin = (ev: CustomEvent<IWidgetApiRequest>): void => {
(async (): Promise<void> => {
await defaultDeviceSetup(ev.detail.data as unknown as JoinCallData);
await enterRTCSession(rtcSession, perParticipantE2EE);
widget!.api.transport.reply(ev.detail, {});
})().catch((e) => {
logger.error("Error joining RTC session", e);
});
};
widget.lazyActions.on(ElementWidgetActions.JoinCall, onJoin);
return (): void => {
widget!.lazyActions.off(ElementWidgetActions.JoinCall, onJoin);
};
} else if (widget && !preload && skipLobby) {
// No lobby and no preload: we enter the rtc session right away
(async (): Promise<void> => {
await defaultDeviceSetup({ audioInput: null, videoInput: null });
await enterRTCSession(rtcSession, perParticipantE2EE);
})().catch((e) => {
logger.error("Error joining RTC session", e);
});
if (skipLobby) {
if (widget) {
if (preload) {
// In preload mode without lobby we wait for a join action before entering
const onJoin = (ev: CustomEvent<IWidgetApiRequest>): void => {
(async (): Promise<void> => {
await defaultDeviceSetup(
ev.detail.data as unknown as JoinCallData,
);
await enterRTCSession(rtcSession, perParticipantE2EE);
widget!.api.transport.reply(ev.detail, {});
})().catch((e) => {
logger.error("Error joining RTC session", e);
});
};
widget.lazyActions.on(ElementWidgetActions.JoinCall, onJoin);
return (): void => {
widget!.lazyActions.off(ElementWidgetActions.JoinCall, onJoin);
};
} else {
// No lobby and no preload: we enter the rtc session right away
(async (): Promise<void> => {
await defaultDeviceSetup({ audioInput: null, videoInput: null });
await enterRTCSession(rtcSession, perParticipantE2EE);
})().catch((e) => {
logger.error("Error joining RTC session", e);
});
}
} else {
void enterRTCSession(rtcSession, perParticipantE2EE);
}
}
}, [rtcSession, preload, skipLobby, perParticipantE2EE]);

View File

@@ -5,15 +5,16 @@ SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/
import { FC, useEffect, useState, useCallback, ReactNode } from "react";
import { FC, useEffect, useState, ReactNode, useRef } from "react";
import { logger } from "matrix-js-sdk/src/logger";
import { useTranslation } from "react-i18next";
import { CheckIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { MatrixError } from "matrix-js-sdk/src/http-api";
import { Heading, Text } from "@vector-im/compound-web";
import { useClientLegacy } from "../ClientContext";
import { ErrorView, LoadingView } from "../FullScreenView";
import { ErrorView, FullScreenView, LoadingView } from "../FullScreenView";
import { RoomAuthView } from "./RoomAuthView";
import { GroupCallLoader } from "./GroupCallLoader";
import { GroupCallView } from "./GroupCallView";
import { useRoomIdentifier, useUrlParams } from "../UrlParams";
import { useRegisterPasswordlessUser } from "../auth/useRegisterPasswordlessUser";
@@ -21,13 +22,14 @@ import { HomePage } from "../home/HomePage";
import { platform } from "../Platform";
import { AppSelectionModal } from "./AppSelectionModal";
import { widget } from "../widget";
import { GroupCallStatus } from "./useLoadGroupCall";
import { CallTerminatedMessage, useLoadGroupCall } from "./useLoadGroupCall";
import { LobbyView } from "./LobbyView";
import { E2eeType } from "../e2ee/e2eeType";
import { useProfile } from "../profile/useProfile";
import { useMuteStates } from "./MuteStates";
import { useOptInAnalytics } from "../settings/settings";
import { Config } from "../config/Config";
import { Link } from "../button/Link";
export const RoomPage: FC = () => {
const {
@@ -53,6 +55,7 @@ export const RoomPage: FC = () => {
useClientLegacy();
const { avatarUrl, displayName: userDisplayName } = useProfile(client);
const groupCallState = useLoadGroupCall(client, roomIdOrAlias, viaServers);
const muteStates = useMuteStates();
useEffect(() => {
@@ -82,82 +85,112 @@ export const RoomPage: FC = () => {
if (optInAnalytics === null && setOptInAnalytics) setOptInAnalytics(true);
}, [optInAnalytics, setOptInAnalytics]);
const groupCallView = useCallback(
(groupCallState: GroupCallStatus): JSX.Element => {
switch (groupCallState.kind) {
case "loaded":
return (
<GroupCallView
client={client!}
rtcSession={groupCallState.rtcSession}
isPasswordlessUser={passwordlessUser}
confineToRoom={confineToRoom}
preload={preload}
skipLobby={skipLobby}
hideHeader={hideHeader}
muteStates={muteStates}
/>
const wasInWaitForInviteState = useRef<boolean>(false);
useEffect(() => {
if (groupCallState.kind === "loaded" && wasInWaitForInviteState.current) {
logger.log("Play join sound 'Not yet implemented'");
}
}, [groupCallState.kind]);
const groupCallView = (): JSX.Element => {
switch (groupCallState.kind) {
case "loaded":
return (
<GroupCallView
client={client!}
rtcSession={groupCallState.rtcSession}
isPasswordlessUser={passwordlessUser}
confineToRoom={confineToRoom}
preload={preload}
skipLobby={skipLobby || wasInWaitForInviteState.current}
hideHeader={hideHeader}
muteStates={muteStates}
/>
);
case "waitForInvite":
case "canKnock": {
wasInWaitForInviteState.current =
wasInWaitForInviteState.current ||
groupCallState.kind === "waitForInvite";
const knock =
groupCallState.kind === "canKnock" ? groupCallState.knock : null;
const label: string | JSX.Element =
groupCallState.kind === "canKnock" ? (
t("lobby.ask_to_join")
) : (
<>
{t("lobby.waiting_for_invite")}
<CheckIcon />
</>
);
case "waitForInvite":
case "canKnock": {
const knock =
groupCallState.kind === "canKnock" ? groupCallState.knock : null;
const label: string | JSX.Element =
groupCallState.kind === "canKnock" ? (
t("lobby.ask_to_join")
) : (
<>
{t("lobby.waiting_for_invite")}
<CheckIcon />
</>
);
return (
<LobbyView
client={client!}
matrixInfo={{
userId: client!.getUserId() ?? "",
displayName: userDisplayName ?? "",
avatarUrl: avatarUrl ?? "",
roomAlias: null,
roomId: groupCallState.roomSummary.room_id,
roomName: groupCallState.roomSummary.name ?? "",
roomAvatar: groupCallState.roomSummary.avatar_url ?? null,
e2eeSystem: {
kind: groupCallState.roomSummary[
"im.nheko.summary.encryption"
]
? E2eeType.PER_PARTICIPANT
: E2eeType.NONE,
},
}}
onEnter={(): void => knock?.()}
enterLabel={label}
waitingForInvite={groupCallState.kind === "waitForInvite"}
confineToRoom={confineToRoom}
hideHeader={hideHeader}
participantCount={null}
muteStates={muteStates}
onShareClick={null}
/>
);
}
default:
return <> </>;
return (
<LobbyView
client={client!}
matrixInfo={{
userId: client!.getUserId() ?? "",
displayName: userDisplayName ?? "",
avatarUrl: avatarUrl ?? "",
roomAlias: null,
roomId: groupCallState.roomSummary.room_id,
roomName: groupCallState.roomSummary.name ?? "",
roomAvatar: groupCallState.roomSummary.avatar_url ?? null,
e2eeSystem: {
kind: groupCallState.roomSummary["im.nheko.summary.encryption"]
? E2eeType.PER_PARTICIPANT
: E2eeType.NONE,
},
}}
onEnter={(): void => knock?.()}
enterLabel={label}
waitingForInvite={groupCallState.kind === "waitForInvite"}
confineToRoom={confineToRoom}
hideHeader={hideHeader}
participantCount={null}
muteStates={muteStates}
onShareClick={null}
/>
);
}
},
[
client,
passwordlessUser,
confineToRoom,
preload,
skipLobby,
hideHeader,
muteStates,
t,
userDisplayName,
avatarUrl,
],
);
case "loading":
return (
<FullScreenView>
<h1>{t("common.loading")}</h1>
</FullScreenView>
);
case "failed":
wasInWaitForInviteState.current = false;
if ((groupCallState.error as MatrixError).errcode === "M_NOT_FOUND") {
return (
<FullScreenView>
<Heading>{t("group_call_loader.failed_heading")}</Heading>
<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 to="/">{t("common.home")}</Link>
</FullScreenView>
);
} else if (groupCallState.error instanceof CallTerminatedMessage) {
return (
<FullScreenView>
<Heading>{groupCallState.error.message}</Heading>
<Text>{groupCallState.error.messageBody}</Text>
{groupCallState.error.reason && (
<>
{t("group_call_loader.reason")}:
<Text size="sm">"{groupCallState.error.reason}"</Text>
</>
)}
<Link to="/">{t("common.home")}</Link>
</FullScreenView>
);
} else {
return <ErrorView error={groupCallState.error} />;
}
default:
return <> </>;
}
};
let content: ReactNode;
if (loading || isRegistering) {
@@ -170,15 +203,7 @@ export const RoomPage: FC = () => {
// 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>
);
content = groupCallView();
}
return (

View File

@@ -117,8 +117,8 @@ export class CallTerminatedMessage extends Error {
}
export const useLoadGroupCall = (
client: MatrixClient,
roomIdOrAlias: string,
client: MatrixClient | undefined,
roomIdOrAlias: string | null,
viaServers: string[],
): GroupCallStatus => {
const [state, setState] = useState<GroupCallStatus>({ kind: "loading" });
@@ -159,6 +159,9 @@ export const useLoadGroupCall = (
?.getContent().reason;
useEffect(() => {
if (!client || !roomIdOrAlias) {
return;
}
const getRoomByAlias = async (alias: string): Promise<Room> => {
// We lowercase the localpart when we create the room, so we must lowercase
// it here too (we just do the whole alias). We can't do the same to room IDs

View File

@@ -102,7 +102,7 @@ export const playReactionsSound = new Setting<boolean>(
export const soundEffectVolumeSetting = new Setting<number>(
"sound-effect-volume",
1,
0.5,
);
export const alwaysShowSelf = new Setting<boolean>("always-show-self", true);

Binary file not shown.

Binary file not shown.

View File

@@ -26,6 +26,7 @@ import {
VisibilityOnIcon,
UserProfileIcon,
ExpandIcon,
VolumeOffSolidIcon,
} from "@vector-im/compound-design-tokens/assets/web/icons";
import {
ContextMenu,
@@ -62,6 +63,7 @@ interface TileProps {
interface UserMediaTileProps extends TileProps {
vm: UserMediaViewModel;
mirror: boolean;
locallyMuted: boolean;
menuStart?: ReactNode;
menuEnd?: ReactNode;
}
@@ -71,6 +73,7 @@ const UserMediaTile = forwardRef<HTMLDivElement, UserMediaTileProps>(
{
vm,
showSpeakingIndicators,
locallyMuted,
menuStart,
menuEnd,
className,
@@ -96,7 +99,16 @@ const UserMediaTile = forwardRef<HTMLDivElement, UserMediaTileProps>(
);
const { raisedHands, lowerHand, reactions } = useReactions();
const MicIcon = audioEnabled ? MicOnSolidIcon : MicOffSolidIcon;
const AudioIcon = locallyMuted
? VolumeOffSolidIcon
: audioEnabled
? MicOnSolidIcon
: MicOffSolidIcon;
const audioIconLabel = locallyMuted
? t("video_tile.muted_for_me")
: audioEnabled
? t("microphone_on")
: t("microphone_off");
const [menuOpen, setMenuOpen] = useState(false);
const menu = (
@@ -134,11 +146,11 @@ const UserMediaTile = forwardRef<HTMLDivElement, UserMediaTileProps>(
[styles.handRaised]: !showSpeaking && !!handRaised,
})}
nameTagLeadingIcon={
<MicIcon
<AudioIcon
width={20}
height={20}
aria-label={audioEnabled ? t("microphone_on") : t("microphone_off")}
data-muted={!audioEnabled}
aria-label={audioIconLabel}
data-muted={locallyMuted || !audioEnabled}
className={styles.muteIcon}
/>
}
@@ -199,6 +211,7 @@ const LocalUserMediaTile = forwardRef<HTMLDivElement, LocalUserMediaTileProps>(
<UserMediaTile
ref={ref}
vm={vm}
locallyMuted={false}
mirror={mirror}
menuStart={
<ToggleMenuItem
@@ -255,6 +268,7 @@ const RemoteUserMediaTile = forwardRef<
<UserMediaTile
ref={ref}
vm={vm}
locallyMuted={locallyMuted}
mirror={false}
menuStart={
<>

View File

@@ -195,11 +195,12 @@ export const ReactionsProvider = ({
// Skip any event without a sender or event ID.
if (!sender || !reactionEventId) return;
room.client
.decryptEventIfNeeded(event)
.catch((e) => logger.warn(`Failed to decrypt ${event.getId()}`, e));
if (event.isBeingDecrypted() || event.isDecryptionFailure()) return;
if (event.getType() === ElementCallReactionEventType) {
room.client
.decryptEventIfNeeded(event)
.catch((e) => logger.warn(`Failed to decrypt ${event.getId()}`, e));
if (event.isBeingDecrypted() || event.isDecryptionFailure()) return;
const content: ECallReactionEventContent = event.getContent();
const membershipEventId = content?.["m.relates_to"]?.event_id;

View File

@@ -123,6 +123,7 @@ export async function initClient(
localTimeoutMs: 5000,
useE2eForGroupCall: e2eEnabled,
fallbackICEServerAllowed: fallbackICEServerAllowed,
store: new MemoryStore(),
});
// In case of logging in a new matrix account but there is still crypto local store. This is needed for:

View File

@@ -8,6 +8,7 @@ Please see LICENSE in the repository root for full details.
import { defineConfig, loadEnv } from "vite";
import svgrPlugin from "vite-plugin-svgr";
import htmlTemplate from "vite-plugin-html-template";
import { codecovVitePlugin } from "@codecov/vite-plugin";
import { sentryVitePlugin } from "@sentry/vite-plugin";
import react from "@vitejs/plugin-react";
import basicSsl from "@vitejs/plugin-basic-ssl";
@@ -31,6 +32,12 @@ export default defineConfig(({ mode }) => {
title: env.VITE_PRODUCT_NAME || "Element Call",
},
}),
codecovVitePlugin({
enableBundleAnalysis: process.env.CODECOV_TOKEN !== undefined,
bundleName: "element-call",
uploadToken: process.env.CODECOV_TOKEN,
}),
];
if (

964
yarn.lock

File diff suppressed because it is too large Load Diff