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/
|
||||
.vscode
|
||||
/project/project
|
||||
|
||||
.bsp
|
||||
|
56
build.sbt
56
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"
|
||||
@ -17,30 +17,53 @@ 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-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.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" %% "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.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-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(
|
||||
"-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 +82,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
|
||||
)
|
||||
)
|
||||
|
@ -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.eed3si9n" % "sbt-assembly" % "0.14.10")
|
||||
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
|
||||
|
||||
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 scala.concurrent.duration._
|
||||
|
||||
import _root_.monix.bio.BIOApp
|
||||
import _root_.monix.bio.Task
|
||||
import _root_.monix.bio.UIO
|
||||
import cats.effect.ExitCode
|
||||
import cats.effect.Resource
|
||||
import cats.implicits._
|
||||
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 {
|
||||
|
||||
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()
|
||||
def appResource(startTime: Long) =
|
||||
for {
|
||||
implicit0(logger: Logger[Task]) <-
|
||||
consoleLogger().withAsync(timeWindow = 1.millis) |+| fileLogger(
|
||||
"application.log"
|
||||
).withAsync()
|
||||
schedulers = new Schedulers()
|
||||
backend <- Resource.make(
|
||||
toIO(HttpClientMonixBackend()(schedulers.async))
|
||||
)(c => toIO(c.close()))
|
||||
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)))
|
||||
_ <- Resource.liftF(wire[MainApp].program)
|
||||
} yield ()
|
||||
|
||||
override def run(args: List[String]): UIO[ExitCode] =
|
||||
appResource(System.currentTimeMillis())
|
||||
.use(_ => Task.unit)
|
||||
.onErrorHandle(_.printStackTrace())
|
||||
.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
|
||||
|
||||
import nova.monadic_sfx.actors.ActorModule
|
||||
import nova.monadic_sfx.ui.UiModule
|
||||
import nova.monadic_sfx.http.HttpModule
|
||||
import nova.monadic_sfx.ui.UiModule
|
||||
|
||||
trait MainModule extends ActorModule with UiModule with HttpModule
|
||||
|
@ -1,13 +1,14 @@
|
||||
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
|
||||
|
||||
trait AppTypes {}
|
||||
object AppTypes {
|
||||
import monix.eval.Task
|
||||
import monix.reactive.Observable
|
||||
import sttp.client.SttpBackend
|
||||
import sttp.client.asynchttpclient.WebSocketHandler
|
||||
|
||||
trait AppTypes {
|
||||
type HttpBackend =
|
||||
SttpBackend[Task, Observable[ByteBuffer], WebSocketHandler]
|
||||
}
|
||||
object AppTypes extends AppTypes {}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
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]
|
||||
|
@ -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
|
||||
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
||||
|
@ -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 monix.bio.Task
|
||||
import nova.monadic_sfx.executors.Schedulers
|
||||
import nova.monadic_sfx.ui.DefaultUI
|
||||
import scalafx.application.JFXApp
|
||||
import scalafx.application.JFXApp.PrimaryStage
|
||||
|
||||
import akka.actor.typed._
|
||||
import nova.monadic_sfx.actors.Counter
|
||||
import akka.util.Timeout
|
||||
class MyFxApp(val schedulers: Schedulers)(implicit logger: Logger[Task]) {
|
||||
|
||||
class MyFxApp(
|
||||
logger: Logger[Task],
|
||||
backend: AppTypes.HttpBackend,
|
||||
actorSystem: ActorSystem[SpawnProtocol.Command],
|
||||
requesters: Requesters,
|
||||
schedulers: Schedulers
|
||||
) extends JFXApp {
|
||||
private lazy val internal = new JFXApp {
|
||||
stage = new PrimaryStage {
|
||||
scene = DefaultUI.scene
|
||||
}
|
||||
}
|
||||
|
||||
implicit lazy val defaultScheduler: Scheduler = schedulers.fx
|
||||
// def stage = Task(internal.stage)
|
||||
|
||||
lazy val fxActor: Task[ActorRef[Counter.Command]] = wireWith(
|
||||
MyFxApp.makeCounterActor _
|
||||
)
|
||||
// def stage_=(stage: PrimaryStage) = Task(internal.stage = stage)
|
||||
|
||||
lazy val application =
|
||||
def useInternal[T](f: JFXApp => Task[T]): Task[T] =
|
||||
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 {
|
||||
// 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 init(stage: => PrimaryStage, delay: FiniteDuration = 2000.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)
|
||||
|
||||
// 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
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
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 {
|
||||
|
@ -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 ()
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
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
|
||||
|
||||
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…
Reference in New Issue
Block a user