Files
Toju/toju-app/src/app/features/settings/settings-modal/members-settings/members-settings.component.ts
Myx 31962aeb1a fix: restore build and stabilize E2E cross-signal behavior
Revert the automated member-ordering pass that broke Angular field init
(TS2729) and disable that rule until a safe reorder strategy exists.
Fix modal/confirm dialog i18n defaults via template fallbacks, search all
active endpoints (including offline), register foreign rooms with actor
owner IDs, sync profile display names from avatar summaries, and guard
dm-chat when a private call converts to a group conversation.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-11 12:16:40 +02:00

167 lines
5.1 KiB
TypeScript

/* eslint-disable @typescript-eslint/member-ordering */
import {
Component,
computed,
inject,
input
} from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { NgIcon, provideIcons } from '@ng-icons/core';
import { Store } from '@ngrx/store';
import { lucideUserX, lucideBan } from '@ng-icons/lucide';
import {
Room,
RoomMember,
RoomRole
} from '../../../../shared-kernel';
import { RoomsActions } from '../../../../store/rooms/rooms.actions';
import { UsersActions } from '../../../../store/users/users.actions';
import { selectCurrentUser, selectUsersEntities } from '../../../../store/users/users.selectors';
import { selectCurrentRoom } from '../../../../store/rooms/rooms.selectors';
import { UserAvatarComponent } from '../../../../shared';
import {
canManageMember,
findAssignableRoles,
getDisplayRoleName,
getRoleIdsForMember,
normalizeRoomAccessControl,
setRoleAssignmentsForMember
} from '../../../../domains/access-control';
import { AppI18nService, APP_TRANSLATE_IMPORTS } from '../../../../core/i18n';
interface ServerMemberView extends RoomMember {
assignedRoleIds: string[];
displayRoleName: string;
isOnline: boolean;
}
@Component({
selector: 'app-members-settings',
standalone: true,
imports: [
CommonModule,
FormsModule,
NgIcon,
UserAvatarComponent,
...APP_TRANSLATE_IMPORTS
],
viewProviders: [
provideIcons({
lucideUserX,
lucideBan
})
],
templateUrl: './members-settings.component.html'
})
export class MembersSettingsComponent {
private store = inject(Store);
private readonly i18n = inject(AppI18nService);
/** The currently selected server, passed from the parent. */
server = input<Room | null>(null);
/** Whether the current user is admin of this server. */
isAdmin = input(false);
accessRole = input<string | null>(null);
currentUser = this.store.selectSignal(selectCurrentUser);
currentRoom = this.store.selectSignal(selectCurrentRoom);
usersEntities = this.store.selectSignal(selectUsersEntities);
normalizedServer = computed(() => {
const room = this.server();
return room ? normalizeRoomAccessControl(room) : null;
});
assignableRoles = computed<RoomRole[]>(() => findAssignableRoles(this.normalizedServer()?.roles ?? []));
members = computed<ServerMemberView[]>(() => {
const room = this.normalizedServer();
const me = this.currentUser();
const currentRoom = this.currentRoom();
const usersEntities = this.usersEntities();
if (!room)
return [];
return (room.members ?? [])
.filter((member) => member.id !== me?.id && member.oderId !== me?.oderId)
.map((member) => {
const liveUser = currentRoom?.id === room.id
? (usersEntities[member.id]
|| Object.values(usersEntities).find((user) => !!user && user.oderId === member.oderId)
|| null)
: null;
return {
...member,
assignedRoleIds: getRoleIdsForMember(room, member),
displayRoleName: getDisplayRoleName(room, member, (key) => this.i18n.instant(key)),
avatarUrl: liveUser?.avatarUrl || member.avatarUrl,
displayName: liveUser?.displayName || member.displayName,
isOnline: !!liveUser && (liveUser.isOnline === true || liveUser.status !== 'offline')
};
});
});
canChangeRoles(member: ServerMemberView): boolean {
const room = this.normalizedServer();
const currentUser = this.currentUser();
return !!room && !!currentUser && canManageMember(room, currentUser, member, 'manageRoles');
}
canKickMembers(member: ServerMemberView): boolean {
const room = this.normalizedServer();
const currentUser = this.currentUser();
return !!room && !!currentUser && canManageMember(room, currentUser, member, 'kickMembers');
}
canBanMembers(member: ServerMemberView): boolean {
const room = this.normalizedServer();
const currentUser = this.currentUser();
return !!room && !!currentUser && canManageMember(room, currentUser, member, 'banMembers');
}
toggleRole(member: ServerMemberView, roleId: string, event: Event): void {
const room = this.normalizedServer();
if (!room)
return;
const checkbox = event.target as HTMLInputElement;
const nextRoleIds = checkbox.checked
? [...member.assignedRoleIds, roleId]
: member.assignedRoleIds.filter((candidateRoleId) => candidateRoleId !== roleId);
const roleAssignments = setRoleAssignmentsForMember(room.roleAssignments, member, nextRoleIds);
this.store.dispatch(RoomsActions.updateRoomAccessControl({
roomId: room.id,
changes: { roleAssignments }
}));
}
kickMember(member: ServerMemberView): void {
const room = this.normalizedServer();
if (!room)
return;
this.store.dispatch(UsersActions.kickUser({ userId: member.id,
roomId: room.id }));
}
banMember(member: ServerMemberView): void {
const room = this.normalizedServer();
if (!room)
return;
this.store.dispatch(UsersActions.banUser({ userId: member.id,
roomId: room.id,
displayName: member.displayName }));
}
}