Added store pattern using monix

also added relevant implicits to use it
This commit is contained in:
Rohan Sircar 2020-12-15 12:51:25 +05:30
parent 536f1b0af3
commit b988ad267e
29 changed files with 1537 additions and 373 deletions

2
.gitignore vendored
View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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