9 Commits

Author SHA1 Message Date
Rohan Sircar c59d48f6ec Added split combinator to actionObservable 3 years ago
Rohan Sircar 935ca358e6 Many changes 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 3 years ago
Rohan Sircar 536f1b0af3 Added scalafx wrappers for jfoenix and ikonli 3 years ago
  1. 2
      .gitignore
  2. 61
      build.sbt
  3. 2
      project/build.properties
  4. 1
      project/plugin.sbt
  5. 53
      src/main/scala/nova/monadic_sfx/Main.scala
  6. 189
      src/main/scala/nova/monadic_sfx/MainApp.scala
  7. 38
      src/main/scala/nova/monadic_sfx/MainModule.scala
  8. 11
      src/main/scala/nova/monadic_sfx/Types.scala
  9. 70
      src/main/scala/nova/monadic_sfx/actors/ActorModule.scala
  10. 20
      src/main/scala/nova/monadic_sfx/actors/TestActor.scala
  11. 26
      src/main/scala/nova/monadic_sfx/executors/GUIExecutor.scala
  12. 25
      src/main/scala/nova/monadic_sfx/executors/Schedulers.scala
  13. 4
      src/main/scala/nova/monadic_sfx/http/HttpModule.scala
  14. 38
      src/main/scala/nova/monadic_sfx/http/requests/DummyRequest.scala
  15. 98
      src/main/scala/nova/monadic_sfx/implicits/ActionObservable.scala
  16. 44
      src/main/scala/nova/monadic_sfx/implicits/FontIcon.scala
  17. 42
      src/main/scala/nova/monadic_sfx/implicits/JFXButton.scala
  18. 36
      src/main/scala/nova/monadic_sfx/implicits/JFXListCell.scala
  19. 48
      src/main/scala/nova/monadic_sfx/implicits/JFXListView.scala
  20. 29
      src/main/scala/nova/monadic_sfx/implicits/JFXSpinner.scala
  21. 40
      src/main/scala/nova/monadic_sfx/implicits/JFXTextArea.scala
  22. 35
      src/main/scala/nova/monadic_sfx/implicits/JFXTextField.scala
  23. 62
      src/main/scala/nova/monadic_sfx/implicits/JFXTreeTableView.scala
  24. 327
      src/main/scala/nova/monadic_sfx/implicits/JavaFxMonixObservables.scala
  25. 9
      src/main/scala/nova/monadic_sfx/implicits/MenuItem.scala
  26. 50
      src/main/scala/nova/monadic_sfx/implicits/package.scala
  27. 7
      src/main/scala/nova/monadic_sfx/models/DummyModels.scala
  28. 65
      src/main/scala/nova/monadic_sfx/ui/DefaultUI.scala
  29. 146
      src/main/scala/nova/monadic_sfx/ui/MyFxApp.scala
  30. 129
      src/main/scala/nova/monadic_sfx/ui/MyFxAppOld.scala
  31. 18
      src/main/scala/nova/monadic_sfx/ui/UiModule.scala
  32. 107
      src/main/scala/nova/monadic_sfx/ui/components/router/FXRouter.scala
  33. 378
      src/main/scala/nova/monadic_sfx/ui/components/todo/TodoListComponentOld.scala
  34. 89
      src/main/scala/nova/monadic_sfx/ui/components/todo/TodoListStore.scala
  35. 134
      src/main/scala/nova/monadic_sfx/ui/components/todo/TodoListView.scala
  36. 95
      src/main/scala/nova/monadic_sfx/ui/controller/TodoController.scala
  37. 28
      src/main/scala/nova/monadic_sfx/ui/screens/HomeScreen.scala
  38. 34
      src/main/scala/nova/monadic_sfx/ui/screens/LoginScreen.scala
  39. 2
      src/main/scala/nova/monadic_sfx/ui/screens/Screen.scala
  40. 4
      src/main/scala/nova/monadic_sfx/util/Action.scala
  41. 21
      src/main/scala/nova/monadic_sfx/util/IOUtils.scala
  42. 63
      src/main/scala/nova/monadic_sfx/util/Misc.scala
  43. 75
      src/main/scala/nova/monadic_sfx/util/reactive/Middlewares.scala
  44. 23
      src/main/scala/nova/monadic_sfx/util/reactive/MonixProSubject.scala
  45. 39
      src/main/scala/nova/monadic_sfx/util/reactive/Reducer.scala
  46. 97
      src/main/scala/nova/monadic_sfx/util/reactive/Store.scala
  47. 18
      src/main/scala/nova/monadic_sfx/util/reactive/package.scala
  48. 76
      src/main/scala/org/slf4j/impl/StaticLoggerBuilder.scala

