You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
147 lines
4.1 KiB
147 lines
4.1 KiB
package nova.monadic_sfx.ui.components.router
|
|
|
|
import scala.concurrent.duration._
|
|
|
|
import cats.kernel.Eq
|
|
import cats.syntax.eq._
|
|
import com.softwaremill.quicklens._
|
|
import io.circe.Codec
|
|
import io.circe.Decoder
|
|
import io.circe.Encoder
|
|
import io.circe.generic.JsonCodec
|
|
import io.circe.generic.semiauto._
|
|
import io.odin.Logger
|
|
import monix.bio.Task
|
|
import monix.eval.Coeval
|
|
import monix.reactive.Observable
|
|
import nova.monadic_sfx.util.History
|
|
import nova.monadic_sfx.util.IOUtils
|
|
import nova.monadic_sfx.util.controls.JFXSpinner
|
|
import nova.monadic_sfx.util.reactive.store.Middlewares
|
|
import nova.monadic_sfx.util.reactive.store.Reducer
|
|
import nova.monadic_sfx.util.reactive.store.Store
|
|
import scalafx.scene.Parent
|
|
|
|
object FXRouter {
|
|
|
|
final case class State[P](page: P, history: History[P])
|
|
object State {
|
|
implicit def eqForAction[T] = Eq.fromUniversalEquals[State[T]]
|
|
}
|
|
|
|
sealed abstract class Action[+T]
|
|
final case class Replace[T](page: T) extends Action[T]
|
|
final case class HistoryEvent[T](page: T) extends Action[T]
|
|
final case object Forward extends Action[Nothing]
|
|
final case object Backward extends Action[Nothing]
|
|
|
|
object Action {
|
|
implicit def codec[T: Encoder: Decoder]: Codec[Action[T]] = deriveCodec
|
|
implicit def eqForAction[T] = Eq.fromUniversalEquals[Action[T]]
|
|
}
|
|
|
|
type FXStore[P] = Store[Action[P], State[P]]
|
|
|
|
}
|
|
|
|
class FXRouter[P]()(implicit E: Encoder[P], D: Decoder[P]) {
|
|
import FXRouter._
|
|
|
|
def store(initialPage: P, logger: Logger[Task]): Task[FXStore[P]] =
|
|
Task.deferAction(implicit s =>
|
|
for {
|
|
mw <- Middlewares.actionLoggerMiddleware[Action[P], State[P]](
|
|
logger,
|
|
"RouterStore"
|
|
)
|
|
store <- Store.createL[Action[P], State[P]](
|
|
Replace(initialPage),
|
|
State(initialPage, History(initialPage)),
|
|
Reducer.withOptionalEffects[Task, Action[P], State[P]](reducer _),
|
|
Seq(mw)
|
|
// Seq(classOf[HistoryEvent[P]])
|
|
)
|
|
} yield store
|
|
)
|
|
|
|
def reducer(
|
|
state: State[P],
|
|
action: Action[P]
|
|
): (State[P], Option[Task[Action[P]]]) =
|
|
action match {
|
|
// case Init => (state, None)
|
|
case Replace(p) =>
|
|
(state.copy(page = p, history = state.history :+ p), None)
|
|
case HistoryEvent(p) =>
|
|
(state.copy(page = p), None)
|
|
case Forward =>
|
|
val s1 = state.modify(_.history).using(_.forward)
|
|
val s2 = s1.modify(_.page).setTo(s1.history.current)
|
|
s2 -> Some(Task.pure(HistoryEvent(s2.history.current)))
|
|
case Backward =>
|
|
val s1 = state.modify(_.history).using(_.backward)
|
|
val s2 = s1.modify(_.page).setTo(s1.history.current)
|
|
s2 -> Some(Task.pure(HistoryEvent(s2.history.current)))
|
|
}
|
|
|
|
def render(
|
|
resolver: P => Parent,
|
|
transitionDelay: FiniteDuration = 500.millis
|
|
)(implicit store: FXStore[P]) =
|
|
store
|
|
.filter {
|
|
case (a, _) => a =!= FXRouter.Forward
|
|
}
|
|
.filter {
|
|
case (a, _) => a =!= FXRouter.Backward
|
|
}
|
|
.distinctUntilChanged
|
|
.flatMap {
|
|
case (_, FXRouter.State(p, _)) =>
|
|
Observable.from(Coeval(new JFXSpinner)) ++ Observable.from(
|
|
IOUtils.toTask(
|
|
Task
|
|
.racePair(
|
|
Task.sleep(transitionDelay),
|
|
Task.pure(resolver(p))
|
|
)
|
|
.flatMap {
|
|
case Left(_ -> fib) => fib.join
|
|
case Right(fib -> res) => fib.join >> Task.pure(res)
|
|
}
|
|
)
|
|
)
|
|
}
|
|
|
|
def link(
|
|
page: P,
|
|
store: FXStore[P]
|
|
) = {
|
|
store.onNext(Replace(page))
|
|
}
|
|
}
|
|
|
|
@JsonCodec
|
|
sealed trait Page
|
|
object Page {
|
|
final case object Home extends Page
|
|
final case object UserHome extends Page
|
|
final case object Todo extends Page
|
|
|
|
implicit val eqForPage = Eq.fromUniversalEquals[Page]
|
|
}
|
|
|
|
// case class State()
|
|
// object RouterStore {
|
|
|
|
// sealed trait Action
|
|
// case object Init extends Action
|
|
|
|
// def reducer(state: State, action: Action) =
|
|
// action match {
|
|
// case Init => state
|
|
|
|
// }
|
|
// def apply() =
|
|
// Store.createL[Action, State](Init, State(), Reducer(reducer _), Seq.empty)
|
|
// }
|