Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement marking levels as re-uploads #73

Merged
merged 4 commits into from
Apr 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
255 changes: 124 additions & 131 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions src/app/api/types/level-edit-request.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {GameVersion} from "./game-version";

export interface LevelEditRequest {
title: string | undefined;
description: string | undefined;
iconHash: string | undefined;
gameVersion: string | undefined;
originalPublisher: string | undefined;
isReUpload: boolean | undefined;
}
4 changes: 3 additions & 1 deletion src/app/api/types/level.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ export interface Level {
hearts: number
totalPlays: number
uniquePlays: number
publisher: User
publisher: User | undefined
originalPublisher: string | undefined
isReUpload: boolean
teamPicked: boolean
gameVersion: number
score: number
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@
<fa-icon [icon]="faCircleCheck" class="pr-1"></fa-icon>
</tooltip>
<level-statistics [level]="_level" class="text-sm"></level-statistics>
<p-gentle *ngIf="_level.publisher">by <user-link [user]="_level.publisher" class="pl-0.5"></user-link></p-gentle>
<p-gentle>by <user-link [user]="_level.publisher" class="pl-0.5" *ngIf="_level.publisher"
[ngClass]="_level.isReUpload ? 'line-through' : ''"></user-link>
<tooltip class="pl-0.5"
[text]="'This level is a re-upload, originally created by ' + _level.originalPublisher + '.'">
<span>{{ _level.originalPublisher }}</span>
</tooltip></p-gentle>
<p-gentle>Created in {{getGameVersion(_level.gameVersion)}} <date [date]="_level.publishDate"></date></p-gentle>
</div>
</div>
Expand Down
9 changes: 7 additions & 2 deletions src/app/pages/edit-level/edit-level.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@

<div *ngIf="ownUser?.role == UserRoles.Admin">
<page-header class="text-2xl">Admin Settings</page-header>
<form-dropdown name="Game Version" [options]="gameVersions" [(value)]="gameVersion"></form-dropdown>
<div class="flex flex-col gap-y-2.5">
<form-dropdown name="Game Version" [options]="gameVersions" [(value)]="gameVersion"></form-dropdown>
<form-checkbox name="Mark as re-upload" [(value)]="isReUpload" [icon]="faClone"></form-checkbox>
<form-input name="Original Publisher" [(value)]="originalPublisher" [icon]="faUser"></form-input>
</div>
</div>
<divider></divider>

Expand All @@ -29,7 +33,8 @@
<div class="flex gap-3.5">
<div class="group flex h-auto w-44 max-md:w-24 justify-center items-center aspect-square my-2.5">
<input type="file" (change)="iconChanged($event).then()" class="hidden" id="pfpInput">
<label for="pfpInput" class="flex rounded-full bg-[#000000aa] h-auto w-44 max-md:w-24 justify-center items-center aspect-square z-10 opacity-0 group-hover:opacity-100 transition-opacity">
<label for="pfpInput"
class="flex rounded-full bg-[#000000aa] h-auto w-44 max-md:w-24 justify-center items-center aspect-square z-10 opacity-0 group-hover:opacity-100 transition-opacity">
<fa-icon [icon]="faPencil" class="text-4xl"></fa-icon>
</label>
<img [src]="GetAssetImageLink(iconHash)"
Expand Down
17 changes: 16 additions & 1 deletion src/app/pages/edit-level/edit-level.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,14 @@ import {catchError, of} from "rxjs";
import {HttpErrorResponse} from "@angular/common/http";
import {AuthService} from "../../api/auth.service";
import {ApiClient, GetAssetImageLink} from "../../api/api-client.service";
import {faCancel, faCertificate, faFloppyDisk, faPencil, faTrash} from "@fortawesome/free-solid-svg-icons";
import {
faCancel,
faCertificate, faClone,
faFloppyDisk,
faPencil,
faTrash,
faUser
} from "@fortawesome/free-solid-svg-icons";
import {LevelEditRequest} from "../../api/types/level-edit-request";
import {UserRoles} from "../../api/types/user-roles";
import {DropdownOption} from "../../components/form-dropdown/form-dropdown.component";
Expand All @@ -24,6 +31,8 @@ export class EditLevelComponent implements OnInit {
title: string = "";
description: string = "";
gameVersion: string = "0";
isReUpload: boolean = false;
originalPublisher: string | undefined = undefined;

gameVersions: DropdownOption[] = [
{
Expand Down Expand Up @@ -77,6 +86,8 @@ export class EditLevelComponent implements OnInit {
this.description = data.description;
this.gameVersion = data.gameVersion.toString();
this.iconHash = data.iconHash;
this.isReUpload = data.isReUpload;
this.originalPublisher = data.originalPublisher;
});
});

Expand All @@ -94,6 +105,8 @@ export class EditLevelComponent implements OnInit {
description: this.description,
iconHash: undefined,
gameVersion: this.gameVersion,
originalPublisher: this.originalPublisher,
isReUpload: this.isReUpload,
}

this.apiClient.EditLevel(payload, this.level.levelId, this.ownUser?.role == UserRoles.Admin);
Expand Down Expand Up @@ -130,4 +143,6 @@ export class EditLevelComponent implements OnInit {
protected readonly faCancel = faCancel;
protected readonly UserRoles = UserRoles;
protected readonly GetAssetImageLink = GetAssetImageLink;
protected readonly faUser = faUser;
protected readonly faClone = faClone;
}
202 changes: 107 additions & 95 deletions src/app/pages/level/level.component.html
Original file line number Diff line number Diff line change
@@ -1,122 +1,134 @@
<page-header-block *ngIf="level">
<div class="flex gap-3.5">
<level-avatar [level]="level" size="h-auto w-44 max-md:w-24"></level-avatar>
<div class="flex-grow">
<div class="font-bold text-3xl mb-1">
{{ level.title.length == 0 ? 'Unnamed Level' : level.title }}
<div class="flex gap-3.5">
<level-avatar [level]="level" size="h-auto w-44 max-md:w-24"></level-avatar>
<div class="flex-grow">
<div class="font-bold text-3xl mb-1">
{{ level.title.length == 0 ? 'Unnamed Level' : level.title }}

<tooltip text="This level has been team picked." *ngIf="level.teamPicked">
<fa-icon [icon]="faCircleCheck" class="text-xl pr-1"></fa-icon>
</tooltip>
<tooltip text="This level has been team picked." *ngIf="level.teamPicked">
<fa-icon [icon]="faCircleCheck" class="text-xl pr-1"></fa-icon>
</tooltip>

<div class="inline-block text-sm font-normal" *ngIf="level.publisher">
<span-gentle>by
<user-link [user]="level.publisher" class="pl-1"></user-link>
</span-gentle>
</div>
<div class="text-sm font-normal">
<span-gentle>Published for {{getGameVersion(level.gameVersion)}} </span-gentle>
<span-gentle>
<date [date]="level.publishDate"></date>
</span-gentle>
<span-gentle *ngIf="level.publishDate != level.updateDate">,
last updated <date [date]="level.updateDate"></date></span-gentle>
</div>
</div>
<div class="inline-block text-sm font-normal">
<span-gentle>by
<user-link [user]="level.publisher" class="pl-1" *ngIf="level.publisher"
[ngClass]="level.isReUpload ? 'line-through' : ''"></user-link>
<tooltip *ngIf="level.isReUpload" class="pl-1"
[text]="'This level is a re-upload, originally created by ' + level.originalPublisher + '.'">
<span>{{ level.originalPublisher }}</span>
</tooltip>
</span-gentle>
</div>
<div class="text-sm font-normal">
<span-gentle>Published for {{ getGameVersion(level.gameVersion) }}</span-gentle>
<span-gentle class="pl-1">
<date [date]="level.publishDate"></date>
</span-gentle>
<span-gentle *ngIf="level.publishDate != level.updateDate">,
last updated
<date [date]="level.updateDate"></date>
</span-gentle>
</div>
</div>

<level-statistics [level]="level" class="inline-block ml-1 mb-1"></level-statistics>
<level-statistics [level]="level" class="inline-block ml-1 mb-1"></level-statistics>

<div class="bg-backdrop rounded px-5 py-2.5 drop-shadow-lg whitespace-pre-wrap text-foreground">
<p>{{ level.description.length == 0 ? 'No description was provided for this level.' : level.description }}</p>
</div>
<div class="bg-backdrop rounded px-5 py-2.5 drop-shadow-lg whitespace-pre-wrap text-foreground">
<p>{{ level.description.length == 0 ? 'No description was provided for this level.' : level.description }}</p>
</div>
</div>
</div>
</div>
</page-header-block>
<page-header-block> <!-- controls row -->
<div class="flex gap-x-2.5 text-foreground">
<primary-button text="Play Now!" [icon]="faPlay" (click)="setAsOverride()" *ngIf="isOwnUserOnline"></primary-button>
<admin-link-button *ngIf="ownUser?.role == UserRoles.Admin" [routerLink]="'/admin/level/' + level?.levelId"></admin-link-button>
<secondary-button *ngIf="ownUser && (ownUser?.userId == level?.publisher?.userId || ownUser?.role == UserRoles.Admin)"
[routerLink]="'/level/' + level?.levelId + '/edit'"
class="w-36" text="Edit Level" [icon]="faPencil"></secondary-button>
</div>
<div class="flex gap-x-2.5 text-foreground">
<primary-button text="Play Now!" [icon]="faPlay" (click)="setAsOverride()"
*ngIf="isOwnUserOnline"></primary-button>
<admin-link-button *ngIf="ownUser?.role == UserRoles.Admin"
[routerLink]="'/admin/level/' + level?.levelId"></admin-link-button>
<secondary-button
*ngIf="ownUser && (ownUser?.userId == level?.publisher?.userId || ownUser?.role == UserRoles.Admin)"
[routerLink]="'/level/' + level?.levelId + '/edit'"
class="w-36" text="Edit Level" [icon]="faPencil"></secondary-button>
</div>
</page-header-block>

<page-header-block *ngIf="!level">
<div class="flex gap-3.5 animate-pulse">
<div class="inline h-44 w-44 bg-secondary rounded-full"></div>
<div>
<div class="inline-block h-8 w-80 bg-secondary rounded-full"></div>
<br>
<div class="inline-block h-4 w-48 bg-secondary rounded-full"></div>
<div class="flex gap-3.5 animate-pulse">
<div class="inline h-44 w-44 bg-secondary rounded-full"></div>
<div>
<div class="inline-block h-8 w-80 bg-secondary rounded-full"></div>
<br>
<div class="inline-block h-4 w-48 bg-secondary rounded-full"></div>

<div class="bg-backdrop rounded px-5 py-2.5 flex">
<div class="inline-block h-4 w-96 bg-secondary rounded-full m-1"></div>
<div class="inline-block h-4 w-48 bg-secondary rounded-full m-1"></div>
</div>
<div class="bg-backdrop rounded px-5 py-2.5 flex">
<div class="inline-block h-4 w-96 bg-secondary rounded-full m-1"></div>
<div class="inline-block h-4 w-48 bg-secondary rounded-full m-1"></div>
</div>
</div>
</div>
</div>
</page-header-block>

<div class="flex flex-row gap-2.5 max-md:flex-col pt-5">
<container class="w-full">
<div class="flex">
<h2 class="text-3xl font-bold flex-grow">Leaderboard</h2>
<form-dropdown *ngIf="level?.gameVersion !== 4" name="Score Type" [options]="scoreTypes" (change)="this.formChanged()" [(value)]="scoreType"></form-dropdown>
</div>
<divider></divider>
<div *ngIf="scores === undefined">
<p>Loading scores...</p>
</div>
<div *ngIf="scores !== undefined && scores!.length === 0" class="text-center">
<p class="font-bold text-2xl">No {{scoreType}}-player scores</p>
<p>
Nobody has beaten this level with {{scoreType}} player{{scoreType == '1' ? '' : 's'}} yet. Go for it!
</p>
</div>
<div *ngFor="let score of scores">
<div class="my-5 px-2.5">
<a [routerLink]="'/score/' + score.scoreId" class="flex items-center">
<div class="text-2xl">
<span *ngIf="score.rank == 1" class="text-rank-gold pr-2">#{{score.rank}}</span>
<span *ngIf="score.rank == 2" class="text-rank-silver pr-2">#{{score.rank}}</span>
<span *ngIf="score.rank == 3" class="text-rank-bronze pr-2">#{{score.rank}}</span>
<container class="w-full">
<div class="flex">
<h2 class="text-3xl font-bold flex-grow">Leaderboard</h2>
<form-dropdown *ngIf="level?.gameVersion !== 4" name="Score Type" [options]="scoreTypes"
(change)="this.formChanged()" [(value)]="scoreType"></form-dropdown>
</div>
<divider></divider>
<div *ngIf="scores === undefined">
<p>Loading scores...</p>
</div>
<div *ngIf="scores !== undefined && scores!.length === 0" class="text-center">
<p class="font-bold text-2xl">No {{ scoreType }}-player scores</p>
<p>
Nobody has beaten this level with {{ scoreType }} player{{ scoreType == '1' ? '' : 's' }} yet. Go for
it!
</p>
</div>
<div *ngFor="let score of scores">
<div class="my-5 px-2.5">
<a [routerLink]="'/score/' + score.scoreId" class="flex items-center">
<div class="text-2xl">
<span *ngIf="score.rank == 1" class="text-rank-gold pr-2">#{{ score.rank }}</span>
<span *ngIf="score.rank == 2" class="text-rank-silver pr-2">#{{ score.rank }}</span>
<span *ngIf="score.rank == 3" class="text-rank-bronze pr-2">#{{ score.rank }}</span>

<span *ngIf="score.rank !== undefined && score.rank > 3"
class="text-rank-other pr-2">#{{score.rank}}</span>
</div>
<span *ngIf="score.rank !== undefined && score.rank > 3"
class="text-rank-other pr-2">#{{ score.rank }}</span>
</div>

<div class="flex flex-col">
<span class="text-lg">{{score.score.toLocaleString(undefined)}} points</span>
<div class="flex flex-col">
<span class="text-lg">{{ score.score.toLocaleString(undefined) }} points</span>

<span class="text-sm">
<span class="text-sm">
Achieved by
<b><user-link [user]="score.players[0]"></user-link></b>
<date [date]="score.scoreSubmitted" class="ml-1"></date>
</span>
</div>
</a>
</div>
</div>
</div>
</a>
</div>
</div>

<div *ngIf="this.scores !== undefined && this.scores!.length % 10 == 0 && this.scores!.length !== 0">
<secondary-button text="Load more" (click)="loadMoreScores()"></secondary-button>
</div>
</container>
<container class="w-full">
<h2 class="text-3xl font-bold">Recent Activity</h2>
<divider></divider>
<div *ngIf="this.scores !== undefined && this.scores!.length % 10 == 0 && this.scores!.length !== 0">
<secondary-button text="Load more" (click)="loadMoreScores()"></secondary-button>
</div>
</container>
<container class="w-full">
<h2 class="text-3xl font-bold">Recent Activity</h2>
<divider></divider>

<div *ngIf="activity">
<div *ngFor="let event of activity.events">
<activity-event [event]="event" [page]="activity" [contextIsLevel]="true"></activity-event>
</div>
</div>
<div *ngIf="activity">
<div *ngFor="let event of activity.events">
<activity-event [event]="event" [page]="activity" [contextIsLevel]="true"></activity-event>
</div>
</div>

<div *ngIf="!activity">
<div *ngFor="let i of GenerateEmptyList(5)">
<activity-event></activity-event>
</div>
</div>
</container>
<div *ngIf="!activity">
<div *ngFor="let i of GenerateEmptyList(5)">
<activity-event></activity-event>
</div>
</div>
</container>
</div>
Loading