settings.cljs 60 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396
  1. (ns frontend.components.settings
  2. (:require [clojure.string :as string]
  3. [clojure.walk :as walk]
  4. [electron.ipc :as ipc]
  5. [frontend.colors :as colors]
  6. [frontend.common.missionary :as c.m]
  7. [frontend.components.assets :as assets]
  8. [frontend.components.file-sync :as fs]
  9. [frontend.components.shortcut :as shortcut]
  10. [frontend.components.svg :as svg]
  11. [frontend.config :as config]
  12. [frontend.context.i18n :refer [t]]
  13. [frontend.date :as date]
  14. [frontend.db :as db]
  15. [frontend.dicts :as dicts]
  16. [frontend.handler.config :as config-handler]
  17. [frontend.handler.db-based.rtc :as rtc-handler]
  18. [frontend.handler.db-based.vector-search-flows :as vector-search-flows]
  19. [frontend.handler.file-sync :as file-sync-handler]
  20. [frontend.handler.global-config :as global-config-handler]
  21. [frontend.handler.notification :as notification]
  22. [frontend.handler.plugin :as plugin-handler]
  23. [frontend.handler.property :as property-handler]
  24. [frontend.handler.route :as route-handler]
  25. [frontend.handler.ui :as ui-handler]
  26. [frontend.handler.user :as user-handler]
  27. [frontend.mobile.util :as mobile-util]
  28. [frontend.modules.instrumentation.core :as instrument]
  29. [frontend.modules.shortcut.data-helper :as shortcut-helper]
  30. [frontend.persist-db.browser :as db-browser]
  31. [frontend.spec.storage :as storage-spec]
  32. [frontend.state :as state]
  33. [frontend.storage :as storage]
  34. [frontend.ui :as ui]
  35. [frontend.util :refer [classnames web-platform?] :as util]
  36. [frontend.version :as fv]
  37. [goog.object :as gobj]
  38. [goog.string :as gstring]
  39. [logseq.db :as ldb]
  40. [logseq.shui.hooks :as hooks]
  41. [logseq.shui.ui :as shui]
  42. [missionary.core :as m]
  43. [promesa.core :as p]
  44. [reitit.frontend.easy :as rfe]
  45. [rum.core :as rum]))
  46. (defn toggle
  47. [label-for name state on-toggle & [detail-text]]
  48. [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center
  49. [:label.block.text-sm.font-medium.leading-5.opacity-70
  50. {:for label-for}
  51. name]
  52. [:div.rounded-md.sm:max-w-tss.sm:col-span-2
  53. [:div.rounded-md {:style {:display "flex" :gap "1rem" :align-items "center"}}
  54. (ui/toggle state on-toggle true)
  55. detail-text]]])
  56. (rum/defcs app-updater < rum/reactive
  57. [state version]
  58. (let [update-pending? (state/sub :electron/updater-pending?)
  59. {:keys [type payload]} (state/sub :electron/updater)]
  60. [:span.cp__settings-app-updater
  61. [:div.ctls.flex.items-center
  62. [:div.mt-1.sm:mt-0.sm:col-span-2.flex.gap-4.items-center.flex-wrap
  63. [:div (cond
  64. (mobile-util/native-android?)
  65. (ui/button
  66. (t :settings-page/check-for-updates)
  67. :class "text-sm mr-1"
  68. :href "https://github.com/logseq/logseq/releases")
  69. (mobile-util/native-ios?)
  70. (ui/button
  71. (t :settings-page/check-for-updates)
  72. :class "text-sm mr-1"
  73. :href "https://apps.apple.com/app/logseq/id1601013908")
  74. (util/electron?)
  75. (ui/button
  76. (if update-pending? (t :settings-page/checking) (t :settings-page/check-for-updates))
  77. :class "text-sm mr-1"
  78. :disabled update-pending?
  79. :on-click #(js/window.apis.checkForUpdates false))
  80. :else
  81. nil)]
  82. [:div.text-sm.cursor
  83. {:title (str (t :settings-page/revision) config/revision)
  84. :on-click (fn []
  85. (notification/show! [:div "Current Revision: "
  86. [:a {:target "_blank"
  87. :href (str "https://github.com/logseq/logseq/commit/" config/revision)}
  88. config/revision]]
  89. :info
  90. false))}
  91. version]
  92. [:a.text-sm.fade-link.underline.inline
  93. {:target "_blank"
  94. :href "https://docs.logseq.com/#/page/changelog"}
  95. (t :settings-page/changelog)]]]
  96. (when-not (or update-pending?
  97. (string/blank? type))
  98. [:div.update-state.text-sm
  99. (case type
  100. "update-not-available"
  101. [:p (t :settings-page/app-updated)]
  102. "update-available"
  103. (let [{:keys [name url]} payload]
  104. [:p (str (t :settings-page/update-available))
  105. [:a.link
  106. {:on-click
  107. (fn [e]
  108. (js/window.apis.openExternal url)
  109. (util/stop e))}
  110. svg/external-link name " 🎉"]])
  111. "error"
  112. [:p (t :settings-page/update-error-1) [:br] (t :settings-page/update-error-2)
  113. [:a.link
  114. {:on-click
  115. (fn [e]
  116. (js/window.apis.openExternal "https://github.com/logseq/logseq/releases")
  117. (util/stop e))}
  118. svg/external-link " release channel"]])])]))
  119. (rum/defc outdenting-hint
  120. []
  121. [:div.ui__modal-panel
  122. {:style {:box-shadow "0 4px 20px 4px rgba(0, 20, 60, .1), 0 4px 80px -8px rgba(0, 20, 60, .2)"}}
  123. [:div {:style {:margin "12px" :max-width "500px"}}
  124. [:p.text-sm
  125. (t :settings-page/preferred-outdenting-tip)
  126. [:a.text-sm
  127. {:target "_blank" :href "https://discuss.logseq.com/t/whats-your-preferred-outdent-behavior-the-direct-one-or-the-logical-one/978"}
  128. (t :settings-page/preferred-outdenting-tip-more)]]
  129. [:img {:src "https://discuss.logseq.com/uploads/default/original/1X/e8ea82f63a5e01f6d21b5da827927f538f3277b9.gif"
  130. :width 500
  131. :height 500}]]])
  132. (rum/defc auto-expand-hint
  133. []
  134. [:div.ui__modal-panel
  135. {:style {:box-shadow "0 4px 20px 4px rgba(0, 20, 60, .1), 0 4px 80px -8px rgba(0, 20, 60, .2)"}}
  136. [:div {:style {:margin "12px" :max-width "500px"}}
  137. [:p.text-sm
  138. (t :settings-page/auto-expand-block-refs-tip)]
  139. [:img {:src "https://user-images.githubusercontent.com/28241963/225818326-118deda9-9d1e-477d-b0ce-771ca0bcd976.gif"
  140. :width 500
  141. :height 500}]]])
  142. (defn row-with-button-action
  143. [{:keys [left-label description action button-label href on-click desc -for stretch]}]
  144. [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4
  145. {:class "sm:items-start"}
  146. ;; left column
  147. [:div.flex.flex-col
  148. [:label.block.text-sm.font-medium.leading-5.opacity-70
  149. {:for -for}
  150. left-label]
  151. (when description
  152. [:div.text-xs.text-gray-10 description])]
  153. ;; right column
  154. [:div.mt-1.sm:mt-0.sm:col-span-2.flex.items-center
  155. {:style {:display "flex" :gap "0.5rem" :align-items "center"}}
  156. [:div {:style (when stretch {:width "100%"})}
  157. (if action action (shui/button
  158. {:as-child (not (string/blank? href))
  159. :size :sm
  160. :on-click on-click}
  161. (if (string/blank? href) button-label
  162. (shui/link {:href href} button-label))))]
  163. (when-not (or (util/mobile?)
  164. (mobile-util/native-platform?))
  165. [:div.text-sm.flex desc])]])
  166. (defn edit-config-edn []
  167. (row-with-button-action
  168. {:left-label (t :settings-page/custom-configuration)
  169. :button-label (t :settings-page/edit-config-edn)
  170. :href (rfe/href :file {:path (config/get-repo-config-path)})
  171. :on-click ui-handler/toggle-settings-modal!
  172. :-for "config_edn"}))
  173. (defn edit-global-config-edn []
  174. (row-with-button-action
  175. {:left-label (t :settings-page/custom-global-configuration)
  176. :button-label (t :settings-page/edit-global-config-edn)
  177. :href (rfe/href :file {:path (global-config-handler/global-config-path)})
  178. :on-click ui-handler/toggle-settings-modal!
  179. :-for "global_config_edn"}))
  180. (defn edit-custom-css []
  181. (row-with-button-action
  182. {:left-label (t :settings-page/custom-theme)
  183. :button-label (t :settings-page/edit-custom-css)
  184. :href (rfe/href :file {:path (config/get-custom-css-path)})
  185. :on-click ui-handler/toggle-settings-modal!
  186. :-for "customize_css"}))
  187. (defn edit-export-css []
  188. (row-with-button-action
  189. {:left-label (t :settings-page/export-theme)
  190. :button-label (t :settings-page/edit-export-css)
  191. :href (rfe/href :file {:path (config/get-export-css-path)})
  192. :on-click ui-handler/toggle-settings-modal!
  193. :-for "export_css"}))
  194. (defn show-brackets-row [t show-brackets?]
  195. [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center
  196. [:label.block.text-sm.font-medium.leading-5.opacity-70
  197. {:for "show_brackets"}
  198. (t :settings-page/show-brackets)]
  199. [:div
  200. [:div.rounded-md.sm:max-w-xs
  201. (ui/toggle show-brackets?
  202. config-handler/toggle-ui-show-brackets!
  203. true)]]
  204. (when (not (or (util/mobile?) (mobile-util/native-platform?)))
  205. [:div {:style {:text-align "right"}}
  206. (ui/render-keyboard-shortcut (shortcut-helper/gen-shortcut-seq :ui/toggle-brackets))])])
  207. (defn toggle-wide-mode-row [t wide-mode?]
  208. [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center
  209. [:label.block.text-sm.font-medium.leading-5.opacity-70
  210. {:for "wide_mode"}
  211. (t :settings-page/wide-mode)]
  212. [:div
  213. [:div.rounded-md.sm:max-w-xs
  214. (ui/toggle wide-mode?
  215. ui-handler/toggle-wide-mode!
  216. true)]]
  217. (when (not (or (util/mobile?) (mobile-util/native-platform?)))
  218. [:div {:style {:text-align "right"}}
  219. (ui/render-keyboard-shortcut (shortcut-helper/gen-shortcut-seq :ui/toggle-wide-mode))])])
  220. (defn editor-font-family-row [t font]
  221. [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4
  222. [:label.block.text-sm.font-medium.leading-5.opacity-70
  223. {:for "font_family"}
  224. (t :settings-page/editor-font)]
  225. [:div.col-span-2.flex.gap-2
  226. (for [t [:default :serif :mono]
  227. :let [t (name t)
  228. tt (string/capitalize t)
  229. active? (= font t)]]
  230. (shui/button
  231. {:variant :secondary
  232. :class (when active? " border-primary border-[2px]")
  233. :style {:width "4.4rem"}
  234. :on-click #(state/set-editor-font! t)}
  235. [:span.flex.flex-col
  236. {:class (str "ls-font-" t)}
  237. [:strong "Ag"]
  238. [:small tt]]))]])
  239. (rum/defcs switch-spell-check-row < rum/reactive
  240. [state t]
  241. (let [enabled? (state/sub [:electron/user-cfgs :spell-check])]
  242. [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center
  243. [:label.block.text-sm.font-medium.leading-5.opacity-70
  244. (t :settings-page/spell-checker)]
  245. [:div
  246. [:div.rounded-md.sm:max-w-xs
  247. (ui/toggle
  248. enabled?
  249. (fn []
  250. (state/set-state! [:electron/user-cfgs :spell-check] (not enabled?))
  251. (p/then (ipc/ipc :userAppCfgs :spell-check (not enabled?))
  252. #(when (js/confirm (t :relaunch-confirm-to-work))
  253. (js/logseq.api.relaunch))))
  254. true)]]]))
  255. (rum/defcs switch-git-auto-commit-row < rum/reactive
  256. [state t]
  257. (let [enabled? (state/get-git-auto-commit-enabled?)]
  258. [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center
  259. [:label.block.text-sm.font-medium.leading-5.opacity-70
  260. (t :settings-page/git-switcher-label)]
  261. [:div
  262. [:div.rounded-md.sm:max-w-xs
  263. (ui/toggle
  264. enabled?
  265. (fn []
  266. (state/set-state! [:electron/user-cfgs :git/disable-auto-commit?] enabled?)
  267. (p/do!
  268. (ipc/ipc :userAppCfgs :git/disable-auto-commit? enabled?)
  269. (ipc/ipc :setGitAutoCommit)))
  270. true)]]]))
  271. (rum/defcs switch-git-commit-on-close-row < rum/reactive
  272. [state t]
  273. (let [enabled? (state/get-git-commit-on-close-enabled?)]
  274. [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center
  275. [:label.block.text-sm.font-medium.leading-5.opacity-70
  276. (t :settings-page/git-commit-on-close)]
  277. [:div
  278. [:div.rounded-md.sm:max-w-xs
  279. (ui/toggle
  280. enabled?
  281. (fn []
  282. (state/set-state! [:electron/user-cfgs :git/commit-on-close?] (not enabled?))
  283. (ipc/ipc :userAppCfgs :git/commit-on-close? (not enabled?)))
  284. true)]]]))
  285. (rum/defcs git-auto-commit-seconds < rum/reactive
  286. [state t]
  287. (let [secs (or (state/sub [:electron/user-cfgs :git/auto-commit-seconds]) 60)]
  288. [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center
  289. [:label.block.text-sm.font-medium.leading-5.opacity-70
  290. (t :settings-page/git-commit-delay)]
  291. [:div.mt-1.sm:mt-0.sm:col-span-2
  292. [:div.max-w-lg.rounded-md.sm:max-w-xs
  293. [:input#home-default-page.form-input.is-small.transition.duration-150.ease-in-out
  294. {:default-value secs
  295. :on-blur (fn [event]
  296. (let [value (-> (util/evalue event)
  297. util/safe-parse-int)]
  298. (if (and (number? value)
  299. (< 0 value (inc 86400)))
  300. (p/do!
  301. (state/set-state! [:electron/user-cfgs :git/auto-commit-seconds] value)
  302. (ipc/ipc :userAppCfgs :git/auto-commit-seconds value)
  303. (ipc/ipc :setGitAutoCommit))
  304. (when-let [elem (gobj/get event "target")]
  305. (notification/show!
  306. [:div "Invalid value! Must be a number between 1 and 86400"]
  307. :warning true)
  308. (gobj/set elem "value" secs)))))}]]]]))
  309. (rum/defc app-auto-update-row < rum/reactive [t]
  310. (let [enabled? (state/sub [:electron/user-cfgs :auto-update])
  311. enabled? (if (nil? enabled?) true enabled?)]
  312. (toggle "usage-diagnostics"
  313. (t :settings-page/auto-updater)
  314. enabled?
  315. #((state/set-state! [:electron/user-cfgs :auto-update] (not enabled?))
  316. (ipc/ipc :userAppCfgs :auto-update (not enabled?))))))
  317. (defn language-row [t preferred-language]
  318. (let [on-change (fn [e]
  319. (let [lang-code (util/evalue e)]
  320. (state/set-preferred-language! lang-code)
  321. (ui-handler/re-render-root!)))
  322. action [:select.form-select.is-small {:value preferred-language
  323. :on-change on-change}
  324. (for [language dicts/languages]
  325. (let [lang-code (name (:value language))
  326. lang-label (:label language)]
  327. [:option {:key lang-code :value lang-code} lang-label]))]]
  328. (row-with-button-action {:left-label (t :language)
  329. :-for "preferred_language"
  330. :action action})))
  331. (rum/defc theme-modes-row < rum/reactive
  332. [t]
  333. (let [theme (state/sub :ui/theme)
  334. dark? (= "dark" theme)
  335. system-theme? (state/sub :ui/system-theme?)
  336. switch-theme (if dark? "light" "dark")
  337. color-accent (state/sub :ui/radix-color)
  338. pick-theme [:ul.cp__theme-modes-options
  339. [:li {:on-click (partial state/use-theme-mode! "light")
  340. :class (classnames [{:active (and (not system-theme?) (not dark?))}])} [:i.mode-light {:class (when color-accent "radix")}] [:strong (t :settings-page/theme-light)]]
  341. [:li {:on-click (partial state/use-theme-mode! "dark")
  342. :class (classnames [{:active (and (not system-theme?) dark?)}])} [:i.mode-dark {:class (when color-accent "radix")}] [:strong (t :settings-page/theme-dark)]]
  343. [:li {:on-click (partial state/use-theme-mode! "system")
  344. :class (classnames [{:active system-theme?}])} [:i.mode-system {:class (when color-accent "radix")}] [:strong (t :settings-page/theme-system)]]]]
  345. (row-with-button-action {:left-label (t :right-side-bar/switch-theme (string/capitalize switch-theme))
  346. :-for "toggle_theme"
  347. :action pick-theme
  348. :desc (ui/render-keyboard-shortcut (shortcut-helper/gen-shortcut-seq :ui/toggle-theme))})))
  349. (rum/defc accent-color-row < rum/reactive
  350. [_in-modal?]
  351. (let [color-accent (state/sub :ui/radix-color)
  352. pick-theme [:div.cp__accent-colors-list-wrap
  353. {:class (if _in-modal? "as-modal-picker" "")}
  354. (for [color (concat [:none :logseq] colors/color-list)
  355. :let [active? (= color color-accent)
  356. none? (= color :none)]]
  357. [:div.flex.items-center
  358. (ui/tooltip
  359. (shui/button
  360. {:class "w-5 h-5 px-1 rounded-full flex justify-center items-center transition ease-in duration-100 hover:cursor-pointer hover:opacity-100"
  361. :auto-focus (and _in-modal? active?)
  362. :style {:background-color (colors/variable color :09)
  363. :outline-color (colors/variable color (if active? :07 :06))
  364. :outline-width (if active? "4px" "1px")
  365. :outline-style :solid
  366. :opacity (if active? 1 0.5)}
  367. :variant :text
  368. :on-click (fn [_e] (state/set-color-accent! color))}
  369. [:strong
  370. {:class (if none? "h-0.5 w-full bg-red-700"
  371. "w-2 h-2 rounded-full transition ease-in duration-100")
  372. :style {:background-color (if-not none? (str "var(--rx-" (name color) "-07)") "")
  373. :opacity (if (or none? active?) 1 0)}}])
  374. (case color
  375. :none [:p {:style {:max-width "300px"}}
  376. "Cancel accent color. This is currently in beta stage and mainly used for compatibility with custom themes."]
  377. :logseq "Logseq classical color"
  378. (str (name color) " color")))])]]
  379. [:div
  380. (row-with-button-action
  381. {:left-label (t :settings-page/accent-color)
  382. :-for "toggle_radix_theme"
  383. :desc (when-not _in-modal?
  384. [:span.pl-6 (ui/render-keyboard-shortcut
  385. (shortcut-helper/gen-shortcut-seq :ui/customize-appearance))])
  386. :stretch (boolean _in-modal?)
  387. :action pick-theme})
  388. [:div.text-sm.opacity-50.mt-1
  389. (t :settings-page/accent-color-alert)]]))
  390. (rum/defc appearance < rum/reactive
  391. []
  392. [:div#appearance_settings.cp__settings-appearance-modal-inner.w-96.p-4.shadow-xl
  393. (theme-modes-row t)
  394. (editor-font-family-row t (state/sub :ui/editor-font))
  395. (toggle-wide-mode-row t (state/sub :ui/wide-mode?))
  396. (show-brackets-row t (state/show-brackets?))
  397. (accent-color-row true)])
  398. (defn date-format-row [t preferred-date-format]
  399. [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center
  400. [:label.block.text-sm.font-medium.leading-5.opacity-70
  401. {:for "custom_date_format"}
  402. (t :settings-page/custom-date-format)
  403. (when-not (config/db-based-graph? (state/get-current-repo))
  404. (ui/tooltip [:span.flex.px-2 (svg/info)]
  405. [:span (t :settings-page/custom-date-format-warning)]))]
  406. [:div.mt-1.sm:mt-0.sm:col-span-2
  407. [:div.max-w-lg.rounded-md
  408. [:select.form-select.is-small
  409. {:value preferred-date-format
  410. :on-change (fn [e]
  411. (let [repo (state/get-current-repo)
  412. format (util/evalue e)
  413. db-based? (config/db-based-graph? repo)]
  414. (when-not (string/blank? format)
  415. (if db-based?
  416. (p/do!
  417. (property-handler/set-block-property! repo
  418. :logseq.class/Journal
  419. :logseq.property.journal/title-format
  420. format)
  421. (notification/show! "Please refresh the app for this change to take effect"))
  422. (do
  423. (config-handler/set-config! :journal/page-title-format format)
  424. (notification/show!
  425. [:div (t :settings-page/custom-date-format-notification)]
  426. :warning false)))
  427. (shui/dialog-close-all!)
  428. (when-not db-based? (route-handler/redirect! {:to :graphs})))))}
  429. (for [format (sort (date/journal-title-formatters))]
  430. [:option {:key format} format])]]]])
  431. (defn workflow-row [t preferred-workflow]
  432. [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center
  433. [:label.block.text-sm.font-medium.leading-5.opacity-70
  434. {:for "preferred_workflow"}
  435. (t :settings-page/preferred-workflow)]
  436. [:div.mt-1.sm:mt-0.sm:col-span-2
  437. [:div.max-w-lg.rounded-md
  438. [:select.form-select.is-small
  439. {:value (name preferred-workflow)
  440. :on-change (fn [e]
  441. (-> (util/evalue e)
  442. string/lower-case
  443. keyword
  444. (#(if (= % :now) :now :todo))
  445. user-handler/set-preferred-workflow!))}
  446. (for [workflow [:now :todo]]
  447. [:option {:key (name workflow) :value (name workflow)}
  448. (if (= workflow :now) "NOW/LATER" "TODO/DOING")])]]]])
  449. (defn outdenting-row [t logical-outdenting?]
  450. (toggle "preferred_outdenting"
  451. [(t :settings-page/preferred-outdenting)
  452. (ui/tooltip [:span.flex.px-2 (svg/info)]
  453. (outdenting-hint) {:content-props {:side "right"}})]
  454. logical-outdenting?
  455. config-handler/toggle-logical-outdenting!))
  456. (defn showing-full-blocks [t show-full-blocks?]
  457. (toggle "show_full_blocks"
  458. (t :settings-page/show-full-blocks)
  459. show-full-blocks?
  460. config-handler/toggle-show-full-blocks!))
  461. (defn preferred-pasting-file [t preferred-pasting-file?]
  462. (toggle "preferred_pasting_file"
  463. [(t :settings-page/preferred-pasting-file)
  464. (ui/tooltip [:span.flex.px-2 (svg/info)]
  465. [:span.block.w-64 (t :settings-page/preferred-pasting-file-hint)])]
  466. preferred-pasting-file?
  467. config-handler/toggle-preferred-pasting-file!))
  468. (defn auto-expand-row [t auto-expand-block-refs?]
  469. (toggle "auto_expand_block_refs"
  470. [(t :settings-page/auto-expand-block-refs)
  471. (ui/tooltip [:span.flex.px-2 (svg/info)]
  472. (auto-expand-hint))]
  473. auto-expand-block-refs?
  474. config-handler/toggle-auto-expand-block-refs!))
  475. (defn tooltip-row [t enable-tooltip?]
  476. (toggle "enable_tooltip"
  477. (t :settings-page/enable-tooltip)
  478. enable-tooltip?
  479. (fn []
  480. (config-handler/toggle-ui-enable-tooltip!))))
  481. (defn shortcut-tooltip-row [t enable-shortcut-tooltip?]
  482. (toggle "enable_tooltip"
  483. (t :settings-page/enable-shortcut-tooltip)
  484. enable-shortcut-tooltip?
  485. (fn []
  486. (state/toggle-shortcut-tooltip!))))
  487. (defn timetracking-row [t enable-timetracking?]
  488. (toggle "enable_timetracking"
  489. (t :settings-page/enable-timetracking)
  490. enable-timetracking?
  491. #(let [value (not enable-timetracking?)]
  492. (config-handler/set-config! :feature/enable-timetracking? value))))
  493. (defn update-home-page
  494. [event]
  495. (let [value (util/evalue event)]
  496. (cond
  497. (string/blank? value)
  498. (let [home (get (state/get-config) :default-home {})
  499. new-home (dissoc home :page)]
  500. (p/do!
  501. (config-handler/set-config! :default-home new-home)
  502. (config-handler/set-config! :feature/enable-journals? true)
  503. (notification/show! "Journals enabled" :success)))
  504. ;; FIXME: home page should be db id instead of page name
  505. (ldb/get-page (db/get-db) value)
  506. (let [home (get (state/get-config) :default-home {})
  507. new-home (assoc home :page value)]
  508. (config-handler/set-config! :default-home new-home)
  509. (notification/show! "Home default page updated successfully!" :success))
  510. :else
  511. (notification/show! (str "The page \"" value "\" doesn't exist yet. Please create that page first, and then try again.") :warning))))
  512. (defn journal-row [enable-journals?]
  513. (toggle "enable_journals"
  514. (t :settings-page/enable-journals)
  515. enable-journals?
  516. (fn []
  517. (let [value (not enable-journals?)]
  518. (config-handler/set-config! :feature/enable-journals? value)))))
  519. (defn enable-all-pages-public-row [t enable-all-pages-public?]
  520. (toggle "all pages public"
  521. (t :settings-page/enable-all-pages-public)
  522. enable-all-pages-public?
  523. (fn []
  524. (let [value (not enable-all-pages-public?)]
  525. (config-handler/set-config! :publishing/all-pages-public? value)))))
  526. (defn zotero-settings-row []
  527. [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center
  528. [:label.block.text-sm.font-medium.leading-5.opacity-70
  529. {:for "zotero_settings"}
  530. "Zotero"]
  531. [:div.mt-1.sm:mt-0.sm:col-span-2
  532. [:div
  533. (ui/button
  534. (t :settings)
  535. :class "text-sm"
  536. :style {:margin-top "0px"}
  537. :on-click
  538. (fn []
  539. (state/close-settings!)
  540. (route-handler/redirect! {:to :zotero-setting})))]]])
  541. (defn auto-push-row [_t current-repo enable-git-auto-push?]
  542. (when (and current-repo (string/starts-with? current-repo "https://"))
  543. (toggle "enable_git_auto_push"
  544. "Enable Git auto push"
  545. enable-git-auto-push?
  546. (fn []
  547. (let [value (not enable-git-auto-push?)]
  548. (config-handler/set-config! :git-auto-push value))))))
  549. (defn usage-diagnostics-row [t instrument-disabled?]
  550. (toggle "usage-diagnostics"
  551. (t :settings-page/disable-sentry)
  552. (not instrument-disabled?)
  553. (fn [] (instrument/disable-instrument
  554. (not instrument-disabled?)))
  555. [:span.text-sm.opacity-50 (t :settings-page/disable-sentry-desc)]))
  556. ;; (defn clear-cache-row [t]
  557. ;; (row-with-button-action {:left-label (t :settings-page/clear-cache)
  558. ;; :button-label (t :settings-page/clear)
  559. ;; :on-click #(state/pub-event! [:graph/clear-cache!])
  560. ;; :-for "clear_cache"}))
  561. (defn version-row [t version]
  562. (row-with-button-action {:left-label (t :settings-page/current-version)
  563. :action (app-updater version)
  564. :-for "current-version"}))
  565. (defn developer-mode-row [t developer-mode?]
  566. (toggle "developer_mode"
  567. (t :settings-page/developer-mode)
  568. developer-mode?
  569. (fn []
  570. (let [mode (not developer-mode?)]
  571. (state/set-developer-mode! mode)))
  572. [:div.text-sm.opacity-50 (t :settings-page/developer-mode-desc)]))
  573. (rum/defc plugin-enabled-switcher
  574. [t]
  575. (let [value (state/lsp-enabled?-or-theme)
  576. [on? set-on?] (rum/use-state value)
  577. on-toggle #(let [v (not on?)]
  578. (set-on? v)
  579. (storage/set ::storage-spec/lsp-core-enabled v))]
  580. [:div.flex.items-center.gap-2
  581. (ui/toggle on? on-toggle true)
  582. (when (util/electron?)
  583. (when (not= (boolean value) on?)
  584. (ui/button (t :plugin/restart)
  585. :on-click #(js/logseq.api.relaunch)
  586. :small? true :intent "logseq")))]))
  587. (rum/defc http-server-enabled-switcher
  588. [t]
  589. (let [[value _] (rum/use-state (boolean (storage/get ::storage-spec/http-server-enabled)))
  590. [on? set-on?] (rum/use-state value)
  591. on-toggle #(let [v (not on?)]
  592. (set-on? v)
  593. (storage/set ::storage-spec/http-server-enabled v))]
  594. [:div.flex.items-center.gap-2
  595. (ui/toggle on? on-toggle true)
  596. (when (not= (boolean value) on?)
  597. (ui/button (t :plugin/restart)
  598. :on-click #(js/logseq.api.relaunch)
  599. :small? true :intent "logseq"))]))
  600. (rum/defc flashcards-enabled-switcher
  601. [enable-flashcards?]
  602. (ui/toggle enable-flashcards?
  603. (fn []
  604. (let [value (not enable-flashcards?)]
  605. (config-handler/set-config! :feature/enable-flashcards? value)))
  606. true))
  607. (rum/defc user-proxy-settings
  608. [{:keys [type protocol host port] :as agent-opts}]
  609. (ui/button [:span.flex.items-center
  610. [:span.pr-1
  611. (case type
  612. "system" "System Default"
  613. "direct" "Direct"
  614. (and protocol host port (str protocol "://" host ":" port)))]
  615. (ui/icon "edit")]
  616. :class "text-sm"
  617. :on-click #(state/pub-event! [:go/proxy-settings agent-opts])))
  618. (defn plugin-system-switcher-row []
  619. (row-with-button-action
  620. {:left-label (t :settings-page/plugin-system)
  621. :action (plugin-enabled-switcher t)}))
  622. (defn http-server-switcher-row []
  623. (row-with-button-action
  624. {:left-label "HTTP APIs server"
  625. :action (http-server-enabled-switcher t)}))
  626. (defn flashcards-switcher-row [enable-flashcards?]
  627. (row-with-button-action
  628. {:left-label (t :settings-page/enable-flashcards)
  629. :action (flashcards-enabled-switcher enable-flashcards?)}))
  630. (defn https-user-agent-row [agent-opts]
  631. (row-with-button-action
  632. {:left-label (t :settings-page/network-proxy)
  633. :action (user-proxy-settings agent-opts)}))
  634. (rum/defcs auto-chmod-row < rum/reactive
  635. [state t]
  636. (let [enabled? (if (= nil (state/sub [:electron/user-cfgs :feature/enable-automatic-chmod?]))
  637. true
  638. (state/sub [:electron/user-cfgs :feature/enable-automatic-chmod?]))]
  639. (toggle
  640. "automatic-chmod"
  641. (t :settings-page/auto-chmod)
  642. enabled?
  643. #(do
  644. (state/set-state! [:electron/user-cfgs :feature/enable-automatic-chmod?] (not enabled?))
  645. (ipc/ipc :userAppCfgs :feature/enable-automatic-chmod? (not enabled?)))
  646. [:span.text-sm.opacity-50 (t :settings-page/auto-chmod-desc)])))
  647. (rum/defcs native-titlebar-row < rum/reactive
  648. [state t]
  649. (let [enabled? (state/sub [:electron/user-cfgs :window/native-titlebar?])]
  650. (toggle
  651. "native-titlebar"
  652. (t :settings-page/native-titlebar)
  653. enabled?
  654. #(when (js/confirm (t :relaunch-confirm-to-work))
  655. (state/set-state! [:electron/user-cfgs :window/native-titlebar?] (not enabled?))
  656. (ipc/ipc :userAppCfgs :window/native-titlebar? (not enabled?))
  657. (js/logseq.api.relaunch))
  658. [:span.text-sm.opacity-50 (t :settings-page/native-titlebar-desc)])))
  659. (rum/defcs settings-general < rum/reactive
  660. [_state current-repo]
  661. (let [preferred-language (state/sub [:preferred-language])
  662. show-radix-themes? true
  663. editor-font (state/sub :ui/editor-font)]
  664. [:div.panel-wrap.is-general
  665. (version-row t fv/version)
  666. (language-row t preferred-language)
  667. (theme-modes-row t)
  668. (editor-font-family-row t editor-font)
  669. (when (and (util/electron?) (not util/mac?)) (native-titlebar-row t))
  670. (when show-radix-themes? (accent-color-row false))
  671. (when (config/global-config-enabled?) (edit-global-config-edn))
  672. (when current-repo (edit-config-edn))
  673. (when current-repo (edit-custom-css))
  674. (when current-repo (edit-export-css))]))
  675. (defn file-format-row [t preferred-format]
  676. [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center
  677. [:label.block.text-sm.font-medium.leading-5.opacity-70
  678. {:for "preferred_format"}
  679. (t :settings-page/preferred-file-format)]
  680. [:div.mt-1.sm:mt-0.sm:col-span-2
  681. [:div.max-w-lg.rounded-md
  682. [:select.form-select.is-small
  683. {:value (name preferred-format)
  684. :on-change (fn [e]
  685. (let [format (-> (util/evalue e)
  686. (string/lower-case)
  687. keyword)]
  688. (user-handler/set-preferred-format! format)))}
  689. (for [format (map name [:org :markdown])]
  690. [:option {:key format :value format} (string/capitalize format)])]]]])
  691. (rum/defcs settings-editor < rum/reactive
  692. [_state current-repo]
  693. (let [preferred-format (state/get-preferred-format)
  694. preferred-date-format (state/get-date-formatter)
  695. preferred-workflow (state/get-preferred-workflow)
  696. enable-timetracking? (state/enable-timetracking?)
  697. enable-all-pages-public? (state/all-pages-public?)
  698. logical-outdenting? (state/logical-outdenting?)
  699. show-full-blocks? (state/show-full-blocks?)
  700. preferred-pasting-file? (state/preferred-pasting-file?)
  701. auto-expand-block-refs? (state/auto-expand-block-refs?)
  702. enable-tooltip? (state/enable-tooltip?)
  703. enable-shortcut-tooltip? (state/sub :ui/shortcut-tooltip?)
  704. show-brackets? (state/show-brackets?)
  705. wide-mode? (state/sub :ui/wide-mode?)
  706. enable-git-auto-push? (state/enable-git-auto-push? current-repo)
  707. db-graph? (config/db-based-graph? (state/get-current-repo))]
  708. [:div.panel-wrap.is-editor
  709. (when-not db-graph?
  710. (file-format-row t preferred-format))
  711. (date-format-row t preferred-date-format)
  712. (when-not db-graph?
  713. (workflow-row t preferred-workflow))
  714. (show-brackets-row t show-brackets?)
  715. (toggle-wide-mode-row t wide-mode?)
  716. (when (util/electron?) (switch-spell-check-row t))
  717. (outdenting-row t logical-outdenting?)
  718. (showing-full-blocks t show-full-blocks?)
  719. (preferred-pasting-file t preferred-pasting-file?)
  720. (auto-expand-row t auto-expand-block-refs?)
  721. (when-not (or (util/mobile?) (mobile-util/native-platform?))
  722. (shortcut-tooltip-row t enable-shortcut-tooltip?))
  723. (when-not (or (util/mobile?) (mobile-util/native-platform?))
  724. (tooltip-row t enable-tooltip?))
  725. (timetracking-row t enable-timetracking?)
  726. (enable-all-pages-public-row t enable-all-pages-public?)
  727. (auto-push-row t current-repo enable-git-auto-push?)]))
  728. (rum/defc settings-git
  729. []
  730. [:div.panel-wrap
  731. [:div.text-sm.my-4
  732. (ui/admonition
  733. :tip
  734. [:p (t :settings-page/git-tip)])
  735. [:span.text-sm.opacity-50.my-4
  736. (t :settings-page/git-desc-1)]
  737. [:br] [:br]
  738. [:span.text-sm.opacity-50.my-4
  739. (t :settings-page/git-desc-2)]
  740. [:a {:href "https://git-scm.com/" :target "_blank"}
  741. "Git"]
  742. [:span.text-sm.opacity-50.my-4
  743. (t :settings-page/git-desc-3)]]
  744. [:br]
  745. (switch-git-auto-commit-row t)
  746. (switch-git-commit-on-close-row t)
  747. (git-auto-commit-seconds t)])
  748. (rum/defc settings-advanced < rum/reactive
  749. []
  750. (let [instrument-disabled? (state/sub :instrument/disabled?)
  751. developer-mode? (state/sub [:ui/developer-mode?])
  752. https-agent-opts (state/sub [:electron/user-cfgs :settings/agent])]
  753. [:div.panel-wrap.is-advanced
  754. (when (and (or util/mac? util/win32?) (util/electron?)) (app-auto-update-row t))
  755. (usage-diagnostics-row t instrument-disabled?)
  756. (when-not (mobile-util/native-platform?) (developer-mode-row t developer-mode?))
  757. (when (util/electron?) (https-user-agent-row https-agent-opts))
  758. (when (util/electron?) (auto-chmod-row t))
  759. ;; (clear-cache-row t)
  760. ;; (ui/admonition
  761. ;; :warning
  762. ;; [:p (t :settings-page/clear-cache-warning)])
  763. ]))
  764. (rum/defc sync-enabled-switcher
  765. [enabled?]
  766. (ui/toggle enabled?
  767. (fn []
  768. (file-sync-handler/set-sync-enabled! (not enabled?)))
  769. true))
  770. (rum/defc sync-diff-merge-enabled-switcher
  771. [enabled?]
  772. (ui/toggle enabled?
  773. (fn []
  774. (file-sync-handler/set-sync-diff-merge-enabled! (not enabled?)))
  775. true))
  776. (defn sync-switcher-row [repo enabled?]
  777. (row-with-button-action
  778. (cond-> {:left-label (t :settings-page/sync)
  779. :action (sync-enabled-switcher enabled?)}
  780. (config/db-based-graph? repo)
  781. (merge {:action nil :desc "Not available yet for database graphs"}))))
  782. (defn sync-diff-merge-switcher-row [enabled?]
  783. (row-with-button-action
  784. {:left-label (str (t :settings-page/sync-diff-merge) " (Experimental!)") ;; Not included in i18n to avoid outdating translations
  785. :action (sync-diff-merge-enabled-switcher enabled?)
  786. :desc (ui/tooltip [:span.inline-flex.px-1 (svg/info)]
  787. [:div
  788. [:div (t :settings-page/sync-diff-merge-desc)]
  789. [:div (t :settings-page/sync-diff-merge-warn)]])}))
  790. (rum/defc whiteboards-enabled-switcher
  791. [enabled?]
  792. (ui/toggle enabled?
  793. (fn []
  794. (let [value (not enabled?)]
  795. (config-handler/set-config! :feature/enable-whiteboards? value)))
  796. true))
  797. (defn whiteboards-switcher-row [enabled?]
  798. (row-with-button-action
  799. {:left-label (t :settings-page/enable-whiteboards)
  800. :action (whiteboards-enabled-switcher enabled?)}))
  801. (rum/defc settings-account-usage-description [pro-account? graph-usage]
  802. (let [count-usage (count graph-usage)
  803. count-limit (if pro-account? 10 1)
  804. count-percent (js/Math.round (/ count-usage count-limit 0.01))
  805. storage-usage (->> (map :used-gbs graph-usage)
  806. (reduce + 0))
  807. storage-usage-formatted (cond
  808. (zero? storage-usage) "0.0"
  809. (< storage-usage 0.01) "Less than 0.01"
  810. :else (gstring/format "%.2f" storage-usage))
  811. ;; TODO: check logic on this. What are the rules around storage limits?
  812. ;; do we, and should we be able to, give individual users more storage?
  813. ;; should that be on a per graph or per user basis?
  814. default-storage-limit (if pro-account? 10 0.05)
  815. storage-limit (->> (range 0 count-limit)
  816. (map #(get-in graph-usage [% :limit-gbs] default-storage-limit))
  817. (reduce + 0))
  818. storage-percent (/ storage-usage storage-limit 0.01)
  819. storage-percent-formatted (gstring/format "%.1f" storage-percent)]
  820. [:div.text-sm
  821. (when pro-account?
  822. [:<>
  823. (gstring/format "%s of %s synced graphs " count-usage count-limit)
  824. [:strong.text-white (gstring/format "(%s%%)" count-percent)]
  825. ", "])
  826. (gstring/format "%sGB of %sGB total storage " storage-usage-formatted storage-limit)
  827. [:strong.text-white (gstring/format "(%s%%)" storage-percent-formatted)]]))
  828. ; storage-usage-formatted "GB of " storage-limit "GB total storage"
  829. ; [:strong.text-white " (" storage-percent-formatted "%)"]]))
  830. (rum/defc settings-account-usage-graphs [_pro-account? graph-usage]
  831. (when (< 0 (count graph-usage))
  832. [:div.grid.gap-3 {:style {:grid-template-columns (str "repeat(" (count graph-usage) ", 1fr)")}}
  833. (for [{:keys [name used-percent]} graph-usage
  834. :let [color (if (<= 100 used-percent) "bg-red-500" "bg-blue-500")]]
  835. [:div.rounded-full.w-full.h-2 {:class "bg-black/50"
  836. :tooltip name}
  837. [:div.rounded-full.h-2 {:class color
  838. :style {:width (str used-percent "%")
  839. :min-width "0.5rem"
  840. :max-width "100%"}}]])]))
  841. (rum/defc ^:large-vars/cleanup-todo settings-account < rum/reactive
  842. []
  843. (let [current-graph-uuid (state/sub-current-file-sync-graph-uuid)
  844. graph-usage (state/get-remote-graph-usage)
  845. current-graph-is-remote? ((set (map :uuid graph-usage)) current-graph-uuid)
  846. logged-in? (user-handler/logged-in?)
  847. user-info (state/get-user-info)
  848. paid-user? (#{"active" "on_trial" "cancelled"} (:LemonStatus user-info))
  849. gift-user? (some #{"pro"} (:UserGroups user-info))
  850. pro-account? (or paid-user? gift-user?)
  851. expiration-date (some-> user-info :LemonEndsAt date/parse-iso)
  852. renewal-date (some-> user-info :LemonRenewsAt date/parse-iso)
  853. has-subscribed? (some? (:LemonStatus user-info))]
  854. [:div.panel-wrap.is-features.mb-8
  855. [:div.mt-1.sm:mt-0.sm:col-span-2
  856. (cond
  857. logged-in?
  858. [:div.grid.grid-cols-3.gap-8.pt-2
  859. [:div "Current plan"]
  860. [:div.col-span-2
  861. [:div {:class "w-full bg-gray-500/10 rounded-lg p-4 flex flex-col gap-4"}
  862. [:div.flex.gap-4.items-center
  863. (if pro-account?
  864. [:div.flex-1 "Pro"]
  865. [:div.flex-1 "Free"])
  866. (cond
  867. has-subscribed?
  868. (ui/button "Manage plan" {:class "p-1 h-8 justify-center"
  869. :disabled true
  870. :icon "upload"})
  871. ; :on-click user-handler/upgrade})
  872. (not pro-account?)
  873. (ui/button "Upgrade plan" {:class "p-1 h-8 justify-center"
  874. :icon "upload"
  875. :on-click user-handler/upgrade})
  876. :else nil)]
  877. (settings-account-usage-graphs pro-account? graph-usage)
  878. (settings-account-usage-description pro-account? graph-usage)
  879. (if current-graph-is-remote?
  880. (ui/button "Deactivate syncing" {:class "p-1 h-8 justify-center"
  881. :disabled true
  882. :background "gray"
  883. :icon "cloud-off"})
  884. (ui/button "Activate syncing" {:class "p-1 h-8 justify-center"
  885. :background "blue"
  886. :icon "cloud"
  887. :on-click #(fs/maybe-onboarding-show :sync-initiate)}))]]
  888. (when has-subscribed?
  889. [:<>
  890. [:div "Billing"]
  891. [:div.col-span-2.flex.flex-col.gap-4
  892. (cond
  893. ;; If there is no expiration date, print the renewal date
  894. (and renewal-date (nil? expiration-date))
  895. [:div
  896. [:strong.font-semibold "Next billing date: "
  897. (date/get-locale-string renewal-date)]]
  898. ;; If the expiration date is in the future, word it as such
  899. (< (js/Date.) expiration-date)
  900. [:div
  901. [:strong.font-semibold "Pro plan expires on: "
  902. (date/get-locale-string expiration-date)]]
  903. ;; Otherwise, ind
  904. :else
  905. [:div
  906. [:strong.font-semibold "Pro plan expired on: "
  907. (date/get-locale-string expiration-date)]])
  908. [:div (ui/button "Open invoices" {:class "w-full h-8 p-1 justify-center"
  909. :disabled true
  910. :background "gray"
  911. :icon "receipt"})]]])
  912. [:div "Profile"]
  913. [:div.col-span-2.grid.grid-cols-2.gap-4
  914. [:div.flex.flex-col.gap-2.box-border {:class "basis-1/2"}
  915. [:label.text-sm.font-semibold "First name"]
  916. [:input.rounded.border.px-2.py-1.box-border {:class "border-blue-500 bg-black/25 w-full"}]]
  917. [:div.flex.flex-col.gap-2 {:class "basis-1/2"}
  918. [:label.text-sm.font-semibold "Last name"]
  919. [:input.rounded.border.px-2.py-1.box-border {:class "border-blue-500 bg-black/25 w-full"}]]
  920. [:div.flex-1.flex.flex-col.gap-2.col-span-2
  921. [:label.text-sm.font-semibold "Username"]
  922. [:input.rounded.border.px-2.py-1.box-border {:class "border-blue-500 bg-black/25"
  923. :value (user-handler/email)}]]]
  924. [:div "Authentication"]
  925. [:div.col-span-2
  926. [:div.grid.grid-cols-2.gap-4
  927. [:div (ui/button (t :logout) {:class "p-1 h-8 justify-center w-full"
  928. :background "gray"
  929. :icon "logout"
  930. :on-click user-handler/logout})]
  931. [:div (ui/button "Reset password" {:class "p-1 h-8 justify-center w-full"
  932. :disabled true
  933. :background "gray"
  934. :icon "key"
  935. :on-click user-handler/logout})]
  936. [:div.col-span-2 (ui/button "Delete Account" {:class "p-1 h-8 justify-center w-full"
  937. :disabled true
  938. :background "red"})]]]]
  939. (not logged-in?)
  940. [:div.grid.grid-cols-3.gap-8.pt-2
  941. [:div "Authentication"]
  942. [:div.col-span-2.flex.flex-wrap.gap-4
  943. [:div.w-full.text-white "With a Logseq account, you can access cloud-based services like Logseq Sync and alpha/beta features."]
  944. [:div.flex-1 (ui/button "Sign up" {:class "h-8 w-full text-center justify-center"
  945. :on-click (fn []
  946. (state/close-settings!)
  947. (state/pub-event! [:user/login]))})]
  948. [:div.flex-1 (ui/button (t :login) {:icon "login"
  949. :class "h-8 w-full text-center justify-center"
  950. :background "gray"
  951. :on-click (fn []
  952. (state/close-settings!)
  953. (state/pub-event! [:user/login]))})]]
  954. [:div.col-span-3.flex.flex-col.gap-4 {:class "bg-black/20 p-4 rounded-lg"}
  955. [:div.flex.w-full.items-center
  956. [:div {:class "w-1/2 text-lg"}
  957. "Discover the power of "
  958. [:strong {:class "text-white/80"} "Logseq Sync"]]
  959. [:div {:class "w-1/2 bg-gradient-to-r from-white/10 to-transparent p-3 rounded-lg flex items-center gap-2 px-5 ml-5"}
  960. [:div.w-3.h-3.rounded-full.bg-green-500]
  961. "Synced"]]
  962. [:div.flex.w-full.gap-4
  963. [:div {:class "w-1/2 bg-black/50 rounded-lg p-4 pt-10 relative flex flex-col gap-4"}
  964. [:div.absolute.top-0.left-4.bg-gray-700.uppercase.px-2.py-1.rounded-b-lg.font-bold.text-xs "Free"]
  965. [:div
  966. [:strong.text-white.text-xl.font-normal "$0"]]
  967. [:div.text-white.font-bold {:class "h-[2.5rem] "} "Get started with basic syncing"]
  968. [:ul.text-xs.list-none.m-0.flex.flex-col.gap-0.5
  969. [:li "Unlimited unsynced graphs"]
  970. [:li "1 synced graph (up to 50MB, notes only)"]
  971. [:li "No asset syncing"]
  972. [:li "Access to core Logseq features"]]]
  973. [:div {:class "w-1/2 bg-black/50 rounded-lg p-4 pt-10 relative flex flex-col gap-4"}
  974. [:div.absolute.top-0.left-4.bg-blue-700.uppercase.px-2.py-1.rounded-b-lg.font-bold.text-xs "Pro"]
  975. [:div
  976. [:strong.text-white.text-xl.font-normal "$10"]
  977. [:span.text-xs.font-base {:class "ml-0.5"} "/ month"]]
  978. [:div.text-white.font-bold {:class "h-[2.5rem]"} "Unlock advanced syncing and more"]
  979. [:ul.text-xs.list-none.m-0.flex.flex-col.gap-0.5
  980. [:li "Unlimited unsynced graphs"]
  981. [:li "10 synced graphs (up to 5GB each)"]
  982. [:li "Sync assets up to 100MB per file"]
  983. [:li "Early access to alpha/beta features"]
  984. [:li "Upcoming cloud-based features, including Logseq Publish"]]]]]])]]))
  985. (rum/defc settings-features < rum/reactive
  986. []
  987. (let [current-repo (state/get-current-repo)
  988. enable-journals? (state/enable-journals? current-repo)
  989. enable-flashcards? (state/enable-flashcards? current-repo)
  990. enable-sync? (state/enable-sync?)
  991. enable-sync-diff-merge? (state/enable-sync-diff-merge?)
  992. db-based? (config/db-based-graph? current-repo)
  993. enable-whiteboards? (state/enable-whiteboards? current-repo)
  994. logged-in? (user-handler/logged-in?)]
  995. [:div.panel-wrap.is-features.mb-8
  996. (journal-row enable-journals?)
  997. (when (not enable-journals?)
  998. [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center
  999. [:label.block.text-sm.font-medium.leading-5.opacity-70
  1000. {:for "default page"}
  1001. (t :settings-page/home-default-page)]
  1002. [:div.mt-1.sm:mt-0.sm:col-span-2
  1003. [:div.max-w-lg.rounded-md.sm:max-w-xs
  1004. [:input#home-default-page.form-input.is-small.transition.duration-150.ease-in-out
  1005. {:default-value (state/sub-default-home-page)
  1006. :on-blur update-home-page
  1007. :on-key-press (fn [e]
  1008. (when (= "Enter" (util/ekey e))
  1009. (update-home-page e)))}]]]])
  1010. (when-not db-based? (whiteboards-switcher-row enable-whiteboards?))
  1011. (when (and web-platform? config/feature-plugin-system-on?)
  1012. (plugin-system-switcher-row))
  1013. (when (util/electron?)
  1014. (http-server-switcher-row))
  1015. (flashcards-switcher-row enable-flashcards?)
  1016. (when-not db-based? (zotero-settings-row))
  1017. (when-not web-platform?
  1018. [:div.mt-1.sm:mt-0.sm:col-span-2
  1019. [:hr]
  1020. (if logged-in?
  1021. [:div
  1022. (user-handler/email)
  1023. [:p (ui/button (t :logout) {:class "p-1"
  1024. :icon "logout"
  1025. :on-click user-handler/logout})]]
  1026. [:div
  1027. (ui/button (t :login) {:class "p-1"
  1028. :icon "login"
  1029. :on-click (fn []
  1030. (state/close-settings!)
  1031. (state/pub-event! [:user/login]))})
  1032. [:p.text-sm.opacity-50 (t :settings-page/login-prompt)]])])
  1033. (when-not web-platform?
  1034. [:<>
  1035. [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center
  1036. [:label.flex.font-medium.leading-5.self-start.mt-1
  1037. (ui/icon (if logged-in? "lock-open" "lock") {:class "mr-1"}) (t :settings-page/beta-features)]]
  1038. [:div.flex.flex-col.gap-4
  1039. {:class (when-not user-handler/alpha-or-beta-user? "opacity-50 pointer-events-none cursor-not-allowed")}
  1040. (sync-switcher-row current-repo enable-sync?)
  1041. (when (and enable-sync? (not (config/db-based-graph? current-repo)))
  1042. (sync-diff-merge-switcher-row enable-sync-diff-merge?))
  1043. [:div.text-sm
  1044. (t :settings-page/sync-desc-1)
  1045. [:a.mx-1 {:href "https://blog.logseq.com/how-to-setup-and-use-logseq-sync/"
  1046. :target "_blank"}
  1047. (t :settings-page/sync-desc-2)]
  1048. (t :settings-page/sync-desc-3)]]])]))
  1049. ;; (when-not web-platform?
  1050. ;; [:<>
  1051. ;; [:hr]
  1052. ;; [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center
  1053. ;; [:label.flex.font-medium.leading-5.self-start.mt-1 (ui/icon (if logged-in? "lock-open" "lock") {:class "mr-1"}) (t :settings-page/alpha-features)]]
  1054. ;; [:div.flex.flex-col.gap-4
  1055. ;; {:class (when-not user-handler/alpha-user? "opacity-50 pointer-events-none cursor-not-allowed")}
  1056. ;; ;; features
  1057. ;; ]])
  1058. (def DEFAULT-ACTIVE-TAB-STATE (if config/ENABLE-SETTINGS-ACCOUNT-TAB [:account :account] [:general :general]))
  1059. (rum/defc settings-effect
  1060. < rum/static
  1061. [active]
  1062. (hooks/use-effect!
  1063. (fn []
  1064. (let [active (and (sequential? active) (name (first active)))
  1065. ^js ds (.-dataset js/document.body)]
  1066. (if active
  1067. (set! (.-settingsTab ds) active)
  1068. (js-delete ds "settingsTab"))
  1069. #(js-delete ds "settingsTab")))
  1070. [active])
  1071. [:<>])
  1072. (rum/defcs settings-collaboration < rum/reactive
  1073. (rum/local "" ::invite-email)
  1074. {:will-mount (fn [state]
  1075. (rtc-handler/<rtc-get-users-info)
  1076. state)}
  1077. [state]
  1078. (let [*invite-email (::invite-email state)
  1079. current-repo (state/get-current-repo)
  1080. users (get (state/sub :rtc/users-info) current-repo)]
  1081. [:div.panel-wrap.is-collaboration.mb-8
  1082. [:div.flex.flex-col.gap-2.mt-4
  1083. [:h2.opacity-50.font-medium "Members:"]
  1084. [:div.users.flex.flex-col.gap-1
  1085. (for [{user-name :user/name
  1086. user-email :user/email
  1087. graph<->user-user-type :graph<->user/user-type} users]
  1088. [:div.flex.flex-row.items-center.gap-2 {:key (str "user-" user-name)}
  1089. [:div user-name]
  1090. (when user-email [:div.opacity-50.text-sm user-email])
  1091. (when graph<->user-user-type [:div.opacity-50.text-sm (name graph<->user-user-type)])])]
  1092. [:div.flex.flex-col.gap-4.mt-4
  1093. (shui/input
  1094. {:placeholder "Email address"
  1095. :on-change #(reset! *invite-email (util/evalue %))})
  1096. (shui/button
  1097. {:on-click (fn []
  1098. (let [user-email @*invite-email
  1099. graph-uuid (ldb/get-graph-rtc-uuid (db/get-db))]
  1100. (when-not (string/blank? user-email)
  1101. (when graph-uuid
  1102. (rtc-handler/<rtc-invite-email graph-uuid user-email)))))}
  1103. "Invite")]]]))
  1104. (rum/defc settings-ai
  1105. []
  1106. (let [[model-info set-model-info] (hooks/use-state nil)
  1107. [load-model-progress set-load-model-progress] (hooks/use-state nil)
  1108. {:keys [status]} load-model-progress
  1109. repo (state/get-current-repo)
  1110. current-model (:graph-text-embedding-model-name model-info)
  1111. [webgpu? set-webgpu?] (hooks/use-state nil)]
  1112. (hooks/use-effect!
  1113. (fn []
  1114. (p/let [webgpu? (db-browser/<check-webgpu-available?)]
  1115. (set-webgpu? webgpu?)))
  1116. [])
  1117. (hooks/use-effect!
  1118. (fn []
  1119. (c.m/run-task
  1120. ::fetch-model-info
  1121. (m/reduce
  1122. (constantly nil)
  1123. (m/ap
  1124. (m/?> vector-search-flows/infer-worker-ready-flow)
  1125. (let [model-info (c.m/<? (state/<invoke-db-worker :thread-api/vec-search-embedding-model-info repo))]
  1126. (set-model-info model-info))))
  1127. :succ (constantly nil)))
  1128. [])
  1129. (hooks/use-effect!
  1130. (fn []
  1131. (c.m/run-task
  1132. ::update-load-model-progress
  1133. (m/reduce
  1134. (fn [_ v] (set-load-model-progress (walk/keywordize-keys v)))
  1135. vector-search-flows/load-model-progress-flow)
  1136. :succ (constantly nil)))
  1137. [])
  1138. [:div.panel-wrap
  1139. [:div.flex.flex-col.gap-2.mt-4
  1140. [:div.font-medium.text-muted-foreground.text-sm "Semantic search:"]
  1141. [:div.flex.flex-col.gap-2
  1142. [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-start
  1143. [:label.block.text-sm.font-medium.leading-8.opacity-70
  1144. {:for "local-embedding-model"}
  1145. "Local embedding model"]
  1146. [:div.rounded-md.sm:max-w-tss.sm:col-span-2
  1147. (if webgpu?
  1148. [:div.flex.flex-col.gap-2
  1149. (shui/select
  1150. (cond->
  1151. {:on-value-change (fn [model-name]
  1152. (c.m/run-task
  1153. ::load-model
  1154. (m/sp
  1155. (set-model-info (assoc model-info :graph-text-embedding-model-name model-name))
  1156. (c.m/<?
  1157. (state/<invoke-db-worker :thread-api/vec-search-load-model repo model-name))
  1158. (c.m/<?
  1159. (state/<invoke-db-worker :thread-api/vec-search-cancel-indexing repo))
  1160. (c.m/<?
  1161. (state/<invoke-db-worker :thread-api/vec-search-re-embedding-graph-data repo)))
  1162. :succ (constantly nil)))}
  1163. current-model
  1164. (assoc :value current-model))
  1165. (shui/select-trigger
  1166. {:class "h-8"}
  1167. (shui/select-value
  1168. {:placeholder "Select a model"}))
  1169. (shui/select-content
  1170. (shui/select-group
  1171. (for [model-name (:available-model-names model-info)]
  1172. (shui/select-item {:value model-name} model-name)))))
  1173. (when status
  1174. [:div.text-muted-foreground.text-sm
  1175. (let [{:keys [file progress loaded total]} load-model-progress]
  1176. (case status
  1177. ("progress" "download" "initiate")
  1178. (str "Downloading " file
  1179. (when progress
  1180. (util/format " %d/%dm"
  1181. (int (/ loaded 1024 1024))
  1182. (int (/ total 1024 1024)))))
  1183. "done"
  1184. (str "Downloaded " file)
  1185. "ready"
  1186. "Model is ready 🚀"
  1187. nil))])]
  1188. [:div.warning "WebGPU is not supported on this browser, please upgrade it or using another browser."])]]]]]))
  1189. (rum/defcs ^:large-vars/cleanup-todo settings
  1190. < (rum/local DEFAULT-ACTIVE-TAB-STATE ::active)
  1191. {:will-mount
  1192. (fn [state]
  1193. (state/load-app-user-cfgs)
  1194. state)
  1195. :did-mount
  1196. (fn [state]
  1197. (let [active-tab (first (:rum/args state))
  1198. *active (::active state)]
  1199. (when (keyword? active-tab)
  1200. (reset! *active [active-tab nil])))
  1201. state)
  1202. :will-unmount
  1203. (fn [state]
  1204. (state/close-settings!)
  1205. state)}
  1206. rum/reactive
  1207. [state _active-tab]
  1208. (let [current-repo (state/sub :git/current-repo)
  1209. _installed-plugins (state/sub :plugin/installed-plugins)
  1210. plugins-of-settings (and config/lsp-enabled? (seq (plugin-handler/get-enabled-plugins-if-setting-schema)))
  1211. *active (::active state)
  1212. logged-in? (user-handler/logged-in?)
  1213. db-based? (config/db-based-graph?)]
  1214. [:div#settings.cp__settings-main
  1215. (settings-effect @*active)
  1216. [:div.cp__settings-inner
  1217. [:aside.md:w-64 {:style {:min-width "10rem"}}
  1218. [:header.cp__settings-header
  1219. [:h1.cp__settings-modal-title (t :settings)]]
  1220. [:ul.settings-menu
  1221. (for [[label id text icon]
  1222. [(when config/ENABLE-SETTINGS-ACCOUNT-TAB
  1223. [:account "account" (t :settings-page/tab-account) (ui/icon "user-circle")])
  1224. [:general "general" (t :settings-page/tab-general) (ui/icon "adjustments")]
  1225. [:editor "editor" (t :settings-page/tab-editor) (ui/icon "writing")]
  1226. [:keymap "keymap" (t :settings-page/tab-keymap) (ui/icon "keyboard")]
  1227. (when db-based?
  1228. [:ai (t :settings-page/tab-ai) (t :settings-page/ai) (ui/icon "wand")])
  1229. (when (util/electron?)
  1230. [:version-control "git" (t :settings-page/tab-version-control) (ui/icon "history")])
  1231. ;; (when (util/electron?)
  1232. ;; [:assets "assets" (t :settings-page/tab-assets) (ui/icon "box")])
  1233. [:advanced "advanced" (t :settings-page/tab-advanced) (ui/icon "bulb")]
  1234. [:features "features" (t :settings-page/tab-features) (ui/icon "app-feature")]
  1235. (when logged-in?
  1236. [:collaboration "collaboration" (t :settings-page/tab-collaboration) (ui/icon "users")])
  1237. (when plugins-of-settings
  1238. [:plugins-setting "plugins" (t :settings-of-plugins) (ui/icon "puzzle")])]]
  1239. (when label
  1240. [:li.settings-menu-item
  1241. {:key text
  1242. :data-id id
  1243. :class (util/classnames [{:active (= label (first @*active))}])
  1244. :on-click (fn []
  1245. (if (= label :plugins-setting)
  1246. (state/pub-event! [:go/plugins-settings (:id (first plugins-of-settings))])
  1247. (reset! *active [label (first @*active)])))}
  1248. [:a.flex.items-center.settings-menu-link icon [:strong text]]]))]]
  1249. [:article
  1250. [:header.cp__settings-header
  1251. [:h1.cp__settings-category-title (t (keyword (str "settings-page/tab-" (name (first @*active)))))]]
  1252. (case (first @*active)
  1253. :plugins-setting
  1254. (let [label (second @*active)]
  1255. (state/pub-event! [:go/plugins-settings (:id (first plugins-of-settings))])
  1256. (reset! *active [label label])
  1257. nil)
  1258. :account
  1259. (settings-account)
  1260. :general
  1261. (settings-general current-repo)
  1262. :editor
  1263. (settings-editor current-repo)
  1264. :keymap
  1265. (shortcut/shortcut-keymap-x)
  1266. :version-control
  1267. (settings-git)
  1268. :assets
  1269. (assets/settings-content)
  1270. :advanced
  1271. (settings-advanced)
  1272. :features
  1273. (settings-features)
  1274. :collaboration
  1275. (settings-collaboration)
  1276. :ai
  1277. (settings-ai)
  1278. nil)]]]))