Backported hit judgements, fixed bugs
This commit is contained in:
parent
79557b170a
commit
59a2d0448a
@ -207,8 +207,8 @@ class SearchController(
|
||||
InternalSchemaField("replay_id", "Replay ID", Category.score, Type.number, false, "identifier for replay"),
|
||||
InternalSchemaField("score", "Score", Category.score, Type.number, false, "score value"),
|
||||
InternalSchemaField("ur", "UR", Category.score, Type.number, false, "unstable rate"),
|
||||
InternalSchemaField("frametime", "Frame Time", Category.score, Type.number, false, "average frame time during play"),
|
||||
InternalSchemaField("edge_hits", "Edge Hits", Category.score, Type.number, false, "hits at the edge of hit window"),
|
||||
InternalSchemaField("frametime", "Frame Time", Category.score, Type.number, false, "median frame time during play"),
|
||||
InternalSchemaField("edge_hits", "Edge Hits", Category.score, Type.number, false, "hits at the edge of the hitobject (<1px)"),
|
||||
InternalSchemaField("snaps", "Snaps", Category.score, Type.number, false, "rapid cursor movements"),
|
||||
InternalSchemaField("adjusted_ur", "Adj. UR", Category.score, Type.number, true, "adjusted unstable rate"),
|
||||
InternalSchemaField("mean_error", "Mean Error", Category.score, Type.number, false, "average timing error"),
|
||||
|
||||
@ -1,10 +1,8 @@
|
||||
package com.nisemoe.nise.database
|
||||
|
||||
import com.nisemoe.generated.tables.records.ScoresJudgementsRecord
|
||||
import com.nisemoe.generated.tables.records.ScoresRecord
|
||||
import com.nisemoe.generated.tables.references.BEATMAPS
|
||||
import com.nisemoe.generated.tables.references.SCORES
|
||||
import com.nisemoe.generated.tables.references.SCORES_SIMILARITY
|
||||
import com.nisemoe.generated.tables.references.USERS
|
||||
import com.nisemoe.generated.tables.references.*
|
||||
import com.nisemoe.nise.*
|
||||
import com.nisemoe.nise.osu.Mod
|
||||
import com.nisemoe.nise.service.AuthService
|
||||
@ -374,9 +372,11 @@ class ScoreService(
|
||||
val judgementsRecord = dslContext.select(SCORES.JUDGEMENTS)
|
||||
.from(SCORES)
|
||||
.where(SCORES.ID.eq(scoreId))
|
||||
.fetchOneInto(ScoresRecord::class.java) ?: return emptyMap()
|
||||
.fetchOneInto(ScoresRecord::class.java)
|
||||
|
||||
if(judgementsRecord.judgements == null) return emptyMap()
|
||||
if(judgementsRecord?.judgements == null) {
|
||||
return this.getHitDistributionLegacy(scoreId)
|
||||
}
|
||||
|
||||
val judgements = compressJudgements.deserialize(judgementsRecord.judgements!!)
|
||||
|
||||
@ -404,4 +404,33 @@ class ScoreService(
|
||||
}
|
||||
}
|
||||
|
||||
fun getHitDistributionLegacy(scoreId: Int): Map<Int, DistributionEntry> {
|
||||
val judgements = dslContext.selectFrom(SCORES_JUDGEMENTS)
|
||||
.where(SCORES_JUDGEMENTS.SCORE_ID.eq(scoreId))
|
||||
.fetchInto(ScoresJudgementsRecord::class.java)
|
||||
|
||||
val errorDistribution = mutableMapOf<Int, MutableMap<String, Int>>()
|
||||
var totalHits = 0
|
||||
|
||||
judgements.forEach { hit ->
|
||||
val error = (hit.error!!.roundToInt() / 2) * 2
|
||||
val judgementType = hit.type // Assuming this is how you get the judgement type
|
||||
errorDistribution.getOrPut(error) { mutableMapOf("Miss" to 0, "300" to 0, "100" to 0, "50" to 0) }
|
||||
.apply {
|
||||
this[judgementType.toString()] = this.getOrDefault(judgementType.toString(), 0) + 1
|
||||
}
|
||||
totalHits += 1
|
||||
}
|
||||
|
||||
return errorDistribution.mapValues { (_, judgementCounts) ->
|
||||
judgementCounts.values.sum()
|
||||
DistributionEntry(
|
||||
percentageMiss = (judgementCounts.getOrDefault("Miss", 0).toDouble() / totalHits) * 100,
|
||||
percentage300 = (judgementCounts.getOrDefault("300", 0).toDouble() / totalHits) * 100,
|
||||
percentage100 = (judgementCounts.getOrDefault("100", 0).toDouble() / totalHits) * 100,
|
||||
percentage50 = (judgementCounts.getOrDefault("50", 0).toDouble() / totalHits) * 100
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -9,7 +9,7 @@
|
||||
<ng-container *ngFor="let field of fields">
|
||||
<div *ngIf="field.category === category">
|
||||
<label>
|
||||
<input type="checkbox" [(ngModel)]="field.active" (change)="this.saveColumnsStatusToLocalStorage()"/>
|
||||
<input type="checkbox" [(ngModel)]="field.active" (change)="this.saveSettingsToLocalStorage()"/>
|
||||
{{ field.name }} <span class="text-muted" style="margin-left: 6px">{{ field.description }}</span>
|
||||
</label>
|
||||
</div>
|
||||
@ -46,7 +46,7 @@
|
||||
<div class="text-center mt-2">
|
||||
<button (click)="exportSettings()">Export settings</button>
|
||||
<button (click)="fileInput.click()" style="margin-left: 5px">Import settings</button>
|
||||
<input type="file" #fileInput style="display: none" (change)="importSettings($event)" accept=".json">
|
||||
<input type="file" #fileInput style="display: none" (change)="uploadSettingsFile($event)" accept=".json">
|
||||
</div>
|
||||
<div class="text-center mt-1">
|
||||
<button (click)="search()" class="mb-2" style="font-size: 18px">Search</button>
|
||||
|
||||
@ -9,7 +9,6 @@ import {Field, Query, QueryBuilderComponent} from "../../corelib/components/quer
|
||||
import {RouterLink} from "@angular/router";
|
||||
import {CalculatePageRangePipe} from "../../corelib/calculate-page-range.pipe";
|
||||
import {DownloadFilesService} from "../../corelib/service/download-files.service";
|
||||
import {LocalCacheService} from "../../corelib/service/local-cache.service";
|
||||
|
||||
interface SchemaField {
|
||||
name: string;
|
||||
@ -99,7 +98,6 @@ interface SearchResponseEntry {
|
||||
export class SearchComponent implements OnInit {
|
||||
|
||||
constructor(private httpClient: HttpClient,
|
||||
private localCacheService: LocalCacheService,
|
||||
public downloadFilesService: DownloadFilesService) { }
|
||||
|
||||
isError = false;
|
||||
@ -124,28 +122,23 @@ export class SearchComponent implements OnInit {
|
||||
}
|
||||
|
||||
private loadPreviousFromLocalStorage() {
|
||||
const storedQueries = localStorage.getItem('search_queries');
|
||||
const storedQueries = localStorage.getItem('search_settings');
|
||||
if (storedQueries) {
|
||||
this.queries = JSON.parse(storedQueries);
|
||||
} else {
|
||||
this.queries = [];
|
||||
}
|
||||
|
||||
// Load active/inactive status from localStorage
|
||||
const storedStatus = localStorage.getItem('columns_status');
|
||||
if (storedStatus) {
|
||||
const statusMap = JSON.parse(storedStatus);
|
||||
let queries1 = JSON.parse(storedQueries);
|
||||
this.queries = queries1.queries;
|
||||
this.sortingOrder = queries1.sortingOrder;
|
||||
this.fields.forEach(field => {
|
||||
if (statusMap.hasOwnProperty(field.name)) {
|
||||
field.active = statusMap[field.name];
|
||||
if (queries1.columns.hasOwnProperty(field.name)) {
|
||||
field.active = queries1.columns[field.name];
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.queries = [];
|
||||
this.sortingOrder = {
|
||||
field: 'user_id',
|
||||
order: 'ASC'
|
||||
};
|
||||
}
|
||||
|
||||
this.sortingOrder = {
|
||||
field: 'user_id',
|
||||
order: 'ASC'
|
||||
};
|
||||
}
|
||||
|
||||
deselectEntireFieldCategory(categoryName: string): void {
|
||||
@ -154,7 +147,7 @@ export class SearchComponent implements OnInit {
|
||||
field.active = false;
|
||||
}
|
||||
});
|
||||
this.saveColumnsStatusToLocalStorage();
|
||||
this.saveSettingsToLocalStorage();
|
||||
}
|
||||
|
||||
selectEntireFieldCategory(categoryName: string): void {
|
||||
@ -163,7 +156,7 @@ export class SearchComponent implements OnInit {
|
||||
field.active = true;
|
||||
}
|
||||
});
|
||||
this.saveColumnsStatusToLocalStorage();
|
||||
this.saveSettingsToLocalStorage();
|
||||
}
|
||||
|
||||
mapSchemaFieldsToFields(): Field[] {
|
||||
@ -175,36 +168,55 @@ export class SearchComponent implements OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
updateLocalStorage(): void {
|
||||
console.warn('Updating local storage');
|
||||
localStorage.setItem('search_queries', JSON.stringify(this.queries));
|
||||
private serializeSettings() {
|
||||
return {
|
||||
queries: this.queries,
|
||||
sortingOrder: this.sortingOrder,
|
||||
columns: this.getColumnSettings()
|
||||
};
|
||||
}
|
||||
|
||||
private getColumnSettings() {
|
||||
return this.fields.reduce<{ [key: string]: boolean }>((acc, field) => {
|
||||
acc[field.name] = field.active;
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
saveSettingsToLocalStorage(): void {
|
||||
const settings = this.serializeSettings();
|
||||
localStorage.setItem('search_settings', JSON.stringify(settings));
|
||||
}
|
||||
|
||||
exportSettings(): void {
|
||||
const settings = {
|
||||
queries: this.queries,
|
||||
sorting: this.sortingOrder
|
||||
} as any;
|
||||
this.downloadFilesService.downloadJSON(settings, 'nise-settings');
|
||||
const settings = this.serializeSettings();
|
||||
this.downloadFilesService.downloadJSON(settings as any, 'nise-settings');
|
||||
}
|
||||
|
||||
importSettings(json: any): void {
|
||||
if (this.verifySchema(json)) {
|
||||
this.queries = json.queries;
|
||||
this.sortingOrder = json.sortingOrder;
|
||||
this.fields.forEach(field => {
|
||||
if (json.columns.hasOwnProperty(field.name)) {
|
||||
field.active = json.columns[field.name];
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getColumns(): string[] {
|
||||
return this.fields.map(field => field.name);
|
||||
}
|
||||
|
||||
importSettings(event: any): void {
|
||||
uploadSettingsFile(event: any): void {
|
||||
const file = event.target.files[0];
|
||||
if (file) {
|
||||
const fileReader = new FileReader();
|
||||
fileReader.onload = (e) => {
|
||||
try {
|
||||
const json = JSON.parse(fileReader.result as string);
|
||||
if (this.verifySchema(json)) {
|
||||
this.queries = json.queries;
|
||||
this.sortingOrder = json.sorting;
|
||||
} else {
|
||||
console.error('Invalid file schema');
|
||||
}
|
||||
this.importSettings(json);
|
||||
} catch (error) {
|
||||
console.error('Error parsing JSON', error);
|
||||
}
|
||||
@ -215,16 +227,7 @@ export class SearchComponent implements OnInit {
|
||||
|
||||
verifySchema(json: any): boolean {
|
||||
// TODO: Implement schema verification logic here
|
||||
return 'queries' in json && 'sorting' in json;
|
||||
}
|
||||
|
||||
saveColumnsStatusToLocalStorage(): void {
|
||||
const statusMap = this.fields.reduce<{ [key: string]: boolean }>((acc, field) => {
|
||||
acc[field.name] = field.active;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
localStorage.setItem('columns_status', JSON.stringify(statusMap));
|
||||
return 'queries' in json && 'sortingOrder' in json && 'columns' in json;
|
||||
}
|
||||
|
||||
search(pageNumber: number = 1): void {
|
||||
@ -243,12 +246,12 @@ export class SearchComponent implements OnInit {
|
||||
this.response = response;
|
||||
this.isLoading = false;
|
||||
},
|
||||
error: (error) => {
|
||||
error: () => {
|
||||
this.isError = true;
|
||||
this.isLoading = false;
|
||||
}
|
||||
});
|
||||
this.updateLocalStorage();
|
||||
this.saveSettingsToLocalStorage();
|
||||
}
|
||||
|
||||
// Add this method to the SearchComponent class
|
||||
@ -258,4 +261,5 @@ export class SearchComponent implements OnInit {
|
||||
|
||||
protected readonly countryCodeToFlag = countryCodeToFlag;
|
||||
protected readonly Math = Math;
|
||||
|
||||
}
|
||||
|
||||
@ -110,7 +110,7 @@ export class ViewScoreComponent implements OnInit {
|
||||
|
||||
let errorDistribution = Object.entries(this.replayData.error_distribution);
|
||||
|
||||
if (errorDistribution.length > 1) {
|
||||
if (errorDistribution.length >= 1) {
|
||||
const sortedEntries = errorDistribution
|
||||
.sort((a, b) => parseInt(a[0]) - parseInt(b[0]));
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user