From f53a1cab66b888971fd38dc6e30dd03621d0763d Mon Sep 17 00:00:00 2001 From: Paul-Henri Froidmont Date: Fri, 15 Aug 2025 22:51:27 +0200 Subject: [PATCH] Serve static html rendering --- build.mill | 7 ++- core/src/scalive/main.scala | 5 +- zio/src/scalive/Example.scala | 100 ++++++++++++++++++++++++++++++++++ 3 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 zio/src/scalive/Example.scala diff --git a/build.mill b/build.mill index 7204e7b..a11c080 100644 --- a/build.mill +++ b/build.mill @@ -1,11 +1,16 @@ package build import mill.*, scalalib.* -object core extends ScalaModule: +trait Common extends ScalaModule: def scalaVersion = "3.7.2" def scalacOptions = Seq("-Wunused:all") + +object core extends Common: def mvnDeps = Seq(mvn"dev.zio::zio-json:0.7.44") object test extends ScalaTests with TestModule.Utest: def utestVersion = "0.9.0" +object zio extends Common: + def mvnDeps = Seq(mvn"dev.zio::zio-http:3.4.0") + override def moduleDeps = Seq(core) diff --git a/core/src/scalive/main.scala b/core/src/scalive/main.scala index 1a4bf43..a534c2e 100644 --- a/core/src/scalive/main.scala +++ b/core/src/scalive/main.scala @@ -16,7 +16,6 @@ def main = ) ) println(lv.fullDiff.toJsonPretty) - println(HtmlBuilder.build(lv)) println("Edit first and last") @@ -30,6 +29,7 @@ def main = ) ) println(lv.diff.toJsonPretty) + println(HtmlBuilder.build(lv)) println("Add one") lv.update( @@ -43,6 +43,7 @@ def main = ) ) println(lv.diff.toJsonPretty) + println(HtmlBuilder.build(lv)) println("Remove first") lv.update( @@ -55,12 +56,14 @@ def main = ) ) println(lv.diff.toJsonPretty) + println(HtmlBuilder.build(lv)) println("Remove all") lv.update( MyModel(List.empty, "text-lg") ) println(lv.diff.toJsonPretty) + println(HtmlBuilder.build(lv)) final case class MyModel(elems: List[NestedModel], cls: String = "text-xs") final case class NestedModel(name: String, age: Int) diff --git a/zio/src/scalive/Example.scala b/zio/src/scalive/Example.scala new file mode 100644 index 0000000..3b6be2c --- /dev/null +++ b/zio/src/scalive/Example.scala @@ -0,0 +1,100 @@ +package scalive + +import zio._ + +import zio.http.ChannelEvent.{ + ExceptionCaught, + Read, + UserEvent, + UserEventTriggered +} +import zio.http._ +import zio.http.codec.PathCodec.string +import zio.http.template.Html + +object Example extends ZIOAppDefault { + + val lv = + LiveView( + TestView, + MyModel( + List( + NestedModel("a", 10), + NestedModel("b", 15), + NestedModel("c", 20) + ) + ) + ) + + val socketApp: WebSocketApp[Any] = + Handler.webSocket { channel => + channel.receiveAll { + case Read(WebSocketFrame.Text("end")) => + channel.shutdown + + // Send a "bar" if the client sends a "foo" + case Read(WebSocketFrame.Text("foo")) => + channel.send(Read(WebSocketFrame.text("bar"))) + + // Send a "foo" if the client sends a "bar" + case Read(WebSocketFrame.Text("bar")) => + channel.send(Read(WebSocketFrame.text("foo"))) + + // Echo the same message 10 times if it's not "foo" or "bar" + case Read(WebSocketFrame.Text(text)) => + channel + .send(Read(WebSocketFrame.text(s"echo $text"))) + .repeatN(10) + .catchSomeCause { case cause => + ZIO.logErrorCause(s"failed sending", cause) + } + + // Send a "greeting" message to the client once the connection is established + case UserEventTriggered(UserEvent.HandshakeComplete) => + channel.send(Read(WebSocketFrame.text("Greetings!"))) + + // Log when the channel is getting closed + case Read(WebSocketFrame.Close(status, reason)) => + Console.printLine( + "Closing channel with status: " + status + " and reason: " + reason + ) + + // Print the exception if it's not a normal close + case ExceptionCaught(cause) => + Console.printLine(s"Channel error!: ${cause.getMessage}") + + case _ => + ZIO.unit + } + } + + val routes: Routes[Any, Response] = + Routes( + Method.GET / "" -> handler { (_: Request) => + Response.html(Html.raw(HtmlBuilder.build(lv))) + }, + Method.GET / "live" / "ws" -> handler(socketApp.toResponse) + ) + + override val run = Server.serve(routes).provide(Server.default) +} + +final case class MyModel(elems: List[NestedModel], cls: String = "text-xs") +final case class NestedModel(name: String, age: Int) + +object TestView extends View[MyModel]: + val root: HtmlElement[MyModel] = + div( + idAttr := "42", + cls := model(_.cls), + ul( + model.splitByIndex(_.elems)(elem => + li( + "Nom: ", + elem(_.name), + " Age: ", + elem(_.age.toString) + ) + ) + ) + )