2
.gitignore

@ -22,3 +22,5 @@ metals.sbt
.idea/
.vscode
/project/project
.bsp

61
build.sbt

@ -5,7 +5,7 @@ name := "ScalaFX Hello World"
version := "14-R19"
// Version of Scala used by the project
scalaVersion := "2.13.3"
scalaVersion := "2.13.4"
// Add dependency on ScalaFX library
libraryDependencies += "org.scalafx" %% "scalafx" % "14-R19"
@ -16,31 +16,55 @@ enablePlugins(JavaFxPlugin)
libraryDependencies ++= Seq(
"org.typelevel" %% "cats-core" % "2.1.1",
"org.typelevel" %% "cats-effect" % "2.1.4",
"io.monix" %% "monix" % "3.2.2",
"io.monix" %% "monix-bio" % "1.0.0",
"io.monix" %% "monix" % "3.3.0",
"io.monix" %% "monix-bio" % "1.1.0",
"io.circe" %% "circe-core" % "0.13.0",
"io.circe" %% "circe-generic" % "0.13.0",
"com.softwaremill.sttp.client" %% "core" % "2.2.5",
"com.softwaremill.sttp.client" %% "monix" % "2.2.5",
"com.softwaremill.sttp.client" %% "circe" % "2.2.5",
"com.softwaremill.sttp.client" %% "async-http-client-backend-monix" % "2.2.5",
"com.github.valskalla" %% "odin-monix" % "0.8.1",
"com.softwaremill.sttp.client" %% "core" % "2.2.9",
"com.softwaremill.sttp.client" %% "monix" % "2.2.9",
"com.softwaremill.sttp.client" %% "circe" % "2.2.9",
"com.softwaremill.sttp.client" %% "httpclient-backend-monix" % "2.2.9",
"com.softwaremill.quicklens" %% "quicklens" % "1.6.1",
"com.typesafe.akka" %% "akka-actor-typed" % "2.6.8",
"com.softwaremill.macwire" %% "util" % "2.3.7",
"com.softwaremill.macwire" %% "macros" % "2.3.6" % "provided",
"com.softwaremill.macwire" %% "macrosakka" % "2.3.6" % "provided",
"com.github.valskalla" %% "odin-slf4j" % "0.8.1"
"com.github.valskalla" %% "odin-monix" % "0.9.1",
"com.github.valskalla" %% "odin-slf4j" % "0.9.1",
"com.github.valskalla" %% "odin-json" % "0.9.1",
"com.github.valskalla" %% "odin-extras" % "0.9.1",
"com.typesafe.scala-logging" %% "scala-logging" % "3.9.2",
"com.jfoenix" % "jfoenix" % "9.0.10",
"org.kordamp.ikonli" % "ikonli-core" % "12.0.0",
"org.kordamp.ikonli" % "ikonli-javafx" % "12.0.0",
"org.kordamp.ikonli" % "ikonli-fontawesome5-pack" % "12.0.0",
"org.kordamp.ikonli" % "ikonli-material-pack" % "12.0.0",
"io.github.typhon0" % "AnimateFX" % "1.2.1",
"com.beachape" %% "enumeratum" % "1.6.1",
"com.chuusai" %% "shapeless" % "2.3.3",
"org.gerweck.scalafx" %% "scalafx-utils" % "0.15.0"
)
scalacOptions ++= Seq(
"-unchecked",
"-deprecation",
"-Xcheckinit",
"-encoding",
"utf8",
"UTF-8",
"-deprecation",
"-feature",
"-Ywarn-unused:imports"
"-language:existentials",
"-language:experimental.macros",
"-language:higherKinds",
"-language:implicitConversions",
"-unchecked",
"-Xlint",
"-Ywarn-numeric-widen",
"-Ymacro-annotations",
//silence warnings for by-name implicits
"-Wconf:cat=lint-byname-implicit:s",
//give errors on non exhaustive matches
"-Wconf:msg=match may not be exhaustive:e",
"-explaintypes" // Explain type errors in more detail.
)
javacOptions ++= Seq("-source", "11", "-target", "11")
// Fork a new JVM for 'run' and 'test:run', to avoid JavaFX double initialization problems
fork := true
@ -59,3 +83,12 @@ lazy val javaFXModules =
libraryDependencies ++= javaFXModules.map(m =>
"org.openjfx" % s"javafx-$m" % "14.0.1" classifier osName
)
addCompilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.1")
ThisBuild / scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.4.3"
inThisBuild(
List(
scalaVersion := scalaVersion.value, // 2.11.12, or 2.13.3
semanticdbEnabled := true, // enable SemanticDB
semanticdbVersion := "4.4.2" // use Scalafix compatible version
)
)

