header.cljs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. (ns frontend.components.header
  2. (:require ["path" :as path]
  3. [cljs-bean.core :as bean]
  4. [frontend.components.export :as export]
  5. [frontend.components.page-menu :as page-menu]
  6. [frontend.components.plugins :as plugins]
  7. [frontend.components.right-sidebar :as sidebar]
  8. [frontend.components.svg :as svg]
  9. [frontend.config :as config]
  10. [frontend.context.i18n :refer [t]]
  11. [frontend.fs.sync :as fs-sync]
  12. [frontend.handler :as handler]
  13. [frontend.handler.file-sync :as file-sync-handler]
  14. [frontend.handler.plugin :as plugin-handler]
  15. [frontend.handler.route :as route-handler]
  16. [frontend.handler.user :as user-handler]
  17. [frontend.handler.web.nfs :as nfs]
  18. [frontend.mobile.util :as mobile-util]
  19. [frontend.state :as state]
  20. [frontend.ui :as ui]
  21. [frontend.util :as util]
  22. [reitit.frontend.easy :as rfe]
  23. [rum.core :as rum]
  24. [cljs.core.async :as a]))
  25. (rum/defc home-button []
  26. (ui/with-shortcut :go/home "left"
  27. [:button.button.icon.inline
  28. {:on-click #(do
  29. (when (mobile-util/native-iphone?)
  30. (state/set-left-sidebar-open! false))
  31. (route-handler/redirect-to-home!))}
  32. (ui/icon "home" {:style {:fontSize ui/icon-size}})]))
  33. (rum/defc login < rum/reactive
  34. []
  35. (let [_ (state/sub :auth/id-token)]
  36. (when-not config/publishing?
  37. (if (user-handler/logged-in?)
  38. (ui/dropdown-with-links
  39. (fn [{:keys [toggle-fn]}]
  40. [:button.button
  41. {:on-click toggle-fn}
  42. [:span.text-sm.font-medium (user-handler/email)]])
  43. [{:title (t :logout)
  44. :options {:on-click user-handler/logout}}]
  45. {})
  46. [:button.button.text-sm.font-medium.block {:on-click #(js/window.open config/LOGIN-URL)}
  47. [:span (t :login)]]))))
  48. (rum/defcs file-sync-remote-graphs <
  49. (rum/local nil ::remote-graphs)
  50. [state]
  51. (let [*remote-graphs (::remote-graphs state)
  52. refresh-list-fn #(a/go (reset! *remote-graphs (a/<! (file-sync-handler/list-graphs))))]
  53. (when (nil? @*remote-graphs)
  54. (refresh-list-fn))
  55. [:div
  56. [:div.flex
  57. [:h1.title "Remote Graphs"]
  58. [:div
  59. {:on-click refresh-list-fn}
  60. svg/refresh]]
  61. [:p.text-sm "click to delete the selected graph"]
  62. [:ul
  63. (for [graph @*remote-graphs]
  64. [:li.mb-4
  65. [:a.font-medium
  66. {:on-click #(do (println "delete graph" (:GraphName graph) (:GraphUUID graph))
  67. (file-sync-handler/delete-graph (:GraphUUID graph)))}
  68. (:GraphName graph)]])]]))
  69. (rum/defcs file-sync <
  70. rum/reactive
  71. (rum/local nil ::existed-graphs)
  72. [state]
  73. (let [_ (state/sub :auth/id-token)
  74. sync-state (state/sub :file-sync/sync-state)
  75. not-syncing? (or (nil? sync-state) (fs-sync/sync-state--stopped? sync-state))
  76. *existed-graphs (::existed-graphs state)
  77. _ (rum/react file-sync-handler/refresh-file-sync-component)
  78. graph-txid-exists? (file-sync-handler/graph-txid-exists?)
  79. uploading-files (:current-local->remote-files sync-state)
  80. downloading-files (:current-remote->local-files sync-state)]
  81. (when-not config/publishing?
  82. (when (user-handler/logged-in?)
  83. (when-not (file-sync-handler/graph-txid-exists?)
  84. (a/go (reset! *existed-graphs (a/<! (file-sync-handler/list-graphs)))))
  85. (ui/dropdown-with-links
  86. (fn [{:keys [toggle-fn]}]
  87. (if not-syncing?
  88. [:button.button.icon.inline
  89. {:on-click toggle-fn}
  90. (ui/icon "cloud-off" {:style {:fontSize ui/icon-size}})]
  91. [:button.button.icon.inline
  92. {:on-click toggle-fn}
  93. (ui/icon "cloud" {:style {:fontSize ui/icon-size}})]))
  94. (cond-> []
  95. (not graph-txid-exists?)
  96. (concat (->> @*existed-graphs
  97. (filterv #(and (:GraphName %) (:GraphUUID %)))
  98. (mapv (fn [graph]
  99. {:title (:GraphName graph)
  100. :options {:on-click #(file-sync-handler/switch-graph (:GraphUUID graph))}})))
  101. [{:hr true}
  102. {:title "create graph"
  103. :options {:on-click #(file-sync-handler/create-graph (path/basename (state/get-current-repo)))}}])
  104. graph-txid-exists?
  105. (concat
  106. [{:title "toggle file sync"
  107. :options {:on-click #(if not-syncing? (fs-sync/sync-start) (fs-sync/sync-stop))}}
  108. {:title "remote graph list"
  109. :options {:on-click #(state/set-sub-modal! file-sync-remote-graphs)}}]
  110. [{:hr true}]
  111. (map (fn [f] {:title f
  112. :icon (ui/icon "arrow-narrow-up")}) uploading-files)
  113. (map (fn [f] {:title f
  114. :icon (ui/icon "arrow-narrow-down")}) downloading-files)
  115. (when sync-state
  116. (map-indexed (fn [i f] (:time f)
  117. {:title [:div {:key i} [:div (:path f)] [:div.opacity-50 (util/time-ago (:time f))]]})
  118. (take 10 (:history sync-state))))))
  119. (cond-> {}
  120. (not graph-txid-exists?) (assoc :links-header [:div.font-medium.text-sm.opacity-60.px-4.pt-2
  121. "Switch to:"])))))))
  122. (rum/defc left-menu-button < rum/reactive
  123. [{:keys [on-click]}]
  124. (ui/with-shortcut :ui/toggle-left-sidebar "bottom"
  125. [:button.#left-menu.cp__header-left-menu.button.icon
  126. {:on-click on-click}
  127. (ui/icon "menu-2" {:style {:fontSize ui/icon-size}})]))
  128. (rum/defc dropdown-menu < rum/reactive
  129. [{:keys [current-repo t]}]
  130. (let [page-menu (page-menu/page-menu nil)
  131. page-menu-and-hr (when (seq page-menu)
  132. (concat page-menu [{:hr true}]))]
  133. (ui/dropdown-with-links
  134. (fn [{:keys [toggle-fn]}]
  135. [:button.button.icon
  136. {:on-click toggle-fn}
  137. (ui/icon "dots" {:style {:fontSize ui/icon-size}})])
  138. (->>
  139. [(when (state/enable-editing?)
  140. {:title (t :settings)
  141. :options {:on-click state/open-settings!}
  142. :icon (ui/icon "settings")})
  143. (when plugin-handler/lsp-enabled?
  144. {:title (t :plugins)
  145. :options {:on-click #(plugin-handler/goto-plugins-dashboard!)}
  146. :icon (ui/icon "apps")})
  147. (when plugin-handler/lsp-enabled?
  148. {:title (t :themes)
  149. :options {:on-click #(plugins/open-select-theme!)}
  150. :icon (ui/icon "palette")})
  151. (when current-repo
  152. {:title (t :export-graph)
  153. :options {:on-click #(state/set-modal! export/export)}
  154. :icon (ui/icon "database-export")})
  155. (when (and current-repo (state/enable-editing?))
  156. {:title (t :import)
  157. :options {:href (rfe/href :import)}
  158. :icon (ui/icon "file-upload")})
  159. {:title [:div.flex-row.flex.justify-between.items-center
  160. [:span (t :join-community)]]
  161. :options {:href "https://discuss.logseq.com"
  162. :title (t :discourse-title)
  163. :target "_blank"}
  164. :icon (ui/icon "brand-discord")}] ;; TODO we need a discourse brand
  165. (concat page-menu-and-hr)
  166. (remove nil?))
  167. {})))
  168. (rum/defc back-and-forward
  169. []
  170. [:div.flex.flex-row
  171. (ui/with-shortcut :go/backward "bottom"
  172. [:button.it.navigation.nav-left.button.icon
  173. {:title "Go back" :on-click #(js/window.history.back)}
  174. (ui/icon "arrow-left" {:style {:fontSize ui/icon-size}})])
  175. (ui/with-shortcut :go/forward "bottom"
  176. [:button.it.navigation.nav-right.button.icon
  177. {:title "Go forward" :on-click #(js/window.history.forward)}
  178. (ui/icon "arrow-right" {:style {:fontSize ui/icon-size}})])])
  179. (rum/defc updater-tips-new-version
  180. [t]
  181. (let [[downloaded, set-downloaded] (rum/use-state nil)
  182. _ (rum/use-effect!
  183. (fn []
  184. (when-let [channel (and (util/electron?) "auto-updater-downloaded")]
  185. (let [callback (fn [_ args]
  186. (js/console.debug "[new-version downloaded] args:" args)
  187. (let [args (bean/->clj args)]
  188. (set-downloaded args)
  189. (state/set-state! :electron/auto-updater-downloaded args))
  190. nil)]
  191. (js/apis.addListener channel callback)
  192. #(js/apis.removeListener channel callback))))
  193. [])]
  194. (when downloaded
  195. [:div.cp__header-tips
  196. [:p (t :updater/new-version-install)
  197. [:a.restart.ml-2
  198. {:on-click #(handler/quit-and-install-new-version!)}
  199. (svg/reload 16) [:strong (t :updater/quit-and-install)]]]])))
  200. (rum/defc ^:large-vars/cleanup-todo header < rum/reactive
  201. [{:keys [open-fn current-repo default-home new-block-mode]}]
  202. (let [repos (->> (state/sub [:me :repos])
  203. (remove #(= (:url %) config/local-repo)))
  204. electron-mac? (and util/mac? (util/electron?))
  205. show-open-folder? (and (nfs/supported?)
  206. (or (empty? repos)
  207. (nil? (state/sub :git/current-repo)))
  208. (not (mobile-util/native-platform?))
  209. (not config/publishing?))
  210. left-menu (left-menu-button {:on-click (fn []
  211. (open-fn)
  212. (state/set-left-sidebar-open!
  213. (not (:ui/left-sidebar-open? @state/state))))})
  214. custom-home-page? (and (state/custom-home-page?)
  215. (= (state/sub-default-home-page) (state/get-current-page)))]
  216. [:div.cp__header#head
  217. {:class (util/classnames [{:electron-mac electron-mac?
  218. :native-ios (mobile-util/native-ios?)
  219. :native-android (mobile-util/native-android?)}])
  220. :on-double-click (fn [^js e]
  221. (when-let [target (.-target e)]
  222. (when (and (util/electron?)
  223. (.. target -classList (contains "cp__header")))
  224. (js/window.apis.toggleMaxOrMinActiveWindow))))
  225. :style {:fontSize 50}}
  226. [:div.l.flex
  227. (when-not (mobile-util/native-platform?)
  228. [left-menu
  229. (when current-repo ;; this is for the Search button
  230. (ui/with-shortcut :go/search "right"
  231. [:button.button.icon#search-button
  232. {:on-click #(do (when (or (mobile-util/native-android?)
  233. (mobile-util/native-iphone?))
  234. (state/set-left-sidebar-open! false))
  235. (state/pub-event! [:go/search]))}
  236. (ui/icon "search" {:style {:fontSize ui/icon-size}})]))])
  237. (when (mobile-util/native-platform?)
  238. (if (or (state/home?) custom-home-page?)
  239. left-menu
  240. (ui/with-shortcut :go/backward "bottom"
  241. [:button.it.navigation.nav-left.button.icon
  242. {:title "Go back" :on-click #(js/window.history.back)}
  243. (ui/icon "chevron-left" {:style {:fontSize 25}})])))]
  244. [:div.r.flex
  245. (when-not file-sync-handler/hiding-login&file-sync
  246. (file-sync))
  247. (when-not file-sync-handler/hiding-login&file-sync
  248. (login))
  249. (when plugin-handler/lsp-enabled?
  250. (plugins/hook-ui-items :toolbar))
  251. (when (and (not= (state/get-current-route) :home)
  252. (not custom-home-page?))
  253. (home-button))
  254. (when (util/electron?)
  255. (back-and-forward))
  256. (when-not (mobile-util/native-platform?)
  257. (new-block-mode))
  258. (when show-open-folder?
  259. [:a.text-sm.font-medium.button.icon.add-graph-btn.flex.items-center
  260. {:on-click #(route-handler/redirect! {:to :repo-add})}
  261. (ui/icon "folder-plus")
  262. (when-not config/mobile?
  263. [:span.ml-1 {:style {:margin-top (if electron-mac? 0 2)}}
  264. (t :on-boarding/add-graph)])])
  265. (when config/publishing?
  266. [:button.text-sm.font-medium.button {:href (rfe/href :graph)}
  267. (t :graph)])
  268. (dropdown-menu {:t t
  269. :current-repo current-repo
  270. :default-home default-home})
  271. (when (not (state/sub :ui/sidebar-open?))
  272. (sidebar/toggle))
  273. (updater-tips-new-version t)]]))