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.

375 lines
11 KiB

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