Merge branch 'main' of http://192.168.1.163:3000/inmake/stream.graff.tech-new
This commit is contained in:
@@ -5,6 +5,7 @@
|
|||||||
"name": "client",
|
"name": "client",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tanstack/react-query": "^5.90.2",
|
"@tanstack/react-query": "^5.90.2",
|
||||||
|
"@uidotdev/usehooks": "^2.4.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"ky": "^1.11.0",
|
"ky": "^1.11.0",
|
||||||
"react": "^19.1.1",
|
"react": "^19.1.1",
|
||||||
@@ -237,6 +238,8 @@
|
|||||||
|
|
||||||
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.45.0", "", { "dependencies": { "@typescript-eslint/types": "8.45.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-qsaFBA3e09MIDAGFUrTk+dzqtfv1XPVz8t8d1f0ybTzrCY7BKiMC5cjrl1O/P7UmHsNyW90EYSkU/ZWpmXelag=="],
|
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.45.0", "", { "dependencies": { "@typescript-eslint/types": "8.45.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-qsaFBA3e09MIDAGFUrTk+dzqtfv1XPVz8t8d1f0ybTzrCY7BKiMC5cjrl1O/P7UmHsNyW90EYSkU/ZWpmXelag=="],
|
||||||
|
|
||||||
|
"@uidotdev/usehooks": ["@uidotdev/usehooks@2.4.1", "", { "peerDependencies": { "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-1I+RwWyS+kdv3Mv0Vmc+p0dPYH0DTRAo04HLyXReYBL9AeseDWUJyi4THuksBJcu9F0Pih69Ak150VDnqbVnXg=="],
|
||||||
|
|
||||||
"@vitejs/plugin-react-swc": ["@vitejs/plugin-react-swc@4.1.0", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-beta.35", "@swc/core": "^1.13.5" }, "peerDependencies": { "vite": "^4 || ^5 || ^6 || ^7" } }, "sha512-Ff690TUck0Anlh7wdIcnsVMhofeEVgm44Y4OYdeeEEPSKyZHzDI9gfVBvySEhDfXtBp8tLCbfsVKPWEMEjq8/g=="],
|
"@vitejs/plugin-react-swc": ["@vitejs/plugin-react-swc@4.1.0", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-beta.35", "@swc/core": "^1.13.5" }, "peerDependencies": { "vite": "^4 || ^5 || ^6 || ^7" } }, "sha512-Ff690TUck0Anlh7wdIcnsVMhofeEVgm44Y4OYdeeEEPSKyZHzDI9gfVBvySEhDfXtBp8tLCbfsVKPWEMEjq8/g=="],
|
||||||
|
|
||||||
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
|
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tanstack/react-query": "^5.90.2",
|
"@tanstack/react-query": "^5.90.2",
|
||||||
|
"@uidotdev/usehooks": "^2.4.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"ky": "^1.11.0",
|
"ky": "^1.11.0",
|
||||||
"react": "^19.1.1",
|
"react": "^19.1.1",
|
||||||
|
|||||||
@@ -4,69 +4,146 @@ import VideoFilledIcon from "../icons/VideoFilledIcon";
|
|||||||
import ModalWrapper from "../ModalWrapper";
|
import ModalWrapper from "../ModalWrapper";
|
||||||
import Button from "../ui/Button";
|
import Button from "../ui/Button";
|
||||||
import RangeInput from "../ui/RangeInput";
|
import RangeInput from "../ui/RangeInput";
|
||||||
|
import Select from "../ui/Select";
|
||||||
|
import MicrophoneFilledIcon from "../icons/MicrophoneFilledIcon";
|
||||||
|
import Switch from "../ui/Switch";
|
||||||
|
|
||||||
|
const microphoneOptions = ["Realtek HD Microphone"];
|
||||||
|
const speakerOptions = ["Realtek HD Audio"];
|
||||||
|
const cameraOptions = ["Realtek HD Camera"];
|
||||||
|
|
||||||
function SettingsModal() {
|
function SettingsModal() {
|
||||||
const [microphoneVolume, setMicrophoneVolume] = useState(50);
|
const [microphoneVolume, setMicrophoneVolume] = useState(50);
|
||||||
// const [speakerVolume, setSpeakerVolume] = useState(50);
|
const [speakerVolume, setSpeakerVolume] = useState(50);
|
||||||
|
|
||||||
|
const [selectedMicrophone, setSelectedMicrophone] = useState(
|
||||||
|
microphoneOptions[0]
|
||||||
|
);
|
||||||
|
const [selectedSpeaker, setSelectedSpeaker] = useState(speakerOptions[0]);
|
||||||
|
const [camera, setCamera] = useState(cameraOptions[0]);
|
||||||
|
|
||||||
|
const [mediaType, setMediaType] = useState<"sound" | "video">("sound");
|
||||||
|
|
||||||
|
const [participantsVideosHidden, setParticipantsVideosHidden] =
|
||||||
|
useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ModalWrapper title="Настройки">
|
<ModalWrapper title="Настройки">
|
||||||
<div className="2xl:space-y-[1.389vw] space-y-5">
|
<div className="2xl:space-y-[1.389vw] space-y-5 2xl:-mt-[1.389vw] -mt-5">
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<Button variant="menu" size="large" className="w-full">
|
<Button
|
||||||
|
variant="menu"
|
||||||
|
size="large"
|
||||||
|
className="w-full"
|
||||||
|
onClick={() => setMediaType("sound")}
|
||||||
|
>
|
||||||
<div className="flex 2xl:gap-[0.556vw] items-center">
|
<div className="flex 2xl:gap-[0.556vw] items-center">
|
||||||
<div className="2xl:size-[1.111vw] size-4 text-[#7B60F3]">
|
<div className="2xl:size-[1.111vw] size-4 text-[#7D7D7D]">
|
||||||
<SoundIcon />
|
<SoundIcon />
|
||||||
</div>
|
</div>
|
||||||
<p className="font-medium">Звук</p>
|
<p className="font-medium">Звук</p>
|
||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="menu" size="large" className="w-full">
|
<Button
|
||||||
|
variant="menu"
|
||||||
|
size="large"
|
||||||
|
className="w-full"
|
||||||
|
onClick={() => setMediaType("video")}
|
||||||
|
>
|
||||||
<div className="flex 2xl:gap-[0.556vw] items-center">
|
<div className="flex 2xl:gap-[0.556vw] items-center">
|
||||||
<div className="2xl:size-[1.111vw] size-4 text-[#7B60F3]">
|
<div className="2xl:size-[1.111vw] size-4 text-[#7D7D7D]">
|
||||||
<VideoFilledIcon />
|
<VideoFilledIcon />
|
||||||
</div>
|
</div>
|
||||||
<p className="font-medium">Видео</p>
|
<p className="font-medium">Видео</p>
|
||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="2xl:space-y-[1.667vw] space-y-6">
|
{mediaType === "sound" && (
|
||||||
<div className="2xl:space-y-[0.833vw] space-y-3">
|
<div className="2xl:space-y-[1.667vw] space-y-6">
|
||||||
<p className="title-s font-medium">Микрофон</p>
|
<div className="2xl:space-y-[0.833vw] space-y-3">
|
||||||
<div className="2xl:space-y-[1.111vw] space-y-4">
|
<p className="title-s font-medium">Микрофон</p>
|
||||||
<div className="flex 2xl:gap-[0.556vw]">
|
<div className="2xl:space-y-[1.111vw] space-y-4">
|
||||||
<div className="bg-[#F3F3F3] flex-1"></div>
|
<div className="flex 2xl:gap-[0.556vw]">
|
||||||
<Button variant="cta" size="large">
|
<Select
|
||||||
Проверить
|
className="flex-1"
|
||||||
</Button>
|
options={microphoneOptions}
|
||||||
</div>
|
defaultOption={selectedMicrophone}
|
||||||
<div className="flex items-center 2xl:gap-[0.833vw]">
|
onSelect={setSelectedMicrophone}
|
||||||
<div className="2xl:size-[1.111vw] size-4 text-[#7D7D7D]">
|
/>
|
||||||
<SoundIcon />
|
<Button variant="cta" size="large">
|
||||||
|
Проверить
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<RangeInput
|
<div className="flex items-center 2xl:gap-[0.833vw]">
|
||||||
value={microphoneVolume}
|
<div className="2xl:size-[1.111vw] size-4 text-[#7D7D7D]">
|
||||||
onChange={setMicrophoneVolume}
|
<MicrophoneFilledIcon />
|
||||||
/>
|
</div>
|
||||||
<p className="caption-xs font-medium text-[#7D7D7D] 2xl:w-[1.667vw] w-6">
|
<RangeInput
|
||||||
{microphoneVolume.toFixed(0)}%
|
value={microphoneVolume}
|
||||||
|
onChange={setMicrophoneVolume}
|
||||||
|
/>
|
||||||
|
<p className="caption-xs font-medium text-[#7D7D7D] 2xl:w-[1.667vw] w-6">
|
||||||
|
{microphoneVolume.toFixed(0)}%
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr className="bg-[#F6F6F6] 2xl:h-[0.069vw] h-px" />
|
||||||
|
<div className="2xl:space-y-[0.833vw] space-y-3">
|
||||||
|
<p className="title-s font-medium">Динамик</p>
|
||||||
|
<div className="2xl:space-y-[1.111vw] space-y-4">
|
||||||
|
<div className="flex 2xl:gap-[0.556vw]">
|
||||||
|
<Select
|
||||||
|
className="flex-1"
|
||||||
|
options={speakerOptions}
|
||||||
|
defaultOption={selectedSpeaker}
|
||||||
|
onSelect={setSelectedSpeaker}
|
||||||
|
/>
|
||||||
|
<Button variant="cta" size="large">
|
||||||
|
Проверить
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center 2xl:gap-[0.833vw]">
|
||||||
|
<div className="2xl:size-[1.111vw] size-4 text-[#7D7D7D]">
|
||||||
|
<SoundIcon />
|
||||||
|
</div>
|
||||||
|
<RangeInput
|
||||||
|
value={speakerVolume}
|
||||||
|
onChange={setSpeakerVolume}
|
||||||
|
/>
|
||||||
|
<p className="caption-xs font-medium text-[#7D7D7D] 2xl:w-[1.667vw] w-6">
|
||||||
|
{speakerVolume.toFixed(0)}%
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{mediaType === "video" && (
|
||||||
|
<div className="2xl:space-y-[1.667vw] space-y-6">
|
||||||
|
<div className="2xl:space-y-[0.556vw] space-y-2">
|
||||||
|
<p className="title-s font-medium">Камера</p>
|
||||||
|
<Select
|
||||||
|
options={cameraOptions}
|
||||||
|
onSelect={setCamera}
|
||||||
|
defaultOption={camera}
|
||||||
|
/>
|
||||||
|
<div className="2xl:w-[25vw] w-[360px] aspect-[360/202] bg-[#F6F6F6] 2xl:rounded-[1.111vw] rounded-2xl" />
|
||||||
|
</div>
|
||||||
|
<div className="flex items-start 2xl:gap-[0.556vw] gap-2">
|
||||||
|
<Switch
|
||||||
|
enabled={participantsVideosHidden}
|
||||||
|
onChange={setParticipantsVideosHidden}
|
||||||
|
/>
|
||||||
|
<div className="2xl:space-y-[0.278vw] space-y-1">
|
||||||
|
<p className="text-m">Скрыть видео участников</p>
|
||||||
|
<p className="caption-s text-[#7D7D7D]">
|
||||||
|
Это снизит нагрузку на сеть
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<hr className="bg-[#F6F6F6] 2xl:h-[0.069vw] h-px" />
|
)}
|
||||||
<div className="2xl:space-y-[0.833vw] space-y-3">
|
|
||||||
<p className="title-s font-medium">Динамик</p>
|
|
||||||
<div className="2xl:space-y-[1.111vw] space-y-4">
|
|
||||||
<div className="flex 2xl:gap-[0.556vw]">
|
|
||||||
<div className="bg-[#F3F3F3] flex-1"></div>
|
|
||||||
<Button variant="cta" size="large">
|
|
||||||
Проверить
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</ModalWrapper>
|
</ModalWrapper>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ function Button({
|
|||||||
onClick?.(e);
|
onClick?.(e);
|
||||||
}}
|
}}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"transition-all cursor-pointer disabled:!cursor-default flex outline-none gap-2 items-center justify-center font-medium disabled:bg-[#F6F6F6] disabled:!text-[#D6D6D6]",
|
"transition-all select-none cursor-pointer disabled:!cursor-default flex outline-none gap-2 items-center justify-center font-medium disabled:bg-[#F6F6F6] disabled:!text-[#D6D6D6]",
|
||||||
variant === "menu" &&
|
variant === "menu" &&
|
||||||
"text-[#7D7D7D] hover:bg-[#F3F3F3] active:bg-[#F3F1FD] active:text-[#7B60F3]",
|
"text-[#7D7D7D] hover:bg-[#F3F3F3] active:bg-[#F3F1FD] active:text-[#7B60F3]",
|
||||||
variant === "cta" &&
|
variant === "cta" &&
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ function RangeInput({
|
|||||||
style={{ width: `${((value - min) / (max - min)) * 100}%` }}
|
style={{ width: `${((value - min) / (max - min)) * 100}%` }}
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
className="2xl:size-[0.833vw] size-3 rounded-full bg-[#7B60F3] absolute top-1/2 -translate-y-1/2"
|
className="2xl:size-[0.833vw] size-3 rounded-full bg-[#7B60F3] absolute top-1/2 -translate-y-1/2 -translate-x-1/2"
|
||||||
style={{ left: `${((value - min) / (max - min)) * 100}%` }}
|
style={{ left: `${((value - min) / (max - min)) * 100}%` }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
import { useClickAway } from "@uidotdev/usehooks";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
import ChevronDownIcon from "../icons/ChevronDownIcon";
|
||||||
|
|
||||||
|
interface SelectProps {
|
||||||
|
options: string[];
|
||||||
|
onSelect: (value: string) => void;
|
||||||
|
className?: string;
|
||||||
|
defaultOption?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Select({
|
||||||
|
options,
|
||||||
|
onSelect,
|
||||||
|
className,
|
||||||
|
defaultOption: defaultValue = "",
|
||||||
|
}: SelectProps) {
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
const [selectedOption, setSelectedOption] = useState(defaultValue);
|
||||||
|
|
||||||
|
const ref = useClickAway<HTMLDivElement>(() => setIsOpen(false));
|
||||||
|
|
||||||
|
const dropDownRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => setSelectedOption(defaultValue), [defaultValue]);
|
||||||
|
|
||||||
|
useEffect(() => onSelect(selectedOption), [onSelect, selectedOption]);
|
||||||
|
|
||||||
|
function handleScroll() {
|
||||||
|
if (!dropDownRef.current) return;
|
||||||
|
dropDownRef.current.style.maxHeight = `calc(100vh - ${
|
||||||
|
dropDownRef.current?.getBoundingClientRect().y
|
||||||
|
}px - 0.278vw)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
handleScroll();
|
||||||
|
document.addEventListener("scroll", handleScroll);
|
||||||
|
|
||||||
|
return () => document.removeEventListener("scroll", handleScroll);
|
||||||
|
}, [isOpen]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={clsx(
|
||||||
|
"bg-[#F3F3F3] 2xl:p-[1.111vw] p-4 flex justify-between items-center 2xl:gap-[0.556vw] gap-2 relative select-none cursor-pointer",
|
||||||
|
isOpen
|
||||||
|
? "2xl:rounded-t-[1.111vw] rounded-t-2xl"
|
||||||
|
: "2xl:rounded-[1.111vw] rounded-2xl",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
onClick={() => setIsOpen(!isOpen)}
|
||||||
|
>
|
||||||
|
<p className="text-m">{selectedOption}</p>
|
||||||
|
<div
|
||||||
|
className={clsx(
|
||||||
|
"2xl:size-[1.111vw] size-4 text-[#7D7D7D]",
|
||||||
|
isOpen && "rotate-180"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<ChevronDownIcon />
|
||||||
|
</div>
|
||||||
|
{isOpen && (
|
||||||
|
<div
|
||||||
|
className="absolute left-0 top-full z-10 w-full 2xl:rounded-b-[1.111vw] rounded-b-2xlo overflow-hidden"
|
||||||
|
ref={dropDownRef}
|
||||||
|
>
|
||||||
|
{options.map((option) => (
|
||||||
|
<p
|
||||||
|
key={option}
|
||||||
|
className="bg-[#F3F3F3] 2xl:p-[1.111vw] p-4 text-m hover:bg-[#f0f0f0] transition-colors"
|
||||||
|
>
|
||||||
|
{option}
|
||||||
|
</p>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Select;
|
||||||
Reference in New Issue
Block a user