Rohan Sircar
3 years ago
29 changed files with 1535 additions and 371 deletions
-
2.gitignore
-
56build.sbt
-
2project/build.properties
-
1project/plugin.sbt
-
58src/main/scala/nova/monadic_sfx/Main.scala
-
159src/main/scala/nova/monadic_sfx/MainApp.scala
-
2src/main/scala/nova/monadic_sfx/MainModule.scala
-
9src/main/scala/nova/monadic_sfx/Types.scala
-
70src/main/scala/nova/monadic_sfx/actors/ActorModule.scala
-
20src/main/scala/nova/monadic_sfx/actors/TestActor.scala
-
26src/main/scala/nova/monadic_sfx/executors/GUIExecutor.scala
-
25src/main/scala/nova/monadic_sfx/executors/Schedulers.scala
-
4src/main/scala/nova/monadic_sfx/http/HttpModule.scala
-
38src/main/scala/nova/monadic_sfx/http/requests/DummyRequest.scala
-
205src/main/scala/nova/monadic_sfx/implicits/JavaFxMonixObservables.scala
-
50src/main/scala/nova/monadic_sfx/implicits/package.scala
-
7src/main/scala/nova/monadic_sfx/models/DummyModels.scala
-
65src/main/scala/nova/monadic_sfx/ui/DefaultUI.scala
-
144src/main/scala/nova/monadic_sfx/ui/MyFxApp.scala
-
129src/main/scala/nova/monadic_sfx/ui/MyFxAppOld.scala
-
18src/main/scala/nova/monadic_sfx/ui/UiModule.scala
-
556src/main/scala/nova/monadic_sfx/ui/components/todo/TodoListComponent.scala
-
95src/main/scala/nova/monadic_sfx/ui/controller/TodoController.scala
-
28src/main/scala/nova/monadic_sfx/ui/screens/HomeScreen.scala
-
34src/main/scala/nova/monadic_sfx/ui/screens/LoginScreen.scala
-
2src/main/scala/nova/monadic_sfx/ui/screens/Screen.scala
-
4src/main/scala/nova/monadic_sfx/util/Action.scala
-
21src/main/scala/nova/monadic_sfx/util/IOUtils.scala
-
76src/main/scala/org/slf4j/impl/StaticLoggerBuilder.scala
@ -1,2 +1,2 @@ |
|||
sbt.version=1.3.10 |
|||
sbt.version=1.4.3 |
|||
|
@ -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) |
|||
} |
|||
|
|||
} |
@ -0,0 +1,159 @@ |
|||
package nova.monadic_sfx |
|||
|
|||
import com.softwaremill.macwire._ |
|||
import io.odin.Logger |
|||
import monix.bio.Task |
|||
import monix.catnap.ConcurrentChannel |
|||
import nova.monadic_sfx.executors.Schedulers |
|||
import nova.monadic_sfx.implicits.JFXButton |
|||
import nova.monadic_sfx.implicits.JavaFXMonixObservables._ |
|||
import nova.monadic_sfx.ui.MyFxApp |
|||
import nova.monadic_sfx.ui.components.todo.Todo |
|||
import nova.monadic_sfx.ui.components.todo.TodoListComponent |
|||
import nova.monadic_sfx.ui.components.todo.TodoListView |
|||
import nova.monadic_sfx.util.IOUtils._ |
|||
import org.gerweck.scalafx.util._ |
|||
import scalafx.Includes._ |
|||
import scalafx.application.JFXApp.PrimaryStage |
|||
import scalafx.beans.property.ObjectProperty |
|||
import scalafx.beans.property.StringProperty |
|||
import scalafx.collections.ObservableBuffer |
|||
import scalafx.geometry.Insets |
|||
import scalafx.scene.Scene |
|||
import scalafx.scene.control.TableColumn |
|||
import scalafx.scene.control.TableView |
|||
import scalafx.scene.layout.HBox |
|||
import scalafx.scene.paint.Color |
|||
import scalafx.scene.shape.Rectangle |
|||
|
|||
class MainApp( |
|||
// spawnProtocol: ActorSystem[SpawnProtocol.Command], |
|||
schedulers: Schedulers, |
|||
startTime: Long |
|||
)(implicit logger: Logger[Task]) { |
|||
|
|||
lazy val addTodoButton = new JFXButton { |
|||
text = "Add" |
|||
} |
|||
|
|||
lazy val addTodoObs = addTodoButton.observableAction() |
|||
|
|||
lazy val todoListView = TodoListView.defaultListView |
|||
|
|||
lazy val _scene = new Scene { |
|||
root = new HBox { |
|||
padding = Insets(20) |
|||
content = new Rectangle { |
|||
width = 400 |
|||
height = 200 |
|||
fill = Color.DeepSkyBlue |
|||
} |
|||
children ++= Seq( |
|||
new JFXButton { |
|||
text = "DummyButton" |
|||
}, |
|||
new JFXButton { |
|||
text = "DummyButton2" |
|||
}, |
|||
addTodoButton, |
|||
Test.ttv |
|||
// todoListView |
|||
) |
|||
} |
|||
} |
|||
|
|||
private lazy val stage = new PrimaryStage { |
|||
title = "Simple ScalaFX App" |
|||
scene = _scene |
|||
width = 800 |
|||
height = 400 |
|||
} |
|||
|
|||
// implicit val l = logger |
|||
// implicit val sp = spawnProtocol |
|||
|
|||
val program = for { |
|||
(fxApp, fxAppFib) <- wire[MyFxApp].init(stage) |
|||
// _ <- Task(fxApp.stage = stage) |
|||
// .executeOn(schedulers.fx) |
|||
// .delayExecution(2000.millis) |
|||
todoComponent <- createTodoComponent |
|||
_ <- toIO( |
|||
addTodoObs |
|||
.mapEval(_ => |
|||
toTask(todoComponent.send(TodoListComponent.Add(Todo(1, "blah")))) |
|||
) |
|||
.completedL |
|||
.executeOn(schedulers.fx) |
|||
.startAndForget |
|||
) |
|||
_ <- logger.info( |
|||
s"Application started in ${(System.currentTimeMillis() - startTime) / 1000f} seconds" |
|||
) |
|||
_ <- fxAppFib.join |
|||
} yield () |
|||
|
|||
def createTodoComponent: Task[TodoListComponent] = { |
|||
for { |
|||
channel <- |
|||
ConcurrentChannel |
|||
.of[Task, TodoListComponent.Complete, TodoListComponent.Command] |
|||
scheduler = schedulers.fx |
|||
(lv, delObs, editObs) <- |
|||
TodoListView.defaultListView2.executeOn(scheduler) |
|||
todoLV = new TodoListView(lv) |
|||
todoComponent <- wire[TodoListComponent.Props].create |
|||
// TODO make this a "message pass" instead of mutating directly |
|||
_ <- Task(_scene.getChildren += lv).executeOn(scheduler) |
|||
_ <- toIO( |
|||
delObs |
|||
.doOnNext(_ => toTask(logger.debug("Pressed delete"))) |
|||
.doOnNext(todo => |
|||
toTask( |
|||
for { |
|||
_ <- logger.debug(s"Got todo $todo") |
|||
_ <- todoComponent.send(TodoListComponent.Delete(todo.id)) |
|||
// _ <- Task.sequence( |
|||
// lst.map(todo => |
|||
// todoComponent.send(TodoListComponent.Delete(todo.id)) |
|||
// ) |
|||
// ) |
|||
} yield () |
|||
) |
|||
) |
|||
.completedL |
|||
).startAndForget |
|||
_ <- toIO( |
|||
editObs |
|||
.doOnNext(_ => toTask(logger.debug("Pressed edit"))) |
|||
.completedL |
|||
).startAndForget |
|||
} yield todoComponent |
|||
} |
|||
|
|||
} |
|||
|
|||
class TestModel(_name: String, _age: Int) { |
|||
val name = StringProperty(_name).readOnly |
|||
val age = ObjectProperty(_age).readOnly |
|||
} |
|||
|
|||
object Test { |
|||
val items = ObservableBuffer( |
|||
new TestModel("hmm", 1), |
|||
new TestModel("hmm2", 2) |
|||
) |
|||
|
|||
val ttv = new TableView[TestModel](items) { |
|||
columns ++= Seq( |
|||
new TableColumn[TestModel, String] { |
|||
text = "Name" |
|||
cellValueFactory = { _.value.name } |
|||
}, |
|||
new TableColumn[TestModel, Int] { |
|||
text = "Age" |
|||
cellValueFactory = { _.value.age } |
|||
} |
|||
) |
|||
} |
|||
} |
@ -1,7 +1,7 @@ |
|||
package nova.monadic_sfx |
|||
|
|||
import nova.monadic_sfx.actors.ActorModule |
|||
import nova.monadic_sfx.ui.UiModule |
|||
import nova.monadic_sfx.http.HttpModule |
|||
import nova.monadic_sfx.ui.UiModule |
|||
|
|||
trait MainModule extends ActorModule with UiModule with HttpModule |
@ -1,13 +1,14 @@ |
|||
package nova.monadic_sfx |
|||
|
|||
import 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 {} |
@ -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() |
|||
} |
|||
} |
@ -1,9 +1,28 @@ |
|||
package nova.monadic_sfx.executors |
|||
|
|||
import com.typesafe.scalalogging.Logger |
|||
import monix.execution.Scheduler |
|||
import monix.execution.UncaughtExceptionReporter |
|||
import monix.execution.schedulers.TracingScheduler |
|||
|
|||
class Schedulers( |
|||
val blockingIO: Scheduler = Scheduler.io(), |
|||
val cpu: Scheduler = Scheduler.global, |
|||
val fx: Scheduler = JFXExecutionContexts.fxScheduler |
|||
val blocking: Scheduler = TracingScheduler( |
|||
Scheduler |
|||
.io() |
|||
.withUncaughtExceptionReporter(Schedulers.reporter) |
|||
), |
|||
val async: Scheduler = Scheduler.traced |
|||
.withUncaughtExceptionReporter(Schedulers.reporter), |
|||
val fx: Scheduler = TracingScheduler( |
|||
JFXExecutionContexts.fxScheduler |
|||
.withUncaughtExceptionReporter(Schedulers.reporter) |
|||
) |
|||
) |
|||
|
|||
object Schedulers { |
|||
val reporter = UncaughtExceptionReporter { ex => |
|||
val logger = Logger[Schedulers] |
|||
logger.error("Uncaught exception", ex) |
|||
} |
|||
|
|||
} |
@ -1,43 +1,17 @@ |
|||
package nova.monadic_sfx.http.requests |
|||
|
|||
import nova.monadic_sfx.AppTypes |
|||
|
|||
import nova.monadic_sfx.AppTypes.HttpBackend |
|||
import monix.eval.Task |
|||
import nova.monadic_sfx.models._ |
|||
import sttp.client._ |
|||
import sttp.client.circe._ |
|||
import io.circe.generic.auto._ |
|||
import nova.monadic_sfx.models._ |
|||
import cats.data.EitherT |
|||
|
|||
class DummyRequest(backend: HttpBackend) extends AppTypes { |
|||
private implicit val _backend = backend |
|||
def send() = { |
|||
Task |
|||
.suspend { |
|||
for { |
|||
req <- |
|||
basicRequest |
|||
.get(uri"https://httpbin.org/get") |
|||
.response(asJson[HttpBinResponse]) |
|||
.send() |
|||
} yield (req) |
|||
} |
|||
|
|||
} |
|||
def send = |
|||
basicRequest |
|||
.get(uri"https://httpbin.org/get") |
|||
.response(asJson[HttpBinResponse]) |
|||
.send() |
|||
|
|||
def test() = { |
|||
for { |
|||
res <- send() |
|||
res3 <- Task { res.body } |
|||
res2 <- Task { |
|||
res3.fold( |
|||
err => { |
|||
err.toString() |
|||
}, |
|||
value => value.toString() |
|||
) |
|||
} |
|||
} yield (res3) |
|||
} |
|||
} |
@ -0,0 +1,205 @@ |
|||
package nova.monadic_sfx.implicits |
|||
|
|||
import javafx.beans.property.ObjectProperty |
|||
import javafx.collections.ObservableList |
|||
import javafx.scene.{input => jfxsi} |
|||
import javafx.{event => jfxe} |
|||
import monix.bio.Task |
|||
import monix.execution.Ack |
|||
import monix.execution.Cancelable |
|||
import monix.execution.Scheduler |
|||
import monix.reactive.Observable |
|||
import monix.reactive.Observer |
|||
import monix.reactive.OverflowStrategy |
|||
import monix.tail.Iterant |
|||
import monix.{eval => me} |
|||
import scalafx.Includes._ |
|||
import scalafx.beans.property.Property |
|||
import scalafx.beans.value.ObservableValue |
|||
import scalafx.collections.ObservableBuffer |
|||
import scalafx.scene.Scene |
|||
import scalafx.scene.control.ButtonBase |
|||
|
|||
object JavaFXMonixObservables { |
|||
|
|||
implicit final class SceneObservables(private val scene: Scene) |
|||
extends AnyVal { |
|||
|
|||
def observableMousePressed(): Observable[jfxsi.MouseEvent] = { |
|||
import monix.execution.cancelables.SingleAssignCancelable |
|||
Observable.create(OverflowStrategy.Unbounded) { sub => |
|||
val c = SingleAssignCancelable() |
|||
val l = new jfxe.EventHandler[jfxsi.MouseEvent] { |
|||
override def handle(event: jfxsi.MouseEvent): Unit = { |
|||
if (sub.onNext(event) == Ack.Stop) c.cancel() |
|||
} |
|||
} |
|||
|
|||
scene.onMousePressed = l |
|||
c := Cancelable(() => |
|||
scene.removeEventHandler( |
|||
jfxsi.MouseEvent.MOUSE_PRESSED, |
|||
l |
|||
) |
|||
) |
|||
c |
|||
} |
|||
} |
|||
|
|||
def observableMouseDragged(): Observable[jfxsi.MouseEvent] = { |
|||
import monix.execution.cancelables.SingleAssignCancelable |
|||
Observable.create(OverflowStrategy.Unbounded) { sub => |
|||
val c = SingleAssignCancelable() |
|||
val l = new jfxe.EventHandler[jfxsi.MouseEvent] { |
|||
override def handle(event: jfxsi.MouseEvent): Unit = { |
|||
if (sub.onNext(event) == Ack.Stop) c.cancel() |
|||
} |
|||
} |
|||
|
|||
scene.onMouseDragged = l |
|||
c := Cancelable(() => |
|||
scene.removeEventHandler( |
|||
jfxsi.MouseEvent.MOUSE_DRAGGED, |
|||
l |
|||
) |
|||
) |
|||
c |
|||
} |
|||
} |
|||
} |
|||
|
|||
// implicit final class BindObs[T, J](private val prop: Property[T, J]) |
|||
// extends AnyVal { |
|||
// def -->(op: Observer[T]) = { |
|||
// op.onNext(prop.value) |
|||
// } |
|||
|
|||
// def <--(obs: Observable[T])(implicit s: Scheduler) = { |
|||
// obs.doOnNext(v => me.Task(prop.value = v)).subscribe() |
|||
// } |
|||
|
|||
// def observableChange[J1 >: J]() |
|||
// : Observable[(ObservableValue[T, J], J1, J1)] = { |
|||
// import monix.execution.cancelables.SingleAssignCancelable |
|||
// Observable.create(OverflowStrategy.Unbounded) { sub => |
|||
// val c = SingleAssignCancelable() |
|||
|
|||
// val canc = prop.onChange((a, b, c) => sub.onNext((a, b, c))) |
|||
|
|||
// c := Cancelable(() => canc.cancel()) |
|||
// c |
|||
// } |
|||
// } |
|||
// } |
|||
|
|||
implicit final class BindObs2[A](private val prop: ObjectProperty[A]) |
|||
extends AnyVal { |
|||
|
|||
// def -->(sub: Var[A]) = |
|||
// prop.onChange((a, b, c) => sub := c) |
|||
|
|||
def -->(sub: Observer[A]) = |
|||
prop.onChange((a, b, c) => if (c != null) sub.onNext(c)) |
|||
|
|||
// def -->[J1 >: A, T]( |
|||
// op: Observable[J1] => me.Task[T] |
|||
// )(implicit s: Scheduler) = { |
|||
// op(prop.observableChange().map(_._3)).runToFuture |
|||
// } |
|||
|
|||
def <--(obs: Observable[A])(implicit s: Scheduler) = { |
|||
obs.doOnNext(v => me.Task(prop() = v)).subscribe() |
|||
} |
|||
|
|||
def observableChange[J1 >: A]() |
|||
: Observable[(ObservableValue[A, A], J1, J1)] = { |
|||
import monix.execution.cancelables.SingleAssignCancelable |
|||
Observable.create(OverflowStrategy.Unbounded) { sub => |
|||
val c = SingleAssignCancelable() |
|||
|
|||
val canc = prop.onChange((a, b, c) => sub.onNext((a, b, c))) |
|||
|
|||
c := Cancelable(() => canc.cancel()) |
|||
c |
|||
} |
|||
} |
|||
} |
|||
|
|||
implicit final class ObjectPropertyObservableListExt[A]( |
|||
private val prop: ObjectProperty[ObservableList[A]] |
|||
) extends AnyVal { |
|||
def <--(obs: Observable[Seq[A]])(implicit s: Scheduler) = { |
|||
obs.doOnNext(v => me.Task(prop() = ObservableBuffer.from(v))).subscribe() |
|||
} |
|||
|
|||
def -->(sub: Observer[A])(implicit s: Scheduler) = |
|||
prop.onChange((a, b, c) => |
|||
if (c != null) |
|||
Iterant[Task] |
|||
.fromIterable(c.toIterable) |
|||
.consume |
|||
.use(consume(sub, _)) |
|||
.runToFuture |
|||
) |
|||
|
|||
private def loop(sub: Observer[A], it: Iterator[A]): Task[Unit] = |
|||
if (it.hasNext) { |
|||
val next = it.next() |
|||
Task.deferFuture(sub.onNext(next)).flatMap { |
|||
case Ack.Continue => loop(sub, it) |
|||
case Ack.Stop => Task.unit |
|||
} |
|||
} else Task.unit |
|||
|
|||
private def consume( |
|||
sub: Observer[A], |
|||
consumer: Iterant.Consumer[Task, A] |
|||
): Task[Unit] = |
|||
consumer.pull.flatMap { |
|||
case Left(value) => Task.unit |
|||
case Right(value) => |
|||
Task.deferFuture(sub.onNext(value)).flatMap { |
|||
case Ack.Continue => consume(sub, consumer) |
|||
case Ack.Stop => Task.unit |
|||
} |
|||
} |
|||
|
|||
} |
|||
|
|||
implicit final class OnActionObservable( |
|||
private val button: ButtonBase |
|||
) extends AnyVal { |
|||
// def -->[T]( |
|||
// op: Observable[jfxe.ActionEvent] => me.Task[T] |
|||
// )(implicit s: Scheduler) = { |
|||
// op(button.observableAction()).runToFuture |
|||
// } |
|||
|
|||
// def -->( |
|||
// sub: ConcurrentSubject[jfxe.ActionEvent, jfxe.ActionEvent] |
|||
// ) = { |
|||
// button.onAction = value => sub.onNext(value) |
|||
// } |
|||
|
|||
def observableAction(): Observable[jfxe.ActionEvent] = { |
|||
import monix.execution.cancelables.SingleAssignCancelable |
|||
Observable.create(OverflowStrategy.Unbounded) { sub => |
|||
val c = SingleAssignCancelable() |
|||
val l = new jfxe.EventHandler[jfxe.ActionEvent] { |
|||
override def handle(event: jfxe.ActionEvent): Unit = { |
|||
if (sub.onNext(event) == Ack.Stop) c.cancel() |
|||
} |
|||
} |
|||
|
|||
button.onAction = l |
|||
c := Cancelable(() => |
|||
button.removeEventHandler( |
|||
jfxe.ActionEvent.ACTION, |
|||
l |
|||
) |
|||
) |
|||
c |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,50 @@ |
|||
package nova.monadic_sfx |
|||
|
|||
import javafx.event.ActionEvent |
|||
import monix.execution.Ack |
|||
import monix.execution.Cancelable |
|||
import monix.reactive.Observable |
|||
import monix.reactive.OverflowStrategy |
|||
import scalafx.scene.control._ |
|||
|
|||
package object implicits { |
|||
|
|||
implicit class MyButtonExt(val button: Button) extends AnyVal { |
|||
def observableAction(): Observable[ActionEvent] = { |
|||
import monix.execution.cancelables.SingleAssignCancelable |
|||
Observable.create(OverflowStrategy.Unbounded) { sub => |
|||
val c = SingleAssignCancelable() |
|||
val l = new javafx.event.EventHandler[ActionEvent] { |
|||
override def handle(event: ActionEvent): Unit = { |
|||
if (sub.onNext(event) == Ack.Stop) c.cancel() |
|||
} |
|||
} |
|||
|
|||
button.onAction = l |
|||
c := Cancelable(() => |
|||
button.removeEventHandler( |
|||
ActionEvent.ACTION, |
|||
l |
|||
) |
|||
) |
|||
c |
|||
} |
|||
} |
|||
} |
|||
|
|||
// implicit class NodeExt(val node: Node) { |
|||
// def lookup2[T <: SFXDelegate[_]]( |
|||
// selector: String |
|||
// )(implicit c: ClassTag[T]) = { |
|||
// val t = c.runtimeClass |
|||
// Option(node.delegate.lookup(selector)) match { |
|||
// case Some(value) => |
|||
// if (value.getClass == t) Some(value) else None |
|||
// case None => None |
|||
// } |
|||
// } |
|||
|
|||
// val x = node.lookup2("") |
|||
// } |
|||
|
|||
} |
@ -1,7 +1,10 @@ |
|||
package nova.monadic_sfx.models |
|||
|
|||
case class RequestPayload(data: String) |
|||
final case class HttpBinResponse( |
|||
import io.circe.generic.JsonCodec |
|||
|
|||
final case class RequestPayload(data: String) |
|||
|
|||
@JsonCodec final case class HttpBinResponse( |
|||
url: String, |
|||
origin: String, |
|||
headers: Map[String, String] |
|||
|
@ -1,55 +1,52 @@ |
|||
package nova.monadic_sfx.ui |
|||
|
|||
import nova.monadic_sfx.implicits.JFXSpinner |
|||
import scalafx.geometry.Insets |
|||
import scalafx.geometry.Pos |
|||
import scalafx.scene.Scene |
|||
import scalafx.scene.effect.DropShadow |
|||
import scalafx.scene.layout.HBox |
|||
import scalafx.scene.layout.VBox |
|||
import scalafx.scene.paint.Color._ |
|||
import scalafx.scene.paint._ |
|||
import scalafx.scene.text.Text |
|||
import monix.eval.Coeval |
|||
|
|||
class DefaultUI { |
|||
object DefaultUI { |
|||
val scene = |
|||
new Scene { |
|||
fill = Color.rgb(38, 38, 38) |
|||
content = new HBox { |
|||
content = new VBox { |
|||
alignment = Pos.Center |
|||
padding = Insets(50, 80, 50, 80) |
|||
children = Seq( |
|||
new Text { |
|||
text = "Scala" |
|||
style = "-fx-font: normal bold 100pt sans-serif" |
|||
fill = new LinearGradient(endX = 0, stops = Stops(Red, DarkRed)) |
|||
}, |
|||
new Text { |
|||
text = "FX" |
|||
style = "-fx-font: italic bold 100pt sans-serif" |
|||
fill = new LinearGradient( |
|||
endX = 0, |
|||
stops = Stops(White, DarkGray) |
|||
new HBox { |
|||
padding = Insets(50, 80, 50, 80) |
|||
children = Seq( |
|||
new Text { |
|||
text = "Scala" |
|||
style = "-fx-font: normal bold 100pt sans-serif" |
|||
fill = new LinearGradient(endX = 0, stops = Stops(Red, DarkRed)) |
|||
}, |
|||
new Text { |
|||
text = "FX" |
|||
style = "-fx-font: italic bold 100pt sans-serif" |
|||
fill = new LinearGradient( |
|||
endX = 0, |
|||
stops = Stops(White, DarkGray) |
|||
) |
|||
effect = new DropShadow { |
|||
color = DarkGray |
|||
radius = 15 |
|||
spread = 0.25 |
|||
} |
|||
} |
|||
) |
|||
effect = new DropShadow { |
|||
color = DarkGray |
|||
radius = 15 |
|||
spread = 0.25 |
|||
} |
|||
}, |
|||
new JFXSpinner { |
|||
radius = 50 |
|||
// style = "-fx-text-fill: red" |
|||
} |
|||
) |
|||
} |
|||
} |
|||
|
|||
// val program = Coeval |
|||
// .suspend { |
|||
// Coeval(println("hello")) >> |
|||
// Coeval(println(Thread.currentThread().getName())) >> |
|||
// Coeval { |
|||
// stage = new PrimaryStage { |
|||
// // initStyle(StageStyle.Unified) |
|||
// title = "ScalaFX Hello World" |
|||
// scene = scn |
|||
|
|||
// } |
|||
// } |
|||
// } |
|||
|
|||
} |
@ -1,129 +1,41 @@ |
|||
package nova.monadic_sfx.ui |
|||
|
|||
import scalafx.application.JFXApp |
|||
import nova.monadic_sfx.executors.Schedulers |
|||
import monix.execution.Scheduler |
|||
import monix.eval.Task |
|||
import nova.monadic_sfx.screens.LoginScreen |
|||
import nova.monadic_sfx.AppTypes |
|||
import scala.concurrent.duration._ |
|||
|
|||
import io.odin.Logger |
|||
import monix.execution.Callback |
|||
import com.softwaremill.macwire._ |
|||
import nova.monadic_sfx.http.Requesters |
|||
import monix.bio.Task |
|||
import nova.monadic_sfx.executors.Schedulers |
|||
import nova.monadic_sfx.ui.DefaultUI |
|||
import scalafx.application.JFXApp |
|||
import scalafx.application.JFXApp.PrimaryStage |
|||
|
|||
import akka.actor.typed._ |
|||
import nova.monadic_sfx.actors.Counter |
|||
import akka.util.Timeout |
|||
class MyFxApp(val schedulers: Schedulers)(implicit logger: Logger[Task]) { |
|||
|
|||
class MyFxApp( |
|||
logger: Logger[Task], |
|||
backend: AppTypes.HttpBackend, |
|||
actorSystem: ActorSystem[SpawnProtocol.Command], |
|||
requesters: Requesters, |
|||
schedulers: Schedulers |
|||
) extends JFXApp { |
|||
private lazy val internal = new JFXApp { |
|||
stage = new PrimaryStage { |
|||
scene = DefaultUI.scene |
|||
} |
|||
} |
|||
|
|||
implicit lazy val defaultScheduler: Scheduler = schedulers.fx |
|||
// def stage = Task(internal.stage) |
|||
|
|||
lazy val fxActor: Task[ActorRef[Counter.Command]] = wireWith( |
|||
MyFxApp.makeCounterActor _ |
|||
) |
|||
// def stage_=(stage: PrimaryStage) = Task(internal.stage = stage) |
|||
|
|||
lazy val application = |
|||
def useInternal[T](f: JFXApp => Task[T]): Task[T] = |
|||
for { |
|||
appStage <- Task(wireWith(UiModule.makePrimaryStage _)) |
|||
|
|||
// _ <- 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"), |
|||
_ |
|||
) |
|||
) |
|||
} |
|||
} |
|||
} |
@ -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"), |
|||
_ |
|||
) |
|||
) |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,556 @@ |
|||
package nova.monadic_sfx.ui.components.todo |
|||
|
|||
import scala.concurrent.Future |
|||
import scala.concurrent.duration.FiniteDuration |
|||
|
|||
import cats.effect.Sync |
|||
import cats.effect.concurrent.Deferred |
|||
import io.odin.Logger |
|||
import monix.bio.Task |
|||
import monix.catnap.ConcurrentChannel |
|||
import monix.catnap.ConsumerF |
|||
import monix.execution.Ack |
|||
import monix.execution.Cancelable |
|||
import monix.execution.Scheduler |
|||
import monix.reactive.Observable |
|||
import monix.reactive.Observer |
|||
import monix.reactive.OverflowStrategy |
|||
import monix.reactive.observers.Subscriber |
|||
import monix.reactive.subjects.ConcurrentSubject |
|||
import nova.monadic_sfx.implicits.FontIcon |
|||
import nova.monadic_sfx.implicits.IconLiteral |
|||
import nova.monadic_sfx.implicits.JFXListView |
|||
import nova.monadic_sfx.implicits.JavaFXMonixObservables._ |
|||
import nova.monadic_sfx.ui.components.todo.TodoListComponent.Add |
|||
import nova.monadic_sfx.ui.components.todo.TodoListComponent.Delete |
|||
import nova.monadic_sfx.ui.components.todo.TodoListComponent.Edit |
|||
import scalafx.Includes._ |
|||
import scalafx.beans.property.StringProperty |
|||
import scalafx.collections.ObservableBuffer |
|||
import scalafx.scene.control.ContextMenu |
|||
import scalafx.scene.control.ListCell |
|||
import scalafx.scene.control.MenuItem |
|||
import scalafx.scene.control.SelectionMode |
|||
import scalafx.scene.layout.HBox |
|||
import scalafx.scene.text.Text |
|||
import nova.monadic_sfx.ui.components.todo.Store.MonixProSubject |
|||
import nova.monadic_sfx.util.IOUtils |
|||
import monix.tail.Iterant |
|||
|
|||
case class Todo(id: Int, content: String) |
|||
|
|||
class TodoListView( |
|||
val listView: JFXListView[Todo] = TodoListView.defaultListView, |
|||
val lvObs: ObservableBuffer[Todo] = ObservableBuffer.empty |
|||
) { |
|||
listView.items = lvObs |
|||
} |
|||
|
|||
object TodoListView { |
|||
def defaultListView = |
|||
new JFXListView[Todo] { |
|||
// cellFactory = _ => |
|||
// new ListCell[Todo] { |
|||
// // item.onChange((a, b, c) => ()) |
|||
// overr |
|||
// } |
|||
contextMenu = new ContextMenu { |
|||
items ++= Seq( |
|||
new MenuItem { |
|||
text = "delete" |
|||
}, |
|||
new MenuItem { |
|||
text = "edit" |
|||
} |
|||
) |
|||
} |
|||
} |
|||
// import scalafx.scene.control.MultipleSelectionModel |
|||
// .getOrElse(Todo(-1, "blah")) |
|||
implicit class Operations[A](val sink: Observer[A]) extends AnyVal {} |
|||
|
|||
// def reducer( |
|||
// stateC: Coeval[ObservableBuffer[Todo]], |
|||
// action: TodoListComponent.Command |
|||
// ) = |
|||
// action match { |
|||
// case Add(todo) => |
|||
// for { |
|||
// state <- stateC |
|||
// } yield state :+ todo |
|||
// // case Find(id, result) => |
|||
// case Edit(id, content) => stateC |
|||
// case Delete(id) => |
|||
// for { |
|||
// state <- stateC |
|||
// } yield state.filterNot(_.id == id) |
|||
// case _ => stateC |
|||
// } |
|||
|
|||
def reducer( |
|||
state: Vector[Todo], |
|||
action: TodoListComponent.Command |
|||
) = |
|||
action match { |
|||
case Add(todo) => state :+ todo |
|||
// case Find(id, result) => |
|||
case Edit(id, content) => state |
|||
case Delete(id) => |
|||
state.filterNot(_.id == id) |
|||
case _ => state |
|||
} |
|||
|
|||
def defaultListView2: Task[ |
|||
( |
|||
JFXListView[Todo], |
|||
Observable[Todo], |
|||
Observable[Todo] |
|||
) |
|||
] = |
|||
Task.deferAction(implicit s => |
|||
Store |
|||
.createL[TodoListComponent.Command, Vector[Todo]]( |
|||
TodoListComponent.Delete(0), |
|||
Vector.empty[Todo], |
|||
(s: Vector[Todo], a: TodoListComponent.Command) => |
|||
reducer(s, a) -> Observable.empty |
|||
) |
|||
.flatMap(store => |
|||
Task { |
|||
val deleteSub = ConcurrentSubject.publish[Todo] |
|||
val editSub = ConcurrentSubject.publish[Todo] |
|||
|
|||
// store.flatMap(st => Task(st.sink)) |
|||
|
|||
// val deleteSub2 = |
|||
// deleteSub.map(todo => (buf: ObservableBuffer[Todo]) => buf :+ todo) |
|||
// val addSub = |
|||
// ConcurrentSubject |
|||
// .publish[Todo] |
|||
// .map(todo => (buf: ObservableBuffer[Todo]) => buf :+ todo) |
|||
// val state = Observable(deleteSub2, addSub).merge.scan0( |
|||
// ObservableBuffer.empty[Todo] |
|||
// )((buf, fn) => fn(buf)) |
|||
|
|||
val todos = |
|||
store.map { case (_, items) => items } |
|||
|
|||
val listView = new JFXListView[Todo] { lv => |
|||
def selectedItems = lv.selectionModel().selectedItems.view |
|||
// items = todos |
|||
items <-- todos |
|||
// .map(ObservableBuffer.from(_)) |
|||
cellFactory = _ => |
|||
new ListCell[Todo] { |
|||
val _text = StringProperty("") |
|||
val _graphic = new HBox { |
|||
children = Seq( |
|||
new FontIcon { |
|||
iconSize = 10 |
|||
iconLiteral = IconLiteral.Gmi10k |
|||
}, |
|||
new Text { |
|||
text <== _text |
|||
} |
|||
) |
|||
} |
|||
item.onChange((_, _, todo) => { |
|||
println("called") |
|||
if (todo != null) { |
|||
_text() = s"${todo.id} - ${todo.content}" |
|||
graphic = _graphic |
|||
} else { |
|||
_text() = "" |
|||
graphic = null |
|||
} |
|||
}) |
|||
|
|||
} |
|||
selectionModel().selectionMode = SelectionMode.Multiple |
|||
contextMenu = new ContextMenu { |
|||
items ++= Seq( |
|||
new MenuItem { |
|||
text = "Add" |
|||
onAction = _ => |
|||
store.sink |
|||
.onNext(TodoListComponent.Add(Todo(1, "blah3"))) |
|||
}, |
|||
new MenuItem { |
|||
text = "Delete" |
|||
// onAction = _ => |
|||
// for { |
|||
// items <- Option(lv.selectionModel().selectedItems) |
|||
// _ <- Some(items.foreach(item => deleteSub.onNext(item))) |
|||
// } yield () |
|||
onAction = _ => |
|||
selectedItems |
|||
.map(todo => TodoListComponent.Delete(todo.id)) |
|||
.foreach(store.sink.onNext) |
|||
}, |
|||
new MenuItem { |
|||
text = "Edit" |
|||
// onAction = _ => |
|||
// Option(lv.selectionModel().selectedItems).foreach(items => |
|||
// items.foreach(item => editSub.onNext(item)) |
|||
// ) |
|||
} |
|||
) |
|||
} |
|||
} |
|||
|
|||
(listView, deleteSub, editSub) |
|||
} |
|||
) |
|||
) |
|||
} |
|||
|
|||
private[todo] class TodoListComponentImpure( |
|||
todoListView: TodoListView |
|||
) { |
|||
def add(todo: Todo) = todoListView.lvObs += todo |
|||
def find(id: Int) = todoListView.lvObs.find(_.id == id) |
|||
def edit(id: Int, content: String) = |
|||
find(id) |
|||
.map(todo => |
|||
todoListView.lvObs.replaceAll( |
|||
todo, |
|||
Todo(id, content) |
|||
) |
|||
) |
|||
.getOrElse(false) |
|||
} |
|||
|
|||
class TodoListOps private ( |
|||
props: TodoListOps.Props |
|||
) { |
|||
import props._ |
|||
// lazy val internal = new TodoListComponentImpure(todoListView) |
|||
|
|||
// def add(todo: Todo) = Task(internal.add(todo)) |
|||
def add(todo: Todo) = Task(todoListView.lvObs += todo).executeOn(fxScheduler) |
|||
def find(id: Int) = |
|||
Task(todoListView.lvObs.find(_.id == id)).executeOn(fxScheduler) |
|||
def delete(id: Int) = |
|||
(for { |
|||
mbTodo <- find(id) |
|||
_ <- logger.debug(mbTodo.toString()) |
|||
res <- Task( |
|||
mbTodo.map(todo => todoListView.lvObs.removeAll(todo)) |
|||
) |
|||
_ <- logger.debug(todoListView.lvObs.toString()) |
|||
} yield res.getOrElse(false)).executeOn(fxScheduler) |
|||
def edit(id: Int, content: String) = |
|||
(for { |
|||
mbTodo <- find(id) |
|||
res <- Task( |
|||
mbTodo.map(todo => |
|||
todoListView.lvObs.replaceAll( |
|||
todo, |
|||
Todo(id, content) |
|||
) |
|||
) |
|||
) |
|||
} yield res.getOrElse(false)).executeOn(fxScheduler) |
|||
} |
|||
|
|||
object TodoListOps { |
|||
class Props( |
|||
val todoListView: TodoListView, |
|||
val fxScheduler: Scheduler, |
|||
val logger: Logger[Task] |
|||
) { |
|||
def create = Task(new TodoListOps(this)) |
|||
} |
|||
} |
|||
|
|||
object TodoListComponent { |
|||
sealed trait Complete |
|||
object Complete extends Complete |
|||
|
|||
sealed trait Command |
|||
// sealed trait Tell extends Command |
|||
// sealed abstract class Ask extends Command |
|||
|
|||
case class Add(todo: Todo) extends Command |
|||
case class Find(id: Int, result: Deferred[Task, Option[Todo]]) extends Command |
|||
case class Edit(id: Int, content: String) extends Command |
|||
case class Delete(id: Int) extends Command |
|||
// private case class FindInternal(id: Int, result: Deferred[Task, Todo]) |
|||
// extends Ask |
|||
|
|||
class Props( |
|||
val todoListView: TodoListView, |
|||
val fxScheduler: Scheduler, |
|||
val channel: ConcurrentChannel[ |
|||
Task, |
|||
TodoListComponent.Complete, |
|||
TodoListComponent.Command |
|||
], |
|||
val logger: Logger[Task] |
|||
) { |
|||
|
|||
def create = |
|||
for { |
|||
todoListOps <- |
|||
new TodoListOps.Props(todoListView, fxScheduler, logger).create |
|||
consumer = channel.consume.use(ref => todoConsumer(ref, todoListOps)) |
|||
_ <- consumer.startAndForget |
|||
} yield (new TodoListComponent(this)) |
|||
|
|||
private def todoConsumer( |
|||
consumer: ConsumerF[Task, Complete, Command], |
|||
ops: TodoListOps |
|||
): Task[Unit] = |
|||
consumer.pull |
|||
.flatMap { |
|||
case Left(complete) => logger.info("Received `Complete` event") |
|||
case Right(command) => |
|||
logger.debug(s"Received command $command") >> |
|||
(command match { |
|||
// case t: Tell => |
|||
// t match { |
|||
// case Add(todo) => ops.add(todo) |
|||
// case _ => Task.unit |
|||
// } |
|||
case Add(todo) => ops.add(todo) |
|||
// case Find(id) => |
|||
// for { |
|||
// p <- Deferred[Task, Todo] |
|||
// _ <- channel.push(FindInternal(id, p)) |
|||
// res <- p.get |
|||
// } yield (res) |
|||
case Find(id, result) => |
|||
for { |
|||
mbTodo <- ops.find(id) |
|||
} yield result.complete(mbTodo) |
|||
// case _ => Task.unit |
|||
|
|||
case Delete(id) => ops.delete(id) |
|||
|
|||
case Edit(id, content) => ops.edit(id, content) |
|||
}) |
|||
} |
|||
.flatMap(_ => todoConsumer(consumer, ops)) |
|||
|
|||
} |
|||
|
|||
} |
|||
|
|||
class TodoListComponent(props: TodoListComponent.Props) { |
|||
import props._ |
|||
import TodoListComponent._ |
|||
|
|||
def send(command: Command) = channel.push(command) |
|||
|
|||
def ask[T]( |
|||
commandBuilder: Deferred[Task, T] => Command |
|||
)(implicit timeout: FiniteDuration) = |
|||
for { |
|||
p <- Deferred[Task, T] |
|||
_ <- channel.push(commandBuilder(p)) |
|||
res <- p.get.timeout(timeout) |
|||
} yield res |
|||
|
|||
def stop = channel.halt(Complete) |
|||
|
|||
// import scala.concurrent.duration._ |
|||
// val x = ask(FindInternal(0, _))(2.seconds) |
|||
|
|||
} |
|||
// : F[ProSubject[A, (A, M)]] |
|||
|
|||
// interface Middleware<S, A> { |
|||
// fun dispatch(store: Store<S, A>, next: (A) -> Unit, action: A) |
|||
// } |
|||
|
|||
trait Middleware[A, M] { |
|||
def dispatch[T]( |
|||
store: MonixProSubject[A, (A, M)], |
|||
cb: (A, M) => Task[T], |
|||
cb2: (A, M) => Observable[(A, M)], |
|||
cb3: Observable[(A, M)] => Observable[(A, M)] |
|||
) = { |
|||
// store.fil |
|||
store.mapEval { |
|||
case (a, m) => IOUtils.toTask(cb(a, m)) |
|||
} |
|||
store.flatMap { |
|||
case (a, m) => cb2(a, m) |
|||
} |
|||
|
|||
cb3(store) |
|||
|
|||
def cb3impl(obs: Observable[(A, M)]) = |
|||
obs.doOnNext { |
|||
case (a, m) => IOUtils.toTask(Task(println("hello"))) |
|||
} |
|||
|
|||
def cb3impl2(obs: Observable[(A, M)]) = |
|||
obs.filter { |
|||
case (a, m) => m == "" |
|||
} |
|||
|
|||
cb3impl2(cb3impl(store)) |
|||
|
|||
val s = Seq(cb3impl _) |
|||
|
|||
val res = s.foldLeft(Observable.empty[(A, M)]) { |
|||
case (o1, o2) => o2(o1) |
|||
} |
|||
|
|||
val x = Iterant[Task].of(1, 2, 3) |
|||
|
|||
// x match { |
|||
// case Next(item, rest) => () |
|||
// case Halt(e) => () |
|||
// } |
|||
|
|||
} |
|||
} |
|||
|
|||
object Store { |
|||
type Reducer[A, M] = (M, A) => (M, Observable[A]) |
|||
type MonixProSubject[-I, +O] = Observable[O] with Observer[I] |
|||
// class MonixProSubject2[-I, +O] extends Subject[I, O] |
|||
object MonixProSubject { |
|||
def from[I, O]( |
|||
observer: Observer[I], |
|||
observable: Observable[O] |
|||
): MonixProSubject[I, O] = |
|||
new Observable[O] with Observer[I] { |
|||
override def onNext(elem: I): Future[Ack] = observer.onNext(elem) |
|||
override def onError(ex: Throwable): Unit = observer.onError(ex) |
|||
override def onComplete(): Unit = observer.onComplete() |
|||
override def unsafeSubscribeFn(subscriber: Subscriber[O]): Cancelable = |
|||
observable.unsafeSubscribeFn(subscriber) |
|||
} |
|||
} |
|||
|
|||
def createL[A, M]( |
|||
initialAction: A, |
|||
initialState: M, |
|||
reducer: Reducer[A, M], |
|||
overflowStrategy: OverflowStrategy.Synchronous[A] = |
|||
OverflowStrategy.DropOld(50) |
|||
) = |
|||
Task.deferAction { implicit s => |
|||
Task { |
|||
val subject = ConcurrentSubject.publish[A](overflowStrategy) |
|||
|
|||
val fold: ((A, M), A) => (A, M) = { |
|||
case ((_, state), action) => { |
|||
val (newState, effects) = reducer(state, action) |
|||
|
|||
effects.subscribe(subject.onNext _) |
|||
|
|||
action -> newState |
|||
} |
|||
} |
|||
|
|||
MonixProSubject.from( |
|||
subject, |
|||
subject |
|||
.scan[(A, M)](initialAction -> initialState)(fold) |
|||
.behavior(initialAction -> initialState) |
|||
.refCount |
|||
) |
|||
} |
|||
} |
|||
|
|||
def create[F[_], A, M]( |
|||
initialAction: A, |
|||
initialState: M, |
|||
reducer: Reducer[A, M] |
|||
)(implicit s: Scheduler, F: Sync[F]): F[Observable[(A, M)]] = |
|||
F.delay { |
|||
val subject = ConcurrentSubject.publish[A] |
|||
|
|||
val fold: ((A, M), A) => (A, M) = { |
|||
case ((_, state), action) => { |
|||
val (newState, effects) = reducer(state, action) |
|||
|
|||
effects.subscribe(subject.onNext _) |
|||
|
|||
action -> newState |
|||
} |
|||
} |
|||
|
|||
subject |
|||
.scan[(A, M)](initialAction -> initialState)(fold) |
|||
.behavior(initialAction -> initialState) |
|||
.refCount |
|||
} |
|||
|
|||
} |
|||
|
|||
// object TodoListComponent { |
|||
// sealed trait Complete |
|||
// object Complete extends Complete |
|||
|
|||
// sealed trait Command |
|||
|
|||
// class Props( |
|||
// val todoListView: TodoListView, |
|||
// val fxScheduler: Scheduler, |
|||
// val channel: ConcurrentChannel[ |
|||
// Task, |
|||
// TodoListComponent.Complete, |
|||
// TodoListComponent.Command |
|||
// ] |
|||
// ) { |
|||
// def create = Task(new TodoListComponent(this)) |
|||
// } |
|||
// } |
|||
|
|||
// class TodoListComponent(props: TodoListComponent.Props) { |
|||
// import props._ |
|||
// import TodoListComponent._ |
|||
// def init = |
|||
// for { |
|||
// todoListOps <- new TodoListOps.Props(todoListView, fxScheduler).create |
|||
// consumer = channel.consume.use(ref => todoConsumer(ref, todoListOps)) |
|||
// _ <- consumer.startAndForget |
|||
// } yield () |
|||
|
|||
// def send(command: Command) = channel.push(command) |
|||
|
|||
// def todoConsumer( |
|||
// consumer: ConsumerF[Task, Complete, Command], |
|||
// ops: TodoListOps |
|||
// ) = |
|||
// consumer.pull.flatMap { |
|||
// case Left(value) => Task.unit |
|||
// case Right(value) => Task.unit |
|||
// } |
|||
// } |
|||
|
|||
// def askHandler( |
|||
// channel: ConcurrentChannel[ |
|||
// Task, |
|||
// TodoListComponent.Complete, |
|||
// TodoListComponent.Command |
|||
// ], |
|||
// consumer: ConsumerF[Task, Complete, Command], |
|||
// ops: TodoListOps |
|||
// ) = |
|||
// consumer.pull.flatMap { |
|||
// case Left(complete) => Task.unit |
|||
// case Right(command) => |
|||
// command match { |
|||
// case a: Ask => |
|||
// a match { |
|||
// case Find(id) => |
|||
// for { |
|||
// p <- Deferred[Task, Todo] |
|||
// _ <- channel.push(FindInternal(id, p)) |
|||
// res <- p.get |
|||
// } yield (res) |
|||
// case FindInternal(id, result) => |
|||
// for { |
|||
// mb <- ops.find(id) |
|||
// } yield result.complete(mb.get) |
|||
// case _ => Task.unit |
|||
// } |
|||
// case _ => Task.unit |
|||
// } |
|||
// } |
@ -0,0 +1,95 @@ |
|||
package nova.monadic_sfx.ui.controller |
|||
|
|||
import animatefx.animation.AnimationFX |
|||
import animatefx.animation.Bounce |
|||
import animatefx.animation.FadeIn |
|||
import animatefx.util.{SequentialAnimationFX => SeqFX} |
|||
import cats.effect.Sync |
|||
import monix.eval.Task |
|||
import nova.monadic_sfx.implicits.FontIcon |
|||
import nova.monadic_sfx.implicits.IconLiteral |
|||
import nova.monadic_sfx.implicits.JFXButton |
|||
import nova.monadic_sfx.implicits.JFXListView |
|||
import nova.monadic_sfx.implicits.JFXTextArea |
|||
import nova.monadic_sfx.implicits.JFXTextField |
|||
import nova.monadic_sfx.ui.components.todo.TodoListComponent |
|||
import scalafx.collections.ObservableBuffer |
|||
import scalafx.scene.control.Label |
|||
import scalafx.scene.layout.HBox |
|||
import scalafx.scene.paint.Color |
|||
|
|||
class TodoController(todoListComponent: TodoListComponent) { |
|||
import AnimFX._ |
|||
def root = |
|||
new HBox { |
|||
children = Seq( |
|||
new Label { |
|||
text = "Todo" |
|||
}, |
|||
new JFXButton { |
|||
text = " Click me" |
|||
onAction = _ => { |
|||
new FadeIn(new Label("hello")).play() |
|||
val anim = new SeqFX( |
|||
new Bounce(new Label("hello")), |
|||
new FadeIn(new Label("hello")) |
|||
).toAnimFX[Task] |
|||
for { |
|||
_ <- Task.unit |
|||
_ <- anim.playL |
|||
} yield () |
|||
} |
|||
}, |
|||
new JFXTextField { |
|||
text = "hello" |
|||
labelFloat = true |
|||
}, |
|||
new JFXListView[String] { |
|||
items = ObservableBuffer("hello") |
|||
}, |
|||
new JFXTextArea { |
|||
prefWidth = 400 |
|||
text = "blah" |
|||
labelFloat = true |
|||
}, |
|||
new FontIcon { |
|||
iconSize = 50 |
|||
iconColor = Color.Black |
|||
iconLiteral = IconLiteral.Gmi10k |
|||
} |
|||
) |
|||
} |
|||
|
|||
// def test() = { |
|||
// new TextField("hello").lookup() |
|||
// } |
|||
|
|||
// def test2() = { |
|||
// new Label().lookup() |
|||
// } |
|||
} |
|||
|
|||
abstract class AnimFX[F[_]: Sync] { |
|||
def playL: F[Unit] |
|||
} |
|||
object AnimFX { |
|||
implicit class AnimationFXExt(anim: AnimationFX) { |
|||
def toAnimFX[F[_]](implicit |
|||
F: Sync[F] |
|||
) = |
|||
new AnimFX[F] { |
|||
override def playL: F[Unit] = |
|||
F.delay(anim.play()) |
|||
} |
|||
} |
|||
|
|||
implicit class SeqAnimationFXExt(anim: SeqFX) { |
|||
def toAnimFX[F[_]](implicit |
|||
F: Sync[F] |
|||
) = |
|||
new AnimFX[F] { |
|||
override def playL: F[Unit] = |
|||
F.delay(anim.play()) |
|||
} |
|||
} |
|||
} |
@ -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) |
|||
|
|||
} |
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue