imports.cljs 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591
  1. (ns frontend.components.imports
  2. "Import data into Logseq."
  3. (:require [cljs-time.core :as t]
  4. [cljs.pprint :as pprint]
  5. [clojure.string :as string]
  6. [datascript.core :as d]
  7. [frontend.components.onboarding.setups :as setups]
  8. [frontend.components.repo :as repo]
  9. [frontend.components.svg :as svg]
  10. [frontend.config :as config]
  11. [frontend.context.i18n :refer [t]]
  12. [frontend.db :as db]
  13. [frontend.fs :as fs]
  14. [frontend.handler.assets :as assets-handler]
  15. [frontend.handler.db-based.editor :as db-editor-handler]
  16. [frontend.handler.db-based.import :as db-import-handler]
  17. [frontend.handler.file-based.import :as file-import-handler]
  18. [frontend.handler.import :as import-handler]
  19. [frontend.handler.notification :as notification]
  20. [frontend.handler.repo :as repo-handler]
  21. [frontend.handler.route :as route-handler]
  22. [frontend.handler.ui :as ui-handler]
  23. [frontend.persist-db.browser :as db-browser]
  24. [frontend.state :as state]
  25. [frontend.ui :as ui]
  26. [frontend.util :as util]
  27. [frontend.util.fs :as fs-util]
  28. [goog.functions :refer [debounce]]
  29. [goog.object :as gobj]
  30. [lambdaisland.glogi :as log]
  31. [logseq.common.config :as common-config]
  32. [logseq.common.path :as path]
  33. [logseq.db.frontend.asset :as db-asset]
  34. [logseq.db.frontend.validate :as db-validate]
  35. [logseq.graph-parser.exporter :as gp-exporter]
  36. [logseq.shui.dialog.core :as shui-dialog]
  37. [logseq.shui.form.core :as form-core]
  38. [logseq.shui.hooks :as hooks]
  39. [logseq.shui.ui :as shui]
  40. [promesa.core :as p]
  41. [rum.core :as rum]))
  42. ;; Can't name this component as `frontend.components.import` since shadow-cljs
  43. ;; will complain about it.
  44. (defonce *opml-imported-pages (atom nil))
  45. (defn- finished-cb
  46. []
  47. (notification/show! "Import finished!" :success)
  48. (shui/dialog-close! :import-indicator)
  49. (route-handler/redirect-to-home!)
  50. (if util/web-platform?
  51. (js/window.location.reload)
  52. (js/setTimeout ui-handler/re-render-root! 500)))
  53. (defn- roam-import-handler
  54. [e]
  55. (let [file (first (array-seq (.-files (.-target e))))
  56. file-name (gobj/get file "name")]
  57. (if (string/ends-with? file-name ".json")
  58. (do
  59. (state/set-state! :graph/importing :roam-json)
  60. (let [reader (js/FileReader.)]
  61. (set! (.-onload reader)
  62. (fn [e]
  63. (let [text (.. e -target -result)]
  64. (file-import-handler/import-from-roam-json!
  65. text
  66. #(do
  67. (state/set-state! :graph/importing nil)
  68. (finished-cb))))))
  69. (.readAsText reader file)))
  70. (notification/show! "Please choose a JSON file."
  71. :error))))
  72. (defn- lsq-import-handler
  73. [e & {:keys [sqlite? debug-transit? graph-name db-edn?]}]
  74. (let [file (first (array-seq (.-files (.-target e))))
  75. file-name (some-> (gobj/get file "name")
  76. (string/lower-case))
  77. edn? (string/ends-with? file-name ".edn")
  78. json? (string/ends-with? file-name ".json")]
  79. (cond
  80. sqlite?
  81. (let [graph-name (string/trim graph-name)]
  82. (cond
  83. (string/blank? graph-name)
  84. (notification/show! "Empty graph name." :error)
  85. (repo-handler/graph-already-exists? graph-name)
  86. (notification/show! "Please specify another name as another graph with this name already exists!" :error)
  87. :else
  88. (let [reader (js/FileReader.)]
  89. (set! (.-onload reader)
  90. (fn []
  91. (let [buffer (.-result ^js reader)]
  92. (db-import-handler/import-from-sqlite-db! buffer graph-name finished-cb)
  93. (shui/dialog-close!))))
  94. (set! (.-onerror reader) (fn [e] (js/console.error e)))
  95. (set! (.-onabort reader) (fn [e]
  96. (prn :debug :aborted)
  97. (js/console.error e)))
  98. (.readAsArrayBuffer reader file))))
  99. (or debug-transit? db-edn?)
  100. (let [graph-name (string/trim graph-name)]
  101. (cond
  102. (string/blank? graph-name)
  103. (notification/show! "Empty graph name." :error)
  104. (repo-handler/graph-already-exists? graph-name)
  105. (notification/show! "Please specify another name as another graph with this name already exists!" :error)
  106. :else
  107. (do
  108. (state/set-state! :graph/importing :logseq)
  109. (let [reader (js/FileReader.)
  110. import-f (if db-edn?
  111. db-import-handler/import-from-edn-file!
  112. db-import-handler/import-from-debug-transit!)]
  113. (set! (.-onload reader)
  114. (fn [e]
  115. (let [text (.. e -target -result)]
  116. (import-f
  117. graph-name
  118. text
  119. #(do
  120. (state/set-state! :graph/importing nil)
  121. (finished-cb)
  122. ;; graph input not closing
  123. (shui/dialog-close-all!))))))
  124. (.readAsText reader file)))))
  125. (or edn? json?)
  126. (do
  127. (state/set-state! :graph/importing :logseq)
  128. (let [reader (js/FileReader.)
  129. import-f (if edn?
  130. import-handler/import-from-edn!
  131. import-handler/import-from-json!)]
  132. (set! (.-onload reader)
  133. (fn [e]
  134. (let [text (.. e -target -result)]
  135. (import-f
  136. text
  137. #(do
  138. (state/set-state! :graph/importing nil)
  139. (finished-cb))))))
  140. (.readAsText reader file)))
  141. :else
  142. (notification/show! "Please choose an EDN or a JSON file."
  143. :error))))
  144. (defn- opml-import-handler
  145. [e]
  146. (let [file (first (array-seq (.-files (.-target e))))
  147. file-name (gobj/get file "name")]
  148. (if (string/ends-with? file-name ".opml")
  149. (do
  150. (state/set-state! :graph/importing :opml)
  151. (let [reader (js/FileReader.)]
  152. (set! (.-onload reader)
  153. (fn [e]
  154. (let [text (.. e -target -result)]
  155. (import-handler/import-from-opml! text
  156. (fn [pages]
  157. (reset! *opml-imported-pages pages)
  158. (state/set-state! :graph/importing nil)
  159. (finished-cb))))))
  160. (.readAsText reader file)))
  161. (notification/show! "Please choose a OPML file."
  162. :error))))
  163. (rum/defcs set-graph-name-dialog
  164. < rum/reactive
  165. (rum/local "" ::input)
  166. [state input-e opts]
  167. (let [*input (::input state)
  168. on-submit #(if (repo/invalid-graph-name? @*input)
  169. (repo/invalid-graph-name-warning)
  170. (lsq-import-handler input-e (assoc opts :graph-name @*input)))]
  171. [:div.container
  172. [:div.sm:flex.sm:items-start
  173. [:div.mt-3.text-center.sm:mt-0.sm:text-left
  174. [:h3#modal-headline.leading-6.font-medium.pb-2
  175. "New graph name:"]]]
  176. [:input.form-input.block.w-full.sm:text-sm.sm:leading-5.my-2.mb-4
  177. {:auto-focus true
  178. :on-change (fn [e]
  179. (reset! *input (util/evalue e)))
  180. :on-key-down (fn [e]
  181. (when (= "Enter" (util/ekey e))
  182. (on-submit)))}]
  183. [:div.mt-5.sm:mt-4.flex
  184. (ui/button "Submit"
  185. {:on-click on-submit})]]))
  186. (rum/defc import-file-graph-dialog
  187. [initial-name on-submit-fn]
  188. [:div.border.p-6.rounded.bg-gray-01.mt-4
  189. (let [form-ctx (form-core/use-form
  190. {:defaultValues {:graph-name initial-name
  191. :convert-all-tags? true
  192. :tag-classes ""
  193. :remove-inline-tags? true
  194. :property-classes ""
  195. :property-parent-classes ""}
  196. :yupSchema (-> (.object form-core/yup)
  197. (.shape #js {:graph-name (-> (.string form-core/yup) (.required))})
  198. (.required))})
  199. handle-submit (:handleSubmit form-ctx)
  200. on-submit-valid (handle-submit
  201. (fn [^js e]
  202. ;; (js/console.log "[form] submit: " e (js->clj e))
  203. (on-submit-fn (js->clj e :keywordize-keys true))
  204. (shui/dialog-close!)))
  205. [convert-all-tags-input set-convert-all-tags-input!] (rum/use-state true)]
  206. (shui/form-provider form-ctx
  207. [:form
  208. {:on-submit on-submit-valid}
  209. (shui/form-field {:name "graph-name"}
  210. (fn [field error]
  211. (shui/form-item
  212. (shui/form-label "New graph name")
  213. (shui/form-control
  214. (shui/input (merge {:placeholder "Graph name"} field)))
  215. (when error
  216. (shui/form-description
  217. [:b.text-red-800 (:message error)])))))
  218. (shui/form-field {:name "convert-all-tags?"}
  219. (fn [field]
  220. (shui/form-item
  221. {:class "pt-3 flex justify-start items-center space-x-3 space-y-0 my-3 pr-3"}
  222. (shui/form-label "Import all tags")
  223. (shui/form-control
  224. (shui/checkbox {:checked (:value field)
  225. :on-checked-change (fn [e]
  226. ((:onChange field) e)
  227. (set-convert-all-tags-input! (not convert-all-tags-input)))})))))
  228. (shui/form-field {:name "tag-classes"}
  229. (fn [field _error]
  230. (shui/form-item
  231. {:class "pt-3"}
  232. (shui/form-label "Import specific tags")
  233. (shui/form-control
  234. (shui/input (merge field
  235. {:placeholder "tag 1, tag 2" :disabled convert-all-tags-input})))
  236. (shui/form-description "Tags are case insensitive"))))
  237. (shui/form-field {:name "remove-inline-tags?"}
  238. (fn [field]
  239. (shui/form-item
  240. {:class "pt-3 flex justify-start items-center space-x-3 space-y-0 my-3 pr-3"}
  241. (shui/form-label "Remove inline tags")
  242. (shui/form-description "Default behavior for DB graphs")
  243. (shui/form-control
  244. (shui/checkbox {:checked (:value field)
  245. :on-checked-change (:onChange field)})))))
  246. (shui/form-field {:name "property-classes"}
  247. (fn [field _error]
  248. (shui/form-item
  249. {:class "pt-3"}
  250. (shui/form-label "Import additional tags from property values")
  251. (shui/form-control
  252. (shui/input (merge {:placeholder "e.g. type"} field)))
  253. (shui/form-description
  254. "Properties are case insensitive and separated by commas"))))
  255. (shui/form-field {:name "property-parent-classes"}
  256. (fn [field _error]
  257. (shui/form-item
  258. {:class "pt-3"}
  259. (shui/form-label "Import tag parents from property values")
  260. (shui/form-control
  261. (shui/input (merge {:placeholder "e.g. parent"} field)))
  262. (shui/form-description
  263. "Properties are case insensitive and separated by commas"))))
  264. (shui/button {:type "submit" :class "right-0 mt-3"} "Submit")]))])
  265. (defn- validate-imported-data
  266. [db import-state files]
  267. (when-let [org-files (seq (filter #(= "org" (path/file-ext (:path %))) files))]
  268. (log/info :org-files (mapv :path org-files))
  269. (notification/show! (str "Imported " (count org-files) " org file(s) as markdown. Support for org files will be added later.")
  270. :info false))
  271. (when-let [ignored-files (seq @(:ignored-files import-state))]
  272. (notification/show! (str "Import ignored " (count ignored-files) " "
  273. (if (= 1 (count ignored-files)) "file" "files")
  274. ". See the javascript console for more details.")
  275. :info false)
  276. (log/error :import-ignored-files {:msg (str "Import ignored " (count ignored-files) " file(s)")})
  277. (pprint/pprint ignored-files))
  278. (when-let [ignored-assets (seq @(:ignored-assets import-state))]
  279. (notification/show! (str "Import ignored " (count ignored-assets) " "
  280. (if (= 1 (count ignored-assets)) "asset" "assets")
  281. ". See the javascript console for more details.")
  282. :info false)
  283. (log/error :import-ignored-assets {:msg (str "Import ignored " (count ignored-assets) " asset(s)")})
  284. (pprint/pprint ignored-assets))
  285. (when-let [ignored-props (seq @(:ignored-properties import-state))]
  286. (notification/show!
  287. [:.mb-2
  288. [:.text-lg.mb-2 (str "Import ignored " (count ignored-props) " "
  289. (if (= 1 (count ignored-props)) "property" "properties"))]
  290. [:span.text-xs
  291. "To fix a property type, change the property value to the correct type and reimport the graph"]
  292. (->> ignored-props
  293. (map (fn [{:keys [property value schema location]}]
  294. [(str "Property " (pr-str property) " with value " (pr-str value))
  295. (if (= property :icon)
  296. (if (:page location)
  297. (str "Page icons can't be imported. Go to the page " (pr-str (:page location)) " to manually import it.")
  298. (str "Block icons can't be imported. Manually import it at the block: " (pr-str (:block location))))
  299. (if (not= (get-in schema [:type :to]) (get-in schema [:type :from]))
  300. (str "Property value has type " (get-in schema [:type :to]) " instead of type " (get-in schema [:type :from]))
  301. (str "Property should be imported manually")))]))
  302. (map (fn [[k v]]
  303. [:dl.my-2.mb-0
  304. [:dt.m-0 [:strong (str k)]]
  305. [:dd {:class "text-warning"} v]])))]
  306. :warning false))
  307. (let [{:keys [errors datom-count entities]} (db-validate/validate-db! db)]
  308. (if errors
  309. (do
  310. (log/error :import-errors {:msg (str "Import detected " (count errors) " invalid block(s):")
  311. :counts (assoc (db-validate/graph-counts db entities) :datoms datom-count)})
  312. (pprint/pprint errors)
  313. (notification/show! (str "Import detected " (count errors) " invalid block(s). These blocks may be buggy when you interact with them. See the javascript console for more.")
  314. :warning false))
  315. (log/info :import-valid {:msg "Valid import!"
  316. :counts (assoc (db-validate/graph-counts db entities) :datoms datom-count)}))))
  317. (defn- show-notification [{:keys [msg level ex-data]}]
  318. (if (= :error level)
  319. (do
  320. (notification/show! msg :error)
  321. (when ex-data
  322. (log/error :import-error ex-data)))
  323. (notification/show! msg :warning false)))
  324. (defn- read-and-copy-asset [repo repo-dir file assets buffer-handler]
  325. (let [^js file-object (:file-object file)]
  326. (if (assets-handler/exceed-limit-size? file-object)
  327. (do
  328. (js/console.log (str "Skipped copying asset " (pr-str (:path file)) " because it is larger than the 100M max."))
  329. ;; This asset will also be included in the ignored-assets count. Better to be explicit about ignoring
  330. ;; these so users are aware of this
  331. (notification/show!
  332. (str "Skipped copying asset " (pr-str (:path file)) " because it is larger than the 100M max.")
  333. :info
  334. false))
  335. (p/let [buffer (.arrayBuffer file-object)
  336. bytes-array (js/Uint8Array. buffer)
  337. checksum (db-asset/<get-file-array-buffer-checksum buffer)
  338. asset-id (d/squuid)
  339. asset-name (gp-exporter/asset-path->name (:path file))
  340. assets-dir (path/path-join repo-dir common-config/local-assets-dir)
  341. asset-type (db-asset/asset-path->type (:path file))
  342. {:keys [with-edn-content pdf-annotation?]} (buffer-handler bytes-array)]
  343. (swap! assets assoc asset-name
  344. (with-edn-content
  345. {:size (.-size file-object)
  346. :type asset-type
  347. :path (:path file)
  348. :checksum checksum
  349. :asset-id asset-id}))
  350. (fs/mkdir-if-not-exists assets-dir)
  351. (when-not pdf-annotation?
  352. (fs/write-plain-text-file! repo assets-dir (str asset-id "." asset-type) bytes-array {:skip-transact? true}))))))
  353. (defn- import-file-graph
  354. [*files
  355. {:keys [graph-name tag-classes property-classes property-parent-classes] :as user-options}
  356. config-file]
  357. (state/set-state! :graph/importing :file-graph)
  358. (state/set-state! [:graph/importing-state :current-page] "Config files")
  359. (p/let [start-time (t/now)
  360. _ (repo-handler/new-db! graph-name {:file-graph-import? true})
  361. repo (state/get-current-repo)
  362. db-conn (db/get-db repo false)
  363. options {:user-options
  364. (merge
  365. (dissoc user-options :graph-name)
  366. {:tag-classes (some-> tag-classes string/trim not-empty (string/split #",\s*") set)
  367. :property-classes (some-> property-classes string/trim not-empty (string/split #",\s*") set)
  368. :property-parent-classes (some-> property-parent-classes string/trim not-empty (string/split #",\s*") set)})
  369. ;; common options
  370. :notify-user show-notification
  371. :set-ui-state state/set-state!
  372. :<read-file (fn <read-file [file] (.text (:file-object file)))
  373. ;; config file options
  374. :default-config config/config-default-content
  375. :<save-config-file (fn save-config-file [_ path content]
  376. (db-editor-handler/save-file! path content))
  377. ;; logseq file options
  378. :<save-logseq-file (fn save-logseq-file [_ path content]
  379. (db-editor-handler/save-file! path content))
  380. ;; asset file options
  381. :<read-and-copy-asset #(read-and-copy-asset repo (config/get-repo-dir repo) %1 %2 %3)
  382. ;; doc file options
  383. ;; Write to frontend first as writing to worker first is poor ux with slow streaming changes
  384. :export-file (fn export-file [conn m opts]
  385. (let [tx-reports
  386. (gp-exporter/add-file-to-db-graph conn (:file/path m) (:file/content m) opts)]
  387. (doseq [tx-report tx-reports]
  388. (db-browser/transact! repo (:tx-data tx-report) (:tx-meta tx-report)))))}
  389. {:keys [files import-state]} (gp-exporter/export-file-graph repo db-conn config-file *files options)]
  390. (log/info :import-file-graph {:msg (str "Import finished in " (/ (t/in-millis (t/interval start-time (t/now))) 1000) " seconds")})
  391. (state/set-state! :graph/importing nil)
  392. (state/set-state! :graph/importing-state nil)
  393. (validate-imported-data @db-conn import-state files)
  394. (state/pub-event! [:graph/ready (state/get-current-repo)])
  395. (finished-cb)))
  396. (defn import-file-to-db-handler
  397. "Import from a graph folder as a DB-based graph"
  398. [ev opts]
  399. (let [^js file-objs (if ev (array-seq (.-files (.-target ev))) #js [])
  400. original-graph-name (if (first file-objs)
  401. (string/replace (.-webkitRelativePath (first file-objs)) #"/.*" "")
  402. "")
  403. import-graph-fn (or (:import-graph-fn opts)
  404. (fn [user-inputs]
  405. (let [files (->> file-objs
  406. (map #(hash-map :file-object %
  407. :path (path/trim-dir-prefix original-graph-name (.-webkitRelativePath %))))
  408. (remove #(and (not (string/starts-with? (:path %) "assets/"))
  409. ;; TODO: Update this when supporting more formats as this aggressively excludes most formats
  410. (fs-util/ignored-path? original-graph-name (.-webkitRelativePath (:file-object %))))))]
  411. (if-let [config-file (first (filter #(= (:path %) "logseq/config.edn") files))]
  412. (import-file-graph files user-inputs config-file)
  413. (notification/show! "Import failed as the file 'logseq/config.edn' was not found for a Logseq graph."
  414. :error)))))]
  415. (shui/dialog-open!
  416. #(import-file-graph-dialog original-graph-name
  417. (fn [{:keys [graph-name] :as user-inputs}]
  418. (cond
  419. (repo/invalid-graph-name? graph-name)
  420. (repo/invalid-graph-name-warning)
  421. (repo-handler/graph-already-exists? graph-name)
  422. (notification/show! "Please specify another name as another graph with this name already exists!" :error)
  423. :else
  424. (import-graph-fn user-inputs)))))))
  425. (rum/defc indicator-progress < rum/reactive
  426. []
  427. (let [{:keys [total current-idx current-page]} (state/sub :graph/importing-state)
  428. left-label (if (and current-idx total (= current-idx total))
  429. [:div.flex.flex-row.font-bold "Loading ..."]
  430. [:div.flex.flex-row.font-bold
  431. (t :importing)
  432. [:div.hidden.md:flex.flex-row
  433. [:span.mr-1 ": "]
  434. [:div.text-ellipsis-wrapper {:style {:max-width 300}}
  435. current-page]]])
  436. width (js/Math.round (* (.toFixed (/ current-idx total) 2) 100))
  437. process (when (and total current-idx)
  438. (str current-idx "/" total))]
  439. [:div.p-5
  440. (ui/progress-bar-with-label width left-label process)]))
  441. (rum/defc import-indicator
  442. [importing?]
  443. (hooks/use-effect!
  444. (fn []
  445. (when (and importing? (not (shui-dialog/get-modal :import-indicator)))
  446. (shui/dialog-open! indicator-progress
  447. {:id :import-indicator
  448. :content-props
  449. {:onPointerDownOutside #(.preventDefault %)
  450. :onOpenAutoFocus #(.preventDefault %)}})))
  451. [importing?])
  452. [:<>])
  453. (rum/defc ^:large-vars/cleanup-todo importer < rum/reactive
  454. [{:keys [query-params]}]
  455. (let [support-file-based? (config/local-file-based-graph? (state/get-current-repo))
  456. importing? (state/sub :graph/importing)]
  457. [:<>
  458. (import-indicator importing?)
  459. (when-not importing?
  460. (setups/setups-container
  461. :importer
  462. [:article.flex.flex-col.items-center.importer.py-16.px-8
  463. (when-not (util/mobile?)
  464. [:section.c.text-center
  465. [:h1 (t :on-boarding/importing-title)]
  466. [:h2 (t :on-boarding/importing-desc)]])
  467. [:section.d.md:flex.flex-col
  468. [:label.action-input.flex.items-center.mx-2.my-2
  469. [:span.as-flex-center [:i (svg/logo 28)]]
  470. [:span.flex.flex-col
  471. [[:strong "SQLite"]
  472. [:small (t :on-boarding/importing-sqlite-desc)]]]
  473. [:input.absolute.hidden
  474. {:id "import-sqlite-db"
  475. :type "file"
  476. :on-change (fn [e]
  477. (shui/dialog-open!
  478. #(set-graph-name-dialog e {:sqlite? true})))}]]
  479. (when-not (util/mobile?)
  480. [:label.action-input.flex.items-center.mx-2.my-2
  481. [:span.as-flex-center [:i (svg/logo 28)]]
  482. [:span.flex.flex-col
  483. [[:strong "File to DB graph"]
  484. [:small "Import a file-based Logseq graph folder into a new DB graph"]]]
  485. ;; Test form style changes
  486. #_[:a.button {:on-click #(import-file-to-db-handler nil {:import-graph-fn js/alert})} "Open"]
  487. [:input.absolute.hidden
  488. {:id "import-file-graph"
  489. :type "file"
  490. :webkitdirectory "true"
  491. :on-change (debounce (fn [e]
  492. (import-file-to-db-handler e {}))
  493. 1000)}]])
  494. [:label.action-input.flex.items-center.mx-2.my-2
  495. [:span.as-flex-center [:i (svg/logo 28)]]
  496. [:span.flex.flex-col
  497. [[:strong "Debug Transit"]
  498. [:small "Import debug transit file into a new DB graph"]]]
  499. [:input.absolute.hidden
  500. {:id "import-debug-transit"
  501. :type "file"
  502. :on-change (fn [e]
  503. (shui/dialog-open!
  504. #(set-graph-name-dialog e {:debug-transit? true})))}]]
  505. [:label.action-input.flex.items-center.mx-2.my-2
  506. [:span.as-flex-center [:i (svg/logo 28)]]
  507. [:span.flex.flex-col
  508. [[:strong "EDN to DB graph"]
  509. [:small "Import a DB graph's EDN export into a new DB graph"]]]
  510. [:input.absolute.hidden
  511. {:id "import-db-edn"
  512. :type "file"
  513. :on-change (fn [e]
  514. (shui/dialog-open!
  515. #(set-graph-name-dialog e {:db-edn? true})))}]]
  516. (when (and (util/electron?) support-file-based?)
  517. [:label.action-input.flex.items-center.mx-2.my-2
  518. [:span.as-flex-center [:i (svg/logo 28)]]
  519. [:span.flex.flex-col
  520. [[:strong "EDN / JSON to plain text graph"]
  521. [:small (t :on-boarding/importing-lsq-desc)]]]
  522. [:input.absolute.hidden
  523. {:id "import-lsq"
  524. :type "file"
  525. :on-change lsq-import-handler}]])
  526. (when (and (util/electron?) support-file-based?)
  527. [:label.action-input.flex.items-center.mx-2.my-2
  528. [:span.as-flex-center [:i (svg/roam-research 28)]]
  529. [:div.flex.flex-col
  530. [[:strong "RoamResearch"]
  531. [:small (t :on-boarding/importing-roam-desc)]]]
  532. [:input.absolute.hidden
  533. {:id "import-roam"
  534. :type "file"
  535. :on-change roam-import-handler}]])
  536. (when (and (util/electron?) support-file-based?)
  537. [:label.action-input.flex.items-center.mx-2.my-2
  538. [:span.as-flex-center.ml-1 (ui/icon "sitemap" {:size 26})]
  539. [:span.flex.flex-col
  540. [[:strong "OPML"]
  541. [:small (t :on-boarding/importing-opml-desc)]]]
  542. [:input.absolute.hidden
  543. {:id "import-opml"
  544. :type "file"
  545. :on-change opml-import-handler}]])]
  546. (when (= "picker" (:from query-params))
  547. [:section.e
  548. [:a.button {:on-click #(route-handler/redirect-to-home!)} "Skip"]])]))]))