Skip to content

Commit

Permalink
V0.8 small issues
Browse files Browse the repository at this point in the history
  • Loading branch information
nob13 committed Mar 1, 2024
1 parent 41b6afe commit c27bba1
Show file tree
Hide file tree
Showing 8 changed files with 243 additions and 224 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ class EventManager(delegate: EventManagerDelegate)(using ServiceRepository) {
}
underlying(in)
}
case EventTransformer.WithState(runtimeState) => { in =>
case EventTransformer.AddState(runtimeState) => { in =>
try {
val state = fetchStateUnsafe(runtimeState)
underlying((in, state))
Expand All @@ -191,7 +191,7 @@ class EventManager(delegate: EventManagerDelegate)(using ServiceRepository) {
Logger.warn(s"Error fetching runtime state: ${e.getMessage}")
}
}
case EventTransformer.WithEffect(effectFn) => { in =>
case EventTransformer.AddEffect(effectFn) => { in =>
effectFn(in).fn(implicitly[ExecutionContext]).andThen { case result =>
underlying((in, result))
}
Expand Down Expand Up @@ -221,6 +221,10 @@ class EventManager(delegate: EventManagerDelegate)(using ServiceRepository) {
underlying(in)
}
}
case EventTransformer.Chained(a, b) => {
val preTransform1 = buildTransformer(node, b, underlying)
buildTransformer(node, a, preTransform1)
}
}
}

Expand Down Expand Up @@ -280,10 +284,6 @@ class EventManager(delegate: EventManagerDelegate)(using ServiceRepository) {
scalajs.js.timers.setTimeout(0) {
sink(())
}
case e: EventSource.EffectEvent[_, _] =>
bindEffect(ownNode, e, sink)
case a: EventSource.AndSource[_] =>
bindAnd(ownNode, a, sink)
case c: EventSource.ChannelSource[_] =>
bindChannel(ownNode, c, sink)
case o: EventSource.OrSource[_] =>
Expand Down Expand Up @@ -334,30 +334,6 @@ class EventManager(delegate: EventManagerDelegate)(using ServiceRepository) {
s.getter(delegate.locate(s.componentId).asInstanceOf[R])
}

private def bindEffect[E, F[_], R](
own: TreeNode,
event: EventSource.EffectEvent[E, R],
sink: ((E, Try[R])) => Unit
): Unit = {
bindEventSource(
own,
event.trigger,
v =>
event.effectOperation(v).fn(implicitly[ExecutionContext]).andThen { case result =>
sink((v, result))
}
)
}

private def bindAnd[T](ownNode: TreeNode, event: EventSource.AndSource[T], sink1: T => Unit): Unit = {
val sink0 = transformSink(ownNode, event.binding.sink)
val combined: T => Unit = { value =>
sink0(value)
sink1(value)
}
bindEventSource(ownNode, event.binding.source, combined)
}

private def bindChannel[T](ownNode: TreeNode, event: EventSource.ChannelSource[T], sink: T => Unit): Unit = {
event.channel.get.foreach { c =>
_channelBindings.add(c.id, ChannelBinding(sink, ownNode.id))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,7 @@ object ExtendedFormPage extends SimpleComponentBase {
override def assemble(using c: SimpleContext): Html = {

add(
okButton.onClicked.withState(formComponent.validatedState).intoModel(state) { validatedState =>
validatedState.toString
}
okButton.onClicked.withState(formComponent.validatedState).map(_.toString).intoModel(state)
)
div(
h2("Generated Form Example"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import kreuzberg.*
import kreuzberg.examples.showcase.components.{Button, TextInput}
import kreuzberg.examples.showcase.*
import kreuzberg.examples.showcase.todo.TodoPageWithApi.provide
import kreuzberg.extras.LazyLoader
import kreuzberg.extras.{LazyLoader, SimpleRouter}
import kreuzberg.scalatags.*
import kreuzberg.scalatags.all.*

Expand Down Expand Up @@ -43,7 +43,7 @@ object TodoAdderForm extends SimpleComponentBase {
}

object LazyTodoViewer extends LazyLoader[TodoList] {
override def load()(using c: ServiceRepository): Effect[TodoList] = {
override def load()(using c: AssemblerContext): Effect[TodoList] = {
val api = provide[Api]
Effect.future { api.todoApi.listItems() }.map(response => TodoList.apply(response.items))
}
Expand Down
18 changes: 16 additions & 2 deletions extras/shared/src/main/scala/kreuzberg/extras/LazyLoader.scala
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package kreuzberg.extras

import kreuzberg.{
AssemblerContext,
Effect,
EventSink,
EventSource,
EventTransformer,
Html,
Model,
ServiceRepository,
Expand All @@ -21,6 +23,18 @@ abstract class LazyLoader[T] extends SimpleComponentBase {
/** Event sink for refreshing the loader. */
def refresh: EventSink[Any] = EventSink.ModelChange(model, (_, _) => LazyState.Init)

/** Event sink for refreshing (silently) */
def silentRefresh(using a: AssemblerContext): EventSink[Any] = {
EventTransformer
.AddEffect { _ =>
load()
}
.map {
_._2.fold(err => LazyState.Failed(err), ok => LazyState.Ok(ok))
}
.intoModel(model)
}

override def assemble(using c: SimpleContext): Html = {
val data = subscribe(model)
data match {
Expand All @@ -32,7 +46,7 @@ abstract class LazyLoader[T] extends SimpleComponentBase {
_.fold(err => LazyState.Failed(err), ok => LazyState.Ok(ok))
}
.intoModel(model),
EventSource.Assembled.setModel(model, LazyState.WaitResponse)
EventSource.Assembled.setModelTo(model, LazyState.WaitResponse)
)
waiting()
case LazyState.WaitResponse =>
Expand All @@ -45,7 +59,7 @@ abstract class LazyLoader[T] extends SimpleComponentBase {
}

/** Load from external service. */
def load()(using c: ServiceRepository): Effect[T]
def load()(using c: AssemblerContext): Effect[T]

/** Html which is rendered during loading. */
def waiting()(using c: SimpleContext): Html = {
Expand Down
19 changes: 15 additions & 4 deletions extras/shared/src/main/scala/kreuzberg/extras/SimpleRouter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ case class SimpleRouter(
val route = decideRoute(routeValue)
subscribe(SimpleRouter.currentTarget)

val target = route match {
val target = route match {
case e: EagerRoute => e.eagerTarget(routeValue)
case _ => read(SimpleRouter.currentTarget)
}
Expand All @@ -49,12 +49,12 @@ case class SimpleRouter(
BrowserRouting.setDocumentTitle(titlePrefix + title)
}
.and
.setModel(SimpleRouter.loading, true)
.setModelTo(SimpleRouter.loading, true)
.and
.effect { case (path, route) =>
route.target(path)
}
.setModel(SimpleRouter.loading, false)
.setModelTo(SimpleRouter.loading, false)
.and
.map {
case ((path, _), Success(target)) => (path, target)
Expand All @@ -70,13 +70,20 @@ case class SimpleRouter(
model.copy(currentRoute = Some(path))
}
.and
.intoModel(SimpleRouter.currentTarget)(_._2)
.map(_._2)
.intoModel(SimpleRouter.currentTarget)
}

add(
SimpleRouter.gotoChannel.transform(handlePath(_, true))
)

add(
SimpleRouter.reloadChannel
.map { _ => routeValue }
.transform(handlePath(_, false))
)

add(
EventSource.Js
.window("load")
Expand Down Expand Up @@ -121,6 +128,10 @@ object SimpleRouter {
/** Event Sink for going to a specific route. */
def goto: EventSink[String] = EventSink.ChannelSink(gotoChannel)

/** Force a reload. */
val reloadChannel: Channel[Any] = Channel.create()
def reload: EventSink[Any] = EventSink.ChannelSink(reloadChannel)

/** Event Sink for going to a specific fixed route. */
def gotoTarget(target: String): EventSink[Any] = goto.contraMap(_ => target)

Expand Down
62 changes: 46 additions & 16 deletions lib/shared/src/main/scala/kreuzberg/EventBinding.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,33 @@ import scala.concurrent.{ExecutionContext, Future}
import scala.util.Try
import kreuzberg.dom.{ScalaJsElement, ScalaJsEvent}

import scala.annotation.unchecked.uncheckedVariance
import scala.concurrent.duration.FiniteDuration
import scala.ref.WeakReference
import scala.util.Failure
import scala.util.Success

trait EventTransformable[+E] extends EventTransformationDsl[E] with EventSinkApplicationDsl[E]

/** A Source of an [[EventBinding]]. */
sealed trait EventSource[+E] extends EventSourceDsl[E]
sealed trait EventSource[+E] extends EventTransformable[E] {
override type WithTransformer[F] = EventSource[F]
override type WithSink[G] = EventBinding.SourceSink[G]

override def withTransformer[Q](transformer: EventTransformer[E, Q]): EventSource[Q] = {
EventSource.PostTransformer(this, transformer)
}

/** Transform via function. */
inline def transform[R](f: EventSource[E] => R): R = f(this)

/** Combine with some other event source. */
def or[T >: E](source: EventSource[T]): EventSource[T] = EventSource.OrSource(this, source)

override def to[T >: E](sink: EventSink[T]): EventBinding.SourceSink[T] = {
EventBinding.SourceSink(this, sink)
}
}

object EventSource {

Expand All @@ -34,17 +54,6 @@ object EventSource {
inline def apply[E](channel: Channel[E]): ChannelSource[E] = ChannelSource[E](WeakReference(channel))
}

/** Some side effect operation (e.g. API Call) */
case class EffectEvent[E, R](
trigger: EventSource[E],
effectOperation: E => Effect[R]
) extends EventSource[(E, Try[R])]

/** Pseudo Event source, to chain multiple reactions on one source. */
case class AndSource[E](
binding: EventBinding.SourceSink[E]
) extends EventSource[E]

case class OrSource[E](
left: EventSource[E],
right: EventSource[E]
Expand All @@ -63,22 +72,41 @@ object EventSource {
) extends EventSource[O]
}

sealed trait EventTransformer[I, O]
sealed trait EventTransformer[-I, +O] extends EventTransformable[O] {

/** Transform via function. */
inline def transform[R](f: EventTransformer[I, O] => R): R = f(this)

override final type WithTransformer[X] = EventTransformer[I @uncheckedVariance, X]
override final type WithSink[X] = EventSink[I @uncheckedVariance]

/** Transforms using a Transformer. */

override def withTransformer[Q](transformer: EventTransformer[O, Q]): EventTransformer[I, Q] = {
EventTransformer.Chained[I, O, Q](this, transformer)
}

override def to[T >: O](sink: EventSink[T]): EventSink[I] = {
EventSink.PreTransformer(sink, this)
}
}

object EventTransformer {
// Simple Transformations
case class Map[I, O](fn: I => O) extends EventTransformer[I, O]
case class Collect[I, O](fn: PartialFunction[I, O]) extends EventTransformer[I, O]
case class Tapped[I](fn: I => Unit) extends EventTransformer[I, I]
// Adding Runtime State
case class WithState[I, S](runtimeState: RuntimeState[S]) extends EventTransformer[I, (I, S)]
case class AddState[I, S](runtimeState: RuntimeState[S]) extends EventTransformer[I, (I, S)]
// Adding an Effect
case class WithEffect[I, E](effectFn: I => Effect[E]) extends EventTransformer[I, (I, Try[E])]
case class AddEffect[I, E](effectFn: I => Effect[E]) extends EventTransformer[I, (I, Try[E])]
// Removing the Failure part of trys
case class TryUnpack1[E](failure: EventSink[Throwable]) extends EventTransformer[Try[E], E]
case class TryUnpack2[I, E](failure: EventSink[(I, Throwable)]) extends EventTransformer[(I, Try[E]), (I, E)]
// Call other sinks, used for fan out.
case class And[I](other: EventSink[I]) extends EventTransformer[I, I]

case class Chained[I, X, O](a: EventTransformer[I, X], b: EventTransformer[X, O]) extends EventTransformer[I, O]
}

/** Sink of an [[EventBinding]] */
Expand Down Expand Up @@ -139,7 +167,9 @@ object EventBinding {
) extends EventBinding {

/** Helper for adding more sinks on one source. */
def and: EventSource.AndSource[E] = EventSource.AndSource(this)
inline def and: EventSource[E] = source.withTransformer(
EventTransformer.And(sink)
)
}

def apply[E](
Expand Down

0 comments on commit c27bba1

Please sign in to comment.