From 94b5dcb8f83981f8a16d882cd21eef452c977184 Mon Sep 17 00:00:00 2001 From: Naftoli Gugenheim <98384+nafg@users.noreply.github.com> Date: Mon, 20 Dec 2021 02:16:10 -0500 Subject: [PATCH] Hopefully more efficient rendering --- .../io/github/nafg/simplefacade/Facade.scala | 6 +-- .../io/github/nafg/simplefacade/Factory.scala | 15 +++---- .../github/nafg/simplefacade/MergeProps.scala | 40 +++---------------- .../github/nafg/simplefacade/PropTypes.scala | 27 +++++++++---- .../io/github/nafg/simplefacade/Tests.scala | 8 ++++ 5 files changed, 44 insertions(+), 52 deletions(-) diff --git a/simpleFacade/src/main/scala/io/github/nafg/simplefacade/Facade.scala b/simpleFacade/src/main/scala/io/github/nafg/simplefacade/Facade.scala index 4bef543..a6af827 100644 --- a/simpleFacade/src/main/scala/io/github/nafg/simplefacade/Facade.scala +++ b/simpleFacade/src/main/scala/io/github/nafg/simplefacade/Facade.scala @@ -10,13 +10,13 @@ object Facade { type JsComponentType = Js.ComponentSimple[ js.Object, - CtorType.Summoner.Aux[js.Object, Children.Varargs, CtorType.PropsAndChildren]#CT, + CtorType.Summoner.Aux[js.Object, Children.Varargs, CtorType.Props]#CT, Js.UnmountedWithRawType[js.Object, Null, Js.RawMounted[js.Object, Null]] - ] + ] def apply(component: Facade.JsComponentType): Facade = new Facade(component) - def apply(component: js.Any): Facade = apply(JsComponent[js.Object, Children.Varargs, Null](component)) + def apply(component: js.Any): Facade = apply(JsComponent[js.Object, Children.None, Null](component)) } class Facade(component: Facade.JsComponentType) { diff --git a/simpleFacade/src/main/scala/io/github/nafg/simplefacade/Factory.scala b/simpleFacade/src/main/scala/io/github/nafg/simplefacade/Factory.scala index 41d187f..a105c0d 100644 --- a/simpleFacade/src/main/scala/io/github/nafg/simplefacade/Factory.scala +++ b/simpleFacade/src/main/scala/io/github/nafg/simplefacade/Factory.scala @@ -2,10 +2,10 @@ package io.github.nafg.simplefacade import scala.language.implicitConversions import scala.scalajs.js -import scala.scalajs.js.JSConverters._ import japgolly.scalajs.react.component.Js import japgolly.scalajs.react.vdom.VdomElement +import io.github.nafg.simplefacade.MergeProps.AnyDict object Factory { @@ -14,10 +14,11 @@ object Factory { type Setting[A] = A => PropTypes.Setting } -case class Factory[A](propTypes: A, component: Facade.JsComponentType, values: Seq[PropTypes.Setting] = Vector.empty) { - def apply(pairs: Factory.Setting[A]*): Factory[A] = copy(values = values ++ pairs.map(_.apply(propTypes))) - def rawProps: js.Object = MergeProps(values.toJSArray.map(_.toRawProps)) - def render: Js.UnmountedWithRawType[js.Object, Null, Js.RawMounted[js.Object, Null]] = { - component.apply(rawProps)() - } +case class Factory[A](propTypes: A, + component: Facade.JsComponentType, + settings: Seq[PropTypes.Setting] = Vector.empty) { + def apply(pairs: Factory.Setting[A]*): Factory[A] = copy(settings = settings ++ pairs.map(_.apply(propTypes))) + def rawProps: AnyDict = PropTypes.Setting.toDict(settings: _*) + def render: Js.UnmountedWithRawType[js.Object, Null, Js.RawMounted[js.Object, Null]] = + component.apply(rawProps.asInstanceOf[js.Object]) } diff --git a/simpleFacade/src/main/scala/io/github/nafg/simplefacade/MergeProps.scala b/simpleFacade/src/main/scala/io/github/nafg/simplefacade/MergeProps.scala index 7913155..4e9767e 100644 --- a/simpleFacade/src/main/scala/io/github/nafg/simplefacade/MergeProps.scala +++ b/simpleFacade/src/main/scala/io/github/nafg/simplefacade/MergeProps.scala @@ -2,14 +2,13 @@ package io.github.nafg.simplefacade import scala.scalajs.js - // Based loosely on https://github.com/zhangkaiyulw/react-merge-props private[simplefacade] object MergeProps { private trait AnyJsFunction extends js.ThisFunction { def apply(_this: js.Any, args: js.Any*): js.Any } - private def AnyJsFunction(f: AnyJsFunction): AnyJsFunction = f + type AnyDict = js.Dictionary[js.Any] private def mergeChildrenArrays(value1: js.Any, value2: js.Any) = if (value1.asInstanceOf[js.Array[js.Any]].length == 0) value2 else if (value2.asInstanceOf[js.Array[js.Any]].length == 0) value1 @@ -27,12 +26,12 @@ private[simplefacade] object MergeProps { value1.asInstanceOf[js.Object], value2.asInstanceOf[js.Object] ) - private def mergeEventHandlers(value1: js.Any, value2: js.Any): js.ThisFunction = - AnyJsFunction { (_this, args) => + private def mergeEventHandlers(value1: js.Any, value2: js.Any): AnyJsFunction = { + (_this: js.Any, args: Seq[js.Any]) => value1.asInstanceOf[js.Function].call(_this, args: _*) value2.asInstanceOf[js.Function].call(_this, args: _*) - } - private def merge(key: String, value1: js.Any, value2: js.Any): js.Any = + } + def merge(key: String, value1: js.Any, value2: js.Any): js.Any = if (js.isUndefined(value1)) value2 else if (js.isUndefined(value2)) @@ -49,33 +48,4 @@ private[simplefacade] object MergeProps { mergeEventHandlers(value1, value2) else value2 - def apply(propsObjects: js.Array[js.Object]): js.Object = - if (propsObjects.length == 0) - js.Object() - else { - val firstObject = propsObjects(0) - if (propsObjects.length == 1) - firstObject - else { - val firstObjectAsDict = firstObject.asInstanceOf[js.Dictionary[js.Any]] - var isFirst = true - for (props <- propsObjects) - if (isFirst) - isFirst = false - else - js.Object.keys(props).foreach { key => - val value = props.asInstanceOf[js.Dictionary[js.Any]](key) - if (!firstObjectAsDict.contains(key)) - firstObjectAsDict(key) = value - else { - val baseValue = firstObjectAsDict(key) - if (js.isUndefined(baseValue)) - firstObjectAsDict(key) = value - else - firstObjectAsDict(key) = merge(key, baseValue, value) - } - } - firstObject - } - } } diff --git a/simpleFacade/src/main/scala/io/github/nafg/simplefacade/PropTypes.scala b/simpleFacade/src/main/scala/io/github/nafg/simplefacade/PropTypes.scala index a418072..239944c 100644 --- a/simpleFacade/src/main/scala/io/github/nafg/simplefacade/PropTypes.scala +++ b/simpleFacade/src/main/scala/io/github/nafg/simplefacade/PropTypes.scala @@ -2,28 +2,34 @@ package io.github.nafg.simplefacade import scala.language.{dynamics, implicitConversions} import scala.scalajs.js -import scala.scalajs.js.JSConverters._ import japgolly.scalajs.react.Key import japgolly.scalajs.react.vdom.TagMod +import io.github.nafg.simplefacade.MergeProps.AnyDict import slinky.readwrite.Writer object PropTypes { sealed trait Setting { - def toRawProps: js.Object + def applyToDict(dict: AnyDict): Unit } object Setting { class Single(val key: String, val value: js.Any) extends Setting { - override def toRawProps = js.Dynamic.literal(key -> value) override def toString = s"""$key: $value""" + override def applyToDict(dict: AnyDict): Unit = { + val existingValue: js.Any = if (dict.contains(key)) js.Any.wrapDictionary(dict)(key) else js.undefined + dict(key) = MergeProps.merge(key, existingValue, value) + } } + implicit class FromBooleanProp(prop: Prop[Boolean]) extends Single(prop.name, true) - implicit class Multiple(val settings: Seq[Setting]) extends Setting { - override def toRawProps = MergeProps(settings.toJSArray.map(_.toRawProps)) + + implicit class Multiple(val settings: Iterable[Setting]) extends Setting { + override def applyToDict(dict: AnyDict): Unit = settings.foreach(_.applyToDict(dict)) } implicit def fromConvertibleToIterablePairs[A](pairs: A)(implicit view: A => Iterable[(String, js.Any)]): Setting = - new Multiple(view(pairs).map { case (k, v) => new Single(k, v) }.toSeq) + new Multiple(view(pairs).map { case (k, v) => new Single(k, v) }) + implicit def fromTagMod(tagMod: TagMod): Setting = { val raw = tagMod.toJs raw.addKeyToProps() @@ -31,10 +37,17 @@ object PropTypes { raw.addClassNameToProps() new Multiple( raw.nonEmptyChildren.toList.map(new Single("children", _)) :+ - fromConvertibleToIterablePairs(raw.props.asInstanceOf[js.Dictionary[js.Any]]) + new Multiple(raw.props.asInstanceOf[AnyDict].map { case (k, v) => new Single(k, v) }) ) } + implicit def toFactorySetting[A](value: A)(implicit view: A => Setting): Any => Setting = _ => view(value) + + def toDict(settings: Setting*): AnyDict = { + val base = js.Dictionary.empty[js.Any] + settings.foreach(_.applyToDict(base)) + base + } } class Prop[A](val name: String)(implicit writer: Writer[A]) { diff --git a/simpleFacade/src/test/scala/io/github/nafg/simplefacade/Tests.scala b/simpleFacade/src/test/scala/io/github/nafg/simplefacade/Tests.scala index 081fb0a..cdbe138 100644 --- a/simpleFacade/src/test/scala/io/github/nafg/simplefacade/Tests.scala +++ b/simpleFacade/src/test/scala/io/github/nafg/simplefacade/Tests.scala @@ -25,4 +25,12 @@ class Tests extends munit.FunSuite { Map[String, AnyVal]("a" -> 1, "b" -> true) ) } + test("MergeProps") { + var x = 1 + val f = + MergeProps.merge("onEvent", js.Any.fromFunction1(x += (_: Int)), js.Any.fromFunction1(x *= (_: Int))) + .asInstanceOf[js.Function1[Int, Unit]] + f(2) + assertEquals(x, 6) + } }