add search by winner
parent
478c01ff21
commit
2c5708047c
|
@ -12,7 +12,11 @@ import chess.{ Mode }
|
|||
final class DataForm {
|
||||
|
||||
val search = Form(mapping(
|
||||
"usernames" -> optional(nonEmptyText),
|
||||
"players" -> mapping(
|
||||
"a" -> optional(nonEmptyText),
|
||||
"b" -> optional(nonEmptyText),
|
||||
"winner" -> optional(nonEmptyText)
|
||||
)(SearchPlayer.apply)(SearchPlayer.unapply),
|
||||
"variant" -> numberIn(Query.variants),
|
||||
"mode" -> numberIn(Query.modes),
|
||||
"opening" -> stringIn(Query.openings),
|
||||
|
@ -28,8 +32,10 @@ final class DataForm {
|
|||
"dateMin" -> stringIn(Query.dates),
|
||||
"dateMax" -> stringIn(Query.dates),
|
||||
"status" -> numberIn(Query.statuses),
|
||||
"sortField" -> nonEmptyText.verifying(hasKey(Sorting.fields, _)),
|
||||
"sortOrder" -> nonEmptyText.verifying(hasKey(Sorting.orders, _))
|
||||
"sort" -> mapping(
|
||||
"field" -> nonEmptyText.verifying(hasKey(Sorting.fields, _)),
|
||||
"order" -> nonEmptyText.verifying(hasKey(Sorting.orders, _))
|
||||
)(SearchSort.apply)(SearchSort.unapply)
|
||||
)(SearchData.apply)(SearchData.unapply))
|
||||
|
||||
private def numberIn(choices: Seq[(Int, String)]) =
|
||||
|
@ -43,7 +49,7 @@ final class DataForm {
|
|||
}
|
||||
|
||||
case class SearchData(
|
||||
usernames: Option[String] = None,
|
||||
players: SearchPlayer = SearchPlayer(),
|
||||
variant: Option[Int] = None,
|
||||
mode: Option[Int] = None,
|
||||
opening: Option[String] = None,
|
||||
|
@ -59,11 +65,12 @@ case class SearchData(
|
|||
dateMin: Option[String] = None,
|
||||
dateMax: Option[String] = None,
|
||||
status: Option[Int] = None,
|
||||
sortField: String = Sorting.default.field,
|
||||
sortOrder: String = Sorting.default.order) {
|
||||
sort: SearchSort = SearchSort()) {
|
||||
|
||||
lazy val query = Query(
|
||||
usernames = (~usernames).split(" ").toList map clean filter (_.nonEmpty),
|
||||
user1 = players.cleanA,
|
||||
user2 = players.cleanB,
|
||||
winner = players.cleanWinner,
|
||||
variant = variant,
|
||||
rated = mode flatMap Mode.apply map (_.rated),
|
||||
opening = opening map clean,
|
||||
|
@ -74,7 +81,7 @@ case class SearchData(
|
|||
duration = Range(durationMin, durationMax),
|
||||
date = Range(dateMin flatMap toDate, dateMax flatMap toDate),
|
||||
status = status,
|
||||
sorting = Sorting(sortField, sortOrder)
|
||||
sorting = Sorting(sort.field, sort.order)
|
||||
)
|
||||
|
||||
private def clean(s: String) = s.trim.toLowerCase
|
||||
|
@ -88,3 +95,22 @@ case class SearchData(
|
|||
case _ ⇒ None
|
||||
}
|
||||
}
|
||||
|
||||
case class SearchPlayer(
|
||||
a: Option[String] = None,
|
||||
b: Option[String] = None,
|
||||
winner: Option[String] = None) {
|
||||
|
||||
def cleanA = clean(a)
|
||||
def cleanB = clean(b)
|
||||
def cleanWinner = clean(winner) |> { w ⇒
|
||||
w filter List(a, b).flatten.contains
|
||||
}
|
||||
|
||||
private def clean(s: Option[String]) =
|
||||
s map (_.trim.toLowerCase) filter (_.nonEmpty)
|
||||
}
|
||||
|
||||
case class SearchSort(
|
||||
field: String = Sorting.default.field,
|
||||
order: String = Sorting.default.order)
|
||||
|
|
|
@ -14,6 +14,7 @@ object Game {
|
|||
val rated = "ra"
|
||||
val variant = "va"
|
||||
val uids = "ui"
|
||||
val winner = "wi"
|
||||
val averageElo = "el"
|
||||
val ai = "ai"
|
||||
val opening = "op"
|
||||
|
@ -37,6 +38,7 @@ object Game {
|
|||
field(rated, "boolean"),
|
||||
field(variant, "short"),
|
||||
field(uids, "string"),
|
||||
field(winner, "string"),
|
||||
field(averageElo, "short"),
|
||||
field(ai, "short"),
|
||||
field(opening, "string"),
|
||||
|
@ -54,6 +56,7 @@ object Game {
|
|||
rated -> game.rated.some,
|
||||
variant -> game.variant.id.some,
|
||||
uids -> (game.userIds.toNel map (_.list)),
|
||||
winner -> (game.winner flatMap (_.userId)),
|
||||
averageElo -> game.averageUsersElo,
|
||||
ai -> game.aiLevel,
|
||||
date -> (dateFormatter print createdAt).some,
|
||||
|
|
|
@ -9,7 +9,9 @@ import org.joda.time.DateTime
|
|||
import org.scala_tools.time.Imports._
|
||||
|
||||
case class Query(
|
||||
usernames: List[String] = Nil,
|
||||
user1: Option[String] = None,
|
||||
user2: Option[String] = None,
|
||||
winner: Option[String] = None,
|
||||
variant: Option[Int] = None,
|
||||
status: Option[Int] = None,
|
||||
turns: Range[Int] = Range.none,
|
||||
|
@ -23,7 +25,9 @@ case class Query(
|
|||
sorting: Sorting = Sorting.default) {
|
||||
|
||||
def nonEmpty =
|
||||
usernames.nonEmpty ||
|
||||
user1.nonEmpty ||
|
||||
user2.nonEmpty ||
|
||||
winner.nonEmpty ||
|
||||
variant.nonEmpty ||
|
||||
status.nonEmpty ||
|
||||
turns.nonEmpty ||
|
||||
|
@ -44,8 +48,11 @@ case class Query(
|
|||
|
||||
def countRequest = CountRequest(matchAllQuery, filters)
|
||||
|
||||
def usernames = List(user1, user2).flatten
|
||||
|
||||
private def filters = List(
|
||||
usernames map { u ⇒ termFilter(fields.uids, u.toLowerCase) },
|
||||
usernames map { termFilter(fields.uids, _) },
|
||||
toFilters(winner, fields.winner),
|
||||
turns filters fields.turns,
|
||||
averageElo filters fields.averageElo,
|
||||
duration map (60 *) filters fields.duration,
|
||||
|
@ -108,25 +115,4 @@ object Query {
|
|||
}
|
||||
|
||||
val statuses = Status.finishedNotCheated map { s ⇒ s.id -> s.name }
|
||||
|
||||
def test = Query(
|
||||
usernames = List("thibault"),
|
||||
duration = Range(1.some, 3.some),
|
||||
sorting = Sorting(fields.averageElo, "desc")
|
||||
)
|
||||
def test2 = Query(
|
||||
opening = "A04".some,
|
||||
sorting = Sorting(fields.turns, "desc")
|
||||
)
|
||||
def test3 = Query(
|
||||
usernames = List("controlaltdelete"),
|
||||
variant = 1.some,
|
||||
turns = Range(20.some, 100.some),
|
||||
averageElo = Range(1100.some, 2000.some),
|
||||
opening = "A00".some,
|
||||
hasAi = true.some,
|
||||
aiLevel = Range.none,
|
||||
date = Range(Some(DateTime.now - 1.year), none),
|
||||
sorting = Sorting(fields.date, "desc")
|
||||
)
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ object Sorting {
|
|||
|
||||
def fieldKeys = fields map (_._1)
|
||||
|
||||
val orders = List(SortOrder.ASC, SortOrder.DESC) map { s ⇒ s.toString -> s.toString }
|
||||
val orders = List(SortOrder.DESC, SortOrder.ASC) map { s ⇒ s.toString -> s.toString }
|
||||
|
||||
val default = Sorting(Game.fields.date, "desc")
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import play.api.templates.Html
|
|||
|
||||
trait AssetHelper {
|
||||
|
||||
val assetVersion = 71
|
||||
val assetVersion = 73
|
||||
|
||||
def cssTag(name: String) = css("stylesheets/" + name)
|
||||
|
||||
|
|
|
@ -26,16 +26,22 @@ moreCss = moreCss) {
|
|||
<table>
|
||||
<tr>
|
||||
<th>
|
||||
<label for="@form("usernames").id">Players</label>
|
||||
<label>Players</label>
|
||||
</th>
|
||||
<td>
|
||||
<input
|
||||
type="text"
|
||||
value="@form("usernames").value"
|
||||
name="@form("usernames").name"
|
||||
id="@form("usernames").id" />
|
||||
<td class="usernames">
|
||||
<div class="half">@input(form("players")("a"))</div>
|
||||
<div class="half">vs @input(form("players")("b"))</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="winner none">
|
||||
<th>
|
||||
<label for="@form("players")("winner").id">Winner</label>
|
||||
</th>
|
||||
<td class="single">
|
||||
<select id="@form("players")("winner").id" name="@form("players")("winner").name">
|
||||
<option class="blank" value=""></option>
|
||||
</select>
|
||||
</td>
|
||||
<th class="help">Type up to two usernames</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
|
@ -55,7 +61,7 @@ moreCss = moreCss) {
|
|||
@select(form("hasAi"), Query.hasAis, "Human or Computer".some)
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="aiLevel">
|
||||
<tr class="aiLevel none">
|
||||
<th>
|
||||
<label for="@form("aiLevel").id">Stockfish level</label>
|
||||
</th>
|
||||
|
@ -128,8 +134,8 @@ moreCss = moreCss) {
|
|||
<label>Sort</label>
|
||||
</th>
|
||||
<td>
|
||||
<div class="half">By @select(form("sortField"), Sorting.fields)</div>
|
||||
<div class="half">Order @select(form("sortOrder"), Sorting.orders)</div>
|
||||
<div class="half">By @select(form("sort")("field"), Sorting.fields)</div>
|
||||
<div class="half">Order @select(form("sort")("order"), Sorting.orders)</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
@ -137,7 +143,7 @@ moreCss = moreCss) {
|
|||
<div class="search_result">
|
||||
@paginator.map { pager =>
|
||||
@if(pager.nbResults > 0) {
|
||||
<div class="nb_results">
|
||||
<div class="search_status">
|
||||
@paginator.map { pager =>
|
||||
@pager.nbResults.localize games found
|
||||
}
|
||||
|
@ -151,9 +157,11 @@ moreCss = moreCss) {
|
|||
@game.widgets(pager.currentPageResults)
|
||||
</div>
|
||||
} else {
|
||||
<div class="no_result">No game found</div>
|
||||
}
|
||||
<div class="search_status">No game found</div>
|
||||
}
|
||||
}.getOrElse {
|
||||
<div class="search_status">Search ready</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
@(field: play.api.data.Field)
|
||||
|
||||
<input type="text" value="@field.value" name="@field.name" id="@field.id" />
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
<select id="@field.id" name="@field.name">
|
||||
@default.map { d =>
|
||||
<option class="blank" value=""></option>
|
||||
<option value=""></option>
|
||||
}
|
||||
@options.map { v =>
|
||||
<option value="@v._1" @(if(field.value == Some(v._1.toString)) "selected" else "")>@v._2</option>
|
||||
|
|
|
@ -38,7 +38,6 @@ object Main {
|
|||
case "game-per-day" :: days :: Nil ⇒ games.perDay(parseIntOption(days) err "days: Int")
|
||||
case "wiki-fetch" :: Nil ⇒ wiki.fetch
|
||||
case "search-reset" :: Nil ⇒ search.reset
|
||||
case "search-test" :: Nil ⇒ search.test
|
||||
case _ ⇒
|
||||
putStrLn("Unknown command: " + args.mkString(" "))
|
||||
}
|
||||
|
|
|
@ -8,12 +8,6 @@ case class Search(env: SearchEnv) {
|
|||
|
||||
def reset: IO[Unit] = env.indexer.rebuildAll
|
||||
|
||||
def test: IO[Unit] = env.indexer toGames {
|
||||
env.indexer search Query.test.searchRequest(0, 10)
|
||||
} flatMap { games ⇒
|
||||
putStrLn(games map showGame mkString "\n")
|
||||
}
|
||||
|
||||
private def showGame(game: lila.game.DbGame) =
|
||||
game.id + " " + game.turns //+ " " + game
|
||||
}
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
$(function() {
|
||||
|
||||
var $form = $("form.search");
|
||||
var $usernames = $form.find(".usernames input");
|
||||
var $winnerRow = $form.find(".winner");
|
||||
var $winner = $winnerRow.find("select");
|
||||
var $result = $(".search_result");
|
||||
|
||||
function realtimeResults() {
|
||||
$("div.search_status").text("Searching...");
|
||||
$result.load(
|
||||
$form.attr("action") + "?" + $form.serialize() + " .search_result",
|
||||
function() {
|
||||
|
@ -11,27 +15,76 @@ $(function() {
|
|||
$result.find('.search_infinitescroll:has(.pager a)').each(function() {
|
||||
var $next = $(this).find(".pager a:last")
|
||||
$next.attr("href", $next.attr("href") + "&" + $form.serialize());
|
||||
$(this).infinitescroll({
|
||||
navSelector: ".pager",
|
||||
nextSelector: $next,
|
||||
itemSelector: ".search_infinitescroll .paginated_element",
|
||||
loading: {
|
||||
msgText: "",
|
||||
img: "/assets/images/hloader3.gif",
|
||||
finishedMsg: "---"
|
||||
}
|
||||
}, function() {
|
||||
$("#infscr-loading").remove();
|
||||
$('body').trigger('lichess.content_loaded');
|
||||
});
|
||||
$(this).infinitescroll({
|
||||
navSelector: ".pager",
|
||||
nextSelector: $next,
|
||||
itemSelector: ".search_infinitescroll .paginated_element",
|
||||
loading: {
|
||||
msgText: "",
|
||||
img: "/assets/images/hloader3.gif",
|
||||
finishedMsg: "---"
|
||||
}
|
||||
}, function() {
|
||||
$("#infscr-loading").remove();
|
||||
$('body').trigger('lichess.content_loaded');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function winnerChoices() {
|
||||
var options = ["<option value=''></option>"];
|
||||
$usernames.each(function() {
|
||||
var user = $.trim($(this).val());
|
||||
if (user.length > 1) {
|
||||
options.push("<option value='"+user+"'>"+user+"</option>");
|
||||
}
|
||||
});
|
||||
$winner.html(options.join(""));
|
||||
$winnerRow.toggle(options.length > 1);
|
||||
}
|
||||
|
||||
$form.find("select").change(realtimeResults);
|
||||
$form.find("input").change(realtimeResults);
|
||||
$usernames.bind("keyup", winnerChoices).trigger("keyup");
|
||||
$usernames.bindWithDelay("keyup", realtimeResults, 400);
|
||||
|
||||
$form.find(".opponent select").change(function() {
|
||||
$form.find(".aiLevel").toggle($(this).val() == 1);
|
||||
}).trigger("change");
|
||||
});
|
||||
|
||||
// https://github.com/bgrins/bindWithDelay/blob/master/bindWithDelay.js
|
||||
$.fn.bindWithDelay = function( type, data, fn, timeout, throttle ) {
|
||||
|
||||
if ( $.isFunction( data ) ) {
|
||||
throttle = timeout;
|
||||
timeout = fn;
|
||||
fn = data;
|
||||
data = undefined;
|
||||
}
|
||||
|
||||
// Allow delayed function to be removed with fn in unbind function
|
||||
fn.guid = fn.guid || ($.guid && $.guid++);
|
||||
|
||||
// Bind each separately so that each element has its own delay
|
||||
return this.each(function() {
|
||||
|
||||
var wait = null;
|
||||
|
||||
function cb() {
|
||||
var e = $.extend(true, { }, arguments[0]);
|
||||
var ctx = this;
|
||||
var throttler = function() {
|
||||
wait = null;
|
||||
fn.apply(ctx, [e]);
|
||||
};
|
||||
|
||||
if (!throttle) { clearTimeout(wait); wait = null; }
|
||||
if (!wait) { wait = setTimeout(throttler, timeout); }
|
||||
}
|
||||
|
||||
cb.guid = fn.guid;
|
||||
|
||||
$(this).bind(type, data, cb);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -2,14 +2,6 @@ form.search {
|
|||
padding: 10px 25px;
|
||||
}
|
||||
|
||||
div.nb_results {
|
||||
margin-top: 10px;
|
||||
padding: 10px 25px;
|
||||
background: #f4f4f4;
|
||||
border: 1px solid #e4e4e4;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
form.search td {
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
@ -39,7 +31,8 @@ form.search .single select {
|
|||
width: 99%;
|
||||
}
|
||||
|
||||
form.search .half select {
|
||||
form.search .half select,
|
||||
form.search .half input {
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
|
@ -55,13 +48,16 @@ form.search th.help {
|
|||
padding-left: 10px;
|
||||
}
|
||||
|
||||
div.no_result {
|
||||
padding: 10px;
|
||||
font-size: 1.3em;
|
||||
font-style: italic;
|
||||
text-align: center
|
||||
div.search_status {
|
||||
margin-top: 10px;
|
||||
padding: 10px 25px;
|
||||
background: #f4f4f4;
|
||||
border-top: 1px solid #e4e4e4;
|
||||
border-bottom: 1px solid #e4e4e4;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
/* Errors */
|
||||
form.search .error {
|
||||
margin-left: 160px;
|
||||
|
|
Loading…
Reference in New Issue