ui.cljs 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. (ns capacitor.components.ui
  2. (:require [capacitor.ionic :as ion]
  3. [cljs-bean.core :as bean]
  4. [frontend.handler.notification :as notification]
  5. [frontend.rum :as r]
  6. [frontend.state :as fstate]
  7. [medley.core :as medley]
  8. [react-transition-group :refer [CSSTransition TransitionGroup]]
  9. [rum.core :as rum]))
  10. (defonce transition-group (r/adapt-class TransitionGroup))
  11. (defonce css-transition (r/adapt-class CSSTransition))
  12. (rum/defc safe-page-container
  13. [content {:keys [header-content page-props content-props]}]
  14. (ion/page
  15. (merge {:class "app-safe-page"} page-props)
  16. (some-> header-content (ion/header))
  17. (ion/content
  18. (merge {:class "ion-padding"} content-props)
  19. content)))
  20. (rum/defc classic-app-container-wrap
  21. [content]
  22. [:main#app-container-wrapper.ls-fold-button-on-right
  23. [:div#app-container.pt-2
  24. [:div#main-container.flex.flex-1
  25. [:div#main-content-container.w-full.!px-0 content]]]])
  26. (rum/defc notification-clear-all
  27. []
  28. [:div.ui__notifications-content
  29. [:div.pointer-events-auto.notification-clear
  30. (ion/button
  31. {:on-click (fn []
  32. (notification/clear-all!))}
  33. "clear all")]])
  34. (rum/defc notification-content
  35. [state content status uid]
  36. (when (and content status)
  37. (let [svg
  38. (if (keyword? status)
  39. (case status
  40. :success
  41. (ion/tabler-icon "circle-check" {:class "text-green-600" :size "20"})
  42. :warning
  43. (ion/tabler-icon "alert-circle" {:class "text-yellow-600" :size "20"})
  44. :error
  45. (ion/tabler-icon "circle-x" {:class "text-red-600" :size "20"})
  46. (ion/tabler-icon "info-circle" {:class "text-indigo-600" :size "20"}))
  47. status)]
  48. [:div.ui__notifications-content
  49. {:style
  50. (when (or (= state "exiting")
  51. (= state "exited"))
  52. {:z-index -1})}
  53. [:div.max-w-sm.w-full.shadow-lg.rounded-lg.pointer-events-auto.notification-area
  54. {:class (case state
  55. "entering" "transition ease-out duration-300 transform opacity-0 translate-y-2 sm:translate-x-0"
  56. "entered" "transition ease-out duration-300 transform translate-y-0 opacity-100 sm:translate-x-0"
  57. "exiting" "transition ease-in duration-100 opacity-100"
  58. "exited" "transition ease-in duration-100 opacity-0")}
  59. [:div.rounded-lg.shadow-xs {:style {:max-height "calc(100vh - 200px)"
  60. :overflow-y "auto"
  61. :overflow-x "hidden"}}
  62. [:div.p-4
  63. [:div.flex.items-start
  64. [:div.flex-shrink-0.pt-2
  65. svg]
  66. [:div.ml-3.w-0.flex-1.pt-2
  67. [:div.text-sm.leading-5.font-medium.whitespace-pre-line {:style {:margin 0}}
  68. content]]
  69. [:div.flex-shrink-0.flex {:style {:margin-top -9
  70. :margin-right -18}}
  71. (ion/button
  72. {:fill "clear"
  73. :mode "ios"
  74. :shape "round"
  75. :on-click (fn []
  76. (notification/clear! uid))}
  77. [:span {:slot "icon-only"}
  78. (ion/tabler-icon "x")])]]]]]])))
  79. (rum/defc install-notifications < rum/reactive
  80. []
  81. (let [contents (fstate/sub :notification/contents)]
  82. (transition-group
  83. {:class-name "notifications ui__notifications"}
  84. (let [notifications
  85. (map (fn [el]
  86. (let [k (first el)
  87. v (second el)]
  88. (css-transition
  89. {:timeout 100
  90. :key (name k)}
  91. (fn [state]
  92. (notification-content state (:content v) (:status v) k)))))
  93. contents)
  94. clear-all (when (> (count contents) 3)
  95. (css-transition
  96. {:timeout 100
  97. :k "clear-all"}
  98. (fn [_state]
  99. (notification-clear-all))))
  100. items (if clear-all (cons clear-all notifications) notifications)]
  101. (doall items)))))
  102. (defonce *modals (atom []))
  103. (defonce ^:private *id (atom 0))
  104. (defonce ^:private gen-id #(reset! *id (inc @*id)))
  105. (rum/defc x-modal
  106. [{:keys [close! as-page? type on-action title buttons inputs modal-props]} content]
  107. (let [{:keys [class header]} modal-props]
  108. (case type
  109. :alert
  110. (ion/alert
  111. (merge modal-props
  112. {:is-open true
  113. :header (or title header)
  114. :message content
  115. :backdropDismiss false
  116. :onWillDismiss (fn [^js e]
  117. (when on-action
  118. (on-action (bean/->clj (.-detail e))))
  119. (close!))
  120. :buttons (bean/->js (or buttons (:buttons modal-props)))
  121. :inputs (bean/->js (or inputs (:inputs modal-props) []))}))
  122. :action-sheet
  123. (ion/action-sheet
  124. (merge modal-props
  125. {:is-open true
  126. :header (or content title header)
  127. :onWillDismiss (fn [^js e]
  128. (when on-action
  129. (on-action (bean/->clj (.-detail e))))
  130. (close!))
  131. :buttons (bean/->js (or buttons (:buttons modal-props)))}))
  132. ;; default
  133. (ion/modal
  134. (merge modal-props
  135. {:is-open true
  136. :onWillDismiss (fn [] (close!))
  137. :class (str class (when (not (true? as-page?)) " ion-datetime-button-overlay"))})
  138. (if (fn? content)
  139. (content) content)))))
  140. (defn get-modal
  141. ([] (some-> @*modals last))
  142. ([id]
  143. (when id
  144. (some->> (medley/indexed @*modals)
  145. (filter #(= id (:id (second %)))) (first)))))
  146. (defn- upsert-modal!
  147. [config]
  148. (when-let [id (:id config)]
  149. (if-let [[index config'] (get-modal id)]
  150. (swap! *modals assoc index (merge config' config))
  151. (swap! *modals conj config)) id))
  152. (defn- delete-modal!
  153. [id]
  154. (when-let [[index _] (get-modal id)]
  155. (swap! *modals #(->> % (medley/remove-nth index) (vec)))))
  156. (defn open-modal!
  157. [content & {:keys [id type] :as props}]
  158. (upsert-modal!
  159. (merge props
  160. {:id (or id (gen-id))
  161. :type (or type :default) ;; :alert :confirm :page
  162. :as-page? (= type :page)
  163. :content content})))
  164. (defn close-modal!
  165. ([] (some-> @*modals (last) :id (close-modal!)))
  166. ([id] (delete-modal! id)))
  167. (rum/defc install-modals []
  168. (let [_ (r/use-atom *modals)]
  169. [:<>
  170. (for [{:keys [id content] :as props} @*modals
  171. :let [close! #(close-modal! id)
  172. props' (assoc props :close! close!)]]
  173. (x-modal props'
  174. (if (fn? content) (content props') content)))]))