Skip to content

Commit 001f9e3

Browse files
authored
Merge pull request #175 from Toastbrot236/level-edit
Implements a level edit page.
2 parents 6f306b5 + 89c0eff commit 001f9e3

17 files changed

+654
-46
lines changed

src/app/api/client.service.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {Contest} from "./types/contests/contest";
1616
import {Score} from "./types/levels/score";
1717
import { LevelRelations } from './types/levels/level-relations';
1818
import { Asset } from './types/asset';
19+
import { LevelUpdateRequest } from './types/levels/level-update-request';
1920

2021
export const defaultPageSize: number = 40;
2122

@@ -55,6 +56,18 @@ export class ClientService extends ApiImplementation {
5556
getLevelById(id: number) {
5657
return this.http.get<Level>(`/levels/id/${id}`);
5758
}
59+
60+
updateLevelById(id: number, data: LevelUpdateRequest, isCurator: boolean) {
61+
return this.http.patch<Level>(`${isCurator ? '/admin' : ''}/levels/id/${id}`, data);
62+
}
63+
64+
updateLevelIconById(id: number, hash: string, isCurator: boolean) {
65+
return this.http.patch<Level>(`${isCurator ? '/admin' : ''}/levels/id/${id}`, {iconHash: hash});
66+
}
67+
68+
deleteLevelById(id: number, isModerator: boolean) {
69+
return this.http.delete<Level>(`${isModerator ? '/admin' : ''}/levels/id/${id}`);
70+
}
5871

5972
getScoresForLevel(id: number, scoreType: number, skip: number, count: number = defaultPageSize, params: Params | null = null) {
6073
return this.http.get<ListWithData<Score>>(`/scores/${id}/${scoreType}`, {params: this.setPageQuery(params, skip, count)});
@@ -135,6 +148,14 @@ export class ClientService extends ApiImplementation {
135148
return this.http.post<Response>(`/levels/id/${id}/setAsOverride`, null);
136149
}
137150

151+
teamPickLevel(id: number) {
152+
return this.http.post<Response>(`/admin/levels/id/${id}/teamPick`, null);
153+
}
154+
155+
unTeamPickLevel(id: number) {
156+
return this.http.post<Response>(`/admin/levels/id/${id}/removeTeamPick`, null);
157+
}
158+
138159
uploadAsset(hash: string, data: ArrayBuffer) {
139160
return this.http.post<Asset>(`/assets/${hash}`, data);
140161
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { GameVersion } from "../game-version";
2+
3+
export interface LevelUpdateRequest {
4+
title: string | undefined;
5+
description: string | undefined;
6+
7+
gameVersion: GameVersion | undefined;
8+
isReUpload: boolean | undefined;
9+
originalPublisher: string | undefined;
10+
}

src/app/api/types/levels/level.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,20 @@ export interface Level {
55
title: string;
66
description: string;
77
iconHash: string;
8+
publisher: User | undefined;
9+
originalPublisher: string | undefined;
10+
isReUpload: boolean;
11+
teamPicked: boolean;
12+
dateTeamPicked: Date;
13+
gameVersion: number;
14+
score: number;
15+
slotType: number;
816
publishDate: Date;
917
updateDate: Date;
18+
1019
booRatings: number;
1120
yayRatings: number;
1221
hearts: number;
1322
totalPlays: number;
1423
uniquePlays: number;
15-
publisher: User | undefined;
16-
teamPicked: boolean;
17-
gameVersion: number;
18-
score: number;
19-
slotType: number;
2024
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export enum UserRoles {
2+
Admin = 127,
3+
Moderator = 96,
4+
Curator = 64,
5+
Trusted = 1,
6+
User = 0,
7+
Restricted = -126,
8+
Banned = 127,
9+
}

src/app/app.routes.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@ export const routes: Routes = [
2929
},
3030
...alias("level/:id/:slug", "slot/:id/:slug"),
3131
...alias("level/:id", "slot/:id",),
32+
{
33+
path: 'level/:id/:slug/edit',
34+
loadComponent: () => import('./pages/level-edit/level-edit.component').then(x => x.LevelEditComponent),
35+
data: {title: "Edit Level"},
36+
},
37+
...alias("level/:id/:slug/edit", "slot/:id/:slug/edit"),
3238
{
3339
path: 'photos',
3440
loadComponent: () => import('./pages/photo-listing/photo-listing.component').then(x => x.PhotoListingComponent),

src/app/components/ui/dialog.component.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,27 @@
1-
import {Component, ElementRef, OnDestroy, OnInit, ViewChild} from '@angular/core';
1+
import {Component, ElementRef, OnDestroy, OnInit, Output, ViewChild, EventEmitter} from '@angular/core';
22

33
@Component({
44
selector: 'app-dialog',
55
imports: [],
66
template: `
7-
<dialog #dialog class="backdrop:backdrop-brightness-50 text-foreground bg-red bg-opacity-0 overflow-y-clip" tabindex="-1">
7+
<dialog (close)="close()" #dialog class="backdrop:backdrop-brightness-50 flex flex-row flex-grow text-foreground bg-container-background bg-opacity-0 overflow-y-clip" tabindex="-1">
88
<ng-content></ng-content>
99
</dialog>
1010
`,
1111
styles: ``
1212
})
1313
export class DialogComponent implements OnInit, OnDestroy {
1414
@ViewChild('dialog', { static: true }) dialog!: ElementRef<HTMLDialogElement>;
15+
@Output() onDialogClose = new EventEmitter;
1516

1617
ngOnInit(): void {
1718
this.dialog.nativeElement.showModal();
1819
}
1920

21+
close(): void {
22+
this.onDialogClose.emit();
23+
}
24+
2025
ngOnDestroy(): void {
2126
this.dialog.nativeElement.close();
2227
}

src/app/components/ui/form/button.component.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,24 +10,27 @@ import { NgClass } from "@angular/common";
1010
NgClass
1111
],
1212
template: `
13-
<button class="rounded px-4 py-1.5 hover:brightness-110 active:brightness-95 transition-[filter] disabled:grayscale"
14-
[ngClass]="color" [type]=type [disabled]="!enabled">
13+
<button class="flex flex-row justify-center rounded px-4 py-1.5 hover:brightness-110 active:brightness-95 transition-[filter] disabled:grayscale"
14+
[ngClass]="color + ' ' + width" [type]=type [disabled]="!enabled">
1515
@if (icon) {
16-
<fa-icon [icon]="icon" [ngClass]="text && text.length > 0 ? 'mr-1' : ''"></fa-icon>
16+
<fa-icon class="right-1" [icon]="icon" [ngClass]="text && text.length > 0 ? 'mr-2' : ''"></fa-icon>
17+
}
18+
@if (text) {
19+
<div class="flex flex-row flex-grow justify-center"> {{ text }} </div>
1720
}
18-
{{ text }}
1921
</button>
2022
`
2123
})
2224
export class ButtonComponent {
2325
// metadata
24-
@Input({required: true}) text: string = "Button";
26+
@Input() text: string | undefined;
2527
@Input() icon: IconProp | undefined;
2628
@Input() color: string = "bg-secondary";
2729

2830
@Input() type: "submit" | "reset" | "button" = "button";
2931

3032
@Input() enabled: boolean = true;
33+
@Input() width: string = "";
3134

3235
// actions
3336
@Input() routerLink: any[] | string | null | undefined
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import {Component, Injectable, Input} from '@angular/core';
2+
import {ReactiveFormsModule} from "@angular/forms";
3+
import { NgClass } from "@angular/common";
4+
5+
@Component({
6+
selector: 'app-dropdown-menu',
7+
imports: [
8+
ReactiveFormsModule,
9+
NgClass
10+
],
11+
template: `
12+
<div class="flex flex-row content-center space-x-1 group relative">
13+
<ng-content select="[trigger]"></ng-content>
14+
<div class="absolute z-1 flex flex-col gap-y-1.5 px-5 py-2.5 rounded bg-header-background
15+
border-4 border-backdrop border-solid"
16+
[ngClass]="(showMenu ? '' : 'hidden ') + offsets + ' w-' + width">
17+
<ng-content select="[content]"></ng-content>
18+
</div>
19+
</div>
20+
`
21+
})
22+
@Injectable({
23+
providedIn: 'root'
24+
})
25+
export class DropdownMenuComponent {
26+
@Input() offsets: string = ""
27+
@Input({required: true}) width: number = 0;
28+
29+
@Input() showMenu: boolean = false;
30+
}

src/app/components/ui/form/radio-button.component.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ import {FormGroup, ReactiveFormsModule} from "@angular/forms";
77
ReactiveFormsModule
88
],
99
template: `
10-
<div [formGroup]="form" class="min-w-full flex flex-row content-center justify-start rounded-md px-2 py-1 hover-within:outline-2 hover-within:outline hover-within:outline-secondary-bright max-w-fit transition-[outline]">
11-
<input type="radio" [id]=id [formControlName]="ctrlName" [name]="ctrlName" [value]=value class="outline-hidden bg-teritary placeholder:text-gentle placeholder:italic" [required]="required">
10+
<div [formGroup]="form" class="cursor-pointer min-w-full flex flex-row content-center justify-start rounded-md px-2 py-1 hover-within:outline-2 hover-within:outline hover-within:outline-secondary-bright max-w-fit transition-[outline]">
11+
<input type="radio" [id]=id [formControlName]="ctrlName" [name]="ctrlName" [value]=value class="cursor-pointer outline-hidden bg-teritary placeholder:text-gentle placeholder:italic" [required]="required">
1212
1313
@if (label.length > 0) {
14-
<label [for]=id class="text-base hyphens-manual ml-3">{{label}}</label>
14+
<label [for]=id class="text-base hyphens-manual ml-3 cursor-pointer">{{label}}</label>
1515
}
1616
</div>
1717
`

src/app/components/ui/form/textarea.component.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,22 @@ import {FormGroup, ReactiveFormsModule} from "@angular/forms";
66
@Component({
77
selector: 'app-textarea',
88
imports: [
9-
FaIconComponent,
10-
ReactiveFormsModule
11-
],
9+
FaIconComponent,
10+
ReactiveFormsModule
11+
],
1212
template: `
1313
@if (label.length > 0) {
1414
<label [for]=ctrlName class="text-sm">{{label}}</label>
1515
}
1616
<div [formGroup]="form" class="min-w-full flex group rounded-md px-4 py-1.5 bg-teritary focus-within:outline-2 focus-within:outline focus-within:outline-secondary-bright max-w-fit text-nowrap transition-[outline]">
17-
<fa-icon [icon]="icon" class="text-gentle mr-2 group-focus-within:text-secondary-bright transition-colors"></fa-icon>
18-
<textarea [id]=ctrlName [formControlName]="ctrlName" [placeholder]="placeholder" class="grow min-h-20 outline-hidden wrap-break-word bg-teritary placeholder:text-gentle placeholder:italic" [required]="required"></textarea>
17+
<div class="flex flex-col align-center mr-2 gap-y-2">
18+
<fa-icon [icon]="icon" class="flex flex-row justify-center mt-1 text-gentle group-focus-within:text-secondary-bright transition-colors"></fa-icon>
19+
@if (showMaxLength == true) {
20+
<p>{{maxLength - (form.get(ctrlName)?.value?.length ?? 0)}}</p>
21+
}
22+
</div>
23+
<textarea [id]=ctrlName [formControlName]="ctrlName" [maxLength]="maxLength" [placeholder]="placeholder" [rows]="defaultRowCount"
24+
class="grow min-w-10 min-h-20 outline-hidden wrap-break-word bg-teritary placeholder:text-gentle placeholder:italic" [required]="required"></textarea>
1925
</div>
2026
`
2127
})
@@ -28,4 +34,9 @@ export class TextAreaComponent {
2834
@Input({required: true}) ctrlName: string = "";
2935

3036
@Input() required: boolean = true;
37+
38+
@Input() maxLength: number = 4096;
39+
@Input() showMaxLength: boolean = false;
40+
41+
@Input() defaultRowCount: number = 4;
3142
}

0 commit comments

Comments
 (0)