Compare commits

...

66 Commits

Author SHA1 Message Date
David Baker
c13040f0b0 Merge pull request #327 from vector-im/dbkr/end_talk_sound
Add sound when speaker stops speaking
2022-05-16 20:37:38 +01:00
David Baker
24a1091954 Merge pull request #325 from vector-im/dbkr/fix_mute_races
Mute local mic if blocked and fix races on mute / unmute
2022-05-16 14:12:15 +01:00
David Baker
9fd7329554 Add sound when speaker stops speaking
And also a slightly nicer blocked sound (ok, I couldn't let it go).
2022-05-13 21:00:14 +01:00
David Baker
2a19a9964d Merge pull request #326 from vector-im/dbkr/ptt_button_touch_fixes
Fixes for touch interface on push-to-talk button
2022-05-13 20:45:22 +01:00
David Baker
3fc9c1b74a Import createref separately 2022-05-13 20:43:20 +01:00
David Baker
f6f0c20b08 Chain promises correctly 2022-05-13 20:39:21 +01:00
David Baker
26a1c165d9 Merge pull request #322 from vector-im/dbkr/blocked_sound_on_timeout
Play the blocked sound on time limit
2022-05-13 19:26:46 +01:00
David Baker
2af87fa8b8 Fixes for touch interface on push-to-talk button
* Avoid also getting a 'mousedown' event by making the event listener
   non-passive so the preventDefault() works
 * Remember the touch that pressed the button so we only un-press
   when that touch ends, otherwise the button gets released if the
   user taps the screen anywhere else.
2022-05-13 18:28:48 +01:00
David Baker
d34c8d08a4 Add comment 2022-05-13 18:09:45 +01:00
David Baker
0f687fb8b8 Fix races on mute / unmute
By serialising everything on a promise chain
2022-05-13 17:58:59 +01:00
David Baker
603dd3786a Play the blocked sound on time limit
Play the 'blocked' sound effect whenever the user is till holding
the button but can't speak anymore, whether they've been cut off
by someone else or have reached their time limit.
2022-05-12 14:13:03 +01:00
David Baker
9fbe4278c2 Merge pull request #321 from vector-im/dbkr/ptt_mobile_touch_prompt
Fix hold-to-speak and prompt text on mobile
2022-05-12 12:16:48 +01:00
David Baker
b222b4f708 Fix hold-to-speak and prompt text on mobile 2022-05-12 12:07:04 +01:00
David Baker
abc2449b07 Merge pull request #320 from vector-im/dbkr/pttcallview_types
More types on PTTCallView
2022-05-12 10:07:59 +01:00
David Baker
e6459de0d9 Merge pull request #319 from vector-im/dbkr/pttsounds
Add push-to-talk sound effects
2022-05-12 10:07:50 +01:00
David Baker
323505fbb4 Put back listeners commented for testing 2022-05-12 10:04:14 +01:00
David Baker
2b06c6f2e6 More types on PTTCallView
Give it a props type
2022-05-11 17:44:26 +01:00
David Baker
5a56e46f7b Prettier 2022-05-11 16:50:41 +01:00
David Baker
abe9ece38f Add push-to-talk sound effects
Fixes https://github.com/vector-im/element-call/issues/296
2022-05-11 16:28:08 +01:00
David Baker
cb8d837370 Fetch redirects file from github 2022-05-11 13:20:35 +01:00
David Baker
500a19d655 Merge pull request #318 from vector-im/dbkr/netlify_redirect
Add redirects to netlify deployment
2022-05-11 13:11:09 +01:00
David Baker
0d3daf5fa3 move config files into config/ 2022-05-11 12:11:49 +01:00
David Baker
66aede01dc Merge pull request #316 from vector-im/dbkr/typescript_round_1
Initial round of typescripting
2022-05-11 11:50:07 +01:00
David Baker
6d7be57dcf More comments 2022-05-11 11:42:17 +01:00
David Baker
5b913205af Add redirects to netlify deployment 2022-05-11 11:39:48 +01:00
David Baker
fd93d89b26 Merge pull request #317 from dbkr/main
Add GHA to deploy main branch to Netlify
2022-05-11 11:13:18 +01:00
David Baker
abdfcd879d Hopefully add sentry to cd builds 2022-05-10 21:24:20 +01:00
Robert Long
b231424f96 Reference vite/svgr types 2022-05-10 13:04:04 -07:00
David Baker
b2418d5384 Put manual deployment updating back
the netlify action's deployment updating doesn't seem to work
2022-05-10 20:48:30 +01:00
David Baker
f2232a0740 Enable production deploys
and also remove the manual deployment updating as it looks like the netlify action supports it anyway
2022-05-10 19:55:05 +01:00
David Baker
04c6d990bd Make dir name match 2022-05-10 19:39:27 +01:00
David Baker
455bb09108 Try bumping token permissions 2022-05-10 19:35:40 +01:00
David Baker
d8fe617535 Try this path 2022-05-10 19:20:15 +01:00
David Baker
970568fd17 yaml indenting 2022-05-10 19:19:22 +01:00
David Baker
f6677889e0 Hopefully deploy main branch to netlify 2022-05-10 19:11:45 +01:00
David Baker
04780ab7aa Seems we can turn noUnusedLocals on after all 2022-05-10 18:12:10 +01:00
David Baker
b7df8019f0 Import request typings
to tell tsc about _Request which has abort()
2022-05-10 18:09:17 +01:00
David Baker
0a9115248d Include more js-sdk types 2022-05-10 17:45:54 +01:00
David Baker
27d492e9e2 Pin js-sdk commit & add olm for types 2022-05-10 17:41:50 +01:00
David Baker
bc22d36ef8 Use the merged js-sdk 2022-05-10 17:22:20 +01:00
David Baker
cf9625f33e Final type fixes
Revert previous type import hack and type a couple more bits
2022-05-10 17:18:26 +01:00
David Baker
446fd9c7c0 Make tsc happy with the js-sdk 2022-05-07 19:02:54 +01:00
David Baker
adc7892d8c Enable type checking & disable lint warning 2022-05-06 22:55:47 +01:00
David Baker
f805f4ead6 Remove some unnecessary tsconfig bits 2022-05-06 22:44:03 +01:00
David Baker
00ffa1b6cd Add types from merge & fix import order 2022-05-06 22:43:22 +01:00
David Baker
055fbe786d Merge remote-tracking branch 'origin/main' into dbkr/typescript_round_1 2022-05-06 21:41:14 +01:00
David Baker
7a561bd034 Merge pull request #315 from vector-im/dbkr/fix_lint_errors
Fix lint errors
2022-05-06 21:35:55 +01:00
David Baker
5fb1f556d5 Prettier 2022-05-06 21:34:58 +01:00
David Baker
f4ba315cef Add more hook dependencies 2022-05-06 21:33:49 +01:00
David Baker
9ba12da544 Merge branch 'main' into dbkr/fix_lint_errors 2022-05-06 21:31:35 +01:00
David Baker
657096fd9a Merge pull request #314 from vector-im/dbkr/vscode_prettier
Set formatter to prettier
2022-05-06 21:29:00 +01:00
David Baker
9374900ce0 Merge pull request #313 from vector-im/dbkr/fix_time_limit
Return to normal state when time limit reached
2022-05-06 21:28:40 +01:00
David Baker
7e5610eb36 Prettier 2022-05-06 21:27:07 +01:00
David Baker
1253638861 Store unmuteError in main state 2022-05-06 21:23:29 +01:00
David Baker
83feb28909 Merge pull request #312 from vector-im/dbkr/license_headers
Add all the license headers
2022-05-06 21:19:44 +01:00
David Baker
5422cb76f1 Merge branch 'main' into dbkr/license_headers 2022-05-06 21:18:45 +01:00
David Baker
a6eb52ae76 Merge pull request #311 from vector-im/dbkr/fix_pt_button_behaviour
Fix mouseup/down behaviour of PTT button
2022-05-06 21:17:50 +01:00
David Baker
4488947eed Initial round of typescripting 2022-05-06 11:32:09 +01:00
David Baker
bf8f164f55 Fix lint errors
Various hooks either missing dependencies or with extra ones.

Two remaining errors are from the recapcta code where I can't
work out if the extra dependency is intentional or not.
2022-05-05 13:15:07 +01:00
David Baker
5487fbc048 Set formatter to prettier
Although this will only work with the extension installed
2022-05-05 12:31:09 +01:00
David Baker
a70dbb130f Run prettier 2022-05-05 12:26:30 +01:00
David Baker
7edf544d73 Return to normal state when time limit reached 2022-05-05 12:22:51 +01:00
David Baker
ad3bde9920 Undo unintentionally commented line 2022-05-04 17:36:35 +01:00
David Baker
85a98b3706 Remove onWindowBlur
we already do this in usePTT
2022-05-04 17:35:43 +01:00
David Baker
85e3f3761a Add all the license headers 2022-05-04 17:09:48 +01:00
David Baker
f0b116714b Fix mouseup/down behaviour of PTT button
Handle mouseup events anywhere so the button releases if you move
the cursor out of the button & release. Likewsie for window losing
focus.
2022-05-04 16:52:45 +01:00
89 changed files with 2114 additions and 241 deletions

View File

@@ -19,12 +19,18 @@ module.exports = {
// We break this rule in a few places: dial it back to a warning
// (and run with max warnings) to tolerate the existing code
"react-hooks/exhaustive-deps": ["warn"],
"jsx-a11y/media-has-caption": ["off"],
},
overrides: [
{
files: [
"src/**/*.{ts,tsx}",
],
extends: [
"plugin:matrix-org/typescript",
"plugin:matrix-org/react",
"prettier",
],
},
],
settings: {

31
.github/workflows/build.yaml vendored Normal file
View File

@@ -0,0 +1,31 @@
name: Build
on:
push:
branches: [main]
env:
VITE_DEFAULT_HOMESERVER: "https://call.ems.host"
VITE_SENTRY_DSN: https://b1e328d49be3402ba96101338989fb35@sentry.matrix.org/41
VITE_SENTRY_ENVIRONMENT: main-branch-cd
VITE_RAGESHAKE_SUBMIT_URL: https://element.io/bugreports/submit
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Yarn cache
uses: actions/setup-node@v3
with:
cache: 'yarn'
- name: Install dependencies
run: "yarn install"
- name: Build
run: "yarn run build"
- name: Upload Artifact
uses: actions/upload-artifact@v2
with:
name: build
path: dist
# We'll only use this in a triggered job, then we're done with it
retention-days: 1

View File

@@ -1,9 +1,9 @@
name: Lint and format
name: Lint, format & type check
on:
pull_request: {}
jobs:
prettier:
name: Lint and format
name: Lint, format & type check
runs-on: ubuntu-latest
steps:
- name: Checkout code
@@ -17,4 +17,6 @@ jobs:
- name: Prettier
run: "yarn run prettier:check"
- name: ESLint
run: "yarn run lint"
run: "yarn run lint:js"
- name: Type check
run: "yarn run lint:types"

79
.github/workflows/netlify-main.yaml vendored Normal file
View File

@@ -0,0 +1,79 @@
name: Netlify Main
on:
workflow_run:
workflows: ["Build"]
types:
- completed
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
deployments: write
if: github.event.workflow_run.conclusion == 'success'
steps:
- name: Create Deployment
uses: bobheadxi/deployments@v1
id: deployment
with:
step: start
token: ${{ secrets.GITHUB_TOKEN }}
env: main-branch-cd
ref: ${{ github.event.workflow_run.head_sha }}
- name: 'Download artifact'
uses: actions/github-script@v3.1.0
with:
script: |
const artifacts = await github.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: ${{ github.event.workflow_run.id }},
});
const matchArtifact = artifacts.data.artifacts.filter((artifact) => {
return artifact.name == "build"
})[0];
const download = await github.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: matchArtifact.id,
archive_format: 'zip',
});
const fs = require('fs');
fs.writeFileSync('${{github.workspace}}/build.zip', Buffer.from(download.data));
- name: Extract Artifacts
run: unzip -d dist build.zip && rm build.zip
- name: Add redirects file
# We fetch from github directly as we don't bother checking out the repo
run: curl -s https://raw.githubusercontent.com/vector-im/element-call/main/config/netlify_redirects > dist/_redirects
- name: Deploy to Netlify
id: netlify
uses: nwtgck/actions-netlify@v1.2.3
with:
publish-dir: dist
deploy-message: "Deploy from GitHub Actions"
production-branch: main
production-deploy: true
# These don't work because we're in workflow_run
enable-pull-request-comment: false
enable-commit-comment: false
github-deployment-environment: main
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
timeout-minutes: 1
- name: Update deployment status
uses: bobheadxi/deployments@v1
if: always()
with:
step: finish
override: false
token: ${{ secrets.GITHUB_TOKEN }}
status: ${{ job.status }}
env: ${{ steps.deployment.outputs.env }}
deployment_id: ${{ steps.deployment.outputs.deployment_id }}
env_url: ${{ steps.netlify.outputs.deploy-url }}

