Skip to content

Commit

Permalink
more frontend
Browse files Browse the repository at this point in the history
  • Loading branch information
tsa96 committed Dec 24, 2024
1 parent af10b73 commit cf17a98
Show file tree
Hide file tree
Showing 19 changed files with 188 additions and 118 deletions.
4 changes: 3 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,9 @@ module.exports = {
// What the hell is wrong with .flat(2)
'unicorn/no-magic-array-flat-depth': ['off'],
// Stupid CJS/ESM complaints, don't care.
'unicorn/import-style': ['off']
'unicorn/import-style': ['off'],
// Fine in some cases.
'unicorn/no-array-reduce': ['off']
}
},
{
Expand Down
6 changes: 4 additions & 2 deletions apps/frontend/src/app/components/chips/chips.component.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<div class="inputlike flex flex-wrap items-center gap-1.5 px-2 py-1.5">
<div class="inputlike flex flex-wrap items-center gap-1.5 px-1.5 py-1.5">
<button
(click)="op.toggle($event)"
[mTooltip]="available.length > 0 ? 'Add ' + name : null"
Expand All @@ -24,7 +24,9 @@
</p-overlayPanel>
</button>
@for (chip of selected; track $index) {
<div class="flex h-7 items-center gap-1 rounded bg-blue-400 bg-opacity-50 py-2 pl-2.5 pr-1.5 transition-colors hover:bg-opacity-60">
<div
class="flex h-7 items-center gap-1 rounded border border-white border-opacity-5 bg-blue-400 bg-opacity-50 py-2 pl-2.5 pr-1.5 transition-colors hover:bg-opacity-60"
>
<span class="pointer-events-none font-medium text-gray-50">{{ chip }}</span>
<button
(click)="remove(chip)"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<div class="flex h-full flex-col">
<p class="mb-1 text-xl font-medium">{{ type.value }}</p>
<div
class="flex-grow rounded bg-black bg-opacity-25"
class="flex-grow rounded bg-black bg-opacity-25 border inputlike"
id="{{ type.key }}"
cdkDropList
[cdkDropListData]="value[type.key]"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export class MapLeaderboardSelectionComponent implements ControlValueAccessor {
{ type: TrackType.MAIN, label: 'Main' },
{ type: TrackType.BONUS, label: 'Bonus' }
];
protected readonly MapTags = MapTags;
protected readonly MAX_MAP_SUGGESTION_COMMENT_LENGTH =
MAX_MAP_SUGGESTION_COMMENT_LENGTH;

Expand Down Expand Up @@ -114,8 +115,14 @@ export class MapLeaderboardSelectionComponent implements ControlValueAccessor {
this.onTouched();
}

getTags(gamemode: Gamemode) {
return [...(MapTags.get(gamemode) ?? [])];
updateRankedCheckbox(item: MapSubmissionSuggestion, isRanked: boolean) {
item.type = isRanked ? LeaderboardType.RANKED : LeaderboardType.UNRANKED;
this.onChange(this.value);
}

updateTags(item: MapSubmissionSuggestion, tags: MapTag[]) {
item.tags = tags;
this.onChange(this.value);
}

writeValue(value: MapSubmissionSuggestion[] | null): void {
Expand All @@ -139,14 +146,4 @@ export class MapLeaderboardSelectionComponent implements ControlValueAccessor {
registerOnTouched(fn: () => void): void {
this.onTouched = fn;
}

updateRankedCheckbox(item: MapSubmissionSuggestion, isRanked: boolean) {
item.type = isRanked ? LeaderboardType.RANKED : LeaderboardType.UNRANKED;
this.onChange(this.value);
}

updateTags(item: MapSubmissionSuggestion, tags: MapTag[]) {
item.tags = tags;
this.onChange(this.value);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
Add Leaderboard
</button>
<table
class="grid w-full border-collapse gap-1 [grid-template-columns:minmax(10rem,1fr)_minmax(8rem,1fr)_8rem_auto_minmax(1rem,auto)_minmax(8rem,4fr)_minmax(8rem,4fr)_auto]"
class="grid w-full border-collapse grid-cols-[minmax(10rem,1fr)_minmax(8rem,1fr)_8rem_auto_minmax(1rem,auto)_minmax(8rem,4fr)_minmax(8rem,4fr)_auto] gap-1"
[ngClass]="{
'pointer-events-none brightness-50 saturate-50': disabled
}"
Expand Down Expand Up @@ -94,12 +94,7 @@
/>
</td>
<td>
<m-chips
[chips]="getTags(item.gamemode)"
[ngModel]="item.tags"
(ngModelChange)="updateTags(item, $event)"
name="Tags"
/>
<m-chips [chips]="MapTags.get(item.gamemode)" [ngModel]="item.tags" (ngModelChange)="updateTags(item, $event)" name="Tags" />
</td>
<td>
<button type="button" class="btn btn-red" (click)="removeItem($index)">Delete</button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@
}
</div>

<!-- TODO: submitter and review suggestion summary -->

<div class="flex items-center gap-4">
<p [ngClass]="{ 'opacity-50': leaderboard.type === LeaderboardType.HIDDEN }">Tier</p>
<input
Expand All @@ -70,6 +72,15 @@
(onChange)="onChange(value)"
appendTo="body"
/>

<p>Tags</p>
<m-chips
class="flex-grow"
[chips]="MapTags.get(leaderboard.gamemode)"
[ngModel]="leaderboard.tags"
(ngModelChange)="updateTags(leaderboard, $event)"
name="Tags"
/>
</div>
</div>
@if (leaderboard.averageTier) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { Component, forwardRef, Input } from '@angular/core';
import {
Gamemode,
GamemodeInfo,
LeaderboardType,
MapSubmissionApproval,
MapTag,
MapTags,
TrackType,
TrackTypeName
} from '@momentum/constants';
Expand All @@ -16,6 +19,7 @@ import { AccordionItemComponent } from '../../accordion/accordion-item.component
import { SliderComponent } from '../../slider/slider.component';
import { GroupedLeaderboards } from './map-status-form.component';
import { SharedModule } from '../../../shared.module';
import { ChipsComponent } from '../../chips/chips.component';

@Component({
selector: 'm-map-final-approval-form',
Expand All @@ -27,7 +31,8 @@ import { SharedModule } from '../../../shared.module';
ChartModule,
AccordionComponent,
AccordionItemComponent,
SliderComponent
SliderComponent,
ChipsComponent
],
providers: [
{
Expand All @@ -47,6 +52,7 @@ export class MapFinalApprovalFormComponent implements ControlValueAccessor {
];
protected readonly TTName = TrackTypeName;
protected readonly GamemodeInfo = GamemodeInfo;
protected readonly MapTags = MapTags;
protected readonly TierChartOptions: ChartOptions;
protected readonly RatingChartOptions: ChartOptions;

Expand Down Expand Up @@ -82,6 +88,11 @@ export class MapFinalApprovalFormComponent implements ControlValueAccessor {
// Doesn't have an actual value as value, just a getter.
}

updateTags(leaderboard: MapSubmissionApproval, tags: MapTag[]) {
leaderboard.tags = tags;
this.onChange(this.value);
}

setDisabledState(isDisabled: boolean) {
this.disabled = isDisabled;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,14 +117,14 @@ <h2>Final Approval</h2>

<m-map-final-approval-form [groupedLeaderboards]="groupedLeaderboards" [formControl]="finalLeaderboards" />
@if (finalLeaderboards.pristine) {
<p class="ml-auto text-blue-100">
<p class="ml-auto font-medium text-blue-100">
These selections create approved leaderboards, so are heavily validated. At minimum you need an unhidden, tiered leaderboard in at
least one gamemode for each track.
</p>
} @else if (finalLeaderboards.errors) {
<p class="ml-auto text-red-500">{{ finalLeaderboards.errors['error'] }}</p>
<p class="ml-auto font-medium text-red-500">{{ finalLeaderboards.errors['error'] }}</p>
} @else {
<p class="ml-auto text-green-500">Final leaderboards are valid! Good job :)</p>
<p class="ml-auto font-medium text-green-500">Final leaderboards are valid! Good job :)</p>
}
</div>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ import {
MMap,
PagedResponse,
Role,
TrackType
TrackType,
MapTag
} from '@momentum/constants';
import { FormControl, FormGroup } from '@angular/forms';
import { MessageService } from 'primeng/api';
Expand Down Expand Up @@ -47,6 +48,7 @@ export type GroupedLeaderboards = Map<
graphs: { tiers: ChartData; ratings: any };
averageTier?: number;
averageRating?: number;
reviewTags?: Partial<Record<MapTag, number>>;
tier: number | null;
type: Exclude<LeaderboardType, LeaderboardType.IN_SUBMISSION> | null;
}
Expand Down Expand Up @@ -270,6 +272,13 @@ export class MapStatusFormComponent implements OnChanges {
).length;

r.averageRating = +(sumRatings / totalRatings).toFixed(2);

r.reviewTags = reviews
.flatMap(({ tags }) => tags)
.reduce((acc, tag) => {
acc[tag] = (acc[tag] ?? 0) + 1;
return acc;
}, {});
}

return r;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,24 @@
@if (editing) {
<button type="button" class="btn btn-red btn-thin self-end" (click)="canceledEditing.next()">Cancel editing</button>
}
<div class="flex items-center justify-end gap-2">
<button type="button" class="btn btn-blue btn-thin" (click)="suggs.addEmptyItem()">
Add Tier/Ranking Suggestions
<m-icon class="ml-2" icon="tooltip-question-outline" [mTooltip]="suggestionInfo" />
</button>
<ng-template #suggestionInfo>
<div class="prose">
<p>Your suggestions for the tier and/or gameplay quality of each track.</p>
<p><b>Tier</b> is used to decide the final tier, and <b>gameplay rating</b> for whether the track should provide rank points.</p>
</div>
</ng-template>
</div>
<button type="button" class="btn btn-blue btn-thin" (click)="suggs.addEmptyItem()">
Add Tier/Ranking Suggestions
<m-icon class="ml-2" icon="tooltip-question-outline" [mTooltip]="suggestionInfo" />
</button>
<ng-template #suggestionInfo>
<div class="prose p-3">
<p>Your suggestions for the tier, gameplay quality rating, and/or tags for tracks.</p>
<p><b>Tier</b> is the difficulty of the track, from 1 to 10.</p>
<p>
<b>Rating</b> is for overall <i>gameplay</i> quality of the track, from 1 to 10, used to decide whether the track should provide
rank points.
</p>
<p>
<b>Tags</b> highlight specific gameplay aspects of the track. Only suggest a tag that adds to the overall difficulty of that
track; e.g. don't suggest "spins" for T3 or more surf track with a single easy spin.
</p>
</div>
</ng-template>
<button type="submit" class="btn btn-green btn-thin self-end" [disabled]="form.invalid || form.pristine">Post</button>
</div>
</form>
Original file line number Diff line number Diff line change
Expand Up @@ -47,30 +47,43 @@ export class MapReviewFormComponent {
@Output() public readonly reviewPosted = new EventEmitter<void>();
@Output() public readonly canceledEditing = new EventEmitter<void>();

public readonly form = new FormGroup({
mainText: new FormControl<string>('', {
validators: [Validators.required, Validators.maxLength(MAX_REVIEW_LENGTH)]
}),
needsResolving: new FormControl<boolean>(false),
suggestions: new FormControl<MapReviewSuggestion[]>(null, {
validators: [
// FormGroup is constructed at component class ctor but map is undefined
// at that point, and can change over time, so use pass a closure to
// validator that fetches the current map zones on the class whenever
// validator is run.
suggestionsValidator(
() => this.map?.currentVersion?.zones,
SuggestionType.REVIEW
public readonly form = new FormGroup(
{
mainText: new FormControl<string>('', {
validators: [
Validators.required,
Validators.maxLength(MAX_REVIEW_LENGTH)
]
}),
needsResolving: new FormControl<boolean>(false),
suggestions: new FormControl<MapReviewSuggestion[]>(null, {
validators: [
// FormGroup is constructed at component class ctor but map is undefined
// at that point, and can change over time, so use pass a closure to
// validator that fetches the current map zones on the class whenever
// validator is run.
suggestionsValidator(
() => this.map?.currentVersion?.zones,
SuggestionType.REVIEW
)
]
}),
images: new FormControl<File[]>(null, {
validators: [
FileValidators.maxSize(MAX_MAP_IMAGE_SIZE),
FileValidators.extension(['png', 'jpg', 'jpeg'])
]
})
},
(group: FormGroup) =>
Object.entries(group?.controls)
.filter(([k]) => ['mainText', 'suggestions'].includes(k))
.some(([, { dirty, value }]) =>
dirty && Array.isArray(value) ? value.length > 0 : value != null
)
]
}),
images: new FormControl<File[]>(null, {
validators: [
FileValidators.maxSize(MAX_MAP_IMAGE_SIZE),
FileValidators.extension(['png', 'jpg', 'jpeg'])
]
})
});
? null
: { noChanges: true }
);

get mainText() {
return this.form.get('mainText') as FormControl<string>;
Expand Down Expand Up @@ -102,6 +115,15 @@ export class MapReviewFormComponent {
const suggestions =
this.suggestions.value?.length > 0 ? this.suggestions.value : undefined;

// Ignore any empty suggestions
if (suggestions) {
suggestions.forEach(({ tier, gameplayRating, tags }, i) => {
if (tier == null && gameplayRating == null && !tags?.length) {
suggestions.splice(i, 1);
}
});
}

const req = this.editing
? this.mapsService.updateMapReview(this.reviewID, {
mainText: this.mainText.value,
Expand Down
Loading

0 comments on commit cf17a98

Please sign in to comment.