Improved data validation for import

This commit is contained in:
Sven Heidemann 2023-11-01 22:37:33 +01:00
parent 2d358188af
commit 84fedfaa0b
15 changed files with 361 additions and 321 deletions

View File

@ -41,6 +41,9 @@ class LevelMutation(QueryABC):
int(input["permissions"]), int(input["permissions"]),
server, server,
) )
levels = self._levels.get_levels_by_server_id(server.id)
if levels.where(lambda x: x.name == level.name).count() > 0:
raise ValueError(f"Level with name {level.name} already exists")
self._levels.add_level(level) self._levels.add_level(level)
self._db.save_changes() self._db.save_changes()

View File

@ -1,58 +1,65 @@
import { Level } from "../models/data/level.model";
export interface Column { export interface Column {
key: string; key: string;
name: string; name: string;
} }
export class ComponentWithTable { export class ComponentWithTable {
private _hiddenColumns: Column[] = []; private _hiddenColumns: Column[] = [];
set hiddenColumns(value: Column[]) { set hiddenColumns(value: Column[]) {
this._hiddenColumns = value; this._hiddenColumns = value;
localStorage.setItem("hiddenColumns", JSON.stringify(value)); localStorage.setItem("hiddenColumns", JSON.stringify(value));
}
get hiddenColumns(): Column[] {
return this._hiddenColumns;
}
public name: string = "";
public columns: Column[] = [];
public isEditingNew: boolean = false;
public callback = (data: any[], isNew: boolean) => {
this.save(data, isNew);
};
public validator: (oldElement: any, newElement: any) => boolean = (oldElement: any, newElement: any) => {
return true;
};
constructor(
name: string,
columns: string[],
validator?: (oldElement: any, newElement: any) => boolean
) {
this.name = name;
this.columns = columns.map(column => {
return { key: this.getKey(column), name: column };
});
let hiddenColumns = localStorage.getItem("hiddenColumns");
if (!hiddenColumns) {
localStorage.setItem("hiddenColumns", JSON.stringify([{}]));
hiddenColumns = localStorage.getItem("hiddenColumns") ?? JSON.stringify([{}]);
} }
this._hiddenColumns = JSON.parse(hiddenColumns);
get hiddenColumns(): Column[] { if (validator) {
return this._hiddenColumns; this.validator = validator;
} }
}
public name: string = ""; private getKey(column: string): string {
public columns: Column[] = []; return `${this.name}_${column}`;
}
constructor( public isColumnVisible(column: string): boolean {
name: string, return !this._hiddenColumns.map(column => column.key).includes(this.getKey(column));
columns: string[] }
) {
this.name = name; public onRowEditSave(newLevel: any, index: number) {
this.columns = columns.map(column => { }
return { key: this.getKey(column), name: column };
}); public save(data: any[], isNew: boolean) {
let hiddenColumns = localStorage.getItem("hiddenColumns"); for (let i = 0; i < data.length; i++) {
if (!hiddenColumns) { this.isEditingNew = isNew;
localStorage.setItem("hiddenColumns", JSON.stringify([{}])); this.onRowEditSave(data[i], i);
hiddenColumns = localStorage.getItem("hiddenColumns") ?? JSON.stringify([{}]);
}
this._hiddenColumns = JSON.parse(hiddenColumns);
}
private getKey(column: string): string {
return `${this.name}_${column}`;
}
public isColumnVisible(column: string): boolean {
return !this._hiddenColumns.map(column => column.key).includes(this.getKey(column));
}
public callback = (data: any[]) => {
this.save(data);
};
public onRowEditSave(newLevel: any, index: number) {
}
public save(data: any[]) {
for (let i = 0; i < data.length; i++) {
this.onRowEditSave(data[i], i);
}
} }
}
} }

View File

