diff --git a/frontend/src/app/components/values-picker/values-picker.component.html b/frontend/src/app/components/values-picker/values-picker.component.html new file mode 100644 index 0000000..4d50d63 --- /dev/null +++ b/frontend/src/app/components/values-picker/values-picker.component.html @@ -0,0 +1,39 @@ + + {{ nameCapMul }} + + + {{ item }} + + + + + + + {{ item }} + + + diff --git a/frontend/src/app/components/values-picker/values-picker.component.scss b/frontend/src/app/components/values-picker/values-picker.component.scss new file mode 100644 index 0000000..5d7d730 --- /dev/null +++ b/frontend/src/app/components/values-picker/values-picker.component.scss @@ -0,0 +1,7 @@ +mat-form-field { + width: 100%; +} + +:host { + display: block; +} diff --git a/frontend/src/app/components/values-picker/values-picker.component.ts b/frontend/src/app/components/values-picker/values-picker.component.ts new file mode 100644 index 0000000..0a981ca --- /dev/null +++ b/frontend/src/app/components/values-picker/values-picker.component.ts @@ -0,0 +1,108 @@ +import { COMMA, ENTER, SPACE } from '@angular/cdk/keycodes'; +import { Component, Input, OnInit } from '@angular/core'; +import { FormControl } from '@angular/forms'; +import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; +import { MatChipInputEvent } from '@angular/material/chips'; +import Fuse from 'fuse.js'; +import { AutoUnsubscribe } from 'ngx-auto-unsubscribe-decorator'; +import { BehaviorSubject } from 'rxjs'; +import { Required } from 'src/app/models/decorators/required.decorator'; + +@Component({ + selector: 'values-picker', + templateUrl: './values-picker.component.html', + styleUrls: ['./values-picker.component.scss'], +}) +export class ValuesPickerComponent implements OnInit { + // Static data + readonly separatorKeysCodes: number[] = [ENTER, COMMA, SPACE]; + + // Ui niceties + @Input('name') @Required name: string = ''; + public get nameCap(): string { + return this.name.charAt(0).toUpperCase() + this.name.slice(1); + } + public get nameCapMul(): string { + return `${this.nameCap}s`; + } + + // Inputs/oututs + @Input('selection-list') @Required fullSelection: string[] = []; + @Input('control') @Required myControl: FormControl; + + @Input('disabled-list') disabledSelection: string[] = []; + + // Selection + private selectableSubject = new BehaviorSubject([]); + + public selectable = this.selectableSubject.asObservable(); + public inputControl = new FormControl(''); + + public ngOnInit(): void { + this.subscribeInputValue(); + this.subscribeMyValue(); + } + + public isDisabled(value: string): boolean { + return this.disabledSelection.includes(value); + } + + // Remove/add + public removeItem(item: string) { + const selected: string[] = this.myControl.value; + const newSelection = selected.filter((s) => s !== item); + this.myControl.setValue(newSelection); + } + + public addItemInput(event: MatChipInputEvent) { + const value = (event.value ?? '').trim(); + this.addItem(value); + } + + public addItemSelect(event: MatAutocompleteSelectedEvent): void { + this.addItem(event.option.viewValue); + } + + private addItem(value: string) { + console.log('adding', value); + const selectable = this.selectableSubject.getValue(); + if (this.isDisabled(value) || !selectable.includes(value)) return; + + const selected: string[] = this.myControl.value; + this.myControl.setValue([...selected, value]); + + this.inputControl.setValue(''); + } + + // Update and subscribe + private updateSelectable() { + const selected: string[] = this.myControl.value; + const available = this.fullSelection.filter( + (s) => !this.isDisabled(s) && !selected.includes(s) + ); + + const searchValue = this.inputControl.value; + if (searchValue && available.length > 0) { + const fuse = new Fuse(available); + const result = fuse.search(searchValue).map((r) => r.item); + + this.selectableSubject.next(result); + } else { + this.selectableSubject.next(available); + } + } + + @AutoUnsubscribe() + private subscribeInputValue() { + return this.inputControl.valueChanges.subscribe((value) => { + this.updateSelectable(); + }); + } + + @AutoUnsubscribe() + private subscribeMyValue() { + return this.myControl.valueChanges.subscribe((value) => { + this.updateSelectable(); + }); + } +} diff --git a/frontend/src/app/components/values-picker/values-picker.module.ts b/frontend/src/app/components/values-picker/values-picker.module.ts new file mode 100644 index 0000000..3236029 --- /dev/null +++ b/frontend/src/app/components/values-picker/values-picker.module.ts @@ -0,0 +1,25 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { MatAutocompleteModule } from '@angular/material/autocomplete'; +import { MatChipsModule } from '@angular/material/chips'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { ValuesPickerComponent } from './values-picker.component'; + +@NgModule({ + declarations: [ValuesPickerComponent], + imports: [ + CommonModule, + MatIconModule, + MatFormFieldModule, + MatInputModule, + MatChipsModule, + MatAutocompleteModule, + FormsModule, + ReactiveFormsModule, + ], + exports: [ValuesPickerComponent], +}) +export class ValuesPickerModule {} diff --git a/frontend/src/app/models/decorators/required.decorator.ts b/frontend/src/app/models/decorators/required.decorator.ts new file mode 100644 index 0000000..a27fe9e --- /dev/null +++ b/frontend/src/app/models/decorators/required.decorator.ts @@ -0,0 +1,15 @@ +export function Required(target: object, propertyKey: string) { + Object.defineProperty(target, propertyKey, { + get() { + throw new Error(`Attribute ${propertyKey} is required`); + }, + set(value) { + Object.defineProperty(target, propertyKey, { + value, + writable: true, + configurable: true, + }); + }, + configurable: true, + }); +} diff --git a/frontend/src/app/models/dto/picsur-routes.dto.ts b/frontend/src/app/models/dto/picsur-routes.dto.ts index 0d038dc..951404d 100644 --- a/frontend/src/app/models/dto/picsur-routes.dto.ts +++ b/frontend/src/app/models/dto/picsur-routes.dto.ts @@ -3,13 +3,18 @@ import { Route } from '@angular/router'; export type PRouteData = { page?: { + // This is not the tab-title, but the title in the sidenav title?: string; + // This is not the favicon, but the icon in the sidenav icon?: string; category?: string; }; permissions?: string[]; noContainer?: boolean; sidebar?: ComponentType; + + // This is not meant to be set by the user, but by a resolver service + // It just cant be stored anywhere else _sidebar_portal?: Portal; }; diff --git a/frontend/src/app/models/forms/login.control.ts b/frontend/src/app/models/forms/login.control.ts index a1096f1..df9c5ff 100644 --- a/frontend/src/app/models/forms/login.control.ts +++ b/frontend/src/app/models/forms/login.control.ts @@ -20,6 +20,7 @@ export class LoginControl { return CreatePasswordError(this.password.errors); } + // This getter firstly verifies the form, RawData does not public getData(): Failable { if (this.username.errors || this.password.errors) return Fail('Invalid username or password'); diff --git a/frontend/src/app/models/forms/register.control.ts b/frontend/src/app/models/forms/register.control.ts index e9fe387..b08b760 100644 --- a/frontend/src/app/models/forms/register.control.ts +++ b/frontend/src/app/models/forms/register.control.ts @@ -3,7 +3,10 @@ import { Fail, Failable } from 'picsur-shared/dist/types'; import { UserPassModel } from '../forms-dto/userpass.dto'; import { Compare } from '../validators/compare.validator'; import { - CreatePasswordError, CreateUsernameError, PasswordValidators, UsernameValidators + CreatePasswordError, + CreateUsernameError, + PasswordValidators, + UsernameValidators } from '../validators/user.validator'; export class RegisterControl { @@ -26,6 +29,7 @@ export class RegisterControl { return CreatePasswordError(this.passwordConfirm.errors); } + // This getter firstly verifies the form, RawData does not public getData(): Failable { if ( this.username.errors || diff --git a/frontend/src/app/models/forms/updaterole.control.ts b/frontend/src/app/models/forms/updaterole.control.ts index feb1b06..01da744 100644 --- a/frontend/src/app/models/forms/updaterole.control.ts +++ b/frontend/src/app/models/forms/updaterole.control.ts @@ -1,24 +1,11 @@ import { FormControl } from '@angular/forms'; -import Fuse from 'fuse.js'; -import { BehaviorSubject, Subscription } from 'rxjs'; import { RoleModel } from '../forms-dto/role.dto'; import { RoleNameValidators } from '../validators/role.validator'; import { CreateUsernameError } from '../validators/user.validator'; export class UpdateRoleControl { - // Set once - private permissions: string[] = []; - - // Variables - private selectablePermissionsSubject = new BehaviorSubject([]); - private permissionsInputSubscription: null | Subscription; - public rolename = new FormControl('', RoleNameValidators); - - public permissionControl = new FormControl('', []); - public selectablePermissions = - this.selectablePermissionsSubject.asObservable(); - public selectedPermissions: string[] = []; + public permissions = new FormControl([]); public get rolenameValue() { return this.rolename.value; @@ -28,48 +15,18 @@ export class UpdateRoleControl { return CreateUsernameError(this.rolename.errors); } - constructor() { - this.permissionsInputSubscription = - this.permissionControl.valueChanges.subscribe((roles) => { - this.updateSelectablePermissions(); - }); - } - - public destroy() { - if (this.permissionsInputSubscription) { - this.permissionsInputSubscription.unsubscribe(); - this.permissionsInputSubscription = null; - } - } - - public addPermission(permission: string) { - if (!this.selectablePermissionsSubject.value.includes(permission)) return; - - this.selectedPermissions.push(permission); - this.clearInput(); - } - - public removePermission(permission: string) { - this.selectedPermissions = this.selectedPermissions.filter( - (r) => r !== permission - ); - this.updateSelectablePermissions(); + public get selectedPermissions() { + return this.permissions.value; } // Data interaction - public putAllPermissions(permissions: string[]) { - this.permissions = permissions; - this.updateSelectablePermissions(); - } - public putRoleName(rolename: string) { this.rolename.setValue(rolename); } public putPermissions(permissions: string[]) { - this.selectedPermissions = permissions; - this.updateSelectablePermissions(); + this.permissions.setValue(permissions); } public getData(): RoleModel { @@ -78,28 +35,4 @@ export class UpdateRoleControl { permissions: this.selectedPermissions, }; } - - // Logic - - private updateSelectablePermissions() { - const availablePermissins = this.permissions.filter( - (r) => !this.selectedPermissions.includes(r) - ); - - const searchValue = this.permissionControl.value; - if (searchValue && availablePermissins.length > 0) { - const fuse = new Fuse(availablePermissins); - const result = fuse - .search(this.permissionControl.value ?? '') - .map((r) => r.item); - - this.selectablePermissionsSubject.next(result); - } else { - this.selectablePermissionsSubject.next(availablePermissins); - } - } - - private clearInput() { - this.permissionControl.setValue(''); - } } diff --git a/frontend/src/app/models/forms/updateuser.control.ts b/frontend/src/app/models/forms/updateuser.control.ts index 356ba89..f5b797c 100644 --- a/frontend/src/app/models/forms/updateuser.control.ts +++ b/frontend/src/app/models/forms/updateuser.control.ts @@ -1,7 +1,4 @@ import { FormControl } from '@angular/forms'; -import Fuse from 'fuse.js'; -import { ERole } from 'picsur-shared/dist/entities/role.entity'; -import { BehaviorSubject, Subscription } from 'rxjs'; import { FullUserModel } from '../forms-dto/fulluser.dto'; import { CreatePasswordError, @@ -11,23 +8,9 @@ import { } from '../validators/user.validator'; export class UpdateUserControl { - // Special roles - private SoulBoundRolesList: string[] = []; - - // Set once - private fullRoles: ERole[] = []; - private roles: string[] = []; - - // Variables - private selectableRolesSubject = new BehaviorSubject([]); - private rolesInputSubscription: null | Subscription; - public username = new FormControl('', UsernameValidators); public password = new FormControl('', PasswordValidators); - - public rolesControl = new FormControl('', []); - public selectableRoles = this.selectableRolesSubject.asObservable(); - public selectedRoles: string[] = []; + public roles = new FormControl([]); public get usernameValue() { return this.username.value; @@ -41,74 +24,18 @@ export class UpdateUserControl { return CreatePasswordError(this.password.errors); } - constructor() { - this.rolesInputSubscription = this.rolesControl.valueChanges.subscribe( - (roles) => { - this.updateSelectableRoles(); - } - ); - } - - public destroy() { - if (this.rolesInputSubscription) { - this.rolesInputSubscription.unsubscribe(); - this.rolesInputSubscription = null; - } - } - - public addRole(role: string) { - if (!this.selectableRolesSubject.value.includes(role)) return; - - this.selectedRoles.push(role); - this.clearInput(); - } - - public removeRole(role: string) { - this.selectedRoles = this.selectedRoles.filter((r) => r !== role); - this.updateSelectableRoles(); - } - - public isRemovable(role: string) { - if (this.SoulBoundRolesList.includes(role)) return false; - return true; - } - - public getEffectivePermissions(): string[] { - const permissions: string[] = []; - for (const role of this.selectedRoles) { - const fullRole = this.fullRoles.find((r) => r.name === role); - if (!fullRole) { - console.warn(`Role ${role} not found`); - continue; - } - - permissions.push( - ...fullRole.permissions.filter((p) => !permissions.includes(p)) - ); - } - - return permissions; + public get selectedRoles(): string[] { + return this.roles.value; } // Data interaction - public putAllRoles(roles: ERole[]) { - this.fullRoles = roles; - this.roles = roles.map((role) => role.name); - this.updateSelectableRoles(); - } - public putUsername(username: string) { this.username.setValue(username); } public putRoles(roles: string[]) { - this.selectedRoles = roles; - this.updateSelectableRoles(); - } - - public putSoulBoundRoles(roles: string[]) { - this.SoulBoundRolesList = roles; + this.roles.setValue(roles); } public getData(): FullUserModel { @@ -118,30 +45,4 @@ export class UpdateUserControl { roles: this.selectedRoles, }; } - - // Logic - - private updateSelectableRoles() { - const availableRoles = this.roles.filter( - // Not available if either already selected, or the role is not addable/removable - (r) => - !(this.selectedRoles.includes(r) || this.SoulBoundRolesList.includes(r)) - ); - - const searchValue = this.rolesControl.value; - if (searchValue && availableRoles.length > 0) { - const fuse = new Fuse(availableRoles); - const result = fuse - .search(this.rolesControl.value ?? '') - .map((r) => r.item); - - this.selectableRolesSubject.next(result); - } else { - this.selectableRolesSubject.next(availableRoles); - } - } - - private clearInput() { - this.rolesControl.setValue(''); - } } diff --git a/frontend/src/app/routes/settings/general/settings-general.component.html b/frontend/src/app/routes/settings/general/settings-general.component.html index 987fabb..52b633a 100644 --- a/frontend/src/app/routes/settings/general/settings-general.component.html +++ b/frontend/src/app/routes/settings/general/settings-general.component.html @@ -1,2 +1 @@

Settings

- diff --git a/frontend/src/app/routes/settings/general/settings-general.component.ts b/frontend/src/app/routes/settings/general/settings-general.component.ts index 6edead4..3d69789 100644 --- a/frontend/src/app/routes/settings/general/settings-general.component.ts +++ b/frontend/src/app/routes/settings/general/settings-general.component.ts @@ -6,7 +6,5 @@ import { Component, OnInit } from '@angular/core'; export class SettingsGeneralComponent implements OnInit { constructor() {} - ngOnInit(): void { - } - + ngOnInit(): void {} } diff --git a/frontend/src/app/routes/settings/roles/settings-roles-edit/settings-roles-edit.component.html b/frontend/src/app/routes/settings/roles/settings-roles-edit/settings-roles-edit.component.html index 57f70e8..775d444 100644 --- a/frontend/src/app/routes/settings/roles/settings-roles-edit/settings-roles-edit.component.html +++ b/frontend/src/app/routes/settings/roles/settings-roles-edit/settings-roles-edit.component.html @@ -28,43 +28,11 @@
- - Permissions - - - {{ uiFriendlyPermission(permission) }} - - - - - - - {{ permission }} - - - +
@@ -86,3 +54,5 @@ + +
diff --git a/frontend/src/app/routes/settings/roles/settings-roles-edit/settings-roles-edit.component.scss b/frontend/src/app/routes/settings/roles/settings-roles-edit/settings-roles-edit.component.scss index c7acb4b..0779a03 100644 --- a/frontend/src/app/routes/settings/roles/settings-roles-edit/settings-roles-edit.component.scss +++ b/frontend/src/app/routes/settings/roles/settings-roles-edit/settings-roles-edit.component.scss @@ -1,3 +1,3 @@ -mat-form-field { +values-picker { width: 100%; } diff --git a/frontend/src/app/routes/settings/roles/settings-roles-edit/settings-roles-edit.component.ts b/frontend/src/app/routes/settings/roles/settings-roles-edit/settings-roles-edit.component.ts index 98d8639..be8c73d 100644 --- a/frontend/src/app/routes/settings/roles/settings-roles-edit/settings-roles-edit.component.ts +++ b/frontend/src/app/routes/settings/roles/settings-roles-edit/settings-roles-edit.component.ts @@ -1,12 +1,7 @@ -import { COMMA, ENTER, SPACE } from '@angular/cdk/keycodes'; import { Component, OnInit } from '@angular/core'; -import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; -import { MatChipInputEvent } from '@angular/material/chips'; import { ActivatedRoute, Router } from '@angular/router'; -import { Permission } from 'picsur-shared/dist/dto/permissions.dto'; import { HasFailed } from 'picsur-shared/dist/types'; -import { UIFriendlyPermissions } from 'src/app/i18n/permissions.i18n'; -import { SnackBarType } from "src/app/models/dto/snack-bar-type.dto"; +import { SnackBarType } from 'src/app/models/dto/snack-bar-type.dto'; import { UpdateRoleControl } from 'src/app/models/forms/updaterole.control'; import { PermissionService } from 'src/app/services/api/permission.service'; import { RolesService } from 'src/app/services/api/roles.service'; @@ -23,11 +18,10 @@ enum EditMode { styleUrls: ['./settings-roles-edit.component.scss'], }) export class SettingsRolesEditComponent implements OnInit { - readonly separatorKeysCodes: number[] = [ENTER, COMMA, SPACE]; - private mode: EditMode = EditMode.edit; model = new UpdateRoleControl(); + allPermissions: string[] = []; get adding() { return this.mode === EditMode.add; @@ -49,26 +43,29 @@ export class SettingsRolesEditComponent implements OnInit { } private async initRole() { + // Check if adding or editing const rolename = this.route.snapshot.paramMap.get('role'); if (!rolename) { this.mode = EditMode.add; return; } + // Set data thats already known this.mode = EditMode.edit; this.model.putRoleName(rolename); + // Fetch data and populate form const role = await this.rolesService.getRole(rolename); if (HasFailed(role)) { this.utilService.showSnackBar('Failed to get role', SnackBarType.Error); return; } - this.model.putRoleName(role.name); this.model.putPermissions(role.permissions); } private async initPermissions() { + // Get a list of all permissions so that we can select them const allPermissions = await this.permissionsService.fetchAllPermission(); if (HasFailed(allPermissions)) { this.utilService.showSnackBar( @@ -78,30 +75,13 @@ export class SettingsRolesEditComponent implements OnInit { return; } - this.model.putAllPermissions(allPermissions); - } - - removePermission(permission: string) { - this.model.removePermission(permission); - } - - addPermission(event: MatChipInputEvent) { - const value = (event.value ?? '').trim(); - this.model.addPermission(value); - } - - selectedPermission(event: MatAutocompleteSelectedEvent): void { - this.model.addPermission(event.option.viewValue); + this.allPermissions = allPermissions; } cancel() { this.router.navigate(['/settings/roles']); } - uiFriendlyPermission(permission: string) { - return UIFriendlyPermissions[permission as Permission] ?? permission; - } - async updateUser() { const data = this.model.getData(); diff --git a/frontend/src/app/routes/settings/roles/settings-roles.module.ts b/frontend/src/app/routes/settings/roles/settings-roles.module.ts index 4a81ac3..90da77f 100644 --- a/frontend/src/app/routes/settings/roles/settings-roles.module.ts +++ b/frontend/src/app/routes/settings/roles/settings-roles.module.ts @@ -1,7 +1,6 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { MatAutocompleteModule } from '@angular/material/autocomplete'; import { MatButtonModule } from '@angular/material/button'; import { MatChipsModule } from '@angular/material/chips'; import { MatFormFieldModule } from '@angular/material/form-field'; @@ -9,6 +8,7 @@ import { MatIconModule } from '@angular/material/icon'; import { MatInputModule } from '@angular/material/input'; import { MatPaginatorModule } from '@angular/material/paginator'; import { MatTableModule } from '@angular/material/table'; +import { ValuesPickerModule } from 'src/app/components/values-picker/values-picker.module'; import { SettingsRolesEditComponent } from './settings-roles-edit/settings-roles-edit.component'; import { SettingsRolesComponent } from './settings-roles.component'; import { SettingsRolesRoutingModule } from './settings-roles.routing.module'; @@ -27,7 +27,7 @@ import { SettingsRolesRoutingModule } from './settings-roles.routing.module'; MatFormFieldModule, MatInputModule, ReactiveFormsModule, - MatAutocompleteModule + ValuesPickerModule ], }) export class SettingsRolesRouteModule {} diff --git a/frontend/src/app/routes/settings/users/settings-users-edit/settings-users-edit.component.html b/frontend/src/app/routes/settings/users/settings-users-edit/settings-users-edit.component.html index c55e9b0..4e57cf8 100644 --- a/frontend/src/app/routes/settings/users/settings-users-edit/settings-users-edit.component.html +++ b/frontend/src/app/routes/settings/users/settings-users-edit/settings-users-edit.component.html @@ -46,45 +46,12 @@
- - Roles - - - {{ role }} - - - - - - - {{ role }} - - - +
diff --git a/frontend/src/app/routes/settings/users/settings-users-edit/settings-users-edit.component.scss b/frontend/src/app/routes/settings/users/settings-users-edit/settings-users-edit.component.scss index c7acb4b..0779a03 100644 --- a/frontend/src/app/routes/settings/users/settings-users-edit/settings-users-edit.component.scss +++ b/frontend/src/app/routes/settings/users/settings-users-edit/settings-users-edit.component.scss @@ -1,3 +1,3 @@ -mat-form-field { +values-picker { width: 100%; } diff --git a/frontend/src/app/routes/settings/users/settings-users-edit/settings-users-edit.component.ts b/frontend/src/app/routes/settings/users/settings-users-edit/settings-users-edit.component.ts index 6ed57d6..900654b 100644 --- a/frontend/src/app/routes/settings/users/settings-users-edit/settings-users-edit.component.ts +++ b/frontend/src/app/routes/settings/users/settings-users-edit/settings-users-edit.component.ts @@ -1,12 +1,10 @@ -import { COMMA, ENTER, SPACE } from '@angular/cdk/keycodes'; import { Component, OnInit } from '@angular/core'; -import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; -import { MatChipInputEvent } from '@angular/material/chips'; import { ActivatedRoute, Router } from '@angular/router'; import { Permission } from 'picsur-shared/dist/dto/permissions.dto'; +import { ERole } from 'picsur-shared/dist/entities/role.entity'; import { HasFailed } from 'picsur-shared/dist/types'; import { UIFriendlyPermissions } from 'src/app/i18n/permissions.i18n'; -import { SnackBarType } from "src/app/models/dto/snack-bar-type.dto"; +import { SnackBarType } from 'src/app/models/dto/snack-bar-type.dto'; import { UpdateUserControl } from 'src/app/models/forms/updateuser.control'; import { RolesService } from 'src/app/services/api/roles.service'; import { UserManageService } from 'src/app/services/api/usermanage.service'; @@ -24,12 +22,14 @@ enum EditMode { }) export class SettingsUsersEditComponent implements OnInit { private ImmutableUsersList: string[] = []; - - readonly separatorKeysCodes: number[] = [ENTER, COMMA, SPACE]; - private mode: EditMode = EditMode.edit; model = new UpdateUserControl(); + allFullRoles: ERole[] = []; + get allRoles(): string[] { + return this.allFullRoles.map((role) => role.name); + } + soulBoundRoles: string[] = []; get adding() { return this.mode === EditMode.add; @@ -53,14 +53,21 @@ export class SettingsUsersEditComponent implements OnInit { private async initUser() { const username = this.route.snapshot.paramMap.get('username'); - const { DefaultRoles, SoulBoundRoles } = - await this.rolesService.getSpecialRolesOptimistic(); - this.model.putSoulBoundRoles(SoulBoundRoles); + const SpecialRoles = await this.rolesService.getSpecialRoles(); + if (HasFailed(SpecialRoles)) { + this.utilService.showSnackBar( + 'Failed to get special roles', + SnackBarType.Error + ); + return; + } + + this.soulBoundRoles = SpecialRoles.SoulBoundRoles; if (!username) { this.mode = EditMode.add; - this.model.putRoles(DefaultRoles); + this.model.putRoles(SpecialRoles.DefaultRoles); return; } @@ -88,29 +95,25 @@ export class SettingsUsersEditComponent implements OnInit { return; } - this.model.putAllRoles(roles); + this.allFullRoles = roles; } - getEffectivePermissions() { - return this.model - .getEffectivePermissions() - .map( - (permission) => - UIFriendlyPermissions[permission as Permission] ?? permission + public getEffectivePermissions(): string[] { + const permissions: string[] = []; + + for (const role of this.model.selectedRoles) { + const fullRole = this.allFullRoles.find((r) => r.name === role); + if (!fullRole) { + console.warn(`Role ${role} not found`); + continue; + } + + permissions.push( + ...fullRole.permissions.filter((p) => !permissions.includes(p)) ); - } + } - removeRole(role: string) { - this.model.removeRole(role); - } - - addRole(event: MatChipInputEvent) { - const value = (event.value ?? '').trim(); - this.model.addRole(value); - } - - selectedRole(event: MatAutocompleteSelectedEvent): void { - this.model.addRole(event.option.viewValue); + return permissions.map((p) => UIFriendlyPermissions[p as Permission] ?? p); } cancel() { diff --git a/frontend/src/app/routes/settings/users/settings-users.module.ts b/frontend/src/app/routes/settings/users/settings-users.module.ts index 9130969..d9e4edb 100644 --- a/frontend/src/app/routes/settings/users/settings-users.module.ts +++ b/frontend/src/app/routes/settings/users/settings-users.module.ts @@ -1,7 +1,6 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { MatAutocompleteModule } from '@angular/material/autocomplete'; import { MatButtonModule } from '@angular/material/button'; import { MatChipsModule } from '@angular/material/chips'; import { MatFormFieldModule } from '@angular/material/form-field'; @@ -9,15 +8,13 @@ import { MatIconModule } from '@angular/material/icon'; import { MatInputModule } from '@angular/material/input'; import { MatPaginatorModule } from '@angular/material/paginator'; import { MatTableModule } from '@angular/material/table'; +import { ValuesPickerModule } from 'src/app/components/values-picker/values-picker.module'; import { SettingsUsersEditComponent } from './settings-users-edit/settings-users-edit.component'; import { SettingsUsersComponent } from './settings-users.component'; import { SettingsUsersRoutingModule } from './settings-users.routing.module'; @NgModule({ - declarations: [ - SettingsUsersComponent, - SettingsUsersEditComponent, - ], + declarations: [SettingsUsersComponent, SettingsUsersEditComponent], imports: [ CommonModule, SettingsUsersRoutingModule, @@ -28,9 +25,9 @@ import { SettingsUsersRoutingModule } from './settings-users.routing.module'; MatFormFieldModule, MatInputModule, MatChipsModule, - MatAutocompleteModule, FormsModule, ReactiveFormsModule, + ValuesPickerModule, ], }) export class SettingsUsersRouteModule {}