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.

378 lines
11 KiB

  1. package nova.monadic_sfx.ui.components.todo
  2. import scala.concurrent.duration.FiniteDuration
  3. import cats.effect.concurrent.Deferred
  4. import io.odin.Logger
  5. import monix.bio.Task
  6. import monix.catnap.ConcurrentChannel
  7. import monix.catnap.ConsumerF
  8. import monix.execution.Scheduler
  9. import monix.reactive.Observable
  10. import monix.reactive.Observer
  11. import nova.monadic_sfx.util.controls.FontIcon
  12. import nova.monadic_sfx.util.controls.IconLiteral
  13. import nova.monadic_sfx.util.controls.JFXListView
  14. import nova.monadic_sfx.implicits._
  15. import nova.monadic_sfx.util.reactive.store._
  16. import scalafx.Includes._
  17. import scalafx.beans.property.StringProperty
  18. import scalafx.collections.ObservableBuffer
  19. import scalafx.scene.control.ContextMenu
  20. import scalafx.scene.control.ListCell
  21. import scalafx.scene.control.MenuItem
  22. import scalafx.scene.control.SelectionMode
  23. import scalafx.scene.layout.HBox
  24. import scalafx.scene.text.Text
  25. class TodoListViewOld(
  26. val listView: JFXListView[Todo] = TodoListViewOld.defaultListView,
  27. val lvObs: ObservableBuffer[Todo] = ObservableBuffer.empty
  28. ) {
  29. listView.items = lvObs
  30. }
  31. object TodoListViewOld {
  32. def defaultListView =
  33. new JFXListView[Todo] {
  34. contextMenu = new ContextMenu {
  35. items ++= Seq(
  36. new MenuItem {
  37. text = "delete"
  38. },
  39. new MenuItem {
  40. text = "edit"
  41. }
  42. )
  43. }
  44. }
  45. implicit class Operations[A](val sink: Observer[A]) extends AnyVal {}
  46. def defaultListView2(
  47. store: MonixProSubject[
  48. TodoListComponentOld.Command,
  49. (TodoListComponentOld.Command, Vector[Todo])
  50. ]
  51. ): Task[JFXListView[Todo]] =
  52. Task.deferAction(implicit s =>
  53. Task {
  54. val todos =
  55. store.map { case (_, items) => items }
  56. val listView = new JFXListView[Todo] { lv =>
  57. def selectedItems = lv.selectionModel().selectedItems.view
  58. // items = todos
  59. items <-- todos
  60. // .map(ObservableBuffer.from(_))
  61. cellFactory = _ =>
  62. new ListCell[Todo] {
  63. val _text = StringProperty("")
  64. val _graphic = new HBox {
  65. children = Seq(
  66. new FontIcon {
  67. iconSize = 10
  68. iconLiteral = IconLiteral.Gmi10k
  69. },
  70. new Text {
  71. text <== _text
  72. }
  73. )
  74. }
  75. item.onChange((_, _, todo) => {
  76. println("called")
  77. if (todo != null) {
  78. _text() = s"${todo.id} - ${todo.content}"
  79. graphic = _graphic
  80. } else {
  81. _text() = ""
  82. graphic = null
  83. }
  84. })
  85. }
  86. selectionModel().selectionMode = SelectionMode.Multiple
  87. contextMenu = new ContextMenu {
  88. items ++= Seq(
  89. new MenuItem {
  90. text = "Add"
  91. onAction = _ =>
  92. store.sink
  93. .onNext(TodoListComponentOld.Add(Todo(1, "blah3")))
  94. },
  95. new MenuItem {
  96. text = "Delete"
  97. // onAction = _ =>
  98. // for {
  99. // items <- Option(lv.selectionModel().selectedItems)
  100. // _ <- Some(items.foreach(item => deleteSub.onNext(item)))
  101. // } yield ()
  102. onAction = _ =>
  103. selectedItems
  104. .map(todo => TodoListComponentOld.Delete(todo.id))
  105. .foreach(store.sink.onNext)
  106. },
  107. new MenuItem {
  108. text = "Edit"
  109. // onAction = _ =>
  110. // Option(lv.selectionModel().selectedItems).foreach(items =>
  111. // items.foreach(item => editSub.onNext(item))
  112. // )
  113. }
  114. )
  115. }
  116. }
  117. listView
  118. }
  119. )
  120. }
  121. private[todo] class TodoListComponentImpure(
  122. todoListView: TodoListViewOld
  123. ) {
  124. def add(todo: Todo) = todoListView.lvObs += todo
  125. def find(id: Int) = todoListView.lvObs.find(_.id == id)
  126. def edit(id: Int, content: String) =
  127. find(id)
  128. .map(todo =>
  129. todoListView.lvObs.replaceAll(
  130. todo,
  131. Todo(id, content)
  132. )
  133. )
  134. .getOrElse(false)
  135. }
  136. class TodoListOps private (
  137. props: TodoListOps.Props
  138. ) {
  139. import props._
  140. // lazy val internal = new TodoListComponentImpure(todoListView)
  141. // def add(todo: Todo) = Task(internal.add(todo))
  142. def add(todo: Todo) = Task(todoListView.lvObs += todo).executeOn(fxScheduler)
  143. def find(id: Int) =
  144. Task(todoListView.lvObs.find(_.id == id)).executeOn(fxScheduler)
  145. def delete(id: Int) =
  146. (for {
  147. mbTodo <- find(id)
  148. _ <- logger.debug(mbTodo.toString())
  149. res <- Task(
  150. mbTodo.map(todo => todoListView.lvObs.removeAll(todo))
  151. )
  152. _ <- logger.debug(todoListView.lvObs.toString())
  153. } yield res.getOrElse(false)).executeOn(fxScheduler)
  154. def edit(id: Int, content: String) =
  155. (for {
  156. mbTodo <- find(id)
  157. res <- Task(
  158. mbTodo.map(todo =>
  159. todoListView.lvObs.replaceAll(
  160. todo,
  161. Todo(id, content)
  162. )
  163. )
  164. )
  165. } yield res.getOrElse(false)).executeOn(fxScheduler)
  166. }
  167. object TodoListOps {
  168. class Props(
  169. val todoListView: TodoListViewOld,
  170. val fxScheduler: Scheduler,
  171. val logger: Logger[Task]
  172. ) {
  173. def create = Task(new TodoListOps(this))
  174. }
  175. }
  176. object TodoListComponentOld {
  177. sealed trait Complete
  178. object Complete extends Complete
  179. sealed trait Command
  180. // sealed trait Tell extends Command
  181. // sealed abstract class Ask extends Command
  182. case class Add(todo: Todo) extends Command
  183. case class Find(id: Int, result: Deferred[Task, Option[Todo]]) extends Command
  184. case class Edit(id: Int, content: String) extends Command
  185. case class Delete(id: Int) extends Command
  186. // private case class FindInternal(id: Int, result: Deferred[Task, Todo])
  187. // extends Ask
  188. def reducer(
  189. state: Vector[Todo],
  190. action: TodoListComponentOld.Command
  191. ) =
  192. action match {
  193. case Add(todo) => state :+ todo
  194. // case Find(id, result) =>
  195. case Edit(id, content) => state
  196. case Delete(id) =>
  197. state.filterNot(_.id == id)
  198. case _ => state
  199. }
  200. val store =
  201. Store
  202. .createL[TodoListComponentOld.Command, Vector[Todo]](
  203. TodoListComponentOld.Delete(0),
  204. Vector.empty[Todo],
  205. (s: Vector[Todo], a: TodoListComponentOld.Command) =>
  206. reducer(s, a) -> Observable.empty
  207. )
  208. class Props(
  209. val todoListView: TodoListViewOld,
  210. val fxScheduler: Scheduler,
  211. val channel: ConcurrentChannel[
  212. Task,
  213. TodoListComponentOld.Complete,
  214. TodoListComponentOld.Command
  215. ],
  216. val logger: Logger[Task]
  217. ) {
  218. def create =
  219. for {
  220. todoListOps <-
  221. new TodoListOps.Props(todoListView, fxScheduler, logger).create
  222. consumer = channel.consume.use(ref => todoConsumer(ref, todoListOps))
  223. _ <- consumer.startAndForget
  224. } yield (new TodoListComponentOld(this))
  225. private def todoConsumer(
  226. consumer: ConsumerF[Task, Complete, Command],
  227. ops: TodoListOps
  228. ): Task[Unit] =
  229. consumer.pull
  230. .flatMap {
  231. case Left(complete) => logger.info("Received `Complete` event")
  232. case Right(command) =>
  233. logger.debug(s"Received command $command") >>
  234. (command match {
  235. // case t: Tell =>
  236. // t match {
  237. // case Add(todo) => ops.add(todo)
  238. // case _ => Task.unit
  239. // }
  240. case Add(todo) => ops.add(todo)
  241. // case Find(id) =>
  242. // for {
  243. // p <- Deferred[Task, Todo]
  244. // _ <- channel.push(FindInternal(id, p))
  245. // res <- p.get
  246. // } yield (res)
  247. case Find(id, result) =>
  248. for {
  249. mbTodo <- ops.find(id)
  250. } yield result.complete(mbTodo)
  251. // case _ => Task.unit
  252. case Delete(id) => ops.delete(id)
  253. case Edit(id, content) => ops.edit(id, content)
  254. })
  255. }
  256. .flatMap(_ => todoConsumer(consumer, ops))
  257. }
  258. }
  259. class TodoListComponentOld(props: TodoListComponentOld.Props) {
  260. import props._
  261. import TodoListComponentOld._
  262. def send(command: Command) = channel.push(command)
  263. def ask[T](
  264. commandBuilder: Deferred[Task, T] => Command
  265. )(implicit timeout: FiniteDuration) =
  266. for {
  267. p <- Deferred[Task, T]
  268. _ <- channel.push(commandBuilder(p))
  269. res <- p.get.timeout(timeout)
  270. } yield res
  271. def stop = channel.halt(Complete)
  272. // import scala.concurrent.duration._
  273. // val x = ask(FindInternal(0, _))(2.seconds)
  274. }
  275. // object TodoListComponent {
  276. // sealed trait Complete
  277. // object Complete extends Complete
  278. // sealed trait Command
  279. // class Props(
  280. // val todoListView: TodoListView,
  281. // val fxScheduler: Scheduler,
  282. // val channel: ConcurrentChannel[
  283. // Task,
  284. // TodoListComponent.Complete,
  285. // TodoListComponent.Command
  286. // ]
  287. // ) {
  288. // def create = Task(new TodoListComponent(this))
  289. // }
  290. // }
  291. // class TodoListComponent(props: TodoListComponent.Props) {
  292. // import props._
  293. // import TodoListComponent._
  294. // def init =
  295. // for {
  296. // todoListOps <- new TodoListOps.Props(todoListView, fxScheduler).create
  297. // consumer = channel.consume.use(ref => todoConsumer(ref, todoListOps))
  298. // _ <- consumer.startAndForget
  299. // } yield ()
  300. // def send(command: Command) = channel.push(command)
  301. // def todoConsumer(
  302. // consumer: ConsumerF[Task, Complete, Command],
  303. // ops: TodoListOps
  304. // ) =
  305. // consumer.pull.flatMap {
  306. // case Left(value) => Task.unit
  307. // case Right(value) => Task.unit
  308. // }
  309. // }
  310. // def askHandler(
  311. // channel: ConcurrentChannel[
  312. // Task,
  313. // TodoListComponent.Complete,
  314. // TodoListComponent.Command
  315. // ],
  316. // consumer: ConsumerF[Task, Complete, Command],
  317. // ops: TodoListOps
  318. // ) =
  319. // consumer.pull.flatMap {
  320. // case Left(complete) => Task.unit
  321. // case Right(command) =>
  322. // command match {
  323. // case a: Ask =>
  324. // a match {
  325. // case Find(id) =>
  326. // for {
  327. // p <- Deferred[Task, Todo]
  328. // _ <- channel.push(FindInternal(id, p))
  329. // res <- p.get
  330. // } yield (res)
  331. // case FindInternal(id, result) =>
  332. // for {
  333. // mb <- ops.find(id)
  334. // } yield result.complete(mb.get)
  335. // case _ => Task.unit
  336. // }
  337. // case _ => Task.unit
  338. // }
  339. // }