mirror of
https://github.com/vector-im/element-call.git
synced 2024-11-24 00:38:31 +08:00
Upgrade eslint-plugin-matrix-org to 1.2.1
This upgrade came with a number of new lints that needed to be fixed across the code base. Primarily: explicit return types on functions, and explicit visibility modifiers on class members.
This commit is contained in:
parent
444a37224b
commit
a7624806b2
@ -1,13 +1,31 @@
|
|||||||
|
const COPYRIGHT_HEADER = `/*
|
||||||
|
Copyright %%CURRENT_YEAR%% New Vector Ltd
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
`;
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
plugins: ["matrix-org"],
|
plugins: ["matrix-org"],
|
||||||
extends: [
|
extends: [
|
||||||
"prettier",
|
|
||||||
"plugin:matrix-org/react",
|
"plugin:matrix-org/react",
|
||||||
"plugin:matrix-org/a11y",
|
"plugin:matrix-org/a11y",
|
||||||
"plugin:matrix-org/typescript",
|
"plugin:matrix-org/typescript",
|
||||||
|
"prettier",
|
||||||
],
|
],
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
ecmaVersion: 2018,
|
ecmaVersion: "latest",
|
||||||
sourceType: "module",
|
sourceType: "module",
|
||||||
project: ["./tsconfig.json"],
|
project: ["./tsconfig.json"],
|
||||||
},
|
},
|
||||||
@ -15,27 +33,11 @@ module.exports = {
|
|||||||
browser: true,
|
browser: true,
|
||||||
node: true,
|
node: true,
|
||||||
},
|
},
|
||||||
parserOptions: {
|
|
||||||
ecmaVersion: "latest",
|
|
||||||
sourceType: "module",
|
|
||||||
},
|
|
||||||
rules: {
|
rules: {
|
||||||
"jsx-a11y/media-has-caption": ["off"],
|
"matrix-org/require-copyright-header": ["error", COPYRIGHT_HEADER],
|
||||||
|
"jsx-a11y/media-has-caption": "off",
|
||||||
|
"deprecate/import": "off", // Disabled because it crashes the linter
|
||||||
},
|
},
|
||||||
overrides: [
|
|
||||||
{
|
|
||||||
files: ["src/**/*.{ts,tsx}", "test/**/*.{ts,tsx}"],
|
|
||||||
extends: [
|
|
||||||
"plugin:matrix-org/typescript",
|
|
||||||
"plugin:matrix-org/react",
|
|
||||||
"prettier",
|
|
||||||
],
|
|
||||||
rules: {
|
|
||||||
// We're aiming to convert this code to strict mode
|
|
||||||
"@typescript-eslint/no-non-null-assertion": "off",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
settings: {
|
settings: {
|
||||||
react: {
|
react: {
|
||||||
version: "detect",
|
version: "detect",
|
||||||
|
@ -104,11 +104,13 @@
|
|||||||
"eslint": "^8.14.0",
|
"eslint": "^8.14.0",
|
||||||
"eslint-config-google": "^0.14.0",
|
"eslint-config-google": "^0.14.0",
|
||||||
"eslint-config-prettier": "^9.0.0",
|
"eslint-config-prettier": "^9.0.0",
|
||||||
|
"eslint-plugin-deprecate": "^0.8.2",
|
||||||
"eslint-plugin-import": "^2.26.0",
|
"eslint-plugin-import": "^2.26.0",
|
||||||
"eslint-plugin-jsx-a11y": "^6.5.1",
|
"eslint-plugin-jsx-a11y": "^6.5.1",
|
||||||
"eslint-plugin-matrix-org": "^0.4.0",
|
"eslint-plugin-matrix-org": "^1.2.1",
|
||||||
"eslint-plugin-react": "^7.29.4",
|
"eslint-plugin-react": "^7.29.4",
|
||||||
"eslint-plugin-react-hooks": "^4.5.0",
|
"eslint-plugin-react-hooks": "^4.5.0",
|
||||||
|
"eslint-plugin-unicorn": "^48.0.1",
|
||||||
"i18next-parser": "^6.6.0",
|
"i18next-parser": "^6.6.0",
|
||||||
"identity-obj-proxy": "^3.0.0",
|
"identity-obj-proxy": "^3.0.0",
|
||||||
"jest": "^29.2.2",
|
"jest": "^29.2.2",
|
||||||
@ -118,6 +120,7 @@
|
|||||||
"sass": "^1.42.1",
|
"sass": "^1.42.1",
|
||||||
"storybook-builder-vite": "^0.1.12",
|
"storybook-builder-vite": "^0.1.12",
|
||||||
"typescript": "^5.1.6",
|
"typescript": "^5.1.6",
|
||||||
|
"typescript-eslint-language-service": "^5.0.5",
|
||||||
"vite": "^4.2.0",
|
"vite": "^4.2.0",
|
||||||
"vite-plugin-html-template": "^1.1.0",
|
"vite-plugin-html-template": "^1.1.0",
|
||||||
"vite-plugin-svgr": "^3.2.0"
|
"vite-plugin-svgr": "^3.2.0"
|
||||||
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Suspense, useEffect, useState } from "react";
|
import { FC, Suspense, useEffect, useState } from "react";
|
||||||
import {
|
import {
|
||||||
BrowserRouter as Router,
|
BrowserRouter as Router,
|
||||||
Switch,
|
Switch,
|
||||||
@ -43,7 +43,7 @@ interface BackgroundProviderProps {
|
|||||||
children: JSX.Element;
|
children: JSX.Element;
|
||||||
}
|
}
|
||||||
|
|
||||||
const BackgroundProvider = ({ children }: BackgroundProviderProps) => {
|
const BackgroundProvider: FC<BackgroundProviderProps> = ({ children }) => {
|
||||||
const { pathname } = useLocation();
|
const { pathname } = useLocation();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -63,7 +63,7 @@ interface AppProps {
|
|||||||
history: History;
|
history: History;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function App({ history }: AppProps) {
|
export const App: FC<AppProps> = ({ history }) => {
|
||||||
const [loaded, setLoaded] = useState(false);
|
const [loaded, setLoaded] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -116,4 +116,4 @@ export default function App({ history }: AppProps) {
|
|||||||
</BackgroundProvider>
|
</BackgroundProvider>
|
||||||
</Router>
|
</Router>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ReactNode } from "react";
|
import { FC, ReactNode } from "react";
|
||||||
|
|
||||||
import styles from "./Banner.module.css";
|
import styles from "./Banner.module.css";
|
||||||
|
|
||||||
@ -22,6 +22,6 @@ interface Props {
|
|||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Banner = ({ children }: Props) => {
|
export const Banner: FC<Props> = ({ children }) => {
|
||||||
return <div className={styles.banner}>{children}</div>;
|
return <div className={styles.banner}>{children}</div>;
|
||||||
};
|
};
|
||||||
|
@ -82,7 +82,8 @@ export type SetClientParams = {
|
|||||||
|
|
||||||
const ClientContext = createContext<ClientState | undefined>(undefined);
|
const ClientContext = createContext<ClientState | undefined>(undefined);
|
||||||
|
|
||||||
export const useClientState = () => useContext(ClientContext);
|
export const useClientState = (): ClientState | undefined =>
|
||||||
|
useContext(ClientContext);
|
||||||
|
|
||||||
export function useClient(): {
|
export function useClient(): {
|
||||||
client?: MatrixClient;
|
client?: MatrixClient;
|
||||||
@ -408,8 +409,8 @@ export interface Session {
|
|||||||
tempPassword?: string;
|
tempPassword?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const clearSession = () => localStorage.removeItem("matrix-auth-store");
|
const clearSession = (): void => localStorage.removeItem("matrix-auth-store");
|
||||||
const saveSession = (s: Session) =>
|
const saveSession = (s: Session): void =>
|
||||||
localStorage.setItem("matrix-auth-store", JSON.stringify(s));
|
localStorage.setItem("matrix-auth-store", JSON.stringify(s));
|
||||||
const loadSession = (): Session | undefined => {
|
const loadSession = (): Session | undefined => {
|
||||||
const data = localStorage.getItem("matrix-auth-store");
|
const data = localStorage.getItem("matrix-auth-store");
|
||||||
@ -423,4 +424,5 @@ const loadSession = (): Session | undefined => {
|
|||||||
const clientIsDisconnected = (
|
const clientIsDisconnected = (
|
||||||
syncState: SyncState,
|
syncState: SyncState,
|
||||||
syncData?: ISyncStateData
|
syncData?: ISyncStateData
|
||||||
) => syncState === "ERROR" && syncData?.error?.name === "ConnectionError";
|
): boolean =>
|
||||||
|
syncState === "ERROR" && syncData?.error?.name === "ConnectionError";
|
||||||
|
@ -15,22 +15,22 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { HTMLAttributes, ReactNode } from "react";
|
import { FC, HTMLAttributes, ReactNode } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import styles from "./DisconnectedBanner.module.css";
|
import styles from "./DisconnectedBanner.module.css";
|
||||||
import { ValidClientState, useClientState } from "./ClientContext";
|
import { ValidClientState, useClientState } from "./ClientContext";
|
||||||
|
|
||||||
interface DisconnectedBannerProps extends HTMLAttributes<HTMLElement> {
|
interface Props extends HTMLAttributes<HTMLElement> {
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DisconnectedBanner({
|
export const DisconnectedBanner: FC<Props> = ({
|
||||||
children,
|
children,
|
||||||
className,
|
className,
|
||||||
...rest
|
...rest
|
||||||
}: DisconnectedBannerProps) {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const clientState = useClientState();
|
const clientState = useClientState();
|
||||||
let shouldShowBanner = false;
|
let shouldShowBanner = false;
|
||||||
@ -50,4 +50,4 @@ export function DisconnectedBanner({
|
|||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
@ -15,13 +15,14 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Trans } from "react-i18next";
|
import { Trans } from "react-i18next";
|
||||||
|
import { FC } from "react";
|
||||||
|
|
||||||
import { Banner } from "./Banner";
|
import { Banner } from "./Banner";
|
||||||
import styles from "./E2EEBanner.module.css";
|
import styles from "./E2EEBanner.module.css";
|
||||||
import { ReactComponent as LockOffIcon } from "./icons/LockOff.svg";
|
import { ReactComponent as LockOffIcon } from "./icons/LockOff.svg";
|
||||||
import { useEnableE2EE } from "./settings/useSetting";
|
import { useEnableE2EE } from "./settings/useSetting";
|
||||||
|
|
||||||
export const E2EEBanner = () => {
|
export const E2EEBanner: FC = () => {
|
||||||
const [e2eeEnabled] = useEnableE2EE();
|
const [e2eeEnabled] = useEnableE2EE();
|
||||||
if (e2eeEnabled) return null;
|
if (e2eeEnabled) return null;
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { HTMLAttributes } from "react";
|
import { FC, HTMLAttributes } from "react";
|
||||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@ -30,14 +30,14 @@ interface Props extends HTMLAttributes<HTMLDivElement> {
|
|||||||
size?: Size | number;
|
size?: Size | number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Facepile({
|
export const Facepile: FC<Props> = ({
|
||||||
className,
|
className,
|
||||||
client,
|
client,
|
||||||
members,
|
members,
|
||||||
max = 3,
|
max = 3,
|
||||||
size = Size.XS,
|
size = Size.XS,
|
||||||
...rest
|
...rest
|
||||||
}: Props) {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const displayedMembers = members.slice(0, max);
|
const displayedMembers = members.slice(0, max);
|
||||||
@ -63,4 +63,4 @@ export function Facepile({
|
|||||||
})}
|
})}
|
||||||
</AvatarStack>
|
</AvatarStack>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ReactNode, useCallback, useEffect } from "react";
|
import { FC, ReactNode, useCallback, useEffect } from "react";
|
||||||
import { useLocation } from "react-router-dom";
|
import { useLocation } from "react-router-dom";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { Trans, useTranslation } from "react-i18next";
|
import { Trans, useTranslation } from "react-i18next";
|
||||||
@ -32,7 +32,10 @@ interface FullScreenViewProps {
|
|||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function FullScreenView({ className, children }: FullScreenViewProps) {
|
export const FullScreenView: FC<FullScreenViewProps> = ({
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className={classNames(styles.page, className)}>
|
<div className={classNames(styles.page, className)}>
|
||||||
<Header>
|
<Header>
|
||||||
@ -46,13 +49,13 @@ export function FullScreenView({ className, children }: FullScreenViewProps) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
interface ErrorViewProps {
|
interface ErrorViewProps {
|
||||||
error: Error;
|
error: Error;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ErrorView({ error }: ErrorViewProps) {
|
export const ErrorView: FC<ErrorViewProps> = ({ error }) => {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@ -95,9 +98,9 @@ export function ErrorView({ error }: ErrorViewProps) {
|
|||||||
)}
|
)}
|
||||||
</FullScreenView>
|
</FullScreenView>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export function CrashView() {
|
export const CrashView: FC = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const onReload = useCallback(() => {
|
const onReload = useCallback(() => {
|
||||||
@ -126,9 +129,9 @@ export function CrashView() {
|
|||||||
</Button>
|
</Button>
|
||||||
</FullScreenView>
|
</FullScreenView>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export function LoadingView() {
|
export const LoadingView: FC = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -136,4 +139,4 @@ export function LoadingView() {
|
|||||||
<h1>{t("Loading…")}</h1>
|
<h1>{t("Loading…")}</h1>
|
||||||
</FullScreenView>
|
</FullScreenView>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
@ -33,13 +33,13 @@ interface HeaderProps extends HTMLAttributes<HTMLElement> {
|
|||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Header({ children, className, ...rest }: HeaderProps) {
|
export const Header: FC<HeaderProps> = ({ children, className, ...rest }) => {
|
||||||
return (
|
return (
|
||||||
<header className={classNames(styles.header, className)} {...rest}>
|
<header className={classNames(styles.header, className)} {...rest}>
|
||||||
{children}
|
{children}
|
||||||
</header>
|
</header>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
interface LeftNavProps extends HTMLAttributes<HTMLElement> {
|
interface LeftNavProps extends HTMLAttributes<HTMLElement> {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
@ -47,12 +47,12 @@ interface LeftNavProps extends HTMLAttributes<HTMLElement> {
|
|||||||
hideMobile?: boolean;
|
hideMobile?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function LeftNav({
|
export const LeftNav: FC<LeftNavProps> = ({
|
||||||
children,
|
children,
|
||||||
className,
|
className,
|
||||||
hideMobile,
|
hideMobile,
|
||||||
...rest
|
...rest
|
||||||
}: LeftNavProps) {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
@ -66,7 +66,7 @@ export function LeftNav({
|
|||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
interface RightNavProps extends HTMLAttributes<HTMLElement> {
|
interface RightNavProps extends HTMLAttributes<HTMLElement> {
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
@ -74,12 +74,12 @@ interface RightNavProps extends HTMLAttributes<HTMLElement> {
|
|||||||
hideMobile?: boolean;
|
hideMobile?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function RightNav({
|
export const RightNav: FC<RightNavProps> = ({
|
||||||
children,
|
children,
|
||||||
className,
|
className,
|
||||||
hideMobile,
|
hideMobile,
|
||||||
...rest
|
...rest
|
||||||
}: RightNavProps) {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
@ -93,13 +93,13 @@ export function RightNav({
|
|||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
interface HeaderLogoProps {
|
interface HeaderLogoProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function HeaderLogo({ className }: HeaderLogoProps) {
|
export const HeaderLogo: FC<HeaderLogoProps> = ({ className }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -111,7 +111,7 @@ export function HeaderLogo({ className }: HeaderLogoProps) {
|
|||||||
<Logo />
|
<Logo />
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
interface RoomHeaderInfoProps {
|
interface RoomHeaderInfoProps {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -14,7 +14,13 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { MutableRefObject, PointerEvent, useCallback, useRef } from "react";
|
import {
|
||||||
|
MutableRefObject,
|
||||||
|
PointerEvent,
|
||||||
|
ReactNode,
|
||||||
|
useCallback,
|
||||||
|
useRef,
|
||||||
|
} from "react";
|
||||||
import { useListBox, useOption, AriaListBoxOptions } from "@react-aria/listbox";
|
import { useListBox, useOption, AriaListBoxOptions } from "@react-aria/listbox";
|
||||||
import { ListState } from "@react-stately/list";
|
import { ListState } from "@react-stately/list";
|
||||||
import { Node } from "@react-types/shared";
|
import { Node } from "@react-types/shared";
|
||||||
@ -35,7 +41,7 @@ export function ListBox<T>({
|
|||||||
className,
|
className,
|
||||||
listBoxRef,
|
listBoxRef,
|
||||||
...rest
|
...rest
|
||||||
}: ListBoxProps<T>) {
|
}: ListBoxProps<T>): ReactNode {
|
||||||
const ref = useRef<HTMLUListElement>(null);
|
const ref = useRef<HTMLUListElement>(null);
|
||||||
|
|
||||||
const listRef = listBoxRef ?? ref;
|
const listRef = listBoxRef ?? ref;
|
||||||
@ -66,7 +72,7 @@ interface OptionProps<T> {
|
|||||||
item: Node<T>;
|
item: Node<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function Option<T>({ item, state, className }: OptionProps<T>) {
|
function Option<T>({ item, state, className }: OptionProps<T>): ReactNode {
|
||||||
const ref = useRef(null);
|
const ref = useRef(null);
|
||||||
const { optionProps, isSelected, isFocused, isDisabled } = useOption(
|
const { optionProps, isSelected, isFocused, isDisabled } = useOption(
|
||||||
{ key: item.key },
|
{ key: item.key },
|
||||||
|
11
src/Menu.tsx
11
src/Menu.tsx
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Key, useRef, useState } from "react";
|
import { Key, ReactNode, useRef, useState } from "react";
|
||||||
import { AriaMenuOptions, useMenu, useMenuItem } from "@react-aria/menu";
|
import { AriaMenuOptions, useMenu, useMenuItem } from "@react-aria/menu";
|
||||||
import { TreeState, useTreeState } from "@react-stately/tree";
|
import { TreeState, useTreeState } from "@react-stately/tree";
|
||||||
import { mergeProps } from "@react-aria/utils";
|
import { mergeProps } from "@react-aria/utils";
|
||||||
@ -37,7 +37,7 @@ export function Menu<T extends object>({
|
|||||||
onClose,
|
onClose,
|
||||||
label,
|
label,
|
||||||
...rest
|
...rest
|
||||||
}: MenuProps<T>) {
|
}: MenuProps<T>): ReactNode {
|
||||||
const state = useTreeState<T>({ ...rest, selectionMode: "none" });
|
const state = useTreeState<T>({ ...rest, selectionMode: "none" });
|
||||||
const menuRef = useRef(null);
|
const menuRef = useRef(null);
|
||||||
const { menuProps } = useMenu<T>(rest, state, menuRef);
|
const { menuProps } = useMenu<T>(rest, state, menuRef);
|
||||||
@ -68,7 +68,12 @@ interface MenuItemProps<T> {
|
|||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function MenuItem<T>({ item, state, onAction, onClose }: MenuItemProps<T>) {
|
function MenuItem<T>({
|
||||||
|
item,
|
||||||
|
state,
|
||||||
|
onAction,
|
||||||
|
onClose,
|
||||||
|
}: MenuItemProps<T>): ReactNode {
|
||||||
const ref = useRef(null);
|
const ref = useRef(null);
|
||||||
const { menuItemProps } = useMenuItem(
|
const { menuItemProps } = useMenuItem(
|
||||||
{
|
{
|
||||||
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ReactNode, useCallback } from "react";
|
import { FC, ReactNode, useCallback } from "react";
|
||||||
import { AriaDialogProps } from "@react-types/dialog";
|
import { AriaDialogProps } from "@react-types/dialog";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import {
|
import {
|
||||||
@ -36,7 +36,7 @@ import { useMediaQuery } from "./useMediaQuery";
|
|||||||
import { Glass } from "./Glass";
|
import { Glass } from "./Glass";
|
||||||
|
|
||||||
// TODO: Support tabs
|
// TODO: Support tabs
|
||||||
export interface ModalProps extends AriaDialogProps {
|
export interface Props extends AriaDialogProps {
|
||||||
title: string;
|
title: string;
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
@ -58,14 +58,14 @@ export interface ModalProps extends AriaDialogProps {
|
|||||||
* A modal, taking the form of a drawer / bottom sheet on touchscreen devices,
|
* A modal, taking the form of a drawer / bottom sheet on touchscreen devices,
|
||||||
* and a dialog box on desktop.
|
* and a dialog box on desktop.
|
||||||
*/
|
*/
|
||||||
export function Modal({
|
export const Modal: FC<Props> = ({
|
||||||
title,
|
title,
|
||||||
children,
|
children,
|
||||||
className,
|
className,
|
||||||
open,
|
open,
|
||||||
onDismiss,
|
onDismiss,
|
||||||
...rest
|
...rest
|
||||||
}: ModalProps) {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
// Empirically, Chrome on Android can end up not matching (hover: none), but
|
// Empirically, Chrome on Android can end up not matching (hover: none), but
|
||||||
// still matching (pointer: coarse) :/
|
// still matching (pointer: coarse) :/
|
||||||
@ -140,4 +140,4 @@ export function Modal({
|
|||||||
</DialogRoot>
|
</DialogRoot>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useCallback, useState } from "react";
|
import { FC, useCallback, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -30,7 +30,7 @@ interface DebugLog {
|
|||||||
remoteUserIds: string[];
|
remoteUserIds: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SequenceDiagramViewerPage() {
|
export const SequenceDiagramViewerPage: FC = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
usePageTitle(t("Inspector"));
|
usePageTitle(t("Inspector"));
|
||||||
|
|
||||||
@ -69,4 +69,4 @@ export function SequenceDiagramViewerPage() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
@ -37,5 +37,7 @@ class TranslatedErrorImpl extends TranslatedError {}
|
|||||||
|
|
||||||
// i18next-parser can't detect calls to a constructor, so we expose a bare
|
// i18next-parser can't detect calls to a constructor, so we expose a bare
|
||||||
// function instead
|
// function instead
|
||||||
export const translatedError = (messageKey: string, t: typeof i18n.t) =>
|
export const translatedError = (
|
||||||
new TranslatedErrorImpl(messageKey, t);
|
messageKey: string,
|
||||||
|
t: typeof i18n.t
|
||||||
|
): TranslatedError => new TranslatedErrorImpl(messageKey, t);
|
||||||
|
@ -134,7 +134,7 @@ class ParamParser {
|
|||||||
private fragmentParams: URLSearchParams;
|
private fragmentParams: URLSearchParams;
|
||||||
private queryParams: URLSearchParams;
|
private queryParams: URLSearchParams;
|
||||||
|
|
||||||
constructor(search: string, hash: string) {
|
public constructor(search: string, hash: string) {
|
||||||
this.queryParams = new URLSearchParams(search);
|
this.queryParams = new URLSearchParams(search);
|
||||||
|
|
||||||
const fragmentQueryStart = hash.indexOf("?");
|
const fragmentQueryStart = hash.indexOf("?");
|
||||||
@ -146,18 +146,18 @@ class ParamParser {
|
|||||||
// Normally, URL params should be encoded in the fragment so as to avoid
|
// Normally, URL params should be encoded in the fragment so as to avoid
|
||||||
// leaking them to the server. However, we also check the normal query
|
// leaking them to the server. However, we also check the normal query
|
||||||
// string for backwards compatibility with versions that only used that.
|
// string for backwards compatibility with versions that only used that.
|
||||||
getParam(name: string): string | null {
|
public getParam(name: string): string | null {
|
||||||
return this.fragmentParams.get(name) ?? this.queryParams.get(name);
|
return this.fragmentParams.get(name) ?? this.queryParams.get(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
getAllParams(name: string): string[] {
|
public getAllParams(name: string): string[] {
|
||||||
return [
|
return [
|
||||||
...this.fragmentParams.getAll(name),
|
...this.fragmentParams.getAll(name),
|
||||||
...this.queryParams.getAll(name),
|
...this.queryParams.getAll(name),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
getFlagParam(name: string, defaultValue = false): boolean {
|
public getFlagParam(name: string, defaultValue = false): boolean {
|
||||||
const param = this.getParam(name);
|
const param = this.getParam(name);
|
||||||
return param === null ? defaultValue : param !== "false";
|
return param === null ? defaultValue : param !== "false";
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useCallback, useMemo } from "react";
|
import { FC, ReactNode, useCallback, useMemo } from "react";
|
||||||
import { Item } from "@react-stately/collections";
|
import { Item } from "@react-stately/collections";
|
||||||
import { useLocation } from "react-router-dom";
|
import { useLocation } from "react-router-dom";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@ -31,7 +31,7 @@ import { ReactComponent as LogoutIcon } from "./icons/Logout.svg";
|
|||||||
import { Body } from "./typography/Typography";
|
import { Body } from "./typography/Typography";
|
||||||
import styles from "./UserMenu.module.css";
|
import styles from "./UserMenu.module.css";
|
||||||
|
|
||||||
interface UserMenuProps {
|
interface Props {
|
||||||
preventNavigation: boolean;
|
preventNavigation: boolean;
|
||||||
isAuthenticated: boolean;
|
isAuthenticated: boolean;
|
||||||
isPasswordlessUser: boolean;
|
isPasswordlessUser: boolean;
|
||||||
@ -41,7 +41,7 @@ interface UserMenuProps {
|
|||||||
onAction: (value: string) => void;
|
onAction: (value: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function UserMenu({
|
export const UserMenu: FC<Props> = ({
|
||||||
preventNavigation,
|
preventNavigation,
|
||||||
isAuthenticated,
|
isAuthenticated,
|
||||||
isPasswordlessUser,
|
isPasswordlessUser,
|
||||||
@ -49,7 +49,7 @@ export function UserMenu({
|
|||||||
displayName,
|
displayName,
|
||||||
avatarUrl,
|
avatarUrl,
|
||||||
onAction,
|
onAction,
|
||||||
}: UserMenuProps) {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
@ -123,7 +123,7 @@ export function UserMenu({
|
|||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
{
|
{
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
(props: any) => (
|
(props: any): ReactNode => (
|
||||||
<Menu {...props} label={t("User menu")} onAction={onAction}>
|
<Menu {...props} label={t("User menu")} onAction={onAction}>
|
||||||
{items.map(({ key, icon: Icon, label, dataTestid }) => (
|
{items.map(({ key, icon: Icon, label, dataTestid }) => (
|
||||||
<Item key={key} textValue={label}>
|
<Item key={key} textValue={label}>
|
||||||
@ -141,4 +141,4 @@ export function UserMenu({
|
|||||||
}
|
}
|
||||||
</PopoverMenuTrigger>
|
</PopoverMenuTrigger>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useCallback, useState } from "react";
|
import { FC, useCallback, useState } from "react";
|
||||||
import { useHistory, useLocation } from "react-router-dom";
|
import { useHistory, useLocation } from "react-router-dom";
|
||||||
|
|
||||||
import { useClientLegacy } from "./ClientContext";
|
import { useClientLegacy } from "./ClientContext";
|
||||||
@ -26,7 +26,7 @@ interface Props {
|
|||||||
preventNavigation?: boolean;
|
preventNavigation?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function UserMenuContainer({ preventNavigation = false }: Props) {
|
export const UserMenuContainer: FC<Props> = ({ preventNavigation = false }) => {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const { client, logout, authenticated, passwordlessUser } = useClientLegacy();
|
const { client, logout, authenticated, passwordlessUser } = useClientLegacy();
|
||||||
@ -83,4 +83,4 @@ export function UserMenuContainer({ preventNavigation = false }: Props) {
|
|||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
@ -1,3 +1,19 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2023 New Vector Ltd
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
import { Trans } from "react-i18next";
|
import { Trans } from "react-i18next";
|
||||||
|
|
||||||
|
@ -117,7 +117,7 @@ export class PosthogAnalytics {
|
|||||||
return this.internalInstance;
|
return this.internalInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(private readonly posthog: PostHog) {
|
private constructor(private readonly posthog: PostHog) {
|
||||||
const posthogConfig: PosthogSettings = {
|
const posthogConfig: PosthogSettings = {
|
||||||
project_api_key: Config.get().posthog?.api_key,
|
project_api_key: Config.get().posthog?.api_key,
|
||||||
api_host: Config.get().posthog?.api_host,
|
api_host: Config.get().posthog?.api_host,
|
||||||
@ -183,7 +183,7 @@ export class PosthogAnalytics {
|
|||||||
return properties;
|
return properties;
|
||||||
};
|
};
|
||||||
|
|
||||||
private registerSuperProperties(properties: Properties) {
|
private registerSuperProperties(properties: Properties): void {
|
||||||
if (this.enabled) {
|
if (this.enabled) {
|
||||||
this.posthog.register(properties);
|
this.posthog.register(properties);
|
||||||
}
|
}
|
||||||
@ -202,7 +202,7 @@ export class PosthogAnalytics {
|
|||||||
eventName: string,
|
eventName: string,
|
||||||
properties: Properties,
|
properties: Properties,
|
||||||
options?: CaptureOptions
|
options?: CaptureOptions
|
||||||
) {
|
): void {
|
||||||
if (!this.enabled) {
|
if (!this.enabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -213,7 +213,7 @@ export class PosthogAnalytics {
|
|||||||
return this.enabled;
|
return this.enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
setAnonymity(anonymity: Anonymity): void {
|
private setAnonymity(anonymity: Anonymity): void {
|
||||||
// Update this.anonymity.
|
// Update this.anonymity.
|
||||||
// To update the anonymity typically you want to call updateAnonymityFromSettings
|
// To update the anonymity typically you want to call updateAnonymityFromSettings
|
||||||
// to ensure this value is in step with the user's settings.
|
// to ensure this value is in step with the user's settings.
|
||||||
@ -236,7 +236,9 @@ export class PosthogAnalytics {
|
|||||||
.join("");
|
.join("");
|
||||||
}
|
}
|
||||||
|
|
||||||
private async identifyUser(analyticsIdGenerator: () => string) {
|
private async identifyUser(
|
||||||
|
analyticsIdGenerator: () => string
|
||||||
|
): Promise<void> {
|
||||||
if (this.anonymity == Anonymity.Pseudonymous && this.enabled) {
|
if (this.anonymity == Anonymity.Pseudonymous && this.enabled) {
|
||||||
// Check the user's account_data for an analytics ID to use. Storing the ID in account_data allows
|
// Check the user's account_data for an analytics ID to use. Storing the ID in account_data allows
|
||||||
// different devices to send the same ID.
|
// different devices to send the same ID.
|
||||||
@ -271,7 +273,7 @@ export class PosthogAnalytics {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAnalyticsId() {
|
private async getAnalyticsId(): Promise<string | null> {
|
||||||
const client: MatrixClient = window.matrixclient;
|
const client: MatrixClient = window.matrixclient;
|
||||||
let accountAnalyticsId;
|
let accountAnalyticsId;
|
||||||
if (widget) {
|
if (widget) {
|
||||||
@ -291,7 +293,9 @@ export class PosthogAnalytics {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async hashedEcAnalyticsId(accountAnalyticsId: string): Promise<string> {
|
private async hashedEcAnalyticsId(
|
||||||
|
accountAnalyticsId: string
|
||||||
|
): Promise<string> {
|
||||||
const client: MatrixClient = window.matrixclient;
|
const client: MatrixClient = window.matrixclient;
|
||||||
const posthogIdMaterial = "ec" + accountAnalyticsId + client.getUserId();
|
const posthogIdMaterial = "ec" + accountAnalyticsId + client.getUserId();
|
||||||
const bufferForPosthogId = await crypto.subtle.digest(
|
const bufferForPosthogId = await crypto.subtle.digest(
|
||||||
@ -304,7 +308,7 @@ export class PosthogAnalytics {
|
|||||||
.join("");
|
.join("");
|
||||||
}
|
}
|
||||||
|
|
||||||
async setAccountAnalyticsId(analyticsID: string) {
|
private async setAccountAnalyticsId(analyticsID: string): Promise<void> {
|
||||||
if (!widget) {
|
if (!widget) {
|
||||||
const client = window.matrixclient;
|
const client = window.matrixclient;
|
||||||
|
|
||||||
@ -335,7 +339,7 @@ export class PosthogAnalytics {
|
|||||||
this.updateAnonymityAndIdentifyUser(optInAnalytics);
|
this.updateAnonymityAndIdentifyUser(optInAnalytics);
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateSuperProperties() {
|
private updateSuperProperties(): void {
|
||||||
// Update super properties in posthog with our platform (app version, platform).
|
// Update super properties in posthog with our platform (app version, platform).
|
||||||
// These properties will be subsequently passed in every event.
|
// These properties will be subsequently passed in every event.
|
||||||
//
|
//
|
||||||
|
@ -36,18 +36,22 @@ export class CallEndedTracker {
|
|||||||
maxParticipantsCount: 0,
|
maxParticipantsCount: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
cacheStartCall(time: Date) {
|
public cacheStartCall(time: Date): void {
|
||||||
this.cache.startTime = time;
|
this.cache.startTime = time;
|
||||||
}
|
}
|
||||||
|
|
||||||
cacheParticipantCountChanged(count: number) {
|
public cacheParticipantCountChanged(count: number): void {
|
||||||
this.cache.maxParticipantsCount = Math.max(
|
this.cache.maxParticipantsCount = Math.max(
|
||||||
count,
|
count,
|
||||||
this.cache.maxParticipantsCount
|
this.cache.maxParticipantsCount
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
track(callId: string, callParticipantsNow: number, sendInstantly: boolean) {
|
public track(
|
||||||
|
callId: string,
|
||||||
|
callParticipantsNow: number,
|
||||||
|
sendInstantly: boolean
|
||||||
|
): void {
|
||||||
PosthogAnalytics.instance.trackEvent<CallEnded>(
|
PosthogAnalytics.instance.trackEvent<CallEnded>(
|
||||||
{
|
{
|
||||||
eventName: "CallEnded",
|
eventName: "CallEnded",
|
||||||
@ -67,7 +71,7 @@ interface CallStarted extends IPosthogEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class CallStartedTracker {
|
export class CallStartedTracker {
|
||||||
track(callId: string) {
|
public track(callId: string): void {
|
||||||
PosthogAnalytics.instance.trackEvent<CallStarted>({
|
PosthogAnalytics.instance.trackEvent<CallStarted>({
|
||||||
eventName: "CallStarted",
|
eventName: "CallStarted",
|
||||||
callId: callId,
|
callId: callId,
|
||||||
@ -86,19 +90,19 @@ export class SignupTracker {
|
|||||||
signupEnd: new Date(0),
|
signupEnd: new Date(0),
|
||||||
};
|
};
|
||||||
|
|
||||||
cacheSignupStart(time: Date) {
|
public cacheSignupStart(time: Date): void {
|
||||||
this.cache.signupStart = time;
|
this.cache.signupStart = time;
|
||||||
}
|
}
|
||||||
|
|
||||||
getSignupEndTime() {
|
public getSignupEndTime(): Date {
|
||||||
return this.cache.signupEnd;
|
return this.cache.signupEnd;
|
||||||
}
|
}
|
||||||
|
|
||||||
cacheSignupEnd(time: Date) {
|
public cacheSignupEnd(time: Date): void {
|
||||||
this.cache.signupEnd = time;
|
this.cache.signupEnd = time;
|
||||||
}
|
}
|
||||||
|
|
||||||
track() {
|
public track(): void {
|
||||||
PosthogAnalytics.instance.trackEvent<Signup>({
|
PosthogAnalytics.instance.trackEvent<Signup>({
|
||||||
eventName: "Signup",
|
eventName: "Signup",
|
||||||
signupDuration: Date.now() - this.cache.signupStart.getTime(),
|
signupDuration: Date.now() - this.cache.signupStart.getTime(),
|
||||||
@ -112,7 +116,7 @@ interface Login extends IPosthogEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class LoginTracker {
|
export class LoginTracker {
|
||||||
track() {
|
public track(): void {
|
||||||
PosthogAnalytics.instance.trackEvent<Login>({
|
PosthogAnalytics.instance.trackEvent<Login>({
|
||||||
eventName: "Login",
|
eventName: "Login",
|
||||||
});
|
});
|
||||||
@ -127,7 +131,7 @@ interface MuteMicrophone {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class MuteMicrophoneTracker {
|
export class MuteMicrophoneTracker {
|
||||||
track(targetIsMute: boolean, callId: string) {
|
public track(targetIsMute: boolean, callId: string): void {
|
||||||
PosthogAnalytics.instance.trackEvent<MuteMicrophone>({
|
PosthogAnalytics.instance.trackEvent<MuteMicrophone>({
|
||||||
eventName: "MuteMicrophone",
|
eventName: "MuteMicrophone",
|
||||||
targetMuteState: targetIsMute ? "mute" : "unmute",
|
targetMuteState: targetIsMute ? "mute" : "unmute",
|
||||||
@ -143,7 +147,7 @@ interface MuteCamera {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class MuteCameraTracker {
|
export class MuteCameraTracker {
|
||||||
track(targetIsMute: boolean, callId: string) {
|
public track(targetIsMute: boolean, callId: string): void {
|
||||||
PosthogAnalytics.instance.trackEvent<MuteCamera>({
|
PosthogAnalytics.instance.trackEvent<MuteCamera>({
|
||||||
eventName: "MuteCamera",
|
eventName: "MuteCamera",
|
||||||
targetMuteState: targetIsMute ? "mute" : "unmute",
|
targetMuteState: targetIsMute ? "mute" : "unmute",
|
||||||
@ -158,7 +162,7 @@ interface UndecryptableToDeviceEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class UndecryptableToDeviceEventTracker {
|
export class UndecryptableToDeviceEventTracker {
|
||||||
track(callId: string) {
|
public track(callId: string): void {
|
||||||
PosthogAnalytics.instance.trackEvent<UndecryptableToDeviceEvent>({
|
PosthogAnalytics.instance.trackEvent<UndecryptableToDeviceEvent>({
|
||||||
eventName: "UndecryptableToDeviceEvent",
|
eventName: "UndecryptableToDeviceEvent",
|
||||||
callId,
|
callId,
|
||||||
@ -174,7 +178,7 @@ interface QualitySurveyEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class QualitySurveyEventTracker {
|
export class QualitySurveyEventTracker {
|
||||||
track(callId: string, feedbackText: string, stars: number) {
|
public track(callId: string, feedbackText: string, stars: number): void {
|
||||||
PosthogAnalytics.instance.trackEvent<QualitySurveyEvent>({
|
PosthogAnalytics.instance.trackEvent<QualitySurveyEvent>({
|
||||||
eventName: "QualitySurvey",
|
eventName: "QualitySurvey",
|
||||||
callId,
|
callId,
|
||||||
@ -190,7 +194,7 @@ interface CallDisconnectedEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class CallDisconnectedEventTracker {
|
export class CallDisconnectedEventTracker {
|
||||||
track(reason?: DisconnectReason) {
|
public track(reason?: DisconnectReason): void {
|
||||||
PosthogAnalytics.instance.trackEvent<CallDisconnectedEvent>({
|
PosthogAnalytics.instance.trackEvent<CallDisconnectedEvent>({
|
||||||
eventName: "CallDisconnected",
|
eventName: "CallDisconnected",
|
||||||
reason,
|
reason,
|
||||||
|
@ -39,9 +39,9 @@ const maxRejoinMs = 2 * 60 * 1000; // 2 minutes
|
|||||||
* Span processor that extracts certain metrics from spans to send to PostHog
|
* Span processor that extracts certain metrics from spans to send to PostHog
|
||||||
*/
|
*/
|
||||||
export class PosthogSpanProcessor implements SpanProcessor {
|
export class PosthogSpanProcessor implements SpanProcessor {
|
||||||
async forceFlush(): Promise<void> {}
|
public async forceFlush(): Promise<void> {}
|
||||||
|
|
||||||
onStart(span: Span): void {
|
public onStart(span: Span): void {
|
||||||
// Hack: Yield to allow attributes to be set before processing
|
// Hack: Yield to allow attributes to be set before processing
|
||||||
Promise.resolve().then(() => {
|
Promise.resolve().then(() => {
|
||||||
switch (span.name) {
|
switch (span.name) {
|
||||||
@ -55,7 +55,7 @@ export class PosthogSpanProcessor implements SpanProcessor {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onEnd(span: ReadableSpan): void {
|
public onEnd(span: ReadableSpan): void {
|
||||||
switch (span.name) {
|
switch (span.name) {
|
||||||
case "matrix.groupCallMembership":
|
case "matrix.groupCallMembership":
|
||||||
this.onGroupCallMembershipEnd(span);
|
this.onGroupCallMembershipEnd(span);
|
||||||
@ -157,7 +157,7 @@ export class PosthogSpanProcessor implements SpanProcessor {
|
|||||||
/**
|
/**
|
||||||
* Shutdown the processor.
|
* Shutdown the processor.
|
||||||
*/
|
*/
|
||||||
shutdown(): Promise<void> {
|
public shutdown(): Promise<void> {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,20 @@
|
|||||||
import { Attributes } from "@opentelemetry/api";
|
/*
|
||||||
|
Copyright 2023 New Vector Ltd
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { AttributeValue, Attributes } from "@opentelemetry/api";
|
||||||
import { hrTimeToMicroseconds } from "@opentelemetry/core";
|
import { hrTimeToMicroseconds } from "@opentelemetry/core";
|
||||||
import {
|
import {
|
||||||
SpanProcessor,
|
SpanProcessor,
|
||||||
@ -6,7 +22,21 @@ import {
|
|||||||
Span,
|
Span,
|
||||||
} from "@opentelemetry/sdk-trace-base";
|
} from "@opentelemetry/sdk-trace-base";
|
||||||
|
|
||||||
const dumpAttributes = (attr: Attributes) =>
|
const dumpAttributes = (
|
||||||
|
attr: Attributes
|
||||||
|
): {
|
||||||
|
key: string;
|
||||||
|
type:
|
||||||
|
| "string"
|
||||||
|
| "number"
|
||||||
|
| "bigint"
|
||||||
|
| "boolean"
|
||||||
|
| "symbol"
|
||||||
|
| "undefined"
|
||||||
|
| "object"
|
||||||
|
| "function";
|
||||||
|
value: AttributeValue | undefined;
|
||||||
|
}[] =>
|
||||||
Object.entries(attr).map(([key, value]) => ({
|
Object.entries(attr).map(([key, value]) => ({
|
||||||
key,
|
key,
|
||||||
type: typeof value,
|
type: typeof value,
|
||||||
@ -20,13 +50,13 @@ const dumpAttributes = (attr: Attributes) =>
|
|||||||
export class RageshakeSpanProcessor implements SpanProcessor {
|
export class RageshakeSpanProcessor implements SpanProcessor {
|
||||||
private readonly spans: ReadableSpan[] = [];
|
private readonly spans: ReadableSpan[] = [];
|
||||||
|
|
||||||
async forceFlush(): Promise<void> {}
|
public async forceFlush(): Promise<void> {}
|
||||||
|
|
||||||
onStart(span: Span): void {
|
public onStart(span: Span): void {
|
||||||
this.spans.push(span);
|
this.spans.push(span);
|
||||||
}
|
}
|
||||||
|
|
||||||
onEnd(): void {}
|
public onEnd(): void {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dumps the spans collected so far as Jaeger-compatible JSON.
|
* Dumps the spans collected so far as Jaeger-compatible JSON.
|
||||||
@ -110,5 +140,5 @@ export class RageshakeSpanProcessor implements SpanProcessor {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async shutdown(): Promise<void> {}
|
public async shutdown(): Promise<void> {}
|
||||||
}
|
}
|
||||||
|
@ -68,7 +68,7 @@ export const RegisterPage: FC = () => {
|
|||||||
|
|
||||||
if (password !== passwordConfirmation) return;
|
if (password !== passwordConfirmation) return;
|
||||||
|
|
||||||
const submit = async () => {
|
const submit = async (): Promise<void> => {
|
||||||
setRegistering(true);
|
setRegistering(true);
|
||||||
|
|
||||||
const recaptchaResponse = await execute();
|
const recaptchaResponse = await execute();
|
||||||
@ -183,7 +183,7 @@ export const RegisterPage: FC = () => {
|
|||||||
required
|
required
|
||||||
name="password"
|
name="password"
|
||||||
type="password"
|
type="password"
|
||||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
onChange={(e: ChangeEvent<HTMLInputElement>): void =>
|
||||||
setPassword(e.target.value)
|
setPassword(e.target.value)
|
||||||
}
|
}
|
||||||
value={password}
|
value={password}
|
||||||
@ -197,7 +197,7 @@ export const RegisterPage: FC = () => {
|
|||||||
required
|
required
|
||||||
type="password"
|
type="password"
|
||||||
name="passwordConfirmation"
|
name="passwordConfirmation"
|
||||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
onChange={(e: ChangeEvent<HTMLInputElement>): void =>
|
||||||
setPasswordConfirmation(e.target.value)
|
setPasswordConfirmation(e.target.value)
|
||||||
}
|
}
|
||||||
value={passwordConfirmation}
|
value={passwordConfirmation}
|
||||||
|
@ -21,8 +21,12 @@ import { createClient, MatrixClient } from "matrix-js-sdk/src/matrix";
|
|||||||
import { initClient } from "../matrix-utils";
|
import { initClient } from "../matrix-utils";
|
||||||
import { Session } from "../ClientContext";
|
import { Session } from "../ClientContext";
|
||||||
|
|
||||||
export const useInteractiveLogin = () =>
|
export function useInteractiveLogin(): (
|
||||||
useCallback<
|
homeserver: string,
|
||||||
|
username: string,
|
||||||
|
password: string
|
||||||
|
) => Promise<[MatrixClient, Session]> {
|
||||||
|
return useCallback<
|
||||||
(
|
(
|
||||||
homeserver: string,
|
homeserver: string,
|
||||||
username: string,
|
username: string,
|
||||||
@ -41,8 +45,8 @@ export const useInteractiveLogin = () =>
|
|||||||
},
|
},
|
||||||
password,
|
password,
|
||||||
}),
|
}),
|
||||||
stateUpdated: (...args) => {},
|
stateUpdated: (): void => {},
|
||||||
requestEmailToken: (...args): Promise<{ sid: string }> => {
|
requestEmailToken: (): Promise<{ sid: string }> => {
|
||||||
return Promise.resolve({ sid: "" });
|
return Promise.resolve({ sid: "" });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -69,6 +73,6 @@ export const useInteractiveLogin = () =>
|
|||||||
false
|
false
|
||||||
);
|
);
|
||||||
/* eslint-enable camelcase */
|
/* eslint-enable camelcase */
|
||||||
|
|
||||||
return [client, session];
|
return [client, session];
|
||||||
}, []);
|
}, []);
|
||||||
|
}
|
||||||
|
@ -72,7 +72,7 @@ export const useInteractiveRegistration = (): {
|
|||||||
password,
|
password,
|
||||||
auth: auth || undefined,
|
auth: auth || undefined,
|
||||||
}),
|
}),
|
||||||
stateUpdated: (nextStage, status) => {
|
stateUpdated: (nextStage, status): void => {
|
||||||
if (status.error) {
|
if (status.error) {
|
||||||
throw new Error(status.error);
|
throw new Error(status.error);
|
||||||
}
|
}
|
||||||
@ -88,7 +88,7 @@ export const useInteractiveRegistration = (): {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
requestEmailToken: (...args) => {
|
requestEmailToken: (): Promise<{ sid: string }> => {
|
||||||
return Promise.resolve({ sid: "dummy" });
|
return Promise.resolve({ sid: "dummy" });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -34,7 +34,11 @@ interface RecaptchaPromiseRef {
|
|||||||
reject: (error: Error) => void;
|
reject: (error: Error) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useRecaptcha = (sitekey?: string) => {
|
export function useRecaptcha(sitekey?: string): {
|
||||||
|
execute: () => Promise<string>;
|
||||||
|
reset: () => void;
|
||||||
|
recaptchaId: string;
|
||||||
|
} {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [recaptchaId] = useState(() => randomString(16));
|
const [recaptchaId] = useState(() => randomString(16));
|
||||||
const promiseRef = useRef<RecaptchaPromiseRef>();
|
const promiseRef = useRef<RecaptchaPromiseRef>();
|
||||||
@ -42,7 +46,7 @@ export const useRecaptcha = (sitekey?: string) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!sitekey) return;
|
if (!sitekey) return;
|
||||||
|
|
||||||
const onRecaptchaLoaded = () => {
|
const onRecaptchaLoaded = (): void => {
|
||||||
if (!document.getElementById(recaptchaId)) return;
|
if (!document.getElementById(recaptchaId)) return;
|
||||||
|
|
||||||
window.grecaptcha.render(recaptchaId, {
|
window.grecaptcha.render(recaptchaId, {
|
||||||
@ -90,11 +94,11 @@ export const useRecaptcha = (sitekey?: string) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
promiseRef.current = {
|
promiseRef.current = {
|
||||||
resolve: (value) => {
|
resolve: (value): void => {
|
||||||
resolve(value);
|
resolve(value);
|
||||||
observer.disconnect();
|
observer.disconnect();
|
||||||
},
|
},
|
||||||
reject: (error) => {
|
reject: (error): void => {
|
||||||
reject(error);
|
reject(error);
|
||||||
observer.disconnect();
|
observer.disconnect();
|
||||||
},
|
},
|
||||||
@ -119,4 +123,4 @@ export const useRecaptcha = (sitekey?: string) => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return { execute, reset, recaptchaId };
|
return { execute, reset, recaptchaId };
|
||||||
};
|
}
|
||||||
|
@ -13,7 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
import { forwardRef } from "react";
|
import { FC, forwardRef } from "react";
|
||||||
import { PressEvent } from "@react-types/shared";
|
import { PressEvent } from "@react-types/shared";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { useButton } from "@react-aria/button";
|
import { useButton } from "@react-aria/button";
|
||||||
@ -135,14 +135,11 @@ export const Button = forwardRef<HTMLButtonElement, Props>(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export function MicButton({
|
export const MicButton: FC<{
|
||||||
muted,
|
|
||||||
...rest
|
|
||||||
}: {
|
|
||||||
muted: boolean;
|
muted: boolean;
|
||||||
// TODO: add all props for <Button>
|
// TODO: add all props for <Button>
|
||||||
[index: string]: unknown;
|
[index: string]: unknown;
|
||||||
}) {
|
}> = ({ muted, ...rest }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const Icon = muted ? MicOffSolidIcon : MicOnSolidIcon;
|
const Icon = muted ? MicOffSolidIcon : MicOnSolidIcon;
|
||||||
const label = muted ? t("Unmute microphone") : t("Mute microphone");
|
const label = muted ? t("Unmute microphone") : t("Mute microphone");
|
||||||
@ -154,16 +151,13 @@ export function MicButton({
|
|||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export function VideoButton({
|
export const VideoButton: FC<{
|
||||||
muted,
|
|
||||||
...rest
|
|
||||||
}: {
|
|
||||||
muted: boolean;
|
muted: boolean;
|
||||||
// TODO: add all props for <Button>
|
// TODO: add all props for <Button>
|
||||||
[index: string]: unknown;
|
[index: string]: unknown;
|
||||||
}) {
|
}> = ({ muted, ...rest }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const Icon = muted ? VideoCallOffIcon : VideoCallIcon;
|
const Icon = muted ? VideoCallOffIcon : VideoCallIcon;
|
||||||
const label = muted ? t("Start video") : t("Stop video");
|
const label = muted ? t("Start video") : t("Stop video");
|
||||||
@ -175,18 +169,14 @@ export function VideoButton({
|
|||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export function ScreenshareButton({
|
export const ScreenshareButton: FC<{
|
||||||
enabled,
|
|
||||||
className,
|
|
||||||
...rest
|
|
||||||
}: {
|
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
// TODO: add all props for <Button>
|
// TODO: add all props for <Button>
|
||||||
[index: string]: unknown;
|
[index: string]: unknown;
|
||||||
}) {
|
}> = ({ enabled, className, ...rest }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const label = enabled ? t("Sharing screen") : t("Share screen");
|
const label = enabled ? t("Sharing screen") : t("Share screen");
|
||||||
|
|
||||||
@ -197,16 +187,13 @@ export function ScreenshareButton({
|
|||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export function HangupButton({
|
export const HangupButton: FC<{
|
||||||
className,
|
|
||||||
...rest
|
|
||||||
}: {
|
|
||||||
className?: string;
|
className?: string;
|
||||||
// TODO: add all props for <Button>
|
// TODO: add all props for <Button>
|
||||||
[index: string]: unknown;
|
[index: string]: unknown;
|
||||||
}) {
|
}> = ({ className, ...rest }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -220,16 +207,13 @@ export function HangupButton({
|
|||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export function SettingsButton({
|
export const SettingsButton: FC<{
|
||||||
className,
|
|
||||||
...rest
|
|
||||||
}: {
|
|
||||||
className?: string;
|
className?: string;
|
||||||
// TODO: add all props for <Button>
|
// TODO: add all props for <Button>
|
||||||
[index: string]: unknown;
|
[index: string]: unknown;
|
||||||
}) {
|
}> = ({ className, ...rest }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -239,7 +223,7 @@ export function SettingsButton({
|
|||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
interface AudioButtonProps extends Omit<Props, "variant"> {
|
interface AudioButtonProps extends Omit<Props, "variant"> {
|
||||||
/**
|
/**
|
||||||
@ -248,7 +232,7 @@ interface AudioButtonProps extends Omit<Props, "variant"> {
|
|||||||
volume: number;
|
volume: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AudioButton({ volume, ...rest }: AudioButtonProps) {
|
export const AudioButton: FC<AudioButtonProps> = ({ volume, ...rest }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -258,16 +242,16 @@ export function AudioButton({ volume, ...rest }: AudioButtonProps) {
|
|||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
interface FullscreenButtonProps extends Omit<Props, "variant"> {
|
interface FullscreenButtonProps extends Omit<Props, "variant"> {
|
||||||
fullscreen?: boolean;
|
fullscreen?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function FullscreenButton({
|
export const FullscreenButton: FC<FullscreenButtonProps> = ({
|
||||||
fullscreen,
|
fullscreen,
|
||||||
...rest
|
...rest
|
||||||
}: FullscreenButtonProps) {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const Icon = fullscreen ? FullscreenExit : Fullscreen;
|
const Icon = fullscreen ? FullscreenExit : Fullscreen;
|
||||||
const label = fullscreen ? t("Exit full screen") : t("Full screen");
|
const label = fullscreen ? t("Exit full screen") : t("Full screen");
|
||||||
@ -279,4 +263,4 @@ export function FullscreenButton({
|
|||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
@ -16,6 +16,7 @@ limitations under the License.
|
|||||||
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import useClipboard from "react-use-clipboard";
|
import useClipboard from "react-use-clipboard";
|
||||||
|
import { FC } from "react";
|
||||||
|
|
||||||
import { ReactComponent as CheckIcon } from "../icons/Check.svg";
|
import { ReactComponent as CheckIcon } from "../icons/Check.svg";
|
||||||
import { ReactComponent as CopyIcon } from "../icons/Copy.svg";
|
import { ReactComponent as CopyIcon } from "../icons/Copy.svg";
|
||||||
@ -28,14 +29,15 @@ interface Props {
|
|||||||
variant?: ButtonVariant;
|
variant?: ButtonVariant;
|
||||||
copiedMessage?: string;
|
copiedMessage?: string;
|
||||||
}
|
}
|
||||||
export function CopyButton({
|
|
||||||
|
export const CopyButton: FC<Props> = ({
|
||||||
value,
|
value,
|
||||||
children,
|
children,
|
||||||
className,
|
className,
|
||||||
variant,
|
variant,
|
||||||
copiedMessage,
|
copiedMessage,
|
||||||
...rest
|
...rest
|
||||||
}: Props) {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [isCopied, setCopied] = useClipboard(value, { successDuration: 3000 });
|
const [isCopied, setCopied] = useClipboard(value, { successDuration: 3000 });
|
||||||
|
|
||||||
@ -62,4 +64,4 @@ export function CopyButton({
|
|||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { HTMLAttributes } from "react";
|
import { FC, HTMLAttributes } from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import * as H from "history";
|
import * as H from "history";
|
||||||
@ -34,14 +34,14 @@ interface Props extends HTMLAttributes<HTMLAnchorElement> {
|
|||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function LinkButton({
|
export const LinkButton: FC<Props> = ({
|
||||||
children,
|
children,
|
||||||
to,
|
to,
|
||||||
size,
|
size,
|
||||||
variant,
|
variant,
|
||||||
className,
|
className,
|
||||||
...rest
|
...rest
|
||||||
}: Props) {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
className={classNames(
|
className={classNames(
|
||||||
@ -55,4 +55,4 @@ export function LinkButton({
|
|||||||
{children}
|
{children}
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
@ -18,6 +18,7 @@ import { Link } from "react-router-dom";
|
|||||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
||||||
import { Room } from "matrix-js-sdk/src/models/room";
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
|
import { FC } from "react";
|
||||||
|
|
||||||
import { CopyButton } from "../button";
|
import { CopyButton } from "../button";
|
||||||
import { Avatar, Size } from "../Avatar";
|
import { Avatar, Size } from "../Avatar";
|
||||||
@ -31,7 +32,8 @@ interface CallListProps {
|
|||||||
rooms: GroupCallRoom[];
|
rooms: GroupCallRoom[];
|
||||||
client: MatrixClient;
|
client: MatrixClient;
|
||||||
}
|
}
|
||||||
export function CallList({ rooms, client }: CallListProps) {
|
|
||||||
|
export const CallList: FC<CallListProps> = ({ rooms, client }) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={styles.callList}>
|
<div className={styles.callList}>
|
||||||
@ -54,7 +56,7 @@ export function CallList({ rooms, client }: CallListProps) {
|
|||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
interface CallTileProps {
|
interface CallTileProps {
|
||||||
name: string;
|
name: string;
|
||||||
avatarUrl: string;
|
avatarUrl: string;
|
||||||
@ -62,7 +64,8 @@ interface CallTileProps {
|
|||||||
participants: RoomMember[];
|
participants: RoomMember[];
|
||||||
client: MatrixClient;
|
client: MatrixClient;
|
||||||
}
|
}
|
||||||
function CallTile({ name, avatarUrl, room }: CallTileProps) {
|
|
||||||
|
const CallTile: FC<CallTileProps> = ({ name, avatarUrl, room }) => {
|
||||||
const roomSharedKey = useRoomSharedKey(room.roomId);
|
const roomSharedKey = useRoomSharedKey(room.roomId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -92,4 +95,4 @@ function CallTile({ name, avatarUrl, room }: CallTileProps) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
@ -15,6 +15,7 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { FC } from "react";
|
||||||
|
|
||||||
import { useClientState } from "../ClientContext";
|
import { useClientState } from "../ClientContext";
|
||||||
import { ErrorView, LoadingView } from "../FullScreenView";
|
import { ErrorView, LoadingView } from "../FullScreenView";
|
||||||
@ -22,7 +23,7 @@ import { UnauthenticatedView } from "./UnauthenticatedView";
|
|||||||
import { RegisteredView } from "./RegisteredView";
|
import { RegisteredView } from "./RegisteredView";
|
||||||
import { usePageTitle } from "../usePageTitle";
|
import { usePageTitle } from "../usePageTitle";
|
||||||
|
|
||||||
export function HomePage() {
|
export const HomePage: FC = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
usePageTitle(t("Home"));
|
usePageTitle(t("Home"));
|
||||||
|
|
||||||
@ -39,4 +40,4 @@ export function HomePage() {
|
|||||||
<UnauthenticatedView />
|
<UnauthenticatedView />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
@ -16,6 +16,7 @@ limitations under the License.
|
|||||||
|
|
||||||
import { PressEvent } from "@react-types/shared";
|
import { PressEvent } from "@react-types/shared";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { FC } from "react";
|
||||||
|
|
||||||
import { Modal } from "../Modal";
|
import { Modal } from "../Modal";
|
||||||
import { Button } from "../button";
|
import { Button } from "../button";
|
||||||
@ -28,7 +29,11 @@ interface Props {
|
|||||||
onJoin: (e: PressEvent) => void;
|
onJoin: (e: PressEvent) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function JoinExistingCallModal({ onJoin, open, onDismiss }: Props) {
|
export const JoinExistingCallModal: FC<Props> = ({
|
||||||
|
onJoin,
|
||||||
|
open,
|
||||||
|
onDismiss,
|
||||||
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -42,4 +47,4 @@ export function JoinExistingCallModal({ onJoin, open, onDismiss }: Props) {
|
|||||||
</FieldRow>
|
</FieldRow>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useState, useCallback, FormEvent, FormEventHandler } from "react";
|
import { useState, useCallback, FormEvent, FormEventHandler, FC } from "react";
|
||||||
import { useHistory } from "react-router-dom";
|
import { useHistory } from "react-router-dom";
|
||||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
import { randomString } from "matrix-js-sdk/src/randomstring";
|
import { randomString } from "matrix-js-sdk/src/randomstring";
|
||||||
@ -48,7 +48,7 @@ interface Props {
|
|||||||
client: MatrixClient;
|
client: MatrixClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function RegisteredView({ client }: Props) {
|
export const RegisteredView: FC<Props> = ({ client }) => {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState<Error>();
|
const [error, setError] = useState<Error>();
|
||||||
const [optInAnalytics] = useOptInAnalytics();
|
const [optInAnalytics] = useOptInAnalytics();
|
||||||
@ -72,7 +72,7 @@ export function RegisteredView({ client }: Props) {
|
|||||||
? sanitiseRoomNameInput(roomNameData)
|
? sanitiseRoomNameInput(roomNameData)
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
async function submit() {
|
async function submit(): Promise<void> {
|
||||||
setError(undefined);
|
setError(undefined);
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
@ -176,4 +176,4 @@ export function RegisteredView({ client }: Props) {
|
|||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
@ -73,7 +73,7 @@ export const UnauthenticatedView: FC = () => {
|
|||||||
const roomName = sanitiseRoomNameInput(data.get("callName") as string);
|
const roomName = sanitiseRoomNameInput(data.get("callName") as string);
|
||||||
const displayName = data.get("displayName") as string;
|
const displayName = data.get("displayName") as string;
|
||||||
|
|
||||||
async function submit() {
|
async function submit(): Promise<void> {
|
||||||
setError(undefined);
|
setError(undefined);
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const recaptchaResponse = await execute();
|
const recaptchaResponse = await execute();
|
||||||
|
@ -31,7 +31,7 @@ export interface GroupCallRoom {
|
|||||||
}
|
}
|
||||||
const tsCache: { [index: string]: number } = {};
|
const tsCache: { [index: string]: number } = {};
|
||||||
|
|
||||||
function getLastTs(client: MatrixClient, r: Room) {
|
function getLastTs(client: MatrixClient, r: Room): number {
|
||||||
if (tsCache[r.roomId]) {
|
if (tsCache[r.roomId]) {
|
||||||
return tsCache[r.roomId];
|
return tsCache[r.roomId];
|
||||||
}
|
}
|
||||||
@ -82,7 +82,7 @@ export function useGroupCallRooms(client: MatrixClient): GroupCallRoom[] {
|
|||||||
const [rooms, setRooms] = useState<GroupCallRoom[]>([]);
|
const [rooms, setRooms] = useState<GroupCallRoom[]>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function updateRooms() {
|
function updateRooms(): void {
|
||||||
if (!client.groupCallEventHandler) {
|
if (!client.groupCallEventHandler) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -35,11 +35,11 @@ enum LoadState {
|
|||||||
class DependencyLoadStates {
|
class DependencyLoadStates {
|
||||||
// TODO: decide where olm should be initialized (see TODO comment below)
|
// TODO: decide where olm should be initialized (see TODO comment below)
|
||||||
// olm: LoadState = LoadState.None;
|
// olm: LoadState = LoadState.None;
|
||||||
config: LoadState = LoadState.None;
|
public config: LoadState = LoadState.None;
|
||||||
sentry: LoadState = LoadState.None;
|
public sentry: LoadState = LoadState.None;
|
||||||
openTelemetry: LoadState = LoadState.None;
|
public openTelemetry: LoadState = LoadState.None;
|
||||||
|
|
||||||
allDepsAreLoaded() {
|
public allDepsAreLoaded(): boolean {
|
||||||
return !Object.values(this).some((s) => s !== LoadState.Loaded);
|
return !Object.values(this).some((s) => s !== LoadState.Loaded);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -52,7 +52,7 @@ export class Initializer {
|
|||||||
return Initializer.internalInstance?.isInitialized;
|
return Initializer.internalInstance?.isInitialized;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static initBeforeReact() {
|
public static initBeforeReact(): void {
|
||||||
// this maybe also needs to return a promise in the future,
|
// this maybe also needs to return a promise in the future,
|
||||||
// if we have to do async inits before showing the loading screen
|
// if we have to do async inits before showing the loading screen
|
||||||
// but this should be avioded if possible
|
// but this should be avioded if possible
|
||||||
@ -126,9 +126,9 @@ export class Initializer {
|
|||||||
return Initializer.internalInstance.initPromise;
|
return Initializer.internalInstance.initPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
loadStates = new DependencyLoadStates();
|
private loadStates = new DependencyLoadStates();
|
||||||
|
|
||||||
initStep(resolve: (value: void | PromiseLike<void>) => void) {
|
private initStep(resolve: (value: void | PromiseLike<void>) => void): void {
|
||||||
// TODO: Olm is initialized with the client currently (see `initClient()` and `olm.ts`)
|
// TODO: Olm is initialized with the client currently (see `initClient()` and `olm.ts`)
|
||||||
// we need to decide if we want to init it here or keep it in initClient
|
// we need to decide if we want to init it here or keep it in initClient
|
||||||
// if (this.loadStates.olm === LoadState.None) {
|
// if (this.loadStates.olm === LoadState.None) {
|
||||||
|
@ -64,7 +64,7 @@ export const AvatarInputField = forwardRef<HTMLInputElement, Props>(
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const currentInput = fileInputRef.current;
|
const currentInput = fileInputRef.current;
|
||||||
|
|
||||||
const onChange = (e: Event) => {
|
const onChange = (e: Event): void => {
|
||||||
const inputEvent = e as unknown as ChangeEvent<HTMLInputElement>;
|
const inputEvent = e as unknown as ChangeEvent<HTMLInputElement>;
|
||||||
if (inputEvent.target.files && inputEvent.target.files.length > 0) {
|
if (inputEvent.target.files && inputEvent.target.files.length > 0) {
|
||||||
setObjUrl(URL.createObjectURL(inputEvent.target.files[0]));
|
setObjUrl(URL.createObjectURL(inputEvent.target.files[0]));
|
||||||
@ -76,7 +76,7 @@ export const AvatarInputField = forwardRef<HTMLInputElement, Props>(
|
|||||||
|
|
||||||
currentInput.addEventListener("change", onChange);
|
currentInput.addEventListener("change", onChange);
|
||||||
|
|
||||||
return () => {
|
return (): void => {
|
||||||
currentInput?.removeEventListener("change", onChange);
|
currentInput?.removeEventListener("change", onChange);
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -41,8 +41,8 @@ export function StarRatingInput({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={styles.inputContainer}
|
className={styles.inputContainer}
|
||||||
onMouseEnter={() => setHover(index)}
|
onMouseEnter={(): void => setHover(index)}
|
||||||
onMouseLeave={() => setHover(rating)}
|
onMouseLeave={(): void => setHover(rating)}
|
||||||
key={index}
|
key={index}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
@ -51,7 +51,7 @@ export function StarRatingInput({
|
|||||||
id={"starInput" + String(index)}
|
id={"starInput" + String(index)}
|
||||||
value={String(index) + "Star"}
|
value={String(index) + "Star"}
|
||||||
name="star rating"
|
name="star rating"
|
||||||
onChange={(_ev) => {
|
onChange={(_ev): void => {
|
||||||
setRating(index);
|
setRating(index);
|
||||||
onChange(index);
|
onChange(index);
|
||||||
}}
|
}}
|
||||||
|
@ -52,7 +52,7 @@ export interface MediaDevices {
|
|||||||
function useObservableState<T>(
|
function useObservableState<T>(
|
||||||
observable: Observable<T> | undefined,
|
observable: Observable<T> | undefined,
|
||||||
startWith: T
|
startWith: T
|
||||||
) {
|
): T {
|
||||||
const [state, setState] = useState<T>(startWith);
|
const [state, setState] = useState<T>(startWith);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// observable state doesn't run in SSR
|
// observable state doesn't run in SSR
|
||||||
@ -207,7 +207,8 @@ export const MediaDevicesProvider: FC<Props> = ({ children }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useMediaDevices = () => useContext(MediaDevicesContext);
|
export const useMediaDevices = (): MediaDevices =>
|
||||||
|
useContext(MediaDevicesContext);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* React hook that requests for the media devices context to be populated with
|
* React hook that requests for the media devices context to be populated with
|
||||||
@ -215,7 +216,10 @@ export const useMediaDevices = () => useContext(MediaDevicesContext);
|
|||||||
* default because it may involve requesting additional permissions from the
|
* default because it may involve requesting additional permissions from the
|
||||||
* user.
|
* user.
|
||||||
*/
|
*/
|
||||||
export const useMediaDeviceNames = (context: MediaDevices, enabled = true) =>
|
export const useMediaDeviceNames = (
|
||||||
|
context: MediaDevices,
|
||||||
|
enabled = true
|
||||||
|
): void =>
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
context.startUsingDeviceNames();
|
context.startUsingDeviceNames();
|
||||||
|
@ -43,13 +43,13 @@ export type OpenIDClientParts = Pick<
|
|||||||
export function useOpenIDSFU(
|
export function useOpenIDSFU(
|
||||||
client: OpenIDClientParts,
|
client: OpenIDClientParts,
|
||||||
rtcSession: MatrixRTCSession
|
rtcSession: MatrixRTCSession
|
||||||
) {
|
): SFUConfig | undefined {
|
||||||
const [sfuConfig, setSFUConfig] = useState<SFUConfig | undefined>(undefined);
|
const [sfuConfig, setSFUConfig] = useState<SFUConfig | undefined>(undefined);
|
||||||
|
|
||||||
const activeFocus = useActiveFocus(rtcSession);
|
const activeFocus = useActiveFocus(rtcSession);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async (): Promise<void> => {
|
||||||
const sfuConfig = activeFocus
|
const sfuConfig = activeFocus
|
||||||
? await getSFUConfigWithOpenID(client, activeFocus)
|
? await getSFUConfigWithOpenID(client, activeFocus)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
@ -1,3 +1,19 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2023 New Vector Ltd
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AudioPresets,
|
AudioPresets,
|
||||||
DefaultReconnectPolicy,
|
DefaultReconnectPolicy,
|
||||||
|
@ -119,7 +119,7 @@ export function useECConnectionState(
|
|||||||
`SFU config changed! URL was ${currentSFUConfig.current?.url} now ${sfuConfig?.url}`
|
`SFU config changed! URL was ${currentSFUConfig.current?.url} now ${sfuConfig?.url}`
|
||||||
);
|
);
|
||||||
|
|
||||||
(async () => {
|
(async (): Promise<void> => {
|
||||||
setSwitchingFocus(true);
|
setSwitchingFocus(true);
|
||||||
await livekitRoom?.disconnect();
|
await livekitRoom?.disconnect();
|
||||||
setIsInDoConnect(true);
|
setIsInDoConnect(true);
|
||||||
|
@ -156,7 +156,7 @@ export function useLiveKit(
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Sync the requested devices with LiveKit's devices
|
// Sync the requested devices with LiveKit's devices
|
||||||
if (room !== undefined && connectionState === ConnectionState.Connected) {
|
if (room !== undefined && connectionState === ConnectionState.Connected) {
|
||||||
const syncDevice = (kind: MediaDeviceKind, device: MediaDevice) => {
|
const syncDevice = (kind: MediaDeviceKind, device: MediaDevice): void => {
|
||||||
const id = device.selectedId;
|
const id = device.selectedId;
|
||||||
if (id !== undefined && room.getActiveDevice(kind) !== id) {
|
if (id !== undefined && room.getActiveDevice(kind) !== id) {
|
||||||
room
|
room
|
||||||
|
@ -26,7 +26,7 @@ import { createBrowserHistory } from "history";
|
|||||||
import "./index.css";
|
import "./index.css";
|
||||||
import { setLogLevel as setLKLogLevel } from "livekit-client";
|
import { setLogLevel as setLKLogLevel } from "livekit-client";
|
||||||
|
|
||||||
import App from "./App";
|
import { App } from "./App";
|
||||||
import { init as initRageshake } from "./settings/rageshake";
|
import { init as initRageshake } from "./settings/rageshake";
|
||||||
import { Initializer } from "./initializer";
|
import { Initializer } from "./initializer";
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ export const fallbackICEServerAllowed =
|
|||||||
import.meta.env.VITE_FALLBACK_STUN_ALLOWED === "true";
|
import.meta.env.VITE_FALLBACK_STUN_ALLOWED === "true";
|
||||||
|
|
||||||
export class CryptoStoreIntegrityError extends Error {
|
export class CryptoStoreIntegrityError extends Error {
|
||||||
constructor() {
|
public constructor() {
|
||||||
super("Crypto store data was expected, but none was found");
|
super("Crypto store data was expected, but none was found");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -52,13 +52,13 @@ const SYNC_STORE_NAME = "element-call-sync";
|
|||||||
// (It's a good opportunity to make the database names consistent.)
|
// (It's a good opportunity to make the database names consistent.)
|
||||||
const CRYPTO_STORE_NAME = "element-call-crypto";
|
const CRYPTO_STORE_NAME = "element-call-crypto";
|
||||||
|
|
||||||
function waitForSync(client: MatrixClient) {
|
function waitForSync(client: MatrixClient): Promise<void> {
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
const onSync = (
|
const onSync = (
|
||||||
state: SyncState,
|
state: SyncState,
|
||||||
_old: SyncState | null,
|
_old: SyncState | null,
|
||||||
data?: ISyncStateData
|
data?: ISyncStateData
|
||||||
) => {
|
): void => {
|
||||||
if (state === "PREPARED") {
|
if (state === "PREPARED") {
|
||||||
client.removeListener(ClientEvent.Sync, onSync);
|
client.removeListener(ClientEvent.Sync, onSync);
|
||||||
resolve();
|
resolve();
|
||||||
@ -108,7 +108,7 @@ export async function initClient(
|
|||||||
// Chrome supports it. (It bundles them fine in production mode.)
|
// Chrome supports it. (It bundles them fine in production mode.)
|
||||||
workerFactory: import.meta.env.DEV
|
workerFactory: import.meta.env.DEV
|
||||||
? undefined
|
? undefined
|
||||||
: () => new IndexedDBWorker(),
|
: (): Worker => new IndexedDBWorker(),
|
||||||
});
|
});
|
||||||
} else if (localStorage) {
|
} else if (localStorage) {
|
||||||
baseOpts.store = new MemoryStore({ localStorage });
|
baseOpts.store = new MemoryStore({ localStorage });
|
||||||
@ -307,7 +307,7 @@ export async function createRoom(
|
|||||||
|
|
||||||
// Wait for the room to arrive
|
// Wait for the room to arrive
|
||||||
await new Promise<void>((resolve, reject) => {
|
await new Promise<void>((resolve, reject) => {
|
||||||
const onRoom = async (room: Room) => {
|
const onRoom = async (room: Room): Promise<void> => {
|
||||||
if (room.roomId === (await createPromise).room_id) {
|
if (room.roomId === (await createPromise).room_id) {
|
||||||
resolve();
|
resolve();
|
||||||
cleanUp();
|
cleanUp();
|
||||||
@ -318,7 +318,7 @@ export async function createRoom(
|
|||||||
cleanUp();
|
cleanUp();
|
||||||
});
|
});
|
||||||
|
|
||||||
const cleanUp = () => {
|
const cleanUp = (): void => {
|
||||||
client.off(ClientEvent.Room, onRoom);
|
client.off(ClientEvent.Room, onRoom);
|
||||||
};
|
};
|
||||||
client.on(ClientEvent.Room, onRoom);
|
client.on(ClientEvent.Room, onRoom);
|
||||||
|
@ -44,7 +44,7 @@ export class OTelCall {
|
|||||||
OTelCallAbstractMediaStreamSpan
|
OTelCallAbstractMediaStreamSpan
|
||||||
>();
|
>();
|
||||||
|
|
||||||
constructor(
|
public constructor(
|
||||||
public userId: string,
|
public userId: string,
|
||||||
public deviceId: string,
|
public deviceId: string,
|
||||||
public call: MatrixCall,
|
public call: MatrixCall,
|
||||||
@ -60,7 +60,7 @@ export class OTelCall {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public dispose() {
|
public dispose(): void {
|
||||||
this.call.peerConn?.removeEventListener(
|
this.call.peerConn?.removeEventListener(
|
||||||
"connectionstatechange",
|
"connectionstatechange",
|
||||||
this.onCallConnectionStateChanged
|
this.onCallConnectionStateChanged
|
||||||
|
@ -1,3 +1,19 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2023 New Vector Ltd
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
import opentelemetry, { Span } from "@opentelemetry/api";
|
import opentelemetry, { Span } from "@opentelemetry/api";
|
||||||
import { TrackStats } from "matrix-js-sdk/src/webrtc/stats/statsReport";
|
import { TrackStats } from "matrix-js-sdk/src/webrtc/stats/statsReport";
|
||||||
|
|
||||||
@ -14,8 +30,8 @@ export abstract class OTelCallAbstractMediaStreamSpan {
|
|||||||
public readonly span;
|
public readonly span;
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
readonly oTel: ElementCallOpenTelemetry,
|
protected readonly oTel: ElementCallOpenTelemetry,
|
||||||
readonly callSpan: Span,
|
protected readonly callSpan: Span,
|
||||||
protected readonly type: string
|
protected readonly type: string
|
||||||
) {
|
) {
|
||||||
const ctx = opentelemetry.trace.setSpan(
|
const ctx = opentelemetry.trace.setSpan(
|
||||||
@ -32,7 +48,7 @@ export abstract class OTelCallAbstractMediaStreamSpan {
|
|||||||
this.span = oTel.tracer.startSpan(this.type, options, ctx);
|
this.span = oTel.tracer.startSpan(this.type, options, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected upsertTrackSpans(tracks: TrackStats[]) {
|
protected upsertTrackSpans(tracks: TrackStats[]): void {
|
||||||
let prvTracks: TrackId[] = [...this.trackSpans.keys()];
|
let prvTracks: TrackId[] = [...this.trackSpans.keys()];
|
||||||
tracks.forEach((t) => {
|
tracks.forEach((t) => {
|
||||||
if (!this.trackSpans.has(t.id)) {
|
if (!this.trackSpans.has(t.id)) {
|
||||||
|
@ -1,3 +1,19 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2023 New Vector Ltd
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
import { Span } from "@opentelemetry/api";
|
import { Span } from "@opentelemetry/api";
|
||||||
import {
|
import {
|
||||||
CallFeedStats,
|
CallFeedStats,
|
||||||
@ -10,9 +26,9 @@ import { OTelCallAbstractMediaStreamSpan } from "./OTelCallAbstractMediaStreamSp
|
|||||||
export class OTelCallFeedMediaStreamSpan extends OTelCallAbstractMediaStreamSpan {
|
export class OTelCallFeedMediaStreamSpan extends OTelCallAbstractMediaStreamSpan {
|
||||||
private readonly prev: { isAudioMuted: boolean; isVideoMuted: boolean };
|
private readonly prev: { isAudioMuted: boolean; isVideoMuted: boolean };
|
||||||
|
|
||||||
constructor(
|
public constructor(
|
||||||
readonly oTel: ElementCallOpenTelemetry,
|
protected readonly oTel: ElementCallOpenTelemetry,
|
||||||
readonly callSpan: Span,
|
protected readonly callSpan: Span,
|
||||||
callFeed: CallFeedStats
|
callFeed: CallFeedStats
|
||||||
) {
|
) {
|
||||||
const postFix =
|
const postFix =
|
||||||
|
@ -1,3 +1,19 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2023 New Vector Ltd
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
import { TrackStats } from "matrix-js-sdk/src/webrtc/stats/statsReport";
|
import { TrackStats } from "matrix-js-sdk/src/webrtc/stats/statsReport";
|
||||||
import opentelemetry, { Span } from "@opentelemetry/api";
|
import opentelemetry, { Span } from "@opentelemetry/api";
|
||||||
|
|
||||||
@ -8,8 +24,8 @@ export class OTelCallMediaStreamTrackSpan {
|
|||||||
private prev: TrackStats;
|
private prev: TrackStats;
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
readonly oTel: ElementCallOpenTelemetry,
|
protected readonly oTel: ElementCallOpenTelemetry,
|
||||||
readonly streamSpan: Span,
|
protected readonly streamSpan: Span,
|
||||||
data: TrackStats
|
data: TrackStats
|
||||||
) {
|
) {
|
||||||
const ctx = opentelemetry.trace.setSpan(
|
const ctx = opentelemetry.trace.setSpan(
|
||||||
|
@ -1,3 +1,19 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2023 New Vector Ltd
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
import { Span } from "@opentelemetry/api";
|
import { Span } from "@opentelemetry/api";
|
||||||
import {
|
import {
|
||||||
TrackStats,
|
TrackStats,
|
||||||
@ -13,9 +29,9 @@ export class OTelCallTransceiverMediaStreamSpan extends OTelCallAbstractMediaStr
|
|||||||
currentDirection: string;
|
currentDirection: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(
|
public constructor(
|
||||||
readonly oTel: ElementCallOpenTelemetry,
|
protected readonly oTel: ElementCallOpenTelemetry,
|
||||||
readonly callSpan: Span,
|
protected readonly callSpan: Span,
|
||||||
stats: TransceiverStats
|
stats: TransceiverStats
|
||||||
) {
|
) {
|
||||||
super(oTel, callSpan, `matrix.call.transceiver.${stats.mid}`);
|
super(oTel, callSpan, `matrix.call.transceiver.${stats.mid}`);
|
||||||
|
@ -62,7 +62,7 @@ export class OTelGroupCallMembership {
|
|||||||
};
|
};
|
||||||
private readonly speakingSpans = new Map<RoomMember, Map<string, Span>>();
|
private readonly speakingSpans = new Map<RoomMember, Map<string, Span>>();
|
||||||
|
|
||||||
constructor(private groupCall: GroupCall, client: MatrixClient) {
|
public constructor(private groupCall: GroupCall, client: MatrixClient) {
|
||||||
const clientId = client.getUserId();
|
const clientId = client.getUserId();
|
||||||
if (clientId) {
|
if (clientId) {
|
||||||
this.myUserId = clientId;
|
this.myUserId = clientId;
|
||||||
@ -76,14 +76,14 @@ export class OTelGroupCallMembership {
|
|||||||
this.groupCall.on(GroupCallEvent.CallsChanged, this.onCallsChanged);
|
this.groupCall.on(GroupCallEvent.CallsChanged, this.onCallsChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose() {
|
public dispose(): void {
|
||||||
this.groupCall.removeListener(
|
this.groupCall.removeListener(
|
||||||
GroupCallEvent.CallsChanged,
|
GroupCallEvent.CallsChanged,
|
||||||
this.onCallsChanged
|
this.onCallsChanged
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public onJoinCall() {
|
public onJoinCall(): void {
|
||||||
if (!ElementCallOpenTelemetry.instance) return;
|
if (!ElementCallOpenTelemetry.instance) return;
|
||||||
if (this.callMembershipSpan !== undefined) {
|
if (this.callMembershipSpan !== undefined) {
|
||||||
logger.warn("Call membership span is already started");
|
logger.warn("Call membership span is already started");
|
||||||
@ -114,7 +114,7 @@ export class OTelGroupCallMembership {
|
|||||||
this.callMembershipSpan?.addEvent("matrix.joinCall");
|
this.callMembershipSpan?.addEvent("matrix.joinCall");
|
||||||
}
|
}
|
||||||
|
|
||||||
public onLeaveCall() {
|
public onLeaveCall(): void {
|
||||||
if (this.callMembershipSpan === undefined) {
|
if (this.callMembershipSpan === undefined) {
|
||||||
logger.warn("Call membership span is already ended");
|
logger.warn("Call membership span is already ended");
|
||||||
return;
|
return;
|
||||||
@ -127,7 +127,7 @@ export class OTelGroupCallMembership {
|
|||||||
this.groupCallContext = undefined;
|
this.groupCallContext = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
public onUpdateRoomState(event: MatrixEvent) {
|
public onUpdateRoomState(event: MatrixEvent): void {
|
||||||
if (
|
if (
|
||||||
!event ||
|
!event ||
|
||||||
(!event.getType().startsWith("m.call") &&
|
(!event.getType().startsWith("m.call") &&
|
||||||
@ -142,7 +142,7 @@ export class OTelGroupCallMembership {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public onCallsChanged = (calls: CallsByUserAndDevice) => {
|
public onCallsChanged(calls: CallsByUserAndDevice): void {
|
||||||
for (const [userId, userCalls] of calls.entries()) {
|
for (const [userId, userCalls] of calls.entries()) {
|
||||||
for (const [deviceId, call] of userCalls.entries()) {
|
for (const [deviceId, call] of userCalls.entries()) {
|
||||||
if (!this.callsByCallId.has(call.callId)) {
|
if (!this.callsByCallId.has(call.callId)) {
|
||||||
@ -179,9 +179,9 @@ export class OTelGroupCallMembership {
|
|||||||
this.callsByCallId.delete(callTrackingInfo.call.callId);
|
this.callsByCallId.delete(callTrackingInfo.call.callId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
public onCallStateChange(call: MatrixCall, newState: CallState) {
|
public onCallStateChange(call: MatrixCall, newState: CallState): void {
|
||||||
const callTrackingInfo = this.callsByCallId.get(call.callId);
|
const callTrackingInfo = this.callsByCallId.get(call.callId);
|
||||||
if (!callTrackingInfo) {
|
if (!callTrackingInfo) {
|
||||||
logger.error(`Got call state change for unknown call ID ${call.callId}`);
|
logger.error(`Got call state change for unknown call ID ${call.callId}`);
|
||||||
@ -193,7 +193,7 @@ export class OTelGroupCallMembership {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public onSendEvent(call: MatrixCall, event: VoipEvent) {
|
public onSendEvent(call: MatrixCall, event: VoipEvent): void {
|
||||||
const eventType = event.eventType as string;
|
const eventType = event.eventType as string;
|
||||||
if (
|
if (
|
||||||
!eventType.startsWith("m.call") &&
|
!eventType.startsWith("m.call") &&
|
||||||
@ -220,7 +220,7 @@ export class OTelGroupCallMembership {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public onReceivedVoipEvent(event: MatrixEvent) {
|
public onReceivedVoipEvent(event: MatrixEvent): void {
|
||||||
// These come straight from CallEventHandler so don't have
|
// These come straight from CallEventHandler so don't have
|
||||||
// a call already associated (in principle we could receive
|
// a call already associated (in principle we could receive
|
||||||
// events for calls we don't know about).
|
// events for calls we don't know about).
|
||||||
@ -251,37 +251,41 @@ export class OTelGroupCallMembership {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public onToggleMicrophoneMuted(newValue: boolean) {
|
public onToggleMicrophoneMuted(newValue: boolean): void {
|
||||||
this.callMembershipSpan?.addEvent("matrix.toggleMicMuted", {
|
this.callMembershipSpan?.addEvent("matrix.toggleMicMuted", {
|
||||||
"matrix.microphone.muted": newValue,
|
"matrix.microphone.muted": newValue,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public onSetMicrophoneMuted(setMuted: boolean) {
|
public onSetMicrophoneMuted(setMuted: boolean): void {
|
||||||
this.callMembershipSpan?.addEvent("matrix.setMicMuted", {
|
this.callMembershipSpan?.addEvent("matrix.setMicMuted", {
|
||||||
"matrix.microphone.muted": setMuted,
|
"matrix.microphone.muted": setMuted,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public onToggleLocalVideoMuted(newValue: boolean) {
|
public onToggleLocalVideoMuted(newValue: boolean): void {
|
||||||
this.callMembershipSpan?.addEvent("matrix.toggleVidMuted", {
|
this.callMembershipSpan?.addEvent("matrix.toggleVidMuted", {
|
||||||
"matrix.video.muted": newValue,
|
"matrix.video.muted": newValue,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public onSetLocalVideoMuted(setMuted: boolean) {
|
public onSetLocalVideoMuted(setMuted: boolean): void {
|
||||||
this.callMembershipSpan?.addEvent("matrix.setVidMuted", {
|
this.callMembershipSpan?.addEvent("matrix.setVidMuted", {
|
||||||
"matrix.video.muted": setMuted,
|
"matrix.video.muted": setMuted,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public onToggleScreensharing(newValue: boolean) {
|
public onToggleScreensharing(newValue: boolean): void {
|
||||||
this.callMembershipSpan?.addEvent("matrix.setVidMuted", {
|
this.callMembershipSpan?.addEvent("matrix.setVidMuted", {
|
||||||
"matrix.screensharing.enabled": newValue,
|
"matrix.screensharing.enabled": newValue,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public onSpeaking(member: RoomMember, deviceId: string, speaking: boolean) {
|
public onSpeaking(
|
||||||
|
member: RoomMember,
|
||||||
|
deviceId: string,
|
||||||
|
speaking: boolean
|
||||||
|
): void {
|
||||||
if (speaking) {
|
if (speaking) {
|
||||||
// Ensure that there's an audio activity span for this speaker
|
// Ensure that there's an audio activity span for this speaker
|
||||||
let deviceMap = this.speakingSpans.get(member);
|
let deviceMap = this.speakingSpans.get(member);
|
||||||
@ -311,7 +315,7 @@ export class OTelGroupCallMembership {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public onCallError(error: CallError, call: MatrixCall) {
|
public onCallError(error: CallError, call: MatrixCall): void {
|
||||||
const callTrackingInfo = this.callsByCallId.get(call.callId);
|
const callTrackingInfo = this.callsByCallId.get(call.callId);
|
||||||
if (!callTrackingInfo) {
|
if (!callTrackingInfo) {
|
||||||
logger.error(`Got error for unknown call ID ${call.callId}`);
|
logger.error(`Got error for unknown call ID ${call.callId}`);
|
||||||
@ -321,17 +325,19 @@ export class OTelGroupCallMembership {
|
|||||||
callTrackingInfo.span.recordException(error);
|
callTrackingInfo.span.recordException(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
public onGroupCallError(error: GroupCallError) {
|
public onGroupCallError(error: GroupCallError): void {
|
||||||
this.callMembershipSpan?.recordException(error);
|
this.callMembershipSpan?.recordException(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
public onUndecryptableToDevice(event: MatrixEvent) {
|
public onUndecryptableToDevice(event: MatrixEvent): void {
|
||||||
this.callMembershipSpan?.addEvent("matrix.toDevice.undecryptable", {
|
this.callMembershipSpan?.addEvent("matrix.toDevice.undecryptable", {
|
||||||
"sender.userId": event.getSender(),
|
"sender.userId": event.getSender(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public onCallFeedStatsReport(report: GroupCallStatsReport<CallFeedReport>) {
|
public onCallFeedStatsReport(
|
||||||
|
report: GroupCallStatsReport<CallFeedReport>
|
||||||
|
): void {
|
||||||
if (!ElementCallOpenTelemetry.instance) return;
|
if (!ElementCallOpenTelemetry.instance) return;
|
||||||
let call: OTelCall | undefined;
|
let call: OTelCall | undefined;
|
||||||
const callId = report.report?.callId;
|
const callId = report.report?.callId;
|
||||||
@ -362,7 +368,7 @@ export class OTelGroupCallMembership {
|
|||||||
|
|
||||||
public onConnectionStatsReport(
|
public onConnectionStatsReport(
|
||||||
statsReport: GroupCallStatsReport<ConnectionStatsReport>
|
statsReport: GroupCallStatsReport<ConnectionStatsReport>
|
||||||
) {
|
): void {
|
||||||
this.buildCallStatsSpan(
|
this.buildCallStatsSpan(
|
||||||
OTelStatsReportType.ConnectionReport,
|
OTelStatsReportType.ConnectionReport,
|
||||||
statsReport.report
|
statsReport.report
|
||||||
@ -371,7 +377,7 @@ export class OTelGroupCallMembership {
|
|||||||
|
|
||||||
public onByteSentStatsReport(
|
public onByteSentStatsReport(
|
||||||
statsReport: GroupCallStatsReport<ByteSentStatsReport>
|
statsReport: GroupCallStatsReport<ByteSentStatsReport>
|
||||||
) {
|
): void {
|
||||||
this.buildCallStatsSpan(
|
this.buildCallStatsSpan(
|
||||||
OTelStatsReportType.ByteSentReport,
|
OTelStatsReportType.ByteSentReport,
|
||||||
statsReport.report
|
statsReport.report
|
||||||
@ -431,7 +437,7 @@ export class OTelGroupCallMembership {
|
|||||||
|
|
||||||
public onSummaryStatsReport(
|
public onSummaryStatsReport(
|
||||||
statsReport: GroupCallStatsReport<SummaryStatsReport>
|
statsReport: GroupCallStatsReport<SummaryStatsReport>
|
||||||
) {
|
): void {
|
||||||
if (!ElementCallOpenTelemetry.instance) return;
|
if (!ElementCallOpenTelemetry.instance) return;
|
||||||
|
|
||||||
const type = OTelStatsReportType.SummaryReport;
|
const type = OTelStatsReportType.SummaryReport;
|
||||||
|
@ -45,9 +45,9 @@ export class ObjectFlattener {
|
|||||||
return flatObject;
|
return flatObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
static flattenSummaryStatsReportObject(
|
public static flattenSummaryStatsReportObject(
|
||||||
statsReport: GroupCallStatsReport<SummaryStatsReport>
|
statsReport: GroupCallStatsReport<SummaryStatsReport>
|
||||||
) {
|
): Attributes {
|
||||||
const flatObject = {};
|
const flatObject = {};
|
||||||
ObjectFlattener.flattenObjectRecursive(
|
ObjectFlattener.flattenObjectRecursive(
|
||||||
statsReport.report,
|
statsReport.report,
|
||||||
|
@ -36,7 +36,7 @@ export class ElementCallOpenTelemetry {
|
|||||||
private otlpExporter?: OTLPTraceExporter;
|
private otlpExporter?: OTLPTraceExporter;
|
||||||
public readonly rageshakeProcessor?: RageshakeSpanProcessor;
|
public readonly rageshakeProcessor?: RageshakeSpanProcessor;
|
||||||
|
|
||||||
static globalInit(): void {
|
public static globalInit(): void {
|
||||||
const config = Config.get();
|
const config = Config.get();
|
||||||
// we always enable opentelemetry in general. We only enable the OTLP
|
// we always enable opentelemetry in general. We only enable the OTLP
|
||||||
// collector if a URL is defined (and in future if another setting is defined)
|
// collector if a URL is defined (and in future if another setting is defined)
|
||||||
@ -55,11 +55,11 @@ export class ElementCallOpenTelemetry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static get instance(): ElementCallOpenTelemetry {
|
public static get instance(): ElementCallOpenTelemetry {
|
||||||
return sharedInstance;
|
return sharedInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(
|
private constructor(
|
||||||
collectorUrl: string | undefined,
|
collectorUrl: string | undefined,
|
||||||
rageshakeUrl: string | undefined
|
rageshakeUrl: string | undefined
|
||||||
) {
|
) {
|
||||||
|
@ -38,7 +38,11 @@ type ProfileSaveCallback = ({
|
|||||||
removeAvatar: boolean;
|
removeAvatar: boolean;
|
||||||
}) => Promise<void>;
|
}) => Promise<void>;
|
||||||
|
|
||||||
export function useProfile(client: MatrixClient | undefined) {
|
interface UseProfile extends ProfileLoadState {
|
||||||
|
saveProfile: ProfileSaveCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useProfile(client: MatrixClient | undefined): UseProfile {
|
||||||
const [{ success, loading, displayName, avatarUrl, error }, setState] =
|
const [{ success, loading, displayName, avatarUrl, error }, setState] =
|
||||||
useState<ProfileLoadState>(() => {
|
useState<ProfileLoadState>(() => {
|
||||||
let user: User | undefined = undefined;
|
let user: User | undefined = undefined;
|
||||||
@ -59,7 +63,7 @@ export function useProfile(client: MatrixClient | undefined) {
|
|||||||
const onChangeUser = (
|
const onChangeUser = (
|
||||||
_event: MatrixEvent | undefined,
|
_event: MatrixEvent | undefined,
|
||||||
{ displayName, avatarUrl }: User
|
{ displayName, avatarUrl }: User
|
||||||
) => {
|
): void => {
|
||||||
setState({
|
setState({
|
||||||
success: false,
|
success: false,
|
||||||
loading: false,
|
loading: false,
|
||||||
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { FC, FormEventHandler, useCallback, useState } from "react";
|
import { FC, FormEventHandler, ReactNode, useCallback, useState } from "react";
|
||||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
import { Trans, useTranslation } from "react-i18next";
|
import { Trans, useTranslation } from "react-i18next";
|
||||||
import { useHistory } from "react-router-dom";
|
import { useHistory } from "react-router-dom";
|
||||||
@ -148,7 +148,7 @@ export const CallEndedView: FC<Props> = ({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderBody = () => {
|
const renderBody = (): ReactNode => {
|
||||||
if (leaveError) {
|
if (leaveError) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -28,7 +28,8 @@ import {
|
|||||||
useContext,
|
useContext,
|
||||||
Dispatch,
|
Dispatch,
|
||||||
SetStateAction,
|
SetStateAction,
|
||||||
ReactNode,
|
FC,
|
||||||
|
PropsWithChildren,
|
||||||
} from "react";
|
} from "react";
|
||||||
import ReactJson, { CollapsedFieldProps } from "react-json-view";
|
import ReactJson, { CollapsedFieldProps } from "react-json-view";
|
||||||
import mermaid from "mermaid";
|
import mermaid from "mermaid";
|
||||||
@ -72,11 +73,11 @@ const defaultCollapsedFields = [
|
|||||||
"content",
|
"content",
|
||||||
];
|
];
|
||||||
|
|
||||||
function shouldCollapse({ name }: CollapsedFieldProps) {
|
function shouldCollapse({ name }: CollapsedFieldProps): boolean {
|
||||||
return name ? defaultCollapsedFields.includes(name) : false;
|
return name ? defaultCollapsedFields.includes(name) : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getUserName(userId: string) {
|
function getUserName(userId: string): string {
|
||||||
const match = userId.match(/@([^:]+):/);
|
const match = userId.match(/@([^:]+):/);
|
||||||
|
|
||||||
return match && match.length > 0
|
return match && match.length > 0
|
||||||
@ -84,7 +85,7 @@ function getUserName(userId: string) {
|
|||||||
: userId.replace(/\W/g, "");
|
: userId.replace(/\W/g, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatContent(type: string, content: CallEventContent) {
|
function formatContent(type: string, content: CallEventContent): string {
|
||||||
if (type === "m.call.hangup") {
|
if (type === "m.call.hangup") {
|
||||||
return `callId: ${content.call_id.slice(-4)} reason: ${
|
return `callId: ${content.call_id.slice(-4)} reason: ${
|
||||||
content.reason
|
content.reason
|
||||||
@ -123,7 +124,7 @@ const dateFormatter = new Intl.DateTimeFormat([], {
|
|||||||
fractionalSecondDigits: 3,
|
fractionalSecondDigits: 3,
|
||||||
});
|
});
|
||||||
|
|
||||||
function formatTimestamp(timestamp: number | Date) {
|
function formatTimestamp(timestamp: number | Date): string {
|
||||||
return dateFormatter.format(timestamp);
|
return dateFormatter.format(timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,11 +146,9 @@ export const InspectorContext =
|
|||||||
[InspectorContextState, Dispatch<SetStateAction<InspectorContextState>>]
|
[InspectorContextState, Dispatch<SetStateAction<InspectorContextState>>]
|
||||||
>(undefined);
|
>(undefined);
|
||||||
|
|
||||||
export function InspectorContextProvider({
|
export const InspectorContextProvider: FC<PropsWithChildren<{}>> = ({
|
||||||
children,
|
children,
|
||||||
}: {
|
}) => {
|
||||||
children: ReactNode;
|
|
||||||
}) {
|
|
||||||
// We take the tuple of [currentState, setter] and stick
|
// We take the tuple of [currentState, setter] and stick
|
||||||
// it straight into the context for other things to call
|
// it straight into the context for other things to call
|
||||||
// the setState method... this feels like a fairly severe
|
// the setState method... this feels like a fairly severe
|
||||||
@ -161,7 +160,7 @@ export function InspectorContextProvider({
|
|||||||
{children}
|
{children}
|
||||||
</InspectorContext.Provider>
|
</InspectorContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
type CallEventContent = {
|
type CallEventContent = {
|
||||||
["m.calls"]: {
|
["m.calls"]: {
|
||||||
@ -192,13 +191,13 @@ interface SequenceDiagramViewerProps {
|
|||||||
events: SequenceDiagramMatrixEvent[];
|
events: SequenceDiagramMatrixEvent[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SequenceDiagramViewer({
|
export const SequenceDiagramViewer: FC<SequenceDiagramViewerProps> = ({
|
||||||
localUserId,
|
localUserId,
|
||||||
remoteUserIds,
|
remoteUserIds,
|
||||||
selectedUserId,
|
selectedUserId,
|
||||||
onSelectUserId,
|
onSelectUserId,
|
||||||
events,
|
events,
|
||||||
}: SequenceDiagramViewerProps) {
|
}) => {
|
||||||
const mermaidElRef = useRef<HTMLDivElement>(null);
|
const mermaidElRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -232,7 +231,7 @@ export function SequenceDiagramViewer({
|
|||||||
className={styles.selectInput}
|
className={styles.selectInput}
|
||||||
label="Remote User"
|
label="Remote User"
|
||||||
selectedKey={selectedUserId}
|
selectedKey={selectedUserId}
|
||||||
onSelectionChange={(key) => onSelectUserId(key.toString())}
|
onSelectionChange={(key): void => onSelectUserId(key.toString())}
|
||||||
>
|
>
|
||||||
{remoteUserIds.map((userId) => (
|
{remoteUserIds.map((userId) => (
|
||||||
<Item key={userId}>{userId}</Item>
|
<Item key={userId}>{userId}</Item>
|
||||||
@ -243,7 +242,7 @@ export function SequenceDiagramViewer({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
function reducer(
|
function reducer(
|
||||||
state: InspectorContextState,
|
state: InspectorContextState,
|
||||||
@ -254,7 +253,7 @@ function reducer(
|
|||||||
callStateEvent?: MatrixEvent;
|
callStateEvent?: MatrixEvent;
|
||||||
memberStateEvents?: MatrixEvent[];
|
memberStateEvents?: MatrixEvent[];
|
||||||
}
|
}
|
||||||
) {
|
): InspectorContextState {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case RoomStateEvent.Events: {
|
case RoomStateEvent.Events: {
|
||||||
const { event, callStateEvent, memberStateEvents } = action;
|
const { event, callStateEvent, memberStateEvents } = action;
|
||||||
@ -380,7 +379,7 @@ function useGroupCallState(
|
|||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function onUpdateRoomState(event?: MatrixEvent) {
|
function onUpdateRoomState(event?: MatrixEvent): void {
|
||||||
const callStateEvent = groupCall.room.currentState.getStateEvents(
|
const callStateEvent = groupCall.room.currentState.getStateEvents(
|
||||||
"org.matrix.msc3401.call",
|
"org.matrix.msc3401.call",
|
||||||
groupCall.groupCallId
|
groupCall.groupCallId
|
||||||
@ -400,13 +399,13 @@ function useGroupCallState(
|
|||||||
otelGroupCallMembership?.onUpdateRoomState(event);
|
otelGroupCallMembership?.onUpdateRoomState(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onReceivedVoipEvent(event: MatrixEvent) {
|
function onReceivedVoipEvent(event: MatrixEvent): void {
|
||||||
dispatch({ type: ClientEvent.ReceivedVoipEvent, event });
|
dispatch({ type: ClientEvent.ReceivedVoipEvent, event });
|
||||||
|
|
||||||
otelGroupCallMembership?.onReceivedVoipEvent(event);
|
otelGroupCallMembership?.onReceivedVoipEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onSendVoipEvent(event: VoipEvent, call: MatrixCall) {
|
function onSendVoipEvent(event: VoipEvent, call: MatrixCall): void {
|
||||||
dispatch({ type: CallEvent.SendVoipEvent, rawEvent: event });
|
dispatch({ type: CallEvent.SendVoipEvent, rawEvent: event });
|
||||||
|
|
||||||
otelGroupCallMembership?.onSendEvent(call, event);
|
otelGroupCallMembership?.onSendEvent(call, event);
|
||||||
@ -416,19 +415,19 @@ function useGroupCallState(
|
|||||||
newState: CallState,
|
newState: CallState,
|
||||||
_: CallState,
|
_: CallState,
|
||||||
call: MatrixCall
|
call: MatrixCall
|
||||||
) {
|
): void {
|
||||||
otelGroupCallMembership?.onCallStateChange(call, newState);
|
otelGroupCallMembership?.onCallStateChange(call, newState);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onCallError(error: CallError, call: MatrixCall) {
|
function onCallError(error: CallError, call: MatrixCall): void {
|
||||||
otelGroupCallMembership.onCallError(error, call);
|
otelGroupCallMembership.onCallError(error, call);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onGroupCallError(error: GroupCallError) {
|
function onGroupCallError(error: GroupCallError): void {
|
||||||
otelGroupCallMembership.onGroupCallError(error);
|
otelGroupCallMembership.onGroupCallError(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onUndecryptableToDevice(event: MatrixEvent) {
|
function onUndecryptableToDevice(event: MatrixEvent): void {
|
||||||
dispatch({ type: ClientEvent.ReceivedVoipEvent, event });
|
dispatch({ type: ClientEvent.ReceivedVoipEvent, event });
|
||||||
|
|
||||||
Sentry.captureMessage("Undecryptable to-device Event");
|
Sentry.captureMessage("Undecryptable to-device Event");
|
||||||
@ -478,12 +477,12 @@ interface GroupCallInspectorProps {
|
|||||||
show: boolean;
|
show: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function GroupCallInspector({
|
export const GroupCallInspector: FC<GroupCallInspectorProps> = ({
|
||||||
client,
|
client,
|
||||||
groupCall,
|
groupCall,
|
||||||
otelGroupCallMembership,
|
otelGroupCallMembership,
|
||||||
show,
|
show,
|
||||||
}: GroupCallInspectorProps) {
|
}) => {
|
||||||
const [currentTab, setCurrentTab] = useState("sequence-diagrams");
|
const [currentTab, setCurrentTab] = useState("sequence-diagrams");
|
||||||
const [selectedUserId, setSelectedUserId] = useState<string>();
|
const [selectedUserId, setSelectedUserId] = useState<string>();
|
||||||
const state = useGroupCallState(client, groupCall, otelGroupCallMembership);
|
const state = useGroupCallState(client, groupCall, otelGroupCallMembership);
|
||||||
@ -506,10 +505,12 @@ export function GroupCallInspector({
|
|||||||
className={styles.inspector}
|
className={styles.inspector}
|
||||||
>
|
>
|
||||||
<div className={styles.toolbar}>
|
<div className={styles.toolbar}>
|
||||||
<button onClick={() => setCurrentTab("sequence-diagrams")}>
|
<button onClick={(): void => setCurrentTab("sequence-diagrams")}>
|
||||||
Sequence Diagrams
|
Sequence Diagrams
|
||||||
</button>
|
</button>
|
||||||
<button onClick={() => setCurrentTab("inspector")}>Inspector</button>
|
<button onClick={(): void => setCurrentTab("inspector")}>
|
||||||
|
Inspector
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{currentTab === "sequence-diagrams" &&
|
{currentTab === "sequence-diagrams" &&
|
||||||
state.localUserId &&
|
state.localUserId &&
|
||||||
@ -539,4 +540,4 @@ export function GroupCallInspector({
|
|||||||
)}
|
)}
|
||||||
</Resizable>
|
</Resizable>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
import { FC, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { useHistory } from "react-router-dom";
|
import { useHistory } from "react-router-dom";
|
||||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
import { Room, isE2EESupported } from "livekit-client";
|
import { Room, isE2EESupported } from "livekit-client";
|
||||||
@ -64,14 +64,14 @@ interface Props {
|
|||||||
rtcSession: MatrixRTCSession;
|
rtcSession: MatrixRTCSession;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function GroupCallView({
|
export const GroupCallView: FC<Props> = ({
|
||||||
client,
|
client,
|
||||||
isPasswordlessUser,
|
isPasswordlessUser,
|
||||||
confineToRoom,
|
confineToRoom,
|
||||||
preload,
|
preload,
|
||||||
hideHeader,
|
hideHeader,
|
||||||
rtcSession,
|
rtcSession,
|
||||||
}: Props) {
|
}) => {
|
||||||
const memberships = useMatrixRTCSessionMemberships(rtcSession);
|
const memberships = useMatrixRTCSessionMemberships(rtcSession);
|
||||||
const isJoined = useMatrixRTCSessionJoinState(rtcSession);
|
const isJoined = useMatrixRTCSessionJoinState(rtcSession);
|
||||||
|
|
||||||
@ -135,7 +135,9 @@ export function GroupCallView({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (widget && preload) {
|
if (widget && preload) {
|
||||||
// In preload mode, wait for a join action before entering
|
// In preload mode, wait for a join action before entering
|
||||||
const onJoin = async (ev: CustomEvent<IWidgetApiRequest>) => {
|
const onJoin = async (
|
||||||
|
ev: CustomEvent<IWidgetApiRequest>
|
||||||
|
): Promise<void> => {
|
||||||
// XXX: I think this is broken currently - LiveKit *won't* request
|
// XXX: I think this is broken currently - LiveKit *won't* request
|
||||||
// permissions and give you device names unless you specify a kind, but
|
// permissions and give you device names unless you specify a kind, but
|
||||||
// here we want all kinds of devices. This needs a fix in livekit-client
|
// here we want all kinds of devices. This needs a fix in livekit-client
|
||||||
@ -247,9 +249,11 @@ export function GroupCallView({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (widget && isJoined) {
|
if (widget && isJoined) {
|
||||||
const onHangup = async (ev: CustomEvent<IWidgetApiRequest>) => {
|
const onHangup = async (
|
||||||
|
ev: CustomEvent<IWidgetApiRequest>
|
||||||
|
): Promise<void> => {
|
||||||
leaveRTCSession(rtcSession);
|
leaveRTCSession(rtcSession);
|
||||||
await widget!.api.transport.reply(ev.detail, {});
|
widget!.api.transport.reply(ev.detail, {});
|
||||||
widget!.api.setAlwaysOnScreen(false);
|
widget!.api.setAlwaysOnScreen(false);
|
||||||
};
|
};
|
||||||
widget.lazyActions.once(ElementWidgetActions.HangupCall, onHangup);
|
widget.lazyActions.once(ElementWidgetActions.HangupCall, onHangup);
|
||||||
@ -388,7 +392,7 @@ export function GroupCallView({
|
|||||||
client={client}
|
client={client}
|
||||||
matrixInfo={matrixInfo}
|
matrixInfo={matrixInfo}
|
||||||
muteStates={muteStates}
|
muteStates={muteStates}
|
||||||
onEnter={() => enterRTCSession(rtcSession)}
|
onEnter={(): void => enterRTCSession(rtcSession)}
|
||||||
confineToRoom={confineToRoom}
|
confineToRoom={confineToRoom}
|
||||||
hideHeader={hideHeader}
|
hideHeader={hideHeader}
|
||||||
participatingMembers={participatingMembers}
|
participatingMembers={participatingMembers}
|
||||||
@ -397,4 +401,4 @@ export function GroupCallView({
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
@ -27,7 +27,16 @@ import { ConnectionState, Room, Track } from "livekit-client";
|
|||||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
||||||
import { Room as MatrixRoom } from "matrix-js-sdk/src/models/room";
|
import { Room as MatrixRoom } from "matrix-js-sdk/src/models/room";
|
||||||
import { Ref, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
import {
|
||||||
|
FC,
|
||||||
|
ReactNode,
|
||||||
|
Ref,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import useMeasure from "react-use-measure";
|
import useMeasure from "react-use-measure";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
@ -91,7 +100,7 @@ export interface ActiveCallProps
|
|||||||
e2eeConfig?: E2EEConfig;
|
e2eeConfig?: E2EEConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ActiveCall(props: ActiveCallProps) {
|
export const ActiveCall: FC<ActiveCallProps> = (props) => {
|
||||||
const sfuConfig = useOpenIDSFU(props.client, props.rtcSession);
|
const sfuConfig = useOpenIDSFU(props.client, props.rtcSession);
|
||||||
const { livekitRoom, connState } = useLiveKit(
|
const { livekitRoom, connState } = useLiveKit(
|
||||||
props.muteStates,
|
props.muteStates,
|
||||||
@ -112,7 +121,7 @@ export function ActiveCall(props: ActiveCallProps) {
|
|||||||
<InCallView {...props} livekitRoom={livekitRoom} connState={connState} />
|
<InCallView {...props} livekitRoom={livekitRoom} connState={connState} />
|
||||||
</RoomContext.Provider>
|
</RoomContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export interface InCallViewProps {
|
export interface InCallViewProps {
|
||||||
client: MatrixClient;
|
client: MatrixClient;
|
||||||
@ -128,7 +137,7 @@ export interface InCallViewProps {
|
|||||||
onShareClick: (() => void) | null;
|
onShareClick: (() => void) | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function InCallView({
|
export const InCallView: FC<InCallViewProps> = ({
|
||||||
client,
|
client,
|
||||||
matrixInfo,
|
matrixInfo,
|
||||||
rtcSession,
|
rtcSession,
|
||||||
@ -140,7 +149,7 @@ export function InCallView({
|
|||||||
otelGroupCallMembership,
|
otelGroupCallMembership,
|
||||||
connState,
|
connState,
|
||||||
onShareClick,
|
onShareClick,
|
||||||
}: InCallViewProps) {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
usePreventScroll();
|
usePreventScroll();
|
||||||
useWakeLock();
|
useWakeLock();
|
||||||
@ -211,13 +220,13 @@ export function InCallView({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (widget) {
|
if (widget) {
|
||||||
const onTileLayout = async (ev: CustomEvent<IWidgetApiRequest>) => {
|
const onTileLayout = (ev: CustomEvent<IWidgetApiRequest>): void => {
|
||||||
setLayout("grid");
|
setLayout("grid");
|
||||||
await widget!.api.transport.reply(ev.detail, {});
|
widget!.api.transport.reply(ev.detail, {});
|
||||||
};
|
};
|
||||||
const onSpotlightLayout = async (ev: CustomEvent<IWidgetApiRequest>) => {
|
const onSpotlightLayout = (ev: CustomEvent<IWidgetApiRequest>): void => {
|
||||||
setLayout("spotlight");
|
setLayout("spotlight");
|
||||||
await widget!.api.transport.reply(ev.detail, {});
|
widget!.api.transport.reply(ev.detail, {});
|
||||||
};
|
};
|
||||||
|
|
||||||
widget.lazyActions.on(ElementWidgetActions.TileLayout, onTileLayout);
|
widget.lazyActions.on(ElementWidgetActions.TileLayout, onTileLayout);
|
||||||
@ -296,7 +305,7 @@ export function InCallView({
|
|||||||
disableAnimations={prefersReducedMotion || isSafari}
|
disableAnimations={prefersReducedMotion || isSafari}
|
||||||
layoutStates={layoutStates}
|
layoutStates={layoutStates}
|
||||||
>
|
>
|
||||||
{(props) => (
|
{(props): ReactNode => (
|
||||||
<VideoTile
|
<VideoTile
|
||||||
maximised={false}
|
maximised={false}
|
||||||
fullscreen={false}
|
fullscreen={false}
|
||||||
@ -444,7 +453,7 @@ export function InCallView({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
function findMatrixMember(
|
function findMatrixMember(
|
||||||
room: MatrixRoom,
|
room: MatrixRoom,
|
||||||
|
@ -17,7 +17,7 @@ limitations under the License.
|
|||||||
import { FC, useEffect } from "react";
|
import { FC, useEffect } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { Modal, ModalProps } from "../Modal";
|
import { Modal, Props as ModalProps } from "../Modal";
|
||||||
import { Button } from "../button";
|
import { Button } from "../button";
|
||||||
import { FieldRow, ErrorMessage } from "../input/Input";
|
import { FieldRow, ErrorMessage } from "../input/Input";
|
||||||
import { useSubmitRageshake } from "../settings/submit-rageshake";
|
import { useSubmitRageshake } from "../settings/submit-rageshake";
|
||||||
@ -52,8 +52,8 @@ export const RageshakeRequestModal: FC<Props> = ({
|
|||||||
</Body>
|
</Body>
|
||||||
<FieldRow>
|
<FieldRow>
|
||||||
<Button
|
<Button
|
||||||
onPress={() =>
|
onPress={(): void =>
|
||||||
submitRageshake({
|
void submitRageshake({
|
||||||
sendLogs: true,
|
sendLogs: true,
|
||||||
rageshakeRequestId,
|
rageshakeRequestId,
|
||||||
roomId,
|
roomId,
|
||||||
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useCallback, useState } from "react";
|
import { FC, useCallback, useState } from "react";
|
||||||
import { useLocation } from "react-router-dom";
|
import { useLocation } from "react-router-dom";
|
||||||
import { Trans, useTranslation } from "react-i18next";
|
import { Trans, useTranslation } from "react-i18next";
|
||||||
|
|
||||||
@ -28,7 +28,7 @@ import { UserMenuContainer } from "../UserMenuContainer";
|
|||||||
import { useRegisterPasswordlessUser } from "../auth/useRegisterPasswordlessUser";
|
import { useRegisterPasswordlessUser } from "../auth/useRegisterPasswordlessUser";
|
||||||
import { Config } from "../config/Config";
|
import { Config } from "../config/Config";
|
||||||
|
|
||||||
export function RoomAuthView() {
|
export const RoomAuthView: FC = () => {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState<Error>();
|
const [error, setError] = useState<Error>();
|
||||||
|
|
||||||
@ -121,4 +121,4 @@ export function RoomAuthView() {
|
|||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
@ -22,11 +22,11 @@ import { TileDescriptor } from "../video-grid/VideoGrid";
|
|||||||
import { useReactiveState } from "../useReactiveState";
|
import { useReactiveState } from "../useReactiveState";
|
||||||
import { useEventTarget } from "../useEvents";
|
import { useEventTarget } from "../useEvents";
|
||||||
|
|
||||||
const isFullscreen = () =>
|
const isFullscreen = (): boolean =>
|
||||||
Boolean(document.fullscreenElement) ||
|
Boolean(document.fullscreenElement) ||
|
||||||
Boolean(document.webkitFullscreenElement);
|
Boolean(document.webkitFullscreenElement);
|
||||||
|
|
||||||
function enterFullscreen() {
|
function enterFullscreen(): void {
|
||||||
if (document.body.requestFullscreen) {
|
if (document.body.requestFullscreen) {
|
||||||
document.body.requestFullscreen();
|
document.body.requestFullscreen();
|
||||||
} else if (document.body.webkitRequestFullscreen) {
|
} else if (document.body.webkitRequestFullscreen) {
|
||||||
@ -36,7 +36,7 @@ function enterFullscreen() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function exitFullscreen() {
|
function exitFullscreen(): void {
|
||||||
if (document.exitFullscreen) {
|
if (document.exitFullscreen) {
|
||||||
document.exitFullscreen();
|
document.exitFullscreen();
|
||||||
} else if (document.webkitExitFullscreen) {
|
} else if (document.webkitExitFullscreen) {
|
||||||
@ -46,7 +46,7 @@ function exitFullscreen() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function useFullscreenChange(onFullscreenChange: () => void) {
|
function useFullscreenChange(onFullscreenChange: () => void): void {
|
||||||
useEventTarget(document.body, "fullscreenchange", onFullscreenChange);
|
useEventTarget(document.body, "fullscreenchange", onFullscreenChange);
|
||||||
useEventTarget(document.body, "webkitfullscreenchange", onFullscreenChange);
|
useEventTarget(document.body, "webkitfullscreenchange", onFullscreenChange);
|
||||||
}
|
}
|
||||||
|
@ -15,12 +15,14 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
|
import { JoinRule } from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
import type { Room } from "matrix-js-sdk/src/models/room";
|
import type { Room } from "matrix-js-sdk/src/models/room";
|
||||||
import { useRoomState } from "./useRoomState";
|
import { useRoomState } from "./useRoomState";
|
||||||
|
|
||||||
export const useJoinRule = (room: Room) =>
|
export function useJoinRule(room: Room): JoinRule {
|
||||||
useRoomState(
|
return useRoomState(
|
||||||
room,
|
room,
|
||||||
useCallback((state) => state.getJoinRule(), [])
|
useCallback((state) => state.getJoinRule(), [])
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
@ -107,13 +107,13 @@ export const useLoadGroupCall = (
|
|||||||
return rtcSession;
|
return rtcSession;
|
||||||
};
|
};
|
||||||
|
|
||||||
const waitForClientSyncing = async () => {
|
const waitForClientSyncing = async (): Promise<void> => {
|
||||||
if (client.getSyncState() !== SyncState.Syncing) {
|
if (client.getSyncState() !== SyncState.Syncing) {
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"useLoadGroupCall: waiting for client to start syncing..."
|
"useLoadGroupCall: waiting for client to start syncing..."
|
||||||
);
|
);
|
||||||
await new Promise<void>((resolve) => {
|
await new Promise<void>((resolve) => {
|
||||||
const onSync = () => {
|
const onSync = (): void => {
|
||||||
if (client.getSyncState() === SyncState.Syncing) {
|
if (client.getSyncState() === SyncState.Syncing) {
|
||||||
client.off(ClientEvent.Sync, onSync);
|
client.off(ClientEvent.Sync, onSync);
|
||||||
return resolve();
|
return resolve();
|
||||||
|
@ -18,11 +18,11 @@ import { useEffect } from "react";
|
|||||||
|
|
||||||
import { platform } from "../Platform";
|
import { platform } from "../Platform";
|
||||||
|
|
||||||
export function usePageUnload(callback: () => void) {
|
export function usePageUnload(callback: () => void): void {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let pageVisibilityTimeout: ReturnType<typeof setTimeout>;
|
let pageVisibilityTimeout: ReturnType<typeof setTimeout>;
|
||||||
|
|
||||||
function onBeforeUnload(event: PageTransitionEvent) {
|
function onBeforeUnload(event: PageTransitionEvent): void {
|
||||||
if (event.type === "visibilitychange") {
|
if (event.type === "visibilitychange") {
|
||||||
if (document.visibilityState === "visible") {
|
if (document.visibilityState === "visible") {
|
||||||
clearTimeout(pageVisibilityTimeout);
|
clearTimeout(pageVisibilityTimeout);
|
||||||
|
@ -19,8 +19,9 @@ import { Room } from "matrix-js-sdk/src/models/room";
|
|||||||
|
|
||||||
import { useRoomState } from "./useRoomState";
|
import { useRoomState } from "./useRoomState";
|
||||||
|
|
||||||
export const useRoomAvatar = (room: Room) =>
|
export function useRoomAvatar(room: Room): string | null {
|
||||||
useRoomState(
|
return useRoomState(
|
||||||
room,
|
room,
|
||||||
useCallback(() => room.getMxcAvatarUrl(), [room])
|
useCallback(() => room.getMxcAvatarUrl(), [room])
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
@ -33,7 +33,7 @@ function makeFocus(livekitAlias: string): LivekitFocus {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function enterRTCSession(rtcSession: MatrixRTCSession) {
|
export function enterRTCSession(rtcSession: MatrixRTCSession): void {
|
||||||
PosthogAnalytics.instance.eventCallEnded.cacheStartCall(new Date());
|
PosthogAnalytics.instance.eventCallEnded.cacheStartCall(new Date());
|
||||||
PosthogAnalytics.instance.eventCallStarted.track(rtcSession.room.roomId);
|
PosthogAnalytics.instance.eventCallStarted.track(rtcSession.room.roomId);
|
||||||
|
|
||||||
@ -47,7 +47,7 @@ export function enterRTCSession(rtcSession: MatrixRTCSession) {
|
|||||||
rtcSession.joinRoomSession([makeFocus(livekitAlias)]);
|
rtcSession.joinRoomSession([makeFocus(livekitAlias)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function leaveRTCSession(rtcSession: MatrixRTCSession) {
|
export function leaveRTCSession(rtcSession: MatrixRTCSession): void {
|
||||||
//groupCallOTelMembership?.onLeaveCall();
|
//groupCallOTelMembership?.onLeaveCall();
|
||||||
rtcSession.leaveRoomSession();
|
rtcSession.leaveRoomSession();
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useCallback } from "react";
|
import { FC, useCallback } from "react";
|
||||||
import { randomString } from "matrix-js-sdk/src/randomstring";
|
import { randomString } from "matrix-js-sdk/src/randomstring";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
@ -29,7 +29,7 @@ interface Props {
|
|||||||
roomId?: string;
|
roomId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function FeedbackSettingsTab({ roomId }: Props) {
|
export const FeedbackSettingsTab: FC<Props> = ({ roomId }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { submitRageshake, sending, sent, error } = useSubmitRageshake();
|
const { submitRageshake, sending, sent, error } = useSubmitRageshake();
|
||||||
const sendRageshakeRequest = useRageshakeRequest();
|
const sendRageshakeRequest = useRageshakeRequest();
|
||||||
@ -104,4 +104,4 @@ export function FeedbackSettingsTab({ roomId }: Props) {
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useCallback, useEffect, useMemo, useRef } from "react";
|
import { FC, useCallback, useEffect, useMemo, useRef } from "react";
|
||||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
@ -26,7 +26,7 @@ import styles from "./ProfileSettingsTab.module.css";
|
|||||||
interface Props {
|
interface Props {
|
||||||
client: MatrixClient;
|
client: MatrixClient;
|
||||||
}
|
}
|
||||||
export function ProfileSettingsTab({ client }: Props) {
|
export const ProfileSettingsTab: FC<Props> = ({ client }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { error, displayName, avatarUrl, saveProfile } = useProfile(client);
|
const { error, displayName, avatarUrl, saveProfile } = useProfile(client);
|
||||||
const userId = useMemo(() => client.getUserId(), [client]);
|
const userId = useMemo(() => client.getUserId(), [client]);
|
||||||
@ -120,4 +120,4 @@ export function ProfileSettingsTab({ client }: Props) {
|
|||||||
)}
|
)}
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
@ -15,7 +15,7 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useCallback } from "react";
|
import { FC, useCallback } from "react";
|
||||||
|
|
||||||
import { Button } from "../button";
|
import { Button } from "../button";
|
||||||
import { Config } from "../config/Config";
|
import { Config } from "../config/Config";
|
||||||
@ -26,7 +26,7 @@ interface Props {
|
|||||||
description: string;
|
description: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RageshakeButton = ({ description }: Props) => {
|
export const RageshakeButton: FC<Props> = ({ description }) => {
|
||||||
const { submitRageshake, sending, sent, error } = useSubmitRageshake();
|
const { submitRageshake, sending, sent, error } = useSubmitRageshake();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ChangeEvent, Key, useCallback, useState } from "react";
|
import { ChangeEvent, FC, Key, ReactNode, useCallback, useState } from "react";
|
||||||
import { Item } from "@react-stately/collections";
|
import { Item } from "@react-stately/collections";
|
||||||
import { Trans, useTranslation } from "react-i18next";
|
import { Trans, useTranslation } from "react-i18next";
|
||||||
import { MatrixClient } from "matrix-js-sdk";
|
import { MatrixClient } from "matrix-js-sdk";
|
||||||
@ -59,7 +59,7 @@ interface Props {
|
|||||||
defaultTab?: string;
|
defaultTab?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SettingsModal = (props: Props) => {
|
export const SettingsModal: FC<Props> = (props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [showInspector, setShowInspector] = useShowInspector();
|
const [showInspector, setShowInspector] = useShowInspector();
|
||||||
@ -73,7 +73,10 @@ export const SettingsModal = (props: Props) => {
|
|||||||
const downloadDebugLog = useDownloadDebugLog();
|
const downloadDebugLog = useDownloadDebugLog();
|
||||||
|
|
||||||
// Generate a `SelectInput` with a list of devices for a given device kind.
|
// Generate a `SelectInput` with a list of devices for a given device kind.
|
||||||
const generateDeviceSelection = (devices: MediaDevice, caption: string) => {
|
const generateDeviceSelection = (
|
||||||
|
devices: MediaDevice,
|
||||||
|
caption: string
|
||||||
|
): ReactNode => {
|
||||||
if (devices.available.length == 0) return null;
|
if (devices.available.length == 0) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -84,7 +87,7 @@ export const SettingsModal = (props: Props) => {
|
|||||||
? "default"
|
? "default"
|
||||||
: devices.selectedId
|
: devices.selectedId
|
||||||
}
|
}
|
||||||
onSelectionChange={(id) => devices.select(id.toString())}
|
onSelectionChange={(id): void => devices.select(id.toString())}
|
||||||
>
|
>
|
||||||
{devices.available.map(({ deviceId, label }, index) => (
|
{devices.available.map(({ deviceId, label }, index) => (
|
||||||
<Item key={deviceId}>
|
<Item key={deviceId}>
|
||||||
@ -197,7 +200,7 @@ export const SettingsModal = (props: Props) => {
|
|||||||
checked={developerSettingsTab}
|
checked={developerSettingsTab}
|
||||||
label={t("Developer Settings")}
|
label={t("Developer Settings")}
|
||||||
description={t("Expose developer settings in the settings window.")}
|
description={t("Expose developer settings in the settings window.")}
|
||||||
onChange={(event: ChangeEvent<HTMLInputElement>) =>
|
onChange={(event: ChangeEvent<HTMLInputElement>): void =>
|
||||||
setDeveloperSettingsTab(event.target.checked)
|
setDeveloperSettingsTab(event.target.checked)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@ -209,7 +212,7 @@ export const SettingsModal = (props: Props) => {
|
|||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={optInAnalytics ?? undefined}
|
checked={optInAnalytics ?? undefined}
|
||||||
description={optInDescription}
|
description={optInDescription}
|
||||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
onChange={(event: ChangeEvent<HTMLInputElement>): void => {
|
||||||
setOptInAnalytics?.(event.target.checked);
|
setOptInAnalytics?.(event.target.checked);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -241,7 +244,7 @@ export const SettingsModal = (props: Props) => {
|
|||||||
label={t("Show call inspector")}
|
label={t("Show call inspector")}
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={showInspector}
|
checked={showInspector}
|
||||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
onChange={(e: ChangeEvent<HTMLInputElement>): void =>
|
||||||
setShowInspector(e.target.checked)
|
setShowInspector(e.target.checked)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@ -253,7 +256,7 @@ export const SettingsModal = (props: Props) => {
|
|||||||
label={t("Show connection stats")}
|
label={t("Show connection stats")}
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={showConnectionStats}
|
checked={showConnectionStats}
|
||||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
onChange={(e: ChangeEvent<HTMLInputElement>): void =>
|
||||||
setShowConnectionStats(e.target.checked)
|
setShowConnectionStats(e.target.checked)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@ -270,7 +273,7 @@ export const SettingsModal = (props: Props) => {
|
|||||||
disabled={!setEnableE2EE}
|
disabled={!setEnableE2EE}
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={enableE2EE ?? undefined}
|
checked={enableE2EE ?? undefined}
|
||||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
onChange={(e: ChangeEvent<HTMLInputElement>): void =>
|
||||||
setEnableE2EE?.(e.target.checked)
|
setEnableE2EE?.(e.target.checked)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
@ -89,7 +89,7 @@ class ConsoleLogger extends EventEmitter {
|
|||||||
this.originalFunctions[name] = originalFn;
|
this.originalFunctions[name] = originalFn;
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
consoleObj[name] = (...args) => {
|
consoleObj[name] = (...args): void => {
|
||||||
this.log(level, ...args);
|
this.log(level, ...args);
|
||||||
originalFn(...args);
|
originalFn(...args);
|
||||||
};
|
};
|
||||||
@ -158,7 +158,7 @@ class IndexedDBLogStore {
|
|||||||
private flushAgainPromise?: Promise<void>;
|
private flushAgainPromise?: Promise<void>;
|
||||||
private id: string;
|
private id: string;
|
||||||
|
|
||||||
constructor(
|
public constructor(
|
||||||
private indexedDB: IDBFactory,
|
private indexedDB: IDBFactory,
|
||||||
private loggerInstance: ConsoleLogger
|
private loggerInstance: ConsoleLogger
|
||||||
) {
|
) {
|
||||||
@ -174,20 +174,20 @@ class IndexedDBLogStore {
|
|||||||
public connect(): Promise<void> {
|
public connect(): Promise<void> {
|
||||||
const req = this.indexedDB.open("logs");
|
const req = this.indexedDB.open("logs");
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
req.onsuccess = () => {
|
req.onsuccess = (): void => {
|
||||||
this.db = req.result;
|
this.db = req.result;
|
||||||
|
|
||||||
resolve();
|
resolve();
|
||||||
};
|
};
|
||||||
|
|
||||||
req.onerror = () => {
|
req.onerror = (): void => {
|
||||||
const err = "Failed to open log database: " + req?.error?.name;
|
const err = "Failed to open log database: " + req?.error?.name;
|
||||||
logger.error(err);
|
logger.error(err);
|
||||||
reject(new Error(err));
|
reject(new Error(err));
|
||||||
};
|
};
|
||||||
|
|
||||||
// First time: Setup the object store
|
// First time: Setup the object store
|
||||||
req.onupgradeneeded = () => {
|
req.onupgradeneeded = (): void => {
|
||||||
const db = req.result;
|
const db = req.result;
|
||||||
// This is the log entries themselves. Each entry is a chunk of
|
// This is the log entries themselves. Each entry is a chunk of
|
||||||
// logs (ie multiple lines). 'id' is the instance ID (so logs with
|
// logs (ie multiple lines). 'id' is the instance ID (so logs with
|
||||||
@ -218,7 +218,7 @@ class IndexedDBLogStore {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private onLoggerLog = () => {
|
private onLoggerLog = (): void => {
|
||||||
if (!this.db) return;
|
if (!this.db) return;
|
||||||
|
|
||||||
this.throttledFlush();
|
this.throttledFlush();
|
||||||
@ -289,10 +289,10 @@ class IndexedDBLogStore {
|
|||||||
}
|
}
|
||||||
const txn = this.db.transaction(["logs", "logslastmod"], "readwrite");
|
const txn = this.db.transaction(["logs", "logslastmod"], "readwrite");
|
||||||
const objStore = txn.objectStore("logs");
|
const objStore = txn.objectStore("logs");
|
||||||
txn.oncomplete = () => {
|
txn.oncomplete = (): void => {
|
||||||
resolve();
|
resolve();
|
||||||
};
|
};
|
||||||
txn.onerror = (event) => {
|
txn.onerror = (event): void => {
|
||||||
logger.error("Failed to flush logs : ", event);
|
logger.error("Failed to flush logs : ", event);
|
||||||
reject(new Error("Failed to write logs: " + txn?.error?.message));
|
reject(new Error("Failed to write logs: " + txn?.error?.message));
|
||||||
};
|
};
|
||||||
@ -333,10 +333,10 @@ class IndexedDBLogStore {
|
|||||||
.index("id")
|
.index("id")
|
||||||
.openCursor(IDBKeyRange.only(id), "prev");
|
.openCursor(IDBKeyRange.only(id), "prev");
|
||||||
let lines = "";
|
let lines = "";
|
||||||
query.onerror = () => {
|
query.onerror = (): void => {
|
||||||
reject(new Error("Query failed: " + query?.error?.message));
|
reject(new Error("Query failed: " + query?.error?.message));
|
||||||
};
|
};
|
||||||
query.onsuccess = () => {
|
query.onsuccess = (): void => {
|
||||||
const cursor = query.result;
|
const cursor = query.result;
|
||||||
if (!cursor) {
|
if (!cursor) {
|
||||||
resolve(lines);
|
resolve(lines);
|
||||||
@ -379,7 +379,7 @@ class IndexedDBLogStore {
|
|||||||
const o = txn.objectStore("logs");
|
const o = txn.objectStore("logs");
|
||||||
// only load the key path, not the data which may be huge
|
// only load the key path, not the data which may be huge
|
||||||
const query = o.index("id").openKeyCursor(IDBKeyRange.only(id));
|
const query = o.index("id").openKeyCursor(IDBKeyRange.only(id));
|
||||||
query.onsuccess = () => {
|
query.onsuccess = (): void => {
|
||||||
const cursor = query.result;
|
const cursor = query.result;
|
||||||
if (!cursor) {
|
if (!cursor) {
|
||||||
return;
|
return;
|
||||||
@ -387,10 +387,10 @@ class IndexedDBLogStore {
|
|||||||
o.delete(cursor.primaryKey);
|
o.delete(cursor.primaryKey);
|
||||||
cursor.continue();
|
cursor.continue();
|
||||||
};
|
};
|
||||||
txn.oncomplete = () => {
|
txn.oncomplete = (): void => {
|
||||||
resolve();
|
resolve();
|
||||||
};
|
};
|
||||||
txn.onerror = () => {
|
txn.onerror = (): void => {
|
||||||
reject(
|
reject(
|
||||||
new Error(
|
new Error(
|
||||||
"Failed to delete logs for " + `'${id}' : ${txn?.error?.message}`
|
"Failed to delete logs for " + `'${id}' : ${txn?.error?.message}`
|
||||||
@ -477,11 +477,11 @@ function selectQuery<T>(
|
|||||||
const query = store.openCursor(keyRange);
|
const query = store.openCursor(keyRange);
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const results: T[] = [];
|
const results: T[] = [];
|
||||||
query.onerror = () => {
|
query.onerror = (): void => {
|
||||||
reject(new Error("Query failed: " + query?.error?.message));
|
reject(new Error("Query failed: " + query?.error?.message));
|
||||||
};
|
};
|
||||||
// collect results
|
// collect results
|
||||||
query.onsuccess = () => {
|
query.onsuccess = (): void => {
|
||||||
const cursor = query.result;
|
const cursor = query.result;
|
||||||
if (!cursor) {
|
if (!cursor) {
|
||||||
resolve(results);
|
resolve(results);
|
||||||
|
@ -360,7 +360,7 @@ export function useRageshakeRequestModal(
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!client) return;
|
if (!client) return;
|
||||||
|
|
||||||
const onEvent = (event: MatrixEvent) => {
|
const onEvent = (event: MatrixEvent): void => {
|
||||||
const type = event.getType();
|
const type = event.getType();
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
@ -55,15 +55,15 @@ export const getSetting = <T>(name: string, defaultValue: T): T => {
|
|||||||
return item === null ? defaultValue : JSON.parse(item);
|
return item === null ? defaultValue : JSON.parse(item);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setSetting = <T>(name: string, newValue: T) =>
|
export const setSetting = <T>(name: string, newValue: T): void =>
|
||||||
setLocalStorageItem(getSettingKey(name), JSON.stringify(newValue));
|
setLocalStorageItem(getSettingKey(name), JSON.stringify(newValue));
|
||||||
|
|
||||||
export const isFirefox = () => {
|
export const isFirefox = (): boolean => {
|
||||||
const { userAgent } = navigator;
|
const { userAgent } = navigator;
|
||||||
return userAgent.includes("Firefox");
|
return userAgent.includes("Firefox");
|
||||||
};
|
};
|
||||||
|
|
||||||
const canEnableSpatialAudio = () => {
|
const canEnableSpatialAudio = (): boolean => {
|
||||||
// Spatial audio means routing audio through audio contexts. On Chrome,
|
// Spatial audio means routing audio through audio contexts. On Chrome,
|
||||||
// this bypasses the AEC processor and so breaks echo cancellation.
|
// this bypasses the AEC processor and so breaks echo cancellation.
|
||||||
// We only allow spatial audio to be enabled on Firefox which we know
|
// We only allow spatial audio to be enabled on Firefox which we know
|
||||||
@ -83,7 +83,8 @@ export const useSpatialAudio = (): DisableableSetting<boolean> => {
|
|||||||
return [false, null];
|
return [false, null];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useShowInspector = () => useSetting("show-inspector", false);
|
export const useShowInspector = (): Setting<boolean> =>
|
||||||
|
useSetting("show-inspector", false);
|
||||||
|
|
||||||
// null = undecided
|
// null = undecided
|
||||||
export const useOptInAnalytics = (): DisableableSetting<boolean | null> => {
|
export const useOptInAnalytics = (): DisableableSetting<boolean | null> => {
|
||||||
@ -103,15 +104,15 @@ export const useEnableE2EE = (): DisableableSetting<boolean | null> => {
|
|||||||
return [false, null];
|
return [false, null];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useDeveloperSettingsTab = () =>
|
export const useDeveloperSettingsTab = (): Setting<boolean> =>
|
||||||
useSetting("developer-settings-tab", false);
|
useSetting("developer-settings-tab", false);
|
||||||
|
|
||||||
export const useShowConnectionStats = () =>
|
export const useShowConnectionStats = (): Setting<boolean> =>
|
||||||
useSetting("show-connection-stats", false);
|
useSetting("show-connection-stats", false);
|
||||||
|
|
||||||
export const useAudioInput = () =>
|
export const useAudioInput = (): Setting<string | undefined> =>
|
||||||
useSetting<string | undefined>("audio-input", undefined);
|
useSetting<string | undefined>("audio-input", undefined);
|
||||||
export const useAudioOutput = () =>
|
export const useAudioOutput = (): Setting<string | undefined> =>
|
||||||
useSetting<string | undefined>("audio-output", undefined);
|
useSetting<string | undefined>("audio-output", undefined);
|
||||||
export const useVideoInput = () =>
|
export const useVideoInput = (): Setting<string | undefined> =>
|
||||||
useSetting<string | undefined>("video-input", undefined);
|
useSetting<string | undefined>("video-input", undefined);
|
||||||
|
@ -35,7 +35,7 @@ export function useCallViewKeyboardShortcuts(
|
|||||||
toggleMicrophoneMuted: () => void,
|
toggleMicrophoneMuted: () => void,
|
||||||
toggleLocalVideoMuted: () => void,
|
toggleLocalVideoMuted: () => void,
|
||||||
setMicrophoneMuted: (muted: boolean) => void
|
setMicrophoneMuted: (muted: boolean) => void
|
||||||
) {
|
): void {
|
||||||
const spacebarHeld = useRef(false);
|
const spacebarHeld = useRef(false);
|
||||||
|
|
||||||
// These event handlers are set on the window because we want users to be able
|
// These event handlers are set on the window because we want users to be able
|
||||||
|
@ -24,12 +24,12 @@ import type {
|
|||||||
} from "matrix-js-sdk/src/models/typed-event-emitter";
|
} from "matrix-js-sdk/src/models/typed-event-emitter";
|
||||||
|
|
||||||
// Shortcut for registering a listener on an EventTarget
|
// Shortcut for registering a listener on an EventTarget
|
||||||
export const useEventTarget = <T extends Event>(
|
export function useEventTarget<T extends Event>(
|
||||||
target: EventTarget | null | undefined,
|
target: EventTarget | null | undefined,
|
||||||
eventType: string,
|
eventType: string,
|
||||||
listener: (event: T) => void,
|
listener: (event: T) => void,
|
||||||
options?: AddEventListenerOptions
|
options?: AddEventListenerOptions
|
||||||
) => {
|
): void {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (target) {
|
if (target) {
|
||||||
target.addEventListener(eventType, listener as EventListener, options);
|
target.addEventListener(eventType, listener as EventListener, options);
|
||||||
@ -41,10 +41,10 @@ export const useEventTarget = <T extends Event>(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}, [target, eventType, listener, options]);
|
}, [target, eventType, listener, options]);
|
||||||
};
|
}
|
||||||
|
|
||||||
// Shortcut for registering a listener on a TypedEventEmitter
|
// Shortcut for registering a listener on a TypedEventEmitter
|
||||||
export const useTypedEventEmitter = <
|
export function useTypedEventEmitter<
|
||||||
Events extends string,
|
Events extends string,
|
||||||
Arguments extends ListenerMap<Events>,
|
Arguments extends ListenerMap<Events>,
|
||||||
T extends Events
|
T extends Events
|
||||||
@ -52,28 +52,28 @@ export const useTypedEventEmitter = <
|
|||||||
emitter: TypedEventEmitter<Events, Arguments>,
|
emitter: TypedEventEmitter<Events, Arguments>,
|
||||||
eventType: T,
|
eventType: T,
|
||||||
listener: Listener<Events, Arguments, T>
|
listener: Listener<Events, Arguments, T>
|
||||||
) => {
|
): void {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
emitter.on(eventType, listener);
|
emitter.on(eventType, listener);
|
||||||
return () => {
|
return () => {
|
||||||
emitter.off(eventType, listener);
|
emitter.off(eventType, listener);
|
||||||
};
|
};
|
||||||
}, [emitter, eventType, listener]);
|
}, [emitter, eventType, listener]);
|
||||||
};
|
}
|
||||||
|
|
||||||
// Shortcut for registering a listener on an eventemitter3 EventEmitter (ie. what the LiveKit SDK uses)
|
// Shortcut for registering a listener on an eventemitter3 EventEmitter (ie. what the LiveKit SDK uses)
|
||||||
export const useEventEmitterThree = <
|
export function useEventEmitterThree<
|
||||||
EventType extends keyof T,
|
EventType extends keyof T,
|
||||||
T extends EventMap
|
T extends EventMap
|
||||||
>(
|
>(
|
||||||
emitter: EventEmitter<T>,
|
emitter: EventEmitter<T>,
|
||||||
eventType: EventType,
|
eventType: EventType,
|
||||||
listener: T[EventType]
|
listener: T[EventType]
|
||||||
) => {
|
): void {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
emitter.on(eventType, listener);
|
emitter.on(eventType, listener);
|
||||||
return () => {
|
return () => {
|
||||||
emitter.off(eventType, listener);
|
emitter.off(eventType, listener);
|
||||||
};
|
};
|
||||||
}, [emitter, eventType, listener]);
|
}, [emitter, eventType, listener]);
|
||||||
};
|
}
|
||||||
|
@ -21,7 +21,7 @@ import { useEventTarget } from "./useEvents";
|
|||||||
/**
|
/**
|
||||||
* React hook that tracks whether the given media query matches.
|
* React hook that tracks whether the given media query matches.
|
||||||
*/
|
*/
|
||||||
export const useMediaQuery = (query: string) => {
|
export function useMediaQuery(query: string): boolean {
|
||||||
const mediaQuery = useMemo(() => matchMedia(query), [query]);
|
const mediaQuery = useMemo(() => matchMedia(query), [query]);
|
||||||
|
|
||||||
const [numChanges, setNumChanges] = useState(0);
|
const [numChanges, setNumChanges] = useState(0);
|
||||||
@ -34,4 +34,4 @@ export const useMediaQuery = (query: string) => {
|
|||||||
// We want any change to the update counter to trigger an update here
|
// We want any change to the update counter to trigger an update here
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
return useMemo(() => mediaQuery.matches, [mediaQuery, numChanges]);
|
return useMemo(() => mediaQuery.matches, [mediaQuery, numChanges]);
|
||||||
};
|
}
|
||||||
|
@ -19,5 +19,5 @@ import { useMediaQuery } from "./useMediaQuery";
|
|||||||
/**
|
/**
|
||||||
* @returns Whether the user has requested reduced motion.
|
* @returns Whether the user has requested reduced motion.
|
||||||
*/
|
*/
|
||||||
export const usePrefersReducedMotion = () =>
|
export const usePrefersReducedMotion = (): boolean =>
|
||||||
useMediaQuery("(prefers-reduced-motion)");
|
useMediaQuery("(prefers-reduced-motion)");
|
||||||
|
@ -20,7 +20,7 @@ import { useEffect } from "react";
|
|||||||
/**
|
/**
|
||||||
* React hook that inhibits the device from automatically going to sleep.
|
* React hook that inhibits the device from automatically going to sleep.
|
||||||
*/
|
*/
|
||||||
export const useWakeLock = () => {
|
export function useWakeLock(): void {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if ("wakeLock" in navigator) {
|
if ("wakeLock" in navigator) {
|
||||||
let mounted = true;
|
let mounted = true;
|
||||||
@ -28,7 +28,7 @@ export const useWakeLock = () => {
|
|||||||
|
|
||||||
// The lock is automatically released whenever the window goes invisible,
|
// The lock is automatically released whenever the window goes invisible,
|
||||||
// so we need to reacquire it on visiblity changes
|
// so we need to reacquire it on visiblity changes
|
||||||
const onVisiblityChange = async () => {
|
const onVisiblityChange = async (): Promise<void> => {
|
||||||
if (document.visibilityState === "visible") {
|
if (document.visibilityState === "visible") {
|
||||||
try {
|
try {
|
||||||
lock = await navigator.wakeLock.request("screen");
|
lock = await navigator.wakeLock.request("screen");
|
||||||
@ -57,4 +57,4 @@ export const useWakeLock = () => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
};
|
}
|
||||||
|
@ -78,7 +78,7 @@ export interface SparseGrid {
|
|||||||
export function getPaths(
|
export function getPaths(
|
||||||
g: SparseGrid,
|
g: SparseGrid,
|
||||||
dest: number,
|
dest: number,
|
||||||
avoid: (cell: number) => boolean = () => false
|
avoid: (cell: number) => boolean = (): boolean => false
|
||||||
): (number | null)[] {
|
): (number | null)[] {
|
||||||
const destRow = row(dest, g);
|
const destRow = row(dest, g);
|
||||||
const destColumn = column(dest, g);
|
const destColumn = column(dest, g);
|
||||||
@ -91,7 +91,7 @@ export function getPaths(
|
|||||||
edges[dest] = null;
|
edges[dest] = null;
|
||||||
const heap = new TinyQueue([dest], (i) => distances[i]);
|
const heap = new TinyQueue([dest], (i) => distances[i]);
|
||||||
|
|
||||||
const visit = (curr: number, via: number, distanceVia: number) => {
|
const visit = (curr: number, via: number, distanceVia: number): void => {
|
||||||
if (distanceVia < distances[curr]) {
|
if (distanceVia < distances[curr]) {
|
||||||
distances[curr] = distanceVia;
|
distances[curr] = distanceVia;
|
||||||
edges[curr] = via;
|
edges[curr] = via;
|
||||||
@ -128,7 +128,7 @@ export function getPaths(
|
|||||||
return edges;
|
return edges;
|
||||||
}
|
}
|
||||||
|
|
||||||
const is1By1 = (c: Cell) => c.columns === 1 && c.rows === 1;
|
const is1By1 = (c: Cell): boolean => c.columns === 1 && c.rows === 1;
|
||||||
|
|
||||||
const findLast1By1Index = (g: SparseGrid): number | null =>
|
const findLast1By1Index = (g: SparseGrid): number | null =>
|
||||||
findLastIndex(g.cells, (c) => c !== undefined && is1By1(c));
|
findLastIndex(g.cells, (c) => c !== undefined && is1By1(c));
|
||||||
@ -257,7 +257,7 @@ function getNextGap(
|
|||||||
* along the way.
|
* along the way.
|
||||||
* Precondition: the destination area must consist of only 1×1 tiles.
|
* Precondition: the destination area must consist of only 1×1 tiles.
|
||||||
*/
|
*/
|
||||||
function moveTileUnchecked(g: SparseGrid, from: number, to: number) {
|
function moveTileUnchecked(g: SparseGrid, from: number, to: number): void {
|
||||||
const tile = g.cells[from]!;
|
const tile = g.cells[from]!;
|
||||||
const fromEnd = areaEnd(from, tile.columns, tile.rows, g);
|
const fromEnd = areaEnd(from, tile.columns, tile.rows, g);
|
||||||
const toEnd = areaEnd(to, tile.columns, tile.rows, g);
|
const toEnd = areaEnd(to, tile.columns, tile.rows, g);
|
||||||
@ -333,7 +333,7 @@ function pushTileUp(
|
|||||||
g: SparseGrid,
|
g: SparseGrid,
|
||||||
from: number,
|
from: number,
|
||||||
rows: number,
|
rows: number,
|
||||||
avoid: (cell: number) => boolean = () => false
|
avoid: (cell: number) => boolean = (): boolean => false
|
||||||
): number {
|
): number {
|
||||||
const tile = g.cells[from]!;
|
const tile = g.cells[from]!;
|
||||||
|
|
||||||
@ -359,7 +359,7 @@ function pushTileUp(
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function trimTrailingGaps(g: SparseGrid) {
|
function trimTrailingGaps(g: SparseGrid): void {
|
||||||
// Shrink the array to remove trailing gaps
|
// Shrink the array to remove trailing gaps
|
||||||
const newLength = (findLastIndex(g.cells, (c) => c !== undefined) ?? -1) + 1;
|
const newLength = (findLastIndex(g.cells, (c) => c !== undefined) ?? -1) + 1;
|
||||||
if (newLength !== g.cells.length) g.cells = g.cells.slice(0, newLength);
|
if (newLength !== g.cells.length) g.cells = g.cells.slice(0, newLength);
|
||||||
@ -485,7 +485,7 @@ export function fillGaps(
|
|||||||
export function fillGaps(
|
export function fillGaps(
|
||||||
g: SparseGrid,
|
g: SparseGrid,
|
||||||
packLargeTiles = true,
|
packLargeTiles = true,
|
||||||
ignoreGap: (cell: number) => boolean = () => false
|
ignoreGap: (cell: number) => boolean = (): boolean => false
|
||||||
): SparseGrid {
|
): SparseGrid {
|
||||||
const lastGap = findLastIndex(
|
const lastGap = findLastIndex(
|
||||||
g.cells,
|
g.cells,
|
||||||
@ -785,7 +785,11 @@ export function setTileSize<G extends Grid | SparseGrid>(
|
|||||||
gridWithoutTile.cells[i] = undefined;
|
gridWithoutTile.cells[i] = undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
const placeTile = (to: number, toEnd: number, grid: Grid | SparseGrid) => {
|
const placeTile = (
|
||||||
|
to: number,
|
||||||
|
toEnd: number,
|
||||||
|
grid: Grid | SparseGrid
|
||||||
|
): void => {
|
||||||
forEachCellInArea(to, toEnd, grid, (_c, i) => {
|
forEachCellInArea(to, toEnd, grid, (_c, i) => {
|
||||||
grid.cells[i] = {
|
grid.cells[i] = {
|
||||||
item: fromCell.item,
|
item: fromCell.item,
|
||||||
@ -904,7 +908,7 @@ export function resize(g: Grid, columns: number): Grid {
|
|||||||
/**
|
/**
|
||||||
* Promotes speakers to the first page of the grid.
|
* Promotes speakers to the first page of the grid.
|
||||||
*/
|
*/
|
||||||
export function promoteSpeakers(g: SparseGrid) {
|
export function promoteSpeakers(g: SparseGrid): void {
|
||||||
// This is all a bit of a hack right now, because we don't know if the designs
|
// This is all a bit of a hack right now, because we don't know if the designs
|
||||||
// will stick with this approach in the long run
|
// will stick with this approach in the long run
|
||||||
// We assume that 4 rows are probably about 1 page
|
// We assume that 4 rows are probably about 1 page
|
||||||
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ComponentType, useCallback, useMemo, useRef } from "react";
|
import { ComponentType, ReactNode, useCallback, useMemo, useRef } from "react";
|
||||||
|
|
||||||
import type { RectReadOnly } from "react-use-measure";
|
import type { RectReadOnly } from "react-use-measure";
|
||||||
import { useReactiveState } from "../useReactiveState";
|
import { useReactiveState } from "../useReactiveState";
|
||||||
@ -98,16 +98,33 @@ export const useLayoutStates = (): LayoutStatesMap => {
|
|||||||
return layoutStates.current as LayoutStatesMap;
|
return layoutStates.current as LayoutStatesMap;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface UseLayout<State, T> {
|
||||||
|
state: State;
|
||||||
|
orderedItems: TileDescriptor<T>[];
|
||||||
|
generation: number;
|
||||||
|
canDragTile: (tile: TileDescriptor<T>) => boolean;
|
||||||
|
dragTile: (
|
||||||
|
from: TileDescriptor<T>,
|
||||||
|
to: TileDescriptor<T>,
|
||||||
|
xPositionOnFrom: number,
|
||||||
|
yPositionOnFrom: number,
|
||||||
|
xPositionOnTo: number,
|
||||||
|
yPositionOnTo: number
|
||||||
|
) => void;
|
||||||
|
toggleFocus: ((tile: TileDescriptor<T>) => void) | undefined;
|
||||||
|
slots: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hook which uses the provided layout system to arrange a set of items into a
|
* Hook which uses the provided layout system to arrange a set of items into a
|
||||||
* concrete layout state, and provides callbacks for user interaction.
|
* concrete layout state, and provides callbacks for user interaction.
|
||||||
*/
|
*/
|
||||||
export const useLayout = <State, T>(
|
export function useLayout<State, T>(
|
||||||
layout: Layout<State>,
|
layout: Layout<State>,
|
||||||
items: TileDescriptor<T>[],
|
items: TileDescriptor<T>[],
|
||||||
bounds: RectReadOnly,
|
bounds: RectReadOnly,
|
||||||
layoutStates: LayoutStatesMap
|
layoutStates: LayoutStatesMap
|
||||||
) => {
|
): UseLayout<State, T> {
|
||||||
const prevLayout = useRef<Layout<unknown>>();
|
const prevLayout = useRef<Layout<unknown>>();
|
||||||
const prevState = layoutStates.get(layout);
|
const prevState = layoutStates.get(layout);
|
||||||
|
|
||||||
@ -169,10 +186,10 @@ export const useLayout = <State, T>(
|
|||||||
toggleFocus: useMemo(
|
toggleFocus: useMemo(
|
||||||
() =>
|
() =>
|
||||||
layout.toggleFocus &&
|
layout.toggleFocus &&
|
||||||
((tile: TileDescriptor<T>) =>
|
((tile: TileDescriptor<T>): void =>
|
||||||
setState((s) => layout.toggleFocus!(s, tile))),
|
setState((s) => layout.toggleFocus!(s, tile))),
|
||||||
[layout, setState]
|
[layout, setState]
|
||||||
),
|
),
|
||||||
slots: <layout.Slots s={state} />,
|
slots: <layout.Slots s={state} />,
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
@ -81,7 +81,7 @@ export function NewVideoGrid<T>({
|
|||||||
disableAnimations,
|
disableAnimations,
|
||||||
layoutStates,
|
layoutStates,
|
||||||
children,
|
children,
|
||||||
}: Props<T>) {
|
}: Props<T>): ReactNode {
|
||||||
// Overview: This component lays out tiles by rendering an invisible template
|
// Overview: This component lays out tiles by rendering an invisible template
|
||||||
// grid of "slots" for tiles to go in. Once rendered, it uses the DOM API to
|
// grid of "slots" for tiles to go in. Once rendered, it uses the DOM API to
|
||||||
// get the dimensions of each slot, feeding these numbers back into
|
// get the dimensions of each slot, feeding these numbers back into
|
||||||
@ -210,7 +210,7 @@ export function NewVideoGrid<T>({
|
|||||||
springRef.start();
|
springRef.start();
|
||||||
}, [tiles, springRef]);
|
}, [tiles, springRef]);
|
||||||
|
|
||||||
const animateDraggedTile = (endOfGesture: boolean) => {
|
const animateDraggedTile = (endOfGesture: boolean): void => {
|
||||||
const { tileId, tileX, tileY, cursorX, cursorY } = dragState.current!;
|
const { tileId, tileX, tileY, cursorX, cursorY } = dragState.current!;
|
||||||
const tile = tiles.find((t) => t.item.id === tileId)!;
|
const tile = tiles.find((t) => t.item.id === tileId)!;
|
||||||
|
|
||||||
@ -226,7 +226,8 @@ export function NewVideoGrid<T>({
|
|||||||
y: tile.y,
|
y: tile.y,
|
||||||
width: tile.width,
|
width: tile.width,
|
||||||
height: tile.height,
|
height: tile.height,
|
||||||
immediate: disableAnimations || ((key) => key === "zIndex"),
|
immediate:
|
||||||
|
disableAnimations || ((key): boolean => key === "zIndex"),
|
||||||
// Allow the tile's position to settle before pushing its
|
// Allow the tile's position to settle before pushing its
|
||||||
// z-index back down
|
// z-index back down
|
||||||
delay: (key) => (key === "zIndex" ? 500 : 0),
|
delay: (key) => (key === "zIndex" ? 500 : 0),
|
||||||
@ -239,7 +240,8 @@ export function NewVideoGrid<T>({
|
|||||||
y: tileY,
|
y: tileY,
|
||||||
immediate:
|
immediate:
|
||||||
disableAnimations ||
|
disableAnimations ||
|
||||||
((key) => key === "zIndex" || key === "x" || key === "y"),
|
((key): boolean =>
|
||||||
|
key === "zIndex" || key === "x" || key === "y"),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -286,7 +288,7 @@ export function NewVideoGrid<T>({
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
last,
|
last,
|
||||||
}: Parameters<Handler<"drag", EventTypes["drag"]>>[0]
|
}: Parameters<Handler<"drag", EventTypes["drag"]>>[0]
|
||||||
) => {
|
): void => {
|
||||||
if (tap) {
|
if (tap) {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||||||
import {
|
import {
|
||||||
ComponentProps,
|
ComponentProps,
|
||||||
Key,
|
Key,
|
||||||
|
MutableRefObject,
|
||||||
ReactNode,
|
ReactNode,
|
||||||
Ref,
|
Ref,
|
||||||
useCallback,
|
useCallback,
|
||||||
@ -112,7 +113,7 @@ export function useVideoGridLayout(hasScreenshareFeeds: boolean): {
|
|||||||
|
|
||||||
const GAP = 8;
|
const GAP = 8;
|
||||||
|
|
||||||
function useIsMounted() {
|
function useIsMounted(): MutableRefObject<boolean> {
|
||||||
const isMountedRef = useRef<boolean>(false);
|
const isMountedRef = useRef<boolean>(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -478,7 +479,7 @@ function centerTiles(
|
|||||||
gridHeight: number,
|
gridHeight: number,
|
||||||
offsetLeft: number,
|
offsetLeft: number,
|
||||||
offsetTop: number
|
offsetTop: number
|
||||||
) {
|
): TilePosition[] {
|
||||||
const bounds = getSubGridBoundingBox(positions);
|
const bounds = getSubGridBoundingBox(positions);
|
||||||
|
|
||||||
const leftOffset = Math.round((gridWidth - bounds.width) / 2) + offsetLeft;
|
const leftOffset = Math.round((gridWidth - bounds.width) / 2) + offsetLeft;
|
||||||
@ -493,7 +494,7 @@ function applyTileOffsets(
|
|||||||
positions: TilePosition[],
|
positions: TilePosition[],
|
||||||
leftOffset: number,
|
leftOffset: number,
|
||||||
topOffset: number
|
topOffset: number
|
||||||
) {
|
): TilePosition[] {
|
||||||
for (const position of positions) {
|
for (const position of positions) {
|
||||||
position.x += leftOffset;
|
position.x += leftOffset;
|
||||||
position.y += topOffset;
|
position.y += topOffset;
|
||||||
@ -623,7 +624,7 @@ function getSubGridPositions(
|
|||||||
tileAspectRatio: number,
|
tileAspectRatio: number,
|
||||||
gridWidth: number,
|
gridWidth: number,
|
||||||
gridHeight: number
|
gridHeight: number
|
||||||
) {
|
): TilePosition[] {
|
||||||
if (tileCount === 0) {
|
if (tileCount === 0) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@ -726,7 +727,11 @@ function displayedTileCount(
|
|||||||
|
|
||||||
// Sets the 'order' property on tiles based on the layout param and
|
// Sets the 'order' property on tiles based on the layout param and
|
||||||
// other properties of the tiles, eg. 'focused' and 'presenter'
|
// other properties of the tiles, eg. 'focused' and 'presenter'
|
||||||
function reorderTiles<T>(tiles: Tile<T>[], layout: Layout, displayedTile = -1) {
|
function reorderTiles<T>(
|
||||||
|
tiles: Tile<T>[],
|
||||||
|
layout: Layout,
|
||||||
|
displayedTile = -1
|
||||||
|
): void {
|
||||||
// We use a special layout for 1:1 to always put the local tile first.
|
// We use a special layout for 1:1 to always put the local tile first.
|
||||||
// We only do this if there are two tiles (obviously) and exactly one
|
// We only do this if there are two tiles (obviously) and exactly one
|
||||||
// of them is local: during startup we can have tiles from other users
|
// of them is local: during startup we can have tiles from other users
|
||||||
@ -841,7 +846,7 @@ export function VideoGrid<T>({
|
|||||||
layout,
|
layout,
|
||||||
disableAnimations,
|
disableAnimations,
|
||||||
children,
|
children,
|
||||||
}: VideoGridProps<T>) {
|
}: VideoGridProps<T>): ReactNode {
|
||||||
// Place the PiP in the bottom right corner by default
|
// Place the PiP in the bottom right corner by default
|
||||||
const [pipXRatio, setPipXRatio] = useState(1);
|
const [pipXRatio, setPipXRatio] = useState(1);
|
||||||
const [pipYRatio, setPipYRatio] = useState(1);
|
const [pipYRatio, setPipYRatio] = useState(1);
|
||||||
@ -1208,7 +1213,7 @@ export function VideoGrid<T>({
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
event,
|
event,
|
||||||
}: Parameters<Handler<"drag", EventTypes["drag"]>>[0]
|
}: Parameters<Handler<"drag", EventTypes["drag"]>>[0]
|
||||||
) => {
|
): void => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
if (tap) {
|
if (tap) {
|
||||||
|
@ -97,12 +97,12 @@ export const VideoTile = forwardRef<HTMLDivElement, Props>(
|
|||||||
);
|
);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (member) {
|
if (member) {
|
||||||
const updateName = () => {
|
const updateName = (): void => {
|
||||||
setDisplayName(member.rawDisplayName);
|
setDisplayName(member.rawDisplayName);
|
||||||
};
|
};
|
||||||
|
|
||||||
member!.on(RoomMemberEvent.Name, updateName);
|
member!.on(RoomMemberEvent.Name, updateName);
|
||||||
return () => {
|
return (): void => {
|
||||||
member!.removeListener(RoomMemberEvent.Name, updateName);
|
member!.removeListener(RoomMemberEvent.Name, updateName);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { ChangeEvent, useState } from "react";
|
import { ChangeEvent, FC, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { RemoteParticipant, Track } from "livekit-client";
|
import { RemoteParticipant, Track } from "livekit-client";
|
||||||
|
|
||||||
@ -29,7 +29,7 @@ interface LocalVolumeProps {
|
|||||||
content: TileContent;
|
content: TileContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
const LocalVolume: React.FC<LocalVolumeProps> = ({
|
const LocalVolume: FC<LocalVolumeProps> = ({
|
||||||
participant,
|
participant,
|
||||||
content,
|
content,
|
||||||
}: LocalVolumeProps) => {
|
}: LocalVolumeProps) => {
|
||||||
@ -42,7 +42,7 @@ const LocalVolume: React.FC<LocalVolumeProps> = ({
|
|||||||
participant.getVolume(source) ?? 0
|
participant.getVolume(source) ?? 0
|
||||||
);
|
);
|
||||||
|
|
||||||
const onLocalVolumeChanged = (event: ChangeEvent<HTMLInputElement>) => {
|
const onLocalVolumeChanged = (event: ChangeEvent<HTMLInputElement>): void => {
|
||||||
const value: number = +event.target.value;
|
const value: number = +event.target.value;
|
||||||
setLocalVolume(value);
|
setLocalVolume(value);
|
||||||
participant.setVolume(value, source);
|
participant.setVolume(value, source);
|
||||||
@ -72,7 +72,11 @@ interface Props {
|
|||||||
onDismiss: () => void;
|
onDismiss: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const VideoTileSettingsModal = ({ data, open, onDismiss }: Props) => {
|
export const VideoTileSettingsModal: FC<Props> = ({
|
||||||
|
data,
|
||||||
|
open,
|
||||||
|
onDismiss,
|
||||||
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -68,7 +68,7 @@ interface WidgetHelpers {
|
|||||||
* is declared and initialized on the top level because the widget messaging
|
* is declared and initialized on the top level because the widget messaging
|
||||||
* needs to be set up ASAP on load to ensure it doesn't miss any requests.
|
* needs to be set up ASAP on load to ensure it doesn't miss any requests.
|
||||||
*/
|
*/
|
||||||
export const widget: WidgetHelpers | null = (() => {
|
export const widget = ((): WidgetHelpers | null => {
|
||||||
try {
|
try {
|
||||||
const query = new URLSearchParams(window.location.search);
|
const query = new URLSearchParams(window.location.search);
|
||||||
const widgetId = query.get("widgetId");
|
const widgetId = query.get("widgetId");
|
||||||
@ -161,7 +161,7 @@ export const widget: WidgetHelpers | null = (() => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const clientPromise = new Promise<MatrixClient>((resolve) => {
|
const clientPromise = new Promise<MatrixClient>((resolve) => {
|
||||||
(async () => {
|
(async (): Promise<void> => {
|
||||||
// wait for the config file to be ready (we load very early on so it might not
|
// wait for the config file to be ready (we load very early on so it might not
|
||||||
// be otherwise)
|
// be otherwise)
|
||||||
await Config.init();
|
await Config.init();
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"noUnusedLocals": true,
|
"noUnusedLocals": true,
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"declaration": true
|
"declaration": true,
|
||||||
|
|
||||||
// TODO: Enable the following options later.
|
// TODO: Enable the following options later.
|
||||||
// "forceConsistentCasingInFileNames": true,
|
// "forceConsistentCasingInFileNames": true,
|
||||||
@ -23,6 +23,8 @@
|
|||||||
// "noPropertyAccessFromIndexSignature": true,
|
// "noPropertyAccessFromIndexSignature": true,
|
||||||
// "noUncheckedIndexedAccess": true,
|
// "noUncheckedIndexedAccess": true,
|
||||||
// "noUnusedParameters": true,
|
// "noUnusedParameters": true,
|
||||||
|
|
||||||
|
"plugins": [{ "name": "typescript-eslint-language-service" }]
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"./node_modules/matrix-js-sdk/src/@types/*.d.ts",
|
"./node_modules/matrix-js-sdk/src/@types/*.d.ts",
|
||||||
|
84
yarn.lock
84
yarn.lock
@ -6341,6 +6341,11 @@ bufrw@^1.2.1:
|
|||||||
hexer "^1.5.0"
|
hexer "^1.5.0"
|
||||||
xtend "^4.0.0"
|
xtend "^4.0.0"
|
||||||
|
|
||||||
|
builtin-modules@^3.3.0:
|
||||||
|
version "3.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6"
|
||||||
|
integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==
|
||||||
|
|
||||||
builtin-status-codes@^3.0.0:
|
builtin-status-codes@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8"
|
resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8"
|
||||||
@ -6627,7 +6632,7 @@ chrome-trace-event@^1.0.2:
|
|||||||
resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac"
|
resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac"
|
||||||
integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==
|
integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==
|
||||||
|
|
||||||
ci-info@^3.2.0:
|
ci-info@^3.2.0, ci-info@^3.8.0:
|
||||||
version "3.8.0"
|
version "3.8.0"
|
||||||
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.8.0.tgz#81408265a5380c929f0bc665d62256628ce9ef91"
|
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.8.0.tgz#81408265a5380c929f0bc665d62256628ce9ef91"
|
||||||
integrity sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==
|
integrity sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==
|
||||||
@ -6667,6 +6672,13 @@ clean-css@^4.2.3:
|
|||||||
dependencies:
|
dependencies:
|
||||||
source-map "~0.6.0"
|
source-map "~0.6.0"
|
||||||
|
|
||||||
|
clean-regexp@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/clean-regexp/-/clean-regexp-1.0.0.tgz#8df7c7aae51fd36874e8f8d05b9180bc11a3fed7"
|
||||||
|
integrity sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==
|
||||||
|
dependencies:
|
||||||
|
escape-string-regexp "^1.0.5"
|
||||||
|
|
||||||
clean-stack@^2.0.0:
|
clean-stack@^2.0.0:
|
||||||
version "2.2.0"
|
version "2.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b"
|
resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b"
|
||||||
@ -8516,6 +8528,11 @@ eslint-module-utils@^2.8.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
debug "^3.2.7"
|
debug "^3.2.7"
|
||||||
|
|
||||||
|
eslint-plugin-deprecate@^0.8.2:
|
||||||
|
version "0.8.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/eslint-plugin-deprecate/-/eslint-plugin-deprecate-0.8.2.tgz#2399730da3d72aa6d3704400995932f52f0482cf"
|
||||||
|
integrity sha512-THs60MUqJoHtrF6F8eNUnyU0ER6p4wUX7yyoUZQdBDPFiE9kzZTo4CgRKZicUVj5cjXLT76nW+QdSZwZKtjLIA==
|
||||||
|
|
||||||
eslint-plugin-import@^2.26.0:
|
eslint-plugin-import@^2.26.0:
|
||||||
version "2.28.1"
|
version "2.28.1"
|
||||||
resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.28.1.tgz#63b8b5b3c409bfc75ebaf8fb206b07ab435482c4"
|
resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.28.1.tgz#63b8b5b3c409bfc75ebaf8fb206b07ab435482c4"
|
||||||
@ -8561,10 +8578,10 @@ eslint-plugin-jsx-a11y@^6.5.1:
|
|||||||
object.fromentries "^2.0.6"
|
object.fromentries "^2.0.6"
|
||||||
semver "^6.3.0"
|
semver "^6.3.0"
|
||||||
|
|
||||||
eslint-plugin-matrix-org@^0.4.0:
|
eslint-plugin-matrix-org@^1.2.1:
|
||||||
version "0.4.0"
|
version "1.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/eslint-plugin-matrix-org/-/eslint-plugin-matrix-org-0.4.0.tgz#de2d2db1cd471d637728133ce9a2b921690e5cd1"
|
resolved "https://registry.yarnpkg.com/eslint-plugin-matrix-org/-/eslint-plugin-matrix-org-1.2.1.tgz#76d1505daa93fb99ba4156008b9b32f57682c9b1"
|
||||||
integrity sha512-yVkNwtc33qtrQB4PPzpU+PUdFzdkENPan3JF4zhtAQJRUYXyvKEXnYSrXLUWYRXoYFxs9LbyI2CnhJL/RnHJaQ==
|
integrity sha512-A3cDjhG7RHwfCS8o3bOip8hSCsxtmgk2ahvqE5v/Ic2kPEZxixY6w8zLj7hFGsrRmPSEpLWqkVLt8uvQBapiQA==
|
||||||
|
|
||||||
eslint-plugin-react-hooks@^4.5.0:
|
eslint-plugin-react-hooks@^4.5.0:
|
||||||
version "4.6.0"
|
version "4.6.0"
|
||||||
@ -8593,6 +8610,27 @@ eslint-plugin-react@^7.29.4:
|
|||||||
semver "^6.3.1"
|
semver "^6.3.1"
|
||||||
string.prototype.matchall "^4.0.8"
|
string.prototype.matchall "^4.0.8"
|
||||||
|
|
||||||
|
eslint-plugin-unicorn@^48.0.1:
|
||||||
|
version "48.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/eslint-plugin-unicorn/-/eslint-plugin-unicorn-48.0.1.tgz#a6573bc1687ae8db7121fdd8f92394b6549a6959"
|
||||||
|
integrity sha512-FW+4r20myG/DqFcCSzoumaddKBicIPeFnTrifon2mWIzlfyvzwyqZjqVP7m4Cqr/ZYisS2aiLghkUWaPg6vtCw==
|
||||||
|
dependencies:
|
||||||
|
"@babel/helper-validator-identifier" "^7.22.5"
|
||||||
|
"@eslint-community/eslint-utils" "^4.4.0"
|
||||||
|
ci-info "^3.8.0"
|
||||||
|
clean-regexp "^1.0.0"
|
||||||
|
esquery "^1.5.0"
|
||||||
|
indent-string "^4.0.0"
|
||||||
|
is-builtin-module "^3.2.1"
|
||||||
|
jsesc "^3.0.2"
|
||||||
|
lodash "^4.17.21"
|
||||||
|
pluralize "^8.0.0"
|
||||||
|
read-pkg-up "^7.0.1"
|
||||||
|
regexp-tree "^0.1.27"
|
||||||
|
regjsparser "^0.10.0"
|
||||||
|
semver "^7.5.4"
|
||||||
|
strip-indent "^3.0.0"
|
||||||
|
|
||||||
eslint-scope@5.1.1:
|
eslint-scope@5.1.1:
|
||||||
version "5.1.1"
|
version "5.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c"
|
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c"
|
||||||
@ -8679,7 +8717,7 @@ esprima@^4.0.0, esprima@^4.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
|
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
|
||||||
integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
|
integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
|
||||||
|
|
||||||
esquery@^1.4.2:
|
esquery@^1.4.2, esquery@^1.5.0:
|
||||||
version "1.5.0"
|
version "1.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b"
|
resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b"
|
||||||
integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==
|
integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==
|
||||||
@ -10283,6 +10321,13 @@ is-buffer@^2.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191"
|
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191"
|
||||||
integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==
|
integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==
|
||||||
|
|
||||||
|
is-builtin-module@^3.2.1:
|
||||||
|
version "3.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-3.2.1.tgz#f03271717d8654cfcaf07ab0463faa3571581169"
|
||||||
|
integrity sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==
|
||||||
|
dependencies:
|
||||||
|
builtin-modules "^3.3.0"
|
||||||
|
|
||||||
is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7:
|
is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7:
|
||||||
version "1.2.7"
|
version "1.2.7"
|
||||||
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055"
|
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055"
|
||||||
@ -11187,6 +11232,11 @@ jsesc@^2.5.1:
|
|||||||
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4"
|
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4"
|
||||||
integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==
|
integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==
|
||||||
|
|
||||||
|
jsesc@^3.0.2:
|
||||||
|
version "3.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.0.2.tgz#bb8b09a6597ba426425f2e4a07245c3d00b9343e"
|
||||||
|
integrity sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==
|
||||||
|
|
||||||
jsesc@~0.5.0:
|
jsesc@~0.5.0:
|
||||||
version "0.5.0"
|
version "0.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
|
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
|
||||||
@ -12850,6 +12900,11 @@ pkg-dir@^7.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
find-up "^6.3.0"
|
find-up "^6.3.0"
|
||||||
|
|
||||||
|
pluralize@^8.0.0:
|
||||||
|
version "8.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1"
|
||||||
|
integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==
|
||||||
|
|
||||||
pnp-webpack-plugin@1.6.4:
|
pnp-webpack-plugin@1.6.4:
|
||||||
version "1.6.4"
|
version "1.6.4"
|
||||||
resolved "https://registry.yarnpkg.com/pnp-webpack-plugin/-/pnp-webpack-plugin-1.6.4.tgz#c9711ac4dc48a685dabafc86f8b6dd9f8df84149"
|
resolved "https://registry.yarnpkg.com/pnp-webpack-plugin/-/pnp-webpack-plugin-1.6.4.tgz#c9711ac4dc48a685dabafc86f8b6dd9f8df84149"
|
||||||
@ -13857,6 +13912,11 @@ regex-not@^1.0.0, regex-not@^1.0.2:
|
|||||||
extend-shallow "^3.0.2"
|
extend-shallow "^3.0.2"
|
||||||
safe-regex "^1.1.0"
|
safe-regex "^1.1.0"
|
||||||
|
|
||||||
|
regexp-tree@^0.1.27:
|
||||||
|
version "0.1.27"
|
||||||
|
resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.27.tgz#2198f0ef54518ffa743fe74d983b56ffd631b6cd"
|
||||||
|
integrity sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==
|
||||||
|
|
||||||
regexp.prototype.flags@^1.5.0, regexp.prototype.flags@^1.5.1:
|
regexp.prototype.flags@^1.5.0, regexp.prototype.flags@^1.5.1:
|
||||||
version "1.5.1"
|
version "1.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz#90ce989138db209f81492edd734183ce99f9677e"
|
resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz#90ce989138db209f81492edd734183ce99f9677e"
|
||||||
@ -13878,6 +13938,13 @@ regexpu-core@^5.3.1:
|
|||||||
unicode-match-property-ecmascript "^2.0.0"
|
unicode-match-property-ecmascript "^2.0.0"
|
||||||
unicode-match-property-value-ecmascript "^2.1.0"
|
unicode-match-property-value-ecmascript "^2.1.0"
|
||||||
|
|
||||||
|
regjsparser@^0.10.0:
|
||||||
|
version "0.10.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.10.0.tgz#b1ed26051736b436f22fdec1c8f72635f9f44892"
|
||||||
|
integrity sha512-qx+xQGZVsy55CH0a1hiVwHmqjLryfh7wQyF5HO07XJ9f7dQMY/gPQHhlyDkIzJKC+x2fUCpCcUODUUUFrm7SHA==
|
||||||
|
dependencies:
|
||||||
|
jsesc "~0.5.0"
|
||||||
|
|
||||||
regjsparser@^0.9.1:
|
regjsparser@^0.9.1:
|
||||||
version "0.9.1"
|
version "0.9.1"
|
||||||
resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.9.1.tgz#272d05aa10c7c1f67095b1ff0addae8442fc5709"
|
resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.9.1.tgz#272d05aa10c7c1f67095b1ff0addae8442fc5709"
|
||||||
@ -15454,6 +15521,11 @@ typedarray@^0.0.6:
|
|||||||
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
||||||
integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==
|
integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==
|
||||||
|
|
||||||
|
typescript-eslint-language-service@^5.0.5:
|
||||||
|
version "5.0.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/typescript-eslint-language-service/-/typescript-eslint-language-service-5.0.5.tgz#b0f06290df01c55771f2674d261512d17e7a39ad"
|
||||||
|
integrity sha512-b7gWXpwSTqMVKpPX3WttNZEyVAMKs/2jsHKF79H+qaD6mjzCyU5jboJe/lOZgLJD+QRsXCr0GjIVxvl5kI1NMw==
|
||||||
|
|
||||||
typescript@^4.2.4:
|
typescript@^4.2.4:
|
||||||
version "4.8.4"
|
version "4.8.4"
|
||||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.4.tgz#c464abca159669597be5f96b8943500b238e60e6"
|
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.4.tgz#c464abca159669597be5f96b8943500b238e60e6"
|
||||||
|
Loading…
Reference in New Issue
Block a user