imports.cljs 29 KB

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