View File

@@ -1,5 +1,6 @@
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.insertSpaces": true,
"editor.tabSize": 2
}

View File

@@ -9,7 +9,7 @@ RUN element-call/scripts/dockerbuild.sh
FROM nginxinc/nginx-unprivileged:alpine
COPY --from=builder /src/element-call/dist /app
COPY scripts/default.conf /etc/nginx/conf.d/
COPY config/default.conf /etc/nginx/conf.d/
USER root

4
config/netlify_redirects Normal file
View File

@@ -0,0 +1,4 @@
# This file is copied to the netlify deploy dir in the upload stage
# Redirect any unknown path to index.html
/* /index.html 200

View File

@@ -8,7 +8,8 @@
"build-storybook": "build-storybook",
"prettier:check": "prettier -c src",
"prettier:format": "prettier -w src",
"lint": "eslint --max-warnings 11 src"
"lint:js": "eslint --max-warnings 2 src",
"lint:types": "tsc"
},
"dependencies": {
"@juggle/resize-observer": "^3.3.1",
@@ -34,7 +35,7 @@
"color-hash": "^2.0.1",
"events": "^3.3.0",
"lodash-move": "^1.1.1",
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#robertlong/group-call",
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#acef1d7dd0b915368730efabee94deb42b2e4058",
"mermaid": "^8.13.8",
"normalize.css": "^8.0.1",
"pako": "^2.0.4",
@@ -51,10 +52,16 @@
},
"devDependencies": {
"@babel/core": "^7.16.5",
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.8.tgz",
"@storybook/react": "^6.5.0-alpha.5",
"@types/request": "^2.48.8",
"@typescript-eslint/eslint-plugin": "^5.22.0",
"@typescript-eslint/parser": "^5.22.0",
"babel-loader": "^8.2.3",
"eslint": "^8.14.0",
"eslint-config-google": "^0.14.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-jsx-a11y": "^6.5.1",
"eslint-plugin-matrix-org": "^0.4.0",
"eslint-plugin-react": "^7.29.4",
@@ -62,6 +69,7 @@
"prettier": "^2.6.2",
"sass": "^1.42.1",
"storybook-builder-vite": "^0.1.12",
"typescript": "^4.6.4",
"vite": "^2.4.2",
"vite-plugin-html-template": "^1.1.0",
"vite-plugin-svgr": "^0.4.0"

17
src/@types/global.d.ts vendored Normal file
View File

@@ -0,0 +1,17 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
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 "matrix-js-sdk/src/@types/global";

2
src/@types/modules.d.ts vendored Normal file
View File

@@ -0,0 +1,2 @@
/// <reference types="vite/client" />
/// <reference types="vite-plugin-svgr/client" />

View File

@@ -1,5 +1,6 @@
import React, { useMemo } from "react";
import classNames from "classnames";
import styles from "./Avatar.module.css";
const backgroundColors = [
@@ -13,7 +14,7 @@ const backgroundColors = [
"#74D12C",
];
function hashStringToArrIndex(str, arrLength) {
function hashStringToArrIndex(str: string, arrLength: number) {
let sum = 0;
for (let i = 0; i < str.length; i++) {
@@ -23,7 +24,16 @@ function hashStringToArrIndex(str, arrLength) {
return sum % arrLength;
}
export function Avatar({
interface Props extends React.HTMLAttributes<HTMLDivElement> {
bgKey?: string;
src: string;
fallback: string;
size?: number;
className: string;
style: React.CSSProperties;
}
export const Avatar: React.FC<Props> = ({
bgKey,
src,
fallback,
@@ -31,7 +41,7 @@ export function Avatar({
className,
style,
...rest
}) {
}) => {
const backgroundColor = useMemo(() => {
const index = hashStringToArrIndex(
bgKey || fallback || src || "",
@@ -40,6 +50,7 @@ export function Avatar({
return backgroundColors[index];
}, [bgKey, src, fallback]);
/* eslint-disable jsx-a11y/alt-text */
return (
<div
className={classNames(styles.avatar, styles[size || "md"], className)}
@@ -55,4 +66,4 @@ export function Avatar({
)}
</div>
);
}
};

View File

