repo.cljs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  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.page :as page-handler]
  8. [frontend.handler.repo :as repo-handler]
  9. [frontend.handler.web.nfs :as nfs-handler]
  10. [frontend.modules.shortcut.core :as shortcut]
  11. [frontend.state :as state]
  12. [frontend.ui :as ui]
  13. [frontend.util :as util]
  14. [reitit.frontend.easy :as rfe]
  15. [rum.core :as rum]
  16. [frontend.mobile.util :as mobile-util]
  17. [frontend.util.text :as text-util]
  18. [promesa.core :as p]
  19. [electron.ipc :as ipc]
  20. [goog.object :as gobj]
  21. [frontend.components.encryption :as encryption]
  22. [frontend.encrypt :as e]
  23. [cljs.core.async :as async :refer [go <!]]
  24. [frontend.handler.file-sync :as file-sync]))
  25. (rum/defc add-repo
  26. [args]
  27. (if-let [graph-types (get-in args [:query-params :graph-types])]
  28. (let [graph-types-s (->> (string/split graph-types #",")
  29. (mapv keyword))]
  30. (when (seq graph-types-s)
  31. (widgets/add-graph :graph-types graph-types-s)))
  32. (widgets/add-graph)))
  33. (rum/defc normalized-graph-label
  34. [{:keys [url remote? GraphName GraphUUID] :as graph} on-click]
  35. (when graph
  36. (let [local? (config/local-db? url)]
  37. [:span.flex.items-center
  38. (if local?
  39. (let [local-dir (config/get-local-dir url)
  40. graph-name (text-util/get-graph-name-from-path local-dir)]
  41. [:a {:title local-dir
  42. :on-click #(on-click graph)}
  43. [:span graph-name (and GraphName [:strong.px-1 "(" GraphName ")"])]
  44. (when remote? [:strong.pr-1 (ui/icon "cloud")])])
  45. [:a {:title GraphUUID
  46. :on-click #(on-click graph)}
  47. (db/get-repo-path (or url GraphName))
  48. (when remote? [:strong.pl-1 (ui/icon "cloud")])])])))
  49. (rum/defc repos < rum/reactive
  50. []
  51. (let [login? (boolean (state/sub :auth/id-token))
  52. repos (state/sub [:me :repos])
  53. repos (util/distinct-by :url repos)
  54. remotes (state/sub [:file-sync/remote-graphs :graphs])
  55. repos (if (and login? (seq remotes))
  56. (repo-handler/combine-local-&-remote-graphs repos remotes) repos)
  57. repos (remove #(= (:url %) config/local-repo) repos)]
  58. (if (seq repos)
  59. [:div#graphs
  60. [:h1.title "All Graphs"]
  61. [:p.ml-2.opacity-70
  62. "A \"graph\" in Logseq means a local directory."]
  63. [:div.pl-1.content.mt-3
  64. [:div.flex.flex-row.my-4
  65. (when (or (nfs-handler/supported?)
  66. (mobile-util/native-platform?))
  67. [:div.mr-8
  68. (ui/button
  69. (t :open-a-directory)
  70. :on-click #(page-handler/ls-dir-files! shortcut/refresh!))])]
  71. (for [{:keys [url remote? GraphUUID GraphName] :as repo} repos
  72. :let [only-cloud? (and remote? (nil? url))]]
  73. [:div.flex.justify-between.mb-4 {:key (or url GraphUUID)}
  74. (normalized-graph-label repo #(if only-cloud?
  75. (state/pub-event! [:graph/pick-dest-to-sync repo])
  76. (state/pub-event! [:graph/switch url])))
  77. [:div.controls
  78. (when (or (e/encrypted-db? url) (and url remote?))
  79. [:a.control {:title "Show encryption information about this graph"
  80. :on-click (fn []
  81. (if remote?
  82. (state/pub-event! [:modal/remote-encryption-input-pw-dialog url repo])
  83. (state/set-modal! (encryption/encryption-dialog url))))}
  84. "🔐"])
  85. [:a.text-gray-400.ml-4.font-medium.text-sm
  86. {:title (if only-cloud?
  87. "Warning: It can't be recovered!"
  88. "No worries, unlink this graph will clear its cache only, it does not remove your files on the disk.")
  89. :on-click (fn []
  90. (if only-cloud?
  91. (when (js/confirm (str "Are you sure remove remote this graph (" GraphName ")!"))
  92. (go (<! (file-sync/delete-graph GraphUUID))
  93. (file-sync/load-session-graphs)))
  94. (do
  95. (repo-handler/remove-repo! repo)
  96. (file-sync/load-session-graphs))))}
  97. (if only-cloud?
  98. [:span.text-red-600 "Remove"]
  99. "Unlink")]]])]]
  100. (widgets/add-graph))))
  101. (defn refresh-cb []
  102. (page-handler/create-today-journal!)
  103. (shortcut/refresh!))
  104. (defn- check-multiple-windows?
  105. [state]
  106. (when (util/electron?)
  107. (p/let [multiple-windows? (ipc/ipc "graphHasMultipleWindows" (state/get-current-repo))]
  108. (reset! (::electron-multiple-windows? state) multiple-windows?))))
  109. (defn- repos-dropdown-links [repos current-repo *multiple-windows?]
  110. (let [switch-repos (if-not (nil? current-repo)
  111. (remove (fn [repo] (= current-repo (:url repo))) repos) repos) ; exclude current repo
  112. repo-links (mapv
  113. (fn [{:keys [url remote? GraphName GraphUUID] :as graph}]
  114. (let [local? (config/local-db? url)
  115. repo-path (if local? (db/get-repo-name url) GraphName )
  116. short-repo-name (if local? (text-util/get-graph-name-from-path repo-path) GraphName)]
  117. {:title [:span.flex.items-center short-repo-name
  118. (when remote? [:span.pl-1
  119. {:title (str "<" GraphName "> #" GraphUUID)}
  120. (ui/icon "cloud")])]
  121. :hover-detail repo-path ;; show full path on hover
  122. :options {:class "ml-1"
  123. :on-click (fn [e]
  124. (if (gobj/get e "shiftKey")
  125. (state/pub-event! [:graph/open-new-window url])
  126. (if-not local?
  127. (state/pub-event! [:graph/pick-dest-to-sync graph])
  128. (state/pub-event! [:graph/switch url]))))}}))
  129. switch-repos)
  130. refresh-link (let [nfs-repo? (config/local-db? current-repo)]
  131. (when (and nfs-repo?
  132. (not= current-repo config/local-repo)
  133. (or (nfs-handler/supported?)
  134. (mobile-util/native-platform?)))
  135. {:title (t :sync-from-local-files)
  136. :hover-detail (t :sync-from-local-files-detail)
  137. :options {:on-click
  138. (fn []
  139. (state/pub-event!
  140. [:modal/show
  141. [:div {:style {:max-width 700}}
  142. [:p (t :sync-from-local-changes-detected)]
  143. (ui/button
  144. (t :yes)
  145. :autoFocus "on"
  146. :large? true
  147. :on-click (fn []
  148. (state/close-modal!)
  149. (nfs-handler/refresh! (state/get-current-repo) refresh-cb)))]]))}}))
  150. reindex-link {:title (t :re-index)
  151. :hover-detail (t :re-index-detail)
  152. :options (cond->
  153. {:on-click
  154. (fn []
  155. (state/pub-event! [:graph/ask-for-re-index *multiple-windows?]))})}
  156. new-window-link (when (util/electron?)
  157. {:title (t :open-new-window)
  158. :options {:on-click #(state/pub-event! [:graph/open-new-window nil])}})]
  159. (->>
  160. (concat repo-links
  161. [(when (seq repo-links) {:hr true})
  162. {:title (t :new-graph) :options {:on-click #(page-handler/ls-dir-files! shortcut/refresh!)}}
  163. {:title (t :all-graphs) :options {:href (rfe/href :repos)}}
  164. refresh-link
  165. reindex-link
  166. new-window-link])
  167. (remove nil?))))
  168. (rum/defcs repos-dropdown < rum/reactive
  169. (rum/local false ::electron-multiple-windows?)
  170. [state]
  171. (let [multiple-windows? (::electron-multiple-windows? state)
  172. current-repo (state/sub :git/current-repo)
  173. login? (boolean (state/sub :auth/id-token))]
  174. (when (or login? current-repo)
  175. (let [repos (state/sub [:me :repos])
  176. remotes (state/sub [:file-sync/remote-graphs :graphs])
  177. repos (if (and (seq remotes) login?)
  178. (repo-handler/combine-local-&-remote-graphs repos remotes) repos)
  179. links (repos-dropdown-links repos current-repo multiple-windows?)
  180. render-content (fn [{:keys [toggle-fn]}]
  181. (let [valid-remotes-but-locals? (and (seq repos) (not (some :url repos)))
  182. remote? (when-not valid-remotes-but-locals?
  183. (:remote? (first (filter #(= current-repo (:url %)) repos))))
  184. repo-path (if-not valid-remotes-but-locals?
  185. (db/get-repo-name current-repo) "")
  186. short-repo-name (if-not valid-remotes-but-locals?
  187. (db/get-short-repo-name repo-path) "Select a Graph")]
  188. [:a.item.group.flex.items-center.px-2.py-2.text-sm.font-medium.rounded-md
  189. {:on-click (fn []
  190. (check-multiple-windows? state)
  191. (toggle-fn))
  192. :title repo-path} ;; show full path on hover
  193. (ui/icon "database mr-2" {:style {:font-size 16} :id "database-icon"})
  194. [:div.graphs
  195. [:span#repo-switch.block.pr-2.whitespace-nowrap
  196. [:span [:span#repo-name.font-medium
  197. (if (= config/local-repo short-repo-name) "Demo" short-repo-name)
  198. (when remote? [:span.pl-1 (ui/icon "cloud")])]]
  199. [:span.dropdown-caret.ml-2 {:style {:border-top-color "#6b7280"}}]]]]))
  200. links-header (cond->
  201. {:modal-class (util/hiccup->class
  202. "origin-top-right.absolute.left-0.mt-2.rounded-md.shadow-lg")}
  203. (> (count repos) 1) ; show switch to if there are multiple repos
  204. (assoc :links-header [:div.font-medium.text-sm.opacity-60.px-4.pt-2
  205. "Switch to:"]))]
  206. (when (seq repos)
  207. (ui/dropdown-with-links render-content links links-header))))))