2
project/build.properties

@ -1,2 +1,2 @@
sbt.version=1.3.10
sbt.version=1.4.3

1
project/plugin.sbt

@ -4,3 +4,4 @@ scalacOptions ++= Seq("-unchecked", "-deprecation")
addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "5.2.4")
// addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.10")
addSbtPlugin("com.quadstingray" % "sbt-javafx" % "1.5.2")
addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.23")

53
src/main/scala/nova/monadic_sfx/Main.scala

@ -1,37 +1,32 @@
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.implicits._
import cats.effect.Resource
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)
}
}

189
src/main/scala/nova/monadic_sfx/MainApp.scala

@ -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 }
}
)
}
}

38
src/main/scala/nova/monadic_sfx/MainModule.scala

@ -1,7 +1,41 @@
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.ui.UiModule
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)
}

11
src/main/scala/nova/monadic_sfx/Types.scala

@ -1,13 +1,14 @@
package nova.monadic_sfx
import java.nio.ByteBuffer
import monix.eval.Task
import sttp.client.SttpBackend
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 =
SttpBackend[Task, Observable[ByteBuffer], WebSocketHandler]
}
object AppTypes extends AppTypes {}

70
src/main/scala/nova/monadic_sfx/actors/ActorModule.scala

@ -1,74 +1,28 @@
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.Future
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 {
import scala.concurrent.ExecutionContext
implicit val timeout: Timeout = Timeout(3.seconds)
implicit def timeout: Timeout = Timeout(3.seconds)
def actorSystemResource(
logger: Logger[Task]
): Resource[Task, ActorSystem[SpawnProtocol.Command]] =
Resource.make(logger.info("Creating Actor System") >> Task {
ActorSystem(HelloWorldMain(), name = "FXActorSystem")
ActorSystem(SpawnProtocol(), name = "FXActorSystem")
})(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()
}
}

20
src/main/scala/nova/monadic_sfx/actors/TestActor.scala

@ -36,15 +36,13 @@ class Counter(context: ActorContext[Counter.Command])
}
}
override def onSignal: PartialFunction[Signal, Behavior[Counter.Command]] =
PartialFunction.fromFunction((signal: Signal) => {
signal match {
case _: Terminated =>
context.log.info("Recieved shutdown counter actor terminated")
this
case PostStop =>
context.log.info("Recieved shutdown counter actor poststop")
this
}
})
override def onSignal: PartialFunction[Signal, Behavior[Counter.Command]] = {
case _: Terminated =>
context.log.info("Recieved shutdown counter actor terminated")
this
case PostStop =>
context.log.info("Recieved shutdown counter actor poststop")
this
}
}

26
src/main/scala/nova/monadic_sfx/executors/GUIExecutor.scala

