Add seperation of voice channels, creation of new ones, and move around users

This commit is contained in:
2026-03-30 02:11:39 +02:00
parent 83694570e3
commit 727059fb52
19 changed files with 614 additions and 50 deletions

View File

@@ -172,6 +172,8 @@ export class RoomsSidePanelComponent {
volumeMenuY = signal(0);
volumeMenuPeerId = signal('');
volumeMenuDisplayName = signal('');
draggedVoiceUserId = signal<string | null>(null);
dragTargetVoiceChannelId = signal<string | null>(null);
private roomMemberKey(member: RoomMember): string {
return member.oderId || member.id;
@@ -371,9 +373,16 @@ export class RoomsSidePanelComponent {
}
const channels = this.currentRoom()?.channels ?? [];
const channelType = excludeChannelId
? channels.find((channel) => channel.id === excludeChannelId)?.type
: this.createChannelType();
if (isChannelNameTaken(channels, name, excludeChannelId)) {
return 'Channel names must be unique in a server.';
if (!channelType) {
return null;
}
if (isChannelNameTaken(channels, name, channelType, excludeChannelId)) {
return 'Channel names must be unique within text or voice channels.';
}
return null;
@@ -618,6 +627,113 @@ export class RoomsSidePanelComponent {
this.voiceWorkspace.focusStream(userId, { connectRemoteShares: true });
}
canMoveVoiceUsers(): boolean {
return this.canManageChannels();
}
canDragVoiceUser(user: User): boolean {
return this.canMoveVoiceUsers() && !this.isCurrentUserIdentity(user) && !!user.voiceState?.isConnected;
}
onVoiceUserDragStart(event: DragEvent, user: User): void {
if (!this.canDragVoiceUser(user)) {
event.preventDefault();
return;
}
const dragId = user.id || user.oderId;
if (!dragId) {
event.preventDefault();
return;
}
this.draggedVoiceUserId.set(dragId);
event.dataTransfer?.setData('text/plain', dragId);
if (event.dataTransfer) {
event.dataTransfer.effectAllowed = 'move';
}
}
onVoiceUserDragEnd(): void {
this.draggedVoiceUserId.set(null);
this.dragTargetVoiceChannelId.set(null);
}
onVoiceChannelDragOver(event: DragEvent, channelId: string): void {
if (!this.draggedVoiceUserId()) {
return;
}
event.preventDefault();
this.dragTargetVoiceChannelId.set(channelId);
if (event.dataTransfer) {
event.dataTransfer.dropEffect = 'move';
}
}
onVoiceChannelDragLeave(channelId: string): void {
if (this.dragTargetVoiceChannelId() === channelId) {
this.dragTargetVoiceChannelId.set(null);
}
}
onVoiceChannelDrop(event: DragEvent, channelId: string): void {
event.preventDefault();
const draggedUserId = this.draggedVoiceUserId() || event.dataTransfer?.getData('text/plain') || null;
this.draggedVoiceUserId.set(null);
this.dragTargetVoiceChannelId.set(null);
if (!draggedUserId) {
return;
}
this.moveVoiceUserToChannel(draggedUserId, channelId);
}
private moveVoiceUserToChannel(draggedUserId: string, channelId: string): void {
const room = this.currentRoom();
const actor = this.currentUser();
if (!room || !actor || !this.canMoveVoiceUsers()) {
return;
}
const targetUser = this.onlineUsers().find((user) => user.id === draggedUserId || user.oderId === draggedUserId);
if (!targetUser?.voiceState?.isConnected || targetUser.voiceState.serverId !== room.id || targetUser.voiceState.roomId === channelId) {
return;
}
const movedVoiceState: Partial<User['voiceState']> = {
isConnected: true,
isMuted: targetUser.voiceState.isMuted,
isDeafened: targetUser.voiceState.isDeafened,
isSpeaking: targetUser.voiceState.isSpeaking,
isMutedByAdmin: targetUser.voiceState.isMutedByAdmin,
volume: targetUser.voiceState.volume,
roomId: channelId,
serverId: room.id
};
this.store.dispatch(UsersActions.updateVoiceState({
userId: targetUser.id,
voiceState: movedVoiceState
}));
this.realtime.broadcastMessage({
type: 'voice-channel-move',
roomId: room.id,
targetUserId: targetUser.oderId || targetUser.id,
voiceState: movedVoiceState,
displayName: targetUser.displayName
});
}
isUserLocallyMuted(user: User): boolean {
const peerId = user.oderId || user.id;