@@ -1,8 +1,25 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
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 {
uniqueNamesGenerator,
adjectives,
colors,
animals,
Config,
} from "unique-names-generator";
const elements = [
@@ -126,7 +143,7 @@ const elements = [
"oganesson",
];
export function generateRandomName(config) {
export function generateRandomName(config: Config): string {
return uniqueNamesGenerator({
dictionaries: [colors, adjectives, animals, elements],
style: "lowerCase",

View File

@@ -1,3 +1,19 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
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 matrix, { InteractiveAuth } from "matrix-js-sdk/src/browser-index";
import { useState, useCallback } from "react";
import { useClient } from "../ClientContext";
@@ -7,39 +23,42 @@ export function useInteractiveLogin() {
const { setClient } = useClient();
const [state, setState] = useState({ loading: false });
const auth = useCallback(async (homeserver, username, password) => {
const authClient = matrix.createClient(homeserver);
const auth = useCallback(
async (homeserver, username, password) => {
const authClient = matrix.createClient(homeserver);
const interactiveAuth = new InteractiveAuth({
matrixClient: authClient,
busyChanged(loading) {
setState((prev) => ({ ...prev, loading }));
},
async doRequest(_auth, _background) {
return authClient.login("m.login.password", {
identifier: {
type: "m.id.user",
user: username,
},
password,
});
},
});
const interactiveAuth = new InteractiveAuth({
matrixClient: authClient,
busyChanged(loading) {
setState((prev) => ({ ...prev, loading }));
},
async doRequest(_auth, _background) {
return authClient.login("m.login.password", {
identifier: {
type: "m.id.user",
user: username,
},
password,
});
},
});
const { user_id, access_token, device_id } =
await interactiveAuth.attemptAuth();
const { user_id, access_token, device_id } =
await interactiveAuth.attemptAuth();
const client = await initClient({
baseUrl: defaultHomeserver,
accessToken: access_token,
userId: user_id,
deviceId: device_id,
});
const client = await initClient({
baseUrl: defaultHomeserver,
accessToken: access_token,
userId: user_id,
deviceId: device_id,
});
setClient(client, { user_id, access_token, device_id });
setClient(client, { user_id, access_token, device_id });
return client;
}, []);
return client;
},
[setClient]
);
return [state, auth];
}

View File

@@ -1,3 +1,19 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
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 matrix, { InteractiveAuth } from "matrix-js-sdk/src/browser-index";
import { useState, useEffect, useCallback, useRef } from "react";
import { useClient } from "../ClientContext";
@@ -89,7 +105,7 @@ export function useInteractiveRegistration() {
return client;
},
[]
[setClient]
);
return [state, register];

View File

@@ -1,3 +1,19 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
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 { randomString } from "matrix-js-sdk/src/randomstring";
import { useEffect, useCallback, useRef, useState } from "react";

View File

@@ -1,3 +1,19 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { forwardRef } from "react";
import classNames from "classnames";
import styles from "./Button.module.css";

View File

@@ -1,3 +1,19 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from "react";
import useClipboard from "react-use-clipboard";
import { ReactComponent as CheckIcon } from "../icons/Check.svg";

View File

@@ -1,3 +1,19 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from "react";
import { Link } from "react-router-dom";
import classNames from "classnames";

View File

@@ -1,3 +1,19 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
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.
*/
export * from "./Button";
export * from "./CopyButton";
export * from "./LinkButton";

View File

@@ -1,3 +1,19 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
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 classNames from "classnames";
import React, { forwardRef } from "react";
import styles from "./Form.module.css";

View File

@@ -1,3 +1,19 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from "react";
import { Link } from "react-router-dom";
import { CopyButton } from "../button";

View File

@@ -1,3 +1,19 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from "react";
import { Modal, ModalContent } from "../Modal";
import { Button } from "../button";

View File

@@ -1,3 +1,19 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { useState, useCallback } from "react";
import { createRoom, roomAliasFromRoomName } from "../matrix-utils";
import { useGroupCallRooms } from "./useGroupCallRooms";

View File

@@ -1,3 +1,19 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { useCallback, useState } from "react";
import { Header, HeaderLogo, LeftNav, RightNav } from "../Header";
import { UserMenuContainer } from "../UserMenuContainer";

View File

@@ -1,3 +1,19 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { useState, useEffect } from "react";
const tsCache = {};
@@ -81,7 +97,7 @@ export function useGroupCallRooms(client) {
client.removeListener("GroupCall.incoming", updateRooms);
client.removeListener("GroupCall.participants", updateRooms);
};
}, []);
}, [client]);
return rooms;
}

View File

@@ -1,3 +1,19 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
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 { useObjectRef } from "@react-aria/utils";
import React, { useEffect } from "react";
import { useCallback } from "react";

View File

@@ -1,3 +1,19 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { forwardRef } from "react";
import classNames from "classnames";
import styles from "./Input.module.css";

View File

@@ -1,3 +1,19 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { useRef } from "react";
import { HiddenSelect, useSelect } from "@react-aria/select";
import { useButton } from "@react-aria/button";

View File

@@ -1,3 +1,19 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { useCallback, useRef } from "react";
import styles from "./Toggle.module.css";
import { useToggleButton } from "@react-aria/button";

View File

