diff --git a/app/src/main/scala/mdr/pdb/app/LaminarApp.scala b/app/src/main/scala/mdr/pdb/app/LaminarApp.scala new file mode 100644 index 0000000..151e08d --- /dev/null +++ b/app/src/main/scala/mdr/pdb/app/LaminarApp.scala @@ -0,0 +1,91 @@ +package mdr.pdb.app + +import zio.* +import com.raquo.laminar.api.L.{*, given} +import com.raquo.waypoint.Router +import org.scalajs.dom +import mdr.pdb.app.state.AppState +import com.raquo.waypoint.SplitRender + +trait LaminarApp: + def renderApp: Task[Unit] + +object LaminarApp: + def renderApp: RIO[LaminarApp, Unit] = ZIO.serviceWith(_.renderApp) + +object LaminarAppLive: + val layer: URLayer[Router[Page], LaminarApp] = + (LaminarAppLive(_)).toLayer[LaminarApp] + +class LaminarAppLive(router: Router[Page]) extends LaminarApp: + given Router[Page] = router + + def renderApp: Task[Unit] = + for + _ <- setupAirstream + _ <- renderLaminar + yield () + + private def renderLaminar: Task[Unit] = + Task.attempt { + val appContainer = dom.document.querySelector("#app") + render( + appContainer, + renderPage(state.MockAppState(using unsafeWindowOwner, router)) + ) + } + + private def setupAirstream: Task[Unit] = + Task.attempt { + AirstreamError.registerUnhandledErrorCallback(err => + router.forcePage( + Page.UnhandledError( + Some(err.getClass.getName), // TODO: Fill only in dev mode + Some(err.getMessage) + ) + ) + ) + } + + private def renderPage(state: AppState)(using + router: Router[Page] + ): HtmlElement = + val pageSplitter = SplitRender[Page, HtmlElement](router.$currentPage) + .collectSignal[Page.Detail]( + connectors + .DetailPageConnector(state)(_) + .apply + ) + .collectSignal[Page.DetailParametru]( + connectors + .DetailParametruPageConnector(state)(_) + .apply + ) + .collectSignal[Page.DetailKriteria]( + connectors + .DetailKriteriaPageConnector(state)(_) + .apply + ) + .collectSignal[Page.UpravDukazKriteria]( + pages.detail.UpravDukaz.Connector(state)(_).apply + ) + .collectStatic(Page.Dashboard)( + connectors.DashboardPageConnector(state).apply + ) + .collect[Page.NotFound](pg => + pages.errors.NotFoundPage(Routes.homePage, pg.url, state.actionBus) + ) + .collect[Page.UnhandledError](pg => + pages.errors + .UnhandledErrorPage( + pages.errors.UnhandledErrorPage + .ViewModel(Routes.homePage, pg.errorName, pg.errorMessage), + state.actionBus + ) + ) + .collectStatic(Page.Directory)( + connectors + .DirectoryPageConnector(state) + .apply + ) + div(cls := "h-full", child <-- pageSplitter.$view) diff --git a/app/src/main/scala/mdr/pdb/app/LaminarApp.scala b/app/src/main/scala/mdr/pdb/app/LaminarApp.scala new file mode 100644 index 0000000..151e08d --- /dev/null +++ b/app/src/main/scala/mdr/pdb/app/LaminarApp.scala @@ -0,0 +1,91 @@ +package mdr.pdb.app + +import zio.* +import com.raquo.laminar.api.L.{*, given} +import com.raquo.waypoint.Router +import org.scalajs.dom +import mdr.pdb.app.state.AppState +import com.raquo.waypoint.SplitRender + +trait LaminarApp: + def renderApp: Task[Unit] + +object LaminarApp: + def renderApp: RIO[LaminarApp, Unit] = ZIO.serviceWith(_.renderApp) + +object LaminarAppLive: + val layer: URLayer[Router[Page], LaminarApp] = + (LaminarAppLive(_)).toLayer[LaminarApp] + +class LaminarAppLive(router: Router[Page]) extends LaminarApp: + given Router[Page] = router + + def renderApp: Task[Unit] = + for + _ <- setupAirstream + _ <- renderLaminar + yield () + + private def renderLaminar: Task[Unit] = + Task.attempt { + val appContainer = dom.document.querySelector("#app") + render( + appContainer, + renderPage(state.MockAppState(using unsafeWindowOwner, router)) + ) + } + + private def setupAirstream: Task[Unit] = + Task.attempt { + AirstreamError.registerUnhandledErrorCallback(err => + router.forcePage( + Page.UnhandledError( + Some(err.getClass.getName), // TODO: Fill only in dev mode + Some(err.getMessage) + ) + ) + ) + } + + private def renderPage(state: AppState)(using + router: Router[Page] + ): HtmlElement = + val pageSplitter = SplitRender[Page, HtmlElement](router.$currentPage) + .collectSignal[Page.Detail]( + connectors + .DetailPageConnector(state)(_) + .apply + ) + .collectSignal[Page.DetailParametru]( + connectors + .DetailParametruPageConnector(state)(_) + .apply + ) + .collectSignal[Page.DetailKriteria]( + connectors + .DetailKriteriaPageConnector(state)(_) + .apply + ) + .collectSignal[Page.UpravDukazKriteria]( + pages.detail.UpravDukaz.Connector(state)(_).apply + ) + .collectStatic(Page.Dashboard)( + connectors.DashboardPageConnector(state).apply + ) + .collect[Page.NotFound](pg => + pages.errors.NotFoundPage(Routes.homePage, pg.url, state.actionBus) + ) + .collect[Page.UnhandledError](pg => + pages.errors + .UnhandledErrorPage( + pages.errors.UnhandledErrorPage + .ViewModel(Routes.homePage, pg.errorName, pg.errorMessage), + state.actionBus + ) + ) + .collectStatic(Page.Directory)( + connectors + .DirectoryPageConnector(state) + .apply + ) + div(cls := "h-full", child <-- pageSplitter.$view) diff --git a/app/src/main/scala/mdr/pdb/app/Main.scala b/app/src/main/scala/mdr/pdb/app/Main.scala index 8c9960c..25582c1 100644 --- a/app/src/main/scala/mdr/pdb/app/Main.scala +++ b/app/src/main/scala/mdr/pdb/app/Main.scala @@ -11,7 +11,8 @@ import com.raquo.waypoint.SplitRender import mdr.pdb.app.services.DataFetcher import scala.scalajs.js.JSON -import zio.json._ +import zio.* +import zio.json.* import mdr.pdb.UserInfo import mdr.pdb.app.state.AppState @@ -28,81 +29,37 @@ object pdbParams extends js.Object @JSExportTopLevel("app") -object Main: +object Main extends ZIOApp: - @JSExport - def main(args: Array[String]): Unit = + override type Environment = ZEnv & LaminarApp + + override val tag: EnvironmentTag[Environment] = EnvironmentTag[Environment] + + override val layer: ZLayer[ZIOAppArgs, Any, Environment] = + ZEnv.live ++ (Routes.layer >>> LaminarAppLive.layer) + + override def run = + for + _ <- RIO.async[LaminarApp, Unit](cb => + documentEvents.onDomContentLoaded + .foreach(_ => cb(program))(unsafeWindowOwner) + ) + yield () + + private def program: RIO[LaminarApp, Unit] = import Routes.given - onLoad { - Api(Some("/mdr/pdb/api")) - .alive(()) - .foreach(org.scalajs.dom.console.log(_))(using - scala.concurrent.ExecutionContext.global - ) - setupAirstream() - val appContainer = dom.document.querySelector("#app") - val _ = - render( - appContainer, - renderPage(state.MockAppState(using unsafeWindowOwner, router)) - ) - } + for + _ <- testApi + _ <- LaminarApp.renderApp + yield () - private def onLoad(f: => Unit): Unit = - documentEvents.onDomContentLoaded.foreach(_ => f)(unsafeWindowOwner) - - private def setupAirstream()(using router: Router[Page]): Unit = - AirstreamError.registerUnhandledErrorCallback(err => - router.forcePage( - Page.UnhandledError( - Some(err.getClass.getName), // TODO: Fill only in dev mode - Some(err.getMessage) - ) + private val testApi: Task[Unit] = Task.attempt { + Api(Some("/mdr/pdb/api")) + .alive(()) + .foreach(org.scalajs.dom.console.log(_))(using + scala.concurrent.ExecutionContext.global ) - ) - - def renderPage(state: AppState)(using - router: Router[Page] - ): HtmlElement = - val pageSplitter = SplitRender[Page, HtmlElement](router.$currentPage) - .collectSignal[Page.Detail]( - connectors - .DetailPageConnector(state)(_) - .apply - ) - .collectSignal[Page.DetailParametru]( - connectors - .DetailParametruPageConnector(state)(_) - .apply - ) - .collectSignal[Page.DetailKriteria]( - connectors - .DetailKriteriaPageConnector(state)(_) - .apply - ) - .collectSignal[Page.UpravDukazKriteria]( - pages.detail.UpravDukaz.Connector(state)(_).apply - ) - .collectStatic(Page.Dashboard)( - connectors.DashboardPageConnector(state).apply - ) - .collect[Page.NotFound](pg => - pages.errors.NotFoundPage(Routes.homePage, pg.url, state.actionBus) - ) - .collect[Page.UnhandledError](pg => - pages.errors - .UnhandledErrorPage( - pages.errors.UnhandledErrorPage - .ViewModel(Routes.homePage, pg.errorName, pg.errorMessage), - state.actionBus - ) - ) - .collectStatic(Page.Directory)( - connectors - .DirectoryPageConnector(state) - .apply - ) - div(cls := "h-full", child <-- pageSplitter.$view) + } // Pull in the stylesheet val css: Css.type = Css diff --git a/app/src/main/scala/mdr/pdb/app/LaminarApp.scala b/app/src/main/scala/mdr/pdb/app/LaminarApp.scala new file mode 100644 index 0000000..151e08d --- /dev/null +++ b/app/src/main/scala/mdr/pdb/app/LaminarApp.scala @@ -0,0 +1,91 @@ +package mdr.pdb.app + +import zio.* +import com.raquo.laminar.api.L.{*, given} +import com.raquo.waypoint.Router +import org.scalajs.dom +import mdr.pdb.app.state.AppState +import com.raquo.waypoint.SplitRender + +trait LaminarApp: + def renderApp: Task[Unit] + +object LaminarApp: + def renderApp: RIO[LaminarApp, Unit] = ZIO.serviceWith(_.renderApp) + +object LaminarAppLive: + val layer: URLayer[Router[Page], LaminarApp] = + (LaminarAppLive(_)).toLayer[LaminarApp] + +class LaminarAppLive(router: Router[Page]) extends LaminarApp: + given Router[Page] = router + + def renderApp: Task[Unit] = + for + _ <- setupAirstream + _ <- renderLaminar + yield () + + private def renderLaminar: Task[Unit] = + Task.attempt { + val appContainer = dom.document.querySelector("#app") + render( + appContainer, + renderPage(state.MockAppState(using unsafeWindowOwner, router)) + ) + } + + private def setupAirstream: Task[Unit] = + Task.attempt { + AirstreamError.registerUnhandledErrorCallback(err => + router.forcePage( + Page.UnhandledError( + Some(err.getClass.getName), // TODO: Fill only in dev mode + Some(err.getMessage) + ) + ) + ) + } + + private def renderPage(state: AppState)(using + router: Router[Page] + ): HtmlElement = + val pageSplitter = SplitRender[Page, HtmlElement](router.$currentPage) + .collectSignal[Page.Detail]( + connectors + .DetailPageConnector(state)(_) + .apply + ) + .collectSignal[Page.DetailParametru]( + connectors + .DetailParametruPageConnector(state)(_) + .apply + ) + .collectSignal[Page.DetailKriteria]( + connectors + .DetailKriteriaPageConnector(state)(_) + .apply + ) + .collectSignal[Page.UpravDukazKriteria]( + pages.detail.UpravDukaz.Connector(state)(_).apply + ) + .collectStatic(Page.Dashboard)( + connectors.DashboardPageConnector(state).apply + ) + .collect[Page.NotFound](pg => + pages.errors.NotFoundPage(Routes.homePage, pg.url, state.actionBus) + ) + .collect[Page.UnhandledError](pg => + pages.errors + .UnhandledErrorPage( + pages.errors.UnhandledErrorPage + .ViewModel(Routes.homePage, pg.errorName, pg.errorMessage), + state.actionBus + ) + ) + .collectStatic(Page.Directory)( + connectors + .DirectoryPageConnector(state) + .apply + ) + div(cls := "h-full", child <-- pageSplitter.$view) diff --git a/app/src/main/scala/mdr/pdb/app/Main.scala b/app/src/main/scala/mdr/pdb/app/Main.scala index 8c9960c..25582c1 100644 --- a/app/src/main/scala/mdr/pdb/app/Main.scala +++ b/app/src/main/scala/mdr/pdb/app/Main.scala @@ -11,7 +11,8 @@ import com.raquo.waypoint.SplitRender import mdr.pdb.app.services.DataFetcher import scala.scalajs.js.JSON -import zio.json._ +import zio.* +import zio.json.* import mdr.pdb.UserInfo import mdr.pdb.app.state.AppState @@ -28,81 +29,37 @@ object pdbParams extends js.Object @JSExportTopLevel("app") -object Main: +object Main extends ZIOApp: - @JSExport - def main(args: Array[String]): Unit = + override type Environment = ZEnv & LaminarApp + + override val tag: EnvironmentTag[Environment] = EnvironmentTag[Environment] + + override val layer: ZLayer[ZIOAppArgs, Any, Environment] = + ZEnv.live ++ (Routes.layer >>> LaminarAppLive.layer) + + override def run = + for + _ <- RIO.async[LaminarApp, Unit](cb => + documentEvents.onDomContentLoaded + .foreach(_ => cb(program))(unsafeWindowOwner) + ) + yield () + + private def program: RIO[LaminarApp, Unit] = import Routes.given - onLoad { - Api(Some("/mdr/pdb/api")) - .alive(()) - .foreach(org.scalajs.dom.console.log(_))(using - scala.concurrent.ExecutionContext.global - ) - setupAirstream() - val appContainer = dom.document.querySelector("#app") - val _ = - render( - appContainer, - renderPage(state.MockAppState(using unsafeWindowOwner, router)) - ) - } + for + _ <- testApi + _ <- LaminarApp.renderApp + yield () - private def onLoad(f: => Unit): Unit = - documentEvents.onDomContentLoaded.foreach(_ => f)(unsafeWindowOwner) - - private def setupAirstream()(using router: Router[Page]): Unit = - AirstreamError.registerUnhandledErrorCallback(err => - router.forcePage( - Page.UnhandledError( - Some(err.getClass.getName), // TODO: Fill only in dev mode - Some(err.getMessage) - ) + private val testApi: Task[Unit] = Task.attempt { + Api(Some("/mdr/pdb/api")) + .alive(()) + .foreach(org.scalajs.dom.console.log(_))(using + scala.concurrent.ExecutionContext.global ) - ) - - def renderPage(state: AppState)(using - router: Router[Page] - ): HtmlElement = - val pageSplitter = SplitRender[Page, HtmlElement](router.$currentPage) - .collectSignal[Page.Detail]( - connectors - .DetailPageConnector(state)(_) - .apply - ) - .collectSignal[Page.DetailParametru]( - connectors - .DetailParametruPageConnector(state)(_) - .apply - ) - .collectSignal[Page.DetailKriteria]( - connectors - .DetailKriteriaPageConnector(state)(_) - .apply - ) - .collectSignal[Page.UpravDukazKriteria]( - pages.detail.UpravDukaz.Connector(state)(_).apply - ) - .collectStatic(Page.Dashboard)( - connectors.DashboardPageConnector(state).apply - ) - .collect[Page.NotFound](pg => - pages.errors.NotFoundPage(Routes.homePage, pg.url, state.actionBus) - ) - .collect[Page.UnhandledError](pg => - pages.errors - .UnhandledErrorPage( - pages.errors.UnhandledErrorPage - .ViewModel(Routes.homePage, pg.errorName, pg.errorMessage), - state.actionBus - ) - ) - .collectStatic(Page.Directory)( - connectors - .DirectoryPageConnector(state) - .apply - ) - div(cls := "h-full", child <-- pageSplitter.$view) + } // Pull in the stylesheet val css: Css.type = Css diff --git a/app/src/main/scala/mdr/pdb/app/Page.scala b/app/src/main/scala/mdr/pdb/app/Page.scala new file mode 100644 index 0000000..7e067a2 --- /dev/null +++ b/app/src/main/scala/mdr/pdb/app/Page.scala @@ -0,0 +1,104 @@ +package mdr.pdb.app + +import mdr.pdb.OsobniCislo +import mdr.pdb.UserInfo +import mdr.pdb.Parameter +import mdr.pdb.ParameterCriteria + +// enum is not working with Waypoints' SplitRender collectStatic +sealed abstract class Page( + val id: String, + val title: String, + val parent: Option[Page] +) { + val path: Vector[Page] = + parent match + case None => Vector(this) + case Some(p) => p.path :+ this + + val isRoot: Boolean = parent.isEmpty +} + +object Page: + + case class Titled[V](value: V, title: Option[String] = None): + val show: String = title.getOrElse(value.toString) + + case object Directory extends Page("directory", "Adresář", None) + + case object Dashboard extends Page("dashboard", "Přehled", Some(Directory)) + + case class Detail(osobniCislo: Titled[OsobniCislo]) + extends Page("user", osobniCislo.show, Some(Directory)) + + object Detail { + def apply(o: UserInfo): Detail = Detail( + Titled(o.personalNumber, Some(o.name)) + ) + } + + case class DetailParametru( + osobniCislo: Titled[OsobniCislo], + parametr: Titled[String] + ) extends Page( + "parameter", + parametr.show, + Some(Detail(osobniCislo)) + ) + + object DetailParametru { + def apply(o: UserInfo, p: Parameter): DetailParametru = + DetailParametru( + Titled(o.personalNumber, Some(o.name)), + Titled(p.id, Some(p.name)) + ) + } + + case class DetailKriteria( + osobniCislo: Titled[OsobniCislo], + parametr: Titled[String], + kriterium: Titled[String] + ) extends Page( + "criteria", + kriterium.show, + Some(DetailParametru(osobniCislo, parametr)) + ) + + object DetailKriteria { + def apply(o: UserInfo, p: Parameter, k: ParameterCriteria): DetailKriteria = + DetailKriteria( + Titled(o.personalNumber, Some(o.name)), + Titled(p.id, Some(p.name)), + Titled(k.id, Some(k.id)) + ) + } + + case class UpravDukazKriteria( + osobniCislo: Titled[OsobniCislo], + parametr: Titled[String], + kriterium: Titled[String] + ) extends Page( + "addProof", + "Důkaz", + Some(DetailKriteria(osobniCislo, parametr, kriterium)) + ) + + object UpravDukazKriteria { + def apply( + o: UserInfo, + p: Parameter, + k: ParameterCriteria + ): UpravDukazKriteria = + UpravDukazKriteria( + Titled(o.personalNumber, Some(o.name)), + Titled(p.id, Some(p.name)), + Titled(k.id, Some(k.id)) + ) + } + + case class NotFound(url: String) extends Page("404", "404", Some(Directory)) + + case class UnhandledError( + errorName: Option[String], + errorMessage: Option[String] + ) extends Page("500", "Unexpected error", Some(Directory)) diff --git a/app/src/main/scala/mdr/pdb/app/LaminarApp.scala b/app/src/main/scala/mdr/pdb/app/LaminarApp.scala new file mode 100644 index 0000000..151e08d --- /dev/null +++ b/app/src/main/scala/mdr/pdb/app/LaminarApp.scala @@ -0,0 +1,91 @@ +package mdr.pdb.app + +import zio.* +import com.raquo.laminar.api.L.{*, given} +import com.raquo.waypoint.Router +import org.scalajs.dom +import mdr.pdb.app.state.AppState +import com.raquo.waypoint.SplitRender + +trait LaminarApp: + def renderApp: Task[Unit] + +object LaminarApp: + def renderApp: RIO[LaminarApp, Unit] = ZIO.serviceWith(_.renderApp) + +object LaminarAppLive: + val layer: URLayer[Router[Page], LaminarApp] = + (LaminarAppLive(_)).toLayer[LaminarApp] + +class LaminarAppLive(router: Router[Page]) extends LaminarApp: + given Router[Page] = router + + def renderApp: Task[Unit] = + for + _ <- setupAirstream + _ <- renderLaminar + yield () + + private def renderLaminar: Task[Unit] = + Task.attempt { + val appContainer = dom.document.querySelector("#app") + render( + appContainer, + renderPage(state.MockAppState(using unsafeWindowOwner, router)) + ) + } + + private def setupAirstream: Task[Unit] = + Task.attempt { + AirstreamError.registerUnhandledErrorCallback(err => + router.forcePage( + Page.UnhandledError( + Some(err.getClass.getName), // TODO: Fill only in dev mode + Some(err.getMessage) + ) + ) + ) + } + + private def renderPage(state: AppState)(using + router: Router[Page] + ): HtmlElement = + val pageSplitter = SplitRender[Page, HtmlElement](router.$currentPage) + .collectSignal[Page.Detail]( + connectors + .DetailPageConnector(state)(_) + .apply + ) + .collectSignal[Page.DetailParametru]( + connectors + .DetailParametruPageConnector(state)(_) + .apply + ) + .collectSignal[Page.DetailKriteria]( + connectors + .DetailKriteriaPageConnector(state)(_) + .apply + ) + .collectSignal[Page.UpravDukazKriteria]( + pages.detail.UpravDukaz.Connector(state)(_).apply + ) + .collectStatic(Page.Dashboard)( + connectors.DashboardPageConnector(state).apply + ) + .collect[Page.NotFound](pg => + pages.errors.NotFoundPage(Routes.homePage, pg.url, state.actionBus) + ) + .collect[Page.UnhandledError](pg => + pages.errors + .UnhandledErrorPage( + pages.errors.UnhandledErrorPage + .ViewModel(Routes.homePage, pg.errorName, pg.errorMessage), + state.actionBus + ) + ) + .collectStatic(Page.Directory)( + connectors + .DirectoryPageConnector(state) + .apply + ) + div(cls := "h-full", child <-- pageSplitter.$view) diff --git a/app/src/main/scala/mdr/pdb/app/Main.scala b/app/src/main/scala/mdr/pdb/app/Main.scala index 8c9960c..25582c1 100644 --- a/app/src/main/scala/mdr/pdb/app/Main.scala +++ b/app/src/main/scala/mdr/pdb/app/Main.scala @@ -11,7 +11,8 @@ import com.raquo.waypoint.SplitRender import mdr.pdb.app.services.DataFetcher import scala.scalajs.js.JSON -import zio.json._ +import zio.* +import zio.json.* import mdr.pdb.UserInfo import mdr.pdb.app.state.AppState @@ -28,81 +29,37 @@ object pdbParams extends js.Object @JSExportTopLevel("app") -object Main: +object Main extends ZIOApp: - @JSExport - def main(args: Array[String]): Unit = + override type Environment = ZEnv & LaminarApp + + override val tag: EnvironmentTag[Environment] = EnvironmentTag[Environment] + + override val layer: ZLayer[ZIOAppArgs, Any, Environment] = + ZEnv.live ++ (Routes.layer >>> LaminarAppLive.layer) + + override def run = + for + _ <- RIO.async[LaminarApp, Unit](cb => + documentEvents.onDomContentLoaded + .foreach(_ => cb(program))(unsafeWindowOwner) + ) + yield () + + private def program: RIO[LaminarApp, Unit] = import Routes.given - onLoad { - Api(Some("/mdr/pdb/api")) - .alive(()) - .foreach(org.scalajs.dom.console.log(_))(using - scala.concurrent.ExecutionContext.global - ) - setupAirstream() - val appContainer = dom.document.querySelector("#app") - val _ = - render( - appContainer, - renderPage(state.MockAppState(using unsafeWindowOwner, router)) - ) - } + for + _ <- testApi + _ <- LaminarApp.renderApp + yield () - private def onLoad(f: => Unit): Unit = - documentEvents.onDomContentLoaded.foreach(_ => f)(unsafeWindowOwner) - - private def setupAirstream()(using router: Router[Page]): Unit = - AirstreamError.registerUnhandledErrorCallback(err => - router.forcePage( - Page.UnhandledError( - Some(err.getClass.getName), // TODO: Fill only in dev mode - Some(err.getMessage) - ) + private val testApi: Task[Unit] = Task.attempt { + Api(Some("/mdr/pdb/api")) + .alive(()) + .foreach(org.scalajs.dom.console.log(_))(using + scala.concurrent.ExecutionContext.global ) - ) - - def renderPage(state: AppState)(using - router: Router[Page] - ): HtmlElement = - val pageSplitter = SplitRender[Page, HtmlElement](router.$currentPage) - .collectSignal[Page.Detail]( - connectors - .DetailPageConnector(state)(_) - .apply - ) - .collectSignal[Page.DetailParametru]( - connectors - .DetailParametruPageConnector(state)(_) - .apply - ) - .collectSignal[Page.DetailKriteria]( - connectors - .DetailKriteriaPageConnector(state)(_) - .apply - ) - .collectSignal[Page.UpravDukazKriteria]( - pages.detail.UpravDukaz.Connector(state)(_).apply - ) - .collectStatic(Page.Dashboard)( - connectors.DashboardPageConnector(state).apply - ) - .collect[Page.NotFound](pg => - pages.errors.NotFoundPage(Routes.homePage, pg.url, state.actionBus) - ) - .collect[Page.UnhandledError](pg => - pages.errors - .UnhandledErrorPage( - pages.errors.UnhandledErrorPage - .ViewModel(Routes.homePage, pg.errorName, pg.errorMessage), - state.actionBus - ) - ) - .collectStatic(Page.Directory)( - connectors - .DirectoryPageConnector(state) - .apply - ) - div(cls := "h-full", child <-- pageSplitter.$view) + } // Pull in the stylesheet val css: Css.type = Css diff --git a/app/src/main/scala/mdr/pdb/app/Page.scala b/app/src/main/scala/mdr/pdb/app/Page.scala new file mode 100644 index 0000000..7e067a2 --- /dev/null +++ b/app/src/main/scala/mdr/pdb/app/Page.scala @@ -0,0 +1,104 @@ +package mdr.pdb.app + +import mdr.pdb.OsobniCislo +import mdr.pdb.UserInfo +import mdr.pdb.Parameter +import mdr.pdb.ParameterCriteria + +// enum is not working with Waypoints' SplitRender collectStatic +sealed abstract class Page( + val id: String, + val title: String, + val parent: Option[Page] +) { + val path: Vector[Page] = + parent match + case None => Vector(this) + case Some(p) => p.path :+ this + + val isRoot: Boolean = parent.isEmpty +} + +object Page: + + case class Titled[V](value: V, title: Option[String] = None): + val show: String = title.getOrElse(value.toString) + + case object Directory extends Page("directory", "Adresář", None) + + case object Dashboard extends Page("dashboard", "Přehled", Some(Directory)) + + case class Detail(osobniCislo: Titled[OsobniCislo]) + extends Page("user", osobniCislo.show, Some(Directory)) + + object Detail { + def apply(o: UserInfo): Detail = Detail( + Titled(o.personalNumber, Some(o.name)) + ) + } + + case class DetailParametru( + osobniCislo: Titled[OsobniCislo], + parametr: Titled[String] + ) extends Page( + "parameter", + parametr.show, + Some(Detail(osobniCislo)) + ) + + object DetailParametru { + def apply(o: UserInfo, p: Parameter): DetailParametru = + DetailParametru( + Titled(o.personalNumber, Some(o.name)), + Titled(p.id, Some(p.name)) + ) + } + + case class DetailKriteria( + osobniCislo: Titled[OsobniCislo], + parametr: Titled[String], + kriterium: Titled[String] + ) extends Page( + "criteria", + kriterium.show, + Some(DetailParametru(osobniCislo, parametr)) + ) + + object DetailKriteria { + def apply(o: UserInfo, p: Parameter, k: ParameterCriteria): DetailKriteria = + DetailKriteria( + Titled(o.personalNumber, Some(o.name)), + Titled(p.id, Some(p.name)), + Titled(k.id, Some(k.id)) + ) + } + + case class UpravDukazKriteria( + osobniCislo: Titled[OsobniCislo], + parametr: Titled[String], + kriterium: Titled[String] + ) extends Page( + "addProof", + "Důkaz", + Some(DetailKriteria(osobniCislo, parametr, kriterium)) + ) + + object UpravDukazKriteria { + def apply( + o: UserInfo, + p: Parameter, + k: ParameterCriteria + ): UpravDukazKriteria = + UpravDukazKriteria( + Titled(o.personalNumber, Some(o.name)), + Titled(p.id, Some(p.name)), + Titled(k.id, Some(k.id)) + ) + } + + case class NotFound(url: String) extends Page("404", "404", Some(Directory)) + + case class UnhandledError( + errorName: Option[String], + errorMessage: Option[String] + ) extends Page("500", "Unexpected error", Some(Directory)) diff --git a/app/src/main/scala/mdr/pdb/app/Routes.scala b/app/src/main/scala/mdr/pdb/app/Routes.scala index 2cc860c..f81c9d4 100644 --- a/app/src/main/scala/mdr/pdb/app/Routes.scala +++ b/app/src/main/scala/mdr/pdb/app/Routes.scala @@ -1,116 +1,23 @@ package mdr.pdb.app +import zio.* import com.raquo.laminar.api.L.{*, given} import com.raquo.waypoint.* import org.scalajs.dom import zio.json.{*, given} -import mdr.pdb.OsobniCislo - import scala.scalajs.js -import mdr.pdb.UserInfo -import mdr.pdb.Parameter -import mdr.pdb.ParameterCriteria -import mdr.pdb.app.Page.Titled - -// enum is not working with Waypoints' SplitRender collectStatic -sealed abstract class Page( - val id: String, - val title: String, - val parent: Option[Page] -) { - val path: Vector[Page] = - parent match - case None => Vector(this) - case Some(p) => p.path :+ this - - val isRoot: Boolean = parent.isEmpty -} - -object Page: - - case class Titled[V](value: V, title: Option[String] = None): - val show: String = title.getOrElse(value.toString) - - case object Directory extends Page("directory", "Adresář", None) - - case object Dashboard extends Page("dashboard", "Přehled", Some(Directory)) - - case class Detail(osobniCislo: Titled[OsobniCislo]) - extends Page("user", osobniCislo.show, Some(Directory)) - - object Detail { - def apply(o: UserInfo): Detail = Detail( - Titled(o.personalNumber, Some(o.name)) - ) - } - - case class DetailParametru( - osobniCislo: Titled[OsobniCislo], - parametr: Titled[String] - ) extends Page( - "parameter", - parametr.show, - Some(Detail(osobniCislo)) - ) - - object DetailParametru { - def apply(o: UserInfo, p: Parameter): DetailParametru = - DetailParametru( - Titled(o.personalNumber, Some(o.name)), - Titled(p.id, Some(p.name)) - ) - } - - case class DetailKriteria( - osobniCislo: Titled[OsobniCislo], - parametr: Titled[String], - kriterium: Titled[String] - ) extends Page( - "criteria", - kriterium.show, - Some(DetailParametru(osobniCislo, parametr)) - ) - - object DetailKriteria { - def apply(o: UserInfo, p: Parameter, k: ParameterCriteria): DetailKriteria = - DetailKriteria( - Titled(o.personalNumber, Some(o.name)), - Titled(p.id, Some(p.name)), - Titled(k.id, Some(k.id)) - ) - } - - case class UpravDukazKriteria( - osobniCislo: Titled[OsobniCislo], - parametr: Titled[String], - kriterium: Titled[String] - ) extends Page( - "addProof", - "Důkaz", - Some(DetailKriteria(osobniCislo, parametr, kriterium)) - ) - - object UpravDukazKriteria { - def apply( - o: UserInfo, - p: Parameter, - k: ParameterCriteria - ): UpravDukazKriteria = - UpravDukazKriteria( - Titled(o.personalNumber, Some(o.name)), - Titled(p.id, Some(p.name)), - Titled(k.id, Some(k.id)) - ) - } - - case class NotFound(url: String) extends Page("404", "404", Some(Directory)) - - case class UnhandledError( - errorName: Option[String], - errorMessage: Option[String] - ) extends Page("500", "Unexpected error", Some(Directory)) +import mdr.pdb.* object Routes: + + val layer: ULayer[Router[Page]] = ZLayer.succeed(Routes().router) + + val homePage: Page = Page.Directory + +class Routes(): + import Page.* + import Routes.* + given JsonDecoder[OsobniCislo] = JsonDecoder.string.map(OsobniCislo.apply) given JsonEncoder[OsobniCislo] = JsonEncoder.string.contramap(_.toString) given [V: JsonEncoder]: JsonEncoder[Titled[V]] = @@ -124,32 +31,29 @@ js.`import`.meta.env.BASE_URL .asInstanceOf[String] + "app" - val homePage: Page = Page.Directory - given router: Router[Page] = Router[Page]( routes = List( Route.static(homePage, root / endOfSegments, basePath = base), Route.static( - Page.Dashboard, + Dashboard, root / "dashboard" / endOfSegments, basePath = base ), - Route[Page.Detail, String]( + Route[Detail, String]( encode = _.osobniCislo.value.toString, - decode = osc => Page.Detail(Titled(OsobniCislo(osc))), + decode = osc => Detail(Titled(OsobniCislo(osc))), root / "osoba" / segment[String] / endOfSegments, basePath = base ), - Route[Page.DetailParametru, (String, String)]( + Route[DetailParametru, (String, String)]( encode = p => (p.osobniCislo.value.toString, p.parametr.value), - decode = - p => Page.DetailParametru(Titled(OsobniCislo(p._1)), Titled(p._2)), + decode = p => DetailParametru(Titled(OsobniCislo(p._1)), Titled(p._2)), root / "osoba" / segment[String] / "parametr" / segment[ String ] / endOfSegments, basePath = base ), - Route[Page.DetailKriteria, (String, String, String)]( + Route[DetailKriteria, (String, String, String)]( encode = p => ( p.osobniCislo.value.toString, @@ -157,7 +61,7 @@ p.kriterium.value.replaceAll("\\.", "--") ), decode = p => - Page.DetailKriteria( + DetailKriteria( Titled(OsobniCislo(p._1)), Titled(p._2), Titled(p._3.replaceAll("--", ".")) @@ -167,7 +71,7 @@ ] / "kriterium" / segment[String] / endOfSegments, basePath = base ), - Route[Page.UpravDukazKriteria, (String, String, String)]( + Route[UpravDukazKriteria, (String, String, String)]( encode = p => ( p.osobniCislo.value.toString, @@ -175,7 +79,7 @@ p.kriterium.value.replaceAll("\\.", "--") ), decode = p => - Page.UpravDukazKriteria( + UpravDukazKriteria( Titled(OsobniCislo(p._1)), Titled(p._2), Titled(p._3.replaceAll("--", ".")) @@ -190,8 +94,8 @@ deserializePage = _.fromJson[Page] .fold(s => throw IllegalStateException(s), identity), getPageTitle = _.title, - routeFallback = url => Page.NotFound(url), - deserializeFallback = _ => Page.Dashboard + routeFallback = url => NotFound(url), + deserializeFallback = _ => Dashboard )( $popStateEvent = windowEvents.onPopState, owner = unsafeWindowOwner