Compare commits
	
		
			3 Commits
		
	
	
		
			b29d1a1ef1
			...
			90e886c62d
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					90e886c62d | ||
| 
						 | 
					1f28a03d47 | ||
| 
						 | 
					6c1220b544 | 
@ -39,31 +39,6 @@ trait Auth[F[_]] {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
object Auth {
 | 
					object Auth {
 | 
				
			||||||
  def pureBadStub = new Auth[IO] {
 | 
					 | 
				
			||||||
    override def authUser =
 | 
					 | 
				
			||||||
      Kleisli((req: Request[IO]) =>
 | 
					 | 
				
			||||||
        OptionT.liftF(
 | 
					 | 
				
			||||||
          IO(println(s"authUser: $req")) >> IO(
 | 
					 | 
				
			||||||
            PlayerID(14) -> RoomID("testroom")
 | 
					 | 
				
			||||||
          )
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
      )
 | 
					 | 
				
			||||||
    override def joinRoom(
 | 
					 | 
				
			||||||
        roomName: String,
 | 
					 | 
				
			||||||
        roomPassword: String,
 | 
					 | 
				
			||||||
        nickName: String,
 | 
					 | 
				
			||||||
        nickPassword: String
 | 
					 | 
				
			||||||
    ) =
 | 
					 | 
				
			||||||
      IO(
 | 
					 | 
				
			||||||
        println(
 | 
					 | 
				
			||||||
          s"> access room for $roomName $roomPassword $nickName, to return stub 111"
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
      ) >> IO.pure(Right(ResponseCookie("authcookie", "1")))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    override def deleteSession(sessionId: Long): IO[Unit] =
 | 
					 | 
				
			||||||
      IO(s"got request to leave for $sessionId")
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  type SessionsMap = Map[Long, (RoomID, PlayerID)]
 | 
					  type SessionsMap = Map[Long, (RoomID, PlayerID)]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  class SimpleAuth[F[_]: Sync](
 | 
					  class SimpleAuth[F[_]: Sync](
 | 
				
			||||||
@ -71,7 +46,7 @@ object Auth {
 | 
				
			|||||||
      roomService: RoomService[F]
 | 
					      roomService: RoomService[F]
 | 
				
			||||||
  ) extends Auth[F] {
 | 
					  ) extends Auth[F] {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    val authcookie = "authcookie"
 | 
					    val authcookieName = "authcookie"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override def joinRoom(
 | 
					    override def joinRoom(
 | 
				
			||||||
        roomName: String,
 | 
					        roomName: String,
 | 
				
			||||||
@ -87,29 +62,38 @@ object Auth {
 | 
				
			|||||||
          roomService.joinRoom(roomId, nickName, nickPassword, roomPassword)
 | 
					          roomService.joinRoom(roomId, nickName, nickPassword, roomPassword)
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
          .leftMap(_.toString())
 | 
					          .leftMap(_.toString())
 | 
				
			||||||
        newSessionId = Random.nextLong()
 | 
					        // newSessionId = Random.nextLong() // TODO return after i stop mocking RoomService
 | 
				
			||||||
 | 
					        newSessionId = TestModels.testSessionId
 | 
				
			||||||
        _ <- EitherT.liftF(sessions.update(_.updated(newSessionId, (roomId, playerId))))
 | 
					        _ <- EitherT.liftF(sessions.update(_.updated(newSessionId, (roomId, playerId))))
 | 
				
			||||||
      } yield ResponseCookie(name = authcookie, content = newSessionId.toString(), secure = true)
 | 
					      } yield ResponseCookie(
 | 
				
			||||||
 | 
					        name = authcookieName,
 | 
				
			||||||
 | 
					        content = newSessionId.toString(),
 | 
				
			||||||
 | 
					        secure = true
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      result.value
 | 
					      result.value
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override def authUser
 | 
					    override def authUser
 | 
				
			||||||
        : Kleisli[[A] =>> cats.data.OptionT[F, A], Request[F], (PlayerID, RoomID)] = {
 | 
					        : Kleisli[[A] =>> cats.data.OptionT[F, A], Request[F], (PlayerID, RoomID)] = {
 | 
				
			||||||
      // check authcookie presence, exchange it for playerID ad roomID
 | 
					      Kleisli { (request: Request[F]) =>
 | 
				
			||||||
      ???
 | 
					        OptionT(sessions.get.map { sessionsMap =>
 | 
				
			||||||
 | 
					          for {
 | 
				
			||||||
 | 
					            authcookie <- request.cookies.find(_.name == authcookieName)
 | 
				
			||||||
 | 
					            sessionId <- authcookie.content.toLongOption
 | 
				
			||||||
 | 
					            (roomId, playerId) <- sessionsMap.get(sessionId)
 | 
				
			||||||
 | 
					          } yield (playerId, roomId)
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override def deleteSession(sessionId: Long): F[Unit] = {
 | 
					    override def deleteSession(sessionId: Long): F[Unit] = {
 | 
				
			||||||
      // i suppose leaving the room should just be authed route & method
 | 
					      sessions.update(_.removed(sessionId))
 | 
				
			||||||
      ???
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def make[F[_]: Sync](roomsService: RoomService[F]): F[Auth[F]] =
 | 
					  def make[F[_]: Sync](roomsService: RoomService[F]): F[Auth[F]] =
 | 
				
			||||||
    for {
 | 
					    for {
 | 
				
			||||||
      sessionsMap <- Ref.of[F, SessionsMap](
 | 
					      sessionsMap <- Ref.of[F, SessionsMap](TestModels.testSessions)
 | 
				
			||||||
        Map(1L -> (RoomID("testroom"), PlayerID(1L)))
 | 
					 | 
				
			||||||
      )
 | 
					 | 
				
			||||||
    } yield new SimpleAuth(sessionsMap, roomsService)
 | 
					    } yield new SimpleAuth(sessionsMap, roomsService)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -14,7 +14,8 @@ object BackendApp extends IOApp {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    val wiring = for {
 | 
					    val wiring = for {
 | 
				
			||||||
      roomService <- Resource.eval(RoomService.make[IO])
 | 
					      roomService <- Resource.eval(RoomService.make[IO])
 | 
				
			||||||
      httpService = MyHttpService.create(Auth.pureBadStub)
 | 
					      auth <- Resource.eval(Auth.make(roomService))
 | 
				
			||||||
 | 
					      httpService = MyHttpService.create(auth)
 | 
				
			||||||
      server <- EmberServerBuilder
 | 
					      server <- EmberServerBuilder
 | 
				
			||||||
        .default[IO]
 | 
					        .default[IO]
 | 
				
			||||||
        .withHost(host)
 | 
					        .withHost(host)
 | 
				
			||||||
 | 
				
			|||||||
@ -24,17 +24,19 @@ object MyHttpService {
 | 
				
			|||||||
      AuthedRoutes.of {
 | 
					      AuthedRoutes.of {
 | 
				
			||||||
        case GET -> Root / "subscribe" as (playerId, roomId) => {
 | 
					        case GET -> Root / "subscribe" as (playerId, roomId) => {
 | 
				
			||||||
          val send: Stream[IO, WebSocketFrame] =
 | 
					          val send: Stream[IO, WebSocketFrame] =
 | 
				
			||||||
 | 
					            (
 | 
				
			||||||
              Stream
 | 
					              Stream
 | 
				
			||||||
                .emits(TestModels.testChangesList)
 | 
					                .emits(TestModels.testChangesList)
 | 
				
			||||||
                .covary[IO]
 | 
					                .covary[IO]
 | 
				
			||||||
              .metered(1.second)
 | 
					                .metered(1.second) ++ Stream.never[IO]
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
              .map(state => WebSocketFrame.Text(state.asJson.noSpaces))
 | 
					              .map(state => WebSocketFrame.Text(state.asJson.noSpaces))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          val receive: Pipe[IO, WebSocketFrame, Unit] = _.evalMap {
 | 
					          val receive: Pipe[IO, WebSocketFrame, Unit] = _.evalMap {
 | 
				
			||||||
            case WebSocketFrame.Text(text, _) => Sync[IO].delay(println(text))
 | 
					            case WebSocketFrame.Text(text, _) => Sync[IO].delay(println(text))
 | 
				
			||||||
            case other                        => Sync[IO].delay(println(other))
 | 
					            case other                        => Sync[IO].delay(println(other))
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          wsb.build(send, receive)
 | 
					          IO(println(s"got ws request from $playerId in $roomId")) >> wsb.build(send, receive)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        case GET -> Root / "vote" / vote as (playerId, roomId) => {
 | 
					        case GET -> Root / "vote" / vote as (playerId, roomId) => {
 | 
				
			||||||
          // TODO forward these to the service implementation
 | 
					          // TODO forward these to the service implementation
 | 
				
			||||||
@ -60,7 +62,8 @@ object MyHttpService {
 | 
				
			|||||||
              data.roomName,
 | 
					              data.roomName,
 | 
				
			||||||
              data.password,
 | 
					              data.password,
 | 
				
			||||||
              data.nickname,
 | 
					              data.nickname,
 | 
				
			||||||
              data.nickPassword)
 | 
					              data.nickPassword
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
            resp <- authResult match {
 | 
					            resp <- authResult match {
 | 
				
			||||||
              case Left(error) =>
 | 
					              case Left(error) =>
 | 
				
			||||||
                Forbidden(error)
 | 
					                Forbidden(error)
 | 
				
			||||||
 | 
				
			|||||||
@ -91,6 +91,6 @@ class InMemoryRoomService[F[_]: Sync](stateRef: Ref[F, Map[RoomID, Room]]) exten
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
object RoomService {
 | 
					object RoomService {
 | 
				
			||||||
  def make[F[_]: Sync]: F[RoomService[F]] = {
 | 
					  def make[F[_]: Sync]: F[RoomService[F]] = {
 | 
				
			||||||
    Ref.of[F, Map[RoomID, Room]](Map.empty).map(new InMemoryRoomService[F](_))
 | 
					    Ref.of[F, Map[RoomID, Room]](TestModels.testRooms).map(new InMemoryRoomService[F](_))
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -33,7 +33,7 @@ lazy val frontend = project
 | 
				
			|||||||
    libraryDependencies += "org.scala-js" %%% "scalajs-dom" % "2.4.0",
 | 
					    libraryDependencies += "org.scala-js" %%% "scalajs-dom" % "2.4.0",
 | 
				
			||||||
    libraryDependencies += "com.raquo" %%% "laminar" % "15.0.1",
 | 
					    libraryDependencies += "com.raquo" %%% "laminar" % "15.0.1",
 | 
				
			||||||
    libraryDependencies += "io.laminext" %%% "websocket-circe" % "0.15.0",
 | 
					    libraryDependencies += "io.laminext" %%% "websocket-circe" % "0.15.0",
 | 
				
			||||||
    libraryDependencies += "io.laminext" %%% "fetch" % "0.15.0"
 | 
					    libraryDependencies += "io.laminext" %%% "fetch-circe" % "0.15.0"
 | 
				
			||||||
  )
 | 
					  )
 | 
				
			||||||
  .dependsOn(common.js)
 | 
					  .dependsOn(common.js)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -63,7 +63,7 @@ object Models {
 | 
				
			|||||||
  final case class Room(
 | 
					  final case class Room(
 | 
				
			||||||
      id: RoomID,
 | 
					      id: RoomID,
 | 
				
			||||||
      players: List[Player],
 | 
					      players: List[Player],
 | 
				
			||||||
      owner: PlayerID,
 | 
					      owner: PlayerID, // TODO switch to nickname
 | 
				
			||||||
      password: String,
 | 
					      password: String,
 | 
				
			||||||
      allowedCards: List[String],
 | 
					      allowedCards: List[String],
 | 
				
			||||||
      round: RoundState,
 | 
					      round: RoundState,
 | 
				
			||||||
 | 
				
			|||||||
@ -3,31 +3,26 @@ package industries.sunshine.planningpoker
 | 
				
			|||||||
import industries.sunshine.planningpoker.common.Models.*
 | 
					import industries.sunshine.planningpoker.common.Models.*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
object TestModels {
 | 
					object TestModels {
 | 
				
			||||||
  val me = Player("wormy", PlayerID(1))
 | 
					  val me = Player("me", PlayerID(1))
 | 
				
			||||||
  val pony = Player("pony", PlayerID(10))
 | 
					  val pony = Player("pony", PlayerID(10))
 | 
				
			||||||
  val birdy = Player("birdy", PlayerID(11))
 | 
					  val birdy = Player("birdy", PlayerID(11))
 | 
				
			||||||
  val horsey = Player("horsey", PlayerID(12))
 | 
					  val horsey = Player("horsey", PlayerID(12))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  val testRoom = {
 | 
					  val testRoomBackend = Room(
 | 
				
			||||||
    RoomStateView(
 | 
					      id = RoomID("testroom"),
 | 
				
			||||||
      players = List(me, birdy, pony),
 | 
					 | 
				
			||||||
      me = me.id,
 | 
					 | 
				
			||||||
      allowedCards = List("xs", "s", "m", "l", "xl"),
 | 
					 | 
				
			||||||
      round = RoundState.Voting(myCard = Some("s"), alreadyVoted = List(me, pony)),
 | 
					 | 
				
			||||||
      canCloseRound = true
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  val testOpenedRoom = {
 | 
					 | 
				
			||||||
    RoomStateView(
 | 
					 | 
				
			||||||
      players = List(me, birdy, pony, horsey),
 | 
					      players = List(me, birdy, pony, horsey),
 | 
				
			||||||
      me = me.id,
 | 
					      owner = me.id,
 | 
				
			||||||
 | 
					      password = "password",
 | 
				
			||||||
      allowedCards = List("xs", "s", "m", "l", "xl"),
 | 
					      allowedCards = List("xs", "s", "m", "l", "xl"),
 | 
				
			||||||
            round = RoundState.Viewing(
 | 
					            round = RoundState.Viewing(
 | 
				
			||||||
        List(me.id -> "xs", pony.id -> "l", birdy.id -> "s", horsey.id -> "m")
 | 
					        List(me.id -> "xs", pony.id -> "l", birdy.id -> "s", horsey.id -> "m")
 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
      canCloseRound = true
 | 
					      playersPasswords = Map("me" -> "nickpassword") // nickname into password
 | 
				
			||||||
  )
 | 
					  )
 | 
				
			||||||
  }
 | 
					
 | 
				
			||||||
 | 
					  val testSessionId = 1L
 | 
				
			||||||
 | 
					  val testSessions = Map(testSessionId -> (testRoomBackend.id, me.id))
 | 
				
			||||||
 | 
					  val testRooms = Map(testRoomBackend.id -> testRoomBackend)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  val testChangesList = List(
 | 
					  val testChangesList = List(
 | 
				
			||||||
    RoomStateView(
 | 
					    RoomStateView(
 | 
				
			||||||
 | 
				
			|||||||
@ -25,7 +25,6 @@ object Main {
 | 
				
			|||||||
  )
 | 
					  )
 | 
				
			||||||
  // TODO is this ok for state creation? link with auth component and use in another?
 | 
					  // TODO is this ok for state creation? link with auth component and use in another?
 | 
				
			||||||
  val appStateSignal = Var(AppState(Some(TestModels.me.id))).signal
 | 
					  val appStateSignal = Var(AppState(Some(TestModels.me.id))).signal
 | 
				
			||||||
  val staticStateSignal = Var(TestModels.testRoom).signal
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  import io.laminext.websocket.circe.WebSocket._
 | 
					  import io.laminext.websocket.circe.WebSocket._
 | 
				
			||||||
  import io.laminext.websocket.circe.webSocketReceiveBuilderSyntax
 | 
					  import io.laminext.websocket.circe.webSocketReceiveBuilderSyntax
 | 
				
			||||||
@ -60,7 +59,8 @@ object Main {
 | 
				
			|||||||
        className := "h-24 w-full flex flex-for justify-center items-center bg-green-200",
 | 
					        className := "h-24 w-full flex flex-for justify-center items-center bg-green-200",
 | 
				
			||||||
        p(className := "text-2xl", "Here be header")
 | 
					        p(className := "text-2xl", "Here be header")
 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
      RoomView.renderRoom(stateSignal),
 | 
					      child <-- roomStateWSStream.isConnected.map( if (_) emptyNode else  JoinRoomComponent.render()),
 | 
				
			||||||
 | 
					      child <-- roomStateWSStream.isConnected.map( if (_) RoomView.renderRoom(stateSignal) else  emptyNode),
 | 
				
			||||||
      roomStateWSStream.connect
 | 
					      roomStateWSStream.connect
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,117 @@
 | 
				
			|||||||
 | 
					package industries.sunshine.planningpoker
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import scala.scalajs.js
 | 
				
			||||||
 | 
					import scala.scalajs.js.annotation.*
 | 
				
			||||||
 | 
					import com.raquo.laminar.api.L.{*, given}
 | 
				
			||||||
 | 
					import io.laminext.fetch.Fetch
 | 
				
			||||||
 | 
					import io.laminext.fetch.circe._
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import scala.util.Failure
 | 
				
			||||||
 | 
					import scala.util.Success
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import concurrent.ExecutionContext.Implicits.global
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					object JoinRoomComponent {
 | 
				
			||||||
 | 
					  // TODO inputs for room name, room password, nick name, nick password
 | 
				
			||||||
 | 
					  // do the get to /login route
 | 
				
			||||||
 | 
					  // display errors if any.
 | 
				
			||||||
 | 
					  // but then what? attempt to start the websocket?
 | 
				
			||||||
 | 
					  // and if websocket closed show this component,
 | 
				
			||||||
 | 
					  // if it's open - doesn't show this component, show the room.
 | 
				
			||||||
 | 
					  // i suppose it should be managed on a level up
 | 
				
			||||||
 | 
					  // or rather what? ws stream should be retried every time someone presses submit button
 | 
				
			||||||
 | 
					  // and receives 200 ok
 | 
				
			||||||
 | 
					  // so, parent page should send in observer for the successful auth. and on that observer - start \ restart the websocket
 | 
				
			||||||
 | 
					  def nameInput(data: Var[String], placeholderText: String) = input(
 | 
				
			||||||
 | 
					    className := "border-2 m-1 rounded",
 | 
				
			||||||
 | 
					    placeholder := placeholderText,
 | 
				
			||||||
 | 
					    controlled(
 | 
				
			||||||
 | 
					      value <-- data.signal,
 | 
				
			||||||
 | 
					      onInput.mapToValue --> data.writer
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					  def passInput(data: Var[String], placeholderText: String) = input(
 | 
				
			||||||
 | 
					    tpe := "password",
 | 
				
			||||||
 | 
					    className := "border-2 m-1 rounded",
 | 
				
			||||||
 | 
					    placeholder := placeholderText,
 | 
				
			||||||
 | 
					    controlled(
 | 
				
			||||||
 | 
					      value <-- data.signal,
 | 
				
			||||||
 | 
					      onInput.mapToValue --> data.writer
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  val roomNameVar = Var("testroom")
 | 
				
			||||||
 | 
					  val roomPassVar = Var("password")
 | 
				
			||||||
 | 
					  val nicknameVar = Var("me")
 | 
				
			||||||
 | 
					  val nicknamePass = Var("nickpass")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  val (responsesStream, responseReceived) = EventStream.withCallback[FetchResponse[String]]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  val submitButton = button(
 | 
				
			||||||
 | 
					    "Join room",
 | 
				
			||||||
 | 
					    onClick
 | 
				
			||||||
 | 
					      .mapTo {
 | 
				
			||||||
 | 
					        (roomNameVar.now(), roomPassVar.now(), nicknameVar.now(), nicknamePass.now())
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      .flatMap { case (roomName, roomPass, nickname, nicknamePass) =>
 | 
				
			||||||
 | 
					        Fetch
 | 
				
			||||||
 | 
					          .post(
 | 
				
			||||||
 | 
					            "/api/login",
 | 
				
			||||||
 | 
					            body = Requests.LogIn(
 | 
				
			||||||
 | 
					              roomName,
 | 
				
			||||||
 | 
					              nickname,
 | 
				
			||||||
 | 
					              roomPass,
 | 
				
			||||||
 | 
					              nicknamePass
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					          )
 | 
				
			||||||
 | 
					          .text
 | 
				
			||||||
 | 
					      } --> responseReceived
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def render(): Element = {
 | 
				
			||||||
 | 
					    div(
 | 
				
			||||||
 | 
					      className := "flex flex-col h-full justify-center",
 | 
				
			||||||
 | 
					      "Logging in:",
 | 
				
			||||||
 | 
					      nameInput(roomNameVar, "Enter room name:"),
 | 
				
			||||||
 | 
					      passInput(roomPassVar, "room password"),
 | 
				
			||||||
 | 
					      nameInput(nicknameVar, "Enter your nickname:"),
 | 
				
			||||||
 | 
					      passInput(nicknamePass, "nickname pass:"),
 | 
				
			||||||
 | 
					      submitButton,
 | 
				
			||||||
 | 
					      div(
 | 
				
			||||||
 | 
					        div(
 | 
				
			||||||
 | 
					          code("received:")
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        div(
 | 
				
			||||||
 | 
					          cls := "flex flex-col space-y-4 p-4 max-h-96 overflow-auto bg-gray-900",
 | 
				
			||||||
 | 
					          children.command <-- responsesStream.recoverToTry.map {
 | 
				
			||||||
 | 
					            case Success(response) =>
 | 
				
			||||||
 | 
					              CollectionCommand.Append(
 | 
				
			||||||
 | 
					                div(
 | 
				
			||||||
 | 
					                  div(
 | 
				
			||||||
 | 
					                    cls := "flex space-x-2 items-center",
 | 
				
			||||||
 | 
					                    code(cls := "text-green-500", "Status: "),
 | 
				
			||||||
 | 
					                    code(cls := "text-green-300", s"${response.status} ${response.statusText}")
 | 
				
			||||||
 | 
					                  ),
 | 
				
			||||||
 | 
					                  div(
 | 
				
			||||||
 | 
					                    cls := "text-green-400 text-xs",
 | 
				
			||||||
 | 
					                    code(response.data)
 | 
				
			||||||
 | 
					                  )
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					              )
 | 
				
			||||||
 | 
					            case Failure(exception) =>
 | 
				
			||||||
 | 
					              CollectionCommand.Append(
 | 
				
			||||||
 | 
					                div(
 | 
				
			||||||
 | 
					                  div(
 | 
				
			||||||
 | 
					                    cls := "flex space-x-2 items-center",
 | 
				
			||||||
 | 
					                    code(cls := "text-red-500", "Error: "),
 | 
				
			||||||
 | 
					                    code(cls := "text-red-300", exception.getMessage)
 | 
				
			||||||
 | 
					                  )
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					              )
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user