Added store pattern using monix
also added relevant implicits to use it
This commit is contained in:
parent
536f1b0af3
commit
b988ad267e
2
.gitignore
vendored
2
.gitignore
vendored
@ -22,3 +22,5 @@ metals.sbt
|
|||||||
.idea/
|
.idea/
|
||||||
.vscode
|
.vscode
|
||||||
/project/project
|
/project/project
|
||||||
|
|
||||||
|
.bsp
|
||||||
|
56
build.sbt
56
build.sbt
@ -5,7 +5,7 @@ name := "ScalaFX Hello World"
|
|||||||
version := "14-R19"
|
version := "14-R19"
|
||||||
|
|
||||||
// Version of Scala used by the project
|
// Version of Scala used by the project
|
||||||
scalaVersion := "2.13.3"
|
scalaVersion := "2.13.4"
|
||||||
|
|
||||||
// Add dependency on ScalaFX library
|
// Add dependency on ScalaFX library
|
||||||
libraryDependencies += "org.scalafx" %% "scalafx" % "14-R19"
|
libraryDependencies += "org.scalafx" %% "scalafx" % "14-R19"
|
||||||
@ -17,30 +17,53 @@ libraryDependencies ++= Seq(
|
|||||||
"org.typelevel" %% "cats-core" % "2.1.1",
|
"org.typelevel" %% "cats-core" % "2.1.1",
|
||||||
"org.typelevel" %% "cats-effect" % "2.1.4",
|
"org.typelevel" %% "cats-effect" % "2.1.4",
|
||||||
"io.monix" %% "monix" % "3.2.2",
|
"io.monix" %% "monix" % "3.2.2",
|
||||||
"io.monix" %% "monix-bio" % "1.0.0",
|
"io.monix" %% "monix-bio" % "1.1.0",
|
||||||
"io.circe" %% "circe-core" % "0.13.0",
|
"io.circe" %% "circe-core" % "0.13.0",
|
||||||
"io.circe" %% "circe-generic" % "0.13.0",
|
"io.circe" %% "circe-generic" % "0.13.0",
|
||||||
"com.softwaremill.sttp.client" %% "core" % "2.2.5",
|
"com.softwaremill.sttp.client" %% "core" % "2.2.9",
|
||||||
"com.softwaremill.sttp.client" %% "monix" % "2.2.5",
|
"com.softwaremill.sttp.client" %% "monix" % "2.2.9",
|
||||||
"com.softwaremill.sttp.client" %% "circe" % "2.2.5",
|
"com.softwaremill.sttp.client" %% "circe" % "2.2.9",
|
||||||
"com.softwaremill.sttp.client" %% "async-http-client-backend-monix" % "2.2.5",
|
"com.softwaremill.sttp.client" %% "async-http-client-backend-monix" % "2.2.9",
|
||||||
|
"com.softwaremill.sttp.client" %% "httpclient-backend-monix" % "2.2.9",
|
||||||
"com.github.valskalla" %% "odin-monix" % "0.8.1",
|
"com.github.valskalla" %% "odin-monix" % "0.8.1",
|
||||||
"com.typesafe.akka" %% "akka-actor-typed" % "2.6.8",
|
"com.typesafe.akka" %% "akka-actor-typed" % "2.6.8",
|
||||||
"com.softwaremill.macwire" %% "util" % "2.3.7",
|
"com.softwaremill.macwire" %% "util" % "2.3.7",
|
||||||
"com.softwaremill.macwire" %% "macros" % "2.3.6" % "provided",
|
"com.softwaremill.macwire" %% "macros" % "2.3.6" % "provided",
|
||||||
"com.softwaremill.macwire" %% "macrosakka" % "2.3.6" % "provided",
|
"com.softwaremill.macwire" %% "macrosakka" % "2.3.6" % "provided",
|
||||||
"com.github.valskalla" %% "odin-slf4j" % "0.8.1"
|
"com.github.valskalla" %% "odin-slf4j" % "0.8.1",
|
||||||
|
"com.github.valskalla" %% "odin-json" % "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(
|
scalacOptions ++= Seq(
|
||||||
"-unchecked",
|
|
||||||
"-deprecation",
|
|
||||||
"-Xcheckinit",
|
|
||||||
"-encoding",
|
"-encoding",
|
||||||
"utf8",
|
"UTF-8",
|
||||||
|
"-deprecation",
|
||||||
"-feature",
|
"-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 a new JVM for 'run' and 'test:run', to avoid JavaFX double initialization problems
|
||||||
fork := true
|
fork := true
|
||||||
@ -59,3 +82,12 @@ lazy val javaFXModules =
|
|||||||
libraryDependencies ++= javaFXModules.map(m =>
|
libraryDependencies ++= javaFXModules.map(m =>
|
||||||
"org.openjfx" % s"javafx-$m" % "14.0.1" classifier osName
|
"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
|
||||||
|
)
|
||||||
|
)
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
sbt.version=1.3.10
|
sbt.version=1.4.3
|
||||||
|
|
||||||
|
@ -4,3 +4,4 @@ scalacOptions ++= Seq("-unchecked", "-deprecation")
|
|||||||
addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "5.2.4")
|
addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "5.2.4")
|
||||||
// addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.10")
|
// addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.10")
|
||||||
addSbtPlugin("com.quadstingray" % "sbt-javafx" % "1.5.2")
|
addSbtPlugin("com.quadstingray" % "sbt-javafx" % "1.5.2")
|
||||||
|
addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.23")
|
||||||
|
@ -1,37 +1,39 @@
|
|||||||
package nova.monadic_sfx
|
package nova.monadic_sfx
|
||||||
|
|
||||||
import monix.eval.Task
|
import scala.concurrent.duration._
|
||||||
// import sttp.client.asynchttpclient.monix.AsyncHttpClientMonixBackend
|
|
||||||
// import sttp.client._
|
import _root_.monix.bio.BIOApp
|
||||||
// import sttp.client.circe._
|
import _root_.monix.bio.Task
|
||||||
// import io.circe.generic.auto._
|
import _root_.monix.bio.UIO
|
||||||
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 cats.effect.ExitCode
|
import cats.effect.ExitCode
|
||||||
|
import cats.effect.Resource
|
||||||
import cats.implicits._
|
import cats.implicits._
|
||||||
import com.softwaremill.macwire._
|
import com.softwaremill.macwire._
|
||||||
|
import io.odin._
|
||||||
|
import io.odin.syntax._
|
||||||
|
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 {
|
||||||
override def run(args: List[String]): Task[ExitCode] = {
|
implicit0(logger: Logger[Task]) <-
|
||||||
// val startTime = Task.clock
|
consoleLogger().withAsync(timeWindow = 1.millis) |+| fileLogger(
|
||||||
// .monotonic(scala.concurrent.duration.MILLISECONDS)
|
"application.log"
|
||||||
// .map(Duration.fromNanos(_))
|
).withAsync()
|
||||||
lazy val appResource = for {
|
schedulers = new Schedulers()
|
||||||
// clock <- Resource.liftF(Task(Task.clock))
|
backend <- Resource.make(
|
||||||
logger <- consoleLogger().withAsync()
|
toIO(HttpClientMonixBackend()(schedulers.async))
|
||||||
backend <- AsyncHttpClientMonixBackend.resource()
|
)(c => toIO(c.close()))
|
||||||
actorSystem <- actorSystemResource(logger)
|
actorSystem <- actorSystemResource(logger)
|
||||||
reqs <- Resource.liftF(Task(wireWith(requesters _)))
|
_ <- Resource.liftF(wire[MainApp].program)
|
||||||
schedulers <- Resource.liftF(Task(new Schedulers()))
|
} yield ()
|
||||||
fxApp <- wireWith(fxAppResource _)
|
|
||||||
} yield (fxApp)
|
override def run(args: List[String]): UIO[ExitCode] =
|
||||||
appResource
|
appResource(System.currentTimeMillis())
|
||||||
.use(fxApp => Task(fxApp.main(args.toArray)))
|
.use(_ => Task.unit)
|
||||||
|
.onErrorHandle(_.printStackTrace())
|
||||||
.as(ExitCode.Success)
|
.as(ExitCode.Success)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
159
src/main/scala/nova/monadic_sfx/MainApp.scala
Normal file
159
src/main/scala/nova/monadic_sfx/MainApp.scala
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
package nova.monadic_sfx
|
||||||
|
|
||||||
|
import com.softwaremill.macwire._
|
||||||
|
import io.odin.Logger
|
||||||
|
import monix.bio.Task
|
||||||
|
import monix.catnap.ConcurrentChannel
|
||||||
|
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.todo.Todo
|
||||||
|
import nova.monadic_sfx.ui.components.todo.TodoListComponent
|
||||||
|
import nova.monadic_sfx.ui.components.todo.TodoListView
|
||||||
|
import nova.monadic_sfx.util.IOUtils._
|
||||||
|
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 = 800
|
||||||
|
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
|
||||||
|
)
|
||||||
|
_ <- 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, delObs, editObs) <-
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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,7 @@
|
|||||||
package nova.monadic_sfx
|
package nova.monadic_sfx
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
trait MainModule extends ActorModule with UiModule with HttpModule
|
trait MainModule extends ActorModule with UiModule with HttpModule
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
package nova.monadic_sfx
|
package nova.monadic_sfx
|
||||||
|
|
||||||
import monix.eval.Task
|
|
||||||
import sttp.client.SttpBackend
|
|
||||||
import monix.reactive.Observable
|
|
||||||
import sttp.client.asynchttpclient.WebSocketHandler
|
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
|
|
||||||
trait AppTypes {}
|
import monix.eval.Task
|
||||||
object AppTypes {
|
import monix.reactive.Observable
|
||||||
|
import sttp.client.SttpBackend
|
||||||
|
import sttp.client.asynchttpclient.WebSocketHandler
|
||||||
|
|
||||||
|
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 akka.util.Timeout
|
||||||
import scala.concurrent.Await
|
import cats.effect.Resource
|
||||||
import nova.monadic_sfx.executors.Schedulers
|
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(
|
for {
|
||||||
sys.terminate()
|
_ <- Task(sys.terminate())
|
||||||
) >> logger.info("Actor System terminated")
|
_ <- 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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -36,9 +36,7 @@ class Counter(context: ActorContext[Counter.Command])
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override def onSignal: PartialFunction[Signal, Behavior[Counter.Command]] =
|
override def onSignal: PartialFunction[Signal, Behavior[Counter.Command]] = {
|
||||||
PartialFunction.fromFunction((signal: Signal) => {
|
|
||||||
signal match {
|
|
||||||
case _: Terminated =>
|
case _: Terminated =>
|
||||||
context.log.info("Recieved shutdown counter actor terminated")
|
context.log.info("Recieved shutdown counter actor terminated")
|
||||||
this
|
this
|
||||||
@ -46,5 +44,5 @@ class Counter(context: ActorContext[Counter.Command])
|
|||||||
context.log.info("Recieved shutdown counter actor poststop")
|
context.log.info("Recieved shutdown counter actor poststop")
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
@ -1,23 +1,21 @@
|
|||||||
package nova.monadic_sfx.executors
|
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.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 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 javafx.application.Platform
|
||||||
import monix.execution.Scheduler
|
import monix.execution.Scheduler
|
||||||
import scala.concurrent.ExecutionContext
|
|
||||||
import java.util.concurrent.Executor
|
|
||||||
|
|
||||||
// First we wrap invokeLater/runLater as an ExecutorService
|
// First we wrap invokeLater/runLater as an ExecutorService
|
||||||
trait GUIExecutorService extends AbstractExecutorService {
|
trait GUIExecutorService extends AbstractExecutorService {
|
||||||
|
@ -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 blocking: Scheduler = TracingScheduler(
|
||||||
val cpu: Scheduler = Scheduler.global,
|
Scheduler
|
||||||
val fx: Scheduler = JFXExecutionContexts.fxScheduler
|
.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,8 +1,8 @@
|
|||||||
package nova.monadic_sfx.http
|
package nova.monadic_sfx.http
|
||||||
|
|
||||||
import nova.monadic_sfx.http.requests.DummyRequest
|
|
||||||
import nova.monadic_sfx.AppTypes
|
|
||||||
import akka.actor.typed._
|
import akka.actor.typed._
|
||||||
|
import nova.monadic_sfx.AppTypes
|
||||||
|
import nova.monadic_sfx.http.requests.DummyRequest
|
||||||
|
|
||||||
trait HttpModule {
|
trait HttpModule {
|
||||||
def requesters(
|
def requesters(
|
||||||
|
@ -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() = {
|
def send =
|
||||||
Task
|
|
||||||
.suspend {
|
|
||||||
for {
|
|
||||||
req <-
|
|
||||||
basicRequest
|
basicRequest
|
||||||
.get(uri"https://httpbin.org/get")
|
.get(uri"https://httpbin.org/get")
|
||||||
.response(asJson[HttpBinResponse])
|
.response(asJson[HttpBinResponse])
|
||||||
.send()
|
.send()
|
||||||
} yield (req)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def test() = {
|
|
||||||
for {
|
|
||||||
res <- send()
|
|
||||||
res3 <- Task { res.body }
|
|
||||||
res2 <- Task {
|
|
||||||
res3.fold(
|
|
||||||
err => {
|
|
||||||
err.toString()
|
|
||||||
},
|
|
||||||
value => value.toString()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} yield (res3)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -0,0 +1,205 @@
|
|||||||
|
package nova.monadic_sfx.implicits
|
||||||
|
|
||||||
|
import javafx.beans.property.ObjectProperty
|
||||||
|
import javafx.collections.ObservableList
|
||||||
|
import javafx.scene.{input => jfxsi}
|
||||||
|
import javafx.{event => jfxe}
|
||||||
|
import monix.bio.Task
|
||||||
|
import monix.execution.Ack
|
||||||
|
import monix.execution.Cancelable
|
||||||
|
import monix.execution.Scheduler
|
||||||
|
import monix.reactive.Observable
|
||||||
|
import monix.reactive.Observer
|
||||||
|
import monix.reactive.OverflowStrategy
|
||||||
|
import monix.tail.Iterant
|
||||||
|
import monix.{eval => me}
|
||||||
|
import scalafx.Includes._
|
||||||
|
import scalafx.beans.property.Property
|
||||||
|
import scalafx.beans.value.ObservableValue
|
||||||
|
import scalafx.collections.ObservableBuffer
|
||||||
|
import scalafx.scene.Scene
|
||||||
|
import scalafx.scene.control.ButtonBase
|
||||||
|
|
||||||
|
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 <--(obs: Observable[T])(implicit s: Scheduler) = {
|
||||||
|
// obs.doOnNext(v => me.Task(prop.value = v)).subscribe()
|
||||||
|
// }
|
||||||
|
|
||||||
|
// def observableChange[J1 >: J]()
|
||||||
|
// : Observable[(ObservableValue[T, J], J1, J1)] = {
|
||||||
|
// import monix.execution.cancelables.SingleAssignCancelable
|
||||||
|
// Observable.create(OverflowStrategy.Unbounded) { sub =>
|
||||||
|
// val c = SingleAssignCancelable()
|
||||||
|
|
||||||
|
// val canc = prop.onChange((a, b, c) => sub.onNext((a, b, c)))
|
||||||
|
|
||||||
|
// c := Cancelable(() => canc.cancel())
|
||||||
|
// c
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
implicit final class BindObs2[A](private val prop: ObjectProperty[A])
|
||||||
|
extends AnyVal {
|
||||||
|
|
||||||
|
// def -->(sub: Var[A]) =
|
||||||
|
// prop.onChange((a, b, c) => sub := c)
|
||||||
|
|
||||||
|
def -->(sub: Observer[A]) =
|
||||||
|
prop.onChange((a, b, c) => if (c != null) sub.onNext(c))
|
||||||
|
|
||||||
|
// def -->[J1 >: A, T](
|
||||||
|
// op: Observable[J1] => me.Task[T]
|
||||||
|
// )(implicit s: Scheduler) = {
|
||||||
|
// op(prop.observableChange().map(_._3)).runToFuture
|
||||||
|
// }
|
||||||
|
|
||||||
|
def <--(obs: Observable[A])(implicit s: Scheduler) = {
|
||||||
|
obs.doOnNext(v => me.Task(prop() = v)).subscribe()
|
||||||
|
}
|
||||||
|
|
||||||
|
def observableChange[J1 >: A]()
|
||||||
|
: Observable[(ObservableValue[A, A], J1, J1)] = {
|
||||||
|
import monix.execution.cancelables.SingleAssignCancelable
|
||||||
|
Observable.create(OverflowStrategy.Unbounded) { sub =>
|
||||||
|
val c = SingleAssignCancelable()
|
||||||
|
|
||||||
|
val canc = prop.onChange((a, b, c) => sub.onNext((a, b, c)))
|
||||||
|
|
||||||
|
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) =
|
||||||
|
prop.onChange((a, b, c) =>
|
||||||
|
if (c != null)
|
||||||
|
Iterant[Task]
|
||||||
|
.fromIterable(c.toIterable)
|
||||||
|
.consume
|
||||||
|
.use(consume(sub, _))
|
||||||
|
.runToFuture
|
||||||
|
)
|
||||||
|
|
||||||
|
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],
|
||||||
|
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, consumer)
|
||||||
|
case Ack.Stop => Task.unit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
implicit final class OnActionObservable(
|
||||||
|
private val button: ButtonBase
|
||||||
|
) extends AnyVal {
|
||||||
|
// def -->[T](
|
||||||
|
// op: Observable[jfxe.ActionEvent] => me.Task[T]
|
||||||
|
// )(implicit s: Scheduler) = {
|
||||||
|
// op(button.observableAction()).runToFuture
|
||||||
|
// }
|
||||||
|
|
||||||
|
// def -->(
|
||||||
|
// sub: ConcurrentSubject[jfxe.ActionEvent, jfxe.ActionEvent]
|
||||||
|
// ) = {
|
||||||
|
// button.onAction = value => sub.onNext(value)
|
||||||
|
// }
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
50
src/main/scala/nova/monadic_sfx/implicits/package.scala
Normal file
50
src/main/scala/nova/monadic_sfx/implicits/package.scala
Normal file
@ -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)
|
import io.circe.generic.JsonCodec
|
||||||
final case class HttpBinResponse(
|
|
||||||
|
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,19 +1,25 @@
|
|||||||
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)
|
||||||
|
children = Seq(
|
||||||
|
new HBox {
|
||||||
padding = Insets(50, 80, 50, 80)
|
padding = Insets(50, 80, 50, 80)
|
||||||
children = Seq(
|
children = Seq(
|
||||||
new Text {
|
new Text {
|
||||||
@ -35,21 +41,12 @@ class DefaultUI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
},
|
||||||
|
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 io.odin.Logger
|
||||||
import monix.execution.Callback
|
import monix.bio.Task
|
||||||
import com.softwaremill.macwire._
|
import nova.monadic_sfx.executors.Schedulers
|
||||||
import nova.monadic_sfx.http.Requesters
|
import nova.monadic_sfx.ui.DefaultUI
|
||||||
|
import scalafx.application.JFXApp
|
||||||
|
import scalafx.application.JFXApp.PrimaryStage
|
||||||
|
|
||||||
import akka.actor.typed._
|
class MyFxApp(val schedulers: Schedulers)(implicit logger: Logger[Task]) {
|
||||||
import nova.monadic_sfx.actors.Counter
|
|
||||||
import akka.util.Timeout
|
|
||||||
|
|
||||||
class MyFxApp(
|
private lazy val internal = new JFXApp {
|
||||||
logger: Logger[Task],
|
stage = new PrimaryStage {
|
||||||
backend: AppTypes.HttpBackend,
|
scene = DefaultUI.scene
|
||||||
actorSystem: ActorSystem[SpawnProtocol.Command],
|
}
|
||||||
requesters: Requesters,
|
}
|
||||||
schedulers: Schedulers
|
|
||||||
) extends JFXApp {
|
|
||||||
|
|
||||||
implicit lazy val defaultScheduler: Scheduler = schedulers.fx
|
// def stage = Task(internal.stage)
|
||||||
|
|
||||||
lazy val fxActor: Task[ActorRef[Counter.Command]] = wireWith(
|
// def stage_=(stage: PrimaryStage) = Task(internal.stage = stage)
|
||||||
MyFxApp.makeCounterActor _
|
|
||||||
)
|
|
||||||
|
|
||||||
lazy val application =
|
def useInternal[T](f: JFXApp => Task[T]): Task[T] =
|
||||||
for {
|
for {
|
||||||
appStage <- Task(wireWith(UiModule.makePrimaryStage _))
|
_ <- logger.debug("Request for using internal value")
|
||||||
|
res <- f(internal).executeOn(schedulers.fx)
|
||||||
|
_ <- logger.debug(s"Result was ${res.toString()}")
|
||||||
|
} yield (res)
|
||||||
|
|
||||||
// _ <- Task {
|
def init(stage: => PrimaryStage, delay: FiniteDuration = 2000.millis) =
|
||||||
// val counterActor = testActor(actorSystem)
|
for {
|
||||||
// counterActor ! (Counter.Increment)
|
_ <- logger.info("Starting FX App")
|
||||||
// }
|
fib <- Task(internal.main(Array.empty)).start
|
||||||
// ta <- testActor2(actorSystem)
|
_ <- Task.sleep(200.millis)
|
||||||
// actor <-
|
_ <- Task(internal.stage = stage)
|
||||||
// actorTask.bracket(actor => Task(actor ! (Counter.Increment)))(actor =>
|
.executeOn(schedulers.fx)
|
||||||
// Task(actor ! (Counter.Stop))
|
.delayExecution(delay)
|
||||||
// )
|
} yield (this, fib)
|
||||||
// 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 MyFxApp {
|
|
||||||
def makeCounterActor(
|
|
||||||
system: ActorSystem[SpawnProtocol.Command]
|
|
||||||
): 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"),
|
|
||||||
_
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
129
src/main/scala/nova/monadic_sfx/ui/MyFxAppOld.scala
Normal file
129
src/main/scala/nova/monadic_sfx/ui/MyFxAppOld.scala
Normal file
@ -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"),
|
||||||
|
_
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,15 +1,15 @@
|
|||||||
package nova.monadic_sfx.ui
|
package nova.monadic_sfx.ui
|
||||||
|
|
||||||
import scalafx.application.JFXApp
|
import akka.actor.typed._
|
||||||
import monix.eval.Task
|
|
||||||
import nova.monadic_sfx.AppTypes
|
|
||||||
import scalafx.application.JFXApp.PrimaryStage
|
|
||||||
import io.odin.Logger
|
|
||||||
import cats.effect.Resource
|
import cats.effect.Resource
|
||||||
import com.softwaremill.macwire._
|
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 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 {
|
trait UiModule {
|
||||||
def fxAppResource(
|
def fxAppResource(
|
||||||
@ -21,7 +21,7 @@ trait UiModule {
|
|||||||
): Resource[Task, JFXApp] =
|
): Resource[Task, JFXApp] =
|
||||||
Resource.make(for {
|
Resource.make(for {
|
||||||
_ <- logger.info("Creating FX Application")
|
_ <- logger.info("Creating FX Application")
|
||||||
app <- Task { wire[MyFxApp] }
|
app <- Task { wire[MyFxAppOld] }
|
||||||
} yield (app))(app => logger.info("Stopping FX Application"))
|
} yield (app))(app => logger.info("Stopping FX Application"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,7 +31,7 @@ object UiModule {
|
|||||||
actorSystem: ActorSystem[SpawnProtocol.Command]
|
actorSystem: ActorSystem[SpawnProtocol.Command]
|
||||||
) = {
|
) = {
|
||||||
new PrimaryStage {
|
new PrimaryStage {
|
||||||
scene = new DefaultUI().scene
|
scene = DefaultUI.scene
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,556 @@
|
|||||||
|
package nova.monadic_sfx.ui.components.todo
|
||||||
|
|
||||||
|
import scala.concurrent.Future
|
||||||
|
import scala.concurrent.duration.FiniteDuration
|
||||||
|
|
||||||
|
import cats.effect.Sync
|
||||||
|
import cats.effect.concurrent.Deferred
|
||||||
|
import io.odin.Logger
|
||||||
|
import monix.bio.Task
|
||||||
|
import monix.catnap.ConcurrentChannel
|
||||||
|
import monix.catnap.ConsumerF
|
||||||
|
import monix.execution.Ack
|
||||||
|
import monix.execution.Cancelable
|
||||||
|
import monix.execution.Scheduler
|
||||||
|
import monix.reactive.Observable
|
||||||
|
import monix.reactive.Observer
|
||||||
|
import monix.reactive.OverflowStrategy
|
||||||
|
import monix.reactive.observers.Subscriber
|
||||||
|
import monix.reactive.subjects.ConcurrentSubject
|
||||||
|
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.ui.components.todo.TodoListComponent.Add
|
||||||
|
import nova.monadic_sfx.ui.components.todo.TodoListComponent.Delete
|
||||||
|
import nova.monadic_sfx.ui.components.todo.TodoListComponent.Edit
|
||||||
|
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
|
||||||
|
import nova.monadic_sfx.ui.components.todo.Store.MonixProSubject
|
||||||
|
import nova.monadic_sfx.util.IOUtils
|
||||||
|
import monix.tail.Iterant
|
||||||
|
|
||||||
|
case class Todo(id: Int, content: String)
|
||||||
|
|
||||||
|
class TodoListView(
|
||||||
|
val listView: JFXListView[Todo] = TodoListView.defaultListView,
|
||||||
|
val lvObs: ObservableBuffer[Todo] = ObservableBuffer.empty
|
||||||
|
) {
|
||||||
|
listView.items = lvObs
|
||||||
|
}
|
||||||
|
|
||||||
|
object TodoListView {
|
||||||
|
def defaultListView =
|
||||||
|
new JFXListView[Todo] {
|
||||||
|
// cellFactory = _ =>
|
||||||
|
// new ListCell[Todo] {
|
||||||
|
// // item.onChange((a, b, c) => ())
|
||||||
|
// overr
|
||||||
|
// }
|
||||||
|
contextMenu = new ContextMenu {
|
||||||
|
items ++= Seq(
|
||||||
|
new MenuItem {
|
||||||
|
text = "delete"
|
||||||
|
},
|
||||||
|
new MenuItem {
|
||||||
|
text = "edit"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// import scalafx.scene.control.MultipleSelectionModel
|
||||||
|
// .getOrElse(Todo(-1, "blah"))
|
||||||
|
implicit class Operations[A](val sink: Observer[A]) extends AnyVal {}
|
||||||
|
|
||||||
|
// def reducer(
|
||||||
|
// stateC: Coeval[ObservableBuffer[Todo]],
|
||||||
|
// action: TodoListComponent.Command
|
||||||
|
// ) =
|
||||||
|
// action match {
|
||||||
|
// case Add(todo) =>
|
||||||
|
// for {
|
||||||
|
// state <- stateC
|
||||||
|
// } yield state :+ todo
|
||||||
|
// // case Find(id, result) =>
|
||||||
|
// case Edit(id, content) => stateC
|
||||||
|
// case Delete(id) =>
|
||||||
|
// for {
|
||||||
|
// state <- stateC
|
||||||
|
// } yield state.filterNot(_.id == id)
|
||||||
|
// case _ => stateC
|
||||||
|
// }
|
||||||
|
|
||||||
|
def reducer(
|
||||||
|
state: Vector[Todo],
|
||||||
|
action: TodoListComponent.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
|
||||||
|
}
|
||||||
|
|
||||||
|
def defaultListView2: Task[
|
||||||
|
(
|
||||||
|
JFXListView[Todo],
|
||||||
|
Observable[Todo],
|
||||||
|
Observable[Todo]
|
||||||
|
)
|
||||||
|
] =
|
||||||
|
Task.deferAction(implicit s =>
|
||||||
|
Store
|
||||||
|
.createL[TodoListComponent.Command, Vector[Todo]](
|
||||||
|
TodoListComponent.Delete(0),
|
||||||
|
Vector.empty[Todo],
|
||||||
|
(s: Vector[Todo], a: TodoListComponent.Command) =>
|
||||||
|
reducer(s, a) -> Observable.empty
|
||||||
|
)
|
||||||
|
.flatMap(store =>
|
||||||
|
Task {
|
||||||
|
val deleteSub = ConcurrentSubject.publish[Todo]
|
||||||
|
val editSub = ConcurrentSubject.publish[Todo]
|
||||||
|
|
||||||
|
// store.flatMap(st => Task(st.sink))
|
||||||
|
|
||||||
|
// val deleteSub2 =
|
||||||
|
// deleteSub.map(todo => (buf: ObservableBuffer[Todo]) => buf :+ todo)
|
||||||
|
// val addSub =
|
||||||
|
// ConcurrentSubject
|
||||||
|
// .publish[Todo]
|
||||||
|
// .map(todo => (buf: ObservableBuffer[Todo]) => buf :+ todo)
|
||||||
|
// val state = Observable(deleteSub2, addSub).merge.scan0(
|
||||||
|
// ObservableBuffer.empty[Todo]
|
||||||
|
// )((buf, fn) => fn(buf))
|
||||||
|
|
||||||
|
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(TodoListComponent.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 => TodoListComponent.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, deleteSub, editSub)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private[todo] class TodoListComponentImpure(
|
||||||
|
todoListView: TodoListView
|
||||||
|
) {
|
||||||
|
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: TodoListView,
|
||||||
|
val fxScheduler: Scheduler,
|
||||||
|
val logger: Logger[Task]
|
||||||
|
) {
|
||||||
|
def create = Task(new TodoListOps(this))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object TodoListComponent {
|
||||||
|
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
|
||||||
|
|
||||||
|
class Props(
|
||||||
|
val todoListView: TodoListView,
|
||||||
|
val fxScheduler: Scheduler,
|
||||||
|
val channel: ConcurrentChannel[
|
||||||
|
Task,
|
||||||
|
TodoListComponent.Complete,
|
||||||
|
TodoListComponent.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 TodoListComponent(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 TodoListComponent(props: TodoListComponent.Props) {
|
||||||
|
import props._
|
||||||
|
import TodoListComponent._
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
}
|
||||||
|
// : F[ProSubject[A, (A, M)]]
|
||||||
|
|
||||||
|
// interface Middleware<S, A> {
|
||||||
|
// fun dispatch(store: Store<S, A>, next: (A) -> Unit, action: A)
|
||||||
|
// }
|
||||||
|
|
||||||
|
trait Middleware[A, M] {
|
||||||
|
def dispatch[T](
|
||||||
|
store: MonixProSubject[A, (A, M)],
|
||||||
|
cb: (A, M) => Task[T],
|
||||||
|
cb2: (A, M) => Observable[(A, M)],
|
||||||
|
cb3: Observable[(A, M)] => Observable[(A, M)]
|
||||||
|
) = {
|
||||||
|
// store.fil
|
||||||
|
store.mapEval {
|
||||||
|
case (a, m) => IOUtils.toTask(cb(a, m))
|
||||||
|
}
|
||||||
|
store.flatMap {
|
||||||
|
case (a, m) => cb2(a, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
cb3(store)
|
||||||
|
|
||||||
|
def cb3impl(obs: Observable[(A, M)]) =
|
||||||
|
obs.doOnNext {
|
||||||
|
case (a, m) => IOUtils.toTask(Task(println("hello")))
|
||||||
|
}
|
||||||
|
|
||||||
|
def cb3impl2(obs: Observable[(A, M)]) =
|
||||||
|
obs.filter {
|
||||||
|
case (a, m) => m == ""
|
||||||
|
}
|
||||||
|
|
||||||
|
cb3impl2(cb3impl(store))
|
||||||
|
|
||||||
|
val s = Seq(cb3impl _)
|
||||||
|
|
||||||
|
val res = s.foldLeft(Observable.empty[(A, M)]) {
|
||||||
|
case (o1, o2) => o2(o1)
|
||||||
|
}
|
||||||
|
|
||||||
|
val x = Iterant[Task].of(1, 2, 3)
|
||||||
|
|
||||||
|
// x match {
|
||||||
|
// case Next(item, rest) => ()
|
||||||
|
// case Halt(e) => ()
|
||||||
|
// }
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object Store {
|
||||||
|
type Reducer[A, M] = (M, A) => (M, Observable[A])
|
||||||
|
type MonixProSubject[-I, +O] = Observable[O] with Observer[I]
|
||||||
|
// class MonixProSubject2[-I, +O] extends Subject[I, O]
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def createL[A, M](
|
||||||
|
initialAction: A,
|
||||||
|
initialState: M,
|
||||||
|
reducer: Reducer[A, M],
|
||||||
|
overflowStrategy: OverflowStrategy.Synchronous[A] =
|
||||||
|
OverflowStrategy.DropOld(50)
|
||||||
|
) =
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MonixProSubject.from(
|
||||||
|
subject,
|
||||||
|
subject
|
||||||
|
.scan[(A, M)](initialAction -> initialState)(fold)
|
||||||
|
.behavior(initialAction -> initialState)
|
||||||
|
.refCount
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def create[F[_], A, M](
|
||||||
|
initialAction: A,
|
||||||
|
initialState: M,
|
||||||
|
reducer: Reducer[A, M]
|
||||||
|
)(implicit s: Scheduler, F: Sync[F]): F[Observable[(A, M)]] =
|
||||||
|
F.delay {
|
||||||
|
val subject = ConcurrentSubject.publish[A]
|
||||||
|
|
||||||
|
val fold: ((A, M), A) => (A, M) = {
|
||||||
|
case ((_, state), action) => {
|
||||||
|
val (newState, effects) = reducer(state, action)
|
||||||
|
|
||||||
|
effects.subscribe(subject.onNext _)
|
||||||
|
|
||||||
|
action -> newState
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
subject
|
||||||
|
.scan[(A, M)](initialAction -> initialState)(fold)
|
||||||
|
.behavior(initialAction -> initialState)
|
||||||
|
.refCount
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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,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.TodoListComponent
|
||||||
|
import scalafx.collections.ObservableBuffer
|
||||||
|
import scalafx.scene.control.Label
|
||||||
|
import scalafx.scene.layout.HBox
|
||||||
|
import scalafx.scene.paint.Color
|
||||||
|
|
||||||
|
class TodoController(todoListComponent: TodoListComponent) {
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,23 +1,26 @@
|
|||||||
package nova.monadic_sfx.screens
|
package nova.monadic_sfx.screens
|
||||||
|
import akka.actor.typed._
|
||||||
|
import monix.eval.Task
|
||||||
import nova.monadic_sfx.AppTypes
|
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.control._
|
||||||
import scalafx.scene.layout.VBox
|
|
||||||
import scalafx.scene.Node
|
|
||||||
import scalafx.Includes._
|
|
||||||
import scalafx.scene.layout.HBox
|
import scalafx.scene.layout.HBox
|
||||||
import scalafx.scene.text.Text
|
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(
|
class HomeScreen(
|
||||||
backend: AppTypes.HttpBackend,
|
backend: AppTypes.HttpBackend,
|
||||||
system: ActorSystem[SpawnProtocol.Command],
|
system: ActorSystem[SpawnProtocol.Command],
|
||||||
onLogout: () => Task[Unit]
|
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 =>
|
private lazy val root = Task.deferAction { implicit s =>
|
||||||
Task {
|
Task {
|
||||||
new HBox {
|
new HBox {
|
||||||
@ -25,15 +28,14 @@ class HomeScreen(
|
|||||||
new Text {
|
new Text {
|
||||||
text = "hello"
|
text = "hello"
|
||||||
},
|
},
|
||||||
new Button {
|
myButton
|
||||||
text = "logout"
|
|
||||||
onAction = () => Action.asyncT(onLogout())
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def render = root
|
def render = root
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
object HomeScreen {
|
object HomeScreen {
|
||||||
|
@ -1,26 +1,19 @@
|
|||||||
package nova.monadic_sfx.screens
|
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.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.TextField
|
||||||
import scalafx.scene.control._
|
import scalafx.scene.control._
|
||||||
import scalafx.scene.layout.VBox
|
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 io.odin.syntax._
|
||||||
// import _root_.monix.eval.Task
|
// import _root_.monix.eval.Task
|
||||||
// import io.odin.monix._
|
// import io.odin.monix._
|
||||||
@ -62,7 +55,7 @@ class LoginScreen(
|
|||||||
_ <- Task { stage.scene().setRoot(homeScreen) }
|
_ <- Task { stage.scene().setRoot(homeScreen) }
|
||||||
} yield ()
|
} yield ()
|
||||||
|
|
||||||
private lazy val root = Task.deferAction(implicit scheduler =>
|
private lazy val root = Task.deferAction(implicit s =>
|
||||||
Task {
|
Task {
|
||||||
new VBox {
|
new VBox {
|
||||||
children = Seq(
|
children = Seq(
|
||||||
@ -76,6 +69,7 @@ class LoginScreen(
|
|||||||
new TextField(),
|
new TextField(),
|
||||||
new Button {
|
new Button {
|
||||||
text = "Login"
|
text = "Login"
|
||||||
|
// onAction.-->
|
||||||
onAction = () =>
|
onAction = () =>
|
||||||
Action.asyncT {
|
Action.asyncT {
|
||||||
Task
|
Task
|
||||||
@ -95,7 +89,7 @@ class LoginScreen(
|
|||||||
def render: Task[Parent] = root
|
def render: Task[Parent] = root
|
||||||
|
|
||||||
val testRequest = for {
|
val testRequest = for {
|
||||||
res <- dummyRequester.send()
|
res <- dummyRequester.send
|
||||||
_ <- logger.info(res.body.toString())
|
_ <- logger.info(res.body.toString())
|
||||||
} yield ()
|
} yield ()
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package nova.monadic_sfx.ui.screens
|
package nova.monadic_sfx.ui.screens
|
||||||
|
|
||||||
|
import monix.eval.Task
|
||||||
import scalafx.application.JFXApp.PrimaryStage
|
import scalafx.application.JFXApp.PrimaryStage
|
||||||
import scalafx.scene.Parent
|
import scalafx.scene.Parent
|
||||||
import monix.eval.Task
|
|
||||||
|
|
||||||
trait Screen {
|
trait Screen {
|
||||||
protected def appStage: PrimaryStage
|
protected def appStage: PrimaryStage
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
package nova.monadic_sfx.util
|
package nova.monadic_sfx.util
|
||||||
|
|
||||||
import monix.eval.Task
|
|
||||||
import monix.execution.Scheduler
|
|
||||||
import cats.effect.Effect
|
import cats.effect.Effect
|
||||||
import cats.effect.implicits._
|
import cats.effect.implicits._
|
||||||
|
import monix.eval.Task
|
||||||
|
import monix.execution.Scheduler
|
||||||
|
|
||||||
object Action {
|
object Action {
|
||||||
|
|
||||||
|
21
src/main/scala/nova/monadic_sfx/util/IOUtils.scala
Normal file
21
src/main/scala/nova/monadic_sfx/util/IOUtils.scala
Normal file
@ -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)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +1,19 @@
|
|||||||
package org.slf4j.impl
|
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.ExecutionContext
|
||||||
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
import _root_.monix.execution.Scheduler
|
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
|
//effect type should be specified inbefore
|
||||||
//log line will be recorded right after the call with no suspension
|
//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 cs: ContextShift[IO] = IO.contextShift(ec)
|
||||||
implicit val F: Effect[IO] = IO.ioEffect
|
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]] = {
|
val loggers: PartialFunction[String, Logger[IO]] = {
|
||||||
case "some.external.package.SpecificClass" =>
|
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
|
case asyncHttpClient
|
||||||
if asyncHttpClient.startsWith("org.asynchttpclient.netty") =>
|
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
|
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 {
|
object StaticLoggerBinder extends StaticLoggerBinder {
|
||||||
|
|
||||||
var REQUESTED_API_VERSION: String = "1.7"
|
val REQUESTED_API_VERSION: String = "1.7"
|
||||||
|
|
||||||
def getSingleton: StaticLoggerBinder = this
|
def getSingleton: StaticLoggerBinder = this
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user