@ -31,7 +31,6 @@ export class AuthUserComponent extends ComponentWithTable implements OnInit, OnD
activityValues: number[] = [0, 100]; activityValues: number[] = [0, 100];
clonedUsers: { [s: string]: AuthUserDTO; } = {}; clonedUsers: { [s: string]: AuthUserDTO; } = {};
isEditingNew: boolean = false;
authRoles = [ authRoles = [
{ label: AuthRoles[AuthRoles.Normal].toString(), value: AuthRoles.Normal }, { label: AuthRoles[AuthRoles.Normal].toString(), value: AuthRoles.Normal },

View File

@ -1,66 +1,86 @@
import { Component, EventEmitter, Input, Output, ViewChild } from "@angular/core"; import { Component, EventEmitter, Input, Output, ViewChild } from "@angular/core";
import { ToastService } from "../../../../services/toast/toast.service"; import { ToastService } from "../../../../services/toast/toast.service";
import { TranslateService } from "@ngx-translate/core"; import { TranslateService } from "@ngx-translate/core";
import { elementAt } from "rxjs";
interface UploadEvent { interface UploadEvent {
originalEvent: Event; originalEvent: Event;
files: File[]; files: File[];
} }
@Component({ @Component({
selector: "app-data-import-and-export", selector: "app-data-import-and-export",
templateUrl: "./data-import-and-export.component.html", templateUrl: "./data-import-and-export.component.html",
styleUrls: ["./data-import-and-export.component.scss"] styleUrls: ["./data-import-and-export.component.scss"]
}) })
export class DataImportAndExportComponent { export class DataImportAndExportComponent {
@ViewChild("upload") upload: any; @ViewChild("upload") upload: any;
private _data: any[] = []; private _data: any[] = [];
@Input() name: string = ""; @Input() name: string = "";
@Input() @Input()
set data(data: any[]) { set data(data: any[]) {
this._data = data; this._data = data;
this.dataChange.emit(data); this.dataChange.emit(data);
} }
get data(): any[] { get data(): any[] {
return this._data; return this._data;
} }
@Output() dataChange: EventEmitter<any[]> = new EventEmitter<any[]>(); @Output() dataChange: EventEmitter<any[]> = new EventEmitter<any[]>();
@Input() callback!: (data: any[]) => void; @Input() callback!: (data: any[], isNew: boolean) => void;
@Input() validator: (oldElement: any, newElement: any) => boolean = (oldElement: any, newElement: any) => {
return true;
};
public constructor( public constructor(
private toastService: ToastService, private toastService: ToastService,
private translate: TranslateService private translate: TranslateService
) { ) {
} }
public export() { public export() {
const json = JSON.stringify(this.data); const json = JSON.stringify(this.data);
const element = document.createElement("a"); const element = document.createElement("a");
element.setAttribute("href", "data:application/json;charset=UTF-8," + encodeURIComponent(json)); element.setAttribute("href", "data:application/json;charset=UTF-8," + encodeURIComponent(json));
element.setAttribute("download", `${this.name}.json`); element.setAttribute("download", `${this.name}.json`);
element.style.display = "none"; element.style.display = "none";
document.body.appendChild(element); document.body.appendChild(element);
element.click(); // simulate click element.click(); // simulate click
document.body.removeChild(element); document.body.removeChild(element);
} }
public import(event: { files: File[] }) { public import(event: { files: File[] }) {
const file = event.files[0]; const file = event.files[0];
const fileReader = new FileReader(); const fileReader = new FileReader();
fileReader.onload = () => { fileReader.onload = () => {
if (!fileReader.result) return; if (!fileReader.result) return;
this.data = JSON.parse(fileReader.result.toString()); const newData: any[] = JSON.parse(fileReader.result.toString());
this.upload.clear(); this.upload.clear();
this.callback(this.data); newData.forEach(element => {
this.toastService.success(this.translate.instant("common.file.uploaded"), this.translate.instant("common.file.uploaded")); element.id = 0;
}; });
fileReader.readAsText(file, "UTF-8"); this.data.forEach(element => {
} const existingElement = newData.find(x => this.validator(x, element));
if (existingElement) {
const index = this.data.indexOf(element);
const oldId = element.id;
element = existingElement;
element.id = oldId;
this.data[index] = element;
newData.splice(newData.indexOf(existingElement), 1);
}
});
this.callback(this.data, false);
this.callback(newData, true);
this.data.push(...newData);
this.toastService.success(this.translate.instant("common.file.uploaded"), this.translate.instant("common.file.uploaded"));
};
fileReader.readAsText(file, "UTF-8");
}
} }

View File

@ -28,7 +28,7 @@
class="icon-btn btn" (click)="resetFilters()"> class="icon-btn btn" (click)="resetFilters()">
</button> </button>
<app-data-import-and-export name="achievements" [(data)]="achievements" <app-data-import-and-export name="achievements" [(data)]="achievements"
[callback]="callback"></app-data-import-and-export> [callback]="callback" [validator]="validator"></app-data-import-and-export>
</div> </div>
</div> </div>
</ng-template> </ng-template>

View File

@ -15,7 +15,13 @@ import { TranslateService } from "@ngx-translate/core";
import { DataService } from "../../../../../../services/data/data.service"; import { DataService } from "../../../../../../services/data/data.service";
import { SidebarService } from "../../../../../../services/sidebar/sidebar.service"; import { SidebarService } from "../../../../../../services/sidebar/sidebar.service";
import { ActivatedRoute } from "@angular/router"; import { ActivatedRoute } from "@angular/router";
import { AchievementListQuery, AchievementTypeQuery, GameServerListQuery, LevelListQuery, Query } from "../../../../../../models/graphql/query.model"; import {
AchievementListQuery,
AchievementTypeQuery,
GameServerListQuery,
LevelListQuery,
Query
} from "../../../../../../models/graphql/query.model";
import { catchError, debounceTime, takeUntil } from "rxjs/operators"; import { catchError, debounceTime, takeUntil } from "rxjs/operators";
import { LazyLoadEvent, MenuItem } from "primeng/api"; import { LazyLoadEvent, MenuItem } from "primeng/api";
import { Table } from "primeng/table"; import { Table } from "primeng/table";
@ -33,8 +39,6 @@ export class AchievementComponent extends ComponentWithTable implements OnInit,
public achievements: Achievement[] = []; public achievements: Achievement[] = [];
public loading = true; public loading = true;
public isEditingNew: boolean = false;
public filterForm!: FormGroup<{ public filterForm!: FormGroup<{
id: FormControl<number | null>, id: FormControl<number | null>,
name: FormControl<string | null>, name: FormControl<string | null>,
@ -81,7 +85,10 @@ export class AchievementComponent extends ComponentWithTable implements OnInit,
private data: DataService, private data: DataService,
private sidebar: SidebarService, private sidebar: SidebarService,
private route: ActivatedRoute) { private route: ActivatedRoute) {
super("achievement", ["id", "name", "description", "attribute", "operator", "value"]); super("achievement", ["id", "name", "description", "attribute", "operator", "value"],
(oldElement: Achievement, newElement: Achievement) => {
return oldElement.name === newElement.name;
});
} }
public ngOnInit(): void { public ngOnInit(): void {

View File

@ -27,7 +27,7 @@
class="icon-btn btn" (click)="resetFilters()"> class="icon-btn btn" (click)="resetFilters()">
</button> </button>
<app-data-import-and-export name="achievements" [(data)]="rules" <app-data-import-and-export name="achievements" [(data)]="rules"
[callback]="callback"></app-data-import-and-export> [callback]="callback" [validator]="validator"></app-data-import-and-export>
</div> </div>
</div> </div>
</ng-template> </ng-template>

View File

@ -1,7 +1,7 @@
import { Component, OnDestroy, OnInit } from "@angular/core"; import { Component, OnDestroy, OnInit } from "@angular/core";
import { DataService } from "../../../../../../services/data/data.service"; import { DataService } from "../../../../../../services/data/data.service";
import { ActivatedRoute, Router } from "@angular/router"; import { ActivatedRoute, Router } from "@angular/router";
import { AutoRoleRule, AutoRoleRuleFilter } from "../../../../../../models/data/auto_role.model"; import { AutoRole, AutoRoleRule, AutoRoleRuleFilter } from "../../../../../../models/data/auto_role.model";
import { Guild } from "../../../../../../models/data/discord.model"; import { Guild } from "../../../../../../models/data/discord.model";
import { LazyLoadEvent, MenuItem } from "primeng/api"; import { LazyLoadEvent, MenuItem } from "primeng/api";
import { User } from "../../../../../../models/data/user.model"; import { User } from "../../../../../../models/data/user.model";
@ -40,7 +40,6 @@ export class AutoRolesRulesComponent extends ComponentWithTable implements OnIni
autoRoleId!: number; autoRoleId!: number;
clonedUsers: { [s: string]: User; } = {}; clonedUsers: { [s: string]: User; } = {};
isEditingNew: boolean = false;
newAutoRoleTemplate: AutoRoleRule = { newAutoRoleTemplate: AutoRoleRule = {
createdAt: "", createdAt: "",
@ -81,7 +80,10 @@ export class AutoRolesRulesComponent extends ComponentWithTable implements OnIni
private route: ActivatedRoute, private route: ActivatedRoute,
private router: Router private router: Router
) { ) {
super("auto-role-rules", ["id", "role", "emoji"]); super("auto-role-rules", ["id", "role", "emoji"], (oldElement: AutoRoleRule, newElement: AutoRoleRule) => {
return oldElement.autoRole?.id === newElement.autoRole?.id &&
oldElement.roleId === newElement.roleId;
});
} }
public getEmojiUrl(name: string): string { public getEmojiUrl(name: string): string {

View File

@ -28,7 +28,7 @@
class="icon-btn btn" (click)="resetFilters()"> class="icon-btn btn" (click)="resetFilters()">
</button> </button>
<app-data-import-and-export name="achievements" [(data)]="auto_roles" <app-data-import-and-export name="achievements" [(data)]="auto_roles"
[callback]="callback"></app-data-import-and-export> [callback]="callback" [validator]="validator"></app-data-import-and-export>
</div> </div>
</div> </div>
</ng-template> </ng-template>

View File

@ -36,7 +36,6 @@ export class AutoRolesComponent extends ComponentWithTable implements OnInit, On
loading = true; loading = true;
clonedUsers: { [s: string]: User; } = {}; clonedUsers: { [s: string]: User; } = {};
isEditingNew: boolean = false;
newAutoRoleTemplate: AutoRole = { newAutoRoleTemplate: AutoRole = {
createdAt: "", createdAt: "",
@ -77,7 +76,10 @@ export class AutoRolesComponent extends ComponentWithTable implements OnInit, On
private sidebar: SidebarService, private sidebar: SidebarService,
private route: ActivatedRoute private route: ActivatedRoute
) { ) {
super("auto-role", ["id", "channel_id", "channel_name", "message_id", "rule_count"]); super("auto-role", ["id", "channel_id", "channel_name", "message_id", "rule_count"], (oldElement: AutoRole, newElement: AutoRole) => {
return oldElement.channelId === newElement.channelId &&
oldElement.messageId === newElement.messageId;
});
} }
public ngOnInit(): void { public ngOnInit(): void {

View File

@ -31,7 +31,7 @@
class="icon-btn btn" (click)="resetFilters()"> class="icon-btn btn" (click)="resetFilters()">
</button> </button>
<app-data-import-and-export name="levels" [(data)]="levels" <app-data-import-and-export name="levels" [(data)]="levels"
[callback]="callback"></app-data-import-and-export> [callback]="callback" [validator]="validator"></app-data-import-and-export>
</div> </div>
</div> </div>
</ng-template> </ng-template>

View File

@ -19,247 +19,247 @@ import { Table } from "primeng/table";
import { User } from "../../../../../../models/data/user.model"; import { User } from "../../../../../../models/data/user.model";
import { LevelMutationResult, UpdateUserMutationResult } from "../../../../../../models/graphql/result.model"; import { LevelMutationResult, UpdateUserMutationResult } from "../../../../../../models/graphql/result.model";
import { Mutations } from "../../../../../../models/graphql/mutations.model"; import { Mutations } from "../../../../../../models/graphql/mutations.model";
import { Subject, throwError } from "rxjs"; import { forkJoin, Subject, throwError } from "rxjs";
import { Server } from "../../../../../../models/data/server.model"; import { Server } from "../../../../../../models/data/server.model";
import { UserDTO } from "../../../../../../models/auth/auth-user.dto"; import { UserDTO } from "../../../../../../models/auth/auth-user.dto";
import { ComponentWithTable } from "../../../../../../base/component-with-table"; import { ComponentWithTable } from "../../../../../../base/component-with-table";
@Component({ @Component({
selector: "app-levels", selector: "app-levels",
templateUrl: "./levels.component.html", templateUrl: "./levels.component.html",
styleUrls: ["./levels.component.scss"] styleUrls: ["./levels.component.scss"]
}) })
export class LevelsComponent extends ComponentWithTable implements OnInit, OnDestroy { export class LevelsComponent extends ComponentWithTable implements OnInit, OnDestroy {
public levels: Level[] = []; public levels: Level[] = [];
public loading = true; public loading = true;
public isEditingNew: boolean = false; public filterForm!: FormGroup<{
id: FormControl<number | null>,
name: FormControl<string | null>,
color: FormControl<string | null>,
min_xp: FormControl<number | null>,
permissions: FormControl<string | null>,
}>;
public filterForm!: FormGroup<{ public filter: LevelFilter = {};
id: FormControl<number | null>, public page: Page = {
name: FormControl<string | null>, pageSize: undefined,
color: FormControl<string | null>, pageIndex: undefined
min_xp: FormControl<number | null>, };
permissions: FormControl<string | null>, public sort: Sort = {
}>; sortColumn: undefined,
sortDirection: undefined
};
public filter: LevelFilter = {}; public totalRecords: number = 0;
public page: Page = {
pageSize: undefined,
pageIndex: undefined
};
public sort: Sort = {
sortColumn: undefined,
sortDirection: undefined
};
public totalRecords: number = 0; public clonedLevels: { [s: string]: Level; } = {};
public clonedLevels: { [s: string]: Level; } = {}; private unsubscriber = new Subject<void>();
private server: Server = {};
public user: UserDTO | null = null;
private unsubscriber = new Subject<void>(); query: string = Queries.levelWithHistoryQuery;
private server: Server = {};
public user: UserDTO | null = null;
query: string = Queries.levelWithHistoryQuery; public constructor(
private authService: AuthService,
private spinner: SpinnerService,
private toastService: ToastService,
private confirmDialog: ConfirmationDialogService,
private fb: FormBuilder,
private translate: TranslateService,
private data: DataService,
private sidebar: SidebarService,
private route: ActivatedRoute
) {
super("level", ["id", "name", "color", "min_xp", "permissions"], (oldElement: Level, newElement: Level) => {
return oldElement.name === newElement.name;
});
}
public constructor( public ngOnInit(): void {
private authService: AuthService, this.setFilterForm();
private spinner: SpinnerService, this.data.getServerFromRoute(this.route).then(async server => {
private toastService: ToastService, this.server = server;
private confirmDialog: ConfirmationDialogService, this.loadNextPage();
private fb: FormBuilder, let authUser = await this.authService.getLoggedInUser();
private translate: TranslateService, this.user = authUser?.users?.find(u => u.server == this.server.id) ?? null;
private data: DataService, });
private sidebar: SidebarService, }
private route: ActivatedRoute
) { public ngOnDestroy(): void {
super("level", ["id", "name", "color", "min_xp", "permissions"]); this.unsubscriber.next();
this.unsubscriber.complete();
}
public loadNextPage(): void {
this.loading = true;
this.data.query<LevelListQuery>(Queries.levelQuery, {
serverId: this.server.id, filter: this.filter, page: this.page, sort: this.sort
},
(data: Query) => {
return data.servers[0];
}
).subscribe(data => {
this.totalRecords = data.levelCount;
this.levels = data.levels;
this.spinner.hideSpinner();
this.loading = false;
});
}
public setFilterForm(): void {
this.filterForm = this.fb.group({
id: new FormControl<number | null>(null),
name: new FormControl<string | null>(null),
color: new FormControl<string | null>(null),
min_xp: new FormControl<number | null>(null),
permissions: new FormControl<string | null>(null)
});
this.filterForm.valueChanges.pipe(
takeUntil(this.unsubscriber),
debounceTime(600)
).subscribe(changes => {
if (changes.id) {
this.filter.id = changes.id;
} else {
this.filter.id = undefined;
}
if (changes.name) {
this.filter.name = changes.name;
} else {
this.filter.name = undefined;
}
if (this.page.pageSize)
this.page.pageSize = 10;
if (this.page.pageIndex)
this.page.pageIndex = 0;
this.loadNextPage();
});
}
public newLevelTemplate: Level = {
createdAt: "",
modifiedAt: ""
};
public nextPage(event: LazyLoadEvent): void {
this.page.pageSize = event.rows ?? 0;
if (event.first != null && event.rows != null)
this.page.pageIndex = event.first / event.rows;
this.sort.sortColumn = event.sortField ?? undefined;
this.sort.sortDirection = event.sortOrder === 1 ? SortDirection.ASC : event.sortOrder === -1 ? SortDirection.DESC : SortDirection.ASC;
this.loadNextPage();
}
public resetFilters(): void {
this.filterForm.reset();
}
public onRowEditInit(table: Table, user: User, index: number): void {
this.clonedLevels[index] = { ...user };
}
public override onRowEditSave(newLevel: Level, index: number): void {
if (this.isEditingNew && JSON.stringify(newLevel) === JSON.stringify(this.newLevelTemplate)) {
this.isEditingNew = false;
this.levels.splice(index, 1);
return;
} }
public ngOnInit(): void { if (!newLevel.id && !this.isEditingNew || !newLevel.minXp && !newLevel?.name && !newLevel?.permissions) {
this.setFilterForm(); return;
this.data.getServerFromRoute(this.route).then(async server => {
this.server = server;
this.loadNextPage();
let authUser = await this.authService.getLoggedInUser();
this.user = authUser?.users?.find(u => u.server == this.server.id) ?? null;
});
} }
public ngOnDestroy(): void { if (this.isEditingNew) {
this.unsubscriber.next(); this.spinner.showSpinner();
this.unsubscriber.complete(); this.data.mutation<LevelMutationResult>(Mutations.createLevel, {
} name: newLevel.name,
color: newLevel.color,
public loadNextPage(): void { minXp: newLevel.minXp,
this.loading = true; permissions: newLevel.permissions,
this.data.query<LevelListQuery>(Queries.levelQuery, { serverId: this.server.id
serverId: this.server.id, filter: this.filter, page: this.page, sort: this.sort }
}, ).pipe(catchError(err => {
(data: Query) => { this.isEditingNew = false;
return data.servers[0]; this.spinner.hideSpinner();
} return throwError(err);
).subscribe(data => { })).subscribe(result => {
this.totalRecords = data.levelCount; this.isEditingNew = false;
this.levels = data.levels; this.spinner.hideSpinner();
this.spinner.hideSpinner(); this.toastService.success(this.translate.instant("view.server.levels.message.level_create"), this.translate.instant("view.server.levels.message.level_create_d", { name: result.level.createLevel?.name }));
this.loading = false;
});
}
public setFilterForm(): void {
this.filterForm = this.fb.group({
id: new FormControl<number | null>(null),
name: new FormControl<string | null>(null),
color: new FormControl<string | null>(null),
min_xp: new FormControl<number | null>(null),
permissions: new FormControl<string | null>(null)
});
this.filterForm.valueChanges.pipe(
takeUntil(this.unsubscriber),
debounceTime(600)
).subscribe(changes => {
if (changes.id) {
this.filter.id = changes.id;
} else {
this.filter.id = undefined;
}
if (changes.name) {
this.filter.name = changes.name;
} else {
this.filter.name = undefined;
}
if (this.page.pageSize)
this.page.pageSize = 10;
if (this.page.pageIndex)
this.page.pageIndex = 0;
this.loadNextPage();
});
}
public newLevelTemplate: Level = {
createdAt: "",
modifiedAt: ""
};
public nextPage(event: LazyLoadEvent): void {
this.page.pageSize = event.rows ?? 0;
if (event.first != null && event.rows != null)
this.page.pageIndex = event.first / event.rows;
this.sort.sortColumn = event.sortField ?? undefined;
this.sort.sortDirection = event.sortOrder === 1 ? SortDirection.ASC : event.sortOrder === -1 ? SortDirection.DESC : SortDirection.ASC;
this.loadNextPage(); this.loadNextPage();
});
return;
} }
public resetFilters(): void { this.spinner.showSpinner();
this.filterForm.reset(); this.data.mutation<UpdateUserMutationResult>(Mutations.updateLevel, {
id: newLevel.id,
name: newLevel.name,
color: newLevel.color,
minXp: newLevel.minXp,
permissions: newLevel.permissions
}
).pipe(catchError(err => {
this.spinner.hideSpinner();
return throwError(err);
})).subscribe(_ => {
this.spinner.hideSpinner();
this.toastService.success(this.translate.instant("view.server.levels.message.level_update"), this.translate.instant("view.server.levels.message.level_update_d", { name: newLevel.name }));
this.loadNextPage();
});
}
public onRowEditCancel(index: number): void {
if (this.isEditingNew) {
this.levels.splice(index, 1);
delete this.clonedLevels[index];
this.isEditingNew = false;
return;
} }
public onRowEditInit(table: Table, user: User, index: number): void { this.levels[index] = this.clonedLevels[index];
this.clonedLevels[index] = { ...user }; delete this.clonedLevels[index];
} }
public override onRowEditSave(newLevel: Level, index: number): void {
if (this.isEditingNew && JSON.stringify(newLevel) === JSON.stringify(this.newLevelTemplate)) {
this.isEditingNew = false;
this.levels.splice(index, 1);
return;
}
if (!newLevel.id && !this.isEditingNew || !newLevel.minXp && !newLevel?.name && !newLevel?.permissions) {
return;
}
if (this.isEditingNew) {
this.spinner.showSpinner();
this.data.mutation<LevelMutationResult>(Mutations.createLevel, {
name: newLevel.name,
color: newLevel.color,
minXp: newLevel.minXp,
permissions: newLevel.permissions,
serverId: this.server.id
}
).pipe(catchError(err => {
this.isEditingNew = false;
this.spinner.hideSpinner();
return throwError(err);
})).subscribe(result => {
this.isEditingNew = false;
this.spinner.hideSpinner();
this.toastService.success(this.translate.instant("view.server.levels.message.level_create"), this.translate.instant("view.server.levels.message.level_create_d", { name: result.level.createLevel?.name }));
this.loadNextPage();
});
return;
}
public deleteLevel(level: Level): void {
this.confirmDialog.confirmDialog(
this.translate.instant("view.server.levels.message.level_delete"), this.translate.instant("view.server.levels.message.level_delete_q", { name: level.name }),
() => {
this.spinner.showSpinner(); this.spinner.showSpinner();
this.data.mutation<UpdateUserMutationResult>(Mutations.updateLevel, { this.data.mutation<LevelMutationResult>(Mutations.deleteLevel, {
id: newLevel.id, id: level.id
name: newLevel.name, }
color: newLevel.color,
minXp: newLevel.minXp,
permissions: newLevel.permissions
}
).pipe(catchError(err => { ).pipe(catchError(err => {
this.spinner.hideSpinner(); this.spinner.hideSpinner();
return throwError(err); return throwError(err);
})).subscribe(_ => { })).subscribe(l => {
this.spinner.hideSpinner(); this.spinner.hideSpinner();
this.toastService.success(this.translate.instant("view.server.levels.message.level_update"), this.translate.instant("view.server.levels.message.level_update_d", { name: newLevel.name })); this.toastService.success(this.translate.instant("view.server.levels.message.level_deleted"), this.translate.instant("view.server.levels.message.level_deleted_d", { name: level.name }));
this.loadNextPage(); this.loadNextPage();
}); });
});
}
} public addLevel(table: Table): void {
const newLevel = JSON.parse(JSON.stringify(this.newLevelTemplate));
public onRowEditCancel(index: number): void { this.levels = [newLevel, ...this.levels];
if (this.isEditingNew) {
this.levels.splice(index, 1);
delete this.clonedLevels[index];
this.isEditingNew = false;
return;
}
this.levels[index] = this.clonedLevels[index]; table.initRowEdit(newLevel);
delete this.clonedLevels[index];
}
public deleteLevel(level: Level): void { const index = this.levels.findIndex(l => l.id == newLevel.id);
this.confirmDialog.confirmDialog( this.onRowEditInit(table, newLevel, index);
this.translate.instant("view.server.levels.message.level_delete"), this.translate.instant("view.server.levels.message.level_delete_q", { name: level.name }),
() => {
this.spinner.showSpinner();
this.data.mutation<LevelMutationResult>(Mutations.deleteLevel, {
id: level.id
}
).pipe(catchError(err => {
this.spinner.hideSpinner();
return throwError(err);
})).subscribe(l => {
this.spinner.hideSpinner();
this.toastService.success(this.translate.instant("view.server.levels.message.level_deleted"), this.translate.instant("view.server.levels.message.level_deleted_d", { name: level.name }));
this.loadNextPage();
});
});
}
public addLevel(table: Table): void { this.isEditingNew = true;
const newLevel = JSON.parse(JSON.stringify(this.newLevelTemplate)); }
this.levels = [newLevel, ...this.levels];
table.initRowEdit(newLevel);
const index = this.levels.findIndex(l => l.id == newLevel.id);
this.onRowEditInit(table, newLevel, index);
this.isEditingNew = true;
}
} }

View File

@ -34,7 +34,6 @@ export class MembersComponent extends ComponentWithTable implements OnInit, OnDe
loading = true; loading = true;
clonedUsers: { [s: string]: User; } = {}; clonedUsers: { [s: string]: User; } = {};
isEditingNew: boolean = false;
newUserTemplate: User = { newUserTemplate: User = {
discordId: 0, discordId: 0,

View File

@ -28,7 +28,7 @@
class="icon-btn btn" (click)="resetFilters()"> class="icon-btn btn" (click)="resetFilters()">
</button> </button>
<app-data-import-and-export name="achievements" [(data)]="shortRoleNames" <app-data-import-and-export name="achievements" [(data)]="shortRoleNames"
[callback]="callback"></app-data-import-and-export> [callback]="callback" [validator]="validator"></app-data-import-and-export>
</div> </div>
</div> </div>
</ng-template> </ng-template>

View File

@ -33,8 +33,6 @@ export class ShortRoleNamesComponent extends ComponentWithTable implements OnIni
public shortRoleNames: ShortRoleName[] = []; public shortRoleNames: ShortRoleName[] = [];
public loading = true; public loading = true;
public isEditingNew: boolean = false;
public filterForm!: FormGroup<{ public filterForm!: FormGroup<{
id: FormControl<number | null>, id: FormControl<number | null>,
shortName: FormControl<string | null>, shortName: FormControl<string | null>,
@ -75,7 +73,10 @@ export class ShortRoleNamesComponent extends ComponentWithTable implements OnIni
private sidebar: SidebarService, private sidebar: SidebarService,
private route: ActivatedRoute private route: ActivatedRoute
) { ) {
super("short-role-names", ["id", "name", "role", "position"]); super("short-role-names", ["id", "name", "role", "position"], (oldElement: ShortRoleName, newElement: ShortRoleName) => {
return oldElement.shortName === newElement.shortName &&
oldElement.roleId === newElement.roleId;
});
} }
public ngOnInit(): void { public ngOnInit(): void {