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("replay_id", "Replay ID", Category.score, Type.number, false, "identifier for replay"),
|
||||||
InternalSchemaField("score", "Score", Category.score, Type.number, false, "score value"),
|
InternalSchemaField("score", "Score", Category.score, Type.number, false, "score value"),
|
||||||
InternalSchemaField("ur", "UR", Category.score, Type.number, false, "unstable rate"),
|
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("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 hit window"),
|
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("snaps", "Snaps", Category.score, Type.number, false, "rapid cursor movements"),
|
||||||
InternalSchemaField("adjusted_ur", "Adj. UR", Category.score, Type.number, true, "adjusted unstable rate"),
|
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"),
|
InternalSchemaField("mean_error", "Mean Error", Category.score, Type.number, false, "average timing error"),
|
||||||
|
|||||||
@ -1,10 +1,8 @@
|
|||||||
package com.nisemoe.nise.database
|
package com.nisemoe.nise.database
|
||||||
|
|
||||||
|
import com.nisemoe.generated.tables.records.ScoresJudgementsRecord
|
||||||
import com.nisemoe.generated.tables.records.ScoresRecord
|
import com.nisemoe.generated.tables.records.ScoresRecord
|
||||||
import com.nisemoe.generated.tables.references.BEATMAPS
|
import com.nisemoe.generated.tables.references.*
|
||||||
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.nise.*
|
import com.nisemoe.nise.*
|
||||||
import com.nisemoe.nise.osu.Mod
|
import com.nisemoe.nise.osu.Mod
|
||||||
import com.nisemoe.nise.service.AuthService
|
import com.nisemoe.nise.service.AuthService
|
||||||
@ -374,9 +372,11 @@ class ScoreService(
|
|||||||
val judgementsRecord = dslContext.select(SCORES.JUDGEMENTS)
|
val judgementsRecord = dslContext.select(SCORES.JUDGEMENTS)
|
||||||
.from(SCORES)
|
.from(SCORES)
|
||||||
.where(SCORES.ID.eq(scoreId))
|
.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!!)
|
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">
|
<ng-container *ngFor="let field of fields">
|
||||||
<div *ngIf="field.category === category">
|
<div *ngIf="field.category === category">
|
||||||
<label>
|
<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>
|
{{ field.name }} <span class="text-muted" style="margin-left: 6px">{{ field.description }}</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@ -46,7 +46,7 @@
|
|||||||
<div class="text-center mt-2">
|
<div class="text-center mt-2">
|
||||||
<button (click)="exportSettings()">Export settings</button>
|
<button (click)="exportSettings()">Export settings</button>
|
||||||
<button (click)="fileInput.click()" style="margin-left: 5px">Import 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>
|
||||||
<div class="text-center mt-1">
|
<div class="text-center mt-1">
|
||||||
<button (click)="search()" class="mb-2" style="font-size: 18px">Search</button>
|
<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 {RouterLink} from "@angular/router";
|
||||||
import {CalculatePageRangePipe} from "../../corelib/calculate-page-range.pipe";
|
import {CalculatePageRangePipe} from "../../corelib/calculate-page-range.pipe";
|
||||||
import {DownloadFilesService} from "../../corelib/service/download-files.service";
|
import {DownloadFilesService} from "../../corelib/service/download-files.service";
|
||||||
import {LocalCacheService} from "../../corelib/service/local-cache.service";
|
|
||||||
|
|
||||||
interface SchemaField {
|
interface SchemaField {
|
||||||
name: string;
|
name: string;
|
||||||
@ -99,7 +98,6 @@ interface SearchResponseEntry {
|
|||||||
export class SearchComponent implements OnInit {
|
export class SearchComponent implements OnInit {
|
||||||
|
|
||||||
constructor(private httpClient: HttpClient,
|
constructor(private httpClient: HttpClient,
|
||||||
private localCacheService: LocalCacheService,
|
|
||||||
public downloadFilesService: DownloadFilesService) { }
|
public downloadFilesService: DownloadFilesService) { }
|
||||||
|
|
||||||
isError = false;
|
isError = false;
|
||||||
@ -124,29 +122,24 @@ export class SearchComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private loadPreviousFromLocalStorage() {
|
private loadPreviousFromLocalStorage() {
|
||||||
const storedQueries = localStorage.getItem('search_queries');
|
const storedQueries = localStorage.getItem('search_settings');
|
||||||
if (storedQueries) {
|
if (storedQueries) {
|
||||||
this.queries = JSON.parse(storedQueries);
|
let queries1 = JSON.parse(storedQueries);
|
||||||
} else {
|
this.queries = queries1.queries;
|
||||||
this.queries = [];
|
this.sortingOrder = queries1.sortingOrder;
|
||||||
}
|
|
||||||
|
|
||||||
// Load active/inactive status from localStorage
|
|
||||||
const storedStatus = localStorage.getItem('columns_status');
|
|
||||||
if (storedStatus) {
|
|
||||||
const statusMap = JSON.parse(storedStatus);
|
|
||||||
this.fields.forEach(field => {
|
this.fields.forEach(field => {
|
||||||
if (statusMap.hasOwnProperty(field.name)) {
|
if (queries1.columns.hasOwnProperty(field.name)) {
|
||||||
field.active = statusMap[field.name];
|
field.active = queries1.columns[field.name];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
} else {
|
||||||
|
this.queries = [];
|
||||||
this.sortingOrder = {
|
this.sortingOrder = {
|
||||||
field: 'user_id',
|
field: 'user_id',
|
||||||
order: 'ASC'
|
order: 'ASC'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
deselectEntireFieldCategory(categoryName: string): void {
|
deselectEntireFieldCategory(categoryName: string): void {
|
||||||
this.fields.forEach(field => {
|
this.fields.forEach(field => {
|
||||||
@ -154,7 +147,7 @@ export class SearchComponent implements OnInit {
|
|||||||
field.active = false;
|
field.active = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.saveColumnsStatusToLocalStorage();
|
this.saveSettingsToLocalStorage();
|
||||||
}
|
}
|
||||||
|
|
||||||
selectEntireFieldCategory(categoryName: string): void {
|
selectEntireFieldCategory(categoryName: string): void {
|
||||||
@ -163,7 +156,7 @@ export class SearchComponent implements OnInit {
|
|||||||
field.active = true;
|
field.active = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.saveColumnsStatusToLocalStorage();
|
this.saveSettingsToLocalStorage();
|
||||||
}
|
}
|
||||||
|
|
||||||
mapSchemaFieldsToFields(): Field[] {
|
mapSchemaFieldsToFields(): Field[] {
|
||||||
@ -175,36 +168,55 @@ export class SearchComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
updateLocalStorage(): void {
|
private serializeSettings() {
|
||||||
console.warn('Updating local storage');
|
return {
|
||||||
localStorage.setItem('search_queries', JSON.stringify(this.queries));
|
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 {
|
exportSettings(): void {
|
||||||
const settings = {
|
const settings = this.serializeSettings();
|
||||||
queries: this.queries,
|
this.downloadFilesService.downloadJSON(settings as any, 'nise-settings');
|
||||||
sorting: this.sortingOrder
|
}
|
||||||
} as any;
|
|
||||||
this.downloadFilesService.downloadJSON(settings, '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[] {
|
getColumns(): string[] {
|
||||||
return this.fields.map(field => field.name);
|
return this.fields.map(field => field.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
importSettings(event: any): void {
|
uploadSettingsFile(event: any): void {
|
||||||
const file = event.target.files[0];
|
const file = event.target.files[0];
|
||||||
if (file) {
|
if (file) {
|
||||||
const fileReader = new FileReader();
|
const fileReader = new FileReader();
|
||||||
fileReader.onload = (e) => {
|
fileReader.onload = (e) => {
|
||||||
try {
|
try {
|
||||||
const json = JSON.parse(fileReader.result as string);
|
const json = JSON.parse(fileReader.result as string);
|
||||||
if (this.verifySchema(json)) {
|
this.importSettings(json);
|
||||||
this.queries = json.queries;
|
|
||||||
this.sortingOrder = json.sorting;
|
|
||||||
} else {
|
|
||||||
console.error('Invalid file schema');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error parsing JSON', error);
|
console.error('Error parsing JSON', error);
|
||||||
}
|
}
|
||||||
@ -215,16 +227,7 @@ export class SearchComponent implements OnInit {
|
|||||||
|
|
||||||
verifySchema(json: any): boolean {
|
verifySchema(json: any): boolean {
|
||||||
// TODO: Implement schema verification logic here
|
// TODO: Implement schema verification logic here
|
||||||
return 'queries' in json && 'sorting' in json;
|
return 'queries' in json && 'sortingOrder' in json && 'columns' 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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
search(pageNumber: number = 1): void {
|
search(pageNumber: number = 1): void {
|
||||||
@ -243,12 +246,12 @@ export class SearchComponent implements OnInit {
|
|||||||
this.response = response;
|
this.response = response;
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
},
|
},
|
||||||
error: (error) => {
|
error: () => {
|
||||||
this.isError = true;
|
this.isError = true;
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.updateLocalStorage();
|
this.saveSettingsToLocalStorage();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add this method to the SearchComponent class
|
// Add this method to the SearchComponent class
|
||||||
@ -258,4 +261,5 @@ export class SearchComponent implements OnInit {
|
|||||||
|
|
||||||
protected readonly countryCodeToFlag = countryCodeToFlag;
|
protected readonly countryCodeToFlag = countryCodeToFlag;
|
||||||
protected readonly Math = Math;
|
protected readonly Math = Math;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -110,7 +110,7 @@ export class ViewScoreComponent implements OnInit {
|
|||||||
|
|
||||||
let errorDistribution = Object.entries(this.replayData.error_distribution);
|
let errorDistribution = Object.entries(this.replayData.error_distribution);
|
||||||
|
|
||||||
if (errorDistribution.length > 1) {
|
if (errorDistribution.length >= 1) {
|
||||||
const sortedEntries = errorDistribution
|
const sortedEntries = errorDistribution
|
||||||
.sort((a, b) => parseInt(a[0]) - parseInt(b[0]));
|
.sort((a, b) => parseInt(a[0]) - parseInt(b[0]));
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user