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.

128 lines
3.3 KiB

  1. package nova.monadic_sfx.ui.components.router
  2. import scala.concurrent.duration._
  3. import io.circe.Codec
  4. import io.circe.Decoder
  5. import io.circe.Encoder
  6. import io.circe.generic.JsonCodec
  7. import io.circe.generic.semiauto._
  8. import io.odin.Logger
  9. import monix.bio.Task
  10. import monix.eval.Coeval
  11. import monix.reactive.Observable
  12. import nova.monadic_sfx.util.IOUtils
  13. import nova.monadic_sfx.util.MutHistory
  14. import nova.monadic_sfx.util.controls.JFXSpinner
  15. import nova.monadic_sfx.util.reactive.store.Middlewares
  16. import nova.monadic_sfx.util.reactive.store.Reducer
  17. import nova.monadic_sfx.util.reactive.store.Store
  18. import scalafx.scene.Parent
  19. object FXRouter {
  20. final case class State[P](page: P)
  21. sealed abstract class Action[+T]
  22. final case class Replace[T](page: T) extends Action[T]
  23. final case class HistoryEvent[T](page: T) extends Action[T]
  24. final case object Forward extends Action[Nothing]
  25. final case object Backward extends Action[Nothing]
  26. object Action {
  27. implicit def codec[T: Encoder: Decoder]: Codec[Action[T]] = deriveCodec
  28. }
  29. type FXStore[P] = Store[Action[P], State[P]]
  30. }
  31. class FXRouter[P](history: MutHistory[P])(implicit
  32. E: Encoder[P],
  33. D: Decoder[P]
  34. ) {
  35. import FXRouter._
  36. def store(initialPage: P, logger: Logger[Task]): Task[FXStore[P]] =
  37. Task.deferAction(implicit s =>
  38. for {
  39. mw <- Middlewares.actionLoggerMiddleware[Action[P], State[P]](
  40. logger,
  41. "RouterStore"
  42. )
  43. store <- Store.createL[Action[P], State[P]](
  44. Replace(initialPage),
  45. State(initialPage),
  46. Reducer.withOptionalEffects[Task, Action[P], State[P]](reducer _),
  47. Seq(mw)
  48. )
  49. } yield store
  50. )
  51. def reducer(
  52. state: State[P],
  53. action: Action[P]
  54. ): (State[P], Option[Task[Action[P]]]) =
  55. action match {
  56. // case Init => (state, None)
  57. case Replace(p) =>
  58. history.push(p)
  59. (state.copy(page = p), None)
  60. case HistoryEvent(p) =>
  61. (state.copy(page = p), None)
  62. case Forward => (state, None)
  63. case Backward => (state, None)
  64. }
  65. def render(
  66. resolver: P => Task[Parent],
  67. transitionDelay: FiniteDuration = 500.millis
  68. )(implicit store: FXStore[P]) =
  69. store
  70. .flatMap {
  71. case (_, FXRouter.State(p)) =>
  72. Observable.from(Coeval(new JFXSpinner)) ++ Observable.from(
  73. IOUtils.toTask(
  74. Task
  75. .racePair(
  76. Task.sleep(transitionDelay),
  77. resolver(p)
  78. )
  79. .flatMap {
  80. case Left(_ -> fib) => fib.join
  81. case Right(fib -> res) => fib.join >> Task.pure(res)
  82. }
  83. )
  84. )
  85. }
  86. def link(
  87. page: P,
  88. store: FXStore[P]
  89. ) = {
  90. store.onNext(Replace(page))
  91. }
  92. }
  93. @JsonCodec
  94. sealed trait Page
  95. object Page {
  96. final case object Home extends Page
  97. final case class UserHome(id: Int) extends Page
  98. final case object Todo extends Page
  99. }
  100. // case class State()
  101. // object RouterStore {
  102. // sealed trait Action
  103. // case object Init extends Action
  104. // def reducer(state: State, action: Action) =
  105. // action match {
  106. // case Init => state
  107. // }
  108. // def apply() =
  109. // Store.createL[Action, State](Init, State(), Reducer(reducer _), Seq.empty)
  110. // }