base views

This commit is contained in:
Evgenii Alekseev 2019-10-17 20:08:28 +03:00
parent d5233361e5
commit 9668a0edd1
9 changed files with 272 additions and 16 deletions

View File

@ -23,7 +23,7 @@ class BiSView(override val storage: ActorRef, ariyala: ActorRef)(implicit timeou
get { get {
complete { complete {
bis(partyId, None).map { players => bis(partyId, None).map { players =>
BiSView.template(partyId, players, Piece.available, None) BiSView.template(partyId, players, None)
}.map { text => }.map { text =>
(StatusCodes.OK, RootView.toHtml(text)) (StatusCodes.OK, RootView.toHtml(text))
} }
@ -53,8 +53,10 @@ class BiSView(override val storage: ActorRef, ariyala: ActorRef)(implicit timeou
maybePiece: Option[String], maybeIsTome: Option[String], maybePiece: Option[String], maybeIsTome: Option[String],
maybeLink: Option[String], action: String) maybeLink: Option[String], action: String)
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Unit] = { (implicit executionContext: ExecutionContext, timeout: Timeout): Future[Unit] = {
import me.arcanis.ffxivbis.utils.Implicits._
def getPiece(playerId: PlayerId, piece: String) = def getPiece(playerId: PlayerId, piece: String) =
Try(Piece(piece, maybeIsTome.isDefined, playerId.job)).toOption Try(Piece(piece, maybeIsTome, playerId.job)).toOption
PlayerId(partyId, player) match { PlayerId(partyId, player) match {
case Some(playerId) => (maybePiece, action, maybeLink) match { case Some(playerId) => (maybePiece, action, maybeLink) match {
@ -77,7 +79,7 @@ class BiSView(override val storage: ActorRef, ariyala: ActorRef)(implicit timeou
object BiSView { object BiSView {
import scalatags.Text.all._ import scalatags.Text.all._
def template(partyId: String, party: Seq[Player], pieces: Seq[String], error: Option[String]): String = def template(partyId: String, party: Seq[Player], error: Option[String]): String =
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">" + "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">" +
html(lang:="en", html(lang:="en",
head( head(
@ -95,7 +97,7 @@ object BiSView {
select(name:="player", id:="player", title:="player") select(name:="player", id:="player", title:="player")
(for (player <- party) yield option(player.playerId.toString)), (for (player <- party) yield option(player.playerId.toString)),
select(name:="piece", id:="piece", title:="piece") select(name:="piece", id:="piece", title:="piece")
(for (piece <- pieces) yield option(piece)), (for (piece <- Piece.available) yield option(piece)),
input(name:="is_tome", id:="is_tome", title:="is tome", `type`:="checkbox"), input(name:="is_tome", id:="is_tome", title:="is tome", `type`:="checkbox"),
label(`for`:="is_tome")("is tome gear"), label(`for`:="is_tome")("is tome gear"),
input(name:="action", id:="action", `type`:="hidden", value:="add"), input(name:="action", id:="action", `type`:="hidden", value:="add"),

View File

@ -0,0 +1,120 @@
package me.arcanis.ffxivbis.http.view
import akka.actor.ActorRef
import akka.http.scaladsl.model.StatusCodes
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Route
import akka.util.Timeout
import me.arcanis.ffxivbis.http.{Authorization, LootHelper}
import me.arcanis.ffxivbis.models.{Piece, PlayerIdWithCounters}
import scala.concurrent.{ExecutionContext, Future}
import scala.util.{Failure, Success, Try}
class LootSuggestView(override val storage: ActorRef)(implicit timeout: Timeout)
extends LootHelper(storage) with Authorization {
def route: Route = getIndex ~ suggestLoot
def getIndex: Route =
path("party" / Segment / "suggest") { partyId: String =>
extractExecutionContext { implicit executionContext =>
authenticateBasicBCrypt(s"party $partyId", authGet(partyId)) { _ =>
get {
complete {
val text = LootSuggestView.template(partyId, Seq.empty, None, None)
(StatusCodes.OK, RootView.toHtml(text))
}
}
}
}
}
def suggestLoot: Route =
path("party" / Segment / "suggest") { partyId: String =>
extractExecutionContext { implicit executionContext =>
authenticateBasicBCrypt(s"party $partyId", authGet(partyId)) { _ =>
post {
formFields("piece".as[String], "is_tome".as[String].?) { (piece, maybeTome) =>
import me.arcanis.ffxivbis.utils.Implicits._
val maybePiece = Try(Piece(piece, maybeTome)).toOption
onComplete(suggestLootCall(partyId, maybePiece)) {
case Success(players) =>
val text = LootSuggestView.template(partyId, players, maybePiece, None)
complete(StatusCodes.OK, RootView.toHtml(text))
case Failure(exception) =>
val text = LootSuggestView.template(partyId, Seq.empty, maybePiece, Some(exception.getMessage))
complete(StatusCodes.OK, RootView.toHtml(text))
}
}
}
}
}
}
private def suggestLootCall(partyId: String, maybePiece: Option[Piece])
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Seq[PlayerIdWithCounters]] =
maybePiece match {
case Some(piece) => suggestPiece(partyId, piece)
case _ => Future.failed(new Error(s"Could not construct piece from `$maybePiece`"))
}
}
object LootSuggestView {
import scalatags.Text.all._
def template(partyId: String, party: Seq[PlayerIdWithCounters], piece: Option[Piece], error: Option[String]): String =
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">" +
html(lang:="en",
head(
title:="Suggest loot",
link(rel:="stylesheet", `type`:="text/css", href:="/static/styles.css")
),
body(
h2("Suggest loot"),
ErrorView.template(error),
SearchLineView.template,
form(action:=s"/party/$partyId/suggest", method:="post")(
select(name:="piece", id:="piece", title:="piece")
(for (piece <- Piece.available) yield option(piece)),
input(name:="is_tome", id:="is_tome", title:="is tome", `type`:="checkbox"),
label(`for`:="is_tome")("is tome gear"),
input(name:="suggest", id:="suggest", `type`:="submit", value:="suggest")
),
table(id:="result")(
tr(
th("player"),
th("is required"),
th("these pieces looted"),
th("total bis pieces looted"),
th("total pieces looted"),
th("")
),
for (player <- party) yield tr(
td(`class`:="include_search")(player.playerId.toString),
td(player.isRequiredToString),
td(player.lootCount),
td(player.lootCountBiS),
td(player.lootCountTotal),
td(
form(action:=s"/party/$partyId/loot", method:="post")(
input(name:="player", id:="player", `type`:="hidden", value:=player.playerId.toString),
input(name:="piece", id:="piece", `type`:="hidden", value:=piece.map(_.piece).getOrElse("")),
input(name:="is_tome", id:="is_tome", `type`:="hidden", value:=piece.map(_.isTomeToString).getOrElse("")),
input(name:="action", id:="action", `type`:="hidden", value:="add"),
input(name:="add", id:="add", `type`:="submit", value:="add")
)
)
)
),
ExportToCSVView.template,
script(src:="/static/table_search.js", `type`:="text/javascript")
)
)
}

View File

@ -23,7 +23,7 @@ class LootView (override val storage: ActorRef)(implicit timeout: Timeout)
get { get {
complete { complete {
loot(partyId, None).map { players => loot(partyId, None).map { players =>
LootView.template(partyId, players, Piece.available, None) LootView.template(partyId, players, None)
}.map { text => }.map { text =>
(StatusCodes.OK, RootView.toHtml(text)) (StatusCodes.OK, RootView.toHtml(text))
} }
@ -53,8 +53,10 @@ class LootView (override val storage: ActorRef)(implicit timeout: Timeout)
maybePiece: String, maybeIsTome: Option[String], maybePiece: String, maybeIsTome: Option[String],
action: String) action: String)
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Unit] = { (implicit executionContext: ExecutionContext, timeout: Timeout): Future[Unit] = {
import me.arcanis.ffxivbis.utils.Implicits._
def getPiece(playerId: PlayerId) = def getPiece(playerId: PlayerId) =
Try(Piece(maybePiece, maybeIsTome.isDefined, playerId.job)).toOption Try(Piece(maybePiece, maybeIsTome, playerId.job)).toOption
PlayerId(partyId, player) match { PlayerId(partyId, player) match {
case Some(playerId) => (getPiece(playerId), action) match { case Some(playerId) => (getPiece(playerId), action) match {
@ -70,7 +72,7 @@ class LootView (override val storage: ActorRef)(implicit timeout: Timeout)
object LootView { object LootView {
import scalatags.Text.all._ import scalatags.Text.all._
def template(partyId: String, party: Seq[Player], pieces: Seq[String], error: Option[String]): String = def template(partyId: String, party: Seq[Player], error: Option[String]): String =
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">" + "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">" +
html(lang:="en", html(lang:="en",
head( head(
@ -88,7 +90,7 @@ object LootView {
select(name:="player", id:="player", title:="player") select(name:="player", id:="player", title:="player")
(for (player <- party) yield option(player.playerId.toString)), (for (player <- party) yield option(player.playerId.toString)),
select(name:="piece", id:="piece", title:="piece") select(name:="piece", id:="piece", title:="piece")
(for (piece <- pieces) yield option(piece)), (for (piece <- Piece.available) yield option(piece)),
input(name:="is_tome", id:="is_tome", title:="is tome", `type`:="checkbox"), input(name:="is_tome", id:="is_tome", title:="is tome", `type`:="checkbox"),
label(`for`:="is_tome")("is tome gear"), label(`for`:="is_tome")("is tome gear"),
input(name:="action", id:="action", `type`:="hidden", value:="add"), input(name:="action", id:="action", `type`:="hidden", value:="add"),

View File

@ -0,0 +1,124 @@
package me.arcanis.ffxivbis.http.view
import akka.actor.ActorRef
import akka.http.scaladsl.model.StatusCodes
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Route
import akka.util.Timeout
import me.arcanis.ffxivbis.http.{Authorization, PlayerHelper}
import me.arcanis.ffxivbis.models.{BiS, Job, Player, PlayerId, PlayerIdWithCounters}
import scala.concurrent.{ExecutionContext, Future}
class PlayerView(override val storage: ActorRef, ariyala: ActorRef)(implicit timeout: Timeout)
extends PlayerHelper(storage, ariyala) with Authorization {
def route: Route = getParty ~ modifyParty
def getParty: Route =
path("party" / Segment) { partyId: String =>
extractExecutionContext { implicit executionContext =>
authenticateBasicBCrypt(s"party $partyId", authGet(partyId)) { _ =>
get {
complete {
getPlayers(partyId, None).map { players =>
PlayerView.template(partyId, players.map(_.withCounters(None)), None)
}.map { text =>
(StatusCodes.OK, RootView.toHtml(text))
}
}
}
}
}
}
def modifyParty: Route =
path("party" / Segment) { partyId: String =>
extractExecutionContext { implicit executionContext =>
authenticateBasicBCrypt(s"party $partyId", authPost(partyId)) { _ =>
post {
formFields("nick".as[String], "job".as[String], "priority".as[Int].?, "link".as[String].?, "action".as[String]) {
(nick, job, maybePriority, maybeLink, action) =>
onComplete(modifyPartyCall(partyId, nick, job, maybePriority, maybeLink, action)) {
case _ => redirect(s"/party/$partyId", StatusCodes.Found)
}
}
}
}
}
}
private def modifyPartyCall(partyId: String, nick: String, job: String,
maybePriority: Option[Int], maybeLink: Option[String],
action: String)
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Unit] = {
def maybePlayerId = PlayerId(partyId, Some(nick), Some(job))
def player(playerId: PlayerId) =
Player(partyId, playerId.job, playerId.nick, BiS(), Seq.empty, maybeLink, maybePriority.getOrElse(0))
(action, maybePlayerId) match {
case ("add", Some(playerId)) => addPlayer(player(playerId)).map(_ => ())
case ("remove", Some(playerId)) => removePlayer(playerId).map(_ => ())
case _ => Future.failed(new Error(s"Could not perform $action with $nick ($job)"))
}
}
}
object PlayerView {
import scalatags.Text.all._
def template(partyId: String, party: Seq[PlayerIdWithCounters], error: Option[String]): String =
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">" +
html(lang:="en",
head(
title:="Party",
link(rel:="stylesheet", `type`:="text/css", href:="/static/styles.css")
),
body(
h2("Party"),
ErrorView.template(error),
SearchLineView.template,
form(action:=s"/party/$partyId", method:="post")(
input(name:="nick", id:="nick", placeholder:="nick", title:="nick", `type`:="nick"),
select(name:="job", id:="job", title:="job")
(for (job <- Job.groupAll) yield option(job.toString)),
input(name:="link", id:="link", placeholder:="player bis link", title:="link", `type`:="text"),
input(name:="prioiry", id:="priority", placeholder:="priority", title:="priority", `type`:="number", value:="0"),
input(name:="action", id:="action", `type`:="hidden", value:="add"),
input(name:="add", id:="add", `type`:="submit", value:="add")
),
table(id:="result")(
tr(
th("nick"),
th("job"),
th("total bis pieces looted"),
th("total pieces looted"),
th("priority"),
th("")
),
for (player <- party) yield tr(
td(`class`:="include_search")(player.nick),
td(`class`:="include_search")(player.job.toString),
td(player.lootCountBiS),
td(player.lootCountTotal),
td(player.priority),
td(
form(action:=s"/party/$partyId", method:="post")(
input(name:="nick", id:="nick", `type`:="hidden", value:=player.nick),
input(name:="job", id:="job", `type`:="hidden", value:=player.job.toString),
input(name:="action", id:="action", `type`:="hidden", value:="remove"),
input(name:="remove", id:="remove", `type`:="submit", value:="x")
)
)
)
),
ExportToCSVView.template,
script(src:="/static/table_search.js", `type`:="text/javascript")
)
)
}

View File

@ -10,10 +10,12 @@ class RootView(storage: ActorRef, ariyala: ActorRef)(implicit timeout: Timeout)
private val biSView = new BiSView(storage, ariyala) private val biSView = new BiSView(storage, ariyala)
private val lootView = new LootView(storage) private val lootView = new LootView(storage)
private val lootSuggestView = new LootSuggestView(storage)
private val playerView = new PlayerView(storage, ariyala)
private val userView = new UserView(storage) private val userView = new UserView(storage)
def route: Route = def route: Route =
biSView.route ~ lootView.route ~ userView.route biSView.route ~ lootView.route ~ lootSuggestView.route ~ playerView.route ~ userView.route
} }
object RootView { object RootView {

View File

@ -14,7 +14,7 @@ import scala.util.Try
class UserView(override val storage: ActorRef)(implicit timeout: Timeout) class UserView(override val storage: ActorRef)(implicit timeout: Timeout)
extends UserHelper(storage) with Authorization { extends UserHelper(storage) with Authorization {
def route: Route = getUsers def route: Route = getUsers ~ modifyUsers
def getUsers: Route = def getUsers: Route =
path("party" / Segment / "users") { partyId: String => path("party" / Segment / "users") { partyId: String =>
@ -85,8 +85,8 @@ object UserView {
SearchLineView.template, SearchLineView.template,
form(action:=s"/party/$partyId/users", method:="post")( form(action:=s"/party/$partyId/users", method:="post")(
input(name:="username", id:="username", title:="username", placeholder:="username", `type`:="text"), input(name:="username", id:="username", placeholder:="username", title:="username", `type`:="text"),
input(name:="password", id:="password", title:="password", placeholder:="password", `type`:="password"), input(name:="password", id:="password", placeholder:="password", title:="password", `type`:="password"),
select(name:="permission", id:="permission", title:="permission")(option("get"), option("post")), select(name:="permission", id:="permission", title:="permission")(option("get"), option("post")),
input(name:="action", id:="action", `type`:="hidden", value:="add"), input(name:="action", id:="action", `type`:="hidden", value:="add"),
input(name:="add", id:="add", `type`:="submit", value:="add") input(name:="add", id:="add", `type`:="submit", value:="add")

View File

@ -1,5 +1,6 @@
package me.arcanis.ffxivbis.models package me.arcanis.ffxivbis.models
import scala.util.Try
import scala.util.matching.Regex import scala.util.matching.Regex
trait PlayerIdBase { trait PlayerIdBase {
@ -14,13 +15,13 @@ case class PlayerId(partyId: String, job: Job.Job, nick: String) extends PlayerI
object PlayerId { object PlayerId {
def apply(partyId: String, maybeNick: Option[String], maybeJob: Option[String]): Option[PlayerId] = def apply(partyId: String, maybeNick: Option[String], maybeJob: Option[String]): Option[PlayerId] =
(maybeNick, maybeJob) match { (maybeNick, maybeJob) match {
case (Some(nick), Some(job)) => Some(PlayerId(partyId, Job.fromString(job), nick)) case (Some(nick), Some(job)) => Try(PlayerId(partyId, Job.fromString(job), nick)).toOption
case _ => None case _ => None
} }
private val prettyPlayerIdRegex: Regex = "^(.*) \\(([A-Z]{3})\\)$".r private val prettyPlayerIdRegex: Regex = "^(.*) \\(([A-Z]{3})\\)$".r
def apply(partyId: String, player: String): Option[PlayerId] = player match { def apply(partyId: String, player: String): Option[PlayerId] = player match {
case s"${prettyPlayerIdRegex(nick, job)}" => Some(PlayerId(partyId, Job.fromString(job), nick)) case s"${prettyPlayerIdRegex(nick, job)}" => Try(PlayerId(partyId, Job.fromString(job), nick)).toOption
case _ => None case _ => None
} }
} }

View File

@ -12,10 +12,10 @@ case class PlayerIdWithCounters(partyId: String,
extends PlayerIdBase { extends PlayerIdBase {
import PlayerIdWithCounters._ import PlayerIdWithCounters._
def playerId: PlayerId = PlayerId(partyId, job, nick)
def gt(that: PlayerIdWithCounters, orderBy: Seq[String]): Boolean = def gt(that: PlayerIdWithCounters, orderBy: Seq[String]): Boolean =
withCounters(orderBy) > that.withCounters(orderBy) withCounters(orderBy) > that.withCounters(orderBy)
def isRequiredToString: String = if (isRequired) "yes" else "no"
def playerId: PlayerId = PlayerId(partyId, job, nick)
private val counters: Map[String, Int] = Map( private val counters: Map[String, Int] = Map(
"isRequired" -> (if (isRequired) 1 else 0), "isRequired" -> (if (isRequired) 1 else 0),

View File

@ -9,6 +9,11 @@ import scala.concurrent.duration.FiniteDuration
import scala.language.implicitConversions import scala.language.implicitConversions
object Implicits { object Implicits {
implicit def getBooleanFromOptionString(maybeYes: Option[String]): Boolean = maybeYes match {
case Some("yes" | "on") => true
case _ => false
}
implicit def getFiniteDuration(duration: Duration): Timeout = implicit def getFiniteDuration(duration: Duration): Timeout =
FiniteDuration(duration.toNanos, TimeUnit.NANOSECONDS) FiniteDuration(duration.toNanos, TimeUnit.NANOSECONDS)
} }