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 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 { 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 { Room } from "matrix-js-sdk/src/models/room";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
@ -30,7 +28,6 @@ import { ReactComponent as VideoIcon } from "./icons/Video.svg";
|
||||||
import { Subtitle } from "./typography/Typography";
|
import { Subtitle } from "./typography/Typography";
|
||||||
import { Avatar, Size } from "./Avatar";
|
import { Avatar, Size } from "./Avatar";
|
||||||
import { IncompatibleVersionModal } from "./IncompatibleVersionModal";
|
import { IncompatibleVersionModal } from "./IncompatibleVersionModal";
|
||||||
import { ReactComponent as ArrowLeftIcon } from "./icons/ArrowLeft.svg";
|
|
||||||
|
|
||||||
interface HeaderProps extends HTMLAttributes<HTMLElement> {
|
interface HeaderProps extends HTMLAttributes<HTMLElement> {
|
||||||
children: ReactNode;
|
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 {
|
interface VersionMismatchWarningProps {
|
||||||
users: Set<string>;
|
users: Set<string>;
|
||||||
room: Room;
|
room: Room;
|
||||||
|
|
|
@ -30,7 +30,7 @@ import {
|
||||||
} from "@react-stately/overlays";
|
} from "@react-stately/overlays";
|
||||||
import { useDialog } from "@react-aria/dialog";
|
import { useDialog } from "@react-aria/dialog";
|
||||||
import { FocusScope } from "@react-aria/focus";
|
import { FocusScope } from "@react-aria/focus";
|
||||||
import { ButtonAria, useButton } from "@react-aria/button";
|
import { useButton } from "@react-aria/button";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { AriaDialogProps } from "@react-types/dialog";
|
import { AriaDialogProps } from "@react-types/dialog";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
@ -133,72 +133,3 @@ export function useModalTriggerState(): {
|
||||||
);
|
);
|
||||||
return { modalState, modalProps };
|
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;
|
$set_once?: void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum Anonymity {
|
enum Anonymity {
|
||||||
Disabled,
|
Disabled,
|
||||||
Anonymous,
|
Anonymous,
|
||||||
Pseudonymous,
|
Pseudonymous,
|
||||||
|
|
|
@ -40,7 +40,7 @@ interface TooltipProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Tooltip = forwardRef<HTMLDivElement, TooltipProps>(
|
const Tooltip = forwardRef<HTMLDivElement, TooltipProps>(
|
||||||
(
|
(
|
||||||
{ state, className, children, ...rest }: TooltipProps,
|
{ state, className, children, ...rest }: TooltipProps,
|
||||||
ref: ForwardedRef<HTMLDivElement>
|
ref: ForwardedRef<HTMLDivElement>
|
||||||
|
|
|
@ -17,7 +17,7 @@ limitations under the License.
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { useLocation } from "react-router-dom";
|
import { useLocation } from "react-router-dom";
|
||||||
|
|
||||||
export interface UrlParams {
|
interface UrlParams {
|
||||||
roomAlias: string | null;
|
roomAlias: string | null;
|
||||||
roomId: string | null;
|
roomId: string | null;
|
||||||
viaServers: string[];
|
viaServers: string[];
|
||||||
|
|
|
@ -22,7 +22,7 @@ import { useInteractiveRegistration } from "../auth/useInteractiveRegistration";
|
||||||
import { generateRandomName } from "../auth/generateRandomName";
|
import { generateRandomName } from "../auth/generateRandomName";
|
||||||
import { useRecaptcha } from "../auth/useRecaptcha";
|
import { useRecaptcha } from "../auth/useRecaptcha";
|
||||||
|
|
||||||
export interface UseRegisterPasswordlessUserType {
|
interface UseRegisterPasswordlessUserType {
|
||||||
privacyPolicyUrl: string;
|
privacyPolicyUrl: string;
|
||||||
registerPasswordlessUser: (displayName: string) => Promise<void>;
|
registerPasswordlessUser: (displayName: string) => Promise<void>;
|
||||||
recaptchaId: string;
|
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 DisableVideoIcon } from "../icons/DisableVideo.svg";
|
||||||
import { ReactComponent as HangupIcon } from "../icons/Hangup.svg";
|
import { ReactComponent as HangupIcon } from "../icons/Hangup.svg";
|
||||||
import { ReactComponent as ScreenshareIcon } from "../icons/Screenshare.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 { ReactComponent as ArrowDownIcon } from "../icons/ArrowDown.svg";
|
||||||
import { TooltipTrigger } from "../Tooltip";
|
import { TooltipTrigger } from "../Tooltip";
|
||||||
|
|
||||||
|
@ -220,43 +218,3 @@ export function HangupButton({
|
||||||
</TooltipTrigger>
|
</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;
|
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>;
|
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();
|
.toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fullAliasFromRoomName(
|
function fullAliasFromRoomName(roomName: string, client: MatrixClient): string {
|
||||||
roomName: string,
|
|
||||||
client: MatrixClient
|
|
||||||
): string {
|
|
||||||
return `#${roomAliasLocalpartFromRoomName(roomName)}:${client.getDomain()}`;
|
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 { TranslatedError, translatedError } from "../TranslatedError";
|
||||||
import { ElementWidgetActions, ScreenshareStartData, widget } from "../widget";
|
import { ElementWidgetActions, ScreenshareStartData, widget } from "../widget";
|
||||||
|
|
||||||
export enum ConnectionState {
|
enum ConnectionState {
|
||||||
EstablishingCall = "establishing call", // call hasn't been established yet
|
EstablishingCall = "establishing call", // call hasn't been established yet
|
||||||
WaitMedia = "wait_media", // call is set up, waiting for ICE to connect
|
WaitMedia = "wait_media", // call is set up, waiting for ICE to connect
|
||||||
Connected = "connected", // media is flowing
|
Connected = "connected", // media is flowing
|
||||||
|
@ -44,7 +44,7 @@ export interface ParticipantInfo {
|
||||||
presenter: boolean;
|
presenter: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UseGroupCallReturnType {
|
interface UseGroupCallReturnType {
|
||||||
state: GroupCallState;
|
state: GroupCallState;
|
||||||
localCallFeed: CallFeed;
|
localCallFeed: CallFeed;
|
||||||
activeSpeaker: CallFeed | null;
|
activeSpeaker: CallFeed | null;
|
||||||
|
|
|
@ -32,7 +32,7 @@ import { isLocalRoomId, createRoom, roomNameFromRoomId } from "../matrix-utils";
|
||||||
import { translatedError } from "../TranslatedError";
|
import { translatedError } from "../TranslatedError";
|
||||||
import { widget } from "../widget";
|
import { widget } from "../widget";
|
||||||
|
|
||||||
export interface GroupCallLoadState {
|
interface GroupCallLoadState {
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
error?: Error;
|
error?: Error;
|
||||||
groupCall?: GroupCall;
|
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;
|
index?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ConsoleLogger extends EventEmitter {
|
class ConsoleLogger extends EventEmitter {
|
||||||
private logs = "";
|
private logs = "";
|
||||||
private originalFunctions: { [key in LogFunctionName]?: LogFunction } = {};
|
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.
|
// A class which stores log lines in an IndexedDB instance.
|
||||||
export class IndexedDBLogStore {
|
class IndexedDBLogStore {
|
||||||
private index = 0;
|
private index = 0;
|
||||||
private db: IDBDatabase = null;
|
private db: IDBDatabase = null;
|
||||||
private flushPromise: Promise<void> = null;
|
private flushPromise: Promise<void> = null;
|
||||||
|
@ -511,7 +511,7 @@ export function init(): Promise<void> {
|
||||||
* then this no-ops.
|
* then this no-ops.
|
||||||
* @return {Promise} Resolves when complete.
|
* @return {Promise} Resolves when complete.
|
||||||
*/
|
*/
|
||||||
export function tryInitStorage(): Promise<void> {
|
function tryInitStorage(): Promise<void> {
|
||||||
if (global.mx_rage_initStoragePromise) {
|
if (global.mx_rage_initStoragePromise) {
|
||||||
return global.mx_rage_initStoragePromise;
|
return global.mx_rage_initStoragePromise;
|
||||||
}
|
}
|
||||||
|
@ -537,24 +537,6 @@ export function tryInitStorage(): Promise<void> {
|
||||||
return global.mx_rage_initStoragePromise;
|
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
|
* 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 { layout, setLayout } = useVideoGridLayout(false);
|
||||||
const [participantCount, setParticipantCount] = useState(1);
|
const [participantCount, setParticipantCount] = useState(1);
|
||||||
|
|
||||||
|
|
|
@ -47,11 +47,6 @@ export enum ElementWidgetActions {
|
||||||
ScreenshareStop = "io.element.screenshare_stop",
|
ScreenshareStop = "io.element.screenshare_stop",
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface JoinCallData {
|
|
||||||
audioInput: string | null;
|
|
||||||
videoInput: string | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ScreenshareStartData {
|
export interface ScreenshareStartData {
|
||||||
desktopCapturerSourceId: string;
|
desktopCapturerSourceId: string;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue