Skip to content

Commit

Permalink
Hopefully more efficient rendering
Browse files Browse the repository at this point in the history
  • Loading branch information
nafg committed Dec 20, 2021
1 parent de86c1a commit 94b5dcb
Show file tree
Hide file tree
Showing 5 changed files with 44 additions and 52 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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])
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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))
Expand All @@ -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
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,52 @@ 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()
raw.addStyleToProps()
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]) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

0 comments on commit 94b5dcb

Please sign in to comment.