refactor: audio UX and deafen

This commit is contained in:
Tristan Yang
2023-03-30 22:13:29 +08:00
parent 92a3569b44
commit 0d19b7adec
11 changed files with 141 additions and 55 deletions
+5 -4
View File
@@ -5,17 +5,18 @@ import JoinVoice from './JoinVoice';
import { useVoice } from '../../../common/component/Voice';
type Props = {
visible: boolean,
context?: "channel" | "dm",
id: number,
}
const Dashboard = ({ context = "channel", id }: Props) => {
const { joinVoice, joined, joining, voicingInfo } = useVoice({ id, context });
const Dashboard = ({ context = "channel", id, visible }: Props) => {
const { joinVoice, joined, joining, voicingInfo, setMute, setDeafen, leave } = useVoice({ id, context });
// const dispatch = useDispatch();
return <div className='absolute -left-full -translate-x-full -top-1 z-50 shadow rounded p-2 bg-white dark:bg-black px-2 py-4'>
{joined ? <VoiceManagement info={voicingInfo} /> : <JoinVoice join={joinVoice} joining={joining} />}
return <div className={`h-full flex-col gap-1 w-[226px] overflow-y-scroll p-2 shadow-[inset_1px_0px_0px_rgba(0,_0,_0,_0.1)] ${visible ? "flex" : "hidden"}`}>
{joined ? <VoiceManagement info={voicingInfo} setMute={setMute} setDeafen={setDeafen} leave={leave} /> : <JoinVoice join={joinVoice} joining={joining} />}
</div>;
};
+3 -3
View File
@@ -7,12 +7,12 @@ type Props = {
}
const JoinVoice = ({ joining, join }: Props) => {
if (joining) return <div>
Joining
if (joining) return <div className='w-full h-full flex-center p-1 text-sm text-gray-600 dark:text-gray-400'>
Connecting to voice channel...
</div>;
return (
<div className='w-full h-full flex-center p-4'>
<StyledButton className='mini' onClick={join}>Join</StyledButton>
<StyledButton className='w-full' onClick={join}>Start Audio Chat</StyledButton>
</div>
);
};
+45 -26
View File
@@ -3,15 +3,22 @@ import React from 'react';
import { VoicingInfo } from '../../../app/slices/voice';
import { useAppSelector } from '../../../app/store';
import Avatar from '../../../common/component/Avatar';
import IconHeadphone from '../../../assets/icons/headphone.svg';
import IconHeadphoneOff from '../../../assets/icons/headphone.off.svg';
import IconMic from '../../../assets/icons/mic.on.svg';
import IconMicOff from '../../../assets/icons/mic.off.svg';
import StyledButton from '../../../common/component/styled/Button';
import IconCallOff from '../../../assets/icons/call.off.svg';
// import User from '../../../common/component/User';
type Props = {
info: VoicingInfo | null
info: VoicingInfo | null,
setMute: (param: boolean) => void,
setDeafen: (param: boolean) => void,
leave: () => void
}
const VoiceManagement = ({ info }: Props) => {
const VoiceManagement = ({ info, setMute, setDeafen, leave }: Props) => {
const { userData, voicingMembers } = useAppSelector(store => {
return {
userData: store.users.byId,
@@ -19,39 +26,37 @@ const VoiceManagement = ({ info }: Props) => {
};
});
if (!info) return null;
const { context, id } = info;
const { deafen, muted } = info;
const nameClass = clsx(`text-sm text-gray-500 max-w-[190px] truncate font-semibold dark:text-white`);
const members = voicingMembers.ids;
const membersData = voicingMembers.byId;
return (
<div className='w-full h-full py-2'>
<ul className='flex flex-col gap-2'>
<div className='w-full h-full py-2 flex flex-col'>
<ul className='flex grow flex-col gap-2'>
{members.map((uid) => {
const curr = userData[uid];
if (!curr) return null;
const { muted, speakingVolume = 0 } = membersData[uid];
const { muted, deafen, speakingVolume = 0 } = membersData[uid];
const speaking = speakingVolume > 50;
return <li key={uid}>
<div className="flex items-center justify-between gap-6 ">
<div className="flex items-center gap-2 transition-opacity" style={{ opacity: `${speaking ? 0.4 : 1}` }}>
<div className="w-8 h-8 flex shrink-0">
<Avatar
width={32}
height={32}
className="w-full h-full rounded-full object-cover"
src={curr.avatar}
name={curr.name}
alt="avatar"
/>
</div>
<span className={nameClass} title={curr?.name}>
{curr?.name}
</span>
</div>
<div className="flex items-center">
{/* {muted ? <IconMicOff className="w-4" /> : <IconMic className="w-4" />} */}
{muted ? <IconMicOff className="w-4" /> : <IconMic className="w-4" />}
return <li key={uid} className="flex items-center justify-between gap-6 ">
<div className="flex items-center gap-2 transition-opacity" style={{ opacity: `${speaking ? 0.4 : 1}` }}>
<div className="w-8 h-8 flex shrink-0">
<Avatar
width={32}
height={32}
className="w-full h-full rounded-full object-cover"
src={curr.avatar}
name={curr.name}
alt="avatar"
/>
</div>
<span className={nameClass} title={curr?.name}>
{curr?.name}
</span>
</div>
<div className="flex items-center gap-2">
{deafen ? <IconHeadphoneOff className="w-4" /> : <IconHeadphone className="w-4" />}
{muted ? <IconMicOff className="w-4 fill-gray-500" /> : <IconMic className="w-4 fill-gray-500" />}
</div>
{/* <User uid={uid} interactive={false} /> */}
{/* {userData[uid]?.name} */}
@@ -59,6 +64,20 @@ const VoiceManagement = ({ info }: Props) => {
})}
</ul>
<div className="flex flex-col gap-2">
<ul className='flex justify-between'>
<li role={"button"} onClick={setDeafen.bind(null, !deafen)} className="py-2 px-3 rounded bg-gray-100 dark:bg-gray-900">
{deafen ? <IconHeadphoneOff className="fill-gray-700 dark:fill-gray-300" /> : <IconHeadphone className="fill-gray-700 dark:fill-gray-300" />}
</li>
<li role={"button"} onClick={setMute.bind(null, !muted)} className="py-2 px-3 rounded bg-gray-100 dark:bg-gray-900">
{muted ? <IconMicOff className="fill-gray-700 dark:fill-gray-300" /> : <IconMic className="fill-gray-700 dark:fill-gray-300" />}
</li>
</ul>
<StyledButton onClick={leave} className='bg-red-600 hover:!bg-red-700 text-center'>
<IconCallOff className="m-auto" />
</StyledButton>
</div>
</div>
);
};
+16 -8
View File
@@ -1,11 +1,13 @@
// import React from 'react';
// import Tippy from '@tippyjs/react';
import { useState } from 'react';
// import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import { updateChannelVisibleAside } from '../../../app/slices/channels';
import { useAppSelector } from '../../../app/store';
import IconHeadphone from '../../../assets/icons/headphone.svg';
import Tooltip from '../../../common/component/Tooltip';
import Dashboard from './Dashboard';
// import Dashboard from './Dashboard';
type Props = {
context?: "channel" | "dm"
@@ -13,18 +15,24 @@ type Props = {
}
const VoiceChat = ({ id, context = "channel" }: Props) => {
const [dashboardVisible, setDashboardVisible] = useState(false);
const { loginUser } = useAppSelector(store => { return { loginUser: store.authData.user }; });
const dispatch = useDispatch();
const { loginUser, contextData } = useAppSelector(store => { return { loginUser: store.authData.user, contextData: context == "channel" ? store.channels.byId[id] : store.users.byId[id] }; });
const { t } = useTranslation("chat");
const toggleDashboard = () => {
setDashboardVisible(prev => !prev);
if (context == "channel") {
dispatch(updateChannelVisibleAside({
id,
aside: contextData.visibleAside == "voice" ? null : "voice"
}));
}
// todo DM
};
if (!loginUser) return null;
const visible = contextData.visibleAside == "voice";
return (
<Tooltip disabled={dashboardVisible} tip={t("voice")} placement="left">
<Tooltip disabled={visible} tip={t("voice")} placement="left">
<li className={`relative`} >
<IconHeadphone className={dashboardVisible ? "fill-gray-600" : "fill-gray-500"} role="button" onClick={toggleDashboard} />
{dashboardVisible && <Dashboard id={id} context={context} />}
<IconHeadphone className={visible ? "fill-gray-600" : "fill-gray-500"} role="button" onClick={toggleDashboard} />
</li>
</Tooltip>
);