Skip to content

Instantly share code, notes, and snippets.

@arturaz
Created January 18, 2024 22:01
Show Gist options
  • Save arturaz/d29f622fcb7ae3c4303d8f3bd667e49b to your computer and use it in GitHub Desktop.
Save arturaz/d29f622fcb7ae3c4303d8f3bd667e49b to your computer and use it in GitHub Desktop.
package app.webclient_prelude.utils
import com.raquo.airstream.core.Signal
import com.raquo.airstream.split.SplittableSignal
import com.raquo.airstream.state.Var
/** Like [[Var.zoom]] but is not required to have an [[com.raquo.airstream.ownership.Owner]]. */
trait UpdatableSignal[A] { self =>
/** The [[Signal]] which is most likely mapped from a [[Var]]. */
def signal: Signal[A]
/** Performs the modification on the underlying data source so that [[signal]] value changes.
*/
def update: UpdatableSignal.UpdateFn[A]
/** Returns a new [[UpdatableSignal]] which is mapped.
*
* Example:
* {{{
* lineItem.bimap(_.name)((item, value) => item.copy(name = value))
* }}}
*/
def bimap[PartOfA](
get: A => PartOfA
)(set: UpdatableSignal.Setter[A, PartOfA]): UpdatableSignal[PartOfA] =
UpdatableSignal[PartOfA](
signal.map(get),
updatePartOfA =>
self.update { a =>
val partOfA = get(a)
set(a, updatePartOfA(partOfA))
},
)
/** Returns a new [[UpdatableSignal]] which is constructed from a [[Signal]] (obtained via one of the
* [[SplittableSignal.split]] methods) and an update function.
*
* Example:
* {{{
* val lineItems: UpdatableSignal[NonEmptyVector[LineItem]] = ???
*
* div(
* children <-- lineItems.signal.splitByIndex((idx, _, signal) =>
* LineItem(
* lineItems.bimapFromSplit(signal)(_.updatedWith(idx, _)),
* )
* )
* )
* }}}
*
* @param signal
* the [[Signal]] that represents a part of [[A]], usually obtained via one of the `split` methods.
* @param update
* the function that updates the underlying data source. The first argument is the current value of
* [[self.signal]], the second argument is a function that updates the given part of [[A]].
*/
def bimapFromSplit[PartOfA](signal: Signal[PartOfA])(
update: UpdatableSignal.Updater[A, PartOfA]
): UpdatableSignal[PartOfA] = UpdatableSignal[PartOfA](
signal,
updateB => self.update(a => update(a, updateB)),
)
}
object UpdatableSignal {
/** Receives a value and returns the modified value. */
// noinspection ScalaWeakerAccess
type UpdateFn[A] = (A => A) => Unit
/** Receives a value as the first argument and an update function as the second argument.
*
* The update function receives a part of the value and returns the updated part.
*
* @return
* the modified value.
*/
// noinspection ScalaWeakerAccess
type Updater[A, PartOfA] = (A, PartOfA => PartOfA) => A
/** Sets [[PartOfA]] in [[A]] and returns the modified [[A]]. */
// noinspection ScalaWeakerAccess
type Setter[A, PartOfA] = (A, PartOfA) => A
/** Creates an instance from a [[Signal]] and [[UpdateFn]]. */
def apply[A](
signal: Signal[A],
update: UpdateFn[A],
): UpdatableSignal[A] = {
val s = signal
val u = update
new UpdatableSignal[A] {
override def signal: Signal[A] = s
override def update: UpdateFn[A] = u
}
}
/** Creates an instance from a [[Var]] and mapping functions. */
implicit def fromVar[A](
rxVar: Var[A]
): UpdatableSignal[A] = UpdatableSignal[A](
rxVar.signal,
update => rxVar.update(update),
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment