Added mod selector
This commit is contained in:
parent
fa348553a1
commit
1d02e3e436
@ -73,7 +73,7 @@ class ScoreSearchController(
|
|||||||
val count_miss: Int?,
|
val count_miss: Int?,
|
||||||
val date: String?,
|
val date: String?,
|
||||||
val max_combo: Int?,
|
val max_combo: Int?,
|
||||||
val mods: Int?,
|
val mods: List<String>?,
|
||||||
val perfect: Boolean?,
|
val perfect: Boolean?,
|
||||||
val pp: Double?,
|
val pp: Double?,
|
||||||
val rank: String?,
|
val rank: String?,
|
||||||
|
|||||||
@ -46,7 +46,7 @@ class ScoreSearchSchemaController(
|
|||||||
InternalSchemaField("count_miss", "Misses", Category.score, Type.number, false, "missed hits in score", databaseField = SCORES.COUNT_MISS),
|
InternalSchemaField("count_miss", "Misses", Category.score, Type.number, false, "missed hits in score", databaseField = SCORES.COUNT_MISS),
|
||||||
InternalSchemaField("date", "Date", Category.score, Type.datetime, true, "when score was achieved", databaseField = SCORES.DATE),
|
InternalSchemaField("date", "Date", Category.score, Type.datetime, true, "when score was achieved", databaseField = SCORES.DATE),
|
||||||
InternalSchemaField("max_combo", "Max Combo", Category.score, Type.number, false, "highest combo in score", databaseField = SCORES.MAX_COMBO),
|
InternalSchemaField("max_combo", "Max Combo", Category.score, Type.number, false, "highest combo in score", databaseField = SCORES.MAX_COMBO),
|
||||||
InternalSchemaField("mods", "Mods", Category.score, Type.number, false, "game modifiers used", databaseField = SCORES.MODS),
|
InternalSchemaField("mods", "Mods", Category.score, Type.mods, false, "game modifiers used", databaseField = SCORES.MODS),
|
||||||
InternalSchemaField("perfect", "Perfect", Category.score, Type.boolean, false, "if score is a full combo", databaseField = SCORES.PERFECT),
|
InternalSchemaField("perfect", "Perfect", Category.score, Type.boolean, false, "if score is a full combo", databaseField = SCORES.PERFECT),
|
||||||
InternalSchemaField("pp", "Score PP", Category.score, Type.number, true, "performance points for score", databaseField = SCORES.PP),
|
InternalSchemaField("pp", "Score PP", Category.score, Type.number, true, "performance points for score", databaseField = SCORES.PP),
|
||||||
InternalSchemaField("rank", "Rank", Category.score, Type.grade, false, "score grade", databaseField = SCORES.RANK),
|
InternalSchemaField("rank", "Rank", Category.score, Type.grade, false, "score grade", databaseField = SCORES.RANK),
|
||||||
@ -109,7 +109,7 @@ class ScoreSearchSchemaController(
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum class Type {
|
enum class Type {
|
||||||
number, string, flag, grade, boolean, datetime, playtime
|
number, string, flag, grade, boolean, datetime, playtime, mods
|
||||||
}
|
}
|
||||||
|
|
||||||
data class SearchSchema(
|
data class SearchSchema(
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import com.nisemoe.generated.tables.references.BEATMAPS
|
|||||||
import com.nisemoe.generated.tables.references.SCORES
|
import com.nisemoe.generated.tables.references.SCORES
|
||||||
import com.nisemoe.generated.tables.references.USERS
|
import com.nisemoe.generated.tables.references.USERS
|
||||||
import com.nisemoe.nise.Format
|
import com.nisemoe.nise.Format
|
||||||
|
import com.nisemoe.nise.osu.Mod
|
||||||
import com.nisemoe.nise.service.AuthService
|
import com.nisemoe.nise.service.AuthService
|
||||||
import org.jooq.*
|
import org.jooq.*
|
||||||
import org.jooq.impl.DSL
|
import org.jooq.impl.DSL
|
||||||
@ -164,7 +165,7 @@ class ScoreSearchService(
|
|||||||
count_miss = it.get(SCORES.COUNT_MISS),
|
count_miss = it.get(SCORES.COUNT_MISS),
|
||||||
date = it.get(SCORES.DATE)?.let { it1 -> Format.formatLocalDateTime(it1) },
|
date = it.get(SCORES.DATE)?.let { it1 -> Format.formatLocalDateTime(it1) },
|
||||||
max_combo = it.get(SCORES.MAX_COMBO),
|
max_combo = it.get(SCORES.MAX_COMBO),
|
||||||
mods = it.get(SCORES.MODS),
|
mods = Mod.parseModCombination(it.get(SCORES.MODS, Int::class.java)),
|
||||||
perfect = it.get(SCORES.PERFECT),
|
perfect = it.get(SCORES.PERFECT),
|
||||||
pp = it.get(SCORES.PP)?.roundToInt()?.toDouble(),
|
pp = it.get(SCORES.PP)?.roundToInt()?.toDouble(),
|
||||||
rank = it.get(SCORES.RANK),
|
rank = it.get(SCORES.RANK),
|
||||||
@ -248,6 +249,7 @@ class ScoreSearchService(
|
|||||||
"grade" -> buildGradeCondition(field as Field<String>, predicate.operator.operatorType, predicate.value)
|
"grade" -> buildGradeCondition(field as Field<String>, predicate.operator.operatorType, predicate.value)
|
||||||
"datetime" -> buildDatetimeCondition(field as Field<String>, predicate.operator.operatorType, predicate.value)
|
"datetime" -> buildDatetimeCondition(field as Field<String>, predicate.operator.operatorType, predicate.value)
|
||||||
"playtime" -> buildNumberCondition(field as Field<Double>, predicate.operator.operatorType, predicate.value.toDouble())
|
"playtime" -> buildNumberCondition(field as Field<Double>, predicate.operator.operatorType, predicate.value.toDouble())
|
||||||
|
"mods" -> buildNumberCondition(field as Field<Double>, predicate.operator.operatorType, predicate.value.toDouble())
|
||||||
else -> throw IllegalArgumentException("Invalid field type")
|
else -> throw IllegalArgumentException("Invalid field type")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -148,6 +148,9 @@
|
|||||||
✗
|
✗
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
<ng-container *ngIf="column.type == 'mods'">
|
||||||
|
{{ getValue(entry, column.name) }}
|
||||||
|
</ng-container>
|
||||||
<ng-container *ngIf="column.type == 'playtime'">
|
<ng-container *ngIf="column.type == 'playtime'">
|
||||||
{{ formatDuration(getValue(entry, column.name)) }}
|
{{ formatDuration(getValue(entry, column.name)) }}
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|||||||
@ -21,7 +21,7 @@ export interface SchemaField {
|
|||||||
name: string;
|
name: string;
|
||||||
shortName: string;
|
shortName: string;
|
||||||
category: 'user' | 'score' | 'beatmap' | 'metrics';
|
category: 'user' | 'score' | 'beatmap' | 'metrics';
|
||||||
type: 'number' | 'string' | 'flag' | 'grade' | 'boolean' | 'datetime' | 'playtime';
|
type: 'number' | 'string' | 'flag' | 'grade' | 'boolean' | 'datetime' | 'playtime' | 'mods';
|
||||||
validOperators: Operator[];
|
validOperators: Operator[];
|
||||||
active: boolean;
|
active: boolean;
|
||||||
description: string;
|
description: string;
|
||||||
@ -80,7 +80,7 @@ export class SearchComponent implements OnInit {
|
|||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
public downloadFilesService: DownloadFilesService) { }
|
public downloadFilesService: DownloadFilesService) { }
|
||||||
|
|
||||||
currentSchemaVersion = 2
|
currentSchemaVersion = 2;
|
||||||
|
|
||||||
isError = false;
|
isError = false;
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
@ -135,7 +135,7 @@ export class SearchComponent implements OnInit {
|
|||||||
switch (fieldType) {
|
switch (fieldType) {
|
||||||
case 'number':
|
case 'number':
|
||||||
return ['=', '>', '<', '>=', '<=', '!=']
|
return ['=', '>', '<', '>=', '<=', '!=']
|
||||||
.map((operatorType: String) => ({operatorType: operatorType, acceptsValues: 'any'}) as Operator);
|
.map((operatorType: String) => ({operatorType: operatorType, acceptsValues: 'number'}) as Operator);
|
||||||
case 'string':
|
case 'string':
|
||||||
return ['=', 'contains', 'like']
|
return ['=', 'contains', 'like']
|
||||||
.map((operatorType: String) => ({operatorType: operatorType, acceptsValues: 'any'}) as Operator);
|
.map((operatorType: String) => ({operatorType: operatorType, acceptsValues: 'any'}) as Operator);
|
||||||
@ -148,6 +148,9 @@ export class SearchComponent implements OnInit {
|
|||||||
case 'grade':
|
case 'grade':
|
||||||
return ['=', '!=']
|
return ['=', '!=']
|
||||||
.map((operatorType: String) => ({operatorType: operatorType, acceptsValues: 'grade'}) as Operator);
|
.map((operatorType: String) => ({operatorType: operatorType, acceptsValues: 'grade'}) as Operator);
|
||||||
|
case 'mods':
|
||||||
|
return ['=', '!=']
|
||||||
|
.map((operatorType: String) => ({operatorType: operatorType, acceptsValues: 'mods'}) as Operator);
|
||||||
case 'datetime':
|
case 'datetime':
|
||||||
return ['before', 'after']
|
return ['before', 'after']
|
||||||
.map((operatorType: String) => ({operatorType: operatorType, acceptsValues: 'datetime'}) as Operator);
|
.map((operatorType: String) => ({operatorType: operatorType, acceptsValues: 'datetime'}) as Operator);
|
||||||
|
|||||||
@ -4,9 +4,9 @@ import {NgForOf, NgIf} from "@angular/common";
|
|||||||
import {QueryComponent} from "../query/query.component";
|
import {QueryComponent} from "../query/query.component";
|
||||||
import {SchemaField} from "../../../app/search/search.component";
|
import {SchemaField} from "../../../app/search/search.component";
|
||||||
|
|
||||||
export type FieldType = 'number' | 'string' | 'flag' | 'grade' | 'boolean' | 'datetime' | 'playtime';
|
export type FieldType = 'number' | 'string' | 'flag' | 'grade' | 'boolean' | 'datetime' | 'playtime' | 'mods';
|
||||||
export type OperatorType = '=' | '>' | '<' | 'contains' | 'like' | '>=' | '<=' | '!=';
|
export type OperatorType = '=' | '>' | '<' | 'contains' | 'like' | '>=' | '<=' | '!=';
|
||||||
export type ValueType = 'any' | 'boolean' | 'flag' | 'grade' | 'datetime';
|
export type ValueType = 'any' | 'boolean' | 'flag' | 'grade' | 'datetime' | 'number' | 'mods';
|
||||||
|
|
||||||
export interface Predicate {
|
export interface Predicate {
|
||||||
field: SchemaField | null;
|
field: SchemaField | null;
|
||||||
|
|||||||
@ -44,6 +44,18 @@
|
|||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<dialog #modsSelector style="background-color: #161616; color: white; border: solid 2px #1e1e1e; padding: 15px">
|
||||||
|
<div>
|
||||||
|
<label *ngFor="let mod of osuMods | keyvalue">
|
||||||
|
<input type="checkbox" [value]="mod.value" [checked]="isModSelected(predicate, mod.value)" (change)="onModChange($event, predicate)">
|
||||||
|
{{ mod.key }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<div class="text-center">
|
||||||
|
<button (click)="modsSelector.close()">OK</button>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
|
||||||
<ng-container *ngIf="predicate.operator">
|
<ng-container *ngIf="predicate.operator">
|
||||||
|
|
||||||
@ -51,6 +63,17 @@
|
|||||||
<input [(ngModel)]="predicate.value" type="text" placeholder="Value" [disabled]="!predicate.field" (change)="this.queryChanged.emit()">
|
<input [(ngModel)]="predicate.value" type="text" placeholder="Value" [disabled]="!predicate.field" (change)="this.queryChanged.emit()">
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container *ngIf="predicate.operator.acceptsValues == 'number'">
|
||||||
|
<input inputmode="numeric" [(ngModel)]="predicate.value" type="number" placeholder="Value" [disabled]="!predicate.field" (change)="this.queryChanged.emit()">
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container *ngIf="predicate.operator.acceptsValues == 'mods'">
|
||||||
|
<a (click)="modsSelector.show()" style="padding: 2px; border: 1px dashed gray; cursor: pointer; margin-left: 5px; margin-right: 5px">
|
||||||
|
<span *ngIf="getSelectedMods(predicate).length === 0">Select mods</span>
|
||||||
|
<span *ngIf="getSelectedMods(predicate).length > 0">{{ getSelectedMods(predicate).join(', ') }}</span>
|
||||||
|
</a>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
<ng-container *ngIf="predicate.operator.acceptsValues == 'boolean'">
|
<ng-container *ngIf="predicate.operator.acceptsValues == 'boolean'">
|
||||||
|
|
||||||
<select [(ngModel)]="predicate.value" [disabled]="!predicate.field" (change)="this.queryChanged.emit()">
|
<select [(ngModel)]="predicate.value" [disabled]="!predicate.field" (change)="this.queryChanged.emit()">
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import {Component, EventEmitter, Input, Output} from '@angular/core';
|
import {Component, EventEmitter, Input, Output} from '@angular/core';
|
||||||
import {FieldType, Operator, Predicate, Query} from "../query-builder/query-builder.component";
|
import {FieldType, Operator, Predicate, Query} from "../query-builder/query-builder.component";
|
||||||
import {JsonPipe, NgForOf, NgIf} from "@angular/common";
|
import {JsonPipe, KeyValuePipe, NgForOf, NgIf} from "@angular/common";
|
||||||
import {FormsModule, ReactiveFormsModule} from "@angular/forms";
|
import {FormsModule, ReactiveFormsModule} from "@angular/forms";
|
||||||
import {v4 as uuidv4} from 'uuid';
|
import {v4 as uuidv4} from 'uuid';
|
||||||
import {SchemaField} from "../../../app/search/search.component";
|
import {SchemaField} from "../../../app/search/search.component";
|
||||||
@ -13,7 +13,8 @@ import {SchemaField} from "../../../app/search/search.component";
|
|||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
NgIf,
|
NgIf,
|
||||||
JsonPipe
|
JsonPipe,
|
||||||
|
KeyValuePipe
|
||||||
],
|
],
|
||||||
templateUrl: './query.component.html',
|
templateUrl: './query.component.html',
|
||||||
styleUrl: './query.component.css'
|
styleUrl: './query.component.css'
|
||||||
@ -81,4 +82,42 @@ export class QueryComponent {
|
|||||||
this.queryChanged.emit();
|
this.queryChanged.emit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
osuMods: { [key: string]: number } = {
|
||||||
|
"NF": 1,
|
||||||
|
"EZ": 2,
|
||||||
|
"HD": 8,
|
||||||
|
"HR": 16,
|
||||||
|
"SD": 32,
|
||||||
|
"DT": 64,
|
||||||
|
"RL": 128,
|
||||||
|
"HT": 256,
|
||||||
|
"NC": 512,
|
||||||
|
"FL": 1024
|
||||||
|
};
|
||||||
|
|
||||||
|
onModChange(event: any, predicate: Predicate) {
|
||||||
|
const modValue = parseInt(event.target.value);
|
||||||
|
const currentMods = predicate.value as number || 0;
|
||||||
|
|
||||||
|
if (event.target.checked) {
|
||||||
|
predicate.value = currentMods | modValue;
|
||||||
|
} else {
|
||||||
|
predicate.value = currentMods & ~modValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.queryChanged.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
getSelectedMods(predicate: Predicate): string[] {
|
||||||
|
const currentMods = predicate.value as number || 0;
|
||||||
|
return Object.entries(this.osuMods)
|
||||||
|
.filter(([_, value]) => (currentMods & value) !== 0)
|
||||||
|
.map(([key]) => key);
|
||||||
|
}
|
||||||
|
|
||||||
|
isModSelected(predicate: Predicate, modValue: number): boolean {
|
||||||
|
const currentMods = predicate.value as number || 0;
|
||||||
|
return (currentMods & modValue) !== 0;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user