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

This commit is contained in:
Bryan1029384756
2026-02-12 18:49:31 -06:00
parent 751f428adc
commit 2201c56cb2
16 changed files with 791 additions and 270 deletions

View File

@@ -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 && (