feat: voice fullscreen mode
This commit is contained in:
@@ -5,6 +5,7 @@ interface Params {
|
||||
data: any;
|
||||
operation: string;
|
||||
}
|
||||
const ignores = ["voice_fullscreen", "voice"];
|
||||
export default async function handler({ operation, data = {}, payload }: Params) {
|
||||
const table = window.CACHE["footprint"] as typeof localforage;;
|
||||
if (operation.startsWith("reset")) {
|
||||
@@ -71,14 +72,14 @@ export default async function handler({ operation, data = {}, payload }: Params)
|
||||
break;
|
||||
case "updateChannelVisibleAside":
|
||||
{
|
||||
if (payload.aside !== "voice") {
|
||||
if (!ignores.includes(payload.aside)) {
|
||||
await table?.setItem("channelAsides", data.channelAsides);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "updateDMVisibleAside":
|
||||
{
|
||||
if (payload.aside !== "voice") {
|
||||
if (!ignores.includes(payload.aside)) {
|
||||
await table?.setItem("dmAsides", data.dmAsides);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { OG } from "../../types/resource";
|
||||
import { AutoDeleteMessageSettingDTO, AutoDeleteMsgForGroup, AutoDeleteMsgForUser, AutoDeleteSettingForChannels, AutoDeleteSettingForUsers } from "../../types/sse";
|
||||
import { resetAuthData } from "./auth.data";
|
||||
|
||||
type ChannelAside = "members" | "voice" | null;
|
||||
type ChannelAside = "members" | "voice" | "voice_fullscreen" | null;
|
||||
type DMAside = "voice" | null;
|
||||
export interface State {
|
||||
og: { [url: string]: OG }
|
||||
|
||||
@@ -72,7 +72,7 @@ const User: FC<Props> = ({
|
||||
name={curr.name}
|
||||
alt="avatar"
|
||||
/>
|
||||
{curr.is_bot ? <IconBot className={clsx(compact && "absolute -bottom-[2.5px] -right-[2.5px]", "!w-[15px] !h-[15px]")} /> : statusElement}
|
||||
{curr.is_bot ? <IconBot className={clsx("absolute -bottom-[2.5px] -right-[2.5px]", "!w-[15px] !h-[15px]")} /> : statusElement}
|
||||
</div>
|
||||
{!compact && (
|
||||
<span className={nameClass} title={curr?.name}>
|
||||
|
||||
@@ -36,6 +36,17 @@ const Voice = () => {
|
||||
window.VOICE_TRACK_MAP[+user.uid] = user.audioTrack;
|
||||
}
|
||||
if (mediaType == "video") {
|
||||
const label = user.videoTrack?.getMediaStreamTrack()?.label;
|
||||
console.log("labell", label);
|
||||
|
||||
if (label?.includes("screen")) {
|
||||
// 远端用户共享屏幕
|
||||
// const screenTrack = user.videoTrack as ShareScreenTrack;
|
||||
// screenTrack.on("track-ended", () => {
|
||||
// // 远端用户停止共享屏幕
|
||||
dispatch(updateVoicingMember({ uid: +user.uid, info: { shareScreen: true } }));
|
||||
}
|
||||
|
||||
window.VIDEO_TRACK_MAP[+user.uid] = user.videoTrack;
|
||||
playAgoraVideo(+user.uid);
|
||||
}
|
||||
@@ -46,7 +57,9 @@ const Voice = () => {
|
||||
}
|
||||
if (!user.hasVideo) {
|
||||
// 远端用户取消了视频
|
||||
dispatch(updateVoicingMember({ uid: +user.uid, info: { video: false } }));
|
||||
dispatch(updateVoicingMember({ uid: +user.uid, info: { video: false, shareScreen: false } }));
|
||||
// 注销本地视频变量
|
||||
window.VIDEO_TRACK_MAP[+user.uid] = null;
|
||||
// 关闭视频后,需要把视频的高度设置回去
|
||||
const playerEle = document.querySelector(`#CAMERA_${user.uid}`) as HTMLElement;
|
||||
playerEle.classList.remove("h-[120px]");
|
||||
@@ -216,30 +229,32 @@ const useVoice = ({ id, context = "channel" }: VoiceProps) => {
|
||||
}
|
||||
};
|
||||
const stopShareScreen = async () => {
|
||||
const localVideoTrack = window.VIDEO_TRACK_MAP[loginUid] as ShareScreenTrack;
|
||||
if (localVideoTrack) {
|
||||
await window.VOICE_CLIENT?.unpublish(localVideoTrack);
|
||||
if ("close" in localVideoTrack) {
|
||||
localVideoTrack.close();
|
||||
} else {
|
||||
localVideoTrack[0].close();
|
||||
}
|
||||
// localVideoTrack.close();
|
||||
window.VIDEO_TRACK_MAP[loginUid] = null;
|
||||
// 关闭视频后,需要把视频的高度设置回去
|
||||
const playerEle = document.querySelector(`#CAMERA_${loginUid}`) as HTMLElement;
|
||||
playerEle.classList.remove("h-[120px]");
|
||||
dispatch(updateVoicingInfo({
|
||||
video: false,
|
||||
shareScreen: false,
|
||||
id,
|
||||
context,
|
||||
}));
|
||||
let localVideoTrack = window.VIDEO_TRACK_MAP[loginUid] as ShareScreenTrack | null;
|
||||
if (!localVideoTrack) return;
|
||||
await window.VOICE_CLIENT?.unpublish(localVideoTrack);
|
||||
if ("close" in localVideoTrack) {
|
||||
localVideoTrack.close();
|
||||
} else {
|
||||
localVideoTrack[0].close();
|
||||
// localVideoTrack[0]=null
|
||||
}
|
||||
// localVideoTrack.close();
|
||||
window.VIDEO_TRACK_MAP[loginUid] = null;
|
||||
// 关闭视频后,需要把视频的高度设置回去
|
||||
const playerEle = document.querySelector(`#CAMERA_${loginUid}`) as HTMLElement;
|
||||
playerEle.classList.remove("h-[120px]");
|
||||
dispatch(updateVoicingInfo({
|
||||
video: false,
|
||||
shareScreen: false,
|
||||
id,
|
||||
context,
|
||||
}));
|
||||
};
|
||||
const startShareScreen = async () => {
|
||||
try {
|
||||
const localVideoTrack = await AgoraRTC.createScreenVideoTrack({
|
||||
electronScreenSourceId: "share_screen",
|
||||
selfBrowserSurface: "exclude",
|
||||
// 配置屏幕共享编码参数,详情请查看 API 文档。
|
||||
encoderConfig: "1080p_1",
|
||||
// 设置视频传输优化策略为清晰优先或流畅优先。
|
||||
|
||||
@@ -14,10 +14,11 @@ import IconScreen from '../../assets/icons/share.screen.svg';
|
||||
import { useVoice } from '../../common/component/Voice';
|
||||
import Signal from '../../common/component/Signal';
|
||||
import Tooltip from '../../common/component/Tooltip';
|
||||
import { ChatContext } from '../../types/common';
|
||||
|
||||
type Props = {
|
||||
id: number,
|
||||
context?: "channel" | "dm"
|
||||
context?: ChatContext
|
||||
}
|
||||
|
||||
const RTCWidget = ({ id, context = "channel" }: Props) => {
|
||||
|
||||
@@ -9,8 +9,9 @@ import ContextMenu, { Item } from "../../../common/component/ContextMenu";
|
||||
import useUserOperation from "../../../common/hook/useUserOperation";
|
||||
import { useAppSelector } from "../../../app/store";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ChatContext } from "../../../types/common";
|
||||
type Props = {
|
||||
context: "dm" | "channel";
|
||||
context: ChatContext;
|
||||
id: number;
|
||||
visible: boolean;
|
||||
mid: number;
|
||||
|
||||
@@ -14,9 +14,10 @@ import useContextMenu from "../../../common/hook/useContextMenu";
|
||||
import useUploadFile from "../../../common/hook/useUploadFile";
|
||||
import { useAppSelector } from "../../../app/store";
|
||||
import { fromNowTime } from "../../../common/utils";
|
||||
import { ChatContext } from "../../../types/common";
|
||||
|
||||
interface IProps {
|
||||
type?: "dm" | "channel";
|
||||
type?: ChatContext;
|
||||
id: number;
|
||||
mid: number;
|
||||
setDeleteChannelId: (param: number) => void;
|
||||
|
||||
@@ -5,8 +5,9 @@ import Session from "./Session";
|
||||
import DeleteChannelConfirmModal from "../../settingChannel/DeleteConfirmModal";
|
||||
import InviteModal from "../../../common/component/InviteModal";
|
||||
import { useAppSelector } from "../../../app/store";
|
||||
import { ChatContext } from "../../../types/common";
|
||||
export interface ChatSession {
|
||||
type: "dm" | "channel";
|
||||
type: ChatContext;
|
||||
id: number;
|
||||
mid: number;
|
||||
unread: number;
|
||||
|
||||
@@ -2,20 +2,21 @@
|
||||
// import { useDispatch } from 'react-redux';
|
||||
import VoiceManagement from './VoiceManagement';
|
||||
import { useVoice } from '../../../common/component/Voice';
|
||||
import { ChatContext } from '../../../types/common';
|
||||
|
||||
type Props = {
|
||||
visible: boolean,
|
||||
context?: "channel" | "dm",
|
||||
context?: ChatContext,
|
||||
id: number,
|
||||
}
|
||||
|
||||
const Dashboard = ({ context = "channel", id, visible }: Props) => {
|
||||
const { voicingInfo, setMute, setDeafen, leave, closeCamera, openCamera, startShareScreen, stopShareScreen } = useVoice({ id, context });
|
||||
const { voicingInfo, setMute, leave, closeCamera, openCamera, startShareScreen, stopShareScreen } = useVoice({ id, context });
|
||||
// const dispatch = useDispatch();
|
||||
|
||||
|
||||
return <div className={`h-full flex-col gap-1 w-[226px] overflow-y-scroll overflow-x-hidden p-2 shadow-[inset_1px_0px_0px_rgba(0,_0,_0,_0.1)] ${visible ? "flex" : "hidden"}`}>
|
||||
<VoiceManagement info={voicingInfo} setMute={setMute} setDeafen={setDeafen} leave={leave} closeCamera={closeCamera} openCamera={openCamera} startShareScreen={startShareScreen} stopShareScreen={stopShareScreen} />
|
||||
<VoiceManagement id={id} context={context} info={voicingInfo} setMute={setMute} leave={leave} closeCamera={closeCamera} openCamera={openCamera} startShareScreen={startShareScreen} stopShareScreen={stopShareScreen} />
|
||||
</div>;
|
||||
};
|
||||
|
||||
|
||||
@@ -5,23 +5,28 @@ import { useTranslation } from 'react-i18next';
|
||||
import { VoicingInfo } from '../../../app/slices/voice';
|
||||
import { useAppSelector } from '../../../app/store';
|
||||
import Avatar from '../../../common/component/Avatar';
|
||||
import IconHeadphone from '../../../assets/icons/sound.on.svg';
|
||||
import IconHeadphoneOff from '../../../assets/icons/sound.off.svg';
|
||||
// import IconHeadphone from '../../../assets/icons/sound.on.svg';
|
||||
// import IconHeadphoneOff from '../../../assets/icons/sound.off.svg';
|
||||
import IconMic from '../../../assets/icons/mic.on.svg';
|
||||
import IconMicOff from '../../../assets/icons/mic.off.svg';
|
||||
import IconCameraOff from '../../../assets/icons/camera.off.svg';
|
||||
import IconCamera from '../../../assets/icons/camera.svg';
|
||||
import IconFullscreen from '../../../assets/icons/fullscreen.svg';
|
||||
import IconScreen from '../../../assets/icons/share.screen.svg';
|
||||
import StyledButton from '../../../common/component/styled/Button';
|
||||
import IconCallOff from '../../../assets/icons/call.off.svg';
|
||||
import StyledButton from '../../../common/component/styled/Button';
|
||||
import Tooltip from '../../../common/component/Tooltip';
|
||||
import { playAgoraVideo } from '../../../common/utils';
|
||||
import { ChatContext } from '../../../types/common';
|
||||
import { updateChannelVisibleAside } from '../../../app/slices/footprint';
|
||||
import { useDispatch } from 'react-redux';
|
||||
// import User from '../../../common/component/User';
|
||||
|
||||
type Props = {
|
||||
id: number,
|
||||
context: ChatContext,
|
||||
info: VoicingInfo | null,
|
||||
setMute: (param: boolean) => void,
|
||||
setDeafen: (param: boolean) => void,
|
||||
leave: () => void,
|
||||
closeCamera: () => void,
|
||||
openCamera: () => void,
|
||||
@@ -29,8 +34,9 @@ type Props = {
|
||||
stopShareScreen: () => void
|
||||
}
|
||||
|
||||
const VoiceManagement = ({ info, setMute, setDeafen, leave, closeCamera, openCamera, startShareScreen, stopShareScreen }: Props) => {
|
||||
const VoiceManagement = ({ id, context, info, setMute, leave, closeCamera, openCamera, startShareScreen, stopShareScreen }: Props) => {
|
||||
const { t } = useTranslation("chat");
|
||||
const dispatch = useDispatch();
|
||||
const { userData, voicingMembers } = useAppSelector(store => {
|
||||
return {
|
||||
userData: store.users.byId,
|
||||
@@ -43,9 +49,13 @@ const VoiceManagement = ({ info, setMute, setDeafen, leave, closeCamera, openCam
|
||||
playAgoraVideo(id);
|
||||
});
|
||||
}, [voicingMembers.ids]);
|
||||
|
||||
const handleFullscreen = () => {
|
||||
if (context == "channel") {
|
||||
dispatch(updateChannelVisibleAside({ id, aside: "voice_fullscreen" }));
|
||||
}
|
||||
};
|
||||
if (!info) return null;
|
||||
const { deafen, muted, video, shareScreen } = info;
|
||||
const { muted, video, shareScreen } = info;
|
||||
const nameClass = clsx(`text-sm text-gray-500 max-w-[120px] truncate font-semibold dark:text-white`);
|
||||
const members = voicingMembers.ids;
|
||||
const membersData = voicingMembers.byId;
|
||||
@@ -102,11 +112,6 @@ const VoiceManagement = ({ info, setMute, setDeafen, leave, closeCamera, openCam
|
||||
</ul>
|
||||
<div className="flex flex-col gap-2">
|
||||
<ul className='flex justify-between'>
|
||||
<Tooltip tip={deafen ? t("undeafen") : t("deafen")} placement="top">
|
||||
<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>
|
||||
</Tooltip>
|
||||
<Tooltip tip={muted ? t("unmute") : t("mute")} placement="top">
|
||||
<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" />}
|
||||
@@ -122,6 +127,11 @@ const VoiceManagement = ({ info, setMute, setDeafen, leave, closeCamera, openCam
|
||||
<IconScreen className="fill-gray-700 dark:fill-gray-300" />
|
||||
</li>
|
||||
</Tooltip>
|
||||
<Tooltip tip={"Fullscreen"} placement="top">
|
||||
<li role={"button"} onClick={handleFullscreen} className="py-2 px-3 rounded bg-gray-100 dark:bg-gray-900">
|
||||
<IconFullscreen className="fill-gray-700 dark:fill-gray-300" />
|
||||
</li>
|
||||
</Tooltip>
|
||||
</ul>
|
||||
<StyledButton onClick={leave} className='bg-red-600 hover:!bg-red-700 text-center'>
|
||||
<Tooltip tip={t("leave_voice")} placement="top" offset={[0, 24]} >
|
||||
|
||||
@@ -9,9 +9,10 @@ import IconHeadphone from '../../../assets/icons/headphone.svg';
|
||||
import Tooltip from '../../../common/component/Tooltip';
|
||||
import { useVoice } from '../../../common/component/Voice';
|
||||
import { useGetAgoraStatusQuery } from '../../../app/services/server';
|
||||
import { ChatContext } from '../../../types/common';
|
||||
|
||||
type Props = {
|
||||
context?: "channel" | "dm"
|
||||
context?: ChatContext
|
||||
id: number,
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
import { useEffect, useState, MouseEvent } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import clsx from 'clsx';
|
||||
import IconMic from '../../assets/icons/mic.on.svg';
|
||||
import IconMicOff from '../../assets/icons/mic.off.svg';
|
||||
import IconCameraOff from '../../assets/icons/camera.off.svg';
|
||||
import IconPin from '../../assets/icons/pin.svg';
|
||||
import IconCamera from '../../assets/icons/camera.svg';
|
||||
import IconScreen from '../../assets/icons/share.screen.svg';
|
||||
import IconExitScreen from '../../assets/icons/fullscreen.exit.svg';
|
||||
import IconCallOff from '../../assets/icons/call.off.svg';
|
||||
import { ChatContext } from '../../types/common';
|
||||
import { useAppSelector } from '../../app/store';
|
||||
import Avatar from '../../common/component/Avatar';
|
||||
import { playAgoraVideo } from '../../common/utils';
|
||||
import { updateChannelVisibleAside } from '../../app/slices/footprint';
|
||||
import Tooltip from '../../common/component/Tooltip';
|
||||
import { useVoice } from '../../common/component/Voice';
|
||||
import StyledButton from '../../common/component/styled/Button';
|
||||
|
||||
type Props = {
|
||||
context: ChatContext,
|
||||
id: number
|
||||
}
|
||||
|
||||
const VoiceFullscreen = ({ id, context }: Props) => {
|
||||
const [speakingIndex, setSpeakingIndex] = useState(0);
|
||||
const [pin, setPin] = useState<number | undefined>(undefined);
|
||||
const dispatch = useDispatch();
|
||||
const { t } = useTranslation("chat");
|
||||
const { voicingInfo, setMute, leave, closeCamera, openCamera, startShareScreen, stopShareScreen } = useVoice({ id, context });
|
||||
const { name, userData, voicingMembers } = useAppSelector(store => {
|
||||
return {
|
||||
userData: store.users.byId,
|
||||
voicingMembers: store.voice.voicingMembers,
|
||||
name: context == "channel" ? store.channels.byId[id].name : store.users.byId[id].name
|
||||
};
|
||||
});
|
||||
useEffect(() => {
|
||||
const ids = voicingMembers.ids;
|
||||
ids.forEach((id, idx) => {
|
||||
playAgoraVideo(id);
|
||||
const { speakingVolume = 0 } = voicingMembers.byId[id];
|
||||
const speaking = speakingVolume > 50;
|
||||
if (speaking) {
|
||||
setSpeakingIndex(idx);
|
||||
}
|
||||
});
|
||||
|
||||
}, [voicingMembers]);
|
||||
const handleExitFullscreen = () => {
|
||||
if (context == "channel") {
|
||||
dispatch(updateChannelVisibleAside({ id, aside: "voice" }));
|
||||
}
|
||||
};
|
||||
const handlePin = (evt: MouseEvent<HTMLButtonElement>) => {
|
||||
const idx = evt.currentTarget.dataset.idx ?? "";
|
||||
if (idx == "undefined") {
|
||||
setPin(undefined);
|
||||
} else {
|
||||
setPin(parseInt(idx));
|
||||
}
|
||||
};
|
||||
if (!voicingInfo) return null;
|
||||
const _name = context == "channel" ? `# ${name}` : `@ ${name}`;
|
||||
const members = voicingMembers.ids;
|
||||
const membersData = voicingMembers.byId;
|
||||
const hasPin = typeof pin !== "undefined";
|
||||
const { muted, video, shareScreen } = voicingInfo;
|
||||
return (
|
||||
<div className='h-full bg-black text-gray-300 flex flex-col justify-between rounded-r-2xl'>
|
||||
{/* top */}
|
||||
<div className="px-7 py-6 flex justify-between">
|
||||
<span className='text-sm font-semibold'>{_name}</span>
|
||||
<div className="flex gap-4">
|
||||
<IconExitScreen role="button" onClick={handleExitFullscreen} className="fill-gray-700 dark:fill-gray-300" />
|
||||
</div>
|
||||
</div>
|
||||
{/* middle */}
|
||||
<ul className='flex grow justify-center items-end relative gap-2'>
|
||||
{members.map((uid, idx) => {
|
||||
const curr = userData[uid];
|
||||
if (!curr) return null;
|
||||
const { muted, speakingVolume = 0, shareScreen } = membersData[uid];
|
||||
const speaking = speakingVolume > 50;
|
||||
const special = hasPin ? pin == idx : idx == speakingIndex;
|
||||
const disablePin = special && !hasPin;
|
||||
return <li key={uid} className={clsx('bg-gray-700 group', special ? "absolute left-0 top-0 w-full h-[calc(100%_-_110px)] flex-center" : "relative rounded-lg py-1.5 px-12")}>
|
||||
<div className={clsx("w-20 h-20 flex shrink-0 relative transition-opacity")}>
|
||||
{speaking && <div className={clsx("absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 opacity-0 rounded-full bg-green-500 animate-speaking", special ? "w-[88px] h-[88px]" : "w-[86px] h-[86px]")}></div>}
|
||||
<Avatar
|
||||
width={80}
|
||||
height={80}
|
||||
className="w-full h-full rounded-full object-cover z-20"
|
||||
src={curr.avatar}
|
||||
name={curr.name}
|
||||
alt="avatar"
|
||||
/>
|
||||
</div>
|
||||
{shareScreen ? <div className={clsx("w-1 h-1 absolute z-40 rounded-full bg-green-700/60", special ? "top-2 left-2" : "top-1 left-1 px-2")} /> : null}
|
||||
{!disablePin && <button data-idx={special ? "undefined" : idx} role={"button"} onClick={handlePin} className={clsx("absolute left-1 top-1 z-40 p-1 rounded bg-black/50", special ? "" : "invisible group-hover:visible")}>
|
||||
{special ? <IconPin className="w-3 h-3 fill-gray-700 dark:fill-gray-300" /> : <IconPin className="w-3 h-3 fill-gray-700 dark:fill-gray-300" />}
|
||||
</button>}
|
||||
<span className={clsx("text-gray-300 bg-black/50 rounded-lg absolute z-40", special ? "left-2 bottom-2 px-2 py-1 text-sm " : "left-1 bottom-1 p-1 text-xs")} title={curr?.name}>
|
||||
{curr?.name}
|
||||
</span>
|
||||
<div className={clsx("flex items-center gap-2 absolute z-40 rounded bg-black/50", special ? "bottom-2 right-2 px-2 py-0.5" : "bottom-1 right-1 px-2")}>
|
||||
{muted ? <IconMicOff className={clsx("fill-gray-300", special ? "w-4" : "w-3")} /> : <IconMic className={clsx(" fill-gray-300 ", special ? "w-4" : "w-3")} />}
|
||||
</div>
|
||||
<div id={`CAMERA_${uid}`} className={clsx("absolute top-0 left-0 z-30 w-full h-full overflow-hidden m-auto", special ? "" : "rounded")}>
|
||||
{/* camera placeholder */}
|
||||
</div>
|
||||
</li>;
|
||||
})}
|
||||
|
||||
</ul>
|
||||
{/* bottom */}
|
||||
<ul className='py-4 flex justify-center gap-2'>
|
||||
<Tooltip tip={muted ? t("unmute") : t("mute")} placement="top">
|
||||
<li role={"button"} onClick={setMute.bind(null, !muted)} className="flex-center py-1 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>
|
||||
</Tooltip>
|
||||
<Tooltip tip={video ? t("camera_off") : t("camera_on")} placement="top">
|
||||
<li role={"button"} onClick={video ? closeCamera : openCamera} className="flex-center py-1 px-3 rounded bg-gray-100 dark:bg-gray-900">
|
||||
{video ? <IconCamera className="fill-gray-700 dark:fill-gray-300" /> : <IconCameraOff className="fill-gray-700 dark:fill-gray-300" />}
|
||||
</li>
|
||||
</Tooltip>
|
||||
<Tooltip tip={"Share Screen"} placement="top">
|
||||
<li role={"button"} onClick={shareScreen ? stopShareScreen : startShareScreen} className={clsx("flex-center py-1 px-3 rounded ", shareScreen ? "bg-green-700" : "bg-gray-100 dark:bg-gray-900")}>
|
||||
<IconScreen className="fill-gray-700 dark:fill-gray-300 w-6 h-6" />
|
||||
</li>
|
||||
</Tooltip>
|
||||
<StyledButton onClick={leave} className='bg-red-600 hover:!bg-red-700 text-center'>
|
||||
<Tooltip tip={t("leave_voice")} placement="top" offset={[0, 24]} >
|
||||
<IconCallOff className="m-auto" />
|
||||
</Tooltip>
|
||||
</StyledButton>
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default VoiceFullscreen;
|
||||
@@ -15,6 +15,7 @@ import GuestChannelChat from "./GuestChannelChat";
|
||||
import GuestSessionList from "./GuestSessionList";
|
||||
import ErrorCatcher from "../../common/component/ErrorCatcher";
|
||||
import RTCWidget from "./RTCWidget";
|
||||
import VoiceFullscreen from "./VoiceFullscreen";
|
||||
|
||||
function ChatPage() {
|
||||
const isHomePath = useMatch(`/`);
|
||||
@@ -23,10 +24,11 @@ function ChatPage() {
|
||||
const [channelModalVisible, setChannelModalVisible] = useState(false);
|
||||
const [usersModalVisible, setUsersModalVisible] = useState(false);
|
||||
const { channel_id = 0, user_id = 0 } = useParams();
|
||||
const { sessionUids, isGuest } = useAppSelector((store) => {
|
||||
const { sessionUids, isGuest, aside = "" } = useAppSelector((store) => {
|
||||
return {
|
||||
isGuest: store.authData.guest,
|
||||
sessionUids: store.userMessage.ids
|
||||
sessionUids: store.userMessage.ids,
|
||||
aside: channel_id ? store.footprint.channelAsides[+channel_id] : store.footprint.dmAsides[+user_id]
|
||||
};
|
||||
});
|
||||
const toggleUsersModalVisible = () => {
|
||||
@@ -49,9 +51,14 @@ function ChatPage() {
|
||||
: undefined;
|
||||
// console.log("temp uid", tmpUid);
|
||||
const placeholderVisible = channel_id == 0 && user_id == 0;
|
||||
const voiceFullscreenVisible = aside === "voice_fullscreen";
|
||||
const channelChatVisible = channel_id != 0 && aside !== "voice_fullscreen";
|
||||
const dmChatVisible = user_id != 0 && aside !== "voice_fullscreen";
|
||||
const isMainPath = isHomePath || isChatHomePath;
|
||||
const context = channel_id !== 0 ? "channel" : "dm";
|
||||
const contextId = (channel_id || user_id) ?? 0;
|
||||
const contextId = (+channel_id || +user_id) ?? 0;
|
||||
console.log("fffff", channel_id, user_id, aside, channelChatVisible);
|
||||
|
||||
return (
|
||||
<ErrorCatcher>
|
||||
{channelModalVisible && (
|
||||
@@ -71,14 +78,15 @@ function ChatPage() {
|
||||
</footer> : <RTCWidget id={+contextId} context={context} />}
|
||||
</div>
|
||||
<div className={clsx(`right-container md:rounded-r-2xl w-full bg-white dark:!bg-gray-700`, placeholderVisible && "h-full flex-center", isMainPath && "hidden md:flex")}>
|
||||
{voiceFullscreenVisible && <VoiceFullscreen id={contextId} context={context} />}
|
||||
{placeholderVisible && (isGuest ? <GuestBlankPlaceholder /> : <BlankPlaceholder />)}
|
||||
{channel_id !== 0 &&
|
||||
{channelChatVisible &&
|
||||
(isGuest ? (
|
||||
<GuestChannelChat cid={+channel_id} />
|
||||
) : (
|
||||
<ChannelChat cid={+channel_id} />
|
||||
))}
|
||||
{user_id !== 0 && <DMChat uid={+user_id} />}
|
||||
{dmChatVisible && <DMChat uid={+user_id} />}
|
||||
</div>
|
||||
</div>
|
||||
</ErrorCatcher>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export interface EntityId {
|
||||
id: number;
|
||||
}
|
||||
export type ChatContext = "dm" | "channel";
|
||||
export type Theme = "auto" | "dark" | "light";
|
||||
export type AuthType = "register" | "login";
|
||||
export type PriceType = "subscription" | "payment" | "booking";
|
||||
|
||||
Reference in New Issue
Block a user