Improve LiveView trait

This commit is contained in:
Paul-Henri Froidmont 2025-08-17 22:52:22 +02:00
parent bb062b9679
commit 57f43a0780
Signed by: phfroidmont
GPG key ID: BE948AFD7E7873BE
7 changed files with 133 additions and 141 deletions

View file

@ -0,0 +1,54 @@
import scalive.*
final case class MyModel(cls: String = "text-xs", bool: Boolean = true)
final case class Elem(name: String, age: Int)
class TestView extends LiveView[TestView.Cmd]:
import TestView.Cmd.*
private val textCls = LiveState.Key[String]
private val someBool = LiveState.Key[Boolean]
private val elems = LiveState.Key[List[Elem]]
def mount(state: LiveState): LiveState =
state
.set(textCls, "text-xs")
.set(someBool, true)
.set(
elems,
List(
Elem("a", 10),
Elem("b", 15),
Elem("c", 20)
)
)
def handleCommand(cmd: TestView.Cmd, state: LiveState): LiveState = cmd match
case UpdateElems(es) => state.set(elems, es)
case UpdateBool(b) => state.set(someBool, b)
case UpdateTextCls(cls) => state.set(textCls, cls)
val render =
div(
idAttr := "42",
cls := textCls,
draggable := someBool,
disabled := someBool,
ul(
elems.splitByIndex(elem =>
li(
"Nom: ",
elem(_.name),
" Age: ",
elem(_.age.toString)
)
)
)
)
end TestView
object TestView:
enum Cmd:
case UpdateElems(es: List[Elem])
case UpdateBool(b: Boolean)
case UpdateTextCls(cls: String)

62
core/src/main.scala Normal file
View file

@ -0,0 +1,62 @@
import scalive.*
@main
def main =
val s = Socket(TestView())
println("Init")
println(s.renderHtml)
s.syncClient
s.syncClient
println("Edit first and last")
s.receiveCommand(
TestView.Cmd.UpdateTextCls("text-lg")
)
s.syncClient
println("Edit first and last")
s.receiveCommand(
TestView.Cmd.UpdateElems(
List(
Elem("x", 10),
Elem("b", 15),
Elem("c", 99)
)
)
)
s.syncClient
println("Add one")
s.receiveCommand(
TestView.Cmd.UpdateElems(
List(
Elem("x", 10),
Elem("b", 15),
Elem("c", 99),
Elem("d", 35)
)
)
)
s.syncClient
//
// println("Remove first")
// lv.update(
// MyModel(
// List(
// NestedModel("b", 15),
// NestedModel("c", 99),
// NestedModel("d", 35)
// )
// )
// )
// println(lv.diff.toJsonPretty)
// println(HtmlBuilder.build(lv))
//
// println("Remove all")
// lv.update(
// MyModel(List.empty, "text-lg", bool = false)
// )
// println(lv.diff.toJsonPretty)
// println(HtmlBuilder.build(lv))
end main

View file

@ -41,10 +41,6 @@ enum Mod:
dynList: Dyn[List[T]], dynList: Dyn[List[T]],
project: Dyn[T] => HtmlElement) project: Dyn[T] => HtmlElement)
given [T]: Conversion[HtmlElement, Mod] = Mod.Tag(_)
given [T]: Conversion[String, Mod] = Mod.Text(_)
given [T]: Conversion[Dyn[String], Mod] = Mod.DynText(_)
final case class Dyn[T](key: LiveState.Key, f: key.Type => T): final case class Dyn[T](key: LiveState.Key, f: key.Type => T):
def render(state: LiveState, trackUpdates: Boolean): Option[T] = def render(state: LiveState, trackUpdates: Boolean): Option[T] =
val entry = state(key) val entry = state(key)

View file

@ -1,4 +1,6 @@
package scalive package scalive
trait LiveView: trait LiveView[Cmd]:
def mount(state: LiveState): LiveState
def handleCommand(cmd: Cmd, state: LiveState): LiveState
def render: HtmlElement def render: HtmlElement

View file

