mirror of
https://github.com/logseq/logseq.git
synced 2026-02-01 22:47:36 +00:00
enhance(ux): add progress bar when exporting zip
This commit is contained in:
8
deps/cli/src/logseq/cli/common/util.cljc
vendored
8
deps/cli/src/logseq/cli/common/util.cljc
vendored
@@ -8,14 +8,16 @@
|
||||
#?(:cljs
|
||||
(defn make-export-zip
|
||||
"Makes a zipfile for an exported version of graph. Removes files with blank content"
|
||||
[zip-filename file-name-content]
|
||||
[zip-filename file-name-content & {:as file-opts}]
|
||||
(let [zip (JSZip.)
|
||||
folder (.folder zip zip-filename)]
|
||||
folder (.folder zip zip-filename)
|
||||
file-opts (when (some? file-opts) (clj->js file-opts))]
|
||||
(doseq [[file-name content] file-name-content]
|
||||
(when-not (string/blank? content)
|
||||
(.file folder (-> file-name
|
||||
(string/replace #"^/+" ""))
|
||||
content)))
|
||||
content
|
||||
file-opts)))
|
||||
zip)))
|
||||
|
||||
;; Macros are defined at top-level for frontend and nbb
|
||||
|
||||
@@ -429,11 +429,12 @@
|
||||
|
||||
(rum/defc indicator-progress < rum/reactive
|
||||
[]
|
||||
(let [{:keys [total current-idx current-page]} (state/sub :graph/importing-state)
|
||||
(let [{:keys [total current-idx current-page label]} (state/sub :graph/importing-state)
|
||||
label (or label (t :importing))
|
||||
left-label (if (and current-idx total (= current-idx total))
|
||||
[:div.flex.flex-row.font-bold "Loading ..."]
|
||||
[:div.flex.flex-row.font-bold
|
||||
(t :importing)
|
||||
label
|
||||
[:div.hidden.md:flex.flex-row
|
||||
[:span.mr-1 ": "]
|
||||
[:div.text-ellipsis-wrapper {:style {:max-width 300}}
|
||||
|
||||
22
src/main/frontend/components/progress.cljs
Normal file
22
src/main/frontend/components/progress.cljs
Normal file
@@ -0,0 +1,22 @@
|
||||
(ns frontend.components.progress
|
||||
(:require [frontend.state :as state]
|
||||
[rum.core :as rum]))
|
||||
|
||||
(defn- progress-bar
|
||||
[width]
|
||||
[:div.w-full.rounded-full.h-2\.5.animate-pulse.bg-gray-06-alpha
|
||||
[:div.bg-gray-09-alpha.h-2\.5.rounded-full {:style {:width (str width "%")}
|
||||
:transition "width 1s"}]])
|
||||
|
||||
(rum/defc progress-indicator < rum/reactive
|
||||
[]
|
||||
(let [{:keys [total current-idx current-page label]} (state/sub :graph/importing-state)
|
||||
label (or label "Processing")
|
||||
width (js/Math.round (* (.toFixed (/ (or current-idx 0) (max 1 total)) 2) 100))]
|
||||
[:div.p-5
|
||||
[:div.flex.justify-between.mb-1
|
||||
[:span.text-base label]
|
||||
[:span.text-sm.font-medium (when (and total current-idx)
|
||||
(str current-idx "/" total))]]
|
||||
[:div.text-xs.opacity-70.mb-2 current-page]
|
||||
(progress-bar width)]))
|
||||
@@ -9,7 +9,17 @@
|
||||
(aset args "lastModified" last-modified)
|
||||
(js/File. blob-content file-name args)))
|
||||
|
||||
(defn make-zip [zip-filename file-name-content _repo]
|
||||
(let [zip (cli-common-util/make-export-zip zip-filename file-name-content)]
|
||||
(p/let [zip-blob (.generateAsync zip #js {:type "blob"})]
|
||||
(defn make-zip
|
||||
[zip-filename file-name-content _repo & {:keys [progress-fn compression]}]
|
||||
(let [compression (or compression "STORE")
|
||||
zip (cli-common-util/make-export-zip zip-filename
|
||||
file-name-content
|
||||
{:compression compression})
|
||||
opts #js {:type "blob"
|
||||
:streamFiles true
|
||||
:compression compression}]
|
||||
(p/let [zip-blob (.generateAsync zip opts
|
||||
(when progress-fn
|
||||
(fn [metadata]
|
||||
(progress-fn (.-percent metadata)))))]
|
||||
(make-file zip-blob (str zip-filename ".zip") {:type "application/zip"}))))
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
[frontend.handler.db-based.vector-search-flows :as vector-search-flows]
|
||||
[frontend.handler.e2ee]
|
||||
[frontend.handler.events :as events]
|
||||
[frontend.handler.events.export]
|
||||
[frontend.handler.events.rtc]
|
||||
[frontend.handler.events.ui]
|
||||
[frontend.handler.global-config :as global-config-handler]
|
||||
|
||||
@@ -133,32 +133,34 @@
|
||||
:current-page "Assets"}))]
|
||||
(if-not entry
|
||||
(notification/show! "Zip missing db.sqlite. Please check the archive structure." :error)
|
||||
(p/let [sqlite-buffer (.async ^js (:entry entry) "arraybuffer")]
|
||||
(import-from-sqlite-db!
|
||||
sqlite-buffer
|
||||
bare-graph-name
|
||||
(fn []
|
||||
(p/let [repo (state/get-current-repo)
|
||||
{:keys [copied failed]}
|
||||
(<copy-zip-assets!
|
||||
repo
|
||||
entries
|
||||
{:total asset-total
|
||||
:progress-fn (fn [current total]
|
||||
(when (pos? total)
|
||||
(state/set-state! :graph/importing-state {:total total
|
||||
:current-idx current
|
||||
:current-page "Assets"})))})]
|
||||
(when (pos? copied)
|
||||
(notification/show! (str "Imported " copied " assets.") :success))
|
||||
(when (seq failed)
|
||||
(notification/show!
|
||||
(str "Skipped " (count failed) " assets. See console for details.")
|
||||
:warning false)
|
||||
(js/console.warn "Zip import skipped assets:" (clj->js failed)))
|
||||
(state/set-state! :graph/importing nil)
|
||||
(state/set-state! :graph/importing-state nil)
|
||||
(finished-ok-handler)))))))
|
||||
(do
|
||||
(shui/dialog-close!)
|
||||
(p/let [sqlite-buffer (.async ^js (:entry entry) "arraybuffer")]
|
||||
(import-from-sqlite-db!
|
||||
sqlite-buffer
|
||||
bare-graph-name
|
||||
(fn []
|
||||
(p/let [repo (state/get-current-repo)
|
||||
{:keys [copied failed]}
|
||||
(<copy-zip-assets!
|
||||
repo
|
||||
entries
|
||||
{:total asset-total
|
||||
:progress-fn (fn [current total]
|
||||
(when (pos? total)
|
||||
(state/set-state! :graph/importing-state {:total total
|
||||
:current-idx current
|
||||
:current-page "Assets"})))})]
|
||||
(when (pos? copied)
|
||||
(notification/show! (str "Imported " copied " assets.") :success))
|
||||
(when (seq failed)
|
||||
(notification/show!
|
||||
(str "Skipped " (count failed) " assets. See console for details.")
|
||||
:warning false)
|
||||
(js/console.warn "Zip import skipped assets:" (clj->js failed)))
|
||||
(state/set-state! :graph/importing nil)
|
||||
(state/set-state! :graph/importing-state nil)
|
||||
(finished-ok-handler))))))))
|
||||
(p/catch
|
||||
(fn [e]
|
||||
(js/console.error e)
|
||||
|
||||
45
src/main/frontend/handler/events/export.cljs
Normal file
45
src/main/frontend/handler/events/export.cljs
Normal file
@@ -0,0 +1,45 @@
|
||||
(ns frontend.handler.events.export
|
||||
"Export events"
|
||||
(:require [frontend.handler.events :as events]
|
||||
[frontend.state :as state]
|
||||
[frontend.ui :as ui]
|
||||
[logseq.shui.dialog.core :as shui-dialog]
|
||||
[logseq.shui.ui :as shui]
|
||||
[rum.core :as rum]))
|
||||
|
||||
(rum/defc indicator-progress < rum/reactive
|
||||
[]
|
||||
(let [{:keys [total current-idx current-page label]} (state/sub :graph/exporting-state)
|
||||
label (or label "Exporting")
|
||||
left-label (if (and current-idx total (= current-idx total))
|
||||
[:div.flex.flex-row.font-bold "Loading ..."]
|
||||
[:div.flex.flex-row.font-bold
|
||||
label
|
||||
[:div.hidden.md:flex.flex-row
|
||||
[:span.mr-1 ": "]
|
||||
[:div.text-ellipsis-wrapper {:style {:max-width 300}}
|
||||
current-page]]])
|
||||
width (js/Math.round (* (.toFixed (/ current-idx total) 2) 100))
|
||||
process (when (and total current-idx)
|
||||
(str current-idx "/" total))]
|
||||
[:div.p-5
|
||||
(ui/progress-bar-with-label width left-label process)]))
|
||||
|
||||
(defmethod events/handle :dialog/export-zip [[_ label]]
|
||||
(shui/dialog-close!)
|
||||
(state/set-state! :graph/exporting :export-zip)
|
||||
(state/set-state! :graph/exporting-state {:total 100
|
||||
:current-idx 0
|
||||
:current-page label
|
||||
:label "Exporting"})
|
||||
(when-not (shui-dialog/get-modal :export-indicator)
|
||||
(shui/dialog-open! indicator-progress
|
||||
{:id :export-indicator
|
||||
:content-props
|
||||
{:onPointerDownOutside #(.preventDefault %)
|
||||
:onOpenAutoFocus #(.preventDefault %)}})))
|
||||
|
||||
(defmethod events/handle :dialog/close-export-zip [_]
|
||||
(state/set-state! :graph/exporting nil)
|
||||
(state/set-state! :graph/exporting-state nil)
|
||||
(shui/dialog-close! :export-indicator))
|
||||
@@ -44,16 +44,42 @@
|
||||
|
||||
(defn db-based-export-repo-as-zip!
|
||||
[repo]
|
||||
(p/let [db-data (persist-db/<export-db repo {:return-data? true})
|
||||
filename "db.sqlite"
|
||||
repo-name (common-sqlite/sanitize-db-name repo)
|
||||
assets (assets-handler/<get-all-assets)
|
||||
files (cons [filename db-data] assets)
|
||||
zipfile (zip/make-zip repo-name files repo)]
|
||||
(when-let [anchor (gdom/getElement "download-as-zip")]
|
||||
(.setAttribute anchor "href" (js/window.URL.createObjectURL zipfile))
|
||||
(.setAttribute anchor "download" (.-name zipfile))
|
||||
(.click anchor))))
|
||||
(state/pub-event! [:dialog/export-zip "Preparing zip"])
|
||||
(-> (p/let [db-data (persist-db/<export-db repo {:return-data? true})
|
||||
filename "db.sqlite"
|
||||
repo-name (common-sqlite/sanitize-db-name repo)
|
||||
_ (state/set-state! :graph/exporting-state {:total 100
|
||||
:current-idx 20
|
||||
:current-page "Collecting assets"
|
||||
:label "Exporting"})
|
||||
assets (assets-handler/<get-all-assets)
|
||||
files (cons [filename db-data] assets)
|
||||
_ (state/set-state! :graph/exporting-state {:total 100
|
||||
:current-idx 40
|
||||
:current-page "Creating zip"
|
||||
:label "Exporting"})
|
||||
zipfile (zip/make-zip repo-name files repo
|
||||
{:compression "STORE"
|
||||
:progress-fn (fn [percent]
|
||||
(let [scaled (+ 40 (* 0.6 percent))]
|
||||
(state/set-state! :graph/exporting-state
|
||||
{:total 100
|
||||
:current-idx (js/Math.round scaled)
|
||||
:current-page "Creating zip"
|
||||
:label "Exporting"})))})]
|
||||
(state/set-state! :graph/exporting-state {:total 100
|
||||
:current-idx 100
|
||||
:current-page "Finalizing"
|
||||
:label "Exporting"})
|
||||
(when-let [anchor (gdom/getElement "download-as-zip")]
|
||||
(.setAttribute anchor "href" (js/window.URL.createObjectURL zipfile))
|
||||
(.setAttribute anchor "download" (.-name zipfile))
|
||||
(.click anchor)))
|
||||
(p/catch (fn [error]
|
||||
(js/console.error error)
|
||||
(notification/show! "Export zip failed." :error)))
|
||||
(p/finally (fn []
|
||||
(state/pub-event! [:dialog/close-export-zip])))))
|
||||
|
||||
(defn export-repo-as-zip!
|
||||
[repo]
|
||||
|
||||
Reference in New Issue
Block a user