diff --git a/src/assets/icons/full_sound.svg b/src/assets/icons/full_sound.svg
new file mode 100644
index 0000000..147dc86
--- /dev/null
+++ b/src/assets/icons/full_sound.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/icon.tsx b/src/assets/icons/icon.tsx
index 1f7e112..c0761af 100644
--- a/src/assets/icons/icon.tsx
+++ b/src/assets/icons/icon.tsx
@@ -9,6 +9,10 @@ import ConfirmSvg from "./done.svg?react";
import StepLeftSvg from "./chevron_left.svg?react";
import StepRightSvg from "./navigate_next.svg?react";
import Settings from "./settings.svg?react";
+import NoSound from "./no_sound.svg?react";
+import FullSound from "./full_sound.svg?react";
+import Minus from "./minus.svg?react";
+import Plus from "./plus.svg?react";
import ChevronDown from "./chevron_down.svg?react";
import ChevronUp from "./chevron_up.svg?react";
import Headset from "./headset.svg?react";
@@ -35,6 +39,14 @@ export const StepRightIcon = () => ;
export const SettingsIcon = () => ;
+export const NoSoundIcon = () => ;
+
+export const FullSoundIcon = () => ;
+
+export const MinusIcon = () => ;
+
+export const PlusIcon = () => ;
+
export const ChevronDownIcon = () => ;
export const ChevronUpIcon = () => ;
diff --git a/src/assets/icons/minus.svg b/src/assets/icons/minus.svg
new file mode 100644
index 0000000..eacef25
--- /dev/null
+++ b/src/assets/icons/minus.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/no_sound.svg b/src/assets/icons/no_sound.svg
new file mode 100644
index 0000000..17b8f3a
--- /dev/null
+++ b/src/assets/icons/no_sound.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/plus.svg b/src/assets/icons/plus.svg
new file mode 100644
index 0000000..be10a07
--- /dev/null
+++ b/src/assets/icons/plus.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/src/components/production-line/exit-call-button.tsx b/src/components/production-line/exit-call-button.tsx
index 94b4370..0c3c548 100644
--- a/src/components/production-line/exit-call-button.tsx
+++ b/src/components/production-line/exit-call-button.tsx
@@ -6,6 +6,8 @@ const StyledBackBtn = styled(PrimaryButton)`
padding: 0;
margin: 0;
width: 4rem;
+ background: #32383b;
+ border: 0.2rem solid #6d6d6d;
`;
export const ExitCallButton = ({
diff --git a/src/components/production-line/production-line.tsx b/src/components/production-line/production-line.tsx
index afbb254..0f7d3f9 100644
--- a/src/components/production-line/production-line.tsx
+++ b/src/components/production-line/production-line.tsx
@@ -26,6 +26,7 @@ import { useCheckBadLineData } from "./use-check-bad-line-data.ts";
import { useAudioCue } from "./use-audio-cue.ts";
import { DisplayWarning } from "../display-box.tsx";
import { SettingsModal, Hotkeys } from "./settings-modal.tsx";
+import { VolumeSlider } from "../volume-slider/volume-slider.tsx";
import { CallState } from "../../global-state/types.ts";
import { ExitCallButton } from "./exit-call-button.tsx";
import { Modal } from "../modal/modal.tsx";
@@ -74,11 +75,11 @@ const FlexButtonWrapper = styled.div`
width: 50%;
padding: 0 1rem 2rem 1rem;
- :first-of-type {
+ &.first {
padding-left: 0;
}
- :last-of-type {
+ &.last {
padding-right: 0;
}
`;
@@ -145,11 +146,15 @@ export const ProductionLine = ({
muteHotkey: "m",
speakerHotkey: "n",
pressToTalkHotkey: "t",
+ increaseVolumeHotkey: "u",
+ decreaseVolumeHotkey: "d",
});
const [savedHotkeys, setSavedHotkeys] = useState({
muteHotkey: "m",
speakerHotkey: "n",
pressToTalkHotkey: "t",
+ increaseVolumeHotkey: "u",
+ decreaseVolumeHotkey: "d",
});
const {
joinProductionOptions,
@@ -333,18 +338,21 @@ export const ProductionLine = ({
}}
>
Controls
-
+
-
+
muteOutput()}>
{isOutputMuted ? : }
-
{inputAudioStream && inputAudioStream !== "no-device" && (
-
+
muteInput(!isInputMuted)}
@@ -401,6 +409,18 @@ export const ProductionLine = ({
Push to Talk
+
+
+ {savedHotkeys.increaseVolumeHotkey.toUpperCase()}:{" "}
+
+ Increase Volume
+
+
+
+ {savedHotkeys.decreaseVolumeHotkey.toUpperCase()}:{" "}
+
+ Decrease Volume
+
{isSettingsModalOpen && (
void;
onSave: () => void;
};
+
export const SettingsModal = ({
hotkeys,
lineName,
@@ -87,39 +90,39 @@ export const SettingsModal = ({
muteHotkey: "",
speakerHotkey: "",
pressToTalkHotkey: "",
+ increaseVolumeHotkey: "",
+ decreaseVolumeHotkey: "",
});
- const validateFields = (key: string, value: string) => {
+ const validateFields = (key: keyof Hotkeys, value: string) => {
const currentValues = {
...hotkeys,
[key]: value,
};
- const duplicates = Object.entries(currentValues).reduce(
- (acc, [field, val]) => {
- if (val && value && val === value && field !== key) {
- acc[key] = "This key is already in use.";
+ const newErrors = (
+ Object.keys(currentValues) as Array
+ ).reduce(
+ (acc, field) => {
+ const isDuplicate =
+ Object.values(currentValues).filter(
+ (val) => val && val === currentValues[field]
+ ).length > 1;
+
+ if (!currentValues[field]) {
+ acc[field] = "This field can not be empty.";
+ } else if (isDuplicate) {
+ acc[field] = "This key is already in use.";
+ } else {
+ acc[field] = "";
}
+
return acc;
},
- {} as { [key: string]: string }
+ {} as { [K in keyof Hotkeys]: string }
);
- setErrors((prevErrors) => ({
- ...prevErrors,
- muteHotkey:
- key === "muteHotkey"
- ? duplicates.muteHotkey || ""
- : prevErrors.muteHotkey,
- speakerHotkey:
- key === "speakerHotkey"
- ? duplicates.speakerHotkey || ""
- : prevErrors.speakerHotkey,
- pressToTalkHotkey:
- key === "pressToTalkHotkey"
- ? duplicates.pressToTalkHotkey || ""
- : prevErrors.pressToTalkHotkey,
- }));
+ setErrors(newErrors);
};
const handleInputChange = (key: keyof typeof hotkeys, value: string) => {
@@ -134,9 +137,13 @@ export const SettingsModal = ({
const handleSave = () => {
const hasErrors = Object.values(errors).some((error) => error !== "");
- if (!hasErrors) {
- onSave();
+ const hasEmptyFields = Object.values(hotkeys).some((value) => !value);
+
+ if (hasErrors || hasEmptyFields) {
+ return;
}
+
+ onSave();
};
const handleKeyDown = (e: React.KeyboardEvent, index: number) => {
@@ -237,6 +244,56 @@ export const SettingsModal = ({
/>
)}
+
+ Increase volume:
+ setInputRef(3, el)}
+ type="text"
+ value={hotkeys.increaseVolumeHotkey}
+ onChange={(e) =>
+ handleInputChange("increaseVolumeHotkey", e.target.value)
+ }
+ placeholder="Enter hotkey"
+ maxLength={1}
+ onKeyDown={(e) => handleKeyDown(e, 3)}
+ />
+ {errors.increaseVolumeHotkey && (
+
+ )}
+
+
+ Decrease volume:
+ setInputRef(4, el)}
+ type="text"
+ value={hotkeys.decreaseVolumeHotkey}
+ onChange={(e) =>
+ handleInputChange("decreaseVolumeHotkey", e.target.value)
+ }
+ placeholder="Enter hotkey"
+ maxLength={1}
+ onKeyDown={(e) => handleKeyDown(e, 4)}
+ />
+ {errors.decreaseVolumeHotkey && (
+
+ )}
+
Cancel
diff --git a/src/components/volume-slider/volume-slider.tsx b/src/components/volume-slider/volume-slider.tsx
new file mode 100644
index 0000000..d7dbf44
--- /dev/null
+++ b/src/components/volume-slider/volume-slider.tsx
@@ -0,0 +1,180 @@
+import styled from "@emotion/styled";
+import { FC, useState } from "react";
+import { useHotkeys } from "react-hotkeys-hook";
+import {
+ NoSoundIcon,
+ FullSoundIcon,
+ MinusIcon,
+ PlusIcon,
+} from "../../assets/icons/icon";
+import { isMobile } from "../../bowser";
+import { ActionButton } from "../landing-page/form-elements";
+
+const SliderWrapper = styled.div`
+ width: 100%;
+ margin: 2rem 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ position: relative;
+`;
+
+const VolumeContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+`;
+
+const IconWrapper = styled.div`
+ width: 5rem;
+ height: 5rem;
+ padding: 0.5rem;
+`;
+
+const SliderTrack = styled.div`
+ width: 80%;
+ height: 0.4rem;
+ background-color: #e0e0e0;
+ border-radius: 0.2rem;
+ position: relative;
+`;
+
+const SliderThumb = styled.div<{ position: number }>`
+ width: 1.5rem;
+ height: 1.5rem;
+ background-color: #59cbe8;
+ border-radius: 50%;
+ position: absolute;
+ top: -0.6rem;
+ left: ${({ position }) => `${position}%`};
+ transform: translateX(-50%);
+ cursor: pointer;
+`;
+
+const VolumeButton = styled(ActionButton)`
+ background-color: #32383b;
+ width: 7rem;
+ align-items: center;
+ height: 4.5rem;
+ padding: 1.5rem;
+ cursor: pointer;
+ margin-top: 1rem;
+ border: 0.2rem solid #6d6d6d;
+`;
+
+const VolumeButtonContainer = styled.div`
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ width: 100%;
+`;
+
+const VolumeWrapper = styled.div`
+ display: flex;
+ flex-direction: row;
+ width: 100%;
+ align-items: center;
+`;
+
+type TVolumeSliderProps = {
+ audioElements: HTMLAudioElement[];
+ increaseVolumeKey?: string;
+ decreaseVolumeKey?: string;
+};
+
+export const VolumeSlider: FC = ({
+ audioElements,
+ increaseVolumeKey,
+ decreaseVolumeKey,
+}) => {
+ const [value, setValue] = useState(0.75);
+
+ const handleInputChange = (e: React.ChangeEvent) => {
+ const newValue = parseFloat(e.target.value);
+ setValue(newValue);
+
+ audioElements.forEach((audioElement) => {
+ console.log("Setting volume to: ", newValue);
+ // eslint-disable-next-line no-param-reassign
+ audioElement.volume = newValue;
+ });
+ };
+
+ const thumbPosition = value * 100;
+
+ useHotkeys(increaseVolumeKey || "u", () => {
+ const newValue = Math.min(value + 0.05, 1);
+ setValue(newValue);
+
+ audioElements.forEach((audioElement) => {
+ // eslint-disable-next-line no-param-reassign
+ audioElement.volume = newValue;
+ });
+ });
+
+ useHotkeys(decreaseVolumeKey || "d", () => {
+ const newValue = Math.max(value - 0.05, 0);
+ setValue(newValue);
+
+ audioElements.forEach((audioElement) => {
+ // eslint-disable-next-line no-param-reassign
+ audioElement.volume = newValue;
+ });
+ });
+
+ const handleVolumeButtonClick = (type: "increase" | "decrease") => {
+ const newValue =
+ type === "increase"
+ ? Math.min(value + 0.05, 1)
+ : Math.max(value - 0.05, 0);
+ setValue(newValue);
+ // TODO: Fix for iOS
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {isMobile && (
+
+ handleVolumeButtonClick("decrease")}>
+
+
+ handleVolumeButtonClick("increase")}>
+
+
+
+ )}
+
+ );
+};