Add seperation of voice channels, creation of new ones, and move around users
This commit is contained in:
@@ -116,7 +116,15 @@
|
||||
}
|
||||
<div class="space-y-1">
|
||||
@for (ch of voiceChannels(); track ch.id) {
|
||||
<div>
|
||||
<div
|
||||
class="rounded-md transition-colors"
|
||||
[class.bg-primary/10]="dragTargetVoiceChannelId() === ch.id"
|
||||
[class.ring-1]="dragTargetVoiceChannelId() === ch.id"
|
||||
[class.ring-primary/40]="dragTargetVoiceChannelId() === ch.id"
|
||||
(dragover)="onVoiceChannelDragOver($event, ch.id)"
|
||||
(dragleave)="onVoiceChannelDragLeave(ch.id)"
|
||||
(drop)="onVoiceChannelDrop($event, ch.id)"
|
||||
>
|
||||
<button
|
||||
class="w-full px-2 py-1.5 text-sm rounded hover:bg-secondary/60 flex items-center justify-between text-left transition-colors"
|
||||
(click)="joinVoice(ch.id)"
|
||||
@@ -163,6 +171,11 @@
|
||||
@for (u of voiceUsersInRoom(ch.id); track u.id) {
|
||||
<div
|
||||
class="flex items-center gap-2 px-2 py-1.5 rounded hover:bg-secondary/40"
|
||||
[class.cursor-pointer]="canDragVoiceUser(u)"
|
||||
[class.opacity-60]="draggedVoiceUserId() === (u.id || u.oderId)"
|
||||
[draggable]="canDragVoiceUser(u)"
|
||||
(dragstart)="onVoiceUserDragStart($event, u)"
|
||||
(dragend)="onVoiceUserDragEnd()"
|
||||
(contextmenu)="openVoiceUserVolumeMenu($event, u)"
|
||||
>
|
||||
<app-user-avatar
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user