@ -1,23 +1,21 @@
package nova.monadic_sfx.executors
import akka.dispatch.{
DispatcherPrerequisites,
ExecutorServiceFactory,
ExecutorServiceConfigurator
}
import com.typesafe.config.Config
import java.util.concurrent.{
ExecutorService,
AbstractExecutorService,
ThreadFactory,
TimeUnit
}
import java.util.Collections
import java.util.concurrent.AbstractExecutorService
import java.util.concurrent.Executor
import java.util.concurrent.ExecutorService
import java.util.concurrent.ThreadFactory
import java.util.concurrent.TimeUnit
import javax.swing.SwingUtilities
import scala.concurrent.ExecutionContext
import akka.dispatch.DispatcherPrerequisites
import akka.dispatch.ExecutorServiceConfigurator
import akka.dispatch.ExecutorServiceFactory
import com.typesafe.config.Config
import javafx.application.Platform
import monix.execution.Scheduler
import scala.concurrent.ExecutionContext
import java.util.concurrent.Executor
// First we wrap invokeLater/runLater as an ExecutorService
trait GUIExecutorService extends AbstractExecutorService {

25
src/main/scala/nova/monadic_sfx/executors/Schedulers.scala

@ -1,9 +1,28 @@
package nova.monadic_sfx.executors
import com.typesafe.scalalogging.Logger
import monix.execution.Scheduler
import monix.execution.UncaughtExceptionReporter
import monix.execution.schedulers.TracingScheduler
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)
}
}

4
src/main/scala/nova/monadic_sfx/http/HttpModule.scala

@ -1,8 +1,8 @@
package nova.monadic_sfx.http
import nova.monadic_sfx.http.requests.DummyRequest
import nova.monadic_sfx.AppTypes
import akka.actor.typed._
import nova.monadic_sfx.AppTypes
import nova.monadic_sfx.http.requests.DummyRequest
trait HttpModule {
def requesters(

38
src/main/scala/nova/monadic_sfx/http/requests/DummyRequest.scala

@ -1,43 +1,17 @@
package nova.monadic_sfx.http.requests
import nova.monadic_sfx.AppTypes
import nova.monadic_sfx.AppTypes.HttpBackend
import monix.eval.Task
import nova.monadic_sfx.models._
import sttp.client._
import sttp.client.circe._
import io.circe.generic.auto._
import nova.monadic_sfx.models._
import cats.data.EitherT
class DummyRequest(backend: HttpBackend) extends AppTypes {
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)
}
}

98
src/main/scala/nova/monadic_sfx/implicits/ActionObservable.scala

@ -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()
}
}

44
src/main/scala/nova/monadic_sfx/implicits/FontIcon.scala

@ -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")
}

42
src/main/scala/nova/monadic_sfx/implicits/JFXButton.scala

@ -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())
}

36
src/main/scala/nova/monadic_sfx/implicits/JFXListCell.scala

@ -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)
}
}

48
src/main/scala/nova/monadic_sfx/implicits/JFXListView.scala

@ -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
}

29
src/main/scala/nova/monadic_sfx/implicits/JFXSpinner.scala

@ -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)
}

40
src/main/scala/nova/monadic_sfx/implicits/JFXTextArea.scala

@ -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)
}

35
src/main/scala/nova/monadic_sfx/implicits/JFXTextField.scala

@ -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)
}

62
src/main/scala/nova/monadic_sfx/implicits/JFXTreeTableView.scala

@ -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

327
src/main/scala/nova/monadic_sfx/implicits/JavaFxMonixObservables.scala

@ -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
}
}
}
}

9
src/main/scala/nova/monadic_sfx/implicits/MenuItem.scala

@ -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)
}

50
src/main/scala/nova/monadic_sfx/implicits/package.scala

@ -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("")
// }
}

7
src/main/scala/nova/monadic_sfx/models/DummyModels.scala

@ -1,7 +1,10 @@
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,
origin: String,
headers: Map[String, String]

65
src/main/scala/nova/monadic_sfx/ui/DefaultUI.scala