@ -1,9 +1,9 @@
package scalive
import scalive.defs.attrs.HtmlAttrs import scalive.defs.attrs.HtmlAttrs
import scalive.defs.complex.ComplexHtmlKeys import scalive.defs.complex.ComplexHtmlKeys
import scalive.defs.tags.HtmlTags import scalive.defs.tags.HtmlTags
object Scalive extends HtmlTags with HtmlAttrs with ComplexHtmlKeys package object scalive extends HtmlTags with HtmlAttrs with ComplexHtmlKeys:
implicit def stringToMod(v: String): Mod = Mod.Text(v)
export Scalive.* implicit def htmlElementToMod(el: HtmlElement): Mod = Mod.Tag(el)
implicit def dynStringToMod(d: Dyn[String]): Mod = Mod.DynText(d)
implicit def keyToDyn(k: LiveState.Key): Dyn[k.Type] = k.id

View file

@ -2,18 +2,19 @@ package scalive
import zio.json.* import zio.json.*
final case class Socket(view: LiveView, initState: LiveState): final case class Socket[Cmd](lv: LiveView[Cmd]):
private var state: LiveState = initState private var state: LiveState = lv.mount(LiveState.empty)
private var fingerprint: Fingerprint = Fingerprint.empty private var fingerprint: Fingerprint = Fingerprint.empty
val id: String = "scl-123" val id: String = "scl-123"
def setState(newState: LiveState): Unit = state = newState def receiveCommand(cmd: Cmd): Unit =
def updateState(f: LiveState => LiveState): Unit = state = f(state) state = lv.handleCommand(cmd, state)
def renderHtml: String =
HtmlBuilder.build(Rendered.render(view.render, state), isRoot = true) def renderHtml: String =
HtmlBuilder.build(Rendered.render(lv.render, state), isRoot = true)
def syncClient: Unit = def syncClient: Unit =
val r = Rendered.render(view.render, state) val r = Rendered.render(lv.render, state)
println(DiffBuilder.build(r, fingerprint).toJsonPretty) println(DiffBuilder.build(r, fingerprint).toJsonPretty)
fingerprint = Fingerprint(r) fingerprint = Fingerprint(r)
println(fingerprint)
state = state.setAllUnchanged state = state.setAllUnchanged

View file

@ -1,123 +0,0 @@
package scalive
@main
def main =
val s = Socket(
TestView,
LiveState.empty.set(
TestView.model,
MyModel(
List(
NestedModel("a", 10),
NestedModel("b", 15),
NestedModel("c", 20)
)
)
)
)
println("Init")
println(s.renderHtml)
s.syncClient
s.syncClient
println("Edit first and last")
s.updateState(
_.set(
TestView.model,
MyModel(
List(
NestedModel("x", 10),
NestedModel("b", 15),
NestedModel("c", 99)
)
)
)
)
s.syncClient
// val lv =
// LiveView(
// TestView,
// LiveState.empty.set(
// TestView.model,
// MyModel(
// List(
// NestedModel("a", 10),
// NestedModel("b", 15),
// NestedModel("c", 20)
// )
// )
// )
// )
// println(lv.fullDiff.toJsonPretty)
// println(HtmlBuilder.build(lv))
//
// println("Edit first and last")
// lv.update(
// MyModel(
// List(
// NestedModel("x", 10),
// NestedModel("b", 15),
// NestedModel("c", 99)
// )
// )
// )
// println(lv.diff.toJsonPretty)
// println(HtmlBuilder.build(lv))
//
// println("Add one")
// lv.update(
// MyModel(
// List(
// NestedModel("x", 10),
// NestedModel("b", 15),
// NestedModel("c", 99),
// NestedModel("d", 35)
// )
// )
// )
// println(lv.diff.toJsonPretty)
// println(HtmlBuilder.build(lv))
//
// println("Remove first")
// lv.update(
// MyModel(
// List(
// NestedModel("b", 15),
// NestedModel("c", 99),
// NestedModel("d", 35)
// )
// )
// )
// println(lv.diff.toJsonPretty)
// println(HtmlBuilder.build(lv))
//
// println("Remove all")
// lv.update(
// MyModel(List.empty, "text-lg", bool = false)
// )
// println(lv.diff.toJsonPretty)
// println(HtmlBuilder.build(lv))
end main
final case class MyModel(elems: List[NestedModel], cls: String = "text-xs", bool: Boolean = true)
final case class NestedModel(name: String, age: Int)
object TestView extends LiveView:
val model = LiveState.Key[MyModel]
val render =
div(
idAttr := "42",
cls := model(_.cls),
draggable := model(_.bool),
disabled := model(_.bool),
ul(
model(_.elems).splitByIndex(elem =>
li(
"Nom: ",
elem(_.name),
" Age: ",
elem(_.age.toString)
)
)
)
)