Fase 8 (2/N): delete the client-side RTC/E2EE/sync UI and finish the teardown
Removes the client-side half of the remote CRDT sync protocol left after
the worker-side deletion in 565c211: RTC/remote-graph UI (components/rtc/*,
db/rtc/debug_ui.cljs, cloud-graph upload/download/leave/delete flows in
components/repo.cljs and mobile/components/app.cljs), E2EE UI and handlers
(components/e2ee.cljs, handler/e2ee.cljs, handler/events/rtc*.cljs), the
db-worker-node ui-request RPC (handler/worker.cljs's :db-worker/ui-request,
which existed only to serve E2EE password prompts), settings panels for the
legacy custom sync-server URL, and the now-orphaned :rtc/* client state
(presence UI, upload/download progress, remote-graph lists) — presence.cljs
was already deleted in the worker-side pass, contrary to the plan's "keep"
list, so all of this had zero producers left and was fully inert.
Also fixes several dead thread-api invokes discovered on LIVE paths that
would throw at runtime rather than being merely dead UI:
- db/restore.cljs's restore-graph! (called on every graph load)
- persist_db.cljs's <ensure-remote! (the Fase 3/4 daemon connect path)
- persist_db/browser.cljs's worker init and shutdown
- handler/user.cljs's logout
Verified via full compile of all 5 shadow-cljs targets (app, electron,
mobile, db-worker-node, test — 0 warnings) and a full bb dev:test run:
every namespace passes except the same 7 frontend.persist-db-test failures
that reproduce identically on unmodified HEAD (confirmed via git stash),
pre-existing network-sandbox flakiness unrelated to this change.
Known follow-up, not yet done: a handful of harmless-but-vestigial :rtc/*
references remain (handler/user.cljs's storage/graph-count exceed-limit
events, handler/events.cljs's :rtc-download? opt, worker/state.cljs's
*db-sync-config atom, worker/handler/graph.cljs's get-rtc-graph-uuid reused
by handler/editor.cljs's quick-add filter) — none throw or affect behavior,
left for a future pass rather than risk removing something still load-bearing
without deeper tracing. deps/db-sync/ (the Cloudflare Worker) is also still
untouched, per the plan's explicit note that it needs separate, larger surgery.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -38,8 +38,7 @@
|
|||||||
goog.debug.LOGGING_ENABLED true
|
goog.debug.LOGGING_ENABLED true
|
||||||
frontend.modules.instrumentation.sentry/SENTRY-DSN #shadow/env "LOGSEQ_SENTRY_DSN"
|
frontend.modules.instrumentation.sentry/SENTRY-DSN #shadow/env "LOGSEQ_SENTRY_DSN"
|
||||||
frontend.modules.instrumentation.posthog/POSTHOG-TOKEN #shadow/env "LOGSEQ_POSTHOG_TOKEN"
|
frontend.modules.instrumentation.posthog/POSTHOG-TOKEN #shadow/env "LOGSEQ_POSTHOG_TOKEN"
|
||||||
frontend.config/ENABLE-PLUGINS #shadow/env ["ENABLE_PLUGINS" :as :bool :default true]
|
frontend.config/ENABLE-PLUGINS #shadow/env ["ENABLE_PLUGINS" :as :bool :default true]}
|
||||||
frontend.config/ENABLE-DB-SYNC-LOCAL #shadow/env ["ENABLE_DB_SYNC_LOCAL" :as :bool :default false]}
|
|
||||||
|
|
||||||
:devtools {:before-load frontend.core/stop ;; before live-reloading any code call this function
|
:devtools {:before-load frontend.core/stop ;; before live-reloading any code call this function
|
||||||
:after-load frontend.core/start ;; after live-reloading finishes call this function
|
:after-load frontend.core/start ;; after live-reloading finishes call this function
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
[clojure.string :as string]
|
[clojure.string :as string]
|
||||||
[dommy.core :as dom]
|
[dommy.core :as dom]
|
||||||
[electron.ipc :as ipc]
|
[electron.ipc :as ipc]
|
||||||
[frontend.components.avatar :as avatar]
|
|
||||||
[frontend.components.block.breadcrumb-model :as breadcrumb-model]
|
[frontend.components.block.breadcrumb-model :as breadcrumb-model]
|
||||||
[frontend.components.block.asset :as block-asset]
|
[frontend.components.block.asset :as block-asset]
|
||||||
[frontend.components.block.comments :as block-comments]
|
[frontend.components.block.comments :as block-comments]
|
||||||
@@ -1094,25 +1093,11 @@
|
|||||||
[:a.asset-ref {:target "_blank" :href real-path-url}
|
[:a.asset-ref {:target "_blank" :href real-path-url}
|
||||||
title-or-path])]))
|
title-or-path])]))
|
||||||
|
|
||||||
(defn- maybe-request-asset-download!
|
|
||||||
[file-exists? requested? block]
|
|
||||||
(let [repo (state/get-current-repo)
|
|
||||||
asset-file-write-finish (state/get-state :assets/asset-file-write-finish)
|
|
||||||
asset-file-write-finished? (get-in asset-file-write-finish [repo (str (:block/uuid block))])
|
|
||||||
file-ready? (or file-exists? asset-file-write-finished?)]
|
|
||||||
(when (and (true? @requested?) file-ready?)
|
|
||||||
(reset! requested? false))
|
|
||||||
(when (and (not @requested?)
|
|
||||||
(assets-handler/maybe-request-remote-asset-download! repo block file-ready?))
|
|
||||||
(reset! requested? true))))
|
|
||||||
|
|
||||||
(defn- retry-missing-asset-upload!
|
(defn- retry-missing-asset-upload!
|
||||||
[repo block file-exists?*]
|
[repo block file-exists?*]
|
||||||
(let [asset-rpath (block-asset/asset-relative-path block)]
|
(let [asset-rpath (block-asset/asset-relative-path block)]
|
||||||
(p/let [exists? (fs/file-exists? (config/get-repo-dir repo) asset-rpath)]
|
(p/let [exists? (fs/file-exists? (config/get-repo-dir repo) asset-rpath)]
|
||||||
(reset! file-exists?* exists?)
|
(reset! file-exists?* exists?))))
|
||||||
(when exists?
|
|
||||||
(state/<invoke-db-worker :thread-api/db-sync-retry-asset-upload repo)))))
|
|
||||||
|
|
||||||
(hsx/defc missing-asset-file
|
(hsx/defc missing-asset-file
|
||||||
[block on-reload]
|
[block on-reload]
|
||||||
@@ -1132,25 +1117,10 @@
|
|||||||
(let [asset-type (:logseq.property.asset/type block)
|
(let [asset-type (:logseq.property.asset/type block)
|
||||||
file (block-asset/asset-file-name block)
|
file (block-asset/asset-file-name block)
|
||||||
file-exists?* (hooks/use-memo #(atom nil) [(:block/uuid block) asset-type])
|
file-exists?* (hooks/use-memo #(atom nil) [(:block/uuid block) asset-type])
|
||||||
requested?* (hooks/use-memo #(atom false) [(:block/uuid block)])
|
|
||||||
[file-exists?] (hooks/use-atom file-exists?*)
|
[file-exists?] (hooks/use-atom file-exists?*)
|
||||||
repo (state/get-current-repo)
|
repo (state/get-current-repo)
|
||||||
asset-file-write-finished? (rfx/use-sub [:assets/asset-file-write-finish repo (str (:block/uuid block))])
|
asset-file-write-finished? (rfx/use-sub [:assets/asset-file-write-finish repo (str (:block/uuid block))])
|
||||||
file-ready? (or file-exists? asset-file-write-finished?)
|
file-ready? (or file-exists? asset-file-write-finished?)
|
||||||
progress-entry (rfx/use-sub [:rtc/asset-upload-download-progress repo (str (:block/uuid block))])
|
|
||||||
{:keys [direction loaded total]} progress-entry
|
|
||||||
in-progress? (and (number? loaded) (number? total) (pos? total) (not= loaded total))
|
|
||||||
percent (when in-progress?
|
|
||||||
(int (* 100 (/ loaded total))))
|
|
||||||
label (case direction
|
|
||||||
:upload (t :asset/uploading)
|
|
||||||
:download (t :asset/downloading)
|
|
||||||
(t :asset/syncing))
|
|
||||||
progress-view (when in-progress?
|
|
||||||
[:div.asset-transfer-progress
|
|
||||||
[:div.asset-transfer-progress-label (str label " " percent "%")]
|
|
||||||
[:div.asset-transfer-progress-bar
|
|
||||||
[:span {:style {:width (str percent "%")}}]]])
|
|
||||||
image? (contains? (common-config/img-formats) (keyword asset-type))
|
image? (contains? (common-config/img-formats) (keyword asset-type))
|
||||||
gallery-image? (and (:gallery-view? config) image?)
|
gallery-image? (and (:gallery-view? config) image?)
|
||||||
width (get-in block [:logseq.property.asset/resize-metadata :width])
|
width (get-in block [:logseq.property.asset/resize-metadata :width])
|
||||||
@@ -1184,8 +1154,6 @@
|
|||||||
(fs/file-exists? (config/get-repo-dir (state/get-current-repo)) path))]
|
(fs/file-exists? (config/get-repo-dir (state/get-current-repo)) path))]
|
||||||
(reset! file-exists?* result))))
|
(reset! file-exists?* result))))
|
||||||
[file])
|
[file])
|
||||||
_ (hooks/use-effect!
|
|
||||||
#(maybe-request-asset-download! file-exists? requested?* block))
|
|
||||||
content (cond
|
content (cond
|
||||||
(or file-ready? gallery-image?)
|
(or file-ready? gallery-image?)
|
||||||
(asset-link (cond-> (assoc config :asset-block block)
|
(asset-link (cond-> (assoc config :asset-block block)
|
||||||
@@ -1202,12 +1170,7 @@
|
|||||||
#(retry-missing-asset-upload! repo block file-exists?*))
|
#(retry-missing-asset-upload! repo block file-exists?*))
|
||||||
(block-asset/show-image-placeholder? block file-ready? gallery-image?)
|
(block-asset/show-image-placeholder? block file-ready? gallery-image?)
|
||||||
img-placeholder)]
|
img-placeholder)]
|
||||||
(if progress-view
|
content))
|
||||||
[:div.asset-transfer-shell
|
|
||||||
(or content
|
|
||||||
[:div.asset-transfer-placeholder (t :asset/transfer-placeholder label)])
|
|
||||||
progress-view]
|
|
||||||
content)))
|
|
||||||
|
|
||||||
(defn- img-audio-video?
|
(defn- img-audio-video?
|
||||||
[block]
|
[block]
|
||||||
@@ -2227,24 +2190,11 @@
|
|||||||
(open-comment-thread! block-uuid comment-thread))}
|
(open-comment-thread! block-uuid comment-thread))}
|
||||||
(shui/tabler-icon "message-circle" {:size 15})))))))
|
(shui/tabler-icon "message-circle" {:size 15})))))))
|
||||||
|
|
||||||
(defn- editing-user-avatar
|
|
||||||
[{:user/keys [name uuid]}]
|
|
||||||
(let [user-name (or name uuid)]
|
|
||||||
(when (avatar/initials user-name)
|
|
||||||
[:span.block-editing-avatar-wrap
|
|
||||||
(avatar/user-avatar
|
|
||||||
{:class "block-editing-avatar w-4 h-4 flex-none"
|
|
||||||
:title user-name
|
|
||||||
:name user-name
|
|
||||||
:uuid uuid
|
|
||||||
:fallback-props {:style {:font-size 9}}})])))
|
|
||||||
|
|
||||||
(hsx/defc ^:large-vars/cleanup-todo block-control
|
(hsx/defc ^:large-vars/cleanup-todo block-control
|
||||||
[config block {:keys [uuid block-id collapsed? has-children? *control-show? edit? selected? top? bottom?]}]
|
[config block {:keys [uuid block-id collapsed? has-children? *control-show? edit? selected? top? bottom?]}]
|
||||||
(let [*bullet-dragging? (hooks/use-memo #(atom false) [])
|
(let [*bullet-dragging? (hooks/use-memo #(atom false) [])
|
||||||
doc-mode? (rfx/use-sub [:document/mode?])
|
doc-mode? (rfx/use-sub [:document/mode?])
|
||||||
[control-show?] (hooks/use-atom *control-show?)
|
[control-show?] (hooks/use-atom *control-show?)
|
||||||
editing-user (rfx/use-sub [:rtc/editing-users-by-block (str uuid)])
|
|
||||||
ref? (:ref? config)
|
ref? (:ref? config)
|
||||||
container-id (:container-id config)
|
container-id (:container-id config)
|
||||||
empty-content? (block-content-empty? block)
|
empty-content? (block-content-empty? block)
|
||||||
@@ -2276,8 +2226,6 @@
|
|||||||
:is-with-icon with-icon?
|
:is-with-icon with-icon?
|
||||||
:bullet-closed collapsed?
|
:bullet-closed collapsed?
|
||||||
:bullet-hidden (:hide-bullet? config)}])}
|
:bullet-hidden (:hide-bullet? config)}])}
|
||||||
(when (and (not page-title?) editing-user)
|
|
||||||
(editing-user-avatar editing-user))
|
|
||||||
(when (and (or (not fold-button-right?) collapsable? collapsed?)
|
(when (and (or (not fold-button-right?) collapsable? collapsed?)
|
||||||
(not (:table? config)))
|
(not (:table? config)))
|
||||||
[:a.block-control
|
[:a.block-control
|
||||||
@@ -3296,78 +3244,6 @@
|
|||||||
{:align :end}))}
|
{:align :end}))}
|
||||||
(clock/seconds->days:hours:minutes:seconds seconds))])))
|
(clock/seconds->days:hours:minutes:seconds seconds))])))
|
||||||
|
|
||||||
(defn- sync-conflict-attr-label
|
|
||||||
[attr]
|
|
||||||
(case attr
|
|
||||||
:block/title (t :property.built-in/title)
|
|
||||||
(name attr)))
|
|
||||||
|
|
||||||
(defn- visible-sync-conflicts
|
|
||||||
[block conflicts]
|
|
||||||
(->> conflicts
|
|
||||||
(remove (fn [{:keys [attr value]}]
|
|
||||||
(= value (get block attr))))
|
|
||||||
vec))
|
|
||||||
|
|
||||||
(hsx/defc sync-conflict-item
|
|
||||||
[{:keys [id attr value created-at]}]
|
|
||||||
[:div.border.rounded.p-3 {:key id}
|
|
||||||
[:div.flex.flex-row.items-center.justify-between.gap-3.mb-2.text-xs.text-muted-foreground
|
|
||||||
[:span (sync-conflict-attr-label attr)]
|
|
||||||
[:span (date/int->local-time-2 created-at)]]
|
|
||||||
[:pre.whitespace-pre-wrap.text-sm.bg-muted.p-2.rounded.max-h-64.overflow-auto value]
|
|
||||||
[:div.flex.justify-end.mt-2
|
|
||||||
(shui/button
|
|
||||||
{:variant :secondary
|
|
||||||
:size :sm
|
|
||||||
:on-click (fn []
|
|
||||||
(util/copy-to-clipboard! value)
|
|
||||||
(notification/show! (t :notification/copied) :success))}
|
|
||||||
(t :ui/copy))]])
|
|
||||||
|
|
||||||
(hsx/defc sync-conflicts-popup
|
|
||||||
[conflicts on-mark-resolved]
|
|
||||||
[:div.p-3.w-96
|
|
||||||
{:style {:max-width "90vw"}}
|
|
||||||
[:h2.text-lg.font-medium.mb-2 (t :sync/conflicts-title)]
|
|
||||||
[:p.text-sm.text-muted-foreground.mb-3
|
|
||||||
(t :sync/conflicts-description)]
|
|
||||||
[:div.flex.flex-col.gap-3
|
|
||||||
(for [conflict conflicts]
|
|
||||||
(sync-conflict-item conflict))]
|
|
||||||
[:div.flex.justify-end.mt-3
|
|
||||||
(ui/button (t :sync/mark-conflicts-resolved)
|
|
||||||
:on-click on-mark-resolved)]])
|
|
||||||
|
|
||||||
(hsx/defc sync-conflicts-warning-button
|
|
||||||
[block]
|
|
||||||
(let [repo (state/get-current-repo)
|
|
||||||
block-id (:block/uuid block)
|
|
||||||
conflicts (rfx/use-sub [:sync/block-conflicts repo (str block-id)])
|
|
||||||
visible-conflicts (visible-sync-conflicts block conflicts)]
|
|
||||||
(when (seq visible-conflicts)
|
|
||||||
(ui/tooltip
|
|
||||||
(shui/button
|
|
||||||
{:variant :secondary
|
|
||||||
:size :sm
|
|
||||||
:title (t :sync/show-conflicts)
|
|
||||||
:class "ls-sync-conflict-warning ls-small-icon px-1 !py-0 h-5"
|
|
||||||
:on-click (fn [e]
|
|
||||||
(util/stop e)
|
|
||||||
(shui/popup-show! (.-target e)
|
|
||||||
(fn []
|
|
||||||
(sync-conflicts-popup
|
|
||||||
visible-conflicts
|
|
||||||
(fn []
|
|
||||||
(p/let [_ (state/<invoke-db-worker
|
|
||||||
:thread-api/db-sync-clear-block-conflicts
|
|
||||||
repo
|
|
||||||
block-id)]
|
|
||||||
(shui/popup-hide!)))))
|
|
||||||
{:align :end}))}
|
|
||||||
(ui/icon "alert-triangle" {:size 14}))
|
|
||||||
[:div (t :sync/show-conflicts)]))))
|
|
||||||
|
|
||||||
(hsx/defc ^:large-vars/cleanup-todo block-content
|
(hsx/defc ^:large-vars/cleanup-todo block-content
|
||||||
[config {:block/keys [uuid] :as block} edit-input-id block-id *show-query?]
|
[config {:block/keys [uuid] :as block} edit-input-id block-id *show-query?]
|
||||||
(let [repo (state/get-current-repo)
|
(let [repo (state/get-current-repo)
|
||||||
@@ -3619,10 +3495,6 @@
|
|||||||
|
|
||||||
(when-not (:table-block-title? config)
|
(when-not (:table-block-title? config)
|
||||||
[:div.ls-block-right.flex.flex-row.items-center.self-start.gap-1
|
[:div.ls-block-right.flex.flex-row.items-center.self-start.gap-1
|
||||||
(when-not (or (:block-ref? config) (:table? config) (:gallery-view? config)
|
|
||||||
(:property? config))
|
|
||||||
(sync-conflicts-warning-button block))
|
|
||||||
|
|
||||||
(when-not table?
|
(when-not table?
|
||||||
[:div.opacity-70.hover:opacity-100
|
[:div.opacity-70.hover:opacity-100
|
||||||
(block-positioned-properties config block :block-right)])
|
(block-positioned-properties config block :block-right)])
|
||||||
|
|||||||
@@ -467,8 +467,7 @@
|
|||||||
logged? (user-handler/logged-in?)
|
logged? (user-handler/logged-in?)
|
||||||
fold-button-on-right? (state/use-enable-fold-button-right?)
|
fold-button-on-right? (state/use-enable-fold-button-right?)
|
||||||
show-action-bar? (rfx/use-sub [:mobile/show-action-bar?])
|
show-action-bar? (rfx/use-sub [:mobile/show-action-bar?])
|
||||||
preferred-language (rfx/use-sub [:preferred-language])
|
preferred-language (rfx/use-sub [:preferred-language])]
|
||||||
uploading? (rfx/use-sub [:rtc/uploading?])]
|
|
||||||
(hooks/use-effect!
|
(hooks/use-effect!
|
||||||
(fn []
|
(fn []
|
||||||
(let [keydown-handler (fn [e]
|
(let [keydown-handler (fn [e]
|
||||||
@@ -554,18 +553,15 @@
|
|||||||
(when (util/electron?)
|
(when (util/electron?)
|
||||||
(find-in-page/search))
|
(find-in-page/search))
|
||||||
|
|
||||||
(if uploading?
|
(main {:route-match route-match
|
||||||
[:div.flex.items-center.justify-center.full-height-without-header
|
:margin-less-pages? margin-less-pages?
|
||||||
(ui/loading (t :sync/creating-remote-graph))]
|
:logged? logged?
|
||||||
(main {:route-match route-match
|
:home? home?
|
||||||
:margin-less-pages? margin-less-pages?
|
:route-name route-name
|
||||||
:logged? logged?
|
:light? light?
|
||||||
:home? home?
|
:db-restoring? db-restoring?
|
||||||
:route-name route-name
|
:main-content main-content'
|
||||||
:light? light?
|
:show-action-bar? show-action-bar?})]
|
||||||
:db-restoring? db-restoring?
|
|
||||||
:main-content main-content'
|
|
||||||
:show-action-bar? show-action-bar?}))]
|
|
||||||
|
|
||||||
(when window-controls?
|
(when window-controls?
|
||||||
(window-controls/container))
|
(window-controls/container))
|
||||||
|
|||||||
@@ -1,126 +0,0 @@
|
|||||||
(ns frontend.components.e2ee
|
|
||||||
(:require [clojure.string :as string]
|
|
||||||
[frontend.common.crypt :as crypt]
|
|
||||||
[frontend.context.i18n :refer [t]]
|
|
||||||
[frontend.state :as state]
|
|
||||||
[frontend.ui :as ui]
|
|
||||||
[frontend.util :as util]
|
|
||||||
[logseq.shui.hooks :as hooks]
|
|
||||||
[logseq.shui.ui :as shui]
|
|
||||||
[promesa.core :as p]
|
|
||||||
[io.factorhouse.hsx.core :as hsx]))
|
|
||||||
|
|
||||||
(hsx/defc e2ee-request-new-password
|
|
||||||
[password-promise]
|
|
||||||
(let [[password set-password!] (hooks/use-state "")
|
|
||||||
[password-confirm set-password-confirm!] (hooks/use-state "")
|
|
||||||
[matched? set-matched!] (hooks/use-state nil)
|
|
||||||
on-submit (fn []
|
|
||||||
(p/resolve! password-promise password)
|
|
||||||
(shui/dialog-close!))]
|
|
||||||
[:div.e2ee-password-modal-overlay
|
|
||||||
[:div.encryption-password.max-w-2xl.e2ee-password-modal-content.flex.flex-col.gap-8.p-4
|
|
||||||
[:div.text-2xl.font-medium (t :encryption/set-password-title)]
|
|
||||||
|
|
||||||
[:div.init-remote-pw-tips.space-x-4.hidden.sm:flex
|
|
||||||
[:div.flex-1.flex.items-center
|
|
||||||
[:span.px-3.flex (ui/icon "key")]
|
|
||||||
[:p (t :encryption/remember-password-rich)]]
|
|
||||||
|
|
||||||
[:div.flex-1.flex.items-center
|
|
||||||
[:span.px-3.flex (ui/icon "lock")]
|
|
||||||
[:p (t :encryption/cloud-password-rich)]]]
|
|
||||||
|
|
||||||
[:div.flex.flex-col.gap-4
|
|
||||||
(shui/toggle-password
|
|
||||||
{:placeholder (t :encryption/enter-password)
|
|
||||||
:value password
|
|
||||||
:on-change (fn [e] (set-password! (-> e .-target .-value)))
|
|
||||||
:on-blur (fn []
|
|
||||||
(when-not (string/blank? password-confirm)
|
|
||||||
(set-matched! (= password-confirm password))))})
|
|
||||||
|
|
||||||
[:div.flex.flex-col.gap-2
|
|
||||||
(shui/toggle-password
|
|
||||||
{:placeholder (t :encryption/enter-password-again)
|
|
||||||
:value password-confirm
|
|
||||||
:on-change (fn [e] (set-password-confirm! (-> e .-target .-value)))
|
|
||||||
:on-blur (fn [] (set-matched! (= password-confirm password)))})
|
|
||||||
|
|
||||||
(when (false? matched?)
|
|
||||||
[:div.text-warning.text-sm
|
|
||||||
(t :encryption/password-not-matched)])]
|
|
||||||
|
|
||||||
(shui/button
|
|
||||||
{:on-click on-submit
|
|
||||||
:disabled (or (string/blank? password)
|
|
||||||
(false? matched?))}
|
|
||||||
(t :ui/submit))]]]))
|
|
||||||
|
|
||||||
(hsx/defc e2ee-request-password
|
|
||||||
[password-promise]
|
|
||||||
(let [*password-input (hooks/use-ref nil)
|
|
||||||
[password set-password!] (hooks/use-state "")
|
|
||||||
on-submit (fn []
|
|
||||||
(p/resolve! password-promise
|
|
||||||
(-> (hooks/deref *password-input)
|
|
||||||
.-value))
|
|
||||||
(shui/dialog-close!))]
|
|
||||||
[:div.e2ee-password-modal-overlay
|
|
||||||
[:div.e2ee-password-modal-content.flex.flex-col.gap-8.p-4
|
|
||||||
[:div.text-2xl.font-medium (t :encryption/enter-password-title)]
|
|
||||||
[:div.flex.flex-col.gap-4
|
|
||||||
(shui/toggle-password
|
|
||||||
{:ref *password-input
|
|
||||||
:value password
|
|
||||||
:on-key-press (fn [e]
|
|
||||||
(when (= "Enter" (util/ekey e))
|
|
||||||
(on-submit)))
|
|
||||||
:on-change (fn [e]
|
|
||||||
(set-password! (-> e .-target .-value)))})
|
|
||||||
(shui/button
|
|
||||||
{:on-click on-submit
|
|
||||||
:disabled (string/blank? password)
|
|
||||||
:on-key-press (fn [e]
|
|
||||||
(when (= "Enter" (util/ekey e))
|
|
||||||
(on-submit)))}
|
|
||||||
(t :ui/submit))]]]))
|
|
||||||
|
|
||||||
(hsx/defc e2ee-password-to-decrypt-private-key
|
|
||||||
[encrypted-private-key private-key-promise]
|
|
||||||
(let [*password-input (hooks/use-ref nil)
|
|
||||||
[password set-password!] (hooks/use-state "")
|
|
||||||
[decrypt-fail? set-decrypt-fail!] (hooks/use-state false)
|
|
||||||
on-submit (fn []
|
|
||||||
(->
|
|
||||||
(p/let [entered-password (-> (hooks/deref *password-input)
|
|
||||||
.-value)
|
|
||||||
private-key (crypt/<decrypt-private-key entered-password encrypted-private-key)]
|
|
||||||
(state/<invoke-db-worker :thread-api/save-e2ee-password entered-password)
|
|
||||||
(p/resolve! private-key-promise private-key)
|
|
||||||
(shui/dialog-close!))
|
|
||||||
(p/catch (fn [e]
|
|
||||||
(when (= "decrypt-private-key" (ex-message e))
|
|
||||||
(set-decrypt-fail! true))))))]
|
|
||||||
[:div.e2ee-password-modal-overlay
|
|
||||||
[:div.e2ee-password-modal-content.flex.flex-col.gap-8.p-4
|
|
||||||
[:div.text-2xl.font-medium (t :encryption/enter-password-title)]
|
|
||||||
[:div.flex.flex-col.gap-4
|
|
||||||
[:div.flex.flex-col.gap-1
|
|
||||||
(shui/toggle-password
|
|
||||||
{:ref *password-input
|
|
||||||
:value password
|
|
||||||
:on-key-press (fn [e]
|
|
||||||
(when (= "Enter" (util/ekey e))
|
|
||||||
(on-submit)))
|
|
||||||
:on-change (fn [e]
|
|
||||||
(set-decrypt-fail! false)
|
|
||||||
(set-password! (-> e .-target .-value)))})
|
|
||||||
(when decrypt-fail? [:p.text-warning.text-sm (t :encryption/wrong-password)])]
|
|
||||||
(shui/button
|
|
||||||
{:on-click on-submit
|
|
||||||
:disabled (string/blank? password)
|
|
||||||
:on-key-press (fn [e]
|
|
||||||
(when (= "Enter" (util/ekey e))
|
|
||||||
(on-submit)))}
|
|
||||||
(t :ui/submit))]]]))
|
|
||||||
@@ -6,17 +6,13 @@
|
|||||||
[clojure.string :as string]
|
[clojure.string :as string]
|
||||||
[dommy.core :as d]
|
[dommy.core :as d]
|
||||||
[electron.ipc :as ipc]
|
[electron.ipc :as ipc]
|
||||||
[frontend.components.avatar :as avatar]
|
|
||||||
[frontend.components.block :as component-block]
|
[frontend.components.block :as component-block]
|
||||||
[frontend.components.email :as email-component]
|
[frontend.components.email :as email-component]
|
||||||
[frontend.components.export :as export]
|
[frontend.components.export :as export]
|
||||||
[frontend.components.page-menu :as page-menu]
|
[frontend.components.page-menu :as page-menu]
|
||||||
[frontend.components.plugins :as plugins]
|
[frontend.components.plugins :as plugins]
|
||||||
[frontend.components.repo :as repo]
|
|
||||||
[frontend.components.right-sidebar :as sidebar]
|
[frontend.components.right-sidebar :as sidebar]
|
||||||
[frontend.components.rtc.indicator :as rtc-indicator]
|
|
||||||
[frontend.components.server :as server]
|
[frontend.components.server :as server]
|
||||||
[frontend.components.settings :as settings]
|
|
||||||
[frontend.components.svg :as svg]
|
[frontend.components.svg :as svg]
|
||||||
[frontend.config :as config]
|
[frontend.config :as config]
|
||||||
[frontend.context.i18n :as i18n :refer [t]]
|
[frontend.context.i18n :as i18n :refer [t]]
|
||||||
@@ -32,7 +28,6 @@
|
|||||||
[frontend.state :as state]
|
[frontend.state :as state]
|
||||||
[frontend.ui :as ui]
|
[frontend.ui :as ui]
|
||||||
[frontend.util :as util]
|
[frontend.util :as util]
|
||||||
[frontend.util.email :as email-util]
|
|
||||||
[frontend.util.entity :as entity]
|
[frontend.util.entity :as entity]
|
||||||
[frontend.version :refer [version]]
|
[frontend.version :refer [version]]
|
||||||
[logseq.common.config :as common-config]
|
[logseq.common.config :as common-config]
|
||||||
@@ -55,86 +50,6 @@
|
|||||||
(t :nav/home)
|
(t :nav/home)
|
||||||
{:trigger-props {:as-child true}}))
|
{:trigger-props {:as-child true}}))
|
||||||
|
|
||||||
(defn current-local-uploadable-graph
|
|
||||||
[db-rtc-uuid]
|
|
||||||
(let [current-repo (state/get-current-repo)]
|
|
||||||
(some (fn [{:keys [url] :as graph}]
|
|
||||||
(when (and (= current-repo url)
|
|
||||||
(repo/local-uploadable-graph?
|
|
||||||
(assoc graph :rtc-graph?
|
|
||||||
(boolean db-rtc-uuid))))
|
|
||||||
graph))
|
|
||||||
(state/get-repos))))
|
|
||||||
|
|
||||||
(defn- use-db-rtc-uuid
|
|
||||||
[repo]
|
|
||||||
(let [[db-rtc-uuid set-db-rtc-uuid!] (hooks/use-state nil)]
|
|
||||||
(hooks/use-effect!
|
|
||||||
(fn []
|
|
||||||
(when repo
|
|
||||||
(p/let [graph-uuid (state/<invoke-db-worker :thread-api/get-rtc-graph-uuid repo)]
|
|
||||||
(set-db-rtc-uuid! graph-uuid)))
|
|
||||||
nil)
|
|
||||||
[repo])
|
|
||||||
db-rtc-uuid))
|
|
||||||
|
|
||||||
(defn- current-remote-rtc-graph
|
|
||||||
[current-repo rtc-graphs]
|
|
||||||
(some #(when (= current-repo (:url %)) %) rtc-graphs))
|
|
||||||
|
|
||||||
(defn- rtc-indicator-visible?
|
|
||||||
[{:keys [current-repo rtc-graphs db-rtc-uuid rtc-state logged-in? rtc-group?]}]
|
|
||||||
(let [remote-graph (current-remote-rtc-graph current-repo rtc-graphs)
|
|
||||||
remote-graph-uuid (some-> (:GraphUUID remote-graph) str)
|
|
||||||
state-graph-uuid (some-> (:graph-uuid rtc-state) str)]
|
|
||||||
(and current-repo
|
|
||||||
logged-in?
|
|
||||||
rtc-group?
|
|
||||||
remote-graph
|
|
||||||
(or db-rtc-uuid
|
|
||||||
(and (seq state-graph-uuid)
|
|
||||||
(= state-graph-uuid remote-graph-uuid))))))
|
|
||||||
|
|
||||||
(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}}))
|
|
||||||
|
|
||||||
(hsx/defc rtc-collaborators
|
|
||||||
[]
|
|
||||||
(let [rtc-graph-id (use-db-rtc-uuid (state/get-current-repo))
|
|
||||||
config (rfx/use-sub [:config])
|
|
||||||
online-users (rfx/use-sub [:rtc/state :online-users])]
|
|
||||||
(when rtc-graph-id
|
|
||||||
[:div.rtc-collaborators.flex.gap-1.text-sm.bg-gray-01.items-center
|
|
||||||
(shui/button-ghost-icon :user-plus
|
|
||||||
{:on-click #(shui/dialog-open!
|
|
||||||
(fn []
|
|
||||||
[:div.p-2.-mb-8
|
|
||||||
[:h1.text-3xl.-mt-2.-ml-2 (t :collaboration/members)]
|
|
||||||
(settings/settings-collaboration)])
|
|
||||||
{:id :rtc-collaborators})})
|
|
||||||
|
|
||||||
(when (seq online-users)
|
|
||||||
(for [{user-email :user/email
|
|
||||||
user-name :user/name
|
|
||||||
user-uuid :user/uuid} online-users
|
|
||||||
:when user-name
|
|
||||||
:let [key (str "rtc-user-" (or user-uuid user-email user-name))]]
|
|
||||||
^{:key key}
|
|
||||||
[:<>
|
|
||||||
(avatar/user-avatar
|
|
||||||
{:class "w-5 h-5"
|
|
||||||
:style {:app-region "no-drag"}
|
|
||||||
:title (email-util/display-email user-email config)
|
|
||||||
:name user-name
|
|
||||||
:uuid user-uuid
|
|
||||||
:fallback-props {:style {:font-size 11}}})]))])))
|
|
||||||
|
|
||||||
(hsx/defc left-menu-button
|
(hsx/defc left-menu-button
|
||||||
[{:keys [on-click]}]
|
[{:keys [on-click]}]
|
||||||
(ui/with-shortcut :ui/toggle-left-sidebar "bottom"
|
(ui/with-shortcut :ui/toggle-left-sidebar "bottom"
|
||||||
@@ -502,9 +417,6 @@
|
|||||||
(hsx/defc ^:large-vars/cleanup-todo header-aux
|
(hsx/defc ^:large-vars/cleanup-todo header-aux
|
||||||
[{:keys [current-repo default-home new-block-mode]}]
|
[{:keys [current-repo default-home new-block-mode]}]
|
||||||
(let [electron-mac? (and util/mac? (util/electron?))
|
(let [electron-mac? (and util/mac? (util/electron?))
|
||||||
rtc-graphs (rfx/use-sub [:rtc/graphs])
|
|
||||||
rtc-state (rfx/use-sub [:rtc/state])
|
|
||||||
db-rtc-uuid (use-db-rtc-uuid current-repo)
|
|
||||||
default-home-page (get-in (state/config-for-repo (rfx/use-sub [:config])
|
default-home-page (get-in (state/config-for-repo (rfx/use-sub [:config])
|
||||||
(state/get-current-repo))
|
(state/get-current-repo))
|
||||||
[:default-home :page] "")
|
[:default-home :page] "")
|
||||||
@@ -554,28 +466,9 @@
|
|||||||
[:div.flex.flex-1
|
[:div.flex.flex-1
|
||||||
(block-breadcrumb (state/get-current-page))]
|
(block-breadcrumb (state/get-current-page))]
|
||||||
[:div.flex.items-center
|
[:div.flex.items-center
|
||||||
(when (rtc-indicator-visible?
|
(recent-slider)
|
||||||
{:current-repo current-repo
|
|
||||||
:rtc-graphs rtc-graphs
|
|
||||||
:db-rtc-uuid db-rtc-uuid
|
|
||||||
:rtc-state rtc-state
|
|
||||||
:logged-in? (user-handler/logged-in?)
|
|
||||||
:rtc-group? (user-handler/rtc-group?)})
|
|
||||||
[:<>
|
|
||||||
(recent-slider)
|
|
||||||
^{:key (str "collab-" current-repo)}
|
|
||||||
[rtc-collaborators]
|
|
||||||
(rtc-indicator/indicator)])
|
|
||||||
|
|
||||||
(when (user-handler/logged-in?)
|
|
||||||
(rtc-indicator/downloading-detail))
|
|
||||||
(when (user-handler/logged-in?)
|
|
||||||
(rtc-indicator/uploading-detail))
|
|
||||||
(search-index-progress)
|
(search-index-progress)
|
||||||
|
|
||||||
(when-let [graph (current-local-uploadable-graph db-rtc-uuid)]
|
|
||||||
(local-graph-sync-button graph))
|
|
||||||
|
|
||||||
(when (and (not= (state/get-current-route) :home)
|
(when (and (not= (state/get-current-route) :home)
|
||||||
(not custom-home-page?))
|
(not custom-home-page?))
|
||||||
(home-button))
|
(home-button))
|
||||||
@@ -608,6 +501,5 @@
|
|||||||
|
|
||||||
(hsx/defc header
|
(hsx/defc header
|
||||||
[opts]
|
[opts]
|
||||||
(let [_user-groups (rfx/use-sub [:user/info :UserGroups])
|
(let [_user-groups (rfx/use-sub [:user/info :UserGroups])]
|
||||||
_rtc-running? (rfx/use-sub [:rtc/state :rtc-lock])]
|
|
||||||
(header-aux opts)))
|
(header-aux opts)))
|
||||||
|
|||||||
@@ -1,23 +1,17 @@
|
|||||||
(ns frontend.components.repo
|
(ns frontend.components.repo
|
||||||
(:require [clojure.string :as string]
|
(:require [clojure.string :as string]
|
||||||
[frontend.components.rtc.indicator :as rtc-indicator]
|
|
||||||
[frontend.config :as config]
|
[frontend.config :as config]
|
||||||
[frontend.context.i18n :as i18n :refer [t]]
|
[frontend.context.i18n :as i18n :refer [t]]
|
||||||
[frontend.handler.db-based.sync :as rtc-handler]
|
|
||||||
[frontend.handler.events.rtc-error :as rtc-error]
|
|
||||||
[frontend.handler.graph :as graph]
|
[frontend.handler.graph :as graph]
|
||||||
[frontend.handler.notification :as notification]
|
[frontend.handler.notification :as notification]
|
||||||
[frontend.handler.repo :as repo-handler]
|
[frontend.handler.repo :as repo-handler]
|
||||||
[frontend.handler.route :as route-handler]
|
[frontend.handler.route :as route-handler]
|
||||||
[frontend.handler.user :as user-handler]
|
|
||||||
[frontend.mobile.util :as mobile-util]
|
|
||||||
[frontend.rfx :as rfx]
|
[frontend.rfx :as rfx]
|
||||||
[frontend.state :as state]
|
[frontend.state :as state]
|
||||||
[frontend.ui :as ui]
|
[frontend.ui :as ui]
|
||||||
[frontend.util :as util]
|
[frontend.util :as util]
|
||||||
[frontend.util.text :as text-util]
|
[frontend.util.text :as text-util]
|
||||||
[goog.object :as gobj]
|
[goog.object :as gobj]
|
||||||
[lambdaisland.glogi :as log]
|
|
||||||
[logseq.common.util :as common-util]
|
[logseq.common.util :as common-util]
|
||||||
[logseq.shui.hooks :as hooks]
|
[logseq.shui.hooks :as hooks]
|
||||||
[logseq.shui.ui :as shui]
|
[logseq.shui.ui :as shui]
|
||||||
@@ -25,11 +19,6 @@
|
|||||||
[promesa.core :as p]
|
[promesa.core :as p]
|
||||||
[io.factorhouse.hsx.core :as hsx]))
|
[io.factorhouse.hsx.core :as hsx]))
|
||||||
|
|
||||||
(defn graph-sync-icon-name
|
|
||||||
[{:keys [remote? graph-e2ee?]}]
|
|
||||||
(when remote?
|
|
||||||
(if graph-e2ee? "lock" "cloud")))
|
|
||||||
|
|
||||||
(defn- repo-display-name
|
(defn- repo-display-name
|
||||||
[repo-url]
|
[repo-url]
|
||||||
(let [repo-name (text-util/get-graph-name-from-path repo-url)]
|
(let [repo-name (text-util/get-graph-name-from-path repo-url)]
|
||||||
@@ -37,85 +26,15 @@
|
|||||||
(config/db-graph-name repo-name)
|
(config/db-graph-name repo-name)
|
||||||
repo-name)))
|
repo-name)))
|
||||||
|
|
||||||
(defn local-uploadable-graph?
|
|
||||||
[{:keys [root remote? rtc-graph?]}]
|
|
||||||
(and (or root
|
|
||||||
(mobile-util/native-platform?))
|
|
||||||
(not remote?)
|
|
||||||
(not rtc-graph?)
|
|
||||||
(user-handler/logged-in?)
|
|
||||||
(user-handler/rtc-group?)))
|
|
||||||
|
|
||||||
(defn <invoke-db-worker
|
|
||||||
[op & args]
|
|
||||||
(apply state/<invoke-db-worker op args))
|
|
||||||
|
|
||||||
(defn- handle-cloud-graph-error!
|
|
||||||
[error]
|
|
||||||
(log/error :db-sync/cloud-graph-failed error)
|
|
||||||
(when (rtc-error/e2ee-decrypt-failed? error)
|
|
||||||
(notification/show! (t :encryption/wrong-password) :error false)))
|
|
||||||
|
|
||||||
(defn- graph-e2ee-enabled?
|
|
||||||
[{:keys [url graph-e2ee?] :as graph}]
|
|
||||||
(cond
|
|
||||||
(contains? graph :graph-e2ee?)
|
|
||||||
(p/resolved (true? graph-e2ee?))
|
|
||||||
|
|
||||||
(= url (state/get-current-repo))
|
|
||||||
(p/let [e2ee? (<invoke-db-worker :thread-api/get-key-value url :logseq.kv/graph-rtc-e2ee?)]
|
|
||||||
(if (nil? e2ee?) true (true? e2ee?)))
|
|
||||||
|
|
||||||
:else
|
|
||||||
(p/resolved true)))
|
|
||||||
|
|
||||||
(defn- <ensure-current-graph-for-upload!
|
|
||||||
[repo]
|
|
||||||
(if (= repo (state/get-current-repo))
|
|
||||||
(p/resolved nil)
|
|
||||||
(state/pub-event! [:graph/switch repo])))
|
|
||||||
|
|
||||||
(defn upload-local-graph-with-confirm!
|
|
||||||
[{:keys [url] :as graph}]
|
|
||||||
(let [graph-name (config/db-graph-name url)
|
|
||||||
dialog-config {:cancel-label (t :ui/cancel)
|
|
||||||
:ok-label (t :ui/confirm)}]
|
|
||||||
(-> (shui/dialog-confirm!
|
|
||||||
[:p.font-medium.mb-6 (t :graph/upload-local-confirm-desc graph-name)]
|
|
||||||
dialog-config)
|
|
||||||
(p/then
|
|
||||||
(fn []
|
|
||||||
(p/let [_ (<ensure-current-graph-for-upload! url)
|
|
||||||
graph-e2ee? (graph-e2ee-enabled? graph)]
|
|
||||||
(let [mobile? (util/mobile?)
|
|
||||||
hide-upload-log! (fn []
|
|
||||||
(when mobile?
|
|
||||||
(shui/popup-hide! :rtc-graph-upload-log)))]
|
|
||||||
(when mobile?
|
|
||||||
(shui/popup-show! nil
|
|
||||||
(fn []
|
|
||||||
(rtc-indicator/uploading-logs))
|
|
||||||
{:id :rtc-graph-upload-log}))
|
|
||||||
(rtc-indicator/on-upload-finished-task
|
|
||||||
hide-upload-log!)
|
|
||||||
(-> (rtc-handler/<rtc-upload-graph! url graph-e2ee?)
|
|
||||||
(p/catch (fn [error]
|
|
||||||
(handle-cloud-graph-error! error)
|
|
||||||
(p/rejected error)))
|
|
||||||
(p/finally hide-upload-log!)))))))))
|
|
||||||
|
|
||||||
(hsx/defc normalized-graph-label
|
(hsx/defc normalized-graph-label
|
||||||
[{:keys [url remote?] :as graph} on-click]
|
[{:keys [url] :as graph} on-click]
|
||||||
(when graph
|
(when graph
|
||||||
[:span.flex.items-center
|
[:span.flex.items-center
|
||||||
(let [local-dir (config/get-local-dir url)
|
(let [local-dir (config/get-local-dir url)
|
||||||
graph-name (text-util/get-graph-name-from-path url)]
|
graph-name (text-util/get-graph-name-from-path url)]
|
||||||
[:<>
|
[:a.flex.items-center {:title local-dir
|
||||||
[:a.flex.items-center {:title local-dir
|
:on-click #(on-click graph)}
|
||||||
:on-click #(on-click graph)}
|
[:span graph-name]])]))
|
||||||
[:span graph-name]
|
|
||||||
(when remote?
|
|
||||||
[:strong.px-1.flex.items-center (ui/icon (graph-sync-icon-name graph))])]])]))
|
|
||||||
|
|
||||||
(defn sort-repos-with-metadata-local
|
(defn sort-repos-with-metadata-local
|
||||||
[repos]
|
[repos]
|
||||||
@@ -183,28 +102,19 @@
|
|||||||
(when target
|
(when target
|
||||||
(state/pub-event! [:graph/open-new-window target])))))
|
(state/pub-event! [:graph/open-new-window target])))))
|
||||||
|
|
||||||
(hsx/defc ^:large-vars/cleanup-todo repos-inner
|
(hsx/defc repos-inner
|
||||||
"Graph list in `All graphs` page"
|
"Graph list in `All graphs` page"
|
||||||
[repos]
|
[repos]
|
||||||
(for [{:keys [root url remote? graph-e2ee? GraphUUID GraphSchemaVersion GraphName created-at last-seen-at] :as repo}
|
(for [{:keys [root url created-at last-seen-at] :as repo}
|
||||||
(sort-repos-with-metadata-local repos)
|
(sort-repos-with-metadata-local repos)]
|
||||||
:let [graph-name (config/db-graph-name url)]]
|
[:div.flex.justify-between.mb-2.items-center.group {:key url
|
||||||
[:div.flex.justify-between.mb-2.items-center.group {:key (or url GraphUUID)
|
|
||||||
"data-testid" url}
|
"data-testid" url}
|
||||||
[:div
|
[:div
|
||||||
[:span.flex.items-center.gap-1
|
[:span.flex.items-center.gap-1
|
||||||
(normalized-graph-label repo
|
(normalized-graph-label repo
|
||||||
(fn []
|
(fn []
|
||||||
(when-not (state/get-state :rtc/downloading-graph-uuid)
|
(when root
|
||||||
(cond
|
(state/pub-event! [:graph/switch url]))))]
|
||||||
root ; exists locally
|
|
||||||
(state/pub-event! [:graph/switch url])
|
|
||||||
|
|
||||||
remote?
|
|
||||||
(state/pub-event! [:rtc/download-remote-graph GraphName GraphUUID GraphSchemaVersion graph-e2ee?])
|
|
||||||
|
|
||||||
:else
|
|
||||||
nil))))]
|
|
||||||
(when-let [time (some-> (or last-seen-at created-at) (safe-locale-date))]
|
(when-let [time (some-> (or last-seen-at created-at) (safe-locale-date))]
|
||||||
[:small.text-muted-foreground (t :graph/last-opened-at-label time)])]
|
[:small.text-muted-foreground (t :graph/last-opened-at-label time)])]
|
||||||
|
|
||||||
@@ -215,120 +125,42 @@
|
|||||||
{:on-click #(open-repo-folder! repo)}
|
{:on-click #(open-repo-folder! repo)}
|
||||||
(shui/tabler-icon "folder-pin") [:span.pl-1 root]])
|
(shui/tabler-icon "folder-pin") [:span.pl-1 root]])
|
||||||
|
|
||||||
(let [manager? (user-handler/manager? url)
|
(shui/dropdown-menu
|
||||||
dialog-config {:cancel-label (t :ui/cancel)
|
(shui/dropdown-menu-trigger
|
||||||
:ok-label (t :ui/confirm)}]
|
{:asChild true}
|
||||||
(shui/dropdown-menu
|
(shui/button
|
||||||
(shui/dropdown-menu-trigger
|
{:variant "ghost"
|
||||||
{:asChild true}
|
:class "graph-action-btn !px-1"
|
||||||
(shui/button
|
:size :sm}
|
||||||
{:variant "ghost"
|
(ui/icon "dots" {:size 15})))
|
||||||
:class "graph-action-btn !px-1"
|
(shui/dropdown-menu-content
|
||||||
:size :sm}
|
{:align "end"}
|
||||||
(ui/icon "dots" {:size 15})))
|
(when (open-in-another-tab-action? repo)
|
||||||
(shui/dropdown-menu-content
|
(shui/dropdown-menu-item
|
||||||
{:align "end"}
|
{:key "open-in-another-tab"
|
||||||
(when (open-in-another-tab-action? repo)
|
:class "open-in-another-tab-menu-item"
|
||||||
(shui/dropdown-menu-item
|
:on-click #(open-graph-in-another-tab! repo)}
|
||||||
{:key "open-in-another-tab"
|
(t :graph/open-in-another-tab-action)))
|
||||||
:class "open-in-another-tab-menu-item"
|
|
||||||
:on-click #(open-graph-in-another-tab! repo)}
|
|
||||||
(t :graph/open-in-another-tab-action)))
|
|
||||||
|
|
||||||
(when root
|
(when root
|
||||||
(let [disabled? (not (can-delete-local-graph? repo))]
|
(let [disabled? (not (can-delete-local-graph? repo))]
|
||||||
(shui/dropdown-menu-item
|
|
||||||
{:key "delete-locally"
|
|
||||||
:class "delete-local-graph-menu-item"
|
|
||||||
:disabled disabled?
|
|
||||||
:on-click #(when-not disabled?
|
|
||||||
(delete-local-graph! repo))}
|
|
||||||
(t :graph/delete-local-action))))
|
|
||||||
|
|
||||||
(when (and root
|
|
||||||
(user-handler/logged-in?)
|
|
||||||
(user-handler/rtc-group?)
|
|
||||||
(not remote?)
|
|
||||||
(= url (state/get-current-repo)))
|
|
||||||
(shui/dropdown-menu-item
|
(shui/dropdown-menu-item
|
||||||
{:key "logseq-sync"
|
{:key "delete-locally"
|
||||||
:class "use-logseq-sync-menu-item"
|
:class "delete-local-graph-menu-item"
|
||||||
:on-click #(upload-local-graph-with-confirm! repo)}
|
:disabled disabled?
|
||||||
(t :graph/use-sync-beta)))
|
:on-click #(when-not disabled?
|
||||||
|
(delete-local-graph! repo))}
|
||||||
(when (and remote?
|
(t :graph/delete-local-action))))))]]]))
|
||||||
manager?)
|
|
||||||
(shui/dropdown-menu-item
|
|
||||||
{:key "delete-remotely"
|
|
||||||
:class "delete-remote-graph-menu-item"
|
|
||||||
:on-click (fn []
|
|
||||||
(let [prompt-str (t :graph/delete-server-confirm-desc graph-name)]
|
|
||||||
(-> (shui/dialog-confirm!
|
|
||||||
(assoc dialog-config
|
|
||||||
:title prompt-str
|
|
||||||
:description (t :graph/delete-warning)))
|
|
||||||
(p/then
|
|
||||||
(fn []
|
|
||||||
(state/set-state! :rtc/loading-graphs? true)
|
|
||||||
(when (= (state/get-current-repo) repo)
|
|
||||||
(rtc-handler/<rtc-stop!))
|
|
||||||
(p/do! (rtc-handler/<rtc-delete-graph! GraphUUID GraphSchemaVersion)
|
|
||||||
(state/set-state! :rtc/loading-graphs? false)
|
|
||||||
(rtc-handler/<get-remote-graphs)))))))}
|
|
||||||
(t :graph/delete-server-action)))
|
|
||||||
|
|
||||||
(when (and remote? (not manager?))
|
|
||||||
(shui/dropdown-menu-item
|
|
||||||
{:key "leave-shared-graph"
|
|
||||||
:class "leave-shared-graph-menu-item"
|
|
||||||
:on-click (fn []
|
|
||||||
(let [prompt-str (t :graph/leave-confirm-desc)]
|
|
||||||
(-> (shui/dialog-confirm!
|
|
||||||
[:p.font-medium.-my-4 prompt-str]
|
|
||||||
dialog-config)
|
|
||||||
(p/then
|
|
||||||
(fn []
|
|
||||||
(state/set-state! :rtc/loading-graphs? true)
|
|
||||||
(when (= (state/get-current-repo) repo)
|
|
||||||
(rtc-handler/<rtc-stop!))
|
|
||||||
(-> (rtc-handler/<rtc-leave-graph! GraphUUID)
|
|
||||||
(p/then (fn []
|
|
||||||
(notification/show! (t :graph/left) :success)
|
|
||||||
(rtc-handler/<get-remote-graphs)))
|
|
||||||
(p/catch (fn [e]
|
|
||||||
(notification/show! (t :graph/leave-error) :error)
|
|
||||||
(log/error :db-sync/leave-graph-failed
|
|
||||||
{:error e
|
|
||||||
:graph-uuid GraphUUID})))
|
|
||||||
(p/finally (fn []
|
|
||||||
(state/set-state! :rtc/loading-graphs? false)))))))))}
|
|
||||||
(t :graph/leave-action))))))]]]))
|
|
||||||
|
|
||||||
(hsx/defc repos-cp
|
(hsx/defc repos-cp
|
||||||
[]
|
[]
|
||||||
(let [login? (boolean (rfx/use-sub [:auth/id-token]))
|
(let [repos (rfx/use-sub [:me :repos])
|
||||||
repos (rfx/use-sub [:me :repos])
|
|
||||||
repos (util/distinct-by :url repos)
|
repos (util/distinct-by :url repos)
|
||||||
remotes (rfx/use-sub [:rtc/graphs])
|
repos (remove #(= (:url %) config/demo-repo) repos)
|
||||||
remotes-loading? (rfx/use-sub [:rtc/loading-graphs?])
|
repos (filter (fn [item]
|
||||||
repos (->> (if (and login? (seq remotes))
|
;; use `config/db-based-graph?` to avoid loading old file graphs
|
||||||
(repo-handler/combine-local-&-remote-graphs repos remotes)
|
(config/db-based-graph? (:url item)))
|
||||||
repos)
|
repos)]
|
||||||
(util/distinct-by :url))
|
|
||||||
repos (cond->>
|
|
||||||
(remove #(= (:url %) config/demo-repo) repos)
|
|
||||||
true
|
|
||||||
(filter (fn [item]
|
|
||||||
;; use `config/db-based-graph?` to avoid loading old file graphs
|
|
||||||
(config/db-based-graph? (:url item)))))
|
|
||||||
{remote-graphs true local-graphs false} (group-by (comp boolean :remote?) repos)
|
|
||||||
{own-graphs true shared-graphs false}
|
|
||||||
(group-by (fn [graph] (= "manager" (:graph<->user-user-type graph))) remote-graphs)]
|
|
||||||
(hooks/use-effect!
|
|
||||||
(fn []
|
|
||||||
(when (and login? (user-handler/rtc-group?))
|
|
||||||
(rtc-handler/<get-remote-graphs)))
|
|
||||||
[login?])
|
|
||||||
[:div#graphs
|
[:div#graphs
|
||||||
(when-not (util/capacitor?)
|
(when-not (util/capacitor?)
|
||||||
[:h1.title (t :graph/all-graphs)])
|
[:h1.title (t :graph/all-graphs)])
|
||||||
@@ -344,78 +176,32 @@
|
|||||||
|
|
||||||
[:div
|
[:div
|
||||||
[:h2.text-lg.font-medium.mb-4 (t :graph/local-graphs)]
|
[:h2.text-lg.font-medium.mb-4 (t :graph/local-graphs)]
|
||||||
(when (seq local-graphs)
|
(when (seq repos)
|
||||||
(repos-inner local-graphs))]
|
(repos-inner repos))]]]))
|
||||||
|
|
||||||
(when (and (user-handler/rtc-group?)
|
(defn- repos-dropdown-links [repos current-repo & {:as opts}]
|
||||||
(seq remote-graphs)
|
|
||||||
login?)
|
|
||||||
[:<>
|
|
||||||
(when (seq own-graphs)
|
|
||||||
[:div
|
|
||||||
[:hr.mt-8]
|
|
||||||
[:div.flex.align-items.justify-between
|
|
||||||
[:h2.text-lg.font-medium.mb-4 (t :graph/remote-graphs)]
|
|
||||||
[:div
|
|
||||||
(ui/button
|
|
||||||
[:span.flex.items-center (t :ui/refresh)
|
|
||||||
(when remotes-loading? [:small.pl-2 (ui/loading nil)])]
|
|
||||||
:background "gray"
|
|
||||||
:disabled remotes-loading?
|
|
||||||
:on-click (fn [] (rtc-handler/<get-remote-graphs)))]]
|
|
||||||
(repos-inner own-graphs)])
|
|
||||||
|
|
||||||
(when (seq shared-graphs)
|
|
||||||
[:div
|
|
||||||
[:hr.mt-8]
|
|
||||||
[:div.flex.align-items.justify-between
|
|
||||||
[:h2.text-lg.font-medium.mb-4 (t :graph/shared-graphs)]]
|
|
||||||
(repos-inner shared-graphs)])])]]))
|
|
||||||
|
|
||||||
(defn- repos-dropdown-links [repos current-repo downloading-graph-id & {:as opts}]
|
|
||||||
(let [switch-repos (if-not (nil? current-repo)
|
(let [switch-repos (if-not (nil? current-repo)
|
||||||
(remove (fn [repo] (= current-repo (:url repo))) repos) repos) ; exclude current repo
|
(remove (fn [repo] (= current-repo (:url repo))) repos) repos) ; exclude current repo
|
||||||
repo-links (mapv
|
repo-links (mapv
|
||||||
(fn [{:keys [url remote? graph-e2ee? rtc-graph? GraphName GraphSchemaVersion GraphUUID graph-ready-for-use?] :as graph}]
|
(fn [{:keys [url] :as graph}]
|
||||||
(let [repo-url url
|
(let [repo-url url
|
||||||
short-repo-name (text-util/get-graph-name-from-path repo-url)
|
short-repo-name (text-util/get-graph-name-from-path repo-url)]
|
||||||
downloading? (and downloading-graph-id (= GraphUUID downloading-graph-id))
|
|
||||||
ready-for-use? (not= false graph-ready-for-use?)
|
|
||||||
title (str "<" GraphName "> #" GraphUUID)]
|
|
||||||
(when short-repo-name
|
(when short-repo-name
|
||||||
{:title [:span.flex.items-center.title-wrap short-repo-name
|
{:title [:span.flex.items-center.title-wrap short-repo-name]
|
||||||
(when remote? [:span.pl-1.flex.items-center
|
|
||||||
{:title title}
|
|
||||||
(ui/icon (if graph-e2ee? "lock" "cloud") {:size 18})
|
|
||||||
(when-not ready-for-use?
|
|
||||||
[:span.opacity.text-sm.pl-1 (t :graph/preparing)])
|
|
||||||
(when downloading?
|
|
||||||
[:span.opacity.text-sm.pl-1 (t :graph/downloading)])])]
|
|
||||||
:hover-detail repo-url ;; show full path on hover
|
:hover-detail repo-url ;; show full path on hover
|
||||||
:options {:on-click
|
:options {:on-click
|
||||||
(fn [e]
|
(fn [e]
|
||||||
(when (and ready-for-use? (not downloading?))
|
(when-let [on-click (:on-click opts)]
|
||||||
(when-let [on-click (:on-click opts)]
|
(on-click e))
|
||||||
(on-click e))
|
(if (and (gobj/get e "shiftKey") (:root graph))
|
||||||
(if (and (gobj/get e "shiftKey") (:root graph))
|
(if (util/electron?)
|
||||||
(if (util/electron?)
|
(state/pub-event! [:graph/open-new-window url])
|
||||||
(state/pub-event! [:graph/open-new-window url])
|
(if-let [target (graph-open-new-window-target graph)]
|
||||||
(if-let [target (graph-open-new-window-target graph)]
|
(state/pub-event! [:graph/open-new-window target])
|
||||||
(state/pub-event! [:graph/open-new-window target])
|
(p/let [target (<graph-open-new-window-target graph)]
|
||||||
(p/let [target (<graph-open-new-window-target graph)]
|
(when target
|
||||||
(when target
|
(state/pub-event! [:graph/open-new-window target])))))
|
||||||
(state/pub-event! [:graph/open-new-window target])))))
|
(state/pub-event! [:graph/switch url])))}})))
|
||||||
(cond
|
|
||||||
;; exists locally?
|
|
||||||
(or (:root graph) (not rtc-graph?))
|
|
||||||
(state/pub-event! [:graph/switch url])
|
|
||||||
|
|
||||||
(and rtc-graph? remote?)
|
|
||||||
(state/pub-event!
|
|
||||||
[:rtc/download-remote-graph GraphName GraphUUID GraphSchemaVersion graph-e2ee?])
|
|
||||||
|
|
||||||
:else
|
|
||||||
nil))))}})))
|
|
||||||
switch-repos)]
|
switch-repos)]
|
||||||
(->> repo-links (remove nil?))))
|
(->> repo-links (remove nil?))))
|
||||||
|
|
||||||
@@ -458,35 +244,13 @@
|
|||||||
[& {:keys [contentid footer?] :as opts
|
[& {:keys [contentid footer?] :as opts
|
||||||
:or {footer? true}}]
|
:or {footer? true}}]
|
||||||
(let [current-repo (rfx/use-sub [:git/current-repo])
|
(let [current-repo (rfx/use-sub [:git/current-repo])
|
||||||
login? (boolean (rfx/use-sub [:auth/id-token]))
|
|
||||||
repos (rfx/use-sub [:me :repos])
|
repos (rfx/use-sub [:me :repos])
|
||||||
rtc-graphs (rfx/use-sub [:rtc/graphs])
|
|
||||||
downloading-graph-id (rfx/use-sub [:rtc/downloading-graph-uuid])
|
|
||||||
remotes-loading? (rfx/use-sub [:rtc/loading-graphs?])
|
|
||||||
repos (sort-repos-with-metadata-local repos)
|
repos (sort-repos-with-metadata-local repos)
|
||||||
repos (->>
|
repos (util/distinct-by :url repos)
|
||||||
(if (and (seq rtc-graphs) login?)
|
items-fn #(repos-dropdown-links repos current-repo opts)
|
||||||
(repo-handler/combine-local-&-remote-graphs repos rtc-graphs)
|
|
||||||
repos)
|
|
||||||
|
|
||||||
(util/distinct-by :url))
|
|
||||||
items-fn #(repos-dropdown-links repos current-repo downloading-graph-id opts)
|
|
||||||
header-fn #(when (> (count repos) 1) ; show switch to if there are multiple repos
|
header-fn #(when (> (count repos) 1) ; show switch to if there are multiple repos
|
||||||
[:div.font-medium.md:text-sm.md:opacity-50.p-2.flex.flex-row.justify-between.items-center
|
[:div.font-medium.md:text-sm.md:opacity-50.p-2.flex.flex-row.justify-between.items-center
|
||||||
[:h4.pb-1 (t :graph.switch/prompt)]
|
[:h4.pb-1 (t :graph.switch/prompt)]])]
|
||||||
|
|
||||||
(when login?
|
|
||||||
(if remotes-loading?
|
|
||||||
(ui/loading "")
|
|
||||||
(shui/button
|
|
||||||
{:variant :ghost
|
|
||||||
:size :sm
|
|
||||||
:title (t :graph/refresh-remote-graphs)
|
|
||||||
:class "!h-6 !px-1 relative right-[-4px]"
|
|
||||||
:on-click (fn []
|
|
||||||
(rtc-handler/<get-remote-graphs))}
|
|
||||||
(ui/icon "refresh" {:size 15}))))])
|
|
||||||
_remote? (and current-repo (:remote? (first (filter #(= current-repo (:url %)) repos))))]
|
|
||||||
|
|
||||||
[:div
|
[:div
|
||||||
{:class (when (<= (count repos) 1) "no-repos")}
|
{:class (when (<= (count repos) 1) "no-repos")}
|
||||||
@@ -546,7 +310,6 @@
|
|||||||
(let [current-repo (rfx/use-sub [:git/current-repo])
|
(let [current-repo (rfx/use-sub [:git/current-repo])
|
||||||
user-repos (rfx/use-sub [:me :repos])
|
user-repos (rfx/use-sub [:me :repos])
|
||||||
current-repo' (some->> user-repos (medley/find-first #(= current-repo (:url %))))
|
current-repo' (some->> user-repos (medley/find-first #(= current-repo (:url %))))
|
||||||
remote? (:remote? current-repo')
|
|
||||||
short-repo-name (if current-repo
|
short-repo-name (if current-repo
|
||||||
(repo-display-name current-repo)
|
(repo-display-name current-repo)
|
||||||
(t :graph.switch/select-prompt))
|
(t :graph.switch/select-prompt))
|
||||||
@@ -577,7 +340,7 @@
|
|||||||
[:a.item.flex.items-center.gap-1.select-none
|
[:a.item.flex.items-center.gap-1.select-none
|
||||||
selector-opts
|
selector-opts
|
||||||
[:span.thumb
|
[:span.thumb
|
||||||
(shui/tabler-icon (if remote? "cloud" "topology-star") {:size 16})]
|
(shui/tabler-icon "topology-star" {:size 16})]
|
||||||
[:strong short-repo-name]
|
[:strong short-repo-name]
|
||||||
(shui/tabler-icon "selector" {:size 18})]]))
|
(shui/tabler-icon "selector" {:size 18})]]))
|
||||||
|
|
||||||
@@ -623,28 +386,9 @@
|
|||||||
(string/includes? graph-name "+")
|
(string/includes? graph-name "+")
|
||||||
(string/includes? graph-name "/")))
|
(string/includes? graph-name "/")))
|
||||||
|
|
||||||
(defn ensure-e2ee-rsa-key-for-cloud!
|
|
||||||
[{:keys [cloud? graph-e2ee? refresh-token token user-uuid e2ee-rsa-key-ensured?]} set-e2ee-rsa-key-ensured?]
|
|
||||||
(if (and cloud? graph-e2ee? refresh-token token user-uuid (not e2ee-rsa-key-ensured?))
|
|
||||||
(-> (p/do!
|
|
||||||
(state/pub-event! [:rtc/sync-app-state])
|
|
||||||
(<invoke-db-worker :thread-api/set-db-sync-config
|
|
||||||
{:enabled? true
|
|
||||||
:ws-url (config/db-sync-ws-url)
|
|
||||||
:http-base (config/db-sync-http-base)})
|
|
||||||
(p/let [rsa-key-pair (<invoke-db-worker :thread-api/db-sync-ensure-user-rsa-keys)]
|
|
||||||
(set-e2ee-rsa-key-ensured? (some? rsa-key-pair))))
|
|
||||||
(p/catch (fn [e]
|
|
||||||
(log/error :db-sync/ensure-user-rsa-keys-failed e)
|
|
||||||
e)))
|
|
||||||
(p/resolved nil)))
|
|
||||||
|
|
||||||
(hsx/defc new-db-graph-inner
|
(hsx/defc new-db-graph-inner
|
||||||
[rtc-group?]
|
[]
|
||||||
(let [[creating-db? set-creating-db?] (hooks/use-state false)
|
(let [[creating-db? set-creating-db?] (hooks/use-state false)
|
||||||
[cloud? set-cloud?] (hooks/use-state false)
|
|
||||||
[graph-e2ee? set-graph-e2ee?] (hooks/use-state true)
|
|
||||||
[e2ee-rsa-key-ensured? set-e2ee-rsa-key-ensured?] (hooks/use-state nil)
|
|
||||||
input-ref (hooks/create-ref)
|
input-ref (hooks/create-ref)
|
||||||
new-db-f (fn new-db-f
|
new-db-f (fn new-db-f
|
||||||
[graph-name]
|
[graph-name]
|
||||||
@@ -654,15 +398,7 @@
|
|||||||
(invalid-graph-name-warning)
|
(invalid-graph-name-warning)
|
||||||
(do
|
(do
|
||||||
(set-creating-db? true)
|
(set-creating-db? true)
|
||||||
(p/let [repo (repo-handler/new-db! graph-name
|
(p/let [_repo (repo-handler/new-db! graph-name)]
|
||||||
{:creating-remote-graph? cloud?})]
|
|
||||||
(when cloud?
|
|
||||||
(->
|
|
||||||
(p/do
|
|
||||||
(rtc-handler/<rtc-create-graph-and-start-sync! repo graph-e2ee?))
|
|
||||||
(p/catch handle-cloud-graph-error!)
|
|
||||||
(p/finally (fn []
|
|
||||||
(set-creating-db? false)))))
|
|
||||||
(shui/dialog-close!))))))
|
(shui/dialog-close!))))))
|
||||||
submit! (fn submit!
|
submit! (fn submit!
|
||||||
[^js e click?]
|
[^js e click?]
|
||||||
@@ -675,21 +411,6 @@
|
|||||||
(js/setTimeout #(.focus input) 32)))
|
(js/setTimeout #(.focus input) 32)))
|
||||||
[])
|
[])
|
||||||
|
|
||||||
(hooks/use-effect!
|
|
||||||
(fn []
|
|
||||||
(let [token (state/get-auth-id-token)
|
|
||||||
user-uuid (user-handler/user-uuid)
|
|
||||||
refresh-token (state/get-auth-refresh-token)]
|
|
||||||
(ensure-e2ee-rsa-key-for-cloud!
|
|
||||||
{:cloud? cloud?
|
|
||||||
:graph-e2ee? graph-e2ee?
|
|
||||||
:refresh-token refresh-token
|
|
||||||
:token token
|
|
||||||
:user-uuid user-uuid
|
|
||||||
:e2ee-rsa-key-ensured? e2ee-rsa-key-ensured?}
|
|
||||||
set-e2ee-rsa-key-ensured?)))
|
|
||||||
[cloud? graph-e2ee?])
|
|
||||||
|
|
||||||
[:div.new-graph.flex.flex-col.gap-4.p-1.pt-2
|
[:div.new-graph.flex.flex-col.gap-4.p-1.pt-2
|
||||||
(shui/input
|
(shui/input
|
||||||
{:disabled creating-db?
|
{:disabled creating-db?
|
||||||
@@ -697,32 +418,8 @@
|
|||||||
:placeholder (t :graph/name-placeholder)
|
:placeholder (t :graph/name-placeholder)
|
||||||
:on-key-down submit!
|
:on-key-down submit!
|
||||||
:autoComplete "off"})
|
:autoComplete "off"})
|
||||||
(when rtc-group?
|
|
||||||
[:div.flex.flex-col
|
|
||||||
[:div.flex.flex-row.items-center.gap-1
|
|
||||||
(shui/checkbox
|
|
||||||
{:id "rtc-sync"
|
|
||||||
:checked cloud?
|
|
||||||
:on-checked-change
|
|
||||||
(fn []
|
|
||||||
(let [v (not cloud?)]
|
|
||||||
(set-cloud? v)))})
|
|
||||||
[:label.opacity-70.text-sm
|
|
||||||
{:for "rtc-sync"}
|
|
||||||
(t :graph/use-sync-label)]
|
|
||||||
(when cloud?
|
|
||||||
[:div.flex.flex-row.items-center.gap-1.ml-3
|
|
||||||
(shui/checkbox
|
|
||||||
{:id "rtc-graph-e2ee"
|
|
||||||
:checked graph-e2ee?
|
|
||||||
:on-checked-change
|
|
||||||
(fn []
|
|
||||||
(set-graph-e2ee? (not graph-e2ee?)))})
|
|
||||||
[:label.opacity-70.text-sm
|
|
||||||
{:for "rtc-graph-e2ee"}
|
|
||||||
(t :graph/encrypt-data-label)]])]])
|
|
||||||
(shui/button
|
(shui/button
|
||||||
{:disabled (and cloud? graph-e2ee? (not e2ee-rsa-key-ensured?))
|
{:disabled creating-db?
|
||||||
:on-click #(submit! % true)
|
:on-click #(submit! % true)
|
||||||
:on-key-down submit!}
|
:on-key-down submit!}
|
||||||
(if creating-db?
|
(if creating-db?
|
||||||
@@ -731,5 +428,4 @@
|
|||||||
|
|
||||||
(hsx/defc new-db-graph
|
(hsx/defc new-db-graph
|
||||||
[]
|
[]
|
||||||
(let [rtc-group? (user-handler/rtc-group?)]
|
(new-db-graph-inner))
|
||||||
(new-db-graph-inner rtc-group?)))
|
|
||||||
|
|||||||
@@ -15,7 +15,6 @@
|
|||||||
[frontend.date :as date]
|
[frontend.date :as date]
|
||||||
[frontend.db.async :as db-async]
|
[frontend.db.async :as db-async]
|
||||||
[frontend.db.hooks :as db-hooks]
|
[frontend.db.hooks :as db-hooks]
|
||||||
[frontend.db.rtc.debug-ui :as rtc-debug-ui]
|
|
||||||
[frontend.handler.editor :as editor-handler]
|
[frontend.handler.editor :as editor-handler]
|
||||||
[frontend.handler.route :as route-handler]
|
[frontend.handler.route :as route-handler]
|
||||||
[frontend.handler.ui :as ui-handler]
|
[frontend.handler.ui :as ui-handler]
|
||||||
@@ -155,10 +154,6 @@
|
|||||||
[[:.flex.items-center (ui/icon "command" {:class "text-md mr-2"}) (t :help.shortcuts/label)]
|
[[:.flex.items-center (ui/icon "command" {:class "text-md mr-2"}) (t :help.shortcuts/label)]
|
||||||
(shortcut-settings)]
|
(shortcut-settings)]
|
||||||
|
|
||||||
:rtc
|
|
||||||
[[:.flex.items-center (ui/icon "cloud" {:class "text-md mr-2"}) "(Dev) RTC"]
|
|
||||||
(rtc-debug-ui/rtc-debug-ui)]
|
|
||||||
|
|
||||||
:undo-redo
|
:undo-redo
|
||||||
[[:.flex.items-center (ui/icon "rotate-clockwise" {:class "text-md mr-2"}) "(Dev) Undo/Redo"]
|
[[:.flex.items-center (ui/icon "rotate-clockwise" {:class "text-md mr-2"}) "(Dev) Undo/Redo"]
|
||||||
(undo-redo-debug-ui/undo-redo-debug-ui)]
|
(undo-redo-debug-ui/undo-redo-debug-ui)]
|
||||||
@@ -189,9 +184,6 @@
|
|||||||
(defn dev-sidebar-items
|
(defn dev-sidebar-items
|
||||||
[developer-mode?]
|
[developer-mode?]
|
||||||
(cond-> []
|
(cond-> []
|
||||||
(and developer-mode? (not config/publishing?))
|
|
||||||
(conj {:db-id "rtc" :block-type :rtc :label "(Dev) RTC"})
|
|
||||||
|
|
||||||
developer-mode?
|
developer-mode?
|
||||||
(conj {:db-id "undo-redo" :block-type :undo-redo :label "(Dev) Undo/Redo"})
|
(conj {:db-id "undo-redo" :block-type :undo-redo :label "(Dev) Undo/Redo"})
|
||||||
|
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
(ns frontend.components.rtc.download-progress
|
|
||||||
(:require [frontend.components.rtc.indicator :as indicator]
|
|
||||||
[frontend.context.i18n :refer [t]]
|
|
||||||
[frontend.mobile.util :as mobile-util]
|
|
||||||
[logseq.shui.ui :as shui]))
|
|
||||||
|
|
||||||
(defn- content
|
|
||||||
[graph-name]
|
|
||||||
[:div.flex.flex-col.items-center.justify-center.gap-4
|
|
||||||
[:div (t :sync/downloading-graph graph-name)]
|
|
||||||
(indicator/downloading-logs)])
|
|
||||||
|
|
||||||
(defn show!
|
|
||||||
[graph-name]
|
|
||||||
(if (mobile-util/native-platform?)
|
|
||||||
(shui/popup-show!
|
|
||||||
nil
|
|
||||||
#(content graph-name)
|
|
||||||
{:id :download-rtc-graph})
|
|
||||||
(shui/dialog-open!
|
|
||||||
#(content graph-name)
|
|
||||||
{:id :download-rtc-graph
|
|
||||||
:content-props
|
|
||||||
{:onPointerDownOutside #(.preventDefault %)
|
|
||||||
:onOpenAutoFocus #(.preventDefault %)}})))
|
|
||||||
|
|
||||||
(defn hide!
|
|
||||||
[]
|
|
||||||
(if (mobile-util/native-platform?)
|
|
||||||
(shui/popup-hide! :download-rtc-graph)
|
|
||||||
(shui/dialog-close! :download-rtc-graph)))
|
|
||||||
@@ -1,360 +0,0 @@
|
|||||||
(ns frontend.components.rtc.indicator
|
|
||||||
"RTC state indicator"
|
|
||||||
(:require [clojure.pprint :as pprint]
|
|
||||||
[clojure.string :as string]
|
|
||||||
[frontend.config :as config]
|
|
||||||
[frontend.db.async :as db-async]
|
|
||||||
[frontend.handler.db-based.rtc-flows :as rtc-flows]
|
|
||||||
[frontend.context.i18n :refer [locale-format-date t]]
|
|
||||||
[frontend.handler.db-based.sync :as rtc-handler]
|
|
||||||
[frontend.rfx :as rfx]
|
|
||||||
[frontend.state :as state]
|
|
||||||
[frontend.ui :as ui]
|
|
||||||
[frontend.util :as util]
|
|
||||||
[logseq.shui.hooks :as hooks]
|
|
||||||
[logseq.shui.ui :as shui]
|
|
||||||
[promesa.core :as p]
|
|
||||||
[io.factorhouse.hsx.core :as hsx]))
|
|
||||||
|
|
||||||
(comment
|
|
||||||
(def rtc-state-schema
|
|
||||||
[:enum :open :close]))
|
|
||||||
|
|
||||||
(defonce ^:private *detail-logs
|
|
||||||
(atom {:download-logs nil
|
|
||||||
:upload-logs nil
|
|
||||||
:misc-logs nil}))
|
|
||||||
|
|
||||||
(defn rtc-state->detail-info
|
|
||||||
[state]
|
|
||||||
{:pending-local-ops (:unpushed-block-update-count state)
|
|
||||||
:pending-asset-ops (:pending-asset-ops-count state)
|
|
||||||
:missing-asset-upload-files (:missing-asset-upload-files state)
|
|
||||||
:pending-server-ops (or (:pending-server-ops-count state)
|
|
||||||
(when (and (number? (:remote-tx state))
|
|
||||||
(number? (:local-tx state)))
|
|
||||||
(max 0 (- (:remote-tx state) (:local-tx state)))))
|
|
||||||
:graph-uuid (:graph-uuid state)
|
|
||||||
:local-tx (:local-tx state)
|
|
||||||
:remote-tx (:remote-tx state)
|
|
||||||
:local-checksum (:local-checksum state)
|
|
||||||
:remote-checksum (:remote-checksum state)
|
|
||||||
:rtc-state (if (:rtc-lock state) :open :close)})
|
|
||||||
|
|
||||||
(defn- update-detail-log!
|
|
||||||
[k log]
|
|
||||||
(when log
|
|
||||||
(swap! *detail-logs update k (fn [logs] (take 5 (conj logs log))))))
|
|
||||||
|
|
||||||
(add-watch rtc-flows/rtc-download-log ::update-detail-download-log
|
|
||||||
(fn [_ _ _ log] (update-detail-log! :download-logs log)))
|
|
||||||
(add-watch rtc-flows/rtc-upload-log ::update-detail-upload-log
|
|
||||||
(fn [_ _ _ log] (update-detail-log! :upload-logs log)))
|
|
||||||
(add-watch rtc-flows/rtc-misc-log ::update-detail-misc-log
|
|
||||||
(fn [_ _ _ log] (update-detail-log! :misc-logs log)))
|
|
||||||
|
|
||||||
(defn use-detail-info
|
|
||||||
[]
|
|
||||||
(merge (rtc-state->detail-info (rfx/use-sub [:rtc/state]))
|
|
||||||
(hooks/use-atom-value *detail-logs)))
|
|
||||||
|
|
||||||
(defn asset-transfer-counts
|
|
||||||
[progress]
|
|
||||||
(reduce-kv
|
|
||||||
(fn [counts _id {:keys [direction loaded total]}]
|
|
||||||
(if (and (contains? #{:upload :download} direction)
|
|
||||||
(number? loaded)
|
|
||||||
(number? total)
|
|
||||||
(not= loaded total))
|
|
||||||
(update counts direction inc)
|
|
||||||
counts))
|
|
||||||
{:upload 0
|
|
||||||
:download 0}
|
|
||||||
(or progress {})))
|
|
||||||
|
|
||||||
(defn asset-status-rows
|
|
||||||
[{:keys [pending-asset-ops missing-asset-upload-files]
|
|
||||||
transfer-counts :asset-transfer-counts}]
|
|
||||||
(let [{:keys [upload download]} transfer-counts
|
|
||||||
missing-count (count missing-asset-upload-files)
|
|
||||||
pending-upload-count (max 0 (- (or pending-asset-ops 0) missing-count))]
|
|
||||||
(cond-> []
|
|
||||||
(pos? missing-count)
|
|
||||||
(conj {:count missing-count
|
|
||||||
:label-key :sync/missing-asset-files})
|
|
||||||
|
|
||||||
(pos? pending-upload-count)
|
|
||||||
(conj {:count pending-upload-count
|
|
||||||
:label-key :sync/pending-asset-uploads})
|
|
||||||
|
|
||||||
(pos? (or upload 0))
|
|
||||||
(conj {:count upload
|
|
||||||
:label-key :sync/assets-uploading})
|
|
||||||
|
|
||||||
(pos? (or download 0))
|
|
||||||
(conj {:count download
|
|
||||||
:label-key :sync/assets-downloading}))))
|
|
||||||
|
|
||||||
(defn indicator-button-class
|
|
||||||
[{:keys [online? rtc-state pending-local-ops pending-asset-ops pending-server-ops]}]
|
|
||||||
(let [open? (and online? (= :open rtc-state))
|
|
||||||
syncing? (and open? (pos? (or pending-server-ops 0)))
|
|
||||||
idle? (and open?
|
|
||||||
(zero? (or pending-local-ops 0))
|
|
||||||
(zero? (or pending-asset-ops 0))
|
|
||||||
(zero? (or pending-server-ops 0)))
|
|
||||||
queuing? (or (pos? (or pending-local-ops 0))
|
|
||||||
(pos? (or pending-asset-ops 0)))]
|
|
||||||
(string/join " " (cond-> ["cloud"]
|
|
||||||
open? (conj "on")
|
|
||||||
syncing? (conj "syncing")
|
|
||||||
idle? (conj "idle")
|
|
||||||
queuing? (conj "queuing")))))
|
|
||||||
|
|
||||||
(defn- asset-status-label
|
|
||||||
[label-key]
|
|
||||||
(case label-key
|
|
||||||
:sync/missing-asset-files (t :sync/missing-asset-files)
|
|
||||||
:sync/pending-asset-uploads (t :sync/pending-asset-uploads)
|
|
||||||
:sync/assets-uploading (t :sync/assets-uploading)
|
|
||||||
:sync/assets-downloading (t :sync/assets-downloading)))
|
|
||||||
|
|
||||||
(hsx/defc missing-asset-files
|
|
||||||
[files]
|
|
||||||
(when (seq files)
|
|
||||||
[:details.assets-missing-files
|
|
||||||
[:summary
|
|
||||||
(t :sync/missing-asset-files-count (count files))]
|
|
||||||
[:div.flex.flex-col.gap-1.text-sm
|
|
||||||
(for [{:keys [file]} files]
|
|
||||||
[:div.flex.flex-row.gap-1.items-center
|
|
||||||
{:key file}
|
|
||||||
(ui/icon "alert-triangle" {:size 14})
|
|
||||||
[:span.truncate file]])]]))
|
|
||||||
|
|
||||||
(hsx/defc assets-progressing
|
|
||||||
[progress]
|
|
||||||
(let [ids (->> progress
|
|
||||||
(keep (fn [[id {:keys [loaded total]}]]
|
|
||||||
(when (and (not= loaded total)
|
|
||||||
(number? loaded) (number? total))
|
|
||||||
(uuid id))))
|
|
||||||
distinct
|
|
||||||
vec)
|
|
||||||
[id->block set-id->block!] (hooks/use-state {})
|
|
||||||
_ (hooks/use-effect!
|
|
||||||
(fn []
|
|
||||||
(p/let [results (db-async/<get-blocks (state/get-current-repo) ids {:children? false})]
|
|
||||||
(set-id->block! (into {} (keep (fn [{:keys [block]}]
|
|
||||||
(when-let [id (:block/uuid block)]
|
|
||||||
[id block]))
|
|
||||||
results))))
|
|
||||||
nil)
|
|
||||||
[ids])
|
|
||||||
downloading (->>
|
|
||||||
(keep (fn [[id {:keys [direction loaded total]}]]
|
|
||||||
(when (and (= direction :download)
|
|
||||||
(not= loaded total)
|
|
||||||
(number? loaded) (number? total))
|
|
||||||
(when-let [block (get id->block (uuid id))]
|
|
||||||
{:block block
|
|
||||||
:percent (int (* 100 (/ loaded total)))}))) progress)
|
|
||||||
(sort-by (fn [{:keys [block]}] (:block/title block))))
|
|
||||||
uploading (->> (keep (fn [[id {:keys [direction loaded total]}]]
|
|
||||||
(when (and (= direction :upload)
|
|
||||||
(not= loaded total)
|
|
||||||
(number? loaded) (number? total))
|
|
||||||
(when-let [block (get id->block (uuid id))]
|
|
||||||
{:block block
|
|
||||||
:percent (int (* 100 (/ loaded total)))}))) progress)
|
|
||||||
(sort-by (fn [{:keys [block]}] (:block/title block))))]
|
|
||||||
[:div.assets-sync-progress.flex.flex-col.gap-2
|
|
||||||
(when (seq downloading)
|
|
||||||
[:details
|
|
||||||
[:summary
|
|
||||||
(t :sync/assets-downloading-count (count downloading))]
|
|
||||||
[:div.flex.flex-col.gap-1.text-sm
|
|
||||||
(for [{:keys [block percent]} downloading]
|
|
||||||
[:div.flex.flex-row.gap-1.items-center
|
|
||||||
(ui/indicator-progress-pie percent)
|
|
||||||
(:block/title block)])]])
|
|
||||||
(when (seq uploading)
|
|
||||||
[:details
|
|
||||||
[:summary
|
|
||||||
(t :sync/assets-uploading-count (count uploading))]
|
|
||||||
[:div.flex.flex-col.gap-1.text-sm
|
|
||||||
(for [{:keys [block percent]} uploading]
|
|
||||||
[:div.flex.flex-row.gap-1.items-center
|
|
||||||
(ui/indicator-progress-pie percent)
|
|
||||||
(:block/title block)])]])]))
|
|
||||||
|
|
||||||
(hsx/defc details
|
|
||||||
[]
|
|
||||||
(let [online? (rfx/use-sub [:network/online?])
|
|
||||||
repo (state/get-current-repo)
|
|
||||||
asset-progress (rfx/use-sub [:rtc/asset-upload-download-progress repo])
|
|
||||||
[expand-debug? set-expand-debug!] (hooks/use-state false)
|
|
||||||
show-checksums? (or config/dev? util/node-test?)
|
|
||||||
detail-info (use-detail-info)
|
|
||||||
{:keys [graph-uuid local-tx remote-tx local-checksum remote-checksum rtc-state
|
|
||||||
download-logs upload-logs misc-logs pending-local-ops pending-asset-ops
|
|
||||||
missing-asset-upload-files pending-server-ops]}
|
|
||||||
detail-info
|
|
||||||
asset-rows (asset-status-rows {:pending-asset-ops pending-asset-ops
|
|
||||||
:missing-asset-upload-files missing-asset-upload-files
|
|
||||||
:asset-transfer-counts (asset-transfer-counts asset-progress)})]
|
|
||||||
[:div.rtc-info.flex.flex-col.gap-1.p-2.text-gray-11
|
|
||||||
[:div.font-medium.mb-2 (t (if online? :sync/online :sync/offline))]
|
|
||||||
[:div [:span.font-medium.mr-1 (or pending-local-ops 0)] (t :sync/pending-local-changes)]
|
|
||||||
(for [{:keys [count label-key]} asset-rows]
|
|
||||||
[:div {:key (name label-key)}
|
|
||||||
[:span.font-medium.mr-1 count]
|
|
||||||
(asset-status-label label-key)])
|
|
||||||
;; FIXME: pending-server-ops
|
|
||||||
[:div [:span.font-medium.mr-1 (or pending-server-ops 0)] (t :sync/pending-server-changes)]
|
|
||||||
(missing-asset-files missing-asset-upload-files)
|
|
||||||
(assets-progressing asset-progress)
|
|
||||||
;; FIXME: What's the type for downloaded log?
|
|
||||||
(when-let [latest-log (some (fn [l] (when (contains? #{:rtc.log/push-local-update} (:type l)) l)) misc-logs)]
|
|
||||||
(when-let [time (:created-at latest-log)]
|
|
||||||
[:div.text-sm (t :sync/last-synced-time-label (locale-format-date time))]))
|
|
||||||
[:a.fade-link.text-sm {:on-click #(set-expand-debug! (not expand-debug?))}
|
|
||||||
(t :sync/more-debug-info)]
|
|
||||||
(when expand-debug?
|
|
||||||
[:div.rtc-info-debug
|
|
||||||
[:pre.select-text
|
|
||||||
(-> (cond-> {:pending-local-ops pending-local-ops}
|
|
||||||
download-logs (assoc :download download-logs)
|
|
||||||
upload-logs (assoc :upload upload-logs)
|
|
||||||
misc-logs (assoc :misc misc-logs)
|
|
||||||
graph-uuid (assoc :graph-uuid graph-uuid)
|
|
||||||
local-tx (assoc :local-tx local-tx)
|
|
||||||
remote-tx (assoc :remote-tx remote-tx)
|
|
||||||
(and show-checksums? local-checksum) (assoc :local-checksum local-checksum)
|
|
||||||
(and show-checksums? remote-checksum) (assoc :remote-checksum remote-checksum)
|
|
||||||
rtc-state (assoc :rtc-state rtc-state))
|
|
||||||
pprint/pprint
|
|
||||||
with-out-str)]])
|
|
||||||
(when-not (= rtc-state :open)
|
|
||||||
[:div.mt-4
|
|
||||||
(shui/button {:variant :default
|
|
||||||
:size :sm
|
|
||||||
:on-click (fn []
|
|
||||||
(rtc-handler/<rtc-start! (state/get-current-repo)
|
|
||||||
{:stop-before-start? true}))}
|
|
||||||
(t :sync/start-sync))])]))
|
|
||||||
|
|
||||||
(hsx/defc indicator
|
|
||||||
[]
|
|
||||||
(let [detail-info (use-detail-info)
|
|
||||||
_ (rfx/use-sub [:auth/current-login-user])
|
|
||||||
online? (rfx/use-sub [:network/online?])
|
|
||||||
rtc-state (:rtc-state detail-info)
|
|
||||||
unpushed-block-update-count (:pending-local-ops detail-info)
|
|
||||||
pending-asset-ops (:pending-asset-ops detail-info)
|
|
||||||
pending-server-ops (:pending-server-ops detail-info)
|
|
||||||
{:keys [local-tx remote-tx]} detail-info]
|
|
||||||
[:div.cp__rtc-sync
|
|
||||||
[:div.hidden {"data-testid" "rtc-tx"} (pr-str {:local-tx local-tx :remote-tx remote-tx})]
|
|
||||||
[:div.cp__rtc-sync-indicator.flex.flex-row.items-center.gap-1
|
|
||||||
(shui/button-ghost-icon :cloud
|
|
||||||
{:on-click #(shui/popup-show! (.-target %)
|
|
||||||
(details)
|
|
||||||
{:align "end"
|
|
||||||
:dropdown-menu? true})
|
|
||||||
:class (indicator-button-class
|
|
||||||
{:online? online?
|
|
||||||
:rtc-state rtc-state
|
|
||||||
:pending-local-ops unpushed-block-update-count
|
|
||||||
:pending-asset-ops pending-asset-ops
|
|
||||||
:pending-server-ops pending-server-ops})})]]))
|
|
||||||
|
|
||||||
(def ^:private *accumulated-download-logs (atom []))
|
|
||||||
(when-not config/publishing?
|
|
||||||
(add-watch rtc-flows/rtc-download-log ::update-accumulated-download-logs
|
|
||||||
(fn [_ _ _ log]
|
|
||||||
(when log
|
|
||||||
(if (= :download-completed (:sub-type log))
|
|
||||||
(reset! *accumulated-download-logs [])
|
|
||||||
(swap! *accumulated-download-logs (fn [logs] (take 20 (conj logs log)))))))))
|
|
||||||
|
|
||||||
(def ^:private *accumulated-upload-logs (atom []))
|
|
||||||
(when-not config/publishing?
|
|
||||||
(add-watch rtc-flows/rtc-upload-log ::update-accumulated-upload-logs
|
|
||||||
(fn [_ _ _ log]
|
|
||||||
(when log
|
|
||||||
(if (= :upload-completed (:sub-type log))
|
|
||||||
(reset! *accumulated-upload-logs [])
|
|
||||||
(swap! *accumulated-upload-logs (fn [logs] (take 20 (conj logs log)))))))))
|
|
||||||
|
|
||||||
(defn- current-graph-logs
|
|
||||||
[logs]
|
|
||||||
(when-let [first-log (first logs)]
|
|
||||||
(let [graph-uuid (:graph-uuid first-log)]
|
|
||||||
(take-while (fn [log] (= (str graph-uuid) (str (:graph-uuid log)))) logs))))
|
|
||||||
|
|
||||||
(defn- log-row-key
|
|
||||||
[prefix idx log]
|
|
||||||
(str prefix "-" (:graph-uuid log) "-" (:sub-type log) "-" (:message log) "-" idx))
|
|
||||||
|
|
||||||
(hsx/defc downloading-logs
|
|
||||||
[]
|
|
||||||
(let [download-logs (current-graph-logs (hooks/use-atom-value *accumulated-download-logs))]
|
|
||||||
(when (seq download-logs)
|
|
||||||
[:div.flex.flex-col.gap-1
|
|
||||||
(for [[idx log] (map-indexed vector download-logs)]
|
|
||||||
^{:key (log-row-key "download" idx log)}
|
|
||||||
[:div (string/capitalize (:message log))])])))
|
|
||||||
|
|
||||||
(hsx/defc uploading-logs
|
|
||||||
[]
|
|
||||||
(let [upload-logs (current-graph-logs (hooks/use-atom-value *accumulated-upload-logs))]
|
|
||||||
(when (seq upload-logs)
|
|
||||||
[:div.capitalize.flex.flex-col.gap-1
|
|
||||||
(for [[idx log] (map-indexed vector (reverse upload-logs))]
|
|
||||||
^{:key (log-row-key "upload" idx log)}
|
|
||||||
[:div (:message log)])])))
|
|
||||||
|
|
||||||
(def ^:private *downloading? (atom false))
|
|
||||||
(add-watch rtc-flows/rtc-download-log ::update-downloading?
|
|
||||||
(fn [_ _ _ log]
|
|
||||||
(reset! *downloading? (not= :download-completed (:sub-type log)))))
|
|
||||||
|
|
||||||
(hsx/defc downloading-detail
|
|
||||||
[]
|
|
||||||
(when (true? (hooks/use-atom-value *downloading?))
|
|
||||||
(shui/button
|
|
||||||
{:class "opacity-50"
|
|
||||||
:variant :ghost
|
|
||||||
:size :sm
|
|
||||||
:on-click #(shui/popup-show! (.-target %)
|
|
||||||
(downloading-logs)
|
|
||||||
{:align "end"})}
|
|
||||||
(t :sync/downloading))))
|
|
||||||
|
|
||||||
(def ^:private *uploading? (atom false))
|
|
||||||
(add-watch rtc-flows/rtc-upload-log ::update-uploading?
|
|
||||||
(fn [_ _ _ log]
|
|
||||||
(reset! *uploading? (not= :upload-completed (:sub-type log)))))
|
|
||||||
|
|
||||||
(defn on-upload-finished-task
|
|
||||||
[on-success]
|
|
||||||
(let [watch-key (random-uuid)]
|
|
||||||
(add-watch rtc-flows/rtc-upload-log watch-key
|
|
||||||
(fn [_ _ _ log]
|
|
||||||
(when (= :upload-completed (:sub-type log))
|
|
||||||
(remove-watch rtc-flows/rtc-upload-log watch-key)
|
|
||||||
(on-success))))
|
|
||||||
#(remove-watch rtc-flows/rtc-upload-log watch-key)))
|
|
||||||
|
|
||||||
(hsx/defc uploading-detail
|
|
||||||
[]
|
|
||||||
(when (true? (hooks/use-atom-value *uploading?))
|
|
||||||
(shui/button
|
|
||||||
{:class "opacity-50"
|
|
||||||
:variant :ghost
|
|
||||||
:size :sm
|
|
||||||
:on-click #(shui/popup-show! (.-target %)
|
|
||||||
(uploading-logs)
|
|
||||||
{:align "end"})}
|
|
||||||
(t :sync/uploading))))
|
|
||||||
@@ -11,7 +11,6 @@
|
|||||||
[frontend.date :as date]
|
[frontend.date :as date]
|
||||||
[frontend.dicts :as dicts]
|
[frontend.dicts :as dicts]
|
||||||
[frontend.handler.config :as config-handler]
|
[frontend.handler.config :as config-handler]
|
||||||
[frontend.handler.db-based.sync :as rtc-handler]
|
|
||||||
[frontend.handler.global-config :as global-config-handler]
|
[frontend.handler.global-config :as global-config-handler]
|
||||||
[frontend.handler.notification :as notification]
|
[frontend.handler.notification :as notification]
|
||||||
[frontend.handler.plugin :as plugin-handler]
|
[frontend.handler.plugin :as plugin-handler]
|
||||||
@@ -637,59 +636,6 @@
|
|||||||
(config-handler/set-config! :feature/enable-flashcards? value)))
|
(config-handler/set-config! :feature/enable-flashcards? value)))
|
||||||
true))
|
true))
|
||||||
|
|
||||||
(defn- push-sync-config-to-worker!
|
|
||||||
"Push the current sync URL config to the db worker so changes take effect
|
|
||||||
without restarting the app."
|
|
||||||
[]
|
|
||||||
(state/<invoke-db-worker :thread-api/set-db-sync-config
|
|
||||||
{:enabled? true
|
|
||||||
:ws-url (config/db-sync-ws-url)
|
|
||||||
:http-base (config/db-sync-http-base)}))
|
|
||||||
|
|
||||||
(hsx/defc sync-server-url-settings-container
|
|
||||||
[]
|
|
||||||
(let [current-url (config/get-custom-sync-server-url)
|
|
||||||
[url set-url!] (hooks/use-state (or current-url ""))
|
|
||||||
reset-url! (fn []
|
|
||||||
(config/set-custom-sync-server-url! nil)
|
|
||||||
(set-url! "")
|
|
||||||
(-> (push-sync-config-to-worker!)
|
|
||||||
(p/then #(notification/show! (t :settings.sync-server/clear-success) :success))
|
|
||||||
(p/catch #(notification/show! (str "Failed to update worker: " %) :error))))]
|
|
||||||
[:div.cp__settings-sync-server-cnt
|
|
||||||
[:h1.mb-2.text-2xl.font-bold (t :settings.sync-server/url)]
|
|
||||||
[:div.p-2
|
|
||||||
[:p.text-sm.opacity-70.mb-4 (t :settings.sync-server/url-desc)]
|
|
||||||
[:p
|
|
||||||
[:label
|
|
||||||
[:strong "URL"]
|
|
||||||
[:input.form-input.is-small
|
|
||||||
{:value url
|
|
||||||
:placeholder config/default-db-sync-http-base
|
|
||||||
:style {:width "100%"}
|
|
||||||
:on-change #(set-url! (util/evalue %))}]]]
|
|
||||||
[:p.pt-2.flex.gap-2
|
|
||||||
(shui/button
|
|
||||||
{:size :sm
|
|
||||||
:on-click (fn []
|
|
||||||
(let [trimmed (string/trim url)]
|
|
||||||
(if (string/blank? trimmed)
|
|
||||||
(reset-url!)
|
|
||||||
(if-not (config/valid-sync-server-url? trimmed)
|
|
||||||
(notification/show! (t :settings.sync-server/url-invalid-error) :error)
|
|
||||||
(do
|
|
||||||
(config/set-custom-sync-server-url! trimmed)
|
|
||||||
(-> (push-sync-config-to-worker!)
|
|
||||||
(p/then #(notification/show! (t :settings.sync-server/save-success) :success))
|
|
||||||
(p/catch #(notification/show! (str "Failed to update worker: " %) :error))))))))}
|
|
||||||
(t :ui/save))
|
|
||||||
(when (seq url)
|
|
||||||
(shui/button
|
|
||||||
{:size :sm
|
|
||||||
:variant :outline
|
|
||||||
:on-click (fn [] (reset-url!))}
|
|
||||||
(t :settings.sync-server/reset)))]]]))
|
|
||||||
|
|
||||||
(hsx/defc db-worker-server-settings-container
|
(hsx/defc db-worker-server-settings-container
|
||||||
"Connects a web/mobile client to a self-hosted db-worker-node server (see
|
"Connects a web/mobile client to a self-hosted db-worker-node server (see
|
||||||
frontend.persist-db/remote-runtime?). Electron manages its own local
|
frontend.persist-db/remote-runtime?). Electron manages its own local
|
||||||
@@ -788,23 +734,6 @@
|
|||||||
{:left-label "Server"
|
{:left-label "Server"
|
||||||
:action (db-worker-server-button)}))
|
:action (db-worker-server-button)}))
|
||||||
|
|
||||||
(hsx/defc sync-server-url-button
|
|
||||||
[]
|
|
||||||
(let [current-url (config/get-custom-sync-server-url)]
|
|
||||||
(ui/button [:span.flex.items-center
|
|
||||||
[:span.pr-1
|
|
||||||
(if (seq current-url)
|
|
||||||
current-url
|
|
||||||
"Logseq Sync")]
|
|
||||||
(ui/icon "edit")]
|
|
||||||
:class "text-sm"
|
|
||||||
:on-click #(state/pub-event! [:go/sync-server-settings]))))
|
|
||||||
|
|
||||||
(defn sync-server-url-row []
|
|
||||||
(row-with-button-action
|
|
||||||
{:left-label (t :settings.sync-server/url)
|
|
||||||
:action (sync-server-url-button)}))
|
|
||||||
|
|
||||||
(hsx/defc publish-server-url-settings-container
|
(hsx/defc publish-server-url-settings-container
|
||||||
[]
|
[]
|
||||||
(let [current-url (config/get-custom-publish-server-url)
|
(let [current-url (config/get-custom-publish-server-url)
|
||||||
@@ -1024,7 +953,6 @@
|
|||||||
(usage-diagnostics-row t instrument-disabled?)
|
(usage-diagnostics-row t instrument-disabled?)
|
||||||
(when-not (mobile-util/native-platform?) (developer-mode-row t developer-mode?))
|
(when-not (mobile-util/native-platform?) (developer-mode-row t developer-mode?))
|
||||||
(when-not (util/electron?) (db-worker-server-row))
|
(when-not (util/electron?) (db-worker-server-row))
|
||||||
(sync-server-url-row)
|
|
||||||
(publish-server-url-row)
|
(publish-server-url-row)
|
||||||
(when (util/electron?) (https-user-agent-row https-agent-opts))
|
(when (util/electron?) (https-user-agent-row https-agent-opts))
|
||||||
(when (util/electron?) (auto-chmod-row t))
|
(when (util/electron?) (auto-chmod-row t))
|
||||||
@@ -1294,221 +1222,6 @@
|
|||||||
|
|
||||||
[:<>])
|
[:<>])
|
||||||
|
|
||||||
(hsx/defc settings-rtc-members
|
|
||||||
[]
|
|
||||||
(let [[invite-email set-invite-email!] (hooks/use-state "")
|
|
||||||
[loading? set-loading!] (hooks/use-state true)
|
|
||||||
[graph-uuid set-graph-uuid!] (hooks/use-state nil)
|
|
||||||
current-repo (state/get-current-repo)
|
|
||||||
manager? (user-handler/manager? current-repo)
|
|
||||||
users-info (rfx/use-sub [:rtc/users-info])
|
|
||||||
users (get users-info current-repo)
|
|
||||||
invite-user! (fn []
|
|
||||||
(when-not (string/blank? invite-email)
|
|
||||||
(when graph-uuid
|
|
||||||
(rtc-handler/<rtc-invite-email graph-uuid invite-email))))]
|
|
||||||
(hooks/use-effect!
|
|
||||||
#(do
|
|
||||||
(p/let [graph-uuid (state/<invoke-db-worker :thread-api/get-rtc-graph-uuid current-repo)
|
|
||||||
_ (rtc-handler/<rtc-get-users-info)]
|
|
||||||
(set-graph-uuid! graph-uuid)
|
|
||||||
(set-loading! false))
|
|
||||||
nil)
|
|
||||||
[])
|
|
||||||
[:div.flex.flex-col.gap-2.mt-4
|
|
||||||
{:on-key-press (fn [e]
|
|
||||||
(when (= "Enter" (.-key e))
|
|
||||||
(invite-user!)))}
|
|
||||||
[:div.users.flex.flex-col.gap-1
|
|
||||||
(if loading?
|
|
||||||
(for [i (range 2)]
|
|
||||||
[:div.flex.flex-row.items-center.gap-2.pr-4 {:key (str "skeleton-" i)}
|
|
||||||
(shui/skeleton {:class "h-4 w-32"})
|
|
||||||
(shui/skeleton {:class "h-4 w-full"})])
|
|
||||||
(for [{user-name :user/name
|
|
||||||
user-email :user/email
|
|
||||||
user-uuid :user/uuid
|
|
||||||
graph<->user-user-type :graph<->user/user-type} users]
|
|
||||||
(let [member? (= :member graph<->user-user-type)
|
|
||||||
can-remove? (and manager? member?)]
|
|
||||||
[:div.flex.flex-row.items-center.gap-2
|
|
||||||
{:key (str "user-" (or user-uuid user-name))}
|
|
||||||
[:div user-name]
|
|
||||||
(when user-email
|
|
||||||
(email-component/email-address {:email user-email
|
|
||||||
:class "opacity-50 text-sm"}))
|
|
||||||
(when graph<->user-user-type [:div.opacity-50.text-sm (name graph<->user-user-type)])
|
|
||||||
(when can-remove?
|
|
||||||
(shui/dropdown-menu
|
|
||||||
(shui/dropdown-menu-trigger
|
|
||||||
{:asChild true}
|
|
||||||
(shui/button
|
|
||||||
{:variant "ghost"
|
|
||||||
:size :sm
|
|
||||||
:class "px-1 h-7"}
|
|
||||||
(ui/icon "dots" {:size 14})))
|
|
||||||
(shui/dropdown-menu-content
|
|
||||||
{:align "end"}
|
|
||||||
(shui/dropdown-menu-item
|
|
||||||
{:class "remove-member-menu-item"
|
|
||||||
:on-click (fn []
|
|
||||||
(let [member-id user-uuid]
|
|
||||||
(when (and graph-uuid member-id)
|
|
||||||
(-> (rtc-handler/<rtc-remove-member! graph-uuid member-id)
|
|
||||||
(p/then (fn []
|
|
||||||
(rtc-handler/<rtc-get-users-info true)))
|
|
||||||
(p/catch (fn [e]
|
|
||||||
(notification/show! (t :collaboration/remove-access-error) :error)
|
|
||||||
(log/error :db-sync/remove-member-failed {:error e
|
|
||||||
:graph-uuid graph-uuid
|
|
||||||
:member-id member-id})))))))}
|
|
||||||
(t :collaboration/remove-access)))))])))]
|
|
||||||
[:div.flex.flex-col.gap-4.mt-4
|
|
||||||
(shui/input
|
|
||||||
{:placeholder (t :collaboration/email-address)
|
|
||||||
:on-change #(set-invite-email!
|
|
||||||
(string/trim (util/evalue %)))})
|
|
||||||
(shui/button
|
|
||||||
{:on-click invite-user!}
|
|
||||||
(t :collaboration/invite))]]))
|
|
||||||
|
|
||||||
(hsx/defc settings-collaboration
|
|
||||||
[]
|
|
||||||
[:div.panel-wrap.is-collaboration.mb-8
|
|
||||||
(settings-rtc-members)])
|
|
||||||
|
|
||||||
(hsx/defc forgot-password
|
|
||||||
[token refresh-token user-uuid]
|
|
||||||
(let [[new-password set-new-password!] (hooks/use-state "")
|
|
||||||
[force-reset-status set-force-reset-status!] (hooks/use-state nil)
|
|
||||||
<force-reset-password-fn
|
|
||||||
(fn []
|
|
||||||
(-> (p/do!
|
|
||||||
(set-force-reset-status! (t :encryption/force-resetting-password))
|
|
||||||
(state/<invoke-db-worker :thread-api/reset-user-rsa-key-pair
|
|
||||||
token refresh-token user-uuid new-password)
|
|
||||||
(set-force-reset-status! (t :encryption/force-reset-password-successfully)))
|
|
||||||
(p/catch (fn [e]
|
|
||||||
(log/error :forgot-password e)
|
|
||||||
(set-force-reset-status! (t :encryption/failed-to-force-reset-password))))))]
|
|
||||||
[:div.flex.flex-col.gap-4
|
|
||||||
[:p
|
|
||||||
(t :encryption/forgot-password-warning)]
|
|
||||||
[:label.opacity-70 {:for "new-password"} (t :encryption/set-new-password)]
|
|
||||||
(shui/toggle-password
|
|
||||||
{:id "new-password"
|
|
||||||
:value new-password
|
|
||||||
:on-change #(set-new-password! (util/evalue %))})
|
|
||||||
(when force-reset-status [:p force-reset-status])
|
|
||||||
(shui/button
|
|
||||||
{:on-click <force-reset-password-fn
|
|
||||||
:disabled (string/blank? new-password)}
|
|
||||||
(t :encryption/force-reset-password))]))
|
|
||||||
|
|
||||||
(hsx/defc reset-encryption-password
|
|
||||||
[current-password new-password {:keys [set-new-password!
|
|
||||||
set-current-password!
|
|
||||||
reset-password-status
|
|
||||||
on-click forgot? set-forgot!
|
|
||||||
token refresh-token user-uuid]}]
|
|
||||||
(let [[reset? set-reset!] (hooks/use-state false)]
|
|
||||||
(cond
|
|
||||||
forgot?
|
|
||||||
(forgot-password token refresh-token user-uuid)
|
|
||||||
reset?
|
|
||||||
[:div.flex.flex-col.gap-4
|
|
||||||
[:label.opacity-70 {:for "current-password"} (t :encryption/current-password)]
|
|
||||||
(shui/toggle-password
|
|
||||||
{:id "current-password"
|
|
||||||
:value current-password
|
|
||||||
:on-change #(set-current-password! (util/evalue %))})
|
|
||||||
[:label.opacity-70 {:for "new-password"} (t :encryption/set-new-password)]
|
|
||||||
(shui/toggle-password
|
|
||||||
{:id "new-password"
|
|
||||||
:value new-password
|
|
||||||
:on-change #(set-new-password! (util/evalue %))})
|
|
||||||
(when reset-password-status [:p reset-password-status])
|
|
||||||
(shui/button
|
|
||||||
{:on-click on-click
|
|
||||||
:disabled (string/blank? new-password)}
|
|
||||||
(t :encryption/reset-password))
|
|
||||||
[:a.opacity-70.hover:opacity-100 {:on-click #(set-forgot! true)}
|
|
||||||
(t :encryption/forgot-password-question)]]
|
|
||||||
:else
|
|
||||||
[:a.opacity-70.hover:opacity-100 {:on-click #(set-reset! true)}
|
|
||||||
(t :encryption/reset-password)])))
|
|
||||||
|
|
||||||
(hsx/defc encryption
|
|
||||||
[]
|
|
||||||
(let [user-uuid (user-handler/user-uuid)
|
|
||||||
token (state/get-auth-id-token)
|
|
||||||
refresh-token (str (state/get-auth-refresh-token))
|
|
||||||
[rsa-key-pair set-rsa-key-pair!] (hooks/use-state :not-inited)
|
|
||||||
[init-key-err set-init-key-err!] (hooks/use-state nil)
|
|
||||||
[get-key-err set-get-key-err!] (hooks/use-state nil)
|
|
||||||
[current-password set-current-password!] (hooks/use-state nil)
|
|
||||||
[new-password set-new-password!] (hooks/use-state nil)
|
|
||||||
[reset-password-status set-reset-password-status!] (hooks/use-state nil)
|
|
||||||
[forgot? set-forgot!] (hooks/use-state false)]
|
|
||||||
[:div.panel-wrap.is-encryption.mb-8
|
|
||||||
(hooks/use-effect!
|
|
||||||
(fn []
|
|
||||||
(when (and user-uuid token)
|
|
||||||
(-> (p/let [r (state/<invoke-db-worker :thread-api/get-user-rsa-key-pair token user-uuid)]
|
|
||||||
(set-rsa-key-pair! r))
|
|
||||||
(p/catch set-get-key-err!))
|
|
||||||
(-> (p/let [{:keys [password]} (state/<invoke-db-worker :thread-api/get-e2ee-password refresh-token)]
|
|
||||||
(set-current-password! password))
|
|
||||||
(p/catch (fn [_] (set-current-password! ""))))))
|
|
||||||
[user-uuid token])
|
|
||||||
[:div.flex.flex-col.gap-2.mt-4
|
|
||||||
(when (and user-uuid token)
|
|
||||||
(cond
|
|
||||||
get-key-err
|
|
||||||
[:p (t :encryption/fetch-key-pair-error get-key-err)]
|
|
||||||
(= rsa-key-pair :not-inited)
|
|
||||||
[:p (t :encryption/fetching-key-pair)]
|
|
||||||
(nil? rsa-key-pair)
|
|
||||||
[:div.flex.flex-col.gap-2
|
|
||||||
(when init-key-err [:p (t :encryption/init-key-pair-error init-key-err)])
|
|
||||||
(shui/button
|
|
||||||
{:on-click (fn []
|
|
||||||
(-> (p/do!
|
|
||||||
(state/<invoke-db-worker :thread-api/init-user-rsa-key-pair
|
|
||||||
token
|
|
||||||
refresh-token
|
|
||||||
user-uuid)
|
|
||||||
(p/let [r (state/<invoke-db-worker :thread-api/get-user-rsa-key-pair token user-uuid)]
|
|
||||||
(set-rsa-key-pair! r)))
|
|
||||||
(p/catch set-init-key-err!)))}
|
|
||||||
(t :encryption/init-key-pair))]
|
|
||||||
rsa-key-pair
|
|
||||||
(let [on-submit (fn []
|
|
||||||
(-> (p/do!
|
|
||||||
(set-reset-password-status! (t :encryption/updating-password))
|
|
||||||
(state/<invoke-db-worker :thread-api/change-e2ee-password
|
|
||||||
token refresh-token user-uuid current-password new-password)
|
|
||||||
(set-reset-password-status! (t :encryption/password-updated-successfully)))
|
|
||||||
(p/catch (fn [e]
|
|
||||||
(log/error :reset-password-failed e)
|
|
||||||
(set-reset-password-status! (t :encryption/failed-to-update-password))))))]
|
|
||||||
[:div.flex.flex-col.gap-4
|
|
||||||
;; [:p "E2EE key-pair already generated!"]
|
|
||||||
(when-not forgot?
|
|
||||||
[:div.flex.flex-col
|
|
||||||
[:p (t :encryption/remember-password-rich)]
|
|
||||||
[:p (t :encryption/cloud-password-rich)]])
|
|
||||||
(reset-encryption-password current-password new-password
|
|
||||||
{:reset-password-status reset-password-status
|
|
||||||
:set-new-password! set-new-password!
|
|
||||||
:set-current-password! set-current-password!
|
|
||||||
:on-click on-submit
|
|
||||||
:token token
|
|
||||||
:forgot? forgot?
|
|
||||||
:set-forgot! set-forgot!
|
|
||||||
:refresh-token refresh-token
|
|
||||||
:user-uuid user-uuid})])))]]))
|
|
||||||
|
|
||||||
(hsx/defc mcp-server-row
|
(hsx/defc mcp-server-row
|
||||||
[t]
|
[t]
|
||||||
(let [server-state (rfx/use-sub [:electron/server])
|
(let [server-state (rfx/use-sub [:electron/server])
|
||||||
@@ -1552,8 +1265,7 @@
|
|||||||
_installed-plugins (rfx/use-sub [:plugin/installed-plugins])
|
_installed-plugins (rfx/use-sub [:plugin/installed-plugins])
|
||||||
plugins-of-settings (and config/lsp-enabled? (seq (plugin-handler/get-enabled-plugins-if-setting-schema)))
|
plugins-of-settings (and config/lsp-enabled? (seq (plugin-handler/get-enabled-plugins-if-setting-schema)))
|
||||||
*active (hooks/use-memo #(atom DEFAULT-ACTIVE-TAB-STATE) [])
|
*active (hooks/use-memo #(atom DEFAULT-ACTIVE-TAB-STATE) [])
|
||||||
[active] (hooks/use-atom *active)
|
[active] (hooks/use-atom *active)]
|
||||||
logged-in? (user-handler/logged-in?)]
|
|
||||||
(hooks/use-effect!
|
(hooks/use-effect!
|
||||||
(fn []
|
(fn []
|
||||||
(state/load-app-user-cfgs)
|
(state/load-app-user-cfgs)
|
||||||
@@ -1584,11 +1296,6 @@
|
|||||||
|
|
||||||
[:advanced (t :settings/advanced) (ui/icon "bulb")]
|
[:advanced (t :settings/advanced) (ui/icon "bulb")]
|
||||||
[:features (t :settings/features) (ui/icon "app-feature")]
|
[:features (t :settings/features) (ui/icon "app-feature")]
|
||||||
(when logged-in?
|
|
||||||
[:collaboration (t :settings/collaboration) (ui/icon "users")])
|
|
||||||
|
|
||||||
(when logged-in?
|
|
||||||
[:encryption (t :settings/encryption) (ui/icon "lock")])
|
|
||||||
|
|
||||||
(when plugins-of-settings
|
(when plugins-of-settings
|
||||||
[:plugins-setting (t :settings/plugins) (ui/icon "puzzle")])]]
|
[:plugins-setting (t :settings/plugins) (ui/icon "puzzle")])]]
|
||||||
@@ -1641,12 +1348,6 @@
|
|||||||
:features
|
:features
|
||||||
(settings-features)
|
(settings-features)
|
||||||
|
|
||||||
:collaboration
|
|
||||||
(settings-collaboration)
|
|
||||||
|
|
||||||
:encryption
|
|
||||||
(encryption)
|
|
||||||
|
|
||||||
:ai
|
:ai
|
||||||
(if (util/electron?)
|
(if (util/electron?)
|
||||||
(settings-ai)
|
(settings-ai)
|
||||||
|
|||||||
@@ -39,70 +39,12 @@
|
|||||||
;; Enable for local development
|
;; Enable for local development
|
||||||
;; (def default-publish-api-base "http://localhost:8787")
|
;; (def default-publish-api-base "http://localhost:8787")
|
||||||
|
|
||||||
(goog-define ENABLE-DB-SYNC-LOCAL false)
|
|
||||||
(defonce db-sync-local? ENABLE-DB-SYNC-LOCAL)
|
|
||||||
|
|
||||||
(defonce default-db-sync-ws-url
|
|
||||||
(if db-sync-local?
|
|
||||||
"ws://127.0.0.1:8787/sync/%s"
|
|
||||||
"wss://api.logseq.io/sync/%s"))
|
|
||||||
|
|
||||||
(defonce default-db-sync-http-base
|
|
||||||
(if db-sync-local?
|
|
||||||
"http://127.0.0.1:8787"
|
|
||||||
"https://api.logseq.io"))
|
|
||||||
|
|
||||||
(defn get-custom-sync-server-url
|
|
||||||
"Read the user-configured custom sync server URL from localStorage.
|
|
||||||
Returns nil when not set or empty."
|
|
||||||
[]
|
|
||||||
(when-not util/node-test?
|
|
||||||
(let [v (.getItem js/localStorage "sync-server-url")]
|
|
||||||
(when (and (string? v) (not (string/blank? v)))
|
|
||||||
v))))
|
|
||||||
|
|
||||||
(defn set-custom-sync-server-url!
|
|
||||||
"Persist the custom sync server URL to localStorage. Pass nil or empty string to clear."
|
|
||||||
[url]
|
|
||||||
(when-not util/node-test?
|
|
||||||
(if (or (nil? url) (string/blank? url))
|
|
||||||
(.removeItem js/localStorage "sync-server-url")
|
|
||||||
(.setItem js/localStorage "sync-server-url" (string/trim url)))))
|
|
||||||
|
|
||||||
(defn valid-sync-server-url?
|
(defn valid-sync-server-url?
|
||||||
"Return true when `url` looks like a valid HTTP(S) base URL."
|
"Return true when `url` looks like a valid HTTP(S) base URL."
|
||||||
[url]
|
[url]
|
||||||
(and (string? url)
|
(and (string? url)
|
||||||
(re-find #"^https?://" url)))
|
(re-find #"^https?://" url)))
|
||||||
|
|
||||||
(defn custom-url->ws-url
|
|
||||||
"Derive a WebSocket sync URL from a custom HTTP base URL. Pure function."
|
|
||||||
[custom-url]
|
|
||||||
(let [scheme (if (string/starts-with? custom-url "https") "wss" "ws")
|
|
||||||
base (-> custom-url
|
|
||||||
(string/replace #"^https?://" "")
|
|
||||||
(string/replace #"/+$" ""))]
|
|
||||||
(str scheme "://" base "/sync/%s")))
|
|
||||||
|
|
||||||
(defn custom-url->http-base
|
|
||||||
"Normalize a custom HTTP base URL by stripping trailing slashes. Pure function."
|
|
||||||
[custom-url]
|
|
||||||
(string/replace custom-url #"/+$" ""))
|
|
||||||
|
|
||||||
(defn db-sync-ws-url
|
|
||||||
"Return the WebSocket sync URL. Uses custom server when configured, otherwise the default."
|
|
||||||
[]
|
|
||||||
(if-let [custom (get-custom-sync-server-url)]
|
|
||||||
(custom-url->ws-url custom)
|
|
||||||
default-db-sync-ws-url))
|
|
||||||
|
|
||||||
(defn db-sync-http-base
|
|
||||||
"Return the HTTP base URL for sync. Uses custom server when configured, otherwise the default."
|
|
||||||
[]
|
|
||||||
(if-let [custom (get-custom-sync-server-url)]
|
|
||||||
(custom-url->http-base custom)
|
|
||||||
default-db-sync-http-base))
|
|
||||||
|
|
||||||
;; Web/mobile clients connect to a self-hosted db-worker-node daemon by
|
;; Web/mobile clients connect to a self-hosted db-worker-node daemon by
|
||||||
;; base URL + bearer token, instead of Electron's IPC-managed local runtime
|
;; base URL + bearer token, instead of Electron's IPC-managed local runtime
|
||||||
;; (see frontend.persist-db/remote-runtime?). Stored client-side only; the
|
;; (see frontend.persist-db/remote-runtime?). Stored client-side only; the
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
[frontend.components.plugins :as plugins]
|
[frontend.components.plugins :as plugins]
|
||||||
[frontend.config :as config]
|
[frontend.config :as config]
|
||||||
[frontend.handler :as handler]
|
[frontend.handler :as handler]
|
||||||
[frontend.handler.db-based.rtc-background-tasks]
|
|
||||||
[frontend.handler.plugin :as plugin-handler]
|
[frontend.handler.plugin :as plugin-handler]
|
||||||
[frontend.handler.route :as route-handler]
|
[frontend.handler.route :as route-handler]
|
||||||
[frontend.log]
|
[frontend.log]
|
||||||
|
|||||||
@@ -16,12 +16,8 @@
|
|||||||
{:keys [schema]} (persist-db/<open-and-fetch-schema repo opts)
|
{:keys [schema]} (persist-db/<open-and-fetch-schema repo opts)
|
||||||
_ (when (nil? schema)
|
_ (when (nil? schema)
|
||||||
(throw (ex-info "No valid schema found when reloading db" {:repo repo})))
|
(throw (ex-info "No valid schema found when reloading db" {:repo repo})))
|
||||||
conflicts-by-block (state/<invoke-db-worker
|
|
||||||
:thread-api/db-sync-get-all-block-conflicts
|
|
||||||
repo)
|
|
||||||
_ (state/set-current-repo! repo)
|
_ (state/set-current-repo! repo)
|
||||||
_ (db-subs/reset-graph! repo)
|
_ (db-subs/reset-graph! repo)
|
||||||
_ (state/set-sync-block-conflicts! repo conflicts-by-block)
|
|
||||||
end-time (t/now)]
|
end-time (t/now)]
|
||||||
(log/info ::restore-graph!
|
(log/info ::restore-graph!
|
||||||
(str "opens worker graph in "
|
(str "opens worker graph in "
|
||||||
|
|||||||
@@ -1,136 +0,0 @@
|
|||||||
(ns frontend.db.rtc.debug-ui
|
|
||||||
"Debug UI for rtc module"
|
|
||||||
(:require [fipp.edn :as fipp]
|
|
||||||
[frontend.handler.db-based.rtc-flows :as rtc-flows]
|
|
||||||
[frontend.handler.db-based.sync :as rtc-handler]
|
|
||||||
[frontend.handler.user :as user]
|
|
||||||
[frontend.state :as state]
|
|
||||||
[lambdaisland.glogi :as log]
|
|
||||||
[logseq.db.frontend.schema :as db-schema]
|
|
||||||
[logseq.shui.hooks :as hooks]
|
|
||||||
[logseq.shui.ui :as shui]
|
|
||||||
[promesa.core :as p]
|
|
||||||
[io.factorhouse.hsx.core :as hsx]))
|
|
||||||
|
|
||||||
(defonce debug-state (atom (state/get-state :rtc/state)))
|
|
||||||
|
|
||||||
(defn- stop
|
|
||||||
[]
|
|
||||||
(p/do!
|
|
||||||
(rtc-handler/<rtc-stop!)
|
|
||||||
(reset! debug-state nil)))
|
|
||||||
|
|
||||||
(hsx/defc ^:large-vars/cleanup-todo rtc-debug-ui
|
|
||||||
[]
|
|
||||||
(let [[debug-state*] (hooks/use-atom debug-state)
|
|
||||||
[rtc-logs set-rtc-logs!] (hooks/use-state nil)
|
|
||||||
[keys-state set-keys-state!] (hooks/use-state nil)
|
|
||||||
[current-page-blocks-count set-current-page-blocks-count!] (hooks/use-state nil)
|
|
||||||
rtc-state (:rtc-state debug-state*)]
|
|
||||||
(hooks/use-effect!
|
|
||||||
(fn []
|
|
||||||
(let [logs* (atom nil)
|
|
||||||
watch-key ::sub-logs]
|
|
||||||
(add-watch rtc-flows/rtc-log watch-key
|
|
||||||
(fn [_ _ _ log]
|
|
||||||
(let [logs (if log
|
|
||||||
(take 10 (conj @logs* log))
|
|
||||||
@logs*)]
|
|
||||||
(reset! logs* logs)
|
|
||||||
(set-rtc-logs! logs))))
|
|
||||||
#(remove-watch rtc-flows/rtc-log watch-key)))
|
|
||||||
[])
|
|
||||||
(hooks/use-effect!
|
|
||||||
(fn []
|
|
||||||
(if-let [page (state/get-current-page)]
|
|
||||||
(p/let [blocks-count (state/<invoke-db-worker
|
|
||||||
:thread-api/q
|
|
||||||
(state/get-current-repo)
|
|
||||||
['[:find (count ?block) .
|
|
||||||
:in $ ?page-name
|
|
||||||
:where
|
|
||||||
[?page :block/name ?page-name]
|
|
||||||
[?block :block/page ?page]]
|
|
||||||
page])]
|
|
||||||
(set-current-page-blocks-count! blocks-count))
|
|
||||||
(set-current-page-blocks-count! nil))
|
|
||||||
nil)
|
|
||||||
[(state/get-current-page)])
|
|
||||||
[:div
|
|
||||||
{:on-click (fn [^js e]
|
|
||||||
(when-let [^js btn (.closest (.-target e) ".ui__button")]
|
|
||||||
(.setAttribute btn "disabled" "true")
|
|
||||||
(js/setTimeout #(.removeAttribute btn "disabled") 2000)))}
|
|
||||||
[:div.flex.gap-2.flex-wrap.items-center.pb-3
|
|
||||||
(shui/button
|
|
||||||
{:size :sm
|
|
||||||
:on-click
|
|
||||||
(fn [_]
|
|
||||||
(p/let [graph-list (rtc-handler/<get-remote-graphs)]
|
|
||||||
(swap! debug-state assoc :remote-graphs graph-list)))}
|
|
||||||
(shui/tabler-icon "download") "graph-list")
|
|
||||||
(shui/button
|
|
||||||
{:size :sm
|
|
||||||
:on-click #(user/<upload-user-avatar "TEST_AVATAR")}
|
|
||||||
(shui/tabler-icon "upload") "upload-test-avatar")]
|
|
||||||
|
|
||||||
[:div.pb-4
|
|
||||||
[:pre.select-text
|
|
||||||
(-> {:user-uuid (user/user-uuid)
|
|
||||||
:graph (:graph-uuid debug-state*)
|
|
||||||
:rtc-state rtc-state
|
|
||||||
:rtc-logs rtc-logs
|
|
||||||
:local-tx (:local-tx debug-state*)
|
|
||||||
:pending-block-update-count (:unpushed-block-update-count debug-state*)
|
|
||||||
:remote-graphs (:remote-graphs debug-state*)
|
|
||||||
:online-users (:online-users debug-state*)
|
|
||||||
:auto-push? (:auto-push? debug-state*)
|
|
||||||
:remote-profile? (:remote-profile? debug-state*)
|
|
||||||
:current-page (state/get-current-page)
|
|
||||||
:blocks-count current-page-blocks-count
|
|
||||||
:schema-version {:app (db-schema/schema-version->string db-schema/version)
|
|
||||||
:local-graph (:local-graph-schema-version debug-state*)
|
|
||||||
:remote-graph (str (:remote-graph-schema-version debug-state*))}}
|
|
||||||
(fipp/pprint {:width 20})
|
|
||||||
with-out-str)]]
|
|
||||||
|
|
||||||
[:div.my-2.flex.gap-2
|
|
||||||
(shui/button
|
|
||||||
{:variant :outline
|
|
||||||
:size :sm
|
|
||||||
:class "text-green-rx-09 border-green-rx-10 hover:text-green-rx-10"
|
|
||||||
:on-click (fn [] (rtc-handler/<rtc-start! (state/get-current-repo)))}
|
|
||||||
(shui/tabler-icon "player-play") "start")
|
|
||||||
(shui/button
|
|
||||||
{:variant :outline
|
|
||||||
:class "text-red-rx-09 border-red-rx-08 hover:text-red-rx-10"
|
|
||||||
:size :sm
|
|
||||||
:on-click (fn [] (stop))}
|
|
||||||
(shui/tabler-icon "player-stop") "stop")]
|
|
||||||
|
|
||||||
[:hr.my-2]
|
|
||||||
[:div
|
|
||||||
[:div.pb-2.flex.flex-row.items-center.gap-2
|
|
||||||
(shui/button
|
|
||||||
{:size :sm
|
|
||||||
:on-click (fn [_]
|
|
||||||
(when-let [user-uuid (user/user-uuid)]
|
|
||||||
(p/let [user-rsa-key-pair (state/<invoke-db-worker
|
|
||||||
:thread-api/get-user-rsa-key-pair
|
|
||||||
(state/get-auth-id-token) user-uuid)]
|
|
||||||
(set-keys-state! user-rsa-key-pair))))}
|
|
||||||
(shui/tabler-icon "refresh") "keys-state")
|
|
||||||
(shui/button
|
|
||||||
{:size :sm
|
|
||||||
:on-click (fn [_]
|
|
||||||
(when-let [token (state/get-auth-id-token)]
|
|
||||||
(-> (state/<invoke-db-worker
|
|
||||||
:thread-api/init-user-rsa-key-pair token (user/user-uuid))
|
|
||||||
(p/catch (fn [error]
|
|
||||||
(log/error :init-user-rsa-key-pair error))))))}
|
|
||||||
(shui/tabler-icon "upload") "init upload user rsa-key-pair")]
|
|
||||||
[:div.pb-1
|
|
||||||
[:pre.select-text
|
|
||||||
(-> keys-state
|
|
||||||
(fipp/pprint {:width 20})
|
|
||||||
with-out-str)]]]]))
|
|
||||||
@@ -15,10 +15,8 @@
|
|||||||
[frontend.db.restore :as db-restore]
|
[frontend.db.restore :as db-restore]
|
||||||
[frontend.error :as error]
|
[frontend.error :as error]
|
||||||
[frontend.handler.command-palette :as command-palette]
|
[frontend.handler.command-palette :as command-palette]
|
||||||
[frontend.handler.e2ee]
|
|
||||||
[frontend.handler.events :as events]
|
[frontend.handler.events :as events]
|
||||||
[frontend.handler.events.export]
|
[frontend.handler.events.export]
|
||||||
[frontend.handler.events.rtc]
|
|
||||||
[frontend.handler.events.ui]
|
[frontend.handler.events.ui]
|
||||||
[frontend.handler.graph :as graph-handler]
|
[frontend.handler.graph :as graph-handler]
|
||||||
[frontend.handler.global-config :as global-config-handler]
|
[frontend.handler.global-config :as global-config-handler]
|
||||||
|
|||||||
@@ -336,32 +336,3 @@
|
|||||||
(p/let [[repo-dir assets-dir] (ensure-assets-dir! (state/get-current-repo))]
|
(p/let [[repo-dir assets-dir] (ensure-assets-dir! (state/get-current-repo))]
|
||||||
(path/path-join repo-dir assets-dir filename)))
|
(path/path-join repo-dir assets-dir filename)))
|
||||||
|
|
||||||
(defn- asset-transfer-in-progress?
|
|
||||||
[progress-entry]
|
|
||||||
(let [{:keys [loaded total]} progress-entry]
|
|
||||||
(and (number? loaded) (number? total) (pos? total) (not= loaded total))))
|
|
||||||
|
|
||||||
(defn should-request-remote-asset-download?
|
|
||||||
[repo asset-block file-ready? progress]
|
|
||||||
(let [asset-uuid (:block/uuid asset-block)
|
|
||||||
asset-type (:logseq.property.asset/type asset-block)
|
|
||||||
external-url (:logseq.property.asset/external-url asset-block)
|
|
||||||
remote-metadata (:logseq.property.asset/remote-metadata asset-block)
|
|
||||||
progress-entry (get progress (str asset-uuid))]
|
|
||||||
(and (seq repo)
|
|
||||||
remote-metadata
|
|
||||||
asset-uuid
|
|
||||||
(seq asset-type)
|
|
||||||
(string/blank? external-url)
|
|
||||||
(not file-ready?)
|
|
||||||
(not (asset-transfer-in-progress? progress-entry)))))
|
|
||||||
|
|
||||||
(defn maybe-request-remote-asset-download!
|
|
||||||
[repo asset-block file-ready?]
|
|
||||||
(let [progress (get (or (state/get-state :rtc/asset-upload-download-progress) {}) repo)]
|
|
||||||
(when (should-request-remote-asset-download? repo asset-block file-ready? progress)
|
|
||||||
(state/<invoke-db-worker
|
|
||||||
:thread-api/db-sync-request-asset-download
|
|
||||||
repo
|
|
||||||
(:block/uuid asset-block))
|
|
||||||
true)))
|
|
||||||
|
|||||||
@@ -2,11 +2,9 @@
|
|||||||
"Common fns for developer related functionality"
|
"Common fns for developer related functionality"
|
||||||
(:require ["/frontend/utils" :as utils]
|
(:require ["/frontend/utils" :as utils]
|
||||||
[cljs.pprint :as pprint]
|
[cljs.pprint :as pprint]
|
||||||
[clojure.set :as set]
|
|
||||||
[clojure.string :as string]
|
[clojure.string :as string]
|
||||||
[frontend.context.i18n :refer [t]]
|
[frontend.context.i18n :refer [t]]
|
||||||
[frontend.format.mldoc :as mldoc]
|
[frontend.format.mldoc :as mldoc]
|
||||||
[frontend.handler.db-based.sync :as rtc-handler]
|
|
||||||
[frontend.handler.notification :as notification]
|
[frontend.handler.notification :as notification]
|
||||||
[frontend.persist-db :as persist-db]
|
[frontend.persist-db :as persist-db]
|
||||||
[frontend.state :as state]
|
[frontend.state :as state]
|
||||||
@@ -84,13 +82,6 @@
|
|||||||
(defn ^:export validate-db []
|
(defn ^:export validate-db []
|
||||||
(state/<invoke-db-worker :thread-api/validate-db (state/get-current-repo)))
|
(state/<invoke-db-worker :thread-api/validate-db (state/get-current-repo)))
|
||||||
|
|
||||||
(defn- checksum-export-file-name
|
|
||||||
[repo]
|
|
||||||
(-> (or repo "graph")
|
|
||||||
(string/replace #"^/+" "")
|
|
||||||
(string/replace #"[\\/]+" "_")
|
|
||||||
(str "_checksum_" (quot (util/time-ms) 1000))))
|
|
||||||
|
|
||||||
(defn- client-ops-export-file-name
|
(defn- client-ops-export-file-name
|
||||||
[repo]
|
[repo]
|
||||||
(-> (or repo "graph")
|
(-> (or repo "graph")
|
||||||
@@ -98,113 +89,6 @@
|
|||||||
(string/replace #"[\\/]+" "_")
|
(string/replace #"[\\/]+" "_")
|
||||||
(str "_client_ops_" (quot (util/time-ms) 1000))))
|
(str "_client_ops_" (quot (util/time-ms) 1000))))
|
||||||
|
|
||||||
(defn- <fetch-server-checksum-diagnostics
|
|
||||||
[repo]
|
|
||||||
(p/let [base (rtc-handler/http-base)
|
|
||||||
graph-id' (state/<invoke-db-worker :thread-api/get-rtc-graph-uuid repo)
|
|
||||||
graph-id (some-> graph-id' str)]
|
|
||||||
(if (and (seq base) (seq graph-id))
|
|
||||||
(rtc-handler/fetch-json (str base "/sync/" graph-id "/checksum/diagnostics")
|
|
||||||
{:method "GET"}
|
|
||||||
{:error-schema :error})
|
|
||||||
(p/rejected (ex-info "missing sync diagnostics context"
|
|
||||||
{:repo repo
|
|
||||||
:base base
|
|
||||||
:graph-id graph-id})))))
|
|
||||||
|
|
||||||
(defn- normalize-diff-block
|
|
||||||
[{:keys [block/uuid block/parent block/page] :as block}]
|
|
||||||
(cond-> block
|
|
||||||
uuid (assoc :block/uuid (str uuid))
|
|
||||||
parent (assoc :block/parent (str parent))
|
|
||||||
page (assoc :block/page (str page))))
|
|
||||||
|
|
||||||
(defn- blocks-by-uuid
|
|
||||||
[blocks]
|
|
||||||
(into {}
|
|
||||||
(keep (fn [block]
|
|
||||||
(let [block' (normalize-diff-block block)
|
|
||||||
uuid (:block/uuid block')]
|
|
||||||
(when (seq uuid)
|
|
||||||
[uuid block']))))
|
|
||||||
blocks))
|
|
||||||
|
|
||||||
(defn- different-blocks
|
|
||||||
[local-blocks server-blocks]
|
|
||||||
(let [local-by-uuid (blocks-by-uuid local-blocks)
|
|
||||||
server-by-uuid (blocks-by-uuid server-blocks)
|
|
||||||
block-uuids (sort (set/union (set (keys local-by-uuid))
|
|
||||||
(set (keys server-by-uuid))))]
|
|
||||||
(->> block-uuids
|
|
||||||
(keep (fn [uuid]
|
|
||||||
(let [local-block (get local-by-uuid uuid)
|
|
||||||
server-block (get server-by-uuid uuid)]
|
|
||||||
(when (not= local-block server-block)
|
|
||||||
{:block/uuid uuid
|
|
||||||
:local-block local-block
|
|
||||||
:server-block server-block}))))
|
|
||||||
vec)))
|
|
||||||
|
|
||||||
(defn- <log-checksum-mismatch-diff!
|
|
||||||
[repo export-edn]
|
|
||||||
(p/let [server-diagnostics (<fetch-server-checksum-diagnostics repo)
|
|
||||||
server-blocks (map (fn [b]
|
|
||||||
{:block/uuid (uuid (:uuid b))
|
|
||||||
:block/parent (when-let [id (:parent b)] (uuid id))
|
|
||||||
:block/page (when-let [id (:page b)] (uuid id))
|
|
||||||
:block/order (:order b)})
|
|
||||||
(:blocks server-diagnostics))
|
|
||||||
diff-blocks (different-blocks (:blocks export-edn) server-blocks)
|
|
||||||
diff-data {:repo repo
|
|
||||||
:local-checksum (:local-checksum export-edn)
|
|
||||||
:remote-checksum (:remote-checksum export-edn)
|
|
||||||
:recomputed-checksum (:recomputed-checksum export-edn)
|
|
||||||
:server-checksum (:checksum server-diagnostics)
|
|
||||||
:different-blocks diff-blocks}]
|
|
||||||
(pprint/pprint diff-data)
|
|
||||||
(when (seq diff-blocks)
|
|
||||||
(js/console.warn "Checksum mismatch between client and server. Diff data:" diff-data))))
|
|
||||||
|
|
||||||
|
|
||||||
(defn ^:export recompute-checksum-diagnostics
|
|
||||||
[]
|
|
||||||
(if-let [repo (state/get-current-repo)]
|
|
||||||
(-> (state/<invoke-db-worker :thread-api/recompute-checksum-diagnostics repo)
|
|
||||||
(p/then (fn [{:keys [recomputed-checksum local-checksum remote-checksum blocks checksum-attrs e2ee?]
|
|
||||||
:as result}]
|
|
||||||
(if (map? result)
|
|
||||||
(let [export-edn {:repo repo
|
|
||||||
:generated-at (.toISOString (js/Date.))
|
|
||||||
:e2ee? e2ee?
|
|
||||||
:recomputed-checksum recomputed-checksum
|
|
||||||
:local-checksum local-checksum
|
|
||||||
:remote-checksum remote-checksum
|
|
||||||
:checksum-attrs checksum-attrs
|
|
||||||
:blocks blocks}
|
|
||||||
content (with-out-str (pprint/pprint export-edn))
|
|
||||||
blob (js/Blob. #js [content] (clj->js {:type "text/edn;charset=utf-8"}))
|
|
||||||
filename (checksum-export-file-name repo)]
|
|
||||||
(p/let [_ (-> (<log-checksum-mismatch-diff! repo export-edn)
|
|
||||||
(p/catch (fn [error]
|
|
||||||
(js/console.error "checksum mismatch diff fetch failed:" error)
|
|
||||||
nil)))]
|
|
||||||
(utils/saveToFile blob filename "edn")
|
|
||||||
(notification/show!
|
|
||||||
(t :graph.diagnostics/checksum-recomputed-success
|
|
||||||
recomputed-checksum
|
|
||||||
(or local-checksum "<nil>")
|
|
||||||
(or remote-checksum "<nil>")
|
|
||||||
filename
|
|
||||||
(count blocks)
|
|
||||||
(pr-str checksum-attrs))
|
|
||||||
:success
|
|
||||||
false)))
|
|
||||||
(notification/show! (t :graph.diagnostics/checksum-unavailable-warning) :warning))))
|
|
||||||
(p/catch (fn [error]
|
|
||||||
(js/console.error "recompute-checksum-diagnostics failed:" error)
|
|
||||||
(notification/show! (t :graph.diagnostics/checksum-failed-error) :error))))
|
|
||||||
(notification/show! (t :graph.diagnostics/no-graph-warning) :warning)))
|
|
||||||
|
|
||||||
(defn ^:export export-client-ops-sqlite
|
(defn ^:export export-client-ops-sqlite
|
||||||
[]
|
[]
|
||||||
(if (and util/web-platform? (not (util/electron?)))
|
(if (and util/web-platform? (not (util/electron?)))
|
||||||
@@ -238,9 +122,3 @@
|
|||||||
|
|
||||||
(defn ^:export replace-graph-with-db-file []
|
(defn ^:export replace-graph-with-db-file []
|
||||||
(state/pub-event! [:dialog-select/db-graph-replace]))
|
(state/pub-event! [:dialog-select/db-graph-replace]))
|
||||||
|
|
||||||
(defn ^:export rtc-stop []
|
|
||||||
(rtc-handler/<rtc-stop!))
|
|
||||||
|
|
||||||
(defn ^:export rtc-start []
|
|
||||||
(rtc-handler/<rtc-start! (state/get-current-repo)))
|
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
(ns frontend.handler.db-based.rtc-background-tasks
|
|
||||||
"Background tasks related to RTC"
|
|
||||||
(:require [cljs-time.core :as t]
|
|
||||||
[frontend.config :as config]
|
|
||||||
[frontend.handler.db-based.rtc-flows :as rtc-flows]
|
|
||||||
[frontend.handler.db-based.sync :as rtc-handler]
|
|
||||||
[frontend.state :as state]
|
|
||||||
[lambdaisland.glogi :as log]
|
|
||||||
[logseq.common.util :as common-util]
|
|
||||||
[promesa.core :as p]))
|
|
||||||
|
|
||||||
(defn- add-watch-when-not-publishing!
|
|
||||||
[atom' key' f]
|
|
||||||
(when-not config/publishing?
|
|
||||||
(remove-watch atom' key')
|
|
||||||
(add-watch atom' key' (fn [_ _ _ value] (f value)))
|
|
||||||
(when-some [value @atom']
|
|
||||||
(f value))))
|
|
||||||
|
|
||||||
(add-watch-when-not-publishing!
|
|
||||||
rtc-flows/rtc-try-restart
|
|
||||||
::restart-rtc-to-reconnect
|
|
||||||
(fn [{:keys [graph-uuid t]}]
|
|
||||||
(when (and graph-uuid t)
|
|
||||||
(p/let [repo (state/get-current-repo)
|
|
||||||
current-graph-uuid (state/<invoke-db-worker :thread-api/get-rtc-graph-uuid repo)]
|
|
||||||
(when (and (= graph-uuid (some-> current-graph-uuid str))
|
|
||||||
(> 5000 (- (common-util/time-ms) t)))
|
|
||||||
(log/info :trying-to-restart-rtc graph-uuid :t (t/now))
|
|
||||||
(rtc-handler/<rtc-start! repo :stop-before-start? false))))))
|
|
||||||
|
|
||||||
(add-watch-when-not-publishing!
|
|
||||||
rtc-flows/logout
|
|
||||||
::stop-rtc-if-needed
|
|
||||||
(fn [event]
|
|
||||||
(when (= :logout event)
|
|
||||||
(log/info :try-to-stop-rtc-if-needed :logout)
|
|
||||||
(rtc-handler/<rtc-stop!))))
|
|
||||||
|
|
||||||
(add-watch-when-not-publishing!
|
|
||||||
rtc-flows/trigger-start-rtc
|
|
||||||
::auto-start-rtc-if-possible
|
|
||||||
(fn [[start-reason repo]]
|
|
||||||
(log/info :try-to-start-rtc [start-reason repo])
|
|
||||||
(p/do! (rtc-handler/<rtc-start! (or repo (state/get-current-repo))))))
|
|
||||||
@@ -1,135 +0,0 @@
|
|||||||
(ns frontend.handler.db-based.rtc-flows
|
|
||||||
"Reactive RTC atoms."
|
|
||||||
(:require [frontend.flows :as flows]
|
|
||||||
[frontend.mobile.flows :as mobile-flows]
|
|
||||||
[logseq.common.util :as common-util]))
|
|
||||||
|
|
||||||
(def rtc-log
|
|
||||||
(let [state* (atom nil)
|
|
||||||
source (flows/sub-atom [:rtc/log])]
|
|
||||||
(add-watch source ::rtc-log
|
|
||||||
(fn [_ _ _ log]
|
|
||||||
(when-not (and (= :skip (:sub-type log))
|
|
||||||
(= :rtc.log/apply-remote-update (:type log)))
|
|
||||||
(reset! state* log))))
|
|
||||||
state*))
|
|
||||||
|
|
||||||
(def rtc-download-log
|
|
||||||
(let [state* (atom nil)]
|
|
||||||
(add-watch rtc-log ::rtc-download-log
|
|
||||||
(fn [_ _ _ log]
|
|
||||||
(when (keyword-identical? :rtc.log/download (:type log))
|
|
||||||
(reset! state* log))))
|
|
||||||
state*))
|
|
||||||
|
|
||||||
(def rtc-upload-log
|
|
||||||
(let [state* (atom nil)]
|
|
||||||
(add-watch rtc-log ::rtc-upload-log
|
|
||||||
(fn [_ _ _ log]
|
|
||||||
(when (keyword-identical? :rtc.log/upload (:type log))
|
|
||||||
(reset! state* log))))
|
|
||||||
state*))
|
|
||||||
|
|
||||||
(def rtc-misc-log
|
|
||||||
(let [state* (atom nil)]
|
|
||||||
(add-watch rtc-log ::rtc-misc-log
|
|
||||||
(fn [_ _ _ log]
|
|
||||||
(when-not (contains? #{:rtc.log/download :rtc.log/upload} (:type log))
|
|
||||||
(reset! state* log))))
|
|
||||||
state*))
|
|
||||||
|
|
||||||
(def rtc-state
|
|
||||||
(flows/sub-atom [:rtc/state]))
|
|
||||||
|
|
||||||
(def rtc-try-restart
|
|
||||||
"Updates when it's time to restart rtc loop.
|
|
||||||
conditions:
|
|
||||||
- user logged in
|
|
||||||
- no rtc loop running now
|
|
||||||
- last rtc stop-reason is websocket message timeout
|
|
||||||
- online
|
|
||||||
- throttle 5000ms"
|
|
||||||
(let [state* (atom nil)
|
|
||||||
last-emitted-at (atom 0)
|
|
||||||
update! (fn []
|
|
||||||
(let [{:keys [rtc-lock last-stop-exception-ex-data graph-uuid]} @rtc-state
|
|
||||||
login-user @flows/current-login-user
|
|
||||||
online? @flows/network-online?
|
|
||||||
now (common-util/time-ms)]
|
|
||||||
(when (and online?
|
|
||||||
(some? (:email login-user))
|
|
||||||
(some? graph-uuid)
|
|
||||||
(not rtc-lock)
|
|
||||||
(= :rtc.exception/ws-timeout (:type last-stop-exception-ex-data))
|
|
||||||
(>= (- now @last-emitted-at) 5000))
|
|
||||||
(reset! last-emitted-at now)
|
|
||||||
(reset! state* {:graph-uuid graph-uuid :t now}))))]
|
|
||||||
(doseq [[watch-key atom'] {::rtc-try-restart-state rtc-state
|
|
||||||
::rtc-try-restart-online flows/network-online?
|
|
||||||
::rtc-try-restart-user flows/current-login-user}]
|
|
||||||
(add-watch atom' watch-key (fn [_ _ _ _] (update!))))
|
|
||||||
state*))
|
|
||||||
|
|
||||||
(def logout
|
|
||||||
(let [state* (atom nil)]
|
|
||||||
(add-watch flows/current-login-user ::logout
|
|
||||||
(fn [_ _ _ user]
|
|
||||||
(when (= :logout user)
|
|
||||||
(reset! state* :logout))))
|
|
||||||
state*))
|
|
||||||
|
|
||||||
(def ^:private *rtc-start-trigger (atom nil))
|
|
||||||
|
|
||||||
(defn trigger-rtc-start
|
|
||||||
[repo]
|
|
||||||
(assert (some? repo))
|
|
||||||
(reset! *rtc-start-trigger repo))
|
|
||||||
|
|
||||||
(defn- document-visible->restart-event
|
|
||||||
[visibility]
|
|
||||||
(when (= "visible" visibility)
|
|
||||||
:document-visible&rtc-not-running))
|
|
||||||
|
|
||||||
(defn- network-online->restart-event
|
|
||||||
[online?]
|
|
||||||
(when online?
|
|
||||||
:network-online&rtc-not-running))
|
|
||||||
|
|
||||||
(defn- mobile-app-active->restart-event
|
|
||||||
[app-active?]
|
|
||||||
(when app-active?
|
|
||||||
:mobile-app-active&rtc-not-running))
|
|
||||||
|
|
||||||
(def trigger-start-rtc
|
|
||||||
(let [state* (atom nil)
|
|
||||||
timeout-id (atom nil)
|
|
||||||
emit! (fn [event]
|
|
||||||
(when @flows/current-login-user
|
|
||||||
(when-let [id @timeout-id]
|
|
||||||
(js/clearTimeout id))
|
|
||||||
(reset! timeout-id (js/setTimeout #(reset! state* event) 50))))]
|
|
||||||
(add-watch flows/current-login-user ::trigger-start-rtc-login
|
|
||||||
(fn [_ _ _ user]
|
|
||||||
(when (:email user)
|
|
||||||
(emit! [:login]))))
|
|
||||||
(add-watch flows/current-repo ::trigger-start-rtc-repo
|
|
||||||
(fn [_ _ _ repo]
|
|
||||||
(when repo
|
|
||||||
(emit! [:graph-switch repo]))))
|
|
||||||
(add-watch *rtc-start-trigger ::trigger-start-rtc-manual
|
|
||||||
(fn [_ _ _ repo]
|
|
||||||
(when repo
|
|
||||||
(emit! [:trigger-rtc repo]))))
|
|
||||||
(add-watch flows/document-visibility-state ::trigger-start-rtc-visible
|
|
||||||
(fn [_ _ _ visibility]
|
|
||||||
(when-let [event (document-visible->restart-event visibility)]
|
|
||||||
(emit! [event]))))
|
|
||||||
(add-watch flows/network-online? ::trigger-start-rtc-online
|
|
||||||
(fn [_ _ _ online?]
|
|
||||||
(when-let [event (network-online->restart-event online?)]
|
|
||||||
(emit! [event]))))
|
|
||||||
(add-watch mobile-flows/*mobile-app-state ::trigger-start-rtc-mobile-active
|
|
||||||
(fn [_ _ _ app-active?]
|
|
||||||
(when-let [event (mobile-app-active->restart-event app-active?)]
|
|
||||||
(emit! [event]))))
|
|
||||||
state*))
|
|
||||||
@@ -1,463 +0,0 @@
|
|||||||
(ns frontend.handler.db-based.sync
|
|
||||||
"DB-sync handler based on Cloudflare Durable Objects."
|
|
||||||
(:require [clojure.string :as string]
|
|
||||||
[frontend.config :as config]
|
|
||||||
[frontend.context.i18n :refer [t]]
|
|
||||||
[frontend.handler.notification :as notification]
|
|
||||||
[frontend.handler.repo :as repo-handler]
|
|
||||||
[frontend.handler.user :as user-handler]
|
|
||||||
[frontend.persist-db :as persist-db]
|
|
||||||
[frontend.state :as state]
|
|
||||||
[frontend.util :as util]
|
|
||||||
[lambdaisland.glogi :as log]
|
|
||||||
[logseq.db-sync.malli-schema :as db-sync-schema]
|
|
||||||
[promesa.core :as p]))
|
|
||||||
|
|
||||||
(defn- ws->http-base [ws-url]
|
|
||||||
(when (string? ws-url)
|
|
||||||
(let [base (cond
|
|
||||||
(string/starts-with? ws-url "wss://")
|
|
||||||
(str "https://" (subs ws-url (count "wss://")))
|
|
||||||
|
|
||||||
(string/starts-with? ws-url "ws://")
|
|
||||||
(str "http://" (subs ws-url (count "ws://")))
|
|
||||||
|
|
||||||
:else ws-url)
|
|
||||||
base (string/replace base #"/sync/%s$" "")]
|
|
||||||
base)))
|
|
||||||
|
|
||||||
(defn http-base []
|
|
||||||
(or (config/db-sync-http-base)
|
|
||||||
(ws->http-base (config/db-sync-ws-url))))
|
|
||||||
|
|
||||||
(defn- auth-headers []
|
|
||||||
(when-let [token (state/get-auth-id-token)]
|
|
||||||
{"authorization" (str "Bearer " token)}))
|
|
||||||
|
|
||||||
(defn- with-auth-headers [opts]
|
|
||||||
(if-let [auth (auth-headers)]
|
|
||||||
(assoc opts :headers (merge (or (:headers opts) {}) auth))
|
|
||||||
opts))
|
|
||||||
|
|
||||||
(declare fetch-json)
|
|
||||||
|
|
||||||
(declare coerce-http-response)
|
|
||||||
(declare <sync-auth-state-to-db-worker!)
|
|
||||||
|
|
||||||
(defn fetch-json
|
|
||||||
[url opts {:keys [response-schema error-schema] :or {error-schema :error}}]
|
|
||||||
(p/let [resp (js/fetch url (clj->js (with-auth-headers opts)))
|
|
||||||
text (.text resp)
|
|
||||||
data (when (seq text) (js/JSON.parse text))]
|
|
||||||
(if (.-ok resp)
|
|
||||||
(let [body (js->clj data :keywordize-keys true)
|
|
||||||
body (if response-schema
|
|
||||||
(coerce-http-response response-schema body)
|
|
||||||
body)]
|
|
||||||
(if (or (nil? response-schema) body)
|
|
||||||
body
|
|
||||||
(throw (ex-info "db-sync invalid response"
|
|
||||||
{:status (.-status resp)
|
|
||||||
:url url
|
|
||||||
:body body}))))
|
|
||||||
(let [body (when data (js->clj data :keywordize-keys true))
|
|
||||||
body (if error-schema
|
|
||||||
(coerce-http-response error-schema body)
|
|
||||||
body)]
|
|
||||||
(throw (ex-info "db-sync request failed"
|
|
||||||
{:status (.-status resp)
|
|
||||||
:url url
|
|
||||||
:body body}))))))
|
|
||||||
|
|
||||||
(def ^:private invalid-coerce ::invalid-coerce)
|
|
||||||
|
|
||||||
(defn- coerce
|
|
||||||
[coercer value context]
|
|
||||||
(try
|
|
||||||
(coercer value)
|
|
||||||
(catch :default e
|
|
||||||
(log/error :db-sync/malli-coerce-failed (merge context {:error e :value value}))
|
|
||||||
invalid-coerce)))
|
|
||||||
|
|
||||||
(defn- coerce-http-request [schema-key body]
|
|
||||||
(if-let [coercer (get db-sync-schema/http-request-coercers schema-key)]
|
|
||||||
(let [coerced (coerce coercer body {:schema schema-key :dir :request})]
|
|
||||||
(when-not (= coerced invalid-coerce)
|
|
||||||
coerced))
|
|
||||||
body))
|
|
||||||
|
|
||||||
(defn- coerce-http-response [schema-key body]
|
|
||||||
(if-let [coercer (get db-sync-schema/http-response-coercers schema-key)]
|
|
||||||
(let [coerced (coerce coercer body {:schema schema-key :dir :response})]
|
|
||||||
(when-not (= coerced invalid-coerce)
|
|
||||||
coerced))
|
|
||||||
body))
|
|
||||||
|
|
||||||
(defn- remote-graph
|
|
||||||
[repo]
|
|
||||||
(some #(when (= repo (:url %)) %) (state/get-rtc-graphs)))
|
|
||||||
|
|
||||||
(defn- <get-rtc-graph-uuid
|
|
||||||
[repo]
|
|
||||||
(p/let [graph-uuid (state/<invoke-db-worker :thread-api/get-rtc-graph-uuid repo)]
|
|
||||||
(some-> graph-uuid str)))
|
|
||||||
|
|
||||||
(defn- <get-rtc-graph-e2ee?
|
|
||||||
[repo]
|
|
||||||
(state/<invoke-db-worker :thread-api/get-key-value repo :logseq.kv/graph-rtc-e2ee?))
|
|
||||||
|
|
||||||
(defn- <ensure-invite-auth!
|
|
||||||
[]
|
|
||||||
(user-handler/<ensure-id&access-token!))
|
|
||||||
|
|
||||||
(defn- <grant-graph-access-for-invite!
|
|
||||||
[repo graph-uuid email]
|
|
||||||
(p/let [e2ee? (when repo (<get-rtc-graph-e2ee? repo))]
|
|
||||||
(when (and repo e2ee?)
|
|
||||||
(state/<invoke-db-worker :thread-api/db-sync-grant-graph-access
|
|
||||||
repo graph-uuid email))))
|
|
||||||
|
|
||||||
(defn- should-start-rtc?
|
|
||||||
[repo]
|
|
||||||
(and (not (true? (:rtc/uploading? (state/get-state))))
|
|
||||||
(let [graph (remote-graph repo)]
|
|
||||||
(and (some? graph)
|
|
||||||
(not= false (:graph-ready-for-use? graph))))))
|
|
||||||
|
|
||||||
(defn- normalize-graph-e2ee?
|
|
||||||
[graph-e2ee?]
|
|
||||||
(if (nil? graph-e2ee?)
|
|
||||||
true
|
|
||||||
(true? graph-e2ee?)))
|
|
||||||
|
|
||||||
(defn- active-graph-operation []
|
|
||||||
(let [{:rtc/keys [downloading-graph-uuid uploading?]} (state/get-state)]
|
|
||||||
(cond
|
|
||||||
downloading-graph-uuid
|
|
||||||
{:active-operation :download
|
|
||||||
:active-graph-uuid downloading-graph-uuid}
|
|
||||||
|
|
||||||
(true? uploading?)
|
|
||||||
{:active-operation :upload})))
|
|
||||||
|
|
||||||
(defn- reject-graph-operation-in-progress
|
|
||||||
[requested-operation active-operation]
|
|
||||||
(p/rejected
|
|
||||||
(ex-info "graph operation already in progress"
|
|
||||||
(assoc active-operation
|
|
||||||
:type :db-sync/graph-operation-in-progress
|
|
||||||
:requested-operation requested-operation))))
|
|
||||||
|
|
||||||
(defn- <ensure-download-runtime-bound!
|
|
||||||
[repo]
|
|
||||||
(if (util/electron?)
|
|
||||||
(p/let [_ (persist-db/<open-and-fetch-schema repo {:sync-download-graph? true})
|
|
||||||
_ (<sync-auth-state-to-db-worker!)]
|
|
||||||
nil)
|
|
||||||
(p/resolved nil)))
|
|
||||||
|
|
||||||
(defn- <ensure-user-rsa-keys-on-server!
|
|
||||||
[{:keys [server-rsa-keys-exists?]}]
|
|
||||||
(if (not= false server-rsa-keys-exists?)
|
|
||||||
(p/resolved nil)
|
|
||||||
(if @state/*db-worker
|
|
||||||
(-> (p/let [_ (<sync-auth-state-to-db-worker!)]
|
|
||||||
(state/<invoke-db-worker :thread-api/db-sync-ensure-user-rsa-keys
|
|
||||||
{:ensure-server? true
|
|
||||||
:server-rsa-keys-exists? false}))
|
|
||||||
(p/catch (fn [error]
|
|
||||||
(log/error :db-sync/ensure-user-rsa-keys-failed
|
|
||||||
{:error error
|
|
||||||
:reason :server-rsa-keys-missing})
|
|
||||||
nil)))
|
|
||||||
(do
|
|
||||||
(log/warn :db-sync/ensure-user-rsa-keys-skipped
|
|
||||||
{:reason :db-worker-not-ready
|
|
||||||
:server-rsa-keys-exists? server-rsa-keys-exists?})
|
|
||||||
(p/resolved nil)))))
|
|
||||||
|
|
||||||
(defn- <wait-for-db-worker-ready!
|
|
||||||
[]
|
|
||||||
(if @state/*db-worker
|
|
||||||
(p/resolved true)
|
|
||||||
(let [ready (p/deferred)
|
|
||||||
watch-key (keyword "frontend.handler.db-based.sync"
|
|
||||||
(str "wait-db-worker-ready-" (random-uuid)))]
|
|
||||||
(add-watch state/*db-worker watch-key
|
|
||||||
(fn [_ _ _ worker]
|
|
||||||
(when worker
|
|
||||||
(remove-watch state/*db-worker watch-key)
|
|
||||||
(p/resolve! ready true))))
|
|
||||||
;; If worker becomes ready between the initial check and add-watch.
|
|
||||||
(when @state/*db-worker
|
|
||||||
(remove-watch state/*db-worker watch-key)
|
|
||||||
(p/resolve! ready true))
|
|
||||||
ready)))
|
|
||||||
|
|
||||||
(defn <rtc-stop!
|
|
||||||
[]
|
|
||||||
(if @state/*db-worker
|
|
||||||
(do
|
|
||||||
(log/info :db-sync/stop true)
|
|
||||||
(state/<invoke-db-worker :thread-api/db-sync-stop))
|
|
||||||
(do
|
|
||||||
(log/info :db-sync/stop-skipped {:reason :db-worker-not-ready})
|
|
||||||
(p/resolved nil))))
|
|
||||||
|
|
||||||
(defn- sync-app-state-payload
|
|
||||||
[]
|
|
||||||
(let [payload (select-keys (state/get-state) [:git/current-repo :config
|
|
||||||
:auth/id-token :auth/access-token :auth/refresh-token
|
|
||||||
:auth/oauth-token-url :auth/oauth-domain :auth/oauth-client-id
|
|
||||||
:user/info])]
|
|
||||||
(cond-> (if (nil? (:git/current-repo payload))
|
|
||||||
(dissoc payload :git/current-repo)
|
|
||||||
payload)
|
|
||||||
(seq config/OAUTH-DOMAIN)
|
|
||||||
(assoc :auth/oauth-domain config/OAUTH-DOMAIN)
|
|
||||||
|
|
||||||
(seq config/COGNITO-CLIENT-ID)
|
|
||||||
(assoc :auth/oauth-client-id config/COGNITO-CLIENT-ID))))
|
|
||||||
|
|
||||||
(defn- <sync-auth-state-to-db-worker!
|
|
||||||
[]
|
|
||||||
(p/let [_ (user-handler/<ensure-id&access-token!)
|
|
||||||
payload (sync-app-state-payload)]
|
|
||||||
(state/<invoke-db-worker :thread-api/sync-app-state payload)))
|
|
||||||
|
|
||||||
(defn <rtc-start!
|
|
||||||
[repo & {:keys [_stop-before-start?] :as _opts}]
|
|
||||||
(p/let [_ (<wait-for-db-worker-ready!)]
|
|
||||||
(if (should-start-rtc? repo)
|
|
||||||
(do
|
|
||||||
(log/info :db-sync/start {:repo repo})
|
|
||||||
(p/let [_ (<sync-auth-state-to-db-worker!)]
|
|
||||||
(state/<invoke-db-worker :thread-api/db-sync-start repo)))
|
|
||||||
(do
|
|
||||||
(log/info :db-sync/skip-start {:repo repo :reason :graph-not-in-remote-list
|
|
||||||
:remote-graphs-loading? (:rtc/loading-graphs? (state/get-state))})
|
|
||||||
(<rtc-stop!)))))
|
|
||||||
|
|
||||||
(defonce ^:private debounced-update-presence
|
|
||||||
(util/debounce
|
|
||||||
(fn [editing-block-uuid]
|
|
||||||
(state/<invoke-db-worker :thread-api/db-sync-update-presence editing-block-uuid))
|
|
||||||
1000))
|
|
||||||
|
|
||||||
(defn <rtc-update-presence!
|
|
||||||
[editing-block-uuid]
|
|
||||||
(debounced-update-presence editing-block-uuid))
|
|
||||||
|
|
||||||
(defn <rtc-get-users-info
|
|
||||||
([] (<rtc-get-users-info false))
|
|
||||||
([force?]
|
|
||||||
(let [repo (state/get-current-repo)]
|
|
||||||
(p/let [graph-uuid (<get-rtc-graph-uuid repo)]
|
|
||||||
(when graph-uuid
|
|
||||||
(let [base (http-base)
|
|
||||||
users-info (state/get-state :rtc/users-info)
|
|
||||||
cached-users (get users-info repo)]
|
|
||||||
(cond
|
|
||||||
(and (not force?) (contains? users-info repo))
|
|
||||||
(p/resolved cached-users)
|
|
||||||
|
|
||||||
base
|
|
||||||
(p/let [_ (user-handler/<ensure-id&access-token!)
|
|
||||||
resp (fetch-json (str base "/graphs/" graph-uuid "/members")
|
|
||||||
{:method "GET"}
|
|
||||||
{:response-schema :graph-members/list})
|
|
||||||
members (:members resp)
|
|
||||||
users (mapv (fn [{:keys [user-id role email username]}]
|
|
||||||
(let [name (or username email user-id)
|
|
||||||
user-type (some-> role keyword)]
|
|
||||||
(cond-> {:user/uuid user-id
|
|
||||||
:user/name name
|
|
||||||
:graph<->user/user-type user-type}
|
|
||||||
(string? email) (assoc :user/email email))))
|
|
||||||
members)]
|
|
||||||
(state/set-state! :rtc/users-info users :nested-path repo)
|
|
||||||
users)
|
|
||||||
|
|
||||||
:else
|
|
||||||
(p/resolved nil))))))))
|
|
||||||
|
|
||||||
(defn <rtc-create-graph!
|
|
||||||
([repo]
|
|
||||||
(<rtc-create-graph! repo true true))
|
|
||||||
([repo graph-e2ee?]
|
|
||||||
(<rtc-create-graph! repo graph-e2ee? true))
|
|
||||||
([repo graph-e2ee? graph-ready-for-use?]
|
|
||||||
(state/<invoke-db-worker :thread-api/db-sync-create-remote-graph
|
|
||||||
repo graph-e2ee? graph-ready-for-use?)))
|
|
||||||
|
|
||||||
(defn <rtc-delete-graph!
|
|
||||||
[graph-uuid _schema-version]
|
|
||||||
(let [base (http-base)]
|
|
||||||
(if (and graph-uuid base)
|
|
||||||
(p/let [_ (user-handler/<ensure-id&access-token!)]
|
|
||||||
(fetch-json (str base "/graphs/" graph-uuid)
|
|
||||||
{:method "DELETE"}
|
|
||||||
{:response-schema :graphs/delete}))
|
|
||||||
(p/rejected (ex-info "db-sync missing graph id"
|
|
||||||
{:type :db-sync/invalid-graph
|
|
||||||
:graph-uuid graph-uuid
|
|
||||||
:base base})))))
|
|
||||||
|
|
||||||
(defn <rtc-download-graph!
|
|
||||||
([graph-name graph-uuid]
|
|
||||||
(<rtc-download-graph! graph-name graph-uuid true))
|
|
||||||
([graph-name graph-uuid graph-e2ee?]
|
|
||||||
(if-let [operation (active-graph-operation)]
|
|
||||||
(reject-graph-operation-in-progress :download operation)
|
|
||||||
(do
|
|
||||||
(state/set-state! :rtc/downloading-graph-uuid graph-uuid)
|
|
||||||
(state/pub-event!
|
|
||||||
[:rtc/log {:type :rtc.log/download
|
|
||||||
:sub-type :download-progress
|
|
||||||
:graph-uuid graph-uuid
|
|
||||||
:message "Preparing graph snapshot download"}])
|
|
||||||
(let [graph-e2ee? (normalize-graph-e2ee? graph-e2ee?)
|
|
||||||
base (http-base)]
|
|
||||||
(-> (if (and graph-uuid base)
|
|
||||||
(p/let [_ (user-handler/<ensure-id&access-token!)
|
|
||||||
graph (str config/db-version-prefix graph-name)
|
|
||||||
_ (<ensure-download-runtime-bound! graph)
|
|
||||||
_ (state/<invoke-db-worker :thread-api/db-sync-download-graph-by-id
|
|
||||||
graph graph-uuid graph-e2ee?)
|
|
||||||
_ (when (util/electron?)
|
|
||||||
(state/<invoke-db-worker :thread-api/db-sync-download-missing-assets
|
|
||||||
graph graph-uuid))]
|
|
||||||
true)
|
|
||||||
(p/rejected (ex-info "db-sync missing graph info"
|
|
||||||
{:type :db-sync/invalid-graph
|
|
||||||
:graph-uuid graph-uuid
|
|
||||||
:base base})))
|
|
||||||
(p/catch (fn [error]
|
|
||||||
(throw error)))
|
|
||||||
(p/finally
|
|
||||||
(fn []
|
|
||||||
(state/set-state! :rtc/downloading-graph-uuid nil)))))))))
|
|
||||||
|
|
||||||
(defn <get-remote-graphs
|
|
||||||
[]
|
|
||||||
(let [base (http-base)]
|
|
||||||
(if-not base
|
|
||||||
(p/resolved [])
|
|
||||||
(-> (p/let [_ (state/set-state! :rtc/loading-graphs? true)
|
|
||||||
_ (user-handler/<ensure-id&access-token!)
|
|
||||||
resp (fetch-json (str base "/graphs")
|
|
||||||
{:method "GET"}
|
|
||||||
{:response-schema :graphs/list})
|
|
||||||
_ (<ensure-user-rsa-keys-on-server! {:server-rsa-keys-exists?
|
|
||||||
(:user-rsa-keys-exists? resp)})
|
|
||||||
graphs (:graphs resp)
|
|
||||||
result (mapv (fn [graph]
|
|
||||||
(let [graph-e2ee? (if (contains? graph :graph-e2ee?)
|
|
||||||
(normalize-graph-e2ee? (:graph-e2ee? graph))
|
|
||||||
true)
|
|
||||||
graph-ready-for-use? (not= false (:graph-ready-for-use? graph))]
|
|
||||||
(merge
|
|
||||||
{:url (str config/db-version-prefix (:graph-name graph))
|
|
||||||
:GraphName (:graph-name graph)
|
|
||||||
:GraphSchemaVersion (:schema-version graph)
|
|
||||||
:GraphUUID (:graph-id graph)
|
|
||||||
:rtc-graph? true
|
|
||||||
:graph-e2ee? graph-e2ee?
|
|
||||||
:graph-ready-for-use? graph-ready-for-use?
|
|
||||||
:graph<->user-user-type (:role graph)
|
|
||||||
:graph<->user-grant-by-user (:invited-by graph)}
|
|
||||||
(dissoc graph :graph-id :graph-name :schema-version :role :invited-by :graph-ready-for-use?))))
|
|
||||||
graphs)]
|
|
||||||
(state/set-state! :rtc/graphs result)
|
|
||||||
(repo-handler/refresh-repos!)
|
|
||||||
result)
|
|
||||||
(p/finally
|
|
||||||
(fn []
|
|
||||||
(state/set-state! :rtc/loading-graphs? false)))))))
|
|
||||||
|
|
||||||
(defn <rtc-invite-email
|
|
||||||
[graph-uuid email]
|
|
||||||
(let [base (http-base)
|
|
||||||
graph-uuid (str graph-uuid)]
|
|
||||||
(if (and base (string? graph-uuid) (string? email))
|
|
||||||
(->
|
|
||||||
(p/let [_ (<ensure-invite-auth!)
|
|
||||||
body (coerce-http-request :graph-members/create
|
|
||||||
{:email email
|
|
||||||
:role "member"})
|
|
||||||
_ (when (nil? body)
|
|
||||||
(throw (ex-info "db-sync invalid invite body"
|
|
||||||
{:graph-uuid graph-uuid
|
|
||||||
:email email})))
|
|
||||||
_ (fetch-json (str base "/graphs/" graph-uuid "/members")
|
|
||||||
{:method "POST"
|
|
||||||
:headers {"content-type" "application/json"}
|
|
||||||
:body (js/JSON.stringify (clj->js body))}
|
|
||||||
{:response-schema :graph-members/create})
|
|
||||||
repo (state/get-current-repo)
|
|
||||||
_ (<grant-graph-access-for-invite! repo graph-uuid email)
|
|
||||||
_ (<rtc-get-users-info true)]
|
|
||||||
(notification/show! (t :sync/invitation-sent) :success))
|
|
||||||
(p/catch (fn [e]
|
|
||||||
(if (= "user not found" (get-in (ex-data e) [:body :error]))
|
|
||||||
(notification/show! (t :sync/user-doesnt-exist-yet) :warning)
|
|
||||||
(do
|
|
||||||
(notification/show! (t :sync/something-wrong) :error)
|
|
||||||
(log/error :db-sync/invite-email-failed
|
|
||||||
{:error e
|
|
||||||
:graph-uuid graph-uuid
|
|
||||||
:email email}))))))
|
|
||||||
(p/rejected (ex-info "db-sync missing invite info"
|
|
||||||
{:type :db-sync/invalid-invite
|
|
||||||
:graph-uuid graph-uuid
|
|
||||||
:email email
|
|
||||||
:base base})))))
|
|
||||||
|
|
||||||
(defn <rtc-remove-member!
|
|
||||||
[graph-uuid member-id]
|
|
||||||
(let [base (http-base)
|
|
||||||
graph-uuid (some-> graph-uuid str)
|
|
||||||
member-id (some-> member-id str)]
|
|
||||||
(if (and base (string? graph-uuid) (string? member-id))
|
|
||||||
(p/let [_ (user-handler/<ensure-id&access-token!)]
|
|
||||||
(fetch-json (str base "/graphs/" graph-uuid "/members/" member-id)
|
|
||||||
{:method "DELETE"}
|
|
||||||
{:response-schema :graph-members/delete}))
|
|
||||||
(p/rejected (ex-info "db-sync missing member info"
|
|
||||||
{:type :db-sync/invalid-member
|
|
||||||
:graph-uuid graph-uuid
|
|
||||||
:member-id member-id
|
|
||||||
:base base})))))
|
|
||||||
|
|
||||||
(defn <rtc-leave-graph!
|
|
||||||
[graph-uuid]
|
|
||||||
(if-let [member-id (user-handler/user-uuid)]
|
|
||||||
(<rtc-remove-member! graph-uuid member-id)
|
|
||||||
(p/rejected (ex-info "db-sync missing user id"
|
|
||||||
{:type :db-sync/invalid-member
|
|
||||||
:graph-uuid graph-uuid}))))
|
|
||||||
|
|
||||||
(defn <rtc-upload-graph!
|
|
||||||
[repo _graph-e2ee?]
|
|
||||||
(if-let [operation (active-graph-operation)]
|
|
||||||
(reject-graph-operation-in-progress :upload operation)
|
|
||||||
(do
|
|
||||||
(state/set-state! :rtc/uploading? true)
|
|
||||||
(-> (p/let [_ (<sync-auth-state-to-db-worker!)
|
|
||||||
_ (state/<invoke-db-worker :thread-api/db-sync-upload-graph repo)
|
|
||||||
_ (<get-remote-graphs)
|
|
||||||
_ (state/set-state! :rtc/uploading? false)
|
|
||||||
_ (<rtc-start! repo)]
|
|
||||||
true)
|
|
||||||
(p/finally
|
|
||||||
(fn []
|
|
||||||
(state/set-state! :rtc/uploading? false)))))))
|
|
||||||
|
|
||||||
(defn <rtc-create-graph-and-start-sync!
|
|
||||||
[repo graph-e2ee?]
|
|
||||||
(p/let [graph-id (<rtc-create-graph! repo graph-e2ee? true)]
|
|
||||||
(when (nil? graph-id)
|
|
||||||
(throw (ex-info "graph id doesn't exist when creating remote graph" {:repo repo})))
|
|
||||||
(p/do!
|
|
||||||
(<get-remote-graphs)
|
|
||||||
(<rtc-start! repo))))
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
(ns frontend.handler.e2ee
|
|
||||||
"rtc E2EE related fns"
|
|
||||||
(:require [electron.ipc :as ipc]
|
|
||||||
[frontend.mobile.secure-storage :as secure-storage]
|
|
||||||
[frontend.util :as util]
|
|
||||||
[promesa.core :as p]))
|
|
||||||
|
|
||||||
(def ^:private save-op :keychain/save-e2ee-password)
|
|
||||||
(def ^:private get-op :keychain/get-e2ee-password)
|
|
||||||
(def ^:private delete-op :keychain/delete-e2ee-password)
|
|
||||||
|
|
||||||
(defn- <keychain-save!
|
|
||||||
[key encrypted-text]
|
|
||||||
(cond
|
|
||||||
(util/electron?)
|
|
||||||
(ipc/ipc save-op key encrypted-text)
|
|
||||||
|
|
||||||
(util/capacitor?)
|
|
||||||
(secure-storage/<set-item! key encrypted-text)
|
|
||||||
|
|
||||||
:else
|
|
||||||
(p/resolved nil)))
|
|
||||||
|
|
||||||
(defn- <keychain-get
|
|
||||||
[key]
|
|
||||||
(cond
|
|
||||||
(util/electron?)
|
|
||||||
(ipc/ipc get-op key)
|
|
||||||
|
|
||||||
(util/capacitor?)
|
|
||||||
(secure-storage/<get-item key)
|
|
||||||
|
|
||||||
:else
|
|
||||||
(p/resolved nil)))
|
|
||||||
|
|
||||||
(defn- <keychain-delete!
|
|
||||||
[key]
|
|
||||||
(cond
|
|
||||||
(util/electron?)
|
|
||||||
(ipc/ipc delete-op key)
|
|
||||||
|
|
||||||
(util/capacitor?)
|
|
||||||
(secure-storage/<remove-item! key)
|
|
||||||
|
|
||||||
:else
|
|
||||||
(p/resolved nil)))
|
|
||||||
|
|
||||||
(defn native-storage-supported?
|
|
||||||
[]
|
|
||||||
(or (util/electron?) (util/capacitor?)))
|
|
||||||
|
|
||||||
(defn <native-save-secret!
|
|
||||||
[key encrypted-text]
|
|
||||||
(if (native-storage-supported?)
|
|
||||||
(<keychain-save! key encrypted-text)
|
|
||||||
(p/resolved nil)))
|
|
||||||
|
|
||||||
(defn <native-get-secret
|
|
||||||
[key]
|
|
||||||
(if (native-storage-supported?)
|
|
||||||
(<keychain-get key)
|
|
||||||
(p/resolved nil)))
|
|
||||||
|
|
||||||
(defn <native-delete-secret!
|
|
||||||
[key]
|
|
||||||
(if (native-storage-supported?)
|
|
||||||
(<keychain-delete! key)
|
|
||||||
(p/resolved nil)))
|
|
||||||
@@ -8,7 +8,6 @@
|
|||||||
[cljs-time.core :as t]
|
[cljs-time.core :as t]
|
||||||
[clojure.string :as string]
|
[clojure.string :as string]
|
||||||
[frontend.commands :as commands]
|
[frontend.commands :as commands]
|
||||||
[frontend.components.rtc.download-progress :as download-progress]
|
|
||||||
[frontend.config :as config]
|
[frontend.config :as config]
|
||||||
[frontend.context.i18n :refer [t]]
|
[frontend.context.i18n :refer [t]]
|
||||||
[frontend.date :as date]
|
[frontend.date :as date]
|
||||||
@@ -20,10 +19,7 @@
|
|||||||
[frontend.handler.comments :as comments-handler]
|
[frontend.handler.comments :as comments-handler]
|
||||||
[frontend.handler.common.page :as page-common-handler]
|
[frontend.handler.common.page :as page-common-handler]
|
||||||
[frontend.handler.db-based.property :as db-property-handler]
|
[frontend.handler.db-based.property :as db-property-handler]
|
||||||
[frontend.handler.db-based.rtc-flows :as rtc-flows]
|
|
||||||
[frontend.handler.db-based.sync :as rtc-handler]
|
|
||||||
[frontend.handler.editor :as editor-handler]
|
[frontend.handler.editor :as editor-handler]
|
||||||
[frontend.handler.events.rtc-error :as rtc-error]
|
|
||||||
[frontend.handler.export :as export]
|
[frontend.handler.export :as export]
|
||||||
[frontend.handler.graph :as graph-handler]
|
[frontend.handler.graph :as graph-handler]
|
||||||
[frontend.handler.notification :as notification]
|
[frontend.handler.notification :as notification]
|
||||||
@@ -34,7 +30,6 @@
|
|||||||
[frontend.handler.route :as route-handler]
|
[frontend.handler.route :as route-handler]
|
||||||
[frontend.handler.shell :as shell-handler]
|
[frontend.handler.shell :as shell-handler]
|
||||||
[frontend.handler.ui :as ui-handler]
|
[frontend.handler.ui :as ui-handler]
|
||||||
[frontend.handler.user :as user-handler]
|
|
||||||
[frontend.mobile.util :as mobile-util]
|
[frontend.mobile.util :as mobile-util]
|
||||||
[frontend.modules.instrumentation.posthog :as posthog]
|
[frontend.modules.instrumentation.posthog :as posthog]
|
||||||
[frontend.modules.outliner.pipeline :as pipeline]
|
[frontend.modules.outliner.pipeline :as pipeline]
|
||||||
@@ -283,7 +278,6 @@
|
|||||||
(when graph
|
(when graph
|
||||||
(schedule-search-index-build! graph))
|
(schedule-search-index-build! graph))
|
||||||
(export/auto-db-backup! graph)
|
(export/auto-db-backup! graph)
|
||||||
(rtc-flows/trigger-rtc-start graph)
|
|
||||||
(fsrs/update-due-cards-count)
|
(fsrs/update-due-cards-count)
|
||||||
nil)
|
nil)
|
||||||
|
|
||||||
@@ -414,69 +408,6 @@
|
|||||||
(<get-upsert-type-block repo (:block/uuid db-block))))]
|
(<get-upsert-type-block repo (:block/uuid db-block))))]
|
||||||
(js/setTimeout #(editor-handler/edit-block! converted-block :max) 100)))))
|
(js/setTimeout #(editor-handler/edit-block! converted-block :max) 100)))))
|
||||||
|
|
||||||
(defn- editing-users-by-block
|
|
||||||
[online-users current-user-uuid]
|
|
||||||
(reduce (fn [result {:user/keys [editing-block-uuid uuid] :as user}]
|
|
||||||
(if (and (string? editing-block-uuid)
|
|
||||||
(not= uuid current-user-uuid)
|
|
||||||
(not (contains? result editing-block-uuid)))
|
|
||||||
(assoc result editing-block-uuid user)
|
|
||||||
result))
|
|
||||||
{}
|
|
||||||
online-users))
|
|
||||||
|
|
||||||
(defn- sync-editing-users-by-block!
|
|
||||||
[online-users]
|
|
||||||
(let [current-user-uuid (user-handler/user-uuid)
|
|
||||||
old-users-by-block (or (state/get-state :rtc/editing-users-by-block) {})
|
|
||||||
new-users-by-block (editing-users-by-block online-users current-user-uuid)
|
|
||||||
affected-block-ids (into (set (keys old-users-by-block))
|
|
||||||
(keys new-users-by-block))]
|
|
||||||
(doseq [block-id affected-block-ids
|
|
||||||
:let [old-user (get old-users-by-block block-id)
|
|
||||||
new-user (get new-users-by-block block-id)]
|
|
||||||
:when (not= old-user new-user)]
|
|
||||||
(state/set-state! :rtc/editing-users-by-block new-user :nested-path block-id))))
|
|
||||||
|
|
||||||
(defevent! :rtc/sync-state [[_ state]]
|
|
||||||
(when (contains? state :online-users)
|
|
||||||
(sync-editing-users-by-block! (:online-users state)))
|
|
||||||
(doseq [[k value] state]
|
|
||||||
(state/set-state! :rtc/state value :nested-path k)))
|
|
||||||
|
|
||||||
(defevent! :rtc/presence-update [[_ {:keys [editing-block-uuid]}]]
|
|
||||||
(rtc-handler/<rtc-update-presence! editing-block-uuid))
|
|
||||||
|
|
||||||
(defevent! :rtc/log [[_ data]]
|
|
||||||
(state/set-state! :rtc/log data))
|
|
||||||
|
|
||||||
(defevent! :rtc/remote-graph-gone [_]
|
|
||||||
(p/do!
|
|
||||||
(notification/show! (t :graph/removed-from-sync) :warning false)
|
|
||||||
(rtc-handler/<get-remote-graphs)))
|
|
||||||
|
|
||||||
(defevent! :rtc/download-remote-graph [[_ graph-name graph-uuid graph-schema-version graph-e2ee?]]
|
|
||||||
(assert (= (:major (db-schema/parse-schema-version db-schema/version))
|
|
||||||
(:major (db-schema/parse-schema-version graph-schema-version)))
|
|
||||||
{:app db-schema/version
|
|
||||||
:remote-graph graph-schema-version})
|
|
||||||
(->
|
|
||||||
(p/do!
|
|
||||||
(when (util/mobile?)
|
|
||||||
(download-progress/show! graph-name))
|
|
||||||
(rtc-handler/<rtc-download-graph! graph-name graph-uuid graph-e2ee?)
|
|
||||||
(rtc-handler/<get-remote-graphs)
|
|
||||||
(state/pub-event! [:graph/switch (str config/db-version-prefix graph-name) {:rtc-download? true}])
|
|
||||||
(when (util/mobile?)
|
|
||||||
(download-progress/hide!)))
|
|
||||||
(p/catch (fn [e]
|
|
||||||
(println "RTC download graph failed, error:")
|
|
||||||
(log/error :rtc-download-graph-failed e)
|
|
||||||
(when (util/mobile?)
|
|
||||||
(download-progress/hide!))
|
|
||||||
(when (rtc-error/e2ee-decrypt-failed? e)
|
|
||||||
(notification/show! (t :encryption/wrong-password) :error false))))))
|
|
||||||
|
|
||||||
;; db-worker -> UI
|
;; db-worker -> UI
|
||||||
(defevent! :db/sync-changes [[_ data]]
|
(defevent! :db/sync-changes [[_ data]]
|
||||||
(pipeline/invoke-hooks data)
|
(pipeline/invoke-hooks data)
|
||||||
|
|||||||
@@ -1,108 +0,0 @@
|
|||||||
(ns frontend.handler.events.rtc
|
|
||||||
"RTC events"
|
|
||||||
(:require-macros [frontend.handler.events.macros :refer [defevent!]])
|
|
||||||
(:require [frontend.common.crypt :as crypt]
|
|
||||||
[frontend.components.e2ee :as e2ee]
|
|
||||||
[frontend.config :as config]
|
|
||||||
[frontend.context.i18n :refer [t]]
|
|
||||||
[frontend.flows :as flows]
|
|
||||||
[frontend.handler.events :as events]
|
|
||||||
[frontend.handler.notification :as notification]
|
|
||||||
[frontend.state :as state]
|
|
||||||
[lambdaisland.glogi :as log]
|
|
||||||
[logseq.shui.ui :as shui]
|
|
||||||
[promesa.core :as p]))
|
|
||||||
|
|
||||||
(defn- rtc-collaborators-dialog?
|
|
||||||
[]
|
|
||||||
(= :rtc-collaborators (state/get-dialog-id)))
|
|
||||||
|
|
||||||
(defn- close-e2ee-blocking-ui!
|
|
||||||
[]
|
|
||||||
(when-not (rtc-collaborators-dialog?)
|
|
||||||
(shui/dialog-close-all!))
|
|
||||||
(shui/popup-hide! :download-rtc-graph))
|
|
||||||
|
|
||||||
(defevent! :rtc/decrypt-user-e2ee-private-key [[_ encrypted-private-key]]
|
|
||||||
(let [private-key-promise (p/deferred)
|
|
||||||
refresh-token (str (state/get-auth-refresh-token))]
|
|
||||||
(close-e2ee-blocking-ui!)
|
|
||||||
(->
|
|
||||||
(p/let [{:keys [password]} (state/<invoke-db-worker :thread-api/get-e2ee-password refresh-token)
|
|
||||||
private-key (crypt/<decrypt-private-key password encrypted-private-key)]
|
|
||||||
(p/resolve! private-key-promise private-key))
|
|
||||||
(p/catch
|
|
||||||
(fn [error]
|
|
||||||
(log/error :read-e2ee-password-failed error)
|
|
||||||
(shui/dialog-open!
|
|
||||||
#(e2ee/e2ee-password-to-decrypt-private-key encrypted-private-key private-key-promise)
|
|
||||||
{:auto-width? true
|
|
||||||
:content-props {:onPointerDownOutside #(.preventDefault %)}
|
|
||||||
:on-close (fn []
|
|
||||||
(p/reject! private-key-promise (ex-info "input E2EE password cancelled" {}))
|
|
||||||
(shui/dialog-close!))}))))
|
|
||||||
private-key-promise))
|
|
||||||
|
|
||||||
(defevent! :rtc/request-e2ee-password [[_ {:keys [reason]}]]
|
|
||||||
(let [password-promise (p/deferred)
|
|
||||||
decrypt-reason? (= :decrypt-user-rsa-private-key reason)]
|
|
||||||
(close-e2ee-blocking-ui!)
|
|
||||||
(shui/dialog-open!
|
|
||||||
#(if decrypt-reason?
|
|
||||||
(e2ee/e2ee-request-password password-promise)
|
|
||||||
(e2ee/e2ee-request-new-password password-promise))
|
|
||||||
{:auto-width? true
|
|
||||||
:content-props {:onPointerDownOutside #(.preventDefault %)}
|
|
||||||
:on-close (fn []
|
|
||||||
(p/reject! password-promise (ex-info "cancelled" {}))
|
|
||||||
(shui/dialog-close!))})
|
|
||||||
password-promise))
|
|
||||||
|
|
||||||
(defevent! :rtc/storage-exceed-limit [[_]]
|
|
||||||
(notification/show! (t :sync/storage-exceed-limit) :warning false))
|
|
||||||
|
|
||||||
(defevent! :rtc/graph-count-exceed-limit [[_]]
|
|
||||||
(notification/show! (t :sync/graph-count-exceed-limit) :warning false))
|
|
||||||
|
|
||||||
(defonce ^:private *sync-app-state-cancel! (atom nil))
|
|
||||||
|
|
||||||
(defn- sync-app-state!
|
|
||||||
[]
|
|
||||||
(when-let [cancel! @*sync-app-state-cancel!]
|
|
||||||
(cancel!))
|
|
||||||
(let [state-atoms {:git/current-repo flows/current-repo
|
|
||||||
:config (flows/sub-atom [:config])
|
|
||||||
:auth/id-token (flows/sub-atom [:auth/id-token])
|
|
||||||
:auth/access-token (flows/sub-atom [:auth/access-token])
|
|
||||||
:auth/refresh-token (flows/sub-atom [:auth/refresh-token])
|
|
||||||
:auth/oauth-token-url (flows/sub-atom [:auth/oauth-token-url])
|
|
||||||
:auth/oauth-domain (flows/sub-atom [:auth/oauth-domain])
|
|
||||||
:auth/oauth-client-id (flows/sub-atom [:auth/oauth-client-id])
|
|
||||||
:user/info (flows/sub-atom [:user/info])}
|
|
||||||
<init-sync-done? (p/deferred)
|
|
||||||
last-state (atom ::not-set)
|
|
||||||
app-state (fn []
|
|
||||||
(cond-> (update-vals state-atoms deref)
|
|
||||||
(seq config/OAUTH-DOMAIN)
|
|
||||||
(assoc :auth/oauth-domain config/OAUTH-DOMAIN)
|
|
||||||
|
|
||||||
(seq config/COGNITO-CLIENT-ID)
|
|
||||||
(assoc :auth/oauth-client-id config/COGNITO-CLIENT-ID)))
|
|
||||||
sync! (fn []
|
|
||||||
(let [m (app-state)]
|
|
||||||
(when-not (= @last-state m)
|
|
||||||
(reset! last-state m)
|
|
||||||
(p/let [_ (when (:git/current-repo m)
|
|
||||||
(state/<invoke-db-worker :thread-api/sync-app-state m))]
|
|
||||||
(p/resolve! <init-sync-done?)))))]
|
|
||||||
(doseq [[k atom'] state-atoms]
|
|
||||||
(add-watch atom' [::sync-app-state k] (fn [_ _ _ _] (sync!))))
|
|
||||||
(sync!)
|
|
||||||
(reset! *sync-app-state-cancel!
|
|
||||||
(fn []
|
|
||||||
(doseq [[k atom'] state-atoms]
|
|
||||||
(remove-watch atom' [::sync-app-state k]))))
|
|
||||||
<init-sync-done?))
|
|
||||||
|
|
||||||
(defevent! :rtc/sync-app-state [[_]]
|
|
||||||
(sync-app-state!))
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
(ns frontend.handler.events.rtc-error
|
|
||||||
"RTC event error helpers.")
|
|
||||||
|
|
||||||
(def ^:private e2ee-decrypt-error-codes
|
|
||||||
#{:db-sync/invalid-e2ee-password})
|
|
||||||
|
|
||||||
(defn- throwable-message
|
|
||||||
[error]
|
|
||||||
(or (ex-message error)
|
|
||||||
(when (instance? js/Error error)
|
|
||||||
(.-message error))
|
|
||||||
(some-> error str)))
|
|
||||||
|
|
||||||
(defn- error-texts
|
|
||||||
[error]
|
|
||||||
(when error
|
|
||||||
(let [data (ex-data error)]
|
|
||||||
(concat
|
|
||||||
[(throwable-message error)
|
|
||||||
(:error-message data)
|
|
||||||
(:error-cause data)]
|
|
||||||
(error-texts (:error data))
|
|
||||||
(error-texts (ex-cause error))))))
|
|
||||||
|
|
||||||
(defn- error-codes
|
|
||||||
[error]
|
|
||||||
(when error
|
|
||||||
(let [data (ex-data error)]
|
|
||||||
(concat
|
|
||||||
[(:code data)]
|
|
||||||
(error-codes (:error data))
|
|
||||||
(error-codes (ex-cause error))))))
|
|
||||||
|
|
||||||
(defn e2ee-decrypt-failed?
|
|
||||||
[error]
|
|
||||||
(boolean
|
|
||||||
(or (some e2ee-decrypt-error-codes (error-codes error))
|
|
||||||
(some #{"decrypt-aes-key"} (error-texts error)))))
|
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
"UI events"
|
"UI events"
|
||||||
(:require-macros [frontend.handler.events.macros :refer [defevent!]])
|
(:require-macros [frontend.handler.events.macros :refer [defevent!]])
|
||||||
(:require [clojure.core.async :as async]
|
(:require [clojure.core.async :as async]
|
||||||
[clojure.core.async.interop :refer [p->c]]
|
|
||||||
[frontend.components.assets :as assets]
|
[frontend.components.assets :as assets]
|
||||||
[frontend.components.cmdk.core :as cmdk]
|
[frontend.components.cmdk.core :as cmdk]
|
||||||
[frontend.components.icon :as icon-component]
|
[frontend.components.icon :as icon-component]
|
||||||
@@ -25,8 +24,6 @@
|
|||||||
[frontend.extensions.pdf.assets :as pdf-assets]
|
[frontend.extensions.pdf.assets :as pdf-assets]
|
||||||
[frontend.fs :as fs]
|
[frontend.fs :as fs]
|
||||||
[frontend.handler.assets :as assets-handler]
|
[frontend.handler.assets :as assets-handler]
|
||||||
[frontend.handler.db-based.rtc-flows :as rtc-flows]
|
|
||||||
[frontend.handler.db-based.sync :as rtc-handler]
|
|
||||||
[frontend.handler.editor :as editor-handler]
|
[frontend.handler.editor :as editor-handler]
|
||||||
[frontend.handler.events :as events]
|
[frontend.handler.events :as events]
|
||||||
[frontend.handler.notification :as notification]
|
[frontend.handler.notification :as notification]
|
||||||
@@ -50,15 +47,6 @@
|
|||||||
[promesa.core :as p]
|
[promesa.core :as p]
|
||||||
[logseq.shui.dialog.core :as shui-dialog]))
|
[logseq.shui.dialog.core :as shui-dialog]))
|
||||||
|
|
||||||
(defn- <asset-file-ready?
|
|
||||||
[asset file-name]
|
|
||||||
(if (or config/publishing?
|
|
||||||
(seq (:logseq.property.asset/external-url asset)))
|
|
||||||
(p/resolved true)
|
|
||||||
(fs/file-exists?
|
|
||||||
(config/get-repo-dir (state/get-current-repo))
|
|
||||||
(path/path-join common-config/local-assets-dir file-name))))
|
|
||||||
|
|
||||||
(defevent! :go/search [_]
|
(defevent! :go/search [_]
|
||||||
(when-not (editor-handler/dialog-exists? :ls-dialog-cmdk)
|
(when-not (editor-handler/dialog-exists? :ls-dialog-cmdk)
|
||||||
(shui/dialog-open!
|
(shui/dialog-open!
|
||||||
@@ -100,11 +88,6 @@
|
|||||||
(plugin/user-proxy-settings-container agent-opts)
|
(plugin/user-proxy-settings-container agent-opts)
|
||||||
{:id :https-proxy-panel :center? true :class "lg:max-w-2xl"}))
|
{:id :https-proxy-panel :center? true :class "lg:max-w-2xl"}))
|
||||||
|
|
||||||
(defevent! :go/sync-server-settings [[_]]
|
|
||||||
(shui/dialog-open!
|
|
||||||
(settings/sync-server-url-settings-container)
|
|
||||||
{:id :sync-server-panel :center? true :class "lg:max-w-2xl"}))
|
|
||||||
|
|
||||||
(defevent! :go/db-worker-server-settings [[_]]
|
(defevent! :go/db-worker-server-settings [[_]]
|
||||||
(shui/dialog-open!
|
(shui/dialog-open!
|
||||||
(settings/db-worker-server-settings-container)
|
(settings/db-worker-server-settings-container)
|
||||||
@@ -456,28 +439,21 @@
|
|||||||
:h (or (:logseq.property.asset/height asset) 800)}])))
|
:h (or (:logseq.property.asset/height asset) 800)}])))
|
||||||
|
|
||||||
video?
|
video?
|
||||||
(p/let [file-ready? (<asset-file-ready? asset file-name)
|
(p/let [url (assets-handler/<make-asset-url rel-path)]
|
||||||
requested? (assets-handler/maybe-request-remote-asset-download!
|
(when url
|
||||||
(state/get-current-repo)
|
(shui/dialog-open!
|
||||||
asset
|
(fn []
|
||||||
file-ready?)]
|
[:div.flex.flex-col.gap-2.items-center
|
||||||
(if requested?
|
[:div.font-medium.text-sm.self-start.truncate.max-w-full
|
||||||
(notification/show! (t :asset/downloading))
|
(:block/title asset)]
|
||||||
(p/let [url (assets-handler/<make-asset-url rel-path)]
|
[:video.rounded.max-w-full
|
||||||
(when url
|
{:src url
|
||||||
(shui/dialog-open!
|
:controls true
|
||||||
(fn []
|
:autoPlay true
|
||||||
[:div.flex.flex-col.gap-2.items-center
|
:style {:max-height "80vh"}}]])
|
||||||
[:div.font-medium.text-sm.self-start.truncate.max-w-full
|
{:id :asset-video-preview
|
||||||
(:block/title asset)]
|
:auto-width? true
|
||||||
[:video.rounded.max-w-full
|
:center? true})))
|
||||||
{:src url
|
|
||||||
:controls true
|
|
||||||
:autoPlay true
|
|
||||||
:style {:max-height "80vh"}}]])
|
|
||||||
{:id :asset-video-preview
|
|
||||||
:auto-width? true
|
|
||||||
:center? true})))))
|
|
||||||
|
|
||||||
pdf?
|
pdf?
|
||||||
(p/let [url (assets-handler/<make-asset-url rel-path)]
|
(p/let [url (assets-handler/<make-asset-url rel-path)]
|
||||||
@@ -487,21 +463,6 @@
|
|||||||
:else
|
:else
|
||||||
(route-handler/redirect-to-page! (:block/uuid asset))))))
|
(route-handler/redirect-to-page! (:block/uuid asset))))))
|
||||||
|
|
||||||
(defn ensure-user-rsa-keys-if-possible!
|
|
||||||
[]
|
|
||||||
(if @state/*db-worker
|
|
||||||
(-> (p/do!
|
|
||||||
(state/pub-event! [:rtc/sync-app-state])
|
|
||||||
(state/<invoke-db-worker :thread-api/set-db-sync-config
|
|
||||||
{:enabled? true
|
|
||||||
:ws-url (config/db-sync-ws-url)
|
|
||||||
:http-base (config/db-sync-http-base)})
|
|
||||||
(state/<invoke-db-worker :thread-api/db-sync-ensure-user-rsa-keys))
|
|
||||||
(p/catch (fn [error]
|
|
||||||
(log/error :db-sync/ensure-user-rsa-keys-failed error)
|
|
||||||
nil)))
|
|
||||||
(p/resolved nil)))
|
|
||||||
|
|
||||||
(defevent! :user/fetch-info-and-graphs [[_]]
|
(defevent! :user/fetch-info-and-graphs [[_]]
|
||||||
(state/set-state! [:ui/loading? :login] false)
|
(state/set-state! [:ui/loading? :login] false)
|
||||||
(async/go
|
(async/go
|
||||||
@@ -513,18 +474,8 @@
|
|||||||
(do
|
(do
|
||||||
(state/set-user-info! result)
|
(state/set-user-info! result)
|
||||||
(when-let [uid (user-handler/user-uuid)]
|
(when-let [uid (user-handler/user-uuid)]
|
||||||
(sentry-event/set-user! uid)
|
(sentry-event/set-user! uid))
|
||||||
(ensure-user-rsa-keys-if-possible!))
|
(repo-handler/refresh-repos!))))))
|
||||||
(let [status (if (user-handler/alpha-or-beta-user?) :welcome :unavailable)
|
|
||||||
fetch-graphs? (and (user-handler/logged-in?)
|
|
||||||
(or (= status :welcome)
|
|
||||||
(user-handler/rtc-group?)))]
|
|
||||||
(when fetch-graphs?
|
|
||||||
(async/<! (p->c (rtc-handler/<get-remote-graphs)))
|
|
||||||
(repo-handler/refresh-repos!)
|
|
||||||
(when-let [current-repo (state/get-current-repo)]
|
|
||||||
(when (some #(= current-repo (:url %)) (state/get-rtc-graphs))
|
|
||||||
(rtc-flows/trigger-rtc-start current-repo))))))))))
|
|
||||||
|
|
||||||
(defevent! :dialog/show-block [[_ block option]]
|
(defevent! :dialog/show-block [[_ block option]]
|
||||||
(shui/dialog-open!
|
(shui/dialog-open!
|
||||||
|
|||||||
@@ -21,7 +21,6 @@
|
|||||||
[frontend.util :as util]
|
[frontend.util :as util]
|
||||||
[frontend.util.text :as text-util]
|
[frontend.util.text :as text-util]
|
||||||
[logseq.common.version :as build-version]
|
[logseq.common.version :as build-version]
|
||||||
[logseq.db.frontend.schema :as db-schema]
|
|
||||||
[promesa.core :as p]))
|
[promesa.core :as p]))
|
||||||
|
|
||||||
;; Project settings should be checked in two situations:
|
;; Project settings should be checked in two situations:
|
||||||
@@ -103,43 +102,11 @@
|
|||||||
:root (config/get-local-dir graph-name)
|
:root (config/get-local-dir graph-name)
|
||||||
:nfs? true}))))))
|
:nfs? true}))))))
|
||||||
|
|
||||||
(defn combine-local-&-remote-graphs
|
|
||||||
[local-repos remote-repos]
|
|
||||||
(when-let [repos' (seq (concat (map (fn [{:keys [sync-meta metadata] :as repo}]
|
|
||||||
(let [graph-id (some-> (or (:kv/value metadata)
|
|
||||||
(second sync-meta)) str)]
|
|
||||||
(if (and graph-id (nil? (:GraphUUID repo)))
|
|
||||||
(assoc repo :GraphUUID graph-id)
|
|
||||||
repo)))
|
|
||||||
local-repos)
|
|
||||||
(some->> remote-repos
|
|
||||||
(map #(assoc % :remote? true)))))]
|
|
||||||
(let [app-major-schema-version (str (:major (db-schema/parse-schema-version db-schema/version)))
|
|
||||||
repos' (group-by :url repos')
|
|
||||||
repos'' (mapcat (fn [[k vs]]
|
|
||||||
(if (some? k)
|
|
||||||
(let [remote-repos (filter :remote? vs)
|
|
||||||
version-matched-remote-repo
|
|
||||||
(first
|
|
||||||
(filter
|
|
||||||
#(= app-major-schema-version (:GraphSchemaVersion %))
|
|
||||||
remote-repos))]
|
|
||||||
[(merge (first vs) (second vs) version-matched-remote-repo)])
|
|
||||||
vs))
|
|
||||||
repos')]
|
|
||||||
(sort-by (fn [repo]
|
|
||||||
(let [graph-name (or (:GraphName repo)
|
|
||||||
(last (string/split (:root repo) #"/")))]
|
|
||||||
[(:remote? repo) (string/lower-case graph-name)])) repos''))))
|
|
||||||
|
|
||||||
(defn refresh-repos!
|
(defn refresh-repos!
|
||||||
[]
|
[]
|
||||||
(p/let [repos (get-repos)
|
(p/let [repos (get-repos)]
|
||||||
repos' (combine-local-&-remote-graphs
|
(state/set-repos! repos)
|
||||||
repos
|
repos))
|
||||||
(state/get-rtc-graphs))]
|
|
||||||
(state/set-repos! repos')
|
|
||||||
repos'))
|
|
||||||
|
|
||||||
(defn graph-ready!
|
(defn graph-ready!
|
||||||
;; FIXME: Call electron that the graph is loaded, an ugly implementation for redirect to page when graph is restored
|
;; FIXME: Call electron that the graph is loaded, an ugly implementation for redirect to page when graph is restored
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
[cljs-time.coerce :as tc]
|
[cljs-time.coerce :as tc]
|
||||||
[cljs-time.core :as t]
|
[cljs-time.core :as t]
|
||||||
[cljs.core.async :as async :refer [<! go timeout]]
|
[cljs.core.async :as async :refer [<! go timeout]]
|
||||||
[clojure.set :as set]
|
|
||||||
[clojure.string :as string]
|
[clojure.string :as string]
|
||||||
[electron.ipc :as ipc]
|
[electron.ipc :as ipc]
|
||||||
[frontend.common.thread-api :refer [def-thread-api]]
|
[frontend.common.thread-api :refer [def-thread-api]]
|
||||||
@@ -271,14 +270,6 @@
|
|||||||
(auto-fill-refresh-token-from-cognito!)
|
(auto-fill-refresh-token-from-cognito!)
|
||||||
(state/pub-event! [:user/fetch-info-and-graphs]))
|
(state/pub-event! [:user/fetch-info-and-graphs]))
|
||||||
|
|
||||||
(defn- clear-e2ee-password!
|
|
||||||
[]
|
|
||||||
(when @state/*db-worker
|
|
||||||
(-> (state/<invoke-db-worker :thread-api/clear-e2ee-password)
|
|
||||||
(p/catch (fn [error]
|
|
||||||
(js/console.warn :clear-e2ee-password-failed error)
|
|
||||||
nil)))))
|
|
||||||
|
|
||||||
(defn ^:export login-with-username-password-e2e
|
(defn ^:export login-with-username-password-e2e
|
||||||
[username' password client-id client-secret]
|
[username' password client-id client-secret]
|
||||||
(let [text-encoder (new js/TextEncoder)
|
(let [text-encoder (new js/TextEncoder)
|
||||||
@@ -306,7 +297,6 @@
|
|||||||
{:id-token id-token :access-token access-token :refresh-token refresh-token})))))
|
{:id-token id-token :access-token access-token :refresh-token refresh-token})))))
|
||||||
|
|
||||||
(defn logout []
|
(defn logout []
|
||||||
(clear-e2ee-password!)
|
|
||||||
(clear-tokens)
|
(clear-tokens)
|
||||||
(.clear js/localStorage)
|
(.clear js/localStorage)
|
||||||
(state/clear-user-info!)
|
(state/clear-user-info!)
|
||||||
@@ -354,13 +344,6 @@
|
|||||||
|
|
||||||
;;; user groups
|
;;; user groups
|
||||||
|
|
||||||
(defn rtc-group?
|
|
||||||
[]
|
|
||||||
(boolean (or
|
|
||||||
config/dev?
|
|
||||||
(some? (config/get-custom-sync-server-url))
|
|
||||||
(seq (set/intersection (state/user-groups) #{"team" "rtc_2025_07_10"})))))
|
|
||||||
|
|
||||||
(defn alpha-user?
|
(defn alpha-user?
|
||||||
[]
|
[]
|
||||||
(or config/dev?
|
(or config/dev?
|
||||||
@@ -375,20 +358,6 @@
|
|||||||
[]
|
[]
|
||||||
(or (alpha-user?) (beta-user?)))
|
(or (alpha-user?) (beta-user?)))
|
||||||
|
|
||||||
(defn get-user-type
|
|
||||||
[repo]
|
|
||||||
(-> (some #(when (= repo (:url %)) %) (:rtc/graphs (state/get-state)))
|
|
||||||
:graph<->user-user-type))
|
|
||||||
|
|
||||||
(defn manager?
|
|
||||||
[repo]
|
|
||||||
(= (get-user-type repo) "manager"))
|
|
||||||
|
|
||||||
;; TODO: Remove if still unused
|
|
||||||
#_(defn member?
|
|
||||||
[repo]
|
|
||||||
(= (get-user-type repo) "member"))
|
|
||||||
|
|
||||||
(defn <upload-user-avatar
|
(defn <upload-user-avatar
|
||||||
[avatar-str]
|
[avatar-str]
|
||||||
(when-let [token (state/get-auth-id-token)]
|
(when-let [token (state/get-auth-id-token)]
|
||||||
|
|||||||
@@ -3,14 +3,11 @@
|
|||||||
(:require [cljs-bean.core :as bean]
|
(:require [cljs-bean.core :as bean]
|
||||||
[clojure.string :as string]
|
[clojure.string :as string]
|
||||||
[frontend.common.thread-api :as thread-api]
|
[frontend.common.thread-api :as thread-api]
|
||||||
[frontend.common.crypt :as crypt]
|
|
||||||
[frontend.context.i18n :as i18n]
|
[frontend.context.i18n :as i18n]
|
||||||
[frontend.handler.e2ee :as e2ee-handler]
|
|
||||||
[frontend.handler.notification :as notification]
|
[frontend.handler.notification :as notification]
|
||||||
[frontend.state :as state]
|
[frontend.state :as state]
|
||||||
[lambdaisland.glogi :as log]
|
[lambdaisland.glogi :as log]
|
||||||
[logseq.db :as ldb]
|
[logseq.db :as ldb]))
|
||||||
[promesa.core :as p]))
|
|
||||||
|
|
||||||
(defmulti handle identity)
|
(defmulti handle identity)
|
||||||
|
|
||||||
@@ -41,16 +38,6 @@
|
|||||||
(defmethod handle :add-repo [_ _worker data]
|
(defmethod handle :add-repo [_ _worker data]
|
||||||
(state/add-repo! {:url (:repo data)}))
|
(state/add-repo! {:url (:repo data)}))
|
||||||
|
|
||||||
(defmethod handle :rtc-sync-state [_ _worker data]
|
|
||||||
(let [state data]
|
|
||||||
(state/pub-event! [:rtc/sync-state state])))
|
|
||||||
|
|
||||||
(defmethod handle :rtc-asset-upload-download-progress [_ _worker {:keys [repo asset-id progress]}]
|
|
||||||
(when (and (seq repo) (seq asset-id) (map? progress))
|
|
||||||
(state/update-state!
|
|
||||||
:rtc/asset-upload-download-progress
|
|
||||||
(fn [m] (assoc-in m [repo asset-id] progress)))))
|
|
||||||
|
|
||||||
(defmethod handle :asset-file-write-finish [_ _worker {:keys [repo asset-id ts]}]
|
(defmethod handle :asset-file-write-finish [_ _worker {:keys [repo asset-id ts]}]
|
||||||
(when (and (seq repo) (seq asset-id))
|
(when (and (seq repo) (seq asset-id))
|
||||||
(state/update-state!
|
(state/update-state!
|
||||||
@@ -67,13 +54,6 @@
|
|||||||
(defmethod handle :resync [_ _worker data]
|
(defmethod handle :resync [_ _worker data]
|
||||||
(state/pub-event! [:db/resync data]))
|
(state/pub-event! [:db/resync data]))
|
||||||
|
|
||||||
(defmethod handle :sync-conflicts-updated [_ _worker {:keys [repo block-uuid conflicts]}]
|
|
||||||
(when (and (seq repo) block-uuid)
|
|
||||||
(state/set-sync-block-conflicts! repo block-uuid conflicts)))
|
|
||||||
|
|
||||||
(defmethod handle :rtc-log [_ _worker log]
|
|
||||||
(state/pub-event! [:rtc/log log]))
|
|
||||||
|
|
||||||
(defmethod handle :export-current-db [_]
|
(defmethod handle :export-current-db [_]
|
||||||
(state/pub-event! [:db/export-sqlite]))
|
(state/pub-event! [:db/export-sqlite]))
|
||||||
|
|
||||||
@@ -90,103 +70,6 @@
|
|||||||
(defmethod handle :notify-existing-file [_ _worker data]
|
(defmethod handle :notify-existing-file [_ _worker data]
|
||||||
(state/pub-event! [:graph/notify-existing-file data]))
|
(state/pub-event! [:graph/notify-existing-file data]))
|
||||||
|
|
||||||
(defmethod handle :remote-graph-gone []
|
|
||||||
(state/pub-event! [:rtc/remote-graph-gone]))
|
|
||||||
|
|
||||||
(defn- <invoke-worker-thread-api
|
|
||||||
[wrapped-worker qkw & args]
|
|
||||||
(apply wrapped-worker qkw args))
|
|
||||||
|
|
||||||
(defn- ui-request-error->payload
|
|
||||||
[error]
|
|
||||||
(let [data (ex-data error)]
|
|
||||||
(merge {:code (or (:code data) :ui-request-failed)
|
|
||||||
:message (or (ex-message error) (str error))}
|
|
||||||
(when (seq data)
|
|
||||||
{:data data}))))
|
|
||||||
|
|
||||||
(defn- <db-worker-ui-action
|
|
||||||
[action payload]
|
|
||||||
(case action
|
|
||||||
:request-e2ee-password
|
|
||||||
(p/let [password-promise (state/pub-event! [:rtc/request-e2ee-password payload])
|
|
||||||
password password-promise]
|
|
||||||
{:password password})
|
|
||||||
|
|
||||||
:decrypt-user-e2ee-private-key
|
|
||||||
(let [encrypted-private-key (:encrypted-private-key payload)]
|
|
||||||
(p/let [private-key-promise (state/pub-event! [:rtc/decrypt-user-e2ee-private-key encrypted-private-key])
|
|
||||||
private-key private-key-promise]
|
|
||||||
(crypt/<export-private-key private-key)))
|
|
||||||
|
|
||||||
:native-save-e2ee-password
|
|
||||||
(let [{:keys [key encrypted-text]} payload]
|
|
||||||
(if-not (and (string? key) (string? encrypted-text))
|
|
||||||
(p/rejected (ex-info "invalid native-save-e2ee-password payload"
|
|
||||||
{:code :invalid-ui-action-payload
|
|
||||||
:action action
|
|
||||||
:payload payload}))
|
|
||||||
(if-not (e2ee-handler/native-storage-supported?)
|
|
||||||
(p/resolved {:supported? false})
|
|
||||||
(p/let [_ (e2ee-handler/<native-save-secret! key encrypted-text)]
|
|
||||||
{:supported? true}))))
|
|
||||||
|
|
||||||
:native-get-e2ee-password
|
|
||||||
(let [{:keys [key]} payload]
|
|
||||||
(if-not (string? key)
|
|
||||||
(p/rejected (ex-info "invalid native-get-e2ee-password payload"
|
|
||||||
{:code :invalid-ui-action-payload
|
|
||||||
:action action
|
|
||||||
:payload payload}))
|
|
||||||
(if-not (e2ee-handler/native-storage-supported?)
|
|
||||||
(p/resolved {:supported? false})
|
|
||||||
(p/let [encrypted-text (e2ee-handler/<native-get-secret key)]
|
|
||||||
{:supported? true
|
|
||||||
:encrypted-text encrypted-text}))))
|
|
||||||
|
|
||||||
:native-delete-e2ee-password
|
|
||||||
(let [{:keys [key]} payload]
|
|
||||||
(if-not (string? key)
|
|
||||||
(p/rejected (ex-info "invalid native-delete-e2ee-password payload"
|
|
||||||
{:code :invalid-ui-action-payload
|
|
||||||
:action action
|
|
||||||
:payload payload}))
|
|
||||||
(if-not (e2ee-handler/native-storage-supported?)
|
|
||||||
(p/resolved {:supported? false})
|
|
||||||
(p/let [_ (e2ee-handler/<native-delete-secret! key)]
|
|
||||||
{:supported? true}))))
|
|
||||||
|
|
||||||
(p/rejected (ex-info "unsupported db-worker ui action"
|
|
||||||
{:code :unsupported-ui-action
|
|
||||||
:action action
|
|
||||||
:payload payload}))))
|
|
||||||
|
|
||||||
(defn- <handle-db-worker-ui-request
|
|
||||||
[wrapped-worker {:keys [request-id action payload]}]
|
|
||||||
(if (and (string? request-id) (keyword? action))
|
|
||||||
(-> (<db-worker-ui-action action payload)
|
|
||||||
(p/then (fn [result]
|
|
||||||
(<invoke-worker-thread-api wrapped-worker
|
|
||||||
:thread-api/resolve-ui-request
|
|
||||||
request-id
|
|
||||||
result)))
|
|
||||||
(p/catch (fn [error]
|
|
||||||
(log/warn :db-worker/ui-request-failed
|
|
||||||
{:request-id request-id
|
|
||||||
:action action
|
|
||||||
:error error})
|
|
||||||
(<invoke-worker-thread-api wrapped-worker
|
|
||||||
:thread-api/reject-ui-request
|
|
||||||
request-id
|
|
||||||
(ui-request-error->payload error)))))
|
|
||||||
(log/error :db-worker/ui-request-invalid
|
|
||||||
{:request-id request-id
|
|
||||||
:action action
|
|
||||||
:payload payload})))
|
|
||||||
|
|
||||||
(defmethod handle :db-worker/ui-request [_ wrapped-worker data]
|
|
||||||
(<handle-db-worker-ui-request wrapped-worker data))
|
|
||||||
|
|
||||||
(defmethod handle :default [_ _worker data]
|
(defmethod handle :default [_ _worker data]
|
||||||
(prn :debug "Worker data not handled: " data))
|
(prn :debug "Worker data not handled: " data))
|
||||||
|
|
||||||
|
|||||||
@@ -523,22 +523,10 @@
|
|||||||
:desc "(Dev) Validate current graph"
|
:desc "(Dev) Validate current graph"
|
||||||
:inactive (not (state/developer-mode?))
|
:inactive (not (state/developer-mode?))
|
||||||
:fn :frontend.handler.common.developer/validate-db}
|
:fn :frontend.handler.common.developer/validate-db}
|
||||||
:dev/recompute-checksum {:binding []
|
|
||||||
:desc "(Dev) Recompute graph checksum"
|
|
||||||
:inactive (not (state/developer-mode?))
|
|
||||||
:fn :frontend.handler.common.developer/recompute-checksum-diagnostics}
|
|
||||||
:dev/export-client-ops-sqlite {:binding []
|
:dev/export-client-ops-sqlite {:binding []
|
||||||
:desc "(Dev) Export client ops sqlite"
|
:desc "(Dev) Export client ops sqlite"
|
||||||
:inactive (not (state/developer-mode?))
|
:inactive (not (state/developer-mode?))
|
||||||
:fn :frontend.handler.common.developer/export-client-ops-sqlite}
|
:fn :frontend.handler.common.developer/export-client-ops-sqlite}})
|
||||||
:dev/rtc-stop {:binding []
|
|
||||||
:desc "(Dev) RTC Stop"
|
|
||||||
:inactive (not (state/developer-mode?))
|
|
||||||
:fn :frontend.handler.common.developer/rtc-stop}
|
|
||||||
:dev/rtc-start {:binding []
|
|
||||||
:desc "(Dev) RTC Start"
|
|
||||||
:inactive (not (state/developer-mode?))
|
|
||||||
:fn :frontend.handler.common.developer/rtc-start}})
|
|
||||||
|
|
||||||
(let [keyboard-commands
|
(let [keyboard-commands
|
||||||
{::commands (->> (keys all-built-in-keyboard-shortcuts)
|
{::commands (->> (keys all-built-in-keyboard-shortcuts)
|
||||||
@@ -746,11 +734,8 @@
|
|||||||
:dev/show-page-data
|
:dev/show-page-data
|
||||||
:dev/replace-graph-with-db-file
|
:dev/replace-graph-with-db-file
|
||||||
:dev/validate-db
|
:dev/validate-db
|
||||||
:dev/recompute-checksum
|
|
||||||
:dev/export-client-ops-sqlite
|
:dev/export-client-ops-sqlite
|
||||||
:dev/gc-graph
|
:dev/gc-graph
|
||||||
:dev/rtc-stop
|
|
||||||
:dev/rtc-start
|
|
||||||
:ui/customize-appearance])
|
:ui/customize-appearance])
|
||||||
(with-meta {:before m/enable-when-not-editing-mode!}))
|
(with-meta {:before m/enable-when-not-editing-mode!}))
|
||||||
|
|
||||||
@@ -912,11 +897,8 @@
|
|||||||
:dev/show-page-data
|
:dev/show-page-data
|
||||||
:dev/replace-graph-with-db-file
|
:dev/replace-graph-with-db-file
|
||||||
:dev/validate-db
|
:dev/validate-db
|
||||||
:dev/recompute-checksum
|
|
||||||
:dev/export-client-ops-sqlite
|
:dev/export-client-ops-sqlite
|
||||||
:dev/gc-graph
|
:dev/gc-graph
|
||||||
:dev/rtc-stop
|
|
||||||
:dev/rtc-start
|
|
||||||
:ui/clear-all-notifications]
|
:ui/clear-all-notifications]
|
||||||
|
|
||||||
:shortcut.category/plugins
|
:shortcut.category/plugins
|
||||||
|
|||||||
@@ -220,12 +220,6 @@
|
|||||||
(ipc/ipc "releaseDbWorkerRuntime" repo)
|
(ipc/ipc "releaseDbWorkerRuntime" repo)
|
||||||
(p/resolved nil)))
|
(p/resolved nil)))
|
||||||
|
|
||||||
(defn- current-db-sync-config
|
|
||||||
[]
|
|
||||||
{:enabled? true
|
|
||||||
:ws-url (config/db-sync-ws-url)
|
|
||||||
:http-base (config/db-sync-http-base)})
|
|
||||||
|
|
||||||
(defn- <sync-markdown-mirror-setting!
|
(defn- <sync-markdown-mirror-setting!
|
||||||
[repo]
|
[repo]
|
||||||
(state/<invoke-db-worker :thread-api/markdown-mirror-set-enabled
|
(state/<invoke-db-worker :thread-api/markdown-mirror-set-enabled
|
||||||
@@ -327,9 +321,7 @@
|
|||||||
:repo repo :error e)))))
|
:repo repo :error e)))))
|
||||||
(do
|
(do
|
||||||
(set-remote-runtime! repo client session-id)
|
(set-remote-runtime! repo client session-id)
|
||||||
(p/let [_ (state/<invoke-db-worker :thread-api/set-db-sync-config
|
(p/let [_ (<sync-markdown-mirror-setting! repo)]
|
||||||
(current-db-sync-config))
|
|
||||||
_ (<sync-markdown-mirror-setting! repo)]
|
|
||||||
(sync-markdown-mirror-setting-watch!)
|
(sync-markdown-mirror-setting-watch!)
|
||||||
nil)
|
nil)
|
||||||
(ldb/register-transact-fn!
|
(ldb/register-transact-fn!
|
||||||
|
|||||||
@@ -174,9 +174,6 @@
|
|||||||
|
|
||||||
(defn stop-db-worker!
|
(defn stop-db-worker!
|
||||||
[]
|
[]
|
||||||
(when @state/*db-worker
|
|
||||||
(-> (state/<invoke-db-worker :thread-api/cancel-ui-requests {:reason :stop-db-worker})
|
|
||||||
(p/catch (constantly nil))))
|
|
||||||
(when-let [^js worker @state/*db-worker-thread]
|
(when-let [^js worker @state/*db-worker-thread]
|
||||||
(set! (.-onmessage worker) nil)
|
(set! (.-onmessage worker) nil)
|
||||||
(.terminate worker))
|
(.terminate worker))
|
||||||
@@ -222,11 +219,6 @@
|
|||||||
(worker-handler/handle-message! worker wrapped-worker)
|
(worker-handler/handle-message! worker wrapped-worker)
|
||||||
(reset! state/*db-worker wrapped-worker)
|
(reset! state/*db-worker wrapped-worker)
|
||||||
(-> (p/let [_ (state/<invoke-db-worker :thread-api/init)
|
(-> (p/let [_ (state/<invoke-db-worker :thread-api/init)
|
||||||
_ (state/<invoke-db-worker :thread-api/set-db-sync-config
|
|
||||||
{:enabled? true
|
|
||||||
:ws-url (config/db-sync-ws-url)
|
|
||||||
:http-base (config/db-sync-http-base)})
|
|
||||||
_ (state/pub-event! [:rtc/sync-app-state])
|
|
||||||
_ (log/info "init worker spent" (str (- (util/time-ms) t1) "ms"))
|
_ (log/info "init worker spent" (str (- (util/time-ms) t1) "ms"))
|
||||||
_ (sync-ui-state!)
|
_ (sync-ui-state!)
|
||||||
_ (ask-persist-permission!)
|
_ (ask-persist-permission!)
|
||||||
|
|||||||
@@ -340,18 +340,6 @@
|
|||||||
:auth/oauth-client-id nil
|
:auth/oauth-client-id nil
|
||||||
:auth/current-login-user nil
|
:auth/current-login-user nil
|
||||||
|
|
||||||
;; graph-uuid -> ...
|
|
||||||
:rtc/state {}
|
|
||||||
:rtc/editing-users-by-block {}
|
|
||||||
:rtc/loading-graphs? nil
|
|
||||||
;; only latest rtc-log stored here, derive a log stream from state when needed.
|
|
||||||
:rtc/log nil
|
|
||||||
:rtc/uploading? false
|
|
||||||
:rtc/downloading-graph-uuid nil
|
|
||||||
:rtc/graphs []
|
|
||||||
:rtc/online-info {}
|
|
||||||
:rtc/asset-upload-download-progress {}
|
|
||||||
:rtc/users-info {}
|
|
||||||
:sync/block-conflicts {}
|
:sync/block-conflicts {}
|
||||||
|
|
||||||
:user/info {:UserGroups (storage/get :user-groups)}
|
:user/info {:UserGroups (storage/get :user-groups)}
|
||||||
@@ -360,7 +348,6 @@
|
|||||||
:ui/loading? {}
|
:ui/loading? {}
|
||||||
:ui/container-id 0
|
:ui/container-id 0
|
||||||
:ui/cached-key->container-id {}
|
:ui/cached-key->container-id {}
|
||||||
:feature/enable-sync? (storage/get :logseq-sync-enabled)
|
|
||||||
|
|
||||||
:ui/find-in-page nil
|
:ui/find-in-page nil
|
||||||
:comments/inline-thread nil
|
:comments/inline-thread nil
|
||||||
@@ -879,23 +866,6 @@ should be done through this fn in order to get global config and config defaults
|
|||||||
[]
|
[]
|
||||||
(:git/current-repo (rfx/snapshot)))
|
(:git/current-repo (rfx/snapshot)))
|
||||||
|
|
||||||
(defn get-rtc-graphs
|
|
||||||
[]
|
|
||||||
(:rtc/graphs (rfx/snapshot)))
|
|
||||||
|
|
||||||
;; TODO: rtc version
|
|
||||||
(comment
|
|
||||||
(defn get-remote-graph-usage
|
|
||||||
[graphs]
|
|
||||||
(->> graphs
|
|
||||||
(map #(hash-map :uuid (:GraphUUID %)
|
|
||||||
:name (:GraphName %)
|
|
||||||
:used-gbs (/ (:GraphStorageUsage %) 1024 1024 1024)
|
|
||||||
:limit-gbs (/ (:GraphStorageLimit %) 1024 1024 1024)
|
|
||||||
:used-percent (/ (:GraphStorageUsage %) (:GraphStorageLimit %) 0.01)))
|
|
||||||
(map #(assoc % :free-gbs (- (:limit-gbs %) (:used-gbs %))))
|
|
||||||
(vec))))
|
|
||||||
|
|
||||||
(defn get-repos
|
(defn get-repos
|
||||||
[]
|
[]
|
||||||
(get-in (rfx/snapshot) [:me :repos]))
|
(get-in (rfx/snapshot) [:me :repos]))
|
||||||
@@ -1312,27 +1282,22 @@ should be done through this fn in order to get global config and config defaults
|
|||||||
(defn clear-edit!
|
(defn clear-edit!
|
||||||
[& {:keys [clear-editing-block?]
|
[& {:keys [clear-editing-block?]
|
||||||
:or {clear-editing-block? true}}]
|
:or {clear-editing-block? true}}]
|
||||||
(let [online-users (some-> (get-state :rtc/state) :online-users)]
|
(swap-state!
|
||||||
(swap-state!
|
(fn [db]
|
||||||
(fn [db]
|
(cond-> (assoc db
|
||||||
(cond-> (assoc db
|
:editor/action nil
|
||||||
:editor/action nil
|
:editor/args nil
|
||||||
:editor/args nil
|
:editor/start-pos nil
|
||||||
:editor/start-pos nil
|
:editor/last-saved-cursor {}
|
||||||
:editor/last-saved-cursor {}
|
:editor/cursor-range nil
|
||||||
:editor/cursor-range nil
|
:ui/select-query-cache {}
|
||||||
:ui/select-query-cache {}
|
:editor/block-refs #{}
|
||||||
:editor/block-refs #{}
|
:editor/action-data nil
|
||||||
:editor/action-data nil
|
:view/selected-blocks nil)
|
||||||
:view/selected-blocks nil)
|
clear-editing-block?
|
||||||
clear-editing-block?
|
(assoc :editor/editing? nil
|
||||||
(assoc :editor/editing? nil
|
:editor/block nil
|
||||||
:editor/block nil
|
:editor/content {}))))
|
||||||
:editor/content {}))))
|
|
||||||
(when (and clear-editing-block?
|
|
||||||
(coll? online-users)
|
|
||||||
(> (count online-users) 1))
|
|
||||||
(pub-event! [:rtc/presence-update {:editing-block-uuid nil}])))
|
|
||||||
nil)
|
nil)
|
||||||
|
|
||||||
(defn set-editor-last-pos!
|
(defn set-editor-last-pos!
|
||||||
@@ -1876,9 +1841,6 @@ should be done through this fn in order to get global config and config defaults
|
|||||||
(assoc-in [:editor/content block-id] content))
|
(assoc-in [:editor/content block-id] content))
|
||||||
native-platform?
|
native-platform?
|
||||||
(assoc :mobile/show-action-bar? false))))
|
(assoc :mobile/show-action-bar? false))))
|
||||||
(let [online-users (some-> (get-state :rtc/state) :online-users)]
|
|
||||||
(when (and (coll? online-users) (> (count online-users) 1))
|
|
||||||
(pub-event! [:rtc/presence-update {:editing-block-uuid (str block-id)}])))
|
|
||||||
(when (= :code (:logseq.property.node/display-type block))
|
(when (= :code (:logseq.property.node/display-type block))
|
||||||
(pub-event! [:editor/focus-code-editor block block-element]))
|
(pub-event! [:editor/focus-code-editor block block-element]))
|
||||||
(when-let [input (gdom/getElement edit-input-id)]
|
(when-let [input (gdom/getElement edit-input-id)]
|
||||||
|
|||||||
@@ -8,26 +8,3 @@
|
|||||||
(get (state/get-state) (keyword path))
|
(get (state/get-state) (keyword path))
|
||||||
(state/get-state))
|
(state/get-state))
|
||||||
(bean/->js)))
|
(bean/->js)))
|
||||||
|
|
||||||
(defn- current-repo-or-throw
|
|
||||||
[]
|
|
||||||
(if-let [repo (state/get-current-repo)]
|
|
||||||
repo
|
|
||||||
(throw (ex-info "No current repo" {:type :debug/no-current-repo}))))
|
|
||||||
|
|
||||||
(defn ^:export syncStopUpload
|
|
||||||
[]
|
|
||||||
(state/<invoke-db-worker :thread-api/db-sync-stop-upload (current-repo-or-throw)))
|
|
||||||
|
|
||||||
(defn ^:export syncResumeUpload
|
|
||||||
[]
|
|
||||||
(state/<invoke-db-worker :thread-api/db-sync-resume-upload (current-repo-or-throw)))
|
|
||||||
|
|
||||||
(defn ^:export syncUploadStopped
|
|
||||||
[]
|
|
||||||
(state/<invoke-db-worker :thread-api/db-sync-upload-stopped? (current-repo-or-throw)))
|
|
||||||
|
|
||||||
;; Snake_case aliases for consistency with existing debug exports.
|
|
||||||
(defn ^:export sync_stop_upload [] (syncStopUpload))
|
|
||||||
(defn ^:export sync_resume_upload [] (syncResumeUpload))
|
|
||||||
(defn ^:export sync_upload_stopped [] (syncUploadStopped))
|
|
||||||
|
|||||||
@@ -3,16 +3,13 @@
|
|||||||
(:require [cljs-bean.core :as bean]
|
(:require [cljs-bean.core :as bean]
|
||||||
[clojure.string :as string]
|
[clojure.string :as string]
|
||||||
[frontend.context.i18n :refer [t]]
|
[frontend.context.i18n :refer [t]]
|
||||||
[frontend.handler.db-based.sync :as rtc-handler]
|
|
||||||
[frontend.handler.editor :as editor-handler]
|
[frontend.handler.editor :as editor-handler]
|
||||||
[frontend.handler.notification :as notification]
|
|
||||||
[frontend.handler.repo :as repo-handler]
|
[frontend.handler.repo :as repo-handler]
|
||||||
[frontend.handler.route :as route-handler]
|
[frontend.handler.route :as route-handler]
|
||||||
[frontend.mobile.util :as mobile-util]
|
[frontend.mobile.util :as mobile-util]
|
||||||
[frontend.state :as state]
|
[frontend.state :as state]
|
||||||
[frontend.storage :as storage]
|
[frontend.storage :as storage]
|
||||||
[frontend.util :as util]
|
[frontend.util :as util]
|
||||||
[lambdaisland.glogi :as log]
|
|
||||||
[mobile.navigation :as mobile-nav]
|
[mobile.navigation :as mobile-nav]
|
||||||
[mobile.search :as mobile-search]
|
[mobile.search :as mobile-search]
|
||||||
[mobile.state :as mobile-state]
|
[mobile.state :as mobile-state]
|
||||||
@@ -113,47 +110,6 @@
|
|||||||
(when (repo-handler/removable-repo? repo (state/get-repos))
|
(when (repo-handler/removable-repo? repo (state/get-repos))
|
||||||
(repo-handler/remove-repo! repo))))
|
(repo-handler/remove-repo! repo))))
|
||||||
|
|
||||||
(defn- refresh-remote-graphs!
|
|
||||||
[]
|
|
||||||
(state/set-state! :rtc/loading-graphs? true)
|
|
||||||
(-> (rtc-handler/<get-remote-graphs)
|
|
||||||
(p/finally
|
|
||||||
(fn []
|
|
||||||
(state/set-state! :rtc/loading-graphs? false)))))
|
|
||||||
|
|
||||||
(defn- delete-remote-graph!
|
|
||||||
[url graph-uuid graph-schema-version]
|
|
||||||
(state/set-state! :rtc/loading-graphs? true)
|
|
||||||
(-> (p/do!
|
|
||||||
(when (= (state/get-current-repo) url)
|
|
||||||
(rtc-handler/<rtc-stop!))
|
|
||||||
(rtc-handler/<rtc-delete-graph! graph-uuid graph-schema-version)
|
|
||||||
(rtc-handler/<get-remote-graphs))
|
|
||||||
(p/finally
|
|
||||||
(fn []
|
|
||||||
(state/set-state! :rtc/loading-graphs? false)))))
|
|
||||||
|
|
||||||
(defn- leave-remote-graph!
|
|
||||||
[url graph-uuid]
|
|
||||||
(state/set-state! :rtc/loading-graphs? true)
|
|
||||||
(-> (p/do!
|
|
||||||
(when (= (state/get-current-repo) url)
|
|
||||||
(rtc-handler/<rtc-stop!))
|
|
||||||
(rtc-handler/<rtc-leave-graph! graph-uuid))
|
|
||||||
(p/then
|
|
||||||
(fn []
|
|
||||||
(notification/show! (t :graph/left) :success)
|
|
||||||
(rtc-handler/<get-remote-graphs)))
|
|
||||||
(p/catch
|
|
||||||
(fn [e]
|
|
||||||
(notification/show! (t :graph/leave-error) :error)
|
|
||||||
(log/error :db-sync/leave-graph-failed
|
|
||||||
{:error e
|
|
||||||
:graph-uuid graph-uuid})))
|
|
||||||
(p/finally
|
|
||||||
(fn []
|
|
||||||
(state/set-state! :rtc/loading-graphs? false)))))
|
|
||||||
|
|
||||||
(defn add-graph-action-listener!
|
(defn add-graph-action-listener!
|
||||||
[]
|
[]
|
||||||
(when (and (mobile-util/native-ios?) liquid-tabs)
|
(when (and (mobile-util/native-ios?) liquid-tabs)
|
||||||
@@ -167,42 +123,11 @@
|
|||||||
(when-not (string/blank? url)
|
(when-not (string/blank? url)
|
||||||
(state/pub-event! [:graph/switch url])))
|
(state/pub-event! [:graph/switch url])))
|
||||||
|
|
||||||
"download"
|
|
||||||
(let [graph-name (.-graphName data)
|
|
||||||
graph-uuid (.-graphUUID data)
|
|
||||||
graph-schema-version (.-graphSchemaVersion data)]
|
|
||||||
(when (and (not (string/blank? graph-name))
|
|
||||||
(not (string/blank? graph-uuid))
|
|
||||||
(not (string/blank? graph-schema-version)))
|
|
||||||
(state/pub-event!
|
|
||||||
[:rtc/download-remote-graph
|
|
||||||
graph-name
|
|
||||||
graph-uuid
|
|
||||||
graph-schema-version
|
|
||||||
(boolean (.-graphE2ee data))])))
|
|
||||||
|
|
||||||
"refresh"
|
|
||||||
(refresh-remote-graphs!)
|
|
||||||
|
|
||||||
"deleteLocal"
|
"deleteLocal"
|
||||||
(when-let [url (.-url data)]
|
(when-let [url (.-url data)]
|
||||||
(when-not (string/blank? url)
|
(when-not (string/blank? url)
|
||||||
(delete-local-graph! url)))
|
(delete-local-graph! url)))
|
||||||
|
|
||||||
"deleteRemote"
|
|
||||||
(let [url (.-url data)
|
|
||||||
graph-uuid (.-graphUUID data)
|
|
||||||
graph-schema-version (.-graphSchemaVersion data)]
|
|
||||||
(when (and (not (string/blank? graph-uuid))
|
|
||||||
(not (string/blank? graph-schema-version)))
|
|
||||||
(delete-remote-graph! url graph-uuid graph-schema-version)))
|
|
||||||
|
|
||||||
"leaveRemote"
|
|
||||||
(let [url (.-url data)
|
|
||||||
graph-uuid (.-graphUUID data)]
|
|
||||||
(when-not (string/blank? graph-uuid)
|
|
||||||
(leave-remote-graph! url graph-uuid)))
|
|
||||||
|
|
||||||
nil)))))
|
nil)))))
|
||||||
|
|
||||||
(defn add-keyboard-hack-listener!
|
(defn add-keyboard-hack-listener!
|
||||||
|
|||||||
@@ -6,10 +6,8 @@
|
|||||||
[frontend.components.quick-add :as quick-add]
|
[frontend.components.quick-add :as quick-add]
|
||||||
[frontend.context.i18n :as i18n :refer [t]]
|
[frontend.context.i18n :as i18n :refer [t]]
|
||||||
[frontend.handler.common :as common-handler]
|
[frontend.handler.common :as common-handler]
|
||||||
[frontend.handler.db-based.sync :as rtc-handler]
|
|
||||||
[frontend.handler.editor :as editor-handler]
|
[frontend.handler.editor :as editor-handler]
|
||||||
[frontend.handler.repo :as repo-handler]
|
[frontend.handler.repo :as repo-handler]
|
||||||
[frontend.handler.user :as user-handler]
|
|
||||||
[frontend.extensions.fsrs :as fsrs]
|
[frontend.extensions.fsrs :as fsrs]
|
||||||
[frontend.mobile.util :as mobile-util]
|
[frontend.mobile.util :as mobile-util]
|
||||||
[frontend.rfx :as rfx]
|
[frontend.rfx :as rfx]
|
||||||
@@ -112,8 +110,8 @@
|
|||||||
:cancelButton (t :ui/cancel)})
|
:cancelButton (t :ui/cancel)})
|
||||||
|
|
||||||
(defn- native-graph-actions
|
(defn- native-graph-actions
|
||||||
[repos {:keys [url root remote? GraphUUID GraphSchemaVersion] :as graph}]
|
[repos {:keys [root] :as graph}]
|
||||||
(let [graph-name (config/db-graph-name url)
|
(let [graph-name (config/db-graph-name (:url graph))
|
||||||
warning (t :graph/delete-warning)
|
warning (t :graph/delete-warning)
|
||||||
delete-message (fn [message] (str message "\n\n" warning))
|
delete-message (fn [message] (str message "\n\n" warning))
|
||||||
delete-local? (repo-handler/removable-repo? graph repos)]
|
delete-local? (repo-handler/removable-repo? graph repos)]
|
||||||
@@ -124,114 +122,57 @@
|
|||||||
"deleteLocal"
|
"deleteLocal"
|
||||||
(t :graph/delete-local-action)
|
(t :graph/delete-local-action)
|
||||||
true
|
true
|
||||||
(delete-message (t :graph/delete-local-confirm-desc graph-name))))
|
(delete-message (t :graph/delete-local-confirm-desc graph-name)))))))
|
||||||
|
|
||||||
(and remote?
|
|
||||||
GraphUUID
|
|
||||||
GraphSchemaVersion
|
|
||||||
(user-handler/manager? url))
|
|
||||||
(conj
|
|
||||||
(native-graph-action
|
|
||||||
"deleteRemote"
|
|
||||||
(t :graph/delete-server-action)
|
|
||||||
true
|
|
||||||
(delete-message (t :graph/delete-server-confirm-desc graph-name))))
|
|
||||||
|
|
||||||
(and remote?
|
|
||||||
GraphUUID
|
|
||||||
(not (user-handler/manager? url)))
|
|
||||||
(conj
|
|
||||||
(native-graph-action
|
|
||||||
"leaveRemote"
|
|
||||||
(t :graph/leave-action)
|
|
||||||
true
|
|
||||||
(t :graph/leave-confirm-desc))))))
|
|
||||||
|
|
||||||
(defn- native-graph-item
|
(defn- native-graph-item
|
||||||
[repos downloading-graph-id {:keys [url root remote? graph-e2ee?
|
[repos {:keys [url root created-at last-seen-at] :as graph}]
|
||||||
GraphName GraphSchemaVersion GraphUUID
|
|
||||||
graph-ready-for-use? created-at last-seen-at]
|
|
||||||
:as graph}]
|
|
||||||
(let [display-name (text-util/get-graph-name-from-path url)
|
(let [display-name (text-util/get-graph-name-from-path url)
|
||||||
time (safe-locale-date (or last-seen-at created-at))
|
time (safe-locale-date (or last-seen-at created-at))]
|
||||||
downloading? (and downloading-graph-id (= GraphUUID downloading-graph-id))]
|
|
||||||
(when (seq display-name)
|
(when (seq display-name)
|
||||||
{:id (or GraphUUID url)
|
{:id url
|
||||||
:url url
|
:url url
|
||||||
:displayName display-name
|
:displayName display-name
|
||||||
:subtitle (when time (t :graph/last-opened-at-label time))
|
:subtitle (when time (t :graph/last-opened-at-label time))
|
||||||
:remote (boolean remote?)
|
|
||||||
:local (boolean root)
|
:local (boolean root)
|
||||||
:readyForUse (not= false graph-ready-for-use?)
|
:readyForUse true
|
||||||
:downloading (boolean downloading?)
|
:downloading false
|
||||||
:e2ee (boolean graph-e2ee?)
|
|
||||||
:graphName GraphName
|
|
||||||
:graphUUID GraphUUID
|
|
||||||
:graphSchemaVersion GraphSchemaVersion
|
|
||||||
:actions (native-graph-actions repos graph)})))
|
:actions (native-graph-actions repos graph)})))
|
||||||
|
|
||||||
(defn- native-graph-sections
|
(defn- native-graph-sections
|
||||||
[repos remotes login? downloading-graph-id]
|
[repos]
|
||||||
(let [repos (util/distinct-by :url repos)
|
(let [repos (->> repos
|
||||||
repos (->> (if (and login? (seq remotes))
|
(remove #(= (:url %) config/demo-repo))
|
||||||
(repo-handler/combine-local-&-remote-graphs repos remotes)
|
(filter (fn [item] (config/db-based-graph? (:url item))))
|
||||||
repos)
|
|
||||||
(util/distinct-by :url))
|
(util/distinct-by :url))
|
||||||
repos (cond->>
|
items (vec (keep #(native-graph-item repos %) repos))]
|
||||||
(remove #(= (:url %) config/demo-repo) repos)
|
(if (seq items)
|
||||||
true
|
[{:id "local"
|
||||||
(filter (fn [item]
|
:title (t :graph/local-graphs)
|
||||||
(config/db-based-graph? (:url item)))))
|
:refreshable false
|
||||||
{remote-graphs true local-graphs false} (group-by (comp boolean :remote?) repos)
|
:graphs items}]
|
||||||
{own-graphs true shared-graphs false}
|
[])))
|
||||||
(group-by (fn [graph] (= "manager" (:graph<->user-user-type graph))) remote-graphs)
|
|
||||||
section (fn [id title refreshable? show-empty? graphs]
|
|
||||||
(let [items (vec (keep #(native-graph-item repos downloading-graph-id %) graphs))]
|
|
||||||
(when (or show-empty? (seq items))
|
|
||||||
{:id id
|
|
||||||
:title title
|
|
||||||
:refreshable refreshable?
|
|
||||||
:graphs items})))]
|
|
||||||
(vec
|
|
||||||
(keep identity
|
|
||||||
[(section "local" (t :graph/local-graphs) false true local-graphs)
|
|
||||||
(section "remote" (t :graph/remote-graphs) true false own-graphs)
|
|
||||||
(section "shared" (t :graph/shared-graphs) false false shared-graphs)]))))
|
|
||||||
|
|
||||||
(defn- use-native-graphs-effects!
|
(defn- use-native-graphs-effects!
|
||||||
[]
|
[]
|
||||||
(let [id-token (rfx/use-sub [:auth/id-token])
|
(let [repos (rfx/use-sub [:me :repos])
|
||||||
repos (rfx/use-sub [:me :repos])
|
|
||||||
remotes (rfx/use-sub [:rtc/graphs])
|
|
||||||
downloading-graph-id (rfx/use-sub [:rtc/downloading-graph-uuid])
|
|
||||||
loading-graphs? (rfx/use-sub [:rtc/loading-graphs?])
|
|
||||||
route-match (rfx/use-sub [:route-match])
|
route-match (rfx/use-sub [:route-match])
|
||||||
_preferred-language (rfx/use-sub [:preferred-language])
|
_preferred-language (rfx/use-sub [:preferred-language])
|
||||||
[tab] (hooks/use-atom mobile-state/*tab)
|
[tab] (hooks/use-atom mobile-state/*tab)
|
||||||
login? (boolean id-token)
|
|
||||||
route-name (get-in route-match [:data :name])
|
route-name (get-in route-match [:data :name])
|
||||||
visible? (and (= tab "graphs")
|
visible? (and (= tab "graphs")
|
||||||
(not (contains? #{:import :export} route-name)))
|
(not (contains? #{:import :export} route-name)))
|
||||||
sections (native-graph-sections repos remotes login? downloading-graph-id)
|
sections (native-graph-sections repos)
|
||||||
labels {:refresh (t :ui/refresh)
|
labels {:refresh (t :ui/refresh)
|
||||||
:preparing (t :graph/preparing)
|
:preparing (t :graph/preparing)
|
||||||
:downloading (t :graph/downloading)}]
|
:downloading (t :graph/downloading)}]
|
||||||
(hooks/use-effect!
|
|
||||||
(fn []
|
|
||||||
(when (and (mobile-util/native-ios?)
|
|
||||||
login?
|
|
||||||
(user-handler/rtc-group?))
|
|
||||||
(rtc-handler/<get-remote-graphs))
|
|
||||||
nil)
|
|
||||||
[login?])
|
|
||||||
(hooks/use-effect!
|
(hooks/use-effect!
|
||||||
(fn []
|
(fn []
|
||||||
(bottom-tabs/update-native-graphs! {:labels labels
|
(bottom-tabs/update-native-graphs! {:labels labels
|
||||||
:sections sections
|
:sections sections
|
||||||
:visible visible?
|
:visible visible?
|
||||||
:refreshing (boolean loading-graphs?)})
|
:refreshing false})
|
||||||
nil)
|
nil)
|
||||||
[labels sections visible? loading-graphs?])))
|
[labels sections visible?])))
|
||||||
|
|
||||||
(hsx/defc native-graphs-bridge
|
(hsx/defc native-graphs-bridge
|
||||||
[]
|
[]
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
(:require ["@capacitor/dialog" :refer [Dialog]]
|
(:require ["@capacitor/dialog" :refer [Dialog]]
|
||||||
[clojure.string :as string]
|
[clojure.string :as string]
|
||||||
[frontend.components.repo :as repo]
|
[frontend.components.repo :as repo]
|
||||||
[frontend.components.rtc.indicator :as rtc-indicator]
|
|
||||||
[frontend.context.i18n :refer [t]]
|
[frontend.context.i18n :refer [t]]
|
||||||
[frontend.date :as date]
|
[frontend.date :as date]
|
||||||
[frontend.db.async :as db-async]
|
[frontend.db.async :as db-async]
|
||||||
@@ -11,7 +10,6 @@
|
|||||||
[frontend.handler.notification :as notification]
|
[frontend.handler.notification :as notification]
|
||||||
[frontend.handler.page :as page-handler]
|
[frontend.handler.page :as page-handler]
|
||||||
[frontend.handler.route :as route-handler]
|
[frontend.handler.route :as route-handler]
|
||||||
[frontend.handler.user :as user-handler]
|
|
||||||
[frontend.mobile.util :as mobile-util]
|
[frontend.mobile.util :as mobile-util]
|
||||||
[frontend.rfx :as rfx]
|
[frontend.rfx :as rfx]
|
||||||
[frontend.state :as state]
|
[frontend.state :as state]
|
||||||
@@ -165,22 +163,6 @@
|
|||||||
{:title (t :flashcard/select-cards)
|
{:title (t :flashcard/select-cards)
|
||||||
:default-height false})))
|
:default-height false})))
|
||||||
|
|
||||||
(defn current-local-uploadable-graph
|
|
||||||
[rtc-graph-uuid]
|
|
||||||
(let [current-repo (state/get-current-repo)]
|
|
||||||
(some (fn [{:keys [url] :as graph}]
|
|
||||||
(when (and (= current-repo url)
|
|
||||||
(repo/local-uploadable-graph?
|
|
||||||
(assoc graph :rtc-graph?
|
|
||||||
(boolean rtc-graph-uuid))))
|
|
||||||
graph))
|
|
||||||
(state/get-repos))))
|
|
||||||
|
|
||||||
(defn <load-rtc-graph-uuid
|
|
||||||
[current-repo db-worker-ready?]
|
|
||||||
(when (and current-repo db-worker-ready?)
|
|
||||||
(state/<invoke-db-worker :thread-api/get-rtc-graph-uuid current-repo)))
|
|
||||||
|
|
||||||
(defn- register-native-top-bar-events! [*configure-top-bar-f]
|
(defn- register-native-top-bar-events! [*configure-top-bar-f]
|
||||||
(when (and (mobile-util/native-platform?)
|
(when (and (mobile-util/native-platform?)
|
||||||
mobile-util/native-top-bar
|
mobile-util/native-top-bar
|
||||||
@@ -200,13 +182,6 @@
|
|||||||
"add-graph" (open-new-db-graph!)
|
"add-graph" (open-new-db-graph!)
|
||||||
"home-setting" (open-home-settings-actions!)
|
"home-setting" (open-home-settings-actions!)
|
||||||
"graph-setting" (open-graph-settings-actions!)
|
"graph-setting" (open-graph-settings-actions!)
|
||||||
"sync" (p/let [rtc-graph-uuid (state/<invoke-db-worker :thread-api/get-rtc-graph-uuid
|
|
||||||
(state/get-current-repo))]
|
|
||||||
(if-let [graph (current-local-uploadable-graph rtc-graph-uuid)]
|
|
||||||
(repo/upload-local-graph-with-confirm! graph)
|
|
||||||
(shui/popup-show! nil
|
|
||||||
(rtc-indicator/details)
|
|
||||||
{})))
|
|
||||||
"favorite" (when-let [id (state/get-current-page)]
|
"favorite" (when-let [id (state/get-current-page)]
|
||||||
(when (common-util/uuid-string? id)
|
(when (common-util/uuid-string? id)
|
||||||
(p/let [block (db-async/<get-block (state/get-current-repo)
|
(p/let [block (db-async/<get-block (state/get-current-repo)
|
||||||
@@ -236,7 +211,7 @@
|
|||||||
(reset! native-top-bar-listener-version native-top-bar-listener-current-version)))
|
(reset! native-top-bar-listener-version native-top-bar-listener-current-version)))
|
||||||
|
|
||||||
(defn- configure-native-top-bar!
|
(defn- configure-native-top-bar!
|
||||||
[{:keys [tab title route-name route-view sync-color favorited? show-sync? show-local-upload?]}]
|
[{:keys [tab title route-name route-view favorited?]}]
|
||||||
(when (and (mobile-util/native-platform?)
|
(when (and (mobile-util/native-platform?)
|
||||||
mobile-util/native-top-bar)
|
mobile-util/native-top-bar)
|
||||||
(let [hidden? (and (mobile-util/native-ios?) (= tab "search"))
|
(let [hidden? (and (mobile-util/native-ios?) (= tab "search"))
|
||||||
@@ -261,13 +236,7 @@
|
|||||||
(= tab "home")
|
(= tab "home")
|
||||||
(cond-> []
|
(cond-> []
|
||||||
(nil? route-view)
|
(nil? route-view)
|
||||||
(conj {:id "home-setting" :systemIcon "ellipsis"})
|
(conj {:id "home-setting" :systemIcon "ellipsis"}))
|
||||||
(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"}))
|
|
||||||
|
|
||||||
(= tab "graphs")
|
(= tab "graphs")
|
||||||
[{:id "graph-setting" :systemIcon "ellipsis"}
|
[{:id "graph-setting" :systemIcon "ellipsis"}
|
||||||
@@ -325,43 +294,8 @@
|
|||||||
native-ios-graphs? (and (mobile-util/native-ios?) (= tab "graphs"))
|
native-ios-graphs? (and (mobile-util/native-ios?) (= tab "graphs"))
|
||||||
page-route? (and (= route-name :page) (not native-ios-graphs?))
|
page-route? (and (= route-name :page) (not native-ios-graphs?))
|
||||||
[*configure-top-bar-f _] (hooks/use-state (atom nil))
|
[*configure-top-bar-f _] (hooks/use-state (atom nil))
|
||||||
[rtc-graph-uuid set-rtc-graph-uuid!] (hooks/use-state nil)
|
|
||||||
db-worker-ready? (hooks/use-atom-value state/db-worker-ready?)
|
|
||||||
detail-info (rtc-indicator/use-detail-info)
|
|
||||||
_ (rfx/use-sub [:auth/current-login-user])
|
_ (rfx/use-sub [:auth/current-login-user])
|
||||||
online? (rfx/use-sub [:network/online?])
|
fallback-title (build-fallback-title current-repo tab flashcards-header)]
|
||||||
rtc-state (:rtc-state detail-info)
|
|
||||||
graph-uuid (or (:graph-uuid detail-info)
|
|
||||||
rtc-graph-uuid)
|
|
||||||
local-uploadable-graph (current-local-uploadable-graph graph-uuid)
|
|
||||||
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 (build-fallback-title current-repo tab flashcards-header)
|
|
||||||
sync-color (if (and online?
|
|
||||||
(= :open rtc-state)
|
|
||||||
(zero? unpushed-block-update-count)
|
|
||||||
(zero? pending-asset-ops))
|
|
||||||
;; green
|
|
||||||
"#16A34A"
|
|
||||||
;; yellow
|
|
||||||
"#CA8A04")]
|
|
||||||
(hooks/use-effect!
|
|
||||||
(fn []
|
|
||||||
(if-let [rtc-graph-uuid-request (<load-rtc-graph-uuid current-repo db-worker-ready?)]
|
|
||||||
(let [cancelled? (atom false)]
|
|
||||||
(-> rtc-graph-uuid-request
|
|
||||||
(p/then (fn [uuid]
|
|
||||||
(when-not @cancelled?
|
|
||||||
(set-rtc-graph-uuid! uuid))))
|
|
||||||
(p/catch (fn [_] nil)))
|
|
||||||
#(reset! cancelled? true))
|
|
||||||
(do
|
|
||||||
(set-rtc-graph-uuid! nil)
|
|
||||||
nil)))
|
|
||||||
[current-repo db-worker-ready?])
|
|
||||||
|
|
||||||
(hooks/use-effect!
|
(hooks/use-effect!
|
||||||
(fn []
|
(fn []
|
||||||
(when (and (mobile-util/native-platform?)
|
(when (and (mobile-util/native-platform?)
|
||||||
@@ -374,14 +308,11 @@
|
|||||||
:title title
|
:title title
|
||||||
:route-name route-name
|
:route-name route-name
|
||||||
:route-view route-view
|
:route-view route-view
|
||||||
:sync-color sync-color
|
|
||||||
:show-sync? show-sync?
|
|
||||||
:show-local-upload? show-local-upload?
|
|
||||||
:favorited? favorited?}))]
|
:favorited? favorited?}))]
|
||||||
(reset! *configure-top-bar-f f)
|
(reset! *configure-top-bar-f f)
|
||||||
(f nil)))
|
(f nil)))
|
||||||
nil)
|
nil)
|
||||||
[current-repo tab route-name route-view fallback-title sync-color show-sync? show-local-upload?])
|
[current-repo tab route-name route-view fallback-title])
|
||||||
|
|
||||||
(hooks/use-effect!
|
(hooks/use-effect!
|
||||||
(fn []
|
(fn []
|
||||||
@@ -403,16 +334,13 @@
|
|||||||
:title title
|
:title title
|
||||||
:route-name route-name
|
:route-name route-name
|
||||||
:route-view route-view
|
:route-view route-view
|
||||||
:sync-color sync-color
|
|
||||||
:show-sync? show-sync?
|
|
||||||
:show-local-upload? show-local-upload?
|
|
||||||
:favorited? favorited?}))]
|
:favorited? favorited?}))]
|
||||||
(reset! *configure-top-bar-f f)
|
(reset! *configure-top-bar-f f)
|
||||||
(f favorited?))))
|
(f favorited?))))
|
||||||
(p/catch (fn [_] nil)))
|
(p/catch (fn [_] nil)))
|
||||||
#(reset! cancelled? true))
|
#(reset! cancelled? true))
|
||||||
nil))
|
nil))
|
||||||
[current-repo tab route-name route-view route-id sync-color show-sync? show-local-upload? page-route?])
|
[current-repo tab route-name route-view route-id page-route?])
|
||||||
|
|
||||||
[:<>]))
|
[:<>]))
|
||||||
|
|
||||||
|
|||||||
@@ -149,23 +149,16 @@
|
|||||||
|
|
||||||
(defn popup-hide!
|
(defn popup-hide!
|
||||||
[& args]
|
[& args]
|
||||||
(cond
|
(if (and @*last-popup? (not (= (first args) :editor.commands/commands)))
|
||||||
(= :download-rtc-graph (first args))
|
(if (some? @*pending-native-sheet-data)
|
||||||
(do
|
(do
|
||||||
(dismiss-native-sheet!)
|
(reset! *pending-native-sheet-data nil)
|
||||||
(mobile-state/set-tab! "home"))
|
(reset! *last-popup? false)
|
||||||
|
(reset! *last-popup-data nil)
|
||||||
:else
|
(mobile-state/set-popup-presenting! false)
|
||||||
(if (and @*last-popup? (not (= (first args) :editor.commands/commands)))
|
(mobile-state/set-popup! nil))
|
||||||
(if (some? @*pending-native-sheet-data)
|
(dismiss-native-sheet!))
|
||||||
(do
|
(apply shui-popup/hide! args)))
|
||||||
(reset! *pending-native-sheet-data nil)
|
|
||||||
(reset! *last-popup? false)
|
|
||||||
(reset! *last-popup-data nil)
|
|
||||||
(mobile-state/set-popup-presenting! false)
|
|
||||||
(mobile-state/set-popup! nil))
|
|
||||||
(dismiss-native-sheet!))
|
|
||||||
(apply shui-popup/hide! args))))
|
|
||||||
|
|
||||||
(set! shui/popup-show! popup-show!)
|
(set! shui/popup-show! popup-show!)
|
||||||
(set! shui/popup-hide! popup-hide!)
|
(set! shui/popup-hide! popup-hide!)
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
[frontend.components.dnd :as dnd]
|
[frontend.components.dnd :as dnd]
|
||||||
[frontend.components.email :as email-component]
|
[frontend.components.email :as email-component]
|
||||||
[frontend.components.user.login :as login]
|
[frontend.components.user.login :as login]
|
||||||
[frontend.config :as config]
|
|
||||||
[frontend.context.i18n :refer [t]]
|
[frontend.context.i18n :refer [t]]
|
||||||
[frontend.dicts :as dicts]
|
[frontend.dicts :as dicts]
|
||||||
[frontend.handler.ui :as ui-handler]
|
[frontend.handler.ui :as ui-handler]
|
||||||
@@ -278,14 +277,6 @@
|
|||||||
(shui/popup-show! nil (fn [] (log)) {}))}
|
(shui/popup-show! nil (fn [] (log)) {}))}
|
||||||
[:span.text-base (t :mobile.settings/check-log)]]
|
[:span.text-base (t :mobile.settings/check-log)]]
|
||||||
|
|
||||||
[:div.mobile-setting-item
|
|
||||||
{:on-click #(state/pub-event! [:go/sync-server-settings])}
|
|
||||||
[:span.text-base (t :settings.sync-server/url)]
|
|
||||||
[:span.text-sm.opacity-70
|
|
||||||
(if-let [custom (config/get-custom-sync-server-url)]
|
|
||||||
custom
|
|
||||||
"Logseq Sync")]]
|
|
||||||
|
|
||||||
(when login?
|
(when login?
|
||||||
[:div.mobile-setting-item
|
[:div.mobile-setting-item
|
||||||
{:on-click (fn []
|
{:on-click (fn []
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
[clojure.string :as string]
|
[clojure.string :as string]
|
||||||
[frontend.background-tasks]
|
[frontend.background-tasks]
|
||||||
[frontend.handler :as fhandler]
|
[frontend.handler :as fhandler]
|
||||||
[frontend.handler.db-based.rtc-background-tasks]
|
|
||||||
[frontend.handler.notification :as notification]
|
[frontend.handler.notification :as notification]
|
||||||
[frontend.handler.route :as route-handler]
|
[frontend.handler.route :as route-handler]
|
||||||
[frontend.mobile.util :as mobile-util]
|
[frontend.mobile.util :as mobile-util]
|
||||||
|
|||||||
@@ -1,31 +1,15 @@
|
|||||||
(ns frontend.components.repo-test
|
(ns frontend.components.repo-test
|
||||||
(:require [cljs.test :refer [deftest is async]]
|
(:require [cljs.test :refer [deftest is async]]
|
||||||
[frontend.components.repo :as repo]
|
[frontend.components.repo :as repo]
|
||||||
[frontend.components.rtc.indicator :as rtc-indicator]
|
|
||||||
[frontend.handler.db-based.sync :as rtc-handler]
|
|
||||||
[frontend.handler.graph :as graph-handler]
|
[frontend.handler.graph :as graph-handler]
|
||||||
[frontend.handler.repo :as repo-handler]
|
|
||||||
[frontend.handler.user :as user-handler]
|
|
||||||
[frontend.state :as state]
|
[frontend.state :as state]
|
||||||
[frontend.mobile.util :as mobile-util]
|
|
||||||
[frontend.util :as util]
|
[frontend.util :as util]
|
||||||
[logseq.shui.ui :as shui]
|
|
||||||
[promesa.core :as p]))
|
[promesa.core :as p]))
|
||||||
|
|
||||||
(defn- ensure-rsa-key-fn
|
|
||||||
[]
|
|
||||||
#'repo/ensure-e2ee-rsa-key-for-cloud!)
|
|
||||||
|
|
||||||
(defn- finish-async-test!
|
(defn- finish-async-test!
|
||||||
[done]
|
[done]
|
||||||
(js/setTimeout done 0))
|
(js/setTimeout done 0))
|
||||||
|
|
||||||
(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 open-in-another-tab-action-is-web-only-for-existing-graphs-test
|
(deftest open-in-another-tab-action-is-web-only-for-existing-graphs-test
|
||||||
(let [actionable-f (some-> (resolve 'frontend.components.repo/open-in-another-tab-action?) deref)]
|
(let [actionable-f (some-> (resolve 'frontend.components.repo/open-in-another-tab-action?) deref)]
|
||||||
(is (fn? actionable-f) "All graphs should expose a web open-in-another-tab action predicate")
|
(is (fn? actionable-f) "All graphs should expose a web open-in-another-tab action predicate")
|
||||||
@@ -80,381 +64,6 @@
|
|||||||
(is false (str error))
|
(is false (str error))
|
||||||
(finish-async-test! done))))))))
|
(finish-async-test! done))))))))
|
||||||
|
|
||||||
(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!")
|
|
||||||
(finish-async-test! done))
|
|
||||||
(-> (p/with-redefs [shui/dialog-confirm! (fn [content opts]
|
|
||||||
(swap! dialogs conj {:content content
|
|
||||||
:opts opts})
|
|
||||||
(p/resolved nil))
|
|
||||||
rtc-handler/<rtc-upload-graph! (fn [repo graph-e2ee?]
|
|
||||||
(swap! upload-calls conj [repo graph-e2ee?])
|
|
||||||
(p/resolved nil))
|
|
||||||
state/get-current-repo (fn []
|
|
||||||
"logseq_db_demo")
|
|
||||||
rtc-indicator/on-upload-finished-task (fn [f]
|
|
||||||
(swap! finished-calls inc)
|
|
||||||
(f))
|
|
||||||
util/mobile? (fn [] false)
|
|
||||||
shui/popup-show! (fn [& _] nil)
|
|
||||||
shui/popup-hide! (fn [& _] nil)]
|
|
||||||
(upload-fn {:url "logseq_db_demo"
|
|
||||||
:graph-e2ee? true}))
|
|
||||||
(p/then (fn [_]
|
|
||||||
(is (= 1 (count @dialogs)))
|
|
||||||
(is (= [["logseq_db_demo" true]] @upload-calls))
|
|
||||||
(is (= 1 @finished-calls))
|
|
||||||
(finish-async-test! done)))
|
|
||||||
(p/catch (fn [error]
|
|
||||||
(is false (str error))
|
|
||||||
(finish-async-test! done))))))))
|
|
||||||
|
|
||||||
(deftest upload-local-graph-switches-before-uploading-non-current-graph-test
|
|
||||||
(async done
|
|
||||||
(let [upload-fn (some-> (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!")
|
|
||||||
(finish-async-test! 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))
|
|
||||||
repo/<invoke-db-worker
|
|
||||||
(fn [op repo key]
|
|
||||||
(swap! calls conj [op repo key])
|
|
||||||
(is (= :thread-api/get-key-value op))
|
|
||||||
(is (= "logseq_db_other" repo))
|
|
||||||
(is (= :logseq.kv/graph-rtc-e2ee? key))
|
|
||||||
(p/resolved false))
|
|
||||||
rtc-handler/<rtc-upload-graph! (fn [repo graph-e2ee?]
|
|
||||||
(swap! calls conj [:upload repo graph-e2ee?])
|
|
||||||
(p/resolved nil))
|
|
||||||
rtc-indicator/on-upload-finished-task (fn [f]
|
|
||||||
(f))
|
|
||||||
util/mobile? (fn [] false)
|
|
||||||
shui/popup-show! (fn [& _] nil)
|
|
||||||
shui/popup-hide! (fn [& _] nil)]
|
|
||||||
(upload-fn {:url "logseq_db_other"}))
|
|
||||||
(p/then (fn [_]
|
|
||||||
(is (= [[:graph/switch "logseq_db_other"]
|
|
||||||
[:thread-api/get-key-value "logseq_db_other" :logseq.kv/graph-rtc-e2ee?]
|
|
||||||
[:upload "logseq_db_other" false]]
|
|
||||||
@calls))
|
|
||||||
(finish-async-test! done)))
|
|
||||||
(p/catch (fn [error]
|
|
||||||
(is false (str error))
|
|
||||||
(finish-async-test! done))))))))
|
|
||||||
|
|
||||||
(deftest upload-local-graph-reads-current-graph-e2ee-from-worker-test
|
|
||||||
(async done
|
|
||||||
(let [upload-fn (some-> (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!")
|
|
||||||
(finish-async-test! done))
|
|
||||||
(-> (p/with-redefs [shui/dialog-confirm! (fn [_content _opts]
|
|
||||||
(p/resolved nil))
|
|
||||||
state/get-current-repo (fn []
|
|
||||||
"logseq_db_demo")
|
|
||||||
repo/<invoke-db-worker
|
|
||||||
(fn [op repo key]
|
|
||||||
(swap! calls conj [op repo key])
|
|
||||||
(is (= :thread-api/get-key-value op))
|
|
||||||
(is (= "logseq_db_demo" repo))
|
|
||||||
(is (= :logseq.kv/graph-rtc-e2ee? key))
|
|
||||||
(p/resolved false))
|
|
||||||
rtc-handler/<rtc-upload-graph! (fn [repo graph-e2ee?]
|
|
||||||
(swap! calls conj [:upload repo graph-e2ee?])
|
|
||||||
(p/resolved nil))
|
|
||||||
rtc-indicator/on-upload-finished-task (fn [f]
|
|
||||||
(f))
|
|
||||||
util/mobile? (fn [] false)
|
|
||||||
shui/popup-show! (fn [& _] nil)
|
|
||||||
shui/popup-hide! (fn [& _] nil)]
|
|
||||||
(upload-fn {:url "logseq_db_demo"}))
|
|
||||||
(p/then (fn [_]
|
|
||||||
(is (= [[:thread-api/get-key-value "logseq_db_demo" :logseq.kv/graph-rtc-e2ee?]
|
|
||||||
[:upload "logseq_db_demo" false]]
|
|
||||||
@calls))
|
|
||||||
(finish-async-test! done)))
|
|
||||||
(p/catch (fn [error]
|
|
||||||
(is false (str error))
|
|
||||||
(finish-async-test! done))))))))
|
|
||||||
|
|
||||||
(deftest upload-local-graph-shows-mobile-popup-before-upload-starts-test
|
|
||||||
(async done
|
|
||||||
(let [upload-fn (some-> (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!")
|
|
||||||
(finish-async-test! done))
|
|
||||||
(-> (p/with-redefs [shui/dialog-confirm! (fn [_content _opts]
|
|
||||||
(p/resolved nil))
|
|
||||||
state/get-current-repo (fn []
|
|
||||||
"logseq_db_demo")
|
|
||||||
rtc-handler/<rtc-upload-graph! (fn [_repo _graph-e2ee?]
|
|
||||||
(swap! calls conj :upload)
|
|
||||||
(is (= [:popup-show :finish-handler :upload] @calls))
|
|
||||||
(p/resolved nil))
|
|
||||||
rtc-indicator/on-upload-finished-task (fn [_f]
|
|
||||||
(swap! calls conj :finish-handler))
|
|
||||||
util/mobile? (fn [] true)
|
|
||||||
shui/popup-show! (fn [& _]
|
|
||||||
(swap! calls conj :popup-show))
|
|
||||||
shui/popup-hide! (fn [& _]
|
|
||||||
(swap! calls conj :popup-hide))]
|
|
||||||
(upload-fn {:url "logseq_db_demo"
|
|
||||||
:graph-e2ee? true}))
|
|
||||||
(p/then (fn [_]
|
|
||||||
(is (= [:popup-show :finish-handler :upload :popup-hide]
|
|
||||||
@calls))
|
|
||||||
(finish-async-test! done)))
|
|
||||||
(p/catch (fn [error]
|
|
||||||
(is false (str error))
|
|
||||||
(finish-async-test! done))))))))
|
|
||||||
|
|
||||||
(deftest upload-local-graph-hides-mobile-popup-when-upload-errors-test
|
|
||||||
(async done
|
|
||||||
(let [upload-fn (some-> (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!")
|
|
||||||
(finish-async-test! done))
|
|
||||||
(-> (p/with-redefs [shui/dialog-confirm! (fn [_content _opts]
|
|
||||||
(p/resolved nil))
|
|
||||||
state/get-current-repo (fn []
|
|
||||||
"logseq_db_demo")
|
|
||||||
rtc-handler/<rtc-upload-graph! (fn [_repo _graph-e2ee?]
|
|
||||||
(swap! calls conj :upload)
|
|
||||||
(p/rejected (ex-info "upload failed" {})))
|
|
||||||
rtc-indicator/on-upload-finished-task (fn [_f]
|
|
||||||
(swap! calls conj :finish-handler))
|
|
||||||
util/mobile? (fn [] true)
|
|
||||||
shui/popup-show! (fn [& _]
|
|
||||||
(swap! calls conj :popup-show))
|
|
||||||
shui/popup-hide! (fn [& _]
|
|
||||||
(swap! calls conj :popup-hide))]
|
|
||||||
(upload-fn {:url "logseq_db_demo"
|
|
||||||
:graph-e2ee? true}))
|
|
||||||
(p/then (fn [_]
|
|
||||||
(is false "expected upload failure")
|
|
||||||
(finish-async-test! done)))
|
|
||||||
(p/catch (fn [error]
|
|
||||||
(is (= "upload failed" (ex-message error)))
|
|
||||||
(is (= [:popup-show :finish-handler :upload :popup-hide]
|
|
||||||
@calls))
|
|
||||||
(finish-async-test! done))))))))
|
|
||||||
|
|
||||||
(deftest ensure-rsa-key-does-not-create-graph-test
|
|
||||||
(async done
|
|
||||||
(let [ensure-fn (ensure-rsa-key-fn)
|
|
||||||
db-worker-calls (atom [])
|
|
||||||
create-calls (atom 0)
|
|
||||||
ensured-values (atom [])]
|
|
||||||
(if-not ensure-fn
|
|
||||||
(do
|
|
||||||
(is false "missing ensure-e2ee-rsa-key-for-cloud!")
|
|
||||||
(finish-async-test! done))
|
|
||||||
(-> (p/with-redefs [state/pub-event! (fn [_event]
|
|
||||||
(p/resolved nil))
|
|
||||||
repo/<invoke-db-worker
|
|
||||||
(fn [op & args]
|
|
||||||
(swap! db-worker-calls conj (into [op] args))
|
|
||||||
(if (= op :thread-api/db-sync-ensure-user-rsa-keys)
|
|
||||||
(p/resolved {:public-key "pk"})
|
|
||||||
(p/resolved nil)))
|
|
||||||
repo-handler/new-db!
|
|
||||||
(fn [& _]
|
|
||||||
(swap! create-calls inc)
|
|
||||||
(p/resolved "repo"))]
|
|
||||||
(ensure-fn {:cloud? true
|
|
||||||
:graph-e2ee? true
|
|
||||||
:refresh-token "refresh"
|
|
||||||
:token "token"
|
|
||||||
:user-uuid "user-1"
|
|
||||||
:e2ee-rsa-key-ensured? false}
|
|
||||||
(fn [value]
|
|
||||||
(swap! ensured-values conj value))))
|
|
||||||
(p/then (fn [_]
|
|
||||||
(let [[set-config-call ensure-call] @db-worker-calls
|
|
||||||
set-config (second set-config-call)]
|
|
||||||
(is (= :thread-api/set-db-sync-config (first set-config-call)))
|
|
||||||
(is (= :thread-api/db-sync-ensure-user-rsa-keys (first ensure-call)))
|
|
||||||
(is (= true (:enabled? set-config)))
|
|
||||||
(is (contains? set-config :ws-url))
|
|
||||||
(is (contains? set-config :http-base))
|
|
||||||
(is (not (contains? set-config :oauth-domain)))
|
|
||||||
(is (not (contains? set-config :oauth-client-id))))
|
|
||||||
(is (= [true] @ensured-values))
|
|
||||||
(is (zero? @create-calls))
|
|
||||||
(finish-async-test! done)))
|
|
||||||
(p/catch (fn [error]
|
|
||||||
(is false (str error))
|
|
||||||
(finish-async-test! done))))))))
|
|
||||||
|
|
||||||
(deftest ensure-rsa-key-nil-result-does-not-create-graph-test
|
|
||||||
(async done
|
|
||||||
(let [ensure-fn (ensure-rsa-key-fn)
|
|
||||||
db-worker-calls (atom [])
|
|
||||||
create-calls (atom 0)
|
|
||||||
ensured-values (atom [])]
|
|
||||||
(if-not ensure-fn
|
|
||||||
(do
|
|
||||||
(is false "missing ensure-e2ee-rsa-key-for-cloud!")
|
|
||||||
(finish-async-test! done))
|
|
||||||
(-> (p/with-redefs [state/pub-event! (fn [_event]
|
|
||||||
(p/resolved nil))
|
|
||||||
repo/<invoke-db-worker
|
|
||||||
(fn [op & args]
|
|
||||||
(swap! db-worker-calls conj (into [op] args))
|
|
||||||
(p/resolved nil))
|
|
||||||
repo-handler/new-db!
|
|
||||||
(fn [& _]
|
|
||||||
(swap! create-calls inc)
|
|
||||||
(p/resolved "repo"))]
|
|
||||||
(ensure-fn {:cloud? true
|
|
||||||
:graph-e2ee? true
|
|
||||||
:refresh-token "refresh"
|
|
||||||
:token "token"
|
|
||||||
:user-uuid "user-1"
|
|
||||||
:e2ee-rsa-key-ensured? false}
|
|
||||||
(fn [value]
|
|
||||||
(swap! ensured-values conj value))))
|
|
||||||
(p/then (fn [_]
|
|
||||||
(let [[set-config-call ensure-call] @db-worker-calls
|
|
||||||
set-config (second set-config-call)]
|
|
||||||
(is (= :thread-api/set-db-sync-config (first set-config-call)))
|
|
||||||
(is (= :thread-api/db-sync-ensure-user-rsa-keys (first ensure-call)))
|
|
||||||
(is (= true (:enabled? set-config)))
|
|
||||||
(is (contains? set-config :ws-url))
|
|
||||||
(is (contains? set-config :http-base))
|
|
||||||
(is (not (contains? set-config :oauth-domain)))
|
|
||||||
(is (not (contains? set-config :oauth-client-id))))
|
|
||||||
(is (= [false] @ensured-values))
|
|
||||||
(is (zero? @create-calls))
|
|
||||||
(finish-async-test! done)))
|
|
||||||
(p/catch (fn [error]
|
|
||||||
(is false (str error))
|
|
||||||
(finish-async-test! done))))))))
|
|
||||||
|
|
||||||
(deftest ensure-rsa-key-skips-when-prerequisites-missing-test
|
|
||||||
(async done
|
|
||||||
(let [ensure-fn (ensure-rsa-key-fn)
|
|
||||||
ensure-calls (atom 0)
|
|
||||||
create-calls (atom 0)
|
|
||||||
ensured-values (atom [])]
|
|
||||||
(if-not ensure-fn
|
|
||||||
(do
|
|
||||||
(is false "missing ensure-e2ee-rsa-key-for-cloud!")
|
|
||||||
(finish-async-test! done))
|
|
||||||
(-> (p/with-redefs [repo/<invoke-db-worker
|
|
||||||
(fn [& _]
|
|
||||||
(swap! ensure-calls inc)
|
|
||||||
(p/resolved {:public-key "pk"}))
|
|
||||||
repo-handler/new-db!
|
|
||||||
(fn [& _]
|
|
||||||
(swap! create-calls inc)
|
|
||||||
(p/resolved "repo"))]
|
|
||||||
(ensure-fn {:cloud? false
|
|
||||||
:refresh-token nil
|
|
||||||
:token nil
|
|
||||||
:user-uuid nil
|
|
||||||
:e2ee-rsa-key-ensured? false}
|
|
||||||
(fn [value]
|
|
||||||
(swap! ensured-values conj value))))
|
|
||||||
(p/then (fn [_]
|
|
||||||
(is (zero? @ensure-calls))
|
|
||||||
(is (empty? @ensured-values))
|
|
||||||
(is (zero? @create-calls))
|
|
||||||
(finish-async-test! done)))
|
|
||||||
(p/catch (fn [error]
|
|
||||||
(is false (str error))
|
|
||||||
(finish-async-test! done))))))))
|
|
||||||
|
|
||||||
(deftest ensure-rsa-key-skips-when-graph-e2ee-disabled-test
|
|
||||||
(async done
|
|
||||||
(let [ensure-fn (ensure-rsa-key-fn)
|
|
||||||
ensure-calls (atom 0)
|
|
||||||
ensured-values (atom [])]
|
|
||||||
(if-not ensure-fn
|
|
||||||
(do
|
|
||||||
(is false "missing ensure-e2ee-rsa-key-for-cloud!")
|
|
||||||
(finish-async-test! done))
|
|
||||||
(-> (p/with-redefs [repo/<invoke-db-worker
|
|
||||||
(fn [& _]
|
|
||||||
(swap! ensure-calls inc)
|
|
||||||
(p/resolved {:public-key "pk"}))]
|
|
||||||
(ensure-fn {:cloud? true
|
|
||||||
:graph-e2ee? false
|
|
||||||
:refresh-token "refresh"
|
|
||||||
:token "token"
|
|
||||||
:user-uuid "user-1"
|
|
||||||
:e2ee-rsa-key-ensured? false}
|
|
||||||
(fn [value]
|
|
||||||
(swap! ensured-values conj value))))
|
|
||||||
(p/then (fn [_]
|
|
||||||
(is (zero? @ensure-calls))
|
|
||||||
(is (empty? @ensured-values))
|
|
||||||
(finish-async-test! done)))
|
|
||||||
(p/catch (fn [error]
|
|
||||||
(is false (str error))
|
|
||||||
(finish-async-test! done))))))))
|
|
||||||
|
|
||||||
(deftest not-ready-remote-graph-does-not-trigger-download-test
|
|
||||||
(let [events (atom [])
|
|
||||||
links (#'repo/repos-dropdown-links
|
|
||||||
[{:url "logseq_db_demo"
|
|
||||||
:remote? true
|
|
||||||
:rtc-graph? true
|
|
||||||
:GraphName "demo"
|
|
||||||
:GraphUUID "graph-1"
|
|
||||||
:GraphSchemaVersion "65"
|
|
||||||
:graph-ready-for-use? false}]
|
|
||||||
nil
|
|
||||||
nil)
|
|
||||||
on-click (get-in (first links) [:options :on-click])]
|
|
||||||
(with-redefs [state/pub-event! (fn [event]
|
|
||||||
(swap! events conj event))]
|
|
||||||
(on-click #js {})
|
|
||||||
(is (empty? @events)))))
|
|
||||||
|
|
||||||
(deftest native-mobile-remote-graph-download-allows-popup-to-close-test
|
|
||||||
(let [events (atom [])
|
|
||||||
links (#'repo/repos-dropdown-links
|
|
||||||
[{:url "logseq_db_demo"
|
|
||||||
:remote? true
|
|
||||||
:rtc-graph? true
|
|
||||||
:GraphName "demo"
|
|
||||||
:GraphUUID "graph-1"
|
|
||||||
:GraphSchemaVersion "65"
|
|
||||||
:graph-ready-for-use? true}]
|
|
||||||
nil
|
|
||||||
nil)
|
|
||||||
on-click (get-in (first links) [:options :on-click])]
|
|
||||||
(with-redefs [mobile-util/native-platform? (constantly true)
|
|
||||||
state/pub-event! (fn [event]
|
|
||||||
(swap! events conj event))]
|
|
||||||
(is (not (false? (on-click #js {}))))
|
|
||||||
(is (= [[:rtc/download-remote-graph "demo" "graph-1" "65" nil]]
|
|
||||||
@events)))))
|
|
||||||
|
|
||||||
(deftest graph-selection-defers-closing-sidebar-at-mobile-breakpoint-test
|
(deftest graph-selection-defers-closing-sidebar-at-mobile-breakpoint-test
|
||||||
(async done
|
(async done
|
||||||
(let [close-f (some-> (resolve 'frontend.components.repo/close-sidebar-after-repo-popup-action!)
|
(let [close-f (some-> (resolve 'frontend.components.repo/close-sidebar-after-repo-popup-action!)
|
||||||
@@ -498,35 +107,12 @@
|
|||||||
(is (true? (target? (target true))))
|
(is (true? (target? (target true))))
|
||||||
(is (false? (target? (target false)))))))
|
(is (false? (target? (target false)))))))
|
||||||
|
|
||||||
(deftest shift-click-opens-downloaded-remote-graph-in-new-tab-with-graph-id-test
|
|
||||||
(let [events (atom [])
|
|
||||||
links (#'repo/repos-dropdown-links
|
|
||||||
[{:url "logseq_db_demo"
|
|
||||||
:root "/graphs/demo"
|
|
||||||
:remote? true
|
|
||||||
:rtc-graph? true
|
|
||||||
:GraphName "demo"
|
|
||||||
:GraphUUID "graph-uuid"
|
|
||||||
:GraphSchemaVersion "65"
|
|
||||||
:graph-ready-for-use? true}]
|
|
||||||
nil
|
|
||||||
nil)
|
|
||||||
on-click (get-in (first links) [:options :on-click])]
|
|
||||||
(with-redefs [state/pub-event! (fn [event]
|
|
||||||
(swap! events conj event))]
|
|
||||||
(on-click #js {:shiftKey true})
|
|
||||||
(is (= [[:graph/open-new-window {:repo "logseq_db_demo"
|
|
||||||
:graph-id "graph-uuid"}]]
|
|
||||||
@events)))))
|
|
||||||
|
|
||||||
(deftest shift-click-opens-local-graph-in-new-tab-with-registry-graph-id-test
|
(deftest shift-click-opens-local-graph-in-new-tab-with-registry-graph-id-test
|
||||||
(async done
|
(async done
|
||||||
(let [events (atom [])
|
(let [events (atom [])
|
||||||
links (#'repo/repos-dropdown-links
|
links (#'repo/repos-dropdown-links
|
||||||
[{:url "logseq_db_demo"
|
[{:url "logseq_db_demo"
|
||||||
:root "/graphs/demo"
|
:root "/graphs/demo"}]
|
||||||
:graph-ready-for-use? true}]
|
|
||||||
nil
|
|
||||||
nil)
|
nil)
|
||||||
on-click (get-in (first links) [:options :on-click])]
|
on-click (get-in (first links) [:options :on-click])]
|
||||||
(-> (p/with-redefs [graph-handler/<get-graph-registry
|
(-> (p/with-redefs [graph-handler/<get-graph-registry
|
||||||
@@ -551,9 +137,7 @@
|
|||||||
registry-reads (atom 0)
|
registry-reads (atom 0)
|
||||||
links (#'repo/repos-dropdown-links
|
links (#'repo/repos-dropdown-links
|
||||||
[{:url "logseq_db_demo"
|
[{:url "logseq_db_demo"
|
||||||
:root "/graphs/demo"
|
:root "/graphs/demo"}]
|
||||||
:graph-ready-for-use? true}]
|
|
||||||
nil
|
|
||||||
nil)
|
nil)
|
||||||
on-click (get-in (first links) [:options :on-click])]
|
on-click (get-in (first links) [:options :on-click])]
|
||||||
(with-redefs [util/electron? (constantly true)
|
(with-redefs [util/electron? (constantly true)
|
||||||
|
|||||||
@@ -1,50 +0,0 @@
|
|||||||
(ns frontend.components.rtc.indicator-test
|
|
||||||
(:require [cljs.test :refer [deftest is]]
|
|
||||||
[frontend.components.rtc.indicator :as indicator]))
|
|
||||||
|
|
||||||
(deftest asset-transfer-counts-counts-active-uploads-and-downloads
|
|
||||||
(is (= {:upload 2
|
|
||||||
:download 1}
|
|
||||||
(indicator/asset-transfer-counts
|
|
||||||
{"upload-1" {:direction :upload :loaded 0 :total 10}
|
|
||||||
"upload-2" {:direction :upload :loaded 5 :total 10}
|
|
||||||
"upload-done" {:direction :upload :loaded 10 :total 10}
|
|
||||||
"download-1" {:direction :download :loaded 1 :total 10}
|
|
||||||
"missing-total" {:direction :download :loaded 1}
|
|
||||||
"other" {:direction :other :loaded 0 :total 10}}))))
|
|
||||||
|
|
||||||
(deftest asset-status-rows-shows-pending-upload-and-active-transfer-info
|
|
||||||
(is (= [{:count 2 :label-key :sync/missing-asset-files}
|
|
||||||
{:count 1 :label-key :sync/pending-asset-uploads}
|
|
||||||
{:count 1 :label-key :sync/assets-uploading}
|
|
||||||
{:count 2 :label-key :sync/assets-downloading}]
|
|
||||||
(indicator/asset-status-rows
|
|
||||||
{:pending-asset-ops 3
|
|
||||||
:missing-asset-upload-files [{:file "assets/missing-1.pdf"}
|
|
||||||
{:file "assets/missing-2.pdf"}]
|
|
||||||
:asset-transfer-counts {:upload 1
|
|
||||||
:download 2}}))))
|
|
||||||
|
|
||||||
(deftest asset-status-rows-hides-zero-counts
|
|
||||||
(is (= []
|
|
||||||
(indicator/asset-status-rows
|
|
||||||
{:pending-asset-ops 0
|
|
||||||
:missing-asset-upload-files []
|
|
||||||
:asset-transfer-counts {:upload 0
|
|
||||||
:download 0}}))))
|
|
||||||
|
|
||||||
(deftest detail-info-state-is-closed-without-rtc-lock
|
|
||||||
(is (= :close
|
|
||||||
(:rtc-state (indicator/rtc-state->detail-info {})))))
|
|
||||||
|
|
||||||
(deftest indicator-button-class-is-space-separated
|
|
||||||
(let [class-name (indicator/indicator-button-class
|
|
||||||
{:online? true
|
|
||||||
:rtc-state :open
|
|
||||||
:pending-local-ops 0
|
|
||||||
:pending-asset-ops 0
|
|
||||||
:pending-server-ops 0})]
|
|
||||||
(is (string? class-name))
|
|
||||||
(is (not (re-find #"," class-name)))
|
|
||||||
(is (= #{"cloud" "on" "idle"}
|
|
||||||
(set (.split class-name " "))))))
|
|
||||||
@@ -14,60 +14,6 @@
|
|||||||
(is (= "/tmp/home/logseq/graphs/foo~2Fbar/backup"
|
(is (= "/tmp/home/logseq/graphs/foo~2Fbar/backup"
|
||||||
(config/get-electron-backup-dir (str common-config/db-version-prefix "foo/bar"))))))
|
(config/get-electron-backup-dir (str common-config/db-version-prefix "foo/bar"))))))
|
||||||
|
|
||||||
(deftest custom-url->ws-url-test
|
|
||||||
(testing "https URL becomes wss"
|
|
||||||
(is (= "wss://my-server.example.com/sync/%s"
|
|
||||||
(config/custom-url->ws-url "https://my-server.example.com"))))
|
|
||||||
|
|
||||||
(testing "http URL becomes ws"
|
|
||||||
(is (= "ws://localhost:8787/sync/%s"
|
|
||||||
(config/custom-url->ws-url "http://localhost:8787"))))
|
|
||||||
|
|
||||||
(testing "trailing slashes are stripped"
|
|
||||||
(is (= "wss://my-server.example.com/sync/%s"
|
|
||||||
(config/custom-url->ws-url "https://my-server.example.com/")))
|
|
||||||
(is (= "wss://my-server.example.com/sync/%s"
|
|
||||||
(config/custom-url->ws-url "https://my-server.example.com///"))))
|
|
||||||
|
|
||||||
(testing "preserves port in URL"
|
|
||||||
(is (= "wss://example.com:3000/sync/%s"
|
|
||||||
(config/custom-url->ws-url "https://example.com:3000"))))
|
|
||||||
|
|
||||||
(testing "preserves subpath in host"
|
|
||||||
;; Users should only provide a base URL, but verify trailing path doesn't break things
|
|
||||||
(is (= "wss://example.com/api/sync/%s"
|
|
||||||
(config/custom-url->ws-url "https://example.com/api")))))
|
|
||||||
|
|
||||||
(deftest custom-url->http-base-test
|
|
||||||
(testing "returns URL as-is when no trailing slash"
|
|
||||||
(is (= "https://my-server.example.com"
|
|
||||||
(config/custom-url->http-base "https://my-server.example.com"))))
|
|
||||||
|
|
||||||
(testing "strips trailing slashes"
|
|
||||||
(is (= "https://my-server.example.com"
|
|
||||||
(config/custom-url->http-base "https://my-server.example.com/")))
|
|
||||||
(is (= "https://my-server.example.com"
|
|
||||||
(config/custom-url->http-base "https://my-server.example.com///"))))
|
|
||||||
|
|
||||||
(testing "preserves http scheme"
|
|
||||||
(is (= "http://localhost:8787"
|
|
||||||
(config/custom-url->http-base "http://localhost:8787"))))
|
|
||||||
|
|
||||||
(testing "preserves port"
|
|
||||||
(is (= "https://example.com:3000"
|
|
||||||
(config/custom-url->http-base "https://example.com:3000/")))))
|
|
||||||
|
|
||||||
(deftest default-urls-are-returned-when-no-custom-url
|
|
||||||
(testing "db-sync-ws-url returns default when no custom URL is set"
|
|
||||||
;; In test environment, node-test? is true so get-custom-sync-server-url
|
|
||||||
;; always returns nil, meaning we always get the default
|
|
||||||
(is (string? (config/db-sync-ws-url)))
|
|
||||||
(is (= config/default-db-sync-ws-url (config/db-sync-ws-url))))
|
|
||||||
|
|
||||||
(testing "db-sync-http-base returns default when no custom URL is set"
|
|
||||||
(is (string? (config/db-sync-http-base)))
|
|
||||||
(is (= config/default-db-sync-http-base (config/db-sync-http-base)))))
|
|
||||||
|
|
||||||
(deftest valid-sync-server-url?-test
|
(deftest valid-sync-server-url?-test
|
||||||
(testing "accepts http and https URLs"
|
(testing "accepts http and https URLs"
|
||||||
(is (config/valid-sync-server-url? "https://my-server.example.com"))
|
(is (config/valid-sync-server-url? "https://my-server.example.com"))
|
||||||
|
|||||||
@@ -11,8 +11,6 @@
|
|||||||
(deftest restore-graph-does-not-create-renderer-datascript-conn-test
|
(deftest restore-graph-does-not-create-renderer-datascript-conn-test
|
||||||
(async done
|
(async done
|
||||||
(let [repo "logseq_db_restore_without_ui_conn"
|
(let [repo "logseq_db_restore_without_ui_conn"
|
||||||
block-id (str (random-uuid))
|
|
||||||
conflicts-by-block {block-id [{:value "remote"}]}
|
|
||||||
previous-state (state/get-state)
|
previous-state (state/get-state)
|
||||||
previous-conns @db-conn/conns
|
previous-conns @db-conn/conns
|
||||||
calls (atom [])
|
calls (atom [])
|
||||||
@@ -36,13 +34,6 @@
|
|||||||
(swap! calls conj [:current-repo repo'])
|
(swap! calls conj [:current-repo repo'])
|
||||||
(state/swap-state! assoc :git/current-repo repo')
|
(state/swap-state! assoc :git/current-repo repo')
|
||||||
nil)
|
nil)
|
||||||
state/<invoke-db-worker
|
|
||||||
(fn [api repo']
|
|
||||||
(swap! calls conj [:worker api repo'])
|
|
||||||
(p/resolved conflicts-by-block))
|
|
||||||
state/set-sync-block-conflicts!
|
|
||||||
(fn [& args]
|
|
||||||
(swap! calls conj (into [:hydrate] args)))
|
|
||||||
db-subs/reset-graph!
|
db-subs/reset-graph!
|
||||||
(fn [repo']
|
(fn [repo']
|
||||||
(swap! reset-graphs conj repo')
|
(swap! reset-graphs conj repo')
|
||||||
@@ -56,14 +47,12 @@
|
|||||||
(is (= [[:graph/restored repo] [:ui/re-render-root]]
|
(is (= [[:graph/restored repo] [:ui/re-render-root]]
|
||||||
@events))
|
@events))
|
||||||
(is (= [[:open repo]
|
(is (= [[:open repo]
|
||||||
[:worker :thread-api/db-sync-get-all-block-conflicts repo]
|
|
||||||
[:current-repo repo]
|
[:current-repo repo]
|
||||||
[:reset repo]
|
[:reset repo]
|
||||||
[:hydrate repo conflicts-by-block]
|
|
||||||
[:event [:graph/restored repo]]
|
[:event [:graph/restored repo]]
|
||||||
[:event [:ui/re-render-root]]]
|
[:event [:ui/re-render-root]]]
|
||||||
@calls)
|
@calls)
|
||||||
"Restore hydrates all conflicts once after graph reset and before rendering.")
|
"Restore resets renderer subscriptions before rendering.")
|
||||||
(is (empty? @db-conn/conns)
|
(is (empty? @db-conn/conns)
|
||||||
"Restoring a graph must not create a renderer DataScript connection.")))
|
"Restoring a graph must not create a renderer DataScript connection.")))
|
||||||
(p/catch
|
(p/catch
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
(:require [cljs.test :refer [async deftest is]]
|
(:require [cljs.test :refer [async deftest is]]
|
||||||
[clojure.string :as string]
|
[clojure.string :as string]
|
||||||
[frontend.handler.common.developer :as dev-common-handler]
|
[frontend.handler.common.developer :as dev-common-handler]
|
||||||
[frontend.handler.db-based.sync :as rtc-handler]
|
|
||||||
[frontend.handler.notification :as notification]
|
[frontend.handler.notification :as notification]
|
||||||
[frontend.state :as state]
|
[frontend.state :as state]
|
||||||
[promesa.core :as p]))
|
[promesa.core :as p]))
|
||||||
@@ -36,31 +35,3 @@
|
|||||||
(fn [error]
|
(fn [error]
|
||||||
(is false (str error))))
|
(is false (str error))))
|
||||||
(p/finally done)))))
|
(p/finally done)))))
|
||||||
|
|
||||||
(deftest checksum-diagnostics-fetches-graph-id-through-worker-test
|
|
||||||
(async done
|
|
||||||
(let [repo "logseq_db_checksum_worker"
|
|
||||||
worker-calls (atom [])
|
|
||||||
fetch-calls (atom [])]
|
|
||||||
(p/with-redefs [state/<invoke-db-worker
|
|
||||||
(fn [api repo']
|
|
||||||
(swap! worker-calls conj [api repo'])
|
|
||||||
(p/resolved #uuid "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"))
|
|
||||||
rtc-handler/http-base
|
|
||||||
(fn [] "https://sync.example.test")
|
|
||||||
rtc-handler/fetch-json
|
|
||||||
(fn [url request opts]
|
|
||||||
(swap! fetch-calls conj [url request opts])
|
|
||||||
(p/resolved {:checksum "server-checksum"}))]
|
|
||||||
(-> (p/let [result (#'dev-common-handler/<fetch-server-checksum-diagnostics repo)]
|
|
||||||
(is (= {:checksum "server-checksum"} result))
|
|
||||||
(is (= [[:thread-api/get-rtc-graph-uuid repo]]
|
|
||||||
@worker-calls))
|
|
||||||
(is (= [["https://sync.example.test/sync/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa/checksum/diagnostics"
|
|
||||||
{:method "GET"}
|
|
||||||
{:error-schema :error}]]
|
|
||||||
@fetch-calls)))
|
|
||||||
(p/catch
|
|
||||||
(fn [error]
|
|
||||||
(is false (str error))))
|
|
||||||
(p/finally done))))))
|
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
(ns frontend.handler.db-based.rtc-flows-test
|
|
||||||
(:require [cljs.test :refer [deftest is]]
|
|
||||||
[frontend.config :as config]
|
|
||||||
[frontend.handler.db-based.rtc-background-tasks :as rtc-background-tasks]
|
|
||||||
[frontend.handler.db-based.rtc-flows :as rtc-flows]))
|
|
||||||
|
|
||||||
(deftest resume-restart-events-do-not-depend-on-cached-rtc-lock-test
|
|
||||||
(is (= :document-visible&rtc-not-running
|
|
||||||
(#'rtc-flows/document-visible->restart-event "visible")))
|
|
||||||
(is (= :network-online&rtc-not-running
|
|
||||||
(#'rtc-flows/network-online->restart-event true)))
|
|
||||||
(is (= :mobile-app-active&rtc-not-running
|
|
||||||
(#'rtc-flows/mobile-app-active->restart-event true))))
|
|
||||||
|
|
||||||
(deftest rtc-background-watch-consumes-existing-event-test
|
|
||||||
(let [source (atom [:graph-switch "repo"])
|
|
||||||
events (atom [])]
|
|
||||||
(with-redefs [config/publishing? false]
|
|
||||||
(#'rtc-background-tasks/add-watch-when-not-publishing!
|
|
||||||
source
|
|
||||||
::consume-existing-event
|
|
||||||
#(swap! events conj %))
|
|
||||||
(is (= [[:graph-switch "repo"]] @events))
|
|
||||||
(remove-watch source ::consume-existing-event))))
|
|
||||||
@@ -1,764 +0,0 @@
|
|||||||
(ns frontend.handler.db-based.sync-test
|
|
||||||
(:require [cljs.test :refer [deftest is async]]
|
|
||||||
[clojure.string :as string]
|
|
||||||
[frontend.config :as config]
|
|
||||||
[frontend.handler.db-based.sync :as db-sync]
|
|
||||||
[frontend.persist-db :as persist-db]
|
|
||||||
[frontend.handler.repo :as repo-handler]
|
|
||||||
[frontend.handler.user :as user-handler]
|
|
||||||
[frontend.state :as state]
|
|
||||||
[frontend.util :as util]
|
|
||||||
[promesa.core :as p]))
|
|
||||||
|
|
||||||
(defn- finish-async-test!
|
|
||||||
[done]
|
|
||||||
(js/setTimeout done 0))
|
|
||||||
|
|
||||||
(deftest coerce-http-request-does-not-add-client-revision-to-member-request-test
|
|
||||||
(is (= {:email "user@example.com"}
|
|
||||||
(#'db-sync/coerce-http-request :graph-members/create
|
|
||||||
{:email "user@example.com"}))))
|
|
||||||
|
|
||||||
(deftest remove-member-request-test
|
|
||||||
(async done
|
|
||||||
(let [called (atom nil)]
|
|
||||||
(-> (p/with-redefs [db-sync/http-base (fn [] "http://base")
|
|
||||||
db-sync/fetch-json (fn [url opts _]
|
|
||||||
(reset! called {:url url :opts opts})
|
|
||||||
(p/resolved {:ok true}))
|
|
||||||
user-handler/<ensure-id&access-token! (fn [] (p/resolved true))]
|
|
||||||
(p/let [_ (db-sync/<rtc-remove-member! "graph-1" "user-2")
|
|
||||||
{:keys [url opts]} @called]
|
|
||||||
(is (= "http://base/graphs/graph-1/members/user-2" url))
|
|
||||||
(is (= "DELETE" (:method opts)))
|
|
||||||
(finish-async-test! done)))
|
|
||||||
(p/catch (fn [e]
|
|
||||||
(is false (str e))
|
|
||||||
(finish-async-test! done)))))))
|
|
||||||
|
|
||||||
(deftest leave-graph-uses-current-user-test
|
|
||||||
(async done
|
|
||||||
(let [called (atom nil)]
|
|
||||||
(-> (p/with-redefs [db-sync/http-base (fn [] "http://base")
|
|
||||||
db-sync/fetch-json (fn [url opts _]
|
|
||||||
(reset! called {:url url :opts opts})
|
|
||||||
(p/resolved {:ok true}))
|
|
||||||
user-handler/<ensure-id&access-token! (fn [] (p/resolved true))
|
|
||||||
user-handler/user-uuid (fn [] "user-1")]
|
|
||||||
(p/let [_ (db-sync/<rtc-leave-graph! "graph-1")
|
|
||||||
{:keys [url opts]} @called]
|
|
||||||
(is (= "http://base/graphs/graph-1/members/user-1" url))
|
|
||||||
(is (= "DELETE" (:method opts)))
|
|
||||||
(finish-async-test! done)))
|
|
||||||
(p/catch (fn [e]
|
|
||||||
(is false (str e))
|
|
||||||
(finish-async-test! done)))))))
|
|
||||||
|
|
||||||
(deftest leave-graph-missing-user-test
|
|
||||||
(async done
|
|
||||||
(-> (p/with-redefs [user-handler/user-uuid (fn [] nil)]
|
|
||||||
(db-sync/<rtc-leave-graph! "graph-1"))
|
|
||||||
(p/then (fn [_]
|
|
||||||
(is false "expected rejection")
|
|
||||||
(finish-async-test! done)))
|
|
||||||
(p/catch (fn [e]
|
|
||||||
(is (= :db-sync/invalid-member (:type (ex-data e))))
|
|
||||||
(finish-async-test! done))))))
|
|
||||||
|
|
||||||
(deftest rtc-get-users-info-uses-cached-members-test
|
|
||||||
(async done
|
|
||||||
(let [state-prev (state/get-state)
|
|
||||||
fetch-calls (atom 0)]
|
|
||||||
(state/replace-state! (assoc state-prev :rtc/users-info {}))
|
|
||||||
(-> (p/with-redefs [db-sync/http-base (fn [] "http://base")
|
|
||||||
db-sync/<get-rtc-graph-uuid
|
|
||||||
(fn [repo]
|
|
||||||
(is (= "repo-1" repo))
|
|
||||||
(p/resolved "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"))
|
|
||||||
state/get-current-repo (fn [] "repo-1")
|
|
||||||
db-sync/fetch-json (fn [url opts _]
|
|
||||||
(swap! fetch-calls inc)
|
|
||||||
(is (= "http://base/graphs/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa/members" url))
|
|
||||||
(is (= "GET" (:method opts)))
|
|
||||||
(p/resolved {:members [{:user-id "user-1"
|
|
||||||
:role "member"
|
|
||||||
:email "user@example.com"
|
|
||||||
:username "User"}]}))
|
|
||||||
user-handler/<ensure-id&access-token! (fn []
|
|
||||||
(p/resolved true))]
|
|
||||||
(p/let [first-result (db-sync/<rtc-get-users-info)
|
|
||||||
second-result (db-sync/<rtc-get-users-info)
|
|
||||||
refreshed-result (db-sync/<rtc-get-users-info true)]
|
|
||||||
(is (= 2 @fetch-calls))
|
|
||||||
(is (= first-result second-result))
|
|
||||||
(is (= first-result refreshed-result))
|
|
||||||
(is (= {"repo-1" first-result} (state/get-state :rtc/users-info)))))
|
|
||||||
(p/catch (fn [e]
|
|
||||||
(is false (str e))))
|
|
||||||
(p/finally (fn []
|
|
||||||
(state/replace-state! state-prev)
|
|
||||||
(finish-async-test! done)))))))
|
|
||||||
|
|
||||||
(deftest rtc-create-graph-persists-disabled-e2ee-flag-test
|
|
||||||
(async done
|
|
||||||
(let [worker-calls (atom [])]
|
|
||||||
(-> (p/with-redefs [db-sync/http-base (fn [] "http://base")
|
|
||||||
state/<invoke-db-worker (fn [& args]
|
|
||||||
(swap! worker-calls conj args)
|
|
||||||
(p/resolved {:graph-id "graph-1"
|
|
||||||
:graph-e2ee? false}))]
|
|
||||||
(db-sync/<rtc-create-graph! "logseq_db_demo" false))
|
|
||||||
(p/then (fn [result]
|
|
||||||
(is (= {:graph-id "graph-1"
|
|
||||||
:graph-e2ee? false}
|
|
||||||
result))
|
|
||||||
(is (= [[:thread-api/db-sync-create-remote-graph
|
|
||||||
"logseq_db_demo"
|
|
||||||
false
|
|
||||||
true]]
|
|
||||||
@worker-calls))
|
|
||||||
(finish-async-test! done)))
|
|
||||||
(p/catch (fn [e]
|
|
||||||
(is false (str e))
|
|
||||||
(finish-async-test! done)))))))
|
|
||||||
|
|
||||||
(deftest rtc-create-graph-defaults-e2ee-enabled-test
|
|
||||||
(async done
|
|
||||||
(let [worker-calls (atom [])]
|
|
||||||
(-> (p/with-redefs [db-sync/http-base (fn [] "http://base")
|
|
||||||
user-handler/<ensure-id&access-token! (fn [] (p/resolved true))
|
|
||||||
state/<invoke-db-worker (fn [& args]
|
|
||||||
(swap! worker-calls conj args)
|
|
||||||
(p/resolved {:graph-id "graph-2"
|
|
||||||
:graph-e2ee? true}))]
|
|
||||||
(db-sync/<rtc-create-graph! "logseq_db_demo"))
|
|
||||||
(p/then (fn [result]
|
|
||||||
(is (= {:graph-id "graph-2"
|
|
||||||
:graph-e2ee? true}
|
|
||||||
result))
|
|
||||||
(is (= [[:thread-api/db-sync-create-remote-graph
|
|
||||||
"logseq_db_demo"
|
|
||||||
true
|
|
||||||
true]]
|
|
||||||
@worker-calls))
|
|
||||||
(finish-async-test! done)))
|
|
||||||
(p/catch (fn [e]
|
|
||||||
(is false (str e))
|
|
||||||
(finish-async-test! done)))))))
|
|
||||||
|
|
||||||
(deftest rtc-worker-error-values-reject-test
|
|
||||||
(async done
|
|
||||||
(let [state-prev (state/get-state)
|
|
||||||
worker-prev @state/*db-worker
|
|
||||||
error (ex-info "invalid-e2ee-password"
|
|
||||||
{:code :db-sync/invalid-e2ee-password})
|
|
||||||
continued-calls (atom [])
|
|
||||||
capture (fn [promise]
|
|
||||||
(-> promise
|
|
||||||
(p/then (fn [result] [:resolved result]))
|
|
||||||
(p/catch (fn [reason] [:rejected reason]))))]
|
|
||||||
(state/set-state! :rtc/uploading? false)
|
|
||||||
(reset! state/*db-worker
|
|
||||||
(fn [op & _args]
|
|
||||||
(if (contains? #{:thread-api/db-sync-create-remote-graph
|
|
||||||
:thread-api/db-sync-upload-graph}
|
|
||||||
op)
|
|
||||||
(p/resolved error)
|
|
||||||
(p/resolved :ok))))
|
|
||||||
(-> (p/with-redefs [user-handler/<ensure-id&access-token! (fn [] (p/resolved true))
|
|
||||||
db-sync/<get-remote-graphs (fn []
|
|
||||||
(swap! continued-calls conj :refresh)
|
|
||||||
(p/resolved []))
|
|
||||||
db-sync/<rtc-start! (fn [& _]
|
|
||||||
(swap! continued-calls conj :start)
|
|
||||||
(p/resolved :ok))]
|
|
||||||
(p/all [(capture (db-sync/<rtc-create-graph! "logseq_db_demo" true))
|
|
||||||
(capture (db-sync/<rtc-upload-graph! "logseq_db_demo" true))]))
|
|
||||||
(p/then (fn [results]
|
|
||||||
(is (= [:rejected :rejected] (mapv first results)))
|
|
||||||
(is (every? #(= :db-sync/invalid-e2ee-password
|
|
||||||
(:code (ex-data (second %))))
|
|
||||||
results))
|
|
||||||
(is (empty? @continued-calls))
|
|
||||||
(finish-async-test! done)))
|
|
||||||
(p/catch (fn [failure]
|
|
||||||
(is false (str failure))
|
|
||||||
(finish-async-test! done)))
|
|
||||||
(p/finally (fn []
|
|
||||||
(reset! state/*db-worker worker-prev)
|
|
||||||
(state/replace-state! state-prev)))))))
|
|
||||||
|
|
||||||
(deftest rtc-upload-graph-creates-remote-graph-as-not-ready-test
|
|
||||||
(async done
|
|
||||||
(let [upload-calls (atom [])
|
|
||||||
refresh-calls (atom 0)
|
|
||||||
start-calls (atom [])
|
|
||||||
state-prev (state/get-state)]
|
|
||||||
(state/set-auth-id-token "id-token-1")
|
|
||||||
(state/set-auth-access-token "access-token-1")
|
|
||||||
(state/set-auth-refresh-token "refresh-token-1")
|
|
||||||
(state/set-state! :auth/oauth-token-url "http://oauth/token")
|
|
||||||
(state/set-state! :auth/oauth-client-id "client-1")
|
|
||||||
(-> (p/with-redefs [db-sync/http-base (fn [] "http://base")
|
|
||||||
user-handler/<ensure-id&access-token! (fn [] (p/resolved true))
|
|
||||||
state/<invoke-db-worker (fn [& args]
|
|
||||||
(swap! upload-calls conj args)
|
|
||||||
(p/resolved :ok))
|
|
||||||
db-sync/<get-remote-graphs (fn []
|
|
||||||
(swap! refresh-calls inc)
|
|
||||||
(p/resolved []))
|
|
||||||
db-sync/<rtc-start! (fn [repo & _]
|
|
||||||
(swap! start-calls conj repo)
|
|
||||||
(p/resolved :ok))]
|
|
||||||
(db-sync/<rtc-upload-graph! "logseq_db_demo" false))
|
|
||||||
(p/then (fn [_]
|
|
||||||
(is (= [:thread-api/sync-app-state
|
|
||||||
:thread-api/db-sync-upload-graph]
|
|
||||||
(mapv first @upload-calls)))
|
|
||||||
(is (= "refresh-token-1"
|
|
||||||
(:auth/refresh-token (second (first @upload-calls)))))
|
|
||||||
(is (= [:thread-api/db-sync-upload-graph "logseq_db_demo"]
|
|
||||||
(second @upload-calls)))
|
|
||||||
(is (= 1 @refresh-calls))
|
|
||||||
(is (= ["logseq_db_demo"] @start-calls))
|
|
||||||
(finish-async-test! done)))
|
|
||||||
(p/catch (fn [e]
|
|
||||||
(is false (str e))
|
|
||||||
(finish-async-test! done)))
|
|
||||||
(p/finally (fn []
|
|
||||||
(state/replace-state! state-prev)))))))
|
|
||||||
|
|
||||||
(deftest rtc-upload-graph-syncs-cleared-refresh-token-test
|
|
||||||
(async done
|
|
||||||
(let [worker-calls (atom [])
|
|
||||||
state-prev (state/get-state)]
|
|
||||||
(state/set-auth-refresh-token nil)
|
|
||||||
(-> (p/with-redefs [user-handler/<ensure-id&access-token! (fn [] (p/resolved true))
|
|
||||||
state/<invoke-db-worker (fn [& args]
|
|
||||||
(swap! worker-calls conj args)
|
|
||||||
(p/resolved :ok))
|
|
||||||
db-sync/<get-remote-graphs (fn [] (p/resolved []))
|
|
||||||
db-sync/<rtc-start! (fn [& _] (p/resolved :ok))]
|
|
||||||
(db-sync/<rtc-upload-graph! "logseq_db_demo" false))
|
|
||||||
(p/then (fn [_]
|
|
||||||
(let [payload (second (first @worker-calls))]
|
|
||||||
(is (contains? payload :auth/refresh-token))
|
|
||||||
(is (nil? (:auth/refresh-token payload))))
|
|
||||||
(finish-async-test! done)))
|
|
||||||
(p/catch (fn [e]
|
|
||||||
(is false (str e))
|
|
||||||
(finish-async-test! done)))
|
|
||||||
(p/finally (fn []
|
|
||||||
(state/replace-state! state-prev)))))))
|
|
||||||
|
|
||||||
(deftest rtc-download-graph-rejects-while-another-download-is-active-test
|
|
||||||
(async done
|
|
||||||
(let [state-prev (state/get-state)
|
|
||||||
worker-calls (atom [])]
|
|
||||||
(state/swap-state! assoc
|
|
||||||
:rtc/downloading-graph-uuid "graph-1"
|
|
||||||
:rtc/uploading? false)
|
|
||||||
(-> (p/with-redefs [db-sync/http-base (fn [] "http://base")
|
|
||||||
user-handler/<ensure-id&access-token! (fn [] (p/resolved true))
|
|
||||||
util/electron? (fn [] false)
|
|
||||||
state/<invoke-db-worker (fn [& args]
|
|
||||||
(swap! worker-calls conj args)
|
|
||||||
(p/resolved :ok))
|
|
||||||
state/pub-event! (fn [& _] nil)]
|
|
||||||
(db-sync/<rtc-download-graph! "demo-graph" "graph-2" false))
|
|
||||||
(p/then (fn [_]
|
|
||||||
(is false "expected rejection")
|
|
||||||
(state/replace-state! state-prev)
|
|
||||||
(finish-async-test! done)))
|
|
||||||
(p/catch (fn [error]
|
|
||||||
(is (= :db-sync/graph-operation-in-progress
|
|
||||||
(:type (ex-data error))))
|
|
||||||
(is (empty? @worker-calls))
|
|
||||||
(state/replace-state! state-prev)
|
|
||||||
(finish-async-test! done)))))))
|
|
||||||
|
|
||||||
(deftest rtc-download-graph-rejects-while-upload-is-active-test
|
|
||||||
(async done
|
|
||||||
(let [state-prev (state/get-state)
|
|
||||||
worker-calls (atom [])]
|
|
||||||
(state/swap-state! assoc
|
|
||||||
:rtc/downloading-graph-uuid nil
|
|
||||||
:rtc/uploading? true)
|
|
||||||
(-> (p/with-redefs [db-sync/http-base (fn [] "http://base")
|
|
||||||
user-handler/<ensure-id&access-token! (fn [] (p/resolved true))
|
|
||||||
state/<invoke-db-worker (fn [& args]
|
|
||||||
(swap! worker-calls conj args)
|
|
||||||
(p/resolved :ok))
|
|
||||||
state/pub-event! (fn [& _] nil)]
|
|
||||||
(db-sync/<rtc-download-graph! "demo-graph" "graph-1" false))
|
|
||||||
(p/then (fn [_]
|
|
||||||
(is false "expected rejection")
|
|
||||||
(state/replace-state! state-prev)
|
|
||||||
(finish-async-test! done)))
|
|
||||||
(p/catch (fn [error]
|
|
||||||
(is (= :db-sync/graph-operation-in-progress
|
|
||||||
(:type (ex-data error))))
|
|
||||||
(is (empty? @worker-calls))
|
|
||||||
(state/replace-state! state-prev)
|
|
||||||
(finish-async-test! done)))))))
|
|
||||||
|
|
||||||
(deftest rtc-upload-graph-rejects-while-download-is-active-test
|
|
||||||
(async done
|
|
||||||
(let [state-prev (state/get-state)
|
|
||||||
upload-calls (atom [])
|
|
||||||
refresh-calls (atom 0)
|
|
||||||
start-calls (atom [])]
|
|
||||||
(state/swap-state! assoc
|
|
||||||
:rtc/downloading-graph-uuid "graph-1"
|
|
||||||
:rtc/uploading? false)
|
|
||||||
(-> (p/with-redefs [state/<invoke-db-worker (fn [& args]
|
|
||||||
(swap! upload-calls conj args)
|
|
||||||
(p/resolved :ok))
|
|
||||||
db-sync/<get-remote-graphs (fn []
|
|
||||||
(swap! refresh-calls inc)
|
|
||||||
(p/resolved []))
|
|
||||||
db-sync/<rtc-start! (fn [repo & _]
|
|
||||||
(swap! start-calls conj repo)
|
|
||||||
(p/resolved :ok))]
|
|
||||||
(db-sync/<rtc-upload-graph! "logseq_db_demo" false))
|
|
||||||
(p/then (fn [_]
|
|
||||||
(is false "expected rejection")
|
|
||||||
(state/replace-state! state-prev)
|
|
||||||
(finish-async-test! done)))
|
|
||||||
(p/catch (fn [error]
|
|
||||||
(is (= :db-sync/graph-operation-in-progress
|
|
||||||
(:type (ex-data error))))
|
|
||||||
(is (empty? @upload-calls))
|
|
||||||
(is (zero? @refresh-calls))
|
|
||||||
(is (empty? @start-calls))
|
|
||||||
(state/replace-state! state-prev)
|
|
||||||
(finish-async-test! done)))))))
|
|
||||||
|
|
||||||
(deftest rtc-create-graph-and-start-sync-does-not-upload-snapshot-test
|
|
||||||
(async done
|
|
||||||
(let [create-calls (atom [])
|
|
||||||
refresh-calls (atom 0)
|
|
||||||
start-calls (atom [])
|
|
||||||
upload-calls (atom [])]
|
|
||||||
(-> (p/with-redefs [db-sync/<rtc-create-graph! (fn [repo graph-e2ee? & [graph-ready-for-use?]]
|
|
||||||
(swap! create-calls conj [repo graph-e2ee? graph-ready-for-use?])
|
|
||||||
(p/resolved "graph-4"))
|
|
||||||
db-sync/<get-remote-graphs (fn []
|
|
||||||
(swap! refresh-calls inc)
|
|
||||||
(p/resolved []))
|
|
||||||
db-sync/<rtc-start! (fn [repo & _]
|
|
||||||
(swap! start-calls conj repo)
|
|
||||||
(p/resolved :ok))
|
|
||||||
state/<invoke-db-worker (fn [& args]
|
|
||||||
(swap! upload-calls conj args)
|
|
||||||
(p/resolved :ok))]
|
|
||||||
(db-sync/<rtc-create-graph-and-start-sync! "logseq_db_demo" true))
|
|
||||||
(p/then (fn [_]
|
|
||||||
(is (= [["logseq_db_demo" true true]] @create-calls))
|
|
||||||
(is (= 1 @refresh-calls))
|
|
||||||
(is (= ["logseq_db_demo"] @start-calls))
|
|
||||||
(is (empty? @upload-calls))
|
|
||||||
(finish-async-test! done)))
|
|
||||||
(p/catch (fn [e]
|
|
||||||
(is false (str e))
|
|
||||||
(finish-async-test! done)))))))
|
|
||||||
|
|
||||||
(deftest rtc-start-skips-while-graph-upload-is-active-test
|
|
||||||
(async done
|
|
||||||
(let [worker-prev @state/*db-worker
|
|
||||||
state-prev (state/get-state)
|
|
||||||
calls (atom [])]
|
|
||||||
(reset! state/*db-worker :worker)
|
|
||||||
(state/swap-state! assoc
|
|
||||||
:rtc/uploading? true
|
|
||||||
:rtc/loading-graphs? false)
|
|
||||||
(-> (p/with-redefs [state/get-rtc-graphs (fn [] [{:url "demo-graph"}])
|
|
||||||
state/<invoke-db-worker (fn [& args]
|
|
||||||
(swap! calls conj args)
|
|
||||||
(p/resolved :ok))]
|
|
||||||
(db-sync/<rtc-start! "demo-graph"))
|
|
||||||
(p/then (fn [_]
|
|
||||||
(is (not-any? #(= :thread-api/db-sync-start (first %)) @calls))
|
|
||||||
(is (some #(= :thread-api/db-sync-stop (first %)) @calls))
|
|
||||||
(finish-async-test! done)))
|
|
||||||
(p/catch (fn [error]
|
|
||||||
(is false (str error))
|
|
||||||
(finish-async-test! done)))
|
|
||||||
(p/finally (fn []
|
|
||||||
(reset! state/*db-worker worker-prev)
|
|
||||||
(state/replace-state! state-prev)))))))
|
|
||||||
|
|
||||||
(deftest rtc-start-skips-when-remote-graph-is-not-ready-for-use-test
|
|
||||||
(async done
|
|
||||||
(let [worker-prev @state/*db-worker
|
|
||||||
state-prev (state/get-state)
|
|
||||||
calls (atom [])]
|
|
||||||
(reset! state/*db-worker :worker)
|
|
||||||
(state/swap-state! assoc
|
|
||||||
:rtc/uploading? false
|
|
||||||
:rtc/loading-graphs? false)
|
|
||||||
(-> (p/with-redefs [state/get-rtc-graphs (fn [] [{:url "demo-graph"
|
|
||||||
:graph-ready-for-use? false}])
|
|
||||||
state/<invoke-db-worker (fn [& args]
|
|
||||||
(swap! calls conj args)
|
|
||||||
(p/resolved :ok))]
|
|
||||||
(db-sync/<rtc-start! "demo-graph"))
|
|
||||||
(p/then (fn [_]
|
|
||||||
(is (not-any? #(= :thread-api/db-sync-start (first %)) @calls))
|
|
||||||
(is (some #(= :thread-api/db-sync-stop (first %)) @calls))
|
|
||||||
(finish-async-test! done)))
|
|
||||||
(p/catch (fn [error]
|
|
||||||
(is false (str error))
|
|
||||||
(finish-async-test! done)))
|
|
||||||
(p/finally (fn []
|
|
||||||
(reset! state/*db-worker worker-prev)
|
|
||||||
(state/replace-state! state-prev)))))))
|
|
||||||
|
|
||||||
(deftest rtc-start-syncs-auth-state-before-db-sync-start-test
|
|
||||||
(async done
|
|
||||||
(let [worker-prev @state/*db-worker
|
|
||||||
state-prev (state/get-state)
|
|
||||||
calls (atom [])]
|
|
||||||
(reset! state/*db-worker :worker)
|
|
||||||
(state/replace-state! (assoc state-prev
|
|
||||||
:git/current-repo "demo-graph"
|
|
||||||
:auth/id-token "id-token"
|
|
||||||
:auth/access-token "access-token"
|
|
||||||
:auth/refresh-token "refresh-token"
|
|
||||||
:user/info {:sub "user-1"}
|
|
||||||
:config {:a 1}
|
|
||||||
:rtc/uploading? false
|
|
||||||
:rtc/loading-graphs? false))
|
|
||||||
(-> (p/with-redefs [user-handler/<ensure-id&access-token! (fn [] (p/resolved true))
|
|
||||||
state/get-rtc-graphs (fn [] [{:url "demo-graph"
|
|
||||||
:graph-ready-for-use? true}])
|
|
||||||
state/<invoke-db-worker (fn [& args]
|
|
||||||
(swap! calls conj args)
|
|
||||||
(p/resolved :ok))]
|
|
||||||
(db-sync/<rtc-start! "demo-graph"))
|
|
||||||
(p/then (fn [_]
|
|
||||||
(is (= :thread-api/sync-app-state (ffirst @calls)))
|
|
||||||
(is (= (cond-> {:git/current-repo "demo-graph"
|
|
||||||
:config {:a 1}
|
|
||||||
:auth/id-token "id-token"
|
|
||||||
:auth/access-token "access-token"
|
|
||||||
:auth/refresh-token "refresh-token"
|
|
||||||
:auth/oauth-token-url nil
|
|
||||||
:user/info {:sub "user-1"}}
|
|
||||||
(seq config/OAUTH-DOMAIN)
|
|
||||||
(assoc :auth/oauth-domain config/OAUTH-DOMAIN)
|
|
||||||
|
|
||||||
(seq config/COGNITO-CLIENT-ID)
|
|
||||||
(assoc :auth/oauth-client-id config/COGNITO-CLIENT-ID))
|
|
||||||
(second (first @calls))))
|
|
||||||
(is (= [:thread-api/db-sync-start "demo-graph"]
|
|
||||||
(second @calls)))
|
|
||||||
(finish-async-test! done)))
|
|
||||||
(p/catch (fn [error]
|
|
||||||
(is false (str error))
|
|
||||||
(finish-async-test! done)))
|
|
||||||
(p/finally (fn []
|
|
||||||
(reset! state/*db-worker worker-prev)
|
|
||||||
(state/replace-state! state-prev)))))))
|
|
||||||
|
|
||||||
(deftest rtc-download-graph-emits-feedback-before-snapshot-fetch-test
|
|
||||||
(let [trace (atom [])
|
|
||||||
log-events (atom [])]
|
|
||||||
(with-redefs [db-sync/http-base (fn [] "http://base")
|
|
||||||
state/set-state! (fn [k v]
|
|
||||||
(swap! trace conj [:set k v]))
|
|
||||||
state/pub-event! (fn [[event payload :as e]]
|
|
||||||
(when (and (= :rtc/log event)
|
|
||||||
(= "graph-1" (:graph-uuid payload)))
|
|
||||||
(swap! trace conj :log)
|
|
||||||
(swap! log-events conj e)))
|
|
||||||
;; Keep auth pending so we only validate immediate click-time feedback.
|
|
||||||
user-handler/<ensure-id&access-token! (fn [] (p/deferred))
|
|
||||||
db-sync/fetch-json (fn [url _opts _schema]
|
|
||||||
(swap! trace conj [:fetch url])
|
|
||||||
(p/resolved {:t 1}))]
|
|
||||||
(db-sync/<rtc-download-graph! "demo-graph" "graph-1")
|
|
||||||
(is (= [[:set :rtc/downloading-graph-uuid "graph-1"] :log]
|
|
||||||
(take 2 @trace)))
|
|
||||||
(let [[event {:keys [type sub-type graph-uuid message]}] (first @log-events)]
|
|
||||||
(is (= :rtc/log event))
|
|
||||||
(is (= :rtc.log/download type))
|
|
||||||
(is (= :download-progress sub-type))
|
|
||||||
(is (= "graph-1" graph-uuid))
|
|
||||||
(is (and (string? message)
|
|
||||||
(string/includes? message "Preparing")))))))
|
|
||||||
|
|
||||||
(deftest get-remote-graphs-includes-ready-for-use-flag-test
|
|
||||||
(async done
|
|
||||||
(let [graphs-state (atom nil)
|
|
||||||
worker-prev @state/*db-worker
|
|
||||||
ensure-calls (atom [])]
|
|
||||||
(reset! state/*db-worker :worker)
|
|
||||||
(-> (p/with-redefs [db-sync/http-base (fn [] "http://base")
|
|
||||||
user-handler/<ensure-id&access-token! (fn [] (p/resolved true))
|
|
||||||
db-sync/fetch-json (fn [_url _opts _schema]
|
|
||||||
(p/resolved {:graphs [{:graph-id "graph-1"
|
|
||||||
:graph-name "demo"
|
|
||||||
:schema-version "65"
|
|
||||||
:graph-e2ee? true
|
|
||||||
:graph-ready-for-use? false
|
|
||||||
:created-at 1
|
|
||||||
:updated-at 2}]
|
|
||||||
:user-rsa-keys-exists? true}))
|
|
||||||
state/<invoke-db-worker (fn [& args]
|
|
||||||
(when (= :thread-api/db-sync-ensure-user-rsa-keys
|
|
||||||
(first args))
|
|
||||||
(swap! ensure-calls conj args))
|
|
||||||
(p/resolved :ok))
|
|
||||||
state/set-state! (fn [k v]
|
|
||||||
(when (= k :rtc/graphs)
|
|
||||||
(reset! graphs-state v))
|
|
||||||
nil)
|
|
||||||
repo-handler/refresh-repos! (fn [] nil)]
|
|
||||||
(db-sync/<get-remote-graphs))
|
|
||||||
(p/then (fn [graphs]
|
|
||||||
(is (= false (:graph-ready-for-use? (first graphs))))
|
|
||||||
(is (= false (:graph-ready-for-use? (first @graphs-state))))
|
|
||||||
(is (empty? @ensure-calls))
|
|
||||||
(finish-async-test! done)))
|
|
||||||
(p/catch (fn [error]
|
|
||||||
(is false (str error))
|
|
||||||
(finish-async-test! done)))
|
|
||||||
(p/finally (fn []
|
|
||||||
(reset! state/*db-worker worker-prev)))))))
|
|
||||||
|
|
||||||
(deftest get-remote-graphs-ensures-user-rsa-keys-when-server-missing-test
|
|
||||||
(async done
|
|
||||||
(let [worker-prev @state/*db-worker
|
|
||||||
ensure-calls (atom [])]
|
|
||||||
(reset! state/*db-worker :worker)
|
|
||||||
(-> (p/with-redefs [db-sync/http-base (fn [] "http://base")
|
|
||||||
user-handler/<ensure-id&access-token! (fn [] (p/resolved true))
|
|
||||||
db-sync/fetch-json (fn [_url _opts _schema]
|
|
||||||
(p/resolved {:graphs []
|
|
||||||
:user-rsa-keys-exists? false}))
|
|
||||||
state/<invoke-db-worker (fn [& args]
|
|
||||||
(when (= :thread-api/db-sync-ensure-user-rsa-keys
|
|
||||||
(first args))
|
|
||||||
(swap! ensure-calls conj args))
|
|
||||||
(p/resolved {:public-key "pk"}))
|
|
||||||
state/set-state! (fn [& _] nil)
|
|
||||||
repo-handler/refresh-repos! (fn [] nil)]
|
|
||||||
(db-sync/<get-remote-graphs))
|
|
||||||
(p/then (fn [_]
|
|
||||||
(is (= [[:thread-api/db-sync-ensure-user-rsa-keys
|
|
||||||
{:ensure-server? true
|
|
||||||
:server-rsa-keys-exists? false}]]
|
|
||||||
@ensure-calls))
|
|
||||||
(finish-async-test! done)))
|
|
||||||
(p/catch (fn [error]
|
|
||||||
(is false (str error))
|
|
||||||
(finish-async-test! done)))
|
|
||||||
(p/finally (fn []
|
|
||||||
(reset! state/*db-worker worker-prev)))))))
|
|
||||||
|
|
||||||
(deftest get-remote-graphs-syncs-auth-before-ensuring-user-rsa-keys-test
|
|
||||||
(async done
|
|
||||||
(let [worker-prev @state/*db-worker
|
|
||||||
state-prev (state/get-state)
|
|
||||||
worker-calls (atom [])]
|
|
||||||
(reset! state/*db-worker :worker)
|
|
||||||
(state/swap-state! assoc
|
|
||||||
:auth/id-token "id-token-1"
|
|
||||||
:auth/access-token "access-token-1"
|
|
||||||
:auth/refresh-token "refresh-token-1"
|
|
||||||
:auth/oauth-token-url "http://oauth/token"
|
|
||||||
:auth/oauth-client-id "client-1")
|
|
||||||
(-> (p/with-redefs [db-sync/http-base (fn [] "http://base")
|
|
||||||
user-handler/<ensure-id&access-token! (fn []
|
|
||||||
(p/resolved true))
|
|
||||||
db-sync/fetch-json (fn [_url _opts _schema]
|
|
||||||
(p/resolved {:graphs []
|
|
||||||
:user-rsa-keys-exists? false}))
|
|
||||||
state/<invoke-db-worker (fn [& args]
|
|
||||||
(swap! worker-calls conj args)
|
|
||||||
(p/resolved {:public-key "pk"}))
|
|
||||||
state/set-state! (fn [& _] nil)
|
|
||||||
repo-handler/refresh-repos! (fn [] nil)]
|
|
||||||
(db-sync/<get-remote-graphs))
|
|
||||||
(p/then (fn [_]
|
|
||||||
(is (= [:thread-api/sync-app-state
|
|
||||||
:thread-api/db-sync-ensure-user-rsa-keys]
|
|
||||||
(mapv first @worker-calls)))
|
|
||||||
(is (= "refresh-token-1"
|
|
||||||
(:auth/refresh-token (second (first @worker-calls)))))
|
|
||||||
(finish-async-test! done)))
|
|
||||||
(p/catch (fn [error]
|
|
||||||
(is false (str error))
|
|
||||||
(finish-async-test! done)))
|
|
||||||
(p/finally (fn []
|
|
||||||
(reset! state/*db-worker worker-prev)
|
|
||||||
(state/replace-state! state-prev)))))))
|
|
||||||
|
|
||||||
(deftest rtc-download-graph-delegates-to-worker-download-api-test
|
|
||||||
(async done
|
|
||||||
(let [worker-calls (atom [])]
|
|
||||||
(-> (p/with-redefs [db-sync/http-base (fn [] "http://base")
|
|
||||||
user-handler/<ensure-id&access-token! (fn [] (p/resolved true))
|
|
||||||
state/<invoke-db-worker (fn [& args]
|
|
||||||
(swap! worker-calls conj args)
|
|
||||||
(p/resolved :ok))
|
|
||||||
state/set-state! (fn [& _] nil)
|
|
||||||
state/pub-event! (fn [& _] nil)]
|
|
||||||
(db-sync/<rtc-download-graph! "demo-graph" "graph-1" false))
|
|
||||||
(p/then (fn [_]
|
|
||||||
(is (= 1 (count @worker-calls)))
|
|
||||||
(let [[op graph graph-uuid graph-e2ee?] (first @worker-calls)]
|
|
||||||
(is (= :thread-api/db-sync-download-graph-by-id op))
|
|
||||||
(is (string/ends-with? graph "demo-graph"))
|
|
||||||
(is (= "graph-1" graph-uuid))
|
|
||||||
(is (= false graph-e2ee?)))
|
|
||||||
(finish-async-test! done)))
|
|
||||||
(p/catch (fn [error]
|
|
||||||
(is false (str error))
|
|
||||||
(finish-async-test! done)))))))
|
|
||||||
|
|
||||||
(deftest rtc-download-graph-sets-and-clears-downloading-state-test
|
|
||||||
(async done
|
|
||||||
(let [state-calls (atom [])
|
|
||||||
worker-prev @state/*db-worker]
|
|
||||||
(reset! state/*db-worker nil)
|
|
||||||
(-> (p/with-redefs [db-sync/http-base (fn [] "http://base")
|
|
||||||
user-handler/<ensure-id&access-token! (fn [] (p/resolved true))
|
|
||||||
state/<invoke-db-worker (fn [& _] (p/resolved :ok))
|
|
||||||
state/pub-event! (fn [& _] nil)
|
|
||||||
state/set-state! (fn [k v]
|
|
||||||
(swap! state-calls conj [k v])
|
|
||||||
nil)]
|
|
||||||
(db-sync/<rtc-download-graph! "demo-graph" "graph-1" false))
|
|
||||||
(p/then (fn [_]
|
|
||||||
(is (= [[:rtc/downloading-graph-uuid "graph-1"]
|
|
||||||
[:rtc/downloading-graph-uuid nil]]
|
|
||||||
@state-calls))
|
|
||||||
(finish-async-test! done)))
|
|
||||||
(p/catch (fn [error]
|
|
||||||
(reset! state/*db-worker worker-prev)
|
|
||||||
(is false (str error))
|
|
||||||
(finish-async-test! done)))
|
|
||||||
(p/finally (fn []
|
|
||||||
(reset! state/*db-worker worker-prev)))))))
|
|
||||||
|
|
||||||
(deftest rtc-download-graph-rebinds-desktop-runtime-before-download-test
|
|
||||||
(async done
|
|
||||||
(let [runtime-bound-repo (atom "logseq_db_d")
|
|
||||||
invoke-calls (atom [])]
|
|
||||||
(-> (p/with-redefs [db-sync/http-base (fn [] "http://base")
|
|
||||||
user-handler/<ensure-id&access-token! (fn [] (p/resolved true))
|
|
||||||
util/electron? (fn [] true)
|
|
||||||
persist-db/<open-and-fetch-schema (fn [repo _opts]
|
|
||||||
(reset! runtime-bound-repo repo)
|
|
||||||
(p/resolved {:schema {}}))
|
|
||||||
state/<invoke-db-worker (fn [& args]
|
|
||||||
(swap! invoke-calls conj args)
|
|
||||||
(let [[op repo] args]
|
|
||||||
(case op
|
|
||||||
:thread-api/sync-app-state
|
|
||||||
(p/resolved :ok)
|
|
||||||
|
|
||||||
:thread-api/db-sync-download-graph-by-id
|
|
||||||
(if (= repo @runtime-bound-repo)
|
|
||||||
(p/resolved :ok)
|
|
||||||
(p/rejected
|
|
||||||
(ex-info "repo does not match bound repo"
|
|
||||||
{:status 409
|
|
||||||
:code :repo-mismatch
|
|
||||||
:error {:code "repo-mismatch"
|
|
||||||
:message "repo does not match bound repo"
|
|
||||||
:repo repo
|
|
||||||
:bound-repo @runtime-bound-repo}})))
|
|
||||||
|
|
||||||
(p/resolved :ok))))
|
|
||||||
state/pub-event! (fn [& _] nil)
|
|
||||||
state/set-state! (fn [& _] nil)]
|
|
||||||
(db-sync/<rtc-download-graph! "db1" "graph-1" false))
|
|
||||||
(p/then (fn [_]
|
|
||||||
(is (= "logseq_db_db1" @runtime-bound-repo))
|
|
||||||
(is (= :thread-api/sync-app-state
|
|
||||||
(ffirst @invoke-calls)))
|
|
||||||
(is (= :thread-api/db-sync-download-graph-by-id
|
|
||||||
(first (second @invoke-calls))))
|
|
||||||
(finish-async-test! done)))
|
|
||||||
(p/catch (fn [error]
|
|
||||||
(is false (str error))
|
|
||||||
(finish-async-test! done)))))))
|
|
||||||
|
|
||||||
(deftest rtc-download-graph-downloads-missing-assets-on-electron-test
|
|
||||||
(async done
|
|
||||||
(let [worker-calls (atom [])]
|
|
||||||
(-> (p/with-redefs [db-sync/http-base (fn [] "http://base")
|
|
||||||
user-handler/<ensure-id&access-token! (fn []
|
|
||||||
(p/resolved true))
|
|
||||||
util/electron? (fn [] true)
|
|
||||||
persist-db/<open-and-fetch-schema (fn [_repo _opts]
|
|
||||||
(p/resolved {:schema {}}))
|
|
||||||
state/<invoke-db-worker (fn [& args]
|
|
||||||
(swap! worker-calls conj args)
|
|
||||||
(p/resolved :ok))
|
|
||||||
state/pub-event! (fn [& _] nil)
|
|
||||||
state/set-state! (fn [& _] nil)]
|
|
||||||
(db-sync/<rtc-download-graph! "db1" "graph-1" false))
|
|
||||||
(p/then (fn [_]
|
|
||||||
(is (= [:thread-api/sync-app-state
|
|
||||||
:thread-api/db-sync-download-graph-by-id
|
|
||||||
:thread-api/db-sync-download-missing-assets]
|
|
||||||
(mapv first @worker-calls)))
|
|
||||||
(is (= ["logseq_db_db1" "graph-1"]
|
|
||||||
(rest (nth @worker-calls 2))))
|
|
||||||
(finish-async-test! done)))
|
|
||||||
(p/catch (fn [error]
|
|
||||||
(is false (str error))
|
|
||||||
(finish-async-test! done)))))))
|
|
||||||
|
|
||||||
(deftest rtc-download-graph-skips-runtime-rebind-outside-electron-test
|
|
||||||
(async done
|
|
||||||
(let [runtime-rebind-calls (atom [])
|
|
||||||
worker-calls (atom [])]
|
|
||||||
(-> (p/with-redefs [db-sync/http-base (fn [] "http://base")
|
|
||||||
user-handler/<ensure-id&access-token! (fn [] (p/resolved true))
|
|
||||||
util/electron? (fn [] false)
|
|
||||||
persist-db/<open-and-fetch-schema (fn [& args]
|
|
||||||
(swap! runtime-rebind-calls conj args)
|
|
||||||
(p/resolved {:schema {}}))
|
|
||||||
state/<invoke-db-worker (fn [& args]
|
|
||||||
(swap! worker-calls conj args)
|
|
||||||
(p/resolved :ok))
|
|
||||||
state/pub-event! (fn [& _] nil)
|
|
||||||
state/set-state! (fn [& _] nil)]
|
|
||||||
(db-sync/<rtc-download-graph! "db1" "graph-1" true))
|
|
||||||
(p/then (fn [_]
|
|
||||||
(is (empty? @runtime-rebind-calls))
|
|
||||||
(is (= :thread-api/db-sync-download-graph-by-id
|
|
||||||
(ffirst @worker-calls)))
|
|
||||||
(finish-async-test! done)))
|
|
||||||
(p/catch (fn [error]
|
|
||||||
(is false (str error))
|
|
||||||
(finish-async-test! done)))))))
|
|
||||||
|
|
||||||
(deftest rtc-download-graph-syncs-auth-state-after-runtime-rebind-test
|
|
||||||
(async done
|
|
||||||
(let [worker-calls (atom [])]
|
|
||||||
(-> (p/with-redefs [db-sync/http-base (fn [] "http://base")
|
|
||||||
user-handler/<ensure-id&access-token! (fn [] (p/resolved true))
|
|
||||||
util/electron? (fn [] true)
|
|
||||||
persist-db/<open-and-fetch-schema (fn [_repo _opts]
|
|
||||||
(p/resolved {:schema {}}))
|
|
||||||
state/<invoke-db-worker (fn [& args]
|
|
||||||
(swap! worker-calls conj args)
|
|
||||||
(p/resolved :ok))
|
|
||||||
state/pub-event! (fn [& _] nil)
|
|
||||||
state/set-state! (fn [& _] nil)]
|
|
||||||
(db-sync/<rtc-download-graph! "db1" "graph-1" true))
|
|
||||||
(p/then (fn [_]
|
|
||||||
(is (= :thread-api/sync-app-state
|
|
||||||
(ffirst @worker-calls)))
|
|
||||||
(is (= :thread-api/db-sync-download-graph-by-id
|
|
||||||
(first (second @worker-calls))))
|
|
||||||
(finish-async-test! done)))
|
|
||||||
(p/catch (fn [error]
|
|
||||||
(is false (str error))
|
|
||||||
(finish-async-test! done)))))))
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
(ns frontend.handler.events.rtc-error-test
|
|
||||||
(:require [cljs.test :refer [deftest is]]
|
|
||||||
[frontend.handler.events.rtc-error :as rtc-error]))
|
|
||||||
|
|
||||||
(deftest e2ee-decrypt-failed-detects-invalid-password-code-test
|
|
||||||
(is (true?
|
|
||||||
(rtc-error/e2ee-decrypt-failed?
|
|
||||||
(ex-info "db-sync download failed"
|
|
||||||
{:code :db-sync/invalid-e2ee-password})))))
|
|
||||||
|
|
||||||
(deftest e2ee-decrypt-failed-detects-nested-error-test
|
|
||||||
(is (true?
|
|
||||||
(rtc-error/e2ee-decrypt-failed?
|
|
||||||
(ex-info "db-sync download failed"
|
|
||||||
{:error (ex-info "decrypt-aes-key" {})})))))
|
|
||||||
|
|
||||||
(deftest e2ee-decrypt-failed-ignores-other-errors-test
|
|
||||||
(is (false?
|
|
||||||
(rtc-error/e2ee-decrypt-failed?
|
|
||||||
(ex-info "db-sync download failed"
|
|
||||||
{:error-message "snapshot download failed"})))))
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
(ns frontend.handler.events-test
|
|
||||||
(:require [cljs.test :refer [deftest is testing]]
|
|
||||||
[frontend.components.rtc.download-progress :as download-progress]
|
|
||||||
[frontend.mobile.util :as mobile-util]
|
|
||||||
[logseq.shui.ui :as shui]))
|
|
||||||
|
|
||||||
(deftest download-graph-progress-surface-test
|
|
||||||
(let [calls (atom [])]
|
|
||||||
(testing "web mobile uses a centered dialog"
|
|
||||||
(with-redefs [mobile-util/native-platform? (constantly false)
|
|
||||||
shui/dialog-open! (fn [& args] (swap! calls conj [:dialog args]))
|
|
||||||
shui/dialog-close! (fn [& args] (swap! calls conj [:dialog-close args]))
|
|
||||||
shui/popup-show! (fn [& args] (swap! calls conj [:popup args]))
|
|
||||||
shui/popup-hide! (fn [& args] (swap! calls conj [:popup-hide args]))]
|
|
||||||
(download-progress/show! "My notes")
|
|
||||||
(download-progress/hide!)
|
|
||||||
(is (= [:dialog :dialog-close] (mapv first @calls)))))
|
|
||||||
(testing "native mobile keeps the native popup"
|
|
||||||
(reset! calls [])
|
|
||||||
(with-redefs [mobile-util/native-platform? (constantly true)
|
|
||||||
shui/dialog-open! (fn [& args] (swap! calls conj [:dialog args]))
|
|
||||||
shui/dialog-close! (fn [& args] (swap! calls conj [:dialog-close args]))
|
|
||||||
shui/popup-show! (fn [& args] (swap! calls conj [:popup args]))
|
|
||||||
shui/popup-hide! (fn [& args] (swap! calls conj [:popup-hide args]))]
|
|
||||||
(download-progress/show! "My notes")
|
|
||||||
(download-progress/hide!)
|
|
||||||
(is (= [:popup :popup-hide] (mapv first @calls)))))))
|
|
||||||
@@ -131,41 +131,3 @@
|
|||||||
(restore!)
|
(restore!)
|
||||||
(done))))))
|
(done))))))
|
||||||
|
|
||||||
(deftest logout-clears-e2ee-password-when-db-worker-ready-test
|
|
||||||
(testing "logout should request db-worker to clear persisted e2ee password"
|
|
||||||
(let [ops* (atom [])
|
|
||||||
old-worker @state/*db-worker]
|
|
||||||
(reset! state/*db-worker :worker)
|
|
||||||
(try
|
|
||||||
(with-mocked-local-storage
|
|
||||||
(fn []
|
|
||||||
(with-redefs [state/<invoke-db-worker (fn [op & _]
|
|
||||||
(swap! ops* conj op)
|
|
||||||
(p/resolved nil))
|
|
||||||
state/clear-user-info! (fn [] nil)
|
|
||||||
state/pub-event! (fn [& _] nil)
|
|
||||||
user-handler/clear-tokens (fn [] nil)]
|
|
||||||
(user-handler/logout)
|
|
||||||
(is (= :thread-api/clear-e2ee-password
|
|
||||||
(first @ops*))))))
|
|
||||||
(finally
|
|
||||||
(reset! state/*db-worker old-worker))))))
|
|
||||||
|
|
||||||
(deftest logout-skips-e2ee-password-clear-when-db-worker-missing-test
|
|
||||||
(testing "logout should not call db-worker API when db-worker is unavailable"
|
|
||||||
(let [invoke-calls* (atom 0)
|
|
||||||
old-worker @state/*db-worker]
|
|
||||||
(reset! state/*db-worker nil)
|
|
||||||
(try
|
|
||||||
(with-mocked-local-storage
|
|
||||||
(fn []
|
|
||||||
(with-redefs [state/<invoke-db-worker (fn [& _]
|
|
||||||
(swap! invoke-calls* inc)
|
|
||||||
(p/resolved nil))
|
|
||||||
state/clear-user-info! (fn [] nil)
|
|
||||||
state/pub-event! (fn [& _] nil)
|
|
||||||
user-handler/clear-tokens (fn [] nil)]
|
|
||||||
(user-handler/logout)
|
|
||||||
(is (zero? @invoke-calls*)))))
|
|
||||||
(finally
|
|
||||||
(reset! state/*db-worker old-worker))))))
|
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
(ns frontend.handler.worker-test
|
(ns frontend.handler.worker-test
|
||||||
(:require [cljs.test :refer [async deftest is]]
|
(:require [cljs.test :refer [async deftest is]]
|
||||||
[frontend.handler.worker :as worker-handler]
|
[frontend.handler.worker :as worker-handler]
|
||||||
[frontend.state :as state]
|
[frontend.state :as state]))
|
||||||
[promesa.core :as p]))
|
|
||||||
|
|
||||||
(deftest handle-message-reports-comlink-worker-throw-with-extra-data-test
|
(deftest handle-message-reports-comlink-worker-throw-with-extra-data-test
|
||||||
(let [captured-events (atom [])
|
(let [captured-events (atom [])
|
||||||
@@ -63,52 +62,3 @@
|
|||||||
(finally
|
(finally
|
||||||
(reset! state/*db-worker previous-worker)
|
(reset! state/*db-worker previous-worker)
|
||||||
(reset! state/*db-worker-thread previous-thread)))))
|
(reset! state/*db-worker-thread previous-thread)))))
|
||||||
|
|
||||||
(deftest db-worker-ui-request-resolve-passes-request-id-and-result-test
|
|
||||||
(async done
|
|
||||||
(let [calls (atom [])
|
|
||||||
wrapped-worker (fn [qkw & args]
|
|
||||||
(swap! calls conj [qkw args])
|
|
||||||
(p/resolved {:ok true}))]
|
|
||||||
(with-redefs [worker-handler/<db-worker-ui-action
|
|
||||||
(fn [_action _payload]
|
|
||||||
(p/resolved {:password "pw"}))]
|
|
||||||
(-> (worker-handler/handle :db-worker/ui-request
|
|
||||||
wrapped-worker
|
|
||||||
{:request-id "req-1"
|
|
||||||
:action :request-e2ee-password
|
|
||||||
:payload {:reason :decrypt-user-rsa-private-key}})
|
|
||||||
(p/then (fn [_]
|
|
||||||
(is (= [[:thread-api/resolve-ui-request ["req-1" {:password "pw"}]]]
|
|
||||||
@calls))
|
|
||||||
(done)))
|
|
||||||
(p/catch (fn [error]
|
|
||||||
(is nil (str "unexpected error: " error))
|
|
||||||
(done))))))))
|
|
||||||
|
|
||||||
(deftest db-worker-ui-request-reject-passes-request-id-and-error-test
|
|
||||||
(async done
|
|
||||||
(let [calls (atom [])
|
|
||||||
wrapped-worker (fn [qkw & args]
|
|
||||||
(swap! calls conj [qkw args])
|
|
||||||
(p/resolved {:ok true}))]
|
|
||||||
(with-redefs [worker-handler/<db-worker-ui-action
|
|
||||||
(fn [_action _payload]
|
|
||||||
(p/rejected (ex-info "boom" {:code :boom :x 1})))]
|
|
||||||
(-> (worker-handler/handle :db-worker/ui-request
|
|
||||||
wrapped-worker
|
|
||||||
{:request-id "req-2"
|
|
||||||
:action :request-e2ee-password
|
|
||||||
:payload {:reason :decrypt-user-rsa-private-key}})
|
|
||||||
(p/then (fn [_]
|
|
||||||
(is (= [[:thread-api/reject-ui-request
|
|
||||||
["req-2"
|
|
||||||
{:code :boom
|
|
||||||
:message "boom"
|
|
||||||
:data {:code :boom
|
|
||||||
:x 1}}]]]
|
|
||||||
@calls))
|
|
||||||
(done)))
|
|
||||||
(p/catch (fn [error]
|
|
||||||
(is nil (str "unexpected error: " error))
|
|
||||||
(done))))))))
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
(:require [cljs.test :refer [async deftest is use-fixtures]]
|
(:require [cljs.test :refer [async deftest is use-fixtures]]
|
||||||
[electron.ipc :as ipc]
|
[electron.ipc :as ipc]
|
||||||
[frontend.common.thread-api :as thread-api]
|
[frontend.common.thread-api :as thread-api]
|
||||||
[frontend.config :as config]
|
|
||||||
[frontend.handler.notification :as notification]
|
[frontend.handler.notification :as notification]
|
||||||
[frontend.persist-db.browser :as browser]
|
[frontend.persist-db.browser :as browser]
|
||||||
[frontend.persist-db :as persist-db]
|
[frontend.persist-db :as persist-db]
|
||||||
@@ -680,47 +679,6 @@
|
|||||||
(set! storage/remove original-storage-remove)
|
(set! storage/remove original-storage-remove)
|
||||||
(done)))))))
|
(done)))))))
|
||||||
|
|
||||||
(deftest electron-ensure-remote-pushes-db-sync-config-on-start-test
|
|
||||||
(async done
|
|
||||||
(let [worker-calls (atom [])
|
|
||||||
ensure-remote! #'persist-db/<ensure-remote!
|
|
||||||
original-ipc ipc/ipc
|
|
||||||
original-start! remote/start!
|
|
||||||
original-stop! remote/stop!
|
|
||||||
original-ws config/db-sync-ws-url
|
|
||||||
original-http config/db-sync-http-base]
|
|
||||||
(reset-runtime-state!)
|
|
||||||
(set! ipc/ipc (fn [channel repo]
|
|
||||||
(is (= "db-worker-runtime" channel))
|
|
||||||
(p/resolved {:base-url "http://127.0.0.1:9101"
|
|
||||||
:auth-token nil
|
|
||||||
:repo repo})))
|
|
||||||
(set! config/db-sync-ws-url (fn [] "wss://sync.example.test/sync/%s"))
|
|
||||||
(set! config/db-sync-http-base (fn [] "https://sync.example.test"))
|
|
||||||
(set! remote/start! (fn [{:keys [repo]}]
|
|
||||||
(->FakeRemote repo
|
|
||||||
(fn [qkw & args]
|
|
||||||
(swap! worker-calls conj [qkw args])
|
|
||||||
(p/resolved nil)))))
|
|
||||||
(set! remote/stop! (fn [_] (p/resolved true)))
|
|
||||||
(-> (p/let [_ (ensure-remote! "logseq_db_graph_a")]
|
|
||||||
(let [set-config-call (first (filter #(= :thread-api/set-db-sync-config (first %))
|
|
||||||
@worker-calls))]
|
|
||||||
(is (= [:thread-api/set-db-sync-config
|
|
||||||
[{:enabled? true
|
|
||||||
:ws-url "wss://sync.example.test/sync/%s"
|
|
||||||
:http-base "https://sync.example.test"}]]
|
|
||||||
set-config-call))))
|
|
||||||
(p/catch (fn [e]
|
|
||||||
(is false (str "unexpected error: " e))))
|
|
||||||
(p/finally (fn []
|
|
||||||
(set! ipc/ipc original-ipc)
|
|
||||||
(set! remote/start! original-start!)
|
|
||||||
(set! remote/stop! original-stop!)
|
|
||||||
(set! config/db-sync-ws-url original-ws)
|
|
||||||
(set! config/db-sync-http-base original-http)
|
|
||||||
(done)))))))
|
|
||||||
|
|
||||||
(deftest electron-ensure-remote-pushes-markdown-mirror-setting-on-start-test
|
(deftest electron-ensure-remote-pushes-markdown-mirror-setting-on-start-test
|
||||||
(async done
|
(async done
|
||||||
(let [worker-calls (atom [])
|
(let [worker-calls (atom [])
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
(ns mobile.header-test
|
|
||||||
(:require [cljs.test :refer [deftest is testing]]
|
|
||||||
[frontend.state :as state]
|
|
||||||
[mobile.components.header :as header]))
|
|
||||||
|
|
||||||
(deftest rtc-graph-uuid-is-not-loaded-before-db-worker-is-ready
|
|
||||||
(testing "mobile startup does not invoke the DB worker before initialization"
|
|
||||||
(let [calls (atom [])]
|
|
||||||
(with-redefs [state/<invoke-db-worker (fn [& args]
|
|
||||||
(swap! calls conj args))]
|
|
||||||
(is (nil? (header/<load-rtc-graph-uuid "graph" false)))
|
|
||||||
(is (empty? @calls))))))
|
|
||||||
|
|
||||||
(deftest rtc-graph-uuid-is-loaded-after-db-worker-is-ready
|
|
||||||
(let [calls (atom [])]
|
|
||||||
(with-redefs [state/<invoke-db-worker (fn [& args]
|
|
||||||
(swap! calls conj args)
|
|
||||||
:graph-uuid)]
|
|
||||||
(is (= :graph-uuid (header/<load-rtc-graph-uuid "graph" true)))
|
|
||||||
(is (= [[:thread-api/get-rtc-graph-uuid "graph"]]
|
|
||||||
@calls)))))
|
|
||||||
Reference in New Issue
Block a user