@ -1,55 +1,52 @@
package nova.monadic_sfx.ui
import nova.monadic_sfx.implicits.JFXSpinner
import scalafx.geometry.Insets
import scalafx.geometry.Pos
import scalafx.scene.Scene
import scalafx.scene.effect.DropShadow
import scalafx.scene.layout.HBox
import scalafx.scene.layout.VBox
import scalafx.scene.paint.Color._
import scalafx.scene.paint._
import scalafx.scene.text.Text
import monix.eval.Coeval
class DefaultUI {
object DefaultUI {
val scene =
new Scene {
fill = Color.rgb(38, 38, 38)
content = new HBox {
content = new VBox {
alignment = Pos.Center
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)
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
// }
// }
// }
}

146
src/main/scala/nova/monadic_sfx/ui/MyFxApp.scala

@ -1,129 +1,41 @@
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 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"),
_
)
)
}
}
}

129
src/main/scala/nova/monadic_sfx/ui/MyFxAppOld.scala

@ -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"),
_
)
)
}
}
}

18
src/main/scala/nova/monadic_sfx/ui/UiModule.scala

@ -1,15 +1,15 @@
package nova.monadic_sfx.ui
import scalafx.application.JFXApp
import monix.eval.Task
import nova.monadic_sfx.AppTypes
import scalafx.application.JFXApp.PrimaryStage
import io.odin.Logger
import akka.actor.typed._
import cats.effect.Resource
import com.softwaremill.macwire._
import nova.monadic_sfx.http.Requesters
import io.odin.Logger
import monix.eval.Task
import nova.monadic_sfx.AppTypes
import nova.monadic_sfx.executors.Schedulers
import akka.actor.typed._
import nova.monadic_sfx.http.Requesters
import scalafx.application.JFXApp
import scalafx.application.JFXApp.PrimaryStage
trait UiModule {
def fxAppResource(
@ -21,7 +21,7 @@ trait UiModule {
): Resource[Task, JFXApp] =
Resource.make(for {
_ <- logger.info("Creating FX Application")
app <- Task { wire[MyFxApp] }
app <- Task { wire[MyFxAppOld] }
} yield (app))(app => logger.info("Stopping FX Application"))
}
@ -31,7 +31,7 @@ object UiModule {
actorSystem: ActorSystem[SpawnProtocol.Command]
) = {
new PrimaryStage {
scene = new DefaultUI().scene
scene = DefaultUI.scene
}
}
}

107
src/main/scala/nova/monadic_sfx/ui/components/router/FXRouter.scala

@ -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)
// }

378
src/main/scala/nova/monadic_sfx/ui/components/todo/TodoListComponentOld.scala

@ -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
// }
// }

89
src/main/scala/nova/monadic_sfx/ui/components/todo/TodoListStore.scala

@ -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")
// // )
// )
// )

134
src/main/scala/nova/monadic_sfx/ui/components/todo/TodoListView.scala

@ -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
}
)
}
}
)
}

95
src/main/scala/nova/monadic_sfx/ui/controller/TodoController.scala

@ -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())
}
}
}

28
src/main/scala/nova/monadic_sfx/ui/screens/HomeScreen.scala

