From 131f90fcd839b38335b83849f4bc2632e07ff3a7 Mon Sep 17 00:00:00 2001 From: davidontop Date: Tue, 24 Sep 2024 06:28:42 +0200 Subject: [PATCH] feat: reactivity 1. --- .../scala/top/davidon/sfs/dom/Element.scala | 4 +- .../davidon/sfs/dom/ReactiveRenderer.scala | 2 +- .../davidon/sfs/dom/ScalaFullStackDOM.scala | 30 +++++---- .../davidon/sfs/dom/TriggerableValue.scala | 5 -- .../scala/top/davidon/sfs/dom/Value.scala | 67 ++++++++++--------- .../top/davidon/sfs/dom/keys/AriaAttr.scala | 7 +- .../top/davidon/sfs/dom/keys/EventProp.scala | 16 +++-- .../top/davidon/sfs/dom/keys/HtmlAttr.scala | 7 +- .../top/davidon/sfs/dom/keys/HtmlProp.scala | 7 +- .../top/davidon/sfs/dom/keys/SvgAttr.scala | 7 +- .../top/davidon/sfs/dom/mods/Modifier.scala | 4 +- .../davidon/sfs/dom/plain/PlainModifier.scala | 5 ++ .../davidon/sfs/dom/plain/PlainValue.scala | 25 +++++++ .../davidon/sfs/dom/reactive/Observable.scala | 9 +++ .../sfs/dom/reactive/ReactiveModifier.scala | 5 ++ .../sfs/dom/reactive/ReactiveValue.scala | 18 +++++ .../sfs/dom/reactive/TriggerableValue.scala | 5 ++ .../scala/top/davidon/sfs/dom/tags/Tag.scala | 4 +- .../sfs/renderers/StringRenderer.scala | 4 +- 19 files changed, 153 insertions(+), 78 deletions(-) delete mode 100644 dom/src/main/scala/top/davidon/sfs/dom/TriggerableValue.scala create mode 100644 dom/src/main/scala/top/davidon/sfs/dom/plain/PlainModifier.scala create mode 100644 dom/src/main/scala/top/davidon/sfs/dom/plain/PlainValue.scala create mode 100644 dom/src/main/scala/top/davidon/sfs/dom/reactive/Observable.scala create mode 100644 dom/src/main/scala/top/davidon/sfs/dom/reactive/ReactiveModifier.scala create mode 100644 dom/src/main/scala/top/davidon/sfs/dom/reactive/ReactiveValue.scala create mode 100644 dom/src/main/scala/top/davidon/sfs/dom/reactive/TriggerableValue.scala diff --git a/dom/src/main/scala/top/davidon/sfs/dom/Element.scala b/dom/src/main/scala/top/davidon/sfs/dom/Element.scala index a801eb0..3574008 100644 --- a/dom/src/main/scala/top/davidon/sfs/dom/Element.scala +++ b/dom/src/main/scala/top/davidon/sfs/dom/Element.scala @@ -6,6 +6,6 @@ import top.davidon.sfs.dom.tags.Tag /** tag + modifiers + value */ class Element[+Ref <: org.scalajs.dom.Element]( val tag: Tag[Ref], - val mods: Iterable[Modifier[?, ?]], - val children: Seq[Element[?] | Value[?, String]] + val mods: Iterable[Modifier[?]], + val children: Seq[Element[?] | Value[String]] ) {} diff --git a/dom/src/main/scala/top/davidon/sfs/dom/ReactiveRenderer.scala b/dom/src/main/scala/top/davidon/sfs/dom/ReactiveRenderer.scala index 272a3e4..e922455 100644 --- a/dom/src/main/scala/top/davidon/sfs/dom/ReactiveRenderer.scala +++ b/dom/src/main/scala/top/davidon/sfs/dom/ReactiveRenderer.scala @@ -5,5 +5,5 @@ import top.davidon.sfs.dom.mods.Modifier trait ReactiveRenderer { def valueFunc[F](element: dom.Element, value: F): Unit - def modifierFunc[F, T](modifier: Modifier[F, T], value: F): Unit + def modifierFunc[F, T](modifier: Modifier[T], value: F): Unit } diff --git a/dom/src/main/scala/top/davidon/sfs/dom/ScalaFullStackDOM.scala b/dom/src/main/scala/top/davidon/sfs/dom/ScalaFullStackDOM.scala index 11fa3ca..fee109f 100644 --- a/dom/src/main/scala/top/davidon/sfs/dom/ScalaFullStackDOM.scala +++ b/dom/src/main/scala/top/davidon/sfs/dom/ScalaFullStackDOM.scala @@ -6,6 +6,7 @@ import top.davidon.sfs.dom.defs.complex.{ComplexHtmlKeys, ComplexSvgKeys} import top.davidon.sfs.dom.defs.eventProps.GlobalEventProps import top.davidon.sfs.dom.defs.props.HtmlProps import top.davidon.sfs.dom.defs.tags.{HtmlTags, SvgTags} +import top.davidon.sfs.dom.plain.PlainValue trait ScalaFullStackDOM extends HtmlTags @@ -20,23 +21,24 @@ trait ScalaFullStackDOM object svg extends SvgTags with SvgAttrs with ComplexSvgKeys - given strToVal: Conversion[String, Value[String, String]] with { - def apply(from: String): Value[String, String] = - Value(from, StringAsIsCodec) + given strToVal: Conversion[String, Value[String]] with { + def apply(from: String): PlainValue[String, String] = + PlainValue(from, StringAsIsCodec) } - given intToVal: Conversion[Int, Value[Int, String]] with { - def apply(from: Int): Value[Int, String] = Value(from, IntAsStringCodec) + given intToVal: Conversion[Int, Value[String]] with { + def apply(from: Int): PlainValue[Int, String] = + PlainValue(from, IntAsStringCodec) } - given doubleToVal: Conversion[Double, Value[Double, String]] with { - def apply(from: Double): Value[Double, String] = - Value(from, DoubleAsStringCodec) + given doubleToVal: Conversion[Double, Value[String]] with { + def apply(from: Double): PlainValue[Double, String] = + PlainValue(from, DoubleAsStringCodec) } - given longToVal: Conversion[Long, Value[Long, String]] with { - def apply(from: Long): Value[Long, String] = Value(from, LongAsStringCodec) + given longToVal: Conversion[Long, Value[String]] with { + def apply(from: Long): PlainValue[Long, String] = + PlainValue(from, LongAsStringCodec) } - given iterToVal: Conversion[Iterable[String], Value[Iterable[String], String]] - with { - def apply(from: Iterable[String]): Value[Iterable[String], String] = - Value(from, IterableAsSpaceSeparatedStringCodec) + given iterToVal: Conversion[Iterable[String], Value[String]] with { + def apply(from: Iterable[String]): PlainValue[Iterable[String], String] = + PlainValue(from, IterableAsSpaceSeparatedStringCodec) } } diff --git a/dom/src/main/scala/top/davidon/sfs/dom/TriggerableValue.scala b/dom/src/main/scala/top/davidon/sfs/dom/TriggerableValue.scala deleted file mode 100644 index 1af938d..0000000 --- a/dom/src/main/scala/top/davidon/sfs/dom/TriggerableValue.scala +++ /dev/null @@ -1,5 +0,0 @@ -package top.davidon.sfs.dom - -trait TriggerableValue[T] { - def trigger[T](value: T): Unit -} diff --git a/dom/src/main/scala/top/davidon/sfs/dom/Value.scala b/dom/src/main/scala/top/davidon/sfs/dom/Value.scala index 9c7f192..4a9b3cd 100644 --- a/dom/src/main/scala/top/davidon/sfs/dom/Value.scala +++ b/dom/src/main/scala/top/davidon/sfs/dom/Value.scala @@ -1,36 +1,41 @@ package top.davidon.sfs.dom -import top.davidon.sfs.dom.codecs.{Codec, StringAsIsCodec} +import top.davidon.sfs.dom.codecs.Codec +import top.davidon.sfs.dom.reactive.Observable -class Value[F, T]( - val value: F, - val codec: Codec[F, T] -) { - def apply(): T = { - codec.encode(value) - } - - override def toString: String = { - value match - case v: Int => codecs.IntAsStringCodec.encode(v) - case v: Long => codecs.LongAsStringCodec.encode(v) - case v: Double => codecs.DoubleAsStringCodec.encode(v) - case v: Boolean => codecs.BooleanAsTrueFalseStringCodec.encode(v) - case v: Iterable[String] => - codecs.IterableAsSpaceSeparatedStringCodec.encode(v) - case _ => - throw Exception("Couldn't find codec to convert a value to a string") - } +trait Value[T] { + def apply(): T } -object Value { - def join( - iterator: Iterable[Value[?, String]] - ): Value[String, String] = { - Value( - iterator.map(v => v.toString).mkString(""), - StringAsIsCodec - ) - } - -} +//class Value[F, T]( +// val value: F, +// val codec: Codec[F, T] +//) { +// def apply(): T = { +// codec.encode(value) +// } +// +// override def toString: String = { +// value match +// case v: Int => codecs.IntAsStringCodec.encode(v) +// case v: Long => codecs.LongAsStringCodec.encode(v) +// case v: Double => codecs.DoubleAsStringCodec.encode(v) +// case v: Boolean => codecs.BooleanAsTrueFalseStringCodec.encode(v) +// case v: Iterable[String] => +// codecs.IterableAsSpaceSeparatedStringCodec.encode(v) +// case _ => +// throw Exception("Couldn't find codec to convert a value to a string") +// } +//} +// +//object Value { +// def join( +// iterator: Iterable[Value[?, String]] +// ): Value[String, String] = { +// Value( +// iterator.map(v => v.toString).mkString(""), +// StringAsIsCodec +// ) +// } +// +//} diff --git a/dom/src/main/scala/top/davidon/sfs/dom/keys/AriaAttr.scala b/dom/src/main/scala/top/davidon/sfs/dom/keys/AriaAttr.scala index 2cf968a..09dba45 100644 --- a/dom/src/main/scala/top/davidon/sfs/dom/keys/AriaAttr.scala +++ b/dom/src/main/scala/top/davidon/sfs/dom/keys/AriaAttr.scala @@ -2,6 +2,7 @@ package top.davidon.sfs.dom.keys import top.davidon.sfs.dom.codecs.{Codec, StringCodec} import top.davidon.sfs.dom.Value import top.davidon.sfs.dom.mods.Modifier +import top.davidon.sfs.dom.plain.PlainValue class AriaAttr[V]( suffix: String, @@ -9,11 +10,11 @@ class AriaAttr[V]( ) extends Key { override val name: String = "aria-" + suffix - @inline def apply(value: V): Modifier[V, String] = { + @inline def apply(value: V): Modifier[String] = { this := value } - def :=(value: V): Modifier[V, String] = { - Modifier(this, Value(value, codec)) + def :=(value: V): Modifier[String] = { + Modifier(this, PlainValue(value, codec)) } } diff --git a/dom/src/main/scala/top/davidon/sfs/dom/keys/EventProp.scala b/dom/src/main/scala/top/davidon/sfs/dom/keys/EventProp.scala index fd28f5d..c656426 100644 --- a/dom/src/main/scala/top/davidon/sfs/dom/keys/EventProp.scala +++ b/dom/src/main/scala/top/davidon/sfs/dom/keys/EventProp.scala @@ -1,9 +1,11 @@ package top.davidon.sfs.dom.keys import org.scalajs.dom +import top.davidon.sfs.dom.Value import top.davidon.sfs.dom.codecs.{EmptyCodec, StringAsIsCodec} import top.davidon.sfs.dom.mods.{EventMod, Modifier} -import top.davidon.sfs.dom.{TriggerableValue, Value} +import top.davidon.sfs.dom.plain.PlainValue +import top.davidon.sfs.dom.reactive.TriggerableValue class EventProp[E <: dom.Event](override val name: String) extends Key { @@ -11,23 +13,23 @@ class EventProp[E <: dom.Event](override val name: String) extends Key { * @param value * @return */ - def :=(value: E => Unit): Modifier[EventMod[E], Unit] = { - Modifier(this, Value(EventMod(this, value), EmptyCodec())) + def :=(value: E => Unit): Modifier[Unit] = { + Modifier(this, PlainValue(EventMod(this, value), EmptyCodec())) } /** Don't use with StringRenderer and ssr off/false * @param value * @return */ - def :=(value: TriggerableValue[E]): Modifier[EventMod[E], Unit] = { - Modifier(this, Value(EventMod(this, value.trigger), EmptyCodec())) + def :=(value: TriggerableValue[E]): Modifier[Unit] = { + Modifier(this, PlainValue(EventMod(this, value.trigger), EmptyCodec())) } /** Only use with StringRenderer and ssr off/false * @param value * @return */ - def :=(value: String): Modifier[String, String] = { - Modifier(this, Value(value, StringAsIsCodec)) + def :=(value: String): Modifier[String] = { + Modifier(this, PlainValue(value, StringAsIsCodec)) } } diff --git a/dom/src/main/scala/top/davidon/sfs/dom/keys/HtmlAttr.scala b/dom/src/main/scala/top/davidon/sfs/dom/keys/HtmlAttr.scala index db3b016..d881aa0 100644 --- a/dom/src/main/scala/top/davidon/sfs/dom/keys/HtmlAttr.scala +++ b/dom/src/main/scala/top/davidon/sfs/dom/keys/HtmlAttr.scala @@ -3,16 +3,17 @@ package top.davidon.sfs.dom.keys import top.davidon.sfs.dom.codecs.{Codec, StringCodec} import top.davidon.sfs.dom.Value import top.davidon.sfs.dom.mods.Modifier +import top.davidon.sfs.dom.plain.PlainValue class HtmlAttr[V]( override val name: String, val codec: StringCodec[V] ) extends Key { - @inline def apply(value: V): Modifier[V, String] = { + @inline def apply(value: V): Modifier[String] = { this := value } - def :=(value: V): Modifier[V, String] = { - Modifier(this, Value(value, codec)) + def :=(value: V): Modifier[String] = { + Modifier(this, PlainValue(value, codec)) } } diff --git a/dom/src/main/scala/top/davidon/sfs/dom/keys/HtmlProp.scala b/dom/src/main/scala/top/davidon/sfs/dom/keys/HtmlProp.scala index a88b7c8..deb3384 100644 --- a/dom/src/main/scala/top/davidon/sfs/dom/keys/HtmlProp.scala +++ b/dom/src/main/scala/top/davidon/sfs/dom/keys/HtmlProp.scala @@ -2,16 +2,17 @@ package top.davidon.sfs.dom.keys import top.davidon.sfs.dom.codecs.Codec import top.davidon.sfs.dom.Value import top.davidon.sfs.dom.mods.Modifier +import top.davidon.sfs.dom.plain.PlainValue class HtmlProp[V, DomV]( override val name: String, val codec: Codec[V, DomV] ) extends Key { - @inline def apply(value: V): Modifier[V, DomV] = { + @inline def apply(value: V): Modifier[DomV] = { this := value } - def :=(value: V): Modifier[V, DomV] = { - Modifier(this, Value(value, codec)) + def :=(value: V): Modifier[DomV] = { + Modifier(this, PlainValue(value, codec)) } } diff --git a/dom/src/main/scala/top/davidon/sfs/dom/keys/SvgAttr.scala b/dom/src/main/scala/top/davidon/sfs/dom/keys/SvgAttr.scala index 6d26407..69e8073 100644 --- a/dom/src/main/scala/top/davidon/sfs/dom/keys/SvgAttr.scala +++ b/dom/src/main/scala/top/davidon/sfs/dom/keys/SvgAttr.scala @@ -2,6 +2,7 @@ package top.davidon.sfs.dom.keys import top.davidon.sfs.dom.codecs.{Codec, StringCodec} import top.davidon.sfs.dom.Value import top.davidon.sfs.dom.mods.Modifier +import top.davidon.sfs.dom.plain.PlainValue class SvgAttr[V]( val localName: String, @@ -13,12 +14,12 @@ class SvgAttr[V]( val namespaceUri: Option[String] = namespacePrefix.map(SvgAttr.namespaceUri) - @inline def apply(value: V): Modifier[V, String] = { + @inline def apply(value: V): Modifier[String] = { this := value } - def :=(value: V): Modifier[V, String] = { - Modifier(this, Value(value, codec)) + def :=(value: V): Modifier[String] = { + Modifier(this, PlainValue(value, codec)) } } diff --git a/dom/src/main/scala/top/davidon/sfs/dom/mods/Modifier.scala b/dom/src/main/scala/top/davidon/sfs/dom/mods/Modifier.scala index c2960cb..a074766 100644 --- a/dom/src/main/scala/top/davidon/sfs/dom/mods/Modifier.scala +++ b/dom/src/main/scala/top/davidon/sfs/dom/mods/Modifier.scala @@ -4,7 +4,7 @@ import top.davidon.sfs.dom.Value import top.davidon.sfs.dom.codecs.Codec import top.davidon.sfs.dom.keys.Key -class Modifier[F, T]( +class Modifier[T]( val key: Key, - val value: Value[F, T] + val value: Value[T] ) {} diff --git a/dom/src/main/scala/top/davidon/sfs/dom/plain/PlainModifier.scala b/dom/src/main/scala/top/davidon/sfs/dom/plain/PlainModifier.scala new file mode 100644 index 0000000..b9b26f6 --- /dev/null +++ b/dom/src/main/scala/top/davidon/sfs/dom/plain/PlainModifier.scala @@ -0,0 +1,5 @@ +package top.davidon.sfs.dom.plain + +class PlainModifier { + +} diff --git a/dom/src/main/scala/top/davidon/sfs/dom/plain/PlainValue.scala b/dom/src/main/scala/top/davidon/sfs/dom/plain/PlainValue.scala new file mode 100644 index 0000000..35ca9d7 --- /dev/null +++ b/dom/src/main/scala/top/davidon/sfs/dom/plain/PlainValue.scala @@ -0,0 +1,25 @@ +package top.davidon.sfs.dom.plain + +import top.davidon.sfs.dom.{Value, codecs} +import top.davidon.sfs.dom.codecs.Codec + +class PlainValue[F, T]( + val value: F, + val codec: Codec[F, T] +) extends Value[T] { + def apply(): T = { + codec.encode(value) + } + + override def toString: String = { + value match + case v: Int => codecs.IntAsStringCodec.encode(v) + case v: Long => codecs.LongAsStringCodec.encode(v) + case v: Double => codecs.DoubleAsStringCodec.encode(v) + case v: Boolean => codecs.BooleanAsTrueFalseStringCodec.encode(v) + case v: Iterable[String] => + codecs.IterableAsSpaceSeparatedStringCodec.encode(v) + case _ => + throw Exception("Couldn't find codec to convert a value to a string") + } +} diff --git a/dom/src/main/scala/top/davidon/sfs/dom/reactive/Observable.scala b/dom/src/main/scala/top/davidon/sfs/dom/reactive/Observable.scala new file mode 100644 index 0000000..b376c13 --- /dev/null +++ b/dom/src/main/scala/top/davidon/sfs/dom/reactive/Observable.scala @@ -0,0 +1,9 @@ +package top.davidon.sfs.dom.reactive + +trait Observable[T] { + def subscribe(observer: T => Unit): Unit + def unsubscribe(observer: T => Unit): Unit + def notify(value: T): Unit + def update(value: T): Unit + def now(): T +} diff --git a/dom/src/main/scala/top/davidon/sfs/dom/reactive/ReactiveModifier.scala b/dom/src/main/scala/top/davidon/sfs/dom/reactive/ReactiveModifier.scala new file mode 100644 index 0000000..70a94f5 --- /dev/null +++ b/dom/src/main/scala/top/davidon/sfs/dom/reactive/ReactiveModifier.scala @@ -0,0 +1,5 @@ +package top.davidon.sfs.dom.reactive + +class ReactiveModifier { + +} diff --git a/dom/src/main/scala/top/davidon/sfs/dom/reactive/ReactiveValue.scala b/dom/src/main/scala/top/davidon/sfs/dom/reactive/ReactiveValue.scala new file mode 100644 index 0000000..3a328f3 --- /dev/null +++ b/dom/src/main/scala/top/davidon/sfs/dom/reactive/ReactiveValue.scala @@ -0,0 +1,18 @@ +package top.davidon.sfs.dom.reactive + +import top.davidon.sfs.dom.Value +import top.davidon.sfs.dom.codecs.Codec +import top.davidon.sfs.dom.plain.PlainValue + +class ReactiveValue[F, T]( + val value: Observable[F], + val codec: Codec[F, T] +) extends Value[T] { + def apply(): T = { + codec.encode(value.now()) + } + + def toValueNow: PlainValue[F, T] = { + PlainValue(value.now(), codec) + } +} diff --git a/dom/src/main/scala/top/davidon/sfs/dom/reactive/TriggerableValue.scala b/dom/src/main/scala/top/davidon/sfs/dom/reactive/TriggerableValue.scala new file mode 100644 index 0000000..fae9eaa --- /dev/null +++ b/dom/src/main/scala/top/davidon/sfs/dom/reactive/TriggerableValue.scala @@ -0,0 +1,5 @@ +package top.davidon.sfs.dom.reactive + +trait TriggerableValue[T] { + def trigger(value: T): Unit +} diff --git a/dom/src/main/scala/top/davidon/sfs/dom/tags/Tag.scala b/dom/src/main/scala/top/davidon/sfs/dom/tags/Tag.scala index 3be7a0a..93d8012 100644 --- a/dom/src/main/scala/top/davidon/sfs/dom/tags/Tag.scala +++ b/dom/src/main/scala/top/davidon/sfs/dom/tags/Tag.scala @@ -9,9 +9,9 @@ trait Tag[+Ref <: dom.Element] { val void: Boolean def apply( - modifiers: Modifier[?, ?]* + modifiers: Modifier[?]* )( - values: Element[?] | Value[?, String]* + values: Element[?] | Value[String]* ): Element[Ref] = { Element[Ref](this, modifiers, values.toSeq) } diff --git a/sfs/src/main/scala/top/davidon/sfs/renderers/StringRenderer.scala b/sfs/src/main/scala/top/davidon/sfs/renderers/StringRenderer.scala index 658ae9e..0c27425 100644 --- a/sfs/src/main/scala/top/davidon/sfs/renderers/StringRenderer.scala +++ b/sfs/src/main/scala/top/davidon/sfs/renderers/StringRenderer.scala @@ -15,10 +15,10 @@ class StringRenderer(val ssr: Boolean) extends Renderer[String] { e.mods.map(m => s" ${m.key.name}=\"${m.value.toString}\"").mkString("") val bodyStr = e.children .map { + case c: Value[String] => + c() case e: Element[?] => renderElement(e) - case c: Value[?, String] => - c() } .mkString(" ") s"<${e.tag.name}$modsStr>$bodyStr${