feat: introduce a comprehensive LiveKit-based voice and video chat system with state management and screen sharing capabilities.
All checks were successful
Build and Release / build-and-release (push) Successful in 10m6s
All checks were successful
Build and Release / build-and-release (push) Successful in 10m6s
This commit is contained in:
@@ -30,6 +30,7 @@ import categoryCollapsedIcon from '../assets/icons/category_collapsed_icon.svg';
|
||||
import PingSound from '../assets/sounds/ping.mp3';
|
||||
import screenShareStartSound from '../assets/sounds/screenshare_start.mp3';
|
||||
import screenShareStopSound from '../assets/sounds/screenshare_stop.mp3';
|
||||
import { getUserPref, setUserPref } from '../utils/userPreferences';
|
||||
|
||||
const USER_COLORS = ['#5865F2', '#EBA7CD', '#57F287', '#FEE75C', '#EB459E', '#ED4245'];
|
||||
|
||||
@@ -376,7 +377,7 @@ function getScreenCaptureConstraints(selection) {
|
||||
};
|
||||
}
|
||||
|
||||
const VoiceUserContextMenu = ({ x, y, onClose, user, onMute, isMuted, onServerMute, isServerMuted, hasPermission, onMessage }) => {
|
||||
const VoiceUserContextMenu = ({ x, y, onClose, user, onMute, isMuted, onServerMute, isServerMuted, hasPermission, onMessage, isSelf, userVolume, onVolumeChange }) => {
|
||||
const menuRef = useRef(null);
|
||||
const [pos, setPos] = useState({ top: y, left: x });
|
||||
|
||||
@@ -397,8 +398,30 @@ const VoiceUserContextMenu = ({ x, y, onClose, user, onMute, isMuted, onServerMu
|
||||
setPos({ top: newTop, left: newLeft });
|
||||
}, [x, y]);
|
||||
|
||||
const sliderPercent = (userVolume / 200) * 100;
|
||||
|
||||
return (
|
||||
<div ref={menuRef} className="context-menu" style={{ top: pos.top, left: pos.left }} onClick={(e) => e.stopPropagation()}>
|
||||
{!isSelf && (
|
||||
<>
|
||||
<div className="context-menu-volume" onMouseDown={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}>
|
||||
<div className="context-menu-volume-label">
|
||||
<span>User Volume</span>
|
||||
<span>{userVolume}%</span>
|
||||
</div>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="200"
|
||||
value={userVolume}
|
||||
onChange={(e) => onVolumeChange(Number(e.target.value))}
|
||||
className="context-menu-volume-slider"
|
||||
style={{ background: `linear-gradient(to right, hsl(235 86% 65%) ${sliderPercent}%, var(--bg-tertiary) ${sliderPercent}%)` }}
|
||||
/>
|
||||
</div>
|
||||
<div className="context-menu-separator" />
|
||||
</>
|
||||
)}
|
||||
<div
|
||||
className="context-menu-item context-menu-checkbox-item"
|
||||
role="menuitemcheckbox"
|
||||
@@ -726,7 +749,7 @@ const Sidebar = ({ channels, categories, activeChannel, onSelectChannel, usernam
|
||||
const [newChannelType, setNewChannelType] = useState('text');
|
||||
const [editingChannel, setEditingChannel] = useState(null);
|
||||
const [isScreenShareModalOpen, setIsScreenShareModalOpen] = useState(false);
|
||||
const [collapsedCategories, setCollapsedCategories] = useState({});
|
||||
const [collapsedCategories, setCollapsedCategories] = useState(() => getUserPref(userId, 'collapsedCategories', {}));
|
||||
const [channelListContextMenu, setChannelListContextMenu] = useState(null);
|
||||
const [voiceUserMenu, setVoiceUserMenu] = useState(null);
|
||||
const [showCreateChannelModal, setShowCreateChannelModal] = useState(false);
|
||||
@@ -815,7 +838,7 @@ const Sidebar = ({ channels, categories, activeChannel, onSelectChannel, usernam
|
||||
if (activeChannel === id) onSelectChannel(null);
|
||||
};
|
||||
|
||||
const { connectToVoice, activeChannelId: voiceChannelId, connectionState, disconnectVoice, activeChannelName: voiceChannelName, voiceStates, room, activeSpeakers, setScreenSharing, isPersonallyMuted, togglePersonalMute, isMuted: selfMuted, toggleMute, serverMute, isServerMuted, serverSettings } = useVoice();
|
||||
const { connectToVoice, activeChannelId: voiceChannelId, connectionState, disconnectVoice, activeChannelName: voiceChannelName, voiceStates, room, activeSpeakers, setScreenSharing, isPersonallyMuted, togglePersonalMute, isMuted: selfMuted, toggleMute, serverMute, isServerMuted, serverSettings, getUserVolume, setUserVolume } = useVoice();
|
||||
|
||||
const handleStartCreate = () => {
|
||||
setIsCreating(true);
|
||||
@@ -1084,7 +1107,11 @@ const Sidebar = ({ channels, categories, activeChannel, onSelectChannel, usernam
|
||||
};
|
||||
|
||||
const toggleCategory = (cat) => {
|
||||
setCollapsedCategories(prev => ({ ...prev, [cat]: !prev[cat] }));
|
||||
setCollapsedCategories(prev => {
|
||||
const next = { ...prev, [cat]: !prev[cat] };
|
||||
setUserPref(userId, 'collapsedCategories', next);
|
||||
return next;
|
||||
});
|
||||
};
|
||||
|
||||
// Group channels by categoryId
|
||||
@@ -1590,6 +1617,7 @@ const Sidebar = ({ channels, categories, activeChannel, onSelectChannel, usernam
|
||||
y={voiceUserMenu.y}
|
||||
user={voiceUserMenu.user}
|
||||
onClose={() => setVoiceUserMenu(null)}
|
||||
isSelf={voiceUserMenu.user.userId === userId}
|
||||
isMuted={voiceUserMenu.user.userId === userId ? selfMuted : isPersonallyMuted(voiceUserMenu.user.userId)}
|
||||
onMute={() => voiceUserMenu.user.userId === userId ? toggleMute() : togglePersonalMute(voiceUserMenu.user.userId)}
|
||||
isServerMuted={isServerMuted(voiceUserMenu.user.userId)}
|
||||
@@ -1599,6 +1627,8 @@ const Sidebar = ({ channels, categories, activeChannel, onSelectChannel, usernam
|
||||
onOpenDM(voiceUserMenu.user.userId, voiceUserMenu.user.username);
|
||||
onViewChange('me');
|
||||
}}
|
||||
userVolume={getUserVolume(voiceUserMenu.user.userId)}
|
||||
onVolumeChange={(vol) => setUserVolume(voiceUserMenu.user.userId, vol)}
|
||||
/>
|
||||
)}
|
||||
{showCreateChannelModal && (
|
||||
|
||||
Reference in New Issue
Block a user