repo.cljs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539
  1. (ns frontend.handler.repo
  2. (:refer-clojure :exclude [clone])
  3. (:require [clojure.string :as string]
  4. [frontend.config :as config]
  5. [frontend.context.i18n :refer [t]]
  6. [frontend.date :as date]
  7. [frontend.db :as db]
  8. [frontend.fs :as fs]
  9. [frontend.fs.nfs :as nfs]
  10. [frontend.handler.common :as common-handler]
  11. [frontend.handler.file :as file-handler]
  12. [frontend.handler.repo-config :as repo-config-handler]
  13. [frontend.handler.common.file :as file-common-handler]
  14. [frontend.handler.route :as route-handler]
  15. [frontend.handler.ui :as ui-handler]
  16. [frontend.handler.metadata :as metadata-handler]
  17. [frontend.handler.global-config :as global-config-handler]
  18. [frontend.idb :as idb]
  19. [frontend.search :as search]
  20. [frontend.spec :as spec]
  21. [frontend.state :as state]
  22. [frontend.util :as util]
  23. [frontend.util.fs :as util-fs]
  24. [lambdaisland.glogi :as log]
  25. [promesa.core :as p]
  26. [shadow.resource :as rc]
  27. [frontend.db.persist :as db-persist]
  28. [logseq.graph-parser.util :as gp-util]
  29. [logseq.graph-parser :as graph-parser]
  30. [electron.ipc :as ipc]
  31. [cljs-bean.core :as bean]
  32. [clojure.core.async :as async]
  33. [frontend.encrypt :as encrypt]
  34. [frontend.mobile.util :as mobile-util]
  35. [medley.core :as medley]))
  36. ;; Project settings should be checked in two situations:
  37. ;; 1. User changes the config.edn directly in logseq.com (fn: alter-file)
  38. ;; 2. Git pulls the new change (fn: load-files)
  39. (defn create-contents-file
  40. [repo-url]
  41. (spec/validate :repos/url repo-url)
  42. (p/let [repo-dir (config/get-repo-dir repo-url)
  43. pages-dir (state/get-pages-directory)
  44. [org-path md-path] (map #(str "/" pages-dir "/contents." %) ["org" "md"])
  45. contents-file-exist? (some #(fs/file-exists? repo-dir %) [org-path md-path])]
  46. (when-not contents-file-exist?
  47. (let [format (state/get-preferred-format)
  48. path (str pages-dir "/contents."
  49. (config/get-file-extension format))
  50. file-path (str "/" path)
  51. default-content (case (name format)
  52. "org" (rc/inline "contents.org")
  53. "markdown" (rc/inline "contents.md")
  54. "")]
  55. (p/let [_ (fs/mkdir-if-not-exists (util/safe-path-join repo-dir pages-dir))
  56. file-exists? (fs/create-if-not-exists repo-url repo-dir file-path default-content)]
  57. (when-not file-exists?
  58. (file-common-handler/reset-file! repo-url path default-content)))))))
  59. (defn create-custom-theme
  60. [repo-url]
  61. (spec/validate :repos/url repo-url)
  62. (let [repo-dir (config/get-repo-dir repo-url)
  63. path (str config/app-name "/" config/custom-css-file)
  64. file-path (str "/" path)
  65. default-content ""]
  66. (p/let [_ (fs/mkdir-if-not-exists (util/safe-path-join repo-dir config/app-name))
  67. file-exists? (fs/create-if-not-exists repo-url repo-dir file-path default-content)]
  68. (when-not file-exists?
  69. (file-common-handler/reset-file! repo-url path default-content)))))
  70. (defn create-dummy-notes-page
  71. [repo-url content]
  72. (spec/validate :repos/url repo-url)
  73. (let [repo-dir (config/get-repo-dir repo-url)
  74. path (str (config/get-pages-directory) "/how_to_make_dummy_notes.md")
  75. file-path (str "/" path)]
  76. (p/let [_ (fs/mkdir-if-not-exists (util/safe-path-join repo-dir (config/get-pages-directory)))
  77. _file-exists? (fs/create-if-not-exists repo-url repo-dir file-path content)]
  78. (file-common-handler/reset-file! repo-url path content))))
  79. (defn- create-today-journal-if-not-exists
  80. [repo-url {:keys [content]}]
  81. (spec/validate :repos/url repo-url)
  82. (when (state/enable-journals? repo-url)
  83. (let [repo-dir (config/get-repo-dir repo-url)
  84. format (state/get-preferred-format repo-url)
  85. title (date/today)
  86. file-name (date/journal-title->default title)
  87. default-content (util/default-content-with-title format)
  88. template (state/get-default-journal-template)
  89. template (when (and template
  90. (not (string/blank? template)))
  91. template)
  92. content (cond
  93. content
  94. content
  95. template
  96. (str default-content template)
  97. :else
  98. default-content)
  99. path (util/safe-path-join (config/get-journals-directory) (str file-name "."
  100. (config/get-file-extension format)))
  101. file-path (str "/" path)
  102. page-exists? (db/entity repo-url [:block/name (util/page-name-sanity-lc title)])
  103. empty-blocks? (db/page-empty? repo-url (util/page-name-sanity-lc title))]
  104. (when (or empty-blocks? (not page-exists?))
  105. (p/let [_ (nfs/check-directory-permission! repo-url)
  106. _ (fs/mkdir-if-not-exists (util/safe-path-join repo-dir (config/get-journals-directory)))
  107. file-exists? (fs/file-exists? repo-dir file-path)]
  108. (when-not file-exists?
  109. (p/let [_ (file-common-handler/reset-file! repo-url path content)]
  110. (p/let [_ (fs/create-if-not-exists repo-url repo-dir file-path content)]
  111. (when-not (state/editing?)
  112. (ui-handler/re-render-root!)))))
  113. (when-not (state/editing?)
  114. (ui-handler/re-render-root!)))))))
  115. (defn create-default-files!
  116. ([repo-url]
  117. (create-default-files! repo-url false))
  118. ([repo-url encrypted?]
  119. (spec/validate :repos/url repo-url)
  120. (let [repo-dir (config/get-repo-dir repo-url)]
  121. (p/let [_ (fs/mkdir-if-not-exists (util/safe-path-join repo-dir config/app-name))
  122. _ (fs/mkdir-if-not-exists (util/safe-path-join repo-dir (str config/app-name "/" config/recycle-dir)))
  123. _ (fs/mkdir-if-not-exists (util/safe-path-join repo-dir (config/get-journals-directory)))
  124. _ (file-handler/create-metadata-file repo-url encrypted?)
  125. _ (repo-config-handler/create-config-file-if-not-exists repo-url)
  126. _ (create-contents-file repo-url)
  127. _ (create-custom-theme repo-url)]
  128. (state/pub-event! [:page/create-today-journal repo-url])))))
  129. (defn- load-pages-metadata!
  130. "force?: if set true, skip the metadata timestamp range check"
  131. ([repo file-paths files]
  132. (load-pages-metadata! repo file-paths files false))
  133. ([repo file-paths files force?]
  134. (try
  135. (let [file (config/get-pages-metadata-path)]
  136. (when (contains? (set file-paths) file)
  137. (when-let [content (some #(when (= (:file/path %) file) (:file/content %)) files)]
  138. (let [metadata (common-handler/safe-read-string content "Parsing pages metadata file failed: ")
  139. pages (db/get-all-pages repo)
  140. pages (zipmap (map :block/name pages) pages)
  141. metadata (->>
  142. (filter (fn [{:block/keys [name created-at updated-at]}]
  143. (when-let [page (get pages name)]
  144. (and
  145. (>= updated-at created-at) ;; metadata validation
  146. (or force? ;; when force is true, shortcut timestamp range check
  147. (and (or (nil? (:block/created-at page))
  148. (>= created-at (:block/created-at page)))
  149. (or (nil? (:block/updated-at page))
  150. (>= updated-at (:block/created-at page)))))
  151. (or ;; persistent metadata is the gold standard
  152. (not= created-at (:block/created-at page))
  153. (not= updated-at (:block/created-at page)))))) metadata)
  154. (remove nil?))]
  155. (when (seq metadata)
  156. (db/transact! repo metadata {:new-graph? true}))))))
  157. (catch :default e
  158. (log/error :exception e)))))
  159. (defn update-pages-metadata!
  160. "update pages meta content -> db. Only accept non-encrypted content!"
  161. [repo content force?]
  162. (let [path (config/get-pages-metadata-path)
  163. files [{:file/path path
  164. :file/content content}]
  165. file-paths [path]]
  166. (load-pages-metadata! repo file-paths files force?)))
  167. (defonce *file-tx (atom nil))
  168. (defn- parse-and-load-file!
  169. [repo-url file {:keys [new-graph? verbose skip-db-transact?]
  170. :or {skip-db-transact? true}}]
  171. (try
  172. (reset! *file-tx
  173. (file-handler/alter-file repo-url
  174. (:file/path file)
  175. (:file/content file)
  176. (merge {:new-graph? new-graph?
  177. :re-render-root? false
  178. :from-disk? true
  179. :skip-db-transact? skip-db-transact?}
  180. (when (some? verbose) {:verbose verbose}))))
  181. (catch :default e
  182. (state/set-parsing-state! (fn [m]
  183. (update m :failed-parsing-files conj [(:file/path file) e])))))
  184. (state/set-parsing-state! (fn [m]
  185. (update m :finished inc)))
  186. @*file-tx)
  187. (defn- after-parse
  188. [repo-url files file-paths db-encrypted? re-render? re-render-opts opts graph-added-chan]
  189. (load-pages-metadata! repo-url file-paths files true)
  190. (when (or (:new-graph? opts) (not (:refresh? opts)))
  191. (if (and (not db-encrypted?) (state/enable-encryption? repo-url))
  192. (state/pub-event! [:modal/encryption-setup-dialog repo-url
  193. #(create-default-files! repo-url %)])
  194. (create-default-files! repo-url db-encrypted?)))
  195. (when re-render?
  196. (ui-handler/re-render-root! re-render-opts))
  197. (state/pub-event! [:graph/added repo-url opts])
  198. (state/reset-parsing-state!)
  199. (state/set-loading-files! repo-url false)
  200. (async/offer! graph-added-chan true))
  201. (defn- parse-files-and-create-default-files-inner!
  202. [repo-url files delete-files delete-blocks file-paths db-encrypted? re-render? re-render-opts opts]
  203. (let [supported-files (graph-parser/filter-files files)
  204. delete-data (->> (concat delete-files delete-blocks)
  205. (remove nil?))
  206. indexed-files (medley/indexed supported-files)
  207. chan (async/to-chan! indexed-files)
  208. graph-added-chan (async/promise-chan)
  209. total (count supported-files)
  210. large-graph? (> total 1000)]
  211. (when (seq delete-data) (db/transact! repo-url delete-data))
  212. (state/set-current-repo! repo-url)
  213. (state/set-parsing-state! {:total (count supported-files)})
  214. ;; Synchronous for tests for not breaking anything
  215. (if util/node-test?
  216. (do
  217. (doseq [file supported-files]
  218. (state/set-parsing-state! (fn [m]
  219. (assoc m
  220. :current-parsing-file (:file/path file))))
  221. (parse-and-load-file! repo-url file (assoc
  222. (select-keys opts [:new-graph? :verbose])
  223. :skip-db-transact? false)))
  224. (after-parse repo-url files file-paths db-encrypted? re-render? re-render-opts opts graph-added-chan))
  225. (async/go-loop [tx []]
  226. (if-let [item (async/<! chan)]
  227. (let [[idx file] item
  228. yield-for-ui? (or (not large-graph?)
  229. (zero? (rem idx 10))
  230. (<= (- total idx) 10))]
  231. (state/set-parsing-state! (fn [m]
  232. (assoc m :current-parsing-file (:file/path file))))
  233. (when yield-for-ui? (async/<! (async/timeout 1)))
  234. (let [result (parse-and-load-file! repo-url file (select-keys opts [:new-graph? :verbose]))
  235. tx' (concat tx result)
  236. tx' (if (zero? (rem (inc idx) 100))
  237. (do (db/transact! repo-url tx' {:from-disk? true})
  238. [])
  239. tx')]
  240. (recur tx')))
  241. (do
  242. (when (seq tx) (db/transact! repo-url tx {:from-disk? true}))
  243. (after-parse repo-url files file-paths db-encrypted? re-render? re-render-opts opts graph-added-chan)))))
  244. graph-added-chan))
  245. (defn- parse-files-and-create-default-files!
  246. [repo-url files delete-files delete-blocks file-paths db-encrypted? re-render? re-render-opts opts]
  247. (if db-encrypted?
  248. (p/let [files (p/all
  249. (map (fn [file]
  250. (p/let [content (encrypt/decrypt (:file/content file))]
  251. (assoc file :file/content content)))
  252. files))]
  253. (parse-files-and-create-default-files-inner! repo-url files delete-files delete-blocks file-paths db-encrypted? re-render? re-render-opts opts))
  254. (parse-files-and-create-default-files-inner! repo-url files delete-files delete-blocks file-paths db-encrypted? re-render? re-render-opts opts)))
  255. (defn parse-files-and-load-to-db!
  256. [repo-url files {:keys [delete-files delete-blocks re-render? re-render-opts _refresh?] :as opts
  257. :or {re-render? true}}]
  258. (let [file-paths (map :file/path files)
  259. metadata-file (config/get-metadata-path)
  260. metadata-content (some #(when (= (:file/path %) metadata-file)
  261. (:file/content %)) files)
  262. metadata (when metadata-content
  263. (common-handler/read-metadata! metadata-content))
  264. db-encrypted? (:db/encrypted? metadata)
  265. db-encrypted-secret (if db-encrypted? (:db/encrypted-secret metadata) nil)]
  266. (if db-encrypted?
  267. (let [close-fn #(parse-files-and-create-default-files! repo-url files delete-files delete-blocks file-paths db-encrypted? re-render? re-render-opts opts)]
  268. (state/set-state! :encryption/graph-parsing? true)
  269. (state/pub-event! [:modal/encryption-input-secret-dialog repo-url
  270. db-encrypted-secret
  271. close-fn]))
  272. (parse-files-and-create-default-files! repo-url files delete-files delete-blocks file-paths db-encrypted? re-render? re-render-opts opts))))
  273. (defn load-repo-to-db!
  274. [repo-url {:keys [diffs nfs-files refresh? new-graph? empty-graph?]}]
  275. (spec/validate :repos/url repo-url)
  276. (route-handler/redirect-to-home!)
  277. (state/set-parsing-state! {:graph-loading? true})
  278. (let [config (or (when-let [content (some-> (first (filter #(= (config/get-repo-config-path repo-url) (:file/path %)) nfs-files))
  279. :file/content)]
  280. (repo-config-handler/read-repo-config repo-url content))
  281. (state/get-config repo-url))
  282. ;; NOTE: Use config while parsing. Make sure it's the corrent journal title format
  283. _ (state/set-config! repo-url config)
  284. relate-path-fn (fn [m k]
  285. (some-> (get m k)
  286. (string/replace (js/decodeURI (config/get-local-dir repo-url)) "")))
  287. nfs-files (common-handler/remove-hidden-files nfs-files config #(relate-path-fn % :file/path))
  288. diffs (common-handler/remove-hidden-files diffs config #(relate-path-fn % :path))
  289. load-contents (fn [files option]
  290. (file-handler/load-files-contents!
  291. repo-url
  292. files
  293. (fn [files-contents]
  294. (parse-files-and-load-to-db! repo-url files-contents (assoc option :refresh? refresh?)))))]
  295. (cond
  296. (and (not (seq diffs)) nfs-files)
  297. (parse-files-and-load-to-db! repo-url nfs-files {:new-graph? new-graph?
  298. :empty-graph? empty-graph?})
  299. :else
  300. (when (seq diffs)
  301. (let [filter-diffs (fn [type] (->> (filter (fn [f] (= type (:type f))) diffs)
  302. (map :path)))
  303. remove-files (filter-diffs "remove")
  304. modify-files (filter-diffs "modify")
  305. add-files (filter-diffs "add")
  306. delete-files (when (seq remove-files)
  307. (db/delete-files remove-files))
  308. delete-blocks (db/delete-blocks repo-url remove-files true)
  309. delete-blocks (->>
  310. (concat
  311. delete-blocks
  312. (db/delete-blocks repo-url modify-files false))
  313. (remove nil?))
  314. delete-pages (if (seq remove-files)
  315. (db/delete-pages-by-files remove-files)
  316. [])
  317. add-or-modify-files (some->>
  318. (concat modify-files add-files)
  319. (gp-util/remove-nils))
  320. options {:delete-files (concat delete-files delete-pages)
  321. :delete-blocks delete-blocks
  322. :re-render? true}]
  323. (if (seq nfs-files)
  324. (parse-files-and-load-to-db! repo-url nfs-files
  325. (assoc options
  326. :refresh? refresh?
  327. :re-render-opts {:clear-all-query-state? true}))
  328. (load-contents add-or-modify-files options)))))))
  329. (defn remove-repo!
  330. [{:keys [url] :as repo}]
  331. (let [delete-db-f (fn []
  332. (let [graph-exists? (db/get-db url)]
  333. (db/remove-conn! url)
  334. (db-persist/delete-graph! url)
  335. (search/remove-db! url)
  336. (state/delete-repo! repo)
  337. (when graph-exists? (ipc/ipc "graphUnlinked" repo))
  338. (when (= (state/get-current-repo) url)
  339. (state/set-current-repo! (:url (first (state/get-repos)))))))]
  340. (when (or (config/local-db? url) (= url "local"))
  341. (-> (p/let [_ (idb/clear-local-db! url)] ; clear file handles
  342. )
  343. (p/finally delete-db-f)))))
  344. (defn start-repo-db-if-not-exists!
  345. [repo]
  346. (state/set-current-repo! repo)
  347. (db/start-db-conn! repo))
  348. (defn- setup-local-repo-if-not-exists-impl!
  349. []
  350. ;; loop query if js/window.pfs is ready, interval 100ms
  351. (if js/window.pfs
  352. (let [repo config/local-repo]
  353. (p/do! (fs/mkdir-if-not-exists (str "/" repo))
  354. (state/set-current-repo! repo)
  355. (db/start-db-conn! repo)
  356. (when-not config/publishing?
  357. (let [dummy-notes (t :tutorial/dummy-notes)]
  358. (create-dummy-notes-page repo dummy-notes)))
  359. (when-not config/publishing?
  360. (let [tutorial (t :tutorial/text)
  361. tutorial (string/replace-first tutorial "$today" (date/today))]
  362. (create-today-journal-if-not-exists repo {:content tutorial})))
  363. (repo-config-handler/create-config-file-if-not-exists repo)
  364. (create-contents-file repo)
  365. (create-custom-theme repo)
  366. (state/set-db-restoring! false)
  367. (ui-handler/re-render-root!)))
  368. (p/then (p/delay 100) ;; TODO Junyi remove the string
  369. setup-local-repo-if-not-exists-impl!)))
  370. (defn setup-local-repo-if-not-exists!
  371. []
  372. ;; ensure `(state/set-db-restoring! false)` at exit
  373. (-> (setup-local-repo-if-not-exists-impl!)
  374. (p/timeout 3000)
  375. (p/catch (fn []
  376. (prn "setup-local-repo failed! timeout 3000ms")))
  377. (p/finally (fn []
  378. (state/set-db-restoring! false)))))
  379. (defn restore-and-setup-repo!
  380. "Restore the db of a graph from the persisted data, and setup. Create a new
  381. conn, or replace the conn in state with a new one."
  382. [repo]
  383. (p/do!
  384. (state/set-db-restoring! true)
  385. (db/restore-graph! repo)
  386. (repo-config-handler/restore-repo-config! repo)
  387. (when (config/global-config-enabled?)
  388. (global-config-handler/restore-global-config!))
  389. ;; Don't have to unlisten the old listener, as it will be destroyed with the conn
  390. (db/listen-and-persist! repo)
  391. (state/pub-event! [:shortcut/refresh])
  392. (ui-handler/add-style-if-exists!)
  393. (state/set-db-restoring! false)))
  394. (defn rebuild-index!
  395. [url]
  396. (when-not (state/unlinked-dir? (config/get-repo-dir url))
  397. (when url
  398. (search/reset-indice! url)
  399. (db/remove-conn! url)
  400. (db/clear-query-state!)
  401. (-> (p/do! (db-persist/delete-graph! url))
  402. (p/catch (fn [error]
  403. (prn "Delete repo failed, error: " error)))))))
  404. (defn re-index!
  405. [nfs-rebuild-index! ok-handler]
  406. (when-let [repo (state/get-current-repo)]
  407. (let [dir (config/get-repo-dir repo)]
  408. (when-not (state/unlinked-dir? dir)
  409. (route-handler/redirect-to-home!)
  410. (let [local? (config/local-db? repo)]
  411. (if local?
  412. (p/let [_ (metadata-handler/set-pages-metadata! repo)]
  413. (nfs-rebuild-index! repo ok-handler))
  414. (rebuild-index! repo))
  415. (js/setTimeout
  416. (route-handler/redirect-to-home!)
  417. 500))))))
  418. (defn persist-db!
  419. ([]
  420. (persist-db! {}))
  421. ([handlers]
  422. (persist-db! (state/get-current-repo) handlers))
  423. ([repo {:keys [before on-success on-error]}]
  424. (->
  425. (p/do!
  426. (when before
  427. (before))
  428. (metadata-handler/set-pages-metadata! repo)
  429. (db/persist! repo)
  430. (when on-success
  431. (on-success)))
  432. (p/catch (fn [error]
  433. (js/console.error error)
  434. (when on-error
  435. (on-error)))))))
  436. (defn broadcast-persist-db!
  437. "Only works for electron
  438. Call backend to handle persisting a specific db on other window
  439. Skip persisting if no other windows is open (controlled by electron)
  440. step 1. [In HERE] a window ---broadcastPersistGraph----> electron
  441. step 2. electron ---------persistGraph-------> window holds the graph
  442. step 3. window w/ graph --broadcastPersistGraphDone-> electron
  443. step 4. [In HERE] a window <---broadcastPersistGraph---- electron"
  444. [graph]
  445. (p/let [_ (ipc/ipc "broadcastPersistGraph" graph)] ;; invoke for chaining promise
  446. nil))
  447. (defn get-repos
  448. []
  449. (p/let [nfs-dbs (db-persist/get-all-graphs)
  450. nfs-dbs (map (fn [db]
  451. {:url db
  452. :root (config/get-local-dir db)
  453. :nfs? true}) nfs-dbs)
  454. nfs-dbs (and (seq nfs-dbs)
  455. (cond (util/electron?)
  456. (ipc/ipc :inflateGraphsInfo nfs-dbs)
  457. (mobile-util/native-platform?)
  458. (util-fs/inflate-graphs-info nfs-dbs)
  459. :else
  460. nil))
  461. nfs-dbs (seq (bean/->clj nfs-dbs))]
  462. (cond
  463. (seq nfs-dbs)
  464. nfs-dbs
  465. :else
  466. [{:url config/local-repo
  467. :example? true}])))
  468. (defn combine-local-&-remote-graphs
  469. [local-repos remote-repos]
  470. (when-let [repos' (seq (concat (map #(if-let [sync-meta (seq (:sync-meta %))]
  471. (assoc % :GraphUUID (second sync-meta)) %)
  472. local-repos)
  473. (some->> remote-repos
  474. (map #(assoc % :remote? true)))))]
  475. (let [repos' (group-by :GraphUUID repos')
  476. repos'' (mapcat (fn [[k vs]]
  477. (if-not (nil? k)
  478. [(merge (first vs) (second vs))] vs))
  479. repos')]
  480. (sort-by (fn [repo]
  481. (let [graph-name (or (:GraphName repo)
  482. (last (string/split (:root repo) #"/")))]
  483. [(:remote? repo) (string/lower-case graph-name)])) repos''))))
  484. (defn get-detail-graph-info
  485. [url]
  486. (when-let [graphs (seq (and url (combine-local-&-remote-graphs
  487. (state/get-repos)
  488. (state/get-remote-repos))))]
  489. (first (filter #(when-let [url' (:url %)]
  490. (= url url')) graphs))))
  491. (defn refresh-repos!
  492. []
  493. (p/let [repos (get-repos)
  494. repos' (combine-local-&-remote-graphs
  495. repos
  496. (state/get-remote-repos))]
  497. (state/set-repos! repos')
  498. repos'))
  499. (defn graph-ready!
  500. "Call electron that the graph is loaded."
  501. [graph]
  502. (ipc/ipc "graphReady" graph))