diff --git a/android/app/src/main/java/com/logseq/app/MaterialIconResolver.kt b/android/app/src/main/java/com/logseq/app/MaterialIconResolver.kt index a8a3ebd4b2..d27e220b31 100644 --- a/android/app/src/main/java/com/logseq/app/MaterialIconResolver.kt +++ b/android/app/src/main/java/com/logseq/app/MaterialIconResolver.kt @@ -79,6 +79,7 @@ object MaterialIconResolver { "go-to", "goto" -> Icons.Outlined.Explore "bookmark" -> Icons.Filled.Bookmarks "sync" -> Icons.Outlined.Equalizer + "cloud", "cloud-upload", "icloud-and-arrow-up" -> Icons.Filled.CloudUpload else -> null } } diff --git a/src/main/frontend/components/header.cljs b/src/main/frontend/components/header.cljs index 3c86d02671..6ce32db2ae 100644 --- a/src/main/frontend/components/header.cljs +++ b/src/main/frontend/components/header.cljs @@ -10,6 +10,7 @@ [frontend.components.export :as export] [frontend.components.page-menu :as page-menu] [frontend.components.plugins :as plugins] + [frontend.components.repo :as repo] [frontend.components.right-sidebar :as sidebar] [frontend.components.rtc.indicator :as rtc-indicator] [frontend.components.server :as server] @@ -53,6 +54,24 @@ (t :nav/home) {:trigger-props {:as-child true}})) +(defn current-local-uploadable-graph + [] + (let [current-repo (state/get-current-repo)] + (some (fn [{:keys [url] :as graph}] + (when (and (= current-repo url) + (repo/local-uploadable-graph? graph)) + graph)) + (state/get-repos)))) + +(defn local-graph-sync-button + [graph] + (ui/tooltip + (shui/button-ghost-icon :cloud + {:class "local-graph-sync-btn" + :on-click #(repo/upload-local-graph-with-confirm! graph)}) + (t :graph/use-sync-beta) + {:trigger-props {:as-child true}})) + (rum/defcs rtc-collaborators < rum/reactive (rum/local nil ::online-users) @@ -449,6 +468,9 @@ (rtc-indicator/uploading-detail)) (search-index-progress) + (when-let [graph (current-local-uploadable-graph)] + (local-graph-sync-button graph)) + (when (and (not= (state/get-current-route) :home) (not custom-home-page?)) (home-button)) diff --git a/src/main/frontend/components/repo.cljs b/src/main/frontend/components/repo.cljs index 0a9a2117a2..4582d29849 100644 --- a/src/main/frontend/components/repo.cljs +++ b/src/main/frontend/components/repo.cljs @@ -4,13 +4,13 @@ [frontend.config :as config] [frontend.context.i18n :as i18n :refer [t]] [frontend.db :as db] - [frontend.handler.db-based.rtc-flows :as rtc-flows] [frontend.handler.db-based.sync :as rtc-handler] [frontend.handler.graph :as graph] [frontend.handler.notification :as notification] [frontend.handler.repo :as repo-handler] [frontend.handler.route :as route-handler] [frontend.handler.user :as user-handler] + [frontend.mobile.util :as mobile-util] [frontend.state :as state] [frontend.ui :as ui] [frontend.util :as util] @@ -25,16 +25,72 @@ [promesa.core :as p] [rum.core :as rum])) +(defn graph-sync-icon-name + [{:keys [remote? graph-e2ee?]}] + (when remote? + (if graph-e2ee? "lock" "cloud"))) + +(defn local-uploadable-graph? + [{:keys [root remote?]}] + (and (or root + (mobile-util/native-platform?)) + (not remote?) + (user-handler/logged-in?) + (user-handler/rtc-group?))) + +(defn- graph-e2ee-enabled? + [{:keys [url graph-e2ee?] :as graph}] + (if (contains? graph :graph-e2ee?) + (true? graph-e2ee?) + (if (= url (state/get-current-repo)) + (let [e2ee? (ldb/get-graph-rtc-e2ee? (db/get-db))] + (if (nil? e2ee?) true (true? e2ee?))) + true))) + +(defn- (shui/dialog-confirm! + [:p.font-medium.-my-4 (t :graph/upload-local-confirm-desc graph-name)] + dialog-config) + (p/then + (fn [] + (p/let [_ ( (rtc-handler/ + [:a.flex.items-center {:title local-dir + :on-click #(on-click graph)} + [:span graph-name] + (when remote? + [:strong.px-1.flex.items-center (ui/icon (graph-sync-icon-name graph))])]])])) (defn sort-repos-with-metadata-local [repos] @@ -122,6 +178,7 @@ :class "delete-local-graph-menu-item" :on-click #(delete-local-graph! repo)} (t :graph/delete-local-action))) + (when (and root (user-handler/logged-in?) (user-handler/rtc-group?) @@ -130,26 +187,7 @@ (shui/dropdown-menu-item {:key "logseq-sync" :class "use-logseq-sync-menu-item" - :on-click (fn [] - (let [repo (state/get-current-repo) - token (state/get-auth-id-token) - remote-graph-name (config/db-graph-name (state/get-current-repo)) - graph-e2ee? (let [e2ee? (ldb/get-graph-rtc-e2ee? (db/get-db))] - (if (nil? e2ee?) true (true? e2ee?)))] - (when (and token remote-graph-name) - (rtc-handler/aes-key (atom {})) (defonce ^:private *user-rsa-key-pair-inflight (atom {})) +(defonce ^:private *ensure-user-rsa-key-pair-inflight (atom {})) (defonce ^:private node-default-auth-file "~/logseq/auth.json") (defonce ^:private e2ee-password-secret-key "logseq-encrypted-password") (def ^:private invalid-transit ::invalid-transit) @@ -377,7 +378,8 @@ exported-public-key (crypt/ ( [] (nil? route-view) (conj {:id "home-setting" :systemIcon "ellipsis"}) - (and show-sync? (not page?)) + (and show-local-upload? (not page?)) + (conj {:id "sync" :systemIcon "icloud.and.arrow.up" + :size "medium"}) + (and (not show-local-upload?) show-sync? (not page?)) (conj {:id "sync" :systemIcon "circle.fill" :color sync-color :size "small"})) @@ -212,7 +229,9 @@ rtc-state (:rtc-state detail-info) graph-uuid (or (:graph-uuid detail-info) (ldb/get-graph-rtc-uuid (db/get-db))) + local-uploadable-graph (current-local-uploadable-graph) show-sync? (and current-repo graph-uuid (user-handler/logged-in?)) + show-local-upload? (some? local-uploadable-graph) unpushed-block-update-count (:pending-local-ops detail-info) pending-asset-ops (:pending-asset-ops detail-info) fallback-title (cond @@ -253,11 +272,12 @@ :route-view route-view :sync-color sync-color :show-sync? show-sync? + :show-local-upload? show-local-upload? :favorited? favorited?}))] (reset! *configure-top-bar-f f) (f favorited?))) nil) - [current-repo tab route-name route-view route-id fallback-title sync-color show-sync? page-route?]) + [current-repo tab route-name route-view route-id fallback-title sync-color show-sync? show-local-upload? page-route?]) (hooks/use-effect! (fn [] @@ -282,13 +302,14 @@ :route-view route-view :sync-color sync-color :show-sync? show-sync? + :show-local-upload? show-local-upload? :favorited? favorited?}))] (reset! *configure-top-bar-f f) (f favorited?))))) (p/catch (fn [_] nil))) #(reset! cancelled? true)) nil)) - [current-repo tab route-name route-view route-id sync-color show-sync? page-route?]) + [current-repo tab route-name route-view route-id sync-color show-sync? show-local-upload? page-route?]) [:<>])) diff --git a/src/resources/dicts/en.edn b/src/resources/dicts/en.edn index 46a2dae63d..29f8c5d76d 100644 --- a/src/resources/dicts/en.edn +++ b/src/resources/dicts/en.edn @@ -736,6 +736,7 @@ :graph/time-travel-now "Now" :graph/toggle-tag "Toggle tag {1}" :graph/updated-switching "Graph updated! Switching to graph ..." + :graph/upload-local-confirm-desc "Upload graph \"{1}\" to Logseq Server?" :graph/use-sync-beta "Use Logseq Sync (Beta testing)" :graph/use-sync-label "Use Logseq Sync?" :graph/view-mode "View mode" diff --git a/src/resources/dicts/zh-cn.edn b/src/resources/dicts/zh-cn.edn index b533af8bd6..8a19631f5d 100644 --- a/src/resources/dicts/zh-cn.edn +++ b/src/resources/dicts/zh-cn.edn @@ -732,6 +732,7 @@ :graph/time-travel-now "现在" :graph/toggle-tag "切换标签 {1}" :graph/updated-switching "图谱已更新!正在切换到图谱……" + :graph/upload-local-confirm-desc "要将图谱“{1}”上传到 Logseq Server 吗?" :graph/use-sync-beta "使用 Logseq Sync(Beta 测试)" :graph/use-sync-label "使用 Logseq Sync?" :graph/view-mode "视图模式" diff --git a/src/test/frontend/components/repo_test.cljs b/src/test/frontend/components/repo_test.cljs index bbc58f9ea6..c48a70b8eb 100644 --- a/src/test/frontend/components/repo_test.cljs +++ b/src/test/frontend/components/repo_test.cljs @@ -1,14 +1,170 @@ (ns frontend.components.repo-test (:require [cljs.test :refer [deftest is async]] [frontend.components.repo :as repo] + [frontend.components.rtc.indicator :as rtc-indicator] + [frontend.db :as db] + [frontend.handler.db-based.sync :as rtc-handler] [frontend.handler.repo :as repo-handler] + [frontend.handler.user :as user-handler] [frontend.state :as state] + [frontend.mobile.util :as mobile-util] + [frontend.util :as util] + [logseq.db :as ldb] + [logseq.shui.ui :as shui] [promesa.core :as p])) (defn- ensure-rsa-key-fn [] #'repo/ensure-e2ee-rsa-key-for-cloud!) +(deftest local-uploadable-graph-allows-native-mobile-local-graph-without-root-test + (with-redefs [mobile-util/native-platform? (constantly true) + user-handler/logged-in? (constantly true) + user-handler/rtc-group? (constantly true)] + (is (true? (repo/local-uploadable-graph? {:url "logseq_db_mobile"}))))) + +(deftest upload-local-graph-with-confirm-asks-before-upload-test + (async done + (let [upload-fn (some-> (resolve 'frontend.components.repo/upload-local-graph-with-confirm!) deref) + dialogs (atom []) + upload-calls (atom []) + finished-calls (atom 0)] + (if-not upload-fn + (do + (is false "missing upload-local-graph-with-confirm!") + (done)) + (-> (p/with-redefs [shui/dialog-confirm! (fn [content opts] + (swap! dialogs conj {:content content + :opts opts}) + (p/resolved nil)) + rtc-handler/ (resolve 'frontend.components.repo/upload-local-graph-with-confirm!) deref) + current-repo (atom "logseq_db_current") + calls (atom [])] + (if-not upload-fn + (do + (is false "missing upload-local-graph-with-confirm!") + (done)) + (-> (p/with-redefs [shui/dialog-confirm! (fn [_content _opts] + (p/resolved nil)) + state/get-current-repo (fn [] + @current-repo) + state/pub-event! (fn [event] + (swap! calls conj event) + (reset! current-repo (second event)) + (p/resolved nil)) + db/get-db (fn [] + :db-after-switch) + ldb/get-graph-rtc-e2ee? (fn [db] + (is (= :db-after-switch db)) + false) + rtc-handler/ (resolve 'frontend.components.repo/upload-local-graph-with-confirm!) deref) + calls (atom [])] + (if-not upload-fn + (do + (is false "missing upload-local-graph-with-confirm!") + (done)) + (-> (p/with-redefs [shui/dialog-confirm! (fn [_content _opts] + (p/resolved nil)) + state/get-current-repo (fn [] + "logseq_db_demo") + rtc-handler/ (resolve 'frontend.components.repo/upload-local-graph-with-confirm!) deref) + calls (atom [])] + (if-not upload-fn + (do + (is false "missing upload-local-graph-with-confirm!") + (done)) + (-> (p/with-redefs [shui/dialog-confirm! (fn [_content _opts] + (p/resolved nil)) + state/get-current-repo (fn [] + "logseq_db_demo") + rtc-handler/ (p/with-redefs [sync-crypt/e2ee-base (fn [] "http://base") + sync-crypt/ (p/with-redefs [sync-crypt/e2ee-base (fn [] "http://base") + sync-crypt/