Cleaning up comments

This commit is contained in:
2026-03-06 05:21:41 +01:00
parent fe2347b54e
commit 10467dfccb
50 changed files with 51 additions and 885 deletions

View File

@@ -1,4 +1,4 @@
/* eslint-disable @typescript-eslint/member-ordering, @typescript-eslint/no-unused-vars */
/* eslint-disable @typescript-eslint/member-ordering */
import {
Component,
inject,
@@ -51,7 +51,7 @@ import {
RoomMember,
Room,
User
} from '../../../core/models';
} from '../../../core/models/index';
import { v4 as uuidv4 } from 'uuid';
type TabView = 'channels' | 'users';
@@ -84,9 +84,6 @@ type TabView = 'channels' | 'users';
],
templateUrl: './rooms-side-panel.component.html'
})
/**
* Side panel listing text and voice channels, online users, and channel management actions.
*/
export class RoomsSidePanelComponent {
private store = inject(Store);
private webrtc = inject(WebRTCService);
@@ -129,35 +126,28 @@ export class RoomsSidePanelComponent {
return memberIds.size;
});
// Channel context menu state
showChannelMenu = signal(false);
channelMenuX = signal(0);
channelMenuY = signal(0);
contextChannel = signal<Channel | null>(null);
// Rename state
renamingChannelId = signal<string | null>(null);
// Create channel dialog state
showCreateChannelDialog = signal(false);
createChannelType = signal<'text' | 'voice'>('text');
newChannelName = '';
// User context menu state
showUserMenu = signal(false);
userMenuX = signal(0);
userMenuY = signal(0);
contextMenuUser = signal<User | null>(null);
// Per-user volume context menu state
showVolumeMenu = signal(false);
volumeMenuX = signal(0);
volumeMenuY = signal(0);
volumeMenuPeerId = signal('');
volumeMenuDisplayName = signal('');
/** Return online users excluding the current user. */
// Filter out current user from online users list
onlineUsersFiltered() {
const current = this.currentUser();
const currentId = current?.id;
@@ -170,7 +160,6 @@ export class RoomsSidePanelComponent {
return member.oderId || member.id;
}
/** Check whether the current user has permission to manage channels. */
canManageChannels(): boolean {
const room = this.currentRoom();
const user = this.currentUser();
@@ -178,7 +167,6 @@ export class RoomsSidePanelComponent {
if (!room || !user)
return false;
// Owner always can
if (room.hostId === user.id)
return true;
@@ -193,17 +181,13 @@ export class RoomsSidePanelComponent {
return false;
}
/** Select a text channel (no-op if currently renaming). */
// ---- Text channel selection ----
selectTextChannel(channelId: string) {
if (this.renamingChannelId())
return; // don't switch while renaming
return;
this.store.dispatch(RoomsActions.selectChannel({ channelId }));
}
/** Open the context menu for a channel at the cursor position. */
// ---- Channel context menu ----
openChannelContextMenu(evt: MouseEvent, channel: Channel) {
evt.preventDefault();
this.contextChannel.set(channel);
@@ -212,12 +196,10 @@ export class RoomsSidePanelComponent {
this.showChannelMenu.set(true);
}
/** Close the channel context menu. */
closeChannelMenu() {
this.showChannelMenu.set(false);
}
/** Begin inline renaming of the context-menu channel. */
startRename() {
const ch = this.contextChannel();
@@ -228,7 +210,6 @@ export class RoomsSidePanelComponent {
}
}
/** Commit the channel rename from the inline input value. */
confirmRename(event: Event) {
const input = event.target as HTMLInputElement;
const name = input.value.trim();
@@ -241,12 +222,10 @@ export class RoomsSidePanelComponent {
this.renamingChannelId.set(null);
}
/** Cancel the inline rename operation. */
cancelRename() {
this.renamingChannelId.set(null);
}
/** Delete the context-menu channel. */
deleteChannel() {
const ch = this.contextChannel();
@@ -257,7 +236,6 @@ export class RoomsSidePanelComponent {
}
}
/** Trigger a message inventory re-sync from all connected peers. */
resyncMessages() {
this.closeChannelMenu();
const room = this.currentRoom();
@@ -266,36 +244,26 @@ export class RoomsSidePanelComponent {
return;
}
// Dispatch startSync for UI spinner
this.store.dispatch(MessagesActions.startSync());
// Request inventory from all connected peers
const peers = this.webrtc.getConnectedPeers();
if (peers.length === 0) {
// No connected peers - sync will time out
}
const inventoryRequest: ChatEvent = { type: 'chat-inventory-request', roomId: room.id };
peers.forEach((pid) => {
try {
this.webrtc.sendToPeer(pid, inventoryRequest);
} catch (_error) {
// Failed to send inventory request to this peer
} catch {
return;
}
});
}
/** Open the create-channel dialog for the given channel type. */
// ---- Create channel ----
createChannel(type: 'text' | 'voice') {
this.createChannelType.set(type);
this.newChannelName = '';
this.showCreateChannelDialog.set(true);
}
/** Confirm channel creation and dispatch the add-channel action. */
confirmCreateChannel() {
const name = this.newChannelName.trim();
@@ -315,13 +283,10 @@ export class RoomsSidePanelComponent {
this.showCreateChannelDialog.set(false);
}
/** Cancel channel creation and close the dialog. */
cancelCreateChannel() {
this.showCreateChannelDialog.set(false);
}
/** Open the user context menu for admin actions (kick/role change). */
// ---- User context menu (kick/role) ----
openUserContextMenu(evt: MouseEvent, user: User) {
evt.preventDefault();
@@ -334,16 +299,12 @@ export class RoomsSidePanelComponent {
this.showUserMenu.set(true);
}
/** Close the user context menu. */
closeUserMenu() {
this.showUserMenu.set(false);
}
/** Open the per-user volume context menu for a voice channel participant. */
openVoiceUserVolumeMenu(evt: MouseEvent, user: User) {
evt.preventDefault();
// Don't show volume menu for the local user
const me = this.currentUser();
if (user.id === me?.id || user.oderId === me?.oderId)
@@ -356,7 +317,6 @@ export class RoomsSidePanelComponent {
this.showVolumeMenu.set(true);
}
/** Change a user's role and broadcast the update to connected peers. */
changeUserRole(role: 'admin' | 'moderator' | 'member') {
const user = this.contextMenuUser();
const roomId = this.currentRoom()?.id;
@@ -365,7 +325,6 @@ export class RoomsSidePanelComponent {
if (user) {
this.store.dispatch(UsersActions.updateUserRole({ userId: user.id, role }));
// Broadcast role change to peers
this.webrtc.broadcastMessage({
type: 'role-change',
roomId,
@@ -375,7 +334,6 @@ export class RoomsSidePanelComponent {
}
}
/** Kick a user and broadcast the action to peers. */
kickUserAction() {
const user = this.contextMenuUser();
@@ -383,7 +341,6 @@ export class RoomsSidePanelComponent {
if (user) {
this.store.dispatch(UsersActions.kickUser({ userId: user.id }));
// Broadcast kick to peers
this.webrtc.broadcastMessage({
type: 'kick',
targetUserId: user.id,
@@ -392,14 +349,10 @@ export class RoomsSidePanelComponent {
}
}
/** Join a voice channel, managing permissions and existing voice connections. */
// ---- Voice ----
joinVoice(roomId: string) {
// Gate by room permissions
const room = this.currentRoom();
if (room && room.permissions && room.permissions.allowVoice === false) {
// Voice is disabled by room permissions
return;
}
@@ -408,12 +361,8 @@ export class RoomsSidePanelComponent {
const current = this.currentUser();
// Check if already connected to voice in a DIFFERENT server - must disconnect first
// Also handle stale voice state: if the store says connected but voice isn't actually active,
// clear it so the user can join.
if (current?.voiceState?.isConnected && current.voiceState.serverId !== room?.id) {
if (!this.webrtc.isVoiceConnected()) {
// Stale state - clear it so the user can proceed
if (current.id) {
this.store.dispatch(
UsersActions.updateVoiceState({
@@ -429,21 +378,16 @@ export class RoomsSidePanelComponent {
);
}
} else {
// Already connected to voice in another server; must disconnect first
return;
}
}
// If switching channels within the same server, just update the room
const isSwitchingChannels = current?.voiceState?.isConnected && current.voiceState.serverId === room?.id && current.voiceState.roomId !== roomId;
// Enable microphone and broadcast voice-state
const enableVoicePromise = isSwitchingChannels ? Promise.resolve() : this.webrtc.enableVoice();
enableVoicePromise
.then(() => this.onVoiceJoinSucceeded(roomId, room, current ?? null))
.catch((_error) => {
// Failed to join voice room
});
.catch(() => undefined);
}
private onVoiceJoinSucceeded(roomId: string, room: Room, current: User | null): void {
@@ -523,23 +467,18 @@ export class RoomsSidePanelComponent {
});
}
/** Leave a voice channel and broadcast the disconnect state. */
leaveVoice(roomId: string) {
const current = this.currentUser();
// Only leave if currently in this room
if (!(current?.voiceState?.isConnected && current.voiceState.roomId === roomId))
return;
// Stop voice heartbeat
this.webrtc.stopVoiceHeartbeat();
this.untrackCurrentUserMic();
// Disable voice locally
this.webrtc.disableVoice();
// Update store voice state
if (current?.id) {
this.store.dispatch(
UsersActions.updateVoiceState({
@@ -555,7 +494,6 @@ export class RoomsSidePanelComponent {
);
}
// Broadcast disconnect
this.webrtc.broadcastMessage({
type: 'voice-state',
oderId: current?.oderId || current?.id,
@@ -569,37 +507,31 @@ export class RoomsSidePanelComponent {
}
});
// End voice session
this.voiceSessionService.endSession();
}
/** Count the number of users connected to a voice channel in the current room. */
voiceOccupancy(roomId: string): number {
return this.voiceUsersInRoom(roomId).length;
}
/** Dispatch a viewer:focus event to display a remote user's screen share. */
viewShare(userId: string) {
const evt = new CustomEvent('viewer:focus', { detail: { userId } });
window.dispatchEvent(evt);
}
/** Dispatch a viewer:focus event to display a remote user's stream. */
viewStream(userId: string) {
const evt = new CustomEvent('viewer:focus', { detail: { userId } });
window.dispatchEvent(evt);
}
/** Check whether the local user has muted a specific voice user. */
isUserLocallyMuted(user: User): boolean {
const peerId = user.oderId || user.id;
return this.voicePlayback.isUserMuted(peerId);
}
/** Check whether a user is currently sharing their screen. */
isUserSharing(userId: string): boolean {
const me = this.currentUser();
@@ -618,7 +550,6 @@ export class RoomsSidePanelComponent {
return !!stream && stream.getVideoTracks().length > 0;
}
/** Return all users currently connected to a specific voice channel, including the local user. */
voiceUsersInRoom(roomId: string) {
const room = this.currentRoom();
const me = this.currentUser();
@@ -626,13 +557,11 @@ export class RoomsSidePanelComponent {
(user) => !!user.voiceState?.isConnected && user.voiceState?.roomId === roomId && user.voiceState?.serverId === room?.id
);
// Include the local user at the top if they are in this voice channel
if (
me?.voiceState?.isConnected &&
me.voiceState?.roomId === roomId &&
me.voiceState?.serverId === room?.id
) {
// Avoid duplicates if the current user is already in onlineUsers
const meId = me.id;
const meOderId = me.oderId;
const alreadyIncluded = remoteUsers.some(
@@ -647,7 +576,6 @@ export class RoomsSidePanelComponent {
return remoteUsers;
}
/** Check whether the current user is connected to the specified voice channel. */
isCurrentRoom(roomId: string): boolean {
const me = this.currentUser();
const room = this.currentRoom();
@@ -655,32 +583,18 @@ export class RoomsSidePanelComponent {
return !!(me?.voiceState?.isConnected && me.voiceState?.roomId === roomId && me.voiceState?.serverId === room?.id);
}
/** Check whether voice is enabled by the current room's permissions. */
voiceEnabled(): boolean {
const room = this.currentRoom();
return room?.permissions?.allowVoice !== false;
}
/**
* Get the measured latency (ms) to a voice user.
* Returns `null` when no measurement is available yet.
*/
getPeerLatency(user: User): number | null {
const latencies = this.webrtc.peerLatencies();
// Try oderId first (primary peer key), then fall back to user id
return latencies.get(user.oderId ?? '') ?? latencies.get(user.id) ?? null;
}
/**
* Return a Tailwind `bg-*` class representing the latency quality.
* - green : < 100 ms
* - yellow : 100-199 ms
* - orange : 200-349 ms
* - red : >= 350 ms
* - gray : no data yet
*/
getPingColorClass(user: User): string {
const ms = this.getPeerLatency(user);