@ -1,23 +1,26 @@
package nova.monadic_sfx.screens
import akka.actor.typed._
import monix.eval.Task
import nova.monadic_sfx.AppTypes
import scalafx.scene.control.TextField
import nova.monadic_sfx.implicits._
import scalafx.scene.Parent
import scalafx.scene.control._
import scalafx.scene.layout.VBox
import scalafx.scene.Node
import scalafx.Includes._
import scalafx.scene.layout.HBox
import scalafx.scene.text.Text
import scalafx.scene.Parent
import scalafx.application.JFXApp.PrimaryStage
import monix.eval.Task
import nova.monadic_sfx.util.Action
import akka.actor.typed._
class HomeScreen(
backend: AppTypes.HttpBackend,
system: ActorSystem[SpawnProtocol.Command],
onLogout: () => Task[Unit]
) {
val myButton = new Button {
id = "LogoutButton"
text = "logout"
// onAction = () => Action.asyncT(onLogout())
}
val myObs = myButton.observableAction()
// myObs.foreachL(_ => ())
private lazy val root = Task.deferAction { implicit s =>
Task {
new HBox {
@ -25,15 +28,14 @@ class HomeScreen(
new Text {
text = "hello"
},
new Button {
text = "logout"
onAction = () => Action.asyncT(onLogout())
}
myButton
)
}
}
}
def render = root
}
object HomeScreen {

34
src/main/scala/nova/monadic_sfx/ui/screens/LoginScreen.scala

@ -1,26 +1,19 @@
package nova.monadic_sfx.screens
import akka.actor.typed._
import io.odin.Logger
import monix.eval.Task
import nova.monadic_sfx.AppTypes
import nova.monadic_sfx.executors.Schedulers
import nova.monadic_sfx.http.Requesters
import nova.monadic_sfx.http.requests.DummyRequest
import nova.monadic_sfx.ui.screens.Screen
import nova.monadic_sfx.util.Action
import scalafx.Includes._
import scalafx.application.JFXApp.PrimaryStage
import scalafx.scene.Parent
import scalafx.scene.control.TextField
import scalafx.scene.control._
import scalafx.scene.layout.VBox
import scalafx.scene.Node
import scalafx.Includes._
import scalafx.scene.Parent
import scalafx.application.JFXApp.PrimaryStage
import nova.monadic_sfx.http.requests.DummyRequest
import monix.eval.Task
import monix.execution.Scheduler
import cats.effect.Effect
import cats.effect.implicits._
import nova.monadic_sfx.util.Action
import io.odin.Logger
import nova.monadic_sfx.http.Requesters
import sttp.client.Response
import nova.monadic_sfx.models.HttpBinResponse
import sttp.client.ResponseError
import nova.monadic_sfx.executors.Schedulers
import nova.monadic_sfx.ui.screens.Screen
import akka.actor.typed._
// import io.odin.syntax._
// import _root_.monix.eval.Task
// import io.odin.monix._
@ -62,7 +55,7 @@ class LoginScreen(
_ <- Task { stage.scene().setRoot(homeScreen) }
} yield ()
private lazy val root = Task.deferAction(implicit scheduler =>
private lazy val root = Task.deferAction(implicit s =>
Task {
new VBox {
children = Seq(
@ -76,6 +69,7 @@ class LoginScreen(
new TextField(),
new Button {
text = "Login"
// onAction.-->
onAction = () =>
Action.asyncT {
Task
@ -95,7 +89,7 @@ class LoginScreen(
def render: Task[Parent] = root
val testRequest = for {
res <- dummyRequester.send()
res <- dummyRequester.send
_ <- logger.info(res.body.toString())
} yield ()

2
src/main/scala/nova/monadic_sfx/ui/screens/Screen.scala

@ -1,8 +1,8 @@
package nova.monadic_sfx.ui.screens
import monix.eval.Task
import scalafx.application.JFXApp.PrimaryStage
import scalafx.scene.Parent
import monix.eval.Task
trait Screen {
protected def appStage: PrimaryStage

4
src/main/scala/nova/monadic_sfx/util/Action.scala

@ -1,9 +1,9 @@
package nova.monadic_sfx.util
import monix.eval.Task
import monix.execution.Scheduler
import cats.effect.Effect
import cats.effect.implicits._
import monix.eval.Task
import monix.execution.Scheduler
object Action {

21
src/main/scala/nova/monadic_sfx/util/IOUtils.scala

@ -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)
}
}

63
src/main/scala/nova/monadic_sfx/util/Misc.scala

@ -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
}
}

75
src/main/scala/nova/monadic_sfx/util/reactive/Middlewares.scala

@ -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))
}
)
)
}

23
src/main/scala/nova/monadic_sfx/util/reactive/MonixProSubject.scala

@ -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)
}
}

39
src/main/scala/nova/monadic_sfx/util/reactive/Reducer.scala

@ -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)
)
}

97
src/main/scala/nova/monadic_sfx/util/reactive/Store.scala

@ -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
)
}
}
}

18
src/main/scala/nova/monadic_sfx/util/reactive/package.scala

@ -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])
}

76
src/main/scala/org/slf4j/impl/StaticLoggerBuilder.scala

