Added flag tooltip, fixed returning privileged fields when not admin, added playtime/date UI improvements, show null fields explicitly, etc
This commit is contained in:
parent
e1aac9342e
commit
335bd0a2cf
@ -1,5 +1,6 @@
|
|||||||
package com.nisemoe.nise.search
|
package com.nisemoe.nise.search
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude
|
||||||
import jakarta.validation.Valid
|
import jakarta.validation.Valid
|
||||||
import jakarta.validation.constraints.Min
|
import jakarta.validation.constraints.Min
|
||||||
import jakarta.validation.constraints.NotBlank
|
import jakarta.validation.constraints.NotBlank
|
||||||
@ -25,6 +26,7 @@ class SearchController(
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
data class SearchResponse(
|
data class SearchResponse(
|
||||||
val scores: List<SearchResponseEntry>,
|
val scores: List<SearchResponseEntry>,
|
||||||
val pagination: SearchResponsePagination
|
val pagination: SearchResponsePagination
|
||||||
@ -41,6 +43,7 @@ class SearchController(
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
data class SearchResponseEntry(
|
data class SearchResponseEntry(
|
||||||
// User fields
|
// User fields
|
||||||
val user_id: Long?,
|
val user_id: Long?,
|
||||||
|
|||||||
@ -20,7 +20,7 @@ class SearchSchemaController(
|
|||||||
// User fields
|
// User fields
|
||||||
InternalSchemaField("user_id", "ID", Category.user, Type.number, false, "unique identifier for a user", databaseField = USERS.USER_ID),
|
InternalSchemaField("user_id", "ID", Category.user, Type.number, false, "unique identifier for a user", databaseField = USERS.USER_ID),
|
||||||
InternalSchemaField("user_username", "Username", Category.user, Type.string, true, "user's name", databaseField = USERS.USERNAME),
|
InternalSchemaField("user_username", "Username", Category.user, Type.string, true, "user's name", databaseField = USERS.USERNAME),
|
||||||
InternalSchemaField("user_join_date", "Join Date", Category.user, Type.string, false, "when the user joined", databaseField = USERS.JOIN_DATE),
|
InternalSchemaField("user_join_date", "Join Date", Category.user, Type.datetime, false, "when the user joined", databaseField = USERS.JOIN_DATE),
|
||||||
InternalSchemaField("user_country", "Country", Category.user, Type.flag, true, "user's country flag", databaseField = USERS.COUNTRY),
|
InternalSchemaField("user_country", "Country", Category.user, Type.flag, true, "user's country flag", databaseField = USERS.COUNTRY),
|
||||||
InternalSchemaField("user_country_rank", "Country Rank", Category.user, Type.number, false, "ranking within user's country", databaseField = USERS.COUNTRY_RANK),
|
InternalSchemaField("user_country_rank", "Country Rank", Category.user, Type.number, false, "ranking within user's country", databaseField = USERS.COUNTRY_RANK),
|
||||||
InternalSchemaField("user_rank", "Rank", Category.user, Type.number, false, "global ranking", databaseField = USERS.RANK),
|
InternalSchemaField("user_rank", "Rank", Category.user, Type.number, false, "global ranking", databaseField = USERS.RANK),
|
||||||
@ -29,7 +29,7 @@ class SearchSchemaController(
|
|||||||
InternalSchemaField("user_playcount", "Playcount", Category.user, Type.number, false, "total plays", databaseField = USERS.PLAYCOUNT),
|
InternalSchemaField("user_playcount", "Playcount", Category.user, Type.number, false, "total plays", databaseField = USERS.PLAYCOUNT),
|
||||||
InternalSchemaField("user_total_score", "Total Score", Category.user, Type.number, false, "cumulative score", databaseField = USERS.TOTAL_SCORE),
|
InternalSchemaField("user_total_score", "Total Score", Category.user, Type.number, false, "cumulative score", databaseField = USERS.TOTAL_SCORE),
|
||||||
InternalSchemaField("user_ranked_score", "Ranked Score", Category.user, Type.number, false, "score from ranked maps", databaseField = USERS.RANKED_SCORE),
|
InternalSchemaField("user_ranked_score", "Ranked Score", Category.user, Type.number, false, "score from ranked maps", databaseField = USERS.RANKED_SCORE),
|
||||||
InternalSchemaField("user_seconds_played", "Play Time", Category.user, Type.number, false, "total play time in seconds", databaseField = USERS.SECONDS_PLAYED),
|
InternalSchemaField("user_seconds_played", "Play Time", Category.user, Type.playtime, false, "total play time in seconds", databaseField = USERS.SECONDS_PLAYED),
|
||||||
InternalSchemaField("user_count_300", "300s", Category.user, Type.number, false, "number of 300 hits", databaseField = USERS.COUNT_300),
|
InternalSchemaField("user_count_300", "300s", Category.user, Type.number, false, "number of 300 hits", databaseField = USERS.COUNT_300),
|
||||||
InternalSchemaField("user_count_100", "100s", Category.user, Type.number, false, "number of 100 hits", databaseField = USERS.COUNT_100),
|
InternalSchemaField("user_count_100", "100s", Category.user, Type.number, false, "number of 100 hits", databaseField = USERS.COUNT_100),
|
||||||
InternalSchemaField("user_count_50", "50s", Category.user, Type.number, false, "number of 50 hits", databaseField = USERS.COUNT_50),
|
InternalSchemaField("user_count_50", "50s", Category.user, Type.number, false, "number of 50 hits", databaseField = USERS.COUNT_50),
|
||||||
@ -42,7 +42,7 @@ class SearchSchemaController(
|
|||||||
InternalSchemaField("count_100", "100s", Category.score, Type.number, false, "number of 100 hits in score", databaseField = SCORES.COUNT_100),
|
InternalSchemaField("count_100", "100s", Category.score, Type.number, false, "number of 100 hits in score", databaseField = SCORES.COUNT_100),
|
||||||
InternalSchemaField("count_50", "50s", Category.score, Type.number, false, "number of 50 hits in score", databaseField = SCORES.COUNT_50),
|
InternalSchemaField("count_50", "50s", Category.score, Type.number, false, "number of 50 hits in score", databaseField = SCORES.COUNT_50),
|
||||||
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.string, 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.number, 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),
|
||||||
@ -50,24 +50,24 @@ class SearchSchemaController(
|
|||||||
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),
|
||||||
InternalSchemaField("replay_id", "Replay ID", Category.score, Type.number, false, "identifier for replay", databaseField = SCORES.REPLAY_ID),
|
InternalSchemaField("replay_id", "Replay ID", Category.score, Type.number, false, "identifier for replay", databaseField = SCORES.REPLAY_ID),
|
||||||
InternalSchemaField("score", "Score", Category.score, Type.number, false, "score value", databaseField = SCORES.SCORE),
|
InternalSchemaField("score", "Score", Category.score, Type.number, false, "score value", databaseField = SCORES.SCORE),
|
||||||
InternalSchemaField("ur", "UR", Category.score, Type.number, false, "unstable rate", databaseField = SCORES.UR),
|
InternalSchemaField("ur", "UR", Category.metrics, Type.number, false, "unstable rate", databaseField = SCORES.UR),
|
||||||
InternalSchemaField("frametime", "Frame Time", Category.score, Type.number, false, "median frame time during play", databaseField = SCORES.FRAMETIME),
|
InternalSchemaField("frametime", "Frame Time", Category.metrics, Type.number, false, "median frame time during play", databaseField = SCORES.FRAMETIME),
|
||||||
InternalSchemaField("edge_hits", "Edge Hits", Category.score, Type.number, false, "hits at the edge of the hitobject (<1px)", databaseField = SCORES.EDGE_HITS),
|
InternalSchemaField("edge_hits", "Edge Hits", Category.metrics, Type.number, false, "hits at the edge of the hitobject (<1px)", databaseField = SCORES.EDGE_HITS),
|
||||||
InternalSchemaField("snaps", "Snaps", Category.score, Type.number, false, "rapid cursor movements", databaseField = SCORES.SNAPS),
|
InternalSchemaField("snaps", "Snaps", Category.metrics, Type.number, false, "rapid cursor movements", databaseField = SCORES.SNAPS),
|
||||||
InternalSchemaField("adjusted_ur", "Adj. UR", Category.score, Type.number, true, "adjusted unstable rate", databaseField = SCORES.ADJUSTED_UR),
|
InternalSchemaField("adjusted_ur", "Adj. UR", Category.metrics, Type.number, true, "adjusted unstable rate", databaseField = SCORES.ADJUSTED_UR),
|
||||||
InternalSchemaField("mean_error", "Mean Error", Category.score, Type.number, false, "average timing error", databaseField = SCORES.MEAN_ERROR),
|
InternalSchemaField("mean_error", "Mean Error", Category.metrics, Type.number, false, "average timing error", databaseField = SCORES.MEAN_ERROR),
|
||||||
InternalSchemaField("error_variance", "Error Var.", Category.score, Type.number, false, "variability of error in scores", databaseField = SCORES.ERROR_VARIANCE),
|
InternalSchemaField("error_variance", "Error Var.", Category.metrics, Type.number, false, "variability of error in scores", databaseField = SCORES.ERROR_VARIANCE),
|
||||||
InternalSchemaField("error_standard_deviation", "Error SD", Category.score, Type.number, false, "standard deviation of error", databaseField = SCORES.ERROR_STANDARD_DEVIATION),
|
InternalSchemaField("error_standard_deviation", "Error SD", Category.metrics, Type.number, false, "standard deviation of error", databaseField = SCORES.ERROR_STANDARD_DEVIATION),
|
||||||
InternalSchemaField("minimum_error", "Min Error", Category.score, Type.number, false, "smallest error recorded", databaseField = SCORES.MINIMUM_ERROR),
|
InternalSchemaField("minimum_error", "Min Error", Category.metrics, Type.number, false, "smallest error recorded", databaseField = SCORES.MINIMUM_ERROR),
|
||||||
InternalSchemaField("maximum_error", "Max Error", Category.score, Type.number, false, "largest error recorded", databaseField = SCORES.MAXIMUM_ERROR),
|
InternalSchemaField("maximum_error", "Max Error", Category.metrics, Type.number, false, "largest error recorded", databaseField = SCORES.MAXIMUM_ERROR),
|
||||||
InternalSchemaField("error_range", "Error Range", Category.score, Type.number, false, "range between min and max error", databaseField = SCORES.ERROR_RANGE),
|
InternalSchemaField("error_range", "Error Range", Category.metrics, Type.number, false, "range between min and max error", databaseField = SCORES.ERROR_RANGE),
|
||||||
InternalSchemaField("error_coefficient_of_variation", "Error CV", Category.score, Type.number, false, "relative variability of error", databaseField = SCORES.ERROR_COEFFICIENT_OF_VARIATION),
|
InternalSchemaField("error_coefficient_of_variation", "Error CV", Category.metrics, Type.number, false, "relative variability of error", databaseField = SCORES.ERROR_COEFFICIENT_OF_VARIATION),
|
||||||
InternalSchemaField("error_kurtosis", "Kurtosis", Category.score, Type.number, false, "peakedness of error distribution", databaseField = SCORES.ERROR_KURTOSIS),
|
InternalSchemaField("error_kurtosis", "Kurtosis", Category.metrics, Type.number, false, "peakedness of error distribution", databaseField = SCORES.ERROR_KURTOSIS),
|
||||||
InternalSchemaField("error_skewness", "Skewness", Category.score, Type.number, false, "asymmetry of error distribution", databaseField = SCORES.ERROR_SKEWNESS),
|
InternalSchemaField("error_skewness", "Skewness", Category.metrics, Type.number, false, "asymmetry of error distribution", databaseField = SCORES.ERROR_SKEWNESS),
|
||||||
InternalSchemaField("keypresses_median_adjusted", "KP Median Adj.", Category.score, Type.number, false, "median of adjusted keypresses", isPrivileged = true, databaseField = SCORES.KEYPRESSES_MEDIAN_ADJUSTED),
|
InternalSchemaField("keypresses_median_adjusted", "KP Median Adj.", Category.metrics, Type.number, false, "median of adjusted keypresses", isPrivileged = true, databaseField = SCORES.KEYPRESSES_MEDIAN_ADJUSTED),
|
||||||
InternalSchemaField("keypresses_standard_deviation_adjusted", "KP std. Adj.", Category.score, Type.number, false, "std. dev of adjusted keypresses", isPrivileged = true, databaseField = SCORES.KEYPRESSES_STANDARD_DEVIATION_ADJUSTED),
|
InternalSchemaField("keypresses_standard_deviation_adjusted", "KP std. Adj.", Category.metrics, Type.number, false, "std. dev of adjusted keypresses", isPrivileged = true, databaseField = SCORES.KEYPRESSES_STANDARD_DEVIATION_ADJUSTED),
|
||||||
InternalSchemaField("sliderend_release_median_adjusted", "Sliderend Median Adj.", Category.score, Type.number, false, "median of adjusted sliderend releases", isPrivileged = true, databaseField = SCORES.SLIDEREND_RELEASE_MEDIAN_ADJUSTED),
|
InternalSchemaField("sliderend_release_median_adjusted", "Sliderend Median Adj.", Category.metrics, Type.number, false, "median of adjusted sliderend releases", isPrivileged = true, databaseField = SCORES.SLIDEREND_RELEASE_MEDIAN_ADJUSTED),
|
||||||
InternalSchemaField("sliderend_release_standard_deviation_adjusted", "Sliderend std. Adj.", Category.score, Type.number, false, "std. dev of adjusted sliderend releases", isPrivileged = true, databaseField = SCORES.SLIDEREND_RELEASE_STANDARD_DEVIATION_ADJUSTED),
|
InternalSchemaField("sliderend_release_standard_deviation_adjusted", "Sliderend std. Adj.", Category.metrics, Type.number, false, "std. dev of adjusted sliderend releases", isPrivileged = true, databaseField = SCORES.SLIDEREND_RELEASE_STANDARD_DEVIATION_ADJUSTED),
|
||||||
|
|
||||||
// Beatmap fields
|
// Beatmap fields
|
||||||
InternalSchemaField("beatmap_artist", "Artist", Category.beatmap, Type.string, false, "artist of the beatmap", databaseField = BEATMAPS.ARTIST),
|
InternalSchemaField("beatmap_artist", "Artist", Category.beatmap, Type.string, false, "artist of the beatmap", databaseField = BEATMAPS.ARTIST),
|
||||||
@ -103,11 +103,11 @@ class SearchSchemaController(
|
|||||||
)
|
)
|
||||||
|
|
||||||
enum class Category {
|
enum class Category {
|
||||||
user, score, beatmap
|
user, score, beatmap, metrics
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class Type {
|
enum class Type {
|
||||||
number, string, flag, grade, boolean
|
number, string, flag, grade, boolean, datetime, playtime
|
||||||
}
|
}
|
||||||
|
|
||||||
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.service.AuthService
|
||||||
import org.jooq.*
|
import org.jooq.*
|
||||||
import org.jooq.impl.DSL
|
import org.jooq.impl.DSL
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
@ -11,7 +12,8 @@ import kotlin.math.roundToInt
|
|||||||
|
|
||||||
@Service
|
@Service
|
||||||
class SearchService(
|
class SearchService(
|
||||||
private val dslContext: DSLContext
|
private val dslContext: DSLContext,
|
||||||
|
private val authService: AuthService
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun search(request: SearchController.SearchRequest): SearchController.SearchResponse {
|
fun search(request: SearchController.SearchRequest): SearchController.SearchResponse {
|
||||||
@ -25,8 +27,8 @@ class SearchService(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val results = dslContext.select(
|
val selectFields = mutableListOf<Field<*>>(
|
||||||
// User fields
|
// Common fields list
|
||||||
USERS.USERNAME,
|
USERS.USERNAME,
|
||||||
USERS.USER_ID,
|
USERS.USER_ID,
|
||||||
USERS.JOIN_DATE,
|
USERS.JOIN_DATE,
|
||||||
@ -72,10 +74,6 @@ class SearchService(
|
|||||||
SCORES.ERROR_COEFFICIENT_OF_VARIATION,
|
SCORES.ERROR_COEFFICIENT_OF_VARIATION,
|
||||||
SCORES.ERROR_KURTOSIS,
|
SCORES.ERROR_KURTOSIS,
|
||||||
SCORES.ERROR_SKEWNESS,
|
SCORES.ERROR_SKEWNESS,
|
||||||
SCORES.KEYPRESSES_MEDIAN_ADJUSTED,
|
|
||||||
SCORES.KEYPRESSES_STANDARD_DEVIATION_ADJUSTED,
|
|
||||||
SCORES.SLIDEREND_RELEASE_MEDIAN_ADJUSTED,
|
|
||||||
SCORES.SLIDEREND_RELEASE_STANDARD_DEVIATION_ADJUSTED,
|
|
||||||
|
|
||||||
// Beatmaps fields
|
// Beatmaps fields
|
||||||
BEATMAPS.ARTIST,
|
BEATMAPS.ARTIST,
|
||||||
@ -86,6 +84,21 @@ class SearchService(
|
|||||||
BEATMAPS.TITLE,
|
BEATMAPS.TITLE,
|
||||||
BEATMAPS.VERSION
|
BEATMAPS.VERSION
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Conditionally add admin-specific fields
|
||||||
|
if (authService.isAdmin()) {
|
||||||
|
selectFields.addAll(
|
||||||
|
listOf(
|
||||||
|
SCORES.KEYPRESSES_MEDIAN_ADJUSTED,
|
||||||
|
SCORES.KEYPRESSES_STANDARD_DEVIATION_ADJUSTED,
|
||||||
|
SCORES.SLIDEREND_RELEASE_MEDIAN_ADJUSTED,
|
||||||
|
SCORES.SLIDEREND_RELEASE_STANDARD_DEVIATION_ADJUSTED
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val query = dslContext
|
||||||
|
.select(selectFields)
|
||||||
.from(SCORES)
|
.from(SCORES)
|
||||||
.join(USERS).on(SCORES.USER_ID.eq(USERS.USER_ID))
|
.join(USERS).on(SCORES.USER_ID.eq(USERS.USER_ID))
|
||||||
.join(BEATMAPS).on(SCORES.BEATMAP_ID.eq(BEATMAPS.BEATMAP_ID))
|
.join(BEATMAPS).on(SCORES.BEATMAP_ID.eq(BEATMAPS.BEATMAP_ID))
|
||||||
@ -96,8 +109,12 @@ class SearchService(
|
|||||||
}
|
}
|
||||||
.offset((request.page - 1) * SearchController.RESULTS_PER_PAGE)
|
.offset((request.page - 1) * SearchController.RESULTS_PER_PAGE)
|
||||||
.limit(SearchController.RESULTS_PER_PAGE)
|
.limit(SearchController.RESULTS_PER_PAGE)
|
||||||
|
|
||||||
|
val results = query
|
||||||
.fetch()
|
.fetch()
|
||||||
|
|
||||||
|
println(query.toString())
|
||||||
|
|
||||||
// Get total results
|
// Get total results
|
||||||
val totalResults = dslContext.selectCount()
|
val totalResults = dslContext.selectCount()
|
||||||
.from(SCORES)
|
.from(SCORES)
|
||||||
@ -166,10 +183,12 @@ class SearchService(
|
|||||||
error_coefficient_of_variation = it.get(SCORES.ERROR_COEFFICIENT_OF_VARIATION),
|
error_coefficient_of_variation = it.get(SCORES.ERROR_COEFFICIENT_OF_VARIATION),
|
||||||
error_kurtosis = it.get(SCORES.ERROR_KURTOSIS),
|
error_kurtosis = it.get(SCORES.ERROR_KURTOSIS),
|
||||||
error_skewness = it.get(SCORES.ERROR_SKEWNESS),
|
error_skewness = it.get(SCORES.ERROR_SKEWNESS),
|
||||||
keypresses_median_adjusted = it.get(SCORES.KEYPRESSES_MEDIAN_ADJUSTED),
|
|
||||||
keypresses_standard_deviation_adjusted = it.get(SCORES.KEYPRESSES_STANDARD_DEVIATION_ADJUSTED),
|
// Admin-specific fields, conditional based on isAdmin
|
||||||
sliderend_release_median_adjusted = it.get(SCORES.SLIDEREND_RELEASE_MEDIAN_ADJUSTED),
|
keypresses_median_adjusted = if (authService.isAdmin()) it.get(SCORES.KEYPRESSES_MEDIAN_ADJUSTED) else null,
|
||||||
sliderend_release_standard_deviation_adjusted = it.get(SCORES.SLIDEREND_RELEASE_STANDARD_DEVIATION_ADJUSTED),
|
keypresses_standard_deviation_adjusted = if (authService.isAdmin()) it.get(SCORES.KEYPRESSES_STANDARD_DEVIATION_ADJUSTED) else null,
|
||||||
|
sliderend_release_median_adjusted = if (authService.isAdmin()) it.get(SCORES.SLIDEREND_RELEASE_MEDIAN_ADJUSTED) else null,
|
||||||
|
sliderend_release_standard_deviation_adjusted = if (authService.isAdmin()) it.get(SCORES.SLIDEREND_RELEASE_STANDARD_DEVIATION_ADJUSTED) else null,
|
||||||
|
|
||||||
// Beatmap fields
|
// Beatmap fields
|
||||||
beatmap_artist = it.get(BEATMAPS.ARTIST),
|
beatmap_artist = it.get(BEATMAPS.ARTIST),
|
||||||
@ -199,7 +218,7 @@ class SearchService(
|
|||||||
|
|
||||||
query.childQueries.forEach { childQuery ->
|
query.childQueries.forEach { childQuery ->
|
||||||
val childCondition = buildCondition(childQuery) // Recursively build condition for child queries
|
val childCondition = buildCondition(childQuery) // Recursively build condition for child queries
|
||||||
baseCondition = when (query.logicalOperator.lowercase()) {
|
baseCondition = when (childQuery.logicalOperator.lowercase()) {
|
||||||
"and" -> baseCondition.and(childCondition)
|
"and" -> baseCondition.and(childCondition)
|
||||||
"or" -> baseCondition.or(childCondition)
|
"or" -> baseCondition.or(childCondition)
|
||||||
else -> throw IllegalArgumentException("Invalid logical operator")
|
else -> throw IllegalArgumentException("Invalid logical operator")
|
||||||
@ -226,6 +245,8 @@ class SearchService(
|
|||||||
"boolean" -> buildBooleanCondition(field as Field<Boolean>, predicate.operator.operatorType, predicate.value.toBoolean())
|
"boolean" -> buildBooleanCondition(field as Field<Boolean>, predicate.operator.operatorType, predicate.value.toBoolean())
|
||||||
"flag" -> buildStringCondition(field as Field<String>, predicate.operator.operatorType, predicate.value)
|
"flag" -> buildStringCondition(field as Field<String>, predicate.operator.operatorType, predicate.value)
|
||||||
"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)
|
||||||
|
"playtime" -> buildNumberCondition(field as Field<Double>, predicate.operator.operatorType, predicate.value.toDouble())
|
||||||
else -> throw IllegalArgumentException("Invalid field type")
|
else -> throw IllegalArgumentException("Invalid field type")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -288,4 +309,12 @@ class SearchService(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun buildDatetimeCondition(field: Field<String>, operator: String, value: String): Condition {
|
||||||
|
return when (operator.lowercase()) {
|
||||||
|
"before" -> field.lessThan(value)
|
||||||
|
"after" -> field.greaterThan(value)
|
||||||
|
else -> throw IllegalArgumentException("Invalid operator")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -1,6 +1,12 @@
|
|||||||
import {ReplayData} from "./replays";
|
import {ReplayData} from "./replays";
|
||||||
|
|
||||||
export function formatDuration(seconds: number): string {
|
export function formatDuration(seconds: number): string | null {
|
||||||
|
if(!seconds) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(seconds);
|
||||||
|
|
||||||
const days = Math.floor(seconds / (3600 * 24));
|
const days = Math.floor(seconds / (3600 * 24));
|
||||||
const hours = Math.floor((seconds % (3600 * 24)) / 3600);
|
const hours = Math.floor((seconds % (3600 * 24)) / 3600);
|
||||||
const minutes = Math.floor((seconds % 3600) / 60);
|
const minutes = Math.floor((seconds % 3600) / 60);
|
||||||
|
|||||||
@ -19,3 +19,9 @@
|
|||||||
.score-entry:hover {
|
.score-entry:hover {
|
||||||
background-color: rgba(179, 184, 195, 0.15);
|
background-color: rgba(179, 184, 195, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
background-color: rgba(227, 232, 255, 0.1);
|
||||||
|
padding: 2px;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|||||||
@ -9,7 +9,7 @@
|
|||||||
<ng-template #searchPanel>
|
<ng-template #searchPanel>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>Table columns</legend>
|
<legend>Table columns</legend>
|
||||||
<ng-container *ngFor="let category of ['user', 'beatmap', 'score']">
|
<ng-container *ngFor="let category of ['user', 'beatmap', 'score', 'metrics']">
|
||||||
<fieldset class="mb-2">
|
<fieldset class="mb-2">
|
||||||
<legend>{{ category }} <button (click)="this.selectEntireFieldCategory(category)">Select all</button> <button (click)="this.deselectEntireFieldCategory(category)">Deselect all</button></legend>
|
<legend>{{ category }} <button (click)="this.selectEntireFieldCategory(category)">Select all</button> <button (click)="this.deselectEntireFieldCategory(category)">Deselect all</button></legend>
|
||||||
<ng-container *ngFor="let field of fields">
|
<ng-container *ngFor="let field of fields">
|
||||||
@ -26,18 +26,27 @@
|
|||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<div class="search-container mt-2" *ngIf="this.queries">
|
<div class="search-container mt-2" *ngIf="this.queries">
|
||||||
<app-query-builder [queries]="this.queries" [fields]="this.mapSchemaFieldsToFields()"></app-query-builder>
|
<app-query-builder [queries]="this.queries" [fields]="fields"></app-query-builder>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<fieldset class="mt-2" *ngIf="this.sortingOrder">
|
<fieldset class="mt-2" *ngIf="this.sortingOrder">
|
||||||
<legend>sorting</legend>
|
<legend>sorting</legend>
|
||||||
|
|
||||||
<select>
|
<select>
|
||||||
<option *ngFor="let field of fields"
|
<ng-container *ngFor="let category of ['user', 'beatmap', 'score', 'metrics']">
|
||||||
|
<optgroup label="{{ category }}">
|
||||||
|
<ng-container *ngFor="let field of fields">
|
||||||
|
<ng-container *ngIf="field.category === category">
|
||||||
|
<option
|
||||||
[value]="field.name"
|
[value]="field.name"
|
||||||
(click)="this.sortingOrder.field = field.name"
|
(click)="this.sortingOrder.field = field.name"
|
||||||
[selected]="field.name == this.sortingOrder.field"
|
[selected]="field.name == this.sortingOrder.field"
|
||||||
>{{ field.name }}</option>
|
>{{ field.name }}
|
||||||
|
</option>
|
||||||
|
</ng-container>
|
||||||
|
</ng-container>
|
||||||
|
</optgroup>
|
||||||
|
</ng-container>
|
||||||
</select>
|
</select>
|
||||||
<label>
|
<label>
|
||||||
<input type="radio" name="sortingOrder" [(ngModel)]="this.sortingOrder.order" value="ASC" />
|
<input type="radio" name="sortingOrder" [(ngModel)]="this.sortingOrder.order" value="ASC" />
|
||||||
@ -55,12 +64,13 @@
|
|||||||
<input type="file" #fileInput style="display: none" (change)="uploadSettingsFile($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()" [disabled]="this.isLoading" class="mb-2" style="font-size: 18px">Search</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ng-container *ngIf="this.isLoading">
|
<ng-container *ngIf="this.isLoading">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<p>Loading...</p>
|
<p>Loading <app-cute-loading></app-cute-loading></p>
|
||||||
|
<p>please be patient - the database is working hard!</p>
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
@ -92,20 +102,25 @@
|
|||||||
<th *ngFor="let column of fields" [hidden]="!column.active" class="text-center">
|
<th *ngFor="let column of fields" [hidden]="!column.active" class="text-center">
|
||||||
{{ column.shortName }}
|
{{ column.shortName }}
|
||||||
</th>
|
</th>
|
||||||
|
<th>Links</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr *ngFor="let entry of response.scores" class="score-entry">
|
<tr *ngFor="let entry of response.scores" class="score-entry">
|
||||||
<td *ngFor="let column of fields" [hidden]="!column.active" class="text-center" style="line-height: 32px">
|
<td *ngFor="let column of fields" [hidden]="!column.active" class="text-center" style="line-height: 32px">
|
||||||
|
<ng-container *ngIf="getValue(entry, column.name) !== null; else nullDisplay">
|
||||||
<ng-container *ngIf="column.type == 'number'">
|
<ng-container *ngIf="column.type == 'number'">
|
||||||
{{ getValue(entry, column.name) | number }}
|
{{ getValue(entry, column.name) | number }}
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container *ngIf="column.type == 'flag'">
|
<ng-container *ngIf="column.type == 'flag'">
|
||||||
<span class="flag">{{ countryCodeToFlag(getValue(entry, column.name)) }}</span>
|
<span class="flag" [title]="getValue(entry, column.name)">{{ countryCodeToFlag(getValue(entry, column.name)) }}</span>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container *ngIf="column.type == 'grade'">
|
<ng-container *ngIf="column.type == 'grade'">
|
||||||
<app-osu-grade [grade]="getValue(entry, column.name)"></app-osu-grade>
|
<app-osu-grade [grade]="getValue(entry, column.name)"></app-osu-grade>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
<ng-container *ngIf="column.type == 'datetime'">
|
||||||
|
{{ getValue(entry, column.name) }}
|
||||||
|
</ng-container>
|
||||||
<ng-container *ngIf="column.type == 'boolean'">
|
<ng-container *ngIf="column.type == 'boolean'">
|
||||||
<ng-container *ngIf="getValue(entry, column.name) == true">
|
<ng-container *ngIf="getValue(entry, column.name) == true">
|
||||||
✓
|
✓
|
||||||
@ -114,9 +129,26 @@
|
|||||||
✗
|
✗
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container *ngIf="column.type == 'string'">
|
<ng-container *ngIf="column.type == 'playtime'">
|
||||||
{{ getValue(entry, column.name) }}
|
{{ formatDuration(getValue(entry, column.name)) }}
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
<ng-container *ngIf="column.type == 'string'">
|
||||||
|
|
||||||
|
<ng-container *ngIf="column.name == 'user_username'; else stringField">
|
||||||
|
<a [href]="'/u/' + getValue(entry, column.name)" target="_blank">{{ getValue(entry, column.name) }}</a>
|
||||||
|
</ng-container>
|
||||||
|
<ng-template #stringField>
|
||||||
|
{{ getValue(entry, column.name) }}
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
</ng-container>
|
||||||
|
</ng-container>
|
||||||
|
<ng-template #nullDisplay><code>null</code></ng-template>
|
||||||
|
</td>
|
||||||
|
<td class="text-center" style="line-height: 32px">
|
||||||
|
<a [href]="'/s/' + this.getId(entry)" target="_blank">
|
||||||
|
details
|
||||||
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@ -3,18 +3,19 @@ import {FormsModule, ReactiveFormsModule} from "@angular/forms";
|
|||||||
import {DecimalPipe, JsonPipe, NgForOf, NgIf} from "@angular/common";
|
import {DecimalPipe, JsonPipe, NgForOf, NgIf} from "@angular/common";
|
||||||
import {HttpClient} from "@angular/common/http";
|
import {HttpClient} from "@angular/common/http";
|
||||||
import {environment} from "../../environments/environment";
|
import {environment} from "../../environments/environment";
|
||||||
import {countryCodeToFlag} 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 {Field, 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";
|
||||||
|
|
||||||
interface SchemaField {
|
export interface SchemaField {
|
||||||
name: string;
|
name: string;
|
||||||
shortName: string;
|
shortName: string;
|
||||||
category: 'user' | 'score' | 'beatmap';
|
category: 'user' | 'score' | 'beatmap' | 'metrics';
|
||||||
type: 'number' | 'string' | 'flag' | 'grade' | 'boolean';
|
type: 'number' | 'string' | 'flag' | 'grade' | 'boolean' | 'datetime' | 'playtime';
|
||||||
active: boolean;
|
active: boolean;
|
||||||
description: string;
|
description: string;
|
||||||
}
|
}
|
||||||
@ -53,7 +54,8 @@ interface Sorting {
|
|||||||
OsuGradeComponent,
|
OsuGradeComponent,
|
||||||
QueryBuilderComponent,
|
QueryBuilderComponent,
|
||||||
RouterLink,
|
RouterLink,
|
||||||
CalculatePageRangePipe
|
CalculatePageRangePipe,
|
||||||
|
CuteLoadingComponent
|
||||||
],
|
],
|
||||||
templateUrl: './search.component.html',
|
templateUrl: './search.component.html',
|
||||||
styleUrl: './search.component.css'
|
styleUrl: './search.component.css'
|
||||||
@ -125,15 +127,6 @@ export class SearchComponent implements OnInit {
|
|||||||
this.saveSettingsToLocalStorage();
|
this.saveSettingsToLocalStorage();
|
||||||
}
|
}
|
||||||
|
|
||||||
mapSchemaFieldsToFields(): Field[] {
|
|
||||||
return this.fields.map(field => {
|
|
||||||
return {
|
|
||||||
name: field.name,
|
|
||||||
type: field.type,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private serializeSettings() {
|
private serializeSettings() {
|
||||||
return {
|
return {
|
||||||
queries: this.queries,
|
queries: this.queries,
|
||||||
@ -220,12 +213,20 @@ export class SearchComponent implements OnInit {
|
|||||||
this.saveSettingsToLocalStorage();
|
this.saveSettingsToLocalStorage();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add this method to the SearchComponent class
|
|
||||||
getValue(entry: any, columnName: string): any {
|
getValue(entry: any, columnName: string): any {
|
||||||
return entry[columnName as keyof any];
|
if (entry.hasOwnProperty(columnName)) {
|
||||||
|
return entry[columnName];
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getId(entry: any): any {
|
||||||
|
return this.getValue(entry, 'replay_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
protected readonly countryCodeToFlag = countryCodeToFlag;
|
protected readonly countryCodeToFlag = countryCodeToFlag;
|
||||||
protected readonly Math = Math;
|
protected readonly Math = Math;
|
||||||
|
|
||||||
|
protected readonly formatDuration = formatDuration;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,7 +30,7 @@
|
|||||||
<ul>
|
<ul>
|
||||||
<li *ngIf="this.userInfo.user_details.pp_raw">PP: {{ this.userInfo.user_details.pp_raw | number: '1.0-0' }}</li>
|
<li *ngIf="this.userInfo.user_details.pp_raw">PP: {{ this.userInfo.user_details.pp_raw | number: '1.0-0' }}</li>
|
||||||
<li *ngIf="this.userInfo.user_details.rank">Rank: #{{ this.userInfo.user_details.rank | number: '1.0-1' }}</li>
|
<li *ngIf="this.userInfo.user_details.rank">Rank: #{{ this.userInfo.user_details.rank | number: '1.0-1' }}</li>
|
||||||
<li *ngIf="this.userInfo.user_details.country">Country: <span class="flag">{{ countryCodeToFlag(this.userInfo.user_details.country) }}</span> {{ this.userInfo.user_details.country }}</li>
|
<li *ngIf="this.userInfo.user_details.country">Country: <span class="flag" [title]="this.userInfo.user_details.country">{{ countryCodeToFlag(this.userInfo.user_details.country) }}</span> {{ this.userInfo.user_details.country }}</li>
|
||||||
<li *ngIf="this.userInfo.user_details.country_rank">Country Rank: #{{ this.userInfo.user_details.country_rank | number: '1.0-1' }}</li>
|
<li *ngIf="this.userInfo.user_details.country_rank">Country Rank: #{{ this.userInfo.user_details.country_rank | number: '1.0-1' }}</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|||||||
@ -2,10 +2,11 @@ import {Component, Input} from '@angular/core';
|
|||||||
import {FormsModule} from "@angular/forms";
|
import {FormsModule} from "@angular/forms";
|
||||||
import {NgForOf, NgIf} from "@angular/common";
|
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";
|
||||||
|
|
||||||
export type FieldType = 'number' | 'string' | 'flag' | 'grade' | 'boolean';
|
export type FieldType = 'number' | 'string' | 'flag' | 'grade' | 'boolean' | 'datetime' | 'playtime';
|
||||||
export type OperatorType = '=' | '>' | '<' | 'contains' | 'like' | '>=' | '<=' | '!=';
|
export type OperatorType = '=' | '>' | '<' | 'contains' | 'like' | '>=' | '<=' | '!=';
|
||||||
export type ValueType = 'any' | 'boolean' | 'flag' | 'grade';
|
export type ValueType = 'any' | 'boolean' | 'flag' | 'grade' | 'datetime';
|
||||||
|
|
||||||
export interface Field {
|
export interface Field {
|
||||||
name: string;
|
name: string;
|
||||||
@ -44,7 +45,7 @@ export interface Query {
|
|||||||
export class QueryBuilderComponent {
|
export class QueryBuilderComponent {
|
||||||
|
|
||||||
@Input() queries: Query[] = [];
|
@Input() queries: Query[] = [];
|
||||||
@Input() fields: Field[] = [];
|
@Input() fields: SchemaField[] = [];
|
||||||
|
|
||||||
addQuery(): void {
|
addQuery(): void {
|
||||||
this.queries.push({
|
this.queries.push({
|
||||||
|
|||||||
@ -15,9 +15,20 @@
|
|||||||
<div *ngFor="let predicate of query.predicates; let i = index">
|
<div *ngFor="let predicate of query.predicates; let i = index">
|
||||||
|
|
||||||
<select style="max-width: 40%">
|
<select style="max-width: 40%">
|
||||||
<option *ngFor="let field of fields" (click)="onFieldChange(predicate, field)" [selected]="field.name === predicate.field?.name">{{ field.name }}</option>
|
<ng-container *ngFor="let category of ['user', 'beatmap', 'score', 'metrics']">
|
||||||
|
<optgroup label="{{ category }}">
|
||||||
|
<ng-container *ngFor="let field of fields">
|
||||||
|
<ng-container *ngIf="field.category === category">
|
||||||
|
<option [value]="field.name" [selected]="field.name === predicate.field?.name" (click)="onFieldChange(predicate, field)">
|
||||||
|
{{ field.name }}
|
||||||
|
</option>
|
||||||
|
</ng-container>
|
||||||
|
</ng-container>
|
||||||
|
</optgroup>
|
||||||
|
</ng-container>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
|
||||||
<select [disabled]="!predicate.field">
|
<select [disabled]="!predicate.field">
|
||||||
<option *ngFor="let operator of getOperators(predicate.field?.type)"
|
<option *ngFor="let operator of getOperators(predicate.field?.type)"
|
||||||
[selected]="operator.operatorType === predicate.operator?.operatorType"
|
[selected]="operator.operatorType === predicate.operator?.operatorType"
|
||||||
@ -41,6 +52,12 @@
|
|||||||
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container *ngIf="predicate.operator.acceptsValues == 'datetime'">
|
||||||
|
|
||||||
|
<input type="datetime-local" [(ngModel)]="predicate.value" [disabled]="!predicate.field" (change)="this.queryChanged.emit()">
|
||||||
|
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
<ng-container *ngIf="predicate.operator.acceptsValues == 'flag'">
|
<ng-container *ngIf="predicate.operator.acceptsValues == 'flag'">
|
||||||
|
|
||||||
<select [(ngModel)]="predicate.value" [disabled]="!predicate.field" (change)="this.queryChanged.emit()" style="max-width: 30%">
|
<select [(ngModel)]="predicate.value" [disabled]="!predicate.field" (change)="this.queryChanged.emit()" style="max-width: 30%">
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
import {Component, EventEmitter, Input, Output} from '@angular/core';
|
import {Component, EventEmitter, Input, Output} from '@angular/core';
|
||||||
import {Field, 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, 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";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-query',
|
selector: 'app-query',
|
||||||
@ -20,7 +21,7 @@ import { v4 as uuidv4 } from 'uuid';
|
|||||||
export class QueryComponent {
|
export class QueryComponent {
|
||||||
|
|
||||||
@Input() query!: Query;
|
@Input() query!: Query;
|
||||||
@Input() fields!: Field[];
|
@Input() fields!: SchemaField[];
|
||||||
|
|
||||||
@Output() removeQuery = new EventEmitter<void>();
|
@Output() removeQuery = new EventEmitter<void>();
|
||||||
@Output() queryChanged = new EventEmitter<void>();
|
@Output() queryChanged = new EventEmitter<void>();
|
||||||
@ -38,19 +39,25 @@ export class QueryComponent {
|
|||||||
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: 'any'}) 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);
|
||||||
case 'boolean':
|
case 'boolean':
|
||||||
return ['=', '!=']
|
return ['=', '!=']
|
||||||
.map((operatorType: String) => ({ operatorType: operatorType, acceptsValues: 'boolean'}) as Operator);
|
.map((operatorType: String) => ({operatorType: operatorType, acceptsValues: 'boolean'}) as Operator);
|
||||||
case 'flag':
|
case 'flag':
|
||||||
return ['=', '!=']
|
return ['=', '!=']
|
||||||
.map((operatorType: String) => ({ operatorType: operatorType, acceptsValues: 'flag'}) as Operator);
|
.map((operatorType: String) => ({operatorType: operatorType, acceptsValues: 'flag'}) as Operator);
|
||||||
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 '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:
|
default:
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user