adding backend project

will try to do rest & websocket api with http4s
This commit is contained in:
efim
2023-04-23 11:12:08 +04:00
parent 7e488b7e62
commit d8af92787d
6 changed files with 70 additions and 4 deletions

View File

@@ -0,0 +1,38 @@
package industries.sunshine.planningpoker
import scala.scalajs.js
import scala.scalajs.js.annotation.*
import com.raquo.laminar.api.L.{*, given}
import industries.sunshine.planningpoker.common.Models.*
import org.scalajs.dom
// import javascriptLogo from "/javascript.svg"
@js.native @JSImport("/javascript.svg", JSImport.Default)
val javascriptLogo: String = js.native
@main
def PlanningPokerUrgh(): Unit =
renderOnDomContentLoaded(
dom.document.getElementById("app"),
Main.appElement()
)
object Main {
final case class AppState(
myId: Option[PlayerID]
)
// TODO is this ok for state creation? link with auth component and use in another?
val appStateSignal = Var(AppState(Some(RoomStateView.me.id))).signal
def appElement(): Element = {
div(
className := "w-screen h-screen flex flex-col justify-center items-center",
div(
className := "h-24 w-full flex flex-for justify-center items-center bg-green-200",
p(className := "text-2xl", "Here be header"),
),
RoomView.renderRoom(RoomStateView.testRoom)
)
}
}

View File

@@ -0,0 +1,53 @@
package industries.sunshine.planningpoker
import scala.scalajs.js
import com.raquo.laminar.api.L.{*, given}
import industries.sunshine.planningpoker.common.Models.*
/** Rendering of the Room state
*/
object RoomView {
// TODO this will take in signal of the room observable
// NOTE i guess "other players" would have to be in circle with 'me' as empty space in the bottom
def renderRoom(state: RoomStateView): Element = {
val roomStateSignal = Var(state).signal
// i want to number other players, for the star arrangement
val otherPlayers = roomStateSignal.map { state =>
state.players.filterNot(_.id == state.me).zipWithIndex
}
div(
className := "w-full h-full border-4 border-amber-900 flex flex-col",
div(
className := "flex flex-row",
children <-- otherPlayers.split(_._1.id) { (id, initial, playerSignal) =>
renderPlayer(playerSignal, roomStateSignal.map(_.allowedCards.size))
}
),
TableView.renderTable(roomStateSignal)
)
}
def renderPlayer(p: Signal[(Player, Int)], cardsAmount: Signal[Int]): Element = {
val xOffsetStyleSignal = p.map(_._2)
div(
className := "w-20 h-20 border-2 border-amber-400 m-2 absolute",
// left <-- p.map(tuple => ((1 + tuple._2) * 10000).toString()),
styleAttr <-- p.map(tuple => s"left: ${(1 + tuple._2) * 100}px"),
child.text <-- p.map(_._1.name),
renderHandCardBacks(cardsAmount)
)
}
def renderHandCardBacks(amountSignal: Signal[Int]): Element = {
def renderCard(index: Int): Element =
div(
className := "w-4 h-8 m-1 rounded bg-gray-600 text-yellow"
)
div(
className := "flex flex-row",
children <-- amountSignal.map { amount => (1 to amount).map(renderCard) }
)
}
}

View File

@@ -0,0 +1,78 @@
package industries.sunshine.planningpoker
import scala.scalajs.js
import com.raquo.laminar.api.L.{*, given}
import scala.scalajs.js.Dynamic.{global => g}
import industries.sunshine.planningpoker.common.Models.*
object TableView {
// new plan. map to players, split by playerId, map into specific card
// have single funciton that calculates the card from the state.
// but, can't have map to player, because overall state is required.
// but can i split full state into several observables by that key? i think i should be able to
// so, it's more efficient to share an observable, than to create multiple copies...
def renderTable(roundSignal: Signal[RoomStateView]): Element = {
val playerIdToCardTypeSignal =
roundSignal.combineWith(Main.appStateSignal.map(_.myId)).map((state, myIdOpt) =>
state.players.map(p =>
p.id -> getPlayerCardType(p.id, state.round, p.name, myIdOpt)
)
)
div(
className := "w-full h-full border-2 border-amber-700 flex flex-row justify-center items-center bg-green-100",
children <-- playerIdToCardTypeSignal.split(_._1) {
(id, initial, cardTypeSignal) =>
renderPlayerCard(cardTypeSignal.map(_._2))
}
)
}
trait CardState
case class NoCard(name: String) extends CardState
case object CardBack extends CardState
case class Open(value: String) extends CardState
def getPlayerCardType(
id: PlayerID,
state: RoundState,
name: String,
myId: Option[PlayerID]
): CardState = {
state match {
case isOpen: VotingRound =>
if (myId.forall(_ == id)) {
isOpen.myCard.fold(NoCard(name))(vote => Open(vote))
} else isOpen.alreadyVoted.find(_.id == id).fold(NoCard(name))(_ => CardBack)
case isClosed: ViewingRound =>
isClosed.votes
.get(id)
.fold {
g.console.error(s"missing vote for player $name")
Open("error")
} { vote => Open(vote) }
}
}
def renderPlayerCard(state: Signal[CardState]): Element = {
val cardTypeStyle = state.map {
case NoCard(_) => "bg-green-100 text-black border-2 border-black"
case CardBack => "bg-green-500 border-4 border-green-700"
case Open(_) => "text-black bg-gray-50 border-black border-2"
}
div(
className := "w-20 h-40 m-1 rounded flex justify-center items-center m-3",
className <-- cardTypeStyle,
div(
className := "-rotate-45 text-xl",
child.text <-- state.map {
case NoCard(name) => name
case CardBack => ""
case Open(vote) => vote
}
),
)
}
}