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.

291 lines
9.2 KiB

3 years ago
  1. package outwatchapp
  2. import outwatch._
  3. import outwatch.dsl._
  4. import cats.effect.ExitCode
  5. import monix.bio._
  6. import colibri.ext.monix._
  7. import monix.reactive.Observable
  8. import scala.concurrent.duration._
  9. import cats.implicits._
  10. import outwatch.router.AppRouter
  11. import outwatch.router.dsl._
  12. import sttp.client.impl.monix.FetchMonixBackend
  13. import outwatch.reactive.handlers.monix._
  14. import sttp.client._
  15. import monix.{eval => me}
  16. import nova.monadic_sfx.ui.components.todo.TodoListStore
  17. import outwatchapp.implicits._
  18. import monix.eval.Coeval
  19. import nova.monadic_sfx.ui.components.todo.Todo
  20. object OutwatchApp extends BIOApp {
  21. val counter2 = Observable.interval(1.second)
  22. val counter = div(
  23. "Hmm",
  24. button(
  25. onClick
  26. .useScan(0)(_ + 1)
  27. .handled(source => VDomModifier("Counter: ", source))
  28. )
  29. )
  30. implicit val backend = FetchMonixBackend()
  31. val handlerTask = Handler.createF[Task, String]("empty")
  32. def request(query: String) = basicRequest
  33. .get(
  34. uri"https://jsonplaceholder.typicode.com/todos/${query.toIntOption.getOrElse(0)}"
  35. )
  36. .send()
  37. .flatMap {
  38. _.body match {
  39. case Right(value) =>
  40. me.Task(println(value)) >>
  41. me.Task(value)
  42. case Left(error) =>
  43. me.Task(println(error)) >>
  44. me.Task(error)
  45. }
  46. }
  47. val component = Task.deferAction(implicit s =>
  48. for {
  49. handler <- handlerTask
  50. todoContent <- Handler.createF[Task, String]
  51. todoStore <- TodoListStore()
  52. res <- Task(
  53. div(
  54. // cls := "col-lg-8 bd-example",
  55. div(cls := "alert alert-danger", "Some Error Occured!"),
  56. div(
  57. form(
  58. h4(cls := "form-title", "User Finder"),
  59. div(
  60. cls := "form-group",
  61. label(
  62. color := "hsla(0,0%,100%,0.8)",
  63. forId := "httpInput",
  64. "Enter an id: "
  65. ),
  66. input(
  67. idAttr := "httpInput",
  68. typ := "text",
  69. cls := "form-control",
  70. placeholder := "0",
  71. onInput.value --> handler
  72. ),
  73. label(
  74. color := "hsla(0,0%,100%,0.8)",
  75. "Enter content for todo"
  76. ),
  77. small(cls := "form-text text-muted", "default is 0")
  78. ),
  79. div(
  80. cls := "form-group",
  81. input(
  82. cls := "form-control",
  83. onInput.value --> todoContent
  84. ),
  85. button(
  86. cls := "btn",
  87. cls := "btn-info form-control",
  88. "Add Todo",
  89. onClick.preventDefault(
  90. todoContent.map(TodoListStore.Add)
  91. ) --> todoStore.sink
  92. )
  93. )
  94. )
  95. ),
  96. div(
  97. p(
  98. cls := "profile-description",
  99. handler
  100. .doOnNext(str => me.Task(println(str)))
  101. .mapEval(request)
  102. .map(div(_)),
  103. div(
  104. "Todos: ",
  105. todoStore.doOnNextF { case (a, s) => Coeval(println(s)) }.map {
  106. case (a, s) => div(renderTodos(s.todos))
  107. }
  108. )
  109. )
  110. )
  111. )
  112. )
  113. } yield res
  114. )
  115. def renderTodos(todos: Seq[Todo]) = div(
  116. ul(
  117. todos.map(todo => li(div(s"id: ${todo.id} content: ${todo.content}")))
  118. )
  119. )
  120. sealed trait Page
  121. case object Home extends Page
  122. case object SomePage extends Page
  123. case class UserHome(id: Int) extends Page
  124. case object NotFound extends Page
  125. val router = AppRouter.create[Task, Page](NotFound) {
  126. case Root => Home
  127. case Root / "user" / IntVar(id) => UserHome(id)
  128. case Root / "some-page" => SomePage
  129. }
  130. val counterComponent =
  131. Task.deferAction(implicit s =>
  132. Task(div(p(cls := "profile-description", "count: ", counter2)))
  133. )
  134. def resolver: PartialFunction[Page, VDomModifier] = {
  135. case Home =>
  136. div(
  137. div(cls := "title", "Home"),
  138. div(
  139. cls := "card",
  140. div(
  141. cls := "card-body",
  142. counterComponent,
  143. p(
  144. cls := "profile-description",
  145. div(
  146. "hm",
  147. htmlTag("blockQuote")(
  148. cls := "blockquote",
  149. p(
  150. cls := "mb-0",
  151. "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer posuere erat a ante."
  152. ),
  153. footer(
  154. cls := "blockquote-footer",
  155. "Someone famous in ",
  156. cite(title := "Source Title", "Source Title")
  157. )
  158. )
  159. )
  160. )
  161. )
  162. )
  163. )
  164. case SomePage =>
  165. div(div(cls := "title", "SomePage"), component, div(cls := "slider"))
  166. case UserHome(id) => div(div(cls := "title", "UserHome"), s"User id: $id")
  167. case NotFound =>
  168. div(
  169. div(cls := "title", "NotFound"),
  170. p(cls := "profile-description", "notfound")
  171. )
  172. }
  173. def run(args: List[String]): UIO[ExitCode] = {
  174. // div(h1("Hello World"), counterComponent, Task(1))
  175. import org.scalajs.dom.document
  176. val el =
  177. document.createElement("div")
  178. el.setAttribute("id", "#app")
  179. document.body.appendChild(el)
  180. router.store
  181. .flatMap(implicit store =>
  182. OutWatch
  183. .renderInto(
  184. el,
  185. div(
  186. htmlTag("nav")(
  187. cls := "navbar navbar-expand-lg bg-primary ",
  188. attr("color-on-scroll") := "100",
  189. div(
  190. cls := "container",
  191. div(
  192. cls := "navbar-translate",
  193. a(
  194. cls := "navbar-brand",
  195. href := "https://demos.creative-tim.com/blk-design-system/index.html",
  196. rel := "tooltip",
  197. title := "",
  198. attr("data-placement") := "bottom",
  199. target := "_blank",
  200. div("OutwatchApp")
  201. ),
  202. button(
  203. cls := "navbar-toggler navbar-toggler toggled collapsed",
  204. attr("data-toggle") := "collapse",
  205. attr("data-target") := "#navigation",
  206. attr("aria-controls") := "navigation-index",
  207. attr("aria-expanded") := "false",
  208. attr("aria-label") := "Toggle navigation",
  209. div(cls := "navbar-toggler-bar bar1"),
  210. div(cls := "navbar-toggler-bar bar2"),
  211. div(cls := "navbar-toggler-bar bar3")
  212. )
  213. ),
  214. div(
  215. cls := "navbar-collapse justify-content-end collapse",
  216. idAttr := "navigation",
  217. div(
  218. cls := "navbar-collapse-header",
  219. div(
  220. cls := "row",
  221. div(
  222. cls := "col-6 collapse-brand",
  223. a("BLK•")
  224. ),
  225. div(
  226. cls := "col-6 collapse-close text-right",
  227. button(
  228. `type` := "button",
  229. cls := "navbar-toggler collapsed",
  230. attr("data-toggle") := "collapse",
  231. attr("data-target") := "#navigation",
  232. attr("aria-controls") := "navigation-index",
  233. attr("aria-expanded") := "false",
  234. attr("aria-label") := "Toggle navigation",
  235. i(cls := "tim-icons icon-simple-remove")
  236. )
  237. )
  238. )
  239. ),
  240. ul(
  241. cls := "navbar-nav",
  242. li(
  243. cls := "nav-item active",
  244. router.link("/")(
  245. cls := "nav-link",
  246. "Home",
  247. div(cls := "sr-only", "(current)")
  248. )
  249. ),
  250. li(
  251. cls := "nav-item",
  252. router
  253. .link("/some-page")(cls := "nav-link", "SomePage")
  254. ),
  255. li(
  256. cls := "nav-item",
  257. router.link("/user/1")(cls := "nav-link", "User Home")
  258. ),
  259. li(
  260. cls := "nav-item",
  261. router.link("/todomvc")(cls := "nav-link", "TodoMvc")
  262. )
  263. )
  264. )
  265. )
  266. ),
  267. div(
  268. cls := "container",
  269. router.render(resolver),
  270. router.watch()
  271. )
  272. )
  273. )
  274. )
  275. .onErrorHandle(ex => UIO(ex.printStackTrace()))
  276. .as(ExitCode.Success)
  277. }
  278. }