Remove the unused exports with help of ts-prune
This commit is contained in:
parent
8b533018ea
commit
b11ab01bbe
23 changed files with 15 additions and 908 deletions
|
@ -15,10 +15,8 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import classNames from "classnames";
|
||||
import React, { HTMLAttributes, ReactNode, useCallback, useRef } from "react";
|
||||
import React, { HTMLAttributes, ReactNode, useCallback } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { useButton } from "@react-aria/button";
|
||||
import { AriaButtonProps } from "@react-types/button";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
|
@ -30,7 +28,6 @@ import { ReactComponent as VideoIcon } from "./icons/Video.svg";
|
|||
import { Subtitle } from "./typography/Typography";
|
||||
import { Avatar, Size } from "./Avatar";
|
||||
import { IncompatibleVersionModal } from "./IncompatibleVersionModal";
|
||||
import { ReactComponent as ArrowLeftIcon } from "./icons/ArrowLeft.svg";
|
||||
|
||||
interface HeaderProps extends HTMLAttributes<HTMLElement> {
|
||||
children: ReactNode;
|
||||
|
@ -139,37 +136,6 @@ export function RoomHeaderInfo({ roomName, avatarUrl }: RoomHeaderInfo) {
|
|||
);
|
||||
}
|
||||
|
||||
interface RoomSetupHeaderInfoProps extends AriaButtonProps<"button"> {
|
||||
roomName: string;
|
||||
avatarUrl: string;
|
||||
isEmbedded: boolean;
|
||||
}
|
||||
|
||||
export function RoomSetupHeaderInfo({
|
||||
roomName,
|
||||
avatarUrl,
|
||||
isEmbedded,
|
||||
...rest
|
||||
}: RoomSetupHeaderInfoProps) {
|
||||
const ref = useRef();
|
||||
const { buttonProps } = useButton(rest, ref);
|
||||
|
||||
if (isEmbedded) {
|
||||
return (
|
||||
<div ref={ref}>
|
||||
<RoomHeaderInfo roomName={roomName} avatarUrl={avatarUrl} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<button className={styles.backButton} ref={ref} {...buttonProps}>
|
||||
<ArrowLeftIcon width={16} height={16} />
|
||||
<RoomHeaderInfo roomName={roomName} avatarUrl={avatarUrl} />
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
interface VersionMismatchWarningProps {
|
||||
users: Set<string>;
|
||||
room: Room;
|
||||
|
|
|
@ -30,7 +30,7 @@ import {
|
|||
} from "@react-stately/overlays";
|
||||
import { useDialog } from "@react-aria/dialog";
|
||||
import { FocusScope } from "@react-aria/focus";
|
||||
import { ButtonAria, useButton } from "@react-aria/button";
|
||||
import { useButton } from "@react-aria/button";
|
||||
import classNames from "classnames";
|
||||
import { AriaDialogProps } from "@react-types/dialog";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
@ -133,72 +133,3 @@ export function useModalTriggerState(): {
|
|||
);
|
||||
return { modalState, modalProps };
|
||||
}
|
||||
|
||||
export function useToggleModalButton(
|
||||
modalState: OverlayTriggerState,
|
||||
ref: React.RefObject<HTMLButtonElement>
|
||||
): ButtonAria<React.ButtonHTMLAttributes<HTMLButtonElement>> {
|
||||
return useButton(
|
||||
{
|
||||
onPress: () => modalState.toggle(),
|
||||
},
|
||||
ref
|
||||
);
|
||||
}
|
||||
|
||||
export function useOpenModalButton(
|
||||
modalState: OverlayTriggerState,
|
||||
ref: React.RefObject<HTMLButtonElement>
|
||||
): ButtonAria<React.ButtonHTMLAttributes<HTMLButtonElement>> {
|
||||
return useButton(
|
||||
{
|
||||
onPress: () => modalState.open(),
|
||||
},
|
||||
ref
|
||||
);
|
||||
}
|
||||
|
||||
export function useCloseModalButton(
|
||||
modalState: OverlayTriggerState,
|
||||
ref: React.RefObject<HTMLButtonElement>
|
||||
): ButtonAria<React.ButtonHTMLAttributes<HTMLButtonElement>> {
|
||||
return useButton(
|
||||
{
|
||||
onPress: () => modalState.close(),
|
||||
},
|
||||
ref
|
||||
);
|
||||
}
|
||||
|
||||
interface ModalTriggerProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export function ModalTrigger({ children }: ModalTriggerProps) {
|
||||
const { modalState, modalProps } = useModalTriggerState();
|
||||
const buttonRef = useRef();
|
||||
const { buttonProps } = useToggleModalButton(modalState, buttonRef);
|
||||
|
||||
if (
|
||||
!Array.isArray(children) ||
|
||||
children.length > 2 ||
|
||||
typeof children[1] !== "function"
|
||||
) {
|
||||
throw new Error(
|
||||
"ModalTrigger must have two props. The first being a button and the second being a render prop."
|
||||
);
|
||||
}
|
||||
|
||||
const [modalTrigger, modal] = children;
|
||||
|
||||
return (
|
||||
<>
|
||||
<modalTrigger.type
|
||||
{...modalTrigger.props}
|
||||
{...buttonProps}
|
||||
ref={buttonRef}
|
||||
/>
|
||||
{modalState.isOpen && modal(modalProps)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -55,7 +55,7 @@ export interface IPosthogEvent {
|
|||
$set_once?: void;
|
||||
}
|
||||
|
||||
export enum Anonymity {
|
||||
enum Anonymity {
|
||||
Disabled,
|
||||
Anonymous,
|
||||
Pseudonymous,
|
||||
|
|
|
@ -40,7 +40,7 @@ interface TooltipProps {
|
|||
children: ReactNode;
|
||||
}
|
||||
|
||||
export const Tooltip = forwardRef<HTMLDivElement, TooltipProps>(
|
||||
const Tooltip = forwardRef<HTMLDivElement, TooltipProps>(
|
||||
(
|
||||
{ state, className, children, ...rest }: TooltipProps,
|
||||
ref: ForwardedRef<HTMLDivElement>
|
||||
|
|
|
@ -17,7 +17,7 @@ limitations under the License.
|
|||
import { useMemo } from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
|
||||
export interface UrlParams {
|
||||
interface UrlParams {
|
||||
roomAlias: string | null;
|
||||
roomId: string | null;
|
||||
viaServers: string[];
|
||||
|
|
|
@ -22,7 +22,7 @@ import { useInteractiveRegistration } from "../auth/useInteractiveRegistration";
|
|||
import { generateRandomName } from "../auth/generateRandomName";
|
||||
import { useRecaptcha } from "../auth/useRecaptcha";
|
||||
|
||||
export interface UseRegisterPasswordlessUserType {
|
||||
interface UseRegisterPasswordlessUserType {
|
||||
privacyPolicyUrl: string;
|
||||
registerPasswordlessUser: (displayName: string) => Promise<void>;
|
||||
recaptchaId: string;
|
||||
|
|
|
@ -27,8 +27,6 @@ import { ReactComponent as VideoIcon } from "../icons/Video.svg";
|
|||
import { ReactComponent as DisableVideoIcon } from "../icons/DisableVideo.svg";
|
||||
import { ReactComponent as HangupIcon } from "../icons/Hangup.svg";
|
||||
import { ReactComponent as ScreenshareIcon } from "../icons/Screenshare.svg";
|
||||
import { ReactComponent as SettingsIcon } from "../icons/Settings.svg";
|
||||
import { ReactComponent as AddUserIcon } from "../icons/AddUser.svg";
|
||||
import { ReactComponent as ArrowDownIcon } from "../icons/ArrowDown.svg";
|
||||
import { TooltipTrigger } from "../Tooltip";
|
||||
|
||||
|
@ -220,43 +218,3 @@ export function HangupButton({
|
|||
</TooltipTrigger>
|
||||
);
|
||||
}
|
||||
|
||||
export function SettingsButton({
|
||||
className,
|
||||
...rest
|
||||
}: {
|
||||
className?: string;
|
||||
// TODO: add all props for <Button>
|
||||
[index: string]: unknown;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const tooltip = useCallback(() => t("Settings"), [t]);
|
||||
|
||||
return (
|
||||
<TooltipTrigger tooltip={tooltip}>
|
||||
<Button variant="toolbar" {...rest}>
|
||||
<SettingsIcon />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
);
|
||||
}
|
||||
|
||||
export function InviteButton({
|
||||
className,
|
||||
...rest
|
||||
}: {
|
||||
className?: string;
|
||||
// TODO: add all props for <Button>
|
||||
[index: string]: unknown;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const tooltip = useCallback(() => t("Invite"), [t]);
|
||||
|
||||
return (
|
||||
<TooltipTrigger tooltip={tooltip}>
|
||||
<Button variant="toolbar" {...rest}>
|
||||
<AddUserIcon />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ interface FieldProps {
|
|||
className?: string;
|
||||
}
|
||||
|
||||
export function Field({ children, className }: FieldProps): JSX.Element {
|
||||
function Field({ children, className }: FieldProps): JSX.Element {
|
||||
return <div className={classNames(styles.field, className)}>{children}</div>;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,62 +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.
|
||||
*/
|
||||
|
||||
.toggle {
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.button {
|
||||
position: relative;
|
||||
padding: 0;
|
||||
transition: background-color 0.2s ease-out 0.1s;
|
||||
width: 44px;
|
||||
height: 24px;
|
||||
border: none;
|
||||
border-radius: 21px;
|
||||
background-color: var(--quaternary-content);
|
||||
cursor: pointer;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.ball {
|
||||
transition: left 0.15s ease-out 0.1s;
|
||||
position: absolute;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 21px;
|
||||
background-color: var(--background);
|
||||
left: 2px;
|
||||
top: 2px;
|
||||
}
|
||||
|
||||
.label {
|
||||
padding: 10px 8px;
|
||||
line-height: 24px;
|
||||
color: var(--quaternary-content);
|
||||
}
|
||||
|
||||
.toggle.on .button {
|
||||
background-color: var(--accent);
|
||||
}
|
||||
|
||||
.toggle.on .ball {
|
||||
left: 22px;
|
||||
}
|
||||
|
||||
.toggle.on .label {
|
||||
color: var(--primary-content);
|
||||
}
|
|
@ -1,72 +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 React, { useCallback, useRef } from "react";
|
||||
import { useToggleButton } from "@react-aria/button";
|
||||
import classNames from "classnames";
|
||||
|
||||
import styles from "./Toggle.module.css";
|
||||
import { Field } from "./Input";
|
||||
|
||||
interface Props {
|
||||
id: string;
|
||||
label: string;
|
||||
onChange: (selected: boolean) => void;
|
||||
isSelected: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function Toggle({
|
||||
id,
|
||||
label,
|
||||
className,
|
||||
onChange,
|
||||
isSelected,
|
||||
}: Props): JSX.Element {
|
||||
const buttonRef = useRef<HTMLButtonElement>();
|
||||
const toggle = useCallback(() => {
|
||||
onChange(!isSelected);
|
||||
}, [isSelected, onChange]);
|
||||
|
||||
const buttonProps = useToggleButton(
|
||||
{ isSelected },
|
||||
{ isSelected: isSelected, setSelected: undefined, toggle },
|
||||
buttonRef
|
||||
);
|
||||
return (
|
||||
<Field
|
||||
className={classNames(
|
||||
styles.toggle,
|
||||
{ [styles.on]: isSelected },
|
||||
className
|
||||
)}
|
||||
>
|
||||
<button
|
||||
{...buttonProps}
|
||||
ref={buttonRef}
|
||||
id={id}
|
||||
className={classNames(styles.button, {
|
||||
[styles.isPressed]: isSelected,
|
||||
})}
|
||||
>
|
||||
<div className={styles.ball} />
|
||||
</button>
|
||||
<label className={styles.label} htmlFor={id}>
|
||||
{label}
|
||||
</label>
|
||||
</Field>
|
||||
);
|
||||
}
|
|
@ -209,10 +209,7 @@ export function roomAliasLocalpartFromRoomName(roomName: string): string {
|
|||
.toLowerCase();
|
||||
}
|
||||
|
||||
export function fullAliasFromRoomName(
|
||||
roomName: string,
|
||||
client: MatrixClient
|
||||
): string {
|
||||
function fullAliasFromRoomName(roomName: string, client: MatrixClient): string {
|
||||
return `#${roomAliasLocalpartFromRoomName(roomName)}:${client.getDomain()}`;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,53 +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 React, { useEffect, useState } from "react";
|
||||
|
||||
function leftPad(value: number): string {
|
||||
return value < 10 ? "0" + value : "" + value;
|
||||
}
|
||||
|
||||
function formatTime(msElapsed: number): string {
|
||||
const secondsElapsed = msElapsed / 1000;
|
||||
const hours = Math.floor(secondsElapsed / 3600);
|
||||
const minutes = Math.floor(secondsElapsed / 60) - hours * 60;
|
||||
const seconds = Math.floor(secondsElapsed - hours * 3600 - minutes * 60);
|
||||
return `${leftPad(hours)}:${leftPad(minutes)}:${leftPad(seconds)}`;
|
||||
}
|
||||
|
||||
export function Timer({ value }: { value: string }) {
|
||||
const [timestamp, setTimestamp] = useState<string>();
|
||||
|
||||
useEffect(() => {
|
||||
const startTimeMs = performance.now();
|
||||
|
||||
let animationFrame: number;
|
||||
|
||||
function onUpdate(curTimeMs: number) {
|
||||
const msElapsed = curTimeMs - startTimeMs;
|
||||
setTimestamp(formatTime(msElapsed));
|
||||
animationFrame = requestAnimationFrame(onUpdate);
|
||||
}
|
||||
|
||||
onUpdate(startTimeMs);
|
||||
|
||||
return () => {
|
||||
cancelAnimationFrame(animationFrame);
|
||||
};
|
||||
}, [value]);
|
||||
|
||||
return <p>{timestamp}</p>;
|
||||
}
|
|
@ -33,7 +33,7 @@ import { PosthogAnalytics } from "../PosthogAnalytics";
|
|||
import { TranslatedError, translatedError } from "../TranslatedError";
|
||||
import { ElementWidgetActions, ScreenshareStartData, widget } from "../widget";
|
||||
|
||||
export enum ConnectionState {
|
||||
enum ConnectionState {
|
||||
EstablishingCall = "establishing call", // call hasn't been established yet
|
||||
WaitMedia = "wait_media", // call is set up, waiting for ICE to connect
|
||||
Connected = "connected", // media is flowing
|
||||
|
@ -44,7 +44,7 @@ export interface ParticipantInfo {
|
|||
presenter: boolean;
|
||||
}
|
||||
|
||||
export interface UseGroupCallReturnType {
|
||||
interface UseGroupCallReturnType {
|
||||
state: GroupCallState;
|
||||
localCallFeed: CallFeed;
|
||||
activeSpeaker: CallFeed | null;
|
||||
|
|
|
@ -32,7 +32,7 @@ import { isLocalRoomId, createRoom, roomNameFromRoomId } from "../matrix-utils";
|
|||
import { translatedError } from "../TranslatedError";
|
||||
import { widget } from "../widget";
|
||||
|
||||
export interface GroupCallLoadState {
|
||||
interface GroupCallLoadState {
|
||||
loading: boolean;
|
||||
error?: Error;
|
||||
groupCall?: GroupCall;
|
||||
|
|
|
@ -1,290 +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 { useCallback, useEffect, useState } from "react";
|
||||
import { MatrixClient, ClientEvent } from "matrix-js-sdk/src/client";
|
||||
import { GroupCall } from "matrix-js-sdk/src/webrtc/groupCall";
|
||||
import { CallFeed, CallFeedEvent } from "matrix-js-sdk/src/webrtc/callFeed";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { SyncState } from "matrix-js-sdk/src/sync";
|
||||
|
||||
import { PlayClipFunction, PTTClipID } from "../sound/usePttSounds";
|
||||
|
||||
// Works out who the active speaker should be given what feeds are active and
|
||||
// the power level of each user.
|
||||
function getActiveSpeakerFeed(
|
||||
feeds: CallFeed[],
|
||||
groupCall: GroupCall
|
||||
): CallFeed | null {
|
||||
const activeSpeakerFeeds = feeds.filter((f) => !f.isAudioMuted());
|
||||
|
||||
// make sure the feeds are in a deterministic order so every client picks
|
||||
// the same one as the active speaker. The custom sort function sorts
|
||||
// by user ID, so needs a collator of some kind to compare. We make a
|
||||
// specific one to help ensure every client sorts the same way
|
||||
// although of course user IDs shouldn't contain accented characters etc.
|
||||
// anyway).
|
||||
const collator = new Intl.Collator("en", {
|
||||
sensitivity: "variant",
|
||||
usage: "sort",
|
||||
ignorePunctuation: false,
|
||||
});
|
||||
activeSpeakerFeeds.sort((a: CallFeed, b: CallFeed): number =>
|
||||
collator.compare(a.userId, b.userId)
|
||||
);
|
||||
|
||||
let activeSpeakerFeed = null;
|
||||
let highestPowerLevel = null;
|
||||
for (const feed of activeSpeakerFeeds) {
|
||||
const member = groupCall.room.getMember(feed.userId);
|
||||
if (highestPowerLevel === null || member.powerLevel > highestPowerLevel) {
|
||||
highestPowerLevel = member.powerLevel;
|
||||
activeSpeakerFeed = feed;
|
||||
}
|
||||
}
|
||||
|
||||
return activeSpeakerFeed;
|
||||
}
|
||||
|
||||
export interface PTTState {
|
||||
pttButtonHeld: boolean;
|
||||
isAdmin: boolean;
|
||||
talkOverEnabled: boolean;
|
||||
setTalkOverEnabled: (boolean) => void;
|
||||
activeSpeakerUserId: string;
|
||||
activeSpeakerVolume: number;
|
||||
startTalking: () => void;
|
||||
stopTalking: () => void;
|
||||
transmitBlocked: boolean;
|
||||
// connected is actually an indication of whether we're connected to the HS
|
||||
// (ie. the client's syncing state) rather than media connection, since
|
||||
// it's peer to peer so we can't really say which peer is 'disconnected' if
|
||||
// there's only one other person in the call and they've lost Internet.
|
||||
connected: boolean;
|
||||
}
|
||||
|
||||
export const usePTT = (
|
||||
client: MatrixClient,
|
||||
groupCall: GroupCall,
|
||||
userMediaFeeds: CallFeed[],
|
||||
playClip: PlayClipFunction
|
||||
): PTTState => {
|
||||
// Used to serialise all the mute calls so they don't race. It has
|
||||
// its own state as its always set separately from anything else.
|
||||
const [mutePromise, setMutePromise] = useState(
|
||||
Promise.resolve<boolean | void>(false)
|
||||
);
|
||||
|
||||
// Wrapper to serialise all the mute operations on the promise
|
||||
const setMicMuteWrapper = useCallback(
|
||||
(muted: boolean) => {
|
||||
setMutePromise(
|
||||
mutePromise.then(() => {
|
||||
return groupCall.setMicrophoneMuted(muted).catch((e) => {
|
||||
logger.error("Failed to unmute microphone", e);
|
||||
});
|
||||
})
|
||||
);
|
||||
},
|
||||
[groupCall, mutePromise]
|
||||
);
|
||||
|
||||
const [
|
||||
{
|
||||
pttButtonHeld,
|
||||
isAdmin,
|
||||
talkOverEnabled,
|
||||
activeSpeakerUserId,
|
||||
activeSpeakerVolume,
|
||||
transmitBlocked,
|
||||
},
|
||||
setState,
|
||||
] = useState(() => {
|
||||
// slightly concerningly, this can end up null as we seem to sometimes get
|
||||
// here before the room state contains our own member event
|
||||
const roomMember = groupCall.room.getMember(client.getUserId());
|
||||
|
||||
const activeSpeakerFeed = getActiveSpeakerFeed(userMediaFeeds, groupCall);
|
||||
|
||||
return {
|
||||
isAdmin: roomMember ? roomMember.powerLevel >= 100 : false,
|
||||
talkOverEnabled: false,
|
||||
pttButtonHeld: false,
|
||||
activeSpeakerUserId: activeSpeakerFeed ? activeSpeakerFeed.userId : null,
|
||||
activeSpeakerVolume: -Infinity,
|
||||
transmitBlocked: false,
|
||||
};
|
||||
});
|
||||
|
||||
const onMuteStateChanged = useCallback(() => {
|
||||
const activeSpeakerFeed = getActiveSpeakerFeed(userMediaFeeds, groupCall);
|
||||
|
||||
let blocked = transmitBlocked;
|
||||
if (activeSpeakerUserId === null && activeSpeakerFeed !== null) {
|
||||
if (activeSpeakerFeed.userId === client.getUserId()) {
|
||||
playClip(PTTClipID.START_TALKING_LOCAL);
|
||||
} else {
|
||||
playClip(PTTClipID.START_TALKING_REMOTE);
|
||||
}
|
||||
} else if (activeSpeakerUserId !== null && activeSpeakerFeed === null) {
|
||||
playClip(PTTClipID.END_TALKING);
|
||||
} else if (
|
||||
pttButtonHeld &&
|
||||
activeSpeakerFeed?.userId !== client.getUserId() &&
|
||||
!transmitBlocked
|
||||
) {
|
||||
// We were talking but we've been cut off: mute our own mic
|
||||
// (this is the easier way of cutting other speakers off if an
|
||||
// admin barges in: we could also mute the non-admin speaker
|
||||
// on all receivers, but we'd have to make sure we unmuted them
|
||||
// correctly.)
|
||||
setMicMuteWrapper(true);
|
||||
blocked = true;
|
||||
playClip(PTTClipID.BLOCKED);
|
||||
}
|
||||
|
||||
setState((prevState) => ({
|
||||
...prevState,
|
||||
activeSpeakerUserId: activeSpeakerFeed ? activeSpeakerFeed.userId : null,
|
||||
transmitBlocked: blocked,
|
||||
}));
|
||||
}, [
|
||||
playClip,
|
||||
groupCall,
|
||||
pttButtonHeld,
|
||||
activeSpeakerUserId,
|
||||
client,
|
||||
userMediaFeeds,
|
||||
setMicMuteWrapper,
|
||||
transmitBlocked,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
for (const callFeed of userMediaFeeds) {
|
||||
callFeed.on(CallFeedEvent.MuteStateChanged, onMuteStateChanged);
|
||||
}
|
||||
|
||||
const activeSpeakerFeed = getActiveSpeakerFeed(userMediaFeeds, groupCall);
|
||||
|
||||
setState((prevState) => ({
|
||||
...prevState,
|
||||
activeSpeakerUserId: activeSpeakerFeed ? activeSpeakerFeed.userId : null,
|
||||
}));
|
||||
|
||||
return () => {
|
||||
for (const callFeed of userMediaFeeds) {
|
||||
callFeed.off(CallFeedEvent.MuteStateChanged, onMuteStateChanged);
|
||||
}
|
||||
};
|
||||
}, [userMediaFeeds, onMuteStateChanged, groupCall]);
|
||||
|
||||
const onVolumeChanged = useCallback((volume: number) => {
|
||||
setState((prevState) => ({
|
||||
...prevState,
|
||||
activeSpeakerVolume: volume,
|
||||
}));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const activeSpeakerFeed = getActiveSpeakerFeed(userMediaFeeds, groupCall);
|
||||
activeSpeakerFeed?.on(CallFeedEvent.VolumeChanged, onVolumeChanged);
|
||||
return () => {
|
||||
activeSpeakerFeed?.off(CallFeedEvent.VolumeChanged, onVolumeChanged);
|
||||
setState((prevState) => ({
|
||||
...prevState,
|
||||
activeSpeakerVolume: -Infinity,
|
||||
}));
|
||||
};
|
||||
}, [activeSpeakerUserId, onVolumeChanged, userMediaFeeds, groupCall]);
|
||||
|
||||
const startTalking = useCallback(async () => {
|
||||
if (pttButtonHeld) return;
|
||||
|
||||
let blocked = false;
|
||||
if (activeSpeakerUserId && !(isAdmin && talkOverEnabled)) {
|
||||
playClip(PTTClipID.BLOCKED);
|
||||
blocked = true;
|
||||
}
|
||||
// setstate before doing the async call to mute / unmute the mic
|
||||
setState((prevState) => ({
|
||||
...prevState,
|
||||
pttButtonHeld: true,
|
||||
transmitBlocked: blocked,
|
||||
}));
|
||||
|
||||
if (!blocked && groupCall.isMicrophoneMuted()) {
|
||||
setMicMuteWrapper(false);
|
||||
}
|
||||
}, [
|
||||
pttButtonHeld,
|
||||
groupCall,
|
||||
activeSpeakerUserId,
|
||||
isAdmin,
|
||||
talkOverEnabled,
|
||||
setState,
|
||||
playClip,
|
||||
setMicMuteWrapper,
|
||||
]);
|
||||
|
||||
const stopTalking = useCallback(async () => {
|
||||
setState((prevState) => ({
|
||||
...prevState,
|
||||
pttButtonHeld: false,
|
||||
transmitBlocked: false,
|
||||
}));
|
||||
|
||||
setMicMuteWrapper(true);
|
||||
}, [setMicMuteWrapper]);
|
||||
|
||||
// separate state for connected: we set it separately from other things
|
||||
// in the client sync callback
|
||||
const [connected, setConnected] = useState(true);
|
||||
|
||||
const onClientSync = useCallback(
|
||||
(syncState: SyncState) => {
|
||||
setConnected(syncState !== SyncState.Error);
|
||||
},
|
||||
[setConnected]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
client.on(ClientEvent.Sync, onClientSync);
|
||||
|
||||
return () => {
|
||||
client.removeListener(ClientEvent.Sync, onClientSync);
|
||||
};
|
||||
}, [client, onClientSync]);
|
||||
|
||||
const setTalkOverEnabled = useCallback((talkOverEnabled) => {
|
||||
setState((prevState) => ({
|
||||
...prevState,
|
||||
talkOverEnabled,
|
||||
}));
|
||||
}, []);
|
||||
|
||||
return {
|
||||
pttButtonHeld,
|
||||
isAdmin,
|
||||
talkOverEnabled,
|
||||
setTalkOverEnabled,
|
||||
activeSpeakerUserId,
|
||||
activeSpeakerVolume,
|
||||
startTalking,
|
||||
stopTalking,
|
||||
transmitBlocked,
|
||||
connected,
|
||||
};
|
||||
};
|
|
@ -1,26 +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 { useCallback } from "react";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
|
||||
import { useRoomState } from "./useRoomState";
|
||||
|
||||
export const useRoomAvatar = (room: Room) =>
|
||||
useRoomState(
|
||||
room,
|
||||
useCallback(() => room.getMxcAvatarUrl(), [room])
|
||||
);
|
|
@ -67,7 +67,7 @@ interface LogEntry {
|
|||
index?: number;
|
||||
}
|
||||
|
||||
export class ConsoleLogger extends EventEmitter {
|
||||
class ConsoleLogger extends EventEmitter {
|
||||
private logs = "";
|
||||
private originalFunctions: { [key in LogFunctionName]?: LogFunction } = {};
|
||||
|
||||
|
@ -145,7 +145,7 @@ export class ConsoleLogger extends EventEmitter {
|
|||
}
|
||||
|
||||
// A class which stores log lines in an IndexedDB instance.
|
||||
export class IndexedDBLogStore {
|
||||
class IndexedDBLogStore {
|
||||
private index = 0;
|
||||
private db: IDBDatabase = null;
|
||||
private flushPromise: Promise<void> = null;
|
||||
|
@ -511,7 +511,7 @@ export function init(): Promise<void> {
|
|||
* then this no-ops.
|
||||
* @return {Promise} Resolves when complete.
|
||||
*/
|
||||
export function tryInitStorage(): Promise<void> {
|
||||
function tryInitStorage(): Promise<void> {
|
||||
if (global.mx_rage_initStoragePromise) {
|
||||
return global.mx_rage_initStoragePromise;
|
||||
}
|
||||
|
@ -537,24 +537,6 @@ export function tryInitStorage(): Promise<void> {
|
|||
return global.mx_rage_initStoragePromise;
|
||||
}
|
||||
|
||||
export function flush(): Promise<void> {
|
||||
if (!global.mx_rage_store) {
|
||||
return;
|
||||
}
|
||||
global.mx_rage_store.flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up old logs.
|
||||
* @return {Promise} Resolves if cleaned logs.
|
||||
*/
|
||||
export async function cleanup(): Promise<void> {
|
||||
if (!global.mx_rage_store) {
|
||||
return;
|
||||
}
|
||||
await global.mx_rage_store.consume();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a recent snapshot of the logs, ready for attaching to a bug report
|
||||
*
|
||||
|
|
|
@ -1,19 +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.
|
||||
*/
|
||||
|
||||
.pttClip {
|
||||
display: none;
|
||||
}
|
|
@ -1,70 +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 React from "react";
|
||||
|
||||
import startTalkLocalOggUrl from "./start_talk_local.ogg";
|
||||
import startTalkLocalMp3Url from "./start_talk_local.mp3";
|
||||
import startTalkRemoteOggUrl from "./start_talk_remote.ogg";
|
||||
import startTalkRemoteMp3Url from "./start_talk_remote.mp3";
|
||||
import endTalkOggUrl from "./end_talk.ogg";
|
||||
import endTalkMp3Url from "./end_talk.mp3";
|
||||
import blockedOggUrl from "./blocked.ogg";
|
||||
import blockedMp3Url from "./blocked.mp3";
|
||||
import styles from "./PTTClips.module.css";
|
||||
|
||||
interface Props {
|
||||
startTalkingLocalRef: React.RefObject<HTMLAudioElement>;
|
||||
startTalkingRemoteRef: React.RefObject<HTMLAudioElement>;
|
||||
endTalkingRef: React.RefObject<HTMLAudioElement>;
|
||||
blockedRef: React.RefObject<HTMLAudioElement>;
|
||||
}
|
||||
|
||||
export const PTTClips: React.FC<Props> = ({
|
||||
startTalkingLocalRef,
|
||||
startTalkingRemoteRef,
|
||||
endTalkingRef,
|
||||
blockedRef,
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<audio
|
||||
preload="auto"
|
||||
className={styles.pttClip}
|
||||
ref={startTalkingLocalRef}
|
||||
>
|
||||
<source type="audio/ogg" src={startTalkLocalOggUrl} />
|
||||
<source type="audio/mpeg" src={startTalkLocalMp3Url} />
|
||||
</audio>
|
||||
<audio
|
||||
preload="auto"
|
||||
className={styles.pttClip}
|
||||
ref={startTalkingRemoteRef}
|
||||
>
|
||||
<source type="audio/ogg" src={startTalkRemoteOggUrl} />
|
||||
<source type="audio/mpeg" src={startTalkRemoteMp3Url} />
|
||||
</audio>
|
||||
<audio preload="auto" className={styles.pttClip} ref={endTalkingRef}>
|
||||
<source type="audio/ogg" src={endTalkOggUrl} />
|
||||
<source type="audio/mpeg" src={endTalkMp3Url} />
|
||||
</audio>
|
||||
<audio preload="auto" className={styles.pttClip} ref={blockedRef}>
|
||||
<source type="audio/ogg" src={blockedOggUrl} />
|
||||
<source type="audio/mpeg" src={blockedMp3Url} />
|
||||
</audio>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -1,81 +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 React, { useCallback, useState } from "react";
|
||||
|
||||
export enum PTTClipID {
|
||||
START_TALKING_LOCAL,
|
||||
START_TALKING_REMOTE,
|
||||
END_TALKING,
|
||||
BLOCKED,
|
||||
}
|
||||
|
||||
export type PlayClipFunction = (clipID: PTTClipID) => void;
|
||||
|
||||
interface PTTSounds {
|
||||
startTalkingLocalRef: React.RefObject<HTMLAudioElement>;
|
||||
startTalkingRemoteRef: React.RefObject<HTMLAudioElement>;
|
||||
endTalkingRef: React.RefObject<HTMLAudioElement>;
|
||||
blockedRef: React.RefObject<HTMLAudioElement>;
|
||||
playClip: PlayClipFunction;
|
||||
}
|
||||
|
||||
export const usePTTSounds = (): PTTSounds => {
|
||||
const [startTalkingLocalRef] = useState(React.createRef<HTMLAudioElement>());
|
||||
const [startTalkingRemoteRef] = useState(React.createRef<HTMLAudioElement>());
|
||||
const [endTalkingRef] = useState(React.createRef<HTMLAudioElement>());
|
||||
const [blockedRef] = useState(React.createRef<HTMLAudioElement>());
|
||||
|
||||
const playClip = useCallback(
|
||||
async (clipID: PTTClipID) => {
|
||||
let ref: React.RefObject<HTMLAudioElement>;
|
||||
|
||||
switch (clipID) {
|
||||
case PTTClipID.START_TALKING_LOCAL:
|
||||
ref = startTalkingLocalRef;
|
||||
break;
|
||||
case PTTClipID.START_TALKING_REMOTE:
|
||||
ref = startTalkingRemoteRef;
|
||||
break;
|
||||
case PTTClipID.END_TALKING:
|
||||
ref = endTalkingRef;
|
||||
break;
|
||||
case PTTClipID.BLOCKED:
|
||||
ref = blockedRef;
|
||||
break;
|
||||
}
|
||||
if (ref.current) {
|
||||
try {
|
||||
ref.current.currentTime = 0;
|
||||
await ref.current.play();
|
||||
} catch (e) {
|
||||
console.log("Couldn't play sound effect", e);
|
||||
}
|
||||
} else {
|
||||
console.log("No media element found");
|
||||
}
|
||||
},
|
||||
[startTalkingLocalRef, startTalkingRemoteRef, endTalkingRef, blockedRef]
|
||||
);
|
||||
|
||||
return {
|
||||
startTalkingLocalRef,
|
||||
startTalkingRemoteRef,
|
||||
endTalkingRef,
|
||||
blockedRef,
|
||||
playClip,
|
||||
};
|
||||
};
|
|
@ -1,49 +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 { useState, useRef, useCallback } from "react";
|
||||
|
||||
// Like useState, except state updates can be enqueued with a configurable delay
|
||||
export const useDelayedState = <T>(
|
||||
initial?: T
|
||||
): [T, (value: T, delay: number) => void, (value: T) => void] => {
|
||||
const [state, setState] = useState<T>(initial);
|
||||
const timers = useRef<Set<ReturnType<typeof setTimeout>>>();
|
||||
if (!timers.current) timers.current = new Set();
|
||||
|
||||
const setStateDelayed = useCallback(
|
||||
(value: T, delay: number) => {
|
||||
const timer = setTimeout(() => {
|
||||
setState(value);
|
||||
timers.current.delete(timer);
|
||||
}, delay);
|
||||
timers.current.add(timer);
|
||||
},
|
||||
[setState, timers]
|
||||
);
|
||||
const setStateImmediate = useCallback(
|
||||
(value: T) => {
|
||||
// Clear all updates currently in the queue
|
||||
for (const timer of timers.current) clearTimeout(timer);
|
||||
timers.current.clear();
|
||||
|
||||
setState(value);
|
||||
},
|
||||
[setState, timers]
|
||||
);
|
||||
|
||||
return [state, setStateDelayed, setStateImmediate];
|
||||
};
|
|
@ -26,7 +26,7 @@ export default {
|
|||
},
|
||||
};
|
||||
|
||||
export const ParticipantsTest = () => {
|
||||
const ParticipantsTest = () => {
|
||||
const { layout, setLayout } = useVideoGridLayout(false);
|
||||
const [participantCount, setParticipantCount] = useState(1);
|
||||
|
||||
|
|
|
@ -47,11 +47,6 @@ export enum ElementWidgetActions {
|
|||
ScreenshareStop = "io.element.screenshare_stop",
|
||||
}
|
||||
|
||||
export interface JoinCallData {
|
||||
audioInput: string | null;
|
||||
videoInput: string | null;
|
||||
}
|
||||
|
||||
export interface ScreenshareStartData {
|
||||
desktopCapturerSourceId: string;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue