From 008137f0e1e2f29bcd24d3244385338fa194583c Mon Sep 17 00:00:00 2001 From: = Date: Mon, 1 May 2023 16:02:39 +0200 Subject: [PATCH] added custom router build on 3rd party library --- .../routing/src/main/scala/example/Main.scala | 173 ++++++++++++------ 1 file changed, 120 insertions(+), 53 deletions(-) diff --git a/examples/routing/src/main/scala/example/Main.scala b/examples/routing/src/main/scala/example/Main.scala index 112ca40d..5c6fae80 100644 --- a/examples/routing/src/main/scala/example/Main.scala +++ b/examples/routing/src/main/scala/example/Main.scala @@ -1,9 +1,13 @@ package myorg import cats.effect.IO +import myorg.Page.getPage import org.scalajs.dom import tyrian.Html.{param => _, _} import tyrian.* +import urldsl.errors.SimplePathMatchingError +import urldsl.language.PathSegment +import urldsl.language.PathSegmentWithQueryParams import urldsl.language.simpleErrorImpl.* import urldsl.vocabulary.Param import urldsl.vocabulary.Segment @@ -14,42 +18,12 @@ import scala.scalajs.js.annotation.JSExportTopLevel @JSExportTopLevel("TyrianApp") object HelloTyrian extends TyrianApp[Msg, Model]: - val homePath = root / "home" - val counterPath = root / "counter" - val moreComplexPath = root / "id" / segment[Int] - val pathWithParam = (root / "user" / endOfSegments) ? param[Int]("age").? - - private def getPageFromURL(path: String): Page = - homePath - .matchRawUrl(path) - .fold( - _ => - counterPath - .matchRawUrl(path) - .fold( - _ => - moreComplexPath - .matchRawUrl(path) - .fold( - _ => - pathWithParam - .matchRawUrl(path) - .fold( - _ => Page.NotFound, - { case UrlMatching(_, ageOption) => - Page.UserAgePage(ageOption) - } - ), - userId => Page.UserPage(userId) - ), - _ => Page.Counter - ), - _ => Page.Home - ) - def init(flags: Map[String, String]): (Model, Cmd[IO, Msg]) = ( - AppState(getPageFromURL(dom.window.location.href), counter = 0), + AppState( + dom.window.location.href.getPage(), + counter = 0 + ), Cmd.None ) @@ -57,11 +31,33 @@ object HelloTyrian extends TyrianApp[Msg, Model]: case Msg.Increment => (model.copy(counter = model.counter + 1), Cmd.None) case Msg.Decrement => (model.copy(counter = model.counter - 1), Cmd.None) case Msg.Reset => (model.copy(counter = 0), Cmd.None) - case Msg.GoToPage(page) => - (model.copy(page = page), Cmd.SideEffect(dom.window.history.replaceState({}, "TyrianApp", Page.toString(page)))) + case Msg.Void => (model, Cmd.None) + case Msg.GoToCounterPage => + val newModel = model.copy(page = Page.Counter) + + ( + newModel, + Cmd.SideEffect( + dom.window.history + .replaceState({}, "Counter", newModel.page.asString()) + ) + ) + case Msg.GoToHomePage => + val newModel = model.copy(page = Page.Home) + + ( + newModel, + Cmd.SideEffect( + dom.window.history + .replaceState({}, "Home", newModel.page.asString()) + ) + ) def viewHome(model: Model): Html[Msg] = - div(p("Hello from home!")) + div( + p("Hello from home!"), + button(onClick(Msg.GoToCounterPage))("Go counter") + ) def viewCounter(model: Model): Html[Msg] = div(`class` := "flex flex-col justify-center items-center")( @@ -87,17 +83,27 @@ object HelloTyrian extends TyrianApp[Msg, Model]: else button( `class` := "bg-red-300 text-gray-700 font-bold py-2 px-4 rounded mt-2 cursor-not-allowed" - )("Reset") + )("Reset"), + button(onClick(Msg.GoToHomePage))("Go to homepage") ) def viewNotFound(): Html[Msg] = - div(p("Not found the requested page"), button(onClick(Msg.GoToPage(Page.Home)))("Go Home")) + div( + p("Not found the requested page"), + button(onClick(Msg.GoToHomePage))("Go to homepage") + ) def viewUserPage(userId: Int): Html[Msg] = - div(p(s"Welcome at user page with user id: ${userId}")) + div( + p(s"Welcome at user page with user id: ${userId}"), + button(onClick(Msg.GoToHomePage))("Go to home") + ) def viewUserMaybeAgePage(ageOption: Option[Int]): Html[Msg] = - ageOption.fold(div(p("You're so boring...")))(age => div(p(s"Thanks, you are cool and have $age years!"))) + ageOption.fold(div(p("You're so boring...")))(age => + div(p(s"Thanks, you are cool and have $age years!")) + ) + def view(model: Model): Html[Msg] = model.page match { @@ -117,20 +123,79 @@ enum Page { case NotFound case UserPage(userId: Int) case UserAgePage(ageOption: Option[Int]) + } object Page { - def toString(p: Page) = p match { - case Home => "/home" - case Counter => "/counter" - case UserPage(userId) => s"/id/${userId}" - case UserAgePage(ageOption) => - ageOption match { - case None => "/user" - case Some(age) => s"/user?age=${age}" - } - case _ => ??? - } + val homePath = root / "home" + val counterPath = root / "counter" + val moreComplexPath = root / "id" / segment[Int] + val pathWithParam = (root / "user" / endOfSegments) ? param[Int]("age").? + + case class SimpleRoute[X, P]( + pathSegment: PathSegment[X, SimplePathMatchingError], + combinator: X => P + ) + + case class ComplexRoute[X, P]( + pathSegment: PathSegmentWithQueryParams[?, SimplePathMatchingError, X, ?], + combinator: UrlMatching[?, X] => P + ) + + def routerFromList( + xs: List[SimpleRoute[?, Page] | ComplexRoute[?, Page]], + path: String + ): Page = + xs match { + case Nil => Page.NotFound + case (head: SimpleRoute[?, Page]) :: Nil => + head.pathSegment + .matchRawUrl(path) + .fold[Page](_ => Page.NotFound, head.combinator(_)) + case (head: ComplexRoute[?, Page]) :: Nil => + head.pathSegment + .matchRawUrl(path) + .fold[Page](_ => Page.NotFound, head.combinator(_)) + case (head: SimpleRoute[?, Page]) :: tail => + head.pathSegment + .matchRawUrl(path) + .fold[Page](_ => routerFromList(tail, path), head.combinator(_)) + case (head: ComplexRoute[?, Page]) :: tail => + head.pathSegment + .matchRawUrl(path) + .fold[Page](_ => routerFromList(tail, path), head.combinator(_)) + } + + extension (p: Page) + def asString(): String = p match { + case Counter => "/counter" + case Home => "/home" + case NotFound => "/notfound" + case UserPage(userId) => s"/id/${userId}" + case UserAgePage(ageOption) => + ageOption match { + case None => "/user" + case Some(age) => s"/user?age=${age}" + } + } + + extension (path: String) + def getPage(): Page = routerFromList( + List( + SimpleRoute[Unit, Page](homePath, _ => Page.Home), + SimpleRoute[Unit, Page](counterPath, _ => Page.Counter), + SimpleRoute[Int, Page]( + moreComplexPath, + (userId: Int) => Page.UserPage(userId) + ), + ComplexRoute[Option[Int], Page]( + pathWithParam, + { case UrlMatching(_, ageOption) => Page.UserAgePage(ageOption) } + ) + ), + path + ) + } case class AppState(page: Page, counter: Int) @@ -139,4 +204,6 @@ type Model = AppState enum Msg: case Increment, Decrement, Reset - case GoToPage(p: Page) + case Void + case GoToCounterPage + case GoToHomePage