Просмотр исходного кода

enhance: sync databases between multiple windows on Electron

Tienson Qin 4 лет назад
Родитель
Сommit
aa0ce9a3b1

+ 12 - 19
src/electron/electron/fs_watcher.cljs

@@ -20,6 +20,13 @@
       (send file-watcher-chan
             (bean/->js {:type type :payload payload}))))
 
+(defn- publish-file-event!
+  [win dir path event]
+  (send-file-watcher! win event {:dir (utils/fix-win-path! dir)
+                                 :path (utils/fix-win-path! path)
+                                 :content (utils/read-file path)
+                                 :stat (fs/statSync path)}))
+
 (defn watch-dir!
   [win dir]
   (when (fs/existsSync dir)
@@ -39,29 +46,15 @@
       ;; TODO: batch sender
       (.on watcher "add"
            (fn [path]
-             (let [windows (win/get-all-windows)]
-               (doseq [win windows]
-                 (send-file-watcher! win "add"
-                                     {:dir (utils/fix-win-path! dir)
-                                      :path (utils/fix-win-path! path)
-                                      :content (utils/read-file path)
-                                      :stat (fs/statSync path)})))))
+             (publish-file-event! win dir path "add")))
       (.on watcher "change"
            (fn [path]
-             (let [windows (win/get-all-windows)]
-               (doseq [win windows]
-                 (send-file-watcher! win "change"
-                                     {:dir (utils/fix-win-path! dir)
-                                      :path (utils/fix-win-path! path)
-                                      :content (utils/read-file path)
-                                      :stat (fs/statSync path)})))))
+             (publish-file-event! win dir path "change")))
       (.on watcher "unlink"
            (fn [path]
-             (let [windows (win/get-all-windows)]
-               (doseq [win windows]
-                 (send-file-watcher! win "unlink"
-                                     {:dir (utils/fix-win-path! dir)
-                                      :path (utils/fix-win-path! path)})))))
+             (send-file-watcher! win "unlink"
+                                 {:dir (utils/fix-win-path! dir)
+                                  :path (utils/fix-win-path! path)})))
       (.on watcher "error"
            (fn [path]
              (println "Watch error happened: "

+ 26 - 3
src/electron/electron/handler.cljs

@@ -300,9 +300,10 @@
 (defmethod handle :getAppBaseInfo [^js win [_ opts]]
   {:isFullScreen (.isFullScreen win)})
 
-(defmethod handle :setCurrentGraph [_ [_ path]]
-  (let [path (when path (string/replace path "logseq_local_" ""))]
+(defmethod handle :setCurrentGraph [^js win [_ path]]
+  (let [path (when path (utils/get-graph-dir path))]
     (swap! state/state assoc :graph/current path)
+    (swap! state/state assoc-in [:window/graph win] path)
     nil))
 
 (defmethod handle :runGit [_ [_ args]]
@@ -328,6 +329,28 @@
   (doseq [window (win/get-all-windows)]
     (utils/send-to-renderer window "graphUnlinked" (bean/->clj repo))))
 
+(defmethod handle :dbsync [^js win [_ graph tx-data]]
+  (let [dir (utils/get-graph-dir graph)]
+    (doseq [window (win/get-graph-all-windows dir)]
+      (utils/send-to-renderer window "dbsync"
+                              (bean/->clj {:graph graph
+                                           :tx-data tx-data})))))
+
+(defn- graph-has-other-windows? [win graph]
+  (let [dir (utils/get-graph-dir graph)
+        windows (win/get-graph-all-windows dir)
+        windows (filter #(.isVisible %) windows)]
+    (boolean (some (fn [^js window] (not= (.-id win) (.-id window))) windows))))
+
+(defmethod handle :graphHasOtherWindow [^js win [_ graph]]
+  (graph-has-other-windows? win graph))
+
+(defmethod handle :graphHasMultipleWindows [^js _win [_ graph]]
+  (let [dir (utils/get-graph-dir graph)
+        windows (win/get-graph-all-windows dir)
+        windows (filter #(.isVisible %) windows)]
+    (> (count windows) 1)))
+
 (defmethod handle :default [args]
   (println "Error: no ipc handler for: " (bean/->js args)))
 
@@ -341,7 +364,7 @@
                  (catch js/Error e
                    (when-not (contains? #{"mkdir" "stat"} (nth args-js 0))
                      (println "IPC error: " {:event event
-                                            :args args-js}
+                                             :args args-js}
                              e))
                    e))))
     #(.removeHandler ipcMain main-channel)))

+ 13 - 4
src/electron/electron/state.cljs

@@ -1,15 +1,20 @@
 (ns electron.state
   (:require [clojure.core.async :as async]
-            [electron.configs :as config]))
+            [electron.configs :as config]
+            [medley.core :as medley]))
 
 (defonce persistent-dbs-chan (async/chan 1))
 
 (defonce state
-  (atom {:graph/current nil
-         :git/auto-commit-interval nil
+  (atom {:git/auto-commit-interval nil
 
          :config (config/get-config)
-         :db/persisted-before-closing-non-main-window? nil}))
+
+         ;; FIXME: replace with :window/graph
+         :graph/current nil
+
+         ;; window -> current graph
+         :window/graph {}}))
 
 (defn set-state!
   [path value]
@@ -37,3 +42,7 @@
 (defn get-graph-path
   []
   (:graph/current @state))
+
+(defn close-window!
+  [window]
+  (swap! state medley/dissoc-in [:window/graph window]))

+ 4 - 0
src/electron/electron/utils.cljs

@@ -90,3 +90,7 @@
    (when window
      (.. ^js window -webContents
          (send kind (bean/->js payload))))))
+
+(defn get-graph-dir
+  [graph-name]
+  (string/replace graph-name "logseq_local_" ""))

+ 7 - 0
src/electron/electron/window.cljs

@@ -70,6 +70,7 @@
   [^js win]
   (.on win "close" (fn [e]
                      (.preventDefault e)
+                     (state/close-window! win)
                      (let [web-contents (. win -webContents)]
                        (.send web-contents "persistent-dbs"))
                      (async/go
@@ -82,6 +83,12 @@
   []
   (.getAllWindows BrowserWindow))
 
+(defn get-graph-all-windows
+  [graph-path]
+  (->> (group-by second (:window/graph @state/state))
+       (#(get % graph-path))
+       (map first)))
+
 (defn setup-window-listeners!
   [^js win]
   (when win

+ 11 - 2
src/main/electron/listener.cljs

@@ -1,9 +1,11 @@
 (ns electron.listener
   (:require [frontend.state :as state]
             [frontend.handler.route :as route-handler]
+            [frontend.handler.ui :as ui-handler]
             [cljs-bean.core :as bean]
             [frontend.fs.watcher-handler :as watcher-handler]
             [frontend.db :as db]
+            [datascript.core :as d]
             [promesa.core :as p]
             [electron.ipc :as ipc]
             [frontend.handler.notification :as notification]
@@ -79,8 +81,15 @@
                      (fn [data]
                        (let [{:keys [payload]} (bean/->clj data)
                              payload (update payload :to keyword)]
-                         (prn {:payload payload})
-                         (route-handler/redirect! payload)))))
+                         (route-handler/redirect! payload))))
+
+  (js/window.apis.on "dbsync"
+                     (fn [data]
+                       (let [{:keys [graph tx-data]} (bean/->clj data)
+                             tx-data (db/string->db (:data tx-data))]
+                         (when-let [conn (db/get-conn graph false)]
+                           (d/transact! conn tx-data {:dbsync? true}))
+                         (ui-handler/re-render-root!)))))
 
 (defn listen!
   []

+ 125 - 103
src/main/frontend/components/repo.cljs

@@ -24,7 +24,9 @@
             [reitit.frontend.easy :as rfe]
             [rum.core :as rum]
             [frontend.mobile.util :as mobile-util]
-            [frontend.text :as text]))
+            [frontend.text :as text]
+            [promesa.core :as p]
+            [electron.ipc :as ipc]))
 
 (defn- open-repo-url [url]
   (repo-handler/push-if-auto-enabled! (state/get-current-repo))
@@ -66,13 +68,13 @@
                      (mobile-util/is-native-platform?))
              [:div.mr-8
               (ui/button
-               (t :open-a-directory)
-               :on-click #(page-handler/ls-dir-files! shortcut/refresh!))])
+                (t :open-a-directory)
+                :on-click #(page-handler/ls-dir-files! shortcut/refresh!))])
            (when (and (state/logged?) (not (util/electron?)))
              (ui/button
-              "Add another git repo"
-              :href (rfe/href :repo-add nil {:graph-types "github"})
-              :intent "logseq"))]
+               "Add another git repo"
+               :href (rfe/href :repo-add nil {:graph-types "github"})
+               :intent "logseq"))]
           (for [{:keys [id url] :as repo} repos]
             (let [local? (config/local-db? url)]
               [:div.flex.justify-between.mb-4 {:key id}
@@ -172,7 +174,7 @@
               ;; [:a.text-sm.font-bold {:href "/diff"} "Check diff"]
               [:div.flex.flex-row.justify-between.align-items.mt-2
                (ui/button (t :git/push)
-                          :on-click (fn [] (state/set-modal! commit/add-commit-message)))
+                 :on-click (fn [] (state/set-modal! commit/add-commit-message)))
                (when pushing? svg/loading)]]
              [:hr]
              [:div
@@ -181,7 +183,7 @@
                  (str ": " last-pulled-at)])
               [:div.flex.flex-row.justify-between.align-items
                (ui/button (t :git/pull)
-                          :on-click (fn [] (repo-handler/pull-current-repo)))
+                 :on-click (fn [] (repo-handler/pull-current-repo)))
                (when pulling? svg/loading)]
               [:a.mt-5.text-sm.opacity-50.block
                {:on-click (fn []
@@ -190,100 +192,120 @@
               [:p.pt-2.text-sm.opacity-50
                (t :git/version) (str " " version/version)]]])))])))
 
