From 012ba5ca26a175334b16030b0b5a7c33d38c34af Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Mon, 23 Jan 2023 00:29:28 +0000 Subject: [PATCH] Add `Resource` variants of attr+prop modifiers --- .../src/main/scala/calico/html/HtmlAttr.scala | 38 +++++++++++ .../src/main/scala/calico/html/Modifier.scala | 10 +++ calico/src/main/scala/calico/html/Prop.scala | 63 ++++++++++++++++--- 3 files changed, 101 insertions(+), 10 deletions(-) diff --git a/calico/src/main/scala/calico/html/HtmlAttr.scala b/calico/src/main/scala/calico/html/HtmlAttr.scala index 396fc6b1..722f0684 100644 --- a/calico/src/main/scala/calico/html/HtmlAttr.scala +++ b/calico/src/main/scala/calico/html/HtmlAttr.scala @@ -31,9 +31,15 @@ sealed class HtmlAttr[F[_], V] private[calico] (key: String, codec: Codec[V, Str @inline def <--(vs: Signal[F, V]): SignalModifier[F, V] = SignalModifier(key, codec, vs) + @inline def <--(vs: Resource[F, Signal[F, V]]): SignalResourceModifier[F, V] = + SignalResourceModifier(key, codec, vs) + @inline def <--(vs: Signal[F, Option[V]]): OptionSignalModifier[F, V] = OptionSignalModifier(key, codec, vs) + @inline def <--(vs: Resource[F, Signal[F, Option[V]]]): OptionSignalResourceModifier[F, V] = + OptionSignalResourceModifier(key, codec, vs) + object HtmlAttr: final class ConstantModifier[V] private[calico] ( private[calico] val key: String, @@ -47,12 +53,24 @@ object HtmlAttr: private[calico] val values: Signal[F, V] ) + final class SignalResourceModifier[F[_], V] private[calico] ( + private[calico] val key: String, + private[calico] val codec: Codec[V, String], + private[calico] val values: Resource[F, Signal[F, V]] + ) + final class OptionSignalModifier[F[_], V] private[calico] ( private[calico] val key: String, private[calico] val codec: Codec[V, String], private[calico] val values: Signal[F, Option[V]] ) + final class OptionSignalResourceModifier[F[_], V] private[calico] ( + private[calico] val key: String, + private[calico] val codec: Codec[V, String], + private[calico] val values: Resource[F, Signal[F, Option[V]]] + ) + private trait HtmlAttrModifiers[F[_]](using F: Async[F]): import HtmlAttr.* @@ -72,6 +90,15 @@ private trait HtmlAttrModifiers[F[_]](using F: Async[F]): F.delay(e.setAttribute(m.key, m.codec.encode(v))) } + inline given forSignalResourceHtmlAttr[E <: fs2.dom.Element[F], V] + : Modifier[F, E, SignalResourceModifier[F, V]] = + _forSignalHtmlAttr.asInstanceOf[Modifier[F, E, SignalResourceModifier[F, V]]] + + private val _forSignalResourceHtmlAttr = + Modifier.forSignalResource[F, dom.Element, SignalResourceModifier[F, Any], Any](_.values) { + (m, e) => v => F.delay(e.setAttribute(m.key, m.codec.encode(v))) + } + inline given forOptionSignalHtmlAttr[E <: fs2.dom.Element[F], V] : Modifier[F, E, OptionSignalModifier[F, V]] = _forOptionSignalHtmlAttr.asInstanceOf[Modifier[F, E, OptionSignalModifier[F, V]]] @@ -82,6 +109,17 @@ private trait HtmlAttrModifiers[F[_]](using F: Async[F]): F.delay(v.fold(e.removeAttribute(m.key))(v => e.setAttribute(m.key, m.codec.encode(v)))) } + inline given forOptionSignalResourceHtmlAttr[E <: fs2.dom.Element[F], V] + : Modifier[F, E, OptionSignalResourceModifier[F, V]] = + _forOptionSignalHtmlAttr.asInstanceOf[Modifier[F, E, OptionSignalResourceModifier[F, V]]] + + private val _forOptionSignalResourceHtmlAttr = + Modifier + .forSignalResource[F, dom.Element, OptionSignalResourceModifier[F, Any], Option[Any]]( + _.values) { (m, e) => v => + F.delay(v.fold(e.removeAttribute(m.key))(v => e.setAttribute(m.key, m.codec.encode(v)))) + } + final class Aria[F[_]] private extends AriaAttrs[F] private object Aria: diff --git a/calico/src/main/scala/calico/html/Modifier.scala b/calico/src/main/scala/calico/html/Modifier.scala index 0838e58d..e4693a85 100644 --- a/calico/src/main/scala/calico/html/Modifier.scala +++ b/calico/src/main/scala/calico/html/Modifier.scala @@ -43,6 +43,16 @@ private object Modifier: tail.foreach(modify(_)).compile.drain.cedeBackground.void } + def forSignalResource[F[_]: Async, E, M, V](signal: M => Resource[F, Signal[F, V]])( + mkModify: (M, E) => V => F[Unit]): Modifier[F, E, M] = (m, e) => + signal(m).flatMap { sig => + sig.getAndUpdates.flatMap { (head, tail) => + val modify = mkModify(m, e) + Resource.eval(modify(head)) *> + tail.foreach(modify(_)).compile.drain.cedeBackground.void + } + } + private trait Modifiers[F[_]](using F: Async[F]): inline given forUnit[E]: Modifier[F, E, Unit] = _forUnit.asInstanceOf[Modifier[F, E, Unit]] diff --git a/calico/src/main/scala/calico/html/Prop.scala b/calico/src/main/scala/calico/html/Prop.scala index e54b91e8..bbf95c78 100644 --- a/calico/src/main/scala/calico/html/Prop.scala +++ b/calico/src/main/scala/calico/html/Prop.scala @@ -37,9 +37,16 @@ sealed class Prop[F[_], V, J] private[calico] (name: String, codec: Codec[V, J]) @inline def <--(vs: Signal[F, V]): SignalModifier[F, V, J] = SignalModifier(name, codec, vs) + @inline def <--(vs: Resource[F, Signal[F, V]]): SignalResourceModifier[F, V, J] = + SignalResourceModifier(name, codec, vs) + @inline def <--(vs: Signal[F, Option[V]]): OptionSignalModifier[F, V, J] = OptionSignalModifier(name, codec, vs) + @inline def <--( + vs: Resource[F, Signal[F, Option[V]]]): OptionSignalResourceModifier[F, V, J] = + OptionSignalResourceModifier(name, codec, vs) + object Prop: final class ConstantModifier[V, J] private[calico] ( private[calico] val name: String, @@ -53,30 +60,62 @@ object Prop: private[calico] val values: Signal[F, V] ) + final class SignalResourceModifier[F[_], V, J] private[calico] ( + private[calico] val name: String, + private[calico] val codec: Codec[V, J], + private[calico] val values: Resource[F, Signal[F, V]] + ) + final class OptionSignalModifier[F[_], V, J] private[calico] ( private[calico] val name: String, private[calico] val codec: Codec[V, J], private[calico] val values: Signal[F, Option[V]] ) + final class OptionSignalResourceModifier[F[_], V, J] private[calico] ( + private[calico] val name: String, + private[calico] val codec: Codec[V, J], + private[calico] val values: Resource[F, Signal[F, Option[V]]] + ) + private trait PropModifiers[F[_]](using F: Async[F]): import Prop.* - private inline def setProp[N, V, J](node: N, value: V, name: String, codec: Codec[V, J]) = - F.delay(node.asInstanceOf[js.Dictionary[J]](name) = codec.encode(value)) + private inline def setProp[N, V, J](node: N, name: String, codec: Codec[V, J]) = + (value: V) => + F.delay { + node.asInstanceOf[js.Dictionary[J]](name) = codec.encode(value) + () + } + + private inline def setPropOption[N, V, J](node: N, name: String, codec: Codec[V, J]) = + (value: Option[V]) => + F.delay { + val dict = node.asInstanceOf[js.Dictionary[Any]] + value.fold(dict -= name)(v => dict(name) = codec.encode(v)) + () + } inline given forConstantProp[N, V, J]: Modifier[F, N, ConstantModifier[V, J]] = _forConstantProp.asInstanceOf[Modifier[F, N, ConstantModifier[V, J]]] private val _forConstantProp: Modifier[F, Any, ConstantModifier[Any, Any]] = - (m, n) => Resource.eval(setProp(n, m.value, m.name, m.codec)) + (m, n) => Resource.eval(setProp(n, m.name, m.codec).apply(m.value)) inline given forSignalProp[N, V, J]: Modifier[F, N, SignalModifier[F, V, J]] = _forSignalProp.asInstanceOf[Modifier[F, N, SignalModifier[F, V, J]]] private val _forSignalProp = - Modifier.forSignal[F, Any, SignalModifier[F, Any, Any], Any](_.values) { (m, n) => v => - setProp(n, v, m.name, m.codec) + Modifier.forSignal[F, Any, SignalModifier[F, Any, Any], Any](_.values) { (m, n) => + setProp(n, m.name, m.codec) + } + + inline given forSignalResourceProp[N, V, J]: Modifier[F, N, SignalResourceModifier[F, V, J]] = + _forSignalResourceProp.asInstanceOf[Modifier[F, N, SignalResourceModifier[F, V, J]]] + + private val _forSignalResourceProp = + Modifier.forSignalResource[F, Any, SignalResourceModifier[F, Any, Any], Any](_.values) { + (m, n) => setProp(n, m.name, m.codec) } inline given forOptionSignalProp[N, V, J]: Modifier[F, N, OptionSignalModifier[F, V, J]] = @@ -84,13 +123,17 @@ private trait PropModifiers[F[_]](using F: Async[F]): private val _forOptionSignalProp = Modifier.forSignal[F, Any, OptionSignalModifier[F, Any, Any], Option[Any]](_.values) { - (m, n) => v => - F.delay { - val dict = n.asInstanceOf[js.Dictionary[Any]] - v.fold(dict -= m.name)(v => dict(m.name) = m.codec.encode(v)) - } + (m, n) => setPropOption(n, m.name, m.codec) } + inline given forOptionSignalResourceProp[N, V, J] + : Modifier[F, N, OptionSignalResourceModifier[F, V, J]] = + _forOptionSignalProp.asInstanceOf[Modifier[F, N, OptionSignalResourceModifier[F, V, J]]] + + private val _forOptionSignalResourceProp = + Modifier.forSignalResource[F, Any, OptionSignalResourceModifier[F, Any, Any], Option[Any]]( + _.values) { (m, n) => setPropOption(n, m.name, m.codec) } + final class EventProp[F[_], E] private[calico] (key: String): import EventProp.* inline def -->(sink: Pipe[F, E, Nothing]): PipeModifier[F, E] = PipeModifier(key, sink)