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]],
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):
def render(state: LiveState, trackUpdates: Boolean): Option[T] =
val entry = state(key)

View file

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

View file

@ -1,9 +1,9 @@
package scalive
import scalive.defs.attrs.HtmlAttrs
import scalive.defs.complex.ComplexHtmlKeys
import scalive.defs.tags.HtmlTags
object Scalive extends HtmlTags with HtmlAttrs with ComplexHtmlKeys
export Scalive.*
package object scalive extends HtmlTags with HtmlAttrs with ComplexHtmlKeys:
implicit def stringToMod(v: String): Mod = Mod.Text(v)
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.*
final case class Socket(view: LiveView, initState: LiveState):
private var state: LiveState = initState
final case class Socket[Cmd](lv: LiveView[Cmd]):
private var state: LiveState = lv.mount(LiveState.empty)
private var fingerprint: Fingerprint = Fingerprint.empty
val id: String = "scl-123"
def setState(newState: LiveState): Unit = state = newState
def updateState(f: LiveState => LiveState): Unit = state = f(state)
def receiveCommand(cmd: Cmd): Unit =
state = lv.handleCommand(cmd, state)
def renderHtml: String =
HtmlBuilder.build(Rendered.render(view.render, state), isRoot = true)
HtmlBuilder.build(Rendered.render(lv.render, state), isRoot = true)
def syncClient: Unit =
val r = Rendered.render(view.render, state)
val r = Rendered.render(lv.render, state)
println(DiffBuilder.build(r, fingerprint).toJsonPretty)
fingerprint = Fingerprint(r)
println(fingerprint)
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)
)
)
)
)