Compare commits
96 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c13040f0b0 | ||
|
|
24a1091954 | ||
|
|
9fd7329554 | ||
|
|
2a19a9964d | ||
|
|
3fc9c1b74a | ||
|
|
f6f0c20b08 | ||
|
|
26a1c165d9 | ||
|
|
2af87fa8b8 | ||
|
|
d34c8d08a4 | ||
|
|
0f687fb8b8 | ||
|
|
603dd3786a | ||
|
|
9fbe4278c2 | ||
|
|
b222b4f708 | ||
|
|
abc2449b07 | ||
|
|
e6459de0d9 | ||
|
|
323505fbb4 | ||
|
|
2b06c6f2e6 | ||
|
|
5a56e46f7b | ||
|
|
abe9ece38f | ||
|
|
cb8d837370 | ||
|
|
500a19d655 | ||
|
|
0d3daf5fa3 | ||
|
|
66aede01dc | ||
|
|
6d7be57dcf | ||
|
|
5b913205af | ||
|
|
fd93d89b26 | ||
|
|
abdfcd879d | ||
|
|
b231424f96 | ||
|
|
b2418d5384 | ||
|
|
f2232a0740 | ||
|
|
04c6d990bd | ||
|
|
455bb09108 | ||
|
|
d8fe617535 | ||
|
|
970568fd17 | ||
|
|
f6677889e0 | ||
|
|
04780ab7aa | ||
|
|
b7df8019f0 | ||
|
|
0a9115248d | ||
|
|
27d492e9e2 | ||
|
|
bc22d36ef8 | ||
|
|
cf9625f33e | ||
|
|
446fd9c7c0 | ||
|
|
adc7892d8c | ||
|
|
f805f4ead6 | ||
|
|
00ffa1b6cd | ||
|
|
055fbe786d | ||
|
|
7a561bd034 | ||
|
|
5fb1f556d5 | ||
|
|
f4ba315cef | ||
|
|
9ba12da544 | ||
|
|
657096fd9a | ||
|
|
9374900ce0 | ||
|
|
7e5610eb36 | ||
|
|
1253638861 | ||
|
|
83feb28909 | ||
|
|
5422cb76f1 | ||
|
|
a6eb52ae76 | ||
|
|
4488947eed | ||
|
|
bf8f164f55 | ||
|
|
5487fbc048 | ||
|
|
a70dbb130f | ||
|
|
7edf544d73 | ||
|
|
ad3bde9920 | ||
|
|
85a98b3706 | ||
|
|
85e3f3761a | ||
|
|
f0b116714b | ||
|
|
dbef06269b | ||
|
|
894815268a | ||
|
|
8ecec0bc7e | ||
|
|
66839e02f6 | ||
|
|
bad8f36bf5 | ||
|
|
f5c50230a9 | ||
|
|
0136fd3cab | ||
|
|
2d18953344 | ||
|
|
d930ab869a | ||
|
|
dbdb82bd74 | ||
|
|
61309bacd9 | ||
|
|
b3e88d33a7 | ||
|
|
73fda641c8 | ||
|
|
be01a4bd81 | ||
|
|
0814e3c905 | ||
|
|
c7dd2e2093 | ||
|
|
cfa525f957 | ||
|
|
43d579744f | ||
|
|
48a008093b | ||
|
|
70c099c4b5 | ||
|
|
363f2340a0 | ||
|
|
3a6346aa63 | ||
|
|
9ef9680e07 | ||
|
|
e3cec93669 | ||
|
|
b6c926d2c8 | ||
|
|
c430ebb3a3 | ||
|
|
ae13814449 | ||
|
|
dc75c1cfb4 | ||
|
|
38f9a79bd3 | ||
|
|
fc1aaf02bf |
41
.eslintrc.js
Normal file
41
.eslintrc.js
Normal file
@@ -0,0 +1,41 @@
|
||||
module.exports = {
|
||||
plugins: [
|
||||
"matrix-org",
|
||||
],
|
||||
extends: [
|
||||
"plugin:matrix-org/react",
|
||||
"plugin:matrix-org/a11y",
|
||||
"prettier",
|
||||
],
|
||||
env: {
|
||||
browser: true,
|
||||
node: true,
|
||||
},
|
||||
parserOptions: {
|
||||
"ecmaVersion": "latest",
|
||||
"sourceType": "module",
|
||||
},
|
||||
rules: {
|
||||
// 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: {
|
||||
react: {
|
||||
version: "detect",
|
||||
},
|
||||
},
|
||||
};
|
||||
31
.github/workflows/build.yaml
vendored
Normal file
31
.github/workflows/build.yaml
vendored
Normal 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
|
||||
22
.github/workflows/lint.yaml
vendored
Normal file
22
.github/workflows/lint.yaml
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
name: Lint, format & type check
|
||||
on:
|
||||
pull_request: {}
|
||||
jobs:
|
||||
prettier:
|
||||
name: Lint, format & type check
|
||||
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: Prettier
|
||||
run: "yarn run prettier:check"
|
||||
- name: ESLint
|
||||
run: "yarn run lint:js"
|
||||
- name: Type check
|
||||
run: "yarn run lint:types"
|
||||
79
.github/workflows/netlify-main.yaml
vendored
Normal file
79
.github/workflows/netlify-main.yaml
vendored
Normal 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 }}
|
||||
2
.prettierignore
Normal file
2
.prettierignore
Normal file
@@ -0,0 +1,2 @@
|
||||
node_modules
|
||||
dist
|
||||
1
.prettierrc.json
Normal file
1
.prettierrc.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"editor.formatOnSave": true,
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.insertSpaces": true,
|
||||
"editor.tabSize": 2
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
4
config/netlify_redirects
Normal 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
|
||||
22
package.json
22
package.json
@@ -5,7 +5,11 @@
|
||||
"build": "vite build",
|
||||
"serve": "vite preview",
|
||||
"storybook": "start-storybook -p 6006",
|
||||
"build-storybook": "build-storybook"
|
||||
"build-storybook": "build-storybook",
|
||||
"prettier:check": "prettier -c src",
|
||||
"prettier:format": "prettier -w src",
|
||||
"lint:js": "eslint --max-warnings 2 src",
|
||||
"lint:types": "tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"@juggle/resize-observer": "^3.3.1",
|
||||
@@ -31,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",
|
||||
@@ -48,10 +52,24 @@
|
||||
},
|
||||
"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",
|
||||
"eslint-plugin-react-hooks": "^4.5.0",
|
||||
"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
17
src/@types/global.d.ts
vendored
Normal 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
2
src/@types/modules.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/// <reference types="vite/client" />
|
||||
/// <reference types="vite-plugin-svgr/client" />
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -4,35 +4,63 @@ import classNames from "classnames";
|
||||
import { Avatar } from "./Avatar";
|
||||
import { getAvatarUrl } from "./matrix-utils";
|
||||
|
||||
export function Facepile({ className, client, participants, ...rest }) {
|
||||
const overlapMap = {
|
||||
xs: 2,
|
||||
sm: 4,
|
||||
md: 8,
|
||||
};
|
||||
|
||||
const sizeMap = {
|
||||
xs: 24,
|
||||
sm: 32,
|
||||
md: 36,
|
||||
};
|
||||
|
||||
export function Facepile({
|
||||
className,
|
||||
client,
|
||||
participants,
|
||||
max,
|
||||
size,
|
||||
...rest
|
||||
}) {
|
||||
const _size = sizeMap[size];
|
||||
const _overlap = overlapMap[size];
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(styles.facepile, className)}
|
||||
className={classNames(styles.facepile, styles[size], className)}
|
||||
title={participants.map((member) => member.name).join(", ")}
|
||||
style={{ width: participants.length * (_size - _overlap) + _overlap }}
|
||||
{...rest}
|
||||
>
|
||||
{participants.slice(0, 3).map((member, i) => {
|
||||
{participants.slice(0, max).map((member, i) => {
|
||||
const avatarUrl = member.user?.avatarUrl;
|
||||
return (
|
||||
<Avatar
|
||||
key={member.userId}
|
||||
size="xs"
|
||||
src={avatarUrl && getAvatarUrl(client, avatarUrl, 22)}
|
||||
size={size}
|
||||
src={avatarUrl && getAvatarUrl(client, avatarUrl, _size)}
|
||||
fallback={member.name.slice(0, 1).toUpperCase()}
|
||||
className={styles.avatar}
|
||||
style={{ left: i * 22 }}
|
||||
style={{ left: i * (_size - _overlap) }}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{participants.length > 3 && (
|
||||
{participants.length > max && (
|
||||
<Avatar
|
||||
key="additional"
|
||||
size="xs"
|
||||
fallback={`+${participants.length - 3}`}
|
||||
size={size}
|
||||
fallback={`+${participants.length - max}`}
|
||||
className={styles.avatar}
|
||||
style={{ left: 3 * 22 }}
|
||||
style={{ left: max * (_size - _overlap) }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Facepile.defaultProps = {
|
||||
max: 3,
|
||||
size: "xs",
|
||||
};
|
||||
|
||||
@@ -1,11 +1,26 @@
|
||||
.facepile {
|
||||
width: 100%;
|
||||
height: 24px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.facepile.xs {
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.facepile.sm {
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.facepile.md {
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
.facepile .avatar {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
border: 1px solid var(--bgColor2);
|
||||
}
|
||||
|
||||
.facepile.md .avatar {
|
||||
border-width: 2px;
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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";
|
||||
@@ -7,6 +23,8 @@ import { ReactComponent as VideoIcon } from "../icons/Video.svg";
|
||||
import { ReactComponent as DisableVideoIcon } from "../icons/DisableVideo.svg";
|
||||
import { ReactComponent as HangupIcon } from "../icons/Hangup.svg";
|
||||
import { ReactComponent as ScreenshareIcon } from "../icons/Screenshare.svg";
|
||||
import { ReactComponent as SettingsIcon } from "../icons/Settings.svg";
|
||||
import { ReactComponent as AddUserIcon } from "../icons/AddUser.svg";
|
||||
import { useButton } from "@react-aria/button";
|
||||
import { mergeProps, useObjectRef } from "@react-aria/utils";
|
||||
import { TooltipTrigger } from "../Tooltip";
|
||||
@@ -20,6 +38,7 @@ export const variantToClassName = {
|
||||
copy: [styles.copyButton],
|
||||
iconCopy: [styles.iconCopyButton],
|
||||
secondaryCopy: [styles.copyButton],
|
||||
secondaryHangup: [styles.secondaryHangup],
|
||||
};
|
||||
|
||||
export const sizeToClassName = {
|
||||
@@ -126,3 +145,25 @@ export function HangupButton({ className, ...rest }) {
|
||||
</TooltipTrigger>
|
||||
);
|
||||
}
|
||||
|
||||
export function SettingsButton({ className, ...rest }) {
|
||||
return (
|
||||
<TooltipTrigger>
|
||||
<Button variant="toolbar" {...rest}>
|
||||
<SettingsIcon />
|
||||
</Button>
|
||||
{() => "Settings"}
|
||||
</TooltipTrigger>
|
||||
);
|
||||
}
|
||||
|
||||
export function InviteButton({ className, ...rest }) {
|
||||
return (
|
||||
<TooltipTrigger>
|
||||
<Button variant="toolbar" {...rest}>
|
||||
<AddUserIcon />
|
||||
</Button>
|
||||
{() => "Invite"}
|
||||
</TooltipTrigger>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ limitations under the License.
|
||||
.iconButton,
|
||||
.iconCopyButton,
|
||||
.secondary,
|
||||
.secondaryHangup,
|
||||
.copyButton {
|
||||
position: relative;
|
||||
display: flex;
|
||||
@@ -34,6 +35,7 @@ limitations under the License.
|
||||
}
|
||||
|
||||
.secondary,
|
||||
.secondaryHangup,
|
||||
.button,
|
||||
.copyButton {
|
||||
padding: 7px 15px;
|
||||
@@ -53,6 +55,7 @@ limitations under the License.
|
||||
.iconButton:focus,
|
||||
.iconCopyButton:focus,
|
||||
.secondary:focus,
|
||||
.secondaryHangup:focus,
|
||||
.copyButton:focus {
|
||||
outline: auto;
|
||||
}
|
||||
@@ -119,6 +122,12 @@ limitations under the License.
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.secondaryHangup {
|
||||
color: #ff5b55;
|
||||
border: 2px solid #ff5b55;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.copyButton.secondaryCopy {
|
||||
color: var(--textColor1);
|
||||
border-color: var(--textColor1);
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
@@ -13,22 +29,25 @@ import { JoinExistingCallModal } from "./JoinExistingCallModal";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { Headline, Title } from "../typography/Typography";
|
||||
import { Form } from "../form/Form";
|
||||
import { useShouldShowPtt } from "../useShouldShowPtt";
|
||||
|
||||
export function RegisteredView({ client }) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState();
|
||||
const history = useHistory();
|
||||
const shouldShowPtt = useShouldShowPtt();
|
||||
const onSubmit = useCallback(
|
||||
(e) => {
|
||||
e.preventDefault();
|
||||
const data = new FormData(e.target);
|
||||
const roomName = data.get("callName");
|
||||
const ptt = data.get("ptt") !== null;
|
||||
|
||||
async function submit() {
|
||||
setError(undefined);
|
||||
setLoading(true);
|
||||
|
||||
const roomIdOrAlias = await createRoom(client, roomName);
|
||||
const roomIdOrAlias = await createRoom(client, roomName, ptt);
|
||||
|
||||
if (roomIdOrAlias) {
|
||||
history.push(`/room/${roomIdOrAlias}`);
|
||||
@@ -87,6 +106,7 @@ export function RegisteredView({ client }) {
|
||||
required
|
||||
autoComplete="off"
|
||||
/>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
size="lg"
|
||||
@@ -96,6 +116,16 @@ export function RegisteredView({ client }) {
|
||||
{loading ? "Loading..." : "Go"}
|
||||
</Button>
|
||||
</FieldRow>
|
||||
{shouldShowPtt && (
|
||||
<FieldRow className={styles.fieldRow}>
|
||||
<InputField
|
||||
id="ptt"
|
||||
name="ptt"
|
||||
label="Push to Talk"
|
||||
type="checkbox"
|
||||
/>
|
||||
</FieldRow>
|
||||
)}
|
||||
{error && (
|
||||
<FieldRow className={styles.fieldRow}>
|
||||
<ErrorMessage>{error.message}</ErrorMessage>
|
||||
|
||||
@@ -7,6 +7,10 @@
|
||||
}
|
||||
|
||||
.fieldRow {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.fieldRow:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -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";
|
||||
@@ -15,8 +31,10 @@ import { Form } from "../form/Form";
|
||||
import styles from "./UnauthenticatedView.module.css";
|
||||
import commonStyles from "./common.module.css";
|
||||
import { generateRandomName } from "../auth/generateRandomName";
|
||||
import { useShouldShowPtt } from "../useShouldShowPtt";
|
||||
|
||||
export function UnauthenticatedView() {
|
||||
const shouldShowPtt = useShouldShowPtt();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState();
|
||||
const [{ privacyPolicyUrl, recaptchaKey }, register] =
|
||||
@@ -28,6 +46,7 @@ export function UnauthenticatedView() {
|
||||
const data = new FormData(e.target);
|
||||
const roomName = data.get("callName");
|
||||
const displayName = data.get("displayName");
|
||||
const ptt = data.get("ptt") !== null;
|
||||
|
||||
async function submit() {
|
||||
setError(undefined);
|
||||
@@ -41,7 +60,7 @@ export function UnauthenticatedView() {
|
||||
recaptchaResponse,
|
||||
true
|
||||
);
|
||||
const roomIdOrAlias = await createRoom(client, roomName);
|
||||
const roomIdOrAlias = await createRoom(client, roomName, ptt);
|
||||
|
||||
if (roomIdOrAlias) {
|
||||
history.push(`/room/${roomIdOrAlias}`);
|
||||
@@ -111,6 +130,16 @@ export function UnauthenticatedView() {
|
||||
autoComplete="off"
|
||||
/>
|
||||
</FieldRow>
|
||||
{shouldShowPtt && (
|
||||
<FieldRow>
|
||||
<InputField
|
||||
id="ptt"
|
||||
name="ptt"
|
||||
label="Push to Talk"
|
||||
type="checkbox"
|
||||
/>
|
||||
</FieldRow>
|
||||
)}
|
||||
<Caption>
|
||||
By clicking "Go", you agree to our{" "}
|
||||
<Link href={privacyPolicyUrl}>Terms and conditions</Link>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
57
src/input/Toggle.jsx
Normal file
57
src/input/Toggle.jsx
Normal file
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
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";
|
||||
import classNames from "classnames";
|
||||
import { Field } from "./Input";
|
||||
|
||||
export function Toggle({ id, label, className, onChange, isSelected }) {
|
||||
const buttonRef = useRef();
|
||||
const toggle = useCallback(() => {
|
||||
onChange(!isSelected);
|
||||
});
|
||||
const { buttonProps } = useToggleButton(
|
||||
{ isSelected },
|
||||
{ toggle },
|
||||
buttonRef
|
||||
);
|
||||
|
||||
return (
|
||||
<Field
|
||||
className={classNames(
|
||||
styles.toggle,
|
||||
{ [styles.on]: isSelected },
|
||||
className
|
||||
)}
|
||||
>
|
||||
<button
|
||||
{...buttonProps}
|
||||
ref={buttonRef}
|
||||
id={id}
|
||||
className={classNames(styles.button, {
|
||||
[styles.isPressed]: isSelected,
|
||||
})}
|
||||
>
|
||||
<div className={styles.ball} />
|
||||
</button>
|
||||
<label className={styles.label} htmlFor={id}>
|
||||
{label}
|
||||
</label>
|
||||
</Field>
|
||||
);
|
||||
}
|
||||
46
src/input/Toggle.module.css
Normal file
46
src/input/Toggle.module.css
Normal file
@@ -0,0 +1,46 @@
|
||||
.toggle {
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.button {
|
||||
position: relative;
|
||||
padding: 0;
|
||||
transition: background-color 0.2s ease-out 0.1s;
|
||||
width: 44px;
|
||||
height: 24px;
|
||||
border: none;
|
||||
border-radius: 21px;
|
||||
background-color: #6f7882;
|
||||
cursor: pointer;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.ball {
|
||||
transition: left 0.15s ease-out 0.1s;
|
||||
position: absolute;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 21px;
|
||||
background-color: #15191e;
|
||||
left: 2px;
|
||||
top: 2px;
|
||||
}
|
||||
|
||||
.label {
|
||||
padding: 10px 8px;
|
||||
line-height: 24px;
|
||||
color: #6f7882;
|
||||
}
|
||||
|
||||
.toggle.on .button {
|
||||
background-color: #0dbd8b;
|
||||
}
|
||||
|
||||
.toggle.on .ball {
|
||||
left: 22px;
|
||||
}
|
||||
|
||||
.toggle.on .label {
|
||||
color: #ffffff;
|
||||
}
|
||||
@@ -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),
|
||||
|
||||
@@ -76,7 +76,7 @@ export function isLocalRoomId(roomId) {
|
||||
return parts[1] === defaultHomeserverHost;
|
||||
}
|
||||
|
||||
export async function createRoom(client, name) {
|
||||
export async function createRoom(client, name, isPtt = false) {
|
||||
const { room_id, room_alias } = await client.createRoom({
|
||||
visibility: "private",
|
||||
preset: "public_chat",
|
||||
@@ -107,9 +107,12 @@ export async function createRoom(client, name) {
|
||||
},
|
||||
});
|
||||
|
||||
console.log({ isPtt });
|
||||
|
||||
await client.createGroupCall(
|
||||
room_id,
|
||||
GroupCallType.Video,
|
||||
isPtt ? GroupCallType.Voice : GroupCallType.Video,
|
||||
isPtt,
|
||||
GroupCallIntent.Prompt
|
||||
);
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
77
src/room/AudioPreview.jsx
Normal file
77
src/room/AudioPreview.jsx
Normal 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 from "react";
|
||||
import styles from "./AudioPreview.module.css";
|
||||
import { GroupCallState } from "matrix-js-sdk/src/webrtc/groupCall";
|
||||
import { SelectInput } from "../input/SelectInput";
|
||||
import { Item } from "@react-stately/collections";
|
||||
import { Body } from "../typography/Typography";
|
||||
|
||||
export function AudioPreview({
|
||||
state,
|
||||
roomName,
|
||||
audioInput,
|
||||
audioInputs,
|
||||
setAudioInput,
|
||||
audioOutput,
|
||||
audioOutputs,
|
||||
setAudioOutput,
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<h1>{`${roomName} - Radio Call`}</h1>
|
||||
<div className={styles.preview}>
|
||||
{state === GroupCallState.LocalCallFeedUninitialized && (
|
||||
<Body fontWeight="semiBold" className={styles.microphonePermissions}>
|
||||
Microphone permissions needed to join the call.
|
||||
</Body>
|
||||
)}
|
||||
{state === GroupCallState.InitializingLocalCallFeed && (
|
||||
<Body fontWeight="semiBold" className={styles.microphonePermissions}>
|
||||
Accept microphone permissions to join the call.
|
||||
</Body>
|
||||
)}
|
||||
{state === GroupCallState.LocalCallFeedInitialized && (
|
||||
<>
|
||||
<SelectInput
|
||||
label="Microphone"
|
||||
selectedKey={audioInput}
|
||||
onSelectionChange={setAudioInput}
|
||||
className={styles.inputField}
|
||||
>
|
||||
{audioInputs.map(({ deviceId, label }) => (
|
||||
<Item key={deviceId}>{label}</Item>
|
||||
))}
|
||||
</SelectInput>
|
||||
{audioOutputs.length > 0 && (
|
||||
<SelectInput
|
||||
label="Speaker"
|
||||
selectedKey={audioOutput}
|
||||
onSelectionChange={setAudioOutput}
|
||||
className={styles.inputField}
|
||||
>
|
||||
{audioOutputs.map(({ deviceId, label }) => (
|
||||
<Item key={deviceId}>{label}</Item>
|
||||
))}
|
||||
</SelectInput>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
27
src/room/AudioPreview.module.css
Normal file
27
src/room/AudioPreview.module.css
Normal file
@@ -0,0 +1,27 @@
|
||||
.preview {
|
||||
margin: 20px 0;
|
||||
padding: 24px 20px;
|
||||
border-radius: 8px;
|
||||
width: calc(100% - 40px);
|
||||
max-width: 414px;
|
||||
}
|
||||
|
||||
.inputField {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.inputField:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.microphonePermissions {
|
||||
margin: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (min-width: 800px) {
|
||||
.preview {
|
||||
margin-top: 40px;
|
||||
background-color: #21262c;
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
@@ -5,6 +21,7 @@ import { useGroupCall } from "./useGroupCall";
|
||||
import { ErrorView, FullScreenView } from "../FullScreenView";
|
||||
import { LobbyView } from "./LobbyView";
|
||||
import { InCallView } from "./InCallView";
|
||||
import { PTTCallView } from "./PTTCallView";
|
||||
import { CallEndedView } from "./CallEndedView";
|
||||
import { useSentryGroupCallHandler } from "./useSentryGroupCallHandler";
|
||||
import { useLocationNavigation } from "../useLocationNavigation";
|
||||
@@ -47,6 +64,7 @@ export function GroupCallView({
|
||||
localScreenshareFeed,
|
||||
screenshareFeeds,
|
||||
hasLocalParticipant,
|
||||
participants,
|
||||
} = useGroupCall(groupCall);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -72,27 +90,43 @@ export function GroupCallView({
|
||||
if (error) {
|
||||
return <ErrorView error={error} />;
|
||||
} else if (state === GroupCallState.Entered) {
|
||||
return (
|
||||
<InCallView
|
||||
groupCall={groupCall}
|
||||
client={client}
|
||||
roomName={groupCall.room.name}
|
||||
microphoneMuted={microphoneMuted}
|
||||
localVideoMuted={localVideoMuted}
|
||||
toggleLocalVideoMuted={toggleLocalVideoMuted}
|
||||
toggleMicrophoneMuted={toggleMicrophoneMuted}
|
||||
userMediaFeeds={userMediaFeeds}
|
||||
activeSpeaker={activeSpeaker}
|
||||
onLeave={onLeave}
|
||||
toggleScreensharing={toggleScreensharing}
|
||||
isScreensharing={isScreensharing}
|
||||
localScreenshareFeed={localScreenshareFeed}
|
||||
screenshareFeeds={screenshareFeeds}
|
||||
setShowInspector={onChangeShowInspector}
|
||||
showInspector={showInspector}
|
||||
roomId={roomId}
|
||||
/>
|
||||
);
|
||||
if (groupCall.isPtt) {
|
||||
return (
|
||||
<PTTCallView
|
||||
client={client}
|
||||
roomId={roomId}
|
||||
roomName={groupCall.room.name}
|
||||
groupCall={groupCall}
|
||||
participants={participants}
|
||||
userMediaFeeds={userMediaFeeds}
|
||||
onLeave={onLeave}
|
||||
setShowInspector={onChangeShowInspector}
|
||||
showInspector={showInspector}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<InCallView
|
||||
groupCall={groupCall}
|
||||
client={client}
|
||||
roomName={groupCall.room.name}
|
||||
microphoneMuted={microphoneMuted}
|
||||
localVideoMuted={localVideoMuted}
|
||||
toggleLocalVideoMuted={toggleLocalVideoMuted}
|
||||
toggleMicrophoneMuted={toggleMicrophoneMuted}
|
||||
userMediaFeeds={userMediaFeeds}
|
||||
activeSpeaker={activeSpeaker}
|
||||
onLeave={onLeave}
|
||||
toggleScreensharing={toggleScreensharing}
|
||||
isScreensharing={isScreensharing}
|
||||
localScreenshareFeed={localScreenshareFeed}
|
||||
screenshareFeeds={screenshareFeeds}
|
||||
setShowInspector={onChangeShowInspector}
|
||||
showInspector={showInspector}
|
||||
roomId={roomId}
|
||||
/>
|
||||
);
|
||||
}
|
||||
} else if (state === GroupCallState.Entering) {
|
||||
return (
|
||||
<FullScreenView>
|
||||
@@ -105,6 +139,7 @@ export function GroupCallView({
|
||||
return (
|
||||
<LobbyView
|
||||
client={client}
|
||||
groupCall={groupCall}
|
||||
hasLocalParticipant={hasLocalParticipant}
|
||||
roomName={groupCall.room.name}
|
||||
state={state}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -1,23 +1,36 @@
|
||||
/*
|
||||
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, MicButton, VideoButton } from "../button";
|
||||
import { Button, CopyButton } from "../button";
|
||||
import { Header, LeftNav, RightNav, RoomHeaderInfo } from "../Header";
|
||||
import { GroupCallState } from "matrix-js-sdk/src/webrtc/groupCall";
|
||||
import { useCallFeed } from "../video-grid/useCallFeed";
|
||||
import { useMediaStream } from "../video-grid/useMediaStream";
|
||||
import { getRoomUrl } from "../matrix-utils";
|
||||
import { OverflowMenu } from "./OverflowMenu";
|
||||
import { UserMenuContainer } from "../UserMenuContainer";
|
||||
import { Body, Link } from "../typography/Typography";
|
||||
import { Avatar } from "../Avatar";
|
||||
import { useProfile } from "../profile/useProfile";
|
||||
import useMeasure from "react-use-measure";
|
||||
import { ResizeObserver } from "@juggle/resize-observer";
|
||||
import { useLocationNavigation } from "../useLocationNavigation";
|
||||
import { useMediaHandler } from "../settings/useMediaHandler";
|
||||
import { VideoPreview } from "./VideoPreview";
|
||||
import { AudioPreview } from "./AudioPreview";
|
||||
|
||||
export function LobbyView({
|
||||
client,
|
||||
groupCall,
|
||||
roomName,
|
||||
state,
|
||||
onInitLocalCallFeed,
|
||||
@@ -32,11 +45,14 @@ export function LobbyView({
|
||||
roomId,
|
||||
}) {
|
||||
const { stream } = useCallFeed(localCallFeed);
|
||||
const { audioOutput } = useMediaHandler();
|
||||
const videoRef = useMediaStream(stream, audioOutput, true);
|
||||
const { displayName, avatarUrl } = useProfile(client);
|
||||
const [previewRef, previewBounds] = useMeasure({ polyfill: ResizeObserver });
|
||||
const avatarSize = (previewBounds.height - 66) / 2;
|
||||
const {
|
||||
audioInput,
|
||||
audioInputs,
|
||||
setAudioInput,
|
||||
audioOutput,
|
||||
audioOutputs,
|
||||
setAudioOutput,
|
||||
} = useMediaHandler();
|
||||
|
||||
useEffect(() => {
|
||||
onInitLocalCallFeed();
|
||||
@@ -64,53 +80,31 @@ export function LobbyView({
|
||||
</Header>
|
||||
<div className={styles.joinRoom}>
|
||||
<div className={styles.joinRoomContent}>
|
||||
<div className={styles.preview} ref={previewRef}>
|
||||
<video ref={videoRef} muted playsInline disablePictureInPicture />
|
||||
{state === GroupCallState.LocalCallFeedUninitialized && (
|
||||
<Body fontWeight="semiBold" className={styles.webcamPermissions}>
|
||||
Webcam/microphone permissions needed to join the call.
|
||||
</Body>
|
||||
)}
|
||||
{state === GroupCallState.InitializingLocalCallFeed && (
|
||||
<Body fontWeight="semiBold" className={styles.webcamPermissions}>
|
||||
Accept webcam/microphone permissions to join the call.
|
||||
</Body>
|
||||
)}
|
||||
{state === GroupCallState.LocalCallFeedInitialized && (
|
||||
<>
|
||||
{localVideoMuted && (
|
||||
<div className={styles.avatarContainer}>
|
||||
<Avatar
|
||||
style={{
|
||||
width: avatarSize,
|
||||
height: avatarSize,
|
||||
borderRadius: avatarSize,
|
||||
fontSize: Math.round(avatarSize / 2),
|
||||
}}
|
||||
src={avatarUrl}
|
||||
fallback={displayName.slice(0, 1).toUpperCase()}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className={styles.previewButtons}>
|
||||
<MicButton
|
||||
muted={microphoneMuted}
|
||||
onPress={toggleMicrophoneMuted}
|
||||
/>
|
||||
<VideoButton
|
||||
muted={localVideoMuted}
|
||||
onPress={toggleLocalVideoMuted}
|
||||
/>
|
||||
<OverflowMenu
|
||||
roomId={roomId}
|
||||
setShowInspector={setShowInspector}
|
||||
showInspector={showInspector}
|
||||
client={client}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{groupCall.isPtt ? (
|
||||
<AudioPreview
|
||||
roomName={roomName}
|
||||
state={state}
|
||||
audioInput={audioInput}
|
||||
audioInputs={audioInputs}
|
||||
setAudioInput={setAudioInput}
|
||||
audioOutput={audioOutput}
|
||||
audioOutputs={audioOutputs}
|
||||
setAudioOutput={setAudioOutput}
|
||||
/>
|
||||
) : (
|
||||
<VideoPreview
|
||||
state={state}
|
||||
client={client}
|
||||
microphoneMuted={microphoneMuted}
|
||||
localVideoMuted={localVideoMuted}
|
||||
toggleLocalVideoMuted={toggleLocalVideoMuted}
|
||||
toggleMicrophoneMuted={toggleMicrophoneMuted}
|
||||
setShowInspector={setShowInspector}
|
||||
showInspector={showInspector}
|
||||
stream={stream}
|
||||
audioOutput={audioOutput}
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
ref={joinCallButtonRef}
|
||||
className={styles.copyButton}
|
||||
|
||||
@@ -46,58 +46,6 @@ limitations under the License.
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
.preview {
|
||||
position: relative;
|
||||
min-height: 280px;
|
||||
height: 50vh;
|
||||
border-radius: 24px;
|
||||
overflow: hidden;
|
||||
background-color: var(--bgColor3);
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.preview video {
|
||||
width: calc(100% + 1px);
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
background-color: black;
|
||||
/* transform scale doesn't perfectly match width, so make -1.01 border issues */
|
||||
transform: scaleX(-1.01);
|
||||
}
|
||||
|
||||
.avatarContainer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 66px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: var(--bgColor3);
|
||||
}
|
||||
|
||||
.webcamPermissions {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.previewButtons {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 66px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: rgba(23, 25, 28, 0.9);
|
||||
}
|
||||
|
||||
.joinCallButton {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
@@ -118,17 +66,3 @@ limitations under the License.
|
||||
.copyButton:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.previewButtons > * {
|
||||
margin-right: 30px;
|
||||
}
|
||||
|
||||
.previewButtons > :last-child {
|
||||
margin-right: 0px;
|
||||
}
|
||||
|
||||
@media (min-width: 800px) {
|
||||
.preview {
|
||||
margin-top: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
25
src/room/PTTButton.module.css
Normal file
25
src/room/PTTButton.module.css
Normal file
@@ -0,0 +1,25 @@
|
||||
.pttButton {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
max-height: 232px;
|
||||
max-width: 232px;
|
||||
border-radius: 116px;
|
||||
color: ##fff;
|
||||
border: 6px solid #0dbd8b;
|
||||
background-color: #21262c;
|
||||
position: relative;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.talking {
|
||||
background-color: #0dbd8b;
|
||||
box-shadow: 0px 0px 0px 17px rgba(13, 189, 139, 0.2),
|
||||
0px 0px 0px 34px rgba(13, 189, 139, 0.2);
|
||||
}
|
||||
|
||||
.error {
|
||||
background-color: #ff5b55;
|
||||
border-color: #ff5b55;
|
||||
box-shadow: 0px 0px 0px 17px rgba(255, 91, 85, 0.2),
|
||||
0px 0px 0px 34px rgba(255, 91, 85, 0.2);
|
||||
}
|
||||
164
src/room/PTTButton.tsx
Normal file
164
src/room/PTTButton.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
112
src/room/PTTCallView.module.css
Normal file
112
src/room/PTTCallView.module.css
Normal file
@@ -0,0 +1,112 @@
|
||||
.pttCallView {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
min-height: 100%;
|
||||
position: fixed;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (hover: none) {
|
||||
.pttCallView {
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
.center {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.participants {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.participants > p {
|
||||
color: #a9b2bc;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.facepile {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.talkingInfo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
height: 88px;
|
||||
}
|
||||
|
||||
.speakerIcon {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.pttButtonContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.actionTip {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
height: 64px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.footer > * {
|
||||
margin-right: 30px;
|
||||
}
|
||||
|
||||
.footer > :last-child {
|
||||
margin-right: 0px;
|
||||
}
|
||||
|
||||
@media (min-width: 800px) {
|
||||
.participants {
|
||||
margin-bottom: 67px;
|
||||
}
|
||||
|
||||
.talkingInfo {
|
||||
margin-bottom: 38px;
|
||||
}
|
||||
|
||||
.center {
|
||||
margin-top: 48px;
|
||||
}
|
||||
|
||||
.actionTip {
|
||||
margin-top: 42px;
|
||||
margin-bottom: 45px;
|
||||
}
|
||||
|
||||
.pttButtonContainer {
|
||||
flex: 0;
|
||||
margin-bottom: 0;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.footer {
|
||||
flex: auto;
|
||||
order: 4;
|
||||
}
|
||||
}
|
||||
248
src/room/PTTCallView.tsx
Normal file
248
src/room/PTTCallView.tsx
Normal file
@@ -0,0 +1,248 @@
|
||||
/*
|
||||
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";
|
||||
import { HangupButton, InviteButton, SettingsButton } from "../button";
|
||||
import { Header, LeftNav, RightNav, RoomSetupHeaderInfo } from "../Header";
|
||||
import styles from "./PTTCallView.module.css";
|
||||
import { Facepile } from "../Facepile";
|
||||
import { PTTButton } from "./PTTButton";
|
||||
import { PTTFeed } from "./PTTFeed";
|
||||
import { useMediaHandler } from "../settings/useMediaHandler";
|
||||
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";
|
||||
|
||||
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,
|
||||
groupCall,
|
||||
participants,
|
||||
userMediaFeeds,
|
||||
onLeave,
|
||||
setShowInspector,
|
||||
showInspector,
|
||||
}) => {
|
||||
const { modalState: inviteModalState, modalProps: inviteModalProps } =
|
||||
useModalTriggerState();
|
||||
const { modalState: settingsModalState, modalProps: settingsModalProps } =
|
||||
useModalTriggerState();
|
||||
const [containerRef, bounds] = useMeasure({ polyfill: ResizeObserver });
|
||||
const facepileSize = bounds.width < 800 ? "sm" : "md";
|
||||
const pttButtonSize = 232;
|
||||
const pttBorderWidth = 6;
|
||||
|
||||
const { audioOutput } = useMediaHandler();
|
||||
|
||||
const {
|
||||
startTalkingLocalRef,
|
||||
startTalkingRemoteRef,
|
||||
blockedRef,
|
||||
endTalkingRef,
|
||||
playClip,
|
||||
} = usePTTSounds();
|
||||
|
||||
const {
|
||||
pttButtonHeld,
|
||||
isAdmin,
|
||||
talkOverEnabled,
|
||||
setTalkOverEnabled,
|
||||
activeSpeakerUserId,
|
||||
startTalking,
|
||||
stopTalking,
|
||||
transmitBlocked,
|
||||
} = usePTT(client, groupCall, userMediaFeeds, playClip);
|
||||
|
||||
const showTalkOverError = pttButtonHeld && transmitBlocked;
|
||||
|
||||
const activeSpeakerIsLocalUser =
|
||||
activeSpeakerUserId && client.getUserId() === activeSpeakerUserId;
|
||||
const activeSpeakerUser = activeSpeakerUserId
|
||||
? client.getUser(activeSpeakerUserId)
|
||||
: null;
|
||||
const activeSpeakerAvatarUrl = activeSpeakerUser
|
||||
? getAvatarUrl(
|
||||
client,
|
||||
activeSpeakerUser.avatarUrl,
|
||||
pttButtonSize - pttBorderWidth * 2
|
||||
)
|
||||
: null;
|
||||
const activeSpeakerDisplayName = activeSpeakerUser
|
||||
? activeSpeakerUser.displayName
|
||||
: "";
|
||||
|
||||
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} />
|
||||
</LeftNav>
|
||||
<RightNav />
|
||||
</Header>
|
||||
<div className={styles.center}>
|
||||
<div className={styles.participants}>
|
||||
<p>{`${participants.length} ${
|
||||
participants.length > 1 ? "people" : "person"
|
||||
} connected`}</p>
|
||||
<Facepile
|
||||
size={facepileSize}
|
||||
max={8}
|
||||
className={styles.facepile}
|
||||
client={client}
|
||||
participants={participants}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.footer}>
|
||||
<SettingsButton onPress={() => settingsModalState.open()} />
|
||||
<HangupButton onPress={onLeave} />
|
||||
<InviteButton onPress={() => inviteModalState.open()} />
|
||||
</div>
|
||||
|
||||
<div className={styles.pttButtonContainer}>
|
||||
{activeSpeakerUserId ? (
|
||||
<div className={styles.talkingInfo}>
|
||||
<h2>
|
||||
{!activeSpeakerIsLocalUser && (
|
||||
<AudioIcon className={styles.speakerIcon} />
|
||||
)}
|
||||
{activeSpeakerIsLocalUser
|
||||
? "Talking..."
|
||||
: `${activeSpeakerDisplayName} is talking...`}
|
||||
</h2>
|
||||
<Timer value={activeSpeakerUserId} />
|
||||
</div>
|
||||
) : (
|
||||
<div className={styles.talkingInfo} />
|
||||
)}
|
||||
<PTTButton
|
||||
showTalkOverError={showTalkOverError}
|
||||
activeSpeakerUserId={activeSpeakerUserId}
|
||||
activeSpeakerDisplayName={activeSpeakerDisplayName}
|
||||
activeSpeakerAvatarUrl={activeSpeakerAvatarUrl}
|
||||
activeSpeakerIsLocalUser={activeSpeakerIsLocalUser}
|
||||
size={pttButtonSize}
|
||||
startTalking={startTalking}
|
||||
stopTalking={stopTalking}
|
||||
/>
|
||||
<p className={styles.actionTip}>
|
||||
{getPromptText(
|
||||
showTalkOverError,
|
||||
pttButtonHeld,
|
||||
activeSpeakerIsLocalUser,
|
||||
talkOverEnabled,
|
||||
activeSpeakerUserId,
|
||||
activeSpeakerDisplayName
|
||||
)}
|
||||
</p>
|
||||
{userMediaFeeds.map((callFeed) => (
|
||||
<PTTFeed
|
||||
key={callFeed.userId}
|
||||
callFeed={callFeed}
|
||||
audioOutputDevice={audioOutput}
|
||||
/>
|
||||
))}
|
||||
{isAdmin && (
|
||||
<Toggle
|
||||
isSelected={talkOverEnabled}
|
||||
onChange={setTalkOverEnabled}
|
||||
label="Talk over speaker"
|
||||
id="talkOverEnabled"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{settingsModalState.isOpen && (
|
||||
<SettingsModal
|
||||
{...settingsModalProps}
|
||||
setShowInspector={setShowInspector}
|
||||
showInspector={showInspector}
|
||||
/>
|
||||
)}
|
||||
{inviteModalState.isOpen && (
|
||||
<InviteModal roomId={roomId} {...inviteModalProps} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
26
src/room/PTTFeed.jsx
Normal file
26
src/room/PTTFeed.jsx
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
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";
|
||||
import styles from "./PTTFeed.module.css";
|
||||
|
||||
export function PTTFeed({ callFeed, audioOutputDevice }) {
|
||||
const { isLocal, stream } = useCallFeed(callFeed);
|
||||
const mediaRef = useMediaStream(stream, audioOutputDevice, isLocal);
|
||||
return <audio ref={mediaRef} className={styles.audioFeed} playsInline />;
|
||||
}
|
||||
3
src/room/PTTFeed.module.css
Normal file
3
src/room/PTTFeed.module.css
Normal file
@@ -0,0 +1,3 @@
|
||||
.audioFeed {
|
||||
display: none;
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
53
src/room/Timer.jsx
Normal file
53
src/room/Timer.jsx
Normal file
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
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) {
|
||||
return value < 10 ? "0" + value : value;
|
||||
}
|
||||
|
||||
function formatTime(msElapsed) {
|
||||
const secondsElapsed = msElapsed / 1000;
|
||||
const hours = Math.floor(secondsElapsed / 3600);
|
||||
const minutes = Math.floor(secondsElapsed / 60) - hours * 60;
|
||||
const seconds = Math.floor(secondsElapsed - hours * 3600 - minutes * 60);
|
||||
return `${leftPad(hours)}:${leftPad(minutes)}:${leftPad(seconds)}`;
|
||||
}
|
||||
|
||||
export function Timer({ value }) {
|
||||
const [timestamp, setTimestamp] = useState();
|
||||
|
||||
useEffect(() => {
|
||||
const startTimeMs = performance.now();
|
||||
|
||||
let animationFrame;
|
||||
|
||||
function onUpdate(curTimeMs) {
|
||||
const msElapsed = curTimeMs - startTimeMs;
|
||||
setTimestamp(formatTime(msElapsed));
|
||||
animationFrame = requestAnimationFrame(onUpdate);
|
||||
}
|
||||
|
||||
onUpdate(startTimeMs);
|
||||
|
||||
return () => {
|
||||
cancelAnimationFrame(animationFrame);
|
||||
};
|
||||
}, [value]);
|
||||
|
||||
return <p>{timestamp}</p>;
|
||||
}
|
||||
96
src/room/VideoPreview.jsx
Normal file
96
src/room/VideoPreview.jsx
Normal file
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
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";
|
||||
import { OverflowMenu } from "./OverflowMenu";
|
||||
import { Avatar } from "../Avatar";
|
||||
import { useProfile } from "../profile/useProfile";
|
||||
import useMeasure from "react-use-measure";
|
||||
import { ResizeObserver } from "@juggle/resize-observer";
|
||||
import { GroupCallState } from "matrix-js-sdk/src/webrtc/groupCall";
|
||||
import styles from "./VideoPreview.module.css";
|
||||
import { Body } from "../typography/Typography";
|
||||
|
||||
export function VideoPreview({
|
||||
client,
|
||||
state,
|
||||
roomId,
|
||||
microphoneMuted,
|
||||
localVideoMuted,
|
||||
toggleLocalVideoMuted,
|
||||
toggleMicrophoneMuted,
|
||||
setShowInspector,
|
||||
showInspector,
|
||||
audioOutput,
|
||||
stream,
|
||||
}) {
|
||||
const videoRef = useMediaStream(stream, audioOutput, true);
|
||||
const { displayName, avatarUrl } = useProfile(client);
|
||||
const [previewRef, previewBounds] = useMeasure({ polyfill: ResizeObserver });
|
||||
const avatarSize = (previewBounds.height - 66) / 2;
|
||||
|
||||
return (
|
||||
<div className={styles.preview} ref={previewRef}>
|
||||
<video ref={videoRef} muted playsInline disablePictureInPicture />
|
||||
{state === GroupCallState.LocalCallFeedUninitialized && (
|
||||
<Body fontWeight="semiBold" className={styles.webcamPermissions}>
|
||||
Webcam/microphone permissions needed to join the call.
|
||||
</Body>
|
||||
)}
|
||||
{state === GroupCallState.InitializingLocalCallFeed && (
|
||||
<Body fontWeight="semiBold" className={styles.webcamPermissions}>
|
||||
Accept webcam/microphone permissions to join the call.
|
||||
</Body>
|
||||
)}
|
||||
{state === GroupCallState.LocalCallFeedInitialized && (
|
||||
<>
|
||||
{localVideoMuted && (
|
||||
<div className={styles.avatarContainer}>
|
||||
<Avatar
|
||||
style={{
|
||||
width: avatarSize,
|
||||
height: avatarSize,
|
||||
borderRadius: avatarSize,
|
||||
fontSize: Math.round(avatarSize / 2),
|
||||
}}
|
||||
src={avatarUrl}
|
||||
fallback={displayName.slice(0, 1).toUpperCase()}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className={styles.previewButtons}>
|
||||
<MicButton
|
||||
muted={microphoneMuted}
|
||||
onPress={toggleMicrophoneMuted}
|
||||
/>
|
||||
<VideoButton
|
||||
muted={localVideoMuted}
|
||||
onPress={toggleLocalVideoMuted}
|
||||
/>
|
||||
<OverflowMenu
|
||||
roomId={roomId}
|
||||
setShowInspector={setShowInspector}
|
||||
showInspector={showInspector}
|
||||
client={client}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
65
src/room/VideoPreview.module.css
Normal file
65
src/room/VideoPreview.module.css
Normal file
@@ -0,0 +1,65 @@
|
||||
.preview {
|
||||
position: relative;
|
||||
min-height: 280px;
|
||||
height: 50vh;
|
||||
border-radius: 24px;
|
||||
overflow: hidden;
|
||||
background-color: var(--bgColor3);
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.preview video {
|
||||
width: calc(100% + 1px);
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
background-color: black;
|
||||
/* transform scale doesn't perfectly match width, so make -1.01 border issues */
|
||||
transform: scaleX(-1.01);
|
||||
}
|
||||
|
||||
.avatarContainer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 66px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: var(--bgColor3);
|
||||
}
|
||||
|
||||
.webcamPermissions {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.previewButtons {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 66px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: rgba(23, 25, 28, 0.9);
|
||||
}
|
||||
|
||||
.previewButtons > * {
|
||||
margin-right: 30px;
|
||||
}
|
||||
|
||||
.previewButtons > :last-child {
|
||||
margin-right: 0px;
|
||||
}
|
||||
|
||||
@media (min-width: 800px) {
|
||||
.preview {
|
||||
margin-top: 40px;
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
278
src/room/usePTT.ts
Normal file
278
src/room/usePTT.ts
Normal 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,
|
||||
};
|
||||
};
|
||||
@@ -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
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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 } };
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
19
src/sound/PTTClips.module.css
Normal file
19
src/sound/PTTClips.module.css
Normal 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
70
src/sound/PTTClips.tsx
Normal 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
BIN
src/sound/blocked.mp3
Normal file
Binary file not shown.
BIN
src/sound/blocked.ogg
Normal file
BIN
src/sound/blocked.ogg
Normal file
Binary file not shown.
BIN
src/sound/end_talk.mp3
Normal file
BIN
src/sound/end_talk.mp3
Normal file
Binary file not shown.
BIN
src/sound/end_talk.ogg
Normal file
BIN
src/sound/end_talk.ogg
Normal file
Binary file not shown.
BIN
src/sound/start_talk_local.mp3
Normal file
BIN
src/sound/start_talk_local.mp3
Normal file
Binary file not shown.
BIN
src/sound/start_talk_local.ogg
Normal file
BIN
src/sound/start_talk_local.ogg
Normal file
Binary file not shown.
BIN
src/sound/start_talk_remote.mp3
Normal file
BIN
src/sound/start_talk_remote.mp3
Normal file
Binary file not shown.
BIN
src/sound/start_talk_remote.ogg
Normal file
BIN
src/sound/start_talk_remote.ogg
Normal file
Binary file not shown.
77
src/sound/usePttSounds.ts
Normal file
77
src/sound/usePttSounds.ts
Normal 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,
|
||||
};
|
||||
};
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
6
src/useShouldShowPtt.js
Normal file
6
src/useShouldShowPtt.js
Normal file
@@ -0,0 +1,6 @@
|
||||
import { useLocation } from "react-router-dom";
|
||||
|
||||
export function useShouldShowPtt() {
|
||||
const { hash } = useLocation();
|
||||
return hash.startsWith("#ptt");
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
@@ -4,4 +4,3 @@
|
||||
flex: 1;
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
.videoMutedOverlay {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #21262C;
|
||||
background-color: #21262c;
|
||||
}
|
||||
|
||||
.presenterLabel {
|
||||
@@ -93,7 +93,7 @@
|
||||
top: 20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background-color: #17191C;
|
||||
background-color: #17191c;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
@@ -110,4 +110,4 @@
|
||||
width: 25%;
|
||||
max-width: 360px;
|
||||
border-radius: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user