Fixed <select> flickering

This commit is contained in:
nise.moe 2024-02-25 20:12:50 +01:00
parent a055e88063
commit 6129257a3b
4 changed files with 46 additions and 39 deletions

View File

@ -5,17 +5,24 @@ import {HttpClient} from "@angular/common/http";
import {environment} from "../../environments/environment"; import {environment} from "../../environments/environment";
import {countryCodeToFlag, formatDuration} from "../format"; import {countryCodeToFlag, formatDuration} from "../format";
import {OsuGradeComponent} from "../../corelib/components/osu-grade/osu-grade.component"; import {OsuGradeComponent} from "../../corelib/components/osu-grade/osu-grade.component";
import {Field, Query, QueryBuilderComponent} from "../../corelib/components/query-builder/query-builder.component"; import {
FieldType,
Operator,
Query,
QueryBuilderComponent
} from "../../corelib/components/query-builder/query-builder.component";
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 {CuteLoadingComponent} from "../../corelib/components/cute-loading/cute-loading.component"; import {CuteLoadingComponent} from "../../corelib/components/cute-loading/cute-loading.component";
import {Title} from "@angular/platform-browser";
export interface SchemaField { 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';
validOperators: Operator[];
active: boolean; active: boolean;
description: string; description: string;
} }
@ -63,6 +70,7 @@ interface Sorting {
export class SearchComponent implements OnInit { export class SearchComponent implements OnInit {
constructor(private httpClient: HttpClient, constructor(private httpClient: HttpClient,
private title: Title,
public downloadFilesService: DownloadFilesService) { } public downloadFilesService: DownloadFilesService) { }
isError = false; isError = false;
@ -76,10 +84,14 @@ export class SearchComponent implements OnInit {
queries: Query[] | null = null; queries: Query[] | null = null;
ngOnInit(): void { ngOnInit(): void {
this.title.setTitle("/k/ - advanced search");
this.isLoadingSchema = true; this.isLoadingSchema = true;
this.httpClient.get<SchemaResponse>(`${environment.apiUrl}/search/schema`,).subscribe({ this.httpClient.get<SchemaResponse>(`${environment.apiUrl}/search/schema`,).subscribe({
next: (response) => { next: (response) => {
this.fields = response.fields; this.fields = response.fields;
this.fields.forEach(field => {
field.validOperators = this.getOperators(field.type);
})
this.loadPreviousFromLocalStorage(); this.loadPreviousFromLocalStorage();
this.isLoadingSchema = false; this.isLoadingSchema = false;
}, },
@ -89,6 +101,34 @@ export class SearchComponent implements OnInit {
}); });
} }
getOperators(fieldType: FieldType | undefined): Operator[] {
switch (fieldType) {
case 'number':
return ['=', '>', '<', '>=', '<=', '!=']
.map((operatorType: String) => ({operatorType: operatorType, acceptsValues: 'any'}) as Operator);
case 'string':
return ['=', 'contains', 'like']
.map((operatorType: String) => ({operatorType: operatorType, acceptsValues: 'any'}) as Operator);
case 'boolean':
return ['=', '!=']
.map((operatorType: String) => ({operatorType: operatorType, acceptsValues: 'boolean'}) as Operator);
case 'flag':
return ['=', '!=']
.map((operatorType: String) => ({operatorType: operatorType, acceptsValues: 'flag'}) as Operator);
case 'grade':
return ['=', '!=']
.map((operatorType: String) => ({operatorType: operatorType, acceptsValues: 'grade'}) as Operator);
case 'datetime':
return ['before', 'after']
.map((operatorType: String) => ({operatorType: operatorType, acceptsValues: 'datetime'}) as Operator);
case 'playtime':
return ['>', '<', '>=', '<=']
.map((operatorType: String) => ({operatorType: operatorType, acceptsValues: 'any'}) as Operator);
default:
return [];
}
}
private loadPreviousFromLocalStorage() { private loadPreviousFromLocalStorage() {
const storedQueries = localStorage.getItem('search_settings'); const storedQueries = localStorage.getItem('search_settings');
if (storedQueries) { if (storedQueries) {
@ -227,6 +267,6 @@ export class SearchComponent implements OnInit {
protected readonly countryCodeToFlag = countryCodeToFlag; protected readonly countryCodeToFlag = countryCodeToFlag;
protected readonly Math = Math; protected readonly Math = Math;
protected readonly formatDuration = formatDuration; protected readonly formatDuration = formatDuration;
} }

View File

@ -8,13 +8,8 @@ export type FieldType = 'number' | 'string' | 'flag' | 'grade' | 'boolean' | 'da
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';
export interface Field {
name: string;
type: FieldType;
}
export interface Predicate { export interface Predicate {
field: Field | null; field: SchemaField | null;
operator: Operator | null; operator: Operator | null;
value: string | number | null; value: string | number | null;
} }
@ -27,7 +22,7 @@ export interface Operator {
export interface Query { export interface Query {
predicates: Predicate[]; predicates: Predicate[];
logicalOperator: 'AND' | 'OR'; logicalOperator: 'AND' | 'OR';
childQueries?: Query[]; // Optional property for sub-queries childQueries?: Query[];
} }
@Component({ @Component({

View File

@ -30,7 +30,7 @@
<select [disabled]="!predicate.field"> <select [disabled]="!predicate.field">
<option *ngFor="let operator of getOperators(predicate.field?.type)" <option *ngFor="let operator of predicate.field?.validOperators"
[selected]="operator.operatorType === predicate.operator?.operatorType" [selected]="operator.operatorType === predicate.operator?.operatorType"
(click)="predicate.operator = operator; this.queryChanged.emit()"> (click)="predicate.operator = operator; this.queryChanged.emit()">
{{ operator.operatorType }} {{ operator.operatorType }}

View File

@ -32,35 +32,7 @@ export class QueryComponent {
onFieldChange(predicate: Predicate, selectedField: any): void { onFieldChange(predicate: Predicate, selectedField: any): void {
predicate.field = selectedField; predicate.field = selectedField;
predicate.operator = this.getOperators(selectedField.type)[0]; predicate.operator = selectedField.validOperators[0];
}
getOperators(fieldType: FieldType | undefined): Operator[] {
switch (fieldType) {
case 'number':
return ['=', '>', '<', '>=', '<=', '!=']
.map((operatorType: String) => ({operatorType: operatorType, acceptsValues: 'any'}) as Operator);
case 'string':
return ['=', 'contains', 'like']
.map((operatorType: String) => ({operatorType: operatorType, acceptsValues: 'any'}) as Operator);
case 'boolean':
return ['=', '!=']
.map((operatorType: String) => ({operatorType: operatorType, acceptsValues: 'boolean'}) as Operator);
case 'flag':
return ['=', '!=']
.map((operatorType: String) => ({operatorType: operatorType, acceptsValues: 'flag'}) as Operator);
case 'grade':
return ['=', '!=']
.map((operatorType: String) => ({operatorType: operatorType, acceptsValues: 'grade'}) as Operator);
case 'datetime':
return ['before', 'after']
.map((operatorType: String) => ({operatorType: operatorType, acceptsValues: 'datetime'}) as Operator);
case 'playtime':
return ['>', '<', '>=', '<=']
.map((operatorType: String) => ({operatorType: operatorType, acceptsValues: 'any'}) as Operator);
default:
return [];
}
} }
addPredicate(): void { addPredicate(): void {