-(rum/defc repos-dropdown < rum/reactive
-  []
-  (when-let [current-repo (state/sub :git/current-repo)]
-    (rum/with-context [[t] i18n/*tongue-context*]
-      (let [get-repo-name (fn [repo]
-                            (cond
-                              (mobile-util/is-native-platform?)
-                              (text/get-graph-name-from-path repo)
+(defn- check-multiple-windows?
+  [state]
+  (when (util/electron?)
+    (p/let [multiple-windows? (ipc/ipc "graphHasMultipleWindows" (state/get-current-repo))]
+      (when multiple-windows?
+        (reset! (::electron-multiple-windows? state) true)))))
+
+(rum/defcs repos-dropdown < rum/reactive
+  (rum/local false ::electron-multiple-windows?)
+  [state]
+  (let [multiple-windows? (::electron-multiple-windows? state)]
+    (when-let [current-repo (state/sub :git/current-repo)]
+      (rum/with-context [[t] i18n/*tongue-context*]
+        (let [get-repo-name (fn [repo]
+                              (cond
+                                (mobile-util/is-native-platform?)
+                                (text/get-graph-name-from-path repo)
 
-                              (config/local-db? repo)
-                              (config/get-local-dir repo)
+                                (config/local-db? repo)
+                                (config/get-local-dir repo)
 
-                              :else
-                              (db/get-repo-path repo)))
-            repos (state/sub [:me :repos])
-            repos (remove (fn [r] (= config/local-repo (:url r))) repos)
-            switch-repos (remove (fn [repo]
-                                   (= current-repo (:url repo)))
-                                 repos)
-            repo-links (mapv
-                        (fn [{:keys [id url]}]
-                          (let [repo-path (get-repo-name url)
-                                short-repo-name (text/get-graph-name-from-path repo-path)]
-                            {:title short-repo-name
-                             :hover-detail repo-path ;; show full path on hover
-                             :options {:class "ml-1"
-                                       :on-click #(open-repo-url url)}}))
-                        switch-repos)
-            links (concat repo-links
-                          [(when (seq switch-repos)
-                             {:hr true})
-                           {:title (t :new-graph)
-                            :options {:href (rfe/href :repo-add)}}
-                           {:title (t :all-graphs)
-                            :options {:href (rfe/href :repos)}}
-                           (let [nfs-repo? (config/local-db? current-repo)]
-                             (when (and nfs-repo?
-                                        (not= current-repo config/local-repo)
-                                        (or (nfs-handler/supported?)
-                                            (mobile-util/is-native-platform?)))
-                               {:title (t :sync-from-local-files)
-                                :hover-detail (t :sync-from-local-files-detail)
-                                :options {:on-click
-                                          (fn []
-                                            (state/pub-event!
-                                             [:modal/show
-                                              [:div {:style {:max-width 700}}
-                                               [:p "Refresh detects and processes files modified on your disk and diverged from the actual Logseq page content. Continue?"]
-                                               (ui/button
-                                                "Yes"
-                                                :autoFocus "on"
-                                                :large? true
-                                                :on-click (fn []
-                                                            (state/close-modal!)
-                                                            (nfs-handler/refresh! (state/get-current-repo) refresh-cb)))]]))}}))
-                           {:title        (t :re-index)
-                            :hover-detail (t :re-index-detail)
-                            :options {:on-click
-                                      (fn []
-                                        (state/pub-event!
-                                         [:modal/show
-                                          [:div {:style {:max-width 700}}
-                                           [:p "Re-index will discard the current graph, and then processes all the files again as they are currently stored on disk. You will lose unsaved changes and it might take a while. Continue?"]
-                                           (ui/button
-                                            "Yes"
-                                            :autoFocus "on"
-                                            :large? true
-                                            :on-click (fn []
-                                                        (state/close-modal!)
-                                                        (repo-handler/re-index!
-                                                         nfs-handler/rebuild-index!
-                                                         page-handler/create-today-journal!)))]]))}}
-                           (when (util/electron?)
-                             {:title        (t :open-new-window)
-                              :options {:on-click ui-handler/open-new-window!}})])]
-        (when (seq repos)
-          (ui/dropdown-with-links
-           (fn [{:keys [toggle-fn]}]
-             (let [repo-path (get-repo-name current-repo)
-                   short-repo-name (if (or (util/electron?)
-                                           (mobile-util/is-native-platform?))
-                                     (text/get-file-basename repo-path)
-                                     repo-path)]
-               [:a.item.group.flex.items-center.px-2.py-2.text-sm.font-medium.rounded-md
-                {:on-click toggle-fn :title repo-path} ;; show full path on hover
-                (ui/icon "database mr-3" {:style {:font-size 20} :id "database-icon"})
-                [:div.graphs
-                 [:span#repo-switch.block.pr-2.whitespace-nowrap
-                  [:span [:span#repo-name.font-medium short-repo-name]]
-                  [:span.dropdown-caret.ml-2 {:style {:border-top-color "#6b7280"}}]]]]))
-           links
-           (cond->
-            {:modal-class (util/hiccup->class
-                           "origin-top-right.absolute.left-0.mt-2.rounded-md.shadow-lg")}
-             (seq switch-repos)
-             (assoc :links-header [:div.font-medium.text-sm.opacity-60.px-4.pt-2
-                                   "Switch to:"]))))))))
+                                :else
+                                (db/get-repo-path repo)))
+              repos (state/sub [:me :repos])
+              repos (remove (fn [r] (= config/local-repo (:url r))) repos)
+              switch-repos (remove (fn [repo]
+                                     (= current-repo (:url repo)))
+                                   repos)
+              repo-links (mapv
+                          (fn [{:keys [id url]}]
+                            (let [repo-path (get-repo-name url)
+                                  short-repo-name (text/get-graph-name-from-path repo-path)]
+                              {:title short-repo-name
+                               :hover-detail repo-path ;; show full path on hover
+                               :options {:class "ml-1"
+                                         :on-click #(open-repo-url url)}}))
+                          switch-repos)
+              links (->>
+                     (concat repo-links
+                             [(when (seq switch-repos)
+                                {:hr true})
+                              {:title (t :new-graph)
+                               :options {:href (rfe/href :repo-add)}}
+                              {:title (t :all-graphs)
+                               :options {:href (rfe/href :repos)}}
+                              (let [nfs-repo? (config/local-db? current-repo)]
+                                (when (and nfs-repo?
+                                           (not= current-repo config/local-repo)
+                                           (or (nfs-handler/supported?)
+                                               (mobile-util/is-native-platform?)))
+                                  {:title (t :sync-from-local-files)
+                                   :hover-detail (t :sync-from-local-files-detail)
+                                   :options {:on-click
+                                             (fn []
+                                               (state/pub-event!
+                                                [:modal/show
+                                                 [:div {:style {:max-width 700}}
+                                                  [:p "Refresh detects and processes files modified on your disk and diverged from the actual Logseq page content. Continue?"]
+                                                  (ui/button
+                                                    "Yes"
+                                                    :autoFocus "on"
+                                                    :large? true
+                                                    :on-click (fn []
+                                                                (state/close-modal!)
+                                                                (nfs-handler/refresh! (state/get-current-repo) refresh-cb)))]]))}}))
+                              {:title        (t :re-index)
+                               :hover-detail (t :re-index-detail)
+                               :options (cond->
+                                          {:on-click
+                                           (fn []
+                                             (if @multiple-windows?
+                                               (state/pub-event!
+                                                [:modal/show
+                                                 [:div
+                                                  [:p "You need to close the other windows before re-index this graph."]]])
+                                               (state/pub-event!
+                                                [:modal/show
+                                                 [:div {:style {:max-width 700}}
+                                                  [:p "Re-index will discard the current graph, and then processes all the files again as they are currently stored on disk. You will lose unsaved changes and it might take a while. Continue?"]
+                                                  (ui/button
+                                                    "Yes"
+                                                    :autoFocus "on"
+                                                    :large? true
+                                                    :on-click (fn []
+                                                                (state/close-modal!)
+                                                                (repo-handler/re-index!
+                                                                 nfs-handler/rebuild-index!
+                                                                 page-handler/create-today-journal!)))]])))})}
+                              (when (util/electron?)
+                                {:title        (t :open-new-window)
+                                 :options {:on-click ui-handler/open-new-window!}})])
+                     (remove nil?))]
+          (when (seq repos)
+            (ui/dropdown-with-links
+             (fn [{:keys [toggle-fn]}]
+               (let [repo-path (get-repo-name current-repo)
+                     short-repo-name (if (or (util/electron?)
+                                             (mobile-util/is-native-platform?))
+                                       (text/get-file-basename repo-path)
+                                       repo-path)]
+                 [:a.item.group.flex.items-center.px-2.py-2.text-sm.font-medium.rounded-md
+                  {:on-click (fn []
+                               (check-multiple-windows? state)
+                               (toggle-fn))
+                   :title repo-path} ;; show full path on hover
+                  (ui/icon "database mr-3" {:style {:font-size 20} :id "database-icon"})
+                  [:div.graphs
+                   [:span#repo-switch.block.pr-2.whitespace-nowrap
+                    [:span [:span#repo-name.font-medium short-repo-name]]
+                    [:span.dropdown-caret.ml-2 {:style {:border-top-color "#6b7280"}}]]]]))
+             links
+             (cond->
+               {:modal-class (util/hiccup->class
+                              "origin-top-right.absolute.left-0.mt-2.rounded-md.shadow-lg")}
+               (seq switch-repos)
+               (assoc :links-header [:div.font-medium.text-sm.opacity-60.px-4.pt-2
+                                     "Switch to:"])))))))))

+ 12 - 9
src/main/frontend/components/search.cljs

@@ -76,8 +76,7 @@
 
 (rum/defc block-search-result-item
   [repo uuid format content q search-mode]
-  [:div [
-         (when (not= search-mode :page)
+  [:div [(when (not= search-mode :page)
            [:div {:class "mb-1" :key "parents"} (block/block-parents {:id "block-search-block-parent"
                                                                       :block? true
                                                                       :search? true}
@@ -196,11 +195,16 @@
 
                         :block
                         (let [block-uuid (uuid (:block/uuid data))
-                              collapsed? (db/parents-collapsed? (state/get-current-repo) block-uuid)]
-                          (if collapsed?
-                            (route/redirect-to-page! block-uuid)
-                            (let [page (:block/name (:block/page (db/entity [:block/uuid block-uuid])))]
-                              (route/redirect-to-page! page  (str "ls-block-" (:block/uuid data))))))
+                              collapsed? (db/parents-collapsed? (state/get-current-repo) block-uuid)
+                              page (:block/name (:block/page (db/entity [:block/uuid block-uuid])))]
+                          (if page
+                            (if collapsed?
+                             (route/redirect-to-page! block-uuid)
+                             (route/redirect-to-page! page (str "ls-block-" (:block/uuid data))))
+                            ;; search indice outdated
+                            (println "[Error] Block page missing: "
+                                     {:block-id block-uuid
+                                      :block (db/pull [:block/uuid block-uuid])})))
                         nil)
                       (state/close-modal!))
          :on-shift-chosen (fn [{:keys [type data alias]}]
@@ -256,8 +260,7 @@
 
                                                   :block
                                                   (let [{:block/keys [page content uuid]} data
-                                                        page (or (:block/original-name page)
-                                                                (:block/name page))
+                                                        page (util/get-page-original-name page)
                                                         repo (state/sub :git/current-repo)
                                                         format (db/get-page-format page)]
                                                     [:span {:data-block-ref uuid}

+ 9 - 2
src/main/frontend/db.cljs

@@ -12,7 +12,9 @@
             [frontend.namespaces :refer [import-vars]]
             [frontend.state :as state]
             [frontend.util :as util]
-            [promesa.core :as p]))
+            [promesa.core :as p]
+            [frontend.config :as config]
+            [electron.ipc :as ipc]))
 
 (import-vars
  [frontend.db.conn
@@ -104,7 +106,12 @@
   [repo conn]
   (d/listen! conn :persistence
              (fn [tx-report]
-               (when-not (util/electron?)
+               (if (util/electron?)
+                 (when-not (:dbsync? (:tx-meta tx-report))
+                   ;; sync with other windows if needed
+                   (p/let [graph-has-other-window? (ipc/ipc "graphHasOtherWindow" repo)]
+                    (when graph-has-other-window?
+                      (ipc/ipc "dbsync" repo {:data (db->string (:tx-data tx-report))}))))
                  (let [tx-id (get-tx-id tx-report)]
                    (state/set-last-transact-time! repo (util/time-ms))
                    (persist-if-idle! repo)))