@@ -54,6 +54,7 @@ const history = createBrowserHistory();
Sentry.init({
dsn: import.meta.env.VITE_SENTRY_DSN,
environment: import.meta.env.VITE_SENTRY_ENVIRONMENT ?? "production",
integrations: [
new Integrations.BrowserTracing({
routingInstrumentation: Sentry.reactRouterV5Instrumentation(history),

View File

@@ -1,3 +1,19 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { forwardRef, useRef } from "react";
import { DismissButton, useOverlay } from "@react-aria/overlays";
import { FocusScope } from "@react-aria/focus";

View File

@@ -1,3 +1,19 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { forwardRef, useRef } from "react";
import styles from "./PopoverMenu.module.css";
import { useMenuTriggerState } from "@react-stately/menu";

View File

@@ -1,3 +1,19 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { useCallback, useEffect, useState } from "react";
import { Button } from "../button";
import { useProfile } from "./useProfile";

View File

@@ -1,3 +1,19 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { useState, useCallback, useEffect } from "react";
import { getAvatarUrl } from "../matrix-utils";

View File

@@ -1,3 +1,19 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from "react";
import styles from "./AudioPreview.module.css";
import { GroupCallState } from "matrix-js-sdk/src/webrtc/groupCall";

View File

@@ -1,3 +1,19 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from "react";
import styles from "./CallEndedView.module.css";
import { LinkButton } from "../button";

View File

@@ -1,3 +1,19 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { useCallback, useEffect } from "react";
import { Modal, ModalContent } from "../Modal";
import { Button } from "../button";

View File

@@ -1,3 +1,19 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from "react";
import { Button } from "../button";
import { PopoverMenuTrigger } from "../popover/PopoverMenu";

View File

@@ -1,3 +1,19 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
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 { Resizable } from "re-resizable";
import React, {
useEffect,

View File

@@ -1,3 +1,19 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from "react";
import { useLoadGroupCall } from "./useLoadGroupCall";
import { ErrorView, FullScreenView } from "../FullScreenView";

View File

@@ -1,3 +1,19 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { useCallback, useEffect, useState } from "react";
import { useHistory } from "react-router-dom";
import { GroupCallState } from "matrix-js-sdk/src/webrtc/groupCall";

View File

@@ -1,3 +1,19 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { useCallback, useMemo } from "react";
import styles from "./InCallView.module.css";
import {

View File

@@ -1,3 +1,19 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from "react";
import { Modal, ModalContent } from "../Modal";
import { CopyButton } from "../button";

View File

@@ -1,3 +1,19 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { useEffect, useRef } from "react";
import styles from "./LobbyView.module.css";
import { Button, CopyButton } from "../button";

View File

@@ -1,3 +1,19 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { useCallback } from "react";
import { Button } from "../button";
import { Menu } from "../Menu";

View File

@@ -1,48 +0,0 @@
import React from "react";
import classNames from "classnames";
import styles from "./PTTButton.module.css";
import { ReactComponent as MicIcon } from "../icons/Mic.svg";
import { Avatar } from "../Avatar";
export function PTTButton({
showTalkOverError,
activeSpeakerUserId,
activeSpeakerDisplayName,
activeSpeakerAvatarUrl,
activeSpeakerIsLocalUser,
size,
startTalking,
stopTalking,
}) {
return (
<button
className={classNames(styles.pttButton, {
[styles.talking]: activeSpeakerUserId,
[styles.error]: showTalkOverError,
})}
onMouseDown={startTalking}
onMouseUp={stopTalking}
>
{activeSpeakerIsLocalUser || !activeSpeakerUserId ? (
<MicIcon
className={styles.micIcon}
width={size / 3}
height={size / 3}
/>
) : (
<Avatar
key={activeSpeakerUserId}
style={{
width: size - 12,
height: size - 12,
borderRadius: size - 12,
fontSize: Math.round((size - 12) / 2),
}}
src={activeSpeakerAvatarUrl}
fallback={activeSpeakerDisplayName.slice(0, 1).toUpperCase()}
className={styles.avatar}
/>
)}
</button>
);
}

164
src/room/PTTButton.tsx Normal file
View File

@@ -0,0 +1,164 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { useCallback, useEffect, useState, createRef } from "react";
import classNames from "classnames";
import styles from "./PTTButton.module.css";
import { ReactComponent as MicIcon } from "../icons/Mic.svg";
import { Avatar } from "../Avatar";
interface Props {
showTalkOverError: boolean;
activeSpeakerUserId: string;
activeSpeakerDisplayName: string;
activeSpeakerAvatarUrl: string;
activeSpeakerIsLocalUser: boolean;
size: number;
startTalking: () => void;
stopTalking: () => void;
}
interface State {
isHeld: boolean;
// If the button is being pressed by touch, the ID of that touch
activeTouchID: number | null;
}
export const PTTButton: React.FC<Props> = ({
showTalkOverError,
activeSpeakerUserId,
activeSpeakerDisplayName,
activeSpeakerAvatarUrl,
activeSpeakerIsLocalUser,
size,
startTalking,
stopTalking,
}) => {
const buttonRef = createRef<HTMLButtonElement>();
const [{ isHeld, activeTouchID }, setState] = useState<State>({
isHeld: false,
activeTouchID: null,
});
const onWindowMouseUp = useCallback(
(e) => {
if (isHeld) stopTalking();
setState({ isHeld: false, activeTouchID: null });
},
[isHeld, setState, stopTalking]
);
const onWindowTouchEnd = useCallback(
(e: TouchEvent) => {
// ignore any ended touches that weren't the one pressing the
// button (bafflingly the TouchList isn't an iterable so we
// have to do this a really old-school way).
let touchFound = false;
for (let i = 0; i < e.changedTouches.length; ++i) {
if (e.changedTouches.item(i).identifier === activeTouchID) {
touchFound = true;
break;
}
}
if (!touchFound) return;
e.preventDefault();
if (isHeld) stopTalking();
setState({ isHeld: false, activeTouchID: null });
},
[isHeld, activeTouchID, setState, stopTalking]
);
const onButtonMouseDown = useCallback(
(e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
setState({ isHeld: true, activeTouchID: null });
startTalking();
},
[setState, startTalking]
);
const onButtonTouchStart = useCallback(
(e: TouchEvent) => {
e.preventDefault();
if (isHeld) return;
setState({
isHeld: true,
activeTouchID: e.changedTouches.item(0).identifier,
});
startTalking();
},
[isHeld, setState, startTalking]
);
useEffect(() => {
const currentButtonElement = buttonRef.current;
// These listeners go on the window so even if the user's cursor / finger
// leaves the button while holding it, the button stays pushed until
// they stop clicking / tapping.
window.addEventListener("mouseup", onWindowMouseUp);
window.addEventListener("touchend", onWindowTouchEnd);
// This is a native DOM listener too because we want to preventDefault in it
// to stop also getting a click event, so we need it to be non-passive.
currentButtonElement.addEventListener("touchstart", onButtonTouchStart, {
passive: false,
});
return () => {
window.removeEventListener("mouseup", onWindowMouseUp);
window.removeEventListener("touchend", onWindowTouchEnd);
currentButtonElement.removeEventListener(
"touchstart",
onButtonTouchStart
);
};
}, [onWindowMouseUp, onWindowTouchEnd, onButtonTouchStart, buttonRef]);
return (
<button
className={classNames(styles.pttButton, {
[styles.talking]: activeSpeakerUserId,
[styles.error]: showTalkOverError,
})}
onMouseDown={onButtonMouseDown}
ref={buttonRef}
>
{activeSpeakerIsLocalUser || !activeSpeakerUserId ? (
<MicIcon
className={styles.micIcon}
width={size / 3}
height={size / 3}
/>
) : (
<Avatar
key={activeSpeakerUserId}
style={{
width: size - 12,
height: size - 12,
borderRadius: size - 12,
fontSize: Math.round((size - 12) / 2),
}}
src={activeSpeakerAvatarUrl}
fallback={activeSpeakerDisplayName.slice(0, 1).toUpperCase()}
className={styles.avatar}
/>
)}
</button>
);
};

View File

@@ -10,6 +10,12 @@
width: 100%;
}
@media (hover: none) {
.pttCallView {
user-select: none;
}
}
.center {
width: 100%;
display: flex;

View File

@@ -1,4 +1,25 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from "react";
import useMeasure from "react-use-measure";
import { ResizeObserver } from "@juggle/resize-observer";
import { GroupCall, MatrixClient, RoomMember } from "matrix-js-sdk";
import { CallFeed } from "matrix-js-sdk/src/webrtc/callFeed";
import { useModalTriggerState } from "../Modal";
import { SettingsModal } from "../settings/SettingsModal";
import { InviteModal } from "./InviteModal";
@@ -9,15 +30,64 @@ import { Facepile } from "../Facepile";
import { PTTButton } from "./PTTButton";
import { PTTFeed } from "./PTTFeed";
import { useMediaHandler } from "../settings/useMediaHandler";
import useMeasure from "react-use-measure";
import { ResizeObserver } from "@juggle/resize-observer";
import { usePTT } from "./usePTT";
import { Timer } from "./Timer";
import { Toggle } from "../input/Toggle";
import { getAvatarUrl } from "../matrix-utils";
import { ReactComponent as AudioIcon } from "../icons/Audio.svg";
import { usePTTSounds } from "../sound/usePttSounds";
import { PTTClips } from "../sound/PTTClips";
export function PTTCallView({
function getPromptText(
showTalkOverError: boolean,
pttButtonHeld: boolean,
activeSpeakerIsLocalUser: boolean,
talkOverEnabled: boolean,
activeSpeakerUserId: string,
activeSpeakerDisplayName: string
): string {
const isTouchScreen = Boolean(window.ontouchstart !== undefined);
if (showTalkOverError) {
return "You can't talk at the same time";
}
if (pttButtonHeld && activeSpeakerIsLocalUser) {
if (isTouchScreen) {
return "Release to stop";
} else {
return "Release spacebar key to stop";
}
}
if (talkOverEnabled && activeSpeakerUserId && !activeSpeakerIsLocalUser) {
if (isTouchScreen) {
return `Press and hold to talk over ${activeSpeakerDisplayName}`;
} else {
return `Press and hold spacebar to talk over ${activeSpeakerDisplayName}`;
}
}
if (isTouchScreen) {
return "Press and hold to talk";
} else {
return "Press and hold spacebar to talk";
}
}
interface Props {
client: MatrixClient;
roomId: string;
roomName: string;
groupCall: GroupCall;
participants: RoomMember[];
userMediaFeeds: CallFeed[];
onLeave: () => void;
setShowInspector: (boolean) => void;
showInspector: boolean;
}
export const PTTCallView: React.FC<Props> = ({
client,
roomId,
roomName,
@@ -27,7 +97,7 @@ export function PTTCallView({
onLeave,
setShowInspector,
showInspector,
}) {
}) => {
const { modalState: inviteModalState, modalProps: inviteModalProps } =
useModalTriggerState();
const { modalState: settingsModalState, modalProps: settingsModalProps } =
@@ -39,6 +109,14 @@ export function PTTCallView({
const { audioOutput } = useMediaHandler();
const {
startTalkingLocalRef,
startTalkingRemoteRef,
blockedRef,
endTalkingRef,
playClip,
} = usePTTSounds();
const {
pttButtonHeld,
isAdmin,
@@ -47,12 +125,13 @@ export function PTTCallView({
activeSpeakerUserId,
startTalking,
stopTalking,
} = usePTT(client, groupCall, userMediaFeeds);
transmitBlocked,
} = usePTT(client, groupCall, userMediaFeeds, playClip);
const showTalkOverError = pttButtonHeld && transmitBlocked;
const activeSpeakerIsLocalUser =
activeSpeakerUserId && client.getUserId() === activeSpeakerUserId;
const showTalkOverError =
pttButtonHeld && !activeSpeakerIsLocalUser && !talkOverEnabled;
const activeSpeakerUser = activeSpeakerUserId
? client.getUser(activeSpeakerUserId)
: null;
@@ -69,6 +148,12 @@ export function PTTCallView({
return (
<div className={styles.pttCallView} ref={containerRef}>
<PTTClips
startTalkingLocalRef={startTalkingLocalRef}
startTalkingRemoteRef={startTalkingRemoteRef}
endTalkingRef={endTalkingRef}
blockedRef={blockedRef}
/>
<Header className={styles.header}>
<LeftNav>
<RoomSetupHeaderInfo roomName={roomName} onPress={onLeave} />
@@ -121,15 +206,14 @@ export function PTTCallView({
stopTalking={stopTalking}
/>
<p className={styles.actionTip}>
{showTalkOverError
? "You can't talk at the same time"
: pttButtonHeld
? "Release spacebar key to stop"
: talkOverEnabled &&
activeSpeakerUserId &&
!activeSpeakerIsLocalUser
? `Press and hold spacebar to talk over ${activeSpeakerDisplayName}`
: "Press and hold spacebar to talk"}
{getPromptText(
showTalkOverError,
pttButtonHeld,
activeSpeakerIsLocalUser,
talkOverEnabled,
activeSpeakerUserId,
activeSpeakerDisplayName
)}
</p>
{userMediaFeeds.map((callFeed) => (
<PTTFeed
@@ -161,4 +245,4 @@ export function PTTCallView({
)}
</div>
);
}
};

View File

@@ -1,3 +1,19 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from "react";
import { useCallFeed } from "../video-grid/useCallFeed";
import { useMediaStream } from "../video-grid/useMediaStream";

View File

@@ -1,3 +1,19 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { useEffect } from "react";
import { Modal, ModalContent } from "../Modal";
import { Button } from "../button";

View File

@@ -1,3 +1,19 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { useCallback, useState } from "react";
import styles from "./RoomAuthView.module.css";
import { Button } from "../button";

View File

@@ -1,3 +1,19 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { useEffect } from "react";
import { useLocation, useHistory } from "react-router-dom";
import { defaultHomeserverHost } from "../matrix-utils";

View File

@@ -1,3 +1,19 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { useEffect, useState } from "react";
function leftPad(value) {

View File

@@ -1,3 +1,19 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from "react";
import { MicButton, VideoButton } from "../button";
import { useMediaStream } from "../video-grid/useMediaStream";

View File

@@ -1,3 +1,19 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { useCallback, useEffect, useState } from "react";
import {
GroupCallEvent,

View File

@@ -1,3 +1,19 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { useState, useEffect } from "react";
import { isLocalRoomId, createRoom, roomNameFromRoomId } from "../matrix-utils";
@@ -86,7 +102,7 @@ export function useLoadGroupCall(client, roomId, viaServers, createIfNotFound) {
.catch((error) =>
setState((prevState) => ({ ...prevState, loading: false, error }))
);
}, [client, roomId, state.reloadId]);
}, [client, roomId, state.reloadId, createIfNotFound, viaServers]);
return state;
}

View File

@@ -1,121 +0,0 @@
import { useCallback, useEffect, useState } from "react";
export function usePTT(client, groupCall, userMediaFeeds) {
const [
{ pttButtonHeld, isAdmin, talkOverEnabled, activeSpeakerUserId },
setState,
] = useState(() => {
const roomMember = groupCall.room.getMember(client.getUserId());
const activeSpeakerFeed = userMediaFeeds.find((f) => !f.isAudioMuted());
return {
isAdmin: roomMember.powerLevel >= 100,
talkOverEnabled: false,
pttButtonHeld: false,
activeSpeakerUserId: activeSpeakerFeed ? activeSpeakerFeed.userId : null,
};
});
useEffect(() => {
function onMuteStateChanged(...args) {
const activeSpeakerFeed = userMediaFeeds.find((f) => !f.isAudioMuted());
setState((prevState) => ({
...prevState,
activeSpeakerUserId: activeSpeakerFeed
? activeSpeakerFeed.userId
: null,
}));
}
for (const callFeed of userMediaFeeds) {
callFeed.addListener("mute_state_changed", onMuteStateChanged);
}
const activeSpeakerFeed = userMediaFeeds.find((f) => !f.isAudioMuted());
setState((prevState) => ({
...prevState,
activeSpeakerUserId: activeSpeakerFeed ? activeSpeakerFeed.userId : null,
}));
return () => {
for (const callFeed of userMediaFeeds) {
callFeed.removeListener("mute_state_changed", onMuteStateChanged);
}
};
}, [userMediaFeeds]);
const startTalking = useCallback(() => {
if (!activeSpeakerUserId || isAdmin || talkOverEnabled) {
if (groupCall.isMicrophoneMuted()) {
groupCall.setMicrophoneMuted(false);
}
setState((prevState) => ({ ...prevState, pttButtonHeld: true }));
}
}, []);
const stopTalking = useCallback(() => {
if (!groupCall.isMicrophoneMuted()) {
groupCall.setMicrophoneMuted(true);
}
setState((prevState) => ({ ...prevState, pttButtonHeld: false }));
}, []);
useEffect(() => {
function onKeyDown(event) {
if (event.code === "Space") {
event.preventDefault();
startTalking();
}
}
function onKeyUp(event) {
if (event.code === "Space") {
event.preventDefault();
stopTalking();
}
}
function onBlur() {
// TODO: We will need to disable this for a global PTT hotkey to work
if (!groupCall.isMicrophoneMuted()) {
groupCall.setMicrophoneMuted(true);
}
setState((prevState) => ({ ...prevState, pttButtonHeld: false }));
}
window.addEventListener("keydown", onKeyDown);
window.addEventListener("keyup", onKeyUp);
window.addEventListener("blur", onBlur);
return () => {
window.removeEventListener("keydown", onKeyDown);
window.removeEventListener("keyup", onKeyUp);
window.removeEventListener("blur", onBlur);
};
}, [activeSpeakerUserId, isAdmin, talkOverEnabled]);
const setTalkOverEnabled = useCallback((talkOverEnabled) => {
setState((prevState) => ({
...prevState,
talkOverEnabled,
}));
}, []);
return {
pttButtonHeld,
isAdmin,
talkOverEnabled,
setTalkOverEnabled,
activeSpeakerUserId,
startTalking,
stopTalking,
};
}

278
src/room/usePTT.ts Normal file
View File

@@ -0,0 +1,278 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { useCallback, useEffect, useState } from "react";
import { MatrixClient } from "matrix-js-sdk/src/client";
import { GroupCall } from "matrix-js-sdk/src/webrtc/groupCall";
import { CallFeed, CallFeedEvent } from "matrix-js-sdk/src/webrtc/callFeed";
import { logger } from "matrix-js-sdk/src/logger";
import { PlayClipFunction, PTTClipID } from "../sound/usePttSounds";
// Works out who the active speaker should be given what feeds are active and
// the power level of each user.
function getActiveSpeakerFeed(
feeds: CallFeed[],
groupCall: GroupCall
): CallFeed | null {
const activeSpeakerFeeds = feeds.filter((f) => !f.isAudioMuted());
let activeSpeakerFeed = null;
let highestPowerLevel = null;
for (const feed of activeSpeakerFeeds) {
const member = groupCall.room.getMember(feed.userId);
if (highestPowerLevel === null || member.powerLevel > highestPowerLevel) {
highestPowerLevel = member.powerLevel;
activeSpeakerFeed = feed;
}
}
return activeSpeakerFeed;
}
export interface PTTState {
pttButtonHeld: boolean;
isAdmin: boolean;
talkOverEnabled: boolean;
setTalkOverEnabled: (boolean) => void;
activeSpeakerUserId: string;
startTalking: () => void;
stopTalking: () => void;
transmitBlocked: boolean;
}
export const usePTT = (
client: MatrixClient,
groupCall: GroupCall,
userMediaFeeds: CallFeed[],
playClip: PlayClipFunction
): PTTState => {
// Used to serialise all the mute calls so they don't race. It has
// its own state as its always set separately from anything else.
const [mutePromise, setMutePromise] = useState(
Promise.resolve<boolean | void>(false)
);
// Wrapper to serialise all the mute operations on the promise
const setMicMuteWrapper = useCallback(
(muted: boolean) => {
setMutePromise(
mutePromise.then(() => {
return groupCall.setMicrophoneMuted(muted).catch((e) => {
logger.error("Failed to unmute microphone", e);
});
})
);
},
[groupCall, mutePromise]
);
const [
{
pttButtonHeld,
isAdmin,
talkOverEnabled,
activeSpeakerUserId,
transmitBlocked,
},
setState,
] = useState(() => {
const roomMember = groupCall.room.getMember(client.getUserId());
const activeSpeakerFeed = getActiveSpeakerFeed(userMediaFeeds, groupCall);
return {
isAdmin: roomMember.powerLevel >= 100,
talkOverEnabled: false,
pttButtonHeld: false,
activeSpeakerUserId: activeSpeakerFeed ? activeSpeakerFeed.userId : null,
transmitBlocked: false,
};
});
const onMuteStateChanged = useCallback(() => {
const activeSpeakerFeed = getActiveSpeakerFeed(userMediaFeeds, groupCall);
let blocked = false;
if (activeSpeakerUserId === null && activeSpeakerFeed !== null) {
if (activeSpeakerFeed.userId === client.getUserId()) {
playClip(PTTClipID.START_TALKING_LOCAL);
} else {
playClip(PTTClipID.START_TALKING_REMOTE);
}
} else if (activeSpeakerUserId !== null && activeSpeakerFeed === null) {
playClip(PTTClipID.END_TALKING);
} else if (
pttButtonHeld &&
activeSpeakerUserId === client.getUserId() &&
activeSpeakerFeed?.userId !== client.getUserId()
) {
// We were talking but we've been cut off: mute our own mic
// (this is the easier way of cutting other speakers off if an
// admin barges in: we could also mute the non-admin speaker
// on all receivers, but we'd have to make sure we unmuted them
// correctly.)
setMicMuteWrapper(true);
blocked = true;
playClip(PTTClipID.BLOCKED);
}
setState((prevState) => {
return {
...prevState,
activeSpeakerUserId: activeSpeakerFeed
? activeSpeakerFeed.userId
: null,
transmitBlocked: blocked,
};
});
}, [
playClip,
groupCall,
pttButtonHeld,
activeSpeakerUserId,
client,
userMediaFeeds,
setMicMuteWrapper,
]);
useEffect(() => {
for (const callFeed of userMediaFeeds) {
callFeed.addListener(CallFeedEvent.MuteStateChanged, onMuteStateChanged);
}
const activeSpeakerFeed = getActiveSpeakerFeed(userMediaFeeds, groupCall);
setState((prevState) => ({
...prevState,
activeSpeakerUserId: activeSpeakerFeed ? activeSpeakerFeed.userId : null,
}));
return () => {
for (const callFeed of userMediaFeeds) {
callFeed.removeListener(
CallFeedEvent.MuteStateChanged,
onMuteStateChanged
);
}
};
}, [userMediaFeeds, onMuteStateChanged, groupCall]);
const startTalking = useCallback(async () => {
if (pttButtonHeld) return;
let blocked = false;
if (activeSpeakerUserId && !(isAdmin && talkOverEnabled)) {
playClip(PTTClipID.BLOCKED);
blocked = true;
}
// setstate before doing the async call to mute / unmute the mic
setState((prevState) => ({
...prevState,
pttButtonHeld: true,
transmitBlocked: blocked,
}));
if (!blocked && groupCall.isMicrophoneMuted()) {
setMicMuteWrapper(false);
}
}, [
pttButtonHeld,
groupCall,
activeSpeakerUserId,
isAdmin,
talkOverEnabled,
setState,
playClip,
setMicMuteWrapper,
]);
const stopTalking = useCallback(async () => {
setState((prevState) => ({
...prevState,
pttButtonHeld: false,
transmitBlocked: false,
}));
setMicMuteWrapper(true);
}, [setMicMuteWrapper]);
useEffect(() => {
function onKeyDown(event: KeyboardEvent): void {
if (event.code === "Space") {
event.preventDefault();
if (pttButtonHeld) return;
startTalking();
}
}
function onKeyUp(event: KeyboardEvent): void {
if (event.code === "Space") {
event.preventDefault();
stopTalking();
}
}
function onBlur(): void {
// TODO: We will need to disable this for a global PTT hotkey to work
if (!groupCall.isMicrophoneMuted()) {
setMicMuteWrapper(true);
}
setState((prevState) => ({ ...prevState, pttButtonHeld: false }));
}
window.addEventListener("keydown", onKeyDown);
window.addEventListener("keyup", onKeyUp);
window.addEventListener("blur", onBlur);
return () => {
window.removeEventListener("keydown", onKeyDown);
window.removeEventListener("keyup", onKeyUp);
window.removeEventListener("blur", onBlur);
};
}, [
groupCall,
startTalking,
stopTalking,
activeSpeakerUserId,
isAdmin,
talkOverEnabled,
pttButtonHeld,
setMicMuteWrapper,
]);
const setTalkOverEnabled = useCallback((talkOverEnabled) => {
setState((prevState) => ({
...prevState,
talkOverEnabled,
}));
}, []);
return {
pttButtonHeld,
isAdmin,
talkOverEnabled,
setTalkOverEnabled,
activeSpeakerUserId,
startTalking,
stopTalking,
transmitBlocked,
};
};

View File

@@ -1,3 +1,19 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
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 { useEffect } from "react";
// https://stackoverflow.com/a/9039885

View File

@@ -1,3 +1,19 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
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 { useEffect } from "react";
import * as Sentry from "@sentry/react";

View File

@@ -1,3 +1,19 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from "react";
import { Modal } from "../Modal";
import styles from "./SettingsModal.module.css";

View File

@@ -1,3 +1,19 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { useCallback, useContext, useEffect, useState } from "react";
import { getLogsForReport } from "./rageshake";
import pako from "pako";
@@ -223,7 +239,7 @@ export function useSubmitRageshake() {
console.error(error);
}
},
[client]
[client, json, sending]
);
return {
@@ -294,7 +310,7 @@ export function useRageshakeRequestModal(roomId) {
return () => {
client.removeListener("event", onEvent);
};
}, [modalState.open, roomId]);
}, [modalState.open, roomId, client, modalState]);
return { modalState, modalProps: { ...modalProps, rageshakeRequestId } };
}

View File

@@ -1,3 +1,19 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React, {
useState,
useEffect,

View File

@@ -0,0 +1,19 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
.pttClip {
display: none;
}

70
src/sound/PTTClips.tsx Normal file
View File

@@ -0,0 +1,70 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from "react";
import startTalkLocalOggUrl from "./start_talk_local.ogg";
import startTalkLocalMp3Url from "./start_talk_local.mp3";
import startTalkRemoteOggUrl from "./start_talk_remote.ogg";
import startTalkRemoteMp3Url from "./start_talk_remote.mp3";
import endTalkOggUrl from "./end_talk.ogg";
import endTalkMp3Url from "./end_talk.mp3";
import blockedOggUrl from "./blocked.ogg";
import blockedMp3Url from "./blocked.mp3";
import styles from "./PTTClips.module.css";
interface Props {
startTalkingLocalRef: React.RefObject<HTMLAudioElement>;
startTalkingRemoteRef: React.RefObject<HTMLAudioElement>;
endTalkingRef: React.RefObject<HTMLAudioElement>;
blockedRef: React.RefObject<HTMLAudioElement>;
}
export const PTTClips: React.FC<Props> = ({
startTalkingLocalRef,
startTalkingRemoteRef,
endTalkingRef,
blockedRef,
}) => {
return (
<>
<audio
preload="true"
className={styles.pttClip}
ref={startTalkingLocalRef}
>
<source type="audio/ogg" src={startTalkLocalOggUrl} />
<source type="audio/mpeg" src={startTalkLocalMp3Url} />
</audio>
<audio
preload="true"
className={styles.pttClip}
ref={startTalkingRemoteRef}
>
<source type="audio/ogg" src={startTalkRemoteOggUrl} />
<source type="audio/mpeg" src={startTalkRemoteMp3Url} />
</audio>
<audio preload="true" className={styles.pttClip} ref={endTalkingRef}>
<source type="audio/ogg" src={endTalkOggUrl} />
<source type="audio/mpeg" src={endTalkMp3Url} />
</audio>
<audio preload="true" className={styles.pttClip} ref={blockedRef}>
<source type="audio/ogg" src={blockedOggUrl} />
<source type="audio/mpeg" src={blockedMp3Url} />
</audio>
</>
);
};

BIN
src/sound/blocked.mp3 Normal file

Binary file not shown.

BIN
src/sound/blocked.ogg Normal file

Binary file not shown.

BIN
src/sound/end_talk.mp3 Normal file

Binary file not shown.

BIN
src/sound/end_talk.ogg Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

77
src/sound/usePttSounds.ts Normal file
View File

@@ -0,0 +1,77 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { useCallback, useState } from "react";
export enum PTTClipID {
START_TALKING_LOCAL,
START_TALKING_REMOTE,
END_TALKING,
BLOCKED,
}
export type PlayClipFunction = (clipID: PTTClipID) => void;
interface PTTSounds {
startTalkingLocalRef: React.RefObject<HTMLAudioElement>;
startTalkingRemoteRef: React.RefObject<HTMLAudioElement>;
endTalkingRef: React.RefObject<HTMLAudioElement>;
blockedRef: React.RefObject<HTMLAudioElement>;
playClip: PlayClipFunction;
}
export const usePTTSounds = (): PTTSounds => {
const [startTalkingLocalRef] = useState(React.createRef<HTMLAudioElement>());
const [startTalkingRemoteRef] = useState(React.createRef<HTMLAudioElement>());
const [endTalkingRef] = useState(React.createRef<HTMLAudioElement>());
const [blockedRef] = useState(React.createRef<HTMLAudioElement>());
const playClip = useCallback(
async (clipID: PTTClipID) => {
let ref: React.RefObject<HTMLAudioElement>;
switch (clipID) {
case PTTClipID.START_TALKING_LOCAL:
ref = startTalkingLocalRef;
break;
case PTTClipID.START_TALKING_REMOTE:
ref = startTalkingRemoteRef;
break;
case PTTClipID.END_TALKING:
ref = endTalkingRef;
break;
case PTTClipID.BLOCKED:
ref = blockedRef;
break;
}
if (ref.current) {
ref.current.currentTime = 0;
await ref.current.play();
} else {
console.log("No media element found");
}
},
[startTalkingLocalRef, startTalkingRemoteRef, endTalkingRef, blockedRef]
);
return {
startTalkingLocalRef,
startTalkingRemoteRef,
endTalkingRef,
blockedRef,
playClip,
};
};

View File

@@ -1,3 +1,19 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { useRef } from "react";
import { useTabList, useTab, useTabPanel } from "@react-aria/tabs";
import { Item } from "@react-stately/collections";

View File

@@ -1,3 +1,19 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from "react";
import { TabContainer, TabItem } from "./Tabs";
import { ReactComponent as AudioIcon } from "../icons/Audio.svg";

View File

@@ -1,3 +1,19 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { forwardRef } from "react";
import classNames from "classnames";
import { Link as RouterLink } from "react-router-dom";

View File

@@ -1,3 +1,19 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from "react";
import { Headline, Title, Subtitle, Body, Caption, Micro } from "./Typography";

View File

@@ -1,3 +1,19 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { useCallback, useEffect, useRef, useState } from "react";
import { useDrag, useGesture } from "@use-gesture/react";
import { useSprings } from "@react-spring/web";

View File

@@ -1,3 +1,19 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { useState } from "react";
import { VideoGrid, useVideoGridLayout } from "./VideoGrid";
import { VideoTile } from "./VideoTile";

View File

@@ -1,3 +1,19 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from "react";
import { animated } from "@react-spring/web";
import classNames from "classnames";

View File

@@ -1,3 +1,19 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
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 { SDPStreamMetadataPurpose } from "matrix-js-sdk/src/webrtc/callEventTypes";
import React from "react";
import { useCallFeed } from "./useCallFeed";

View File

@@ -1,3 +1,19 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { useState, useEffect } from "react";
import { CallFeedEvent } from "matrix-js-sdk/src/webrtc/callFeed";

View File

@@ -1,3 +1,19 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
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 { useRef, useEffect } from "react";
export function useMediaStream(stream, audioOutputDevice, mute = false) {

View File

@@ -1,3 +1,19 @@
/*
Copyright 2022 Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { useState, useEffect } from "react";
export function useRoomMemberName(member) {

21
tsconfig.json Normal file
View File

@@ -0,0 +1,21 @@
{
"compilerOptions": {
"target": "es2016",
"esModuleInterop": true,
"module": "commonjs",
"moduleResolution": "node",
"noEmit": true,
"noImplicitAny": false,
"noUnusedLocals": true,
"jsx": "preserve",
"lib": [
"es2020",
"dom",
"dom.iterable"
],
},
"include": [
"./src/**/*.ts",
"./src/**/*.tsx",
],
}

267
yarn.lock
View File

@@ -1364,6 +1364,10 @@
resolved "https://registry.yarnpkg.com/@juggle/resize-observer/-/resize-observer-3.3.1.tgz#b50a781709c81e10701004214340f25475a171a0"
integrity sha512-zMM9Ds+SawiUkakS7y94Ymqx+S0ORzpG3frZirN3l+UlXUmSUR7hF4wxCVqW+ei94JzV5kt0uXBcoOEAuiydrw==
"@matrix-org/olm@https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.8.tgz":
version "3.2.8"
resolved "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.8.tgz#8d53636d045e1776e2a2ec6613e57330dd9ce856"
"@mdx-js/mdx@^1.6.22":
version "1.6.22"
resolved "https://registry.yarnpkg.com/@mdx-js/mdx/-/mdx-1.6.22.tgz#8a723157bf90e78f17dc0f27995398e6c731f1ba"
@@ -2826,6 +2830,11 @@
"@svgr/hast-util-to-babel-ast" "^5.5.0"
svg-parser "^2.0.2"
"@types/caseless@*":
version "0.12.2"
resolved "https://registry.yarnpkg.com/@types/caseless/-/caseless-0.12.2.tgz#f65d3d6389e01eeb458bd54dc8f52b95a9463bc8"
integrity sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==
"@types/color-convert@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@types/color-convert/-/color-convert-2.0.0.tgz#8f5ee6b9e863dcbee5703f5a517ffb13d3ea4e22"
@@ -2894,6 +2903,16 @@
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d"
integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==
"@types/json-schema@^7.0.9":
version "7.0.11"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3"
integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==
"@types/json5@^0.0.29":
version "0.0.29"
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4=
"@types/mdast@^3.0.0":
version "3.0.10"
resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.10.tgz#4724244a82a4598884cbbe9bcfd73dff927ee8af"
@@ -2980,6 +2999,16 @@
"@types/scheduler" "*"
csstype "^3.0.2"
"@types/request@^2.48.8":
version "2.48.8"
resolved "https://registry.yarnpkg.com/@types/request/-/request-2.48.8.tgz#0b90fde3b655ab50976cb8c5ac00faca22f5a82c"
integrity sha512-whjk1EDJPcAR2kYHRbFl/lKeeKYTi05A15K9bnLInCVroNDCtXce57xKdI0/rQaA3K+6q0eFyUBPmqfSndUZdQ==
dependencies:
"@types/caseless" "*"
"@types/node" "*"
"@types/tough-cookie" "*"
form-data "^2.5.0"
"@types/retry@^0.12.0":
version "0.12.1"
resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.1.tgz#d8f1c0d0dc23afad6dc16a9e993a0865774b4065"
@@ -3000,6 +3029,11 @@
resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.8.tgz#b94a4391c85666c7b73299fd3ad79d4faa435310"
integrity sha512-ipixuVrh2OdNmauvtT51o3d8z12p6LtFW9in7U79der/kwejjdNchQC5UMn5u/KxNoM7VHHOs/l8KS8uHxhODQ==
"@types/tough-cookie@*":
version "4.0.2"
resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.2.tgz#6286b4c7228d58ab7866d19716f3696e03a09397"
integrity sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==
"@types/uglify-js@*":
version "3.13.1"
resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.13.1.tgz#5e889e9e81e94245c75b6450600e1c5ea2878aea"
@@ -3038,6 +3072,86 @@
anymatch "^3.0.0"
source-map "^0.6.0"
"@typescript-eslint/eslint-plugin@^5.22.0":
version "5.22.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.22.0.tgz#7b52a0de2e664044f28b36419210aea4ab619e2a"
integrity sha512-YCiy5PUzpAeOPGQ7VSGDEY2NeYUV1B0swde2e0HzokRsHBYjSdF6DZ51OuRZxVPHx0032lXGLvOMls91D8FXlg==
dependencies:
"@typescript-eslint/scope-manager" "5.22.0"
"@typescript-eslint/type-utils" "5.22.0"
"@typescript-eslint/utils" "5.22.0"
debug "^4.3.2"
functional-red-black-tree "^1.0.1"
ignore "^5.1.8"
regexpp "^3.2.0"
semver "^7.3.5"
tsutils "^3.21.0"
"@typescript-eslint/parser@^5.22.0":
version "5.22.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.22.0.tgz#7bedf8784ef0d5d60567c5ba4ce162460e70c178"
integrity sha512-piwC4krUpRDqPaPbFaycN70KCP87+PC5WZmrWs+DlVOxxmF+zI6b6hETv7Quy4s9wbkV16ikMeZgXsvzwI3icQ==
dependencies:
"@typescript-eslint/scope-manager" "5.22.0"
"@typescript-eslint/types" "5.22.0"
"@typescript-eslint/typescript-estree" "5.22.0"
debug "^4.3.2"
"@typescript-eslint/scope-manager@5.22.0":
version "5.22.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.22.0.tgz#590865f244ebe6e46dc3e9cab7976fc2afa8af24"
integrity sha512-yA9G5NJgV5esANJCO0oF15MkBO20mIskbZ8ijfmlKIvQKg0ynVKfHZ15/nhAJN5m8Jn3X5qkwriQCiUntC9AbA==
dependencies:
"@typescript-eslint/types" "5.22.0"
"@typescript-eslint/visitor-keys" "5.22.0"
"@typescript-eslint/type-utils@5.22.0":
version "5.22.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.22.0.tgz#0c0e93b34210e334fbe1bcb7250c470f4a537c19"
integrity sha512-iqfLZIsZhK2OEJ4cQ01xOq3NaCuG5FQRKyHicA3xhZxMgaxQazLUHbH/B2k9y5i7l3+o+B5ND9Mf1AWETeMISA==
dependencies:
"@typescript-eslint/utils" "5.22.0"
debug "^4.3.2"
tsutils "^3.21.0"
"@typescript-eslint/types@5.22.0":
version "5.22.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.22.0.tgz#50a4266e457a5d4c4b87ac31903b28b06b2c3ed0"
integrity sha512-T7owcXW4l0v7NTijmjGWwWf/1JqdlWiBzPqzAWhobxft0SiEvMJB56QXmeCQjrPuM8zEfGUKyPQr/L8+cFUBLw==
"@typescript-eslint/typescript-estree@5.22.0":
version "5.22.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.22.0.tgz#e2116fd644c3e2fda7f4395158cddd38c0c6df97"
integrity sha512-EyBEQxvNjg80yinGE2xdhpDYm41so/1kOItl0qrjIiJ1kX/L/L8WWGmJg8ni6eG3DwqmOzDqOhe6763bF92nOw==
dependencies:
"@typescript-eslint/types" "5.22.0"
"@typescript-eslint/visitor-keys" "5.22.0"
debug "^4.3.2"
globby "^11.0.4"
is-glob "^4.0.3"
semver "^7.3.5"
tsutils "^3.21.0"
"@typescript-eslint/utils@5.22.0":
version "5.22.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.22.0.tgz#1f2c4897e2cf7e44443c848a13c60407861babd8"
integrity sha512-HodsGb037iobrWSUMS7QH6Hl1kppikjA1ELiJlNSTYf/UdMEwzgj0WIp+lBNb6WZ3zTwb0tEz51j0Wee3iJ3wQ==
dependencies:
"@types/json-schema" "^7.0.9"
"@typescript-eslint/scope-manager" "5.22.0"
"@typescript-eslint/types" "5.22.0"
"@typescript-eslint/typescript-estree" "5.22.0"
eslint-scope "^5.1.1"
eslint-utils "^3.0.0"
"@typescript-eslint/visitor-keys@5.22.0":
version "5.22.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.22.0.tgz#f49c0ce406944ffa331a1cfabeed451ea4d0909c"
integrity sha512-DbgTqn2Dv5RFWluG88tn0pP6Ex0ROF+dpDO1TNNZdRtLjUr6bdznjA6f/qNqJLjd2PgguAES2Zgxh/JzwzETDg==
dependencies:
"@typescript-eslint/types" "5.22.0"
eslint-visitor-keys "^3.0.0"
"@use-gesture/core@10.2.11":
version "10.2.11"
resolved "https://registry.yarnpkg.com/@use-gesture/core/-/core-10.2.11.tgz#914c36f190bcf452500d11a11fc294fe56e5dc2f"
@@ -3601,6 +3715,16 @@ array.prototype.flat@^1.2.1:
define-properties "^1.1.3"
es-abstract "^1.19.0"
array.prototype.flat@^1.2.5:
version "1.3.0"
resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.0.tgz#0b0c1567bf57b38b56b4c97b8aa72ab45e4adc7b"
integrity sha512-12IUEkHsAhA4DY5s0FPgNXIdc8VRSqD9Zp78a5au9abH/SOBrsp082JOWFNTjkMozh8mqcdiKuaLGhPeYztxSw==
dependencies:
call-bind "^1.0.2"
define-properties "^1.1.3"
es-abstract "^1.19.2"
es-shim-unscopables "^1.0.0"
array.prototype.flatmap@^1.2.1:
version "1.2.5"
resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.2.5.tgz#908dc82d8a406930fdf38598d51e7411d18d4446"
@@ -5453,14 +5577,14 @@ debounce@^1.2.1:
resolved "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz"
integrity sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==
debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0:
debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
dependencies:
ms "2.0.0"
debug@^3.0.0:
debug@^3.0.0, debug@^3.2.7:
version "3.2.7"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a"
integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==
@@ -6203,11 +6327,51 @@ escape-string-regexp@^4.0.0:
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
eslint-config-google@^0.14.0:
version "0.14.0"
resolved "https://registry.yarnpkg.com/eslint-config-google/-/eslint-config-google-0.14.0.tgz#4f5f8759ba6e11b424294a219dbfa18c508bcc1a"
integrity sha512-WsbX4WbjuMvTdeVL6+J3rK1RGhCTqjsFjX7UMSMgZiyxxaNLkoJENbrGExzERFeoTpGw3F3FypTiWAP9ZXzkEw==
eslint-config-prettier@^8.5.0:
version "8.5.0"
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz#5a81680ec934beca02c7b1a61cf8ca34b66feab1"
integrity sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==
eslint-import-resolver-node@^0.3.6:
version "0.3.6"
resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz#4048b958395da89668252001dbd9eca6b83bacbd"
integrity sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==
dependencies:
debug "^3.2.7"
resolve "^1.20.0"
eslint-module-utils@^2.7.3:
version "2.7.3"
resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.3.tgz#ad7e3a10552fdd0642e1e55292781bd6e34876ee"
integrity sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ==
dependencies:
debug "^3.2.7"
find-up "^2.1.0"
eslint-plugin-import@^2.26.0:
version "2.26.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz#f812dc47be4f2b72b478a021605a59fc6fe8b88b"
integrity sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==
dependencies:
array-includes "^3.1.4"
array.prototype.flat "^1.2.5"
debug "^2.6.9"
doctrine "^2.1.0"
eslint-import-resolver-node "^0.3.6"
eslint-module-utils "^2.7.3"
has "^1.0.3"
is-core-module "^2.8.1"
is-glob "^4.0.3"
minimatch "^3.1.2"
object.values "^1.1.5"
resolve "^1.22.0"
tsconfig-paths "^3.14.1"
eslint-plugin-jsx-a11y@^6.5.1:
version "6.5.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.5.1.tgz#cdbf2df901040ca140b6ec14715c988889c2a6d8"
@@ -6256,7 +6420,7 @@ eslint-plugin-react@^7.29.4:
semver "^6.3.0"
string.prototype.matchall "^4.0.6"
eslint-scope@5.1.1:
eslint-scope@5.1.1, eslint-scope@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c"
integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==
@@ -6292,7 +6456,7 @@ eslint-visitor-keys@^2.0.0:
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303"
integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==
eslint-visitor-keys@^3.3.0:
eslint-visitor-keys@^3.0.0, eslint-visitor-keys@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826"
integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==
@@ -6683,6 +6847,13 @@ find-up@^1.0.0:
path-exists "^2.0.0"
pinkie-promise "^2.0.0"
find-up@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7"
integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c=
dependencies:
locate-path "^2.0.0"
find-up@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73"
@@ -6790,6 +6961,15 @@ fork-ts-checker-webpack-plugin@^6.0.4:
semver "^7.3.2"
tapable "^1.0.0"
form-data@^2.5.0:
version "2.5.1"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4"
integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.6"
mime-types "^2.1.12"
form-data@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f"
@@ -7073,7 +7253,7 @@ globalthis@^1.0.0:
dependencies:
define-properties "^1.1.3"
globby@^11.0.2:
globby@^11.0.2, globby@^11.0.4:
version "11.1.0"
resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b"
integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==
@@ -7474,7 +7654,7 @@ ignore@^4.0.3:
resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==
ignore@^5.2.0:
ignore@^5.1.8, ignore@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a"
integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==
@@ -8251,6 +8431,14 @@ loader-utils@^2.0.0:
emojis-list "^3.0.0"
json5 "^2.1.2"
locate-path@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e"
integrity sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=
dependencies:
p-locate "^2.0.0"
path-exists "^3.0.0"
locate-path@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e"
@@ -8406,9 +8594,14 @@ markdown-to-jsx@^7.1.3:
resolved "https://registry.yarnpkg.com/markdown-to-jsx/-/markdown-to-jsx-7.1.6.tgz#421487df2a66fe4231d94db653a34da033691e62"
integrity sha512-1wrIGZYwIG2gR3yfRmbr4FlQmhaAKoKTpRo4wur4fp9p0njU1Hi7vR8fj0AUKKIcPduiJmPprzmCB5B/GvlC7g==
"matrix-js-sdk@github:matrix-org/matrix-js-sdk#robertlong/group-call":
version "15.1.0"
resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/8d9cd0fcb37188f63a938f4dd16e1f4e967cf1b6"
matrix-events-sdk@^0.0.1-beta.7:
version "0.0.1-beta.7"
resolved "https://registry.yarnpkg.com/matrix-events-sdk/-/matrix-events-sdk-0.0.1-beta.7.tgz#5ffe45eba1f67cc8d7c2377736c728b322524934"
integrity sha512-9jl4wtWanUFSy2sr2lCjErN/oC8KTAtaeaozJtrgot1JiQcEI4Rda9OLgQ7nLKaqb4Z/QUx/fR3XpDzm5Jy1JA==
"matrix-js-sdk@github:matrix-org/matrix-js-sdk#acef1d7dd0b915368730efabee94deb42b2e4058":
version "17.2.0"
resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/acef1d7dd0b915368730efabee94deb42b2e4058"
dependencies:
"@babel/runtime" "^7.12.5"
another-json "^0.2.0"
@@ -8416,6 +8609,7 @@ markdown-to-jsx@^7.1.3:
bs58 "^4.0.1"
content-type "^1.0.4"
loglevel "^1.7.1"
matrix-events-sdk "^0.0.1-beta.7"
p-retry "^4.5.0"
qs "^6.9.6"
request "^2.88.2"
@@ -8672,6 +8866,11 @@ minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5:
resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz"
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
minimist@^1.2.6:
version "1.2.6"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
minipass-collect@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617"
@@ -9134,6 +9333,13 @@ p-finally@^1.0.0:
resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=
p-limit@^1.1.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8"
integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==
dependencies:
p-try "^1.0.0"
p-limit@^2.0.0, p-limit@^2.2.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
@@ -9148,6 +9354,13 @@ p-limit@^3.0.2:
dependencies:
yocto-queue "^0.1.0"
p-locate@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43"
integrity sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=
dependencies:
p-limit "^1.1.0"
p-locate@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4"
@@ -9203,6 +9416,11 @@ p-timeout@^3.1.0:
dependencies:
p-finally "^1.0.0"
p-try@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3"
integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=
p-try@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
@@ -10656,7 +10874,7 @@ resolve-url@^0.2.1:
resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=
resolve@^1.1.6:
resolve@^1.1.6, resolve@^1.22.0:
version "1.22.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198"
integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==
@@ -11339,6 +11557,11 @@ strip-bom@^2.0.0:
dependencies:
is-utf8 "^0.2.0"
strip-bom@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=
strip-indent@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2"
@@ -11658,7 +11881,17 @@ ts-pnp@^1.1.6:
resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.2.0.tgz#a500ad084b0798f1c3071af391e65912c86bca92"
integrity sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw==
tslib@^1.9.3:
tsconfig-paths@^3.14.1:
version "3.14.1"
resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz#ba0734599e8ea36c862798e920bcf163277b137a"
integrity sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==
dependencies:
"@types/json5" "^0.0.29"
json5 "^1.0.1"
minimist "^1.2.6"
strip-bom "^3.0.0"
tslib@^1.8.1, tslib@^1.9.3:
version "1.14.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
@@ -11668,6 +11901,13 @@ tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.0:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01"
integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==
tsutils@^3.21.0:
version "3.21.0"
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623"
integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==
dependencies:
tslib "^1.8.1"
tty-browserify@0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6"
@@ -11720,6 +11960,11 @@ typedarray@^0.0.6:
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
typescript@^4.6.4:
version "4.6.4"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.4.tgz#caa78bbc3a59e6a5c510d35703f6a09877ce45e9"
integrity sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==
ua-parser-js@^0.7.18:
version "0.7.28"
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.28.tgz#8ba04e653f35ce210239c64661685bf9121dec31"