change to seperate value-picker component
This commit is contained in:
parent
bf2fa9e771
commit
87af2c47c0
|
@ -0,0 +1,39 @@
|
|||
<mat-form-field class="value-picker" appearance="outline" color="accent">
|
||||
<mat-label>{{ nameCapMul }}</mat-label>
|
||||
<mat-chip-list #chipList>
|
||||
<mat-chip
|
||||
*ngFor="let item of this.myControl.value"
|
||||
[removable]="!isDisabled(item)"
|
||||
[disabled]="isDisabled(item)"
|
||||
(removed)="removeItem(item)"
|
||||
>
|
||||
{{ item }}
|
||||
<button *ngIf="!isDisabled(item)" matChipRemove>
|
||||
<mat-icon>cancel</mat-icon>
|
||||
</button>
|
||||
</mat-chip>
|
||||
<input
|
||||
placeholder="Add {{name}}..."
|
||||
#fruitInput
|
||||
[formControl]="inputControl"
|
||||
[value]="inputControl.value"
|
||||
[matAutocomplete]="auto"
|
||||
[matChipInputFor]="chipList"
|
||||
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
|
||||
(matChipInputTokenEnd)="addItemInput($event)"
|
||||
autocorrect="off"
|
||||
autocapitalize="none"
|
||||
/>
|
||||
</mat-chip-list>
|
||||
<mat-autocomplete
|
||||
#auto="matAutocomplete"
|
||||
(optionSelected)="addItemSelect($event)"
|
||||
>
|
||||
<mat-option
|
||||
*ngFor="let item of selectable | async"
|
||||
[value]="item"
|
||||
>
|
||||
{{ item }}
|
||||
</mat-option>
|
||||
</mat-autocomplete>
|
||||
</mat-form-field>
|
|
@ -0,0 +1,7 @@
|
|||
mat-form-field {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
}
|
|
@ -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<string[]>([]);
|
||||
|
||||
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();
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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 {}
|
15
frontend/src/app/models/decorators/required.decorator.ts
Normal file
15
frontend/src/app/models/decorators/required.decorator.ts
Normal file
|
@ -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,
|
||||
});
|
||||
}
|
|
@ -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<unknown>;
|
||||
|
||||
// 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<unknown>;
|
||||
};
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ export class LoginControl {
|
|||
return CreatePasswordError(this.password.errors);
|
||||
}
|
||||
|
||||
// This getter firstly verifies the form, RawData does not
|
||||
public getData(): Failable<UserPassModel> {
|
||||
if (this.username.errors || this.password.errors)
|
||||
return Fail('Invalid username or password');
|
||||
|
|
|
@ -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<UserPassModel> {
|
||||
if (
|
||||
this.username.errors ||
|
||||
|
|
|
@ -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<string[]>([]);
|
||||
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('');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<string[]>([]);
|
||||
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('');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,2 +1 @@
|
|||
<h1>Settings</h1>
|
||||
|
||||
|
|
|
@ -6,7 +6,5 @@ import { Component, OnInit } from '@angular/core';
|
|||
export class SettingsGeneralComponent implements OnInit {
|
||||
constructor() {}
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
ngOnInit(): void {}
|
||||
}
|
||||
|
|
|
@ -28,43 +28,11 @@
|
|||
|
||||
<div class="row">
|
||||
<div class="col-lg-6 col-12">
|
||||
<mat-form-field appearance="outline" color="accent">
|
||||
<mat-label>Permissions</mat-label>
|
||||
<mat-chip-list #chipList aria-label="Permissions Selection">
|
||||
<mat-chip
|
||||
*ngFor="let permission of model.selectedPermissions"
|
||||
(removed)="removePermission(permission)"
|
||||
>
|
||||
{{ uiFriendlyPermission(permission) }}
|
||||
<button matChipRemove>
|
||||
<mat-icon>cancel</mat-icon>
|
||||
</button>
|
||||
</mat-chip>
|
||||
<input
|
||||
placeholder="Add permission..."
|
||||
#fruitInput
|
||||
[formControl]="model.permissionControl"
|
||||
[value]="model.permissionControl.value"
|
||||
[matAutocomplete]="auto"
|
||||
[matChipInputFor]="chipList"
|
||||
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
|
||||
(matChipInputTokenEnd)="addPermission($event)"
|
||||
autocorrect="off"
|
||||
autocapitalize="none"
|
||||
/>
|
||||
</mat-chip-list>
|
||||
<mat-autocomplete
|
||||
#auto="matAutocomplete"
|
||||
(optionSelected)="selectedPermission($event)"
|
||||
>
|
||||
<mat-option
|
||||
*ngFor="let permission of model.selectablePermissions | async"
|
||||
[value]="permission"
|
||||
>
|
||||
{{ permission }}
|
||||
</mat-option>
|
||||
</mat-autocomplete>
|
||||
</mat-form-field>
|
||||
<values-picker
|
||||
name="permission"
|
||||
[control]="model.permissions"
|
||||
[selection-list]="allPermissions"
|
||||
></values-picker>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -86,3 +54,5 @@
|
|||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="value-picker"></div>
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
mat-form-field {
|
||||
values-picker {
|
||||
width: 100%;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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 {}
|
||||
|
|
|
@ -46,45 +46,12 @@
|
|||
|
||||
<div class="row" *ngIf="!isLockedPerms()">
|
||||
<div class="col-lg-6 col-12">
|
||||
<mat-form-field appearance="outline" color="accent">
|
||||
<mat-label>Roles</mat-label>
|
||||
<mat-chip-list #chipList aria-label="Roles Selection">
|
||||
<mat-chip
|
||||
*ngFor="let role of model.selectedRoles"
|
||||
[removable]="model.isRemovable(role)"
|
||||
[disabled]="!model.isRemovable(role)"
|
||||
(removed)="removeRole(role)"
|
||||
>
|
||||
{{ role }}
|
||||
<button *ngIf="model.isRemovable(role)" matChipRemove>
|
||||
<mat-icon>cancel</mat-icon>
|
||||
</button>
|
||||
</mat-chip>
|
||||
<input
|
||||
placeholder="Add role..."
|
||||
#fruitInput
|
||||
[formControl]="model.rolesControl"
|
||||
[value]="model.rolesControl.value"
|
||||
[matAutocomplete]="auto"
|
||||
[matChipInputFor]="chipList"
|
||||
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
|
||||
(matChipInputTokenEnd)="addRole($event)"
|
||||
autocorrect="off"
|
||||
autocapitalize="none"
|
||||
/>
|
||||
</mat-chip-list>
|
||||
<mat-autocomplete
|
||||
#auto="matAutocomplete"
|
||||
(optionSelected)="selectedRole($event)"
|
||||
>
|
||||
<mat-option
|
||||
*ngFor="let role of model.selectableRoles | async"
|
||||
[value]="role"
|
||||
>
|
||||
{{ role }}
|
||||
</mat-option>
|
||||
</mat-autocomplete>
|
||||
</mat-form-field>
|
||||
<values-picker
|
||||
name="role"
|
||||
[control]="model.roles"
|
||||
[disabled-list]="soulBoundRoles"
|
||||
[selection-list]="allRoles"
|
||||
></values-picker>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
mat-form-field {
|
||||
values-picker {
|
||||
width: 100%;
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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 {}
|
||||
|
|
Loading…
Reference in a new issue