Skip to content

Commit

Permalink
enhance: use shadcn form components for import
Browse files Browse the repository at this point in the history
Part of LOG-3235. Also adds inline client validation for required name field.
Form components look better, easier to add more inputs and there are
many examples to learn from
  • Loading branch information
logseq-cldwalker committed Nov 4, 2024
1 parent 3f232ce commit 28e2ca1
Show file tree
Hide file tree
Showing 2 changed files with 168 additions and 171 deletions.
337 changes: 167 additions & 170 deletions src/main/frontend/components/imports.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
[rum.core :as rum]
[logseq.shui.ui :as shui]
[logseq.shui.dialog.core :as shui-dialog]
[logseq.shui.form.core :as form-core]
[lambdaisland.glogi :as log]
[logseq.db.frontend.validate :as db-validate]
[logseq.db :as ldb]))
Expand Down Expand Up @@ -165,86 +166,82 @@
{:on-click on-submit})]]))

(rum/defc import-file-graph-dialog
[initial-name on-graph-name-confirmed]
(let [[graph-input set-graph-input!] (rum/use-state initial-name)
[tag-classes-input set-tag-classes-input!] (rum/use-state "")
[convert-all-tags-input set-convert-all-tags-input!] (rum/use-state false)
[property-classes-input set-property-classes-input!] (rum/use-state "")
[property-parent-classes-input set-property-parent-classes-input!] (rum/use-state "")
on-submit #(do (on-graph-name-confirmed
{:graph-name graph-input
:tag-classes tag-classes-input
:convert-all-tags? convert-all-tags-input
:property-classes property-classes-input
:property-parent-classes property-parent-classes-input})
(shui/dialog-close!))]
[:div.container
[:div.sm:flex.sm:items-start
[:div.mt-3.text-center.sm:mt-0.sm:text-left
[:h3#modal-headline.leading-6.font-medium
"New graph name:"]]]
(shui/input
{:class "my-2 mb-4"
:auto-focus true
:default-value graph-input
:on-change (fn [e]
(set-graph-input! (util/evalue e)))
:on-key-down (fn [e]
(when (= "Enter" (util/ekey e))
(on-submit)))})

[:div.sm:flex.sm:items-start
[:div.mt-3.text-center.sm:mt-0.sm:text-left
[:h3#modal-headline.leading-6.font-medium
"(Optional) Tags to import as new tags:"]
[:div
[:span.text-sm.mr-2 "Import all tags"]
(shui/checkbox {:default-checked convert-all-tags-input
:on-checked-change set-convert-all-tags-input!})]
[:div
[:span.text-sm "Import only these tags. "]
[:span.text-xs "Tags are case insensitive and separated by commas"]]]]
(shui/input
{:class "my-2 mb-4"
:default-value tag-classes-input
:disabled convert-all-tags-input
:on-change (fn [e]
(set-tag-classes-input! (util/evalue e)))
:on-key-down (fn [e]
(when (= "Enter" (util/ekey e))
(on-submit)))})

[:div.sm:flex.sm:items-start
[:div.mt-3.text-center.sm:mt-0.sm:text-left
[:h3#modal-headline.leading-6.font-medium
"(Optional) Properties whose values are imported as new tags e.g. 'type':"]
[:span.text-xs
"Properties are case insensitive and separated by commas"]]]
(shui/input
{:class "my-2 mb-4"
:default-value property-classes-input
:on-change (fn [e]
(set-property-classes-input! (util/evalue e)))
:on-key-down (fn [e]
(when (= "Enter" (util/ekey e))
(on-submit)))})

[:div.sm:flex.sm:items-start
[:div.mt-3.text-center.sm:mt-0.sm:text-left
[:h3#modal-headline.leading-6.font-medium
"(Optional) Properties whose values are imported as parents of new tags e.g. 'parent':"]
[:span.text-xs
"Properties are case insensitive and separated by commas"]]]
(shui/input
{:class "my-2 mb-4"
:default-value property-parent-classes-input
:on-change (fn [e]
(set-property-parent-classes-input! (util/evalue e)))
:on-key-down (fn [e]
(when (= "Enter" (util/ekey e))
(on-submit)))})

(shui/button {:size :sm :on-click on-submit} "Submit")]))
[initial-name on-submit-fn]
[:div.border.p-6.rounded.bg-gray-01.mt-4
(let [form-ctx (form-core/use-form
{:defaultValues {:graph-name initial-name
:convert-all-tags false
:tag-classes ""
:property-classes ""
:property-parent-classes ""}
:yupSchema (-> (.object form-core/yup)
(.shape #js {:graph-name (-> (.string form-core/yup) (.required))})
(.required))})
handle-submit (:handleSubmit form-ctx)
on-submit-valid (handle-submit
(fn [^js e]
;; (js/console.log "[form] submit: " e (js->clj e))
(on-submit-fn (js->clj e :keywordize-keys true))
(shui/dialog-close!)))
[convert-all-tags-input set-convert-all-tags-input!] (rum/use-state false)]

(shui/form-provider form-ctx
[:form
{:on-submit on-submit-valid}

(shui/form-field {:name "graph-name"}
(fn [field error]
(shui/form-item
(shui/form-label "New graph name")
(shui/form-control
(shui/input (merge {:placeholder "Graph name"} field)))
(when error
(shui/form-description
[:b.text-red-800 (:message error)])))))

(shui/form-field {:name "convert-all-tags"}
(fn [field]
(shui/form-item
{:class "pt-3 flex justify-start items-center space-x-3 space-y-0 my-3 pr-3"}
(shui/form-label "Import all tags")
(shui/form-control
(shui/checkbox {:checked (:value field)
:on-checked-change (fn [e]
((:onChange field) e)
(set-convert-all-tags-input! (not convert-all-tags-input)))})))))

(shui/form-field {:name "tag-classes"}
(fn [field _error]
(shui/form-item
{:class "pt-3"}
(shui/form-label "Import specific tags")
(shui/form-control
(shui/input (merge field
{:placeholder "tag 1, tag 2" :disabled convert-all-tags-input})))
(shui/form-description
"Tags are case insensitive"))))

(shui/form-field {:name "property-classes"}
(fn [field _error]
(shui/form-item
{:class "pt-3"}
(shui/form-label "Import additional tags from property values")
(shui/form-control
(shui/input (merge {:placeholder "e.g. type"} field)))
(shui/form-description
"Properties are case insensitive and separated by commas"))))

(shui/form-field {:name "property-parent-classes"}
(fn [field _error]
(shui/form-item
{:class "pt-3"}
(shui/form-label "Import tag parents from property values")
(shui/form-control
(shui/input (merge {:placeholder "e.g. parent"} field)))
(shui/form-description
"Properties are case insensitive and separated by commas"))))

(shui/button {:type "submit" :class "right-0 mt-3"} "Submit")]))])

