Skip to content

Instantly share code, notes, and snippets.

@fredshonorio
Last active March 11, 2023 17:59
Show Gist options
  • Save fredshonorio/4aa4b2e5995b771874d5a348b134a2ad to your computer and use it in GitHub Desktop.
Save fredshonorio/4aa4b2e5995b771874d5a348b134a2ad to your computer and use it in GitHub Desktop.
remote thingy for calico
package remote
import cats.effect.*
import cats.syntax.all.*
import fs2.dom.*
import calico.*
import calico.html.io.{*, given}
import fs2.concurrent.*
import fs2.*
sealed trait Remote[+A]
object Remote {
case object NotRequested extends Remote[Nothing]
case object Loading extends Remote[Nothing]
case class Loaded[A](value: A) extends Remote[A]
case class Error(cause: Throwable) extends Remote[Nothing]
}
trait RemoteRequest[REQ, RES] {
def responses: Signal[IO, Remote[RES]]
def request(req: REQ): IO[Unit]
}
object RemoteRequest {
def build[REQ, RES](action: REQ => IO[RES]): Resource[IO, RemoteRequest[REQ, RES]] =
Resource
.eval(Channel.unbounded[IO, REQ])
.flatMap { channel =>
val signal = channel.stream
.flatMap[IO, Remote[RES]](arg =>
Stream.eval(IO.pure(Remote.Loading: Remote[RES])) ++
Stream.eval(action(arg).map(Remote.Loaded(_)).recover(Remote.Error(_)))
)
.holdResource(Remote.NotRequested)
signal.map(sig =>
new RemoteRequest[REQ, RES] {
val responses: Signal[IO, Remote[RES]] = sig
def request(req: REQ): IO[Unit] = channel.send(req).void
}
)
}
def example: Resource[IO, HtmlElement[IO]] = {
import scala.concurrent.duration.*
import cats.effect.std.Random
Random
.scalaUtilRandom[IO]
.toResource
.flatMap { rng =>
val req = (i: Int) =>
rng
.betweenInt(0, 10)
.flatMap(chance =>
if (chance > 5) IO.sleep(1.second) >> IO.pure(i.toString)
else IO.raiseError(new RuntimeException("bad"))
)
RemoteRequest.build(req).flatMap { remote =>
div(
button(
cls := "button",
"do it",
onClick --> (_.foreach(_ => rng.betweenInt(0, 10).flatMap(remote.request)))
),
remote.responses.map {
case Remote.NotRequested => div("")
case Remote.Loading => div("loading")
case Remote.Loaded(value) => div(value)
case Remote.Error(cause) => div(cause.getMessage)
}
)
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment