diff --git a/.scalafmt.conf b/.scalafmt.conf new file mode 100644 index 0000000..fd76373 --- /dev/null +++ b/.scalafmt.conf @@ -0,0 +1,2 @@ +runner.dialect = scala3 +version = 3.7.3 diff --git a/build.sbt b/build.sbt index 6cc8e3e..37e9db3 100644 --- a/build.sbt +++ b/build.sbt @@ -1,6 +1,7 @@ import org.scalajs.linker.interface.ModuleSplitStyle -lazy val planningPokerGrargh = project.in(file(".")) +lazy val planningPokerGrargh = project + .in(file(".")) .enablePlugins(ScalaJSPlugin) // Enable the Scala.js plugin in this project .settings( scalaVersion := "3.2.0", @@ -18,12 +19,13 @@ lazy val planningPokerGrargh = project.in(file(".")) scalaJSLinkerConfig ~= { _.withModuleKind(ModuleKind.ESModule) .withModuleSplitStyle( - ModuleSplitStyle.SmallModulesFor(List("livechart"))) + ModuleSplitStyle.SmallModulesFor(List("livechart")) + ) }, /* Depend on the scalajs-dom library. * It provides static types for the browser DOM APIs. */ libraryDependencies += "org.scala-js" %%% "scalajs-dom" % "2.4.0", - libraryDependencies += "com.raquo" %%% "laminar" % "15.0.1", + libraryDependencies += "com.raquo" %%% "laminar" % "15.0.1" ) diff --git a/src/main/scala/industries/sunshine/planningpoker/LiveChart.scala b/src/main/scala/industries/sunshine/planningpoker/LiveChart.scala index 8ba52d5..d106159 100644 --- a/src/main/scala/industries/sunshine/planningpoker/LiveChart.scala +++ b/src/main/scala/industries/sunshine/planningpoker/LiveChart.scala @@ -23,25 +23,29 @@ object Main { className := "w-screen h-screen flex flex-col justify-center items-center bg-green-100", div( // container for row of pictures className := "flex flex-row w-1/2 justify-center", - a(className := "flex-initial", - href := "https://vitejs.dev", target := "_blank", - img(src := "/vite.svg", className := "", alt := "Vite logo"), - ), - a(className := "flex-initial", - href := "https://developer.mozilla.org/en-US/docs/Web/JavaScript", target := "_blank", - img(src := javascriptLogo, className := "", alt := "JavaScript logo"), + a( + className := "flex-initial", + href := "https://vitejs.dev", + target := "_blank", + img(src := "/vite.svg", className := "", alt := "Vite logo") ), + a( + className := "flex-initial", + href := "https://developer.mozilla.org/en-US/docs/Web/JavaScript", + target := "_blank", + img(src := javascriptLogo, className := "", alt := "JavaScript logo") + ) ), div( className := "flex-initial", - h1("Hello Laminar and Vite and stuff and other stuff!"), + h1("Hello Laminar and Vite and stuff and other stuff!") ), - div(className := "bg-blue-400 flex-initial rounded-lg border-2 border-slate-500 p-1 m-1", - counterButton(), - ), - p(className := "flex-initial", - "Click on the Vite logo to learn more", + div( + className := "bg-blue-400 flex-initial rounded-lg border-2 border-slate-500 p-1 m-1", + counterButton() ), + p(className := "flex-initial", "Click on the Vite logo to learn more"), + RoomView.renderRoom(RoomStateView.testRoom) ) } } @@ -52,7 +56,7 @@ def counterButton(): Element = { tpe := "button", "count is ", child.text <-- counter, - onClick --> { event => counter.update(c => c + 1) }, + onClick --> { event => counter.update(c => c + 1) } ) } diff --git a/src/main/scala/industries/sunshine/planningpoker/Models.scala b/src/main/scala/industries/sunshine/planningpoker/Models.scala new file mode 100644 index 0000000..d490081 --- /dev/null +++ b/src/main/scala/industries/sunshine/planningpoker/Models.scala @@ -0,0 +1,55 @@ +package industries.sunshine.planningpoker + +import java.util.UUID + +/** view of the single planning poker round + * @param players + * \- people who are currently playing + * @param allowedCards + * \- the cards values that can be used by players + * @param round + * \- state of the selected cards of the players + * @param canCloseRound + * \- whether current player has access to button to finish the round + */ +final case class RoomStateView( + players: List[Player], + me: PlayerID, + allowedCards: List[String], + round: RoundState, + canCloseRound: Boolean = false +) { + def playersCount: Int = players.size +} + +object RoomStateView { + val testRoom = { + val me = Player("wormy", PlayerID()) + RoomStateView( + players = List(me, Player("birdy", PlayerID()), Player("pony", PlayerID())), + me = me.id, + allowedCards = List("xs", "s", "m", "l", "xl"), + round = OpenRound(myCard = Some("l"), alreadyVoted = List(me)), + canCloseRound = true + ) + } +} + +trait RoundState + +/** view state for round before votes are open player can know their vote and + * who of the other players have voted + */ +final case class OpenRound( + myCard: Option[String], + alreadyVoted: List[Player] +) extends RoundState + +/** view state for round after opening the votes + */ +final case class ClosedRound( + votes: Map[Player, String] +) extends RoundState + +final class PlayerID +final case class Player(name: String, id: PlayerID) diff --git a/src/main/scala/industries/sunshine/planningpoker/RoomView.scala b/src/main/scala/industries/sunshine/planningpoker/RoomView.scala new file mode 100644 index 0000000..3051a09 --- /dev/null +++ b/src/main/scala/industries/sunshine/planningpoker/RoomView.scala @@ -0,0 +1,48 @@ +package industries.sunshine.planningpoker + +import scala.scalajs.js +import com.raquo.laminar.api.L.{*, given} + +/** 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 + val otherPlayers = roomStateSignal.map { state => + state.players.filterNot(_.id == state.me) + } + + div( + className := "w-96 h-96 border-4 border-amber-900", + div( + className := "flex flex-row", + children <-- otherPlayers.split(_.id) { (id, initial, playerSignal) => + renderPlayer(playerSignal, roomStateSignal.map(_.allowedCards.size)) + } + ) + ) + } + + def renderPlayer(p: Signal[Player], cardsAmount: Signal[Int]): Element = { + div( + className := "w-20 h-20 border-2 border-amber-400 m-2", + child.text <-- p.map(_.name), + renderHandCardBacks(cardsAmount) + ) + } + + // TODO will also be a signal, i suppose separate, yeah + 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-400" + ) + + div( + className := "flex flex-row", + children <-- amountSignal.map { amount => (1 to amount).map(renderCard) } + ) + } +}