Outwatch Router

Route creation strategy was mostly taken from Http4s. To create paths and map them to pages:

import outwatch.router._

sealed abstract class Page
case class RootPage() extends Page
case class Login() extends Page
case class Register() extends Page
case class Profile(userId: String) extends Page
case class NotFound() extends Page

def routes: PartialFunction[Path, Page] = {
  case Root => RootPage()
  case Root / "login" => Login()
  case Root / "register" => Register()
  case Root / "profile" / userId => Profile(userId)
  case _ => NotFound()
}

routes(Root)
// res0: Page = RootPage()
routes(Root / "login")
// res1: Page = Login()
routes(Root / "profile" / "saopa98f")
// res2: Page = Profile("saopa98f")

routes(Path("/profile/asd"))
// res3: Page = Profile("asd")
routes(Path("/apsinoasn"))
// res4: Page = NotFound()

A minimum working usage example:

import outwatch._
import outwatch.dsl._

import cats.effect.ExitCode
import monix.bio._
import outwatch.router.AppRouter
import outwatch.router.dsl._

object IntVar {
    def unapply(str: String): Option[Int] = {
      if (!str.isEmpty) str.toIntOption
      else None
    }
  }

object OutwatchApp extends BIOApp {

  sealed trait Page
  case object Home extends Page
  case object SomePage extends Page
  case class UserHome(id: Int) extends Page
  case object NotFound extends Page

  val router = AppRouter.create[Task, Page](NotFound) {
    case Root                       => Home
    case Root / "user" / IntVar(id) => UserHome(id)
    case Root / "some-page"         => SomePage
  }

  def resolver: PartialFunction[Page, VDomModifier] = {
    case Home         => div("hm")
    case SomePage     => div("page1")
    case UserHome(id) => div(s"User id: $id")
    case NotFound     => div("notfound")
  }

  def run(args: List[String]): UIO[ExitCode] = {
    import org.scalajs.dom.document
    val el = document.createElement("div")
    el.setAttribute("id", "#myapp")
    document.body.appendChild(el)
    router.store
      .flatMap(implicit store =>
        OutWatch
          .renderInto(
            el,
            div(
              div(
                cls := "four wide column",
                ul(
                  li(router.link("/")("Home")),
                  li(router.link("/some-page")("SomePage")),
                  li(router.link("/user/0")("User Home")),
                  // simulate not found
                  li(router.link("/about")("About"))
                )
              ),
              router.render(resolver),
              // watch for history events
              router.watch()
            )
          )
      )
      .onErrorHandleWith(ex => UIO(ex.printStackTrace()))
      .as(ExitCode.Success)
  }
}

Note that although monix’s BIO is used here, any effect that implements cats.effect.Sync can be used.