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
-
6src/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
-
30src/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
-
33src/main/scala/nova/monadic_sfx/ui/DefaultUI.scala
-
142src/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 |
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.ExitCode |
||||
|
import cats.effect.Resource |
||||
import cats.implicits._ |
import cats.implicits._ |
||||
import com.softwaremill.macwire._ |
import com.softwaremill.macwire._ |
||||
|
import io.odin._ |
||||
|
import io.odin.syntax._ |
||||
|
import nova.monadic_sfx.executors._ |
||||
|
import nova.monadic_sfx.util.IOUtils._ |
||||
|
import sttp.client.httpclient.monix.HttpClientMonixBackend |
||||
|
object Main extends MainModule with BIOApp { |
||||
|
|
||||
object Main extends MainModule with TaskApp { |
|
||||
|
|
||||
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) |
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) |
.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 |
package nova.monadic_sfx |
||||
|
|
||||
import nova.monadic_sfx.actors.ActorModule |
import nova.monadic_sfx.actors.ActorModule |
||||
import nova.monadic_sfx.ui.UiModule |
|
||||
import nova.monadic_sfx.http.HttpModule |
import nova.monadic_sfx.http.HttpModule |
||||
|
import nova.monadic_sfx.ui.UiModule |
||||
|
|
||||
trait MainModule extends ActorModule with UiModule with HttpModule |
trait MainModule extends ActorModule with UiModule with HttpModule |
@ -1,13 +1,14 @@ |
|||||
package nova.monadic_sfx |
package nova.monadic_sfx |
||||
|
|
||||
|
import java.nio.ByteBuffer |
||||
|
|
||||
import monix.eval.Task |
import monix.eval.Task |
||||
import sttp.client.SttpBackend |
|
||||
import monix.reactive.Observable |
import monix.reactive.Observable |
||||
|
import sttp.client.SttpBackend |
||||
import sttp.client.asynchttpclient.WebSocketHandler |
import sttp.client.asynchttpclient.WebSocketHandler |
||||
import java.nio.ByteBuffer |
|
||||
|
|
||||
trait AppTypes {} |
|
||||
object AppTypes { |
|
||||
|
trait AppTypes { |
||||
type HttpBackend = |
type HttpBackend = |
||||
SttpBackend[Task, Observable[ByteBuffer], WebSocketHandler] |
SttpBackend[Task, Observable[ByteBuffer], WebSocketHandler] |
||||
} |
} |
||||
|
object AppTypes extends AppTypes {} |
@ -1,74 +1,28 @@ |
|||||
package nova.monadic_sfx.actors |
package nova.monadic_sfx.actors |
||||
|
|
||||
import io.odin.Logger |
|
||||
import monix.eval.Task |
|
||||
import cats.effect.Resource |
|
||||
import akka.actor.typed.scaladsl.Behaviors |
|
||||
import com.softwaremill.macwire._ |
|
||||
import akka.util.Timeout |
|
||||
import scala.concurrent.duration._ |
import scala.concurrent.duration._ |
||||
import scala.concurrent.Future |
|
||||
|
|
||||
import akka.actor.typed._ |
import akka.actor.typed._ |
||||
import akka.actor.typed.scaladsl.AskPattern._ |
|
||||
import 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 { |
trait ActorModule { |
||||
import scala.concurrent.ExecutionContext |
|
||||
|
|
||||
implicit val timeout: Timeout = Timeout(3.seconds) |
|
||||
|
implicit def timeout: Timeout = Timeout(3.seconds) |
||||
|
|
||||
def actorSystemResource( |
def actorSystemResource( |
||||
logger: Logger[Task] |
logger: Logger[Task] |
||||
): Resource[Task, ActorSystem[SpawnProtocol.Command]] = |
): Resource[Task, ActorSystem[SpawnProtocol.Command]] = |
||||
Resource.make(logger.info("Creating Actor System") >> Task { |
Resource.make(logger.info("Creating Actor System") >> Task { |
||||
ActorSystem(HelloWorldMain(), name = "FXActorSystem") |
|
||||
|
ActorSystem(SpawnProtocol(), name = "FXActorSystem") |
||||
})(sys => |
})(sys => |
||||
logger.info("Shutting down actor system") >> Task( |
|
||||
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 |
package nova.monadic_sfx.executors |
||||
|
|
||||
|
import com.typesafe.scalalogging.Logger |
||||
import monix.execution.Scheduler |
import monix.execution.Scheduler |
||||
|
import monix.execution.UncaughtExceptionReporter |
||||
|
import monix.execution.schedulers.TracingScheduler |
||||
|
|
||||
class Schedulers( |
class Schedulers( |
||||
val blockingIO: Scheduler = Scheduler.io(), |
|
||||
val 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 |
package nova.monadic_sfx.http.requests |
||||
|
|
||||
import nova.monadic_sfx.AppTypes |
import nova.monadic_sfx.AppTypes |
||||
|
|
||||
import nova.monadic_sfx.AppTypes.HttpBackend |
import nova.monadic_sfx.AppTypes.HttpBackend |
||||
import monix.eval.Task |
|
||||
|
import nova.monadic_sfx.models._ |
||||
import sttp.client._ |
import sttp.client._ |
||||
import sttp.client.circe._ |
import sttp.client.circe._ |
||||
import io.circe.generic.auto._ |
|
||||
import nova.monadic_sfx.models._ |
|
||||
import cats.data.EitherT |
|
||||
|
|
||||
class DummyRequest(backend: HttpBackend) extends AppTypes { |
class DummyRequest(backend: HttpBackend) extends AppTypes { |
||||
private implicit val _backend = backend |
private implicit val _backend = backend |
||||
def send() = { |
|
||||
Task |
|
||||
.suspend { |
|
||||
for { |
|
||||
req <- |
|
||||
|
def send = |
||||
basicRequest |
basicRequest |
||||
.get(uri"https://httpbin.org/get") |
.get(uri"https://httpbin.org/get") |
||||
.response(asJson[HttpBinResponse]) |
.response(asJson[HttpBinResponse]) |
||||
.send() |
.send() |
||||
} yield (req) |
|
||||
} |
|
||||
|
|
||||
} |
} |
||||
|
|
||||
def test() = { |
|
||||
for { |
|
||||
res <- send() |
|
||||
res3 <- Task { res.body } |
|
||||
res2 <- Task { |
|
||||
res3.fold( |
|
||||
err => { |
|
||||
err.toString() |
|
||||
}, |
|
||||
value => value.toString() |
|
||||
) |
|
||||
} |
|
||||
} yield (res3) |
|
||||
} |
|
||||
} |
|
@ -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 |
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, |
url: String, |
||||
origin: String, |
origin: String, |
||||
headers: Map[String, String] |
headers: Map[String, String] |
||||
|
@ -1,129 +1,41 @@ |
|||||
package nova.monadic_sfx.ui |
package nova.monadic_sfx.ui |
||||
|
|
||||
import scalafx.application.JFXApp |
|
||||
import nova.monadic_sfx.executors.Schedulers |
|
||||
import monix.execution.Scheduler |
|
||||
import monix.eval.Task |
|
||||
import nova.monadic_sfx.screens.LoginScreen |
|
||||
import nova.monadic_sfx.AppTypes |
|
||||
import scala.concurrent.duration._ |
import scala.concurrent.duration._ |
||||
import io.odin.Logger |
|
||||
import monix.execution.Callback |
|
||||
import com.softwaremill.macwire._ |
|
||||
import nova.monadic_sfx.http.Requesters |
|
||||
|
|
||||
import akka.actor.typed._ |
|
||||
import nova.monadic_sfx.actors.Counter |
|
||||
import akka.util.Timeout |
|
||||
|
|
||||
class MyFxApp( |
|
||||
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 _ |
|
||||
) |
|
||||
|
import io.odin.Logger |
||||
|
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 |
||||
|
|
||||
lazy val application = |
|
||||
for { |
|
||||
appStage <- Task(wireWith(UiModule.makePrimaryStage _)) |
|
||||
|
class MyFxApp(val schedulers: Schedulers)(implicit logger: Logger[Task]) { |
||||
|
|
||||
// _ <- 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 |
|
||||
) |
|
||||
|
private lazy val internal = new JFXApp { |
||||
|
stage = new PrimaryStage { |
||||
|
scene = DefaultUI.scene |
||||
} |
} |
||||
} 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() |
|
||||
} |
|
||||
|
// def stage = Task(internal.stage) |
||||
|
|
||||
} |
|
||||
) |
|
||||
|
// def stage_=(stage: PrimaryStage) = Task(internal.stage = stage) |
||||
|
|
||||
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() |
|
||||
} |
|
||||
} |
|
||||
|
def useInternal[T](f: JFXApp => Task[T]): Task[T] = |
||||
|
for { |
||||
|
_ <- logger.debug("Request for using internal value") |
||||
|
res <- f(internal).executeOn(schedulers.fx) |
||||
|
_ <- logger.debug(s"Result was ${res.toString()}") |
||||
|
} yield (res) |
||||
|
|
||||
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