@ -1,11 +1,19 @@
package org.slf4j.impl
import cats.effect.{ContextShift, Clock, Effect, IO, Timer}
import io.odin._
import io.odin.slf4j.OdinLoggerBinder
import scala.concurrent.ExecutionContext
import scala.concurrent.duration._
import _root_.monix.execution.Scheduler
import cats.effect.Clock
import cats.effect.ContextShift
import cats.effect.Effect
import cats.effect.IO
import cats.effect.Timer
import cats.implicits._
import io.odin._
import io.odin.json.Formatter
import io.odin.slf4j.OdinLoggerBinder
import io.odin.syntax._
//effect type should be specified inbefore
//log line will be recorded right after the call with no suspension
@ -17,20 +25,72 @@ class StaticLoggerBinder extends OdinLoggerBinder[IO] {
implicit val cs: ContextShift[IO] = IO.contextShift(ec)
implicit val F: Effect[IO] = IO.ioEffect
// val loggers: PartialFunction[String, Logger[IO]] = {
// case "some.external.package.SpecificClass" =>
// consoleLogger[IO](minLevel = Level.Warn) //disable noisy external logs
// case asyncHttpClient
// if asyncHttpClient.startsWith("org.asynchttpclient.netty") =>
// consoleLogger[IO](minLevel = Level.Warn)
// case _ => //if wildcard case isn't provided, default logger is no-op
// consoleLogger[IO]()
// }
// private lazy val (defaultConsoleLogger, release1) =
// consoleLogger[IO](minLevel = Level.Debug)
// .withAsync(timeWindow = 1.milliseconds)
// .allocated
// .unsafeRunSync()
private lazy val (mainFileLogger, release2) =
fileLogger[IO](
"application-log-2.log",
Formatter.json,
minLevel = Level.Debug
).withAsync(timeWindow = 1.milliseconds)
.allocated
.unsafeRunSync()
sys.addShutdownHook(release2.unsafeRunSync())
// private lazy val (eventBusFileLogger, release3) =
// fileLogger[IO](
// "eventbus.log",
// Formatter.json,
// minLevel = Level.Debug
// ).withAsync(timeWindow = 1.milliseconds)
// .allocated
// .unsafeRunSync()
// {
// ArraySeq(release1, release2, release3).foreach(r =>
// sys.addShutdownHook(r.unsafeRunSync())
// )
// }
val loggers: PartialFunction[String, Logger[IO]] = {
case "some.external.package.SpecificClass" =>
consoleLogger[IO](minLevel = Level.Warn) //disable noisy external logs
//disable noisy external logs
consoleLogger[IO](minLevel = Level.Debug).withMinimalLevel(Level.Warn)
case asyncHttpClient
if asyncHttpClient.startsWith("org.asynchttpclient.netty") =>
consoleLogger[IO](minLevel = Level.Warn)
consoleLogger[IO](minLevel = Level.Debug).withMinimalLevel(Level.Warn)
// case s
// if s.startsWith(
// "wow.doge.mygame.subsystems.movement.PlayerMovementEventHandler"
// ) =>
// consoleLogger[IO](minLevel = Level.Debug).withMinimalLevel( Level.Trace) //selectively turn on trace logging for specific classes
// case s if s.startsWith("wow.doge.mygame.events.EventBus") =>
// consoleLogger[IO](minLevel = Level.Debug).withMinimalLevel(Level.Debug) |+| eventBusFileLogger
case s if s.startsWith("akka.actor") || s.startsWith("nova.monadic_sfx") =>
consoleLogger[IO](minLevel = Level.Debug)
.withMinimalLevel(Level.Debug) |+| mainFileLogger
case _ => //if wildcard case isn't provided, default logger is no-op
consoleLogger[IO]()
consoleLogger[IO](minLevel = Level.Debug).withMinimalLevel(Level.Debug)
}
}
object StaticLoggerBinder extends StaticLoggerBinder {
var REQUESTED_API_VERSION: String = "1.7"
val REQUESTED_API_VERSION: String = "1.7"
def getSingleton: StaticLoggerBinder = this

Loading…
Cancel
Save