Fix splitById

This commit is contained in:
Paul-Henri Froidmont 2025-11-07 01:20:14 +01:00
parent 21309629bc
commit 1da129f855
Signed by: phfroidmont
GPG key ID: BE948AFD7E7873BE
6 changed files with 247 additions and 40 deletions

View file

@ -10,10 +10,10 @@ enum Diff:
dynamic: Seq[Diff.Dynamic] = Seq.empty)
case Comprehension(
static: Seq[String] = Seq.empty,
entries: Seq[Diff.Dynamic] = Seq.empty,
entries: Seq[Diff.Dynamic | Diff.IndexChange] = Seq.empty,
count: Int = 0)
case Value(value: String)
case Dynamic(key: String, diff: Diff)
case Dynamic(index: Int, diff: Diff)
case Deleted
extension (diff: Diff)
@ -27,6 +27,8 @@ extension (diff: Diff)
object Diff:
given JsonEncoder[Diff] = JsonEncoder[Json].contramap(toJson(_))
final case class IndexChange(index: Int, previousIndex: Int)
private def toJson(diff: Diff): Json =
diff match
case Diff.Tag(static, dynamic) =>
@ -35,7 +37,7 @@ object Diff:
.when(static.nonEmpty)("s" -> Json.Arr(static.map(Json.Str(_))*))
.to(Chunk)
.appendedAll(
dynamic.map(d => d.key -> toJson(d.diff))
dynamic.map(d => d.index.toString -> toJson(d.diff))
)
)
case Diff.Comprehension(static, entries, count) =>
@ -47,7 +49,12 @@ object Diff:
"k" ->
Json
.Obj(
entries.map(d => d.key -> toJson(d.diff))*
entries.map {
case Diff.Dynamic(index, diff) =>
index.toString -> toJson(diff)
case Diff.IndexChange(index, previousIndex) =>
index.toString -> Json.Num(previousIndex)
}*
).add("kc", Json.Num(count))
)
)

View file

@ -17,7 +17,7 @@ object DiffBuilder:
static = static,
dynamic =
buildDynamic(dynamicMods, trackUpdates).zipWithIndex.collect { case (Some(diff), index) =>
Diff.Dynamic(index.toString, diff)
Diff.Dynamic(index, diff)
}
)
@ -47,15 +47,24 @@ object DiffBuilder:
case Some((entries, keysCount, includeStatics)) =>
val static =
if !trackUpdates || includeStatics then
entries.collectFirst { case (_, el) => el.static }.getOrElse(List.empty)
entries.collectFirst { case (_, _, el) => el.static }.getOrElse(List.empty)
else List.empty
List(
Some(
Diff.Comprehension(
static = static,
entries = entries.map((key, el) =>
Diff.Dynamic(key.toString, build(Seq.empty, el.dynamicMods, trackUpdates))
),
entries = entries.map {
case entry @ (previousIndex = None) =>
Diff.Dynamic(
entry.index,
build(Seq.empty, entry.value.dynamicMods, trackUpdates)
)
case (index, Some(previousIndex), _) =>
Diff.IndexChange(
index,
previousIndex
)
},
count = keysCount
)
)

View file

@ -99,11 +99,14 @@ private class SplitVar[I, O, Key](
private val memoized: mutable.Map[Key, (Var[I], O)] =
mutable.Map.empty
private var previousKeysToIndex: Map[Key, Int] = Map.empty
private var nonEmptySyncCount = 0
private[scalive] def sync(): Unit =
parent.sync()
if parent.changed then
previousKeysToIndex = memoized.keys.zipWithIndex.toMap
// We keep track of the keys to remove deleted ones afterwards
val nextKeys = mutable.HashSet.empty[Key]
parent.currentValue.foreach(input =>
@ -126,15 +129,25 @@ private class SplitVar[I, O, Key](
)
if memoized.nonEmpty then nonEmptySyncCount += 1
private[scalive] def render(trackUpdates: Boolean)
: Option[(changeList: List[(Int, O)], keysCount: Int, includeStatics: Boolean)] =
private[scalive] def render(trackUpdates: Boolean): Option[
(
changeList: List[(index: Int, previousIndex: Option[Int], value: O)],
keysCount: Int,
includeStatics: Boolean
)
] =
if parent.changed || !trackUpdates then
Some(
(
changeList = memoized.values.zipWithIndex.collect {
case ((entryVar, output), index) if !trackUpdates || entryVar.changed =>
(index, output)
}.toList,
changeList = memoized.zipWithIndex
.map { case ((key, (entryVar, output)), index) =>
(index, previousKeysToIndex.get(key).filterNot(_ == index), entryVar, output)
}
.collect {
case (index, previousIndex, entryVar, output)
if !trackUpdates || entryVar.changed || previousIndex.isDefined =>
(index, previousIndex, output)
}.toList,
keysCount = memoized.size,
includeStatics = nonEmptySyncCount == 1
)

View file

@ -36,10 +36,8 @@ object HtmlBuilder:
case Content.DynElementColl(dyn) => ???
case Content.DynSplit(splitVar) =>
val (entries, _, _) = splitVar.render(false).getOrElse((List.empty, 0, true))
val staticOpt = entries.collectFirst { case (_, el) => el.static }
entries.foreach((_, entryEl) =>
build(staticOpt.getOrElse(Nil), entryEl.dynamicMods, strw)
)
val staticOpt = entries.collectFirst { case (value = el) => el.static }
entries.foreach(entry => build(staticOpt.getOrElse(Nil), entry.value.dynamicMods, strw))
strw.write(static.last)
end HtmlBuilder