diff --git a/src/button/Button.tsx b/src/button/Button.tsx
index 83e3697c..97c6166f 100644
--- a/src/button/Button.tsx
+++ b/src/button/Button.tsx
@@ -30,6 +30,7 @@ import { ReactComponent as SettingsIcon } from "../icons/Settings.svg";
import { ReactComponent as AddUserIcon } from "../icons/AddUser.svg";
import { ReactComponent as ArrowDownIcon } from "../icons/ArrowDown.svg";
import { TooltipTrigger } from "../Tooltip";
+import { ReactComponent as OverflowIcon } from "../icons/Overflow.svg";
export type ButtonVariant =
| "default"
@@ -237,3 +238,20 @@ export function InviteButton({
);
}
+
+export function OptionsButton({
+ className,
+ ...rest
+}: {
+ className?: string;
+ [index: string]: unknown;
+}) {
+ return (
+
+
+ {() => "Options"}
+
+ );
+}
diff --git a/src/video-grid/VideoTile.jsx b/src/video-grid/VideoTile.jsx
index 2dd41921..206f8a46 100644
--- a/src/video-grid/VideoTile.jsx
+++ b/src/video-grid/VideoTile.jsx
@@ -20,6 +20,7 @@ import classNames from "classnames";
import styles from "./VideoTile.module.css";
import { ReactComponent as MicMutedIcon } from "../icons/MicMuted.svg";
import { ReactComponent as VideoMutedIcon } from "../icons/VideoMuted.svg";
+import { OptionsButton } from "../button/Button";
export const VideoTile = forwardRef(
(
@@ -35,6 +36,8 @@ export const VideoTile = forwardRef(
name,
showName,
mediaRef,
+ onOptionsPress,
+ showOptions,
...rest
},
ref
@@ -62,13 +65,18 @@ export const VideoTile = forwardRef(
) : (
(showName || audioMuted || (videoMuted && !noVideo)) && (
-
+
{audioMuted && !(videoMuted && !noVideo) && }
{videoMuted && !noVideo && }
{showName && {name}}
)
)}
+ {showOptions && (
+
+
+
+ )}
);
diff --git a/src/video-grid/VideoTile.module.css b/src/video-grid/VideoTile.module.css
index eda83ca2..3bc7aa83 100644
--- a/src/video-grid/VideoTile.module.css
+++ b/src/video-grid/VideoTile.module.css
@@ -44,10 +44,8 @@
object-fit: contain;
}
-.memberName {
+.infoBubble {
position: absolute;
- bottom: 16px;
- left: 16px;
height: 24px;
padding: 0 8px;
color: white;
@@ -62,6 +60,21 @@
z-index: 1;
}
+.optionsButton {
+ right: 16px;
+ top: 16px;
+}
+
+.optionsButton button svg {
+ height: 20px;
+ width: 20px;
+}
+
+.memberName {
+ left: 16px;
+ bottom: 16px;
+}
+
.memberName > * {
margin-right: 6px;
}
diff --git a/src/video-grid/VideoTileContainer.jsx b/src/video-grid/VideoTileContainer.jsx
index 8fb4f803..23cfe536 100644
--- a/src/video-grid/VideoTileContainer.jsx
+++ b/src/video-grid/VideoTileContainer.jsx
@@ -20,6 +20,9 @@ import { useCallFeed } from "./useCallFeed";
import { useSpatialMediaStream } from "./useMediaStream";
import { useRoomMemberName } from "./useRoomMemberName";
import { VideoTile } from "./VideoTile";
+import { VideoTileSettingsModal } from "./VideoTileSettingsModal";
+import { useModalTriggerState } from "../Modal";
+
export function VideoTileContainer({
item,
@@ -37,6 +40,7 @@ export function VideoTileContainer({
isLocal,
audioMuted,
videoMuted,
+ localVolume,
noVideo,
speaking,
stream,
@@ -49,26 +53,37 @@ export function VideoTileContainer({
audioOutputDevice,
audioContext,
audioDestination,
- isLocal
+ isLocal,
+ localVolume
);
+ const { modalState: videoTileSettingsModalState, modalProps: videoTileSettingsModalProps } =
+ useModalTriggerState();
+ const onOptionsPress = () => {
+ videoTileSettingsModalState.open();
+ }
// Firefox doesn't respect the disablePictureInPicture attribute
// https://bugzilla.mozilla.org/show_bug.cgi?id=1611831
return (
-
+ <>
+
+ {videoTileSettingsModalState.isOpen &&
}
+ >
);
}
diff --git a/src/video-grid/VideoTileSettingsModal.module.css b/src/video-grid/VideoTileSettingsModal.module.css
new file mode 100644
index 00000000..c41020e9
--- /dev/null
+++ b/src/video-grid/VideoTileSettingsModal.module.css
@@ -0,0 +1,76 @@
+.content {
+ margin: 27px 34px;
+}
+
+.localVolumePercentage {
+ width: 40px;
+}
+
+.localVolumeSlider[type="range"] {
+ -ms-appearance: none;
+ -moz-appearance: none;
+ -webkit-appearance: none;
+ appearance: none;
+
+ cursor: pointer;
+ width: 100%;
+}
+
+.localVolumeSlider[type="range"]::-moz-range-track {
+ -ms-appearance: none;
+ -moz-appearance: none;
+ -webkit-appearance: none;
+ appearance: none;
+
+ height: 4px;
+}
+.localVolumeSlider[type="range"]::-ms-track {
+ -ms-appearance: none;
+ -moz-appearance: none;
+ -webkit-appearance: none;
+ appearance: none;
+
+ height: 4px;
+}
+.localVolumeSlider[type="range"]::-webkit-slider-runnable-track {
+ -ms-appearance: none;
+ -moz-appearance: none;
+ -webkit-appearance: none;
+ appearance: none;
+
+ height: 4px;
+}
+
+.localVolumeSlider[type="range"]::-moz-range-thumb {
+ -moz-appearance: none;
+ appearance: none;
+
+ height: 16px;
+ width: 16px;
+ margin-top: -6px;
+
+ border-radius: 100%;
+ background: var(--accent);
+}
+.localVolumeSlider[type="range"]::-ms-thumb {
+ -ms-appearance: none;
+ appearance: none;
+
+ height: 16px;
+ width: 16px;
+ margin-top: -6px;
+
+ border-radius: 100%;
+ background: var(--accent);
+}
+.localVolumeSlider[type="range"]::-webkit-slider-thumb {
+ -webkit-appearance: none;
+ appearance: none;
+
+ height: 16px;
+ width: 16px;
+ margin-top: -6px;
+
+ border-radius: 100%;
+ background: var(--accent);
+}
diff --git a/src/video-grid/VideoTileSettingsModal.tsx b/src/video-grid/VideoTileSettingsModal.tsx
new file mode 100644
index 00000000..a141f3b3
--- /dev/null
+++ b/src/video-grid/VideoTileSettingsModal.tsx
@@ -0,0 +1,73 @@
+/*
+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, { ChangeEvent, useState } from "react";
+import { CallFeed } from "matrix-js-sdk/src/webrtc/callFeed";
+
+import selectInputStyles from "../input/SelectInput.module.css";
+import { FieldRow } from "../input/Input";
+import { Modal } from "../Modal";
+import styles from "./VideoTileSettingsModal.module.css";
+
+interface LocalVolumeProps {
+ feed: CallFeed;
+}
+
+const LocalVolume: React.FC
= ({
+ feed,
+}: LocalVolumeProps) => {
+ const [localVolume, setLocalVolume] = useState(feed.getLocalVolume());
+
+ const onLocalVolumeChanged = (event: ChangeEvent) => {
+ const value: number = +event.target.value;
+ setLocalVolume(value);
+ feed.setLocalVolume(value);
+ };
+
+ return (
+ <>
+ Local Volume
+
+
+ {`${Math.round(localVolume * 100)}%`}
+
+
+
+ >
+ );
+};
+
+interface Props {
+ feed: CallFeed;
+}
+
+export const VideoTileSettingsModal = (props: Props) => {
+ return (
+
+
+
+
+
+ );
+};