lookup puzzles generated from games of a player - closes #8025
parent
cb011dc5b6
commit
b22e323149
|
@ -96,6 +96,16 @@ final class Puzzle(
|
|||
}
|
||||
}
|
||||
|
||||
def ofPlayer(name: Option[String], page: Int) =
|
||||
Open { implicit ctx =>
|
||||
val fixed = name.map(_.trim).filter(_.nonEmpty)
|
||||
fixed.??(env.user.repo.named) orElse fuccess(ctx.me) flatMap { user =>
|
||||
user.?? { env.puzzle.api.puzzle.of(_, page) dmap some } map { puzzles =>
|
||||
Ok(views.html.puzzle.ofPlayer(~fixed, user, puzzles))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def onComplete[A](form: Form[RoundData])(id: Puz.Id, theme: PuzzleTheme, mobileBc: Boolean)(implicit
|
||||
ctx: BodyContext[A]
|
||||
) = {
|
||||
|
|
|
@ -51,6 +51,9 @@ object bits {
|
|||
),
|
||||
a(cls := active.active("history"), href := routes.Puzzle.history(1))(
|
||||
trans.puzzle.history()
|
||||
),
|
||||
a(cls := active.active("player"), href := routes.Puzzle.ofPlayer())(
|
||||
"From my games"
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
package views
|
||||
package html.puzzle
|
||||
|
||||
import controllers.routes
|
||||
import play.api.i18n.Lang
|
||||
|
||||
import lila.api.Context
|
||||
import lila.app.templating.Environment._
|
||||
import lila.app.ui.ScalatagsTemplate._
|
||||
import lila.common.paginator.Paginator
|
||||
import lila.puzzle.Puzzle
|
||||
import lila.puzzle.PuzzleTheme
|
||||
import lila.user.User
|
||||
|
||||
object ofPlayer {
|
||||
|
||||
def apply(query: String, user: Option[User], puzzles: Option[Paginator[Puzzle]])(implicit ctx: Context) =
|
||||
views.html.base.layout(
|
||||
title = user.fold("Lookup puzzles from a player's games")(u => s"Puzzles from ${u.username}' games"),
|
||||
moreCss = cssTag("puzzle.page"),
|
||||
moreJs = infiniteScrollTag
|
||||
)(
|
||||
main(cls := "page-menu")(
|
||||
bits.pageMenu("player"),
|
||||
div(cls := "page-menu__content puzzle-of-player box box-pad")(
|
||||
form(
|
||||
action := routes.Puzzle.ofPlayer(),
|
||||
method := "get",
|
||||
cls := "form3 puzzle-of-player__form complete-parent"
|
||||
)(
|
||||
st.input(
|
||||
name := "name",
|
||||
value := query,
|
||||
cls := "form-control user-autocomplete",
|
||||
placeholder := "Lichess username",
|
||||
autocomplete := "off",
|
||||
dataTag := "span",
|
||||
autofocus
|
||||
),
|
||||
submitButton(cls := "button")("Search puzzles")
|
||||
),
|
||||
div(cls := "puzzle-of-player__results")(
|
||||
(user, puzzles) match {
|
||||
case (Some(u), Some(pager)) =>
|
||||
frag(
|
||||
p(strong(pager.nbResults), " puzzles found in ", userLink(u), " games."),
|
||||
div(cls := "puzzle-of-player__pager infinite-scroll")(
|
||||
pager.currentPageResults.map { puzzle =>
|
||||
div(cls := "puzzle-of-player__puzzle")(
|
||||
views.html.board.bits.mini(
|
||||
fen = puzzle.fenAfterInitialMove,
|
||||
color = puzzle.color,
|
||||
lastMove = puzzle.line.head.uci
|
||||
)(
|
||||
a(
|
||||
cls := s"puzzle-of-player__puzzle__board",
|
||||
href := routes.Puzzle.show(puzzle.id.value)
|
||||
)
|
||||
),
|
||||
span(cls := "puzzle-of-player__puzzle__meta")(
|
||||
span(cls := "puzzle-of-player__puzzle__id", s"#${puzzle.id}"),
|
||||
span(cls := "puzzle-of-player__puzzle__rating", puzzle.glicko.intRating)
|
||||
)
|
||||
)
|
||||
},
|
||||
pagerNext(pager, np => s"${routes.Puzzle.ofPlayer(u.username.some, np).url}")
|
||||
)
|
||||
)
|
||||
case (_, _) => emptyFrag
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
|
@ -43,7 +43,14 @@ object theme {
|
|||
span(pt.theme.description())
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
cat.key == "puzzle:origin" option
|
||||
a(cls := "puzzle-themes__link", href := routes.Puzzle.ofPlayer())(
|
||||
span(
|
||||
h3("Player games"),
|
||||
span("Lookup puzzles generated from your games, or from another player's games.")
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
},
|
||||
|
|
|
@ -74,8 +74,9 @@ object storm {
|
|||
|
||||
def dashboard(user: User, history: Paginator[StormDay], high: StormHigh)(implicit ctx: Context) =
|
||||
views.html.base.layout(
|
||||
title = s"${user.username} Puzzle Storm",
|
||||
moreCss = frag(cssTag("storm.dashboard")),
|
||||
title = s"${user.username} Puzzle Storm"
|
||||
moreJs = infiniteScrollTag
|
||||
)(
|
||||
main(cls := "storm-dashboard page-small")(
|
||||
div(cls := "storm-dashboard__high box box-pad")(
|
||||
|
@ -106,7 +107,7 @@ object storm {
|
|||
th(trans.storm.runs())
|
||||
)
|
||||
),
|
||||
tbody(
|
||||
tbody(cls := "infinite-scroll")(
|
||||
history.currentPageResults.map { day =>
|
||||
tr(
|
||||
td(showDate(day._id.day.toDate)),
|
||||
|
|
|
@ -85,6 +85,7 @@ GET /training/daily controllers.Puzzle.daily
|
|||
GET /training/frame controllers.Puzzle.frame
|
||||
GET /training/export/gif/thumbnail/:id.gif controllers.Export.puzzleThumbnail(id: String)
|
||||
GET /training/themes controllers.Puzzle.themes
|
||||
GET /training/of-player controllers.Puzzle.ofPlayer(name: Option[String] ?= None, page: Int ?= 1)
|
||||
GET /training/dashboard/$days<\d+> controllers.Puzzle.dashboard(days: Int, path: String = "home")
|
||||
GET /training/dashboard/$days<\d+>/:path controllers.Puzzle.dashboard(days: Int, path: String)
|
||||
GET /training/replay/$days<\d+>/:theme controllers.Puzzle.replay(days: Int, theme: String)
|
||||
|
|
|
@ -4,8 +4,11 @@ import cats.implicits._
|
|||
import org.joda.time.DateTime
|
||||
import scala.concurrent.duration._
|
||||
|
||||
import lila.common.paginator.Paginator
|
||||
import lila.common.config.MaxPerPage
|
||||
import lila.db.AsyncColl
|
||||
import lila.db.dsl._
|
||||
import lila.db.paginator.Adapter
|
||||
import lila.memo.CacheApi
|
||||
import lila.user.{ User, UserRepo }
|
||||
|
||||
|
@ -25,6 +28,20 @@ final class PuzzleApi(
|
|||
|
||||
def delete(id: Puzzle.Id): Funit =
|
||||
colls.puzzle(_.delete.one($id(id.value))).void
|
||||
|
||||
def of(user: User, page: Int): Fu[Paginator[Puzzle]] =
|
||||
colls.puzzle { coll =>
|
||||
Paginator(
|
||||
adapter = new Adapter[Puzzle](
|
||||
collection = coll,
|
||||
selector = $doc("users" -> user.id),
|
||||
projection = none,
|
||||
sort = $sort asc "glicko.r"
|
||||
),
|
||||
page,
|
||||
MaxPerPage(30)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
object round {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
.puzzle-themes {
|
||||
|
||||
h2 {
|
||||
@extend %box-padding-horiz, %roboto;
|
||||
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
|
@ -9,9 +9,12 @@
|
|||
display: grid;
|
||||
margin-left: var(--box-padding);
|
||||
grid-template-columns: repeat(auto-fill, minmax(50ch, 1fr));
|
||||
|
||||
@include breakpoint($mq-not-xx-small) {
|
||||
grid-template-columns: repeat(auto-fill, minmax(40ch, 1fr));
|
||||
}
|
||||
|
||||
|
||||
&.puzzle-recommended {
|
||||
display: block;
|
||||
}
|
||||
|
@ -19,19 +22,23 @@
|
|||
|
||||
&__link {
|
||||
@extend %box-radius;
|
||||
|
||||
display: flex;
|
||||
padding: 1.5em 1em 1.5em 0;
|
||||
|
||||
&::before {
|
||||
@extend %data-icon;
|
||||
|
||||
content: '-';
|
||||
color: $c-font-dimmer;
|
||||
flex: 0 0 1.6em;
|
||||
text-align: center;
|
||||
font-size: 4.5em;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: mix($c-bg-box, $c-link, 90%);
|
||||
|
||||
&::before {
|
||||
color: $c-primary;
|
||||
}
|
||||
|
@ -41,9 +48,11 @@
|
|||
&::before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
|
||||
> span {
|
||||
flex: 1 1 100%;
|
||||
margin: 0;
|
||||
|
@ -55,8 +64,10 @@
|
|||
display: block;
|
||||
line-height: 1em;
|
||||
margin: .1em 0 .25em 0;
|
||||
|
||||
em {
|
||||
@extend %roboto;
|
||||
|
||||
color: $c-font-dimmer;
|
||||
font-size: .8em;
|
||||
margin-left: .7ch;
|
||||
|
@ -66,19 +77,24 @@
|
|||
|
||||
.puzzle-recommended & {
|
||||
@extend %box-neat;
|
||||
|
||||
font-size: 1.2em;
|
||||
background: mix($c-bg-box, $c-good, 80%);
|
||||
color: $c-good;
|
||||
margin: 2em 4em;
|
||||
|
||||
&::before {
|
||||
color: $c-good;
|
||||
}
|
||||
|
||||
> span {
|
||||
color: $c-font;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: mix($c-bg-box, $c-good, 74%);
|
||||
}
|
||||
|
||||
@include breakpoint($mq-not-small) {
|
||||
margin: 2em var(--box-padding) 2em 0;
|
||||
}
|
||||
|
@ -87,6 +103,54 @@
|
|||
|
||||
&__db {
|
||||
@extend %box-padding;
|
||||
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.puzzle-of-player {
|
||||
&__form {
|
||||
input {
|
||||
margin-right: 1em;
|
||||
width: 30ch;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
&__pager {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(12em, 1fr));
|
||||
align-content: start;
|
||||
}
|
||||
|
||||
&__results {
|
||||
p {
|
||||
font-size: 1.5em;
|
||||
margin: 2em 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__puzzle {
|
||||
padding: .4em;
|
||||
|
||||
|
||||
&__meta {
|
||||
@extend %flex-between;
|
||||
font-size: .9em;
|
||||
padding: 0 .3em;
|
||||
}
|
||||
|
||||
&__rating {
|
||||
|
||||
}
|
||||
|
||||
&__id {
|
||||
opacity: 0;
|
||||
@include transition;
|
||||
}
|
||||
|
||||
&:hover .puzzle-of-player__puzzle__id {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
@import "../../../common/css/plugin";
|
||||
@import "../../../common/css/form/form3";
|
||||
|
||||
@import "../page";
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
@import "../../../common/css/plugin";
|
||||
@import "../../../common/css/vendor/chessground/coords";
|
||||
@import "../../../common/css/layout/uniboard";
|
||||
@import "../../../common/css/component/board-resize";
|
||||
@import "../../../common/css/component/bar-glider";
|
||||
|
|
Loading…
Reference in New Issue