diff --git a/nise-backend/src/main/kotlin/com/nisemoe/nise/controller/SearchController.kt b/nise-backend/src/main/kotlin/com/nisemoe/nise/controller/SearchController.kt index 042a059..fd35d6f 100644 --- a/nise-backend/src/main/kotlin/com/nisemoe/nise/controller/SearchController.kt +++ b/nise-backend/src/main/kotlin/com/nisemoe/nise/controller/SearchController.kt @@ -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"), diff --git a/nise-backend/src/main/kotlin/com/nisemoe/nise/database/ScoreService.kt b/nise-backend/src/main/kotlin/com/nisemoe/nise/database/ScoreService.kt index c6dd58c..4740ece 100644 --- a/nise-backend/src/main/kotlin/com/nisemoe/nise/database/ScoreService.kt +++ b/nise-backend/src/main/kotlin/com/nisemoe/nise/database/ScoreService.kt @@ -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 { + val judgements = dslContext.selectFrom(SCORES_JUDGEMENTS) + .where(SCORES_JUDGEMENTS.SCORE_ID.eq(scoreId)) + .fetchInto(ScoresJudgementsRecord::class.java) + + val errorDistribution = mutableMapOf>() + 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 + ) + } + } + } \ No newline at end of file diff --git a/nise-frontend/src/app/search/search.component.html b/nise-frontend/src/app/search/search.component.html index c269bbf..d9c4f2f 100644 --- a/nise-frontend/src/app/search/search.component.html +++ b/nise-frontend/src/app/search/search.component.html @@ -9,7 +9,7 @@
@@ -46,7 +46,7 @@
- +
diff --git a/nise-frontend/src/app/search/search.component.ts b/nise-frontend/src/app/search/search.component.ts index f30add1..81c38bd 100644 --- a/nise-frontend/src/app/search/search.component.ts +++ b/nise-frontend/src/app/search/search.component.ts @@ -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; + } diff --git a/nise-frontend/src/app/view-score/view-score.component.ts b/nise-frontend/src/app/view-score/view-score.component.ts index 769aafb..9dbe4e4 100644 --- a/nise-frontend/src/app/view-score/view-score.component.ts +++ b/nise-frontend/src/app/view-score/view-score.component.ts @@ -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]));