From b988ad267ecb5d36918f6c93ba87aebd1c2a8952 Mon Sep 17 00:00:00 2001 From: Rohan Sircar Date: Tue, 15 Dec 2020 12:51:25 +0530 Subject: [PATCH] Added store pattern using monix also added relevant implicits to use it --- .gitignore | 2 + build.sbt | 56 +- project/build.properties | 2 +- project/plugin.sbt | 1 + src/main/scala/nova/monadic_sfx/Main.scala | 58 +- src/main/scala/nova/monadic_sfx/MainApp.scala | 159 +++++ .../scala/nova/monadic_sfx/MainModule.scala | 2 +- src/main/scala/nova/monadic_sfx/Types.scala | 9 +- .../nova/monadic_sfx/actors/ActorModule.scala | 70 +-- .../nova/monadic_sfx/actors/TestActor.scala | 20 +- .../monadic_sfx/executors/GUIExecutor.scala | 26 +- .../monadic_sfx/executors/Schedulers.scala | 25 +- .../nova/monadic_sfx/http/HttpModule.scala | 4 +- .../http/requests/DummyRequest.scala | 38 +- .../implicits/JavaFxMonixObservables.scala | 205 +++++++ .../nova/monadic_sfx/implicits/package.scala | 50 ++ .../nova/monadic_sfx/models/DummyModels.scala | 7 +- .../scala/nova/monadic_sfx/ui/DefaultUI.scala | 65 +- .../scala/nova/monadic_sfx/ui/MyFxApp.scala | 144 +---- .../nova/monadic_sfx/ui/MyFxAppOld.scala | 129 ++++ .../scala/nova/monadic_sfx/ui/UiModule.scala | 18 +- .../components/todo/TodoListComponent.scala | 556 ++++++++++++++++++ .../ui/controller/TodoController.scala | 95 +++ .../monadic_sfx/ui/screens/HomeScreen.scala | 28 +- .../monadic_sfx/ui/screens/LoginScreen.scala | 34 +- .../nova/monadic_sfx/ui/screens/Screen.scala | 2 +- .../scala/nova/monadic_sfx/util/Action.scala | 4 +- .../scala/nova/monadic_sfx/util/IOUtils.scala | 21 + .../org/slf4j/impl/StaticLoggerBuilder.scala | 76 ++- 29 files changed, 1535 insertions(+), 371 deletions(-) create mode 100644 src/main/scala/nova/monadic_sfx/MainApp.scala create mode 100644 src/main/scala/nova/monadic_sfx/implicits/JavaFxMonixObservables.scala create mode 100644 src/main/scala/nova/monadic_sfx/implicits/package.scala create mode 100644 src/main/scala/nova/monadic_sfx/ui/MyFxAppOld.scala create mode 100644 src/main/scala/nova/monadic_sfx/ui/components/todo/TodoListComponent.scala create mode 100644 src/main/scala/nova/monadic_sfx/ui/controller/TodoController.scala create mode 100644 src/main/scala/nova/monadic_sfx/util/IOUtils.scala diff --git a/.gitignore b/.gitignore index cbda843..9054cba 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,5 @@ metals.sbt .idea/ .vscode /project/project + +.bsp diff --git a/build.sbt b/build.sbt index 2c8a9a5..fdacc62 100644 --- a/build.sbt +++ b/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 + ) +) diff --git a/project/build.properties b/project/build.properties index d1d2638..9061e4e 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1,2 +1,2 @@ -sbt.version=1.3.10 +sbt.version=1.4.3 diff --git a/project/plugin.sbt b/project/plugin.sbt index 4d48037..b7d325d 100644 --- a/project/plugin.sbt +++ b/project/plugin.sbt @@ -4,3 +4,4 @@ scalacOptions ++= Seq("-unchecked", "-deprecation") addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "5.2.4") // addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.10") addSbtPlugin("com.quadstingray" % "sbt-javafx" % "1.5.2") +addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.23") diff --git a/src/main/scala/nova/monadic_sfx/Main.scala b/src/main/scala/nova/monadic_sfx/Main.scala index a81c0e4..66d4752 100644 --- a/src/main/scala/nova/monadic_sfx/Main.scala +++ b/src/main/scala/nova/monadic_sfx/Main.scala @@ -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) - } + } diff --git a/src/main/scala/nova/monadic_sfx/MainApp.scala b/src/main/scala/nova/monadic_sfx/MainApp.scala new file mode 100644 index 0000000..d8a101d --- /dev/null +++ b/src/main/scala/nova/monadic_sfx/MainApp.scala @@ -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 } + } + ) + } +} diff --git a/src/main/scala/nova/monadic_sfx/MainModule.scala b/src/main/scala/nova/monadic_sfx/MainModule.scala index 93b5cca..03c4c80 100644 --- a/src/main/scala/nova/monadic_sfx/MainModule.scala +++ b/src/main/scala/nova/monadic_sfx/MainModule.scala @@ -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 diff --git a/src/main/scala/nova/monadic_sfx/Types.scala b/src/main/scala/nova/monadic_sfx/Types.scala index f68926c..273b2e0 100644 --- a/src/main/scala/nova/monadic_sfx/Types.scala +++ b/src/main/scala/nova/monadic_sfx/Types.scala @@ -1,13 +1,14 @@ package nova.monadic_sfx +import java.nio.ByteBuffer + import monix.eval.Task -import sttp.client.SttpBackend import monix.reactive.Observable +import sttp.client.SttpBackend import sttp.client.asynchttpclient.WebSocketHandler -import java.nio.ByteBuffer -trait AppTypes {} -object AppTypes { +trait AppTypes { type HttpBackend = SttpBackend[Task, Observable[ByteBuffer], WebSocketHandler] } +object AppTypes extends AppTypes {} diff --git a/src/main/scala/nova/monadic_sfx/actors/ActorModule.scala b/src/main/scala/nova/monadic_sfx/actors/ActorModule.scala index 9e535ca..d31df3a 100644 --- a/src/main/scala/nova/monadic_sfx/actors/ActorModule.scala +++ b/src/main/scala/nova/monadic_sfx/actors/ActorModule.scala @@ -1,74 +1,28 @@ package nova.monadic_sfx.actors -import io.odin.Logger -import monix.eval.Task -import cats.effect.Resource -import akka.actor.typed.scaladsl.Behaviors -import com.softwaremill.macwire._ -import akka.util.Timeout import scala.concurrent.duration._ -import scala.concurrent.Future + import akka.actor.typed._ -import akka.actor.typed.scaladsl.AskPattern._ -import scala.concurrent.Await -import nova.monadic_sfx.executors.Schedulers +import akka.util.Timeout +import cats.effect.Resource +import io.odin.Logger +import monix.bio.Task trait ActorModule { - import scala.concurrent.ExecutionContext - implicit val timeout: Timeout = Timeout(3.seconds) + implicit def timeout: Timeout = Timeout(3.seconds) def actorSystemResource( logger: Logger[Task] ): Resource[Task, ActorSystem[SpawnProtocol.Command]] = Resource.make(logger.info("Creating Actor System") >> Task { - ActorSystem(HelloWorldMain(), name = "FXActorSystem") + ActorSystem(SpawnProtocol(), name = "FXActorSystem") })(sys => - logger.info("Shutting down actor system") >> Task( - sys.terminate() - ) >> logger.info("Actor System terminated") + for { + _ <- Task(sys.terminate()) + _ <- Task.fromFuture(sys.whenTerminated) + _ <- logger.info("Actor System Terminated") + } yield () ) - // def actorsResource( - // system: ActorSystem[SpawnProtocol.Command], - // logger: Logger[Task], - // schedulers: Schedulers - // ): Resource[Task, Task[ActorRef[Counter.Command]]] = { - // implicit val ec: ExecutionContext = system.executionContext - // implicit val scheduler = system.scheduler - // Resource.make( - // Task { - // val actor = Task.deferFuture { - // system.ask[ActorRef[Counter.Command]]( - // SpawnProtocol.Spawn( - // behavior = Counter(), - // name = "counterActor", - // // DispatcherSelector.fromConfig("javafx-dispatcher"), - // // Props.empty, - // _ - // ) - // ) - // } - // // system. - // actor - // } - // )(actorTask => - // for { - // actor <- actorTask - // _ <- logger.info("Stopping actor counter") - // t <- Task(actor ! Counter.Stop) - // _ <- logger.info("Counter actor stopped") - // } yield () - // ) - // } - -} -object HelloWorldMain { - def apply(): Behavior[SpawnProtocol.Command] = - Behaviors.setup { context => - // Start initial tasks - // context.spawn(...) - - SpawnProtocol() - } } diff --git a/src/main/scala/nova/monadic_sfx/actors/TestActor.scala b/src/main/scala/nova/monadic_sfx/actors/TestActor.scala index 15ec3ca..878bad6 100644 --- a/src/main/scala/nova/monadic_sfx/actors/TestActor.scala +++ b/src/main/scala/nova/monadic_sfx/actors/TestActor.scala @@ -36,15 +36,13 @@ class Counter(context: ActorContext[Counter.Command]) } } - override def onSignal: PartialFunction[Signal, Behavior[Counter.Command]] = - PartialFunction.fromFunction((signal: Signal) => { - signal match { - case _: Terminated => - context.log.info("Recieved shutdown counter actor terminated") - this - case PostStop => - context.log.info("Recieved shutdown counter actor poststop") - this - } - }) + override def onSignal: PartialFunction[Signal, Behavior[Counter.Command]] = { + case _: Terminated => + context.log.info("Recieved shutdown counter actor terminated") + this + case PostStop => + context.log.info("Recieved shutdown counter actor poststop") + this + } + } diff --git a/src/main/scala/nova/monadic_sfx/executors/GUIExecutor.scala b/src/main/scala/nova/monadic_sfx/executors/GUIExecutor.scala index 98085ac..3affbb2 100644 --- a/src/main/scala/nova/monadic_sfx/executors/GUIExecutor.scala +++ b/src/main/scala/nova/monadic_sfx/executors/GUIExecutor.scala @@ -1,23 +1,21 @@ package nova.monadic_sfx.executors -import akka.dispatch.{ - DispatcherPrerequisites, - ExecutorServiceFactory, - ExecutorServiceConfigurator -} -import com.typesafe.config.Config -import java.util.concurrent.{ - ExecutorService, - AbstractExecutorService, - ThreadFactory, - TimeUnit -} import java.util.Collections +import java.util.concurrent.AbstractExecutorService +import java.util.concurrent.Executor +import java.util.concurrent.ExecutorService +import java.util.concurrent.ThreadFactory +import java.util.concurrent.TimeUnit import javax.swing.SwingUtilities + +import scala.concurrent.ExecutionContext + +import akka.dispatch.DispatcherPrerequisites +import akka.dispatch.ExecutorServiceConfigurator +import akka.dispatch.ExecutorServiceFactory +import com.typesafe.config.Config import javafx.application.Platform import monix.execution.Scheduler -import scala.concurrent.ExecutionContext -import java.util.concurrent.Executor // First we wrap invokeLater/runLater as an ExecutorService trait GUIExecutorService extends AbstractExecutorService { diff --git a/src/main/scala/nova/monadic_sfx/executors/Schedulers.scala b/src/main/scala/nova/monadic_sfx/executors/Schedulers.scala index f8eaea2..e19359a 100644 --- a/src/main/scala/nova/monadic_sfx/executors/Schedulers.scala +++ b/src/main/scala/nova/monadic_sfx/executors/Schedulers.scala @@ -1,9 +1,28 @@ package nova.monadic_sfx.executors +import com.typesafe.scalalogging.Logger import monix.execution.Scheduler +import monix.execution.UncaughtExceptionReporter +import monix.execution.schedulers.TracingScheduler class Schedulers( - val blockingIO: Scheduler = Scheduler.io(), - val cpu: Scheduler = Scheduler.global, - val fx: Scheduler = JFXExecutionContexts.fxScheduler + val blocking: Scheduler = TracingScheduler( + Scheduler + .io() + .withUncaughtExceptionReporter(Schedulers.reporter) + ), + val async: Scheduler = Scheduler.traced + .withUncaughtExceptionReporter(Schedulers.reporter), + val fx: Scheduler = TracingScheduler( + JFXExecutionContexts.fxScheduler + .withUncaughtExceptionReporter(Schedulers.reporter) + ) ) + +object Schedulers { + val reporter = UncaughtExceptionReporter { ex => + val logger = Logger[Schedulers] + logger.error("Uncaught exception", ex) + } + +} diff --git a/src/main/scala/nova/monadic_sfx/http/HttpModule.scala b/src/main/scala/nova/monadic_sfx/http/HttpModule.scala index 553f1ca..b539384 100644 --- a/src/main/scala/nova/monadic_sfx/http/HttpModule.scala +++ b/src/main/scala/nova/monadic_sfx/http/HttpModule.scala @@ -1,8 +1,8 @@ package nova.monadic_sfx.http -import nova.monadic_sfx.http.requests.DummyRequest -import nova.monadic_sfx.AppTypes import akka.actor.typed._ +import nova.monadic_sfx.AppTypes +import nova.monadic_sfx.http.requests.DummyRequest trait HttpModule { def requesters( diff --git a/src/main/scala/nova/monadic_sfx/http/requests/DummyRequest.scala b/src/main/scala/nova/monadic_sfx/http/requests/DummyRequest.scala index f3fa76b..7a10067 100644 --- a/src/main/scala/nova/monadic_sfx/http/requests/DummyRequest.scala +++ b/src/main/scala/nova/monadic_sfx/http/requests/DummyRequest.scala @@ -1,43 +1,17 @@ package nova.monadic_sfx.http.requests import nova.monadic_sfx.AppTypes - import nova.monadic_sfx.AppTypes.HttpBackend -import monix.eval.Task +import nova.monadic_sfx.models._ import sttp.client._ import sttp.client.circe._ -import io.circe.generic.auto._ -import nova.monadic_sfx.models._ -import cats.data.EitherT class DummyRequest(backend: HttpBackend) extends AppTypes { private implicit val _backend = backend - def send() = { - Task - .suspend { - for { - req <- - basicRequest - .get(uri"https://httpbin.org/get") - .response(asJson[HttpBinResponse]) - .send() - } yield (req) - } - - } + def send = + basicRequest + .get(uri"https://httpbin.org/get") + .response(asJson[HttpBinResponse]) + .send() - def test() = { - for { - res <- send() - res3 <- Task { res.body } - res2 <- Task { - res3.fold( - err => { - err.toString() - }, - value => value.toString() - ) - } - } yield (res3) - } } diff --git a/src/main/scala/nova/monadic_sfx/implicits/JavaFxMonixObservables.scala b/src/main/scala/nova/monadic_sfx/implicits/JavaFxMonixObservables.scala new file mode 100644 index 0000000..660d8e0 --- /dev/null +++ b/src/main/scala/nova/monadic_sfx/implicits/JavaFxMonixObservables.scala @@ -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 + } + } + } +} diff --git a/src/main/scala/nova/monadic_sfx/implicits/package.scala b/src/main/scala/nova/monadic_sfx/implicits/package.scala new file mode 100644 index 0000000..ab34862 --- /dev/null +++ b/src/main/scala/nova/monadic_sfx/implicits/package.scala @@ -0,0 +1,50 @@ +package nova.monadic_sfx + +import javafx.event.ActionEvent +import monix.execution.Ack +import monix.execution.Cancelable +import monix.reactive.Observable +import monix.reactive.OverflowStrategy +import scalafx.scene.control._ + +package object implicits { + + implicit class MyButtonExt(val button: Button) extends AnyVal { + def observableAction(): Observable[ActionEvent] = { + import monix.execution.cancelables.SingleAssignCancelable + Observable.create(OverflowStrategy.Unbounded) { sub => + val c = SingleAssignCancelable() + val l = new javafx.event.EventHandler[ActionEvent] { + override def handle(event: ActionEvent): Unit = { + if (sub.onNext(event) == Ack.Stop) c.cancel() + } + } + + button.onAction = l + c := Cancelable(() => + button.removeEventHandler( + ActionEvent.ACTION, + l + ) + ) + c + } + } + } + + // implicit class NodeExt(val node: Node) { + // def lookup2[T <: SFXDelegate[_]]( + // selector: String + // )(implicit c: ClassTag[T]) = { + // val t = c.runtimeClass + // Option(node.delegate.lookup(selector)) match { + // case Some(value) => + // if (value.getClass == t) Some(value) else None + // case None => None + // } + // } + + // val x = node.lookup2("") + // } + +} diff --git a/src/main/scala/nova/monadic_sfx/models/DummyModels.scala b/src/main/scala/nova/monadic_sfx/models/DummyModels.scala index 554ca0d..97b48e3 100644 --- a/src/main/scala/nova/monadic_sfx/models/DummyModels.scala +++ b/src/main/scala/nova/monadic_sfx/models/DummyModels.scala @@ -1,7 +1,10 @@ package nova.monadic_sfx.models -case class RequestPayload(data: String) -final case class HttpBinResponse( +import io.circe.generic.JsonCodec + +final case class RequestPayload(data: String) + +@JsonCodec final case class HttpBinResponse( url: String, origin: String, headers: Map[String, String] diff --git a/src/main/scala/nova/monadic_sfx/ui/DefaultUI.scala b/src/main/scala/nova/monadic_sfx/ui/DefaultUI.scala index afe0f07..55c857c 100644 --- a/src/main/scala/nova/monadic_sfx/ui/DefaultUI.scala +++ b/src/main/scala/nova/monadic_sfx/ui/DefaultUI.scala @@ -1,55 +1,52 @@ package nova.monadic_sfx.ui +import nova.monadic_sfx.implicits.JFXSpinner import scalafx.geometry.Insets +import scalafx.geometry.Pos import scalafx.scene.Scene import scalafx.scene.effect.DropShadow import scalafx.scene.layout.HBox +import scalafx.scene.layout.VBox import scalafx.scene.paint.Color._ import scalafx.scene.paint._ import scalafx.scene.text.Text -import monix.eval.Coeval -class DefaultUI { +object DefaultUI { val scene = new Scene { fill = Color.rgb(38, 38, 38) - content = new HBox { + content = new VBox { + alignment = Pos.Center padding = Insets(50, 80, 50, 80) children = Seq( - new Text { - text = "Scala" - style = "-fx-font: normal bold 100pt sans-serif" - fill = new LinearGradient(endX = 0, stops = Stops(Red, DarkRed)) - }, - new Text { - text = "FX" - style = "-fx-font: italic bold 100pt sans-serif" - fill = new LinearGradient( - endX = 0, - stops = Stops(White, DarkGray) + new HBox { + padding = Insets(50, 80, 50, 80) + children = Seq( + new Text { + text = "Scala" + style = "-fx-font: normal bold 100pt sans-serif" + fill = new LinearGradient(endX = 0, stops = Stops(Red, DarkRed)) + }, + new Text { + text = "FX" + style = "-fx-font: italic bold 100pt sans-serif" + fill = new LinearGradient( + endX = 0, + stops = Stops(White, DarkGray) + ) + effect = new DropShadow { + color = DarkGray + radius = 15 + spread = 0.25 + } + } ) - effect = new DropShadow { - color = DarkGray - radius = 15 - spread = 0.25 - } + }, + new JFXSpinner { + radius = 50 + // style = "-fx-text-fill: red" } ) } } - -// val program = Coeval -// .suspend { -// Coeval(println("hello")) >> -// Coeval(println(Thread.currentThread().getName())) >> -// Coeval { -// stage = new PrimaryStage { -// // initStyle(StageStyle.Unified) -// title = "ScalaFX Hello World" -// scene = scn - -// } -// } -// } - } diff --git a/src/main/scala/nova/monadic_sfx/ui/MyFxApp.scala b/src/main/scala/nova/monadic_sfx/ui/MyFxApp.scala index a178a56..186039f 100644 --- a/src/main/scala/nova/monadic_sfx/ui/MyFxApp.scala +++ b/src/main/scala/nova/monadic_sfx/ui/MyFxApp.scala @@ -1,129 +1,41 @@ package nova.monadic_sfx.ui -import scalafx.application.JFXApp -import nova.monadic_sfx.executors.Schedulers -import monix.execution.Scheduler -import monix.eval.Task -import nova.monadic_sfx.screens.LoginScreen -import nova.monadic_sfx.AppTypes import scala.concurrent.duration._ + import io.odin.Logger -import monix.execution.Callback -import com.softwaremill.macwire._ -import nova.monadic_sfx.http.Requesters +import 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 _)) - - // _ <- 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)] { + _ <- logger.debug("Request for using internal value") + res <- f(internal).executeOn(schedulers.fx) + _ <- logger.debug(s"Result was ${res.toString()}") + } yield (res) - 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 + 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) - 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"), - _ - ) - ) - } - } } diff --git a/src/main/scala/nova/monadic_sfx/ui/MyFxAppOld.scala b/src/main/scala/nova/monadic_sfx/ui/MyFxAppOld.scala new file mode 100644 index 0000000..66556a9 --- /dev/null +++ b/src/main/scala/nova/monadic_sfx/ui/MyFxAppOld.scala @@ -0,0 +1,129 @@ +package nova.monadic_sfx.ui + +import scala.concurrent.duration._ + +import akka.actor.typed._ +import akka.util.Timeout +import com.softwaremill.macwire._ +import io.odin.Logger +import monix.eval.Task +import monix.execution.Callback +import monix.execution.Scheduler +import nova.monadic_sfx.AppTypes +import nova.monadic_sfx.actors.Counter +import nova.monadic_sfx.executors.Schedulers +import nova.monadic_sfx.http.Requesters +import scalafx.application.JFXApp + +class MyFxAppOld( + logger: Logger[Task], + backend: AppTypes.HttpBackend, + actorSystem: ActorSystem[SpawnProtocol.Command], + requesters: Requesters, + schedulers: Schedulers +) extends JFXApp { + + implicit lazy val defaultScheduler: Scheduler = schedulers.fx + + // lazy val fxActor: Task[ActorRef[Counter.Command]] = wireWith( + // MyFxApp.makeCounterActor _ + // ) + + lazy val application = + for { + appStage <- Task(wireWith(UiModule.makePrimaryStage _)) + + // _ <- Task { + // val counterActor = testActor(actorSystem) + // counterActor ! (Counter.Increment) + // } + // ta <- testActor2(actorSystem) + // actor <- + // actorTask.bracket(actor => Task(actor ! (Counter.Increment)))(actor => + // Task(actor ! (Counter.Stop)) + // ) + // actor <- actorTask + // actor <- fxActor + // _ <- Task(actor ! Counter.Increment) + _ <- Task { stage = appStage } + _ <- Task.sleep(2.seconds) + // loginScene <- wire[LoginScreen].render + // _ <- Task { + // // appStage.maximized = true + // appStage.height = 800 + // appStage.width = 800 + // appStage + // .scene() + // .setRoot( + // loginScene + // ) + // } + } yield () + +// def testActor( +// system: ActorSystem +// ): akka.actor.typed.ActorRef[Counter.Command] = { +// val behaviour: Behavior[Counter.Command] = +// Behaviors.setup(context => wire[Counter]) +// system.spawn( +// behaviour, +// "CounterActor", +// DispatcherSelector.fromConfig("javafx-dispatcher") +// ) +// } + + application.timed.runAsync( + new Callback[Throwable, (FiniteDuration, Unit)] { + + override def onSuccess(value: (FiniteDuration, Unit)): Unit = { + val (duration, _) = value + println( + s"Application started successfully in ${duration.toSeconds} seconds" + ) + } + + override def onError(e: Throwable): Unit = { + println("Application start failed. Reason -") + e.printStackTrace() + } + + } + ) + + override def stopApp() = { + // val stop = for { + // actor <- fxActor + // _ <- logger.info("Stopping actor counter") + // // _ <- Task.fromFuture { actor.ask[Counter.Value](Counter.GetValue) } + // t <- Task(actor ! Counter.Stop) + // // _ <- Task.sleep(1.second) + // _ <- logger.info("Counter actor stopped") + // } yield () + // stop.runAsyncAndForget + // // Platform.exit() + } +} + +object MyFxAppOld { + def makeCounterActor( + system: ActorSystem[SpawnProtocol.Command], + logger: Logger[Task] + ): Task[ActorRef[Counter.Command]] = { + import akka.actor.typed.scaladsl.AskPattern._ + import scala.concurrent.ExecutionContext + + implicit val timeout: Timeout = Timeout(3.seconds) + implicit val ec: ExecutionContext = system.executionContext + implicit val scheduler = system.scheduler + Task.fromFuture { + system.ask( + SpawnProtocol.Spawn( + behavior = wireWith(Counter.apply _), + name = "counterActor", + DispatcherSelector.fromConfig("javafx-dispatcher"), + _ + ) + ) + } + } +} diff --git a/src/main/scala/nova/monadic_sfx/ui/UiModule.scala b/src/main/scala/nova/monadic_sfx/ui/UiModule.scala index d032b32..b1f6f0d 100644 --- a/src/main/scala/nova/monadic_sfx/ui/UiModule.scala +++ b/src/main/scala/nova/monadic_sfx/ui/UiModule.scala @@ -1,15 +1,15 @@ package nova.monadic_sfx.ui -import scalafx.application.JFXApp -import monix.eval.Task -import nova.monadic_sfx.AppTypes -import scalafx.application.JFXApp.PrimaryStage -import io.odin.Logger +import akka.actor.typed._ import cats.effect.Resource import com.softwaremill.macwire._ -import nova.monadic_sfx.http.Requesters +import io.odin.Logger +import monix.eval.Task +import nova.monadic_sfx.AppTypes import nova.monadic_sfx.executors.Schedulers -import akka.actor.typed._ +import nova.monadic_sfx.http.Requesters +import scalafx.application.JFXApp +import scalafx.application.JFXApp.PrimaryStage trait UiModule { def fxAppResource( @@ -21,7 +21,7 @@ trait UiModule { ): Resource[Task, JFXApp] = Resource.make(for { _ <- logger.info("Creating FX Application") - app <- Task { wire[MyFxApp] } + app <- Task { wire[MyFxAppOld] } } yield (app))(app => logger.info("Stopping FX Application")) } @@ -31,7 +31,7 @@ object UiModule { actorSystem: ActorSystem[SpawnProtocol.Command] ) = { new PrimaryStage { - scene = new DefaultUI().scene + scene = DefaultUI.scene } } } diff --git a/src/main/scala/nova/monadic_sfx/ui/components/todo/TodoListComponent.scala b/src/main/scala/nova/monadic_sfx/ui/components/todo/TodoListComponent.scala new file mode 100644 index 0000000..d6365cd --- /dev/null +++ b/src/main/scala/nova/monadic_sfx/ui/components/todo/TodoListComponent.scala @@ -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 { +// fun dispatch(store: Store, 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 +// } +// } diff --git a/src/main/scala/nova/monadic_sfx/ui/controller/TodoController.scala b/src/main/scala/nova/monadic_sfx/ui/controller/TodoController.scala new file mode 100644 index 0000000..a8c2695 --- /dev/null +++ b/src/main/scala/nova/monadic_sfx/ui/controller/TodoController.scala @@ -0,0 +1,95 @@ +package nova.monadic_sfx.ui.controller + +import animatefx.animation.AnimationFX +import animatefx.animation.Bounce +import animatefx.animation.FadeIn +import animatefx.util.{SequentialAnimationFX => SeqFX} +import cats.effect.Sync +import monix.eval.Task +import nova.monadic_sfx.implicits.FontIcon +import nova.monadic_sfx.implicits.IconLiteral +import nova.monadic_sfx.implicits.JFXButton +import nova.monadic_sfx.implicits.JFXListView +import nova.monadic_sfx.implicits.JFXTextArea +import nova.monadic_sfx.implicits.JFXTextField +import nova.monadic_sfx.ui.components.todo.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()) + } + } +} diff --git a/src/main/scala/nova/monadic_sfx/ui/screens/HomeScreen.scala b/src/main/scala/nova/monadic_sfx/ui/screens/HomeScreen.scala index ec27705..8544876 100644 --- a/src/main/scala/nova/monadic_sfx/ui/screens/HomeScreen.scala +++ b/src/main/scala/nova/monadic_sfx/ui/screens/HomeScreen.scala @@ -1,23 +1,26 @@ package nova.monadic_sfx.screens +import akka.actor.typed._ +import monix.eval.Task import nova.monadic_sfx.AppTypes -import scalafx.scene.control.TextField +import nova.monadic_sfx.implicits._ +import scalafx.scene.Parent import scalafx.scene.control._ -import scalafx.scene.layout.VBox -import scalafx.scene.Node -import scalafx.Includes._ import scalafx.scene.layout.HBox import scalafx.scene.text.Text -import scalafx.scene.Parent -import scalafx.application.JFXApp.PrimaryStage -import monix.eval.Task -import nova.monadic_sfx.util.Action -import akka.actor.typed._ class HomeScreen( backend: AppTypes.HttpBackend, system: ActorSystem[SpawnProtocol.Command], onLogout: () => Task[Unit] ) { + val myButton = new Button { + id = "LogoutButton" + text = "logout" + // onAction = () => Action.asyncT(onLogout()) + } + + val myObs = myButton.observableAction() + // myObs.foreachL(_ => ()) private lazy val root = Task.deferAction { implicit s => Task { new HBox { @@ -25,15 +28,14 @@ class HomeScreen( new Text { text = "hello" }, - new Button { - text = "logout" - onAction = () => Action.asyncT(onLogout()) - } + myButton ) } } } + def render = root + } object HomeScreen { diff --git a/src/main/scala/nova/monadic_sfx/ui/screens/LoginScreen.scala b/src/main/scala/nova/monadic_sfx/ui/screens/LoginScreen.scala index 04fc5f8..aa7f6c7 100644 --- a/src/main/scala/nova/monadic_sfx/ui/screens/LoginScreen.scala +++ b/src/main/scala/nova/monadic_sfx/ui/screens/LoginScreen.scala @@ -1,26 +1,19 @@ package nova.monadic_sfx.screens +import akka.actor.typed._ +import io.odin.Logger +import monix.eval.Task import nova.monadic_sfx.AppTypes +import nova.monadic_sfx.executors.Schedulers +import nova.monadic_sfx.http.Requesters +import nova.monadic_sfx.http.requests.DummyRequest +import nova.monadic_sfx.ui.screens.Screen +import nova.monadic_sfx.util.Action +import scalafx.Includes._ +import scalafx.application.JFXApp.PrimaryStage +import scalafx.scene.Parent import scalafx.scene.control.TextField import scalafx.scene.control._ import scalafx.scene.layout.VBox -import scalafx.scene.Node -import scalafx.Includes._ -import scalafx.scene.Parent -import scalafx.application.JFXApp.PrimaryStage -import nova.monadic_sfx.http.requests.DummyRequest -import monix.eval.Task -import monix.execution.Scheduler -import cats.effect.Effect -import cats.effect.implicits._ -import nova.monadic_sfx.util.Action -import io.odin.Logger -import nova.monadic_sfx.http.Requesters -import sttp.client.Response -import nova.monadic_sfx.models.HttpBinResponse -import sttp.client.ResponseError -import nova.monadic_sfx.executors.Schedulers -import nova.monadic_sfx.ui.screens.Screen -import akka.actor.typed._ // import io.odin.syntax._ // import _root_.monix.eval.Task // import io.odin.monix._ @@ -62,7 +55,7 @@ class LoginScreen( _ <- Task { stage.scene().setRoot(homeScreen) } } yield () - private lazy val root = Task.deferAction(implicit scheduler => + private lazy val root = Task.deferAction(implicit s => Task { new VBox { children = Seq( @@ -76,6 +69,7 @@ class LoginScreen( new TextField(), new Button { text = "Login" + // onAction.--> onAction = () => Action.asyncT { Task @@ -95,7 +89,7 @@ class LoginScreen( def render: Task[Parent] = root val testRequest = for { - res <- dummyRequester.send() + res <- dummyRequester.send _ <- logger.info(res.body.toString()) } yield () diff --git a/src/main/scala/nova/monadic_sfx/ui/screens/Screen.scala b/src/main/scala/nova/monadic_sfx/ui/screens/Screen.scala index aa09a6e..b881c08 100644 --- a/src/main/scala/nova/monadic_sfx/ui/screens/Screen.scala +++ b/src/main/scala/nova/monadic_sfx/ui/screens/Screen.scala @@ -1,8 +1,8 @@ package nova.monadic_sfx.ui.screens +import monix.eval.Task import scalafx.application.JFXApp.PrimaryStage import scalafx.scene.Parent -import monix.eval.Task trait Screen { protected def appStage: PrimaryStage diff --git a/src/main/scala/nova/monadic_sfx/util/Action.scala b/src/main/scala/nova/monadic_sfx/util/Action.scala index 7367265..a2507f1 100644 --- a/src/main/scala/nova/monadic_sfx/util/Action.scala +++ b/src/main/scala/nova/monadic_sfx/util/Action.scala @@ -1,9 +1,9 @@ package nova.monadic_sfx.util -import monix.eval.Task -import monix.execution.Scheduler import cats.effect.Effect import cats.effect.implicits._ +import monix.eval.Task +import monix.execution.Scheduler object Action { diff --git a/src/main/scala/nova/monadic_sfx/util/IOUtils.scala b/src/main/scala/nova/monadic_sfx/util/IOUtils.scala new file mode 100644 index 0000000..a3d43b6 --- /dev/null +++ b/src/main/scala/nova/monadic_sfx/util/IOUtils.scala @@ -0,0 +1,21 @@ +package nova.monadic_sfx.util + +import cats.arrow.FunctionK +import monix.bio.IO + +object IOUtils { + def toIO[T](task: monix.eval.Task[T]) = + IO.deferAction(implicit s => IO.from(task)) + + def toTask[T](bio: monix.bio.IO[Throwable, T]) = + monix.eval.Task.deferAction(implicit s => bio.to[monix.eval.Task]) + + val ioTaskMapk = + new FunctionK[monix.eval.Task, monix.bio.Task] { + + override def apply[A]( + fa: monix.eval.Task[A] + ): monix.bio.Task[A] = toIO(fa) + + } +} diff --git a/src/main/scala/org/slf4j/impl/StaticLoggerBuilder.scala b/src/main/scala/org/slf4j/impl/StaticLoggerBuilder.scala index 907e7dc..110fe07 100644 --- a/src/main/scala/org/slf4j/impl/StaticLoggerBuilder.scala +++ b/src/main/scala/org/slf4j/impl/StaticLoggerBuilder.scala @@ -1,11 +1,19 @@ package org.slf4j.impl -import cats.effect.{ContextShift, Clock, Effect, IO, Timer} -import io.odin._ -import io.odin.slf4j.OdinLoggerBinder - import scala.concurrent.ExecutionContext +import scala.concurrent.duration._ + import _root_.monix.execution.Scheduler +import cats.effect.Clock +import cats.effect.ContextShift +import cats.effect.Effect +import cats.effect.IO +import cats.effect.Timer +import cats.implicits._ +import io.odin._ +import io.odin.json.Formatter +import io.odin.slf4j.OdinLoggerBinder +import io.odin.syntax._ //effect type should be specified inbefore //log line will be recorded right after the call with no suspension @@ -17,20 +25,72 @@ class StaticLoggerBinder extends OdinLoggerBinder[IO] { implicit val cs: ContextShift[IO] = IO.contextShift(ec) implicit val F: Effect[IO] = IO.ioEffect + // val loggers: PartialFunction[String, Logger[IO]] = { + // case "some.external.package.SpecificClass" => + // consoleLogger[IO](minLevel = Level.Warn) //disable noisy external logs + // case asyncHttpClient + // if asyncHttpClient.startsWith("org.asynchttpclient.netty") => + // consoleLogger[IO](minLevel = Level.Warn) + // case _ => //if wildcard case isn't provided, default logger is no-op + // consoleLogger[IO]() + // } + // private lazy val (defaultConsoleLogger, release1) = + // consoleLogger[IO](minLevel = Level.Debug) + // .withAsync(timeWindow = 1.milliseconds) + // .allocated + // .unsafeRunSync() + + private lazy val (mainFileLogger, release2) = + fileLogger[IO]( + "application-log-2.log", + Formatter.json, + minLevel = Level.Debug + ).withAsync(timeWindow = 1.milliseconds) + .allocated + .unsafeRunSync() + + sys.addShutdownHook(release2.unsafeRunSync()) + + // private lazy val (eventBusFileLogger, release3) = + // fileLogger[IO]( + // "eventbus.log", + // Formatter.json, + // minLevel = Level.Debug + // ).withAsync(timeWindow = 1.milliseconds) + // .allocated + // .unsafeRunSync() + + // { + // ArraySeq(release1, release2, release3).foreach(r => + // sys.addShutdownHook(r.unsafeRunSync()) + // ) + // } + val loggers: PartialFunction[String, Logger[IO]] = { case "some.external.package.SpecificClass" => - consoleLogger[IO](minLevel = Level.Warn) //disable noisy external logs + //disable noisy external logs + consoleLogger[IO](minLevel = Level.Debug).withMinimalLevel(Level.Warn) case asyncHttpClient if asyncHttpClient.startsWith("org.asynchttpclient.netty") => - consoleLogger[IO](minLevel = Level.Warn) + consoleLogger[IO](minLevel = Level.Debug).withMinimalLevel(Level.Warn) + // case s + // if s.startsWith( + // "wow.doge.mygame.subsystems.movement.PlayerMovementEventHandler" + // ) => + // consoleLogger[IO](minLevel = Level.Debug).withMinimalLevel( Level.Trace) //selectively turn on trace logging for specific classes + // case s if s.startsWith("wow.doge.mygame.events.EventBus") => + // consoleLogger[IO](minLevel = Level.Debug).withMinimalLevel(Level.Debug) |+| eventBusFileLogger + case s if s.startsWith("akka.actor") || s.startsWith("nova.monadic_sfx") => + consoleLogger[IO](minLevel = Level.Debug) + .withMinimalLevel(Level.Debug) |+| mainFileLogger case _ => //if wildcard case isn't provided, default logger is no-op - consoleLogger[IO]() + consoleLogger[IO](minLevel = Level.Debug).withMinimalLevel(Level.Debug) } } object StaticLoggerBinder extends StaticLoggerBinder { - var REQUESTED_API_VERSION: String = "1.7" + val REQUESTED_API_VERSION: String = "1.7" def getSingleton: StaticLoggerBinder = this