You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

556 lines
16 KiB

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