From e3abe03456577a1bda692caa584589c76e1a456c Mon Sep 17 00:00:00 2001 From: Rohan Sircar Date: Thu, 27 Aug 2020 19:58:18 +0530 Subject: [PATCH] First commit --- .gitignore | 24 ++ .scalafmt.conf | 1 + UNLICENSE | 24 ++ build.sbt | 61 +++++ project/build.properties | 2 + project/plugin.sbt | 6 + src/main/resources/application.conf | 5 + .../scala/nova/monadic_sfx/SFXActors.scala | 219 ++++++++++++++++++ src/main/scala/nova/monadic_sfx/Types.scala | 13 ++ .../monadic_sfx/executors/GUIExecutor.scala | 84 +++++++ .../monadic_sfx/executors/Schedulers.scala | 9 + .../scala/nova/monadic_sfx/http/Backend.scala | 10 + .../http/requests/DummyRequest.scala | 27 +++ .../nova/monadic_sfx/models/DummyModels.scala | 8 + .../nova/monadic_sfx/modules/MainModule.scala | 79 +++++++ .../nova/monadic_sfx/pages/HomePage.scala | 39 ++++ .../nova/monadic_sfx/pages/LoginPage.scala | 57 +++++ .../scala/nova/monadic_sfx/ui/DefaultUI.scala | 55 +++++ .../org/slf4j/impl/StaticLoggerBuilder.scala | 36 +++ 19 files changed, 759 insertions(+) create mode 100644 .gitignore create mode 100644 .scalafmt.conf create mode 100644 UNLICENSE create mode 100644 build.sbt create mode 100644 project/build.properties create mode 100644 project/plugin.sbt create mode 100644 src/main/resources/application.conf create mode 100644 src/main/scala/nova/monadic_sfx/SFXActors.scala create mode 100644 src/main/scala/nova/monadic_sfx/Types.scala create mode 100644 src/main/scala/nova/monadic_sfx/executors/GUIExecutor.scala create mode 100644 src/main/scala/nova/monadic_sfx/executors/Schedulers.scala create mode 100644 src/main/scala/nova/monadic_sfx/http/Backend.scala create mode 100644 src/main/scala/nova/monadic_sfx/http/requests/DummyRequest.scala create mode 100644 src/main/scala/nova/monadic_sfx/models/DummyModels.scala create mode 100644 src/main/scala/nova/monadic_sfx/modules/MainModule.scala create mode 100644 src/main/scala/nova/monadic_sfx/pages/HomePage.scala create mode 100644 src/main/scala/nova/monadic_sfx/pages/LoginPage.scala create mode 100644 src/main/scala/nova/monadic_sfx/ui/DefaultUI.scala create mode 100644 src/main/scala/org/slf4j/impl/StaticLoggerBuilder.scala diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cbda843 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +*.class +*.log + +# sbt specific +.cache/ +.history/ +.lib/ +dist/* +target/ +lib_managed/ +src_managed/ +project/boot/ +project/plugins/project/ +metals.sbt +.metals +.bloop + +# Scala-IDE specific +.scala_dependencies +.worksheet + +.idea/ +.vscode +/project/project diff --git a/.scalafmt.conf b/.scalafmt.conf new file mode 100644 index 0000000..ba14fb7 --- /dev/null +++ b/.scalafmt.conf @@ -0,0 +1 @@ +version = "2.6.4" diff --git a/UNLICENSE b/UNLICENSE new file mode 100644 index 0000000..fdddb29 --- /dev/null +++ b/UNLICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/build.sbt b/build.sbt new file mode 100644 index 0000000..2c8a9a5 --- /dev/null +++ b/build.sbt @@ -0,0 +1,61 @@ +// Name of the project +name := "ScalaFX Hello World" + +// Project version +version := "14-R19" + +// Version of Scala used by the project +scalaVersion := "2.13.3" + +// Add dependency on ScalaFX library +libraryDependencies += "org.scalafx" %% "scalafx" % "14-R19" +resolvers += Resolver.sonatypeRepo("snapshots") + +enablePlugins(JavaFxPlugin) + +libraryDependencies ++= Seq( + "org.typelevel" %% "cats-core" % "2.1.1", + "org.typelevel" %% "cats-effect" % "2.1.4", + "io.monix" %% "monix" % "3.2.2", + "io.monix" %% "monix-bio" % "1.0.0", + "io.circe" %% "circe-core" % "0.13.0", + "io.circe" %% "circe-generic" % "0.13.0", + "com.softwaremill.sttp.client" %% "core" % "2.2.5", + "com.softwaremill.sttp.client" %% "monix" % "2.2.5", + "com.softwaremill.sttp.client" %% "circe" % "2.2.5", + "com.softwaremill.sttp.client" %% "async-http-client-backend-monix" % "2.2.5", + "com.github.valskalla" %% "odin-monix" % "0.8.1", + "com.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" +) + +scalacOptions ++= Seq( + "-unchecked", + "-deprecation", + "-Xcheckinit", + "-encoding", + "utf8", + "-feature", + "-Ywarn-unused:imports" +) + +// Fork a new JVM for 'run' and 'test:run', to avoid JavaFX double initialization problems +fork := true + +// Determine OS version of JavaFX binaries +lazy val osName = System.getProperty("os.name") match { + case n if n.startsWith("Linux") => "linux" + case n if n.startsWith("Mac") => "mac" + case n if n.startsWith("Windows") => "win" + case _ => throw new Exception("Unknown platform!") +} + +// Add JavaFX dependencies +lazy val javaFXModules = + Seq("base", "controls", "fxml", "graphics", "media", "swing", "web") +libraryDependencies ++= javaFXModules.map(m => + "org.openjfx" % s"javafx-$m" % "14.0.1" classifier osName +) diff --git a/project/build.properties b/project/build.properties new file mode 100644 index 0000000..d1d2638 --- /dev/null +++ b/project/build.properties @@ -0,0 +1,2 @@ +sbt.version=1.3.10 + diff --git a/project/plugin.sbt b/project/plugin.sbt new file mode 100644 index 0000000..4d48037 --- /dev/null +++ b/project/plugin.sbt @@ -0,0 +1,6 @@ +scalacOptions ++= Seq("-unchecked", "-deprecation") + +// [https://github.com/sbt/sbteclipse] +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") diff --git a/src/main/resources/application.conf b/src/main/resources/application.conf new file mode 100644 index 0000000..a368af4 --- /dev/null +++ b/src/main/resources/application.conf @@ -0,0 +1,5 @@ +javafx-dispatcher { + type = "Dispatcher" + executor = "akka.dispatch.gui.JavaFXEventThreadExecutorServiceConfigurator" + throughput = 1 +} \ No newline at end of file diff --git a/src/main/scala/nova/monadic_sfx/SFXActors.scala b/src/main/scala/nova/monadic_sfx/SFXActors.scala new file mode 100644 index 0000000..54f95c0 --- /dev/null +++ b/src/main/scala/nova/monadic_sfx/SFXActors.scala @@ -0,0 +1,219 @@ +package nova.monadic_sfx + +import scalafx.application.JFXApp +import scalafx.application.JFXApp.PrimaryStage +import monix.eval.Task +import monix.execution.Scheduler +// import sttp.client.asynchttpclient.monix.AsyncHttpClientMonixBackend +// import sttp.client._ +// import sttp.client.circe._ +// import io.circe.generic.auto._ +import scala.util.Failure +import scala.util.Success +import akka.{actor => classic} +import nova.monadic_sfx.executors._ +import cats.effect.Resource +import nova.monadic_sfx.models._ +import nova.monadic_sfx.ui.DefaultUI +import nova.monadic_sfx.http.Backend +import nova.monadic_sfx.modules.MainModule +import scalafx.stage.Stage +import scalafx.scene.layout.FlowPane +import nova.monadic_sfx.pages.LoginPage +import scala.concurrent.duration._ +import sttp.client.asynchttpclient.monix.AsyncHttpClientMonixBackend +import scalafx.Includes._ +import scala.concurrent.Await +import akka.actor.typed.scaladsl.adapter._ +import scalafx.application.Platform +import io.odin.syntax._ +// import io.odin._ +import io.odin.monix._ +import nova.monadic_sfx.pages.HomePage + +object ScalaFXHelloWorld extends JFXApp with MainModule { + + val logger = consoleLogger().withAsyncUnsafe() + + lazy val schedulers: Schedulers = new Schedulers() + + implicit lazy val defaultScheduler: Scheduler = schedulers.fx + + lazy val backendTask = AsyncHttpClientMonixBackend() + lazy val actorSystemTask = Task { + classic.ActorSystem( + name = "FXActorSystem" + ) + } + + lazy val application = for { + _ <- logger.info("Starting application..") + backend <- backendTask + actorSystem <- actorSystemTask + // to spawn child actors + // _ <- Task { actorSystem.spawn() } + appStage <- Task { makePrimaryStage(backend, actorSystem) } + // splash screen + _ <- Task { + // this stage refers to implicit jfx stage + // makes this impure, but I can't think of a better way right now + stage = appStage + } + // wait 2 seconds before showing home screen + d <- deps + fib1 <- d.send().start + _ <- Task.sleep(2.seconds) + _ <- Task { + // appStage.maximized = true + appStage.height = 800 + appStage.width = 800 + appStage + .scene() + .setRoot( + LoginPage(appStage, backend, actorSystem) + ) + } + // _ <- fib1.join + } yield () + application.runToFuture + .onComplete(res => + res match { + case Failure(exception) => { + println("Application start failed. Reason -") + exception.printStackTrace() + } + case Success(value) => println("Application started Successfully") + } + ) + + // Task + // .suspend { + // val program = Task { + // stage = new PrimaryStage { + // // initStyle(StageStyle.Unified) + // title = "ScalaFX Hello World" + // scene = defaultUI.scene + + // } + // } + + // val backendResource = AsyncHttpClientMonixBackend + // .resource() + // .use { implicit backend => + // Task + // .suspend( + // (for { + // req <- + // basicRequest + // .get(uri"https://httpbin.org/get") + // .response(asJson[HttpBinResponse]) + // .send() + // } yield println(req)) >> + // Task(println(Thread.currentThread().getName())) + // ) + // // .delayExecution(1.second) + // } + // .executeOn(Scheduler.global) + // val akkaResource = Resource + // .make(Task { + // classic.ActorSystem( + // name = "FXActorSystem" + // ) + // })(sys => Task(println("Shutting down actor system")) >> Task(sys.terminate())) + // .use { implicit system => + // // system.spa + // // system.typed + // // val javaFxActor = system.actorOf( + // // Props[JavaFxActor]().withDispatcher("javafx-dispatcher"), + // // "javaFxActor" + // // ) + // // val swingActor = system.actorOf( + // // Props[SwingActor]().withDispatcher("swing-dispatcher"), + // // "swingActor" + // // ) + // Task.unit + // } + // .delayExecution(1.second) + // backendResource.start >> akkaResource.start >> + // program.to[Task].asyncBoundary >> + // Task(println(Thread.currentThread().getName())) + // .executeOn(Scheduler.global) + // Task.parZip3( + // program.to[Task].executeOn(defaultScheduler), + // // backendResource, + // // dummyRequester.send(), + // // akkaResource, + // Task(println(Thread.currentThread().getName())) + // .executeOn(schedulers.cpu) + // ) + // } + // .runToFuture + // .onComplete(res => + // res match { + // case Failure(exception) => { + // println("Application start failed. Reason -") + // exception.printStackTrace() + // } + // case Success(value) => println("Application started Successfully") + // } + // ) + + // new TaskApp { + // override protected def scheduler: Scheduler = + // JFXExecutionContexts.javaFxScheduler + // override def run(args: List[String]): Task[ExitCode] = + // Task.suspend { + // Task { + // AsyncHttpClientMonixBackend().flatMap(implicit backend => { + // val req = RequestPayload("").asJson + // basicRequest.get(uri"").body(req).send() + // }) + // } >> + // program.to[Task].executeOn(JFXExecutionContexts.javaFxScheduler) >> + // Task(println(Thread.currentThread().getName())) + // .executeOn(Scheduler.global) >> + // // Task.unit.asyncBoundary >> + // Task.pure(ExitCode.Success) + // } + // } +// } + +// Task.sleep(3.seconds).flatMap { _ => +// +// (for { +// req <- +// basicRequest +// .get(uri"https://httpbin.org/get") +// .response(asJson[HttpBinResponse]) +// .send() +// } yield println(req)) >> Task( +// println(Thread.currentThread().getName()) +// ) >> +// backend.close() +// } +// + def test(stage: Stage) = { + stage.scene().setRoot(new FlowPane()) + } + def makePrimaryStage( + backend: AppTypes.HttpBackend, + actorSystem: classic.ActorSystem + ) = { + new PrimaryStage { + scene = new DefaultUI().scene + onCloseRequest = () => { + val f2 = actorSystem.terminate() + val f1 = backend.close().runToFuture + + println("Closing backend") + Await.result(f1, 3.seconds) + println("Closing actor system") + println(Thread.currentThread().getName()) + Await.result(f2, 3.seconds) + println("Actor system closed") + Platform.exit() + System.exit(0) + } + } + } +} diff --git a/src/main/scala/nova/monadic_sfx/Types.scala b/src/main/scala/nova/monadic_sfx/Types.scala new file mode 100644 index 0000000..f68926c --- /dev/null +++ b/src/main/scala/nova/monadic_sfx/Types.scala @@ -0,0 +1,13 @@ +package nova.monadic_sfx + +import monix.eval.Task +import sttp.client.SttpBackend +import monix.reactive.Observable +import sttp.client.asynchttpclient.WebSocketHandler +import java.nio.ByteBuffer + +trait AppTypes {} +object AppTypes { + type HttpBackend = + SttpBackend[Task, Observable[ByteBuffer], WebSocketHandler] +} diff --git a/src/main/scala/nova/monadic_sfx/executors/GUIExecutor.scala b/src/main/scala/nova/monadic_sfx/executors/GUIExecutor.scala new file mode 100644 index 0000000..735762e --- /dev/null +++ b/src/main/scala/nova/monadic_sfx/executors/GUIExecutor.scala @@ -0,0 +1,84 @@ +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 javax.swing.SwingUtilities +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 +abstract class GUIExecutorService extends AbstractExecutorService { + def execute(command: Runnable): Unit + + def shutdown(): Unit = () + + def shutdownNow() = Collections.emptyList[Runnable] + + def isShutdown = false + + def isTerminated = false + + def awaitTermination(l: Long, timeUnit: TimeUnit) = true +} + +object JavaFXExecutorService extends GUIExecutorService { + override def execute(command: Runnable) = Platform.runLater(command) +} + +object SwingExecutorService extends GUIExecutorService { + override def execute(command: Runnable) = SwingUtilities.invokeLater(command) +} + +class JavaFXEventThreadExecutorServiceConfigurator( + config: Config, + prerequisites: DispatcherPrerequisites +) extends ExecutorServiceConfigurator(config, prerequisites) { + private val f = new ExecutorServiceFactory { + def createExecutorService: ExecutorService = JavaFXExecutorService + } + + def createExecutorServiceFactory( + id: String, + threadFactory: ThreadFactory + ): ExecutorServiceFactory = f +} + +// Then we create an ExecutorServiceConfigurator so that Akka can use our SwingExecutorService for the dispatchers +class SwingEventThreadExecutorServiceConfigurator( + config: Config, + prerequisites: DispatcherPrerequisites +) extends ExecutorServiceConfigurator(config, prerequisites) { + private val f = new ExecutorServiceFactory { + def createExecutorService: ExecutorService = SwingExecutorService + } + + def createExecutorServiceFactory( + id: String, + threadFactory: ThreadFactory + ): ExecutorServiceFactory = f +} + +object JFXExecutionContexts { + val javaFxExecutionContext: ExecutionContext = + ExecutionContext.fromExecutor(new Executor { + def execute(command: Runnable): Unit = { + Platform.runLater(command) + } + }) + val fxScheduler = + Scheduler(javaFxExecutionContext) + +} diff --git a/src/main/scala/nova/monadic_sfx/executors/Schedulers.scala b/src/main/scala/nova/monadic_sfx/executors/Schedulers.scala new file mode 100644 index 0000000..f8eaea2 --- /dev/null +++ b/src/main/scala/nova/monadic_sfx/executors/Schedulers.scala @@ -0,0 +1,9 @@ +package nova.monadic_sfx.executors + +import monix.execution.Scheduler + +class Schedulers( + val blockingIO: Scheduler = Scheduler.io(), + val cpu: Scheduler = Scheduler.global, + val fx: Scheduler = JFXExecutionContexts.fxScheduler +) diff --git a/src/main/scala/nova/monadic_sfx/http/Backend.scala b/src/main/scala/nova/monadic_sfx/http/Backend.scala new file mode 100644 index 0000000..158c578 --- /dev/null +++ b/src/main/scala/nova/monadic_sfx/http/Backend.scala @@ -0,0 +1,10 @@ +package nova.monadic_sfx.http + +import sttp.client.asynchttpclient.monix.AsyncHttpClientMonixBackend +import nova.monadic_sfx.AppTypes + +object Backend { + val backend = AsyncHttpClientMonixBackend + .resource() + def apply() = { backend } +} diff --git a/src/main/scala/nova/monadic_sfx/http/requests/DummyRequest.scala b/src/main/scala/nova/monadic_sfx/http/requests/DummyRequest.scala new file mode 100644 index 0000000..132411f --- /dev/null +++ b/src/main/scala/nova/monadic_sfx/http/requests/DummyRequest.scala @@ -0,0 +1,27 @@ +package nova.monadic_sfx.http.requests + +import nova.monadic_sfx.AppTypes + +import nova.monadic_sfx.AppTypes.HttpBackend +import monix.eval.Task +import sttp.client._ +import sttp.client.circe._ +import io.circe.generic.auto._ +import nova.monadic_sfx.models._ + +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 println(req)) >> + Task(println(Thread.currentThread().getName())) + ) + } +} diff --git a/src/main/scala/nova/monadic_sfx/models/DummyModels.scala b/src/main/scala/nova/monadic_sfx/models/DummyModels.scala new file mode 100644 index 0000000..554ca0d --- /dev/null +++ b/src/main/scala/nova/monadic_sfx/models/DummyModels.scala @@ -0,0 +1,8 @@ +package nova.monadic_sfx.models + +case class RequestPayload(data: String) +final case class HttpBinResponse( + url: String, + origin: String, + headers: Map[String, String] +) diff --git a/src/main/scala/nova/monadic_sfx/modules/MainModule.scala b/src/main/scala/nova/monadic_sfx/modules/MainModule.scala new file mode 100644 index 0000000..182b7fc --- /dev/null +++ b/src/main/scala/nova/monadic_sfx/modules/MainModule.scala @@ -0,0 +1,79 @@ +package nova.monadic_sfx.modules + +import nova.monadic_sfx.executors.Schedulers +import monix.execution.Scheduler +import nova.monadic_sfx.http.requests.DummyRequest +import monix.eval.Task +import akka.{actor => classic} +import cats.effect.Resource +import nova.monadic_sfx.AppTypes + +trait MainModule { + import com.softwaremill.macwire._ +// val schedulers: Schedulers = new Schedulers() + +// implicit val defaultScheduler: Scheduler = schedulers.fx + +// val program = +// for { +// backend <- Backend() +// fxActorSystem <- +// Resource +// .make(Task { +// classic.ActorSystem( +// name = "FXActorSystem" +// ) +// })(sys => Task(sys.terminate())) + +// } yield { +// val dummyRequester = wire[DummyRequest] +// dummyRequester.send() +// } + +// val program = Backend().use(backend => +// Resource +// .make(Task { +// classic.ActorSystem( +// name = "FXActorSystem" +// ) +// })(sys => Task(sys.terminate())) +// .use { implicit system => +// // system.spa +// // system.typed +// // val javaFxActor = system.actorOf( +// // Props[JavaFxActor]().withDispatcher("javafx-dispatcher"), +// // "javaFxActor" +// // ) +// // val swingActor = system.actorOf( +// // Props[SwingActor]().withDispatcher("swing-dispatcher"), +// // "swingActor" +// // ) +// Task(new DummyRequest(backend)) >> +// Task.unit +// } +// ) + def schedulers: Schedulers + + def defaultScheduler: Scheduler + + def backendTask: Task[AppTypes.HttpBackend] + def actorSystemTask: Task[classic.ActorSystem] + + def deps = + for { + backend <- backendTask + actorSystem <- actorSystemTask + dummyRequesterTask <- Task { + wireDeps(backend, actorSystem) + } + } yield dummyRequesterTask + + def wireDeps( + backend: AppTypes.HttpBackend, + system: classic.ActorSystem + ): DummyRequest = { + wire[DummyRequest] + // new DummyRequest(backend) + } + +} diff --git a/src/main/scala/nova/monadic_sfx/pages/HomePage.scala b/src/main/scala/nova/monadic_sfx/pages/HomePage.scala new file mode 100644 index 0000000..ccc8213 --- /dev/null +++ b/src/main/scala/nova/monadic_sfx/pages/HomePage.scala @@ -0,0 +1,39 @@ +package nova.monadic_sfx.pages +import nova.monadic_sfx.AppTypes +import scalafx.scene.control.TextField +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 + +class HomePage( + backend: AppTypes.HttpBackend, + system: akka.actor.ActorSystem, + onLogout: () => Unit +) { + private lazy val root = new HBox { + children = List( + new Text { + text = "hello" + }, + new Button { + text = "logout" + onAction = () => onLogout() + } + ) + } + def render = root +} + +object HomePage { + def apply( + backend: AppTypes.HttpBackend, + system: akka.actor.ActorSystem, + onLogout: () => Unit + ): Parent = + new HomePage(backend, system, onLogout).render +} diff --git a/src/main/scala/nova/monadic_sfx/pages/LoginPage.scala b/src/main/scala/nova/monadic_sfx/pages/LoginPage.scala new file mode 100644 index 0000000..66a1c7a --- /dev/null +++ b/src/main/scala/nova/monadic_sfx/pages/LoginPage.scala @@ -0,0 +1,57 @@ +package nova.monadic_sfx.pages +import nova.monadic_sfx.AppTypes +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 io.odin.syntax._ +// import _root_.monix.eval.Task +// import io.odin.monix._ +// import javafx.beans.property.ObjectProperty +// import javafx.event.{ActionEvent, EventHandler} +class LoginPage( + appStage: PrimaryStage, + backend: AppTypes.HttpBackend, + system: akka.actor.ActorSystem +) { + + //pure function callbacks, but with side effects still + private def onLogout(stage: PrimaryStage) = { + println("logging out") + stage.scene().setRoot(render) + } + private def onLogin(stage: PrimaryStage) = { + println("logging in") + stage + .scene() + .setRoot(HomePage(backend, system, () => onLogout(appStage))) + } + private lazy val root = new VBox { + children = Seq( + new TextField { + text = "username" + editable = true + }, + new TextField { + text = "password" + }, + new Button { + text = "Login" + onAction = () => onLogin(appStage) + } + ) + } + def render: Parent = root + +} + +object LoginPage { + def apply( + appStage: PrimaryStage, + backend: AppTypes.HttpBackend, + system: akka.actor.ActorSystem + ) = new LoginPage(appStage, backend, system).render +} diff --git a/src/main/scala/nova/monadic_sfx/ui/DefaultUI.scala b/src/main/scala/nova/monadic_sfx/ui/DefaultUI.scala new file mode 100644 index 0000000..afe0f07 --- /dev/null +++ b/src/main/scala/nova/monadic_sfx/ui/DefaultUI.scala @@ -0,0 +1,55 @@ +package nova.monadic_sfx.ui + +import scalafx.geometry.Insets +import scalafx.scene.Scene +import scalafx.scene.effect.DropShadow +import scalafx.scene.layout.HBox +import scalafx.scene.paint.Color._ +import scalafx.scene.paint._ +import scalafx.scene.text.Text +import monix.eval.Coeval + +class DefaultUI { + val scene = + new Scene { + fill = Color.rgb(38, 38, 38) + content = 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 + } + } + ) + } + } + +// 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/org/slf4j/impl/StaticLoggerBuilder.scala b/src/main/scala/org/slf4j/impl/StaticLoggerBuilder.scala new file mode 100644 index 0000000..6c51edc --- /dev/null +++ b/src/main/scala/org/slf4j/impl/StaticLoggerBuilder.scala @@ -0,0 +1,36 @@ +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 _root_.monix.execution.Scheduler + +//effect type should be specified inbefore +//log line will be recorded right after the call with no suspension +class StaticLoggerBinder extends OdinLoggerBinder[IO] { + + val ec: ExecutionContext = Scheduler.global + implicit val timer: Timer[IO] = IO.timer(ec) + implicit val clock: Clock[IO] = timer.clock + 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 "org.asynchttpclient.netty.channel.DefaultChannelPool" => + consoleLogger[IO](minLevel = Level.Warn) + case _ => //if wildcard case isn't provided, default logger is no-op + consoleLogger[IO]() + } +} + +object StaticLoggerBinder extends StaticLoggerBinder { + + var REQUESTED_API_VERSION: String = "1.7" + + def getSingleton: StaticLoggerBinder = this + +}