9 Commits
e2f5dc15c3
...
c59d48f6ec
Author | SHA1 | Message | Date |
---|---|---|---|
Rohan Sircar | c59d48f6ec |
Added split combinator to actionObservable
|
3 years ago |
Rohan Sircar | 935ca358e6 |
Many changes
Updated store code - wrapped fold function in Task Added JSON logging functionality to store middleware Initial attempt at creating filter combinator for fx observables Made ListStore code use Effects Made a router using the store pattern Misc updates to fx monix implicits |
3 years ago |
Rohan Sircar | 857fd03bf1 |
Cleanup and added some methods to actionObservable
|
3 years ago |
Rohan Sircar | f95f50574e |
Added proper todo buttons reactively connected
|
3 years ago |
Rohan Sircar | f95ecc2cb3 |
Wrapped error handle in UIO
|
3 years ago |
Rohan Sircar | 1f06c536c8 |
Updated monix version to 3.3.0 (was 3.2.2)
|
3 years ago |
Rohan Sircar | 8f1fe0cc84 |
Cleanup and refactoring
|
3 years ago |
Rohan Sircar | b988ad267e |
Added store pattern using monix
also added relevant implicits to use it |
3 years ago |
Rohan Sircar | 536f1b0af3 |
Added scalafx wrappers for jfoenix and ikonli
|
3 years ago |
48 changed files with 2631 additions and 377 deletions
-
2.gitignore
-
61build.sbt
-
2project/build.properties
-
1project/plugin.sbt
-
53src/main/scala/nova/monadic_sfx/Main.scala
-
189src/main/scala/nova/monadic_sfx/MainApp.scala
-
38src/main/scala/nova/monadic_sfx/MainModule.scala
-
11src/main/scala/nova/monadic_sfx/Types.scala
-
70src/main/scala/nova/monadic_sfx/actors/ActorModule.scala
-
20src/main/scala/nova/monadic_sfx/actors/TestActor.scala
-
26src/main/scala/nova/monadic_sfx/executors/GUIExecutor.scala
-
25src/main/scala/nova/monadic_sfx/executors/Schedulers.scala
-
4src/main/scala/nova/monadic_sfx/http/HttpModule.scala
-
38src/main/scala/nova/monadic_sfx/http/requests/DummyRequest.scala
-
98src/main/scala/nova/monadic_sfx/implicits/ActionObservable.scala
-
44src/main/scala/nova/monadic_sfx/implicits/FontIcon.scala
-
42src/main/scala/nova/monadic_sfx/implicits/JFXButton.scala
-
36src/main/scala/nova/monadic_sfx/implicits/JFXListCell.scala
-
48src/main/scala/nova/monadic_sfx/implicits/JFXListView.scala
-
29src/main/scala/nova/monadic_sfx/implicits/JFXSpinner.scala
-
40src/main/scala/nova/monadic_sfx/implicits/JFXTextArea.scala
-
35src/main/scala/nova/monadic_sfx/implicits/JFXTextField.scala
-
62src/main/scala/nova/monadic_sfx/implicits/JFXTreeTableView.scala
-
327src/main/scala/nova/monadic_sfx/implicits/JavaFxMonixObservables.scala
-
9src/main/scala/nova/monadic_sfx/implicits/MenuItem.scala
-
50src/main/scala/nova/monadic_sfx/implicits/package.scala
-
7src/main/scala/nova/monadic_sfx/models/DummyModels.scala
-
65src/main/scala/nova/monadic_sfx/ui/DefaultUI.scala
-
146src/main/scala/nova/monadic_sfx/ui/MyFxApp.scala
-
129src/main/scala/nova/monadic_sfx/ui/MyFxAppOld.scala
-
18src/main/scala/nova/monadic_sfx/ui/UiModule.scala
-
107src/main/scala/nova/monadic_sfx/ui/components/router/FXRouter.scala
-
378src/main/scala/nova/monadic_sfx/ui/components/todo/TodoListComponentOld.scala
-
89src/main/scala/nova/monadic_sfx/ui/components/todo/TodoListStore.scala
-
134src/main/scala/nova/monadic_sfx/ui/components/todo/TodoListView.scala
-
95src/main/scala/nova/monadic_sfx/ui/controller/TodoController.scala
-
28src/main/scala/nova/monadic_sfx/ui/screens/HomeScreen.scala
-
34src/main/scala/nova/monadic_sfx/ui/screens/LoginScreen.scala
-
2src/main/scala/nova/monadic_sfx/ui/screens/Screen.scala
-
4src/main/scala/nova/monadic_sfx/util/Action.scala
-
21src/main/scala/nova/monadic_sfx/util/IOUtils.scala
-
63src/main/scala/nova/monadic_sfx/util/Misc.scala
-
75src/main/scala/nova/monadic_sfx/util/reactive/Middlewares.scala
-
23src/main/scala/nova/monadic_sfx/util/reactive/MonixProSubject.scala
-
39src/main/scala/nova/monadic_sfx/util/reactive/Reducer.scala
-
97src/main/scala/nova/monadic_sfx/util/reactive/Store.scala
-
18src/main/scala/nova/monadic_sfx/util/reactive/package.scala
-
76src/main/scala/org/slf4j/impl/StaticLoggerBuilder.scala
@ -1,2 +1,2 @@ |
|||||
sbt.version=1.3.10 |
|
||||
|
sbt.version=1.4.3 |
||||
|
|
@ -1,37 +1,32 @@ |
|||||
package nova.monadic_sfx |
package nova.monadic_sfx |
||||
|
|
||||
import monix.eval.Task |
|
||||
// import sttp.client.asynchttpclient.monix.AsyncHttpClientMonixBackend |
|
||||
// import sttp.client._ |
|
||||
// import sttp.client.circe._ |
|
||||
// import io.circe.generic.auto._ |
|
||||
import nova.monadic_sfx.executors._ |
|
||||
import cats.effect.Resource |
|
||||
import sttp.client.asynchttpclient.monix.AsyncHttpClientMonixBackend |
|
||||
import io.odin.syntax._ |
|
||||
import io.odin.monix._ |
|
||||
import monix.eval.TaskApp |
|
||||
|
import _root_.monix.bio.BIOApp |
||||
|
import _root_.monix.bio.Task |
||||
|
import _root_.monix.bio.UIO |
||||
import cats.effect.ExitCode |
import cats.effect.ExitCode |
||||
import cats.implicits._ |
|
||||
|
import cats.effect.Resource |
||||
import com.softwaremill.macwire._ |
import com.softwaremill.macwire._ |
||||
|
import io.odin._ |
||||
|
import nova.monadic_sfx.executors._ |
||||
|
// import nova.monadic_sfx.util.IOUtils._ |
||||
|
// import sttp.client.httpclient.monix.HttpClientMonixBackend |
||||
|
object Main extends MainModule with BIOApp { |
||||
|
|
||||
object Main extends MainModule with TaskApp { |
|
||||
|
def appResource(startTime: Long) = |
||||
|
for { |
||||
|
implicit0(logger: Logger[Task]) <- makeLogger |
||||
|
schedulers = new Schedulers() |
||||
|
// backend <- Resource.make( |
||||
|
// toIO(HttpClientMonixBackend()(schedulers.async)) |
||||
|
// )(c => toIO(c.close())) |
||||
|
// actorSystem <- actorSystemResource(logger) |
||||
|
_ <- Resource.liftF(wire[MainApp].program) |
||||
|
} yield () |
||||
|
|
||||
override def run(args: List[String]): Task[ExitCode] = { |
|
||||
// val startTime = Task.clock |
|
||||
// .monotonic(scala.concurrent.duration.MILLISECONDS) |
|
||||
// .map(Duration.fromNanos(_)) |
|
||||
lazy val appResource = for { |
|
||||
// clock <- Resource.liftF(Task(Task.clock)) |
|
||||
logger <- consoleLogger().withAsync() |
|
||||
backend <- AsyncHttpClientMonixBackend.resource() |
|
||||
actorSystem <- actorSystemResource(logger) |
|
||||
reqs <- Resource.liftF(Task(wireWith(requesters _))) |
|
||||
schedulers <- Resource.liftF(Task(new Schedulers())) |
|
||||
fxApp <- wireWith(fxAppResource _) |
|
||||
} yield (fxApp) |
|
||||
appResource |
|
||||
.use(fxApp => Task(fxApp.main(args.toArray))) |
|
||||
|
override def run(args: List[String]): UIO[ExitCode] = |
||||
|
appResource(System.currentTimeMillis()) |
||||
|
.use(_ => Task.unit) |
||||
|
.onErrorHandleWith(ex => UIO(ex.printStackTrace())) |
||||
.as(ExitCode.Success) |
.as(ExitCode.Success) |
||||
} |
|
||||
|
|
||||
} |
} |
@ -0,0 +1,189 @@ |
|||||
|
package nova.monadic_sfx |
||||
|
|
||||
|
import com.softwaremill.macwire._ |
||||
|
import io.odin.Logger |
||||
|
import monix.bio.Task |
||||
|
import nova.monadic_sfx.executors.Schedulers |
||||
|
import nova.monadic_sfx.implicits.JFXButton |
||||
|
import nova.monadic_sfx.implicits.JavaFXMonixObservables._ |
||||
|
import nova.monadic_sfx.ui.MyFxApp |
||||
|
import nova.monadic_sfx.ui.components.router.BrainNotWorking |
||||
|
import nova.monadic_sfx.ui.components.router.FXRouter |
||||
|
import nova.monadic_sfx.ui.components.todo.TodoListStore |
||||
|
import nova.monadic_sfx.ui.components.todo.TodoListView |
||||
|
import org.gerweck.scalafx.util._ |
||||
|
import scalafx.Includes._ |
||||
|
import scalafx.application.JFXApp.PrimaryStage |
||||
|
import scalafx.beans.property.ObjectProperty |
||||
|
import scalafx.beans.property.StringProperty |
||||
|
import scalafx.collections.ObservableBuffer |
||||
|
import scalafx.geometry.Insets |
||||
|
import scalafx.scene.Scene |
||||
|
import scalafx.scene.control.TableColumn |
||||
|
import scalafx.scene.control.TableView |
||||
|
import scalafx.scene.layout.HBox |
||||
|
import scalafx.scene.paint.Color |
||||
|
import scalafx.scene.shape.Rectangle |
||||
|
|
||||
|
class MainApp( |
||||
|
// spawnProtocol: ActorSystem[SpawnProtocol.Command], |
||||
|
schedulers: Schedulers, |
||||
|
startTime: Long |
||||
|
)(implicit logger: Logger[Task]) { |
||||
|
|
||||
|
lazy val addTodoButton = new JFXButton { |
||||
|
text = "Add" |
||||
|
} |
||||
|
|
||||
|
lazy val addTodoObs = addTodoButton.observableAction |
||||
|
|
||||
|
// lazy val todoListView = TodoListView.defaultListView |
||||
|
|
||||
|
lazy val _scene = new Scene { |
||||
|
root = new HBox { |
||||
|
padding = Insets(20) |
||||
|
content = new Rectangle { |
||||
|
width = 400 |
||||
|
height = 200 |
||||
|
fill = Color.DeepSkyBlue |
||||
|
} |
||||
|
children ++= Seq( |
||||
|
// new JFXButton { |
||||
|
// text = "DummyButton" |
||||
|
// }, |
||||
|
// new JFXButton { |
||||
|
// text = "DummyButton2" |
||||
|
// }, |
||||
|
// addTodoButton, |
||||
|
// Test.ttv |
||||
|
// todoListView |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private lazy val stage = new PrimaryStage { |
||||
|
title = "Simple ScalaFX App" |
||||
|
scene = _scene |
||||
|
width = 1000 |
||||
|
height = 400 |
||||
|
} |
||||
|
|
||||
|
// implicit val l = logger |
||||
|
// implicit val sp = spawnProtocol |
||||
|
|
||||
|
val program = for { |
||||
|
(fxApp, fxAppFib) <- wire[MyFxApp].init(stage) |
||||
|
// _ <- Task(fxApp.stage = stage) |
||||
|
// .executeOn(schedulers.fx) |
||||
|
// .delayExecution(2000.millis) |
||||
|
// todoComponent <- createTodoComponent |
||||
|
// _ <- toIO( |
||||
|
// addTodoObs |
||||
|
// .mapEval(_ => |
||||
|
// toTask(todoComponent.send(TodoListComponent.Add(Todo(1, "blah")))) |
||||
|
// ) |
||||
|
// .completedL |
||||
|
// .executeOn(schedulers.fx) |
||||
|
// .startAndForget |
||||
|
// ) |
||||
|
_ <- createTodoComponent.executeOn(schedulers.fx) |
||||
|
router <- Task(BrainNotWorking.router) |
||||
|
routerStore <- router.store(BrainNotWorking.Page.Home, logger) |
||||
|
routerNode <- for { |
||||
|
node <- |
||||
|
Task |
||||
|
.deferAction(implicit s => |
||||
|
Task(new HBox { |
||||
|
children <-- router |
||||
|
.render(BrainNotWorking.resolver)(routerStore) |
||||
|
.map(_.delegate) |
||||
|
}) |
||||
|
) |
||||
|
.executeOn(schedulers.fx) |
||||
|
_ <- Task.deferFuture( |
||||
|
routerStore.onNext(FXRouter.Replace(BrainNotWorking.Page.UserHome(1))) |
||||
|
) |
||||
|
|
||||
|
} yield node |
||||
|
// _ <- |
||||
|
// BrainNotWorking |
||||
|
// .routerTask(logger) |
||||
|
// .flatMap(node => Task(_scene.getChildren += node)) |
||||
|
// .executeOn(schedulers.fx) |
||||
|
_ <- Task(_scene.getChildren += routerNode).executeOn(schedulers.fx) |
||||
|
_ <- logger.info( |
||||
|
s"Application started in ${(System.currentTimeMillis() - startTime) / 1000f} seconds" |
||||
|
) |
||||
|
_ <- fxAppFib.join |
||||
|
} yield () |
||||
|
|
||||
|
// def createTodoComponent: Task[TodoListComponent] = { |
||||
|
// for { |
||||
|
// channel <- |
||||
|
// ConcurrentChannel |
||||
|
// .of[Task, TodoListComponent.Complete, TodoListComponent.Command] |
||||
|
// scheduler = schedulers.fx |
||||
|
// lv <- TodoListView.defaultListView2.executeOn(scheduler) |
||||
|
// // todoLV = new TodoListView(lv) |
||||
|
// todoComponent <- wire[TodoListComponent.Props].create |
||||
|
// // TODO make this a "message pass" instead of mutating directly |
||||
|
// _ <- Task(_scene.getChildren += lv).executeOn(scheduler) |
||||
|
// // _ <- toIO( |
||||
|
// // delObs |
||||
|
// // .doOnNext(_ => toTask(logger.debug("Pressed delete"))) |
||||
|
// // .doOnNext(todo => |
||||
|
// // toTask( |
||||
|
// // for { |
||||
|
// // _ <- logger.debug(s"Got todo $todo") |
||||
|
// // _ <- todoComponent.send(TodoListComponent.Delete(todo.id)) |
||||
|
// // // _ <- Task.sequence( |
||||
|
// // // lst.map(todo => |
||||
|
// // // todoComponent.send(TodoListComponent.Delete(todo.id)) |
||||
|
// // // ) |
||||
|
// // // ) |
||||
|
// // } yield () |
||||
|
// // ) |
||||
|
// // ) |
||||
|
// // .completedL |
||||
|
// // ).startAndForget |
||||
|
// // _ <- toIO( |
||||
|
// // editObs |
||||
|
// // .doOnNext(_ => toTask(logger.debug("Pressed edit"))) |
||||
|
// // .completedL |
||||
|
// // ).startAndForget |
||||
|
// } yield todoComponent |
||||
|
// } |
||||
|
|
||||
|
def createTodoComponent: Task[Unit] = |
||||
|
for { |
||||
|
store <- TodoListStore(logger) |
||||
|
rootNode <- TodoListView(store) |
||||
|
_ <- Task(_scene.getChildren += rootNode) |
||||
|
} yield () |
||||
|
|
||||
|
} |
||||
|
|
||||
|
class TestModel(_name: String, _age: Int) { |
||||
|
val name = StringProperty(_name).readOnly |
||||
|
val age = ObjectProperty(_age).readOnly |
||||
|
} |
||||
|
|
||||
|
object Test { |
||||
|
val items = ObservableBuffer( |
||||
|
new TestModel("hmm", 1), |
||||
|
new TestModel("hmm2", 2) |
||||
|
) |
||||
|
|
||||
|
val ttv = new TableView[TestModel](items) { |
||||
|
columns ++= Seq( |
||||
|
new TableColumn[TestModel, String] { |
||||
|
text = "Name" |
||||
|
cellValueFactory = { _.value.name } |
||||
|
}, |
||||
|
new TableColumn[TestModel, Int] { |
||||
|
text = "Age" |
||||
|
cellValueFactory = { _.value.age } |
||||
|
} |
||||
|
) |
||||
|
} |
||||
|
} |
@ -1,7 +1,41 @@ |
|||||
package nova.monadic_sfx |
package nova.monadic_sfx |
||||
|
|
||||
|
import scala.concurrent.duration._ |
||||
|
|
||||
|
import _root_.monix.bio.Task |
||||
|
import cats.implicits._ |
||||
|
import io.odin._ |
||||
|
import io.odin.config._ |
||||
|
import io.odin.syntax._ |
||||
import nova.monadic_sfx.actors.ActorModule |
import nova.monadic_sfx.actors.ActorModule |
||||
import nova.monadic_sfx.ui.UiModule |
|
||||
import nova.monadic_sfx.http.HttpModule |
import nova.monadic_sfx.http.HttpModule |
||||
|
import nova.monadic_sfx.ui.UiModule |
||||
|
import nova.monadic_sfx.util.reactive.Middlewares |
||||
|
|
||||
|
trait MainModule extends ActorModule with UiModule with HttpModule { |
||||
|
def routerLogger(defaultLogger: Logger[Task], storeLogger: Logger[Task]) = |
||||
|
enclosureRouting[Task]( |
||||
|
"nova.monadic_sfx.util.reactive.Middlewares" -> storeLogger, |
||||
|
"nova.monadic_sfx.util.reactive.Store" -> storeLogger |
||||
|
) |
||||
|
.withFallback(defaultLogger) |
||||
|
.withAsync() |
||||
|
|
||||
trait MainModule extends ActorModule with UiModule with HttpModule |
|
||||
|
def makeLogger = |
||||
|
for { |
||||
|
defaultLogger <- consoleLogger[Task]() |
||||
|
.withAsync(timeWindow = 1.millis) |+| fileLogger[Task]( |
||||
|
"application.log" |
||||
|
).withAsync() |
||||
|
middlewareLogger <- |
||||
|
consoleLogger[ |
||||
|
Task |
||||
|
](formatter = Middlewares.format) |
||||
|
.withMinimalLevel(Level.Trace) |
||||
|
.withAsync() |+| fileLogger[Task]( |
||||
|
"stores.log", |
||||
|
formatter = Middlewares.format |
||||
|
).withAsync() |
||||
|
routerLogger <- routerLogger(defaultLogger, middlewareLogger) |
||||
|
} yield (routerLogger) |
||||
|
} |
@ -1,13 +1,14 @@ |
|||||
package nova.monadic_sfx |
package nova.monadic_sfx |
||||
|
|
||||
|
import java.nio.ByteBuffer |
||||
|
|
||||
import monix.eval.Task |
import monix.eval.Task |
||||
import sttp.client.SttpBackend |
|
||||
import monix.reactive.Observable |
import monix.reactive.Observable |
||||
import sttp.client.asynchttpclient.WebSocketHandler |
|
||||
import java.nio.ByteBuffer |
|
||||
|
import sttp.client.SttpBackend |
||||
|
import sttp.client.httpclient.WebSocketHandler |
||||
|
|
||||
trait AppTypes {} |
|
||||
object AppTypes { |
|
||||
|
trait AppTypes { |
||||
type HttpBackend = |
type HttpBackend = |
||||
SttpBackend[Task, Observable[ByteBuffer], WebSocketHandler] |
SttpBackend[Task, Observable[ByteBuffer], WebSocketHandler] |
||||
} |
} |
||||
|
object AppTypes extends AppTypes {} |
@ -1,74 +1,28 @@ |
|||||
package nova.monadic_sfx.actors |
package nova.monadic_sfx.actors |
||||
|
|
||||
import io.odin.Logger |
|
||||
import monix.eval.Task |
|
||||
import cats.effect.Resource |
|
||||
import akka.actor.typed.scaladsl.Behaviors |
|
||||
import com.softwaremill.macwire._ |
|
||||
import akka.util.Timeout |
|
||||
import scala.concurrent.duration._ |
import scala.concurrent.duration._ |
||||
import scala.concurrent.Future |
|
||||
|
|
||||
import akka.actor.typed._ |
import akka.actor.typed._ |
||||
import akka.actor.typed.scaladsl.AskPattern._ |
|
||||
import scala.concurrent.Await |
|
||||
import nova.monadic_sfx.executors.Schedulers |
|
||||
|
import akka.util.Timeout |
||||
|
import cats.effect.Resource |
||||
|
import io.odin.Logger |
||||
|
import monix.bio.Task |
||||
|
|
||||
trait ActorModule { |
trait ActorModule { |
||||
import scala.concurrent.ExecutionContext |
|
||||
|
|
||||
implicit val timeout: Timeout = Timeout(3.seconds) |
|
||||
|
implicit def timeout: Timeout = Timeout(3.seconds) |
||||
|
|
||||
def actorSystemResource( |
def actorSystemResource( |
||||
logger: Logger[Task] |
logger: Logger[Task] |
||||
): Resource[Task, ActorSystem[SpawnProtocol.Command]] = |
): Resource[Task, ActorSystem[SpawnProtocol.Command]] = |
||||
Resource.make(logger.info("Creating Actor System") >> Task { |
Resource.make(logger.info("Creating Actor System") >> Task { |
||||
ActorSystem(HelloWorldMain(), name = "FXActorSystem") |
|
||||
|
ActorSystem(SpawnProtocol(), name = "FXActorSystem") |
||||
})(sys => |
})(sys => |
||||
logger.info("Shutting down actor system") >> Task( |
|
||||
sys.terminate() |
|
||||
) >> logger.info("Actor System terminated") |
|
||||
|
for { |
||||
|
_ <- Task(sys.terminate()) |
||||
|
_ <- Task.fromFuture(sys.whenTerminated) |
||||
|
_ <- logger.info("Actor System Terminated") |
||||
|
} yield () |
||||
) |
) |
||||
|
|
||||
// def actorsResource( |
|
||||
// system: ActorSystem[SpawnProtocol.Command], |
|
||||
// logger: Logger[Task], |
|
||||
// schedulers: Schedulers |
|
||||
// ): Resource[Task, Task[ActorRef[Counter.Command]]] = { |
|
||||
// implicit val ec: ExecutionContext = system.executionContext |
|
||||
// implicit val scheduler = system.scheduler |
|
||||
// Resource.make( |
|
||||
// Task { |
|
||||
// val actor = Task.deferFuture { |
|
||||
// system.ask[ActorRef[Counter.Command]]( |
|
||||
// SpawnProtocol.Spawn( |
|
||||
// behavior = Counter(), |
|
||||
// name = "counterActor", |
|
||||
// // DispatcherSelector.fromConfig("javafx-dispatcher"), |
|
||||
// // Props.empty, |
|
||||
// _ |
|
||||
// ) |
|
||||
// ) |
|
||||
// } |
|
||||
// // system. |
|
||||
// actor |
|
||||
// } |
|
||||
// )(actorTask => |
|
||||
// for { |
|
||||
// actor <- actorTask |
|
||||
// _ <- logger.info("Stopping actor counter") |
|
||||
// t <- Task(actor ! Counter.Stop) |
|
||||
// _ <- logger.info("Counter actor stopped") |
|
||||
// } yield () |
|
||||
// ) |
|
||||
// } |
|
||||
|
|
||||
} |
|
||||
object HelloWorldMain { |
|
||||
def apply(): Behavior[SpawnProtocol.Command] = |
|
||||
Behaviors.setup { context => |
|
||||
// Start initial tasks |
|
||||
// context.spawn(...) |
|
||||
|
|
||||
SpawnProtocol() |
|
||||
} |
|
||||
} |
} |
@ -1,9 +1,28 @@ |
|||||
package nova.monadic_sfx.executors |
package nova.monadic_sfx.executors |
||||
|
|
||||
|
import com.typesafe.scalalogging.Logger |
||||
import monix.execution.Scheduler |
import monix.execution.Scheduler |
||||
|
import monix.execution.UncaughtExceptionReporter |
||||
|
import monix.execution.schedulers.TracingScheduler |
||||
|
|
||||
class Schedulers( |
class Schedulers( |
||||
val blockingIO: Scheduler = Scheduler.io(), |
|
||||
val cpu: Scheduler = Scheduler.global, |
|
||||
val fx: Scheduler = JFXExecutionContexts.fxScheduler |
|
||||
|
val blocking: Scheduler = TracingScheduler( |
||||
|
Scheduler |
||||
|
.io() |
||||
|
.withUncaughtExceptionReporter(Schedulers.reporter) |
||||
|
), |
||||
|
val async: Scheduler = Scheduler.traced |
||||
|
.withUncaughtExceptionReporter(Schedulers.reporter), |
||||
|
val fx: Scheduler = TracingScheduler( |
||||
|
JFXExecutionContexts.fxScheduler |
||||
|
.withUncaughtExceptionReporter(Schedulers.reporter) |
||||
|
) |
||||
) |
) |
||||
|
|
||||
|
object Schedulers { |
||||
|
val reporter = UncaughtExceptionReporter { ex => |
||||
|
val logger = Logger[Schedulers] |
||||
|
logger.error("Uncaught exception", ex) |
||||
|
} |
||||
|
|
||||
|
} |
@ -1,43 +1,17 @@ |
|||||
package nova.monadic_sfx.http.requests |
package nova.monadic_sfx.http.requests |
||||
|
|
||||
import nova.monadic_sfx.AppTypes |
import nova.monadic_sfx.AppTypes |
||||
|
|
||||
import nova.monadic_sfx.AppTypes.HttpBackend |
import nova.monadic_sfx.AppTypes.HttpBackend |
||||
import monix.eval.Task |
|
||||
|
import nova.monadic_sfx.models._ |
||||
import sttp.client._ |
import sttp.client._ |
||||
import sttp.client.circe._ |
import sttp.client.circe._ |
||||
import io.circe.generic.auto._ |
|
||||
import nova.monadic_sfx.models._ |
|
||||
import cats.data.EitherT |
|
||||
|
|
||||
class DummyRequest(backend: HttpBackend) extends AppTypes { |
class DummyRequest(backend: HttpBackend) extends AppTypes { |
||||
private implicit val _backend = backend |
private implicit val _backend = backend |
||||
def send() = { |
|
||||
Task |
|
||||
.suspend { |
|
||||
for { |
|
||||
req <- |
|
||||
basicRequest |
|
||||
.get(uri"https://httpbin.org/get") |
|
||||
.response(asJson[HttpBinResponse]) |
|
||||
.send() |
|
||||
} yield (req) |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
|
def send = |
||||
|
basicRequest |
||||
|
.get(uri"https://httpbin.org/get") |
||||
|
.response(asJson[HttpBinResponse]) |
||||
|
.send() |
||||
|
|
||||
def test() = { |
|
||||
for { |
|
||||
res <- send() |
|
||||
res3 <- Task { res.body } |
|
||||
res2 <- Task { |
|
||||
res3.fold( |
|
||||
err => { |
|
||||
err.toString() |
|
||||
}, |
|
||||
value => value.toString() |
|
||||
) |
|
||||
} |
|
||||
} yield (res3) |
|
||||
} |
|
||||
} |
} |
@ -0,0 +1,98 @@ |
|||||
|
package nova.monadic_sfx.implicits |
||||
|
|
||||
|
import monix.execution.Cancelable |
||||
|
import monix.execution.Scheduler |
||||
|
import monix.execution.cancelables.CompositeCancelable |
||||
|
import monix.reactive.Observable |
||||
|
import monix.reactive.Observer |
||||
|
import monix.{eval => me} |
||||
|
|
||||
|
class ActionObservableExecutor[T]( |
||||
|
private val delegate: Observable[T] |
||||
|
) extends AnyVal { |
||||
|
def -->(sub: Observer[T])(implicit s: Scheduler) = |
||||
|
delegate |
||||
|
.doOnNext(el => me.Task(sub.onNext(el))) |
||||
|
.subscribe() |
||||
|
|
||||
|
} |
||||
|
class ActionObservableBuilder[A]( |
||||
|
private val observableAction: Observable[A] |
||||
|
) extends AnyVal { |
||||
|
def useLazyEval[T](v: => me.Task[T]) = |
||||
|
new ActionObservableExecutor[T](observableAction.mapEval(_ => v)) |
||||
|
|
||||
|
def useEval[T](cb: A => me.Task[T]) = |
||||
|
new ActionObservableExecutor[T]( |
||||
|
observableAction.mapEval(cb) |
||||
|
) |
||||
|
|
||||
|
def useIterableEval[T](cb: A => collection.immutable.Iterable[T]) = |
||||
|
new ActionObservableExecutor[T]( |
||||
|
observableAction.flatMap(a => |
||||
|
Observable.suspend(Observable.fromIterable(cb(a))) |
||||
|
) |
||||
|
) |
||||
|
|
||||
|
def doOnNext(cb: A => me.Task[Unit]): ActionObservableBuilder[A] = |
||||
|
new ActionObservableBuilder(observableAction.doOnNext(cb)) |
||||
|
|
||||
|
def mapEval[B](cb: A => me.Task[B]) = |
||||
|
new ActionObservableBuilder(observableAction.mapEval(cb)) |
||||
|
|
||||
|
def underlying = observableAction |
||||
|
|
||||
|
// Caution: Experimental stuff below.. |
||||
|
|
||||
|
def useEval2[B, C](f: A => me.Task[B], g: A => me.Task[C]) = |
||||
|
new ActionObservableExecutor[(B, C)]( |
||||
|
observableAction.publishSelector(conn => |
||||
|
conn |
||||
|
.mapEval(f) |
||||
|
.switchMap(b => |
||||
|
conn.mapEval(a => |
||||
|
for { |
||||
|
c <- g(a) |
||||
|
} yield (b, c) |
||||
|
) |
||||
|
) |
||||
|
) |
||||
|
) |
||||
|
|
||||
|
def bifurcate[B, C]( |
||||
|
f: ActionObservableBuilder[A] => B, |
||||
|
g: ActionObservableBuilder[A] => C |
||||
|
)(implicit s: Scheduler) = |
||||
|
observableAction |
||||
|
.publishSelector(conn => |
||||
|
Observable( |
||||
|
Observable.unit.doOnNext(_ => |
||||
|
me.Task(f(new ActionObservableBuilder[A](conn))) >> me.Task.unit |
||||
|
), |
||||
|
Observable.unit.doOnNext(_ => |
||||
|
me.Task(g(new ActionObservableBuilder[A](conn))) >> me.Task.unit |
||||
|
) |
||||
|
).merge |
||||
|
) |
||||
|
.subscribe() |
||||
|
|
||||
|
def split( |
||||
|
lst: (ActionObservableBuilder[A] => Cancelable)* |
||||
|
)(implicit s: Scheduler): Cancelable = { |
||||
|
val comp = CompositeCancelable() |
||||
|
comp += observableAction |
||||
|
.publishSelector(conn => |
||||
|
Observable( |
||||
|
lst.map(f => |
||||
|
Observable.unit.doOnNext(_ => |
||||
|
me.Task( |
||||
|
comp += f(new ActionObservableBuilder[A](conn)) |
||||
|
) >> me.Task.unit |
||||
|
) |
||||
|
): _* |
||||
|
).merge |
||||
|
) |
||||
|
.subscribe() |
||||
|
} |
||||
|
|
||||
|
} |
@ -0,0 +1,44 @@ |
|||||
|
package nova.monadic_sfx.implicits |
||||
|
|
||||
|
import javafx.{scene => jfxs} |
||||
|
import org.kordamp.ikonli.{javafx => ikonlifx} |
||||
|
import scalafx.scene.paint.Paint |
||||
|
import scalafx.scene.text.Text |
||||
|
|
||||
|
object FontIcon { |
||||
|
implicit def sfxText2jfx(v: FontIcon): jfxs.text.Text = |
||||
|
if (v != null) v.delegate else null |
||||
|
|
||||
|
} |
||||
|
|
||||
|
// extends Shape(delegate) |
||||
|
// with PositionDelegate[ikonlifx.FontIcon] |
||||
|
// with SFXDelegate[ikonlifx.FontIcon] |
||||
|
|
||||
|
class FontIcon(override val delegate: ikonlifx.FontIcon = new ikonlifx.FontIcon) |
||||
|
extends Text(delegate) { |
||||
|
|
||||
|
// def iconCode_=(v: Ikon) = delegate.setIconCode(v) |
||||
|
|
||||
|
def iconColor = delegate.getIconColor() |
||||
|
|
||||
|
def iconColor_=(color: Paint) = delegate.setIconColor(color) |
||||
|
|
||||
|
def iconSize = delegate.getIconSize() |
||||
|
|
||||
|
def iconSize_=(size: Int) = delegate.setIconSize(size) |
||||
|
|
||||
|
def iconLiteral = delegate.getIconLiteral() |
||||
|
|
||||
|
def iconLiteral_=(literal: IconLiteral) = |
||||
|
delegate.setIconLiteral(literal.value) |
||||
|
|
||||
|
def iconLiteral_=(literal: String) = delegate.setIconLiteral(literal) |
||||
|
|
||||
|
} |
||||
|
|
||||
|
sealed abstract class IconLiteral(val value: String) |
||||
|
object IconLiteral { |
||||
|
// fab-accusoft |
||||
|
case object Gmi10k extends IconLiteral("gmi-10k") |
||||
|
} |
@ -0,0 +1,42 @@ |
|||||
|
package nova.monadic_sfx.implicits |
||||
|
|
||||
|
import com.jfoenix.{controls => jfoenixc} |
||||
|
import javafx.{scene => jfxs} |
||||
|
import scalafx.Includes._ |
||||
|
import scalafx.beans.property.ObjectProperty |
||||
|
import scalafx.scene.Node |
||||
|
import scalafx.scene.control.Button |
||||
|
|
||||
|
import jfxs.{paint => jfxsp} |
||||
|
|
||||
|
object JFXButton { |
||||
|
implicit def sfxButton2jfx(v: JFXButton): jfoenixc.JFXButton = |
||||
|
if (v != null) v.delegate else null |
||||
|
} |
||||
|
|
||||
|
class JFXButton( |
||||
|
override val delegate: jfoenixc.JFXButton = new jfoenixc.JFXButton |
||||
|
) extends Button(delegate) { |
||||
|
|
||||
|
/** |
||||
|
* Creates a button with the specified text as its label. |
||||
|
*/ |
||||
|
def this(text: String) = this(new jfoenixc.JFXButton(text)) |
||||
|
|
||||
|
/** |
||||
|
* Creates a button with the specified text and icon for its label. |
||||
|
*/ |
||||
|
def this(text: String, graphic: Node) = |
||||
|
this(new jfoenixc.JFXButton(text, graphic)) |
||||
|
|
||||
|
def ripplerFill: ObjectProperty[jfxsp.Paint] = |
||||
|
jfxObjectProperty2sfx(delegate.ripplerFillProperty) |
||||
|
|
||||
|
def ripplerFill_=(b: jfxsp.Paint): Unit = { |
||||
|
ripplerFill() = b |
||||
|
} |
||||
|
|
||||
|
def obsAction = |
||||
|
new ActionObservableBuilder(this.observableAction()) |
||||
|
|
||||
|
} |
@ -0,0 +1,36 @@ |
|||||
|
package nova.monadic_sfx.implicits |
||||
|
|
||||
|
import com.jfoenix.{controls => jfoenixc} |
||||
|
import javafx.scene.{control => jfxsc} |
||||
|
import scalafx.Includes._ |
||||
|
import scalafx.beans.property.ReadOnlyObjectProperty |
||||
|
import scalafx.delegate.SFXDelegate |
||||
|
import scalafx.scene.control.IndexedCell |
||||
|
import scalafx.scene.control.ListView |
||||
|
|
||||
|
object JFXListCell { |
||||
|
implicit def sfxListCell2jfx[T]( |
||||
|
l: JFXListCell[T] |
||||
|
): jfoenixc.JFXListCell[T] = |
||||
|
if (l != null) l.delegate else null |
||||
|
} |
||||
|
|
||||
|
class JFXListCell[T]( |
||||
|
override val delegate: jfoenixc.JFXListCell[T] = new jfoenixc.JFXListCell[T] |
||||
|
) extends IndexedCell(delegate) |
||||
|
with SFXDelegate[jfoenixc.JFXListCell[T]] { |
||||
|
|
||||
|
/** |
||||
|
* The ListView associated with this Cell. |
||||
|
*/ |
||||
|
def listView: ReadOnlyObjectProperty[jfxsc.ListView[T]] = |
||||
|
delegate.listViewProperty |
||||
|
|
||||
|
/** |
||||
|
* Updates the ListView associated with this Cell. |
||||
|
*/ |
||||
|
def updateListView(listView: ListView[T]): Unit = { |
||||
|
delegate.updateListView(listView) |
||||
|
} |
||||
|
|
||||
|
} |
@ -0,0 +1,48 @@ |
|||||
|
package nova.monadic_sfx.implicits |
||||
|
|
||||
|
import com.jfoenix.{controls => jfoenixc} |
||||
|
import monix.execution.Scheduler |
||||
|
import monix.reactive.Observable |
||||
|
import scalafx.Includes._ |
||||
|
import scalafx.collections.ObservableBuffer |
||||
|
import scalafx.scene.control.ListView |
||||
|
|
||||
|
object JFXListView { |
||||
|
implicit def sfxListView2jfx[T](l: JFXListView[T]): jfoenixc.JFXListView[T] = |
||||
|
if (l != null) l.delegate else null |
||||
|
} |
||||
|
|
||||
|
// extends Control(delegate) |
||||
|
// with SFXDelegate[jfoenixc.JFXListView[T]] |
||||
|
|
||||
|
class JFXListView[T]( |
||||
|
override val delegate: jfoenixc.JFXListView[T] = new jfoenixc.JFXListView[T] |
||||
|
) extends ListView[T] { |
||||
|
|
||||
|
// def items_=( |
||||
|
// v: Observable[ObservableBuffer[T]] |
||||
|
// )(implicit s: Scheduler): Unit = { |
||||
|
// v.foreach { items() = _ } |
||||
|
// } |
||||
|
|
||||
|
def items_=( |
||||
|
v: Observable[Seq[T]] |
||||
|
)(implicit s: Scheduler): Unit = { |
||||
|
v |
||||
|
.map { |
||||
|
// case buf: ObservableBuffer[T] => buf |
||||
|
case other => ObservableBuffer.from(other) |
||||
|
} |
||||
|
// .map(myDiff(items(), _)) |
||||
|
.foreach { items() = _ } |
||||
|
} |
||||
|
|
||||
|
def depth = delegate.depthProperty() |
||||
|
def depth_=(depth: Int) = delegate.setDepth(depth) |
||||
|
|
||||
|
def expanded = delegate.expandedProperty() |
||||
|
def expanded_=(v: Boolean) = expanded() = v |
||||
|
|
||||
|
|
||||
|
|
||||
|
} |
@ -0,0 +1,29 @@ |
|||||
|
package nova.monadic_sfx.implicits |
||||
|
|
||||
|
import com.jfoenix.{controls => jfoenixc} |
||||
|
import scalafx.scene.control.ProgressIndicator |
||||
|
|
||||
|
object JFXSpinner { |
||||
|
implicit def sfxSpinner2jfx( |
||||
|
v: JFXSpinner |
||||
|
): jfoenixc.JFXSpinner = if (v != null) v.delegate else null |
||||
|
|
||||
|
} |
||||
|
|
||||
|
// extends Control(delegate) |
||||
|
// with SFXDelegate[jfoenixc.JFXSpinner] |
||||
|
|
||||
|
/** |
||||
|
* Wraps [[JFoenix JFXSpinner]] |
||||
|
*/ |
||||
|
class JFXSpinner( |
||||
|
override val delegate: jfoenixc.JFXSpinner = new jfoenixc.JFXSpinner |
||||
|
) extends ProgressIndicator(delegate) { |
||||
|
|
||||
|
def radius = delegate.getRadius() |
||||
|
def radius_=(radius: Double) = delegate.setRadius(radius) |
||||
|
|
||||
|
def startingAngle = delegate.startingAngleProperty() |
||||
|
def startingAngle_=(angle: Double) = delegate.setStartingAngle(angle) |
||||
|
|
||||
|
} |
@ -0,0 +1,40 @@ |
|||||
|
package nova.monadic_sfx.implicits |
||||
|
|
||||
|
import com.jfoenix.{controls => jfoenixc} |
||||
|
import scalafx.Includes._ |
||||
|
import scalafx.beans.property.BooleanProperty |
||||
|
import scalafx.scene.control.TextArea |
||||
|
import scalafx.scene.paint.Paint |
||||
|
|
||||
|
object JFXTextArea { |
||||
|
implicit def sfxTextArea2jfx(v: JFXTextArea): jfoenixc.JFXTextArea = |
||||
|
if (v != null) v.delegate else null |
||||
|
} |
||||
|
// extends TextInputControl(delegate) |
||||
|
// with SFXDelegate[jfoenixc.JFXTextArea] |
||||
|
class JFXTextArea( |
||||
|
override val delegate: jfoenixc.JFXTextArea = new jfoenixc.JFXTextArea() |
||||
|
) extends TextArea(delegate) { |
||||
|
|
||||
|
/** |
||||
|
* Creates a TextArea with initial text content. |
||||
|
* |
||||
|
* @param text - A string for text content. |
||||
|
*/ |
||||
|
def this(text: String) = this(new jfoenixc.JFXTextArea(text)) |
||||
|
|
||||
|
def labelFloat = delegate.labelFloatProperty() |
||||
|
def labelFloat_=(v: Boolean) = delegate.setLabelFloat(v) |
||||
|
|
||||
|
def focusColor: Paint = delegate.getFocusColor() |
||||
|
def focusColor_=(color: Paint) = delegate.setFocusColor(color) |
||||
|
|
||||
|
def unFocusColor = delegate.getUnFocusColor() |
||||
|
def unFocusColor_=(color: Paint) = delegate.setUnFocusColor(color) |
||||
|
|
||||
|
def disableAnimation: BooleanProperty = delegate.disableAnimationProperty() |
||||
|
|
||||
|
def disableAnimation_=(disable: Boolean) = |
||||
|
delegate.setDisableAnimation(disable) |
||||
|
|
||||
|
} |
@ -0,0 +1,35 @@ |
|||||
|
package nova.monadic_sfx.implicits |
||||
|
|
||||
|
import com.jfoenix.{controls => jfoenixc} |
||||
|
import scalafx.Includes._ |
||||
|
import scalafx.beans.property.BooleanProperty |
||||
|
import scalafx.scene.control.TextField |
||||
|
import scalafx.scene.paint.Paint |
||||
|
|
||||
|
object JFXTextField { |
||||
|
implicit def sfxTextField2jfx(v: JFXTextField): jfoenixc.JFXTextField = |
||||
|
if (v != null) v.delegate else null |
||||
|
} |
||||
|
|
||||
|
// TextInputControl(delegate) |
||||
|
// with AlignmentDelegate[jfoenixc.JFXTextField] |
||||
|
// with SFXDelegate[jfoenixc.JFXTextField] { |
||||
|
|
||||
|
class JFXTextField( |
||||
|
override val delegate: jfoenixc.JFXTextField = new jfoenixc.JFXTextField |
||||
|
) extends TextField(delegate) { |
||||
|
|
||||
|
def labelFloat = delegate.labelFloatProperty() |
||||
|
def labelFloat_=(v: Boolean) = delegate.setLabelFloat(v) |
||||
|
|
||||
|
def focusColor: Paint = delegate.getFocusColor() |
||||
|
def focusColor_=(color: Paint) = delegate.setFocusColor(color) |
||||
|
|
||||
|
def unFocusColor = delegate.getUnFocusColor() |
||||
|
def unFocusColor_=(color: Paint) = delegate.setUnFocusColor(color) |
||||
|
|
||||
|
def disableAnimation: BooleanProperty = delegate.disableAnimationProperty() |
||||
|
|
||||
|
def disableAnimation_=(disable: Boolean) = |
||||
|
delegate.setDisableAnimation(disable) |
||||
|
} |
@ -0,0 +1,62 @@ |
|||||
|
package nova.monadic_sfx.implicits |
||||
|
|
||||
|
import com.jfoenix.controls.datamodels.treetable.RecursiveTreeObject |
||||
|
import com.jfoenix.{controls => jfoenixc} |
||||
|
import javafx.scene.{control => jfxsc} |
||||
|
import scalafx.collections.ObservableBuffer |
||||
|
import scalafx.scene.control.TreeItem |
||||
|
import scalafx.scene.control.TreeTableView |
||||
|
|
||||
|
class RecursiveTreeItem[G <: RecursiveTreeObject[G]]( |
||||
|
override val delegate: jfoenixc.RecursiveTreeItem[G] = |
||||
|
new jfoenixc.RecursiveTreeItem[G]((item: RecursiveTreeObject[G]) => |
||||
|
item.getChildren() |
||||
|
) |
||||
|
) extends TreeItem[G](delegate) { |
||||
|
def this(value: G) = |
||||
|
this( |
||||
|
new jfoenixc.RecursiveTreeItem[G]( |
||||
|
value, |
||||
|
(item: RecursiveTreeObject[G]) => item.getChildren() |
||||
|
) |
||||
|
) |
||||
|
def this(items: ObservableBuffer[G]) = |
||||
|
this( |
||||
|
new jfoenixc.RecursiveTreeItem[G]( |
||||
|
items, |
||||
|
(item: RecursiveTreeObject[G]) => item.getChildren() |
||||
|
) |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
object RecursiveTreeItem { |
||||
|
implicit def sfxTreeItem2jfxTreeItem[G <: RecursiveTreeObject[G]]( |
||||
|
v: RecursiveTreeItem[G] |
||||
|
): jfoenixc.RecursiveTreeItem[G] = v.delegate |
||||
|
} |
||||
|
|
||||
|
// @formatter:off |
||||
|
class JFXTreeTableView[S <: RecursiveTreeObject[S]]( |
||||
|
override val delegate: jfoenixc.JFXTreeTableView[S] = new jfoenixc.JFXTreeTableView[S] |
||||
|
) extends TreeTableView(delegate) { |
||||
|
|
||||
|
|
||||
|
def this(root: TreeItem[S]) = this(new jfoenixc.JFXTreeTableView[S](root)) |
||||
|
// def this(root: TreeItem[S], items: ObservableBuffer[S]) = this(new jfoenixc.JFXTreeTableView[S](root, items)) |
||||
|
|
||||
|
// @formatter:on |
||||
|
def currentItemsCount = delegate.currentItemsCountProperty() |
||||
|
def predicate = delegate.predicateProperty() |
||||
|
// delegate.set |
||||
|
|
||||
|
// override def treeColumn_=(v: TreeTableColumn[S, _]): Unit = ??? |
||||
|
// delegate.setTreeColumn() |
||||
|
} |
||||
|
|
||||
|
// @formatter:off |
||||
|
object JFXTreeTableView { |
||||
|
implicit def sfxTreeTableView2jfx[S <: RecursiveTreeObject[S]]( |
||||
|
v: JFXTreeTableView[S] |
||||
|
): jfxsc.TreeTableView[S] = if (v != null) v.delegate else null |
||||
|
} |
||||
|
// @formatter:on |
@ -0,0 +1,327 @@ |
|||||
|
package nova.monadic_sfx.implicits |
||||
|
|
||||
|
import javafx.beans.property.ObjectProperty |
||||
|
import javafx.collections.ObservableList |
||||
|
import javafx.event.ActionEvent |
||||
|
import javafx.event.EventHandler |
||||
|
import javafx.scene.{input => jfxsi} |
||||
|
import javafx.{event => jfxe} |
||||
|
import monix.bio.Task |
||||
|
import monix.eval.Coeval |
||||
|
import monix.execution.Ack |
||||
|
import monix.execution.Cancelable |
||||
|
import monix.execution.Scheduler |
||||
|
import monix.execution.cancelables.SingleAssignCancelable |
||||
|
import monix.reactive.Observable |
||||
|
import monix.reactive.Observer |
||||
|
import monix.reactive.OverflowStrategy |
||||
|
import monix.tail.Iterant |
||||
|
import monix.{eval => me} |
||||
|
import org.gerweck.scalafx.util._ |
||||
|
import scalafx.Includes._ |
||||
|
import scalafx.beans.property.Property |
||||
|
import scalafx.beans.property.ReadOnlyProperty |
||||
|
import scalafx.collections.ObservableBuffer |
||||
|
import scalafx.event.subscriptions.Subscription |
||||
|
import scalafx.scene.Scene |
||||
|
import scalafx.scene.control.ButtonBase |
||||
|
import scalafx.scene.control.MenuItem |
||||
|
|
||||
|
object JavaFXMonixObservables { |
||||
|
|
||||
|
implicit final class SceneObservables(private val scene: Scene) |
||||
|
extends AnyVal { |
||||
|
|
||||
|
def observableMousePressed(): Observable[jfxsi.MouseEvent] = { |
||||
|
import monix.execution.cancelables.SingleAssignCancelable |
||||
|
Observable.create(OverflowStrategy.Unbounded) { sub => |
||||
|
val c = SingleAssignCancelable() |
||||
|
val l = new jfxe.EventHandler[jfxsi.MouseEvent] { |
||||
|
override def handle(event: jfxsi.MouseEvent): Unit = { |
||||
|
if (sub.onNext(event) == Ack.Stop) c.cancel() |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
scene.onMousePressed = l |
||||
|
c := Cancelable(() => |
||||
|
scene.removeEventHandler( |
||||
|
jfxsi.MouseEvent.MOUSE_PRESSED, |
||||
|
l |
||||
|
) |
||||
|
) |
||||
|
c |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
def observableMouseDragged(): Observable[jfxsi.MouseEvent] = { |
||||
|
import monix.execution.cancelables.SingleAssignCancelable |
||||
|
Observable.create(OverflowStrategy.Unbounded) { sub => |
||||
|
val c = SingleAssignCancelable() |
||||
|
val l = new jfxe.EventHandler[jfxsi.MouseEvent] { |
||||
|
override def handle(event: jfxsi.MouseEvent): Unit = { |
||||
|
if (sub.onNext(event) == Ack.Stop) c.cancel() |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
scene.onMouseDragged = l |
||||
|
c := Cancelable(() => |
||||
|
scene.removeEventHandler( |
||||
|
jfxsi.MouseEvent.MOUSE_DRAGGED, |
||||
|
l |
||||
|
) |
||||
|
) |
||||
|
c |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
implicit final class BindObs[T, J](private val prop: Property[T, J]) |
||||
|
extends AnyVal { |
||||
|
def -->(op: Observer[T]) = { |
||||
|
op.onNext(prop.value) |
||||
|
} |
||||
|
|
||||
|
def ==>(op: Property[T, J]) = { |
||||
|
op <== prop |
||||
|
} |
||||
|
|
||||
|
def <--(obs: Observable[T])(implicit s: Scheduler) = { |
||||
|
obs.doOnNextF(v => Coeval(prop.value = v)).subscribe() |
||||
|
} |
||||
|
|
||||
|
def asOption = prop.map(Option(_)) |
||||
|
|
||||
|
def observableChange[J1 >: J]: Observable[J1] = { |
||||
|
import monix.execution.cancelables.SingleAssignCancelable |
||||
|
Observable.create(OverflowStrategy.Unbounded) { sub => |
||||
|
val c = SingleAssignCancelable() |
||||
|
|
||||
|
val canc = |
||||
|
prop.onChange((a, b, c1) => |
||||
|
if (c1 != null && sub.onNext(c1) == Ack.Stop) c.cancel() |
||||
|
) |
||||
|
|
||||
|
c := Cancelable(() => canc.cancel()) |
||||
|
c |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
implicit final class BindObs2[A](private val prop: ObjectProperty[A]) |
||||
|
extends AnyVal { |
||||
|
|
||||
|
def -->(sub: Observer[A]) = |
||||
|
prop.onChange((a, b, c) => if (c != null) sub.onNext(c)) |
||||
|
|
||||
|
def -->(op: Property[A, A]) = { |
||||
|
prop.onChange((a, b, c) => if (c != null) op() = c) |
||||
|
} |
||||
|
|
||||
|
def <--(obs: Observable[A])(implicit s: Scheduler) = { |
||||
|
obs.doOnNextF(v => Coeval(prop() = v)).subscribe() |
||||
|
} |
||||
|
|
||||
|
def observableChange[J1 >: A]: Observable[J1] = { |
||||
|
import monix.execution.cancelables.SingleAssignCancelable |
||||
|
Observable.create(OverflowStrategy.Unbounded) { sub => |
||||
|
val c = SingleAssignCancelable() |
||||
|
|
||||
|
val canc = prop.onChange((_, _, c1) => |
||||
|
if (c1 != null && sub.onNext(c1) == Ack.Stop) c.cancel() |
||||
|
) |
||||
|
|
||||
|
c := Cancelable(() => canc.cancel()) |
||||
|
c |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
implicit final class ObservableListExt[A]( |
||||
|
private val buffer: ObservableList[A] |
||||
|
) extends AnyVal { |
||||
|
|
||||
|
// def -->(sub: Observer[A]) = |
||||
|
// buffer.onChange((a, b, c) => if (c != null) sub.onNext(c)) |
||||
|
|
||||
|
// def -->(op: Property[A, A]) = { |
||||
|
// buffer.onChange((a, b, c) => if (c != null) op() = c) |
||||
|
// } |
||||
|
|
||||
|
def <--(obs: Observable[A])(implicit s: Scheduler) = { |
||||
|
obs |
||||
|
.doOnNextF(v => |
||||
|
for { |
||||
|
_ <- Coeval(buffer.clear()) |
||||
|
_ <- Coeval(buffer += v) |
||||
|
} yield () |
||||
|
) |
||||
|
.subscribe() |
||||
|
} |
||||
|
|
||||
|
def observableChange[J1 >: A]: Observable[J1] = { |
||||
|
import monix.execution.cancelables.SingleAssignCancelable |
||||
|
Observable.create(OverflowStrategy.Unbounded) { sub => |
||||
|
val c = SingleAssignCancelable() |
||||
|
|
||||
|
implicit val s = sub.scheduler |
||||
|
|
||||
|
val canc = |
||||
|
buffer.onChange((buf, _) => |
||||
|
loop(sub, buf.toIterable.iterator, c).runToFuture |
||||
|
) |
||||
|
|
||||
|
c := Cancelable(() => canc.cancel()) |
||||
|
c |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private def loop( |
||||
|
sub: Observer[A], |
||||
|
it: Iterator[A], |
||||
|
c: Cancelable |
||||
|
): Task[Unit] = |
||||
|
if (it.hasNext) { |
||||
|
val next = it.next() |
||||
|
Task.deferFuture(sub.onNext(next)).flatMap { |
||||
|
case Ack.Continue => loop(sub, it, c) |
||||
|
case Ack.Stop => Task(c.cancel()) |
||||
|
} |
||||
|
} else Task.unit |
||||
|
} |
||||
|
|
||||
|
implicit final class BindObs3[T, J](private val prop: ReadOnlyProperty[T, J]) |
||||
|
extends AnyVal { |
||||
|
def -->(op: Observer[T]) = { |
||||
|
op.onNext(prop.value) |
||||
|
} |
||||
|
|
||||
|
def ==>(op: Property[T, J]) = { |
||||
|
op <== prop |
||||
|
} |
||||
|
|
||||
|
def observableChange[J1 >: J]: Observable[J1] = { |
||||
|
import monix.execution.cancelables.SingleAssignCancelable |
||||
|
Observable.create(OverflowStrategy.Unbounded) { sub => |
||||
|
val c = SingleAssignCancelable() |
||||
|
|
||||
|
val canc = prop.onChange((a, b, c1) => |
||||
|
if (c1 != null && sub.onNext(c1) == Ack.Stop) c.cancel() |
||||
|
) |
||||
|
|
||||
|
c := Cancelable(() => canc.cancel()) |
||||
|
c |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
implicit final class ObjectPropertyObservableListExt[A]( |
||||
|
private val prop: ObjectProperty[ObservableList[A]] |
||||
|
) extends AnyVal { |
||||
|
def <--(obs: Observable[Seq[A]])(implicit s: Scheduler) = { |
||||
|
obs.doOnNext(v => me.Task(prop() = ObservableBuffer.from(v))).subscribe() |
||||
|
} |
||||
|
|
||||
|
def -->(sub: Observer[A])(implicit s: Scheduler) = { |
||||
|
val c = SingleAssignCancelable() |
||||
|
val subs: Subscription = prop.onChange((a, b, c1) => |
||||
|
if (c1 != null) |
||||
|
Iterant[Task] |
||||
|
.fromIterable(c1.toIterable) |
||||
|
.consume |
||||
|
.use(consume(sub, c, _)) |
||||
|
.runToFuture |
||||
|
) |
||||
|
c := Cancelable(() => subs.cancel()) |
||||
|
} |
||||
|
|
||||
|
private def loop(sub: Observer[A], it: Iterator[A]): Task[Unit] = |
||||
|
if (it.hasNext) { |
||||
|
val next = it.next() |
||||
|
Task.deferFuture(sub.onNext(next)).flatMap { |
||||
|
case Ack.Continue => loop(sub, it) |
||||
|
case Ack.Stop => Task.unit |
||||
|
} |
||||
|
} else Task.unit |
||||
|
|
||||
|
private def consume( |
||||
|
sub: Observer[A], |
||||
|
c: Cancelable, |
||||
|
consumer: Iterant.Consumer[Task, A] |
||||
|
): Task[Unit] = |
||||
|
consumer.pull.flatMap { |
||||
|
case Left(value) => Task.unit |
||||
|
case Right(value) => |
||||
|
Task.deferFuture(sub.onNext(value)).flatMap { |
||||
|
case Ack.Continue => consume(sub, c, consumer) |
||||
|
case Ack.Stop => Task(c.cancel()) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
implicit final class ObjectPropertyActionEvent( |
||||
|
private val prop: ObjectProperty[EventHandler[ActionEvent]] |
||||
|
) extends AnyVal { |
||||
|
// def <--(obs: Observable[ActionEvent])(implicit s: Scheduler) = { |
||||
|
// obs.doOnNext(v => me.Task(prop() = ObservableBuffer.from(v))).subscribe() |
||||
|
// } |
||||
|
|
||||
|
// def -->(sub: Observer[ActionEvent]) = |
||||
|
// prop(). |
||||
|
|
||||
|
} |
||||
|
|
||||
|
// def emit(prop: ObjectProperty[EventHandler[ActionEvent]]) = |
||||
|
|
||||
|
implicit final class OnActionObservable( |
||||
|
private val button: ButtonBase |
||||
|
) extends AnyVal { |
||||
|
|
||||
|
def observableAction: Observable[jfxe.ActionEvent] = { |
||||
|
import monix.execution.cancelables.SingleAssignCancelable |
||||
|
Observable.create(OverflowStrategy.Unbounded) { sub => |
||||
|
val c = SingleAssignCancelable() |
||||
|
val l = new jfxe.EventHandler[jfxe.ActionEvent] { |
||||
|
override def handle(event: jfxe.ActionEvent): Unit = { |
||||
|
if (sub.onNext(event) == Ack.Stop) c.cancel() |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
button.onAction = l |
||||
|
c := Cancelable(() => |
||||
|
button.removeEventHandler( |
||||
|
jfxe.ActionEvent.ACTION, |
||||
|
l |
||||
|
) |
||||
|
) |
||||
|
c |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
implicit final class MenuItemActionObservable( |
||||
|
private val item: MenuItem |
||||
|
) extends AnyVal { |
||||
|
|
||||
|
def observableAction: Observable[jfxe.ActionEvent] = { |
||||
|
import monix.execution.cancelables.SingleAssignCancelable |
||||
|
Observable.create(OverflowStrategy.Unbounded) { sub => |
||||
|
val c = SingleAssignCancelable() |
||||
|
val l = new jfxe.EventHandler[jfxe.ActionEvent] { |
||||
|
override def handle(event: jfxe.ActionEvent): Unit = { |
||||
|
if (sub.onNext(event) == Ack.Stop) c.cancel() |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
item.onAction = l |
||||
|
c := Cancelable(() => |
||||
|
item.removeEventHandler( |
||||
|
jfxe.ActionEvent.ACTION, |
||||
|
l |
||||
|
) |
||||
|
) |
||||
|
c |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,9 @@ |
|||||
|
package nova.monadic_sfx.implicits |
||||
|
|
||||
|
import scalafx.scene.{control => sfxc} |
||||
|
|
||||
|
import JavaFXMonixObservables._ |
||||
|
|
||||
|
class MenuItem extends sfxc.MenuItem { |
||||
|
def obsAction = new ActionObservableBuilder(this.observableAction) |
||||
|
} |
@ -0,0 +1,50 @@ |
|||||
|
package nova.monadic_sfx |
||||
|
|
||||
|
import javafx.event.ActionEvent |
||||
|
import monix.execution.Ack |
||||
|
import monix.execution.Cancelable |
||||
|
import monix.reactive.Observable |
||||
|
import monix.reactive.OverflowStrategy |
||||
|
import scalafx.scene.control._ |
||||
|
|
||||
|
package object implicits { |
||||
|
|
||||
|
implicit class MyButtonExt(val button: Button) extends AnyVal { |
||||
|
def observableAction(): Observable[ActionEvent] = { |
||||
|
import monix.execution.cancelables.SingleAssignCancelable |
||||
|
Observable.create(OverflowStrategy.Unbounded) { sub => |
||||
|
val c = SingleAssignCancelable() |
||||
|
val l = new javafx.event.EventHandler[ActionEvent] { |
||||
|
override def handle(event: ActionEvent): Unit = { |
||||
|
if (sub.onNext(event) == Ack.Stop) c.cancel() |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
button.onAction = l |
||||
|
c := Cancelable(() => |
||||
|
button.removeEventHandler( |
||||
|
ActionEvent.ACTION, |
||||
|
l |
||||
|
) |
||||
|
) |
||||
|
c |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// implicit class NodeExt(val node: Node) { |
||||
|
// def lookup2[T <: SFXDelegate[_]]( |
||||
|
// selector: String |
||||
|
// )(implicit c: ClassTag[T]) = { |
||||
|
// val t = c.runtimeClass |
||||
|
// Option(node.delegate.lookup(selector)) match { |
||||
|
// case Some(value) => |
||||
|
// if (value.getClass == t) Some(value) else None |
||||
|
// case None => None |
||||
|
// } |
||||
|
// } |
||||
|
|
||||
|
// val x = node.lookup2("") |
||||
|
// } |
||||
|
|
||||
|
} |
@ -1,7 +1,10 @@ |
|||||
package nova.monadic_sfx.models |
package nova.monadic_sfx.models |
||||
|
|
||||
case class RequestPayload(data: String) |
|
||||
final case class HttpBinResponse( |
|
||||
|
import io.circe.generic.JsonCodec |
||||
|
|
||||
|
final case class RequestPayload(data: String) |
||||
|
|
||||
|
@JsonCodec final case class HttpBinResponse( |
||||
url: String, |
url: String, |
||||
origin: String, |
origin: String, |
||||
headers: Map[String, String] |
headers: Map[String, String] |
||||
|
@ -1,55 +1,52 @@ |
|||||
package nova.monadic_sfx.ui |
package nova.monadic_sfx.ui |
||||
|
|
||||
|
import nova.monadic_sfx.implicits.JFXSpinner |
||||
import scalafx.geometry.Insets |
import scalafx.geometry.Insets |
||||
|
import scalafx.geometry.Pos |
||||
import scalafx.scene.Scene |
import scalafx.scene.Scene |
||||
import scalafx.scene.effect.DropShadow |
import scalafx.scene.effect.DropShadow |
||||
import scalafx.scene.layout.HBox |
import scalafx.scene.layout.HBox |
||||
|
import scalafx.scene.layout.VBox |
||||
import scalafx.scene.paint.Color._ |
import scalafx.scene.paint.Color._ |
||||
import scalafx.scene.paint._ |
import scalafx.scene.paint._ |
||||
import scalafx.scene.text.Text |
import scalafx.scene.text.Text |
||||
import monix.eval.Coeval |
|
||||
|
|
||||
class DefaultUI { |
|
||||
|
object DefaultUI { |
||||
val scene = |
val scene = |
||||
new Scene { |
new Scene { |
||||
fill = Color.rgb(38, 38, 38) |
fill = Color.rgb(38, 38, 38) |
||||
content = new HBox { |
|
||||
|
content = new VBox { |
||||
|
alignment = Pos.Center |
||||
padding = Insets(50, 80, 50, 80) |
padding = Insets(50, 80, 50, 80) |
||||
children = Seq( |
children = Seq( |
||||
new Text { |
|
||||
text = "Scala" |
|
||||
style = "-fx-font: normal bold 100pt sans-serif" |
|
||||
fill = new LinearGradient(endX = 0, stops = Stops(Red, DarkRed)) |
|
||||
}, |
|
||||
new Text { |
|
||||
text = "FX" |
|
||||
style = "-fx-font: italic bold 100pt sans-serif" |
|
||||
fill = new LinearGradient( |
|
||||
endX = 0, |
|
||||
stops = Stops(White, DarkGray) |
|
||||
|
new HBox { |
||||
|
padding = Insets(50, 80, 50, 80) |
||||
|
children = Seq( |
||||
|
new Text { |
||||
|
text = "Scala" |
||||
|
style = "-fx-font: normal bold 100pt sans-serif" |
||||
|
fill = new LinearGradient(endX = 0, stops = Stops(Red, DarkRed)) |
||||
|
}, |
||||
|
new Text { |
||||
|
text = "FX" |
||||
|
style = "-fx-font: italic bold 100pt sans-serif" |
||||
|
fill = new LinearGradient( |
||||
|
endX = 0, |
||||
|
stops = Stops(White, DarkGray) |
||||
|
) |
||||
|
effect = new DropShadow { |
||||
|
color = DarkGray |
||||
|
radius = 15 |
||||
|
spread = 0.25 |
||||
|
} |
||||
|
} |
||||
) |
) |
||||
effect = new DropShadow { |
|
||||
color = DarkGray |
|
||||
radius = 15 |
|
||||
spread = 0.25 |
|
||||
} |
|
||||
|
}, |
||||
|
new JFXSpinner { |
||||
|
radius = 50 |
||||
|
// style = "-fx-text-fill: red" |
||||
} |
} |
||||
) |
) |
||||
} |
} |
||||
} |
} |
||||
|
|
||||
// val program = Coeval |
|
||||
// .suspend { |
|
||||
// Coeval(println("hello")) >> |
|
||||
// Coeval(println(Thread.currentThread().getName())) >> |
|
||||
// Coeval { |
|
||||
// stage = new PrimaryStage { |
|
||||
// // initStyle(StageStyle.Unified) |
|
||||
// title = "ScalaFX Hello World" |
|
||||
// scene = scn |
|
||||
|
|
||||
// } |
|
||||
// } |
|
||||
// } |
|
||||
|
|
||||
} |
} |
@ -1,129 +1,41 @@ |
|||||
package nova.monadic_sfx.ui |
package nova.monadic_sfx.ui |
||||
|
|
||||
import scalafx.application.JFXApp |
|
||||
import nova.monadic_sfx.executors.Schedulers |
|
||||
import monix.execution.Scheduler |
|
||||
import monix.eval.Task |
|
||||
import nova.monadic_sfx.screens.LoginScreen |
|
||||
import nova.monadic_sfx.AppTypes |
|
||||
import scala.concurrent.duration._ |
import scala.concurrent.duration._ |
||||
import io.odin.Logger |
|
||||
import monix.execution.Callback |
|
||||
import com.softwaremill.macwire._ |
|
||||
import nova.monadic_sfx.http.Requesters |
|
||||
|
|
||||
import akka.actor.typed._ |
|
||||
import nova.monadic_sfx.actors.Counter |
|
||||
import akka.util.Timeout |
|
||||
|
|
||||
class MyFxApp( |
|
||||
logger: Logger[Task], |
|
||||
backend: AppTypes.HttpBackend, |
|
||||
actorSystem: ActorSystem[SpawnProtocol.Command], |
|
||||
requesters: Requesters, |
|
||||
schedulers: Schedulers |
|
||||
) extends JFXApp { |
|
||||
|
|
||||
implicit lazy val defaultScheduler: Scheduler = schedulers.fx |
|
||||
|
|
||||
lazy val fxActor: Task[ActorRef[Counter.Command]] = wireWith( |
|
||||
MyFxApp.makeCounterActor _ |
|
||||
) |
|
||||
|
|
||||
lazy val application = |
|
||||
for { |
|
||||
appStage <- Task(wireWith(UiModule.makePrimaryStage _)) |
|
||||
|
|
||||
// _ <- Task { |
|
||||
// val counterActor = testActor(actorSystem) |
|
||||
// counterActor ! (Counter.Increment) |
|
||||
// } |
|
||||
// ta <- testActor2(actorSystem) |
|
||||
// actor <- |
|
||||
// actorTask.bracket(actor => Task(actor ! (Counter.Increment)))(actor => |
|
||||
// Task(actor ! (Counter.Stop)) |
|
||||
// ) |
|
||||
// actor <- actorTask |
|
||||
actor <- fxActor |
|
||||
_ <- Task(actor ! (Counter.Increment)) |
|
||||
_ <- Task { stage = appStage } |
|
||||
_ <- Task.sleep(2.seconds) |
|
||||
loginScene <- wire[LoginScreen].render |
|
||||
_ <- Task { |
|
||||
// appStage.maximized = true |
|
||||
appStage.height = 800 |
|
||||
appStage.width = 800 |
|
||||
appStage |
|
||||
.scene() |
|
||||
.setRoot( |
|
||||
loginScene |
|
||||
) |
|
||||
} |
|
||||
} yield () |
|
||||
|
|
||||
// def testActor( |
|
||||
// system: ActorSystem |
|
||||
// ): akka.actor.typed.ActorRef[Counter.Command] = { |
|
||||
// val behaviour: Behavior[Counter.Command] = |
|
||||
// Behaviors.setup(context => wire[Counter]) |
|
||||
// system.spawn( |
|
||||
// behaviour, |
|
||||
// "CounterActor", |
|
||||
// DispatcherSelector.fromConfig("javafx-dispatcher") |
|
||||
// ) |
|
||||
// } |
|
||||
|
import io.odin.Logger |
||||
|
import monix.bio.Task |
||||
|
import nova.monadic_sfx.executors.Schedulers |
||||
|
import nova.monadic_sfx.ui.DefaultUI |
||||
|
import scalafx.application.JFXApp |
||||
|
import scalafx.application.JFXApp.PrimaryStage |
||||
|
|
||||
application.timed.runAsync( |
|
||||
new Callback[Throwable, (FiniteDuration, Unit)] { |
|
||||
|
class MyFxApp(val schedulers: Schedulers)(implicit logger: Logger[Task]) { |
||||
|
|
||||
override def onSuccess(value: (FiniteDuration, Unit)): Unit = { |
|
||||
val (duration, _) = value |
|
||||
println( |
|
||||
s"Application started successfully in ${duration.toSeconds} seconds" |
|
||||
) |
|
||||
} |
|
||||
|
private lazy val internal = new JFXApp { |
||||
|
stage = new PrimaryStage { |
||||
|
scene = DefaultUI.scene |
||||
|
} |
||||
|
} |
||||
|
|
||||
override def onError(e: Throwable): Unit = { |
|
||||
println("Application start failed. Reason -") |
|
||||
e.printStackTrace() |
|
||||
} |
|
||||
|
// def stage = Task(internal.stage) |
||||
|
|
||||
} |
|
||||
) |
|
||||
|
// def stage_=(stage: PrimaryStage) = Task(internal.stage = stage) |
||||
|
|
||||
override def stopApp() = { |
|
||||
val stop = for { |
|
||||
actor <- fxActor |
|
||||
_ <- logger.info("Stopping actor counter") |
|
||||
// _ <- Task.fromFuture { actor.ask[Counter.Value](Counter.GetValue) } |
|
||||
t <- Task(actor ! Counter.Stop) |
|
||||
// _ <- Task.sleep(1.second) |
|
||||
_ <- logger.info("Counter actor stopped") |
|
||||
} yield () |
|
||||
stop.runAsyncAndForget |
|
||||
// Platform.exit() |
|
||||
} |
|
||||
} |
|
||||
|
// def useInternal[T](f: JFXApp => Task[T]): Task[T] = |
||||
|
// for { |
||||
|
// _ <- logger.debug("Request for using internal value") |
||||
|
// res <- f(internal).executeOn(schedulers.fx) |
||||
|
// _ <- logger.debug(s"Result was ${res.toString()}") |
||||
|
// } yield (res) |
||||
|
|
||||
object MyFxApp { |
|
||||
def makeCounterActor( |
|
||||
system: ActorSystem[SpawnProtocol.Command] |
|
||||
): Task[ActorRef[Counter.Command]] = { |
|
||||
import akka.actor.typed.scaladsl.AskPattern._ |
|
||||
import scala.concurrent.ExecutionContext |
|
||||
|
def init(stage: => PrimaryStage, delay: FiniteDuration = 200.millis) = |
||||
|
for { |
||||
|
_ <- logger.info("Starting FX App") |
||||
|
fib <- Task(internal.main(Array.empty)).start |
||||
|
_ <- Task.sleep(200.millis) |
||||
|
_ <- Task(internal.stage = stage) |
||||
|
.executeOn(schedulers.fx) |
||||
|
.delayExecution(delay) |
||||
|
} yield (this, fib) |
||||
|
|
||||
implicit val timeout: Timeout = Timeout(3.seconds) |
|
||||
implicit val ec: ExecutionContext = system.executionContext |
|
||||
implicit val scheduler = system.scheduler |
|
||||
Task.fromFuture { |
|
||||
system.ask( |
|
||||
SpawnProtocol.Spawn( |
|
||||
behavior = wireWith(Counter.apply _), |
|
||||
name = "counterActor", |
|
||||
DispatcherSelector.fromConfig("javafx-dispatcher"), |
|
||||
_ |
|
||||
) |
|
||||
) |
|
||||
} |
|
||||
} |
|
||||
} |
} |
@ -0,0 +1,129 @@ |
|||||
|
package nova.monadic_sfx.ui |
||||
|
|
||||
|
import scala.concurrent.duration._ |
||||
|
|
||||
|
import akka.actor.typed._ |
||||
|
import akka.util.Timeout |
||||
|
import com.softwaremill.macwire._ |
||||
|
import io.odin.Logger |
||||
|
import monix.eval.Task |
||||
|
import monix.execution.Callback |
||||
|
import monix.execution.Scheduler |
||||
|
import nova.monadic_sfx.AppTypes |
||||
|
import nova.monadic_sfx.actors.Counter |
||||
|
import nova.monadic_sfx.executors.Schedulers |
||||
|
import nova.monadic_sfx.http.Requesters |
||||
|
import scalafx.application.JFXApp |
||||
|
|
||||
|
class MyFxAppOld( |
||||
|
logger: Logger[Task], |
||||
|
backend: AppTypes.HttpBackend, |
||||
|
actorSystem: ActorSystem[SpawnProtocol.Command], |
||||
|
requesters: Requesters, |
||||
|
schedulers: Schedulers |
||||
|
) extends JFXApp { |
||||
|
|
||||
|
implicit lazy val defaultScheduler: Scheduler = schedulers.fx |
||||
|
|
||||
|
// lazy val fxActor: Task[ActorRef[Counter.Command]] = wireWith( |
||||
|
// MyFxApp.makeCounterActor _ |
||||
|
// ) |
||||
|
|
||||
|
lazy val application = |
||||
|
for { |
||||
|
appStage <- Task(wireWith(UiModule.makePrimaryStage _)) |
||||
|
|
||||
|
// _ <- Task { |
||||
|
// val counterActor = testActor(actorSystem) |
||||
|
// counterActor ! (Counter.Increment) |
||||
|
// } |
||||
|
// ta <- testActor2(actorSystem) |
||||
|
// actor <- |
||||
|
// actorTask.bracket(actor => Task(actor ! (Counter.Increment)))(actor => |
||||
|
// Task(actor ! (Counter.Stop)) |
||||
|
// ) |
||||
|
// actor <- actorTask |
||||
|
// actor <- fxActor |
||||
|
// _ <- Task(actor ! Counter.Increment) |
||||
|
_ <- Task { stage = appStage } |
||||
|
_ <- Task.sleep(2.seconds) |
||||
|
// loginScene <- wire[LoginScreen].render |
||||
|
// _ <- Task { |
||||
|
// // appStage.maximized = true |
||||
|
// appStage.height = 800 |
||||
|
// appStage.width = 800 |
||||
|
// appStage |
||||
|
// .scene() |
||||
|
// .setRoot( |
||||
|
// loginScene |
||||
|
// ) |
||||
|
// } |
||||
|
} yield () |
||||
|
|
||||
|
// def testActor( |
||||
|
// system: ActorSystem |
||||
|
// ): akka.actor.typed.ActorRef[Counter.Command] = { |
||||
|
// val behaviour: Behavior[Counter.Command] = |
||||
|
// Behaviors.setup(context => wire[Counter]) |
||||
|
// system.spawn( |
||||
|
// behaviour, |
||||
|
// "CounterActor", |
||||
|
// DispatcherSelector.fromConfig("javafx-dispatcher") |
||||
|
// ) |
||||
|
// } |
||||
|
|
||||
|
application.timed.runAsync( |
||||
|
new Callback[Throwable, (FiniteDuration, Unit)] { |
||||
|
|
||||
|
override def onSuccess(value: (FiniteDuration, Unit)): Unit = { |
||||
|
val (duration, _) = value |
||||
|
println( |
||||
|
s"Application started successfully in ${duration.toSeconds} seconds" |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
override def onError(e: Throwable): Unit = { |
||||
|
println("Application start failed. Reason -") |
||||
|
e.printStackTrace() |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
) |
||||
|
|
||||
|
override def stopApp() = { |
||||
|
// val stop = for { |
||||
|
// actor <- fxActor |
||||
|
// _ <- logger.info("Stopping actor counter") |
||||
|
// // _ <- Task.fromFuture { actor.ask[Counter.Value](Counter.GetValue) } |
||||
|
// t <- Task(actor ! Counter.Stop) |
||||
|
// // _ <- Task.sleep(1.second) |
||||
|
// _ <- logger.info("Counter actor stopped") |
||||
|
// } yield () |
||||
|
// stop.runAsyncAndForget |
||||
|
// // Platform.exit() |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
object MyFxAppOld { |
||||
|
def makeCounterActor( |
||||
|
system: ActorSystem[SpawnProtocol.Command], |
||||
|
logger: Logger[Task] |
||||
|
): Task[ActorRef[Counter.Command]] = { |
||||
|
import akka.actor.typed.scaladsl.AskPattern._ |
||||
|
import scala.concurrent.ExecutionContext |
||||
|
|
||||
|
implicit val timeout: Timeout = Timeout(3.seconds) |
||||
|
implicit val ec: ExecutionContext = system.executionContext |
||||
|
implicit val scheduler = system.scheduler |
||||
|
Task.fromFuture { |
||||
|
system.ask( |
||||
|
SpawnProtocol.Spawn( |
||||
|
behavior = wireWith(Counter.apply _), |
||||
|
name = "counterActor", |
||||
|
DispatcherSelector.fromConfig("javafx-dispatcher"), |
||||
|
_ |
||||
|
) |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,107 @@ |
|||||
|
package nova.monadic_sfx.ui.components.router |
||||
|
|
||||
|
import enumeratum._ |
||||
|
import io.circe.Encoder |
||||
|
import io.circe.generic.JsonCodec |
||||
|
import io.odin.Logger |
||||
|
import monix.bio.Task |
||||
|
import nova.monadic_sfx.util.IOUtils |
||||
|
import nova.monadic_sfx.util.reactive.Reducer |
||||
|
import nova.monadic_sfx.util.reactive.Store |
||||
|
import scalafx.scene.Parent |
||||
|
import scalafx.scene.control.Label |
||||
|
|
||||
|
object FXRouter { |
||||
|
|
||||
|
final case class State[P](page: P) |
||||
|
|
||||
|
@JsonCodec |
||||
|
sealed abstract class Action[T] |
||||
|
// final case object Init extends Action |
||||
|
final case class Replace[T](p: T) extends Action[T] |
||||
|
|
||||
|
type FXStore[P] = Store[Action[P], State[P]] |
||||
|
|
||||
|
// def resolver2 = resolver.lift.andThen(_.getOrElse(notFound)) |
||||
|
|
||||
|
// def resolver: PartialFunction[P <: Enum[P]][P, Parent] = { |
||||
|
// case Home => new TextField |
||||
|
// } |
||||
|
|
||||
|
} |
||||
|
|
||||
|
class FXRouter[P <: EnumEntry]( |
||||
|
)(implicit E: Encoder[P]) { |
||||
|
import FXRouter._ |
||||
|
|
||||
|
def store(initialPage: P, logger: Logger[Task]) = |
||||
|
Task.deferAction(implicit s => |
||||
|
Store.createL[Action[P], State[P]]( |
||||
|
Replace(initialPage), |
||||
|
State(initialPage), |
||||
|
Reducer.withOptionalEffects[Task, Action[P], State[P]](reducer _) |
||||
|
// Seq(actionLoggerMiddleware(logger, "RouterStore")) |
||||
|
) |
||||
|
) |
||||
|
|
||||
|
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), None) |
||||
|
} |
||||
|
|
||||
|
def render( |
||||
|
resolver: P => Task[Parent] |
||||
|
)(implicit store: FXStore[P]) = |
||||
|
store.mapEval { case (_, FXRouter.State(p)) => IOUtils.toTask(resolver(p)) } |
||||
|
|
||||
|
def link( |
||||
|
page: P, |
||||
|
store: FXStore[P] |
||||
|
) = { |
||||
|
store.onNext(Replace(page)) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
object BrainNotWorking { |
||||
|
|
||||
|
@JsonCodec |
||||
|
sealed trait Page extends EnumEntry |
||||
|
object Page extends Enum[Page] { |
||||
|
val values = findValues |
||||
|
final case object Home extends Page |
||||
|
final case class UserHome(id: Int) extends Page |
||||
|
} |
||||
|
|
||||
|
def resolver: PartialFunction[Page, Task[Parent]] = { |
||||
|
case Page.Home => |
||||
|
Task(new Label { |
||||
|
text = "HomePage" |
||||
|
}) |
||||
|
case Page.UserHome(id0) => |
||||
|
Task(new Label { |
||||
|
text = s"User Home, Id = $id0" |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
val router = new FXRouter[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) |
||||
|
// } |
@ -0,0 +1,378 @@ |
|||||
|
package nova.monadic_sfx.ui.components.todo |
||||
|
|
||||
|
import scala.concurrent.duration.FiniteDuration |
||||
|
|
||||
|
import cats.effect.concurrent.Deferred |
||||
|
import io.odin.Logger |
||||
|
import monix.bio.Task |
||||
|
import monix.catnap.ConcurrentChannel |
||||
|
import monix.catnap.ConsumerF |
||||
|
import monix.execution.Scheduler |
||||
|
import monix.reactive.Observable |
||||
|
import monix.reactive.Observer |
||||
|
import nova.monadic_sfx.implicits.FontIcon |
||||
|
import nova.monadic_sfx.implicits.IconLiteral |
||||
|
import nova.monadic_sfx.implicits.JFXListView |
||||
|
import nova.monadic_sfx.implicits.JavaFXMonixObservables._ |
||||
|
import nova.monadic_sfx.util.reactive._ |
||||
|
import scalafx.Includes._ |
||||
|
import scalafx.beans.property.StringProperty |
||||
|
import scalafx.collections.ObservableBuffer |
||||
|
import scalafx.scene.control.ContextMenu |
||||
|
import scalafx.scene.control.ListCell |
||||
|
import scalafx.scene.control.MenuItem |
||||
|
import scalafx.scene.control.SelectionMode |
||||
|
import scalafx.scene.layout.HBox |
||||
|
import scalafx.scene.text.Text |
||||
|
|
||||
|
class TodoListViewOld( |
||||
|
val listView: JFXListView[Todo] = TodoListViewOld.defaultListView, |
||||
|
val lvObs: ObservableBuffer[Todo] = ObservableBuffer.empty |
||||
|
) { |
||||
|
listView.items = lvObs |
||||
|
} |
||||
|
|
||||
|
object TodoListViewOld { |
||||
|
def defaultListView = |
||||
|
new JFXListView[Todo] { |
||||
|
contextMenu = new ContextMenu { |
||||
|
items ++= Seq( |
||||
|
new MenuItem { |
||||
|
text = "delete" |
||||
|
}, |
||||
|
new MenuItem { |
||||
|
text = "edit" |
||||
|
} |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
implicit class Operations[A](val sink: Observer[A]) extends AnyVal {} |
||||
|
|
||||
|
def defaultListView2( |
||||
|
store: MonixProSubject[ |
||||
|
TodoListComponentOld.Command, |
||||
|
(TodoListComponentOld.Command, Vector[Todo]) |
||||
|
] |
||||
|
): Task[JFXListView[Todo]] = |
||||
|
Task.deferAction(implicit s => |
||||
|
Task { |
||||
|
val todos = |
||||
|
store.map { case (_, items) => items } |
||||
|
|
||||
|
val listView = new JFXListView[Todo] { lv => |
||||
|
def selectedItems = lv.selectionModel().selectedItems.view |
||||
|
// items = todos |
||||
|
items <-- todos |
||||
|
// .map(ObservableBuffer.from(_)) |
||||
|
cellFactory = _ => |
||||
|
new ListCell[Todo] { |
||||
|
val _text = StringProperty("") |
||||
|
val _graphic = new HBox { |
||||
|
children = Seq( |
||||
|
new FontIcon { |
||||
|
iconSize = 10 |
||||
|
iconLiteral = IconLiteral.Gmi10k |
||||
|
}, |
||||
|
new Text { |
||||
|
text <== _text |
||||
|
} |
||||
|
) |
||||
|
} |
||||
|
item.onChange((_, _, todo) => { |
||||
|
println("called") |
||||
|
if (todo != null) { |
||||
|
_text() = s"${todo.id} - ${todo.content}" |
||||
|
graphic = _graphic |
||||
|
} else { |
||||
|
_text() = "" |
||||
|
graphic = null |
||||
|
} |
||||
|
}) |
||||
|
|
||||
|
} |
||||
|
selectionModel().selectionMode = SelectionMode.Multiple |
||||
|
contextMenu = new ContextMenu { |
||||
|
items ++= Seq( |
||||
|
new MenuItem { |
||||
|
text = "Add" |
||||
|
onAction = _ => |
||||
|
store.sink |
||||
|
.onNext(TodoListComponentOld.Add(Todo(1, "blah3"))) |
||||
|
}, |
||||
|
new MenuItem { |
||||
|
text = "Delete" |
||||
|
// onAction = _ => |
||||
|
// for { |
||||
|
// items <- Option(lv.selectionModel().selectedItems) |
||||
|
// _ <- Some(items.foreach(item => deleteSub.onNext(item))) |
||||
|
// } yield () |
||||
|
onAction = _ => |
||||
|
selectedItems |
||||
|
.map(todo => TodoListComponentOld.Delete(todo.id)) |
||||
|
.foreach(store.sink.onNext) |
||||
|
}, |
||||
|
new MenuItem { |
||||
|
text = "Edit" |
||||
|
// onAction = _ => |
||||
|
// Option(lv.selectionModel().selectedItems).foreach(items => |
||||
|
// items.foreach(item => editSub.onNext(item)) |
||||
|
// ) |
||||
|
} |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
listView |
||||
|
} |
||||
|
) |
||||
|
|
||||
|
} |
||||
|
|
||||
|
private[todo] class TodoListComponentImpure( |
||||
|
todoListView: TodoListViewOld |
||||
|
) { |
||||
|
def add(todo: Todo) = todoListView.lvObs += todo |
||||
|
def find(id: Int) = todoListView.lvObs.find(_.id == id) |
||||
|
def edit(id: Int, content: String) = |
||||
|
find(id) |
||||
|
.map(todo => |
||||
|
todoListView.lvObs.replaceAll( |
||||
|
todo, |
||||
|
Todo(id, content) |
||||
|
) |
||||
|
) |
||||
|
.getOrElse(false) |
||||
|
} |
||||
|
|
||||
|
class TodoListOps private ( |
||||
|
props: TodoListOps.Props |
||||
|
) { |
||||
|
import props._ |
||||
|
// lazy val internal = new TodoListComponentImpure(todoListView) |
||||
|
|
||||
|
// def add(todo: Todo) = Task(internal.add(todo)) |
||||
|
def add(todo: Todo) = Task(todoListView.lvObs += todo).executeOn(fxScheduler) |
||||
|
def find(id: Int) = |
||||
|
Task(todoListView.lvObs.find(_.id == id)).executeOn(fxScheduler) |
||||
|
def delete(id: Int) = |
||||
|
(for { |
||||
|
mbTodo <- find(id) |
||||
|
_ <- logger.debug(mbTodo.toString()) |
||||
|
res <- Task( |
||||
|
mbTodo.map(todo => todoListView.lvObs.removeAll(todo)) |
||||
|
) |
||||
|
_ <- logger.debug(todoListView.lvObs.toString()) |
||||
|
} yield res.getOrElse(false)).executeOn(fxScheduler) |
||||
|
def edit(id: Int, content: String) = |
||||
|
(for { |
||||
|
mbTodo <- find(id) |
||||
|
res <- Task( |
||||
|
mbTodo.map(todo => |
||||
|
todoListView.lvObs.replaceAll( |
||||
|
todo, |
||||
|
Todo(id, content) |
||||
|
) |
||||
|
) |
||||
|
) |
||||
|
} yield res.getOrElse(false)).executeOn(fxScheduler) |
||||
|
} |
||||
|
|
||||
|
object TodoListOps { |
||||
|
class Props( |
||||
|
val todoListView: TodoListViewOld, |
||||
|
val fxScheduler: Scheduler, |
||||
|
val logger: Logger[Task] |
||||
|
) { |
||||
|
def create = Task(new TodoListOps(this)) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
object TodoListComponentOld { |
||||
|
sealed trait Complete |
||||
|
object Complete extends Complete |
||||
|
|
||||
|
sealed trait Command |
||||
|
// sealed trait Tell extends Command |
||||
|
// sealed abstract class Ask extends Command |
||||
|
|
||||
|
case class Add(todo: Todo) extends Command |
||||
|
case class Find(id: Int, result: Deferred[Task, Option[Todo]]) extends Command |
||||
|
case class Edit(id: Int, content: String) extends Command |
||||
|
case class Delete(id: Int) extends Command |
||||
|
// private case class FindInternal(id: Int, result: Deferred[Task, Todo]) |
||||
|
// extends Ask |
||||
|
|
||||
|
def reducer( |
||||
|
state: Vector[Todo], |
||||
|
action: TodoListComponentOld.Command |
||||
|
) = |
||||
|
action match { |
||||
|
case Add(todo) => state :+ todo |
||||
|
// case Find(id, result) => |
||||
|
case Edit(id, content) => state |
||||
|
case Delete(id) => |
||||
|
state.filterNot(_.id == id) |
||||
|
case _ => state |
||||
|
} |
||||
|
|
||||
|
val store = |
||||
|
Store |
||||
|
.createL[TodoListComponentOld.Command, Vector[Todo]]( |
||||
|
TodoListComponentOld.Delete(0), |
||||
|
Vector.empty[Todo], |
||||
|
(s: Vector[Todo], a: TodoListComponentOld.Command) => |
||||
|
reducer(s, a) -> Observable.empty |
||||
|
) |
||||
|
|
||||
|
class Props( |
||||
|
val todoListView: TodoListViewOld, |
||||
|
val fxScheduler: Scheduler, |
||||
|
val channel: ConcurrentChannel[ |
||||
|
Task, |
||||
|
TodoListComponentOld.Complete, |
||||
|
TodoListComponentOld.Command |
||||
|
], |
||||
|
val logger: Logger[Task] |
||||
|
) { |
||||
|
|
||||
|
def create = |
||||
|
for { |
||||
|
todoListOps <- |
||||
|
new TodoListOps.Props(todoListView, fxScheduler, logger).create |
||||
|
consumer = channel.consume.use(ref => todoConsumer(ref, todoListOps)) |
||||
|
_ <- consumer.startAndForget |
||||
|
} yield (new TodoListComponentOld(this)) |
||||
|
|
||||
|
private def todoConsumer( |
||||
|
consumer: ConsumerF[Task, Complete, Command], |
||||
|
ops: TodoListOps |
||||
|
): Task[Unit] = |
||||
|
consumer.pull |
||||
|
.flatMap { |
||||
|
case Left(complete) => logger.info("Received `Complete` event") |
||||
|
case Right(command) => |
||||
|
logger.debug(s"Received command $command") >> |
||||
|
(command match { |
||||
|
// case t: Tell => |
||||
|
// t match { |
||||
|
// case Add(todo) => ops.add(todo) |
||||
|
// case _ => Task.unit |
||||
|
// } |
||||
|
case Add(todo) => ops.add(todo) |
||||
|
// case Find(id) => |
||||
|
// for { |
||||
|
// p <- Deferred[Task, Todo] |
||||
|
// _ <- channel.push(FindInternal(id, p)) |
||||
|
// res <- p.get |
||||
|
// } yield (res) |
||||
|
case Find(id, result) => |
||||
|
for { |
||||
|
mbTodo <- ops.find(id) |
||||
|
} yield result.complete(mbTodo) |
||||
|
// case _ => Task.unit |
||||
|
|
||||
|
case Delete(id) => ops.delete(id) |
||||
|
|
||||
|
case Edit(id, content) => ops.edit(id, content) |
||||
|
}) |
||||
|
} |
||||
|
.flatMap(_ => todoConsumer(consumer, ops)) |
||||
|
|
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
class TodoListComponentOld(props: TodoListComponentOld.Props) { |
||||
|
import props._ |
||||
|
import TodoListComponentOld._ |
||||
|
|
||||
|
def send(command: Command) = channel.push(command) |
||||
|
|
||||
|
def ask[T]( |
||||
|
commandBuilder: Deferred[Task, T] => Command |
||||
|
)(implicit timeout: FiniteDuration) = |
||||
|
for { |
||||
|
p <- Deferred[Task, T] |
||||
|
_ <- channel.push(commandBuilder(p)) |
||||
|
res <- p.get.timeout(timeout) |
||||
|
} yield res |
||||
|
|
||||
|
def stop = channel.halt(Complete) |
||||
|
|
||||
|
// import scala.concurrent.duration._ |
||||
|
// val x = ask(FindInternal(0, _))(2.seconds) |
||||
|
|
||||
|
} |
||||
|
|
||||
|
// object TodoListComponent { |
||||
|
// sealed trait Complete |
||||
|
// object Complete extends Complete |
||||
|
|
||||
|
// sealed trait Command |
||||
|
|
||||
|
// class Props( |
||||
|
// val todoListView: TodoListView, |
||||
|
// val fxScheduler: Scheduler, |
||||
|
// val channel: ConcurrentChannel[ |
||||
|
// Task, |
||||
|
// TodoListComponent.Complete, |
||||
|
// TodoListComponent.Command |
||||
|
// ] |
||||
|
// ) { |
||||
|
// def create = Task(new TodoListComponent(this)) |
||||
|
// } |
||||
|
// } |
||||
|
|
||||
|
// class TodoListComponent(props: TodoListComponent.Props) { |
||||
|
// import props._ |
||||
|
// import TodoListComponent._ |
||||
|
// def init = |
||||
|
// for { |
||||
|
// todoListOps <- new TodoListOps.Props(todoListView, fxScheduler).create |
||||
|
// consumer = channel.consume.use(ref => todoConsumer(ref, todoListOps)) |
||||
|
// _ <- consumer.startAndForget |
||||
|
// } yield () |
||||
|
|
||||
|
// def send(command: Command) = channel.push(command) |
||||
|
|
||||
|
// def todoConsumer( |
||||
|
// consumer: ConsumerF[Task, Complete, Command], |
||||
|
// ops: TodoListOps |
||||
|
// ) = |
||||
|
// consumer.pull.flatMap { |
||||
|
// case Left(value) => Task.unit |
||||
|
// case Right(value) => Task.unit |
||||
|
// } |
||||
|
// } |
||||
|
|
||||
|
// def askHandler( |
||||
|
// channel: ConcurrentChannel[ |
||||
|
// Task, |
||||
|
// TodoListComponent.Complete, |
||||
|
// TodoListComponent.Command |
||||
|
// ], |
||||
|
// consumer: ConsumerF[Task, Complete, Command], |
||||
|
// ops: TodoListOps |
||||
|
// ) = |
||||
|
// consumer.pull.flatMap { |
||||
|
// case Left(complete) => Task.unit |
||||
|
// case Right(command) => |
||||
|
// command match { |
||||
|
// case a: Ask => |
||||
|
// a match { |
||||
|
// case Find(id) => |
||||
|
// for { |
||||
|
// p <- Deferred[Task, Todo] |
||||
|
// _ <- channel.push(FindInternal(id, p)) |
||||
|
// res <- p.get |
||||
|
// } yield (res) |
||||
|
// case FindInternal(id, result) => |
||||
|
// for { |
||||
|
// mb <- ops.find(id) |
||||
|
// } yield result.complete(mb.get) |
||||
|
// case _ => Task.unit |
||||
|
// } |
||||
|
// case _ => Task.unit |
||||
|
// } |
||||
|
// } |
@ -0,0 +1,89 @@ |
|||||
|
package nova.monadic_sfx.ui.components.todo |
||||
|
|
||||
|
import com.softwaremill.quicklens._ |
||||
|
import io.circe.generic.JsonCodec |
||||
|
import io.odin.Logger |
||||
|
import monix.bio.Task |
||||
|
import nova.monadic_sfx.util.reactive.Middlewares.actionLoggerMiddleware |
||||
|
import nova.monadic_sfx.util.reactive.Reducer |
||||
|
import nova.monadic_sfx.util.reactive.Store |
||||
|
|
||||
|
case class Todo(id: Int, content: String) |
||||
|
|
||||
|
object TodoListStore { |
||||
|
|
||||
|
@JsonCodec |
||||
|
sealed trait Action |
||||
|
case object Init extends Action |
||||
|
case class Add(content: String) extends Action |
||||
|
case class Edit(id: Int, content: String) extends Action |
||||
|
case class Delete(id: Int) extends Action |
||||
|
|
||||
|
private case class InternalAdd(content: String) extends Action |
||||
|
private case object End extends Action |
||||
|
|
||||
|
case class State(todos: Vector[Todo], counter: Int) |
||||
|
|
||||
|
def reducer(logger: Logger[Task])( |
||||
|
state: State, |
||||
|
action: Action |
||||
|
): (State, Option[Task[Action]]) = |
||||
|
action match { |
||||
|
case Init => (state, None) |
||||
|
case Add(content) => |
||||
|
val nextAction = Some(for { |
||||
|
// _ <- logger.debug(s"Received $content") |
||||
|
res <- Task.pure(InternalAdd(content)) |
||||
|
} yield res) |
||||
|
(state, nextAction) |
||||
|
case Edit(_id, content) => |
||||
|
val condition: Todo => Boolean = _.id == _id |
||||
|
val nextState = state |
||||
|
.modify(_.todos.eachWhere(condition)) |
||||
|
.using(_.copy(content = content)) |
||||
|
(nextState, None) |
||||
|
case Delete(id) => |
||||
|
(state.copy(state.todos.filterNot(_.id == id)), None) |
||||
|
|
||||
|
case InternalAdd(content) => |
||||
|
val nextState = state.copy( |
||||
|
todos = state.todos :+ Todo(state.counter, content), |
||||
|
counter = state.counter + 1 |
||||
|
) |
||||
|
(nextState, Some(logger.debug(s"Received $content") >> Task.pure(End))) |
||||
|
case End => (state, None) |
||||
|
} |
||||
|
|
||||
|
def apply(logger: Logger[Task]) = |
||||
|
Task.deferAction(implicit s => |
||||
|
for { |
||||
|
logMware <- actionLoggerMiddleware[Action, State](logger, "TodoStore") |
||||
|
store <- |
||||
|
Store |
||||
|
.createL[Action, State]( |
||||
|
Init, |
||||
|
State(Vector.empty[Todo], 0), |
||||
|
Reducer.withOptionalEffects(reducer(logger) _), |
||||
|
Seq( |
||||
|
// actionLoggerMiddleware(logger, "TodoStore2") |
||||
|
logMware |
||||
|
) |
||||
|
) |
||||
|
} yield (store) |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
// Task.deferAction(implicit s => |
||||
|
// Store |
||||
|
// .createJsonL[Action, State]( |
||||
|
// Init, |
||||
|
// State(Vector.empty[Todo], 0), |
||||
|
// Reducer.withOptionalEffects(reducer(logger) _), |
||||
|
// "TodoStore", |
||||
|
// logger |
||||
|
// // Seq( |
||||
|
// // actionLoggerMiddleware(logger, "TodoStore") |
||||
|
// // // actionLoggerMiddleware(logger, "TodoStore2") |
||||
|
// // ) |
||||
|
// ) |
||||
|
// ) |
@ -0,0 +1,134 @@ |
|||||
|
package nova.monadic_sfx.ui.components.todo |
||||
|
|
||||
|
import monix.bio.Task |
||||
|
import monix.execution.cancelables.CompositeCancelable |
||||
|
import monix.{eval => me} |
||||
|
import nova.monadic_sfx.implicits.FontIcon |
||||
|
import nova.monadic_sfx.implicits.IconLiteral |
||||
|
import nova.monadic_sfx.implicits.JFXButton |
||||
|
import nova.monadic_sfx.implicits.JFXListView |
||||
|
import nova.monadic_sfx.implicits.JFXTextField |
||||
|
import nova.monadic_sfx.implicits.JavaFXMonixObservables._ |
||||
|
import nova.monadic_sfx.implicits.MenuItem |
||||
|
import nova.monadic_sfx.util.reactive._ |
||||
|
import org.gerweck.scalafx.util._ |
||||
|
import scalafx.Includes._ |
||||
|
import scalafx.beans.property.ObjectProperty |
||||
|
import scalafx.beans.property.StringProperty |
||||
|
import scalafx.geometry.Insets |
||||
|
import scalafx.scene.Node |
||||
|
import scalafx.scene.control.ContextMenu |
||||
|
import scalafx.scene.control.ListCell |
||||
|
import scalafx.scene.control.SelectionMode |
||||
|
import scalafx.scene.layout.HBox |
||||
|
import scalafx.scene.text.Text |
||||
|
|
||||
|
object TodoListView { |
||||
|
def apply( |
||||
|
store: MonixProSubject[ |
||||
|
TodoListStore.Action, |
||||
|
(TodoListStore.Action, TodoListStore.State) |
||||
|
] |
||||
|
): Task[Node] = |
||||
|
Task.deferAction(implicit s => |
||||
|
Task { |
||||
|
val cc = CompositeCancelable() |
||||
|
val todos = |
||||
|
store.map { case (_, state) => state.todos } |
||||
|
// Todo(-1, "").some |
||||
|
val _selectedItems = ObjectProperty(Seq.empty[Todo]) |
||||
|
|
||||
|
new HBox { |
||||
|
padding = Insets(5) |
||||
|
val _content = StringProperty("") |
||||
|
children = Seq( |
||||
|
new JFXTextField { |
||||
|
text ==> _content |
||||
|
}, |
||||
|
new JFXListView[Todo] { |
||||
|
def selectedItems = selectionModel().selectedItems.view |
||||
|
|
||||
|
selectionModel().selectionMode = SelectionMode.Multiple |
||||
|
selectionModel().selectedItems.observableSeqValue ==> _selectedItems |
||||
|
|
||||
|
cc += items <-- todos |
||||
|
|
||||
|
val emptyCell = ObjectProperty(new HBox) |
||||
|
cellFactory = _ => |
||||
|
new ListCell[Todo] { |
||||
|
val _text = StringProperty("") |
||||
|
val _graphic = ObjectProperty( |
||||
|
new HBox { |
||||
|
children = Seq( |
||||
|
new FontIcon { |
||||
|
iconSize = 10 |
||||
|
iconLiteral = IconLiteral.Gmi10k |
||||
|
}, |
||||
|
new Text { |
||||
|
text <== _text |
||||
|
} |
||||
|
) |
||||
|
} |
||||
|
) |
||||
|
|
||||
|
item.asOption.map( |
||||
|
_.fold("")(todo => s"${todo.id} - ${todo.content}") |
||||
|
) ==> _text |
||||
|
|
||||
|
graphic <== item.asOption.flatMap( |
||||
|
_.fold(emptyCell)(_ => _graphic) |
||||
|
) |
||||
|
|
||||
|
} |
||||
|
|
||||
|
contextMenu = new ContextMenu { |
||||
|
items ++= Seq( |
||||
|
new MenuItem { |
||||
|
text = "Add" |
||||
|
// obsAction.useLazyEval(TodoListStore.Add("blah3")) --> store |
||||
|
}, |
||||
|
new MenuItem { |
||||
|
text = "Delete" |
||||
|
obsAction.useIterableEval(_ => |
||||
|
selectedItems |
||||
|
.map(todo => TodoListStore.Delete(todo.id)) |
||||
|
.toList |
||||
|
) --> store |
||||
|
|
||||
|
// obsAction.split( |
||||
|
// _.useLazyEval(me.Task(TodoListStore.Delete(0))) --> store, |
||||
|
// _.useLazyEval(me.Task(TodoListStore.Delete(0))) --> store, |
||||
|
// _.useLazyEval(me.Task(TodoListStore.Delete(0))) --> store |
||||
|
// ) |
||||
|
}, |
||||
|
new MenuItem { |
||||
|
text = "Edit" |
||||
|
} |
||||
|
) |
||||
|
} |
||||
|
}, |
||||
|
new JFXButton { |
||||
|
text = "Add" |
||||
|
// disable <== _selectedItems.map(_.length > 0) |
||||
|
obsAction |
||||
|
.useLazyEval(me.Task(TodoListStore.Add(_content()))) --> store |
||||
|
}, |
||||
|
new JFXButton { |
||||
|
text = "Edit" |
||||
|
disable <== _selectedItems.map(_.length > 1) |
||||
|
obsAction.useLazyEval( |
||||
|
me.Task( |
||||
|
TodoListStore.Edit( |
||||
|
_selectedItems |
||||
|
.map(_.headOption.map(_.id).getOrElse(-1)) |
||||
|
.value, |
||||
|
_content() |
||||
|
) |
||||
|
) |
||||
|
) --> store |
||||
|
} |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
) |
||||
|
} |
@ -0,0 +1,95 @@ |
|||||
|
package nova.monadic_sfx.ui.controller |
||||
|
|
||||
|
import animatefx.animation.AnimationFX |
||||
|
import animatefx.animation.Bounce |
||||
|
import animatefx.animation.FadeIn |
||||
|
import animatefx.util.{SequentialAnimationFX => SeqFX} |
||||
|
import cats.effect.Sync |
||||
|
import monix.eval.Task |
||||
|
import nova.monadic_sfx.implicits.FontIcon |
||||
|
import nova.monadic_sfx.implicits.IconLiteral |
||||
|
import nova.monadic_sfx.implicits.JFXButton |
||||
|
import nova.monadic_sfx.implicits.JFXListView |
||||
|
import nova.monadic_sfx.implicits.JFXTextArea |
||||
|
import nova.monadic_sfx.implicits.JFXTextField |
||||
|
import nova.monadic_sfx.ui.components.todo.TodoListComponentOld |
||||
|
import scalafx.collections.ObservableBuffer |
||||
|
import scalafx.scene.control.Label |
||||
|
import scalafx.scene.layout.HBox |
||||
|
import scalafx.scene.paint.Color |
||||
|
|
||||
|
class TodoController(todoListComponent: TodoListComponentOld) { |
||||
|
import AnimFX._ |
||||
|
def root = |
||||
|
new HBox { |
||||
|
children = Seq( |
||||
|
new Label { |
||||
|
text = "Todo" |
||||
|
}, |
||||
|
new JFXButton { |
||||
|
text = " Click me" |
||||
|
onAction = _ => { |
||||
|
new FadeIn(new Label("hello")).play() |
||||
|
val anim = new SeqFX( |
||||
|
new Bounce(new Label("hello")), |
||||
|
new FadeIn(new Label("hello")) |
||||
|
).toAnimFX[Task] |
||||
|
for { |
||||
|
_ <- Task.unit |
||||
|
_ <- anim.playL |
||||
|
} yield () |
||||
|
} |
||||
|
}, |
||||
|
new JFXTextField { |
||||
|
text = "hello" |
||||
|
labelFloat = true |
||||
|
}, |
||||
|
new JFXListView[String] { |
||||
|
items = ObservableBuffer("hello") |
||||
|
}, |
||||
|
new JFXTextArea { |
||||
|
prefWidth = 400 |
||||
|
text = "blah" |
||||
|
labelFloat = true |
||||
|
}, |
||||
|
new FontIcon { |
||||
|
iconSize = 50 |
||||
|
iconColor = Color.Black |
||||
|
iconLiteral = IconLiteral.Gmi10k |
||||
|
} |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
// def test() = { |
||||
|
// new TextField("hello").lookup() |
||||
|
// } |
||||
|
|
||||
|
// def test2() = { |
||||
|
// new Label().lookup() |
||||
|
// } |
||||
|
} |
||||
|
|
||||
|
abstract class AnimFX[F[_]: Sync] { |
||||
|
def playL: F[Unit] |
||||
|
} |
||||
|
object AnimFX { |
||||
|
implicit class AnimationFXExt(anim: AnimationFX) { |
||||
|
def toAnimFX[F[_]](implicit |
||||
|
F: Sync[F] |
||||
|
) = |
||||
|
new AnimFX[F] { |
||||
|
override def playL: F[Unit] = |
||||
|
F.delay(anim.play()) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
implicit class SeqAnimationFXExt(anim: SeqFX) { |
||||
|
def toAnimFX[F[_]](implicit |
||||
|
F: Sync[F] |
||||
|
) = |
||||
|
new AnimFX[F] { |
||||
|
override def playL: F[Unit] = |
||||
|
F.delay(anim.play()) |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,21 @@ |
|||||
|
package nova.monadic_sfx.util |
||||
|
|
||||
|
import cats.arrow.FunctionK |
||||
|
import monix.bio.IO |
||||
|
|
||||
|
object IOUtils { |
||||
|
def toIO[T](task: monix.eval.Task[T]) = |
||||
|
IO.deferAction(implicit s => IO.from(task)) |
||||
|
|
||||
|
def toTask[T](bio: monix.bio.IO[Throwable, T]) = |
||||
|
monix.eval.Task.deferAction(implicit s => bio.to[monix.eval.Task]) |
||||
|
|
||||
|
val ioTaskMapk = |
||||
|
new FunctionK[monix.eval.Task, monix.bio.Task] { |
||||
|
|
||||
|
override def apply[A]( |
||||
|
fa: monix.eval.Task[A] |
||||
|
): monix.bio.Task[A] = toIO(fa) |
||||
|
|
||||
|
} |
||||
|
} |
@ -0,0 +1,63 @@ |
|||||
|
package nova.monadic_sfx.util |
||||
|
|
||||
|
import scalafx.beans.property.ObjectProperty |
||||
|
import scalafx.beans.property.ReadOnlyObjectProperty |
||||
|
import scalafx.beans.value.ObservableValue |
||||
|
|
||||
|
object Misc { |
||||
|
|
||||
|
implicit final class MyRichObservable[A, C](val self: ObservableValue[A, C]) |
||||
|
extends AnyVal { |
||||
|
def filter(f: A => Boolean): ReadOnlyObjectProperty[A] = |
||||
|
Method.filter(self)(f) |
||||
|
def filterNull: ReadOnlyObjectProperty[A] = Method.filterNull(self) |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
object Method { |
||||
|
type Observable[A] = ObservableValue[A, _] |
||||
|
|
||||
|
def filter[B]( |
||||
|
a: Observable[B] |
||||
|
)(f: B => Boolean): ReadOnlyObjectProperty[B] = { |
||||
|
|
||||
|
val prop = ObjectProperty[B](a.value) |
||||
|
|
||||
|
def changeHandler() = |
||||
|
prop.synchronized { |
||||
|
if (f(a.value)) { |
||||
|
prop.value = a.value |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
a onChange changeHandler() |
||||
|
prop |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Simply creates a new observable that mirrors the source observable but |
||||
|
* doesn't emit null values. JavaFX likes to work with null values in scene |
||||
|
* nodes/properties (shrugs) and observables by default emit null values |
||||
|
* that can cause crashes. ScalaFX does not offer any *fixes* for this |
||||
|
* |
||||
|
* @param a |
||||
|
* @return |
||||
|
*/ |
||||
|
def filterNull[B]( |
||||
|
a: Observable[B] |
||||
|
): ReadOnlyObjectProperty[B] = { |
||||
|
|
||||
|
val prop = ObjectProperty[B](a.value) |
||||
|
|
||||
|
def changeHandler() = |
||||
|
prop.synchronized { |
||||
|
if (a.value != null) { |
||||
|
prop.value = a.value |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
a onChange changeHandler() |
||||
|
prop |
||||
|
} |
||||
|
|
||||
|
} |
@ -0,0 +1,75 @@ |
|||||
|
package nova.monadic_sfx.util.reactive |
||||
|
|
||||
|
import java.time.LocalDateTime |
||||
|
|
||||
|
import io.circe.Encoder |
||||
|
import io.circe.Printer |
||||
|
import io.circe.generic.JsonCodec |
||||
|
import io.circe.syntax._ |
||||
|
import io.odin.Logger |
||||
|
import io.odin.LoggerMessage |
||||
|
import io.odin.formatter.options.PositionFormat |
||||
|
import io.odin.formatter.options.ThrowableFormat |
||||
|
import io.odin.meta.Render |
||||
|
import monix.bio.Task |
||||
|
import monix.reactive.Observable |
||||
|
// object Middleware { |
||||
|
// def apply[A,M,T](ob: Observable[(A,M)], cb: (A,M) => T): Observable[(A,M)] = ob |
||||
|
// } |
||||
|
|
||||
|
@JsonCodec |
||||
|
final case class StoreInfo[A]( |
||||
|
name: String, |
||||
|
action: A, |
||||
|
time: LocalDateTime = LocalDateTime.now() |
||||
|
) |
||||
|
|
||||
|
object StoreInfo { |
||||
|
val printer = Printer.noSpaces |
||||
|
implicit def render[T: Encoder]: Render[StoreInfo[T]] = |
||||
|
new Render[StoreInfo[T]] { |
||||
|
override def render(m: StoreInfo[T]): String = printer.print(m.asJson) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
object Middlewares { |
||||
|
|
||||
|
// val encoder: Encoder[LoggerMessage] = |
||||
|
// Encoder.forProduct1("message")(m => m.message.value) |
||||
|
|
||||
|
val format = create(ThrowableFormat.Default, PositionFormat.Full) |
||||
|
|
||||
|
def create( |
||||
|
throwableFormat: ThrowableFormat, |
||||
|
positionFormat: PositionFormat |
||||
|
): io.odin.formatter.Formatter = { |
||||
|
// val encoder: Encoder[LoggerMessage] = |
||||
|
// Encoder.forProduct1("message")(m => m.message.value) |
||||
|
(msg: LoggerMessage) => msg.message.value |
||||
|
} |
||||
|
|
||||
|
def actionStateLoggerMiddleware[A, M]( |
||||
|
logger: Logger[Task] |
||||
|
): Task[Middleware[A, M]] = |
||||
|
Task.deferAction(implicit s => |
||||
|
Task((obs: Observable[(A, M)]) => |
||||
|
obs.doOnNextF { |
||||
|
case (a, m) => |
||||
|
logger.debug(s"Received action $a with state $m") |
||||
|
} |
||||
|
) |
||||
|
) |
||||
|
|
||||
|
def actionLoggerMiddleware[A: Encoder, M]( |
||||
|
logger: Logger[Task], |
||||
|
name: String |
||||
|
): Task[Middleware[A, M]] = |
||||
|
Task.deferAction(implicit s => |
||||
|
Task((obs: Observable[(A, M)]) => |
||||
|
obs.doOnNextF { |
||||
|
case (a, _) => |
||||
|
logger.debug(StoreInfo(name, a)) |
||||
|
} |
||||
|
) |
||||
|
) |
||||
|
} |
@ -0,0 +1,23 @@ |
|||||
|
package nova.monadic_sfx.util.reactive |
||||
|
|
||||
|
import scala.concurrent.Future |
||||
|
|
||||
|
import monix.execution.Ack |
||||
|
import monix.execution.Cancelable |
||||
|
import monix.reactive.Observable |
||||
|
import monix.reactive.Observer |
||||
|
import monix.reactive.observers.Subscriber |
||||
|
|
||||
|
object MonixProSubject { |
||||
|
def from[I, O]( |
||||
|
observer: Observer[I], |
||||
|
observable: Observable[O] |
||||
|
): MonixProSubject[I, O] = |
||||
|
new Observable[O] with Observer[I] { |
||||
|
override def onNext(elem: I): Future[Ack] = observer.onNext(elem) |
||||
|
override def onError(ex: Throwable): Unit = observer.onError(ex) |
||||
|
override def onComplete(): Unit = observer.onComplete() |
||||
|
override def unsafeSubscribeFn(subscriber: Subscriber[O]): Cancelable = |
||||
|
observable.unsafeSubscribeFn(subscriber) |
||||
|
} |
||||
|
} |
@ -0,0 +1,39 @@ |
|||||
|
package nova.monadic_sfx.util.reactive |
||||
|
|
||||
|
import cats.implicits._ |
||||
|
import monix.reactive.Observable |
||||
|
import monix.reactive.ObservableLike |
||||
|
|
||||
|
object Reducer { |
||||
|
|
||||
|
/** |
||||
|
* Creates a Reducer which yields a new State, as-well as an Observable of Effects |
||||
|
* Effects are Actions which will be executed after the Action that caused them to occur. |
||||
|
* This is accomplished by subscribing to the Effects Observable within the stores scan loop. |
||||
|
* |
||||
|
* CAUTION: There is currently a bug which causes the Effect-States to emit, |
||||
|
* before the State of the action that caused the effects is emitted. |
||||
|
* However, this only effects immediate emissions of the Effects Observable, delayed emissions should be fine. |
||||
|
* @param f The Reducing Function returning the (Model, Effects) tuple. |
||||
|
*/ |
||||
|
def withEffects[F[_]: ObservableLike, A, M]( |
||||
|
f: (M, A) => (M, F[A]) |
||||
|
): Reducer[A, M] = (s: M, a: A) => f(s, a).map(ObservableLike[F].apply) |
||||
|
|
||||
|
/** |
||||
|
* Creates a reducer which just transforms the state, without additional effects. |
||||
|
*/ |
||||
|
def apply[A, M](f: (M, A) => M): Reducer[A, M] = |
||||
|
(s: M, a: A) => f(s, a) -> Observable.empty |
||||
|
|
||||
|
/** |
||||
|
* Creates a Reducer with an optional effect. |
||||
|
*/ |
||||
|
def withOptionalEffects[F[_]: ObservableLike, A, M]( |
||||
|
f: (M, A) => (M, Option[F[A]]) |
||||
|
): Reducer[A, M] = |
||||
|
(s: M, a: A) => |
||||
|
f(s, a).map( |
||||
|
_.fold[Observable[A]](Observable.empty)(ObservableLike[F].apply) |
||||
|
) |
||||
|
} |
@ -0,0 +1,97 @@ |
|||||
|
package nova.monadic_sfx.util.reactive |
||||
|
|
||||
|
import io.circe.Encoder |
||||
|
import io.odin.Logger |
||||
|
import monix.bio.Task |
||||
|
import monix.reactive.OverflowStrategy |
||||
|
import monix.reactive.subjects.ConcurrentSubject |
||||
|
|
||||
|
object Store { |
||||
|
def createL[A, M]( |
||||
|
initialAction: A, |
||||
|
initialState: M, |
||||
|
reducer: Reducer[A, M], |
||||
|
middlewares: Seq[Middleware[A, M]] = Seq.empty, |
||||
|
overflowStrategy: OverflowStrategy.Synchronous[A] = |
||||
|
OverflowStrategy.DropOld(50) |
||||
|
): Task[Store[A, M]] = |
||||
|
Task.deferAction { implicit s => |
||||
|
Task { |
||||
|
val subject = ConcurrentSubject.publish[A](overflowStrategy) |
||||
|
|
||||
|
val fold: ((A, M), A) => (A, M) = { |
||||
|
case ((_, state), action) => { |
||||
|
val (newState, effects) = reducer(state, action) |
||||
|
|
||||
|
effects.subscribe(subject.onNext _) |
||||
|
|
||||
|
action -> newState |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
val obs = subject |
||||
|
.scan[(A, M)](initialAction -> initialState)(fold) |
||||
|
.behavior(initialAction -> initialState) |
||||
|
.refCount |
||||
|
|
||||
|
val res = middlewares.foldLeft(obs) { |
||||
|
case (obs, middleware) => middleware(obs) |
||||
|
} |
||||
|
|
||||
|
MonixProSubject.from( |
||||
|
subject, |
||||
|
res |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
def createJsonL[A: Encoder, M]( |
||||
|
initialAction: A, |
||||
|
initialState: M, |
||||
|
reducer: Reducer[A, M], |
||||
|
storeName: String, |
||||
|
logger: Logger[Task], |
||||
|
middlewares: Seq[Middleware[A, M]] = Seq.empty, |
||||
|
overflowStrategy: OverflowStrategy.Synchronous[A] = |
||||
|
OverflowStrategy.DropOld(50) |
||||
|
): Task[Store[A, M]] = |
||||
|
Task.deferAction { implicit s => |
||||
|
Task { |
||||
|
val subject = ConcurrentSubject.publish[A](overflowStrategy) |
||||
|
|
||||
|
val fold: ((A, M), A) => Task[(A, M)] = { |
||||
|
case ((_, state), action) => |
||||
|
Task { |
||||
|
val (newState, effects) = reducer(state, action) |
||||
|
|
||||
|
effects.subscribe(subject.onNext _) |
||||
|
|
||||
|
action -> newState |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
val obs = subject |
||||
|
.doOnNextF(action => |
||||
|
logger.debug( |
||||
|
StoreInfo(storeName, action) |
||||
|
) // .executeOn(Scheduler.global) |
||||
|
) |
||||
|
// .doOnNextF(action => Coeval(println(action))) |
||||
|
.scanEvalF[Task, (A, M)](Task.pure(initialAction -> initialState))( |
||||
|
fold |
||||
|
) |
||||
|
.behavior(initialAction -> initialState) |
||||
|
.refCount |
||||
|
|
||||
|
// val res = middlewares.foldLeft(obs) { |
||||
|
// case (obs, middleware) => middleware(obs) |
||||
|
// } |
||||
|
|
||||
|
MonixProSubject.from( |
||||
|
subject, |
||||
|
obs |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
@ -0,0 +1,18 @@ |
|||||
|
package nova.monadic_sfx.util |
||||
|
|
||||
|
import monix.reactive.Observable |
||||
|
import monix.reactive.Observer |
||||
|
|
||||
|
package object reactive { |
||||
|
type MonixProSubject[-I, +O] = Observable[O] with Observer[I] |
||||
|
type Middleware[A, M] = Observable[(A, M)] => Observable[(A, M)] |
||||
|
type Store[A, M] = MonixProSubject[A, (A, M)] |
||||
|
|
||||
|
/** |
||||
|
* A Function that applies an Action onto the Stores current state. |
||||
|
* @param reducer The reducing function |
||||
|
* @tparam A The Action Type |
||||
|
* @tparam M The Model Type |
||||
|
*/ |
||||
|
type Reducer[A, M] = (M, A) => (M, Observable[A]) |
||||
|
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue