repo.cljs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. (ns frontend.components.repo
  2. (:require [clojure.string :as string]
  3. [frontend.components.widgets :as widgets]
  4. [frontend.config :as config]
  5. [frontend.context.i18n :refer [t]]
  6. [frontend.db :as db]
  7. [frontend.handler.repo :as repo-handler]
  8. [frontend.handler.web.nfs :as nfs-handler]
  9. [frontend.state :as state]
  10. [frontend.ui :as ui]
  11. [frontend.util :as util]
  12. [rum.core :as rum]
  13. [frontend.mobile.util :as mobile-util]
  14. [frontend.util.text :as text-util]
  15. [promesa.core :as p]
  16. [electron.ipc :as ipc]
  17. [goog.object :as gobj]
  18. [cljs.core.async :as async :refer [go <!]]
  19. [frontend.handler.file-sync :as file-sync]
  20. [reitit.frontend.easy :as rfe]))
  21. (rum/defc add-repo
  22. [args]
  23. (if-let [graph-types (get-in args [:query-params :graph-types])]
  24. (let [graph-types-s (->> (string/split graph-types #",")
  25. (mapv keyword))]
  26. (when (seq graph-types-s)
  27. (widgets/add-graph :graph-types graph-types-s)))
  28. (widgets/add-graph)))
  29. (rum/defc normalized-graph-label
  30. [{:keys [url remote? GraphName GraphUUID] :as graph} on-click]
  31. (when graph
  32. (let [local? (config/local-db? url)]
  33. [:span.flex.items-center
  34. (if local?
  35. (let [local-dir (config/get-local-dir url)
  36. graph-name (text-util/get-graph-name-from-path local-dir)]
  37. [:a.flex.items-center {:title local-dir
  38. :on-click #(on-click graph)}
  39. [:span graph-name (and GraphName [:strong.px-1 "(" GraphName ")"])]
  40. (when remote? [:strong.pr-1.flex.items-center (ui/icon "cloud")])])
  41. [:a.flex.items-center {:title GraphUUID
  42. :on-click #(on-click graph)}
  43. (db/get-repo-path (or url GraphName))
  44. (when remote? [:strong.pl-1.flex.items-center (ui/icon "cloud")])])])))
  45. (rum/defc repos-inner
  46. [repos]
  47. (for [{:keys [url remote? GraphUUID GraphName] :as repo} repos
  48. :let [only-cloud? (and remote? (nil? url))]]
  49. [:div.flex.justify-between.mb-4.items-center {:key (or url GraphUUID)}
  50. (normalized-graph-label repo #(if only-cloud?
  51. (state/pub-event! [:graph/pull-down-remote-graph repo])
  52. (state/pub-event! [:graph/switch url])))
  53. [:div.controls
  54. [:div.flex.flex-row.items-center
  55. (ui/tippy {:html [:div.text-sm.max-w-xs
  56. (if only-cloud?
  57. "Deletes this remote graph. Note this can't be recovered."
  58. "Removes Logseq's access to the local file path of your graph. It won't remove your local files.")]
  59. :class "tippy-hover"
  60. :interactive true}
  61. [:a.text-gray-400.ml-4.font-medium.text-sm.whitespace-nowrap
  62. {:on-click (fn []
  63. (if only-cloud?
  64. (let [confirm-fn
  65. (fn []
  66. (ui/make-confirm-modal
  67. {:title [:div
  68. {:style {:max-width 700}}
  69. (str "Are you sure to permanently delete the graph \"" GraphName "\" from our server?")]
  70. :sub-title [:div.small.mt-1
  71. "Notice that we can't recover this graph after being deleted. Make sure you have backups before deleting it."]
  72. :on-confirm (fn [_ {:keys [close-fn]}]
  73. (close-fn)
  74. (state/set-state! [:file-sync/remote-graphs :loading] true)
  75. (go (<! (file-sync/<delete-graph GraphUUID))
  76. (state/delete-repo! repo)
  77. (state/delete-remote-graph! repo)
  78. (state/set-state! [:file-sync/remote-graphs :loading] false)))}))]
  79. (state/set-modal! (confirm-fn)))
  80. (let [current-repo (state/get-current-repo)]
  81. (repo-handler/remove-repo! repo)
  82. (state/pub-event! [:graph/unlinked repo current-repo]))))}
  83. (if only-cloud? "Remove" "Unlink")])]]]))
  84. (rum/defc repos < rum/reactive
  85. []
  86. (let [login? (boolean (state/sub :auth/id-token))
  87. repos (state/sub [:me :repos])
  88. repos (util/distinct-by :url repos)
  89. remotes (state/sub [:file-sync/remote-graphs :graphs])
  90. remotes-loading? (state/sub [:file-sync/remote-graphs :loading])
  91. repos (if (and login? (seq remotes))
  92. (repo-handler/combine-local-&-remote-graphs repos remotes) repos)
  93. repos (remove #(= (:url %) config/local-repo) repos)
  94. {remote-graphs true local-graphs false} (group-by (comp boolean :remote?) repos)]
  95. (if (seq repos)
  96. [:div#graphs
  97. [:h1.title (t :graph/all-graphs)]
  98. [:div.pl-1.content.mt-3
  99. [:div
  100. [:h2.text-lg.font-medium.my-4 (str (t :graph/local-graphs) ":")]
  101. (when (seq local-graphs)
  102. (repos-inner local-graphs))
  103. [:div.flex.flex-row.my-4
  104. (when (or (nfs-handler/supported?)
  105. (mobile-util/native-platform?))
  106. [:div.mr-8
  107. (ui/button
  108. (t :open-a-directory)
  109. :on-click #(state/pub-event! [:graph/setup-a-repo]))])]]
  110. (when (file-sync/enable-sync?)
  111. [:div
  112. [:hr]
  113. [:div.flex.align-items.justify-between
  114. [:h2.text-lg.font-medium.my-4 (str (t :graph/remote-graphs) ":")]
  115. [:div
  116. (ui/button
  117. [:span.flex.items-center "Refresh"
  118. (when remotes-loading? [:small.pl-2 (ui/loading nil)])]
  119. :background "gray"
  120. :disabled remotes-loading?
  121. :on-click #(file-sync/load-session-graphs))]]
  122. (repos-inner remote-graphs)])]]
  123. (widgets/add-graph))))
  124. (defn- check-multiple-windows?
  125. [state]
  126. (when (util/electron?)
  127. (p/let [multiple-windows? (ipc/ipc "graphHasMultipleWindows" (state/get-current-repo))]
  128. (reset! (::electron-multiple-windows? state) multiple-windows?))))
  129. (defn- repos-dropdown-links [repos current-repo *multiple-windows?]
  130. (let [switch-repos (if-not (nil? current-repo)
  131. (remove (fn [repo] (= current-repo (:url repo))) repos) repos) ; exclude current repo
  132. repo-links (mapv
  133. (fn [{:keys [url remote? GraphName GraphUUID] :as graph}]
  134. (let [local? (config/local-db? url)
  135. repo-path (if local? (db/get-repo-name url) GraphName )
  136. short-repo-name (if local? (text-util/get-graph-name-from-path repo-path) GraphName)]
  137. (when short-repo-name
  138. {:title [:span.flex.items-center.whitespace-nowrap short-repo-name
  139. (when remote? [:span.pl-1.flex.items-center
  140. {:title (str "<" GraphName "> #" GraphUUID)}
  141. (ui/icon "cloud" {:size 18})])]
  142. :hover-detail repo-path ;; show full path on hover
  143. :options {:on-click (fn [e]
  144. (if (gobj/get e "shiftKey")
  145. (state/pub-event! [:graph/open-new-window url])
  146. (if-not local?
  147. (state/pub-event! [:graph/pull-down-remote-graph graph])
  148. (state/pub-event! [:graph/switch url]))))}})))
  149. switch-repos)
  150. refresh-link (let [nfs-repo? (config/local-db? current-repo)]
  151. (when (and nfs-repo?
  152. (not= current-repo config/local-repo)
  153. (or (nfs-handler/supported?)
  154. (mobile-util/native-platform?)))
  155. {:title (t :sync-from-local-files)
  156. :hover-detail (t :sync-from-local-files-detail)
  157. :options {:on-click #(state/pub-event! [:graph/ask-for-re-fresh])}}))
  158. reindex-link {:title (t :re-index)
  159. :hover-detail (t :re-index-detail)
  160. :options (cond->
  161. {:on-click
  162. (fn []
  163. (state/pub-event! [:graph/ask-for-re-index *multiple-windows? nil]))})}
  164. new-window-link (when (and (util/electron?)
  165. ;; New Window button in menu bar of macOS is available.
  166. (not util/mac?))
  167. {:title (t :open-new-window)
  168. :options {:on-click #(state/pub-event! [:graph/open-new-window nil])}})]
  169. (->>
  170. (concat repo-links
  171. [(when (seq repo-links) {:hr true})
  172. {:title (t :new-graph) :options {:on-click #(state/pub-event! [:graph/setup-a-repo])}}
  173. {:title (t :all-graphs) :options {:href (rfe/href :repos)}}
  174. refresh-link
  175. reindex-link
  176. new-window-link])
  177. (remove nil?))))
  178. (rum/defcs repos-dropdown < rum/reactive
  179. (rum/local false ::electron-multiple-windows?)
  180. [state]
  181. (let [multiple-windows? (::electron-multiple-windows? state)
  182. current-repo (state/sub :git/current-repo)
  183. login? (boolean (state/sub :auth/id-token))
  184. remotes-loading? (state/sub [:file-sync/remote-graphs :loading])]
  185. (when (or login? current-repo)
  186. (let [repos (state/sub [:me :repos])
  187. remotes (state/sub [:file-sync/remote-graphs :graphs])
  188. repos (if (and (seq remotes) login?)
  189. (repo-handler/combine-local-&-remote-graphs repos remotes) repos)
  190. links (repos-dropdown-links repos current-repo multiple-windows?)
  191. render-content (fn [{:keys [toggle-fn]}]
  192. (let [valid-remotes-but-locals? (and (seq repos) (not (some :url repos)))
  193. remote? (when-not valid-remotes-but-locals?
  194. (:remote? (first (filter #(= current-repo (:url %)) repos))))
  195. repo-path (if-not valid-remotes-but-locals?
  196. (db/get-repo-name current-repo) "")
  197. short-repo-name (if-not valid-remotes-but-locals?
  198. (db/get-short-repo-name repo-path) "Select a Graph")]
  199. [:a.item.group.flex.items-center.p-2.text-sm.font-medium.rounded-md
  200. {:on-click (fn []
  201. (check-multiple-windows? state)
  202. (toggle-fn))
  203. :title repo-path} ;; show full path on hover
  204. [:span.flex.relative
  205. {:style {:top 1}}
  206. (ui/icon "database" {:size 16 :id "database-icon"})]
  207. [:div.graphs
  208. [:span#repo-switch.block.pr-2.whitespace-nowrap
  209. [:span [:span#repo-name.font-medium
  210. (if (= config/local-repo short-repo-name) "Demo" short-repo-name)
  211. (when remote? [:span.pl-1 (ui/icon "cloud")])]]
  212. [:span.dropdown-caret.ml-2 {:style {:border-top-color "#6b7280"}}]]]]))
  213. links-header (cond->
  214. {:z-index 1000
  215. :modal-class (util/hiccup->class
  216. "origin-top-right.absolute.left-0.mt-2.rounded-md.shadow-lg")}
  217. (> (count repos) 1) ; show switch to if there are multiple repos
  218. (assoc :links-header [:div.font-medium.text-sm.opacity-70.px-4.pt-2.pb-1.flex.flex-row.justify-between.items-center
  219. [:div "Switch to:"]
  220. (when (file-sync/enable-sync?)
  221. (if remotes-loading?
  222. (ui/loading "")
  223. [:a.flex {:title "Refresh remote graphs"
  224. :on-click file-sync/load-session-graphs}
  225. (ui/icon "refresh")]))]))]
  226. (when (seq repos)
  227. (ui/dropdown-with-links render-content links links-header))))))