(defn- counts-from-entities
[entities]
Expand Down Expand Up @@ -313,7 +310,7 @@
(fs/write-file! repo repo-dir (:path file) content {:skip-transact? true})))))))

(defn- import-file-graph
[*files {:keys [graph-name tag-classes convert-all-tags? property-classes property-parent-classes]} config-file]
[*files {:keys [graph-name tag-classes convert-all-tags property-classes property-parent-classes]} config-file]
(state/set-state! :graph/importing :file-graph)
(state/set-state! [:graph/importing-state :current-page] "Config files")
(p/let [start-time (t/now)
Expand All @@ -324,7 +321,7 @@
:tag-classes (set (string/split tag-classes #",\s*"))
:property-classes (set (string/split property-classes #",\s*"))
:property-parent-classes (set (string/split property-parent-classes #",\s*"))
:convert-all-tags? convert-all-tags?
:convert-all-tags? convert-all-tags
;; common options
:notify-user show-notification
:set-ui-state state/set-state!
Expand Down Expand Up @@ -355,33 +352,31 @@
(finished-cb)))

(defn import-file-to-db-handler
"Import from a graph folder as a DB-based graph.
- Page name, journal name creation"
[ev _opts]
(let [^js file-objs (array-seq (.-files (.-target ev)))
original-graph-name (string/replace (.-webkitRelativePath (first file-objs)) #"/.*" "")
import-graph-fn (fn [user-inputs]
(let [files (->> file-objs
(map #(hash-map :file-object %
:path (path/trim-dir-prefix original-graph-name (.-webkitRelativePath %))))
(remove #(and (not (string/starts-with? (:path %) "assets/"))
"Import from a graph folder as a DB-based graph"
[ev opts]
(let [^js file-objs (if ev (array-seq (.-files (.-target ev))) #js [])
original-graph-name (if (first file-objs)
(string/replace (.-webkitRelativePath (first file-objs)) #"/.*" "")
"")
import-graph-fn (or (:import-graph-fn opts)
(fn [user-inputs]
(let [files (->> file-objs
(map #(hash-map :file-object %
:path (path/trim-dir-prefix original-graph-name (.-webkitRelativePath %))))
(remove #(and (not (string/starts-with? (:path %) "assets/"))
;; TODO: Update this when supporting more formats as this aggressively excludes most formats
(fs-util/ignored-path? original-graph-name (.-webkitRelativePath (:file-object %))))))]
(if-let [config-file (first (filter #(= (:path %) "logseq/config.edn") files))]
(import-file-graph files user-inputs config-file)
(notification/show! "Import failed as the file 'logseq/config.edn' was not found for a Logseq graph."
:error))))]
(fs-util/ignored-path? original-graph-name (.-webkitRelativePath (:file-object %))))))]
(if-let [config-file (first (filter #(= (:path %) "logseq/config.edn") files))]
(import-file-graph files user-inputs config-file)
(notification/show! "Import failed as the file 'logseq/config.edn' was not found for a Logseq graph."
:error)))))]
(shui/dialog-open!
#(import-file-graph-dialog original-graph-name
(fn [{:keys [graph-name] :as user-inputs}]
(cond
(repo/invalid-graph-name? graph-name)
(repo/invalid-graph-name-warning)

(string/blank? graph-name)
(notification/show! "Empty graph name." :error)

(repo-handler/graph-already-exists? graph-name)
(notification/show! "Please specify another name as another graph with this name already exists!" :error)

Expand Down Expand Up @@ -426,72 +421,74 @@
(import-indicator importing?)
(when-not importing?
(setups/setups-container
:importer
[:article.flex.flex-col.items-center.importer.py-16.px-8
[:section.c.text-center
[:h1 (t :on-boarding/importing-title)]
[:h2 (t :on-boarding/importing-desc)]]
[:section.d.md:flex.flex-col
[:label.action-input.flex.items-center.mx-2.my-2
[:span.as-flex-center [:i (svg/logo 28)]]
[:span.flex.flex-col
[[:strong "SQLite"]
[:small (t :on-boarding/importing-sqlite-desc)]]]
[:input.absolute.hidden
{:id "import-sqlite-db"
:type "file"
:on-change (fn [e]
(shui/dialog-open!
#(set-graph-name-dialog e {:sqlite? true})))}]]

(when (or (util/electron?) util/web-platform?)
[:label.action-input.flex.items-center.mx-2.my-2
[:span.as-flex-center [:i (svg/logo 28)]]
[:span.flex.flex-col
[[:strong "File to DB graph"]
[:small "Import a file-based Logseq graph folder into a new DB graph"]]]
[:input.absolute.hidden
{:id "import-file-graph"
:type "file"
:webkitdirectory "true"
:on-change (debounce (fn [e]
(import-file-to-db-handler e {}))
1000)}]])

(when (and (util/electron?) support-file-based?)
[:label.action-input.flex.items-center.mx-2.my-2
[:span.as-flex-center [:i (svg/logo 28)]]
[:span.flex.flex-col
[[:strong "EDN / JSON"]
[:small (t :on-boarding/importing-lsq-desc)]]]
[:input.absolute.hidden
{:id "import-lsq"
:type "file"
:on-change lsq-import-handler}]])

(when (and (util/electron?) support-file-based?)
[:label.action-input.flex.items-center.mx-2.my-2
[:span.as-flex-center [:i (svg/roam-research 28)]]
[:div.flex.flex-col
[[:strong "RoamResearch"]
[:small (t :on-boarding/importing-roam-desc)]]]
[:input.absolute.hidden
{:id "import-roam"
:type "file"
:on-change roam-import-handler}]])

(when (and (util/electron?) support-file-based?)
[:label.action-input.flex.items-center.mx-2.my-2
[:span.as-flex-center.ml-1 (ui/icon "sitemap" {:size 26})]
[:span.flex.flex-col
[[:strong "OPML"]
[:small (t :on-boarding/importing-opml-desc)]]]

[:input.absolute.hidden
{:id "import-opml"
:type "file"
:on-change opml-import-handler}]])]

(when (= "picker" (:from query-params))
[:section.e
[:a.button {:on-click #(route-handler/redirect-to-home!)} "Skip"]])]))]))
:importer
[:article.flex.flex-col.items-center.importer.py-16.px-8
[:section.c.text-center
[:h1 (t :on-boarding/importing-title)]
[:h2 (t :on-boarding/importing-desc)]]
[:section.d.md:flex.flex-col
[:label.action-input.flex.items-center.mx-2.my-2
[:span.as-flex-center [:i (svg/logo 28)]]
[:span.flex.flex-col
[[:strong "SQLite"]
[:small (t :on-boarding/importing-sqlite-desc)]]]
[:input.absolute.hidden
{:id "import-sqlite-db"
:type "file"
:on-change (fn [e]
(shui/dialog-open!
#(set-graph-name-dialog e {:sqlite? true})))}]]

(when (or (util/electron?) util/web-platform?)
[:label.action-input.flex.items-center.mx-2.my-2
[:span.as-flex-center [:i (svg/logo 28)]]
[:span.flex.flex-col
[[:strong "File to DB graph"]
[:small "Import a file-based Logseq graph folder into a new DB graph"]]]
;; Test form style changes
#_[:a.button {:on-click #(import-file-to-db-handler nil {:import-graph-fn js/alert})} "Open"]
[:input.absolute.hidden
{:id "import-file-graph"
:type "file"
:webkitdirectory "true"
:on-change (debounce (fn [e]
(import-file-to-db-handler e {}))
1000)}]])

(when (and (util/electron?) support-file-based?)
[:label.action-input.flex.items-center.mx-2.my-2
[:span.as-flex-center [:i (svg/logo 28)]]
[:span.flex.flex-col
[[:strong "EDN / JSON"]
[:small (t :on-boarding/importing-lsq-desc)]]]
[:input.absolute.hidden
{:id "import-lsq"
:type "file"
:on-change lsq-import-handler}]])

(when (and (util/electron?) support-file-based?)
[:label.action-input.flex.items-center.mx-2.my-2
[:span.as-flex-center [:i (svg/roam-research 28)]]
[:div.flex.flex-col
[[:strong "RoamResearch"]
[:small (t :on-boarding/importing-roam-desc)]]]
[:input.absolute.hidden
{:id "import-roam"
:type "file"
:on-change roam-import-handler}]])

(when (and (util/electron?) support-file-based?)
[:label.action-input.flex.items-center.mx-2.my-2
[:span.as-flex-center.ml-1 (ui/icon "sitemap" {:size 26})]
[:span.flex.flex-col
[[:strong "OPML"]
[:small (t :on-boarding/importing-opml-desc)]]]

[:input.absolute.hidden
{:id "import-opml"
:type "file"
:on-change opml-import-handler}]])]

(when (= "picker" (:from query-params))
[:section.e
[:a.button {:on-click #(route-handler/redirect-to-home!)} "Skip"]])]))]))
2 changes: 1 addition & 1 deletion src/main/frontend/routes.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
[frontend.extensions.zotero :as zotero]
[frontend.components.bug-report :as bug-report]
[frontend.components.user.login :as login]
[logseq.shui.demo2 :as shui]
[logseq.shui.demo :as shui]
[frontend.components.imports :as imports]
[frontend.config :as config]
[logseq.db :as ldb]
Expand Down

0 comments on commit 28e2ca1

Please sign in to comment.