feat(ui): add main menu navbar and join game functionality (#35)
Reviewed-on: #35 Reviewed-by: lq64 <lq@blackhole.local> Co-authored-by: Janis <janis.e.20@gmx.de> Co-committed-by: Janis <janis.e.20@gmx.de>
This commit is contained in:
@@ -11,7 +11,7 @@ get {
|
|||||||
}
|
}
|
||||||
|
|
||||||
params:path {
|
params:path {
|
||||||
id: uZDNZA
|
id: BZvtJ3
|
||||||
}
|
}
|
||||||
|
|
||||||
settings {
|
settings {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ post {
|
|||||||
}
|
}
|
||||||
|
|
||||||
params:path {
|
params:path {
|
||||||
id: uZDNZA
|
id: nR1o3n
|
||||||
}
|
}
|
||||||
|
|
||||||
settings {
|
settings {
|
||||||
|
|||||||
Submodule knockoutwhist updated: e0e45c4b43...b9a7b0a2af
@@ -111,7 +111,10 @@ class IngameController @Inject()(
|
|||||||
cardIdOpt match {
|
cardIdOpt match {
|
||||||
case Some(cardId) =>
|
case Some(cardId) =>
|
||||||
val result = Try {
|
val result = Try {
|
||||||
g.playCard(g.getUserSession(request.user.id), cardId.toInt)
|
val session = g.getUserSession(request.user.id)
|
||||||
|
session.lock.lock()
|
||||||
|
g.playCard(session, cardId.toInt)
|
||||||
|
session.lock.unlock()
|
||||||
}
|
}
|
||||||
if (result.isSuccess) {
|
if (result.isSuccess) {
|
||||||
NoContent
|
NoContent
|
||||||
@@ -146,9 +149,15 @@ class IngameController @Inject()(
|
|||||||
val result = Try {
|
val result = Try {
|
||||||
cardIdOpt match {
|
cardIdOpt match {
|
||||||
case Some(cardId) if cardId == "skip" =>
|
case Some(cardId) if cardId == "skip" =>
|
||||||
g.playDogCard(g.getUserSession(request.user.id), -1)
|
val session = g.getUserSession(request.user.id)
|
||||||
|
session.lock.lock()
|
||||||
|
g.playDogCard(session, -1)
|
||||||
|
session.lock.unlock()
|
||||||
case Some(cardId) =>
|
case Some(cardId) =>
|
||||||
g.playDogCard(g.getUserSession(request.user.id), cardId.toInt)
|
val session = g.getUserSession(request.user.id)
|
||||||
|
session.lock.lock()
|
||||||
|
g.playDogCard(session, cardId.toInt)
|
||||||
|
session.lock.unlock()
|
||||||
case None =>
|
case None =>
|
||||||
throw new IllegalArgumentException("cardId parameter is missing")
|
throw new IllegalArgumentException("cardId parameter is missing")
|
||||||
}
|
}
|
||||||
@@ -184,7 +193,10 @@ class IngameController @Inject()(
|
|||||||
trumpOpt match {
|
trumpOpt match {
|
||||||
case Some(trump) =>
|
case Some(trump) =>
|
||||||
val result = Try {
|
val result = Try {
|
||||||
g.selectTrump(g.getUserSession(request.user.id), trump.toInt)
|
val session = g.getUserSession(request.user.id)
|
||||||
|
session.lock.lock()
|
||||||
|
g.selectTrump(session, trump.toInt)
|
||||||
|
session.lock.unlock()
|
||||||
}
|
}
|
||||||
if (result.isSuccess) {
|
if (result.isSuccess) {
|
||||||
NoContent
|
NoContent
|
||||||
@@ -216,7 +228,10 @@ class IngameController @Inject()(
|
|||||||
tieOpt match {
|
tieOpt match {
|
||||||
case Some(tie) =>
|
case Some(tie) =>
|
||||||
val result = Try {
|
val result = Try {
|
||||||
|
val session = g.getUserSession(request.user.id)
|
||||||
|
session.lock.lock()
|
||||||
g.selectTie(g.getUserSession(request.user.id), tie.toInt)
|
g.selectTie(g.getUserSession(request.user.id), tie.toInt)
|
||||||
|
session.lock.unlock()
|
||||||
}
|
}
|
||||||
if (result.isSuccess) {
|
if (result.isSuccess) {
|
||||||
NoContent
|
NoContent
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class MainMenuController @Inject()(
|
|||||||
|
|
||||||
// Pass the request-handling function directly to authAction (no nested Action)
|
// Pass the request-handling function directly to authAction (no nested Action)
|
||||||
def mainMenu(): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] =>
|
def mainMenu(): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] =>
|
||||||
Ok("Main Menu for user: " + request.user.name)
|
Ok(views.html.mainmenu.navbar(Some(request.user)))
|
||||||
}
|
}
|
||||||
|
|
||||||
def index(): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] =>
|
def index(): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] =>
|
||||||
@@ -36,6 +36,22 @@ class MainMenuController @Inject()(
|
|||||||
)
|
)
|
||||||
Redirect(routes.IngameController.game(gameLobby.id))
|
Redirect(routes.IngameController.game(gameLobby.id))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def joinGame(): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] =>
|
||||||
|
val postData = request.body.asFormUrlEncoded
|
||||||
|
if (postData.isDefined) {
|
||||||
|
val gameId = postData.get.get("gameId").flatMap(_.headOption).getOrElse("")
|
||||||
|
val game = podManager.getGame(gameId)
|
||||||
|
game match {
|
||||||
|
case Some(g) =>
|
||||||
|
Redirect(routes.IngameController.joinGame(gameId))
|
||||||
|
case None =>
|
||||||
|
NotFound("Game not found")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
BadRequest("Invalid form submission")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def rules(): Action[AnyContent] = {
|
def rules(): Action[AnyContent] = {
|
||||||
Action { implicit request =>
|
Action { implicit request =>
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ class UserController @Inject()(
|
|||||||
if (sessionCookie.isDefined) {
|
if (sessionCookie.isDefined) {
|
||||||
sessionManager.invalidateSession(sessionCookie.get.value)
|
sessionManager.invalidateSession(sessionCookie.get.value)
|
||||||
}
|
}
|
||||||
NoContent.discardingCookies(DiscardingCookie("sessionId"))
|
Redirect(routes.UserController.login()).discardingCookies(DiscardingCookie("sessionId"))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -70,6 +70,9 @@ class GameLobby private(
|
|||||||
if (!sessionOpt.get.host) {
|
if (!sessionOpt.get.host) {
|
||||||
throw new NotHostException("Only the host can start the game!")
|
throw new NotHostException("Only the host can start the game!")
|
||||||
}
|
}
|
||||||
|
if (logic.getCurrentState != Lobby) {
|
||||||
|
throw new IllegalStateException("The game has already started!")
|
||||||
|
}
|
||||||
val playerNamesList = ListBuffer[AbstractPlayer]()
|
val playerNamesList = ListBuffer[AbstractPlayer]()
|
||||||
users.values.foreach { player =>
|
users.values.foreach { player =>
|
||||||
playerNamesList += PlayerFactory.createPlayer(player.name, player.id, HUMAN)
|
playerNamesList += PlayerFactory.createPlayer(player.name, player.id, HUMAN)
|
||||||
@@ -108,6 +111,7 @@ class GameLobby private(
|
|||||||
if (!PlayerUtil.canPlayCard(card, getRound, getTrick, player)) {
|
if (!PlayerUtil.canPlayCard(card, getRound, getTrick, player)) {
|
||||||
throw new CantPlayCardException("You can't play this card!")
|
throw new CantPlayCardException("You can't play this card!")
|
||||||
}
|
}
|
||||||
|
userSession.resetCanInteract()
|
||||||
logic.playerInputLogic.receivedCard(card)
|
logic.playerInputLogic.receivedCard(card)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,6 +133,7 @@ class GameLobby private(
|
|||||||
}
|
}
|
||||||
val hand = getHand(player)
|
val hand = getHand(player)
|
||||||
val card = hand.cards(cardIndex)
|
val card = hand.cards(cardIndex)
|
||||||
|
userSession.resetCanInteract()
|
||||||
logic.playerInputLogic.receivedDog(Some(card))
|
logic.playerInputLogic.receivedDog(Some(card))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,6 +146,7 @@ class GameLobby private(
|
|||||||
val player = getPlayerInteractable(userSession, InteractionType.TrumpSuit)
|
val player = getPlayerInteractable(userSession, InteractionType.TrumpSuit)
|
||||||
val trumpSuits = Suit.values.toList
|
val trumpSuits = Suit.values.toList
|
||||||
val selectedTrump = trumpSuits(trumpIndex)
|
val selectedTrump = trumpSuits(trumpIndex)
|
||||||
|
userSession.resetCanInteract()
|
||||||
logic.playerInputLogic.receivedTrumpSuit(selectedTrump)
|
logic.playerInputLogic.receivedTrumpSuit(selectedTrump)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,6 +157,7 @@ class GameLobby private(
|
|||||||
*/
|
*/
|
||||||
def selectTie(userSession: UserSession, tieNumber: Int): Unit = {
|
def selectTie(userSession: UserSession, tieNumber: Int): Unit = {
|
||||||
val player = getPlayerInteractable(userSession, InteractionType.TieChoice)
|
val player = getPlayerInteractable(userSession, InteractionType.TieChoice)
|
||||||
|
userSession.resetCanInteract()
|
||||||
logic.playerTieLogic.receivedTieBreakerCard(tieNumber)
|
logic.playerTieLogic.receivedTieBreakerCard(tieNumber)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,4 +28,8 @@ class UserSession(user: User, val host: Boolean) extends PlayerSession {
|
|||||||
|
|
||||||
override def name: String = user.name
|
override def name: String = user.name
|
||||||
|
|
||||||
|
def resetCanInteract(): Unit = {
|
||||||
|
canInteract = None
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
@if(logic.getCurrentTrick.get.firstCard.isDefined) {
|
@if(logic.getCurrentTrick.get.firstCard.isDefined) {
|
||||||
@util.WebUIUtils.cardtoImage(logic.getCurrentTrick.get.firstCard.get)
|
@util.WebUIUtils.cardtoImage(logic.getCurrentTrick.get.firstCard.get)
|
||||||
} else {
|
} else {
|
||||||
@views.html.render.card.apply("../../../public/images/cards/1B.png")("Blank Card")
|
@views.html.render.card.apply("images/cards/1B.png")("Blank Card")
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
56
knockoutwhistweb/app/views/mainmenu/navbar.scala.html
Normal file
56
knockoutwhistweb/app/views/mainmenu/navbar.scala.html
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
@(user: Option[model.users.User])
|
||||||
|
@main("Knockout Whist - Main Menu") {
|
||||||
|
<nav class="navbar navbar-expand-lg bg-body-tertiary">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navBar" aria-controls="navBar" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="collapse navbar-collapse" id="navBar">
|
||||||
|
<a class="navbar-brand" href="@routes.MainMenuController.mainMenu()">KnockOutWhist</a>
|
||||||
|
<div class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||||
|
<ul class="navbar-nav mb-2 mb-lg-0">
|
||||||
|
@if(user.isDefined) {
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link active" aria-current="page" href="#">Create Game</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link disabled" aria-disabled="true">Lobbies</a>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link active" href="@routes.MainMenuController.rules()">Rules</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<form class="navbar-nav me-auto mb-2 mb-lg-0" method="post" action="@routes.MainMenuController.joinGame()">
|
||||||
|
<input class="form-control me-2" type="text" placeholder="Enter GameCode" name="gameId" aria-label="Join Game"/>
|
||||||
|
<button class="btn btn-outline-success" type="submit">Join</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@* Right side: profile dropdown if logged in, otherwise Login / Sign Up buttons *@
|
||||||
|
@if(user.isDefined) {
|
||||||
|
<ul class="navbar-nav ms-auto mb-2 mb-lg-0">
|
||||||
|
<li class="nav-item dropdown">
|
||||||
|
<a class="nav-link dropdown-toggle d-flex align-items-center" href="#" id="profileDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
|
<img src="@routes.Assets.versioned("images/profile.png")" alt="Profile" class="rounded-circle" width="30" height="30" />
|
||||||
|
<span class="ms-2">@user.get.name</span>
|
||||||
|
</a>
|
||||||
|
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="profileDropdown">
|
||||||
|
<li><a class="dropdown-item disabled" href="#" tabindex="-1" aria-disabled="true">Stats</a></li>
|
||||||
|
<li><a class="dropdown-item disabled" href="#" tabindex="-1" aria-disabled="true">Settings</a></li>
|
||||||
|
<li><hr class="dropdown-divider"></li>
|
||||||
|
<li><a class="dropdown-item" href="@routes.UserController.logout()">Logout</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
} else {
|
||||||
|
<div class="d-flex ms-auto">
|
||||||
|
<a class="btn btn-outline-primary me-2" href="@routes.UserController.login()">Login</a>
|
||||||
|
<a class="btn btn-primary" href="@routes.UserController.login()">Sign Up</a>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
@()
|
@()
|
||||||
|
|
||||||
@main("Rules") {
|
@main("Rules") {
|
||||||
<div id="rules">
|
<div id="rules" class="game-field game-field-background">
|
||||||
<table>
|
<table>
|
||||||
<caption>Rules Overview and Equipment</caption>
|
<caption>Rules Overview and Equipment</caption>
|
||||||
<thead>
|
<thead>
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ GET /mainmenu controllers.MainMenuController.mainMenu()
|
|||||||
GET /rules controllers.MainMenuController.rules()
|
GET /rules controllers.MainMenuController.rules()
|
||||||
|
|
||||||
POST /createGame controllers.MainMenuController.createGame()
|
POST /createGame controllers.MainMenuController.createGame()
|
||||||
|
POST /joinGame controllers.MainMenuController.joinGame()
|
||||||
|
|
||||||
# User authentication routes
|
# User authentication routes
|
||||||
GET /login controllers.UserController.login()
|
GET /login controllers.UserController.login()
|
||||||
@@ -22,9 +23,7 @@ GET /logout controllers.UserController.logout()
|
|||||||
|
|
||||||
# In-game routes
|
# In-game routes
|
||||||
GET /game/:id controllers.IngameController.game(id: String)
|
GET /game/:id controllers.IngameController.game(id: String)
|
||||||
POST /game/:id/join controllers.IngameController.joinGame(id: String)
|
GET /game/:id/join controllers.IngameController.joinGame(id: String)
|
||||||
|
|
||||||
POST /game/:id/start controllers.IngameController.startGame(id: String)
|
POST /game/:id/start controllers.IngameController.startGame(id: String)
|
||||||
|
|
||||||
|
|
||||||
POST /game/:id/playCard controllers.IngameController.playCard(id: String)
|
POST /game/:id/playCard controllers.IngameController.playCard(id: String)
|
||||||
BIN
knockoutwhistweb/public/images/profile.png
Normal file
BIN
knockoutwhistweb/public/images/profile.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
Reference in New Issue
Block a user