Compare commits
	
		
			No commits in common. "90e886c62db6ad5a1f1063c3903ba3594a68fbdd" and "b29d1a1ef1d8d158c66346bc5976c9994af351f6" have entirely different histories.
		
	
	
		
			90e886c62d
			...
			b29d1a1ef1
		
	
		
@ -39,6 +39,31 @@ 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](
 | 
				
			||||||
@ -46,7 +71,7 @@ object Auth {
 | 
				
			|||||||
      roomService: RoomService[F]
 | 
					      roomService: RoomService[F]
 | 
				
			||||||
  ) extends Auth[F] {
 | 
					  ) extends Auth[F] {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    val authcookieName = "authcookie"
 | 
					    val authcookie = "authcookie"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override def joinRoom(
 | 
					    override def joinRoom(
 | 
				
			||||||
        roomName: String,
 | 
					        roomName: String,
 | 
				
			||||||
@ -62,38 +87,29 @@ object Auth {
 | 
				
			|||||||
          roomService.joinRoom(roomId, nickName, nickPassword, roomPassword)
 | 
					          roomService.joinRoom(roomId, nickName, nickPassword, roomPassword)
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
          .leftMap(_.toString())
 | 
					          .leftMap(_.toString())
 | 
				
			||||||
        // newSessionId = Random.nextLong() // TODO return after i stop mocking RoomService
 | 
					        newSessionId = Random.nextLong()
 | 
				
			||||||
        newSessionId = TestModels.testSessionId
 | 
					 | 
				
			||||||
        _ <- EitherT.liftF(sessions.update(_.updated(newSessionId, (roomId, playerId))))
 | 
					        _ <- EitherT.liftF(sessions.update(_.updated(newSessionId, (roomId, playerId))))
 | 
				
			||||||
      } yield ResponseCookie(
 | 
					      } yield ResponseCookie(name = authcookie, content = newSessionId.toString(), secure = true)
 | 
				
			||||||
        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)] = {
 | 
				
			||||||
      Kleisli { (request: Request[F]) =>
 | 
					      // check authcookie presence, exchange it for playerID ad roomID
 | 
				
			||||||
        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] = {
 | 
				
			||||||
      sessions.update(_.removed(sessionId))
 | 
					      // i suppose leaving the room should just be authed route & method
 | 
				
			||||||
 | 
					      ???
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  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](TestModels.testSessions)
 | 
					      sessionsMap <- Ref.of[F, SessionsMap](
 | 
				
			||||||
 | 
					        Map(1L -> (RoomID("testroom"), PlayerID(1L)))
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
    } yield new SimpleAuth(sessionsMap, roomsService)
 | 
					    } yield new SimpleAuth(sessionsMap, roomsService)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -14,8 +14,7 @@ object BackendApp extends IOApp {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    val wiring = for {
 | 
					    val wiring = for {
 | 
				
			||||||
      roomService <- Resource.eval(RoomService.make[IO])
 | 
					      roomService <- Resource.eval(RoomService.make[IO])
 | 
				
			||||||
      auth <- Resource.eval(Auth.make(roomService))
 | 
					      httpService = MyHttpService.create(Auth.pureBadStub)
 | 
				
			||||||
      httpService = MyHttpService.create(auth)
 | 
					 | 
				
			||||||
      server <- EmberServerBuilder
 | 
					      server <- EmberServerBuilder
 | 
				
			||||||
        .default[IO]
 | 
					        .default[IO]
 | 
				
			||||||
        .withHost(host)
 | 
					        .withHost(host)
 | 
				
			||||||
 | 
				
			|||||||
@ -24,19 +24,17 @@ 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))
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          IO(println(s"got ws request from $playerId in $roomId")) >> wsb.build(send, receive)
 | 
					          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
 | 
				
			||||||
@ -62,8 +60,7 @@ 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]](TestModels.testRooms).map(new InMemoryRoomService[F](_))
 | 
					    Ref.of[F, Map[RoomID, Room]](Map.empty).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-circe" % "0.15.0"
 | 
					    libraryDependencies += "io.laminext" %%% "fetch" % "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, // TODO switch to nickname
 | 
					      owner: PlayerID,
 | 
				
			||||||
      password: String,
 | 
					      password: String,
 | 
				
			||||||
      allowedCards: List[String],
 | 
					      allowedCards: List[String],
 | 
				
			||||||
      round: RoundState,
 | 
					      round: RoundState,
 | 
				
			||||||
 | 
				
			|||||||
@ -3,26 +3,31 @@ package industries.sunshine.planningpoker
 | 
				
			|||||||
import industries.sunshine.planningpoker.common.Models.*
 | 
					import industries.sunshine.planningpoker.common.Models.*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
object TestModels {
 | 
					object TestModels {
 | 
				
			||||||
  val me = Player("me", PlayerID(1))
 | 
					  val me = Player("wormy", 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 testRoomBackend = Room(
 | 
					  val testRoom = {
 | 
				
			||||||
      id = RoomID("testroom"),
 | 
					    RoomStateView(
 | 
				
			||||||
      players = List(me, birdy, pony, horsey),
 | 
					      players = List(me, birdy, pony),
 | 
				
			||||||
      owner = me.id,
 | 
					      me = me.id,
 | 
				
			||||||
      password = "password",
 | 
					 | 
				
			||||||
      allowedCards = List("xs", "s", "m", "l", "xl"),
 | 
					      allowedCards = List("xs", "s", "m", "l", "xl"),
 | 
				
			||||||
            round = RoundState.Viewing(
 | 
					      round = RoundState.Voting(myCard = Some("s"), alreadyVoted = List(me, pony)),
 | 
				
			||||||
 | 
					      canCloseRound = true
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  val testOpenedRoom = {
 | 
				
			||||||
 | 
					    RoomStateView(
 | 
				
			||||||
 | 
					      players = List(me, birdy, pony, horsey),
 | 
				
			||||||
 | 
					      me = me.id,
 | 
				
			||||||
 | 
					      allowedCards = List("xs", "s", "m", "l", "xl"),
 | 
				
			||||||
 | 
					      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")
 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
      playersPasswords = Map("me" -> "nickpassword") // nickname into password
 | 
					      canCloseRound = true
 | 
				
			||||||
  )
 | 
					    )
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  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,6 +25,7 @@ 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
 | 
				
			||||||
@ -59,8 +60,7 @@ 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")
 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
      child <-- roomStateWSStream.isConnected.map( if (_) emptyNode else  JoinRoomComponent.render()),
 | 
					      RoomView.renderRoom(stateSignal),
 | 
				
			||||||
      child <-- roomStateWSStream.isConnected.map( if (_) RoomView.renderRoom(stateSignal) else  emptyNode),
 | 
					 | 
				
			||||||
      roomStateWSStream.connect
 | 
					      roomStateWSStream.connect
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
				
			|||||||
@ -1,117 +0,0 @@
 | 
				
			|||||||
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