header.cljs 13 KB

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