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

  1. package nova.monadic_sfx.ui.components.todo
  2. import scala.concurrent.Future
  3. import scala.concurrent.duration.FiniteDuration
  4. import cats.effect.Sync
  5. import cats.effect.concurrent.Deferred
  6. import io.odin.Logger
  7. import monix.bio.Task
  8. import monix.catnap.ConcurrentChannel
  9. import monix.catnap.ConsumerF
  10. import monix.execution.Ack
  11. import monix.execution.Cancelable
  12. import monix.execution.Scheduler
  13. import monix.reactive.Observable
  14. import monix.reactive.Observer
  15. import monix.reactive.OverflowStrategy
  16. import monix.reactive.observers.Subscriber
  17. import monix.reactive.subjects.ConcurrentSubject
  18. import nova.monadic_sfx.implicits.FontIcon
  19. import nova.monadic_sfx.implicits.IconLiteral
  20. import nova.monadic_sfx.implicits.JFXListView
  21. import nova.monadic_sfx.implicits.JavaFXMonixObservables._
  22. import nova.monadic_sfx.ui.components.todo.TodoListComponent.Add
  23. import nova.monadic_sfx.ui.components.todo.TodoListComponent.Delete
  24. import nova.monadic_sfx.ui.components.todo.TodoListComponent.Edit
  25. import scalafx.Includes._
  26. import scalafx.beans.property.StringProperty
  27. import scalafx.collections.ObservableBuffer
  28. import scalafx.scene.control.ContextMenu
  29. import scalafx.scene.control.ListCell
  30. import scalafx.scene.control.MenuItem
  31. import scalafx.scene.control.SelectionMode
  32. import scalafx.scene.layout.HBox
  33. import scalafx.scene.text.Text
  34. import nova.monadic_sfx.ui.components.todo.Store.MonixProSubject
  35. import nova.monadic_sfx.util.IOUtils
  36. import monix.tail.Iterant
  37. case class Todo(id: Int, content: String)
  38. class TodoListView(
  39. val listView: JFXListView[Todo] = TodoListView.defaultListView,
  40. val lvObs: ObservableBuffer[Todo] = ObservableBuffer.empty
  41. ) {
  42. listView.items = lvObs
  43. }
  44. object TodoListView {
  45. def defaultListView =
  46. new JFXListView[Todo] {
  47. // cellFactory = _ =>
  48. // new ListCell[Todo] {
  49. // // item.onChange((a, b, c) => ())
  50. // overr
  51. // }
  52. contextMenu = new ContextMenu {
  53. items ++= Seq(
  54. new MenuItem {
  55. text = "delete"
  56. },
  57. new MenuItem {
  58. text = "edit"
  59. }
  60. )
  61. }
  62. }
  63. // import scalafx.scene.control.MultipleSelectionModel
  64. // .getOrElse(Todo(-1, "blah"))
  65. implicit class Operations[A](val sink: Observer[A]) extends AnyVal {}
  66. // def reducer(
  67. // stateC: Coeval[ObservableBuffer[Todo]],
  68. // action: TodoListComponent.Command
  69. // ) =
  70. // action match {
  71. // case Add(todo) =>
  72. // for {
  73. // state <- stateC
  74. // } yield state :+ todo
  75. // // case Find(id, result) =>
  76. // case Edit(id, content) => stateC
  77. // case Delete(id) =>
  78. // for {
  79. // state <- stateC
  80. // } yield state.filterNot(_.id == id)
  81. // case _ => stateC
  82. // }
  83. def reducer(
  84. state: Vector[Todo],
  85. action: TodoListComponent.Command
  86. ) =
  87. action match {
  88. case Add(todo) => state :+ todo
  89. // case Find(id, result) =>
  90. case Edit(id, content) => state
  91. case Delete(id) =>
  92. state.filterNot(_.id == id)
  93. case _ => state
  94. }
  95. def defaultListView2: Task[
  96. (
  97. JFXListView[Todo],
  98. Observable[Todo],
  99. Observable[Todo]
  100. )
  101. ] =
  102. Task.deferAction(implicit s =>
  103. Store
  104. .createL[TodoListComponent.Command, Vector[Todo]](
  105. TodoListComponent.Delete(0),
  106. Vector.empty[Todo],
  107. (s: Vector[Todo], a: TodoListComponent.Command) =>
  108. reducer(s, a) -> Observable.empty
  109. )
  110. .flatMap(store =>
  111. Task {
  112. val deleteSub = ConcurrentSubject.publish[Todo]
  113. val editSub = ConcurrentSubject.publish[Todo]
  114. // store.flatMap(st => Task(st.sink))
  115. // val deleteSub2 =
  116. // deleteSub.map(todo => (buf: ObservableBuffer[Todo]) => buf :+ todo)
  117. // val addSub =
  118. // ConcurrentSubject
  119. // .publish[Todo]
  120. // .map(todo => (buf: ObservableBuffer[Todo]) => buf :+ todo)
  121. // val state = Observable(deleteSub2, addSub).merge.scan0(
  122. // ObservableBuffer.empty[Todo]
  123. // )((buf, fn) => fn(buf))
  124. val todos =
  125. store.map { case (_, items) => items }
  126. val listView = new JFXListView[Todo] { lv =>
  127. def selectedItems = lv.selectionModel().selectedItems.view
  128. // items = todos
  129. items <-- todos
  130. // .map(ObservableBuffer.from(_))
  131. cellFactory = _ =>
  132. new ListCell[Todo] {
  133. val _text = StringProperty("")
  134. val _graphic = new HBox {
  135. children = Seq(
  136. new FontIcon {
  137. iconSize = 10
  138. iconLiteral = IconLiteral.Gmi10k
  139. },
  140. new Text {
  141. text <== _text
  142. }
  143. )
  144. }
  145. item.onChange((_, _, todo) => {
  146. println("called")
  147. if (todo != null) {
  148. _text() = s"${todo.id} - ${todo.content}"
  149. graphic = _graphic
  150. } else {
  151. _text() = ""
  152. graphic = null
  153. }
  154. })
  155. }
  156. selectionModel().selectionMode = SelectionMode.Multiple
  157. contextMenu = new ContextMenu {
  158. items ++= Seq(
  159. new MenuItem {
  160. text = "Add"
  161. onAction = _ =>
  162. store.sink
  163. .onNext(TodoListComponent.Add(Todo(1, "blah3")))
  164. },
  165. new MenuItem {
  166. text = "Delete"
  167. // onAction = _ =>
  168. // for {
  169. // items <- Option(lv.selectionModel().selectedItems)
  170. // _ <- Some(items.foreach(item => deleteSub.onNext(item)))
  171. // } yield ()
  172. onAction = _ =>
  173. selectedItems
  174. .map(todo => TodoListComponent.Delete(todo.id))
  175. .foreach(store.sink.onNext)
  176. },
  177. new MenuItem {
  178. text = "Edit"
  179. // onAction = _ =>
  180. // Option(lv.selectionModel().selectedItems).foreach(items =>
  181. // items.foreach(item => editSub.onNext(item))
  182. // )
  183. }
  184. )
  185. }
  186. }
  187. (listView, deleteSub, editSub)
  188. }
  189. )
  190. )
  191. }
  192. private[todo] class TodoListComponentImpure(
  193. todoListView: TodoListView
  194. ) {
  195. def add(todo: Todo) = todoListView.lvObs += todo
  196. def find(id: Int) = todoListView.lvObs.find(_.id == id)
  197. def edit(id: Int, content: String) =
  198. find(id)
  199. .map(todo =>
  200. todoListView.lvObs.replaceAll(
  201. todo,
  202. Todo(id, content)
  203. )
  204. )
  205. .getOrElse(false)
  206. }
  207. class TodoListOps private (
  208. props: TodoListOps.Props
  209. ) {
  210. import props._
  211. // lazy val internal = new TodoListComponentImpure(todoListView)
  212. // def add(todo: Todo) = Task(internal.add(todo))
  213. def add(todo: Todo) = Task(todoListView.lvObs += todo).executeOn(fxScheduler)
  214. def find(id: Int) =
  215. Task(todoListView.lvObs.find(_.id == id)).executeOn(fxScheduler)
  216. def delete(id: Int) =
  217. (for {
  218. mbTodo <- find(id)
  219. _ <- logger.debug(mbTodo.toString())
  220. res <- Task(
  221. mbTodo.map(todo => todoListView.lvObs.removeAll(todo))
  222. )
  223. _ <- logger.debug(todoListView.lvObs.toString())
  224. } yield res.getOrElse(false)).executeOn(fxScheduler)
  225. def edit(id: Int, content: String) =
  226. (for {
  227. mbTodo <- find(id)
  228. res <- Task(
  229. mbTodo.map(todo =>
  230. todoListView.lvObs.replaceAll(
  231. todo,
  232. Todo(id, content)
  233. )
  234. )
  235. )
  236. } yield res.getOrElse(false)).executeOn(fxScheduler)
  237. }
  238. object TodoListOps {
  239. class Props(
  240. val todoListView: TodoListView,
  241. val fxScheduler: Scheduler,
  242. val logger: Logger[Task]
  243. ) {
  244. def create = Task(new TodoListOps(this))
  245. }
  246. }
  247. object TodoListComponent {
  248. sealed trait Complete
  249. object Complete extends Complete
  250. sealed trait Command
  251. // sealed trait Tell extends Command
  252. // sealed abstract class Ask extends Command
  253. case class Add(todo: Todo) extends Command
  254. case class Find(id: Int, result: Deferred[Task, Option[Todo]]) extends Command
  255. case class Edit(id: Int, content: String) extends Command
  256. case class Delete(id: Int) extends Command
  257. // private case class FindInternal(id: Int, result: Deferred[Task, Todo])
  258. // extends Ask
  259. class Props(
  260. val todoListView: TodoListView,
  261. val fxScheduler: Scheduler,
  262. val channel: ConcurrentChannel[
  263. Task,
  264. TodoListComponent.Complete,
  265. TodoListComponent.Command
  266. ],
  267. val logger: Logger[Task]
  268. ) {
  269. def create =
  270. for {
  271. todoListOps <-
  272. new TodoListOps.Props(todoListView, fxScheduler, logger).create
  273. consumer = channel.consume.use(ref => todoConsumer(ref, todoListOps))
  274. _ <- consumer.startAndForget
  275. } yield (new TodoListComponent(this))
  276. private def todoConsumer(
  277. consumer: ConsumerF[Task, Complete, Command],
  278. ops: TodoListOps
  279. ): Task[Unit] =
  280. consumer.pull
  281. .flatMap {
  282. case Left(complete) => logger.info("Received `Complete` event")
  283. case Right(command) =>
  284. logger.debug(s"Received command $command") >>
  285. (command match {
  286. // case t: Tell =>
  287. // t match {
  288. // case Add(todo) => ops.add(todo)
  289. // case _ => Task.unit
  290. // }
  291. case Add(todo) => ops.add(todo)
  292. // case Find(id) =>
  293. // for {
  294. // p <- Deferred[Task, Todo]
  295. // _ <- channel.push(FindInternal(id, p))
  296. // res <- p.get
  297. // } yield (res)
  298. case Find(id, result) =>
  299. for {
  300. mbTodo <- ops.find(id)
  301. } yield result.complete(mbTodo)
  302. // case _ => Task.unit
  303. case Delete(id) => ops.delete(id)
  304. case Edit(id, content) => ops.edit(id, content)
  305. })
  306. }
  307. .flatMap(_ => todoConsumer(consumer, ops))
  308. }
  309. }
  310. class TodoListComponent(props: TodoListComponent.Props) {
  311. import props._
  312. import TodoListComponent._
  313. def send(command: Command) = channel.push(command)
  314. def ask[T](
  315. commandBuilder: Deferred[Task, T] => Command
  316. )(implicit timeout: FiniteDuration) =
  317. for {
  318. p <- Deferred[Task, T]
  319. _ <- channel.push(commandBuilder(p))
  320. res <- p.get.timeout(timeout)
  321. } yield res
  322. def stop = channel.halt(Complete)
  323. // import scala.concurrent.duration._
  324. // val x = ask(FindInternal(0, _))(2.seconds)
  325. }
  326. // : F[ProSubject[A, (A, M)]]
  327. // interface Middleware<S, A> {
  328. // fun dispatch(store: Store<S, A>, next: (A) -> Unit, action: A)
  329. // }
  330. trait Middleware[A, M] {
  331. def dispatch[T](
  332. store: MonixProSubject[A, (A, M)],
  333. cb: (A, M) => Task[T],
  334. cb2: (A, M) => Observable[(A, M)],
  335. cb3: Observable[(A, M)] => Observable[(A, M)]
  336. ) = {
  337. // store.fil
  338. store.mapEval {
  339. case (a, m) => IOUtils.toTask(cb(a, m))
  340. }
  341. store.flatMap {
  342. case (a, m) => cb2(a, m)
  343. }
  344. cb3(store)
  345. def cb3impl(obs: Observable[(A, M)]) =
  346. obs.doOnNext {
  347. case (a, m) => IOUtils.toTask(Task(println("hello")))
  348. }
  349. def cb3impl2(obs: Observable[(A, M)]) =
  350. obs.filter {
  351. case (a, m) => m == ""
  352. }
  353. cb3impl2(cb3impl(store))
  354. val s = Seq(cb3impl _)
  355. val res = s.foldLeft(Observable.empty[(A, M)]) {
  356. case (o1, o2) => o2(o1)
  357. }
  358. val x = Iterant[Task].of(1, 2, 3)
  359. // x match {
  360. // case Next(item, rest) => ()
  361. // case Halt(e) => ()
  362. // }
  363. }
  364. }
  365. object Store {
  366. type Reducer[A, M] = (M, A) => (M, Observable[A])
  367. type MonixProSubject[-I, +O] = Observable[O] with Observer[I]
  368. // class MonixProSubject2[-I, +O] extends Subject[I, O]
  369. object MonixProSubject {
  370. def from[I, O](
  371. observer: Observer[I],
  372. observable: Observable[O]
  373. ): MonixProSubject[I, O] =
  374. new Observable[O] with Observer[I] {
  375. override def onNext(elem: I): Future[Ack] = observer.onNext(elem)
  376. override def onError(ex: Throwable): Unit = observer.onError(ex)
  377. override def onComplete(): Unit = observer.onComplete()
  378. override def unsafeSubscribeFn(subscriber: Subscriber[O]): Cancelable =
  379. observable.unsafeSubscribeFn(subscriber)
  380. }
  381. }
  382. def createL[A, M](
  383. initialAction: A,
  384. initialState: M,
  385. reducer: Reducer[A, M],
  386. overflowStrategy: OverflowStrategy.Synchronous[A] =
  387. OverflowStrategy.DropOld(50)
  388. ) =
  389. Task.deferAction { implicit s =>
  390. Task {
  391. val subject = ConcurrentSubject.publish[A](overflowStrategy)
  392. val fold: ((A, M), A) => (A, M) = {
  393. case ((_, state), action) => {
  394. val (newState, effects) = reducer(state, action)
  395. effects.subscribe(subject.onNext _)
  396. action -> newState
  397. }
  398. }
  399. MonixProSubject.from(
  400. subject,
  401. subject
  402. .scan[(A, M)](initialAction -> initialState)(fold)
  403. .behavior(initialAction -> initialState)
  404. .refCount
  405. )
  406. }
  407. }
  408. def create[F[_], A, M](
  409. initialAction: A,
  410. initialState: M,
  411. reducer: Reducer[A, M]
  412. )(implicit s: Scheduler, F: Sync[F]): F[Observable[(A, M)]] =
  413. F.delay {
  414. val subject = ConcurrentSubject.publish[A]
  415. val fold: ((A, M), A) => (A, M) = {
  416. case ((_, state), action) => {
  417. val (newState, effects) = reducer(state, action)
  418. effects.subscribe(subject.onNext _)
  419. action -> newState
  420. }
  421. }
  422. subject
  423. .scan[(A, M)](initialAction -> initialState)(fold)
  424. .behavior(initialAction -> initialState)
  425. .refCount
  426. }
  427. }
  428. // object TodoListComponent {
  429. // sealed trait Complete
  430. // object Complete extends Complete
  431. // sealed trait Command
  432. // class Props(
  433. // val todoListView: TodoListView,
  434. // val fxScheduler: Scheduler,
  435. // val channel: ConcurrentChannel[
  436. // Task,
  437. // TodoListComponent.Complete,
  438. // TodoListComponent.Command
  439. // ]
  440. // ) {
  441. // def create = Task(new TodoListComponent(this))
  442. // }
  443. // }
  444. // class TodoListComponent(props: TodoListComponent.Props) {
  445. // import props._
  446. // import TodoListComponent._
  447. // def init =
  448. // for {
  449. // todoListOps <- new TodoListOps.Props(todoListView, fxScheduler).create
  450. // consumer = channel.consume.use(ref => todoConsumer(ref, todoListOps))
  451. // _ <- consumer.startAndForget
  452. // } yield ()
  453. // def send(command: Command) = channel.push(command)
  454. // def todoConsumer(
  455. // consumer: ConsumerF[Task, Complete, Command],
  456. // ops: TodoListOps
  457. // ) =
  458. // consumer.pull.flatMap {
  459. // case Left(value) => Task.unit
  460. // case Right(value) => Task.unit
  461. // }
  462. // }
  463. // def askHandler(
  464. // channel: ConcurrentChannel[
  465. // Task,
  466. // TodoListComponent.Complete,
  467. // TodoListComponent.Command
  468. // ],
  469. // consumer: ConsumerF[Task, Complete, Command],
  470. // ops: TodoListOps
  471. // ) =
  472. // consumer.pull.flatMap {
  473. // case Left(complete) => Task.unit
  474. // case Right(command) =>
  475. // command match {
  476. // case a: Ask =>
  477. // a match {
  478. // case Find(id) =>
  479. // for {
  480. // p <- Deferred[Task, Todo]
  481. // _ <- channel.push(FindInternal(id, p))
  482. // res <- p.get
  483. // } yield (res)
  484. // case FindInternal(id, result) =>
  485. // for {
  486. // mb <- ops.find(id)
  487. // } yield result.complete(mb.get)
  488. // case _ => Task.unit
  489. // }
  490. // case _ => Task.unit
  491. // }
  492. // }